update index
This commit is contained in:
parent
9108deb2d9
commit
df0eee1043
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@ -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,
|
||||
|
||||
@ -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',
|
||||
];
|
||||
|
||||
@ -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',
|
||||
|
||||
@ -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);
|
||||
|
||||
27
db/migrations/20260503_zz_ensure_invoice_due_date.php
Normal file
27
db/migrations/20260503_zz_ensure_invoice_due_date.php
Normal 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;
|
||||
@ -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,
|
||||
|
||||
@ -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
151
index.php
@ -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">
|
||||
|
||||
@ -1,4 +1,8 @@
|
||||
<?php
|
||||
if (function_exists('ensureAccountingSchema')) {
|
||||
ensureAccountingSchema();
|
||||
}
|
||||
|
||||
if ($_SERVER['REQUEST_METHOD'] !== 'POST' && function_exists('seedDefaultAccountingAccounts')) {
|
||||
seedDefaultAccountingAccounts();
|
||||
}
|
||||
|
||||
@ -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":""}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user