mirror of
https://github.com/frappe/erpnext.git
synced 2026-09-09 15:29:30 +00:00
Merge pull request #58887 from aerele/fix/additional-cost-zero-valued-items-v16
fix(stock): distribute additional costs when incoming items have no value (backport #58842)
This commit is contained in:
@@ -1775,22 +1775,28 @@ class StockEntry(StockController, SubcontractingInwardController):
|
||||
|
||||
self.total_additional_costs = sum(flt(t.base_amount) for t in self.get("additional_costs"))
|
||||
|
||||
if self.purpose in ("Repack", "Manufacture"):
|
||||
incoming_items_cost = sum(flt(t.basic_amount) for t in self.get("items") if t.is_finished_item)
|
||||
else:
|
||||
incoming_items_cost = sum(flt(t.basic_amount) for t in self.get("items") if t.t_warehouse)
|
||||
|
||||
if not incoming_items_cost:
|
||||
return
|
||||
incoming_items, basis, total_basis = self.get_additional_cost_allocation()
|
||||
|
||||
for d in self.get("items"):
|
||||
if self.purpose in ("Repack", "Manufacture") and not d.is_finished_item:
|
||||
d.additional_cost = 0
|
||||
continue
|
||||
elif not d.t_warehouse:
|
||||
d.additional_cost = 0
|
||||
continue
|
||||
d.additional_cost = (flt(d.basic_amount) / incoming_items_cost) * self.total_additional_costs
|
||||
d.additional_cost = 0
|
||||
|
||||
if not total_basis:
|
||||
return
|
||||
|
||||
for d in incoming_items:
|
||||
d.additional_cost = (flt(d.get(basis)) / total_basis) * self.total_additional_costs
|
||||
|
||||
def get_additional_cost_allocation(self):
|
||||
if self.purpose in ("Repack", "Manufacture"):
|
||||
incoming_items = [d for d in self.get("items") if d.is_finished_item]
|
||||
else:
|
||||
incoming_items = [d for d in self.get("items") if d.t_warehouse]
|
||||
|
||||
total_basic_amount = sum(flt(d.basic_amount) for d in incoming_items)
|
||||
if total_basic_amount:
|
||||
return incoming_items, "basic_amount", total_basic_amount
|
||||
|
||||
return incoming_items, "transfer_qty", sum(flt(d.transfer_qty) for d in incoming_items)
|
||||
|
||||
def update_valuation_rate(self, reset_outgoing_rate=True):
|
||||
for d in self.get("items"):
|
||||
@@ -2357,32 +2363,20 @@ class StockEntry(StockController, SubcontractingInwardController):
|
||||
def get_gl_entries(self, inventory_account_map):
|
||||
gl_entries = super().get_gl_entries(inventory_account_map)
|
||||
|
||||
if self.purpose in ("Repack", "Manufacture"):
|
||||
total_basic_amount = sum(flt(t.basic_amount) for t in self.get("items") if t.is_finished_item)
|
||||
else:
|
||||
total_basic_amount = sum(flt(t.basic_amount) for t in self.get("items") if t.t_warehouse)
|
||||
|
||||
divide_based_on = total_basic_amount
|
||||
|
||||
if self.get("additional_costs") and not total_basic_amount:
|
||||
# if total_basic_amount is 0, distribute additional charges based on qty
|
||||
divide_based_on = sum(item.qty for item in list(self.get("items")))
|
||||
incoming_items, basis, divide_based_on = self.get_additional_cost_allocation()
|
||||
|
||||
item_account_wise_additional_cost = {}
|
||||
|
||||
for t in self.get("additional_costs"):
|
||||
for d in self.get("items"):
|
||||
if self.purpose in ("Repack", "Manufacture") and not d.is_finished_item:
|
||||
continue
|
||||
elif not d.t_warehouse:
|
||||
continue
|
||||
|
||||
if not divide_based_on:
|
||||
continue
|
||||
for d in incoming_items:
|
||||
item_account_wise_additional_cost.setdefault((d.item_code, d.name), {})
|
||||
item_account_wise_additional_cost[(d.item_code, d.name)].setdefault(
|
||||
t.expense_account, {"amount": 0.0, "base_amount": 0.0}
|
||||
)
|
||||
|
||||
multiply_based_on = d.basic_amount if total_basic_amount else d.qty
|
||||
multiply_based_on = flt(d.get(basis))
|
||||
|
||||
item_account_wise_additional_cost[(d.item_code, d.name)][t.expense_account]["amount"] += (
|
||||
flt(t.amount * multiply_based_on) / divide_based_on
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
|
||||
|
||||
from frappe.permissions import add_user_permission, remove_user_permission
|
||||
from frappe.utils import add_days, cstr, flt, get_time, getdate, nowtime, today
|
||||
from frappe.utils import add_days, cstr, flt, get_time, getdate, nowdate, nowtime, today
|
||||
|
||||
from erpnext.accounts.doctype.account.test_account import get_inventory_account
|
||||
from erpnext.controllers.accounts_controller import InvalidQtyError
|
||||
@@ -1953,10 +1953,12 @@ class TestStockEntry(ERPNextTestSuite):
|
||||
se.insert()
|
||||
se.submit()
|
||||
|
||||
self.assertEqual([33.33, 66.67], [flt(d.additional_cost, 2) for d in se.items])
|
||||
|
||||
self.check_gl_entries(
|
||||
"Stock Entry",
|
||||
se.name,
|
||||
sorted([["Stock Adjustment - TCP1", 100.0, 0.0], ["Miscellaneous Expenses - TCP1", 0.0, 100.0]]),
|
||||
sorted([["Stock In Hand - TCP1", 100.0, 0.0], ["Miscellaneous Expenses - TCP1", 0.0, 100.0]]),
|
||||
)
|
||||
|
||||
def test_conversion_factor_change(self):
|
||||
@@ -2002,6 +2004,184 @@ class TestStockEntry(ERPNextTestSuite):
|
||||
distributed_costs = [d.additional_cost for d in se.items]
|
||||
self.assertEqual([0.0, 100.0, 0.0], distributed_costs)
|
||||
|
||||
def test_additional_cost_distribution_manufacture_zero_valued_items(self):
|
||||
se = frappe.get_doc(
|
||||
doctype="Stock Entry",
|
||||
purpose="Manufacture",
|
||||
additional_costs=[frappe._dict(base_amount=100)],
|
||||
items=[
|
||||
frappe._dict(item_code="RM", basic_amount=0, transfer_qty=10),
|
||||
frappe._dict(
|
||||
item_code="FG", basic_amount=0, transfer_qty=5, t_warehouse="X", is_finished_item=1
|
||||
),
|
||||
frappe._dict(item_code="scrap", basic_amount=0, transfer_qty=2, t_warehouse="X"),
|
||||
],
|
||||
)
|
||||
|
||||
se.distribute_additional_costs()
|
||||
|
||||
distributed_costs = [d.additional_cost for d in se.items]
|
||||
self.assertEqual([0.0, 100.0, 0.0], distributed_costs)
|
||||
|
||||
def test_additional_cost_distribution_zero_valued_items(self):
|
||||
se = frappe.get_doc(
|
||||
doctype="Stock Entry",
|
||||
purpose="Material Receipt",
|
||||
additional_costs=[frappe._dict(base_amount=100)],
|
||||
items=[
|
||||
frappe._dict(item_code="RECEIVED_1", basic_amount=0, transfer_qty=20, t_warehouse="X"),
|
||||
frappe._dict(item_code="RECEIVED_2", basic_amount=0, transfer_qty=30, t_warehouse="X"),
|
||||
],
|
||||
)
|
||||
|
||||
se.distribute_additional_costs()
|
||||
|
||||
distributed_costs = [d.additional_cost for d in se.items]
|
||||
self.assertEqual([40.0, 60.0], distributed_costs)
|
||||
|
||||
def test_additional_cost_gl_for_zero_valued_manufacture(self):
|
||||
company = "_Test Company with perpetual inventory"
|
||||
rm = make_item("_Test Zero Rate RM", {"is_stock_item": 1}).name
|
||||
fg = make_item("_Test Zero Rate FG", {"is_stock_item": 1}).name
|
||||
|
||||
receipt = frappe.get_doc(
|
||||
{
|
||||
"doctype": "Stock Entry",
|
||||
"purpose": "Material Receipt",
|
||||
"stock_entry_type": "Material Receipt",
|
||||
"posting_date": nowdate(),
|
||||
"company": company,
|
||||
"items": [
|
||||
{
|
||||
"item_code": rm,
|
||||
"qty": 5,
|
||||
"basic_rate": 0,
|
||||
"uom": "Nos",
|
||||
"t_warehouse": "Stores - TCP1",
|
||||
"allow_zero_valuation_rate": 1,
|
||||
"cost_center": "Main - TCP1",
|
||||
}
|
||||
],
|
||||
}
|
||||
)
|
||||
receipt.insert()
|
||||
receipt.submit()
|
||||
|
||||
se = frappe.get_doc(
|
||||
{
|
||||
"doctype": "Stock Entry",
|
||||
"purpose": "Manufacture",
|
||||
"stock_entry_type": "Manufacture",
|
||||
"posting_date": nowdate(),
|
||||
"company": company,
|
||||
"items": [
|
||||
{
|
||||
"item_code": rm,
|
||||
"qty": 5,
|
||||
"uom": "Nos",
|
||||
"s_warehouse": "Stores - TCP1",
|
||||
"cost_center": "Main - TCP1",
|
||||
},
|
||||
{
|
||||
"item_code": fg,
|
||||
"qty": 5,
|
||||
"uom": "Nos",
|
||||
"t_warehouse": "Finished Goods - TCP1",
|
||||
"is_finished_item": 1,
|
||||
"cost_center": "Main - TCP1",
|
||||
},
|
||||
],
|
||||
"additional_costs": [
|
||||
{
|
||||
"expense_account": "Miscellaneous Expenses - TCP1",
|
||||
"amount": 500,
|
||||
"description": "freight",
|
||||
}
|
||||
],
|
||||
}
|
||||
)
|
||||
se.insert()
|
||||
se.submit()
|
||||
|
||||
self.assertEqual(500.0, se.items[1].additional_cost)
|
||||
self.check_gl_entries(
|
||||
"Stock Entry",
|
||||
se.name,
|
||||
sorted([["Stock In Hand - TCP1", 500.0, 0.0], ["Miscellaneous Expenses - TCP1", 0.0, 500.0]]),
|
||||
)
|
||||
|
||||
def test_additional_cost_gl_matches_valuation_split(self):
|
||||
company = "_Test Company with perpetual inventory"
|
||||
cost_center = "_Test Additional Cost CC - TCP1"
|
||||
if not frappe.db.exists("Cost Center", cost_center):
|
||||
frappe.get_doc(
|
||||
{
|
||||
"doctype": "Cost Center",
|
||||
"cost_center_name": "_Test Additional Cost CC",
|
||||
"company": company,
|
||||
"is_group": 0,
|
||||
"parent_cost_center": "_Test Company with perpetual inventory - TCP1",
|
||||
}
|
||||
).insert()
|
||||
|
||||
uoms = [{"uom": "Nos", "conversion_factor": 1}, {"uom": "Box", "conversion_factor": 2}]
|
||||
item_a = make_item("_Test Addl Cost CF A", {"is_stock_item": 1, "uoms": uoms}).name
|
||||
uoms[1]["conversion_factor"] = 3
|
||||
item_b = make_item("_Test Addl Cost CF B", {"is_stock_item": 1, "uoms": uoms}).name
|
||||
|
||||
se = frappe.get_doc(
|
||||
{
|
||||
"doctype": "Stock Entry",
|
||||
"purpose": "Material Receipt",
|
||||
"stock_entry_type": "Material Receipt",
|
||||
"posting_date": nowdate(),
|
||||
"company": company,
|
||||
"items": [
|
||||
{
|
||||
"item_code": item_a,
|
||||
"qty": 1,
|
||||
"basic_rate": 0,
|
||||
"uom": "Box",
|
||||
"conversion_factor": 2,
|
||||
"t_warehouse": "Stores - TCP1",
|
||||
"allow_zero_valuation_rate": 1,
|
||||
"cost_center": "Main - TCP1",
|
||||
},
|
||||
{
|
||||
"item_code": item_b,
|
||||
"qty": 1,
|
||||
"basic_rate": 0,
|
||||
"uom": "Box",
|
||||
"conversion_factor": 3,
|
||||
"t_warehouse": "Stores - TCP1",
|
||||
"allow_zero_valuation_rate": 1,
|
||||
"cost_center": cost_center,
|
||||
},
|
||||
],
|
||||
"additional_costs": [
|
||||
{
|
||||
"expense_account": "Miscellaneous Expenses - TCP1",
|
||||
"amount": 100,
|
||||
"description": "misc",
|
||||
}
|
||||
],
|
||||
}
|
||||
)
|
||||
se.insert()
|
||||
se.submit()
|
||||
|
||||
self.assertEqual([40.0, 60.0], [flt(d.additional_cost, 2) for d in se.items])
|
||||
|
||||
expense_by_cost_center = frappe.get_all(
|
||||
"GL Entry",
|
||||
filters={"voucher_no": se.name, "account": "Miscellaneous Expenses - TCP1"},
|
||||
fields=["cost_center", "credit"],
|
||||
)
|
||||
self.assertEqual(
|
||||
{"Main - TCP1": 40.0, cost_center: 60.0},
|
||||
{d.cost_center: d.credit for d in expense_by_cost_center},
|
||||
)
|
||||
|
||||
def test_additional_cost_distribution_non_manufacture(self):
|
||||
se = frappe.get_doc(
|
||||
doctype="Stock Entry",
|
||||
|
||||
Reference in New Issue
Block a user