Reports
This commit is contained in:
@@ -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));
|
||||
?>
|
||||
@@ -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>
|
||||
Reference in New Issue
Block a user