diff --git a/sf_trading/api/quick_entry.py b/sf_trading/api/quick_entry.py new file mode 100644 index 0000000..6581024 --- /dev/null +++ b/sf_trading/api/quick_entry.py @@ -0,0 +1,85 @@ +import frappe +from frappe import _ + + +@frappe.whitelist() +def get_items_with_stock(company=None, price_list=None, item_code=None, limit=200): + """ + Return items that have stock in any non-group warehouse of the company, + one row per (item, warehouse) combination, with the selling rate from + the given price list when available. + + Honours User Permissions on Warehouse: if the current user is restricted + to one or more warehouses, only those will be returned. + """ + from frappe.core.doctype.user_permission.user_permission import get_permitted_documents + + if not company: + company = frappe.defaults.get_user_default("company") + + if not company: + return [] + + limit = int(limit or 200) + + params = {"company": company, "limit": limit} + where = [ + "bin.actual_qty > 0", + "item.is_sales_item = 1", + "item.disabled = 0", + "wh.company = %(company)s", + "wh.disabled = 0", + "wh.is_group = 0", + ] + + if item_code: + params["item_code"] = item_code + where.append("bin.item_code = %(item_code)s") + + # Restrict to warehouses the user is permitted to access via User Permissions. + # Administrator / users with no Warehouse permissions set see all warehouses. + permitted_warehouses = get_permitted_documents("Warehouse") + if permitted_warehouses: + params["permitted_warehouses"] = permitted_warehouses + where.append("bin.warehouse IN %(permitted_warehouses)s") + + price_join = "" + price_select = "NULL AS selling_rate, NULL AS price_currency" + if price_list: + params["price_list"] = price_list + price_join = ( + "LEFT JOIN `tabItem Price` ip " + "ON ip.item_code = bin.item_code " + "AND ip.price_list = %(price_list)s " + "AND ip.selling = 1" + ) + price_select = "ip.price_list_rate AS selling_rate, ip.currency AS price_currency" + + where_sql = " AND ".join(where) + + rows = frappe.db.sql( + """ + SELECT + bin.item_code, + item.item_name, + item.stock_uom, + bin.warehouse, + bin.actual_qty AS stock_qty, + {price_select} + FROM `tabBin` bin + INNER JOIN `tabItem` item ON item.name = bin.item_code + INNER JOIN `tabWarehouse` wh ON wh.name = bin.warehouse + {price_join} + WHERE {where_sql} + ORDER BY item.item_name, bin.warehouse + LIMIT %(limit)s + """.format( + price_select=price_select, + price_join=price_join, + where_sql=where_sql, + ), + params, + as_dict=True, + ) + + return rows diff --git a/sf_trading/hooks.py b/sf_trading/hooks.py index 6e9ccf3..1fafaa7 100644 --- a/sf_trading/hooks.py +++ b/sf_trading/hooks.py @@ -29,6 +29,7 @@ app_include_js = [ "/assets/sf_trading/js/warehouse_stock_popup.js", "/assets/sf_trading/js/last_selling_rate.js", + "/assets/sf_trading/js/quick_entry.js", "/assets/sf_trading/js/create_customer.js", "/assets/sf_trading/js/sales_invoice_barcode.js", "/assets/sf_trading/js/sales_invoice_inter_company.js", diff --git a/sf_trading/public/js/quick_entry.js b/sf_trading/public/js/quick_entry.js new file mode 100644 index 0000000..fc98560 --- /dev/null +++ b/sf_trading/public/js/quick_entry.js @@ -0,0 +1,292 @@ +// Quick Entry feature for sf_trading +// Adds a button to the items grid that opens a dialog listing items with stock, +// allowing the user to select items via checkboxes and add them to the items table. + +frappe.provide("sf_trading"); + +console.log("sf_trading: quick_entry.js loaded"); + +sf_trading.add_quick_entry_button = function (frm) { + if (!frm.fields_dict.items || !frm.fields_dict.items.grid) return; + + const grid = frm.fields_dict.items.grid; + + let $toolbar = grid.wrapper.find(".grid-buttons"); + if (!$toolbar.length) { + const $footer = grid.wrapper.find(".grid-footer"); + if ($footer.length) $toolbar = $footer.find(".grid-buttons"); + } + if (!$toolbar.length) { + const $addRowBtn = grid.wrapper.find("button:contains('Add Row')"); + if ($addRowBtn.length) $toolbar = $addRowBtn.closest(".grid-buttons"); + } + if (!$toolbar.length) return; + + if ($toolbar.find("button:contains('Quick Entry')").length > 0) return; + + // Position after Last Selling Rate button if present + let $target = $toolbar.find("button:contains('Last Selling Rate')").last(); + if ($target.length === 0) $target = $toolbar.find("button:contains('Add Row')").last(); + + const btn = $(``); + + btn.on('click', function () { + sf_trading.open_quick_entry_dialog(frm); + }); + + if ($target.length > 0 && $target.parent().is($toolbar)) { + btn.insertAfter($target); + } else { + $toolbar.append(btn); + } +}; + +sf_trading.open_quick_entry_dialog = function (frm) { + const company = frm.doc.company || frappe.defaults.get_default("company"); + const price_list = frm.doc.selling_price_list || frappe.defaults.get_default("selling_price_list"); + + const d = new frappe.ui.Dialog({ + title: __('Quick Entry'), + size: 'extra-large', + fields: [ + { + fieldname: 'item_code', + label: __('Item Code'), + fieldtype: 'Link', + options: 'Item', + get_query: function () { + return { filters: { "is_sales_item": 1, "disabled": 0 } }; + } + }, + { fieldname: 'results', fieldtype: 'HTML' } + ], + primary_action_label: __('Add'), + primary_action: function () { + sf_trading.add_quick_entry_selection(frm, d); + }, + secondary_action_label: __('Close'), + secondary_action: function () { + d.hide(); + } + }); + + d._frm = frm; + d._company = company; + d._price_list = price_list; + + d.show(); + + // Fetch full list initially; re-fetch when an item is picked + sf_trading.fetch_quick_entry_items(d); + + setTimeout(() => { + if (d.fields_dict.item_code) { + d.fields_dict.item_code.df.onchange = function () { + sf_trading.fetch_quick_entry_items(d); + }; + d.fields_dict.item_code.refresh(); + } + }, 200); +}; + +sf_trading.fetch_quick_entry_items = function (dialog) { + const $wrap = dialog.fields_dict.results.$wrapper; + $wrap.html('
| `, + ` | ` + __('Item Code') + ' | ', + `` + __('Item Name') + ' | ', + `` + __('Warehouse') + ' | ', + `` + __('Warehouse Stock') + ' | ', + `` + __('UOM') + ' | ', + `` + __('Selling Rate') + ' | ', + `` + __('Qty') + ' | ', + '
|---|---|---|---|---|---|---|---|
| `, + ` | ${item_code} | `, + `${item_name} | `, + `${warehouse} | `, + `${stock_qty} | `, + `${uom} | `, + `${rate_display} | `, + ``, + ' |