This commit is contained in:
Thanakorn S
2026-04-29 17:04:11 +07:00
parent f6dc9a3278
commit db5c47b6ca
15 changed files with 679 additions and 43 deletions
@@ -1,32 +0,0 @@
<?php
session_start();
require '../../../assets/utils/db_auth.php';
require '../../../assets/utils/classes/ReportManager.php';
$report = new ReportManager($pdo2, $company_id);
$items = $report->getExpiredStock();
$expired = 0;
$critical = 0;
$warning = 0;
$caution = 0;
foreach ($items as $item) {
if ($item['status'] === 'expired') { $expired++; }
if ($item['status'] === 'critical') { $critical++; }
if ($item['status'] === 'warning') { $warning++; }
if ($item['status'] === 'caution') { $caution++; }
}
$answer['output'] = [
'items' => $items,
'expired' => $expired,
'critical' => $critical,
'warning' => $warning,
'caution' => $caution,
];
$answer['success'] = 1;
exit(json_encode($answer));
?>
-362
View File
@@ -1,362 +0,0 @@
<?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?>ics/api/engine_report/expired_stock.php",
autoPrepare: true,
checkRequired: 0,
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.toLowerCase();
var fs = filter_status;
var filtered = all_items.filter(function(item) {
var match_search = !kw ||
item.product_name.toLowerCase().includes(kw) ||
item.product_sku.toLowerCase().includes(kw) ||
(item.lot_number || '').toLowerCase().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>