index no end changes

This commit is contained in:
Flatlogic Bot 2026-05-03 13:09:07 +00:00
parent c23bc66b9c
commit 0b7a05f3a8
6 changed files with 211 additions and 90 deletions

View File

@ -8827,11 +8827,15 @@ runtime_debug_mark('page:rendering', ['page' => (string)$page]);
<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>
<button class="btn btn-outline-primary edit-quotation-btn" data-id="<?= $q['id'] ?>" 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>
<form method="POST" class="d-inline js-swal-confirm-form" data-confirm-title="Delete this quotation?" data-confirm-text="This quotation will be permanently removed." data-confirm-button="Yes, delete it" data-cancel-button="Keep it">
<input type="hidden" name="delete_quotation" value="1">
<input type="hidden" name="id" value="<?= $q['id'] ?>">
<button type="submit" class="btn btn-outline-danger" title="Delete"><i class="bi bi-trash"></i></button>
</form>
</div>
</td>
</tr>
@ -8950,9 +8954,13 @@ runtime_debug_mark('page:rendering', ['page' => (string)$page]);
<button class="btn btn-outline-info view-lpo-btn" data-json="<?= htmlspecialchars(json_encode($q)) ?>" title="View"><i class="bi bi-eye"></i></button>
<?php if ($q['status'] !== 'converted'): ?>
<button class="btn btn-outline-success" onclick="if(confirm('Convert this LPO to Purchase Invoice?')) { const f = document.createElement('form'); f.method='POST'; f.innerHTML='<input type=hidden name=convert_lpo_to_purchase><input type=hidden name=lpo_id value=<?= $q['id'] ?>>'; document.body.appendChild(f); f.submit(); }" title="Convert to Purchase"><i class="bi bi-arrow-repeat"></i></button>
<button class="btn btn-outline-primary edit-lpo-btn" data-json="<?= htmlspecialchars(json_encode($q)) ?>" data-bs-toggle="modal" data-bs-target="#editLpoModal" title="Edit"><i class="bi bi-pencil-square"></i></button>
<button class="btn btn-outline-primary edit-lpo-btn" data-id="<?= $q['id'] ?>" data-json="<?= htmlspecialchars(json_encode($q)) ?>" data-bs-toggle="modal" data-bs-target="#editLpoModal" title="Edit"><i class="bi bi-pencil-square"></i></button>
<?php endif; ?>
<button class="btn btn-outline-danger" onclick="if(confirm('Delete this LPO?')) { const f = document.createElement('form'); f.method='POST'; f.innerHTML='<input type=hidden name=delete_lpo><input type=hidden name=id value=<?= $q['id'] ?>>'; document.body.appendChild(f); f.submit(); }" title="Delete"><i class="bi bi-trash"></i></button>
<form method="POST" class="d-inline js-swal-confirm-form" data-confirm-title="Delete this LPO?" data-confirm-text="This LPO will be permanently removed." data-confirm-button="Yes, delete it" data-cancel-button="Keep it">
<input type="hidden" name="delete_lpo" value="1">
<input type="hidden" name="id" value="<?= $q['id'] ?>">
<button type="submit" class="btn btn-outline-danger" title="Delete"><i class="bi bi-trash"></i></button>
</form>
</div>
</td>
</tr>
@ -12000,7 +12008,13 @@ document.addEventListener('DOMContentLoaded', function() {
});
});
<?php if (in_array($page, ['sales', 'purchases', 'lpos', 'quotations'], true)): ?>
<?php runtime_debug_require('pages/sales_purchases_invoice_form_helpers.php', ['phase' => 'script', 'page' => (string)$page]); ?>
<?php endif; ?>
<?php if ($page === 'lpos' || $page === 'quotations'): ?>
<?php runtime_debug_require('pages/lpo_quotation_script.php', ['phase' => 'script', 'page' => (string)$page]); ?>
<?php endif; ?>
<?php if ($page === 'sales' || $page === 'purchases'): ?>
<?php runtime_debug_require('pages/sales_purchases_page_script.php', ['phase' => 'script', 'page' => (string)$page]); ?>

View File

