Compare commits

..

1 Commits

Author SHA1 Message Date
Diptanil Saha
4dc59daba1 fix(accounts): update AU standard chart of accounts (#57145)
Co-authored-by: Jebajebas <jeba.j@arus.co.in>
(cherry picked from commit fee3a6e0fd)
2026-07-30 02:22:23 +00:00
4 changed files with 23 additions and 188 deletions

View File

@@ -4,7 +4,7 @@ import inspect
import frappe
from frappe.utils.user import is_website_user
__version__ = "15.118.3"
__version__ = "15.112.0"
def get_default_company(user=None):

View File

@@ -24,7 +24,8 @@
"account_number": "11530"
},
"account_number": "115",
"is_group": 1
"is_group": 1,
"account_type": "Bank"
},
"Trade Receivables": {
"Trade Debtors": {
@@ -529,6 +530,13 @@
"account_number": "630",
"is_group": 1
},
"Accrued Manufacturing Expenses": {
"Accrued Expenses - Manufacturing": {
"account_number": "63510"
},
"account_number": "635",
"is_group": 1
},
"account_number": "63",
"is_group": 1
},
@@ -814,4 +822,4 @@
"root_type": "Expense"
}
}
}
}

View File

@@ -1321,7 +1321,6 @@ class StockEntry(StockController):
# Set rate for outgoing items
outgoing_items_cost = self.set_rate_for_outgoing_items(reset_outgoing_rate, raise_error_if_no_rate)
finished_item_qty = sum(d.transfer_qty for d in self.items if d.is_finished_item)
has_consumption_basis = self.has_consumption_basis()
items = []
# Set basic rate for incoming items
@@ -1329,8 +1328,6 @@ class StockEntry(StockController):
if d.s_warehouse or d.set_basic_rate_manually:
continue
rate_derived_from_consumption = False
if d.allow_zero_valuation_rate:
d.basic_rate = 0.0
items.append(d.item_code)
@@ -1338,17 +1335,12 @@ class StockEntry(StockController):
elif d.is_finished_item:
if self.purpose == "Manufacture":
d.basic_rate = self.get_basic_rate_for_manufactured_item(
finished_item_qty, outgoing_items_cost, has_consumption_basis
finished_item_qty, outgoing_items_cost
)
rate_derived_from_consumption = has_consumption_basis
elif self.purpose == "Repack":
d.basic_rate = self.get_basic_rate_for_repacked_items(d.transfer_qty, outgoing_items_cost)
# Repack rate comes from consumed source-warehouse rows, not consumption entries
rate_derived_from_consumption = any(item.s_warehouse for item in self.get("items"))
# A rate of zero derived from the consumed items is their actual cost, not a missing
# rate. Falling back to the item's valuation here would value free inputs as output.
if not d.basic_rate and not d.allow_zero_valuation_rate and not rate_derived_from_consumption:
if not d.basic_rate and not d.allow_zero_valuation_rate:
if self.is_new():
raise_error_if_no_rate = False
@@ -1383,31 +1375,6 @@ class StockEntry(StockController):
frappe.msgprint(message, alert=True)
def has_consumption_basis(self) -> bool:
"""Whether the cost of the consumed items is known, even when that cost is zero."""
if any(d.s_warehouse for d in self.get("items")):
return True
settings = frappe.get_single("Manufacturing Settings")
if settings.material_consumption and settings.get_rm_cost_from_consumption_entry and self.work_order:
return bool(self.get_consumption_entries())
return False
def get_consumption_entries(self) -> list[str]:
# Cached: queried in both has_consumption_basis() and get_basic_rate_for_manufactured_item()
if getattr(self, "_consumption_entries", None) is None:
self._consumption_entries = frappe.get_all(
"Stock Entry",
filters={
"docstatus": 1,
"work_order": self.work_order,
"purpose": "Material Consumption for Manufacture",
},
pluck="name",
)
return self._consumption_entries
def set_rate_for_outgoing_items(self, reset_outgoing_rate=True, raise_error_if_no_rate=True):
outgoing_items_cost = 0.0
for d in self.get("items"):
@@ -1461,16 +1428,21 @@ class StockEntry(StockController):
)
return flt(outgoing_items_cost / total_fg_qty)
def get_basic_rate_for_manufactured_item(
self, finished_item_qty, outgoing_items_cost=0, has_consumption_basis=False
) -> float:
def get_basic_rate_for_manufactured_item(self, finished_item_qty, outgoing_items_cost=0) -> float:
settings = frappe.get_single("Manufacturing Settings")
scrap_items_cost = sum([flt(d.basic_amount) for d in self.get("items") if d.is_scrap_item])
if settings.material_consumption:
if settings.get_rm_cost_from_consumption_entry and self.work_order:
# Validate only if Material Consumption Entry exists for the Work Order.
if self.get_consumption_entries():
if frappe.db.exists(
"Stock Entry",
{
"docstatus": 1,
"work_order": self.work_order,
"purpose": "Material Consumption for Manufacture",
},
):
for item in self.items:
if not item.is_finished_item and not item.is_scrap_item:
label = frappe.get_meta(settings.doctype).get_label(
@@ -1517,9 +1489,7 @@ class StockEntry(StockController):
)
).run()[0][0] or 0
# Estimate from the BOM only when nothing was consumed. A consumed cost of zero is a
# real cost, so substituting BOM rates would value free inputs as output.
elif not outgoing_items_cost and not has_consumption_basis:
elif not outgoing_items_cost:
bom_items = self.get_bom_raw_materials(finished_item_qty)
outgoing_items_cost = sum([flt(row.qty) * flt(row.rate) for row in bom_items.values()])

