frappe.pages['receiving'].on_page_load = function(wrapper) { var page = frappe.ui.make_app_page({ parent: wrapper, title: 'Receiving', single_column: true }); // Inline HTML $(wrapper).find('.layout-main-section').html(`

🚛 Receiving

Schedule pickups, manage routes, and check in loads.

📅 Pickup Calendar — Next 30 Days
Loading...
Scheduled Pickups
DateWeekdayTypeCustomerContactAddressEst. ItemsDataRED/R2StatusNotesTruckAoRCoD
Loading...
🚛 Truck 1
🚛 Truck 2
🚛 Truck 3
📋 Unassigned
Recent Check-ins
DateCustomerLoad #PalletsWeightContentsData StatusRED/R2
Loading...
`); // Prevent Frappe router from intercepting tab clicks // Tab switching — direct DOM to avoid Frappe router intercepting clicks $("#receiving-tabs").on("click", ".stage-tab", function() { var target = $(this).data("target"); $("#receiving-tabs li, .stage-tab").removeClass("active"); $(this).addClass("active").closest("li").addClass("active"); $(".tab-pane").removeClass("active"); $(target).addClass("active"); }); // ── 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 by name, number, or address...", 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(); } // New Customer button $(document).on("click", "#btn-new-customer", function() { frappe.call({ method: "frappe.client.insert", args: { doc: { doctype: "Customer", customer_type: "Company", customer_group: "All Customer Groups", territory: "United States" } }, callback: function(r) { if (r.message) { frappe.set_route("Form", "Customer", r.message.name); } } }); }); 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 || ""); 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").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 || undefined }, callback: function(r) { if (r.message) { renderPickupTable(r.message.pickups || []); renderCalendar(r.message.calendar || []); } } }); } function renderPickupTable(pickups) { var tbody = $("#pickup-tbody"); $("#pickup-count-label").text("(" + pickups.length + ")"); if (!pickups.length) { tbody.html('No pickups found'); return; } tbody.html(pickups.map(function(p) { var dt = p.pickup_date ? new Date(p.pickup_date + "T00:00:00") : null; var dn = dt ? dayName(dt) : ""; return '' + '' + esc(p.pickup_date || "") + '' + '' + dn + '' + '' + esc(p.pickup_type || "") + '' + '' + esc(p.company_name || p.customer_number || "") + '' + '' + esc(p.contact_name || "") + '' + '' + esc((p.address_line || "") + (p.city ? ", " + p.city : "")) + '' + '' + (p.estimated_items || "—") + '' + '' + esc(p.data_status || "—") + '' + '' + esc(p.red_r2 || "—") + '' + '' + esc(p.status || "") + '' + '' + esc(p.notes || "") + '' + '' + esc(p.truck || "") + '' + '' + (p.needs_aor ? "✓" : "") + '' + '' + (p.needs_cod ? "✓" : "") + ''; }).join("")); } function renderCalendar(calendar) { var el = $("#pickup-calendar"); if (!calendar.length) { el.html('
No data
'); return; } var todayStr = frappe.datetime.nowdate(); var html = '
'; calendar.forEach(function(d) { var cls = "cal-day"; if (d.count > 0) cls += " has-pickups"; if (d.date === todayStr) cls += " today"; var label = d.date.substring(5); html += '
' + label + '
'; }); html += '
'; el.html(html); } $(document).on("click", ".cal-day.has-pickups", function() { $("#pickup-date-filter").val($(this).data("date")); loadPickups(); }); // ── 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(); }); $("#pickup-form").on("submit", function(e) { e.preventDefault(); var customerVal = customer_control ? customer_control.get_value() : ""; if (!customerVal) { frappe.msgprint("Select a customer"); return; } var doc = { doctype: "Scheduled Pickup", pickup_date: $("#sp-pickup_date").val(), pickup_type: $("#sp-pickup_type").val(), customer_number: customerVal, 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(), 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 + ")"); $("#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; var current_pickup_details = 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) { var palletInfo = (c.pallets || []).map(function(p) { return esc(p.pallet_number || p.name); }).join(", "); return '' + '' + esc(c.incoming_date || "") + '' + '' + esc(c.customer_name || c.customer || "") + '' + '' + esc(c.name || "") + '' + '' + (c.pallet_count || 0) + '' + '' + (c.total_weight || "—") + '' + '' + esc(c.data_status || "") + '' + '' + esc(c.red_r2 || "—") + '' + '' + palletInfo + ''; }).join("")); } $("#btn-new-checkin").on("click", function() { $("#checkin-form").show(); $("#ci-received_date").val(frappe.datetime.nowdate()); $("#ci-pickup-details").hide(); current_pickup_details = null; 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"]] ] }; }, onchange: function() { var val = checkin_pickup_control ? checkin_pickup_control.get_value() : ""; if (val) loadPickupDetails(val); else { $("#ci-pickup-details").hide(); current_pickup_details = null; } } }, only_input: true, }); checkin_pickup_control.refresh(); $("#ci-pickup-control .control-input").css("margin", "0"); $("#ci-pickup-control .help-box").remove(); }); function loadPickupDetails(pickup_name) { frappe.call({ method: "westech_r2.api.receiving_api.get_pickup_details", args: { pickup_name: pickup_name }, callback: function(r) { if (!r.message) return; current_pickup_details = r.message; var d = r.message; $("#ci-pickup-details").show(); $("#ci-customer-info").text((d.company_name || d.customer_number || "Unknown") + (d.red_r2 ? " — " + d.red_r2 : "")); $("#ci-address-info").text((d.address_line || "") + (d.city ? ", " + d.city : "") + (d.state ? ", " + d.state : "") + (d.zip_code ? " " + d.zip_code : "")); $("#ci-contact-info").text((d.contact_name || "") + (d.contact_phone ? " • " + d.contact_phone : "") + (d.contact_email ? " • " + d.contact_email : "")); // Special handling for RED/NIST if (d.red_r2 && d.red_r2 !== "Neither" && d.red_r2 !== "") { $("#ci-special-handling").show().html("⚠ " + esc(d.red_r2) + " — Special handling required" + (d.needs_aor ? " • AoR ✓" : "") + (d.needs_cod ? " • CoD ✓" : "")); } else { $("#ci-special-handling").hide(); } // Notes if (d.notes) { $("#ci-pickup-notes").show().html("Notes: " + esc(d.notes)); } else { $("#ci-pickup-notes").hide(); } // Pre-fill check-in fields from pickup $("#ci-actual_pallets").val(d.estimated_items || 1); $("#ci-data_status").val(d.data_status || ""); $("#ci-red_r2").val(d.red_r2 || ""); } }); } $("#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 actualPallets = parseInt($("#ci-actual_pallets").val()) || 0; if (actualPallets < 1) { frappe.msgprint("Enter at least 1 pallet"); return; } frappe.confirm( "Check in " + actualPallets + " pallet(s) for this load?", function() { frappe.call({ method: "westech_r2.api.receiving_api.checkin_load", args: { pickup_name: pickupName, received_date: $("#ci-received_date").val(), actual_pallets: actualPallets, total_weight: $("#ci-total_weight").val(), load_contents: $("#ci-load_contents").val(), data_status: $("#ci-data_status").val(), red_r2: $("#ci-red_r2").val() }, callback: function(r) { if (r.message && r.message.success) { frappe.show_alert({ message: "Load checked in! Created " + r.message.pallets_created + " pallet(s) in Load " + r.message.load, indicator: "green" }); $("#checkin-form").hide(); loadCheckins(); loadPickups(); } } }); } ); }); $("#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(); loadCheckins(); };