edit schema2
This commit is contained in:
parent
af8d134f1e
commit
baf6cef6ff
216
db/migrations/20260502_zz_financial_documents_schema_sync.php
Normal file
216
db/migrations/20260502_zz_financial_documents_schema_sync.php
Normal file
@ -0,0 +1,216 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
require_once __DIR__ . '/../config.php';
|
||||
|
||||
if (!function_exists('financial_documents_schema_sync_20260502_run')) {
|
||||
function financial_documents_schema_sync_20260502_run(): void
|
||||
{
|
||||
$pdo = db();
|
||||
|
||||
$columns = [
|
||||
'invoices' => [
|
||||
'transaction_no' => 'VARCHAR(50) DEFAULT NULL',
|
||||
'payment_type' => 'VARCHAR(100) DEFAULT NULL',
|
||||
'vat_amount' => 'DECIMAL(15,3) DEFAULT 0.000',
|
||||
'total_with_vat' => 'DECIMAL(15,3) DEFAULT 0.000',
|
||||
'terms_conditions' => 'TEXT NULL',
|
||||
'paid_amount' => 'DECIMAL(15,3) DEFAULT 0.000',
|
||||
'status' => "ENUM('paid','unpaid','partially_paid','refunded','cancelled') DEFAULT 'unpaid'",
|
||||
'register_session_id' => 'INT(11) DEFAULT NULL',
|
||||
'is_pos' => 'TINYINT(1) DEFAULT 0',
|
||||
'discount_amount' => 'DECIMAL(15,3) DEFAULT 0.000',
|
||||
'loyalty_points_earned' => 'DECIMAL(15,3) DEFAULT 0.000',
|
||||
'loyalty_points_redeemed' => 'DECIMAL(15,3) DEFAULT 0.000',
|
||||
'created_by' => 'INT(11) DEFAULT NULL',
|
||||
'outlet_id' => 'INT(11) DEFAULT NULL',
|
||||
],
|
||||
'purchases' => [
|
||||
'payment_type' => 'VARCHAR(100) DEFAULT NULL',
|
||||
'total_amount' => 'DECIMAL(15,3) DEFAULT 0.000',
|
||||
'vat_amount' => 'DECIMAL(15,3) DEFAULT 0.000',
|
||||
'total_with_vat' => 'DECIMAL(15,3) DEFAULT 0.000',
|
||||
'terms_conditions' => 'TEXT NULL',
|
||||
'paid_amount' => 'DECIMAL(15,3) DEFAULT 0.000',
|
||||
'status' => "ENUM('paid','unpaid','partially_paid') DEFAULT 'unpaid'",
|
||||
'register_session_id' => 'INT(11) DEFAULT NULL',
|
||||
'due_date' => 'DATE DEFAULT NULL',
|
||||
'outlet_id' => 'INT(11) DEFAULT 1',
|
||||
],
|
||||
'lpos' => [
|
||||
'delivery_date' => 'DATE DEFAULT NULL',
|
||||
'status' => "ENUM('pending','converted','cancelled') DEFAULT 'pending'",
|
||||
'total_amount' => 'DECIMAL(15,3) DEFAULT 0.000',
|
||||
'vat_amount' => 'DECIMAL(15,3) DEFAULT 0.000',
|
||||
'total_with_vat' => 'DECIMAL(15,3) DEFAULT 0.000',
|
||||
'terms_conditions' => 'TEXT NULL',
|
||||
'outlet_id' => 'INT(11) DEFAULT NULL',
|
||||
],
|
||||
'quotations' => [
|
||||
'status' => "ENUM('pending','converted','expired','cancelled') DEFAULT 'pending'",
|
||||
'total_amount' => 'DECIMAL(15,3) DEFAULT 0.000',
|
||||
'vat_amount' => 'DECIMAL(15,3) DEFAULT 0.000',
|
||||
'total_with_vat' => 'DECIMAL(15,3) DEFAULT 0.000',
|
||||
'terms_conditions' => 'TEXT NULL',
|
||||
'outlet_id' => 'INT(11) DEFAULT NULL',
|
||||
],
|
||||
];
|
||||
|
||||
foreach ($columns as $table => $tableColumns) {
|
||||
if (!financial_documents_schema_sync_20260502_table_exists($pdo, $table)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
foreach ($tableColumns as $column => $definition) {
|
||||
if (financial_documents_schema_sync_20260502_column_exists($pdo, $table, $column)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$statement = sprintf(
|
||||
'ALTER TABLE `%s` ADD COLUMN `%s` %s',
|
||||
str_replace('`', '', $table),
|
||||
str_replace('`', '', $column),
|
||||
$definition
|
||||
);
|
||||
|
||||
financial_documents_schema_sync_20260502_exec($pdo, $statement);
|
||||
}
|
||||
}
|
||||
|
||||
foreach (['invoices', 'purchases', 'lpos', 'quotations'] as $table) {
|
||||
financial_documents_schema_sync_20260502_backfill_total_with_vat($pdo, $table);
|
||||
}
|
||||
|
||||
foreach (['invoices', 'purchases'] as $table) {
|
||||
financial_documents_schema_sync_20260502_backfill_paid_amount($pdo, $table);
|
||||
}
|
||||
|
||||
foreach (['purchases', 'lpos', 'quotations'] as $table) {
|
||||
financial_documents_schema_sync_20260502_default_outlet_id($pdo, $table);
|
||||
}
|
||||
}
|
||||
|
||||
function financial_documents_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 financial_documents_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 financial_documents_schema_sync_20260502_exec(PDO $pdo, string $statement): void
|
||||
{
|
||||
try {
|
||||
$pdo->exec($statement);
|
||||
} catch (PDOException $exception) {
|
||||
if (financial_documents_schema_sync_20260502_is_ignorable($exception)) {
|
||||
return;
|
||||
}
|
||||
|
||||
throw $exception;
|
||||
}
|
||||
}
|
||||
|
||||
function financial_documents_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;
|
||||
}
|
||||
|
||||
function financial_documents_schema_sync_20260502_backfill_total_with_vat(PDO $pdo, string $table): void
|
||||
{
|
||||
if (
|
||||
!financial_documents_schema_sync_20260502_table_exists($pdo, $table)
|
||||
|| !financial_documents_schema_sync_20260502_column_exists($pdo, $table, 'total_amount')
|
||||
|| !financial_documents_schema_sync_20260502_column_exists($pdo, $table, 'vat_amount')
|
||||
|| !financial_documents_schema_sync_20260502_column_exists($pdo, $table, 'total_with_vat')
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
$sql = sprintf(
|
||||
'UPDATE `%1$s` SET `total_with_vat` = COALESCE(`total_amount`, 0) + COALESCE(`vat_amount`, 0) WHERE `total_with_vat` IS NULL OR (`total_with_vat` = 0 AND (COALESCE(`total_amount`, 0) <> 0 OR COALESCE(`vat_amount`, 0) <> 0))',
|
||||
str_replace('`', '', $table)
|
||||
);
|
||||
|
||||
$pdo->exec($sql);
|
||||
}
|
||||
|
||||
function financial_documents_schema_sync_20260502_backfill_paid_amount(PDO $pdo, string $table): void
|
||||
{
|
||||
if (
|
||||
!financial_documents_schema_sync_20260502_table_exists($pdo, $table)
|
||||
|| !financial_documents_schema_sync_20260502_column_exists($pdo, $table, 'status')
|
||||
|| !financial_documents_schema_sync_20260502_column_exists($pdo, $table, 'paid_amount')
|
||||
|| !financial_documents_schema_sync_20260502_column_exists($pdo, $table, 'total_with_vat')
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
$sql = sprintf(
|
||||
'UPDATE `%1$s` SET `paid_amount` = COALESCE(`total_with_vat`, 0) WHERE `status` = \'paid\' AND (`paid_amount` IS NULL OR `paid_amount` = 0)',
|
||||
str_replace('`', '', $table)
|
||||
);
|
||||
|
||||
$pdo->exec($sql);
|
||||
}
|
||||
|
||||
function financial_documents_schema_sync_20260502_default_outlet_id(PDO $pdo, string $table): void
|
||||
{
|
||||
if (
|
||||
!financial_documents_schema_sync_20260502_table_exists($pdo, $table)
|
||||
|| !financial_documents_schema_sync_20260502_column_exists($pdo, $table, 'outlet_id')
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
$sql = sprintf(
|
||||
'UPDATE `%1$s` SET `outlet_id` = 1 WHERE `outlet_id` IS NULL',
|
||||
str_replace('`', '', $table)
|
||||
);
|
||||
|
||||
$pdo->exec($sql);
|
||||
}
|
||||
}
|
||||
|
||||
financial_documents_schema_sync_20260502_run();
|
||||
|
||||
return true;
|
||||
53
index.php
53
index.php
@ -82,6 +82,27 @@ if (!function_exists('db_table_exists')) {
|
||||
}
|
||||
}
|
||||
|
||||
if (!function_exists('db_column_exists')) {
|
||||
function db_column_exists(string $tableName, string $columnName): bool {
|
||||
static $cache = [];
|
||||
|
||||
$cacheKey = strtolower($tableName . '.' . $columnName);
|
||||
if (array_key_exists($cacheKey, $cache)) {
|
||||
return $cache[$cacheKey];
|
||||
}
|
||||
|
||||
try {
|
||||
$stmt = db()->prepare("SELECT 1 FROM information_schema.COLUMNS WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = ? AND COLUMN_NAME = ? LIMIT 1");
|
||||
$stmt->execute([$tableName, $columnName]);
|
||||
$cache[$cacheKey] = (bool)$stmt->fetchColumn();
|
||||
} catch (Throwable $e) {
|
||||
$cache[$cacheKey] = false;
|
||||
}
|
||||
|
||||
return $cache[$cacheKey];
|
||||
}
|
||||
}
|
||||
|
||||
if (!function_exists('runtime_debug_can_render_details')) {
|
||||
function runtime_debug_can_render_details(): bool {
|
||||
if (PHP_SAPI === 'cli') {
|
||||
@ -454,15 +475,31 @@ function can(string $permission): bool {
|
||||
}
|
||||
|
||||
function getPurchaseAlerts() {
|
||||
if (!can('dashboard_view')) return [];
|
||||
if (!can('dashboard_view') || !db_table_exists('purchases')) return [];
|
||||
|
||||
$db = db();
|
||||
$stmt = $db->query("SELECT p.id, p.due_date, p.total_with_vat, s.name as supplier_name
|
||||
FROM purchases p
|
||||
LEFT JOIN suppliers s ON p.supplier_id = s.id
|
||||
WHERE p.status != 'paid'
|
||||
AND p.due_date IS NOT NULL
|
||||
AND p.due_date <= DATE_ADD(CURDATE(), INTERVAL 7 DAY)
|
||||
ORDER BY p.due_date ASC");
|
||||
$hasSupplierJoin = db_table_exists('suppliers') && db_column_exists('purchases', 'supplier_id');
|
||||
$dueDateExpression = db_column_exists('purchases', 'due_date') ? 'p.due_date' : 'NULL';
|
||||
$statusPredicate = db_column_exists('purchases', 'status') ? "WHERE p.status != 'paid'" : 'WHERE 1=1';
|
||||
$dueDatePredicate = db_column_exists('purchases', 'due_date')
|
||||
? ' AND p.due_date IS NOT NULL AND p.due_date <= DATE_ADD(CURDATE(), INTERVAL 7 DAY)'
|
||||
: '';
|
||||
$totalExpression = db_column_exists('purchases', 'total_with_vat')
|
||||
? 'p.total_with_vat'
|
||||
: (db_column_exists('purchases', 'total_amount') ? 'COALESCE(p.total_amount, 0)' : '0');
|
||||
$supplierExpression = $hasSupplierJoin ? 's.name' : 'NULL';
|
||||
$joinClause = $hasSupplierJoin ? ' LEFT JOIN suppliers s ON p.supplier_id = s.id' : '';
|
||||
$orderBy = db_column_exists('purchases', 'due_date') ? ' ORDER BY p.due_date ASC' : ' ORDER BY p.id DESC';
|
||||
|
||||
$sql = "SELECT p.id, {$dueDateExpression} AS due_date, {$totalExpression} AS total_with_vat, {$supplierExpression} AS supplier_name"
|
||||
. ' FROM purchases p'
|
||||
. $joinClause
|
||||
. ' '
|
||||
. $statusPredicate
|
||||
. $dueDatePredicate
|
||||
. $orderBy;
|
||||
|
||||
$stmt = $db->query($sql);
|
||||
return $stmt->fetchAll(PDO::FETCH_ASSOC);
|
||||
}
|
||||
|
||||
|
||||
@ -150,3 +150,4 @@
|
||||
2026-05-01 19:32:47 - 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:32:58 - POST: {"username":"admin","password":"admin","login":"1"}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user