diff --git a/westech_r2/page/load-detail/load_detail.html b/westech_r2/page/load-detail/load_detail.html new file mode 100644 index 0000000..ec9f8f8 --- /dev/null +++ b/westech_r2/page/load-detail/load_detail.html @@ -0,0 +1,53 @@ +
+ + +
+

Load:

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

No load specified

Use ?load=MMDDYYYY-XXXX

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

Load not found

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

Load: " + load.name + "

"; + h += "
In Date: " + (load.incoming_date || "N/A") + " | Customer: " + (load.customer_name || load.customer_number || "N/A") + "
"; + h += "
Devices: " + (load.total_devices || 0) + " | Weight: " + (load.total_weight || 0) + " lbs
"; + h += "
"; + h += " "; + h += "Open Form View "; + h += "Edit Load"; + h += ""; + h += ""; + h += ""; + h += ""; + h += ""; + h += ""; + h += ""; + + if (load.material_items && load.material_items.length > 0) { + load.material_items.forEach(function(item) { + h += ""; + h += ""; + h += ""; + h += ""; + h += ""; + h += ""; + h += ""; + h += ""; + h += ""; + h += ""; + h += ""; + h += ""; + h += ""; + h += ""; + h += ""; + h += ""; + h += ""; + }); + } + h += "
Material TypeCountRcv StatusSend ToRecv'dHDD OutDis Send ToSanitizedTest StatusTest SendR2 SentPhysHDD NeedHDD PhysHDD Logic
" + (item.material_type || "") + "" + (item.total_count || 0) + "" + (item.initial_data_status || "") + "" + (item.send_to || "") + "" + (item.devices_received || 0) + "" + (item.hdd_removed || 0) + "" + (item.disassembly_send_to || "") + "" + (item.units_sanitized_software || 0) + "" + (item.test_data_status || "") + "" + (item.test_send_to || "") + "" + (item.r2_units_sent || 0) + "" + (item.units_physical_destruction || 0) + "" + (item.hdd_needs_sanitize || 0) + "" + (item.hdd_physical_destruction || 0) + "" + (item.hdd_logical_sanitization || 0) + "
"; + $(page.body).html(h); + } +}; diff --git a/westech_r2/page/load-update/load_update.js b/westech_r2/page/load-update/load_update.js index ed8b54b..5c03738 100644 --- a/westech_r2/page/load-update/load_update.js +++ b/westech_r2/page/load-update/load_update.js @@ -4,6 +4,7 @@ frappe.pages["load-update"].on_page_load = function(wrapper) { title: "Load Update", single_column: true }); + var loadName = frappe.utils.get_url_arg("load"); if (!loadName) { $(page.body).html("

No load specified

Use ?load=MMDDYYYY-XXXX

"); @@ -26,14 +27,17 @@ frappe.pages["load-update"].on_page_load = function(wrapper) { h += "

Update Load: " + load.name + "

"; h += "

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

"; h += "
"; - h += ""; - h += " "; - h += " Open Form View"; + h += " "; + h += " "; + h += "Open Form View"; h += "
"; - - h += ""; - h += ""; - h += ""; + h += "
Material TypeCountRecv StatusSend ToRecv'dHDD OutDis Send ToSanitizedTest StatusTest SendR2 SentPhysHDD NeedHDD PhysHDD Logic
"; + h += ""; + h += ""; + h += ""; + h += ""; + h += ""; + h += ""; if (load.material_items && load.material_items.length > 0) { load.material_items.forEach(function(item, idx) { diff --git a/westech_r2/page/pallet-list/pallet_list.html b/westech_r2/page/pallet-list/pallet_list.html new file mode 100644 index 0000000..c3ac405 --- /dev/null +++ b/westech_r2/page/pallet-list/pallet_list.html @@ -0,0 +1,82 @@ + + +
+

📦 Pallet List

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

Route Planner

+

Optimize pickup routes for scheduled pickups.

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

Route Planner

"; + h += "

Schedule and optimize pickup routes.

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