Autosave: 20260303-082124
This commit is contained in:
parent
ec6f16b511
commit
1de3f1d7d1
25
api/search_customers.php
Normal file
25
api/search_customers.php
Normal 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
304
customer_statement.php
Normal 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'; ?>
|
||||
247
customers.php
247
customers.php
@ -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'; ?>
|
||||
|
||||
6
db/migrations/15_add_customer_statement_permission.sql
Normal file
6
db/migrations/15_add_customer_statement_permission.sql
Normal 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);
|
||||
@ -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>
|
||||
|
||||
@ -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' => 'لا يمكن حذف العميل لوجود طلبات مرتبطة به',
|
||||
]
|
||||
];
|
||||
|
||||
|
||||
80
reports.php
80
reports.php
@ -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">
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user