new updates
This commit is contained in:
parent
5f5205783f
commit
07894c8f77
14
db/migrations/2026-05-01_items_notes.sql
Normal file
14
db/migrations/2026-05-01_items_notes.sql
Normal file
@ -0,0 +1,14 @@
|
||||
-- Add notes field to products/items for internal product remarks.
|
||||
-- Safe to run multiple times on existing databases.
|
||||
|
||||
SET @db_name := DATABASE();
|
||||
|
||||
SET @sql := IF (
|
||||
EXISTS (
|
||||
SELECT 1 FROM information_schema.COLUMNS
|
||||
WHERE TABLE_SCHEMA = @db_name AND TABLE_NAME = 'items' AND COLUMN_NAME = 'notes'
|
||||
),
|
||||
'SELECT 1',
|
||||
"ALTER TABLE items ADD COLUMN notes text DEFAULT NULL AFTER image_url"
|
||||
);
|
||||
PREPARE stmt FROM @sql; EXECUTE stmt; DEALLOCATE PREPARE stmt;
|
||||
34
db/migrations/2026-05-01_production_updates.sql
Normal file
34
db/migrations/2026-05-01_production_updates.sql
Normal file
@ -0,0 +1,34 @@
|
||||
-- Production sync for 2026-05-01 changes.
|
||||
-- Safe to import on an existing production database.
|
||||
-- Includes all schema migrations introduced on 2026-05-01.
|
||||
-- Current scope:
|
||||
-- 1) items.notes for internal product remarks in stock management.
|
||||
|
||||
SET @OLD_FOREIGN_KEY_CHECKS = @@FOREIGN_KEY_CHECKS;
|
||||
SET FOREIGN_KEY_CHECKS = 0;
|
||||
|
||||
DROP PROCEDURE IF EXISTS apply_production_updates_20260501;
|
||||
DELIMITER $$
|
||||
CREATE PROCEDURE apply_production_updates_20260501()
|
||||
BEGIN
|
||||
-- items.notes
|
||||
IF NOT EXISTS (
|
||||
SELECT 1
|
||||
FROM INFORMATION_SCHEMA.COLUMNS
|
||||
WHERE TABLE_SCHEMA = DATABASE()
|
||||
AND TABLE_NAME = 'items'
|
||||
AND COLUMN_NAME = 'notes'
|
||||
) THEN
|
||||
ALTER TABLE items
|
||||
ADD COLUMN notes TEXT DEFAULT NULL AFTER image_url;
|
||||
END IF;
|
||||
END $$
|
||||
DELIMITER ;
|
||||
|
||||
CALL apply_production_updates_20260501();
|
||||
DROP PROCEDURE IF EXISTS apply_production_updates_20260501;
|
||||
|
||||
SET FOREIGN_KEY_CHECKS = @OLD_FOREIGN_KEY_CHECKS;
|
||||
|
||||
-- Optional verification after import:
|
||||
-- SHOW COLUMNS FROM items;
|
||||
@ -73,6 +73,7 @@ CREATE TABLE IF NOT EXISTS `items` (
|
||||
`category_id` int(10) unsigned DEFAULT NULL,
|
||||
`supplier_id` int(10) unsigned DEFAULT NULL,
|
||||
`image_url` varchar(255) DEFAULT NULL,
|
||||
`notes` text DEFAULT NULL,
|
||||
`created_at` datetime DEFAULT current_timestamp(),
|
||||
`unit_id` int(10) unsigned DEFAULT NULL,
|
||||
`in_catalog` tinyint(1) NOT NULL DEFAULT 0,
|
||||
|
||||
@ -24,7 +24,7 @@ $isEidSale = (($editSale['order_type'] ?? 'standard') === 'eid');
|
||||
$activeNav = $isEidSale ? 'eid_orders' : 'sales';
|
||||
$error = '';
|
||||
$editPaymentSummary = sale_payment_summary($editSale);
|
||||
$paymentAmountInput = (string) ($_POST['payment_amount'] ?? ($editPaymentSummary['paid_amount'] > 0 ? number_format((float) $editPaymentSummary['paid_amount'], 3, '.', '') : ''));
|
||||
$paymentAmountInput = (string) ($_POST['payment_amount'] ?? number_format((float) $editPaymentSummary['paid_amount'], 3, '.', ''));
|
||||
$catalog = catalog();
|
||||
$allowedBranches = get_user_branches($user);
|
||||
$deliveryOptions = eid_delivery_status_options();
|
||||
@ -45,6 +45,7 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
||||
$customerName = trim((string) ($_POST['customer_name'] ?? ''));
|
||||
$paymentMethod = trim((string) ($_POST['payment_method'] ?? 'cash'));
|
||||
$paymentAmountInput = trim((string) ($_POST['payment_amount'] ?? ''));
|
||||
$paymentAmountForCalculation = $paymentAmountInput === '' ? '0' : $paymentAmountInput;
|
||||
$saleStatus = trim((string) ($_POST['sale_status'] ?? 'completed'));
|
||||
$saleStatusInput = $saleStatus;
|
||||
$notes = trim((string) ($_POST['notes'] ?? ''));
|
||||
@ -106,7 +107,7 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
||||
if ($paymentAmountInput !== '' && !is_numeric($paymentAmountInput)) {
|
||||
$error = tr('أدخل مبلغاً مدفوعاً صحيحاً.', 'Enter a valid paid amount.');
|
||||
} else {
|
||||
$paymentMeta = sale_payment_breakdown($totalAmount, $paymentMethod, $paymentAmountInput);
|
||||
$paymentMeta = sale_payment_breakdown($totalAmount, $paymentMethod, $paymentAmountForCalculation);
|
||||
if ($paymentMeta['due_amount'] > 0.0005 && !$customerId) {
|
||||
$error = tr('يجب اختيار عميل مسجل عند وجود مبلغ متبقٍ أو دفعة جزئية.', 'Select a registered customer when there is a remaining balance or partial payment.');
|
||||
}
|
||||
@ -186,7 +187,7 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
||||
}
|
||||
|
||||
set_flash($flashType, $flashMessage);
|
||||
redirect_to('sale.php', ['id' => $editSaleId]);
|
||||
redirect_to($isEidSale ? 'eid_orders.php' : 'sales.php');
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -482,7 +483,21 @@ require __DIR__ . '/includes/header.php';
|
||||
<div class="mb-3">
|
||||
<label class="form-label" for="payment_amount"><?= h(tr('المبلغ المدفوع الآن', 'Paid Now')) ?></label>
|
||||
<input type="number" class="form-control custom-input" id="payment_amount" name="payment_amount" min="0" step="0.001" value="<?= h($paymentAmountInput) ?>" placeholder="0.000">
|
||||
<div class="form-text" id="paymentAmountHint"><?= h(tr('يمكنك تعديل الدفعة الجزئية وسيتم إعادة احتساب المتبقي تلقائياً.', 'Adjust the paid amount and the remaining balance will be recalculated automatically.')) ?></div>
|
||||
<div class="row g-2 mt-2" aria-live="polite">
|
||||
<div class="col-6">
|
||||
<div class="border rounded-3 px-3 py-2 bg-light-subtle h-100">
|
||||
<div class="small text-muted mb-1"><?= h(tr('المدفوع بعد الحفظ', 'Paid after save')) ?></div>
|
||||
<div class="fw-semibold text-success" id="displayPaidAmountLive">0.000 <?= h(tr('ر.ع', 'OMR')) ?></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-6">
|
||||
<div class="border rounded-3 px-3 py-2 bg-light-subtle h-100">
|
||||
<div class="small text-muted mb-1"><?= h(tr('المتبقي بعد الحفظ', 'Remaining after save')) ?></div>
|
||||
<div class="fw-semibold text-primary" id="displayDueAmountLive">0.000 <?= h(tr('ر.ع', 'OMR')) ?></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-text" id="paymentAmountHint"><?= h(tr('يمكنك تعديل المبلغ المدفوع يدوياً وسيتم إعادة احتساب المتبقي تلقائياً.', 'You can edit the paid amount manually and the remaining balance will be recalculated automatically.')) ?></div>
|
||||
</div>
|
||||
<div class="mb-4">
|
||||
<label class="form-label"><?= h(tr('ملاحظات (اختياري)', 'Notes (Optional)')) ?></label>
|
||||
@ -802,15 +817,28 @@ function renderInvoice() {
|
||||
cartJson.value = JSON.stringify(cartData);
|
||||
}
|
||||
|
||||
function formatMoney(value) {
|
||||
return value.toFixed(3) + currencySuffix;
|
||||
}
|
||||
|
||||
function updatePaymentAmountHint() {
|
||||
const paymentAmountField = document.getElementById('payment_amount');
|
||||
const paymentAmountHint = document.getElementById('paymentAmountHint');
|
||||
const displayPaidAmountLive = document.getElementById('displayPaidAmountLive');
|
||||
const displayDueAmountLive = document.getElementById('displayDueAmountLive');
|
||||
if (!paymentAmountField || !paymentAmountHint) {
|
||||
return;
|
||||
}
|
||||
const entered = Math.max(0, parseFloat(paymentAmountField.value || '0') || 0);
|
||||
const due = Math.max(0, currentInvoiceTotal - Math.min(entered, currentInvoiceTotal));
|
||||
paymentAmountHint.innerText = partialPaymentText + ' ' + due.toFixed(3) + currencySuffix;
|
||||
const paid = Math.min(entered, currentInvoiceTotal);
|
||||
const due = Math.max(0, currentInvoiceTotal - paid);
|
||||
paymentAmountHint.innerText = partialPaymentText + ' ' + formatMoney(due);
|
||||
if (displayPaidAmountLive) {
|
||||
displayPaidAmountLive.innerText = formatMoney(paid);
|
||||
}
|
||||
if (displayDueAmountLive) {
|
||||
displayDueAmountLive.innerText = formatMoney(due);
|
||||
}
|
||||
}
|
||||
|
||||
function syncPaymentAmount(force = false) {
|
||||
|
||||
@ -481,7 +481,7 @@ require __DIR__ . '/includes/header.php';
|
||||
<td>
|
||||
<div class="d-flex justify-content-end gap-2">
|
||||
<?php if ((($order['payment_summary']['payment_status'] ?? ($order['payment_status'] ?? 'unpaid'))) !== 'paid'): ?>
|
||||
<form method="post" action="" class="d-inline" onsubmit="return confirm('<?= h(tr('هل أنت متأكد من تغيير حالة الدفع إلى مدفوع؟', 'Are you sure you want to mark as paid?')) ?>');">
|
||||
<form method="post" action="" class="d-inline js-confirm-mark-paid" data-confirm-message="<?= h(tr('هل أنت متأكد من تغيير حالة الدفع إلى مدفوع؟', 'Are you sure you want to mark as paid?')) ?>" data-confirm-title="<?= h(tr('تأكيد الإجراء', 'Confirm action')) ?>">
|
||||
<input type="hidden" name="action" value="mark_as_paid">
|
||||
<input type="hidden" name="id" value="<?= h($order['id']) ?>">
|
||||
<button type="submit" class="btn btn-sm btn-outline-success" title="<?= h(tr('تحويل لمدفوع', 'Mark as Paid')) ?>">
|
||||
@ -530,4 +530,40 @@ require __DIR__ . '/includes/header.php';
|
||||
<?php endif; ?>
|
||||
<?php endif; ?>
|
||||
</section>
|
||||
<script>
|
||||
document.addEventListener('DOMContentLoaded', function () {
|
||||
document.querySelectorAll('.js-confirm-mark-paid').forEach(function (form) {
|
||||
form.addEventListener('submit', function (event) {
|
||||
event.preventDefault();
|
||||
|
||||
var message = form.getAttribute('data-confirm-message') || '';
|
||||
var title = form.getAttribute('data-confirm-title') || '';
|
||||
|
||||
if (typeof Swal === 'undefined') {
|
||||
if (window.confirm(message)) {
|
||||
form.submit();
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
Swal.fire({
|
||||
icon: 'warning',
|
||||
title: title,
|
||||
text: message,
|
||||
position: 'center',
|
||||
showCancelButton: true,
|
||||
confirmButtonText: '<?= h(tr('نعم، تحويلها لمدفوع', 'Yes, mark it as paid')) ?>',
|
||||
cancelButtonText: '<?= h(tr('إلغاء', 'Cancel')) ?>',
|
||||
confirmButtonColor: '#198754',
|
||||
cancelButtonColor: '#6c757d',
|
||||
focusCancel: true
|
||||
}).then(function (result) {
|
||||
if (result.isConfirmed) {
|
||||
form.submit();
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
</script>
|
||||
<?php require __DIR__ . '/includes/footer.php'; ?>
|
||||
|
||||
@ -208,6 +208,20 @@ try {
|
||||
|
||||
@file_put_contents($flagFileV9, '1');
|
||||
}
|
||||
|
||||
$flagFileV10 = sys_get_temp_dir() . '/.schema_migrated_v10_' . md5(__DIR__);
|
||||
if (!file_exists($flagFileV10)) {
|
||||
$pdo = db();
|
||||
$hasItemsTable = (bool) $pdo->query("SHOW TABLES LIKE 'items'")->fetchColumn();
|
||||
if ($hasItemsTable) {
|
||||
$hasNotesColumn = (bool) $pdo->query("SHOW COLUMNS FROM items LIKE 'notes'")->fetchColumn();
|
||||
if (!$hasNotesColumn) {
|
||||
$pdo->exec("ALTER TABLE items ADD COLUMN notes text DEFAULT NULL AFTER image_url");
|
||||
}
|
||||
}
|
||||
|
||||
@file_put_contents($flagFileV10, '1');
|
||||
}
|
||||
} catch (\Throwable $e) {}
|
||||
|
||||
|
||||
@ -693,6 +707,7 @@ function catalog(): array
|
||||
"category_id" => $item["category_id"], "in_catalog" => (int)($item["in_catalog"] ?? 0),
|
||||
"supplier_id" => $item["supplier_id"],
|
||||
"image_url" => $item["image_url"],
|
||||
"notes" => $item["notes"] ?? null,
|
||||
"created_at" => $item["created_at"] ?? null,
|
||||
"unit_id" => $item["unit_id"],
|
||||
"unit_ar" => $item["u_name_ar"] ?? "قطعة",
|
||||
@ -2185,6 +2200,8 @@ function stock_snapshot(): array
|
||||
'supplier_id' => $item['supplier_id'],
|
||||
'unit_id' => $item['unit_id'],
|
||||
'image_url' => $item['image_url'],
|
||||
'in_catalog' => $item['in_catalog'] ?? 0,
|
||||
'notes' => $item['notes'] ?? null,
|
||||
'vat' => $item['vat'],
|
||||
];
|
||||
}
|
||||
|
||||
@ -480,7 +480,21 @@ require __DIR__ . '/header.php';
|
||||
<div class="mb-3">
|
||||
<label class="form-label" for="payment_amount"><?= h(tr('المبلغ المدفوع الآن', 'Paid Now')) ?></label>
|
||||
<input type="number" class="form-control custom-input" id="payment_amount" name="payment_amount" min="0" step="0.001" value="<?= h($paymentAmountInput) ?>" placeholder="0.000">
|
||||
<div class="form-text" id="paymentAmountHint"><?= h(tr('يمكنك إدخال دفعة جزئية وسيتم تتبع الباقي تلقائياً.', 'You can enter a partial payment and the remaining balance will be tracked automatically.')) ?></div>
|
||||
<div class="row g-2 mt-2" aria-live="polite">
|
||||
<div class="col-6">
|
||||
<div class="border rounded-3 px-3 py-2 bg-light-subtle h-100">
|
||||
<div class="small text-muted mb-1"><?= h(tr('المدفوع بعد الحفظ', 'Paid after save')) ?></div>
|
||||
<div class="fw-semibold text-success" id="displayPaidAmountLive">0.000 <?= h(tr('ر.ع', 'OMR')) ?></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-6">
|
||||
<div class="border rounded-3 px-3 py-2 bg-light-subtle h-100">
|
||||
<div class="small text-muted mb-1"><?= h(tr('المتبقي بعد الحفظ', 'Remaining after save')) ?></div>
|
||||
<div class="fw-semibold text-primary" id="displayDueAmountLive">0.000 <?= h(tr('ر.ع', 'OMR')) ?></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-text" id="paymentAmountHint"><?= h(tr('يمكنك تعديل المبلغ المدفوع يدوياً وسيتم تتبع الباقي تلقائياً.', 'You can edit the paid amount manually and the remaining balance will be tracked automatically.')) ?></div>
|
||||
</div>
|
||||
<div class="mb-4">
|
||||
<label class="form-label"><?= h(tr('ملاحظات (اختياري)', 'Notes (Optional)')) ?></label>
|
||||
@ -833,15 +847,28 @@ function renderInvoice() {
|
||||
cartJson.value = JSON.stringify(cartData);
|
||||
}
|
||||
|
||||
function formatMoney(value) {
|
||||
return value.toFixed(3) + currencySuffix;
|
||||
}
|
||||
|
||||
function updatePaymentAmountHint() {
|
||||
const paymentAmountField = document.getElementById('payment_amount');
|
||||
const paymentAmountHint = document.getElementById('paymentAmountHint');
|
||||
const displayPaidAmountLive = document.getElementById('displayPaidAmountLive');
|
||||
const displayDueAmountLive = document.getElementById('displayDueAmountLive');
|
||||
if (!paymentAmountField || !paymentAmountHint) {
|
||||
return;
|
||||
}
|
||||
const entered = Math.max(0, parseFloat(paymentAmountField.value || '0') || 0);
|
||||
const due = Math.max(0, currentInvoiceTotal - Math.min(entered, currentInvoiceTotal));
|
||||
paymentAmountHint.innerText = partialPaymentText + ' ' + due.toFixed(3) + currencySuffix;
|
||||
const paid = Math.min(entered, currentInvoiceTotal);
|
||||
const due = Math.max(0, currentInvoiceTotal - paid);
|
||||
paymentAmountHint.innerText = partialPaymentText + ' ' + formatMoney(due);
|
||||
if (displayPaidAmountLive) {
|
||||
displayPaidAmountLive.innerText = formatMoney(paid);
|
||||
}
|
||||
if (displayDueAmountLive) {
|
||||
displayDueAmountLive.innerText = formatMoney(due);
|
||||
}
|
||||
}
|
||||
|
||||
function syncPaymentAmount(force = false) {
|
||||
|
||||
439
stock.php
439
stock.php
@ -8,13 +8,13 @@ $dbError = null;
|
||||
// Handle Export CSV
|
||||
if ($_SERVER['REQUEST_METHOD'] === 'GET' && isset($_GET['action']) && $_GET['action'] === 'export_csv') {
|
||||
$pdo = db();
|
||||
$stmt = $pdo->query("SELECT sku, name, price, cost_price, base_stock, vat, category_id, supplier_id, unit_id FROM items ORDER BY id DESC");
|
||||
$stmt = $pdo->query("SELECT sku, name, price, cost_price, base_stock, vat, category_id, supplier_id, unit_id, notes FROM items ORDER BY id DESC");
|
||||
$items = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
||||
header('Content-Type: text/csv; charset=utf-8');
|
||||
header('Content-Disposition: attachment; filename=stock_export_' . date('Ymd_His') . '.csv');
|
||||
echo "\xEF\xBB\xBF";
|
||||
$output = fopen('php://output', 'w');
|
||||
fputcsv($output, ['SKU', 'Name', 'Price', 'Cost Price', 'Stock', 'VAT', 'Category ID', 'Supplier ID', 'Unit ID']);
|
||||
fputcsv($output, ['SKU', 'Name', 'Price', 'Cost Price', 'Stock', 'VAT', 'Category ID', 'Supplier ID', 'Unit ID', 'Notes']);
|
||||
foreach ($items as $row) { fputcsv($output, $row); }
|
||||
fclose($output);
|
||||
exit;
|
||||
@ -74,8 +74,8 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['action']) && $_POST['
|
||||
$imported = 0; $updated = 0;
|
||||
$pdo->beginTransaction();
|
||||
try {
|
||||
$stmtInsert = $pdo->prepare("INSERT INTO items (sku, name, price, cost_price, base_stock, vat, category_id, supplier_id, unit_id) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)");
|
||||
$stmtUpdate = $pdo->prepare("UPDATE items SET name=?, price=?, cost_price=?, base_stock=?, vat=?, category_id=?, supplier_id=?, unit_id=? WHERE sku=?");
|
||||
$stmtInsert = $pdo->prepare("INSERT INTO items (sku, name, price, cost_price, base_stock, vat, category_id, supplier_id, unit_id, notes) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)");
|
||||
$stmtUpdate = $pdo->prepare("UPDATE items SET name=?, price=?, cost_price=?, base_stock=?, vat=?, category_id=?, supplier_id=?, unit_id=?, notes=? WHERE sku=?");
|
||||
$stmtCheck = $pdo->prepare("SELECT id FROM items WHERE sku=?");
|
||||
|
||||
$valid_categories = $pdo->query("SELECT id FROM categories")->fetchAll(PDO::FETCH_COLUMN);
|
||||
@ -93,12 +93,14 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['action']) && $_POST['
|
||||
$category_id = (!empty($row[6]) && in_array((int)$row[6], $valid_categories)) ? (int)$row[6] : null;
|
||||
$supplier_id = (!empty($row[7]) && in_array((int)$row[7], $valid_suppliers)) ? (int)$row[7] : null;
|
||||
$unit_id = (!empty($row[8]) && in_array((int)$row[8], $valid_units)) ? (int)$row[8] : null;
|
||||
$notes = isset($row[9]) ? trim((string)$row[9]) : null;
|
||||
$notes = $notes === '' ? null : $notes;
|
||||
$stmtCheck->execute([$sku]);
|
||||
if ($stmtCheck->fetchColumn()) {
|
||||
$stmtUpdate->execute([$name, $price, $cost_price, $base_stock, $vat, $category_id, $supplier_id, $unit_id, $sku]);
|
||||
$stmtUpdate->execute([$name, $price, $cost_price, $base_stock, $vat, $category_id, $supplier_id, $unit_id, $notes, $sku]);
|
||||
$updated++;
|
||||
} else {
|
||||
$stmtInsert->execute([$sku, $name, $price, $cost_price, $base_stock, $vat, $category_id, $supplier_id, $unit_id]);
|
||||
$stmtInsert->execute([$sku, $name, $price, $cost_price, $base_stock, $vat, $category_id, $supplier_id, $unit_id, $notes]);
|
||||
$imported++;
|
||||
}
|
||||
}
|
||||
@ -132,6 +134,8 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['action'])) {
|
||||
$category_id = !empty($_POST['category_id']) ? (int)$_POST['category_id'] : null;
|
||||
$supplier_id = !empty($_POST['supplier_id']) ? (int)$_POST['supplier_id'] : null;
|
||||
$unit_id = !empty($_POST['unit_id']) ? (int)$_POST['unit_id'] : null;
|
||||
$notes = trim((string)($_POST['notes'] ?? ''));
|
||||
$notes = $notes !== '' ? $notes : null;
|
||||
|
||||
if (!$sku || !$name) {
|
||||
echo json_encode(['success' => false, 'error' => 'Missing SKU or Name']);
|
||||
@ -162,8 +166,8 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['action'])) {
|
||||
exit;
|
||||
}
|
||||
|
||||
$sql = "UPDATE items SET sku=?, name=?, price=?, cost_price=?, base_stock=?, vat=?, category_id=?, supplier_id=?, unit_id=?, in_catalog=? " . ($image_url ? ", image_url=?" : "") . " WHERE sku=?";
|
||||
$params = [$sku, $name, $price, $cost_price, $base_stock, $vat, $category_id, $supplier_id, $unit_id, $in_catalog];
|
||||
$sql = "UPDATE items SET sku=?, name=?, price=?, cost_price=?, base_stock=?, vat=?, category_id=?, supplier_id=?, unit_id=?, notes=?, in_catalog=? " . ($image_url ? ", image_url=?" : "") . " WHERE sku=?";
|
||||
$params = [$sku, $name, $price, $cost_price, $base_stock, $vat, $category_id, $supplier_id, $unit_id, $notes, $in_catalog];
|
||||
if ($image_url) {
|
||||
$params[] = $image_url;
|
||||
}
|
||||
@ -175,8 +179,8 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['action'])) {
|
||||
echo json_encode(['success' => false, 'error' => 'SKU already exists']);
|
||||
exit;
|
||||
}
|
||||
$stmt = $pdo->prepare("INSERT INTO items (sku, name, price, cost_price, base_stock, vat, category_id, supplier_id, unit_id, in_catalog, image_url) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)");
|
||||
$stmt->execute([$sku, $name, $price, $cost_price, $base_stock, $vat, $category_id, $supplier_id, $unit_id, $in_catalog, $image_url]);
|
||||
$stmt = $pdo->prepare("INSERT INTO items (sku, name, price, cost_price, base_stock, vat, category_id, supplier_id, unit_id, notes, in_catalog, image_url) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)");
|
||||
$stmt->execute([$sku, $name, $price, $cost_price, $base_stock, $vat, $category_id, $supplier_id, $unit_id, $notes, $in_catalog, $image_url]);
|
||||
}
|
||||
|
||||
echo json_encode(['success' => true]);
|
||||
@ -299,6 +303,172 @@ function sortable_header($field, $label, $currentSortField, $currentSortOrder, $
|
||||
require __DIR__ . '/includes/header.php';
|
||||
?>
|
||||
|
||||
<style>
|
||||
#itemModal .modal-dialog {
|
||||
max-width: min(1140px, calc(100vw - 2rem));
|
||||
}
|
||||
|
||||
#itemModal .modal-content {
|
||||
border: 0;
|
||||
border-radius: 24px;
|
||||
overflow: hidden;
|
||||
box-shadow: 0 24px 80px rgba(15, 23, 42, 0.18);
|
||||
}
|
||||
|
||||
#itemModal .modal-header {
|
||||
padding: 1rem 1.25rem;
|
||||
background: linear-gradient(135deg, #0d6efd, #2f80ff) !important;
|
||||
}
|
||||
|
||||
#itemModal .modal-body {
|
||||
padding: 1.25rem;
|
||||
background: linear-gradient(180deg, #f8fbff 0%, #f5f7fb 100%);
|
||||
}
|
||||
|
||||
#itemModal .modal-footer {
|
||||
padding: 1rem 1.25rem;
|
||||
background: #fff;
|
||||
border-top: 1px solid rgba(13, 110, 253, 0.08);
|
||||
}
|
||||
|
||||
.item-form-panel {
|
||||
height: 100%;
|
||||
padding: 1rem 1rem 0.75rem;
|
||||
border: 1px solid rgba(13, 110, 253, 0.1);
|
||||
border-radius: 20px;
|
||||
background: rgba(255, 255, 255, 0.96);
|
||||
box-shadow: 0 10px 30px rgba(15, 23, 42, 0.06);
|
||||
}
|
||||
|
||||
.item-form-panel__eyebrow {
|
||||
display: inline-block;
|
||||
margin-bottom: 0.35rem;
|
||||
color: #6c757d;
|
||||
font-size: 0.72rem;
|
||||
letter-spacing: 0.08em;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
|
||||
.item-form-panel__title {
|
||||
margin-bottom: 1rem;
|
||||
color: #1f2937;
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.item-form-panel .form-label {
|
||||
margin-bottom: 0.4rem;
|
||||
color: #475467;
|
||||
font-size: 0.86rem;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.item-form-panel .form-control,
|
||||
.item-form-panel .form-select {
|
||||
min-height: 46px;
|
||||
border-color: #dbe4f0;
|
||||
border-radius: 14px;
|
||||
box-shadow: none;
|
||||
}
|
||||
|
||||
.item-form-panel textarea.form-control {
|
||||
min-height: 148px;
|
||||
resize: vertical;
|
||||
}
|
||||
|
||||
.item-form-panel .form-control:focus,
|
||||
.item-form-panel .form-select:focus {
|
||||
border-color: rgba(13, 110, 253, 0.45);
|
||||
box-shadow: 0 0 0 0.2rem rgba(13, 110, 253, 0.12);
|
||||
}
|
||||
|
||||
.item-form-panel .input-group > .form-control {
|
||||
border-start-end-radius: 0;
|
||||
border-end-end-radius: 0;
|
||||
}
|
||||
|
||||
.item-form-panel .input-group > .btn {
|
||||
border-start-start-radius: 0;
|
||||
border-end-start-radius: 0;
|
||||
border-color: #dbe4f0;
|
||||
}
|
||||
|
||||
.item-form-note {
|
||||
display: block;
|
||||
margin-top: 0.45rem;
|
||||
color: #6c757d;
|
||||
font-size: 0.78rem;
|
||||
}
|
||||
|
||||
.item-switch-wrap {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: 0.75rem;
|
||||
padding: 0.9rem 1rem;
|
||||
border: 1px solid rgba(13, 110, 253, 0.12);
|
||||
border-radius: 16px;
|
||||
background: linear-gradient(180deg, #fbfdff 0%, #f3f8ff 100%);
|
||||
}
|
||||
|
||||
.item-switch-wrap .form-check {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.item-switch-wrap .form-check-input {
|
||||
float: none;
|
||||
margin: 0;
|
||||
transform: scale(1.15);
|
||||
}
|
||||
|
||||
.item-image-dropzone {
|
||||
padding: 1rem;
|
||||
border: 1px dashed rgba(13, 110, 253, 0.35);
|
||||
border-radius: 18px;
|
||||
background: linear-gradient(180deg, #fbfdff 0%, #f0f7ff 100%);
|
||||
}
|
||||
|
||||
.item-image-preview {
|
||||
min-height: 190px;
|
||||
margin-top: 0.85rem;
|
||||
padding: 1rem;
|
||||
border: 1px solid rgba(13, 110, 253, 0.12);
|
||||
border-radius: 16px;
|
||||
background: #fff;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.item-image-preview img {
|
||||
max-width: 100%;
|
||||
max-height: 160px;
|
||||
object-fit: contain;
|
||||
border-radius: 14px;
|
||||
}
|
||||
|
||||
.item-image-placeholder {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
gap: 0.5rem;
|
||||
text-align: center;
|
||||
color: #98a2b3;
|
||||
font-size: 0.92rem;
|
||||
}
|
||||
|
||||
@media (max-width: 991.98px) {
|
||||
#itemModal .modal-dialog {
|
||||
max-width: calc(100vw - 1rem);
|
||||
}
|
||||
|
||||
#itemModal .modal-body,
|
||||
#itemModal .modal-footer {
|
||||
padding: 1rem;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
<section class="mb-4">
|
||||
<div class="row g-4 align-items-center mb-3">
|
||||
<div class="col-lg-5">
|
||||
@ -423,7 +593,7 @@ require __DIR__ . '/includes/header.php';
|
||||
<?php endif; ?>
|
||||
</td>
|
||||
<td class="text-end pe-3">
|
||||
<button class="btn btn-sm btn-outline-primary rounded-circle shadow-sm" style="width: 34px; height: 34px; padding: 0;" onclick="openItemModal('<?= h($row['sku']) ?>', '<?= h(addslashes($row['name'])) ?>', '<?= h($row['price']) ?>', '<?= h($row['cost_price'] ?? 0) ?>', '<?= h($row['base_stock']) ?>', '<?= h($row['vat'] ?? get_setting('vat_percentage', 5)) ?>', '<?= h($row['category_id'] ?? '') ?>', '<?= h($row['supplier_id'] ?? '') ?>', '<?= h($row['unit_id'] ?? '') ?>', '<?= h($row['image_url'] ?? '') ?>', '<?= h($row['in_catalog'] ?? 0) ?>')" data-bs-toggle="tooltip" title="<?= h(tr('تعديل', 'Edit')) ?>">
|
||||
<button class="btn btn-sm btn-outline-primary rounded-circle shadow-sm" style="width: 34px; height: 34px; padding: 0;" onclick="openItemModal('<?= h($row['sku']) ?>', '<?= h(addslashes($row['name'])) ?>', '<?= h($row['price']) ?>', '<?= h($row['cost_price'] ?? 0) ?>', '<?= h($row['base_stock']) ?>', '<?= h($row['vat'] ?? get_setting('vat_percentage', 5)) ?>', '<?= h($row['category_id'] ?? '') ?>', '<?= h($row['supplier_id'] ?? '') ?>', '<?= h($row['unit_id'] ?? '') ?>', '<?= h($row['image_url'] ?? '') ?>', '<?= h($row['in_catalog'] ?? 0) ?>', <?= h(json_encode((string) ($row['notes'] ?? ''), JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES)) ?>)" data-bs-toggle="tooltip" title="<?= h(tr('تعديل', 'Edit')) ?>">
|
||||
<i class="bi bi-pencil"></i>
|
||||
</button>
|
||||
<button type="button" onclick="printSingleLabel('<?= h(addslashes($row['sku'])) ?>')" class="btn btn-sm btn-outline-secondary rounded-circle shadow-sm ms-1" style="width: 34px; height: 34px; padding: 0;" data-bs-toggle="tooltip" title="<?= h(tr('طباعة ملصق', 'Print Label')) ?>">
|
||||
@ -490,94 +660,136 @@ require __DIR__ . '/includes/header.php';
|
||||
|
||||
<!-- Item Modal -->
|
||||
<div class="modal fade" id="itemModal" tabindex="-1" aria-labelledby="itemModalLabel" aria-hidden="true">
|
||||
<div class="modal-dialog">
|
||||
<div class="modal-dialog modal-xl modal-dialog-centered modal-dialog-scrollable">
|
||||
<div class="modal-content">
|
||||
<form onsubmit="handleItemSubmit(event)" id="itemForm">
|
||||
<div class="modal-header bg-primary text-white ">
|
||||
<h5 class="modal-title" id="itemModalLabel"><?= h(tr('إضافة / تعديل صنف', 'Add / Edit Item')) ?></h5>
|
||||
<button type="button" class="btn-close btn-close-white " data-bs-dismiss="modal" aria-label="Close" ></button>
|
||||
<div class="modal-header bg-primary text-white">
|
||||
<div>
|
||||
<h5 class="modal-title mb-1" id="itemModalLabel"><?= h(tr('إضافة / تعديل صنف', 'Add / Edit Item')) ?></h5>
|
||||
<div class="small text-white-50"><?= h(tr('نموذج أسرع ومنظم بثلاثة أعمدة لإدخال بيانات الصنف.', 'A faster 3-column layout for product details.')) ?></div>
|
||||
</div>
|
||||
<button type="button" class="btn-close btn-close-white" data-bs-dismiss="modal" aria-label="Close"></button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<input type="hidden" id="item_original_sku">
|
||||
<input type="hidden" id="item_existing_image_url">
|
||||
|
||||
<div class="row">
|
||||
<div class="col-12 mb-3 text-center">
|
||||
<label class="form-label d-block text-start"><?= h(tr('صورة الصنف', 'Item Picture')) ?></label>
|
||||
<input type="file" class="form-control" id="item_picture" accept="image/*">
|
||||
</div>
|
||||
|
||||
<div class="col-md-6 mb-3">
|
||||
<label class="form-label"><?= h(tr('رمز الصنف (SKU)', 'SKU')) ?></label>
|
||||
<div class="input-group">
|
||||
<input type="text" class="form-control" id="item_sku" required maxlength="15">
|
||||
<button type="button" class="btn btn-outline-secondary" onclick="suggestSKU()" title="<?= h(tr('اقتراح رمز', 'Suggest SKU')) ?>">
|
||||
<i class="bi bi-arrow-clockwise"></i>
|
||||
</button>
|
||||
|
||||
<div class="row g-3">
|
||||
<div class="col-12 col-lg-4">
|
||||
<div class="item-form-panel">
|
||||
<span class="item-form-panel__eyebrow"><?= h(tr('الأساسيات', 'Basics')) ?></span>
|
||||
<h6 class="item-form-panel__title"><?= h(tr('بيانات الصنف', 'Item Identity')) ?></h6>
|
||||
|
||||
<div class="mb-3">
|
||||
<label class="form-label"><?= h(tr('رمز الصنف (SKU)', 'SKU')) ?></label>
|
||||
<div class="input-group">
|
||||
<input type="text" class="form-control" id="item_sku" required maxlength="15">
|
||||
<button type="button" class="btn btn-outline-secondary" onclick="suggestSKU()" title="<?= h(tr('اقتراح رمز', 'Suggest SKU')) ?>">
|
||||
<i class="bi bi-arrow-clockwise"></i>
|
||||
</button>
|
||||
</div>
|
||||
<small class="item-form-note"><?= h(tr('يمكنك توليد رمز سريع تلقائياً.', 'Generate a quick SKU automatically.')) ?></small>
|
||||
</div>
|
||||
|
||||
<div class="mb-3">
|
||||
<label class="form-label"><?= h(tr('اسم الصنف', 'Product Name')) ?></label>
|
||||
<input type="text" class="form-control" id="item_name" required>
|
||||
</div>
|
||||
|
||||
<div class="mb-3">
|
||||
<label class="form-label"><?= h(tr('الوحدة', 'Unit')) ?></label>
|
||||
<select class="form-select" id="item_unit">
|
||||
<option value=""><?= h(tr('-- اختر الوحدة --', '-- Select Unit --')) ?></option>
|
||||
<?php foreach($units as $unit): ?>
|
||||
<option value="<?= h($unit['id']) ?>"><?= h(current_lang() === 'ar' ? $unit['name_ar'] : $unit['name_en']) ?></option>
|
||||
<?php endforeach; ?>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div class="mb-0">
|
||||
<label class="form-label"><?= h(tr('التصنيف', 'Category')) ?></label>
|
||||
<select class="form-select" id="item_category">
|
||||
<option value=""><?= h(tr('-- اختر التصنيف --', '-- Select Category --')) ?></option>
|
||||
<?php foreach($categories as $cat): ?>
|
||||
<option value="<?= h($cat['id']) ?>"><?= h(current_lang() === 'ar' ? $cat['name_ar'] : $cat['name_en']) ?></option>
|
||||
<?php endforeach; ?>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-md-6 mb-3">
|
||||
<label class="form-label"><?= h(tr('اسم الصنف', 'Product Name')) ?></label>
|
||||
<input type="text" class="form-control" id="item_name" required>
|
||||
</div>
|
||||
|
||||
<div class="col-md-6 mb-3">
|
||||
<label class="form-label"><?= h(tr('السعر', 'Price')) ?></label>
|
||||
<input type="number" step="0.001" class="form-control" id="item_price" required>
|
||||
</div>
|
||||
|
||||
|
||||
<div class="col-md-6 mb-3">
|
||||
<label class="form-label"><?= h(tr('الظهور في المتجر (الكتالوج)', 'Visible in Catalog')) ?></label>
|
||||
<div class="form-check form-switch mt-2">
|
||||
<input class="form-check-input" type="checkbox" id="item_in_catalog" style="transform: scale(1.3); margin-left: -1.5em; margin-right: 0.5em;">
|
||||
<label class="form-check-label" for="item_in_catalog" style="margin-right: 2.5em;"><?= h(tr('عرض أونلاين', 'Show Online')) ?></label>
|
||||
<div class="col-12 col-lg-4">
|
||||
<div class="item-form-panel">
|
||||
<span class="item-form-panel__eyebrow"><?= h(tr('البيع والمخزون', 'Pricing & Stock')) ?></span>
|
||||
<h6 class="item-form-panel__title"><?= h(tr('التسعير والمخزون', 'Pricing & Inventory')) ?></h6>
|
||||
|
||||
<div class="mb-3">
|
||||
<label class="form-label"><?= h(tr('السعر', 'Price')) ?></label>
|
||||
<input type="number" step="0.001" class="form-control" id="item_price" required>
|
||||
</div>
|
||||
|
||||
<div class="mb-3">
|
||||
<label class="form-label"><?= h(tr('التكلفة', 'Cost Price')) ?></label>
|
||||
<input type="number" step="0.001" class="form-control" id="item_cost_price" required>
|
||||
</div>
|
||||
|
||||
<div class="mb-3">
|
||||
<label class="form-label"><?= h(tr('الرصيد الافتتاحي', 'Opening Stock')) ?></label>
|
||||
<input type="number" class="form-control" id="item_base_stock" required>
|
||||
</div>
|
||||
|
||||
<div class="mb-3">
|
||||
<label class="form-label"><?= h(tr('الضريبة (VAT %)', 'VAT %')) ?></label>
|
||||
<input type="number" step="0.001" class="form-control" id="item_vat" value="<?= h(get_setting('vat_percentage', 5)) ?>" required>
|
||||
</div>
|
||||
|
||||
<div class="mb-3">
|
||||
<label class="form-label"><?= h(tr('المورد', 'Supplier')) ?></label>
|
||||
<select class="form-select" id="item_supplier">
|
||||
<option value=""><?= h(tr('-- اختر المورد --', '-- Select Supplier --')) ?></option>
|
||||
<?php foreach($suppliers as $sup): ?>
|
||||
<option value="<?= h($sup['id']) ?>"><?= h($sup['name']) ?></option>
|
||||
<?php endforeach; ?>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div class="mb-0">
|
||||
<label class="form-label"><?= h(tr('الظهور في المتجر (الكتالوج)', 'Visible in Catalog')) ?></label>
|
||||
<div class="item-switch-wrap">
|
||||
<div>
|
||||
<div class="fw-semibold text-dark"><?= h(tr('عرض أونلاين', 'Show Online')) ?></div>
|
||||
<small class="text-muted"><?= h(tr('إظهار الصنف في الكتالوج والمتجر.', 'Display this item in the catalog/store.')) ?></small>
|
||||
</div>
|
||||
<div class="form-check form-switch">
|
||||
<input class="form-check-input" type="checkbox" id="item_in_catalog">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-6 mb-3">
|
||||
<label class="form-label"><?= h(tr('التكلفة', 'Cost Price')) ?></label>
|
||||
<input type="number" step="0.001" class="form-control" id="item_cost_price" required>
|
||||
</div>
|
||||
|
||||
<div class="col-md-6 mb-3">
|
||||
<label class="form-label"><?= h(tr('الرصيد الافتتاحي', 'Opening Stock')) ?></label>
|
||||
<input type="number" class="form-control" id="item_base_stock" required>
|
||||
</div>
|
||||
|
||||
<div class="col-md-6 mb-3">
|
||||
<label class="form-label"><?= h(tr('الضريبة (VAT %)', 'VAT %')) ?></label>
|
||||
<input type="number" step="0.001" class="form-control" id="item_vat" value="<?= h(get_setting('vat_percentage', 5)) ?>" required>
|
||||
</div>
|
||||
|
||||
<div class="col-md-6 mb-3">
|
||||
<label class="form-label"><?= h(tr('التصنيف', 'Category')) ?></label>
|
||||
<select class="form-select" id="item_category">
|
||||
<option value=""><?= h(tr('-- اختر التصنيف --', '-- Select Category --')) ?></option>
|
||||
<?php foreach($categories as $cat): ?>
|
||||
<option value="<?= h($cat['id']) ?>"><?= h(current_lang() === 'ar' ? $cat['name_ar'] : $cat['name_en']) ?></option>
|
||||
<?php endforeach; ?>
|
||||
</select>
|
||||
</div>
|
||||
<div class="col-12 col-lg-4">
|
||||
<div class="item-form-panel">
|
||||
<span class="item-form-panel__eyebrow"><?= h(tr('الصورة والملاحظات', 'Media & Notes')) ?></span>
|
||||
<h6 class="item-form-panel__title"><?= h(tr('الصورة والملاحظات الداخلية', 'Image & Internal Notes')) ?></h6>
|
||||
|
||||
<div class="col-md-6 mb-3">
|
||||
<label class="form-label"><?= h(tr('المورد', 'Supplier')) ?></label>
|
||||
<select class="form-select" id="item_supplier">
|
||||
<option value=""><?= h(tr('-- اختر المورد --', '-- Select Supplier --')) ?></option>
|
||||
<?php foreach($suppliers as $sup): ?>
|
||||
<option value="<?= h($sup['id']) ?>"><?= h($sup['name']) ?></option>
|
||||
<?php endforeach; ?>
|
||||
</select>
|
||||
</div>
|
||||
<div class="item-image-dropzone mb-3">
|
||||
<label class="form-label d-block"><?= h(tr('صورة الصنف', 'Item Picture')) ?></label>
|
||||
<input type="file" class="form-control" id="item_picture" accept="image/*">
|
||||
<div class="item-image-preview" id="itemPicturePreview">
|
||||
<div class="item-image-placeholder">
|
||||
<i class="bi bi-card-image fs-2"></i>
|
||||
<span><?= h(tr('لا توجد صورة محددة بعد', 'No image selected yet')) ?></span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-md-12 mb-3">
|
||||
<label class="form-label"><?= h(tr('الوحدة', 'Unit')) ?></label>
|
||||
<select class="form-select" id="item_unit">
|
||||
<option value=""><?= h(tr('-- اختر الوحدة --', '-- Select Unit --')) ?></option>
|
||||
<?php foreach($units as $unit): ?>
|
||||
<option value="<?= h($unit['id']) ?>"><?= h(current_lang() === 'ar' ? $unit['name_ar'] : $unit['name_en']) ?></option>
|
||||
<?php endforeach; ?>
|
||||
</select>
|
||||
<div class="mb-0">
|
||||
<label class="form-label"><?= h(tr('ملاحظات المنتج', 'Product Notes')) ?></label>
|
||||
<textarea class="form-control" id="item_notes" rows="6" maxlength="1000" placeholder="<?= h(tr('ملاحظات داخلية عن المنتج...', 'Internal notes about this product...')) ?>"></textarea>
|
||||
<small class="item-form-note"><?= h(tr('هذه الملاحظات داخلية وتساعد الفريق عند التعديل أو الجرد.', 'These notes are internal and help during edits or stock counting.')) ?></small>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@ -613,6 +825,23 @@ document.addEventListener('DOMContentLoaded', function () {
|
||||
|
||||
itemModalObj = new bootstrap.Modal(document.getElementById('itemModal'));
|
||||
|
||||
const pictureInput = document.getElementById('item_picture');
|
||||
if (pictureInput) {
|
||||
pictureInput.addEventListener('change', function () {
|
||||
const file = this.files && this.files[0] ? this.files[0] : null;
|
||||
if (!file) {
|
||||
renderItemImagePreview(document.getElementById('item_existing_image_url').value || '');
|
||||
return;
|
||||
}
|
||||
|
||||
const reader = new FileReader();
|
||||
reader.onload = function (event) {
|
||||
renderItemImagePreview((event.target && event.target.result) ? event.target.result : '');
|
||||
};
|
||||
reader.readAsDataURL(file);
|
||||
});
|
||||
}
|
||||
|
||||
const searchInput = document.getElementById('searchInput');
|
||||
const clearSearchBtn = document.getElementById('clearSearchBtn');
|
||||
let searchTimeout;
|
||||
@ -655,7 +884,27 @@ function openImportModal() {
|
||||
m.show();
|
||||
}
|
||||
|
||||
function openItemModal(sku = '', name = '', price = '', cost_price = '', base_stock = '', vat = '<?= h(get_setting('vat_percentage', 5)) ?>', category_id = '', supplier_id = '', unit_id = '', image_url = '', in_catalog = 0) {
|
||||
function renderItemImagePreview(imageUrl = '') {
|
||||
const preview = document.getElementById('itemPicturePreview');
|
||||
if (!preview) return;
|
||||
|
||||
preview.innerHTML = '';
|
||||
|
||||
if (imageUrl) {
|
||||
const img = document.createElement('img');
|
||||
img.src = imageUrl;
|
||||
img.alt = '<?= h(tr('معاينة صورة الصنف', 'Item image preview')) ?>';
|
||||
preview.appendChild(img);
|
||||
return;
|
||||
}
|
||||
|
||||
const placeholder = document.createElement('div');
|
||||
placeholder.className = 'item-image-placeholder';
|
||||
placeholder.innerHTML = '<i class="bi bi-card-image fs-2"></i><span><?= h(tr('لا توجد صورة محددة بعد', 'No image selected yet')) ?></span>';
|
||||
preview.appendChild(placeholder);
|
||||
}
|
||||
|
||||
function openItemModal(sku = '', name = '', price = '', cost_price = '', base_stock = '', vat = '<?= h(get_setting('vat_percentage', 5)) ?>', category_id = '', supplier_id = '', unit_id = '', image_url = '', in_catalog = 0, notes = '') {
|
||||
document.getElementById('item_original_sku').value = sku;
|
||||
document.getElementById('item_existing_image_url').value = image_url;
|
||||
document.getElementById('item_sku').value = sku;
|
||||
@ -667,22 +916,11 @@ function openItemModal(sku = '', name = '', price = '', cost_price = '', base_st
|
||||
document.getElementById('item_category').value = category_id;
|
||||
document.getElementById('item_supplier').value = supplier_id;
|
||||
document.getElementById('item_unit').value = unit_id;
|
||||
document.getElementById('item_notes').value = notes || '';
|
||||
document.getElementById('item_in_catalog').checked = (parseInt(in_catalog) === 1);
|
||||
|
||||
// Remove old image preview if any
|
||||
const oldPreview = document.getElementById('image_preview');
|
||||
if (oldPreview) oldPreview.remove();
|
||||
|
||||
if (image_url) {
|
||||
const preview = document.createElement('img');
|
||||
preview.id = 'image_preview';
|
||||
preview.src = image_url;
|
||||
preview.style.maxHeight = '100px';
|
||||
preview.className = 'mt-2 rounded shadow-sm border';
|
||||
document.getElementById('item_picture').parentElement.appendChild(preview);
|
||||
}
|
||||
document.getElementById('item_picture').value = '';
|
||||
|
||||
|
||||
renderItemImagePreview(image_url || '');
|
||||
itemModalObj.show();
|
||||
}
|
||||
|
||||
@ -705,6 +943,7 @@ async function handleItemSubmit(e) {
|
||||
formData.append('category_id', document.getElementById('item_category').value);
|
||||
formData.append('supplier_id', document.getElementById('item_supplier').value);
|
||||
formData.append('unit_id', document.getElementById('item_unit').value);
|
||||
formData.append('notes', document.getElementById('item_notes').value);
|
||||
formData.append('in_catalog', document.getElementById('item_in_catalog').checked ? '1' : '0');
|
||||
formData.append('existing_image_url', document.getElementById('item_existing_image_url').value);
|
||||
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user