update last

This commit is contained in:
Flatlogic Bot 2026-05-02 05:05:46 +00:00
parent baf6cef6ff
commit 18ae044a97
9 changed files with 518 additions and 10 deletions

View File

@ -12,7 +12,7 @@
/*!40111 SET @OLD_SQL_NOTES=@@SQL_NOTES, SQL_NOTES=0 */; /*!40111 SET @OLD_SQL_NOTES=@@SQL_NOTES, SQL_NOTES=0 */;
-- Auto-generated full schema snapshot for fresh installs. -- Auto-generated full schema snapshot for fresh installs.
-- Re-run refresh_complete_schema.php after schema changes so new installations stay current. -- Re-run refresh_complete_schema.php after schema or migration changes so new installations stay current.
-- --
-- Table structure for table `acc_accounts` -- Table structure for table `acc_accounts`

View File

@ -0,0 +1,56 @@
<?php
declare(strict_types=1);
// Auto-generated install baseline for fresh installs.
// Refreshed together with complete_schema.sql by refresh_complete_schema.php.
return [
'20260216_add_credit_limit.sql',
'20260216_add_invoices.sql',
'20260216_add_invoice_status.sql',
'20260216_add_payments_table.sql',
'20260216_add_quotations.sql',
'20260216_add_stock_tables.sql',
'20260216_pos_advanced_features.sql',
'20260216_setup_pos_full.sql',
'20260216_update_invoices_payment.sql',
'20260216_update_precision_3_decimal.sql',
'20260216_vat_and_profile.sql',
'20260217_accounting_module.sql',
'20260217_biometric_attendance.sql',
'20260217_biometric_devices.sql',
'20260217_biometric_logs_update.sql',
'20260217_hr_module.sql',
'20260217_hr_payroll_unique.sql',
'20260217_purchase_returns.sql',
'20260217_sales_returns.sql',
'20260217_vat_accounts.sql',
'20260218_create_license_table.sql',
'20260218_modern_loyalty_system.sql',
'20260218_pos_payments.sql',
'20260219_add_license_fields.sql',
'20260219_add_trial_logic.sql',
'20260219_add_vat_to_pos_items.sql',
'20260219_fix_vat_columns.sql',
'20260220_add_due_date_to_invoices.sql',
'20260220_license_server_schema.sql',
'20260220_split_customers_suppliers.sql',
'20260220_split_invoices_purchases.sql',
'20260220_unify_pos_sales.php',
'20260220_unify_pos_sales.sql',
'20260221_add_theme_to_users.sql',
'20260221_fix_invoice_items_columns.sql',
'20260221_fix_pos_invoices_columns.sql',
'20260221_fix_supplier_foreign_keys.sql',
'20260318_add_outlet_id_to_purchases.sql',
'20260318_create_outlets_table.sql',
'20260318_multi_outlet_schema.sql',
'20260318_local_definitions.sql',
'20260318_user_outlets_table.sql',
'20260502_full_schema_sync.php',
'20260502_stock_items_schema_sync.php',
'20260502_zzz_payment_methods_schema_sync.php',
'20260502_zz_financial_documents_schema_sync.php',
'add_outlet_id.sql',
'fix_lpo_foreign_key.sql',
];

View File

