mirror of
https://github.com/frappe/erpnext.git
synced 2026-02-17 00:25:01 +00:00
Merge pull request #49213 from rohitwaghchaure/feat-workstation-operating-component
refactor: workstation operating component
This commit is contained in:
@@ -1421,11 +1421,11 @@ def add_additional_cost(stock_entry, work_order):
|
||||
as_dict=1,
|
||||
)
|
||||
|
||||
expecnse_account = (
|
||||
expense_account = (
|
||||
company_account.default_operating_cost_account or company_account.default_expense_account
|
||||
)
|
||||
add_non_stock_items_cost(stock_entry, work_order, expecnse_account)
|
||||
add_operations_cost(stock_entry, work_order, expecnse_account)
|
||||
add_non_stock_items_cost(stock_entry, work_order, expense_account)
|
||||
add_operations_cost(stock_entry, work_order, expense_account)
|
||||
|
||||
|
||||
def add_non_stock_items_cost(stock_entry, work_order, expense_account):
|
||||
@@ -1460,21 +1460,74 @@ def add_non_stock_items_cost(stock_entry, work_order, expense_account):
|
||||
)
|
||||
|
||||
|
||||
def add_operating_cost_component_wise(
|
||||
stock_entry, work_order=None, operating_cost_per_unit=None, op_expense_account=None
|
||||
):
|
||||
if not work_order:
|
||||
return False
|
||||
|
||||
cost_added = False
|
||||
for row in work_order.operations:
|
||||
workstation_cost = frappe.get_all(
|
||||
"Workstation Cost",
|
||||
fields=["operating_component", "operating_cost"],
|
||||
filters={
|
||||
"parent": row.workstation,
|
||||
"parenttype": "Workstation",
|
||||
},
|
||||
)
|
||||
|
||||
for wc in workstation_cost:
|
||||
expense_account = get_component_account(wc.operating_component) or op_expense_account
|
||||
actual_cp_operating_cost = flt(
|
||||
flt(wc.operating_cost) * flt(flt(row.actual_operation_time) / 60.0),
|
||||
row.precision("actual_operating_cost"),
|
||||
)
|
||||
|
||||
per_unit_cost = flt(actual_cp_operating_cost) / flt(row.completed_qty)
|
||||
|
||||
if per_unit_cost and expense_account:
|
||||
stock_entry.append(
|
||||
"additional_costs",
|
||||
{
|
||||
"expense_account": expense_account,
|
||||
"description": _("{0} Operating Cost for operation {1}").format(
|
||||
wc.operating_component, row.operation
|
||||
),
|
||||
"amount": per_unit_cost * flt(stock_entry.fg_completed_qty),
|
||||
},
|
||||
)
|
||||
|
||||
cost_added = True
|
||||
|
||||
return cost_added
|
||||
|
||||
|
||||
@frappe.request_cache
|
||||
def get_component_account(parent):
|
||||
return frappe.db.get_value("Workstation Operating Component Account", parent, "expense_account")
|
||||
|
||||
|
||||
def add_operations_cost(stock_entry, work_order=None, expense_account=None):
|
||||
from erpnext.stock.doctype.stock_entry.stock_entry import get_operating_cost_per_unit
|
||||
|
||||
operating_cost_per_unit = get_operating_cost_per_unit(work_order, stock_entry.bom_no)
|
||||
|
||||
if operating_cost_per_unit:
|
||||
stock_entry.append(
|
||||
"additional_costs",
|
||||
{
|
||||
"expense_account": expense_account,
|
||||
"description": _("Operating Cost as per Work Order / BOM"),
|
||||
"amount": operating_cost_per_unit * flt(stock_entry.fg_completed_qty),
|
||||
},
|
||||
cost_added = add_operating_cost_component_wise(
|
||||
stock_entry, work_order, operating_cost_per_unit, expense_account
|
||||
)
|
||||
|
||||
if not cost_added:
|
||||
stock_entry.append(
|
||||
"additional_costs",
|
||||
{
|
||||
"expense_account": expense_account,
|
||||
"description": _("Operating Cost as per Work Order / BOM"),
|
||||
"amount": operating_cost_per_unit * flt(stock_entry.fg_completed_qty),
|
||||
},
|
||||
)
|
||||
|
||||
if work_order and work_order.additional_operating_cost and work_order.qty:
|
||||
additional_operating_cost_per_unit = flt(work_order.additional_operating_cost) / flt(work_order.qty)
|
||||
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors and Contributors
|
||||
# See license.txt
|
||||
import frappe
|
||||
from frappe import _
|
||||
from frappe.tests import IntegrationTestCase
|
||||
|
||||
from erpnext.manufacturing.doctype.operation.test_operation import make_operation
|
||||
@@ -75,14 +76,22 @@ class TestWorkstation(IntegrationTestCase):
|
||||
bom_doc = setup_bom(item_code="_Testing Item", routing=routing_doc.name, currency="INR")
|
||||
w1 = frappe.get_doc("Workstation", "_Test Workstation A")
|
||||
# resets values
|
||||
w1.hour_rate_rent = 300
|
||||
w1.hour_rate_labour = 0
|
||||
for row in w1.workstation_costs:
|
||||
if row.operating_component == _("Rent"):
|
||||
row.operating_cost = 300
|
||||
break
|
||||
|
||||
w1.save()
|
||||
bom_doc.update_cost()
|
||||
bom_doc.reload()
|
||||
self.assertEqual(w1.hour_rate, 300)
|
||||
self.assertEqual(bom_doc.operations[0].hour_rate, 300)
|
||||
w1.hour_rate_rent = 250
|
||||
|
||||
for row in w1.workstation_costs:
|
||||
if row.operating_component == _("Rent"):
|
||||
row.operating_cost = 250
|
||||
break
|
||||
|
||||
w1.save()
|
||||
# updating after setting new rates in workstations
|
||||
bom_doc.update_cost()
|
||||
@@ -102,8 +111,24 @@ def make_workstation(*args, **kwargs):
|
||||
workstation_name = args.workstation_name or args.workstation
|
||||
if not frappe.db.exists("Workstation", workstation_name):
|
||||
doc = frappe.get_doc({"doctype": "Workstation", "workstation_name": workstation_name})
|
||||
doc.hour_rate_rent = args.get("hour_rate_rent")
|
||||
doc.hour_rate_labour = args.get("hour_rate_labour")
|
||||
if args.get("hour_rate_rent"):
|
||||
doc.append(
|
||||
"workstation_costs",
|
||||
{
|
||||
"operating_component": _("Rent"),
|
||||
"operating_cost": args.get("hour_rate_rent"),
|
||||
},
|
||||
)
|
||||
|
||||
if args.get("hour_rate_labour"):
|
||||
doc.append(
|
||||
"workstation_costs",
|
||||
{
|
||||
"operating_component": _("Wages"),
|
||||
"operating_cost": args.get("hour_rate_labour"),
|
||||
},
|
||||
)
|
||||
|
||||
doc.workstation_type = args.get("workstation_type")
|
||||
doc.insert()
|
||||
|
||||
|
||||
@@ -27,11 +27,8 @@
|
||||
"column_break_etmc",
|
||||
"off_status_image",
|
||||
"over_heads",
|
||||
"hour_rate_electricity",
|
||||
"hour_rate_consumable",
|
||||
"column_break_11",
|
||||
"hour_rate_rent",
|
||||
"hour_rate_labour",
|
||||
"section_break_auzm",
|
||||
"workstation_costs",
|
||||
"section_break_11",
|
||||
"hour_rate",
|
||||
"workstaion_description",
|
||||
@@ -68,50 +65,6 @@
|
||||
"label": "Operating Costs",
|
||||
"oldfieldtype": "Section Break"
|
||||
},
|
||||
{
|
||||
"bold": 1,
|
||||
"description": "per hour",
|
||||
"fieldname": "hour_rate_electricity",
|
||||
"fieldtype": "Currency",
|
||||
"label": "Electricity Cost",
|
||||
"non_negative": 1,
|
||||
"oldfieldname": "hour_rate_electricity",
|
||||
"oldfieldtype": "Currency"
|
||||
},
|
||||
{
|
||||
"bold": 1,
|
||||
"description": "per hour",
|
||||
"fieldname": "hour_rate_consumable",
|
||||
"fieldtype": "Currency",
|
||||
"label": "Consumable Cost",
|
||||
"non_negative": 1,
|
||||
"oldfieldname": "hour_rate_consumable",
|
||||
"oldfieldtype": "Currency"
|
||||
},
|
||||
{
|
||||
"fieldname": "column_break_11",
|
||||
"fieldtype": "Column Break"
|
||||
},
|
||||
{
|
||||
"bold": 1,
|
||||
"description": "per hour",
|
||||
"fieldname": "hour_rate_rent",
|
||||
"fieldtype": "Currency",
|
||||
"label": "Rent Cost",
|
||||
"non_negative": 1,
|
||||
"oldfieldname": "hour_rate_rent",
|
||||
"oldfieldtype": "Currency"
|
||||
},
|
||||
{
|
||||
"bold": 1,
|
||||
"description": "Wages per hour",
|
||||
"fieldname": "hour_rate_labour",
|
||||
"fieldtype": "Currency",
|
||||
"label": "Wages",
|
||||
"non_negative": 1,
|
||||
"oldfieldname": "hour_rate_labour",
|
||||
"oldfieldtype": "Currency"
|
||||
},
|
||||
{
|
||||
"description": "per hour",
|
||||
"fieldname": "hour_rate",
|
||||
@@ -252,6 +205,17 @@
|
||||
"fieldname": "disabled",
|
||||
"fieldtype": "Check",
|
||||
"label": "Disabled"
|
||||
},
|
||||
{
|
||||
"fieldname": "section_break_auzm",
|
||||
"fieldtype": "Section Break",
|
||||
"label": "Operating Costs (Per Hour)"
|
||||
},
|
||||
{
|
||||
"fieldname": "workstation_costs",
|
||||
"fieldtype": "Table",
|
||||
"label": "Operating Components Cost",
|
||||
"options": "Workstation Cost"
|
||||
}
|
||||
],
|
||||
"hide_toolbar": 1,
|
||||
@@ -259,7 +223,7 @@
|
||||
"idx": 1,
|
||||
"image_field": "on_status_image",
|
||||
"links": [],
|
||||
"modified": "2025-07-13 16:02:13.615001",
|
||||
"modified": "2025-08-19 12:07:05.374386",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Manufacturing",
|
||||
"name": "Workstation",
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
|
||||
|
||||
import frappe
|
||||
from frappe import _
|
||||
from frappe import _, bold
|
||||
from frappe.model.document import Document
|
||||
from frappe.utils import (
|
||||
add_days,
|
||||
@@ -44,6 +44,7 @@ class Workstation(Document):
|
||||
if TYPE_CHECKING:
|
||||
from frappe.types import DF
|
||||
|
||||
from erpnext.manufacturing.doctype.workstation_cost.workstation_cost import WorkstationCost
|
||||
from erpnext.manufacturing.doctype.workstation_working_hour.workstation_working_hour import (
|
||||
WorkstationWorkingHour,
|
||||
)
|
||||
@@ -52,10 +53,6 @@ class Workstation(Document):
|
||||
disabled: DF.Check
|
||||
holiday_list: DF.Link | None
|
||||
hour_rate: DF.Currency
|
||||
hour_rate_consumable: DF.Currency
|
||||
hour_rate_electricity: DF.Currency
|
||||
hour_rate_labour: DF.Currency
|
||||
hour_rate_rent: DF.Currency
|
||||
off_status_image: DF.AttachImage | None
|
||||
on_status_image: DF.AttachImage | None
|
||||
plant_floor: DF.Link | None
|
||||
@@ -64,10 +61,26 @@ class Workstation(Document):
|
||||
total_working_hours: DF.Float
|
||||
warehouse: DF.Link | None
|
||||
working_hours: DF.Table[WorkstationWorkingHour]
|
||||
workstation_costs: DF.Table[WorkstationCost]
|
||||
workstation_name: DF.Data
|
||||
workstation_type: DF.Link | None
|
||||
# end: auto-generated types
|
||||
|
||||
def validate(self):
|
||||
self.validate_duplicate_operating_component()
|
||||
|
||||
def validate_duplicate_operating_component(self):
|
||||
components = []
|
||||
for row in self.workstation_costs:
|
||||
if row.operating_component not in components:
|
||||
components.append(row.operating_component)
|
||||
else:
|
||||
frappe.throw(
|
||||
_("Duplicate Operating Component {0} found in Operating Components").format(
|
||||
bold(row.operating_component)
|
||||
)
|
||||
)
|
||||
|
||||
def before_save(self):
|
||||
self.set_data_based_on_workstation_type()
|
||||
self.set_hour_rate()
|
||||
@@ -95,36 +108,33 @@ class Workstation(Document):
|
||||
frappe.throw(_("Row #{0}: Start Time must be before End Time").format(row.idx))
|
||||
|
||||
def set_hour_rate(self):
|
||||
self.hour_rate = (
|
||||
flt(self.hour_rate_labour)
|
||||
+ flt(self.hour_rate_electricity)
|
||||
+ flt(self.hour_rate_consumable)
|
||||
+ flt(self.hour_rate_rent)
|
||||
)
|
||||
self.hour_rate = 0.0
|
||||
for row in self.workstation_costs:
|
||||
if row.operating_cost:
|
||||
self.hour_rate += flt(row.operating_cost)
|
||||
|
||||
@frappe.whitelist()
|
||||
def set_data_based_on_workstation_type(self):
|
||||
if self.workstation_costs:
|
||||
return
|
||||
|
||||
if self.workstation_type:
|
||||
fields = [
|
||||
"hour_rate_labour",
|
||||
"hour_rate_electricity",
|
||||
"hour_rate_consumable",
|
||||
"hour_rate_rent",
|
||||
"hour_rate",
|
||||
"description",
|
||||
]
|
||||
data = frappe.get_all(
|
||||
"Workstation Cost",
|
||||
fields=["operating_component", "operating_cost", "idx"],
|
||||
filters={"parent": self.workstation_type, "parenttype": "Workstation Type"},
|
||||
order_by="idx",
|
||||
)
|
||||
|
||||
data = frappe.get_cached_value("Workstation Type", self.workstation_type, fields, as_dict=True)
|
||||
|
||||
if not data:
|
||||
return
|
||||
|
||||
for field in fields:
|
||||
if self.get(field):
|
||||
continue
|
||||
|
||||
if value := data.get(field):
|
||||
self.set(field, value)
|
||||
for row in data:
|
||||
self.append(
|
||||
"workstation_costs",
|
||||
{
|
||||
"operating_component": row.operating_component,
|
||||
"operating_cost": row.operating_cost,
|
||||
"idx": row.idx,
|
||||
},
|
||||
)
|
||||
|
||||
def on_update(self):
|
||||
self.validate_overlap_for_operation_timings()
|
||||
|
||||
@@ -0,0 +1,20 @@
|
||||
# Copyright (c) 2025, Frappe Technologies Pvt. Ltd. and Contributors
|
||||
# See license.txt
|
||||
|
||||
# import frappe
|
||||
from frappe.tests import IntegrationTestCase
|
||||
|
||||
# On IntegrationTestCase, the doctype test records and all
|
||||
# link-field test record dependencies are recursively loaded
|
||||
# Use these module variables to add/remove to/from that list
|
||||
EXTRA_TEST_RECORD_DEPENDENCIES = [] # eg. ["User"]
|
||||
IGNORE_TEST_RECORD_DEPENDENCIES = [] # eg. ["User"]
|
||||
|
||||
|
||||
class IntegrationTestWorkstationCost(IntegrationTestCase):
|
||||
"""
|
||||
Integration tests for WorkstationCost.
|
||||
Use this class for testing interactions between multiple components.
|
||||
"""
|
||||
|
||||
pass
|
||||
@@ -0,0 +1,8 @@
|
||||
// Copyright (c) 2025, Frappe Technologies Pvt. Ltd. and contributors
|
||||
// For license information, please see license.txt
|
||||
|
||||
// frappe.ui.form.on("Workstation Cost", {
|
||||
// refresh(frm) {
|
||||
|
||||
// },
|
||||
// });
|
||||
@@ -0,0 +1,43 @@
|
||||
{
|
||||
"actions": [],
|
||||
"allow_rename": 1,
|
||||
"creation": "2025-08-17 16:43:13.542333",
|
||||
"doctype": "DocType",
|
||||
"editable_grid": 1,
|
||||
"engine": "InnoDB",
|
||||
"field_order": [
|
||||
"operating_component",
|
||||
"operating_cost"
|
||||
],
|
||||
"fields": [
|
||||
{
|
||||
"fieldname": "operating_cost",
|
||||
"fieldtype": "Currency",
|
||||
"in_list_view": 1,
|
||||
"label": "Operating Cost",
|
||||
"reqd": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "operating_component",
|
||||
"fieldtype": "Link",
|
||||
"in_list_view": 1,
|
||||
"label": "Operating Component",
|
||||
"options": "Workstation Operating Component",
|
||||
"reqd": 1
|
||||
}
|
||||
],
|
||||
"grid_page_length": 50,
|
||||
"index_web_pages_for_search": 1,
|
||||
"istable": 1,
|
||||
"links": [],
|
||||
"modified": "2025-08-17 19:21:02.725365",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Manufacturing",
|
||||
"name": "Workstation Cost",
|
||||
"owner": "Administrator",
|
||||
"permissions": [],
|
||||
"row_format": "Dynamic",
|
||||
"sort_field": "creation",
|
||||
"sort_order": "DESC",
|
||||
"states": []
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
# Copyright (c) 2025, Frappe Technologies Pvt. Ltd. and contributors
|
||||
# For license information, please see license.txt
|
||||
|
||||
# import frappe
|
||||
from frappe.model.document import Document
|
||||
|
||||
|
||||
class WorkstationCost(Document):
|
||||
# begin: auto-generated types
|
||||
# This code is auto-generated. Do not modify anything in this block.
|
||||
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from frappe.types import DF
|
||||
|
||||
operating_component: DF.Link
|
||||
operating_cost: DF.Currency
|
||||
parent: DF.Data
|
||||
parentfield: DF.Data
|
||||
parenttype: DF.Data
|
||||
# end: auto-generated types
|
||||
|
||||
pass
|
||||
@@ -0,0 +1,20 @@
|
||||
# Copyright (c) 2025, Frappe Technologies Pvt. Ltd. and Contributors
|
||||
# See license.txt
|
||||
|
||||
# import frappe
|
||||
from frappe.tests import IntegrationTestCase
|
||||
|
||||
# On IntegrationTestCase, the doctype test records and all
|
||||
# link-field test record dependencies are recursively loaded
|
||||
# Use these module variables to add/remove to/from that list
|
||||
EXTRA_TEST_RECORD_DEPENDENCIES = [] # eg. ["User"]
|
||||
IGNORE_TEST_RECORD_DEPENDENCIES = [] # eg. ["User"]
|
||||
|
||||
|
||||
class IntegrationTestWorkstationOperatingComponent(IntegrationTestCase):
|
||||
"""
|
||||
Integration tests for WorkstationOperatingComponent.
|
||||
Use this class for testing interactions between multiple components.
|
||||
"""
|
||||
|
||||
pass
|
||||
@@ -0,0 +1,8 @@
|
||||
// Copyright (c) 2025, Frappe Technologies Pvt. Ltd. and contributors
|
||||
// For license information, please see license.txt
|
||||
|
||||
// frappe.ui.form.on("Workstation Operating Component", {
|
||||
// refresh(frm) {
|
||||
|
||||
// },
|
||||
// });
|
||||
@@ -0,0 +1,84 @@
|
||||
{
|
||||
"actions": [],
|
||||
"allow_rename": 1,
|
||||
"autoname": "field:component_name",
|
||||
"creation": "2025-08-17 16:49:30.711201",
|
||||
"doctype": "DocType",
|
||||
"engine": "InnoDB",
|
||||
"field_order": [
|
||||
"component_name",
|
||||
"section_break_ewdg",
|
||||
"accounts"
|
||||
],
|
||||
"fields": [
|
||||
{
|
||||
"fieldname": "component_name",
|
||||
"fieldtype": "Data",
|
||||
"in_list_view": 1,
|
||||
"label": "Component Name",
|
||||
"reqd": 1,
|
||||
"unique": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "section_break_ewdg",
|
||||
"fieldtype": "Section Break"
|
||||
},
|
||||
{
|
||||
"fieldname": "accounts",
|
||||
"fieldtype": "Table",
|
||||
"label": "Component Expense Account",
|
||||
"options": "Workstation Operating Component Account"
|
||||
}
|
||||
],
|
||||
"grid_page_length": 50,
|
||||
"index_web_pages_for_search": 1,
|
||||
"links": [],
|
||||
"modified": "2025-08-17 19:23:47.510540",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Manufacturing",
|
||||
"name": "Workstation Operating Component",
|
||||
"naming_rule": "By fieldname",
|
||||
"owner": "Administrator",
|
||||
"permissions": [
|
||||
{
|
||||
"create": 1,
|
||||
"delete": 1,
|
||||
"email": 1,
|
||||
"export": 1,
|
||||
"print": 1,
|
||||
"read": 1,
|
||||
"report": 1,
|
||||
"role": "System Manager",
|
||||
"share": 1,
|
||||
"write": 1
|
||||
},
|
||||
{
|
||||
"create": 1,
|
||||
"delete": 1,
|
||||
"email": 1,
|
||||
"export": 1,
|
||||
"print": 1,
|
||||
"read": 1,
|
||||
"report": 1,
|
||||
"role": "Manufacturing User",
|
||||
"share": 1,
|
||||
"write": 1
|
||||
},
|
||||
{
|
||||
"create": 1,
|
||||
"delete": 1,
|
||||
"email": 1,
|
||||
"export": 1,
|
||||
"print": 1,
|
||||
"read": 1,
|
||||
"report": 1,
|
||||
"role": "Manufacturing Manager",
|
||||
"share": 1,
|
||||
"write": 1
|
||||
}
|
||||
],
|
||||
"row_format": "Dynamic",
|
||||
"sort_field": "creation",
|
||||
"sort_order": "DESC",
|
||||
"states": []
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
# Copyright (c) 2025, Frappe Technologies Pvt. Ltd. and contributors
|
||||
# For license information, please see license.txt
|
||||
|
||||
# import frappe
|
||||
from frappe.model.document import Document
|
||||
|
||||
|
||||
class WorkstationOperatingComponent(Document):
|
||||
# begin: auto-generated types
|
||||
# This code is auto-generated. Do not modify anything in this block.
|
||||
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from frappe.types import DF
|
||||
|
||||
from erpnext.manufacturing.doctype.workstation_operating_component_account.workstation_operating_component_account import (
|
||||
WorkstationOperatingComponentAccount,
|
||||
)
|
||||
|
||||
accounts: DF.Table[WorkstationOperatingComponentAccount]
|
||||
component_name: DF.Data
|
||||
# end: auto-generated types
|
||||
|
||||
pass
|
||||
@@ -0,0 +1,20 @@
|
||||
# Copyright (c) 2025, Frappe Technologies Pvt. Ltd. and Contributors
|
||||
# See license.txt
|
||||
|
||||
# import frappe
|
||||
from frappe.tests import IntegrationTestCase
|
||||
|
||||
# On IntegrationTestCase, the doctype test records and all
|
||||
# link-field test record dependencies are recursively loaded
|
||||
# Use these module variables to add/remove to/from that list
|
||||
EXTRA_TEST_RECORD_DEPENDENCIES = [] # eg. ["User"]
|
||||
IGNORE_TEST_RECORD_DEPENDENCIES = [] # eg. ["User"]
|
||||
|
||||
|
||||
class IntegrationTestWorkstationOperatingComponentAccount(IntegrationTestCase):
|
||||
"""
|
||||
Integration tests for WorkstationOperatingComponentAccount.
|
||||
Use this class for testing interactions between multiple components.
|
||||
"""
|
||||
|
||||
pass
|
||||
@@ -0,0 +1,8 @@
|
||||
// Copyright (c) 2025, Frappe Technologies Pvt. Ltd. and contributors
|
||||
// For license information, please see license.txt
|
||||
|
||||
// frappe.ui.form.on("Workstation Operating Component Account", {
|
||||
// refresh(frm) {
|
||||
|
||||
// },
|
||||
// });
|
||||
@@ -0,0 +1,43 @@
|
||||
{
|
||||
"actions": [],
|
||||
"allow_rename": 1,
|
||||
"creation": "2025-08-17 19:21:36.356779",
|
||||
"doctype": "DocType",
|
||||
"editable_grid": 1,
|
||||
"engine": "InnoDB",
|
||||
"field_order": [
|
||||
"company",
|
||||
"expense_account"
|
||||
],
|
||||
"fields": [
|
||||
{
|
||||
"fieldname": "company",
|
||||
"fieldtype": "Link",
|
||||
"in_list_view": 1,
|
||||
"label": "Company",
|
||||
"options": "Company",
|
||||
"reqd": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "expense_account",
|
||||
"fieldtype": "Link",
|
||||
"in_list_view": 1,
|
||||
"label": "Expense Account",
|
||||
"options": "Account"
|
||||
}
|
||||
],
|
||||
"grid_page_length": 50,
|
||||
"index_web_pages_for_search": 1,
|
||||
"istable": 1,
|
||||
"links": [],
|
||||
"modified": "2025-08-17 19:24:01.487406",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Manufacturing",
|
||||
"name": "Workstation Operating Component Account",
|
||||
"owner": "Administrator",
|
||||
"permissions": [],
|
||||
"row_format": "Dynamic",
|
||||
"sort_field": "creation",
|
||||
"sort_order": "DESC",
|
||||
"states": []
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
# Copyright (c) 2025, Frappe Technologies Pvt. Ltd. and contributors
|
||||
# For license information, please see license.txt
|
||||
|
||||
# import frappe
|
||||
from frappe.model.document import Document
|
||||
|
||||
|
||||
class WorkstationOperatingComponentAccount(Document):
|
||||
# begin: auto-generated types
|
||||
# This code is auto-generated. Do not modify anything in this block.
|
||||
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from frappe.types import DF
|
||||
|
||||
company: DF.Link
|
||||
expense_account: DF.Link | None
|
||||
parent: DF.Data
|
||||
parentfield: DF.Data
|
||||
parenttype: DF.Data
|
||||
# end: auto-generated types
|
||||
|
||||
pass
|
||||
@@ -10,12 +10,8 @@
|
||||
"engine": "InnoDB",
|
||||
"field_order": [
|
||||
"workstation_type",
|
||||
"over_heads",
|
||||
"hour_rate_electricity",
|
||||
"hour_rate_consumable",
|
||||
"column_break_5",
|
||||
"hour_rate_rent",
|
||||
"hour_rate_labour",
|
||||
"section_break_auzm",
|
||||
"workstation_costs",
|
||||
"section_break_8",
|
||||
"hour_rate",
|
||||
"description_tab",
|
||||
@@ -32,44 +28,6 @@
|
||||
"reqd": 1,
|
||||
"unique": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "over_heads",
|
||||
"fieldtype": "Section Break",
|
||||
"label": "Operating Costs",
|
||||
"oldfieldtype": "Section Break"
|
||||
},
|
||||
{
|
||||
"description": "per hour",
|
||||
"fieldname": "hour_rate_electricity",
|
||||
"fieldtype": "Currency",
|
||||
"label": "Electricity Cost",
|
||||
"oldfieldname": "hour_rate_electricity",
|
||||
"oldfieldtype": "Currency"
|
||||
},
|
||||
{
|
||||
"description": "per hour",
|
||||
"fieldname": "hour_rate_consumable",
|
||||
"fieldtype": "Currency",
|
||||
"label": "Consumable Cost",
|
||||
"oldfieldname": "hour_rate_consumable",
|
||||
"oldfieldtype": "Currency"
|
||||
},
|
||||
{
|
||||
"description": "per hour",
|
||||
"fieldname": "hour_rate_rent",
|
||||
"fieldtype": "Currency",
|
||||
"label": "Rent Cost",
|
||||
"oldfieldname": "hour_rate_rent",
|
||||
"oldfieldtype": "Currency"
|
||||
},
|
||||
{
|
||||
"description": "Wages per hour",
|
||||
"fieldname": "hour_rate_labour",
|
||||
"fieldtype": "Currency",
|
||||
"label": "Wages",
|
||||
"oldfieldname": "hour_rate_labour",
|
||||
"oldfieldtype": "Currency"
|
||||
},
|
||||
{
|
||||
"description": "per hour",
|
||||
"fieldname": "hour_rate",
|
||||
@@ -88,10 +46,6 @@
|
||||
"oldfieldtype": "Text",
|
||||
"width": "300px"
|
||||
},
|
||||
{
|
||||
"fieldname": "column_break_5",
|
||||
"fieldtype": "Column Break"
|
||||
},
|
||||
{
|
||||
"collapsible": 1,
|
||||
"fieldname": "description_tab",
|
||||
@@ -101,11 +55,22 @@
|
||||
{
|
||||
"fieldname": "section_break_8",
|
||||
"fieldtype": "Section Break"
|
||||
},
|
||||
{
|
||||
"fieldname": "section_break_auzm",
|
||||
"fieldtype": "Section Break",
|
||||
"label": "Operating Costs (Per Hour)"
|
||||
},
|
||||
{
|
||||
"fieldname": "workstation_costs",
|
||||
"fieldtype": "Table",
|
||||
"label": "Operating Components Cost",
|
||||
"options": "Workstation Cost"
|
||||
}
|
||||
],
|
||||
"icon": "icon-wrench",
|
||||
"links": [],
|
||||
"modified": "2024-03-27 13:11:00.946367",
|
||||
"modified": "2025-08-19 12:06:56.683558",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Manufacturing",
|
||||
"name": "Workstation Type",
|
||||
@@ -125,9 +90,10 @@
|
||||
}
|
||||
],
|
||||
"quick_entry": 1,
|
||||
"row_format": "Dynamic",
|
||||
"show_name_in_global_search": 1,
|
||||
"sort_field": "creation",
|
||||
"sort_order": "ASC",
|
||||
"states": [],
|
||||
"track_changes": 1
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
# For license information, please see license.txt
|
||||
|
||||
import frappe
|
||||
from frappe import _, bold
|
||||
from frappe.model.document import Document
|
||||
from frappe.utils import flt
|
||||
|
||||
@@ -15,25 +16,38 @@ class WorkstationType(Document):
|
||||
if TYPE_CHECKING:
|
||||
from frappe.types import DF
|
||||
|
||||
from erpnext.manufacturing.doctype.workstation_cost.workstation_cost import WorkstationCost
|
||||
|
||||
description: DF.SmallText | None
|
||||
hour_rate: DF.Currency
|
||||
hour_rate_consumable: DF.Currency
|
||||
hour_rate_electricity: DF.Currency
|
||||
hour_rate_labour: DF.Currency
|
||||
hour_rate_rent: DF.Currency
|
||||
workstation_costs: DF.Table[WorkstationCost]
|
||||
workstation_type: DF.Data
|
||||
# end: auto-generated types
|
||||
|
||||
def validate(self):
|
||||
self.validate_duplicate_operating_component()
|
||||
|
||||
def validate_duplicate_operating_component(self):
|
||||
components = []
|
||||
for row in self.workstation_costs:
|
||||
if row.operating_component not in components:
|
||||
components.append(row.operating_component)
|
||||
else:
|
||||
frappe.throw(
|
||||
_("Duplicate Operating Component {0} found in Operating Components").format(
|
||||
bold(row.operating_component)
|
||||
)
|
||||
)
|
||||
|
||||
def before_save(self):
|
||||
self.set_hour_rate()
|
||||
|
||||
def set_hour_rate(self):
|
||||
self.hour_rate = (
|
||||
flt(self.hour_rate_labour)
|
||||
+ flt(self.hour_rate_electricity)
|
||||
+ flt(self.hour_rate_consumable)
|
||||
+ flt(self.hour_rate_rent)
|
||||
)
|
||||
self.hour_rate = 0.0
|
||||
|
||||
for row in self.workstation_costs:
|
||||
if row.operating_cost:
|
||||
self.hour_rate += flt(row.operating_cost)
|
||||
|
||||
|
||||
def get_workstations(workstation_type):
|
||||
|
||||
@@ -435,3 +435,4 @@ erpnext.patches.v15_0.add_company_payment_gateway_account
|
||||
erpnext.patches.v16_0.update_serial_no_reference_name
|
||||
erpnext.patches.v16_0.set_invoice_type_in_pos_settings
|
||||
erpnext.patches.v15_0.update_fieldname_in_accounting_dimension_filter
|
||||
erpnext.patches.v16_0.make_workstation_operating_components #1
|
||||
|
||||
@@ -0,0 +1,79 @@
|
||||
import frappe
|
||||
from frappe import _
|
||||
|
||||
|
||||
def get_operating_cost_account(company):
|
||||
company_details = frappe.db.get_value(
|
||||
"Company", company, ["default_operating_cost_account", "default_expense_account"], as_dict=True
|
||||
)
|
||||
|
||||
return company_details.get("default_operating_cost_account") or company_details.get(
|
||||
"default_expense_account"
|
||||
)
|
||||
|
||||
|
||||
def execute():
|
||||
components = [
|
||||
"Electricity",
|
||||
"Consumables",
|
||||
"Rent",
|
||||
"Wages",
|
||||
]
|
||||
|
||||
companies = frappe.get_all("Company", filters={"is_group": 0}, pluck="name")
|
||||
|
||||
for component in components:
|
||||
component = _(component)
|
||||
if not frappe.db.exists("Workstation Operating Component", component):
|
||||
doc = frappe.new_doc("Workstation Operating Component")
|
||||
doc.component_name = component
|
||||
|
||||
for company in companies:
|
||||
operating_cost_account = get_operating_cost_account(company)
|
||||
|
||||
doc.append("accounts", {"company": company, "expense_account": operating_cost_account})
|
||||
|
||||
doc.insert()
|
||||
|
||||
workstations = frappe.get_all("Workstation", filters={"hour_rate": (">", 0.0)}, pluck="name") or []
|
||||
workstation_types = (
|
||||
frappe.get_all("Workstation Type", filters={"hour_rate": (">", 0.0)}, pluck="name") or []
|
||||
)
|
||||
|
||||
if not workstations and not workstation_types:
|
||||
return
|
||||
|
||||
components_map = {
|
||||
"hour_rate_electricity": _("Electricity"),
|
||||
"hour_rate_consumable": _("Consumables"),
|
||||
"hour_rate_rent": _("Rent"),
|
||||
"hour_rate_labour": _("Wages"),
|
||||
}
|
||||
|
||||
for workstation in workstations:
|
||||
doc = frappe.get_doc("Workstation", workstation)
|
||||
for field, component in components_map.items():
|
||||
if doc.get(field):
|
||||
doc.append(
|
||||
"workstation_costs",
|
||||
{
|
||||
"operating_component": component,
|
||||
"operating_cost": doc.get(field),
|
||||
},
|
||||
)
|
||||
|
||||
doc.save()
|
||||
|
||||
for workstation_type in workstation_types:
|
||||
doc = frappe.get_doc("Workstation Type", workstation_type)
|
||||
for field, component in components_map.items():
|
||||
if doc.get(field):
|
||||
doc.append(
|
||||
"workstation_costs",
|
||||
{
|
||||
"operating_component": component,
|
||||
"operating_cost": doc.get(field),
|
||||
},
|
||||
)
|
||||
|
||||
doc.save()
|
||||
@@ -294,6 +294,10 @@ def install(country=None):
|
||||
{"doctype": "Market Segment", "market_segment": _("Upper Income")},
|
||||
# Warehouse Type
|
||||
{"doctype": "Warehouse Type", "name": "Transit"},
|
||||
{"doctype": "Workstation Operating Component", "component_name": _("Electricity")},
|
||||
{"doctype": "Workstation Operating Component", "component_name": _("Consumables")},
|
||||
{"doctype": "Workstation Operating Component", "component_name": _("Rent")},
|
||||
{"doctype": "Workstation Operating Component", "component_name": _("Wages")},
|
||||
]
|
||||
|
||||
for doctype, title_field, filename in (
|
||||
|
||||
Reference in New Issue
Block a user