warehouse layers [switchable]

This commit is contained in:
Thanakorn S
2026-05-04 13:43:32 +07:00
parent a90975d9f1
commit 414f1cd81d
26 changed files with 2022 additions and 584 deletions
+48 -12
View File
@@ -138,7 +138,7 @@
<th>Product</th>
<th>SKU</th>
<th>Lot Number</th>
<th>Location</th>
<th class="th_location">Location</th>
<th>Expiry Date</th>
<th>Days Overdue</th>
<th>Quantity</th>
@@ -167,7 +167,7 @@
<th>Product</th>
<th>SKU</th>
<th>Lot Number</th>
<th>Location</th>
<th class="th_location">Location</th>
<th>Expiry Date</th>
<th>Days Remaining</th>
<th>Quantity</th>
@@ -190,9 +190,44 @@
<?php require "../include_ending.php";?>
<script>
var all_items = [];
var search_term = '';
var filter_status = '';
var all_items = [];
var search_term = '';
var filter_status = '';
var advanced = false;
var label_rack = 'Location';
var label_zone = 'Zone';
var label_aisle = 'Aisle';
function apply_location_labels() {
$('.th_location').text(advanced
? label_zone + ' / ' + label_aisle + ' / ' + label_rack
: label_rack
);
}
function format_location(item) {
if (advanced) {
return [item.zone, item.aisle, item.rack].filter(Boolean).join('-') || '<span class="text-muted">—</span>';
}
return item.rack || '<span class="text-muted">—</span>';
}
function load_company_setting() {
return ajax_request({
url: server_url + 'setting/api/engine/company_setting.php',
autoPrepare: true,
checkRequired: 0,
action: 'read',
onSuccess: function(res) {
var s = res.output || {};
advanced = s.advanced_location == 1;
label_rack = s.location_label_rack || (advanced ? 'Rack' : 'Location');
label_zone = s.location_label_zone || 'Zone';
label_aisle = s.location_label_aisle || 'Aisle';
apply_location_labels();
}
});
}
// ── Fetch + populate ─────────────────────────────────────────────────
@@ -270,7 +305,7 @@
<td class="py-3">${item.product_name}</td>
<td class="py-3">${item.product_sku}</td>
<td class="py-3">${item.lot_number || '<span class="text-muted">—</span>'}</td>
<td class="py-3">${item.zone}-${item.aisle}-${item.rack}</td>
<td class="py-3">${format_location(item)}</td>
<td class="py-3">${item.expiry_date}</td>
<td class="py-3 text-danger fw-semibold">${Math.abs(item.days_remaining)} days</td>
<td class="py-3">${item.quantity}</td>
@@ -294,7 +329,7 @@
<td class="py-3">${item.product_name}</td>
<td class="py-3">${item.product_sku}</td>
<td class="py-3">${item.lot_number || '<span class="text-muted">—</span>'}</td>
<td class="py-3">${item.zone}-${item.aisle}-${item.rack}</td>
<td class="py-3">${format_location(item)}</td>
<td class="py-3">${item.expiry_date}</td>
<td class="py-3 text-warning fw-semibold">${item.days_remaining} days</td>
<td class="py-3">${item.quantity}</td>
@@ -347,10 +382,11 @@
// ── Boot ─────────────────────────────────────────────────────────────
$(async function() {
try {
await retrieve_warehouse();
} catch(e) {
$(async function() {
try {
await load_company_setting();
await retrieve_warehouse();
} catch(e) {
console.log(e);
}
});
@@ -362,4 +398,4 @@
</body>
</html>
</html>
+78 -21
View File
@@ -129,9 +129,9 @@
<thead class="table-primary border-light">
<tr>
<th>Warehouse</th>
<th>Zone</th>
<th>Aisle</th>
<th>Rack</th>
<th class="th_loc_zone">Zone</th>
<th class="th_loc_aisle">Aisle</th>
<th class="th_loc_rack">Rack</th>
<th>SKU</th>
<th>Product</th>
<th>Status</th>
@@ -172,9 +172,9 @@
<thead class="table-danger border-light">
<tr>
<th>Warehouse</th>
<th>Zone</th>
<th>Aisle</th>
<th>Rack</th>
<th class="th_loc_zone">Zone</th>
<th class="th_loc_aisle">Aisle</th>
<th class="th_loc_rack">Rack</th>
<th>SKU</th>
<th>Product</th>
<th>Action</th>
@@ -214,9 +214,9 @@
<thead class="table-success border-light">
<tr>
<th>Warehouse</th>
<th>Zone</th>
<th>Aisle</th>
<th>Rack</th>
<th class="th_loc_zone">Zone</th>
<th class="th_loc_aisle">Aisle</th>
<th class="th_loc_rack">Rack</th>
<th>Action</th>
</tr>
</thead>
@@ -263,6 +263,68 @@
<?php require "../include_ending.php";?>
<script>
// ── Location config ───────────────────────────────────────────────────────
var advanced = false;
var label_rack = 'Location';
var label_zone = 'Zone';
var label_aisle = 'Aisle';
function load_location_config(callback) {
return ajax_request({
url: '<?php echo $server_url?>setting/api/engine/company_setting.php',
autoPrepare: true,
checkRequired: 0,
action: 'read',
onSuccess: function(res) {
var s = res.output;
advanced = s.advanced_location == 1;
label_rack = s.location_label_rack || (advanced ? 'Rack' : 'Location');
label_zone = s.location_label_zone || 'Zone';
label_aisle = s.location_label_aisle || 'Aisle';
if (callback) callback();
}
});
}
function apply_location_labels() {
if (advanced) {
$('.th_loc_zone').text(label_zone).show();
$('.th_loc_aisle').text(label_aisle).show();
$('.th_loc_rack').text(label_rack);
} else {
$('.th_loc_zone').hide();
$('.th_loc_aisle').hide();
$('.th_loc_rack').text(label_rack);
}
var loc_hint = advanced
? label_zone.toLowerCase() + ', ' + label_aisle.toLowerCase() + ', ' + label_rack.toLowerCase()
: label_rack.toLowerCase();
$('#search_all, #search_occupied').attr('placeholder',
'Search by warehouse, ' + loc_hint + ', or SKU…');
$('#search_empty').attr('placeholder',
'Search by warehouse or ' + loc_hint + '…');
$('#rackLogModalLabel').text(label_rack + ' Log');
$('#stat_label_racks').text('Total ' + label_rack + 's');
}
// Returns 1 or 3 <td> cells depending on mode
function location_cells(item) {
if (advanced) {
return '<td class="py-3">' + (item.zone || '—') + '</td>'
+ '<td class="py-3">' + (item.aisle || '—') + '</td>'
+ '<td class="py-3">' + (item.rack || '—') + '</td>';
}
return '<td class="py-3">' + (item.rack || '—') + '</td>';
}
// Rack log modal label
function rack_label(item) {
return advanced
? item.warehouse_name + ' ' + item.zone + '-' + item.aisle + '-' + item.rack
: item.warehouse_name + ' ' + item.rack;
}
var search_all = '';
var search_occupied = '';
var search_empty = '';
@@ -334,13 +396,11 @@
: '<span class="badge bg-success">Available</span>';
body += `<tr>
<td class="py-3">${item.warehouse_name}</td>
<td class="py-3">${item.zone}</td>
<td class="py-3">${item.aisle}</td>
<td class="py-3">${item.rack}</td>
` + location_cells(item) + `
<td class="py-3">${item.product_sku || '<span class="text-muted">—</span>'}</td>
<td class="py-3">${item.product_name || '<span class="text-muted">—</span>'}</td>
<td class="py-3">${badge}</td>
<td class="py-3"><a href="javascript:;" onclick="view_rack_log(${item.id}, '${item.warehouse_name} ${item.zone}-${item.aisle}-${item.rack}')"><i class="ti ti-history fs-5"></i></a></td>
<td class="py-3"><a href="javascript:;" onclick="view_rack_log(${item.id}, '${rack_label(item)}')"><i class="ti ti-history fs-5"></i></a></td>
</tr>`;
});
$('table#tbl_all > tbody').html(body);
@@ -377,12 +437,10 @@
$.each(filtered.slice(offset, offset + prop_limit), function(i, item) {
body += `<tr>
<td class="py-3">${item.warehouse_name}</td>
<td class="py-3">${item.zone}</td>
<td class="py-3">${item.aisle}</td>
<td class="py-3">${item.rack}</td>
` + location_cells(item) + `
<td class="py-3">${item.product_sku}</td>
<td class="py-3">${item.product_name || '<span class="text-muted">—</span>'}</td>
<td class="py-3"><a href="javascript:;" onclick="view_rack_log(${item.id}, '${item.warehouse_name} ${item.zone}-${item.aisle}-${item.rack}')"><i class="ti ti-history fs-5"></i></a></td>
<td class="py-3"><a href="javascript:;" onclick="view_rack_log(${item.id}, '${rack_label(item)}')"><i class="ti ti-history fs-5"></i></a></td>
</tr>`;
});
$('table#tbl_occupied > tbody').html(body);
@@ -417,10 +475,8 @@
$.each(filtered.slice(offset, offset + prop_limit), function(i, item) {
body += `<tr>
<td class="py-3">${item.warehouse_name}</td>
<td class="py-3">${item.zone}</td>
<td class="py-3">${item.aisle}</td>
<td class="py-3">${item.rack}</td>
<td class="py-3"><a href="javascript:;" onclick="view_rack_log(${item.id}, '${item.warehouse_name} ${item.zone}-${item.aisle}-${item.rack}')"><i class="ti ti-history fs-5"></i></a></td>
` + location_cells(item) + `
<td class="py-3"><a href="javascript:;" onclick="view_rack_log(${item.id}, '${rack_label(item)}')"><i class="ti ti-history fs-5"></i></a></td>
</tr>`;
});
$('table#tbl_empty > tbody').html(body);
@@ -512,6 +568,7 @@
// ── Boot ─────────────────────────────────────────────────────────────
$(async function() {
try {
await load_location_config(apply_location_labels);
await retrieve_rack_occupancy();
} catch(e) {
console.log(e);
+48 -12
View File
@@ -154,7 +154,7 @@
<th>Date</th>
<th>Type</th>
<th>Warehouse</th>
<th>Location</th>
<th id="th_location">Location</th>
<th>In</th>
<th>Out</th>
<th>Serial</th>
@@ -179,9 +179,13 @@
<script>
var search_term_lot = '';
var lot_log_data = [];
var lot_log_page = 1;
var lot_log_limit = 10;
var lot_log_data = [];
var lot_log_page = 1;
var lot_log_limit = 10;
var advanced = false;
var label_rack = 'Location';
var label_zone = 'Zone';
var label_aisle = 'Aisle';
var type_badge = {
'in': '<span class="badge bg-success">Stock In</span>',
@@ -189,12 +193,43 @@
'transfer': '<span class="badge bg-primary">Transfer</span>',
};
var status_badge = {
var status_badge = {
'ok': '<span class="badge bg-success">OK</span>',
'near': '<span class="badge bg-warning text-dark">Near Expiry</span>',
'critical': '<span class="badge bg-danger">Critical</span>',
'expired': '<span class="badge bg-secondary">Expired</span>',
};
};
function apply_location_labels() {
$('#th_location').text(advanced
? label_zone + ' / ' + label_aisle + ' / ' + label_rack
: label_rack
);
}
function format_location(row) {
if (advanced) {
return [row.zone, row.aisle, row.rack].filter(Boolean).join('-') || '<span class="text-muted">—</span>';
}
return row.rack || '<span class="text-muted">—</span>';
}
function load_company_setting() {
return ajax_request({
url: server_url + 'setting/api/engine/company_setting.php',
autoPrepare: true,
checkRequired: 0,
action: 'read',
onSuccess: function(res) {
var s = res.output || {};
advanced = s.advanced_location == 1;
label_rack = s.location_label_rack || (advanced ? 'Rack' : 'Location');
label_zone = s.location_label_zone || 'Zone';
label_aisle = s.location_label_aisle || 'Aisle';
apply_location_labels();
}
});
}
// ── Fetch ─────────────────────────────────────────────────────────────
@@ -296,7 +331,7 @@
<td class="py-3">${format_date(row.date)}</td>
<td class="py-3">${type_badge[row.type] || row.type}</td>
<td class="py-3">${row.warehouse_name}</td>
<td class="py-3">${row.zone}-${row.aisle}-${row.rack}</td>
<td class="py-3">${format_location(row)}</td>
<td class="py-3 text-success">${parseFloat(row.stock_in) > 0 ? '+' + Number(row.stock_in).toLocaleString() : '<span class="text-muted">—</span>'}</td>
<td class="py-3 text-danger">${parseFloat(row.stock_out) > 0 ? '-' + Number(row.stock_out).toLocaleString() : '<span class="text-muted">—</span>'}</td>
<td class="py-3">${row.serial_number || '<span class="text-muted">—</span>'}</td>
@@ -354,10 +389,11 @@
// ── Boot ─────────────────────────────────────────────────────────────
$(async function() {
try {
await retrieve_product_lot();
} catch(e) {
$(async function() {
try {
await load_company_setting();
await retrieve_product_lot();
} catch(e) {
console.log(e);
}
});
@@ -365,4 +401,4 @@
</body>
</html>
</html>
+55 -13
View File
@@ -134,7 +134,7 @@
<th>Type</th>
<th>SKU</th>
<th>Lot</th>
<th>Location</th>
<th id="th_location">Location</th>
<th>Contact</th>
<th class="text-end">Stock In</th>
<th class="text-end">Stock Out</th>
@@ -170,15 +170,54 @@
<script>
// ── State ─────────────────────────────────────────────────────────────
var sku_queue = []; // ordered list of SKUs returned by summary call
var sku_index = 0; // next SKU to load
var is_loading = false;
var search_params = {}; // warehouse_id, from_month, to_month — fixed per search
var type_badge = {
var sku_index = 0; // next SKU to load
var is_loading = false;
var search_params = {}; // warehouse_id, from_month, to_month — fixed per search
var advanced = false;
var label_rack = 'Location';
var label_zone = 'Zone';
var label_aisle = 'Aisle';
var type_badge = {
'in': '<span class="badge bg-success">In</span>',
'out': '<span class="badge bg-danger">Out</span>',
'transfer': '<span class="badge bg-info text-dark">Transfer</span>',
};
};
function apply_location_labels() {
$('#th_location').text(advanced
? label_zone + ' / ' + label_aisle + ' / ' + label_rack
: label_rack
);
}
function format_location(row) {
if (advanced) {
return [row.zone, row.aisle, row.rack].filter(Boolean).join('-');
}
return row.rack || '';
}
function display_location(row) {
return format_location(row) || '<span class="text-muted">—</span>';
}
function load_company_setting() {
return ajax_request({
url: server_url + 'setting/api/engine/company_setting.php',
autoPrepare: true,
checkRequired: 0,
action: 'read',
onSuccess: function(res) {
var s = res.output || {};
advanced = s.advanced_location == 1;
label_rack = s.location_label_rack || (advanced ? 'Rack' : 'Location');
label_zone = s.location_label_zone || 'Zone';
label_aisle = s.location_label_aisle || 'Aisle';
apply_location_labels();
}
});
}
// ── Default months ────────────────────────────────────────────────────
(function() {
@@ -339,7 +378,7 @@
date: row.date,
type: row.type,
lot: row.lot_number || '',
location: row.zone + '-' + row.aisle + '-' + row.rack,
location: format_location(row),
contact: row.contact_name || '',
stock_in: row.stock_in > 0 ? row.stock_in : '',
stock_out: row.stock_out > 0 ? row.stock_out : '',
@@ -382,7 +421,7 @@
<td class="py-2">${type_badge[row.type] || row.type}</td>
<td class="py-2"><code>${row.product_sku}</code></td>
<td class="py-2">${row.lot_number || '<span class="text-muted">—</span>'}</td>
<td class="py-2">${row.zone}-${row.aisle}-${row.rack}</td>
<td class="py-2">${display_location(row)}</td>
<td class="py-2">${row.contact_name || '<span class="text-muted">—</span>'}</td>
<td class="py-2 text-end text-success">${in_val}</td>
<td class="py-2 text-end text-danger">${out_val}</td>
@@ -473,9 +512,12 @@
$(async function() {
try { await retrieve_warehouse(); } catch(e) { console.log(e); }
});
$(async function() {
try {
await load_company_setting();
await retrieve_warehouse();
} catch(e) { console.log(e); }
});
</script>
<!-- SheetJS — required by AlaSQL for Excel (.xlsx) export -->
@@ -486,4 +528,4 @@
<script src="https://cdnjs.cloudflare.com/ajax/libs/jspdf-autotable/3.8.2/jspdf.plugin.autotable.min.js"></script>
</body>
</html>
</html>