style: format code with black
diff --git a/erpnext/manufacturing/dashboard_fixtures.py b/erpnext/manufacturing/dashboard_fixtures.py
index 1bc12ff..9e64f4d 100644
--- a/erpnext/manufacturing/dashboard_fixtures.py
+++ b/erpnext/manufacturing/dashboard_fixtures.py
@@ -11,33 +11,39 @@
def get_data():
- return frappe._dict({
- "dashboards": get_dashboards(),
- "charts": get_charts(),
- "number_cards": get_number_cards(),
- })
+ return frappe._dict(
+ {
+ "dashboards": get_dashboards(),
+ "charts": get_charts(),
+ "number_cards": get_number_cards(),
+ }
+ )
+
def get_dashboards():
- return [{
- "name": "Manufacturing",
- "dashboard_name": "Manufacturing",
- "charts": [
- { "chart": "Produced Quantity", "width": "Half" },
- { "chart": "Completed Operation", "width": "Half" },
- { "chart": "Work Order Analysis", "width": "Half" },
- { "chart": "Quality Inspection Analysis", "width": "Half" },
- { "chart": "Pending Work Order", "width": "Half" },
- { "chart": "Last Month Downtime Analysis", "width": "Half" },
- { "chart": "Work Order Qty Analysis", "width": "Full" },
- { "chart": "Job Card Analysis", "width": "Full" }
- ],
- "cards": [
- { "card": "Monthly Total Work Order" },
- { "card": "Monthly Completed Work Order" },
- { "card": "Ongoing Job Card" },
- { "card": "Monthly Quality Inspection"}
- ]
- }]
+ return [
+ {
+ "name": "Manufacturing",
+ "dashboard_name": "Manufacturing",
+ "charts": [
+ {"chart": "Produced Quantity", "width": "Half"},
+ {"chart": "Completed Operation", "width": "Half"},
+ {"chart": "Work Order Analysis", "width": "Half"},
+ {"chart": "Quality Inspection Analysis", "width": "Half"},
+ {"chart": "Pending Work Order", "width": "Half"},
+ {"chart": "Last Month Downtime Analysis", "width": "Half"},
+ {"chart": "Work Order Qty Analysis", "width": "Full"},
+ {"chart": "Job Card Analysis", "width": "Full"},
+ ],
+ "cards": [
+ {"card": "Monthly Total Work Order"},
+ {"card": "Monthly Completed Work Order"},
+ {"card": "Ongoing Job Card"},
+ {"card": "Monthly Quality Inspection"},
+ ],
+ }
+ ]
+
def get_charts():
company = erpnext.get_default_company()
@@ -45,200 +51,198 @@
if not company:
company = frappe.db.get_value("Company", {"is_group": 0}, "name")
- return [{
- "doctype": "Dashboard Chart",
- "based_on": "modified",
- "chart_type": "Sum",
- "chart_name": _("Produced Quantity"),
- "name": "Produced Quantity",
- "document_type": "Work Order",
- "filters_json": json.dumps([['Work Order', 'docstatus', '=', 1, False]]),
- "group_by_type": "Count",
- "time_interval": "Monthly",
- "timespan": "Last Year",
- "owner": "Administrator",
- "type": "Line",
- "value_based_on": "produced_qty",
- "is_public": 1,
- "timeseries": 1
- }, {
- "doctype": "Dashboard Chart",
- "based_on": "creation",
- "chart_type": "Sum",
- "chart_name": _("Completed Operation"),
- "name": "Completed Operation",
- "document_type": "Work Order Operation",
- "filters_json": json.dumps([['Work Order Operation', 'docstatus', '=', 1, False]]),
- "group_by_type": "Count",
- "time_interval": "Quarterly",
- "timespan": "Last Year",
- "owner": "Administrator",
- "type": "Line",
- "value_based_on": "completed_qty",
- "is_public": 1,
- "timeseries": 1
- }, {
- "doctype": "Dashboard Chart",
- "time_interval": "Yearly",
- "chart_type": "Report",
- "chart_name": _("Work Order Analysis"),
- "name": "Work Order Analysis",
- "timespan": "Last Year",
- "report_name": "Work Order Summary",
- "owner": "Administrator",
- "filters_json": json.dumps({"company": company, "charts_based_on": "Status"}),
- "type": "Donut",
- "is_public": 1,
- "is_custom": 1,
- "custom_options": json.dumps({
- "axisOptions": {
- "shortenYAxisNumbers": 1
- },
- "height": 300
- }),
- }, {
- "doctype": "Dashboard Chart",
- "time_interval": "Yearly",
- "chart_type": "Report",
- "chart_name": _("Quality Inspection Analysis"),
- "name": "Quality Inspection Analysis",
- "timespan": "Last Year",
- "report_name": "Quality Inspection Summary",
- "owner": "Administrator",
- "filters_json": json.dumps({}),
- "type": "Donut",
- "is_public": 1,
- "is_custom": 1,
- "custom_options": json.dumps({
- "axisOptions": {
- "shortenYAxisNumbers": 1
- },
- "height": 300
- }),
- }, {
- "doctype": "Dashboard Chart",
- "time_interval": "Yearly",
- "chart_type": "Report",
- "chart_name": _("Pending Work Order"),
- "name": "Pending Work Order",
- "timespan": "Last Year",
- "report_name": "Work Order Summary",
- "filters_json": json.dumps({"company": company, "charts_based_on": "Age"}),
- "owner": "Administrator",
- "type": "Donut",
- "is_public": 1,
- "is_custom": 1,
- "custom_options": json.dumps({
- "axisOptions": {
- "shortenYAxisNumbers": 1
- },
- "height": 300
- }),
- }, {
- "doctype": "Dashboard Chart",
- "time_interval": "Yearly",
- "chart_type": "Report",
- "chart_name": _("Last Month Downtime Analysis"),
- "name": "Last Month Downtime Analysis",
- "timespan": "Last Year",
- "filters_json": json.dumps({}),
- "report_name": "Downtime Analysis",
- "owner": "Administrator",
- "is_public": 1,
- "is_custom": 1,
- "type": "Bar"
- }, {
- "doctype": "Dashboard Chart",
- "time_interval": "Yearly",
- "chart_type": "Report",
- "chart_name": _("Work Order Qty Analysis"),
- "name": "Work Order Qty Analysis",
- "timespan": "Last Year",
- "report_name": "Work Order Summary",
- "filters_json": json.dumps({"company": company, "charts_based_on": "Quantity"}),
- "owner": "Administrator",
- "type": "Bar",
- "is_public": 1,
- "is_custom": 1,
- "custom_options": json.dumps({
- "barOptions": { "stacked": 1 }
- }),
- }, {
- "doctype": "Dashboard Chart",
- "time_interval": "Yearly",
- "chart_type": "Report",
- "chart_name": _("Job Card Analysis"),
- "name": "Job Card Analysis",
- "timespan": "Last Year",
- "report_name": "Job Card Summary",
- "owner": "Administrator",
- "is_public": 1,
- "is_custom": 1,
- "filters_json": json.dumps({"company": company, "docstatus": 1, "range":"Monthly"}),
- "custom_options": json.dumps({
- "barOptions": { "stacked": 1 }
- }),
- "type": "Bar"
- }]
+ return [
+ {
+ "doctype": "Dashboard Chart",
+ "based_on": "modified",
+ "chart_type": "Sum",
+ "chart_name": _("Produced Quantity"),
+ "name": "Produced Quantity",
+ "document_type": "Work Order",
+ "filters_json": json.dumps([["Work Order", "docstatus", "=", 1, False]]),
+ "group_by_type": "Count",
+ "time_interval": "Monthly",
+ "timespan": "Last Year",
+ "owner": "Administrator",
+ "type": "Line",
+ "value_based_on": "produced_qty",
+ "is_public": 1,
+ "timeseries": 1,
+ },
+ {
+ "doctype": "Dashboard Chart",
+ "based_on": "creation",
+ "chart_type": "Sum",
+ "chart_name": _("Completed Operation"),
+ "name": "Completed Operation",
+ "document_type": "Work Order Operation",
+ "filters_json": json.dumps([["Work Order Operation", "docstatus", "=", 1, False]]),
+ "group_by_type": "Count",
+ "time_interval": "Quarterly",
+ "timespan": "Last Year",
+ "owner": "Administrator",
+ "type": "Line",
+ "value_based_on": "completed_qty",
+ "is_public": 1,
+ "timeseries": 1,
+ },
+ {
+ "doctype": "Dashboard Chart",
+ "time_interval": "Yearly",
+ "chart_type": "Report",
+ "chart_name": _("Work Order Analysis"),
+ "name": "Work Order Analysis",
+ "timespan": "Last Year",
+ "report_name": "Work Order Summary",
+ "owner": "Administrator",
+ "filters_json": json.dumps({"company": company, "charts_based_on": "Status"}),
+ "type": "Donut",
+ "is_public": 1,
+ "is_custom": 1,
+ "custom_options": json.dumps({"axisOptions": {"shortenYAxisNumbers": 1}, "height": 300}),
+ },
+ {
+ "doctype": "Dashboard Chart",
+ "time_interval": "Yearly",
+ "chart_type": "Report",
+ "chart_name": _("Quality Inspection Analysis"),
+ "name": "Quality Inspection Analysis",
+ "timespan": "Last Year",
+ "report_name": "Quality Inspection Summary",
+ "owner": "Administrator",
+ "filters_json": json.dumps({}),
+ "type": "Donut",
+ "is_public": 1,
+ "is_custom": 1,
+ "custom_options": json.dumps({"axisOptions": {"shortenYAxisNumbers": 1}, "height": 300}),
+ },
+ {
+ "doctype": "Dashboard Chart",
+ "time_interval": "Yearly",
+ "chart_type": "Report",
+ "chart_name": _("Pending Work Order"),
+ "name": "Pending Work Order",
+ "timespan": "Last Year",
+ "report_name": "Work Order Summary",
+ "filters_json": json.dumps({"company": company, "charts_based_on": "Age"}),
+ "owner": "Administrator",
+ "type": "Donut",
+ "is_public": 1,
+ "is_custom": 1,
+ "custom_options": json.dumps({"axisOptions": {"shortenYAxisNumbers": 1}, "height": 300}),
+ },
+ {
+ "doctype": "Dashboard Chart",
+ "time_interval": "Yearly",
+ "chart_type": "Report",
+ "chart_name": _("Last Month Downtime Analysis"),
+ "name": "Last Month Downtime Analysis",
+ "timespan": "Last Year",
+ "filters_json": json.dumps({}),
+ "report_name": "Downtime Analysis",
+ "owner": "Administrator",
+ "is_public": 1,
+ "is_custom": 1,
+ "type": "Bar",
+ },
+ {
+ "doctype": "Dashboard Chart",
+ "time_interval": "Yearly",
+ "chart_type": "Report",
+ "chart_name": _("Work Order Qty Analysis"),
+ "name": "Work Order Qty Analysis",
+ "timespan": "Last Year",
+ "report_name": "Work Order Summary",
+ "filters_json": json.dumps({"company": company, "charts_based_on": "Quantity"}),
+ "owner": "Administrator",
+ "type": "Bar",
+ "is_public": 1,
+ "is_custom": 1,
+ "custom_options": json.dumps({"barOptions": {"stacked": 1}}),
+ },
+ {
+ "doctype": "Dashboard Chart",
+ "time_interval": "Yearly",
+ "chart_type": "Report",
+ "chart_name": _("Job Card Analysis"),
+ "name": "Job Card Analysis",
+ "timespan": "Last Year",
+ "report_name": "Job Card Summary",
+ "owner": "Administrator",
+ "is_public": 1,
+ "is_custom": 1,
+ "filters_json": json.dumps({"company": company, "docstatus": 1, "range": "Monthly"}),
+ "custom_options": json.dumps({"barOptions": {"stacked": 1}}),
+ "type": "Bar",
+ },
+ ]
+
def get_number_cards():
start_date = add_months(nowdate(), -1)
end_date = nowdate()
- return [{
- "doctype": "Number Card",
- "document_type": "Work Order",
- "name": "Monthly Total Work Order",
- "filters_json": json.dumps([
- ['Work Order', 'docstatus', '=', 1],
- ['Work Order', 'creation', 'between', [start_date, end_date]]
- ]),
- "function": "Count",
- "is_public": 1,
- "label": _("Monthly Total Work Orders"),
- "show_percentage_stats": 1,
- "stats_time_interval": "Weekly"
- },
- {
- "doctype": "Number Card",
- "document_type": "Work Order",
- "name": "Monthly Completed Work Order",
- "filters_json": json.dumps([
- ['Work Order', 'status', '=', 'Completed'],
- ['Work Order', 'docstatus', '=', 1],
- ['Work Order', 'creation', 'between', [start_date, end_date]]
- ]),
- "function": "Count",
- "is_public": 1,
- "label": _("Monthly Completed Work Orders"),
- "show_percentage_stats": 1,
- "stats_time_interval": "Weekly"
- },
- {
- "doctype": "Number Card",
- "document_type": "Job Card",
- "name": "Ongoing Job Card",
- "filters_json": json.dumps([
- ['Job Card', 'status','!=','Completed'],
- ['Job Card', 'docstatus', '=', 1]
- ]),
- "function": "Count",
- "is_public": 1,
- "label": _("Ongoing Job Cards"),
- "show_percentage_stats": 1,
- "stats_time_interval": "Weekly"
- },
- {
- "doctype": "Number Card",
- "document_type": "Quality Inspection",
- "name": "Monthly Quality Inspection",
- "filters_json": json.dumps([
- ['Quality Inspection', 'docstatus', '=', 1],
- ['Quality Inspection', 'creation', 'between', [start_date, end_date]]
- ]),
- "function": "Count",
- "is_public": 1,
- "label": _("Monthly Quality Inspections"),
- "show_percentage_stats": 1,
- "stats_time_interval": "Weekly"
- }]
+ return [
+ {
+ "doctype": "Number Card",
+ "document_type": "Work Order",
+ "name": "Monthly Total Work Order",
+ "filters_json": json.dumps(
+ [
+ ["Work Order", "docstatus", "=", 1],
+ ["Work Order", "creation", "between", [start_date, end_date]],
+ ]
+ ),
+ "function": "Count",
+ "is_public": 1,
+ "label": _("Monthly Total Work Orders"),
+ "show_percentage_stats": 1,
+ "stats_time_interval": "Weekly",
+ },
+ {
+ "doctype": "Number Card",
+ "document_type": "Work Order",
+ "name": "Monthly Completed Work Order",
+ "filters_json": json.dumps(
+ [
+ ["Work Order", "status", "=", "Completed"],
+ ["Work Order", "docstatus", "=", 1],
+ ["Work Order", "creation", "between", [start_date, end_date]],
+ ]
+ ),
+ "function": "Count",
+ "is_public": 1,
+ "label": _("Monthly Completed Work Orders"),
+ "show_percentage_stats": 1,
+ "stats_time_interval": "Weekly",
+ },
+ {
+ "doctype": "Number Card",
+ "document_type": "Job Card",
+ "name": "Ongoing Job Card",
+ "filters_json": json.dumps(
+ [["Job Card", "status", "!=", "Completed"], ["Job Card", "docstatus", "=", 1]]
+ ),
+ "function": "Count",
+ "is_public": 1,
+ "label": _("Ongoing Job Cards"),
+ "show_percentage_stats": 1,
+ "stats_time_interval": "Weekly",
+ },
+ {
+ "doctype": "Number Card",
+ "document_type": "Quality Inspection",
+ "name": "Monthly Quality Inspection",
+ "filters_json": json.dumps(
+ [
+ ["Quality Inspection", "docstatus", "=", 1],
+ ["Quality Inspection", "creation", "between", [start_date, end_date]],
+ ]
+ ),
+ "function": "Count",
+ "is_public": 1,
+ "label": _("Monthly Quality Inspections"),
+ "show_percentage_stats": 1,
+ "stats_time_interval": "Weekly",
+ },
+ ]
diff --git a/erpnext/manufacturing/doctype/blanket_order/blanket_order.py b/erpnext/manufacturing/doctype/blanket_order/blanket_order.py
index 5340c51..ff21401 100644
--- a/erpnext/manufacturing/doctype/blanket_order/blanket_order.py
+++ b/erpnext/manufacturing/doctype/blanket_order/blanket_order.py
@@ -29,7 +29,9 @@
def update_ordered_qty(self):
ref_doctype = "Sales Order" if self.blanket_order_type == "Selling" else "Purchase Order"
- item_ordered_qty = frappe._dict(frappe.db.sql("""
+ item_ordered_qty = frappe._dict(
+ frappe.db.sql(
+ """
select trans_item.item_code, sum(trans_item.stock_qty) as qty
from `tab{0} Item` trans_item, `tab{0}` trans
where trans.name = trans_item.parent
@@ -37,18 +39,24 @@
and trans.docstatus=1
and trans.status not in ('Closed', 'Stopped')
group by trans_item.item_code
- """.format(ref_doctype), self.name))
+ """.format(
+ ref_doctype
+ ),
+ self.name,
+ )
+ )
for d in self.items:
d.db_set("ordered_qty", item_ordered_qty.get(d.item_code, 0))
+
@frappe.whitelist()
def make_order(source_name):
doctype = frappe.flags.args.doctype
def update_doc(source_doc, target_doc, source_parent):
- if doctype == 'Quotation':
- target_doc.quotation_to = 'Customer'
+ if doctype == "Quotation":
+ target_doc.quotation_to = "Customer"
target_doc.party_name = source_doc.customer
def update_item(source, target, source_parent):
@@ -62,18 +70,16 @@
target.against_blanket_order = 1
target.blanket_order = source_name
- target_doc = get_mapped_doc("Blanket Order", source_name, {
- "Blanket Order": {
- "doctype": doctype,
- "postprocess": update_doc
- },
- "Blanket Order Item": {
- "doctype": doctype + " Item",
- "field_map": {
- "rate": "blanket_order_rate",
- "parent": "blanket_order"
+ target_doc = get_mapped_doc(
+ "Blanket Order",
+ source_name,
+ {
+ "Blanket Order": {"doctype": doctype, "postprocess": update_doc},
+ "Blanket Order Item": {
+ "doctype": doctype + " Item",
+ "field_map": {"rate": "blanket_order_rate", "parent": "blanket_order"},
+ "postprocess": update_item,
},
- "postprocess": update_item
- }
- })
+ },
+ )
return target_doc
diff --git a/erpnext/manufacturing/doctype/blanket_order/blanket_order_dashboard.py b/erpnext/manufacturing/doctype/blanket_order/blanket_order_dashboard.py
index c6745c8..3106234 100644
--- a/erpnext/manufacturing/doctype/blanket_order/blanket_order_dashboard.py
+++ b/erpnext/manufacturing/doctype/blanket_order/blanket_order_dashboard.py
@@ -1,9 +1,5 @@
def get_data():
return {
- 'fieldname': 'blanket_order',
- 'transactions': [
- {
- 'items': ['Purchase Order', 'Sales Order', 'Quotation']
- }
- ]
+ "fieldname": "blanket_order",
+ "transactions": [{"items": ["Purchase Order", "Sales Order", "Quotation"]}],
}
diff --git a/erpnext/manufacturing/doctype/blanket_order/test_blanket_order.py b/erpnext/manufacturing/doctype/blanket_order/test_blanket_order.py
index d4d337d..2f1f3ae 100644
--- a/erpnext/manufacturing/doctype/blanket_order/test_blanket_order.py
+++ b/erpnext/manufacturing/doctype/blanket_order/test_blanket_order.py
@@ -16,7 +16,7 @@
def test_sales_order_creation(self):
bo = make_blanket_order(blanket_order_type="Selling")
- frappe.flags.args.doctype = 'Sales Order'
+ frappe.flags.args.doctype = "Sales Order"
so = make_order(bo.name)
so.currency = get_company_currency(so.company)
so.delivery_date = today()
@@ -33,16 +33,15 @@
self.assertEqual(so.items[0].qty, bo.items[0].ordered_qty)
# test the quantity
- frappe.flags.args.doctype = 'Sales Order'
+ frappe.flags.args.doctype = "Sales Order"
so1 = make_order(bo.name)
so1.currency = get_company_currency(so1.company)
- self.assertEqual(so1.items[0].qty, (bo.items[0].qty-bo.items[0].ordered_qty))
-
+ self.assertEqual(so1.items[0].qty, (bo.items[0].qty - bo.items[0].ordered_qty))
def test_purchase_order_creation(self):
bo = make_blanket_order(blanket_order_type="Purchasing")
- frappe.flags.args.doctype = 'Purchase Order'
+ frappe.flags.args.doctype = "Purchase Order"
po = make_order(bo.name)
po.currency = get_company_currency(po.company)
po.schedule_date = today()
@@ -59,11 +58,10 @@
self.assertEqual(po.items[0].qty, bo.items[0].ordered_qty)
# test the quantity
- frappe.flags.args.doctype = 'Purchase Order'
+ frappe.flags.args.doctype = "Purchase Order"
po1 = make_order(bo.name)
po1.currency = get_company_currency(po1.company)
- self.assertEqual(po1.items[0].qty, (bo.items[0].qty-bo.items[0].ordered_qty))
-
+ self.assertEqual(po1.items[0].qty, (bo.items[0].qty - bo.items[0].ordered_qty))
def make_blanket_order(**args):
@@ -80,11 +78,14 @@
bo.from_date = today()
bo.to_date = add_months(bo.from_date, months=12)
- bo.append("items", {
- "item_code": args.item_code or "_Test Item",
- "qty": args.quantity or 1000,
- "rate": args.rate or 100
- })
+ bo.append(
+ "items",
+ {
+ "item_code": args.item_code or "_Test Item",
+ "qty": args.quantity or 1000,
+ "rate": args.rate or 100,
+ },
+ )
bo.insert()
bo.submit()
diff --git a/erpnext/manufacturing/doctype/bom/bom.py b/erpnext/manufacturing/doctype/bom/bom.py
index 4d7e459..bf29474 100644
--- a/erpnext/manufacturing/doctype/bom/bom.py
+++ b/erpnext/manufacturing/doctype/bom/bom.py
@@ -19,9 +19,7 @@
from erpnext.stock.doctype.item.item import get_item_details
from erpnext.stock.get_item_details import get_conversion_factor, get_price_list_rate
-form_grid_templates = {
- "items": "templates/form_grid/item_grid.html"
-}
+form_grid_templates = {"items": "templates/form_grid/item_grid.html"}
class BOMTree:
@@ -31,10 +29,12 @@
# ref: https://docs.python.org/3/reference/datamodel.html#slots
__slots__ = ["name", "child_items", "is_bom", "item_code", "exploded_qty", "qty"]
- def __init__(self, name: str, is_bom: bool = True, exploded_qty: float = 1.0, qty: float = 1) -> None:
+ def __init__(
+ self, name: str, is_bom: bool = True, exploded_qty: float = 1.0, qty: float = 1
+ ) -> None:
self.name = name # name of node, BOM number if is_bom else item_code
self.child_items: List["BOMTree"] = [] # list of child items
- self.is_bom = is_bom # true if the node is a BOM and not a leaf item
+ self.is_bom = is_bom # true if the node is a BOM and not a leaf item
self.item_code: str = None # item_code associated with node
self.qty = qty # required unit quantity to make one unit of parent item.
self.exploded_qty = exploded_qty # total exploded qty required for making root of tree.
@@ -62,12 +62,12 @@
"""Get level order traversal of tree.
E.g. for following tree the traversal will return list of nodes in order from top to bottom.
BOM:
- - SubAssy1
- - item1
- - item2
- - SubAssy2
- - item3
- - item4
+ - SubAssy1
+ - item1
+ - item2
+ - SubAssy2
+ - item3
+ - item4
returns = [SubAssy1, item1, item2, SubAssy2, item3, item4]
"""
@@ -96,19 +96,18 @@
rep += child.__repr__(level=level + 1)
return rep
+
class BOM(WebsiteGenerator):
website = frappe._dict(
# page_title_field = "item_name",
- condition_field = "show_in_website",
- template = "templates/generators/bom.html"
+ condition_field="show_in_website",
+ template="templates/generators/bom.html",
)
def autoname(self):
# ignore amended documents while calculating current index
existing_boms = frappe.get_all(
- "BOM",
- filters={"item": self.item, "amended_from": ["is", "not set"]},
- pluck="name"
+ "BOM", filters={"item": self.item, "amended_from": ["is", "not set"]}, pluck="name"
)
if existing_boms:
@@ -135,11 +134,15 @@
conflicting_bom = frappe.get_doc("BOM", name)
if conflicting_bom.item != self.item:
- msg = (_("A BOM with name {0} already exists for item {1}.")
- .format(frappe.bold(name), frappe.bold(conflicting_bom.item)))
+ msg = _("A BOM with name {0} already exists for item {1}.").format(
+ frappe.bold(name), frappe.bold(conflicting_bom.item)
+ )
- frappe.throw(_("{0}{1} Did you rename the item? Please contact Administrator / Tech support")
- .format(msg, "<br>"))
+ frappe.throw(
+ _("{0}{1} Did you rename the item? Please contact Administrator / Tech support").format(
+ msg, "<br>"
+ )
+ )
self.name = name
@@ -164,7 +167,7 @@
return index
def validate(self):
- self.route = frappe.scrub(self.name).replace('_', '-')
+ self.route = frappe.scrub(self.name).replace("_", "-")
if not self.company:
frappe.throw(_("Please select a Company first."), title=_("Mandatory"))
@@ -184,14 +187,14 @@
self.validate_operations()
self.calculate_cost()
self.update_stock_qty()
- self.update_cost(update_parent=False, from_child_bom=True, update_hour_rate = False, save=False)
+ self.update_cost(update_parent=False, from_child_bom=True, update_hour_rate=False, save=False)
self.validate_scrap_items()
def get_context(self, context):
- context.parents = [{'name': 'boms', 'title': _('All BOMs') }]
+ context.parents = [{"name": "boms", "title": _("All BOMs")}]
def on_update(self):
- frappe.cache().hdel('bom_children', self.name)
+ frappe.cache().hdel("bom_children", self.name)
self.check_recursion()
def on_submit(self):
@@ -221,37 +224,53 @@
def get_routing(self):
if self.routing:
self.set("operations", [])
- fields = ["sequence_id", "operation", "workstation", "description",
- "time_in_mins", "batch_size", "operating_cost", "idx", "hour_rate",
- "set_cost_based_on_bom_qty", "fixed_time"]
+ fields = [
+ "sequence_id",
+ "operation",
+ "workstation",
+ "description",
+ "time_in_mins",
+ "batch_size",
+ "operating_cost",
+ "idx",
+ "hour_rate",
+ "set_cost_based_on_bom_qty",
+ "fixed_time",
+ ]
- for row in frappe.get_all("BOM Operation", fields = fields,
- filters = {'parenttype': 'Routing', 'parent': self.routing}, order_by="sequence_id, idx"):
- child = self.append('operations', row)
+ for row in frappe.get_all(
+ "BOM Operation",
+ fields=fields,
+ filters={"parenttype": "Routing", "parent": self.routing},
+ order_by="sequence_id, idx",
+ ):
+ child = self.append("operations", row)
child.hour_rate = flt(row.hour_rate / self.conversion_rate, child.precision("hour_rate"))
def set_bom_material_details(self):
for item in self.get("items"):
self.validate_bom_currency(item)
- item.bom_no = ''
+ item.bom_no = ""
if not item.do_not_explode:
item.bom_no = item.bom_no
- ret = self.get_bom_material_detail({
- "company": self.company,
- "item_code": item.item_code,
- "item_name": item.item_name,
- "bom_no": item.bom_no,
- "stock_qty": item.stock_qty,
- "include_item_in_manufacturing": item.include_item_in_manufacturing,
- "qty": item.qty,
- "uom": item.uom,
- "stock_uom": item.stock_uom,
- "conversion_factor": item.conversion_factor,
- "sourced_by_supplier": item.sourced_by_supplier,
- "do_not_explode": item.do_not_explode
- })
+ ret = self.get_bom_material_detail(
+ {
+ "company": self.company,
+ "item_code": item.item_code,
+ "item_name": item.item_name,
+ "bom_no": item.bom_no,
+ "stock_qty": item.stock_qty,
+ "include_item_in_manufacturing": item.include_item_in_manufacturing,
+ "qty": item.qty,
+ "uom": item.uom,
+ "stock_uom": item.stock_uom,
+ "conversion_factor": item.conversion_factor,
+ "sourced_by_supplier": item.sourced_by_supplier,
+ "do_not_explode": item.do_not_explode,
+ }
+ )
for r in ret:
if not item.get(r):
@@ -263,7 +282,7 @@
"item_code": item.item_code,
"company": self.company,
"scrap_items": True,
- "bom_no": '',
+ "bom_no": "",
}
ret = self.get_bom_material_detail(args)
for key, value in ret.items():
@@ -272,75 +291,93 @@
@frappe.whitelist()
def get_bom_material_detail(self, args=None):
- """ Get raw material details like uom, desc and rate"""
+ """Get raw material details like uom, desc and rate"""
if not args:
- args = frappe.form_dict.get('args')
+ args = frappe.form_dict.get("args")
if isinstance(args, str):
import json
+
args = json.loads(args)
- item = self.get_item_det(args['item_code'])
+ item = self.get_item_det(args["item_code"])
- args['bom_no'] = args['bom_no'] or item and cstr(item['default_bom']) or ''
- args['transfer_for_manufacture'] = (cstr(args.get('include_item_in_manufacturing', '')) or
- item and item.include_item_in_manufacturing or 0)
+ args["bom_no"] = args["bom_no"] or item and cstr(item["default_bom"]) or ""
+ args["transfer_for_manufacture"] = (
+ cstr(args.get("include_item_in_manufacturing", ""))
+ or item
+ and item.include_item_in_manufacturing
+ or 0
+ )
args.update(item)
rate = self.get_rm_rate(args)
ret_item = {
- 'item_name' : item and args['item_name'] or '',
- 'description' : item and args['description'] or '',
- 'image' : item and args['image'] or '',
- 'stock_uom' : item and args['stock_uom'] or '',
- 'uom' : item and args['stock_uom'] or '',
- 'conversion_factor': 1,
- 'bom_no' : args['bom_no'],
- 'rate' : rate,
- 'qty' : args.get("qty") or args.get("stock_qty") or 1,
- 'stock_qty' : args.get("qty") or args.get("stock_qty") or 1,
- 'base_rate' : flt(rate) * (flt(self.conversion_rate) or 1),
- 'include_item_in_manufacturing': cint(args.get('transfer_for_manufacture')),
- 'sourced_by_supplier' : args.get('sourced_by_supplier', 0)
+ "item_name": item and args["item_name"] or "",
+ "description": item and args["description"] or "",
+ "image": item and args["image"] or "",
+ "stock_uom": item and args["stock_uom"] or "",
+ "uom": item and args["stock_uom"] or "",
+ "conversion_factor": 1,
+ "bom_no": args["bom_no"],
+ "rate": rate,
+ "qty": args.get("qty") or args.get("stock_qty") or 1,
+ "stock_qty": args.get("qty") or args.get("stock_qty") or 1,
+ "base_rate": flt(rate) * (flt(self.conversion_rate) or 1),
+ "include_item_in_manufacturing": cint(args.get("transfer_for_manufacture")),
+ "sourced_by_supplier": args.get("sourced_by_supplier", 0),
}
- if args.get('do_not_explode'):
- ret_item['bom_no'] = ''
+ if args.get("do_not_explode"):
+ ret_item["bom_no"] = ""
return ret_item
def validate_bom_currency(self, item):
- if item.get('bom_no') and frappe.db.get_value('BOM', item.get('bom_no'), 'currency') != self.currency:
- frappe.throw(_("Row {0}: Currency of the BOM #{1} should be equal to the selected currency {2}")
- .format(item.idx, item.bom_no, self.currency))
+ if (
+ item.get("bom_no")
+ and frappe.db.get_value("BOM", item.get("bom_no"), "currency") != self.currency
+ ):
+ frappe.throw(
+ _("Row {0}: Currency of the BOM #{1} should be equal to the selected currency {2}").format(
+ item.idx, item.bom_no, self.currency
+ )
+ )
def get_rm_rate(self, arg):
- """ Get raw material rate as per selected method, if bom exists takes bom cost """
+ """Get raw material rate as per selected method, if bom exists takes bom cost"""
rate = 0
if not self.rm_cost_as_per:
self.rm_cost_as_per = "Valuation Rate"
- if arg.get('scrap_items'):
+ if arg.get("scrap_items"):
rate = get_valuation_rate(arg)
elif arg:
- #Customer Provided parts and Supplier sourced parts will have zero rate
- if not frappe.db.get_value('Item', arg["item_code"], 'is_customer_provided_item') and not arg.get('sourced_by_supplier'):
- if arg.get('bom_no') and self.set_rate_of_sub_assembly_item_based_on_bom:
- rate = flt(self.get_bom_unitcost(arg['bom_no'])) * (arg.get("conversion_factor") or 1)
+ # Customer Provided parts and Supplier sourced parts will have zero rate
+ if not frappe.db.get_value(
+ "Item", arg["item_code"], "is_customer_provided_item"
+ ) and not arg.get("sourced_by_supplier"):
+ if arg.get("bom_no") and self.set_rate_of_sub_assembly_item_based_on_bom:
+ rate = flt(self.get_bom_unitcost(arg["bom_no"])) * (arg.get("conversion_factor") or 1)
else:
rate = get_bom_item_rate(arg, self)
if not rate:
if self.rm_cost_as_per == "Price List":
- frappe.msgprint(_("Price not found for item {0} in price list {1}")
- .format(arg["item_code"], self.buying_price_list), alert=True)
+ frappe.msgprint(
+ _("Price not found for item {0} in price list {1}").format(
+ arg["item_code"], self.buying_price_list
+ ),
+ alert=True,
+ )
else:
- frappe.msgprint(_("{0} not found for item {1}")
- .format(self.rm_cost_as_per, arg["item_code"]), alert=True)
+ frappe.msgprint(
+ _("{0} not found for item {1}").format(self.rm_cost_as_per, arg["item_code"]), alert=True
+ )
return flt(rate) * flt(self.plc_conversion_rate or 1) / (self.conversion_rate or 1)
@frappe.whitelist()
- def update_cost(self, update_parent=True, from_child_bom=False, update_hour_rate = True, save=True):
+ def update_cost(self, update_parent=True, from_child_bom=False, update_hour_rate=True, save=True):
if self.docstatus == 2:
return
@@ -350,16 +387,18 @@
if not d.item_code:
continue
- rate = self.get_rm_rate({
- "company": self.company,
- "item_code": d.item_code,
- "bom_no": d.bom_no,
- "qty": d.qty,
- "uom": d.uom,
- "stock_uom": d.stock_uom,
- "conversion_factor": d.conversion_factor,
- "sourced_by_supplier": d.sourced_by_supplier
- })
+ rate = self.get_rm_rate(
+ {
+ "company": self.company,
+ "item_code": d.item_code,
+ "bom_no": d.bom_no,
+ "qty": d.qty,
+ "uom": d.uom,
+ "stock_uom": d.stock_uom,
+ "conversion_factor": d.conversion_factor,
+ "sourced_by_supplier": d.sourced_by_supplier,
+ }
+ )
if rate:
d.rate = rate
@@ -380,8 +419,11 @@
# update parent BOMs
if self.total_cost != existing_bom_cost and update_parent:
- parent_boms = frappe.db.sql_list("""select distinct parent from `tabBOM Item`
- where bom_no = %s and docstatus=1 and parenttype='BOM'""", self.name)
+ parent_boms = frappe.db.sql_list(
+ """select distinct parent from `tabBOM Item`
+ where bom_no = %s and docstatus=1 and parenttype='BOM'""",
+ self.name,
+ )
for bom in parent_boms:
frappe.get_doc("BOM", bom).update_cost(from_child_bom=True)
@@ -393,45 +435,54 @@
if self.total_cost:
cost = self.total_cost / self.quantity
- frappe.db.sql("""update `tabBOM Item` set rate=%s, amount=stock_qty*%s
+ frappe.db.sql(
+ """update `tabBOM Item` set rate=%s, amount=stock_qty*%s
where bom_no = %s and docstatus < 2 and parenttype='BOM'""",
- (cost, cost, self.name))
+ (cost, cost, self.name),
+ )
def get_bom_unitcost(self, bom_no):
- bom = frappe.db.sql("""select name, base_total_cost/quantity as unit_cost from `tabBOM`
- where is_active = 1 and name = %s""", bom_no, as_dict=1)
- return bom and bom[0]['unit_cost'] or 0
+ bom = frappe.db.sql(
+ """select name, base_total_cost/quantity as unit_cost from `tabBOM`
+ where is_active = 1 and name = %s""",
+ bom_no,
+ as_dict=1,
+ )
+ return bom and bom[0]["unit_cost"] or 0
def manage_default_bom(self):
- """ Uncheck others if current one is selected as default or
- check the current one as default if it the only bom for the selected item,
- update default bom in item master
+ """Uncheck others if current one is selected as default or
+ check the current one as default if it the only bom for the selected item,
+ update default bom in item master
"""
if self.is_default and self.is_active:
from frappe.model.utils import set_default
+
set_default(self, "item")
item = frappe.get_doc("Item", self.item)
if item.default_bom != self.name:
- frappe.db.set_value('Item', self.item, 'default_bom', self.name)
- elif not frappe.db.exists(dict(doctype='BOM', docstatus=1, item=self.item, is_default=1)) \
- and self.is_active:
+ frappe.db.set_value("Item", self.item, "default_bom", self.name)
+ elif (
+ not frappe.db.exists(dict(doctype="BOM", docstatus=1, item=self.item, is_default=1))
+ and self.is_active
+ ):
frappe.db.set(self, "is_default", 1)
else:
frappe.db.set(self, "is_default", 0)
item = frappe.get_doc("Item", self.item)
if item.default_bom == self.name:
- frappe.db.set_value('Item', self.item, 'default_bom', None)
+ frappe.db.set_value("Item", self.item, "default_bom", None)
def clear_operations(self):
if not self.with_operations:
- self.set('operations', [])
+ self.set("operations", [])
def clear_inspection(self):
if not self.inspection_required:
self.quality_inspection_template = None
def validate_main_item(self):
- """ Validate main FG item"""
+ """Validate main FG item"""
item = self.get_item_det(self.item)
if not item:
frappe.throw(_("Item {0} does not exist in the system or has expired").format(self.item))
@@ -439,30 +490,34 @@
ret = frappe.db.get_value("Item", self.item, ["description", "stock_uom", "item_name"])
self.description = ret[0]
self.uom = ret[1]
- self.item_name= ret[2]
+ self.item_name = ret[2]
if not self.quantity:
frappe.throw(_("Quantity should be greater than 0"))
def validate_currency(self):
- if self.rm_cost_as_per == 'Price List':
- price_list_currency = frappe.db.get_value('Price List', self.buying_price_list, 'currency')
+ if self.rm_cost_as_per == "Price List":
+ price_list_currency = frappe.db.get_value("Price List", self.buying_price_list, "currency")
if price_list_currency not in (self.currency, self.company_currency()):
- frappe.throw(_("Currency of the price list {0} must be {1} or {2}")
- .format(self.buying_price_list, self.currency, self.company_currency()))
+ frappe.throw(
+ _("Currency of the price list {0} must be {1} or {2}").format(
+ self.buying_price_list, self.currency, self.company_currency()
+ )
+ )
def update_stock_qty(self):
- for m in self.get('items'):
+ for m in self.get("items"):
if not m.conversion_factor:
- m.conversion_factor = flt(get_conversion_factor(m.item_code, m.uom)['conversion_factor'])
+ m.conversion_factor = flt(get_conversion_factor(m.item_code, m.uom)["conversion_factor"])
if m.uom and m.qty:
- m.stock_qty = flt(m.conversion_factor)*flt(m.qty)
+ m.stock_qty = flt(m.conversion_factor) * flt(m.qty)
if not m.uom and m.stock_uom:
m.uom = m.stock_uom
m.qty = m.stock_qty
def validate_uom_is_interger(self):
from erpnext.utilities.transaction_base import validate_uom_is_integer
+
validate_uom_is_integer(self, "uom", "qty", "BOM Item")
validate_uom_is_integer(self, "stock_uom", "stock_qty", "BOM Item")
@@ -470,23 +525,26 @@
if self.currency == self.company_currency():
self.conversion_rate = 1
elif self.conversion_rate == 1 or flt(self.conversion_rate) <= 0:
- self.conversion_rate = get_exchange_rate(self.currency, self.company_currency(), args="for_buying")
+ self.conversion_rate = get_exchange_rate(
+ self.currency, self.company_currency(), args="for_buying"
+ )
def set_plc_conversion_rate(self):
if self.rm_cost_as_per in ["Valuation Rate", "Last Purchase Rate"]:
self.plc_conversion_rate = 1
elif not self.plc_conversion_rate and self.price_list_currency:
- self.plc_conversion_rate = get_exchange_rate(self.price_list_currency,
- self.company_currency(), args="for_buying")
+ self.plc_conversion_rate = get_exchange_rate(
+ self.price_list_currency, self.company_currency(), args="for_buying"
+ )
def validate_materials(self):
- """ Validate raw material entries """
+ """Validate raw material entries"""
- if not self.get('items'):
+ if not self.get("items"):
frappe.throw(_("Raw Materials cannot be blank."))
check_list = []
- for m in self.get('items'):
+ for m in self.get("items"):
if m.bom_no:
validate_bom_no(m.item_code, m.bom_no)
if flt(m.qty) <= 0:
@@ -494,13 +552,20 @@
check_list.append(m)
def check_recursion(self, bom_list=None):
- """ Check whether recursion occurs in any bom"""
+ """Check whether recursion occurs in any bom"""
+
def _throw_error(bom_name):
frappe.throw(_("BOM recursion: {0} cannot be parent or child of {0}").format(bom_name))
bom_list = self.traverse_tree()
- child_items = frappe.get_all('BOM Item', fields=["bom_no", "item_code"],
- filters={'parent': ('in', bom_list), 'parenttype': 'BOM'}) or []
+ child_items = (
+ frappe.get_all(
+ "BOM Item",
+ fields=["bom_no", "item_code"],
+ filters={"parent": ("in", bom_list), "parenttype": "BOM"},
+ )
+ or []
+ )
child_bom = {d.bom_no for d in child_items}
child_items_codes = {d.item_code for d in child_items}
@@ -511,19 +576,26 @@
if self.item in child_items_codes:
_throw_error(self.item)
- bom_nos = frappe.get_all('BOM Item', fields=["parent"],
- filters={'bom_no': self.name, 'parenttype': 'BOM'}) or []
+ bom_nos = (
+ frappe.get_all(
+ "BOM Item", fields=["parent"], filters={"bom_no": self.name, "parenttype": "BOM"}
+ )
+ or []
+ )
if self.name in {d.parent for d in bom_nos}:
_throw_error(self.name)
def traverse_tree(self, bom_list=None):
def _get_children(bom_no):
- children = frappe.cache().hget('bom_children', bom_no)
+ children = frappe.cache().hget("bom_children", bom_no)
if children is None:
- children = frappe.db.sql_list("""SELECT `bom_no` FROM `tabBOM Item`
- WHERE `parent`=%s AND `bom_no`!='' AND `parenttype`='BOM'""", bom_no)
- frappe.cache().hset('bom_children', bom_no, children)
+ children = frappe.db.sql_list(
+ """SELECT `bom_no` FROM `tabBOM Item`
+ WHERE `parent`=%s AND `bom_no`!='' AND `parenttype`='BOM'""",
+ bom_no,
+ )
+ frappe.cache().hset("bom_children", bom_no, children)
return children
count = 0
@@ -533,7 +605,7 @@
if self.name not in bom_list:
bom_list.append(self.name)
- while(count < len(bom_list)):
+ while count < len(bom_list):
for child_bom in _get_children(bom_list[count]):
if child_bom not in bom_list:
bom_list.append(child_bom)
@@ -541,19 +613,21 @@
bom_list.reverse()
return bom_list
- def calculate_cost(self, update_hour_rate = False):
+ def calculate_cost(self, update_hour_rate=False):
"""Calculate bom totals"""
self.calculate_op_cost(update_hour_rate)
self.calculate_rm_cost()
self.calculate_sm_cost()
self.total_cost = self.operating_cost + self.raw_material_cost - self.scrap_material_cost
- self.base_total_cost = self.base_operating_cost + self.base_raw_material_cost - self.base_scrap_material_cost
+ self.base_total_cost = (
+ self.base_operating_cost + self.base_raw_material_cost - self.base_scrap_material_cost
+ )
- def calculate_op_cost(self, update_hour_rate = False):
+ def calculate_op_cost(self, update_hour_rate=False):
"""Update workstation rate and calculates totals"""
self.operating_cost = 0
self.base_operating_cost = 0
- for d in self.get('operations'):
+ for d in self.get("operations"):
if d.workstation:
self.update_rate_and_time(d, update_hour_rate)
@@ -566,13 +640,14 @@
self.operating_cost += flt(operating_cost)
self.base_operating_cost += flt(base_operating_cost)
- def update_rate_and_time(self, row, update_hour_rate = False):
+ def update_rate_and_time(self, row, update_hour_rate=False):
if not row.hour_rate or update_hour_rate:
hour_rate = flt(frappe.get_cached_value("Workstation", row.workstation, "hour_rate"))
if hour_rate:
- row.hour_rate = (hour_rate / flt(self.conversion_rate)
- if self.conversion_rate and hour_rate else hour_rate)
+ row.hour_rate = (
+ hour_rate / flt(self.conversion_rate) if self.conversion_rate and hour_rate else hour_rate
+ )
if row.hour_rate and row.time_in_mins:
row.base_hour_rate = flt(row.hour_rate) * flt(self.conversion_rate)
@@ -589,12 +664,13 @@
total_rm_cost = 0
base_total_rm_cost = 0
- for d in self.get('items'):
+ for d in self.get("items"):
d.base_rate = flt(d.rate) * flt(self.conversion_rate)
d.amount = flt(d.rate, d.precision("rate")) * flt(d.qty, d.precision("qty"))
d.base_amount = d.amount * flt(self.conversion_rate)
- d.qty_consumed_per_unit = flt(d.stock_qty, d.precision("stock_qty")) \
- / flt(self.quantity, self.precision("quantity"))
+ d.qty_consumed_per_unit = flt(d.stock_qty, d.precision("stock_qty")) / flt(
+ self.quantity, self.precision("quantity")
+ )
total_rm_cost += d.amount
base_total_rm_cost += d.base_amount
@@ -607,10 +683,14 @@
total_sm_cost = 0
base_total_sm_cost = 0
- for d in self.get('scrap_items'):
- d.base_rate = flt(d.rate, d.precision("rate")) * flt(self.conversion_rate, self.precision("conversion_rate"))
+ for d in self.get("scrap_items"):
+ d.base_rate = flt(d.rate, d.precision("rate")) * flt(
+ self.conversion_rate, self.precision("conversion_rate")
+ )
d.amount = flt(d.rate, d.precision("rate")) * flt(d.stock_qty, d.precision("stock_qty"))
- d.base_amount = flt(d.amount, d.precision("amount")) * flt(self.conversion_rate, self.precision("conversion_rate"))
+ d.base_amount = flt(d.amount, d.precision("amount")) * flt(
+ self.conversion_rate, self.precision("conversion_rate")
+ )
total_sm_cost += d.amount
base_total_sm_cost += d.base_amount
@@ -619,37 +699,42 @@
def update_new_bom(self, old_bom, new_bom, rate):
for d in self.get("items"):
- if d.bom_no != old_bom: continue
+ if d.bom_no != old_bom:
+ continue
d.bom_no = new_bom
d.rate = rate
d.amount = (d.stock_qty or d.qty) * rate
def update_exploded_items(self, save=True):
- """ Update Flat BOM, following will be correct data"""
+ """Update Flat BOM, following will be correct data"""
self.get_exploded_items()
self.add_exploded_items(save=save)
def get_exploded_items(self):
- """ Get all raw materials including items from child bom"""
+ """Get all raw materials including items from child bom"""
self.cur_exploded_items = {}
- for d in self.get('items'):
+ for d in self.get("items"):
if d.bom_no:
self.get_child_exploded_items(d.bom_no, d.stock_qty)
elif d.item_code:
- self.add_to_cur_exploded_items(frappe._dict({
- 'item_code' : d.item_code,
- 'item_name' : d.item_name,
- 'operation' : d.operation,
- 'source_warehouse': d.source_warehouse,
- 'description' : d.description,
- 'image' : d.image,
- 'stock_uom' : d.stock_uom,
- 'stock_qty' : flt(d.stock_qty),
- 'rate' : flt(d.base_rate) / (flt(d.conversion_factor) or 1.0),
- 'include_item_in_manufacturing': d.include_item_in_manufacturing,
- 'sourced_by_supplier': d.sourced_by_supplier
- }))
+ self.add_to_cur_exploded_items(
+ frappe._dict(
+ {
+ "item_code": d.item_code,
+ "item_name": d.item_name,
+ "operation": d.operation,
+ "source_warehouse": d.source_warehouse,
+ "description": d.description,
+ "image": d.image,
+ "stock_uom": d.stock_uom,
+ "stock_qty": flt(d.stock_qty),
+ "rate": flt(d.base_rate) / (flt(d.conversion_factor) or 1.0),
+ "include_item_in_manufacturing": d.include_item_in_manufacturing,
+ "sourced_by_supplier": d.sourced_by_supplier,
+ }
+ )
+ )
def company_currency(self):
return erpnext.get_company_currency(self.company)
@@ -661,9 +746,10 @@
self.cur_exploded_items[args.item_code] = args
def get_child_exploded_items(self, bom_no, stock_qty):
- """ Add all items from Flat BOM of child BOM"""
+ """Add all items from Flat BOM of child BOM"""
# Did not use qty_consumed_per_unit in the query, as it leads to rounding loss
- child_fb_items = frappe.db.sql("""
+ child_fb_items = frappe.db.sql(
+ """
SELECT
bom_item.item_code,
bom_item.item_name,
@@ -681,31 +767,38 @@
bom_item.parent = bom.name
AND bom.name = %s
AND bom.docstatus = 1
- """, bom_no, as_dict = 1)
+ """,
+ bom_no,
+ as_dict=1,
+ )
for d in child_fb_items:
- self.add_to_cur_exploded_items(frappe._dict({
- 'item_code' : d['item_code'],
- 'item_name' : d['item_name'],
- 'source_warehouse' : d['source_warehouse'],
- 'operation' : d['operation'],
- 'description' : d['description'],
- 'stock_uom' : d['stock_uom'],
- 'stock_qty' : d['qty_consumed_per_unit'] * stock_qty,
- 'rate' : flt(d['rate']),
- 'include_item_in_manufacturing': d.get('include_item_in_manufacturing', 0),
- 'sourced_by_supplier': d.get('sourced_by_supplier', 0)
- }))
+ self.add_to_cur_exploded_items(
+ frappe._dict(
+ {
+ "item_code": d["item_code"],
+ "item_name": d["item_name"],
+ "source_warehouse": d["source_warehouse"],
+ "operation": d["operation"],
+ "description": d["description"],
+ "stock_uom": d["stock_uom"],
+ "stock_qty": d["qty_consumed_per_unit"] * stock_qty,
+ "rate": flt(d["rate"]),
+ "include_item_in_manufacturing": d.get("include_item_in_manufacturing", 0),
+ "sourced_by_supplier": d.get("sourced_by_supplier", 0),
+ }
+ )
+ )
def add_exploded_items(self, save=True):
"Add items to Flat BOM table"
- self.set('exploded_items', [])
+ self.set("exploded_items", [])
if save:
frappe.db.sql("""delete from `tabBOM Explosion Item` where parent=%s""", self.name)
for d in sorted(self.cur_exploded_items, key=itemgetter(0)):
- ch = self.append('exploded_items', {})
+ ch = self.append("exploded_items", {})
for i in self.cur_exploded_items[d].keys():
ch.set(i, self.cur_exploded_items[d][i])
ch.amount = flt(ch.stock_qty) * flt(ch.rate)
@@ -717,10 +810,13 @@
def validate_bom_links(self):
if not self.is_active:
- act_pbom = frappe.db.sql("""select distinct bom_item.parent from `tabBOM Item` bom_item
+ act_pbom = frappe.db.sql(
+ """select distinct bom_item.parent from `tabBOM Item` bom_item
where bom_item.bom_no = %s and bom_item.docstatus = 1 and bom_item.parenttype='BOM'
and exists (select * from `tabBOM` where name = bom_item.parent
- and docstatus = 1 and is_active = 1)""", self.name)
+ and docstatus = 1 and is_active = 1)""",
+ self.name,
+ )
if act_pbom and act_pbom[0][0]:
frappe.throw(_("Cannot deactivate or cancel BOM as it is linked with other BOMs"))
@@ -729,20 +825,23 @@
if not self.with_operations:
self.transfer_material_against = "Work Order"
if not self.transfer_material_against and not self.is_new():
- frappe.throw(_("Setting {} is required").format(self.meta.get_label("transfer_material_against")), title=_("Missing value"))
+ frappe.throw(
+ _("Setting {} is required").format(self.meta.get_label("transfer_material_against")),
+ title=_("Missing value"),
+ )
def set_routing_operations(self):
if self.routing and self.with_operations and not self.operations:
self.get_routing()
def validate_operations(self):
- if self.with_operations and not self.get('operations') and self.docstatus == 1:
+ if self.with_operations and not self.get("operations") and self.docstatus == 1:
frappe.throw(_("Operations cannot be left blank"))
if self.with_operations:
for d in self.operations:
if not d.description:
- d.description = frappe.db.get_value('Operation', d.operation, 'description')
+ d.description = frappe.db.get_value("Operation", d.operation, "description")
if not d.batch_size or d.batch_size <= 0:
d.batch_size = 1
@@ -754,64 +853,75 @@
for item in self.scrap_items:
msg = ""
if item.item_code == self.item and not item.is_process_loss:
- msg = _('Scrap/Loss Item: {0} should have Is Process Loss checked as it is the same as the item to be manufactured or repacked.') \
- .format(frappe.bold(item.item_code))
+ msg = _(
+ "Scrap/Loss Item: {0} should have Is Process Loss checked as it is the same as the item to be manufactured or repacked."
+ ).format(frappe.bold(item.item_code))
elif item.item_code != self.item and item.is_process_loss:
- msg = _('Scrap/Loss Item: {0} should not have Is Process Loss checked as it is different from the item to be manufactured or repacked') \
- .format(frappe.bold(item.item_code))
+ msg = _(
+ "Scrap/Loss Item: {0} should not have Is Process Loss checked as it is different from the item to be manufactured or repacked"
+ ).format(frappe.bold(item.item_code))
must_be_whole_number = frappe.get_value("UOM", item.stock_uom, "must_be_whole_number")
if item.is_process_loss and must_be_whole_number:
- msg = _("Item: {0} with Stock UOM: {1} cannot be a Scrap/Loss Item as {1} is a whole UOM.") \
- .format(frappe.bold(item.item_code), frappe.bold(item.stock_uom))
+ msg = _(
+ "Item: {0} with Stock UOM: {1} cannot be a Scrap/Loss Item as {1} is a whole UOM."
+ ).format(frappe.bold(item.item_code), frappe.bold(item.stock_uom))
if item.is_process_loss and (item.stock_qty >= self.quantity):
- msg = _("Scrap/Loss Item: {0} should have Qty less than finished goods Quantity.") \
- .format(frappe.bold(item.item_code))
+ msg = _("Scrap/Loss Item: {0} should have Qty less than finished goods Quantity.").format(
+ frappe.bold(item.item_code)
+ )
if item.is_process_loss and (item.rate > 0):
- msg = _("Scrap/Loss Item: {0} should have Rate set to 0 because Is Process Loss is checked.") \
- .format(frappe.bold(item.item_code))
+ msg = _(
+ "Scrap/Loss Item: {0} should have Rate set to 0 because Is Process Loss is checked."
+ ).format(frappe.bold(item.item_code))
if msg:
frappe.throw(msg, title=_("Note"))
+
def get_bom_item_rate(args, bom_doc):
- if bom_doc.rm_cost_as_per == 'Valuation Rate':
+ if bom_doc.rm_cost_as_per == "Valuation Rate":
rate = get_valuation_rate(args) * (args.get("conversion_factor") or 1)
- elif bom_doc.rm_cost_as_per == 'Last Purchase Rate':
- rate = (flt(args.get('last_purchase_rate'))
- or flt(frappe.db.get_value("Item", args['item_code'], "last_purchase_rate"))) \
- * (args.get("conversion_factor") or 1)
+ elif bom_doc.rm_cost_as_per == "Last Purchase Rate":
+ rate = (
+ flt(args.get("last_purchase_rate"))
+ or flt(frappe.db.get_value("Item", args["item_code"], "last_purchase_rate"))
+ ) * (args.get("conversion_factor") or 1)
elif bom_doc.rm_cost_as_per == "Price List":
if not bom_doc.buying_price_list:
frappe.throw(_("Please select Price List"))
- bom_args = frappe._dict({
- "doctype": "BOM",
- "price_list": bom_doc.buying_price_list,
- "qty": args.get("qty") or 1,
- "uom": args.get("uom") or args.get("stock_uom"),
- "stock_uom": args.get("stock_uom"),
- "transaction_type": "buying",
- "company": bom_doc.company,
- "currency": bom_doc.currency,
- "conversion_rate": 1, # Passed conversion rate as 1 purposefully, as conversion rate is applied at the end of the function
- "conversion_factor": args.get("conversion_factor") or 1,
- "plc_conversion_rate": 1,
- "ignore_party": True,
- "ignore_conversion_rate": True
- })
+ bom_args = frappe._dict(
+ {
+ "doctype": "BOM",
+ "price_list": bom_doc.buying_price_list,
+ "qty": args.get("qty") or 1,
+ "uom": args.get("uom") or args.get("stock_uom"),
+ "stock_uom": args.get("stock_uom"),
+ "transaction_type": "buying",
+ "company": bom_doc.company,
+ "currency": bom_doc.currency,
+ "conversion_rate": 1, # Passed conversion rate as 1 purposefully, as conversion rate is applied at the end of the function
+ "conversion_factor": args.get("conversion_factor") or 1,
+ "plc_conversion_rate": 1,
+ "ignore_party": True,
+ "ignore_conversion_rate": True,
+ }
+ )
item_doc = frappe.get_cached_doc("Item", args.get("item_code"))
price_list_data = get_price_list_rate(bom_args, item_doc)
rate = price_list_data.price_list_rate
return flt(rate)
+
def get_valuation_rate(args):
- """ Get weighted average of valuation rate from all warehouses """
+ """Get weighted average of valuation rate from all warehouses"""
total_qty, total_value, valuation_rate = 0.0, 0.0, 0.0
- item_bins = frappe.db.sql("""
+ item_bins = frappe.db.sql(
+ """
select
bin.actual_qty, bin.stock_value
from
@@ -820,33 +930,48 @@
bin.item_code=%(item)s
and bin.warehouse = warehouse.name
and warehouse.company=%(company)s""",
- {"item": args['item_code'], "company": args['company']}, as_dict=1)
+ {"item": args["item_code"], "company": args["company"]},
+ as_dict=1,
+ )
for d in item_bins:
total_qty += flt(d.actual_qty)
total_value += flt(d.stock_value)
if total_qty:
- valuation_rate = total_value / total_qty
+ valuation_rate = total_value / total_qty
if valuation_rate <= 0:
- last_valuation_rate = frappe.db.sql("""select valuation_rate
+ last_valuation_rate = frappe.db.sql(
+ """select valuation_rate
from `tabStock Ledger Entry`
where item_code = %s and valuation_rate > 0 and is_cancelled = 0
- order by posting_date desc, posting_time desc, creation desc limit 1""", args['item_code'])
+ order by posting_date desc, posting_time desc, creation desc limit 1""",
+ args["item_code"],
+ )
valuation_rate = flt(last_valuation_rate[0][0]) if last_valuation_rate else 0
if not valuation_rate:
- valuation_rate = frappe.db.get_value("Item", args['item_code'], "valuation_rate")
+ valuation_rate = frappe.db.get_value("Item", args["item_code"], "valuation_rate")
return flt(valuation_rate)
+
def get_list_context(context):
context.title = _("Bill of Materials")
# context.introduction = _('Boms')
-def get_bom_items_as_dict(bom, company, qty=1, fetch_exploded=1, fetch_scrap_items=0, include_non_stock_items=False, fetch_qty_in_stock_uom=True):
+
+def get_bom_items_as_dict(
+ bom,
+ company,
+ qty=1,
+ fetch_exploded=1,
+ fetch_scrap_items=0,
+ include_non_stock_items=False,
+ fetch_qty_in_stock_uom=True,
+):
item_dict = {}
# Did not use qty_consumed_per_unit in the query, as it leads to rounding loss
@@ -883,30 +1008,40 @@
is_stock_item = 0 if include_non_stock_items else 1
if cint(fetch_exploded):
- query = query.format(table="BOM Explosion Item",
+ query = query.format(
+ table="BOM Explosion Item",
where_conditions="",
is_stock_item=is_stock_item,
qty_field="stock_qty",
- select_columns = """, bom_item.source_warehouse, bom_item.operation,
+ select_columns=""", bom_item.source_warehouse, bom_item.operation,
bom_item.include_item_in_manufacturing, bom_item.description, bom_item.rate, bom_item.sourced_by_supplier,
- (Select idx from `tabBOM Item` where item_code = bom_item.item_code and parent = %(parent)s limit 1) as idx""")
-
- items = frappe.db.sql(query, { "parent": bom, "qty": qty, "bom": bom, "company": company }, as_dict=True)
- elif fetch_scrap_items:
- query = query.format(
- table="BOM Scrap Item", where_conditions="",
- select_columns=", bom_item.idx, item.description, is_process_loss",
- is_stock_item=is_stock_item, qty_field="stock_qty"
+ (Select idx from `tabBOM Item` where item_code = bom_item.item_code and parent = %(parent)s limit 1) as idx""",
)
- items = frappe.db.sql(query, { "qty": qty, "bom": bom, "company": company }, as_dict=True)
+ items = frappe.db.sql(
+ query, {"parent": bom, "qty": qty, "bom": bom, "company": company}, as_dict=True
+ )
+ elif fetch_scrap_items:
+ query = query.format(
+ table="BOM Scrap Item",
+ where_conditions="",
+ select_columns=", bom_item.idx, item.description, is_process_loss",
+ is_stock_item=is_stock_item,
+ qty_field="stock_qty",
+ )
+
+ items = frappe.db.sql(query, {"qty": qty, "bom": bom, "company": company}, as_dict=True)
else:
- query = query.format(table="BOM Item", where_conditions="", is_stock_item=is_stock_item,
+ query = query.format(
+ table="BOM Item",
+ where_conditions="",
+ is_stock_item=is_stock_item,
qty_field="stock_qty" if fetch_qty_in_stock_uom else "qty",
- select_columns = """, bom_item.uom, bom_item.conversion_factor, bom_item.source_warehouse,
+ select_columns=""", bom_item.uom, bom_item.conversion_factor, bom_item.source_warehouse,
bom_item.idx, bom_item.operation, bom_item.include_item_in_manufacturing, bom_item.sourced_by_supplier,
- bom_item.description, bom_item.base_rate as rate """)
- items = frappe.db.sql(query, { "qty": qty, "bom": bom, "company": company }, as_dict=True)
+ bom_item.description, bom_item.base_rate as rate """,
+ )
+ items = frappe.db.sql(query, {"qty": qty, "bom": bom, "company": company}, as_dict=True)
for item in items:
if item.item_code in item_dict:
@@ -915,21 +1050,28 @@
item_dict[item.item_code] = item
for item, item_details in item_dict.items():
- for d in [["Account", "expense_account", "stock_adjustment_account"],
- ["Cost Center", "cost_center", "cost_center"], ["Warehouse", "default_warehouse", ""]]:
- company_in_record = frappe.db.get_value(d[0], item_details.get(d[1]), "company")
- if not item_details.get(d[1]) or (company_in_record and company != company_in_record):
- item_dict[item][d[1]] = frappe.get_cached_value('Company', company, d[2]) if d[2] else None
+ for d in [
+ ["Account", "expense_account", "stock_adjustment_account"],
+ ["Cost Center", "cost_center", "cost_center"],
+ ["Warehouse", "default_warehouse", ""],
+ ]:
+ company_in_record = frappe.db.get_value(d[0], item_details.get(d[1]), "company")
+ if not item_details.get(d[1]) or (company_in_record and company != company_in_record):
+ item_dict[item][d[1]] = frappe.get_cached_value("Company", company, d[2]) if d[2] else None
return item_dict
+
@frappe.whitelist()
def get_bom_items(bom, company, qty=1, fetch_exploded=1):
- items = get_bom_items_as_dict(bom, company, qty, fetch_exploded, include_non_stock_items=True).values()
+ items = get_bom_items_as_dict(
+ bom, company, qty, fetch_exploded, include_non_stock_items=True
+ ).values()
items = list(items)
- items.sort(key = functools.cmp_to_key(lambda a, b: a.item_code > b.item_code and 1 or -1))
+ items.sort(key=functools.cmp_to_key(lambda a, b: a.item_code > b.item_code and 1 or -1))
return items
+
def validate_bom_no(item, bom_no):
"""Validate BOM No of sub-contracted items"""
bom = frappe.get_doc("BOM", bom_no)
@@ -941,21 +1083,24 @@
if item:
rm_item_exists = False
for d in bom.items:
- if (d.item_code.lower() == item.lower()):
+ if d.item_code.lower() == item.lower():
rm_item_exists = True
for d in bom.scrap_items:
- if (d.item_code.lower() == item.lower()):
+ if d.item_code.lower() == item.lower():
rm_item_exists = True
- if bom.item.lower() == item.lower() or \
- bom.item.lower() == cstr(frappe.db.get_value("Item", item, "variant_of")).lower():
- rm_item_exists = True
+ if (
+ bom.item.lower() == item.lower()
+ or bom.item.lower() == cstr(frappe.db.get_value("Item", item, "variant_of")).lower()
+ ):
+ rm_item_exists = True
if not rm_item_exists:
frappe.throw(_("BOM {0} does not belong to Item {1}").format(bom_no, item))
+
@frappe.whitelist()
def get_children(parent=None, is_root=False, **filters):
- if not parent or parent=="BOM":
- frappe.msgprint(_('Please select a BOM'))
+ if not parent or parent == "BOM":
+ frappe.msgprint(_("Please select a BOM"))
return
if parent:
@@ -965,38 +1110,45 @@
bom_doc = frappe.get_cached_doc("BOM", frappe.form_dict.parent)
frappe.has_permission("BOM", doc=bom_doc, throw=True)
- bom_items = frappe.get_all('BOM Item',
- fields=['item_code', 'bom_no as value', 'stock_qty'],
- filters=[['parent', '=', frappe.form_dict.parent]],
- order_by='idx')
+ bom_items = frappe.get_all(
+ "BOM Item",
+ fields=["item_code", "bom_no as value", "stock_qty"],
+ filters=[["parent", "=", frappe.form_dict.parent]],
+ order_by="idx",
+ )
- item_names = tuple(d.get('item_code') for d in bom_items)
+ item_names = tuple(d.get("item_code") for d in bom_items)
- items = frappe.get_list('Item',
- fields=['image', 'description', 'name', 'stock_uom', 'item_name', 'is_sub_contracted_item'],
- filters=[['name', 'in', item_names]]) # to get only required item dicts
+ items = frappe.get_list(
+ "Item",
+ fields=["image", "description", "name", "stock_uom", "item_name", "is_sub_contracted_item"],
+ filters=[["name", "in", item_names]],
+ ) # to get only required item dicts
for bom_item in bom_items:
# extend bom_item dict with respective item dict
bom_item.update(
# returns an item dict from items list which matches with item_code
- next(item for item in items if item.get('name')
- == bom_item.get('item_code'))
+ next(item for item in items if item.get("name") == bom_item.get("item_code"))
)
bom_item.parent_bom_qty = bom_doc.quantity
- bom_item.expandable = 0 if bom_item.value in ('', None) else 1
+ bom_item.expandable = 0 if bom_item.value in ("", None) else 1
bom_item.image = frappe.db.escape(bom_item.image)
return bom_items
+
def get_boms_in_bottom_up_order(bom_no=None):
def _get_parent(bom_no):
- return frappe.db.sql_list("""
+ return frappe.db.sql_list(
+ """
select distinct bom_item.parent from `tabBOM Item` bom_item
where bom_item.bom_no = %s and bom_item.docstatus=1 and bom_item.parenttype='BOM'
and exists(select bom.name from `tabBOM` bom where bom.name=bom_item.parent and bom.is_active=1)
- """, bom_no)
+ """,
+ bom_no,
+ )
count = 0
bom_list = []
@@ -1004,12 +1156,14 @@
bom_list.append(bom_no)
else:
# get all leaf BOMs
- bom_list = frappe.db.sql_list("""select name from `tabBOM` bom
+ bom_list = frappe.db.sql_list(
+ """select name from `tabBOM` bom
where docstatus=1 and is_active=1
and not exists(select bom_no from `tabBOM Item`
- where parent=bom.name and ifnull(bom_no, '')!='')""")
+ where parent=bom.name and ifnull(bom_no, '')!='')"""
+ )
- while(count < len(bom_list)):
+ while count < len(bom_list):
for child_bom in _get_parent(bom_list[count]):
if child_bom not in bom_list:
bom_list.append(child_bom)
@@ -1017,69 +1171,92 @@
return bom_list
+
def add_additional_cost(stock_entry, work_order):
# Add non stock items cost in the additional cost
stock_entry.additional_costs = []
- expenses_included_in_valuation = frappe.get_cached_value("Company", work_order.company,
- "expenses_included_in_valuation")
+ expenses_included_in_valuation = frappe.get_cached_value(
+ "Company", work_order.company, "expenses_included_in_valuation"
+ )
add_non_stock_items_cost(stock_entry, work_order, expenses_included_in_valuation)
add_operations_cost(stock_entry, work_order, expenses_included_in_valuation)
+
def add_non_stock_items_cost(stock_entry, work_order, expense_account):
- bom = frappe.get_doc('BOM', work_order.bom_no)
- table = 'exploded_items' if work_order.get('use_multi_level_bom') else 'items'
+ bom = frappe.get_doc("BOM", work_order.bom_no)
+ table = "exploded_items" if work_order.get("use_multi_level_bom") else "items"
items = {}
for d in bom.get(table):
items.setdefault(d.item_code, d.amount)
- non_stock_items = frappe.get_all('Item',
- fields="name", filters={'name': ('in', list(items.keys())), 'ifnull(is_stock_item, 0)': 0}, as_list=1)
+ non_stock_items = frappe.get_all(
+ "Item",
+ fields="name",
+ filters={"name": ("in", list(items.keys())), "ifnull(is_stock_item, 0)": 0},
+ as_list=1,
+ )
non_stock_items_cost = 0.0
for name in non_stock_items:
- non_stock_items_cost += flt(items.get(name[0])) * flt(stock_entry.fg_completed_qty) / flt(bom.quantity)
+ non_stock_items_cost += (
+ flt(items.get(name[0])) * flt(stock_entry.fg_completed_qty) / flt(bom.quantity)
+ )
if non_stock_items_cost:
- stock_entry.append('additional_costs', {
- 'expense_account': expense_account,
- 'description': _("Non stock items"),
- 'amount': non_stock_items_cost
- })
+ stock_entry.append(
+ "additional_costs",
+ {
+ "expense_account": expense_account,
+ "description": _("Non stock items"),
+ "amount": non_stock_items_cost,
+ },
+ )
+
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)
- })
+ 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)
+ additional_operating_cost_per_unit = flt(work_order.additional_operating_cost) / flt(
+ work_order.qty
+ )
if additional_operating_cost_per_unit:
- stock_entry.append('additional_costs', {
- "expense_account": expense_account,
- "description": "Additional Operating Cost",
- "amount": additional_operating_cost_per_unit * flt(stock_entry.fg_completed_qty)
- })
+ stock_entry.append(
+ "additional_costs",
+ {
+ "expense_account": expense_account,
+ "description": "Additional Operating Cost",
+ "amount": additional_operating_cost_per_unit * flt(stock_entry.fg_completed_qty),
+ },
+ )
+
@frappe.whitelist()
def get_bom_diff(bom1, bom2):
from frappe.model import table_fields
if bom1 == bom2:
- frappe.throw(_("BOM 1 {0} and BOM 2 {1} should not be same")
- .format(frappe.bold(bom1), frappe.bold(bom2)))
+ frappe.throw(
+ _("BOM 1 {0} and BOM 2 {1} should not be same").format(frappe.bold(bom1), frappe.bold(bom2))
+ )
- doc1 = frappe.get_doc('BOM', bom1)
- doc2 = frappe.get_doc('BOM', bom2)
+ doc1 = frappe.get_doc("BOM", bom1)
+ doc2 = frappe.get_doc("BOM", bom2)
out = get_diff(doc1, doc2)
out.row_changed = []
@@ -1089,10 +1266,10 @@
meta = doc1.meta
identifiers = {
- 'operations': 'operation',
- 'items': 'item_code',
- 'scrap_items': 'item_code',
- 'exploded_items': 'item_code'
+ "operations": "operation",
+ "items": "item_code",
+ "scrap_items": "item_code",
+ "exploded_items": "item_code",
}
for df in meta.fields:
@@ -1123,6 +1300,7 @@
return out
+
@frappe.whitelist()
@frappe.validate_and_sanitize_search_inputs
def item_query(doctype, txt, searchfield, start, page_len, filters):
@@ -1132,25 +1310,28 @@
order_by = "idx desc, name, item_name"
fields = ["name", "item_group", "item_name", "description"]
- fields.extend([field for field in searchfields
- if not field in ["name", "item_group", "description"]])
+ fields.extend(
+ [field for field in searchfields if not field in ["name", "item_group", "description"]]
+ )
- searchfields = searchfields + [field for field in [searchfield or "name", "item_code", "item_group", "item_name"]
- if not field in searchfields]
+ searchfields = searchfields + [
+ field
+ for field in [searchfield or "name", "item_code", "item_group", "item_name"]
+ if not field in searchfields
+ ]
- query_filters = {
- "disabled": 0,
- "ifnull(end_of_life, '5050-50-50')": (">", today())
- }
+ query_filters = {"disabled": 0, "ifnull(end_of_life, '5050-50-50')": (">", today())}
or_cond_filters = {}
if txt:
for s_field in searchfields:
or_cond_filters[s_field] = ("like", "%{0}%".format(txt))
- barcodes = frappe.get_all("Item Barcode",
+ barcodes = frappe.get_all(
+ "Item Barcode",
fields=["distinct parent as item_code"],
- filters = {"barcode": ("like", "%{0}%".format(txt))})
+ filters={"barcode": ("like", "%{0}%".format(txt))},
+ )
barcodes = [d.item_code for d in barcodes]
if barcodes:
@@ -1164,10 +1345,17 @@
if filters and filters.get("is_stock_item"):
query_filters["is_stock_item"] = 1
- return frappe.get_list("Item",
- fields = fields, filters=query_filters,
- or_filters = or_cond_filters, order_by=order_by,
- limit_start=start, limit_page_length=page_len, as_list=1)
+ return frappe.get_list(
+ "Item",
+ fields=fields,
+ filters=query_filters,
+ or_filters=or_cond_filters,
+ order_by=order_by,
+ limit_start=start,
+ limit_page_length=page_len,
+ as_list=1,
+ )
+
@frappe.whitelist()
def make_variant_bom(source_name, bom_no, item, variant_items, target_doc=None):
@@ -1178,28 +1366,31 @@
doc.quantity = 1
item_data = get_item_details(item)
- doc.update({
- "item_name": item_data.item_name,
- "description": item_data.description,
- "uom": item_data.stock_uom,
- "allow_alternative_item": item_data.allow_alternative_item
- })
+ doc.update(
+ {
+ "item_name": item_data.item_name,
+ "description": item_data.description,
+ "uom": item_data.stock_uom,
+ "allow_alternative_item": item_data.allow_alternative_item,
+ }
+ )
add_variant_item(variant_items, doc, source_name)
- doc = get_mapped_doc('BOM', source_name, {
- 'BOM': {
- 'doctype': 'BOM',
- 'validation': {
- 'docstatus': ['=', 1]
- }
+ doc = get_mapped_doc(
+ "BOM",
+ source_name,
+ {
+ "BOM": {"doctype": "BOM", "validation": {"docstatus": ["=", 1]}},
+ "BOM Item": {
+ "doctype": "BOM Item",
+ # stop get_mapped_doc copying parent bom_no to children
+ "field_no_map": ["bom_no"],
+ "condition": lambda doc: doc.has_variants == 0,
+ },
},
- 'BOM Item': {
- 'doctype': 'BOM Item',
- # stop get_mapped_doc copying parent bom_no to children
- 'field_no_map': ['bom_no'],
- 'condition': lambda doc: doc.has_variants == 0
- },
- }, target_doc, postprocess)
+ target_doc,
+ postprocess,
+ )
return doc
diff --git a/erpnext/manufacturing/doctype/bom/bom_dashboard.py b/erpnext/manufacturing/doctype/bom/bom_dashboard.py
index 0699f74..d8a810b 100644
--- a/erpnext/manufacturing/doctype/bom/bom_dashboard.py
+++ b/erpnext/manufacturing/doctype/bom/bom_dashboard.py
@@ -3,27 +3,28 @@
def get_data():
return {
- 'fieldname': 'bom_no',
- 'non_standard_fieldnames': {
- 'Item': 'default_bom',
- 'Purchase Order': 'bom',
- 'Purchase Receipt': 'bom',
- 'Purchase Invoice': 'bom'
+ "fieldname": "bom_no",
+ "non_standard_fieldnames": {
+ "Item": "default_bom",
+ "Purchase Order": "bom",
+ "Purchase Receipt": "bom",
+ "Purchase Invoice": "bom",
},
- 'transactions': [
+ "transactions": [
+ {"label": _("Stock"), "items": ["Item", "Stock Entry", "Quality Inspection"]},
+ {"label": _("Manufacture"), "items": ["BOM", "Work Order", "Job Card"]},
{
- 'label': _('Stock'),
- 'items': ['Item', 'Stock Entry', 'Quality Inspection']
+ "label": _("Subcontract"),
+ "items": ["Purchase Order", "Purchase Receipt", "Purchase Invoice"],
},
- {
- 'label': _('Manufacture'),
- 'items': ['BOM', 'Work Order', 'Job Card']
- },
- {
- 'label': _('Subcontract'),
- 'items': ['Purchase Order', 'Purchase Receipt', 'Purchase Invoice']
- }
],
- 'disable_create_buttons': ["Item", "Purchase Order", "Purchase Receipt",
- "Purchase Invoice", "Job Card", "Stock Entry", "BOM"]
+ "disable_create_buttons": [
+ "Item",
+ "Purchase Order",
+ "Purchase Receipt",
+ "Purchase Invoice",
+ "Job Card",
+ "Stock Entry",
+ "BOM",
+ ],
}
diff --git a/erpnext/manufacturing/doctype/bom/test_bom.py b/erpnext/manufacturing/doctype/bom/test_bom.py
index 21e0006..524f45b 100644
--- a/erpnext/manufacturing/doctype/bom/test_bom.py
+++ b/erpnext/manufacturing/doctype/bom/test_bom.py
@@ -18,22 +18,27 @@
)
from erpnext.tests.test_subcontracting import set_backflush_based_on
-test_records = frappe.get_test_records('BOM')
+test_records = frappe.get_test_records("BOM")
test_dependencies = ["Item", "Quality Inspection Template"]
+
class TestBOM(FrappeTestCase):
def test_get_items(self):
from erpnext.manufacturing.doctype.bom.bom import get_bom_items_as_dict
- items_dict = get_bom_items_as_dict(bom=get_default_bom(),
- company="_Test Company", qty=1, fetch_exploded=0)
+
+ items_dict = get_bom_items_as_dict(
+ bom=get_default_bom(), company="_Test Company", qty=1, fetch_exploded=0
+ )
self.assertTrue(test_records[2]["items"][0]["item_code"] in items_dict)
self.assertTrue(test_records[2]["items"][1]["item_code"] in items_dict)
self.assertEqual(len(items_dict.values()), 2)
def test_get_items_exploded(self):
from erpnext.manufacturing.doctype.bom.bom import get_bom_items_as_dict
- items_dict = get_bom_items_as_dict(bom=get_default_bom(),
- company="_Test Company", qty=1, fetch_exploded=1)
+
+ items_dict = get_bom_items_as_dict(
+ bom=get_default_bom(), company="_Test Company", qty=1, fetch_exploded=1
+ )
self.assertTrue(test_records[2]["items"][0]["item_code"] in items_dict)
self.assertFalse(test_records[2]["items"][1]["item_code"] in items_dict)
self.assertTrue(test_records[0]["items"][0]["item_code"] in items_dict)
@@ -42,13 +47,14 @@
def test_get_items_list(self):
from erpnext.manufacturing.doctype.bom.bom import get_bom_items
+
self.assertEqual(len(get_bom_items(bom=get_default_bom(), company="_Test Company")), 3)
def test_default_bom(self):
def _get_default_bom_in_item():
return cstr(frappe.db.get_value("Item", "_Test FG Item 2", "default_bom"))
- bom = frappe.get_doc("BOM", {"item":"_Test FG Item 2", "is_default": 1})
+ bom = frappe.get_doc("BOM", {"item": "_Test FG Item 2", "is_default": 1})
self.assertEqual(_get_default_bom_in_item(), bom.name)
bom.is_active = 0
@@ -56,28 +62,33 @@
self.assertEqual(_get_default_bom_in_item(), "")
bom.is_active = 1
- bom.is_default=1
+ bom.is_default = 1
bom.save()
self.assertTrue(_get_default_bom_in_item(), bom.name)
def test_update_bom_cost_in_all_boms(self):
# get current rate for '_Test Item 2'
- rm_rate = frappe.db.sql("""select rate from `tabBOM Item`
+ rm_rate = frappe.db.sql(
+ """select rate from `tabBOM Item`
where parent='BOM-_Test Item Home Desktop Manufactured-001'
- and item_code='_Test Item 2' and docstatus=1 and parenttype='BOM'""")
+ and item_code='_Test Item 2' and docstatus=1 and parenttype='BOM'"""
+ )
rm_rate = rm_rate[0][0] if rm_rate else 0
# Reset item valuation rate
- reset_item_valuation_rate(item_code='_Test Item 2', qty=200, rate=rm_rate + 10)
+ reset_item_valuation_rate(item_code="_Test Item 2", qty=200, rate=rm_rate + 10)
# update cost of all BOMs based on latest valuation rate
update_cost()
# check if new valuation rate updated in all BOMs
- for d in frappe.db.sql("""select rate from `tabBOM Item`
- where item_code='_Test Item 2' and docstatus=1 and parenttype='BOM'""", as_dict=1):
- self.assertEqual(d.rate, rm_rate + 10)
+ for d in frappe.db.sql(
+ """select rate from `tabBOM Item`
+ where item_code='_Test Item 2' and docstatus=1 and parenttype='BOM'""",
+ as_dict=1,
+ ):
+ self.assertEqual(d.rate, rm_rate + 10)
def test_bom_cost(self):
bom = frappe.copy_doc(test_records[2])
@@ -92,7 +103,9 @@
for row in bom.items:
raw_material_cost += row.amount
- base_raw_material_cost = raw_material_cost * flt(bom.conversion_rate, bom.precision("conversion_rate"))
+ base_raw_material_cost = raw_material_cost * flt(
+ bom.conversion_rate, bom.precision("conversion_rate")
+ )
base_op_cost = op_cost * flt(bom.conversion_rate, bom.precision("conversion_rate"))
# test amounts in selected currency, almostEqual checks for 7 digits by default
@@ -120,14 +133,15 @@
for op_row in bom.operations:
self.assertAlmostEqual(op_row.cost_per_unit, op_row.operating_cost / 2)
- self.assertAlmostEqual(bom.operating_cost, op_cost/2)
+ self.assertAlmostEqual(bom.operating_cost, op_cost / 2)
bom.delete()
def test_bom_cost_multi_uom_multi_currency_based_on_price_list(self):
frappe.db.set_value("Price List", "_Test Price List", "price_not_uom_dependent", 1)
for item_code, rate in (("_Test Item", 3600), ("_Test Item Home Desktop Manufactured", 3000)):
- frappe.db.sql("delete from `tabItem Price` where price_list='_Test Price List' and item_code=%s",
- item_code)
+ frappe.db.sql(
+ "delete from `tabItem Price` where price_list='_Test Price List' and item_code=%s", item_code
+ )
item_price = frappe.new_doc("Item Price")
item_price.price_list = "_Test Price List"
item_price.item_code = item_code
@@ -142,7 +156,7 @@
bom.items[0].conversion_factor = 5
bom.insert()
- bom.update_cost(update_hour_rate = False)
+ bom.update_cost(update_hour_rate=False)
# test amounts in selected currency
self.assertEqual(bom.items[0].rate, 300)
@@ -167,11 +181,12 @@
bom.insert()
reset_item_valuation_rate(
- item_code='_Test Item',
- warehouse_list=frappe.get_all("Warehouse",
- {"is_group":0, "company": bom.company}, pluck="name"),
+ item_code="_Test Item",
+ warehouse_list=frappe.get_all(
+ "Warehouse", {"is_group": 0, "company": bom.company}, pluck="name"
+ ),
qty=200,
- rate=200
+ rate=200,
)
bom.update_cost()
@@ -180,68 +195,64 @@
def test_subcontractor_sourced_item(self):
item_code = "_Test Subcontracted FG Item 1"
- set_backflush_based_on('Material Transferred for Subcontract')
+ set_backflush_based_on("Material Transferred for Subcontract")
- if not frappe.db.exists('Item', item_code):
- make_item(item_code, {
- 'is_stock_item': 1,
- 'is_sub_contracted_item': 1,
- 'stock_uom': 'Nos'
- })
+ if not frappe.db.exists("Item", item_code):
+ make_item(item_code, {"is_stock_item": 1, "is_sub_contracted_item": 1, "stock_uom": "Nos"})
- if not frappe.db.exists('Item', "Test Extra Item 1"):
- make_item("Test Extra Item 1", {
- 'is_stock_item': 1,
- 'stock_uom': 'Nos'
- })
+ if not frappe.db.exists("Item", "Test Extra Item 1"):
+ make_item("Test Extra Item 1", {"is_stock_item": 1, "stock_uom": "Nos"})
- if not frappe.db.exists('Item', "Test Extra Item 2"):
- make_item("Test Extra Item 2", {
- 'is_stock_item': 1,
- 'stock_uom': 'Nos'
- })
+ if not frappe.db.exists("Item", "Test Extra Item 2"):
+ make_item("Test Extra Item 2", {"is_stock_item": 1, "stock_uom": "Nos"})
- if not frappe.db.exists('Item', "Test Extra Item 3"):
- make_item("Test Extra Item 3", {
- 'is_stock_item': 1,
- 'stock_uom': 'Nos'
- })
- bom = frappe.get_doc({
- 'doctype': 'BOM',
- 'is_default': 1,
- 'item': item_code,
- 'currency': 'USD',
- 'quantity': 1,
- 'company': '_Test Company'
- })
+ if not frappe.db.exists("Item", "Test Extra Item 3"):
+ make_item("Test Extra Item 3", {"is_stock_item": 1, "stock_uom": "Nos"})
+ bom = frappe.get_doc(
+ {
+ "doctype": "BOM",
+ "is_default": 1,
+ "item": item_code,
+ "currency": "USD",
+ "quantity": 1,
+ "company": "_Test Company",
+ }
+ )
for item in ["Test Extra Item 1", "Test Extra Item 2"]:
- item_doc = frappe.get_doc('Item', item)
+ item_doc = frappe.get_doc("Item", item)
- bom.append('items', {
- 'item_code': item,
- 'qty': 1,
- 'uom': item_doc.stock_uom,
- 'stock_uom': item_doc.stock_uom,
- 'rate': item_doc.valuation_rate
- })
+ bom.append(
+ "items",
+ {
+ "item_code": item,
+ "qty": 1,
+ "uom": item_doc.stock_uom,
+ "stock_uom": item_doc.stock_uom,
+ "rate": item_doc.valuation_rate,
+ },
+ )
- bom.append('items', {
- 'item_code': "Test Extra Item 3",
- 'qty': 1,
- 'uom': item_doc.stock_uom,
- 'stock_uom': item_doc.stock_uom,
- 'rate': 0,
- 'sourced_by_supplier': 1
- })
+ bom.append(
+ "items",
+ {
+ "item_code": "Test Extra Item 3",
+ "qty": 1,
+ "uom": item_doc.stock_uom,
+ "stock_uom": item_doc.stock_uom,
+ "rate": 0,
+ "sourced_by_supplier": 1,
+ },
+ )
bom.insert(ignore_permissions=True)
bom.update_cost()
bom.submit()
# test that sourced_by_supplier rate is zero even after updating cost
self.assertEqual(bom.items[2].rate, 0)
# test in Purchase Order sourced_by_supplier is not added to Supplied Item
- po = create_purchase_order(item_code=item_code, qty=1,
- is_subcontracted="Yes", supplier_warehouse="_Test Warehouse 1 - _TC")
+ po = create_purchase_order(
+ item_code=item_code, qty=1, is_subcontracted="Yes", supplier_warehouse="_Test Warehouse 1 - _TC"
+ )
bom_items = sorted([d.item_code for d in bom.items if d.sourced_by_supplier != 1])
supplied_items = sorted([d.rm_item_code for d in po.supplied_items])
self.assertEqual(bom_items, supplied_items)
@@ -249,7 +260,10 @@
def test_bom_tree_representation(self):
bom_tree = {
"Assembly": {
- "SubAssembly1": {"ChildPart1": {}, "ChildPart2": {},},
+ "SubAssembly1": {
+ "ChildPart1": {},
+ "ChildPart2": {},
+ },
"SubAssembly2": {"ChildPart3": {}},
"SubAssembly3": {"SubSubAssy1": {"ChildPart4": {}}},
"ChildPart5": {},
@@ -260,7 +274,7 @@
parent_bom = create_nested_bom(bom_tree, prefix="")
created_tree = parent_bom.get_tree_representation()
- reqd_order = level_order_traversal(bom_tree)[1:] # skip first item
+ reqd_order = level_order_traversal(bom_tree)[1:] # skip first item
created_order = created_tree.level_order_traversal()
self.assertEqual(len(reqd_order), len(created_order))
@@ -272,14 +286,23 @@
from erpnext.controllers.item_variant import create_variant
template_item = make_item(
- "_TestTemplateItem", {"has_variants": 1, "attributes": [{"attribute": "Test Size"},]}
+ "_TestTemplateItem",
+ {
+ "has_variants": 1,
+ "attributes": [
+ {"attribute": "Test Size"},
+ ],
+ },
)
variant = create_variant(template_item.item_code, {"Test Size": "Large"})
variant.insert(ignore_if_duplicate=True)
bom_tree = {
template_item.item_code: {
- "SubAssembly1": {"ChildPart1": {}, "ChildPart2": {},},
+ "SubAssembly1": {
+ "ChildPart1": {},
+ "ChildPart2": {},
+ },
"ChildPart5": {},
}
}
@@ -302,7 +325,7 @@
def test_bom_recursion_1st_level(self):
"""BOM should not allow BOM item again in child"""
item_code = "_Test BOM Recursion"
- make_item(item_code, {'is_stock_item': 1})
+ make_item(item_code, {"is_stock_item": 1})
bom = frappe.new_doc("BOM")
bom.item = item_code
@@ -316,8 +339,8 @@
def test_bom_recursion_transitive(self):
item1 = "_Test BOM Recursion"
item2 = "_Test BOM Recursion 2"
- make_item(item1, {'is_stock_item': 1})
- make_item(item2, {'is_stock_item': 1})
+ make_item(item1, {"is_stock_item": 1})
+ make_item(item2, {"is_stock_item": 1})
bom1 = frappe.new_doc("BOM")
bom1.item = item1
@@ -373,19 +396,29 @@
self.assertRaises(frappe.ValidationError, bom_doc.submit)
def test_bom_item_query(self):
- query = partial(item_query, doctype="Item", txt="", searchfield="name", start=0, page_len=20, filters={"is_stock_item": 1})
+ query = partial(
+ item_query,
+ doctype="Item",
+ txt="",
+ searchfield="name",
+ start=0,
+ page_len=20,
+ filters={"is_stock_item": 1},
+ )
test_items = query(txt="_Test")
filtered = query(txt="_Test Item 2")
- self.assertNotEqual(len(test_items), len(filtered), msg="Item filtering showing excessive results")
+ self.assertNotEqual(
+ len(test_items), len(filtered), msg="Item filtering showing excessive results"
+ )
self.assertTrue(0 < len(filtered) <= 3, msg="Item filtering showing excessive results")
def test_exclude_exploded_items_from_bom(self):
bom_no = get_default_bom()
- new_bom = frappe.copy_doc(frappe.get_doc('BOM', bom_no))
+ new_bom = frappe.copy_doc(frappe.get_doc("BOM", bom_no))
for row in new_bom.items:
- if row.item_code == '_Test Item Home Desktop Manufactured':
+ if row.item_code == "_Test Item Home Desktop Manufactured":
self.assertTrue(row.bom_no)
row.do_not_explode = True
@@ -394,13 +427,15 @@
new_bom.load_from_db()
for row in new_bom.items:
- if row.item_code == '_Test Item Home Desktop Manufactured' and row.do_not_explode:
+ if row.item_code == "_Test Item Home Desktop Manufactured" and row.do_not_explode:
self.assertFalse(row.bom_no)
new_bom.delete()
def test_valid_transfer_defaults(self):
- bom_with_op = frappe.db.get_value("BOM", {"item": "_Test FG Item 2", "with_operations": 1, "is_active": 1})
+ bom_with_op = frappe.db.get_value(
+ "BOM", {"item": "_Test FG Item 2", "with_operations": 1, "is_active": 1}
+ )
bom = frappe.copy_doc(frappe.get_doc("BOM", bom_with_op), ignore_no_copy=False)
# test defaults
@@ -429,12 +464,8 @@
bom.delete()
def test_bom_name_length(self):
- """ test >140 char names"""
- bom_tree = {
- "x" * 140 : {
- " ".join(["abc"] * 35): {}
- }
- }
+ """test >140 char names"""
+ bom_tree = {"x" * 140: {" ".join(["abc"] * 35): {}}}
create_nested_bom(bom_tree, prefix="")
def test_version_index(self):
@@ -452,15 +483,14 @@
for expected_index, existing_boms in version_index_test_cases:
with self.subTest():
- self.assertEqual(expected_index, bom.get_next_version_index(existing_boms),
- msg=f"Incorrect index for {existing_boms}")
+ self.assertEqual(
+ expected_index,
+ bom.get_next_version_index(existing_boms),
+ msg=f"Incorrect index for {existing_boms}",
+ )
def test_bom_versioning(self):
- bom_tree = {
- frappe.generate_hash(length=10) : {
- frappe.generate_hash(length=10): {}
- }
- }
+ bom_tree = {frappe.generate_hash(length=10): {frappe.generate_hash(length=10): {}}}
bom = create_nested_bom(bom_tree, prefix="")
self.assertEqual(int(bom.name.split("-")[-1]), 1)
original_bom_name = bom.name
@@ -501,7 +531,7 @@
bom.save()
bom.reload()
- self.assertEqual(bom.quality_inspection_template, '_Test Quality Inspection Template')
+ self.assertEqual(bom.quality_inspection_template, "_Test Quality Inspection Template")
bom.inspection_required = 0
bom.save()
@@ -514,8 +544,7 @@
parent = frappe.generate_hash(length=10)
child = frappe.generate_hash(length=10)
- bom_tree = {parent: {child: {}}
- }
+ bom_tree = {parent: {child: {}}}
bom = create_nested_bom(bom_tree, prefix="")
# add last purchase price
@@ -534,6 +563,7 @@
def get_default_bom(item_code="_Test FG Item 2"):
return frappe.db.get_value("BOM", {"item": item_code, "is_active": 1, "is_default": 1})
+
def level_order_traversal(node):
traversal = []
q = deque()
@@ -548,9 +578,9 @@
return traversal
+
def create_nested_bom(tree, prefix="_Test bom "):
- """ Helper function to create a simple nested bom from tree describing item names. (along with required items)
- """
+ """Helper function to create a simple nested bom from tree describing item names. (along with required items)"""
def create_items(bom_tree):
for item_code, subtree in bom_tree.items():
@@ -558,6 +588,7 @@
if not frappe.db.exists("Item", bom_item_code):
frappe.get_doc(doctype="Item", item_code=bom_item_code, item_group="_Test Item Group").insert()
create_items(subtree)
+
create_items(tree)
def dfs(tree, node):
@@ -592,10 +623,13 @@
warehouse_list = [warehouse_list]
if not warehouse_list:
- warehouse_list = frappe.db.sql_list("""
+ warehouse_list = frappe.db.sql_list(
+ """
select warehouse from `tabBin`
where item_code=%s and actual_qty > 0
- """, item_code)
+ """,
+ item_code,
+ )
if not warehouse_list:
warehouse_list.append("_Test Warehouse - _TC")
@@ -603,44 +637,51 @@
for warehouse in warehouse_list:
create_stock_reconciliation(item_code=item_code, warehouse=warehouse, qty=qty, rate=rate)
+
def create_bom_with_process_loss_item(
- fg_item, bom_item, scrap_qty, scrap_rate, fg_qty=2, is_process_loss=1):
+ fg_item, bom_item, scrap_qty, scrap_rate, fg_qty=2, is_process_loss=1
+):
bom_doc = frappe.new_doc("BOM")
bom_doc.item = fg_item.item_code
bom_doc.quantity = fg_qty
- bom_doc.append("items", {
- "item_code": bom_item.item_code,
- "qty": 1,
- "uom": bom_item.stock_uom,
- "stock_uom": bom_item.stock_uom,
- "rate": 100.0
- })
- bom_doc.append("scrap_items", {
- "item_code": fg_item.item_code,
- "qty": scrap_qty,
- "stock_qty": scrap_qty,
- "uom": fg_item.stock_uom,
- "stock_uom": fg_item.stock_uom,
- "rate": scrap_rate,
- "is_process_loss": is_process_loss
- })
+ bom_doc.append(
+ "items",
+ {
+ "item_code": bom_item.item_code,
+ "qty": 1,
+ "uom": bom_item.stock_uom,
+ "stock_uom": bom_item.stock_uom,
+ "rate": 100.0,
+ },
+ )
+ bom_doc.append(
+ "scrap_items",
+ {
+ "item_code": fg_item.item_code,
+ "qty": scrap_qty,
+ "stock_qty": scrap_qty,
+ "uom": fg_item.stock_uom,
+ "stock_uom": fg_item.stock_uom,
+ "rate": scrap_rate,
+ "is_process_loss": is_process_loss,
+ },
+ )
bom_doc.currency = "INR"
return bom_doc
+
def create_process_loss_bom_items():
item_list = [
("_Test Item - Non Whole UOM", "Kg"),
("_Test Item - Whole UOM", "Unit"),
- ("_Test PL BOM Item", "Unit")
+ ("_Test PL BOM Item", "Unit"),
]
return [create_process_loss_bom_item(it) for it in item_list]
+
def create_process_loss_bom_item(item_tuple):
item_code, stock_uom = item_tuple
if frappe.db.exists("Item", item_code) is None:
- return make_item(
- item_code,
- {'stock_uom':stock_uom, 'valuation_rate':100}
- )
+ return make_item(item_code, {"stock_uom": stock_uom, "valuation_rate": 100})
else:
return frappe.get_doc("Item", item_code)
diff --git a/erpnext/manufacturing/doctype/bom_update_tool/bom_update_tool.py b/erpnext/manufacturing/doctype/bom_update_tool/bom_update_tool.py
index 0e3955f..00711ca 100644
--- a/erpnext/manufacturing/doctype/bom_update_tool/bom_update_tool.py
+++ b/erpnext/manufacturing/doctype/bom_update_tool/bom_update_tool.py
@@ -20,14 +20,14 @@
unit_cost = get_new_bom_unit_cost(self.new_bom)
self.update_new_bom(unit_cost)
- frappe.cache().delete_key('bom_children')
+ frappe.cache().delete_key("bom_children")
bom_list = self.get_parent_boms(self.new_bom)
with click.progressbar(bom_list) as bom_list:
pass
for bom in bom_list:
try:
- bom_obj = frappe.get_cached_doc('BOM', bom)
+ bom_obj = frappe.get_cached_doc("BOM", bom)
# this is only used for versioning and we do not want
# to make separate db calls by using load_doc_before_save
# which proves to be expensive while doing bulk replace
@@ -37,7 +37,7 @@
bom_obj.calculate_cost()
bom_obj.update_parent_cost()
bom_obj.db_update()
- if bom_obj.meta.get('track_changes') and not bom_obj.flags.ignore_version:
+ if bom_obj.meta.get("track_changes") and not bom_obj.flags.ignore_version:
bom_obj.save_version()
except Exception:
frappe.log_error(frappe.get_traceback())
@@ -46,20 +46,26 @@
if cstr(self.current_bom) == cstr(self.new_bom):
frappe.throw(_("Current BOM and New BOM can not be same"))
- if frappe.db.get_value("BOM", self.current_bom, "item") \
- != frappe.db.get_value("BOM", self.new_bom, "item"):
- frappe.throw(_("The selected BOMs are not for the same item"))
+ if frappe.db.get_value("BOM", self.current_bom, "item") != frappe.db.get_value(
+ "BOM", self.new_bom, "item"
+ ):
+ frappe.throw(_("The selected BOMs are not for the same item"))
def update_new_bom(self, unit_cost):
- frappe.db.sql("""update `tabBOM Item` set bom_no=%s,
+ frappe.db.sql(
+ """update `tabBOM Item` set bom_no=%s,
rate=%s, amount=stock_qty*%s where bom_no = %s and docstatus < 2 and parenttype='BOM'""",
- (self.new_bom, unit_cost, unit_cost, self.current_bom))
+ (self.new_bom, unit_cost, unit_cost, self.current_bom),
+ )
def get_parent_boms(self, bom, bom_list=None):
if bom_list is None:
bom_list = []
- data = frappe.db.sql("""SELECT DISTINCT parent FROM `tabBOM Item`
- WHERE bom_no = %s AND docstatus < 2 AND parenttype='BOM'""", bom)
+ data = frappe.db.sql(
+ """SELECT DISTINCT parent FROM `tabBOM Item`
+ WHERE bom_no = %s AND docstatus < 2 AND parenttype='BOM'""",
+ bom,
+ )
for d in data:
if self.new_bom == d[0]:
@@ -70,29 +76,45 @@
return list(set(bom_list))
+
def get_new_bom_unit_cost(bom):
- new_bom_unitcost = frappe.db.sql("""SELECT `total_cost`/`quantity`
- FROM `tabBOM` WHERE name = %s""", bom)
+ new_bom_unitcost = frappe.db.sql(
+ """SELECT `total_cost`/`quantity`
+ FROM `tabBOM` WHERE name = %s""",
+ bom,
+ )
return flt(new_bom_unitcost[0][0]) if new_bom_unitcost else 0
+
@frappe.whitelist()
def enqueue_replace_bom(args):
if isinstance(args, str):
args = json.loads(args)
- frappe.enqueue("erpnext.manufacturing.doctype.bom_update_tool.bom_update_tool.replace_bom", args=args, timeout=40000)
+ frappe.enqueue(
+ "erpnext.manufacturing.doctype.bom_update_tool.bom_update_tool.replace_bom",
+ args=args,
+ timeout=40000,
+ )
frappe.msgprint(_("Queued for replacing the BOM. It may take a few minutes."))
+
@frappe.whitelist()
def enqueue_update_cost():
- frappe.enqueue("erpnext.manufacturing.doctype.bom_update_tool.bom_update_tool.update_cost", timeout=40000)
- frappe.msgprint(_("Queued for updating latest price in all Bill of Materials. It may take a few minutes."))
+ frappe.enqueue(
+ "erpnext.manufacturing.doctype.bom_update_tool.bom_update_tool.update_cost", timeout=40000
+ )
+ frappe.msgprint(
+ _("Queued for updating latest price in all Bill of Materials. It may take a few minutes.")
+ )
+
def update_latest_price_in_all_boms():
if frappe.db.get_single_value("Manufacturing Settings", "update_bom_costs_automatically"):
update_cost()
+
def replace_bom(args):
frappe.db.auto_commit_on_many_writes = 1
args = frappe._dict(args)
@@ -104,6 +126,7 @@
frappe.db.auto_commit_on_many_writes = 0
+
def update_cost():
frappe.db.auto_commit_on_many_writes = 1
bom_list = get_boms_in_bottom_up_order()
diff --git a/erpnext/manufacturing/doctype/bom_update_tool/test_bom_update_tool.py b/erpnext/manufacturing/doctype/bom_update_tool/test_bom_update_tool.py
index b4c625d..57785e5 100644
--- a/erpnext/manufacturing/doctype/bom_update_tool/test_bom_update_tool.py
+++ b/erpnext/manufacturing/doctype/bom_update_tool/test_bom_update_tool.py
@@ -8,7 +8,8 @@
from erpnext.manufacturing.doctype.production_plan.test_production_plan import make_bom
from erpnext.stock.doctype.item.test_item import create_item
-test_records = frappe.get_test_records('BOM')
+test_records = frappe.get_test_records("BOM")
+
class TestBOMUpdateTool(FrappeTestCase):
def test_replace_bom(self):
@@ -37,10 +38,13 @@
if item_doc.valuation_rate != 100.00:
frappe.db.set_value("Item", item_doc.name, "valuation_rate", 100)
- bom_no = frappe.db.get_value('BOM', {'item': 'BOM Cost Test Item 1'}, "name")
+ bom_no = frappe.db.get_value("BOM", {"item": "BOM Cost Test Item 1"}, "name")
if not bom_no:
- doc = make_bom(item = 'BOM Cost Test Item 1',
- raw_materials =['BOM Cost Test Item 2', 'BOM Cost Test Item 3'], currency="INR")
+ doc = make_bom(
+ item="BOM Cost Test Item 1",
+ raw_materials=["BOM Cost Test Item 2", "BOM Cost Test Item 3"],
+ currency="INR",
+ )
else:
doc = frappe.get_doc("BOM", bom_no)
diff --git a/erpnext/manufacturing/doctype/job_card/job_card.py b/erpnext/manufacturing/doctype/job_card/job_card.py
index 960d0e5..bf4f82f 100644
--- a/erpnext/manufacturing/doctype/job_card/job_card.py
+++ b/erpnext/manufacturing/doctype/job_card/job_card.py
@@ -26,15 +26,27 @@
)
-class OverlapError(frappe.ValidationError): pass
+class OverlapError(frappe.ValidationError):
+ pass
-class OperationMismatchError(frappe.ValidationError): pass
-class OperationSequenceError(frappe.ValidationError): pass
-class JobCardCancelError(frappe.ValidationError): pass
+
+class OperationMismatchError(frappe.ValidationError):
+ pass
+
+
+class OperationSequenceError(frappe.ValidationError):
+ pass
+
+
+class JobCardCancelError(frappe.ValidationError):
+ pass
+
class JobCard(Document):
def onload(self):
- excess_transfer = frappe.db.get_single_value("Manufacturing Settings", "job_card_excess_transfer")
+ excess_transfer = frappe.db.get_single_value(
+ "Manufacturing Settings", "job_card_excess_transfer"
+ )
self.set_onload("job_card_excess_transfer", excess_transfer)
self.set_onload("work_order_closed", self.is_work_order_closed())
@@ -50,25 +62,33 @@
def set_sub_operations(self):
if not self.sub_operations and self.operation:
self.sub_operations = []
- for row in frappe.get_all('Sub Operation',
- filters = {'parent': self.operation}, fields=['operation', 'idx'], order_by='idx'):
- row.status = 'Pending'
+ for row in frappe.get_all(
+ "Sub Operation",
+ filters={"parent": self.operation},
+ fields=["operation", "idx"],
+ order_by="idx",
+ ):
+ row.status = "Pending"
row.sub_operation = row.operation
- self.append('sub_operations', row)
+ self.append("sub_operations", row)
def validate_time_logs(self):
self.total_time_in_mins = 0.0
self.total_completed_qty = 0.0
- if self.get('time_logs'):
- for d in self.get('time_logs'):
+ if self.get("time_logs"):
+ for d in self.get("time_logs"):
if d.to_time and get_datetime(d.from_time) > get_datetime(d.to_time):
frappe.throw(_("Row {0}: From time must be less than to time").format(d.idx))
data = self.get_overlap_for(d)
if data:
- frappe.throw(_("Row {0}: From Time and To Time of {1} is overlapping with {2}")
- .format(d.idx, self.name, data.name), OverlapError)
+ frappe.throw(
+ _("Row {0}: From Time and To Time of {1} is overlapping with {2}").format(
+ d.idx, self.name, data.name
+ ),
+ OverlapError,
+ )
if d.from_time and d.to_time:
d.time_in_mins = time_diff_in_hours(d.to_time, d.from_time) * 60
@@ -86,8 +106,9 @@
production_capacity = 1
if self.workstation:
- production_capacity = frappe.get_cached_value("Workstation",
- self.workstation, 'production_capacity') or 1
+ production_capacity = (
+ frappe.get_cached_value("Workstation", self.workstation, "production_capacity") or 1
+ )
validate_overlap_for = " and jc.workstation = %(workstation)s "
if args.get("employee"):
@@ -95,11 +116,12 @@
production_capacity = 1
validate_overlap_for = " and jctl.employee = %(employee)s "
- extra_cond = ''
+ extra_cond = ""
if check_next_available_slot:
extra_cond = " or (%(from_time)s <= jctl.from_time and %(to_time)s <= jctl.to_time)"
- existing = frappe.db.sql("""select jc.name as name, jctl.to_time from
+ existing = frappe.db.sql(
+ """select jc.name as name, jctl.to_time from
`tabJob Card Time Log` jctl, `tabJob Card` jc where jctl.parent = jc.name and
(
(%(from_time)s > jctl.from_time and %(from_time)s < jctl.to_time) or
@@ -107,15 +129,19 @@
(%(from_time)s <= jctl.from_time and %(to_time)s >= jctl.to_time) {0}
)
and jctl.name != %(name)s and jc.name != %(parent)s and jc.docstatus < 2 {1}
- order by jctl.to_time desc limit 1""".format(extra_cond, validate_overlap_for),
+ order by jctl.to_time desc limit 1""".format(
+ extra_cond, validate_overlap_for
+ ),
{
"from_time": args.from_time,
"to_time": args.to_time,
"name": args.name or "No Name",
"parent": args.parent or "No Name",
"employee": args.get("employee"),
- "workstation": self.workstation
- }, as_dict=True)
+ "workstation": self.workstation,
+ },
+ as_dict=True,
+ )
if existing and production_capacity > len(existing):
return
@@ -125,10 +151,7 @@
def schedule_time_logs(self, row):
row.remaining_time_in_mins = row.time_in_mins
while row.remaining_time_in_mins > 0:
- args = frappe._dict({
- "from_time": row.planned_start_time,
- "to_time": row.planned_end_time
- })
+ args = frappe._dict({"from_time": row.planned_start_time, "to_time": row.planned_end_time})
self.validate_overlap_for_workstation(args, row)
self.check_workstation_time(row)
@@ -141,13 +164,16 @@
def check_workstation_time(self, row):
workstation_doc = frappe.get_cached_doc("Workstation", self.workstation)
- if (not workstation_doc.working_hours or
- cint(frappe.db.get_single_value("Manufacturing Settings", "allow_overtime"))):
+ if not workstation_doc.working_hours or cint(
+ frappe.db.get_single_value("Manufacturing Settings", "allow_overtime")
+ ):
if get_datetime(row.planned_end_time) < get_datetime(row.planned_start_time):
row.planned_end_time = add_to_date(row.planned_start_time, minutes=row.time_in_mins)
row.remaining_time_in_mins = 0.0
else:
- row.remaining_time_in_mins -= time_diff_in_minutes(row.planned_end_time, row.planned_start_time)
+ row.remaining_time_in_mins -= time_diff_in_minutes(
+ row.planned_end_time, row.planned_start_time
+ )
self.update_time_logs(row)
return
@@ -167,14 +193,15 @@
workstation_start_time = datetime.datetime.combine(start_date, get_time(time_slot.start_time))
workstation_end_time = datetime.datetime.combine(start_date, get_time(time_slot.end_time))
- if (get_datetime(row.planned_start_time) >= workstation_start_time and
- get_datetime(row.planned_start_time) <= workstation_end_time):
+ if (
+ get_datetime(row.planned_start_time) >= workstation_start_time
+ and get_datetime(row.planned_start_time) <= workstation_end_time
+ ):
time_in_mins = time_diff_in_minutes(workstation_end_time, row.planned_start_time)
# If remaining time fit in workstation time logs else split hours as per workstation time
if time_in_mins > row.remaining_time_in_mins:
- row.planned_end_time = add_to_date(row.planned_start_time,
- minutes=row.remaining_time_in_mins)
+ row.planned_end_time = add_to_date(row.planned_start_time, minutes=row.remaining_time_in_mins)
row.remaining_time_in_mins = 0
else:
row.planned_end_time = add_to_date(row.planned_start_time, minutes=time_in_mins)
@@ -182,14 +209,16 @@
self.update_time_logs(row)
- if total_idx != (i+1) and row.remaining_time_in_mins > 0:
- row.planned_start_time = datetime.datetime.combine(start_date,
- get_time(workstation_doc.working_hours[i+1].start_time))
+ if total_idx != (i + 1) and row.remaining_time_in_mins > 0:
+ row.planned_start_time = datetime.datetime.combine(
+ start_date, get_time(workstation_doc.working_hours[i + 1].start_time)
+ )
if row.remaining_time_in_mins > 0:
start_date = add_days(start_date, 1)
- row.planned_start_time = datetime.datetime.combine(start_date,
- get_time(workstation_doc.working_hours[0].start_time))
+ row.planned_start_time = datetime.datetime.combine(
+ start_date, get_time(workstation_doc.working_hours[0].start_time)
+ )
def add_time_log(self, args):
last_row = []
@@ -204,21 +233,25 @@
if last_row and args.get("complete_time"):
for row in self.time_logs:
if not row.to_time:
- row.update({
- "to_time": get_datetime(args.get("complete_time")),
- "operation": args.get("sub_operation"),
- "completed_qty": args.get("completed_qty") or 0.0
- })
+ row.update(
+ {
+ "to_time": get_datetime(args.get("complete_time")),
+ "operation": args.get("sub_operation"),
+ "completed_qty": args.get("completed_qty") or 0.0,
+ }
+ )
elif args.get("start_time"):
- new_args = frappe._dict({
- "from_time": get_datetime(args.get("start_time")),
- "operation": args.get("sub_operation"),
- "completed_qty": 0.0
- })
+ new_args = frappe._dict(
+ {
+ "from_time": get_datetime(args.get("start_time")),
+ "operation": args.get("sub_operation"),
+ "completed_qty": 0.0,
+ }
+ )
if employees:
for name in employees:
- new_args.employee = name.get('employee')
+ new_args.employee = name.get("employee")
self.add_start_time_log(new_args)
else:
self.add_start_time_log(new_args)
@@ -236,10 +269,7 @@
def set_employees(self, employees):
for name in employees:
- self.append('employee', {
- 'employee': name.get('employee'),
- 'completed_qty': 0.0
- })
+ self.append("employee", {"employee": name.get("employee"), "completed_qty": 0.0})
def reset_timer_value(self, args):
self.started_time = None
@@ -263,13 +293,17 @@
operation_wise_completed_time = {}
for time_log in self.time_logs:
if time_log.operation not in operation_wise_completed_time:
- operation_wise_completed_time.setdefault(time_log.operation,
- frappe._dict({"status": "Pending", "completed_qty":0.0, "completed_time": 0.0, "employee": []}))
+ operation_wise_completed_time.setdefault(
+ time_log.operation,
+ frappe._dict(
+ {"status": "Pending", "completed_qty": 0.0, "completed_time": 0.0, "employee": []}
+ ),
+ )
op_row = operation_wise_completed_time[time_log.operation]
op_row.status = "Work In Progress" if not time_log.time_in_mins else "Complete"
- if self.status == 'On Hold':
- op_row.status = 'Pause'
+ if self.status == "On Hold":
+ op_row.status = "Pause"
op_row.employee.append(time_log.employee)
if time_log.time_in_mins:
@@ -279,7 +313,7 @@
for row in self.sub_operations:
operation_deatils = operation_wise_completed_time.get(row.sub_operation)
if operation_deatils:
- if row.status != 'Complete':
+ if row.status != "Complete":
row.status = operation_deatils.status
row.completed_time = operation_deatils.completed_time
@@ -289,43 +323,52 @@
if operation_deatils.completed_qty:
row.completed_qty = operation_deatils.completed_qty / len(set(operation_deatils.employee))
else:
- row.status = 'Pending'
+ row.status = "Pending"
row.completed_time = 0.0
row.completed_qty = 0.0
def update_time_logs(self, row):
- self.append("time_logs", {
- "from_time": row.planned_start_time,
- "to_time": row.planned_end_time,
- "completed_qty": 0,
- "time_in_mins": time_diff_in_minutes(row.planned_end_time, row.planned_start_time),
- })
+ self.append(
+ "time_logs",
+ {
+ "from_time": row.planned_start_time,
+ "to_time": row.planned_end_time,
+ "completed_qty": 0,
+ "time_in_mins": time_diff_in_minutes(row.planned_end_time, row.planned_start_time),
+ },
+ )
@frappe.whitelist()
def get_required_items(self):
- if not self.get('work_order'):
+ if not self.get("work_order"):
return
- doc = frappe.get_doc('Work Order', self.get('work_order'))
- if doc.transfer_material_against == 'Work Order' or doc.skip_transfer:
+ doc = frappe.get_doc("Work Order", self.get("work_order"))
+ if doc.transfer_material_against == "Work Order" or doc.skip_transfer:
return
for d in doc.required_items:
if not d.operation:
- frappe.throw(_("Row {0} : Operation is required against the raw material item {1}")
- .format(d.idx, d.item_code))
+ frappe.throw(
+ _("Row {0} : Operation is required against the raw material item {1}").format(
+ d.idx, d.item_code
+ )
+ )
- if self.get('operation') == d.operation:
- self.append('items', {
- "item_code": d.item_code,
- "source_warehouse": d.source_warehouse,
- "uom": frappe.db.get_value("Item", d.item_code, 'stock_uom'),
- "item_name": d.item_name,
- "description": d.description,
- "required_qty": (d.required_qty * flt(self.for_quantity)) / doc.qty,
- "rate": d.rate,
- "amount": d.amount
- })
+ if self.get("operation") == d.operation:
+ self.append(
+ "items",
+ {
+ "item_code": d.item_code,
+ "source_warehouse": d.source_warehouse,
+ "uom": frappe.db.get_value("Item", d.item_code, "stock_uom"),
+ "item_name": d.item_name,
+ "description": d.description,
+ "required_qty": (d.required_qty * flt(self.for_quantity)) / doc.qty,
+ "rate": d.rate,
+ "amount": d.amount,
+ },
+ )
def on_submit(self):
self.validate_transfer_qty()
@@ -339,31 +382,52 @@
def validate_transfer_qty(self):
if self.items and self.transferred_qty < self.for_quantity:
- frappe.throw(_('Materials needs to be transferred to the work in progress warehouse for the job card {0}')
- .format(self.name))
+ frappe.throw(
+ _(
+ "Materials needs to be transferred to the work in progress warehouse for the job card {0}"
+ ).format(self.name)
+ )
def validate_job_card(self):
- if self.work_order and frappe.get_cached_value('Work Order', self.work_order, 'status') == 'Stopped':
- frappe.throw(_("Transaction not allowed against stopped Work Order {0}")
- .format(get_link_to_form('Work Order', self.work_order)))
+ if (
+ self.work_order
+ and frappe.get_cached_value("Work Order", self.work_order, "status") == "Stopped"
+ ):
+ frappe.throw(
+ _("Transaction not allowed against stopped Work Order {0}").format(
+ get_link_to_form("Work Order", self.work_order)
+ )
+ )
if not self.time_logs:
- frappe.throw(_("Time logs are required for {0} {1}")
- .format(bold("Job Card"), get_link_to_form("Job Card", self.name)))
+ frappe.throw(
+ _("Time logs are required for {0} {1}").format(
+ bold("Job Card"), get_link_to_form("Job Card", self.name)
+ )
+ )
if self.for_quantity and self.total_completed_qty != self.for_quantity:
total_completed_qty = bold(_("Total Completed Qty"))
qty_to_manufacture = bold(_("Qty to Manufacture"))
- frappe.throw(_("The {0} ({1}) must be equal to {2} ({3})")
- .format(total_completed_qty, bold(self.total_completed_qty), qty_to_manufacture,bold(self.for_quantity)))
+ frappe.throw(
+ _("The {0} ({1}) must be equal to {2} ({3})").format(
+ total_completed_qty,
+ bold(self.total_completed_qty),
+ qty_to_manufacture,
+ bold(self.for_quantity),
+ )
+ )
def update_work_order(self):
if not self.work_order:
return
- if self.is_corrective_job_card and not cint(frappe.db.get_single_value('Manufacturing Settings',
- 'add_corrective_operation_cost_in_finished_good_valuation')):
+ if self.is_corrective_job_card and not cint(
+ frappe.db.get_single_value(
+ "Manufacturing Settings", "add_corrective_operation_cost_in_finished_good_valuation"
+ )
+ ):
return
for_quantity, time_in_mins = 0, 0
@@ -375,7 +439,7 @@
for_quantity = flt(data[0].completed_qty)
time_in_mins = flt(data[0].time_in_mins)
- wo = frappe.get_doc('Work Order', self.work_order)
+ wo = frappe.get_doc("Work Order", self.work_order)
if self.is_corrective_job_card:
self.update_corrective_in_work_order(wo)
@@ -386,8 +450,11 @@
def update_corrective_in_work_order(self, wo):
wo.corrective_operation_cost = 0.0
- for row in frappe.get_all('Job Card', fields = ['total_time_in_mins', 'hour_rate'],
- filters = {'is_corrective_job_card': 1, 'docstatus': 1, 'work_order': self.work_order}):
+ for row in frappe.get_all(
+ "Job Card",
+ fields=["total_time_in_mins", "hour_rate"],
+ filters={"is_corrective_job_card": 1, "docstatus": 1, "work_order": self.work_order},
+ ):
wo.corrective_operation_cost += flt(row.total_time_in_mins) * flt(row.hour_rate)
wo.calculate_operating_cost()
@@ -395,27 +462,37 @@
wo.save()
def validate_produced_quantity(self, for_quantity, wo):
- if self.docstatus < 2: return
+ if self.docstatus < 2:
+ return
if wo.produced_qty > for_quantity:
- first_part_msg = (_("The {0} {1} is used to calculate the valuation cost for the finished good {2}.")
- .format(frappe.bold(_("Job Card")), frappe.bold(self.name), frappe.bold(self.production_item)))
+ first_part_msg = _(
+ "The {0} {1} is used to calculate the valuation cost for the finished good {2}."
+ ).format(
+ frappe.bold(_("Job Card")), frappe.bold(self.name), frappe.bold(self.production_item)
+ )
- second_part_msg = (_("Kindly cancel the Manufacturing Entries first against the work order {0}.")
- .format(frappe.bold(get_link_to_form("Work Order", self.work_order))))
+ second_part_msg = _(
+ "Kindly cancel the Manufacturing Entries first against the work order {0}."
+ ).format(frappe.bold(get_link_to_form("Work Order", self.work_order)))
- frappe.throw(_("{0} {1}").format(first_part_msg, second_part_msg),
- JobCardCancelError, title = _("Error"))
+ frappe.throw(
+ _("{0} {1}").format(first_part_msg, second_part_msg), JobCardCancelError, title=_("Error")
+ )
def update_work_order_data(self, for_quantity, time_in_mins, wo):
- time_data = frappe.db.sql("""
+ time_data = frappe.db.sql(
+ """
SELECT
min(from_time) as start_time, max(to_time) as end_time
FROM `tabJob Card` jc, `tabJob Card Time Log` jctl
WHERE
jctl.parent = jc.name and jc.work_order = %s and jc.operation_id = %s
and jc.docstatus = 1 and IFNULL(jc.is_corrective_job_card, 0) = 0
- """, (self.work_order, self.operation_id), as_dict=1)
+ """,
+ (self.work_order, self.operation_id),
+ as_dict=1,
+ )
for data in wo.operations:
if data.get("name") == self.operation_id:
@@ -434,91 +511,118 @@
wo.save()
def get_current_operation_data(self):
- return frappe.get_all('Job Card',
- fields = ["sum(total_time_in_mins) as time_in_mins", "sum(total_completed_qty) as completed_qty"],
- filters = {"docstatus": 1, "work_order": self.work_order, "operation_id": self.operation_id,
- "is_corrective_job_card": 0})
+ return frappe.get_all(
+ "Job Card",
+ fields=["sum(total_time_in_mins) as time_in_mins", "sum(total_completed_qty) as completed_qty"],
+ filters={
+ "docstatus": 1,
+ "work_order": self.work_order,
+ "operation_id": self.operation_id,
+ "is_corrective_job_card": 0,
+ },
+ )
def set_transferred_qty_in_job_card(self, ste_doc):
for row in ste_doc.items:
- if not row.job_card_item: continue
+ if not row.job_card_item:
+ continue
- qty = frappe.db.sql(""" SELECT SUM(qty) from `tabStock Entry Detail` sed, `tabStock Entry` se
+ qty = frappe.db.sql(
+ """ SELECT SUM(qty) from `tabStock Entry Detail` sed, `tabStock Entry` se
WHERE sed.job_card_item = %s and se.docstatus = 1 and sed.parent = se.name and
se.purpose = 'Material Transfer for Manufacture'
- """, (row.job_card_item))[0][0]
+ """,
+ (row.job_card_item),
+ )[0][0]
- frappe.db.set_value('Job Card Item', row.job_card_item, 'transferred_qty', flt(qty))
+ frappe.db.set_value("Job Card Item", row.job_card_item, "transferred_qty", flt(qty))
def set_transferred_qty(self, update_status=False):
"Set total FG Qty for which RM was transferred."
if not self.items:
self.transferred_qty = self.for_quantity if self.docstatus == 1 else 0
- doc = frappe.get_doc('Work Order', self.get('work_order'))
- if doc.transfer_material_against == 'Work Order' or doc.skip_transfer:
+ doc = frappe.get_doc("Work Order", self.get("work_order"))
+ if doc.transfer_material_against == "Work Order" or doc.skip_transfer:
return
if self.items:
# sum of 'For Quantity' of Stock Entries against JC
- self.transferred_qty = frappe.db.get_value('Stock Entry', {
- 'job_card': self.name,
- 'work_order': self.work_order,
- 'docstatus': 1,
- 'purpose': 'Material Transfer for Manufacture'
- }, 'sum(fg_completed_qty)') or 0
+ self.transferred_qty = (
+ frappe.db.get_value(
+ "Stock Entry",
+ {
+ "job_card": self.name,
+ "work_order": self.work_order,
+ "docstatus": 1,
+ "purpose": "Material Transfer for Manufacture",
+ },
+ "sum(fg_completed_qty)",
+ )
+ or 0
+ )
self.db_set("transferred_qty", self.transferred_qty)
qty = 0
if self.work_order:
- doc = frappe.get_doc('Work Order', self.work_order)
- if doc.transfer_material_against == 'Job Card' and not doc.skip_transfer:
+ doc = frappe.get_doc("Work Order", self.work_order)
+ if doc.transfer_material_against == "Job Card" and not doc.skip_transfer:
completed = True
for d in doc.operations:
- if d.status != 'Completed':
+ if d.status != "Completed":
completed = False
break
if completed:
- job_cards = frappe.get_all('Job Card', filters = {'work_order': self.work_order,
- 'docstatus': ('!=', 2)}, fields = 'sum(transferred_qty) as qty', group_by='operation_id')
+ job_cards = frappe.get_all(
+ "Job Card",
+ filters={"work_order": self.work_order, "docstatus": ("!=", 2)},
+ fields="sum(transferred_qty) as qty",
+ group_by="operation_id",
+ )
if job_cards:
qty = min(d.qty for d in job_cards)
- doc.db_set('material_transferred_for_manufacturing', qty)
+ doc.db_set("material_transferred_for_manufacturing", qty)
self.set_status(update_status)
def set_status(self, update_status=False):
- if self.status == "On Hold": return
+ if self.status == "On Hold":
+ return
- self.status = {
- 0: "Open",
- 1: "Submitted",
- 2: "Cancelled"
- }[self.docstatus or 0]
+ self.status = {0: "Open", 1: "Submitted", 2: "Cancelled"}[self.docstatus or 0]
if self.for_quantity <= self.transferred_qty:
- self.status = 'Material Transferred'
+ self.status = "Material Transferred"
if self.time_logs:
- self.status = 'Work In Progress'
+ self.status = "Work In Progress"
- if (self.docstatus == 1 and
- (self.for_quantity <= self.total_completed_qty or not self.items)):
- self.status = 'Completed'
+ if self.docstatus == 1 and (self.for_quantity <= self.total_completed_qty or not self.items):
+ self.status = "Completed"
if update_status:
- self.db_set('status', self.status)
+ self.db_set("status", self.status)
def validate_operation_id(self):
- if (self.get("operation_id") and self.get("operation_row_number") and self.operation and self.work_order and
- frappe.get_cached_value("Work Order Operation", self.operation_row_number, "name") != self.operation_id):
+ if (
+ self.get("operation_id")
+ and self.get("operation_row_number")
+ and self.operation
+ and self.work_order
+ and frappe.get_cached_value("Work Order Operation", self.operation_row_number, "name")
+ != self.operation_id
+ ):
work_order = bold(get_link_to_form("Work Order", self.work_order))
- frappe.throw(_("Operation {0} does not belong to the work order {1}")
- .format(bold(self.operation), work_order), OperationMismatchError)
+ frappe.throw(
+ _("Operation {0} does not belong to the work order {1}").format(
+ bold(self.operation), work_order
+ ),
+ OperationMismatchError,
+ )
def validate_sequence_id(self):
if self.is_corrective_job_card:
@@ -534,18 +638,25 @@
current_operation_qty += flt(self.total_completed_qty)
- data = frappe.get_all("Work Order Operation",
- fields = ["operation", "status", "completed_qty"],
- filters={"docstatus": 1, "parent": self.work_order, "sequence_id": ('<', self.sequence_id)},
- order_by = "sequence_id, idx")
+ data = frappe.get_all(
+ "Work Order Operation",
+ fields=["operation", "status", "completed_qty"],
+ filters={"docstatus": 1, "parent": self.work_order, "sequence_id": ("<", self.sequence_id)},
+ order_by="sequence_id, idx",
+ )
- message = "Job Card {0}: As per the sequence of the operations in the work order {1}".format(bold(self.name),
- bold(get_link_to_form("Work Order", self.work_order)))
+ message = "Job Card {0}: As per the sequence of the operations in the work order {1}".format(
+ bold(self.name), bold(get_link_to_form("Work Order", self.work_order))
+ )
for row in data:
if row.status != "Completed" and row.completed_qty < current_operation_qty:
- frappe.throw(_("{0}, complete the operation {1} before the operation {2}.")
- .format(message, bold(row.operation), bold(self.operation)), OperationSequenceError)
+ frappe.throw(
+ _("{0}, complete the operation {1} before the operation {2}.").format(
+ message, bold(row.operation), bold(self.operation)
+ ),
+ OperationSequenceError,
+ )
def validate_work_order(self):
if self.is_work_order_closed():
@@ -553,13 +664,14 @@
def is_work_order_closed(self):
if self.work_order:
- status = frappe.get_value('Work Order', self.work_order)
+ status = frappe.get_value("Work Order", self.work_order)
if status == "Closed":
return True
return False
+
@frappe.whitelist()
def make_time_log(args):
if isinstance(args, str):
@@ -570,16 +682,17 @@
doc.validate_sequence_id()
doc.add_time_log(args)
+
@frappe.whitelist()
def get_operation_details(work_order, operation):
if work_order and operation:
- return frappe.get_all("Work Order Operation", fields = ["name", "idx"],
- filters = {
- "parent": work_order,
- "operation": operation
- }
+ return frappe.get_all(
+ "Work Order Operation",
+ fields=["name", "idx"],
+ filters={"parent": work_order, "operation": operation},
)
+
@frappe.whitelist()
def get_operations(doctype, txt, searchfield, start, page_len, filters):
if not filters.get("work_order"):
@@ -589,12 +702,16 @@
if txt:
args["operation"] = ("like", "%{0}%".format(txt))
- return frappe.get_all("Work Order Operation",
- filters = args,
- fields = ["distinct operation as operation"],
- limit_start = start,
- limit_page_length = page_len,
- order_by="idx asc", as_list=1)
+ return frappe.get_all(
+ "Work Order Operation",
+ filters=args,
+ fields=["distinct operation as operation"],
+ limit_start=start,
+ limit_page_length=page_len,
+ order_by="idx asc",
+ as_list=1,
+ )
+
@frappe.whitelist()
def make_material_request(source_name, target_doc=None):
@@ -604,26 +721,29 @@
def set_missing_values(source, target):
target.material_request_type = "Material Transfer"
- doclist = get_mapped_doc("Job Card", source_name, {
- "Job Card": {
- "doctype": "Material Request",
- "field_map": {
- "name": "job_card",
+ doclist = get_mapped_doc(
+ "Job Card",
+ source_name,
+ {
+ "Job Card": {
+ "doctype": "Material Request",
+ "field_map": {
+ "name": "job_card",
+ },
+ },
+ "Job Card Item": {
+ "doctype": "Material Request Item",
+ "field_map": {"required_qty": "qty", "uom": "stock_uom", "name": "job_card_item"},
+ "postprocess": update_item,
},
},
- "Job Card Item": {
- "doctype": "Material Request Item",
- "field_map": {
- "required_qty": "qty",
- "uom": "stock_uom",
- "name": "job_card_item"
- },
- "postprocess": update_item,
- }
- }, target_doc, set_missing_values)
+ target_doc,
+ set_missing_values,
+ )
return doclist
+
@frappe.whitelist()
def make_stock_entry(source_name, target_doc=None):
def update_item(source, target, source_parent):
@@ -641,7 +761,7 @@
target.from_bom = 1
# avoid negative 'For Quantity'
- pending_fg_qty = flt(source.get('for_quantity', 0)) - flt(source.get('transferred_qty', 0))
+ pending_fg_qty = flt(source.get("for_quantity", 0)) - flt(source.get("transferred_qty", 0))
target.fg_completed_qty = pending_fg_qty if pending_fg_qty > 0 else 0
target.set_transfer_qty()
@@ -649,36 +769,45 @@
target.set_missing_values()
target.set_stock_entry_type()
- wo_allows_alternate_item = frappe.db.get_value("Work Order", target.work_order, "allow_alternative_item")
+ wo_allows_alternate_item = frappe.db.get_value(
+ "Work Order", target.work_order, "allow_alternative_item"
+ )
for item in target.items:
- item.allow_alternative_item = int(wo_allows_alternate_item and
- frappe.get_cached_value("Item", item.item_code, "allow_alternative_item"))
+ item.allow_alternative_item = int(
+ wo_allows_alternate_item
+ and frappe.get_cached_value("Item", item.item_code, "allow_alternative_item")
+ )
- doclist = get_mapped_doc("Job Card", source_name, {
- "Job Card": {
- "doctype": "Stock Entry",
- "field_map": {
- "name": "job_card",
- "for_quantity": "fg_completed_qty"
+ doclist = get_mapped_doc(
+ "Job Card",
+ source_name,
+ {
+ "Job Card": {
+ "doctype": "Stock Entry",
+ "field_map": {"name": "job_card", "for_quantity": "fg_completed_qty"},
+ },
+ "Job Card Item": {
+ "doctype": "Stock Entry Detail",
+ "field_map": {
+ "source_warehouse": "s_warehouse",
+ "required_qty": "qty",
+ "name": "job_card_item",
+ },
+ "postprocess": update_item,
+ "condition": lambda doc: doc.required_qty > 0,
},
},
- "Job Card Item": {
- "doctype": "Stock Entry Detail",
- "field_map": {
- "source_warehouse": "s_warehouse",
- "required_qty": "qty",
- "name": "job_card_item"
- },
- "postprocess": update_item,
- "condition": lambda doc: doc.required_qty > 0
- }
- }, target_doc, set_missing_values)
+ target_doc,
+ set_missing_values,
+ )
return doclist
+
def time_diff_in_minutes(string_ed_date, string_st_date):
return time_diff(string_ed_date, string_st_date).total_seconds() / 60
+
@frappe.whitelist()
def get_job_details(start, end, filters=None):
events = []
@@ -686,41 +815,49 @@
event_color = {
"Completed": "#cdf5a6",
"Material Transferred": "#ffdd9e",
- "Work In Progress": "#D3D3D3"
+ "Work In Progress": "#D3D3D3",
}
from frappe.desk.reportview import get_filters_cond
+
conditions = get_filters_cond("Job Card", filters, [])
- job_cards = frappe.db.sql(""" SELECT `tabJob Card`.name, `tabJob Card`.work_order,
+ job_cards = frappe.db.sql(
+ """ SELECT `tabJob Card`.name, `tabJob Card`.work_order,
`tabJob Card`.status, ifnull(`tabJob Card`.remarks, ''),
min(`tabJob Card Time Log`.from_time) as from_time,
max(`tabJob Card Time Log`.to_time) as to_time
FROM `tabJob Card` , `tabJob Card Time Log`
WHERE
`tabJob Card`.name = `tabJob Card Time Log`.parent {0}
- group by `tabJob Card`.name""".format(conditions), as_dict=1)
+ group by `tabJob Card`.name""".format(
+ conditions
+ ),
+ as_dict=1,
+ )
for d in job_cards:
- subject_data = []
- for field in ["name", "work_order", "remarks"]:
- if not d.get(field): continue
+ subject_data = []
+ for field in ["name", "work_order", "remarks"]:
+ if not d.get(field):
+ continue
- subject_data.append(d.get(field))
+ subject_data.append(d.get(field))
- color = event_color.get(d.status)
- job_card_data = {
- 'from_time': d.from_time,
- 'to_time': d.to_time,
- 'name': d.name,
- 'subject': '\n'.join(subject_data),
- 'color': color if color else "#89bcde"
- }
+ color = event_color.get(d.status)
+ job_card_data = {
+ "from_time": d.from_time,
+ "to_time": d.to_time,
+ "name": d.name,
+ "subject": "\n".join(subject_data),
+ "color": color if color else "#89bcde",
+ }
- events.append(job_card_data)
+ events.append(job_card_data)
return events
+
@frappe.whitelist()
def make_corrective_job_card(source_name, operation=None, for_operation=None, target_doc=None):
def set_missing_values(source, target):
@@ -728,20 +865,26 @@
target.operation = operation
target.for_operation = for_operation
- target.set('time_logs', [])
- target.set('employee', [])
- target.set('items', [])
+ target.set("time_logs", [])
+ target.set("employee", [])
+ target.set("items", [])
target.set_sub_operations()
target.get_required_items()
target.validate_time_logs()
- doclist = get_mapped_doc("Job Card", source_name, {
- "Job Card": {
- "doctype": "Job Card",
- "field_map": {
- "name": "for_job_card",
- },
- }
- }, target_doc, set_missing_values)
+ doclist = get_mapped_doc(
+ "Job Card",
+ source_name,
+ {
+ "Job Card": {
+ "doctype": "Job Card",
+ "field_map": {
+ "name": "for_job_card",
+ },
+ }
+ },
+ target_doc,
+ set_missing_values,
+ )
return doclist
diff --git a/erpnext/manufacturing/doctype/job_card/job_card_dashboard.py b/erpnext/manufacturing/doctype/job_card/job_card_dashboard.py
index 2c48872..14c1f36 100644
--- a/erpnext/manufacturing/doctype/job_card/job_card_dashboard.py
+++ b/erpnext/manufacturing/doctype/job_card/job_card_dashboard.py
@@ -3,18 +3,10 @@
def get_data():
return {
- 'fieldname': 'job_card',
- 'non_standard_fieldnames': {
- 'Quality Inspection': 'reference_name'
- },
- 'transactions': [
- {
- 'label': _('Transactions'),
- 'items': ['Material Request', 'Stock Entry']
- },
- {
- 'label': _('Reference'),
- 'items': ['Quality Inspection']
- }
- ]
+ "fieldname": "job_card",
+ "non_standard_fieldnames": {"Quality Inspection": "reference_name"},
+ "transactions": [
+ {"label": _("Transactions"), "items": ["Material Request", "Stock Entry"]},
+ {"label": _("Reference"), "items": ["Quality Inspection"]},
+ ],
}
diff --git a/erpnext/manufacturing/doctype/job_card/test_job_card.py b/erpnext/manufacturing/doctype/job_card/test_job_card.py
index c5841c1..4647ddf 100644
--- a/erpnext/manufacturing/doctype/job_card/test_job_card.py
+++ b/erpnext/manufacturing/doctype/job_card/test_job_card.py
@@ -20,13 +20,11 @@
transfer_material_against, source_warehouse = None, None
- tests_that_skip_setup = (
- "test_job_card_material_transfer_correctness",
- )
+ tests_that_skip_setup = ("test_job_card_material_transfer_correctness",)
tests_that_transfer_against_jc = (
"test_job_card_multiple_materials_transfer",
"test_job_card_excess_material_transfer",
- "test_job_card_partial_material_transfer"
+ "test_job_card_partial_material_transfer",
)
if self._testMethodName in tests_that_skip_setup:
@@ -40,7 +38,7 @@
item="_Test FG Item 2",
qty=2,
transfer_material_against=transfer_material_against,
- source_warehouse=source_warehouse
+ source_warehouse=source_warehouse,
)
def tearDown(self):
@@ -48,8 +46,9 @@
def test_job_card(self):
- job_cards = frappe.get_all('Job Card',
- filters = {'work_order': self.work_order.name}, fields = ["operation_id", "name"])
+ job_cards = frappe.get_all(
+ "Job Card", filters={"work_order": self.work_order.name}, fields=["operation_id", "name"]
+ )
if job_cards:
job_card = job_cards[0]
@@ -63,30 +62,38 @@
frappe.delete_doc("Job Card", d.name)
def test_job_card_with_different_work_station(self):
- job_cards = frappe.get_all('Job Card',
- filters = {'work_order': self.work_order.name},
- fields = ["operation_id", "workstation", "name", "for_quantity"])
+ job_cards = frappe.get_all(
+ "Job Card",
+ filters={"work_order": self.work_order.name},
+ fields=["operation_id", "workstation", "name", "for_quantity"],
+ )
job_card = job_cards[0]
if job_card:
- workstation = frappe.db.get_value("Workstation",
- {"name": ("not in", [job_card.workstation])}, "name")
+ workstation = frappe.db.get_value(
+ "Workstation", {"name": ("not in", [job_card.workstation])}, "name"
+ )
if not workstation or job_card.workstation == workstation:
workstation = make_workstation(workstation_name=random_string(5)).name
doc = frappe.get_doc("Job Card", job_card.name)
doc.workstation = workstation
- doc.append("time_logs", {
- "from_time": "2009-01-01 12:06:25",
- "to_time": "2009-01-01 12:37:25",
- "time_in_mins": "31.00002",
- "completed_qty": job_card.for_quantity
- })
+ doc.append(
+ "time_logs",
+ {
+ "from_time": "2009-01-01 12:06:25",
+ "to_time": "2009-01-01 12:37:25",
+ "time_in_mins": "31.00002",
+ "completed_qty": job_card.for_quantity,
+ },
+ )
doc.submit()
- completed_qty = frappe.db.get_value("Work Order Operation", job_card.operation_id, "completed_qty")
+ completed_qty = frappe.db.get_value(
+ "Work Order Operation", job_card.operation_id, "completed_qty"
+ )
self.assertEqual(completed_qty, job_card.for_quantity)
doc.cancel()
@@ -97,51 +104,49 @@
def test_job_card_overlap(self):
wo2 = make_wo_order_test_record(item="_Test FG Item 2", qty=2)
- jc1_name = frappe.db.get_value("Job Card", {'work_order': self.work_order.name})
- jc2_name = frappe.db.get_value("Job Card", {'work_order': wo2.name})
+ jc1_name = frappe.db.get_value("Job Card", {"work_order": self.work_order.name})
+ jc2_name = frappe.db.get_value("Job Card", {"work_order": wo2.name})
jc1 = frappe.get_doc("Job Card", jc1_name)
jc2 = frappe.get_doc("Job Card", jc2_name)
- employee = "_T-Employee-00001" # from test records
+ employee = "_T-Employee-00001" # from test records
- jc1.append("time_logs", {
- "from_time": "2021-01-01 00:00:00",
- "to_time": "2021-01-01 08:00:00",
- "completed_qty": 1,
- "employee": employee,
- })
+ jc1.append(
+ "time_logs",
+ {
+ "from_time": "2021-01-01 00:00:00",
+ "to_time": "2021-01-01 08:00:00",
+ "completed_qty": 1,
+ "employee": employee,
+ },
+ )
jc1.save()
# add a new entry in same time slice
- jc2.append("time_logs", {
- "from_time": "2021-01-01 00:01:00",
- "to_time": "2021-01-01 06:00:00",
- "completed_qty": 1,
- "employee": employee,
- })
+ jc2.append(
+ "time_logs",
+ {
+ "from_time": "2021-01-01 00:01:00",
+ "to_time": "2021-01-01 06:00:00",
+ "completed_qty": 1,
+ "employee": employee,
+ },
+ )
self.assertRaises(OverlapError, jc2.save)
def test_job_card_multiple_materials_transfer(self):
"Test transferring RMs separately against Job Card with multiple RMs."
+ make_stock_entry(item_code="_Test Item", target="Stores - _TC", qty=10, basic_rate=100)
make_stock_entry(
- item_code="_Test Item",
- target="Stores - _TC",
- qty=10,
- basic_rate=100
- )
- make_stock_entry(
- item_code="_Test Item Home Desktop Manufactured",
- target="Stores - _TC",
- qty=6,
- basic_rate=100
+ item_code="_Test Item Home Desktop Manufactured", target="Stores - _TC", qty=6, basic_rate=100
)
- job_card_name = frappe.db.get_value("Job Card", {'work_order': self.work_order.name})
+ job_card_name = frappe.db.get_value("Job Card", {"work_order": self.work_order.name})
job_card = frappe.get_doc("Job Card", job_card_name)
transfer_entry_1 = make_stock_entry_from_jc(job_card_name)
- del transfer_entry_1.items[1] # transfer only 1 of 2 RMs
+ del transfer_entry_1.items[1] # transfer only 1 of 2 RMs
transfer_entry_1.insert()
transfer_entry_1.submit()
@@ -162,12 +167,12 @@
def test_job_card_excess_material_transfer(self):
"Test transferring more than required RM against Job Card."
- make_stock_entry(item_code="_Test Item", target="Stores - _TC",
- qty=25, basic_rate=100)
- make_stock_entry(item_code="_Test Item Home Desktop Manufactured",
- target="Stores - _TC", qty=15, basic_rate=100)
+ make_stock_entry(item_code="_Test Item", target="Stores - _TC", qty=25, basic_rate=100)
+ make_stock_entry(
+ item_code="_Test Item Home Desktop Manufactured", target="Stores - _TC", qty=15, basic_rate=100
+ )
- job_card_name = frappe.db.get_value("Job Card", {'work_order': self.work_order.name})
+ job_card_name = frappe.db.get_value("Job Card", {"work_order": self.work_order.name})
job_card = frappe.get_doc("Job Card", job_card_name)
self.assertEqual(job_card.status, "Open")
@@ -193,11 +198,10 @@
transfer_entry_3 = make_stock_entry_from_jc(job_card_name)
self.assertEqual(transfer_entry_3.fg_completed_qty, 0)
- job_card.append("time_logs", {
- "from_time": "2021-01-01 00:01:00",
- "to_time": "2021-01-01 06:00:00",
- "completed_qty": 2
- })
+ job_card.append(
+ "time_logs",
+ {"from_time": "2021-01-01 00:01:00", "to_time": "2021-01-01 06:00:00", "completed_qty": 2},
+ )
job_card.save()
job_card.submit()
@@ -207,12 +211,12 @@
def test_job_card_partial_material_transfer(self):
"Test partial material transfer against Job Card"
- make_stock_entry(item_code="_Test Item", target="Stores - _TC",
- qty=25, basic_rate=100)
- make_stock_entry(item_code="_Test Item Home Desktop Manufactured",
- target="Stores - _TC", qty=15, basic_rate=100)
+ make_stock_entry(item_code="_Test Item", target="Stores - _TC", qty=25, basic_rate=100)
+ make_stock_entry(
+ item_code="_Test Item Home Desktop Manufactured", target="Stores - _TC", qty=15, basic_rate=100
+ )
- job_card_name = frappe.db.get_value("Job Card", {'work_order': self.work_order.name})
+ job_card_name = frappe.db.get_value("Job Card", {"work_order": self.work_order.name})
job_card = frappe.get_doc("Job Card", job_card_name)
# partially transfer
@@ -242,15 +246,14 @@
def test_job_card_material_transfer_correctness(self):
"""
- 1. Test if only current Job Card Items are pulled in a Stock Entry against a Job Card
- 2. Test impact of changing 'For Qty' in such a Stock Entry
+ 1. Test if only current Job Card Items are pulled in a Stock Entry against a Job Card
+ 2. Test impact of changing 'For Qty' in such a Stock Entry
"""
create_bom_with_multiple_operations()
work_order = make_wo_with_transfer_against_jc()
job_card_name = frappe.db.get_value(
- "Job Card",
- {"work_order": work_order.name,"operation": "Test Operation A"}
+ "Job Card", {"work_order": work_order.name, "operation": "Test Operation A"}
)
job_card = frappe.get_doc("Job Card", job_card_name)
@@ -276,6 +279,7 @@
# rollback via tearDown method
+
def create_bom_with_multiple_operations():
"Create a BOM with multiple operations and Material Transfer against Job Card"
from erpnext.manufacturing.doctype.operation.test_operation import make_operation
@@ -287,19 +291,22 @@
"operation": "Test Operation A",
"workstation": "_Test Workstation A",
"hour_rate_rent": 300,
- "time_in_mins": 60
+ "time_in_mins": 60,
}
make_workstation(row)
make_operation(row)
- bom_doc.append("operations", {
- "operation": "Test Operation A",
- "description": "Test Operation A",
- "workstation": "_Test Workstation A",
- "hour_rate": 300,
- "time_in_mins": 60,
- "operating_cost": 100
- })
+ bom_doc.append(
+ "operations",
+ {
+ "operation": "Test Operation A",
+ "description": "Test Operation A",
+ "workstation": "_Test Workstation A",
+ "hour_rate": 300,
+ "time_in_mins": 60,
+ "operating_cost": 100,
+ },
+ )
bom_doc.transfer_material_against = "Job Card"
bom_doc.save()
@@ -307,6 +314,7 @@
return bom_doc
+
def make_wo_with_transfer_against_jc():
"Create a WO with multiple operations and Material Transfer against Job Card"
@@ -315,7 +323,7 @@
qty=4,
transfer_material_against="Job Card",
source_warehouse="Stores - _TC",
- do_not_submit=True
+ do_not_submit=True,
)
work_order.required_items[0].operation = "Test Operation A"
work_order.required_items[1].operation = "_Test Operation 1"
@@ -323,8 +331,9 @@
return work_order
+
def make_bom_for_jc_tests():
- test_records = frappe.get_test_records('BOM')
+ test_records = frappe.get_test_records("BOM")
bom = frappe.copy_doc(test_records[2])
bom.set_rate_of_sub_assembly_item_based_on_bom = 0
bom.rm_cost_as_per = "Valuation Rate"
diff --git a/erpnext/manufacturing/doctype/manufacturing_settings/manufacturing_settings.py b/erpnext/manufacturing/doctype/manufacturing_settings/manufacturing_settings.py
index c919e8b..730a857 100644
--- a/erpnext/manufacturing/doctype/manufacturing_settings/manufacturing_settings.py
+++ b/erpnext/manufacturing/doctype/manufacturing_settings/manufacturing_settings.py
@@ -11,14 +11,19 @@
class ManufacturingSettings(Document):
pass
+
def get_mins_between_operations():
- return relativedelta(minutes=cint(frappe.db.get_single_value("Manufacturing Settings",
- "mins_between_operations")) or 10)
+ return relativedelta(
+ minutes=cint(frappe.db.get_single_value("Manufacturing Settings", "mins_between_operations"))
+ or 10
+ )
+
@frappe.whitelist()
def is_material_consumption_enabled():
- if not hasattr(frappe.local, 'material_consumption'):
- frappe.local.material_consumption = cint(frappe.db.get_single_value('Manufacturing Settings',
- 'material_consumption'))
+ if not hasattr(frappe.local, "material_consumption"):
+ frappe.local.material_consumption = cint(
+ frappe.db.get_single_value("Manufacturing Settings", "material_consumption")
+ )
return frappe.local.material_consumption
diff --git a/erpnext/manufacturing/doctype/operation/operation.py b/erpnext/manufacturing/doctype/operation/operation.py
index 41726f3..9c8f9ac 100644
--- a/erpnext/manufacturing/doctype/operation/operation.py
+++ b/erpnext/manufacturing/doctype/operation/operation.py
@@ -19,12 +19,14 @@
operation_list = []
for row in self.sub_operations:
if row.operation in operation_list:
- frappe.throw(_("The operation {0} can not add multiple times")
- .format(frappe.bold(row.operation)))
+ frappe.throw(
+ _("The operation {0} can not add multiple times").format(frappe.bold(row.operation))
+ )
if self.name == row.operation:
- frappe.throw(_("The operation {0} can not be the sub operation")
- .format(frappe.bold(row.operation)))
+ frappe.throw(
+ _("The operation {0} can not be the sub operation").format(frappe.bold(row.operation))
+ )
operation_list.append(row.operation)
diff --git a/erpnext/manufacturing/doctype/operation/operation_dashboard.py b/erpnext/manufacturing/doctype/operation/operation_dashboard.py
index 9f7efa2..8dc901a 100644
--- a/erpnext/manufacturing/doctype/operation/operation_dashboard.py
+++ b/erpnext/manufacturing/doctype/operation/operation_dashboard.py
@@ -3,11 +3,6 @@
def get_data():
return {
- 'fieldname': 'operation',
- 'transactions': [
- {
- 'label': _('Manufacture'),
- 'items': ['BOM', 'Work Order', 'Job Card']
- }
- ]
+ "fieldname": "operation",
+ "transactions": [{"label": _("Manufacture"), "items": ["BOM", "Work Order", "Job Card"]}],
}
diff --git a/erpnext/manufacturing/doctype/operation/test_operation.py b/erpnext/manufacturing/doctype/operation/test_operation.py
index e511084..ce9f8e0 100644
--- a/erpnext/manufacturing/doctype/operation/test_operation.py
+++ b/erpnext/manufacturing/doctype/operation/test_operation.py
@@ -5,11 +5,13 @@
import frappe
-test_records = frappe.get_test_records('Operation')
+test_records = frappe.get_test_records("Operation")
+
class TestOperation(unittest.TestCase):
pass
+
def make_operation(*args, **kwargs):
args = args if args else kwargs
if isinstance(args, tuple):
@@ -18,11 +20,9 @@
args = frappe._dict(args)
if not frappe.db.exists("Operation", args.operation):
- doc = frappe.get_doc({
- "doctype": "Operation",
- "name": args.operation,
- "workstation": args.workstation
- })
+ doc = frappe.get_doc(
+ {"doctype": "Operation", "name": args.operation, "workstation": args.workstation}
+ )
doc.insert()
return doc
diff --git a/erpnext/manufacturing/doctype/production_plan/production_plan.py b/erpnext/manufacturing/doctype/production_plan/production_plan.py
index 349f40e..89f9ca6 100644
--- a/erpnext/manufacturing/doctype/production_plan/production_plan.py
+++ b/erpnext/manufacturing/doctype/production_plan/production_plan.py
@@ -36,7 +36,7 @@
def set_pending_qty_in_row_without_reference(self):
"Set Pending Qty in independent rows (not from SO or MR)."
- if self.docstatus > 0: # set only to initialise value before submit
+ if self.docstatus > 0: # set only to initialise value before submit
return
for item in self.po_items:
@@ -49,7 +49,7 @@
self.total_planned_qty += flt(d.planned_qty)
def validate_data(self):
- for d in self.get('po_items'):
+ for d in self.get("po_items"):
if not d.bom_no:
frappe.throw(_("Please select BOM for Item in Row {0}").format(d.idx))
else:
@@ -59,9 +59,9 @@
frappe.throw(_("Please enter Planned Qty for Item {0} at row {1}").format(d.item_code, d.idx))
def _rename_temporary_references(self):
- """ po_items and sub_assembly_items items are both constructed client side without saving.
+ """po_items and sub_assembly_items items are both constructed client side without saving.
- Attempt to fix linkages by using temporary names to map final row names.
+ Attempt to fix linkages by using temporary names to map final row names.
"""
new_name_map = {d.temporary_name: d.name for d in self.po_items if d.temporary_name}
actual_names = {d.name for d in self.po_items}
@@ -72,7 +72,7 @@
@frappe.whitelist()
def get_open_sales_orders(self):
- """ Pull sales orders which are pending to deliver based on criteria selected"""
+ """Pull sales orders which are pending to deliver based on criteria selected"""
open_so = get_sales_orders(self)
if open_so:
@@ -81,20 +81,23 @@
frappe.msgprint(_("Sales orders are not available for production"))
def add_so_in_table(self, open_so):
- """ Add sales orders in the table"""
- self.set('sales_orders', [])
+ """Add sales orders in the table"""
+ self.set("sales_orders", [])
for data in open_so:
- self.append('sales_orders', {
- 'sales_order': data.name,
- 'sales_order_date': data.transaction_date,
- 'customer': data.customer,
- 'grand_total': data.base_grand_total
- })
+ self.append(
+ "sales_orders",
+ {
+ "sales_order": data.name,
+ "sales_order_date": data.transaction_date,
+ "customer": data.customer,
+ "grand_total": data.base_grand_total,
+ },
+ )
@frappe.whitelist()
def get_pending_material_requests(self):
- """ Pull Material Requests that are pending based on criteria selected"""
+ """Pull Material Requests that are pending based on criteria selected"""
mr_filter = item_filter = ""
if self.from_date:
mr_filter += " and mr.transaction_date >= %(from_date)s"
@@ -106,7 +109,8 @@
if self.item_code:
item_filter += " and mr_item.item_code = %(item)s"
- pending_mr = frappe.db.sql("""
+ pending_mr = frappe.db.sql(
+ """
select distinct mr.name, mr.transaction_date
from `tabMaterial Request` mr, `tabMaterial Request Item` mr_item
where mr_item.parent = mr.name
@@ -115,29 +119,34 @@
and mr_item.qty > ifnull(mr_item.ordered_qty,0) {0} {1}
and (exists (select name from `tabBOM` bom where bom.item=mr_item.item_code
and bom.is_active = 1))
- """.format(mr_filter, item_filter), {
+ """.format(
+ mr_filter, item_filter
+ ),
+ {
"from_date": self.from_date,
"to_date": self.to_date,
"warehouse": self.warehouse,
"item": self.item_code,
- "company": self.company
- }, as_dict=1)
+ "company": self.company,
+ },
+ as_dict=1,
+ )
self.add_mr_in_table(pending_mr)
def add_mr_in_table(self, pending_mr):
- """ Add Material Requests in the table"""
- self.set('material_requests', [])
+ """Add Material Requests in the table"""
+ self.set("material_requests", [])
for data in pending_mr:
- self.append('material_requests', {
- 'material_request': data.name,
- 'material_request_date': data.transaction_date
- })
+ self.append(
+ "material_requests",
+ {"material_request": data.name, "material_request_date": data.transaction_date},
+ )
@frappe.whitelist()
def get_items(self):
- self.set('po_items', [])
+ self.set("po_items", [])
if self.get_items_from == "Sales Order":
self.get_so_items()
@@ -152,10 +161,12 @@
def get_bom_item(self):
"""Check if Item or if its Template has a BOM."""
bom_item = None
- has_bom = frappe.db.exists({'doctype': 'BOM', 'item': self.item_code, 'docstatus': 1})
+ has_bom = frappe.db.exists({"doctype": "BOM", "item": self.item_code, "docstatus": 1})
if not has_bom:
- template_item = frappe.db.get_value('Item', self.item_code, ['variant_of'])
- bom_item = "bom.item = {0}".format(frappe.db.escape(template_item)) if template_item else bom_item
+ template_item = frappe.db.get_value("Item", self.item_code, ["variant_of"])
+ bom_item = (
+ "bom.item = {0}".format(frappe.db.escape(template_item)) if template_item else bom_item
+ )
return bom_item
def get_so_items(self):
@@ -167,11 +178,12 @@
item_condition = ""
bom_item = "bom.item = so_item.item_code"
- if self.item_code and frappe.db.exists('Item', self.item_code):
+ if self.item_code and frappe.db.exists("Item", self.item_code):
bom_item = self.get_bom_item() or bom_item
- item_condition = ' and so_item.item_code = {0}'.format(frappe.db.escape(self.item_code))
+ item_condition = " and so_item.item_code = {0}".format(frappe.db.escape(self.item_code))
- items = frappe.db.sql("""
+ items = frappe.db.sql(
+ """
select
distinct parent, item_code, warehouse,
(qty - work_order_qty) * conversion_factor as pending_qty,
@@ -181,16 +193,17 @@
where
parent in (%s) and docstatus = 1 and qty > work_order_qty
and exists (select name from `tabBOM` bom where %s
- and bom.is_active = 1) %s""" %
- (", ".join(["%s"] * len(so_list)),
- bom_item,
- item_condition),
- tuple(so_list), as_dict=1)
+ and bom.is_active = 1) %s"""
+ % (", ".join(["%s"] * len(so_list)), bom_item, item_condition),
+ tuple(so_list),
+ as_dict=1,
+ )
if self.item_code:
- item_condition = ' and so_item.item_code = {0}'.format(frappe.db.escape(self.item_code))
+ item_condition = " and so_item.item_code = {0}".format(frappe.db.escape(self.item_code))
- packed_items = frappe.db.sql("""select distinct pi.parent, pi.item_code, pi.warehouse as warehouse,
+ packed_items = frappe.db.sql(
+ """select distinct pi.parent, pi.item_code, pi.warehouse as warehouse,
(((so_item.qty - so_item.work_order_qty) * pi.qty) / so_item.qty)
as pending_qty, pi.parent_item, pi.description, so_item.name
from `tabSales Order Item` so_item, `tabPacked Item` pi
@@ -198,16 +211,23 @@
and pi.parent_item = so_item.item_code
and so_item.parent in (%s) and so_item.qty > so_item.work_order_qty
and exists (select name from `tabBOM` bom where bom.item=pi.item_code
- and bom.is_active = 1) %s""" % \
- (", ".join(["%s"] * len(so_list)), item_condition), tuple(so_list), as_dict=1)
+ and bom.is_active = 1) %s"""
+ % (", ".join(["%s"] * len(so_list)), item_condition),
+ tuple(so_list),
+ as_dict=1,
+ )
self.add_items(items + packed_items)
self.calculate_total_planned_qty()
def get_mr_items(self):
# Check for empty table or empty rows
- if not self.get("material_requests") or not self.get_so_mr_list("material_request", "material_requests"):
- frappe.throw(_("Please fill the Material Requests table"), title=_("Material Requests Required"))
+ if not self.get("material_requests") or not self.get_so_mr_list(
+ "material_request", "material_requests"
+ ):
+ frappe.throw(
+ _("Please fill the Material Requests table"), title=_("Material Requests Required")
+ )
mr_list = self.get_so_mr_list("material_request", "material_requests")
@@ -215,13 +235,17 @@
if self.item_code:
item_condition = " and mr_item.item_code ={0}".format(frappe.db.escape(self.item_code))
- items = frappe.db.sql("""select distinct parent, name, item_code, warehouse, description,
+ items = frappe.db.sql(
+ """select distinct parent, name, item_code, warehouse, description,
(qty - ordered_qty) * conversion_factor as pending_qty
from `tabMaterial Request Item` mr_item
where parent in (%s) and docstatus = 1 and qty > ordered_qty
and exists (select name from `tabBOM` bom where bom.item=mr_item.item_code
- and bom.is_active = 1) %s""" % \
- (", ".join(["%s"] * len(mr_list)), item_condition), tuple(mr_list), as_dict=1)
+ and bom.is_active = 1) %s"""
+ % (", ".join(["%s"] * len(mr_list)), item_condition),
+ tuple(mr_list),
+ as_dict=1,
+ )
self.add_items(items)
self.calculate_total_planned_qty()
@@ -232,37 +256,36 @@
item_details = get_item_details(data.item_code)
if self.combine_items:
if item_details.bom_no in refs:
- refs[item_details.bom_no]['so_details'].append({
- 'sales_order': data.parent,
- 'sales_order_item': data.name,
- 'qty': data.pending_qty
- })
- refs[item_details.bom_no]['qty'] += data.pending_qty
+ refs[item_details.bom_no]["so_details"].append(
+ {"sales_order": data.parent, "sales_order_item": data.name, "qty": data.pending_qty}
+ )
+ refs[item_details.bom_no]["qty"] += data.pending_qty
continue
else:
refs[item_details.bom_no] = {
- 'qty': data.pending_qty,
- 'po_item_ref': data.name,
- 'so_details': []
+ "qty": data.pending_qty,
+ "po_item_ref": data.name,
+ "so_details": [],
}
- refs[item_details.bom_no]['so_details'].append({
- 'sales_order': data.parent,
- 'sales_order_item': data.name,
- 'qty': data.pending_qty
- })
+ refs[item_details.bom_no]["so_details"].append(
+ {"sales_order": data.parent, "sales_order_item": data.name, "qty": data.pending_qty}
+ )
- pi = self.append('po_items', {
- 'warehouse': data.warehouse,
- 'item_code': data.item_code,
- 'description': data.description or item_details.description,
- 'stock_uom': item_details and item_details.stock_uom or '',
- 'bom_no': item_details and item_details.bom_no or '',
- 'planned_qty': data.pending_qty,
- 'pending_qty': data.pending_qty,
- 'planned_start_date': now_datetime(),
- 'product_bundle_item': data.parent_item
- })
+ pi = self.append(
+ "po_items",
+ {
+ "warehouse": data.warehouse,
+ "item_code": data.item_code,
+ "description": data.description or item_details.description,
+ "stock_uom": item_details and item_details.stock_uom or "",
+ "bom_no": item_details and item_details.bom_no or "",
+ "planned_qty": data.pending_qty,
+ "pending_qty": data.pending_qty,
+ "planned_start_date": now_datetime(),
+ "product_bundle_item": data.parent_item,
+ },
+ )
pi._set_defaults()
if self.get_items_from == "Sales Order":
@@ -277,20 +300,23 @@
if refs:
for po_item in self.po_items:
- po_item.planned_qty = refs[po_item.bom_no]['qty']
- po_item.pending_qty = refs[po_item.bom_no]['qty']
- po_item.sales_order = ''
+ po_item.planned_qty = refs[po_item.bom_no]["qty"]
+ po_item.pending_qty = refs[po_item.bom_no]["qty"]
+ po_item.sales_order = ""
self.add_pp_ref(refs)
def add_pp_ref(self, refs):
for bom_no in refs:
- for so_detail in refs[bom_no]['so_details']:
- self.append('prod_plan_references', {
- 'item_reference': refs[bom_no]['po_item_ref'],
- 'sales_order': so_detail['sales_order'],
- 'sales_order_item': so_detail['sales_order_item'],
- 'qty': so_detail['qty']
- })
+ for so_detail in refs[bom_no]["so_details"]:
+ self.append(
+ "prod_plan_references",
+ {
+ "item_reference": refs[bom_no]["po_item_ref"],
+ "sales_order": so_detail["sales_order"],
+ "sales_order_item": so_detail["sales_order_item"],
+ "qty": so_detail["qty"],
+ },
+ )
def calculate_total_produced_qty(self):
self.total_produced_qty = 0
@@ -308,27 +334,24 @@
self.calculate_total_produced_qty()
self.set_status()
- self.db_set('status', self.status)
+ self.db_set("status", self.status)
def on_cancel(self):
- self.db_set('status', 'Cancelled')
+ self.db_set("status", "Cancelled")
self.delete_draft_work_order()
def delete_draft_work_order(self):
- for d in frappe.get_all('Work Order', fields = ["name"],
- filters = {'docstatus': 0, 'production_plan': ("=", self.name)}):
- frappe.delete_doc('Work Order', d.name)
+ for d in frappe.get_all(
+ "Work Order", fields=["name"], filters={"docstatus": 0, "production_plan": ("=", self.name)}
+ ):
+ frappe.delete_doc("Work Order", d.name)
@frappe.whitelist()
def set_status(self, close=None):
- self.status = {
- 0: 'Draft',
- 1: 'Submitted',
- 2: 'Cancelled'
- }.get(self.docstatus)
+ self.status = {0: "Draft", 1: "Submitted", 2: "Cancelled"}.get(self.docstatus)
if close:
- self.db_set('status', 'Closed')
+ self.db_set("status", "Closed")
return
if self.total_produced_qty > 0:
@@ -336,12 +359,12 @@
if self.all_items_completed():
self.status = "Completed"
- if self.status != 'Completed':
+ if self.status != "Completed":
self.update_ordered_status()
self.update_requested_status()
if close is not None:
- self.db_set('status', self.status)
+ self.db_set("status", self.status)
def update_ordered_status(self):
update_status = False
@@ -349,8 +372,8 @@
if d.planned_qty == d.ordered_qty:
update_status = True
- if update_status and self.status != 'Completed':
- self.status = 'In Process'
+ if update_status and self.status != "Completed":
+ self.status = "In Process"
def update_requested_status(self):
if not self.mr_items:
@@ -362,44 +385,44 @@
update_status = False
if update_status:
- self.status = 'Material Requested'
+ self.status = "Material Requested"
def get_production_items(self):
item_dict = {}
for d in self.po_items:
item_details = {
- "production_item" : d.item_code,
- "use_multi_level_bom" : d.include_exploded_items,
- "sales_order" : d.sales_order,
- "sales_order_item" : d.sales_order_item,
- "material_request" : d.material_request,
- "material_request_item" : d.material_request_item,
- "bom_no" : d.bom_no,
- "description" : d.description,
- "stock_uom" : d.stock_uom,
- "company" : self.company,
- "fg_warehouse" : d.warehouse,
- "production_plan" : self.name,
- "production_plan_item" : d.name,
- "product_bundle_item" : d.product_bundle_item,
- "planned_start_date" : d.planned_start_date,
- "project" : self.project
+ "production_item": d.item_code,
+ "use_multi_level_bom": d.include_exploded_items,
+ "sales_order": d.sales_order,
+ "sales_order_item": d.sales_order_item,
+ "material_request": d.material_request,
+ "material_request_item": d.material_request_item,
+ "bom_no": d.bom_no,
+ "description": d.description,
+ "stock_uom": d.stock_uom,
+ "company": self.company,
+ "fg_warehouse": d.warehouse,
+ "production_plan": self.name,
+ "production_plan_item": d.name,
+ "product_bundle_item": d.product_bundle_item,
+ "planned_start_date": d.planned_start_date,
+ "project": self.project,
}
- if not item_details['project'] and d.sales_order:
- item_details['project'] = frappe.get_cached_value("Sales Order", d.sales_order, "project")
+ if not item_details["project"] and d.sales_order:
+ item_details["project"] = frappe.get_cached_value("Sales Order", d.sales_order, "project")
if self.get_items_from == "Material Request":
- item_details.update({
- "qty": d.planned_qty
- })
+ item_details.update({"qty": d.planned_qty})
item_dict[(d.item_code, d.material_request_item, d.warehouse)] = item_details
else:
- item_details.update({
- "qty": flt(item_dict.get((d.item_code, d.sales_order, d.warehouse),{})
- .get("qty")) + (flt(d.planned_qty) - flt(d.ordered_qty))
- })
+ item_details.update(
+ {
+ "qty": flt(item_dict.get((d.item_code, d.sales_order, d.warehouse), {}).get("qty"))
+ + (flt(d.planned_qty) - flt(d.ordered_qty))
+ }
+ )
item_dict[(d.item_code, d.sales_order, d.warehouse)] = item_details
return item_dict
@@ -415,15 +438,15 @@
self.make_work_order_for_finished_goods(wo_list, default_warehouses)
self.make_work_order_for_subassembly_items(wo_list, subcontracted_po, default_warehouses)
self.make_subcontracted_purchase_order(subcontracted_po, po_list)
- self.show_list_created_message('Work Order', wo_list)
- self.show_list_created_message('Purchase Order', po_list)
+ self.show_list_created_message("Work Order", wo_list)
+ self.show_list_created_message("Purchase Order", po_list)
def make_work_order_for_finished_goods(self, wo_list, default_warehouses):
items_data = self.get_production_items()
for key, item in items_data.items():
if self.sub_assembly_items:
- item['use_multi_level_bom'] = 0
+ item["use_multi_level_bom"] = 0
set_default_warehouses(item, default_warehouses)
work_order = self.create_work_order(item)
@@ -432,13 +455,13 @@
def make_work_order_for_subassembly_items(self, wo_list, subcontracted_po, default_warehouses):
for row in self.sub_assembly_items:
- if row.type_of_manufacturing == 'Subcontract':
+ if row.type_of_manufacturing == "Subcontract":
subcontracted_po.setdefault(row.supplier, []).append(row)
continue
work_order_data = {
- 'wip_warehouse': default_warehouses.get('wip_warehouse'),
- 'fg_warehouse': default_warehouses.get('fg_warehouse')
+ "wip_warehouse": default_warehouses.get("wip_warehouse"),
+ "fg_warehouse": default_warehouses.get("fg_warehouse"),
}
self.prepare_data_for_sub_assembly_items(row, work_order_data)
@@ -447,41 +470,59 @@
wo_list.append(work_order)
def prepare_data_for_sub_assembly_items(self, row, wo_data):
- for field in ["production_item", "item_name", "qty", "fg_warehouse",
- "description", "bom_no", "stock_uom", "bom_level",
- "production_plan_item", "schedule_date"]:
+ for field in [
+ "production_item",
+ "item_name",
+ "qty",
+ "fg_warehouse",
+ "description",
+ "bom_no",
+ "stock_uom",
+ "bom_level",
+ "production_plan_item",
+ "schedule_date",
+ ]:
if row.get(field):
wo_data[field] = row.get(field)
- wo_data.update({
- "use_multi_level_bom": 0,
- "production_plan": self.name,
- "production_plan_sub_assembly_item": row.name
- })
+ wo_data.update(
+ {
+ "use_multi_level_bom": 0,
+ "production_plan": self.name,
+ "production_plan_sub_assembly_item": row.name,
+ }
+ )
def make_subcontracted_purchase_order(self, subcontracted_po, purchase_orders):
if not subcontracted_po:
return
for supplier, po_list in subcontracted_po.items():
- po = frappe.new_doc('Purchase Order')
+ po = frappe.new_doc("Purchase Order")
po.supplier = supplier
po.schedule_date = getdate(po_list[0].schedule_date) if po_list[0].schedule_date else nowdate()
- po.is_subcontracted = 'Yes'
+ po.is_subcontracted = "Yes"
for row in po_list:
po_data = {
- 'item_code': row.production_item,
- 'warehouse': row.fg_warehouse,
- 'production_plan_sub_assembly_item': row.name,
- 'bom': row.bom_no,
- 'production_plan': self.name
+ "item_code": row.production_item,
+ "warehouse": row.fg_warehouse,
+ "production_plan_sub_assembly_item": row.name,
+ "bom": row.bom_no,
+ "production_plan": self.name,
}
- for field in ['schedule_date', 'qty', 'uom', 'stock_uom', 'item_name',
- 'description', 'production_plan_item']:
+ for field in [
+ "schedule_date",
+ "qty",
+ "uom",
+ "stock_uom",
+ "item_name",
+ "description",
+ "production_plan_item",
+ ]:
po_data[field] = row.get(field)
- po.append('items', po_data)
+ po.append("items", po_data)
po.set_missing_values()
po.flags.ignore_mandatory = True
@@ -503,7 +544,7 @@
wo = frappe.new_doc("Work Order")
wo.update(item)
- wo.planned_start_date = item.get('planned_start_date') or item.get('schedule_date')
+ wo.planned_start_date = item.get("planned_start_date") or item.get("schedule_date")
if item.get("warehouse"):
wo.fg_warehouse = item.get("warehouse")
@@ -521,54 +562,60 @@
@frappe.whitelist()
def make_material_request(self):
- '''Create Material Requests grouped by Sales Order and Material Request Type'''
+ """Create Material Requests grouped by Sales Order and Material Request Type"""
material_request_list = []
material_request_map = {}
for item in self.mr_items:
- item_doc = frappe.get_cached_doc('Item', item.item_code)
+ item_doc = frappe.get_cached_doc("Item", item.item_code)
material_request_type = item.material_request_type or item_doc.default_material_request_type
# key for Sales Order:Material Request Type:Customer
- key = '{}:{}:{}'.format(item.sales_order, material_request_type, item_doc.customer or '')
+ key = "{}:{}:{}".format(item.sales_order, material_request_type, item_doc.customer or "")
schedule_date = add_days(nowdate(), cint(item_doc.lead_time_days))
if not key in material_request_map:
# make a new MR for the combination
material_request_map[key] = frappe.new_doc("Material Request")
material_request = material_request_map[key]
- material_request.update({
- "transaction_date": nowdate(),
- "status": "Draft",
- "company": self.company,
- 'material_request_type': material_request_type,
- 'customer': item_doc.customer or ''
- })
+ material_request.update(
+ {
+ "transaction_date": nowdate(),
+ "status": "Draft",
+ "company": self.company,
+ "material_request_type": material_request_type,
+ "customer": item_doc.customer or "",
+ }
+ )
material_request_list.append(material_request)
else:
material_request = material_request_map[key]
# add item
- material_request.append("items", {
- "item_code": item.item_code,
- "from_warehouse": item.from_warehouse,
- "qty": item.quantity,
- "schedule_date": schedule_date,
- "warehouse": item.warehouse,
- "sales_order": item.sales_order,
- 'production_plan': self.name,
- 'material_request_plan_item': item.name,
- "project": frappe.db.get_value("Sales Order", item.sales_order, "project") \
- if item.sales_order else None
- })
+ material_request.append(
+ "items",
+ {
+ "item_code": item.item_code,
+ "from_warehouse": item.from_warehouse,
+ "qty": item.quantity,
+ "schedule_date": schedule_date,
+ "warehouse": item.warehouse,
+ "sales_order": item.sales_order,
+ "production_plan": self.name,
+ "material_request_plan_item": item.name,
+ "project": frappe.db.get_value("Sales Order", item.sales_order, "project")
+ if item.sales_order
+ else None,
+ },
+ )
for material_request in material_request_list:
# submit
material_request.flags.ignore_permissions = 1
material_request.run_method("set_missing_values")
- if self.get('submit_material_request'):
+ if self.get("submit_material_request"):
material_request.submit()
else:
material_request.save()
@@ -576,17 +623,19 @@
frappe.flags.mute_messages = False
if material_request_list:
- material_request_list = ["""<a href="/app/Form/Material Request/{0}">{1}</a>""".format(m.name, m.name) \
- for m in material_request_list]
+ material_request_list = [
+ """<a href="/app/Form/Material Request/{0}">{1}</a>""".format(m.name, m.name)
+ for m in material_request_list
+ ]
msgprint(_("{0} created").format(comma_and(material_request_list)))
- else :
+ else:
msgprint(_("No material request created"))
@frappe.whitelist()
def get_sub_assembly_items(self, manufacturing_type=None):
"Fetch sub assembly items and optionally combine them."
self.sub_assembly_items = []
- sub_assembly_items_store = [] # temporary store to process all subassembly items
+ sub_assembly_items_store = [] # temporary store to process all subassembly items
for row in self.po_items:
bom_data = []
@@ -598,7 +647,7 @@
# Combine subassembly items
sub_assembly_items_store = self.combine_subassembly_items(sub_assembly_items_store)
- sub_assembly_items_store.sort(key= lambda d: d.bom_level, reverse=True) # sort by bom level
+ sub_assembly_items_store.sort(key=lambda d: d.bom_level, reverse=True) # sort by bom level
for idx, row in enumerate(sub_assembly_items_store):
row.idx = idx + 1
@@ -611,16 +660,19 @@
data.production_plan_item = row.name
data.fg_warehouse = row.warehouse
data.schedule_date = row.planned_start_date
- data.type_of_manufacturing = manufacturing_type or ("Subcontract" if data.is_sub_contracted_item
- else "In House")
+ data.type_of_manufacturing = manufacturing_type or (
+ "Subcontract" if data.is_sub_contracted_item else "In House"
+ )
def combine_subassembly_items(self, sub_assembly_items_store):
"Aggregate if same: Item, Warehouse, Inhouse/Outhouse Manu.g, BOM No."
key_wise_data = {}
for row in sub_assembly_items_store:
key = (
- row.get("production_item"), row.get("fg_warehouse"),
- row.get("bom_no"), row.get("type_of_manufacturing")
+ row.get("production_item"),
+ row.get("fg_warehouse"),
+ row.get("bom_no"),
+ row.get("type_of_manufacturing"),
)
if key not in key_wise_data:
# intialise (item, wh, bom no, man.g type) wise dict
@@ -638,12 +690,15 @@
# add row with key
key_wise_data[key] = row
- sub_assembly_items_store = [key_wise_data[key] for key in key_wise_data] # unpack into single level list
+ sub_assembly_items_store = [
+ key_wise_data[key] for key in key_wise_data
+ ] # unpack into single level list
return sub_assembly_items_store
def all_items_completed(self):
- all_items_produced = all(flt(d.planned_qty) - flt(d.produced_qty) < 0.000001
- for d in self.po_items)
+ all_items_produced = all(
+ flt(d.planned_qty) - flt(d.produced_qty) < 0.000001 for d in self.po_items
+ )
if not all_items_produced:
return False
@@ -660,40 +715,81 @@
all_work_orders_completed = all(s == "Completed" for s in wo_status)
return all_work_orders_completed
+
@frappe.whitelist()
def download_raw_materials(doc, warehouses=None):
if isinstance(doc, str):
doc = frappe._dict(json.loads(doc))
- item_list = [['Item Code', 'Item Name', 'Description',
- 'Stock UOM', 'Warehouse', 'Required Qty as per BOM',
- 'Projected Qty', 'Available Qty In Hand', 'Ordered Qty', 'Planned Qty',
- 'Reserved Qty for Production', 'Safety Stock', 'Required Qty']]
+ item_list = [
+ [
+ "Item Code",
+ "Item Name",
+ "Description",
+ "Stock UOM",
+ "Warehouse",
+ "Required Qty as per BOM",
+ "Projected Qty",
+ "Available Qty In Hand",
+ "Ordered Qty",
+ "Planned Qty",
+ "Reserved Qty for Production",
+ "Safety Stock",
+ "Required Qty",
+ ]
+ ]
doc.warehouse = None
frappe.flags.show_qty_in_stock_uom = 1
- items = get_items_for_material_requests(doc, warehouses=warehouses, get_parent_warehouse_data=True)
+ items = get_items_for_material_requests(
+ doc, warehouses=warehouses, get_parent_warehouse_data=True
+ )
for d in items:
- item_list.append([d.get('item_code'), d.get('item_name'),
- d.get('description'), d.get('stock_uom'), d.get('warehouse'),
- d.get('required_bom_qty'), d.get('projected_qty'), d.get('actual_qty'), d.get('ordered_qty'),
- d.get('planned_qty'), d.get('reserved_qty_for_production'), d.get('safety_stock'), d.get('quantity')])
+ item_list.append(
+ [
+ d.get("item_code"),
+ d.get("item_name"),
+ d.get("description"),
+ d.get("stock_uom"),
+ d.get("warehouse"),
+ d.get("required_bom_qty"),
+ d.get("projected_qty"),
+ d.get("actual_qty"),
+ d.get("ordered_qty"),
+ d.get("planned_qty"),
+ d.get("reserved_qty_for_production"),
+ d.get("safety_stock"),
+ d.get("quantity"),
+ ]
+ )
- if not doc.get('for_warehouse'):
- row = {'item_code': d.get('item_code')}
+ if not doc.get("for_warehouse"):
+ row = {"item_code": d.get("item_code")}
for bin_dict in get_bin_details(row, doc.company, all_warehouse=True):
- if d.get("warehouse") == bin_dict.get('warehouse'):
+ if d.get("warehouse") == bin_dict.get("warehouse"):
continue
- item_list.append(['', '', '', bin_dict.get('warehouse'), '',
- bin_dict.get('projected_qty', 0), bin_dict.get('actual_qty', 0),
- bin_dict.get('ordered_qty', 0), bin_dict.get('reserved_qty_for_production', 0)])
+ item_list.append(
+ [
+ "",
+ "",
+ "",
+ bin_dict.get("warehouse"),
+ "",
+ bin_dict.get("projected_qty", 0),
+ bin_dict.get("actual_qty", 0),
+ bin_dict.get("ordered_qty", 0),
+ bin_dict.get("reserved_qty_for_production", 0),
+ ]
+ )
build_csv_response(item_list, doc.name)
+
def get_exploded_items(item_details, company, bom_no, include_non_stock_items, planned_qty=1):
- for d in frappe.db.sql("""select bei.item_code, item.default_bom as bom,
+ for d in frappe.db.sql(
+ """select bei.item_code, item.default_bom as bom,
ifnull(sum(bei.stock_qty/ifnull(bom.quantity, 1)), 0)*%s as qty, item.item_name,
bei.description, bei.stock_uom, item.min_order_qty, bei.source_warehouse,
item.default_material_request_type, item.min_order_qty, item_default.default_warehouse,
@@ -709,21 +805,38 @@
where
bei.docstatus < 2
and bom.name=%s and item.is_stock_item in (1, {0})
- group by bei.item_code, bei.stock_uom""".format(0 if include_non_stock_items else 1),
- (planned_qty, company, bom_no), as_dict=1):
+ group by bei.item_code, bei.stock_uom""".format(
+ 0 if include_non_stock_items else 1
+ ),
+ (planned_qty, company, bom_no),
+ as_dict=1,
+ ):
if not d.conversion_factor and d.purchase_uom:
d.conversion_factor = get_uom_conversion_factor(d.item_code, d.purchase_uom)
- item_details.setdefault(d.get('item_code'), d)
+ item_details.setdefault(d.get("item_code"), d)
return item_details
-def get_uom_conversion_factor(item_code, uom):
- return frappe.db.get_value('UOM Conversion Detail',
- {'parent': item_code, 'uom': uom}, 'conversion_factor')
-def get_subitems(doc, data, item_details, bom_no, company, include_non_stock_items,
- include_subcontracted_items, parent_qty, planned_qty=1):
- items = frappe.db.sql("""
+def get_uom_conversion_factor(item_code, uom):
+ return frappe.db.get_value(
+ "UOM Conversion Detail", {"parent": item_code, "uom": uom}, "conversion_factor"
+ )
+
+
+def get_subitems(
+ doc,
+ data,
+ item_details,
+ bom_no,
+ company,
+ include_non_stock_items,
+ include_subcontracted_items,
+ parent_qty,
+ planned_qty=1,
+):
+ items = frappe.db.sql(
+ """
SELECT
bom_item.item_code, default_material_request_type, item.item_name,
ifnull(%(parent_qty)s * sum(bom_item.stock_qty/ifnull(bom.quantity, 1)) * %(planned_qty)s, 0) as qty,
@@ -743,15 +856,15 @@
bom.name = %(bom)s
and bom_item.docstatus < 2
and item.is_stock_item in (1, {0})
- group by bom_item.item_code""".format(0 if include_non_stock_items else 1),{
- 'bom': bom_no,
- 'parent_qty': parent_qty,
- 'planned_qty': planned_qty,
- 'company': company
- }, as_dict=1)
+ group by bom_item.item_code""".format(
+ 0 if include_non_stock_items else 1
+ ),
+ {"bom": bom_no, "parent_qty": parent_qty, "planned_qty": planned_qty, "company": company},
+ as_dict=1,
+ )
for d in items:
- if not data.get('include_exploded_items') or not d.default_bom:
+ if not data.get("include_exploded_items") or not d.default_bom:
if d.item_code in item_details:
item_details[d.item_code].qty = item_details[d.item_code].qty + d.qty
else:
@@ -760,89 +873,107 @@
item_details[d.item_code] = d
- if data.get('include_exploded_items') and d.default_bom:
- if ((d.default_material_request_type in ["Manufacture", "Purchase"] and
- not d.is_sub_contracted) or (d.is_sub_contracted and include_subcontracted_items)):
+ if data.get("include_exploded_items") and d.default_bom:
+ if (
+ d.default_material_request_type in ["Manufacture", "Purchase"] and not d.is_sub_contracted
+ ) or (d.is_sub_contracted and include_subcontracted_items):
if d.qty > 0:
- get_subitems(doc, data, item_details, d.default_bom, company,
- include_non_stock_items, include_subcontracted_items, d.qty)
+ get_subitems(
+ doc,
+ data,
+ item_details,
+ d.default_bom,
+ company,
+ include_non_stock_items,
+ include_subcontracted_items,
+ d.qty,
+ )
return item_details
-def get_material_request_items(row, sales_order, company,
- ignore_existing_ordered_qty, include_safety_stock, warehouse, bin_dict):
- total_qty = row['qty']
+
+def get_material_request_items(
+ row, sales_order, company, ignore_existing_ordered_qty, include_safety_stock, warehouse, bin_dict
+):
+ total_qty = row["qty"]
required_qty = 0
if ignore_existing_ordered_qty or bin_dict.get("projected_qty", 0) < 0:
required_qty = total_qty
elif total_qty > bin_dict.get("projected_qty", 0):
required_qty = total_qty - bin_dict.get("projected_qty", 0)
- if required_qty > 0 and required_qty < row['min_order_qty']:
- required_qty = row['min_order_qty']
+ if required_qty > 0 and required_qty < row["min_order_qty"]:
+ required_qty = row["min_order_qty"]
item_group_defaults = get_item_group_defaults(row.item_code, company)
- if not row['purchase_uom']:
- row['purchase_uom'] = row['stock_uom']
+ if not row["purchase_uom"]:
+ row["purchase_uom"] = row["stock_uom"]
- if row['purchase_uom'] != row['stock_uom']:
- if not (row['conversion_factor'] or frappe.flags.show_qty_in_stock_uom):
- frappe.throw(_("UOM Conversion factor ({0} -> {1}) not found for item: {2}")
- .format(row['purchase_uom'], row['stock_uom'], row.item_code))
+ if row["purchase_uom"] != row["stock_uom"]:
+ if not (row["conversion_factor"] or frappe.flags.show_qty_in_stock_uom):
+ frappe.throw(
+ _("UOM Conversion factor ({0} -> {1}) not found for item: {2}").format(
+ row["purchase_uom"], row["stock_uom"], row.item_code
+ )
+ )
- required_qty = required_qty / row['conversion_factor']
+ required_qty = required_qty / row["conversion_factor"]
- if frappe.db.get_value("UOM", row['purchase_uom'], "must_be_whole_number"):
+ if frappe.db.get_value("UOM", row["purchase_uom"], "must_be_whole_number"):
required_qty = ceil(required_qty)
if include_safety_stock:
- required_qty += flt(row['safety_stock'])
+ required_qty += flt(row["safety_stock"])
if required_qty > 0:
return {
- 'item_code': row.item_code,
- 'item_name': row.item_name,
- 'quantity': required_qty,
- 'required_bom_qty': total_qty,
- 'stock_uom': row.get("stock_uom"),
- 'warehouse': warehouse or row.get('source_warehouse') \
- or row.get('default_warehouse') or item_group_defaults.get("default_warehouse"),
- 'safety_stock': row.safety_stock,
- 'actual_qty': bin_dict.get("actual_qty", 0),
- 'projected_qty': bin_dict.get("projected_qty", 0),
- 'ordered_qty': bin_dict.get("ordered_qty", 0),
- 'reserved_qty_for_production': bin_dict.get("reserved_qty_for_production", 0),
- 'min_order_qty': row['min_order_qty'],
- 'material_request_type': row.get("default_material_request_type"),
- 'sales_order': sales_order,
- 'description': row.get("description"),
- 'uom': row.get("purchase_uom") or row.get("stock_uom")
+ "item_code": row.item_code,
+ "item_name": row.item_name,
+ "quantity": required_qty,
+ "required_bom_qty": total_qty,
+ "stock_uom": row.get("stock_uom"),
+ "warehouse": warehouse
+ or row.get("source_warehouse")
+ or row.get("default_warehouse")
+ or item_group_defaults.get("default_warehouse"),
+ "safety_stock": row.safety_stock,
+ "actual_qty": bin_dict.get("actual_qty", 0),
+ "projected_qty": bin_dict.get("projected_qty", 0),
+ "ordered_qty": bin_dict.get("ordered_qty", 0),
+ "reserved_qty_for_production": bin_dict.get("reserved_qty_for_production", 0),
+ "min_order_qty": row["min_order_qty"],
+ "material_request_type": row.get("default_material_request_type"),
+ "sales_order": sales_order,
+ "description": row.get("description"),
+ "uom": row.get("purchase_uom") or row.get("stock_uom"),
}
+
def get_sales_orders(self):
so_filter = item_filter = ""
bom_item = "bom.item = so_item.item_code"
date_field_mapper = {
- 'from_date': ('>=', 'so.transaction_date'),
- 'to_date': ('<=', 'so.transaction_date'),
- 'from_delivery_date': ('>=', 'so_item.delivery_date'),
- 'to_delivery_date': ('<=', 'so_item.delivery_date')
+ "from_date": (">=", "so.transaction_date"),
+ "to_date": ("<=", "so.transaction_date"),
+ "from_delivery_date": (">=", "so_item.delivery_date"),
+ "to_delivery_date": ("<=", "so_item.delivery_date"),
}
for field, value in date_field_mapper.items():
if self.get(field):
so_filter += f" and {value[1]} {value[0]} %({field})s"
- for field in ['customer', 'project', 'sales_order_status']:
+ for field in ["customer", "project", "sales_order_status"]:
if self.get(field):
- so_field = 'status' if field == 'sales_order_status' else field
+ so_field = "status" if field == "sales_order_status" else field
so_filter += f" and so.{so_field} = %({field})s"
- if self.item_code and frappe.db.exists('Item', self.item_code):
+ if self.item_code and frappe.db.exists("Item", self.item_code):
bom_item = self.get_bom_item() or bom_item
item_filter += " and so_item.item_code = %(item_code)s"
- open_so = frappe.db.sql(f"""
+ open_so = frappe.db.sql(
+ f"""
select distinct so.name, so.transaction_date, so.customer, so.base_grand_total
from `tabSales Order` so, `tabSales Order Item` so_item
where so_item.parent = so.name
@@ -855,10 +986,14 @@
where pi.parent = so.name and pi.parent_item = so_item.item_code
and exists (select name from `tabBOM` bom where bom.item=pi.item_code
and bom.is_active = 1)))
- """, self.as_dict(), as_dict=1)
+ """,
+ self.as_dict(),
+ as_dict=1,
+ )
return open_so
+
@frappe.whitelist()
def get_bin_details(row, company, for_warehouse=None, all_warehouse=False):
if isinstance(row, str):
@@ -867,30 +1002,42 @@
company = frappe.db.escape(company)
conditions, warehouse = "", ""
- conditions = " and warehouse in (select name from `tabWarehouse` where company = {0})".format(company)
+ conditions = " and warehouse in (select name from `tabWarehouse` where company = {0})".format(
+ company
+ )
if not all_warehouse:
- warehouse = for_warehouse or row.get('source_warehouse') or row.get('default_warehouse')
+ warehouse = for_warehouse or row.get("source_warehouse") or row.get("default_warehouse")
if warehouse:
lft, rgt = frappe.db.get_value("Warehouse", warehouse, ["lft", "rgt"])
conditions = """ and warehouse in (select name from `tabWarehouse`
where lft >= {0} and rgt <= {1} and name=`tabBin`.warehouse and company = {2})
- """.format(lft, rgt, company)
+ """.format(
+ lft, rgt, company
+ )
- return frappe.db.sql(""" select ifnull(sum(projected_qty),0) as projected_qty,
+ return frappe.db.sql(
+ """ select ifnull(sum(projected_qty),0) as projected_qty,
ifnull(sum(actual_qty),0) as actual_qty, ifnull(sum(ordered_qty),0) as ordered_qty,
ifnull(sum(reserved_qty_for_production),0) as reserved_qty_for_production, warehouse,
ifnull(sum(planned_qty),0) as planned_qty
from `tabBin` where item_code = %(item_code)s {conditions}
group by item_code, warehouse
- """.format(conditions=conditions), { "item_code": row['item_code'] }, as_dict=1)
+ """.format(
+ conditions=conditions
+ ),
+ {"item_code": row["item_code"]},
+ as_dict=1,
+ )
+
@frappe.whitelist()
def get_so_details(sales_order):
- return frappe.db.get_value("Sales Order", sales_order,
- ['transaction_date', 'customer', 'grand_total'], as_dict=1
+ return frappe.db.get_value(
+ "Sales Order", sales_order, ["transaction_date", "customer", "grand_total"], as_dict=1
)
+
def get_warehouse_list(warehouses):
warehouse_list = []
@@ -898,7 +1045,7 @@
warehouses = json.loads(warehouses)
for row in warehouses:
- child_warehouses = frappe.db.get_descendants('Warehouse', row.get("warehouse"))
+ child_warehouses = frappe.db.get_descendants("Warehouse", row.get("warehouse"))
if child_warehouses:
warehouse_list.extend(child_warehouses)
else:
@@ -906,6 +1053,7 @@
return warehouse_list
+
@frappe.whitelist()
def get_items_for_material_requests(doc, warehouses=None, get_parent_warehouse_data=None):
if isinstance(doc, str):
@@ -914,73 +1062,92 @@
if warehouses:
warehouses = list(set(get_warehouse_list(warehouses)))
- if doc.get("for_warehouse") and not get_parent_warehouse_data and doc.get("for_warehouse") in warehouses:
+ if (
+ doc.get("for_warehouse")
+ and not get_parent_warehouse_data
+ and doc.get("for_warehouse") in warehouses
+ ):
warehouses.remove(doc.get("for_warehouse"))
- doc['mr_items'] = []
+ doc["mr_items"] = []
- po_items = doc.get('po_items') if doc.get('po_items') else doc.get('items')
+ po_items = doc.get("po_items") if doc.get("po_items") else doc.get("items")
# Check for empty table or empty rows
- if not po_items or not [row.get('item_code') for row in po_items if row.get('item_code')]:
- frappe.throw(_("Items to Manufacture are required to pull the Raw Materials associated with it."),
- title=_("Items Required"))
+ if not po_items or not [row.get("item_code") for row in po_items if row.get("item_code")]:
+ frappe.throw(
+ _("Items to Manufacture are required to pull the Raw Materials associated with it."),
+ title=_("Items Required"),
+ )
- company = doc.get('company')
- ignore_existing_ordered_qty = doc.get('ignore_existing_ordered_qty')
- include_safety_stock = doc.get('include_safety_stock')
+ company = doc.get("company")
+ ignore_existing_ordered_qty = doc.get("ignore_existing_ordered_qty")
+ include_safety_stock = doc.get("include_safety_stock")
so_item_details = frappe._dict()
for data in po_items:
if not data.get("include_exploded_items") and doc.get("sub_assembly_items"):
data["include_exploded_items"] = 1
- planned_qty = data.get('required_qty') or data.get('planned_qty')
- ignore_existing_ordered_qty = data.get('ignore_existing_ordered_qty') or ignore_existing_ordered_qty
- warehouse = doc.get('for_warehouse')
+ planned_qty = data.get("required_qty") or data.get("planned_qty")
+ ignore_existing_ordered_qty = (
+ data.get("ignore_existing_ordered_qty") or ignore_existing_ordered_qty
+ )
+ warehouse = doc.get("for_warehouse")
item_details = {}
if data.get("bom") or data.get("bom_no"):
- if data.get('required_qty'):
- bom_no = data.get('bom')
+ if data.get("required_qty"):
+ bom_no = data.get("bom")
include_non_stock_items = 1
- include_subcontracted_items = 1 if data.get('include_exploded_items') else 0
+ include_subcontracted_items = 1 if data.get("include_exploded_items") else 0
else:
- bom_no = data.get('bom_no')
- include_subcontracted_items = doc.get('include_subcontracted_items')
- include_non_stock_items = doc.get('include_non_stock_items')
+ bom_no = data.get("bom_no")
+ include_subcontracted_items = doc.get("include_subcontracted_items")
+ include_non_stock_items = doc.get("include_non_stock_items")
if not planned_qty:
- frappe.throw(_("For row {0}: Enter Planned Qty").format(data.get('idx')))
+ frappe.throw(_("For row {0}: Enter Planned Qty").format(data.get("idx")))
if bom_no:
- if data.get('include_exploded_items') and include_subcontracted_items:
+ if data.get("include_exploded_items") and include_subcontracted_items:
# fetch exploded items from BOM
- item_details = get_exploded_items(item_details,
- company, bom_no, include_non_stock_items, planned_qty=planned_qty)
+ item_details = get_exploded_items(
+ item_details, company, bom_no, include_non_stock_items, planned_qty=planned_qty
+ )
else:
- item_details = get_subitems(doc, data, item_details, bom_no, company,
- include_non_stock_items, include_subcontracted_items, 1, planned_qty=planned_qty)
- elif data.get('item_code'):
- item_master = frappe.get_doc('Item', data['item_code']).as_dict()
+ item_details = get_subitems(
+ doc,
+ data,
+ item_details,
+ bom_no,
+ company,
+ include_non_stock_items,
+ include_subcontracted_items,
+ 1,
+ planned_qty=planned_qty,
+ )
+ elif data.get("item_code"):
+ item_master = frappe.get_doc("Item", data["item_code"]).as_dict()
purchase_uom = item_master.purchase_uom or item_master.stock_uom
- conversion_factor = (get_uom_conversion_factor(item_master.name, purchase_uom)
- if item_master.purchase_uom else 1.0)
+ conversion_factor = (
+ get_uom_conversion_factor(item_master.name, purchase_uom) if item_master.purchase_uom else 1.0
+ )
item_details[item_master.name] = frappe._dict(
{
- 'item_name' : item_master.item_name,
- 'default_bom' : doc.bom,
- 'purchase_uom' : purchase_uom,
- 'default_warehouse': item_master.default_warehouse,
- 'min_order_qty' : item_master.min_order_qty,
- 'default_material_request_type' : item_master.default_material_request_type,
- 'qty': planned_qty or 1,
- 'is_sub_contracted' : item_master.is_subcontracted_item,
- 'item_code' : item_master.name,
- 'description' : item_master.description,
- 'stock_uom' : item_master.stock_uom,
- 'conversion_factor' : conversion_factor,
- 'safety_stock': item_master.safety_stock
+ "item_name": item_master.item_name,
+ "default_bom": doc.bom,
+ "purchase_uom": purchase_uom,
+ "default_warehouse": item_master.default_warehouse,
+ "min_order_qty": item_master.min_order_qty,
+ "default_material_request_type": item_master.default_material_request_type,
+ "qty": planned_qty or 1,
+ "is_sub_contracted": item_master.is_subcontracted_item,
+ "item_code": item_master.name,
+ "description": item_master.description,
+ "stock_uom": item_master.stock_uom,
+ "conversion_factor": conversion_factor,
+ "safety_stock": item_master.safety_stock,
}
)
@@ -989,7 +1156,9 @@
for item_code, details in item_details.items():
so_item_details.setdefault(sales_order, frappe._dict())
if item_code in so_item_details.get(sales_order, {}):
- so_item_details[sales_order][item_code]['qty'] = so_item_details[sales_order][item_code].get("qty", 0) + flt(details.qty)
+ so_item_details[sales_order][item_code]["qty"] = so_item_details[sales_order][item_code].get(
+ "qty", 0
+ ) + flt(details.qty)
else:
so_item_details[sales_order][item_code] = details
@@ -1001,8 +1170,15 @@
bin_dict = bin_dict[0] if bin_dict else {}
if details.qty > 0:
- items = get_material_request_items(details, sales_order, company,
- ignore_existing_ordered_qty, include_safety_stock, warehouse, bin_dict)
+ items = get_material_request_items(
+ details,
+ sales_order,
+ company,
+ ignore_existing_ordered_qty,
+ include_safety_stock,
+ warehouse,
+ bin_dict,
+ )
if items:
mr_items.append(items)
@@ -1015,18 +1191,26 @@
if not mr_items:
to_enable = frappe.bold(_("Ignore Existing Projected Quantity"))
- warehouse = frappe.bold(doc.get('for_warehouse'))
- message = _("As there are sufficient raw materials, Material Request is not required for Warehouse {0}.").format(warehouse) + "<br><br>"
+ warehouse = frappe.bold(doc.get("for_warehouse"))
+ message = (
+ _(
+ "As there are sufficient raw materials, Material Request is not required for Warehouse {0}."
+ ).format(warehouse)
+ + "<br><br>"
+ )
message += _("If you still want to proceed, please enable {0}.").format(to_enable)
frappe.msgprint(message, title=_("Note"))
return mr_items
+
def get_materials_from_other_locations(item, warehouses, new_mr_items, company):
from erpnext.stock.doctype.pick_list.pick_list import get_available_item_locations
- locations = get_available_item_locations(item.get("item_code"),
- warehouses, item.get("quantity"), company, ignore_validation=True)
+
+ locations = get_available_item_locations(
+ item.get("item_code"), warehouses, item.get("quantity"), company, ignore_validation=True
+ )
required_qty = item.get("quantity")
# get available material by transferring to production warehouse
@@ -1037,12 +1221,14 @@
new_dict = copy.deepcopy(item)
quantity = required_qty if d.get("qty") > required_qty else d.get("qty")
- new_dict.update({
- "quantity": quantity,
- "material_request_type": "Material Transfer",
- "uom": new_dict.get("stock_uom"), # internal transfer should be in stock UOM
- "from_warehouse": d.get("warehouse")
- })
+ new_dict.update(
+ {
+ "quantity": quantity,
+ "material_request_type": "Material Transfer",
+ "uom": new_dict.get("stock_uom"), # internal transfer should be in stock UOM
+ "from_warehouse": d.get("warehouse"),
+ }
+ )
required_qty -= quantity
new_mr_items.append(new_dict)
@@ -1050,16 +1236,17 @@
# raise purchase request for remaining qty
if required_qty:
stock_uom, purchase_uom = frappe.db.get_value(
- 'Item',
- item['item_code'],
- ['stock_uom', 'purchase_uom']
+ "Item", item["item_code"], ["stock_uom", "purchase_uom"]
)
- if purchase_uom != stock_uom and purchase_uom == item['uom']:
- conversion_factor = get_uom_conversion_factor(item['item_code'], item['uom'])
+ if purchase_uom != stock_uom and purchase_uom == item["uom"]:
+ conversion_factor = get_uom_conversion_factor(item["item_code"], item["uom"])
if not (conversion_factor or frappe.flags.show_qty_in_stock_uom):
- frappe.throw(_("UOM Conversion factor ({0} -> {1}) not found for item: {2}")
- .format(purchase_uom, stock_uom, item['item_code']))
+ frappe.throw(
+ _("UOM Conversion factor ({0} -> {1}) not found for item: {2}").format(
+ purchase_uom, stock_uom, item["item_code"]
+ )
+ )
required_qty = required_qty / conversion_factor
@@ -1070,6 +1257,7 @@
new_mr_items.append(item)
+
@frappe.whitelist()
def get_item_data(item_code):
item_details = get_item_details(item_code)
@@ -1077,33 +1265,39 @@
return {
"bom_no": item_details.get("bom_no"),
"stock_uom": item_details.get("stock_uom")
-# "description": item_details.get("description")
+ # "description": item_details.get("description")
}
+
def get_sub_assembly_items(bom_no, bom_data, to_produce_qty, indent=0):
data = get_bom_children(parent=bom_no)
for d in data:
if d.expandable:
parent_item_code = frappe.get_cached_value("BOM", bom_no, "item")
stock_qty = (d.stock_qty / d.parent_bom_qty) * flt(to_produce_qty)
- bom_data.append(frappe._dict({
- 'parent_item_code': parent_item_code,
- 'description': d.description,
- 'production_item': d.item_code,
- 'item_name': d.item_name,
- 'stock_uom': d.stock_uom,
- 'uom': d.stock_uom,
- 'bom_no': d.value,
- 'is_sub_contracted_item': d.is_sub_contracted_item,
- 'bom_level': indent,
- 'indent': indent,
- 'stock_qty': stock_qty
- }))
+ bom_data.append(
+ frappe._dict(
+ {
+ "parent_item_code": parent_item_code,
+ "description": d.description,
+ "production_item": d.item_code,
+ "item_name": d.item_name,
+ "stock_uom": d.stock_uom,
+ "uom": d.stock_uom,
+ "bom_no": d.value,
+ "is_sub_contracted_item": d.is_sub_contracted_item,
+ "bom_level": indent,
+ "indent": indent,
+ "stock_qty": stock_qty,
+ }
+ )
+ )
if d.value:
- get_sub_assembly_items(d.value, bom_data, stock_qty, indent=indent+1)
+ get_sub_assembly_items(d.value, bom_data, stock_qty, indent=indent + 1)
+
def set_default_warehouses(row, default_warehouses):
- for field in ['wip_warehouse', 'fg_warehouse']:
+ for field in ["wip_warehouse", "fg_warehouse"]:
if not row.get(field):
row[field] = default_warehouses.get(field)
diff --git a/erpnext/manufacturing/doctype/production_plan/production_plan_dashboard.py b/erpnext/manufacturing/doctype/production_plan/production_plan_dashboard.py
index e13f042..6fc28a3 100644
--- a/erpnext/manufacturing/doctype/production_plan/production_plan_dashboard.py
+++ b/erpnext/manufacturing/doctype/production_plan/production_plan_dashboard.py
@@ -3,15 +3,9 @@
def get_data():
return {
- 'fieldname': 'production_plan',
- 'transactions': [
- {
- 'label': _('Transactions'),
- 'items': ['Work Order', 'Material Request']
- },
- {
- 'label': _('Subcontract'),
- 'items': ['Purchase Order']
- },
- ]
+ "fieldname": "production_plan",
+ "transactions": [
+ {"label": _("Transactions"), "items": ["Work Order", "Material Request"]},
+ {"label": _("Subcontract"), "items": ["Purchase Order"]},
+ ],
}
diff --git a/erpnext/manufacturing/doctype/production_plan/test_production_plan.py b/erpnext/manufacturing/doctype/production_plan/test_production_plan.py
index ec49703..891a497 100644
--- a/erpnext/manufacturing/doctype/production_plan/test_production_plan.py
+++ b/erpnext/manufacturing/doctype/production_plan/test_production_plan.py
@@ -21,80 +21,88 @@
class TestProductionPlan(FrappeTestCase):
def setUp(self):
- for item in ['Test Production Item 1', 'Subassembly Item 1',
- 'Raw Material Item 1', 'Raw Material Item 2']:
+ for item in [
+ "Test Production Item 1",
+ "Subassembly Item 1",
+ "Raw Material Item 1",
+ "Raw Material Item 2",
+ ]:
create_item(item, valuation_rate=100)
- sr = frappe.db.get_value('Stock Reconciliation Item',
- {'item_code': item, 'docstatus': 1}, 'parent')
+ sr = frappe.db.get_value(
+ "Stock Reconciliation Item", {"item_code": item, "docstatus": 1}, "parent"
+ )
if sr:
- sr_doc = frappe.get_doc('Stock Reconciliation', sr)
+ sr_doc = frappe.get_doc("Stock Reconciliation", sr)
sr_doc.cancel()
- create_item('Test Non Stock Raw Material', is_stock_item=0)
- for item, raw_materials in {'Subassembly Item 1': ['Raw Material Item 1', 'Raw Material Item 2'],
- 'Test Production Item 1': ['Raw Material Item 1', 'Subassembly Item 1',
- 'Test Non Stock Raw Material']}.items():
- if not frappe.db.get_value('BOM', {'item': item}):
- make_bom(item = item, raw_materials = raw_materials)
+ create_item("Test Non Stock Raw Material", is_stock_item=0)
+ for item, raw_materials in {
+ "Subassembly Item 1": ["Raw Material Item 1", "Raw Material Item 2"],
+ "Test Production Item 1": [
+ "Raw Material Item 1",
+ "Subassembly Item 1",
+ "Test Non Stock Raw Material",
+ ],
+ }.items():
+ if not frappe.db.get_value("BOM", {"item": item}):
+ make_bom(item=item, raw_materials=raw_materials)
def tearDown(self) -> None:
frappe.db.rollback()
def test_production_plan_mr_creation(self):
"Test if MRs are created for unavailable raw materials."
- pln = create_production_plan(item_code='Test Production Item 1')
+ pln = create_production_plan(item_code="Test Production Item 1")
self.assertTrue(len(pln.mr_items), 2)
pln.make_material_request()
pln.reload()
- self.assertTrue(pln.status, 'Material Requested')
+ self.assertTrue(pln.status, "Material Requested")
material_requests = frappe.get_all(
- 'Material Request Item',
- fields = ['distinct parent'],
- filters = {'production_plan': pln.name},
- as_list=1
+ "Material Request Item",
+ fields=["distinct parent"],
+ filters={"production_plan": pln.name},
+ as_list=1,
)
self.assertTrue(len(material_requests), 2)
pln.make_work_order()
- work_orders = frappe.get_all('Work Order', fields = ['name'],
- filters = {'production_plan': pln.name}, as_list=1)
+ work_orders = frappe.get_all(
+ "Work Order", fields=["name"], filters={"production_plan": pln.name}, as_list=1
+ )
self.assertTrue(len(work_orders), len(pln.po_items))
for name in material_requests:
- mr = frappe.get_doc('Material Request', name[0])
+ mr = frappe.get_doc("Material Request", name[0])
if mr.docstatus != 0:
mr.cancel()
for name in work_orders:
- mr = frappe.delete_doc('Work Order', name[0])
+ mr = frappe.delete_doc("Work Order", name[0])
- pln = frappe.get_doc('Production Plan', pln.name)
+ pln = frappe.get_doc("Production Plan", pln.name)
pln.cancel()
def test_production_plan_start_date(self):
"Test if Work Order has same Planned Start Date as Prod Plan."
planned_date = add_to_date(date=None, days=3)
plan = create_production_plan(
- item_code='Test Production Item 1',
- planned_start_date=planned_date
+ item_code="Test Production Item 1", planned_start_date=planned_date
)
plan.make_work_order()
work_orders = frappe.get_all(
- 'Work Order',
- fields = ['name', 'planned_start_date'],
- filters = {'production_plan': plan.name}
+ "Work Order", fields=["name", "planned_start_date"], filters={"production_plan": plan.name}
)
self.assertEqual(work_orders[0].planned_start_date, planned_date)
for wo in work_orders:
- frappe.delete_doc('Work Order', wo.name)
+ frappe.delete_doc("Work Order", wo.name)
plan.reload()
plan.cancel()
@@ -104,15 +112,14 @@
- Enable 'ignore_existing_ordered_qty'.
- Test if MR Planning table pulls Raw Material Qty even if it is in stock.
"""
- sr1 = create_stock_reconciliation(item_code="Raw Material Item 1",
- target="_Test Warehouse - _TC", qty=1, rate=110)
- sr2 = create_stock_reconciliation(item_code="Raw Material Item 2",
- target="_Test Warehouse - _TC", qty=1, rate=120)
-
- pln = create_production_plan(
- item_code='Test Production Item 1',
- ignore_existing_ordered_qty=1
+ sr1 = create_stock_reconciliation(
+ item_code="Raw Material Item 1", target="_Test Warehouse - _TC", qty=1, rate=110
)
+ sr2 = create_stock_reconciliation(
+ item_code="Raw Material Item 2", target="_Test Warehouse - _TC", qty=1, rate=120
+ )
+
+ pln = create_production_plan(item_code="Test Production Item 1", ignore_existing_ordered_qty=1)
self.assertTrue(len(pln.mr_items))
self.assertTrue(flt(pln.mr_items[0].quantity), 1.0)
@@ -122,19 +129,13 @@
def test_production_plan_with_non_stock_item(self):
"Test if MR Planning table includes Non Stock RM."
- pln = create_production_plan(
- item_code='Test Production Item 1',
- include_non_stock_items=1
- )
+ pln = create_production_plan(item_code="Test Production Item 1", include_non_stock_items=1)
self.assertTrue(len(pln.mr_items), 3)
pln.cancel()
def test_production_plan_without_multi_level(self):
"Test MR Planning table for non exploded BOM."
- pln = create_production_plan(
- item_code='Test Production Item 1',
- use_multi_level_bom=0
- )
+ pln = create_production_plan(item_code="Test Production Item 1", use_multi_level_bom=0)
self.assertTrue(len(pln.mr_items), 2)
pln.cancel()
@@ -144,15 +145,15 @@
- Test if MR Planning table avoids pulling Raw Material Qty as it is in stock for
non exploded BOM.
"""
- sr1 = create_stock_reconciliation(item_code="Raw Material Item 1",
- target="_Test Warehouse - _TC", qty=1, rate=130)
- sr2 = create_stock_reconciliation(item_code="Subassembly Item 1",
- target="_Test Warehouse - _TC", qty=1, rate=140)
+ sr1 = create_stock_reconciliation(
+ item_code="Raw Material Item 1", target="_Test Warehouse - _TC", qty=1, rate=130
+ )
+ sr2 = create_stock_reconciliation(
+ item_code="Subassembly Item 1", target="_Test Warehouse - _TC", qty=1, rate=140
+ )
pln = create_production_plan(
- item_code='Test Production Item 1',
- use_multi_level_bom=0,
- ignore_existing_ordered_qty=0
+ item_code="Test Production Item 1", use_multi_level_bom=0, ignore_existing_ordered_qty=0
)
self.assertFalse(len(pln.mr_items))
@@ -162,73 +163,86 @@
def test_production_plan_sales_orders(self):
"Test if previously fulfilled SO (with WO) is pulled into Prod Plan."
- item = 'Test Production Item 1'
+ item = "Test Production Item 1"
so = make_sales_order(item_code=item, qty=1)
sales_order = so.name
sales_order_item = so.items[0].name
- pln = frappe.new_doc('Production Plan')
+ pln = frappe.new_doc("Production Plan")
pln.company = so.company
- pln.get_items_from = 'Sales Order'
+ pln.get_items_from = "Sales Order"
- pln.append('sales_orders', {
- 'sales_order': so.name,
- 'sales_order_date': so.transaction_date,
- 'customer': so.customer,
- 'grand_total': so.grand_total
- })
+ pln.append(
+ "sales_orders",
+ {
+ "sales_order": so.name,
+ "sales_order_date": so.transaction_date,
+ "customer": so.customer,
+ "grand_total": so.grand_total,
+ },
+ )
pln.get_so_items()
pln.submit()
pln.make_work_order()
- work_order = frappe.db.get_value('Work Order', {'sales_order': sales_order,
- 'production_plan': pln.name, 'sales_order_item': sales_order_item}, 'name')
+ work_order = frappe.db.get_value(
+ "Work Order",
+ {"sales_order": sales_order, "production_plan": pln.name, "sales_order_item": sales_order_item},
+ "name",
+ )
- wo_doc = frappe.get_doc('Work Order', work_order)
- wo_doc.update({
- 'wip_warehouse': 'Work In Progress - _TC',
- 'fg_warehouse': 'Finished Goods - _TC'
- })
+ wo_doc = frappe.get_doc("Work Order", work_order)
+ wo_doc.update(
+ {"wip_warehouse": "Work In Progress - _TC", "fg_warehouse": "Finished Goods - _TC"}
+ )
wo_doc.submit()
- so_wo_qty = frappe.db.get_value('Sales Order Item', sales_order_item, 'work_order_qty')
+ so_wo_qty = frappe.db.get_value("Sales Order Item", sales_order_item, "work_order_qty")
self.assertTrue(so_wo_qty, 5)
- pln = frappe.new_doc('Production Plan')
- pln.update({
- 'from_date': so.transaction_date,
- 'to_date': so.transaction_date,
- 'customer': so.customer,
- 'item_code': item,
- 'sales_order_status': so.status
- })
+ pln = frappe.new_doc("Production Plan")
+ pln.update(
+ {
+ "from_date": so.transaction_date,
+ "to_date": so.transaction_date,
+ "customer": so.customer,
+ "item_code": item,
+ "sales_order_status": so.status,
+ }
+ )
sales_orders = get_sales_orders(pln) or {}
- sales_orders = [d.get('name') for d in sales_orders if d.get('name') == sales_order]
+ sales_orders = [d.get("name") for d in sales_orders if d.get("name") == sales_order]
self.assertEqual(sales_orders, [])
def test_production_plan_combine_items(self):
"Test combining FG items in Production Plan."
- item = 'Test Production Item 1'
+ item = "Test Production Item 1"
so1 = make_sales_order(item_code=item, qty=1)
- pln = frappe.new_doc('Production Plan')
+ pln = frappe.new_doc("Production Plan")
pln.company = so1.company
- pln.get_items_from = 'Sales Order'
- pln.append('sales_orders', {
- 'sales_order': so1.name,
- 'sales_order_date': so1.transaction_date,
- 'customer': so1.customer,
- 'grand_total': so1.grand_total
- })
+ pln.get_items_from = "Sales Order"
+ pln.append(
+ "sales_orders",
+ {
+ "sales_order": so1.name,
+ "sales_order_date": so1.transaction_date,
+ "customer": so1.customer,
+ "grand_total": so1.grand_total,
+ },
+ )
so2 = make_sales_order(item_code=item, qty=2)
- pln.append('sales_orders', {
- 'sales_order': so2.name,
- 'sales_order_date': so2.transaction_date,
- 'customer': so2.customer,
- 'grand_total': so2.grand_total
- })
+ pln.append(
+ "sales_orders",
+ {
+ "sales_order": so2.name,
+ "sales_order_date": so2.transaction_date,
+ "customer": so2.customer,
+ "grand_total": so2.grand_total,
+ },
+ )
pln.combine_items = 1
pln.get_items()
pln.submit()
@@ -236,26 +250,31 @@
self.assertTrue(pln.po_items[0].planned_qty, 3)
pln.make_work_order()
- work_order = frappe.db.get_value('Work Order', {
- 'production_plan_item': pln.po_items[0].name,
- 'production_plan': pln.name
- }, 'name')
+ work_order = frappe.db.get_value(
+ "Work Order",
+ {"production_plan_item": pln.po_items[0].name, "production_plan": pln.name},
+ "name",
+ )
- wo_doc = frappe.get_doc('Work Order', work_order)
- wo_doc.update({
- 'wip_warehouse': 'Work In Progress - _TC',
- })
+ wo_doc = frappe.get_doc("Work Order", work_order)
+ wo_doc.update(
+ {
+ "wip_warehouse": "Work In Progress - _TC",
+ }
+ )
wo_doc.submit()
so_items = []
for plan_reference in pln.prod_plan_references:
so_items.append(plan_reference.sales_order_item)
- so_wo_qty = frappe.db.get_value('Sales Order Item', plan_reference.sales_order_item, 'work_order_qty')
+ so_wo_qty = frappe.db.get_value(
+ "Sales Order Item", plan_reference.sales_order_item, "work_order_qty"
+ )
self.assertEqual(so_wo_qty, plan_reference.qty)
wo_doc.cancel()
for so_item in so_items:
- so_wo_qty = frappe.db.get_value('Sales Order Item', so_item, 'work_order_qty')
+ so_wo_qty = frappe.db.get_value("Sales Order Item", so_item, "work_order_qty")
self.assertEqual(so_wo_qty, 0.0)
pln.reload()
@@ -269,12 +288,8 @@
"""
from erpnext.manufacturing.doctype.bom.test_bom import create_nested_bom
- bom_tree_1 = {
- "Red-Car": {"Wheel": {"Rubber": {}}}
- }
- bom_tree_2 = {
- "Green-Car": {"Wheel": {"Rubber": {}}}
- }
+ bom_tree_1 = {"Red-Car": {"Wheel": {"Rubber": {}}}}
+ bom_tree_2 = {"Green-Car": {"Wheel": {"Rubber": {}}}}
parent_bom_1 = create_nested_bom(bom_tree_1, prefix="")
parent_bom_2 = create_nested_bom(bom_tree_2, prefix="")
@@ -284,20 +299,23 @@
frappe.db.set_value("BOM Item", parent_bom_2.items[0].name, "bom_no", subassembly_bom)
plan = create_production_plan(item_code="Red-Car", use_multi_level_bom=1, do_not_save=True)
- plan.append("po_items", { # Add Green-Car to Prod Plan
- 'use_multi_level_bom': 1,
- 'item_code': "Green-Car",
- 'bom_no': frappe.db.get_value('Item', "Green-Car", 'default_bom'),
- 'planned_qty': 1,
- 'planned_start_date': now_datetime()
- })
+ plan.append(
+ "po_items",
+ { # Add Green-Car to Prod Plan
+ "use_multi_level_bom": 1,
+ "item_code": "Green-Car",
+ "bom_no": frappe.db.get_value("Item", "Green-Car", "default_bom"),
+ "planned_qty": 1,
+ "planned_start_date": now_datetime(),
+ },
+ )
plan.get_sub_assembly_items()
self.assertTrue(len(plan.sub_assembly_items), 2)
plan.combine_sub_items = 1
plan.get_sub_assembly_items()
- self.assertTrue(len(plan.sub_assembly_items), 1) # check if sub-assembly items merged
+ self.assertTrue(len(plan.sub_assembly_items), 1) # check if sub-assembly items merged
self.assertEqual(plan.sub_assembly_items[0].qty, 2.0)
self.assertEqual(plan.sub_assembly_items[0].stock_qty, 2.0)
@@ -307,25 +325,29 @@
self.assertTrue(len(plan.sub_assembly_items), 2)
def test_pp_to_mr_customer_provided(self):
- " Test Material Request from Production Plan for Customer Provided Item."
- create_item('CUST-0987', is_customer_provided_item = 1, customer = '_Test Customer', is_purchase_item = 0)
- create_item('Production Item CUST')
+ "Test Material Request from Production Plan for Customer Provided Item."
+ create_item(
+ "CUST-0987", is_customer_provided_item=1, customer="_Test Customer", is_purchase_item=0
+ )
+ create_item("Production Item CUST")
- for item, raw_materials in {'Production Item CUST': ['Raw Material Item 1', 'CUST-0987']}.items():
- if not frappe.db.get_value('BOM', {'item': item}):
- make_bom(item = item, raw_materials = raw_materials)
- production_plan = create_production_plan(item_code = 'Production Item CUST')
+ for item, raw_materials in {
+ "Production Item CUST": ["Raw Material Item 1", "CUST-0987"]
+ }.items():
+ if not frappe.db.get_value("BOM", {"item": item}):
+ make_bom(item=item, raw_materials=raw_materials)
+ production_plan = create_production_plan(item_code="Production Item CUST")
production_plan.make_material_request()
material_request = frappe.db.get_value(
- 'Material Request Item',
- {'production_plan': production_plan.name, 'item_code': 'CUST-0987'},
- 'parent'
+ "Material Request Item",
+ {"production_plan": production_plan.name, "item_code": "CUST-0987"},
+ "parent",
)
- mr = frappe.get_doc('Material Request', material_request)
+ mr = frappe.get_doc("Material Request", material_request)
- self.assertTrue(mr.material_request_type, 'Customer Provided')
- self.assertTrue(mr.customer, '_Test Customer')
+ self.assertTrue(mr.material_request_type, "Customer Provided")
+ self.assertTrue(mr.customer, "_Test Customer")
def test_production_plan_with_multi_level_bom(self):
"""
@@ -339,33 +361,34 @@
create_item(item_code, is_stock_item=1)
# created bom upto 3 level
- if not frappe.db.get_value('BOM', {'item': "Test BOM 3"}):
- make_bom(item = "Test BOM 3", raw_materials = ["Test RM BOM 1"], rm_qty=3)
+ if not frappe.db.get_value("BOM", {"item": "Test BOM 3"}):
+ make_bom(item="Test BOM 3", raw_materials=["Test RM BOM 1"], rm_qty=3)
- if not frappe.db.get_value('BOM', {'item': "Test BOM 2"}):
- make_bom(item = "Test BOM 2", raw_materials = ["Test BOM 3"], rm_qty=3)
+ if not frappe.db.get_value("BOM", {"item": "Test BOM 2"}):
+ make_bom(item="Test BOM 2", raw_materials=["Test BOM 3"], rm_qty=3)
- if not frappe.db.get_value('BOM', {'item': "Test BOM 1"}):
- make_bom(item = "Test BOM 1", raw_materials = ["Test BOM 2"], rm_qty=2)
+ if not frappe.db.get_value("BOM", {"item": "Test BOM 1"}):
+ make_bom(item="Test BOM 1", raw_materials=["Test BOM 2"], rm_qty=2)
item_code = "Test BOM 1"
- pln = frappe.new_doc('Production Plan')
+ pln = frappe.new_doc("Production Plan")
pln.company = "_Test Company"
- pln.append("po_items", {
- "item_code": item_code,
- "bom_no": frappe.db.get_value('BOM', {'item': "Test BOM 1"}),
- "planned_qty": 3
- })
+ pln.append(
+ "po_items",
+ {
+ "item_code": item_code,
+ "bom_no": frappe.db.get_value("BOM", {"item": "Test BOM 1"}),
+ "planned_qty": 3,
+ },
+ )
- pln.get_sub_assembly_items('In House')
+ pln.get_sub_assembly_items("In House")
pln.submit()
pln.make_work_order()
- #last level sub-assembly work order produce qty
+ # last level sub-assembly work order produce qty
to_produce_qty = frappe.db.get_value(
- "Work Order",
- {"production_plan": pln.name, "production_item": "Test BOM 3"},
- "qty"
+ "Work Order", {"production_plan": pln.name, "production_item": "Test BOM 3"}, "qty"
)
self.assertEqual(to_produce_qty, 18.0)
@@ -374,70 +397,72 @@
def test_get_warehouse_list_group(self):
"Check if required child warehouses are returned."
- warehouse_json = '[{\"warehouse\":\"_Test Warehouse Group - _TC\"}]'
+ warehouse_json = '[{"warehouse":"_Test Warehouse Group - _TC"}]'
warehouses = set(get_warehouse_list(warehouse_json))
expected_warehouses = {"_Test Warehouse Group-C1 - _TC", "_Test Warehouse Group-C2 - _TC"}
missing_warehouse = expected_warehouses - warehouses
- self.assertTrue(len(missing_warehouse) == 0,
- msg=f"Following warehouses were expected {', '.join(missing_warehouse)}")
+ self.assertTrue(
+ len(missing_warehouse) == 0,
+ msg=f"Following warehouses were expected {', '.join(missing_warehouse)}",
+ )
def test_get_warehouse_list_single(self):
"Check if same warehouse is returned in absence of child warehouses."
- warehouse_json = '[{\"warehouse\":\"_Test Scrap Warehouse - _TC\"}]'
+ warehouse_json = '[{"warehouse":"_Test Scrap Warehouse - _TC"}]'
warehouses = set(get_warehouse_list(warehouse_json))
- expected_warehouses = {"_Test Scrap Warehouse - _TC", }
+ expected_warehouses = {
+ "_Test Scrap Warehouse - _TC",
+ }
self.assertEqual(warehouses, expected_warehouses)
def test_get_sales_order_with_variant(self):
"Check if Template BOM is fetched in absence of Variant BOM."
- rm_item = create_item('PIV_RM', valuation_rate = 100)
- if not frappe.db.exists('Item', {"item_code": 'PIV'}):
- item = create_item('PIV', valuation_rate = 100)
+ rm_item = create_item("PIV_RM", valuation_rate=100)
+ if not frappe.db.exists("Item", {"item_code": "PIV"}):
+ item = create_item("PIV", valuation_rate=100)
variant_settings = {
"attributes": [
- {
- "attribute": "Colour"
- },
+ {"attribute": "Colour"},
],
- "has_variants": 1
+ "has_variants": 1,
}
item.update(variant_settings)
item.save()
- parent_bom = make_bom(item = 'PIV', raw_materials = [rm_item.item_code])
- if not frappe.db.exists('BOM', {"item": 'PIV'}):
- parent_bom = make_bom(item = 'PIV', raw_materials = [rm_item.item_code])
+ parent_bom = make_bom(item="PIV", raw_materials=[rm_item.item_code])
+ if not frappe.db.exists("BOM", {"item": "PIV"}):
+ parent_bom = make_bom(item="PIV", raw_materials=[rm_item.item_code])
else:
- parent_bom = frappe.get_doc('BOM', {"item": 'PIV'})
+ parent_bom = frappe.get_doc("BOM", {"item": "PIV"})
- if not frappe.db.exists('Item', {"item_code": 'PIV-RED'}):
+ if not frappe.db.exists("Item", {"item_code": "PIV-RED"}):
variant = create_variant("PIV", {"Colour": "Red"})
variant.save()
- variant_bom = make_bom(item = variant.item_code, raw_materials = [rm_item.item_code])
+ variant_bom = make_bom(item=variant.item_code, raw_materials=[rm_item.item_code])
else:
- variant = frappe.get_doc('Item', 'PIV-RED')
- if not frappe.db.exists('BOM', {"item": 'PIV-RED'}):
- variant_bom = make_bom(item = variant.item_code, raw_materials = [rm_item.item_code])
+ variant = frappe.get_doc("Item", "PIV-RED")
+ if not frappe.db.exists("BOM", {"item": "PIV-RED"}):
+ variant_bom = make_bom(item=variant.item_code, raw_materials=[rm_item.item_code])
"""Testing when item variant has a BOM"""
so = make_sales_order(item_code="PIV-RED", qty=5)
- pln = frappe.new_doc('Production Plan')
+ pln = frappe.new_doc("Production Plan")
pln.company = so.company
- pln.get_items_from = 'Sales Order'
- pln.item_code = 'PIV-RED'
+ pln.get_items_from = "Sales Order"
+ pln.item_code = "PIV-RED"
pln.get_open_sales_orders()
self.assertEqual(pln.sales_orders[0].sales_order, so.name)
pln.get_so_items()
- self.assertEqual(pln.po_items[0].item_code, 'PIV-RED')
+ self.assertEqual(pln.po_items[0].item_code, "PIV-RED")
self.assertEqual(pln.po_items[0].bom_no, variant_bom.name)
so.cancel()
- frappe.delete_doc('Sales Order', so.name)
+ frappe.delete_doc("Sales Order", so.name)
variant_bom.cancel()
- frappe.delete_doc('BOM', variant_bom.name)
+ frappe.delete_doc("BOM", variant_bom.name)
"""Testing when item variant doesn't have a BOM"""
so = make_sales_order(item_code="PIV-RED", qty=5)
@@ -445,7 +470,7 @@
self.assertEqual(pln.sales_orders[0].sales_order, so.name)
pln.po_items = []
pln.get_so_items()
- self.assertEqual(pln.po_items[0].item_code, 'PIV-RED')
+ self.assertEqual(pln.po_items[0].item_code, "PIV-RED")
self.assertEqual(pln.po_items[0].bom_no, parent_bom.name)
frappe.db.rollback()
@@ -457,27 +482,35 @@
prefix = "_TestLevel_"
boms = {
"Assembly": {
- "SubAssembly1": {"ChildPart1": {}, "ChildPart2": {},},
+ "SubAssembly1": {
+ "ChildPart1": {},
+ "ChildPart2": {},
+ },
"ChildPart6": {},
"SubAssembly4": {"SubSubAssy2": {"ChildPart7": {}}},
},
"MegaDeepAssy": {
- "SecretSubassy": {"SecretPart": {"VerySecret" : { "SuperSecret": {"Classified": {}}}},},
- # ^ assert that this is
- # first item in subassy table
- }
+ "SecretSubassy": {
+ "SecretPart": {"VerySecret": {"SuperSecret": {"Classified": {}}}},
+ },
+ # ^ assert that this is
+ # first item in subassy table
+ },
}
create_nested_bom(boms, prefix=prefix)
items = [prefix + item_code for item_code in boms.keys()]
plan = create_production_plan(item_code=items[0], do_not_save=True)
- plan.append("po_items", {
- 'use_multi_level_bom': 1,
- 'item_code': items[1],
- 'bom_no': frappe.db.get_value('Item', items[1], 'default_bom'),
- 'planned_qty': 1,
- 'planned_start_date': now_datetime()
- })
+ plan.append(
+ "po_items",
+ {
+ "use_multi_level_bom": 1,
+ "item_code": items[1],
+ "bom_no": frappe.db.get_value("Item", items[1], "default_bom"),
+ "planned_qty": 1,
+ "planned_start_date": now_datetime(),
+ },
+ )
plan.get_sub_assembly_items()
bom_level_order = [d.bom_level for d in plan.sub_assembly_items]
@@ -487,6 +520,7 @@
def test_multiple_work_order_for_production_plan_item(self):
"Test producing Prod Plan (making WO) in parts."
+
def create_work_order(item, pln, qty):
# Get Production Items
items_data = pln.get_production_items()
@@ -497,14 +531,13 @@
# Create and Submit Work Order for each item in items_data
for key, item in items_data.items():
if pln.sub_assembly_items:
- item['use_multi_level_bom'] = 0
+ item["use_multi_level_bom"] = 0
wo_name = pln.create_work_order(item)
wo_doc = frappe.get_doc("Work Order", wo_name)
- wo_doc.update({
- 'wip_warehouse': 'Work In Progress - _TC',
- 'fg_warehouse': 'Finished Goods - _TC'
- })
+ wo_doc.update(
+ {"wip_warehouse": "Work In Progress - _TC", "fg_warehouse": "Finished Goods - _TC"}
+ )
wo_doc.submit()
wo_list.append(wo_name)
@@ -554,34 +587,30 @@
make_stock_entry as make_se_from_wo,
)
- make_stock_entry(item_code="Raw Material Item 1",
- target="Work In Progress - _TC",
- qty=2, basic_rate=100
+ make_stock_entry(
+ item_code="Raw Material Item 1", target="Work In Progress - _TC", qty=2, basic_rate=100
)
- make_stock_entry(item_code="Raw Material Item 2",
- target="Work In Progress - _TC",
- qty=2, basic_rate=100
+ make_stock_entry(
+ item_code="Raw Material Item 2", target="Work In Progress - _TC", qty=2, basic_rate=100
)
- item = 'Test Production Item 1'
+ item = "Test Production Item 1"
so = make_sales_order(item_code=item, qty=1)
pln = create_production_plan(
- company=so.company,
- get_items_from="Sales Order",
- sales_order=so,
- skip_getting_mr_items=True
+ company=so.company, get_items_from="Sales Order", sales_order=so, skip_getting_mr_items=True
)
self.assertEqual(pln.po_items[0].pending_qty, 1)
wo = make_wo_order_test_record(
- item_code=item, qty=1,
+ item_code=item,
+ qty=1,
company=so.company,
- wip_warehouse='Work In Progress - _TC',
- fg_warehouse='Finished Goods - _TC',
+ wip_warehouse="Work In Progress - _TC",
+ fg_warehouse="Finished Goods - _TC",
skip_transfer=1,
use_multi_level_bom=1,
- do_not_submit=True
+ do_not_submit=True,
)
wo.production_plan = pln.name
wo.production_plan_item = pln.po_items[0].name
@@ -604,29 +633,25 @@
make_stock_entry as make_se_from_wo,
)
- make_stock_entry(item_code="Raw Material Item 1",
- target="Work In Progress - _TC",
- qty=2, basic_rate=100
+ make_stock_entry(
+ item_code="Raw Material Item 1", target="Work In Progress - _TC", qty=2, basic_rate=100
)
- make_stock_entry(item_code="Raw Material Item 2",
- target="Work In Progress - _TC",
- qty=2, basic_rate=100
+ make_stock_entry(
+ item_code="Raw Material Item 2", target="Work In Progress - _TC", qty=2, basic_rate=100
)
- pln = create_production_plan(
- item_code='Test Production Item 1',
- skip_getting_mr_items=True
- )
+ pln = create_production_plan(item_code="Test Production Item 1", skip_getting_mr_items=True)
self.assertEqual(pln.po_items[0].pending_qty, 1)
wo = make_wo_order_test_record(
- item_code='Test Production Item 1', qty=1,
+ item_code="Test Production Item 1",
+ qty=1,
company=pln.company,
- wip_warehouse='Work In Progress - _TC',
- fg_warehouse='Finished Goods - _TC',
+ wip_warehouse="Work In Progress - _TC",
+ fg_warehouse="Finished Goods - _TC",
skip_transfer=1,
use_multi_level_bom=1,
- do_not_submit=True
+ do_not_submit=True,
)
wo.production_plan = pln.name
wo.production_plan_item = pln.po_items[0].name
@@ -644,26 +669,23 @@
def test_qty_based_status(self):
pp = frappe.new_doc("Production Plan")
- pp.po_items = [
- frappe._dict(planned_qty=5, produce_qty=4)
- ]
+ pp.po_items = [frappe._dict(planned_qty=5, produce_qty=4)]
self.assertFalse(pp.all_items_completed())
pp.po_items = [
frappe._dict(planned_qty=5, produce_qty=10),
- frappe._dict(planned_qty=5, produce_qty=4)
+ frappe._dict(planned_qty=5, produce_qty=4),
]
self.assertFalse(pp.all_items_completed())
def test_production_plan_planned_qty(self):
pln = create_production_plan(item_code="_Test FG Item", planned_qty=0.55)
pln.make_work_order()
- work_order = frappe.db.get_value('Work Order', {'production_plan': pln.name}, 'name')
- wo_doc = frappe.get_doc('Work Order', work_order)
- wo_doc.update({
- 'wip_warehouse': 'Work In Progress - _TC',
- 'fg_warehouse': 'Finished Goods - _TC'
- })
+ work_order = frappe.db.get_value("Work Order", {"production_plan": pln.name}, "name")
+ wo_doc = frappe.get_doc("Work Order", work_order)
+ wo_doc.update(
+ {"wip_warehouse": "Work In Progress - _TC", "fg_warehouse": "Finished Goods - _TC"}
+ )
wo_doc.submit()
self.assertEqual(wo_doc.qty, 0.55)
@@ -674,22 +696,21 @@
# this can not be unittested so mocking data that would be expected
# from client side.
for _ in range(10):
- po_item = pp.append("po_items", {
- "name": frappe.generate_hash(length=10),
- "temporary_name": frappe.generate_hash(length=10),
- })
- pp.append("sub_assembly_items", {
- "production_plan_item": po_item.temporary_name
- })
+ po_item = pp.append(
+ "po_items",
+ {
+ "name": frappe.generate_hash(length=10),
+ "temporary_name": frappe.generate_hash(length=10),
+ },
+ )
+ pp.append("sub_assembly_items", {"production_plan_item": po_item.temporary_name})
pp._rename_temporary_references()
for po_item, subassy_item in zip(pp.po_items, pp.sub_assembly_items):
self.assertEqual(po_item.name, subassy_item.production_plan_item)
# bad links should be erased
- pp.append("sub_assembly_items", {
- "production_plan_item": frappe.generate_hash(length=16)
- })
+ pp.append("sub_assembly_items", {"production_plan_item": frappe.generate_hash(length=16)})
pp._rename_temporary_references()
self.assertIsNone(pp.sub_assembly_items[-1].production_plan_item)
pp.sub_assembly_items.pop()
@@ -708,40 +729,48 @@
"""
args = frappe._dict(args)
- pln = frappe.get_doc({
- 'doctype': 'Production Plan',
- 'company': args.company or '_Test Company',
- 'customer': args.customer or '_Test Customer',
- 'posting_date': nowdate(),
- 'include_non_stock_items': args.include_non_stock_items or 0,
- 'include_subcontracted_items': args.include_subcontracted_items or 0,
- 'ignore_existing_ordered_qty': args.ignore_existing_ordered_qty or 0,
- 'get_items_from': 'Sales Order'
- })
+ pln = frappe.get_doc(
+ {
+ "doctype": "Production Plan",
+ "company": args.company or "_Test Company",
+ "customer": args.customer or "_Test Customer",
+ "posting_date": nowdate(),
+ "include_non_stock_items": args.include_non_stock_items or 0,
+ "include_subcontracted_items": args.include_subcontracted_items or 0,
+ "ignore_existing_ordered_qty": args.ignore_existing_ordered_qty or 0,
+ "get_items_from": "Sales Order",
+ }
+ )
if not args.get("sales_order"):
- pln.append('po_items', {
- 'use_multi_level_bom': args.use_multi_level_bom or 1,
- 'item_code': args.item_code,
- 'bom_no': frappe.db.get_value('Item', args.item_code, 'default_bom'),
- 'planned_qty': args.planned_qty or 1,
- 'planned_start_date': args.planned_start_date or now_datetime()
- })
+ pln.append(
+ "po_items",
+ {
+ "use_multi_level_bom": args.use_multi_level_bom or 1,
+ "item_code": args.item_code,
+ "bom_no": frappe.db.get_value("Item", args.item_code, "default_bom"),
+ "planned_qty": args.planned_qty or 1,
+ "planned_start_date": args.planned_start_date or now_datetime(),
+ },
+ )
if args.get("get_items_from") == "Sales Order" and args.get("sales_order"):
so = args.get("sales_order")
- pln.append('sales_orders', {
- 'sales_order': so.name,
- 'sales_order_date': so.transaction_date,
- 'customer': so.customer,
- 'grand_total': so.grand_total
- })
+ pln.append(
+ "sales_orders",
+ {
+ "sales_order": so.name,
+ "sales_order_date": so.transaction_date,
+ "customer": so.customer,
+ "grand_total": so.grand_total,
+ },
+ )
pln.get_items()
if not args.get("skip_getting_mr_items"):
mr_items = get_items_for_material_requests(pln.as_dict())
for d in mr_items:
- pln.append('mr_items', d)
+ pln.append("mr_items", d)
if not args.do_not_save:
pln.insert()
@@ -750,31 +779,37 @@
return pln
+
def make_bom(**args):
args = frappe._dict(args)
- bom = frappe.get_doc({
- 'doctype': 'BOM',
- 'is_default': 1,
- 'item': args.item,
- 'currency': args.currency or 'USD',
- 'quantity': args.quantity or 1,
- 'company': args.company or '_Test Company',
- 'routing': args.routing,
- 'with_operations': args.with_operations or 0
- })
+ bom = frappe.get_doc(
+ {
+ "doctype": "BOM",
+ "is_default": 1,
+ "item": args.item,
+ "currency": args.currency or "USD",
+ "quantity": args.quantity or 1,
+ "company": args.company or "_Test Company",
+ "routing": args.routing,
+ "with_operations": args.with_operations or 0,
+ }
+ )
for item in args.raw_materials:
- item_doc = frappe.get_doc('Item', item)
+ item_doc = frappe.get_doc("Item", item)
- bom.append('items', {
- 'item_code': item,
- 'qty': args.rm_qty or 1.0,
- 'uom': item_doc.stock_uom,
- 'stock_uom': item_doc.stock_uom,
- 'rate': item_doc.valuation_rate or args.rate,
- 'source_warehouse': args.source_warehouse
- })
+ bom.append(
+ "items",
+ {
+ "item_code": item,
+ "qty": args.rm_qty or 1.0,
+ "uom": item_doc.stock_uom,
+ "stock_uom": item_doc.stock_uom,
+ "rate": item_doc.valuation_rate or args.rate,
+ "source_warehouse": args.source_warehouse,
+ },
+ )
if not args.do_not_save:
bom.insert(ignore_permissions=True)
diff --git a/erpnext/manufacturing/doctype/routing/routing.py b/erpnext/manufacturing/doctype/routing/routing.py
index b207906..d4c37cf 100644
--- a/erpnext/manufacturing/doctype/routing/routing.py
+++ b/erpnext/manufacturing/doctype/routing/routing.py
@@ -19,9 +19,11 @@
def calculate_operating_cost(self):
for operation in self.operations:
if not operation.hour_rate:
- operation.hour_rate = frappe.db.get_value("Workstation", operation.workstation, 'hour_rate')
- operation.operating_cost = flt(flt(operation.hour_rate) * flt(operation.time_in_mins) / 60,
- operation.precision("operating_cost"))
+ operation.hour_rate = frappe.db.get_value("Workstation", operation.workstation, "hour_rate")
+ operation.operating_cost = flt(
+ flt(operation.hour_rate) * flt(operation.time_in_mins) / 60,
+ operation.precision("operating_cost"),
+ )
def set_routing_id(self):
sequence_id = 0
@@ -29,7 +31,10 @@
if not row.sequence_id:
row.sequence_id = sequence_id + 1
elif sequence_id and row.sequence_id and cint(sequence_id) > cint(row.sequence_id):
- frappe.throw(_("At row #{0}: the sequence id {1} cannot be less than previous row sequence id {2}")
- .format(row.idx, row.sequence_id, sequence_id))
+ frappe.throw(
+ _("At row #{0}: the sequence id {1} cannot be less than previous row sequence id {2}").format(
+ row.idx, row.sequence_id, sequence_id
+ )
+ )
sequence_id = row.sequence_id
diff --git a/erpnext/manufacturing/doctype/routing/routing_dashboard.py b/erpnext/manufacturing/doctype/routing/routing_dashboard.py
index d051e38..65d7a45 100644
--- a/erpnext/manufacturing/doctype/routing/routing_dashboard.py
+++ b/erpnext/manufacturing/doctype/routing/routing_dashboard.py
@@ -1,9 +1,2 @@
def get_data():
- return {
- 'fieldname': 'routing',
- 'transactions': [
- {
- 'items': ['BOM']
- }
- ]
- }
+ return {"fieldname": "routing", "transactions": [{"items": ["BOM"]}]}
diff --git a/erpnext/manufacturing/doctype/routing/test_routing.py b/erpnext/manufacturing/doctype/routing/test_routing.py
index 696d9bc..48f1851 100644
--- a/erpnext/manufacturing/doctype/routing/test_routing.py
+++ b/erpnext/manufacturing/doctype/routing/test_routing.py
@@ -16,24 +16,27 @@
@classmethod
def tearDownClass(cls):
- frappe.db.sql('delete from tabBOM where item=%s', cls.item_code)
+ frappe.db.sql("delete from tabBOM where item=%s", cls.item_code)
def test_sequence_id(self):
- operations = [{"operation": "Test Operation A", "workstation": "Test Workstation A", "time_in_mins": 30},
- {"operation": "Test Operation B", "workstation": "Test Workstation A", "time_in_mins": 20}]
+ operations = [
+ {"operation": "Test Operation A", "workstation": "Test Workstation A", "time_in_mins": 30},
+ {"operation": "Test Operation B", "workstation": "Test Workstation A", "time_in_mins": 20},
+ ]
make_test_records("UOM")
setup_operations(operations)
routing_doc = create_routing(routing_name="Testing Route", operations=operations)
bom_doc = setup_bom(item_code=self.item_code, routing=routing_doc.name)
- wo_doc = make_wo_order_test_record(production_item = self.item_code, bom_no=bom_doc.name)
+ wo_doc = make_wo_order_test_record(production_item=self.item_code, bom_no=bom_doc.name)
for row in routing_doc.operations:
self.assertEqual(row.sequence_id, row.idx)
- for data in frappe.get_all("Job Card",
- filters={"work_order": wo_doc.name}, order_by="sequence_id desc"):
+ for data in frappe.get_all(
+ "Job Card", filters={"work_order": wo_doc.name}, order_by="sequence_id desc"
+ ):
job_card_doc = frappe.get_doc("Job Card", data.name)
job_card_doc.time_logs[0].completed_qty = 10
if job_card_doc.sequence_id != 1:
@@ -52,33 +55,25 @@
"operation": "Test Operation A",
"workstation": "_Test Workstation A",
"hour_rate_rent": 300,
- "hour_rate_labour": 750 ,
- "time_in_mins": 30
+ "hour_rate_labour": 750,
+ "time_in_mins": 30,
},
{
"operation": "Test Operation B",
"workstation": "_Test Workstation B",
"hour_rate_labour": 200,
"hour_rate_rent": 1000,
- "time_in_mins": 20
- }
+ "time_in_mins": 20,
+ },
]
test_routing_operations = [
- {
- "operation": "Test Operation A",
- "workstation": "_Test Workstation A",
- "time_in_mins": 30
- },
- {
- "operation": "Test Operation B",
- "workstation": "_Test Workstation A",
- "time_in_mins": 20
- }
+ {"operation": "Test Operation A", "workstation": "_Test Workstation A", "time_in_mins": 30},
+ {"operation": "Test Operation B", "workstation": "_Test Workstation A", "time_in_mins": 20},
]
setup_operations(operations)
routing_doc = create_routing(routing_name="Routing Test", operations=test_routing_operations)
- bom_doc = setup_bom(item_code="_Testing Item", routing=routing_doc.name, currency = 'INR')
+ bom_doc = setup_bom(item_code="_Testing Item", routing=routing_doc.name, currency="INR")
self.assertEqual(routing_doc.operations[0].time_in_mins, 30)
self.assertEqual(routing_doc.operations[1].time_in_mins, 20)
routing_doc.operations[0].time_in_mins = 90
@@ -93,10 +88,12 @@
def setup_operations(rows):
from erpnext.manufacturing.doctype.operation.test_operation import make_operation
from erpnext.manufacturing.doctype.workstation.test_workstation import make_workstation
+
for row in rows:
make_workstation(row)
make_operation(row)
+
def create_routing(**args):
args = frappe._dict(args)
@@ -108,7 +105,7 @@
doc.insert()
except frappe.DuplicateEntryError:
doc = frappe.get_doc("Routing", args.routing_name)
- doc.delete_key('operations')
+ doc.delete_key("operations")
for operation in args.operations:
doc.append("operations", operation)
@@ -116,28 +113,35 @@
return doc
+
def setup_bom(**args):
from erpnext.manufacturing.doctype.production_plan.test_production_plan import make_bom
args = frappe._dict(args)
- if not frappe.db.exists('Item', args.item_code):
- make_item(args.item_code, {
- 'is_stock_item': 1
- })
+ if not frappe.db.exists("Item", args.item_code):
+ make_item(args.item_code, {"is_stock_item": 1})
if not args.raw_materials:
- if not frappe.db.exists('Item', "Test Extra Item N-1"):
- make_item("Test Extra Item N-1", {
- 'is_stock_item': 1,
- })
+ if not frappe.db.exists("Item", "Test Extra Item N-1"):
+ make_item(
+ "Test Extra Item N-1",
+ {
+ "is_stock_item": 1,
+ },
+ )
- args.raw_materials = ['Test Extra Item N-1']
+ args.raw_materials = ["Test Extra Item N-1"]
- name = frappe.db.get_value('BOM', {'item': args.item_code}, 'name')
+ name = frappe.db.get_value("BOM", {"item": args.item_code}, "name")
if not name:
- bom_doc = make_bom(item = args.item_code, raw_materials = args.get("raw_materials"),
- routing = args.routing, with_operations=1, currency = args.currency)
+ bom_doc = make_bom(
+ item=args.item_code,
+ raw_materials=args.get("raw_materials"),
+ routing=args.routing,
+ with_operations=1,
+ currency=args.currency,
+ )
else:
bom_doc = frappe.get_doc("BOM", name)
diff --git a/erpnext/manufacturing/doctype/work_order/test_work_order.py b/erpnext/manufacturing/doctype/work_order/test_work_order.py
index 6a8136d..3721704 100644
--- a/erpnext/manufacturing/doctype/work_order/test_work_order.py
+++ b/erpnext/manufacturing/doctype/work_order/test_work_order.py
@@ -26,29 +26,36 @@
class TestWorkOrder(FrappeTestCase):
def setUp(self):
- self.warehouse = '_Test Warehouse 2 - _TC'
- self.item = '_Test Item'
+ self.warehouse = "_Test Warehouse 2 - _TC"
+ self.item = "_Test Item"
def tearDown(self):
frappe.db.rollback()
def check_planned_qty(self):
- planned0 = frappe.db.get_value("Bin", {"item_code": "_Test FG Item",
- "warehouse": "_Test Warehouse 1 - _TC"}, "planned_qty") or 0
+ planned0 = (
+ frappe.db.get_value(
+ "Bin", {"item_code": "_Test FG Item", "warehouse": "_Test Warehouse 1 - _TC"}, "planned_qty"
+ )
+ or 0
+ )
wo_order = make_wo_order_test_record()
- planned1 = frappe.db.get_value("Bin", {"item_code": "_Test FG Item",
- "warehouse": "_Test Warehouse 1 - _TC"}, "planned_qty")
+ planned1 = frappe.db.get_value(
+ "Bin", {"item_code": "_Test FG Item", "warehouse": "_Test Warehouse 1 - _TC"}, "planned_qty"
+ )
self.assertEqual(planned1, planned0 + 10)
# add raw materials to stores
- test_stock_entry.make_stock_entry(item_code="_Test Item",
- target="Stores - _TC", qty=100, basic_rate=100)
- test_stock_entry.make_stock_entry(item_code="_Test Item Home Desktop 100",
- target="Stores - _TC", qty=100, basic_rate=100)
+ test_stock_entry.make_stock_entry(
+ item_code="_Test Item", target="Stores - _TC", qty=100, basic_rate=100
+ )
+ test_stock_entry.make_stock_entry(
+ item_code="_Test Item Home Desktop 100", target="Stores - _TC", qty=100, basic_rate=100
+ )
# from stores to wip
s = frappe.get_doc(make_stock_entry(wo_order.name, "Material Transfer for Manufacture", 4))
@@ -64,8 +71,9 @@
self.assertEqual(frappe.db.get_value("Work Order", wo_order.name, "produced_qty"), 4)
- planned2 = frappe.db.get_value("Bin", {"item_code": "_Test FG Item",
- "warehouse": "_Test Warehouse 1 - _TC"}, "planned_qty")
+ planned2 = frappe.db.get_value(
+ "Bin", {"item_code": "_Test FG Item", "warehouse": "_Test Warehouse 1 - _TC"}, "planned_qty"
+ )
self.assertEqual(planned2, planned0 + 6)
@@ -74,10 +82,12 @@
def test_over_production(self):
wo_doc = self.check_planned_qty()
- test_stock_entry.make_stock_entry(item_code="_Test Item",
- target="_Test Warehouse - _TC", qty=100, basic_rate=100)
- test_stock_entry.make_stock_entry(item_code="_Test Item Home Desktop 100",
- target="_Test Warehouse - _TC", qty=100, basic_rate=100)
+ test_stock_entry.make_stock_entry(
+ item_code="_Test Item", target="_Test Warehouse - _TC", qty=100, basic_rate=100
+ )
+ test_stock_entry.make_stock_entry(
+ item_code="_Test Item Home Desktop 100", target="_Test Warehouse - _TC", qty=100, basic_rate=100
+ )
s = frappe.get_doc(make_stock_entry(wo_doc.name, "Manufacture", 7))
s.insert()
@@ -85,13 +95,14 @@
self.assertRaises(StockOverProductionError, s.submit)
def test_planned_operating_cost(self):
- wo_order = make_wo_order_test_record(item="_Test FG Item 2",
- planned_start_date=now(), qty=1, do_not_save=True)
+ wo_order = make_wo_order_test_record(
+ item="_Test FG Item 2", planned_start_date=now(), qty=1, do_not_save=True
+ )
wo_order.set_work_order_operations()
cost = wo_order.planned_operating_cost
wo_order.qty = 2
wo_order.set_work_order_operations()
- self.assertEqual(wo_order.planned_operating_cost, cost*2)
+ self.assertEqual(wo_order.planned_operating_cost, cost * 2)
def test_reserved_qty_for_partial_completion(self):
item = "_Test Item"
@@ -102,27 +113,30 @@
# reset to correct value
bin1_at_start.update_reserved_qty_for_production()
- wo_order = make_wo_order_test_record(item="_Test FG Item", qty=2,
- source_warehouse=warehouse, skip_transfer=1)
+ wo_order = make_wo_order_test_record(
+ item="_Test FG Item", qty=2, source_warehouse=warehouse, skip_transfer=1
+ )
reserved_qty_on_submission = cint(get_bin(item, warehouse).reserved_qty_for_production)
# reserved qty for production is updated
self.assertEqual(cint(bin1_at_start.reserved_qty_for_production) + 2, reserved_qty_on_submission)
-
- test_stock_entry.make_stock_entry(item_code="_Test Item",
- target=warehouse, qty=100, basic_rate=100)
- test_stock_entry.make_stock_entry(item_code="_Test Item Home Desktop 100",
- target=warehouse, qty=100, basic_rate=100)
+ test_stock_entry.make_stock_entry(
+ item_code="_Test Item", target=warehouse, qty=100, basic_rate=100
+ )
+ test_stock_entry.make_stock_entry(
+ item_code="_Test Item Home Desktop 100", target=warehouse, qty=100, basic_rate=100
+ )
s = frappe.get_doc(make_stock_entry(wo_order.name, "Manufacture", 1))
s.submit()
bin1_at_completion = get_bin(item, warehouse)
- self.assertEqual(cint(bin1_at_completion.reserved_qty_for_production),
- reserved_qty_on_submission - 1)
+ self.assertEqual(
+ cint(bin1_at_completion.reserved_qty_for_production), reserved_qty_on_submission - 1
+ )
def test_production_item(self):
wo_order = make_wo_order_test_record(item="_Test FG Item", qty=1, do_not_save=True)
@@ -146,16 +160,20 @@
# reset to correct value
self.bin1_at_start.update_reserved_qty_for_production()
- self.wo_order = make_wo_order_test_record(item="_Test FG Item", qty=2,
- source_warehouse=self.warehouse)
+ self.wo_order = make_wo_order_test_record(
+ item="_Test FG Item", qty=2, source_warehouse=self.warehouse
+ )
self.bin1_on_submit = get_bin(self.item, self.warehouse)
# reserved qty for production is updated
- self.assertEqual(cint(self.bin1_at_start.reserved_qty_for_production) + 2,
- cint(self.bin1_on_submit.reserved_qty_for_production))
- self.assertEqual(cint(self.bin1_at_start.projected_qty),
- cint(self.bin1_on_submit.projected_qty) + 2)
+ self.assertEqual(
+ cint(self.bin1_at_start.reserved_qty_for_production) + 2,
+ cint(self.bin1_on_submit.reserved_qty_for_production),
+ )
+ self.assertEqual(
+ cint(self.bin1_at_start.projected_qty), cint(self.bin1_on_submit.projected_qty) + 2
+ )
def test_reserved_qty_for_production_cancel(self):
self.test_reserved_qty_for_production_submit()
@@ -165,52 +183,57 @@
bin1_on_cancel = get_bin(self.item, self.warehouse)
# reserved_qty_for_producion updated
- self.assertEqual(cint(self.bin1_at_start.reserved_qty_for_production),
- cint(bin1_on_cancel.reserved_qty_for_production))
- self.assertEqual(self.bin1_at_start.projected_qty,
- cint(bin1_on_cancel.projected_qty))
+ self.assertEqual(
+ cint(self.bin1_at_start.reserved_qty_for_production),
+ cint(bin1_on_cancel.reserved_qty_for_production),
+ )
+ self.assertEqual(self.bin1_at_start.projected_qty, cint(bin1_on_cancel.projected_qty))
def test_reserved_qty_for_production_on_stock_entry(self):
- test_stock_entry.make_stock_entry(item_code="_Test Item",
- target= self.warehouse, qty=100, basic_rate=100)
- test_stock_entry.make_stock_entry(item_code="_Test Item Home Desktop 100",
- target= self.warehouse, qty=100, basic_rate=100)
+ test_stock_entry.make_stock_entry(
+ item_code="_Test Item", target=self.warehouse, qty=100, basic_rate=100
+ )
+ test_stock_entry.make_stock_entry(
+ item_code="_Test Item Home Desktop 100", target=self.warehouse, qty=100, basic_rate=100
+ )
self.test_reserved_qty_for_production_submit()
- s = frappe.get_doc(make_stock_entry(self.wo_order.name,
- "Material Transfer for Manufacture", 2))
+ s = frappe.get_doc(make_stock_entry(self.wo_order.name, "Material Transfer for Manufacture", 2))
s.submit()
bin1_on_start_production = get_bin(self.item, self.warehouse)
# reserved_qty_for_producion updated
- self.assertEqual(cint(self.bin1_at_start.reserved_qty_for_production),
- cint(bin1_on_start_production.reserved_qty_for_production))
+ self.assertEqual(
+ cint(self.bin1_at_start.reserved_qty_for_production),
+ cint(bin1_on_start_production.reserved_qty_for_production),
+ )
# projected qty will now be 2 less (becuase of item movement)
- self.assertEqual(cint(self.bin1_at_start.projected_qty),
- cint(bin1_on_start_production.projected_qty) + 2)
+ self.assertEqual(
+ cint(self.bin1_at_start.projected_qty), cint(bin1_on_start_production.projected_qty) + 2
+ )
s = frappe.get_doc(make_stock_entry(self.wo_order.name, "Manufacture", 2))
bin1_on_end_production = get_bin(self.item, self.warehouse)
# no change in reserved / projected
- self.assertEqual(cint(bin1_on_end_production.reserved_qty_for_production),
- cint(bin1_on_start_production.reserved_qty_for_production))
+ self.assertEqual(
+ cint(bin1_on_end_production.reserved_qty_for_production),
+ cint(bin1_on_start_production.reserved_qty_for_production),
+ )
def test_reserved_qty_for_production_closed(self):
- wo1 = make_wo_order_test_record(item="_Test FG Item", qty=2,
- source_warehouse=self.warehouse)
+ wo1 = make_wo_order_test_record(item="_Test FG Item", qty=2, source_warehouse=self.warehouse)
item = wo1.required_items[0].item_code
bin_before = get_bin(item, self.warehouse)
bin_before.update_reserved_qty_for_production()
- make_wo_order_test_record(item="_Test FG Item", qty=2,
- source_warehouse=self.warehouse)
+ make_wo_order_test_record(item="_Test FG Item", qty=2, source_warehouse=self.warehouse)
close_work_order(wo1.name, "Closed")
bin_after = get_bin(item, self.warehouse)
@@ -220,10 +243,15 @@
cancel_stock_entry = []
allow_overproduction("overproduction_percentage_for_work_order", 30)
wo_order = make_wo_order_test_record(planned_start_date=now(), qty=100)
- ste1 = test_stock_entry.make_stock_entry(item_code="_Test Item",
- target="_Test Warehouse - _TC", qty=120, basic_rate=5000.0)
- ste2 = test_stock_entry.make_stock_entry(item_code="_Test Item Home Desktop 100",
- target="_Test Warehouse - _TC", qty=240, basic_rate=1000.0)
+ ste1 = test_stock_entry.make_stock_entry(
+ item_code="_Test Item", target="_Test Warehouse - _TC", qty=120, basic_rate=5000.0
+ )
+ ste2 = test_stock_entry.make_stock_entry(
+ item_code="_Test Item Home Desktop 100",
+ target="_Test Warehouse - _TC",
+ qty=240,
+ basic_rate=1000.0,
+ )
cancel_stock_entry.extend([ste1.name, ste2.name])
@@ -253,33 +281,37 @@
allow_overproduction("overproduction_percentage_for_work_order", 0)
def test_reserved_qty_for_stopped_production(self):
- test_stock_entry.make_stock_entry(item_code="_Test Item",
- target= self.warehouse, qty=100, basic_rate=100)
- test_stock_entry.make_stock_entry(item_code="_Test Item Home Desktop 100",
- target= self.warehouse, qty=100, basic_rate=100)
+ test_stock_entry.make_stock_entry(
+ item_code="_Test Item", target=self.warehouse, qty=100, basic_rate=100
+ )
+ test_stock_entry.make_stock_entry(
+ item_code="_Test Item Home Desktop 100", target=self.warehouse, qty=100, basic_rate=100
+ )
# 0 0 0
self.test_reserved_qty_for_production_submit()
- #2 0 -2
+ # 2 0 -2
- s = frappe.get_doc(make_stock_entry(self.wo_order.name,
- "Material Transfer for Manufacture", 1))
+ s = frappe.get_doc(make_stock_entry(self.wo_order.name, "Material Transfer for Manufacture", 1))
s.submit()
- #1 -1 0
+ # 1 -1 0
bin1_on_start_production = get_bin(self.item, self.warehouse)
# reserved_qty_for_producion updated
- self.assertEqual(cint(self.bin1_at_start.reserved_qty_for_production) + 1,
- cint(bin1_on_start_production.reserved_qty_for_production))
+ self.assertEqual(
+ cint(self.bin1_at_start.reserved_qty_for_production) + 1,
+ cint(bin1_on_start_production.reserved_qty_for_production),
+ )
# projected qty will now be 2 less (becuase of item movement)
- self.assertEqual(cint(self.bin1_at_start.projected_qty),
- cint(bin1_on_start_production.projected_qty) + 2)
+ self.assertEqual(
+ cint(self.bin1_at_start.projected_qty), cint(bin1_on_start_production.projected_qty) + 2
+ )
# STOP
stop_unstop(self.wo_order.name, "Stopped")
@@ -287,19 +319,24 @@
bin1_on_stop_production = get_bin(self.item, self.warehouse)
# no change in reserved / projected
- self.assertEqual(cint(bin1_on_stop_production.reserved_qty_for_production),
- cint(self.bin1_at_start.reserved_qty_for_production))
- self.assertEqual(cint(bin1_on_stop_production.projected_qty) + 1,
- cint(self.bin1_at_start.projected_qty))
+ self.assertEqual(
+ cint(bin1_on_stop_production.reserved_qty_for_production),
+ cint(self.bin1_at_start.reserved_qty_for_production),
+ )
+ self.assertEqual(
+ cint(bin1_on_stop_production.projected_qty) + 1, cint(self.bin1_at_start.projected_qty)
+ )
def test_scrap_material_qty(self):
wo_order = make_wo_order_test_record(planned_start_date=now(), qty=2)
# add raw materials to stores
- test_stock_entry.make_stock_entry(item_code="_Test Item",
- target="Stores - _TC", qty=10, basic_rate=5000.0)
- test_stock_entry.make_stock_entry(item_code="_Test Item Home Desktop 100",
- target="Stores - _TC", qty=10, basic_rate=1000.0)
+ test_stock_entry.make_stock_entry(
+ item_code="_Test Item", target="Stores - _TC", qty=10, basic_rate=5000.0
+ )
+ test_stock_entry.make_stock_entry(
+ item_code="_Test Item Home Desktop 100", target="Stores - _TC", qty=10, basic_rate=1000.0
+ )
s = frappe.get_doc(make_stock_entry(wo_order.name, "Material Transfer for Manufacture", 2))
for d in s.get("items"):
@@ -311,8 +348,9 @@
s.insert()
s.submit()
- wo_order_details = frappe.db.get_value("Work Order", wo_order.name,
- ["scrap_warehouse", "qty", "produced_qty", "bom_no"], as_dict=1)
+ wo_order_details = frappe.db.get_value(
+ "Work Order", wo_order.name, ["scrap_warehouse", "qty", "produced_qty", "bom_no"], as_dict=1
+ )
scrap_item_details = get_scrap_item_details(wo_order_details.bom_no)
@@ -321,15 +359,20 @@
for item in s.items:
if item.bom_no and item.item_code in scrap_item_details:
self.assertEqual(wo_order_details.scrap_warehouse, item.t_warehouse)
- self.assertEqual(flt(wo_order_details.qty)*flt(scrap_item_details[item.item_code]), item.qty)
+ self.assertEqual(flt(wo_order_details.qty) * flt(scrap_item_details[item.item_code]), item.qty)
def test_allow_overproduction(self):
allow_overproduction("overproduction_percentage_for_work_order", 0)
wo_order = make_wo_order_test_record(planned_start_date=now(), qty=2)
- test_stock_entry.make_stock_entry(item_code="_Test Item",
- target="_Test Warehouse - _TC", qty=10, basic_rate=5000.0)
- test_stock_entry.make_stock_entry(item_code="_Test Item Home Desktop 100",
- target="_Test Warehouse - _TC", qty=10, basic_rate=1000.0)
+ test_stock_entry.make_stock_entry(
+ item_code="_Test Item", target="_Test Warehouse - _TC", qty=10, basic_rate=5000.0
+ )
+ test_stock_entry.make_stock_entry(
+ item_code="_Test Item Home Desktop 100",
+ target="_Test Warehouse - _TC",
+ qty=10,
+ basic_rate=1000.0,
+ )
s = frappe.get_doc(make_stock_entry(wo_order.name, "Material Transfer for Manufacture", 3))
s.insert()
@@ -346,42 +389,47 @@
so = make_sales_order(item_code="_Test FG Item", qty=2)
allow_overproduction("overproduction_percentage_for_sales_order", 0)
- wo_order = make_wo_order_test_record(planned_start_date=now(),
- sales_order=so.name, qty=3, do_not_save=True)
+ wo_order = make_wo_order_test_record(
+ planned_start_date=now(), sales_order=so.name, qty=3, do_not_save=True
+ )
self.assertRaises(OverProductionError, wo_order.save)
allow_overproduction("overproduction_percentage_for_sales_order", 50)
- wo_order = make_wo_order_test_record(planned_start_date=now(),
- sales_order=so.name, qty=3)
+ wo_order = make_wo_order_test_record(planned_start_date=now(), sales_order=so.name, qty=3)
self.assertEqual(wo_order.docstatus, 1)
allow_overproduction("overproduction_percentage_for_sales_order", 0)
def test_work_order_with_non_stock_item(self):
- items = {'Finished Good Test Item For non stock': 1, '_Test FG Item': 1, '_Test FG Non Stock Item': 0}
+ items = {
+ "Finished Good Test Item For non stock": 1,
+ "_Test FG Item": 1,
+ "_Test FG Non Stock Item": 0,
+ }
for item, is_stock_item in items.items():
- make_item(item, {
- 'is_stock_item': is_stock_item
- })
+ make_item(item, {"is_stock_item": is_stock_item})
- if not frappe.db.get_value('Item Price', {'item_code': '_Test FG Non Stock Item'}):
- frappe.get_doc({
- 'doctype': 'Item Price',
- 'item_code': '_Test FG Non Stock Item',
- 'price_list_rate': 1000,
- 'price_list': 'Standard Buying'
- }).insert(ignore_permissions=True)
+ if not frappe.db.get_value("Item Price", {"item_code": "_Test FG Non Stock Item"}):
+ frappe.get_doc(
+ {
+ "doctype": "Item Price",
+ "item_code": "_Test FG Non Stock Item",
+ "price_list_rate": 1000,
+ "price_list": "Standard Buying",
+ }
+ ).insert(ignore_permissions=True)
- fg_item = 'Finished Good Test Item For non stock'
- test_stock_entry.make_stock_entry(item_code="_Test FG Item",
- target="_Test Warehouse - _TC", qty=1, basic_rate=100)
+ fg_item = "Finished Good Test Item For non stock"
+ test_stock_entry.make_stock_entry(
+ item_code="_Test FG Item", target="_Test Warehouse - _TC", qty=1, basic_rate=100
+ )
- if not frappe.db.get_value('BOM', {'item': fg_item}):
- make_bom(item=fg_item, rate=1000, raw_materials = ['_Test FG Item', '_Test FG Non Stock Item'])
+ if not frappe.db.get_value("BOM", {"item": fg_item}):
+ make_bom(item=fg_item, rate=1000, raw_materials=["_Test FG Item", "_Test FG Non Stock Item"])
- wo = make_wo_order_test_record(production_item = fg_item)
+ wo = make_wo_order_test_record(production_item=fg_item)
se = frappe.get_doc(make_stock_entry(wo.name, "Material Transfer for Manufacture", 1))
se.insert()
@@ -395,25 +443,25 @@
@timeout(seconds=60)
def test_job_card(self):
stock_entries = []
- bom = frappe.get_doc('BOM', {
- 'docstatus': 1,
- 'with_operations': 1,
- 'company': '_Test Company'
- })
+ bom = frappe.get_doc("BOM", {"docstatus": 1, "with_operations": 1, "company": "_Test Company"})
- work_order = make_wo_order_test_record(item=bom.item, qty=1,
- bom_no=bom.name, source_warehouse="_Test Warehouse - _TC")
+ work_order = make_wo_order_test_record(
+ item=bom.item, qty=1, bom_no=bom.name, source_warehouse="_Test Warehouse - _TC"
+ )
for row in work_order.required_items:
- stock_entry_doc = test_stock_entry.make_stock_entry(item_code=row.item_code,
- target="_Test Warehouse - _TC", qty=row.required_qty, basic_rate=100)
+ stock_entry_doc = test_stock_entry.make_stock_entry(
+ item_code=row.item_code, target="_Test Warehouse - _TC", qty=row.required_qty, basic_rate=100
+ )
stock_entries.append(stock_entry_doc)
ste = frappe.get_doc(make_stock_entry(work_order.name, "Material Transfer for Manufacture", 1))
ste.submit()
stock_entries.append(ste)
- job_cards = frappe.get_all('Job Card', filters = {'work_order': work_order.name}, order_by='creation asc')
+ job_cards = frappe.get_all(
+ "Job Card", filters={"work_order": work_order.name}, order_by="creation asc"
+ )
self.assertEqual(len(job_cards), len(bom.operations))
for i, job_card in enumerate(job_cards):
@@ -434,29 +482,33 @@
stock_entry.cancel()
def test_capcity_planning(self):
- frappe.db.set_value("Manufacturing Settings", None, {
- "disable_capacity_planning": 0,
- "capacity_planning_for_days": 1
- })
+ frappe.db.set_value(
+ "Manufacturing Settings",
+ None,
+ {"disable_capacity_planning": 0, "capacity_planning_for_days": 1},
+ )
- data = frappe.get_cached_value('BOM', {'docstatus': 1, 'item': '_Test FG Item 2',
- 'with_operations': 1, 'company': '_Test Company'}, ['name', 'item'])
+ data = frappe.get_cached_value(
+ "BOM",
+ {"docstatus": 1, "item": "_Test FG Item 2", "with_operations": 1, "company": "_Test Company"},
+ ["name", "item"],
+ )
if data:
bom, bom_item = data
planned_start_date = add_months(today(), months=-1)
- work_order = make_wo_order_test_record(item=bom_item,
- qty=10, bom_no=bom, planned_start_date=planned_start_date)
+ work_order = make_wo_order_test_record(
+ item=bom_item, qty=10, bom_no=bom, planned_start_date=planned_start_date
+ )
- work_order1 = make_wo_order_test_record(item=bom_item,
- qty=30, bom_no=bom, planned_start_date=planned_start_date, do_not_submit=1)
+ work_order1 = make_wo_order_test_record(
+ item=bom_item, qty=30, bom_no=bom, planned_start_date=planned_start_date, do_not_submit=1
+ )
self.assertRaises(CapacityError, work_order1.submit)
- frappe.db.set_value("Manufacturing Settings", None, {
- "capacity_planning_for_days": 30
- })
+ frappe.db.set_value("Manufacturing Settings", None, {"capacity_planning_for_days": 30})
work_order1.reload()
work_order1.submit()
@@ -466,22 +518,22 @@
work_order.cancel()
def test_work_order_with_non_transfer_item(self):
- items = {'Finished Good Transfer Item': 1, '_Test FG Item': 1, '_Test FG Item 1': 0}
+ items = {"Finished Good Transfer Item": 1, "_Test FG Item": 1, "_Test FG Item 1": 0}
for item, allow_transfer in items.items():
- make_item(item, {
- 'include_item_in_manufacturing': allow_transfer
- })
+ make_item(item, {"include_item_in_manufacturing": allow_transfer})
- fg_item = 'Finished Good Transfer Item'
- test_stock_entry.make_stock_entry(item_code="_Test FG Item",
- target="_Test Warehouse - _TC", qty=1, basic_rate=100)
- test_stock_entry.make_stock_entry(item_code="_Test FG Item 1",
- target="_Test Warehouse - _TC", qty=1, basic_rate=100)
+ fg_item = "Finished Good Transfer Item"
+ test_stock_entry.make_stock_entry(
+ item_code="_Test FG Item", target="_Test Warehouse - _TC", qty=1, basic_rate=100
+ )
+ test_stock_entry.make_stock_entry(
+ item_code="_Test FG Item 1", target="_Test Warehouse - _TC", qty=1, basic_rate=100
+ )
- if not frappe.db.get_value('BOM', {'item': fg_item}):
- make_bom(item=fg_item, raw_materials = ['_Test FG Item', '_Test FG Item 1'])
+ if not frappe.db.get_value("BOM", {"item": fg_item}):
+ make_bom(item=fg_item, raw_materials=["_Test FG Item", "_Test FG Item 1"])
- wo = make_wo_order_test_record(production_item = fg_item)
+ wo = make_wo_order_test_record(production_item=fg_item)
ste = frappe.get_doc(make_stock_entry(wo.name, "Material Transfer for Manufacture", 1))
ste.insert()
ste.submit()
@@ -499,39 +551,42 @@
rm1 = "Test Batch Size Item RM 1 For BOM"
for item in ["Test Batch Size Item For BOM", "Test Batch Size Item RM 1 For BOM"]:
- make_item(item, {
- "include_item_in_manufacturing": 1,
- "is_stock_item": 1
- })
+ make_item(item, {"include_item_in_manufacturing": 1, "is_stock_item": 1})
- bom_name = frappe.db.get_value("BOM",
- {"item": fg_item, "is_active": 1, "with_operations": 1}, "name")
+ bom_name = frappe.db.get_value(
+ "BOM", {"item": fg_item, "is_active": 1, "with_operations": 1}, "name"
+ )
if not bom_name:
- bom = make_bom(item=fg_item, rate=1000, raw_materials = [rm1], do_not_save=True)
+ bom = make_bom(item=fg_item, rate=1000, raw_materials=[rm1], do_not_save=True)
bom.with_operations = 1
- bom.append("operations", {
- "operation": "_Test Operation 1",
- "workstation": "_Test Workstation 1",
- "description": "Test Data",
- "operating_cost": 100,
- "time_in_mins": 40,
- "batch_size": 5
- })
+ bom.append(
+ "operations",
+ {
+ "operation": "_Test Operation 1",
+ "workstation": "_Test Workstation 1",
+ "description": "Test Data",
+ "operating_cost": 100,
+ "time_in_mins": 40,
+ "batch_size": 5,
+ },
+ )
bom.save()
bom.submit()
bom_name = bom.name
- work_order = make_wo_order_test_record(item=fg_item,
- planned_start_date=now(), qty=1, do_not_save=True)
+ work_order = make_wo_order_test_record(
+ item=fg_item, planned_start_date=now(), qty=1, do_not_save=True
+ )
work_order.set_work_order_operations()
work_order.save()
self.assertEqual(work_order.operations[0].time_in_mins, 8.0)
- work_order1 = make_wo_order_test_record(item=fg_item,
- planned_start_date=now(), qty=5, do_not_save=True)
+ work_order1 = make_wo_order_test_record(
+ item=fg_item, planned_start_date=now(), qty=5, do_not_save=True
+ )
work_order1.set_work_order_operations()
work_order1.save()
@@ -541,65 +596,73 @@
fg_item = "Test Batch Size Item For BOM 3"
rm1 = "Test Batch Size Item RM 1 For BOM 3"
- frappe.db.set_value('Manufacturing Settings', None, 'make_serial_no_batch_from_work_order', 0)
+ frappe.db.set_value("Manufacturing Settings", None, "make_serial_no_batch_from_work_order", 0)
for item in ["Test Batch Size Item For BOM 3", "Test Batch Size Item RM 1 For BOM 3"]:
- item_args = {
- "include_item_in_manufacturing": 1,
- "is_stock_item": 1
- }
+ item_args = {"include_item_in_manufacturing": 1, "is_stock_item": 1}
if item == fg_item:
- item_args['has_batch_no'] = 1
- item_args['create_new_batch'] = 1
- item_args['batch_number_series'] = 'TBSI3.#####'
+ item_args["has_batch_no"] = 1
+ item_args["create_new_batch"] = 1
+ item_args["batch_number_series"] = "TBSI3.#####"
make_item(item, item_args)
- bom_name = frappe.db.get_value("BOM",
- {"item": fg_item, "is_active": 1, "with_operations": 1}, "name")
+ bom_name = frappe.db.get_value(
+ "BOM", {"item": fg_item, "is_active": 1, "with_operations": 1}, "name"
+ )
if not bom_name:
- bom = make_bom(item=fg_item, rate=1000, raw_materials = [rm1], do_not_save=True)
+ bom = make_bom(item=fg_item, rate=1000, raw_materials=[rm1], do_not_save=True)
bom.save()
bom.submit()
bom_name = bom.name
- work_order = make_wo_order_test_record(item=fg_item, skip_transfer=True, planned_start_date=now(), qty=1)
+ work_order = make_wo_order_test_record(
+ item=fg_item, skip_transfer=True, planned_start_date=now(), qty=1
+ )
ste1 = frappe.get_doc(make_stock_entry(work_order.name, "Manufacture", 1))
- for row in ste1.get('items'):
+ for row in ste1.get("items"):
if row.is_finished_item:
self.assertEqual(row.item_code, fg_item)
- work_order = make_wo_order_test_record(item=fg_item, skip_transfer=True, planned_start_date=now(), qty=1)
- frappe.db.set_value('Manufacturing Settings', None, 'make_serial_no_batch_from_work_order', 1)
+ work_order = make_wo_order_test_record(
+ item=fg_item, skip_transfer=True, planned_start_date=now(), qty=1
+ )
+ frappe.db.set_value("Manufacturing Settings", None, "make_serial_no_batch_from_work_order", 1)
ste1 = frappe.get_doc(make_stock_entry(work_order.name, "Manufacture", 1))
- for row in ste1.get('items'):
+ for row in ste1.get("items"):
if row.is_finished_item:
self.assertEqual(row.item_code, fg_item)
- work_order = make_wo_order_test_record(item=fg_item, skip_transfer=True, planned_start_date=now(),
- qty=30, do_not_save = True)
+ work_order = make_wo_order_test_record(
+ item=fg_item, skip_transfer=True, planned_start_date=now(), qty=30, do_not_save=True
+ )
work_order.batch_size = 10
work_order.insert()
work_order.submit()
self.assertEqual(work_order.has_batch_no, 1)
ste1 = frappe.get_doc(make_stock_entry(work_order.name, "Manufacture", 30))
- for row in ste1.get('items'):
+ for row in ste1.get("items"):
if row.is_finished_item:
self.assertEqual(row.item_code, fg_item)
self.assertEqual(row.qty, 10)
- frappe.db.set_value('Manufacturing Settings', None, 'make_serial_no_batch_from_work_order', 0)
+ frappe.db.set_value("Manufacturing Settings", None, "make_serial_no_batch_from_work_order", 0)
def test_partial_material_consumption(self):
frappe.db.set_value("Manufacturing Settings", None, "material_consumption", 1)
wo_order = make_wo_order_test_record(planned_start_date=now(), qty=4)
ste_cancel_list = []
- ste1 = test_stock_entry.make_stock_entry(item_code="_Test Item",
- target="_Test Warehouse - _TC", qty=20, basic_rate=5000.0)
- ste2 = test_stock_entry.make_stock_entry(item_code="_Test Item Home Desktop 100",
- target="_Test Warehouse - _TC", qty=20, basic_rate=1000.0)
+ ste1 = test_stock_entry.make_stock_entry(
+ item_code="_Test Item", target="_Test Warehouse - _TC", qty=20, basic_rate=5000.0
+ )
+ ste2 = test_stock_entry.make_stock_entry(
+ item_code="_Test Item Home Desktop 100",
+ target="_Test Warehouse - _TC",
+ qty=20,
+ basic_rate=1000.0,
+ )
ste_cancel_list.extend([ste1, ste2])
@@ -625,16 +688,25 @@
def test_extra_material_transfer(self):
frappe.db.set_value("Manufacturing Settings", None, "material_consumption", 0)
- frappe.db.set_value("Manufacturing Settings", None, "backflush_raw_materials_based_on",
- "Material Transferred for Manufacture")
+ frappe.db.set_value(
+ "Manufacturing Settings",
+ None,
+ "backflush_raw_materials_based_on",
+ "Material Transferred for Manufacture",
+ )
wo_order = make_wo_order_test_record(planned_start_date=now(), qty=4)
ste_cancel_list = []
- ste1 = test_stock_entry.make_stock_entry(item_code="_Test Item",
- target="_Test Warehouse - _TC", qty=20, basic_rate=5000.0)
- ste2 = test_stock_entry.make_stock_entry(item_code="_Test Item Home Desktop 100",
- target="_Test Warehouse - _TC", qty=20, basic_rate=1000.0)
+ ste1 = test_stock_entry.make_stock_entry(
+ item_code="_Test Item", target="_Test Warehouse - _TC", qty=20, basic_rate=5000.0
+ )
+ ste2 = test_stock_entry.make_stock_entry(
+ item_code="_Test Item Home Desktop 100",
+ target="_Test Warehouse - _TC",
+ qty=20,
+ basic_rate=1000.0,
+ )
ste_cancel_list.extend([ste1, ste2])
@@ -666,30 +738,31 @@
frappe.db.set_value("Manufacturing Settings", None, "backflush_raw_materials_based_on", "BOM")
def test_make_stock_entry_for_customer_provided_item(self):
- finished_item = 'Test Item for Make Stock Entry 1'
- make_item(finished_item, {
+ finished_item = "Test Item for Make Stock Entry 1"
+ make_item(finished_item, {"include_item_in_manufacturing": 1, "is_stock_item": 1})
+
+ customer_provided_item = "CUST-0987"
+ make_item(
+ customer_provided_item,
+ {
+ "is_purchase_item": 0,
+ "is_customer_provided_item": 1,
+ "is_stock_item": 1,
"include_item_in_manufacturing": 1,
- "is_stock_item": 1
- })
+ "customer": "_Test Customer",
+ },
+ )
- customer_provided_item = 'CUST-0987'
- make_item(customer_provided_item, {
- 'is_purchase_item': 0,
- 'is_customer_provided_item': 1,
- "is_stock_item": 1,
- "include_item_in_manufacturing": 1,
- 'customer': '_Test Customer'
- })
-
- if not frappe.db.exists('BOM', {'item': finished_item}):
+ if not frappe.db.exists("BOM", {"item": finished_item}):
make_bom(item=finished_item, raw_materials=[customer_provided_item], rm_qty=1)
company = "_Test Company with perpetual inventory"
customer_warehouse = create_warehouse("Test Customer Provided Warehouse", company=company)
- wo = make_wo_order_test_record(item=finished_item, qty=1, source_warehouse=customer_warehouse,
- company=company)
+ wo = make_wo_order_test_record(
+ item=finished_item, qty=1, source_warehouse=customer_warehouse, company=company
+ )
- ste = frappe.get_doc(make_stock_entry(wo.name, purpose='Material Transfer for Manufacture'))
+ ste = frappe.get_doc(make_stock_entry(wo.name, purpose="Material Transfer for Manufacture"))
ste.insert()
self.assertEqual(len(ste.items), 1)
@@ -698,26 +771,33 @@
self.assertEqual(item.valuation_rate, 0)
def test_valuation_rate_missing_on_make_stock_entry(self):
- item_name = 'Test Valuation Rate Missing'
- rm_item = '_Test raw material item'
- make_item(item_name, {
- "is_stock_item": 1,
- "include_item_in_manufacturing": 1,
- })
- make_item('_Test raw material item', {
- "is_stock_item": 1,
- "include_item_in_manufacturing": 1,
- })
+ item_name = "Test Valuation Rate Missing"
+ rm_item = "_Test raw material item"
+ make_item(
+ item_name,
+ {
+ "is_stock_item": 1,
+ "include_item_in_manufacturing": 1,
+ },
+ )
+ make_item(
+ "_Test raw material item",
+ {
+ "is_stock_item": 1,
+ "include_item_in_manufacturing": 1,
+ },
+ )
- if not frappe.db.get_value('BOM', {'item': item_name}):
+ if not frappe.db.get_value("BOM", {"item": item_name}):
make_bom(item=item_name, raw_materials=[rm_item], rm_qty=1)
company = "_Test Company with perpetual inventory"
source_warehouse = create_warehouse("Test Valuation Rate Missing Warehouse", company=company)
- wo = make_wo_order_test_record(item=item_name, qty=1, source_warehouse=source_warehouse,
- company=company)
+ wo = make_wo_order_test_record(
+ item=item_name, qty=1, source_warehouse=source_warehouse, company=company
+ )
- stock_entry = frappe.get_doc(make_stock_entry(wo.name, 'Material Transfer for Manufacture'))
+ stock_entry = frappe.get_doc(make_stock_entry(wo.name, "Material Transfer for Manufacture"))
self.assertRaises(frappe.ValidationError, stock_entry.save)
def test_wo_completion_with_pl_bom(self):
@@ -727,19 +807,19 @@
)
qty = 4
- scrap_qty = 0.25 # bom item qty = 1, consider as 25% of FG
+ scrap_qty = 0.25 # bom item qty = 1, consider as 25% of FG
source_warehouse = "Stores - _TC"
wip_warehouse = "_Test Warehouse - _TC"
fg_item_non_whole, _, bom_item = create_process_loss_bom_items()
- test_stock_entry.make_stock_entry(item_code=bom_item.item_code,
- target=source_warehouse, qty=4, basic_rate=100)
+ test_stock_entry.make_stock_entry(
+ item_code=bom_item.item_code, target=source_warehouse, qty=4, basic_rate=100
+ )
bom_no = f"BOM-{fg_item_non_whole.item_code}-001"
if not frappe.db.exists("BOM", bom_no):
bom_doc = create_bom_with_process_loss_item(
- fg_item_non_whole, bom_item, scrap_qty=scrap_qty,
- scrap_rate=0, fg_qty=1, is_process_loss=1
+ fg_item_non_whole, bom_item, scrap_qty=scrap_qty, scrap_rate=0, fg_qty=1, is_process_loss=1
)
bom_doc.submit()
@@ -752,16 +832,12 @@
stock_uom=fg_item_non_whole.stock_uom,
)
- se = frappe.get_doc(
- make_stock_entry(wo.name, "Material Transfer for Manufacture", qty)
- )
+ se = frappe.get_doc(make_stock_entry(wo.name, "Material Transfer for Manufacture", qty))
se.get("items")[0].s_warehouse = "Stores - _TC"
se.insert()
se.submit()
- se = frappe.get_doc(
- make_stock_entry(wo.name, "Manufacture", qty)
- )
+ se = frappe.get_doc(make_stock_entry(wo.name, "Manufacture", qty))
se.insert()
se.submit()
@@ -778,41 +854,52 @@
self.assertEqual(fg_item.qty, actual_fg_qty)
# Testing Work Order values
- self.assertEqual(
- frappe.db.get_value("Work Order", wo.name, "produced_qty"),
- qty
- )
- self.assertEqual(
- frappe.db.get_value("Work Order", wo.name, "process_loss_qty"),
- total_pl_qty
- )
+ self.assertEqual(frappe.db.get_value("Work Order", wo.name, "produced_qty"), qty)
+ self.assertEqual(frappe.db.get_value("Work Order", wo.name, "process_loss_qty"), total_pl_qty)
@timeout(seconds=60)
def test_job_card_scrap_item(self):
- items = ['Test FG Item for Scrap Item Test', 'Test RM Item 1 for Scrap Item Test',
- 'Test RM Item 2 for Scrap Item Test']
+ items = [
+ "Test FG Item for Scrap Item Test",
+ "Test RM Item 1 for Scrap Item Test",
+ "Test RM Item 2 for Scrap Item Test",
+ ]
- company = '_Test Company with perpetual inventory'
+ company = "_Test Company with perpetual inventory"
for item_code in items:
- create_item(item_code = item_code, is_stock_item = 1,
- is_purchase_item=1, opening_stock=100, valuation_rate=10, company=company, warehouse='Stores - TCP1')
+ create_item(
+ item_code=item_code,
+ is_stock_item=1,
+ is_purchase_item=1,
+ opening_stock=100,
+ valuation_rate=10,
+ company=company,
+ warehouse="Stores - TCP1",
+ )
- item = 'Test FG Item for Scrap Item Test'
- raw_materials = ['Test RM Item 1 for Scrap Item Test', 'Test RM Item 2 for Scrap Item Test']
- if not frappe.db.get_value('BOM', {'item': item}):
- bom = make_bom(item=item, source_warehouse='Stores - TCP1', raw_materials=raw_materials, do_not_save=True)
+ item = "Test FG Item for Scrap Item Test"
+ raw_materials = ["Test RM Item 1 for Scrap Item Test", "Test RM Item 2 for Scrap Item Test"]
+ if not frappe.db.get_value("BOM", {"item": item}):
+ bom = make_bom(
+ item=item, source_warehouse="Stores - TCP1", raw_materials=raw_materials, do_not_save=True
+ )
bom.with_operations = 1
- bom.append('operations', {
- 'operation': '_Test Operation 1',
- 'workstation': '_Test Workstation 1',
- 'hour_rate': 20,
- 'time_in_mins': 60
- })
+ bom.append(
+ "operations",
+ {
+ "operation": "_Test Operation 1",
+ "workstation": "_Test Workstation 1",
+ "hour_rate": 20,
+ "time_in_mins": 60,
+ },
+ )
bom.submit()
- wo_order = make_wo_order_test_record(item=item, company=company, planned_start_date=now(), qty=20, skip_transfer=1)
- job_card = frappe.db.get_value('Job Card', {'work_order': wo_order.name}, 'name')
+ wo_order = make_wo_order_test_record(
+ item=item, company=company, planned_start_date=now(), qty=20, skip_transfer=1
+ )
+ job_card = frappe.db.get_value("Job Card", {"work_order": wo_order.name}, "name")
update_job_card(job_card)
stock_entry = frappe.get_doc(make_stock_entry(wo_order.name, "Manufacture", 10))
@@ -821,8 +908,10 @@
self.assertEqual(row.qty, 1)
# Partial Job Card 1 with qty 10
- wo_order = make_wo_order_test_record(item=item, company=company, planned_start_date=add_days(now(), 60), qty=20, skip_transfer=1)
- job_card = frappe.db.get_value('Job Card', {'work_order': wo_order.name}, 'name')
+ wo_order = make_wo_order_test_record(
+ item=item, company=company, planned_start_date=add_days(now(), 60), qty=20, skip_transfer=1
+ )
+ job_card = frappe.db.get_value("Job Card", {"work_order": wo_order.name}, "name")
update_job_card(job_card, 10)
stock_entry = frappe.get_doc(make_stock_entry(wo_order.name, "Manufacture", 10))
@@ -835,12 +924,12 @@
wo_order.load_from_db()
for row in wo_order.operations:
n_dict = row.as_dict()
- n_dict['qty'] = 10
- n_dict['pending_qty'] = 10
+ n_dict["qty"] = 10
+ n_dict["pending_qty"] = 10
operations.append(n_dict)
make_job_card(wo_order.name, operations)
- job_card = frappe.db.get_value('Job Card', {'work_order': wo_order.name, 'docstatus': 0}, 'name')
+ job_card = frappe.db.get_value("Job Card", {"work_order": wo_order.name, "docstatus": 0}, "name")
update_job_card(job_card, 10)
stock_entry = frappe.get_doc(make_stock_entry(wo_order.name, "Manufacture", 10))
@@ -849,131 +938,160 @@
self.assertEqual(row.qty, 2)
def test_close_work_order(self):
- items = ['Test FG Item for Closed WO', 'Test RM Item 1 for Closed WO',
- 'Test RM Item 2 for Closed WO']
+ items = [
+ "Test FG Item for Closed WO",
+ "Test RM Item 1 for Closed WO",
+ "Test RM Item 2 for Closed WO",
+ ]
- company = '_Test Company with perpetual inventory'
+ company = "_Test Company with perpetual inventory"
for item_code in items:
- create_item(item_code = item_code, is_stock_item = 1,
- is_purchase_item=1, opening_stock=100, valuation_rate=10, company=company, warehouse='Stores - TCP1')
+ create_item(
+ item_code=item_code,
+ is_stock_item=1,
+ is_purchase_item=1,
+ opening_stock=100,
+ valuation_rate=10,
+ company=company,
+ warehouse="Stores - TCP1",
+ )
- item = 'Test FG Item for Closed WO'
- raw_materials = ['Test RM Item 1 for Closed WO', 'Test RM Item 2 for Closed WO']
- if not frappe.db.get_value('BOM', {'item': item}):
- bom = make_bom(item=item, source_warehouse='Stores - TCP1', raw_materials=raw_materials, do_not_save=True)
+ item = "Test FG Item for Closed WO"
+ raw_materials = ["Test RM Item 1 for Closed WO", "Test RM Item 2 for Closed WO"]
+ if not frappe.db.get_value("BOM", {"item": item}):
+ bom = make_bom(
+ item=item, source_warehouse="Stores - TCP1", raw_materials=raw_materials, do_not_save=True
+ )
bom.with_operations = 1
- bom.append('operations', {
- 'operation': '_Test Operation 1',
- 'workstation': '_Test Workstation 1',
- 'hour_rate': 20,
- 'time_in_mins': 60
- })
+ bom.append(
+ "operations",
+ {
+ "operation": "_Test Operation 1",
+ "workstation": "_Test Workstation 1",
+ "hour_rate": 20,
+ "time_in_mins": 60,
+ },
+ )
bom.submit()
- wo_order = make_wo_order_test_record(item=item, company=company, planned_start_date=now(), qty=20, skip_transfer=1)
- job_cards = frappe.db.get_value('Job Card', {'work_order': wo_order.name}, 'name')
+ wo_order = make_wo_order_test_record(
+ item=item, company=company, planned_start_date=now(), qty=20, skip_transfer=1
+ )
+ job_cards = frappe.db.get_value("Job Card", {"work_order": wo_order.name}, "name")
if len(job_cards) == len(bom.operations):
for jc in job_cards:
- job_card_doc = frappe.get_doc('Job Card', jc)
- job_card_doc.append('time_logs', {
- 'from_time': now(),
- 'time_in_mins': 60,
- 'completed_qty': job_card_doc.for_quantity
- })
+ job_card_doc = frappe.get_doc("Job Card", jc)
+ job_card_doc.append(
+ "time_logs",
+ {"from_time": now(), "time_in_mins": 60, "completed_qty": job_card_doc.for_quantity},
+ )
job_card_doc.submit()
close_work_order(wo_order, "Closed")
- self.assertEqual(wo_order.get('status'), "Closed")
+ self.assertEqual(wo_order.get("status"), "Closed")
def test_fix_time_operations(self):
- bom = frappe.get_doc({
- "doctype": "BOM",
- "item": "_Test FG Item 2",
- "is_active": 1,
- "is_default": 1,
- "quantity": 1.0,
- "with_operations": 1,
- "operations": [
- {
- "operation": "_Test Operation 1",
- "description": "_Test",
- "workstation": "_Test Workstation 1",
- "time_in_mins": 60,
- "operating_cost": 140,
- "fixed_time": 1
- }
- ],
- "items": [
- {
- "amount": 5000.0,
- "doctype": "BOM Item",
- "item_code": "_Test Item",
- "parentfield": "items",
- "qty": 1.0,
- "rate": 5000.0,
- },
- ],
- })
+ bom = frappe.get_doc(
+ {
+ "doctype": "BOM",
+ "item": "_Test FG Item 2",
+ "is_active": 1,
+ "is_default": 1,
+ "quantity": 1.0,
+ "with_operations": 1,
+ "operations": [
+ {
+ "operation": "_Test Operation 1",
+ "description": "_Test",
+ "workstation": "_Test Workstation 1",
+ "time_in_mins": 60,
+ "operating_cost": 140,
+ "fixed_time": 1,
+ }
+ ],
+ "items": [
+ {
+ "amount": 5000.0,
+ "doctype": "BOM Item",
+ "item_code": "_Test Item",
+ "parentfield": "items",
+ "qty": 1.0,
+ "rate": 5000.0,
+ },
+ ],
+ }
+ )
bom.save()
bom.submit()
-
- wo1 = make_wo_order_test_record(item=bom.item, bom_no=bom.name, qty=1, skip_transfer=1, do_not_submit=1)
- wo2 = make_wo_order_test_record(item=bom.item, bom_no=bom.name, qty=2, skip_transfer=1, do_not_submit=1)
+ wo1 = make_wo_order_test_record(
+ item=bom.item, bom_no=bom.name, qty=1, skip_transfer=1, do_not_submit=1
+ )
+ wo2 = make_wo_order_test_record(
+ item=bom.item, bom_no=bom.name, qty=2, skip_transfer=1, do_not_submit=1
+ )
self.assertEqual(wo1.operations[0].time_in_mins, wo2.operations[0].time_in_mins)
def test_partial_manufacture_entries(self):
cancel_stock_entry = []
- frappe.db.set_value("Manufacturing Settings", None,
- "backflush_raw_materials_based_on", "Material Transferred for Manufacture")
+ frappe.db.set_value(
+ "Manufacturing Settings",
+ None,
+ "backflush_raw_materials_based_on",
+ "Material Transferred for Manufacture",
+ )
wo_order = make_wo_order_test_record(planned_start_date=now(), qty=100)
- ste1 = test_stock_entry.make_stock_entry(item_code="_Test Item",
- target="_Test Warehouse - _TC", qty=120, basic_rate=5000.0)
- ste2 = test_stock_entry.make_stock_entry(item_code="_Test Item Home Desktop 100",
- target="_Test Warehouse - _TC", qty=240, basic_rate=1000.0)
+ ste1 = test_stock_entry.make_stock_entry(
+ item_code="_Test Item", target="_Test Warehouse - _TC", qty=120, basic_rate=5000.0
+ )
+ ste2 = test_stock_entry.make_stock_entry(
+ item_code="_Test Item Home Desktop 100",
+ target="_Test Warehouse - _TC",
+ qty=240,
+ basic_rate=1000.0,
+ )
cancel_stock_entry.extend([ste1.name, ste2.name])
sm = frappe.get_doc(make_stock_entry(wo_order.name, "Material Transfer for Manufacture", 100))
- for row in sm.get('items'):
- if row.get('item_code') == '_Test Item':
+ for row in sm.get("items"):
+ if row.get("item_code") == "_Test Item":
row.qty = 110
sm.submit()
cancel_stock_entry.append(sm.name)
s = frappe.get_doc(make_stock_entry(wo_order.name, "Manufacture", 90))
- for row in s.get('items'):
- if row.get('item_code') == '_Test Item':
- self.assertEqual(row.get('qty'), 100)
+ for row in s.get("items"):
+ if row.get("item_code") == "_Test Item":
+ self.assertEqual(row.get("qty"), 100)
s.submit()
cancel_stock_entry.append(s.name)
s1 = frappe.get_doc(make_stock_entry(wo_order.name, "Manufacture", 5))
- for row in s1.get('items'):
- if row.get('item_code') == '_Test Item':
- self.assertEqual(row.get('qty'), 5)
+ for row in s1.get("items"):
+ if row.get("item_code") == "_Test Item":
+ self.assertEqual(row.get("qty"), 5)
s1.submit()
cancel_stock_entry.append(s1.name)
s2 = frappe.get_doc(make_stock_entry(wo_order.name, "Manufacture", 5))
- for row in s2.get('items'):
- if row.get('item_code') == '_Test Item':
- self.assertEqual(row.get('qty'), 5)
+ for row in s2.get("items"):
+ if row.get("item_code") == "_Test Item":
+ self.assertEqual(row.get("qty"), 5)
cancel_stock_entry.reverse()
for ste in cancel_stock_entry:
doc = frappe.get_doc("Stock Entry", ste)
doc.cancel()
- frappe.db.set_value("Manufacturing Settings", None,
- "backflush_raw_materials_based_on", "BOM")
+ frappe.db.set_value("Manufacturing Settings", None, "backflush_raw_materials_based_on", "BOM")
@change_settings("Manufacturing Settings", {"make_serial_no_batch_from_work_order": 1})
def test_auto_batch_creation(self):
@@ -982,7 +1100,7 @@
fg_item = frappe.generate_hash(length=20)
child_item = frappe.generate_hash(length=20)
- bom_tree = {fg_item: {child_item: {}}}
+ bom_tree = {fg_item: {child_item: {}}}
create_nested_bom(bom_tree, prefix="")
@@ -998,54 +1116,55 @@
def update_job_card(job_card, jc_qty=None):
- employee = frappe.db.get_value('Employee', {'status': 'Active'}, 'name')
- job_card_doc = frappe.get_doc('Job Card', job_card)
- job_card_doc.set('scrap_items', [
- {
- 'item_code': 'Test RM Item 1 for Scrap Item Test',
- 'stock_qty': 2
- },
- {
- 'item_code': 'Test RM Item 2 for Scrap Item Test',
- 'stock_qty': 2
- },
- ])
+ employee = frappe.db.get_value("Employee", {"status": "Active"}, "name")
+ job_card_doc = frappe.get_doc("Job Card", job_card)
+ job_card_doc.set(
+ "scrap_items",
+ [
+ {"item_code": "Test RM Item 1 for Scrap Item Test", "stock_qty": 2},
+ {"item_code": "Test RM Item 2 for Scrap Item Test", "stock_qty": 2},
+ ],
+ )
if jc_qty:
job_card_doc.for_quantity = jc_qty
- job_card_doc.append('time_logs', {
- 'from_time': now(),
- 'employee': employee,
- 'time_in_mins': 60,
- 'completed_qty': job_card_doc.for_quantity
- })
+ job_card_doc.append(
+ "time_logs",
+ {
+ "from_time": now(),
+ "employee": employee,
+ "time_in_mins": 60,
+ "completed_qty": job_card_doc.for_quantity,
+ },
+ )
job_card_doc.submit()
def get_scrap_item_details(bom_no):
scrap_items = {}
- for item in frappe.db.sql("""select item_code, stock_qty from `tabBOM Scrap Item`
- where parent = %s""", bom_no, as_dict=1):
+ for item in frappe.db.sql(
+ """select item_code, stock_qty from `tabBOM Scrap Item`
+ where parent = %s""",
+ bom_no,
+ as_dict=1,
+ ):
scrap_items[item.item_code] = item.stock_qty
return scrap_items
+
def allow_overproduction(fieldname, percentage):
doc = frappe.get_doc("Manufacturing Settings")
- doc.update({
- fieldname: percentage
- })
+ doc.update({fieldname: percentage})
doc.save()
+
def make_wo_order_test_record(**args):
args = frappe._dict(args)
if args.company and args.company != "_Test Company":
- warehouse_map = {
- "fg_warehouse": "_Test FG Warehouse",
- "wip_warehouse": "_Test WIP Warehouse"
- }
+ warehouse_map = {"fg_warehouse": "_Test FG Warehouse", "wip_warehouse": "_Test WIP Warehouse"}
for attr, wh_name in warehouse_map.items():
if not args.get(attr):
@@ -1053,16 +1172,17 @@
wo_order = frappe.new_doc("Work Order")
wo_order.production_item = args.production_item or args.item or args.item_code or "_Test FG Item"
- wo_order.bom_no = args.bom_no or frappe.db.get_value("BOM", {"item": wo_order.production_item,
- "is_active": 1, "is_default": 1})
+ wo_order.bom_no = args.bom_no or frappe.db.get_value(
+ "BOM", {"item": wo_order.production_item, "is_active": 1, "is_default": 1}
+ )
wo_order.qty = args.qty or 10
wo_order.wip_warehouse = args.wip_warehouse or "_Test Warehouse - _TC"
wo_order.fg_warehouse = args.fg_warehouse or "_Test Warehouse 1 - _TC"
wo_order.scrap_warehouse = args.fg_warehouse or "_Test Scrap Warehouse - _TC"
wo_order.company = args.company or "_Test Company"
wo_order.stock_uom = args.stock_uom or "_Test UOM"
- wo_order.use_multi_level_bom= args.use_multi_level_bom or 0
- wo_order.skip_transfer=args.skip_transfer or 0
+ wo_order.use_multi_level_bom = args.use_multi_level_bom or 0
+ wo_order.skip_transfer = args.skip_transfer or 0
wo_order.get_items_and_operations_from_bom()
wo_order.sales_order = args.sales_order or None
wo_order.planned_start_date = args.planned_start_date or now()
@@ -1079,4 +1199,5 @@
wo_order.submit()
return wo_order
-test_records = frappe.get_test_records('Work Order')
+
+test_records = frappe.get_test_records("Work Order")
diff --git a/erpnext/manufacturing/doctype/work_order/work_order.py b/erpnext/manufacturing/doctype/work_order/work_order.py
index e832ac9..c8c2f9a 100644
--- a/erpnext/manufacturing/doctype/work_order/work_order.py
+++ b/erpnext/manufacturing/doctype/work_order/work_order.py
@@ -42,11 +42,26 @@
from erpnext.utilities.transaction_base import validate_uom_is_integer
-class OverProductionError(frappe.ValidationError): pass
-class CapacityError(frappe.ValidationError): pass
-class StockOverProductionError(frappe.ValidationError): pass
-class OperationTooLongError(frappe.ValidationError): pass
-class ItemHasVariantError(frappe.ValidationError): pass
+class OverProductionError(frappe.ValidationError):
+ pass
+
+
+class CapacityError(frappe.ValidationError):
+ pass
+
+
+class StockOverProductionError(frappe.ValidationError):
+ pass
+
+
+class OperationTooLongError(frappe.ValidationError):
+ pass
+
+
+class ItemHasVariantError(frappe.ValidationError):
+ pass
+
+
class SerialNoQtyError(frappe.ValidationError):
pass
@@ -74,12 +89,13 @@
validate_uom_is_integer(self, "stock_uom", ["qty", "produced_qty"])
- self.set_required_items(reset_only_qty = len(self.get("required_items")))
+ self.set_required_items(reset_only_qty=len(self.get("required_items")))
def validate_sales_order(self):
if self.sales_order:
self.check_sales_order_on_hold_or_close()
- so = frappe.db.sql("""
+ so = frappe.db.sql(
+ """
select so.name, so_item.delivery_date, so.project
from `tabSales Order` so
inner join `tabSales Order Item` so_item on so_item.parent = so.name
@@ -88,10 +104,14 @@
and so.skip_delivery_note = 0 and (
so_item.item_code=%s or
pk_item.item_code=%s )
- """, (self.sales_order, self.production_item, self.production_item), as_dict=1)
+ """,
+ (self.sales_order, self.production_item, self.production_item),
+ as_dict=1,
+ )
if not so:
- so = frappe.db.sql("""
+ so = frappe.db.sql(
+ """
select
so.name, so_item.delivery_date, so.project
from
@@ -102,7 +122,10 @@
and so.skip_delivery_note = 0
and so_item.item_code = packed_item.parent_item
and so.docstatus = 1 and packed_item.item_code=%s
- """, (self.sales_order, self.production_item), as_dict=1)
+ """,
+ (self.sales_order, self.production_item),
+ as_dict=1,
+ )
if len(so):
if not self.expected_delivery_date:
@@ -123,7 +146,9 @@
def set_default_warehouse(self):
if not self.wip_warehouse:
- self.wip_warehouse = frappe.db.get_single_value("Manufacturing Settings", "default_wip_warehouse")
+ self.wip_warehouse = frappe.db.get_single_value(
+ "Manufacturing Settings", "default_wip_warehouse"
+ )
if not self.fg_warehouse:
self.fg_warehouse = frappe.db.get_single_value("Manufacturing Settings", "default_fg_warehouse")
@@ -145,40 +170,55 @@
self.planned_operating_cost += flt(d.planned_operating_cost)
self.actual_operating_cost += flt(d.actual_operating_cost)
- variable_cost = self.actual_operating_cost if self.actual_operating_cost \
- else self.planned_operating_cost
+ variable_cost = (
+ self.actual_operating_cost if self.actual_operating_cost else self.planned_operating_cost
+ )
- self.total_operating_cost = (flt(self.additional_operating_cost)
- + flt(variable_cost) + flt(self.corrective_operation_cost))
+ self.total_operating_cost = (
+ flt(self.additional_operating_cost) + flt(variable_cost) + flt(self.corrective_operation_cost)
+ )
def validate_work_order_against_so(self):
# already ordered qty
- ordered_qty_against_so = frappe.db.sql("""select sum(qty) from `tabWork Order`
+ ordered_qty_against_so = frappe.db.sql(
+ """select sum(qty) from `tabWork Order`
where production_item = %s and sales_order = %s and docstatus < 2 and name != %s""",
- (self.production_item, self.sales_order, self.name))[0][0]
+ (self.production_item, self.sales_order, self.name),
+ )[0][0]
total_qty = flt(ordered_qty_against_so) + flt(self.qty)
# get qty from Sales Order Item table
- so_item_qty = frappe.db.sql("""select sum(stock_qty) from `tabSales Order Item`
+ so_item_qty = frappe.db.sql(
+ """select sum(stock_qty) from `tabSales Order Item`
where parent = %s and item_code = %s""",
- (self.sales_order, self.production_item))[0][0]
+ (self.sales_order, self.production_item),
+ )[0][0]
# get qty from Packing Item table
- dnpi_qty = frappe.db.sql("""select sum(qty) from `tabPacked Item`
+ dnpi_qty = frappe.db.sql(
+ """select sum(qty) from `tabPacked Item`
where parent = %s and parenttype = 'Sales Order' and item_code = %s""",
- (self.sales_order, self.production_item))[0][0]
+ (self.sales_order, self.production_item),
+ )[0][0]
# total qty in SO
so_qty = flt(so_item_qty) + flt(dnpi_qty)
- allowance_percentage = flt(frappe.db.get_single_value("Manufacturing Settings",
- "overproduction_percentage_for_sales_order"))
+ allowance_percentage = flt(
+ frappe.db.get_single_value(
+ "Manufacturing Settings", "overproduction_percentage_for_sales_order"
+ )
+ )
- if total_qty > so_qty + (allowance_percentage/100 * so_qty):
- frappe.throw(_("Cannot produce more Item {0} than Sales Order quantity {1}")
- .format(self.production_item, so_qty), OverProductionError)
+ if total_qty > so_qty + (allowance_percentage / 100 * so_qty):
+ frappe.throw(
+ _("Cannot produce more Item {0} than Sales Order quantity {1}").format(
+ self.production_item, so_qty
+ ),
+ OverProductionError,
+ )
def update_status(self, status=None):
- '''Update status of work order if unknown'''
+ """Update status of work order if unknown"""
if status != "Stopped" and status != "Closed":
status = self.get_status(status)
@@ -190,17 +230,22 @@
return status
def get_status(self, status=None):
- '''Return the status based on stock entries against this work order'''
+ """Return the status based on stock entries against this work order"""
if not status:
status = self.status
- if self.docstatus==0:
- status = 'Draft'
- elif self.docstatus==1:
- if status != 'Stopped':
- stock_entries = frappe._dict(frappe.db.sql("""select purpose, sum(fg_completed_qty)
+ if self.docstatus == 0:
+ status = "Draft"
+ elif self.docstatus == 1:
+ if status != "Stopped":
+ stock_entries = frappe._dict(
+ frappe.db.sql(
+ """select purpose, sum(fg_completed_qty)
from `tabStock Entry` where work_order=%s and docstatus=1
- group by purpose""", self.name))
+ group by purpose""",
+ self.name,
+ )
+ )
status = "Not Started"
if stock_entries:
@@ -209,31 +254,46 @@
if flt(produced_qty) >= flt(self.qty):
status = "Completed"
else:
- status = 'Cancelled'
+ status = "Cancelled"
return status
def update_work_order_qty(self):
"""Update **Manufactured Qty** and **Material Transferred for Qty** in Work Order
- based on Stock Entry"""
+ based on Stock Entry"""
- allowance_percentage = flt(frappe.db.get_single_value("Manufacturing Settings",
- "overproduction_percentage_for_work_order"))
+ allowance_percentage = flt(
+ frappe.db.get_single_value("Manufacturing Settings", "overproduction_percentage_for_work_order")
+ )
- for purpose, fieldname in (("Manufacture", "produced_qty"),
- ("Material Transfer for Manufacture", "material_transferred_for_manufacturing")):
- if (purpose == 'Material Transfer for Manufacture' and
- self.operations and self.transfer_material_against == 'Job Card'):
+ for purpose, fieldname in (
+ ("Manufacture", "produced_qty"),
+ ("Material Transfer for Manufacture", "material_transferred_for_manufacturing"),
+ ):
+ if (
+ purpose == "Material Transfer for Manufacture"
+ and self.operations
+ and self.transfer_material_against == "Job Card"
+ ):
continue
- qty = flt(frappe.db.sql("""select sum(fg_completed_qty)
+ qty = flt(
+ frappe.db.sql(
+ """select sum(fg_completed_qty)
from `tabStock Entry` where work_order=%s and docstatus=1
- and purpose=%s""", (self.name, purpose))[0][0])
+ and purpose=%s""",
+ (self.name, purpose),
+ )[0][0]
+ )
- completed_qty = self.qty + (allowance_percentage/100 * self.qty)
+ completed_qty = self.qty + (allowance_percentage / 100 * self.qty)
if qty > completed_qty:
- frappe.throw(_("{0} ({1}) cannot be greater than planned quantity ({2}) in Work Order {3}").format(\
- self.meta.get_label(fieldname), qty, completed_qty, self.name), StockOverProductionError)
+ frappe.throw(
+ _("{0} ({1}) cannot be greater than planned quantity ({2}) in Work Order {3}").format(
+ self.meta.get_label(fieldname), qty, completed_qty, self.name
+ ),
+ StockOverProductionError,
+ )
self.db_set(fieldname, qty)
self.set_process_loss_qty()
@@ -247,7 +307,9 @@
self.update_production_plan_status()
def set_process_loss_qty(self):
- process_loss_qty = flt(frappe.db.sql("""
+ process_loss_qty = flt(
+ frappe.db.sql(
+ """
SELECT sum(qty) FROM `tabStock Entry Detail`
WHERE
is_process_loss=1
@@ -258,21 +320,33 @@
AND purpose='Manufacture'
AND docstatus=1
)
- """, (self.name, ))[0][0])
+ """,
+ (self.name,),
+ )[0][0]
+ )
if process_loss_qty is not None:
- self.db_set('process_loss_qty', process_loss_qty)
+ self.db_set("process_loss_qty", process_loss_qty)
def update_production_plan_status(self):
- production_plan = frappe.get_doc('Production Plan', self.production_plan)
+ production_plan = frappe.get_doc("Production Plan", self.production_plan)
produced_qty = 0
if self.production_plan_item:
- total_qty = frappe.get_all("Work Order", fields = "sum(produced_qty) as produced_qty",
- filters = {'docstatus': 1, 'production_plan': self.production_plan,
- 'production_plan_item': self.production_plan_item}, as_list=1)
+ total_qty = frappe.get_all(
+ "Work Order",
+ fields="sum(produced_qty) as produced_qty",
+ filters={
+ "docstatus": 1,
+ "production_plan": self.production_plan,
+ "production_plan_item": self.production_plan_item,
+ },
+ as_list=1,
+ )
produced_qty = total_qty[0][0] if total_qty else 0
- production_plan.run_method("update_produced_pending_qty", produced_qty, self.production_plan_item)
+ production_plan.run_method(
+ "update_produced_pending_qty", produced_qty, self.production_plan_item
+ )
def before_submit(self):
self.create_serial_no_batch_no()
@@ -283,7 +357,9 @@
if not self.fg_warehouse:
frappe.throw(_("For Warehouse is required before Submit"))
- if self.production_plan and frappe.db.exists('Production Plan Item Reference',{'parent':self.production_plan}):
+ if self.production_plan and frappe.db.exists(
+ "Production Plan Item Reference", {"parent": self.production_plan}
+ ):
self.update_work_order_qty_in_combined_so()
else:
self.update_work_order_qty_in_so()
@@ -296,9 +372,11 @@
def on_cancel(self):
self.validate_cancel()
- frappe.db.set(self,'status', 'Cancelled')
+ frappe.db.set(self, "status", "Cancelled")
- if self.production_plan and frappe.db.exists('Production Plan Item Reference',{'parent':self.production_plan}):
+ if self.production_plan and frappe.db.exists(
+ "Production Plan Item Reference", {"parent": self.production_plan}
+ ):
self.update_work_order_qty_in_combined_so()
else:
self.update_work_order_qty_in_so()
@@ -314,16 +392,15 @@
if not (self.has_serial_no or self.has_batch_no):
return
- if not cint(frappe.db.get_single_value("Manufacturing Settings", "make_serial_no_batch_from_work_order")):
+ if not cint(
+ frappe.db.get_single_value("Manufacturing Settings", "make_serial_no_batch_from_work_order")
+ ):
return
if self.has_batch_no:
self.create_batch_for_finished_good()
- args = {
- "item_code": self.production_item,
- "work_order": self.name
- }
+ args = {"item_code": self.production_item, "work_order": self.name}
if self.has_serial_no:
self.make_serial_nos(args)
@@ -336,9 +413,12 @@
batch_auto_creation = frappe.get_cached_value("Item", self.production_item, "create_new_batch")
if not batch_auto_creation:
frappe.msgprint(
- _("Batch not created for item {} since it does not have a batch series.")
- .format(frappe.bold(self.production_item)),
- alert=True, indicator="orange")
+ _("Batch not created for item {} since it does not have a batch series.").format(
+ frappe.bold(self.production_item)
+ ),
+ alert=True,
+ indicator="orange",
+ )
return
while total_qty > 0:
@@ -352,19 +432,23 @@
qty = total_qty
total_qty = 0
- make_batch(frappe._dict({
- "item": self.production_item,
- "qty_to_produce": qty,
- "reference_doctype": self.doctype,
- "reference_name": self.name
- }))
+ make_batch(
+ frappe._dict(
+ {
+ "item": self.production_item,
+ "qty_to_produce": qty,
+ "reference_doctype": self.doctype,
+ "reference_name": self.name,
+ }
+ )
+ )
def delete_auto_created_batch_and_serial_no(self):
- for row in frappe.get_all("Serial No", filters = {"work_order": self.name}):
+ for row in frappe.get_all("Serial No", filters={"work_order": self.name}):
frappe.delete_doc("Serial No", row.name)
self.db_set("serial_no", "")
- for row in frappe.get_all("Batch", filters = {"reference_name": self.name}):
+ for row in frappe.get_all("Batch", filters={"reference_name": self.name}):
frappe.delete_doc("Batch", row.name)
def make_serial_nos(self, args):
@@ -379,8 +463,12 @@
serial_nos_length = len(get_serial_nos(self.serial_no))
if serial_nos_length != self.qty:
- frappe.throw(_("{0} Serial Numbers required for Item {1}. You have provided {2}.")
- .format(self.qty, self.production_item, serial_nos_length), SerialNoQtyError)
+ frappe.throw(
+ _("{0} Serial Numbers required for Item {1}. You have provided {2}.").format(
+ self.qty, self.production_item, serial_nos_length
+ ),
+ SerialNoQtyError,
+ )
def create_job_card(self):
manufacturing_settings_doc = frappe.get_doc("Manufacturing Settings")
@@ -393,8 +481,7 @@
while qty > 0:
qty = split_qty_based_on_batch_size(self, row, qty)
if row.job_card_qty > 0:
- self.prepare_data_for_job_card(row, index,
- plan_days, enable_capacity_planning)
+ self.prepare_data_for_job_card(row, index, plan_days, enable_capacity_planning)
planned_end_date = self.operations and self.operations[-1].planned_end_time
if planned_end_date:
@@ -404,12 +491,14 @@
self.set_operation_start_end_time(index, row)
if not row.workstation:
- frappe.throw(_("Row {0}: select the workstation against the operation {1}")
- .format(row.idx, row.operation))
+ frappe.throw(
+ _("Row {0}: select the workstation against the operation {1}").format(row.idx, row.operation)
+ )
original_start_time = row.planned_start_time
- job_card_doc = create_job_card(self, row, auto_create=True,
- enable_capacity_planning=enable_capacity_planning)
+ job_card_doc = create_job_card(
+ self, row, auto_create=True, enable_capacity_planning=enable_capacity_planning
+ )
if enable_capacity_planning and job_card_doc:
row.planned_start_time = job_card_doc.time_logs[-1].from_time
@@ -417,22 +506,29 @@
if date_diff(row.planned_start_time, original_start_time) > plan_days:
frappe.message_log.pop()
- frappe.throw(_("Unable to find the time slot in the next {0} days for the operation {1}.")
- .format(plan_days, row.operation), CapacityError)
+ frappe.throw(
+ _("Unable to find the time slot in the next {0} days for the operation {1}.").format(
+ plan_days, row.operation
+ ),
+ CapacityError,
+ )
row.db_update()
def set_operation_start_end_time(self, idx, row):
"""Set start and end time for given operation. If first operation, set start as
`planned_start_date`, else add time diff to end time of earlier operation."""
- if idx==0:
+ if idx == 0:
# first operation at planned_start date
row.planned_start_time = self.planned_start_date
else:
- row.planned_start_time = get_datetime(self.operations[idx-1].planned_end_time)\
- + get_mins_between_operations()
+ row.planned_start_time = (
+ get_datetime(self.operations[idx - 1].planned_end_time) + get_mins_between_operations()
+ )
- row.planned_end_time = get_datetime(row.planned_start_time) + relativedelta(minutes = row.time_in_mins)
+ row.planned_end_time = get_datetime(row.planned_start_time) + relativedelta(
+ minutes=row.time_in_mins
+ )
if row.planned_start_time == row.planned_end_time:
frappe.throw(_("Capacity Planning Error, planned start time can not be same as end time"))
@@ -442,23 +538,35 @@
frappe.throw(_("Stopped Work Order cannot be cancelled, Unstop it first to cancel"))
# Check whether any stock entry exists against this Work Order
- stock_entry = frappe.db.sql("""select name from `tabStock Entry`
- where work_order = %s and docstatus = 1""", self.name)
+ stock_entry = frappe.db.sql(
+ """select name from `tabStock Entry`
+ where work_order = %s and docstatus = 1""",
+ self.name,
+ )
if stock_entry:
- frappe.throw(_("Cannot cancel because submitted Stock Entry {0} exists").format(frappe.utils.get_link_to_form('Stock Entry', stock_entry[0][0])))
+ frappe.throw(
+ _("Cannot cancel because submitted Stock Entry {0} exists").format(
+ frappe.utils.get_link_to_form("Stock Entry", stock_entry[0][0])
+ )
+ )
def update_planned_qty(self):
- update_bin_qty(self.production_item, self.fg_warehouse, {
- "planned_qty": get_planned_qty(self.production_item, self.fg_warehouse)
- })
+ update_bin_qty(
+ self.production_item,
+ self.fg_warehouse,
+ {"planned_qty": get_planned_qty(self.production_item, self.fg_warehouse)},
+ )
if self.material_request:
mr_obj = frappe.get_doc("Material Request", self.material_request)
mr_obj.update_requested_qty([self.material_request_item])
def update_ordered_qty(self):
- if self.production_plan and self.production_plan_item \
- and not self.production_plan_sub_assembly_item:
+ if (
+ self.production_plan
+ and self.production_plan_item
+ and not self.production_plan_sub_assembly_item
+ ):
qty = frappe.get_value("Production Plan Item", self.production_plan_item, "ordered_qty") or 0.0
if self.docstatus == 1:
@@ -466,12 +574,11 @@
elif self.docstatus == 2:
qty -= self.qty
- frappe.db.set_value('Production Plan Item',
- self.production_plan_item, 'ordered_qty', qty)
+ frappe.db.set_value("Production Plan Item", self.production_plan_item, "ordered_qty", qty)
- doc = frappe.get_doc('Production Plan', self.production_plan)
+ doc = frappe.get_doc("Production Plan", self.production_plan)
doc.set_status()
- doc.db_set('status', doc.status)
+ doc.db_set("status", doc.status)
def update_work_order_qty_in_so(self):
if not self.sales_order and not self.sales_order_item:
@@ -479,8 +586,11 @@
total_bundle_qty = 1
if self.product_bundle_item:
- total_bundle_qty = frappe.db.sql(""" select sum(qty) from
- `tabProduct Bundle Item` where parent = %s""", (frappe.db.escape(self.product_bundle_item)))[0][0]
+ total_bundle_qty = frappe.db.sql(
+ """ select sum(qty) from
+ `tabProduct Bundle Item` where parent = %s""",
+ (frappe.db.escape(self.product_bundle_item)),
+ )[0][0]
if not total_bundle_qty:
# product bundle is 0 (product bundle allows 0 qty for items)
@@ -488,49 +598,78 @@
cond = "product_bundle_item = %s" if self.product_bundle_item else "production_item = %s"
- qty = frappe.db.sql(""" select sum(qty) from
+ qty = frappe.db.sql(
+ """ select sum(qty) from
`tabWork Order` where sales_order = %s and docstatus = 1 and {0}
- """.format(cond), (self.sales_order, (self.product_bundle_item or self.production_item)), as_list=1)
+ """.format(
+ cond
+ ),
+ (self.sales_order, (self.product_bundle_item or self.production_item)),
+ as_list=1,
+ )
work_order_qty = qty[0][0] if qty and qty[0][0] else 0
- frappe.db.set_value('Sales Order Item',
- self.sales_order_item, 'work_order_qty', flt(work_order_qty/total_bundle_qty, 2))
+ frappe.db.set_value(
+ "Sales Order Item",
+ self.sales_order_item,
+ "work_order_qty",
+ flt(work_order_qty / total_bundle_qty, 2),
+ )
def update_work_order_qty_in_combined_so(self):
total_bundle_qty = 1
if self.product_bundle_item:
- total_bundle_qty = frappe.db.sql(""" select sum(qty) from
- `tabProduct Bundle Item` where parent = %s""", (frappe.db.escape(self.product_bundle_item)))[0][0]
+ total_bundle_qty = frappe.db.sql(
+ """ select sum(qty) from
+ `tabProduct Bundle Item` where parent = %s""",
+ (frappe.db.escape(self.product_bundle_item)),
+ )[0][0]
if not total_bundle_qty:
# product bundle is 0 (product bundle allows 0 qty for items)
total_bundle_qty = 1
- prod_plan = frappe.get_doc('Production Plan', self.production_plan)
- item_reference = frappe.get_value('Production Plan Item', self.production_plan_item, 'sales_order_item')
+ prod_plan = frappe.get_doc("Production Plan", self.production_plan)
+ item_reference = frappe.get_value(
+ "Production Plan Item", self.production_plan_item, "sales_order_item"
+ )
for plan_reference in prod_plan.prod_plan_references:
work_order_qty = 0.0
if plan_reference.item_reference == item_reference:
if self.docstatus == 1:
work_order_qty = flt(plan_reference.qty) / total_bundle_qty
- frappe.db.set_value('Sales Order Item',
- plan_reference.sales_order_item, 'work_order_qty', work_order_qty)
+ frappe.db.set_value(
+ "Sales Order Item", plan_reference.sales_order_item, "work_order_qty", work_order_qty
+ )
def update_completed_qty_in_material_request(self):
if self.material_request:
- frappe.get_doc("Material Request", self.material_request).update_completed_qty([self.material_request_item])
+ frappe.get_doc("Material Request", self.material_request).update_completed_qty(
+ [self.material_request_item]
+ )
def set_work_order_operations(self):
"""Fetch operations from BOM and set in 'Work Order'"""
def _get_operations(bom_no, qty=1):
- data = frappe.get_all("BOM Operation",
- filters={"parent": bom_no},
- fields=["operation", "description", "workstation", "idx",
- "base_hour_rate as hour_rate", "time_in_mins", "parent as bom",
- "batch_size", "sequence_id", "fixed_time"],
- order_by="idx")
+ data = frappe.get_all(
+ "BOM Operation",
+ filters={"parent": bom_no},
+ fields=[
+ "operation",
+ "description",
+ "workstation",
+ "idx",
+ "base_hour_rate as hour_rate",
+ "time_in_mins",
+ "parent as bom",
+ "batch_size",
+ "sequence_id",
+ "fixed_time",
+ ],
+ order_by="idx",
+ )
for d in data:
if not d.fixed_time:
@@ -539,9 +678,8 @@
return data
-
- self.set('operations', [])
- if not self.bom_no or not frappe.get_cached_value('BOM', self.bom_no, 'with_operations'):
+ self.set("operations", [])
+ if not self.bom_no or not frappe.get_cached_value("BOM", self.bom_no, "with_operations"):
return
operations = []
@@ -555,12 +693,12 @@
operations.extend(_get_operations(node.name, qty=node.exploded_qty))
bom_qty = frappe.get_cached_value("BOM", self.bom_no, "quantity")
- operations.extend(_get_operations(self.bom_no, qty=1.0/bom_qty))
+ operations.extend(_get_operations(self.bom_no, qty=1.0 / bom_qty))
for correct_index, operation in enumerate(operations, start=1):
operation.idx = correct_index
- self.set('operations', operations)
+ self.set("operations", operations)
self.calculate_time()
def calculate_time(self):
@@ -576,16 +714,27 @@
holidays = {}
if holiday_list not in holidays:
- holiday_list_days = [getdate(d[0]) for d in frappe.get_all("Holiday", fields=["holiday_date"],
- filters={"parent": holiday_list}, order_by="holiday_date", limit_page_length=0, as_list=1)]
+ holiday_list_days = [
+ getdate(d[0])
+ for d in frappe.get_all(
+ "Holiday",
+ fields=["holiday_date"],
+ filters={"parent": holiday_list},
+ order_by="holiday_date",
+ limit_page_length=0,
+ as_list=1,
+ )
+ ]
holidays[holiday_list] = holiday_list_days
return holidays[holiday_list]
def update_operation_status(self):
- allowance_percentage = flt(frappe.db.get_single_value("Manufacturing Settings", "overproduction_percentage_for_work_order"))
- max_allowed_qty_for_wo = flt(self.qty) + (allowance_percentage/100 * flt(self.qty))
+ allowance_percentage = flt(
+ frappe.db.get_single_value("Manufacturing Settings", "overproduction_percentage_for_work_order")
+ )
+ max_allowed_qty_for_wo = flt(self.qty) + (allowance_percentage / 100 * flt(self.qty))
for d in self.get("operations"):
if not d.completed_qty:
@@ -601,7 +750,9 @@
def set_actual_dates(self):
if self.get("operations"):
- actual_start_dates = [d.actual_start_time for d in self.get("operations") if d.actual_start_time]
+ actual_start_dates = [
+ d.actual_start_time for d in self.get("operations") if d.actual_start_time
+ ]
if actual_start_dates:
self.actual_start_date = min(actual_start_dates)
@@ -609,20 +760,21 @@
if actual_end_dates:
self.actual_end_date = max(actual_end_dates)
else:
- data = frappe.get_all("Stock Entry",
- fields = ["timestamp(posting_date, posting_time) as posting_datetime"],
- filters = {
+ data = frappe.get_all(
+ "Stock Entry",
+ fields=["timestamp(posting_date, posting_time) as posting_datetime"],
+ filters={
"work_order": self.name,
- "purpose": ("in", ["Material Transfer for Manufacture", "Manufacture"])
- }
+ "purpose": ("in", ["Material Transfer for Manufacture", "Manufacture"]),
+ },
)
if data and len(data):
dates = [d.posting_datetime for d in data]
- self.db_set('actual_start_date', min(dates))
+ self.db_set("actual_start_date", min(dates))
if self.status == "Completed":
- self.db_set('actual_end_date', max(dates))
+ self.db_set("actual_end_date", max(dates))
self.set_lead_time()
@@ -645,24 +797,39 @@
if not self.qty > 0:
frappe.throw(_("Quantity to Manufacture must be greater than 0."))
- if self.production_plan and self.production_plan_item \
- and not self.production_plan_sub_assembly_item:
- qty_dict = frappe.db.get_value("Production Plan Item", self.production_plan_item, ["planned_qty", "ordered_qty"], as_dict=1)
+ if (
+ self.production_plan
+ and self.production_plan_item
+ and not self.production_plan_sub_assembly_item
+ ):
+ qty_dict = frappe.db.get_value(
+ "Production Plan Item", self.production_plan_item, ["planned_qty", "ordered_qty"], as_dict=1
+ )
if not qty_dict:
return
- allowance_qty = flt(frappe.db.get_single_value("Manufacturing Settings",
- "overproduction_percentage_for_work_order"))/100 * qty_dict.get("planned_qty", 0)
+ allowance_qty = (
+ flt(
+ frappe.db.get_single_value(
+ "Manufacturing Settings", "overproduction_percentage_for_work_order"
+ )
+ )
+ / 100
+ * qty_dict.get("planned_qty", 0)
+ )
max_qty = qty_dict.get("planned_qty", 0) + allowance_qty - qty_dict.get("ordered_qty", 0)
if not max_qty > 0:
- frappe.throw(_("Cannot produce more item for {0}")
- .format(self.production_item), OverProductionError)
+ frappe.throw(
+ _("Cannot produce more item for {0}").format(self.production_item), OverProductionError
+ )
elif self.qty > max_qty:
- frappe.throw(_("Cannot produce more than {0} items for {1}")
- .format(max_qty, self.production_item), OverProductionError)
+ frappe.throw(
+ _("Cannot produce more than {0} items for {1}").format(max_qty, self.production_item),
+ OverProductionError,
+ )
def validate_transfer_against(self):
if not self.docstatus == 1:
@@ -671,8 +838,10 @@
if not self.operations:
self.transfer_material_against = "Work Order"
if not self.transfer_material_against:
- frappe.throw(_("Setting {} is required").format(self.meta.get_label("transfer_material_against")), title=_("Missing value"))
-
+ frappe.throw(
+ _("Setting {} is required").format(self.meta.get_label("transfer_material_against")),
+ title=_("Missing value"),
+ )
def validate_operation_time(self):
for d in self.operations:
@@ -680,14 +849,14 @@
frappe.throw(_("Operation Time must be greater than 0 for Operation {0}").format(d.operation))
def update_required_items(self):
- '''
+ """
update bin reserved_qty_for_production
called from Stock Entry for production, after submit, cancel
- '''
+ """
# calculate consumed qty based on submitted stock entries
self.update_consumed_qty_for_required_items()
- if self.docstatus==1:
+ if self.docstatus == 1:
# calculate transferred qty based on submitted stock entries
self.update_transferred_qty_for_required_items()
@@ -695,7 +864,7 @@
self.update_reserved_qty_for_production()
def update_reserved_qty_for_production(self, items=None):
- '''update reserved_qty_for_production in bins'''
+ """update reserved_qty_for_production in bins"""
for d in self.required_items:
if d.source_warehouse:
stock_bin = get_bin(d.item_code, d.source_warehouse)
@@ -717,17 +886,18 @@
d.available_qty_at_wip_warehouse = get_latest_stock_qty(d.item_code, self.wip_warehouse)
def set_required_items(self, reset_only_qty=False):
- '''set required_items for production to keep track of reserved qty'''
+ """set required_items for production to keep track of reserved qty"""
if not reset_only_qty:
self.required_items = []
operation = None
- if self.get('operations') and len(self.operations) == 1:
+ if self.get("operations") and len(self.operations) == 1:
operation = self.operations[0].operation
if self.bom_no and self.qty:
- item_dict = get_bom_items_as_dict(self.bom_no, self.company, qty=self.qty,
- fetch_exploded = self.use_multi_level_bom)
+ item_dict = get_bom_items_as_dict(
+ self.bom_no, self.company, qty=self.qty, fetch_exploded=self.use_multi_level_bom
+ )
if reset_only_qty:
for d in self.get("required_items"):
@@ -737,19 +907,22 @@
if not d.operation:
d.operation = operation
else:
- for item in sorted(item_dict.values(), key=lambda d: d['idx'] or float('inf')):
- self.append('required_items', {
- 'rate': item.rate,
- 'amount': item.rate * item.qty,
- 'operation': item.operation or operation,
- 'item_code': item.item_code,
- 'item_name': item.item_name,
- 'description': item.description,
- 'allow_alternative_item': item.allow_alternative_item,
- 'required_qty': item.qty,
- 'source_warehouse': item.source_warehouse or item.default_warehouse,
- 'include_item_in_manufacturing': item.include_item_in_manufacturing
- })
+ for item in sorted(item_dict.values(), key=lambda d: d["idx"] or float("inf")):
+ self.append(
+ "required_items",
+ {
+ "rate": item.rate,
+ "amount": item.rate * item.qty,
+ "operation": item.operation or operation,
+ "item_code": item.item_code,
+ "item_name": item.item_name,
+ "description": item.description,
+ "allow_alternative_item": item.allow_alternative_item,
+ "required_qty": item.qty,
+ "source_warehouse": item.source_warehouse or item.default_warehouse,
+ "include_item_in_manufacturing": item.include_item_in_manufacturing,
+ },
+ )
if not self.project:
self.project = item.get("project")
@@ -757,32 +930,33 @@
self.set_available_qty()
def update_transferred_qty_for_required_items(self):
- '''update transferred qty from submitted stock entries for that item against
- the work order'''
+ """update transferred qty from submitted stock entries for that item against
+ the work order"""
for d in self.required_items:
- transferred_qty = frappe.db.sql('''select sum(qty)
+ transferred_qty = frappe.db.sql(
+ """select sum(qty)
from `tabStock Entry` entry, `tabStock Entry Detail` detail
where
entry.work_order = %(name)s
and entry.purpose = "Material Transfer for Manufacture"
and entry.docstatus = 1
and detail.parent = entry.name
- and (detail.item_code = %(item)s or detail.original_item = %(item)s)''', {
- 'name': self.name,
- 'item': d.item_code
- })[0][0]
+ and (detail.item_code = %(item)s or detail.original_item = %(item)s)""",
+ {"name": self.name, "item": d.item_code},
+ )[0][0]
- d.db_set('transferred_qty', flt(transferred_qty), update_modified = False)
+ d.db_set("transferred_qty", flt(transferred_qty), update_modified=False)
def update_consumed_qty_for_required_items(self):
- '''
- Update consumed qty from submitted stock entries
- against a work order for each stock item
- '''
+ """
+ Update consumed qty from submitted stock entries
+ against a work order for each stock item
+ """
for item in self.required_items:
- consumed_qty = frappe.db.sql('''
+ consumed_qty = frappe.db.sql(
+ """
SELECT
SUM(qty)
FROM
@@ -797,85 +971,97 @@
AND detail.s_warehouse IS NOT null
AND (detail.item_code = %(item)s
OR detail.original_item = %(item)s)
- ''', {
- 'name': self.name,
- 'item': item.item_code
- })[0][0]
+ """,
+ {"name": self.name, "item": item.item_code},
+ )[0][0]
- item.db_set('consumed_qty', flt(consumed_qty), update_modified=False)
+ item.db_set("consumed_qty", flt(consumed_qty), update_modified=False)
@frappe.whitelist()
def make_bom(self):
- data = frappe.db.sql(""" select sed.item_code, sed.qty, sed.s_warehouse
+ data = frappe.db.sql(
+ """ select sed.item_code, sed.qty, sed.s_warehouse
from `tabStock Entry Detail` sed, `tabStock Entry` se
where se.name = sed.parent and se.purpose = 'Manufacture'
and (sed.t_warehouse is null or sed.t_warehouse = '') and se.docstatus = 1
- and se.work_order = %s""", (self.name), as_dict=1)
+ and se.work_order = %s""",
+ (self.name),
+ as_dict=1,
+ )
bom = frappe.new_doc("BOM")
bom.item = self.production_item
bom.conversion_rate = 1
for d in data:
- bom.append('items', {
- 'item_code': d.item_code,
- 'qty': d.qty,
- 'source_warehouse': d.s_warehouse
- })
+ bom.append("items", {"item_code": d.item_code, "qty": d.qty, "source_warehouse": d.s_warehouse})
if self.operations:
- bom.set('operations', self.operations)
+ bom.set("operations", self.operations)
bom.with_operations = 1
bom.set_bom_material_details()
return bom
def update_batch_produced_qty(self, stock_entry_doc):
- if not cint(frappe.db.get_single_value("Manufacturing Settings", "make_serial_no_batch_from_work_order")):
+ if not cint(
+ frappe.db.get_single_value("Manufacturing Settings", "make_serial_no_batch_from_work_order")
+ ):
return
for row in stock_entry_doc.items:
if row.batch_no and (row.is_finished_item or row.is_scrap_item):
- qty = frappe.get_all("Stock Entry Detail", filters = {"batch_no": row.batch_no, "docstatus": 1},
- or_filters= {"is_finished_item": 1, "is_scrap_item": 1}, fields = ["sum(qty)"], as_list=1)[0][0]
+ qty = frappe.get_all(
+ "Stock Entry Detail",
+ filters={"batch_no": row.batch_no, "docstatus": 1},
+ or_filters={"is_finished_item": 1, "is_scrap_item": 1},
+ fields=["sum(qty)"],
+ as_list=1,
+ )[0][0]
frappe.db.set_value("Batch", row.batch_no, "produced_qty", flt(qty))
+
@frappe.whitelist()
@frappe.validate_and_sanitize_search_inputs
def get_bom_operations(doctype, txt, searchfield, start, page_len, filters):
if txt:
- filters['operation'] = ('like', '%%%s%%' % txt)
+ filters["operation"] = ("like", "%%%s%%" % txt)
- return frappe.get_all('BOM Operation',
- filters = filters, fields = ['operation'], as_list=1)
+ return frappe.get_all("BOM Operation", filters=filters, fields=["operation"], as_list=1)
+
@frappe.whitelist()
-def get_item_details(item, project = None, skip_bom_info=False):
- res = frappe.db.sql("""
+def get_item_details(item, project=None, skip_bom_info=False):
+ res = frappe.db.sql(
+ """
select stock_uom, description, item_name, allow_alternative_item,
include_item_in_manufacturing
from `tabItem`
where disabled=0
and (end_of_life is null or end_of_life='0000-00-00' or end_of_life > %s)
and name=%s
- """, (nowdate(), item), as_dict=1)
+ """,
+ (nowdate(), item),
+ as_dict=1,
+ )
if not res:
return {}
res = res[0]
- if skip_bom_info: return res
+ if skip_bom_info:
+ return res
filters = {"item": item, "is_default": 1, "docstatus": 1}
if project:
filters = {"item": item, "project": project}
- res["bom_no"] = frappe.db.get_value("BOM", filters = filters)
+ res["bom_no"] = frappe.db.get_value("BOM", filters=filters)
if not res["bom_no"]:
- variant_of= frappe.db.get_value("Item", item, "variant_of")
+ variant_of = frappe.db.get_value("Item", item, "variant_of")
if variant_of:
res["bom_no"] = frappe.db.get_value("BOM", filters={"item": variant_of, "is_default": 1})
@@ -883,19 +1069,26 @@
if not res["bom_no"]:
if project:
res = get_item_details(item)
- frappe.msgprint(_("Default BOM not found for Item {0} and Project {1}").format(item, project), alert=1)
+ frappe.msgprint(
+ _("Default BOM not found for Item {0} and Project {1}").format(item, project), alert=1
+ )
else:
frappe.throw(_("Default BOM for {0} not found").format(item))
- bom_data = frappe.db.get_value('BOM', res['bom_no'],
- ['project', 'allow_alternative_item', 'transfer_material_against', 'item_name'], as_dict=1)
+ bom_data = frappe.db.get_value(
+ "BOM",
+ res["bom_no"],
+ ["project", "allow_alternative_item", "transfer_material_against", "item_name"],
+ as_dict=1,
+ )
- res['project'] = project or bom_data.pop("project")
+ res["project"] = project or bom_data.pop("project")
res.update(bom_data)
res.update(check_if_scrap_warehouse_mandatory(res["bom_no"]))
return res
+
@frappe.whitelist()
def make_work_order(bom_no, item, qty=0, project=None, variant_items=None):
if not frappe.has_permission("Work Order", "write"):
@@ -917,43 +1110,51 @@
return wo_doc
+
def add_variant_item(variant_items, wo_doc, bom_no, table_name="items"):
if isinstance(variant_items, str):
variant_items = json.loads(variant_items)
for item in variant_items:
- args = frappe._dict({
- "item_code": item.get("variant_item_code"),
- "required_qty": item.get("qty"),
- "qty": item.get("qty"), # for bom
- "source_warehouse": item.get("source_warehouse"),
- "operation": item.get("operation")
- })
+ args = frappe._dict(
+ {
+ "item_code": item.get("variant_item_code"),
+ "required_qty": item.get("qty"),
+ "qty": item.get("qty"), # for bom
+ "source_warehouse": item.get("source_warehouse"),
+ "operation": item.get("operation"),
+ }
+ )
bom_doc = frappe.get_cached_doc("BOM", bom_no)
item_data = get_item_details(args.item_code, skip_bom_info=True)
args.update(item_data)
- args["rate"] = get_bom_item_rate({
- "company": wo_doc.company,
- "item_code": args.get("item_code"),
- "qty": args.get("required_qty"),
- "uom": args.get("stock_uom"),
- "stock_uom": args.get("stock_uom"),
- "conversion_factor": 1
- }, bom_doc)
+ args["rate"] = get_bom_item_rate(
+ {
+ "company": wo_doc.company,
+ "item_code": args.get("item_code"),
+ "qty": args.get("required_qty"),
+ "uom": args.get("stock_uom"),
+ "stock_uom": args.get("stock_uom"),
+ "conversion_factor": 1,
+ },
+ bom_doc,
+ )
if not args.source_warehouse:
- args["source_warehouse"] = get_item_defaults(item.get("variant_item_code"),
- wo_doc.company).default_warehouse
+ args["source_warehouse"] = get_item_defaults(
+ item.get("variant_item_code"), wo_doc.company
+ ).default_warehouse
args["amount"] = flt(args.get("required_qty")) * flt(args.get("rate"))
args["uom"] = item_data.stock_uom
wo_doc.append(table_name, args)
+
@frappe.whitelist()
def check_if_scrap_warehouse_mandatory(bom_no):
- res = {"set_scrap_wh_mandatory": False }
+ res = {"set_scrap_wh_mandatory": False}
if bom_no:
bom = frappe.get_doc("BOM", bom_no)
@@ -962,12 +1163,14 @@
return res
+
@frappe.whitelist()
def set_work_order_ops(name):
- po = frappe.get_doc('Work Order', name)
+ po = frappe.get_doc("Work Order", name)
po.set_work_order_operations()
po.save()
+
@frappe.whitelist()
def make_stock_entry(work_order_id, purpose, qty=None):
work_order = frappe.get_doc("Work Order", work_order_id)
@@ -985,10 +1188,11 @@
stock_entry.use_multi_level_bom = work_order.use_multi_level_bom
stock_entry.fg_completed_qty = qty or (flt(work_order.qty) - flt(work_order.produced_qty))
if work_order.bom_no:
- stock_entry.inspection_required = frappe.db.get_value('BOM',
- work_order.bom_no, 'inspection_required')
+ stock_entry.inspection_required = frappe.db.get_value(
+ "BOM", work_order.bom_no, "inspection_required"
+ )
- if purpose=="Material Transfer for Manufacture":
+ if purpose == "Material Transfer for Manufacture":
stock_entry.to_warehouse = wip_warehouse
stock_entry.project = work_order.project
else:
@@ -1001,6 +1205,7 @@
stock_entry.set_serial_no_batch_for_finished_good()
return stock_entry.as_dict()
+
@frappe.whitelist()
def get_default_warehouse():
doc = frappe.get_cached_doc("Manufacturing Settings")
@@ -1008,12 +1213,13 @@
return {
"wip_warehouse": doc.default_wip_warehouse,
"fg_warehouse": doc.default_fg_warehouse,
- "scrap_warehouse": doc.default_scrap_warehouse
+ "scrap_warehouse": doc.default_scrap_warehouse,
}
+
@frappe.whitelist()
def stop_unstop(work_order, status):
- """ Called from client side on Stop/Unstop event"""
+ """Called from client side on Stop/Unstop event"""
if not frappe.has_permission("Work Order", "write"):
frappe.throw(_("Not permitted"), frappe.PermissionError)
@@ -1030,24 +1236,29 @@
return pro_order.status
+
@frappe.whitelist()
def query_sales_order(production_item):
- out = frappe.db.sql_list("""
+ out = frappe.db.sql_list(
+ """
select distinct so.name from `tabSales Order` so, `tabSales Order Item` so_item
where so_item.parent=so.name and so_item.item_code=%s and so.docstatus=1
union
select distinct so.name from `tabSales Order` so, `tabPacked Item` pi_item
where pi_item.parent=so.name and pi_item.item_code=%s and so.docstatus=1
- """, (production_item, production_item))
+ """,
+ (production_item, production_item),
+ )
return out
+
@frappe.whitelist()
def make_job_card(work_order, operations):
if isinstance(operations, str):
operations = json.loads(operations)
- work_order = frappe.get_doc('Work Order', work_order)
+ work_order = frappe.get_doc("Work Order", work_order)
for row in operations:
row = frappe._dict(row)
validate_operation_data(row)
@@ -1057,6 +1268,7 @@
if row.job_card_qty > 0:
create_job_card(work_order, row, auto_create=True)
+
@frappe.whitelist()
def close_work_order(work_order, status):
if not frappe.has_permission("Work Order", "write"):
@@ -1064,15 +1276,17 @@
work_order = frappe.get_doc("Work Order", work_order)
if work_order.get("operations"):
- job_cards = frappe.get_list("Job Card",
- filters={
- "work_order": work_order.name,
- "status": "Work In Progress"
- }, pluck='name')
+ job_cards = frappe.get_list(
+ "Job Card", filters={"work_order": work_order.name, "status": "Work In Progress"}, pluck="name"
+ )
if job_cards:
job_cards = ", ".join(job_cards)
- frappe.throw(_("Can not close Work Order. Since {0} Job Cards are in Work In Progress state.").format(job_cards))
+ frappe.throw(
+ _("Can not close Work Order. Since {0} Job Cards are in Work In Progress state.").format(
+ job_cards
+ )
+ )
work_order.update_status(status)
work_order.update_planned_qty()
@@ -1080,9 +1294,11 @@
work_order.notify_update()
return work_order.status
+
def split_qty_based_on_batch_size(wo_doc, row, qty):
- if not cint(frappe.db.get_value("Operation",
- row.operation, "create_job_card_based_on_batch_size")):
+ if not cint(
+ frappe.db.get_value("Operation", row.operation, "create_job_card_based_on_batch_size")
+ ):
row.batch_size = row.get("qty") or wo_doc.qty
row.job_card_qty = row.batch_size
@@ -1096,55 +1312,63 @@
return qty
+
def get_serial_nos_for_job_card(row, wo_doc):
if not wo_doc.serial_no:
return
serial_nos = get_serial_nos(wo_doc.serial_no)
used_serial_nos = []
- for d in frappe.get_all('Job Card', fields=['serial_no'],
- filters={'docstatus': ('<', 2), 'work_order': wo_doc.name, 'operation_id': row.name}):
+ for d in frappe.get_all(
+ "Job Card",
+ fields=["serial_no"],
+ filters={"docstatus": ("<", 2), "work_order": wo_doc.name, "operation_id": row.name},
+ ):
used_serial_nos.extend(get_serial_nos(d.serial_no))
serial_nos = sorted(list(set(serial_nos) - set(used_serial_nos)))
- row.serial_no = '\n'.join(serial_nos[0:row.job_card_qty])
+ row.serial_no = "\n".join(serial_nos[0 : row.job_card_qty])
+
def validate_operation_data(row):
if row.get("qty") <= 0:
- frappe.throw(_("Quantity to Manufacture can not be zero for the operation {0}")
- .format(
+ frappe.throw(
+ _("Quantity to Manufacture can not be zero for the operation {0}").format(
frappe.bold(row.get("operation"))
)
)
if row.get("qty") > row.get("pending_qty"):
- frappe.throw(_("For operation {0}: Quantity ({1}) can not be greter than pending quantity({2})")
- .format(
+ frappe.throw(
+ _("For operation {0}: Quantity ({1}) can not be greter than pending quantity({2})").format(
frappe.bold(row.get("operation")),
frappe.bold(row.get("qty")),
- frappe.bold(row.get("pending_qty"))
+ frappe.bold(row.get("pending_qty")),
)
)
+
def create_job_card(work_order, row, enable_capacity_planning=False, auto_create=False):
doc = frappe.new_doc("Job Card")
- doc.update({
- 'work_order': work_order.name,
- 'operation': row.get("operation"),
- 'workstation': row.get("workstation"),
- 'posting_date': nowdate(),
- 'for_quantity': row.job_card_qty or work_order.get('qty', 0),
- 'operation_id': row.get("name"),
- 'bom_no': work_order.bom_no,
- 'project': work_order.project,
- 'company': work_order.company,
- 'sequence_id': row.get("sequence_id"),
- 'wip_warehouse': work_order.wip_warehouse,
- 'hour_rate': row.get("hour_rate"),
- 'serial_no': row.get("serial_no")
- })
+ doc.update(
+ {
+ "work_order": work_order.name,
+ "operation": row.get("operation"),
+ "workstation": row.get("workstation"),
+ "posting_date": nowdate(),
+ "for_quantity": row.job_card_qty or work_order.get("qty", 0),
+ "operation_id": row.get("name"),
+ "bom_no": work_order.bom_no,
+ "project": work_order.project,
+ "company": work_order.company,
+ "sequence_id": row.get("sequence_id"),
+ "wip_warehouse": work_order.wip_warehouse,
+ "hour_rate": row.get("hour_rate"),
+ "serial_no": row.get("serial_no"),
+ }
+ )
- if work_order.transfer_material_against == 'Job Card' and not work_order.skip_transfer:
+ if work_order.transfer_material_against == "Job Card" and not work_order.skip_transfer:
doc.get_required_items()
if auto_create:
@@ -1153,7 +1377,9 @@
doc.schedule_time_logs(row)
doc.insert()
- frappe.msgprint(_("Job card {0} created").format(get_link_to_form("Job Card", doc.name)), alert=True)
+ frappe.msgprint(
+ _("Job card {0} created").format(get_link_to_form("Job Card", doc.name)), alert=True
+ )
if enable_capacity_planning:
# automatically added scheduling rows shouldn't change status to WIP
@@ -1161,15 +1387,18 @@
return doc
+
def get_work_order_operation_data(work_order, operation, workstation):
for d in work_order.operations:
if d.operation == operation and d.workstation == workstation:
return d
+
@frappe.whitelist()
def create_pick_list(source_name, target_doc=None, for_qty=None):
- for_qty = for_qty or json.loads(target_doc).get('for_qty')
- max_finished_goods_qty = frappe.db.get_value('Work Order', source_name, 'qty')
+ for_qty = for_qty or json.loads(target_doc).get("for_qty")
+ max_finished_goods_qty = frappe.db.get_value("Work Order", source_name, "qty")
+
def update_item_quantity(source, target, source_parent):
pending_to_issue = flt(source.required_qty) - flt(source.transferred_qty)
desire_to_transfer = flt(source.required_qty) / max_finished_goods_qty * flt(for_qty)
@@ -1183,25 +1412,25 @@
if qty:
target.qty = qty
target.stock_qty = qty
- target.uom = frappe.get_value('Item', source.item_code, 'stock_uom')
+ target.uom = frappe.get_value("Item", source.item_code, "stock_uom")
target.stock_uom = target.uom
target.conversion_factor = 1
else:
target.delete()
- doc = get_mapped_doc('Work Order', source_name, {
- 'Work Order': {
- 'doctype': 'Pick List',
- 'validation': {
- 'docstatus': ['=', 1]
- }
+ doc = get_mapped_doc(
+ "Work Order",
+ source_name,
+ {
+ "Work Order": {"doctype": "Pick List", "validation": {"docstatus": ["=", 1]}},
+ "Work Order Item": {
+ "doctype": "Pick List Item",
+ "postprocess": update_item_quantity,
+ "condition": lambda doc: abs(doc.transferred_qty) < abs(doc.required_qty),
+ },
},
- 'Work Order Item': {
- 'doctype': 'Pick List Item',
- 'postprocess': update_item_quantity,
- 'condition': lambda doc: abs(doc.transferred_qty) < abs(doc.required_qty)
- },
- }, target_doc)
+ target_doc,
+ )
doc.for_qty = for_qty
@@ -1209,26 +1438,31 @@
return doc
+
def get_reserved_qty_for_production(item_code: str, warehouse: str) -> float:
"""Get total reserved quantity for any item in specified warehouse"""
wo = frappe.qb.DocType("Work Order")
wo_item = frappe.qb.DocType("Work Order Item")
return (
- frappe.qb
- .from_(wo)
+ frappe.qb.from_(wo)
.from_(wo_item)
- .select(Sum(Case()
+ .select(
+ Sum(
+ Case()
.when(wo.skip_transfer == 0, wo_item.required_qty - wo_item.transferred_qty)
- .else_(wo_item.required_qty - wo_item.consumed_qty))
+ .else_(wo_item.required_qty - wo_item.consumed_qty)
)
+ )
.where(
(wo_item.item_code == item_code)
& (wo_item.parent == wo.name)
& (wo.docstatus == 1)
& (wo_item.source_warehouse == warehouse)
& (wo.status.notin(["Stopped", "Completed", "Closed"]))
- & ((wo_item.required_qty > wo_item.transferred_qty)
- | (wo_item.required_qty > wo_item.consumed_qty))
+ & (
+ (wo_item.required_qty > wo_item.transferred_qty)
+ | (wo_item.required_qty > wo_item.consumed_qty)
+ )
)
).run()[0][0] or 0.0
diff --git a/erpnext/manufacturing/doctype/work_order/work_order_dashboard.py b/erpnext/manufacturing/doctype/work_order/work_order_dashboard.py
index 37dd11a..465460f 100644
--- a/erpnext/manufacturing/doctype/work_order/work_order_dashboard.py
+++ b/erpnext/manufacturing/doctype/work_order/work_order_dashboard.py
@@ -3,18 +3,10 @@
def get_data():
return {
- 'fieldname': 'work_order',
- 'non_standard_fieldnames': {
- 'Batch': 'reference_name'
- },
- 'transactions': [
- {
- 'label': _('Transactions'),
- 'items': ['Stock Entry', 'Job Card', 'Pick List']
- },
- {
- 'label': _('Reference'),
- 'items': ['Serial No', 'Batch']
- }
- ]
+ "fieldname": "work_order",
+ "non_standard_fieldnames": {"Batch": "reference_name"},
+ "transactions": [
+ {"label": _("Transactions"), "items": ["Stock Entry", "Job Card", "Pick List"]},
+ {"label": _("Reference"), "items": ["Serial No", "Batch"]},
+ ],
}
diff --git a/erpnext/manufacturing/doctype/work_order_item/work_order_item.py b/erpnext/manufacturing/doctype/work_order_item/work_order_item.py
index 4311d3b..1792747 100644
--- a/erpnext/manufacturing/doctype/work_order_item/work_order_item.py
+++ b/erpnext/manufacturing/doctype/work_order_item/work_order_item.py
@@ -9,5 +9,6 @@
class WorkOrderItem(Document):
pass
+
def on_doctype_update():
frappe.db.add_index("Work Order Item", ["item_code", "source_warehouse"])
diff --git a/erpnext/manufacturing/doctype/workstation/test_workstation.py b/erpnext/manufacturing/doctype/workstation/test_workstation.py
index dd51017..6db985c 100644
--- a/erpnext/manufacturing/doctype/workstation/test_workstation.py
+++ b/erpnext/manufacturing/doctype/workstation/test_workstation.py
@@ -13,19 +13,42 @@
)
test_dependencies = ["Warehouse"]
-test_records = frappe.get_test_records('Workstation')
-make_test_records('Workstation')
+test_records = frappe.get_test_records("Workstation")
+make_test_records("Workstation")
+
class TestWorkstation(FrappeTestCase):
def test_validate_timings(self):
- check_if_within_operating_hours("_Test Workstation 1", "Operation 1", "2013-02-02 11:00:00", "2013-02-02 19:00:00")
- check_if_within_operating_hours("_Test Workstation 1", "Operation 1", "2013-02-02 10:00:00", "2013-02-02 20:00:00")
- self.assertRaises(NotInWorkingHoursError, check_if_within_operating_hours,
- "_Test Workstation 1", "Operation 1", "2013-02-02 05:00:00", "2013-02-02 20:00:00")
- self.assertRaises(NotInWorkingHoursError, check_if_within_operating_hours,
- "_Test Workstation 1", "Operation 1", "2013-02-02 05:00:00", "2013-02-02 20:00:00")
- self.assertRaises(WorkstationHolidayError, check_if_within_operating_hours,
- "_Test Workstation 1", "Operation 1", "2013-02-01 10:00:00", "2013-02-02 20:00:00")
+ check_if_within_operating_hours(
+ "_Test Workstation 1", "Operation 1", "2013-02-02 11:00:00", "2013-02-02 19:00:00"
+ )
+ check_if_within_operating_hours(
+ "_Test Workstation 1", "Operation 1", "2013-02-02 10:00:00", "2013-02-02 20:00:00"
+ )
+ self.assertRaises(
+ NotInWorkingHoursError,
+ check_if_within_operating_hours,
+ "_Test Workstation 1",
+ "Operation 1",
+ "2013-02-02 05:00:00",
+ "2013-02-02 20:00:00",
+ )
+ self.assertRaises(
+ NotInWorkingHoursError,
+ check_if_within_operating_hours,
+ "_Test Workstation 1",
+ "Operation 1",
+ "2013-02-02 05:00:00",
+ "2013-02-02 20:00:00",
+ )
+ self.assertRaises(
+ WorkstationHolidayError,
+ check_if_within_operating_hours,
+ "_Test Workstation 1",
+ "Operation 1",
+ "2013-02-01 10:00:00",
+ "2013-02-02 20:00:00",
+ )
def test_update_bom_operation_rate(self):
operations = [
@@ -33,14 +56,14 @@
"operation": "Test Operation A",
"workstation": "_Test Workstation A",
"hour_rate_rent": 300,
- "time_in_mins": 60
+ "time_in_mins": 60,
},
{
"operation": "Test Operation B",
"workstation": "_Test Workstation B",
"hour_rate_rent": 1000,
- "time_in_mins": 60
- }
+ "time_in_mins": 60,
+ },
]
for row in operations:
@@ -48,21 +71,13 @@
make_operation(row)
test_routing_operations = [
- {
- "operation": "Test Operation A",
- "workstation": "_Test Workstation A",
- "time_in_mins": 60
- },
- {
- "operation": "Test Operation B",
- "workstation": "_Test Workstation A",
- "time_in_mins": 60
- }
+ {"operation": "Test Operation A", "workstation": "_Test Workstation A", "time_in_mins": 60},
+ {"operation": "Test Operation B", "workstation": "_Test Workstation A", "time_in_mins": 60},
]
- routing_doc = create_routing(routing_name = "Routing Test", operations=test_routing_operations)
+ routing_doc = create_routing(routing_name="Routing Test", operations=test_routing_operations)
bom_doc = setup_bom(item_code="_Testing Item", routing=routing_doc.name, currency="INR")
w1 = frappe.get_doc("Workstation", "_Test Workstation A")
- #resets values
+ # resets values
w1.hour_rate_rent = 300
w1.hour_rate_labour = 0
w1.save()
@@ -72,13 +87,14 @@
self.assertEqual(bom_doc.operations[0].hour_rate, 300)
w1.hour_rate_rent = 250
w1.save()
- #updating after setting new rates in workstations
+ # updating after setting new rates in workstations
bom_doc.update_cost()
bom_doc.reload()
self.assertEqual(w1.hour_rate, 250)
self.assertEqual(bom_doc.operations[0].hour_rate, 250)
self.assertEqual(bom_doc.operations[1].hour_rate, 250)
+
def make_workstation(*args, **kwargs):
args = args if args else kwargs
if isinstance(args, tuple):
@@ -88,10 +104,7 @@
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 = 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")
doc.insert()
diff --git a/erpnext/manufacturing/doctype/workstation/workstation.py b/erpnext/manufacturing/doctype/workstation/workstation.py
index 4cfd410..59e5318 100644
--- a/erpnext/manufacturing/doctype/workstation/workstation.py
+++ b/erpnext/manufacturing/doctype/workstation/workstation.py
@@ -19,14 +19,26 @@
from erpnext.support.doctype.issue.issue import get_holidays
-class WorkstationHolidayError(frappe.ValidationError): pass
-class NotInWorkingHoursError(frappe.ValidationError): pass
-class OverlapError(frappe.ValidationError): pass
+class WorkstationHolidayError(frappe.ValidationError):
+ pass
+
+
+class NotInWorkingHoursError(frappe.ValidationError):
+ pass
+
+
+class OverlapError(frappe.ValidationError):
+ pass
+
class Workstation(Document):
def validate(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 = (
+ flt(self.hour_rate_labour)
+ + flt(self.hour_rate_electricity)
+ + flt(self.hour_rate_consumable)
+ + flt(self.hour_rate_rent)
+ )
def on_update(self):
self.validate_overlap_for_operation_timings()
@@ -35,29 +47,41 @@
def validate_overlap_for_operation_timings(self):
"""Check if there is no overlap in setting Workstation Operating Hours"""
for d in self.get("working_hours"):
- existing = frappe.db.sql_list("""select idx from `tabWorkstation Working Hour`
+ existing = frappe.db.sql_list(
+ """select idx from `tabWorkstation Working Hour`
where parent = %s and name != %s
and (
(start_time between %s and %s) or
(end_time between %s and %s) or
(%s between start_time and end_time))
- """, (self.name, d.name, d.start_time, d.end_time, d.start_time, d.end_time, d.start_time))
+ """,
+ (self.name, d.name, d.start_time, d.end_time, d.start_time, d.end_time, d.start_time),
+ )
if existing:
- frappe.throw(_("Row #{0}: Timings conflicts with row {1}").format(d.idx, comma_and(existing)), OverlapError)
+ frappe.throw(
+ _("Row #{0}: Timings conflicts with row {1}").format(d.idx, comma_and(existing)), OverlapError
+ )
def update_bom_operation(self):
- bom_list = frappe.db.sql("""select DISTINCT parent from `tabBOM Operation`
- where workstation = %s and parenttype = 'routing' """, self.name)
+ bom_list = frappe.db.sql(
+ """select DISTINCT parent from `tabBOM Operation`
+ where workstation = %s and parenttype = 'routing' """,
+ self.name,
+ )
for bom_no in bom_list:
- frappe.db.sql("""update `tabBOM Operation` set hour_rate = %s
+ frappe.db.sql(
+ """update `tabBOM Operation` set hour_rate = %s
where parent = %s and workstation = %s""",
- (self.hour_rate, bom_no[0], self.name))
+ (self.hour_rate, bom_no[0], self.name),
+ )
def validate_workstation_holiday(self, schedule_date, skip_holiday_list_check=False):
- if not skip_holiday_list_check and (not self.holiday_list or
- cint(frappe.db.get_single_value("Manufacturing Settings", "allow_production_on_holidays"))):
+ if not skip_holiday_list_check and (
+ not self.holiday_list
+ or cint(frappe.db.get_single_value("Manufacturing Settings", "allow_production_on_holidays"))
+ ):
return schedule_date
if schedule_date in tuple(get_holidays(self.holiday_list)):
@@ -66,18 +90,25 @@
return schedule_date
+
@frappe.whitelist()
def get_default_holiday_list():
- return frappe.get_cached_value('Company', frappe.defaults.get_user_default("Company"), "default_holiday_list")
+ return frappe.get_cached_value(
+ "Company", frappe.defaults.get_user_default("Company"), "default_holiday_list"
+ )
+
def check_if_within_operating_hours(workstation, operation, from_datetime, to_datetime):
if from_datetime and to_datetime:
- if not cint(frappe.db.get_value("Manufacturing Settings", "None", "allow_production_on_holidays")):
+ if not cint(
+ frappe.db.get_value("Manufacturing Settings", "None", "allow_production_on_holidays")
+ ):
check_workstation_for_holiday(workstation, from_datetime, to_datetime)
if not cint(frappe.db.get_value("Manufacturing Settings", None, "allow_overtime")):
is_within_operating_hours(workstation, operation, from_datetime, to_datetime)
+
def is_within_operating_hours(workstation, operation, from_datetime, to_datetime):
operation_length = time_diff_in_seconds(to_datetime, from_datetime)
workstation = frappe.get_doc("Workstation", workstation)
@@ -87,21 +118,35 @@
for working_hour in workstation.working_hours:
if working_hour.start_time and working_hour.end_time:
- slot_length = (to_timedelta(working_hour.end_time or "") - to_timedelta(working_hour.start_time or "")).total_seconds()
+ slot_length = (
+ to_timedelta(working_hour.end_time or "") - to_timedelta(working_hour.start_time or "")
+ ).total_seconds()
if slot_length >= operation_length:
return
- frappe.throw(_("Operation {0} longer than any available working hours in workstation {1}, break down the operation into multiple operations").format(operation, workstation.name), NotInWorkingHoursError)
+ frappe.throw(
+ _(
+ "Operation {0} longer than any available working hours in workstation {1}, break down the operation into multiple operations"
+ ).format(operation, workstation.name),
+ NotInWorkingHoursError,
+ )
+
def check_workstation_for_holiday(workstation, from_datetime, to_datetime):
holiday_list = frappe.db.get_value("Workstation", workstation, "holiday_list")
if holiday_list and from_datetime and to_datetime:
applicable_holidays = []
- for d in frappe.db.sql("""select holiday_date from `tabHoliday` where parent = %s
+ for d in frappe.db.sql(
+ """select holiday_date from `tabHoliday` where parent = %s
and holiday_date between %s and %s """,
- (holiday_list, getdate(from_datetime), getdate(to_datetime))):
- applicable_holidays.append(formatdate(d[0]))
+ (holiday_list, getdate(from_datetime), getdate(to_datetime)),
+ ):
+ applicable_holidays.append(formatdate(d[0]))
if applicable_holidays:
- frappe.throw(_("Workstation is closed on the following dates as per Holiday List: {0}")
- .format(holiday_list) + "\n" + "\n".join(applicable_holidays), WorkstationHolidayError)
+ frappe.throw(
+ _("Workstation is closed on the following dates as per Holiday List: {0}").format(holiday_list)
+ + "\n"
+ + "\n".join(applicable_holidays),
+ WorkstationHolidayError,
+ )
diff --git a/erpnext/manufacturing/doctype/workstation/workstation_dashboard.py b/erpnext/manufacturing/doctype/workstation/workstation_dashboard.py
index 1fa1494..6d02221 100644
--- a/erpnext/manufacturing/doctype/workstation/workstation_dashboard.py
+++ b/erpnext/manufacturing/doctype/workstation/workstation_dashboard.py
@@ -3,17 +3,22 @@
def get_data():
return {
- 'fieldname': 'workstation',
- 'transactions': [
+ "fieldname": "workstation",
+ "transactions": [
+ {"label": _("Master"), "items": ["BOM", "Routing", "Operation"]},
{
- 'label': _('Master'),
- 'items': ['BOM', 'Routing', 'Operation']
+ "label": _("Transaction"),
+ "items": [
+ "Work Order",
+ "Job Card",
+ ],
},
- {
- 'label': _('Transaction'),
- 'items': ['Work Order', 'Job Card',]
- }
],
- 'disable_create_buttons': ['BOM', 'Routing', 'Operation',
- 'Work Order', 'Job Card',]
+ "disable_create_buttons": [
+ "BOM",
+ "Routing",
+ "Operation",
+ "Work Order",
+ "Job Card",
+ ],
}
diff --git a/erpnext/manufacturing/report/bom_explorer/bom_explorer.py b/erpnext/manufacturing/report/bom_explorer/bom_explorer.py
index 19a80ab..c0affd9 100644
--- a/erpnext/manufacturing/report/bom_explorer/bom_explorer.py
+++ b/erpnext/manufacturing/report/bom_explorer/bom_explorer.py
@@ -11,30 +11,37 @@
get_data(filters, data)
return columns, data
+
def get_data(filters, data):
get_exploded_items(filters.bom, data)
+
def get_exploded_items(bom, data, indent=0, qty=1):
- exploded_items = frappe.get_all("BOM Item",
+ exploded_items = frappe.get_all(
+ "BOM Item",
filters={"parent": bom},
- fields= ['qty','bom_no','qty','scrap','item_code','item_name','description','uom'])
+ fields=["qty", "bom_no", "qty", "scrap", "item_code", "item_name", "description", "uom"],
+ )
for item in exploded_items:
print(item.bom_no, indent)
item["indent"] = indent
- data.append({
- 'item_code': item.item_code,
- 'item_name': item.item_name,
- 'indent': indent,
- 'bom_level': indent,
- 'bom': item.bom_no,
- 'qty': item.qty * qty,
- 'uom': item.uom,
- 'description': item.description,
- 'scrap': item.scrap
- })
+ data.append(
+ {
+ "item_code": item.item_code,
+ "item_name": item.item_name,
+ "indent": indent,
+ "bom_level": indent,
+ "bom": item.bom_no,
+ "qty": item.qty * qty,
+ "uom": item.uom,
+ "description": item.description,
+ "scrap": item.scrap,
+ }
+ )
if item.bom_no:
- get_exploded_items(item.bom_no, data, indent=indent+1, qty=item.qty)
+ get_exploded_items(item.bom_no, data, indent=indent + 1, qty=item.qty)
+
def get_columns():
return [
@@ -43,49 +50,13 @@
"fieldtype": "Link",
"fieldname": "item_code",
"width": 300,
- "options": "Item"
+ "options": "Item",
},
- {
- "label": "Item Name",
- "fieldtype": "data",
- "fieldname": "item_name",
- "width": 100
- },
- {
- "label": "BOM",
- "fieldtype": "Link",
- "fieldname": "bom",
- "width": 150,
- "options": "BOM"
- },
- {
- "label": "Qty",
- "fieldtype": "data",
- "fieldname": "qty",
- "width": 100
- },
- {
- "label": "UOM",
- "fieldtype": "data",
- "fieldname": "uom",
- "width": 100
- },
- {
- "label": "BOM Level",
- "fieldtype": "Int",
- "fieldname": "bom_level",
- "width": 100
- },
- {
- "label": "Standard Description",
- "fieldtype": "data",
- "fieldname": "description",
- "width": 150
- },
- {
- "label": "Scrap",
- "fieldtype": "data",
- "fieldname": "scrap",
- "width": 100
- },
+ {"label": "Item Name", "fieldtype": "data", "fieldname": "item_name", "width": 100},
+ {"label": "BOM", "fieldtype": "Link", "fieldname": "bom", "width": 150, "options": "BOM"},
+ {"label": "Qty", "fieldtype": "data", "fieldname": "qty", "width": 100},
+ {"label": "UOM", "fieldtype": "data", "fieldname": "uom", "width": 100},
+ {"label": "BOM Level", "fieldtype": "Int", "fieldname": "bom_level", "width": 100},
+ {"label": "Standard Description", "fieldtype": "data", "fieldname": "description", "width": 150},
+ {"label": "Scrap", "fieldtype": "data", "fieldname": "scrap", "width": 100},
]
diff --git a/erpnext/manufacturing/report/bom_operations_time/bom_operations_time.py b/erpnext/manufacturing/report/bom_operations_time/bom_operations_time.py
index eda9eb9..92c69cf 100644
--- a/erpnext/manufacturing/report/bom_operations_time/bom_operations_time.py
+++ b/erpnext/manufacturing/report/bom_operations_time/bom_operations_time.py
@@ -11,6 +11,7 @@
columns = get_columns(filters)
return columns, data
+
def get_data(filters):
bom_wise_data = {}
bom_data, report_data = [], []
@@ -24,11 +25,9 @@
bom_data.append(d.name)
row.update(d)
else:
- row.update({
- "operation": d.operation,
- "workstation": d.workstation,
- "time_in_mins": d.time_in_mins
- })
+ row.update(
+ {"operation": d.operation, "workstation": d.workstation, "time_in_mins": d.time_in_mins}
+ )
# maintain BOM wise data for grouping such as:
# {"BOM A": [{Row1}, {Row2}], "BOM B": ...}
@@ -43,20 +42,25 @@
return report_data
+
def get_filtered_data(filters):
bom = frappe.qb.DocType("BOM")
bom_ops = frappe.qb.DocType("BOM Operation")
bom_ops_query = (
frappe.qb.from_(bom)
- .join(bom_ops).on(bom.name == bom_ops.parent)
+ .join(bom_ops)
+ .on(bom.name == bom_ops.parent)
.select(
- bom.name, bom.item, bom.item_name, bom.uom,
- bom_ops.operation, bom_ops.workstation, bom_ops.time_in_mins
- ).where(
- (bom.docstatus == 1)
- & (bom.is_active == 1)
+ bom.name,
+ bom.item,
+ bom.item_name,
+ bom.uom,
+ bom_ops.operation,
+ bom_ops.workstation,
+ bom_ops.time_in_mins,
)
+ .where((bom.docstatus == 1) & (bom.is_active == 1))
)
if filters.get("item_code"):
@@ -66,18 +70,20 @@
bom_ops_query = bom_ops_query.where(bom.name.isin(filters.get("bom_id")))
if filters.get("workstation"):
- bom_ops_query = bom_ops_query.where(
- bom_ops.workstation == filters.get("workstation")
- )
+ bom_ops_query = bom_ops_query.where(bom_ops.workstation == filters.get("workstation"))
bom_operation_data = bom_ops_query.run(as_dict=True)
return bom_operation_data
+
def get_bom_count(bom_data):
- data = frappe.get_all("BOM Item",
+ data = frappe.get_all(
+ "BOM Item",
fields=["count(name) as count", "bom_no"],
- filters= {"bom_no": ("in", bom_data)}, group_by = "bom_no")
+ filters={"bom_no": ("in", bom_data)},
+ group_by="bom_no",
+ )
bom_count = {}
for d in data:
@@ -85,58 +91,42 @@
return bom_count
+
def get_args():
- return frappe._dict({
- "name": "",
- "item": "",
- "item_name": "",
- "uom": ""
- })
+ return frappe._dict({"name": "", "item": "", "item_name": "", "uom": ""})
+
def get_columns(filters):
- return [{
- "label": _("BOM ID"),
- "options": "BOM",
- "fieldname": "name",
- "fieldtype": "Link",
- "width": 220
- }, {
- "label": _("Item Code"),
- "options": "Item",
- "fieldname": "item",
- "fieldtype": "Link",
- "width": 150
- }, {
- "label": _("Item Name"),
- "fieldname": "item_name",
- "fieldtype": "Data",
- "width": 110
- }, {
- "label": _("UOM"),
- "options": "UOM",
- "fieldname": "uom",
- "fieldtype": "Link",
- "width": 100
- }, {
- "label": _("Operation"),
- "options": "Operation",
- "fieldname": "operation",
- "fieldtype": "Link",
- "width": 140
- }, {
- "label": _("Workstation"),
- "options": "Workstation",
- "fieldname": "workstation",
- "fieldtype": "Link",
- "width": 110
- }, {
- "label": _("Time (In Mins)"),
- "fieldname": "time_in_mins",
- "fieldtype": "Float",
- "width": 120
- }, {
- "label": _("Sub-assembly BOM Count"),
- "fieldname": "used_as_subassembly_items",
- "fieldtype": "Int",
- "width": 200
- }]
+ return [
+ {"label": _("BOM ID"), "options": "BOM", "fieldname": "name", "fieldtype": "Link", "width": 220},
+ {
+ "label": _("Item Code"),
+ "options": "Item",
+ "fieldname": "item",
+ "fieldtype": "Link",
+ "width": 150,
+ },
+ {"label": _("Item Name"), "fieldname": "item_name", "fieldtype": "Data", "width": 110},
+ {"label": _("UOM"), "options": "UOM", "fieldname": "uom", "fieldtype": "Link", "width": 100},
+ {
+ "label": _("Operation"),
+ "options": "Operation",
+ "fieldname": "operation",
+ "fieldtype": "Link",
+ "width": 140,
+ },
+ {
+ "label": _("Workstation"),
+ "options": "Workstation",
+ "fieldname": "workstation",
+ "fieldtype": "Link",
+ "width": 110,
+ },
+ {"label": _("Time (In Mins)"), "fieldname": "time_in_mins", "fieldtype": "Float", "width": 120},
+ {
+ "label": _("Sub-assembly BOM Count"),
+ "fieldname": "used_as_subassembly_items",
+ "fieldtype": "Int",
+ "width": 200,
+ },
+ ]
diff --git a/erpnext/manufacturing/report/bom_stock_calculated/bom_stock_calculated.py b/erpnext/manufacturing/report/bom_stock_calculated/bom_stock_calculated.py
index 2693352..933be3e 100644
--- a/erpnext/manufacturing/report/bom_stock_calculated/bom_stock_calculated.py
+++ b/erpnext/manufacturing/report/bom_stock_calculated/bom_stock_calculated.py
@@ -23,14 +23,24 @@
summ_data.append(get_report_data(last_pur_price, reqd_qty, row, manufacture_details))
return columns, summ_data
+
def get_report_data(last_pur_price, reqd_qty, row, manufacture_details):
to_build = row.to_build if row.to_build > 0 else 0
diff_qty = to_build - reqd_qty
- return [row.item_code, row.description,
- comma_and(manufacture_details.get(row.item_code, {}).get('manufacturer', []), add_quotes=False),
- comma_and(manufacture_details.get(row.item_code, {}).get('manufacturer_part', []), add_quotes=False),
- row.actual_qty, str(to_build),
- reqd_qty, diff_qty, last_pur_price]
+ return [
+ row.item_code,
+ row.description,
+ comma_and(manufacture_details.get(row.item_code, {}).get("manufacturer", []), add_quotes=False),
+ comma_and(
+ manufacture_details.get(row.item_code, {}).get("manufacturer_part", []), add_quotes=False
+ ),
+ row.actual_qty,
+ str(to_build),
+ reqd_qty,
+ diff_qty,
+ last_pur_price,
+ ]
+
def get_columns():
"""return columns"""
@@ -41,12 +51,13 @@
_("Manufacturer Part Number") + "::250",
_("Qty") + ":Float:50",
_("Stock Qty") + ":Float:100",
- _("Reqd Qty")+ ":Float:100",
- _("Diff Qty")+ ":Float:100",
- _("Last Purchase Price")+ ":Float:100",
+ _("Reqd Qty") + ":Float:100",
+ _("Diff Qty") + ":Float:100",
+ _("Last Purchase Price") + ":Float:100",
]
return columns
+
def get_bom_stock(filters):
conditions = ""
bom = filters.get("bom")
@@ -59,18 +70,23 @@
qty_field = "stock_qty"
if filters.get("warehouse"):
- warehouse_details = frappe.db.get_value("Warehouse", filters.get("warehouse"), ["lft", "rgt"], as_dict=1)
+ warehouse_details = frappe.db.get_value(
+ "Warehouse", filters.get("warehouse"), ["lft", "rgt"], as_dict=1
+ )
if warehouse_details:
- conditions += " and exists (select name from `tabWarehouse` wh \
- where wh.lft >= %s and wh.rgt <= %s and ledger.warehouse = wh.name)" % (warehouse_details.lft,
- warehouse_details.rgt)
+ conditions += (
+ " and exists (select name from `tabWarehouse` wh \
+ where wh.lft >= %s and wh.rgt <= %s and ledger.warehouse = wh.name)"
+ % (warehouse_details.lft, warehouse_details.rgt)
+ )
else:
conditions += " and ledger.warehouse = %s" % frappe.db.escape(filters.get("warehouse"))
else:
conditions += ""
- return frappe.db.sql("""
+ return frappe.db.sql(
+ """
SELECT
bom_item.item_code,
bom_item.description,
@@ -86,14 +102,21 @@
WHERE
bom_item.parent = '{bom}' and bom_item.parenttype='BOM'
- GROUP BY bom_item.item_code""".format(qty_field=qty_field, table=table, conditions=conditions, bom=bom), as_dict=1)
+ GROUP BY bom_item.item_code""".format(
+ qty_field=qty_field, table=table, conditions=conditions, bom=bom
+ ),
+ as_dict=1,
+ )
+
def get_manufacturer_records():
- details = frappe.get_all('Item Manufacturer', fields = ["manufacturer", "manufacturer_part_no", "item_code"])
+ details = frappe.get_all(
+ "Item Manufacturer", fields=["manufacturer", "manufacturer_part_no", "item_code"]
+ )
manufacture_details = frappe._dict()
for detail in details:
- dic = manufacture_details.setdefault(detail.get('item_code'), {})
- dic.setdefault('manufacturer', []).append(detail.get('manufacturer'))
- dic.setdefault('manufacturer_part', []).append(detail.get('manufacturer_part_no'))
+ dic = manufacture_details.setdefault(detail.get("item_code"), {})
+ dic.setdefault("manufacturer", []).append(detail.get("manufacturer"))
+ dic.setdefault("manufacturer_part", []).append(detail.get("manufacturer_part_no"))
return manufacture_details
diff --git a/erpnext/manufacturing/report/bom_stock_report/bom_stock_report.py b/erpnext/manufacturing/report/bom_stock_report/bom_stock_report.py
index fa94391..34e9826 100644
--- a/erpnext/manufacturing/report/bom_stock_report/bom_stock_report.py
+++ b/erpnext/manufacturing/report/bom_stock_report/bom_stock_report.py
@@ -7,7 +7,8 @@
def execute(filters=None):
- if not filters: filters = {}
+ if not filters:
+ filters = {}
columns = get_columns()
@@ -15,6 +16,7 @@
return columns, data
+
def get_columns():
"""return columns"""
columns = [
@@ -29,6 +31,7 @@
return columns
+
def get_bom_stock(filters):
conditions = ""
bom = filters.get("bom")
@@ -37,25 +40,30 @@
qty_field = "stock_qty"
qty_to_produce = filters.get("qty_to_produce", 1)
- if int(qty_to_produce) <= 0:
+ if int(qty_to_produce) <= 0:
frappe.throw(_("Quantity to Produce can not be less than Zero"))
if filters.get("show_exploded_view"):
table = "`tabBOM Explosion Item`"
if filters.get("warehouse"):
- warehouse_details = frappe.db.get_value("Warehouse", filters.get("warehouse"), ["lft", "rgt"], as_dict=1)
+ warehouse_details = frappe.db.get_value(
+ "Warehouse", filters.get("warehouse"), ["lft", "rgt"], as_dict=1
+ )
if warehouse_details:
- conditions += " and exists (select name from `tabWarehouse` wh \
- where wh.lft >= %s and wh.rgt <= %s and ledger.warehouse = wh.name)" % (warehouse_details.lft,
- warehouse_details.rgt)
+ conditions += (
+ " and exists (select name from `tabWarehouse` wh \
+ where wh.lft >= %s and wh.rgt <= %s and ledger.warehouse = wh.name)"
+ % (warehouse_details.lft, warehouse_details.rgt)
+ )
else:
conditions += " and ledger.warehouse = %s" % frappe.db.escape(filters.get("warehouse"))
else:
conditions += ""
- return frappe.db.sql("""
+ return frappe.db.sql(
+ """
SELECT
bom_item.item_code,
bom_item.description ,
@@ -74,9 +82,10 @@
bom_item.parent = {bom} and bom_item.parenttype='BOM'
GROUP BY bom_item.item_code""".format(
- qty_field=qty_field,
- table=table,
- conditions=conditions,
- bom=frappe.db.escape(bom),
- qty_to_produce=qty_to_produce or 1)
- )
+ qty_field=qty_field,
+ table=table,
+ conditions=conditions,
+ bom=frappe.db.escape(bom),
+ qty_to_produce=qty_to_produce or 1,
+ )
+ )
diff --git a/erpnext/manufacturing/report/bom_variance_report/bom_variance_report.py b/erpnext/manufacturing/report/bom_variance_report/bom_variance_report.py
index a5ae43e..3fe2198 100644
--- a/erpnext/manufacturing/report/bom_variance_report/bom_variance_report.py
+++ b/erpnext/manufacturing/report/bom_variance_report/bom_variance_report.py
@@ -12,98 +12,99 @@
data = get_data(filters)
return columns, data
+
def get_columns(filters):
- columns = [{
+ columns = [
+ {
"label": _("Work Order"),
"fieldname": "work_order",
"fieldtype": "Link",
"options": "Work Order",
- "width": 120
- }]
-
- if not filters.get('bom_no'):
- columns.extend([
- {
- "label": _("BOM No"),
- "fieldname": "bom_no",
- "fieldtype": "Link",
- "options": "BOM",
- "width": 180
- }
- ])
-
- columns.extend([
- {
- "label": _("Finished Good"),
- "fieldname": "production_item",
- "fieldtype": "Link",
- "options": "Item",
- "width": 120
- },
- {
- "label": _("Ordered Qty"),
- "fieldname": "qty",
- "fieldtype": "Float",
- "width": 120
- },
- {
- "label": _("Produced Qty"),
- "fieldname": "produced_qty",
- "fieldtype": "Float",
- "width": 120
- },
- {
- "label": _("Raw Material"),
- "fieldname": "raw_material_code",
- "fieldtype": "Link",
- "options": "Item",
- "width": 120
- },
- {
- "label": _("Required Qty"),
- "fieldname": "required_qty",
- "fieldtype": "Float",
- "width": 120
- },
- {
- "label": _("Consumed Qty"),
- "fieldname": "consumed_qty",
- "fieldtype": "Float",
- "width": 120
+ "width": 120,
}
- ])
+ ]
+
+ if not filters.get("bom_no"):
+ columns.extend(
+ [
+ {
+ "label": _("BOM No"),
+ "fieldname": "bom_no",
+ "fieldtype": "Link",
+ "options": "BOM",
+ "width": 180,
+ }
+ ]
+ )
+
+ columns.extend(
+ [
+ {
+ "label": _("Finished Good"),
+ "fieldname": "production_item",
+ "fieldtype": "Link",
+ "options": "Item",
+ "width": 120,
+ },
+ {"label": _("Ordered Qty"), "fieldname": "qty", "fieldtype": "Float", "width": 120},
+ {"label": _("Produced Qty"), "fieldname": "produced_qty", "fieldtype": "Float", "width": 120},
+ {
+ "label": _("Raw Material"),
+ "fieldname": "raw_material_code",
+ "fieldtype": "Link",
+ "options": "Item",
+ "width": 120,
+ },
+ {"label": _("Required Qty"), "fieldname": "required_qty", "fieldtype": "Float", "width": 120},
+ {"label": _("Consumed Qty"), "fieldname": "consumed_qty", "fieldtype": "Float", "width": 120},
+ ]
+ )
return columns
+
def get_data(filters):
cond = "1=1"
- if filters.get('bom_no') and not filters.get('work_order'):
- cond += " and bom_no = '%s'" % filters.get('bom_no')
+ if filters.get("bom_no") and not filters.get("work_order"):
+ cond += " and bom_no = '%s'" % filters.get("bom_no")
- if filters.get('work_order'):
- cond += " and name = '%s'" % filters.get('work_order')
+ if filters.get("work_order"):
+ cond += " and name = '%s'" % filters.get("work_order")
results = []
- for d in frappe.db.sql(""" select name as work_order, qty, produced_qty, production_item, bom_no
- from `tabWork Order` where produced_qty > qty and docstatus = 1 and {0}""".format(cond), as_dict=1):
+ for d in frappe.db.sql(
+ """ select name as work_order, qty, produced_qty, production_item, bom_no
+ from `tabWork Order` where produced_qty > qty and docstatus = 1 and {0}""".format(
+ cond
+ ),
+ as_dict=1,
+ ):
results.append(d)
- for data in frappe.get_all('Work Order Item', fields=["item_code as raw_material_code",
- "required_qty", "consumed_qty"], filters={'parent': d.work_order, 'parenttype': 'Work Order'}):
+ for data in frappe.get_all(
+ "Work Order Item",
+ fields=["item_code as raw_material_code", "required_qty", "consumed_qty"],
+ filters={"parent": d.work_order, "parenttype": "Work Order"},
+ ):
results.append(data)
return results
+
@frappe.whitelist()
@frappe.validate_and_sanitize_search_inputs
def get_work_orders(doctype, txt, searchfield, start, page_len, filters):
cond = "1=1"
- if filters.get('bom_no'):
- cond += " and bom_no = '%s'" % filters.get('bom_no')
+ if filters.get("bom_no"):
+ cond += " and bom_no = '%s'" % filters.get("bom_no")
- return frappe.db.sql("""select name from `tabWork Order`
+ return frappe.db.sql(
+ """select name from `tabWork Order`
where name like %(name)s and {0} and produced_qty > qty and docstatus = 1
- order by name limit {1}, {2}""".format(cond, start, page_len),{
- 'name': "%%%s%%" % txt
- }, as_list=1)
+ order by name limit {1}, {2}""".format(
+ cond, start, page_len
+ ),
+ {"name": "%%%s%%" % txt},
+ as_list=1,
+ )
diff --git a/erpnext/manufacturing/report/cost_of_poor_quality_report/cost_of_poor_quality_report.py b/erpnext/manufacturing/report/cost_of_poor_quality_report/cost_of_poor_quality_report.py
index 88b2117..481fe51 100644
--- a/erpnext/manufacturing/report/cost_of_poor_quality_report/cost_of_poor_quality_report.py
+++ b/erpnext/manufacturing/report/cost_of_poor_quality_report/cost_of_poor_quality_report.py
@@ -11,58 +11,77 @@
def get_data(report_filters):
data = []
- operations = frappe.get_all("Operation", filters = {"is_corrective_operation": 1})
+ operations = frappe.get_all("Operation", filters={"is_corrective_operation": 1})
if operations:
- if report_filters.get('operation'):
- operations = [report_filters.get('operation')]
+ if report_filters.get("operation"):
+ operations = [report_filters.get("operation")]
else:
operations = [d.name for d in operations]
job_card = frappe.qb.DocType("Job Card")
- operating_cost = ((job_card.hour_rate) * (job_card.total_time_in_mins) / 60.0).as_('operating_cost')
- item_code = (job_card.production_item).as_('item_code')
+ operating_cost = ((job_card.hour_rate) * (job_card.total_time_in_mins) / 60.0).as_(
+ "operating_cost"
+ )
+ item_code = (job_card.production_item).as_("item_code")
- query = (frappe.qb
- .from_(job_card)
- .select(job_card.name, job_card.work_order, item_code, job_card.item_name,
- job_card.operation, job_card.serial_no, job_card.batch_no,
- job_card.workstation, job_card.total_time_in_mins, job_card.hour_rate,
- operating_cost)
- .where(
- (job_card.docstatus == 1)
- & (job_card.is_corrective_job_card == 1))
- .groupby(job_card.name)
- )
+ query = (
+ frappe.qb.from_(job_card)
+ .select(
+ job_card.name,
+ job_card.work_order,
+ item_code,
+ job_card.item_name,
+ job_card.operation,
+ job_card.serial_no,
+ job_card.batch_no,
+ job_card.workstation,
+ job_card.total_time_in_mins,
+ job_card.hour_rate,
+ operating_cost,
+ )
+ .where((job_card.docstatus == 1) & (job_card.is_corrective_job_card == 1))
+ .groupby(job_card.name)
+ )
query = append_filters(query, report_filters, operations, job_card)
data = query.run(as_dict=True)
return data
-def append_filters(query, report_filters, operations, job_card):
- """Append optional filters to query builder. """
- for field in ("name", "work_order", "operation", "workstation",
- "company", "serial_no", "batch_no", "production_item"):
+def append_filters(query, report_filters, operations, job_card):
+ """Append optional filters to query builder."""
+
+ for field in (
+ "name",
+ "work_order",
+ "operation",
+ "workstation",
+ "company",
+ "serial_no",
+ "batch_no",
+ "production_item",
+ ):
if report_filters.get(field):
- if field == 'serial_no':
- query = query.where(job_card[field].like('%{}%'.format(report_filters.get(field))))
- elif field == 'operation':
+ if field == "serial_no":
+ query = query.where(job_card[field].like("%{}%".format(report_filters.get(field))))
+ elif field == "operation":
query = query.where(job_card[field].isin(operations))
else:
query = query.where(job_card[field] == report_filters.get(field))
- if report_filters.get('from_date') or report_filters.get('to_date'):
+ if report_filters.get("from_date") or report_filters.get("to_date"):
job_card_time_log = frappe.qb.DocType("Job Card Time Log")
query = query.join(job_card_time_log).on(job_card.name == job_card_time_log.parent)
- if report_filters.get('from_date'):
- query = query.where(job_card_time_log.from_time >= report_filters.get('from_date'))
- if report_filters.get('to_date'):
- query = query.where(job_card_time_log.to_time <= report_filters.get('to_date'))
+ if report_filters.get("from_date"):
+ query = query.where(job_card_time_log.from_time >= report_filters.get("from_date"))
+ if report_filters.get("to_date"):
+ query = query.where(job_card_time_log.to_time <= report_filters.get("to_date"))
return query
+
def get_columns(filters):
return [
{
@@ -70,64 +89,49 @@
"fieldtype": "Link",
"fieldname": "name",
"options": "Job Card",
- "width": "120"
+ "width": "120",
},
{
"label": _("Work Order"),
"fieldtype": "Link",
"fieldname": "work_order",
"options": "Work Order",
- "width": "100"
+ "width": "100",
},
{
"label": _("Item Code"),
"fieldtype": "Link",
"fieldname": "item_code",
"options": "Item",
- "width": "100"
+ "width": "100",
},
- {
- "label": _("Item Name"),
- "fieldtype": "Data",
- "fieldname": "item_name",
- "width": "100"
- },
+ {"label": _("Item Name"), "fieldtype": "Data", "fieldname": "item_name", "width": "100"},
{
"label": _("Operation"),
"fieldtype": "Link",
"fieldname": "operation",
"options": "Operation",
- "width": "100"
+ "width": "100",
},
- {
- "label": _("Serial No"),
- "fieldtype": "Data",
- "fieldname": "serial_no",
- "width": "100"
- },
- {
- "label": _("Batch No"),
- "fieldtype": "Data",
- "fieldname": "batch_no",
- "width": "100"
- },
+ {"label": _("Serial No"), "fieldtype": "Data", "fieldname": "serial_no", "width": "100"},
+ {"label": _("Batch No"), "fieldtype": "Data", "fieldname": "batch_no", "width": "100"},
{
"label": _("Workstation"),
"fieldtype": "Link",
"fieldname": "workstation",
"options": "Workstation",
- "width": "100"
+ "width": "100",
},
{
"label": _("Operating Cost"),
"fieldtype": "Currency",
"fieldname": "operating_cost",
- "width": "150"
+ "width": "150",
},
{
"label": _("Total Time (in Mins)"),
"fieldtype": "Float",
"fieldname": "total_time_in_mins",
- "width": "150"
- }
+ "width": "150",
+ },
]
diff --git a/erpnext/manufacturing/report/downtime_analysis/downtime_analysis.py b/erpnext/manufacturing/report/downtime_analysis/downtime_analysis.py
index 2c515d1..80a1564 100644
--- a/erpnext/manufacturing/report/downtime_analysis/downtime_analysis.py
+++ b/erpnext/manufacturing/report/downtime_analysis/downtime_analysis.py
@@ -14,10 +14,20 @@
chart_data = get_chart_data(data, filters)
return columns, data, None, chart_data
+
def get_data(filters):
query_filters = {}
- fields = ["name", "workstation", "operator", "from_time", "to_time", "downtime", "stop_reason", "remarks"]
+ fields = [
+ "name",
+ "workstation",
+ "operator",
+ "from_time",
+ "to_time",
+ "downtime",
+ "stop_reason",
+ "remarks",
+ ]
query_filters["from_time"] = (">=", filters.get("from_date"))
query_filters["to_time"] = ("<=", filters.get("to_date"))
@@ -25,13 +35,14 @@
if filters.get("workstation"):
query_filters["workstation"] = filters.get("workstation")
- data = frappe.get_all("Downtime Entry", fields= fields, filters=query_filters) or []
+ data = frappe.get_all("Downtime Entry", fields=fields, filters=query_filters) or []
for d in data:
if d.downtime:
d.downtime = d.downtime / 60
return data
+
def get_chart_data(data, columns):
labels = sorted(list(set([d.workstation for d in data])))
@@ -47,17 +58,13 @@
datasets.append(workstation_wise_data.get(label, 0))
chart = {
- "data": {
- "labels": labels,
- "datasets": [
- {"name": "Machine Downtime", "values": datasets}
- ]
- },
- "type": "bar"
+ "data": {"labels": labels, "datasets": [{"name": "Machine Downtime", "values": datasets}]},
+ "type": "bar",
}
return chart
+
def get_columns(filters):
return [
{
@@ -65,50 +72,25 @@
"fieldname": "name",
"fieldtype": "Link",
"options": "Downtime Entry",
- "width": 100
+ "width": 100,
},
{
"label": _("Machine"),
"fieldname": "workstation",
"fieldtype": "Link",
"options": "Workstation",
- "width": 100
+ "width": 100,
},
{
"label": _("Operator"),
"fieldname": "operator",
"fieldtype": "Link",
"options": "Employee",
- "width": 130
+ "width": 130,
},
- {
- "label": _("From Time"),
- "fieldname": "from_time",
- "fieldtype": "Datetime",
- "width": 160
- },
- {
- "label": _("To Time"),
- "fieldname": "to_time",
- "fieldtype": "Datetime",
- "width": 160
- },
- {
- "label": _("Downtime (In Hours)"),
- "fieldname": "downtime",
- "fieldtype": "Float",
- "width": 150
- },
- {
- "label": _("Stop Reason"),
- "fieldname": "stop_reason",
- "fieldtype": "Data",
- "width": 220
- },
- {
- "label": _("Remarks"),
- "fieldname": "remarks",
- "fieldtype": "Text",
- "width": 100
- }
+ {"label": _("From Time"), "fieldname": "from_time", "fieldtype": "Datetime", "width": 160},
+ {"label": _("To Time"), "fieldname": "to_time", "fieldtype": "Datetime", "width": 160},
+ {"label": _("Downtime (In Hours)"), "fieldname": "downtime", "fieldtype": "Float", "width": 150},
+ {"label": _("Stop Reason"), "fieldname": "stop_reason", "fieldtype": "Data", "width": 220},
+ {"label": _("Remarks"), "fieldname": "remarks", "fieldtype": "Text", "width": 100},
]
diff --git a/erpnext/manufacturing/report/exponential_smoothing_forecasting/exponential_smoothing_forecasting.py b/erpnext/manufacturing/report/exponential_smoothing_forecasting/exponential_smoothing_forecasting.py
index 26b3359..7500744 100644
--- a/erpnext/manufacturing/report/exponential_smoothing_forecasting/exponential_smoothing_forecasting.py
+++ b/erpnext/manufacturing/report/exponential_smoothing_forecasting/exponential_smoothing_forecasting.py
@@ -14,6 +14,7 @@
def execute(filters=None):
return ForecastingReport(filters).execute_report()
+
class ExponentialSmoothingForecast(object):
def forecast_future_data(self):
for key, value in self.period_wise_data.items():
@@ -26,24 +27,22 @@
elif forecast_data:
previous_period_data = forecast_data[-1]
- value[forecast_key] = (previous_period_data[1] +
- flt(self.filters.smoothing_constant) * (
- flt(previous_period_data[0]) - flt(previous_period_data[1])
- )
+ value[forecast_key] = previous_period_data[1] + flt(self.filters.smoothing_constant) * (
+ flt(previous_period_data[0]) - flt(previous_period_data[1])
)
if value.get(forecast_key):
# will be use to forecaset next period
forecast_data.append([value.get(period.key), value.get(forecast_key)])
+
class ForecastingReport(ExponentialSmoothingForecast):
def __init__(self, filters=None):
self.filters = frappe._dict(filters or {})
self.data = []
self.doctype = self.filters.based_on_document
self.child_doctype = self.doctype + " Item"
- self.based_on_field = ("qty"
- if self.filters.based_on_field == "Qty" else "amount")
+ self.based_on_field = "qty" if self.filters.based_on_field == "Qty" else "amount"
self.fieldtype = "Float" if self.based_on_field == "qty" else "Currency"
self.company_currency = erpnext.get_company_currency(self.filters.company)
@@ -63,8 +62,15 @@
self.period_wise_data = {}
from_date = add_years(self.filters.from_date, cint(self.filters.no_of_years) * -1)
- self.period_list = get_period_list(from_date, self.filters.to_date,
- from_date, self.filters.to_date, "Date Range", self.filters.periodicity, ignore_fiscal_year=True)
+ self.period_list = get_period_list(
+ from_date,
+ self.filters.to_date,
+ from_date,
+ self.filters.to_date,
+ "Date Range",
+ self.filters.periodicity,
+ ignore_fiscal_year=True,
+ )
order_data = self.get_data_for_forecast() or []
@@ -76,8 +82,10 @@
period_data = self.period_wise_data[key]
for period in self.period_list:
# check if posting date is within the period
- if (entry.posting_date >= period.from_date and entry.posting_date <= period.to_date):
- period_data[period.key] = period_data.get(period.key, 0.0) + flt(entry.get(self.based_on_field))
+ if entry.posting_date >= period.from_date and entry.posting_date <= period.to_date:
+ period_data[period.key] = period_data.get(period.key, 0.0) + flt(
+ entry.get(self.based_on_field)
+ )
for key, value in self.period_wise_data.items():
list_of_period_value = [value.get(p.key, 0) for p in self.period_list]
@@ -90,12 +98,12 @@
def get_data_for_forecast(self):
cond = ""
if self.filters.item_code:
- cond = " AND soi.item_code = %s" %(frappe.db.escape(self.filters.item_code))
+ cond = " AND soi.item_code = %s" % (frappe.db.escape(self.filters.item_code))
warehouses = []
if self.filters.warehouse:
warehouses = get_child_warehouses(self.filters.warehouse)
- cond += " AND soi.warehouse in ({})".format(','.join(['%s'] * len(warehouses)))
+ cond += " AND soi.warehouse in ({})".format(",".join(["%s"] * len(warehouses)))
input_data = [self.filters.from_date, self.filters.company]
if warehouses:
@@ -103,7 +111,8 @@
date_field = "posting_date" if self.doctype == "Delivery Note" else "transaction_date"
- return frappe.db.sql("""
+ return frappe.db.sql(
+ """
SELECT
so.{date_field} as posting_date, soi.item_code, soi.warehouse,
soi.item_name, soi.stock_qty as qty, soi.base_amount as amount
@@ -112,23 +121,27 @@
WHERE
so.docstatus = 1 AND so.name = soi.parent AND
so.{date_field} < %s AND so.company = %s {cond}
- """.format(doc=self.doctype, child_doc=self.child_doctype, date_field=date_field, cond=cond),
- tuple(input_data), as_dict=1)
+ """.format(
+ doc=self.doctype, child_doc=self.child_doctype, date_field=date_field, cond=cond
+ ),
+ tuple(input_data),
+ as_dict=1,
+ )
def prepare_final_data(self):
self.data = []
- if not self.period_wise_data: return
+ if not self.period_wise_data:
+ return
for key in self.period_wise_data:
self.data.append(self.period_wise_data.get(key))
def add_total(self):
- if not self.data: return
+ if not self.data:
+ return
- total_row = {
- "item_code": _(frappe.bold("Total Quantity"))
- }
+ total_row = {"item_code": _(frappe.bold("Total Quantity"))}
for value in self.data:
for period in self.period_list:
@@ -145,43 +158,52 @@
self.data.append(total_row)
def get_columns(self):
- columns = [{
- "label": _("Item Code"),
- "options": "Item",
- "fieldname": "item_code",
- "fieldtype": "Link",
- "width": 130
- }, {
- "label": _("Warehouse"),
- "options": "Warehouse",
- "fieldname": "warehouse",
- "fieldtype": "Link",
- "width": 130
- }]
+ columns = [
+ {
+ "label": _("Item Code"),
+ "options": "Item",
+ "fieldname": "item_code",
+ "fieldtype": "Link",
+ "width": 130,
+ },
+ {
+ "label": _("Warehouse"),
+ "options": "Warehouse",
+ "fieldname": "warehouse",
+ "fieldtype": "Link",
+ "width": 130,
+ },
+ ]
- width = 180 if self.filters.periodicity in ['Yearly', "Half-Yearly", "Quarterly"] else 100
+ width = 180 if self.filters.periodicity in ["Yearly", "Half-Yearly", "Quarterly"] else 100
for period in self.period_list:
- if (self.filters.periodicity in ['Yearly', "Half-Yearly", "Quarterly"]
- or period.from_date >= getdate(self.filters.from_date)):
+ if self.filters.periodicity in [
+ "Yearly",
+ "Half-Yearly",
+ "Quarterly",
+ ] or period.from_date >= getdate(self.filters.from_date):
forecast_key = period.key
label = _(period.label)
if period.from_date >= getdate(self.filters.from_date):
- forecast_key = 'forecast_' + period.key
+ forecast_key = "forecast_" + period.key
label = _(period.label) + " " + _("(Forecast)")
- columns.append({
- "label": label,
- "fieldname": forecast_key,
- "fieldtype": self.fieldtype,
- "width": width,
- "default": 0.0
- })
+ columns.append(
+ {
+ "label": label,
+ "fieldname": forecast_key,
+ "fieldtype": self.fieldtype,
+ "width": width,
+ "default": 0.0,
+ }
+ )
return columns
def get_chart_data(self):
- if not self.data: return
+ if not self.data:
+ return
labels = []
self.total_demand = []
@@ -206,40 +228,35 @@
"data": {
"labels": labels,
"datasets": [
- {
- "name": "Demand",
- "values": self.total_demand
- },
- {
- "name": "Forecast",
- "values": self.total_forecast
- }
- ]
+ {"name": "Demand", "values": self.total_demand},
+ {"name": "Forecast", "values": self.total_forecast},
+ ],
},
- "type": "line"
+ "type": "line",
}
def get_summary_data(self):
- if not self.data: return
+ if not self.data:
+ return
return [
{
"value": sum(self.total_demand),
"label": _("Total Demand (Past Data)"),
"currency": self.company_currency,
- "datatype": self.fieldtype
+ "datatype": self.fieldtype,
},
{
"value": sum(self.total_history_forecast),
"label": _("Total Forecast (Past Data)"),
"currency": self.company_currency,
- "datatype": self.fieldtype
+ "datatype": self.fieldtype,
},
{
"value": sum(self.total_future_forecast),
"indicator": "Green",
"label": _("Total Forecast (Future Data)"),
"currency": self.company_currency,
- "datatype": self.fieldtype
- }
+ "datatype": self.fieldtype,
+ },
]
diff --git a/erpnext/manufacturing/report/job_card_summary/job_card_summary.py b/erpnext/manufacturing/report/job_card_summary/job_card_summary.py
index 4046bb1..a86c7a4 100644
--- a/erpnext/manufacturing/report/job_card_summary/job_card_summary.py
+++ b/erpnext/manufacturing/report/job_card_summary/job_card_summary.py
@@ -16,23 +16,34 @@
chart_data = get_chart_data(data, filters)
return columns, data, None, chart_data
+
def get_data(filters):
query_filters = {
"docstatus": ("<", 2),
- "posting_date": ("between", [filters.from_date, filters.to_date])
+ "posting_date": ("between", [filters.from_date, filters.to_date]),
}
- fields = ["name", "status", "work_order", "production_item", "item_name", "posting_date",
- "total_completed_qty", "workstation", "operation", "total_time_in_mins"]
+ fields = [
+ "name",
+ "status",
+ "work_order",
+ "production_item",
+ "item_name",
+ "posting_date",
+ "total_completed_qty",
+ "workstation",
+ "operation",
+ "total_time_in_mins",
+ ]
for field in ["work_order", "workstation", "operation", "company"]:
if filters.get(field):
query_filters[field] = ("in", filters.get(field))
- data = frappe.get_all("Job Card",
- fields= fields, filters=query_filters)
+ data = frappe.get_all("Job Card", fields=fields, filters=query_filters)
- if not data: return []
+ if not data:
+ return []
job_cards = [d.name for d in data]
@@ -42,9 +53,12 @@
}
job_card_time_details = {}
- for job_card_data in frappe.get_all("Job Card Time Log",
+ for job_card_data in frappe.get_all(
+ "Job Card Time Log",
fields=["min(from_time) as from_time", "max(to_time) as to_time", "parent"],
- filters=job_card_time_filter, group_by="parent"):
+ filters=job_card_time_filter,
+ group_by="parent",
+ ):
job_card_time_details[job_card_data.parent] = job_card_data
res = []
@@ -60,6 +74,7 @@
return res
+
def get_chart_data(job_card_details, filters):
labels, periodic_data = prepare_chart_data(job_card_details, filters)
@@ -73,23 +88,15 @@
datasets.append({"name": "Open", "values": open_job_cards})
datasets.append({"name": "Completed", "values": completed})
- chart = {
- "data": {
- 'labels': labels,
- 'datasets': datasets
- },
- "type": "bar"
- }
+ chart = {"data": {"labels": labels, "datasets": datasets}, "type": "bar"}
return chart
+
def prepare_chart_data(job_card_details, filters):
labels = []
- periodic_data = {
- "Open": {},
- "Completed": {}
- }
+ periodic_data = {"Open": {}, "Completed": {}}
filters.range = "Monthly"
@@ -110,6 +117,7 @@
return labels, periodic_data
+
def get_columns(filters):
columns = [
{
@@ -117,84 +125,62 @@
"fieldname": "name",
"fieldtype": "Link",
"options": "Job Card",
- "width": 100
+ "width": 100,
},
- {
- "label": _("Posting Date"),
- "fieldname": "posting_date",
- "fieldtype": "Date",
- "width": 100
- },
+ {"label": _("Posting Date"), "fieldname": "posting_date", "fieldtype": "Date", "width": 100},
]
if not filters.get("status"):
columns.append(
- {
- "label": _("Status"),
- "fieldname": "status",
- "width": 100
- },
+ {"label": _("Status"), "fieldname": "status", "width": 100},
)
- columns.extend([
- {
- "label": _("Work Order"),
- "fieldname": "work_order",
- "fieldtype": "Link",
- "options": "Work Order",
- "width": 100
- },
- {
- "label": _("Production Item"),
- "fieldname": "production_item",
- "fieldtype": "Link",
- "options": "Item",
- "width": 110
- },
- {
- "label": _("Item Name"),
- "fieldname": "item_name",
- "fieldtype": "Data",
- "width": 100
- },
- {
- "label": _("Workstation"),
- "fieldname": "workstation",
- "fieldtype": "Link",
- "options": "Workstation",
- "width": 110
- },
- {
- "label": _("Operation"),
- "fieldname": "operation",
- "fieldtype": "Link",
- "options": "Operation",
- "width": 110
- },
- {
- "label": _("Total Completed Qty"),
- "fieldname": "total_completed_qty",
- "fieldtype": "Float",
- "width": 120
- },
- {
- "label": _("From Time"),
- "fieldname": "from_time",
- "fieldtype": "Datetime",
- "width": 120
- },
- {
- "label": _("To Time"),
- "fieldname": "to_time",
- "fieldtype": "Datetime",
- "width": 120
- },
- {
- "label": _("Time Required (In Mins)"),
- "fieldname": "total_time_in_mins",
- "fieldtype": "Float",
- "width": 100
- }
- ])
+ columns.extend(
+ [
+ {
+ "label": _("Work Order"),
+ "fieldname": "work_order",
+ "fieldtype": "Link",
+ "options": "Work Order",
+ "width": 100,
+ },
+ {
+ "label": _("Production Item"),
+ "fieldname": "production_item",
+ "fieldtype": "Link",
+ "options": "Item",
+ "width": 110,
+ },
+ {"label": _("Item Name"), "fieldname": "item_name", "fieldtype": "Data", "width": 100},
+ {
+ "label": _("Workstation"),
+ "fieldname": "workstation",
+ "fieldtype": "Link",
+ "options": "Workstation",
+ "width": 110,
+ },
+ {
+ "label": _("Operation"),
+ "fieldname": "operation",
+ "fieldtype": "Link",
+ "options": "Operation",
+ "width": 110,
+ },
+ {
+ "label": _("Total Completed Qty"),
+ "fieldname": "total_completed_qty",
+ "fieldtype": "Float",
+ "width": 120,
+ },
+ {"label": _("From Time"), "fieldname": "from_time", "fieldtype": "Datetime", "width": 120},
+ {"label": _("To Time"), "fieldname": "to_time", "fieldtype": "Datetime", "width": 120},
+ {
+ "label": _("Time Required (In Mins)"),
+ "fieldname": "total_time_in_mins",
+ "fieldtype": "Float",
+ "width": 100,
+ },
+ ]
+ )
return columns
diff --git a/erpnext/manufacturing/report/process_loss_report/process_loss_report.py b/erpnext/manufacturing/report/process_loss_report/process_loss_report.py
index 9b544da..b10e643 100644
--- a/erpnext/manufacturing/report/process_loss_report/process_loss_report.py
+++ b/erpnext/manufacturing/report/process_loss_report/process_loss_report.py
@@ -12,87 +12,71 @@
Columns = List[Dict[str, str]]
QueryArgs = Dict[str, str]
+
def execute(filters: Filters) -> Tuple[Columns, Data]:
columns = get_columns()
data = get_data(filters)
return columns, data
+
def get_data(filters: Filters) -> Data:
query_args = get_query_args(filters)
data = run_query(query_args)
update_data_with_total_pl_value(data)
return data
+
def get_columns() -> Columns:
return [
{
- 'label': _('Work Order'),
- 'fieldname': 'name',
- 'fieldtype': 'Link',
- 'options': 'Work Order',
- 'width': '200'
+ "label": _("Work Order"),
+ "fieldname": "name",
+ "fieldtype": "Link",
+ "options": "Work Order",
+ "width": "200",
},
{
- 'label': _('Item'),
- 'fieldname': 'production_item',
- 'fieldtype': 'Link',
- 'options': 'Item',
- 'width': '100'
+ "label": _("Item"),
+ "fieldname": "production_item",
+ "fieldtype": "Link",
+ "options": "Item",
+ "width": "100",
},
+ {"label": _("Status"), "fieldname": "status", "fieldtype": "Data", "width": "100"},
{
- 'label': _('Status'),
- 'fieldname': 'status',
- 'fieldtype': 'Data',
- 'width': '100'
+ "label": _("Manufactured Qty"),
+ "fieldname": "produced_qty",
+ "fieldtype": "Float",
+ "width": "150",
},
+ {"label": _("Loss Qty"), "fieldname": "process_loss_qty", "fieldtype": "Float", "width": "150"},
{
- 'label': _('Manufactured Qty'),
- 'fieldname': 'produced_qty',
- 'fieldtype': 'Float',
- 'width': '150'
+ "label": _("Actual Manufactured Qty"),
+ "fieldname": "actual_produced_qty",
+ "fieldtype": "Float",
+ "width": "150",
},
+ {"label": _("Loss Value"), "fieldname": "total_pl_value", "fieldtype": "Float", "width": "150"},
+ {"label": _("FG Value"), "fieldname": "total_fg_value", "fieldtype": "Float", "width": "150"},
{
- 'label': _('Loss Qty'),
- 'fieldname': 'process_loss_qty',
- 'fieldtype': 'Float',
- 'width': '150'
+ "label": _("Raw Material Value"),
+ "fieldname": "total_rm_value",
+ "fieldtype": "Float",
+ "width": "150",
},
- {
- 'label': _('Actual Manufactured Qty'),
- 'fieldname': 'actual_produced_qty',
- 'fieldtype': 'Float',
- 'width': '150'
- },
- {
- 'label': _('Loss Value'),
- 'fieldname': 'total_pl_value',
- 'fieldtype': 'Float',
- 'width': '150'
- },
- {
- 'label': _('FG Value'),
- 'fieldname': 'total_fg_value',
- 'fieldtype': 'Float',
- 'width': '150'
- },
- {
- 'label': _('Raw Material Value'),
- 'fieldname': 'total_rm_value',
- 'fieldtype': 'Float',
- 'width': '150'
- }
]
+
def get_query_args(filters: Filters) -> QueryArgs:
query_args = {}
query_args.update(filters)
- query_args.update(
- get_filter_conditions(filters)
- )
+ query_args.update(get_filter_conditions(filters))
return query_args
+
def run_query(query_args: QueryArgs) -> Data:
- return frappe.db.sql("""
+ return frappe.db.sql(
+ """
SELECT
wo.name, wo.status, wo.production_item, wo.qty,
wo.produced_qty, wo.process_loss_qty,
@@ -111,23 +95,26 @@
{work_order_filter}
GROUP BY
se.work_order
- """.format(**query_args), query_args, as_dict=1)
+ """.format(
+ **query_args
+ ),
+ query_args,
+ as_dict=1,
+ )
+
def update_data_with_total_pl_value(data: Data) -> None:
for row in data:
- value_per_unit_fg = row['total_fg_value'] / row['actual_produced_qty']
- row['total_pl_value'] = row['process_loss_qty'] * value_per_unit_fg
+ value_per_unit_fg = row["total_fg_value"] / row["actual_produced_qty"]
+ row["total_pl_value"] = row["process_loss_qty"] * value_per_unit_fg
+
def get_filter_conditions(filters: Filters) -> QueryArgs:
filter_conditions = dict(item_filter="", work_order_filter="")
if "item" in filters:
production_item = filters.get("item")
- filter_conditions.update(
- {"item_filter": f"AND wo.production_item='{production_item}'"}
- )
+ filter_conditions.update({"item_filter": f"AND wo.production_item='{production_item}'"})
if "work_order" in filters:
work_order_name = filters.get("work_order")
- filter_conditions.update(
- {"work_order_filter": f"AND wo.name='{work_order_name}'"}
- )
+ filter_conditions.update({"work_order_filter": f"AND wo.name='{work_order_name}'"})
return filter_conditions
diff --git a/erpnext/manufacturing/report/production_analytics/production_analytics.py b/erpnext/manufacturing/report/production_analytics/production_analytics.py
index d4743d3..12b5d19 100644
--- a/erpnext/manufacturing/report/production_analytics/production_analytics.py
+++ b/erpnext/manufacturing/report/production_analytics/production_analytics.py
@@ -12,16 +12,11 @@
def execute(filters=None):
columns = get_columns(filters)
data, chart = get_data(filters, columns)
- return columns, data, None , chart
+ return columns, data, None, chart
+
def get_columns(filters):
- columns =[
- {
- "label": _("Status"),
- "fieldname": "Status",
- "fieldtype": "Data",
- "width": 140
- }]
+ columns = [{"label": _("Status"), "fieldname": "Status", "fieldtype": "Data", "width": 140}]
ranges = get_period_date_ranges(filters)
@@ -29,22 +24,20 @@
period = get_period(end_date, filters)
- columns.append({
- "label": _(period),
- "fieldname": scrub(period),
- "fieldtype": "Float",
- "width": 120
- })
+ columns.append(
+ {"label": _(period), "fieldname": scrub(period), "fieldtype": "Float", "width": 120}
+ )
return columns
+
def get_periodic_data(filters, entry):
periodic_data = {
"All Work Orders": {},
"Not Started": {},
"Overdue": {},
"Pending": {},
- "Completed": {}
+ "Completed": {},
}
ranges = get_period_date_ranges(filters)
@@ -52,34 +45,37 @@
for from_date, end_date in ranges:
period = get_period(end_date, filters)
for d in entry:
- if getdate(d.creation) <= getdate(from_date) or getdate(d.creation) <= getdate(end_date) :
+ if getdate(d.creation) <= getdate(from_date) or getdate(d.creation) <= getdate(end_date):
periodic_data = update_periodic_data(periodic_data, "All Work Orders", period)
- if d.status == 'Completed':
- if getdate(d.actual_end_date) < getdate(from_date) or getdate(d.modified) < getdate(from_date):
+ if d.status == "Completed":
+ if getdate(d.actual_end_date) < getdate(from_date) or getdate(d.modified) < getdate(
+ from_date
+ ):
periodic_data = update_periodic_data(periodic_data, "Completed", period)
- elif getdate(d.actual_start_date) < getdate(from_date) :
+ elif getdate(d.actual_start_date) < getdate(from_date):
periodic_data = update_periodic_data(periodic_data, "Pending", period)
- elif getdate(d.planned_start_date) < getdate(from_date) :
+ elif getdate(d.planned_start_date) < getdate(from_date):
periodic_data = update_periodic_data(periodic_data, "Overdue", period)
else:
periodic_data = update_periodic_data(periodic_data, "Not Started", period)
- elif d.status == 'In Process':
- if getdate(d.actual_start_date) < getdate(from_date) :
+ elif d.status == "In Process":
+ if getdate(d.actual_start_date) < getdate(from_date):
periodic_data = update_periodic_data(periodic_data, "Pending", period)
- elif getdate(d.planned_start_date) < getdate(from_date) :
+ elif getdate(d.planned_start_date) < getdate(from_date):
periodic_data = update_periodic_data(periodic_data, "Overdue", period)
else:
periodic_data = update_periodic_data(periodic_data, "Not Started", period)
- elif d.status == 'Not Started':
- if getdate(d.planned_start_date) < getdate(from_date) :
+ elif d.status == "Not Started":
+ if getdate(d.planned_start_date) < getdate(from_date):
periodic_data = update_periodic_data(periodic_data, "Overdue", period)
else:
periodic_data = update_periodic_data(periodic_data, "Not Started", period)
return periodic_data
+
def update_periodic_data(periodic_data, status, period):
if periodic_data.get(status).get(period):
periodic_data[status][period] += 1
@@ -88,22 +84,33 @@
return periodic_data
+
def get_data(filters, columns):
data = []
- entry = frappe.get_all("Work Order",
- fields=["creation", "modified", "actual_start_date", "actual_end_date", "planned_start_date", "planned_end_date", "status"],
- filters={"docstatus": 1, "company": filters["company"] })
+ entry = frappe.get_all(
+ "Work Order",
+ fields=[
+ "creation",
+ "modified",
+ "actual_start_date",
+ "actual_end_date",
+ "planned_start_date",
+ "planned_end_date",
+ "status",
+ ],
+ filters={"docstatus": 1, "company": filters["company"]},
+ )
- periodic_data = get_periodic_data(filters,entry)
+ periodic_data = get_periodic_data(filters, entry)
labels = ["All Work Orders", "Not Started", "Overdue", "Pending", "Completed"]
- chart_data = get_chart_data(periodic_data,columns)
+ chart_data = get_chart_data(periodic_data, columns)
ranges = get_period_date_ranges(filters)
for label in labels:
work = {}
work["Status"] = label
- for dummy,end_date in ranges:
+ for dummy, end_date in ranges:
period = get_period(end_date, filters)
if periodic_data.get(label).get(period):
work[scrub(period)] = periodic_data.get(label).get(period)
@@ -113,10 +120,11 @@
return data, chart_data
+
def get_chart_data(periodic_data, columns):
labels = [d.get("label") for d in columns[1:]]
- all_data, not_start, overdue, pending, completed = [], [], [] , [], []
+ all_data, not_start, overdue, pending, completed = [], [], [], [], []
datasets = []
for d in labels:
@@ -126,18 +134,13 @@
pending.append(periodic_data.get("Pending").get(d))
completed.append(periodic_data.get("Completed").get(d))
- datasets.append({'name':'All Work Orders', 'values': all_data})
- datasets.append({'name':'Not Started', 'values': not_start})
- datasets.append({'name':'Overdue', 'values': overdue})
- datasets.append({'name':'Pending', 'values': pending})
- datasets.append({'name':'Completed', 'values': completed})
+ datasets.append({"name": "All Work Orders", "values": all_data})
+ datasets.append({"name": "Not Started", "values": not_start})
+ datasets.append({"name": "Overdue", "values": overdue})
+ datasets.append({"name": "Pending", "values": pending})
+ datasets.append({"name": "Completed", "values": completed})
- chart = {
- "data": {
- 'labels': labels,
- 'datasets': datasets
- }
- }
+ chart = {"data": {"labels": labels, "datasets": datasets}}
chart["type"] = "line"
return chart
diff --git a/erpnext/manufacturing/report/production_plan_summary/production_plan_summary.py b/erpnext/manufacturing/report/production_plan_summary/production_plan_summary.py
index aaa2314..17f7f5e 100644
--- a/erpnext/manufacturing/report/production_plan_summary/production_plan_summary.py
+++ b/erpnext/manufacturing/report/production_plan_summary/production_plan_summary.py
@@ -13,6 +13,7 @@
return columns, data
+
def get_data(filters):
data = []
@@ -23,6 +24,7 @@
return data
+
def get_production_plan_item_details(filters, data, order_details):
itemwise_indent = {}
@@ -30,77 +32,85 @@
for row in production_plan_doc.po_items:
work_order = frappe.get_value(
"Work Order",
- {
- "production_plan_item": row.name,
- "bom_no": row.bom_no,
- "production_item": row.item_code
- },
- "name"
+ {"production_plan_item": row.name, "bom_no": row.bom_no, "production_item": row.item_code},
+ "name",
)
if row.item_code not in itemwise_indent:
itemwise_indent.setdefault(row.item_code, {})
- data.append({
- "indent": 0,
- "item_code": row.item_code,
- "item_name": frappe.get_cached_value("Item", row.item_code, "item_name"),
- "qty": row.planned_qty,
- "document_type": "Work Order",
- "document_name": work_order or "",
- "bom_level": 0,
- "produced_qty": order_details.get((work_order, row.item_code), {}).get("produced_qty", 0),
- "pending_qty": flt(row.planned_qty) - flt(order_details.get((work_order, row.item_code), {}).get("produced_qty", 0))
- })
+ data.append(
+ {
+ "indent": 0,
+ "item_code": row.item_code,
+ "item_name": frappe.get_cached_value("Item", row.item_code, "item_name"),
+ "qty": row.planned_qty,
+ "document_type": "Work Order",
+ "document_name": work_order or "",
+ "bom_level": 0,
+ "produced_qty": order_details.get((work_order, row.item_code), {}).get("produced_qty", 0),
+ "pending_qty": flt(row.planned_qty)
+ - flt(order_details.get((work_order, row.item_code), {}).get("produced_qty", 0)),
+ }
+ )
- get_production_plan_sub_assembly_item_details(filters, row, production_plan_doc, data, order_details)
+ get_production_plan_sub_assembly_item_details(
+ filters, row, production_plan_doc, data, order_details
+ )
-def get_production_plan_sub_assembly_item_details(filters, row, production_plan_doc, data, order_details):
+
+def get_production_plan_sub_assembly_item_details(
+ filters, row, production_plan_doc, data, order_details
+):
for item in production_plan_doc.sub_assembly_items:
if row.name == item.production_plan_item:
- subcontracted_item = (item.type_of_manufacturing == 'Subcontract')
+ subcontracted_item = item.type_of_manufacturing == "Subcontract"
if subcontracted_item:
docname = frappe.get_value(
"Purchase Order Item",
- {
- "production_plan_sub_assembly_item": item.name,
- "docstatus": ("<", 2)
- },
- "parent"
+ {"production_plan_sub_assembly_item": item.name, "docstatus": ("<", 2)},
+ "parent",
)
else:
docname = frappe.get_value(
- "Work Order",
- {
- "production_plan_sub_assembly_item": item.name,
- "docstatus": ("<", 2)
- },
- "name"
+ "Work Order", {"production_plan_sub_assembly_item": item.name, "docstatus": ("<", 2)}, "name"
)
- data.append({
- "indent": 1,
- "item_code": item.production_item,
- "item_name": item.item_name,
- "qty": item.qty,
- "document_type": "Work Order" if not subcontracted_item else "Purchase Order",
- "document_name": docname or "",
- "bom_level": item.bom_level,
- "produced_qty": order_details.get((docname, item.production_item), {}).get("produced_qty", 0),
- "pending_qty": flt(item.qty) - flt(order_details.get((docname, item.production_item), {}).get("produced_qty", 0))
- })
+ data.append(
+ {
+ "indent": 1,
+ "item_code": item.production_item,
+ "item_name": item.item_name,
+ "qty": item.qty,
+ "document_type": "Work Order" if not subcontracted_item else "Purchase Order",
+ "document_name": docname or "",
+ "bom_level": item.bom_level,
+ "produced_qty": order_details.get((docname, item.production_item), {}).get("produced_qty", 0),
+ "pending_qty": flt(item.qty)
+ - flt(order_details.get((docname, item.production_item), {}).get("produced_qty", 0)),
+ }
+ )
+
def get_work_order_details(filters, order_details):
- for row in frappe.get_all("Work Order", filters = {"production_plan": filters.get("production_plan")},
- fields=["name", "produced_qty", "production_plan", "production_item"]):
+ for row in frappe.get_all(
+ "Work Order",
+ filters={"production_plan": filters.get("production_plan")},
+ fields=["name", "produced_qty", "production_plan", "production_item"],
+ ):
order_details.setdefault((row.name, row.production_item), row)
+
def get_purchase_order_details(filters, order_details):
- for row in frappe.get_all("Purchase Order Item", filters = {"production_plan": filters.get("production_plan")},
- fields=["parent", "received_qty as produced_qty", "item_code"]):
+ for row in frappe.get_all(
+ "Purchase Order Item",
+ filters={"production_plan": filters.get("production_plan")},
+ fields=["parent", "received_qty as produced_qty", "item_code"],
+ ):
order_details.setdefault((row.parent, row.item_code), row)
+
def get_column(filters):
return [
{
@@ -108,49 +118,24 @@
"fieldtype": "Link",
"fieldname": "item_code",
"width": 300,
- "options": "Item"
+ "options": "Item",
},
- {
- "label": "Item Name",
- "fieldtype": "data",
- "fieldname": "item_name",
- "width": 100
- },
+ {"label": "Item Name", "fieldtype": "data", "fieldname": "item_name", "width": 100},
{
"label": "Document Type",
"fieldtype": "Link",
"fieldname": "document_type",
"width": 150,
- "options": "DocType"
+ "options": "DocType",
},
{
"label": "Document Name",
"fieldtype": "Dynamic Link",
"fieldname": "document_name",
- "width": 150
+ "width": 150,
},
- {
- "label": "BOM Level",
- "fieldtype": "Int",
- "fieldname": "bom_level",
- "width": 100
- },
- {
- "label": "Order Qty",
- "fieldtype": "Float",
- "fieldname": "qty",
- "width": 120
- },
- {
- "label": "Received Qty",
- "fieldtype": "Float",
- "fieldname": "produced_qty",
- "width": 160
- },
- {
- "label": "Pending Qty",
- "fieldtype": "Float",
- "fieldname": "pending_qty",
- "width": 110
- }
+ {"label": "BOM Level", "fieldtype": "Int", "fieldname": "bom_level", "width": 100},
+ {"label": "Order Qty", "fieldtype": "Float", "fieldname": "qty", "width": 120},
+ {"label": "Received Qty", "fieldtype": "Float", "fieldname": "produced_qty", "width": 160},
+ {"label": "Pending Qty", "fieldtype": "Float", "fieldname": "pending_qty", "width": 110},
]
diff --git a/erpnext/manufacturing/report/production_planning_report/production_planning_report.py b/erpnext/manufacturing/report/production_planning_report/production_planning_report.py
index e1e7225..1404888 100644
--- a/erpnext/manufacturing/report/production_planning_report/production_planning_report.py
+++ b/erpnext/manufacturing/report/production_planning_report/production_planning_report.py
@@ -15,38 +15,36 @@
stock_qty as qty_to_manufacture, `tabSales Order Item`.parent as name, bom_no, warehouse,
`tabSales Order Item`.delivery_date, `tabSales Order`.base_grand_total """,
"filters": """`tabSales Order Item`.docstatus = 1 and stock_qty > produced_qty
- and `tabSales Order`.per_delivered < 100.0"""
+ and `tabSales Order`.per_delivered < 100.0""",
},
"Material Request": {
"fields": """ item_code as production_item, item_name as production_item_name, stock_uom,
stock_qty as qty_to_manufacture, `tabMaterial Request Item`.parent as name, bom_no, warehouse,
`tabMaterial Request Item`.schedule_date """,
"filters": """`tabMaterial Request`.docstatus = 1 and `tabMaterial Request`.per_ordered < 100
- and `tabMaterial Request`.material_request_type = 'Manufacture' """
+ and `tabMaterial Request`.material_request_type = 'Manufacture' """,
},
"Work Order": {
"fields": """ production_item, item_name as production_item_name, planned_start_date,
stock_uom, qty as qty_to_manufacture, name, bom_no, fg_warehouse as warehouse """,
- "filters": "docstatus = 1 and status not in ('Completed', 'Stopped')"
+ "filters": "docstatus = 1 and status not in ('Completed', 'Stopped')",
},
}
order_mapper = {
"Sales Order": {
"Delivery Date": "`tabSales Order Item`.delivery_date asc",
- "Total Amount": "`tabSales Order`.base_grand_total desc"
+ "Total Amount": "`tabSales Order`.base_grand_total desc",
},
- "Material Request": {
- "Required Date": "`tabMaterial Request Item`.schedule_date asc"
- },
- "Work Order": {
- "Planned Start Date": "planned_start_date asc"
- }
+ "Material Request": {"Required Date": "`tabMaterial Request Item`.schedule_date asc"},
+ "Work Order": {"Planned Start Date": "planned_start_date asc"},
}
+
def execute(filters=None):
return ProductionPlanReport(filters).execute_report()
+
class ProductionPlanReport(object):
def __init__(self, filters=None):
self.filters = frappe._dict(filters or {})
@@ -65,46 +63,64 @@
return self.columns, self.data
def get_open_orders(self):
- doctype = ("`tabWork Order`" if self.filters.based_on == "Work Order"
- else "`tab{doc}`, `tab{doc} Item`".format(doc=self.filters.based_on))
+ doctype = (
+ "`tabWork Order`"
+ if self.filters.based_on == "Work Order"
+ else "`tab{doc}`, `tab{doc} Item`".format(doc=self.filters.based_on)
+ )
filters = mapper.get(self.filters.based_on)["filters"]
filters = self.prepare_other_conditions(filters, self.filters.based_on)
order_by = " ORDER BY %s" % (order_mapper[self.filters.based_on][self.filters.order_by])
- self.orders = frappe.db.sql(""" SELECT {fields} from {doctype}
+ self.orders = frappe.db.sql(
+ """ SELECT {fields} from {doctype}
WHERE {filters} {order_by}""".format(
- doctype = doctype,
- filters = filters,
- order_by = order_by,
- fields = mapper.get(self.filters.based_on)["fields"]
- ), tuple(self.filters.docnames), as_dict=1)
+ doctype=doctype,
+ filters=filters,
+ order_by=order_by,
+ fields=mapper.get(self.filters.based_on)["fields"],
+ ),
+ tuple(self.filters.docnames),
+ as_dict=1,
+ )
def prepare_other_conditions(self, filters, doctype):
if self.filters.docnames:
field = "name" if doctype == "Work Order" else "`tab{} Item`.parent".format(doctype)
- filters += " and %s in (%s)" % (field, ','.join(['%s'] * len(self.filters.docnames)))
+ filters += " and %s in (%s)" % (field, ",".join(["%s"] * len(self.filters.docnames)))
if doctype != "Work Order":
filters += " and `tab{doc}`.name = `tab{doc} Item`.parent".format(doc=doctype)
if self.filters.company:
- filters += " and `tab%s`.company = %s" %(doctype, frappe.db.escape(self.filters.company))
+ filters += " and `tab%s`.company = %s" % (doctype, frappe.db.escape(self.filters.company))
return filters
def get_raw_materials(self):
- if not self.orders: return
+ if not self.orders:
+ return
self.warehouses = [d.warehouse for d in self.orders]
self.item_codes = [d.production_item for d in self.orders]
if self.filters.based_on == "Work Order":
work_orders = [d.name for d in self.orders]
- raw_materials = frappe.get_all("Work Order Item",
- fields=["parent", "item_code", "item_name as raw_material_name",
- "source_warehouse as warehouse", "required_qty"],
- filters = {"docstatus": 1, "parent": ("in", work_orders), "source_warehouse": ("!=", "")}) or []
+ raw_materials = (
+ frappe.get_all(
+ "Work Order Item",
+ fields=[
+ "parent",
+ "item_code",
+ "item_name as raw_material_name",
+ "source_warehouse as warehouse",
+ "required_qty",
+ ],
+ filters={"docstatus": 1, "parent": ("in", work_orders), "source_warehouse": ("!=", "")},
+ )
+ or []
+ )
self.warehouses.extend([d.source_warehouse for d in raw_materials])
else:
@@ -118,21 +134,32 @@
bom_nos.append(bom_no)
- bom_doctype = ("BOM Explosion Item"
- if self.filters.include_subassembly_raw_materials else "BOM Item")
+ bom_doctype = (
+ "BOM Explosion Item" if self.filters.include_subassembly_raw_materials else "BOM Item"
+ )
- qty_field = ("qty_consumed_per_unit"
- if self.filters.include_subassembly_raw_materials else "(bom_item.qty / bom.quantity)")
+ qty_field = (
+ "qty_consumed_per_unit"
+ if self.filters.include_subassembly_raw_materials
+ else "(bom_item.qty / bom.quantity)"
+ )
- raw_materials = frappe.db.sql(""" SELECT bom_item.parent, bom_item.item_code,
+ raw_materials = frappe.db.sql(
+ """ SELECT bom_item.parent, bom_item.item_code,
bom_item.item_name as raw_material_name, {0} as required_qty_per_unit
FROM
`tabBOM` as bom, `tab{1}` as bom_item
WHERE
bom_item.parent in ({2}) and bom_item.parent = bom.name and bom.docstatus = 1
- """.format(qty_field, bom_doctype, ','.join(["%s"] * len(bom_nos))), tuple(bom_nos), as_dict=1)
+ """.format(
+ qty_field, bom_doctype, ",".join(["%s"] * len(bom_nos))
+ ),
+ tuple(bom_nos),
+ as_dict=1,
+ )
- if not raw_materials: return
+ if not raw_materials:
+ return
self.item_codes.extend([d.item_code for d in raw_materials])
@@ -144,15 +171,20 @@
rows.append(d)
def get_item_details(self):
- if not (self.orders and self.item_codes): return
+ if not (self.orders and self.item_codes):
+ return
self.item_details = {}
- for d in frappe.get_all("Item Default", fields = ["parent", "default_warehouse"],
- filters = {"company": self.filters.company, "parent": ("in", self.item_codes)}):
+ for d in frappe.get_all(
+ "Item Default",
+ fields=["parent", "default_warehouse"],
+ filters={"company": self.filters.company, "parent": ("in", self.item_codes)},
+ ):
self.item_details[d.parent] = d
def get_bin_details(self):
- if not (self.orders and self.raw_materials_dict): return
+ if not (self.orders and self.raw_materials_dict):
+ return
self.bin_details = {}
self.mrp_warehouses = []
@@ -160,48 +192,55 @@
self.mrp_warehouses.extend(get_child_warehouses(self.filters.raw_material_warehouse))
self.warehouses.extend(self.mrp_warehouses)
- for d in frappe.get_all("Bin",
+ for d in frappe.get_all(
+ "Bin",
fields=["warehouse", "item_code", "actual_qty", "ordered_qty", "projected_qty"],
- filters = {"item_code": ("in", self.item_codes), "warehouse": ("in", self.warehouses)}):
+ filters={"item_code": ("in", self.item_codes), "warehouse": ("in", self.warehouses)},
+ ):
key = (d.item_code, d.warehouse)
if key not in self.bin_details:
self.bin_details.setdefault(key, d)
def get_purchase_details(self):
- if not (self.orders and self.raw_materials_dict): return
+ if not (self.orders and self.raw_materials_dict):
+ return
self.purchase_details = {}
- purchased_items = frappe.get_all("Purchase Order Item",
+ purchased_items = frappe.get_all(
+ "Purchase Order Item",
fields=["item_code", "min(schedule_date) as arrival_date", "qty as arrival_qty", "warehouse"],
filters={
"item_code": ("in", self.item_codes),
"warehouse": ("in", self.warehouses),
"docstatus": 1,
},
- group_by = "item_code, warehouse")
+ group_by="item_code, warehouse",
+ )
for d in purchased_items:
key = (d.item_code, d.warehouse)
if key not in self.purchase_details:
self.purchase_details.setdefault(key, d)
def prepare_data(self):
- if not self.orders: return
+ if not self.orders:
+ return
for d in self.orders:
key = d.name if self.filters.based_on == "Work Order" else d.bom_no
- if not self.raw_materials_dict.get(key): continue
+ if not self.raw_materials_dict.get(key):
+ continue
bin_data = self.bin_details.get((d.production_item, d.warehouse)) or {}
- d.update({
- "for_warehouse": d.warehouse,
- "available_qty": 0
- })
+ d.update({"for_warehouse": d.warehouse, "available_qty": 0})
if bin_data and bin_data.get("actual_qty") > 0 and d.qty_to_manufacture:
- d.available_qty = (bin_data.get("actual_qty")
- if (d.qty_to_manufacture > bin_data.get("actual_qty")) else d.qty_to_manufacture)
+ d.available_qty = (
+ bin_data.get("actual_qty")
+ if (d.qty_to_manufacture > bin_data.get("actual_qty"))
+ else d.qty_to_manufacture
+ )
bin_data["actual_qty"] -= d.available_qty
@@ -232,8 +271,9 @@
d.remaining_qty = d.required_qty
self.pick_materials_from_warehouses(d, data, warehouses)
- if (d.remaining_qty and self.filters.raw_material_warehouse
- and d.remaining_qty != d.required_qty):
+ if (
+ d.remaining_qty and self.filters.raw_material_warehouse and d.remaining_qty != d.required_qty
+ ):
row = self.get_args()
d.warehouse = self.filters.raw_material_warehouse
d.required_qty = d.remaining_qty
@@ -243,7 +283,8 @@
def pick_materials_from_warehouses(self, args, order_data, warehouses):
for index, warehouse in enumerate(warehouses):
- if not args.remaining_qty: return
+ if not args.remaining_qty:
+ return
row = self.get_args()
@@ -255,14 +296,18 @@
args.allotted_qty = 0
if bin_data and bin_data.get("actual_qty") > 0:
- args.allotted_qty = (bin_data.get("actual_qty")
- if (args.required_qty > bin_data.get("actual_qty")) else args.required_qty)
+ args.allotted_qty = (
+ bin_data.get("actual_qty")
+ if (args.required_qty > bin_data.get("actual_qty"))
+ else args.required_qty
+ )
args.remaining_qty -= args.allotted_qty
bin_data["actual_qty"] -= args.allotted_qty
- if ((self.mrp_warehouses and (args.allotted_qty or index == len(warehouses) - 1))
- or not self.mrp_warehouses):
+ if (
+ self.mrp_warehouses and (args.allotted_qty or index == len(warehouses) - 1)
+ ) or not self.mrp_warehouses:
if not self.index:
row.update(order_data)
self.index += 1
@@ -275,52 +320,45 @@
self.data.append(row)
def get_args(self):
- return frappe._dict({
- "work_order": "",
- "sales_order": "",
- "production_item": "",
- "production_item_name": "",
- "qty_to_manufacture": "",
- "produced_qty": ""
- })
+ return frappe._dict(
+ {
+ "work_order": "",
+ "sales_order": "",
+ "production_item": "",
+ "production_item_name": "",
+ "qty_to_manufacture": "",
+ "produced_qty": "",
+ }
+ )
def get_columns(self):
based_on = self.filters.based_on
- self.columns = [{
- "label": _("ID"),
- "options": based_on,
- "fieldname": "name",
- "fieldtype": "Link",
- "width": 100
- }, {
- "label": _("Item Code"),
- "fieldname": "production_item",
- "fieldtype": "Link",
- "options": "Item",
- "width": 120
- }, {
- "label": _("Item Name"),
- "fieldname": "production_item_name",
- "fieldtype": "Data",
- "width": 130
- }, {
- "label": _("Warehouse"),
- "options": "Warehouse",
- "fieldname": "for_warehouse",
- "fieldtype": "Link",
- "width": 100
- }, {
- "label": _("Order Qty"),
- "fieldname": "qty_to_manufacture",
- "fieldtype": "Float",
- "width": 80
- }, {
- "label": _("Available"),
- "fieldname": "available_qty",
- "fieldtype": "Float",
- "width": 80
- }]
+ self.columns = [
+ {"label": _("ID"), "options": based_on, "fieldname": "name", "fieldtype": "Link", "width": 100},
+ {
+ "label": _("Item Code"),
+ "fieldname": "production_item",
+ "fieldtype": "Link",
+ "options": "Item",
+ "width": 120,
+ },
+ {
+ "label": _("Item Name"),
+ "fieldname": "production_item_name",
+ "fieldtype": "Data",
+ "width": 130,
+ },
+ {
+ "label": _("Warehouse"),
+ "options": "Warehouse",
+ "fieldname": "for_warehouse",
+ "fieldtype": "Link",
+ "width": 100,
+ },
+ {"label": _("Order Qty"), "fieldname": "qty_to_manufacture", "fieldtype": "Float", "width": 80},
+ {"label": _("Available"), "fieldname": "available_qty", "fieldtype": "Float", "width": 80},
+ ]
fieldname, fieldtype = "delivery_date", "Date"
if self.filters.based_on == "Sales Order" and self.filters.order_by == "Total Amount":
@@ -330,48 +368,50 @@
elif self.filters.based_on == "Work Order":
fieldname = "planned_start_date"
- self.columns.append({
- "label": _(self.filters.order_by),
- "fieldname": fieldname,
- "fieldtype": fieldtype,
- "width": 100
- })
+ self.columns.append(
+ {
+ "label": _(self.filters.order_by),
+ "fieldname": fieldname,
+ "fieldtype": fieldtype,
+ "width": 100,
+ }
+ )
- self.columns.extend([{
- "label": _("Raw Material Code"),
- "fieldname": "item_code",
- "fieldtype": "Link",
- "options": "Item",
- "width": 120
- }, {
- "label": _("Raw Material Name"),
- "fieldname": "raw_material_name",
- "fieldtype": "Data",
- "width": 130
- }, {
- "label": _("Warehouse"),
- "options": "Warehouse",
- "fieldname": "warehouse",
- "fieldtype": "Link",
- "width": 110
- }, {
- "label": _("Required Qty"),
- "fieldname": "required_qty",
- "fieldtype": "Float",
- "width": 100
- }, {
- "label": _("Allotted Qty"),
- "fieldname": "allotted_qty",
- "fieldtype": "Float",
- "width": 100
- }, {
- "label": _("Expected Arrival Date"),
- "fieldname": "arrival_date",
- "fieldtype": "Date",
- "width": 160
- }, {
- "label": _("Arrival Quantity"),
- "fieldname": "arrival_qty",
- "fieldtype": "Float",
- "width": 140
- }])
+ self.columns.extend(
+ [
+ {
+ "label": _("Raw Material Code"),
+ "fieldname": "item_code",
+ "fieldtype": "Link",
+ "options": "Item",
+ "width": 120,
+ },
+ {
+ "label": _("Raw Material Name"),
+ "fieldname": "raw_material_name",
+ "fieldtype": "Data",
+ "width": 130,
+ },
+ {
+ "label": _("Warehouse"),
+ "options": "Warehouse",
+ "fieldname": "warehouse",
+ "fieldtype": "Link",
+ "width": 110,
+ },
+ {"label": _("Required Qty"), "fieldname": "required_qty", "fieldtype": "Float", "width": 100},
+ {"label": _("Allotted Qty"), "fieldname": "allotted_qty", "fieldtype": "Float", "width": 100},
+ {
+ "label": _("Expected Arrival Date"),
+ "fieldname": "arrival_date",
+ "fieldtype": "Date",
+ "width": 160,
+ },
+ {
+ "label": _("Arrival Quantity"),
+ "fieldname": "arrival_qty",
+ "fieldtype": "Float",
+ "width": 140,
+ },
+ ]
+ )
diff --git a/erpnext/manufacturing/report/quality_inspection_summary/quality_inspection_summary.py b/erpnext/manufacturing/report/quality_inspection_summary/quality_inspection_summary.py
index a0c4a43..0a79130 100644
--- a/erpnext/manufacturing/report/quality_inspection_summary/quality_inspection_summary.py
+++ b/erpnext/manufacturing/report/quality_inspection_summary/quality_inspection_summary.py
@@ -11,13 +11,24 @@
data = get_data(filters)
columns = get_columns(filters)
chart_data = get_chart_data(data, filters)
- return columns, data , None, chart_data
+ return columns, data, None, chart_data
+
def get_data(filters):
query_filters = {"docstatus": ("<", 2)}
- fields = ["name", "status", "report_date", "item_code", "item_name", "sample_size",
- "inspection_type", "reference_type", "reference_name", "inspected_by"]
+ fields = [
+ "name",
+ "status",
+ "report_date",
+ "item_code",
+ "item_name",
+ "sample_size",
+ "inspection_type",
+ "reference_type",
+ "reference_name",
+ "inspected_by",
+ ]
for field in ["status", "item_code", "status", "inspected_by"]:
if filters.get(field):
@@ -26,36 +37,33 @@
query_filters["report_date"] = (">=", filters.get("from_date"))
query_filters["report_date"] = ("<=", filters.get("to_date"))
- return frappe.get_all("Quality Inspection",
- fields= fields, filters=query_filters, order_by="report_date asc")
+ return frappe.get_all(
+ "Quality Inspection", fields=fields, filters=query_filters, order_by="report_date asc"
+ )
+
def get_chart_data(periodic_data, columns):
labels = ["Rejected", "Accepted"]
- status_wise_data = {
- "Accepted": 0,
- "Rejected": 0
- }
+ status_wise_data = {"Accepted": 0, "Rejected": 0}
datasets = []
for d in periodic_data:
status_wise_data[d.status] += 1
- datasets.append({'name':'Qty Wise Chart',
- 'values': [status_wise_data.get("Rejected"), status_wise_data.get("Accepted")]})
+ datasets.append(
+ {
+ "name": "Qty Wise Chart",
+ "values": [status_wise_data.get("Rejected"), status_wise_data.get("Accepted")],
+ }
+ )
- chart = {
- "data": {
- 'labels': labels,
- 'datasets': datasets
- },
- "type": "donut",
- "height": 300
- }
+ chart = {"data": {"labels": labels, "datasets": datasets}, "type": "donut", "height": 300}
return chart
+
def get_columns(filters):
columns = [
{
@@ -63,71 +71,49 @@
"fieldname": "name",
"fieldtype": "Link",
"options": "Work Order",
- "width": 100
+ "width": 100,
},
- {
- "label": _("Report Date"),
- "fieldname": "report_date",
- "fieldtype": "Date",
- "width": 150
- }
+ {"label": _("Report Date"), "fieldname": "report_date", "fieldtype": "Date", "width": 150},
]
if not filters.get("status"):
columns.append(
- {
- "label": _("Status"),
- "fieldname": "status",
- "width": 100
- },
+ {"label": _("Status"), "fieldname": "status", "width": 100},
)
- columns.extend([
- {
- "label": _("Item Code"),
- "fieldname": "item_code",
- "fieldtype": "Link",
- "options": "Item",
- "width": 130
- },
- {
- "label": _("Item Name"),
- "fieldname": "item_name",
- "fieldtype": "Data",
- "width": 130
- },
- {
- "label": _("Sample Size"),
- "fieldname": "sample_size",
- "fieldtype": "Float",
- "width": 110
- },
- {
- "label": _("Inspection Type"),
- "fieldname": "inspection_type",
- "fieldtype": "Data",
- "width": 110
- },
- {
- "label": _("Document Type"),
- "fieldname": "reference_type",
- "fieldtype": "Data",
- "width": 90
- },
- {
- "label": _("Document Name"),
- "fieldname": "reference_name",
- "fieldtype": "Dynamic Link",
- "options": "reference_type",
- "width": 150
- },
- {
- "label": _("Inspected By"),
- "fieldname": "inspected_by",
- "fieldtype": "Link",
- "options": "User",
- "width": 150
- }
- ])
+ columns.extend(
+ [
+ {
+ "label": _("Item Code"),
+ "fieldname": "item_code",
+ "fieldtype": "Link",
+ "options": "Item",
+ "width": 130,
+ },
+ {"label": _("Item Name"), "fieldname": "item_name", "fieldtype": "Data", "width": 130},
+ {"label": _("Sample Size"), "fieldname": "sample_size", "fieldtype": "Float", "width": 110},
+ {
+ "label": _("Inspection Type"),
+ "fieldname": "inspection_type",
+ "fieldtype": "Data",
+ "width": 110,
+ },
+ {"label": _("Document Type"), "fieldname": "reference_type", "fieldtype": "Data", "width": 90},
+ {
+ "label": _("Document Name"),
+ "fieldname": "reference_name",
+ "fieldtype": "Dynamic Link",
+ "options": "reference_type",
+ "width": 150,
+ },
+ {
+ "label": _("Inspected By"),
+ "fieldname": "inspected_by",
+ "fieldtype": "Link",
+ "options": "User",
+ "width": 150,
+ },
+ ]
+ )
return columns
diff --git a/erpnext/manufacturing/report/work_order_consumed_materials/work_order_consumed_materials.py b/erpnext/manufacturing/report/work_order_consumed_materials/work_order_consumed_materials.py
index 0528348..8158bc9 100644
--- a/erpnext/manufacturing/report/work_order_consumed_materials/work_order_consumed_materials.py
+++ b/erpnext/manufacturing/report/work_order_consumed_materials/work_order_consumed_materials.py
@@ -12,12 +12,13 @@
return columns, data
+
def get_data(report_filters):
fields = get_fields()
filters = get_filter_condition(report_filters)
wo_items = {}
- for d in frappe.get_all("Work Order", filters = filters, fields=fields):
+ for d in frappe.get_all("Work Order", filters=filters, fields=fields):
d.extra_consumed_qty = 0.0
if d.consumed_qty and d.consumed_qty > d.required_qty:
d.extra_consumed_qty = d.consumed_qty - d.required_qty
@@ -29,7 +30,7 @@
for key, wo_data in wo_items.items():
for index, row in enumerate(wo_data):
if index != 0:
- #If one work order has multiple raw materials then show parent data in the first row only
+ # If one work order has multiple raw materials then show parent data in the first row only
for field in ["name", "status", "production_item", "qty", "produced_qty"]:
row[field] = ""
@@ -37,17 +38,28 @@
return data
+
def get_fields():
- return ["`tabWork Order Item`.`parent`", "`tabWork Order Item`.`item_code` as raw_material_item_code",
- "`tabWork Order Item`.`item_name` as raw_material_name", "`tabWork Order Item`.`required_qty`",
- "`tabWork Order Item`.`transferred_qty`", "`tabWork Order Item`.`consumed_qty`", "`tabWork Order`.`status`",
- "`tabWork Order`.`name`", "`tabWork Order`.`production_item`", "`tabWork Order`.`qty`",
- "`tabWork Order`.`produced_qty`"]
+ return [
+ "`tabWork Order Item`.`parent`",
+ "`tabWork Order Item`.`item_code` as raw_material_item_code",
+ "`tabWork Order Item`.`item_name` as raw_material_name",
+ "`tabWork Order Item`.`required_qty`",
+ "`tabWork Order Item`.`transferred_qty`",
+ "`tabWork Order Item`.`consumed_qty`",
+ "`tabWork Order`.`status`",
+ "`tabWork Order`.`name`",
+ "`tabWork Order`.`production_item`",
+ "`tabWork Order`.`qty`",
+ "`tabWork Order`.`produced_qty`",
+ ]
+
def get_filter_condition(report_filters):
filters = {
- "docstatus": 1, "status": ("in", ["In Process", "Completed", "Stopped"]),
- "creation": ("between", [report_filters.from_date, report_filters.to_date])
+ "docstatus": 1,
+ "status": ("in", ["In Process", "Completed", "Stopped"]),
+ "creation": ("between", [report_filters.from_date, report_filters.to_date]),
}
for field in ["name", "production_item", "company", "status"]:
@@ -58,6 +70,7 @@
return filters
+
def get_columns():
return [
{
@@ -65,67 +78,38 @@
"fieldname": "name",
"fieldtype": "Link",
"options": "Work Order",
- "width": 80
+ "width": 80,
},
- {
- "label": _("Status"),
- "fieldname": "status",
- "fieldtype": "Data",
- "width": 80
- },
+ {"label": _("Status"), "fieldname": "status", "fieldtype": "Data", "width": 80},
{
"label": _("Production Item"),
"fieldname": "production_item",
"fieldtype": "Link",
"options": "Item",
- "width": 130
+ "width": 130,
},
- {
- "label": _("Qty to Produce"),
- "fieldname": "qty",
- "fieldtype": "Float",
- "width": 120
- },
- {
- "label": _("Produced Qty"),
- "fieldname": "produced_qty",
- "fieldtype": "Float",
- "width": 110
- },
+ {"label": _("Qty to Produce"), "fieldname": "qty", "fieldtype": "Float", "width": 120},
+ {"label": _("Produced Qty"), "fieldname": "produced_qty", "fieldtype": "Float", "width": 110},
{
"label": _("Raw Material Item"),
"fieldname": "raw_material_item_code",
"fieldtype": "Link",
"options": "Item",
- "width": 150
+ "width": 150,
},
- {
- "label": _("Item Name"),
- "fieldname": "raw_material_name",
- "width": 130
- },
- {
- "label": _("Required Qty"),
- "fieldname": "required_qty",
- "fieldtype": "Float",
- "width": 100
- },
+ {"label": _("Item Name"), "fieldname": "raw_material_name", "width": 130},
+ {"label": _("Required Qty"), "fieldname": "required_qty", "fieldtype": "Float", "width": 100},
{
"label": _("Transferred Qty"),
"fieldname": "transferred_qty",
"fieldtype": "Float",
- "width": 100
+ "width": 100,
},
- {
- "label": _("Consumed Qty"),
- "fieldname": "consumed_qty",
- "fieldtype": "Float",
- "width": 100
- },
+ {"label": _("Consumed Qty"), "fieldname": "consumed_qty", "fieldtype": "Float", "width": 100},
{
"label": _("Extra Consumed Qty"),
"fieldname": "extra_consumed_qty",
"fieldtype": "Float",
- "width": 100
- }
+ "width": 100,
+ },
]
diff --git a/erpnext/manufacturing/report/work_order_stock_report/work_order_stock_report.py b/erpnext/manufacturing/report/work_order_stock_report/work_order_stock_report.py
index db0b239..c6b7e58 100644
--- a/erpnext/manufacturing/report/work_order_stock_report/work_order_stock_report.py
+++ b/erpnext/manufacturing/report/work_order_stock_report/work_order_stock_report.py
@@ -12,17 +12,20 @@
columns = get_columns()
return columns, data
+
def get_item_list(wo_list, filters):
out = []
- #Add a row for each item/qty
+ # Add a row for each item/qty
for wo_details in wo_list:
desc = frappe.db.get_value("BOM", wo_details.bom_no, "description")
- for wo_item_details in frappe.db.get_values("Work Order Item",
- {"parent": wo_details.name}, ["item_code", "source_warehouse"], as_dict=1):
+ for wo_item_details in frappe.db.get_values(
+ "Work Order Item", {"parent": wo_details.name}, ["item_code", "source_warehouse"], as_dict=1
+ ):
- item_list = frappe.db.sql("""SELECT
+ item_list = frappe.db.sql(
+ """SELECT
bom_item.item_code as item_code,
ifnull(ledger.actual_qty*bom.quantity/bom_item.stock_qty,0) as build_qty
FROM
@@ -36,8 +39,14 @@
and bom.name = %(bom)s
GROUP BY
bom_item.item_code""",
- {"bom": wo_details.bom_no, "warehouse": wo_item_details.source_warehouse,
- "filterhouse": filters.warehouse, "item_code": wo_item_details.item_code}, as_dict=1)
+ {
+ "bom": wo_details.bom_no,
+ "warehouse": wo_item_details.source_warehouse,
+ "filterhouse": filters.warehouse,
+ "item_code": wo_item_details.item_code,
+ },
+ as_dict=1,
+ )
stock_qty = 0
count = 0
@@ -54,97 +63,99 @@
else:
build = "N"
- row = frappe._dict({
- "work_order": wo_details.name,
- "status": wo_details.status,
- "req_items": cint(count),
- "instock": stock_qty,
- "description": desc,
- "source_warehouse": wo_item_details.source_warehouse,
- "item_code": wo_item_details.item_code,
- "bom_no": wo_details.bom_no,
- "qty": wo_details.qty,
- "buildable_qty": buildable_qty,
- "ready_to_build": build
- })
+ row = frappe._dict(
+ {
+ "work_order": wo_details.name,
+ "status": wo_details.status,
+ "req_items": cint(count),
+ "instock": stock_qty,
+ "description": desc,
+ "source_warehouse": wo_item_details.source_warehouse,
+ "item_code": wo_item_details.item_code,
+ "bom_no": wo_details.bom_no,
+ "qty": wo_details.qty,
+ "buildable_qty": buildable_qty,
+ "ready_to_build": build,
+ }
+ )
out.append(row)
return out
+
def get_work_orders():
- out = frappe.get_all("Work Order", filters={"docstatus": 1, "status": ( "!=","Completed")},
- fields=["name","status", "bom_no", "qty", "produced_qty"], order_by='name')
+ out = frappe.get_all(
+ "Work Order",
+ filters={"docstatus": 1, "status": ("!=", "Completed")},
+ fields=["name", "status", "bom_no", "qty", "produced_qty"],
+ order_by="name",
+ )
return out
+
def get_columns():
- columns = [{
- "fieldname": "work_order",
- "label": "Work Order",
- "fieldtype": "Link",
- "options": "Work Order",
- "width": 110
- }, {
- "fieldname": "bom_no",
- "label": "BOM",
- "fieldtype": "Link",
- "options": "BOM",
- "width": 120
- }, {
- "fieldname": "description",
- "label": "Description",
- "fieldtype": "Data",
- "options": "",
- "width": 230
- }, {
- "fieldname": "item_code",
- "label": "Item Code",
- "fieldtype": "Link",
- "options": "Item",
- "width": 110
- },{
- "fieldname": "source_warehouse",
- "label": "Source Warehouse",
- "fieldtype": "Link",
- "options": "Warehouse",
- "width": 110
- },{
- "fieldname": "qty",
- "label": "Qty to Build",
- "fieldtype": "Data",
- "options": "",
- "width": 110
- }, {
- "fieldname": "status",
- "label": "Status",
- "fieldtype": "Data",
- "options": "",
- "width": 100
- }, {
- "fieldname": "req_items",
- "label": "# Req'd Items",
- "fieldtype": "Data",
- "options": "",
- "width": 105
- }, {
- "fieldname": "instock",
- "label": "# In Stock",
- "fieldtype": "Data",
- "options": "",
- "width": 105
- }, {
- "fieldname": "buildable_qty",
- "label": "Buildable Qty",
- "fieldtype": "Data",
- "options": "",
- "width": 100
- }, {
- "fieldname": "ready_to_build",
- "label": "Build All?",
- "fieldtype": "Data",
- "options": "",
- "width": 90
- }]
+ columns = [
+ {
+ "fieldname": "work_order",
+ "label": "Work Order",
+ "fieldtype": "Link",
+ "options": "Work Order",
+ "width": 110,
+ },
+ {"fieldname": "bom_no", "label": "BOM", "fieldtype": "Link", "options": "BOM", "width": 120},
+ {
+ "fieldname": "description",
+ "label": "Description",
+ "fieldtype": "Data",
+ "options": "",
+ "width": 230,
+ },
+ {
+ "fieldname": "item_code",
+ "label": "Item Code",
+ "fieldtype": "Link",
+ "options": "Item",
+ "width": 110,
+ },
+ {
+ "fieldname": "source_warehouse",
+ "label": "Source Warehouse",
+ "fieldtype": "Link",
+ "options": "Warehouse",
+ "width": 110,
+ },
+ {"fieldname": "qty", "label": "Qty to Build", "fieldtype": "Data", "options": "", "width": 110},
+ {"fieldname": "status", "label": "Status", "fieldtype": "Data", "options": "", "width": 100},
+ {
+ "fieldname": "req_items",
+ "label": "# Req'd Items",
+ "fieldtype": "Data",
+ "options": "",
+ "width": 105,
+ },
+ {
+ "fieldname": "instock",
+ "label": "# In Stock",
+ "fieldtype": "Data",
+ "options": "",
+ "width": 105,
+ },
+ {
+ "fieldname": "buildable_qty",
+ "label": "Buildable Qty",
+ "fieldtype": "Data",
+ "options": "",
+ "width": 100,
+ },
+ {
+ "fieldname": "ready_to_build",
+ "label": "Build All?",
+ "fieldtype": "Data",
+ "options": "",
+ "width": 90,
+ },
+ ]
return columns
diff --git a/erpnext/manufacturing/report/work_order_summary/work_order_summary.py b/erpnext/manufacturing/report/work_order_summary/work_order_summary.py
index d7469dd..2368bfd 100644
--- a/erpnext/manufacturing/report/work_order_summary/work_order_summary.py
+++ b/erpnext/manufacturing/report/work_order_summary/work_order_summary.py
@@ -21,11 +21,23 @@
chart_data = get_chart_data(data, filters)
return columns, data, None, chart_data
+
def get_data(filters):
query_filters = {"docstatus": ("<", 2)}
- fields = ["name", "status", "sales_order", "production_item", "qty", "produced_qty",
- "planned_start_date", "planned_end_date", "actual_start_date", "actual_end_date", "lead_time"]
+ fields = [
+ "name",
+ "status",
+ "sales_order",
+ "production_item",
+ "qty",
+ "produced_qty",
+ "planned_start_date",
+ "planned_end_date",
+ "actual_start_date",
+ "actual_end_date",
+ "lead_time",
+ ]
for field in ["sales_order", "production_item", "status", "company"]:
if filters.get(field):
@@ -34,15 +46,16 @@
query_filters["planned_start_date"] = (">=", filters.get("from_date"))
query_filters["planned_end_date"] = ("<=", filters.get("to_date"))
- data = frappe.get_all("Work Order",
- fields= fields, filters=query_filters, order_by="planned_start_date asc")
+ data = frappe.get_all(
+ "Work Order", fields=fields, filters=query_filters, order_by="planned_start_date asc"
+ )
res = []
for d in data:
start_date = d.actual_start_date or d.planned_start_date
d.age = 0
- if d.status != 'Completed':
+ if d.status != "Completed":
d.age = date_diff(today(), start_date)
if filters.get("age") <= d.age:
@@ -50,6 +63,7 @@
return res
+
def get_chart_data(data, filters):
if filters.get("charts_based_on") == "Status":
return get_chart_based_on_status(data)
@@ -58,6 +72,7 @@
else:
return get_chart_based_on_qty(data, filters)
+
def get_chart_based_on_status(data):
labels = frappe.get_meta("Work Order").get_options("status").split("\n")
if "" in labels:
@@ -71,25 +86,18 @@
values = [status_wise_data[label] for label in labels]
chart = {
- "data": {
- 'labels': labels,
- 'datasets': [{'name':'Qty Wise Chart', 'values': values}]
- },
+ "data": {"labels": labels, "datasets": [{"name": "Qty Wise Chart", "values": values}]},
"type": "donut",
- "height": 300
+ "height": 300,
}
return chart
+
def get_chart_based_on_age(data):
labels = ["0-30 Days", "30-60 Days", "60-90 Days", "90 Above"]
- age_wise_data = {
- "0-30 Days": 0,
- "30-60 Days": 0,
- "60-90 Days": 0,
- "90 Above": 0
- }
+ age_wise_data = {"0-30 Days": 0, "30-60 Days": 0, "60-90 Days": 0, "90 Above": 0}
for d in data:
if d.age > 0 and d.age <= 30:
@@ -101,20 +109,22 @@
else:
age_wise_data["90 Above"] += 1
- values = [age_wise_data["0-30 Days"], age_wise_data["30-60 Days"],
- age_wise_data["60-90 Days"], age_wise_data["90 Above"]]
+ values = [
+ age_wise_data["0-30 Days"],
+ age_wise_data["30-60 Days"],
+ age_wise_data["60-90 Days"],
+ age_wise_data["90 Above"],
+ ]
chart = {
- "data": {
- 'labels': labels,
- 'datasets': [{'name':'Qty Wise Chart', 'values': values}]
- },
+ "data": {"labels": labels, "datasets": [{"name": "Qty Wise Chart", "values": values}]},
"type": "donut",
- "height": 300
+ "height": 300,
}
return chart
+
def get_chart_based_on_qty(data, filters):
labels, periodic_data = prepare_chart_data(data, filters)
@@ -129,25 +139,18 @@
datasets.append({"name": "Completed", "values": completed})
chart = {
- "data": {
- 'labels': labels,
- 'datasets': datasets
- },
+ "data": {"labels": labels, "datasets": datasets},
"type": "bar",
- "barOptions": {
- "stacked": 1
- }
+ "barOptions": {"stacked": 1},
}
return chart
+
def prepare_chart_data(data, filters):
labels = []
- periodic_data = {
- "Pending": {},
- "Completed": {}
- }
+ periodic_data = {"Pending": {}, "Completed": {}}
filters.range = "Monthly"
@@ -165,11 +168,12 @@
for d in data:
if getdate(d.planned_start_date) >= from_date and getdate(d.planned_start_date) <= end_date:
- periodic_data["Pending"][period] += (flt(d.qty) - flt(d.produced_qty))
+ periodic_data["Pending"][period] += flt(d.qty) - flt(d.produced_qty)
periodic_data["Completed"][period] += flt(d.produced_qty)
return labels, periodic_data
+
def get_columns(filters):
columns = [
{
@@ -177,90 +181,77 @@
"fieldname": "name",
"fieldtype": "Link",
"options": "Work Order",
- "width": 100
+ "width": 100,
},
]
if not filters.get("status"):
columns.append(
- {
- "label": _("Status"),
- "fieldname": "status",
- "width": 100
- },
+ {"label": _("Status"), "fieldname": "status", "width": 100},
)
- columns.extend([
- {
- "label": _("Production Item"),
- "fieldname": "production_item",
- "fieldtype": "Link",
- "options": "Item",
- "width": 130
- },
- {
- "label": _("Produce Qty"),
- "fieldname": "qty",
- "fieldtype": "Float",
- "width": 110
- },
- {
- "label": _("Produced Qty"),
- "fieldname": "produced_qty",
- "fieldtype": "Float",
- "width": 110
- },
- {
- "label": _("Sales Order"),
- "fieldname": "sales_order",
- "fieldtype": "Link",
- "options": "Sales Order",
- "width": 90
- },
- {
- "label": _("Planned Start Date"),
- "fieldname": "planned_start_date",
- "fieldtype": "Date",
- "width": 150
- },
- {
- "label": _("Planned End Date"),
- "fieldname": "planned_end_date",
- "fieldtype": "Date",
- "width": 150
- }
- ])
-
- if filters.get("status") != 'Not Started':
- columns.extend([
+ columns.extend(
+ [
{
- "label": _("Actual Start Date"),
- "fieldname": "actual_start_date",
+ "label": _("Production Item"),
+ "fieldname": "production_item",
+ "fieldtype": "Link",
+ "options": "Item",
+ "width": 130,
+ },
+ {"label": _("Produce Qty"), "fieldname": "qty", "fieldtype": "Float", "width": 110},
+ {"label": _("Produced Qty"), "fieldname": "produced_qty", "fieldtype": "Float", "width": 110},
+ {
+ "label": _("Sales Order"),
+ "fieldname": "sales_order",
+ "fieldtype": "Link",
+ "options": "Sales Order",
+ "width": 90,
+ },
+ {
+ "label": _("Planned Start Date"),
+ "fieldname": "planned_start_date",
"fieldtype": "Date",
- "width": 100
+ "width": 150,
},
{
- "label": _("Actual End Date"),
- "fieldname": "actual_end_date",
+ "label": _("Planned End Date"),
+ "fieldname": "planned_end_date",
"fieldtype": "Date",
- "width": 100
+ "width": 150,
},
- {
- "label": _("Age"),
- "fieldname": "age",
- "fieldtype": "Float",
- "width": 110
- },
- ])
+ ]
+ )
- if filters.get("status") == 'Completed':
- columns.extend([
- {
- "label": _("Lead Time (in mins)"),
- "fieldname": "lead_time",
- "fieldtype": "Float",
- "width": 110
- },
- ])
+ if filters.get("status") != "Not Started":
+ columns.extend(
+ [
+ {
+ "label": _("Actual Start Date"),
+ "fieldname": "actual_start_date",
+ "fieldtype": "Date",
+ "width": 100,
+ },
+ {
+ "label": _("Actual End Date"),
+ "fieldname": "actual_end_date",
+ "fieldtype": "Date",
+ "width": 100,
+ },
+ {"label": _("Age"), "fieldname": "age", "fieldtype": "Float", "width": 110},
+ ]
+ )
+
+ if filters.get("status") == "Completed":
+ columns.extend(
+ [
+ {
+ "label": _("Lead Time (in mins)"),
+ "fieldname": "lead_time",
+ "fieldtype": "Float",
+ "width": 110,
+ },
+ ]
+ )
return columns