diff --git a/erpnext/assets/doctype/asset_capitalization/asset_capitalization.py b/erpnext/assets/doctype/asset_capitalization/asset_capitalization.py index e4724dadeee..ada205080cb 100644 --- a/erpnext/assets/doctype/asset_capitalization/asset_capitalization.py +++ b/erpnext/assets/doctype/asset_capitalization/asset_capitalization.py @@ -12,8 +12,6 @@ from frappe.utils import cint, flt, get_link_to_form import erpnext from erpnext.assets.doctype.asset.asset import get_asset_value_after_depreciation from erpnext.assets.doctype.asset.depreciation import ( - depreciate_asset, - get_gl_entries_on_asset_disposal, get_value_after_depreciation_on_disposal_date, reset_depreciation_schedule, reverse_depreciation_entry_made_on_disposal, @@ -396,30 +394,11 @@ class AssetCapitalization(StockController): def get_gl_entries( self, inventory_account_map=None, default_expense_account=None, default_cost_center=None ): - # Stock GL Entries - gl_entries = [] - - self.inventory_account_map = inventory_account_map - if not self.inventory_account_map: - self.inventory_account_map = self.get_inventory_account_map() - - precision = self.get_debit_field_precision() - self.sle_map = self.get_stock_ledger_details() - - target_account = self.get_target_account() - target_against = set() - - self.get_gl_entries_for_consumed_stock_items(gl_entries, target_account, target_against, precision) - self.get_gl_entries_for_consumed_asset_items(gl_entries, target_account, target_against, precision) - self.get_gl_entries_for_consumed_service_items(gl_entries, target_account, target_against, precision) - - composite_component_value = self.get_composite_component_value() - - self.get_gl_entries_for_target_item( - gl_entries, target_account, target_against, precision, composite_component_value + from erpnext.assets.doctype.asset_capitalization.services.gl_composer import ( + AssetCapitalizationGLComposer, ) - return gl_entries + return AssetCapitalizationGLComposer(self).compose(inventory_account_map) def get_target_account(self): from erpnext.assets.doctype.asset.asset import is_cwip_accounting_enabled @@ -435,91 +414,6 @@ class AssetCapitalization(StockController): else: return self.target_fixed_asset_account - def get_gl_entries_for_consumed_stock_items(self, gl_entries, target_account, target_against, precision): - # Consumed Stock Items - for item_row in self.stock_items: - sle_list = self.sle_map.get(item_row.name) - if sle_list: - _inv_dict = self.get_inventory_account_dict(item_row, self.inventory_account_map) - for sle in sle_list: - stock_value_difference = flt(sle.stock_value_difference, precision) - - if erpnext.is_perpetual_inventory_enabled(self.company): - account = _inv_dict["account"] - else: - account = self.get_company_default("default_expense_account") - - target_against.add(account) - gl_entries.append( - self.get_gl_dict( - { - "account": account, - "against": target_account, - "cost_center": item_row.cost_center, - "project": item_row.get("project") or self.get("project"), - "remarks": self.get("remarks") or "Accounting Entry for Stock", - "credit": -1 * stock_value_difference, - }, - _inv_dict["account_currency"], - item=item_row, - ) - ) - - def get_gl_entries_for_consumed_asset_items(self, gl_entries, target_account, target_against, precision): - # Consumed Assets - for item in self.asset_items: - asset = frappe.get_doc("Asset", item.asset) - - if asset.asset_type != "Composite Component": - if asset.calculate_depreciation: - notes = _( - "This schedule was created when Asset {0} was consumed through Asset Capitalization {1}." - ).format( - get_link_to_form(asset.doctype, asset.name), - get_link_to_form(self.doctype, self.get("name")), - ) - depreciate_asset(asset, self.posting_date, notes) - asset.reload() - - fixed_asset_gl_entries = get_gl_entries_on_asset_disposal( - asset, - item.asset_value, - item.get("finance_book") or self.get("finance_book"), - self.get("doctype"), - self.get("name"), - self.get("posting_date"), - ) - - for gle in fixed_asset_gl_entries: - gle["against"] = target_account - gl_entries.append(self.get_gl_dict(gle, item=item)) - target_against.add(gle["account"]) - - asset.db_set("disposal_date", self.posting_date) - self.set_consumed_asset_status(asset) - - def get_gl_entries_for_consumed_service_items( - self, gl_entries, target_account, target_against, precision - ): - # Service Expenses - for item_row in self.service_items: - expense_amount = flt(item_row.amount, precision) - target_against.add(item_row.expense_account) - - gl_entries.append( - self.get_gl_dict( - { - "account": item_row.expense_account, - "against": target_account, - "cost_center": item_row.cost_center, - "project": item_row.get("project") or self.get("project"), - "remarks": self.get("remarks") or "Accounting Entry for Stock", - "credit": expense_amount, - }, - item=item_row, - ) - ) - def get_composite_component_value(self): composite_component_value = 0 for item in self.asset_items: @@ -528,25 +422,6 @@ class AssetCapitalization(StockController): composite_component_value += flt(item.asset_value, item.precision("asset_value")) return composite_component_value - def get_gl_entries_for_target_item( - self, gl_entries, target_account, target_against, precision, composite_component_value - ): - total_value = flt(self.total_value - composite_component_value, precision) - if total_value: - # Capitalization - gl_entries.append( - self.get_gl_dict( - { - "account": target_account, - "against": ", ".join(target_against), - "remarks": self.get("remarks") or _("Accounting Entry for Asset"), - "debit": total_value, - "cost_center": self.get("cost_center"), - }, - item=self, - ) - ) - def update_target_asset(self): total_target_asset_value = flt(self.total_value, self.precision("total_value")) asset_doc = frappe.get_doc("Asset", self.target_asset) diff --git a/erpnext/assets/doctype/asset_capitalization/services/__init__.py b/erpnext/assets/doctype/asset_capitalization/services/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/erpnext/assets/doctype/asset_capitalization/services/gl_composer.py b/erpnext/assets/doctype/asset_capitalization/services/gl_composer.py new file mode 100644 index 00000000000..4f0993c0e92 --- /dev/null +++ b/erpnext/assets/doctype/asset_capitalization/services/gl_composer.py @@ -0,0 +1,160 @@ +# Copyright (c) 2021, Frappe Technologies Pvt. Ltd. and contributors +# License: GNU General Public License v3. See license.txt + +import frappe +from frappe import _ +from frappe.utils import flt + +import erpnext +from erpnext.assets.doctype.asset.depreciation import ( + depreciate_asset, + get_gl_entries_on_asset_disposal, +) +from erpnext.stock.services.base_stock_gl_composer import BaseStockGLComposer + + +class AssetCapitalizationGLComposer(BaseStockGLComposer): + """GL composer for Asset Capitalization. + + Builds GL entries for consumed stock items, consumed asset items (with + depreciation side-effects), consumed service items, and the target asset debit. + """ + + def compose( + self, + inventory_account_map: dict | None = None, + default_expense_account: str | None = None, + default_cost_center: str | None = None, + ) -> list: + doc = self.doc + gl_entries = [] + + self.inventory_account_map = inventory_account_map or doc.get_inventory_account_map() + self.precision = doc.get_debit_field_precision() + self.sle_map = doc.get_stock_ledger_details() + + target_account = doc.get_target_account() + target_against: set = set() + + self._get_gl_entries_for_consumed_stock_items(gl_entries, target_account, target_against) + self._get_gl_entries_for_consumed_asset_items(gl_entries, target_account, target_against) + self._get_gl_entries_for_consumed_service_items(gl_entries, target_account, target_against) + + composite_component_value = doc.get_composite_component_value() + self._get_gl_entries_for_target_item( + gl_entries, target_account, target_against, composite_component_value + ) + + return gl_entries + + def _get_gl_entries_for_consumed_stock_items( + self, gl_entries: list, target_account: str, target_against: set + ) -> None: + doc = self.doc + for item_row in doc.stock_items: + sle_list = self.sle_map.get(item_row.name) + if sle_list: + _inv_dict = doc.get_inventory_account_dict(item_row, self.inventory_account_map) + for sle in sle_list: + stock_value_difference = flt(sle.stock_value_difference, self.precision) + + if erpnext.is_perpetual_inventory_enabled(doc.company): + account = _inv_dict["account"] + else: + account = doc.get_company_default("default_expense_account") + + target_against.add(account) + gl_entries.append( + doc.get_gl_dict( + { + "account": account, + "against": target_account, + "cost_center": item_row.cost_center, + "project": item_row.get("project") or doc.get("project"), + "remarks": doc.get("remarks") or "Accounting Entry for Stock", + "credit": -1 * stock_value_difference, + }, + _inv_dict["account_currency"], + item=item_row, + ) + ) + + def _get_gl_entries_for_consumed_asset_items( + self, gl_entries: list, target_account: str, target_against: set + ) -> None: + doc = self.doc + for item in doc.asset_items: + asset = frappe.get_doc("Asset", item.asset) + + if asset.asset_type != "Composite Component": + if asset.calculate_depreciation: + notes = _( + "This schedule was created when Asset {0} was consumed through Asset Capitalization {1}." + ).format( + frappe.utils.get_link_to_form(asset.doctype, asset.name), + frappe.utils.get_link_to_form(doc.doctype, doc.get("name")), + ) + depreciate_asset(asset, doc.posting_date, notes) + asset.reload() + + fixed_asset_gl_entries = get_gl_entries_on_asset_disposal( + asset, + item.asset_value, + item.get("finance_book") or doc.get("finance_book"), + doc.get("doctype"), + doc.get("name"), + doc.get("posting_date"), + ) + + for gle in fixed_asset_gl_entries: + gle["against"] = target_account + gl_entries.append(doc.get_gl_dict(gle, item=item)) + target_against.add(gle["account"]) + + asset.db_set("disposal_date", doc.posting_date) + doc.set_consumed_asset_status(asset) + + def _get_gl_entries_for_consumed_service_items( + self, gl_entries: list, target_account: str, target_against: set + ) -> None: + doc = self.doc + for item_row in doc.service_items: + expense_amount = flt(item_row.amount, self.precision) + target_against.add(item_row.expense_account) + + gl_entries.append( + doc.get_gl_dict( + { + "account": item_row.expense_account, + "against": target_account, + "cost_center": item_row.cost_center, + "project": item_row.get("project") or doc.get("project"), + "remarks": doc.get("remarks") or "Accounting Entry for Stock", + "credit": expense_amount, + }, + item=item_row, + ) + ) + + def _get_gl_entries_for_target_item( + self, + gl_entries: list, + target_account: str, + target_against: set, + composite_component_value: float, + ) -> None: + doc = self.doc + total_value = flt(doc.total_value - composite_component_value, self.precision) + if total_value: + gl_entries.append( + doc.get_gl_dict( + { + "account": target_account, + "against": ", ".join(target_against), + "remarks": doc.get("remarks") or _("Accounting Entry for Asset"), + "debit": total_value, + "cost_center": doc.get("cost_center"), + }, + item=doc, + ) + ) diff --git a/erpnext/assets/doctype/asset_repair/asset_repair.py b/erpnext/assets/doctype/asset_repair/asset_repair.py index 202f16da684..6347379d577 100644 --- a/erpnext/assets/doctype/asset_repair/asset_repair.py +++ b/erpnext/assets/doctype/asset_repair/asset_repair.py @@ -12,7 +12,6 @@ from erpnext.accounts.doctype.accounting_dimension.accounting_dimension import ( get_accounting_dimensions, ) from erpnext.accounts.general_ledger import make_gl_entries -from erpnext.assets.doctype.asset.asset import get_asset_account from erpnext.assets.doctype.asset_activity.asset_activity import add_asset_activity from erpnext.assets.doctype.asset_depreciation_schedule.asset_depreciation_schedule import ( reschedule_depreciation, @@ -315,113 +314,9 @@ class AssetRepair(AccountsController): make_gl_entries(gl_entries, cancel) def get_gl_entries(self): - gl_entries = [] + from erpnext.assets.doctype.asset_repair.services.gl_composer import AssetRepairGLComposer - fixed_asset_account = get_asset_account("fixed_asset_account", asset=self.asset, company=self.company) - self.get_gl_entries_for_repair_cost(gl_entries, fixed_asset_account) - self.get_gl_entries_for_consumed_items(gl_entries, fixed_asset_account) - - return gl_entries - - def get_gl_entries_for_repair_cost(self, gl_entries, fixed_asset_account): - if flt(self.repair_cost) <= 0: - return - - debit_against_account = set() - - for pi in self.invoices: - debit_against_account.add(pi.expense_account) - gl_entries.append( - self.get_gl_dict( - { - "account": pi.expense_account, - "credit": pi.repair_cost, - "credit_in_account_currency": pi.repair_cost, - "against": fixed_asset_account, - "voucher_type": self.doctype, - "voucher_no": self.name, - "cost_center": self.cost_center, - "posting_date": self.completion_date, - "company": self.company, - }, - item=self, - ) - ) - debit_against_account = ", ".join(debit_against_account) - gl_entries.append( - self.get_gl_dict( - { - "account": fixed_asset_account, - "debit": self.repair_cost, - "debit_in_account_currency": self.repair_cost, - "against": debit_against_account, - "voucher_type": self.doctype, - "voucher_no": self.name, - "cost_center": self.cost_center, - "posting_date": self.completion_date, - "against_voucher_type": "Asset", - "against_voucher": self.asset, - "company": self.company, - }, - item=self, - ) - ) - - def get_gl_entries_for_consumed_items(self, gl_entries, fixed_asset_account): - if not self.get("stock_items"): - return - - # creating GL Entries for each row in Stock Items based on the Stock Entry created for it - stock_entry_name = frappe.db.get_value("Stock Entry", {"asset_repair": self.name}, "name") - stock_entry_items = frappe.get_all( - "Stock Entry Detail", filters={"parent": stock_entry_name}, fields=["expense_account", "amount"] - ) - - default_expense_account = None - if not erpnext.is_perpetual_inventory_enabled(self.company): - default_expense_account = frappe.get_cached_value( - "Company", self.company, "default_expense_account" - ) - if not default_expense_account: - frappe.throw(_("Please set default Expense Account in Company {0}").format(self.company)) - - for item in stock_entry_items: - if flt(item.amount) > 0: - gl_entries.append( - self.get_gl_dict( - { - "account": item.expense_account or default_expense_account, - "credit": item.amount, - "credit_in_account_currency": item.amount, - "against": fixed_asset_account, - "voucher_type": self.doctype, - "voucher_no": self.name, - "cost_center": self.cost_center, - "posting_date": self.completion_date, - "company": self.company, - }, - item=self, - ) - ) - - gl_entries.append( - self.get_gl_dict( - { - "account": fixed_asset_account, - "debit": item.amount, - "debit_in_account_currency": item.amount, - "against": item.expense_account or default_expense_account, - "voucher_type": self.doctype, - "voucher_no": self.name, - "cost_center": self.cost_center, - "posting_date": self.completion_date, - "against_voucher_type": "Stock Entry", - "against_voucher": stock_entry_name, - "company": self.company, - }, - item=self, - ) - ) + return AssetRepairGLComposer(self).compose() def set_increase_in_asset_life(self): if self.asset_doc.calculate_depreciation and cint(self.increase_in_asset_life) > 0: diff --git a/erpnext/assets/doctype/asset_repair/services/__init__.py b/erpnext/assets/doctype/asset_repair/services/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/erpnext/assets/doctype/asset_repair/services/gl_composer.py b/erpnext/assets/doctype/asset_repair/services/gl_composer.py new file mode 100644 index 00000000000..473d7d4853a --- /dev/null +++ b/erpnext/assets/doctype/asset_repair/services/gl_composer.py @@ -0,0 +1,130 @@ +# Copyright (c) 2017, Frappe Technologies Pvt. Ltd. and contributors +# License: GNU General Public License v3. See license.txt + +import frappe +from frappe import _ +from frappe.utils import flt + +import erpnext +from erpnext.accounts.services.base_gl_composer import BaseGLComposer +from erpnext.assets.doctype.asset.asset import get_asset_account + + +class AssetRepairGLComposer(BaseGLComposer): + """GL composer for Asset Repair. + + Builds GL entries for repair cost (per invoice) and consumed stock items + (sourced from the related Stock Entry). + """ + + def compose(self) -> list: + doc = self.doc + gl_entries = [] + + fixed_asset_account = get_asset_account("fixed_asset_account", asset=doc.asset, company=doc.company) + self._get_gl_entries_for_repair_cost(gl_entries, fixed_asset_account) + self._get_gl_entries_for_consumed_items(gl_entries, fixed_asset_account) + + return gl_entries + + def _get_gl_entries_for_repair_cost(self, gl_entries: list, fixed_asset_account: str) -> None: + doc = self.doc + if flt(doc.repair_cost) <= 0: + return + + debit_against_account = set() + + for pi in doc.invoices: + debit_against_account.add(pi.expense_account) + gl_entries.append( + doc.get_gl_dict( + { + "account": pi.expense_account, + "credit": pi.repair_cost, + "credit_in_account_currency": pi.repair_cost, + "against": fixed_asset_account, + "voucher_type": doc.doctype, + "voucher_no": doc.name, + "cost_center": doc.cost_center, + "posting_date": doc.completion_date, + "company": doc.company, + }, + item=doc, + ) + ) + + debit_against_account_str = ", ".join(debit_against_account) + gl_entries.append( + doc.get_gl_dict( + { + "account": fixed_asset_account, + "debit": doc.repair_cost, + "debit_in_account_currency": doc.repair_cost, + "against": debit_against_account_str, + "voucher_type": doc.doctype, + "voucher_no": doc.name, + "cost_center": doc.cost_center, + "posting_date": doc.completion_date, + "against_voucher_type": "Asset", + "against_voucher": doc.asset, + "company": doc.company, + }, + item=doc, + ) + ) + + def _get_gl_entries_for_consumed_items(self, gl_entries: list, fixed_asset_account: str) -> None: + doc = self.doc + if not doc.get("stock_items"): + return + + stock_entry_name = frappe.db.get_value("Stock Entry", {"asset_repair": doc.name}, "name") + stock_entry_items = frappe.get_all( + "Stock Entry Detail", filters={"parent": stock_entry_name}, fields=["expense_account", "amount"] + ) + + default_expense_account = None + if not erpnext.is_perpetual_inventory_enabled(doc.company): + default_expense_account = frappe.get_cached_value( + "Company", doc.company, "default_expense_account" + ) + if not default_expense_account: + frappe.throw(_("Please set default Expense Account in Company {0}").format(doc.company)) + + for item in stock_entry_items: + if flt(item.amount) > 0: + gl_entries.append( + doc.get_gl_dict( + { + "account": item.expense_account or default_expense_account, + "credit": item.amount, + "credit_in_account_currency": item.amount, + "against": fixed_asset_account, + "voucher_type": doc.doctype, + "voucher_no": doc.name, + "cost_center": doc.cost_center, + "posting_date": doc.completion_date, + "company": doc.company, + }, + item=doc, + ) + ) + + gl_entries.append( + doc.get_gl_dict( + { + "account": fixed_asset_account, + "debit": item.amount, + "debit_in_account_currency": item.amount, + "against": item.expense_account or default_expense_account, + "voucher_type": doc.doctype, + "voucher_no": doc.name, + "cost_center": doc.cost_center, + "posting_date": doc.completion_date, + "against_voucher_type": "Stock Entry", + "against_voucher": stock_entry_name, + "company": doc.company, + }, + item=doc, + ) + ) diff --git a/erpnext/subcontracting/doctype/subcontracting_receipt/services/__init__.py b/erpnext/subcontracting/doctype/subcontracting_receipt/services/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/erpnext/subcontracting/doctype/subcontracting_receipt/services/gl_composer.py b/erpnext/subcontracting/doctype/subcontracting_receipt/services/gl_composer.py new file mode 100644 index 00000000000..a0215a74bd1 --- /dev/null +++ b/erpnext/subcontracting/doctype/subcontracting_receipt/services/gl_composer.py @@ -0,0 +1,265 @@ +# Copyright (c) 2022, Frappe Technologies Pvt. Ltd. and contributors +# License: GNU General Public License v3. See license.txt + +import frappe +from frappe import _ +from frappe.utils import flt + +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 SubcontractingReceiptGLComposer(BaseStockGLComposer): + """GL composer for Subcontracting Receipt. + + Builds GL entries for accepted stock, service cost, supplier warehouse + (raw materials), additional costs, LCV, and divisional loss. + """ + + def compose(self, inventory_account_map: dict | None = None) -> list: + import erpnext + + doc = self.doc + if not erpnext.is_perpetual_inventory_enabled(doc.company): + return [] + + gl_entries = [] + self._make_item_gl_entries(gl_entries, inventory_account_map) + self._make_item_gl_entries_for_lcv(gl_entries, inventory_account_map) + + 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: + doc = self.doc + warehouse_with_no_account = [] + + supplied_items_details = frappe._dict() + for item in doc.supplied_items: + supplied_items_details.setdefault(item.reference_name, []).append( + frappe._dict( + { + "item_code": item.rm_item_code, + "amount": item.amount, + "expense_account": item.expense_account, + "cost_center": item.cost_center, + } + ) + ) + + for item in doc.items: + if flt(item.rate) and flt(item.qty): + _inv_dict = doc.get_inventory_account_dict(item, inventory_account_map) + + if _inv_dict.get("account"): + stock_value_diff = frappe.db.get_value( + "Stock Ledger Entry", + { + "voucher_type": "Subcontracting Receipt", + "voucher_no": doc.name, + "voucher_detail_no": item.name, + "warehouse": item.warehouse, + "is_cancelled": 0, + }, + "stock_value_difference", + ) + + remarks = doc.get("remarks") or _("Accounting Entry for Stock") + + doc.add_gl_entry( + gl_entries=gl_entries, + account=_inv_dict["account"], + cost_center=item.cost_center, + debit=stock_value_diff, + credit=0.0, + remarks=remarks, + against_account=item.expense_account, + account_currency=_inv_dict["account_currency"], + project=item.project, + item=item, + ) + + service_cost = flt( + item.service_cost_per_qty, item.precision("service_cost_per_qty") + ) * flt(item.qty, item.precision("qty")) + + doc.add_gl_entry( + gl_entries=gl_entries, + account=item.expense_account, + cost_center=item.cost_center, + debit=0.0, + credit=flt(stock_value_diff) - service_cost, + remarks=remarks, + against_account=_inv_dict["account"], + account_currency=get_account_currency(item.expense_account), + project=item.project, + item=item, + ) + + service_account = item.service_expense_account or item.expense_account + doc.add_gl_entry( + gl_entries=gl_entries, + account=service_account, + cost_center=item.cost_center, + debit=0.0, + credit=service_cost, + remarks=remarks, + against_account=_inv_dict["account"], + account_currency=get_account_currency(service_account), + project=item.project, + item=item, + ) + + if flt(item.rm_supp_cost): + for rm_item in supplied_items_details.get(item.name): + _inv_dict = doc.get_inventory_account_dict( + rm_item, inventory_account_map, "supplier_warehouse" + ) + + doc.add_gl_entry( + gl_entries=gl_entries, + account=_inv_dict.get("account"), + cost_center=rm_item.cost_center or item.cost_center, + debit=0.0, + credit=flt(rm_item.amount), + remarks=remarks, + against_account=rm_item.expense_account or item.expense_account, + account_currency=_inv_dict.get("account_currency"), + project=item.project, + item=item, + ) + doc.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, + debit=flt(rm_item.amount), + credit=0.0, + remarks=remarks, + against_account=_inv_dict.get("account"), + account_currency=get_account_currency(item.expense_account), + project=item.project, + item=item, + ) + + if item.additional_cost_per_qty: + doc.add_gl_entry( + gl_entries=gl_entries, + account=item.expense_account, + cost_center=doc.cost_center or doc.get_company_default("cost_center"), + debit=item.qty * item.additional_cost_per_qty, + credit=0.0, + remarks=remarks, + against_account=None, + account_currency=get_account_currency(item.expense_account), + ) + + if divisional_loss := flt(item.amount - stock_value_diff, item.precision("amount")): + loss_account = doc.get_company_default( + "stock_adjustment_account", ignore_validation=True + ) + + doc.add_gl_entry( + gl_entries=gl_entries, + account=loss_account, + cost_center=item.cost_center, + debit=0.0, + credit=divisional_loss, + remarks=remarks, + against_account=item.expense_account, + account_currency=get_account_currency(loss_account), + project=item.project, + item=item, + ) + doc.add_gl_entry( + gl_entries=gl_entries, + account=item.expense_account, + cost_center=item.cost_center, + debit=divisional_loss, + credit=0.0, + remarks=remarks, + against_account=loss_account, + account_currency=get_account_currency(item.expense_account), + project=item.project, + item=item, + ) + elif ( + item.warehouse not in warehouse_with_no_account + or item.rejected_warehouse not in warehouse_with_no_account + ): + warehouse_with_no_account.append(item.warehouse) + + for row in doc.additional_costs: + credit_amount = ( + flt(row.base_amount) + if (row.base_amount or row.account_currency != doc.company_currency) + else flt(row.amount) + ) + + doc.add_gl_entry( + gl_entries=gl_entries, + account=row.expense_account, + cost_center=doc.cost_center or doc.get_company_default("cost_center"), + debit=0.0, + credit=credit_amount, + remarks=remarks, + against_account=None, + account_currency=get_account_currency(row.expense_account), + ) + + if warehouse_with_no_account: + frappe.msgprint( + _("No accounting entries for the following warehouses") + + ": \n" + + "\n".join(warehouse_with_no_account) + ) + + def _make_item_gl_entries_for_lcv(self, gl_entries: list, inventory_account_map: dict | None) -> None: + doc = self.doc + landed_cost_entries = doc.get_item_account_wise_lcv_entries() + + if not landed_cost_entries: + return + + for item in doc.items: + if item.landed_cost_voucher_amount and landed_cost_entries: + remarks = _("Accounting Entry for Landed Cost Voucher for SCR {0}").format(doc.name) + if (item.item_code, item.name) in landed_cost_entries: + _inv_dict = doc.get_inventory_account_dict(item, inventory_account_map) + + 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"]) + ) + + 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=_inv_dict["account"], + credit_in_account_currency=flt(amount["amount"]), + account_currency=account_currency, + project=item.project, + item=item, + ) + + account_currency = get_account_currency(item.expense_account) + + doc.add_gl_entry( + gl_entries=gl_entries, + account=item.expense_account, + cost_center=item.cost_center, + debit=0.0, + credit=credit_amount * -1, + remarks=remarks, + against_account=_inv_dict["account"], + debit_in_account_currency=flt(amount["amount"]), + account_currency=account_currency, + project=item.project, + item=item, + ) diff --git a/erpnext/subcontracting/doctype/subcontracting_receipt/subcontracting_receipt.py b/erpnext/subcontracting/doctype/subcontracting_receipt/subcontracting_receipt.py index 26ad0039070..1ae55f47017 100644 --- a/erpnext/subcontracting/doctype/subcontracting_receipt/subcontracting_receipt.py +++ b/erpnext/subcontracting/doctype/subcontracting_receipt/subcontracting_receipt.py @@ -11,7 +11,6 @@ from frappe.query_builder.functions import Sum from frappe.utils import cint, flt, get_link_to_form, getdate, nowdate import erpnext -from erpnext.accounts.utils import get_account_currency from erpnext.buying.utils import check_on_hold_or_closed_status from erpnext.controllers.subcontracting_controller import SubcontractingController from erpnext.setup.doctype.brand.brand import get_brand_defaults @@ -731,257 +730,11 @@ class SubcontractingReceipt(SubcontractingController): ) def get_gl_entries(self, inventory_account_map=None): - from erpnext.accounts.general_ledger import process_gl_map + from erpnext.subcontracting.doctype.subcontracting_receipt.services.gl_composer import ( + SubcontractingReceiptGLComposer, + ) - if not erpnext.is_perpetual_inventory_enabled(self.company): - return [] - - gl_entries = [] - self.make_item_gl_entries(gl_entries, inventory_account_map) - self.make_item_gl_entries_for_lcv(gl_entries, inventory_account_map) - - 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): - warehouse_with_no_account = [] - - supplied_items_details = frappe._dict() - for item in self.supplied_items: - supplied_items_details.setdefault(item.reference_name, []).append( - frappe._dict( - { - "item_code": item.rm_item_code, - "amount": item.amount, - "expense_account": item.expense_account, - "cost_center": item.cost_center, - } - ) - ) - - for item in self.items: - if flt(item.rate) and flt(item.qty): - _inv_dict = self.get_inventory_account_dict(item, inventory_account_map) - - if _inv_dict.get("account"): - stock_value_diff = frappe.db.get_value( - "Stock Ledger Entry", - { - "voucher_type": "Subcontracting Receipt", - "voucher_no": self.name, - "voucher_detail_no": item.name, - "warehouse": item.warehouse, - "is_cancelled": 0, - }, - "stock_value_difference", - ) - - remarks = self.get("remarks") or _("Accounting Entry for Stock") - - # Accepted Warehouse Account (Debit) - self.add_gl_entry( - gl_entries=gl_entries, - account=_inv_dict["account"], - cost_center=item.cost_center, - debit=stock_value_diff, - credit=0.0, - remarks=remarks, - against_account=item.expense_account, - account_currency=_inv_dict["account_currency"], - project=item.project, - item=item, - ) - - service_cost = flt( - item.service_cost_per_qty, item.precision("service_cost_per_qty") - ) * flt(item.qty, item.precision("qty")) - # Expense Account (Credit) - self.add_gl_entry( - gl_entries=gl_entries, - account=item.expense_account, - cost_center=item.cost_center, - debit=0.0, - credit=flt(stock_value_diff) - service_cost, - remarks=remarks, - against_account=_inv_dict["account"], - account_currency=get_account_currency(item.expense_account), - project=item.project, - item=item, - ) - - service_account = item.service_expense_account or item.expense_account - # Expense Account (Credit) - self.add_gl_entry( - gl_entries=gl_entries, - account=service_account, - cost_center=item.cost_center, - debit=0.0, - credit=service_cost, - remarks=remarks, - against_account=_inv_dict["account"], - account_currency=get_account_currency(service_account), - project=item.project, - item=item, - ) - - if flt(item.rm_supp_cost): - for rm_item in supplied_items_details.get(item.name): - _inv_dict = self.get_inventory_account_dict( - rm_item, inventory_account_map, "supplier_warehouse" - ) - - # Supplier Warehouse Account (Credit) - self.add_gl_entry( - gl_entries=gl_entries, - account=_inv_dict.get("account"), - cost_center=rm_item.cost_center or item.cost_center, - debit=0.0, - credit=flt(rm_item.amount), - remarks=remarks, - against_account=rm_item.expense_account or item.expense_account, - account_currency=_inv_dict.get("account_currency"), - project=item.project, - item=item, - ) - # Expense Account (Debit) - 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, - debit=flt(rm_item.amount), - credit=0.0, - remarks=remarks, - against_account=_inv_dict.get("account"), - account_currency=get_account_currency(item.expense_account), - project=item.project, - item=item, - ) - - # Expense Account (Debit) - if item.additional_cost_per_qty: - self.add_gl_entry( - gl_entries=gl_entries, - account=item.expense_account, - cost_center=self.cost_center or self.get_company_default("cost_center"), - debit=item.qty * item.additional_cost_per_qty, - credit=0.0, - remarks=remarks, - against_account=None, - account_currency=get_account_currency(item.expense_account), - ) - - if divisional_loss := flt(item.amount - stock_value_diff, item.precision("amount")): - loss_account = self.get_company_default( - "stock_adjustment_account", ignore_validation=True - ) - - # Loss Account (Credit) - self.add_gl_entry( - gl_entries=gl_entries, - account=loss_account, - cost_center=item.cost_center, - debit=0.0, - credit=divisional_loss, - remarks=remarks, - against_account=item.expense_account, - account_currency=get_account_currency(loss_account), - project=item.project, - item=item, - ) - # Expense Account (Debit) - self.add_gl_entry( - gl_entries=gl_entries, - account=item.expense_account, - cost_center=item.cost_center, - debit=divisional_loss, - credit=0.0, - remarks=remarks, - against_account=loss_account, - account_currency=get_account_currency(item.expense_account), - project=item.project, - item=item, - ) - elif ( - item.warehouse not in warehouse_with_no_account - or item.rejected_warehouse not in warehouse_with_no_account - ): - warehouse_with_no_account.append(item.warehouse) - - for row in self.additional_costs: - credit_amount = ( - flt(row.base_amount) - if (row.base_amount or row.account_currency != self.company_currency) - else flt(row.amount) - ) - - # Additional Cost Expense Account (Credit) - self.add_gl_entry( - gl_entries=gl_entries, - account=row.expense_account, - cost_center=self.cost_center or self.get_company_default("cost_center"), - debit=0.0, - credit=credit_amount, - remarks=remarks, - against_account=None, - account_currency=get_account_currency(row.expense_account), - ) - - if warehouse_with_no_account: - frappe.msgprint( - _("No accounting entries for the following warehouses") - + ": \n" - + "\n".join(warehouse_with_no_account) - ) - - def make_item_gl_entries_for_lcv(self, gl_entries, inventory_account_map): - landed_cost_entries = self.get_item_account_wise_lcv_entries() - - if not landed_cost_entries: - return - - for item in self.items: - if item.landed_cost_voucher_amount and landed_cost_entries: - remarks = _("Accounting Entry for Landed Cost Voucher for SCR {0}").format(self.name) - if (item.item_code, item.name) in landed_cost_entries: - _inv_dict = self.get_inventory_account_dict(item, inventory_account_map) - - 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"]) - ) - - 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=_inv_dict["account"], - credit_in_account_currency=flt(amount["amount"]), - account_currency=account_currency, - project=item.project, - item=item, - ) - - account_currency = get_account_currency(item.expense_account) - - # credit amount in negative to knock off the debit entry - self.add_gl_entry( - gl_entries=gl_entries, - account=item.expense_account, - cost_center=item.cost_center, - debit=0.0, - credit=credit_amount * -1, - remarks=remarks, - against_account=_inv_dict["account"], - debit_in_account_currency=flt(amount["amount"]), - account_currency=account_currency, - project=item.project, - item=item, - ) + return SubcontractingReceiptGLComposer(self).compose(inventory_account_map) def auto_create_purchase_receipt(self): if frappe.db.get_single_value("Buying Settings", "auto_create_purchase_receipt"):