add quotation

This commit is contained in:
Flatlogic Bot 2026-02-16 17:07:50 +00:00
parent d6e5b04b82
commit 07c5256175
2 changed files with 659 additions and 2 deletions

View File

@ -0,0 +1,23 @@
CREATE TABLE IF NOT EXISTS quotations (
id INT AUTO_INCREMENT PRIMARY KEY,
customer_id INT,
quotation_date DATE NOT NULL,
valid_until DATE,
status ENUM('pending', 'converted', 'expired', 'cancelled') DEFAULT 'pending',
total_amount DECIMAL(15,3) DEFAULT 0.000,
vat_amount DECIMAL(15,3) DEFAULT 0.000,
total_with_vat DECIMAL(15,3) DEFAULT 0.000,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
FOREIGN KEY (customer_id) REFERENCES customers(id) ON DELETE SET NULL
);
CREATE TABLE IF NOT EXISTS quotation_items (
id INT AUTO_INCREMENT PRIMARY KEY,
quotation_id INT NOT NULL,
item_id INT NOT NULL,
quantity DECIMAL(15,2) NOT NULL,
unit_price DECIMAL(15,3) DEFAULT 0.000,
total_price DECIMAL(15,3) DEFAULT 0.000,
FOREIGN KEY (quotation_id) REFERENCES quotations(id) ON DELETE CASCADE,
FOREIGN KEY (item_id) REFERENCES stock_items(id)
);

638
index.php
View File

