From e8bc4f12a734ff286a008e6b31dffa9c6c946365 Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Mon, 24 Jan 2022 15:59:38 +0530 Subject: [PATCH 01/14] fix:Add shipping charges to taxes only if applicable --- erpnext/accounts/doctype/shipping_rule/shipping_rule.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/erpnext/accounts/doctype/shipping_rule/shipping_rule.py b/erpnext/accounts/doctype/shipping_rule/shipping_rule.py index 7e5129911e4..792e7d21a78 100644 --- a/erpnext/accounts/doctype/shipping_rule/shipping_rule.py +++ b/erpnext/accounts/doctype/shipping_rule/shipping_rule.py @@ -71,7 +71,8 @@ class ShippingRule(Document): if doc.currency != doc.company_currency: shipping_amount = flt(shipping_amount / doc.conversion_rate, 2) - self.add_shipping_rule_to_tax_table(doc, shipping_amount) + if shipping_amount: + self.add_shipping_rule_to_tax_table(doc, shipping_amount) def get_shipping_amount_from_rules(self, value): for condition in self.get("conditions"): From 177a209f5060abdd1d55b90789203b7c4439560f Mon Sep 17 00:00:00 2001 From: Rucha Mahabal Date: Tue, 25 Jan 2022 16:49:53 +0530 Subject: [PATCH 02/14] fix(Employee): set user image and validate user id only if user data is found (#29452) --- erpnext/hr/doctype/employee/employee.py | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/erpnext/hr/doctype/employee/employee.py b/erpnext/hr/doctype/employee/employee.py index 88e5ca9d4c5..8a2950696af 100755 --- a/erpnext/hr/doctype/employee/employee.py +++ b/erpnext/hr/doctype/employee/employee.py @@ -68,12 +68,18 @@ class Employee(NestedSet): self.employee_name = ' '.join(filter(lambda x: x, [self.first_name, self.middle_name, self.last_name])) def validate_user_details(self): - data = frappe.db.get_value('User', - self.user_id, ['enabled', 'user_image'], as_dict=1) - if data.get("user_image") and self.image == '': - self.image = data.get("user_image") - self.validate_for_enabled_user_id(data.get("enabled", 0)) - self.validate_duplicate_user_id() + if self.user_id: + data = frappe.db.get_value("User", + self.user_id, ["enabled", "user_image"], as_dict=1) + + if not data: + self.user_id = None + return + + if data.get("user_image") and self.image == "": + self.image = data.get("user_image") + self.validate_for_enabled_user_id(data.get("enabled", 0)) + self.validate_duplicate_user_id() def update_nsm_model(self): frappe.utils.nestedset.update_nsm(self) From 58fd7044b5da8d78af55c79b5b03ecbf4123198b Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Wed, 26 Jan 2022 12:28:26 +0530 Subject: [PATCH 03/14] fix: remove all stock UOM conversion when changing stock UOM (#29463) (cherry picked from commit eb8d08b411b66797d9f3892e079c49828335d34a) Co-authored-by: Ankush Menat --- erpnext/stock/doctype/item/item.py | 22 ++++++++++++---------- erpnext/stock/doctype/item/test_item.py | 10 ++++++++++ 2 files changed, 22 insertions(+), 10 deletions(-) diff --git a/erpnext/stock/doctype/item/item.py b/erpnext/stock/doctype/item/item.py index e2d2eeaeb0d..557e07bf0a2 100644 --- a/erpnext/stock/doctype/item/item.py +++ b/erpnext/stock/doctype/item/item.py @@ -219,18 +219,20 @@ class Item(Document): self.item_code)) def add_default_uom_in_conversion_factor_table(self): - uom_conv_list = [d.uom for d in self.get("uoms")] - if self.stock_uom not in uom_conv_list: - ch = self.append('uoms', {}) - ch.uom = self.stock_uom - ch.conversion_factor = 1 + if not self.is_new() and self.has_value_changed("stock_uom"): + self.uoms = [] + frappe.msgprint( + _("Successfully changed Stock UOM, please redefine conversion factors for new UOM."), + alert=True, + ) - to_remove = [] - for d in self.get("uoms"): - if d.conversion_factor == 1 and d.uom != self.stock_uom: - to_remove.append(d) + uoms_list = [d.uom for d in self.get("uoms")] - [self.remove(d) for d in to_remove] + if self.stock_uom not in uoms_list: + self.append("uoms", { + "uom": self.stock_uom, + "conversion_factor": 1 + }) def update_website_item(self): """Update Website Item if change in Item impacts it.""" diff --git a/erpnext/stock/doctype/item/test_item.py b/erpnext/stock/doctype/item/test_item.py index e191f0a3293..150cead465b 100644 --- a/erpnext/stock/doctype/item/test_item.py +++ b/erpnext/stock/doctype/item/test_item.py @@ -573,6 +573,16 @@ class TestItem(ERPNextTestCase): except frappe.ValidationError as e: self.fail(f"UoM change not allowed even though no SLE / BIN with positive qty exists: {e}") + def test_erasure_of_old_conversions(self): + item = create_item("_item change uom") + item.stock_uom = "Gram" + item.append("uoms", frappe._dict(uom="Box", conversion_factor=2)) + item.save() + item.reload() + item.stock_uom = "Nos" + item.save() + self.assertEqual(len(item.uoms), 1) + def test_validate_stock_item(self): self.assertRaises(frappe.ValidationError, validate_is_stock_item, "_Test Non Stock Item") From d2b61bbe8a868bf634475dcb785103f64dccf348 Mon Sep 17 00:00:00 2001 From: Akash Krishna Date: Wed, 26 Jan 2022 14:13:40 +0530 Subject: [PATCH 04/14] fix(healthcare): populate current practitioner in appointment availability popup (#29405) * fix: Remove two-way patient fetch from inpatient_record (Lab Test, Vital Signs, Sample Collection) * fix: Patient Appointment - practitioner field gettng cleared on Check Availability dialog, added Practitioner name in the dialog * fix: Patient Appointment - set practitioner in check availability dialog load Co-authored-by: Chillar Anand --- erpnext/healthcare/doctype/lab_test/lab_test.json | 3 +-- .../patient_appointment/patient_appointment.js | 13 +++++++++---- .../patient_appointment/patient_appointment.py | 3 ++- .../sample_collection/sample_collection.json | 3 +-- .../healthcare/doctype/vital_signs/vital_signs.json | 3 +-- 5 files changed, 14 insertions(+), 11 deletions(-) diff --git a/erpnext/healthcare/doctype/lab_test/lab_test.json b/erpnext/healthcare/doctype/lab_test/lab_test.json index ac61fea3ad7..cdf27aa06b3 100644 --- a/erpnext/healthcare/doctype/lab_test/lab_test.json +++ b/erpnext/healthcare/doctype/lab_test/lab_test.json @@ -99,7 +99,6 @@ "search_index": 1 }, { - "fetch_from": "inpatient_record.patient", "fieldname": "patient", "fieldtype": "Link", "ignore_user_permissions": 1, @@ -559,7 +558,7 @@ ], "is_submittable": 1, "links": [], - "modified": "2020-11-30 11:04:17.195848", + "modified": "2022-01-20 12:37:07.943153", "modified_by": "Administrator", "module": "Healthcare", "name": "Lab Test", diff --git a/erpnext/healthcare/doctype/patient_appointment/patient_appointment.js b/erpnext/healthcare/doctype/patient_appointment/patient_appointment.js index bba001c9c0e..ba862783caa 100644 --- a/erpnext/healthcare/doctype/patient_appointment/patient_appointment.js +++ b/erpnext/healthcare/doctype/patient_appointment/patient_appointment.js @@ -337,9 +337,13 @@ let check_and_set_availability = function(frm) { }); d.fields_dict['department'].df.onchange = () => { - d.set_values({ - 'practitioner': '' - }); + if (d.get_value('department') == frm.doc.department) { + d.set_value('practitioner', frm.doc.practitioner); + } else { + d.set_value('practitioner', ''); + d.fields_dict.available_slots.html(''); + d.get_primary_btn().attr('disabled', true); + } let department = d.get_value('department'); if (department) { d.fields_dict.practitioner.get_query = function() { @@ -426,7 +430,8 @@ let check_and_set_availability = function(frm) { slot_details.forEach((slot_info) => { slot_html += `
- ${__('Practitioner Schedule:')} ${slot_info.slot_name}
+ ${slot_info.practitioner_name}
+ ${__('Schedule:')} ${slot_info.slot_name}
${__('Service Unit:')} ${slot_info.service_unit} `; if (slot_info.service_unit_capacity) { diff --git a/erpnext/healthcare/doctype/patient_appointment/patient_appointment.py b/erpnext/healthcare/doctype/patient_appointment/patient_appointment.py index 1e4608f84e0..c4f253a062f 100755 --- a/erpnext/healthcare/doctype/patient_appointment/patient_appointment.py +++ b/erpnext/healthcare/doctype/patient_appointment/patient_appointment.py @@ -388,7 +388,8 @@ def get_available_slots(practitioner_doc, date): fields=['name', 'appointment_time', 'duration', 'status']) slot_details.append({'slot_name': slot_name, 'service_unit': schedule_entry.service_unit, 'avail_slot': available_slots, - 'appointments': appointments, 'allow_overlap': allow_overlap, 'service_unit_capacity': service_unit_capacity}) + 'appointments': appointments, 'allow_overlap': allow_overlap, 'service_unit_capacity': service_unit_capacity, + 'practitioner_name': practitioner_doc.practitioner_name}) return slot_details diff --git a/erpnext/healthcare/doctype/sample_collection/sample_collection.json b/erpnext/healthcare/doctype/sample_collection/sample_collection.json index 83383e34457..f8525f7e14b 100644 --- a/erpnext/healthcare/doctype/sample_collection/sample_collection.json +++ b/erpnext/healthcare/doctype/sample_collection/sample_collection.json @@ -66,7 +66,6 @@ "search_index": 1 }, { - "fetch_from": "inpatient_record.patient", "fieldname": "patient", "fieldtype": "Link", "hide_days": 1, @@ -224,7 +223,7 @@ ], "is_submittable": 1, "links": [], - "modified": "2020-07-30 16:53:13.076104", + "modified": "2022-01-20 12:38:55.382621", "modified_by": "Administrator", "module": "Healthcare", "name": "Sample Collection", diff --git a/erpnext/healthcare/doctype/vital_signs/vital_signs.json b/erpnext/healthcare/doctype/vital_signs/vital_signs.json index 15ab5047bc4..a945032c7e0 100644 --- a/erpnext/healthcare/doctype/vital_signs/vital_signs.json +++ b/erpnext/healthcare/doctype/vital_signs/vital_signs.json @@ -51,7 +51,6 @@ "read_only": 1 }, { - "fetch_from": "inpatient_record.patient", "fieldname": "patient", "fieldtype": "Link", "ignore_user_permissions": 1, @@ -259,7 +258,7 @@ ], "is_submittable": 1, "links": [], - "modified": "2020-05-17 22:23:24.632286", + "modified": "2022-01-20 12:30:07.515185", "modified_by": "Administrator", "module": "Healthcare", "name": "Vital Signs", From 352c585ef5161af7a9a26b77d23dc8afe02a5c73 Mon Sep 17 00:00:00 2001 From: Suraj Shetty Date: Thu, 27 Jan 2022 11:11:37 +0530 Subject: [PATCH 05/14] fix: Further sort sales_order_analysis to get consistent response Since the data was sorted by `transaction_date`, report sorting order used to be inconsistent everytime if there were lots of records with same transaction date. This used to create problems while exporting report as "Excel" with filters. (cherry picked from commit 29c7947db05106cb87a0dfe0905dc3abc8c63cd4) --- .../selling/report/sales_order_analysis/sales_order_analysis.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/selling/report/sales_order_analysis/sales_order_analysis.py b/erpnext/selling/report/sales_order_analysis/sales_order_analysis.py index c530d30c0c2..001095588ba 100644 --- a/erpnext/selling/report/sales_order_analysis/sales_order_analysis.py +++ b/erpnext/selling/report/sales_order_analysis/sales_order_analysis.py @@ -80,7 +80,7 @@ def get_data(conditions, filters): and so.docstatus = 1 {conditions} GROUP BY soi.name - ORDER BY so.transaction_date ASC + ORDER BY so.transaction_date ASC, soi.item_code ASC """.format(conditions=conditions), filters, as_dict=1) return data From 67689e737425ff9a614c1b5ea872449e903853b1 Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Thu, 27 Jan 2022 20:24:51 +0530 Subject: [PATCH 06/14] fix: stock ledger rendering glitch (#29482) undefined Issue (cherry picked from commit f38004219518e901eb736780b181c19fe88ce823) Co-authored-by: smehata <53169014+smehata@users.noreply.github.com> (cherry picked from commit 0dafa8a12a0c0745bbabceb457332075fd6db9f9) Co-authored-by: mergify[bot] <37929162+mergify[bot]@users.noreply.github.com> --- erpnext/stock/report/stock_ledger/stock_ledger.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/erpnext/stock/report/stock_ledger/stock_ledger.js b/erpnext/stock/report/stock_ledger/stock_ledger.js index fe2417bba7e..ef7c2cc7d9e 100644 --- a/erpnext/stock/report/stock_ledger/stock_ledger.js +++ b/erpnext/stock/report/stock_ledger/stock_ledger.js @@ -86,10 +86,10 @@ frappe.query_reports["Stock Ledger"] = { ], "formatter": function (value, row, column, data, default_formatter) { value = default_formatter(value, row, column, data); - if (column.fieldname == "out_qty" && data.out_qty < 0) { + if (column.fieldname == "out_qty" && data && data.out_qty < 0) { value = "" + value + ""; } - else if (column.fieldname == "in_qty" && data.in_qty > 0) { + else if (column.fieldname == "in_qty" && data && data.in_qty > 0) { value = "" + value + ""; } From 03bd68654b4f22a78c46ff6a7653e99b935925d6 Mon Sep 17 00:00:00 2001 From: Ankush Menat Date: Fri, 28 Jan 2022 13:17:43 +0530 Subject: [PATCH 07/14] chore: disable annoying flake8 formatting for QB (#29488) [skip ci] (cherry picked from commit 251576a6bb38c9b0f0793330e10e256fe7898e5c) --- .flake8 | 1 + 1 file changed, 1 insertion(+) diff --git a/.flake8 b/.flake8 index 56c9b9a3699..5735456ae7d 100644 --- a/.flake8 +++ b/.flake8 @@ -28,6 +28,7 @@ ignore = B007, B950, W191, + E124, # closing bracket, irritating while writing QB code max-line-length = 200 exclude=.github/helper/semgrep_rules From 4e14d28564dd052fef1cc1279aa6ddf2ebac3db9 Mon Sep 17 00:00:00 2001 From: Ankush Menat Date: Fri, 28 Jan 2022 11:59:18 +0530 Subject: [PATCH 08/14] fix: ensure correct-ish transfer against (cherry picked from commit 86efa0dbfd45f21d72d047ef77ca0cb8ae2b8d94) --- erpnext/manufacturing/doctype/bom/bom.py | 7 +++++ erpnext/manufacturing/doctype/bom/test_bom.py | 29 +++++++++++++++++++ .../doctype/work_order/work_order.py | 12 ++++++++ 3 files changed, 48 insertions(+) diff --git a/erpnext/manufacturing/doctype/bom/bom.py b/erpnext/manufacturing/doctype/bom/bom.py index f75038eab34..0b5207ef859 100644 --- a/erpnext/manufacturing/doctype/bom/bom.py +++ b/erpnext/manufacturing/doctype/bom/bom.py @@ -149,6 +149,7 @@ class BOM(WebsiteGenerator): self.set_bom_material_details() self.set_bom_scrap_items_detail() self.validate_materials() + self.validate_transfer_against() self.set_routing_operations() self.validate_operations() self.calculate_cost() @@ -682,6 +683,12 @@ class BOM(WebsiteGenerator): if act_pbom and act_pbom[0][0]: frappe.throw(_("Cannot deactivate or cancel BOM as it is linked with other BOMs")) + def validate_transfer_against(self): + if not self.with_operations: + self.transfer_material_against = "Work Order" + if not self.transfer_material_against and not self.is_new(): + frappe.throw(_("Setting {} is required").format(self.meta.get_label("transfer_material_against")), title=_("Missing value")) + def set_routing_operations(self): if self.routing and self.with_operations and not self.operations: self.get_routing() diff --git a/erpnext/manufacturing/doctype/bom/test_bom.py b/erpnext/manufacturing/doctype/bom/test_bom.py index 2f9804d1d4a..0bb0c3948ef 100644 --- a/erpnext/manufacturing/doctype/bom/test_bom.py +++ b/erpnext/manufacturing/doctype/bom/test_bom.py @@ -356,6 +356,35 @@ class TestBOM(ERPNextTestCase): self.assertTrue(0 < len(filtered) <= 3, msg="Item filtering showing excessive results") + def test_valid_transfer_defaults(self): + bom_with_op = frappe.db.get_value("BOM", {"item": "_Test FG Item 2", "with_operations": 1, "is_active": 1}) + bom = frappe.copy_doc(frappe.get_doc("BOM", bom_with_op), ignore_no_copy=False) + + # test defaults + bom.docstatus = 0 + bom.transfer_material_against = None + bom.insert() + self.assertEqual(bom.transfer_material_against, "Work Order") + + bom.reload() + bom.transfer_material_against = None + with self.assertRaises(frappe.ValidationError): + bom.save() + bom.reload() + + # test saner default + bom.transfer_material_against = "Job Card" + bom.with_operations = 0 + bom.save() + self.assertEqual(bom.transfer_material_against, "Work Order") + + # test no value on existing doc + bom.transfer_material_against = None + bom.with_operations = 0 + bom.save() + self.assertEqual(bom.transfer_material_against, "Work Order") + + 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}) diff --git a/erpnext/manufacturing/doctype/work_order/work_order.py b/erpnext/manufacturing/doctype/work_order/work_order.py index 0090f4d04ee..23fb9697cdb 100644 --- a/erpnext/manufacturing/doctype/work_order/work_order.py +++ b/erpnext/manufacturing/doctype/work_order/work_order.py @@ -65,6 +65,7 @@ class WorkOrder(Document): self.validate_warehouse_belongs_to_company() self.calculate_operating_cost() self.validate_qty() + self.validate_transfer_against() self.validate_operation_time() self.status = self.get_status() @@ -72,6 +73,7 @@ class WorkOrder(Document): self.set_required_items(reset_only_qty = len(self.get("required_items"))) + def validate_sales_order(self): if self.sales_order: self.check_sales_order_on_hold_or_close() @@ -621,6 +623,16 @@ class WorkOrder(Document): if not self.qty > 0: frappe.throw(_("Quantity to Manufacture must be greater than 0.")) + def validate_transfer_against(self): + if not self.docstatus == 1: + # let user configure operations until they're ready to submit + return + if not self.operations: + self.transfer_material_against = "Work Order" + if not self.transfer_material_against: + frappe.throw(_("Setting {} is required").format(self.meta.get_label("transfer_material_against")), title=_("Missing value")) + + def validate_operation_time(self): for d in self.operations: if not d.time_in_mins > 0: From de01f0f3641e03d74e951530f26cee791507484f Mon Sep 17 00:00:00 2001 From: Ankush Menat Date: Fri, 28 Jan 2022 13:00:15 +0530 Subject: [PATCH 09/14] fix(patch): update BOMs to have correct transfer_material_against (cherry picked from commit e4c220053f9732255a1af2361b0419230ade8ae6) --- erpnext/patches.txt | 1 + erpnext/patches/v13_0/update_sane_transfer_against.py | 11 +++++++++++ 2 files changed, 12 insertions(+) create mode 100644 erpnext/patches/v13_0/update_sane_transfer_against.py diff --git a/erpnext/patches.txt b/erpnext/patches.txt index 30b055f919c..704b6696db1 100644 --- a/erpnext/patches.txt +++ b/erpnext/patches.txt @@ -345,3 +345,4 @@ erpnext.patches.v13_0.agriculture_deprecation_warning erpnext.patches.v13_0.update_maintenance_schedule_field_in_visit erpnext.patches.v13_0.hospitality_deprecation_warning erpnext.patches.v13_0.delete_bank_reconciliation_detail +erpnext.patches.v13_0.update_sane_transfer_against diff --git a/erpnext/patches/v13_0/update_sane_transfer_against.py b/erpnext/patches/v13_0/update_sane_transfer_against.py new file mode 100644 index 00000000000..a163d385843 --- /dev/null +++ b/erpnext/patches/v13_0/update_sane_transfer_against.py @@ -0,0 +1,11 @@ +import frappe + + +def execute(): + bom = frappe.qb.DocType("BOM") + + (frappe.qb + .update(bom) + .set(bom.transfer_material_against, "Work Order") + .where(bom.with_operations == 0) + ).run() From 5dd443eeb0dac242c4b09072090e13e4e9ee3e6d Mon Sep 17 00:00:00 2001 From: Ankush Menat Date: Fri, 28 Jan 2022 13:50:57 +0530 Subject: [PATCH 10/14] test: delete data after running test [skip ci] --- erpnext/manufacturing/doctype/bom/test_bom.py | 1 + 1 file changed, 1 insertion(+) diff --git a/erpnext/manufacturing/doctype/bom/test_bom.py b/erpnext/manufacturing/doctype/bom/test_bom.py index 0bb0c3948ef..bfafacdfb57 100644 --- a/erpnext/manufacturing/doctype/bom/test_bom.py +++ b/erpnext/manufacturing/doctype/bom/test_bom.py @@ -383,6 +383,7 @@ class TestBOM(ERPNextTestCase): bom.with_operations = 0 bom.save() self.assertEqual(bom.transfer_material_against, "Work Order") + bom.delete() def get_default_bom(item_code="_Test FG Item 2"): From 997ddbea78dc230fcc44a9cdca932132b023db3c Mon Sep 17 00:00:00 2001 From: Ankush Menat Date: Fri, 28 Jan 2022 14:12:57 +0530 Subject: [PATCH 11/14] fix: ignore alternate item while checking pending qty (cherry picked from commit 14e3e163aeec88a61748beca415e59bc0aa62870) --- erpnext/public/js/utils/serial_no_batch_selector.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/public/js/utils/serial_no_batch_selector.js b/erpnext/public/js/utils/serial_no_batch_selector.js index b5d3981ba7f..16e3fa0abd1 100644 --- a/erpnext/public/js/utils/serial_no_batch_selector.js +++ b/erpnext/public/js/utils/serial_no_batch_selector.js @@ -590,6 +590,6 @@ function check_can_calculate_pending_qty(me) { && doc.fg_completed_qty && erpnext.stock.bom && erpnext.stock.bom.name === doc.bom_no; - const itemChecks = !!item; + const itemChecks = !!item && !item.allow_alternative_item; return docChecks && itemChecks; } From cc08125f39bbc0b0c2392eafab77da39ffe3939d Mon Sep 17 00:00:00 2001 From: Ankush Menat Date: Mon, 24 Jan 2022 19:19:58 +0530 Subject: [PATCH 12/14] refactor: reusable clean_serial_nos (cherry picked from commit b20df3745eafdcc18d3b7cb9a93065562aef5c6f) --- erpnext/controllers/stock_controller.py | 13 ++++--------- erpnext/stock/doctype/serial_no/serial_no.py | 7 +++++++ 2 files changed, 11 insertions(+), 9 deletions(-) diff --git a/erpnext/controllers/stock_controller.py b/erpnext/controllers/stock_controller.py index b97432e7485..d7a68f78008 100644 --- a/erpnext/controllers/stock_controller.py +++ b/erpnext/controllers/stock_controller.py @@ -77,17 +77,12 @@ class StockController(AccountsController): .format(d.idx, get_link_to_form("Batch", d.get("batch_no")))) def clean_serial_nos(self): + from erpnext.stock.doctype.serial_no.serial_no import clean_serial_no_string + for row in self.get("items"): if hasattr(row, "serial_no") and row.serial_no: - # replace commas by linefeed - row.serial_no = row.serial_no.replace(",", "\n") - - # strip preceeding and succeeding spaces for each SN - # (SN could have valid spaces in between e.g. SN - 123 - 2021) - serial_no_list = row.serial_no.split("\n") - serial_no_list = [sn.strip() for sn in serial_no_list] - - row.serial_no = "\n".join(serial_no_list) + # remove extra whitespace and store one serial no on each line + row.serial_no = clean_serial_no_string(row.serial_no) def get_gl_entries(self, warehouse_account=None, default_expense_account=None, default_cost_center=None): diff --git a/erpnext/stock/doctype/serial_no/serial_no.py b/erpnext/stock/doctype/serial_no/serial_no.py index 3b325b80295..e300d46db83 100644 --- a/erpnext/stock/doctype/serial_no/serial_no.py +++ b/erpnext/stock/doctype/serial_no/serial_no.py @@ -484,6 +484,13 @@ def get_serial_nos(serial_no): return [s.strip() for s in cstr(serial_no).strip().upper().replace(',', '\n').split('\n') if s.strip()] +def clean_serial_no_string(serial_no: str) -> str: + if not serial_no: + return "" + + serial_no_list = get_serial_nos(serial_no) + return "\n".join(serial_no_list) + def update_args_for_serial_no(serial_no_doc, serial_no, args, is_new=False): for field in ["item_code", "work_order", "company", "batch_no", "supplier", "location"]: if args.get(field): From db3eef9a8b64f8453837b178b7ac95bfdce6f4fd Mon Sep 17 00:00:00 2001 From: Ankush Menat Date: Mon, 24 Jan 2022 19:28:26 +0530 Subject: [PATCH 13/14] fix: extend sr_no cleanup to packed items too (cherry picked from commit e177c5277f223cd0f513d7189053e444554bd745) --- erpnext/controllers/stock_controller.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/erpnext/controllers/stock_controller.py b/erpnext/controllers/stock_controller.py index d7a68f78008..2912d3eb0bd 100644 --- a/erpnext/controllers/stock_controller.py +++ b/erpnext/controllers/stock_controller.py @@ -84,6 +84,11 @@ class StockController(AccountsController): # remove extra whitespace and store one serial no on each line row.serial_no = clean_serial_no_string(row.serial_no) + for row in self.get('packed_items') or []: + if hasattr(row, "serial_no") and row.serial_no: + # remove extra whitespace and store one serial no on each line + row.serial_no = clean_serial_no_string(row.serial_no) + def get_gl_entries(self, warehouse_account=None, default_expense_account=None, default_cost_center=None): From 70dc77305b1e054bb859fea269dc1a818065dc82 Mon Sep 17 00:00:00 2001 From: Ankush Menat Date: Mon, 24 Jan 2022 19:33:47 +0530 Subject: [PATCH 14/14] fix: work order serial no allows storing whitespace (cherry picked from commit 43bd88e741b69fcfd4a2547fd59e5e6286229522) --- erpnext/manufacturing/doctype/work_order/work_order.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/erpnext/manufacturing/doctype/work_order/work_order.py b/erpnext/manufacturing/doctype/work_order/work_order.py index 23fb9697cdb..b12e157390f 100644 --- a/erpnext/manufacturing/doctype/work_order/work_order.py +++ b/erpnext/manufacturing/doctype/work_order/work_order.py @@ -31,6 +31,7 @@ from erpnext.stock.doctype.batch.batch import make_batch from erpnext.stock.doctype.item.item import get_item_defaults, validate_end_of_life from erpnext.stock.doctype.serial_no.serial_no import ( auto_make_serial_nos, + clean_serial_no_string, get_auto_serial_nos, get_serial_nos, ) @@ -358,6 +359,7 @@ class WorkOrder(Document): frappe.delete_doc("Batch", row.name) def make_serial_nos(self, args): + self.serial_no = clean_serial_no_string(self.serial_no) serial_no_series = frappe.get_cached_value("Item", self.production_item, "serial_no_series") if serial_no_series: self.serial_no = get_auto_serial_nos(serial_no_series, self.qty)