diff --git a/erpnext/accounts/doctype/journal_entry/services/gl_composer.py b/erpnext/accounts/doctype/journal_entry/services/gl_composer.py index a8def33e141..16b78eae3b9 100644 --- a/erpnext/accounts/doctype/journal_entry/services/gl_composer.py +++ b/erpnext/accounts/doctype/journal_entry/services/gl_composer.py @@ -14,7 +14,7 @@ class JournalEntryGLComposer(BaseGLComposer): A Journal Entry already carries its ledger rows in the ``accounts`` child table, so composing is a straight projection of those rows into GL dicts - via ``self.doc.get_gl_dict``. The transaction currency/rate are resolved + via ``self.get_gl_dict``. The transaction currency/rate are resolved from the first foreign-currency row (mirroring the former build_gl_map). """ @@ -95,7 +95,7 @@ class JournalEntryGLComposer(BaseGLComposer): frappe.flags.party_not_required = True gl_map.append( - doc.get_gl_dict( + self.get_gl_dict( row, item=d, ) diff --git a/erpnext/accounts/doctype/payment_entry/services/gl_composer.py b/erpnext/accounts/doctype/payment_entry/services/gl_composer.py index 8e13bab3ede..28af09ec309 100644 --- a/erpnext/accounts/doctype/payment_entry/services/gl_composer.py +++ b/erpnext/accounts/doctype/payment_entry/services/gl_composer.py @@ -49,7 +49,7 @@ class PaymentEntryGLComposer(BaseGLComposer): party_account_type = frappe.db.get_value("Party Type", doc.party_type, "account_type") - party_gl_dict = doc.get_gl_dict( + party_gl_dict = self.get_gl_dict( { "account": doc.party_account, "party_type": doc.party_type, @@ -84,7 +84,7 @@ class PaymentEntryGLComposer(BaseGLComposer): dr_or_cr = "debit" if dr_or_cr == "credit" else "credit" gle.update( - doc.get_gl_dict( + self.get_gl_dict( { "account": doc.party_account, "party_type": doc.party_type, @@ -137,7 +137,7 @@ class PaymentEntryGLComposer(BaseGLComposer): gle = party_gl_dict.copy() gle.update( - doc.get_gl_dict( + self.get_gl_dict( { "account": doc.party_account, "party_type": doc.party_type, @@ -167,7 +167,7 @@ class PaymentEntryGLComposer(BaseGLComposer): doc = self.doc if doc.payment_type in ("Pay", "Internal Transfer"): gl_entries.append( - doc.get_gl_dict( + self.get_gl_dict( { "account": doc.paid_from, "account_currency": doc.paid_from_account_currency, @@ -185,7 +185,7 @@ class PaymentEntryGLComposer(BaseGLComposer): ) if doc.payment_type in ("Receive", "Internal Transfer"): gl_entries.append( - doc.get_gl_dict( + self.get_gl_dict( { "account": doc.paid_to, "account_currency": doc.paid_to_account_currency, @@ -222,7 +222,7 @@ class PaymentEntryGLComposer(BaseGLComposer): base_tax_amount = d.base_tax_amount gl_entries.append( - doc.get_gl_dict( + self.get_gl_dict( { "account": d.account_head, "against": against, @@ -249,7 +249,7 @@ class PaymentEntryGLComposer(BaseGLComposer): base_tax_amount = flt((tax_amount / exchange_rate), doc.precision("paid_amount")) gl_entries.append( - doc.get_gl_dict( + self.get_gl_dict( { "account": payment_account, "against": against, @@ -278,7 +278,7 @@ class PaymentEntryGLComposer(BaseGLComposer): frappe.throw(_("Currency for {0} must be {1}").format(d.account, doc.company_currency)) gl_entries.append( - doc.get_gl_dict( + self.get_gl_dict( { "account": d.account, "account_currency": account_currency, diff --git a/erpnext/accounts/doctype/purchase_invoice/services/gl_composer.py b/erpnext/accounts/doctype/purchase_invoice/services/gl_composer.py index 28e26920942..8329cfac53d 100644 --- a/erpnext/accounts/doctype/purchase_invoice/services/gl_composer.py +++ b/erpnext/accounts/doctype/purchase_invoice/services/gl_composer.py @@ -94,7 +94,7 @@ class PurchaseInvoiceGLComposer(BaseGLComposer): } if remarks: gl["remarks"] = remarks - gl_entries.append(doc.get_gl_dict(gl, doc.party_account_currency, item=doc)) + gl_entries.append(self.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 ( @@ -163,7 +163,7 @@ class PurchaseInvoiceGLComposer(BaseGLComposer): ) gl_entries.append( - doc.get_gl_dict( + self.get_gl_dict( { "account": _inv_dict["account"], "against": _inv_dict_from_warehouse["account"], @@ -184,7 +184,7 @@ class PurchaseInvoiceGLComposer(BaseGLComposer): # Intentionally passed negative debit amount to avoid incorrect GL Entry validation gl_entries.append( - doc.get_gl_dict( + self.get_gl_dict( { "account": _inv_dict_from_warehouse["account"], "against": _inv_dict["account"], @@ -201,7 +201,7 @@ class PurchaseInvoiceGLComposer(BaseGLComposer): if not doc.is_internal_transfer(): gl_entries.append( - doc.get_gl_dict( + self.get_gl_dict( { "account": item.expense_account, "against": doc.supplier, @@ -219,7 +219,7 @@ class PurchaseInvoiceGLComposer(BaseGLComposer): else: if not doc.is_internal_transfer(): gl_entries.append( - doc.get_gl_dict( + self.get_gl_dict( { "account": item.expense_account, "against": doc.supplier, @@ -244,7 +244,7 @@ class PurchaseInvoiceGLComposer(BaseGLComposer): (item.item_code, item.name) ].items(): gl_entries.append( - doc.get_gl_dict( + self.get_gl_dict( { "account": account, "against": item.expense_account, @@ -270,7 +270,7 @@ class PurchaseInvoiceGLComposer(BaseGLComposer): _("Please set account in Warehouse {0}").format(doc.supplier_warehouse) ) gl_entries.append( - doc.get_gl_dict( + self.get_gl_dict( { "account": supplier_inventory_account, "against": item.expense_account, @@ -299,7 +299,7 @@ class PurchaseInvoiceGLComposer(BaseGLComposer): if not doc.is_internal_transfer(): gl_entries.append( - doc.get_gl_dict( + self.get_gl_dict( { "account": expense_account, "against": doc.supplier, @@ -330,7 +330,7 @@ class PurchaseInvoiceGLComposer(BaseGLComposer): ) * (exchange_rate_map[item.purchase_receipt] - doc.conversion_rate) gl_entries.append( - doc.get_gl_dict( + self.get_gl_dict( { "account": expense_account, "against": doc.supplier, @@ -343,7 +343,7 @@ class PurchaseInvoiceGLComposer(BaseGLComposer): ) ) gl_entries.append( - doc.get_gl_dict( + self.get_gl_dict( { "account": doc.get_company_default("exchange_gain_loss_account"), "against": doc.supplier, @@ -378,7 +378,7 @@ class PurchaseInvoiceGLComposer(BaseGLComposer): if not negative_expense_booked_in_pr: gl_entries.append( - doc.get_gl_dict( + self.get_gl_dict( { "account": doc.stock_received_but_not_billed, "against": doc.supplier, @@ -505,7 +505,7 @@ class PurchaseInvoiceGLComposer(BaseGLComposer): stock_adjustment_amt = stock_amount - warehouse_debit_amount gl_entries.append( - doc.get_gl_dict( + self.get_gl_dict( { "account": cost_of_goods_sold_account, "against": item.expense_account, @@ -531,7 +531,7 @@ class PurchaseInvoiceGLComposer(BaseGLComposer): stock_adjustment_amt = warehouse_debit_amount - stock_amount gl_entries.append( - doc.get_gl_dict( + self.get_gl_dict( { "account": cost_of_goods_sold_account, "against": item.expense_account, @@ -560,7 +560,7 @@ class PurchaseInvoiceGLComposer(BaseGLComposer): account_currency = get_account_currency(tax.account_head) dr_or_cr = "debit" if tax.add_deduct_tax == "Add" else "credit" gl_entries.append( - doc.get_gl_dict( + self.get_gl_dict( { "account": tax.account_head, "against": doc.supplier, @@ -606,7 +606,7 @@ class PurchaseInvoiceGLComposer(BaseGLComposer): amount_including_divisional_loss -= applicable_amount gl_entries.append( - doc.get_gl_dict( + self.get_gl_dict( { "account": tax.account_head, "cost_center": tax.cost_center, @@ -627,7 +627,7 @@ class PurchaseInvoiceGLComposer(BaseGLComposer): for tax in doc.get("taxes"): if valuation_tax.get(tax.name): gl_entries.append( - doc.get_gl_dict( + self.get_gl_dict( { "account": tax.account_head, "cost_center": tax.cost_center, @@ -648,7 +648,7 @@ class PurchaseInvoiceGLComposer(BaseGLComposer): if doc.is_internal_transfer() and flt(doc.base_total_taxes_and_charges): account_currency = get_account_currency(doc.unrealized_profit_loss_account) gl_entries.append( - doc.get_gl_dict( + self.get_gl_dict( { "account": doc.unrealized_profit_loss_account, "against": doc.supplier, @@ -691,7 +691,7 @@ class PurchaseInvoiceGLComposer(BaseGLComposer): bank_account_currency = get_account_currency(doc.cash_bank_account) gl_entries.append( - doc.get_gl_dict( + self.get_gl_dict( { "account": doc.credit_to, "party_type": "Supplier", @@ -715,7 +715,7 @@ class PurchaseInvoiceGLComposer(BaseGLComposer): ) gl_entries.append( - doc.get_gl_dict( + self.get_gl_dict( { "account": doc.cash_bank_account, "against": doc.supplier, @@ -737,7 +737,7 @@ class PurchaseInvoiceGLComposer(BaseGLComposer): write_off_account_currency = get_account_currency(doc.write_off_account) gl_entries.append( - doc.get_gl_dict( + self.get_gl_dict( { "account": doc.credit_to, "party_type": "Supplier", @@ -760,7 +760,7 @@ class PurchaseInvoiceGLComposer(BaseGLComposer): ) ) gl_entries.append( - doc.get_gl_dict( + self.get_gl_dict( { "account": doc.write_off_account, "against": doc.supplier, @@ -802,7 +802,7 @@ class PurchaseInvoiceGLComposer(BaseGLComposer): round_off_account = round_off_for_opening gl_entries.append( - doc.get_gl_dict( + self.get_gl_dict( { "account": round_off_account, "against": doc.supplier, diff --git a/erpnext/accounts/doctype/sales_invoice/services/gl_composer.py b/erpnext/accounts/doctype/sales_invoice/services/gl_composer.py index 21c00d28da2..24da512a732 100644 --- a/erpnext/accounts/doctype/sales_invoice/services/gl_composer.py +++ b/erpnext/accounts/doctype/sales_invoice/services/gl_composer.py @@ -110,7 +110,7 @@ class SalesInvoiceGLComposer(BaseGLComposer): item_account_currency = get_account_currency(item.expense_account) gl_entries.append( - doc.get_gl_dict( + self.get_gl_dict( { "account": dn_expense_account, "against": item.expense_account, @@ -123,7 +123,7 @@ class SalesInvoiceGLComposer(BaseGLComposer): ) ) gl_entries.append( - doc.get_gl_dict( + self.get_gl_dict( { "account": item.expense_account, "against": dn_expense_account, @@ -157,7 +157,7 @@ class SalesInvoiceGLComposer(BaseGLComposer): # Did not use base_grand_total to book rounding loss gle gl_entries.append( - doc.get_gl_dict( + self.get_gl_dict( { "account": doc.debit_to, "party_type": "Customer", @@ -191,7 +191,7 @@ class SalesInvoiceGLComposer(BaseGLComposer): if flt(tax.base_tax_amount_after_discount_amount): account_currency = get_account_currency(tax.account_head) gl_entries.append( - doc.get_gl_dict( + self.get_gl_dict( { "account": tax.account_head, "against": doc.customer, @@ -216,7 +216,7 @@ class SalesInvoiceGLComposer(BaseGLComposer): if doc.is_internal_transfer() and flt(doc.base_total_taxes_and_charges): account_currency = get_account_currency(doc.unrealized_profit_loss_account) gl_entries.append( - doc.get_gl_dict( + self.get_gl_dict( { "account": doc.unrealized_profit_loss_account, "against": doc.customer, @@ -262,7 +262,7 @@ class SalesInvoiceGLComposer(BaseGLComposer): account_currency = get_account_currency(income_account) gl_entries.append( - doc.get_gl_dict( + self.get_gl_dict( { "account": income_account, "against": doc.customer, @@ -310,13 +310,13 @@ class SalesInvoiceGLComposer(BaseGLComposer): for gle in fixed_asset_gl_entries: gle["against"] = doc.customer - gl_entries.append(doc.get_gl_dict(gle, item=item)) + gl_entries.append(self.get_gl_dict(gle, item=item)) def make_loyalty_point_redemption_gle(self, gl_entries): doc = self.doc if cint(doc.redeem_loyalty_points and doc.loyalty_points and not doc.is_consolidated): gl_entries.append( - doc.get_gl_dict( + self.get_gl_dict( { "account": doc.debit_to, "party_type": "Customer", @@ -334,7 +334,7 @@ class SalesInvoiceGLComposer(BaseGLComposer): ) ) gl_entries.append( - doc.get_gl_dict( + self.get_gl_dict( { "account": doc.loyalty_redemption_account, "cost_center": doc.cost_center or doc.loyalty_redemption_cost_center, @@ -365,7 +365,7 @@ class SalesInvoiceGLComposer(BaseGLComposer): if payment_mode.base_amount: # POS, make payment entries gl_entries.append( - doc.get_gl_dict( + self.get_gl_dict( { "account": doc.debit_to, "party_type": "Customer", @@ -387,7 +387,7 @@ class SalesInvoiceGLComposer(BaseGLComposer): payment_mode_account_currency = get_account_currency(payment_mode.account) gl_entries.append( - doc.get_gl_dict( + self.get_gl_dict( { "account": payment_mode.account, "against": doc.customer, @@ -415,7 +415,7 @@ class SalesInvoiceGLComposer(BaseGLComposer): frappe.throw(_("Please set Account for Change Amount"), title=_("Mandatory Field")) return [ - doc.get_gl_dict( + self.get_gl_dict( { "account": doc.debit_to, "party_type": "Customer", @@ -436,7 +436,7 @@ class SalesInvoiceGLComposer(BaseGLComposer): doc.party_account_currency, item=doc, ), - doc.get_gl_dict( + self.get_gl_dict( { "account": doc.account_for_change_amount, "against": doc.customer, @@ -460,7 +460,7 @@ class SalesInvoiceGLComposer(BaseGLComposer): default_cost_center = frappe.get_cached_value("Company", doc.company, "cost_center") gl_entries.append( - doc.get_gl_dict( + self.get_gl_dict( { "account": doc.debit_to, "party_type": "Customer", @@ -485,7 +485,7 @@ class SalesInvoiceGLComposer(BaseGLComposer): ) ) gl_entries.append( - doc.get_gl_dict( + self.get_gl_dict( { "account": doc.write_off_account, "against": doc.customer, @@ -536,7 +536,7 @@ class SalesInvoiceGLComposer(BaseGLComposer): round_off_account = round_off_for_opening gl_entries.append( - doc.get_gl_dict( + self.get_gl_dict( { "account": round_off_account, "against": doc.customer, diff --git a/erpnext/accounts/services/base_gl_composer.py b/erpnext/accounts/services/base_gl_composer.py index bbe2474297e..2a39c4a5243 100644 --- a/erpnext/accounts/services/base_gl_composer.py +++ b/erpnext/accounts/services/base_gl_composer.py @@ -10,6 +10,8 @@ modelled as a class holding the document being composed. Subclasses implement ``compose`` to return the voucher-specific list of GL entries. """ +from erpnext.accounts.services.gl_entry_builder import add_gl_entry, get_gl_dict + class BaseGLComposer: def __init__(self, doc): @@ -17,3 +19,41 @@ class BaseGLComposer: def compose(self): raise NotImplementedError + + def get_gl_dict(self, args: dict, account_currency: str | None = None, item=None) -> dict: + return get_gl_dict(self.doc, args, account_currency, item) + + def add_gl_entry( + self, + gl_entries: list, + account: str, + cost_center: str, + debit: float, + credit: float, + remarks: str, + against_account: str, + debit_in_account_currency: float | None = None, + credit_in_account_currency: float | None = None, + account_currency: str | None = None, + project: str | None = None, + voucher_detail_no: str | None = None, + item=None, + posting_date=None, + ) -> None: + add_gl_entry( + self.doc, + gl_entries, + account, + cost_center, + debit, + credit, + remarks, + against_account, + debit_in_account_currency, + credit_in_account_currency, + account_currency, + project, + voucher_detail_no, + item, + posting_date, + ) diff --git a/erpnext/accounts/services/gl_entry_builder.py b/erpnext/accounts/services/gl_entry_builder.py new file mode 100644 index 00000000000..df304c020a6 --- /dev/null +++ b/erpnext/accounts/services/gl_entry_builder.py @@ -0,0 +1,223 @@ +# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors +# License: GNU General Public License v3. See license.txt + +"""Free functions for building GL entry dicts. + +These are the implementations behind ``AccountsController.get_gl_dict`` and +``StockController.add_gl_entry``. Extracting them as free functions (with +``doc`` as the first argument) allows ``BaseGLComposer`` to delegate to them +directly — without requiring every composing doctype to inherit from +``AccountsController``. + +``AccountsController`` and ``StockController`` keep thin shims that call these +functions so that existing code continues to work unchanged. +""" + +import frappe +from frappe import _ +from frappe.utils import flt, formatdate + +import erpnext +from erpnext.accounts.doctype.accounting_dimension.accounting_dimension import get_accounting_dimensions +from erpnext.accounts.services.taxes import set_balance_in_account_currency +from erpnext.accounts.utils import get_account_currency, get_fiscal_years +from erpnext.utilities.regional import temporary_flag + + +def get_gl_dict(doc, args: dict, account_currency: str | None = None, item=None) -> dict: + """Build a GL entry dict populated with doc-level fields.""" + posting_date = args.get("posting_date") or doc.get("posting_date") + fiscal_years = get_fiscal_years(posting_date, company=doc.company) + if len(fiscal_years) > 1: + frappe.throw( + _("Multiple fiscal years exist for the date {0}. Please set company in Fiscal Year").format( + formatdate(posting_date) + ) + ) + else: + fiscal_year = fiscal_years[0][0] + + gl_dict = frappe._dict( + { + "company": doc.company, + "posting_date": posting_date, + "fiscal_year": fiscal_year, + "voucher_type": doc.doctype, + "voucher_no": doc.name, + "remarks": doc.get("remarks") or doc.get("remark"), + "debit": 0, + "credit": 0, + "debit_in_account_currency": 0, + "credit_in_account_currency": 0, + "is_opening": doc.get("is_opening") or "No", + "party_type": None, + "party": None, + "project": doc.get("project"), + "post_net_value": args.get("post_net_value"), + "voucher_detail_no": args.get("voucher_detail_no"), + "voucher_subtype": get_voucher_subtype(doc), + } + ) + + with temporary_flag("company", doc.company): + update_gl_dict_with_regional_fields(doc, gl_dict) + + update_gl_dict_with_app_based_fields(doc, gl_dict) + + accounting_dimensions = get_accounting_dimensions() + dimension_dict = frappe._dict() + for dimension in accounting_dimensions: + dimension_dict[dimension] = doc.get(dimension) + if item and item.get(dimension): + dimension_dict[dimension] = item.get(dimension) + + gl_dict.update(dimension_dict) + gl_dict.update(args) + + if not account_currency: + account_currency = get_account_currency(gl_dict.account) + + if gl_dict.account and doc.doctype not in [ + "Journal Entry", + "Period Closing Voucher", + "Payment Entry", + "Purchase Receipt", + "Purchase Invoice", + "Stock Entry", + ]: + validate_account_currency(doc, gl_dict.account, account_currency) + + if gl_dict.account and doc.doctype not in [ + "Journal Entry", + "Period Closing Voucher", + "Payment Entry", + ]: + set_balance_in_account_currency( + gl_dict, + account_currency, + args.get("transaction_exchange_rate") or doc.get("conversion_rate"), + doc.company_currency, + ) + + if doc.doctype not in ["Purchase Invoice", "Sales Invoice", "Journal Entry", "Payment Entry"]: + gl_dict.update( + { + "transaction_currency": doc.get("currency") or doc.company_currency, + "transaction_exchange_rate": args.get("transaction_exchange_rate") + or doc.get("conversion_rate", 1), + "debit_in_transaction_currency": get_value_in_transaction_currency( + doc, account_currency, gl_dict, "debit" + ), + "credit_in_transaction_currency": get_value_in_transaction_currency( + doc, account_currency, gl_dict, "credit" + ), + } + ) + + if not args.get("against_voucher_type") and doc.get("against_voucher_type"): + gl_dict.update({"against_voucher_type": doc.get("against_voucher_type")}) + + if not args.get("against_voucher") and doc.get("against_voucher"): + gl_dict.update({"against_voucher": doc.get("against_voucher")}) + + return gl_dict + + +def add_gl_entry( + doc, + gl_entries: list, + account: str, + cost_center: str, + debit: float, + credit: float, + remarks: str, + against_account: str, + debit_in_account_currency: float | None = None, + credit_in_account_currency: float | None = None, + account_currency: str | None = None, + project: str | None = None, + voucher_detail_no: str | None = None, + item=None, + posting_date=None, +) -> None: + """Build a GL entry via get_gl_dict and append it to gl_entries.""" + gl_entry = { + "account": account, + "cost_center": cost_center, + "debit": debit, + "credit": credit, + "against": against_account, + "remarks": remarks, + } + + if voucher_detail_no: + gl_entry["voucher_detail_no"] = voucher_detail_no + + if debit_in_account_currency: + gl_entry["debit_in_account_currency"] = debit_in_account_currency + + if credit_in_account_currency: + gl_entry["credit_in_account_currency"] = credit_in_account_currency + + if posting_date: + gl_entry["posting_date"] = posting_date + + gl_entries.append(get_gl_dict(doc, gl_entry, account_currency, item=item)) + + +def get_voucher_subtype(doc) -> str: + voucher_subtypes = { + "Journal Entry": "voucher_type", + "Payment Entry": "payment_type", + "Stock Entry": "stock_entry_type", + "Asset Capitalization": "entry_type", + } + + for method_name in frappe.get_hooks("voucher_subtypes"): + voucher_subtype = frappe.get_attr(method_name)(doc) + if voucher_subtype: + return voucher_subtype + + if doc.doctype in voucher_subtypes: + return doc.get(voucher_subtypes[doc.doctype]) + elif doc.doctype == "Purchase Receipt" and doc.is_return: + return "Purchase Return" + elif doc.doctype == "Delivery Note" and doc.is_return: + return "Sales Return" + elif doc.doctype == "Sales Invoice" and doc.is_return: + return "Credit Note" + elif doc.doctype == "Sales Invoice" and doc.is_debit_note: + return "Debit Note" + elif doc.doctype == "Purchase Invoice" and doc.is_return: + return "Debit Note" + + return doc.doctype + + +def get_value_in_transaction_currency(doc, account_currency: str, gl_dict: dict, field: str) -> float: + if account_currency == doc.get("currency"): + return gl_dict.get(field + "_in_account_currency") + return flt(gl_dict.get(field, 0) / doc.get("conversion_rate", 1)) + + +def validate_account_currency(doc, account: str, account_currency: str | None = None) -> None: + valid_currency = [doc.company_currency] + if doc.get("currency") and doc.currency != doc.company_currency: + valid_currency.append(doc.currency) + + if account_currency not in valid_currency: + frappe.throw( + _("Account {0} is invalid. Account Currency must be {1}").format( + account, (" " + _("or") + " ").join(valid_currency) + ) + ) + + +@erpnext.allow_regional +def update_gl_dict_with_regional_fields(doc, gl_dict): + pass + + +def update_gl_dict_with_app_based_fields(doc, gl_dict): + for method in frappe.get_hooks("update_gl_dict_with_app_based_fields", default=[]): + frappe.get_attr(method)(doc, gl_dict) diff --git a/erpnext/assets/doctype/asset_capitalization/services/gl_composer.py b/erpnext/assets/doctype/asset_capitalization/services/gl_composer.py index 4f0993c0e92..2b13bddd5ad 100644 --- a/erpnext/assets/doctype/asset_capitalization/services/gl_composer.py +++ b/erpnext/assets/doctype/asset_capitalization/services/gl_composer.py @@ -65,7 +65,7 @@ class AssetCapitalizationGLComposer(BaseStockGLComposer): target_against.add(account) gl_entries.append( - doc.get_gl_dict( + self.get_gl_dict( { "account": account, "against": target_account, @@ -108,7 +108,7 @@ class AssetCapitalizationGLComposer(BaseStockGLComposer): for gle in fixed_asset_gl_entries: gle["against"] = target_account - gl_entries.append(doc.get_gl_dict(gle, item=item)) + gl_entries.append(self.get_gl_dict(gle, item=item)) target_against.add(gle["account"]) asset.db_set("disposal_date", doc.posting_date) @@ -123,7 +123,7 @@ class AssetCapitalizationGLComposer(BaseStockGLComposer): target_against.add(item_row.expense_account) gl_entries.append( - doc.get_gl_dict( + self.get_gl_dict( { "account": item_row.expense_account, "against": target_account, @@ -147,7 +147,7 @@ class AssetCapitalizationGLComposer(BaseStockGLComposer): total_value = flt(doc.total_value - composite_component_value, self.precision) if total_value: gl_entries.append( - doc.get_gl_dict( + self.get_gl_dict( { "account": target_account, "against": ", ".join(target_against), diff --git a/erpnext/assets/doctype/asset_repair/services/gl_composer.py b/erpnext/assets/doctype/asset_repair/services/gl_composer.py index 473d7d4853a..53ec0b3e61a 100644 --- a/erpnext/assets/doctype/asset_repair/services/gl_composer.py +++ b/erpnext/assets/doctype/asset_repair/services/gl_composer.py @@ -37,7 +37,7 @@ class AssetRepairGLComposer(BaseGLComposer): for pi in doc.invoices: debit_against_account.add(pi.expense_account) gl_entries.append( - doc.get_gl_dict( + self.get_gl_dict( { "account": pi.expense_account, "credit": pi.repair_cost, @@ -55,7 +55,7 @@ class AssetRepairGLComposer(BaseGLComposer): debit_against_account_str = ", ".join(debit_against_account) gl_entries.append( - doc.get_gl_dict( + self.get_gl_dict( { "account": fixed_asset_account, "debit": doc.repair_cost, @@ -94,7 +94,7 @@ class AssetRepairGLComposer(BaseGLComposer): for item in stock_entry_items: if flt(item.amount) > 0: gl_entries.append( - doc.get_gl_dict( + self.get_gl_dict( { "account": item.expense_account or default_expense_account, "credit": item.amount, @@ -111,7 +111,7 @@ class AssetRepairGLComposer(BaseGLComposer): ) gl_entries.append( - doc.get_gl_dict( + self.get_gl_dict( { "account": fixed_asset_account, "debit": item.amount, diff --git a/erpnext/controllers/accounts_controller.py b/erpnext/controllers/accounts_controller.py index 3defc078de9..abd708bad1a 100644 --- a/erpnext/controllers/accounts_controller.py +++ b/erpnext/controllers/accounts_controller.py @@ -19,7 +19,6 @@ from frappe.utils import ( comma_and, flt, fmt_money, - formatdate, get_last_day, get_link_to_form, getdate, @@ -51,7 +50,6 @@ from erpnext.accounts.utils import ( create_gain_loss_journal, get_account_currency, get_currency_precision, - get_fiscal_years, validate_fiscal_year, ) from erpnext.accounts.utils import ( @@ -1293,140 +1291,19 @@ class AccountsController(TransactionBase): ) def get_gl_dict(self, args, account_currency=None, item=None): - """this method populates the common properties of a gl entry record""" + from erpnext.accounts.services.gl_entry_builder import get_gl_dict - posting_date = args.get("posting_date") or self.get("posting_date") - fiscal_years = get_fiscal_years(posting_date, company=self.company) - if len(fiscal_years) > 1: - frappe.throw( - _("Multiple fiscal years exist for the date {0}. Please set company in Fiscal Year").format( - formatdate(posting_date) - ) - ) - else: - fiscal_year = fiscal_years[0][0] - - gl_dict = frappe._dict( - { - "company": self.company, - "posting_date": posting_date, - "fiscal_year": fiscal_year, - "voucher_type": self.doctype, - "voucher_no": self.name, - "remarks": self.get("remarks") or self.get("remark"), - "debit": 0, - "credit": 0, - "debit_in_account_currency": 0, - "credit_in_account_currency": 0, - "is_opening": self.get("is_opening") or "No", - "party_type": None, - "party": None, - "project": self.get("project"), - "post_net_value": args.get("post_net_value"), - "voucher_detail_no": args.get("voucher_detail_no"), - "voucher_subtype": self.get_voucher_subtype(), - } - ) - - with temporary_flag("company", self.company): - update_gl_dict_with_regional_fields(self, gl_dict) - - update_gl_dict_with_app_based_fields(self, gl_dict) - - accounting_dimensions = get_accounting_dimensions() - dimension_dict = frappe._dict() - - for dimension in accounting_dimensions: - dimension_dict[dimension] = self.get(dimension) - if item and item.get(dimension): - dimension_dict[dimension] = item.get(dimension) - - gl_dict.update(dimension_dict) - gl_dict.update(args) - - if not account_currency: - account_currency = get_account_currency(gl_dict.account) - - if gl_dict.account and self.doctype not in [ - "Journal Entry", - "Period Closing Voucher", - "Payment Entry", - "Purchase Receipt", - "Purchase Invoice", - "Stock Entry", - ]: - self.validate_account_currency(gl_dict.account, account_currency) - - if gl_dict.account and self.doctype not in [ - "Journal Entry", - "Period Closing Voucher", - "Payment Entry", - ]: - set_balance_in_account_currency( - gl_dict, - account_currency, - args.get("transaction_exchange_rate") or self.get("conversion_rate"), - self.company_currency, - ) - - # Update details in transaction currency - if self.doctype not in ["Purchase Invoice", "Sales Invoice", "Journal Entry", "Payment Entry"]: - gl_dict.update( - { - "transaction_currency": self.get("currency") or self.company_currency, - "transaction_exchange_rate": args.get("transaction_exchange_rate") - or self.get("conversion_rate", 1), - "debit_in_transaction_currency": self.get_value_in_transaction_currency( - account_currency, gl_dict, "debit" - ), - "credit_in_transaction_currency": self.get_value_in_transaction_currency( - account_currency, gl_dict, "credit" - ), - } - ) - - if not args.get("against_voucher_type") and self.get("against_voucher_type"): - gl_dict.update({"against_voucher_type": self.get("against_voucher_type")}) - - if not args.get("against_voucher") and self.get("against_voucher"): - gl_dict.update({"against_voucher": self.get("against_voucher")}) - - return gl_dict + return get_gl_dict(self, args, account_currency, item) def get_voucher_subtype(self): - voucher_subtypes = { - "Journal Entry": "voucher_type", - "Payment Entry": "payment_type", - "Stock Entry": "stock_entry_type", - "Asset Capitalization": "entry_type", - } + from erpnext.accounts.services.gl_entry_builder import get_voucher_subtype - for method_name in frappe.get_hooks("voucher_subtypes"): - voucher_subtype = frappe.get_attr(method_name)(self) - - if voucher_subtype: - return voucher_subtype - - if self.doctype in voucher_subtypes: - return self.get(voucher_subtypes[self.doctype]) - elif self.doctype == "Purchase Receipt" and self.is_return: - return "Purchase Return" - elif self.doctype == "Delivery Note" and self.is_return: - return "Sales Return" - elif self.doctype == "Sales Invoice" and self.is_return: - return "Credit Note" - elif self.doctype == "Sales Invoice" and self.is_debit_note: - return "Debit Note" - elif self.doctype == "Purchase Invoice" and self.is_return: - return "Debit Note" - - return self.doctype + return get_voucher_subtype(self) def get_value_in_transaction_currency(self, account_currency, gl_dict, field): - if account_currency == self.get("currency"): - return gl_dict.get(field + "_in_account_currency") - else: - return flt(gl_dict.get(field, 0) / self.get("conversion_rate", 1)) + from erpnext.accounts.services.gl_entry_builder import get_value_in_transaction_currency + + return get_value_in_transaction_currency(self, account_currency, gl_dict, field) def validate_zero_qty_for_return_invoices_with_stock(self): rows = [] @@ -1458,16 +1335,9 @@ class AccountsController(TransactionBase): ) def validate_account_currency(self, account, account_currency=None): - valid_currency = [self.company_currency] - if self.get("currency") and self.currency != self.company_currency: - valid_currency.append(self.currency) + from erpnext.accounts.services.gl_entry_builder import validate_account_currency - if account_currency not in valid_currency: - frappe.throw( - _("Account {0} is invalid. Account Currency must be {1}").format( - account, (" " + _("or") + " ").join(valid_currency) - ) - ) + return validate_account_currency(self, account, account_currency) def clear_unallocated_advances(self, childtype, parentfield): self.set(parentfield, self.get(parentfield, {"allocated_amount": ["not in", [0, None, ""]]})) @@ -3622,14 +3492,10 @@ def validate_einvoice_fields(doc): pass -@erpnext.allow_regional -def update_gl_dict_with_regional_fields(doc, gl_dict): - pass - - -def update_gl_dict_with_app_based_fields(doc, gl_dict): - for method in frappe.get_hooks("update_gl_dict_with_app_based_fields", default=[]): - frappe.get_attr(method)(doc, gl_dict) +from erpnext.accounts.services.gl_entry_builder import ( + update_gl_dict_with_app_based_fields, + update_gl_dict_with_regional_fields, +) @frappe.whitelist() diff --git a/erpnext/controllers/stock_controller.py b/erpnext/controllers/stock_controller.py index cf8f27560a5..b8cbce7045f 100644 --- a/erpnext/controllers/stock_controller.py +++ b/erpnext/controllers/stock_controller.py @@ -1661,28 +1661,25 @@ class StockController(AccountsController): item=None, posting_date=None, ): - gl_entry = { - "account": account, - "cost_center": cost_center, - "debit": debit, - "credit": credit, - "against": against_account, - "remarks": remarks, - } + from erpnext.accounts.services.gl_entry_builder import add_gl_entry - if voucher_detail_no: - gl_entry.update({"voucher_detail_no": voucher_detail_no}) - - if debit_in_account_currency: - gl_entry.update({"debit_in_account_currency": debit_in_account_currency}) - - if credit_in_account_currency: - gl_entry.update({"credit_in_account_currency": credit_in_account_currency}) - - if posting_date: - gl_entry.update({"posting_date": posting_date}) - - gl_entries.append(self.get_gl_dict(gl_entry, item=item)) + add_gl_entry( + self, + gl_entries, + account, + cost_center, + debit, + credit, + remarks, + against_account, + debit_in_account_currency, + credit_in_account_currency, + account_currency, + project, + voucher_detail_no, + item, + posting_date, + ) def update_stock_reservation_entries(self): def get_sre_list(): diff --git a/erpnext/stock/doctype/purchase_receipt/services/gl_composer.py b/erpnext/stock/doctype/purchase_receipt/services/gl_composer.py index 20347583bb1..6a01a0484ce 100644 --- a/erpnext/stock/doctype/purchase_receipt/services/gl_composer.py +++ b/erpnext/stock/doctype/purchase_receipt/services/gl_composer.py @@ -58,7 +58,7 @@ class PurchaseReceiptGLComposer(BaseStockGLComposer): 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( + self.add_gl_entry( gl_entries=gl_entries, account=stock_asset_account_name, cost_center=d.cost_center, @@ -108,7 +108,7 @@ class PurchaseReceiptGLComposer(BaseStockGLComposer): if not account: validate_account("Stock or Asset Received But Not Billed") - doc.add_gl_entry( + self.add_gl_entry( gl_entries=gl_entries, account=account, cost_center=item.cost_center, @@ -131,7 +131,7 @@ class PurchaseReceiptGLComposer(BaseStockGLComposer): exchange_rate_map[item.purchase_invoice] - doc.conversion_rate ) - doc.add_gl_entry( + self.add_gl_entry( gl_entries=gl_entries, account=account, cost_center=item.cost_center, @@ -144,7 +144,7 @@ class PurchaseReceiptGLComposer(BaseStockGLComposer): item=item, ) - doc.add_gl_entry( + self.add_gl_entry( gl_entries=gl_entries, account=doc.get_company_default("exchange_gain_loss_account"), cost_center=d.cost_center, @@ -173,7 +173,7 @@ class PurchaseReceiptGLComposer(BaseStockGLComposer): if not account: validate_account("Landed Cost Account") - doc.add_gl_entry( + self.add_gl_entry( gl_entries=gl_entries, account=account, cost_center=item.cost_center, @@ -190,7 +190,7 @@ class PurchaseReceiptGLComposer(BaseStockGLComposer): 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( + self.add_gl_entry( gl_entries=gl_entries, account=stock_asset_rbnb, cost_center=item.cost_center, @@ -205,7 +205,7 @@ class PurchaseReceiptGLComposer(BaseStockGLComposer): def make_sub_contracting_gl_entries(item): if flt(item.rm_supp_cost) and supplier_warehouse_account: - doc.add_gl_entry( + self.add_gl_entry( gl_entries=gl_entries, account=supplier_warehouse_account, cost_center=item.cost_center, @@ -252,7 +252,7 @@ class PurchaseReceiptGLComposer(BaseStockGLComposer): "Company", doc.company, "cost_center" ) account_currency = get_account_currency(loss_account) - doc.add_gl_entry( + self.add_gl_entry( gl_entries=gl_entries, account=loss_account, cost_center=cost_center, @@ -394,7 +394,7 @@ class PurchaseReceiptGLComposer(BaseStockGLComposer): ) amount_including_divisional_loss -= applicable_amount - doc.add_gl_entry( + self.add_gl_entry( gl_entries=gl_entries, account=account, cost_center=tax.cost_center, diff --git a/erpnext/stock/doctype/stock_entry/services/gl_composer.py b/erpnext/stock/doctype/stock_entry/services/gl_composer.py index f4ad4586ebf..4788346833f 100644 --- a/erpnext/stock/doctype/stock_entry/services/gl_composer.py +++ b/erpnext/stock/doctype/stock_entry/services/gl_composer.py @@ -75,7 +75,7 @@ class StockEntryGLComposer(BaseStockGLComposer): continue gl_entries.append( - doc.get_gl_dict( + self.get_gl_dict( { "account": account, "against": d.expense_account, @@ -89,7 +89,7 @@ class StockEntryGLComposer(BaseStockGLComposer): ) gl_entries.append( - doc.get_gl_dict( + self.get_gl_dict( { "account": d.expense_account, "against": account, @@ -122,7 +122,7 @@ class StockEntryGLComposer(BaseStockGLComposer): _inv_dict = doc.get_inventory_account_dict(item, inventory_account_map, "t_warehouse") gl_entries.append( - doc.get_gl_dict( + self.get_gl_dict( { "account": account, "against": _inv_dict["account"], @@ -140,7 +140,7 @@ class StockEntryGLComposer(BaseStockGLComposer): account_currency = get_account_currency(item.expense_account) gl_entries.append( - doc.get_gl_dict( + self.get_gl_dict( { "account": item.expense_account, "against": _inv_dict["account"], diff --git a/erpnext/stock/services/base_stock_gl_composer.py b/erpnext/stock/services/base_stock_gl_composer.py index 27731c0eb9e..89837db9909 100644 --- a/erpnext/stock/services/base_stock_gl_composer.py +++ b/erpnext/stock/services/base_stock_gl_composer.py @@ -56,7 +56,7 @@ class BaseStockGLComposer(BaseGLComposer): expense_account = item_row.expense_account gl_list.append( - doc.get_gl_dict( + self.get_gl_dict( { "account": _inv_dict["account"], "against": expense_account, @@ -72,7 +72,7 @@ class BaseStockGLComposer(BaseGLComposer): ) gl_list.append( - doc.get_gl_dict( + self.get_gl_dict( { "account": expense_account, "against": _inv_dict["account"], @@ -110,7 +110,7 @@ class BaseStockGLComposer(BaseGLComposer): ) gl_list.append( - doc.get_gl_dict( + self.get_gl_dict( { "account": expense_account, "against": warehouse_asset_account, @@ -126,7 +126,7 @@ class BaseStockGLComposer(BaseGLComposer): ) gl_list.append( - doc.get_gl_dict( + self.get_gl_dict( { "account": warehouse_asset_account, "against": expense_account, diff --git a/erpnext/subcontracting/doctype/subcontracting_receipt/services/gl_composer.py b/erpnext/subcontracting/doctype/subcontracting_receipt/services/gl_composer.py index a0215a74bd1..7e31454ab23 100644 --- a/erpnext/subcontracting/doctype/subcontracting_receipt/services/gl_composer.py +++ b/erpnext/subcontracting/doctype/subcontracting_receipt/services/gl_composer.py @@ -66,7 +66,7 @@ class SubcontractingReceiptGLComposer(BaseStockGLComposer): remarks = doc.get("remarks") or _("Accounting Entry for Stock") - doc.add_gl_entry( + self.add_gl_entry( gl_entries=gl_entries, account=_inv_dict["account"], cost_center=item.cost_center, @@ -83,7 +83,7 @@ class SubcontractingReceiptGLComposer(BaseStockGLComposer): item.service_cost_per_qty, item.precision("service_cost_per_qty") ) * flt(item.qty, item.precision("qty")) - doc.add_gl_entry( + self.add_gl_entry( gl_entries=gl_entries, account=item.expense_account, cost_center=item.cost_center, @@ -97,7 +97,7 @@ class SubcontractingReceiptGLComposer(BaseStockGLComposer): ) service_account = item.service_expense_account or item.expense_account - doc.add_gl_entry( + self.add_gl_entry( gl_entries=gl_entries, account=service_account, cost_center=item.cost_center, @@ -116,7 +116,7 @@ class SubcontractingReceiptGLComposer(BaseStockGLComposer): rm_item, inventory_account_map, "supplier_warehouse" ) - doc.add_gl_entry( + self.add_gl_entry( gl_entries=gl_entries, account=_inv_dict.get("account"), cost_center=rm_item.cost_center or item.cost_center, @@ -128,7 +128,7 @@ class SubcontractingReceiptGLComposer(BaseStockGLComposer): project=item.project, item=item, ) - doc.add_gl_entry( + self.add_gl_entry( gl_entries=gl_entries, account=rm_item.expense_account or item.expense_account, cost_center=rm_item.cost_center or item.cost_center, @@ -142,7 +142,7 @@ class SubcontractingReceiptGLComposer(BaseStockGLComposer): ) if item.additional_cost_per_qty: - doc.add_gl_entry( + self.add_gl_entry( gl_entries=gl_entries, account=item.expense_account, cost_center=doc.cost_center or doc.get_company_default("cost_center"), @@ -158,7 +158,7 @@ class SubcontractingReceiptGLComposer(BaseStockGLComposer): "stock_adjustment_account", ignore_validation=True ) - doc.add_gl_entry( + self.add_gl_entry( gl_entries=gl_entries, account=loss_account, cost_center=item.cost_center, @@ -170,7 +170,7 @@ class SubcontractingReceiptGLComposer(BaseStockGLComposer): project=item.project, item=item, ) - doc.add_gl_entry( + self.add_gl_entry( gl_entries=gl_entries, account=item.expense_account, cost_center=item.cost_center, @@ -195,7 +195,7 @@ class SubcontractingReceiptGLComposer(BaseStockGLComposer): else flt(row.amount) ) - doc.add_gl_entry( + self.add_gl_entry( gl_entries=gl_entries, account=row.expense_account, cost_center=doc.cost_center or doc.get_company_default("cost_center"), @@ -234,7 +234,7 @@ class SubcontractingReceiptGLComposer(BaseStockGLComposer): else flt(amount["amount"]) ) - doc.add_gl_entry( + self.add_gl_entry( gl_entries=gl_entries, account=account, cost_center=item.cost_center, @@ -250,7 +250,7 @@ class SubcontractingReceiptGLComposer(BaseStockGLComposer): account_currency = get_account_currency(item.expense_account) - doc.add_gl_entry( + self.add_gl_entry( gl_entries=gl_entries, account=item.expense_account, cost_center=item.cost_center,