@ -0,0 +1,177 @@
<?php
declare(strict_types=1);
require_once __DIR__ . '/../config.php';
if (!function_exists('payment_methods_schema_sync_20260502_run')) {
function payment_methods_schema_sync_20260502_run(): void
{
$pdo = db();
payment_methods_schema_sync_20260502_exec(
$pdo,
"CREATE TABLE IF NOT EXISTS `payment_methods` (
"
. " `id` int(11) NOT NULL AUTO_INCREMENT,
"
. " `name_en` varchar(255) DEFAULT NULL,
"
. " `name_ar` varchar(255) DEFAULT NULL,
"
. " `created_at` timestamp NULL DEFAULT current_timestamp(),
"
. " PRIMARY KEY (`id`)
"
. ") ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci"
);
$columns = [
'name_en' => 'VARCHAR(255) DEFAULT NULL',
'name_ar' => 'VARCHAR(255) DEFAULT NULL',
'created_at' => 'TIMESTAMP NULL DEFAULT CURRENT_TIMESTAMP',
];
foreach ($columns as $column => $definition) {
if (payment_methods_schema_sync_20260502_column_exists($pdo, 'payment_methods', $column)) {
continue;
}
payment_methods_schema_sync_20260502_exec(
$pdo,
sprintf(
'ALTER TABLE `payment_methods` ADD COLUMN `%s` %s',
str_replace('`', '', $column),
$definition
)
);
}
payment_methods_schema_sync_20260502_backfill_legacy_names($pdo);
payment_methods_schema_sync_20260502_seed_defaults($pdo);
}
function payment_methods_schema_sync_20260502_table_exists(PDO $pdo, string $table): bool
{
$stmt = $pdo->prepare(
'SELECT 1 FROM information_schema.TABLES WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = :table LIMIT 1'
);
$stmt->execute(['table' => $table]);
return (bool)$stmt->fetchColumn();
}
function payment_methods_schema_sync_20260502_column_exists(PDO $pdo, string $table, string $column): bool
{
$stmt = $pdo->prepare(
'SELECT 1 FROM information_schema.COLUMNS WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = :table AND COLUMN_NAME = :column LIMIT 1'
);
$stmt->execute([
'table' => $table,
'column' => $column,
]);
return (bool)$stmt->fetchColumn();
}
function payment_methods_schema_sync_20260502_backfill_legacy_names(PDO $pdo): void
{
if (!payment_methods_schema_sync_20260502_table_exists($pdo, 'payment_methods')) {
return;
}
$hasLegacyName = payment_methods_schema_sync_20260502_column_exists($pdo, 'payment_methods', 'name');
$hasNameEn = payment_methods_schema_sync_20260502_column_exists($pdo, 'payment_methods', 'name_en');
$hasNameAr = payment_methods_schema_sync_20260502_column_exists($pdo, 'payment_methods', 'name_ar');
if ($hasLegacyName && $hasNameEn) {
$pdo->exec(
"UPDATE `payment_methods`
SET `name_en` = CASE
WHEN `name_en` IS NULL OR TRIM(`name_en`) = '' THEN `name`
ELSE `name_en`
END
WHERE `name` IS NOT NULL AND TRIM(`name`) <> ''"
);
}
if ($hasNameAr) {
$sourceExpression = $hasLegacyName ? "COALESCE(NULLIF(`name_en`, ''), `name`)" : '`name_en`';
$pdo->exec(
"UPDATE `payment_methods`
SET `name_ar` = $sourceExpression
WHERE (`name_ar` IS NULL OR TRIM(`name_ar`) = '')
AND $sourceExpression IS NOT NULL
AND TRIM($sourceExpression) <> ''"
);
}
}
function payment_methods_schema_sync_20260502_seed_defaults(PDO $pdo): void
{
if (!payment_methods_schema_sync_20260502_table_exists($pdo, 'payment_methods')) {
return;
}
$count = (int)$pdo->query('SELECT COUNT(*) FROM `payment_methods`')->fetchColumn();
if ($count > 0) {
return;
}
$defaults = [
['Cash', 'كاش'],
['Credit Card', 'بطاقة بنكية'],
['Bank Transfer', 'تحويل بنكي'],
];
$stmt = $pdo->prepare('INSERT INTO `payment_methods` (`name_en`, `name_ar`) VALUES (?, ?)');
foreach ($defaults as [$nameEn, $nameAr]) {
$stmt->execute([$nameEn, $nameAr]);
}
}
function payment_methods_schema_sync_20260502_exec(PDO $pdo, string $statement): void
{
try {
$pdo->exec($statement);
} catch (PDOException $exception) {
if (payment_methods_schema_sync_20260502_is_ignorable($exception)) {
return;
}
throw $exception;
}
}
function payment_methods_schema_sync_20260502_is_ignorable(PDOException $exception): bool
{
$driverCode = isset($exception->errorInfo[1]) ? (int)$exception->errorInfo[1] : null;
$message = strtolower($exception->getMessage());
$ignorableCodes = [1050, 1060, 1061, 1062, 1091, 1826];
$ignorableSnippets = [
'already exists',
'duplicate column name',
'duplicate key name',
'duplicate entry',
'duplicate foreign key constraint name',
'duplicate key on write or update',
'errno: 121',
'check that column/key exists',
];
if ($driverCode !== null && in_array($driverCode, $ignorableCodes, true)) {
return true;
}
foreach ($ignorableSnippets as $snippet) {
if (str_contains($message, $snippet)) {
return true;
}
}
return false;
}
}
payment_methods_schema_sync_20260502_run();
return true;

