Autosave: 20260503-032951

This commit is contained in:
Flatlogic Bot 2026-05-03 03:29:51 +00:00
parent 76b078746f
commit b89863b4d3
3 changed files with 336 additions and 329 deletions

331
index.php
View File

@ -4071,134 +4071,7 @@ switch ($page) {
break; break;
case 'sales': case 'sales':
case 'purchases': case 'purchases':
$type = ($page === 'sales') ? 'sale' : 'purchase'; require 'pages/sales_purchases_logic.php';
$table = ($type === 'purchase') ? 'purchases' : 'invoices';
$cust_supplier_col = ($type === 'purchase') ? 'supplier_id' : 'customer_id';
$cust_supplier_table = ($type === 'purchase') ? 'suppliers' : 'customers';
$where = ["1=1"];
$params = [];
$referenceSearchColumn = db_column_exists($table, 'transaction_no') ? 'transaction_no' : null;
if (!empty($_GET['search'])) {
$s = trim((string)$_GET['search']);
$clean_id = preg_replace('/[^0-9]/', '', $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_supplier_col = ?";
$params[] = $_GET['customer_id'];
}
if (!empty($_GET['start_date'])) {
$where[] = "v.invoice_date >= ?";
$params[] = $_GET['start_date'];
}
if (!empty($_GET['end_date'])) {
$where[] = "v.invoice_date <= ?";
$params[] = $_GET['end_date'];
}
$tableHasOutlet = db_column_exists($table, 'outlet_id');
$oid = current_outlet_id();
if ($tableHasOutlet && $oid !== -1) {
$where[] = "(v.outlet_id = ? OR v.outlet_id IS NULL)";
$params[] = $oid;
}
$whereSql = implode(" AND ", $where);
$countStmt = db()->prepare("SELECT COUNT(*) FROM $table v LEFT JOIN $cust_supplier_table c ON v.$cust_supplier_col = c.id WHERE $whereSql");
$countStmt->execute($params);
$total_records = (int)$countStmt->fetchColumn();
$data['total_pages'] = ceil($total_records / $limit);
$data['current_page'] = $page_num;
$customerTaxColumn = entity_tax_column($cust_supplier_table);
$customerTaxSelect = $customerTaxColumn !== null ? "c.$customerTaxColumn" : "''";
$outletSelectSql = "'' AS outlet_name";
$outletJoinSql = '';
if ($tableHasOutlet && db_table_exists('outlets')) {
$outletSelectSql = "o.name AS outlet_name";
$outletJoinSql = "LEFT JOIN outlets o ON v.outlet_id = o.id";
}
$stmt = db()->prepare("SELECT v.*, c.name as customer_name, $customerTaxSelect as customer_tax_id, c.phone as customer_phone, $outletSelectSql
FROM $table v
LEFT JOIN $cust_supplier_table c ON v.$cust_supplier_col = c.id
$outletJoinSql
WHERE $whereSql
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['type'] = $type;
$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']]);
$inv['items'] = $item_stmt->fetchAll(PDO::FETCH_ASSOC);
} else {
$item_stmt = db()->prepare("SELECT pi.*, i.name_en, i.name_ar, i.vat_rate FROM purchase_items pi LEFT JOIN stock_items i ON pi.item_id = i.id WHERE pi.purchase_id = ?");
$item_stmt->execute([$inv['id']]);
$inv['items'] = $item_stmt->fetchAll(PDO::FETCH_ASSOC);
}
}
unset($inv);
$oid = current_outlet_id(); $items_list_raw = db()->query("SELECT i.id, i.name_en, i.name_ar, i.sale_price, i.purchase_price, i.stock_quantity, i.vat_rate, i.is_promotion, i.promotion_start, i.promotion_end, i.promotion_percent FROM stock_items i ORDER BY i.name_en ASC")->fetchAll(PDO::FETCH_ASSOC);
foreach ($items_list_raw as &$item) {
$item['sale_price'] = getPromotionalPrice($item);
}
$data['items_list'] = $items_list_raw;
$data['customers_list'] = db()->query("SELECT id, name FROM $cust_supplier_table ORDER BY name ASC")->fetchAll();
$oid = current_outlet_id();
$outlet_sql = ($oid !== -1) ? "WHERE outlet_id = $oid" : "";
if ($type === 'sale') {
$data['sales_invoices'] = db()->query("SELECT id, invoice_date, total_with_vat FROM invoices $outlet_sql ORDER BY id DESC")->fetchAll();
} else {
$data['purchase_invoices'] = db()->query("SELECT id, invoice_date, total_with_vat FROM purchases $outlet_sql ORDER BY id DESC")->fetchAll();
}
break; break;
case 'sales_returns': case 'sales_returns':
@ -7960,207 +7833,7 @@ $projectDescription = $_SERVER['PROJECT_DESCRIPTION'] ?? 'Accounting System';
</div> </div>
<?php elseif ($page === 'sales' || $page === 'purchases'): ?> <?php elseif ($page === 'sales' || $page === 'purchases'): ?>
<div class="card p-4"> <?php require 'pages/sales_purchases_view.php'; ?>
<!-- Print Header -->
<div class="print-only mb-4">
<div class="row align-items-center">
<div class="col-6">
<?php if (!empty($data['settings']['company_logo'])): ?>
<img src="<?= htmlspecialchars($data['settings']['company_logo']) ?>" alt="Logo" style="max-height: 80px;" class="mb-2">
<?php endif; ?>
<h3 class="mb-1 fw-bold"><?= htmlspecialchars($data['settings']['company_name'] ?? 'Accounting System') ?></h3>
<p class="text-muted small mb-0"><?= nl2br(htmlspecialchars($data['settings']['company_address'] ?? '')) ?></p>
<p class="text-muted small mb-0">VAT: <?= htmlspecialchars($data['settings']['vat_number'] ?? '') ?></p>
</div>
<div class="col-6 text-end">
<h2 class="text-uppercase text-muted"><?= $currTitle['en'] ?> Report</h2>
<p class="mb-0">Date: <?= date('Y-m-d') ?></p>
<?php if (!empty($_GET['start_date']) || !empty($_GET['end_date']) || !empty($_GET['customer_id'])): ?>
<p class="small text-muted mb-0">
<?php if (!empty($_GET['customer_id'])): ?>
<strong><?= $page === 'sales' ? 'Customer' : 'Supplier' ?>:</strong>
<?php
foreach ($data['customers_list'] as $c) {
if ($c['id'] == $_GET['customer_id']) {
echo htmlspecialchars($c['name']);
break;
}
}
?> |
<?php endif; ?>
<strong>Period:</strong> <?= !empty($_GET['start_date']) ? $_GET['start_date'] : 'All' ?> to <?= !empty($_GET['end_date']) ? $_GET['end_date'] : 'All' ?>
</p>
<?php endif; ?>
</div>
</div>
<hr>
</div>
<div class="d-flex justify-content-between align-items-center mb-4 d-print-none">
<h5 class="m-0" data-en="<?= $currTitle['en'] ?>" data-ar="<?= $currTitle['ar'] ?>"><?= $currTitle['en'] ?></h5>
<div class="d-flex gap-2">
<a href="index.php?<?= http_build_query(array_merge($_GET, ['page' => 'export', 'type' => $page, 'format' => 'excel'])) ?>" class="btn btn-outline-success">
<i class="bi bi-file-earmark-excel"></i> <span data-en="Export to Excel" data-ar="تصدير إلى اكسل">Export to Excel</span>
</a>
<?php if (can($page . '_add')): ?>
<button class="btn btn-primary" data-bs-toggle="modal" data-bs-target="#addInvoiceModal">
<i class="bi bi-plus-lg"></i> <span data-en="Create New Tax Invoice" data-ar="إنشاء فاتورة ضريبية جديدة">Create New Tax Invoice</span>
</button>
<?php endif; ?>
</div>
</div>
<!-- Filters Section -->
<div class="bg-light p-3 rounded mb-4 d-print-none">
<form method="GET" class="documents-filter">
<input type="hidden" name="page" value="<?= $page ?>">
<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="<?= $page === 'sales' ? 'Invoice #, transaction # or customer...' : 'Purchase # or supplier...' ?>">
</div>
<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>
<?php foreach ($data['customers_list'] as $c): ?>
<option value="<?= $c['id'] ?>" <?= (($_GET['customer_id'] ?? '') == $c['id']) ? 'selected' : '' ?>><?= htmlspecialchars($c['name']) ?></option>
<?php endforeach; ?>
</select>
</div>
<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="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="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 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>
</div>
<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">
<i class="bi bi-x-circle"></i> <span data-en="Clear" data-ar="مسح">Clear</span>
</a>
</div>
</form>
</div>
<div class="table-responsive">
<table class="table table-hover align-middle documents-table">
<thead>
<tr>
<th data-en="Invoice #" data-ar="رقم الفاتورة">Invoice #</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>
<th data-en="Balance" data-ar="المتبقي" class="text-end">Balance</th>
<th data-en="Actions" data-ar="الإجراءات" class="text-end d-print-none">Actions</th>
</tr>
</thead>
<tbody>
<?php
$itemTable = ($page === 'purchases') ? 'purchase_items' : 'invoice_items';
$fkCol = ($page === 'purchases') ? 'purchase_id' : 'invoice_id';
$total_all = 0; $total_paid = 0; $total_balance = 0;
foreach ($data['invoices'] as $inv):
$total_all += (float)$inv['total_with_vat'];
$total_paid += (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);
$isOverdue = !empty($inv['due_date']) && strtotime((string)$inv['due_date']) < time() && ($inv['status'] ?? '') !== 'paid';
?>
<tr>
<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' : '' ?>">
<?= htmlspecialchars($inv['due_date'] ?: '---') ?>
<?php if ($isOverdue): ?>
<i class="bi bi-exclamation-triangle-fill ms-1" title="Overdue"></i>
<?php endif; ?>
</span>
</div>
</td>
<td><span class="badge bg-light text-dark border"><?= htmlspecialchars($inv['payment_type_label']) ?></span></td>
<td>
<?php
$statusClass = 'bg-secondary';
if ($inv['status'] === 'paid') $statusClass = 'bg-success';
elseif ($inv['status'] === 'unpaid') $statusClass = 'bg-danger';
elseif ($inv['status'] === 'partially_paid') $statusClass = 'bg-warning text-dark';
?>
<span class="badge text-uppercase <?= $statusClass ?>"><?= htmlspecialchars(str_replace('_', ' ', $inv['status'])) ?></span>
</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['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>
<button class="btn btn-outline-warning return-invoice-btn" data-id="<?= $inv['id'] ?>" data-bs-toggle="modal" data-bs-target="<?= $page === 'sales' ? '#addSalesReturnModal' : '#addPurchaseReturnModal' ?>" title="Return"><i class="bi bi-arrow-return-left"></i></button>
<button class="btn btn-outline-primary edit-invoice-btn" data-json="<?= htmlspecialchars(json_encode($inv)) ?>" data-bs-toggle="modal" data-bs-target="#editInvoiceModal" title="Edit"><i class="bi bi-pencil"></i></button>
<?php if ($inv['status'] !== 'paid'): ?>
<button class="btn btn-outline-success pay-invoice-btn" data-id="<?= $inv['id'] ?>" data-total="<?= $inv['total_with_vat'] ?>" data-paid="<?= $inv['paid_amount'] ?>" data-bs-toggle="modal" data-bs-target="#payInvoiceModal" title="Payment"><i class="bi bi-cash-coin"></i></button>
<?php endif; ?>
<button class="btn btn-outline-secondary print-a4-btn" data-json="<?= htmlspecialchars(json_encode($inv)) ?>" title="Print A4 Invoice"><i class="bi bi-printer"></i></button>
<form method="POST" class="d-inline" onsubmit="return confirm('Are you sure you want to delete this invoice?')">
<input type="hidden" name="id" value="<?= $inv['id'] ?>">
<button type="submit" name="delete_invoice" class="btn btn-outline-danger" title="Delete"><i class="bi bi-trash"></i></button>
</form>
</div>
</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">
<td colspan="5" class="text-end" data-en="Totals" data-ar="الإجمالي">Totals</td>
<td class="text-end">OMR <?= number_format((float)$total_all, 3) ?></td>
<td class="text-end text-success">OMR <?= number_format((float)$total_paid, 3) ?></td>
<td class="text-end text-danger">OMR <?= number_format((float)$total_balance, 3) ?></td>
<td class="d-print-none"></td>
</tr>
</tfoot>
</table>
</div>
<?= renderPagination($data['current_page'] ?? 1, $data['total_pages'] ?? 1) ?>
</div>
<?php elseif ($page === 'customer_statement' || $page === 'supplier_statement'): ?> <?php elseif ($page === 'customer_statement' || $page === 'supplier_statement'): ?>
<div class="card p-4"> <div class="card p-4">

View File

@ -0,0 +1,130 @@
<?php
// Shared Sales/Purchases data loading extracted from index.php to reduce risk when editing this module.
$type = ($page === 'sales') ? 'sale' : 'purchase';
$table = ($type === 'purchase') ? 'purchases' : 'invoices';
$cust_supplier_col = ($type === 'purchase') ? 'supplier_id' : 'customer_id';
$cust_supplier_table = ($type === 'purchase') ? 'suppliers' : 'customers';
$where = ["1=1"];
$params = [];
$referenceSearchColumn = db_column_exists($table, 'transaction_no') ? 'transaction_no' : null;
if (!empty($_GET['search'])) {
$s = trim((string)$_GET['search']);
$clean_id = preg_replace('/[^0-9]/', '', $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_supplier_col = ?";
$params[] = $_GET['customer_id'];
}
if (!empty($_GET['start_date'])) {
$where[] = "v.invoice_date >= ?";
$params[] = $_GET['start_date'];
}
if (!empty($_GET['end_date'])) {
$where[] = "v.invoice_date <= ?";
$params[] = $_GET['end_date'];
}
$tableHasOutlet = db_column_exists($table, 'outlet_id');
$oid = current_outlet_id();
if ($tableHasOutlet && $oid !== -1) {
$where[] = "(v.outlet_id = ? OR v.outlet_id IS NULL)";
$params[] = $oid;
}
$whereSql = implode(" AND ", $where);
$countStmt = db()->prepare("SELECT COUNT(*) FROM $table v LEFT JOIN $cust_supplier_table c ON v.$cust_supplier_col = c.id WHERE $whereSql");
$countStmt->execute($params);
$total_records = (int)$countStmt->fetchColumn();
$data['total_pages'] = ceil($total_records / $limit);
$data['current_page'] = $page_num;
$customerTaxColumn = entity_tax_column($cust_supplier_table);
$customerTaxSelect = $customerTaxColumn !== null ? "c.$customerTaxColumn" : "''";
$outletSelectSql = "'' AS outlet_name";
$outletJoinSql = '';
if ($tableHasOutlet && db_table_exists('outlets')) {
$outletSelectSql = "o.name AS outlet_name";
$outletJoinSql = "LEFT JOIN outlets o ON v.outlet_id = o.id";
}
$stmt = db()->prepare("SELECT v.*, c.name as customer_name, $customerTaxSelect as customer_tax_id, c.phone as customer_phone, $outletSelectSql
FROM $table v
LEFT JOIN $cust_supplier_table c ON v.$cust_supplier_col = c.id
$outletJoinSql
WHERE $whereSql
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['type'] = $type;
$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']]);
$inv['items'] = $item_stmt->fetchAll(PDO::FETCH_ASSOC);
} else {
$item_stmt = db()->prepare("SELECT pi.*, i.name_en, i.name_ar, i.vat_rate FROM purchase_items pi LEFT JOIN stock_items i ON pi.item_id = i.id WHERE pi.purchase_id = ?");
$item_stmt->execute([$inv['id']]);
$inv['items'] = $item_stmt->fetchAll(PDO::FETCH_ASSOC);
}
}
unset($inv);
$oid = current_outlet_id(); $items_list_raw = db()->query("SELECT i.id, i.name_en, i.name_ar, i.sale_price, i.purchase_price, i.stock_quantity, i.vat_rate, i.is_promotion, i.promotion_start, i.promotion_end, i.promotion_percent FROM stock_items i ORDER BY i.name_en ASC")->fetchAll(PDO::FETCH_ASSOC);
foreach ($items_list_raw as &$item) {
$item['sale_price'] = getPromotionalPrice($item);
}
$data['items_list'] = $items_list_raw;
$data['customers_list'] = db()->query("SELECT id, name FROM $cust_supplier_table ORDER BY name ASC")->fetchAll();
$oid = current_outlet_id();
$outlet_sql = ($oid !== -1) ? "WHERE outlet_id = $oid" : "";
if ($type === 'sale') {
$data['sales_invoices'] = db()->query("SELECT id, invoice_date, total_with_vat FROM invoices $outlet_sql ORDER BY id DESC")->fetchAll();
} else {
$data['purchase_invoices'] = db()->query("SELECT id, invoice_date, total_with_vat FROM purchases $outlet_sql ORDER BY id DESC")->fetchAll();
}

View File

@ -0,0 +1,204 @@
<?php
// Shared Sales/Purchases list view extracted from index.php to keep the main router thinner.
?>
<div class="card p-4">
<!-- Print Header -->
<div class="print-only mb-4">
<div class="row align-items-center">
<div class="col-6">
<?php if (!empty($data['settings']['company_logo'])): ?>
<img src="<?= htmlspecialchars($data['settings']['company_logo']) ?>" alt="Logo" style="max-height: 80px;" class="mb-2">
<?php endif; ?>
<h3 class="mb-1 fw-bold"><?= htmlspecialchars($data['settings']['company_name'] ?? 'Accounting System') ?></h3>
<p class="text-muted small mb-0"><?= nl2br(htmlspecialchars($data['settings']['company_address'] ?? '')) ?></p>
<p class="text-muted small mb-0">VAT: <?= htmlspecialchars($data['settings']['vat_number'] ?? '') ?></p>
</div>
<div class="col-6 text-end">
<h2 class="text-uppercase text-muted"><?= $currTitle['en'] ?> Report</h2>
<p class="mb-0">Date: <?= date('Y-m-d') ?></p>
<?php if (!empty($_GET['start_date']) || !empty($_GET['end_date']) || !empty($_GET['customer_id'])): ?>
<p class="small text-muted mb-0">
<?php if (!empty($_GET['customer_id'])): ?>
<strong><?= $page === 'sales' ? 'Customer' : 'Supplier' ?>:</strong>
<?php
foreach ($data['customers_list'] as $c) {
if ($c['id'] == $_GET['customer_id']) {
echo htmlspecialchars($c['name']);
break;
}
}
?> |
<?php endif; ?>
<strong>Period:</strong> <?= !empty($_GET['start_date']) ? $_GET['start_date'] : 'All' ?> to <?= !empty($_GET['end_date']) ? $_GET['end_date'] : 'All' ?>
</p>
<?php endif; ?>
</div>
</div>
<hr>
</div>
<div class="d-flex justify-content-between align-items-center mb-4 d-print-none">
<h5 class="m-0" data-en="<?= $currTitle['en'] ?>" data-ar="<?= $currTitle['ar'] ?>"><?= $currTitle['en'] ?></h5>
<div class="d-flex gap-2">
<a href="index.php?<?= http_build_query(array_merge($_GET, ['page' => 'export', 'type' => $page, 'format' => 'excel'])) ?>" class="btn btn-outline-success">
<i class="bi bi-file-earmark-excel"></i> <span data-en="Export to Excel" data-ar="تصدير إلى اكسل">Export to Excel</span>
</a>
<?php if (can($page . '_add')): ?>
<button class="btn btn-primary" data-bs-toggle="modal" data-bs-target="#addInvoiceModal">
<i class="bi bi-plus-lg"></i> <span data-en="Create New Tax Invoice" data-ar="إنشاء فاتورة ضريبية جديدة">Create New Tax Invoice</span>
</button>
<?php endif; ?>
</div>
</div>
<!-- Filters Section -->
<div class="bg-light p-3 rounded mb-4 d-print-none">
<form method="GET" class="documents-filter">
<input type="hidden" name="page" value="<?= $page ?>">
<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="<?= $page === 'sales' ? 'Invoice #, transaction # or customer...' : 'Purchase # or supplier...' ?>">
</div>
<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>
<?php foreach ($data['customers_list'] as $c): ?>
<option value="<?= $c['id'] ?>" <?= (($_GET['customer_id'] ?? '') == $c['id']) ? 'selected' : '' ?>><?= htmlspecialchars($c['name']) ?></option>
<?php endforeach; ?>
</select>
</div>
<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="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="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 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>
</div>
<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">
<i class="bi bi-x-circle"></i> <span data-en="Clear" data-ar="مسح">Clear</span>
</a>
</div>
</form>
</div>
<div class="table-responsive">
<table class="table table-hover align-middle documents-table">
<thead>
<tr>
<th data-en="Invoice #" data-ar="رقم الفاتورة">Invoice #</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>
<th data-en="Balance" data-ar="المتبقي" class="text-end">Balance</th>
<th data-en="Actions" data-ar="الإجراءات" class="text-end d-print-none">Actions</th>
</tr>
</thead>
<tbody>
<?php
$itemTable = ($page === 'purchases') ? 'purchase_items' : 'invoice_items';
$fkCol = ($page === 'purchases') ? 'purchase_id' : 'invoice_id';
$total_all = 0; $total_paid = 0; $total_balance = 0;
foreach ($data['invoices'] as $inv):
$total_all += (float)$inv['total_with_vat'];
$total_paid += (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);
$isOverdue = !empty($inv['due_date']) && strtotime((string)$inv['due_date']) < time() && ($inv['status'] ?? '') !== 'paid';
?>
<tr>
<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' : '' ?>">
<?= htmlspecialchars($inv['due_date'] ?: '---') ?>
<?php if ($isOverdue): ?>
<i class="bi bi-exclamation-triangle-fill ms-1" title="Overdue"></i>
<?php endif; ?>
</span>
</div>
</td>
<td><span class="badge bg-light text-dark border"><?= htmlspecialchars($inv['payment_type_label']) ?></span></td>
<td>
<?php
$statusClass = 'bg-secondary';
if ($inv['status'] === 'paid') $statusClass = 'bg-success';
elseif ($inv['status'] === 'unpaid') $statusClass = 'bg-danger';
elseif ($inv['status'] === 'partially_paid') $statusClass = 'bg-warning text-dark';
?>
<span class="badge text-uppercase <?= $statusClass ?>"><?= htmlspecialchars(str_replace('_', ' ', $inv['status'])) ?></span>
</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['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>
<button class="btn btn-outline-warning return-invoice-btn" data-id="<?= $inv['id'] ?>" data-bs-toggle="modal" data-bs-target="<?= $page === 'sales' ? '#addSalesReturnModal' : '#addPurchaseReturnModal' ?>" title="Return"><i class="bi bi-arrow-return-left"></i></button>
<button class="btn btn-outline-primary edit-invoice-btn" data-json="<?= htmlspecialchars(json_encode($inv)) ?>" data-bs-toggle="modal" data-bs-target="#editInvoiceModal" title="Edit"><i class="bi bi-pencil"></i></button>
<?php if ($inv['status'] !== 'paid'): ?>
<button class="btn btn-outline-success pay-invoice-btn" data-id="<?= $inv['id'] ?>" data-total="<?= $inv['total_with_vat'] ?>" data-paid="<?= $inv['paid_amount'] ?>" data-bs-toggle="modal" data-bs-target="#payInvoiceModal" title="Payment"><i class="bi bi-cash-coin"></i></button>
<?php endif; ?>
<button class="btn btn-outline-secondary print-a4-btn" data-json="<?= htmlspecialchars(json_encode($inv)) ?>" title="Print A4 Invoice"><i class="bi bi-printer"></i></button>
<form method="POST" class="d-inline" onsubmit="return confirm('Are you sure you want to delete this invoice?')">
<input type="hidden" name="id" value="<?= $inv['id'] ?>">
<button type="submit" name="delete_invoice" class="btn btn-outline-danger" title="Delete"><i class="bi bi-trash"></i></button>
</form>
</div>
</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">
<td colspan="5" class="text-end" data-en="Totals" data-ar="الإجمالي">Totals</td>
<td class="text-end">OMR <?= number_format((float)$total_all, 3) ?></td>
<td class="text-end text-success">OMR <?= number_format((float)$total_paid, 3) ?></td>
<td class="text-end text-danger">OMR <?= number_format((float)$total_balance, 3) ?></td>
<td class="d-print-none"></td>
</tr>
</tfoot>
</table>
</div>
<?= renderPagination($data['current_page'] ?? 1, $data['total_pages'] ?? 1) ?>
</div>