diff --git a/westech_r2/__pycache__/__init__.cpython-312.pyc b/westech_r2/__pycache__/__init__.cpython-312.pyc deleted file mode 100644 index 0b2fbe3..0000000 Binary files a/westech_r2/__pycache__/__init__.cpython-312.pyc and /dev/null differ diff --git a/westech_r2/__pycache__/hooks.cpython-312.pyc b/westech_r2/__pycache__/hooks.cpython-312.pyc deleted file mode 100644 index 1438909..0000000 Binary files a/westech_r2/__pycache__/hooks.cpython-312.pyc and /dev/null differ diff --git a/westech_r2/api/__init__.py b/westech_r2/api/__init__.py index 9219fdc..99e389c 100644 --- a/westech_r2/api/__init__.py +++ b/westech_r2/api/__init__.py @@ -1,2 +1,3 @@ from westech_r2.api import sales +from westech_r2.api import receiving_api 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__/optimize_routes.cpython-312.pyc b/westech_r2/api/__pycache__/optimize_routes.cpython-312.pyc deleted file mode 100644 index 2be405b..0000000 Binary files a/westech_r2/api/__pycache__/optimize_routes.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/receiving_api.py b/westech_r2/api/receiving_api.py new file mode 100644 index 0000000..07b203f --- /dev/null +++ b/westech_r2/api/receiving_api.py @@ -0,0 +1,322 @@ +import json +import frappe +from frappe.utils import today, getdate, add_days +from datetime import timedelta + +@frappe.whitelist() +def get_pickups(date=None): + """Fetch scheduled pickups with optional date filter. + Returns pickups, calendar (next 30 days), and weekly chart data.""" + filters = [] + if date: + filters.append(["Scheduled Pickup", "pickup_date", "=", date]) + + fields = [ + "name", "pickup_date", "pickup_type", "status", "truck", "stop_order", + "customer_number", "company_name", + "contact_name", "contact_phone", "contact_email", + "address_line", "city", "state", "zip_code", + "latitude", "longitude", + "estimated_items", "estimated_weight", "load_contents", + "num_labels", "data_status", "red_r2", + "notes", "legacy_notes", "needs_aor", "needs_cod", + ] + + pickups = frappe.get_list("Scheduled Pickup", + fields=fields, + filters=filters if filters else None, + order_by="pickup_date asc, stop_order asc", + limit_page_length=500, + ) + + # Build calendar data (next 30 days) + from_date = getdate(today()) + to_date = add_days(from_date, 30) + + all_pickups = frappe.get_list("Scheduled Pickup", + fields=["pickup_date"], + filters=[["Scheduled Pickup", "pickup_date", ">=", str(from_date)], + ["Scheduled Pickup", "pickup_date", "<=", str(to_date)]], + limit_page_length=500, + ) + + pickup_counts = {} + for p in all_pickups: + d = p.get("pickup_date", "") + if d: + pickup_counts[d] = pickup_counts.get(d, 0) + 1 + + calendar = [] + for i in range(30): + d = add_days(from_date, i) + ds = str(d) + calendar.append({"date": ds, "count": pickup_counts.get(ds, 0)}) + + # Build weekly chart data (last 12 weeks) + weekly = [] + for i in range(11, -1, -1): + week_start = add_days(from_date, -(from_date.weekday() + 7 * i)) + week_end = add_days(week_start, 6) + count = 0 + for d_str, c in pickup_counts.items(): + try: + d = getdate(d_str) + if week_start <= d <= week_end: + count += c + except (ValueError, TypeError): + pass + weekly.append({"label": week_start.strftime("%m/%d"), "count": count}) + + return { + "pickups": pickups, + "calendar": calendar, + "weekly": weekly, + } + + +@frappe.whitelist() +def auto_route(date=None): + """Auto-assign pickups to trucks based on capacity and proximity.""" + if not date: + date = today() + + pickups = frappe.get_list("Scheduled Pickup", + filters={"pickup_date": date}, + fields=["name", "company_name", "estimated_items", "estimated_weight", + "latitude", "longitude", "pickup_type"], + limit_page_length=200, + ) + + if not pickups: + return {"success": True, "assigned": 0} + + trucks = ["Truck 1", "Truck 2", "Truck 3"] + sorted_p = sorted(pickups, key=lambda p: (float(p.get("latitude") or 0), float(p.get("longitude") or 0))) + n = len(sorted_p) + assigned = 0 + + for i, p in enumerate(sorted_p): + if p.get("pickup_type") == "Drop-off": + truck = "" + else: + truck = trucks[i % 3] if n <= 3 else trucks[min(i * 3 // n, 2)] + + doc = frappe.get_doc("Scheduled Pickup", p["name"]) + doc.truck = truck + doc.status = "Routed" if truck else "Scheduled" + doc.stop_order = i + 1 + doc.save() + assigned += 1 + + frappe.db.commit() + return {"success": True, "assigned": assigned} + + +@frappe.whitelist() +def get_checkins(): + """Fetch completed check-ins.""" + checkins = frappe.get_list("Scheduled Pickup", + filters={"status": ["in", ["Complete", "In Progress"]]}, + fields=["name", "pickup_date", "pickup_type", "status", + "company_name", "customer_number", + "estimated_items", "estimated_weight", "load_contents", + "data_status", "red_r2", "notes"], + order_by="pickup_date desc", + limit_page_length=100, + ) + return {"checkins": checkins} + + +@frappe.whitelist() +def cor_report(): + """Generate Certificate of Recycling report.""" + pickups = frappe.get_list("Scheduled Pickup", + filters={"status": "Complete"}, + fields=["name", "pickup_date", "company_name", "customer_number", + "estimated_items", "estimated_weight", "load_contents", + "data_status", "red_r2", "needs_aor", "needs_cod"], + order_by="pickup_date desc", + limit_page_length=200, + ) + + html = "CoR Report" + html += "" + html += "