@ -2,36 +2,77 @@
initInvoiceForm('lpoProductSearchInput', 'lpoSearchSuggestions', 'lpoItemsTableBody', 'lpo_grand_display', 'lpo_subtotal_display', 'lpo_vat_display');
initInvoiceForm('editLpoProductSearchInput', 'editLpoSearchSuggestions', 'editLpoItemsTableBody', 'edit_lpo_grand_display', 'edit_lpo_subtotal_display', 'edit_lpo_vat_display');
const parseLpoQuotationButtonPayload = (btn) => {
if (!btn || !btn.dataset || !btn.dataset.json) return {};
try {
return JSON.parse(btn.dataset.json);
} catch (error) {
console.warn('Failed to parse LPO/Quotation payload from button data.', error);
return {};
}
};
const renderExistingDocumentItems = (items, tableBodyId, grandTotalId, subtotalId, totalVatId) => {
const tableBody = document.getElementById(tableBodyId);
const grandTotalEl = document.getElementById(grandTotalId);
const subtotalEl = document.getElementById(subtotalId);
const totalVatEl = document.getElementById(totalVatId);
if (!tableBody) return;
tableBody.innerHTML = '';
if (!Array.isArray(items) || items.length === 0) {
if (typeof recalculate === 'function') {
recalculate(tableBody, grandTotalEl, subtotalEl, totalVatEl);
}
return;
}
items.forEach(item => {
addItemToTable({
id: item.item_id || item.id,
name_en: item.name_en || item.item_name_en || 'Item',
name_ar: item.name_ar || item.item_name_ar || '',
sku: item.sku || '',
vat_rate: item.vat_rate || 0,
stock_quantity: item.stock_quantity || 0
}, tableBody, null, null, grandTotalEl, subtotalEl, totalVatEl, {
quantity: item.quantity,
unit_price: item.unit_price
});
});
};
document.querySelectorAll('.edit-lpo-btn').forEach(btn => {
btn.addEventListener('click', function() {
const data = JSON.parse(this.dataset.json);
document.getElementById('edit_lpo_id').value = data.id;
const data = parseLpoQuotationButtonPayload(this);
if (Object.keys(data).length === 0) return;
const supplierSelect = document.getElementById('edit_lpo_supplier_id');
supplierSelect.value = data.supplier_id;
if (window.jQuery && $(supplierSelect).data('select2')) {
$(supplierSelect).trigger('change');
const supplierId = data.supplier_id ?? '';
const supplierLabel = data.supplier_name || '';
invoiceEnsureSelectOption('edit_lpo_supplier_id', supplierId, supplierLabel);
const lpoIdInput = document.getElementById('edit_lpo_id');
const lpoDateInput = document.getElementById('edit_lpo_date');
const deliveryDateInput = document.getElementById('edit_lpo_delivery_date');
const statusSelect = document.getElementById('edit_lpo_status');
const termsInput = document.getElementById('edit_lpo_terms');
if (lpoIdInput) lpoIdInput.value = data.id || '';
if (supplierSelect) {
invoiceSetBlankSelectOptionLabel(supplierSelect, (supplierId === '' && supplierLabel) ? supplierLabel : '---');
supplierSelect.value = supplierId;
invoiceSyncSelect2Value(supplierSelect);
}
document.getElementById('edit_lpo_date').value = data.lpo_date;
document.getElementById('edit_lpo_delivery_date').value = data.delivery_date || '';
document.getElementById('edit_lpo_status').value = data.status || 'pending';
document.getElementById('edit_lpo_terms').value = data.terms_conditions || '';
const tableBody = document.getElementById('editLpoItemsTableBody');
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_lpo_grand_display'),
document.getElementById('edit_lpo_subtotal_display'),
document.getElementById('edit_lpo_vat_display'),
{ quantity: item.quantity, unit_price: item.unit_price });
});
if (lpoDateInput) lpoDateInput.value = data.lpo_date || '';
if (deliveryDateInput) deliveryDateInput.value = data.delivery_date || '';
if (statusSelect) statusSelect.value = data.status || 'pending';
if (termsInput) termsInput.value = data.terms_conditions || '';
renderExistingDocumentItems(data.items || [], 'editLpoItemsTableBody', 'edit_lpo_grand_display', 'edit_lpo_subtotal_display', 'edit_lpo_vat_display');
});
});
@ -182,29 +223,31 @@
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 });
});
const data = parseLpoQuotationButtonPayload(this);
if (Object.keys(data).length === 0) return;
const customerSelect = document.getElementById('edit_quot_customer_id');
const customerId = data.customer_id ?? '';
const customerLabel = data.customer_name || data.party_name || '';
invoiceEnsureSelectOption('edit_quot_customer_id', customerId, customerLabel);
const quotationIdInput = document.getElementById('edit_quotation_id');
const quotationDateInput = document.getElementById('edit_quot_date');
const validUntilInput = document.getElementById('edit_quot_valid');
const statusSelect = document.getElementById('edit_quot_status');
if (quotationIdInput) quotationIdInput.value = data.id || '';
if (customerSelect) {
invoiceSetBlankSelectOptionLabel(customerSelect, (customerId === '' && customerLabel) ? customerLabel : '---');
customerSelect.value = customerId;
invoiceSyncSelect2Value(customerSelect);
}
if (quotationDateInput) quotationDateInput.value = data.quotation_date || '';
if (validUntilInput) validUntilInput.value = data.valid_until || '';
if (statusSelect) statusSelect.value = data.status || 'pending';
renderExistingDocumentItems(data.items || [], 'editQuotItemsTableBody', 'edit_quot_grand_display', 'edit_quot_subtotal_display', 'edit_quot_vat_display');
});
});
@ -355,10 +398,8 @@
editBtn.className = 'btn btn-primary';
editBtn.innerHTML = '<i class="bi bi-pencil-square"></i> <span data-en="Edit" data-ar="تعديل">Edit</span>';
editBtn.onclick = function() {
const editModal = new bootstrap.Modal(document.getElementById('editQuotationModal'));
modal.hide();
const originalEditBtn = document.querySelector(`.edit-quotation-btn[data-json*='"id":${data.id},']`) ||
document.querySelector(`.edit-quotation-btn[data-json*='"id":${data.id}']`);
const originalEditBtn = document.querySelector(`.edit-quotation-btn[data-id="${data.id}"]`);
if (originalEditBtn) originalEditBtn.click();
};
actionButtons.appendChild(editBtn);

