diff --git a/app/accounting/api/engine/manage_account.php b/app/accounting/api/engine/manage_account.php index 39e0845..56e1b32 100644 --- a/app/accounting/api/engine/manage_account.php +++ b/app/accounting/api/engine/manage_account.php @@ -4,7 +4,8 @@ require '../../../assets/utils/db_auth.php'; require '../../../assets/utils/classes_ac/ChartOfAccounts.php'; if (empty($data['account_code']) || empty($data['account_name']) || empty($data['account_type'])) { - exit(json_encode(['message' => 'Account code, name, and type are required'])); + $answer['message'] = 'Account code, name, and type are required'; + exit(json_encode($answer)); } $coa = new ChartOfAccounts($pdo2, $company_id); diff --git a/app/accounting/api/engine/manage_department.php b/app/accounting/api/engine/manage_department.php index 3fa331e..e496a42 100644 --- a/app/accounting/api/engine/manage_department.php +++ b/app/accounting/api/engine/manage_department.php @@ -4,7 +4,8 @@ require '../../../assets/utils/db_auth.php'; require '../../../assets/utils/classes_ac/DepartmentManager.php'; if (empty($data['dept_code']) || empty($data['dept_name'])) { - exit(json_encode(['message' => 'Department code and name are required'])); + $answer['message'] = 'Department code and name are required'; + exit(json_encode($answer)); } $dept = new DepartmentManager($pdo2, $company_id); diff --git a/app/accounting/api/engine/remove_account.php b/app/accounting/api/engine/remove_account.php index 7af9ac2..7bab0aa 100644 --- a/app/accounting/api/engine/remove_account.php +++ b/app/accounting/api/engine/remove_account.php @@ -5,7 +5,8 @@ require '../../../assets/utils/classes_ac/ChartOfAccounts.php'; $id = (int)($data['id'] ?? 0); if (!$id) { - exit(json_encode(['message' => 'Missing id'])); + $answer['message'] = 'Missing id'; + exit(json_encode($answer)); } $coa = new ChartOfAccounts($pdo2, $company_id); diff --git a/app/accounting/api/engine/remove_department.php b/app/accounting/api/engine/remove_department.php index f0a1b06..70de3cd 100644 --- a/app/accounting/api/engine/remove_department.php +++ b/app/accounting/api/engine/remove_department.php @@ -4,7 +4,7 @@ require '../../../assets/utils/db_auth.php'; require '../../../assets/utils/classes_ac/DepartmentManager.php'; $id = (int)($data['id'] ?? 0); -if (!$id) exit(json_encode(['message' => 'Missing id'])); +if (!$id) { $answer['message'] = 'Missing id'; exit(json_encode($answer)); } $dept = new DepartmentManager($pdo2, $company_id); $dept->delete($id); diff --git a/app/accounting/api/engine/retrieve_account.php b/app/accounting/api/engine/retrieve_account.php index 0b94c86..25797e1 100644 --- a/app/accounting/api/engine/retrieve_account.php +++ b/app/accounting/api/engine/retrieve_account.php @@ -5,13 +5,15 @@ require '../../../assets/utils/classes_ac/ChartOfAccounts.php'; $id = (int)($data['id'] ?? 0); if (!$id) { - exit(json_encode(['message' => 'Missing id'])); + $answer['message'] = 'Missing id'; + exit(json_encode($answer)); } $coa = new ChartOfAccounts($pdo2, $company_id); $row = $coa->getById($id); if (!$row) { - exit(json_encode(['message' => 'Account not found'])); + $answer['message'] = 'Account not found'; + exit(json_encode($answer)); } $answer['output'] = $row; diff --git a/app/accounting/api/engine/retrieve_department.php b/app/accounting/api/engine/retrieve_department.php index 41997be..eaa47cf 100644 --- a/app/accounting/api/engine/retrieve_department.php +++ b/app/accounting/api/engine/retrieve_department.php @@ -4,11 +4,11 @@ require '../../../assets/utils/db_auth.php'; require '../../../assets/utils/classes_ac/DepartmentManager.php'; $id = (int)($data['id'] ?? 0); -if (!$id) exit(json_encode(['message' => 'Missing id'])); +if (!$id) { $answer['message'] = 'Missing id'; exit(json_encode($answer)); } $dept = new DepartmentManager($pdo2, $company_id); $row = $dept->getById($id); -if (!$row) exit(json_encode(['message' => 'Department not found'])); +if (!$row) { $answer['message'] = 'Department not found'; exit(json_encode($answer)); } $answer['output'] = $row; $answer['success'] = 1; diff --git a/app/expense/api/engine/convert_purchase_request.php b/app/expense/api/engine/convert_purchase_request.php index 35fa63e..323aca0 100644 --- a/app/expense/api/engine/convert_purchase_request.php +++ b/app/expense/api/engine/convert_purchase_request.php @@ -12,10 +12,12 @@ $discount = (float)($data['discount'] ?? 0); $shipping_fee = (float)($data['shipping_fee'] ?? 0); if (!$request_id) { - exit(json_encode(['success' => 0, 'message' => 'Purchase request ID is required.'])); + $answer['message'] = 'Purchase request ID is required.'; + exit(json_encode($answer)); } if (!$contact_id) { - exit(json_encode(['success' => 0, 'message' => 'Supplier (contact_id) is required for the PO.'])); + $answer['message'] = 'Supplier (contact_id) is required for the PO.'; + exit(json_encode($answer)); } $prm = new PurchaseRequestManager($pdo2, $company_id); @@ -23,16 +25,19 @@ $prm = new PurchaseRequestManager($pdo2, $company_id); // Load PR — must be Approved $pr = $prm->getById($request_id); if (!$pr || (int)$pr['status'] !== 2) { - exit(json_encode(['success' => 0, 'message' => 'Purchase request not found or not in Approved status.'])); + $answer['message'] = 'Purchase request not found or not in Approved status.'; + exit(json_encode($answer)); } $pr_items = $pr['items']; if (empty($pr_items)) { - exit(json_encode(['success' => 0, 'message' => 'Purchase request has no items.'])); + $answer['message'] = 'Purchase request has no items.'; + exit(json_encode($answer)); } if ((float)$pr['total_remaining'] <= 0.000001) { - exit(json_encode(['success' => 0, 'message' => 'Purchase request is already fully converted.'])); + $answer['message'] = 'Purchase request is already fully converted.'; + exit(json_encode($answer)); } // Index by item_id for validation @@ -62,7 +67,8 @@ foreach ($convert_items as $ci) { if ($qty <= 0) continue; if (!isset($pr_by_id[$item_id])) { - exit(json_encode(['success' => 0, 'message' => "Item #{$item_id} not found in this purchase request."])); + $answer['message'] = "Item #{$item_id} not found in this purchase request."; + exit(json_encode($answer)); } $pi = $pr_by_id[$item_id]; @@ -70,10 +76,8 @@ foreach ($convert_items as $ci) { if ($qty - $remaining > 0.000001) { $name = $pi['product_name'] ?: $pi['product_sku']; - exit(json_encode([ - 'success' => 0, - 'message' => "Cannot convert {$qty} for \"{$name}\": only {$remaining} remaining.", - ])); + $answer['message'] = "Cannot convert {$qty} for \"{$name}\": only {$remaining} remaining."; + exit(json_encode($answer)); } $unit_price = (float)$pi['unit_price']; @@ -95,7 +99,8 @@ foreach ($convert_items as $ci) { } if (empty($po_items)) { - exit(json_encode(['success' => 0, 'message' => 'No valid items to convert.'])); + $answer['message'] = 'No valid items to convert.'; + exit(json_encode($answer)); } $po_data = [ diff --git a/app/expense/api/engine/retrieve_purchase_request.php b/app/expense/api/engine/retrieve_purchase_request.php index 7bb4e43..37fd452 100644 --- a/app/expense/api/engine/retrieve_purchase_request.php +++ b/app/expense/api/engine/retrieve_purchase_request.php @@ -10,7 +10,8 @@ if ($id > 0) { $row = $prm->getById($id); if (!$row) { http_response_code(404); - exit(json_encode(['success' => 0, 'message' => 'Purchase request not found.'])); + $answer['message'] = 'Purchase request not found.'; + exit(json_encode($answer)); } $answer['output'] = $row; } else { diff --git a/app/expense/api/engine/update_purchase_request_status.php b/app/expense/api/engine/update_purchase_request_status.php index f1b075e..349b701 100644 --- a/app/expense/api/engine/update_purchase_request_status.php +++ b/app/expense/api/engine/update_purchase_request_status.php @@ -8,7 +8,8 @@ $id = (int)($data['id'] ?? 0); $action = $data['action_type'] ?? ''; if (!$id || !$action) { - exit(json_encode(['success' => 0, 'message' => 'ID and action are required.'])); + $answer['message'] = 'ID and action are required.'; + exit(json_encode($answer)); } $prm = new PurchaseRequestManager($pdo2, $company_id); diff --git a/app/ics/api/engine/approve_stock.php b/app/ics/api/engine/approve_stock.php index 3c11df8..496a403 100644 --- a/app/ics/api/engine/approve_stock.php +++ b/app/ics/api/engine/approve_stock.php @@ -24,7 +24,8 @@ $type = $data['type'] ?? ''; if (!$id || !$warehouse_id || !in_array($type, ['in', 'out', 'transfer'])) { http_response_code(400); - exit(json_encode(['success' => 0, 'message' => 'Invalid parameters.'])); + $answer['message'] = 'Invalid parameters.'; + exit(json_encode($answer)); } $stock = new StockManager($pdo2, $company_id); @@ -44,5 +45,6 @@ try { } catch (Exception $e) { $pdo2->rollBack(); http_response_code(400); - exit(json_encode(['success' => 0, 'message' => $e->getMessage()])); + $answer['message'] = $e->getMessage(); + exit(json_encode($answer)); } \ No newline at end of file diff --git a/app/login/api/engine/login_otp.php b/app/login/api/engine/login_otp.php index 4ff9ae1..e692d2a 100644 --- a/app/login/api/engine/login_otp.php +++ b/app/login/api/engine/login_otp.php @@ -141,7 +141,7 @@ if (password_verify(trim($data["password"]), $temp["password"])) { $verify_url = $base_url . '/login/verify.php?token=' . $token; try { - require_once $include_url . 'assets/utils/module/mailer.php'; + require_once '../../../assets/utils/module/mailer.php'; $mailer = new mailer(['pdo1' => $pdo1]); $mailer->send_email([ 'company_id' => 0, diff --git a/app/login/api/engine/onboarding.php b/app/login/api/engine/onboarding.php index 92809a9..d5b0887 100644 --- a/app/login/api/engine/onboarding.php +++ b/app/login/api/engine/onboarding.php @@ -73,7 +73,8 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') { $csrf = $_SERVER['HTTP_X_CSRF_TOKEN'] ?? ''; if (empty($csrf) || $csrf !== ($_SESSION['csrf_token'] ?? '')) { http_response_code(403); - exit(json_encode(['message' => 'Invalid request.'])); + $answer['message'] = 'Invalid request.'; + exit(json_encode($answer)); } } @@ -142,7 +143,7 @@ try { // Sends a test email to the onboarding user's registered address. // If the mailer throws or exits, no DB records have been created yet, // so the user can correct their SMTP settings and retry cleanly. - require_once $include_url . 'assets/utils/module/mailer.php'; + require_once '../../../assets/utils/module/mailer.php'; $mailer = new mailer(['pdo1' => $pdo1]); $mailer->send_email([ diff --git a/app/login/api/engine/register.php b/app/login/api/engine/register.php index 133a91e..16e1a9d 100644 --- a/app/login/api/engine/register.php +++ b/app/login/api/engine/register.php @@ -60,7 +60,8 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') { $csrf = $_SERVER['HTTP_X_CSRF_TOKEN'] ?? ''; if (empty($csrf) || $csrf !== ($_SESSION['csrf_token'] ?? '')) { http_response_code(403); - exit(json_encode(['message' => 'Invalid request.'])); + $answer['message'] = 'Invalid request.'; + exit(json_encode($answer)); } } @@ -177,7 +178,7 @@ try { // Uses $SMTP from config.php (system-level, not company SMTP) because the // user does not have a company yet at registration time. // If the mailer fails it exits internally with its own error JSON response. - require_once $include_url . 'assets/utils/module/mailer.php'; + require_once '../../../assets/utils/module/mailer.php'; $mailer = new mailer(['pdo1' => $pdo1]); $mailer->send_email([ diff --git a/app/login/api/engine/request_reset_otp.php b/app/login/api/engine/request_reset_otp.php index 48df661..c52b36f 100644 --- a/app/login/api/engine/request_reset_otp.php +++ b/app/login/api/engine/request_reset_otp.php @@ -9,7 +9,8 @@ $identifier = strtolower(trim($data['identifier'] ?? '')); if (!$identifier) { http_response_code(422); - exit(json_encode(['success' => 0, 'message' => 'Please enter your username or email.'])); + $answer['message'] = 'Please enter your username or email.'; + exit(json_encode($answer)); } $sth = $pdo1->prepare( @@ -20,13 +21,14 @@ $user = $sth->fetch(PDO::FETCH_ASSOC); if (!$user) { http_response_code(404); - exit(json_encode(['success' => 0, 'message' => 'No account found with that username or email.'])); + $answer['message'] = 'No account found with that username or email.'; + exit(json_encode($answer)); } $user_id = (int)$user['user_id']; $company_id = (int)($user['default_company'] ?? 0); -require_once $include_url . 'assets/utils/classes/PasswordResetManager.php'; +require_once '../../../assets/utils/classes/PasswordResetManager.php'; $manager = new PasswordResetManager($pdo1, $pdo2, $include_url, $SMTP, $pinkey); $manager->handleRequestOtp($user_id, $company_id); diff --git a/app/login/api/engine/reset_password_otp.php b/app/login/api/engine/reset_password_otp.php index 8ee3230..1fa85c1 100644 --- a/app/login/api/engine/reset_password_otp.php +++ b/app/login/api/engine/reset_password_otp.php @@ -6,12 +6,13 @@ require '../../../assets/utils/db_auth.php'; if (empty($_SESSION['reset_user_id'])) { http_response_code(400); - exit(json_encode(['success' => 0, 'message' => 'No active reset request. Please request a new OTP.'])); + $answer['message'] = 'No active reset request. Please request a new OTP.'; + exit(json_encode($answer)); } $user_id = (int)$_SESSION['reset_user_id']; -require_once $include_url . 'assets/utils/classes/PasswordResetManager.php'; +require_once '../../../assets/utils/classes/PasswordResetManager.php'; $manager = new PasswordResetManager($pdo1, $pdo2, $include_url, $SMTP, $pinkey); $manager->handleConfirmReset($user_id, $data); diff --git a/app/reports/stock_movement.php b/app/reports/stock_movement.php index acac5f6..8320341 100644 --- a/app/reports/stock_movement.php +++ b/app/reports/stock_movement.php @@ -351,8 +351,8 @@ // Reset UI $('#tbl_body').html('Loading…'); $('#lazy_sentinel').hide(); + $('#lazy_loader').hide(); $('#lazy_done').hide(); - $('#lazy_loader').show(); $('#result_summary').text(''); // Reset stats @@ -404,6 +404,7 @@ } is_loading = true; + $('#lazy_loader').show(); var sku = sku_queue[sku_index]; return ajax_request({ @@ -427,21 +428,29 @@ return; } - // If sentinel is still within the viewport (or above it), - // the table hasn't filled the window yet — keep loading immediately. + // If sentinel is still within the viewport keep loading immediately. + // Otherwise hide the spinner and wait for the IntersectionObserver to + // fire when the user scrolls close to the sentinel. if (sentinel_is_visible()) { load_next_sku(); + } else { + $('#lazy_loader').hide(); } + }, + onError: function() { + is_loading = false; + $('#lazy_loader').hide(); } }); } - // ── Returns true if the sentinel is visible in the current viewport ─── + // ── Returns true if the sentinel is within one window-height of the fold ─ function sentinel_is_visible() { var el = document.getElementById('lazy_sentinel'); var rect = el.getBoundingClientRect(); - return rect.top <= (window.innerHeight || document.documentElement.clientHeight); + var vh = window.innerHeight || document.documentElement.clientHeight; + return rect.top <= vh * 2; } @@ -614,10 +623,10 @@ observer.observe(document.getElementById('lazy_sentinel')); - // ── Helpers ─────────────────────────────────────────────────────────── - function fmt(n) { - return format_number(n, 2); - } + // ── Helpers ─────────────────────────────────────────────────────────── + function fmt(n) { + return format_number(n, 2); + } // ── Export ─────────────────────────────────────────────────────────── @@ -697,4 +706,4 @@ - + diff --git a/app/revenue/api/engine/convert_quotation.php b/app/revenue/api/engine/convert_quotation.php index fa96bdf..4ad16c2 100644 --- a/app/revenue/api/engine/convert_quotation.php +++ b/app/revenue/api/engine/convert_quotation.php @@ -8,7 +8,8 @@ $quotation_id = (int)($data['quotation_id'] ?? 0); $convert_items = $data['convert_items'] ?? []; if (!$quotation_id) { - exit(json_encode(['success' => 0, 'message' => 'Quotation ID required.'])); + $answer['message'] = 'Quotation ID required.'; + exit(json_encode($answer)); } $qm = new QuotationManager($pdo2, $company_id); @@ -16,16 +17,19 @@ $qm = new QuotationManager($pdo2, $company_id); // Load quotation — must be Accepted $q = $qm->getById($quotation_id); if (!$q || (int)$q['status'] !== 2) { - exit(json_encode(['success' => 0, 'message' => 'Quotation not found or not in Accepted status.'])); + $answer['message'] = 'Quotation not found or not in Accepted status.'; + exit(json_encode($answer)); } $qt_items = $q['items']; if (empty($qt_items)) { - exit(json_encode(['success' => 0, 'message' => 'Quotation has no items.'])); + $answer['message'] = 'Quotation has no items.'; + exit(json_encode($answer)); } if ((float)$q['total_remaining'] <= 0.000001) { - exit(json_encode(['success' => 0, 'message' => 'Quotation is already fully converted.'])); + $answer['message'] = 'Quotation is already fully converted.'; + exit(json_encode($answer)); } // Index by item_id for validation @@ -55,7 +59,8 @@ foreach ($convert_items as $ci) { if ($qty <= 0) continue; if (!isset($qt_by_id[$item_id])) { - exit(json_encode(['success' => 0, 'message' => "Item #{$item_id} not found in this quotation."])); + $answer['message'] = "Item #{$item_id} not found in this quotation."; + exit(json_encode($answer)); } $qi = $qt_by_id[$item_id]; @@ -63,10 +68,8 @@ foreach ($convert_items as $ci) { if ($qty - $remaining > 0.000001) { $name = $qi['product_name'] ?: $qi['product_sku']; - exit(json_encode([ - 'success' => 0, - 'message' => "Cannot convert {$qty} for \"{$name}\": only {$remaining} remaining.", - ])); + $answer['message'] = "Cannot convert {$qty} for \"{$name}\": only {$remaining} remaining."; + exit(json_encode($answer)); } $unit_price = (float)$qi['unit_price']; @@ -89,7 +92,8 @@ foreach ($convert_items as $ci) { } if (empty($ord_items)) { - exit(json_encode(['success' => 0, 'message' => 'No valid items to convert.'])); + $answer['message'] = 'No valid items to convert.'; + exit(json_encode($answer)); } $order_data = [ diff --git a/app/revenue/api/engine/manage_order.php b/app/revenue/api/engine/manage_order.php index fcd6b63..6a05a60 100644 --- a/app/revenue/api/engine/manage_order.php +++ b/app/revenue/api/engine/manage_order.php @@ -7,11 +7,13 @@ $action = $data['action'] ?? ''; $data['items'] = json_decode($data['items'] ?? '[]', true) ?: []; if (!in_array($action, ['create', 'update'], true)) { - exit(json_encode(['success' => 0, 'message' => 'Unknown action'])); + $answer['message'] = 'Unknown action'; + exit(json_encode($answer)); } if (empty($data['items'])) { - exit(json_encode(['success' => 0, 'message' => 'At least one item is required'])); + $answer['message'] = 'At least one item is required'; + exit(json_encode($answer)); } try { @@ -51,4 +53,3 @@ try { } exit(json_encode($answer)); -?> diff --git a/app/revenue/api/engine/retrieve_quotation.php b/app/revenue/api/engine/retrieve_quotation.php index b831c83..068afe0 100644 --- a/app/revenue/api/engine/retrieve_quotation.php +++ b/app/revenue/api/engine/retrieve_quotation.php @@ -10,7 +10,8 @@ if ($id) { $row = $qm->getById($id); if (!$row) { http_response_code(404); - exit(json_encode(['success' => 0, 'message' => 'Not found.'])); + $answer['message'] = 'Not found.'; + exit(json_encode($answer)); } $answer['output'] = $row; } else { diff --git a/app/revenue/api/engine/update_quotation_status.php b/app/revenue/api/engine/update_quotation_status.php index d767717..0c5c2a1 100644 --- a/app/revenue/api/engine/update_quotation_status.php +++ b/app/revenue/api/engine/update_quotation_status.php @@ -7,7 +7,8 @@ $id = (int)($data['id'] ?? 0); $action = $data['action_type'] ?? ''; if (!$id || !$action) { - exit(json_encode(['success' => 0, 'message' => 'ID and action are required.'])); + $answer['message'] = 'ID and action are required.'; + exit(json_encode($answer)); } $qm = new QuotationManager($pdo2, $company_id);