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