update last
This commit is contained in:
parent
baf6cef6ff
commit
18ae044a97
@ -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`
|
||||||
|
|||||||
56
db/install_baseline_migrations.php
Normal file
56
db/install_baseline_migrations.php
Normal 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',
|
||||||
|
];
|
||||||
177
db/migrations/20260502_zzz_payment_methods_schema_sync.php
Normal file
177
db/migrations/20260502_zzz_payment_methods_schema_sync.php
Normal 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;
|
||||||
@ -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
142
index.php
@ -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>
|
||||||
|
|||||||
@ -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
|
||||||
|
|||||||
@ -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":""}
|
||||||
|
|||||||
@ -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
1
runtime_debug.log
Normal 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}
|
||||||
Loading…
x
Reference in New Issue
Block a user