PHP event trigger by NodeJS [stock dashboard]
This commit is contained in:
+192
-1
@@ -276,10 +276,25 @@
|
||||
|
||||
<?php require "../include_ending.php";?>
|
||||
|
||||
<style>
|
||||
@keyframes card-flash {
|
||||
0% { box-shadow: 0 0 0 3px rgba(250,204,21,0.8); }
|
||||
100% { box-shadow: none; }
|
||||
}
|
||||
.card-flash { animation: card-flash 1s ease-out; }
|
||||
</style>
|
||||
|
||||
<script>
|
||||
var movementChart = null;
|
||||
var overallChart = null;
|
||||
|
||||
function flash_card(el) {
|
||||
if (!el) return;
|
||||
el.classList.remove('card-flash');
|
||||
void el.offsetWidth;
|
||||
el.classList.add('card-flash');
|
||||
setTimeout(function() { el.classList.remove('card-flash'); }, 1000);
|
||||
}
|
||||
|
||||
// ── Show all overlays ─────────────────────────────────────────────────
|
||||
function show_overlays() {
|
||||
@@ -479,7 +494,7 @@
|
||||
}
|
||||
|
||||
|
||||
// ── Fetch + render ────────────────────────────────────────────────────
|
||||
// ── Fetch + render (full) ─────────────────────────────────────────────
|
||||
function retrieve_dashboard() {
|
||||
show_overlays();
|
||||
|
||||
@@ -495,6 +510,175 @@
|
||||
});
|
||||
}
|
||||
|
||||
// ── Fetch + render (stock sections only) ─────────────────────────────
|
||||
// Called by socket stock_updated — skips orders/revenue/invoices/returns.
|
||||
function retrieve_stock_sections() {
|
||||
$('#overlay_in, #overlay_out, #overlay_ls, #overlay_chart, #overlay_moved, #overlay_low_list, #overlay_activity, #overlay_overall').show();
|
||||
|
||||
return ajax_request({
|
||||
url: "<?php echo $server_url?>dashboard/api/engine_report/reports_stats.php",
|
||||
autoPrepare: true,
|
||||
checkRequired: 0,
|
||||
noLoading: true,
|
||||
action: 'read',
|
||||
data: {},
|
||||
onSuccess: async function(res) {
|
||||
var d = res.output;
|
||||
|
||||
// Stat cards — stock only
|
||||
$('#stat_total_in').text(format_number(d.total_in, 2));
|
||||
$('#stat_total_out').text(format_number(d.total_out, 2));
|
||||
$('#stat_low_stock_count').text(format_number(d.low_stock));
|
||||
$('#overlay_in, #overlay_out, #overlay_ls').hide();
|
||||
flash_card(document.getElementById('overlay_in').closest('.card'));
|
||||
flash_card(document.getElementById('overlay_out').closest('.card'));
|
||||
flash_card(document.getElementById('overlay_ls').closest('.card'));
|
||||
|
||||
// Stock Movement chart
|
||||
var mv = d.stock_movement;
|
||||
if (movementChart) {
|
||||
await movementChart.updateOptions({ xaxis: { categories: mv.labels || [] } }, false, false);
|
||||
await movementChart.updateSeries([
|
||||
{ name: 'Stock In', data: mv.stock_in || [] },
|
||||
{ name: 'Stock Out', data: mv.stock_out || [] }
|
||||
]);
|
||||
}
|
||||
$('#overlay_chart').hide();
|
||||
flash_card(document.getElementById('overlay_chart').closest('.card'));
|
||||
|
||||
// Stock Health
|
||||
var sh = d.stock_health || { healthy: 0, warning: 0, critical: 0, total: 0 };
|
||||
var sh_count = sh.healthy + sh.warning + sh.critical;
|
||||
var healthy_pct = sh_count > 0 ? Math.round(sh.healthy / sh_count * 100) : 0;
|
||||
var warning_pct = sh_count > 0 ? Math.round(sh.warning / sh_count * 100) : 0;
|
||||
var critical_pct = sh_count > 0 ? Math.round(sh.critical / sh_count * 100) : 0;
|
||||
$('#stat_health_healthy').text(format_number(sh.healthy));
|
||||
$('#stat_health_critical').text(format_number(sh.warning + sh.critical));
|
||||
$('#stat_health_healthy_pct').text(healthy_pct + '%');
|
||||
$('#stat_health_critical_pct').text((warning_pct + critical_pct) + '%');
|
||||
if (overallChart) await overallChart.updateSeries([healthy_pct, warning_pct + critical_pct]);
|
||||
$('#overlay_overall').hide();
|
||||
flash_card(document.getElementById('overlay_overall').closest('.card'));
|
||||
|
||||
// Most Moved
|
||||
var moved_html = '';
|
||||
if (!d.most_moved || d.most_moved.length === 0) {
|
||||
moved_html = '<li class="list-group-item text-center text-muted py-4">No data for this month.</li>';
|
||||
} else {
|
||||
$.each(d.most_moved.slice(0, 5), function(i, item) {
|
||||
moved_html += `<li class="list-group-item d-flex align-items-center gap-3 py-3">
|
||||
<div class="icon-shape icon-sm bg-danger bg-opacity-10 text-danger rounded-2 flex-shrink-0"><i class="ti ti-trending-down"></i></div>
|
||||
<div class="flex-grow-1"><p class="mb-0 fw-semibold">${item.product_name || item.product_sku}</p><small class="text-muted">${item.product_sku}</small></div>
|
||||
<div class="text-end"><span class="fw-bold text-danger">-${Number(item.total_out).toLocaleString()}</span><br><small class="text-success">+${Number(item.total_in).toLocaleString()}</small></div>
|
||||
</li>`;
|
||||
});
|
||||
}
|
||||
$('#list_most_moved').html(moved_html);
|
||||
$('#overlay_moved').hide();
|
||||
flash_card(document.getElementById('overlay_moved').closest('.card'));
|
||||
|
||||
// Low Stock list
|
||||
var low_html = '';
|
||||
if (!d.low_stock_items || d.low_stock_items.length === 0) {
|
||||
low_html = '<li class="list-group-item text-center text-muted py-4">No low stock items.</li>';
|
||||
} else {
|
||||
$.each(d.low_stock_items.slice(0, 5), function(i, item) {
|
||||
var sc = item.status === 'critical' ? 'danger' : 'warning';
|
||||
low_html += `<li class="list-group-item d-flex align-items-center gap-3 py-3">
|
||||
<div class="icon-shape icon-sm bg-${sc} bg-opacity-10 text-${sc} rounded-2 flex-shrink-0"><i class="ti ti-alert-triangle"></i></div>
|
||||
<div class="flex-grow-1"><p class="mb-0 fw-semibold">${item.product_name || item.product_sku}</p><small class="text-muted">${item.product_sku}</small></div>
|
||||
<div class="text-end"><span class="fw-bold text-${sc}">${Number(item.balance).toLocaleString()}</span><br><small class="text-muted">min: ${Number(item.min_stock).toLocaleString()}</small></div>
|
||||
</li>`;
|
||||
});
|
||||
}
|
||||
$('#list_low_stock').html(low_html);
|
||||
$('#overlay_low_list').hide();
|
||||
flash_card(document.getElementById('overlay_low_list').closest('.card'));
|
||||
|
||||
// Recent Activity
|
||||
var act_html = '';
|
||||
if (!d.recent_activity || d.recent_activity.length === 0) {
|
||||
act_html = '<li class="list-group-item text-center text-muted py-4">No recent activity.</li>';
|
||||
} else {
|
||||
$.each(d.recent_activity.slice(0, 5), function(i, item) {
|
||||
var is_in = item.direction === 'in';
|
||||
var color = is_in ? 'success' : 'danger';
|
||||
var icon = is_in ? 'ti-trending-up' : 'ti-trending-down';
|
||||
var sign = is_in ? '+' : '-';
|
||||
act_html += `<li class="list-group-item d-flex align-items-center gap-3 py-3">
|
||||
<div class="icon-shape icon-sm bg-${color} bg-opacity-10 text-${color} rounded-2 flex-shrink-0"><i class="ti ${icon}"></i></div>
|
||||
<div class="flex-grow-1"><p class="mb-0 fw-semibold">${item.product_name}</p><small class="text-muted">${item.warehouse_name}</small></div>
|
||||
<div class="text-end"><span class="fw-bold text-${color}">${sign}${Number(item.qty).toLocaleString()}</span><br><small class="text-muted">${time_ago(item.date)}</small></div>
|
||||
</li>`;
|
||||
});
|
||||
}
|
||||
$('#list_recent_activity').html(act_html);
|
||||
$('#overlay_activity').hide();
|
||||
flash_card(document.getElementById('overlay_activity').closest('.card'));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
// ── Fetch + render (order sections only) ─────────────────────────────
|
||||
function retrieve_order_sections() {
|
||||
$('#overlay_orders').show();
|
||||
return ajax_request({
|
||||
url: "<?php echo $server_url?>dashboard/api/engine_report/reports_stats.php",
|
||||
autoPrepare: true,
|
||||
checkRequired: 0,
|
||||
noLoading: true,
|
||||
action: 'read',
|
||||
data: {},
|
||||
onSuccess: function(res) {
|
||||
var d = res.output;
|
||||
$('#stat_total_orders').text(format_number(d.total_orders));
|
||||
$('#overlay_orders').hide();
|
||||
flash_card(document.getElementById('overlay_orders').closest('.card'));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// ── Fetch + render (invoice sections only) ────────────────────────────
|
||||
function retrieve_invoice_sections() {
|
||||
$('#overlay_revenue, #overlay_ui').show();
|
||||
return ajax_request({
|
||||
url: "<?php echo $server_url?>dashboard/api/engine_report/reports_stats.php",
|
||||
autoPrepare: true,
|
||||
checkRequired: 0,
|
||||
noLoading: true,
|
||||
action: 'read',
|
||||
data: {},
|
||||
onSuccess: function(res) {
|
||||
var d = res.output;
|
||||
$('#stat_total_revenue').text(format_number(d.total_revenue, 2));
|
||||
$('#stat_unpaid_invoices').text(format_number(d.unpaid_invoices));
|
||||
$('#overlay_revenue, #overlay_ui').hide();
|
||||
flash_card(document.getElementById('overlay_revenue').closest('.card'));
|
||||
flash_card(document.getElementById('overlay_ui').closest('.card'));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// ── Fetch + render (return sections only) ─────────────────────────────
|
||||
function retrieve_return_sections() {
|
||||
$('#overlay_pr').show();
|
||||
return ajax_request({
|
||||
url: "<?php echo $server_url?>dashboard/api/engine_report/reports_stats.php",
|
||||
autoPrepare: true,
|
||||
checkRequired: 0,
|
||||
noLoading: true,
|
||||
action: 'read',
|
||||
data: {},
|
||||
onSuccess: function(res) {
|
||||
var d = res.output;
|
||||
$('#stat_pending_returns').text(format_number(d.pending_returns));
|
||||
$('#overlay_pr').hide();
|
||||
flash_card(document.getElementById('overlay_pr').closest('.card'));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
// ── Helpers ───────────────────────────────────────────────────────────
|
||||
function time_ago(dateStr) {
|
||||
@@ -518,6 +702,13 @@
|
||||
}
|
||||
});
|
||||
|
||||
if (window._socket) {
|
||||
window._socket.on('stock_updated', function() { retrieve_stock_sections(); });
|
||||
window._socket.on('order_updated', function() { retrieve_order_sections(); });
|
||||
window._socket.on('invoice_updated', function() { retrieve_invoice_sections(); });
|
||||
window._socket.on('return_updated', function() { retrieve_return_sections(); });
|
||||
}
|
||||
|
||||
|
||||
|
||||
</script>
|
||||
|
||||
@@ -15,6 +15,7 @@ session_start();
|
||||
require_once '../../../assets/utils/db_auth.php';
|
||||
require_once '../../../assets/utils/classes/StockManager.php';
|
||||
require_once '../../../assets/utils/classes/WarehouseManager.php';
|
||||
require_once '../../../assets/utils/notify_node.php';
|
||||
|
||||
require_role($user_role, ['owner', 'admin']);
|
||||
|
||||
@@ -38,6 +39,8 @@ try {
|
||||
|
||||
$pdo2->commit();
|
||||
|
||||
notify_node('stock_updated', ['warehouse_id' => $warehouse_id, 'type' => $type], $company_id);
|
||||
|
||||
$answer['success'] = 1;
|
||||
$answer['message'] = 'Stock approved.';
|
||||
exit(json_encode($answer));
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
require_role($user_role, ['owner', 'admin']);
|
||||
require_once '../../../assets/utils/classes/OrderManager.php';
|
||||
require_once '../../../assets/utils/classes/WarehouseManager.php';
|
||||
require_once '../../../assets/utils/notify_node.php';
|
||||
|
||||
$id = (int)($data['id'] ?? 0);
|
||||
|
||||
@@ -21,6 +22,7 @@
|
||||
|
||||
$answer['success'] = 1;
|
||||
$answer['message'] = 'Order cancelled.';
|
||||
notify_node('order_updated', [], $company_id);
|
||||
|
||||
} catch (PDOException $e) {
|
||||
$answer['message'] = 'Database error, please try again.';
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
require_role($user_role, ['owner', 'admin']);
|
||||
require_once '../../../assets/utils/classes/ReturnManager.php';
|
||||
require_once '../../../assets/utils/classes/WarehouseManager.php';
|
||||
require_once '../../../assets/utils/notify_node.php';
|
||||
|
||||
$id = (int)($data['id'] ?? 0);
|
||||
|
||||
@@ -22,6 +23,7 @@
|
||||
|
||||
$answer['success'] = 1;
|
||||
$answer['message'] = 'Return cancelled.';
|
||||
notify_node('return_updated', [], $company_id);
|
||||
|
||||
} catch (PDOException $e) {
|
||||
$answer['message'] = 'Database error, please try again.';
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
require_once '../../../assets/utils/classes/WarehouseManager.php';
|
||||
require_once '../../../assets/utils/classes/InvoiceManager.php';
|
||||
require_once '../../../assets/utils/classes/CompanySettingManager.php';
|
||||
require_once '../../../assets/utils/notify_node.php';
|
||||
|
||||
$id = (int)($data['id'] ?? 0);
|
||||
|
||||
@@ -35,6 +36,10 @@
|
||||
$answer['message'] = 'Order confirmed.';
|
||||
$answer['auto_approved'] = $auto_approve;
|
||||
$answer['auto_invoice'] = $auto_invoice;
|
||||
notify_node('order_updated', [], $company_id);
|
||||
if ($auto_invoice) {
|
||||
notify_node('invoice_updated', [], $company_id);
|
||||
}
|
||||
|
||||
} catch (PDOException $e) {
|
||||
$answer['message'] = 'Database error, please try again.';
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
require_once '../../../assets/utils/classes/InvoiceManager.php';
|
||||
require_once '../../../assets/utils/classes/WarehouseManager.php';
|
||||
require_once '../../../assets/utils/classes/CompanySettingManager.php';
|
||||
require_once '../../../assets/utils/notify_node.php';
|
||||
|
||||
$id = (int)($data['id'] ?? 0);
|
||||
|
||||
@@ -30,6 +31,10 @@
|
||||
$answer['success'] = 1;
|
||||
$answer['message'] = 'Return confirmed.';
|
||||
$answer['auto_cn'] = $auto_cn;
|
||||
notify_node('return_updated', [], $company_id);
|
||||
if ($auto_cn) {
|
||||
notify_node('invoice_updated', [], $company_id);
|
||||
}
|
||||
|
||||
} catch (PDOException $e) {
|
||||
$answer['message'] = 'Database error, please try again.';
|
||||
|
||||
@@ -3,6 +3,7 @@ session_start();
|
||||
require_once '../../../assets/utils/db_auth.php';
|
||||
require_role($user_role, ['owner', 'admin']);
|
||||
require_once '../../../assets/utils/classes/InvoiceManager.php';
|
||||
require_once '../../../assets/utils/notify_node.php';
|
||||
|
||||
$id = (int)($data['id'] ?? 0);
|
||||
|
||||
@@ -20,6 +21,7 @@ try {
|
||||
|
||||
$answer['success'] = 1;
|
||||
$answer['message'] = 'Document deleted.';
|
||||
notify_node('invoice_updated', [], $company_id);
|
||||
|
||||
} catch (PDOException $e) {
|
||||
$answer['message'] = 'Database error, please try again.';
|
||||
|
||||
@@ -4,6 +4,7 @@ require_once '../../../assets/utils/db_auth.php';
|
||||
require_role($user_role, ['owner', 'admin']);
|
||||
require_once '../../../assets/utils/classes/OrderManager.php';
|
||||
require_once '../../../assets/utils/classes/WarehouseManager.php';
|
||||
require_once '../../../assets/utils/notify_node.php';
|
||||
|
||||
$id = (int)($data['id'] ?? 0);
|
||||
|
||||
@@ -21,6 +22,7 @@ try {
|
||||
|
||||
$answer['success'] = 1;
|
||||
$answer['message'] = 'Sales order deleted.';
|
||||
notify_node('order_updated', [], $company_id);
|
||||
|
||||
} catch (PDOException $e) {
|
||||
$answer['message'] = 'Database error, please try again.';
|
||||
|
||||
@@ -4,6 +4,7 @@ require_once '../../../assets/utils/db_auth.php';
|
||||
require_role($user_role, ['owner', 'admin']);
|
||||
require_once '../../../assets/utils/classes/ReturnManager.php';
|
||||
require_once '../../../assets/utils/classes/WarehouseManager.php';
|
||||
require_once '../../../assets/utils/notify_node.php';
|
||||
|
||||
$id = (int)($data['id'] ?? 0);
|
||||
|
||||
@@ -21,6 +22,7 @@ try {
|
||||
|
||||
$answer['success'] = 1;
|
||||
$answer['message'] = 'Return deleted.';
|
||||
notify_node('return_updated', [], $company_id);
|
||||
|
||||
} catch (PDOException $e) {
|
||||
$answer['message'] = 'Database error, please try again.';
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
require_role($user_role, ['owner', 'admin', 'staff']);
|
||||
require_once '../../../assets/utils/classes/UsageGuard.php';
|
||||
require_once '../../../assets/utils/classes/InvoiceManager.php';
|
||||
require_once '../../../assets/utils/notify_node.php';
|
||||
|
||||
try {
|
||||
$action = $data['action'] ?? 'update';
|
||||
@@ -29,6 +30,7 @@
|
||||
if ($is_new) {
|
||||
(new UsageGuard($pdo1, $company_id, $packages))->increment();
|
||||
}
|
||||
notify_node('invoice_updated', [], $company_id);
|
||||
|
||||
} catch (PDOException $e) {
|
||||
$answer['message'] = 'Database error, please try again.';
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
require_role($user_role, ['owner', 'admin', 'staff']);
|
||||
require_once '../../../assets/utils/classes/UsageGuard.php';
|
||||
require_once '../../../assets/utils/classes/OrderManager.php';
|
||||
require_once '../../../assets/utils/notify_node.php';
|
||||
|
||||
// Decode items JSON string back to array
|
||||
$data['items'] = json_decode($data['items'] ?? '[]', true) ?: [];
|
||||
@@ -28,6 +29,7 @@
|
||||
$answer['new_id'] = $new_id;
|
||||
(new UsageGuard($pdo1, $company_id, $packages))->increment();
|
||||
}
|
||||
notify_node('order_updated', [], $company_id);
|
||||
|
||||
} catch (PDOException $e) {
|
||||
$answer['message'] = 'Database error, please try again.';
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
require_once '../../../assets/utils/db_auth.php';
|
||||
require_role($user_role, ['owner', 'admin', 'staff']);
|
||||
require_once '../../../assets/utils/classes/ReturnManager.php';
|
||||
require_once '../../../assets/utils/notify_node.php';
|
||||
|
||||
$action = $data['action'] ?? 'save';
|
||||
$data['items'] = json_decode($data['items'] ?? '[]', true) ?: [];
|
||||
@@ -30,6 +31,7 @@
|
||||
if ($new_id) {
|
||||
$answer['new_id'] = $new_id;
|
||||
}
|
||||
notify_node('return_updated', [], $company_id);
|
||||
|
||||
} catch (PDOException $e) {
|
||||
$answer['message'] = 'Database error, please try again.';
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
require_once '../../../assets/utils/db_auth.php';
|
||||
require_role($user_role, ['owner', 'admin', 'staff']);
|
||||
require_once '../../../assets/utils/classes/InvoiceManager.php';
|
||||
require_once '../../../assets/utils/notify_node.php';
|
||||
|
||||
$order_id = (int)($data['order_id'] ?? 0);
|
||||
|
||||
@@ -22,6 +23,7 @@
|
||||
|
||||
$answer['success'] = 1;
|
||||
$answer['new_id'] = $new_id;
|
||||
notify_node('invoice_updated', [], $company_id);
|
||||
|
||||
} catch (PDOException $e) {
|
||||
$answer['message'] = 'Database error, please try again.';
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
require_once '../../../assets/utils/db_auth.php';
|
||||
require_role($user_role, ['owner', 'admin', 'staff']);
|
||||
require_once '../../../assets/utils/classes/InvoiceManager.php';
|
||||
require_once '../../../assets/utils/notify_node.php';
|
||||
|
||||
$id = (int)($data['invoice_id'] ?? 0);
|
||||
$status = (int)($data['status'] ?? 0);
|
||||
@@ -29,6 +30,7 @@
|
||||
|
||||
$answer['success'] = 1;
|
||||
$answer['message'] = 'Invoice status updated.';
|
||||
notify_node('invoice_updated', [], $company_id);
|
||||
|
||||
} catch (PDOException $e) {
|
||||
$answer['message'] = 'Database error, please try again.';
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
require_once '../../../assets/utils/db_auth.php';
|
||||
require_role($user_role, ['owner', 'admin']);
|
||||
require_once '../../../assets/utils/classes/InvoiceManager.php';
|
||||
require_once '../../../assets/utils/notify_node.php';
|
||||
|
||||
$id = (int)($data['id'] ?? 0);
|
||||
|
||||
@@ -20,6 +21,7 @@
|
||||
|
||||
$answer['success'] = 1;
|
||||
$answer['message'] = 'Invoice voided.';
|
||||
notify_node('invoice_updated', [], $company_id);
|
||||
|
||||
} catch (PDOException $e) {
|
||||
$answer['message'] = 'Database error, please try again.';
|
||||
|
||||
Reference in New Issue
Block a user