Fix load-detail and load-update page JS, add route-planner, fix pallet_list directory
This commit is contained in:
Binary file not shown.
Binary file not shown.
@@ -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": "<pre>* * * * *\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</pre>\n",
|
||||
"description": "<pre>* * * * *\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</pre>\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": "<pre>* * * * *\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</pre>\n",
|
||||
"description": "<pre>* * * * *\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</pre>\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",
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
/* CSS */
|
||||
@@ -0,0 +1,53 @@
|
||||
<div class="ld-container" style="padding: 20px;">
|
||||
<style>
|
||||
.ld-header { background: #f8f9fa; padding: 15px; border-radius: 8px; margin-bottom: 20px; }
|
||||
.ld-table { width: 100%; border-collapse: collapse; margin-top: 20px; font-size: 13px; }
|
||||
.ld-table th { background: #37474f; color: white; padding: 8px; text-align: left; }
|
||||
.ld-table td { padding: 6px 8px; border-bottom: 1px solid #e0e0e0; }
|
||||
.ld-table .num { text-align: right; font-family: monospace; }
|
||||
.ld-btn { background: #455a64; color: white; border: none; padding: 8px 20px; border-radius: 4px; cursor: pointer; margin-bottom: 20px; margin-right: 10px; }
|
||||
.ld-badge { display: inline-block; padding: 2px 8px; border-radius: 12px; font-size: 11px; margin-right: 4px; }
|
||||
.bg-rcv { background: #e3f2fd; color: #1565c0; }
|
||||
.bg-hdr { background: #fff3e0; color: #e65100; }
|
||||
.bg-tst { background: #e8f5e9; color: #2e7d32; }
|
||||
.bg-r2 { background: #f3e5f5; color: #7b1fa2; }
|
||||
.bg-des { background: #ffebee; color: #c62828; }
|
||||
</style>
|
||||
|
||||
<div class="ld-header">
|
||||
<h2>Load: <span id="ld-name"></span></h2>
|
||||
<div>In Date: <span id="ld-date"></span> | Customer: <span id="ld-cust"></span> | Devices: <span id="ld-dev"></span> | Weight: <span id="ld-wt"></span> lbs</div>
|
||||
</div>
|
||||
|
||||
<div style="margin-bottom: 15px;">
|
||||
<span class="ld-badge bg-rcv">Receiving</span>
|
||||
<span class="ld-badge bg-hdr">HDR / Disassembly</span>
|
||||
<span class="ld-badge bg-tst">Test</span>
|
||||
<span class="ld-badge bg-r2">R2 Downstream</span>
|
||||
<span class="ld-badge bg-des">Destruction</span>
|
||||
</div>
|
||||
|
||||
<button class="ld-btn" onclick="window.print()">Print Data Tracking Worksheet</button>
|
||||
<button class="ld-btn" style="background: #1976d2;" onclick="window.location.href='/app/load-list'">Back to Loads</button>
|
||||
|
||||
<table class="ld-table" id="ld-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th rowspan="2">Material Type</th>
|
||||
<th colspan="3" style="text-align:center; background:#1565c0;">Receiving</th>
|
||||
<th colspan="3" style="text-align:center; background:#e65100;">HDR / Disassembly</th>
|
||||
<th colspan="3" style="text-align:center; background:#2e7d32;">Test</th>
|
||||
<th style="text-align:center; background:#7b1fa2;">R2</th>
|
||||
<th colspan="4" style="text-align:center; background:#c62828;">Destruction</th>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>Count</th><th>Status</th><th>Send To</th>
|
||||
<th>Recv'd</th><th>HDD Out</th><th>Send To</th>
|
||||
<th>Sanitized</th><th>Status</th><th>Send To</th>
|
||||
<th>Sent</th>
|
||||
<th>Phys</th><th>HDD Need</th><th>HDD Phys</th><th>HDD Logic</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody id="ld-body"></tbody>
|
||||
</table>
|
||||
</div>
|
||||
@@ -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("<div style='padding:40px;text-align:center;'><h2>No load specified</h2><p>Use ?load=MMDDYYYY-XXXX</p></div>");
|
||||
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("<div style='padding:40px;text-align:center;'><h2>Load not found</h2></div>"); }
|
||||
}
|
||||
});
|
||||
function showLoad(page, load) {
|
||||
var h = "<div style='padding:20px;'>";
|
||||
h += "<h2>Load: " + load.name + "</h2>";
|
||||
h += "<p>Date: " + (load.incoming_date || "N/A") + " | Customer: " + (load.customer_name || load.customer_number || "N/A") + "</p>";
|
||||
h += "<p>Total Devices: " + (load.total_devices || 0) + "</p>";
|
||||
h += "<table style='width:100%;border-collapse:collapse;margin-top:20px;'><tr style='background:#37474f;color:white;'><th>Material Type</th><th>Count</th><th>Status</th><th>Send To</th></tr>";
|
||||
if (load.material_items && load.material_items.length > 0) {
|
||||
load.material_items.forEach(function(item) {
|
||||
h += "<tr style='border-bottom:1px solid #e0e0e0;'>";
|
||||
h += "<td><strong>" + (item.material_type || "") + "</strong></td>";
|
||||
h += "<td style='text-align:right;'>" + (item.total_count || "") + "</td>";
|
||||
h += "<td>" + (item.initial_data_status || "") + "</td>";
|
||||
h += "<td>" + (item.send_to || "") + "</td></tr>";
|
||||
});
|
||||
}
|
||||
h += "</table></div>";
|
||||
$(page.body).html(h);
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1 @@
|
||||
/* Load Detail page CSS */
|
||||
@@ -0,0 +1 @@
|
||||
// Load Detail page JS
|
||||
@@ -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
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
<div style="padding:20px;">
|
||||
<h2>Load Update</h2>
|
||||
<p>Use the form below to update load material items.</p>
|
||||
</div>
|
||||
@@ -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("<div style='padding:40px;text-align:center;'><h2>No load specified</h2><p>Use ?load=MMDDYYYY-XXXX</p></div>");
|
||||
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("<div style='padding:40px;text-align:center;'><h2>Load not found</h2></div>"); }
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function renderForm(page, load) {
|
||||
var h = "<div style='padding:20px;'>";
|
||||
h += "<h2>Update Load: " + load.name + "</h2>";
|
||||
h += "<p>Date: " + (load.incoming_date || "N/A") + " | Customer: " + (load.customer_name || load.customer_number || "N/A") + "</p>";
|
||||
h += "<div style='margin-bottom:15px;'>";
|
||||
h += "<button id='lu-save' class='btn btn-primary'>Save Changes</button>";
|
||||
h += " <button id='lu-print' class='btn btn-default'>Print Worksheet</button>";
|
||||
h += " <a href='/app/load/" + encodeURIComponent(load.name) + "' class='btn btn-default'>Open Form View</a>";
|
||||
h += "</div>";
|
||||
|
||||
h += "<table style='width:100%;border-collapse:collapse;font-size:13px;'><thead>";
|
||||
h += "<tr style='background:#37474f;color:white;'><th>Material Type</th><th>Count</th><th>Recv Status</th><th>Send To</th><th>Recv'd</th><th>HDD Out</th><th>Dis Send To</th><th>Sanitized</th><th>Test Status</th><th>Test Send</th><th>R2 Sent</th><th>Phys</th><th>HDD Need</th><th>HDD Phys</th><th>HDD Logic</th></tr>";
|
||||
h += "</thead><tbody>";
|
||||
|
||||
if (load.material_items && load.material_items.length > 0) {
|
||||
load.material_items.forEach(function(item, idx) {
|
||||
var prefix = "item_" + idx + "_";
|
||||
h += "<tr style='border-bottom:1px solid #e0e0e0;'>";
|
||||
h += "<td><strong>" + (item.material_type || "") + "</strong></td>";
|
||||
h += "<td><input type='number' id='" + prefix + "total_count' value='" + (item.total_count || 0) + "' style='width:50px;'></td>";
|
||||
h += "<td><input type='text' id='" + prefix + "initial_data_status' value='" + (item.initial_data_status || "") + "' style='width:60px;'></td>";
|
||||
h += "<td><input type='text' id='" + prefix + "send_to' value='" + (item.send_to || "") + "' style='width:80px;'></td>";
|
||||
h += "<td><input type='number' id='" + prefix + "devices_received' value='" + (item.devices_received || 0) + "' style='width:50px;'></td>";
|
||||
h += "<td><input type='number' id='" + prefix + "hdd_removed' value='" + (item.hdd_removed || 0) + "' style='width:50px;'></td>";
|
||||
h += "<td><input type='text' id='" + prefix + "disassembly_send_to' value='" + (item.disassembly_send_to || "") + "' style='width:80px;'></td>";
|
||||
h += "<td><input type='number' id='" + prefix + "units_sanitized_software' value='" + (item.units_sanitized_software || 0) + "' style='width:50px;'></td>";
|
||||
h += "<td><input type='text' id='" + prefix + "test_data_status' value='" + (item.test_data_status || "") + "' style='width:60px;'></td>";
|
||||
h += "<td><input type='text' id='" + prefix + "test_send_to' value='" + (item.test_send_to || "") + "' style='width:80px;'></td>";
|
||||
h += "<td><input type='number' id='" + prefix + "r2_units_sent' value='" + (item.r2_units_sent || 0) + "' style='width:50px;'></td>";
|
||||
h += "<td><input type='number' id='" + prefix + "units_physical_destruction' value='" + (item.units_physical_destruction || 0) + "' style='width:50px;'></td>";
|
||||
h += "<td><input type='number' id='" + prefix + "hdd_needs_sanitize' value='" + (item.hdd_needs_sanitize || 0) + "' style='width:50px;'></td>";
|
||||
h += "<td><input type='number' id='" + prefix + "hdd_physical_destruction' value='" + (item.hdd_physical_destruction || 0) + "' style='width:50px;'></td>";
|
||||
h += "<td><input type='number' id='" + prefix + "hdd_logical_sanitization' value='" + (item.hdd_logical_sanitization || 0) + "' style='width:50px;'></td>";
|
||||
h += "</tr>";
|
||||
});
|
||||
}
|
||||
h += "</tbody></table></div>";
|
||||
$(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();
|
||||
};
|
||||
@@ -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"}
|
||||
@@ -0,0 +1 @@
|
||||
/* Load Update page CSS */
|
||||
@@ -0,0 +1,4 @@
|
||||
<div style="padding:20px;">
|
||||
<h2>Load Update</h2>
|
||||
<p>Use the form below to update load material items.</p>
|
||||
</div>
|
||||
@@ -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("<div style='padding:40px;text-align:center;'><h2>No load specified</h2><p>Use ?load=MMDDYYYY-XXXX</p></div>");
|
||||
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("<div style='padding:40px;text-align:center;'><h2>Load not found</h2></div>"); }
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function renderForm(page, load) {
|
||||
var h = "<div style='padding:20px;'>";
|
||||
h += "<h2>Update Load: " + load.name + "</h2>";
|
||||
h += "<p>Date: " + (load.incoming_date || "N/A") + " | Customer: " + (load.customer_name || load.customer_number || "N/A") + "</p>";
|
||||
h += "<div style='margin-bottom:15px;'>";
|
||||
h += "<button id='lu-save' class='btn btn-primary'>Save Changes</button>";
|
||||
h += " <button id='lu-print' class='btn btn-default'>Print Worksheet</button>";
|
||||
h += " <a href='/app/load/" + encodeURIComponent(load.name) + "' class='btn btn-default'>Open Form View</a>";
|
||||
h += "</div>";
|
||||
|
||||
h += "<table style='width:100%;border-collapse:collapse;font-size:13px;'><thead>";
|
||||
h += "<tr style='background:#37474f;color:white;'><th>Material Type</th><th>Count</th><th>Recv Status</th><th>Send To</th><th>Recv'd</th><th>HDD Out</th><th>Dis Send To</th><th>Sanitized</th><th>Test Status</th><th>Test Send</th><th>R2 Sent</th><th>Phys</th><th>HDD Need</th><th>HDD Phys</th><th>HDD Logic</th></tr>";
|
||||
h += "</thead><tbody>";
|
||||
|
||||
if (load.material_items && load.material_items.length > 0) {
|
||||
load.material_items.forEach(function(item, idx) {
|
||||
var prefix = "item_" + idx + "_";
|
||||
h += "<tr style='border-bottom:1px solid #e0e0e0;'>";
|
||||
h += "<td><strong>" + (item.material_type || "") + "</strong></td>";
|
||||
h += "<td><input type='number' id='" + prefix + "total_count' value='" + (item.total_count || 0) + "' style='width:50px;'></td>";
|
||||
h += "<td><input type='text' id='" + prefix + "initial_data_status' value='" + (item.initial_data_status || "") + "' style='width:60px;'></td>";
|
||||
h += "<td><input type='text' id='" + prefix + "send_to' value='" + (item.send_to || "") + "' style='width:80px;'></td>";
|
||||
h += "<td><input type='number' id='" + prefix + "devices_received' value='" + (item.devices_received || 0) + "' style='width:50px;'></td>";
|
||||
h += "<td><input type='number' id='" + prefix + "hdd_removed' value='" + (item.hdd_removed || 0) + "' style='width:50px;'></td>";
|
||||
h += "<td><input type='text' id='" + prefix + "disassembly_send_to' value='" + (item.disassembly_send_to || "") + "' style='width:80px;'></td>";
|
||||
h += "<td><input type='number' id='" + prefix + "units_sanitized_software' value='" + (item.units_sanitized_software || 0) + "' style='width:50px;'></td>";
|
||||
h += "<td><input type='text' id='" + prefix + "test_data_status' value='" + (item.test_data_status || "") + "' style='width:60px;'></td>";
|
||||
h += "<td><input type='text' id='" + prefix + "test_send_to' value='" + (item.test_send_to || "") + "' style='width:80px;'></td>";
|
||||
h += "<td><input type='number' id='" + prefix + "r2_units_sent' value='" + (item.r2_units_sent || 0) + "' style='width:50px;'></td>";
|
||||
h += "<td><input type='number' id='" + prefix + "units_physical_destruction' value='" + (item.units_physical_destruction || 0) + "' style='width:50px;'></td>";
|
||||
h += "<td><input type='number' id='" + prefix + "hdd_needs_sanitize' value='" + (item.hdd_needs_sanitize || 0) + "' style='width:50px;'></td>";
|
||||
h += "<td><input type='number' id='" + prefix + "hdd_physical_destruction' value='" + (item.hdd_physical_destruction || 0) + "' style='width:50px;'></td>";
|
||||
h += "<td><input type='number' id='" + prefix + "hdd_logical_sanitization' value='" + (item.hdd_logical_sanitization || 0) + "' style='width:50px;'></td>";
|
||||
h += "</tr>";
|
||||
});
|
||||
}
|
||||
h += "</tbody></table></div>";
|
||||
$(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();
|
||||
};
|
||||
@@ -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
|
||||
}
|
||||
@@ -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"}
|
||||
@@ -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("<div style='padding:40px;text-align:center;'><h2>No load specified</h2><p>Use ?load=MMDDYYYY-XXXX</p></div>");
|
||||
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("<div style='padding:40px;text-align:center;'><h2>Load not found</h2></div>"); }
|
||||
}
|
||||
});
|
||||
|
||||
function showLoad(page, load) {
|
||||
var h = "<div style='padding:20px;'>";
|
||||
h += "<div style='background:#f8f9fa;padding:15px;border-radius:8px;margin-bottom:20px;'>";
|
||||
h += "<h2>Load: " + load.name + "</h2>";
|
||||
h += "<div>In Date: " + (load.incoming_date || "N/A") + " | Customer: " + (load.customer_name || load.customer_number || "N/A") + "</div>";
|
||||
h += "<div>Devices: " + (load.total_devices || 0) + " | Weight: " + (load.total_weight || 0) + " lbs</div>";
|
||||
h += "</div>";
|
||||
h += "<button class='btn btn-primary' onclick='window.print()' style='margin-bottom:15px;'>Print Data Tracking Worksheet</button> ";
|
||||
h += "<a href='/app/load/" + encodeURIComponent(load.name) + "' class='btn btn-default' style='margin-bottom:15px;'>Open Form View</a> ";
|
||||
h += "<a href='/app/load-update?load=" + encodeURIComponent(load.name) + "' class='btn btn-default' style='margin-bottom:15px;'>Edit Load</a>";
|
||||
h += "<table style='width:100%;border-collapse:collapse;font-size:13px;margin-top:20px;'>";
|
||||
h += "<thead><tr style='background:#37474f;color:white;'>";
|
||||
h += "<th>Material Type</th><th>Count</th><th>Rcv Status</th><th>Send To</th>";
|
||||
h += "<th>Recv'd</th><th>HDD Out</th><th>Dis Send To</th>";
|
||||
h += "<th>Sanitized</th><th>Test Status</th><th>Test Send</th>";
|
||||
h += "<th>R2 Sent</th><th>Phys</th><th>HDD Need</th><th>HDD Phys</th><th>HDD Logic</th>";
|
||||
h += "</tr></thead><tbody>";
|
||||
|
||||
if (load.material_items && load.material_items.length > 0) {
|
||||
load.material_items.forEach(function(item) {
|
||||
h += "<tr style='border-bottom:1px solid #e0e0e0;'>";
|
||||
h += "<td><strong>" + (item.material_type || "") + "</strong></td>";
|
||||
h += "<td style='text-align:right;'>" + (item.total_count || 0) + "</td>";
|
||||
h += "<td>" + (item.initial_data_status || "") + "</td>";
|
||||
h += "<td>" + (item.send_to || "") + "</td>";
|
||||
h += "<td style='text-align:right;'>" + (item.devices_received || 0) + "</td>";
|
||||
h += "<td style='text-align:right;'>" + (item.hdd_removed || 0) + "</td>";
|
||||
h += "<td>" + (item.disassembly_send_to || "") + "</td>";
|
||||
h += "<td style='text-align:right;'>" + (item.units_sanitized_software || 0) + "</td>";
|
||||
h += "<td>" + (item.test_data_status || "") + "</td>";
|
||||
h += "<td>" + (item.test_send_to || "") + "</td>";
|
||||
h += "<td style='text-align:right;'>" + (item.r2_units_sent || 0) + "</td>";
|
||||
h += "<td style='text-align:right;'>" + (item.units_physical_destruction || 0) + "</td>";
|
||||
h += "<td style='text-align:right;'>" + (item.hdd_needs_sanitize || 0) + "</td>";
|
||||
h += "<td style='text-align:right;'>" + (item.hdd_physical_destruction || 0) + "</td>";
|
||||
h += "<td style='text-align:right;'>" + (item.hdd_logical_sanitization || 0) + "</td>";
|
||||
h += "</tr>";
|
||||
});
|
||||
}
|
||||
h += "</tbody></table></div>";
|
||||
$(page.body).html(h);
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1 @@
|
||||
/* CSS */
|
||||
@@ -0,0 +1,53 @@
|
||||
<div class="ld-container" style="padding: 20px;">
|
||||
<style>
|
||||
.ld-header { background: #f8f9fa; padding: 15px; border-radius: 8px; margin-bottom: 20px; }
|
||||
.ld-table { width: 100%; border-collapse: collapse; margin-top: 20px; font-size: 13px; }
|
||||
.ld-table th { background: #37474f; color: white; padding: 8px; text-align: left; }
|
||||
.ld-table td { padding: 6px 8px; border-bottom: 1px solid #e0e0e0; }
|
||||
.ld-table .num { text-align: right; font-family: monospace; }
|
||||
.ld-btn { background: #455a64; color: white; border: none; padding: 8px 20px; border-radius: 4px; cursor: pointer; margin-bottom: 20px; margin-right: 10px; }
|
||||
.ld-badge { display: inline-block; padding: 2px 8px; border-radius: 12px; font-size: 11px; margin-right: 4px; }
|
||||
.bg-rcv { background: #e3f2fd; color: #1565c0; }
|
||||
.bg-hdr { background: #fff3e0; color: #e65100; }
|
||||
.bg-tst { background: #e8f5e9; color: #2e7d32; }
|
||||
.bg-r2 { background: #f3e5f5; color: #7b1fa2; }
|
||||
.bg-des { background: #ffebee; color: #c62828; }
|
||||
</style>
|
||||
|
||||
<div class="ld-header">
|
||||
<h2>Load: <span id="ld-name"></span></h2>
|
||||
<div>In Date: <span id="ld-date"></span> | Customer: <span id="ld-cust"></span> | Devices: <span id="ld-dev"></span> | Weight: <span id="ld-wt"></span> lbs</div>
|
||||
</div>
|
||||
|
||||
<div style="margin-bottom: 15px;">
|
||||
<span class="ld-badge bg-rcv">Receiving</span>
|
||||
<span class="ld-badge bg-hdr">HDR / Disassembly</span>
|
||||
<span class="ld-badge bg-tst">Test</span>
|
||||
<span class="ld-badge bg-r2">R2 Downstream</span>
|
||||
<span class="ld-badge bg-des">Destruction</span>
|
||||
</div>
|
||||
|
||||
<button class="ld-btn" onclick="window.print()">Print Data Tracking Worksheet</button>
|
||||
<button class="ld-btn" style="background: #1976d2;" onclick="window.location.href='/app/load-list'">Back to Loads</button>
|
||||
|
||||
<table class="ld-table" id="ld-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th rowspan="2">Material Type</th>
|
||||
<th colspan="3" style="text-align:center; background:#1565c0;">Receiving</th>
|
||||
<th colspan="3" style="text-align:center; background:#e65100;">HDR / Disassembly</th>
|
||||
<th colspan="3" style="text-align:center; background:#2e7d32;">Test</th>
|
||||
<th style="text-align:center; background:#7b1fa2;">R2</th>
|
||||
<th colspan="4" style="text-align:center; background:#c62828;">Destruction</th>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>Count</th><th>Status</th><th>Send To</th>
|
||||
<th>Recv'd</th><th>HDD Out</th><th>Send To</th>
|
||||
<th>Sanitized</th><th>Status</th><th>Send To</th>
|
||||
<th>Sent</th>
|
||||
<th>Phys</th><th>HDD Need</th><th>HDD Phys</th><th>HDD Logic</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody id="ld-body"></tbody>
|
||||
</table>
|
||||
</div>
|
||||
@@ -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("<div style='padding:40px;text-align:center;'><h2>No load specified</h2><p>Use ?load=MMDDYYYY-XXXX</p></div>");
|
||||
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("<div style='padding:40px;text-align:center;'><h2>Load not found</h2></div>"); }
|
||||
}
|
||||
});
|
||||
|
||||
function showLoad(page, load) {
|
||||
var h = "<div style='padding:20px;'>";
|
||||
h += "<div style='background:#f8f9fa;padding:15px;border-radius:8px;margin-bottom:20px;'>";
|
||||
h += "<h2>Load: " + load.name + "</h2>";
|
||||
h += "<div>In Date: " + (load.incoming_date || "N/A") + " | Customer: " + (load.customer_name || load.customer_number || "N/A") + "</div>";
|
||||
h += "<div>Devices: " + (load.total_devices || 0) + " | Weight: " + (load.total_weight || 0) + " lbs</div>";
|
||||
h += "</div>";
|
||||
h += "<button class='btn btn-primary' onclick='window.print()' style='margin-bottom:15px;'>Print Data Tracking Worksheet</button> ";
|
||||
h += "<a href='/app/load/" + encodeURIComponent(load.name) + "' class='btn btn-default' style='margin-bottom:15px;'>Open Form View</a> ";
|
||||
h += "<a href='/app/load-update?load=" + encodeURIComponent(load.name) + "' class='btn btn-default' style='margin-bottom:15px;'>Edit Load</a>";
|
||||
h += "<table style='width:100%;border-collapse:collapse;font-size:13px;margin-top:20px;'>";
|
||||
h += "<thead><tr style='background:#37474f;color:white;'>";
|
||||
h += "<th>Material Type</th><th>Count</th><th>Rcv Status</th><th>Send To</th>";
|
||||
h += "<th>Recv'd</th><th>HDD Out</th><th>Dis Send To</th>";
|
||||
h += "<th>Sanitized</th><th>Test Status</th><th>Test Send</th>";
|
||||
h += "<th>R2 Sent</th><th>Phys</th><th>HDD Need</th><th>HDD Phys</th><th>HDD Logic</th>";
|
||||
h += "</tr></thead><tbody>";
|
||||
|
||||
if (load.material_items && load.material_items.length > 0) {
|
||||
load.material_items.forEach(function(item) {
|
||||
h += "<tr style='border-bottom:1px solid #e0e0e0;'>";
|
||||
h += "<td><strong>" + (item.material_type || "") + "</strong></td>";
|
||||
h += "<td style='text-align:right;'>" + (item.total_count || 0) + "</td>";
|
||||
h += "<td>" + (item.initial_data_status || "") + "</td>";
|
||||
h += "<td>" + (item.send_to || "") + "</td>";
|
||||
h += "<td style='text-align:right;'>" + (item.devices_received || 0) + "</td>";
|
||||
h += "<td style='text-align:right;'>" + (item.hdd_removed || 0) + "</td>";
|
||||
h += "<td>" + (item.disassembly_send_to || "") + "</td>";
|
||||
h += "<td style='text-align:right;'>" + (item.units_sanitized_software || 0) + "</td>";
|
||||
h += "<td>" + (item.test_data_status || "") + "</td>";
|
||||
h += "<td>" + (item.test_send_to || "") + "</td>";
|
||||
h += "<td style='text-align:right;'>" + (item.r2_units_sent || 0) + "</td>";
|
||||
h += "<td style='text-align:right;'>" + (item.units_physical_destruction || 0) + "</td>";
|
||||
h += "<td style='text-align:right;'>" + (item.hdd_needs_sanitize || 0) + "</td>";
|
||||
h += "<td style='text-align:right;'>" + (item.hdd_physical_destruction || 0) + "</td>";
|
||||
h += "<td style='text-align:right;'>" + (item.hdd_logical_sanitization || 0) + "</td>";
|
||||
h += "</tr>";
|
||||
});
|
||||
}
|
||||
h += "</tbody></table></div>";
|
||||
$(page.body).html(h);
|
||||
}
|
||||
};
|
||||
@@ -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
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
<div style="padding:20px;">
|
||||
<h2>Load Update</h2>
|
||||
<p>Use the form below to update load material items.</p>
|
||||
</div>
|
||||
@@ -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("<div style='padding:40px;text-align:center;'><h2>No load specified</h2><p>Use ?load=MMDDYYYY-XXXX</p></div>");
|
||||
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("<div style='padding:40px;text-align:center;'><h2>Load not found</h2></div>"); }
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function renderForm(page, load) {
|
||||
var h = "<div style='padding:20px;'>";
|
||||
h += "<h2>Update Load: " + load.name + "</h2>";
|
||||
h += "<p>Date: " + (load.incoming_date || "N/A") + " | Customer: " + (load.customer_name || load.customer_number || "N/A") + "</p>";
|
||||
h += "<div style='margin-bottom:15px;'>";
|
||||
h += "<button id='lu-save' class='btn btn-primary'>Save Changes</button> ";
|
||||
h += "<button id='lu-print' class='btn btn-default'>Print Worksheet</button> ";
|
||||
h += "<a href='/app/load/" + encodeURIComponent(load.name) + "' class='btn btn-default'>Open Form View</a>";
|
||||
h += "</div>";
|
||||
h += "<table style='width:100%;border-collapse:collapse;font-size:13px;'>";
|
||||
h += "<thead><tr style='background:#37474f;color:white;'>";
|
||||
h += "<th>Material Type</th><th>Count</th><th>Recv Status</th><th>Send To</th>";
|
||||
h += "<th>Recv'd</th><th>HDD Out</th><th>Dis Send To</th>";
|
||||
h += "<th>Sanitized</th><th>Test Status</th><th>Test Send</th>";
|
||||
h += "<th>R2 Sent</th><th>Phys</th><th>HDD Need</th><th>HDD Phys</th><th>HDD Logic</th>";
|
||||
h += "</tr></thead><tbody>";
|
||||
|
||||
if (load.material_items && load.material_items.length > 0) {
|
||||
load.material_items.forEach(function(item, idx) {
|
||||
var prefix = "item_" + idx + "_";
|
||||
h += "<tr style='border-bottom:1px solid #e0e0e0;'>";
|
||||
h += "<td><strong>" + (item.material_type || "") + "</strong></td>";
|
||||
h += "<td><input type='number' id='" + prefix + "total_count' value='" + (item.total_count || 0) + "' style='width:50px;'></td>";
|
||||
h += "<td><input type='text' id='" + prefix + "initial_data_status' value='" + (item.initial_data_status || "") + "' style='width:60px;'></td>";
|
||||
h += "<td><input type='text' id='" + prefix + "send_to' value='" + (item.send_to || "") + "' style='width:80px;'></td>";
|
||||
h += "<td><input type='number' id='" + prefix + "devices_received' value='" + (item.devices_received || 0) + "' style='width:50px;'></td>";
|
||||
h += "<td><input type='number' id='" + prefix + "hdd_removed' value='" + (item.hdd_removed || 0) + "' style='width:50px;'></td>";
|
||||
h += "<td><input type='text' id='" + prefix + "disassembly_send_to' value='" + (item.disassembly_send_to || "") + "' style='width:80px;'></td>";
|
||||
h += "<td><input type='number' id='" + prefix + "units_sanitized_software' value='" + (item.units_sanitized_software || 0) + "' style='width:50px;'></td>";
|
||||
h += "<td><input type='text' id='" + prefix + "test_data_status' value='" + (item.test_data_status || "") + "' style='width:60px;'></td>";
|
||||
h += "<td><input type='text' id='" + prefix + "test_send_to' value='" + (item.test_send_to || "") + "' style='width:80px;'></td>";
|
||||
h += "<td><input type='number' id='" + prefix + "r2_units_sent' value='" + (item.r2_units_sent || 0) + "' style='width:50px;'></td>";
|
||||
h += "<td><input type='number' id='" + prefix + "units_physical_destruction' value='" + (item.units_physical_destruction || 0) + "' style='width:50px;'></td>";
|
||||
h += "<td><input type='number' id='" + prefix + "hdd_needs_sanitize' value='" + (item.hdd_needs_sanitize || 0) + "' style='width:50px;'></td>";
|
||||
h += "<td><input type='number' id='" + prefix + "hdd_physical_destruction' value='" + (item.hdd_physical_destruction || 0) + "' style='width:50px;'></td>";
|
||||
h += "<td><input type='number' id='" + prefix + "hdd_logical_sanitization' value='" + (item.hdd_logical_sanitization || 0) + "' style='width:50px;'></td>";
|
||||
h += "</tr>";
|
||||
});
|
||||
}
|
||||
h += "</tbody></table></div>";
|
||||
$(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();
|
||||
};
|
||||
@@ -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"}
|
||||
@@ -0,0 +1 @@
|
||||
/* Load Update page CSS */
|
||||
@@ -0,0 +1,4 @@
|
||||
<div style="padding:20px;">
|
||||
<h2>Load Update</h2>
|
||||
<p>Use the form below to update load material items.</p>
|
||||
</div>
|
||||
@@ -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("<div style='padding:40px;text-align:center;'><h2>No load specified</h2><p>Use ?load=MMDDYYYY-XXXX</p></div>");
|
||||
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("<div style='padding:40px;text-align:center;'><h2>Load not found</h2></div>"); }
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function renderForm(page, load) {
|
||||
var h = "<div style='padding:20px;'>";
|
||||
h += "<h2>Update Load: " + load.name + "</h2>";
|
||||
h += "<p>Date: " + (load.incoming_date || "N/A") + " | Customer: " + (load.customer_name || load.customer_number || "N/A") + "</p>";
|
||||
h += "<div style='margin-bottom:15px;'>";
|
||||
h += "<button id='lu-save' class='btn btn-primary'>Save Changes</button> ";
|
||||
h += "<button id='lu-print' class='btn btn-default'>Print Worksheet</button> ";
|
||||
h += "<a href='/app/load/" + encodeURIComponent(load.name) + "' class='btn btn-default'>Open Form View</a>";
|
||||
h += "</div>";
|
||||
h += "<table style='width:100%;border-collapse:collapse;font-size:13px;'>";
|
||||
h += "<thead><tr style='background:#37474f;color:white;'>";
|
||||
h += "<th>Material Type</th><th>Count</th><th>Recv Status</th><th>Send To</th>";
|
||||
h += "<th>Recv'd</th><th>HDD Out</th><th>Dis Send To</th>";
|
||||
h += "<th>Sanitized</th><th>Test Status</th><th>Test Send</th>";
|
||||
h += "<th>R2 Sent</th><th>Phys</th><th>HDD Need</th><th>HDD Phys</th><th>HDD Logic</th>";
|
||||
h += "</tr></thead><tbody>";
|
||||
|
||||
if (load.material_items && load.material_items.length > 0) {
|
||||
load.material_items.forEach(function(item, idx) {
|
||||
var prefix = "item_" + idx + "_";
|
||||
h += "<tr style='border-bottom:1px solid #e0e0e0;'>";
|
||||
h += "<td><strong>" + (item.material_type || "") + "</strong></td>";
|
||||
h += "<td><input type='number' id='" + prefix + "total_count' value='" + (item.total_count || 0) + "' style='width:50px;'></td>";
|
||||
h += "<td><input type='text' id='" + prefix + "initial_data_status' value='" + (item.initial_data_status || "") + "' style='width:60px;'></td>";
|
||||
h += "<td><input type='text' id='" + prefix + "send_to' value='" + (item.send_to || "") + "' style='width:80px;'></td>";
|
||||
h += "<td><input type='number' id='" + prefix + "devices_received' value='" + (item.devices_received || 0) + "' style='width:50px;'></td>";
|
||||
h += "<td><input type='number' id='" + prefix + "hdd_removed' value='" + (item.hdd_removed || 0) + "' style='width:50px;'></td>";
|
||||
h += "<td><input type='text' id='" + prefix + "disassembly_send_to' value='" + (item.disassembly_send_to || "") + "' style='width:80px;'></td>";
|
||||
h += "<td><input type='number' id='" + prefix + "units_sanitized_software' value='" + (item.units_sanitized_software || 0) + "' style='width:50px;'></td>";
|
||||
h += "<td><input type='text' id='" + prefix + "test_data_status' value='" + (item.test_data_status || "") + "' style='width:60px;'></td>";
|
||||
h += "<td><input type='text' id='" + prefix + "test_send_to' value='" + (item.test_send_to || "") + "' style='width:80px;'></td>";
|
||||
h += "<td><input type='number' id='" + prefix + "r2_units_sent' value='" + (item.r2_units_sent || 0) + "' style='width:50px;'></td>";
|
||||
h += "<td><input type='number' id='" + prefix + "units_physical_destruction' value='" + (item.units_physical_destruction || 0) + "' style='width:50px;'></td>";
|
||||
h += "<td><input type='number' id='" + prefix + "hdd_needs_sanitize' value='" + (item.hdd_needs_sanitize || 0) + "' style='width:50px;'></td>";
|
||||
h += "<td><input type='number' id='" + prefix + "hdd_physical_destruction' value='" + (item.hdd_physical_destruction || 0) + "' style='width:50px;'></td>";
|
||||
h += "<td><input type='number' id='" + prefix + "hdd_logical_sanitization' value='" + (item.hdd_logical_sanitization || 0) + "' style='width:50px;'></td>";
|
||||
h += "</tr>";
|
||||
});
|
||||
}
|
||||
h += "</tbody></table></div>";
|
||||
$(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();
|
||||
};
|
||||
@@ -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
|
||||
}
|
||||
@@ -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"}
|
||||
Binary file not shown.
Binary file not shown.
@@ -0,0 +1 @@
|
||||
# Pallet List page for Westech R2
|
||||
Binary file not shown.
Binary file not shown.
@@ -0,0 +1,82 @@
|
||||
<style>
|
||||
.pallet-list-page { font-family: Helvetica Neue, Arial, sans-serif; }
|
||||
.pallet-list-page h2 { color: #2F5496; margin-bottom: 16px; font-size: 20px; }
|
||||
.pallet-search-box { margin-bottom: 16px; display: flex; gap: 10px; align-items: center; flex-wrap: wrap; }
|
||||
.pallet-search-box input, .pallet-search-box select {
|
||||
padding: 6px 10px; border: 1px solid #ddd; border-radius: 4px; font-size: 13px;
|
||||
}
|
||||
.pallet-search-box button {
|
||||
padding: 6px 14px; border: 1px solid #ddd; border-radius: 4px; cursor: pointer;
|
||||
font-size: 13px; background: white;
|
||||
}
|
||||
.pallet-search-box button.btn-primary { background: #2F5496; color: white; border-color: #2F5496; }
|
||||
.pallet-search-box button.btn-success { background: #548235; color: white; border-color: #548235; }
|
||||
.pallet-table-container { padding: 0; overflow-x: auto; background: white; border-radius: 4px; }
|
||||
.pallet-table { width: 100%; border-collapse: collapse; font-size: 13px; }
|
||||
.pallet-table th {
|
||||
background: #2F5496; color: white; padding: 8px 10px; text-align: left;
|
||||
cursor: pointer; white-space: nowrap; font-weight: 500;
|
||||
}
|
||||
.pallet-table td { padding: 5px 10px; border-bottom: 1px solid #eee; }
|
||||
.pallet-table tr:hover { background: #f5f5f5; }
|
||||
.pallet-table tr.new-row { background: #FFF9E6 !important; }
|
||||
.pallet-table a { color: #2F5496; text-decoration: none; font-weight: 600; }
|
||||
.status-received { color: #2196F3; font-weight: 600; }
|
||||
.status-sorting { color: #FF9800; font-weight: 600; }
|
||||
.status-processing { color: #9C27B0; font-weight: 600; }
|
||||
.status-complete { color: #4CAF50; font-weight: 600; }
|
||||
.status-shipped { color: #607D8B; font-weight: 600; }
|
||||
.pallet-pagination { margin-top: 12px; display: flex; gap: 5px; align-items: center; }
|
||||
.pallet-pagination button {
|
||||
padding: 5px 12px; border: 1px solid #ddd; background: white; cursor: pointer;
|
||||
border-radius: 4px; font-size: 13px; color: #2F5496;
|
||||
}
|
||||
.pallet-pagination button.active { background: #2F5496; color: white; border-color: #2F5496; }
|
||||
.pallet-pagination button:disabled { opacity: 0.5; cursor: not-allowed; }
|
||||
.pallet-count { margin-left: auto; color: #666; font-size: 13px; }
|
||||
</style>
|
||||
|
||||
<div class="pallet-list-page">
|
||||
<h2>📦 Pallet List</h2>
|
||||
|
||||
<div class="pallet-search-box">
|
||||
<input type="text" id="pallet-search" placeholder="Search pallet #..." style="max-width:200px;">
|
||||
<select id="status-filter">
|
||||
<option value="">All Statuses</option>
|
||||
<option value="Received">Received</option>
|
||||
<option value="Sorting">Sorting</option>
|
||||
<option value="Processing">Processing</option>
|
||||
<option value="Complete">Complete</option>
|
||||
<option value="Shipped">Shipped</option>
|
||||
</select>
|
||||
<button class="btn btn-default" id="btn-clear">✕ Clear</button>
|
||||
<button class="btn btn-success" id="btn-export">⬇ Export CSV</button>
|
||||
<span class="pallet-count" id="pallet-count"></span>
|
||||
</div>
|
||||
|
||||
<div class="pallet-table-container">
|
||||
<table class="pallet-table" id="pallet-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th data-sort="pallet_number">Pallet # ⇅</th>
|
||||
<th data-sort="date_reserved">Date Reserved ⇅</th>
|
||||
<th data-sort="received_date">Rec. Date ⇅</th>
|
||||
<th data-sort="customer_number">Customer # ⇅</th>
|
||||
<th data-sort="inbound_weight">Lbs ⇅</th>
|
||||
<th data-sort="tester">Who ⇅</th>
|
||||
<th data-sort="description">Items Tested ⇅</th>
|
||||
<th data-sort="qty_to_sales">QTY Sale ⇅</th>
|
||||
<th data-sort="weight_to_sales">Lbs Sale ⇅</th>
|
||||
<th data-sort="finish_date">Finish Date ⇅</th>
|
||||
<th>Status</th>
|
||||
<th>Notes</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody id="pallet-tbody">
|
||||
<tr><td colspan="12" style="text-align:center;padding:40px;color:#666;">Loading...</td></tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<div class="pallet-pagination" id="pallet-pagination"></div>
|
||||
</div>
|
||||
@@ -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('<tr><td colspan="13" style="text-align:center;padding:40px;">No pallets found</td></tr>');
|
||||
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, "<").replace(/>/g, ">");
|
||||
|
||||
var row = '<tr>' +
|
||||
'<td><a href="' + link + '" style="color:#3cc062;text-decoration:none;font-weight:600;">' + pn + '</a></td>' +
|
||||
'<td>' + fmtDate(p.date_reserved) + '</td>' +
|
||||
'<td>' + fmtDate(p.received_date) + '</td>' +
|
||||
'<td>' + (p.customer_number || "") + '</td>' +
|
||||
'<td>' + (p.company_name || "") + '</td>' +
|
||||
'<td>' + (p.inbound_weight || "") + '</td>' +
|
||||
'<td>' + (p.tester || "") + '</td>' +
|
||||
'<td>' + (p.description || "") + '</td>' +
|
||||
'<td>' + (p.qty_to_sales || "") + '</td>' +
|
||||
'<td>' + (p.weight_to_sales || "") + '</td>' +
|
||||
'<td>' + fmtDate(p.finish_date) + '</td>' +
|
||||
'<td class="' + statusClass + '">' + (p.status || "") + '</td>' +
|
||||
'<td>' + (p.notes || "").substring(0, 50) + '</td>' +
|
||||
'</tr>';
|
||||
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 = $('<button>« Prev</button>').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 = $('<button>' + i + '</button>')
|
||||
.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 = $('<button>Next »</button>').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();
|
||||
};
|
||||
@@ -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
|
||||
}
|
||||
@@ -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}
|
||||
@@ -0,0 +1,82 @@
|
||||
<style>
|
||||
.pallet-list-page { font-family: Helvetica Neue, Arial, sans-serif; }
|
||||
.pallet-list-page h2 { color: #2F5496; margin-bottom: 16px; font-size: 20px; }
|
||||
.pallet-search-box { margin-bottom: 16px; display: flex; gap: 10px; align-items: center; flex-wrap: wrap; }
|
||||
.pallet-search-box input, .pallet-search-box select {
|
||||
padding: 6px 10px; border: 1px solid #ddd; border-radius: 4px; font-size: 13px;
|
||||
}
|
||||
.pallet-search-box button {
|
||||
padding: 6px 14px; border: 1px solid #ddd; border-radius: 4px; cursor: pointer;
|
||||
font-size: 13px; background: white;
|
||||
}
|
||||
.pallet-search-box button.btn-primary { background: #2F5496; color: white; border-color: #2F5496; }
|
||||
.pallet-search-box button.btn-success { background: #548235; color: white; border-color: #548235; }
|
||||
.pallet-table-container { padding: 0; overflow-x: auto; background: white; border-radius: 4px; }
|
||||
.pallet-table { width: 100%; border-collapse: collapse; font-size: 13px; }
|
||||
.pallet-table th {
|
||||
background: #2F5496; color: white; padding: 8px 10px; text-align: left;
|
||||
cursor: pointer; white-space: nowrap; font-weight: 500;
|
||||
}
|
||||
.pallet-table td { padding: 5px 10px; border-bottom: 1px solid #eee; }
|
||||
.pallet-table tr:hover { background: #f5f5f5; }
|
||||
.pallet-table tr.new-row { background: #FFF9E6 !important; }
|
||||
.pallet-table a { color: #2F5496; text-decoration: none; font-weight: 600; }
|
||||
.status-received { color: #2196F3; font-weight: 600; }
|
||||
.status-sorting { color: #FF9800; font-weight: 600; }
|
||||
.status-processing { color: #9C27B0; font-weight: 600; }
|
||||
.status-complete { color: #4CAF50; font-weight: 600; }
|
||||
.status-shipped { color: #607D8B; font-weight: 600; }
|
||||
.pallet-pagination { margin-top: 12px; display: flex; gap: 5px; align-items: center; }
|
||||
.pallet-pagination button {
|
||||
padding: 5px 12px; border: 1px solid #ddd; background: white; cursor: pointer;
|
||||
border-radius: 4px; font-size: 13px; color: #2F5496;
|
||||
}
|
||||
.pallet-pagination button.active { background: #2F5496; color: white; border-color: #2F5496; }
|
||||
.pallet-pagination button:disabled { opacity: 0.5; cursor: not-allowed; }
|
||||
.pallet-count { margin-left: auto; color: #666; font-size: 13px; }
|
||||
</style>
|
||||
|
||||
<div class="pallet-list-page">
|
||||
<h2>📦 Pallet List</h2>
|
||||
|
||||
<div class="pallet-search-box">
|
||||
<input type="text" id="pallet-search" placeholder="Search pallet #..." style="max-width:200px;">
|
||||
<select id="status-filter">
|
||||
<option value="">All Statuses</option>
|
||||
<option value="Received">Received</option>
|
||||
<option value="Sorting">Sorting</option>
|
||||
<option value="Processing">Processing</option>
|
||||
<option value="Complete">Complete</option>
|
||||
<option value="Shipped">Shipped</option>
|
||||
</select>
|
||||
<button class="btn btn-default" id="btn-clear">✕ Clear</button>
|
||||
<button class="btn btn-success" id="btn-export">⬇ Export CSV</button>
|
||||
<span class="pallet-count" id="pallet-count"></span>
|
||||
</div>
|
||||
|
||||
<div class="pallet-table-container">
|
||||
<table class="pallet-table" id="pallet-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th data-sort="pallet_number">Pallet # ⇅</th>
|
||||
<th data-sort="date_reserved">Date Reserved ⇅</th>
|
||||
<th data-sort="received_date">Rec. Date ⇅</th>
|
||||
<th data-sort="customer_number">Customer # ⇅</th>
|
||||
<th data-sort="inbound_weight">Lbs ⇅</th>
|
||||
<th data-sort="tester">Who ⇅</th>
|
||||
<th data-sort="description">Items Tested ⇅</th>
|
||||
<th data-sort="qty_to_sales">QTY Sale ⇅</th>
|
||||
<th data-sort="weight_to_sales">Lbs Sale ⇅</th>
|
||||
<th data-sort="finish_date">Finish Date ⇅</th>
|
||||
<th>Status</th>
|
||||
<th>Notes</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody id="pallet-tbody">
|
||||
<tr><td colspan="12" style="text-align:center;padding:40px;color:#666;">Loading...</td></tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<div class="pallet-pagination" id="pallet-pagination"></div>
|
||||
</div>
|
||||
@@ -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('<tr><td colspan="13" style="text-align:center;padding:40px;">No pallets found</td></tr>');
|
||||
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, "<").replace(/>/g, ">");
|
||||
|
||||
var row = '<tr>' +
|
||||
'<td><a href="' + link + '" style="color:#3cc062;text-decoration:none;font-weight:600;">' + pn + '</a></td>' +
|
||||
'<td>' + fmtDate(p.date_reserved) + '</td>' +
|
||||
'<td>' + fmtDate(p.received_date) + '</td>' +
|
||||
'<td>' + (p.customer_number || "") + '</td>' +
|
||||
'<td>' + (p.company_name || "") + '</td>' +
|
||||
'<td>' + (p.inbound_weight || "") + '</td>' +
|
||||
'<td>' + (p.tester || "") + '</td>' +
|
||||
'<td>' + (p.description || "") + '</td>' +
|
||||
'<td>' + (p.qty_to_sales || "") + '</td>' +
|
||||
'<td>' + (p.weight_to_sales || "") + '</td>' +
|
||||
'<td>' + fmtDate(p.finish_date) + '</td>' +
|
||||
'<td class="' + statusClass + '">' + (p.status || "") + '</td>' +
|
||||
'<td>' + (p.notes || "").substring(0, 50) + '</td>' +
|
||||
'</tr>';
|
||||
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 = $('<button>« Prev</button>').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 = $('<button>' + i + '</button>')
|
||||
.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 = $('<button>Next »</button>').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();
|
||||
};
|
||||
@@ -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
|
||||
}
|
||||
@@ -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}
|
||||
@@ -0,0 +1,4 @@
|
||||
<div style="padding:20px;">
|
||||
<h2>Route Planner</h2>
|
||||
<p>Optimize pickup routes for scheduled pickups.</p>
|
||||
</div>
|
||||
@@ -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 = "<div style='padding:20px;'>";
|
||||
h += "<h2>Route Planner</h2>";
|
||||
h += "<p>Schedule and optimize pickup routes.</p>";
|
||||
h += "<div id='route-map' style='width:100%;height:400px;background:#f0f0f0;margin:20px 0;'></div>";
|
||||
h += "<button class='btn btn-primary' id='optimize-btn'>Optimize Routes</button>";
|
||||
h += "<div id='routes-container' style='margin-top:20px;'></div>";
|
||||
h += "</div>";
|
||||
$(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"});
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"doctype": "Page",
|
||||
"name": "route-planner",
|
||||
"page_name": "route-planner",
|
||||
"title": "Route Planner",
|
||||
"module": "Westech R2",
|
||||
"standard": "Yes",
|
||||
"roles": [{"role": "All"}]
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
<div style="padding:20px;">
|
||||
<h2>Route Planner</h2>
|
||||
<p>Optimize pickup routes for scheduled pickups.</p>
|
||||
</div>
|
||||
@@ -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 = "<div style='padding:20px;'>";
|
||||
h += "<h2>Route Planner</h2>";
|
||||
h += "<p>Schedule and optimize pickup routes.</p>";
|
||||
h += "<div id='route-map' style='width:100%;height:400px;background:#f0f0f0;margin:20px 0;'></div>";
|
||||
h += "<button class='btn btn-primary' id='optimize-btn'>Optimize Routes</button>";
|
||||
h += "<div id='routes-container' style='margin-top:20px;'></div>";
|
||||
h += "</div>";
|
||||
$(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"});
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"doctype": "Page",
|
||||
"name": "route-planner",
|
||||
"page_name": "route-planner",
|
||||
"title": "Route Planner",
|
||||
"module": "Westech R2",
|
||||
"standard": "Yes",
|
||||
"roles": [{"role": "All"}]
|
||||
}
|
||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -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
|
||||
});
|
||||
}
|
||||
@@ -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"
|
||||
}
|
||||
@@ -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("<div style='padding:40px;text-align:center;'><h2>No load specified</h2><p>Use ?load=MMDDYYYY-XXXX</p></div>");
|
||||
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("<div style='padding:40px;text-align:center;'><h2>Load not found</h2></div>"); }
|
||||
}
|
||||
});
|
||||
|
||||
function showLoad(page, load) {
|
||||
var h = "<div style='padding:20px;'>";
|
||||
h += "<div style='background:#f8f9fa;padding:15px;border-radius:8px;margin-bottom:20px;'>";
|
||||
h += "<h2>Load: " + load.name + "</h2>";
|
||||
h += "<div>In Date: " + (load.incoming_date || "N/A") + " | Customer: " + (load.customer_name || load.customer_number || "N/A") + "</div>";
|
||||
h += "<div>Devices: " + (load.total_devices || 0) + " | Weight: " + (load.total_weight || 0) + " lbs</div>";
|
||||
h += "</div>";
|
||||
h += "<button class='btn btn-primary' onclick='window.print()' style='margin-bottom:15px;'>Print Data Tracking Worksheet</button> ";
|
||||
h += "<a href='/app/load/" + encodeURIComponent(load.name) + "' class='btn btn-default' style='margin-bottom:15px;'>Open Form View</a> ";
|
||||
h += "<a href='/app/load-update?load=" + encodeURIComponent(load.name) + "' class='btn btn-default' style='margin-bottom:15px;'>Edit Load</a>";
|
||||
h += "<table style='width:100%;border-collapse:collapse;font-size:13px;margin-top:20px;'>";
|
||||
h += "<thead><tr style='background:#37474f;color:white;'>";
|
||||
h += "<th>Material Type</th><th>Count</th><th>Rcv Status</th><th>Send To</th>";
|
||||
h += "<th>Recv'd</th><th>HDD Out</th><th>Dis Send To</th>";
|
||||
h += "<th>Sanitized</th><th>Test Status</th><th>Test Send</th>";
|
||||
h += "<th>R2 Sent</th><th>Phys</th><th>HDD Need</th><th>HDD Phys</th><th>HDD Logic</th>";
|
||||
h += "</tr></thead><tbody>";
|
||||
|
||||
if (load.material_items && load.material_items.length > 0) {
|
||||
load.material_items.forEach(function(item) {
|
||||
h += "<tr style='border-bottom:1px solid #e0e0e0;'>";
|
||||
h += "<td><strong>" + (item.material_type || "") + "</strong></td>";
|
||||
h += "<td style='text-align:right;'>" + (item.total_count || 0) + "</td>";
|
||||
h += "<td>" + (item.initial_data_status || "") + "</td>";
|
||||
h += "<td>" + (item.send_to || "") + "</td>";
|
||||
h += "<td style='text-align:right;'>" + (item.devices_received || 0) + "</td>";
|
||||
h += "<td style='text-align:right;'>" + (item.hdd_removed || 0) + "</td>";
|
||||
h += "<td>" + (item.disassembly_send_to || "") + "</td>";
|
||||
h += "<td style='text-align:right;'>" + (item.units_sanitized_software || 0) + "</td>";
|
||||
h += "<td>" + (item.test_data_status || "") + "</td>";
|
||||
h += "<td>" + (item.test_send_to || "") + "</td>";
|
||||
h += "<td style='text-align:right;'>" + (item.r2_units_sent || 0) + "</td>";
|
||||
h += "<td style='text-align:right;'>" + (item.units_physical_destruction || 0) + "</td>";
|
||||
h += "<td style='text-align:right;'>" + (item.hdd_needs_sanitize || 0) + "</td>";
|
||||
h += "<td style='text-align:right;'>" + (item.hdd_physical_destruction || 0) + "</td>";
|
||||
h += "<td style='text-align:right;'>" + (item.hdd_logical_sanitization || 0) + "</td>";
|
||||
h += "</tr>";
|
||||
});
|
||||
}
|
||||
h += "</tbody></table></div>";
|
||||
$(page.body).html(h);
|
||||
}
|
||||
};
|
||||
File diff suppressed because one or more lines are too long
@@ -0,0 +1,53 @@
|
||||
<div class="ld-container" style="padding: 20px;">
|
||||
<style>
|
||||
.ld-header { background: #f8f9fa; padding: 15px; border-radius: 8px; margin-bottom: 20px; }
|
||||
.ld-table { width: 100%; border-collapse: collapse; margin-top: 20px; font-size: 13px; }
|
||||
.ld-table th { background: #37474f; color: white; padding: 8px; text-align: left; }
|
||||
.ld-table td { padding: 6px 8px; border-bottom: 1px solid #e0e0e0; }
|
||||
.ld-table .num { text-align: right; font-family: monospace; }
|
||||
.ld-btn { background: #455a64; color: white; border: none; padding: 8px 20px; border-radius: 4px; cursor: pointer; margin-bottom: 20px; margin-right: 10px; }
|
||||
.ld-badge { display: inline-block; padding: 2px 8px; border-radius: 12px; font-size: 11px; margin-right: 4px; }
|
||||
.bg-rcv { background: #e3f2fd; color: #1565c0; }
|
||||
.bg-hdr { background: #fff3e0; color: #e65100; }
|
||||
.bg-tst { background: #e8f5e9; color: #2e7d32; }
|
||||
.bg-r2 { background: #f3e5f5; color: #7b1fa2; }
|
||||
.bg-des { background: #ffebee; color: #c62828; }
|
||||
</style>
|
||||
|
||||
<div class="ld-header">
|
||||
<h2>Load: <span id="ld-name"></span></h2>
|
||||
<div>In Date: <span id="ld-date"></span> | Customer: <span id="ld-cust"></span> | Devices: <span id="ld-dev"></span> | Weight: <span id="ld-wt"></span> lbs</div>
|
||||
</div>
|
||||
|
||||
<div style="margin-bottom: 15px;">
|
||||
<span class="ld-badge bg-rcv">Receiving</span>
|
||||
<span class="ld-badge bg-hdr">HDR / Disassembly</span>
|
||||
<span class="ld-badge bg-tst">Test</span>
|
||||
<span class="ld-badge bg-r2">R2 Downstream</span>
|
||||
<span class="ld-badge bg-des">Destruction</span>
|
||||
</div>
|
||||
|
||||
<button class="ld-btn" onclick="window.print()">Print Data Tracking Worksheet</button>
|
||||
<button class="ld-btn" style="background: #1976d2;" onclick="window.location.href='/app/load-list'">Back to Loads</button>
|
||||
|
||||
<table class="ld-table" id="ld-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th rowspan="2">Material Type</th>
|
||||
<th colspan="3" style="text-align:center; background:#1565c0;">Receiving</th>
|
||||
<th colspan="3" style="text-align:center; background:#e65100;">HDR / Disassembly</th>
|
||||
<th colspan="3" style="text-align:center; background:#2e7d32;">Test</th>
|
||||
<th style="text-align:center; background:#7b1fa2;">R2</th>
|
||||
<th colspan="4" style="text-align:center; background:#c62828;">Destruction</th>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>Count</th><th>Status</th><th>Send To</th>
|
||||
<th>Recv'd</th><th>HDD Out</th><th>Send To</th>
|
||||
<th>Sanitized</th><th>Status</th><th>Send To</th>
|
||||
<th>Sent</th>
|
||||
<th>Phys</th><th>HDD Need</th><th>HDD Phys</th><th>HDD Logic</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody id="ld-body"></tbody>
|
||||
</table>
|
||||
</div>
|
||||
@@ -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("<div style='padding:40px;text-align:center;'><h2>No load specified</h2><p>Use ?load=MMDDYYYY-XXXX</p></div>");
|
||||
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("<div style='padding:40px;text-align:center;'><h2>Load not found</h2></div>"); }
|
||||
}
|
||||
});
|
||||
|
||||
function showLoad(page, load) {
|
||||
var h = "<div style='padding:20px;'>";
|
||||
h += "<div style='background:#f8f9fa;padding:15px;border-radius:8px;margin-bottom:20px;'>";
|
||||
h += "<h2>Load: " + load.name + "</h2>";
|
||||
h += "<div>In Date: " + (load.incoming_date || "N/A") + " | Customer: " + (load.customer_name || load.customer_number || "N/A") + "</div>";
|
||||
h += "<div>Devices: " + (load.total_devices || 0) + " | Weight: " + (load.total_weight || 0) + " lbs</div>";
|
||||
h += "</div>";
|
||||
h += "<button class='btn btn-primary' onclick='window.print()' style='margin-bottom:15px;'>Print Data Tracking Worksheet</button> ";
|
||||
h += "<a href='/app/load/" + encodeURIComponent(load.name) + "' class='btn btn-default' style='margin-bottom:15px;'>Open Form View</a> ";
|
||||
h += "<a href='/app/load-update?load=" + encodeURIComponent(load.name) + "' class='btn btn-default' style='margin-bottom:15px;'>Edit Load</a>";
|
||||
h += "<table style='width:100%;border-collapse:collapse;font-size:13px;margin-top:20px;'>";
|
||||
h += "<thead><tr style='background:#37474f;color:white;'>";
|
||||
h += "<th>Material Type</th><th>Count</th><th>Rcv Status</th><th>Send To</th>";
|
||||
h += "<th>Recv'd</th><th>HDD Out</th><th>Dis Send To</th>";
|
||||
h += "<th>Sanitized</th><th>Test Status</th><th>Test Send</th>";
|
||||
h += "<th>R2 Sent</th><th>Phys</th><th>HDD Need</th><th>HDD Phys</th><th>HDD Logic</th>";
|
||||
h += "</tr></thead><tbody>";
|
||||
|
||||
if (load.material_items && load.material_items.length > 0) {
|
||||
load.material_items.forEach(function(item) {
|
||||
h += "<tr style='border-bottom:1px solid #e0e0e0;'>";
|
||||
h += "<td><strong>" + (item.material_type || "") + "</strong></td>";
|
||||
h += "<td style='text-align:right;'>" + (item.total_count || 0) + "</td>";
|
||||
h += "<td>" + (item.initial_data_status || "") + "</td>";
|
||||
h += "<td>" + (item.send_to || "") + "</td>";
|
||||
h += "<td style='text-align:right;'>" + (item.devices_received || 0) + "</td>";
|
||||
h += "<td style='text-align:right;'>" + (item.hdd_removed || 0) + "</td>";
|
||||
h += "<td>" + (item.disassembly_send_to || "") + "</td>";
|
||||
h += "<td style='text-align:right;'>" + (item.units_sanitized_software || 0) + "</td>";
|
||||
h += "<td>" + (item.test_data_status || "") + "</td>";
|
||||
h += "<td>" + (item.test_send_to || "") + "</td>";
|
||||
h += "<td style='text-align:right;'>" + (item.r2_units_sent || 0) + "</td>";
|
||||
h += "<td style='text-align:right;'>" + (item.units_physical_destruction || 0) + "</td>";
|
||||
h += "<td style='text-align:right;'>" + (item.hdd_needs_sanitize || 0) + "</td>";
|
||||
h += "<td style='text-align:right;'>" + (item.hdd_physical_destruction || 0) + "</td>";
|
||||
h += "<td style='text-align:right;'>" + (item.hdd_logical_sanitization || 0) + "</td>";
|
||||
h += "</tr>";
|
||||
});
|
||||
}
|
||||
h += "</tbody></table></div>";
|
||||
$(page.body).html(h);
|
||||
}
|
||||
};
|
||||
@@ -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"
|
||||
}
|
||||
@@ -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("<div style='padding:40px;text-align:center;'><h2>No load specified</h2><p>Use ?load=MMDDYYYY-XXXX</p></div>");
|
||||
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("<div style='padding:40px;text-align:center;'><h2>Load not found</h2></div>"); }
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function renderForm(page, load) {
|
||||
var h = "<div style='padding:20px;'>";
|
||||
h += "<h2>Update Load: " + load.name + "</h2>";
|
||||
h += "<p>Date: " + (load.incoming_date || "N/A") + " | Customer: " + (load.customer_name || load.customer_number || "N/A") + "</p>";
|
||||
h += "<div style='margin-bottom:15px;'>";
|
||||
h += "<button id='lu-save' class='btn btn-primary'>Save Changes</button> ";
|
||||
h += "<button id='lu-print' class='btn btn-default'>Print Worksheet</button> ";
|
||||
h += "<a href='/app/load/" + encodeURIComponent(load.name) + "' class='btn btn-default'>Open Form View</a>";
|
||||
h += "</div>";
|
||||
h += "<table style='width:100%;border-collapse:collapse;font-size:13px;'>";
|
||||
h += "<thead><tr style='background:#37474f;color:white;'>";
|
||||
h += "<th>Material Type</th><th>Count</th><th>Recv Status</th><th>Send To</th>";
|
||||
h += "<th>Recv'd</th><th>HDD Out</th><th>Dis Send To</th>";
|
||||
h += "<th>Sanitized</th><th>Test Status</th><th>Test Send</th>";
|
||||
h += "<th>R2 Sent</th><th>Phys</th><th>HDD Need</th><th>HDD Phys</th><th>HDD Logic</th>";
|
||||
h += "</tr></thead><tbody>";
|
||||
|
||||
if (load.material_items && load.material_items.length > 0) {
|
||||
load.material_items.forEach(function(item, idx) {
|
||||
var prefix = "item_" + idx + "_";
|
||||
h += "<tr style='border-bottom:1px solid #e0e0e0;'>";
|
||||
h += "<td><strong>" + (item.material_type || "") + "</strong></td>";
|
||||
h += "<td><input type='number' id='" + prefix + "total_count' value='" + (item.total_count || 0) + "' style='width:50px;'></td>";
|
||||
h += "<td><input type='text' id='" + prefix + "initial_data_status' value='" + (item.initial_data_status || "") + "' style='width:60px;'></td>";
|
||||
h += "<td><input type='text' id='" + prefix + "send_to' value='" + (item.send_to || "") + "' style='width:80px;'></td>";
|
||||
h += "<td><input type='number' id='" + prefix + "devices_received' value='" + (item.devices_received || 0) + "' style='width:50px;'></td>";
|
||||
h += "<td><input type='number' id='" + prefix + "hdd_removed' value='" + (item.hdd_removed || 0) + "' style='width:50px;'></td>";
|
||||
h += "<td><input type='text' id='" + prefix + "disassembly_send_to' value='" + (item.disassembly_send_to || "") + "' style='width:80px;'></td>";
|
||||
h += "<td><input type='number' id='" + prefix + "units_sanitized_software' value='" + (item.units_sanitized_software || 0) + "' style='width:50px;'></td>";
|
||||
h += "<td><input type='text' id='" + prefix + "test_data_status' value='" + (item.test_data_status || "") + "' style='width:60px;'></td>";
|
||||
h += "<td><input type='text' id='" + prefix + "test_send_to' value='" + (item.test_send_to || "") + "' style='width:80px;'></td>";
|
||||
h += "<td><input type='number' id='" + prefix + "r2_units_sent' value='" + (item.r2_units_sent || 0) + "' style='width:50px;'></td>";
|
||||
h += "<td><input type='number' id='" + prefix + "units_physical_destruction' value='" + (item.units_physical_destruction || 0) + "' style='width:50px;'></td>";
|
||||
h += "<td><input type='number' id='" + prefix + "hdd_needs_sanitize' value='" + (item.hdd_needs_sanitize || 0) + "' style='width:50px;'></td>";
|
||||
h += "<td><input type='number' id='" + prefix + "hdd_physical_destruction' value='" + (item.hdd_physical_destruction || 0) + "' style='width:50px;'></td>";
|
||||
h += "<td><input type='number' id='" + prefix + "hdd_logical_sanitization' value='" + (item.hdd_logical_sanitization || 0) + "' style='width:50px;'></td>";
|
||||
h += "</tr>";
|
||||
});
|
||||
}
|
||||
h += "</tbody></table></div>";
|
||||
$(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();
|
||||
};
|
||||
@@ -0,0 +1,4 @@
|
||||
<div style="padding:20px;">
|
||||
<h2>Load Update</h2>
|
||||
<p>Use the form below to update load material items.</p>
|
||||
</div>
|
||||
@@ -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("<div style='padding:40px;text-align:center;'><h2>No load specified</h2><p>Use ?load=MMDDYYYY-XXXX</p></div>");
|
||||
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("<div style='padding:40px;text-align:center;'><h2>Load not found</h2></div>"); }
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function renderForm(page, load) {
|
||||
var h = "<div style='padding:20px;'>";
|
||||
h += "<h2>Update Load: " + load.name + "</h2>";
|
||||
h += "<p>Date: " + (load.incoming_date || "N/A") + " | Customer: " + (load.customer_name || load.customer_number || "N/A") + "</p>";
|
||||
h += "<div style='margin-bottom:15px;'>";
|
||||
h += "<button id='lu-save' class='btn btn-primary'>Save Changes</button> ";
|
||||
h += "<button id='lu-print' class='btn btn-default'>Print Worksheet</button> ";
|
||||
h += "<a href='/app/load/" + encodeURIComponent(load.name) + "' class='btn btn-default'>Open Form View</a>";
|
||||
h += "</div>";
|
||||
h += "<table style='width:100%;border-collapse:collapse;font-size:13px;'>";
|
||||
h += "<thead><tr style='background:#37474f;color:white;'>";
|
||||
h += "<th>Material Type</th><th>Count</th><th>Recv Status</th><th>Send To</th>";
|
||||
h += "<th>Recv'd</th><th>HDD Out</th><th>Dis Send To</th>";
|
||||
h += "<th>Sanitized</th><th>Test Status</th><th>Test Send</th>";
|
||||
h += "<th>R2 Sent</th><th>Phys</th><th>HDD Need</th><th>HDD Phys</th><th>HDD Logic</th>";
|
||||
h += "</tr></thead><tbody>";
|
||||
|
||||
if (load.material_items && load.material_items.length > 0) {
|
||||
load.material_items.forEach(function(item, idx) {
|
||||
var prefix = "item_" + idx + "_";
|
||||
h += "<tr style='border-bottom:1px solid #e0e0e0;'>";
|
||||
h += "<td><strong>" + (item.material_type || "") + "</strong></td>";
|
||||
h += "<td><input type='number' id='" + prefix + "total_count' value='" + (item.total_count || 0) + "' style='width:50px;'></td>";
|
||||
h += "<td><input type='text' id='" + prefix + "initial_data_status' value='" + (item.initial_data_status || "") + "' style='width:60px;'></td>";
|
||||
h += "<td><input type='text' id='" + prefix + "send_to' value='" + (item.send_to || "") + "' style='width:80px;'></td>";
|
||||
h += "<td><input type='number' id='" + prefix + "devices_received' value='" + (item.devices_received || 0) + "' style='width:50px;'></td>";
|
||||
h += "<td><input type='number' id='" + prefix + "hdd_removed' value='" + (item.hdd_removed || 0) + "' style='width:50px;'></td>";
|
||||
h += "<td><input type='text' id='" + prefix + "disassembly_send_to' value='" + (item.disassembly_send_to || "") + "' style='width:80px;'></td>";
|
||||
h += "<td><input type='number' id='" + prefix + "units_sanitized_software' value='" + (item.units_sanitized_software || 0) + "' style='width:50px;'></td>";
|
||||
h += "<td><input type='text' id='" + prefix + "test_data_status' value='" + (item.test_data_status || "") + "' style='width:60px;'></td>";
|
||||
h += "<td><input type='text' id='" + prefix + "test_send_to' value='" + (item.test_send_to || "") + "' style='width:80px;'></td>";
|
||||
h += "<td><input type='number' id='" + prefix + "r2_units_sent' value='" + (item.r2_units_sent || 0) + "' style='width:50px;'></td>";
|
||||
h += "<td><input type='number' id='" + prefix + "units_physical_destruction' value='" + (item.units_physical_destruction || 0) + "' style='width:50px;'></td>";
|
||||
h += "<td><input type='number' id='" + prefix + "hdd_needs_sanitize' value='" + (item.hdd_needs_sanitize || 0) + "' style='width:50px;'></td>";
|
||||
h += "<td><input type='number' id='" + prefix + "hdd_physical_destruction' value='" + (item.hdd_physical_destruction || 0) + "' style='width:50px;'></td>";
|
||||
h += "<td><input type='number' id='" + prefix + "hdd_logical_sanitization' value='" + (item.hdd_logical_sanitization || 0) + "' style='width:50px;'></td>";
|
||||
h += "</tr>";
|
||||
});
|
||||
}
|
||||
h += "</tbody></table></div>";
|
||||
$(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();
|
||||
};
|
||||
@@ -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"
|
||||
}
|
||||
@@ -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"}
|
||||
@@ -0,0 +1 @@
|
||||
# Pallet List page for Westech R2
|
||||
@@ -0,0 +1,82 @@
|
||||
<style>
|
||||
.pallet-list-page { font-family: Helvetica Neue, Arial, sans-serif; }
|
||||
.pallet-list-page h2 { color: #2F5496; margin-bottom: 16px; font-size: 20px; }
|
||||
.pallet-search-box { margin-bottom: 16px; display: flex; gap: 10px; align-items: center; flex-wrap: wrap; }
|
||||
.pallet-search-box input, .pallet-search-box select {
|
||||
padding: 6px 10px; border: 1px solid #ddd; border-radius: 4px; font-size: 13px;
|
||||
}
|
||||
.pallet-search-box button {
|
||||
padding: 6px 14px; border: 1px solid #ddd; border-radius: 4px; cursor: pointer;
|
||||
font-size: 13px; background: white;
|
||||
}
|
||||
.pallet-search-box button.btn-primary { background: #2F5496; color: white; border-color: #2F5496; }
|
||||
.pallet-search-box button.btn-success { background: #548235; color: white; border-color: #548235; }
|
||||
.pallet-table-container { padding: 0; overflow-x: auto; background: white; border-radius: 4px; }
|
||||
.pallet-table { width: 100%; border-collapse: collapse; font-size: 13px; }
|
||||
.pallet-table th {
|
||||
background: #2F5496; color: white; padding: 8px 10px; text-align: left;
|
||||
cursor: pointer; white-space: nowrap; font-weight: 500;
|
||||
}
|
||||
.pallet-table td { padding: 5px 10px; border-bottom: 1px solid #eee; }
|
||||
.pallet-table tr:hover { background: #f5f5f5; }
|
||||
.pallet-table tr.new-row { background: #FFF9E6 !important; }
|
||||
.pallet-table a { color: #2F5496; text-decoration: none; font-weight: 600; }
|
||||
.status-received { color: #2196F3; font-weight: 600; }
|
||||
.status-sorting { color: #FF9800; font-weight: 600; }
|
||||
.status-processing { color: #9C27B0; font-weight: 600; }
|
||||
.status-complete { color: #4CAF50; font-weight: 600; }
|
||||
.status-shipped { color: #607D8B; font-weight: 600; }
|
||||
.pallet-pagination { margin-top: 12px; display: flex; gap: 5px; align-items: center; }
|
||||
.pallet-pagination button {
|
||||
padding: 5px 12px; border: 1px solid #ddd; background: white; cursor: pointer;
|
||||
border-radius: 4px; font-size: 13px; color: #2F5496;
|
||||
}
|
||||
.pallet-pagination button.active { background: #2F5496; color: white; border-color: #2F5496; }
|
||||
.pallet-pagination button:disabled { opacity: 0.5; cursor: not-allowed; }
|
||||
.pallet-count { margin-left: auto; color: #666; font-size: 13px; }
|
||||
</style>
|
||||
|
||||
<div class="pallet-list-page">
|
||||
<h2>📦 Pallet List</h2>
|
||||
|
||||
<div class="pallet-search-box">
|
||||
<input type="text" id="pallet-search" placeholder="Search pallet #..." style="max-width:200px;">
|
||||
<select id="status-filter">
|
||||
<option value="">All Statuses</option>
|
||||
<option value="Received">Received</option>
|
||||
<option value="Sorting">Sorting</option>
|
||||
<option value="Processing">Processing</option>
|
||||
<option value="Complete">Complete</option>
|
||||
<option value="Shipped">Shipped</option>
|
||||
</select>
|
||||
<button class="btn btn-default" id="btn-clear">✕ Clear</button>
|
||||
<button class="btn btn-success" id="btn-export">⬇ Export CSV</button>
|
||||
<span class="pallet-count" id="pallet-count"></span>
|
||||
</div>
|
||||
|
||||
<div class="pallet-table-container">
|
||||
<table class="pallet-table" id="pallet-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th data-sort="pallet_number">Pallet # ⇅</th>
|
||||
<th data-sort="date_reserved">Date Reserved ⇅</th>
|
||||
<th data-sort="received_date">Rec. Date ⇅</th>
|
||||
<th data-sort="customer_number">Customer # ⇅</th>
|
||||
<th data-sort="inbound_weight">Lbs ⇅</th>
|
||||
<th data-sort="tester">Who ⇅</th>
|
||||
<th data-sort="description">Items Tested ⇅</th>
|
||||
<th data-sort="qty_to_sales">QTY Sale ⇅</th>
|
||||
<th data-sort="weight_to_sales">Lbs Sale ⇅</th>
|
||||
<th data-sort="finish_date">Finish Date ⇅</th>
|
||||
<th>Status</th>
|
||||
<th>Notes</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody id="pallet-tbody">
|
||||
<tr><td colspan="12" style="text-align:center;padding:40px;color:#666;">Loading...</td></tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<div class="pallet-pagination" id="pallet-pagination"></div>
|
||||
</div>
|
||||
@@ -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('<tr><td colspan="13" style="text-align:center;padding:40px;">No pallets found</td></tr>');
|
||||
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, "<").replace(/>/g, ">");
|
||||
|
||||
var row = '<tr>' +
|
||||
'<td><a href="' + link + '" style="color:#3cc062;text-decoration:none;font-weight:600;">' + pn + '</a></td>' +
|
||||
'<td>' + fmtDate(p.date_reserved) + '</td>' +
|
||||
'<td>' + fmtDate(p.received_date) + '</td>' +
|
||||
'<td>' + (p.customer_number || "") + '</td>' +
|
||||
'<td>' + (p.company_name || "") + '</td>' +
|
||||
'<td>' + (p.inbound_weight || "") + '</td>' +
|
||||
'<td>' + (p.tester || "") + '</td>' +
|
||||
'<td>' + (p.description || "") + '</td>' +
|
||||
'<td>' + (p.qty_to_sales || "") + '</td>' +
|
||||
'<td>' + (p.weight_to_sales || "") + '</td>' +
|
||||
'<td>' + fmtDate(p.finish_date) + '</td>' +
|
||||
'<td class="' + statusClass + '">' + (p.status || "") + '</td>' +
|
||||
'<td>' + (p.notes || "").substring(0, 50) + '</td>' +
|
||||
'</tr>';
|
||||
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 = $('<button>« Prev</button>').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 = $('<button>' + i + '</button>')
|
||||
.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 = $('<button>Next »</button>').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();
|
||||
};
|
||||
@@ -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
|
||||
}
|
||||
@@ -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}
|
||||
@@ -0,0 +1,82 @@
|
||||
<style>
|
||||
.pallet-list-page { font-family: Helvetica Neue, Arial, sans-serif; }
|
||||
.pallet-list-page h2 { color: #2F5496; margin-bottom: 16px; font-size: 20px; }
|
||||
.pallet-search-box { margin-bottom: 16px; display: flex; gap: 10px; align-items: center; flex-wrap: wrap; }
|
||||
.pallet-search-box input, .pallet-search-box select {
|
||||
padding: 6px 10px; border: 1px solid #ddd; border-radius: 4px; font-size: 13px;
|
||||
}
|
||||
.pallet-search-box button {
|
||||
padding: 6px 14px; border: 1px solid #ddd; border-radius: 4px; cursor: pointer;
|
||||
font-size: 13px; background: white;
|
||||
}
|
||||
.pallet-search-box button.btn-primary { background: #2F5496; color: white; border-color: #2F5496; }
|
||||
.pallet-search-box button.btn-success { background: #548235; color: white; border-color: #548235; }
|
||||
.pallet-table-container { padding: 0; overflow-x: auto; background: white; border-radius: 4px; }
|
||||
.pallet-table { width: 100%; border-collapse: collapse; font-size: 13px; }
|
||||
.pallet-table th {
|
||||
background: #2F5496; color: white; padding: 8px 10px; text-align: left;
|
||||
cursor: pointer; white-space: nowrap; font-weight: 500;
|
||||
}
|
||||
.pallet-table td { padding: 5px 10px; border-bottom: 1px solid #eee; }
|
||||
.pallet-table tr:hover { background: #f5f5f5; }
|
||||
.pallet-table tr.new-row { background: #FFF9E6 !important; }
|
||||
.pallet-table a { color: #2F5496; text-decoration: none; font-weight: 600; }
|
||||
.status-received { color: #2196F3; font-weight: 600; }
|
||||
.status-sorting { color: #FF9800; font-weight: 600; }
|
||||
.status-processing { color: #9C27B0; font-weight: 600; }
|
||||
.status-complete { color: #4CAF50; font-weight: 600; }
|
||||
.status-shipped { color: #607D8B; font-weight: 600; }
|
||||
.pallet-pagination { margin-top: 12px; display: flex; gap: 5px; align-items: center; }
|
||||
.pallet-pagination button {
|
||||
padding: 5px 12px; border: 1px solid #ddd; background: white; cursor: pointer;
|
||||
border-radius: 4px; font-size: 13px; color: #2F5496;
|
||||
}
|
||||
.pallet-pagination button.active { background: #2F5496; color: white; border-color: #2F5496; }
|
||||
.pallet-pagination button:disabled { opacity: 0.5; cursor: not-allowed; }
|
||||
.pallet-count { margin-left: auto; color: #666; font-size: 13px; }
|
||||
</style>
|
||||
|
||||
<div class="pallet-list-page">
|
||||
<h2>📦 Pallet List</h2>
|
||||
|
||||
<div class="pallet-search-box">
|
||||
<input type="text" id="pallet-search" placeholder="Search pallet #..." style="max-width:200px;">
|
||||
<select id="status-filter">
|
||||
<option value="">All Statuses</option>
|
||||
<option value="Received">Received</option>
|
||||
<option value="Sorting">Sorting</option>
|
||||
<option value="Processing">Processing</option>
|
||||
<option value="Complete">Complete</option>
|
||||
<option value="Shipped">Shipped</option>
|
||||
</select>
|
||||
<button class="btn btn-default" id="btn-clear">✕ Clear</button>
|
||||
<button class="btn btn-success" id="btn-export">⬇ Export CSV</button>
|
||||
<span class="pallet-count" id="pallet-count"></span>
|
||||
</div>
|
||||
|
||||
<div class="pallet-table-container">
|
||||
<table class="pallet-table" id="pallet-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th data-sort="pallet_number">Pallet # ⇅</th>
|
||||
<th data-sort="date_reserved">Date Reserved ⇅</th>
|
||||
<th data-sort="received_date">Rec. Date ⇅</th>
|
||||
<th data-sort="customer_number">Customer # ⇅</th>
|
||||
<th data-sort="inbound_weight">Lbs ⇅</th>
|
||||
<th data-sort="tester">Who ⇅</th>
|
||||
<th data-sort="description">Items Tested ⇅</th>
|
||||
<th data-sort="qty_to_sales">QTY Sale ⇅</th>
|
||||
<th data-sort="weight_to_sales">Lbs Sale ⇅</th>
|
||||
<th data-sort="finish_date">Finish Date ⇅</th>
|
||||
<th>Status</th>
|
||||
<th>Notes</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody id="pallet-tbody">
|
||||
<tr><td colspan="12" style="text-align:center;padding:40px;color:#666;">Loading...</td></tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<div class="pallet-pagination" id="pallet-pagination"></div>
|
||||
</div>
|
||||
@@ -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('<tr><td colspan="13" style="text-align:center;padding:40px;">No pallets found</td></tr>');
|
||||
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, "<").replace(/>/g, ">");
|
||||
|
||||
var row = '<tr>' +
|
||||
'<td><a href="' + link + '" style="color:#3cc062;text-decoration:none;font-weight:600;">' + pn + '</a></td>' +
|
||||
'<td>' + fmtDate(p.date_reserved) + '</td>' +
|
||||
'<td>' + fmtDate(p.received_date) + '</td>' +
|
||||
'<td>' + (p.customer_number || "") + '</td>' +
|
||||
'<td>' + (p.company_name || "") + '</td>' +
|
||||
'<td>' + (p.inbound_weight || "") + '</td>' +
|
||||
'<td>' + (p.tester || "") + '</td>' +
|
||||
'<td>' + (p.description || "") + '</td>' +
|
||||
'<td>' + (p.qty_to_sales || "") + '</td>' +
|
||||
'<td>' + (p.weight_to_sales || "") + '</td>' +
|
||||
'<td>' + fmtDate(p.finish_date) + '</td>' +
|
||||
'<td class="' + statusClass + '">' + (p.status || "") + '</td>' +
|
||||
'<td>' + (p.notes || "").substring(0, 50) + '</td>' +
|
||||
'</tr>';
|
||||
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 = $('<button>« Prev</button>').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 = $('<button>' + i + '</button>')
|
||||
.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 = $('<button>Next »</button>').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();
|
||||
};
|
||||
@@ -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
|
||||
}
|
||||
@@ -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}
|
||||
@@ -0,0 +1,4 @@
|
||||
<div style="padding:20px;">
|
||||
<h2>Route Planner</h2>
|
||||
<p>Optimize pickup routes for scheduled pickups.</p>
|
||||
</div>
|
||||
@@ -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 = "<div style='padding:20px;'>";
|
||||
h += "<h2>Route Planner</h2>";
|
||||
h += "<p>Schedule and optimize pickup routes.</p>";
|
||||
h += "<div id='route-map' style='width:100%;height:400px;background:#f0f0f0;margin:20px 0;'></div>";
|
||||
h += "<button class='btn btn-primary' id='optimize-btn'>Optimize Routes</button>";
|
||||
h += "<div id='routes-container' style='margin-top:20px;'></div>";
|
||||
h += "</div>";
|
||||
$(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"});
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,4 @@
|
||||
<div style="padding:20px;">
|
||||
<h2>Route Planner</h2>
|
||||
<p>Optimize pickup routes for scheduled pickups.</p>
|
||||
</div>
|
||||
@@ -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 = "<div style='padding:20px;'>";
|
||||
h += "<h2>Route Planner</h2>";
|
||||
h += "<p>Schedule and optimize pickup routes.</p>";
|
||||
h += "<div id='route-map' style='width:100%;height:400px;background:#f0f0f0;margin:20px 0;'></div>";
|
||||
h += "<button class='btn btn-primary' id='optimize-btn'>Optimize Routes</button>";
|
||||
h += "<div id='routes-container' style='margin-top:20px;'></div>";
|
||||
h += "</div>";
|
||||
$(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"});
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
};
|
||||
Reference in New Issue
Block a user