Autosave: 20260303-082124

This commit is contained in:
Flatlogic Bot 2026-03-03 08:21:24 +00:00
parent ec6f16b511
commit 1de3f1d7d1
7 changed files with 664 additions and 34 deletions

25
api/search_customers.php Normal file
View File

@ -0,0 +1,25 @@
<?php
header('Content-Type: application/json');
require_once __DIR__ . '/../db/config.php';
session_start();
if (!isset($_SESSION['user_id'])) {
echo json_encode([]);
exit;
}
$query = $_GET['query'] ?? '';
if (strlen($query) < 2) {
echo json_encode([]);
exit;
}
try {
$stmt = db()->prepare("SELECT id, name_en, name_ar, phone FROM customers WHERE name_en LIKE ? OR name_ar LIKE ? OR phone LIKE ? LIMIT 10");
$stmt->execute(["%$query%", "%$query%", "%$query%"]);
$customers = $stmt->fetchAll();
echo json_encode($customers);
} catch (Exception $e) {
echo json_encode([]);
}

304
customer_statement.php Normal file
View File

@ -0,0 +1,304 @@
<?php
$title = 'customer_statement';
require_once __DIR__ . '/includes/header.php';
$customer_id = $_GET['id'] ?? null;
if (!$customer_id) {
header('Location: customers.php');
exit;
}
// Fetch customer details
$stmt = db()->prepare("SELECT * FROM customers WHERE id = ?");
$stmt->execute([$customer_id]);
$customer = $stmt->fetch();
if (!$customer) {
header('Location: customers.php');
exit;
}
$from_date = $_GET['from_date'] ?? '';
$to_date = $_GET['to_date'] ?? '';
// Base queries
$orders_sql = "SELECT id, order_number, total_price, created_at FROM orders WHERE customer_id = ?";
$payments_sql = "SELECT p.id, p.amount, p.payment_method, p.created_at, o.order_number
FROM payments p
JOIN orders o ON p.order_id = o.id
WHERE o.customer_id = ?";
$params = [$customer_id];
if ($from_date) {
$orders_sql .= " AND DATE(created_at) >= ?";
$payments_sql .= " AND DATE(p.created_at) >= ?";
$params[] = $from_date;
}
if ($to_date) {
$orders_sql .= " AND DATE(created_at) <= ?";
$payments_sql .= " AND DATE(p.created_at) <= ?";
$params[] = $to_date;
}
$stmt_orders = db()->prepare($orders_sql);
// We need to be careful with params if both filters are set
$orders_params = [$customer_id];
if ($from_date) $orders_params[] = $from_date;
if ($to_date) $orders_params[] = $to_date;
$stmt_orders->execute($orders_params);
$orders = $stmt_orders->fetchAll();
$stmt_payments = db()->prepare($payments_sql);
$payments_params = [$customer_id];
if ($from_date) $payments_params[] = $from_date;
if ($to_date) $payments_params[] = $to_date;
$stmt_payments->execute($payments_params);
$payments = $stmt_payments->fetchAll();
// Combine and sort
$transactions = [];
foreach ($orders as $o) {
$transactions[] = [
'date' => $o['created_at'],
'type' => 'order',
'ref' => $o['order_number'],
'debit' => $o['total_price'],
'credit' => 0,
'description' => __('order') . ' #' . $o['order_number']
];
}
foreach ($payments as $p) {
$transactions[] = [
'date' => $p['created_at'],
'type' => 'payment',
'ref' => $p['order_number'],
'debit' => 0,
'credit' => $p['amount'],
'description' => __('payment') . ' (' . __($p['payment_method']) . ') - ' . __('order') . ' #' . $p['order_number']
];
}
usort($transactions, function($a, $b) {
return strtotime($a['date']) - strtotime($b['date']);
});
$total_debit = 0;
$total_credit = 0;
$balance = 0;
foreach ($transactions as &$t) {
$total_debit += $t['debit'];
$total_credit += $t['credit'];
$balance += ($t['debit'] - $t['credit']);
$t['running_balance'] = $balance;
}
?>
<div class="d-print-none mb-4">
<nav aria-label="breadcrumb">
<ol class="breadcrumb">
<li class="breadcrumb-item"><a href="customers.php"><?= __('customers') ?></a></li>
<li class="breadcrumb-item active"><?= __('customer_statement') ?></li>
</ol>
</nav>
</div>
<div class="card p-4 border-0 shadow-sm mb-4 d-print-none" style="border-radius: 20px;">
<form action="" method="GET" class="row g-3 align-items-end">
<input type="hidden" name="id" value="<?= $customer_id ?>">
<div class="col-md-4">
<label class="form-label small fw-bold"><?= __('from_date') ?></label>
<input type="date" name="from_date" class="form-control" value="<?= $from_date ?>" style="border-radius: 12px;">
</div>
<div class="col-md-4">
<label class="form-label small fw-bold"><?= __('to_date') ?></label>
<input type="date" name="to_date" class="form-control" value="<?= $to_date ?>" style="border-radius: 12px;">
</div>
<div class="col-md-4 d-flex gap-2">
<button type="submit" class="btn btn-primary px-4 w-100" style="border-radius: 12px;">
<i class="bi bi-filter me-1"></i> <?= __('filter') ?? 'Filter' ?>
</button>
<button type="button" class="btn btn-outline-dark px-4 w-100" style="border-radius: 12px;" onclick="window.print()">
<i class="bi bi-printer me-1"></i> <?= __('print') ?>
</button>
</div>
</form>
</div>
<div id="printableStatement" class="card p-5 border-0 shadow-sm" style="border-radius: 20px;">
<!-- Formal Header for Printing -->
<div class="d-none d-print-block mb-5">
<div class="row align-items-center mb-4">
<div class="col-7">
<?php if ($company_info['logo']): ?>
<img src="<?= $company_info['logo'] ?>" alt="Logo" style="max-height: 100px;" class="mb-3">
<?php endif; ?>
<h2 class="fw-bold mb-1"><?= htmlspecialchars(is_arabic() ? $company_info['name_ar'] : $company_info['name_en']) ?></h2>
<p class="mb-0 text-muted">
<?= is_arabic() ? ($company_info['address_ar'] ?? '') : ($company_info['address_en'] ?? '') ?><br>
<?= __('phone') ?>: <?= $company_info['phone'] ?? '' ?> | <?= __('email') ?>: <?= $company_info['email'] ?? '' ?><br>
<?php if ($company_info['vat_no']): ?>
<?= __('vat_no') ?>: <?= $company_info['vat_no'] ?>
<?php endif; ?>
<?php if ($company_info['ctr_no']): ?>
| <?= __('ctr_no') ?>: <?= $company_info['ctr_no'] ?>
<?php endif; ?>
</p>
</div>
<div class="col-5 text-end">
<h1 class="fw-bold text-uppercase mb-2" style="color: #0d6efd;"><?= __('statement') ?></h1>
<div class="mt-3">
<p class="mb-0 fw-bold"><?= __('date') ?>: <?= date('d/m/Y') ?></p>
<?php if ($from_date || $to_date): ?>
<p class="mb-0 text-muted small">
<?= $from_date ? __('from_date') . ': ' . date('d/m/Y', strtotime($from_date)) : '' ?>
<?= $to_date ? ' ' . __('to_date') . ': ' . date('d/m/Y', strtotime($to_date)) : '' ?>
</p>
<?php endif; ?>
</div>
</div>
</div>
<div class="row p-3 bg-light rounded-3 mb-4 mx-0 border">
<div class="col-12">
<h6 class="fw-bold text-muted text-uppercase mb-2"><?= __('customer_details') ?></h6>
<h4 class="fw-bold mb-1"><?= $lang === 'ar' ? ($customer['name_ar'] ?: $customer['name_en']) : $customer['name_en'] ?></h4>
<p class="mb-0 text-muted"><?= $customer['phone'] ?> | <?= $customer['email'] ?></p>
</div>
</div>
</div>
<!-- Screen Header -->
<div class="d-print-none mb-4">
<h4 class="fw-bold mb-1"><?= $lang === 'ar' ? ($customer['name_ar'] ?: $customer['name_en']) : $customer['name_en'] ?></h4>
<p class="text-muted mb-0"><?= $customer['phone'] ?> | <?= $customer['email'] ?></p>
</div>
<div class="table-responsive">
<table class="table table-striped align-middle mt-4">
<thead class="table-dark">
<tr>
<th><?= __('date') ?></th>
<th><?= __('description') ?></th>
<th class="text-center"><?= __('ref') ?? 'Ref' ?></th>
<th class="text-end"><?= __('debit') ?></th>
<th class="text-end"><?= __('credit') ?></th>
<th class="text-end"><?= __('balance') ?></th>
</tr>
</thead>
<tbody>
<?php if (empty($transactions)): ?>
<tr>
<td colspan="6" class="text-center py-5 text-muted">
<?= __('no_transactions_found') ?? 'No transactions found' ?>
</td>
</tr>
<?php else: ?>
<?php foreach ($transactions as $t): ?>
<tr>
<td class="small"><?= date('d/m/Y H:i', strtotime($t['date'])) ?></td>
<td><?= $t['description'] ?></td>
<td class="text-center"><span class="badge bg-light text-dark border"><?= $t['ref'] ?></span></td>
<td class="text-end"><?= $t['debit'] > 0 ? format_amount($t['debit']) : '-' ?></td>
<td class="text-end"><?= $t['credit'] > 0 ? format_amount($t['credit']) : '-' ?></td>
<td class="text-end fw-bold"><?= format_amount($t['running_balance']) ?></td>
</tr>
<?php endforeach; ?>
<?php endif; ?>
</tbody>
<tfoot class="table-light fw-bold border-top">
<tr>
<td colspan="3" class="text-end"><?= __('total') ?></td>
<td class="text-end"><?= format_amount($total_debit) ?></td>
<td class="text-end"><?= format_amount($total_credit) ?></td>
<td class="text-end text-primary"><?= format_amount($balance) ?></td>
</tr>
</tfoot>
</table>
</div>
<div class="row mt-5">
<div class="col-md-5 ms-auto">
<div class="card bg-white border" style="border-radius: 15px;">
<div class="card-body p-4">
<h5 class="fw-bold mb-4 border-bottom pb-2"><?= __('summary') ?? 'Summary' ?></h5>
<div class="d-flex justify-content-between mb-2">
<span class="text-muted"><?= __('total_debit') ?>:</span>
<span class="fw-bold"><?= format_amount($total_debit) ?></span>
</div>
<div class="d-flex justify-content-between mb-2">
<span class="text-muted"><?= __('total_credit') ?>:</span>
<span class="fw-bold"><?= format_amount($total_credit) ?></span>
</div>
<div class="d-flex justify-content-between pt-2 mt-2 border-top">
<span class="fw-bold fs-5"><?= __('closing_balance') ?>:</span>
<span class="fw-bold text-primary fs-5"><?= format_amount($balance) ?></span>
</div>
</div>
</div>
</div>
</div>
</div>
<style>
@media print {
@page {
size: auto;
margin: 15mm;
}
body {
background-color: white !important;
font-size: 11px;
}
.main-content {
padding: 0 !important;
margin: 0 !important;
width: 100% !important;
max-width: 100% !important;
}
header, .sidebar, .d-print-none, .breadcrumb, nav {
display: none !important;
}
.container-fluid, .row, .col-lg-10 {
padding: 0 !important;
margin: 0 !important;
width: 100% !important;
max-width: 100% !important;
flex: 0 0 100% !important;
}
.card {
box-shadow: none !important;
border: none !important;
padding: 0 !important;
margin: 0 !important;
}
#printableStatement {
padding: 0 !important;
}
table {
width: 100% !important;
border-collapse: collapse !important;
}
th, td {
border: 1px solid #dee2e6 !important;
padding: 6px !important;
}
.badge {
border: none !important;
padding: 0 !important;
}
.bg-light {
background-color: #f8f9fa !important;
}
.table-dark {
background-color: #212529 !important;
color: white !important;
}
/* Hide Browser Headers/Footers */
title, .navbar {
display: none !important;
}
}
</style>
<?php require_once __DIR__ . '/includes/footer.php'; ?>

