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 e984c082d48..59a586e4047 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 a96ebfcdee6..dda0a027829 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 f72b85c0f6e..b162697e324 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 b162697e324..24d49a45fb7 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 13b74b5eb16..ba1475347a9 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 dda0a027829..a6f8c0db458 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 59a586e4047..f32a05a266f 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 24d49a45fb7..5a73fea17ae 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 70d2f23070c..29dafbb5cac 100644 --- a/erpnext/stock/doctype/pick_list/pick_list.py +++ b/erpnext/stock/doctype/pick_list/pick_list.py @@ -42,8 +42,14 @@ class PickList(Document): 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 29dafbb5cac..2bcb93a69eb 100644 --- a/erpnext/stock/doctype/pick_list/pick_list.py +++ b/erpnext/stock/doctype/pick_list/pick_list.py @@ -43,13 +43,9 @@ class PickList(Document): 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 2bcb93a69eb..ce3ec60d7ac 100644 --- a/erpnext/stock/doctype/pick_list/pick_list.py +++ b/erpnext/stock/doctype/pick_list/pick_list.py @@ -43,9 +43,12 @@ class PickList(Document): 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 f552299806c..1c90f32b376 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 @@ class TestPickList(FrappeTestCase): } ) 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 @@ class TestPickList(FrappeTestCase): } ) 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 @@ class TestPickList(FrappeTestCase): } ) 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 @@ class TestPickList(FrappeTestCase): 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 @@ class TestPickList(FrappeTestCase): 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 @@ class TestPickList(FrappeTestCase): 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 @@ class TestPickList(FrappeTestCase): 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 1c90f32b376..f552299806c 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 @@ class TestPickList(FrappeTestCase): } ) 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 @@ class TestPickList(FrappeTestCase): } ) 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 @@ class TestPickList(FrappeTestCase): } ) 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 @@ class TestPickList(FrappeTestCase): 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 @@ class TestPickList(FrappeTestCase): 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 @@ class TestPickList(FrappeTestCase): 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 @@ class TestPickList(FrappeTestCase): 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 5a73fea17ae..c8a539d2ebe 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 c8a539d2ebe..25b4f622177 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 ba1475347a9..70e530cbfe0 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 f32a05a266f..f2d7543cae9 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 ce3ec60d7ac..d2e266f9654 100644 --- a/erpnext/stock/doctype/pick_list/pick_list.py +++ b/erpnext/stock/doctype/pick_list/pick_list.py @@ -41,14 +41,16 @@ class PickList(Document): 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 25b4f622177..1cf8716e79d 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 1cf8716e79d..51ee680177e 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 70e530cbfe0..82663e3f02d 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 f2d7543cae9..28cd5df8368 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 28cd5df8368..ff209097a01 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 e545b8ea5c3..9a854311100 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 d2e266f9654..6b0e928baee 100644 --- a/erpnext/stock/doctype/pick_list/pick_list.py +++ b/erpnext/stock/doctype/pick_list/pick_list.py @@ -43,9 +43,9 @@ class PickList(Document): 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 51ee680177e..75105be4b27 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 75105be4b27..48c7a8fe3cc 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 82663e3f02d..799406cd79e 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 48c7a8fe3cc..3ae1234767c 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; - 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(); + // 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); - return existing_item_row || this.get_existing_blank_row(); + 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 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 46fb354c11c..276d6ff3725 100644 --- a/.eslintrc +++ b/.eslintrc @@ -5,7 +5,7 @@ "es6": true }, "parserOptions": { - "ecmaVersion": 9, + "ecmaVersion": 11, "sourceType": "module" }, "extends": "eslint:recommended",