Seal live dashboard event gaps

This commit is contained in:
Thanakorn S
2026-05-25 17:02:52 +07:00
parent 4733c78ac6
commit 4f884177a5
13 changed files with 147 additions and 17 deletions
@@ -3,10 +3,12 @@ session_start();
require_once '../../../assets/utils/db_auth.php';
require_once '../../../assets/utils/classes_ac/PostingWindowGuard.php';
require_once '../../../assets/utils/classes_ac/GlManager.php';
require_once '../../../assets/utils/notify_node.php';
require_role($user_role, ['owner', 'admin']);
$gl_id = (int)($data['gl_id'] ?? 0);
$event_action = $gl_id > 0 ? 'replaced' : 'posted';
$reference = trim((string)($data['reference'] ?? ''));
$description = trim((string)($data['description'] ?? ''));
$journal_date = trim((string)($data['journal_date'] ?? ''));
@@ -73,6 +75,7 @@ try {
$pdo2->commit();
$answer['success'] = 1;
$answer['gl_id'] = $gl_id;
notify_node('gl_posted', gl_posted_payload('manual', (int)$gl_id, $event_action, $lines), $company_id);
} catch (Exception $e) {
if ($pdo2->inTransaction()) $pdo2->rollBack();
$answer['message'] = $e->getMessage();
@@ -1,5 +1,8 @@
<?php
require_once __DIR__ . '/../db_helpers.php';
require_once __DIR__ . '/../notify_node.php';
/**
* StockManager
*
@@ -856,6 +859,11 @@ class StockManager {
(int)$inbound_row['id'], $inbound_row['source'] ?? '', (int)($inbound_row['source_id'] ?? 0), $inbound_row['date'] ?? '');
}
}
$notify_company_id = $this->company_id;
db_after_commit(function() use ($warehouse_id, $type, $notify_company_id) {
notify_node("stock_updated", ["warehouse_id" => $warehouse_id, "type" => $type], $notify_company_id);
});
}
}
+29 -2
View File
@@ -69,17 +69,43 @@ function db_check($sth, &$answer, $options = []) {
// Run a callback after the surrounding dbTransaction() commits. If no managed
// transaction is active, run immediately. Used for non-authoritative side effects
// such as live dashboard notifications.
function db_after_commit(callable $callback): void {
if (!isset($GLOBALS['_db_after_commit_stack']) || empty($GLOBALS['_db_after_commit_stack'])) {
$callback();
return;
}
$idx = count($GLOBALS['_db_after_commit_stack']) - 1;
$GLOBALS['_db_after_commit_stack'][$idx][] = $callback;
}
// transaction wrapper with retry logic for deadlocks
function dbTransaction($pdo, $callback, $maxRetries = 3) {
for ($attempt = 0; $attempt < $maxRetries; $attempt++) {
$GLOBALS['_db_after_commit_stack'][] = [];
try {
$pdo->beginTransaction();
$result = $callback($pdo);
$pdo->commit();
$callbacks = array_pop($GLOBALS['_db_after_commit_stack']);
foreach ($callbacks as $after_commit) {
try {
$after_commit();
} catch (Throwable $e) {
error_log("[AFTER COMMIT ERROR] " . $e->getMessage() . " | " . $e->getFile() . ":" . $e->getLine());
}
}
return $result;
} catch (PDOException $e) {
$pdo->rollBack();
if ($pdo->inTransaction()) $pdo->rollBack();
array_pop($GLOBALS['_db_after_commit_stack']);
if ($e->getCode() == '40001' && $attempt < $maxRetries - 1) {
usleep(600000 * ($attempt + 1));
@@ -90,7 +116,8 @@ function dbTransaction($pdo, $callback, $maxRetries = 3) {
throw $e;
} catch (Exception $e) {
$pdo->rollBack();
if ($pdo->inTransaction()) $pdo->rollBack();
array_pop($GLOBALS['_db_after_commit_stack']);
error_log("[ERROR] " . $e->getMessage() . " | " . $e->getFile() . ":" . $e->getLine());
throw $e;
}
+20
View File
@@ -34,3 +34,23 @@ function notify_node(string $event, array $data, int $company_id): void
curl_exec($ch);
curl_close($ch);
}
function gl_posted_payload(string $doc_type, int $id, string $action, array $lines): array
{
$has_revenue = false;
$has_expense = false;
foreach ($lines as $line) {
$code = (int)($line['account_code'] ?? 0);
if ($code >= 4000 && $code < 5000) $has_revenue = true;
if ($code >= 5000) $has_expense = true;
}
return [
'doc_type' => $doc_type,
'id' => $id,
'action' => $action,
'has_revenue' => $has_revenue,
'has_expense' => $has_expense,
];
}
@@ -0,0 +1,35 @@
<?php
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);
if (!$id) {
$answer['message'] = 'Invalid supplier credit note ID.';
http_response_code(400);
exit(json_encode($answer));
}
try {
dbTransaction($pdo2, function($pdo) use ($id, $company_id) {
$mgr = new InvoiceManager($pdo, $company_id);
$mgr->softDelete($id);
});
$answer['success'] = 1;
$answer['message'] = 'Supplier credit note deleted.';
notify_node('invoice_updated', [], $company_id);
} catch (PDOException $e) {
$answer['message'] = 'Database error, please try again.';
http_response_code(500);
} catch (Exception $e) {
$answer['message'] = $e->getMessage();
http_response_code(400);
}
exit(json_encode($answer));
+1 -1
View File
@@ -69,7 +69,7 @@
$pdo2->beginTransaction();
$gl->post('payment', $new_id, $built['formula_id'], $built['period'], $built['lines'], ['journal_date' => $built['doc_date']]);
$pdo2->commit();
notify_node('gl_posted', ['doc_type' => 'payment', 'id' => $new_id, 'action' => 'posted'], $company_id);
notify_node('gl_posted', gl_posted_payload('payment', (int)$new_id, 'posted', $built['lines'] ?? []), $company_id);
$answer['gl_posted'] = true;
} catch (Exception $e) {
if ($pdo2->inTransaction()) $pdo2->rollBack();
+1 -1
View File
@@ -69,7 +69,7 @@
$pdo2->beginTransaction();
$gl->post('receipt', $new_id, $built['formula_id'], $built['period'], $built['lines'], ['journal_date' => $built['doc_date']]);
$pdo2->commit();
notify_node('gl_posted', ['doc_type' => 'receipt', 'id' => $new_id, 'action' => 'posted'], $company_id);
notify_node('gl_posted', gl_posted_payload('receipt', (int)$new_id, 'posted', $built['lines'] ?? []), $company_id);
$answer['gl_posted'] = true;
} catch (Exception $e) {
if ($pdo2->inTransaction()) $pdo2->rollBack();
+5 -12
View File
@@ -15,7 +15,6 @@ 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']);
@@ -29,24 +28,18 @@ if (!$id || !$warehouse_id || !in_array($type, ['in', 'out', 'transfer'])) {
exit(json_encode($answer));
}
$stock = new StockManager($pdo2, $company_id);
$whMgmt = new WarehouseManager($pdo2, $company_id);
try {
$pdo2->beginTransaction();
$result = $stock->approveStock($id, $warehouse_id, $type, $whMgmt);
$pdo2->commit();
notify_node('stock_updated', ['warehouse_id' => $warehouse_id, 'type' => $type], $company_id);
dbTransaction($pdo2, function($pdo) use ($id, $warehouse_id, $type, $company_id) {
$stock = new StockManager($pdo, $company_id);
$whMgmt = new WarehouseManager($pdo, $company_id);
$stock->approveStock($id, $warehouse_id, $type, $whMgmt);
});
$answer['success'] = 1;
$answer['message'] = 'Stock approved.';
exit(json_encode($answer));
} catch (Exception $e) {
$pdo2->rollBack();
http_response_code(400);
$answer['message'] = $e->getMessage();
exit(json_encode($answer));
+3
View File
@@ -32,6 +32,9 @@
$answer['message'] = 'Return confirmed.';
$answer['auto_cn'] = $auto_cn;
notify_node('return_updated', [], $company_id);
if ($auto_approve) {
notify_node('stock_updated', ['warehouse_id' => 0, 'type' => 'in'], $company_id);
}
if ($auto_cn) {
notify_node('invoice_updated', [], $company_id);
}
+1 -1
View File
@@ -57,7 +57,7 @@
}
$pdo2->commit();
notify_node('gl_posted', ['doc_type' => $doc['doc_type'], 'id' => $id, 'action' => 'posted'], $company_id);
notify_node('gl_posted', gl_posted_payload((string)$doc['doc_type'], (int)$id, $existing ? 'replaced' : 'posted', $built['lines'] ?? []), $company_id);
$answer['gl_posted'] = true;
} catch (Exception $e) {
if ($pdo2->inTransaction()) $pdo2->rollBack();
@@ -6,6 +6,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';
$return_id = (int)($data['id'] ?? 0);
@@ -29,6 +30,9 @@
$answer['success'] = 1;
$answer['message'] = 'Supplier return confirmed.';
if ($auto_approve) {
notify_node('stock_updated', ['warehouse_id' => 0, 'type' => 'out'], $company_id);
}
} catch (PDOException $e) {
$answer['message'] = 'Database error, please try again.';
@@ -0,0 +1,35 @@
<?php
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);
if (!$id) {
$answer['message'] = 'Invalid credit note ID.';
http_response_code(400);
exit(json_encode($answer));
}
try {
dbTransaction($pdo2, function($pdo) use ($id, $company_id) {
$mgr = new InvoiceManager($pdo, $company_id);
$mgr->softDelete($id);
});
$answer['success'] = 1;
$answer['message'] = 'Credit note deleted.';
notify_node('invoice_updated', [], $company_id);
} catch (PDOException $e) {
$answer['message'] = 'Database error, please try again.';
http_response_code(500);
} catch (Exception $e) {
$answer['message'] = $e->getMessage();
http_response_code(400);
}
exit(json_encode($answer));
@@ -4,6 +4,7 @@
require_role($user_role, ['owner', 'admin', 'staff']);
require_once '../../../assets/utils/classes/OrderManager.php';
require_once '../../../assets/utils/classes/InvoiceManager.php';
require_once '../../../assets/utils/notify_node.php';
$order_id = (int)($data['order_id'] ?? 0);
@@ -26,6 +27,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.';