refactor!: Make required changes to create SCO from PO
diff --git a/erpnext/buying/doctype/purchase_order/purchase_order.js b/erpnext/buying/doctype/purchase_order/purchase_order.js
index 2005dac..1f6de1a 100644
--- a/erpnext/buying/doctype/purchase_order/purchase_order.js
+++ b/erpnext/buying/doctype/purchase_order/purchase_order.js
@@ -28,6 +28,11 @@
}
});
+ frm.set_query("fg_item", "items", function() {
+ return {
+ filters: {'is_sub_contracted_item': 1}
+ }
+ });
},
company: function(frm) {
@@ -109,6 +114,7 @@
'Purchase Invoice': 'Purchase Invoice',
'Stock Entry': 'Material to Supplier',
'Payment Entry': 'Payment',
+ 'Subcontracting Order': 'Subcontracting Order'
}
super.setup();
@@ -183,6 +189,9 @@
cur_frm.add_custom_button(__('Material to Supplier'),
function() { me.make_stock_entry(); }, __("Transfer"));
}
+ if (doc.is_subcontracted) {
+ cur_frm.add_custom_button(__('Subcontracting Order'), this.make_subcontracting_order, __('Create'));
+ }
}
if(flt(doc.per_billed) < 100)
cur_frm.add_custom_button(__('Purchase Invoice'),
@@ -407,6 +416,14 @@
})
}
+ make_subcontracting_order() {
+ frappe.model.open_mapped_doc({
+ method: "erpnext.buying.doctype.purchase_order.purchase_order.make_subcontracting_order",
+ frm: cur_frm,
+ freeze_message: __("Creating Subcontracting Order ...")
+ })
+ }
+
add_from_mappers() {
var me = this;
this.frm.add_custom_button(__('Material Request'),
diff --git a/erpnext/buying/doctype/purchase_order/purchase_order.json b/erpnext/buying/doctype/purchase_order/purchase_order.json
index 896208f..307b576 100644
--- a/erpnext/buying/doctype/purchase_order/purchase_order.json
+++ b/erpnext/buying/doctype/purchase_order/purchase_order.json
@@ -16,6 +16,8 @@
"supplier_name",
"apply_tds",
"tax_withholding_category",
+ "is_subcontracted",
+ "supplier_warehouse",
"column_break1",
"company",
"transaction_date",
@@ -51,10 +53,7 @@
"price_list_currency",
"plc_conversion_rate",
"ignore_pricing_rule",
- "sec_warehouse",
- "is_subcontracted",
- "col_break_warehouse",
- "supplier_warehouse",
+ "section_break_45",
"before_items_section",
"scan_barcode",
"items_col_break",
@@ -154,7 +153,8 @@
"hidden": 1,
"label": "Title",
"no_copy": 1,
- "print_hide": 1
+ "print_hide": 1,
+ "reqd": 1
},
{
"fieldname": "naming_series",
@@ -440,11 +440,6 @@
"print_hide": 1
},
{
- "fieldname": "sec_warehouse",
- "fieldtype": "Section Break",
- "label": "Subcontracting"
- },
- {
"description": "Sets 'Warehouse' in each row of the Items table.",
"fieldname": "set_warehouse",
"fieldtype": "Link",
@@ -453,14 +448,9 @@
"print_hide": 1
},
{
- "fieldname": "col_break_warehouse",
- "fieldtype": "Column Break"
- },
- {
"default": "No",
"fieldname": "is_subcontracted",
"fieldtype": "Select",
- "in_standard_filter": 1,
"label": "Supply Raw Materials",
"options": "No\nYes",
"print_hide": 1
@@ -1138,16 +1128,21 @@
"fieldtype": "Link",
"label": "Tax Withholding Category",
"options": "Tax Withholding Category"
+ },
+ {
+ "fieldname": "section_break_45",
+ "fieldtype": "Section Break"
}
],
"icon": "fa fa-file-text",
"idx": 105,
"is_submittable": 1,
"links": [],
- "modified": "2021-09-28 13:10:47.955401",
+ "modified": "2022-04-26 18:46:58.863174",
"modified_by": "Administrator",
"module": "Buying",
"name": "Purchase Order",
+ "naming_rule": "By \"Naming Series\" field",
"owner": "Administrator",
"permissions": [
{
@@ -1194,6 +1189,7 @@
"show_name_in_global_search": 1,
"sort_field": "modified",
"sort_order": "DESC",
+ "states": [],
"timeline_field": "supplier",
"title_field": "supplier_name",
"track_changes": 1
diff --git a/erpnext/buying/doctype/purchase_order/purchase_order.py b/erpnext/buying/doctype/purchase_order/purchase_order.py
index 582bd8d..e8b8b87 100644
--- a/erpnext/buying/doctype/purchase_order/purchase_order.py
+++ b/erpnext/buying/doctype/purchase_order/purchase_order.py
@@ -69,7 +69,7 @@
self.validate_with_previous_doc()
self.validate_for_subcontracting()
self.validate_minimum_order_qty()
- self.validate_bom_for_subcontracting_items()
+ self.validate_fg_item_for_subcontracting()
self.create_raw_materials_supplied("supplied_items")
self.set_received_qty_for_drop_ship_items()
validate_inter_company_party(
@@ -193,12 +193,25 @@
).format(item_code, qty, itemwise_min_order_qty.get(item_code))
)
- def validate_bom_for_subcontracting_items(self):
- if self.is_subcontracted == "Yes":
+ def validate_fg_item_for_subcontracting(self):
+ if self.is_subcontracted:
for item in self.items:
- if not item.bom:
+ if not item.fg_item:
frappe.throw(
- _("BOM is not specified for subcontracting item {0} at row {1}").format(
+ _("Finished Good Item is not specified for service item {0} at row {1}").format(
+ item.item_code, item.idx
+ )
+ )
+ else:
+ if not frappe.get_value("Item", item.fg_item, "is_sub_contracted_item"):
+ frappe.throw(
+ _(
+ "Finished Good Item {0} must be a sub-contracted item for service item {1} at row {2}"
+ ).format(item.fg_item, item.item_code, item.idx)
+ )
+ if not item.fg_item_qty:
+ frappe.throw(
+ _("Finished Good Item Qty is not specified for service item {0} at row {1}").format(
item.item_code, item.idx
)
)
@@ -746,3 +759,43 @@
"serial_no": "\n".join(row.serial_no) if row.serial_no else "",
}
)
+
+
+@frappe.whitelist()
+def make_subcontracting_order(source_name, target_doc=None):
+ return get_mapped_subcontracting_order(source_name, target_doc)
+
+
+def get_mapped_subcontracting_order(source_name, target_doc=None):
+
+ if target_doc and isinstance(target_doc, str):
+ target_doc = json.loads(target_doc)
+ for key in ["service_items", "items", "supplied_items"]:
+ if key in target_doc:
+ del target_doc[key]
+ target_doc = json.dumps(target_doc)
+
+ target_doc = get_mapped_doc(
+ "Purchase Order",
+ source_name,
+ {
+ "Purchase Order": {
+ "doctype": "Subcontracting Order",
+ "field_map": {},
+ "field_no_map": ["total_qty", "total", "net_total"],
+ "validation": {
+ "docstatus": ["=", 1],
+ },
+ },
+ "Purchase Order Item": {
+ "doctype": "Subcontracting Order Service Item",
+ "field_map": {},
+ "field_no_map": [],
+ },
+ },
+ target_doc,
+ )
+
+ target_doc.populate_items_table()
+
+ return target_doc
diff --git a/erpnext/buying/doctype/purchase_order/purchase_order_dashboard.py b/erpnext/buying/doctype/purchase_order/purchase_order_dashboard.py
index 81f2010..0c38c3e 100644
--- a/erpnext/buying/doctype/purchase_order/purchase_order_dashboard.py
+++ b/erpnext/buying/doctype/purchase_order/purchase_order_dashboard.py
@@ -22,6 +22,6 @@
"label": _("Reference"),
"items": ["Material Request", "Supplier Quotation", "Project", "Auto Repeat"],
},
- {"label": _("Sub-contracting"), "items": ["Stock Entry"]},
+ {"label": _("Sub-contracting"), "items": ["Subcontracting Order"]},
],
}
diff --git a/erpnext/buying/doctype/purchase_order_item/purchase_order_item.json b/erpnext/buying/doctype/purchase_order_item/purchase_order_item.json
index a18c527..b4cdb18 100644
--- a/erpnext/buying/doctype/purchase_order_item/purchase_order_item.json
+++ b/erpnext/buying/doctype/purchase_order_item/purchase_order_item.json
@@ -11,6 +11,8 @@
"supplier_part_no",
"item_name",
"product_bundle",
+ "fg_item",
+ "fg_item_qty",
"column_break_4",
"schedule_date",
"expected_delivery_date",
@@ -572,18 +574,18 @@
"read_only": 1
},
{
- "depends_on": "eval:parent.is_subcontracted == 'Yes'",
"fieldname": "bom",
"fieldtype": "Link",
"label": "BOM",
"options": "BOM",
- "print_hide": 1
+ "print_hide": 1,
+ "read_only": 1
},
{
"default": "0",
- "depends_on": "eval:parent.is_subcontracted == 'Yes'",
"fieldname": "include_exploded_items",
"fieldtype": "Check",
+ "hidden": 1,
"label": "Include Exploded Items",
"print_hide": 1
},
@@ -845,13 +847,29 @@
"label": "Sales Order Packed Item",
"no_copy": 1,
"print_hide": 1
+ },
+ {
+ "depends_on": "eval:parent.is_subcontracted == 'Yes'",
+ "fieldname": "fg_item",
+ "fieldtype": "Link",
+ "label": "Finished Good Item",
+ "mandatory_depends_on": "eval:parent.is_subcontracted == 'Yes'",
+ "options": "Item"
+ },
+ {
+ "default": "1",
+ "depends_on": "eval:parent.is_subcontracted == 'Yes'",
+ "fieldname": "fg_item_qty",
+ "fieldtype": "Float",
+ "label": "Finished Good Item Qty",
+ "mandatory_depends_on": "eval:parent.is_subcontracted == 'Yes'"
}
],
"idx": 1,
"index_web_pages_for_search": 1,
"istable": 1,
"links": [],
- "modified": "2022-02-02 13:10:18.398976",
+ "modified": "2022-04-07 14:53:16.684010",
"modified_by": "Administrator",
"module": "Buying",
"name": "Purchase Order Item",
diff --git a/erpnext/controllers/buying_controller.py b/erpnext/controllers/buying_controller.py
index 4789207..4823e8b 100644
--- a/erpnext/controllers/buying_controller.py
+++ b/erpnext/controllers/buying_controller.py
@@ -803,10 +803,7 @@
if self.doctype == "Material Request":
return
- if hasattr(self, "is_subcontracted") and self.is_subcontracted == "Yes":
- validate_item_type(self, "is_sub_contracted_item", "subcontracted")
- else:
- validate_item_type(self, "is_purchase_item", "purchase")
+ validate_item_type(self, "is_purchase_item", "purchase")
def get_asset_item_details(asset_items):
diff --git a/erpnext/public/js/controllers/buying.js b/erpnext/public/js/controllers/buying.js
index 54e5daa..a925470 100644
--- a/erpnext/public/js/controllers/buying.js
+++ b/erpnext/public/js/controllers/buying.js
@@ -84,7 +84,7 @@
if (me.frm.doc.is_subcontracted == "Yes") {
return{
query: "erpnext.controllers.queries.item_query",
- filters:{ 'supplier': me.frm.doc.supplier, 'is_sub_contracted_item': 1 }
+ filters:{ 'supplier': me.frm.doc.supplier, 'is_stock_item': 0 }
}
}
else {
diff --git a/erpnext/public/js/utils.js b/erpnext/public/js/utils.js
index 9339c5d..8260426 100755
--- a/erpnext/public/js/utils.js
+++ b/erpnext/public/js/utils.js
@@ -484,7 +484,7 @@
filters = {"is_sales_item": 1};
} else if (frm.doc.doctype == 'Purchase Order') {
if (frm.doc.is_subcontracted == "Yes") {
- filters = {"is_sub_contracted_item": 1};
+ filters = {"is_stock_item": 0};
} else {
filters = {"is_purchase_item": 1};
}
diff --git a/erpnext/stock/doctype/bin/bin.py b/erpnext/stock/doctype/bin/bin.py
index 6cb9f7e..a66e6f8 100644
--- a/erpnext/stock/doctype/bin/bin.py
+++ b/erpnext/stock/doctype/bin/bin.py
@@ -43,20 +43,19 @@
def update_reserved_qty_for_sub_contracting(self):
# reserved qty
- po = frappe.qb.DocType("Purchase Order")
- supplied_item = frappe.qb.DocType("Purchase Order Item Supplied")
+ sco = frappe.qb.DocType("Subcontracting Order")
+ supplied_item = frappe.qb.DocType("Subcontracting Order Supplied Item")
reserved_qty_for_sub_contract = (
- frappe.qb.from_(po)
+ frappe.qb.from_(sco)
.from_(supplied_item)
.select(Sum(Coalesce(supplied_item.required_qty, 0)))
.where(
(supplied_item.rm_item_code == self.item_code)
- & (po.name == supplied_item.parent)
- & (po.docstatus == 1)
- & (po.is_subcontracted == "Yes")
- & (po.status != "Closed")
- & (po.per_received < 100)
+ & (sco.name == supplied_item.parent)
+ & (sco.docstatus == 1)
+ & (sco.status != "Closed")
+ & (sco.per_received < 100)
& (supplied_item.reserve_warehouse == self.warehouse)
)
).run()[0][0] or 0.0
@@ -67,21 +66,20 @@
materials_transferred = (
frappe.qb.from_(se)
.from_(se_item)
- .from_(po)
+ .from_(sco)
.select(
Sum(Case().when(se.is_return == 1, se_item.transfer_qty * -1).else_(se_item.transfer_qty))
)
.where(
(se.docstatus == 1)
& (se.purpose == "Send to Subcontractor")
- & (Coalesce(se.purchase_order, "") != "")
+ & (Coalesce(se.subcontracting_order, "") != "")
& ((se_item.item_code == self.item_code) | (se_item.original_item == self.item_code))
& (se.name == se_item.parent)
- & (po.name == se.purchase_order)
- & (po.docstatus == 1)
- & (po.is_subcontracted == "Yes")
- & (po.status != "Closed")
- & (po.per_received < 100)
+ & (sco.name == se.subcontracting_order)
+ & (sco.docstatus == 1)
+ & (sco.status != "Closed")
+ & (sco.per_received < 100)
)
).run()[0][0] or 0.0
diff --git a/erpnext/stock/doctype/stock_entry/stock_entry.py b/erpnext/stock/doctype/stock_entry/stock_entry.py
index 1e62471..b851795 100644
--- a/erpnext/stock/doctype/stock_entry/stock_entry.py
+++ b/erpnext/stock/doctype/stock_entry/stock_entry.py
@@ -134,8 +134,8 @@
update_serial_nos_after_submit(self, "items")
self.update_work_order()
- self.validate_purchase_order()
- self.update_purchase_order_supplied_items()
+ self.validate_subcontracting_order()
+ self.update_subcontracting_order_supplied_items()
self.make_gl_entries()
@@ -154,7 +154,7 @@
self.set_material_request_transfer_status("Completed")
def on_cancel(self):
- self.update_purchase_order_supplied_items()
+ self.update_subcontracting_order_supplied_items()
if self.work_order and self.purpose == "Material Consumption for Manufacture":
self.validate_work_order_status()
@@ -810,8 +810,8 @@
serial_nos.append(sn)
- def validate_purchase_order(self):
- """Throw exception if more raw material is transferred against Purchase Order than in
+ def validate_subcontracting_order(self):
+ """Throw exception if more raw material is transferred against Subcontracting Order than in
the raw materials supplied table"""
backflush_raw_materials_based_on = frappe.db.get_single_value(
"Buying Settings", "backflush_raw_materials_of_subcontract_based_on"
@@ -819,24 +819,28 @@
qty_allowance = flt(frappe.db.get_single_value("Buying Settings", "over_transfer_allowance"))
- if not (self.purpose == "Send to Subcontractor" and self.purchase_order):
+ if not (self.purpose == "Send to Subcontractor" and self.subcontracting_order):
return
if backflush_raw_materials_based_on == "BOM":
- purchase_order = frappe.get_doc("Purchase Order", self.purchase_order)
+ subcontracting_order = frappe.get_doc("Subcontracting Order", self.subcontracting_order)
for se_item in self.items:
item_code = se_item.original_item or se_item.item_code
precision = cint(frappe.db.get_default("float_precision")) or 3
required_qty = sum(
- [flt(d.required_qty) for d in purchase_order.supplied_items if d.rm_item_code == item_code]
+ [
+ flt(d.required_qty)
+ for d in subcontracting_order.supplied_items
+ if d.rm_item_code == item_code
+ ]
)
total_allowed = required_qty + (required_qty * (qty_allowance / 100))
if not required_qty:
bom_no = frappe.db.get_value(
- "Purchase Order Item",
- {"parent": self.purchase_order, "item_code": se_item.subcontracted_item},
+ "Subcontracting Order Item",
+ {"parent": self.subcontracting_order, "item_code": se_item.subcontracted_item},
"bom",
)
@@ -848,7 +852,7 @@
required_qty = sum(
[
flt(d.required_qty)
- for d in purchase_order.supplied_items
+ for d in subcontracting_order.supplied_items
if d.rm_item_code == original_item_code
]
)
@@ -857,26 +861,39 @@
if not required_qty:
frappe.throw(
- _("Item {0} not found in 'Raw Materials Supplied' table in Purchase Order {1}").format(
- se_item.item_code, self.purchase_order
+ _("Item {0} not found in 'Raw Materials Supplied' table in Subcontracting Order {1}").format(
+ se_item.item_code, self.subcontracting_order
)
)
total_supplied = frappe.db.sql(
"""select sum(transfer_qty)
from `tabStock Entry Detail`, `tabStock Entry`
- where `tabStock Entry`.purchase_order = %s
+ where `tabStock Entry`.subcontracting_order = %s
and `tabStock Entry`.docstatus = 1
and `tabStock Entry Detail`.item_code = %s
and `tabStock Entry Detail`.parent = `tabStock Entry`.name""",
- (self.purchase_order, se_item.item_code),
+ (self.subcontracting_order, se_item.item_code),
)[0][0]
if flt(total_supplied, precision) > flt(total_allowed, precision):
frappe.throw(
- _("Row {0}# Item {1} cannot be transferred more than {2} against Purchase Order {3}").format(
- se_item.idx, se_item.item_code, total_allowed, self.purchase_order
+ _(
+ "Row {0}# Item {1} cannot be transferred more than {2} against Subcontracting Order {3}"
+ ).format(
+ se_item.idx, se_item.item_code, total_allowed, self.subcontracting_order
)
)
+ elif not se_item.sco_rm_detail:
+ filters = {
+ "parent": self.subcontracting_order,
+ "docstatus": 1,
+ "rm_item_code": se_item.item_code,
+ "main_item_code": se_item.subcontracted_item,
+ }
+
+ sco_rm_detail = frappe.db.get_value("Subcontracting Order Supplied Item", filters, "name")
+ if sco_rm_detail:
+ se_item.db_set("sco_rm_detail", sco_rm_detail)
elif backflush_raw_materials_based_on == "Material Transferred for Subcontract":
for row in self.items:
if not row.subcontracted_item:
@@ -885,17 +902,17 @@
row.idx, frappe.bold(row.item_code)
)
)
- elif not row.po_detail:
+ elif not row.sco_rm_detail:
filters = {
- "parent": self.purchase_order,
+ "parent": self.subcontracting_order,
"docstatus": 1,
"rm_item_code": row.item_code,
"main_item_code": row.subcontracted_item,
}
- po_detail = frappe.db.get_value("Purchase Order Item Supplied", filters, "name")
- if po_detail:
- row.db_set("po_detail", po_detail)
+ sco_rm_detail = frappe.db.get_value("Subcontracting Order Supplied Item", filters, "name")
+ if sco_rm_detail:
+ row.db_set("sco_rm_detail", sco_rm_detail)
def validate_bom(self):
for d in self.get("items"):
@@ -1901,7 +1918,7 @@
se_child.is_process_loss = item_row.get("is_process_loss", 0)
for field in [
- "po_detail",
+ "sco_rm_detail",
"original_item",
"expense_account",
"description",
@@ -1975,26 +1992,26 @@
else:
frappe.throw(_("Batch {0} of Item {1} is disabled.").format(item.batch_no, item.item_code))
- def update_purchase_order_supplied_items(self):
- if self.purchase_order and (
+ def update_subcontracting_order_supplied_items(self):
+ if self.subcontracting_order and (
self.purpose in ["Send to Subcontractor", "Material Transfer"] or self.is_return
):
- # Get PO Supplied Items Details
+ # Get SCO Supplied Items Details
item_wh = frappe._dict(
frappe.db.sql(
"""
select rm_item_code, reserve_warehouse
- from `tabPurchase Order` po, `tabPurchase Order Item Supplied` poitemsup
- where po.name = poitemsup.parent
- and po.name = %s""",
- self.purchase_order,
+ from `tabSubcontracting Order` sco, `tabSubcontracting Order Supplied Item` scoitemsup
+ where sco.name = scoitemsup.parent
+ and sco.name = %s""",
+ self.subcontracting_order,
)
)
- supplied_items = get_supplied_items(self.purchase_order)
+ supplied_items = get_supplied_items(self.subcontracting_order)
for name, item in supplied_items.items():
- frappe.db.set_value("Purchase Order Item Supplied", name, item)
+ frappe.db.set_value("Subcontracting Order Supplied Item", name, item)
# Update reserved sub contracted quantity in bin based on Supplied Item Details and
for d in self.get("items"):
@@ -2479,25 +2496,25 @@
return sample_quantity
-def get_supplied_items(purchase_order):
+def get_supplied_items(subcontracting_order):
fields = [
"`tabStock Entry Detail`.`transfer_qty`",
"`tabStock Entry`.`is_return`",
- "`tabStock Entry Detail`.`po_detail`",
+ "`tabStock Entry Detail`.`sco_rm_detail`",
"`tabStock Entry Detail`.`item_code`",
]
filters = [
["Stock Entry", "docstatus", "=", 1],
- ["Stock Entry", "purchase_order", "=", purchase_order],
+ ["Stock Entry", "subcontracting_order", "=", subcontracting_order],
]
supplied_item_details = {}
for row in frappe.get_all("Stock Entry", fields=fields, filters=filters):
- if not row.po_detail:
+ if not row.sco_rm_detail:
continue
- key = row.po_detail
+ key = row.sco_rm_detail
if key not in supplied_item_details:
supplied_item_details.setdefault(
key, frappe._dict({"supplied_qty": 0, "returned_qty": 0, "total_supplied_qty": 0})
diff --git a/erpnext/stock/get_item_details.py b/erpnext/stock/get_item_details.py
index f72588e..0d7d472 100644
--- a/erpnext/stock/get_item_details.py
+++ b/erpnext/stock/get_item_details.py
@@ -237,8 +237,8 @@
throw(_("Item {0} is a template, please select one of its variants").format(item.name))
elif args.transaction_type == "buying" and args.doctype != "Material Request":
- if args.get("is_subcontracted") == "Yes" and item.is_sub_contracted_item != 1:
- throw(_("Item {0} must be a Sub-contracted Item").format(item.name))
+ if args.get("is_subcontracted") == "Yes" and item.is_stock_item:
+ throw(_("Item {0} must be a Non-Stock Item").format(item.name))
def get_basic_details(args, item, overwrite_warehouse=True):