Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Add corrective job card operating cost as additional costs in s… #45282

Merged
merged 4 commits into from
Jan 20, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
58 changes: 58 additions & 0 deletions erpnext/manufacturing/doctype/bom/bom.py
Original file line number Diff line number Diff line change
Expand Up @@ -1433,6 +1433,64 @@ def add_operations_cost(stock_entry, work_order=None, expense_account=None):
},
)

def get_max_op_qty():
from frappe.query_builder.functions import Sum

table = frappe.qb.DocType("Job Card")
query = (
frappe.qb.from_(table)
.select(Sum(table.total_completed_qty).as_("qty"))
.where(
(table.docstatus == 1)
& (table.work_order == work_order.name)
& (table.is_corrective_job_card == 0)
)
.groupby(table.operation)
)
return min([d.qty for d in query.run(as_dict=True)], default=0)

def get_utilised_cc():
from frappe.query_builder.functions import Sum

table = frappe.qb.DocType("Stock Entry")
subquery = (
frappe.qb.from_(table)
.select(table.name)
.where(
(table.docstatus == 1)
& (table.work_order == work_order.name)
& (table.purpose == "Manufacture")
)
)
table = frappe.qb.DocType("Landed Cost Taxes and Charges")
query = (
frappe.qb.from_(table)
.select(Sum(table.amount).as_("amount"))
.where(table.parent.isin(subquery) & (table.has_corrective_cost == 1))
)
return query.run(as_dict=True)[0].amount or 0

if (
work_order
and work_order.corrective_operation_cost
and cint(
frappe.db.get_single_value(
"Manufacturing Settings", "add_corrective_operation_cost_in_finished_good_valuation"
)
)
):
max_qty = get_max_op_qty() - work_order.produced_qty
remaining_cc = work_order.corrective_operation_cost - get_utilised_cc()
stock_entry.append(
"additional_costs",
{
"expense_account": expense_account,
"description": "Corrective Operation Cost",
"has_corrective_cost": 1,
"amount": remaining_cc / max_qty * flt(stock_entry.fg_completed_qty),
},
)


@frappe.whitelist()
def get_bom_diff(bom1, bom2):
Expand Down
6 changes: 5 additions & 1 deletion erpnext/manufacturing/doctype/job_card/job_card.py
Original file line number Diff line number Diff line change
Expand Up @@ -670,7 +670,11 @@ def get_required_items(self):
)
)

if self.get("operation") == d.operation or self.operation_row_id == d.operation_row_id:
if (
self.get("operation") == d.operation
or self.operation_row_id == d.operation_row_id
or self.is_corrective_job_card
):
self.append(
"items",
{
Expand Down
84 changes: 84 additions & 0 deletions erpnext/manufacturing/doctype/job_card/test_job_card.py
Original file line number Diff line number Diff line change
Expand Up @@ -442,6 +442,90 @@ def test_corrective_costing(self):
cost_after_cancel = self.work_order.total_operating_cost
self.assertEqual(cost_after_cancel, original_cost)

@IntegrationTestCase.change_settings(
"Manufacturing Settings", {"add_corrective_operation_cost_in_finished_good_valuation": 1}
)
def test_if_corrective_jc_ops_cost_is_added_to_manufacture_stock_entry(self):
wo = make_wo_order_test_record(
item="_Test FG Item 2",
qty=10,
transfer_material_against=self.transfer_material_against,
source_warehouse=self.source_warehouse,
)
self.generate_required_stock(wo)
job_card = frappe.get_last_doc("Job Card", {"work_order": wo.name})
job_card.update({"for_quantity": 4})
job_card.append(
"time_logs",
{"from_time": now(), "to_time": add_to_date(now(), hours=1), "completed_qty": 4},
)
job_card.submit()

corrective_action = frappe.get_doc(
doctype="Operation", is_corrective_operation=1, name=frappe.generate_hash()
).insert()

corrective_job_card = make_corrective_job_card(
job_card.name, operation=corrective_action.name, for_operation=job_card.operation
)
corrective_job_card.hour_rate = 100
corrective_job_card.insert()
corrective_job_card.append(
"time_logs",
{
"from_time": add_to_date(now(), hours=2),
"to_time": add_to_date(now(), hours=2, minutes=30),
"completed_qty": 4,
},
)
corrective_job_card.submit()
wo.reload()

from erpnext.manufacturing.doctype.work_order.work_order import (
make_stock_entry as make_stock_entry_for_wo,
)

stock_entry = make_stock_entry_for_wo(wo.name, "Manufacture", qty=3)
self.assertEqual(stock_entry.additional_costs[1].amount, 37.5)
frappe.get_doc(stock_entry).submit()

from erpnext.manufacturing.doctype.work_order.work_order import make_job_card

make_job_card(
wo.name,
[{"name": wo.operations[0].name, "operation": "_Test Operation 1", "qty": 3, "pending_qty": 3}],
)
job_card = frappe.get_last_doc("Job Card", {"work_order": wo.name})
job_card.update({"for_quantity": 3})
job_card.append(
"time_logs",
{
"from_time": add_to_date(now(), hours=3),
"to_time": add_to_date(now(), hours=4),
"completed_qty": 3,
},
)
job_card.submit()

corrective_job_card = make_corrective_job_card(
job_card.name, operation=corrective_action.name, for_operation=job_card.operation
)
corrective_job_card.hour_rate = 80
corrective_job_card.insert()
corrective_job_card.append(
"time_logs",
{
"from_time": add_to_date(now(), hours=4),
"to_time": add_to_date(now(), hours=4, minutes=30),
"completed_qty": 3,
},
)
corrective_job_card.submit()
wo.reload()

stock_entry = make_stock_entry_for_wo(wo.name, "Manufacture", qty=4)
self.assertEqual(stock_entry.additional_costs[1].amount, 52.5)

def test_job_card_statuses(self):
def assertStatus(status):
jc.set_status()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,8 @@
"description",
"col_break3",
"amount",
"base_amount"
"base_amount",
"has_corrective_cost"
],
"fields": [
{
Expand Down Expand Up @@ -62,12 +63,19 @@
"label": "Amount (Company Currency)",
"options": "Company:company:default_currency",
"read_only": 1
},
{
"default": "0",
"fieldname": "has_corrective_cost",
"fieldtype": "Check",
"label": "Has Corrective Cost",
"read_only": 1
}
],
"index_web_pages_for_search": 1,
"istable": 1,
"links": [],
"modified": "2024-03-27 13:09:59.493991",
"modified": "2025-01-20 12:22:03.455762",
"modified_by": "Administrator",
"module": "Stock",
"name": "Landed Cost Taxes and Charges",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ class LandedCostTaxesandCharges(Document):
description: DF.SmallText
exchange_rate: DF.Float
expense_account: DF.Link | None
has_corrective_cost: DF.Check
parent: DF.Data
parentfield: DF.Data
parenttype: DF.Data
Expand Down
11 changes: 0 additions & 11 deletions erpnext/stock/doctype/stock_entry/stock_entry.py
Original file line number Diff line number Diff line change
Expand Up @@ -2892,17 +2892,6 @@ def get_operating_cost_per_unit(work_order=None, bom_no=None):
if bom.quantity:
operating_cost_per_unit = flt(bom.operating_cost) / flt(bom.quantity)

if (
work_order
and work_order.produced_qty
and cint(
frappe.db.get_single_value(
"Manufacturing Settings", "add_corrective_operation_cost_in_finished_good_valuation"
)
)
):
operating_cost_per_unit += flt(work_order.corrective_operation_cost) / flt(work_order.produced_qty)

return operating_cost_per_unit


Expand Down
Loading