Merge pull request #36377 from rohitwaghchaure/fixed-pp-sub-assembly-available-items
fix: multiple issues related to Production Plan
This commit is contained in:
commit
4e58503075
@ -9,19 +9,25 @@ frappe.ui.form.on('Production Plan', {
|
|||||||
item.temporary_name = item.name;
|
item.temporary_name = item.name;
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
setup(frm) {
|
setup(frm) {
|
||||||
|
frm.trigger("setup_queries");
|
||||||
|
|
||||||
frm.custom_make_buttons = {
|
frm.custom_make_buttons = {
|
||||||
'Work Order': 'Work Order / Subcontract PO',
|
'Work Order': 'Work Order / Subcontract PO',
|
||||||
'Material Request': 'Material Request',
|
'Material Request': 'Material Request',
|
||||||
};
|
};
|
||||||
|
},
|
||||||
|
|
||||||
frm.fields_dict['po_items'].grid.get_field('warehouse').get_query = function(doc) {
|
setup_queries(frm) {
|
||||||
|
frm.set_query("sales_order", "sales_orders", () => {
|
||||||
return {
|
return {
|
||||||
|
query: "erpnext.manufacturing.doctype.production_plan.production_plan.sales_order_query",
|
||||||
filters: {
|
filters: {
|
||||||
company: doc.company
|
company: frm.doc.company,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
});
|
||||||
|
|
||||||
frm.set_query('for_warehouse', function(doc) {
|
frm.set_query('for_warehouse', function(doc) {
|
||||||
return {
|
return {
|
||||||
@ -42,32 +48,40 @@ frappe.ui.form.on('Production Plan', {
|
|||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
frm.fields_dict['po_items'].grid.get_field('item_code').get_query = function(doc) {
|
frm.set_query("item_code", "po_items", (doc, cdt, cdn) => {
|
||||||
return {
|
return {
|
||||||
query: "erpnext.controllers.queries.item_query",
|
query: "erpnext.controllers.queries.item_query",
|
||||||
filters:{
|
filters:{
|
||||||
'is_stock_item': 1,
|
'is_stock_item': 1,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
});
|
||||||
|
|
||||||
frm.fields_dict['po_items'].grid.get_field('bom_no').get_query = function(doc, cdt, cdn) {
|
frm.set_query("bom_no", "po_items", (doc, cdt, cdn) => {
|
||||||
var d = locals[cdt][cdn];
|
var d = locals[cdt][cdn];
|
||||||
if (d.item_code) {
|
if (d.item_code) {
|
||||||
return {
|
return {
|
||||||
query: "erpnext.controllers.queries.bom",
|
query: "erpnext.controllers.queries.bom",
|
||||||
filters:{'item': cstr(d.item_code), 'docstatus': 1}
|
filters:{'item': d.item_code, 'docstatus': 1}
|
||||||
}
|
}
|
||||||
} else frappe.msgprint(__("Please enter Item first"));
|
} else frappe.msgprint(__("Please enter Item first"));
|
||||||
}
|
});
|
||||||
|
|
||||||
frm.fields_dict['mr_items'].grid.get_field('warehouse').get_query = function(doc) {
|
frm.set_query("warehouse", "mr_items", (doc) => {
|
||||||
return {
|
return {
|
||||||
filters: {
|
filters: {
|
||||||
company: doc.company
|
company: doc.company
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
});
|
||||||
|
|
||||||
|
frm.set_query("warehouse", "po_items", (doc) => {
|
||||||
|
return {
|
||||||
|
filters: {
|
||||||
|
company: doc.company
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
refresh(frm) {
|
refresh(frm) {
|
||||||
@ -436,7 +450,7 @@ frappe.ui.form.on("Production Plan Item", {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
frappe.ui.form.on("Material Request Plan Item", {
|
frappe.ui.form.on("Material Request Plan Item", {
|
||||||
@ -467,31 +481,36 @@ frappe.ui.form.on("Material Request Plan Item", {
|
|||||||
|
|
||||||
frappe.ui.form.on("Production Plan Sales Order", {
|
frappe.ui.form.on("Production Plan Sales Order", {
|
||||||
sales_order(frm, cdt, cdn) {
|
sales_order(frm, cdt, cdn) {
|
||||||
const { sales_order } = locals[cdt][cdn];
|
let row = locals[cdt][cdn];
|
||||||
|
const sales_order = row.sales_order;
|
||||||
if (!sales_order) {
|
if (!sales_order) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
frappe.call({
|
|
||||||
method: "erpnext.manufacturing.doctype.production_plan.production_plan.get_so_details",
|
if (row.sales_order) {
|
||||||
args: { sales_order },
|
frm.call({
|
||||||
callback(r) {
|
method: "validate_sales_orders",
|
||||||
const {transaction_date, customer, grand_total} = r.message;
|
doc: frm.doc,
|
||||||
frappe.model.set_value(cdt, cdn, 'sales_order_date', transaction_date);
|
args: {
|
||||||
frappe.model.set_value(cdt, cdn, 'customer', customer);
|
sales_order: row.sales_order,
|
||||||
frappe.model.set_value(cdt, cdn, 'grand_total', grand_total);
|
},
|
||||||
}
|
callback(r) {
|
||||||
});
|
frappe.call({
|
||||||
|
method: "erpnext.manufacturing.doctype.production_plan.production_plan.get_so_details",
|
||||||
|
args: { sales_order },
|
||||||
|
callback(r) {
|
||||||
|
const {transaction_date, customer, grand_total} = r.message;
|
||||||
|
frappe.model.set_value(cdt, cdn, 'sales_order_date', transaction_date);
|
||||||
|
frappe.model.set_value(cdt, cdn, 'customer', customer);
|
||||||
|
frappe.model.set_value(cdt, cdn, 'grand_total', grand_total);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
cur_frm.fields_dict['sales_orders'].grid.get_field("sales_order").get_query = function() {
|
|
||||||
return{
|
|
||||||
filters: [
|
|
||||||
['Sales Order','docstatus', '=' ,1]
|
|
||||||
]
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
frappe.tour['Production Plan'] = [
|
frappe.tour['Production Plan'] = [
|
||||||
{
|
{
|
||||||
fieldname: "get_items_from",
|
fieldname: "get_items_from",
|
||||||
|
|||||||
@ -228,10 +228,10 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"default": "0",
|
"default": "0",
|
||||||
"description": "To know more about projected quantity, <a href=\"https://erpnext.com/docs/user/manual/en/stock/projected-quantity\" style=\"text-decoration: underline;\" target=\"_blank\">click here</a>.",
|
"description": "If enabled, the system won't create material requests for the available items.",
|
||||||
"fieldname": "ignore_existing_ordered_qty",
|
"fieldname": "ignore_existing_ordered_qty",
|
||||||
"fieldtype": "Check",
|
"fieldtype": "Check",
|
||||||
"label": "Ignore Existing Projected Quantity"
|
"label": "Ignore Available Stock"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"fieldname": "column_break_25",
|
"fieldname": "column_break_25",
|
||||||
@ -339,7 +339,7 @@
|
|||||||
"depends_on": "eval:doc.get_items_from == 'Sales Order'",
|
"depends_on": "eval:doc.get_items_from == 'Sales Order'",
|
||||||
"fieldname": "combine_items",
|
"fieldname": "combine_items",
|
||||||
"fieldtype": "Check",
|
"fieldtype": "Check",
|
||||||
"label": "Consolidate Items"
|
"label": "Consolidate Sales Order Items"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"fieldname": "section_break_25",
|
"fieldname": "section_break_25",
|
||||||
@ -399,7 +399,7 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"default": "0",
|
"default": "0",
|
||||||
"description": "System consider the projected quantity to check available or will be available sub-assembly items ",
|
"description": "If this checkbox is enabled, then the system won\u2019t run the MRP for the available sub-assembly items.",
|
||||||
"fieldname": "skip_available_sub_assembly_item",
|
"fieldname": "skip_available_sub_assembly_item",
|
||||||
"fieldtype": "Check",
|
"fieldtype": "Check",
|
||||||
"label": "Skip Available Sub Assembly Items"
|
"label": "Skip Available Sub Assembly Items"
|
||||||
@ -422,7 +422,7 @@
|
|||||||
"index_web_pages_for_search": 1,
|
"index_web_pages_for_search": 1,
|
||||||
"is_submittable": 1,
|
"is_submittable": 1,
|
||||||
"links": [],
|
"links": [],
|
||||||
"modified": "2023-05-22 23:36:31.770517",
|
"modified": "2023-07-28 13:37:43.926686",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "Manufacturing",
|
"module": "Manufacturing",
|
||||||
"name": "Production Plan",
|
"name": "Production Plan",
|
||||||
|
|||||||
@ -39,6 +39,36 @@ class ProductionPlan(Document):
|
|||||||
self.set_status()
|
self.set_status()
|
||||||
self._rename_temporary_references()
|
self._rename_temporary_references()
|
||||||
validate_uom_is_integer(self, "stock_uom", "planned_qty")
|
validate_uom_is_integer(self, "stock_uom", "planned_qty")
|
||||||
|
self.validate_sales_orders()
|
||||||
|
|
||||||
|
@frappe.whitelist()
|
||||||
|
def validate_sales_orders(self, sales_order=None):
|
||||||
|
sales_orders = []
|
||||||
|
|
||||||
|
if sales_order:
|
||||||
|
sales_orders.append(sales_order)
|
||||||
|
else:
|
||||||
|
sales_orders = [row.sales_order for row in self.sales_orders if row.sales_order]
|
||||||
|
|
||||||
|
data = sales_order_query(filters={"company": self.company, "sales_orders": sales_orders})
|
||||||
|
|
||||||
|
title = _("Production Plan Already Submitted")
|
||||||
|
if not data:
|
||||||
|
msg = _("No items are available in the sales order {0} for production").format(sales_orders[0])
|
||||||
|
if len(sales_orders) > 1:
|
||||||
|
sales_orders = ", ".join(sales_orders)
|
||||||
|
msg = _("No items are available in sales orders {0} for production").format(sales_orders)
|
||||||
|
|
||||||
|
frappe.throw(msg, title=title)
|
||||||
|
|
||||||
|
data = [d[0] for d in data]
|
||||||
|
|
||||||
|
for sales_order in sales_orders:
|
||||||
|
if sales_order not in data:
|
||||||
|
frappe.throw(
|
||||||
|
_("No items are available in the sales order {0} for production").format(sales_order),
|
||||||
|
title=title,
|
||||||
|
)
|
||||||
|
|
||||||
def set_pending_qty_in_row_without_reference(self):
|
def set_pending_qty_in_row_without_reference(self):
|
||||||
"Set Pending Qty in independent rows (not from SO or MR)."
|
"Set Pending Qty in independent rows (not from SO or MR)."
|
||||||
@ -205,6 +235,7 @@ class ProductionPlan(Document):
|
|||||||
).as_("pending_qty"),
|
).as_("pending_qty"),
|
||||||
so_item.description,
|
so_item.description,
|
||||||
so_item.name,
|
so_item.name,
|
||||||
|
so_item.bom_no,
|
||||||
)
|
)
|
||||||
.distinct()
|
.distinct()
|
||||||
.where(
|
.where(
|
||||||
@ -342,7 +373,7 @@ class ProductionPlan(Document):
|
|||||||
"item_code": data.item_code,
|
"item_code": data.item_code,
|
||||||
"description": data.description or item_details.description,
|
"description": data.description or item_details.description,
|
||||||
"stock_uom": item_details and item_details.stock_uom or "",
|
"stock_uom": item_details and item_details.stock_uom or "",
|
||||||
"bom_no": item_details and item_details.bom_no or "",
|
"bom_no": data.bom_no or item_details and item_details.bom_no or "",
|
||||||
"planned_qty": data.pending_qty,
|
"planned_qty": data.pending_qty,
|
||||||
"pending_qty": data.pending_qty,
|
"pending_qty": data.pending_qty,
|
||||||
"planned_start_date": now_datetime(),
|
"planned_start_date": now_datetime(),
|
||||||
@ -401,11 +432,50 @@ class ProductionPlan(Document):
|
|||||||
|
|
||||||
def on_submit(self):
|
def on_submit(self):
|
||||||
self.update_bin_qty()
|
self.update_bin_qty()
|
||||||
|
self.update_sales_order()
|
||||||
|
|
||||||
def on_cancel(self):
|
def on_cancel(self):
|
||||||
self.db_set("status", "Cancelled")
|
self.db_set("status", "Cancelled")
|
||||||
self.delete_draft_work_order()
|
self.delete_draft_work_order()
|
||||||
self.update_bin_qty()
|
self.update_bin_qty()
|
||||||
|
self.update_sales_order()
|
||||||
|
|
||||||
|
def update_sales_order(self):
|
||||||
|
sales_orders = [row.sales_order for row in self.po_items if row.sales_order]
|
||||||
|
if sales_orders:
|
||||||
|
so_wise_planned_qty = self.get_so_wise_planned_qty(sales_orders)
|
||||||
|
|
||||||
|
for row in self.po_items:
|
||||||
|
if not row.sales_order and not row.sales_order_item:
|
||||||
|
continue
|
||||||
|
|
||||||
|
key = (row.sales_order, row.sales_order_item)
|
||||||
|
frappe.db.set_value(
|
||||||
|
"Sales Order Item",
|
||||||
|
row.sales_order_item,
|
||||||
|
"production_plan_qty",
|
||||||
|
flt(so_wise_planned_qty.get(key)),
|
||||||
|
)
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def get_so_wise_planned_qty(sales_orders):
|
||||||
|
so_wise_planned_qty = frappe._dict()
|
||||||
|
data = frappe.get_all(
|
||||||
|
"Production Plan Item",
|
||||||
|
fields=["sales_order", "sales_order_item", "SUM(planned_qty) as qty"],
|
||||||
|
filters={
|
||||||
|
"sales_order": ("in", sales_orders),
|
||||||
|
"docstatus": 1,
|
||||||
|
"sales_order_item": ("is", "set"),
|
||||||
|
},
|
||||||
|
group_by="sales_order, sales_order_item",
|
||||||
|
)
|
||||||
|
|
||||||
|
for row in data:
|
||||||
|
key = (row.sales_order, row.sales_order_item)
|
||||||
|
so_wise_planned_qty[key] = row.qty
|
||||||
|
|
||||||
|
return so_wise_planned_qty
|
||||||
|
|
||||||
def update_bin_qty(self):
|
def update_bin_qty(self):
|
||||||
for d in self.mr_items:
|
for d in self.mr_items:
|
||||||
@ -719,6 +789,9 @@ class ProductionPlan(Document):
|
|||||||
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:
|
for row in self.po_items:
|
||||||
|
if self.skip_available_sub_assembly_item and not row.warehouse:
|
||||||
|
frappe.throw(_("Row #{0}: Please select the FG Warehouse in Assembly Items").format(row.idx))
|
||||||
|
|
||||||
if not row.item_code:
|
if not row.item_code:
|
||||||
frappe.throw(_("Row #{0}: Please select Item Code in Assembly Items").format(row.idx))
|
frappe.throw(_("Row #{0}: Please select Item Code in Assembly Items").format(row.idx))
|
||||||
|
|
||||||
@ -1142,7 +1215,7 @@ def get_sales_orders(self):
|
|||||||
& (so.docstatus == 1)
|
& (so.docstatus == 1)
|
||||||
& (so.status.notin(["Stopped", "Closed"]))
|
& (so.status.notin(["Stopped", "Closed"]))
|
||||||
& (so.company == self.company)
|
& (so.company == self.company)
|
||||||
& (so_item.qty > so_item.work_order_qty)
|
& (so_item.qty > so_item.production_plan_qty)
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -1566,7 +1639,6 @@ def get_reserved_qty_for_production_plan(item_code, warehouse):
|
|||||||
def get_raw_materials_of_sub_assembly_items(
|
def get_raw_materials_of_sub_assembly_items(
|
||||||
item_details, company, bom_no, include_non_stock_items, sub_assembly_items, planned_qty=1
|
item_details, company, bom_no, include_non_stock_items, sub_assembly_items, planned_qty=1
|
||||||
):
|
):
|
||||||
|
|
||||||
bei = frappe.qb.DocType("BOM Item")
|
bei = frappe.qb.DocType("BOM Item")
|
||||||
bom = frappe.qb.DocType("BOM")
|
bom = frappe.qb.DocType("BOM")
|
||||||
item = frappe.qb.DocType("Item")
|
item = frappe.qb.DocType("Item")
|
||||||
@ -1609,7 +1681,10 @@ def get_raw_materials_of_sub_assembly_items(
|
|||||||
|
|
||||||
for item in items:
|
for item in items:
|
||||||
key = (item.item_code, item.bom_no)
|
key = (item.item_code, item.bom_no)
|
||||||
if item.bom_no and key in sub_assembly_items:
|
if item.bom_no and key not in sub_assembly_items:
|
||||||
|
continue
|
||||||
|
|
||||||
|
if item.bom_no:
|
||||||
planned_qty = flt(sub_assembly_items[key])
|
planned_qty = flt(sub_assembly_items[key])
|
||||||
get_raw_materials_of_sub_assembly_items(
|
get_raw_materials_of_sub_assembly_items(
|
||||||
item_details,
|
item_details,
|
||||||
@ -1626,3 +1701,42 @@ def get_raw_materials_of_sub_assembly_items(
|
|||||||
item_details.setdefault(item.get("item_code"), item)
|
item_details.setdefault(item.get("item_code"), item)
|
||||||
|
|
||||||
return item_details
|
return item_details
|
||||||
|
|
||||||
|
|
||||||
|
@frappe.whitelist()
|
||||||
|
def sales_order_query(
|
||||||
|
doctype=None, txt=None, searchfield=None, start=None, page_len=None, filters=None
|
||||||
|
):
|
||||||
|
frappe.has_permission("Production Plan", throw=True)
|
||||||
|
|
||||||
|
if not filters:
|
||||||
|
filters = {}
|
||||||
|
|
||||||
|
so_table = frappe.qb.DocType("Sales Order")
|
||||||
|
table = frappe.qb.DocType("Sales Order Item")
|
||||||
|
|
||||||
|
query = (
|
||||||
|
frappe.qb.from_(so_table)
|
||||||
|
.join(table)
|
||||||
|
.on(table.parent == so_table.name)
|
||||||
|
.select(table.parent)
|
||||||
|
.distinct()
|
||||||
|
.where((table.qty > table.production_plan_qty) & (table.docstatus == 1))
|
||||||
|
)
|
||||||
|
|
||||||
|
if filters.get("company"):
|
||||||
|
query = query.where(so_table.company == filters.get("company"))
|
||||||
|
|
||||||
|
if filters.get("sales_orders"):
|
||||||
|
query = query.where(so_table.name.isin(filters.get("sales_orders")))
|
||||||
|
|
||||||
|
if txt:
|
||||||
|
query = query.where(table.item_code.like(f"{txt}%"))
|
||||||
|
|
||||||
|
if page_len:
|
||||||
|
query = query.limit(page_len)
|
||||||
|
|
||||||
|
if start:
|
||||||
|
query = query.offset(start)
|
||||||
|
|
||||||
|
return query.run()
|
||||||
|
|||||||
@ -225,6 +225,102 @@ class TestProductionPlan(FrappeTestCase):
|
|||||||
|
|
||||||
self.assertEqual(sales_orders, [])
|
self.assertEqual(sales_orders, [])
|
||||||
|
|
||||||
|
def test_donot_allow_to_make_multiple_pp_against_same_so(self):
|
||||||
|
item = "Test SO Production Item 1"
|
||||||
|
create_item(item)
|
||||||
|
|
||||||
|
raw_material = "Test SO RM Production Item 1"
|
||||||
|
create_item(raw_material)
|
||||||
|
|
||||||
|
if not frappe.db.get_value("BOM", {"item": item}):
|
||||||
|
make_bom(item=item, raw_materials=[raw_material])
|
||||||
|
|
||||||
|
so = make_sales_order(item_code=item, qty=4)
|
||||||
|
pln = frappe.new_doc("Production Plan")
|
||||||
|
pln.company = so.company
|
||||||
|
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.get_so_items()
|
||||||
|
pln.submit()
|
||||||
|
|
||||||
|
pln = frappe.new_doc("Production Plan")
|
||||||
|
pln.company = so.company
|
||||||
|
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.get_so_items()
|
||||||
|
self.assertRaises(frappe.ValidationError, pln.save)
|
||||||
|
|
||||||
|
def test_so_based_bill_of_material(self):
|
||||||
|
item = "Test SO Production Item 1"
|
||||||
|
create_item(item)
|
||||||
|
|
||||||
|
raw_material = "Test SO RM Production Item 1"
|
||||||
|
create_item(raw_material)
|
||||||
|
|
||||||
|
bom1 = make_bom(item=item, raw_materials=[raw_material])
|
||||||
|
|
||||||
|
so = make_sales_order(item_code=item, qty=4)
|
||||||
|
|
||||||
|
# Create new BOM and assign to new sales order
|
||||||
|
bom2 = make_bom(item=item, raw_materials=[raw_material])
|
||||||
|
so2 = make_sales_order(item_code=item, qty=4)
|
||||||
|
|
||||||
|
pln1 = frappe.new_doc("Production Plan")
|
||||||
|
pln1.company = so.company
|
||||||
|
pln1.get_items_from = "Sales Order"
|
||||||
|
|
||||||
|
pln1.append(
|
||||||
|
"sales_orders",
|
||||||
|
{
|
||||||
|
"sales_order": so.name,
|
||||||
|
"sales_order_date": so.transaction_date,
|
||||||
|
"customer": so.customer,
|
||||||
|
"grand_total": so.grand_total,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
pln1.get_so_items()
|
||||||
|
|
||||||
|
self.assertEqual(pln1.po_items[0].bom_no, bom1.name)
|
||||||
|
|
||||||
|
pln2 = frappe.new_doc("Production Plan")
|
||||||
|
pln2.company = so2.company
|
||||||
|
pln2.get_items_from = "Sales Order"
|
||||||
|
|
||||||
|
pln2.append(
|
||||||
|
"sales_orders",
|
||||||
|
{
|
||||||
|
"sales_order": so2.name,
|
||||||
|
"sales_order_date": so2.transaction_date,
|
||||||
|
"customer": so2.customer,
|
||||||
|
"grand_total": so2.grand_total,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
pln2.get_so_items()
|
||||||
|
|
||||||
|
self.assertEqual(pln2.po_items[0].bom_no, bom2.name)
|
||||||
|
|
||||||
def test_production_plan_combine_items(self):
|
def test_production_plan_combine_items(self):
|
||||||
"Test combining FG items in Production Plan."
|
"Test combining FG items in Production Plan."
|
||||||
item = "Test Production Item 1"
|
item = "Test Production Item 1"
|
||||||
|
|||||||
@ -84,6 +84,7 @@
|
|||||||
"actual_qty",
|
"actual_qty",
|
||||||
"ordered_qty",
|
"ordered_qty",
|
||||||
"planned_qty",
|
"planned_qty",
|
||||||
|
"production_plan_qty",
|
||||||
"column_break_69",
|
"column_break_69",
|
||||||
"work_order_qty",
|
"work_order_qty",
|
||||||
"delivered_qty",
|
"delivered_qty",
|
||||||
@ -882,12 +883,19 @@
|
|||||||
"print_hide": 1,
|
"print_hide": 1,
|
||||||
"read_only": 1,
|
"read_only": 1,
|
||||||
"report_hide": 1
|
"report_hide": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "production_plan_qty",
|
||||||
|
"fieldtype": "Float",
|
||||||
|
"label": "Production Plan Qty",
|
||||||
|
"no_copy": 1,
|
||||||
|
"read_only": 1
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"idx": 1,
|
"idx": 1,
|
||||||
"istable": 1,
|
"istable": 1,
|
||||||
"links": [],
|
"links": [],
|
||||||
"modified": "2023-04-04 10:44:05.707488",
|
"modified": "2023-07-28 14:56:42.031636",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "Selling",
|
"module": "Selling",
|
||||||
"name": "Sales Order Item",
|
"name": "Sales Order Item",
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user