@ -721,6 +721,149 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') {
}
}
if (isset($_POST['add_quotation'])) {
$customer_id = $_POST['customer_id'] ?: null;
$quotation_date = $_POST['quotation_date'] ?: date('Y-m-d');
$valid_until = $_POST['valid_until'] ?: null;
$item_ids = $_POST['item_ids'] ?? [];
$quantities = $_POST['quantities'] ?? [];
$prices = $_POST['prices'] ?? [];
if (!empty($item_ids)) {
$db = db();
$db->beginTransaction();
try {
$subtotal = 0;
$total_vat = 0;
$items_data = [];
foreach ($item_ids as $index => $item_id) {
$qty = (float)$quantities[$index];
$price = (float)$prices[$index];
$stmtVat = $db->prepare("SELECT vat_rate FROM stock_items WHERE id = ?");
$stmtVat->execute([$item_id]);
$vat_rate = (float)$stmtVat->fetchColumn();
$line_total = $qty * $price;
$line_vat = $line_total * ($vat_rate / 100);
$subtotal += $line_total;
$total_vat += $line_vat;
$items_data[] = ['id' => $item_id, 'qty' => $qty, 'price' => $price, 'total' => $line_total];
}
$total_with_vat = $subtotal + $total_vat;
$stmt = $db->prepare("INSERT INTO quotations (customer_id, quotation_date, valid_until, total_amount, vat_amount, total_with_vat) VALUES (?, ?, ?, ?, ?, ?)");
$stmt->execute([$customer_id, $quotation_date, $valid_until, $subtotal, $total_vat, $total_with_vat]);
$quotation_id = $db->lastInsertId();
foreach ($items_data as $item) {
$stmt = $db->prepare("INSERT INTO quotation_items (quotation_id, item_id, quantity, unit_price, total_price) VALUES (?, ?, ?, ?, ?)");
$stmt->execute([$quotation_id, $item['id'], $item['qty'], $item['price'], $item['total']]);
}
$db->commit();
$message = "Quotation #$quotation_id created successfully!";
} catch (Exception $e) {
$db->rollBack();
$message = "Error: " . $e->getMessage();
}
}
}
if (isset($_POST['edit_quotation'])) {
$quotation_id = (int)$_POST['quotation_id'];
$customer_id = $_POST['customer_id'] ?: null;
$quotation_date = $_POST['quotation_date'] ?: date('Y-m-d');
$valid_until = $_POST['valid_until'] ?: null;
$status = $_POST['status'] ?? 'pending';
$item_ids = $_POST['item_ids'] ?? [];
$quantities = $_POST['quantities'] ?? [];
$prices = $_POST['prices'] ?? [];
if ($quotation_id && !empty($item_ids)) {
$db = db();
$db->beginTransaction();
try {
$stmt = $db->prepare("DELETE FROM quotation_items WHERE quotation_id = ?");
$stmt->execute([$quotation_id]);
$subtotal = 0;
$total_vat = 0;
$items_data = [];
foreach ($item_ids as $index => $item_id) {
$qty = (float)$quantities[$index];
$price = (float)$prices[$index];
$stmtVat = $db->prepare("SELECT vat_rate FROM stock_items WHERE id = ?");
$stmtVat->execute([$item_id]);
$vat_rate = (float)$stmtVat->fetchColumn();
$line_total = $qty * $price;
$line_vat = $line_total * ($vat_rate / 100);
$subtotal += $line_total;
$total_vat += $line_vat;
$items_data[] = ['id' => $item_id, 'qty' => $qty, 'price' => $price, 'total' => $line_total];
}
$total_with_vat = $subtotal + $total_vat;
$stmt = $db->prepare("UPDATE quotations SET customer_id = ?, quotation_date = ?, valid_until = ?, status = ?, total_amount = ?, vat_amount = ?, total_with_vat = ? WHERE id = ?");
$stmt->execute([$customer_id, $quotation_date, $valid_until, $status, $subtotal, $total_vat, $total_with_vat, $quotation_id]);
foreach ($items_data as $item) {
$stmt = $db->prepare("INSERT INTO quotation_items (quotation_id, item_id, quantity, unit_price, total_price) VALUES (?, ?, ?, ?, ?)");
$stmt->execute([$quotation_id, $item['id'], $item['qty'], $item['price'], $item['total']]);
}
$db->commit();
$message = "Quotation #$quotation_id updated successfully!";
} catch (Exception $e) {
$db->rollBack();
$message = "Error: " . $e->getMessage();
}
}
}
if (isset($_POST['delete_quotation'])) {
$id = (int)$_POST['id'];
if ($id) {
$stmt = db()->prepare("DELETE FROM quotations WHERE id = ?");
$stmt->execute([$id]);
$message = "Quotation deleted successfully!";
}
}
if (isset($_POST['convert_to_invoice'])) {
$quotation_id = (int)$_POST['quotation_id'];
if ($quotation_id) {
$db = db();
$db->beginTransaction();
try {
$stmt = $db->prepare("SELECT * FROM quotations WHERE id = ?");
$stmt->execute([$quotation_id]);
$q = $stmt->fetch();
if (!$q) throw new Exception("Quotation not found");
if ($q['status'] === 'converted') throw new Exception("Quotation already converted");
$stmt = $db->prepare("SELECT * FROM quotation_items WHERE quotation_id = ?");
$stmt->execute([$quotation_id]);
$items = $stmt->fetchAll();
// Create Invoice
$stmt = $db->prepare("INSERT INTO invoices (customer_id, invoice_date, type, status, total_amount, vat_amount, total_with_vat, paid_amount) VALUES (?, CURDATE(), 'sale', 'unpaid', ?, ?, ?, 0)");
$stmt->execute([$q['customer_id'], $q['total_amount'], $q['vat_amount'], $q['total_with_vat']]);
$invoice_id = $db->lastInsertId();
foreach ($items as $item) {
$stmt = $db->prepare("INSERT INTO invoice_items (invoice_id, item_id, quantity, unit_price, total_price) VALUES (?, ?, ?, ?, ?)");
$stmt->execute([$invoice_id, $item['item_id'], $item['quantity'], $item['unit_price'], $item['total_price']]);
// Update stock
$stmt = $db->prepare("UPDATE stock_items SET stock_quantity = stock_quantity - ? WHERE id = ?");
$stmt->execute([$item['quantity'], $item['item_id']]);
}
// Update quotation status
$stmt = $db->prepare("UPDATE quotations SET status = 'converted' WHERE id = ?");
$stmt->execute([$quotation_id]);
$db->commit();
$message = "Quotation converted to Invoice #$invoice_id successfully!";
} catch (Exception $e) {
$db->rollBack();
$message = "Error: " . $e->getMessage();
}
}
}
if (isset($_POST['add_payment_method'])) {
$name_en = $_POST['name_en'] ?? '';
$name_ar = $_POST['name_ar'] ?? '';
@ -961,6 +1104,37 @@ switch ($page) {
$stmt->execute($params);
$data['items'] = $stmt->fetchAll();
break;
case 'quotations':
$where = ["1=1"];
$params = [];
if (!empty($_GET['search'])) {
$where[] = "(q.id LIKE ? OR c.name LIKE ?)";
$params[] = "%{$_GET['search']}%";
$params[] = "%{$_GET['search']}%";
}
if (!empty($_GET['customer_id'])) {
$where[] = "q.customer_id = ?";
$params[] = $_GET['customer_id'];
}
if (!empty($_GET['start_date'])) {
$where[] = "q.quotation_date >= ?";
$params[] = $_GET['start_date'];
}
if (!empty($_GET['end_date'])) {
$where[] = "q.quotation_date <= ?";
$params[] = $_GET['end_date'];
}
$whereSql = implode(" AND ", $where);
$stmt = db()->prepare("SELECT q.*, c.name as customer_name
FROM quotations q
LEFT JOIN customers c ON q.customer_id = c.id
WHERE $whereSql
ORDER BY q.id DESC");
$stmt->execute($params);
$data['quotations'] = $stmt->fetchAll();
$data['items_list'] = db()->query("SELECT id, name_en, name_ar, sale_price, purchase_price, stock_quantity, vat_rate FROM stock_items ORDER BY name_en ASC")->fetchAll();
$data['customers_list'] = db()->query("SELECT id, name FROM customers WHERE type = 'customer' ORDER BY name ASC")->fetchAll();
break;
case 'payment_methods':
$data['payment_methods'] = db()->query("SELECT * FROM payment_methods ORDER BY id DESC")->fetchAll();
break;
@ -1110,7 +1284,7 @@ $projectDescription = $_SERVER['PROJECT_DESCRIPTION'] ?? 'Accounting System';
<span data-en="Operations" data-ar="العمليات">Operations</span>
<i class="bi bi-chevron-down"></i>
</div>
<div class="collapse <?= in_array($page, ['sales', 'purchases', 'pos']) ? 'show' : '' ?>" id="ops-collapse">
<div class="collapse <?= in_array($page, ['sales', 'purchases', 'pos', 'quotations']) ? 'show' : '' ?>" id="ops-collapse">
<a href="index.php?page=pos" class="nav-link <?= isset($_GET['page']) && $_GET['page'] === 'pos' ? 'active' : '' ?>">
<i class="bi bi-display"></i> <span data-en="Point of Sale" data-ar="نقطة البيع">Point of Sale</span>
</a>
@ -1120,6 +1294,9 @@ $projectDescription = $_SERVER['PROJECT_DESCRIPTION'] ?? 'Accounting System';
<a href="index.php?page=purchases" class="nav-link <?= isset($_GET['page']) && $_GET['page'] === 'purchases' ? 'active' : '' ?>">
<i class="bi bi-bag"></i> <span data-en="Purchase Tax Invoices" data-ar="فواتير المشتريات الضريبية">Purchase Tax Invoices</span>
</a>
<a href="index.php?page=quotations" class="nav-link <?= isset($_GET['page']) && $_GET['page'] === 'quotations' ? 'active' : '' ?>">
<i class="bi bi-file-earmark-text"></i> <span data-en="Quotations" data-ar="العروض">Quotations</span>
</a>
<a href="#" class="nav-link">
<i class="bi bi-wallet2"></i> <span data-en="Expenses" data-ar="المصروفات">Expenses</span>
</a>
@ -1196,6 +1373,7 @@ $projectDescription = $_SERVER['PROJECT_DESCRIPTION'] ?? 'Accounting System';
$titles = [
'dashboard' => ['en' => 'Dashboard', 'ar' => 'لوحة القيادة'],
'pos' => ['en' => 'Point of Sale', 'ar' => 'نقطة البيع'],
'quotations' => ['en' => 'Quotations', 'ar' => 'العروض'],
'customers' => ['en' => 'Customers', 'ar' => 'العملاء'],
'suppliers' => ['en' => 'Suppliers', 'ar' => 'الموردون'],
'categories' => ['en' => 'Stock Categories', 'ar' => 'فئات المخزون'],
@ -2528,6 +2706,109 @@ $projectDescription = $_SERVER['PROJECT_DESCRIPTION'] ?? 'Accounting System';
});
</script>
<?php elseif ($page === 'quotations'): ?>
<div class="card p-4">
<div class="d-flex justify-content-between align-items-center mb-4">
<h5 class="m-0" data-en="Quotations" data-ar="عروض الأسعار">Quotations</h5>
<button class="btn btn-primary" data-bs-toggle="modal" data-bs-target="#addQuotationModal">
<i class="bi bi-plus-lg"></i> <span data-en="Create New Quotation" data-ar="إنشاء عرض سعر جديد">Create New Quotation</span>
</button>
</div>
<!-- Filters Section -->
<div class="bg-light p-3 rounded mb-4">
<form method="GET" class="row g-3">
<input type="hidden" name="page" value="quotations">
<div class="col-md-3">
<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="Quot # or Name...">
</div>
<div class="col-md-3">
<label class="form-label small fw-bold" data-en="Customer" data-ar="العميل">Customer</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="col-md-2">
<label class="form-label small fw-bold" data-en="Start Date" data-ar="من تاريخ">Start Date</label>
<input type="date" name="start_date" class="form-control form-control-sm" value="<?= htmlspecialchars($_GET['start_date'] ?? '') ?>">
</div>
<div class="col-md-2">
<label class="form-label small fw-bold" data-en="End Date" data-ar="إلى تاريخ">End Date</label>
<input type="date" name="end_date" class="form-control form-control-sm" value="<?= htmlspecialchars($_GET['end_date'] ?? '') ?>">
</div>
<div class="col-md-2 d-flex align-items-end gap-1">
<button type="submit" class="btn btn-primary btn-sm flex-grow-1">
<i class="bi bi-filter"></i> <span data-en="Filter" data-ar="تصفية">Filter</span>
</button>
<a href="index.php?page=quotations" class="btn btn-outline-secondary btn-sm flex-grow-1">
<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">
<thead>
<tr>
<th data-en="Quotation #" data-ar="رقم العرض">Quotation #</th>
<th data-en="Date" data-ar="التاريخ">Date</th>
<th data-en="Valid Until" data-ar="صالح حتى">Valid Until</th>
<th data-en="Customer" data-ar="العميل">Customer</th>
<th data-en="Status" data-ar="الحالة">Status</th>
<th data-en="Total" data-ar="الإجمالي" class="text-end">Total</th>
<th data-en="Actions" data-ar="الإجراءات" class="text-end">Actions</th>
</tr>
</thead>
<tbody>
<?php
foreach ($data['quotations'] as $q):
$items = db()->prepare("SELECT qi.*, i.name_en, i.name_ar, i.vat_rate
FROM quotation_items qi
JOIN stock_items i ON qi.item_id = i.id
WHERE qi.quotation_id = ?");
$items->execute([$q['id']]);
$q['items'] = $items->fetchAll(PDO::FETCH_ASSOC);
?>
<tr>
<td>QUO-<?= str_pad((string)$q['id'], 5, '0', STR_PAD_LEFT) ?></td>
<td><?= $q['quotation_date'] ?></td>
<td><?= $q['valid_until'] ?: '---' ?></td>
<td><?= htmlspecialchars($q['customer_name'] ?? '---') ?></td>
<td>
<?php
$statusClass = 'bg-secondary';
if ($q['status'] === 'converted') $statusClass = 'bg-success';
elseif ($q['status'] === 'pending') $statusClass = 'bg-warning text-dark';
elseif ($q['status'] === 'expired' || $q['status'] === 'cancelled') $statusClass = 'bg-danger';
?>
<span class="badge text-uppercase <?= $statusClass ?>"><?= htmlspecialchars($q['status']) ?></span>
</td>
<td class="text-end fw-bold">OMR <?= number_format((float)$q['total_with_vat'], 3) ?></td>
<td class="text-end">
<div class="btn-group btn-group-sm">
<button class="btn btn-outline-info view-quotation-btn" data-json="<?= htmlspecialchars(json_encode($q)) ?>" title="View"><i class="bi bi-eye"></i></button>
<button class="btn btn-outline-secondary" onclick="window.viewAndPrintQuotation(<?= htmlspecialchars(json_encode($q)) ?>)" title="Print"><i class="bi bi-printer"></i></button>
<button class="btn btn-outline-primary edit-quotation-btn" data-json="<?= htmlspecialchars(json_encode($q)) ?>" data-bs-toggle="modal" data-bs-target="#editQuotationModal" title="Edit"><i class="bi bi-pencil-square"></i></button>
<?php if ($q['status'] === 'pending'): ?>
<button class="btn btn-outline-success convert-quotation-btn" data-id="<?= $q['id'] ?>" title="Convert to Invoice"><i class="bi bi-receipt"></i></button>
<?php endif; ?>
<button class="btn btn-outline-danger" onclick="if(confirm('Delete this quotation?')) { const f = document.createElement('form'); f.method='POST'; f.innerHTML='<input type=hidden name=delete_quotation><input type=hidden name=id value=<?= $q['id'] ?>>'; document.body.appendChild(f); f.submit(); }" title="Delete"><i class="bi bi-trash"></i></button>
</div>
</td>
</tr>
<?php endforeach; ?>
<?php if (empty($data['quotations'])): ?>
<tr><td colspan="7" class="text-center py-4 text-muted" data-en="No quotations found" data-ar="لا توجد عروض أسعار">No quotations found</td></tr>
<?php endif; ?>
</tbody>
</table>
</div>
</div>
<?php elseif ($page === 'sales' || $page === 'purchases'): ?>
<div class="card p-4">
<div class="d-flex justify-content-between align-items-center mb-4">
@ -3470,10 +3751,175 @@ document.addEventListener('DOMContentLoaded', function() {
});
}
const invoiceType = '<?= $page === "sales" ? "sale" : ($page === "purchases" ? "purchase" : "") ?>';
const invoiceType = '<?= in_array($page, ["sales", "quotations"]) ? "sale" : ($page === "purchases" ? "purchase" : "") ?>';
initInvoiceForm('productSearchInput', 'searchSuggestions', 'invoiceItemsTableBody', 'grandTotal', 'subtotal', 'totalVat');
initInvoiceForm('editProductSearchInput', 'editSearchSuggestions', 'editInvoiceItemsTableBody', 'edit_grandTotal', 'edit_subtotal', 'edit_totalVat');
// Quotation Form Logic
initInvoiceForm('quotProductSearchInput', 'quotSearchSuggestions', 'quotItemsTableBody', 'quot_grand_display', 'quot_subtotal_display', 'quot_vat_display');
initInvoiceForm('editQuotProductSearchInput', 'editQuotSearchSuggestions', 'editQuotItemsTableBody', 'edit_quot_grand_display', 'edit_quot_subtotal_display', 'edit_quot_vat_display');
document.querySelectorAll('.edit-quotation-btn').forEach(btn => {
btn.addEventListener('click', function() {
const data = JSON.parse(this.dataset.json);
document.getElementById('edit_quotation_id').value = data.id;
document.getElementById('edit_quot_customer_id').value = data.customer_id;
document.getElementById('edit_quot_date').value = data.quotation_date;
document.getElementById('edit_quot_valid').value = data.valid_until || '';
document.getElementById('edit_quot_status').value = data.status || 'pending';
const tableBody = document.getElementById('editQuotItemsTableBody');
tableBody.innerHTML = '';
data.items.forEach(item => {
addItemToTable({
id: item.item_id,
name_en: item.name_en,
name_ar: item.name_ar,
sku: '',
vat_rate: item.vat_rate || 0
}, tableBody, null, null,
document.getElementById('edit_quot_grand_display'),
document.getElementById('edit_quot_subtotal_display'),
document.getElementById('edit_quot_vat_display'),
{ quantity: item.quantity, unit_price: item.unit_price });
});
});
});
document.querySelectorAll('.convert-quotation-btn').forEach(btn => {
btn.addEventListener('click', function() {
if (confirm('Convert this quotation to an invoice? This will reduce stock.')) {
const f = document.createElement('form');
f.method = 'POST';
f.innerHTML = `<input type="hidden" name="convert_to_invoice" value="1"><input type="hidden" name="quotation_id" value="${this.dataset.id}">`;
document.body.appendChild(f);
f.submit();
}
});
});
// View Quotation Logic
window.viewAndPrintQuotation = function(data, autoPrint = false) {
const modal = new bootstrap.Modal(document.getElementById('viewQuotationModal'));
const content = document.getElementById('quotationPrintableArea');
let itemsHtml = '';
data.items.forEach((item, index) => {
itemsHtml += `
<tr>
<td>${index + 1}</td>
<td>${item.name_en}<br><small>${item.name_ar}</small></td>
<td class="text-center">${item.quantity}</td>
<td class="text-end">${parseFloat(item.unit_price).toFixed(3)}</td>
<td class="text-center">${item.vat_rate}%</td>
<td class="text-end">${parseFloat(item.total_price).toFixed(3)}</td>
</tr>
`;
});
content.innerHTML = `
<div class="p-5">
<div class="d-flex justify-content-between mb-4 border-bottom pb-3">
<div>
<h3 class="text-primary fw-bold">QUOTATION</h3>
<p class="mb-0"><strong>No:</strong> QUO-${data.id.toString().padStart(5, '0')}</p>
<p class="mb-0"><strong>Date:</strong> ${data.quotation_date}</p>
<p class="mb-0"><strong>Valid Until:</strong> ${data.valid_until || 'N/A'}</p>
</div>
<div class="text-end">
<h4 class="fw-bold">${data.customer_name || 'Walk-in Customer'}</h4>
<p class="mb-0">Status: <span class="badge ${data.status === 'converted' ? 'bg-success' : 'bg-secondary'}">${data.status.toUpperCase()}</span></p>
</div>
</div>
<table class="table table-bordered table-striped">
<thead class="bg-dark text-white">
<tr>
<th>#</th>
<th>Item Description</th>
<th class="text-center">Qty</th>
<th class="text-end">Unit Price</th>
<th class="text-center">VAT</th>
<th class="text-end">Total</th>
</tr>
</thead>
<tbody>${itemsHtml}</tbody>
<tfoot>
<tr>
<th colspan="5" class="text-end">Subtotal</th>
<td class="text-end fw-bold">${parseFloat(data.total_amount).toFixed(3)}</td>
</tr>
<tr>
<th colspan="5" class="text-end">VAT Amount</th>
<td class="text-end fw-bold">${parseFloat(data.vat_amount).toFixed(3)}</td>
</tr>
<tr class="table-primary">
<th colspan="5" class="text-end h5">Grand Total (OMR)</th>
<td class="text-end h5 fw-bold">${parseFloat(data.total_with_vat).toFixed(3)}</td>
</tr>
</tfoot>
</table>
<div class="mt-5 pt-3 border-top">
<div class="row">
<div class="col-6">
<p class="small text-muted">Terms & Conditions:</p>
<ul class="small text-muted">
<li>Quotation is valid until the date mentioned above.</li>
<li>Prices are inclusive of VAT where applicable.</li>
</ul>
</div>
<div class="col-6 text-end pt-4">
<div class="border-top d-inline-block px-5">Authorized Signature</div>
</div>
</div>
</div>
</div>
`;
const actionButtons = document.getElementById('quotationActionButtons');
actionButtons.innerHTML = '';
if (data.status === 'pending') {
const convertBtn = document.createElement('button');
convertBtn.className = 'btn btn-success me-2';
convertBtn.innerHTML = '<i class="bi bi-receipt"></i> Convert to Invoice';
convertBtn.onclick = function() {
if (confirm('Convert this quotation to an invoice?')) {
const f = document.createElement('form');
f.method = 'POST';
f.innerHTML = `<input type="hidden" name="convert_to_invoice" value="1"><input type="hidden" name="quotation_id" value="${data.id}">`;
document.body.appendChild(f);
f.submit();
}
};
actionButtons.appendChild(convertBtn);
const editBtn = document.createElement('button');
editBtn.className = 'btn btn-primary';
editBtn.innerHTML = '<i class="bi bi-pencil-square"></i> Edit';
editBtn.onclick = function() {
const editModal = new bootstrap.Modal(document.getElementById('editQuotationModal'));
modal.hide();
// Trigger the existing edit button click logic or manually populate
const originalEditBtn = document.querySelector(`.edit-quotation-btn[data-json*='"id":${data.id},']`) ||
document.querySelector(`.edit-quotation-btn[data-json*='"id":${data.id}']`);
if (originalEditBtn) originalEditBtn.click();
};
actionButtons.appendChild(editBtn);
}
modal.show();
if (autoPrint) {
setTimeout(() => { window.print(); }, 500);
}
};
document.querySelectorAll('.view-quotation-btn').forEach(btn => {
btn.addEventListener('click', function() {
const data = JSON.parse(this.dataset.json);
window.viewAndPrintQuotation(data, false);
});
});
// Edit Invoice Logic
document.querySelectorAll('.edit-invoice-btn').forEach(btn => {
btn.addEventListener('click', function() {
@ -3747,6 +4193,194 @@ document.addEventListener('DOMContentLoaded', function() {
</div>
</div>
<!-- Add Quotation Modal -->
<div class="modal fade" id="addQuotationModal" tabindex="-1">
<div class="modal-dialog modal-xl">
<div class="modal-content border-0 shadow">
<div class="modal-header bg-primary text-white">
<h5 class="modal-title" data-en="Create New Quotation" data-ar="إنشاء عرض سعر جديد">Create New Quotation</h5>
<button type="button" class="btn-close btn-close-white" data-bs-dismiss="modal"></button>
</div>
<form method="POST">
<div class="modal-body p-4">
<div class="row g-3 mb-4">
<div class="col-md-4">
<label class="form-label fw-bold" data-en="Customer" data-ar="العميل">Customer</label>
<select name="customer_id" class="form-select" required>
<option value="">---</option>
<?php foreach ($data['customers_list'] as $c): ?>
<option value="<?= $c['id'] ?>"><?= htmlspecialchars($c['name']) ?></option>
<?php endforeach; ?>
</select>
</div>
<div class="col-md-4">
<label class="form-label fw-bold" data-en="Date" data-ar="التاريخ">Date</label>
<input type="date" name="quotation_date" class="form-control" value="<?= date('Y-m-d') ?>" required>
</div>
<div class="col-md-4">
<label class="form-label fw-bold" data-en="Valid Until" data-ar="صالح حتى">Valid Until</label>
<input type="date" name="valid_until" class="form-control">
</div>
</div>
<div class="card mb-4">
<div class="card-body bg-light">
<label class="form-label fw-bold" data-en="Search Items" data-ar="بحث عن أصناف">Search Items</label>
<div class="position-relative">
<input type="text" id="quotProductSearchInput" class="form-control" placeholder="Search by name or SKU..." autocomplete="off">
<div id="quotSearchSuggestions" class="list-group position-absolute w-100 shadow-sm" style="display: none; z-index: 1000;"></div>
</div>
</div>
</div>
<div class="table-responsive">
<table class="table table-bordered align-middle">
<thead class="bg-light">
<tr>
<th style="width: 40%;" data-en="Item Details" data-ar="تفاصيل الصنف">Item Details</th>
<th style="width: 15%;" data-en="Qty" data-ar="الكمية">Qty</th>
<th style="width: 15%;" data-en="Unit Price" data-ar="سعر الوحدة">Unit Price</th>
<th style="width: 10%;" data-en="VAT" data-ar="الضريبة">VAT</th>
<th style="width: 15%;" data-en="Total" data-ar="الإجمالي">Total</th>
<th style="width: 5%;"></th>
</tr>
</thead>
<tbody id="quotItemsTableBody"></tbody>
<tfoot>
<tr>
<td colspan="4" class="text-end fw-bold" data-en="Subtotal" data-ar="الإجمالي الفرعي">Subtotal</td>
<td class="text-end fw-bold" id="quot_subtotal_display">OMR 0.000</td>
</tr>
<tr>
<td colspan="4" class="text-end fw-bold" data-en="Total VAT" data-ar="إجمالي الضريبة">Total VAT</td>
<td class="text-end fw-bold" id="quot_vat_display">OMR 0.000</td>
</tr>
<tr class="table-primary">
<td colspan="4" class="text-end fw-bold h5" data-en="Grand Total" data-ar="الإجمالي النهائي">Grand Total</td>
<td class="text-end fw-bold h5" id="quot_grand_display">OMR 0.000</td>
</tr>
</tfoot>
</table>
</div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-light" data-bs-dismiss="modal" data-en="Cancel" data-ar="إلغاء">Cancel</button>
<button type="submit" name="add_quotation" class="btn btn-primary" data-en="Create Quotation" data-ar="إنشاء عرض السعر">Create Quotation</button>
</div>
</form>
</div>
</div>
</div>
<!-- Edit Quotation Modal -->
<div class="modal fade" id="editQuotationModal" tabindex="-1">
<div class="modal-dialog modal-xl">
<div class="modal-content border-0 shadow">
<div class="modal-header bg-primary text-white">
<h5 class="modal-title" data-en="Edit Quotation" data-ar="تعديل عرض السعر">Edit Quotation</h5>
<button type="button" class="btn-close btn-close-white" data-bs-dismiss="modal"></button>
</div>
<form method="POST">
<input type="hidden" name="quotation_id" id="edit_quotation_id">
<div class="modal-body p-4">
<div class="row g-3 mb-4">
<div class="col-md-3">
<label class="form-label fw-bold" data-en="Customer" data-ar="العميل">Customer</label>
<select name="customer_id" id="edit_quot_customer_id" class="form-select" required>
<option value="">---</option>
<?php foreach ($data['customers_list'] as $c): ?>
<option value="<?= $c['id'] ?>"><?= htmlspecialchars($c['name']) ?></option>
<?php endforeach; ?>
</select>
</div>
<div class="col-md-3">
<label class="form-label fw-bold" data-en="Date" data-ar="التاريخ">Date</label>
<input type="date" name="quotation_date" id="edit_quot_date" class="form-control" required>
</div>
<div class="col-md-3">
<label class="form-label fw-bold" data-en="Valid Until" data-ar="صالح حتى">Valid Until</label>
<input type="date" name="valid_until" id="edit_quot_valid" class="form-control">
</div>
<div class="col-md-3">
<label class="form-label fw-bold" data-en="Status" data-ar="الحالة">Status</label>
<select name="status" id="edit_quot_status" class="form-select">
<option value="pending">Pending</option>
<option value="converted">Converted</option>
<option value="expired">Expired</option>
<option value="cancelled">Cancelled</option>
</select>
</div>
</div>
<div class="card mb-4">
<div class="card-body bg-light">
<label class="form-label fw-bold" data-en="Search Items" data-ar="بحث عن أصناف">Search Items</label>
<div class="position-relative">
<input type="text" id="editQuotProductSearchInput" class="form-control" placeholder="Search by name or SKU..." autocomplete="off">
<div id="editQuotSearchSuggestions" class="list-group position-absolute w-100 shadow-sm" style="display: none; z-index: 1000;"></div>
</div>
</div>
</div>
<div class="table-responsive">
<table class="table table-bordered align-middle">
<thead class="bg-light">
<tr>
<th style="width: 40%;" data-en="Item Details" data-ar="تفاصيل الصنف">Item Details</th>
<th style="width: 15%;" data-en="Qty" data-ar="الكمية">Qty</th>
<th style="width: 15%;" data-en="Unit Price" data-ar="سعر الوحدة">Unit Price</th>
<th style="width: 10%;" data-en="VAT" data-ar="الضريبة">VAT</th>
<th style="width: 15%;" data-en="Total" data-ar="الإجمالي">Total</th>
<th style="width: 5%;"></th>
</tr>
</thead>
<tbody id="editQuotItemsTableBody"></tbody>
<tfoot>
<tr>
<td colspan="4" class="text-end fw-bold" data-en="Subtotal" data-ar="الإجمالي الفرعي">Subtotal</td>
<td class="text-end fw-bold" id="edit_quot_subtotal_display">OMR 0.000</td>
</tr>
<tr>
<td colspan="4" class="text-end fw-bold" data-en="Total VAT" data-ar="إجمالي الضريبة">Total VAT</td>
<td class="text-end fw-bold" id="edit_quot_vat_display">OMR 0.000</td>
</tr>
<tr class="table-primary">
<td colspan="4" class="text-end fw-bold h5" data-en="Grand Total" data-ar="الإجمالي النهائي">Grand Total</td>
<td class="text-end fw-bold h5" id="edit_quot_grand_display">OMR 0.000</td>
</tr>
</tfoot>
</table>
</div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-light" data-bs-dismiss="modal" data-en="Cancel" data-ar="إلغاء">Cancel</button>
<button type="submit" name="edit_quotation" class="btn btn-primary" data-en="Update Quotation" data-ar="تحديث عرض السعر">Update Quotation</button>
</div>
</form>
</div>
</div>
</div>
<!-- View Quotation Modal -->
<div class="modal fade" id="viewQuotationModal" tabindex="-1">
<div class="modal-dialog modal-lg">
<div class="modal-content border-0 shadow">
<div class="modal-header bg-info text-white d-print-none">
<h5 class="modal-title" data-en="View Quotation" data-ar="عرض سعر">View Quotation</h5>
<button type="button" class="btn-close btn-close-white" data-bs-dismiss="modal"></button>
</div>
<div class="modal-body p-0" id="quotationPrintableArea">
<!-- Dynamic content -->
</div>
<div class="modal-footer d-print-none">
<div id="quotationActionButtons" class="me-auto"></div>
<button type="button" class="btn btn-secondary" onclick="window.print()"><i class="bi bi-printer"></i> Print</button>
<button type="button" class="btn btn-light" data-bs-dismiss="modal">Close</button>
</div>
</div>
</div>
</div>
<style>
@media print {
.no-print, .sidebar, .topbar, .card, .btn, .modal-header, .modal-footer, .d-print-none, .table-responsive,