update index

This commit is contained in:
Flatlogic Bot 2026-05-03 02:57:40 +00:00
parent 9108deb2d9
commit df0eee1043
11 changed files with 288 additions and 65 deletions

View File

@ -715,3 +715,94 @@ body:not(.theme-default) .form-select:focus {
.form-grid-3 .input-group {
width: 100%;
}
/* Document list filters + table */
.documents-filter {
display: flex;
align-items: flex-end;
gap: 0.75rem;
flex-wrap: nowrap;
}
.documents-filter__field {
flex: 1 1 0;
min-width: 0;
}
.documents-filter__field--search {
flex: 1.45 1 230px;
}
.documents-filter__field--party {
flex: 1.2 1 210px;
}
.documents-filter__field--date {
flex: 0 1 150px;
}
.documents-filter__field .form-label {
margin-bottom: 0.35rem;
}
.documents-filter__field .form-control,
.documents-filter__field .form-select {
width: 100%;
}
.documents-filter__actions {
display: flex;
flex: 0 0 auto;
align-items: flex-end;
justify-content: flex-end;
gap: 0.5rem;
flex-wrap: wrap;
margin-left: auto;
}
.documents-filter__actions .btn,
.documents-filter__actions .dropdown-toggle {
white-space: nowrap;
}
.documents-table__party {
min-width: 190px;
}
.documents-table__dates {
min-width: 148px;
}
@media (max-width: 1399.98px) {
.documents-filter {
flex-wrap: wrap;
}
.documents-filter__field--date {
flex: 1 1 165px;
}
.documents-filter__actions {
width: 100%;
justify-content: flex-start;
margin-left: 0;
}
}
@media (max-width: 767.98px) {
.documents-filter {
gap: 0.6rem;
}
.documents-filter__field,
.documents-filter__actions {
flex: 1 1 100%;
width: 100%;
}
.documents-filter__actions .btn,
.documents-filter__actions .dropdown {
flex: 1 1 calc(50% - 0.3rem);
}
}

View File

@ -316,6 +316,7 @@ CREATE TABLE IF NOT EXISTS `invoices` (
`transaction_no` varchar(50) DEFAULT NULL,
`customer_id` int(11) DEFAULT NULL,
`invoice_date` date NOT NULL,
`due_date` date DEFAULT NULL,
`type` enum('sale','purchase') NOT NULL,
`payment_type` varchar(100) DEFAULT NULL,
`total_amount` decimal(15,3) DEFAULT 0.000,

View File

@ -50,6 +50,7 @@ return [
'20260502_stock_items_schema_sync.php',
'20260502_zzz_payment_methods_schema_sync.php',
'20260502_zz_financial_documents_schema_sync.php',
'20260503_zz_ensure_invoice_due_date.php',
'add_outlet_id.sql',
'fix_lpo_foreign_key.sql',
];

View File

@ -12,6 +12,7 @@ if (!function_exists('financial_documents_schema_sync_20260502_run')) {
'invoices' => [
'transaction_no' => 'VARCHAR(50) DEFAULT NULL',
'payment_type' => 'VARCHAR(100) DEFAULT NULL',
'due_date' => 'DATE DEFAULT NULL',
'vat_amount' => 'DECIMAL(15,3) DEFAULT 0.000',
'total_with_vat' => 'DECIMAL(15,3) DEFAULT 0.000',
'terms_conditions' => 'TEXT NULL',

View File

@ -4,6 +4,19 @@ declare(strict_types=1);
require_once dirname(__DIR__) . '/config.php';
require_once dirname(__DIR__, 2) . '/includes/accounting_helper.php';
if (function_exists('seedDefaultAccountingAccounts')) {
seedDefaultAccountingAccounts();
if (function_exists('ensureAccountingSchema')) {
ensureAccountingSchema();
}
$added = function_exists('seedDefaultAccountingAccounts') ? seedDefaultAccountingAccounts() : 0;
$total = 0;
try {
$total = (int) db()->query('SELECT COUNT(*) FROM acc_accounts')->fetchColumn();
} catch (Throwable $e) {
error_log('Chart of accounts count failed: ' . $e->getMessage());
}
echo $added > 0
? sprintf('Chart of accounts seeded successfully. Added %d account(s); total is now %d.', $added, $total)
: sprintf('Chart of accounts already up to date. Total accounts: %d.', $total);

View File

@ -0,0 +1,27 @@
<?php
declare(strict_types=1);
require_once __DIR__ . '/../config.php';
$pdo = db();
$tableStmt = $pdo->prepare(
'SELECT 1 FROM information_schema.TABLES WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = :table LIMIT 1'
);
$tableStmt->execute(['table' => 'invoices']);
if ((bool)$tableStmt->fetchColumn()) {
$columnStmt = $pdo->prepare(
'SELECT 1 FROM information_schema.COLUMNS WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = :table AND COLUMN_NAME = :column LIMIT 1'
);
$columnStmt->execute([
'table' => 'invoices',
'column' => 'due_date',
]);
if (!(bool)$columnStmt->fetchColumn()) {
$pdo->exec('ALTER TABLE `invoices` ADD COLUMN `due_date` DATE DEFAULT NULL AFTER `invoice_date`');
}
}
return true;

View File

@ -359,6 +359,7 @@ CREATE TABLE `invoices` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`customer_id` int(11) DEFAULT NULL,
`invoice_date` date NOT NULL,
`due_date` date DEFAULT NULL,
`type` enum('sale','purchase') NOT NULL,
`payment_type` varchar(100) DEFAULT NULL,
`total_amount` decimal(15,3) DEFAULT 0.000,

View File

@ -3,10 +3,14 @@
* Accounting Helper for Automatic Journal Entries
*/
function accountingTableExists(string $tableName): bool {
function accountingTableExists(string $tableName, bool $refresh = false): bool {
static $cache = [];
$normalized = strtolower($tableName);
if ($refresh) {
unset($cache[$normalized]);
}
if (array_key_exists($normalized, $cache)) {
return $cache[$normalized];
}
@ -25,6 +29,50 @@ function accountingTableExists(string $tableName): bool {
return $cache[$normalized];
}
function ensureAccountingSchema(): void {
$db = db();
$db->exec(<<<SQL
CREATE TABLE IF NOT EXISTS acc_accounts (
id INT AUTO_INCREMENT PRIMARY KEY,
code VARCHAR(20) UNIQUE NOT NULL,
name_en VARCHAR(100) NOT NULL,
name_ar VARCHAR(100) NOT NULL,
type ENUM('asset', 'liability', 'equity', 'revenue', 'expense') NOT NULL,
parent_id INT NULL,
FOREIGN KEY (parent_id) REFERENCES acc_accounts(id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4
SQL);
$db->exec(<<<SQL
CREATE TABLE IF NOT EXISTS acc_journal_entries (
id INT AUTO_INCREMENT PRIMARY KEY,
entry_date DATE NOT NULL,
description TEXT,
reference VARCHAR(100),
source_type VARCHAR(50),
source_id INT,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4
SQL);
$db->exec(<<<SQL
CREATE TABLE IF NOT EXISTS acc_ledger (
id INT AUTO_INCREMENT PRIMARY KEY,
journal_entry_id INT NOT NULL,
account_id INT NOT NULL,
debit DECIMAL(15, 3) DEFAULT 0,
credit DECIMAL(15, 3) DEFAULT 0,
FOREIGN KEY (journal_entry_id) REFERENCES acc_journal_entries(id) ON DELETE CASCADE,
FOREIGN KEY (account_id) REFERENCES acc_accounts(id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4
SQL);
accountingTableExists('acc_accounts', true);
accountingTableExists('acc_journal_entries', true);
accountingTableExists('acc_ledger', true);
}
function getDefaultAccountingAccounts(): array {
return [
['code' => '1000', 'name_en' => 'Assets', 'name_ar' => 'الأصول', 'type' => 'asset', 'parent_code' => null],
@ -59,7 +107,9 @@ function getDefaultAccountingAccounts(): array {
}
function seedDefaultAccountingAccounts(): int {
if (!accountingTableExists('acc_accounts')) {
ensureAccountingSchema();
if (!accountingTableExists('acc_accounts', true)) {
return 0;
}
@ -120,7 +170,9 @@ function seedDefaultAccountingAccounts(): int {
}
function createAccountingAccount(string $code, string $nameEn, string $nameAr, string $type, ?int $parentId = null): array {
if (!accountingTableExists('acc_accounts')) {
ensureAccountingSchema();
if (!accountingTableExists('acc_accounts', true)) {
return ['success' => false, 'error' => 'Accounting tables are not ready yet.'];
}

151
index.php
View File

@ -3622,19 +3622,25 @@ if ($page === 'export') {
$where = ["1=1"];
$params = [];
$referenceSearchColumn = db_column_exists($table, 'transaction_no') ? 'transaction_no' : null;
if (!empty($_GET['search'])) {
$s = $_GET['search'];
$s = trim((string)$_GET['search']);
$clean_id = preg_replace('/[^0-9]/', '', $s);
if ($clean_id !== '') {
$where[] = "(v.id LIKE ? OR c.name LIKE ? OR v.id = ?)";
$params[] = "%$s%";
$params[] = "%$s%";
$params[] = $clean_id;
} else {
$where[] = "(v.id LIKE ? OR c.name LIKE ?)";
$params[] = "%$s%";
$params[] = "%$s%";
$searchClauses = ["CAST(v.id AS CHAR) LIKE ?", "c.name LIKE ?"];
$searchParams = ["%$s%", "%$s%"];
if ($referenceSearchColumn !== null) {
$searchClauses[] = "v.$referenceSearchColumn LIKE ?";
$searchParams[] = "%$s%";
}
if ($clean_id !== '') {
$searchClauses[] = "v.id = ?";
$searchParams[] = $clean_id;
}
$where[] = '(' . implode(' OR ', $searchClauses) . ')';
$params = array_merge($params, $searchParams);
}
if (!empty($_GET['customer_id'])) { $where[] = "v.$cust_col = ?"; $params[] = $_GET['customer_id']; }
if (!empty($_GET['start_date'])) { $where[] = "v.invoice_date >= ?"; $params[] = $_GET['start_date']; }
@ -4064,19 +4070,25 @@ switch ($page) {
$where = ["1=1"];
$params = [];
$referenceSearchColumn = db_column_exists($table, 'transaction_no') ? 'transaction_no' : null;
if (!empty($_GET['search'])) {
$s = $_GET['search'];
$s = trim((string)$_GET['search']);
$clean_id = preg_replace('/[^0-9]/', '', $s);
if ($clean_id !== '') {
$where[] = "(v.id LIKE ? OR c.name LIKE ? OR v.id = ?)";
$params[] = "%$s%";
$params[] = "%$s%";
$params[] = $clean_id;
} else {
$where[] = "(v.id LIKE ? OR c.name LIKE ?)";
$params[] = "%$s%";
$params[] = "%$s%";
$searchClauses = ["CAST(v.id AS CHAR) LIKE ?", "c.name LIKE ?"];
$searchParams = ["%$s%", "%$s%"];
if ($referenceSearchColumn !== null) {
$searchClauses[] = "v.$referenceSearchColumn LIKE ?";
$searchParams[] = "%$s%";
}
if ($clean_id !== '') {
$searchClauses[] = "v.id = ?";
$searchParams[] = $clean_id;
}
$where[] = '(' . implode(' OR ', $searchClauses) . ')';
$params = array_merge($params, $searchParams);
}
if (!empty($_GET['customer_id'])) {
@ -4118,8 +4130,31 @@ switch ($page) {
ORDER BY v.id DESC LIMIT $limit OFFSET $offset");
$stmt->execute($params);
$data['invoices'] = $stmt->fetchAll();
$documentPrefix = ($type === 'purchase') ? 'PUR' : 'INV';
foreach ($data['invoices'] as &$inv) {
$inv['due_date'] = $inv['due_date'] ?? null;
$transactionNo = trim((string)($inv['transaction_no'] ?? ''));
$partyFallback = ($type === 'sale' && !empty($inv['is_pos'])) ? 'Walk-in Customer' : '---';
$normalizedPaymentType = strtolower(str_replace([' ', '-'], '_', (string)($inv['payment_type'] ?? 'cash')));
$paymentTypeLabel = 'Cash';
if ($normalizedPaymentType === 'bank_transfer') {
$paymentTypeLabel = 'Bank Transfer';
} elseif (in_array($normalizedPaymentType, ['card', 'credit_card'], true)) {
$paymentTypeLabel = 'Card';
} elseif ($normalizedPaymentType === 'credit') {
$paymentTypeLabel = 'Credit';
}
$inv['party_name'] = trim((string)($inv['customer_name'] ?? '')) !== '' ? (string)$inv['customer_name'] : $partyFallback;
$inv['document_no'] = ($type === 'sale' && $transactionNo !== '') ? $transactionNo : $documentPrefix . '-' . str_pad((string)$inv['id'], 5, '0', STR_PAD_LEFT);
$inv['payment_type'] = $normalizedPaymentType;
$inv['payment_type_label'] = $paymentTypeLabel;
$inv['total_with_vat'] = (float)($inv['total_with_vat'] ?? (($inv['total_amount'] ?? 0) + ($inv['vat_amount'] ?? 0)));
$inv['paid_amount'] = (float)($inv['paid_amount'] ?? 0);
$inv['balance_amount'] = max($inv['total_with_vat'] - $inv['paid_amount'], 0);
$inv['total_in_words'] = numberToWordsOMR($inv['total_with_vat']);
if ($type === 'sale') {
$item_stmt = db()->prepare("SELECT ii.*, i.name_en, i.name_ar, i.vat_rate FROM invoice_items ii LEFT JOIN stock_items i ON ii.item_id = i.id WHERE ii.invoice_id = ?");
$item_stmt->execute([$inv['id']]);
@ -7960,13 +7995,14 @@ $projectDescription = $_SERVER['PROJECT_DESCRIPTION'] ?? 'Accounting System';
<!-- Filters Section -->
<div class="bg-light p-3 rounded mb-4 d-print-none">
<form method="GET" class="form-grid-3">
<form method="GET" class="documents-filter">
<input type="hidden" name="page" value="<?= $page ?>">
<div class="col-md-3">
<input type="hidden" name="limit" value="<?= (int)($_GET['limit'] ?? 20) ?>">
<div class="documents-filter__field documents-filter__field--search">
<label class="form-label small fw-bold" data-en="Search" data-ar="بحث">Search</label>
<input type="text" name="search" class="form-control form-control-sm" value="<?= htmlspecialchars($_GET['search'] ?? '') ?>" placeholder="Inv # or Name...">
<input type="text" name="search" class="form-control form-control-sm" value="<?= htmlspecialchars($_GET['search'] ?? '') ?>" placeholder="<?= $page === 'sales' ? 'Invoice #, transaction # or customer...' : 'Purchase # or supplier...' ?>">
</div>
<div class="col-md-3">
<div class="documents-filter__field documents-filter__field--party">
<label class="form-label small fw-bold" data-en="<?= $page === 'sales' ? 'Customer' : 'Supplier' ?>" data-ar="<?= $page === 'sales' ? 'العميل' : 'المورد' ?>"><?= $page === 'sales' ? 'Customer' : 'Supplier' ?></label>
<select name="customer_id" class="form-select form-select-sm">
<option value="" data-en="All" data-ar="الكل">All</option>
@ -7975,23 +8011,23 @@ $projectDescription = $_SERVER['PROJECT_DESCRIPTION'] ?? 'Accounting System';
<?php endforeach; ?>
</select>
</div>
<div class="col-md-2">
<div class="documents-filter__field documents-filter__field--date">
<label class="form-label small fw-bold" data-en="Start Date" data-ar="من تاريخ">Start Date</label>
<input type="date" name="start_date" class="form-control form-control-sm" value="<?= htmlspecialchars($_GET['start_date'] ?? '') ?>">
</div>
<div class="col-md-2">
<div class="documents-filter__field documents-filter__field--date">
<label class="form-label small fw-bold" data-en="End Date" data-ar="إلى تاريخ">End Date</label>
<input type="date" name="end_date" class="form-control form-control-sm" value="<?= htmlspecialchars($_GET['end_date'] ?? '') ?>">
</div>
<div class="col-md-2 d-flex align-items-end gap-1">
<button type="submit" class="btn btn-primary btn-sm flex-grow-1">
<div class="documents-filter__actions">
<button type="submit" class="btn btn-primary btn-sm">
<i class="bi bi-filter"></i> <span data-en="Filter" data-ar="تصفية">Filter</span>
</button>
<div class="dropdown d-inline-block">
<button class="btn btn-success btn-sm dropdown-toggle" type="button" data-bs-toggle="dropdown">
<i class="bi bi-download"></i> <span data-en="Export" data-ar="تصدير">Export</span>
</button>
<ul class="dropdown-menu">
<ul class="dropdown-menu dropdown-menu-end">
<li><a class="dropdown-item" href="index.php?<?= http_build_query(array_merge($_GET, ['page' => 'export', 'type' => $page, 'format' => 'csv'])) ?>"><i class="bi bi-filetype-csv me-2"></i> CSV</a></li>
<li><a class="dropdown-item" href="index.php?<?= http_build_query(array_merge($_GET, ['page' => 'export', 'type' => $page, 'format' => 'excel'])) ?>"><i class="bi bi-file-earmark-excel me-2"></i> Excel</a></li>
</ul>
@ -7999,32 +8035,20 @@ $projectDescription = $_SERVER['PROJECT_DESCRIPTION'] ?? 'Accounting System';
<button type="button" class="btn btn-outline-primary btn-sm" onclick="window.print()">
<i class="bi bi-printer"></i> <span data-en="Print" data-ar="طباعة">Print</span>
</button>
<a href="index.php?page=<?= $page ?>" class="btn btn-outline-secondary btn-sm flex-grow-1">
<a href="index.php?page=<?= $page ?>" class="btn btn-outline-secondary btn-sm">
<i class="bi bi-x-circle"></i> <span data-en="Clear" data-ar="مسح">Clear</span>
</a>
</div>
<div class="col-md-auto ms-auto d-flex align-items-end mt-2 mt-md-0">
<div class="input-group input-group-sm w-auto">
<span class="input-group-text" data-en="Limit" data-ar="الحد">Limit</span>
<select name="limit" class="form-select" onchange="this.form.submit()">
<option value="20" <?= (($_GET['limit'] ?? 20) == 20) ? 'selected' : '' ?>>20</option>
<option value="40" <?= (($_GET['limit'] ?? 20) == 40) ? 'selected' : '' ?>>40</option>
<option value="60" <?= (($_GET['limit'] ?? 20) == 60) ? 'selected' : '' ?>>60</option>
<option value="100" <?= (($_GET['limit'] ?? 20) == 100) ? 'selected' : '' ?>>100</option>
</select>
</div>
</div>
</form>
</div>
<div class="table-responsive">
<table class="table table-hover align-middle">
<table class="table table-hover align-middle documents-table">
<thead>
<tr>
<th data-en="Invoice #" data-ar="رقم الفاتورة">Invoice #</th>
<th data-en="Date" data-ar="التاريخ">Date</th>
<th data-en="Due Date" data-ar="تاريخ الاستحقاق">Due Date</th>
<th data-en="<?= $page === 'sales' ? 'Customer' : 'Supplier' ?>" data-ar="<?= $page === 'sales' ? 'العميل' : 'المورد' ?>"><?= $page === 'sales' ? 'Customer' : 'Supplier' ?></th>
<th data-en="Dates" data-ar="التواريخ">Dates</th>
<th data-en="Payment" data-ar="الدفع">Payment</th>
<th data-en="Status" data-ar="الحالة">Status</th>
<th data-en="Total" data-ar="الإجمالي" class="text-end">Total</th>
<th data-en="Paid" data-ar="المدفوع" class="text-end">Paid</th>
@ -8040,34 +8064,36 @@ $projectDescription = $_SERVER['PROJECT_DESCRIPTION'] ?? 'Accounting System';
foreach ($data['invoices'] as $inv):
$total_all += (float)$inv['total_with_vat'];
$total_paid += (float)$inv['paid_amount'];
$total_balance += ((float)$inv['total_with_vat'] - (float)$inv['paid_amount']);
$total_balance += (float)$inv['balance_amount'];
$items = db()->prepare("SELECT ii.*, i.name_en, i.name_ar, i.vat_rate
FROM $itemTable ii
JOIN stock_items i ON ii.item_id = i.id
WHERE ii.$fkCol = ?");
$items->execute([$inv['id']]);
$inv['items'] = $items->fetchAll(PDO::FETCH_ASSOC);
$prefix = ($page === 'purchases') ? 'PUR' : 'INV';
$isOverdue = !empty($inv['due_date']) && strtotime((string)$inv['due_date']) < time() && ($inv['status'] ?? '') !== 'paid';
?>
<tr>
<td><?= !empty($inv['is_pos']) && !empty($inv['transaction_no']) ? htmlspecialchars($inv['transaction_no']) : $prefix . '-' . str_pad((string)$inv['id'], 5, '0', STR_PAD_LEFT) ?></td>
<td><?= $inv['invoice_date'] ?></td>
<td>
<?php if ($inv['due_date']): ?>
<?php
$isOverdue = strtotime($inv['due_date']) < time() && $inv['status'] !== 'paid';
?>
<td class="fw-semibold"><?= htmlspecialchars($inv['document_no']) ?></td>
<td class="documents-table__party">
<div class="fw-semibold text-dark"><?= htmlspecialchars($inv['party_name']) ?></div>
<?php if (!empty($inv['customer_phone'])): ?>
<div class="small text-muted"><?= htmlspecialchars($inv['customer_phone']) ?></div>
<?php endif; ?>
</td>
<td class="documents-table__dates">
<div class="small text-nowrap"><i class="bi bi-calendar-event text-muted me-1"></i><?= htmlspecialchars($inv['invoice_date'] ?? '---') ?></div>
<div class="small text-nowrap">
<i class="bi bi-hourglass-split text-muted me-1"></i>
<span class="<?= $isOverdue ? 'text-danger fw-bold' : '' ?>">
<?= $inv['due_date'] ?>
<?= htmlspecialchars($inv['due_date'] ?: '---') ?>
<?php if ($isOverdue): ?>
<i class="bi bi-exclamation-triangle-fill ms-1" title="Overdue"></i>
<?php endif; ?>
</span>
<?php else: ?>
---
<?php endif; ?>
</div>
</td>
<td><?= htmlspecialchars($inv['customer_name'] ?? '---') ?></td>
<td><span class="badge bg-light text-dark border"><?= htmlspecialchars($inv['payment_type_label']) ?></span></td>
<td>
<?php
$statusClass = 'bg-secondary';
@ -8079,7 +8105,7 @@ $projectDescription = $_SERVER['PROJECT_DESCRIPTION'] ?? 'Accounting System';
</td>
<td class="text-end fw-bold">OMR <?= number_format((float)$inv['total_with_vat'], 3) ?></td>
<td class="text-end text-success">OMR <?= number_format((float)$inv['paid_amount'], 3) ?></td>
<td class="text-end text-danger fw-bold">OMR <?= number_format((float)($inv['total_with_vat'] - $inv['paid_amount']), 3) ?></td>
<td class="text-end text-danger fw-bold">OMR <?= number_format((float)$inv['balance_amount'], 3) ?></td>
<td class="text-end d-print-none">
<div class="btn-group btn-group-sm">
<button class="btn btn-outline-info view-invoice-btn" data-json="<?= htmlspecialchars(json_encode($inv)) ?>" title="View"><i class="bi bi-eye"></i></button>
@ -8097,6 +8123,11 @@ $projectDescription = $_SERVER['PROJECT_DESCRIPTION'] ?? 'Accounting System';
</td>
</tr>
<?php endforeach; ?>
<?php if (empty($data['invoices'])): ?>
<tr>
<td colspan="9" class="text-center py-4 text-muted" data-en="No invoices found" data-ar="لا توجد فواتير">No invoices found</td>
</tr>
<?php endif; ?>
</tbody>
<tfoot>
<tr class="fw-bold bg-light">

View File

@ -1,4 +1,8 @@
<?php
if (function_exists('ensureAccountingSchema')) {
ensureAccountingSchema();
}
if ($_SERVER['REQUEST_METHOD'] !== 'POST' && function_exists('seedDefaultAccountingAccounts')) {
seedDefaultAccountingAccounts();
}

View File

@ -187,3 +187,4 @@
2026-05-02 18:40:57 - POST: {"name":"\u0645\u062d\u0627\u0633\u0628 1","add_cash_register":""}
2026-05-02 18:41:32 - POST: {"id":"1","name":"\u0627\u0644\u0641\u0631\u0639 \u0627\u0644\u0631\u0626\u064a\u0633\u064a","phone":"","address":"Head Office","status":"active","edit_outlet":""}
2026-05-02 19:20:30 - POST: {"license_key":"BACC-F7B0-A44F-2AC2","activate":"1"}
2026-05-03 02:27:31 - POST: {"seed_default_accounts":""}