diff --git a/westech_r2/doctype/load/__pycache__/__init__.cpython-312.pyc b/westech_r2/doctype/load/__pycache__/__init__.cpython-312.pyc new file mode 100644 index 0000000..ad4acfc Binary files /dev/null and b/westech_r2/doctype/load/__pycache__/__init__.cpython-312.pyc differ diff --git a/westech_r2/doctype/load/__pycache__/load.cpython-312.pyc b/westech_r2/doctype/load/__pycache__/load.cpython-312.pyc new file mode 100644 index 0000000..cb6d7e4 Binary files /dev/null and b/westech_r2/doctype/load/__pycache__/load.cpython-312.pyc differ diff --git a/westech_r2/fixtures/doctype.json b/westech_r2/fixtures/doctype.json index b881c2e..3c0cf08 100644 --- a/westech_r2/fixtures/doctype.json +++ b/westech_r2/fixtures/doctype.json @@ -79615,7 +79615,7 @@ "columns": 0, "default": null, "depends_on": null, - "description": "For multiple addresses, enter the address on different line. e.g. test@test.com ⏎ test1@test.com", + "description": "For multiple addresses, enter the address on different line. e.g. test@test.com \u23ce test1@test.com", "documentation_url": null, "fetch_from": null, "fetch_if_empty": 0, @@ -139175,7 +139175,7 @@ "columns": 0, "default": "0", "depends_on": null, - "description": "If this checkbox is enabled, then the system won’t run the MRP for the available sub-assembly items.", + "description": "If this checkbox is enabled, then the system won\u2019t run the MRP for the available sub-assembly items.", "documentation_url": null, "fetch_from": null, "fetch_if_empty": 0, @@ -265328,7 +265328,7 @@ "columns": 0, "default": null, "depends_on": "eval:doc.frequency==='Cron'", - "description": "
*  *  *  *  *\n┬  ┬  ┬  ┬  ┬\n│  │  │  │  │\n│  │  │  │  └ day of week (0 - 6) (0 is Sunday)\n│  │  │  └───── month (1 - 12)\n│  │  └────────── day of month (1 - 31)\n│  └─────────────── hour (0 - 23)\n└──────────────────── minute (0 - 59)\n\n---\n\n* - Any value\n/ - Step values\n
\n", + "description": "
*  *  *  *  *\n\u252c  \u252c  \u252c  \u252c  \u252c\n\u2502  \u2502  \u2502  \u2502  \u2502\n\u2502  \u2502  \u2502  \u2502  \u2514 day of week (0 - 6) (0 is Sunday)\n\u2502  \u2502  \u2502  \u2514\u2500\u2500\u2500\u2500\u2500 month (1 - 12)\n\u2502  \u2502  \u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 day of month (1 - 31)\n\u2502  \u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 hour (0 - 23)\n\u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 minute (0 - 59)\n\n---\n\n* - Any value\n/ - Step values\n
\n", "documentation_url": null, "fetch_from": null, "fetch_if_empty": 0, @@ -267871,7 +267871,7 @@ "columns": 0, "default": null, "depends_on": "eval:doc.script_type === \"Scheduler Event\" && doc.event_frequency === 'Cron'", - "description": "
*  *  *  *  *\n┬  ┬  ┬  ┬  ┬\n│  │  │  │  │\n│  │  │  │  └ day of week (0 - 6) (0 is Sunday)\n│  │  │  └───── month (1 - 12)\n│  │  └────────── day of month (1 - 31)\n│  └─────────────── hour (0 - 23)\n└──────────────────── minute (0 - 59)\n\n---\n\n* - Any value\n/ - Step values\n
\n", + "description": "
*  *  *  *  *\n\u252c  \u252c  \u252c  \u252c  \u252c\n\u2502  \u2502  \u2502  \u2502  \u2502\n\u2502  \u2502  \u2502  \u2502  \u2514 day of week (0 - 6) (0 is Sunday)\n\u2502  \u2502  \u2502  \u2514\u2500\u2500\u2500\u2500\u2500 month (1 - 12)\n\u2502  \u2502  \u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 day of month (1 - 31)\n\u2502  \u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 hour (0 - 23)\n\u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 minute (0 - 59)\n\n---\n\n* - Any value\n/ - Step values\n
\n", "documentation_url": null, "fetch_from": null, "fetch_if_empty": 0, @@ -475145,7 +475145,7 @@ "fetch_from": null, "fetch_if_empty": 0, "fieldname": "material_type", - "fieldtype": "Select", + "fieldtype": "Data", "hidden": 0, "hide_border": 0, "hide_days": 0, @@ -475168,7 +475168,7 @@ "non_negative": 0, "oldfieldname": null, "oldfieldtype": null, - "options": "\nChromebook / Notebook\nAll In One\nHPStream\nLaptop\nDesktop\nServer\nExternal Hard Drive\nLoose Hard Drive\nLoose SSD or mSATA Drive\nCD / Floppy / DVD / Tapes\nCell Phone / Smart Phone\nDVR\nGaming Systems\nGPS\nNetwork / Modems / Routers\nOffice/ IP Phone\nPersonal Electronics / PDA\nPOS\nPOS Terminals\nPrinters/Copiers\nPrinters/Copiers Hard Drives\nSD Cards\nSmart TV\nCRT TV\nSwitch\nTablet\nThin Clients\nUSB Drive\n# Of Pallets\nDesktops", + "options": "", "parent": "Load Item", "parentfield": "fields", "parenttype": "DocType", @@ -475529,7 +475529,7 @@ "fetch_from": null, "fetch_if_empty": 0, "fieldname": "initial_data_status", - "fieldtype": "Select", + "fieldtype": "Data", "hidden": 0, "hide_border": 0, "hide_days": 0, @@ -475552,7 +475552,7 @@ "non_negative": 0, "oldfieldname": null, "oldfieldtype": null, - "options": "\nD0\nD1\nND1\nND2\nND3\nND4\nND5", + "options": "", "parent": "Load Item", "parentfield": "fields", "parenttype": "DocType", @@ -475593,7 +475593,7 @@ "fetch_from": null, "fetch_if_empty": 0, "fieldname": "send_to", - "fieldtype": "Select", + "fieldtype": "Data", "hidden": 0, "hide_border": 0, "hide_days": 0, @@ -475616,7 +475616,7 @@ "non_negative": 0, "oldfieldname": null, "oldfieldtype": null, - "options": "\nHDR\nR2 Downstream Vendor\nShred / Degauss\nWiping\nResale\nTest\nDisassembly\nInventory", + "options": "", "parent": "Load Item", "parentfield": "fields", "parenttype": "DocType", @@ -475849,7 +475849,7 @@ "fetch_from": null, "fetch_if_empty": 0, "fieldname": "disassembly_data_status", - "fieldtype": "Select", + "fieldtype": "Data", "hidden": 0, "hide_border": 0, "hide_days": 0, @@ -475872,7 +475872,7 @@ "non_negative": 0, "oldfieldname": null, "oldfieldtype": null, - "options": "\nD0\nD1\nND1\nND2\nND3\nND4\nND5", + "options": "", "parent": "Load Item", "parentfield": "fields", "parenttype": "DocType", @@ -475913,7 +475913,7 @@ "fetch_from": null, "fetch_if_empty": 0, "fieldname": "disassembly_send_to", - "fieldtype": "Select", + "fieldtype": "Data", "hidden": 0, "hide_border": 0, "hide_days": 0, @@ -475936,7 +475936,7 @@ "non_negative": 0, "oldfieldname": null, "oldfieldtype": null, - "options": "\nHDR\nR2 Downstream Vendor\nShred / Degauss\nWiping\nResale\nTest\nDisassembly\nInventory", + "options": "", "parent": "Load Item", "parentfield": "fields", "parenttype": "DocType", @@ -476105,7 +476105,7 @@ "fetch_from": null, "fetch_if_empty": 0, "fieldname": "test_data_status", - "fieldtype": "Select", + "fieldtype": "Data", "hidden": 0, "hide_border": 0, "hide_days": 0, @@ -476128,7 +476128,7 @@ "non_negative": 0, "oldfieldname": null, "oldfieldtype": null, - "options": "\nD0\nD1\nND1\nND2\nND3\nND4\nND5", + "options": "", "parent": "Load Item", "parentfield": "fields", "parenttype": "DocType", @@ -476169,7 +476169,7 @@ "fetch_from": null, "fetch_if_empty": 0, "fieldname": "test_send_to", - "fieldtype": "Select", + "fieldtype": "Data", "hidden": 0, "hide_border": 0, "hide_days": 0, @@ -476192,7 +476192,7 @@ "non_negative": 0, "oldfieldname": null, "oldfieldtype": null, - "options": "\nHDR\nR2 Downstream Vendor\nShred / Degauss\nWiping\nResale\nTest\nDisassembly\nInventory", + "options": "", "parent": "Load Item", "parentfield": "fields", "parenttype": "DocType", diff --git a/westech_r2/page/__init__.py b/westech_r2/page/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/westech_r2/page/load-detail/__init__.py b/westech_r2/page/load-detail/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/westech_r2/page/load-detail/load-detail.css b/westech_r2/page/load-detail/load-detail.css new file mode 100644 index 0000000..f077569 --- /dev/null +++ b/westech_r2/page/load-detail/load-detail.css @@ -0,0 +1 @@ +/* CSS */ diff --git a/westech_r2/page/load-detail/load-detail.html b/westech_r2/page/load-detail/load-detail.html new file mode 100644 index 0000000..ec9f8f8 --- /dev/null +++ b/westech_r2/page/load-detail/load-detail.html @@ -0,0 +1,53 @@ +
+ + +
+

Load:

+
In Date: | Customer: | Devices: | Weight: lbs
+
+ +
+ Receiving + HDR / Disassembly + Test + R2 Downstream + Destruction +
+ + + + + + + + + + + + + + + + + + + + + + + +
Material TypeReceivingHDR / DisassemblyTestR2Destruction
CountStatusSend ToRecv'dHDD OutSend ToSanitizedStatusSend ToSentPhysHDD NeedHDD PhysHDD Logic
+
\ No newline at end of file diff --git a/westech_r2/page/load-detail/load-detail.js b/westech_r2/page/load-detail/load-detail.js new file mode 100644 index 0000000..7b02407 --- /dev/null +++ b/westech_r2/page/load-detail/load-detail.js @@ -0,0 +1,38 @@ +frappe.pages["load-detail"].on_page_load = function(wrapper) { + var page = frappe.ui.make_app_page({ + parent: wrapper, + title: "Load Detail", + single_column: true + }); + var loadName = frappe.utils.get_url_arg("load"); + if (!loadName) { + $(page.body).html("

No load specified

Use ?load=MMDDYYYY-XXXX

"); + return; + } + frappe.call({ + method: "frappe.client.get", + args: { doctype: "Load", name: loadName }, + callback: function(r) { + if (r.message) { showLoad(page, r.message); } + else { $(page.body).html("

Load not found

"); } + } + }); + function showLoad(page, load) { + var h = "
"; + h += "

Load: " + load.name + "

"; + h += "

Date: " + (load.incoming_date || "N/A") + " | Customer: " + (load.customer_name || load.customer_number || "N/A") + "

"; + h += "

Total Devices: " + (load.total_devices || 0) + "

"; + h += ""; + if (load.material_items && load.material_items.length > 0) { + load.material_items.forEach(function(item) { + h += ""; + h += ""; + h += ""; + h += ""; + h += ""; + }); + } + h += "
Material TypeCountStatusSend To
" + (item.material_type || "") + "" + (item.total_count || "") + "" + (item.initial_data_status || "") + "" + (item.send_to || "") + "
"; + $(page.body).html(h); + } +}; diff --git a/westech_r2/page/load-detail/load_detail.css b/westech_r2/page/load-detail/load_detail.css new file mode 100644 index 0000000..fd15dfe --- /dev/null +++ b/westech_r2/page/load-detail/load_detail.css @@ -0,0 +1 @@ +/* Load Detail page CSS */ diff --git a/westech_r2/page/load-detail/load_detail.js b/westech_r2/page/load-detail/load_detail.js new file mode 100644 index 0000000..af78546 --- /dev/null +++ b/westech_r2/page/load-detail/load_detail.js @@ -0,0 +1 @@ +// Load Detail page JS diff --git a/westech_r2/page/load-detail/load_detail.json b/westech_r2/page/load-detail/load_detail.json new file mode 100644 index 0000000..77dbb1d --- /dev/null +++ b/westech_r2/page/load-detail/load_detail.json @@ -0,0 +1,18 @@ +{ + "doctype": "Page", + "name": "load-detail", + "page_name": "load-detail", + "title": "Load Detail", + "page_type": "Web Page", + "module": "Westech R2", + "standard": "Yes", + "system_page": 0, + "roles": [ + { + "role": "All" + } + ], + "content": "", + "script": null, + "style": null +} \ No newline at end of file diff --git a/westech_r2/page/load-update/__init__.py b/westech_r2/page/load-update/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/westech_r2/page/load-update/load-update.html b/westech_r2/page/load-update/load-update.html new file mode 100644 index 0000000..6e7e05e --- /dev/null +++ b/westech_r2/page/load-update/load-update.html @@ -0,0 +1,4 @@ +
+

Load Update

+

Use the form below to update load material items.

+
diff --git a/westech_r2/page/load-update/load-update.js b/westech_r2/page/load-update/load-update.js new file mode 100644 index 0000000..ed8b54b --- /dev/null +++ b/westech_r2/page/load-update/load-update.js @@ -0,0 +1,105 @@ +frappe.pages["load-update"].on_page_load = function(wrapper) { + var page = frappe.ui.make_app_page({ + parent: wrapper, + title: "Load Update", + single_column: true + }); + var loadName = frappe.utils.get_url_arg("load"); + if (!loadName) { + $(page.body).html("

No load specified

Use ?load=MMDDYYYY-XXXX

"); + return; + } + + function loadLoad() { + frappe.call({ + method: "frappe.client.get", + args: { doctype: "Load", name: loadName }, + callback: function(r) { + if (r.message) { renderForm(page, r.message); } + else { $(page.body).html("

Load not found

"); } + } + }); + } + + function renderForm(page, load) { + var h = "
"; + h += "

Update Load: " + load.name + "

"; + h += "

Date: " + (load.incoming_date || "N/A") + " | Customer: " + (load.customer_name || load.customer_number || "N/A") + "

"; + h += "
"; + h += ""; + h += " "; + h += " Open Form View"; + h += "
"; + + h += ""; + h += ""; + h += ""; + + if (load.material_items && load.material_items.length > 0) { + load.material_items.forEach(function(item, idx) { + var prefix = "item_" + idx + "_"; + h += ""; + h += ""; + h += ""; + h += ""; + h += ""; + h += ""; + h += ""; + h += ""; + h += ""; + h += ""; + h += ""; + h += ""; + h += ""; + h += ""; + h += ""; + h += ""; + h += ""; + }); + } + h += "
Material TypeCountRecv StatusSend ToRecv'dHDD OutDis Send ToSanitizedTest StatusTest SendR2 SentPhysHDD NeedHDD PhysHDD Logic
" + (item.material_type || "") + "
"; + $(page.body).html(h); + + $("#lu-save").on("click", function() { + var items = []; + load.material_items.forEach(function(item, idx) { + var prefix = "#item_" + idx + "_"; + items.push({ + name: item.name, + total_count: parseInt($(prefix + "total_count").val()) || 0, + initial_data_status: $(prefix + "initial_data_status").val(), + send_to: $(prefix + "send_to").val(), + devices_received: parseInt($(prefix + "devices_received").val()) || 0, + hdd_removed: parseInt($(prefix + "hdd_removed").val()) || 0, + disassembly_send_to: $(prefix + "disassembly_send_to").val(), + units_sanitized_software: parseInt($(prefix + "units_sanitized_software").val()) || 0, + test_data_status: $(prefix + "test_data_status").val(), + test_send_to: $(prefix + "test_send_to").val(), + r2_units_sent: parseInt($(prefix + "r2_units_sent").val()) || 0, + units_physical_destruction: parseInt($(prefix + "units_physical_destruction").val()) || 0, + hdd_needs_sanitize: parseInt($(prefix + "hdd_needs_sanitize").val()) || 0, + hdd_physical_destruction: parseInt($(prefix + "hdd_physical_destruction").val()) || 0, + hdd_logical_sanitization: parseInt($(prefix + "hdd_logical_sanitization").val()) || 0 + }); + }); + frappe.call({ + method: "westech_r2.page.load-update.load-update.save_load_items", + args: { load_name: loadName, items: JSON.stringify(items) }, + callback: function(r) { + if (r.message && r.message.status === "ok") { + frappe.show_alert({message: "Saved", indicator: "green"}); + loadLoad(); + } else { + frappe.show_alert({message: "Save failed", indicator: "red"}); + } + } + }); + }); + + $("#lu-print").on("click", function() { + window.print(); + }); + } + + loadLoad(); +}; diff --git a/westech_r2/page/load-update/load-update.py b/westech_r2/page/load-update/load-update.py new file mode 100644 index 0000000..4afaf5e --- /dev/null +++ b/westech_r2/page/load-update/load-update.py @@ -0,0 +1,17 @@ +import frappe +import json + +@frappe.whitelist() +def save_load_items(load_name, items): + items = json.loads(items) + load_doc = frappe.get_doc("Load", load_name) + for item_data in items: + for row in load_doc.material_items: + if row.name == item_data["name"]: + for field, value in item_data.items(): + if field != "name": + row.set(field, value) + break + load_doc.save(ignore_permissions=True) + frappe.db.commit() + return {"status": "ok", "message": "Saved " + str(len(items)) + " items"} diff --git a/westech_r2/page/load-update/load_update.css b/westech_r2/page/load-update/load_update.css new file mode 100644 index 0000000..3404ed3 --- /dev/null +++ b/westech_r2/page/load-update/load_update.css @@ -0,0 +1 @@ +/* Load Update page CSS */ diff --git a/westech_r2/page/load-update/load_update.html b/westech_r2/page/load-update/load_update.html new file mode 100644 index 0000000..6e7e05e --- /dev/null +++ b/westech_r2/page/load-update/load_update.html @@ -0,0 +1,4 @@ +
+

Load Update

+

Use the form below to update load material items.

+
diff --git a/westech_r2/page/load-update/load_update.js b/westech_r2/page/load-update/load_update.js new file mode 100644 index 0000000..ed8b54b --- /dev/null +++ b/westech_r2/page/load-update/load_update.js @@ -0,0 +1,105 @@ +frappe.pages["load-update"].on_page_load = function(wrapper) { + var page = frappe.ui.make_app_page({ + parent: wrapper, + title: "Load Update", + single_column: true + }); + var loadName = frappe.utils.get_url_arg("load"); + if (!loadName) { + $(page.body).html("

No load specified

Use ?load=MMDDYYYY-XXXX

"); + return; + } + + function loadLoad() { + frappe.call({ + method: "frappe.client.get", + args: { doctype: "Load", name: loadName }, + callback: function(r) { + if (r.message) { renderForm(page, r.message); } + else { $(page.body).html("

Load not found

"); } + } + }); + } + + function renderForm(page, load) { + var h = "
"; + h += "

Update Load: " + load.name + "

"; + h += "

Date: " + (load.incoming_date || "N/A") + " | Customer: " + (load.customer_name || load.customer_number || "N/A") + "

"; + h += "
"; + h += ""; + h += " "; + h += " Open Form View"; + h += "
"; + + h += ""; + h += ""; + h += ""; + + if (load.material_items && load.material_items.length > 0) { + load.material_items.forEach(function(item, idx) { + var prefix = "item_" + idx + "_"; + h += ""; + h += ""; + h += ""; + h += ""; + h += ""; + h += ""; + h += ""; + h += ""; + h += ""; + h += ""; + h += ""; + h += ""; + h += ""; + h += ""; + h += ""; + h += ""; + h += ""; + }); + } + h += "
Material TypeCountRecv StatusSend ToRecv'dHDD OutDis Send ToSanitizedTest StatusTest SendR2 SentPhysHDD NeedHDD PhysHDD Logic
" + (item.material_type || "") + "
"; + $(page.body).html(h); + + $("#lu-save").on("click", function() { + var items = []; + load.material_items.forEach(function(item, idx) { + var prefix = "#item_" + idx + "_"; + items.push({ + name: item.name, + total_count: parseInt($(prefix + "total_count").val()) || 0, + initial_data_status: $(prefix + "initial_data_status").val(), + send_to: $(prefix + "send_to").val(), + devices_received: parseInt($(prefix + "devices_received").val()) || 0, + hdd_removed: parseInt($(prefix + "hdd_removed").val()) || 0, + disassembly_send_to: $(prefix + "disassembly_send_to").val(), + units_sanitized_software: parseInt($(prefix + "units_sanitized_software").val()) || 0, + test_data_status: $(prefix + "test_data_status").val(), + test_send_to: $(prefix + "test_send_to").val(), + r2_units_sent: parseInt($(prefix + "r2_units_sent").val()) || 0, + units_physical_destruction: parseInt($(prefix + "units_physical_destruction").val()) || 0, + hdd_needs_sanitize: parseInt($(prefix + "hdd_needs_sanitize").val()) || 0, + hdd_physical_destruction: parseInt($(prefix + "hdd_physical_destruction").val()) || 0, + hdd_logical_sanitization: parseInt($(prefix + "hdd_logical_sanitization").val()) || 0 + }); + }); + frappe.call({ + method: "westech_r2.page.load-update.load-update.save_load_items", + args: { load_name: loadName, items: JSON.stringify(items) }, + callback: function(r) { + if (r.message && r.message.status === "ok") { + frappe.show_alert({message: "Saved", indicator: "green"}); + loadLoad(); + } else { + frappe.show_alert({message: "Save failed", indicator: "red"}); + } + } + }); + }); + + $("#lu-print").on("click", function() { + window.print(); + }); + } + + loadLoad(); +}; diff --git a/westech_r2/page/load-update/load_update.json b/westech_r2/page/load-update/load_update.json new file mode 100644 index 0000000..b5c8853 --- /dev/null +++ b/westech_r2/page/load-update/load_update.json @@ -0,0 +1,18 @@ +{ + "doctype": "Page", + "name": "load-update", + "page_name": "load-update", + "title": "Load Update", + "page_type": "Web Page", + "module": "Westech R2", + "standard": "Yes", + "system_page": 0, + "roles": [ + { + "role": "All" + } + ], + "content": "", + "script": null, + "style": null +} \ No newline at end of file diff --git a/westech_r2/page/load-update/load_update.py b/westech_r2/page/load-update/load_update.py new file mode 100644 index 0000000..4afaf5e --- /dev/null +++ b/westech_r2/page/load-update/load_update.py @@ -0,0 +1,17 @@ +import frappe +import json + +@frappe.whitelist() +def save_load_items(load_name, items): + items = json.loads(items) + load_doc = frappe.get_doc("Load", load_name) + for item_data in items: + for row in load_doc.material_items: + if row.name == item_data["name"]: + for field, value in item_data.items(): + if field != "name": + row.set(field, value) + break + load_doc.save(ignore_permissions=True) + frappe.db.commit() + return {"status": "ok", "message": "Saved " + str(len(items)) + " items"} diff --git a/westech_r2/page/load_detail/__init__.py b/westech_r2/page/load_detail/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/westech_r2/page/load_detail/load-detail.js b/westech_r2/page/load_detail/load-detail.js new file mode 100644 index 0000000..b13cdca --- /dev/null +++ b/westech_r2/page/load_detail/load-detail.js @@ -0,0 +1,65 @@ +frappe.pages["load-detail"].on_page_load = function(wrapper) { + var page = frappe.ui.make_app_page({ + parent: wrapper, + title: "Load Detail", + single_column: true + }); + + var loadName = frappe.utils.get_url_arg("load"); + if (!loadName) { + $(page.body).html("

No load specified

Use ?load=MMDDYYYY-XXXX

"); + return; + } + + frappe.call({ + method: "frappe.client.get", + args: { doctype: "Load", name: loadName }, + callback: function(r) { + if (r.message) { showLoad(page, r.message); } + else { $(page.body).html("

Load not found

"); } + } + }); + + function showLoad(page, load) { + var h = "
"; + h += "
"; + h += "

Load: " + load.name + "

"; + h += "
In Date: " + (load.incoming_date || "N/A") + " | Customer: " + (load.customer_name || load.customer_number || "N/A") + "
"; + h += "
Devices: " + (load.total_devices || 0) + " | Weight: " + (load.total_weight || 0) + " lbs
"; + h += "
"; + h += " "; + h += "Open Form View "; + h += "Edit Load"; + h += ""; + h += ""; + h += ""; + h += ""; + h += ""; + h += ""; + h += ""; + + if (load.material_items && load.material_items.length > 0) { + load.material_items.forEach(function(item) { + h += ""; + h += ""; + h += ""; + h += ""; + h += ""; + h += ""; + h += ""; + h += ""; + h += ""; + h += ""; + h += ""; + h += ""; + h += ""; + h += ""; + h += ""; + h += ""; + h += ""; + }); + } + h += "
Material TypeCountRcv StatusSend ToRecv'dHDD OutDis Send ToSanitizedTest StatusTest SendR2 SentPhysHDD NeedHDD PhysHDD Logic
" + (item.material_type || "") + "" + (item.total_count || 0) + "" + (item.initial_data_status || "") + "" + (item.send_to || "") + "" + (item.devices_received || 0) + "" + (item.hdd_removed || 0) + "" + (item.disassembly_send_to || "") + "" + (item.units_sanitized_software || 0) + "" + (item.test_data_status || "") + "" + (item.test_send_to || "") + "" + (item.r2_units_sent || 0) + "" + (item.units_physical_destruction || 0) + "" + (item.hdd_needs_sanitize || 0) + "" + (item.hdd_physical_destruction || 0) + "" + (item.hdd_logical_sanitization || 0) + "
"; + $(page.body).html(h); + } +}; diff --git a/westech_r2/page/load_detail/load_detail.css b/westech_r2/page/load_detail/load_detail.css new file mode 100644 index 0000000..f077569 --- /dev/null +++ b/westech_r2/page/load_detail/load_detail.css @@ -0,0 +1 @@ +/* CSS */ diff --git a/westech_r2/page/load_detail/load_detail.html b/westech_r2/page/load_detail/load_detail.html new file mode 100644 index 0000000..ec9f8f8 --- /dev/null +++ b/westech_r2/page/load_detail/load_detail.html @@ -0,0 +1,53 @@ +
+ + +
+

Load:

+
In Date: | Customer: | Devices: | Weight: lbs
+
+ +
+ Receiving + HDR / Disassembly + Test + R2 Downstream + Destruction +
+ + + + + + + + + + + + + + + + + + + + + + + +
Material TypeReceivingHDR / DisassemblyTestR2Destruction
CountStatusSend ToRecv'dHDD OutSend ToSanitizedStatusSend ToSentPhysHDD NeedHDD PhysHDD Logic
+
\ No newline at end of file diff --git a/westech_r2/page/load_detail/load_detail.js b/westech_r2/page/load_detail/load_detail.js new file mode 100644 index 0000000..b13cdca --- /dev/null +++ b/westech_r2/page/load_detail/load_detail.js @@ -0,0 +1,65 @@ +frappe.pages["load-detail"].on_page_load = function(wrapper) { + var page = frappe.ui.make_app_page({ + parent: wrapper, + title: "Load Detail", + single_column: true + }); + + var loadName = frappe.utils.get_url_arg("load"); + if (!loadName) { + $(page.body).html("

No load specified

Use ?load=MMDDYYYY-XXXX

"); + return; + } + + frappe.call({ + method: "frappe.client.get", + args: { doctype: "Load", name: loadName }, + callback: function(r) { + if (r.message) { showLoad(page, r.message); } + else { $(page.body).html("

Load not found

"); } + } + }); + + function showLoad(page, load) { + var h = "
"; + h += "
"; + h += "

Load: " + load.name + "

"; + h += "
In Date: " + (load.incoming_date || "N/A") + " | Customer: " + (load.customer_name || load.customer_number || "N/A") + "
"; + h += "
Devices: " + (load.total_devices || 0) + " | Weight: " + (load.total_weight || 0) + " lbs
"; + h += "
"; + h += " "; + h += "Open Form View "; + h += "Edit Load"; + h += ""; + h += ""; + h += ""; + h += ""; + h += ""; + h += ""; + h += ""; + + if (load.material_items && load.material_items.length > 0) { + load.material_items.forEach(function(item) { + h += ""; + h += ""; + h += ""; + h += ""; + h += ""; + h += ""; + h += ""; + h += ""; + h += ""; + h += ""; + h += ""; + h += ""; + h += ""; + h += ""; + h += ""; + h += ""; + h += ""; + }); + } + h += "
Material TypeCountRcv StatusSend ToRecv'dHDD OutDis Send ToSanitizedTest StatusTest SendR2 SentPhysHDD NeedHDD PhysHDD Logic
" + (item.material_type || "") + "" + (item.total_count || 0) + "" + (item.initial_data_status || "") + "" + (item.send_to || "") + "" + (item.devices_received || 0) + "" + (item.hdd_removed || 0) + "" + (item.disassembly_send_to || "") + "" + (item.units_sanitized_software || 0) + "" + (item.test_data_status || "") + "" + (item.test_send_to || "") + "" + (item.r2_units_sent || 0) + "" + (item.units_physical_destruction || 0) + "" + (item.hdd_needs_sanitize || 0) + "" + (item.hdd_physical_destruction || 0) + "" + (item.hdd_logical_sanitization || 0) + "
"; + $(page.body).html(h); + } +}; diff --git a/westech_r2/page/load_detail/load_detail.json b/westech_r2/page/load_detail/load_detail.json new file mode 100644 index 0000000..77dbb1d --- /dev/null +++ b/westech_r2/page/load_detail/load_detail.json @@ -0,0 +1,18 @@ +{ + "doctype": "Page", + "name": "load-detail", + "page_name": "load-detail", + "title": "Load Detail", + "page_type": "Web Page", + "module": "Westech R2", + "standard": "Yes", + "system_page": 0, + "roles": [ + { + "role": "All" + } + ], + "content": "", + "script": null, + "style": null +} \ No newline at end of file diff --git a/westech_r2/page/load_update/__init__.py b/westech_r2/page/load_update/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/westech_r2/page/load_update/load-update.html b/westech_r2/page/load_update/load-update.html new file mode 100644 index 0000000..6e7e05e --- /dev/null +++ b/westech_r2/page/load_update/load-update.html @@ -0,0 +1,4 @@ +
+

Load Update

+

Use the form below to update load material items.

+
diff --git a/westech_r2/page/load_update/load-update.js b/westech_r2/page/load_update/load-update.js new file mode 100644 index 0000000..5c03738 --- /dev/null +++ b/westech_r2/page/load_update/load-update.js @@ -0,0 +1,109 @@ +frappe.pages["load-update"].on_page_load = function(wrapper) { + var page = frappe.ui.make_app_page({ + parent: wrapper, + title: "Load Update", + single_column: true + }); + + var loadName = frappe.utils.get_url_arg("load"); + if (!loadName) { + $(page.body).html("

No load specified

Use ?load=MMDDYYYY-XXXX

"); + return; + } + + function loadLoad() { + frappe.call({ + method: "frappe.client.get", + args: { doctype: "Load", name: loadName }, + callback: function(r) { + if (r.message) { renderForm(page, r.message); } + else { $(page.body).html("

Load not found

"); } + } + }); + } + + function renderForm(page, load) { + var h = "
"; + h += "

Update Load: " + load.name + "

"; + h += "

Date: " + (load.incoming_date || "N/A") + " | Customer: " + (load.customer_name || load.customer_number || "N/A") + "

"; + h += "
"; + h += " "; + h += " "; + h += "Open Form View"; + h += "
"; + h += ""; + h += ""; + h += ""; + h += ""; + h += ""; + h += ""; + h += ""; + + if (load.material_items && load.material_items.length > 0) { + load.material_items.forEach(function(item, idx) { + var prefix = "item_" + idx + "_"; + h += ""; + h += ""; + h += ""; + h += ""; + h += ""; + h += ""; + h += ""; + h += ""; + h += ""; + h += ""; + h += ""; + h += ""; + h += ""; + h += ""; + h += ""; + h += ""; + h += ""; + }); + } + h += "
Material TypeCountRecv StatusSend ToRecv'dHDD OutDis Send ToSanitizedTest StatusTest SendR2 SentPhysHDD NeedHDD PhysHDD Logic
" + (item.material_type || "") + "
"; + $(page.body).html(h); + + $("#lu-save").on("click", function() { + var items = []; + load.material_items.forEach(function(item, idx) { + var prefix = "#item_" + idx + "_"; + items.push({ + name: item.name, + total_count: parseInt($(prefix + "total_count").val()) || 0, + initial_data_status: $(prefix + "initial_data_status").val(), + send_to: $(prefix + "send_to").val(), + devices_received: parseInt($(prefix + "devices_received").val()) || 0, + hdd_removed: parseInt($(prefix + "hdd_removed").val()) || 0, + disassembly_send_to: $(prefix + "disassembly_send_to").val(), + units_sanitized_software: parseInt($(prefix + "units_sanitized_software").val()) || 0, + test_data_status: $(prefix + "test_data_status").val(), + test_send_to: $(prefix + "test_send_to").val(), + r2_units_sent: parseInt($(prefix + "r2_units_sent").val()) || 0, + units_physical_destruction: parseInt($(prefix + "units_physical_destruction").val()) || 0, + hdd_needs_sanitize: parseInt($(prefix + "hdd_needs_sanitize").val()) || 0, + hdd_physical_destruction: parseInt($(prefix + "hdd_physical_destruction").val()) || 0, + hdd_logical_sanitization: parseInt($(prefix + "hdd_logical_sanitization").val()) || 0 + }); + }); + frappe.call({ + method: "westech_r2.page.load-update.load-update.save_load_items", + args: { load_name: loadName, items: JSON.stringify(items) }, + callback: function(r) { + if (r.message && r.message.status === "ok") { + frappe.show_alert({message: "Saved", indicator: "green"}); + loadLoad(); + } else { + frappe.show_alert({message: "Save failed", indicator: "red"}); + } + } + }); + }); + + $("#lu-print").on("click", function() { + window.print(); + }); + } + + loadLoad(); +}; diff --git a/westech_r2/page/load_update/load-update.py b/westech_r2/page/load_update/load-update.py new file mode 100644 index 0000000..4afaf5e --- /dev/null +++ b/westech_r2/page/load_update/load-update.py @@ -0,0 +1,17 @@ +import frappe +import json + +@frappe.whitelist() +def save_load_items(load_name, items): + items = json.loads(items) + load_doc = frappe.get_doc("Load", load_name) + for item_data in items: + for row in load_doc.material_items: + if row.name == item_data["name"]: + for field, value in item_data.items(): + if field != "name": + row.set(field, value) + break + load_doc.save(ignore_permissions=True) + frappe.db.commit() + return {"status": "ok", "message": "Saved " + str(len(items)) + " items"} diff --git a/westech_r2/page/load_update/load_update.css b/westech_r2/page/load_update/load_update.css new file mode 100644 index 0000000..3404ed3 --- /dev/null +++ b/westech_r2/page/load_update/load_update.css @@ -0,0 +1 @@ +/* Load Update page CSS */ diff --git a/westech_r2/page/load_update/load_update.html b/westech_r2/page/load_update/load_update.html new file mode 100644 index 0000000..6e7e05e --- /dev/null +++ b/westech_r2/page/load_update/load_update.html @@ -0,0 +1,4 @@ +
+

Load Update

+

Use the form below to update load material items.

+
diff --git a/westech_r2/page/load_update/load_update.js b/westech_r2/page/load_update/load_update.js new file mode 100644 index 0000000..5c03738 --- /dev/null +++ b/westech_r2/page/load_update/load_update.js @@ -0,0 +1,109 @@ +frappe.pages["load-update"].on_page_load = function(wrapper) { + var page = frappe.ui.make_app_page({ + parent: wrapper, + title: "Load Update", + single_column: true + }); + + var loadName = frappe.utils.get_url_arg("load"); + if (!loadName) { + $(page.body).html("

No load specified

Use ?load=MMDDYYYY-XXXX

"); + return; + } + + function loadLoad() { + frappe.call({ + method: "frappe.client.get", + args: { doctype: "Load", name: loadName }, + callback: function(r) { + if (r.message) { renderForm(page, r.message); } + else { $(page.body).html("

Load not found

"); } + } + }); + } + + function renderForm(page, load) { + var h = "
"; + h += "

Update Load: " + load.name + "

"; + h += "

Date: " + (load.incoming_date || "N/A") + " | Customer: " + (load.customer_name || load.customer_number || "N/A") + "

"; + h += "
"; + h += " "; + h += " "; + h += "Open Form View"; + h += "
"; + h += ""; + h += ""; + h += ""; + h += ""; + h += ""; + h += ""; + h += ""; + + if (load.material_items && load.material_items.length > 0) { + load.material_items.forEach(function(item, idx) { + var prefix = "item_" + idx + "_"; + h += ""; + h += ""; + h += ""; + h += ""; + h += ""; + h += ""; + h += ""; + h += ""; + h += ""; + h += ""; + h += ""; + h += ""; + h += ""; + h += ""; + h += ""; + h += ""; + h += ""; + }); + } + h += "
Material TypeCountRecv StatusSend ToRecv'dHDD OutDis Send ToSanitizedTest StatusTest SendR2 SentPhysHDD NeedHDD PhysHDD Logic
" + (item.material_type || "") + "
"; + $(page.body).html(h); + + $("#lu-save").on("click", function() { + var items = []; + load.material_items.forEach(function(item, idx) { + var prefix = "#item_" + idx + "_"; + items.push({ + name: item.name, + total_count: parseInt($(prefix + "total_count").val()) || 0, + initial_data_status: $(prefix + "initial_data_status").val(), + send_to: $(prefix + "send_to").val(), + devices_received: parseInt($(prefix + "devices_received").val()) || 0, + hdd_removed: parseInt($(prefix + "hdd_removed").val()) || 0, + disassembly_send_to: $(prefix + "disassembly_send_to").val(), + units_sanitized_software: parseInt($(prefix + "units_sanitized_software").val()) || 0, + test_data_status: $(prefix + "test_data_status").val(), + test_send_to: $(prefix + "test_send_to").val(), + r2_units_sent: parseInt($(prefix + "r2_units_sent").val()) || 0, + units_physical_destruction: parseInt($(prefix + "units_physical_destruction").val()) || 0, + hdd_needs_sanitize: parseInt($(prefix + "hdd_needs_sanitize").val()) || 0, + hdd_physical_destruction: parseInt($(prefix + "hdd_physical_destruction").val()) || 0, + hdd_logical_sanitization: parseInt($(prefix + "hdd_logical_sanitization").val()) || 0 + }); + }); + frappe.call({ + method: "westech_r2.page.load-update.load-update.save_load_items", + args: { load_name: loadName, items: JSON.stringify(items) }, + callback: function(r) { + if (r.message && r.message.status === "ok") { + frappe.show_alert({message: "Saved", indicator: "green"}); + loadLoad(); + } else { + frappe.show_alert({message: "Save failed", indicator: "red"}); + } + } + }); + }); + + $("#lu-print").on("click", function() { + window.print(); + }); + } + + loadLoad(); +}; diff --git a/westech_r2/page/load_update/load_update.json b/westech_r2/page/load_update/load_update.json new file mode 100644 index 0000000..b5c8853 --- /dev/null +++ b/westech_r2/page/load_update/load_update.json @@ -0,0 +1,18 @@ +{ + "doctype": "Page", + "name": "load-update", + "page_name": "load-update", + "title": "Load Update", + "page_type": "Web Page", + "module": "Westech R2", + "standard": "Yes", + "system_page": 0, + "roles": [ + { + "role": "All" + } + ], + "content": "", + "script": null, + "style": null +} \ No newline at end of file diff --git a/westech_r2/page/load_update/load_update.py b/westech_r2/page/load_update/load_update.py new file mode 100644 index 0000000..4afaf5e --- /dev/null +++ b/westech_r2/page/load_update/load_update.py @@ -0,0 +1,17 @@ +import frappe +import json + +@frappe.whitelist() +def save_load_items(load_name, items): + items = json.loads(items) + load_doc = frappe.get_doc("Load", load_name) + for item_data in items: + for row in load_doc.material_items: + if row.name == item_data["name"]: + for field, value in item_data.items(): + if field != "name": + row.set(field, value) + break + load_doc.save(ignore_permissions=True) + frappe.db.commit() + return {"status": "ok", "message": "Saved " + str(len(items)) + " items"} diff --git a/westech_r2/page/pallet-list/__pycache__/__init__.cpython-312.pyc b/westech_r2/page/pallet-list/__pycache__/__init__.cpython-312.pyc new file mode 100644 index 0000000..338fb5b Binary files /dev/null and b/westech_r2/page/pallet-list/__pycache__/__init__.cpython-312.pyc differ diff --git a/westech_r2/page/pallet-list/__pycache__/pallet-list.cpython-312.pyc b/westech_r2/page/pallet-list/__pycache__/pallet-list.cpython-312.pyc new file mode 100644 index 0000000..5b60feb Binary files /dev/null and b/westech_r2/page/pallet-list/__pycache__/pallet-list.cpython-312.pyc differ diff --git a/westech_r2/page/pallet_list/__init__.py b/westech_r2/page/pallet_list/__init__.py new file mode 100644 index 0000000..eb0d74d --- /dev/null +++ b/westech_r2/page/pallet_list/__init__.py @@ -0,0 +1 @@ +# Pallet List page for Westech R2 diff --git a/westech_r2/page/pallet_list/__pycache__/__init__.cpython-312.pyc b/westech_r2/page/pallet_list/__pycache__/__init__.cpython-312.pyc new file mode 100644 index 0000000..338fb5b Binary files /dev/null and b/westech_r2/page/pallet_list/__pycache__/__init__.cpython-312.pyc differ diff --git a/westech_r2/page/pallet_list/__pycache__/pallet-list.cpython-312.pyc b/westech_r2/page/pallet_list/__pycache__/pallet-list.cpython-312.pyc new file mode 100644 index 0000000..5b60feb Binary files /dev/null and b/westech_r2/page/pallet_list/__pycache__/pallet-list.cpython-312.pyc differ diff --git a/westech_r2/page/pallet_list/pallet-list.html b/westech_r2/page/pallet_list/pallet-list.html new file mode 100644 index 0000000..c3ac405 --- /dev/null +++ b/westech_r2/page/pallet_list/pallet-list.html @@ -0,0 +1,82 @@ + + +
+

📦 Pallet List

+ + + +
+ + + + + + + + + + + + + + + + + + + + +
Pallet # ⇅Date Reserved ⇅Rec. Date ⇅Customer # ⇅Lbs ⇅Who ⇅Items Tested ⇅QTY Sale ⇅Lbs Sale ⇅Finish Date ⇅StatusNotes
Loading...
+
+ +
+
diff --git a/westech_r2/page/pallet_list/pallet-list.js b/westech_r2/page/pallet_list/pallet-list.js new file mode 100644 index 0000000..0c3f9c1 --- /dev/null +++ b/westech_r2/page/pallet_list/pallet-list.js @@ -0,0 +1,128 @@ +frappe.pages["pallet-list"].on_page_load = function(wrapper) { + var page = frappe.ui.make_app_page({ + parent: wrapper, + title: __("Pallet List"), + single_column: true + }); + + var content = frappe.render_template("pallet-list", {}); + $(page.body).append(content); + + var currentPage = 1; + var pageSize = 100; + var currentSort = "pallet_number"; + var sortDir = "desc"; + var searchTerm = ""; + var statusFilter = ""; + + function loadPallets() { + frappe.call({ + method: "westech_r2.page.pallet-list.pallet-list.get_pallets", + args: { + page: currentPage, + page_size: pageSize, + sort_field: currentSort, + sort_dir: sortDir, + status_filter: statusFilter, + search: searchTerm + }, + callback: function(r) { + if (r.message) { + renderPallets(r.message.pallets, r.message.total); + } + } + }); + } + + function renderPallets(pallets, total) { + var tbody = $("#pallet-tbody"); + tbody.empty(); + + if (!pallets || pallets.length === 0) { + tbody.append('No pallets found'); + return; + } + + pallets.forEach(function(p) { + var statusClass = "status-" + (p.status || "").toLowerCase().replace(/\s+/g, "-"); + var link = "/app/pallet/" + encodeURIComponent(p.name); + var pn = (p.pallet_number || "").replace(//g, ">"); + + var row = '' + + '' + pn + '' + + '' + fmtDate(p.date_reserved) + '' + + '' + fmtDate(p.received_date) + '' + + '' + (p.customer_number || "") + '' + + '' + (p.company_name || "") + '' + + '' + (p.inbound_weight || "") + '' + + '' + (p.tester || "") + '' + + '' + (p.description || "") + '' + + '' + (p.qty_to_sales || "") + '' + + '' + (p.weight_to_sales || "") + '' + + '' + fmtDate(p.finish_date) + '' + + '' + (p.status || "") + '' + + '' + (p.notes || "").substring(0, 50) + '' + + ''; + tbody.append(row); + }); + + renderPagination(total); + } + + function renderPagination(total) { + var totalPages = Math.ceil(total / pageSize); + var pagination = $("#pallet-pagination"); + pagination.empty(); + + if (totalPages <= 1) return; + + var prevBtn = $('').attr("disabled", currentPage === 1) + .css({padding: "5px 12px", border: "1px solid #ddd", background: "white", cursor: "pointer", marginRight: "5px"}) + .on("click", function() { + if (currentPage > 1) { currentPage--; loadPallets(); } + }); + pagination.append(prevBtn); + + var startPage = Math.max(1, currentPage - 2); + var endPage = Math.min(totalPages, startPage + 4); + + for (var i = startPage; i <= endPage; i++) { + var btn = $('') + .css({padding: "5px 12px", border: "1px solid #ddd", background: i === currentPage ? "#3cc062" : "white", + color: i === currentPage ? "white" : "#333", cursor: "pointer", marginRight: "5px"}) + .on("click", function(page) { + return function() { currentPage = page; loadPallets(); }; + }(i)); + pagination.append(btn); + } + + var nextBtn = $('').attr("disabled", currentPage === totalPages) + .css({padding: "5px 12px", border: "1px solid #ddd", background: "white", cursor: "pointer", marginRight: "5px"}) + .on("click", function() { + if (currentPage < totalPages) { currentPage++; loadPallets(); } + }); + pagination.append(nextBtn); + } + + function fmtDate(v) { + if (!v) return ""; + var s = String(v); + if (s.indexOf("T") > -1) s = s.split("T")[0]; + if (s.indexOf(" ") > -1) s = s.split(" ")[0]; + return s; + } + + $("#pallet-search").on("input", function() { + searchTerm = $(this).val(); + currentPage = 1; + loadPallets(); + }); + + $("#status-filter").on("change", function() { + statusFilter = $(this).val(); + currentPage = 1; + loadPallets(); + }); + + loadPallets(); +}; diff --git a/westech_r2/page/pallet_list/pallet-list.json b/westech_r2/page/pallet_list/pallet-list.json new file mode 100644 index 0000000..d7304c4 --- /dev/null +++ b/westech_r2/page/pallet_list/pallet-list.json @@ -0,0 +1,19 @@ +{ + content: null, + creation: 2026-05-19 13:00:00.000000, + docstatus: 0, + doctype: Page, + idx: 0, + modified: 2026-05-19 13:00:00.000000, + modified_by: Administrator, + module: Westech R2, + name: pallet-list, + owner: Administrator, + page_name: pallet-list, + roles: [], + script: null, + standard: Yes, + style: null, + system_page: 0, + title: Pallet List +} diff --git a/westech_r2/page/pallet_list/pallet-list.py b/westech_r2/page/pallet_list/pallet-list.py new file mode 100644 index 0000000..0a0e433 --- /dev/null +++ b/westech_r2/page/pallet_list/pallet-list.py @@ -0,0 +1,64 @@ +import frappe + +@frappe.whitelist() +def get_pallets(page=1, page_size=100, sort_field="pallet_number", sort_dir="desc", status_filter="", search=""): + page = int(page) + page_size = int(page_size) + offset = (page - 1) * page_size + + conditions = [ + "pallet_number IS NOT NULL", + "pallet_number != ''", + "date_reserved IS NOT NULL", + "customer_number IS NOT NULL", + "customer_number != ''" + ] + + junk = ["", "0", "0000", "N/A", "TBD", "null", "999990", "999995"] + junk_list = "', '".join(junk) + conditions.append("pallet_number NOT IN ('" + junk_list + "')") + conditions.append("pallet_number NOT LIKE '999%'") + conditions.append("pallet_number REGEXP '^[0-9]'") + + if status_filter: + conditions.append("status = '" + frappe.db.escape(status_filter) + "'") + if search: + conditions.append("pallet_number LIKE '%" + frappe.db.escape(search) + "%'") + + where_clause = " AND ".join(conditions) + + total = frappe.db.sql("SELECT COUNT(*) FROM tabPallet WHERE " + where_clause)[0][0] + + pallets = frappe.db.sql("SELECT name, pallet_number, date_reserved, received_date, customer_number, company_name, inbound_weight, tester, description, qty_to_sales, weight_to_sales, finish_date, notes, status FROM tabPallet WHERE " + where_clause + " ORDER BY CAST(pallet_number AS UNSIGNED) " + sort_dir + " LIMIT " + str(page_size) + " OFFSET " + str(offset), as_dict=True) + + empty_pallet = frappe.db.sql("SELECT name, pallet_number, date_reserved, received_date, customer_number, company_name, inbound_weight, tester, description, qty_to_sales, weight_to_sales, finish_date, notes, status FROM tabPallet WHERE date_reserved IS NULL AND (customer_number IS NULL OR customer_number = '') AND pallet_number NOT IN ('" + junk_list + "') AND pallet_number NOT LIKE '999%' AND pallet_number REGEXP '^[0-9]' ORDER BY CAST(pallet_number AS UNSIGNED) DESC LIMIT 1", as_dict=True) + + result = [] + if empty_pallet: + empty_pallet[0]["_is_new"] = True + result.append(empty_pallet[0]) + result.extend(pallets) + + return {"pallets": result, "total": total, "page": page, "page_size": page_size} + +@frappe.whitelist() +def update_pallet(docname, field, value): + pallet = frappe.get_doc("Pallet", docname) + pallet.set(field, value) + pallet.save(ignore_permissions=True) + frappe.db.commit() + return {"status": "ok", "message": "Updated " + field} + +@frappe.whitelist() +def create_pallet(data): + data = frappe.parse_json(data) + pallet = frappe.new_doc("Pallet") + pallet.pallet_number = data.get("pallet_number") + pallet.status = data.get("status", "Received") + pallet.date_reserved = data.get("date_reserved") + for field, value in data.items(): + if field not in ["pallet_number", "status", "date_reserved"] and value: + pallet.set(field, value) + pallet.insert(ignore_permissions=True) + frappe.db.commit() + return {"status": "ok", "name": pallet.name} diff --git a/westech_r2/page/pallet_list/pallet_list.html b/westech_r2/page/pallet_list/pallet_list.html new file mode 100644 index 0000000..c3ac405 --- /dev/null +++ b/westech_r2/page/pallet_list/pallet_list.html @@ -0,0 +1,82 @@ + + +
+

📦 Pallet List

+ + + +
+ + + + + + + + + + + + + + + + + + + + +
Pallet # ⇅Date Reserved ⇅Rec. Date ⇅Customer # ⇅Lbs ⇅Who ⇅Items Tested ⇅QTY Sale ⇅Lbs Sale ⇅Finish Date ⇅StatusNotes
Loading...
+
+ +
+
diff --git a/westech_r2/page/pallet_list/pallet_list.js b/westech_r2/page/pallet_list/pallet_list.js new file mode 100644 index 0000000..0c3f9c1 --- /dev/null +++ b/westech_r2/page/pallet_list/pallet_list.js @@ -0,0 +1,128 @@ +frappe.pages["pallet-list"].on_page_load = function(wrapper) { + var page = frappe.ui.make_app_page({ + parent: wrapper, + title: __("Pallet List"), + single_column: true + }); + + var content = frappe.render_template("pallet-list", {}); + $(page.body).append(content); + + var currentPage = 1; + var pageSize = 100; + var currentSort = "pallet_number"; + var sortDir = "desc"; + var searchTerm = ""; + var statusFilter = ""; + + function loadPallets() { + frappe.call({ + method: "westech_r2.page.pallet-list.pallet-list.get_pallets", + args: { + page: currentPage, + page_size: pageSize, + sort_field: currentSort, + sort_dir: sortDir, + status_filter: statusFilter, + search: searchTerm + }, + callback: function(r) { + if (r.message) { + renderPallets(r.message.pallets, r.message.total); + } + } + }); + } + + function renderPallets(pallets, total) { + var tbody = $("#pallet-tbody"); + tbody.empty(); + + if (!pallets || pallets.length === 0) { + tbody.append('No pallets found'); + return; + } + + pallets.forEach(function(p) { + var statusClass = "status-" + (p.status || "").toLowerCase().replace(/\s+/g, "-"); + var link = "/app/pallet/" + encodeURIComponent(p.name); + var pn = (p.pallet_number || "").replace(//g, ">"); + + var row = '' + + '' + pn + '' + + '' + fmtDate(p.date_reserved) + '' + + '' + fmtDate(p.received_date) + '' + + '' + (p.customer_number || "") + '' + + '' + (p.company_name || "") + '' + + '' + (p.inbound_weight || "") + '' + + '' + (p.tester || "") + '' + + '' + (p.description || "") + '' + + '' + (p.qty_to_sales || "") + '' + + '' + (p.weight_to_sales || "") + '' + + '' + fmtDate(p.finish_date) + '' + + '' + (p.status || "") + '' + + '' + (p.notes || "").substring(0, 50) + '' + + ''; + tbody.append(row); + }); + + renderPagination(total); + } + + function renderPagination(total) { + var totalPages = Math.ceil(total / pageSize); + var pagination = $("#pallet-pagination"); + pagination.empty(); + + if (totalPages <= 1) return; + + var prevBtn = $('').attr("disabled", currentPage === 1) + .css({padding: "5px 12px", border: "1px solid #ddd", background: "white", cursor: "pointer", marginRight: "5px"}) + .on("click", function() { + if (currentPage > 1) { currentPage--; loadPallets(); } + }); + pagination.append(prevBtn); + + var startPage = Math.max(1, currentPage - 2); + var endPage = Math.min(totalPages, startPage + 4); + + for (var i = startPage; i <= endPage; i++) { + var btn = $('') + .css({padding: "5px 12px", border: "1px solid #ddd", background: i === currentPage ? "#3cc062" : "white", + color: i === currentPage ? "white" : "#333", cursor: "pointer", marginRight: "5px"}) + .on("click", function(page) { + return function() { currentPage = page; loadPallets(); }; + }(i)); + pagination.append(btn); + } + + var nextBtn = $('').attr("disabled", currentPage === totalPages) + .css({padding: "5px 12px", border: "1px solid #ddd", background: "white", cursor: "pointer", marginRight: "5px"}) + .on("click", function() { + if (currentPage < totalPages) { currentPage++; loadPallets(); } + }); + pagination.append(nextBtn); + } + + function fmtDate(v) { + if (!v) return ""; + var s = String(v); + if (s.indexOf("T") > -1) s = s.split("T")[0]; + if (s.indexOf(" ") > -1) s = s.split(" ")[0]; + return s; + } + + $("#pallet-search").on("input", function() { + searchTerm = $(this).val(); + currentPage = 1; + loadPallets(); + }); + + $("#status-filter").on("change", function() { + statusFilter = $(this).val(); + currentPage = 1; + loadPallets(); + }); + + loadPallets(); +}; diff --git a/westech_r2/page/pallet_list/pallet_list.json b/westech_r2/page/pallet_list/pallet_list.json new file mode 100644 index 0000000..d7304c4 --- /dev/null +++ b/westech_r2/page/pallet_list/pallet_list.json @@ -0,0 +1,19 @@ +{ + content: null, + creation: 2026-05-19 13:00:00.000000, + docstatus: 0, + doctype: Page, + idx: 0, + modified: 2026-05-19 13:00:00.000000, + modified_by: Administrator, + module: Westech R2, + name: pallet-list, + owner: Administrator, + page_name: pallet-list, + roles: [], + script: null, + standard: Yes, + style: null, + system_page: 0, + title: Pallet List +} diff --git a/westech_r2/page/pallet_list/pallet_list.py b/westech_r2/page/pallet_list/pallet_list.py new file mode 100644 index 0000000..0a0e433 --- /dev/null +++ b/westech_r2/page/pallet_list/pallet_list.py @@ -0,0 +1,64 @@ +import frappe + +@frappe.whitelist() +def get_pallets(page=1, page_size=100, sort_field="pallet_number", sort_dir="desc", status_filter="", search=""): + page = int(page) + page_size = int(page_size) + offset = (page - 1) * page_size + + conditions = [ + "pallet_number IS NOT NULL", + "pallet_number != ''", + "date_reserved IS NOT NULL", + "customer_number IS NOT NULL", + "customer_number != ''" + ] + + junk = ["", "0", "0000", "N/A", "TBD", "null", "999990", "999995"] + junk_list = "', '".join(junk) + conditions.append("pallet_number NOT IN ('" + junk_list + "')") + conditions.append("pallet_number NOT LIKE '999%'") + conditions.append("pallet_number REGEXP '^[0-9]'") + + if status_filter: + conditions.append("status = '" + frappe.db.escape(status_filter) + "'") + if search: + conditions.append("pallet_number LIKE '%" + frappe.db.escape(search) + "%'") + + where_clause = " AND ".join(conditions) + + total = frappe.db.sql("SELECT COUNT(*) FROM tabPallet WHERE " + where_clause)[0][0] + + pallets = frappe.db.sql("SELECT name, pallet_number, date_reserved, received_date, customer_number, company_name, inbound_weight, tester, description, qty_to_sales, weight_to_sales, finish_date, notes, status FROM tabPallet WHERE " + where_clause + " ORDER BY CAST(pallet_number AS UNSIGNED) " + sort_dir + " LIMIT " + str(page_size) + " OFFSET " + str(offset), as_dict=True) + + empty_pallet = frappe.db.sql("SELECT name, pallet_number, date_reserved, received_date, customer_number, company_name, inbound_weight, tester, description, qty_to_sales, weight_to_sales, finish_date, notes, status FROM tabPallet WHERE date_reserved IS NULL AND (customer_number IS NULL OR customer_number = '') AND pallet_number NOT IN ('" + junk_list + "') AND pallet_number NOT LIKE '999%' AND pallet_number REGEXP '^[0-9]' ORDER BY CAST(pallet_number AS UNSIGNED) DESC LIMIT 1", as_dict=True) + + result = [] + if empty_pallet: + empty_pallet[0]["_is_new"] = True + result.append(empty_pallet[0]) + result.extend(pallets) + + return {"pallets": result, "total": total, "page": page, "page_size": page_size} + +@frappe.whitelist() +def update_pallet(docname, field, value): + pallet = frappe.get_doc("Pallet", docname) + pallet.set(field, value) + pallet.save(ignore_permissions=True) + frappe.db.commit() + return {"status": "ok", "message": "Updated " + field} + +@frappe.whitelist() +def create_pallet(data): + data = frappe.parse_json(data) + pallet = frappe.new_doc("Pallet") + pallet.pallet_number = data.get("pallet_number") + pallet.status = data.get("status", "Received") + pallet.date_reserved = data.get("date_reserved") + for field, value in data.items(): + if field not in ["pallet_number", "status", "date_reserved"] and value: + pallet.set(field, value) + pallet.insert(ignore_permissions=True) + frappe.db.commit() + return {"status": "ok", "name": pallet.name} diff --git a/westech_r2/page/route-planner/route-planner.html b/westech_r2/page/route-planner/route-planner.html new file mode 100644 index 0000000..0116139 --- /dev/null +++ b/westech_r2/page/route-planner/route-planner.html @@ -0,0 +1,4 @@ +
+

Route Planner

+

Optimize pickup routes for scheduled pickups.

+
diff --git a/westech_r2/page/route-planner/route-planner.js b/westech_r2/page/route-planner/route-planner.js new file mode 100644 index 0000000..9d8ff74 --- /dev/null +++ b/westech_r2/page/route-planner/route-planner.js @@ -0,0 +1,38 @@ +frappe.pages["route-planner"].on_page_load = function(wrapper) { + var page = frappe.ui.make_app_page({ + parent: wrapper, + title: "Route Planner", + single_column: true + }); + + frappe.call({ + method: "westech_r2.api.optimize_routes.get_scheduled_pickups", + callback: function(r) { + if (r.message) { + renderRoutePlanner(page, r.message); + } + } + }); + + function renderRoutePlanner(page, data) { + var h = "
"; + h += "

Route Planner

"; + h += "

Schedule and optimize pickup routes.

"; + h += "
"; + h += ""; + h += "
"; + h += "
"; + $(page.body).html(h); + + $("#optimize-btn").on("click", function() { + frappe.call({ + method: "westech_r2.api.optimize_routes.optimize_routes", + callback: function(r) { + if (r.message) { + frappe.show_alert({message: "Routes optimized", indicator: "green"}); + } + } + }); + }); + } +}; diff --git a/westech_r2/page/route-planner/route-planner.json b/westech_r2/page/route-planner/route-planner.json new file mode 100644 index 0000000..7e8b89e --- /dev/null +++ b/westech_r2/page/route-planner/route-planner.json @@ -0,0 +1,9 @@ +{ + "doctype": "Page", + "name": "route-planner", + "page_name": "route-planner", + "title": "Route Planner", + "module": "Westech R2", + "standard": "Yes", + "roles": [{"role": "All"}] +} diff --git a/westech_r2/page/route_planner/route_planner.html b/westech_r2/page/route_planner/route_planner.html new file mode 100644 index 0000000..0116139 --- /dev/null +++ b/westech_r2/page/route_planner/route_planner.html @@ -0,0 +1,4 @@ +
+

Route Planner

+

Optimize pickup routes for scheduled pickups.

+
diff --git a/westech_r2/page/route_planner/route_planner.js b/westech_r2/page/route_planner/route_planner.js new file mode 100644 index 0000000..9d8ff74 --- /dev/null +++ b/westech_r2/page/route_planner/route_planner.js @@ -0,0 +1,38 @@ +frappe.pages["route-planner"].on_page_load = function(wrapper) { + var page = frappe.ui.make_app_page({ + parent: wrapper, + title: "Route Planner", + single_column: true + }); + + frappe.call({ + method: "westech_r2.api.optimize_routes.get_scheduled_pickups", + callback: function(r) { + if (r.message) { + renderRoutePlanner(page, r.message); + } + } + }); + + function renderRoutePlanner(page, data) { + var h = "
"; + h += "

Route Planner

"; + h += "

Schedule and optimize pickup routes.

"; + h += "
"; + h += ""; + h += "
"; + h += "
"; + $(page.body).html(h); + + $("#optimize-btn").on("click", function() { + frappe.call({ + method: "westech_r2.api.optimize_routes.optimize_routes", + callback: function(r) { + if (r.message) { + frappe.show_alert({message: "Routes optimized", indicator: "green"}); + } + } + }); + }); + } +}; diff --git a/westech_r2/page/route_planner/route_planner.json b/westech_r2/page/route_planner/route_planner.json new file mode 100644 index 0000000..7e8b89e --- /dev/null +++ b/westech_r2/page/route_planner/route_planner.json @@ -0,0 +1,9 @@ +{ + "doctype": "Page", + "name": "route-planner", + "page_name": "route-planner", + "title": "Route Planner", + "module": "Westech R2", + "standard": "Yes", + "roles": [{"role": "All"}] +} diff --git a/westech_r2/westech_r2/page/__init__.py b/westech_r2/westech_r2/page/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/westech_r2/westech_r2/page/load-detail/load-detail.json b/westech_r2/westech_r2/page/load-detail/load-detail.json new file mode 100644 index 0000000..fdf173c --- /dev/null +++ b/westech_r2/westech_r2/page/load-detail/load-detail.json @@ -0,0 +1 @@ +{"content": "
\n\n\n
\n

Load:

\n
In Date: | Customer: | Devices: | Weight: lbs
\n
\n\n
\n Receiving\n HDR / Disassembly\n Test\n R2 Downstream\n Destruction\n
\n\n\n\n\n\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n
Material TypeReceivingHDR / DisassemblyTestR2Destruction
CountStatusSend ToRecv'dHDD OutSend ToSanitizedStatusSend ToSentPhysHDD NeedHDD PhysHDD Logic
\n\n\n
\n", "title": "Load Detail", "route": "load-detail"} \ No newline at end of file diff --git a/westech_r2/westech_r2/page/load-detail/load_detail.json b/westech_r2/westech_r2/page/load-detail/load_detail.json new file mode 100644 index 0000000..fdf173c --- /dev/null +++ b/westech_r2/westech_r2/page/load-detail/load_detail.json @@ -0,0 +1 @@ +{"content": "
\n\n\n
\n

Load:

\n
In Date: | Customer: | Devices: | Weight: lbs
\n
\n\n
\n Receiving\n HDR / Disassembly\n Test\n R2 Downstream\n Destruction\n
\n\n\n\n\n\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n
Material TypeReceivingHDR / DisassemblyTestR2Destruction
CountStatusSend ToRecv'dHDD OutSend ToSanitizedStatusSend ToSentPhysHDD NeedHDD PhysHDD Logic
\n\n\n
\n", "title": "Load Detail", "route": "load-detail"} \ No newline at end of file diff --git a/westech_r2/westech_r2/page/load-update/__init__.py b/westech_r2/westech_r2/page/load-update/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/westech_r2/westech_r2/page/load-update/load_update.js b/westech_r2/westech_r2/page/load-update/load_update.js new file mode 100644 index 0000000..bd7fac9 --- /dev/null +++ b/westech_r2/westech_r2/page/load-update/load_update.js @@ -0,0 +1,7 @@ +frappe.pages['load-update'].on_page_load = function(wrapper) { + var page = frappe.ui.make_app_page({ + parent: wrapper, + title: 'Load Update', + single_column: true + }); +} \ No newline at end of file diff --git a/westech_r2/westech_r2/page/load-update/load_update.json b/westech_r2/westech_r2/page/load-update/load_update.json new file mode 100644 index 0000000..59ee53b --- /dev/null +++ b/westech_r2/westech_r2/page/load-update/load_update.json @@ -0,0 +1,23 @@ +{ + "content": null, + "creation": "2026-05-19 19:43:48.962424", + "docstatus": 0, + "doctype": "Page", + "idx": 0, + "modified": "2026-05-19 19:43:48.962424", + "modified_by": "Administrator", + "module": "Westech R2", + "name": "load-update", + "owner": "Administrator", + "page_name": "load-update", + "roles": [ + { + "role": "All" + } + ], + "script": null, + "standard": "Yes", + "style": null, + "system_page": 0, + "title": "Load Update" +} \ No newline at end of file diff --git a/westech_r2/westech_r2/page/load_detail/__init__.py b/westech_r2/westech_r2/page/load_detail/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/westech_r2/westech_r2/page/load_detail/load-detail.js b/westech_r2/westech_r2/page/load_detail/load-detail.js new file mode 100644 index 0000000..b13cdca --- /dev/null +++ b/westech_r2/westech_r2/page/load_detail/load-detail.js @@ -0,0 +1,65 @@ +frappe.pages["load-detail"].on_page_load = function(wrapper) { + var page = frappe.ui.make_app_page({ + parent: wrapper, + title: "Load Detail", + single_column: true + }); + + var loadName = frappe.utils.get_url_arg("load"); + if (!loadName) { + $(page.body).html("

No load specified

Use ?load=MMDDYYYY-XXXX

"); + return; + } + + frappe.call({ + method: "frappe.client.get", + args: { doctype: "Load", name: loadName }, + callback: function(r) { + if (r.message) { showLoad(page, r.message); } + else { $(page.body).html("

Load not found

"); } + } + }); + + function showLoad(page, load) { + var h = "
"; + h += "
"; + h += "

Load: " + load.name + "

"; + h += "
In Date: " + (load.incoming_date || "N/A") + " | Customer: " + (load.customer_name || load.customer_number || "N/A") + "
"; + h += "
Devices: " + (load.total_devices || 0) + " | Weight: " + (load.total_weight || 0) + " lbs
"; + h += "
"; + h += " "; + h += "Open Form View "; + h += "Edit Load"; + h += ""; + h += ""; + h += ""; + h += ""; + h += ""; + h += ""; + h += ""; + + if (load.material_items && load.material_items.length > 0) { + load.material_items.forEach(function(item) { + h += ""; + h += ""; + h += ""; + h += ""; + h += ""; + h += ""; + h += ""; + h += ""; + h += ""; + h += ""; + h += ""; + h += ""; + h += ""; + h += ""; + h += ""; + h += ""; + h += ""; + }); + } + h += "
Material TypeCountRcv StatusSend ToRecv'dHDD OutDis Send ToSanitizedTest StatusTest SendR2 SentPhysHDD NeedHDD PhysHDD Logic
" + (item.material_type || "") + "" + (item.total_count || 0) + "" + (item.initial_data_status || "") + "" + (item.send_to || "") + "" + (item.devices_received || 0) + "" + (item.hdd_removed || 0) + "" + (item.disassembly_send_to || "") + "" + (item.units_sanitized_software || 0) + "" + (item.test_data_status || "") + "" + (item.test_send_to || "") + "" + (item.r2_units_sent || 0) + "" + (item.units_physical_destruction || 0) + "" + (item.hdd_needs_sanitize || 0) + "" + (item.hdd_physical_destruction || 0) + "" + (item.hdd_logical_sanitization || 0) + "
"; + $(page.body).html(h); + } +}; diff --git a/westech_r2/westech_r2/page/load_detail/load-detail.json b/westech_r2/westech_r2/page/load_detail/load-detail.json new file mode 100644 index 0000000..fdf173c --- /dev/null +++ b/westech_r2/westech_r2/page/load_detail/load-detail.json @@ -0,0 +1 @@ +{"content": "
\n\n\n
\n

Load:

\n
In Date: | Customer: | Devices: | Weight: lbs
\n
\n\n
\n Receiving\n HDR / Disassembly\n Test\n R2 Downstream\n Destruction\n
\n\n\n\n\n\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n
Material TypeReceivingHDR / DisassemblyTestR2Destruction
CountStatusSend ToRecv'dHDD OutSend ToSanitizedStatusSend ToSentPhysHDD NeedHDD PhysHDD Logic
\n\n\n
\n", "title": "Load Detail", "route": "load-detail"} \ No newline at end of file diff --git a/westech_r2/westech_r2/page/load_detail/load_detail.html b/westech_r2/westech_r2/page/load_detail/load_detail.html new file mode 100644 index 0000000..ec9f8f8 --- /dev/null +++ b/westech_r2/westech_r2/page/load_detail/load_detail.html @@ -0,0 +1,53 @@ +
+ + +
+

Load:

+
In Date: | Customer: | Devices: | Weight: lbs
+
+ +
+ Receiving + HDR / Disassembly + Test + R2 Downstream + Destruction +
+ + + + + + + + + + + + + + + + + + + + + + + +
Material TypeReceivingHDR / DisassemblyTestR2Destruction
CountStatusSend ToRecv'dHDD OutSend ToSanitizedStatusSend ToSentPhysHDD NeedHDD PhysHDD Logic
+
\ No newline at end of file diff --git a/westech_r2/westech_r2/page/load_detail/load_detail.js b/westech_r2/westech_r2/page/load_detail/load_detail.js new file mode 100644 index 0000000..b13cdca --- /dev/null +++ b/westech_r2/westech_r2/page/load_detail/load_detail.js @@ -0,0 +1,65 @@ +frappe.pages["load-detail"].on_page_load = function(wrapper) { + var page = frappe.ui.make_app_page({ + parent: wrapper, + title: "Load Detail", + single_column: true + }); + + var loadName = frappe.utils.get_url_arg("load"); + if (!loadName) { + $(page.body).html("

No load specified

Use ?load=MMDDYYYY-XXXX

"); + return; + } + + frappe.call({ + method: "frappe.client.get", + args: { doctype: "Load", name: loadName }, + callback: function(r) { + if (r.message) { showLoad(page, r.message); } + else { $(page.body).html("

Load not found

"); } + } + }); + + function showLoad(page, load) { + var h = "
"; + h += "
"; + h += "

Load: " + load.name + "

"; + h += "
In Date: " + (load.incoming_date || "N/A") + " | Customer: " + (load.customer_name || load.customer_number || "N/A") + "
"; + h += "
Devices: " + (load.total_devices || 0) + " | Weight: " + (load.total_weight || 0) + " lbs
"; + h += "
"; + h += " "; + h += "Open Form View "; + h += "Edit Load"; + h += ""; + h += ""; + h += ""; + h += ""; + h += ""; + h += ""; + h += ""; + + if (load.material_items && load.material_items.length > 0) { + load.material_items.forEach(function(item) { + h += ""; + h += ""; + h += ""; + h += ""; + h += ""; + h += ""; + h += ""; + h += ""; + h += ""; + h += ""; + h += ""; + h += ""; + h += ""; + h += ""; + h += ""; + h += ""; + h += ""; + }); + } + h += "
Material TypeCountRcv StatusSend ToRecv'dHDD OutDis Send ToSanitizedTest StatusTest SendR2 SentPhysHDD NeedHDD PhysHDD Logic
" + (item.material_type || "") + "" + (item.total_count || 0) + "" + (item.initial_data_status || "") + "" + (item.send_to || "") + "" + (item.devices_received || 0) + "" + (item.hdd_removed || 0) + "" + (item.disassembly_send_to || "") + "" + (item.units_sanitized_software || 0) + "" + (item.test_data_status || "") + "" + (item.test_send_to || "") + "" + (item.r2_units_sent || 0) + "" + (item.units_physical_destruction || 0) + "" + (item.hdd_needs_sanitize || 0) + "" + (item.hdd_physical_destruction || 0) + "" + (item.hdd_logical_sanitization || 0) + "
"; + $(page.body).html(h); + } +}; diff --git a/westech_r2/westech_r2/page/load_detail/load_detail.json b/westech_r2/westech_r2/page/load_detail/load_detail.json new file mode 100644 index 0000000..b907099 --- /dev/null +++ b/westech_r2/westech_r2/page/load_detail/load_detail.json @@ -0,0 +1,23 @@ +{ + "content": null, + "creation": "2026-05-19 19:43:48.923372", + "docstatus": 0, + "doctype": "Page", + "idx": 0, + "modified": "2026-05-19 19:43:48.923372", + "modified_by": "Administrator", + "module": "Westech R2", + "name": "load-detail", + "owner": "Administrator", + "page_name": "load-detail", + "roles": [ + { + "role": "All" + } + ], + "script": null, + "standard": "Yes", + "style": null, + "system_page": 0, + "title": "Load Detail" +} \ No newline at end of file diff --git a/westech_r2/westech_r2/page/load_update/__init__.py b/westech_r2/westech_r2/page/load_update/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/westech_r2/westech_r2/page/load_update/load-update.js b/westech_r2/westech_r2/page/load_update/load-update.js new file mode 100644 index 0000000..5c03738 --- /dev/null +++ b/westech_r2/westech_r2/page/load_update/load-update.js @@ -0,0 +1,109 @@ +frappe.pages["load-update"].on_page_load = function(wrapper) { + var page = frappe.ui.make_app_page({ + parent: wrapper, + title: "Load Update", + single_column: true + }); + + var loadName = frappe.utils.get_url_arg("load"); + if (!loadName) { + $(page.body).html("

No load specified

Use ?load=MMDDYYYY-XXXX

"); + return; + } + + function loadLoad() { + frappe.call({ + method: "frappe.client.get", + args: { doctype: "Load", name: loadName }, + callback: function(r) { + if (r.message) { renderForm(page, r.message); } + else { $(page.body).html("

Load not found

"); } + } + }); + } + + function renderForm(page, load) { + var h = "
"; + h += "

Update Load: " + load.name + "

"; + h += "

Date: " + (load.incoming_date || "N/A") + " | Customer: " + (load.customer_name || load.customer_number || "N/A") + "

"; + h += "
"; + h += " "; + h += " "; + h += "Open Form View"; + h += "
"; + h += ""; + h += ""; + h += ""; + h += ""; + h += ""; + h += ""; + h += ""; + + if (load.material_items && load.material_items.length > 0) { + load.material_items.forEach(function(item, idx) { + var prefix = "item_" + idx + "_"; + h += ""; + h += ""; + h += ""; + h += ""; + h += ""; + h += ""; + h += ""; + h += ""; + h += ""; + h += ""; + h += ""; + h += ""; + h += ""; + h += ""; + h += ""; + h += ""; + h += ""; + }); + } + h += "
Material TypeCountRecv StatusSend ToRecv'dHDD OutDis Send ToSanitizedTest StatusTest SendR2 SentPhysHDD NeedHDD PhysHDD Logic
" + (item.material_type || "") + "
"; + $(page.body).html(h); + + $("#lu-save").on("click", function() { + var items = []; + load.material_items.forEach(function(item, idx) { + var prefix = "#item_" + idx + "_"; + items.push({ + name: item.name, + total_count: parseInt($(prefix + "total_count").val()) || 0, + initial_data_status: $(prefix + "initial_data_status").val(), + send_to: $(prefix + "send_to").val(), + devices_received: parseInt($(prefix + "devices_received").val()) || 0, + hdd_removed: parseInt($(prefix + "hdd_removed").val()) || 0, + disassembly_send_to: $(prefix + "disassembly_send_to").val(), + units_sanitized_software: parseInt($(prefix + "units_sanitized_software").val()) || 0, + test_data_status: $(prefix + "test_data_status").val(), + test_send_to: $(prefix + "test_send_to").val(), + r2_units_sent: parseInt($(prefix + "r2_units_sent").val()) || 0, + units_physical_destruction: parseInt($(prefix + "units_physical_destruction").val()) || 0, + hdd_needs_sanitize: parseInt($(prefix + "hdd_needs_sanitize").val()) || 0, + hdd_physical_destruction: parseInt($(prefix + "hdd_physical_destruction").val()) || 0, + hdd_logical_sanitization: parseInt($(prefix + "hdd_logical_sanitization").val()) || 0 + }); + }); + frappe.call({ + method: "westech_r2.page.load-update.load-update.save_load_items", + args: { load_name: loadName, items: JSON.stringify(items) }, + callback: function(r) { + if (r.message && r.message.status === "ok") { + frappe.show_alert({message: "Saved", indicator: "green"}); + loadLoad(); + } else { + frappe.show_alert({message: "Save failed", indicator: "red"}); + } + } + }); + }); + + $("#lu-print").on("click", function() { + window.print(); + }); + } + + loadLoad(); +}; diff --git a/westech_r2/westech_r2/page/load_update/load_update.html b/westech_r2/westech_r2/page/load_update/load_update.html new file mode 100644 index 0000000..6e7e05e --- /dev/null +++ b/westech_r2/westech_r2/page/load_update/load_update.html @@ -0,0 +1,4 @@ +
+

Load Update

+

Use the form below to update load material items.

+
diff --git a/westech_r2/westech_r2/page/load_update/load_update.js b/westech_r2/westech_r2/page/load_update/load_update.js new file mode 100644 index 0000000..5c03738 --- /dev/null +++ b/westech_r2/westech_r2/page/load_update/load_update.js @@ -0,0 +1,109 @@ +frappe.pages["load-update"].on_page_load = function(wrapper) { + var page = frappe.ui.make_app_page({ + parent: wrapper, + title: "Load Update", + single_column: true + }); + + var loadName = frappe.utils.get_url_arg("load"); + if (!loadName) { + $(page.body).html("

No load specified

Use ?load=MMDDYYYY-XXXX

"); + return; + } + + function loadLoad() { + frappe.call({ + method: "frappe.client.get", + args: { doctype: "Load", name: loadName }, + callback: function(r) { + if (r.message) { renderForm(page, r.message); } + else { $(page.body).html("

Load not found

"); } + } + }); + } + + function renderForm(page, load) { + var h = "
"; + h += "

Update Load: " + load.name + "

"; + h += "

Date: " + (load.incoming_date || "N/A") + " | Customer: " + (load.customer_name || load.customer_number || "N/A") + "

"; + h += "
"; + h += " "; + h += " "; + h += "Open Form View"; + h += "
"; + h += ""; + h += ""; + h += ""; + h += ""; + h += ""; + h += ""; + h += ""; + + if (load.material_items && load.material_items.length > 0) { + load.material_items.forEach(function(item, idx) { + var prefix = "item_" + idx + "_"; + h += ""; + h += ""; + h += ""; + h += ""; + h += ""; + h += ""; + h += ""; + h += ""; + h += ""; + h += ""; + h += ""; + h += ""; + h += ""; + h += ""; + h += ""; + h += ""; + h += ""; + }); + } + h += "
Material TypeCountRecv StatusSend ToRecv'dHDD OutDis Send ToSanitizedTest StatusTest SendR2 SentPhysHDD NeedHDD PhysHDD Logic
" + (item.material_type || "") + "
"; + $(page.body).html(h); + + $("#lu-save").on("click", function() { + var items = []; + load.material_items.forEach(function(item, idx) { + var prefix = "#item_" + idx + "_"; + items.push({ + name: item.name, + total_count: parseInt($(prefix + "total_count").val()) || 0, + initial_data_status: $(prefix + "initial_data_status").val(), + send_to: $(prefix + "send_to").val(), + devices_received: parseInt($(prefix + "devices_received").val()) || 0, + hdd_removed: parseInt($(prefix + "hdd_removed").val()) || 0, + disassembly_send_to: $(prefix + "disassembly_send_to").val(), + units_sanitized_software: parseInt($(prefix + "units_sanitized_software").val()) || 0, + test_data_status: $(prefix + "test_data_status").val(), + test_send_to: $(prefix + "test_send_to").val(), + r2_units_sent: parseInt($(prefix + "r2_units_sent").val()) || 0, + units_physical_destruction: parseInt($(prefix + "units_physical_destruction").val()) || 0, + hdd_needs_sanitize: parseInt($(prefix + "hdd_needs_sanitize").val()) || 0, + hdd_physical_destruction: parseInt($(prefix + "hdd_physical_destruction").val()) || 0, + hdd_logical_sanitization: parseInt($(prefix + "hdd_logical_sanitization").val()) || 0 + }); + }); + frappe.call({ + method: "westech_r2.page.load-update.load-update.save_load_items", + args: { load_name: loadName, items: JSON.stringify(items) }, + callback: function(r) { + if (r.message && r.message.status === "ok") { + frappe.show_alert({message: "Saved", indicator: "green"}); + loadLoad(); + } else { + frappe.show_alert({message: "Save failed", indicator: "red"}); + } + } + }); + }); + + $("#lu-print").on("click", function() { + window.print(); + }); + } + + loadLoad(); +}; diff --git a/westech_r2/westech_r2/page/load_update/load_update.json b/westech_r2/westech_r2/page/load_update/load_update.json new file mode 100644 index 0000000..36a0249 --- /dev/null +++ b/westech_r2/westech_r2/page/load_update/load_update.json @@ -0,0 +1,23 @@ +{ + "content": null, + "creation": "2026-05-19 20:47:42.992119", + "docstatus": 0, + "doctype": "Page", + "idx": 0, + "modified": "2026-05-19 20:47:42.992119", + "modified_by": "Administrator", + "module": "Westech R2", + "name": "load-update", + "owner": "Administrator", + "page_name": "load-update", + "roles": [ + { + "role": "All" + } + ], + "script": null, + "standard": "Yes", + "style": null, + "system_page": 0, + "title": "Load Update" +} \ No newline at end of file diff --git a/westech_r2/westech_r2/page/load_update/load_update.py b/westech_r2/westech_r2/page/load_update/load_update.py new file mode 100644 index 0000000..4afaf5e --- /dev/null +++ b/westech_r2/westech_r2/page/load_update/load_update.py @@ -0,0 +1,17 @@ +import frappe +import json + +@frappe.whitelist() +def save_load_items(load_name, items): + items = json.loads(items) + load_doc = frappe.get_doc("Load", load_name) + for item_data in items: + for row in load_doc.material_items: + if row.name == item_data["name"]: + for field, value in item_data.items(): + if field != "name": + row.set(field, value) + break + load_doc.save(ignore_permissions=True) + frappe.db.commit() + return {"status": "ok", "message": "Saved " + str(len(items)) + " items"} diff --git a/westech_r2/westech_r2/page/pallet-list/__init__.py b/westech_r2/westech_r2/page/pallet-list/__init__.py new file mode 100644 index 0000000..eb0d74d --- /dev/null +++ b/westech_r2/westech_r2/page/pallet-list/__init__.py @@ -0,0 +1 @@ +# Pallet List page for Westech R2 diff --git a/westech_r2/westech_r2/page/pallet-list/pallet-list.html b/westech_r2/westech_r2/page/pallet-list/pallet-list.html new file mode 100644 index 0000000..c3ac405 --- /dev/null +++ b/westech_r2/westech_r2/page/pallet-list/pallet-list.html @@ -0,0 +1,82 @@ + + +
+

📦 Pallet List

+ + + +
+ + + + + + + + + + + + + + + + + + + + +
Pallet # ⇅Date Reserved ⇅Rec. Date ⇅Customer # ⇅Lbs ⇅Who ⇅Items Tested ⇅QTY Sale ⇅Lbs Sale ⇅Finish Date ⇅StatusNotes
Loading...
+
+ +
+
diff --git a/westech_r2/westech_r2/page/pallet-list/pallet-list.js b/westech_r2/westech_r2/page/pallet-list/pallet-list.js new file mode 100644 index 0000000..0c3f9c1 --- /dev/null +++ b/westech_r2/westech_r2/page/pallet-list/pallet-list.js @@ -0,0 +1,128 @@ +frappe.pages["pallet-list"].on_page_load = function(wrapper) { + var page = frappe.ui.make_app_page({ + parent: wrapper, + title: __("Pallet List"), + single_column: true + }); + + var content = frappe.render_template("pallet-list", {}); + $(page.body).append(content); + + var currentPage = 1; + var pageSize = 100; + var currentSort = "pallet_number"; + var sortDir = "desc"; + var searchTerm = ""; + var statusFilter = ""; + + function loadPallets() { + frappe.call({ + method: "westech_r2.page.pallet-list.pallet-list.get_pallets", + args: { + page: currentPage, + page_size: pageSize, + sort_field: currentSort, + sort_dir: sortDir, + status_filter: statusFilter, + search: searchTerm + }, + callback: function(r) { + if (r.message) { + renderPallets(r.message.pallets, r.message.total); + } + } + }); + } + + function renderPallets(pallets, total) { + var tbody = $("#pallet-tbody"); + tbody.empty(); + + if (!pallets || pallets.length === 0) { + tbody.append('No pallets found'); + return; + } + + pallets.forEach(function(p) { + var statusClass = "status-" + (p.status || "").toLowerCase().replace(/\s+/g, "-"); + var link = "/app/pallet/" + encodeURIComponent(p.name); + var pn = (p.pallet_number || "").replace(//g, ">"); + + var row = '' + + '' + pn + '' + + '' + fmtDate(p.date_reserved) + '' + + '' + fmtDate(p.received_date) + '' + + '' + (p.customer_number || "") + '' + + '' + (p.company_name || "") + '' + + '' + (p.inbound_weight || "") + '' + + '' + (p.tester || "") + '' + + '' + (p.description || "") + '' + + '' + (p.qty_to_sales || "") + '' + + '' + (p.weight_to_sales || "") + '' + + '' + fmtDate(p.finish_date) + '' + + '' + (p.status || "") + '' + + '' + (p.notes || "").substring(0, 50) + '' + + ''; + tbody.append(row); + }); + + renderPagination(total); + } + + function renderPagination(total) { + var totalPages = Math.ceil(total / pageSize); + var pagination = $("#pallet-pagination"); + pagination.empty(); + + if (totalPages <= 1) return; + + var prevBtn = $('').attr("disabled", currentPage === 1) + .css({padding: "5px 12px", border: "1px solid #ddd", background: "white", cursor: "pointer", marginRight: "5px"}) + .on("click", function() { + if (currentPage > 1) { currentPage--; loadPallets(); } + }); + pagination.append(prevBtn); + + var startPage = Math.max(1, currentPage - 2); + var endPage = Math.min(totalPages, startPage + 4); + + for (var i = startPage; i <= endPage; i++) { + var btn = $('') + .css({padding: "5px 12px", border: "1px solid #ddd", background: i === currentPage ? "#3cc062" : "white", + color: i === currentPage ? "white" : "#333", cursor: "pointer", marginRight: "5px"}) + .on("click", function(page) { + return function() { currentPage = page; loadPallets(); }; + }(i)); + pagination.append(btn); + } + + var nextBtn = $('').attr("disabled", currentPage === totalPages) + .css({padding: "5px 12px", border: "1px solid #ddd", background: "white", cursor: "pointer", marginRight: "5px"}) + .on("click", function() { + if (currentPage < totalPages) { currentPage++; loadPallets(); } + }); + pagination.append(nextBtn); + } + + function fmtDate(v) { + if (!v) return ""; + var s = String(v); + if (s.indexOf("T") > -1) s = s.split("T")[0]; + if (s.indexOf(" ") > -1) s = s.split(" ")[0]; + return s; + } + + $("#pallet-search").on("input", function() { + searchTerm = $(this).val(); + currentPage = 1; + loadPallets(); + }); + + $("#status-filter").on("change", function() { + statusFilter = $(this).val(); + currentPage = 1; + loadPallets(); + }); + + loadPallets(); +}; diff --git a/westech_r2/westech_r2/page/pallet-list/pallet-list.json b/westech_r2/westech_r2/page/pallet-list/pallet-list.json new file mode 100644 index 0000000..d7304c4 --- /dev/null +++ b/westech_r2/westech_r2/page/pallet-list/pallet-list.json @@ -0,0 +1,19 @@ +{ + content: null, + creation: 2026-05-19 13:00:00.000000, + docstatus: 0, + doctype: Page, + idx: 0, + modified: 2026-05-19 13:00:00.000000, + modified_by: Administrator, + module: Westech R2, + name: pallet-list, + owner: Administrator, + page_name: pallet-list, + roles: [], + script: null, + standard: Yes, + style: null, + system_page: 0, + title: Pallet List +} diff --git a/westech_r2/westech_r2/page/pallet-list/pallet-list.py b/westech_r2/westech_r2/page/pallet-list/pallet-list.py new file mode 100644 index 0000000..0a0e433 --- /dev/null +++ b/westech_r2/westech_r2/page/pallet-list/pallet-list.py @@ -0,0 +1,64 @@ +import frappe + +@frappe.whitelist() +def get_pallets(page=1, page_size=100, sort_field="pallet_number", sort_dir="desc", status_filter="", search=""): + page = int(page) + page_size = int(page_size) + offset = (page - 1) * page_size + + conditions = [ + "pallet_number IS NOT NULL", + "pallet_number != ''", + "date_reserved IS NOT NULL", + "customer_number IS NOT NULL", + "customer_number != ''" + ] + + junk = ["", "0", "0000", "N/A", "TBD", "null", "999990", "999995"] + junk_list = "', '".join(junk) + conditions.append("pallet_number NOT IN ('" + junk_list + "')") + conditions.append("pallet_number NOT LIKE '999%'") + conditions.append("pallet_number REGEXP '^[0-9]'") + + if status_filter: + conditions.append("status = '" + frappe.db.escape(status_filter) + "'") + if search: + conditions.append("pallet_number LIKE '%" + frappe.db.escape(search) + "%'") + + where_clause = " AND ".join(conditions) + + total = frappe.db.sql("SELECT COUNT(*) FROM tabPallet WHERE " + where_clause)[0][0] + + pallets = frappe.db.sql("SELECT name, pallet_number, date_reserved, received_date, customer_number, company_name, inbound_weight, tester, description, qty_to_sales, weight_to_sales, finish_date, notes, status FROM tabPallet WHERE " + where_clause + " ORDER BY CAST(pallet_number AS UNSIGNED) " + sort_dir + " LIMIT " + str(page_size) + " OFFSET " + str(offset), as_dict=True) + + empty_pallet = frappe.db.sql("SELECT name, pallet_number, date_reserved, received_date, customer_number, company_name, inbound_weight, tester, description, qty_to_sales, weight_to_sales, finish_date, notes, status FROM tabPallet WHERE date_reserved IS NULL AND (customer_number IS NULL OR customer_number = '') AND pallet_number NOT IN ('" + junk_list + "') AND pallet_number NOT LIKE '999%' AND pallet_number REGEXP '^[0-9]' ORDER BY CAST(pallet_number AS UNSIGNED) DESC LIMIT 1", as_dict=True) + + result = [] + if empty_pallet: + empty_pallet[0]["_is_new"] = True + result.append(empty_pallet[0]) + result.extend(pallets) + + return {"pallets": result, "total": total, "page": page, "page_size": page_size} + +@frappe.whitelist() +def update_pallet(docname, field, value): + pallet = frappe.get_doc("Pallet", docname) + pallet.set(field, value) + pallet.save(ignore_permissions=True) + frappe.db.commit() + return {"status": "ok", "message": "Updated " + field} + +@frappe.whitelist() +def create_pallet(data): + data = frappe.parse_json(data) + pallet = frappe.new_doc("Pallet") + pallet.pallet_number = data.get("pallet_number") + pallet.status = data.get("status", "Received") + pallet.date_reserved = data.get("date_reserved") + for field, value in data.items(): + if field not in ["pallet_number", "status", "date_reserved"] and value: + pallet.set(field, value) + pallet.insert(ignore_permissions=True) + frappe.db.commit() + return {"status": "ok", "name": pallet.name} diff --git a/westech_r2/westech_r2/page/pallet_list/pallet_list.html b/westech_r2/westech_r2/page/pallet_list/pallet_list.html new file mode 100644 index 0000000..c3ac405 --- /dev/null +++ b/westech_r2/westech_r2/page/pallet_list/pallet_list.html @@ -0,0 +1,82 @@ + + +
+

📦 Pallet List

+ + + +
+ + + + + + + + + + + + + + + + + + + + +
Pallet # ⇅Date Reserved ⇅Rec. Date ⇅Customer # ⇅Lbs ⇅Who ⇅Items Tested ⇅QTY Sale ⇅Lbs Sale ⇅Finish Date ⇅StatusNotes
Loading...
+
+ +
+
diff --git a/westech_r2/westech_r2/page/pallet_list/pallet_list.js b/westech_r2/westech_r2/page/pallet_list/pallet_list.js new file mode 100644 index 0000000..0c3f9c1 --- /dev/null +++ b/westech_r2/westech_r2/page/pallet_list/pallet_list.js @@ -0,0 +1,128 @@ +frappe.pages["pallet-list"].on_page_load = function(wrapper) { + var page = frappe.ui.make_app_page({ + parent: wrapper, + title: __("Pallet List"), + single_column: true + }); + + var content = frappe.render_template("pallet-list", {}); + $(page.body).append(content); + + var currentPage = 1; + var pageSize = 100; + var currentSort = "pallet_number"; + var sortDir = "desc"; + var searchTerm = ""; + var statusFilter = ""; + + function loadPallets() { + frappe.call({ + method: "westech_r2.page.pallet-list.pallet-list.get_pallets", + args: { + page: currentPage, + page_size: pageSize, + sort_field: currentSort, + sort_dir: sortDir, + status_filter: statusFilter, + search: searchTerm + }, + callback: function(r) { + if (r.message) { + renderPallets(r.message.pallets, r.message.total); + } + } + }); + } + + function renderPallets(pallets, total) { + var tbody = $("#pallet-tbody"); + tbody.empty(); + + if (!pallets || pallets.length === 0) { + tbody.append('No pallets found'); + return; + } + + pallets.forEach(function(p) { + var statusClass = "status-" + (p.status || "").toLowerCase().replace(/\s+/g, "-"); + var link = "/app/pallet/" + encodeURIComponent(p.name); + var pn = (p.pallet_number || "").replace(//g, ">"); + + var row = '' + + '' + pn + '' + + '' + fmtDate(p.date_reserved) + '' + + '' + fmtDate(p.received_date) + '' + + '' + (p.customer_number || "") + '' + + '' + (p.company_name || "") + '' + + '' + (p.inbound_weight || "") + '' + + '' + (p.tester || "") + '' + + '' + (p.description || "") + '' + + '' + (p.qty_to_sales || "") + '' + + '' + (p.weight_to_sales || "") + '' + + '' + fmtDate(p.finish_date) + '' + + '' + (p.status || "") + '' + + '' + (p.notes || "").substring(0, 50) + '' + + ''; + tbody.append(row); + }); + + renderPagination(total); + } + + function renderPagination(total) { + var totalPages = Math.ceil(total / pageSize); + var pagination = $("#pallet-pagination"); + pagination.empty(); + + if (totalPages <= 1) return; + + var prevBtn = $('').attr("disabled", currentPage === 1) + .css({padding: "5px 12px", border: "1px solid #ddd", background: "white", cursor: "pointer", marginRight: "5px"}) + .on("click", function() { + if (currentPage > 1) { currentPage--; loadPallets(); } + }); + pagination.append(prevBtn); + + var startPage = Math.max(1, currentPage - 2); + var endPage = Math.min(totalPages, startPage + 4); + + for (var i = startPage; i <= endPage; i++) { + var btn = $('') + .css({padding: "5px 12px", border: "1px solid #ddd", background: i === currentPage ? "#3cc062" : "white", + color: i === currentPage ? "white" : "#333", cursor: "pointer", marginRight: "5px"}) + .on("click", function(page) { + return function() { currentPage = page; loadPallets(); }; + }(i)); + pagination.append(btn); + } + + var nextBtn = $('').attr("disabled", currentPage === totalPages) + .css({padding: "5px 12px", border: "1px solid #ddd", background: "white", cursor: "pointer", marginRight: "5px"}) + .on("click", function() { + if (currentPage < totalPages) { currentPage++; loadPallets(); } + }); + pagination.append(nextBtn); + } + + function fmtDate(v) { + if (!v) return ""; + var s = String(v); + if (s.indexOf("T") > -1) s = s.split("T")[0]; + if (s.indexOf(" ") > -1) s = s.split(" ")[0]; + return s; + } + + $("#pallet-search").on("input", function() { + searchTerm = $(this).val(); + currentPage = 1; + loadPallets(); + }); + + $("#status-filter").on("change", function() { + statusFilter = $(this).val(); + currentPage = 1; + loadPallets(); + }); + + loadPallets(); +}; diff --git a/westech_r2/westech_r2/page/pallet_list/pallet_list.json b/westech_r2/westech_r2/page/pallet_list/pallet_list.json new file mode 100644 index 0000000..d7304c4 --- /dev/null +++ b/westech_r2/westech_r2/page/pallet_list/pallet_list.json @@ -0,0 +1,19 @@ +{ + content: null, + creation: 2026-05-19 13:00:00.000000, + docstatus: 0, + doctype: Page, + idx: 0, + modified: 2026-05-19 13:00:00.000000, + modified_by: Administrator, + module: Westech R2, + name: pallet-list, + owner: Administrator, + page_name: pallet-list, + roles: [], + script: null, + standard: Yes, + style: null, + system_page: 0, + title: Pallet List +} diff --git a/westech_r2/westech_r2/page/pallet_list/pallet_list.py b/westech_r2/westech_r2/page/pallet_list/pallet_list.py new file mode 100644 index 0000000..0a0e433 --- /dev/null +++ b/westech_r2/westech_r2/page/pallet_list/pallet_list.py @@ -0,0 +1,64 @@ +import frappe + +@frappe.whitelist() +def get_pallets(page=1, page_size=100, sort_field="pallet_number", sort_dir="desc", status_filter="", search=""): + page = int(page) + page_size = int(page_size) + offset = (page - 1) * page_size + + conditions = [ + "pallet_number IS NOT NULL", + "pallet_number != ''", + "date_reserved IS NOT NULL", + "customer_number IS NOT NULL", + "customer_number != ''" + ] + + junk = ["", "0", "0000", "N/A", "TBD", "null", "999990", "999995"] + junk_list = "', '".join(junk) + conditions.append("pallet_number NOT IN ('" + junk_list + "')") + conditions.append("pallet_number NOT LIKE '999%'") + conditions.append("pallet_number REGEXP '^[0-9]'") + + if status_filter: + conditions.append("status = '" + frappe.db.escape(status_filter) + "'") + if search: + conditions.append("pallet_number LIKE '%" + frappe.db.escape(search) + "%'") + + where_clause = " AND ".join(conditions) + + total = frappe.db.sql("SELECT COUNT(*) FROM tabPallet WHERE " + where_clause)[0][0] + + pallets = frappe.db.sql("SELECT name, pallet_number, date_reserved, received_date, customer_number, company_name, inbound_weight, tester, description, qty_to_sales, weight_to_sales, finish_date, notes, status FROM tabPallet WHERE " + where_clause + " ORDER BY CAST(pallet_number AS UNSIGNED) " + sort_dir + " LIMIT " + str(page_size) + " OFFSET " + str(offset), as_dict=True) + + empty_pallet = frappe.db.sql("SELECT name, pallet_number, date_reserved, received_date, customer_number, company_name, inbound_weight, tester, description, qty_to_sales, weight_to_sales, finish_date, notes, status FROM tabPallet WHERE date_reserved IS NULL AND (customer_number IS NULL OR customer_number = '') AND pallet_number NOT IN ('" + junk_list + "') AND pallet_number NOT LIKE '999%' AND pallet_number REGEXP '^[0-9]' ORDER BY CAST(pallet_number AS UNSIGNED) DESC LIMIT 1", as_dict=True) + + result = [] + if empty_pallet: + empty_pallet[0]["_is_new"] = True + result.append(empty_pallet[0]) + result.extend(pallets) + + return {"pallets": result, "total": total, "page": page, "page_size": page_size} + +@frappe.whitelist() +def update_pallet(docname, field, value): + pallet = frappe.get_doc("Pallet", docname) + pallet.set(field, value) + pallet.save(ignore_permissions=True) + frappe.db.commit() + return {"status": "ok", "message": "Updated " + field} + +@frappe.whitelist() +def create_pallet(data): + data = frappe.parse_json(data) + pallet = frappe.new_doc("Pallet") + pallet.pallet_number = data.get("pallet_number") + pallet.status = data.get("status", "Received") + pallet.date_reserved = data.get("date_reserved") + for field, value in data.items(): + if field not in ["pallet_number", "status", "date_reserved"] and value: + pallet.set(field, value) + pallet.insert(ignore_permissions=True) + frappe.db.commit() + return {"status": "ok", "name": pallet.name} diff --git a/westech_r2/westech_r2/page/route-planner/route-planner.html b/westech_r2/westech_r2/page/route-planner/route-planner.html new file mode 100644 index 0000000..0116139 --- /dev/null +++ b/westech_r2/westech_r2/page/route-planner/route-planner.html @@ -0,0 +1,4 @@ +
+

Route Planner

+

Optimize pickup routes for scheduled pickups.

+
diff --git a/westech_r2/westech_r2/page/route-planner/route-planner.js b/westech_r2/westech_r2/page/route-planner/route-planner.js new file mode 100644 index 0000000..9d8ff74 --- /dev/null +++ b/westech_r2/westech_r2/page/route-planner/route-planner.js @@ -0,0 +1,38 @@ +frappe.pages["route-planner"].on_page_load = function(wrapper) { + var page = frappe.ui.make_app_page({ + parent: wrapper, + title: "Route Planner", + single_column: true + }); + + frappe.call({ + method: "westech_r2.api.optimize_routes.get_scheduled_pickups", + callback: function(r) { + if (r.message) { + renderRoutePlanner(page, r.message); + } + } + }); + + function renderRoutePlanner(page, data) { + var h = "
"; + h += "

Route Planner

"; + h += "

Schedule and optimize pickup routes.

"; + h += "
"; + h += ""; + h += "
"; + h += "
"; + $(page.body).html(h); + + $("#optimize-btn").on("click", function() { + frappe.call({ + method: "westech_r2.api.optimize_routes.optimize_routes", + callback: function(r) { + if (r.message) { + frappe.show_alert({message: "Routes optimized", indicator: "green"}); + } + } + }); + }); + } +}; diff --git a/westech_r2/westech_r2/page/route_planner/route_planner.html b/westech_r2/westech_r2/page/route_planner/route_planner.html new file mode 100644 index 0000000..0116139 --- /dev/null +++ b/westech_r2/westech_r2/page/route_planner/route_planner.html @@ -0,0 +1,4 @@ +
+

Route Planner

+

Optimize pickup routes for scheduled pickups.

+
diff --git a/westech_r2/westech_r2/page/route_planner/route_planner.js b/westech_r2/westech_r2/page/route_planner/route_planner.js new file mode 100644 index 0000000..9d8ff74 --- /dev/null +++ b/westech_r2/westech_r2/page/route_planner/route_planner.js @@ -0,0 +1,38 @@ +frappe.pages["route-planner"].on_page_load = function(wrapper) { + var page = frappe.ui.make_app_page({ + parent: wrapper, + title: "Route Planner", + single_column: true + }); + + frappe.call({ + method: "westech_r2.api.optimize_routes.get_scheduled_pickups", + callback: function(r) { + if (r.message) { + renderRoutePlanner(page, r.message); + } + } + }); + + function renderRoutePlanner(page, data) { + var h = "
"; + h += "

Route Planner

"; + h += "

Schedule and optimize pickup routes.

"; + h += "
"; + h += ""; + h += "
"; + h += "
"; + $(page.body).html(h); + + $("#optimize-btn").on("click", function() { + frappe.call({ + method: "westech_r2.api.optimize_routes.optimize_routes", + callback: function(r) { + if (r.message) { + frappe.show_alert({message: "Routes optimized", indicator: "green"}); + } + } + }); + }); + } +};