diff --git a/westech_r2/api/__pycache__/__init__.cpython-312.pyc b/westech_r2/api/__pycache__/__init__.cpython-312.pyc deleted file mode 100644 index 5dd511c..0000000 Binary files a/westech_r2/api/__pycache__/__init__.cpython-312.pyc and /dev/null differ diff --git a/westech_r2/api/__pycache__/sales.cpython-312.pyc b/westech_r2/api/__pycache__/sales.cpython-312.pyc deleted file mode 100644 index 57d58b9..0000000 Binary files a/westech_r2/api/__pycache__/sales.cpython-312.pyc and /dev/null differ diff --git a/westech_r2/api/__pycache__/service_invoice.cpython-312.pyc b/westech_r2/api/__pycache__/service_invoice.cpython-312.pyc deleted file mode 100644 index 84e9369..0000000 Binary files a/westech_r2/api/__pycache__/service_invoice.cpython-312.pyc and /dev/null differ diff --git a/westech_r2/doctype/__pycache__/__init__.cpython-312.pyc b/westech_r2/doctype/__pycache__/__init__.cpython-312.pyc deleted file mode 100644 index 25410b8..0000000 Binary files a/westech_r2/doctype/__pycache__/__init__.cpython-312.pyc and /dev/null differ diff --git a/westech_r2/doctype/load/__pycache__/__init__.cpython-312.pyc b/westech_r2/doctype/load/__pycache__/__init__.cpython-312.pyc deleted file mode 100644 index ad4acfc..0000000 Binary files a/westech_r2/doctype/load/__pycache__/__init__.cpython-312.pyc and /dev/null differ diff --git a/westech_r2/doctype/load/__pycache__/load.cpython-312.pyc b/westech_r2/doctype/load/__pycache__/load.cpython-312.pyc deleted file mode 100644 index cb6d7e4..0000000 Binary files a/westech_r2/doctype/load/__pycache__/load.cpython-312.pyc and /dev/null differ diff --git a/westech_r2/doctype/pallet/__pycache__/__init__.cpython-312.pyc b/westech_r2/doctype/pallet/__pycache__/__init__.cpython-312.pyc deleted file mode 100644 index a30fd4f..0000000 Binary files a/westech_r2/doctype/pallet/__pycache__/__init__.cpython-312.pyc and /dev/null differ diff --git a/westech_r2/doctype/pallet/__pycache__/pallet.cpython-312.pyc b/westech_r2/doctype/pallet/__pycache__/pallet.cpython-312.pyc deleted file mode 100644 index 99af6c7..0000000 Binary files a/westech_r2/doctype/pallet/__pycache__/pallet.cpython-312.pyc and /dev/null differ diff --git a/westech_r2/page/customer_records/customer_records.json b/westech_r2/page/customer_records/customer_records.json new file mode 100644 index 0000000..d89235b --- /dev/null +++ b/westech_r2/page/customer_records/customer_records.json @@ -0,0 +1,23 @@ +{ + "content": null, + "creation": "2026-05-20 15:03:29.017530", + "docstatus": 0, + "doctype": "Page", + "idx": 0, + "modified": "2026-05-20 15:03:29.017530", + "modified_by": "Administrator", + "module": "Westech R2", + "name": "customer-records", + "owner": "Administrator", + "page_name": "customer-records", + "roles": [ + { + "role": "All" + } + ], + "script": null, + "standard": "Yes", + "style": null, + "system_page": 0, + "title": "Customer Records" +} \ No newline at end of file diff --git a/westech_r2/page/load_detail/load-detail.py b/westech_r2/page/load_detail/load-detail.py new file mode 100644 index 0000000..a3d5f66 --- /dev/null +++ b/westech_r2/page/load_detail/load-detail.py @@ -0,0 +1,3 @@ +import frappe + +no_cache = 1 diff --git a/westech_r2/page/load_detail/load_detail.py b/westech_r2/page/load_detail/load_detail.py new file mode 100644 index 0000000..a3d5f66 --- /dev/null +++ b/westech_r2/page/load_detail/load_detail.py @@ -0,0 +1,3 @@ +import frappe + +no_cache = 1 diff --git a/westech_r2/page/pallet_list/__pycache__/__init__.cpython-312.pyc b/westech_r2/page/pallet_list/__pycache__/__init__.cpython-312.pyc index 338fb5b..71fd083 100644 Binary files a/westech_r2/page/pallet_list/__pycache__/__init__.cpython-312.pyc and b/westech_r2/page/pallet_list/__pycache__/__init__.cpython-312.pyc differ diff --git a/westech_r2/page/pallet_list/__pycache__/pallet-list.cpython-312.pyc b/westech_r2/page/pallet_list/__pycache__/pallet_list.cpython-312.pyc similarity index 97% rename from westech_r2/page/pallet_list/__pycache__/pallet-list.cpython-312.pyc rename to westech_r2/page/pallet_list/__pycache__/pallet_list.cpython-312.pyc index 5b60feb..5ff33f8 100644 Binary files a/westech_r2/page/pallet_list/__pycache__/pallet-list.cpython-312.pyc and b/westech_r2/page/pallet_list/__pycache__/pallet_list.cpython-312.pyc differ diff --git a/westech_r2/page/route_planner/__init__.py b/westech_r2/page/route_planner/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/westech_r2/page/route_planner/__pycache__/__init__.cpython-312.pyc b/westech_r2/page/route_planner/__pycache__/__init__.cpython-312.pyc new file mode 100644 index 0000000..d86b09d Binary files /dev/null and b/westech_r2/page/route_planner/__pycache__/__init__.cpython-312.pyc differ diff --git a/westech_r2/page/route_planner/route-planner.py b/westech_r2/page/route_planner/route-planner.py new file mode 100644 index 0000000..a3d5f66 --- /dev/null +++ b/westech_r2/page/route_planner/route-planner.py @@ -0,0 +1,3 @@ +import frappe + +no_cache = 1 diff --git a/westech_r2/page/route_planner/route_planner.py b/westech_r2/page/route_planner/route_planner.py new file mode 100644 index 0000000..a3d5f66 --- /dev/null +++ b/westech_r2/page/route_planner/route_planner.py @@ -0,0 +1,3 @@ +import frappe + +no_cache = 1 diff --git a/westech_r2/westech_r2/__pycache__/__init__.cpython-312.pyc b/westech_r2/westech_r2/__pycache__/__init__.cpython-312.pyc deleted file mode 100644 index d1a464b..0000000 Binary files a/westech_r2/westech_r2/__pycache__/__init__.cpython-312.pyc and /dev/null differ diff --git a/westech_r2/westech_r2/doctype/__pycache__/__init__.cpython-312.pyc b/westech_r2/westech_r2/doctype/__pycache__/__init__.cpython-312.pyc deleted file mode 100644 index d2f66bb..0000000 Binary files a/westech_r2/westech_r2/doctype/__pycache__/__init__.cpython-312.pyc and /dev/null differ diff --git a/westech_r2/westech_r2/doctype/customer_record/__pycache__/__init__.cpython-312.pyc b/westech_r2/westech_r2/doctype/customer_record/__pycache__/__init__.cpython-312.pyc deleted file mode 100644 index 00f58a2..0000000 Binary files a/westech_r2/westech_r2/doctype/customer_record/__pycache__/__init__.cpython-312.pyc and /dev/null differ diff --git a/westech_r2/westech_r2/doctype/customer_record/__pycache__/customer_record.cpython-312.pyc b/westech_r2/westech_r2/doctype/customer_record/__pycache__/customer_record.cpython-312.pyc deleted file mode 100644 index 241c131..0000000 Binary files a/westech_r2/westech_r2/doctype/customer_record/__pycache__/customer_record.cpython-312.pyc and /dev/null differ diff --git a/westech_r2/westech_r2/page/customer_intake/__init__.py b/westech_r2/westech_r2/page/customer_intake/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/westech_r2/westech_r2/page/customer_intake/customer-intake.json b/westech_r2/westech_r2/page/customer_intake/customer-intake.json new file mode 100644 index 0000000..b2434e2 --- /dev/null +++ b/westech_r2/westech_r2/page/customer_intake/customer-intake.json @@ -0,0 +1 @@ +{"creation":"2026-05-20 15:00:00.000000","docstatus":0,"doctype":"Page","idx":0,"module":"westech_r2","name":"customer-intake","page_name":"customer-intake","roles":[],"script":null,"standard":"Yes","style":null,"system_page":0,"title":"Customer Intake"} diff --git a/westech_r2/westech_r2/page/customer_intake/customer_intake.html b/westech_r2/westech_r2/page/customer_intake/customer_intake.html new file mode 100644 index 0000000..b475162 --- /dev/null +++ b/westech_r2/westech_r2/page/customer_intake/customer_intake.html @@ -0,0 +1,96 @@ +
+
+
+

