refactor: migrate PI item/stock/provisional GL builders onto the composer

Move make_item_gl_entries, make_stock_adjustment_entry,
get_provisional_accounts, make_provisional_gl_entry, and
update_net_purchase_amount_for_linked_assets from PurchaseInvoice onto
PurchaseInvoiceGLComposer, completing the full GL builder migration.

purchase_invoice.py no longer contains any GL row-building logic;
PurchaseInvoiceGLComposer is the single authoritative source for all
PI GL entries, mirroring the SalesInvoiceGLComposer pattern.

All 12 GL characterization snapshots pass.
This commit is contained in:
Nabin Hait
2026-05-27 08:40:01 +05:30
parent 32c4b1d98a
commit 9c78c9ab7b
2 changed files with 455 additions and 458 deletions

View File

@@ -871,463 +871,6 @@ class PurchaseInvoice(BuyingController):
return 1
return 0
def make_item_gl_entries(self, gl_entries):
# item gl entries
stock_items = self.get_stock_items()
if self.update_stock and self.auto_accounting_for_stock:
inventory_account_map = self.get_inventory_account_map()
landed_cost_entries = self.get_item_account_wise_lcv_entries()
voucher_wise_stock_value = {}
if self.update_stock:
stock_ledger_entries = frappe.get_all(
"Stock Ledger Entry",
fields=["voucher_detail_no", "stock_value_difference", "warehouse"],
filters={"voucher_no": self.name, "voucher_type": self.doctype, "is_cancelled": 0},
)
for d in stock_ledger_entries:
voucher_wise_stock_value.setdefault(
(d.voucher_detail_no, d.warehouse), d.stock_value_difference
)
valuation_tax_accounts = [
d.account_head
for d in self.get("taxes")
if d.category in ("Valuation", "Valuation and Total")
and flt(d.base_tax_amount_after_discount_amount)
]
exchange_rate_map, net_rate_map = get_purchase_document_details(self)
provisional_accounting_for_non_stock_items = cint(
frappe.get_cached_value(
"Company", self.company, "enable_provisional_accounting_for_non_stock_items"
)
)
if provisional_accounting_for_non_stock_items:
self.get_provisional_accounts()
adjust_incoming_rate = frappe.db.get_single_value(
"Buying Settings", "set_landed_cost_based_on_purchase_invoice_rate"
)
for item in self.get("items"):
if flt(item.base_net_amount) or (self.get("update_stock") and item.valuation_rate):
if item.item_code:
frappe.get_cached_value("Item", item.item_code, "asset_category")
if (
self.update_stock
and self.auto_accounting_for_stock
and (item.item_code in stock_items or item.is_fixed_asset)
):
account_currency = get_account_currency(item.expense_account)
# warehouse account
warehouse_debit_amount = self.make_stock_adjustment_entry(
gl_entries, item, voucher_wise_stock_value, account_currency
)
if item.from_warehouse:
_inv_dict = self.get_inventory_account_dict(item, inventory_account_map)
_inv_dict_from_warehouse = self.get_inventory_account_dict(
item, inventory_account_map, "from_warehouse"
)
gl_entries.append(
self.get_gl_dict(
{
"account": _inv_dict["account"],
"against": _inv_dict_from_warehouse["account"],
"cost_center": item.cost_center,
"project": item.project or self.project,
"remarks": self.get("remarks") or _("Accounting Entry for Stock"),
"debit": warehouse_debit_amount,
"debit_in_transaction_currency": item.net_amount,
},
_inv_dict["account_currency"],
item=item,
)
)
credit_amount = item.base_net_amount
if self.is_internal_supplier and item.valuation_rate:
credit_amount = flt(item.valuation_rate * item.stock_qty)
# Intentionally passed negative debit amount to avoid incorrect GL Entry validation
gl_entries.append(
self.get_gl_dict(
{
"account": _inv_dict_from_warehouse["account"],
"against": _inv_dict["account"],
"cost_center": item.cost_center,
"project": item.project or self.project,
"remarks": self.get("remarks") or _("Accounting Entry for Stock"),
"debit": -1 * flt(credit_amount, item.precision("base_net_amount")),
"debit_in_transaction_currency": item.net_amount,
},
_inv_dict_from_warehouse["account_currency"],
item=item,
)
)
# Do not book expense for transfer within same company transfer
if not self.is_internal_transfer():
gl_entries.append(
self.get_gl_dict(
{
"account": item.expense_account,
"against": self.supplier,
"debit": flt(item.base_net_amount, item.precision("base_net_amount")),
"debit_in_transaction_currency": item.net_amount,
"remarks": self.get("remarks") or _("Accounting Entry for Stock"),
"cost_center": item.cost_center,
"project": item.project,
},
account_currency,
item=item,
)
)
else:
if not self.is_internal_transfer():
gl_entries.append(
self.get_gl_dict(
{
"account": item.expense_account,
"against": self.supplier,
"debit": warehouse_debit_amount,
"debit_in_transaction_currency": flt(
warehouse_debit_amount / self.conversion_rate,
item.precision("net_amount"),
),
"remarks": self.get("remarks") or _("Accounting Entry for Stock"),
"cost_center": item.cost_center,
"project": item.project or self.project,
},
account_currency,
item=item,
)
)
# Amount added through landed-cost-voucher
if landed_cost_entries:
if (item.item_code, item.name) in landed_cost_entries:
for account, base_amount in landed_cost_entries[
(item.item_code, item.name)
].items():
gl_entries.append(
self.get_gl_dict(
{
"account": account,
"against": item.expense_account,
"cost_center": item.cost_center,
"remarks": self.get("remarks") or _("Accounting Entry for Stock"),
"credit": flt(base_amount["base_amount"]),
"credit_in_account_currency": flt(base_amount["amount"]),
"credit_in_transaction_currency": item.net_amount,
"project": item.project or self.project,
},
item=item,
)
)
# sub-contracting warehouse
if flt(item.rm_supp_cost):
supplier_wh_dict = self.get_inventory_account_dict(
item, inventory_account_map, "supplier_warehouse"
)
supplier_inventory_account = supplier_wh_dict["account"]
if not supplier_inventory_account:
frappe.throw(
_("Please set account in Warehouse {0}").format(self.supplier_warehouse)
)
gl_entries.append(
self.get_gl_dict(
{
"account": supplier_inventory_account,
"against": item.expense_account,
"cost_center": item.cost_center,
"project": item.project or self.project,
"remarks": self.get("remarks") or _("Accounting Entry for Stock"),
"credit": flt(item.rm_supp_cost),
"credit_in_transaction_currency": item.net_amount,
},
supplier_wh_dict["account_currency"],
item=item,
)
)
else:
expense_account = (
item.expense_account
if (not item.enable_deferred_expense or self.is_return)
else item.deferred_expense_account
)
account_currency = get_account_currency(expense_account)
amount, base_amount = self.get_amount_and_base_amount(item, None)
if provisional_accounting_for_non_stock_items:
self.make_provisional_gl_entry(gl_entries, item)
if not self.is_internal_transfer():
gl_entries.append(
self.get_gl_dict(
{
"account": expense_account,
"against": self.supplier,
"debit": base_amount,
"debit_in_transaction_currency": amount,
"cost_center": item.cost_center,
"project": item.project or self.project,
},
account_currency,
item=item,
)
)
# check if the exchange rate has changed
if (
not adjust_incoming_rate
and item.get("purchase_receipt")
and self.auto_accounting_for_stock
):
if (
exchange_rate_map[item.purchase_receipt]
and self.conversion_rate != exchange_rate_map[item.purchase_receipt]
and item.net_rate == net_rate_map[item.pr_detail]
and item.item_code in stock_items
):
discrepancy_caused_by_exchange_rate_difference = (
item.qty * item.net_rate
) * (exchange_rate_map[item.purchase_receipt] - self.conversion_rate)
gl_entries.append(
self.get_gl_dict(
{
"account": expense_account,
"against": self.supplier,
"debit": discrepancy_caused_by_exchange_rate_difference,
"cost_center": item.cost_center,
"project": item.project or self.project,
},
account_currency,
item=item,
)
)
gl_entries.append(
self.get_gl_dict(
{
"account": self.get_company_default("exchange_gain_loss_account"),
"against": self.supplier,
"credit": discrepancy_caused_by_exchange_rate_difference,
"cost_center": item.cost_center,
"project": item.project or self.project,
},
account_currency,
item=item,
)
)
if (
self.auto_accounting_for_stock
and self.is_opening == "No"
and item.item_code in stock_items
and item.item_tax_amount
):
# Post reverse entry for Stock-Received-But-Not-Billed if it is booked in Purchase Receipt
if item.purchase_receipt and valuation_tax_accounts:
negative_expense_booked_in_pr = frappe.db.sql(
"""select name from `tabGL Entry`
where voucher_type='Purchase Receipt' and voucher_no=%s and account in %s""",
(item.purchase_receipt, valuation_tax_accounts),
)
(
self.get_company_default("asset_received_but_not_billed")
if item.is_fixed_asset
else self.stock_received_but_not_billed
)
if not negative_expense_booked_in_pr:
gl_entries.append(
self.get_gl_dict(
{
"account": self.stock_received_but_not_billed,
"against": self.supplier,
"debit": flt(item.item_tax_amount, item.precision("item_tax_amount")),
"debit_in_transaction_currency": flt(
item.item_tax_amount / self.conversion_rate,
item.precision("item_tax_amount"),
),
"remarks": self.remarks or _("Accounting Entry for Stock"),
"cost_center": self.cost_center,
"project": item.project or self.project,
},
item=item,
)
)
self.negative_expense_to_be_booked += flt(
item.item_tax_amount, item.precision("item_tax_amount")
)
if item.is_fixed_asset and item.landed_cost_voucher_amount:
self.update_net_purchase_amount_for_linked_assets(item)
def get_provisional_accounts(self):
self.provisional_accounts = frappe._dict()
linked_purchase_receipts = set([d.purchase_receipt for d in self.items if d.purchase_receipt])
if not linked_purchase_receipts:
return
pr_items = frappe.get_all(
"Purchase Receipt Item",
filters={"parent": ("in", linked_purchase_receipts)},
fields=["name", "provisional_expense_account", "qty", "base_rate", "rate"],
)
default_provisional_account = self.get_company_default("default_provisional_account")
provisional_accounts = set(
[
d.provisional_expense_account
if d.provisional_expense_account
else default_provisional_account
for d in pr_items
]
)
provisional_gl_entries = frappe.get_all(
"GL Entry",
filters={
"voucher_type": "Purchase Receipt",
"voucher_no": ("in", linked_purchase_receipts),
"account": ("in", provisional_accounts),
"is_cancelled": 0,
},
fields=["voucher_detail_no"],
)
rows_with_provisional_entries = [d.voucher_detail_no for d in provisional_gl_entries]
for item in pr_items:
self.provisional_accounts[item.name] = {
"provisional_account": item.provisional_expense_account or default_provisional_account,
"qty": item.qty,
"base_rate": item.base_rate,
"rate": item.rate,
"has_provisional_entry": item.name in rows_with_provisional_entries,
}
def make_provisional_gl_entry(self, gl_entries, item):
if item.purchase_receipt:
pr_item = self.provisional_accounts.get(item.pr_detail, {})
if pr_item.get("has_provisional_entry"):
purchase_receipt_doc = frappe.get_cached_doc("Purchase Receipt", item.purchase_receipt)
# Intentionally passing purchase invoice item to handle partial billing
purchase_receipt_doc.add_provisional_gl_entry(
item,
gl_entries,
self.posting_date,
pr_item.get("provisional_account"),
reverse=1,
item_amount=(
(min(item.qty, pr_item.get("qty")) * pr_item.get("rate"))
* purchase_receipt_doc.get("conversion_rate")
),
)
def update_net_purchase_amount_for_linked_assets(self, item):
assets = frappe.db.get_all(
"Asset",
filters={
"purchase_invoice": self.name,
"item_code": item.item_code,
"purchase_invoice_item": ("in", [item.name, ""]),
},
fields=["name", "asset_quantity"],
)
for asset in assets:
purchase_amount = flt(item.valuation_rate) * asset.asset_quantity
frappe.db.set_value(
"Asset",
asset.name,
{
"net_purchase_amount": purchase_amount,
"purchase_amount": purchase_amount,
},
)
def make_stock_adjustment_entry(self, gl_entries, item, voucher_wise_stock_value, account_currency):
net_amt_precision = item.precision("base_net_amount")
val_rate_db_precision = 6 if cint(item.precision("valuation_rate")) <= 6 else 9
warehouse_debit_amount = flt(
flt(item.valuation_rate, val_rate_db_precision) * flt(item.qty) * flt(item.conversion_factor),
net_amt_precision,
)
if self.is_return and self.update_stock and (self.is_internal_supplier or not self.return_against):
net_rate = item.base_net_amount
if item.sales_incoming_rate: # for internal transfer
net_rate = item.qty * item.sales_incoming_rate
stock_amount = net_rate + item.item_tax_amount + flt(item.landed_cost_voucher_amount)
warehouse_debit_amount = flt(
voucher_wise_stock_value.get((item.name, item.warehouse)), net_amt_precision
)
if flt(stock_amount, net_amt_precision) != flt(warehouse_debit_amount, net_amt_precision):
cost_of_goods_sold_account = self.get_company_default("default_expense_account")
stock_adjustment_amt = stock_amount - warehouse_debit_amount
gl_entries.append(
self.get_gl_dict(
{
"account": cost_of_goods_sold_account,
"against": item.expense_account,
"debit": stock_adjustment_amt,
"debit_in_transaction_currency": stock_adjustment_amt / self.conversion_rate,
"remarks": self.get("remarks") or _("Stock Adjustment"),
"cost_center": item.cost_center,
"project": item.project or self.project,
},
account_currency,
item=item,
)
)
elif (
self.update_stock
and voucher_wise_stock_value.get((item.name, item.warehouse))
and warehouse_debit_amount
!= flt(voucher_wise_stock_value.get((item.name, item.warehouse)), net_amt_precision)
):
cost_of_goods_sold_account = self.get_company_default("default_expense_account")
stock_amount = flt(voucher_wise_stock_value.get((item.name, item.warehouse)), net_amt_precision)
stock_adjustment_amt = warehouse_debit_amount - stock_amount
gl_entries.append(
self.get_gl_dict(
{
"account": cost_of_goods_sold_account,
"against": item.expense_account,
"debit": stock_adjustment_amt,
"debit_in_transaction_currency": stock_adjustment_amt / self.conversion_rate,
"remarks": self.get("remarks") or _("Stock Adjustment"),
"cost_center": item.cost_center,
"project": item.project or self.project,
},
account_currency,
item=item,
)
)
warehouse_debit_amount = stock_amount
return warehouse_debit_amount
def on_cancel(self):
check_if_return_invoice_linked_with_payment_entry(self)

