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 @@
+
')
+ .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
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ FOR
+
+
+
+
+
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 = `
+
+ | Title |
+ Price |
+ Condition |
+ Sold |
+ Shipping |
+
+ `;
+ data.results.forEach(item => {
+ html += `
+ | ${frappe.utils.escape_html(item.title || '')} |
+ $${(item.price || 0).toFixed(2)} |
+ ${frappe.utils.escape_html(item.condition || '')} |
+ ${item.sold || ''} |
+ ${item.shipping || ''} |
+
`;
+ });
+ html += `
`;
+
+ 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
+
+
+ | Manufacturer |
+ Model |
+ Status |
+ Age |
+ Low |
+ High |
+ Avg |
+ Samples |
+ Source |
+ Last Priced |
+
+ `;
+
+ 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 += `
+ | ${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) || ''} |
+
`;
+ });
+
+ html += `
`;
+ $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(`
+
+
+
+
+
+
+
+
+
+
+ | Status |
+ Customer # |
+ Driver |
+ Received |
+ RED/R2 |
+ Items |
+ Weight |
+ Actions |
+
+
+
+ | 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 += '';
+ 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('