View File

@ -29,6 +29,7 @@ class DatabaseInstaller {
self::executeSqlFile($seedFile); self::executeSqlFile($seedFile);
} }
self::seedInstallMigrationBaseline($pdo, $schemaFile);
self::ensureCurrentSchema(); self::ensureCurrentSchema();
return true; return true;
@ -52,6 +53,57 @@ class DatabaseInstaller {
return (int) $stmt->fetchColumn() === 0; return (int) $stmt->fetchColumn() === 0;
} }
private static function seedInstallMigrationBaseline(PDO $pdo, string $schemaFile): void {
if (!self::shouldSeedInstallMigrationBaseline($schemaFile)) {
return;
}
$baselineMigrations = self::getInstallBaselineMigrations();
if ($baselineMigrations === []) {
return;
}
self::ensureMigrationsTable($pdo);
foreach ($baselineMigrations as $migrationName) {
self::recordMigration($pdo, $migrationName);
}
}
private static function shouldSeedInstallMigrationBaseline(string $schemaFile): bool {
$completeSchemaFile = __DIR__ . '/../complete_schema.sql';
$schemaRealPath = realpath($schemaFile);
$completeSchemaRealPath = realpath($completeSchemaFile);
if ($schemaRealPath === false || $completeSchemaRealPath === false) {
return false;
}
return $schemaRealPath === $completeSchemaRealPath;
}
private static function getInstallBaselineMigrations(): array {
$baselineFile = __DIR__ . '/../db/install_baseline_migrations.php';
if (!is_file($baselineFile)) {
return [];
}
$baseline = require $baselineFile;
if (!is_array($baseline)) {
return [];
}
$normalized = [];
foreach ($baseline as $migrationName) {
if (!is_string($migrationName) || trim($migrationName) === '') {
continue;
}
$normalized[basename($migrationName)] = true;
}
return array_keys($normalized);
}
public static function ensureCurrentSchema(): void { public static function ensureCurrentSchema(): void {
require_once __DIR__ . '/../db/config.php'; require_once __DIR__ . '/../db/config.php';

142
index.php
View File

@ -1973,11 +1973,97 @@ function getPromotionalPrice($item) {
} }
if (isset($_POST['add_payment_method'])) { if (isset($_POST['add_payment_method'])) {
$name = $_POST['name'] ?? ''; $name_en = trim((string)($_POST['name_en'] ?? ''));
db()->prepare("INSERT INTO payment_methods (name) VALUES (?)")->execute([$name]); $name_ar = trim((string)($_POST['name_ar'] ?? ''));
if ($name_en === '' && $name_ar === '') {
redirectWithMessage("Please enter a payment method name.", "index.php?page=payment_methods");
}
if ($name_en === '') {
$name_en = $name_ar;
}
if ($name_ar === '') {
$name_ar = $name_en;
}
if (db_column_exists('payment_methods', 'name_en') || db_column_exists('payment_methods', 'name_ar')) {
$columns = [];
$placeholders = [];
$values = [];
if (db_column_exists('payment_methods', 'name_en')) {
$columns[] = 'name_en';
$placeholders[] = '?';
$values[] = $name_en;
}
if (db_column_exists('payment_methods', 'name_ar')) {
$columns[] = 'name_ar';
$placeholders[] = '?';
$values[] = $name_ar;
}
db()->prepare("INSERT INTO payment_methods (" . implode(', ', $columns) . ") VALUES (" . implode(', ', $placeholders) . ")")->execute($values);
} elseif (db_column_exists('payment_methods', 'name')) {
db()->prepare("INSERT INTO payment_methods (`name`) VALUES (?)")->execute([$name_en]);
} else {
throw new RuntimeException('payment_methods table is missing a usable name column.');
}
redirectWithMessage("Payment method added!", "index.php?page=payment_methods"); redirectWithMessage("Payment method added!", "index.php?page=payment_methods");
} }
if (isset($_POST['edit_payment_method'])) {
$id = (int)($_POST['id'] ?? 0);
$name_en = trim((string)($_POST['name_en'] ?? ''));
$name_ar = trim((string)($_POST['name_ar'] ?? ''));
if ($id <= 0) {
redirectWithMessage("Invalid payment method.", "index.php?page=payment_methods");
}
if ($name_en === '' && $name_ar === '') {
redirectWithMessage("Please enter a payment method name.", "index.php?page=payment_methods");
}
if ($name_en === '') {
$name_en = $name_ar;
}
if ($name_ar === '') {
$name_ar = $name_en;
}
if (db_column_exists('payment_methods', 'name_en') || db_column_exists('payment_methods', 'name_ar')) {
$sets = [];
$values = [];
if (db_column_exists('payment_methods', 'name_en')) {
$sets[] = 'name_en = ?';
$values[] = $name_en;
}
if (db_column_exists('payment_methods', 'name_ar')) {
$sets[] = 'name_ar = ?';
$values[] = $name_ar;
}
$values[] = $id;
db()->prepare("UPDATE payment_methods SET " . implode(', ', $sets) . " WHERE id = ?")->execute($values);
} elseif (db_column_exists('payment_methods', 'name')) {
db()->prepare("UPDATE payment_methods SET `name` = ? WHERE id = ?")->execute([$name_en, $id]);
} else {
throw new RuntimeException('payment_methods table is missing a usable name column.');
}
redirectWithMessage("Payment method updated!", "index.php?page=payment_methods");
}
if (isset($_POST['delete_payment_method'])) {
$id = (int)($_POST['id'] ?? 0);
if ($id > 0) {
db()->prepare("DELETE FROM payment_methods WHERE id = ?")->execute([$id]);
}
redirectWithMessage("Payment method deleted!", "index.php?page=payment_methods");
}
if (isset($_POST['delete_invoice'])) { if (isset($_POST['delete_invoice'])) {
$id = (int)$_POST['id']; $id = (int)$_POST['id'];
$type = ($page === 'purchases') ? 'purchase' : 'sale'; $type = ($page === 'purchases') ? 'purchase' : 'sale';
@ -7647,6 +7733,7 @@ $projectDescription = $_SERVER['PROJECT_DESCRIPTION'] ?? 'Accounting System';
</div> </div>
</div> </div>
<?php elseif ($page === 'payment_methods'): ?>
<div class="card p-4 d-print-none"> <div class="card p-4 d-print-none">
<div class="d-flex justify-content-between align-items-center mb-4"> <div class="d-flex justify-content-between align-items-center mb-4">
<h5 class="m-0" data-en="Payment Methods" data-ar="طرق الدفع">Payment Methods</h5> <h5 class="m-0" data-en="Payment Methods" data-ar="طرق الدفع">Payment Methods</h5>
@ -7665,22 +7752,61 @@ $projectDescription = $_SERVER['PROJECT_DESCRIPTION'] ?? 'Accounting System';
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
<?php foreach ($data['payment_methods'] as $pm): ?> <?php if (empty($data['payment_methods'])): ?>
<tr> <tr>
<td><?= $pm['id'] ?></td> <td colspan="4" class="text-center text-muted py-4" data-en="No payment methods found yet. Add one to get started." data-ar="لا توجد طرق دفع حتى الآن. أضف طريقة دفع للبدء.">No payment methods found yet. Add one to get started.</td>
<td><?= htmlspecialchars($pm['name_en'] ?? '') ?></td> </tr>
<td><?= htmlspecialchars($pm['name_ar'] ?? '') ?></td> <?php else: ?>
<?php foreach ($data['payment_methods'] as $pm): ?>
<?php
$paymentMethodId = (int)($pm['id'] ?? 0);
$paymentMethodNameEn = (string)($pm['name_en'] ?? $pm['name'] ?? '');
$paymentMethodNameAr = (string)($pm['name_ar'] ?? $pm['name_en'] ?? $pm['name'] ?? '');
?>
<tr>
<td><?= $paymentMethodId ?></td>
<td><?= htmlspecialchars($paymentMethodNameEn) ?></td>
<td><?= htmlspecialchars($paymentMethodNameAr) ?></td>
<td class="text-end"> <td class="text-end">
<div class="btn-group btn-group-sm"> <div class="btn-group btn-group-sm">
<button class="btn btn-outline-primary" title="Edit" data-bs-toggle="modal" data-bs-target="#editPaymentMethodModal<?= $pm['id'] ?>"><i class="bi bi-pencil"></i></button> <button class="btn btn-outline-primary" title="Edit" data-bs-toggle="modal" data-bs-target="#editPaymentMethodModal<?= $paymentMethodId ?>"><i class="bi bi-pencil"></i></button>
<form method="POST" class="d-inline" onsubmit="return confirm('Are you sure?')"> <form method="POST" class="d-inline" onsubmit="return confirm('Are you sure?')">
<input type="hidden" name="id" value="<?= $pm['id'] ?>"> <input type="hidden" name="id" value="<?= $paymentMethodId ?>">
<button type="submit" name="delete_payment_method" class="btn btn-outline-danger" title="Delete"><i class="bi bi-trash"></i></button> <button type="submit" name="delete_payment_method" class="btn btn-outline-danger" title="Delete"><i class="bi bi-trash"></i></button>
</form> </form>
</div> </div>
<div class="modal fade" id="editPaymentMethodModal<?= $paymentMethodId ?>" tabindex="-1">
<div class="modal-dialog">
<div class="modal-content border-0 shadow text-start">
<div class="modal-header">
<h5 class="modal-title" data-en="Edit Payment Method" data-ar="تعديل طريقة الدفع">Edit Payment Method</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
</div>
<form method="POST">
<input type="hidden" name="id" value="<?= $paymentMethodId ?>">
<div class="modal-body">
<div class="mb-3">
<label class="form-label" data-en="Name (EN)" data-ar="الاسم (EN)">Name (EN)</label>
<input type="text" name="name_en" class="form-control" value="<?= htmlspecialchars($paymentMethodNameEn) ?>" required>
</div>
<div class="mb-3">
<label class="form-label" data-en="Name (AR)" data-ar="الاسم (AR)">Name (AR)</label>
<input type="text" name="name_ar" class="form-control" value="<?= htmlspecialchars($paymentMethodNameAr) ?>" required>
</div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-light" data-bs-dismiss="modal" data-en="Cancel" data-ar="إلغاء">Cancel</button>
<button type="submit" name="edit_payment_method" class="btn btn-primary" data-en="Update" data-ar="تحديث">Update</button>
</div>
</form>
</div>
</div>
</div>
</td> </td>
</tr> </tr>
<?php endforeach; ?> <?php endforeach; ?>
<?php endif; ?>
</tbody> </tbody>
</table> </table>
</div> </div>

View File

@ -3,3 +3,5 @@
2026-02-26 17:58:46 - Failed login for 'admin'. Reason: Password mismatch 2026-02-26 17:58:46 - Failed login for 'admin'. Reason: Password mismatch
2026-02-26 17:59:03 - Failed login for 'admin'. Reason: Password mismatch 2026-02-26 17:59:03 - Failed login for 'admin'. Reason: Password mismatch
2026-03-17 10:12:37 - Failed login for 'moosa'. Reason: Password mismatch 2026-03-17 10:12:37 - Failed login for 'moosa'. Reason: Password mismatch
2026-05-02 04:46:38 - Failed login for 'moosa'. Reason: Password mismatch
2026-05-02 04:46:47 - Failed login for 'admin'. Reason: Password mismatch

View File

@ -151,3 +151,26 @@
2026-05-02 02:56:23 - POST: {"username":"admin","password":"admin","login":"1"} 2026-05-02 02:56:23 - POST: {"username":"admin","password":"admin","login":"1"}
2026-05-02 03:00:39 - POST: {"username":"admin","password":"admin","login":"1"} 2026-05-02 03:00:39 - POST: {"username":"admin","password":"admin","login":"1"}
2026-05-02 03:32:58 - POST: {"username":"admin","password":"admin","login":"1"} 2026-05-02 03:32:58 - POST: {"username":"admin","password":"admin","login":"1"}
2026-05-02 04:46:38 - POST: {"username":"moosa","password":"moosa123","login":""}
2026-05-02 04:46:47 - POST: {"username":"admin","password":"admin123","login":""}
2026-05-02 04:46:55 - POST: {"username":"admin","password":"admin","login":""}
2026-05-02 04:50:45 - POST: {"username":"admin","password":"admin","login":"1"}
2026-05-02 04:50:50 - POST: {"username":"admin","password":"admin","login":"1"}
2026-05-02 04:50:50 - POST: {"name_en":"Wallet","name_ar":"\u0645\u062d\u0641\u0638\u0629","add_payment_method":"1"}
2026-05-02 04:54:23 - POST: {"username":"admin","password":"admin","login":"1"}
2026-05-02 04:54:23 - POST: {"name_en":"QA Wallet","name_ar":"\u0645\u062d\u0641\u0638\u0629 \u062a\u062c\u0631\u064a\u0628\u064a\u0629","add_payment_method":"1"}
2026-05-02 04:54:32 - POST: {"username":"admin","password":"admin","login":"1"}
2026-05-02 04:54:39 - POST: {"username":"admin","password":"admin","login":"1"}
2026-05-02 04:54:45 - POST: {"username":"admin","password":"admin","login":"1"}
2026-05-02 04:54:49 - POST: {"username":"admin","password":"admin","login":"1"}
2026-05-02 04:54:54 - POST: {"username":"admin","password":"admin","login":"1"}
2026-05-02 04:55:00 - POST: {"username":"admin","password":"admin","login":"1"}
2026-05-02 04:55:44 - POST: {"username":"admin","password":"admin","login":"1"}
2026-05-02 04:55:52 - POST: {"username":"admin","password":"admin","login":"1"}
2026-05-02 04:55:52 - POST: {"name_en":"QA Wallet","name_ar":"\u0645\u062d\u0641\u0638\u0629 \u062a\u062c\u0631\u064a\u0628\u064a\u0629","add_payment_method":"1"}
2026-05-02 04:55:52 - POST: {"id":"5","name_en":"QA Wallet Updated","name_ar":"\u0645\u062d\u0641\u0638\u0629 \u0645\u062d\u062f\u062b\u0629","edit_payment_method":"1"}
2026-05-02 04:55:52 - POST: {"id":"5","delete_payment_method":"1"}
2026-05-02 04:56:04 - POST: {"username":"admin","password":"admin","login":"1"}
2026-05-02 04:56:12 - POST: {"username":"admin","password":"admin","login":"1"}
2026-05-02 04:56:19 - POST: {"username":"admin","password":"admin","login":"1"}
2026-05-02 04:56:42 - POST: {"id":"3","name_en":"Bank Transfer","name_ar":"\u062a\u062d\u0648\u064a\u0644 \u0628\u0646\u0643\u064a","edit_payment_method":""}

View File

@ -83,6 +83,66 @@ function normalizeCreateTableSql(string $sql): string
return rtrim($sql, ';') . ';'; return rtrim($sql, ';') . ';';
} }
function schemaSnapshotMigrationSortKey(string $filePath): string
{
$basename = basename($filePath);
return match ($basename) {
'20260318_add_outlet_id_to_purchases.sql' => '20260318_10_add_outlet_id_to_purchases.sql',
'20260318_create_outlets_table.sql' => '20260318_20_create_outlets_table.sql',
'20260318_multi_outlet_schema.sql' => '20260318_30_multi_outlet_schema.sql',
'20260318_local_definitions.sql' => '20260318_40_local_definitions.sql',
'20260318_user_outlets_table.sql' => '20260318_50_user_outlets_table.sql',
default => $basename,
};
}
function fetchInstallBaselineMigrationNames(): array
{
$files = array_merge(
glob(__DIR__ . '/db/migrations/*.sql') ?: [],
glob(__DIR__ . '/db/migrations/*.php') ?: []
);
usort($files, static function (string $left, string $right): int {
return strnatcasecmp(schemaSnapshotMigrationSortKey($left), schemaSnapshotMigrationSortKey($right));
});
$names = [];
foreach ($files as $filePath) {
$migrationName = basename($filePath);
if ($migrationName === '') {
continue;
}
$names[$migrationName] = true;
}
return array_keys($names);
}
function buildInstallBaselineContent(array $migrationNames): string
{
$lines = [
'<?php',
'declare(strict_types=1);',
'',
'// Auto-generated install baseline for fresh installs.',
'// Refreshed together with complete_schema.sql by refresh_complete_schema.php.',
'',
'return [',
];
foreach ($migrationNames as $migrationName) {
$lines[] = ' ' . var_export((string) $migrationName, true) . ',';
}
$lines[] = '];';
$lines[] = '';
return implode(PHP_EOL, $lines);
}
function fetchBaseTableNames(PDO $pdo): array function fetchBaseTableNames(PDO $pdo): array
{ {
$stmt = $pdo->query("SHOW FULL TABLES WHERE Table_type = 'BASE TABLE'"); $stmt = $pdo->query("SHOW FULL TABLES WHERE Table_type = 'BASE TABLE'");
@ -245,7 +305,7 @@ function buildSnapshotContent(PDO $pdo, array &$tableOrder = []): string
'/*!40111 SET @OLD_SQL_NOTES=@@SQL_NOTES, SQL_NOTES=0 */;', '/*!40111 SET @OLD_SQL_NOTES=@@SQL_NOTES, SQL_NOTES=0 */;',
'', '',
'-- Auto-generated full schema snapshot for fresh installs.', '-- Auto-generated full schema snapshot for fresh installs.',
'-- Re-run refresh_complete_schema.php after schema changes so new installations stay current.', '-- Re-run refresh_complete_schema.php after schema or migration changes so new installations stay current.',
'', '',
]; ];
@ -291,9 +351,20 @@ function runSchemaSnapshotRefresh(): int
throw new RuntimeException('Unable to write complete_schema.sql'); throw new RuntimeException('Unable to write complete_schema.sql');
} }
$baselineNames = fetchInstallBaselineMigrationNames();
$baselineTargetFile = __DIR__ . '/db/install_baseline_migrations.php';
$baselineBytesWritten = file_put_contents($baselineTargetFile, buildInstallBaselineContent($baselineNames));
if ($baselineBytesWritten === false) {
throw new RuntimeException('Unable to write db/install_baseline_migrations.php');
}
snapshotOutput('OK: refreshed complete_schema.sql'); snapshotOutput('OK: refreshed complete_schema.sql');
snapshotOutput('OK: refreshed db/install_baseline_migrations.php');
snapshotOutput('Tables: ' . count($tableOrder)); snapshotOutput('Tables: ' . count($tableOrder));
snapshotOutput('Baseline migrations: ' . count($baselineNames));
snapshotOutput('Path: complete_schema.sql'); snapshotOutput('Path: complete_schema.sql');
snapshotOutput('Path: db/install_baseline_migrations.php');
return 0; return 0;
} catch (Throwable $throwable) { } catch (Throwable $throwable) {
if (PHP_SAPI !== 'cli') { if (PHP_SAPI !== 'cli') {

1
runtime_debug.log Normal file
View File

@ -0,0 +1 @@
2026-05-02 04:50:50 || [PDOException] || SQLSTATE[42S22]: Column not found: 1054 Unknown column 'name' in 'INSERT INTO' || file=/home/ubuntu/executor/workspace/index.php:1977 || page=payment_methods || uri=/index.php?page=payment_methods || user_id=1 || hint=Missing column: name || error_info=["42S22",1054,"Unknown column 'name' in 'INSERT INTO'"] || trace=#0 /home/ubuntu/executor/workspace/index.php(1977): PDOStatement->execute() | #1 {main}