stock aggregate table

This commit is contained in:
Thanakorn S
2026-05-25 13:30:10 +07:00
parent 293097363b
commit ba96de50a1
20 changed files with 725 additions and 62 deletions
+248
View File
@@ -0,0 +1,248 @@
<?php
session_start();
require '../config.php';
require '../include_header.php';
?>
<body>
<?php require '../include_topbar.php'; ?>
<?php require '../include_setting_sidebar.php'; ?>
<main id="content" class="content py-15">
<div class="container-fluid">
<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">ETL Stock Summary Maintenance</h1>
<p class="mb-0">Manages the <code>etl_stock_summary</code> table used by the stock overview dashboard</p>
</div>
</div>
</div>
</div>
<div class="row g-5">
<!-- Automatic Maintenance -->
<div class="col-12 col-lg-6">
<div class="card border-0 shadow-sm h-100" style="position:relative;">
<div class="card-header bg-transparent border-bottom">
<h6 class="mb-0"><i class="ti ti-clock me-2"></i>Automatic Maintenance</h6>
</div>
<div class="card-body">
<p class="text-muted small mb-4">
The system automatically checks and repairs the stock summary table nightly.
Each company is assigned a fixed time slot to distribute load across the day.
</p>
<table class="table table-sm table-borderless mb-0">
<tbody>
<tr>
<td class="text-muted small" style="width:160px;">Scheduled slot</td>
<td><strong id="stat_slot">—</strong></td>
</tr>
<tr>
<td class="text-muted small">Last ran</td>
<td id="stat_last_ran">—</td>
</tr>
<tr>
<td class="text-muted small">Last result</td>
<td id="stat_last_result">—</td>
</tr>
<tr>
<td class="text-muted small">Gaps found</td>
<td id="stat_gaps_found">—</td>
</tr>
<tr>
<td class="text-muted small">Periods rebuilt</td>
<td id="stat_periods_rebuilt">—</td>
</tr>
<tr>
<td class="text-muted small">Next run</td>
<td id="stat_next_run">—</td>
</tr>
<tr><td colspan="2"><hr class="my-2"></td></tr>
<tr>
<td class="text-muted small">Current gaps</td>
<td id="stat_current_gaps">
<span class="spinner-border spinner-border-sm text-secondary"></span>
</td>
</tr>
</tbody>
</table>
</div>
<div class="card-loader-overlay" id="overlay_status">
<div class="spinner-border spinner-border-sm text-primary"></div>
</div>
</div>
</div>
<!-- Manual Rebuild -->
<div class="col-12 col-lg-6">
<div class="card border-0 shadow-sm h-100">
<div class="card-header bg-transparent border-bottom">
<h6 class="mb-0"><i class="ti ti-database me-2"></i>Manual Full Rebuild</h6>
</div>
<div class="card-body d-flex flex-column">
<p class="text-muted small mb-3">
Wipes and rebuilds the stock summary for a specific warehouse, one month at a time from raw stock rows.
Use this after a direct database fix, data import, or if dashboard figures look incorrect.
</p>
<div class="alert alert-warning d-flex gap-2 align-items-start py-2 px-3 small mb-4">
<i class="ti ti-alert-triangle mt-1 flex-shrink-0"></i>
<span>The dashboard stays usable during the rebuild — it falls back to raw data for any month not yet rebuilt.</span>
</div>
<div class="mb-3">
<label class="form-label small text-muted">Warehouse</label>
<select id="sel_warehouse" class="form-select form-select-sm">
<option value="">Loading…</option>
</select>
</div>
<div class="mt-auto">
<button class="btn btn-danger" id="btn_rebuild_full">
<i class="ti ti-database-import me-1"></i>Rebuild ETL for Selected Warehouse
</button>
</div>
</div>
</div>
</div>
</div>
</div>
</main>
<?php require '../include_ending.php'; ?>
<script>
var API = server_url + 'setting/api/engine/stock_maintenance.php';
function pad2(n) { return String(n).padStart(2, '0'); }
function load_status() {
$('#overlay_status').show();
ajax_request({
url: API,
autoPrepare: true, checkRequired: 0, noLoading: true, queueLock: false, action: 'read',
onSuccess: function(res) {
var d = res.output;
$('#stat_slot').text(pad2(d.slot_hour) + ':30 daily');
$('#stat_last_ran').text(d.last_ran || '—');
$('#stat_last_result').html(
d.last_result === 'ok'
? '<span class="badge bg-success-subtle text-success">OK</span>'
: d.last_result
? '<span class="badge bg-danger-subtle text-danger">' + escape_html(d.last_result) + '</span>'
: '—'
);
$('#stat_gaps_found').text(d.last_result ? d.gaps_found : '—');
$('#stat_periods_rebuilt').text(d.last_result ? d.periods_rebuilt : '—');
$('#stat_next_run').text(d.next_run || '—');
$('#overlay_status').hide();
}
});
}
function load_warehouses() {
ajax_request({
url: API,
autoPrepare: true, checkRequired: 0, noLoading: true, queueLock: false, action: 'get_warehouses',
onSuccess: function(res) {
var opts = '<option value="">Select warehouse…</option>';
$.each(res.output || [], function(_, wh) {
opts += '<option value="' + wh.id + '">' + escape_html(wh.warehouse_name) + '</option>';
});
$('#sel_warehouse').html(opts);
}
});
}
$('#btn_rebuild_full').on('click', function() {
var warehouse_id = $('#sel_warehouse').val();
var warehouse_name = $('#sel_warehouse option:selected').text();
if (!warehouse_id) { bootbox.alert('Please select a warehouse first.'); return; }
bootbox.confirm({
title: 'Rebuild ETL — ' + warehouse_name,
message: 'This will rebuild <strong>etl_stock_summary</strong> for <strong>' + escape_html(warehouse_name) + '</strong> one month at a time from raw stock rows. Continue?',
buttons: {
confirm: { label: 'Rebuild', className: 'btn-danger' },
cancel: { label: 'Cancel', className: 'btn-secondary' }
},
callback: function(confirmed) {
if (!confirmed) return;
var btn = $('#btn_rebuild_full').prop('disabled', true);
ajax_request({
url: API,
autoPrepare: true, checkRequired: 0, noLoading: true, queueLock: false,
action: 'get_periods', data: { warehouse_id: warehouse_id },
onSuccess: function(res) {
var periods = res.output || [];
var total = periods.length;
if (total === 0) {
btn.prop('disabled', false);
bootbox.alert('No approved stock periods found for this warehouse.');
return;
}
BatchOverlay.show('Rebuilding Stock ETL — ' + warehouse_name, total, { unit: 'periods', color: '#dc3545' });
function rebuild_next(i) {
if (i >= total) {
BatchOverlay.done(total + ' period(s) rebuilt successfully.', function() {
btn.prop('disabled', false);
});
load_status();
load_current_gaps();
return;
}
BatchOverlay.update(i, total, periods[i]);
ajax_request({
url: API,
autoPrepare: true, checkRequired: 0, noLoading: true, queueLock: false,
action: 'rebuild_period', data: { warehouse_id: warehouse_id, period: periods[i] },
onSuccess: function() { rebuild_next(i + 1); },
onError: function() { rebuild_next(i + 1); }
});
}
rebuild_next(0);
},
onError: function() {
BatchOverlay.hide();
btn.prop('disabled', false);
}
});
}
});
});
function load_current_gaps() {
ajax_request({
url: API,
autoPrepare: true, checkRequired: 0, noLoading: true, queueLock: false, action: 'get_current_gaps',
onSuccess: function(res) {
var n = res.output.current_gaps;
$('#stat_current_gaps').html(
n === 0
? '<span class="badge bg-success-subtle text-success">0 — up to date</span>'
: '<span class="badge bg-warning-subtle text-warning">' + n + ' period(s) need repair</span>'
);
},
onError: function() {
$('#stat_current_gaps').text('—');
}
});
}
load_status();
load_warehouses();
load_current_gaps();
</script>
</body>
</html>