refactor(accounts): GL composer pattern for SCR, AssetCapitalization, AssetRepair

Extracts get_gl_entries logic from SubcontractingReceipt,
AssetCapitalization, and AssetRepair into dedicated GL composer classes
under each doctype's services/ package. Each composer follows the
established BaseGLComposer / BaseStockGLComposer pattern, and the
original get_gl_entries becomes a 3-line shim.

- SubcontractingReceiptGLComposer(BaseStockGLComposer): moves
  make_item_gl_entries and make_item_gl_entries_for_lcv
- AssetCapitalizationGLComposer(BaseStockGLComposer): moves
  get_gl_entries_for_consumed_{stock,asset,service}_items and
  get_gl_entries_for_target_item; inventory_account_map/sle_map/precision
  become composer instance attributes
- AssetRepairGLComposer(BaseGLComposer): moves
  get_gl_entries_for_repair_cost and get_gl_entries_for_consumed_items
  (AR inherits AccountsController, not StockController)

All 29 GL snapshot tests and existing doctype test suites (32 SCR,
5 AC, 18 AR) pass.
This commit is contained in:
Nabin Hait
2026-05-27 15:34:54 +05:30
parent 8d3efe287e
commit 8783689ec5
9 changed files with 564 additions and 486 deletions

View File

@@ -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)

View File

@@ -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,
)
)

View File

@@ -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:

View File

@@ -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,
)
)

View File

@@ -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,
)

View File

@@ -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"):