feat: Create subcontracted PO from Material Request (#44745)
* feat: Create subcontracted PO from Material Request * fix: Made minor changes in logic to pass all test cases * refactor: Made changes suggested by mentor and simplified logic * test: Made changes to tests
This commit is contained in:
@@ -421,6 +421,13 @@ class StatusUpdater(Document):
|
||||
if d.doctype != args["source_dt"]:
|
||||
continue
|
||||
|
||||
if (
|
||||
d.get("material_request")
|
||||
and frappe.db.get_value("Material Request", d.material_request, "material_request_type")
|
||||
== "Subcontracting"
|
||||
):
|
||||
args.update({"source_field": "fg_item_qty"})
|
||||
|
||||
self._update_modified(args, update_modified)
|
||||
|
||||
# updates qty in the child table
|
||||
|
||||
@@ -122,6 +122,12 @@ frappe.ui.form.on("Material Request", {
|
||||
() => frm.events.make_purchase_order(frm),
|
||||
__("Create")
|
||||
);
|
||||
} else if (frm.doc.material_request_type === "Subcontracting") {
|
||||
frm.add_custom_button(
|
||||
__("Subcontracted Purchase Order"),
|
||||
() => frm.events.make_purchase_order(frm),
|
||||
__("Create")
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -80,7 +80,7 @@
|
||||
"in_list_view": 1,
|
||||
"in_standard_filter": 1,
|
||||
"label": "Purpose",
|
||||
"options": "Purchase\nMaterial Transfer\nMaterial Issue\nManufacture\nCustomer Provided",
|
||||
"options": "Purchase\nMaterial Transfer\nMaterial Issue\nManufacture\nSubcontracting\nCustomer Provided",
|
||||
"reqd": 1
|
||||
},
|
||||
{
|
||||
@@ -357,7 +357,7 @@
|
||||
"idx": 70,
|
||||
"is_submittable": 1,
|
||||
"links": [],
|
||||
"modified": "2024-03-27 13:10:04.971211",
|
||||
"modified": "2024-12-16 12:46:02.262167",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Stock",
|
||||
"name": "Material Request",
|
||||
|
||||
@@ -18,6 +18,9 @@ from erpnext.controllers.buying_controller import BuyingController
|
||||
from erpnext.manufacturing.doctype.work_order.work_order import get_item_details
|
||||
from erpnext.stock.doctype.item.item import get_item_defaults
|
||||
from erpnext.stock.stock_balance import get_indented_qty, update_bin_qty
|
||||
from erpnext.subcontracting.doctype.subcontracting_bom.subcontracting_bom import (
|
||||
get_subcontracting_boms_for_finished_goods,
|
||||
)
|
||||
|
||||
form_grid_templates = {"items": "templates/form_grid/material_request_grid.html"}
|
||||
|
||||
@@ -40,7 +43,12 @@ class MaterialRequest(BuyingController):
|
||||
job_card: DF.Link | None
|
||||
letter_head: DF.Link | None
|
||||
material_request_type: DF.Literal[
|
||||
"Purchase", "Material Transfer", "Material Issue", "Manufacture", "Customer Provided"
|
||||
"Purchase",
|
||||
"Material Transfer",
|
||||
"Material Issue",
|
||||
"Manufacture",
|
||||
"Subcontracting",
|
||||
"Customer Provided",
|
||||
]
|
||||
naming_series: DF.Literal["MAT-MR-.YYYY.-"]
|
||||
per_ordered: DF.Percent
|
||||
@@ -385,6 +393,22 @@ def update_item(obj, target, source_parent):
|
||||
if getdate(target.schedule_date) < getdate(nowdate()):
|
||||
target.schedule_date = None
|
||||
|
||||
if target.fg_item:
|
||||
target.fg_item_qty = obj.stock_qty
|
||||
if sc_bom := get_subcontracting_boms_for_finished_goods(target.fg_item):
|
||||
target.item_code = sc_bom.service_item
|
||||
target.uom = sc_bom.service_item_uom
|
||||
target.conversion_factor = (
|
||||
frappe.db.get_value(
|
||||
"UOM Conversion Detail",
|
||||
{"parent": sc_bom.service_item, "uom": sc_bom.service_item_uom},
|
||||
"conversion_factor",
|
||||
)
|
||||
or 1
|
||||
)
|
||||
target.qty = target.fg_item_qty * sc_bom.conversion_factor
|
||||
target.stock_qty = target.qty * target.conversion_factor
|
||||
|
||||
|
||||
def get_list_context(context=None):
|
||||
from erpnext.controllers.website_list_for_contact import get_list_context
|
||||
@@ -416,11 +440,18 @@ def make_purchase_order(source_name, target_doc=None, args=None):
|
||||
if isinstance(args, str):
|
||||
args = json.loads(args)
|
||||
|
||||
is_subcontracted = (
|
||||
frappe.db.get_value("Material Request", source_name, "material_request_type") == "Subcontracting"
|
||||
)
|
||||
|
||||
def postprocess(source, target_doc):
|
||||
target_doc.is_subcontracted = is_subcontracted
|
||||
if frappe.flags.args and frappe.flags.args.default_supplier:
|
||||
# items only for given default supplier
|
||||
supplier_items = []
|
||||
for d in target_doc.items:
|
||||
if is_subcontracted and not d.item_code:
|
||||
continue
|
||||
default_supplier = get_item_defaults(d.item_code, target_doc.company).get("default_supplier")
|
||||
if frappe.flags.args.default_supplier == default_supplier:
|
||||
supplier_items.append(d)
|
||||
@@ -436,25 +467,37 @@ def make_purchase_order(source_name, target_doc=None, args=None):
|
||||
|
||||
return qty < d.stock_qty and child_filter
|
||||
|
||||
def generate_field_map():
|
||||
field_map = [
|
||||
["name", "material_request_item"],
|
||||
["parent", "material_request"],
|
||||
["sales_order", "sales_order"],
|
||||
["sales_order_item", "sales_order_item"],
|
||||
["wip_composite_asset", "wip_composite_asset"],
|
||||
]
|
||||
|
||||
if is_subcontracted:
|
||||
field_map.extend([["item_code", "fg_item"], ["qty", "fg_item_qty"]])
|
||||
else:
|
||||
field_map.extend([["uom", "stock_uom"], ["uom", "uom"]])
|
||||
|
||||
return field_map
|
||||
|
||||
doclist = get_mapped_doc(
|
||||
"Material Request",
|
||||
source_name,
|
||||
{
|
||||
"Material Request": {
|
||||
"doctype": "Purchase Order",
|
||||
"validation": {"docstatus": ["=", 1], "material_request_type": ["=", "Purchase"]},
|
||||
"validation": {
|
||||
"docstatus": ["=", 1],
|
||||
"material_request_type": ["in", ["Purchase", "Subcontracting"]],
|
||||
},
|
||||
},
|
||||
"Material Request Item": {
|
||||
"doctype": "Purchase Order Item",
|
||||
"field_map": [
|
||||
["name", "material_request_item"],
|
||||
["parent", "material_request"],
|
||||
["uom", "stock_uom"],
|
||||
["uom", "uom"],
|
||||
["sales_order", "sales_order"],
|
||||
["sales_order_item", "sales_order_item"],
|
||||
["wip_composite_asset", "wip_composite_asset"],
|
||||
],
|
||||
"field_map": generate_field_map(),
|
||||
"field_no_map": ["item_code", "item_name", "qty"] if is_subcontracted else [],
|
||||
"postprocess": update_item,
|
||||
"condition": select_item,
|
||||
},
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
|
||||
|
||||
import frappe
|
||||
import frappe.model
|
||||
from frappe.tests import IntegrationTestCase, UnitTestCase
|
||||
from frappe.utils import flt, today
|
||||
|
||||
@@ -53,6 +54,55 @@ class TestMaterialRequest(IntegrationTestCase):
|
||||
self.assertEqual(po.doctype, "Purchase Order")
|
||||
self.assertEqual(len(po.get("items")), len(mr.get("items")))
|
||||
|
||||
def test_make_subcontracted_purchase_order(self):
|
||||
from erpnext.manufacturing.doctype.production_plan.test_production_plan import make_bom
|
||||
from erpnext.stock.doctype.item.test_item import create_item, make_item
|
||||
from erpnext.subcontracting.doctype.subcontracting_bom.test_subcontracting_bom import (
|
||||
create_subcontracting_bom,
|
||||
)
|
||||
|
||||
mr = frappe.copy_doc(self.globalTestRecords["Material Request"][0]).insert()
|
||||
mr.material_request_type = "Subcontracting"
|
||||
mr.submit()
|
||||
|
||||
frappe.db.set_value("Item", mr.items[0].item_code, "is_sub_contracted_item", 1)
|
||||
|
||||
raw_materials = ["Raw Material Item 1", "Raw Material Item 2"]
|
||||
for item in raw_materials:
|
||||
create_item(item)
|
||||
|
||||
frappe.new_doc("UOM").update({"uom_name": "Test UOM"}).save()
|
||||
service_item = make_item(
|
||||
properties={"is_stock_item": 0}, uoms=[{"uom": "Test UOM", "conversion_factor": 3}]
|
||||
)
|
||||
|
||||
mr.items[0].default_bom = make_bom(item=mr.items[0].item_code, raw_materials=raw_materials)
|
||||
mr.reload()
|
||||
|
||||
create_subcontracting_bom(
|
||||
finished_good=mr.items[0].item_code,
|
||||
service_item=service_item.name,
|
||||
finished_good_qty=2,
|
||||
service_item_qty=1,
|
||||
service_item_uom="Test UOM",
|
||||
)
|
||||
|
||||
po = make_purchase_order(mr.name)
|
||||
po.supplier = "_Test Supplier"
|
||||
po.items[0].schedule_date = today()
|
||||
po.items.pop(1)
|
||||
|
||||
# Test 1 - Test if items stock qty, qty and finished good qty are calculated correctly based on provided UOMs
|
||||
self.assertEqual(po.items[0].stock_qty, 81)
|
||||
self.assertEqual(po.items[0].qty, 27)
|
||||
self.assertEqual(po.items[0].fg_item_qty, 54)
|
||||
|
||||
po.submit()
|
||||
mr.reload()
|
||||
|
||||
# Test 2 - MR items ordered qty should be updated based on PO items qty when submitted
|
||||
self.assertEqual(mr.items[0].ordered_qty, 54)
|
||||
|
||||
def test_make_supplier_quotation(self):
|
||||
mr = frappe.copy_doc(self.globalTestRecords["Material Request"][0]).insert()
|
||||
|
||||
|
||||
Reference in New Issue
Block a user