Autosave: 20260503-032951
This commit is contained in:
parent
76b078746f
commit
b89863b4d3
331
index.php
331
index.php
@ -4071,134 +4071,7 @@ switch ($page) {
|
||||
break;
|
||||
case 'sales':
|
||||
case 'purchases':
|
||||
$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();
|
||||
}
|
||||
require 'pages/sales_purchases_logic.php';
|
||||
break;
|
||||
|
||||
case 'sales_returns':
|
||||
@ -7960,207 +7833,7 @@ $projectDescription = $_SERVER['PROJECT_DESCRIPTION'] ?? 'Accounting System';
|
||||
</div>
|
||||
|
||||
<?php elseif ($page === 'sales' || $page === 'purchases'): ?>
|
||||
<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>
|
||||
<?php require 'pages/sales_purchases_view.php'; ?>
|
||||
|
||||
<?php elseif ($page === 'customer_statement' || $page === 'supplier_statement'): ?>
|
||||
<div class="card p-4">
|
||||
|
||||
130
pages/sales_purchases_logic.php
Normal file
130
pages/sales_purchases_logic.php
Normal 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();
|
||||
}
|
||||
204
pages/sales_purchases_view.php
Normal file
204
pages/sales_purchases_view.php
Normal 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>
|
||||
Loading…
x
Reference in New Issue
Block a user