View File

@@ -30,7 +30,7 @@ class PurchaseInvoiceGLComposer(BaseGLComposer):
gl_entries = []
self.make_supplier_gl_entry(gl_entries)
doc.make_item_gl_entries(gl_entries)
self.make_item_gl_entries(gl_entries)
doc.make_precision_loss_gl_entry(gl_entries)
self.make_tax_gl_entries(gl_entries)
@@ -96,6 +96,460 @@ class PurchaseInvoiceGLComposer(BaseGLComposer):
gl["remarks"] = remarks
gl_entries.append(doc.get_gl_dict(gl, doc.party_account_currency, item=doc))
def make_item_gl_entries(self, gl_entries):
from erpnext.accounts.doctype.purchase_invoice.purchase_invoice import (
get_purchase_document_details,
)
doc = self.doc
stock_items = doc.get_stock_items()
if doc.update_stock and doc.auto_accounting_for_stock:
inventory_account_map = doc.get_inventory_account_map()
landed_cost_entries = doc.get_item_account_wise_lcv_entries()
voucher_wise_stock_value = {}
if doc.update_stock:
stock_ledger_entries = frappe.get_all(
"Stock Ledger Entry",
fields=["voucher_detail_no", "stock_value_difference", "warehouse"],
filters={"voucher_no": doc.name, "voucher_type": doc.doctype, "is_cancelled": 0},
)
for d in stock_ledger_entries:
voucher_wise_stock_value.setdefault(
(d.voucher_detail_no, d.warehouse), d.stock_value_difference
)
valuation_tax_accounts = [
d.account_head
for d in doc.get("taxes")
if d.category in ("Valuation", "Valuation and Total")
and flt(d.base_tax_amount_after_discount_amount)
]
exchange_rate_map, net_rate_map = get_purchase_document_details(doc)
provisional_accounting_for_non_stock_items = cint(
frappe.get_cached_value(
"Company", doc.company, "enable_provisional_accounting_for_non_stock_items"
)
)
if provisional_accounting_for_non_stock_items:
self.get_provisional_accounts()
adjust_incoming_rate = frappe.db.get_single_value(
"Buying Settings", "set_landed_cost_based_on_purchase_invoice_rate"
)
for item in doc.get("items"):
if flt(item.base_net_amount) or (doc.get("update_stock") and item.valuation_rate):
if item.item_code:
frappe.get_cached_value("Item", item.item_code, "asset_category")
if (
doc.update_stock
and doc.auto_accounting_for_stock
and (item.item_code in stock_items or item.is_fixed_asset)
):
account_currency = get_account_currency(item.expense_account)
warehouse_debit_amount = self.make_stock_adjustment_entry(
gl_entries, item, voucher_wise_stock_value, account_currency
)
if item.from_warehouse:
_inv_dict = doc.get_inventory_account_dict(item, inventory_account_map)
_inv_dict_from_warehouse = doc.get_inventory_account_dict(
item, inventory_account_map, "from_warehouse"
)
gl_entries.append(
doc.get_gl_dict(
{
"account": _inv_dict["account"],
"against": _inv_dict_from_warehouse["account"],
"cost_center": item.cost_center,
"project": item.project or doc.project,
"remarks": doc.get("remarks") or _("Accounting Entry for Stock"),
"debit": warehouse_debit_amount,
"debit_in_transaction_currency": item.net_amount,
},
_inv_dict["account_currency"],
item=item,
)
)
credit_amount = item.base_net_amount
if doc.is_internal_supplier and item.valuation_rate:
credit_amount = flt(item.valuation_rate * item.stock_qty)
# Intentionally passed negative debit amount to avoid incorrect GL Entry validation
gl_entries.append(
doc.get_gl_dict(
{
"account": _inv_dict_from_warehouse["account"],
"against": _inv_dict["account"],
"cost_center": item.cost_center,
"project": item.project or doc.project,
"remarks": doc.get("remarks") or _("Accounting Entry for Stock"),
"debit": -1 * flt(credit_amount, item.precision("base_net_amount")),
"debit_in_transaction_currency": item.net_amount,
},
_inv_dict_from_warehouse["account_currency"],
item=item,
)
)
if not doc.is_internal_transfer():
gl_entries.append(
doc.get_gl_dict(
{
"account": item.expense_account,
"against": doc.supplier,
"debit": flt(item.base_net_amount, item.precision("base_net_amount")),
"debit_in_transaction_currency": item.net_amount,
"remarks": doc.get("remarks") or _("Accounting Entry for Stock"),
"cost_center": item.cost_center,
"project": item.project,
},
account_currency,
item=item,
)
)
else:
if not doc.is_internal_transfer():
gl_entries.append(
doc.get_gl_dict(
{
"account": item.expense_account,
"against": doc.supplier,
"debit": warehouse_debit_amount,
"debit_in_transaction_currency": flt(
warehouse_debit_amount / doc.conversion_rate,
item.precision("net_amount"),
),
"remarks": doc.get("remarks") or _("Accounting Entry for Stock"),
"cost_center": item.cost_center,
"project": item.project or doc.project,
},
account_currency,
item=item,
)
)
# Amount added through landed-cost-voucher
if landed_cost_entries:
if (item.item_code, item.name) in landed_cost_entries:
for account, base_amount in landed_cost_entries[
(item.item_code, item.name)
].items():
gl_entries.append(
doc.get_gl_dict(
{
"account": account,
"against": item.expense_account,
"cost_center": item.cost_center,
"remarks": doc.get("remarks") or _("Accounting Entry for Stock"),
"credit": flt(base_amount["base_amount"]),
"credit_in_account_currency": flt(base_amount["amount"]),
"credit_in_transaction_currency": item.net_amount,
"project": item.project or doc.project,
},
item=item,
)
)
# sub-contracting warehouse
if flt(item.rm_supp_cost):
supplier_wh_dict = doc.get_inventory_account_dict(
item, inventory_account_map, "supplier_warehouse"
)
supplier_inventory_account = supplier_wh_dict["account"]
if not supplier_inventory_account:
frappe.throw(
_("Please set account in Warehouse {0}").format(doc.supplier_warehouse)
)
gl_entries.append(
doc.get_gl_dict(
{
"account": supplier_inventory_account,
"against": item.expense_account,
"cost_center": item.cost_center,
"project": item.project or doc.project,
"remarks": doc.get("remarks") or _("Accounting Entry for Stock"),
"credit": flt(item.rm_supp_cost),
"credit_in_transaction_currency": item.net_amount,
},
supplier_wh_dict["account_currency"],
item=item,
)
)
else:
expense_account = (
item.expense_account
if (not item.enable_deferred_expense or doc.is_return)
else item.deferred_expense_account
)
account_currency = get_account_currency(expense_account)
amount, base_amount = doc.get_amount_and_base_amount(item, None)
if provisional_accounting_for_non_stock_items:
self.make_provisional_gl_entry(gl_entries, item)
if not doc.is_internal_transfer():
gl_entries.append(
doc.get_gl_dict(
{
"account": expense_account,
"against": doc.supplier,
"debit": base_amount,
"debit_in_transaction_currency": amount,
"cost_center": item.cost_center,
"project": item.project or doc.project,
},
account_currency,
item=item,
)
)
# check if the exchange rate has changed
if (
not adjust_incoming_rate
and item.get("purchase_receipt")
and doc.auto_accounting_for_stock
):
if (
exchange_rate_map[item.purchase_receipt]
and doc.conversion_rate != exchange_rate_map[item.purchase_receipt]
and item.net_rate == net_rate_map[item.pr_detail]
and item.item_code in stock_items
):
discrepancy_caused_by_exchange_rate_difference = (
item.qty * item.net_rate
) * (exchange_rate_map[item.purchase_receipt] - doc.conversion_rate)
gl_entries.append(
doc.get_gl_dict(
{
"account": expense_account,
"against": doc.supplier,
"debit": discrepancy_caused_by_exchange_rate_difference,
"cost_center": item.cost_center,
"project": item.project or doc.project,
},
account_currency,
item=item,
)
)
gl_entries.append(
doc.get_gl_dict(
{
"account": doc.get_company_default("exchange_gain_loss_account"),
"against": doc.supplier,
"credit": discrepancy_caused_by_exchange_rate_difference,
"cost_center": item.cost_center,
"project": item.project or doc.project,
},
account_currency,
item=item,
)
)
if (
doc.auto_accounting_for_stock
and doc.is_opening == "No"
and item.item_code in stock_items
and item.item_tax_amount
):
# Post reverse entry for Stock-Received-But-Not-Billed if booked in Purchase Receipt
if item.purchase_receipt and valuation_tax_accounts:
negative_expense_booked_in_pr = frappe.db.sql(
"""select name from `tabGL Entry`
where voucher_type='Purchase Receipt' and voucher_no=%s and account in %s""",
(item.purchase_receipt, valuation_tax_accounts),
)
(
doc.get_company_default("asset_received_but_not_billed")
if item.is_fixed_asset
else doc.stock_received_but_not_billed
)
if not negative_expense_booked_in_pr:
gl_entries.append(
doc.get_gl_dict(
{
"account": doc.stock_received_but_not_billed,
"against": doc.supplier,
"debit": flt(item.item_tax_amount, item.precision("item_tax_amount")),
"debit_in_transaction_currency": flt(
item.item_tax_amount / doc.conversion_rate,
item.precision("item_tax_amount"),
),
"remarks": doc.remarks or _("Accounting Entry for Stock"),
"cost_center": doc.cost_center,
"project": item.project or doc.project,
},
item=item,
)
)
doc.negative_expense_to_be_booked += flt(
item.item_tax_amount, item.precision("item_tax_amount")
)
if item.is_fixed_asset and item.landed_cost_voucher_amount:
self.update_net_purchase_amount_for_linked_assets(item)
def get_provisional_accounts(self):
doc = self.doc
self.provisional_accounts = frappe._dict()
linked_purchase_receipts = {d.purchase_receipt for d in doc.items if d.purchase_receipt}
if not linked_purchase_receipts:
return
pr_items = frappe.get_all(
"Purchase Receipt Item",
filters={"parent": ("in", linked_purchase_receipts)},
fields=["name", "provisional_expense_account", "qty", "base_rate", "rate"],
)
default_provisional_account = doc.get_company_default("default_provisional_account")
provisional_accounts = {
d.provisional_expense_account if d.provisional_expense_account else default_provisional_account
for d in pr_items
}
provisional_gl_entries = frappe.get_all(
"GL Entry",
filters={
"voucher_type": "Purchase Receipt",
"voucher_no": ("in", linked_purchase_receipts),
"account": ("in", provisional_accounts),
"is_cancelled": 0,
},
fields=["voucher_detail_no"],
)
rows_with_provisional_entries = [d.voucher_detail_no for d in provisional_gl_entries]
for item in pr_items:
self.provisional_accounts[item.name] = {
"provisional_account": item.provisional_expense_account or default_provisional_account,
"qty": item.qty,
"base_rate": item.base_rate,
"rate": item.rate,
"has_provisional_entry": item.name in rows_with_provisional_entries,
}
def make_provisional_gl_entry(self, gl_entries, item):
if item.purchase_receipt:
pr_item = self.provisional_accounts.get(item.pr_detail, {})
if pr_item.get("has_provisional_entry"):
purchase_receipt_doc = frappe.get_cached_doc("Purchase Receipt", item.purchase_receipt)
# Intentionally passing purchase invoice item to handle partial billing
purchase_receipt_doc.add_provisional_gl_entry(
item,
gl_entries,
self.doc.posting_date,
pr_item.get("provisional_account"),
reverse=1,
item_amount=(
(min(item.qty, pr_item.get("qty")) * pr_item.get("rate"))
* purchase_receipt_doc.get("conversion_rate")
),
)
def update_net_purchase_amount_for_linked_assets(self, item):
doc = self.doc
assets = frappe.db.get_all(
"Asset",
filters={
"purchase_invoice": doc.name,
"item_code": item.item_code,
"purchase_invoice_item": ("in", [item.name, ""]),
},
fields=["name", "asset_quantity"],
)
for asset in assets:
purchase_amount = flt(item.valuation_rate) * asset.asset_quantity
frappe.db.set_value(
"Asset",
asset.name,
{
"net_purchase_amount": purchase_amount,
"purchase_amount": purchase_amount,
},
)
def make_stock_adjustment_entry(self, gl_entries, item, voucher_wise_stock_value, account_currency):
doc = self.doc
net_amt_precision = item.precision("base_net_amount")
val_rate_db_precision = 6 if cint(item.precision("valuation_rate")) <= 6 else 9
warehouse_debit_amount = flt(
flt(item.valuation_rate, val_rate_db_precision) * flt(item.qty) * flt(item.conversion_factor),
net_amt_precision,
)
if doc.is_return and doc.update_stock and (doc.is_internal_supplier or not doc.return_against):
net_rate = item.base_net_amount
if item.sales_incoming_rate:
net_rate = item.qty * item.sales_incoming_rate
stock_amount = net_rate + item.item_tax_amount + flt(item.landed_cost_voucher_amount)
warehouse_debit_amount = flt(
voucher_wise_stock_value.get((item.name, item.warehouse)), net_amt_precision
)
if flt(stock_amount, net_amt_precision) != flt(warehouse_debit_amount, net_amt_precision):
cost_of_goods_sold_account = doc.get_company_default("default_expense_account")
stock_adjustment_amt = stock_amount - warehouse_debit_amount
gl_entries.append(
doc.get_gl_dict(
{
"account": cost_of_goods_sold_account,
"against": item.expense_account,
"debit": stock_adjustment_amt,
"debit_in_transaction_currency": stock_adjustment_amt / doc.conversion_rate,
"remarks": doc.get("remarks") or _("Stock Adjustment"),
"cost_center": item.cost_center,
"project": item.project or doc.project,
},
account_currency,
item=item,
)
)
elif (
doc.update_stock
and voucher_wise_stock_value.get((item.name, item.warehouse))
and warehouse_debit_amount
!= flt(voucher_wise_stock_value.get((item.name, item.warehouse)), net_amt_precision)
):
cost_of_goods_sold_account = doc.get_company_default("default_expense_account")
stock_amount = flt(voucher_wise_stock_value.get((item.name, item.warehouse)), net_amt_precision)
stock_adjustment_amt = warehouse_debit_amount - stock_amount
gl_entries.append(
doc.get_gl_dict(
{
"account": cost_of_goods_sold_account,
"against": item.expense_account,
"debit": stock_adjustment_amt,
"debit_in_transaction_currency": stock_adjustment_amt / doc.conversion_rate,
"remarks": doc.get("remarks") or _("Stock Adjustment"),
"cost_center": item.cost_center,
"project": item.project or doc.project,
},
account_currency,
item=item,
)
)
warehouse_debit_amount = stock_amount
return warehouse_debit_amount
def make_tax_gl_entries(self, gl_entries):
doc = self.doc
valuation_tax = {}