View File

@ -1,8 +1,84 @@
<?php
// ACTION HANDLING FIRST (to allow redirects)
require_once __DIR__ . '/db/config.php';
require_once __DIR__ . '/includes/lang.php';
if (!isset($_SESSION['user_id'])) {
header('Location: login.php');
exit;
}
// Initial view check
if (!has_permission('view')) {
header('Location: admin.php');
exit;
}
$branch_id = $_SESSION['branch_id'];
// Handle Actions
if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['action'])) {
$action = $_POST['action'];
// Permission mapping for actions
$required_permission = 'view';
if ($action === 'add_customer') $required_permission = 'add';
if ($action === 'edit_customer') $required_permission = 'edit';
if ($action === 'delete_customer') $required_permission = 'delete';
if (!has_permission($required_permission)) {
header('Location: customers.php?error=no_permission');
exit;
}
if ($action === 'add_customer') {
$phone = $_POST['phone'] ?? '';
$name_en = $_POST['name_en'] ?? '';
$name_ar = $_POST['name_ar'] ?? '';
$email = $_POST['email'] ?? '';
$address_en = $_POST['address_en'] ?? '';
$address_ar = $_POST['address_ar'] ?? '';
if ($phone && $name_en) {
$stmt = db()->prepare("INSERT INTO customers (phone, name_en, name_ar, email, address_en, address_ar) VALUES (?, ?, ?, ?, ?, ?)");
$stmt->execute([$phone, $name_en, $name_ar, $email, $address_en, $address_ar]);
header('Location: customers.php?success=customer_added');
exit;
}
} elseif ($action === 'edit_customer') {
$id = $_POST['id'];
$phone = $_POST['phone'] ?? '';
$name_en = $_POST['name_en'] ?? '';
$name_ar = $_POST['name_ar'] ?? '';
$email = $_POST['email'] ?? '';
$address_en = $_POST['address_en'] ?? '';
$address_ar = $_POST['address_ar'] ?? '';
$stmt = db()->prepare("UPDATE customers SET phone = ?, name_en = ?, name_ar = ?, email = ?, address_en = ?, address_ar = ? WHERE id = ?");
$stmt->execute([$phone, $name_en, $name_ar, $email, $address_en, $address_ar, $id]);
header('Location: customers.php?success=customer_updated');
exit;
} elseif ($action === 'delete_customer') {
$id = $_POST['id'];
// Check if customer has orders
$stmt = db()->prepare("SELECT COUNT(*) FROM orders WHERE customer_id = ?");
$stmt->execute([$id]);
if ($stmt->fetchColumn() > 0) {
header('Location: customers.php?error=customer_has_orders');
exit;
}
$stmt = db()->prepare("DELETE FROM customers WHERE id = ?");
$stmt->execute([$id]);
header('Location: customers.php?success=customer_deleted');
exit;
}
}
$title = 'customers';
require_once __DIR__ . '/includes/header.php';
$branch_id = $_SESSION['branch_id'];
$search = $_GET['search'] ?? '';
$sql = "SELECT * FROM customers WHERE 1=1";
@ -34,14 +110,29 @@ $customers = $stmt->fetchAll();
</button>
</div>
</form>
<?php if (has_permission('add')): ?>
<button class="btn btn-primary px-4 shadow-sm" style="border-radius: 12px;" data-bs-toggle="modal" data-bs-target="#addCustomerModal">
<?php if (has_permission('add')):
?><button class="btn btn-primary px-4 shadow-sm" style="border-radius: 12px;" onclick="openCustomerModal()">
<i class="bi bi-person-plus-fill me-1"></i> <?= __('add_new') ?>
</button>
<?php endif; ?>
</div>
</div>
<?php if(isset($_GET['success'])):
?><div class="alert alert-success alert-dismissible fade show rounded-4 shadow-sm mb-4 border-0" role="alert">
<i class="bi bi-check-circle-fill me-2"></i>
<?= __($_GET['success']) ?? 'Action completed successfully' ?>
<button type="button" class="btn-close" data-bs-dismiss="alert"></button>
</div>
<?php endif; ?>
<?php if(isset($_GET['error'])):
?><div class="alert alert-danger alert-dismissible fade show rounded-4 shadow-sm mb-4 border-0" role="alert">
<i class="bi bi-exclamation-triangle-fill me-2"></i>
<?= __($_GET['error']) ?? 'Permission Denied' ?>
<button type="button" class="btn-close" data-bs-dismiss="alert"></button>
</div>
<?php endif; ?>
<div class="table-responsive">
<table class="table table-hover align-middle">
<thead class="table-light">
@ -55,8 +146,8 @@ $customers = $stmt->fetchAll();
</tr>
</thead>
<tbody>
<?php foreach($customers as $c): ?>
<tr>
<?php foreach($customers as $c):
?><tr >
<td><?= $c['id'] ?></td>
<td>
<div class="fw-bold"><?= $lang === 'ar' ? ($c['name_ar'] ?: $c['name_en']) : $c['name_en'] ?></div>
@ -65,16 +156,26 @@ $customers = $stmt->fetchAll();
<td><?= $c['email'] ?: '-' ?></td>
<td class="small text-muted"><?= date('d/m/Y', strtotime($c['created_at'])) ?></td>
<td class="text-end">
<?php if (has_permission('edit')): ?>
<button class="btn btn-sm btn-light border-0 p-2 text-primary" style="border-radius: 8px;">
<i class="bi bi-pencil-fill"></i>
</button>
<?php endif; ?>
<div class="d-flex gap-1 justify-content-end">
<a href="customer_statement.php?id=<?= $c['id'] ?>" class="btn btn-sm btn-light border-0 p-2 text-info" title="<?= __('statement') ?>" style="border-radius: 8px;">
<i class="bi bi-file-earmark-text-fill"></i>
</a>
<?php if (has_permission('edit')):
?><button class="btn btn-sm btn-light border-0 p-2 text-primary" style="border-radius: 8px;" onclick="openCustomerModal(<?= htmlspecialchars(json_encode($c)) ?>)">
<i class="bi bi-pencil-fill"></i>
</button>
<?php endif; ?>
<?php if (has_permission('delete')):
?><button class="btn btn-sm btn-light border-0 p-2 text-danger" style="border-radius: 8px;" onclick="confirmDelete('customer', <?= $c['id'] ?>)">
<i class="bi bi-trash-fill"></i>
</button>
<?php endif; ?>
</div>
</td>
</tr>
<?php endforeach; ?>
<?php if (empty($customers)): ?>
<tr>
<?php if (empty($customers)):
?><tr >
<td colspan="6" class="text-center py-5 text-muted">
<i class="bi bi-people fs-1 d-block mb-3 opacity-25"></i>
<?= __('no_customers_found') ?? 'No customers found' ?>
@ -86,39 +187,129 @@ $customers = $stmt->fetchAll();
</div>
</div>
<!-- Add Customer Modal -->
<?php if (has_permission('add')): ?>
<div class="modal fade" id="addCustomerModal" tabindex="-1">
<!-- Customer Modal (Add/Edit) -->
<div class="modal fade" id="customerModal" tabindex="-1">
<div class="modal-dialog modal-dialog-centered">
<div class="modal-content border-0 shadow-lg" style="border-radius: 20px;">
<div class="modal-header border-0 pb-0">
<h5 class="modal-title fw-bold"><?= __('add_new_customer') ?? 'Add New Customer' ?></h5>
<h5 class="modal-title fw-bold" id="customerModalLabel"><?= __('add_new_customer') ?? 'Add New Customer' ?></h5>
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
</div>
<div class="modal-body p-4">
<form action="api/add_customer_redirect.php" method="POST">
<form id="customerForm" method="POST">
<input type="hidden" name="action" id="customerAction" value="add_customer">
<input type="hidden" name="id" id="customerId">
<div class="mb-3">
<label class="form-label small fw-bold"><?= __('phone') ?></label>
<input type="text" name="phone" class="form-control" required style="border-radius: 12px;">
<input type="text" name="phone" id="customerPhone" class="form-control" required style="border-radius: 12px;">
</div>
<div class="mb-3">
<label class="form-label small fw-bold"><?= __('name_en') ?? 'Name (English)' ?></label>
<input type="text" name="name_en" class="form-control" required style="border-radius: 12px;">
</div>
<div class="mb-3">
<label class="form-label small fw-bold"><?= __('name_ar') ?? 'Name (Arabic)' ?></label>
<input type="text" name="name_ar" class="form-control" style="border-radius: 12px;">
<div class="row">
<div class="col-md-6 mb-3">
<label class="form-label small fw-bold"><?= __('name_en') ?? 'Name (English)' ?></label>
<div class="input-group">
<input type="text" name="name_en" id="customerNameEn" class="form-control" required style="border-top-left-radius: 12px; border-bottom-left-radius: 12px;">
<button type="button" class="btn btn-outline-secondary" style="border-top-right-radius: 12px; border-bottom-right-radius: 12px;" onclick="translateField('customerNameEn', 'customerNameAr', 'en-ar')">
<i class="bi bi-translate"></i>
</button>
</div>
</div>
<div class="col-md-6 mb-3">
<label class="form-label small fw-bold"><?= __('name_ar') ?? 'Name (Arabic)' ?></label>
<div class="input-group">
<input type="text" name="name_ar" id="customerNameAr" class="form-control" style="border-top-left-radius: 12px; border-bottom-left-radius: 12px;">
<button type="button" class="btn btn-outline-secondary" style="border-top-right-radius: 12px; border-bottom-right-radius: 12px;" onclick="translateField('customerNameAr', 'customerNameEn', 'ar-en')">
<i class="bi bi-translate"></i>
</button>
</div>
</div>
</div>
<div class="mb-3">
<label class="form-label small fw-bold"><?= __('email') ?></label>
<input type="email" name="email" class="form-control" style="border-radius: 12px;">
<input type="email" name="email" id="customerEmail" class="form-control" style="border-radius: 12px;">
</div>
<div class="row">
<div class="col-md-6 mb-3">
<label class="form-label small fw-bold"><?= __('address_en') ?? 'Address (English)' ?></label>
<textarea name="address_en" id="customerAddressEn" class="form-control" style="border-radius: 12px;" rows="2"></textarea>
</div>
<div class="col-md-6 mb-3">
<label class="form-label small fw-bold"><?= __('address_ar') ?? 'Address (Arabic)' ?></label>
<textarea name="address_ar" id="customerAddressAr" class="form-control" style="border-radius: 12px;" rows="2"></textarea>
</div>
</div>
<button type="submit" class="btn btn-primary w-100 py-3 mt-2 fw-bold shadow-sm" style="border-radius: 15px;"><?= __('save') ?></button>
</form>
</div>
</div>
</div>
</div>
<?php endif; ?>
<?php require_once __DIR__ . '/includes/footer.php'; ?>
<!-- Delete Confirmation Form -->
<form id="deleteForm" method="POST" style="display: none;">
<input type="hidden" name="action" id="deleteAction">
<input type="hidden" name="id" id="deleteId">
</form>
<script>
async function translateField(sourceId, targetId, direction) {
const sourceEl = document.getElementById(sourceId);
const targetEl = document.getElementById(targetId);
const text = sourceEl.value.trim();
if (!text) return;
const btn = event.currentTarget;
const originalHtml = btn.innerHTML;
btn.disabled = true;
btn.innerHTML = "<span class=\"spinner-border spinner-border-sm\"></span>";
try {
const formData = new FormData();
formData.append("text", text);
formData.append("direction", direction);
const resp = await fetch("api/translate.php", { method: "POST", body: formData });
const data = await resp.json();
if (data.success) targetEl.value = data.translation;
} catch (e) { console.error(e); }
finally { btn.disabled = false; btn.innerHTML = originalHtml; }
}
function openCustomerModal(customer = null) {
const modal = new bootstrap.Modal(document.getElementById('customerModal'));
const form = document.getElementById('customerForm');
const label = document.getElementById('customerModalLabel');
const actionInput = document.getElementById('customerAction');
const idInput = document.getElementById('customerId');
if (customer) {
label.innerText = "<?= __('edit') ?> <?= __('customer') ?>";
actionInput.value = 'edit_customer';
idInput.value = customer.id;
document.getElementById('customerPhone').value = customer.phone;
document.getElementById('customerNameEn').value = customer.name_en;
document.getElementById('customerNameAr').value = customer.name_ar;
document.getElementById('customerEmail').value = customer.email;
document.getElementById('customerAddressEn').value = customer.address_en || '';
document.getElementById('customerAddressAr').value = customer.address_ar || '';
} else {
label.innerText = "<?= __('add_new_customer') ?>";
actionInput.value = 'add_customer';
form.reset();
idInput.value = '';
}
modal.show();
}
function confirmDelete(type, id) {
if (confirm("<?= __('are_you_sure') ?>")) {
document.getElementById('deleteAction').value = 'delete_' + type;
document.getElementById('deleteId').value = id;
document.getElementById('deleteForm').submit();
}
}
</script>
<?php require_once __DIR__ . '/includes/footer.php'; ?>

