Files
wms-app/app/reports/expired_stock.php
T

365 lines
13 KiB
PHP
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<?php
session_start();
require '../config.php';
require '../include_header.php';
?>
<body>
<?php require '../include_topbar.php';?>
<?php require '../include_sidebar.php';?>
<!-- MAIN CONTENT -->
<main id="content" class="content py-15">
<div class="container-fluid">
<!-- Page header -->
<div class="row">
<div class="col-12">
<div class="d-flex flex-column flex-md-row justify-content-between align-items-md-center mb-6 gap-3">
<div>
<h1 class="fs-3 mb-1">Expired / Near Expiry Products</h1>
<p class="mb-0">Monitor and manage products approaching or past expiry dates</p>
</div>
<div class="d-flex align-items-center gap-3">
<label class="mb-0 text-nowrap fw-semibold">Warehouse</label>
<select id="warehouse" class="form-select" style="min-width:200px;" required>
<option value="">Select warehouse...</option>
</select>
</div>
</div>
</div>
</div>
<!-- Main content -->
<div id="overview_content">
<!-- Summary stats -->
<div class="row mb-5 g-5">
<div class="col-xl-3 col-md-6 col-12">
<div class="card bg-danger bg-opacity-10 border-danger border-opacity-25">
<div class="card-body p-5">
<span class="icon-shape icon-md bg-danger text-white rounded-2">
<i class="ti ti-trash fs-4"></i>
</span>
<div class="d-flex flex-column mt-3">
<span class="fs-2 fw-bold mb-0 d-block" id="stat_expired">—</span>
<span class="fs-5">Expired Products</span>
<small class="text-muted">Requires immediate action</small>
</div>
</div>
</div>
</div>
<div class="col-xl-3 col-md-6 col-12">
<div class="card bg-danger bg-opacity-10 border-danger border-opacity-25">
<div class="card-body p-5">
<span class="icon-shape icon-md bg-danger text-white rounded-2">
<i class="ti ti-alert-triangle fs-4"></i>
</span>
<div class="d-flex flex-column mt-3">
<span class="fs-2 fw-bold mb-0 d-block" id="stat_critical">—</span>
<span class="fs-5">Critical (≤7 days)</span>
<small class="text-muted">Expiring within a week</small>
</div>
</div>
</div>
</div>
<div class="col-xl-3 col-md-6 col-12">
<div class="card bg-warning bg-opacity-10 border-warning border-opacity-25">
<div class="card-body p-5">
<span class="icon-shape icon-md bg-warning text-white rounded-2">
<i class="ti ti-clock fs-4"></i>
</span>
<div class="d-flex flex-column mt-3">
<span class="fs-2 fw-bold mb-0 d-block" id="stat_warning">—</span>
<span class="fs-5">Warning (8–14 days)</span>
<small class="text-muted">Expiring in 2 weeks</small>
</div>
</div>
</div>
</div>
<div class="col-xl-3 col-md-6 col-12">
<div class="card bg-warning bg-opacity-10 border-warning border-opacity-25">
<div class="card-body p-5">
<span class="icon-shape icon-md bg-warning text-white rounded-2">
<i class="ti ti-calendar-clock fs-4"></i>
</span>
<div class="d-flex flex-column mt-3">
<span class="fs-2 fw-bold mb-0 d-block" id="stat_caution">—</span>
<span class="fs-5">Caution (15–30 days)</span>
<small class="text-muted">Monitor closely</small>
</div>
</div>
</div>
</div>
</div>
<!-- Filter -->
<div class="row g-5 mb-5">
<div class="col-12">
<div class="card">
<div class="card-body p-5">
<h2 class="fs-4 mb-4">Filter Products</h2>
<div class="row g-5">
<div class="col-lg-7">
<div class="position-relative">
<input type="search" id="search_expired" class="form-control ps-9"
placeholder="Search by product name, SKU, or lot number…">
<span class="position-absolute top-25 ms-4">
<i class="ti ti-search text-muted"></i>
</span>
</div>
</div>
<div class="col-lg-5">
<select class="form-select" id="filter_status">
<option value="">All Status</option>
<option value="expired">Expired</option>
<option value="critical">Critical</option>
<option value="warning">Warning</option>
<option value="caution">Caution</option>
</select>
</div>
</div>
</div>
</div>
</div>
</div>
<!-- Expired table -->
<div class="row g-5 mb-5">
<div class="col-12">
<div class="card">
<div class="card-body p-5">
<h2 class="fs-4 mb-2">Expired Products</h2>
<p class="mb-4">Products that have passed their expiry date and need to be removed</p>
<div class="table-responsive">
<table class="table mb-0 text-nowrap table-hover table-centered" id="tbl_expired">
<thead class="table-danger border-light">
<tr>
<th>Product</th>
<th>SKU</th>
<th>Lot Number</th>
<th>Location</th>
<th>Expiry Date</th>
<th>Days Overdue</th>
<th>Quantity</th>
<th>Status</th>
</tr>
</thead>
<tbody></tbody>
</table>
</div>
</div>
</div>
</div>
</div>
<!-- Near expiry table -->
<div class="row g-5 mb-5">
<div class="col-12">
<div class="card">
<div class="card-body p-5">
<h2 class="fs-4 mb-2">Near Expiry Products</h2>
<p class="mb-4">Products approaching their expiry date that may need attention</p>
<div class="table-responsive">
<table class="table mb-0 text-nowrap table-hover table-centered" id="tbl_near_expiry">
<thead class="table-warning border-light">
<tr>
<th>Product</th>
<th>SKU</th>
<th>Lot Number</th>
<th>Location</th>
<th>Expiry Date</th>
<th>Days Remaining</th>
<th>Quantity</th>
<th>Status</th>
</tr>
</thead>
<tbody></tbody>
</table>
</div>
</div>
</div>
</div>
</div>
</div><!-- /overview_content -->
</div>
</main>
<?php require "../include_ending.php";?>
<script>
var all_items = [];
var search_term = '';
var filter_status = '';
// ── Fetch + populate ─────────────────────────────────────────────────
function retrieve_expired_stock() {
var warehouse_id = $('#warehouse').val();
if (!warehouse_id) return;
return ajax_request({
url: "<?php echo $server_url?>reports/api/engine_report/expired_stock.php",
autoPrepare: true,
checkRequired: 0,
data: {
warehouse_id: warehouse_id
},
action: 'read',
debugMode: 0,
onSuccess: function(res) {
var data = res.output;
$('#stat_expired').text(data.expired);
$('#stat_critical').text(data.critical);
$('#stat_warning').text(data.warning);
$('#stat_caution').text(data.caution);
all_items = data.items || [];
render_tables();
}
});
}
// ── Render both tables from filtered data ────────────────────────────
function render_tables() {
var kw = search_term;
var fs = filter_status;
var filtered = all_items.filter(function(item) {
var match_search = !kw ||
item.product_name || ''.includes(kw) ||
item.product_sku || ''.includes(kw) ||
item.lot_number || ''.includes(kw);
var match_status = !fs || item.status === fs;
return match_search && match_status;
});
render_expired(filtered.filter(function(i) { return i.status === 'expired'; }));
render_near_expiry(filtered.filter(function(i) { return i.status !== 'expired'; }));
}
function status_badge(status) {
var map = {
expired: '<span class="badge bg-danger">Expired</span>',
critical: '<span class="badge bg-danger">Critical (≤7 days)</span>',
warning: '<span class="badge bg-warning text-dark">Warning (8–14 days)</span>',
caution: '<span class="badge bg-warning text-dark">Caution (15–30 days)</span>',
};
return map[status] || '';
}
function render_expired(items) {
if (items.length === 0) {
$('table#tbl_expired tbody').html(
'<tr><td colspan="8" class="text-center py-5 text-muted">No expired products.</td></tr>'
);
return;
}
var body = '';
$.each(items, function(i, item) {
body += `<tr>
<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">${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>
<td class="py-3">${status_badge(item.status)}</td>
</tr>`;
});
$('table#tbl_expired tbody').html(body);
}
function render_near_expiry(items) {
if (items.length === 0) {
$('table#tbl_near_expiry tbody').html(
'<tr><td colspan="8" class="text-center py-5 text-muted">No near-expiry products.</td></tr>'
);
return;
}
var body = '';
$.each(items, function(i, item) {
body += `<tr>
<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">${item.expiry_date}</td>
<td class="py-3 text-warning fw-semibold">${item.days_remaining} days</td>
<td class="py-3">${item.quantity}</td>
<td class="py-3">${status_badge(item.status)}</td>
</tr>`;
});
$('table#tbl_near_expiry tbody').html(body);
}
// ── Search + filter ──────────────────────────────────────────────────
function debounce(fn, ms) {
var t;
return function() { clearTimeout(t); t = setTimeout(fn, ms); };
}
$('#search_expired').on('input', debounce(function() {
search_term = $(this).val().trim();
render_tables();
}, 200));
$('#filter_status').on('change', function() {
filter_status = $(this).val();
render_tables();
});
// ── Load warehouse dropdown ───────────────────────────────────────────
function retrieve_warehouse() {
return ajax_request({
url: "<?php echo $server_url?>ics/api/engine/retrieve_warehouse.php",
autoPrepare: true,
checkRequired: 0,
action: 'read',
onSuccess: function(res) {
var option = '';
$.each(res.output, function(key, item) {
option += `<option value='${item.id}'>${item.warehouse_name}</option>`;
});
$('#warehouse').html(option);
// Auto-select first warehouse on page load
if (res.output && res.output.length > 0) {
$('#warehouse').val(res.output[0].id);
retrieve_expired_stock();
}
}
});
}
// ── Boot ─────────────────────────────────────────────────────────────
$(async function() {
try {
await retrieve_warehouse();
} catch(e) {
console.log(e);
}
});
$('#warehouse').on('change', function() {
retrieve_expired_stock();
});
</script>
</body>
</html>