feat: intake page rework, new Pallet fields, address fix, button rename, COR generator, theme CSS

This commit is contained in:
Westech Admin
2026-05-22 05:15:09 +00:00
parent 3c11969c89
commit be06bca0cf
16 changed files with 419 additions and 800 deletions
+233 -444
View File
@@ -1,11 +1,12 @@
frappe.pages['intake'].on_page_load = function(wrapper) {
try {
var page = frappe.ui.make_app_page({
parent: wrapper,
title: 'Intake Station',
title: 'Customer Management',
single_column: true
});
page.set_primary_action('New Intake', function() {
page.set_primary_action('New Customer', function() {
show_intake_form();
}, 'add');
@@ -18,13 +19,44 @@ frappe.pages['intake'].on_page_load = function(wrapper) {
<div id="intake-form-container" style="display:none;">
<div class="card" style="margin-bottom: 20px;">
<div class="card-header" style="background: linear-gradient(135deg, #6f42c1, #28a745); color: white; padding: 15px;">
<img src="/files/COR_html_952bb51d.png" style="max-height: 32px; vertical-align: middle; margin-right: 10px;"><h4 style="margin:0; color: white; display: inline; vertical-align: middle;">New Intake</h4>
<h4 style="margin:0; color: white;">New Customer</h4>
</div>
<div class="card-body" style="padding: 20px;">
<form id="intake-form">
<div class="row">
<div class="col-md-4">
<h5 style="color:#6f42c1;">📅 Dates & Source</h5>
<h5 style="color:#6f42c1;">Customer</h5>
<div class="form-group">
<label>Customer <span class="text-danger">*</span></label>
<div id="customer-number-control"></div>
</div>
<div class="form-group">
<label>Company Name</label>
<input type="text" id="company_name" class="form-control" readonly style="background:#f8f9fa;">
</div>
<div class="form-group">
<label>Driver</label>
<div id="driver-control"></div>
</div>
<div class="form-group">
<label>Contact Name</label>
<input type="text" id="contact_name" class="form-control" readonly style="background:#f8f9fa;">
</div>
<div class="form-group">
<label>Contact #</label>
<input type="tel" id="contact_number" class="form-control" readonly style="background:#f8f9fa;">
</div>
<div class="form-group">
<label>Contact Email</label>
<input type="email" id="contact_email" class="form-control" readonly style="background:#f8f9fa;">
</div>
<div class="form-group">
<label>Address</label>
<input type="text" id="address_line" class="form-control" readonly style="background:#f8f9fa;">
</div>
</div>
<div class="col-md-4">
<h5 style="color:#6f42c1;">Dates & Source</h5>
<div class="form-group">
<label>Received Date <span class="text-danger">*</span></label>
<input type="date" id="received_date" class="form-control" required>
@@ -33,18 +65,6 @@ frappe.pages['intake'].on_page_load = function(wrapper) {
<label>Weekday</label>
<input type="text" id="weekday" class="form-control" readonly style="background:#f8f9fa;">
</div>
<div class="form-group">
<label>Customer Number <span class="text-danger">*</span></label>
<div id="customer-number-control"></div>
</div>
<div class="form-group">
<label>Driver</label>
<div id="supplier-control"></div>
</div>
<div class="form-group">
<label>Company Name</label>
<input type="text" id="company_name" class="form-control">
</div>
<div class="form-group">
<label>Pickup / Drop-off</label>
<select id="pickup" class="form-control">
@@ -54,16 +74,9 @@ frappe.pages['intake'].on_page_load = function(wrapper) {
</select>
</div>
<div class="form-group">
<label>Notes</label>
<textarea id="notes" class="form-control" rows="3" placeholder="Any additional notes..."></textarea>
<label>Hours of Operation</label>
<input type="text" id="hours_of_operation" class="form-control" readonly style="background:#f8f9fa;" placeholder="Auto-filled from Customer">
</div>
<div class="form-group">
<label>Legacy Notes</label>
<textarea id="legacy_notes" class="form-control" rows="2" style="background:#fafafa;" readonly title="Auto-filled from Customer record"></textarea>
</div>
</div>
<div class="col-md-4">
<h5 style="color:#6f42c1;">📦 Data & Labels</h5>
<div class="form-group">
<label>Data Status</label>
<select id="data_status" class="form-control">
@@ -79,17 +92,20 @@ frappe.pages['intake'].on_page_load = function(wrapper) {
<option value="RED">RED</option>
<option value="R2">R2</option>
<option value="Both">Both</option>
<option value="Neither">Neither</option>
<option value="Clear">Clear</option>
</select>
</div>
<div class="form-group">
<div class="checkbox">
<label><input type="checkbox" id="needs_aor"> <strong>Needs AoR</strong> <small class="text-muted">(Agreement of Recycling)</small></label>
</div>
<div class="checkbox">
<label><input type="checkbox" id="needs_cod"> <strong>Needs COD</strong> <small class="text-muted">(Certificate of Destruction)</small></label>
</div>
<label>Notes</label>
<textarea id="notes" class="form-control" rows="3"></textarea>
</div>
<div class="form-group">
<label>Legacy Notes</label>
<textarea id="legacy_notes" class="form-control" rows="2" readonly style="background:#fafafa;" title="Auto-filled from Customer record"></textarea>
</div>
</div>
<div class="col-md-4">
<h5 style="color:#6f42c1;">Items & Weight</h5>
<div class="form-group">
<label>Barcode</label>
<input type="text" id="barcode" class="form-control" placeholder="Scan barcode...">
@@ -102,33 +118,6 @@ frappe.pages['intake'].on_page_load = function(wrapper) {
<label>Number of Labels</label>
<input type="number" id="num_labels" class="form-control" value="1" min="1" max="20">
</div>
</div>
<div class="col-md-4">
<h5 style="color:#6f42c1;">👤 Contact & Financial</h5>
<div class="form-group">
<label>Contact Name</label>
<input type="text" id="contact_name" class="form-control">
</div>
<div class="form-group">
<label>Contact Persons</label>
<input type="text" id="contact_persons" class="form-control" placeholder="e.g. John Doe, Jane Smith">
</div>
<div class="form-group">
<label>Contact #</label>
<input type="tel" id="contact_number" class="form-control">
</div>
<div class="form-group">
<label>Contact Email</label>
<input type="email" id="contact_email" class="form-control">
</div>
<div class="form-group">
<label>Address</label>
<input type="text" id="address_line" class="form-control">
</div>
<div class="form-group">
<label>Hours of Operation</label>
<input type="text" id="hours_of_operation" class="form-control" placeholder="e.g. Mon-Fri 8am-5pm">
</div>
<hr>
<div class="form-group">
<label>Weight <span class="text-danger">*</span></label>
@@ -156,10 +145,13 @@ frappe.pages['intake'].on_page_load = function(wrapper) {
<div class="row" style="margin-top: 20px;">
<div class="col-md-12">
<button type="submit" class="btn btn-primary btn-lg" style="background: linear-gradient(135deg, #6f42c1, #28a745); border: none;">
<i class="fa fa-save"></i> Save Intake
Save Customer
</button>
<button type="button" class="btn btn-default btn-lg" id="btn-print-labels" disabled>
<i class="fa fa-print"></i> Print Labels
Print Labels
</button>
<button type="button" class="btn btn-default btn-lg" id="btn-generate-cor" disabled>
CoR/AoR
</button>
<button type="button" class="btn btn-default btn-lg" id="btn-cancel">
Cancel
@@ -182,7 +174,7 @@ frappe.pages['intake'].on_page_load = function(wrapper) {
<thead>
<tr>
<th>Status</th>
<th>Customer #</th>
<th>Customer</th>
<th>Driver</th>
<th>Received</th>
<th>RED/R2</th>
@@ -201,10 +193,19 @@ frappe.pages['intake'].on_page_load = function(wrapper) {
</div>
`);
// Build ERPNext Link controls for Customer Number and Supplier
setup_link_controls();
set_today_date();
// Trigger weekday calculation after date field is set
setTimeout(function() {
var dateVal = $('#received_date').val();
if (dateVal) {
var d = new Date(dateVal + 'T12:00:00');
var days = ['Sunday','Monday','Tuesday','Wednesday','Thursday','Friday','Saturday'];
$('#weekday').val(days[d.getDay()] || '');
}
}, 200);
load_recent_pallets();
$('#received_date').on('change', function() {
@@ -225,31 +226,44 @@ frappe.pages['intake'].on_page_load = function(wrapper) {
});
$('#btn-print-labels').on('click', function() {
print_labels();
frappe.msgprint('Label printing coming soon.');
});
$('#btn-generate-cor').on('click', function() {
generate_cor_report();
});
} catch(e) {
console.error('[INTAKE] FATAL:', e.message, e.stack);
$(wrapper).find('.layout-main-section').html(
'<div style="padding:20px;"><h3>Error Loading Page</h3><pre>' + e.message + '</pre></div>'
);
}
};
// ── ERPNext Link Controls ──────────────────────────────────────────────
// ── Link Controls ──────────────────────────────────────────────
var customer_number_control = null;
var supplier_control = null;
var driver_control = null;
var selected_customer_name = 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',
label: 'Customer',
reqd: 1,
placeholder: 'Search customer number...',
placeholder: 'Search customer...',
onchange: function() {
var val = customer_number_control.get_value();
if (val) {
selected_customer_name = val;
fetch_customer_details(val);
} else {
selected_customer_name = null;
clear_customer_fields();
}
}
@@ -257,31 +271,24 @@ function setup_link_controls() {
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'),
driver_control = frappe.ui.form.make_control({
parent: $('#driver-control'),
df: {
fieldtype: 'Link',
fieldname: 'supplier',
fieldname: 'driver',
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
}
}
onchange: function() {}
},
only_input: true,
});
supplier_control.refresh();
$('#supplier-control .control-input').css('margin', '0');
$('#supplier-control .help-box').remove();
driver_control.refresh();
$('#driver-control .control-input').css('margin', '0');
$('#driver-control .help-box').remove();
}
function fetch_customer_details(customer_name) {
@@ -291,115 +298,61 @@ function fetch_customer_details(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
$('#company_name').val(c.customer_name || '');
$('#contact_name').val(c.contact_persons || '');
$('#hours_of_operation').val(c.hours_of_operation || '');
$('#legacy_notes').val(c.legacy_notes || '');
// 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);
}
// Parse phone and email from contact_persons if not in dedicated fields
var phone = c.mobile_no || '';
var email = c.email_id || '';
var addressLine = '';
// 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);
}
}
}
});
// Extract phone from contact_persons text blob
if (!phone && c.contact_persons) {
var phoneMatch = c.contact_persons.match(/\(?\d{3}\)?[-.\s]?\d{3}[-.\s]?\d{4}/);
if (phoneMatch) phone = phoneMatch[0];
}
// Extract email from contact_persons text blob
if (!email && c.contact_persons) {
var emailMatch = c.contact_persons.match(/[\w.+-]+@[\w.-]+\.\w+/);
if (emailMatch) email = emailMatch[0];
}
// 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) {
$('#contact_number').val(phone);
$('#contact_email').val(email);
// Get address — always fetch from Address record for full street+city+state+zip
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);
}
callback: function(r3) {
if (r3.message) {
var addr = r3.message;
var line = (addr.address_line1 || '') + (addr.address_line2 ? ', ' + addr.address_line2 : '');
var full = line + (addr.city ? ', ' + addr.city : '') + (addr.state ? ', ' + addr.state : '') + (addr.pincode ? ' ' + addr.pincode : '');
$('#address_line').val(full);
}
}
});
} else {
// Fallback: search for any linked Address
} else if (c.primary_address) {
// Fallback: parse text blob (less reliable)
var lines = c.primary_address.split('\n');
addressLine = lines.join(', ');
$('#address_line').val(addressLine);
}
// If still missing phone/email, try linked Contact
if ((!phone || !email) && c.customer_primary_contact) {
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);
}
method: 'frappe.client.get',
args: {doctype: 'Contact', name: c.customer_primary_contact},
callback: function(r2) {
if (r2.message) {
var ct = r2.message;
if (!phone) $('#contact_number').val(ct.phone || ct.mobile_no || '');
if (!email) $('#contact_email').val(ct.email_id || '');
}
}
});
@@ -415,10 +368,36 @@ function clear_customer_fields() {
$('#contact_number').val('');
$('#contact_email').val('');
$('#address_line').val('');
if (supplier_control) supplier_control.set_value('');
$('#hours_of_operation').val('');
}
// ── Form Helpers ──────────────────────────────────────────────────────
function clear_form() {
if (customer_number_control) customer_number_control.set_value('');
if (driver_control) driver_control.set_value('');
$('#company_name').val('');
$('#received_date').val('');
$('#weekday').val('');
$('#pickup').val('');
$('#data_status').val('');
$('#red_r2').val('');
$('#barcode').val('');
$('#total_items').val(0);
$('#num_labels').val(1);
$('#contact_name').val('');
$('#contact_number').val('');
$('#contact_email').val('');
$('#address_line').val('');
$('#hours_of_operation').val('');
$('#legacy_notes').val('');
$('#weights').val('');
$('#invoice_check_request').val('');
$('#amount').val(0);
$('#paid_received').val('');
$('#notes').val('');
$('#btn-print-labels').prop('disabled', true);
$('#btn-generate-cor').prop('disabled', true);
$('#save-status').html('');
}
function set_today_date() {
var today = new Date().toISOString().split('T')[0];
@@ -429,96 +408,6 @@ function set_today_date() {
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('<span class="text-success"><i class="fa fa-check"></i> Saved as Intake ' + name + '</span>');
$('#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('<span class="text-danger"><i class="fa fa-exclamation"></i> Error: ' + msg + '</span>');
}
});
}
function load_recent_pallets() {
@@ -526,7 +415,7 @@ function load_recent_pallets() {
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'],
fields: ['name', 'pallet_number', 'status', 'customer_number', 'company_name', 'driver', 'received_date', 'red_r2', 'total_items', 'weights', 'notes'],
limit_page_length: 20,
order_by: 'creation desc'
},
@@ -534,7 +423,7 @@ function load_recent_pallets() {
var tbody = $('#pallet-tbody');
tbody.empty();
if (!r.message || r.message.length === 0) {
tbody.append('<tr><td colspan="8" class="text-center">No pallets yet</td></tr>');
tbody.append('<tr><td colspan="8" class="text-center">No pallets yet. Click "New Customer" to create one.</td></tr>');
return;
}
r.message.forEach(function(p) {
@@ -550,17 +439,14 @@ function load_recent_pallets() {
'<tr>' +
'<td><span class="label label-' + status_class + '">' + (p.status || 'Received') + '</span></td>' +
'<td><strong>' + (p.customer_number || '') + '</strong></td>' +
'<td>' + (p.supplier || '') + '</td>' +
'<td>' + (p.driver || '') + '</td>' +
'<td>' + (p.received_date || '') + '</td>' +
'<td>' + (p.red_r2 || '') +
(p.needs_aor ? ' <span class="label label-warning">AoR</span>' : '') +
(p.needs_cod ? ' <span class="label label-danger">COD</span>' : '') +
'</td>' +
'<td>' + (p.red_r2 || '') + '</td>' +
'<td>' + (p.total_items || 0) + '</td>' +
'<td>' + (p.weights || '') + '</td>' +
'<td>' +
'<button class="btn btn-xs btn-default" onclick="edit_pallet(\'' + p.name + '\')"><i class="fa fa-edit"></i></button> ' +
'<button class="btn btn-xs btn-default" onclick="print_pallet_labels(\'' + p.name + '\')"><i class="fa fa-print"></i></button>' +
'<button class="btn btn-xs btn-default" onclick="window.open(\'https://eim.diagalon.com/report/data-tracking?pallet=' + p.name + '\', \'_blank\')"><i class="fa fa-file-pdf-o"></i></button>' +
'</td>' +
'</tr>'
);
@@ -575,13 +461,12 @@ function edit_pallet(name) {
args: {doctype: 'Pallet', name: name},
callback: function(r) {
if (r.message) {
show_intake_form();
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 || '');
if (driver_control) driver_control.set_value(d.driver || '');
$('#company_name').val(d.company_name || '');
$('#pickup').val(d.pickup || '');
$('#data_status').val(d.data_status || '');
@@ -593,180 +478,84 @@ function edit_pallet(name) {
$('#contact_number').val(d.contact_number || '');
$('#contact_email').val(d.contact_email || '');
$('#address_line').val(d.address_line || '');
$('#hours_of_operation').val(d.hours_of_operation || '');
$('#legacy_notes').val(d.legacy_notes || '');
$('#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);
$('#btn-generate-cor').prop('disabled', false);
}
}
});
}
function print_labels() {
function save_pallet() {
var pallet_name = $('#intake-form-container').data('pallet-name');
if (!pallet_name) return;
print_pallet_labels(pallet_name);
}
var doc = {
doctype: 'Pallet',
received_date: $('#received_date').val(),
customer_number: customer_number_control ? customer_number_control.get_value() : '',
driver: driver_control ? driver_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_number: $('#contact_number').val(),
contact_email: $('#contact_email').val(),
address_line: $('#address_line').val(),
weights: $('#weights').val(),
invoice_check_request: $('#invoice_check_request').val(),
amount: parseFloat($('#amount').val()) || 0,
paid_received: $('#paid_received').val(),
notes: $('#notes').val(),
legacy_notes: $('#legacy_notes').val(),
hours_of_operation: $('#hours_of_operation').val(),
};
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);
if (pallet_name) {
doc.name = pallet_name;
frappe.call({
method: 'frappe.client.update',
args: {doc: doc},
callback: function(r) {
if (r.message) {
frappe.msgprint('Pallet updated successfully!');
$('#save-status').html('<span style="color:green;">Saved!</span>');
}
}
}
});
}
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 += '<div class="label-card">';
labels_html += '<div class="label-header"><img src="' + logo_url + '" style="max-height: 36px; vertical-align: middle;"> <span style="vertical-align: middle;">WESTECH RECYCLERS</span></div>';
labels_html += '<div class="label-body">';
labels_html += '<div class="label-top">';
labels_html += '<div class="label-info">';
labels_html += '<div class="label-row"><span class="label-field">Pallet #:</span> <span class="label-value">' + pallet_num + '</span></div>';
if (customer_num) {
labels_html += '<div class="label-row"><span class="label-field">Customer #:</span> <span class="label-value">' + customer_num + '</span></div>';
}
labels_html += '<div class="label-row"><span class="label-field">Received:</span> <span class="label-value">' + date_str + '</span></div>';
labels_html += '<div class="label-row"><span class="label-field">Driver:</span> <span class="label-value">' + driver + '</span></div>';
labels_html += '<div class="label-row"><span class="label-field">Weight:</span> <span class="label-value">' + weight + '</span></div>';
labels_html += '<div class="label-row"><span class="label-field">Items:</span> <span class="label-value">' + (d.total_items || 0) + '</span></div>';
labels_html += '</div>';
labels_html += '<div class="label-qr" id="qr-' + i + '"></div>';
labels_html += '</div>';
if (red_r2) {
labels_html += '<div class="label-badge">' + red_r2 + '</div>';
}
labels_html += '<div class="label-footer">Label ' + i + ' of ' + label_count + '</div>';
labels_html += '</div></div>';
});
} else {
frappe.call({
method: 'frappe.client.insert',
args: {doc: doc},
callback: function(r) {
if (r.message) {
$('#intake-form-container').data('pallet-name', r.message.name);
frappe.msgprint('Pallet created: ' + r.message.name);
$('#save-status').html('<span style="color:green;">Created!</span>');
$('#btn-print-labels').prop('disabled', false);
$('#btn-generate-cor').prop('disabled', false);
}
}
});
}
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 = '<i class="fa fa-print"></i> Print Labels';
printBtn.onclick = function() {
var pw = window.open('', '_blank');
pw.document.write('<html><head><title>Labels — Pallet ' + pallet_num + '</title>');
pw.document.write('<script src="https://cdn.jsdelivr.net/npm/qrcode-generator@1.4.4/qrcode.min.js"><\/script>');
pw.document.write('<style>');
pw.document.write('@page { size: 4in 6in; margin: 0; }');
pw.document.write('body { font-family: Arial, Helvetica, sans-serif; margin: 0; padding: 5mm; }');
pw.document.write('.label-card { width: 100%; max-width: 3.5in; border: 2px solid #000; margin-bottom: 10px; page-break-inside: avoid; }');
pw.document.write('.label-header { background: #000; color: #fff; font-size: 18pt; font-weight: bold; text-align: center; padding: 6px 0; letter-spacing: 2px; }');
pw.document.write('.label-body { padding: 8px 12px; }');
pw.document.write('.label-top { display: flex; justify-content: space-between; align-items: flex-start; }');
pw.document.write('.label-info { flex: 1; }');
pw.document.write('.label-qr { flex-shrink: 0; margin-left: 10px; }');
pw.document.write('.label-qr table { border-collapse: collapse; }');
pw.document.write('.label-qr td { width: 4px; height: 4px; }');
pw.document.write('.label-row { font-size: 13pt; padding: 2px 0; }');
pw.document.write('.label-field { font-weight: bold; display: inline-block; min-width: 100px; }');
pw.document.write('.label-value { }');
pw.document.write('.label-badge { margin-top: 6px; padding: 4px 12px; background: #d9534f; color: #fff; font-size: 14pt; font-weight: bold; text-align: center; border-radius: 3px; display: inline-block; }');
pw.document.write('.label-footer { font-size: 10pt; color: #666; text-align: center; margin-top: 4px; }');
pw.document.write('@media print { body { padding: 0; } .label-card { margin-bottom: 0; page-break-after: always; } .label-card:last-child { page-break-after: auto; } }');
pw.document.write('</style></head><body>');
pw.document.write(labels_html);
pw.document.write('<script>');
pw.document.write('var qr_url = ' + JSON.stringify(qr_url) + ';');
pw.document.write('var qr = qrcode(0, "M"); qr.addData(qr_url); qr.make();');
pw.document.write('for (var i = 1; i <= ' + label_count + '; i++) { var el = document.getElementById("qr-" + i); if (el) { el.innerHTML = qr.createImgTag(3, 0); } }');
pw.document.write('setTimeout(function() { window.print(); }, 500);');
pw.document.write('<\/script>');
pw.document.write('</body></html>');
pw.document.close();
};
var closeBtn = document.createElement('button');
closeBtn.className = 'btn btn-default';
closeBtn.textContent = 'Cancel';
closeBtn.onclick = function() {
overlay.remove();
};
btnGroup.appendChild(printBtn);
btnGroup.appendChild(closeBtn);
var previewArea = document.createElement('div');
previewArea.style.cssText = 'display:flex;flex-wrap:wrap;gap:15px;justify-content:center;';
previewArea.innerHTML = labels_html;
var previewStyles = document.createElement('style');
previewStyles.textContent =
'#label-preview-overlay .label-card { width: 4in; border: 2px solid #000; background: #fff; box-shadow: 1px 1px 6px rgba(0,0,0,0.15); }' +
'#label-preview-overlay .label-header { background: #000; color: #fff; font-size: 18pt; font-weight: bold; text-align: center; padding: 6px 0; letter-spacing: 2px; }' +
'#label-preview-overlay .label-body { padding: 8px 12px; }' +
'#label-preview-overlay .label-top { display: flex; justify-content: space-between; align-items: flex-start; }' +
'#label-preview-overlay .label-info { flex: 1; }' +
'#label-preview-overlay .label-qr { flex-shrink: 0; margin-left: 10px; }' +
'#label-preview-overlay .label-row { font-size: 13pt; padding: 2px 0; }' +
'#label-preview-overlay .label-field { font-weight: bold; display: inline-block; min-width: 100px; }' +
'#label-preview-overlay .label-badge { margin-top: 6px; padding: 4px 12px; background: #d9534f; color: #fff; font-size: 14pt; font-weight: bold; text-align: center; border-radius: 3px; display: inline-block; }' +
'#label-preview-overlay .label-footer { font-size: 10pt; color: #666; text-align: center; margin-top: 4px; }';
modal.appendChild(title);
modal.appendChild(btnGroup);
modal.appendChild(previewStyles);
modal.appendChild(previewArea);
overlay.appendChild(modal);
document.body.appendChild(overlay);
// Generate QR codes in preview
var script = document.createElement('script');
script.src = 'https://cdn.jsdelivr.net/npm/qrcode-generator@1.4.4/qrcode.min.js';
script.onload = function() {
var qr = qrcode(0, 'M');
qr.addData(qr_url);
qr.make();
for (var i = 1; i <= label_count; i++) {
var el = document.getElementById('qr-' + i);
if (el) el.innerHTML = qr.createImgTag(3, 0);
}
};
document.head.appendChild(script);
overlay.addEventListener('click', function(e) {
if (e.target === overlay) overlay.remove();
});
}
window.edit_pallet = edit_pallet;
window.print_pallet_labels = print_pallet_labels;
function generate_cor_report() {
var pallet_name = $('#intake-form-container').data('pallet-name');
if (!pallet_name) {
frappe.msgprint('Please save the intake first before generating CoR/AoR report.');
return;
}
window.open('https://eim.diagalon.com/report/data-tracking?pallet=' + pallet_name, '_blank');
}
window.edit_pallet = edit_pallet;
+3 -3
View File
@@ -4,7 +4,7 @@
"docstatus": 0,
"doctype": "Page",
"idx": 0,
"modified": "2026-05-09 15:09:48.807066",
"modified": "2026-05-21 18:32:29.966134",
"modified_by": "Administrator",
"module": "Westech R2",
"name": "intake",
@@ -16,8 +16,8 @@
}
],
"script": null,
"standard": "No",
"standard": "Yes",
"style": null,
"system_page": 0,
"title": "Intake Station"
"title": "Customer Management"
}
@@ -1,82 +0,0 @@
<style>
.pallet-list-page { font-family: Helvetica Neue, Arial, sans-serif; }
.pallet-list-page h2 { color: #2F5496; margin-bottom: 16px; font-size: 20px; }
.pallet-search-box { margin-bottom: 16px; display: flex; gap: 10px; align-items: center; flex-wrap: wrap; }
.pallet-search-box input, .pallet-search-box select {
padding: 6px 10px; border: 1px solid #ddd; border-radius: 4px; font-size: 13px;
}
.pallet-search-box button {
padding: 6px 14px; border: 1px solid #ddd; border-radius: 4px; cursor: pointer;
font-size: 13px; background: white;
}
.pallet-search-box button.btn-primary { background: #2F5496; color: white; border-color: #2F5496; }
.pallet-search-box button.btn-success { background: #548235; color: white; border-color: #548235; }
.pallet-table-container { padding: 0; overflow-x: auto; background: white; border-radius: 4px; }
.pallet-table { width: 100%; border-collapse: collapse; font-size: 13px; }
.pallet-table th {
background: #2F5496; color: white; padding: 8px 10px; text-align: left;
cursor: pointer; white-space: nowrap; font-weight: 500;
}
.pallet-table td { padding: 5px 10px; border-bottom: 1px solid #eee; }
.pallet-table tr:hover { background: #f5f5f5; }
.pallet-table tr.new-row { background: #FFF9E6 !important; }
.pallet-table a { color: #2F5496; text-decoration: none; font-weight: 600; }
.status-received { color: #2196F3; font-weight: 600; }
.status-sorting { color: #FF9800; font-weight: 600; }
.status-processing { color: #9C27B0; font-weight: 600; }
.status-complete { color: #4CAF50; font-weight: 600; }
.status-shipped { color: #607D8B; font-weight: 600; }
.pallet-pagination { margin-top: 12px; display: flex; gap: 5px; align-items: center; }
.pallet-pagination button {
padding: 5px 12px; border: 1px solid #ddd; background: white; cursor: pointer;
border-radius: 4px; font-size: 13px; color: #2F5496;
}
.pallet-pagination button.active { background: #2F5496; color: white; border-color: #2F5496; }
.pallet-pagination button:disabled { opacity: 0.5; cursor: not-allowed; }
.pallet-count { margin-left: auto; color: #666; font-size: 13px; }
</style>
<div class="pallet-list-page">
<h2>📦 Pallet List</h2>
<div class="pallet-search-box">
<input type="text" id="pallet-search" placeholder="Search pallet #..." style="max-width:200px;">
<select id="status-filter">
<option value="">All Statuses</option>
<option value="Received">Received</option>
<option value="Sorting">Sorting</option>
<option value="Processing">Processing</option>
<option value="Complete">Complete</option>
<option value="Shipped">Shipped</option>
</select>
<button class="btn btn-default" id="btn-clear">✕ Clear</button>
<button class="btn btn-success" id="btn-export">⬇ Export CSV</button>
<span class="pallet-count" id="pallet-count"></span>
</div>
<div class="pallet-table-container">
<table class="pallet-table" id="pallet-table">
<thead>
<tr>
<th data-sort="pallet_number">Pallet # ⇅</th>
<th data-sort="date_reserved">Date Reserved ⇅</th>
<th data-sort="received_date">Rec. Date ⇅</th>
<th data-sort="customer_number">Customer # ⇅</th>
<th data-sort="inbound_weight">Lbs ⇅</th>
<th data-sort="tester">Who ⇅</th>
<th data-sort="description">Items Tested ⇅</th>
<th data-sort="qty_to_sales">QTY Sale ⇅</th>
<th data-sort="weight_to_sales">Lbs Sale ⇅</th>
<th data-sort="finish_date">Finish Date ⇅</th>
<th>Status</th>
<th>Notes</th>
</tr>
</thead>
<tbody id="pallet-tbody">
<tr><td colspan="12" style="text-align:center;padding:40px;color:#666;">Loading...</td></tr>
</tbody>
</table>
</div>
<div class="pallet-pagination" id="pallet-pagination"></div>
</div>
-128
View File
@@ -1,128 +0,0 @@
frappe.pages["pallet-list"].on_page_load = function(wrapper) {
var page = frappe.ui.make_app_page({
parent: wrapper,
title: __("Pallet List"),
single_column: true
});
var content = frappe.render_template("pallet-list", {});
$(page.body).append(content);
var currentPage = 1;
var pageSize = 100;
var currentSort = "pallet_number";
var sortDir = "desc";
var searchTerm = "";
var statusFilter = "";
function loadPallets() {
frappe.call({
method: "westech_r2.page.pallet-list.pallet-list.get_pallets",
args: {
page: currentPage,
page_size: pageSize,
sort_field: currentSort,
sort_dir: sortDir,
status_filter: statusFilter,
search: searchTerm
},
callback: function(r) {
if (r.message) {
renderPallets(r.message.pallets, r.message.total);
}
}
});
}
function renderPallets(pallets, total) {
var tbody = $("#pallet-tbody");
tbody.empty();
if (!pallets || pallets.length === 0) {
tbody.append('<tr><td colspan="13" style="text-align:center;padding:40px;">No pallets found</td></tr>');
return;
}
pallets.forEach(function(p) {
var statusClass = "status-" + (p.status || "").toLowerCase().replace(/\s+/g, "-");
var link = "/app/pallet/" + encodeURIComponent(p.name);
var pn = (p.pallet_number || "").replace(/</g, "&lt;").replace(/>/g, "&gt;");
var row = '<tr>' +
'<td><a href="' + link + '" style="color:#3cc062;text-decoration:none;font-weight:600;">' + pn + '</a></td>' +
'<td>' + fmtDate(p.date_reserved) + '</td>' +
'<td>' + fmtDate(p.received_date) + '</td>' +
'<td>' + (p.customer_number || "") + '</td>' +
'<td>' + (p.company_name || "") + '</td>' +
'<td>' + (p.inbound_weight || "") + '</td>' +
'<td>' + (p.tester || "") + '</td>' +
'<td>' + (p.description || "") + '</td>' +
'<td>' + (p.qty_to_sales || "") + '</td>' +
'<td>' + (p.weight_to_sales || "") + '</td>' +
'<td>' + fmtDate(p.finish_date) + '</td>' +
'<td class="' + statusClass + '">' + (p.status || "") + '</td>' +
'<td>' + (p.notes || "").substring(0, 50) + '</td>' +
'</tr>';
tbody.append(row);
});
renderPagination(total);
}
function renderPagination(total) {
var totalPages = Math.ceil(total / pageSize);
var pagination = $("#pallet-pagination");
pagination.empty();
if (totalPages <= 1) return;
var prevBtn = $('<button>&laquo; Prev</button>').attr("disabled", currentPage === 1)
.css({padding: "5px 12px", border: "1px solid #ddd", background: "white", cursor: "pointer", marginRight: "5px"})
.on("click", function() {
if (currentPage > 1) { currentPage--; loadPallets(); }
});
pagination.append(prevBtn);
var startPage = Math.max(1, currentPage - 2);
var endPage = Math.min(totalPages, startPage + 4);
for (var i = startPage; i <= endPage; i++) {
var btn = $('<button>' + i + '</button>')
.css({padding: "5px 12px", border: "1px solid #ddd", background: i === currentPage ? "#3cc062" : "white",
color: i === currentPage ? "white" : "#333", cursor: "pointer", marginRight: "5px"})
.on("click", function(page) {
return function() { currentPage = page; loadPallets(); };
}(i));
pagination.append(btn);
}
var nextBtn = $('<button>Next &raquo;</button>').attr("disabled", currentPage === totalPages)
.css({padding: "5px 12px", border: "1px solid #ddd", background: "white", cursor: "pointer", marginRight: "5px"})
.on("click", function() {
if (currentPage < totalPages) { currentPage++; loadPallets(); }
});
pagination.append(nextBtn);
}
function fmtDate(v) {
if (!v) return "";
var s = String(v);
if (s.indexOf("T") > -1) s = s.split("T")[0];
if (s.indexOf(" ") > -1) s = s.split(" ")[0];
return s;
}
$("#pallet-search").on("input", function() {
searchTerm = $(this).val();
currentPage = 1;
loadPallets();
});
$("#status-filter").on("change", function() {
statusFilter = $(this).val();
currentPage = 1;
loadPallets();
});
loadPallets();
};
@@ -1,19 +0,0 @@
{
content: null,
creation: 2026-05-19 13:00:00.000000,
docstatus: 0,
doctype: Page,
idx: 0,
modified: 2026-05-19 13:00:00.000000,
modified_by: Administrator,
module: Westech R2,
name: pallet-list,
owner: Administrator,
page_name: pallet-list,
roles: [],
script: null,
standard: Yes,
style: null,
system_page: 0,
title: Pallet List
}
@@ -1,64 +0,0 @@
import frappe
@frappe.whitelist()
def get_pallets(page=1, page_size=100, sort_field="pallet_number", sort_dir="desc", status_filter="", search=""):
page = int(page)
page_size = int(page_size)
offset = (page - 1) * page_size
conditions = [
"pallet_number IS NOT NULL",
"pallet_number != ''",
"date_reserved IS NOT NULL",
"customer_number IS NOT NULL",
"customer_number != ''"
]
junk = ["", "0", "0000", "N/A", "TBD", "null", "999990", "999995"]
junk_list = "', '".join(junk)
conditions.append("pallet_number NOT IN ('" + junk_list + "')")
conditions.append("pallet_number NOT LIKE '999%'")
conditions.append("pallet_number REGEXP '^[0-9]'")
if status_filter:
conditions.append("status = '" + frappe.db.escape(status_filter) + "'")
if search:
conditions.append("pallet_number LIKE '%" + frappe.db.escape(search) + "%'")
where_clause = " AND ".join(conditions)
total = frappe.db.sql("SELECT COUNT(*) FROM tabPallet WHERE " + where_clause)[0][0]
pallets = frappe.db.sql("SELECT name, pallet_number, date_reserved, received_date, customer_number, company_name, inbound_weight, tester, description, qty_to_sales, weight_to_sales, finish_date, notes, status FROM tabPallet WHERE " + where_clause + " ORDER BY CAST(pallet_number AS UNSIGNED) " + sort_dir + " LIMIT " + str(page_size) + " OFFSET " + str(offset), as_dict=True)
empty_pallet = frappe.db.sql("SELECT name, pallet_number, date_reserved, received_date, customer_number, company_name, inbound_weight, tester, description, qty_to_sales, weight_to_sales, finish_date, notes, status FROM tabPallet WHERE date_reserved IS NULL AND (customer_number IS NULL OR customer_number = '') AND pallet_number NOT IN ('" + junk_list + "') AND pallet_number NOT LIKE '999%' AND pallet_number REGEXP '^[0-9]' ORDER BY CAST(pallet_number AS UNSIGNED) DESC LIMIT 1", as_dict=True)
result = []
if empty_pallet:
empty_pallet[0]["_is_new"] = True
result.append(empty_pallet[0])
result.extend(pallets)
return {"pallets": result, "total": total, "page": page, "page_size": page_size}
@frappe.whitelist()
def update_pallet(docname, field, value):
pallet = frappe.get_doc("Pallet", docname)
pallet.set(field, value)
pallet.save(ignore_permissions=True)
frappe.db.commit()
return {"status": "ok", "message": "Updated " + field}
@frappe.whitelist()
def create_pallet(data):
data = frappe.parse_json(data)
pallet = frappe.new_doc("Pallet")
pallet.pallet_number = data.get("pallet_number")
pallet.status = data.get("status", "Received")
pallet.date_reserved = data.get("date_reserved")
for field, value in data.items():
if field not in ["pallet_number", "status", "date_reserved"] and value:
pallet.set(field, value)
pallet.insert(ignore_permissions=True)
frappe.db.commit()
return {"status": "ok", "name": pallet.name}
+7 -7
View File
@@ -1,6 +1,6 @@
<style>
.pallet-list-page { font-family: Helvetica Neue, Arial, sans-serif; }
.pallet-list-page h2 { color: #2F5496; margin-bottom: 16px; font-size: 20px; }
.pallet-list-page h2 { color: #6f42c1; margin-bottom: 16px; font-size: 20px; }
.pallet-search-box { margin-bottom: 16px; display: flex; gap: 10px; align-items: center; flex-wrap: wrap; }
.pallet-search-box input, .pallet-search-box select {
padding: 6px 10px; border: 1px solid #ddd; border-radius: 4px; font-size: 13px;
@@ -9,18 +9,18 @@
padding: 6px 14px; border: 1px solid #ddd; border-radius: 4px; cursor: pointer;
font-size: 13px; background: white;
}
.pallet-search-box button.btn-primary { background: #2F5496; color: white; border-color: #2F5496; }
.pallet-search-box button.btn-success { background: #548235; color: white; border-color: #548235; }
.pallet-search-box button.btn-primary { background: #6f42c1; color: white; border-color: #6f42c1; }
.pallet-search-box button.btn-success { background: #28a745; color: white; border-color: #28a745; }
.pallet-table-container { padding: 0; overflow-x: auto; background: white; border-radius: 4px; }
.pallet-table { width: 100%; border-collapse: collapse; font-size: 13px; }
.pallet-table th {
background: #2F5496; color: white; padding: 8px 10px; text-align: left;
background: #6f42c1; color: white; padding: 8px 10px; text-align: left;
cursor: pointer; white-space: nowrap; font-weight: 500;
}
.pallet-table td { padding: 5px 10px; border-bottom: 1px solid #eee; }
.pallet-table tr:hover { background: #f5f5f5; }
.pallet-table tr.new-row { background: #FFF9E6 !important; }
.pallet-table a { color: #2F5496; text-decoration: none; font-weight: 600; }
.pallet-table a { color: #6f42c1; text-decoration: none; font-weight: 600; }
.status-received { color: #2196F3; font-weight: 600; }
.status-sorting { color: #FF9800; font-weight: 600; }
.status-processing { color: #9C27B0; font-weight: 600; }
@@ -29,9 +29,9 @@
.pallet-pagination { margin-top: 12px; display: flex; gap: 5px; align-items: center; }
.pallet-pagination button {
padding: 5px 12px; border: 1px solid #ddd; background: white; cursor: pointer;
border-radius: 4px; font-size: 13px; color: #2F5496;
border-radius: 4px; font-size: 13px; color: #6f42c1;
}
.pallet-pagination button.active { background: #2F5496; color: white; border-color: #2F5496; }
.pallet-pagination button.active { background: #6f42c1; color: white; border-color: #6f42c1; }
.pallet-pagination button:disabled { opacity: 0.5; cursor: not-allowed; }
.pallet-count { margin-left: auto; color: #666; font-size: 13px; }
</style>
+1 -1
View File
@@ -17,7 +17,7 @@ frappe.pages["pallet-list"].on_page_load = function(wrapper) {
function loadPallets() {
frappe.call({
method: "westech_r2.page.pallet-list.pallet-list.get_pallets",
method: "westech_r2.page.pallet_list.pallet_list.get_pallets",
args: {
page: currentPage,
page_size: pageSize,
+17 -17
View File
@@ -1,19 +1,19 @@
{
content: null,
creation: 2026-05-19 13:00:00.000000,
docstatus: 0,
doctype: Page,
idx: 0,
modified: 2026-05-19 13:00:00.000000,
modified_by: Administrator,
module: Westech R2,
name: pallet-list,
owner: Administrator,
page_name: pallet-list,
roles: [],
script: null,
standard: Yes,
style: null,
system_page: 0,
title: Pallet List
"content": null,
"creation": "2026-05-19 13:00:00.000000",
"docstatus": 0,
"doctype": "Page",
"idx": 0,
"modified": "2026-05-19 13:00:00.000000",
"modified_by": "Administrator",
"module": "Westech R2",
"name": "pallet-list",
"owner": "Administrator",
"page_name": "pallet-list",
"roles": [],
"script": null,
"standard": "Yes",
"style": null,
"system_page": 0,
"title": "Pallet List"
}
+1 -33
View File
@@ -27,10 +27,6 @@ frappe.pages['receiving'].on_page_load = function(wrapper) {
<div class="panel-heading">📅 Pickup Calendar — Next 30 Days</div>
<div class="panel-body" id="pickup-calendar"><div class="text-muted text-center">Loading...</div></div>
</div>
<div class="panel panel-info">
<div class="panel-heading">📊 Weekly Pickup Volume</div>
<div class="panel-body" style="padding: 5px;"><canvas id="weekly-chart" height="180"></canvas></div>
</div>
</div>
<div class="col-md-9">
<div class="panel panel-default">
@@ -268,7 +264,7 @@ frappe.pages['receiving'].on_page_load = function(wrapper) {
if (r.message) {
renderPickupTable(r.message.pickups || []);
renderCalendar(r.message.calendar || []);
renderWeeklyChart(r.message.weekly || []);
// weekly chart removed
$("#pickup-count-label").text((r.message.pickups || []).length + " pickups");
}
}
@@ -334,34 +330,6 @@ frappe.pages['receiving'].on_page_load = function(wrapper) {
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();