mirror of
https://github.com/frappe/erpnext.git
synced 2026-05-22 14:39:19 +00:00
Co-authored-by: Mihir Kandoi <kandoimihir@gmail.com> fix: consumed operation cost calculation (#54858)
This commit is contained in:
@@ -281,10 +281,10 @@ class StatusUpdater(Document):
|
||||
|
||||
# get unique transactions to update
|
||||
for d in self.get_all_children():
|
||||
if hasattr(d, "qty") and d.qty < 0 and not self.get("is_return"):
|
||||
if hasattr(d, "qty") and flt(d.qty) < 0 and not self.get("is_return"):
|
||||
frappe.throw(_("For an item {0}, quantity must be positive number").format(d.item_code))
|
||||
|
||||
if hasattr(d, "qty") and d.qty > 0 and self.get("is_return"):
|
||||
if hasattr(d, "qty") and flt(d.qty) > 0 and self.get("is_return"):
|
||||
frappe.throw(_("For an item {0}, quantity must be negative number").format(d.item_code))
|
||||
|
||||
if (
|
||||
|
||||
@@ -1638,12 +1638,12 @@ def add_non_stock_items_cost(stock_entry, work_order, expense_account, job_card=
|
||||
)
|
||||
|
||||
|
||||
def add_operating_cost_component_wise(
|
||||
stock_entry, work_order=None, consumed_operating_cost=None, op_expense_account=None, job_card=None
|
||||
):
|
||||
def add_operating_cost_component_wise(stock_entry, work_order=None, op_expense_account=None, job_card=None):
|
||||
if not work_order:
|
||||
return False
|
||||
|
||||
from erpnext.stock.doctype.stock_entry.stock_entry import get_consumed_operating_cost
|
||||
|
||||
cost_added = False
|
||||
for row in work_order.operations:
|
||||
if job_card and job_card.operation_id != row.name:
|
||||
@@ -1661,18 +1661,32 @@ def add_operating_cost_component_wise(
|
||||
},
|
||||
)
|
||||
|
||||
consumed_operating_cost = (
|
||||
get_consumed_operating_cost(work_order.name, stock_entry.bom_no, row.name) or []
|
||||
)
|
||||
for wc in workstation_cost:
|
||||
expense_account = (
|
||||
get_component_account(wc.operating_component, stock_entry.company) or op_expense_account
|
||||
)
|
||||
consumed_op_cost = next(
|
||||
(
|
||||
cost
|
||||
for cost in consumed_operating_cost
|
||||
if cost.get("operating_component") == wc.operating_component
|
||||
),
|
||||
{},
|
||||
)
|
||||
actual_cp_operating_cost = flt(
|
||||
flt(wc.operating_cost) * flt(flt(row.actual_operation_time) / 60.0) - consumed_operating_cost,
|
||||
flt(wc.operating_cost) * flt(flt(row.actual_operation_time) / 60.0)
|
||||
- flt(consumed_op_cost.get("consumed_cost")),
|
||||
row.precision("actual_operating_cost"),
|
||||
)
|
||||
|
||||
per_unit_cost = flt(actual_cp_operating_cost) / flt(row.completed_qty - work_order.produced_qty)
|
||||
remaining_qty = row.completed_qty - consumed_op_cost.get("consumed_qty", 0)
|
||||
per_unit_cost = actual_cp_operating_cost / (remaining_qty or 1)
|
||||
operating_cost = per_unit_cost * stock_entry.fg_completed_qty
|
||||
|
||||
if per_unit_cost:
|
||||
if actual_cp_operating_cost:
|
||||
stock_entry.append(
|
||||
"additional_costs",
|
||||
{
|
||||
@@ -1680,8 +1694,14 @@ def add_operating_cost_component_wise(
|
||||
"description": _("{0} Operating Cost for operation {1}").format(
|
||||
wc.operating_component, row.operation
|
||||
),
|
||||
"amount": per_unit_cost * flt(stock_entry.fg_completed_qty),
|
||||
"amount": flt(
|
||||
min(operating_cost, actual_cp_operating_cost),
|
||||
frappe.get_precision("Landed Cost Taxes and Charges", "amount"),
|
||||
),
|
||||
"has_operating_cost": 1,
|
||||
"operation_id": row.name,
|
||||
"operating_component": wc.operating_component,
|
||||
"qty": min(remaining_qty, stock_entry.fg_completed_qty),
|
||||
},
|
||||
)
|
||||
|
||||
@@ -1699,17 +1719,15 @@ def get_component_account(parent, company):
|
||||
|
||||
def add_operations_cost(stock_entry, work_order=None, expense_account=None, job_card=None):
|
||||
from erpnext.stock.doctype.stock_entry.stock_entry import (
|
||||
get_consumed_operating_cost,
|
||||
get_operating_cost_per_unit,
|
||||
get_remaining_operating_cost,
|
||||
)
|
||||
|
||||
operating_cost_per_unit = get_operating_cost_per_unit(work_order, stock_entry.bom_no)
|
||||
remaining_operating_cost = get_remaining_operating_cost(work_order, stock_entry.bom_no)
|
||||
|
||||
if operating_cost_per_unit:
|
||||
if remaining_operating_cost:
|
||||
cost_added = add_operating_cost_component_wise(
|
||||
stock_entry,
|
||||
work_order,
|
||||
get_consumed_operating_cost(work_order.name, stock_entry.bom_no),
|
||||
expense_account,
|
||||
job_card=job_card,
|
||||
)
|
||||
@@ -1720,7 +1738,10 @@ def add_operations_cost(stock_entry, work_order=None, expense_account=None, job_
|
||||
{
|
||||
"expense_account": expense_account,
|
||||
"description": _("Operating Cost as per Work Order / BOM"),
|
||||
"amount": operating_cost_per_unit * flt(stock_entry.fg_completed_qty),
|
||||
"amount": flt(
|
||||
remaining_operating_cost * stock_entry.fg_completed_qty,
|
||||
frappe.get_precision("Landed Cost Taxes and Charges", "amount"),
|
||||
),
|
||||
"has_operating_cost": 1,
|
||||
},
|
||||
)
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
{
|
||||
"actions": [],
|
||||
"allow_bulk_edit": 1,
|
||||
"autoname": "naming_series:",
|
||||
"creation": "2026-03-31 21:06:16.282931",
|
||||
"doctype": "DocType",
|
||||
@@ -155,6 +156,7 @@
|
||||
"fieldname": "wip_warehouse",
|
||||
"fieldtype": "Link",
|
||||
"label": "WIP Warehouse",
|
||||
"link_filters": "[[\"Warehouse\",\"disabled\",\"=\",0],[\"Warehouse\",\"is_group\",\"=\",0]]",
|
||||
"mandatory_depends_on": "eval:!doc.finished_good || doc.skip_material_transfer === 0 || (doc.skip_material_transfer && doc.backflush_from_wip_warehouse)",
|
||||
"options": "Warehouse"
|
||||
},
|
||||
@@ -506,6 +508,7 @@
|
||||
"fieldname": "target_warehouse",
|
||||
"fieldtype": "Link",
|
||||
"label": "Target Warehouse",
|
||||
"link_filters": "[[\"Warehouse\",\"disabled\",\"=\",0],[\"Warehouse\",\"is_group\",\"=\",0]]",
|
||||
"mandatory_depends_on": "eval:doc.track_semi_finished_goods",
|
||||
"options": "Warehouse"
|
||||
},
|
||||
@@ -518,6 +521,7 @@
|
||||
"fieldname": "source_warehouse",
|
||||
"fieldtype": "Link",
|
||||
"label": "Source Warehouse",
|
||||
"link_filters": "[[\"Warehouse\",\"disabled\",\"=\",0],[\"Warehouse\",\"is_group\",\"=\",0]]",
|
||||
"options": "Warehouse"
|
||||
},
|
||||
{
|
||||
@@ -627,7 +631,7 @@
|
||||
"grid_page_length": 50,
|
||||
"is_submittable": 1,
|
||||
"links": [],
|
||||
"modified": "2026-03-31 21:06:48.987740",
|
||||
"modified": "2026-05-12 12:17:17.750857",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Manufacturing",
|
||||
"name": "Job Card",
|
||||
|
||||
@@ -1080,6 +1080,243 @@ class TestJobCard(ERPNextTestSuite):
|
||||
self.assertEqual(s.items[3].item_code, "_Test Item")
|
||||
self.assertEqual(s.items[3].transfer_qty, 2)
|
||||
|
||||
@ERPNextTestSuite.change_settings(
|
||||
"Manufacturing Settings", {"overproduction_percentage_for_work_order": 100}
|
||||
)
|
||||
def test_operating_cost_with_overproduction(self):
|
||||
from erpnext.manufacturing.doctype.routing.test_routing import (
|
||||
create_routing,
|
||||
setup_bom,
|
||||
setup_operations,
|
||||
)
|
||||
from erpnext.manufacturing.doctype.work_order.work_order import make_job_card
|
||||
from erpnext.manufacturing.doctype.work_order.work_order import (
|
||||
make_stock_entry as make_stock_entry_for_wo,
|
||||
)
|
||||
from erpnext.stock.doctype.item.test_item import make_item
|
||||
from erpnext.stock.doctype.warehouse.test_warehouse import create_warehouse
|
||||
|
||||
workstation = make_workstation(
|
||||
workstation_name="Test Workstation for Overproduction", hour_rate_rent=10, hour_rate_labour=10
|
||||
)
|
||||
operations = [
|
||||
{"operation": "Test Operation 1", "workstation": workstation.name, "time_in_mins": 30},
|
||||
{"operation": "Test Operation 2", "workstation": workstation.name, "time_in_mins": 30},
|
||||
]
|
||||
warehouse = create_warehouse("Test Warehouse for Overproduction")
|
||||
setup_operations(operations)
|
||||
|
||||
fg = make_item("Test FG for Overproduction", {"stock_uom": "Nos", "is_stock_item": 1})
|
||||
rm = make_item("Test RM for Overproduction", {"stock_uom": "Nos", "is_stock_item": 1})
|
||||
|
||||
routing_doc = create_routing(routing_name="Testing Route", operations=operations)
|
||||
bom_doc = setup_bom(
|
||||
item_code=fg.name,
|
||||
routing=routing_doc.name,
|
||||
raw_materials=[rm.name],
|
||||
source_warehouse=warehouse,
|
||||
)
|
||||
|
||||
for row in bom_doc.items:
|
||||
make_stock_entry(
|
||||
item_code=row.item_code,
|
||||
target=row.source_warehouse,
|
||||
qty=100,
|
||||
basic_rate=100,
|
||||
)
|
||||
|
||||
wo_doc = make_wo_order_test_record(
|
||||
production_item=fg.name,
|
||||
bom_no=bom_doc.name,
|
||||
qty=10,
|
||||
skip_transfer=1,
|
||||
source_warehouse=warehouse,
|
||||
)
|
||||
|
||||
first_operation = frappe.get_all(
|
||||
"Job Card",
|
||||
filters={"work_order": wo_doc.name, "sequence_id": 1},
|
||||
fields=["name"],
|
||||
order_by="sequence_id",
|
||||
limit=1,
|
||||
)[0].name
|
||||
|
||||
jc = frappe.get_doc("Job Card", first_operation)
|
||||
from_time = add_to_date(now(), days=1)
|
||||
for _ in jc.scheduled_time_logs:
|
||||
jc.append(
|
||||
"time_logs",
|
||||
{
|
||||
"from_time": from_time,
|
||||
"to_time": add_to_date(from_time, days=1),
|
||||
"completed_qty": 4,
|
||||
},
|
||||
)
|
||||
jc.for_quantity = 4
|
||||
jc.save()
|
||||
jc.submit()
|
||||
|
||||
second_operation = frappe.get_all(
|
||||
"Job Card",
|
||||
filters={"work_order": wo_doc.name, "sequence_id": 2},
|
||||
fields=["name"],
|
||||
order_by="sequence_id",
|
||||
limit=1,
|
||||
)[0].name
|
||||
|
||||
jc = frappe.get_doc("Job Card", second_operation)
|
||||
from_time = add_to_date(now(), days=2)
|
||||
for _ in jc.scheduled_time_logs:
|
||||
jc.append(
|
||||
"time_logs",
|
||||
{
|
||||
"from_time": from_time,
|
||||
"to_time": add_to_date(from_time, days=2),
|
||||
"completed_qty": 4,
|
||||
},
|
||||
)
|
||||
jc.for_quantity = 4
|
||||
jc.save()
|
||||
jc.submit()
|
||||
|
||||
s = frappe.get_doc(make_stock_entry_for_wo(wo_doc.name, "Manufacture", 6)) # overproduction
|
||||
s.submit()
|
||||
|
||||
self.assertEqual(s.additional_costs[0].amount, 240)
|
||||
self.assertEqual(s.additional_costs[1].amount, 240)
|
||||
self.assertEqual(s.additional_costs[2].amount, 480)
|
||||
self.assertEqual(s.additional_costs[3].amount, 480)
|
||||
|
||||
make_job_card(
|
||||
wo_doc.name,
|
||||
[
|
||||
{
|
||||
"name": wo_doc.operations[0].name,
|
||||
"operation": "Test Operation 1",
|
||||
"qty": 2,
|
||||
"pending_qty": 2,
|
||||
}
|
||||
],
|
||||
)
|
||||
|
||||
job_card = frappe.get_last_doc("Job Card", {"work_order": wo_doc.name})
|
||||
from_time = add_to_date(now(), days=4)
|
||||
job_card.append(
|
||||
"time_logs",
|
||||
{
|
||||
"from_time": from_time,
|
||||
"to_time": add_to_date(from_time, days=1),
|
||||
"completed_qty": 2,
|
||||
},
|
||||
)
|
||||
job_card.for_quantity = 2
|
||||
job_card.save()
|
||||
job_card.submit()
|
||||
|
||||
make_job_card(
|
||||
wo_doc.name,
|
||||
[
|
||||
{
|
||||
"name": wo_doc.operations[1].name,
|
||||
"operation": "Test Operation 2",
|
||||
"qty": 2,
|
||||
"pending_qty": 2,
|
||||
}
|
||||
],
|
||||
)
|
||||
|
||||
job_card = frappe.get_last_doc("Job Card", {"work_order": wo_doc.name})
|
||||
from_time = add_to_date(now(), days=5)
|
||||
job_card.append(
|
||||
"time_logs",
|
||||
{
|
||||
"from_time": from_time,
|
||||
"to_time": add_to_date(from_time, days=2),
|
||||
"completed_qty": 2,
|
||||
},
|
||||
)
|
||||
job_card.for_quantity = 2
|
||||
job_card.save()
|
||||
job_card.submit()
|
||||
|
||||
s2 = frappe.get_doc(make_stock_entry_for_wo(wo_doc.name, "Manufacture", 1))
|
||||
s2.submit()
|
||||
|
||||
self.assertEqual(s2.additional_costs[0].amount, 120)
|
||||
self.assertEqual(s2.additional_costs[1].amount, 120)
|
||||
self.assertEqual(s2.additional_costs[2].amount, 240)
|
||||
self.assertEqual(s2.additional_costs[3].amount, 240)
|
||||
|
||||
make_job_card(
|
||||
wo_doc.name,
|
||||
[
|
||||
{
|
||||
"name": wo_doc.operations[0].name,
|
||||
"operation": "Test Operation 1",
|
||||
"qty": 2,
|
||||
"pending_qty": 2,
|
||||
}
|
||||
],
|
||||
)
|
||||
|
||||
job_card = frappe.get_last_doc("Job Card", {"work_order": wo_doc.name})
|
||||
from_time = add_to_date(now(), days=7)
|
||||
job_card.append(
|
||||
"time_logs",
|
||||
{
|
||||
"from_time": from_time,
|
||||
"to_time": add_to_date(from_time, days=1),
|
||||
"completed_qty": 2,
|
||||
},
|
||||
)
|
||||
job_card.for_quantity = 2
|
||||
job_card.save()
|
||||
job_card.submit()
|
||||
|
||||
make_job_card(
|
||||
wo_doc.name,
|
||||
[
|
||||
{
|
||||
"name": wo_doc.operations[1].name,
|
||||
"operation": "Test Operation 2",
|
||||
"qty": 2,
|
||||
"pending_qty": 2,
|
||||
}
|
||||
],
|
||||
)
|
||||
|
||||
job_card = frappe.get_last_doc("Job Card", {"work_order": wo_doc.name})
|
||||
from_time = add_to_date(now(), days=8)
|
||||
job_card.append(
|
||||
"time_logs",
|
||||
{
|
||||
"from_time": from_time,
|
||||
"to_time": add_to_date(from_time, days=2),
|
||||
"completed_qty": 2,
|
||||
},
|
||||
)
|
||||
job_card.for_quantity = 2
|
||||
job_card.save()
|
||||
job_card.submit()
|
||||
|
||||
s = frappe.get_doc(make_stock_entry_for_wo(wo_doc.name, "Manufacture", 2))
|
||||
s.submit()
|
||||
|
||||
self.assertEqual(s.additional_costs[0].amount, 240)
|
||||
self.assertEqual(s.additional_costs[1].amount, 240)
|
||||
self.assertEqual(s.additional_costs[2].amount, 480)
|
||||
self.assertEqual(s.additional_costs[3].amount, 480)
|
||||
|
||||
s2.cancel()
|
||||
|
||||
s = frappe.get_doc(make_stock_entry_for_wo(wo_doc.name, "Manufacture", 3))
|
||||
s.submit()
|
||||
|
||||
self.assertEqual(s.additional_costs[0].amount, 240)
|
||||
self.assertEqual(s.additional_costs[1].amount, 240)
|
||||
self.assertEqual(s.additional_costs[2].amount, 480)
|
||||
self.assertEqual(s.additional_costs[3].amount, 480)
|
||||
|
||||
|
||||
def create_bom_with_multiple_operations():
|
||||
"Create a BOM with multiple operations and Material Transfer against Job Card"
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
{
|
||||
"actions": [],
|
||||
"allow_bulk_edit": 1,
|
||||
"creation": "2018-07-09 17:20:44.737289",
|
||||
"doctype": "DocType",
|
||||
"editable_grid": 1,
|
||||
@@ -34,6 +35,7 @@
|
||||
"ignore_user_permissions": 1,
|
||||
"in_list_view": 1,
|
||||
"label": "Source Warehouse",
|
||||
"link_filters": "[[\"Warehouse\",\"disabled\",\"=\",0],[\"Warehouse\",\"is_group\",\"=\",0]]",
|
||||
"options": "Warehouse"
|
||||
},
|
||||
{
|
||||
@@ -113,7 +115,7 @@
|
||||
"index_web_pages_for_search": 1,
|
||||
"istable": 1,
|
||||
"links": [],
|
||||
"modified": "2025-12-04 14:30:19.472294",
|
||||
"modified": "2026-05-12 12:22:18.506904",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Manufacturing",
|
||||
"name": "Job Card Item",
|
||||
|
||||
@@ -12,21 +12,10 @@ frappe.ui.form.on("Work Order", {
|
||||
frm.ignore_doctypes_on_cancel_all = ["Serial and Batch Bundle"];
|
||||
|
||||
// Set query for warehouses
|
||||
frm.set_query("wip_warehouse", function () {
|
||||
return {
|
||||
filters: {
|
||||
company: frm.doc.company,
|
||||
},
|
||||
};
|
||||
});
|
||||
|
||||
frm.set_query("source_warehouse", function () {
|
||||
return {
|
||||
filters: {
|
||||
company: frm.doc.company,
|
||||
},
|
||||
};
|
||||
});
|
||||
frm.events.set_company_filters(frm, "wip_warehouse");
|
||||
frm.events.set_company_filters(frm, "source_warehouse");
|
||||
frm.events.set_company_filters(frm, "fg_warehouse");
|
||||
frm.events.set_company_filters(frm, "scrap_warehouse");
|
||||
|
||||
frm.set_query("source_warehouse", "required_items", function () {
|
||||
return {
|
||||
@@ -44,24 +33,6 @@ frappe.ui.form.on("Work Order", {
|
||||
};
|
||||
});
|
||||
|
||||
frm.set_query("fg_warehouse", function () {
|
||||
return {
|
||||
filters: {
|
||||
company: frm.doc.company,
|
||||
is_group: 0,
|
||||
},
|
||||
};
|
||||
});
|
||||
|
||||
frm.set_query("scrap_warehouse", function () {
|
||||
return {
|
||||
filters: {
|
||||
company: frm.doc.company,
|
||||
is_group: 0,
|
||||
},
|
||||
};
|
||||
});
|
||||
|
||||
// Set query for BOM
|
||||
frm.set_query("bom_no", function () {
|
||||
if (frm.doc.production_item) {
|
||||
@@ -118,6 +89,16 @@ frappe.ui.form.on("Work Order", {
|
||||
});
|
||||
},
|
||||
|
||||
set_company_filters(frm, fieldname) {
|
||||
frm.set_query(fieldname, () => {
|
||||
return {
|
||||
filters: {
|
||||
company: frm.doc.company,
|
||||
},
|
||||
};
|
||||
});
|
||||
},
|
||||
|
||||
onload: function (frm) {
|
||||
if (!frm.doc.status) frm.doc.status = "Draft";
|
||||
|
||||
@@ -348,7 +329,7 @@ frappe.ui.form.on("Work Order", {
|
||||
{
|
||||
fieldtype: "Data",
|
||||
fieldname: "name",
|
||||
label: __("Operation Id"),
|
||||
label: __("Operation ID"),
|
||||
},
|
||||
{
|
||||
fieldtype: "Float",
|
||||
@@ -425,6 +406,7 @@ frappe.ui.form.on("Work Order", {
|
||||
|
||||
if (pending_qty) {
|
||||
dialog.fields_dict.operations.df.data.push({
|
||||
__checked: 1,
|
||||
name: data.name,
|
||||
operation: data.operation,
|
||||
workstation: data.workstation,
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
{
|
||||
"actions": [],
|
||||
"allow_bulk_edit": 1,
|
||||
"allow_import": 1,
|
||||
"autoname": "naming_series:",
|
||||
"creation": "2025-04-09 12:09:40.634472",
|
||||
@@ -266,6 +267,7 @@
|
||||
"fieldname": "wip_warehouse",
|
||||
"fieldtype": "Link",
|
||||
"label": "Work-in-Progress Warehouse",
|
||||
"link_filters": "[[\"Warehouse\",\"disabled\",\"=\",0],[\"Warehouse\",\"is_group\",\"=\",0]]",
|
||||
"mandatory_depends_on": "eval:(!doc.skip_transfer || doc.from_wip_warehouse) && !doc.track_semi_finished_goods",
|
||||
"options": "Warehouse"
|
||||
},
|
||||
@@ -274,6 +276,7 @@
|
||||
"fieldname": "fg_warehouse",
|
||||
"fieldtype": "Link",
|
||||
"label": "Target Warehouse",
|
||||
"link_filters": "[[\"Warehouse\",\"disabled\",\"=\",0],[\"Warehouse\",\"is_group\",\"=\",0]]",
|
||||
"options": "Warehouse",
|
||||
"read_only_depends_on": "subcontracting_inward_order"
|
||||
},
|
||||
@@ -286,6 +289,7 @@
|
||||
"fieldname": "scrap_warehouse",
|
||||
"fieldtype": "Link",
|
||||
"label": "Scrap Warehouse",
|
||||
"link_filters": "[[\"Warehouse\",\"disabled\",\"=\",0],[\"Warehouse\",\"is_group\",\"=\",0]]",
|
||||
"options": "Warehouse"
|
||||
},
|
||||
{
|
||||
@@ -513,6 +517,7 @@
|
||||
"fieldname": "source_warehouse",
|
||||
"fieldtype": "Link",
|
||||
"label": "Source Warehouse",
|
||||
"link_filters": "[[\"Warehouse\",\"disabled\",\"=\",0],[\"Warehouse\",\"is_group\",\"=\",0]]",
|
||||
"options": "Warehouse",
|
||||
"read_only_depends_on": "eval:doc.subcontracting_inward_order"
|
||||
},
|
||||
@@ -706,7 +711,7 @@
|
||||
"image_field": "image",
|
||||
"is_submittable": 1,
|
||||
"links": [],
|
||||
"modified": "2026-04-17 13:42:12.374055",
|
||||
"modified": "2026-05-19 12:20:38.102403",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Manufacturing",
|
||||
"name": "Work Order",
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
{
|
||||
"actions": [],
|
||||
"allow_bulk_edit": 1,
|
||||
"creation": "2016-04-18 07:38:26.314642",
|
||||
"doctype": "DocType",
|
||||
"editable_grid": 1,
|
||||
@@ -53,6 +54,7 @@
|
||||
"ignore_user_permissions": 1,
|
||||
"in_list_view": 1,
|
||||
"label": "Source Warehouse",
|
||||
"link_filters": "[[\"Warehouse\",\"disabled\",\"=\",0],[\"Warehouse\",\"is_group\",\"=\",0]]",
|
||||
"options": "Warehouse",
|
||||
"read_only_depends_on": "eval:parent.subcontracting_inward_order && doc.is_customer_provided_item"
|
||||
},
|
||||
@@ -207,7 +209,7 @@
|
||||
"grid_page_length": 50,
|
||||
"istable": 1,
|
||||
"links": [],
|
||||
"modified": "2025-12-02 11:16:05.081613",
|
||||
"modified": "2026-05-12 12:05:16.687866",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Manufacturing",
|
||||
"name": "Work Order Item",
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
{
|
||||
"actions": [],
|
||||
"allow_bulk_edit": 1,
|
||||
"creation": "2014-07-11 11:51:00.453717",
|
||||
"doctype": "DocType",
|
||||
"editable_grid": 1,
|
||||
@@ -13,7 +14,10 @@
|
||||
"amount",
|
||||
"base_amount",
|
||||
"has_corrective_cost",
|
||||
"has_operating_cost"
|
||||
"has_operating_cost",
|
||||
"operation_id",
|
||||
"qty",
|
||||
"operating_component"
|
||||
],
|
||||
"fields": [
|
||||
{
|
||||
@@ -78,13 +82,38 @@
|
||||
"fieldtype": "Check",
|
||||
"label": "Has Operating Cost",
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "operation_id",
|
||||
"fieldtype": "Data",
|
||||
"hidden": 1,
|
||||
"label": "Operation ID",
|
||||
"no_copy": 1,
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "qty",
|
||||
"fieldtype": "Float",
|
||||
"hidden": 1,
|
||||
"label": "Qty",
|
||||
"no_copy": 1,
|
||||
"non_negative": 1,
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "operating_component",
|
||||
"fieldtype": "Data",
|
||||
"hidden": 1,
|
||||
"label": "Operating Component",
|
||||
"no_copy": 1,
|
||||
"read_only": 1
|
||||
}
|
||||
],
|
||||
"grid_page_length": 50,
|
||||
"index_web_pages_for_search": 1,
|
||||
"istable": 1,
|
||||
"links": [],
|
||||
"modified": "2025-07-16 15:27:59.175530",
|
||||
"modified": "2026-05-19 12:21:07.953801",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Stock",
|
||||
"name": "Landed Cost Taxes and Charges",
|
||||
|
||||
@@ -22,9 +22,12 @@ class LandedCostTaxesandCharges(Document):
|
||||
expense_account: DF.Link | None
|
||||
has_corrective_cost: DF.Check
|
||||
has_operating_cost: DF.Check
|
||||
operating_component: DF.Data | None
|
||||
operation_id: DF.Data | None
|
||||
parent: DF.Data
|
||||
parentfield: DF.Data
|
||||
parenttype: DF.Data
|
||||
qty: DF.Float
|
||||
# end: auto-generated types
|
||||
|
||||
pass
|
||||
|
||||
@@ -9,7 +9,7 @@ import frappe
|
||||
from frappe import _, bold
|
||||
from frappe.model.mapper import get_mapped_doc
|
||||
from frappe.query_builder import DocType
|
||||
from frappe.query_builder.functions import Sum
|
||||
from frappe.query_builder.functions import Max, Sum
|
||||
from frappe.utils import (
|
||||
cint,
|
||||
comma_or,
|
||||
@@ -3895,28 +3895,33 @@ def get_work_order_details(work_order, company):
|
||||
}
|
||||
|
||||
|
||||
def get_consumed_operating_cost(wo_name, bom_no):
|
||||
def get_consumed_operating_cost(wo_name, bom_no, operation_id):
|
||||
table = frappe.qb.DocType("Stock Entry")
|
||||
child_table = frappe.qb.DocType("Landed Cost Taxes and Charges")
|
||||
query = (
|
||||
frappe.qb.from_(child_table)
|
||||
.join(table)
|
||||
.on(child_table.parent == table.name)
|
||||
.select(Sum(child_table.amount).as_("consumed_cost"))
|
||||
.select(
|
||||
Sum(child_table.amount).as_("consumed_cost"),
|
||||
Sum(child_table.qty).as_("consumed_qty"),
|
||||
child_table.operating_component,
|
||||
)
|
||||
.where(
|
||||
(table.docstatus == 1)
|
||||
& (table.work_order == wo_name)
|
||||
& (table.purpose == "Manufacture")
|
||||
& (table.bom_no == bom_no)
|
||||
& (child_table.has_operating_cost == 1)
|
||||
& (child_table.operation_id == operation_id)
|
||||
)
|
||||
.groupby(child_table.operation_id, child_table.operating_component)
|
||||
)
|
||||
cost = query.run(pluck="consumed_cost")
|
||||
return cost[0] if cost and cost[0] else 0
|
||||
return query.run(as_dict=True)
|
||||
|
||||
|
||||
def get_operating_cost_per_unit(work_order=None, bom_no=None):
|
||||
operating_cost_per_unit = 0
|
||||
def get_remaining_operating_cost(work_order=None, bom_no=None):
|
||||
remaining_operating_cost = 0
|
||||
if work_order:
|
||||
if (
|
||||
bom_no
|
||||
@@ -3931,23 +3936,23 @@ def get_operating_cost_per_unit(work_order=None, bom_no=None):
|
||||
bom_no = work_order.bom_no
|
||||
|
||||
for d in work_order.get("operations"):
|
||||
consumed_op_cost = get_consumed_operating_cost(work_order.name, bom_no, d.name) or []
|
||||
cost = 0
|
||||
for row in consumed_op_cost:
|
||||
cost += flt(row.consumed_cost)
|
||||
|
||||
if flt(d.completed_qty):
|
||||
if not (remaining_qty := flt(d.completed_qty - work_order.produced_qty)):
|
||||
continue
|
||||
operating_cost_per_unit += (
|
||||
flt(d.actual_operating_cost - get_consumed_operating_cost(work_order.name, bom_no))
|
||||
/ remaining_qty
|
||||
)
|
||||
remaining_operating_cost += flt(d.actual_operating_cost - cost)
|
||||
elif work_order.qty:
|
||||
operating_cost_per_unit += flt(d.planned_operating_cost) / flt(work_order.qty)
|
||||
remaining_operating_cost += flt(d.planned_operating_cost) / flt(work_order.qty)
|
||||
|
||||
# Get operating cost from BOM if not found in work_order.
|
||||
if not operating_cost_per_unit and bom_no:
|
||||
if not remaining_operating_cost and bom_no:
|
||||
bom = frappe.db.get_value("BOM", bom_no, ["operating_cost", "quantity"], as_dict=1)
|
||||
if bom.quantity:
|
||||
operating_cost_per_unit = flt(bom.operating_cost) / flt(bom.quantity)
|
||||
remaining_operating_cost = flt(bom.operating_cost) / flt(bom.quantity)
|
||||
|
||||
return operating_cost_per_unit
|
||||
return remaining_operating_cost
|
||||
|
||||
|
||||
def get_used_alternative_items(
|
||||
|
||||
Reference in New Issue
Block a user