View File

@@ -2574,149 +2574,6 @@ class TestStockEntry(FrappeTestCase):
material_request.reload()
self.assertEqual(material_request.transfer_status, "Completed")
def test_manufacture_with_zero_valued_raw_material(self):
# A finished good produced from free inputs is worth nothing. Falling back to the item's
# own valuation would create value out of nothing and inflate it on every production run.
fg_item = make_item(properties={"is_stock_item": 1}).name
rm_item = make_item(properties={"is_stock_item": 1}).name
warehouse = "_Test Warehouse - _TC"
fg_warehouse = "Finished Goods - _TC"
rm_receipt = make_stock_entry(item_code=rm_item, target=warehouse, qty=100, rate=0, do_not_save=True)
rm_receipt.items[0].allow_zero_valuation_rate = 1
rm_receipt.save()
rm_receipt.submit()
# the finished good already carries a valuation in the target warehouse
make_stock_entry(item_code=fg_item, target=fg_warehouse, qty=10, rate=100)
se = frappe.new_doc("Stock Entry")
se.purpose = se.stock_entry_type = "Manufacture"
se.company = "_Test Company"
se.append(
"items",
{"item_code": rm_item, "s_warehouse": warehouse, "qty": 10, "conversion_factor": 1},
)
se.append(
"items",
{
"item_code": fg_item,
"t_warehouse": fg_warehouse,
"qty": 10,
"is_finished_item": 1,
"conversion_factor": 1,
},
)
se.save()
self.assertEqual(se.items[0].basic_amount, 0)
self.assertEqual(se.items[1].basic_rate, 0)
self.assertEqual(se.items[1].basic_amount, 0)
se.submit()
fg_sle = frappe.db.get_value(
"Stock Ledger Entry",
{"voucher_no": se.name, "item_code": fg_item, "is_cancelled": 0},
["incoming_rate", "stock_value_difference"],
as_dict=True,
)
self.assertEqual(fg_sle.incoming_rate, 0)
self.assertEqual(fg_sle.stock_value_difference, 0)
def _make_wo_for_free_raw_material(self, rm_item, fg_item, bom_no):
from erpnext.manufacturing.doctype.work_order.test_work_order import make_wo_order_test_record
from erpnext.manufacturing.doctype.work_order.work_order import (
make_stock_entry as make_stock_entry_from_wo,
)
receipt = make_stock_entry(item_code=rm_item, target="Stores - _TC", qty=10, rate=0, do_not_save=True)
receipt.items[0].allow_zero_valuation_rate = 1
receipt.save()
receipt.submit()
wo = make_wo_order_test_record(production_item=fg_item, bom_no=bom_no, qty=10)
transfer = frappe.get_doc(make_stock_entry_from_wo(wo.name, "Material Transfer for Manufacture", 10))
transfer.items[0].s_warehouse = "Stores - _TC"
transfer.insert().submit()
return wo
@change_settings(
"Manufacturing Settings", {"material_consumption": 1, "get_rm_cost_from_consumption_entry": 0}
)
def test_manufacture_does_not_fall_back_to_bom_cost_for_free_raw_material(self):
# The BOM is only an estimate for when nothing was consumed. Items that were consumed and
# cost nothing are a real cost, so a BOM rate must not stand in for them.
from erpnext.manufacturing.doctype.production_plan.test_production_plan import make_bom
from erpnext.manufacturing.doctype.work_order.work_order import (
make_stock_entry as make_stock_entry_from_wo,
)
rm_item = make_item(properties={"is_stock_item": 1}).name
fg_item = make_item(properties={"is_stock_item": 1}).name
frappe.get_doc(
{
"doctype": "Item Price",
"item_code": rm_item,
"price_list": "_Test Price List India",
"price_list_rate": 150,
"buying": 1,
}
).insert()
# price the BOM off the price list so that it carries a rate the free stock does not
bom = make_bom(item=fg_item, raw_materials=[rm_item], do_not_save=True)
bom.rm_cost_as_per = "Price List"
bom.buying_price_list = "_Test Price List India"
bom.currency = "INR"
bom.save()
bom.submit()
wo = self._make_wo_for_free_raw_material(rm_item, fg_item, bom.name)
manufacture = frappe.get_doc(make_stock_entry_from_wo(wo.name, "Manufacture", 10))
manufacture.save()
fg_row = next(d for d in manufacture.items if d.is_finished_item)
self.assertEqual(fg_row.basic_rate, 0)
self.assertEqual(fg_row.basic_amount, 0)
@change_settings(
"Manufacturing Settings", {"material_consumption": 1, "get_rm_cost_from_consumption_entry": 1}
)
def test_manufacture_with_zero_valued_consumption_entry(self):
# The raw material is consumed by a separate entry, so the Manufacture entry carries no
# consumed rows of its own. Its cost is still known, and it is zero.
from erpnext.manufacturing.doctype.production_plan.test_production_plan import make_bom
from erpnext.manufacturing.doctype.work_order.work_order import (
make_stock_entry as make_stock_entry_from_wo,
)
rm_item = make_item(properties={"is_stock_item": 1}).name
fg_item = make_item(properties={"is_stock_item": 1}).name
# the finished good already carries a valuation in the work order's target warehouse
make_stock_entry(item_code=fg_item, target="_Test Warehouse 1 - _TC", qty=10, rate=100)
bom = make_bom(item=fg_item, raw_materials=[rm_item]).name
wo = self._make_wo_for_free_raw_material(rm_item, fg_item, bom)
consumption = frappe.get_doc(
make_stock_entry_from_wo(wo.name, "Material Consumption for Manufacture", 10)
)
consumption.insert().submit()
manufacture = frappe.get_doc(make_stock_entry_from_wo(wo.name, "Manufacture", 10))
manufacture.save()
fg_row = next(d for d in manufacture.items if d.is_finished_item)
self.assertEqual(fg_row.basic_rate, 0)
self.assertEqual(fg_row.basic_amount, 0)
def test_disassemble_entry_without_wo(self):
from erpnext.manufacturing.doctype.production_plan.test_production_plan import make_bom