diff --git a/app/accounting/manage_account.php b/app/accounting/manage_account.php index 704907b..f5da6c1 100644 --- a/app/accounting/manage_account.php +++ b/app/accounting/manage_account.php @@ -222,7 +222,7 @@ autoPrepare: true, checkRequired: 0, action: 'read', - data: { id: }, + data: { id: }, onSuccess: function(res) { var item = res.output; $('#id').val(item.id); diff --git a/app/accounting/manage_department.php b/app/accounting/manage_department.php index fcaa2e5..f17f99f 100644 --- a/app/accounting/manage_department.php +++ b/app/accounting/manage_department.php @@ -89,7 +89,7 @@ autoPrepare: true, checkRequired: 0, action: 'read', - data: { id: }, + data: { id: }, onSuccess: function(res) { var item = res.output; $('#id').val(item.id); diff --git a/app/assets/js/custom.js b/app/assets/js/custom.js index 12b9f39..5b5ad57 100644 --- a/app/assets/js/custom.js +++ b/app/assets/js/custom.js @@ -1074,6 +1074,36 @@ function to_iso_date(dateStr) { } +/** + * Clear every field inside a form or form-like container — the "Clear" buttons + * on the master-data pages. It was called by five pages but never defined, so + * each click threw a ReferenceError, and where the container is a
rather + * than a
(Chart of Accounts, Departments) the button did nothing at all. + * Disabled and hidden inputs are left alone: those carry the record id and + * locked values, not user input. + */ +function reset_input(selector) { + var $scope = $(selector); + if (!$scope.length) return; + + $scope.find('input, textarea, select').each(function () { + if (this.disabled || this.type === 'hidden' || this.type === 'button' || this.type === 'submit') return; + + if (this._flatpickr) { + this._flatpickr.clear(); + } else if (this.type === 'checkbox' || this.type === 'radio') { + this.checked = this.defaultChecked; + } else if (this.tagName === 'SELECT') { + this.selectedIndex = 0; + } else { + this.value = ''; + } + + $(this).removeAttr('secondary').removeClass('is-invalid is-valid'); + }); +} + + function round_dp(value, places) { var factor = Math.pow(10, places); return Math.round((Number(value) + Number.EPSILON) * factor) / factor; diff --git a/app/assets/utils/classes/StockManager.php b/app/assets/utils/classes/StockManager.php index eab15bd..5ebc09e 100644 --- a/app/assets/utils/classes/StockManager.php +++ b/app/assets/utils/classes/StockManager.php @@ -307,7 +307,10 @@ class StockManager { $sth->execute([':company_id' => $this->company_id, ':id' => $id]); $output = $sth->fetch(PDO::FETCH_ASSOC); - if (!$output) return false; + // Only a transfer's outbound row names a destination. Any other row + // (a stock-in or stock-out reached through a stale or edited link) has + // no ref_warehouse, and resolving it threw "Invalid warehouse id." + if (!$output || $output['type'] !== 'transfer' || (int)$output['ref_warehouse'] <= 0) return false; // Resolve the inbound (to) row via ref_warehouse + uuid $to_warehouse_id = (int)$output['ref_warehouse']; diff --git a/app/assets/utils/db_auth.php b/app/assets/utils/db_auth.php index c7fe263..7c16490 100644 --- a/app/assets/utils/db_auth.php +++ b/app/assets/utils/db_auth.php @@ -7,6 +7,34 @@ ini_set('display_errors', 0); ini_set('log_errors', 1); header('Content-Type: application/json; charset=utf-8'); +// Last-resort handler for exceptions an engine does not catch itself. Many +// engines call a manager with no try/catch, so any exception — including the +// managers' own deliberate validation messages — used to end as a PHP fatal +// with an empty 500 body, which the browser could only report as "Server +// error occurred." This mirrors the convention the catching engines already +// use: a manager's Exception carries a user-facing message (400); a database +// or engine fault stays generic (500) and goes to the server log. +set_exception_handler(function (Throwable $e) { + while (ob_get_level() > 0) ob_end_clean(); + if (!headers_sent()) header('Content-Type: application/json; charset=utf-8'); + + if ($e instanceof PDOException) { + error_log('Uncaught PDOException: ' . $e->getMessage() . ' @ ' . $e->getFile() . ':' . $e->getLine()); + http_response_code(500); + $message = 'Database error, please try again.'; + } elseif ($e instanceof Exception) { + http_response_code(400); + $message = $e->getMessage(); + } else { + // Error / TypeError: a programming fault, not something to show users. + error_log('Uncaught ' . get_class($e) . ': ' . $e->getMessage() . ' @ ' . $e->getFile() . ':' . $e->getLine()); + http_response_code(500); + $message = 'Server error occurred.'; + } + + echo json_encode(['success' => 0, 'message' => $message]); +}); + require_once __DIR__."/../../config.php"; require_once __DIR__."/../../dbconn.php"; require_once __DIR__."/db_helpers.php"; diff --git a/app/ics/api/engine/retrieve_warehouse.php b/app/ics/api/engine/retrieve_warehouse.php index 88071f5..89afaff 100644 --- a/app/ics/api/engine/retrieve_warehouse.php +++ b/app/ics/api/engine/retrieve_warehouse.php @@ -17,11 +17,27 @@ $answer['output'] = $wh->getWarehouseList($type, $sku, $id); } - if (empty($answer['output'])) { + if (!empty($answer['output'])) { + $answer['success'] = 1; + + } elseif ($type === 'from' && !$id && $sku === '') { + // A source ("from") list is filtered to warehouses holding the chosen + // product, so it is empty until a product has been picked. That is the + // normal starting state of a stock-out form, not a failure. Reporting + // it as one showed "create your first warehouse" on every fresh + // stock-out, and the rejected request aborted the page's boot sequence + // before the barcode scanner was initialised. + $answer['success'] = 1; + + } elseif ($type === 'from' && $sku !== '') { + $answer['success'] = 0; + $answer['message'] = $lot + ? 'No approved stock of this product and lot is available in any warehouse.' + : 'No approved stock of this product is available in any warehouse.'; + + } else { $answer['success'] = 0; $answer['message'] = 'No warehouse found. Please go to Inventory → Warehouse to create your first warehouse before using this page.'; - } else { - $answer['success'] = 1; } exit(json_encode($answer)); -?> \ No newline at end of file +?> diff --git a/app/ics/manage_stock_transfer.php b/app/ics/manage_stock_transfer.php index 61da8c0..9ad43e7 100644 --- a/app/ics/manage_stock_transfer.php +++ b/app/ics/manage_stock_transfer.php @@ -630,6 +630,12 @@ action: 'read', onSuccess: function(res) { var data = res.output; + if (!data || !data.ref) { + bootbox.alert('Stock transfer not found.', function() { + window.location.href = 'ics/stock_transfer.php'; + }); + return; + } var from_wh = data.ref.ref_warehouse; var to_wh = data.ref_warehouse; diff --git a/app/order/confirm_order.php b/app/order/confirm_order.php deleted file mode 100644 index dc27ec4..0000000 --- a/app/order/confirm_order.php +++ /dev/null @@ -1,47 +0,0 @@ -get('default_stock_status') === 1; - $auto_invoice = (int)$csm->get('auto_invoice_and_credit_note') === 1; - - try { - dbTransaction($pdo2, function($pdo) use ($id, $company_id, $logging, $uuid, $auto_approve, $auto_invoice) { - $order = new OrderManager($pdo, $company_id); - $order->confirmOrder($id, $uuid, $logging, $auto_approve); - - if ($auto_invoice) { - $invMgmt = new InvoiceManager($pdo, $company_id); - $invMgmt->createFromOrder($id, $logging); - } - }); - - $answer['success'] = 1; - $answer['message'] = 'Order confirmed.'; - $answer['auto_approved'] = $auto_approve; - - } 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)); -?> \ No newline at end of file