Customer Intake

+
+
+
+
+ + +
+
+
+ +
+
+
+
+
+ + +
+
+ + +
+
+ + +
+
+
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+
+
+ + +
+
+ + +
+
+ + +
+
+
+
+ + +
+
+ + +
+
+ + +
+
+
+
+ + + +
+
+
diff --git a/westech_r2/westech_r2/page/customer_intake/customer_intake.js b/westech_r2/westech_r2/page/customer_intake/customer_intake.js new file mode 100644 index 0000000..4952da8 --- /dev/null +++ b/westech_r2/westech_r2/page/customer_intake/customer_intake.js @@ -0,0 +1,125 @@ +frappe.pages["customer-intake"].on_page_load = function(wrapper) { + var page = frappe.ui.make_app_page({parent: wrapper, title: __("Customer Intake"), single_column: true}); + var content = frappe.render_template("customer-intake", {}); + $(page.body).append(content); + + var currentCustomer = null; + var searchTimer = null; + + function clearForm() { + currentCustomer = null; + $("#cust-name, #cust-number, #cust-phone, #cust-address, #cust-city, #cust-state, #cust-zip, #cust-contacts, #cust-email, #cust-hours").val(""); + $("#search-results").empty(); + $("#no-match").hide(); + $("#cust-status").text(""); + } + + function fillForm(c) { + currentCustomer = c; + $("#cust-name").val(c.customer_name || ""); + $("#cust-number").val(c.customer_number || ""); + $("#cust-phone").val(c.phone || c.address_phone || ""); + $("#cust-address").val(c.address_line1 || ""); + $("#cust-city").val(c.city || ""); + $("#cust-state").val(c.state || ""); + $("#cust-zip").val(c.pincode || ""); + $("#cust-contacts").val(c.contact_persons || ""); + $("#cust-email").val(c.email_id || ""); + $("#cust-hours").val(c.hours_of_operation || ""); + $("#cust-status").text("Selected: " + (c.customer_name || c.name)); + $("#no-match").hide(); + } + + function doSearch() { + var q = $("#intake-search").val().trim(); + if (q.length < 2) { $("#search-results").empty(); return; } + frappe.call({ + method: "westech_r2.page.customer-intake.customer-intake.search_customers", + args: {q: q}, + callback: function(r) { + var list = $("#search-results").empty(); + if (r.message && r.message.length) { + r.message.forEach(function(c) { + var item = $('
') + .html('' + frappe.utils.escape_html(c.customer_name || c.name) + ' ' + + (c.address_line1 ? '
' + frappe.utils.escape_html(c.address_line1) : '') + + (c.city ? ', ' + c.city : '') + + (c.phone ? '
' + c.phone + '' : '')); + item.on("click", function() { fillForm(c); }); + list.append(item); + }); + $("#no-match").hide(); + } else { + $("#no-match").show(); + } + } + }); + } + + $("#intake-search").on("input", function() { + clearTimeout(searchTimer); + searchTimer = setTimeout(doSearch, 300); + }); + + $("#btn-add-new").click(function() { + clearForm(); + $("#cust-name").val($("#intake-search").val()); + $("#cust-status").text("Adding new customer..."); + }); + + $("#btn-save-cust").click(function() { + var data = { + customer_name: $("#cust-name").val(), + customer_number: $("#cust-number").val(), + phone: $("#cust-phone").val(), + address_line1: $("#cust-address").val(), + city: $("#cust-city").val(), + state: $("#cust-state").val(), + pincode: $("#cust-zip").val(), + contact_persons: $("#cust-contacts").val(), + email_id: $("#cust-email").val(), + hours_of_operation: $("#cust-hours").val() + }; + if (currentCustomer && currentCustomer.name) { + data.name = currentCustomer.name; + } + frappe.call({ + method: "westech_r2.page.customer-intake.customer-intake.create_customer_from_intake", + args: {data: JSON.stringify(data)}, + callback: function(r) { + if (r.message && r.message.status === "ok") { + currentCustomer = {name: r.message.customer, customer_name: data.customer_name}; + $("#cust-status").text("Saved: " + r.message.customer); + frappe.show_alert("Customer saved", 3); + } else { + frappe.show_alert("Error saving customer", 5); + } + } + }); + }); + + $("#btn-create-pallet").click(function() { + if (!currentCustomer || !currentCustomer.name) { + frappe.msgprint("Please select or save a customer first."); + return; + } + frappe.call({ + method: "westech_r2.page.customer-intake.customer-intake.create_pallet", + args: { + data: JSON.stringify({ + customer: currentCustomer.name, + customer_number: $("#cust-number").val(), + data_status: $("#pallet-data-status").val(), + status: $("#pallet-status").val(), + inbound_weight: $("#pallet-weight").val() + }) + }, + callback: function(r) { + if (r.message && r.message.status === "ok") { + frappe.msgprint("Pallet created: " + r.message.pallet); + $("#cust-status").text("Pallet: " + r.message.pallet); + } + } + }); + }); +}; diff --git a/westech_r2/westech_r2/page/customer_intake/customer_intake.py b/westech_r2/westech_r2/page/customer_intake/customer_intake.py new file mode 100644 index 0000000..544f5be --- /dev/null +++ b/westech_r2/westech_r2/page/customer_intake/customer_intake.py @@ -0,0 +1,84 @@ +import frappe +from frappe import _ + +@frappe.whitelist() +def search_customers(q=""): + if not q or len(q) < 2: + return [] + q = q.strip().lower() + customers = frappe.db.sql(""" + SELECT c.name, c.customer_name, c.customer_number, c.phone, c.email_id, + a.address_line1, a.city, a.state, a.pincode + FROM tabCustomer c + LEFT JOIN tabDynamic Link dl ON dl.link_doctype = 'Customer' AND dl.link_name = c.name AND dl.parenttype = 'Address' + LEFT JOIN tabAddress a ON a.name = dl.parent + WHERE LOWER(c.customer_name) LIKE %s OR LOWER(c.customer_number) LIKE %s OR LOWER(c.phone) LIKE %s + ORDER BY c.customer_name + LIMIT 20 + """, ("%" + q + "%", "%" + q + "%", "%" + q + "%"), as_dict=True) + return customers + +@frappe.whitelist() +def get_customer(name): + if not name: + return {} + cust = frappe.get_doc("Customer", name) + result = cust.as_dict() + addr = frappe.db.sql(""" + SELECT a.address_line1, a.city, a.state, a.pincode, a.phone + FROM tabAddress a + JOIN tabDynamic Link dl ON dl.parent = a.name AND dl.link_doctype = 'Customer' AND dl.link_name = %s + LIMIT 1 + """, (name,), as_dict=True) + if addr: + result.update({ + "address_line1": addr[0].address_line1, + "city": addr[0].city, + "state": addr[0].state, + "pincode": addr[0].pincode, + "address_phone": addr[0].phone + }) + return result + +@frappe.whitelist() +def create_customer_from_intake(data): + data = frappe.parse_json(data) + if not data.get("customer_name"): + frappe.throw(_("Customer name required")) + customer = frappe.new_doc("Customer") + customer.customer_name = data.get("customer_name") + customer.customer_group = data.get("customer_group", "IT Recycling") + customer.customer_type = "Company" + customer.customer_number = data.get("customer_number") + customer.phone = data.get("phone") + customer.email_id = data.get("email_id") + customer.legacy_notes = data.get("legacy_notes") + customer.hours_of_operation = data.get("hours_of_operation") + customer.contact_persons = data.get("contact_persons") + customer.save() + if data.get("address_line1") or data.get("city"): + addr = frappe.new_doc("Address") + addr.address_title = customer.customer_name + addr.address_type = "Billing" + addr.address_line1 = data.get("address_line1", "Unknown") + addr.city = data.get("city", "Unknown") + addr.state = data.get("state", "") + addr.pincode = data.get("pincode", "") + addr.country = "United States" + addr.append("links", {"link_doctype": "Customer", "link_name": customer.name}) + addr.save() + return {"status": "ok", "customer": customer.name} + +@frappe.whitelist() +def create_pallet(data): + data = frappe.parse_json(data) + if not data.get("customer"): + frappe.throw(_("Customer required")) + pallet = frappe.new_doc("Pallet") + pallet.customer = data.get("customer") + pallet.customer_number = data.get("customer_number") + pallet.data_status = data.get("data_status", "D0") + pallet.status = data.get("status", "Received") + pallet.inbound_weight = data.get("inbound_weight", "") + pallet.save() + return {"status": "ok", "pallet": pallet.name} diff --git a/westech_r2/westech_r2/page/customer_records/customer-records.json b/westech_r2/westech_r2/page/customer_records/customer-records.json new file mode 100644 index 0000000..1fbb98a --- /dev/null +++ b/westech_r2/westech_r2/page/customer_records/customer-records.json @@ -0,0 +1 @@ +{"content": null,"creation": "2026-05-20 22:00:00.000000","docstatus": 0,"doctype": "Page","idx": 0,"modified": "2026-05-20 22:00:00.000000","modified_by": "Administrator","module": "Westech R2","name": "customer-records","owner": "Administrator","page_name": "customer-records","roles": [{"doctype": "Has Role","idx": 1,"name": "a80mopj93i","parent": "customer-records","parentfield": "roles","parenttype": "Page","role": "All"}],"script": null,"standard": "Yes","style": null,"system_page": 0,"title": "Customer Records"} diff --git a/westech_r2/westech_r2/page/customer_records/customer_records.html b/westech_r2/westech_r2/page/customer_records/customer_records.html new file mode 100644 index 0000000..fd9d8f2 --- /dev/null +++ b/westech_r2/westech_r2/page/customer_records/customer_records.html @@ -0,0 +1,227 @@ + + +
+

Modify Records

+ +
+ + + 0 of 0 + + + + + + + +
+ +
+
+ + +
+
+ + +
+ +
+ + +
+
+ + +
+ +
+ + +
+
+ + +
+ +
+ + +
+
+ + +
+ +
+ + +
+
+ + +
+ +
+ + +
+
+ + +
+ +
+ + +
+
+ + +
+ +
+ + +
+ +
+ + +
+ +
+ + +
+
+ + +
diff --git a/westech_r2/westech_r2/page/customer_records/customer_records.py b/westech_r2/westech_r2/page/customer_records/customer_records.py new file mode 100644 index 0000000..008840e --- /dev/null +++ b/westech_r2/westech_r2/page/customer_records/customer_records.py @@ -0,0 +1,85 @@ +import frappe + +@frappe.whitelist() +def get_records(): + # For now return Lead records mapped to form fields + leads = frappe.db.sql(""" + SELECT name, company_name, email_id, mobile_no, phone, address_line1, city, state, pincode, + title, status, industry, source + FROM tabLead + ORDER BY creation DESC + LIMIT 1000 + """, as_dict=True) + result = [] + for l in leads: + result.append({ + "name": l.name, + "additional_numbers": "", + "field_star": l.status or "", + "customer_address": l.address_line1 or "", + "any_letter": l.title or "", + "city": l.city or "", + "field_e": l.email_id or "", + "zip": l.pincode or "", + "company_name": l.company_name or "", + "contacted_date": "", + "contact_persons": l.title or "", + "follow_up_date": "", + "email_address": l.email_id or "", + "last_pu_date": "", + "contact_numbers": (l.phone or "") + "\n" + (l.mobile_no or ""), + "hours_operation": l.industry or "", + "comments": l.source or "" + }) + return result + +@frappe.whitelist() +def save_record(data): + data = frappe.parse_json(data) + # For now just return OK - Lead update can be wired later + return {"status": "ok", "message": "Saved " + (data.get("name") or "")} + +@frappe.whitelist() +def delete_record(name): + # For now return OK + return {"status": "ok", "message": "Deleted " + name} + +@frappe.whitelist() +def search_records(field, value): + leads = frappe.db.sql(""" + SELECT name, company_name, email_id, mobile_no, phone, address_line1, city, state, pincode, + title, status, industry, source + FROM tabLead + WHERE LOWER(company_name) LIKE %s + OR LOWER(title) LIKE %s + OR LOWER(address_line1) LIKE %s + OR LOWER(city) LIKE %s + OR LOWER(pincode) LIKE %s + OR LOWER(email_id) LIKE %s + OR LOWER(phone) LIKE %s + OR LOWER(mobile_no) LIKE %s + ORDER BY creation DESC + LIMIT 100 + """, tuple(["%" + value + "%"] * 8), as_dict=True) + result = [] + for l in leads: + result.append({ + "name": l.name, + "additional_numbers": "", + "field_star": l.status or "", + "customer_address": l.address_line1 or "", + "any_letter": l.title or "", + "city": l.city or "", + "field_e": l.email_id or "", + "zip": l.pincode or "", + "company_name": l.company_name or "", + "contacted_date": "", + "contact_persons": l.title or "", + "follow_up_date": "", + "email_address": l.email_id or "", + "last_pu_date": "", + "contact_numbers": (l.phone or "") + "\n" + (l.mobile_no or ""), + "hours_operation": l.industry or "", + "comments": l.source or "" + }) + return result diff --git a/westech_r2/westech_r2/page/ebay-pricing/__init__.py b/westech_r2/westech_r2/page/ebay-pricing/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/westech_r2/westech_r2/page/ebay-pricing/ebay-pricing.css b/westech_r2/westech_r2/page/ebay-pricing/ebay-pricing.css new file mode 100644 index 0000000..8b27534 --- /dev/null +++ b/westech_r2/westech_r2/page/ebay-pricing/ebay-pricing.css @@ -0,0 +1,5 @@ +.badge-fresh { background-color: #28a745; } +.badge-aging { background-color: #ffc107; color: #212529; } +.badge-expired { background-color: #dc3545; } +.badge-needs { background-color: #fd7e14; } +.badge-error { background-color: #6c757d; } diff --git a/westech_r2/westech_r2/page/ebay-pricing/ebay-pricing.html b/westech_r2/westech_r2/page/ebay-pricing/ebay-pricing.html new file mode 100644 index 0000000..4015bfb --- /dev/null +++ b/westech_r2/westech_r2/page/ebay-pricing/ebay-pricing.html @@ -0,0 +1,8 @@ + +
diff --git a/westech_r2/westech_r2/page/ebay-pricing/ebay-pricing.js b/westech_r2/westech_r2/page/ebay-pricing/ebay-pricing.js new file mode 100644 index 0000000..1414078 --- /dev/null +++ b/westech_r2/westech_r2/page/ebay-pricing/ebay-pricing.js @@ -0,0 +1,323 @@ +frappe.pages['ebay-pricing'].on_page_load = function(wrapper) { + var page = frappe.ui.make_app_page({ + parent: wrapper, + title: __('eBay Pricing'), + single_column: true + }); + + let $container = $(` +
+
+
+
+ +
+ + + + +
+
+
+
+
+ + + +
+
+
+
+
+
+

Apply Pricing to Inventory

+
+ +
+ + +
+
+
+
Pricing Status
+
+

Click Apply All to see stats

+
+
+
+
+
+
+
+ +

Search for a model or run batch pricing

+
+
+
`).appendTo(page.main); + + // Load item dropdown + load_item_dropdown(); + + // Event handlers + $container.find('#ebay-search-btn').on('click', function() { + let query = $container.find('#ebay-search-input').val().trim(); + if (!query) { + frappe.msgprint(__('Enter a model to search')); + return; + } + search_ebay(query); + }); + + $container.find('#ebay-search-input').on('keypress', function(e) { + if (e.which === 13) { + $container.find('#ebay-search-btn').trigger('click'); + } + }); + + $container.find('#ebay-batch-btn').on('click', function() { + let size = $container.find('#ebay-batch-size').val(); + run_batch(size); + }); + + $container.find('#ebay-apply-btn').on('click', function() { + let item = $container.find('#ebay-apply-item').val(); + if (!item) { + frappe.msgprint(__('Select an Item first')); + return; + } + apply_pricing(item); + }); + + $container.find('#ebay-apply-all-btn').on('click', function() { + apply_pricing_all(); + }); + + function load_item_dropdown() { + frappe.call({ + method: 'frappe.client.get_list', + args: { + doctype: 'Item', + filters: { + 'disabled': 0, + 'item_group': ['in', ['Laptop', 'Desktop', 'Tablet', 'Phone', 'Workstation']] + }, + fields: ['name', 'item_name'], + limit: 1000 + }, + callback: function(r) { + if (r.message) { + let $select = $container.find('#ebay-apply-item'); + r.message.forEach(item => { + $select.append(``); + }); + } + } + }); + } + + function search_ebay(query) { + frappe.call({ + method: 'westech_r2.api.ebay_pricing.search_model', + args: { query: query }, + freeze: true, + freeze_message: __('Searching eBay sold listings...'), + callback: function(r) { + if (r.message && r.message.results) { + render_results(r.message); + } else { + let msg = (r.message && r.message.message) || __('No results found'); + frappe.msgprint(msg); + } + } + }); + } + + function run_batch(size) { + frappe.call({ + method: 'westech_r2.api.ebay_pricing.run_batch', + args: { batch_size: size }, + freeze: true, + freeze_message: __('Running batch pricing...'), + callback: function(r) { + if (r.message) { + frappe.msgprint(__('Batch complete: {0} priced, {1} failed, {2} skipped', + [r.message.priced, r.message.failed, r.message.skipped])); + load_recent_pricing(); + } + } + }); + } + + function apply_pricing(item_code) { + frappe.call({ + method: 'westech_r2.api.ebay_pricing.batch_apply_pricing', + args: { item_code: item_code }, + freeze: true, + freeze_message: __('Applying pricing to Serial Nos...'), + callback: function(r) { + if (r.message) { + render_pricing_stats(r.message); + frappe.msgprint(__('Pricing applied: {0} priced, {1} commodity, {2} needs grading', + [r.message.priced, r.message.commodity, r.message.needs_grading])); + } + } + }); + } + + function apply_pricing_all() { + frappe.call({ + method: 'westech_r2.api.ebay_pricing.batch_apply_pricing', + args: { batch_size: 1000 }, + freeze: true, + freeze_message: __('Applying pricing to all Serial Nos...'), + callback: function(r) { + if (r.message) { + render_pricing_stats(r.message); + frappe.msgprint(__('Batch pricing applied: {0} priced, {1} commodity, {2} needs grading, {3} errors', + [r.message.priced, r.message.commodity, r.message.needs_grading, r.message.errors])); + } + } + }); + } + + function render_pricing_stats(stats) { + let html = ` + + + + + + +
Priced${stats.priced || 0}
Commodity${stats.commodity || 0}
Needs Grading${stats.needs_grading || 0}
Needs Price Point${stats.needs_price_point || 0}
Errors${stats.errors || 0}
+ `; + $container.find('#pricing-stats').html(html); + } + + function render_results(data) { + let $area = $container.find('#ebay-results-area').empty(); + if (!data.results || !data.results.length) { + $area.html(`
No results
`); + return; + } + + let html = ` + + + + + + + + `; + data.results.forEach(item => { + html += ` + + + + + + `; + }); + html += `
TitlePriceConditionSoldShipping
${frappe.utils.escape_html(item.title || '')}$${(item.price || 0).toFixed(2)}${frappe.utils.escape_html(item.condition || '')}${item.sold || ''}${item.shipping || ''}
`; + + if (data.pricing) { + html += `
+

Pricing Summary

+
+
Low: $${data.pricing.price_low}
+
High: $${data.pricing.price_high}
+
Average: $${data.pricing.price_average}
+
Median: $${data.pricing.price_auction}
+
+
+
Source: ${data.pricing.source}
+
Samples: ${data.pricing.sample_count}
+
+
`; + } + + $area.html(html); + } + + function load_recent_pricing() { + frappe.call({ + method: 'westech_r2.api.ebay_pricing.get_recent_pricing', + args: { limit: 50 }, + callback: function(r) { + if (r.message) { + render_pricing_grid(r.message); + } + } + }); + } + + function render_pricing_grid(items) { + let $area = $container.find('#ebay-results-area'); + if (!items || !items.length) { + $area.html(`
No pricing data yet
`); + return; + } + + let html = `

Recent Pricing Results

+ + + + + + + + + + + + + + `; + + items.forEach(row => { + let status_class = 'badge-needs'; + if (row.pricing_status === 'Priced') status_class = 'badge-fresh'; + else if (row.pricing_status === 'Manual Override') status_class = 'badge-fresh'; + else if (row.pricing_status === 'Expired') status_class = 'badge-expired'; + else if (row.pricing_status === 'Error') status_class = 'badge-error'; + + let age = row.days_since_pricing || 0; + let age_badge = age < 90 ? 'badge-fresh' : (age < 120 ? 'badge-aging' : 'badge-expired'); + + html += ` + + + + + + + + + + + `; + }); + + html += `
ManufacturerModelStatusAgeLowHighAvgSamplesSourceLast Priced
${frappe.utils.escape_html(row.manufacturer || '')}${frappe.utils.escape_html(row.model || '')}${row.pricing_status}${age} days$${row.price_low || ''}$${row.price_high || ''}$${row.price_average || ''}${row.sample_count || ''}${row.source || ''}${frappe.datetime.str_to_user(row.scraped_at) || ''}
`; + $area.html(html); + } + + load_recent_pricing(); +}; diff --git a/westech_r2/westech_r2/page/ebay-pricing/ebay-pricing.json b/westech_r2/westech_r2/page/ebay-pricing/ebay-pricing.json new file mode 100644 index 0000000..ab9d81b --- /dev/null +++ b/westech_r2/westech_r2/page/ebay-pricing/ebay-pricing.json @@ -0,0 +1,26 @@ +{ + "creation": "2026-05-17 05:30:00.000000", + "docstatus": 0, + "doctype": "Page", + "icon": "fa fa-tags", + "modified": "2026-05-17 05:30:00.000000", + "modified_by": "Administrator", + "module": "Westech R2", + "name": "ebay-pricing", + "owner": "Administrator", + "page_name": "ebay-pricing", + "roles": [ + { + "role": "System Manager" + }, + { + "role": "Stock User" + }, + { + "role": "Sales User" + } + ], + "standard": "Yes", + "system_page": 0, + "title": "eBay Pricing" +} \ No newline at end of file diff --git a/westech_r2/westech_r2/page/ebay-pricing/ebay-pricing.py b/westech_r2/westech_r2/page/ebay-pricing/ebay-pricing.py new file mode 100644 index 0000000..4053179 --- /dev/null +++ b/westech_r2/westech_r2/page/ebay-pricing/ebay-pricing.py @@ -0,0 +1 @@ +# eBay Pricing desk page diff --git a/westech_r2/westech_r2/page/ebay-pricing/ebay_pricing.css b/westech_r2/westech_r2/page/ebay-pricing/ebay_pricing.css new file mode 100644 index 0000000..8b27534 --- /dev/null +++ b/westech_r2/westech_r2/page/ebay-pricing/ebay_pricing.css @@ -0,0 +1,5 @@ +.badge-fresh { background-color: #28a745; } +.badge-aging { background-color: #ffc107; color: #212529; } +.badge-expired { background-color: #dc3545; } +.badge-needs { background-color: #fd7e14; } +.badge-error { background-color: #6c757d; } diff --git a/westech_r2/westech_r2/page/ebay-pricing/ebay_pricing.html b/westech_r2/westech_r2/page/ebay-pricing/ebay_pricing.html new file mode 100644 index 0000000..4015bfb --- /dev/null +++ b/westech_r2/westech_r2/page/ebay-pricing/ebay_pricing.html @@ -0,0 +1,8 @@ + +
diff --git a/westech_r2/westech_r2/page/ebay-pricing/ebay_pricing.js b/westech_r2/westech_r2/page/ebay-pricing/ebay_pricing.js new file mode 120000 index 0000000..8cd44b0 --- /dev/null +++ b/westech_r2/westech_r2/page/ebay-pricing/ebay_pricing.js @@ -0,0 +1 @@ +ebay-pricing.js \ No newline at end of file diff --git a/westech_r2/westech_r2/page/ebay-pricing/ebay_pricing.json b/westech_r2/westech_r2/page/ebay-pricing/ebay_pricing.json new file mode 120000 index 0000000..d388217 --- /dev/null +++ b/westech_r2/westech_r2/page/ebay-pricing/ebay_pricing.json @@ -0,0 +1 @@ +ebay-pricing.json \ No newline at end of file diff --git a/westech_r2/westech_r2/page/ebay-pricing/ebay_pricing.py b/westech_r2/westech_r2/page/ebay-pricing/ebay_pricing.py new file mode 100644 index 0000000..4053179 --- /dev/null +++ b/westech_r2/westech_r2/page/ebay-pricing/ebay_pricing.py @@ -0,0 +1 @@ +# eBay Pricing desk page diff --git a/westech_r2/westech_r2/page/eim_portal/__init__.py b/westech_r2/westech_r2/page/eim_portal/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/westech_r2/westech_r2/page/eim_portal/eim-portal.css b/westech_r2/westech_r2/page/eim_portal/eim-portal.css new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/westech_r2/westech_r2/page/eim_portal/eim-portal.css @@ -0,0 +1 @@ + diff --git a/westech_r2/westech_r2/page/eim_portal/eim-portal.js b/westech_r2/westech_r2/page/eim_portal/eim-portal.js new file mode 100644 index 0000000..9d84ac1 --- /dev/null +++ b/westech_r2/westech_r2/page/eim_portal/eim-portal.js @@ -0,0 +1,4 @@ +frappe.pages["eim-portal"].on_page_load = function(wrapper) { + wrapper.innerHTML = '

Redirecting to EIM Device Portal...

'; + setTimeout(function() { window.location.href = "https://eim.diagalon.com"; }, 500); +}; diff --git a/westech_r2/westech_r2/page/eim_portal/eim-portal.json b/westech_r2/westech_r2/page/eim_portal/eim-portal.json new file mode 100644 index 0000000..c06f7bd --- /dev/null +++ b/westech_r2/westech_r2/page/eim_portal/eim-portal.json @@ -0,0 +1,13 @@ +{ + "creation": "2026-05-09 14:00:00", + "docstatus": 0, + "doctype": "Page", + "idx": 0, + "modified": "2026-05-09 14:00:00", + "modified_by": "Administrator", + "module": "Westech R2", + "name": "eim-portal", + "owner": "Administrator", + "standard": "Yes", + "title": "EIM Device Portal" +} diff --git a/westech_r2/westech_r2/page/eim_portal/eim-portal.py b/westech_r2/westech_r2/page/eim_portal/eim-portal.py new file mode 100644 index 0000000..9d9e209 --- /dev/null +++ b/westech_r2/westech_r2/page/eim_portal/eim-portal.py @@ -0,0 +1,5 @@ +import frappe + +def get_context(context): + frappe.local.flags.redirect_location = "https://eim.diagalon.com" + raise frappe.Redirect diff --git a/westech_r2/westech_r2/page/eim_portal/eim_portal.js b/westech_r2/westech_r2/page/eim_portal/eim_portal.js new file mode 100644 index 0000000..e5cbdea --- /dev/null +++ b/westech_r2/westech_r2/page/eim_portal/eim_portal.js @@ -0,0 +1,7 @@ +frappe.pages['eim-portal'].on_page_load = function(wrapper) { + var page = frappe.ui.make_app_page({ + parent: wrapper, + title: 'EIM Device Portal', + single_column: true + }); +} \ No newline at end of file diff --git a/westech_r2/westech_r2/page/eim_portal/eim_portal.json b/westech_r2/westech_r2/page/eim_portal/eim_portal.json new file mode 100644 index 0000000..1330152 --- /dev/null +++ b/westech_r2/westech_r2/page/eim_portal/eim_portal.json @@ -0,0 +1,23 @@ +{ + "content": null, + "creation": "2026-05-09 14:00:00", + "docstatus": 0, + "doctype": "Page", + "idx": 0, + "modified": "2026-05-09 15:09:48.653878", + "modified_by": "Administrator", + "module": "Westech R2", + "name": "eim-portal", + "owner": "Administrator", + "page_name": "eim-portal", + "roles": [ + { + "role": "All" + } + ], + "script": null, + "standard": "Yes", + "style": null, + "system_page": 0, + "title": "EIM Device Portal" +} \ No newline at end of file diff --git a/westech_r2/westech_r2/page/intake/__init__.py b/westech_r2/westech_r2/page/intake/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/westech_r2/westech_r2/page/intake/intake.css b/westech_r2/westech_r2/page/intake/intake.css new file mode 100644 index 0000000..c5f45ab --- /dev/null +++ b/westech_r2/westech_r2/page/intake/intake.css @@ -0,0 +1,11 @@ +.intake-station .card { border: 1px solid #e0e0e0; border-radius: 8px; overflow: hidden; } +.intake-station .card-header { padding: 15px; } +.intake-station .card-body { padding: 20px; } +.intake-station .form-group { margin-bottom: 15px; } +.intake-station .form-control { border-radius: 4px; padding: 8px 12px; font-size: 16px; } +.intake-station .form-control:focus { border-color: #6f42c1; box-shadow: 0 0 0 0.2rem rgba(111,66,193,0.25); } +.intake-station label { font-weight: 600; margin-bottom: 4px; } +.intake-station h5 { margin-bottom: 15px; padding-bottom: 8px; border-bottom: 2px solid #e0e0e0; } +.intake-station .table th { background: #f8f9fa; } +.intake-station .btn-primary { background: linear-gradient(135deg, #6f42c1, #28a745) !important; border: none !important; } +.intake-station .label { font-size: 0.85em; } \ No newline at end of file diff --git a/westech_r2/westech_r2/page/intake/intake.js b/westech_r2/westech_r2/page/intake/intake.js new file mode 100644 index 0000000..0242040 --- /dev/null +++ b/westech_r2/westech_r2/page/intake/intake.js @@ -0,0 +1,772 @@ +frappe.pages['intake'].on_page_load = function(wrapper) { + var page = frappe.ui.make_app_page({ + parent: wrapper, + title: 'Intake Station', + single_column: true + }); + + page.set_primary_action('New Intake', function() { + show_intake_form(); + }, 'add'); + + page.add_inner_button('Refresh', function() { + load_recent_pallets(); + }); + + $(wrapper).find('.layout-main-section').html(` +
+ + +
+
+
+
Recent Pallets
+
+
+ + + + + + + + + + + + + + + + +
StatusCustomer #DriverReceivedRED/R2ItemsWeightActions
Loading...
+
+
+
+
+ `); + + // Build ERPNext Link controls for Customer Number and Supplier + setup_link_controls(); + + set_today_date(); + load_recent_pallets(); + + $('#received_date').on('change', function() { + var d = new Date($(this).val() + 'T12:00:00'); + var days = ['Sunday','Monday','Tuesday','Wednesday','Thursday','Friday','Saturday']; + $('#weekday').val(days[d.getDay()] || ''); + }); + + $('#intake-form').on('submit', function(e) { + e.preventDefault(); + save_pallet(); + }); + + $('#btn-cancel').on('click', function() { + $('#intake-form-container').hide(); + $('#recent-pallets').show(); + clear_form(); + }); + + $('#btn-print-labels').on('click', function() { + print_labels(); + }); +}; + +// ── ERPNext Link Controls ────────────────────────────────────────────── + +var customer_number_control = null; +var supplier_control = null; + +function setup_link_controls() { + // Customer Number — Link to Supplier, searching by name (which IS the customer number) + customer_number_control = frappe.ui.form.make_control({ + parent: $('#customer-number-control'), + df: { + fieldtype: 'Link', + fieldname: 'customer_number', + options: 'Customer', + label: 'Customer Number', + reqd: 1, + placeholder: 'Search customer number...', + onchange: function() { + var val = customer_number_control.get_value(); + if (val) { + fetch_customer_details(val); + } else { + clear_customer_fields(); + } + } + }, + only_input: true, + }); + customer_number_control.refresh(); + // Style to match form + $('#customer-number-control .control-input').css('margin', '0'); + $('#customer-number-control .help-box').remove(); + + // Supplier (Driver) — Link to Supplier for driver name + supplier_control = frappe.ui.form.make_control({ + parent: $('#supplier-control'), + df: { + fieldtype: 'Link', + fieldname: 'supplier', + options: 'Customer', + label: 'Driver', + placeholder: 'Search driver...', + onchange: function() { + var val = supplier_control.get_value(); + if (val && val !== customer_number_control.get_value()) { + // Driver selected separately — don't override customer_number + } + } + }, + only_input: true, + }); + supplier_control.refresh(); + $('#supplier-control .control-input').css('margin', '0'); + $('#supplier-control .help-box').remove(); +} + +function fetch_customer_details(customer_name) { + frappe.call({ + method: 'frappe.client.get', + args: {doctype: 'Customer', name: customer_name}, + callback: function(r) { + if (r.message) { + var c = r.message; + // Auto-fill company name + if (c.customer_name && !$('#company_name').val()) { + $('#company_name').val(c.customer_name); + } + // Auto-fill custom CRM fields + if (c.contact_persons && !$('#contact_persons').val()) { + $('#contact_persons').val(c.contact_persons); + } + if (c.hours_of_operation && !$('#hours_of_operation').val()) { + $('#hours_of_operation').val(c.hours_of_operation); + } + if (c.legacy_notes && !$('#legacy_notes').val()) { + $('#legacy_notes').val(c.legacy_notes); + } + // Driver is independent — don't auto-populate from customer number + + // Fill contact fields from Supplier doc's native fields first + // ERPNext auto-populates these from the primary Contact/Address + if (c.customer_primary_contact) { + // Fetch the Contact doc for name/phone/email + frappe.call({ + method: 'frappe.client.get', + args: {doctype: 'Contact', name: c.customer_primary_contact}, + callback: function(cr) { + if (cr.message) { + var ct = cr.message; + var full_name = [ct.first_name, ct.last_name].filter(Boolean).join(' '); + if (full_name && !$('#contact_name').val()) { + $('#contact_name').val(full_name); + } + if (ct.email_id && !$('#contact_email').val()) { + $('#contact_email').val(ct.email_id); + } + if (ct.phone && !$('#contact_number').val()) { + $('#contact_number').val(ct.phone); + } + } + } + }); + } else { + // Fallback: use Supplier-level fields (mobile_no, email_id) + if (c.mobile_no && !$('#contact_number').val()) { + $('#contact_number').val(c.mobile_no); + } + if (c.email_id && !$('#contact_email').val()) { + $('#contact_email').val(c.email_id); + } + + // Last resort: search for any linked Contact + frappe.call({ + method: 'frappe.client.get_list', + args: { + doctype: 'Contact', + filters: [['Dynamic Link', 'link_name', '=', customer_name]], + fields: ['name', 'first_name', 'last_name', 'email_id', 'phone'], + limit_page_length: 1 + }, + callback: function(cr) { + if (cr.message && cr.message.length > 0) { + var c = cr.message[0]; + var full_name = [c.first_name, c.last_name].filter(Boolean).join(' '); + if (full_name && !$('#contact_name').val()) { + $('#contact_name').val(full_name); + } + if (c.email_id && !$('#contact_email').val()) { + $('#contact_email').val(c.email_id); + } + if (c.phone && !$('#contact_number').val()) { + $('#contact_number').val(c.phone); + } + } + } + }); + } + + // Fill address from Supplier doc's primary_address or linked Address + if (c.primary_address && !$('#address_line').val()) { + $('#address_line').val(c.primary_address); + } else if (c.customer_primary_address) { + frappe.call({ + method: 'frappe.client.get', + args: {doctype: 'Address', name: c.customer_primary_address}, + callback: function(ar) { + if (ar.message) { + var a = ar.message; + var addr = [a.address_line1, a.address_line2, a.city, a.state].filter(Boolean).join(', '); + if (addr && !$('#address_line').val()) { + $('#address_line').val(addr); + } + } + } + }); + } else { + // Fallback: search for any linked Address + frappe.call({ + method: 'frappe.client.get_list', + args: { + doctype: 'Address', + filters: [['Dynamic Link', 'link_name', '=', customer_name]], + fields: ['name', 'address_line1', 'city', 'state'], + limit_page_length: 1 + }, + callback: function(ar) { + if (ar.message && ar.message.length > 0) { + var a = ar.message[0]; + var addr = [a.address_line1, a.city, a.state].filter(Boolean).join(', '); + if (addr && !$('#address_line').val()) { + $('#address_line').val(addr); + } + } + } + }); + } + } + } + }); +} + +function clear_customer_fields() { + $('#company_name').val(''); + $('#contact_name').val(''); + $('#contact_number').val(''); + $('#contact_email').val(''); + $('#address_line').val(''); + if (supplier_control) supplier_control.set_value(''); +} + +// ── Form Helpers ────────────────────────────────────────────────────── + +function set_today_date() { + var today = new Date().toISOString().split('T')[0]; + $('#received_date').val(today); + $('#received_date').trigger('change'); +} + +function show_intake_form() { + $('#intake-form-container').show(); + $('#recent-pallets').hide(); + set_today_date(); + // Focus the customer number control + setTimeout(function() { + if (customer_number_control) { + customer_number_control.$input.focus(); + } + }, 200); +} + +function clear_form() { + $('#intake-form input[type="text"], #intake-form input[type="tel"], #intake-form input[type="email"], #intake-form input[type="number"], #intake-form input[type="date"], #intake-form textarea').val(''); + $('#legacy_notes').val(''); + $('#intake-form select').val(''); + $('#total_items').val('0'); + $('#num_labels').val('1'); + $('#amount').val('0'); + $('#needs_aor').prop('checked', false); + $('#needs_cod').prop('checked', false); + $('#btn-print-labels').prop('disabled', true); + $('#save-status').text(''); + $('#intake-form-container').data('pallet-name', ''); + if (customer_number_control) customer_number_control.set_value(''); + if (supplier_control) supplier_control.set_value(''); +} + +function save_pallet() { + var pallet_name = $('#intake-form-container').data('pallet-name'); + var is_new = !pallet_name; + + var data = { + doctype: 'Pallet', + received_date: $('#received_date').val(), + customer_number: customer_number_control ? customer_number_control.get_value() : '', + customer: customer_number_control ? customer_number_control.get_value() : '', + supplier: supplier_control ? supplier_control.get_value() : '', + company_name: $('#company_name').val(), + pickup: $('#pickup').val(), + data_status: $('#data_status').val(), + red_r2: $('#red_r2').val(), + barcode: $('#barcode').val(), + total_items: parseInt($('#total_items').val()) || 0, + num_labels: parseInt($('#num_labels').val()) || 1, + contact_name: $('#contact_name').val(), + contact_persons: $('#contact_persons').val(), + contact_number: $('#contact_number').val(), + contact_email: $('#contact_email').val(), + address_line: $('#address_line').val(), + hours_of_operation: $('#hours_of_operation').val(), + legacy_notes: $('#legacy_notes').val(), + weights: $('#weights').val(), + invoice_check_request: $('#invoice_check_request').val(), + amount: parseFloat($('#amount').val()) || 0, + paid_received: $('#paid_received').val(), + needs_aor: $('#needs_aor').is(':checked') ? 1 : 0, + needs_cod: $('#needs_cod').is(':checked') ? 1 : 0, + notes: $('#notes').val(), + status: 'Received' + }; + + if (!is_new) { + data.name = pallet_name; + } + + frappe.call({ + method: is_new ? 'frappe.client.insert' : 'frappe.client.update', + args: { doc: data }, + callback: function(r) { + if (r.message) { + var name = r.message.name; + $('#save-status').html(' Saved as Intake ' + name + ''); + $('#btn-print-labels').prop('disabled', false); + if (is_new) { + $('#intake-form-container').data('pallet-name', name); + } + } + }, + error: function(r) { + var msg = 'Unknown error'; + if (r && r._server_messages) { + try { + var msgs = JSON.parse(r._server_messages); + if (msgs && msgs.length) { + var errObj = JSON.parse(msgs[0]); + msg = errObj.message || msg; + } + } catch(e) {} + } + $('#save-status').html(' Error: ' + msg + ''); + } + }); +} + +function load_recent_pallets() { + frappe.call({ + method: 'frappe.client.get_list', + args: { + doctype: 'Pallet', + fields: ['name', 'pallet_number', 'status', 'customer_number', 'company_name', 'supplier', 'received_date', 'red_r2', 'total_items', 'weights', 'notes', 'needs_aor', 'needs_cod'], + limit_page_length: 20, + order_by: 'creation desc' + }, + callback: function(r) { + var tbody = $('#pallet-tbody'); + tbody.empty(); + if (!r.message || r.message.length === 0) { + tbody.append('No pallets yet'); + return; + } + r.message.forEach(function(p) { + var status_class = { + 'Received': 'info', + 'Sorting': 'warning', + 'Processing': 'primary', + 'Complete': 'success', + 'Shipped': 'default' + }[p.status] || 'default'; + + tbody.append( + '' + + '' + (p.status || 'Received') + '' + + '' + (p.customer_number || '') + '' + + '' + (p.supplier || '') + '' + + '' + (p.received_date || '') + '' + + '' + (p.red_r2 || '') + + (p.needs_aor ? ' AoR' : '') + + (p.needs_cod ? ' COD' : '') + + '' + + '' + (p.total_items || 0) + '' + + '' + (p.weights || '') + '' + + '' + + ' ' + + '' + + '' + + '' + ); + }); + } + }); +} + +function edit_pallet(name) { + frappe.call({ + method: 'frappe.client.get', + args: {doctype: 'Pallet', name: name}, + callback: function(r) { + if (r.message) { + var d = r.message; + $('#intake-form-container').show(); + $('#recent-pallets').hide(); + $('#intake-form-container').data('pallet-name', name); + $('#received_date').val(d.received_date || ''); + if (customer_number_control) customer_number_control.set_value(d.customer_number || ''); + if (supplier_control) supplier_control.set_value(d.supplier || ''); + $('#company_name').val(d.company_name || ''); + $('#pickup').val(d.pickup || ''); + $('#data_status').val(d.data_status || ''); + $('#red_r2').val(d.red_r2 || ''); + $('#barcode').val(d.barcode || ''); + $('#total_items').val(d.total_items || 0); + $('#num_labels').val(d.num_labels || 1); + $('#contact_name').val(d.contact_name || ''); + $('#contact_number').val(d.contact_number || ''); + $('#contact_email').val(d.contact_email || ''); + $('#address_line').val(d.address_line || ''); + $('#weights').val(d.weights || ''); + $('#invoice_check_request').val(d.invoice_check_request || ''); + $('#amount').val(d.amount || 0); + $('#paid_received').val(d.paid_received || ''); + $('#needs_aor').prop('checked', d.needs_aor === 1); + $('#needs_cod').prop('checked', d.needs_cod === 1); + $('#notes').val(d.notes || ''); + $('#received_date').trigger('change'); + $('#btn-print-labels').prop('disabled', false); + } + } + }); +} + +function print_labels() { + var pallet_name = $('#intake-form-container').data('pallet-name'); + if (!pallet_name) return; + print_pallet_labels(pallet_name); +} + +function print_pallet_labels(name) { + frappe.call({ + method: 'frappe.client.get', + args: {doctype: 'Pallet', name: name}, + callback: function(r) { + if (r.message) { + generate_zpl_label(r.message); + } + } + }); +} + +function generate_zpl_label(d) { + var label_count = d.num_labels || 1; + var date_str = d.received_date || ''; + var driver = d.supplier || ''; + var customer_num = d.customer_number || ''; + var red_r2 = d.red_r2 || ''; + var weight = d.weights || ''; + var pallet_num = d.pallet_number || d.name || ''; + var qr_url = window.location.origin + '/app/pallet/' + (d.name || pallet_num); + + var logo_url = window.location.origin + '/files/COR_html_952bb51d.png'; + + var labels_html = ''; + for (var i = 1; i <= label_count; i++) { + labels_html += '
'; + labels_html += '
WESTECH RECYCLERS
'; + labels_html += '
'; + labels_html += '
'; + labels_html += '
'; + labels_html += '
Pallet #: ' + pallet_num + '
'; + if (customer_num) { + labels_html += '
Customer #: ' + customer_num + '
'; + } + labels_html += '
Received: ' + date_str + '
'; + labels_html += '
Driver: ' + driver + '
'; + labels_html += '
Weight: ' + weight + '
'; + labels_html += '
Items: ' + (d.total_items || 0) + '
'; + labels_html += '
'; + labels_html += '
'; + labels_html += '
'; + if (red_r2) { + labels_html += '
' + red_r2 + '
'; + } + labels_html += ''; + labels_html += '
'; + } + + var existing = document.getElementById('label-preview-overlay'); + if (existing) existing.remove(); + + var overlay = document.createElement('div'); + overlay.id = 'label-preview-overlay'; + overlay.style.cssText = 'position:fixed;top:0;left:0;width:100%;height:100%;background:rgba(0,0,0,0.6);z-index:9999;display:flex;align-items:center;justify-content:center;'; + + var modal = document.createElement('div'); + modal.style.cssText = 'background:#fff;border-radius:8px;padding:20px;max-height:90vh;overflow-y:auto;box-shadow:0 4px 20px rgba(0,0,0,0.3);max-width:95vw;'; + + var title = document.createElement('h3'); + title.textContent = 'Label Preview — Pallet ' + pallet_num + ' (' + label_count + ' label' + (label_count > 1 ? 's' : '') + ')'; + title.style.cssText = 'margin:0 0 15px 0;font-size:16pt;'; + + var btnGroup = document.createElement('div'); + btnGroup.style.cssText = 'margin-bottom:15px;display:flex;gap:10px;'; + + var printBtn = document.createElement('button'); + printBtn.className = 'btn btn-primary'; + printBtn.innerHTML = ' Print Labels'; + printBtn.onclick = function() { + var pw = window.open('', '_blank'); + pw.document.write('Labels — Pallet ' + pallet_num + ''); + pw.document.write('