mirror of
https://github.com/frappe/erpnext.git
synced 2026-08-14 15:11:52 +00:00
refactor: introduce BaseStockGLComposer, slim StockController.get_gl_entries
Moves the StockController.get_gl_entries body into erpnext/stock/services/base_stock_gl_composer.py → BaseStockGLComposer(BaseGLComposer). compose(inventory_account_map, default_expense_account, default_cost_center) contains all warehouse↔expense-account GL pair building and the internal-transfer rounding-diff block; all helpers (get_inventory_account_dict, get_stock_ledger_details, etc.) remain on self.doc and are called via doc.<method>. StockController.get_gl_entries becomes a 3-line shim. Delivery Note, Stock Entry, and Stock Reconciliation continue to work unchanged — DN inherits the shim directly; SE and SR override and call super(), which now delegates to the composer. Verified: 22 GL snapshots byte-identical on test-erpnext-v17.
This commit is contained in:
@@ -13,7 +13,6 @@ import erpnext
|
||||
from erpnext.accounts.general_ledger import (
|
||||
make_gl_entries,
|
||||
make_reverse_gl_entries,
|
||||
process_gl_map,
|
||||
)
|
||||
from erpnext.accounts.utils import cancel_exchange_gain_loss_journal, get_fiscal_year
|
||||
from erpnext.controllers.accounts_controller import AccountsController
|
||||
@@ -691,140 +690,10 @@ class StockController(AccountsController):
|
||||
def get_gl_entries(
|
||||
self, inventory_account_map=None, default_expense_account=None, default_cost_center=None
|
||||
):
|
||||
if not inventory_account_map:
|
||||
inventory_account_map = self.get_inventory_account_map()
|
||||
from erpnext.stock.services.base_stock_gl_composer import BaseStockGLComposer
|
||||
|
||||
sle_map = self.get_stock_ledger_details()
|
||||
voucher_details = self.get_voucher_details(default_expense_account, default_cost_center, sle_map)
|
||||
|
||||
gl_list = []
|
||||
warehouse_with_no_account = []
|
||||
precision = self.get_debit_field_precision()
|
||||
for item_row in voucher_details:
|
||||
sle_list = sle_map.get(item_row.name)
|
||||
sle_rounding_diff = 0.0
|
||||
if sle_list:
|
||||
for sle in sle_list:
|
||||
_inv_dict = self.get_inventory_account_dict(sle, inventory_account_map)
|
||||
|
||||
if _inv_dict.get("account"):
|
||||
# from warehouse account
|
||||
|
||||
sle_rounding_diff += flt(sle.stock_value_difference)
|
||||
|
||||
self.check_expense_account(item_row)
|
||||
|
||||
# expense account/ target_warehouse / source_warehouse
|
||||
if item_row.get("target_warehouse"):
|
||||
_target_wh_inv_dict = self.get_inventory_account_dict(
|
||||
item_row, inventory_account_map, warehouse_field="target_warehouse"
|
||||
)
|
||||
expense_account = _target_wh_inv_dict["account"]
|
||||
else:
|
||||
expense_account = item_row.expense_account
|
||||
|
||||
gl_list.append(
|
||||
self.get_gl_dict(
|
||||
{
|
||||
"account": _inv_dict["account"],
|
||||
"against": expense_account,
|
||||
"cost_center": item_row.cost_center,
|
||||
"project": sle.get("project") or item_row.project or self.get("project"),
|
||||
"remarks": self.get("remarks") or _("Accounting Entry for Stock"),
|
||||
"debit": flt(sle.stock_value_difference, precision),
|
||||
"is_opening": item_row.get("is_opening")
|
||||
or self.get("is_opening")
|
||||
or "No",
|
||||
},
|
||||
_inv_dict["account_currency"],
|
||||
item=item_row,
|
||||
)
|
||||
)
|
||||
|
||||
gl_list.append(
|
||||
self.get_gl_dict(
|
||||
{
|
||||
"account": expense_account,
|
||||
"against": _inv_dict["account"],
|
||||
"cost_center": item_row.cost_center,
|
||||
"remarks": self.get("remarks") or _("Accounting Entry for Stock"),
|
||||
"debit": -1 * flt(sle.stock_value_difference, precision),
|
||||
"project": sle.get("project")
|
||||
or item_row.get("project")
|
||||
or self.get("project"),
|
||||
"is_opening": item_row.get("is_opening")
|
||||
or self.get("is_opening")
|
||||
or "No",
|
||||
},
|
||||
item=item_row,
|
||||
)
|
||||
)
|
||||
elif sle.warehouse not in warehouse_with_no_account:
|
||||
warehouse_with_no_account.append(sle.warehouse)
|
||||
|
||||
if abs(sle_rounding_diff) > (1.0 / (10**precision)) and self.is_internal_transfer():
|
||||
warehouse_asset_account = ""
|
||||
if self.get("is_internal_customer"):
|
||||
_inv_dict = self.get_inventory_account_dict(
|
||||
item_row, inventory_account_map, warehouse_field="target_warehouse"
|
||||
)
|
||||
|
||||
warehouse_asset_account = _inv_dict.get("account") if _inv_dict else None
|
||||
elif self.get("is_internal_supplier"):
|
||||
_inv_dict = self.get_inventory_account_dict(item_row, inventory_account_map)
|
||||
|
||||
warehouse_asset_account = _inv_dict.get("account") if _inv_dict else None
|
||||
|
||||
expense_account = frappe.get_cached_value("Company", self.company, "default_expense_account")
|
||||
if not expense_account:
|
||||
frappe.throw(
|
||||
_(
|
||||
"Please set default cost of goods sold account in company {0} for booking rounding gain and loss during stock transfer"
|
||||
).format(frappe.bold(self.company))
|
||||
)
|
||||
|
||||
gl_list.append(
|
||||
self.get_gl_dict(
|
||||
{
|
||||
"account": expense_account,
|
||||
"against": warehouse_asset_account,
|
||||
"cost_center": item_row.cost_center,
|
||||
"project": item_row.project or self.get("project"),
|
||||
"remarks": _("Rounding gain/loss Entry for Stock Transfer"),
|
||||
"debit": sle_rounding_diff,
|
||||
"is_opening": item_row.get("is_opening") or self.get("is_opening") or "No",
|
||||
},
|
||||
_inv_dict["account_currency"],
|
||||
item=item_row,
|
||||
)
|
||||
)
|
||||
|
||||
gl_list.append(
|
||||
self.get_gl_dict(
|
||||
{
|
||||
"account": warehouse_asset_account,
|
||||
"against": expense_account,
|
||||
"cost_center": item_row.cost_center,
|
||||
"remarks": _("Rounding gain/loss Entry for Stock Transfer"),
|
||||
"credit": sle_rounding_diff,
|
||||
"project": item_row.get("project") or self.get("project"),
|
||||
"is_opening": item_row.get("is_opening") or self.get("is_opening") or "No",
|
||||
},
|
||||
item=item_row,
|
||||
)
|
||||
)
|
||||
|
||||
if warehouse_with_no_account:
|
||||
for wh in warehouse_with_no_account:
|
||||
if frappe.get_cached_value("Warehouse", wh, "company"):
|
||||
frappe.throw(
|
||||
_(
|
||||
"Warehouse {0} is not linked to any account, please mention the account in the warehouse record or set default inventory account in company {1}."
|
||||
).format(wh, self.company)
|
||||
)
|
||||
|
||||
return process_gl_map(
|
||||
gl_list, precision=precision, from_repost=frappe.flags.through_repost_item_valuation
|
||||
return BaseStockGLComposer(self).compose(
|
||||
inventory_account_map, default_expense_account, default_cost_center
|
||||
)
|
||||
|
||||
def get_debit_field_precision(self):
|
||||
|
||||
0
erpnext/stock/services/__init__.py
Normal file
0
erpnext/stock/services/__init__.py
Normal file
154
erpnext/stock/services/base_stock_gl_composer.py
Normal file
154
erpnext/stock/services/base_stock_gl_composer.py
Normal file
@@ -0,0 +1,154 @@
|
||||
# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
|
||||
# License: GNU General Public License v3. See license.txt
|
||||
|
||||
import frappe
|
||||
from frappe import _
|
||||
from frappe.utils import flt
|
||||
|
||||
from erpnext.accounts.general_ledger import process_gl_map
|
||||
from erpnext.accounts.services.base_gl_composer import BaseGLComposer
|
||||
|
||||
|
||||
class BaseStockGLComposer(BaseGLComposer):
|
||||
"""Shared GL composition logic for stock vouchers.
|
||||
|
||||
Subclasses override ``compose()`` and call ``super().compose()`` to get the
|
||||
warehouse ↔ expense-account GL pairs, then append any doctype-specific
|
||||
entries on top.
|
||||
"""
|
||||
|
||||
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
|
||||
|
||||
if not inventory_account_map:
|
||||
inventory_account_map = doc.get_inventory_account_map()
|
||||
|
||||
sle_map = doc.get_stock_ledger_details()
|
||||
voucher_details = doc.get_voucher_details(default_expense_account, default_cost_center, sle_map)
|
||||
|
||||
gl_list = []
|
||||
warehouse_with_no_account = []
|
||||
precision = doc.get_debit_field_precision()
|
||||
|
||||
for item_row in voucher_details:
|
||||
sle_list = sle_map.get(item_row.name)
|
||||
sle_rounding_diff = 0.0
|
||||
if sle_list:
|
||||
for sle in sle_list:
|
||||
_inv_dict = doc.get_inventory_account_dict(sle, inventory_account_map)
|
||||
|
||||
if _inv_dict.get("account"):
|
||||
sle_rounding_diff += flt(sle.stock_value_difference)
|
||||
|
||||
doc.check_expense_account(item_row)
|
||||
|
||||
if item_row.get("target_warehouse"):
|
||||
_target_wh_inv_dict = doc.get_inventory_account_dict(
|
||||
item_row, inventory_account_map, warehouse_field="target_warehouse"
|
||||
)
|
||||
expense_account = _target_wh_inv_dict["account"]
|
||||
else:
|
||||
expense_account = item_row.expense_account
|
||||
|
||||
gl_list.append(
|
||||
doc.get_gl_dict(
|
||||
{
|
||||
"account": _inv_dict["account"],
|
||||
"against": expense_account,
|
||||
"cost_center": item_row.cost_center,
|
||||
"project": sle.get("project") or item_row.project or doc.get("project"),
|
||||
"remarks": doc.get("remarks") or _("Accounting Entry for Stock"),
|
||||
"debit": flt(sle.stock_value_difference, precision),
|
||||
"is_opening": item_row.get("is_opening") or doc.get("is_opening") or "No",
|
||||
},
|
||||
_inv_dict["account_currency"],
|
||||
item=item_row,
|
||||
)
|
||||
)
|
||||
|
||||
gl_list.append(
|
||||
doc.get_gl_dict(
|
||||
{
|
||||
"account": expense_account,
|
||||
"against": _inv_dict["account"],
|
||||
"cost_center": item_row.cost_center,
|
||||
"remarks": doc.get("remarks") or _("Accounting Entry for Stock"),
|
||||
"debit": -1 * flt(sle.stock_value_difference, precision),
|
||||
"project": sle.get("project")
|
||||
or item_row.get("project")
|
||||
or doc.get("project"),
|
||||
"is_opening": item_row.get("is_opening") or doc.get("is_opening") or "No",
|
||||
},
|
||||
item=item_row,
|
||||
)
|
||||
)
|
||||
elif sle.warehouse not in warehouse_with_no_account:
|
||||
warehouse_with_no_account.append(sle.warehouse)
|
||||
|
||||
if abs(sle_rounding_diff) > (1.0 / (10**precision)) and doc.is_internal_transfer():
|
||||
warehouse_asset_account = ""
|
||||
if doc.get("is_internal_customer"):
|
||||
_inv_dict = doc.get_inventory_account_dict(
|
||||
item_row, inventory_account_map, warehouse_field="target_warehouse"
|
||||
)
|
||||
warehouse_asset_account = _inv_dict.get("account") if _inv_dict else None
|
||||
elif doc.get("is_internal_supplier"):
|
||||
_inv_dict = doc.get_inventory_account_dict(item_row, inventory_account_map)
|
||||
warehouse_asset_account = _inv_dict.get("account") if _inv_dict else None
|
||||
|
||||
expense_account = frappe.get_cached_value("Company", doc.company, "default_expense_account")
|
||||
if not expense_account:
|
||||
frappe.throw(
|
||||
_(
|
||||
"Please set default cost of goods sold account in company {0} for booking rounding gain and loss during stock transfer"
|
||||
).format(frappe.bold(doc.company))
|
||||
)
|
||||
|
||||
gl_list.append(
|
||||
doc.get_gl_dict(
|
||||
{
|
||||
"account": expense_account,
|
||||
"against": warehouse_asset_account,
|
||||
"cost_center": item_row.cost_center,
|
||||
"project": item_row.project or doc.get("project"),
|
||||
"remarks": _("Rounding gain/loss Entry for Stock Transfer"),
|
||||
"debit": sle_rounding_diff,
|
||||
"is_opening": item_row.get("is_opening") or doc.get("is_opening") or "No",
|
||||
},
|
||||
_inv_dict["account_currency"],
|
||||
item=item_row,
|
||||
)
|
||||
)
|
||||
|
||||
gl_list.append(
|
||||
doc.get_gl_dict(
|
||||
{
|
||||
"account": warehouse_asset_account,
|
||||
"against": expense_account,
|
||||
"cost_center": item_row.cost_center,
|
||||
"remarks": _("Rounding gain/loss Entry for Stock Transfer"),
|
||||
"credit": sle_rounding_diff,
|
||||
"project": item_row.get("project") or doc.get("project"),
|
||||
"is_opening": item_row.get("is_opening") or doc.get("is_opening") or "No",
|
||||
},
|
||||
item=item_row,
|
||||
)
|
||||
)
|
||||
|
||||
if warehouse_with_no_account:
|
||||
for wh in warehouse_with_no_account:
|
||||
if frappe.get_cached_value("Warehouse", wh, "company"):
|
||||
frappe.throw(
|
||||
_(
|
||||
"Warehouse {0} is not linked to any account, please mention the account in the warehouse record or set default inventory account in company {1}."
|
||||
).format(wh, doc.company)
|
||||
)
|
||||
|
||||
return process_gl_map(
|
||||
gl_list, precision=precision, from_repost=frappe.flags.through_repost_item_valuation
|
||||
)
|
||||
Reference in New Issue
Block a user