diff --git a/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py b/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py index 30afd561482..0fca30c5458 100644 --- a/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py +++ b/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py @@ -494,350 +494,11 @@ class PurchaseReceipt(BuyingController): item.amount_difference_with_purchase_invoice = 0 def get_gl_entries(self, inventory_account_map=None, via_landed_cost_voucher=False): - from erpnext.accounts.general_ledger import process_gl_map - - gl_entries = [] - - self.make_item_gl_entries(gl_entries, inventory_account_map=inventory_account_map) - self.make_tax_gl_entries(gl_entries, via_landed_cost_voucher) - self.set_gl_entry_for_purchase_expense(gl_entries) - update_regional_gl_entries(gl_entries, self) - - return process_gl_map(gl_entries, from_repost=frappe.flags.through_repost_item_valuation) - - def make_item_gl_entries(self, gl_entries, inventory_account_map=None): - from erpnext.accounts.doctype.purchase_invoice.purchase_invoice import ( - get_purchase_document_details, + from erpnext.stock.doctype.purchase_receipt.services.gl_composer import ( + PurchaseReceiptGLComposer, ) - provisional_accounting_for_non_stock_items = cint( - frappe.db.get_value("Company", self.company, "enable_provisional_accounting_for_non_stock_items") - ) - - exchange_rate_map, net_rate_map = get_purchase_document_details(self) - - def validate_account(account_type): - frappe.throw(_("{0} account not found while submitting purchase receipt").format(account_type)) - - def make_item_asset_inward_gl_entry(item, stock_value_diff, stock_asset_account_name): - account_currency = get_account_currency(stock_asset_account_name) - - if not stock_asset_account_name: - validate_account("Asset or warehouse account") - - self.add_gl_entry( - gl_entries=gl_entries, - account=stock_asset_account_name, - cost_center=d.cost_center, - debit=stock_value_diff, - credit=0.0, - remarks=remarks, - against_account=stock_asset_rbnb, - account_currency=account_currency, - item=item, - ) - - def make_stock_received_but_not_billed_entry(item): - if ( - self.get("is_return") - and item.return_qty_from_rejected_warehouse - and not frappe.db.get_single_value( - "Buying Settings", "set_valuation_rate_for_rejected_materials" - ) - ): - return 0.0 - - account = stock_asset_rbnb - if item.from_warehouse: - _inv_dict = self.get_inventory_account_dict(item, inventory_account_map, "from_warehouse") - account = _inv_dict["account"] - - account_currency = get_account_currency(account) - - # GL Entry for from warehouse or Stock Received but not billed - # Intentionally passed negative debit amount to avoid incorrect GL Entry validation - credit_amount = ( - flt(item.base_net_amount, item.precision("base_net_amount")) - if account_currency == self.company_currency - else flt(item.net_amount, item.precision("net_amount")) - ) - - outgoing_amount = item.base_net_amount - if self.is_internal_transfer() and item.valuation_rate: - outgoing_amount = abs(get_stock_value_difference(self.name, item.name, item.from_warehouse)) - credit_amount = outgoing_amount - - if item.get("rejected_qty") and frappe.db.get_single_value( - "Buying Settings", "set_valuation_rate_for_rejected_materials" - ): - outgoing_amount += get_stock_value_difference(self.name, item.name, item.rejected_warehouse) - credit_amount = outgoing_amount - - if credit_amount: - if not account: - validate_account("Stock or Asset Received But Not Billed") - - self.add_gl_entry( - gl_entries=gl_entries, - account=account, - cost_center=item.cost_center, - debit=-1 * flt(outgoing_amount, item.precision("base_net_amount")), - credit=0.0, - remarks=remarks, - against_account=stock_asset_account_name, - debit_in_account_currency=-1 * flt(outgoing_amount, item.precision("base_net_amount")), - account_currency=account_currency, - item=item, - ) - - # check if the exchange rate has changed - if d.get("purchase_invoice"): - if ( - exchange_rate_map[item.purchase_invoice] - and self.conversion_rate != exchange_rate_map[item.purchase_invoice] - and item.net_rate == net_rate_map[item.purchase_invoice_item] - ): - discrepancy_caused_by_exchange_rate_difference = (item.qty * item.net_rate) * ( - exchange_rate_map[item.purchase_invoice] - self.conversion_rate - ) - - self.add_gl_entry( - gl_entries=gl_entries, - account=account, - cost_center=item.cost_center, - debit=0.0, - credit=discrepancy_caused_by_exchange_rate_difference, - remarks=remarks, - against_account=self.supplier, - debit_in_account_currency=-1 * discrepancy_caused_by_exchange_rate_difference, - account_currency=account_currency, - item=item, - ) - - self.add_gl_entry( - gl_entries=gl_entries, - account=self.get_company_default("exchange_gain_loss_account"), - cost_center=d.cost_center, - debit=discrepancy_caused_by_exchange_rate_difference, - credit=0.0, - remarks=remarks, - against_account=self.supplier, - debit_in_account_currency=-1 * discrepancy_caused_by_exchange_rate_difference, - account_currency=account_currency, - item=item, - ) - - return outgoing_amount - - def make_landed_cost_gl_entries(item): - # Amount added through landed-cost-voucher - if item.landed_cost_voucher_amount and landed_cost_entries: - if (item.item_code, item.name) in landed_cost_entries: - for account, amount in landed_cost_entries[(item.item_code, item.name)].items(): - account_currency = get_account_currency(account) - credit_amount = ( - flt(amount["base_amount"]) - if (amount["base_amount"] or account_currency != self.company_currency) - else flt(amount["amount"]) - ) - - if not account: - validate_account("Landed Cost Account") - - self.add_gl_entry( - gl_entries=gl_entries, - account=account, - cost_center=item.cost_center, - debit=0.0, - credit=credit_amount, - remarks=remarks, - against_account=stock_asset_account_name, - credit_in_account_currency=flt(amount["amount"]), - account_currency=account_currency, - project=item.project, - item=item, - ) - - def make_amount_difference_entry(item): - if item.amount_difference_with_purchase_invoice and stock_asset_rbnb: - account_currency = get_account_currency(stock_asset_rbnb) - self.add_gl_entry( - gl_entries=gl_entries, - account=stock_asset_rbnb, - cost_center=item.cost_center, - debit=0.0, - credit=flt(item.amount_difference_with_purchase_invoice), - remarks=_("Adjustment based on Purchase Invoice rate"), - against_account=stock_asset_account_name, - account_currency=account_currency, - project=item.project, - item=item, - ) - - def make_sub_contracting_gl_entries(item): - # sub-contracting warehouse - if flt(item.rm_supp_cost) and supplier_warehouse_account: - self.add_gl_entry( - gl_entries=gl_entries, - account=supplier_warehouse_account, - cost_center=item.cost_center, - debit=0.0, - credit=flt(item.rm_supp_cost), - remarks=remarks, - against_account=stock_asset_account_name, - account_currency=supplier_warehouse_account_currency, - item=item, - ) - - def make_divisional_loss_gl_entry(item, outgoing_amount): - if item.is_fixed_asset: - return - - # divisional loss adjustment - valuation_amount_as_per_doc = ( - flt(outgoing_amount, d.precision("base_net_amount")) - + flt(item.landed_cost_voucher_amount) - + flt(item.rm_supp_cost) - + flt(item.item_tax_amount) - + flt(item.amount_difference_with_purchase_invoice) - ) - - divisional_loss = flt( - valuation_amount_as_per_doc - flt(stock_value_diff), item.precision("base_net_amount") - ) - - if item.get("rejected_qty") and frappe.db.get_single_value( - "Buying Settings", "set_valuation_rate_for_rejected_materials" - ): - rejected_item_cost = get_stock_value_difference(self.name, item.name, item.rejected_warehouse) - divisional_loss -= rejected_item_cost - - if divisional_loss: - loss_account = ( - self.get_company_default("default_expense_account", ignore_validation=True) - or stock_asset_rbnb - ) - - if self.is_return and item.expense_account: - loss_account = item.expense_account - - cost_center = item.cost_center or frappe.get_cached_value( - "Company", self.company, "cost_center" - ) - account_currency = get_account_currency(loss_account) - self.add_gl_entry( - gl_entries=gl_entries, - account=loss_account, - cost_center=cost_center, - debit=divisional_loss, - credit=0.0, - remarks=remarks, - against_account=stock_asset_account_name, - account_currency=account_currency, - project=item.project, - item=item, - ) - - stock_items = self.get_stock_items() - warehouse_with_no_account = [] - - for d in self.get("items"): - remarks = self.get("remarks") or _("Accounting Entry for {0}").format( - "Asset" if d.is_fixed_asset else "Stock" - ) - - if ( - provisional_accounting_for_non_stock_items - and d.item_code not in stock_items - and flt(d.qty) - and d.get("provisional_expense_account") - and not d.is_fixed_asset - ): - self.add_provisional_gl_entry( - d, gl_entries, self.posting_date, d.get("provisional_expense_account") - ) - elif flt(d.qty) and (flt(d.valuation_rate) or self.is_return): - if not ( - (erpnext.is_perpetual_inventory_enabled(self.company) and d.item_code in stock_items) - or (d.is_fixed_asset and not d.purchase_invoice) - ): - continue - - stock_asset_rbnb = ( - self.get_company_default("asset_received_but_not_billed") - if d.is_fixed_asset - else self.get_company_default("stock_received_but_not_billed") - ) - landed_cost_entries = self.get_item_account_wise_lcv_entries() - if d.is_fixed_asset: - stock_asset_account_name = d.expense_account - stock_value_diff = ( - flt(d.base_net_amount) + flt(d.item_tax_amount) + flt(d.landed_cost_voucher_amount) - ) - elif inventory_account := self.get_inventory_account_dict(d, inventory_account_map): - stock_value_diff = get_stock_value_difference(self.name, d.name, d.warehouse) - stock_asset_account_name = inventory_account["account"] - - supplier_warehouse_account = None - supplier_warehouse_account_currency = None - if self.supplier_warehouse: - if _inv_dict := self.get_inventory_account_dict( - d, inventory_account_map, "supplier_warehouse" - ): - supplier_warehouse_account = _inv_dict["account"] - supplier_warehouse_account_currency = _inv_dict["account_currency"] - - # If PR is sub-contracted and fg item rate is zero - # in that case if account for source and target warehouse are same, - # then GL entries should not be posted - if ( - flt(stock_value_diff) == flt(d.rm_supp_cost) - and supplier_warehouse_account - and stock_asset_account_name == supplier_warehouse_account - ): - continue - - if (flt(d.valuation_rate) or self.is_return or d.is_fixed_asset) and flt(d.qty): - make_item_asset_inward_gl_entry(d, stock_value_diff, stock_asset_account_name) - outgoing_amount = make_stock_received_but_not_billed_entry(d) - make_landed_cost_gl_entries(d) - make_amount_difference_entry(d) - make_sub_contracting_gl_entries(d) - make_divisional_loss_gl_entry(d, outgoing_amount) - elif (d.warehouse and d.qty and d.warehouse not in warehouse_with_no_account) or ( - not frappe.db.get_single_value("Buying Settings", "set_valuation_rate_for_rejected_materials") - and d.rejected_warehouse - and d.rejected_warehouse not in warehouse_with_no_account - ): - warehouse_with_no_account.append(d.warehouse or d.rejected_warehouse) - - if d.is_fixed_asset and d.landed_cost_voucher_amount: - self.update_assets(d, d.valuation_rate) - - if d.rejected_qty and frappe.db.get_single_value( - "Buying Settings", "set_valuation_rate_for_rejected_materials" - ): - stock_asset_rbnb = ( - self.get_company_default("asset_received_but_not_billed") - if d.is_fixed_asset - else self.get_company_default("stock_received_but_not_billed") - ) - - stock_value_diff = get_stock_value_difference(self.name, d.name, d.rejected_warehouse) - _inv_dict = self.get_inventory_account_dict(d, inventory_account_map, "rejected_warehouse") - - stock_asset_account_name = _inv_dict["account"] - - make_item_asset_inward_gl_entry(d, stock_value_diff, stock_asset_account_name) - if not d.qty: - make_stock_received_but_not_billed_entry(d) - - if warehouse_with_no_account: - frappe.msgprint( - _("No accounting entries for the following warehouses") - + ": \n" - + "\n".join(warehouse_with_no_account) - ) + return PurchaseReceiptGLComposer(self).compose(inventory_account_map, via_landed_cost_voucher) def add_provisional_gl_entry( self, item, gl_entries, posting_date, provisional_account, reverse=0, item_amount=None @@ -894,57 +555,6 @@ class PurchaseReceipt(BuyingController): return False - def make_tax_gl_entries(self, gl_entries, via_landed_cost_voucher=False): - negative_expense_to_be_booked = sum([flt(d.item_tax_amount) for d in self.get("items")]) - # Cost center-wise amount breakup for other charges included for valuation - valuation_tax = {} - for tax in self.get("taxes"): - if tax.category in ("Valuation", "Valuation and Total") and flt( - tax.base_tax_amount_after_discount_amount - ): - if not tax.cost_center: - frappe.throw( - _("Cost Center is required in row {0} in Taxes table for type {1}").format( - tax.idx, _(tax.category) - ) - ) - valuation_tax.setdefault(tax.name, 0) - valuation_tax[tax.name] += (tax.add_deduct_tax == "Add" and 1 or -1) * flt( - tax.base_tax_amount_after_discount_amount - ) - - if negative_expense_to_be_booked and valuation_tax: - # Backward compatibility: - # and charges added via Landed Cost Voucher, - # post valuation related charges on "Stock Received But Not Billed" - against_accounts = ", ".join([d.account for d in gl_entries if flt(d.debit) > 0]) - total_valuation_amount = sum(valuation_tax.values()) - amount_including_divisional_loss = negative_expense_to_be_booked - i = 1 - for tax in self.get("taxes"): - if valuation_tax.get(tax.name): - account = tax.account_head - if i == len(valuation_tax): - applicable_amount = amount_including_divisional_loss - else: - applicable_amount = negative_expense_to_be_booked * ( - valuation_tax[tax.name] / total_valuation_amount - ) - amount_including_divisional_loss -= applicable_amount - - self.add_gl_entry( - gl_entries=gl_entries, - account=account, - cost_center=tax.cost_center, - debit=0.0, - credit=applicable_amount, - remarks=self.remarks or _("Accounting Entry for Stock"), - against_account=against_accounts, - item=tax, - ) - - i += 1 - def update_assets(self, item, valuation_rate): assets = frappe.db.get_all( "Asset", diff --git a/erpnext/stock/doctype/purchase_receipt/services/__init__.py b/erpnext/stock/doctype/purchase_receipt/services/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/erpnext/stock/doctype/purchase_receipt/services/gl_composer.py b/erpnext/stock/doctype/purchase_receipt/services/gl_composer.py new file mode 100644 index 00000000000..20347583bb1 --- /dev/null +++ b/erpnext/stock/doctype/purchase_receipt/services/gl_composer.py @@ -0,0 +1,408 @@ +# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors +# License: GNU General Public License v3. See license.txt + +import frappe +from frappe import _ +from frappe.utils import cint, flt + +import erpnext +from erpnext.accounts.general_ledger import process_gl_map +from erpnext.accounts.utils import get_account_currency +from erpnext.stock.services.base_stock_gl_composer import BaseStockGLComposer + + +class PurchaseReceiptGLComposer(BaseStockGLComposer): + """GL composer for Purchase Receipt. + + Builds GL entries for stock/asset inward, taxes, purchase expense, and + regional adjustments. Does not delegate to the base stock GL loop — + PR has its own per-item logic (provisional accounting, fixed assets, LCV, + sub-contracting, divisional loss). + """ + + def compose( + self, + inventory_account_map: dict | None = None, + via_landed_cost_voucher: bool = False, + ) -> list: + gl_entries = [] + self._make_item_gl_entries(gl_entries, inventory_account_map) + self._make_tax_gl_entries(gl_entries, via_landed_cost_voucher) + self.doc.set_gl_entry_for_purchase_expense(gl_entries) + + from erpnext.stock.doctype.purchase_receipt.purchase_receipt import update_regional_gl_entries + + update_regional_gl_entries(gl_entries, self.doc) + + return process_gl_map(gl_entries, from_repost=frappe.flags.through_repost_item_valuation) + + def _make_item_gl_entries(self, gl_entries: list, inventory_account_map: dict | None) -> None: + from erpnext.accounts.doctype.purchase_invoice.purchase_invoice import ( + get_purchase_document_details, + ) + from erpnext.stock.doctype.purchase_receipt.purchase_receipt import get_stock_value_difference + + doc = self.doc + provisional_accounting_for_non_stock_items = cint( + frappe.db.get_value("Company", doc.company, "enable_provisional_accounting_for_non_stock_items") + ) + + exchange_rate_map, net_rate_map = get_purchase_document_details(doc) + stock_items = doc.get_stock_items() + warehouse_with_no_account = [] + + def validate_account(account_type): + frappe.throw(_("{0} account not found while submitting purchase receipt").format(account_type)) + + def make_item_asset_inward_gl_entry(item, stock_value_diff, stock_asset_account_name): + account_currency = get_account_currency(stock_asset_account_name) + if not stock_asset_account_name: + validate_account("Asset or warehouse account") + doc.add_gl_entry( + gl_entries=gl_entries, + account=stock_asset_account_name, + cost_center=d.cost_center, + debit=stock_value_diff, + credit=0.0, + remarks=remarks, + against_account=stock_asset_rbnb, + account_currency=account_currency, + item=item, + ) + + def make_stock_received_but_not_billed_entry(item): + if ( + doc.get("is_return") + and item.return_qty_from_rejected_warehouse + and not frappe.db.get_single_value( + "Buying Settings", "set_valuation_rate_for_rejected_materials" + ) + ): + return 0.0 + + account = stock_asset_rbnb + if item.from_warehouse: + _inv_dict = doc.get_inventory_account_dict(item, inventory_account_map, "from_warehouse") + account = _inv_dict["account"] + + account_currency = get_account_currency(account) + + credit_amount = ( + flt(item.base_net_amount, item.precision("base_net_amount")) + if account_currency == doc.company_currency + else flt(item.net_amount, item.precision("net_amount")) + ) + + outgoing_amount = item.base_net_amount + if doc.is_internal_transfer() and item.valuation_rate: + outgoing_amount = abs(get_stock_value_difference(doc.name, item.name, item.from_warehouse)) + credit_amount = outgoing_amount + + if item.get("rejected_qty") and frappe.db.get_single_value( + "Buying Settings", "set_valuation_rate_for_rejected_materials" + ): + outgoing_amount += get_stock_value_difference(doc.name, item.name, item.rejected_warehouse) + credit_amount = outgoing_amount + + if credit_amount: + if not account: + validate_account("Stock or Asset Received But Not Billed") + + doc.add_gl_entry( + gl_entries=gl_entries, + account=account, + cost_center=item.cost_center, + debit=-1 * flt(outgoing_amount, item.precision("base_net_amount")), + credit=0.0, + remarks=remarks, + against_account=stock_asset_account_name, + debit_in_account_currency=-1 * flt(outgoing_amount, item.precision("base_net_amount")), + account_currency=account_currency, + item=item, + ) + + if d.get("purchase_invoice"): + if ( + exchange_rate_map[item.purchase_invoice] + and doc.conversion_rate != exchange_rate_map[item.purchase_invoice] + and item.net_rate == net_rate_map[item.purchase_invoice_item] + ): + discrepancy_caused_by_exchange_rate_difference = (item.qty * item.net_rate) * ( + exchange_rate_map[item.purchase_invoice] - doc.conversion_rate + ) + + doc.add_gl_entry( + gl_entries=gl_entries, + account=account, + cost_center=item.cost_center, + debit=0.0, + credit=discrepancy_caused_by_exchange_rate_difference, + remarks=remarks, + against_account=doc.supplier, + debit_in_account_currency=-1 * discrepancy_caused_by_exchange_rate_difference, + account_currency=account_currency, + item=item, + ) + + doc.add_gl_entry( + gl_entries=gl_entries, + account=doc.get_company_default("exchange_gain_loss_account"), + cost_center=d.cost_center, + debit=discrepancy_caused_by_exchange_rate_difference, + credit=0.0, + remarks=remarks, + against_account=doc.supplier, + debit_in_account_currency=-1 * discrepancy_caused_by_exchange_rate_difference, + account_currency=account_currency, + item=item, + ) + + return outgoing_amount + + def make_landed_cost_gl_entries(item): + if item.landed_cost_voucher_amount and landed_cost_entries: + if (item.item_code, item.name) in landed_cost_entries: + for account, amount in landed_cost_entries[(item.item_code, item.name)].items(): + account_currency = get_account_currency(account) + credit_amount = ( + flt(amount["base_amount"]) + if (amount["base_amount"] or account_currency != doc.company_currency) + else flt(amount["amount"]) + ) + + if not account: + validate_account("Landed Cost Account") + + doc.add_gl_entry( + gl_entries=gl_entries, + account=account, + cost_center=item.cost_center, + debit=0.0, + credit=credit_amount, + remarks=remarks, + against_account=stock_asset_account_name, + credit_in_account_currency=flt(amount["amount"]), + account_currency=account_currency, + project=item.project, + item=item, + ) + + def make_amount_difference_entry(item): + if item.amount_difference_with_purchase_invoice and stock_asset_rbnb: + account_currency = get_account_currency(stock_asset_rbnb) + doc.add_gl_entry( + gl_entries=gl_entries, + account=stock_asset_rbnb, + cost_center=item.cost_center, + debit=0.0, + credit=flt(item.amount_difference_with_purchase_invoice), + remarks=_("Adjustment based on Purchase Invoice rate"), + against_account=stock_asset_account_name, + account_currency=account_currency, + project=item.project, + item=item, + ) + + def make_sub_contracting_gl_entries(item): + if flt(item.rm_supp_cost) and supplier_warehouse_account: + doc.add_gl_entry( + gl_entries=gl_entries, + account=supplier_warehouse_account, + cost_center=item.cost_center, + debit=0.0, + credit=flt(item.rm_supp_cost), + remarks=remarks, + against_account=stock_asset_account_name, + account_currency=supplier_warehouse_account_currency, + item=item, + ) + + def make_divisional_loss_gl_entry(item, outgoing_amount): + if item.is_fixed_asset: + return + + valuation_amount_as_per_doc = ( + flt(outgoing_amount, d.precision("base_net_amount")) + + flt(item.landed_cost_voucher_amount) + + flt(item.rm_supp_cost) + + flt(item.item_tax_amount) + + flt(item.amount_difference_with_purchase_invoice) + ) + + divisional_loss = flt( + valuation_amount_as_per_doc - flt(stock_value_diff), item.precision("base_net_amount") + ) + + if item.get("rejected_qty") and frappe.db.get_single_value( + "Buying Settings", "set_valuation_rate_for_rejected_materials" + ): + rejected_item_cost = get_stock_value_difference(doc.name, item.name, item.rejected_warehouse) + divisional_loss -= rejected_item_cost + + if divisional_loss: + loss_account = ( + doc.get_company_default("default_expense_account", ignore_validation=True) + or stock_asset_rbnb + ) + + if doc.is_return and item.expense_account: + loss_account = item.expense_account + + cost_center = item.cost_center or frappe.get_cached_value( + "Company", doc.company, "cost_center" + ) + account_currency = get_account_currency(loss_account) + doc.add_gl_entry( + gl_entries=gl_entries, + account=loss_account, + cost_center=cost_center, + debit=divisional_loss, + credit=0.0, + remarks=remarks, + against_account=stock_asset_account_name, + account_currency=account_currency, + project=item.project, + item=item, + ) + + for d in doc.get("items"): + remarks = doc.get("remarks") or _("Accounting Entry for {0}").format( + "Asset" if d.is_fixed_asset else "Stock" + ) + + if ( + provisional_accounting_for_non_stock_items + and d.item_code not in stock_items + and flt(d.qty) + and d.get("provisional_expense_account") + and not d.is_fixed_asset + ): + doc.add_provisional_gl_entry( + d, gl_entries, doc.posting_date, d.get("provisional_expense_account") + ) + elif flt(d.qty) and (flt(d.valuation_rate) or doc.is_return): + if not ( + (erpnext.is_perpetual_inventory_enabled(doc.company) and d.item_code in stock_items) + or (d.is_fixed_asset and not d.purchase_invoice) + ): + continue + + stock_asset_rbnb = ( + doc.get_company_default("asset_received_but_not_billed") + if d.is_fixed_asset + else doc.get_company_default("stock_received_but_not_billed") + ) + landed_cost_entries = doc.get_item_account_wise_lcv_entries() + if d.is_fixed_asset: + stock_asset_account_name = d.expense_account + stock_value_diff = ( + flt(d.base_net_amount) + flt(d.item_tax_amount) + flt(d.landed_cost_voucher_amount) + ) + elif inventory_account := doc.get_inventory_account_dict(d, inventory_account_map): + stock_value_diff = get_stock_value_difference(doc.name, d.name, d.warehouse) + stock_asset_account_name = inventory_account["account"] + + supplier_warehouse_account = None + supplier_warehouse_account_currency = None + if doc.supplier_warehouse: + if _inv_dict := doc.get_inventory_account_dict( + d, inventory_account_map, "supplier_warehouse" + ): + supplier_warehouse_account = _inv_dict["account"] + supplier_warehouse_account_currency = _inv_dict["account_currency"] + + if ( + flt(stock_value_diff) == flt(d.rm_supp_cost) + and supplier_warehouse_account + and stock_asset_account_name == supplier_warehouse_account + ): + continue + + if (flt(d.valuation_rate) or doc.is_return or d.is_fixed_asset) and flt(d.qty): + make_item_asset_inward_gl_entry(d, stock_value_diff, stock_asset_account_name) + outgoing_amount = make_stock_received_but_not_billed_entry(d) + make_landed_cost_gl_entries(d) + make_amount_difference_entry(d) + make_sub_contracting_gl_entries(d) + make_divisional_loss_gl_entry(d, outgoing_amount) + elif (d.warehouse and d.qty and d.warehouse not in warehouse_with_no_account) or ( + not frappe.db.get_single_value("Buying Settings", "set_valuation_rate_for_rejected_materials") + and d.rejected_warehouse + and d.rejected_warehouse not in warehouse_with_no_account + ): + warehouse_with_no_account.append(d.warehouse or d.rejected_warehouse) + + if d.is_fixed_asset and d.landed_cost_voucher_amount: + doc.update_assets(d, d.valuation_rate) + + if d.rejected_qty and frappe.db.get_single_value( + "Buying Settings", "set_valuation_rate_for_rejected_materials" + ): + stock_asset_rbnb = ( + doc.get_company_default("asset_received_but_not_billed") + if d.is_fixed_asset + else doc.get_company_default("stock_received_but_not_billed") + ) + + stock_value_diff = get_stock_value_difference(doc.name, d.name, d.rejected_warehouse) + _inv_dict = doc.get_inventory_account_dict(d, inventory_account_map, "rejected_warehouse") + stock_asset_account_name = _inv_dict["account"] + + make_item_asset_inward_gl_entry(d, stock_value_diff, stock_asset_account_name) + if not d.qty: + make_stock_received_but_not_billed_entry(d) + + if warehouse_with_no_account: + frappe.msgprint( + _("No accounting entries for the following warehouses") + + ": \n" + + "\n".join(warehouse_with_no_account) + ) + + def _make_tax_gl_entries(self, gl_entries: list, via_landed_cost_voucher: bool = False) -> None: + doc = self.doc + negative_expense_to_be_booked = sum([flt(d.item_tax_amount) for d in doc.get("items")]) + valuation_tax = {} + for tax in doc.get("taxes"): + if tax.category in ("Valuation", "Valuation and Total") and flt( + tax.base_tax_amount_after_discount_amount + ): + if not tax.cost_center: + frappe.throw( + _("Cost Center is required in row {0} in Taxes table for type {1}").format( + tax.idx, _(tax.category) + ) + ) + valuation_tax.setdefault(tax.name, 0) + valuation_tax[tax.name] += (tax.add_deduct_tax == "Add" and 1 or -1) * flt( + tax.base_tax_amount_after_discount_amount + ) + + if negative_expense_to_be_booked and valuation_tax: + against_accounts = ", ".join([d.account for d in gl_entries if flt(d.debit) > 0]) + total_valuation_amount = sum(valuation_tax.values()) + amount_including_divisional_loss = negative_expense_to_be_booked + i = 1 + for tax in doc.get("taxes"): + if valuation_tax.get(tax.name): + account = tax.account_head + if i == len(valuation_tax): + applicable_amount = amount_including_divisional_loss + else: + applicable_amount = negative_expense_to_be_booked * ( + valuation_tax[tax.name] / total_valuation_amount + ) + amount_including_divisional_loss -= applicable_amount + + doc.add_gl_entry( + gl_entries=gl_entries, + account=account, + cost_center=tax.cost_center, + debit=0.0, + credit=applicable_amount, + remarks=doc.remarks or _("Accounting Entry for Stock"), + against_account=against_accounts, + item=tax, + ) + + i += 1