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..641971dc --- /dev/null +++ b/inventory_tools/inventory_tools/report/pick_list_tool/pick_list_tool.js @@ -0,0 +1,109 @@ +// 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'), + is_group: 1, + }) + }, + }, + { + 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 == '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 + '' + } + } + 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_pick_list() + }, + __('Create') + ) + report.page.add_inner_button( + __('Pick List & Delivery Note'), + function () { + create_pick_list_delivery_note() + }, + __('Create') + ) + }, +} + +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.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..280752cb --- /dev/null +++ b/inventory_tools/inventory_tools/report/pick_list_tool/pick_list_tool.py @@ -0,0 +1,131 @@ +# 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 = ( + frappe.qb.from_(SalesOrder) + .join(SalesOrderItem) + .on(SalesOrder.name == SalesOrderItem.parent) + .select( + SalesOrder.name.as_("sales_order"), + SalesOrder.per_picked, + SalesOrderItem.item_code, + SalesOrderItem.warehouse, + SalesOrderItem.delivery_date, + SalesOrderItem.qty.as_("so_qty"), + ) + .where(SalesOrder.docstatus == 1) + .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: + 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: + 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")): + 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 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": "per_picked", + "label": "% Picked", + "fieldtype": "Data", + "width": "100px", + }, + { + "fieldname": "total_stock", + "fieldtype": "Data", + "label": "Total Stock", + "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)