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)