Merge branch 'develop' into fix-pos-issues-again

This commit is contained in:
Saqib Ansari
2022-03-25 10:52:08 +05:30
committed by GitHub
11 changed files with 140 additions and 27 deletions

View File

@@ -5,6 +5,12 @@ frappe.ui.form.on('Website Item', {
onload: function(frm) { onload: function(frm) {
// should never check Private // should never check Private
frm.fields_dict["website_image"].df.is_private = 0; frm.fields_dict["website_image"].df.is_private = 0;
frm.set_query("website_warehouse", () => {
return {
filters: {"is_group": 0}
};
});
}, },
image: function() { image: function() {

View File

@@ -2,6 +2,13 @@
// For license information, please see license.txt // For license information, please see license.txt
frappe.ui.form.on('Production Plan', { frappe.ui.form.on('Production Plan', {
before_save: function(frm) {
// preserve temporary names on production plan item to re-link sub-assembly items
frm.doc.po_items.forEach(item => {
item.temporary_name = item.name;
});
},
setup: function(frm) { setup: function(frm) {
frm.custom_make_buttons = { frm.custom_make_buttons = {
'Work Order': 'Work Order / Subcontract PO', 'Work Order': 'Work Order / Subcontract PO',

View File

@@ -32,6 +32,7 @@ class ProductionPlan(Document):
self.set_pending_qty_in_row_without_reference() self.set_pending_qty_in_row_without_reference()
self.calculate_total_planned_qty() self.calculate_total_planned_qty()
self.set_status() self.set_status()
self._rename_temporary_references()
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)."
@@ -57,6 +58,18 @@ class ProductionPlan(Document):
if not flt(d.planned_qty): if not flt(d.planned_qty):
frappe.throw(_("Please enter Planned Qty for Item {0} at row {1}").format(d.item_code, d.idx)) 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.
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}
for sub_assy in self.sub_assembly_items:
if sub_assy.production_plan_item not in actual_names:
sub_assy.production_plan_item = new_name_map.get(sub_assy.production_plan_item)
@frappe.whitelist() @frappe.whitelist()
def get_open_sales_orders(self): 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"""

View File

@@ -667,6 +667,39 @@ class TestProductionPlan(FrappeTestCase):
wo_doc.submit() wo_doc.submit()
self.assertEqual(wo_doc.qty, 0.55) self.assertEqual(wo_doc.qty, 0.55)
def test_temporary_name_relinking(self):
pp = frappe.new_doc("Production Plan")
# 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
})
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._rename_temporary_references()
self.assertIsNone(pp.sub_assembly_items[-1].production_plan_item)
pp.sub_assembly_items.pop()
# reattempting on same doc shouldn't change anything
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)
def create_production_plan(**args): def create_production_plan(**args):
""" """
sales_order (obj): Sales Order Doc Object sales_order (obj): Sales Order Doc Object

View File

@@ -27,7 +27,8 @@
"material_request", "material_request",
"material_request_item", "material_request_item",
"product_bundle_item", "product_bundle_item",
"item_reference" "item_reference",
"temporary_name"
], ],
"fields": [ "fields": [
{ {
@@ -204,17 +205,25 @@
"fieldtype": "Data", "fieldtype": "Data",
"hidden": 1, "hidden": 1,
"label": "Item Reference" "label": "Item Reference"
},
{
"fieldname": "temporary_name",
"fieldtype": "Data",
"hidden": 1,
"label": "temporary name"
} }
], ],
"idx": 1, "idx": 1,
"istable": 1, "istable": 1,
"links": [], "links": [],
"modified": "2021-06-28 18:31:06.822168", "modified": "2022-03-24 04:54:09.940224",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "Manufacturing", "module": "Manufacturing",
"name": "Production Plan Item", "name": "Production Plan Item",
"naming_rule": "Random",
"owner": "Administrator", "owner": "Administrator",
"permissions": [], "permissions": [],
"sort_field": "modified", "sort_field": "modified",
"sort_order": "ASC" "sort_order": "ASC",
"states": []
} }

View File

@@ -457,7 +457,8 @@ class WorkOrder(Document):
mr_obj.update_requested_qty([self.material_request_item]) mr_obj.update_requested_qty([self.material_request_item])
def update_ordered_qty(self): def update_ordered_qty(self):
if self.production_plan and self.production_plan_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 qty = frappe.get_value("Production Plan Item", self.production_plan_item, "ordered_qty") or 0.0
if self.docstatus == 1: if self.docstatus == 1:
@@ -644,9 +645,13 @@ class WorkOrder(Document):
if not self.qty > 0: if not self.qty > 0:
frappe.throw(_("Quantity to Manufacture must be greater than 0.")) frappe.throw(_("Quantity to Manufacture must be greater than 0."))
if self.production_plan and self.production_plan_item: 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) 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", allowance_qty = flt(frappe.db.get_single_value("Manufacturing Settings",
"overproduction_percentage_for_work_order"))/100 * qty_dict.get("planned_qty", 0) "overproduction_percentage_for_work_order"))/100 * qty_dict.get("planned_qty", 0)

View File

@@ -360,5 +360,5 @@ erpnext.patches.v14_0.update_batch_valuation_flag
erpnext.patches.v14_0.delete_non_profit_doctypes erpnext.patches.v14_0.delete_non_profit_doctypes
erpnext.patches.v14_0.update_employee_advance_status erpnext.patches.v14_0.update_employee_advance_status
erpnext.patches.v13_0.add_cost_center_in_loans erpnext.patches.v13_0.add_cost_center_in_loans
erpnext.patches.v13_0.remove_unknown_links_to_prod_plan_items erpnext.patches.v13_0.set_return_against_in_pos_invoice_references
erpnext.patches.v13_0.set_return_against_in_pos_invoice_references erpnext.patches.v13_0.remove_unknown_links_to_prod_plan_items # 24-03-2022

View File

@@ -40,18 +40,15 @@ def get_data(item_code=None, warehouse=None, item_group=None,
filters=filters, filters=filters,
order_by=sort_by + ' ' + sort_order, order_by=sort_by + ' ' + sort_order,
limit_start=start, limit_start=start,
limit_page_length='21') limit_page_length=21)
precision = cint(frappe.db.get_single_value("System Settings", "float_precision")) precision = cint(frappe.db.get_single_value("System Settings", "float_precision"))
for item in items: for item in items:
item.update({ item.update({
'item_name': frappe.get_cached_value( 'item_name': frappe.get_cached_value("Item", item.item_code, 'item_name'),
"Item", item.item_code, 'item_name'), 'disable_quick_entry': frappe.get_cached_value( "Item", item.item_code, 'has_batch_no')
'disable_quick_entry': frappe.get_cached_value( or frappe.get_cached_value( "Item", item.item_code, 'has_serial_no'),
"Item", item.item_code, 'has_batch_no')
or frappe.get_cached_value(
"Item", item.item_code, 'has_serial_no'),
'projected_qty': flt(item.projected_qty, precision), 'projected_qty': flt(item.projected_qty, precision),
'reserved_qty': flt(item.reserved_qty, precision), 'reserved_qty': flt(item.reserved_qty, precision),
'reserved_qty_for_production': flt(item.reserved_qty_for_production, precision), 'reserved_qty_for_production': flt(item.reserved_qty_for_production, precision),

View File

@@ -685,6 +685,12 @@ class TestItem(FrappeTestCase):
# standalone return # standalone return
make_purchase_receipt(is_return=True, qty=-1, **typical_args) make_purchase_receipt(is_return=True, qty=-1, **typical_args)
def test_item_dashboard(self):
from erpnext.stock.dashboard.item_dashboard import get_data
self.assertTrue(get_data(item_code="_Test Item"))
self.assertTrue(get_data(warehouse="_Test Warehouse - _TC"))
self.assertTrue(get_data(item_group="All Item Groups"))
def set_item_variant_settings(fields): def set_item_variant_settings(fields):

View File

@@ -396,6 +396,21 @@ class TestPutawayRule(FrappeTestCase):
rule_1.delete() rule_1.delete()
rule_2.delete() rule_2.delete()
def test_warehouse_capacity_dashbord(self):
from erpnext.stock.dashboard.warehouse_capacity_dashboard import get_data
item = "_Rice"
rule = create_putaway_rule(item_code=item, warehouse=self.warehouse_1, capacity=500,
uom="Kg")
capacities = get_data(warehouse=self.warehouse_1)
for capacity in capacities:
if capacity.item_code == item and capacity.warehouse == self.warehouse_1:
self.assertEqual(capacity.stock_capacity, 500)
get_data(warehouse=self.warehouse_1)
rule.delete()
def create_putaway_rule(**args): def create_putaway_rule(**args):
args = frappe._dict(args) args = frappe._dict(args)
putaway = frappe.new_doc("Putaway Rule") putaway = frappe.new_doc("Putaway Rule")

View File

@@ -1,13 +1,14 @@
""" dumb test to check all function calls on known form loads """ """ smoak tests to check basic functionality calls on known form loads."""
import unittest
import frappe import frappe
from frappe.desk.form.load import getdoc from frappe.desk.form.load import getdoc
from frappe.tests.utils import FrappeTestCase, change_settings
from frappe.www.printview import get_html_and_style
class TestFormLoads(unittest.TestCase): class TestFormLoads(FrappeTestCase):
@change_settings("Print Settings", {"allow_print_for_cancelled": 1})
def test_load(self): def test_load(self):
erpnext_modules = frappe.get_all("Module Def", filters={"app_name": "erpnext"}, pluck="name") erpnext_modules = frappe.get_all("Module Def", filters={"app_name": "erpnext"}, pluck="name")
doctypes = frappe.get_all("DocType", {"istable": 0, "issingle": 0, "is_virtual": 0, "module": ("in", erpnext_modules)}, pluck="name") doctypes = frappe.get_all("DocType", {"istable": 0, "issingle": 0, "is_virtual": 0, "module": ("in", erpnext_modules)}, pluck="name")
@@ -17,14 +18,35 @@ class TestFormLoads(unittest.TestCase):
if not last_doc: if not last_doc:
continue continue
with self.subTest(msg=f"Loading {doctype} - {last_doc}", doctype=doctype, last_doc=last_doc): with self.subTest(msg=f"Loading {doctype} - {last_doc}", doctype=doctype, last_doc=last_doc):
try: self.assertFormLoad(doctype, last_doc)
# reset previous response self.assertDocPrint(doctype, last_doc)
frappe.response = frappe._dict({"docs":[]})
frappe.response.docinfo = None
getdoc(doctype, last_doc) def assertFormLoad(self, doctype, docname):
except Exception as e: # reset previous response
self.fail(f"Failed to load {doctype} - {last_doc}: {e}") frappe.response = frappe._dict({"docs":[]})
frappe.response.docinfo = None
self.assertTrue(frappe.response.docs, msg=f"expected document in reponse, found: {frappe.response.docs}") try:
self.assertTrue(frappe.response.docinfo, msg=f"expected docinfo in reponse, found: {frappe.response.docinfo}") getdoc(doctype, docname)
except Exception as e:
self.fail(f"Failed to load {doctype}-{docname}: {e}")
self.assertTrue(frappe.response.docs, msg=f"expected document in reponse, found: {frappe.response.docs}")
self.assertTrue(frappe.response.docinfo, msg=f"expected docinfo in reponse, found: {frappe.response.docinfo}")
def assertDocPrint(self, doctype, docname):
doc = frappe.get_doc(doctype, docname)
doc.set("__onload", frappe._dict())
doc.run_method("onload")
messages_before = frappe.get_message_log()
ret = get_html_and_style(doc=doc.as_json(), print_format="Standard", no_letterhead=1)
messages_after = frappe.get_message_log()
if len(messages_after) > len(messages_before):
new_messages = messages_after[len(messages_before):]
self.fail("Print view showing error/warnings: \n"
+ "\n".join(str(msg) for msg in new_messages))
# html should exist
self.assertTrue(bool(ret["html"]))