View File

@ -9,34 +9,6 @@
}
};
const syncSelect2Value = (select) => {
if (!select) return;
if (select.classList.contains('select2') && window.jQuery && jQuery.fn && jQuery.fn.select2) {
jQuery(select).trigger('change');
}
};
const ensureSelectOption = (selectId, value, label = '') => {
const select = document.getElementById(selectId);
if (!select || value === null || value === undefined || String(value) === '') return;
const alreadyExists = Array.from(select.options || []).some(option => option.value == String(value));
if (!alreadyExists) {
const option = document.createElement('option');
option.value = String(value);
option.textContent = label || String(value);
select.appendChild(option);
}
};
const setBlankSelectOptionLabel = (select, label = '---') => {
if (!select) return;
const emptyOption = Array.from(select.options || []).find(option => option.value === '');
if (emptyOption) {
emptyOption.textContent = label;
}
};
const normalizeEditPaymentType = (paymentType) => {
let normalized = String(paymentType || 'cash').toLowerCase().replace(/[\s-]+/g, '_');
if (normalized === 'pos') normalized = 'cash';
@ -92,13 +64,13 @@
const partyId = data.customer_id ?? data.supplier_id ?? '';
const partyLabel = data.party_name || data.customer_name || data.supplier_name || '';
ensureSelectOption('edit_customer_id', partyId, partyLabel);
invoiceEnsureSelectOption('edit_customer_id', partyId, partyLabel);
if (invoiceIdInput) invoiceIdInput.value = data.id || '';
if (partySelect) {
setBlankSelectOptionLabel(partySelect, (partyId === '' && partyLabel) ? partyLabel : '---');
invoiceSetBlankSelectOptionLabel(partySelect, (partyId === '' && partyLabel) ? partyLabel : '---');
partySelect.value = partyId;
syncSelect2Value(partySelect);
invoiceSyncSelect2Value(partySelect);
}
if (invoiceDateInput) invoiceDateInput.value = data.invoice_date || '';
if (dueDateInput) dueDateInput.value = data.due_date || '';

View File

@ -53,6 +53,100 @@
});
};
const invoiceSyncSelect2Value = (select) => {
if (!select) return;
if (select.classList.contains('select2') && window.jQuery && window.jQuery.fn && window.jQuery.fn.select2) {
window.jQuery(select).trigger('change');
}
};
const invoiceEnsureSelectOption = (selectId, value, label = '') => {
const select = document.getElementById(selectId);
if (!select || value === null || value === undefined || String(value) === '') return;
const alreadyExists = Array.from(select.options || []).some(option => option.value == String(value));
if (!alreadyExists) {
const option = document.createElement('option');
option.value = String(value);
option.textContent = label || String(value);
select.appendChild(option);
}
};
const invoiceSetBlankSelectOptionLabel = (select, label = '---') => {
if (!select) return;
const emptyOption = Array.from(select.options || []).find(option => option.value === '');
if (emptyOption) {
emptyOption.textContent = label;
}
};
const invoiceShowConfirmDialog = async ({
title = 'Are you sure?',
text = '',
confirmButtonText = 'Yes, continue',
cancelButtonText = 'Cancel',
icon = 'warning'
} = {}) => {
if (window.Swal) {
const result = await Swal.fire({
title,
text,
icon,
showCancelButton: true,
confirmButtonText,
cancelButtonText,
reverseButtons: true,
focusCancel: true,
confirmButtonColor: '#dc3545',
cancelButtonColor: '#6c757d'
});
return !!result.isConfirmed;
}
return window.confirm(text ? `${title}
${text}` : title);
};
const bindInvoiceSweetConfirmForms = () => {
document.querySelectorAll('.js-swal-confirm-form').forEach(form => {
if (form.dataset.confirmBound === '1') return;
form.dataset.confirmBound = '1';
form.addEventListener('submit', async function(event) {
if (form.dataset.skipConfirm === '1') {
delete form.dataset.skipConfirm;
return;
}
event.preventDefault();
const confirmed = await invoiceShowConfirmDialog({
title: form.dataset.confirmTitle || 'Are you sure?',
text: form.dataset.confirmText || '',
confirmButtonText: form.dataset.confirmButton || 'Yes, continue',
cancelButtonText: form.dataset.cancelButton || 'Cancel',
icon: form.dataset.confirmIcon || 'warning'
});
if (!confirmed) return;
const submitter = event.submitter || null;
if (submitter && typeof form.requestSubmit === 'function') {
form.dataset.skipConfirm = '1';
form.requestSubmit(submitter);
return;
}
HTMLFormElement.prototype.submit.call(form);
});
});
};
bindInvoiceSweetConfirmForms();
function addItemToTable(item, tableBody, searchInput, suggestions, grandTotalEl, subtotalEl, totalVatEl, customData = null) {
if (suggestions) suggestions.style.display = 'none';
if (searchInput) searchInput.value = '';

View File

@ -1,6 +1,5 @@
<?php
// Sales/Purchases page-specific JavaScript bundle extracted from index.php.
require __DIR__ . '/sales_purchases_payment_receipt_script.php';
require __DIR__ . '/sales_purchases_invoice_form_helpers.php';
require __DIR__ . '/sales_purchases_print_script.php';
require __DIR__ . '/sales_purchases_invoice_actions_script.php';

View File

@ -173,9 +173,10 @@
<button class="btn btn-outline-success pay-invoice-btn" data-id="<?= $inv['id'] ?>" data-total="<?= $inv['total_with_vat'] ?>" data-paid="<?= $inv['paid_amount'] ?>" data-bs-toggle="modal" data-bs-target="#payInvoiceModal" title="Payment"><i class="bi bi-cash-coin"></i></button>
<?php endif; ?>
<button class="btn btn-outline-secondary print-a4-btn" data-json="<?= htmlspecialchars($invoiceJson) ?>" title="Print A4 Invoice"><i class="bi bi-printer"></i></button>
<form method="POST" class="d-inline" onsubmit="return confirm('Are you sure you want to delete this invoice?')">
<form method="POST" class="d-inline js-swal-confirm-form" data-confirm-title="<?= htmlspecialchars(($lang ?? 'en') === 'ar' ? 'هل تريد حذف هذه الفاتورة؟' : 'Delete this invoice?', ENT_QUOTES) ?>" data-confirm-text="<?= htmlspecialchars(($lang ?? 'en') === 'ar' ? 'سيتم حذف هذه الفاتورة نهائياً.' : 'This invoice will be permanently removed.', ENT_QUOTES) ?>" data-confirm-button="<?= htmlspecialchars(($lang ?? 'en') === 'ar' ? 'نعم، احذفها' : 'Yes, delete it', ENT_QUOTES) ?>" data-cancel-button="<?= htmlspecialchars(($lang ?? 'en') === 'ar' ? 'إلغاء' : 'Keep it', ENT_QUOTES) ?>">
<input type="hidden" name="id" value="<?= $inv['id'] ?>">
<button type="submit" name="delete_invoice" class="btn btn-outline-danger" title="Delete"><i class="bi bi-trash"></i></button>
<input type="hidden" name="delete_invoice" value="1">
<button type="submit" class="btn btn-outline-danger" title="Delete"><i class="bi bi-trash"></i></button>
</form>
</div>
</td>