From e04fbb6a9955276da0d1897a0d181cbd2309680e Mon Sep 17 00:00:00 2001 From: Devin Slauenwhite Date: Wed, 27 Apr 2022 22:02:04 -0400 Subject: [PATCH 01/25] feat: pick list scan fields --- .../stock/doctype/pick_list/pick_list.json | 29 +++++++++++++++++-- .../pick_list_item/pick_list_item.json | 8 ++++- 2 files changed, 34 insertions(+), 3 deletions(-) diff --git a/erpnext/stock/doctype/pick_list/pick_list.json b/erpnext/stock/doctype/pick_list/pick_list.json index e984c082d484..59a586e40470 100644 --- a/erpnext/stock/doctype/pick_list/pick_list.json +++ b/erpnext/stock/doctype/pick_list/pick_list.json @@ -17,6 +17,10 @@ "parent_warehouse", "get_item_locations", "section_break_6", + "scan_barcode", + "column_break_13", + "scan_mode", + "section_break_15", "locations", "amended_from", "print_settings_section", @@ -126,11 +130,32 @@ "fieldtype": "Check", "label": "Group Same Items", "print_hide": 1 + }, + { + "fieldname": "column_break_13", + "fieldtype": "Column Break" + }, + { + "default": "0", + "fieldname": "scan_mode", + "fieldtype": "Check", + "label": "Scan Mode" + }, + { + "fieldname": "section_break_15", + "fieldtype": "Section Break" + }, + { + "depends_on": "eval:doc.scan_mode", + "fieldname": "scan_barcode", + "fieldtype": "Data", + "label": "Scan Barcode", + "options": "Barcode" } ], "is_submittable": 1, "links": [], - "modified": "2022-04-21 07:56:40.646473", + "modified": "2022-04-27 20:31:49.260238", "modified_by": "Administrator", "module": "Stock", "name": "Pick List", @@ -202,4 +227,4 @@ "sort_order": "DESC", "states": [], "track_changes": 1 -} \ No newline at end of file +} diff --git a/erpnext/stock/doctype/pick_list_item/pick_list_item.json b/erpnext/stock/doctype/pick_list_item/pick_list_item.json index a96ebfcdee68..dda0a027829d 100644 --- a/erpnext/stock/doctype/pick_list_item/pick_list_item.json +++ b/erpnext/stock/doctype/pick_list_item/pick_list_item.json @@ -5,6 +5,7 @@ "editable_grid": 1, "engine": "InnoDB", "field_order": [ + "barcode", "item_code", "item_name", "column_break_2", @@ -187,11 +188,16 @@ "hidden": 1, "label": "Product Bundle Item", "read_only": 1 + }, + { + "fieldname": "barcode", + "fieldtype": "Data", + "label": "Barcode" } ], "istable": 1, "links": [], - "modified": "2022-04-22 05:27:38.497997", + "modified": "2022-04-27 22:00:38.462989", "modified_by": "Administrator", "module": "Stock", "name": "Pick List Item", From 159cf2848e8acd9e8f7518201fc4883c7c266869 Mon Sep 17 00:00:00 2001 From: Devin Slauenwhite Date: Wed, 27 Apr 2022 22:03:39 -0400 Subject: [PATCH 02/25] fix: get correct row to modify with duplicate item_codes and max_qty fields are present --- erpnext/public/js/utils/barcode_scanner.js | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/erpnext/public/js/utils/barcode_scanner.js b/erpnext/public/js/utils/barcode_scanner.js index f72b85c0f6ea..b162697e324a 100644 --- a/erpnext/public/js/utils/barcode_scanner.js +++ b/erpnext/public/js/utils/barcode_scanner.js @@ -10,6 +10,7 @@ erpnext.utils.BarcodeScanner = class BarcodeScanner { this.serial_no_field = opts.serial_no_field || "serial_no"; this.batch_no_field = opts.batch_no_field || "batch_no"; this.qty_field = opts.qty_field || "qty"; + this.max_qty_field = opts.max_qty_field || null; this.items_table_name = opts.items_table_name || "items"; this.items_table = this.frm.doc[this.items_table_name]; @@ -182,7 +183,11 @@ erpnext.utils.BarcodeScanner = class BarcodeScanner { get_row_to_modify_on_scan(item_code) { // get an existing item row to increment or blank row to modify - const existing_item_row = this.items_table.find((d) => d.item_code === item_code); + const existing_item_row = this.items_table.filter((d) => { + const [qty, max_qty] = [d[this.qty_field], d[this.max_qty_field] || null] + return d.item_code === item_code && ((max_qty === null) || (qty < max_qty)); + }).splice(0, 1).pop(); + return existing_item_row || this.get_existing_blank_row(); } From ceffbf243e4d39258874c290fa7400577dca7dd1 Mon Sep 17 00:00:00 2001 From: Devin Slauenwhite Date: Wed, 27 Apr 2022 22:07:25 -0400 Subject: [PATCH 03/25] fix: show alert when maximum qty scanned is reached --- erpnext/public/js/utils/barcode_scanner.js | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/erpnext/public/js/utils/barcode_scanner.js b/erpnext/public/js/utils/barcode_scanner.js index b162697e324a..24d49a45fb70 100644 --- a/erpnext/public/js/utils/barcode_scanner.js +++ b/erpnext/public/js/utils/barcode_scanner.js @@ -11,6 +11,10 @@ erpnext.utils.BarcodeScanner = class BarcodeScanner { this.batch_no_field = opts.batch_no_field || "batch_no"; this.qty_field = opts.qty_field || "qty"; this.max_qty_field = opts.max_qty_field || null; + this.allow_new_row = opts.allow_new_row; + if (this.allow_new_row === undefined || this.allow_new_row === null) { + this.allow_new_row = true; + } this.items_table_name = opts.items_table_name || "items"; this.items_table = this.frm.doc[this.items_table_name]; @@ -73,6 +77,15 @@ erpnext.utils.BarcodeScanner = class BarcodeScanner { } if (!row) { + if (!this.allow_new_row) { + frappe.show_alert({ + message: __("Maximum quantity scanned for this barcode."), + indicator: "red" + }); + this.clean_up(); + return; + } + // add new row if new item/batch is scanned row = frappe.model.add_child(this.frm.doc, cur_grid.doctype, this.items_table_name); // trigger any row add triggers defined on child table. From 8053f2dbcd835602d86dca520b8629b0ac866656 Mon Sep 17 00:00:00 2001 From: Devin Slauenwhite Date: Wed, 27 Apr 2022 22:10:15 -0400 Subject: [PATCH 04/25] feat: increment picked_qty on scan_barcode --- erpnext/stock/doctype/pick_list/pick_list.js | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/erpnext/stock/doctype/pick_list/pick_list.js b/erpnext/stock/doctype/pick_list/pick_list.js index 13b74b5eb16f..ba1475347a90 100644 --- a/erpnext/stock/doctype/pick_list/pick_list.js +++ b/erpnext/stock/doctype/pick_list/pick_list.js @@ -158,6 +158,17 @@ frappe.ui.form.on('Pick List', { get_query_filters: get_query_filters }); }); + }, + scan_barcode: (frm) => { + const opts = { + frm, + items_table_name: 'locations', + qty_field: 'picked_qty', + max_qty_field: 'qty', + allow_new_row: false + }; + const barcode_scanner = new erpnext.utils.BarcodeScanner(opts); + barcode_scanner.process_scan(); } }); From 24d8f62b210ac807ed552586ed44072e103cf57b Mon Sep 17 00:00:00 2001 From: Devin Slauenwhite Date: Fri, 29 Apr 2022 13:40:48 -0400 Subject: [PATCH 05/25] revert: barcode scan field from pick list item. --- .../stock/doctype/pick_list_item/pick_list_item.json | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/erpnext/stock/doctype/pick_list_item/pick_list_item.json b/erpnext/stock/doctype/pick_list_item/pick_list_item.json index dda0a027829d..a6f8c0db4583 100644 --- a/erpnext/stock/doctype/pick_list_item/pick_list_item.json +++ b/erpnext/stock/doctype/pick_list_item/pick_list_item.json @@ -5,7 +5,6 @@ "editable_grid": 1, "engine": "InnoDB", "field_order": [ - "barcode", "item_code", "item_name", "column_break_2", @@ -188,16 +187,11 @@ "hidden": 1, "label": "Product Bundle Item", "read_only": 1 - }, - { - "fieldname": "barcode", - "fieldtype": "Data", - "label": "Barcode" } ], "istable": 1, "links": [], - "modified": "2022-04-27 22:00:38.462989", + "modified": "2022-04-22 05:27:38.497997", "modified_by": "Administrator", "module": "Stock", "name": "Pick List Item", @@ -208,4 +202,4 @@ "sort_order": "DESC", "states": [], "track_changes": 1 -} \ No newline at end of file +} From 2554cdceebb2d916a8fba90ddb13d8595c0afd11 Mon Sep 17 00:00:00 2001 From: Devin Slauenwhite Date: Fri, 29 Apr 2022 13:45:59 -0400 Subject: [PATCH 06/25] revert: scan_mode flag --- erpnext/stock/doctype/pick_list/pick_list.json | 17 ++--------------- 1 file changed, 2 insertions(+), 15 deletions(-) diff --git a/erpnext/stock/doctype/pick_list/pick_list.json b/erpnext/stock/doctype/pick_list/pick_list.json index 59a586e40470..f32a05a266fe 100644 --- a/erpnext/stock/doctype/pick_list/pick_list.json +++ b/erpnext/stock/doctype/pick_list/pick_list.json @@ -18,8 +18,6 @@ "get_item_locations", "section_break_6", "scan_barcode", - "column_break_13", - "scan_mode", "section_break_15", "locations", "amended_from", @@ -131,22 +129,11 @@ "label": "Group Same Items", "print_hide": 1 }, - { - "fieldname": "column_break_13", - "fieldtype": "Column Break" - }, - { - "default": "0", - "fieldname": "scan_mode", - "fieldtype": "Check", - "label": "Scan Mode" - }, { "fieldname": "section_break_15", "fieldtype": "Section Break" }, { - "depends_on": "eval:doc.scan_mode", "fieldname": "scan_barcode", "fieldtype": "Data", "label": "Scan Barcode", @@ -155,7 +142,7 @@ ], "is_submittable": 1, "links": [], - "modified": "2022-04-27 20:31:49.260238", + "modified": "2022-04-29 13:45:24.401314", "modified_by": "Administrator", "module": "Stock", "name": "Pick List", @@ -227,4 +214,4 @@ "sort_order": "DESC", "states": [], "track_changes": 1 -} +} \ No newline at end of file From 48128911bed66ac94c17de580424226509b33470 Mon Sep 17 00:00:00 2001 From: Devin Slauenwhite Date: Fri, 29 Apr 2022 16:27:15 -0400 Subject: [PATCH 07/25] fix: syntax --- erpnext/public/js/utils/barcode_scanner.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/public/js/utils/barcode_scanner.js b/erpnext/public/js/utils/barcode_scanner.js index 24d49a45fb70..5a73fea17ae6 100644 --- a/erpnext/public/js/utils/barcode_scanner.js +++ b/erpnext/public/js/utils/barcode_scanner.js @@ -197,7 +197,7 @@ erpnext.utils.BarcodeScanner = class BarcodeScanner { get_row_to_modify_on_scan(item_code) { // get an existing item row to increment or blank row to modify const existing_item_row = this.items_table.filter((d) => { - const [qty, max_qty] = [d[this.qty_field], d[this.max_qty_field] || null] + const [qty, max_qty] = [d[this.qty_field], d[this.max_qty_field] || null]; return d.item_code === item_code && ((max_qty === null) || (qty < max_qty)); }).splice(0, 1).pop(); From 5f8f83c6d87b6c7e991a31cdbef99c1bde46d464 Mon Sep 17 00:00:00 2001 From: Devin Slauenwhite Date: Fri, 29 Apr 2022 16:28:49 -0400 Subject: [PATCH 08/25] fix: warn user pick list is not complete instead of auto fulfilling picked_qty. --- erpnext/stock/doctype/pick_list/pick_list.py | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/erpnext/stock/doctype/pick_list/pick_list.py b/erpnext/stock/doctype/pick_list/pick_list.py index 70d2f23070c1..29dafbb5cac8 100644 --- a/erpnext/stock/doctype/pick_list/pick_list.py +++ b/erpnext/stock/doctype/pick_list/pick_list.py @@ -42,8 +42,14 @@ def before_submit(self): update_sales_orders = set() for item in self.locations: # if the user has not entered any picked qty, set it to stock_qty, before submit - if item.picked_qty == 0: - item.picked_qty = item.stock_qty + if item.picked_qty < item.stock_qty: + frappe.msgprint( + _("Row {0} is short by {1} {2}").format( + item.idx, item.stock_qty - item.picked_qty, item.stock_uom + ), + _("Warning: Pick List Incomplete"), + indicator="yellow", + ) if item.sales_order_item: # update the picked_qty in SO Item From fa1378dd49f093d4cc8d1e5851b9e9fe1af68d35 Mon Sep 17 00:00:00 2001 From: Devin Slauenwhite Date: Fri, 29 Apr 2022 16:38:52 -0400 Subject: [PATCH 09/25] fix: prevent user from proceeding without all qty picked. --- erpnext/stock/doctype/pick_list/pick_list.py | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/erpnext/stock/doctype/pick_list/pick_list.py b/erpnext/stock/doctype/pick_list/pick_list.py index 29dafbb5cac8..2bcb93a69ebf 100644 --- a/erpnext/stock/doctype/pick_list/pick_list.py +++ b/erpnext/stock/doctype/pick_list/pick_list.py @@ -43,13 +43,9 @@ def before_submit(self): for item in self.locations: # if the user has not entered any picked qty, set it to stock_qty, before submit if item.picked_qty < item.stock_qty: - frappe.msgprint( - _("Row {0} is short by {1} {2}").format( - item.idx, item.stock_qty - item.picked_qty, item.stock_uom - ), - _("Warning: Pick List Incomplete"), - indicator="yellow", - ) + frappe.throw(_("Row {0} is short by {1} {2}").format( + item.idx, item.stock_qty - item.picked_qty, item.stock_uom + ), title=_("Pick List Incomplete")) if item.sales_order_item: # update the picked_qty in SO Item From 5ff471e22f3e9d74882380b8425e1f8ea3cf423c Mon Sep 17 00:00:00 2001 From: Devin Slauenwhite Date: Fri, 29 Apr 2022 16:39:33 -0400 Subject: [PATCH 10/25] fix: linter --- erpnext/stock/doctype/pick_list/pick_list.py | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/erpnext/stock/doctype/pick_list/pick_list.py b/erpnext/stock/doctype/pick_list/pick_list.py index 2bcb93a69ebf..ce3ec60d7ac4 100644 --- a/erpnext/stock/doctype/pick_list/pick_list.py +++ b/erpnext/stock/doctype/pick_list/pick_list.py @@ -43,9 +43,12 @@ def before_submit(self): for item in self.locations: # if the user has not entered any picked qty, set it to stock_qty, before submit if item.picked_qty < item.stock_qty: - frappe.throw(_("Row {0} is short by {1} {2}").format( - item.idx, item.stock_qty - item.picked_qty, item.stock_uom - ), title=_("Pick List Incomplete")) + frappe.throw( + _("Row {0} is short by {1} {2}").format( + item.idx, item.stock_qty - item.picked_qty, item.stock_uom + ), + title=_("Pick List Incomplete"), + ) if item.sales_order_item: # update the picked_qty in SO Item From c2335ec0d82a191aefcfd9f3f2022eaae5114cc1 Mon Sep 17 00:00:00 2001 From: Devin Slauenwhite Date: Tue, 3 May 2022 16:28:28 -0400 Subject: [PATCH 11/25] fix(test): manually select picked_qty since before_submit validates qty instead of auto filling it --- erpnext/stock/doctype/pick_list/test_pick_list.py | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/erpnext/stock/doctype/pick_list/test_pick_list.py b/erpnext/stock/doctype/pick_list/test_pick_list.py index f552299806c3..1c90f32b3764 100644 --- a/erpnext/stock/doctype/pick_list/test_pick_list.py +++ b/erpnext/stock/doctype/pick_list/test_pick_list.py @@ -411,6 +411,8 @@ def test_pick_list_for_items_with_multiple_UOM(self): } ) pick_list.set_item_locations() + for location in pick_list.locations: + location.picked_qty = location.stock_qty pick_list.submit() delivery_note = create_delivery_note(pick_list.name) @@ -527,6 +529,8 @@ def test_multiple_dn_creation(self): } ) pick_list.set_item_locations() + for location in pick_list.locations: + location.picked_qty = location.stock_qty pick_list.submit() create_delivery_note(pick_list.name) for dn in frappe.get_all( @@ -571,6 +575,8 @@ def test_multiple_dn_creation(self): } ) pick_list_1.set_item_locations() + for location in pick_list_1.locations: + location.picked_qty = location.stock_qty pick_list_1.submit() create_delivery_note(pick_list_1.name) for dn in frappe.get_all( @@ -591,7 +597,8 @@ def test_picklist_with_multi_uom(self): pl = create_pick_list(so.name) # pick half the qty for loc in pl.locations: - loc.picked_qty = loc.stock_qty / 2 + loc.stock_qty = loc.stock_qty /2 + loc.picked_qty = loc.stock_qty pl.save() pl.submit() @@ -611,6 +618,7 @@ def test_picklist_with_bundles(self): pl.save() self.assertEqual(len(pl.locations), 2) for item in pl.locations: + item.picked_qty = item.stock_qty self.assertEqual(item.stock_qty, bundle_items[item.item_code] * 3) # check picking status on sales order @@ -636,7 +644,8 @@ def test_picklist_with_partial_bundles(self): pl = create_pick_list(so.name) for loc in pl.locations: - loc.picked_qty = loc.qty / 2 + loc.stock_qty = loc.stock_qty / 2 + loc.picked_qty = loc.stock_qty pl.save().submit() so.reload() @@ -649,6 +658,8 @@ def test_picklist_with_partial_bundles(self): self.assertEqual(so.per_delivered, 50) pl = create_pick_list(so.name) + for loc in pl.locations: + loc.picked_qty = loc.stock_qty pl.save().submit() so.reload() self.assertEqual(so.per_picked, 100) From 4a19c1c19d23bd833d845631a0ddc8a04141a32d Mon Sep 17 00:00:00 2001 From: Devin Slauenwhite Date: Wed, 4 May 2022 10:29:52 -0400 Subject: [PATCH 12/25] Revert "fix(test): manually select picked_qty since before_submit validates qty instead of auto filling it" This reverts commit c2335ec0 --- erpnext/stock/doctype/pick_list/test_pick_list.py | 15 ++------------- 1 file changed, 2 insertions(+), 13 deletions(-) diff --git a/erpnext/stock/doctype/pick_list/test_pick_list.py b/erpnext/stock/doctype/pick_list/test_pick_list.py index 1c90f32b3764..f552299806c3 100644 --- a/erpnext/stock/doctype/pick_list/test_pick_list.py +++ b/erpnext/stock/doctype/pick_list/test_pick_list.py @@ -411,8 +411,6 @@ def test_pick_list_for_items_with_multiple_UOM(self): } ) pick_list.set_item_locations() - for location in pick_list.locations: - location.picked_qty = location.stock_qty pick_list.submit() delivery_note = create_delivery_note(pick_list.name) @@ -529,8 +527,6 @@ def test_multiple_dn_creation(self): } ) pick_list.set_item_locations() - for location in pick_list.locations: - location.picked_qty = location.stock_qty pick_list.submit() create_delivery_note(pick_list.name) for dn in frappe.get_all( @@ -575,8 +571,6 @@ def test_multiple_dn_creation(self): } ) pick_list_1.set_item_locations() - for location in pick_list_1.locations: - location.picked_qty = location.stock_qty pick_list_1.submit() create_delivery_note(pick_list_1.name) for dn in frappe.get_all( @@ -597,8 +591,7 @@ def test_picklist_with_multi_uom(self): pl = create_pick_list(so.name) # pick half the qty for loc in pl.locations: - loc.stock_qty = loc.stock_qty /2 - loc.picked_qty = loc.stock_qty + loc.picked_qty = loc.stock_qty / 2 pl.save() pl.submit() @@ -618,7 +611,6 @@ def test_picklist_with_bundles(self): pl.save() self.assertEqual(len(pl.locations), 2) for item in pl.locations: - item.picked_qty = item.stock_qty self.assertEqual(item.stock_qty, bundle_items[item.item_code] * 3) # check picking status on sales order @@ -644,8 +636,7 @@ def test_picklist_with_partial_bundles(self): pl = create_pick_list(so.name) for loc in pl.locations: - loc.stock_qty = loc.stock_qty / 2 - loc.picked_qty = loc.stock_qty + loc.picked_qty = loc.qty / 2 pl.save().submit() so.reload() @@ -658,8 +649,6 @@ def test_picklist_with_partial_bundles(self): self.assertEqual(so.per_delivered, 50) pl = create_pick_list(so.name) - for loc in pl.locations: - loc.picked_qty = loc.stock_qty pl.save().submit() so.reload() self.assertEqual(so.per_picked, 100) From 5560ceca62c89749a21c83c43d6481f2114d0a11 Mon Sep 17 00:00:00 2001 From: Devin Slauenwhite Date: Wed, 4 May 2022 10:32:21 -0400 Subject: [PATCH 13/25] fix: max qty message Co-authored-by: Ankush Menat --- erpnext/public/js/utils/barcode_scanner.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/public/js/utils/barcode_scanner.js b/erpnext/public/js/utils/barcode_scanner.js index 5a73fea17ae6..c8a539d2ebe8 100644 --- a/erpnext/public/js/utils/barcode_scanner.js +++ b/erpnext/public/js/utils/barcode_scanner.js @@ -79,7 +79,7 @@ erpnext.utils.BarcodeScanner = class BarcodeScanner { if (!row) { if (!this.allow_new_row) { frappe.show_alert({ - message: __("Maximum quantity scanned for this barcode."), + message: __("Maximum quantity scanned for item {0}.", [item_code]), indicator: "red" }); this.clean_up(); From 7ae89dedd59815cc0dea63a3ecf67b2355ea4425 Mon Sep 17 00:00:00 2001 From: Devin Slauenwhite Date: Wed, 4 May 2022 11:06:16 -0400 Subject: [PATCH 14/25] fix: cleanup dont_allow_new_row logic --- erpnext/public/js/utils/barcode_scanner.js | 7 ++----- erpnext/stock/doctype/pick_list/pick_list.js | 2 +- 2 files changed, 3 insertions(+), 6 deletions(-) diff --git a/erpnext/public/js/utils/barcode_scanner.js b/erpnext/public/js/utils/barcode_scanner.js index c8a539d2ebe8..25b4f6221772 100644 --- a/erpnext/public/js/utils/barcode_scanner.js +++ b/erpnext/public/js/utils/barcode_scanner.js @@ -11,10 +11,7 @@ erpnext.utils.BarcodeScanner = class BarcodeScanner { this.batch_no_field = opts.batch_no_field || "batch_no"; this.qty_field = opts.qty_field || "qty"; this.max_qty_field = opts.max_qty_field || null; - this.allow_new_row = opts.allow_new_row; - if (this.allow_new_row === undefined || this.allow_new_row === null) { - this.allow_new_row = true; - } + this.dont_allow_new_row = opts.dont_allow_new_row; this.items_table_name = opts.items_table_name || "items"; this.items_table = this.frm.doc[this.items_table_name]; @@ -77,7 +74,7 @@ erpnext.utils.BarcodeScanner = class BarcodeScanner { } if (!row) { - if (!this.allow_new_row) { + if (this.dont_allow_new_row) { frappe.show_alert({ message: __("Maximum quantity scanned for item {0}.", [item_code]), indicator: "red" diff --git a/erpnext/stock/doctype/pick_list/pick_list.js b/erpnext/stock/doctype/pick_list/pick_list.js index ba1475347a90..70e530cbfe0d 100644 --- a/erpnext/stock/doctype/pick_list/pick_list.js +++ b/erpnext/stock/doctype/pick_list/pick_list.js @@ -165,7 +165,7 @@ frappe.ui.form.on('Pick List', { items_table_name: 'locations', qty_field: 'picked_qty', max_qty_field: 'qty', - allow_new_row: false + dont_allow_new_row: true }; const barcode_scanner = new erpnext.utils.BarcodeScanner(opts); barcode_scanner.process_scan(); From d48ab81622de00c1f088caa435468476cfd9c727 Mon Sep 17 00:00:00 2001 From: Ankush Menat Date: Mon, 28 Mar 2022 18:52:46 +0530 Subject: [PATCH 15/25] fix: auto-fulfill picking list when not in scan mode --- erpnext/stock/doctype/pick_list/pick_list.json | 15 ++++++++++++++- erpnext/stock/doctype/pick_list/pick_list.py | 6 ++++-- 2 files changed, 18 insertions(+), 3 deletions(-) diff --git a/erpnext/stock/doctype/pick_list/pick_list.json b/erpnext/stock/doctype/pick_list/pick_list.json index f32a05a266fe..f2d7543cae99 100644 --- a/erpnext/stock/doctype/pick_list/pick_list.json +++ b/erpnext/stock/doctype/pick_list/pick_list.json @@ -18,6 +18,8 @@ "get_item_locations", "section_break_6", "scan_barcode", + "column_break_13", + "scan_mode", "section_break_15", "locations", "amended_from", @@ -138,11 +140,22 @@ "fieldtype": "Data", "label": "Scan Barcode", "options": "Barcode" + }, + { + "fieldname": "column_break_13", + "fieldtype": "Column Break" + }, + { + "default": "0", + "description": "If checked, picked qty won't automatically be fulfilled on submit of pick list.", + "fieldname": "scan_mode", + "fieldtype": "Check", + "label": "Scan Mode" } ], "is_submittable": 1, "links": [], - "modified": "2022-04-29 13:45:24.401314", + "modified": "2022-05-04 11:12:48.044239", "modified_by": "Administrator", "module": "Stock", "name": "Pick List", diff --git a/erpnext/stock/doctype/pick_list/pick_list.py b/erpnext/stock/doctype/pick_list/pick_list.py index ce3ec60d7ac4..d2e266f96540 100644 --- a/erpnext/stock/doctype/pick_list/pick_list.py +++ b/erpnext/stock/doctype/pick_list/pick_list.py @@ -41,14 +41,16 @@ def before_save(self): def before_submit(self): update_sales_orders = set() for item in self.locations: - # if the user has not entered any picked qty, set it to stock_qty, before submit - if item.picked_qty < item.stock_qty: + if self.scan_mode and item.picked_qty < item.stock_qty: frappe.throw( _("Row {0} is short by {1} {2}").format( item.idx, item.stock_qty - item.picked_qty, item.stock_uom ), title=_("Pick List Incomplete"), ) + elif not self.scan_mode and item.picked_qty == 0: + # if the user has not entered any picked qty, set it to stock_qty, before submit + item.picked_qty = item.stock_qty if item.sales_order_item: # update the picked_qty in SO Item From 47b425184409469bb014b6a15faefb8017f3c224 Mon Sep 17 00:00:00 2001 From: Devin Slauenwhite Date: Thu, 5 May 2022 09:39:19 -0400 Subject: [PATCH 16/25] feat: prompt qty on scan --- erpnext/public/js/utils/barcode_scanner.js | 30 +++++++++++++++++----- 1 file changed, 23 insertions(+), 7 deletions(-) diff --git a/erpnext/public/js/utils/barcode_scanner.js b/erpnext/public/js/utils/barcode_scanner.js index 25b4f6221772..1cf8716e79de 100644 --- a/erpnext/public/js/utils/barcode_scanner.js +++ b/erpnext/public/js/utils/barcode_scanner.js @@ -12,6 +12,7 @@ erpnext.utils.BarcodeScanner = class BarcodeScanner { this.qty_field = opts.qty_field || "qty"; this.max_qty_field = opts.max_qty_field || null; this.dont_allow_new_row = opts.dont_allow_new_row; + this.prompt_qty = opts.prompt_qty; this.items_table_name = opts.items_table_name || "items"; this.items_table = this.frm.doc[this.items_table_name]; @@ -94,9 +95,10 @@ erpnext.utils.BarcodeScanner = class BarcodeScanner { return; } - this.show_scan_message(row.idx, row.item_code); this.set_selector_trigger_flag(row, data); - this.set_item(row, item_code); + this.set_item(row, item_code).then(qty => { + this.show_scan_message(row.idx, row.item_code, qty); + }); this.set_serial_no(row, serial_no); this.set_batch_no(row, batch_no); this.set_barcode(row, barcode); @@ -117,9 +119,23 @@ erpnext.utils.BarcodeScanner = class BarcodeScanner { } set_item(row, item_code) { - const item_data = { item_code: item_code }; - item_data[this.qty_field] = (row[this.qty_field] || 0) + 1; - frappe.model.set_value(row.doctype, row.name, item_data); + return new Promise(resolve => { + const increment = (value = 1) => { + const item_data = {item_code: item_code}; + item_data[this.qty_field] = (row[this.qty_field] || 0) + value; + frappe.model.set_value(row.doctype, row.name, item_data); + }; + + if (this.prompt_qty) { + frappe.prompt(__("Please enter quantity for item {0}", [item_code]), ({value}) => { + increment(value); + resolve(value); + }); + } else { + increment(); + resolve(); + } + }); } set_serial_no(row, serial_no) { @@ -148,12 +164,12 @@ erpnext.utils.BarcodeScanner = class BarcodeScanner { } } - show_scan_message(idx, exist = null) { + show_scan_message(idx, exist = null, qty = 1) { // show new row or qty increase toast if (exist) { frappe.show_alert( { - message: __("Row #{0}: Qty increased by 1", [idx]), + message: __("Row #{0}: Qty increased by {1}", [idx, qty]), indicator: "green", }, 5 From 0a77c28594e2951bc5c989904417f28d19d3b30d Mon Sep 17 00:00:00 2001 From: Devin Slauenwhite Date: Thu, 5 May 2022 10:46:40 -0400 Subject: [PATCH 17/25] fix: cast value to Number type --- erpnext/public/js/utils/barcode_scanner.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/public/js/utils/barcode_scanner.js b/erpnext/public/js/utils/barcode_scanner.js index 1cf8716e79de..51ee680177eb 100644 --- a/erpnext/public/js/utils/barcode_scanner.js +++ b/erpnext/public/js/utils/barcode_scanner.js @@ -122,7 +122,7 @@ erpnext.utils.BarcodeScanner = class BarcodeScanner { return new Promise(resolve => { const increment = (value = 1) => { const item_data = {item_code: item_code}; - item_data[this.qty_field] = (row[this.qty_field] || 0) + value; + item_data[this.qty_field] = Number((row[this.qty_field] || 0)) + Number(value); frappe.model.set_value(row.doctype, row.name, item_data); }; From e9cf5cb4b0118d50dee2e38339285c06722cec2f Mon Sep 17 00:00:00 2001 From: Devin Slauenwhite Date: Thu, 5 May 2022 10:47:06 -0400 Subject: [PATCH 18/25] feat: add prompt qty flag to pick list --- erpnext/stock/doctype/pick_list/pick_list.js | 3 ++- erpnext/stock/doctype/pick_list/pick_list.json | 9 ++++++++- 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/erpnext/stock/doctype/pick_list/pick_list.js b/erpnext/stock/doctype/pick_list/pick_list.js index 70e530cbfe0d..82663e3f02d5 100644 --- a/erpnext/stock/doctype/pick_list/pick_list.js +++ b/erpnext/stock/doctype/pick_list/pick_list.js @@ -165,7 +165,8 @@ frappe.ui.form.on('Pick List', { items_table_name: 'locations', qty_field: 'picked_qty', max_qty_field: 'qty', - dont_allow_new_row: true + dont_allow_new_row: true, + prompt_qty: frm.doc.prompt_qty }; const barcode_scanner = new erpnext.utils.BarcodeScanner(opts); barcode_scanner.process_scan(); diff --git a/erpnext/stock/doctype/pick_list/pick_list.json b/erpnext/stock/doctype/pick_list/pick_list.json index f2d7543cae99..28cd5df83683 100644 --- a/erpnext/stock/doctype/pick_list/pick_list.json +++ b/erpnext/stock/doctype/pick_list/pick_list.json @@ -20,6 +20,7 @@ "scan_barcode", "column_break_13", "scan_mode", + "prompt_qty", "section_break_15", "locations", "amended_from", @@ -151,11 +152,17 @@ "fieldname": "scan_mode", "fieldtype": "Check", "label": "Scan Mode" + }, + { + "default": "0", + "fieldname": "prompt_qty", + "fieldtype": "Check", + "label": "Prompt Qty" } ], "is_submittable": 1, "links": [], - "modified": "2022-05-04 11:12:48.044239", + "modified": "2022-05-05 09:50:36.252445", "modified_by": "Administrator", "module": "Stock", "name": "Pick List", From d3121fd845bf754b3831c01a311191b0a6052d1a Mon Sep 17 00:00:00 2001 From: Ankush Menat Date: Wed, 11 May 2022 18:40:44 +0530 Subject: [PATCH 19/25] fix(UX): hide scan section for non-draft docs --- erpnext/stock/doctype/pick_list/pick_list.json | 3 ++- .../doctype/stock_reconciliation/stock_reconciliation.json | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/erpnext/stock/doctype/pick_list/pick_list.json b/erpnext/stock/doctype/pick_list/pick_list.json index 28cd5df83683..ff209097a018 100644 --- a/erpnext/stock/doctype/pick_list/pick_list.json +++ b/erpnext/stock/doctype/pick_list/pick_list.json @@ -41,6 +41,7 @@ "fieldtype": "Column Break" }, { + "depends_on": "eval:!doc.docstatus", "fieldname": "section_break_6", "fieldtype": "Section Break" }, @@ -162,7 +163,7 @@ ], "is_submittable": 1, "links": [], - "modified": "2022-05-05 09:50:36.252445", + "modified": "2022-05-11 09:09:53.029312", "modified_by": "Administrator", "module": "Stock", "name": "Pick List", diff --git a/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.json b/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.json index e545b8ea5c3c..9a8543111008 100644 --- a/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.json +++ b/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.json @@ -170,6 +170,7 @@ "options": "Warehouse" }, { + "depends_on": "eval:!doc.docstatus", "fieldname": "section_break_22", "fieldtype": "Section Break" }, @@ -182,7 +183,7 @@ "idx": 1, "is_submittable": 1, "links": [], - "modified": "2022-03-27 08:57:47.161959", + "modified": "2022-05-11 09:10:26.327652", "modified_by": "Administrator", "module": "Stock", "name": "Stock Reconciliation", From 372d2d870e23543b66b842fe0aafac60d38dd1d5 Mon Sep 17 00:00:00 2001 From: Ankush Menat Date: Wed, 11 May 2022 18:43:10 +0530 Subject: [PATCH 20/25] fix: better message for picked qty shortfall --- erpnext/stock/doctype/pick_list/pick_list.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/erpnext/stock/doctype/pick_list/pick_list.py b/erpnext/stock/doctype/pick_list/pick_list.py index d2e266f96540..6b0e928baeeb 100644 --- a/erpnext/stock/doctype/pick_list/pick_list.py +++ b/erpnext/stock/doctype/pick_list/pick_list.py @@ -43,9 +43,9 @@ def before_submit(self): for item in self.locations: if self.scan_mode and item.picked_qty < item.stock_qty: frappe.throw( - _("Row {0} is short by {1} {2}").format( - item.idx, item.stock_qty - item.picked_qty, item.stock_uom - ), + _( + "Row {0} picked quantity is less than the required quantity, additional {1} {2} required." + ).format(item.idx, item.stock_qty - item.picked_qty, item.stock_uom), title=_("Pick List Incomplete"), ) elif not self.scan_mode and item.picked_qty == 0: From 7f14222700d303d245b2315e3bcf182adb219ed8 Mon Sep 17 00:00:00 2001 From: Ankush Menat Date: Wed, 11 May 2022 18:45:31 +0530 Subject: [PATCH 21/25] docs: document barcode_scanner API --- erpnext/public/js/utils/barcode_scanner.js | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/erpnext/public/js/utils/barcode_scanner.js b/erpnext/public/js/utils/barcode_scanner.js index 51ee680177eb..75105be4b277 100644 --- a/erpnext/public/js/utils/barcode_scanner.js +++ b/erpnext/public/js/utils/barcode_scanner.js @@ -10,8 +10,11 @@ erpnext.utils.BarcodeScanner = class BarcodeScanner { this.serial_no_field = opts.serial_no_field || "serial_no"; this.batch_no_field = opts.batch_no_field || "batch_no"; this.qty_field = opts.qty_field || "qty"; - this.max_qty_field = opts.max_qty_field || null; + // field name on row which defines max quantity to be scanned e.g. picklist + this.max_qty_field = opts.max_qty_field; + // scanner won't add a new row if this flag is set. this.dont_allow_new_row = opts.dont_allow_new_row; + // scanner will ask user to type the quantity instead of incrementing by 1 this.prompt_qty = opts.prompt_qty; this.items_table_name = opts.items_table_name || "items"; From d35a13ec7e4038e3280af95ea72a8a1539663e57 Mon Sep 17 00:00:00 2001 From: Ankush Menat Date: Wed, 11 May 2022 18:52:14 +0530 Subject: [PATCH 22/25] refactor: change alert duration to 3 and modern js --- erpnext/public/js/utils/barcode_scanner.js | 40 +++++----------------- 1 file changed, 9 insertions(+), 31 deletions(-) diff --git a/erpnext/public/js/utils/barcode_scanner.js b/erpnext/public/js/utils/barcode_scanner.js index 75105be4b277..48c7a8fe3ccf 100644 --- a/erpnext/public/js/utils/barcode_scanner.js +++ b/erpnext/public/js/utils/barcode_scanner.js @@ -48,10 +48,7 @@ erpnext.utils.BarcodeScanner = class BarcodeScanner { .then((r) => { const data = r && r.message; if (!data || Object.keys(data).length === 0) { - frappe.show_alert({ - message: __("Cannot find Item with this Barcode"), - indicator: "red", - }); + this.show_alert(__("Cannot find Item with this Barcode"), "red"); this.clean_up(); return; } @@ -79,10 +76,7 @@ erpnext.utils.BarcodeScanner = class BarcodeScanner { if (!row) { if (this.dont_allow_new_row) { - frappe.show_alert({ - message: __("Maximum quantity scanned for item {0}.", [item_code]), - indicator: "red" - }); + this.show_alert(__("Maximum quantity scanned for item {0}.", [item_code]), "red"); this.clean_up(); return; } @@ -170,36 +164,17 @@ erpnext.utils.BarcodeScanner = class BarcodeScanner { show_scan_message(idx, exist = null, qty = 1) { // show new row or qty increase toast if (exist) { - frappe.show_alert( - { - message: __("Row #{0}: Qty increased by {1}", [idx, qty]), - indicator: "green", - }, - 5 - ); + this.show_alert(__("Row #{0}: Qty increased by {1}", [idx, qty]), "green"); } else { - frappe.show_alert( - { - message: __("Row #{0}: Item added", [idx]), - indicator: "green", - }, - 5 - ); + this.show_alert(__("Row #{0}: Item added", [idx]), "green") } } is_duplicate_serial_no(row, serial_no) { - const is_duplicate = !!serial_no && !!row[this.serial_no_field] - && row[this.serial_no_field].includes(serial_no); + const is_duplicate = row[this.serial_no_field]?.includes(serial_no); if (is_duplicate) { - frappe.show_alert( - { - message: __("Serial No {0} is already added", [serial_no]), - indicator: "orange", - }, - 5 - ); + this.show_alert(__("Serial No {0} is already added", [serial_no]), "orange"); } return is_duplicate; } @@ -228,4 +203,7 @@ erpnext.utils.BarcodeScanner = class BarcodeScanner { this.scan_barcode_field.set_value(""); refresh_field(this.items_table_name); } + show_alert(msg, indicator, duration=3) { + frappe.show_alert({message: msg, indicator: indicator}, duration); + } }; From 2f1d118f36f541217f9257b81d52e96ba60fe3d4 Mon Sep 17 00:00:00 2001 From: Ankush Menat Date: Wed, 11 May 2022 18:56:22 +0530 Subject: [PATCH 23/25] fix: disable serial no scanning for picklist Current design of picklist doc doesn't have "picked serial no" so it doesn't make much sense to scan it. Instead it should increment qty only. Might add this in future after fixing UX problems --- erpnext/stock/doctype/pick_list/pick_list.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/erpnext/stock/doctype/pick_list/pick_list.js b/erpnext/stock/doctype/pick_list/pick_list.js index 82663e3f02d5..799406cd79e1 100644 --- a/erpnext/stock/doctype/pick_list/pick_list.js +++ b/erpnext/stock/doctype/pick_list/pick_list.js @@ -166,7 +166,8 @@ frappe.ui.form.on('Pick List', { qty_field: 'picked_qty', max_qty_field: 'qty', dont_allow_new_row: true, - prompt_qty: frm.doc.prompt_qty + prompt_qty: frm.doc.prompt_qty, + serial_no_field: "not_supported", // doesn't make sense for picklist without a separate field. }; const barcode_scanner = new erpnext.utils.BarcodeScanner(opts); barcode_scanner.process_scan(); From ab80783e3a2966057e8ab26a7edfeeac10ca5e50 Mon Sep 17 00:00:00 2001 From: Ankush Menat Date: Wed, 11 May 2022 19:27:16 +0530 Subject: [PATCH 24/25] refactor: single function to fetch related row There was separate function for batch row which frequently didn't receive all the love main function received like: 1. empty row reuse 2. max qty validation Hence it makes sense to combine these in one fat function --- erpnext/public/js/utils/barcode_scanner.js | 40 +++++++++------------- 1 file changed, 17 insertions(+), 23 deletions(-) diff --git a/erpnext/public/js/utils/barcode_scanner.js b/erpnext/public/js/utils/barcode_scanner.js index 48c7a8fe3ccf..3ae1234767c2 100644 --- a/erpnext/public/js/utils/barcode_scanner.js +++ b/erpnext/public/js/utils/barcode_scanner.js @@ -59,20 +59,10 @@ erpnext.utils.BarcodeScanner = class BarcodeScanner { update_table(data) { let cur_grid = this.frm.fields_dict[this.items_table_name].grid; - let row = null; const {item_code, barcode, batch_no, serial_no} = data; - // Check if batch is scanned and table has batch no field - let batch_no_scan = - Boolean(batch_no) && frappe.meta.has_field(cur_grid.doctype, this.batch_no_field); - - if (batch_no_scan) { - row = this.get_batch_row_to_modify(batch_no); - } else { - // serial or barcode scan - row = this.get_row_to_modify_on_scan(item_code); - } + let row = this.get_row_to_modify_on_scan(item_code, batch_no); if (!row) { if (this.dont_allow_new_row) { @@ -179,20 +169,24 @@ erpnext.utils.BarcodeScanner = class BarcodeScanner { return is_duplicate; } - get_batch_row_to_modify(batch_no) { - // get row if batch already exists in table - const existing_batch_row = this.items_table.find((d) => d.batch_no === batch_no); - return existing_batch_row || this.get_existing_blank_row(); - } + get_row_to_modify_on_scan(item_code, batch_no) { + let cur_grid = this.frm.fields_dict[this.items_table_name].grid; + + // Check if batch is scanned and table has batch no field + let is_batch_no_scan = batch_no && frappe.meta.has_field(cur_grid.doctype, this.batch_no_field); + let check_max_qty = this.max_qty_field && frappe.meta.has_field(cur_grid.doctype, this.max_qty_field); - get_row_to_modify_on_scan(item_code) { - // get an existing item row to increment or blank row to modify - const existing_item_row = this.items_table.filter((d) => { - const [qty, max_qty] = [d[this.qty_field], d[this.max_qty_field] || null]; - return d.item_code === item_code && ((max_qty === null) || (qty < max_qty)); - }).splice(0, 1).pop(); + const matching_row = (row) => { + const item_match = row.item_code == item_code; + const batch_match = row.batch_no == batch_no; + const qty_in_limit = flt(row[this.qty_field]) < flt(row[this.max_qty_field]); + + return item_match + && (!is_batch_no_scan || batch_match) + && (!check_max_qty || qty_in_limit) + } - return existing_item_row || this.get_existing_blank_row(); + return this.items_table.find(matching_row) || this.get_existing_blank_row(); } get_existing_blank_row() { From 861d4b856c5602b8f8870c92cf62cc2510163f4c Mon Sep 17 00:00:00 2001 From: Ankush Menat Date: Wed, 11 May 2022 19:32:51 +0530 Subject: [PATCH 25/25] chore: bump ecma version we transpile to es2017, so allow use of es2020 --- .eslintrc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.eslintrc b/.eslintrc index 46fb354c11c4..276d6ff37252 100644 --- a/.eslintrc +++ b/.eslintrc @@ -5,7 +5,7 @@ "es6": true }, "parserOptions": { - "ecmaVersion": 9, + "ecmaVersion": 11, "sourceType": "module" }, "extends": "eslint:recommended",