mirror of
https://github.com/frappe/erpnext.git
synced 2026-06-03 12:19:12 +00:00
Merge branch 'develop' into subcontracting
This commit is contained in:
@@ -445,6 +445,7 @@ class BOM(WebsiteGenerator):
|
||||
and self.is_active
|
||||
):
|
||||
frappe.db.set(self, "is_default", 1)
|
||||
frappe.db.set_value("Item", self.item, "default_bom", self.name)
|
||||
else:
|
||||
frappe.db.set(self, "is_default", 0)
|
||||
item = frappe.get_doc("Item", self.item)
|
||||
|
||||
@@ -575,6 +575,42 @@ class TestBOM(FrappeTestCase):
|
||||
bom.submit()
|
||||
self.assertEqual(bom.items[0].rate, 42)
|
||||
|
||||
def test_set_default_bom_for_item_having_single_bom(self):
|
||||
from erpnext.stock.doctype.item.test_item import make_item
|
||||
|
||||
fg_item = make_item(properties={"is_stock_item": 1})
|
||||
bom_item = make_item(properties={"is_stock_item": 1})
|
||||
|
||||
# Step 1: Create BOM
|
||||
bom = frappe.new_doc("BOM")
|
||||
bom.item = fg_item.item_code
|
||||
bom.quantity = 1
|
||||
bom.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.save()
|
||||
bom.submit()
|
||||
self.assertEqual(frappe.get_value("Item", fg_item.item_code, "default_bom"), bom.name)
|
||||
|
||||
# Step 2: Uncheck is_active field
|
||||
bom.is_active = 0
|
||||
bom.save()
|
||||
bom.reload()
|
||||
self.assertIsNone(frappe.get_value("Item", fg_item.item_code, "default_bom"))
|
||||
|
||||
# Step 3: Check is_active field
|
||||
bom.is_active = 1
|
||||
bom.save()
|
||||
bom.reload()
|
||||
self.assertEqual(frappe.get_value("Item", fg_item.item_code, "default_bom"), bom.name)
|
||||
|
||||
|
||||
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})
|
||||
|
||||
@@ -7,11 +7,11 @@
|
||||
"editable_grid": 1,
|
||||
"engine": "InnoDB",
|
||||
"field_order": [
|
||||
"current_bom",
|
||||
"new_bom",
|
||||
"column_break_3",
|
||||
"update_type",
|
||||
"status",
|
||||
"column_break_3",
|
||||
"current_bom",
|
||||
"new_bom",
|
||||
"error_log",
|
||||
"progress_section",
|
||||
"current_level",
|
||||
@@ -37,6 +37,7 @@
|
||||
"options": "BOM"
|
||||
},
|
||||
{
|
||||
"depends_on": "eval:doc.update_type === \"Replace BOM\"",
|
||||
"fieldname": "column_break_3",
|
||||
"fieldtype": "Column Break"
|
||||
},
|
||||
@@ -87,6 +88,7 @@
|
||||
"options": "BOM Update Batch"
|
||||
},
|
||||
{
|
||||
"depends_on": "eval:doc.status !== \"Completed\"",
|
||||
"fieldname": "current_level",
|
||||
"fieldtype": "Int",
|
||||
"label": "Current Level"
|
||||
@@ -96,7 +98,7 @@
|
||||
"index_web_pages_for_search": 1,
|
||||
"is_submittable": 1,
|
||||
"links": [],
|
||||
"modified": "2022-06-06 15:15:23.883251",
|
||||
"modified": "2022-06-20 15:43:55.696388",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Manufacturing",
|
||||
"name": "BOM Update Log",
|
||||
|
||||
@@ -6,6 +6,8 @@ from typing import Any, Dict, List, Optional, Tuple, Union
|
||||
import frappe
|
||||
from frappe import _
|
||||
from frappe.model.document import Document
|
||||
from frappe.query_builder import DocType, Interval
|
||||
from frappe.query_builder.functions import Now
|
||||
from frappe.utils import cint, cstr
|
||||
|
||||
from erpnext.manufacturing.doctype.bom_update_log.bom_updation_utils import (
|
||||
@@ -22,6 +24,17 @@ class BOMMissingError(frappe.ValidationError):
|
||||
|
||||
|
||||
class BOMUpdateLog(Document):
|
||||
@staticmethod
|
||||
def clear_old_logs(days=None):
|
||||
days = days or 90
|
||||
table = DocType("BOM Update Log")
|
||||
frappe.db.delete(
|
||||
table,
|
||||
filters=(
|
||||
(table.modified < (Now() - Interval(days=days))) & (table.update_type == "Update Cost")
|
||||
),
|
||||
)
|
||||
|
||||
def validate(self):
|
||||
if self.update_type == "Replace BOM":
|
||||
self.validate_boms_are_specified()
|
||||
@@ -77,7 +90,11 @@ class BOMUpdateLog(Document):
|
||||
now=frappe.flags.in_test,
|
||||
)
|
||||
else:
|
||||
process_boms_cost_level_wise(self)
|
||||
frappe.enqueue(
|
||||
method="erpnext.manufacturing.doctype.bom_update_log.bom_update_log.process_boms_cost_level_wise",
|
||||
update_doc=self,
|
||||
now=frappe.flags.in_test,
|
||||
)
|
||||
|
||||
|
||||
def run_replace_bom_job(
|
||||
@@ -112,28 +129,31 @@ def process_boms_cost_level_wise(
|
||||
current_boms = {}
|
||||
values = {}
|
||||
|
||||
if update_doc.status == "Queued":
|
||||
# First level yet to process. On Submit.
|
||||
current_level = 0
|
||||
current_boms = get_leaf_boms()
|
||||
values = {
|
||||
"processed_boms": json.dumps({}),
|
||||
"status": "In Progress",
|
||||
"current_level": current_level,
|
||||
}
|
||||
else:
|
||||
# Resume next level. via Cron Job.
|
||||
if not parent_boms:
|
||||
return
|
||||
try:
|
||||
if update_doc.status == "Queued":
|
||||
# First level yet to process. On Submit.
|
||||
current_level = 0
|
||||
current_boms = get_leaf_boms()
|
||||
values = {
|
||||
"processed_boms": json.dumps({}),
|
||||
"status": "In Progress",
|
||||
"current_level": current_level,
|
||||
}
|
||||
else:
|
||||
# Resume next level. via Cron Job.
|
||||
if not parent_boms:
|
||||
return
|
||||
|
||||
current_level = cint(update_doc.current_level) + 1
|
||||
current_level = cint(update_doc.current_level) + 1
|
||||
|
||||
# Process the next level BOMs. Stage parents as current BOMs.
|
||||
current_boms = parent_boms.copy()
|
||||
values = {"current_level": current_level}
|
||||
# Process the next level BOMs. Stage parents as current BOMs.
|
||||
current_boms = parent_boms.copy()
|
||||
values = {"current_level": current_level}
|
||||
|
||||
set_values_in_log(update_doc.name, values, commit=True)
|
||||
queue_bom_cost_jobs(current_boms, update_doc, current_level)
|
||||
set_values_in_log(update_doc.name, values, commit=True)
|
||||
queue_bom_cost_jobs(current_boms, update_doc, current_level)
|
||||
except Exception:
|
||||
handle_exception(update_doc)
|
||||
|
||||
|
||||
def queue_bom_cost_jobs(
|
||||
@@ -199,16 +219,22 @@ def resume_bom_cost_update_jobs():
|
||||
current_boms, processed_boms = get_processed_current_boms(log, bom_batches)
|
||||
parent_boms = get_next_higher_level_boms(child_boms=current_boms, processed_boms=processed_boms)
|
||||
|
||||
# Unset processed BOMs if log is complete, it is used for next level BOMs
|
||||
# Unset processed BOMs (it is used for next level BOMs) & change status if log is complete
|
||||
status = "Completed" if not parent_boms else "In Progress"
|
||||
processed_boms = json.dumps([] if not parent_boms else processed_boms)
|
||||
set_values_in_log(
|
||||
log.name,
|
||||
values={
|
||||
"processed_boms": json.dumps([] if not parent_boms else processed_boms),
|
||||
"status": "Completed" if not parent_boms else "In Progress",
|
||||
"processed_boms": processed_boms,
|
||||
"status": status,
|
||||
},
|
||||
commit=True,
|
||||
)
|
||||
|
||||
# clear progress section
|
||||
if status == "Completed":
|
||||
frappe.db.delete("BOM Update Batch", {"parent": log.name})
|
||||
|
||||
if parent_boms: # there is a next level to process
|
||||
process_boms_cost_level_wise(
|
||||
update_doc=frappe.get_doc("BOM Update Log", log.name), parent_boms=parent_boms
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
frappe.listview_settings['BOM Update Log'] = {
|
||||
add_fields: ["status"],
|
||||
get_indicator: function(doc) {
|
||||
get_indicator: (doc) => {
|
||||
let status_map = {
|
||||
"Queued": "orange",
|
||||
"In Progress": "blue",
|
||||
@@ -9,5 +9,22 @@ frappe.listview_settings['BOM Update Log'] = {
|
||||
};
|
||||
|
||||
return [__(doc.status), status_map[doc.status], "status,=," + doc.status];
|
||||
}
|
||||
},
|
||||
onload: () => {
|
||||
if (!frappe.model.can_write("Log Settings")) {
|
||||
return;
|
||||
}
|
||||
|
||||
let sidebar_entry = $(
|
||||
'<ul class="list-unstyled sidebar-menu log-retention-note"></ul>'
|
||||
).appendTo(cur_list.page.sidebar);
|
||||
let message = __("Note: Automatic log deletion only applies to logs of type <i>Update Cost</i>");
|
||||
$(`<hr><div class='text-muted'>${message}</div>`).appendTo(sidebar_entry);
|
||||
|
||||
frappe.require("logtypes.bundle.js", () => {
|
||||
frappe.utils.logtypes.show_log_retention_message(cur_list.doctype);
|
||||
});
|
||||
|
||||
|
||||
},
|
||||
};
|
||||
@@ -25,6 +25,7 @@ from erpnext.manufacturing.doctype.bom.bom import get_children as get_bom_childr
|
||||
from erpnext.manufacturing.doctype.bom.bom import validate_bom_no
|
||||
from erpnext.manufacturing.doctype.work_order.work_order import get_item_details
|
||||
from erpnext.setup.doctype.item_group.item_group import get_item_group_defaults
|
||||
from erpnext.utilities.transaction_base import validate_uom_is_integer
|
||||
|
||||
|
||||
class ProductionPlan(Document):
|
||||
@@ -33,6 +34,7 @@ class ProductionPlan(Document):
|
||||
self.calculate_total_planned_qty()
|
||||
self.set_status()
|
||||
self._rename_temporary_references()
|
||||
validate_uom_is_integer(self, "stock_uom", "planned_qty")
|
||||
|
||||
def set_pending_qty_in_row_without_reference(self):
|
||||
"Set Pending Qty in independent rows (not from SO or MR)."
|
||||
|
||||
@@ -679,15 +679,23 @@ class TestProductionPlan(FrappeTestCase):
|
||||
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"}
|
||||
# Case 1: When Planned Qty is non-integer and UOM is integer.
|
||||
from erpnext.utilities.transaction_base import UOMMustBeIntegerError
|
||||
|
||||
self.assertRaises(
|
||||
UOMMustBeIntegerError, create_production_plan, item_code="_Test FG Item", planned_qty=0.55
|
||||
)
|
||||
wo_doc.submit()
|
||||
self.assertEqual(wo_doc.qty, 0.55)
|
||||
|
||||
# Case 2: When Planned Qty is non-integer and UOM is also non-integer.
|
||||
from erpnext.stock.doctype.item.test_item import make_item
|
||||
|
||||
fg_item = make_item(properties={"is_stock_item": 1, "stock_uom": "_Test UOM 1"}).name
|
||||
bom_item = make_item().name
|
||||
|
||||
make_bom(item=fg_item, raw_materials=[bom_item], source_warehouse="_Test Warehouse - _TC")
|
||||
|
||||
pln = create_production_plan(item_code=fg_item, planned_qty=0.55, stock_uom="_Test UOM 1")
|
||||
self.assertEqual(pln.po_items[0].planned_qty, 0.55)
|
||||
|
||||
def test_temporary_name_relinking(self):
|
||||
|
||||
@@ -751,6 +759,7 @@ def create_production_plan(**args):
|
||||
"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(),
|
||||
"stock_uom": args.stock_uom or "Nos",
|
||||
},
|
||||
)
|
||||
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
# Copyright (c) 2021, Frappe Technologies Pvt. Ltd. and Contributors
|
||||
# License: GNU General Public License v3. See license.txt
|
||||
|
||||
import copy
|
||||
|
||||
import frappe
|
||||
from frappe.tests.utils import FrappeTestCase, change_settings, timeout
|
||||
from frappe.utils import add_days, add_months, cint, flt, now, today
|
||||
@@ -19,6 +21,7 @@ from erpnext.manufacturing.doctype.work_order.work_order import (
|
||||
)
|
||||
from erpnext.selling.doctype.sales_order.test_sales_order import make_sales_order
|
||||
from erpnext.stock.doctype.item.test_item import create_item, make_item
|
||||
from erpnext.stock.doctype.serial_no.serial_no import get_serial_nos
|
||||
from erpnext.stock.doctype.stock_entry import test_stock_entry
|
||||
from erpnext.stock.doctype.warehouse.test_warehouse import create_warehouse
|
||||
from erpnext.stock.utils import get_bin
|
||||
@@ -28,6 +31,7 @@ class TestWorkOrder(FrappeTestCase):
|
||||
def setUp(self):
|
||||
self.warehouse = "_Test Warehouse 2 - _TC"
|
||||
self.item = "_Test Item"
|
||||
prepare_data_for_backflush_based_on_materials_transferred()
|
||||
|
||||
def tearDown(self):
|
||||
frappe.db.rollback()
|
||||
@@ -527,6 +531,8 @@ class TestWorkOrder(FrappeTestCase):
|
||||
work_order.cancel()
|
||||
|
||||
def test_work_order_with_non_transfer_item(self):
|
||||
frappe.db.set_value("Manufacturing Settings", None, "backflush_raw_materials_based_on", "BOM")
|
||||
|
||||
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})
|
||||
@@ -1071,7 +1077,7 @@ class TestWorkOrder(FrappeTestCase):
|
||||
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":
|
||||
row.qty = 110
|
||||
row.qty = 120
|
||||
|
||||
sm.submit()
|
||||
cancel_stock_entry.append(sm.name)
|
||||
@@ -1079,21 +1085,21 @@ class TestWorkOrder(FrappeTestCase):
|
||||
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)
|
||||
self.assertEqual(row.get("qty"), 108)
|
||||
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)
|
||||
self.assertEqual(row.get("qty"), 6)
|
||||
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)
|
||||
self.assertEqual(row.get("qty"), 6)
|
||||
|
||||
cancel_stock_entry.reverse()
|
||||
for ste in cancel_stock_entry:
|
||||
@@ -1203,6 +1209,269 @@ class TestWorkOrder(FrappeTestCase):
|
||||
self.assertEqual(work_order.required_items[0].transferred_qty, 1)
|
||||
self.assertEqual(work_order.required_items[1].transferred_qty, 2)
|
||||
|
||||
def test_backflushed_batch_raw_materials_based_on_transferred(self):
|
||||
frappe.db.set_value(
|
||||
"Manufacturing Settings",
|
||||
None,
|
||||
"backflush_raw_materials_based_on",
|
||||
"Material Transferred for Manufacture",
|
||||
)
|
||||
|
||||
batch_item = "Test Batch MCC Keyboard"
|
||||
fg_item = "Test FG Item with Batch Raw Materials"
|
||||
|
||||
ste_doc = test_stock_entry.make_stock_entry(
|
||||
item_code=batch_item, target="Stores - _TC", qty=2, basic_rate=100, do_not_save=True
|
||||
)
|
||||
|
||||
ste_doc.append(
|
||||
"items",
|
||||
{
|
||||
"item_code": batch_item,
|
||||
"item_name": batch_item,
|
||||
"description": batch_item,
|
||||
"basic_rate": 100,
|
||||
"t_warehouse": "Stores - _TC",
|
||||
"qty": 2,
|
||||
"uom": "Nos",
|
||||
"stock_uom": "Nos",
|
||||
"conversion_factor": 1,
|
||||
},
|
||||
)
|
||||
|
||||
# Inward raw materials in Stores warehouse
|
||||
ste_doc.insert()
|
||||
ste_doc.submit()
|
||||
|
||||
batch_list = [row.batch_no for row in ste_doc.items]
|
||||
|
||||
wo_doc = make_wo_order_test_record(production_item=fg_item, qty=4)
|
||||
transferred_ste_doc = frappe.get_doc(
|
||||
make_stock_entry(wo_doc.name, "Material Transfer for Manufacture", 4)
|
||||
)
|
||||
|
||||
transferred_ste_doc.items[0].qty = 2
|
||||
transferred_ste_doc.items[0].batch_no = batch_list[0]
|
||||
|
||||
new_row = copy.deepcopy(transferred_ste_doc.items[0])
|
||||
new_row.name = ""
|
||||
new_row.batch_no = batch_list[1]
|
||||
|
||||
# Transferred two batches from Stores to WIP Warehouse
|
||||
transferred_ste_doc.append("items", new_row)
|
||||
transferred_ste_doc.submit()
|
||||
|
||||
# First Manufacture stock entry
|
||||
manufacture_ste_doc1 = frappe.get_doc(make_stock_entry(wo_doc.name, "Manufacture", 1))
|
||||
|
||||
# Batch no should be same as transferred Batch no
|
||||
self.assertEqual(manufacture_ste_doc1.items[0].batch_no, batch_list[0])
|
||||
self.assertEqual(manufacture_ste_doc1.items[0].qty, 1)
|
||||
|
||||
manufacture_ste_doc1.submit()
|
||||
|
||||
# Second Manufacture stock entry
|
||||
manufacture_ste_doc2 = frappe.get_doc(make_stock_entry(wo_doc.name, "Manufacture", 2))
|
||||
|
||||
# Batch no should be same as transferred Batch no
|
||||
self.assertEqual(manufacture_ste_doc2.items[0].batch_no, batch_list[0])
|
||||
self.assertEqual(manufacture_ste_doc2.items[0].qty, 1)
|
||||
self.assertEqual(manufacture_ste_doc2.items[1].batch_no, batch_list[1])
|
||||
self.assertEqual(manufacture_ste_doc2.items[1].qty, 1)
|
||||
|
||||
def test_backflushed_serial_no_raw_materials_based_on_transferred(self):
|
||||
frappe.db.set_value(
|
||||
"Manufacturing Settings",
|
||||
None,
|
||||
"backflush_raw_materials_based_on",
|
||||
"Material Transferred for Manufacture",
|
||||
)
|
||||
|
||||
sn_item = "Test Serial No BTT Headphone"
|
||||
fg_item = "Test FG Item with Serial No Raw Materials"
|
||||
|
||||
ste_doc = test_stock_entry.make_stock_entry(
|
||||
item_code=sn_item, target="Stores - _TC", qty=4, basic_rate=100, do_not_save=True
|
||||
)
|
||||
|
||||
# Inward raw materials in Stores warehouse
|
||||
ste_doc.submit()
|
||||
|
||||
serial_nos_list = sorted(get_serial_nos(ste_doc.items[0].serial_no))
|
||||
|
||||
wo_doc = make_wo_order_test_record(production_item=fg_item, qty=4)
|
||||
transferred_ste_doc = frappe.get_doc(
|
||||
make_stock_entry(wo_doc.name, "Material Transfer for Manufacture", 4)
|
||||
)
|
||||
|
||||
transferred_ste_doc.items[0].serial_no = "\n".join(serial_nos_list)
|
||||
transferred_ste_doc.submit()
|
||||
|
||||
# First Manufacture stock entry
|
||||
manufacture_ste_doc1 = frappe.get_doc(make_stock_entry(wo_doc.name, "Manufacture", 1))
|
||||
|
||||
# Serial nos should be same as transferred Serial nos
|
||||
self.assertEqual(get_serial_nos(manufacture_ste_doc1.items[0].serial_no), serial_nos_list[0:1])
|
||||
self.assertEqual(manufacture_ste_doc1.items[0].qty, 1)
|
||||
|
||||
manufacture_ste_doc1.submit()
|
||||
|
||||
# Second Manufacture stock entry
|
||||
manufacture_ste_doc2 = frappe.get_doc(make_stock_entry(wo_doc.name, "Manufacture", 2))
|
||||
|
||||
# Serial nos should be same as transferred Serial nos
|
||||
self.assertEqual(get_serial_nos(manufacture_ste_doc2.items[0].serial_no), serial_nos_list[1:3])
|
||||
self.assertEqual(manufacture_ste_doc2.items[0].qty, 2)
|
||||
|
||||
def test_backflushed_serial_no_batch_raw_materials_based_on_transferred(self):
|
||||
frappe.db.set_value(
|
||||
"Manufacturing Settings",
|
||||
None,
|
||||
"backflush_raw_materials_based_on",
|
||||
"Material Transferred for Manufacture",
|
||||
)
|
||||
|
||||
sn_batch_item = "Test Batch Serial No WebCam"
|
||||
fg_item = "Test FG Item with Serial & Batch No Raw Materials"
|
||||
|
||||
ste_doc = test_stock_entry.make_stock_entry(
|
||||
item_code=sn_batch_item, target="Stores - _TC", qty=2, basic_rate=100, do_not_save=True
|
||||
)
|
||||
|
||||
ste_doc.append(
|
||||
"items",
|
||||
{
|
||||
"item_code": sn_batch_item,
|
||||
"item_name": sn_batch_item,
|
||||
"description": sn_batch_item,
|
||||
"basic_rate": 100,
|
||||
"t_warehouse": "Stores - _TC",
|
||||
"qty": 2,
|
||||
"uom": "Nos",
|
||||
"stock_uom": "Nos",
|
||||
"conversion_factor": 1,
|
||||
},
|
||||
)
|
||||
|
||||
# Inward raw materials in Stores warehouse
|
||||
ste_doc.insert()
|
||||
ste_doc.submit()
|
||||
|
||||
batch_dict = {row.batch_no: get_serial_nos(row.serial_no) for row in ste_doc.items}
|
||||
batches = list(batch_dict.keys())
|
||||
|
||||
wo_doc = make_wo_order_test_record(production_item=fg_item, qty=4)
|
||||
transferred_ste_doc = frappe.get_doc(
|
||||
make_stock_entry(wo_doc.name, "Material Transfer for Manufacture", 4)
|
||||
)
|
||||
|
||||
transferred_ste_doc.items[0].qty = 2
|
||||
transferred_ste_doc.items[0].batch_no = batches[0]
|
||||
transferred_ste_doc.items[0].serial_no = "\n".join(batch_dict.get(batches[0]))
|
||||
|
||||
new_row = copy.deepcopy(transferred_ste_doc.items[0])
|
||||
new_row.name = ""
|
||||
new_row.batch_no = batches[1]
|
||||
new_row.serial_no = "\n".join(batch_dict.get(batches[1]))
|
||||
|
||||
# Transferred two batches from Stores to WIP Warehouse
|
||||
transferred_ste_doc.append("items", new_row)
|
||||
transferred_ste_doc.submit()
|
||||
|
||||
# First Manufacture stock entry
|
||||
manufacture_ste_doc1 = frappe.get_doc(make_stock_entry(wo_doc.name, "Manufacture", 1))
|
||||
|
||||
# Batch no & Serial Nos should be same as transferred Batch no & Serial Nos
|
||||
batch_no = manufacture_ste_doc1.items[0].batch_no
|
||||
self.assertEqual(
|
||||
get_serial_nos(manufacture_ste_doc1.items[0].serial_no)[0], batch_dict.get(batch_no)[0]
|
||||
)
|
||||
self.assertEqual(manufacture_ste_doc1.items[0].qty, 1)
|
||||
|
||||
manufacture_ste_doc1.submit()
|
||||
|
||||
# Second Manufacture stock entry
|
||||
manufacture_ste_doc2 = frappe.get_doc(make_stock_entry(wo_doc.name, "Manufacture", 2))
|
||||
|
||||
# Batch no & Serial Nos should be same as transferred Batch no & Serial Nos
|
||||
batch_no = manufacture_ste_doc2.items[0].batch_no
|
||||
self.assertEqual(
|
||||
get_serial_nos(manufacture_ste_doc2.items[0].serial_no)[0], batch_dict.get(batch_no)[1]
|
||||
)
|
||||
self.assertEqual(manufacture_ste_doc2.items[0].qty, 1)
|
||||
|
||||
batch_no = manufacture_ste_doc2.items[1].batch_no
|
||||
self.assertEqual(
|
||||
get_serial_nos(manufacture_ste_doc2.items[1].serial_no)[0], batch_dict.get(batch_no)[0]
|
||||
)
|
||||
self.assertEqual(manufacture_ste_doc2.items[1].qty, 1)
|
||||
|
||||
|
||||
def prepare_data_for_backflush_based_on_materials_transferred():
|
||||
batch_item_doc = make_item(
|
||||
"Test Batch MCC Keyboard",
|
||||
{
|
||||
"is_stock_item": 1,
|
||||
"has_batch_no": 1,
|
||||
"create_new_batch": 1,
|
||||
"batch_number_series": "TBMK.#####",
|
||||
"valuation_rate": 100,
|
||||
"stock_uom": "Nos",
|
||||
},
|
||||
)
|
||||
|
||||
item = make_item(
|
||||
"Test FG Item with Batch Raw Materials",
|
||||
{
|
||||
"is_stock_item": 1,
|
||||
},
|
||||
)
|
||||
|
||||
make_bom(item=item.name, source_warehouse="Stores - _TC", raw_materials=[batch_item_doc.name])
|
||||
|
||||
sn_item_doc = make_item(
|
||||
"Test Serial No BTT Headphone",
|
||||
{
|
||||
"is_stock_item": 1,
|
||||
"has_serial_no": 1,
|
||||
"serial_no_series": "TSBH.#####",
|
||||
"valuation_rate": 100,
|
||||
"stock_uom": "Nos",
|
||||
},
|
||||
)
|
||||
|
||||
item = make_item(
|
||||
"Test FG Item with Serial No Raw Materials",
|
||||
{
|
||||
"is_stock_item": 1,
|
||||
},
|
||||
)
|
||||
|
||||
make_bom(item=item.name, source_warehouse="Stores - _TC", raw_materials=[sn_item_doc.name])
|
||||
|
||||
sn_batch_item_doc = make_item(
|
||||
"Test Batch Serial No WebCam",
|
||||
{
|
||||
"is_stock_item": 1,
|
||||
"has_batch_no": 1,
|
||||
"create_new_batch": 1,
|
||||
"batch_number_series": "TBSW.#####",
|
||||
"has_serial_no": 1,
|
||||
"serial_no_series": "TBSWC.#####",
|
||||
"valuation_rate": 100,
|
||||
"stock_uom": "Nos",
|
||||
},
|
||||
)
|
||||
|
||||
item = make_item(
|
||||
"Test FG Item with Serial & Batch No Raw Materials",
|
||||
{
|
||||
"is_stock_item": 1,
|
||||
},
|
||||
)
|
||||
|
||||
make_bom(item=item.name, source_warehouse="Stores - _TC", raw_materials=[sn_batch_item_doc.name])
|
||||
|
||||
|
||||
def update_job_card(job_card, jc_qty=None):
|
||||
employee = frappe.db.get_value("Employee", {"status": "Active"}, "name")
|
||||
|
||||
Reference in New Issue
Block a user