Certificate of Recycling (CoR) Report

" + html += "

Generated: " + frappe.utils.now() + "

" + html += "

Total completed loads: " + str(len(pickups)) + "

" + + if pickups: + html += "" + html += "" + for p in pickups: + html += "" + html += "" + html += "" + html += "" + html += "" + html += "" + html += "" + html += "" + html += "" + html += "
DateCustomerItemsWeightContentsData StatusRED/R2AoRCoD
" + str(p.get("pickup_date", "")) + "" + str(p.get("company_name", "")) + "" + str(p.get("estimated_items", "")) + "" + str(p.get("estimated_weight", "")) + "" + str(p.get("load_contents", "")) + "" + str(p.get("data_status", "")) + "" + str(p.get("red_r2", "")) + "" + ("✓" if p.get("needs_aor") else "") + "" + ("✓" if p.get("needs_cod") else "") + "
" + else: + html += "

No completed loads found.

" + + html += "" + frappe.local.response["type"] = "html" + frappe.local.response["page_content"] = html + + +@frappe.whitelist() +def print_route_sheet(date=None): + """Generate printable route sheet.""" + if not date: + date = today() + + filters = {"pickup_date": date} if date else {} + pickups = frappe.get_list("Scheduled Pickup", + filters=filters, + fields=["name", "pickup_date", "pickup_type", "status", "truck", "stop_order", + "company_name", "contact_name", "contact_phone", "contact_email", + "address_line", "city", "state", "zip_code", + "estimated_items", "estimated_weight", "load_contents", + "data_status", "red_r2", "needs_aor", "needs_cod", "notes"], + order_by="truck asc, stop_order asc", + limit_page_length=200, + ) + + trucks = {} + unassigned = [] + for p in pickups: + t = p.get("truck", "") + if t and t != "Unassigned": + trucks.setdefault(t, []).append(p) + else: + unassigned.append(p) + + html = "Route Sheet" + html += "" + html += "

Route Sheet — " + str(date or "Today") + "

" + + for truck_name, stops in sorted(trucks.items()): + html += '
🚛 ' + truck_name + " — " + str(len(stops)) + " stops
" + html += "" + html += "" + for i, s in enumerate(stops, 1): + addr = str(s.get("address_line", "")) + ", " + str(s.get("city", "")) + ", " + str(s.get("state", "")) + " " + str(s.get("zip_code", "")) + html += "" + html += "" + html += "" + html += "" + html += "" + html += "" + html += "" + html += "
#CustomerAddressContactItemsWeightDataRED/R2AoRCoDNotes
" + str(i) + "" + str(s.get("company_name", "")) + "" + addr + "" + str(s.get("contact_name", "")) + "
" + str(s.get("contact_phone", "")) + "
" + str(s.get("estimated_items", "")) + "" + str(s.get("estimated_weight", "")) + "" + str(s.get("data_status", "")) + "" + str(s.get("red_r2", "")) + "" + ("✓" if s.get("needs_aor") else "") + "" + ("✓" if s.get("needs_cod") else "") + "" + str(s.get("notes", "")) + "
" + + if unassigned: + html += "

Unassigned

" + for s in unassigned: + html += "" + html += "" + html += "" + html += "
CustomerAddressNotes
" + str(s.get("company_name", "")) + "" + str(s.get("address_line", "")) + "" + str(s.get("notes", "")) + "
" + + html += "" + frappe.local.response["type"] = "html" + frappe.local.response["page_content"] = html + + +@frappe.whitelist() +def print_green_sheet(date=None): + """Generate Green Sheet printout. + ⚠️ TODO: Green Sheet template needs to be filled in with the actual form layout.""" + if not date: + date = today() + + filters = {"pickup_date": date} if date else {} + pickups = frappe.get_list("Scheduled Pickup", + filters=filters, + fields=["name", "pickup_date", "pickup_type", "company_name", + "contact_name", "contact_phone", "address_line", "city", "state", "zip_code", + "estimated_items", "estimated_weight", "load_contents", + "data_status", "red_r2", "needs_aor", "needs_cod", "notes"], + order_by="truck asc, stop_order asc", + limit_page_length=200, + ) + + html = "Green Sheet" + html += "" + html += "

🟢 Green Sheet — " + str(date or "Today") + "

" + html += '

⚠️ This is a PLACEHOLDER template. Replace with actual Green Sheet layout once the form spec is provided.

' + + for p in pickups: + html += '
' + fields = [ + ("Customer", p.get("company_name", "")), + ("Contact", p.get("contact_name", "")), + ("Phone", p.get("contact_phone", "")), + ("Address", str(p.get("address_line", "")) + ", " + str(p.get("city", "")) + ", " + str(p.get("state", "")) + " " + str(p.get("zip_code", ""))), + ("Est. Items", p.get("estimated_items", "")), + ("Est. Weight", p.get("estimated_weight", "")), + ("Load Contents", p.get("load_contents", "")), + ("Data Status", p.get("data_status", "")), + ("RED/R2", p.get("red_r2", "")), + ("Needs AoR", "✓" if p.get("needs_aor") else ""), + ("Needs CoD", "✓" if p.get("needs_cod") else ""), + ("Notes", p.get("notes", "")), + ] + for label, val in fields: + html += '
' + label + ':
' + str(val) + '
' + html += '
' + + html += "" + frappe.local.response["type"] = "html" + frappe.local.response["page_content"] = html + + +@frappe.whitelist() +def print_labels(date=None): + """Generate printable labels.""" + if not date: + date = today() + + filters = {"pickup_date": date} if date else {} + pickups = frappe.get_list("Scheduled Pickup", + filters=filters, + fields=["name", "company_name", "pickup_date", "num_labels", "data_status", "red_r2"], + limit_page_length=200, + ) + + html = "Labels" + html += "" + + for p in pickups: + n = p.get("num_labels") or 1 + for _ in range(n): + html += '
' + html += '
' + str(p.get("company_name", "")) + '
' + html += '
' + str(p.get("pickup_date", "")) + '
' + html += '
' + str(p.get("data_status", "")) + " | " + str(p.get("red_r2", "")) + '
' + html += '
' + + html += "" + frappe.local.response["type"] = "html" + frappe.local.response["page_content"] = html diff --git a/westech_r2/page/pallet_list/__pycache__/__init__.cpython-312.pyc b/westech_r2/page/pallet_list/__pycache__/__init__.cpython-312.pyc deleted file mode 100644 index 71fd083..0000000 Binary files a/westech_r2/page/pallet_list/__pycache__/__init__.cpython-312.pyc and /dev/null 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 deleted file mode 100644 index 5ff33f8..0000000 Binary files a/westech_r2/page/pallet_list/__pycache__/pallet_list.cpython-312.pyc and /dev/null differ diff --git a/westech_r2/westech_r2/__init__.py b/westech_r2/page/receiving/__init__.py similarity index 100% rename from westech_r2/westech_r2/__init__.py rename to westech_r2/page/receiving/__init__.py diff --git a/westech_r2/page/receiving/receiving.js b/westech_r2/page/receiving/receiving.js new file mode 100644 index 0000000..3761266 --- /dev/null +++ b/westech_r2/page/receiving/receiving.js @@ -0,0 +1,598 @@ +frappe.pages['receiving'].on_page_load = function(wrapper) { + var page = frappe.ui.make_app_page({ + parent: wrapper, + title: 'Receiving', + single_column: true + }); + + // Inline HTML — same pattern as intake.js + $(wrapper).find('.layout-main-section').html(` +
+
+
+

🚛 Receiving

+

Schedule pickups, manage routes, and check in loads.

+
+
+ +
+
+
+
+
+
📅 Pickup Calendar — Next 30 Days
+
Loading...
+
+
+
📊 Weekly Pickup Volume
+
+
+
+
+
+
+
+
Scheduled Pickups
+
+ + + +
+
+
+
+ + + +
DateWeekdayTypeCustomerContactAddressEst. ItemsDataRED/R2StatusNotesTruckAoRCoD
Loading...
+
+
+
+
+ +
+
+
+
+
🚛 Truck 1
+
🚛 Truck 2
+
🚛 Truck 3
+
+
📋 Unassigned
+
+
+
+
+
Recent Check-ins
+
+ + + +
DateCustomerTypeActual PalletsActual WeightLoad ContentsData StatusRED/R2Status
Loading...
+
+
+ +
+
+
+ + `); + + + // ── Stage Tabs ── + $("#receiving-tabs a").on("click", function(e) { + e.preventDefault(); + $(this).tab("show"); + var stage = $(this).attr("href").replace("#stage-", ""); + if (stage === "a") loadPickups(); + if (stage === "b") loadRoutes(); + if (stage === "c") loadCheckins(); + }); + + // ── Stage A: Link Controls ── + var customer_control = null; + + function setupCustomerLink() { + customer_control = frappe.ui.form.make_control({ + parent: $("#sp-customer-control"), + df: { + fieldtype: "Link", + fieldname: "customer_number", + options: "Customer", + label: "Customer", + reqd: 1, + placeholder: "Search customer...", + onchange: function() { + var val = customer_control.get_value(); + if (val) fetchCustomerDetails(val); + else clearCustomerFields(); + } + }, + only_input: true, + }); + customer_control.refresh(); + $("#sp-customer-control .control-input").css("margin", "0"); + $("#sp-customer-control .help-box").remove(); + } + + function fetchCustomerDetails(customer_name) { + frappe.call({ + method: "frappe.client.get", + args: { doctype: "Customer", name: customer_name }, + callback: function(r) { + if (!r.message) return; + var c = r.message; + $("#sp-company_name").val(c.customer_name || ""); + $("#sp-contact_name").val(c.contact_name || ""); + $("#sp-contact_phone").val(c.contact_phone || ""); + $("#sp-contact_email").val(c.contact_email || ""); + $("#sp-legacy_notes").val(c.legacy_notes || ""); + $("#sp-hours_of_operation").val(c.hours_of_operation || ""); + + frappe.call({ + method: "frappe.client.get_list", + args: { + doctype: "Address", + filters: [["Dynamic Link", "link_name", "=", customer_name]], + fields: ["address_line1", "city", "state", "pincode"], + limit_page_length: 1 + }, + callback: function(ra) { + if (ra.message && ra.message.length) { + var a = ra.message[0]; + $("#sp-address_line").val(a.address_line1 || ""); + $("#sp-city").val(a.city || ""); + $("#sp-state").val(a.state || "AZ"); + $("#sp-zip_code").val(a.pincode || ""); + } + } + }); + + frappe.call({ + method: "frappe.client.get_list", + args: { + doctype: "Contact", + filters: [["Dynamic Link", "link_name", "=", customer_name]], + fields: ["first_name", "last_name", "email_id", "phone", "mobile_no"], + limit_page_length: 1 + }, + callback: function(rc) { + if (rc.message && rc.message.length) { + var ct = rc.message[0]; + if (!$("#sp-contact_name").val()) { + $("#sp-contact_name").val((ct.first_name || "") + " " + (ct.last_name || "")); + } + if (!$("#sp-contact_phone").val()) { + $("#sp-contact_phone").val(ct.phone || ct.mobile_no || ""); + } + if (!$("#sp-contact_email").val()) { + $("#sp-contact_email").val(ct.email_id || ""); + } + } + } + }); + } + }); + } + + function clearCustomerFields() { + $("#sp-company_name, #sp-contact_name, #sp-contact_phone, #sp-contact_email, #sp-address_line, #sp-city, #sp-state, #sp-zip_code, #sp-legacy_notes, #sp-hours_of_operation").val(""); + $("#sp-state").val("AZ"); + } + + // ── Stage A: Load Pickups ── + function loadPickups() { + var dateFilter = $("#pickup-date-filter").val(); + frappe.call({ + method: "westech_r2.api.receiving_api.get_pickups", + args: { date: dateFilter }, + callback: function(r) { + if (r.message) { + renderPickupTable(r.message.pickups || []); + renderCalendar(r.message.calendar || []); + renderWeeklyChart(r.message.weekly || []); + $("#pickup-count-label").text((r.message.pickups || []).length + " pickups"); + } + } + }); + } + + function renderPickupTable(pickups) { + var tbody = $("#pickup-tbody"); + if (!pickups.length) { + tbody.html('No scheduled pickups'); + return; + } + var statusColors = { "Scheduled": "#2196F3", "Routed": "#009688", "In Progress": "#FF9800", "Complete": "#4CAF50", "Cancelled": "#F44336" }; + var h = ""; + pickups.forEach(function(p) { + var st = p.status || "Scheduled"; + var sc = statusColors[st] || "#999"; + var weekday = p.pickup_date ? dayName(new Date(p.pickup_date + "T12:00:00")) : ""; + var typeBadge = p.pickup_type === "Drop-off" + ? 'Drop-off' + : 'Pickup'; + h += ''; + h += '' + esc(p.pickup_date || "") + ''; + h += '' + weekday + ''; + h += '' + typeBadge + ''; + h += '' + esc(p.company_name || p.customer_number || "") + ''; + h += '' + esc((p.contact_name || "") + (p.contact_phone ? " • " + p.contact_phone : "")) + ''; + h += '' + esc((p.address_line || "") + (p.city ? ", " + p.city : "")) + ''; + h += '' + (p.estimated_items || "—") + ''; + h += '' + esc(p.data_status || "—") + ''; + h += '' + esc(p.red_r2 || "—") + ''; + h += '' + esc(st) + ''; + h += '' + esc(p.notes || "") + ''; + h += '' + esc(p.truck || "—") + ''; + h += '' + (p.needs_aor ? "✓" : "") + ''; + h += '' + (p.needs_cod ? "✓" : "") + ''; + h += ''; + }); + tbody.html(h); + } + + function renderCalendar(days) { + var el = $("#pickup-calendar"); + if (!days || !days.length) { el.html('
No upcoming pickups
'); return; } + var today = frappe.datetime.nowdate(); + var h = '
'; + ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"].forEach(function(d) { + h += '
' + d + '
'; + }); + var first = new Date(days[0].date + "T12:00:00"); + for (var i = 0; i < first.getDay(); i++) h += '
'; + days.forEach(function(d) { + var isToday = d.date === today; + var hasCount = d.count > 0; + var bg = isToday ? "cal-day today" : (hasCount ? "cal-day has-pickups" : "cal-day"); + var onclick = hasCount ? "onclick=$('#pickup-date-filter').val('" + d.date + "');loadPickups();" : ""; + h += '
'; + h += '
' + d.date.split("-")[2] + '
'; + if (hasCount) h += '
' + d.count + '
'; + h += '
'; + }); + h += '
'; + el.html(h); + } + + function renderWeeklyChart(weekly) { + var canvas = document.getElementById("weekly-chart"); + if (!canvas || !weekly || !weekly.length) return; + var ctx = canvas.getContext("2d"); + var W = canvas.width = canvas.parentElement.clientWidth - 20; + var H = canvas.height = 180; + ctx.clearRect(0, 0, W, H); + var maxVal = Math.max.apply(null, weekly.map(function(w) { return w.count; })); + if (maxVal < 1) maxVal = 1; + var barW = Math.min(50, (W - 40) / weekly.length - 8); + var startX = 40; + var barArea = H - 40; + weekly.forEach(function(w, i) { + var x = startX + i * ((W - 60) / weekly.length); + var barH = (w.count / maxVal) * barArea; + var y = H - 30 - barH; + ctx.fillStyle = "#2F5496"; + ctx.fillRect(x, y, barW, barH); + ctx.fillStyle = "#333"; + ctx.font = "11px sans-serif"; + ctx.textAlign = "center"; + ctx.fillText(w.count, x + barW / 2, y - 4); + ctx.fillStyle = "#999"; + ctx.font = "10px sans-serif"; + ctx.fillText(w.label, x + barW / 2, H - 10); + }); + } + + // ── Stage A: New Pickup ── + $("#btn-new-pickup").on("click", function() { + $("#new-pickup-form").show(); + $("#sp-pickup_date").val(frappe.datetime.nowdate()); + setupCustomerLink(); + }); + + $("#btn-cancel-pickup").on("click", function() { + $("#new-pickup-form").hide(); + if (customer_control) customer_control.set_value(""); + }); + + $("#pickup-form").on("submit", function(e) { + e.preventDefault(); + var doc = { + doctype: "Scheduled Pickup", + pickup_date: $("#sp-pickup_date").val(), + pickup_type: $("#sp-pickup_type").val(), + customer_number: customer_control ? customer_control.get_value() : "", + company_name: $("#sp-company_name").val(), + contact_name: $("#sp-contact_name").val(), + contact_phone: $("#sp-contact_phone").val(), + contact_email: $("#sp-contact_email").val(), + address_line: $("#sp-address_line").val(), + city: $("#sp-city").val(), + state: $("#sp-state").val(), + zip_code: $("#sp-zip_code").val(), + estimated_items: parseInt($("#sp-estimated_items").val()) || 0, + estimated_weight: $("#sp-estimated_weight").val(), + load_contents: $("#sp-load_contents").val(), + data_status: $("#sp-data_status").val(), + red_r2: $("#sp-red_r2").val(), + needs_aor: $("#sp-needs_aor").is(":checked") ? 1 : 0, + needs_cod: $("#sp-needs_cod").is(":checked") ? 1 : 0, + notes: $("#sp-notes").val(), + legacy_notes: $("#sp-legacy_notes").val(), + status: "Scheduled" + }; + frappe.call({ + method: "frappe.client.insert", + args: { doc: doc }, + callback: function(r) { + if (r.message) { + frappe.show_alert({ message: "Pickup scheduled", indicator: "green" }); + $("#new-pickup-form").hide(); + loadPickups(); + } + } + }); + }); + + $("#pickup-date-filter").on("change", loadPickups); + $("#btn-clear-date").on("click", function() { + $("#pickup-date-filter").val(""); + loadPickups(); + }); + + // ── Stage B: Routing ── + function loadRoutes() { + var date = $("#route-date").val() || frappe.datetime.nowdate(); + $("#route-date").val(date); + frappe.call({ + method: "westech_r2.api.receiving_api.get_pickups", + args: { date: date }, + callback: function(r) { + if (r.message) renderRouteColumns(r.message.pickups || []); + } + }); + } + + function renderRouteColumns(pickups) { + var trucks = { "Truck 1": [], "Truck 2": [], "Truck 3": [], "Unassigned": [] }; + pickups.forEach(function(p) { + var t = p.truck || ""; + if (t && trucks[t]) trucks[t].push(p); + else trucks["Unassigned"].push(p); + }); + ["Truck 1", "Truck 2", "Truck 3"].forEach(function(t) { + var key = t.toLowerCase().replace(/ /g, ""); + $("#" + key + "-count").text("(" + trucks[t].length + " stops)"); + $("#" + key + "-stops").html(trucks[t].map(function(p, i) { return stopCard(p, i + 1); }).join("")); + }); + $("#unassigned-count").text("(" + trucks["Unassigned"].length + " stops)"); + $("#unassigned-stops").html(trucks["Unassigned"].map(function(p) { return stopCard(p, 0); }).join("")); + } + + function stopCard(p, order) { + var h = '
'; + if (order) h += '
Stop #' + order + '
'; + h += '
' + esc(p.company_name || p.customer_number || "Unknown") + '
'; + h += '
' + esc((p.address_line || "") + (p.city ? ", " + p.city : "")) + '
'; + h += '
'; + if (p.estimated_items) h += '' + p.estimated_items + ' items'; + if (p.data_status) h += '' + esc(p.data_status) + ''; + if (p.red_r2) h += '' + esc(p.red_r2) + ''; + if (p.needs_aor) h += 'AoR'; + if (p.needs_cod) h += 'CoD'; + h += '
'; + return h; + } + + $("#btn-load-routes").on("click", loadRoutes); + + $("#btn-auto-route").on("click", function() { + var date = $("#route-date").val(); + if (!date) { frappe.msgprint("Select a date first"); return; } + frappe.call({ + method: "westech_r2.api.receiving_api.auto_route", + args: { date: date }, + callback: function(r) { + if (r.message && r.message.success) { + frappe.show_alert({ message: "Routes optimized", indicator: "green" }); + loadRoutes(); + } + } + }); + }); + + $("#btn-route-sheet").on("click", function() { + var date = $("#route-date").val() || frappe.datetime.nowdate(); + window.open("/api/method/westech_r2.api.receiving_api.print_route_sheet?date=" + date, "_blank"); + }); + + $("#btn-green-sheet").on("click", function() { + var date = $("#route-date").val() || frappe.datetime.nowdate(); + window.open("/api/method/westech_r2.api.receiving_api.print_green_sheet?date=" + date, "_blank"); + }); + + $("#btn-labels").on("click", function() { + var date = $("#route-date").val() || frappe.datetime.nowdate(); + window.open("/api/method/westech_r2.api.receiving_api.print_labels?date=" + date, "_blank"); + }); + + // ── Stage C: Check-in ── + var checkin_pickup_control = null; + + function loadCheckins() { + frappe.call({ + method: "westech_r2.api.receiving_api.get_checkins", + callback: function(r) { + if (r.message) renderCheckinTable(r.message.checkins || []); + } + }); + } + + function renderCheckinTable(checkins) { + var tbody = $("#checkin-tbody"); + if (!checkins.length) { + tbody.html('No check-ins yet'); + return; + } + tbody.html(checkins.map(function(c) { + return '' + esc(c.pickup_date || "") + '' + + '' + esc(c.company_name || "") + '' + + '' + esc(c.pickup_type || "") + '' + + '' + (c.estimated_items || "—") + '' + + '' + (c.estimated_weight || "—") + '' + + '' + esc(c.load_contents || "") + '' + + '' + esc(c.data_status || "—") + '' + + '' + esc(c.red_r2 || "—") + '' + + '' + esc(c.status || "") + ''; + }).join("")); + } + + $("#btn-new-checkin").on("click", function() { + $("#checkin-form").show(); + $("#ci-received_date").val(frappe.datetime.nowdate()); + checkin_pickup_control = frappe.ui.form.make_control({ + parent: $("#ci-pickup-control"), + df: { + fieldtype: "Link", + fieldname: "pickup_ref", + options: "Scheduled Pickup", + label: "Scheduled Pickup", + reqd: 1, + placeholder: "Search pickup...", + get_query: function() { + return { + filters: [ + ["Scheduled Pickup", "status", "in", ["Scheduled", "Routed", "In Progress"]] + ] + }; + } + }, + only_input: true, + }); + checkin_pickup_control.refresh(); + $("#ci-pickup-control .control-input").css("margin", "0"); + $("#ci-pickup-control .help-box").remove(); + }); + + $("#btn-cancel-checkin").on("click", function() { + $("#checkin-form").hide(); + }); + + $("#checkin-form-inner").on("submit", function(e) { + e.preventDefault(); + var pickupName = checkin_pickup_control ? checkin_pickup_control.get_value() : ""; + if (!pickupName) { frappe.msgprint("Select a pickup"); return; } + + var update = {}; + update.status = "Complete"; + if ($("#ci-actual_pallets").val()) update.estimated_items = parseInt($("#ci-actual_pallets").val()); + if ($("#ci-actual_weight").val()) update.estimated_weight = $("#ci-actual_weight").val(); + if ($("#ci-load_contents").val()) update.load_contents = $("#ci-load_contents").val(); + + frappe.call({ + method: "frappe.client.set_value", + args: { + doctype: "Scheduled Pickup", + name: pickupName, + fieldname: update + }, + callback: function(r) { + if (r.message) { + frappe.show_alert({ message: "Load checked in", indicator: "green" }); + $("#checkin-form").hide(); + loadCheckins(); + } + } + }); + }); + + $("#btn-cor-report").on("click", function() { + window.open("/api/method/westech_r2.api.receiving_api.cor_report", "_blank"); + }); + + // ── Helpers ── + function esc(s) { return s ? String(s).replace(/&/g, "&").replace(//g, ">").replace(/"/g, """) : ""; } + function dayName(d) { return ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"][d.getDay()]; } + + // ── Init ── + loadPickups(); +}; diff --git a/westech_r2/westech_r2/page/r2_tracking/r2_tracking.json b/westech_r2/page/receiving/receiving.json similarity index 60% rename from westech_r2/westech_r2/page/r2_tracking/r2_tracking.json rename to westech_r2/page/receiving/receiving.json index cbedb3f..f2f953a 100644 --- a/westech_r2/westech_r2/page/r2_tracking/r2_tracking.json +++ b/westech_r2/page/receiving/receiving.json @@ -1,15 +1,16 @@ { "content": null, - "creation": "2026-05-09 14:00:00", + "creation": "2026-05-20 21:24:08.575605", "docstatus": 0, "doctype": "Page", + "icon": "stock", "idx": 0, - "modified": "2026-05-09 15:09:48.707863", + "modified": "2026-05-20 21:27:35.049633", "modified_by": "Administrator", "module": "Westech R2", - "name": "r2-tracking", + "name": "receiving", "owner": "Administrator", - "page_name": "r2-tracking", + "page_name": "receiving", "roles": [ { "role": "All" @@ -19,5 +20,5 @@ "standard": "Yes", "style": null, "system_page": 0, - "title": "R2 Data Tracking" + "title": "Receiving" } \ No newline at end of file diff --git a/westech_r2/westech_r2/page/intake/intake.py b/westech_r2/page/receiving/receiving.py similarity index 66% rename from westech_r2/westech_r2/page/intake/intake.py rename to westech_r2/page/receiving/receiving.py index 55cf5c6..f5ae77a 100644 --- a/westech_r2/westech_r2/page/intake/intake.py +++ b/westech_r2/page/receiving/receiving.py @@ -2,4 +2,4 @@ from frappe import _ def get_context(context): context.no_cache = 1 - context.title = _("Intake Station") \ No newline at end of file + context.title = _("Receiving") diff --git a/westech_r2/page/route_planner/__pycache__/__init__.cpython-312.pyc b/westech_r2/page/route_planner/__pycache__/__init__.cpython-312.pyc deleted file mode 100644 index d86b09d..0000000 Binary files a/westech_r2/page/route_planner/__pycache__/__init__.cpython-312.pyc and /dev/null differ 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/__init__.py b/westech_r2/westech_r2/doctype/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/westech_r2/westech_r2/doctype/customer_record/__init__.py b/westech_r2/westech_r2/doctype/customer_record/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/westech_r2/westech_r2/doctype/customer_record/customer_record.js b/westech_r2/westech_r2/doctype/customer_record/customer_record.js deleted file mode 100644 index e9c44e0..0000000 --- a/westech_r2/westech_r2/doctype/customer_record/customer_record.js +++ /dev/null @@ -1,8 +0,0 @@ -// Copyright (c) 2026, Westech and contributors -// For license information, please see license.txt - -// frappe.ui.form.on("Customer Record", { -// refresh(frm) { - -// }, -// }); diff --git a/westech_r2/westech_r2/doctype/customer_record/customer_record.json b/westech_r2/westech_r2/doctype/customer_record/customer_record.json deleted file mode 100644 index 9141732..0000000 --- a/westech_r2/westech_r2/doctype/customer_record/customer_record.json +++ /dev/null @@ -1,158 +0,0 @@ -{ - "actions": [], - "allow_rename": 1, - "autoname": "field:record_number", - "creation": "2026-05-20 15:10:58.374067", - "doctype": "DocType", - "document_type": "Document", - "editable_grid": 1, - "engine": "InnoDB", - "field_order": [ - "record_number", - "company_name", - "supplier", - "customer_number", - "contact_persons", - "email_address", - "phone_numbers", - "customer_address", - "city", - "state", - "zip", - "date_created", - "contacted_date", - "follow_up_date", - "last_pu_date", - "notes", - "comments", - "hours_operation" - ], - "fields": [ - { - "fieldname": "record_number", - "fieldtype": "Data", - "in_list_view": 1, - "label": "Record Number", - "reqd": 1, - "unique": 1 - }, - { - "fieldname": "company_name", - "fieldtype": "Data", - "in_list_view": 1, - "label": "Company Name" - }, - { - "fieldname": "supplier", - "fieldtype": "Link", - "in_list_view": 1, - "label": "Supplier", - "options": "Supplier" - }, - { - "fieldname": "customer_number", - "fieldtype": "Data", - "in_list_view": 1, - "label": "Customer Number" - }, - { - "fieldname": "contact_persons", - "fieldtype": "Text", - "label": "Contact Persons" - }, - { - "fieldname": "email_address", - "fieldtype": "Text", - "label": "Email Addresses" - }, - { - "fieldname": "phone_numbers", - "fieldtype": "Text", - "label": "Phone Numbers" - }, - { - "fieldname": "customer_address", - "fieldtype": "Data", - "label": "Address" - }, - { - "fieldname": "city", - "fieldtype": "Data", - "label": "City" - }, - { - "fieldname": "state", - "fieldtype": "Data", - "label": "State" - }, - { - "fieldname": "zip", - "fieldtype": "Data", - "label": "Zip" - }, - { - "fieldname": "date_created", - "fieldtype": "Date", - "label": "Date Created" - }, - { - "fieldname": "contacted_date", - "fieldtype": "Date", - "label": "Contacted Date" - }, - { - "fieldname": "follow_up_date", - "fieldtype": "Date", - "label": "Follow Up Date" - }, - { - "fieldname": "last_pu_date", - "fieldtype": "Date", - "label": "Last PU Date" - }, - { - "fieldname": "notes", - "fieldtype": "Text", - "label": "Notes" - }, - { - "fieldname": "comments", - "fieldtype": "Text", - "label": "Comments" - }, - { - "fieldname": "hours_operation", - "fieldtype": "Data", - "label": "Hours of Operation" - } - ], - "grid_page_length": 50, - "index_web_pages_for_search": 1, - "links": [], - "modified": "2026-05-20 15:10:58.374067", - "modified_by": "Administrator", - "module": "Westech R2", - "name": "Customer Record", - "naming_rule": "By fieldname", - "owner": "Administrator", - "permissions": [ - { - "create": 1, - "delete": 1, - "email": 1, - "export": 1, - "print": 1, - "read": 1, - "report": 1, - "role": "System Manager", - "share": 1, - "write": 1 - } - ], - "row_format": "Dynamic", - "rows_threshold_for_grid_search": 20, - "sort_field": "modified", - "sort_order": "DESC", - "states": [], - "track_changes": 1 -} \ No newline at end of file diff --git a/westech_r2/westech_r2/doctype/customer_record/customer_record.py b/westech_r2/westech_r2/doctype/customer_record/customer_record.py deleted file mode 100644 index d1bb1ea..0000000 --- a/westech_r2/westech_r2/doctype/customer_record/customer_record.py +++ /dev/null @@ -1,9 +0,0 @@ -# Copyright (c) 2026, Westech and contributors -# For license information, please see license.txt - -# import frappe -from frappe.model.document import Document - - -class CustomerRecord(Document): - pass diff --git a/westech_r2/westech_r2/doctype/customer_record/test_customer_record.py b/westech_r2/westech_r2/doctype/customer_record/test_customer_record.py deleted file mode 100644 index 6f327c6..0000000 --- a/westech_r2/westech_r2/doctype/customer_record/test_customer_record.py +++ /dev/null @@ -1,9 +0,0 @@ -# Copyright (c) 2026, Westech and Contributors -# See license.txt - -# import frappe -from frappe.tests.utils import FrappeTestCase - - -class TestCustomerRecord(FrappeTestCase): - pass diff --git a/westech_r2/westech_r2/page/__init__.py b/westech_r2/westech_r2/page/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/westech_r2/westech_r2/page/customer_intake/__init__.py b/westech_r2/westech_r2/page/customer_intake/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/westech_r2/westech_r2/page/customer_intake/customer-intake.json b/westech_r2/westech_r2/page/customer_intake/customer-intake.json deleted file mode 100644 index b2434e2..0000000 --- a/westech_r2/westech_r2/page/customer_intake/customer-intake.json +++ /dev/null @@ -1 +0,0 @@ -{"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 deleted file mode 100644 index b475162..0000000 --- a/westech_r2/westech_r2/page/customer_intake/customer_intake.html +++ /dev/null @@ -1,96 +0,0 @@ -
-
-
-

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 deleted file mode 100644 index 4952da8..0000000 --- a/westech_r2/westech_r2/page/customer_intake/customer_intake.js +++ /dev/null @@ -1,125 +0,0 @@ -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 deleted file mode 100644 index 544f5be..0000000 --- a/westech_r2/westech_r2/page/customer_intake/customer_intake.py +++ /dev/null @@ -1,84 +0,0 @@ -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/__init__.py b/westech_r2/westech_r2/page/customer_records/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/westech_r2/westech_r2/page/customer_records/customer-records.json b/westech_r2/westech_r2/page/customer_records/customer-records.json deleted file mode 100644 index 1fbb98a..0000000 --- a/westech_r2/westech_r2/page/customer_records/customer-records.json +++ /dev/null @@ -1 +0,0 @@ -{"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 deleted file mode 100644 index fd9d8f2..0000000 --- a/westech_r2/westech_r2/page/customer_records/customer_records.html +++ /dev/null @@ -1,227 +0,0 @@ - - -
-

Modify Records

- -
- - - 0 of 0 - - - - - - - -
- -
-
- - -
-
- - -
- -
- - -
-
- - -
- -
- - -
-
- - -
- -
- - -
-
- - -
- -
- - -
-
- - -
- -
- - -
-
- - -
- -
- - -
-
- - -
- -
- - -
- -
- - -
- -
- - -
-
- - -
diff --git a/westech_r2/westech_r2/page/customer_records/customer_records.js b/westech_r2/westech_r2/page/customer_records/customer_records.js deleted file mode 100644 index 2bef1e7..0000000 --- a/westech_r2/westech_r2/page/customer_records/customer_records.js +++ /dev/null @@ -1,7 +0,0 @@ -frappe.pages['customer-records'].on_page_load = function(wrapper) { - var page = frappe.ui.make_app_page({ - parent: wrapper, - title: 'Customer Records', - single_column: true - }); -} \ No newline at end of file diff --git a/westech_r2/westech_r2/page/customer_records/customer_records.json b/westech_r2/westech_r2/page/customer_records/customer_records.json deleted file mode 100644 index d89235b..0000000 --- a/westech_r2/westech_r2/page/customer_records/customer_records.json +++ /dev/null @@ -1,23 +0,0 @@ -{ - "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/westech_r2/page/customer_records/customer_records.py b/westech_r2/westech_r2/page/customer_records/customer_records.py deleted file mode 100644 index 008840e..0000000 --- a/westech_r2/westech_r2/page/customer_records/customer_records.py +++ /dev/null @@ -1,85 +0,0 @@ -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 deleted file mode 100644 index e69de29..0000000 diff --git a/westech_r2/westech_r2/page/ebay-pricing/ebay-pricing.css b/westech_r2/westech_r2/page/ebay-pricing/ebay-pricing.css deleted file mode 100644 index 8b27534..0000000 --- a/westech_r2/westech_r2/page/ebay-pricing/ebay-pricing.css +++ /dev/null @@ -1,5 +0,0 @@ -.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 deleted file mode 100644 index 4015bfb..0000000 --- a/westech_r2/westech_r2/page/ebay-pricing/ebay-pricing.html +++ /dev/null @@ -1,8 +0,0 @@ - -
diff --git a/westech_r2/westech_r2/page/ebay-pricing/ebay-pricing.js b/westech_r2/westech_r2/page/ebay-pricing/ebay-pricing.js deleted file mode 100644 index 1414078..0000000 --- a/westech_r2/westech_r2/page/ebay-pricing/ebay-pricing.js +++ /dev/null @@ -1,323 +0,0 @@ -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 deleted file mode 100644 index ab9d81b..0000000 --- a/westech_r2/westech_r2/page/ebay-pricing/ebay-pricing.json +++ /dev/null @@ -1,26 +0,0 @@ -{ - "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 deleted file mode 100644 index 4053179..0000000 --- a/westech_r2/westech_r2/page/ebay-pricing/ebay-pricing.py +++ /dev/null @@ -1 +0,0 @@ -# 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 deleted file mode 100644 index 8b27534..0000000 --- a/westech_r2/westech_r2/page/ebay-pricing/ebay_pricing.css +++ /dev/null @@ -1,5 +0,0 @@ -.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 deleted file mode 100644 index 4015bfb..0000000 --- a/westech_r2/westech_r2/page/ebay-pricing/ebay_pricing.html +++ /dev/null @@ -1,8 +0,0 @@ - -
diff --git a/westech_r2/westech_r2/page/ebay-pricing/ebay_pricing.js b/westech_r2/westech_r2/page/ebay-pricing/ebay_pricing.js deleted file mode 120000 index 8cd44b0..0000000 --- a/westech_r2/westech_r2/page/ebay-pricing/ebay_pricing.js +++ /dev/null @@ -1 +0,0 @@ -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 deleted file mode 120000 index d388217..0000000 --- a/westech_r2/westech_r2/page/ebay-pricing/ebay_pricing.json +++ /dev/null @@ -1 +0,0 @@ -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 deleted file mode 100644 index 4053179..0000000 --- a/westech_r2/westech_r2/page/ebay-pricing/ebay_pricing.py +++ /dev/null @@ -1 +0,0 @@ -# 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 deleted file mode 100644 index e69de29..0000000 diff --git a/westech_r2/westech_r2/page/eim_portal/eim-portal.css b/westech_r2/westech_r2/page/eim_portal/eim-portal.css deleted file mode 100644 index 8b13789..0000000 --- a/westech_r2/westech_r2/page/eim_portal/eim-portal.css +++ /dev/null @@ -1 +0,0 @@ - diff --git a/westech_r2/westech_r2/page/eim_portal/eim-portal.js b/westech_r2/westech_r2/page/eim_portal/eim-portal.js deleted file mode 100644 index 9d84ac1..0000000 --- a/westech_r2/westech_r2/page/eim_portal/eim-portal.js +++ /dev/null @@ -1,4 +0,0 @@ -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 deleted file mode 100644 index c06f7bd..0000000 --- a/westech_r2/westech_r2/page/eim_portal/eim-portal.json +++ /dev/null @@ -1,13 +0,0 @@ -{ - "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 deleted file mode 100644 index 9d9e209..0000000 --- a/westech_r2/westech_r2/page/eim_portal/eim-portal.py +++ /dev/null @@ -1,5 +0,0 @@ -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 deleted file mode 100644 index e5cbdea..0000000 --- a/westech_r2/westech_r2/page/eim_portal/eim_portal.js +++ /dev/null @@ -1,7 +0,0 @@ -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 deleted file mode 100644 index 1330152..0000000 --- a/westech_r2/westech_r2/page/eim_portal/eim_portal.json +++ /dev/null @@ -1,23 +0,0 @@ -{ - "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 deleted file mode 100644 index e69de29..0000000 diff --git a/westech_r2/westech_r2/page/intake/intake.css b/westech_r2/westech_r2/page/intake/intake.css deleted file mode 100644 index c5f45ab..0000000 --- a/westech_r2/westech_r2/page/intake/intake.css +++ /dev/null @@ -1,11 +0,0 @@ -.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 deleted file mode 100644 index 0242040..0000000 --- a/westech_r2/westech_r2/page/intake/intake.js +++ /dev/null @@ -1,772 +0,0 @@ -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('