diff --git a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py index 4121dd48e63..6c4910269f2 100644 --- a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py +++ b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py @@ -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) diff --git a/erpnext/accounts/doctype/purchase_invoice/services/gl_composer.py b/erpnext/accounts/doctype/purchase_invoice/services/gl_composer.py index 861f76c65e4..28e26920942 100644 --- a/erpnext/accounts/doctype/purchase_invoice/services/gl_composer.py +++ b/erpnext/accounts/doctype/purchase_invoice/services/gl_composer.py @@ -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 = {}