fix login and configurations

This commit is contained in:
nok
2026-08-03 11:17:41 +07:00
parent 7b294f70da
commit 618045540a
11 changed files with 364 additions and 31 deletions
+27
View File
@@ -22,6 +22,33 @@ if(true){
$time_zone = "Asia/Bangkok"; $time_zone = "Asia/Bangkok";
} }
// ── Real-time Node.js server ─────────────────────────────────────────────────
// Base URL the browser uses to reach nodejs/server.js (Socket.IO). Must match
// PORT in nodejs/.env.
if (!defined('NODE_PUBLIC_URL')) {
define('NODE_PUBLIC_URL', 'http://localhost:3000');
}
// Server-to-server calls (PHP → Node /emit, and Node scheduler → PHP cron
// endpoints) authenticate with this shared secret — must match EMIT_SECRET
// in nodejs/.env exactly.
if (!defined('NODE_EMIT_URL')) {
define('NODE_EMIT_URL', 'http://127.0.0.1:3000/emit');
}
if (!defined('NODE_EMIT_SECRET')) {
define('NODE_EMIT_SECRET', 'YOUR_NODE_EMIT_SECRET'); // must match nodejs/.env EMIT_SECRET
}
// ── Usage packages ───────────────────────────────────────────────────────────
// Keyed by company_list.package (defaults to 'starter'). Read by UsageGuard to
// enforce daily/weekly action limits and which features lock once exceeded.
$packages = [
'starter' => [
'daily_limit' => 30,
'weekly_limit' => 100,
'lock_on_limit' => ['dashboard'],
],
];
// unique key — used for SMTP password encryption, keep consistent across deploys // unique key — used for SMTP password encryption, keep consistent across deploys
+38 -13
View File
@@ -24,9 +24,10 @@
* Fail either → return "Wrong OTP! Please try again." * Fail either → return "Wrong OTP! Please try again."
* 5. On success: * 5. On success:
* a. Concurrent-session check — if the account already has a session_token * a. Concurrent-session check — if the account already has a session_token
* set and session_last_seen is within the last hour, the login is blocked * set and session_last_seen is within SESSION_ACTIVE_GRACE_SECONDS
* with "already signed in on another device." A stale or NULL token allows * (see below), the login is blocked with "already signed in on another
* the login (user closed browser without logging out, or used back.php). * device." A stale or NULL token allows the login (previous session
* expired naturally, or the user used back.php to log out explicitly).
* b. session_regenerate_id(true) — prevents session fixation attack by * b. session_regenerate_id(true) — prevents session fixation attack by
* issuing a new session ID and deleting the old one. * issuing a new session ID and deleting the old one.
* c. Generate a fresh CSRF token and store in session. * c. Generate a fresh CSRF token and store in session.
@@ -109,22 +110,46 @@ if (empty($_SESSION['skip_otp'])) {
// ── Step 4b: Concurrent session check ──────────────────────────────────────── // ── Step 4b: Concurrent session check ────────────────────────────────────────
// Block the login if this account already has an active session. // Block the login if this account already has an active session.
// "Active" = session_token is set AND session_last_seen is within the last hour. // "Active" = session_token is set AND session_last_seen is within the last
// A stale last_seen (user closed browser without logging out) expires after 1 h, // SESSION_ACTIVE_GRACE_SECONDS. db_auth.php refreshes session_last_seen on every
// matching the PHP session GC maxlifetime configured in session.php. // authenticated request (throttled to once per 60s), so a session that is truly
// still in use on another device keeps re-touching this timestamp well within
// the grace window below. A session that has actually ended — browser/tab closed,
// cookie lost, PHP session GC'd — stops refreshing it and goes stale quickly.
//
// This window must stay well above the 60s throttle in db_auth.php (otherwise a
// live second session could go stale between its own refreshes and let a login
// through) but short enough that a real re-login isn't blocked for long after the
// previous session actually ended. It intentionally does NOT match PHP's
// session.gc_maxlifetime (3600s) — that timeout is about when PHP reclaims the
// session file on disk, not about how quickly this check should stop treating a
// dead session as "still active".
// An explicit logout clears session_token to NULL, so back.php bypasses this. // An explicit logout clears session_token to NULL, so back.php bypasses this.
//
// The staleness comparison is done entirely in SQL (session_last_seen vs MySQL's
// own NOW()), not in PHP. session_last_seen is written with MySQL's NOW(), and
// the MySQL server here runs on Asia/Bangkok time while PHP's default timezone is
// UTC (config.php's $time_zone is never applied via date_default_timezone_set()).
// Pulling the timestamp into PHP and comparing with strtotime()/time() silently
// misreads that Bangkok wall-clock string as UTC — 7 hours in the future — which
// made idle_seconds permanently negative and this check block every login,
// regardless of window size. Comparing inside MySQL sidesteps the mismatch
// without touching PHP's global timezone (which would ripple into every other
// date()/time() call in the app).
define('SESSION_ACTIVE_GRACE_SECONDS', 120);
$sth_active = $pdo1->prepare( $sth_active = $pdo1->prepare(
"SELECT session_token, session_last_seen FROM user WHERE user_id = :uid LIMIT 1" "SELECT session_token,
(session_last_seen IS NOT NULL
AND session_last_seen > (NOW() - INTERVAL " . SESSION_ACTIVE_GRACE_SECONDS . " SECOND)) AS is_active
FROM user WHERE user_id = :uid LIMIT 1"
); );
$sth_active->execute([':uid' => $user_id]); $sth_active->execute([':uid' => $user_id]);
$active_row = $sth_active->fetch(PDO::FETCH_ASSOC); $active_row = $sth_active->fetch(PDO::FETCH_ASSOC);
if (!empty($active_row['session_token']) && !empty($active_row['session_last_seen'])) { if (!empty($active_row['session_token']) && !empty($active_row['is_active'])) {
$idle_seconds = time() - strtotime($active_row['session_last_seen']); $answer['message'] = 'This account is currently signed in on another device. Please sign out from that session first.';
if ($idle_seconds < 3600) { exit(json_encode($answer));
$answer['message'] = 'This account is currently signed in on another device. Please sign out from that session first.';
exit(json_encode($answer));
}
} }
// ── Step 4c: Claim session ──────────────────────────────────────────────────── // ── Step 4c: Claim session ────────────────────────────────────────────────────
+14 -1
View File
@@ -342,7 +342,20 @@ if (password_verify(trim($data["password"]), $temp["password"])) {
"company_id" => $default_company, "company_id" => $default_company,
"smtp" => $smtp_config, "smtp" => $smtp_config,
"subject" => "One Time Password (OTP) For reference number " . $reference_number, "subject" => "One Time Password (OTP) For reference number " . $reference_number,
"message" => "Your OTP is " . $otp . " for reference number " . $reference_number, "message" => implode("\n", [
"Dear WMS user,",
"",
"You requested a One-Time Password (OTP) to log in to WMS.",
"",
"Please use the OTP below to complete your request:",
"• OTP code: " . $otp,
"• Reference number: " . $reference_number,
"",
"Please note:",
"• This code will expire in 3 minutes. Please complete your action promptly.",
"• Do not share this code with anyone to keep your account secure.",
"• If you did not request this code, please ignore this email.",
]),
"channel_name" => "WMS LOGIN OTP", "channel_name" => "WMS LOGIN OTP",
"to" => $user_email, "to" => $user_email,
"key" => $pinkey, "key" => $pinkey,
+14 -1
View File
@@ -115,7 +115,20 @@ if (password_verify(trim($data["password"]), $temp["password"])) {
"company_id" => 0, "company_id" => 0,
"smtp" => $SMTP, "smtp" => $SMTP,
"subject" => "One Time Password (OTP) For reference number " . $reference_number, "subject" => "One Time Password (OTP) For reference number " . $reference_number,
"message" => "Your OTP is " . $otp . " for reference number " . $reference_number, "message" => implode("\n", [
"Dear WMS user,",
"",
"You requested a One-Time Password (OTP) to log in to WMS.",
"",
"Please use the OTP below to complete your request:",
"• OTP code: " . $otp,
"• Reference number: " . $reference_number,
"",
"Please note:",
"• This code will expire in 3 minutes. Please complete your action promptly.",
"• Do not share this code with anyone to keep your account secure.",
"• If you did not request this code, please ignore this email.",
]),
"channel_name" => "WMS LOGIN OTP ", "channel_name" => "WMS LOGIN OTP ",
"to" => $user_email, "to" => $user_email,
"key" => $pinkey, "key" => $pinkey,
+1 -1
View File
@@ -1,4 +1,4 @@
<?php <?php
require __DIR__ . '/app/config.php'; require __DIR__ . '/app/config.php';
header('Location: ' . $base_url . 'landing/'); header('Location: ' . $base_url . 'app/');
exit; exit;
+18
View File
@@ -0,0 +1,18 @@
2026-07-22T14:37:48: [2026-07-22T07:37:48.626Z] [PID: 505] [NODE-CRON] [WARN] missed execution at Wed Jul 22 2026 14:30:00 GMT+0700 (Indochina Time)! Possible blocking IO or high CPU user at the same process used by node-cron.
2026-07-23T15:14:43: [2026-07-23T08:14:43.767Z] [PID: 451] [NODE-CRON] [WARN] missed execution at Thu Jul 23 2026 15:00:00 GMT+0700 (Indochina Time)! Possible blocking IO or high CPU user at the same process used by node-cron.
2026-07-23T20:39:04: [2026-07-23T13:39:04.943Z] [PID: 451] [NODE-CRON] [WARN] missed execution at Thu Jul 23 2026 17:00:00 GMT+0700 (Indochina Time)! Possible blocking IO or high CPU user at the same process used by node-cron.
2026-07-23T20:39:04: [2026-07-23T13:39:04.953Z] [PID: 451] [NODE-CRON] [WARN] missed execution at Thu Jul 23 2026 18:00:00 GMT+0700 (Indochina Time)! Possible blocking IO or high CPU user at the same process used by node-cron.
2026-07-23T20:39:04: [2026-07-23T13:39:04.959Z] [PID: 451] [NODE-CRON] [WARN] missed execution at Thu Jul 23 2026 19:00:00 GMT+0700 (Indochina Time)! Possible blocking IO or high CPU user at the same process used by node-cron.
2026-07-23T20:39:04: [2026-07-23T13:39:04.965Z] [PID: 451] [NODE-CRON] [WARN] missed execution at Thu Jul 23 2026 20:00:00 GMT+0700 (Indochina Time)! Possible blocking IO or high CPU user at the same process used by node-cron.
2026-07-23T21:02:17: [2026-07-23T14:02:17.033Z] [PID: 451] [NODE-CRON] [WARN] missed execution at Thu Jul 23 2026 21:00:00 GMT+0700 (Indochina Time)! Possible blocking IO or high CPU user at the same process used by node-cron.
2026-07-23T21:11:21: [2026-07-23T14:11:21.977Z] [PID: 451] [NODE-CRON] [WARN] missed execution at Thu Jul 23 2026 17:30:00 GMT+0700 (Indochina Time)! Possible blocking IO or high CPU user at the same process used by node-cron.
2026-07-23T21:11:21: [2026-07-23T14:11:21.982Z] [PID: 451] [NODE-CRON] [WARN] missed execution at Thu Jul 23 2026 18:30:00 GMT+0700 (Indochina Time)! Possible blocking IO or high CPU user at the same process used by node-cron.
2026-07-23T21:11:21: [2026-07-23T14:11:21.985Z] [PID: 451] [NODE-CRON] [WARN] missed execution at Thu Jul 23 2026 19:30:00 GMT+0700 (Indochina Time)! Possible blocking IO or high CPU user at the same process used by node-cron.
2026-07-23T21:11:21: [2026-07-23T14:11:21.987Z] [PID: 451] [NODE-CRON] [WARN] missed execution at Thu Jul 23 2026 20:30:00 GMT+0700 (Indochina Time)! Possible blocking IO or high CPU user at the same process used by node-cron.
2026-07-25T14:44:32: [2026-07-25T07:44:32.121Z] [PID: 476] [NODE-CRON] [WARN] missed execution at Sat Jul 25 2026 13:30:00 GMT+0700 (Indochina Time)! Possible blocking IO or high CPU user at the same process used by node-cron.
2026-07-25T14:44:32: [2026-07-25T07:44:32.127Z] [PID: 476] [NODE-CRON] [WARN] missed execution at Sat Jul 25 2026 14:30:00 GMT+0700 (Indochina Time)! Possible blocking IO or high CPU user at the same process used by node-cron.
2026-07-25T15:24:00: [2026-07-25T08:24:00.029Z] [PID: 476] [NODE-CRON] [WARN] missed execution at Sat Jul 25 2026 14:00:00 GMT+0700 (Indochina Time)! Possible blocking IO or high CPU user at the same process used by node-cron.
2026-07-25T15:24:00: [2026-07-25T08:24:00.032Z] [PID: 476] [NODE-CRON] [WARN] missed execution at Sat Jul 25 2026 15:00:00 GMT+0700 (Indochina Time)! Possible blocking IO or high CPU user at the same process used by node-cron.
2026-08-03T10:11:12: [2026-08-03T03:11:12.241Z] [PID: 477] [NODE-CRON] [WARN] missed execution at Mon Aug 03 2026 10:00:00 GMT+0700 (Indochina Time)! Possible blocking IO or high CPU user at the same process used by node-cron.
2026-08-03T10:31:46: [2026-08-03T03:31:46.573Z] [PID: 477] [NODE-CRON] [WARN] missed execution at Mon Aug 03 2026 10:30:00 GMT+0700 (Indochina Time)! Possible blocking IO or high CPU user at the same process used by node-cron.
2026-08-03T11:08:26: [2026-08-03T04:08:26.686Z] [PID: 477] [NODE-CRON] [WARN] missed execution at Mon Aug 03 2026 11:00:00 GMT+0700 (Indochina Time)! Possible blocking IO or high CPU user at the same process used by node-cron.
+50
View File
@@ -0,0 +1,50 @@
2026-07-20T17:35:51: [scheduler] running — etl_gl + etl_stock (hourly) | low_stock + overdue_invoices alerts (daily)
2026-07-20T21:00:00: [scheduler] etl_gl slot=21
2026-07-20T21:00:00: [scheduler] etl_gl slot=21 — no companies
2026-07-21T16:06:58: [scheduler] running — etl_gl + etl_stock (hourly) | low_stock + overdue_invoices alerts (daily)
2026-07-22T13:08:58: [scheduler] running — etl_gl + etl_stock (hourly) | low_stock + overdue_invoices alerts (daily)
2026-07-22T14:37:48: [2026-07-22T07:37:48.626Z] [PID: 505] [NODE-CRON] [WARN] missed execution at Wed Jul 22 2026 14:30:00 GMT+0700 (Indochina Time)! Possible blocking IO or high CPU user at the same process used by node-cron.
2026-07-22T15:00:00: [scheduler] etl_gl slot=15
2026-07-22T15:00:00: [scheduler] etl_gl slot=15 — no companies
2026-07-22T15:30:00: [scheduler] etl_stock slot=15
2026-07-22T15:30:00: [scheduler] etl_stock slot=15 — no companies
2026-07-22T16:00:00: [scheduler] etl_gl slot=16
2026-07-22T16:00:00: [scheduler] etl_gl slot=16 — no companies
2026-07-23T12:11:51: [scheduler] running — etl_gl + etl_stock (hourly) | low_stock + overdue_invoices alerts (daily)
2026-07-23T12:30:00: [scheduler] etl_stock slot=12
2026-07-23T12:30:00: [scheduler] etl_stock slot=12 — no companies
2026-07-23T13:36:04: [scheduler] running — etl_gl + etl_stock (hourly) | low_stock + overdue_invoices alerts (daily)
2026-07-23T15:14:43: [2026-07-23T08:14:43.767Z] [PID: 451] [NODE-CRON] [WARN] missed execution at Thu Jul 23 2026 15:00:00 GMT+0700 (Indochina Time)! Possible blocking IO or high CPU user at the same process used by node-cron.
2026-07-23T15:30:00: [scheduler] etl_stock slot=15
2026-07-23T15:30:00: [scheduler] etl_stock slot=15 — no companies
2026-07-23T16:00:00: [scheduler] etl_gl slot=16
2026-07-23T16:00:00: [scheduler] etl_gl slot=16 — no companies
2026-07-23T16:30:00: [scheduler] etl_stock slot=16
2026-07-23T16:30:00: [scheduler] etl_stock slot=16 — no companies
2026-07-23T20:39:04: [2026-07-23T13:39:04.943Z] [PID: 451] [NODE-CRON] [WARN] missed execution at Thu Jul 23 2026 17:00:00 GMT+0700 (Indochina Time)! Possible blocking IO or high CPU user at the same process used by node-cron.
2026-07-23T20:39:04: [2026-07-23T13:39:04.953Z] [PID: 451] [NODE-CRON] [WARN] missed execution at Thu Jul 23 2026 18:00:00 GMT+0700 (Indochina Time)! Possible blocking IO or high CPU user at the same process used by node-cron.
2026-07-23T20:39:04: [2026-07-23T13:39:04.959Z] [PID: 451] [NODE-CRON] [WARN] missed execution at Thu Jul 23 2026 19:00:00 GMT+0700 (Indochina Time)! Possible blocking IO or high CPU user at the same process used by node-cron.
2026-07-23T20:39:04: [2026-07-23T13:39:04.965Z] [PID: 451] [NODE-CRON] [WARN] missed execution at Thu Jul 23 2026 20:00:00 GMT+0700 (Indochina Time)! Possible blocking IO or high CPU user at the same process used by node-cron.
2026-07-23T21:02:17: [2026-07-23T14:02:17.033Z] [PID: 451] [NODE-CRON] [WARN] missed execution at Thu Jul 23 2026 21:00:00 GMT+0700 (Indochina Time)! Possible blocking IO or high CPU user at the same process used by node-cron.
2026-07-23T21:11:21: [2026-07-23T14:11:21.977Z] [PID: 451] [NODE-CRON] [WARN] missed execution at Thu Jul 23 2026 17:30:00 GMT+0700 (Indochina Time)! Possible blocking IO or high CPU user at the same process used by node-cron.
2026-07-23T21:11:21: [2026-07-23T14:11:21.982Z] [PID: 451] [NODE-CRON] [WARN] missed execution at Thu Jul 23 2026 18:30:00 GMT+0700 (Indochina Time)! Possible blocking IO or high CPU user at the same process used by node-cron.
2026-07-23T21:11:21: [2026-07-23T14:11:21.985Z] [PID: 451] [NODE-CRON] [WARN] missed execution at Thu Jul 23 2026 19:30:00 GMT+0700 (Indochina Time)! Possible blocking IO or high CPU user at the same process used by node-cron.
2026-07-23T21:11:21: [2026-07-23T14:11:21.987Z] [PID: 451] [NODE-CRON] [WARN] missed execution at Thu Jul 23 2026 20:30:00 GMT+0700 (Indochina Time)! Possible blocking IO or high CPU user at the same process used by node-cron.
2026-07-23T21:30:00: [scheduler] etl_stock slot=21
2026-07-23T21:30:00: [scheduler] etl_stock slot=21 — no companies
2026-07-25T12:19:55: [scheduler] running — etl_gl + etl_stock (hourly) | low_stock + overdue_invoices alerts (daily)
2026-07-25T12:30:00: [scheduler] etl_stock slot=12
2026-07-25T12:30:00: [scheduler] etl_stock slot=12 — no companies
2026-07-25T13:00:00: [scheduler] etl_gl slot=13
2026-07-25T13:00:00: [scheduler] etl_gl slot=13 — no companies
2026-07-25T14:44:32: [2026-07-25T07:44:32.121Z] [PID: 476] [NODE-CRON] [WARN] missed execution at Sat Jul 25 2026 13:30:00 GMT+0700 (Indochina Time)! Possible blocking IO or high CPU user at the same process used by node-cron.
2026-07-25T14:44:32: [2026-07-25T07:44:32.127Z] [PID: 476] [NODE-CRON] [WARN] missed execution at Sat Jul 25 2026 14:30:00 GMT+0700 (Indochina Time)! Possible blocking IO or high CPU user at the same process used by node-cron.
2026-07-25T15:24:00: [2026-07-25T08:24:00.029Z] [PID: 476] [NODE-CRON] [WARN] missed execution at Sat Jul 25 2026 14:00:00 GMT+0700 (Indochina Time)! Possible blocking IO or high CPU user at the same process used by node-cron.
2026-07-25T15:24:00: [2026-07-25T08:24:00.032Z] [PID: 476] [NODE-CRON] [WARN] missed execution at Sat Jul 25 2026 15:00:00 GMT+0700 (Indochina Time)! Possible blocking IO or high CPU user at the same process used by node-cron.
2026-08-03T08:52:15: [scheduler] running — etl_gl + etl_stock (hourly) | low_stock + overdue_invoices alerts (daily)
2026-08-03T09:00:00: [scheduler] etl_gl slot=9
2026-08-03T09:00:00: [scheduler] alert_overdue_invoices running
2026-08-03T09:00:00: [scheduler] etl_gl slot=9 — no companies
2026-08-03T10:11:12: [2026-08-03T03:11:12.241Z] [PID: 477] [NODE-CRON] [WARN] missed execution at Mon Aug 03 2026 10:00:00 GMT+0700 (Indochina Time)! Possible blocking IO or high CPU user at the same process used by node-cron.
2026-08-03T10:31:46: [2026-08-03T03:31:46.573Z] [PID: 477] [NODE-CRON] [WARN] missed execution at Mon Aug 03 2026 10:30:00 GMT+0700 (Indochina Time)! Possible blocking IO or high CPU user at the same process used by node-cron.
2026-08-03T11:08:26: [2026-08-03T04:08:26.686Z] [PID: 477] [NODE-CRON] [WARN] missed execution at Mon Aug 03 2026 11:00:00 GMT+0700 (Indochina Time)! Possible blocking IO or high CPU user at the same process used by node-cron.
View File
+137
View File
@@ -0,0 +1,137 @@
2026-07-20T17:35:51: Node.js real-time server running on port 3000
2026-07-21T16:06:58: Node.js real-time server running on port 3000
2026-07-22T13:08:58: Node.js real-time server running on port 3000
2026-07-22T13:10:29: [connect] socket=0-LquhazY9eKKN7LAAAB company=1 user=1 role=owner
2026-07-22T13:15:09: [disconnect] socket=0-LquhazY9eKKN7LAAAB
2026-07-22T13:15:12: [connect] socket=P4rxlPGuHWMqT35XAAAD company=1 user=1 role=owner
2026-07-22T13:26:21: [disconnect] socket=P4rxlPGuHWMqT35XAAAD
2026-07-22T13:26:23: [connect] socket=EBxi3tj8fNMUmyoyAAAF company=1 user=1 role=owner
2026-07-22T13:30:24: [disconnect] socket=EBxi3tj8fNMUmyoyAAAF
2026-07-22T13:30:25: [connect] socket=SJtRh1L6usnHrWebAAAH company=1 user=1 role=owner
2026-07-22T13:31:59: [disconnect] socket=SJtRh1L6usnHrWebAAAH
2026-07-22T13:31:59: [connect] socket=xvRdZhqqg-soDWr7AAAJ company=1 user=1 role=owner
2026-07-22T13:41:17: [disconnect] socket=xvRdZhqqg-soDWr7AAAJ
2026-07-22T13:41:17: [connect] socket=QZkAkh2pHOGltp-0AAAL company=1 user=1 role=owner
2026-07-22T13:42:28: [disconnect] socket=QZkAkh2pHOGltp-0AAAL
2026-07-22T13:42:28: [connect] socket=0Vb5YTMHDs1gOC47AAAN company=1 user=1 role=owner
2026-07-22T13:42:36: [disconnect] socket=0Vb5YTMHDs1gOC47AAAN
2026-07-22T13:42:36: [connect] socket=idQO9l1OGLiXPyEoAAAP company=1 user=1 role=owner
2026-07-22T13:42:43: [disconnect] socket=idQO9l1OGLiXPyEoAAAP
2026-07-22T13:42:43: [connect] socket=nvfS_4EF_3kF5PGsAAAR company=1 user=1 role=owner
2026-07-22T13:47:25: [disconnect] socket=nvfS_4EF_3kF5PGsAAAR
2026-07-22T13:47:27: [connect] socket=yytX3fzh594f9phBAAAT company=1 user=1 role=owner
2026-07-22T13:50:19: [emit] company=1 event=stock_updated { warehouse_id: 1, type: 'in' }
2026-07-22T13:50:19: [emit] company=1 event=stock_updated { warehouse_id: 1, type: 'in' }
2026-07-22T13:50:19: [emit] company=1 event=stock_updated { warehouse_id: 1, type: 'in' }
2026-07-22T13:50:19: [emit] company=1 event=stock_updated { warehouse_id: 1, type: 'in' }
2026-07-22T13:50:19: [emit] company=1 event=stock_updated { warehouse_id: 1, type: 'in' }
2026-07-22T13:59:42: [emit] company=1 event=stock_updated { warehouse_id: 2, type: 'in' }
2026-07-22T13:59:42: [emit] company=1 event=stock_updated { warehouse_id: 2, type: 'in' }
2026-07-22T13:59:43: [emit] company=1 event=stock_updated { warehouse_id: 2, type: 'in' }
2026-07-22T13:59:43: [emit] company=1 event=stock_updated { warehouse_id: 2, type: 'in' }
2026-07-22T13:59:43: [emit] company=1 event=stock_updated { warehouse_id: 2, type: 'in' }
2026-07-22T13:59:43: [emit] company=1 event=stock_updated { warehouse_id: 2, type: 'in' }
2026-07-22T13:59:43: [emit] company=1 event=stock_updated { warehouse_id: 2, type: 'in' }
2026-07-22T13:59:43: [emit] company=1 event=stock_updated { warehouse_id: 2, type: 'in' }
2026-07-22T13:59:43: [emit] company=1 event=stock_updated { warehouse_id: 2, type: 'in' }
2026-07-22T13:59:43: [emit] company=1 event=stock_updated { warehouse_id: 2, type: 'in' }
2026-07-22T14:03:26: [disconnect] socket=yytX3fzh594f9phBAAAT
2026-07-22T14:03:28: [connect] socket=waEM4mo4V099RHhAAAAV company=1 user=1 role=owner
2026-07-22T14:03:30: [emit] company=1 event=stock_updated { warehouse_id: 3, type: 'in' }
2026-07-22T14:03:30: [emit] company=1 event=stock_updated { warehouse_id: 3, type: 'in' }
2026-07-22T14:03:30: [emit] company=1 event=stock_updated { warehouse_id: 3, type: 'in' }
2026-07-22T14:03:30: [emit] company=1 event=stock_updated { warehouse_id: 3, type: 'in' }
2026-07-22T14:03:30: [emit] company=1 event=stock_updated { warehouse_id: 3, type: 'in' }
2026-07-22T14:03:30: [emit] company=1 event=stock_updated { warehouse_id: 3, type: 'in' }
2026-07-22T14:03:30: [emit] company=1 event=stock_updated { warehouse_id: 3, type: 'in' }
2026-07-22T14:03:30: [emit] company=1 event=stock_updated { warehouse_id: 3, type: 'in' }
2026-07-22T14:03:30: [emit] company=1 event=stock_updated { warehouse_id: 3, type: 'in' }
2026-07-22T14:03:30: [emit] company=1 event=stock_updated { warehouse_id: 3, type: 'in' }
2026-07-22T14:03:30: [emit] company=1 event=stock_updated { warehouse_id: 3, type: 'out' }
2026-07-22T14:03:30: [emit] company=1 event=stock_updated { warehouse_id: 3, type: 'out' }
2026-07-22T14:03:30: [emit] company=1 event=stock_updated { warehouse_id: 3, type: 'out' }
2026-07-22T14:03:30: [emit] company=1 event=stock_updated { warehouse_id: 3, type: 'out' }
2026-07-22T14:03:30: [emit] company=1 event=stock_updated { warehouse_id: 3, type: 'out' }
2026-07-22T14:11:10: [disconnect] socket=waEM4mo4V099RHhAAAAV
2026-07-22T14:35:14: [connect] socket=ZSmIs38dJh1u4If4AAAX company=1 user=1 role=owner
2026-07-22T14:35:49: [disconnect] socket=ZSmIs38dJh1u4If4AAAX
2026-07-22T14:35:49: [connect] socket=1bcHdQOC7cBTftxyAAAZ company=1 user=1 role=owner
2026-07-22T14:36:37: [disconnect] socket=1bcHdQOC7cBTftxyAAAZ
2026-07-22T14:36:38: [connect] socket=fMazKVGWKhaTm7OUAAAb company=1 user=1 role=owner
2026-07-22T14:36:46: [disconnect] socket=fMazKVGWKhaTm7OUAAAb
2026-07-22T14:36:46: [connect] socket=VfqVaEiOI3NTCA7fAAAd company=1 user=1 role=owner
2026-07-22T14:36:48: [disconnect] socket=VfqVaEiOI3NTCA7fAAAd
2026-07-22T14:36:48: [connect] socket=DAww8irzEgS7j7JLAAAf company=1 user=1 role=owner
2026-07-22T14:36:49: [disconnect] socket=DAww8irzEgS7j7JLAAAf
2026-07-22T14:36:49: [connect] socket=WgF3HArg1rthWkktAAAh company=1 user=1 role=owner
2026-07-22T14:37:05: [disconnect] socket=WgF3HArg1rthWkktAAAh
2026-07-22T14:37:05: [connect] socket=pfMLLNR0x-qoq485AAAj company=1 user=1 role=owner
2026-07-22T14:37:08: [disconnect] socket=pfMLLNR0x-qoq485AAAj
2026-07-22T14:37:09: [connect] socket=7ZtkCaJZqcYXBSZWAAAl company=1 user=1 role=owner
2026-07-22T14:37:14: [disconnect] socket=7ZtkCaJZqcYXBSZWAAAl
2026-07-22T14:37:14: [connect] socket=F6_OvPrmc-vv_KoqAAAn company=1 user=1 role=owner
2026-07-22T14:37:18: [disconnect] socket=F6_OvPrmc-vv_KoqAAAn
2026-07-22T14:37:18: [connect] socket=gdlZxPbkkcdkdE2AAAAp company=1 user=1 role=owner
2026-07-22T14:40:08: [disconnect] socket=gdlZxPbkkcdkdE2AAAAp
2026-07-22T14:40:09: [connect] socket=c8j8ybp-e7MPpa6cAAAr company=1 user=1 role=owner
2026-07-22T14:40:13: [disconnect] socket=c8j8ybp-e7MPpa6cAAAr
2026-07-22T14:40:13: [connect] socket=D36DmJgraENmU5xsAAAt company=1 user=1 role=owner
2026-07-22T14:40:20: [disconnect] socket=D36DmJgraENmU5xsAAAt
2026-07-22T14:40:21: [connect] socket=saY0q6gBioHWgq7RAAAv company=1 user=1 role=owner
2026-07-22T14:40:25: [disconnect] socket=saY0q6gBioHWgq7RAAAv
2026-07-22T14:40:25: [connect] socket=o9h4_9i-KOjDfVmrAAAx company=1 user=1 role=owner
2026-07-22T14:40:27: [disconnect] socket=o9h4_9i-KOjDfVmrAAAx
2026-07-22T14:40:27: [connect] socket=aIM89idl78lyhTbNAAAz company=1 user=1 role=owner
2026-07-22T14:56:05: [emit] company=1 event=stock_updated { warehouse_id: 3, type: 'in' }
2026-07-22T14:56:05: [emit] company=1 event=stock_updated { warehouse_id: 3, type: 'in' }
2026-07-22T14:56:06: [emit] company=1 event=stock_updated { warehouse_id: 3, type: 'out' }
2026-07-22T14:56:06: [emit] company=1 event=stock_updated { warehouse_id: 3, type: 'out' }
2026-07-22T15:21:05: [emit] company=1 event=stock_updated { warehouse_id: 3, type: 'transfer' }
2026-07-22T15:28:23: [disconnect] socket=aIM89idl78lyhTbNAAAz
2026-07-22T15:43:35: [connect] socket=q1N4BECKfiomOEfyAAA1 company=1 user=1 role=owner
2026-07-22T15:43:38: [disconnect] socket=q1N4BECKfiomOEfyAAA1
2026-07-22T15:43:38: [connect] socket=yM_0j72uX0V2b-GuAAA3 company=1 user=1 role=owner
2026-07-22T15:43:43: [disconnect] socket=yM_0j72uX0V2b-GuAAA3
2026-07-22T15:43:43: [connect] socket=dvzUKq0p7lXQMuSLAAA5 company=1 user=1 role=owner
2026-07-22T15:43:45: [disconnect] socket=dvzUKq0p7lXQMuSLAAA5
2026-07-22T15:43:45: [connect] socket=TYvNpQgcOHR5-V1RAAA7 company=1 user=1 role=owner
2026-07-22T15:43:46: [disconnect] socket=TYvNpQgcOHR5-V1RAAA7
2026-07-22T15:43:46: [connect] socket=waNTeqU5sAu8W2jqAAA9 company=1 user=1 role=owner
2026-07-22T16:05:01: [disconnect] socket=waNTeqU5sAu8W2jqAAA9
2026-07-23T12:11:51: Node.js real-time server running on port 3000
2026-07-23T12:13:27: [connect] socket=NYO9Tg4V6Cqp3e4cAAAB company=1 user=1 role=owner
2026-07-23T12:33:05: [disconnect] socket=NYO9Tg4V6Cqp3e4cAAAB
2026-07-23T12:33:06: [connect] socket=pEDe8Dms2dU7gdhCAAAD company=1 user=1 role=owner
2026-07-23T12:34:13: [disconnect] socket=pEDe8Dms2dU7gdhCAAAD
2026-07-23T12:34:17: [connect] socket=vaGUT12vPXkqH_7kAAAF company=1 user=1 role=owner
2026-07-23T12:55:49: [disconnect] socket=vaGUT12vPXkqH_7kAAAF
2026-07-23T13:36:04: Node.js real-time server running on port 3000
2026-07-23T13:36:55: [connect] socket=nVGY71aXPas0zYS_AAAB company=1 user=1 role=owner
2026-07-23T13:51:36: [disconnect] socket=nVGY71aXPas0zYS_AAAB
2026-07-23T13:51:36: [connect] socket=5peGIWbSaRCxnlYBAAAD company=1 user=1 role=owner
2026-07-23T13:51:38: [disconnect] socket=5peGIWbSaRCxnlYBAAAD
2026-07-23T13:51:38: [connect] socket=fGwRmZaYGzedlqqRAAAF company=1 user=1 role=owner
2026-07-23T13:51:40: [disconnect] socket=fGwRmZaYGzedlqqRAAAF
2026-07-23T13:51:40: [connect] socket=YZk7xLKHpmi29ykIAAAH company=1 user=1 role=owner
2026-07-23T13:51:41: [disconnect] socket=YZk7xLKHpmi29ykIAAAH
2026-07-23T13:51:41: [connect] socket=f078x01mtX2eGd2HAAAJ company=1 user=1 role=owner
2026-07-23T13:57:26: [disconnect] socket=f078x01mtX2eGd2HAAAJ
2026-07-23T13:57:28: [connect] socket=vToy_ZVIAk6w9099AAAL company=1 user=1 role=owner
2026-07-23T14:17:58: [disconnect] socket=vToy_ZVIAk6w9099AAAL
2026-07-23T14:18:00: [connect] socket=7yHLdkKxBAAO_nF4AAAN company=1 user=1 role=owner
2026-07-23T16:25:41: [disconnect] socket=7yHLdkKxBAAO_nF4AAAN
2026-07-25T12:19:55: Node.js real-time server running on port 3000
2026-08-03T08:52:15: Node.js real-time server running on port 3000
2026-08-03T10:17:41: [connect] socket=kNyFq-T_JVC3-_WLAAAB company=1 user=1 role=owner
2026-08-03T10:18:50: [disconnect] socket=kNyFq-T_JVC3-_WLAAAB
2026-08-03T10:18:50: [connect] socket=efcou9_hk0YUTnvQAAAD company=1 user=1 role=owner
2026-08-03T10:18:59: [disconnect] socket=efcou9_hk0YUTnvQAAAD
2026-08-03T10:18:59: [connect] socket=PmKuy_3CCIDze4P3AAAF company=1 user=1 role=owner
2026-08-03T10:32:14: [disconnect] socket=PmKuy_3CCIDze4P3AAAF
2026-08-03T10:32:18: [connect] socket=-ZlIPBBmpRazD30gAAAH company=1 user=1 role=owner
2026-08-03T10:49:23: [disconnect] socket=-ZlIPBBmpRazD30gAAAH
2026-08-03T10:49:26: [connect] socket=Azbj9ipTPqb3aOW3AAAJ company=1 user=1 role=owner
2026-08-03T10:54:19: [disconnect] socket=Azbj9ipTPqb3aOW3AAAJ
2026-08-03T10:54:19: [connect] socket=FMnx-fmSk96rxIfwAAAL company=1 user=1 role=owner
2026-08-03T11:13:57: [disconnect] socket=FMnx-fmSk96rxIfwAAAL
2026-08-03T11:13:59: [connect] socket=LgVxBoN_6JuJtxHyAAAN company=1 user=1 role=owner
+11 -11
View File
@@ -260,9 +260,9 @@
} }
}, },
"node_modules/engine.io": { "node_modules/engine.io": {
"version": "6.6.8", "version": "6.6.9",
"resolved": "https://registry.npmjs.org/engine.io/-/engine.io-6.6.8.tgz", "resolved": "https://registry.npmjs.org/engine.io/-/engine.io-6.6.9.tgz",
"integrity": "sha512-2agL3ueZhqxoVrfmntO8yuVj+uNSlIOnhykYHk3Cq0ShYPdUjjUiSJrQvXjq01I9jAuI0Zl2YO8Evv5Mqytm5g==", "integrity": "sha512-clKkw4C7nJ22mGgoVcCg6V/W/TxdNyIOTr89k2ONZu81qqkddPFDF0LXcbAwhzPD8DjkiRCjzuiO6Y+fkpD4vg==",
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@types/cors": "^2.8.12", "@types/cors": "^2.8.12",
@@ -274,7 +274,7 @@
"cors": "~2.8.5", "cors": "~2.8.5",
"debug": "~4.4.1", "debug": "~4.4.1",
"engine.io-parser": "~5.2.1", "engine.io-parser": "~5.2.1",
"ws": "~8.20.1" "ws": "~8.21.0"
}, },
"engines": { "engines": {
"node": ">=10.2.0" "node": ">=10.2.0"
@@ -929,13 +929,13 @@
} }
}, },
"node_modules/socket.io-adapter": { "node_modules/socket.io-adapter": {
"version": "2.5.7", "version": "2.5.8",
"resolved": "https://registry.npmjs.org/socket.io-adapter/-/socket.io-adapter-2.5.7.tgz", "resolved": "https://registry.npmjs.org/socket.io-adapter/-/socket.io-adapter-2.5.8.tgz",
"integrity": "sha512-e0LyK91f3cUxTmv95/KzoLg47+zF+s/sbxRGDNsyG4dmIP8ZSX8ax6byOxfJXeNNtS/8AZlfD+uP7gBeR7DLlg==", "integrity": "sha512-6Oy52pbg+kvdCVvjcN+FnY7BvxZ7cIHNScbvztT/It5d0vbwoJoVZmF2gjJmnV0/4WlXRfG15zc45ySk9Ah8bw==",
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"debug": "~4.4.1", "debug": "~4.4.1",
"ws": "~8.20.1" "ws": "~8.21.0"
} }
}, },
"node_modules/socket.io-adapter/node_modules/debug": { "node_modules/socket.io-adapter/node_modules/debug": {
@@ -1085,9 +1085,9 @@
} }
}, },
"node_modules/ws": { "node_modules/ws": {
"version": "8.20.1", "version": "8.21.1",
"resolved": "https://registry.npmjs.org/ws/-/ws-8.20.1.tgz", "resolved": "https://registry.npmjs.org/ws/-/ws-8.21.1.tgz",
"integrity": "sha512-It4dO0K5v//JtTXuPkfEOaI3uUN87iYPnqo/ZzqCoG3g8uhA66QUMs/SrM0YK7/NAu+r4LMh/9dq2A7k+rHs+w==", "integrity": "sha512-+0NTnW77fFN/DjQi6k/Sq/Yvk4Sgajw7urW8V+asjXnRgDs9gyGkdb7EzgfhA4goXsRIZKE28fzIXBHEzhuiWw==",
"license": "MIT", "license": "MIT",
"engines": { "engines": {
"node": ">=10.0.0" "node": ">=10.0.0"
+54 -4
View File
@@ -269,7 +269,7 @@ run($pdo, "
CREATE TABLE IF NOT EXISTS `md_bin` ( CREATE TABLE IF NOT EXISTS `md_bin` (
`id` int(11) NOT NULL AUTO_INCREMENT, `id` int(11) NOT NULL AUTO_INCREMENT,
`company_id` int(11) NOT NULL, `company_id` int(11) NOT NULL,
`td_stock_id` int(11) NOT NULL DEFAULT 0, `td_stock_id` int(11) DEFAULT NULL,
`warehouse` int(11) NOT NULL, `warehouse` int(11) NOT NULL,
`storage_id` int(11) NOT NULL DEFAULT 0, `storage_id` int(11) NOT NULL DEFAULT 0,
`zone` varchar(10) NOT NULL, `zone` varchar(10) NOT NULL,
@@ -283,6 +283,15 @@ CREATE TABLE IF NOT EXISTS `md_bin` (
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
", 'md_bin'); ", 'md_bin');
// Column made nullable — WarehouseManager::releaseBin() does `SET td_stock_id =
// NULL` to mark a bin empty (mirroring product_sku on the same row), but the
// column was NOT NULL DEFAULT 0. Under strict SQL mode this made releaseBin()
// fail with "Column 'td_stock_id' cannot be null" on every supplier return,
// every fully-depleted stock-out, and every stock transfer. Every read site
// (getBinStock(), occupyBin()) already treats 0 and NULL as equivalent "empty"
// (`if (!$td_stock_id)`), so this is a safe, behavior-preserving widening.
run($pdo, "ALTER TABLE `md_bin` MODIFY `td_stock_id` int(11) DEFAULT NULL", 'md_bin.td_stock_id nullable');
run($pdo, " run($pdo, "
CREATE TABLE IF NOT EXISTS `td_bin_log` ( CREATE TABLE IF NOT EXISTS `td_bin_log` (
`id` bigint(20) unsigned NOT NULL AUTO_INCREMENT, `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT,
@@ -513,7 +522,7 @@ CREATE TABLE IF NOT EXISTS `td_stock` (
`id` int(11) unsigned NOT NULL AUTO_INCREMENT, `id` int(11) unsigned NOT NULL AUTO_INCREMENT,
`ref_id` int(11) unsigned NOT NULL DEFAULT 0, `ref_id` int(11) unsigned NOT NULL DEFAULT 0,
`company_id` int(11) NOT NULL DEFAULT 0, `company_id` int(11) NOT NULL DEFAULT 0,
`uuid` varchar(20) NOT NULL, `uuid` varchar(64) NOT NULL,
`type` varchar(20) NOT NULL, `type` varchar(20) NOT NULL,
`ref_warehouse` varchar(20) NOT NULL DEFAULT '', `ref_warehouse` varchar(20) NOT NULL DEFAULT '',
`date` datetime DEFAULT NULL, `date` datetime DEFAULT NULL,
@@ -540,6 +549,22 @@ CREATE TABLE IF NOT EXISTS `td_stock` (
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb3; ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb3;
", 'td_stock (base template)'); ", 'td_stock (base template)');
// Column widened for db_auth.php's 32-char $uuid (bin2hex(random_bytes(16))) plus
// manager-appended suffixes (e.g. "_ret_0", "_srn_0", "_3") — varchar(20) was too
// narrow and caused "Data too long for column 'uuid'" on every real stock movement
// (PO receipt, order confirm, return confirm, stock transfer) under strict SQL mode.
run($pdo, "ALTER TABLE `td_stock` MODIFY `uuid` varchar(64) NOT NULL", 'td_stock.uuid width');
// Per-warehouse td_stock_<id> tables are cloned from td_stock at warehouse-creation
// time (WarehouseManager::createStockTableForWarehouse()) and are NOT otherwise
// tracked by this script — widen uuid on every one that already exists so
// previously-created warehouses get the same fix. New warehouses get it for free
// from the template above.
$stockTables = $pdo->query("SHOW TABLES LIKE 'td\\_stock\\_%'")->fetchAll(PDO::FETCH_COLUMN);
foreach ($stockTables as $t) {
run($pdo, "ALTER TABLE `{$t}` MODIFY `uuid` varchar(64) NOT NULL", "{$t}.uuid width");
}
run($pdo, " run($pdo, "
CREATE TABLE IF NOT EXISTS `etl_stock_summary` ( CREATE TABLE IF NOT EXISTS `etl_stock_summary` (
`id` int(11) unsigned NOT NULL AUTO_INCREMENT, `id` int(11) unsigned NOT NULL AUTO_INCREMENT,
@@ -558,6 +583,24 @@ CREATE TABLE IF NOT EXISTS `etl_stock_summary` (
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb3; ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb3;
", 'etl_stock_summary'); ", 'etl_stock_summary');
run($pdo, "
CREATE TABLE IF NOT EXISTS `etl_gl_summary` (
`id` int(11) unsigned NOT NULL AUTO_INCREMENT,
`company_id` int(11) unsigned NOT NULL DEFAULT 0,
`acc_code` int(11) NOT NULL DEFAULT 0,
`period` varchar(7) NOT NULL,
`debit` decimal(18,4) NOT NULL DEFAULT 0.0000,
`credit` decimal(18,4) NOT NULL DEFAULT 0.0000,
`count` int(11) NOT NULL DEFAULT 0,
`source_updated_at` datetime DEFAULT NULL,
`updated_at` datetime DEFAULT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `company_id_acc_code_period` (`company_id`,`acc_code`,`period`),
KEY `company_id` (`company_id`),
KEY `period` (`period`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb3;
", 'etl_gl_summary');
// ── Transactions ────────────────────────────────────────────────────────────── // ── Transactions ──────────────────────────────────────────────────────────────
run($pdo, " run($pdo, "
CREATE TABLE IF NOT EXISTS `td_gl` ( CREATE TABLE IF NOT EXISTS `td_gl` (
@@ -795,6 +838,7 @@ CREATE TABLE IF NOT EXISTS `td_return` (
`tax` decimal(18,4) NOT NULL DEFAULT 0.0000, `tax` decimal(18,4) NOT NULL DEFAULT 0.0000,
`tax_adjustment` decimal(8,2) NOT NULL DEFAULT 0.00, `tax_adjustment` decimal(8,2) NOT NULL DEFAULT 0.00,
`notes` text NOT NULL DEFAULT '', `notes` text NOT NULL DEFAULT '',
`formula_id` int(11) DEFAULT NULL,
`log` mediumtext NOT NULL DEFAULT '{}', `log` mediumtext NOT NULL DEFAULT '{}',
PRIMARY KEY (`id`), PRIMARY KEY (`id`),
KEY `company_id` (`company_id`), KEY `company_id` (`company_id`),
@@ -806,6 +850,12 @@ CREATE TABLE IF NOT EXISTS `td_return` (
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb3; ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb3;
", 'td_return'); ", 'td_return');
// Column added for ReturnManager::saveFormula() / custom credit-note GL formula
// selection — was missing from the original table (unlike td_invoice/td_receipt/
// td_payment, which all have formula_id). Without it, ReturnManager::saveReturn()
// fails on every insert.
run($pdo, "ALTER TABLE `td_return` ADD COLUMN `formula_id` int(11) DEFAULT NULL AFTER `notes`", 'td_return.formula_id');
run($pdo, " run($pdo, "
CREATE TABLE IF NOT EXISTS `td_return_item` ( CREATE TABLE IF NOT EXISTS `td_return_item` (
`id` int(11) unsigned NOT NULL AUTO_INCREMENT, `id` int(11) unsigned NOT NULL AUTO_INCREMENT,
@@ -1135,7 +1185,7 @@ CREATE TABLE IF NOT EXISTS `td_payment_item` (
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb3; ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb3;
", 'td_payment_item'); ", 'td_payment_item');
run_ddl(" run($pdo, "
CREATE TABLE IF NOT EXISTS `document_types` ( CREATE TABLE IF NOT EXISTS `document_types` (
`id` int(11) unsigned NOT NULL AUTO_INCREMENT, `id` int(11) unsigned NOT NULL AUTO_INCREMENT,
`company_id` int(11) NOT NULL DEFAULT 0, `company_id` int(11) NOT NULL DEFAULT 0,
@@ -1152,7 +1202,7 @@ CREATE TABLE IF NOT EXISTS `document_types` (
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb3; ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb3;
", 'document_types'); ", 'document_types');
run_ddl(" run($pdo, "
CREATE TABLE IF NOT EXISTS `document_number_sequences` ( CREATE TABLE IF NOT EXISTS `document_number_sequences` (
`id` int(11) unsigned NOT NULL AUTO_INCREMENT, `id` int(11) unsigned NOT NULL AUTO_INCREMENT,
`company_id` int(11) NOT NULL DEFAULT 0, `company_id` int(11) NOT NULL DEFAULT 0,