View File

@ -0,0 +1,6 @@
-- Grant permission to customer_statement.php for all users who have access to customers.php
INSERT INTO user_permissions (user_id, page, can_view, can_add, can_edit, can_delete)
SELECT user_id, 'customer_statement.php', can_view, can_add, can_edit, can_delete
FROM user_permissions
WHERE page = 'customers.php'
ON DUPLICATE KEY UPDATE can_view = VALUES(can_view);

View File

@ -261,7 +261,7 @@ if ($current_user_id && $current_page !== 'login.php' && $current_page !== 'logo
<?php if (has_permission('view', 'lab.php')): ?>
<li class="nav-item">
<a class="nav-link <?= basename($_SERVER['PHP_SELF']) == 'lab.php' ? 'active' : '' ?>" href="lab.php">
<i class="bi bi-flask"></i>
<i class="bi bi-box-seam"></i>
<?= __('lab') ?>
</a>
</li>

View File

@ -197,7 +197,22 @@ $translations = [
'leave_blank_to_keep_current' => 'leave blank to keep current',
'permissions' => 'Permissions',
'user_permissions' => 'User Permissions',
'orders_per_month' => 'Orders per Month'
'orders_per_month' => 'Orders per Month',
'customer_statement' => 'Customer Statement',
'statement' => 'Statement',
'debit' => 'Debit',
'credit' => 'Credit',
'balance' => 'Balance',
'total_debit' => 'Total Debit',
'total_credit' => 'Total Credit',
'opening_balance' => 'Opening Balance',
'closing_balance' => 'Closing Balance',
'transaction_type' => 'Transaction Type',
'description' => 'Description',
'customer_added' => 'Customer added successfully',
'customer_updated' => 'Customer updated successfully',
'customer_deleted' => 'Customer deleted successfully',
'customer_has_orders' => 'Cannot delete customer because they have existing orders',
],
'ar' => [
'dashboard' => 'لوحة القيادة',
@ -388,7 +403,22 @@ $translations = [
'leave_blank_to_keep_current' => 'اتركه فارغاً للاحتفاظ بكلمة المرور الحالية',
'permissions' => 'الصلاحيات',
'user_permissions' => 'صلاحيات المستخدم',
'orders_per_month' => 'الطلبات شهرياً'
'orders_per_month' => 'الطلبات شهرياً',
'customer_statement' => 'كشف حساب عميل',
'statement' => 'كشف حساب',
'debit' => 'مدين',
'credit' => 'دائن',
'balance' => 'الرصيد',
'total_debit' => 'إجمالي المدين',
'total_credit' => 'إجمالي الدائن',
'opening_balance' => 'الرصيد الافتتاحي',
'closing_balance' => 'الرصيد الختامي',
'transaction_type' => 'نوع المعاملة',
'description' => 'الوصف',
'customer_added' => 'تم إضافة العميل بنجاح',
'customer_updated' => 'تم تحديث العميل بنجاح',
'customer_deleted' => 'تم حذف العميل بنجاح',
'customer_has_orders' => 'لا يمكن حذف العميل لوجود طلبات مرتبطة به',
]
];

View File

@ -126,11 +126,85 @@ if ($user_filter !== 'all') {
<div class="d-flex justify-content-between align-items-center mb-4 no-print">
<h4 class="fw-bold mb-0"><?= __('reports') ?></h4>
<button onclick="window.print()" class="btn btn-primary shadow-sm" style="border-radius: 12px;">
<i class="bi bi-printer me-2"></i> <?= __('print_report') ?>
</button>
<div class="d-flex gap-2">
<button type="button" class="btn btn-outline-primary shadow-sm" style="border-radius: 12px;" data-bs-toggle="modal" data-bs-target="#customerStatementModal">
<i class="bi bi-person-lines-fill me-2"></i> <?= __('customer_statement') ?>
</button>
<button onclick="window.print()" class="btn btn-primary shadow-sm" style="border-radius: 12px;">
<i class="bi bi-printer me-2"></i> <?= __('print_report') ?>
</button>
</div>
</div>
<!-- Customer Statement Search Modal -->
<div class="modal fade no-print" id="customerStatementModal" tabindex="-1">
<div class="modal-dialog modal-dialog-centered">
<div class="modal-content border-0 shadow-lg" style="border-radius: 20px;">
<div class="modal-header border-0 pb-0">
<h5 class="modal-title fw-bold"><?= __('customer_statement') ?></h5>
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
</div>
<div class="modal-body p-4">
<form action="customer_statement.php" method="GET">
<div class="mb-3">
<label class="form-label small fw-bold"><?= __('search_customer') ?? 'Search Customer' ?></label>
<div class="input-group">
<span class="input-group-text bg-white border-end-0" style="border-radius: 12px 0 0 12px;"><i class="bi bi-search"></i></span>
<input type="text" id="customerSearch" class="form-control border-start-0" placeholder="<?= __('type_name_or_phone') ?? 'Type name or phone...' ?>" style="border-radius: 0 12px 12px 0;">
</div>
<div id="customerList" class="list-group mt-2 overflow-auto" style="max-height: 200px;"></div>
<input type="hidden" name="id" id="selectedCustomerId" required>
</div>
<div id="selectedCustomerInfo" class="alert alert-light border d-none mb-3" style="border-radius: 12px;">
<div class="fw-bold" id="selectedCustomerName"></div>
<div class="small text-muted" id="selectedCustomerPhone"></div>
</div>
<button type="submit" id="generateStatementBtn" class="btn btn-primary w-100 py-3 fw-bold shadow-sm" style="border-radius: 15px;" disabled>
<?= __('generate_statement') ?? 'Generate Statement' ?>
</button>
</form>
</div>
</div>
</div>
</div>
<script>
document.getElementById('customerSearch').addEventListener('input', function() {
const query = this.value;
if (query.length < 2) {
document.getElementById('customerList').innerHTML = '';
return;
}
fetch('api/search_customers.php?query=' + encodeURIComponent(query))
.then(response => response.json())
.then(data => {
const list = document.getElementById('customerList');
list.innerHTML = '';
if (data.length === 0) {
list.innerHTML = '<div class="list-group-item small text-muted">No customers found</div>';
return;
}
data.forEach(c => {
const item = document.createElement('button');
item.className = 'list-group-item list-group-item-action small';
item.type = 'button';
item.innerHTML = `<strong>${c.name_en}</strong> (${c.phone})`;
item.onclick = () => {
document.getElementById('selectedCustomerId').value = c.id;
document.getElementById('selectedCustomerName').textContent = c.name_en;
document.getElementById('selectedCustomerPhone').textContent = c.phone;
document.getElementById('selectedCustomerInfo').classList.remove('d-none');
document.getElementById('generateStatementBtn').disabled = false;
list.innerHTML = '';
document.getElementById('customerSearch').value = '';
};
list.appendChild(item);
});
});
});
</script>
<!-- Print Header -->
<div class="d-none d-print-block mb-4 border-bottom pb-3">
<div class="row">