From 484fbf6855e871873f6c3177d0584dd58247977c Mon Sep 17 00:00:00 2001 From: fproldan Date: Fri, 13 Oct 2023 11:20:52 -0300 Subject: [PATCH 1/2] feat: wip pick list tool --- .../report/pick_list_tool/__init__.py | 0 .../report/pick_list_tool/pick_list_tool.js | 75 +++++++++++++++ .../report/pick_list_tool/pick_list_tool.json | 35 +++++++ .../report/pick_list_tool/pick_list_tool.py | 95 +++++++++++++++++++ 4 files changed, 205 insertions(+) create mode 100644 inventory_tools/inventory_tools/report/pick_list_tool/__init__.py create mode 100644 inventory_tools/inventory_tools/report/pick_list_tool/pick_list_tool.js create mode 100644 inventory_tools/inventory_tools/report/pick_list_tool/pick_list_tool.json create mode 100644 inventory_tools/inventory_tools/report/pick_list_tool/pick_list_tool.py diff --git a/inventory_tools/inventory_tools/report/pick_list_tool/__init__.py b/inventory_tools/inventory_tools/report/pick_list_tool/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/inventory_tools/inventory_tools/report/pick_list_tool/pick_list_tool.js b/inventory_tools/inventory_tools/report/pick_list_tool/pick_list_tool.js new file mode 100644 index 00000000..47568569 --- /dev/null +++ b/inventory_tools/inventory_tools/report/pick_list_tool/pick_list_tool.js @@ -0,0 +1,75 @@ +// Copyright (c) 2023, AgriTheory and contributors +// For license information, please see license.txt +/* eslint-disable */ + +frappe.query_reports['Pick List Tool'] = { + filters: [ + { + fieldname: 'company', + label: __('Company'), + fieldtype: 'Link', + options: 'Company', + default: frappe.defaults.get_user_default('Company'), + reqd: 1, + }, + { + fieldname: 'status', + label: __('Status'), + fieldtype: 'Select', + options: ['', 'Already Picked', 'Not Picked', 'Unshipped'], + }, + { + fieldname: 'delivery_date_start', + label: __('Delivery Date (start)'), + fieldtype: 'Date', + }, + { + fieldname: 'delivery_date_end', + label: __('Delivery Date (end)'), + fieldtype: 'Date', + }, + { + fieldname: 'warehouse', + label: __('Warehouse'), + fieldtype: 'MultiSelectList', + options: 'Warehouse', + get_data: function (txt) { + return frappe.db.get_link_options('Warehouse', txt, { + company: frappe.query_report.get_filter_value('company'), + }) + }, + }, + { + fieldname: 'customer', + label: __('Customer'), + fieldtype: 'Link', + options: 'Customer', + }, + ], + formatter: function (value, row, column, data, default_formatter) { + value = default_formatter(value, row, column, data) + if (column.fieldname == 'sales_order' && data && data.sales_order) { + value = value.bold() + } else if (column.fieldname == 'picked_percentage' && data && data.picked_percentage) { + if (data.picked_percentage == 100) { + value = "" + value + '' + } else { + value = "" + value + '' + } + } + return value + }, + onload: report => { + report.page.add_button('Check Stock', () => { + check_stock() + }) + report.page.add_button('Print Pick', () => { + print_pick() + }) + report.page.add_inner_button(__('Pick List'), function () {}, __('Create')) + report.page.add_inner_button(__('Pick List & Delivery Note'), function () {}, __('Create')) + }, +} + +function check_stock() {} +function print_pick() {} diff --git a/inventory_tools/inventory_tools/report/pick_list_tool/pick_list_tool.json b/inventory_tools/inventory_tools/report/pick_list_tool/pick_list_tool.json new file mode 100644 index 00000000..74d88636 --- /dev/null +++ b/inventory_tools/inventory_tools/report/pick_list_tool/pick_list_tool.json @@ -0,0 +1,35 @@ +{ + "add_total_row": 0, + "columns": [], + "creation": "2023-10-13 08:52:27.178388", + "disable_prepared_report": 0, + "disabled": 0, + "docstatus": 0, + "doctype": "Report", + "filters": [], + "idx": 0, + "is_standard": "Yes", + "modified": "2023-10-13 08:52:27.178388", + "modified_by": "Administrator", + "module": "Inventory Tools", + "name": "Pick List Tool", + "owner": "Administrator", + "prepared_report": 0, + "ref_doctype": "Pick List", + "report_name": "Pick List Tool", + "report_type": "Script Report", + "roles": [ + { + "role": "Stock Manager" + }, + { + "role": "Stock User" + }, + { + "role": "Manufacturing Manager" + }, + { + "role": "Manufacturing User" + } + ] +} diff --git a/inventory_tools/inventory_tools/report/pick_list_tool/pick_list_tool.py b/inventory_tools/inventory_tools/report/pick_list_tool/pick_list_tool.py new file mode 100644 index 00000000..f923dfec --- /dev/null +++ b/inventory_tools/inventory_tools/report/pick_list_tool/pick_list_tool.py @@ -0,0 +1,95 @@ +# Copyright (c) 2023, AgriTheory and contributors +# For license information, please see license.txt +from itertools import groupby + +import frappe +from frappe.query_builder import DocType + + +def execute(filters=None): + SalesOrder = DocType("Sales Order") + SalesOrderItem = DocType("Sales Order Item") + query = ( + frappe.qb.from_(SalesOrder) + .join(SalesOrderItem) + .on(SalesOrder.name == SalesOrderItem.parent) + .select( + SalesOrder.name.as_("sales_order"), + SalesOrderItem.item_code, + SalesOrderItem.warehouse, + SalesOrderItem.delivery_date, + SalesOrderItem.qty.as_("so_qty"), + ) + .where(SalesOrder.docstatus == 1) + .where(SalesOrder.company == filters.company) + ) + + if filters.customer: + query = query.where(SalesOrder.customer == filters.customer) + if filters.delivery_date_start: + query = query.where(SalesOrderItem.delivery_date >= filters.delivery_date_start) + if filters.delivery_date_end: + query = query.where(SalesOrderItem.delivery_date <= filters.delivery_date_end) + if filters.warehouse: + query = query.where(SalesOrderItem.warehouse.isin(filters.warehouse)) + + data = query.run(as_dict=1) + + output = [] + + for sales_order, _rows in groupby(data, lambda x: x.get("sales_order")): + output.append({"sales_order": sales_order, "indent": 0, "picked_percentage": "100"}) + for row in _rows: + del row["sales_order"] + output.append({**row, "indent": 1}) + + return get_columns(), output + + +def get_columns(): + return [ + { + "fieldname": "sales_order", + "fieldtype": "Link", + "options": "Sales Order", + "label": "Sales Order", + "width": "200px", + }, + { + "fieldname": "item_code", + "fieldtype": "Link", + "options": "Item", + "label": "Item", + "width": "250px", + }, + { + "fieldname": "warehouse", + "fieldtype": "Link", + "options": "Warehouse", + "label": "Warehouse", + "width": "250px", + }, + { + "fieldname": "so_qty", + "label": "SO Qty", + "fieldtype": "Data", + }, + { + "fieldname": "delivery_date", + "label": "Delivery Date", + "fieldtype": "Date", + "width": "120px", + }, + { + "fieldname": "picked_percentage", + "label": "% Picked", + "fieldtype": "Data", + "width": "100px", + }, + { + "fieldname": "total_stock", + "fieldtype": "Data", + "label": "Total Stock", + "width": "120px", + }, + ] From 4cb5fa7a753a8acd231cea7e35ef9103fa98b20e Mon Sep 17 00:00:00 2001 From: fproldan Date: Wed, 18 Oct 2023 09:32:22 -0300 Subject: [PATCH 2/2] feat: more report features --- .../report/pick_list_tool/pick_list_tool.js | 46 ++++++++++++++--- .../report/pick_list_tool/pick_list_tool.py | 50 ++++++++++++++++--- 2 files changed, 83 insertions(+), 13 deletions(-) diff --git a/inventory_tools/inventory_tools/report/pick_list_tool/pick_list_tool.js b/inventory_tools/inventory_tools/report/pick_list_tool/pick_list_tool.js index 47568569..641971dc 100644 --- a/inventory_tools/inventory_tools/report/pick_list_tool/pick_list_tool.js +++ b/inventory_tools/inventory_tools/report/pick_list_tool/pick_list_tool.js @@ -36,6 +36,7 @@ frappe.query_reports['Pick List Tool'] = { get_data: function (txt) { return frappe.db.get_link_options('Warehouse', txt, { company: frappe.query_report.get_filter_value('company'), + is_group: 1, }) }, }, @@ -50,8 +51,14 @@ frappe.query_reports['Pick List Tool'] = { value = default_formatter(value, row, column, data) if (column.fieldname == 'sales_order' && data && data.sales_order) { value = value.bold() - } else if (column.fieldname == 'picked_percentage' && data && data.picked_percentage) { - if (data.picked_percentage == 100) { + } else if (column.fieldname == 'per_picked' && data && data.per_picked !== null) { + if (data.per_picked == 100) { + value = "" + value + '' + } else { + value = "" + value + '' + } + } else if (column.fieldname == 'total_stock' && data && data.total_stock !== null) { + if (data.total_stock == 'Total Availability') { value = "" + value + '' } else { value = "" + value + '' @@ -66,10 +73,37 @@ frappe.query_reports['Pick List Tool'] = { report.page.add_button('Print Pick', () => { print_pick() }) - report.page.add_inner_button(__('Pick List'), function () {}, __('Create')) - report.page.add_inner_button(__('Pick List & Delivery Note'), function () {}, __('Create')) + report.page.add_inner_button( + __('Pick List'), + function () { + create_pick_list() + }, + __('Create') + ) + report.page.add_inner_button( + __('Pick List & Delivery Note'), + function () { + create_pick_list_delivery_note() + }, + __('Create') + ) }, } -function check_stock() {} -function print_pick() {} +async function check_stock() { + let values = frappe.query_report.get_filter_values() + await frappe + .xcall('inventory_tools.inventory_tools.report.pick_list_tool.pick_list_tool.check_stock', { + filters: values, + }) + .then(r => {}) +} +function print_pick() { + if (!frappe.query_report.filters || frappe.query_report.get_filter_value('status') != 'Already Picked') { + frappe.msgprint('Only can print Already Picked') + return + } +} + +function create_pick_list() {} +function create_pick_list_delivery_note() {} diff --git a/inventory_tools/inventory_tools/report/pick_list_tool/pick_list_tool.py b/inventory_tools/inventory_tools/report/pick_list_tool/pick_list_tool.py index f923dfec..280752cb 100644 --- a/inventory_tools/inventory_tools/report/pick_list_tool/pick_list_tool.py +++ b/inventory_tools/inventory_tools/report/pick_list_tool/pick_list_tool.py @@ -1,12 +1,19 @@ # Copyright (c) 2023, AgriTheory and contributors # For license information, please see license.txt +import json from itertools import groupby import frappe +from erpnext.stock.doctype.pick_list.pick_list import get_available_item_locations from frappe.query_builder import DocType +from frappe.utils.nestedset import get_descendants_of def execute(filters=None): + return get_columns(), get_report_output(filters) + + +def get_report_output(filters=None, ignore_validation=True): SalesOrder = DocType("Sales Order") SalesOrderItem = DocType("Sales Order Item") query = ( @@ -15,6 +22,7 @@ def execute(filters=None): .on(SalesOrder.name == SalesOrderItem.parent) .select( SalesOrder.name.as_("sales_order"), + SalesOrder.per_picked, SalesOrderItem.item_code, SalesOrderItem.warehouse, SalesOrderItem.delivery_date, @@ -24,6 +32,13 @@ def execute(filters=None): .where(SalesOrder.company == filters.company) ) + if filters.status: + if filters.status == "Already Picked": + query = query.where(SalesOrder.per_picked == 100) + elif filters.status == "Not Picked": + query = query.where(SalesOrder.per_picked < 100) + elif filters.status == "Unshipped": + query = query.where(SalesOrder.per_delivered < 100) if filters.customer: query = query.where(SalesOrder.customer == filters.customer) if filters.delivery_date_start: @@ -31,19 +46,34 @@ def execute(filters=None): if filters.delivery_date_end: query = query.where(SalesOrderItem.delivery_date <= filters.delivery_date_end) if filters.warehouse: - query = query.where(SalesOrderItem.warehouse.isin(filters.warehouse)) + from_warehouses = get_descendants_of("Warehouse", filters.warehouse) + else: + from_warehouses = frappe.get_all("Warehouse", pluck="name") + query = query.where(SalesOrderItem.warehouse.isin(from_warehouses)) data = query.run(as_dict=1) - output = [] for sales_order, _rows in groupby(data, lambda x: x.get("sales_order")): - output.append({"sales_order": sales_order, "indent": 0, "picked_percentage": "100"}) - for row in _rows: + rows = list(_rows) + output.append( + {"sales_order": sales_order, "indent": 0, "per_picked": f'{rows[0]["per_picked"]} %'} + ) + for row in rows: del row["sales_order"] + del row["per_picked"] + qty_available = get_available_item_locations( + row["item_code"], + from_warehouses, + row["so_qty"], + filters.company, + ignore_validation=ignore_validation, + picked_item_details=None, + ) + row["total_stock"] = "Total Availability" if qty_available else "Not Total Availability" output.append({**row, "indent": 1}) - return get_columns(), output + return output def get_columns(): @@ -81,7 +111,7 @@ def get_columns(): "width": "120px", }, { - "fieldname": "picked_percentage", + "fieldname": "per_picked", "label": "% Picked", "fieldtype": "Data", "width": "100px", @@ -90,6 +120,12 @@ def get_columns(): "fieldname": "total_stock", "fieldtype": "Data", "label": "Total Stock", - "width": "120px", + "width": "150px", }, ] + + +@frappe.whitelist() +def check_stock(filters): + filters = frappe._dict(json.loads(filters)) if isinstance(filters, str) else filters + get_report_output(filters=filters, ignore_validation=False)