Autosave: 20260419-143136

This commit is contained in:
Flatlogic Bot 2026-04-19 14:31:28 +00:00
parent c4d7baf9d6
commit cc0da06fbb
16 changed files with 1613 additions and 48 deletions

View File

@ -59,7 +59,7 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') {
$lineTotal = $price * $qty;
$vatPercent = (float) ($product['vat'] ?? 0);
$itemVat = $lineTotal - ($lineTotal / (1 + ($vatPercent / 100)));
$itemVat = $lineTotal * ($vatPercent / 100);
$totalVat += $itemVat;
$normalized[] = [
@ -389,7 +389,7 @@ require __DIR__ . '/includes/header.php';
<span id="displaySubtotal" class="fw-medium">0.000</span>
</div>
<div class="totals-row">
<span><?= h(tr('الضريبة (مشمولة)', 'VAT (Included)')) ?></span>
<span><?= h(tr('الضريبة (مضافة)', 'VAT (Added)')) ?></span>
<span id="displayVat" class="text-muted">0.000</span>
</div>
<div class="totals-row grand-total">
@ -657,7 +657,7 @@ function renderInvoice() {
const lineTotal = item.qty * item.price;
const vatPercent = parseFloat(catalogData[sku].vat) || 0;
const itemVat = lineTotal - (lineTotal / (1 + (vatPercent / 100)));
const itemVat = lineTotal * (vatPercent / 100);
totalVat += itemVat;
totalAmount += lineTotal;
@ -688,10 +688,11 @@ function renderInvoice() {
}
function updateTotals(total, vat) {
const subtotal = total - vat;
const subtotal = total;
const finalTotal = subtotal + vat;
document.getElementById('displaySubtotal').innerText = subtotal.toFixed(3);
document.getElementById('displayVat').innerText = vat.toFixed(3);
document.getElementById('displayTotal').innerText = total.toFixed(3) + currencySuffix;
document.getElementById('displayTotal').innerText = finalTotal.toFixed(3) + currencySuffix;
}
renderInvoice();

1231
includes/SimpleXLSX.php Normal file

File diff suppressed because it is too large Load Diff

View File

@ -276,9 +276,9 @@ function create_sale(array $data): int
ensure_sales_table();
$stmt = db()->prepare('INSERT INTO sales_orders
(receipt_no, sale_mode, branch_code, cashier_username, cashier_name, role_name, customer_name, payment_method, items_json, item_count, subtotal, total_amount, status, notes, sale_date)
(receipt_no, sale_mode, branch_code, cashier_username, cashier_name, role_name, customer_name, payment_method, items_json, item_count, subtotal, vat_amount, total_amount, status, notes, sale_date)
VALUES
(:receipt_no, :sale_mode, :branch_code, :cashier_username, :cashier_name, :role_name, :customer_name, :payment_method, :items_json, :item_count, :subtotal, :total_amount, :status, :notes, NOW())');
(:receipt_no, :sale_mode, :branch_code, :cashier_username, :cashier_name, :role_name, :customer_name, :payment_method, :items_json, :item_count, :subtotal, :vat_amount, :total_amount, :status, :notes, NOW())');
$stmt->bindValue(':receipt_no', $data['receipt_no']);
$stmt->bindValue(':sale_mode', $data['sale_mode']);
@ -291,9 +291,9 @@ function create_sale(array $data): int
$stmt->bindValue(':items_json', json_encode($data['items'], JSON_UNESCAPED_UNICODE));
$stmt->bindValue(':item_count', $data['item_count'], PDO::PARAM_INT);
$stmt->bindValue(':subtotal', $data['subtotal']);
$stmt->bindValue(':vat_amount', $data['vat_amount'] ?? 0.0);
$stmt->bindValue(':total_amount', $data['total_amount']);
$stmt->bindValue(':status', $data['status'] ?? 'completed');
$stmt->bindValue(':status', $data['status'] ?? 'completed');
$stmt->bindValue(':notes', $data['notes']);
$stmt->execute();
@ -415,6 +415,7 @@ function report_metrics(): array
$paymentTotals = [];
$productTotals = [];
$gross = 0.0;
$totalVat = 0.0;
foreach ($sales as $sale) {
$branch = $sale['branch_code'];
@ -422,6 +423,7 @@ function report_metrics(): array
$payment = $sale['payment_method'];
$paymentTotals[$payment] = ($paymentTotals[$payment] ?? 0.0) + (float) $sale['total_amount'];
$gross += (float) $sale['total_amount'];
$totalVat += (float) $sale['vat_amount'];
foreach ($sale['items'] as $item) {
$sku = (string) ($item['sku'] ?? '');
$qty = (int) ($item['qty'] ?? 0);
@ -435,6 +437,7 @@ function report_metrics(): array
return [
'gross' => $gross,
'total_vat' => $totalVat,
'branch_totals' => $branchTotals,
'payment_totals' => $paymentTotals,
'product_totals' => $productTotals,
@ -467,6 +470,7 @@ function stock_snapshot(): array
'cost_price' => $item['cost_price'] ?? 0,
'category_id' => $item['category_id'],
'supplier_id' => $item['supplier_id'],
'unit_id' => $item['unit_id'],
'image_url' => $item['image_url'],
'vat' => $item['vat'],
];
@ -508,9 +512,9 @@ function create_purchase(array $data): int
db()->beginTransaction();
try {
$stmt = db()->prepare("INSERT INTO purchase_orders
(reference_no, branch_code, user_username, user_name, role_name, supplier_name, items_json, item_count, subtotal, total_amount, status, notes, purchase_date)
(reference_no, branch_code, user_username, user_name, role_name, supplier_name, items_json, item_count, subtotal, vat_amount, total_amount, status, notes, purchase_date)
VALUES
(:reference_no, :branch_code, :user_username, :user_name, :role_name, :supplier_name, :items_json, :item_count, :subtotal, :total_amount, :status, :notes, NOW())");
(:reference_no, :branch_code, :user_username, :user_name, :role_name, :supplier_name, :items_json, :item_count, :subtotal, :vat_amount, :total_amount, :status, :notes, NOW())");
$stmt->bindValue(":reference_no", $data["reference_no"]);
$stmt->bindValue(":branch_code", $data["branch_code"]);
@ -521,6 +525,7 @@ function create_purchase(array $data): int
$stmt->bindValue(":items_json", json_encode($data["items"], JSON_UNESCAPED_UNICODE));
$stmt->bindValue(":item_count", $data["item_count"], PDO::PARAM_INT);
$stmt->bindValue(":subtotal", $data["subtotal"]);
$stmt->bindValue(":vat_amount", $data["vat_amount"] ?? 0.0);
$stmt->bindValue(":total_amount", $data["total_amount"]);
$stmt->bindValue(":status", $data["status"] ?? "completed");
$stmt->bindValue(":notes", $data["notes"]);

View File

@ -32,6 +32,8 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') {
} else {
$normalized = [];
$subtotal = 0.0;
$totalVat = 0.0;
$globalVat = (float) get_setting('vat_percentage', 5);
$itemCount = 0;
foreach ($items as $item) {
$sku = (string) ($item['sku'] ?? '');
@ -51,6 +53,8 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') {
'line_total' => $lineTotal,
];
$subtotal += $lineTotal;
$itemVat = $lineTotal * ($globalVat / 100);
$totalVat += $itemVat;
$itemCount += $qty;
}
@ -68,7 +72,8 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') {
'items' => $normalized,
'item_count' => $itemCount,
'subtotal' => $subtotal,
'total_amount' => $subtotal,
'vat_amount' => $totalVat,
'total_amount' => $subtotal + $totalVat,
'status' => $purchaseStatus,
'notes' => $notes !== '' ? $notes : null,
]);
@ -358,7 +363,7 @@ require __DIR__ . '/header.php';
</div>
<div class="totals-row">
<span><?= h(tr('الضريبة (' . get_setting('vat_percentage', 5) . '%)', 'VAT (' . get_setting('vat_percentage', 5) . '%)')) ?></span>
<span class="text-success small"><?= h(tr('مشمولة', 'Included')) ?></span>
<span id="displayVat" class="text-muted">0.000</span>
</div>
<div class="totals-row grand-total">
<span><?= h(tr('الإجمالي', 'Total')) ?></span>
@ -606,19 +611,22 @@ function renderInvoice() {
if (skus.length === 0) {
tbody.innerHTML = '';
tbody.appendChild(emptyRow);
updateTotals(0);
updateTotals(0, 0);
cartJson.value = '[]';
return;
}
tbody.innerHTML = '';
let totalAmount = 0;
let totalVat = 0;
const globalVat = <?= get_setting('vat_percentage', 5) ?>;
const cartData = [];
skus.forEach(sku => {
const item = invoiceItems[sku];
const lineTotal = item.qty * item.price;
totalAmount += lineTotal;
totalVat += lineTotal * (globalVat / 100);
cartData.push({ sku: item.sku, qty: item.qty, price: item.price });
const tr = document.createElement('tr');
@ -643,13 +651,16 @@ function renderInvoice() {
tbody.appendChild(tr);
});
updateTotals(totalAmount);
updateTotals(totalAmount, totalVat);
cartJson.value = JSON.stringify(cartData);
}
function updateTotals(total) {
document.getElementById('displaySubtotal').innerText = total.toFixed(3);
document.getElementById('displayTotal').innerText = total.toFixed(3) + currencySuffix;
function updateTotals(total, vat) {
const subtotal = total;
const finalTotal = subtotal + vat;
document.getElementById('displaySubtotal').innerText = subtotal.toFixed(3);
document.getElementById('displayVat').innerText = vat.toFixed(3);
document.getElementById('displayTotal').innerText = finalTotal.toFixed(3) + currencySuffix;
}
// Intercept form submission to check if items exist

View File

@ -45,7 +45,7 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') {
$vatPercent = (float) ($product['vat'] ?? 0);
// Assuming price is inclusive of VAT:
$itemVat = $lineTotal - ($lineTotal / (1 + ($vatPercent / 100)));
$itemVat = $lineTotal * ($vatPercent / 100);
$totalVat += $itemVat;
$normalized[] = [
@ -77,9 +77,9 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') {
'payment_method' => $paymentMethod,
'items' => $normalized,
'item_count' => $itemCount,
'subtotal' => $subtotal - $totalVat,
'subtotal' => $subtotal,
'vat_amount' => $totalVat,
'total_amount' => $subtotal,
'total_amount' => $subtotal + $totalVat,
'status' => $saleStatus,
'notes' => $notes !== '' ? $notes : null,
]);
@ -370,7 +370,7 @@ require __DIR__ . '/header.php';
<span id="displaySubtotal" class="fw-medium">0.000</span>
</div>
<div class="totals-row">
<span><?= h(tr('الضريبة (مشمولة)', 'VAT (Included)')) ?></span>
<span><?= h(tr('الضريبة (مضافة)', 'VAT (Added)')) ?></span>
<span id="displayVat" class="text-muted">0.000</span>
</div>
<div class="totals-row grand-total">
@ -626,7 +626,7 @@ function renderInvoice() {
const lineTotal = item.qty * item.price;
const vatPercent = parseFloat(catalogData[sku].vat) || 0;
const itemVat = lineTotal - (lineTotal / (1 + (vatPercent / 100)));
const itemVat = lineTotal * (vatPercent / 100);
totalVat += itemVat;
totalAmount += lineTotal;
@ -657,10 +657,11 @@ function renderInvoice() {
}
function updateTotals(total, vat) {
const subtotal = total - vat;
const subtotal = total;
const finalTotal = subtotal + vat;
document.getElementById('displaySubtotal').innerText = subtotal.toFixed(3);
document.getElementById('displayVat').innerText = vat.toFixed(3);
document.getElementById('displayTotal').innerText = total.toFixed(3) + currencySuffix;
document.getElementById('displayTotal').innerText = finalTotal.toFixed(3) + currencySuffix;
}
// Intercept form submission to check if items exist

13
patch_export.php Normal file
View File

@ -0,0 +1,13 @@
<?php
$content = file_get_contents('stock.php');
$search = "echo \"\";";
$replace = 'echo "\xEF\xBB\xBF";';
if (strpos($content, $search) !== false) {
$content = str_replace($search, $replace, $content);
file_put_contents('stock.php', $content);
echo "Replaced successfully.\n";
} else {
echo "Search string not found.\n";
}

69
patch_import.php Normal file
View File

@ -0,0 +1,69 @@
<?php
$content = file_get_contents('stock.php');
$search = <<<'REPLACE'
if (isset($_FILES['csv_file']) && $_FILES['csv_file']['error'] === UPLOAD_ERR_OK) {
$pdo = db();
$file = fopen($_FILES['csv_file']['tmp_name'], 'r');
$bom = fread($file, 3);
if ($bom !== "") rewind($file);
$header = fgetcsv($file);
$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=?");
$stmtCheck = $pdo->prepare("SELECT id FROM items WHERE sku=?");
while (($row = fgetcsv($file)) !== false) {
REPLACE;
$replace = <<<'REPLACE'
if (isset($_FILES['csv_file']) && $_FILES['csv_file']['error'] === UPLOAD_ERR_OK) {
$pdo = db();
$file_path = $_FILES['csv_file']['tmp_name'];
$raw_content = file_get_contents($file_path);
// Prevent ZIP / XLSX
if (str_starts_with($raw_content, 'PK')) {
header('Location: stock.php?import_error=' . urlencode('يرجى حفظ الملف بصيغة CSV وليس كملف إكسل (XLSX)'));
exit;
}
// Remove UTF-8 BOM if present
if (str_starts_with($raw_content, "\xEF\xBB\xBF")) {
$raw_content = substr($raw_content, 3);
}
// Fix encoding for Windows-1256 (common in Arabic Excel exports)
if (!mb_check_encoding($raw_content, 'UTF-8')) {
$raw_content = mb_convert_encoding($raw_content, 'UTF-8', 'Windows-1256');
}
// Determine delimiter by checking first line
$first_line = strtok($raw_content, "\r\n");
$delimiter = ',';
if ($first_line !== false && substr_count($first_line, ';') > substr_count($first_line, ',')) {
$delimiter = ';';
}
$clean_file = tmpfile();
fwrite($clean_file, $raw_content);
rewind($clean_file);
$header = fgetcsv($clean_file, 0, $delimiter);
$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=?");
$stmtCheck = $pdo->prepare("SELECT id FROM items WHERE sku=?");
while (($row = fgetcsv($clean_file, 0, $delimiter)) !== false) {
REPLACE;
if (strpos($content, $search) !== false) {
$content = str_replace($search, $replace, $content);
file_put_contents('stock.php', $content);
echo "Replaced successfully.\n";
} else {
echo "Search string not found.\n";
}

118
patch_import.py Normal file
View File

@ -0,0 +1,118 @@
import re
with open("stock.php", "r", encoding="utf-8") as f:
content = f.read()
# Replace block from `// Handle Import CSV` to `// Handle AJAX actions`
start_marker = "// Handle Import CSV"
end_marker = "// Handle AJAX actions"
start_idx = content.find(start_marker)
end_idx = content.find(end_marker)
if start_idx == -1 or end_idx == -1:
print("Could not find import_csv block")
exit(1)
new_import_code = """// Handle Import CSV
if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['action']) && $_POST['action'] === 'import_csv') {
if (isset($_FILES['csv_file']) && $_FILES['csv_file']['error'] === UPLOAD_ERR_OK) {
require_once __DIR__ . '/includes/SimpleXLSX.php';
$pdo = db();
$file_path = $_FILES['csv_file']['tmp_name'];
$raw_content = file_get_contents($file_path);
$rows = [];
# Check if XLSX (starts with PK)
if (str_starts_with($raw_content, 'PK')) {
if ( $xlsx = Shuchkin\SimpleXLSX::parse($file_path) ) {
$rows = $xlsx->rows();
if (count($rows) > 0) {
array_shift($rows); # Remove header
}
} else {
header('Location: stock.php?import_error=' . urlencode('خطأ في قراءة ملف الإكسل (XLSX). يرجى التأكد من أن الملف سليم.'));
exit;
}
} else {
# Treat as CSV
# Remove UTF-8 BOM if present
if (str_starts_with($raw_content, "\xEF\xBB\xBF")) {
$raw_content = substr($raw_content, 3);
}
# Fix encoding for Windows-1256 (common in Arabic Excel exports)
if (!mb_check_encoding($raw_content, 'UTF-8')) {
$raw_content = mb_convert_encoding($raw_content, 'UTF-8', 'Windows-1256');
}
# Determine delimiter by checking first line
$first_line = strtok($raw_content, "\r\n");
$delimiter = ',';
if ($first_line !== false && substr_count($first_line, ';') > substr_count($first_line, ',')) {
$delimiter = ';';
}
$clean_file = tmpfile();
fwrite($clean_file, $raw_content);
rewind($clean_file);
$header = fgetcsv($clean_file, 0, $delimiter);
while (($row = fgetcsv($clean_file, 0, $delimiter)) !== false) {
$rows[] = $row;
}
fclose($clean_file);
}
$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=?");
$stmtCheck = $pdo->prepare("SELECT id FROM items WHERE sku=?");
foreach ($rows as $row) {
if (count($row) < 5) continue;
$sku = trim((string)$row[0]); $name = trim((string)$row[1]);
if ($sku === '' || $name === '') continue;
$price = (float)($row[2] ?? 0);
$cost_price = (float)($row[3] ?? 0);
$base_stock = (int)($row[4] ?? 0);
$vat = (float)($row[5] ?? 5);
$category_id = !empty($row[6]) ? (int)$row[6] : null;
$supplier_id = !empty($row[7]) ? (int)$row[7] : null;
$unit_id = !empty($row[8]) ? (int)$row[8] : null;
$stmtCheck->execute([$sku]);
if ($stmtCheck->fetchColumn()) {
$stmtUpdate->execute([$name, $price, $cost_price, $base_stock, $vat, $category_id, $supplier_id, $unit_id, $sku]);
$updated++;
} else {
$stmtInsert->execute([$sku, $name, $price, $cost_price, $base_stock, $vat, $category_id, $supplier_id, $unit_id]);
$imported++;
}
}
$pdo->commit();
header('Location: stock.php?import_success=1&imported='.$imported.'&updated='.$updated);
exit;
} catch (Exception $e) {
$pdo->rollBack();
header('Location: stock.php?import_error='.urlencode($e->getMessage()));
exit;
}
}
header('Location: stock.php?import_error=No+file');
exit;
}
"""
content = content[:start_idx] + new_import_code + content[end_idx:]
# Update the modal text to reflect XLSX support
content = content.replace("ملاحظة: يدعم النظام ملفات CSV فقط. يرجى حفظ ملف Excel بصيغة (CSV UTF-8).", "ملاحظة: يدعم النظام الآن ملفات Excel (XLSX) بالإضافة إلى CSV.")
content = content.replace("Note: The system supports CSV files only. Please save your Excel file as (CSV UTF-8).", "Note: The system now supports Excel (XLSX) files in addition to CSV.")
content = content.replace('accept=".csv"', 'accept=".csv, application/vnd.openxmlformats-officedocument.spreadsheetml.sheet, application/vnd.ms-excel"')
with open("stock.php", "w", encoding="utf-8") as f:
f.write(content)
print("Patched successfully")

31
patch_import_fk.py Normal file
View File

@ -0,0 +1,31 @@
import re
with open("stock.php", "r") as f:
content = f.read()
replacement = """ $valid_categories = $pdo->query("SELECT id FROM categories")->fetchAll(PDO::FETCH_COLUMN);
$valid_suppliers = $pdo->query("SELECT id FROM suppliers")->fetchAll(PDO::FETCH_COLUMN);
$valid_units = $pdo->query("SELECT id FROM units")->fetchAll(PDO::FETCH_COLUMN);
foreach ($rows as $row) {
if (count($row) < 5) continue;
$sku = trim((string)$row[0]); $name = trim((string)$row[1]);
if ($sku === '' || $name === '') continue;
$price = (float)($row[2] ?? 0);
$cost_price = (float)($row[3] ?? 0);
$base_stock = (int)($row[4] ?? 0);
$vat = (float)($row[5] ?? 5);
$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;"""
pattern = r" foreach \(\$rows as \$row\) \{[^\}]+?\$unit_id = !empty\(\$row\[8\]\) \? \(int\)\$row\[8\] : null;"
new_content = re.sub(pattern, replacement, content, flags=re.DOTALL)
if replacement in new_content:
with open("stock.php", "w") as f:
f.write(new_content)
print("Success")
else:
print("Failed")

24
pos.php
View File

@ -34,6 +34,7 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') {
} else {
$normalized = [];
$subtotal = 0.0;
$totalVat = 0.0;
$itemCount = 0;
foreach ($items as $item) {
$sku = (string) ($item['sku'] ?? '');
@ -53,6 +54,9 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') {
'line_total' => $lineTotal,
];
$subtotal += $lineTotal;
$vatPercent = (float) ($product['vat'] ?? 0);
$itemVat = $lineTotal * ($vatPercent / 100);
$totalVat += $itemVat;
$itemCount += $qty;
}
@ -72,7 +76,8 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') {
'items' => $normalized,
'item_count' => $itemCount,
'subtotal' => $subtotal,
'total_amount' => $subtotal,
'vat_amount' => $totalVat,
'total_amount' => $subtotal + $totalVat,
'notes' => $notes !== '' ? $notes : null,
]);
@ -433,6 +438,10 @@ require __DIR__ . '/includes/header.php';
<span><?= h(tr('المجموع الفرعي', 'Subtotal')) ?></span>
<span id="cartSubtotalVal">0.000</span>
</div>
<div class="summary-row">
<span><?= h(tr('الضريبة (مضافة)', 'VAT (Added)')) ?></span>
<span id="cartVatVal" class="text-muted">0.000</span>
</div>
<div class="summary-total">
<span><?= h(tr('الإجمالي', 'Total')) ?></span>
<span class="text-primary" id="cartTotalVal">0.000</span>
@ -694,6 +703,7 @@ function renderCart() {
const list = document.getElementById('cartItemsList');
const badge = document.getElementById('cartBadgeCount');
const subVal = document.getElementById('cartSubtotalVal');
const vatVal = document.getElementById('cartVatVal');
const totalVal = document.getElementById('cartTotalVal');
const btnPay = document.getElementById('btnPay');
const btnHold = document.getElementById('btnHold');
@ -702,6 +712,7 @@ function renderCart() {
list.innerHTML = '';
let total = 0;
let count = 0;
let totalVat = 0;
const skus = Object.keys(cart);
if (skus.length === 0) {
@ -713,6 +724,7 @@ function renderCart() {
`;
badge.innerText = '0';
subVal.innerText = `0.000 ${currencyLabel}`;
vatVal.innerText = `0.000`;
totalVal.innerText = `0.000 ${currencyLabel}`;
btnPay.disabled = true;
btnHold.disabled = true;
@ -723,6 +735,9 @@ function renderCart() {
skus.forEach(sku => {
const item = cart[sku];
const lineTotal = item.price * item.qty;
const vatPercent = parseFloat(catalogData[sku].vat) || 0;
const itemVat = lineTotal * (vatPercent / 100);
totalVat += itemVat;
total += lineTotal;
count += item.qty;
@ -746,8 +761,11 @@ function renderCart() {
});
badge.innerText = count;
const totalStr = `${total.toFixed(3)} ${currencyLabel}`;
subVal.innerText = totalStr;
const subtotal = total;
const finalTotal = subtotal + totalVat;
const totalStr = `${finalTotal.toFixed(3)} ${currencyLabel}`;
subVal.innerText = subtotal.toFixed(3) + ' ' + currencyLabel;
vatVal.innerText = totalVat.toFixed(3);
totalVal.innerText = totalStr;
document.getElementById('modalTotalAmount').innerText = totalStr;

View File

@ -248,7 +248,7 @@ $registerNo = 'REG-01';
<span><?= number_format((float)$sale['subtotal'], 3) ?></span>
</div>
<div class="totals-row">
<span><?= h(tr('ضريبة القيمة المضافة (مشمولة)', 'VAT (Inclusive)')) ?></span>
<span><?= h(tr('ضريبة القيمة المضافة (مضافة)', 'VAT (Added)')) ?></span>
<span><?= number_format((float)($sale['vat_amount'] ?? 0), 3) ?></span>
</div>
<div class="totals-row grand-total">

View File

@ -70,6 +70,8 @@ require __DIR__ . '/includes/header.php';
<th class="text-white border-0 py-3 fw-semibold bg-transparent"><?= h(tr('المورد', 'Supplier')) ?></th>
<th class="text-white border-0 py-3 fw-semibold bg-transparent"><?= h(tr('المرجع', 'Reference')) ?></th>
<th class="text-white border-0 py-3 fw-semibold bg-transparent"><?= h(tr('الفرع', 'Branch')) ?></th>
<th class="text-white border-0 py-3 fw-semibold bg-transparent"><?= h(tr('المجموع', 'Subtotal')) ?></th>
<th class="text-white border-0 py-3 fw-semibold bg-transparent"><?= h(tr('الضريبة', 'VAT')) ?></th>
<th class="text-white border-0 py-3 fw-semibold bg-transparent"><?= h(tr('الإجمالي', 'Total Amount')) ?></th>
<th class="text-white border-0 py-3 fw-semibold bg-transparent"><?= h(tr('الحالة', 'Status')) ?></th>
<th class="text-white border-0 py-3 fw-semibold bg-transparent"><?= h(tr('التاريخ', 'Date')) ?></th>
@ -85,7 +87,9 @@ require __DIR__ . '/includes/header.php';
<td><?= h($row['supplier_name'] ?: '-') ?></td>
<td><?= h($row['reference_no']) ?></td>
<td><?= h(branch_label($row['branch_code'])) ?></td>
<td class="fw-semibold"><?= h(currency((float)$row['total_amount'])) ?></td>
<td class="text-muted"><?= h(currency((float)$row['subtotal'])) ?></td>
<td class="text-muted text-danger"><?= h(currency((float)$row['vat_amount'])) ?></td>
<td class="fw-bold text-success"><?= h(currency((float)$row['total_amount'])) ?></td>
<td>
<?php if ($row['status'] === 'order'): ?>
<span class="badge bg-warning text-dark px-3 py-2 rounded-pill"><i class="bi bi-clock"></i> <?= h(tr('طلب شراء', 'Order')) ?></span>

View File

@ -142,13 +142,19 @@ require __DIR__ . '/includes/header.php';
<th><?= h(tr('الفرع', 'Branch')) ?></th>
<th><?= h(tr('طريقة الدفع', 'Payment Method')) ?></th>
<th><?= h(tr('الحالة', 'Status')) ?></th>
<th class="text-end"><?= h(tr('المجموع', 'Subtotal')) ?></th>
<th class="text-end"><?= h(tr('الضريبة', 'VAT')) ?></th>
<th class="text-end"><?= h(tr('الإجمالي', 'Total')) ?></th>
</tr>
</thead>
<tbody>
<?php
$subtotalSum = 0;
$vatSum = 0;
$totalSum = 0;
foreach($salesReport as $sale):
$subtotalSum += (float) ($sale['subtotal'] ?? 0);
$vatSum += (float) ($sale['vat_amount'] ?? 0);
$totalSum += (float) $sale['total_amount'];
?>
<tr>
@ -164,6 +170,8 @@ require __DIR__ . '/includes/header.php';
<span class="badge bg-success"><i class="bi bi-check-circle"></i> <?= h(tr('مدفوع', 'Paid')) ?></span>
<?php endif; ?>
</td>
<td class="text-end"><?= h(currency((float)($sale['subtotal'] ?? 0))) ?></td>
<td class="text-end"><?= h(currency((float)($sale['vat_amount'] ?? 0))) ?></td>
<td class="text-end fw-bold"><?= h(currency((float)$sale['total_amount'])) ?></td>
</tr>
<?php endforeach; ?>
@ -171,6 +179,8 @@ require __DIR__ . '/includes/header.php';
<tfoot class="table-dark">
<tr>
<td colspan="6" class="text-end"><?= h(tr('الإجمالي الكلي', 'Grand Total')) ?></td>
<td class="text-end fw-bold fs-6"><?= h(currency($subtotalSum)) ?></td>
<td class="text-end fw-bold fs-6"><?= h(currency($vatSum)) ?></td>
<td class="text-end fw-bold fs-5"><?= h(currency($totalSum)) ?></td>
</tr>
</tfoot>
@ -263,9 +273,10 @@ require __DIR__ . '/includes/header.php';
<?php else: ?>
<section class="row g-3 mb-4 d-print-none">
<div class="col-md-4"><article class="metric-card"><div class="eyebrow"><?= h(tr('إجمالي المبيعات', 'Gross sales')) ?></div><div class="metric-value"><?= h(currency((float) $report['gross'])) ?></div><div class="small text-muted"><?= h(tr('حسب نطاق صلاحية المستخدم الحالي', 'Scoped to the current viewer permissions')) ?></div></article></div>
<div class="col-md-4"><article class="metric-card"><div class="eyebrow"><?= h(tr('عدد الفواتير', 'Invoices')) ?></div><div class="metric-value"><?= h((string) $report['sales_count']) ?></div><div class="small text-muted"><?= h(tr('إجمالي الفواتير المسجلة', 'Total logged invoices')) ?></div></article></div>
<div class="col-md-4"><article class="metric-card"><div class="eyebrow"><?= h(tr('أفضل صنف', 'Top product')) ?></div><div class="metric-value small-metric"><?= h($report['product_totals'] ? product_label((string) array_key_first($report['product_totals'])) : tr('لا يوجد', 'None yet')) ?></div><div class="small text-muted"><?= h(tr('الأكثر مبيعاً حتى الآن', 'Most sold item so far')) ?></div></article></div>
<div class="col-md-3"><article class="metric-card"><div class="eyebrow"><?= h(tr('إجمالي المبيعات', 'Gross sales')) ?></div><div class="metric-value"><?= h(currency((float) $report['gross'])) ?></div><div class="small text-muted"><?= h(tr('حسب نطاق صلاحية المستخدم الحالي', 'Scoped to the current viewer permissions')) ?></div></article></div>
<div class="col-md-3"><article class="metric-card"><div class="eyebrow"><?= h(tr('إجمالي الضريبة', 'Total VAT')) ?></div><div class="metric-value"><?= h(currency((float) ($report['total_vat'] ?? 0))) ?></div><div class="small text-muted"><?= h(tr('مجموع ضريبة القيمة المضافة', 'Total Value Added Tax')) ?></div></article></div>
<div class="col-md-3"><article class="metric-card"><div class="eyebrow"><?= h(tr('عدد الفواتير', 'Invoices')) ?></div><div class="metric-value"><?= h((string) $report['sales_count']) ?></div><div class="small text-muted"><?= h(tr('إجمالي الفواتير المسجلة', 'Total logged invoices')) ?></div></article></div>
<div class="col-md-3"><article class="metric-card"><div class="eyebrow"><?= h(tr('أفضل صنف', 'Top product')) ?></div><div class="metric-value small-metric"><?= h($report['product_totals'] ? product_label((string) array_key_first($report['product_totals'])) : tr('لا يوجد', 'None yet')) ?></div><div class="small text-muted"><?= h(tr('الأكثر مبيعاً حتى الآن', 'Most sold item so far')) ?></div></article></div>
</section>
<section class="row g-4 d-print-none">
<div class="col-lg-6">

View File

@ -424,7 +424,7 @@ require __DIR__ . '/includes/header.php';
<td class="total-amount"><?= h(number_format((float) $sale['subtotal'], 3)) ?></td>
</tr>
<tr>
<td class="total-label"><?= h(tr('ضريبة القيمة المضافة (مشمولة)', 'VAT (Inclusive)')) ?></td>
<td class="total-label"><?= h(tr('ضريبة القيمة المضافة (مضافة)', 'VAT (Added)')) ?></td>
<td class="total-amount"><?= number_format((float)($sale['vat_amount'] ?? 0), 3) ?></td>
</tr>
<tr class="grand-total-row">

View File

@ -131,6 +131,8 @@ require __DIR__ . '/includes/header.php';
<th class="text-white border-0 py-3 fw-semibold bg-transparent"><?= h(tr('النوع', 'Type')) ?></th>
<th class="text-white border-0 py-3 fw-semibold bg-transparent"><?= h(tr('الكاشير', 'Cashier')) ?></th>
<th class="text-white border-0 py-3 fw-semibold bg-transparent"><?= h(tr('العميل', 'Customer')) ?></th>
<th class="text-white border-0 py-3 fw-semibold bg-transparent"><?= h(tr('المجموع', 'Subtotal')) ?></th>
<th class="text-white border-0 py-3 fw-semibold bg-transparent"><?= h(tr('الضريبة', 'VAT')) ?></th>
<th class="text-white border-0 py-3 fw-semibold bg-transparent"><?= h(tr('الإجمالي', 'Total')) ?></th>
<th class="text-white border-0 py-3 fw-semibold bg-transparent"><?= h(tr('الحالة', 'Status')) ?></th>
<th class="text-white border-0 py-3 fw-semibold bg-transparent"><?= h(tr('التاريخ', 'Date')) ?></th>
@ -148,7 +150,9 @@ require __DIR__ . '/includes/header.php';
<td><span class="badge text-bg-light border"><?= h(sale_mode_label((string) $sale['sale_mode'])) ?></span></td>
<td><?= h((string) $sale['cashier_name']) ?></td>
<td><?= h((string) ($sale['customer_name'] ?: '-')) ?></td>
<td class="fw-semibold"><?= h(currency((float) $sale['total_amount'])) ?></td>
<td class="text-muted"><?= h(currency((float) $sale['subtotal'])) ?></td>
<td class="text-muted text-danger"><?= h(currency((float) $sale['vat_amount'])) ?></td>
<td class="fw-bold text-success"><?= h(currency((float) $sale['total_amount'])) ?></td>
<td>
<?php if (($sale['status'] ?? 'completed') === 'order'): ?>
<span class="badge bg-warning text-dark px-3 py-2 rounded-pill"><i class="bi bi-clock"></i> <?= h(tr('طلب حجز', 'Order')) ?></span>

View File

@ -12,7 +12,7 @@ if ($_SERVER['REQUEST_METHOD'] === 'GET' && isset($_GET['action']) && $_GET['act
$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 "";
echo "\xEF\xBB\xBF";
$output = fopen('php://output', 'w');
fputcsv($output, ['SKU', 'Name', 'Price', 'Cost Price', 'Stock', 'VAT', 'Category ID', 'Supplier ID', 'Unit ID']);
foreach ($items as $row) { fputcsv($output, $row); }
@ -23,28 +23,76 @@ if ($_SERVER['REQUEST_METHOD'] === 'GET' && isset($_GET['action']) && $_GET['act
// Handle Import CSV
if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['action']) && $_POST['action'] === 'import_csv') {
if (isset($_FILES['csv_file']) && $_FILES['csv_file']['error'] === UPLOAD_ERR_OK) {
require_once __DIR__ . '/includes/SimpleXLSX.php';
$pdo = db();
$file = fopen($_FILES['csv_file']['tmp_name'], 'r');
$bom = fread($file, 3);
if ($bom !== "") rewind($file);
$header = fgetcsv($file);
$file_path = $_FILES['csv_file']['tmp_name'];
$raw_content = file_get_contents($file_path);
$rows = [];
# Check if XLSX (starts with PK)
if (str_starts_with($raw_content, 'PK')) {
if ( $xlsx = Shuchkin\SimpleXLSX::parse($file_path) ) {
$rows = $xlsx->rows();
if (count($rows) > 0) {
array_shift($rows); # Remove header
}
} else {
header('Location: stock.php?import_error=' . urlencode('خطأ في قراءة ملف الإكسل (XLSX). يرجى التأكد من أن الملف سليم.'));
exit;
}
} else {
# Treat as CSV
# Remove UTF-8 BOM if present
if (str_starts_with($raw_content, "")) {
$raw_content = substr($raw_content, 3);
}
# Fix encoding for Windows-1256 (common in Arabic Excel exports)
if (!mb_check_encoding($raw_content, 'UTF-8')) {
$raw_content = mb_convert_encoding($raw_content, 'UTF-8', 'Windows-1256');
}
# Determine delimiter by checking first line
$first_line = strtok($raw_content, "
");
$delimiter = ',';
if ($first_line !== false && substr_count($first_line, ';') > substr_count($first_line, ',')) {
$delimiter = ';';
}
$clean_file = tmpfile();
fwrite($clean_file, $raw_content);
rewind($clean_file);
$header = fgetcsv($clean_file, 0, $delimiter);
while (($row = fgetcsv($clean_file, 0, $delimiter)) !== false) {
$rows[] = $row;
}
fclose($clean_file);
}
$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=?");
$stmtCheck = $pdo->prepare("SELECT id FROM items WHERE sku=?");
while (($row = fgetcsv($file)) !== false) {
$valid_categories = $pdo->query("SELECT id FROM categories")->fetchAll(PDO::FETCH_COLUMN);
$valid_suppliers = $pdo->query("SELECT id FROM suppliers")->fetchAll(PDO::FETCH_COLUMN);
$valid_units = $pdo->query("SELECT id FROM units")->fetchAll(PDO::FETCH_COLUMN);
foreach ($rows as $row) {
if (count($row) < 5) continue;
$sku = trim($row[0]); $name = trim($row[1]);
$sku = trim((string)$row[0]); $name = trim((string)$row[1]);
if ($sku === '' || $name === '') continue;
$price = (float)($row[2] ?? 0);
$cost_price = (float)($row[3] ?? 0);
$base_stock = (int)($row[4] ?? 0);
$vat = (float)($row[5] ?? 5);
$category_id = !empty($row[6]) ? (int)$row[6] : null;
$supplier_id = !empty($row[7]) ? (int)$row[7] : null;
$unit_id = !empty($row[8]) ? (int)$row[8] : null;
$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;
$stmtCheck->execute([$sku]);
if ($stmtCheck->fetchColumn()) {
$stmtUpdate->execute([$name, $price, $cost_price, $base_stock, $vat, $category_id, $supplier_id, $unit_id, $sku]);
@ -285,7 +333,7 @@ require __DIR__ . '/includes/header.php';
</th>
<th class="text-white border-0 py-3 fw-semibold bg-transparent">SKU</th>
<th width="70" class="text-white border-0 py-3 fw-semibold bg-transparent"><?= h(tr('صورة', 'Pic')) ?></th>
<th class="text-white border-0 py-3 fw-semibold bg-transparent text-start"><?= h(tr('الصنف', 'Product')) ?></th>
<th class="text-white border-0 py-3 fw-semibold bg-transparent text-end pe-4"><?= h(tr('الصنف', 'Product')) ?></th>
<th class="text-white border-0 py-3 fw-semibold bg-transparent"><?= h(tr('السعر', 'Price')) ?></th>
<th class="text-white border-0 py-3 fw-semibold bg-transparent"><?= h(tr('التكلفة', 'Cost')) ?></th>
<th class="text-white border-0 py-3 fw-semibold bg-transparent"><?= h(tr('افتتاحي', 'Opening')) ?></th>
@ -314,7 +362,7 @@ require __DIR__ . '/includes/header.php';
</div>
<?php endif; ?>
</td>
<td class="fw-bold text-start text-dark"><?= h($row['name']) ?></td>
<td class="fw-bold text-end text-dark pe-4"><?= h($row['name']) ?></td>
<td><span class="badge bg-light text-dark border px-2 py-1"><?= h(currency($row['price'])) ?></span></td>
<td><span class="badge bg-light text-secondary border px-2 py-1"><?= h(currency($row['cost_price'] ?? 0)) ?></span></td>
<td class="text-secondary"><?= h((string) $row['base_stock']) ?></td>
@ -370,7 +418,7 @@ require __DIR__ . '/includes/header.php';
</div>
<div class="modal-body">
<div class="alert alert-warning small">
<?= h(tr('ملاحظة: يدعم النظام ملفات CSV فقط. يرجى حفظ ملف Excel بصيغة (CSV UTF-8).', 'Note: The system supports CSV files only. Please save your Excel file as (CSV UTF-8).')) ?>
<?= h(tr('ملاحظة: يدعم النظام الآن ملفات Excel (XLSX) بالإضافة إلى CSV.', 'Note: The system now supports Excel (XLSX) files in addition to CSV.')) ?>
</div>
<p class="text-muted small mb-2">
<?= h(tr('يجب أن يحتوي الملف على الأعمدة التالية بالترتيب الدقيق:', 'The file must contain exactly these columns in order:')) ?><br>
@ -378,7 +426,7 @@ require __DIR__ . '/includes/header.php';
</p>
<div class="mb-3">
<label class="form-label"><?= h(tr('ملف الإكسل / CSV', 'Excel / CSV File')) ?></label>
<input type="file" class="form-control" name="csv_file" accept=".csv" required>
<input type="file" class="form-control" name="csv_file" accept=".csv, application/vnd.openxmlformats-officedocument.spreadsheetml.sheet, application/vnd.ms-excel" required>
</div>
</div>
<div class="modal-footer">