missing migrations

This commit is contained in:
Flatlogic Bot 2026-02-27 01:20:05 +00:00
parent ac32148f05
commit 8faf7adbb6
21 changed files with 1411 additions and 62 deletions

View File

@ -1,5 +1,6 @@
<?php
require_once __DIR__ . "/../includes/functions.php";
require_once __DIR__ . "/../db/config.php";
require_permission("ads");
require_once __DIR__ . '/../db/config.php';
$pdo = db();

View File

@ -1,5 +1,6 @@
<?php
require_once __DIR__ . "/../includes/functions.php";
require_once __DIR__ . "/../db/config.php";
require_permission("areas_view");
require_once __DIR__ . '/../db/config.php';
$pdo = db();

View File

@ -1,5 +1,6 @@
<?php
require_once __DIR__ . "/../includes/functions.php";
require_once __DIR__ . "/../db/config.php";
require_permission("settings_view");
require_once __DIR__ . '/../db/config.php';
$pdo = db();

View File

@ -1,5 +1,6 @@
<?php
require_once __DIR__ . "/../includes/functions.php";
require_once __DIR__ . "/../db/config.php";
require_permission("settings_view");
require_once __DIR__ . '/../db/config.php';
$pdo = db();

View File

@ -1,5 +1,6 @@
<?php
require_once __DIR__ . "/../includes/functions.php";
require_once __DIR__ . "/../db/config.php";
require_permission("expense_categories_view");
require_once __DIR__ . '/../db/config.php';
$pdo = db();

View File

@ -1,5 +1,6 @@
<?php
require_once __DIR__ . "/../includes/functions.php";
require_once __DIR__ . "/../db/config.php";
require_permission("settings_view");
require_once __DIR__ . '/../db/config.php';
require_once __DIR__ . '/../includes/WablasService.php';

View File

@ -1,5 +1,6 @@
<?php
require_once __DIR__ . "/../includes/functions.php";
require_once __DIR__ . "/../db/config.php";
require_permission("loyalty_view");
require_once __DIR__ . '/../db/config.php';
$pdo = db();

View File

@ -48,6 +48,7 @@ foreach ($items as $item) {
$vat_or_discount = (float)$order['discount'];
$company_settings = get_company_settings();
$vat_rate = (float)($company_settings['vat_rate'] ?? 0);
?>
<style>
@ -192,18 +193,14 @@ $company_settings = get_company_settings();
<tr>
<td colspan="3" class="text-end py-3 ps-4">
<div class="text-muted mb-1">Subtotal</div>
<?php if ($vat_or_discount != 0): ?>
<div class="text-muted mb-1"><?= $vat_or_discount > 0 ? 'VAT' : 'Discount' ?></div>
<?php endif; ?>
<div class="text-muted mb-1"><?= ($vat_or_discount >= 0) ? "VAT ($vat_rate%)" : 'Discount' ?></div>
<h5 class="fw-bold mb-0 text-dark">Total Amount</h5>
</td>
<td class="text-end py-3 pe-4">
<div class="mb-1"><?= format_currency($subtotal) ?></div>
<?php if ($vat_or_discount != 0): ?>
<div class="mb-1 <?= $vat_or_discount < 0 ? 'text-danger' : 'text-primary' ?>">
<?= ($vat_or_discount > 0 ? '+' : '') . format_currency($vat_or_discount) ?>
</div>
<?php endif; ?>
<h5 class="fw-bold mb-0 text-primary"><?= format_currency($order['total_amount']) ?></h5>
</td>
</tr>
@ -429,6 +426,7 @@ function printThermalReceipt() {
const subtotal = data.total - data.vat;
const logoHtml = COMPANY_SETTINGS.logo_url ? `<img src="${BASE_URL}${COMPANY_SETTINGS.logo_url}" style="max-height: 80px; max-width: 150px; margin-bottom: 10px;">` : '';
const vatRate = COMPANY_SETTINGS.vat_rate || 0;
const html = `
<html dir="ltr">
@ -523,11 +521,10 @@ function printThermalReceipt() {
<td>Subtotal / ${tr['Subtotal']}</td>
<td style="text-align: right">${formatCurrency(subtotal)}</td>
</tr>
${Math.abs(data.vat) > 0 ? `
<tr>
<td>${data.vat < 0 ? 'Discount' : 'VAT'} / ${tr['VAT']}</td>
<td>${data.vat < 0 ? 'Discount' : 'VAT (' + vatRate + '%)'} / ${tr['VAT']}</td>
<td style="text-align: right">${data.vat < 0 ? '-' : '+'}${formatCurrency(Math.abs(data.vat))}</td>
</tr>` : ''}
</tr>
<tr style="font-weight: bold; font-size: 18px;">
<td style="padding-top: 10px;">TOTAL / ${tr['TOTAL']}</td>
<td style="text-align: right; padding-top: 10px;">${formatCurrency(data.total)}</td>

View File

@ -1,5 +1,6 @@
<?php
require_once __DIR__ . "/../includes/functions.php";
require_once __DIR__ . "/../db/config.php";
require_permission("outlets_view");
require_once __DIR__ . '/../db/config.php';
$pdo = db();

View File

@ -1,5 +1,6 @@
<?php
require_once __DIR__ . "/../includes/functions.php";
require_once __DIR__ . "/../db/config.php";
require_permission("payment_types_view");
require_once __DIR__ . '/../db/config.php';
$pdo = db();

View File

@ -1,5 +1,6 @@
<?php
require_once __DIR__ . "/../includes/functions.php";
require_once __DIR__ . "/../db/config.php";
require_permission("purchases_view");
require_once __DIR__ . '/../db/config.php';
$pdo = db();

View File

@ -1,5 +1,6 @@
<?php
require_once __DIR__ . "/../includes/functions.php";
require_once __DIR__ . "/../db/config.php";
require_permission("ratings_view");
require_once __DIR__ . '/../db/config.php';
$pdo = db();

View File

@ -1,5 +1,6 @@
<?php
require_once __DIR__ . "/../includes/functions.php";
require_once __DIR__ . "/../db/config.php";
require_permission("suppliers_view");
require_once __DIR__ . '/../db/config.php';
$pdo = db();

View File

@ -1,7 +1,7 @@
<?php
require_once __DIR__ . '/../db/config.php';
require_once __DIR__ . "/../includes/functions.php";
require_permission("tables_view");
require_once __DIR__ . '/../db/config.php';
$pdo = db();
$message = '';
@ -206,7 +206,7 @@ $baseUrl = $protocol . $host . $project_root;
</div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Close</button>
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Cancel</button>
<button type="submit" class="btn btn-primary">Save Table</button>
</div>
</form>
@ -217,76 +217,96 @@ $baseUrl = $protocol . $host . $project_root;
<!-- QR Modal -->
<div class="modal fade" id="qrModal" tabindex="-1" aria-hidden="true">
<div class="modal-dialog modal-sm">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title">Table QR Code</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<div class="modal-body text-center" id="printableQR">
<h4 class="fw-bold mb-3" id="qrTableNumber">Table</h4>
<div id="qrContainer" class="mb-3">
<!-- QR Image will be loaded here -->
</div>
<p class="small text-muted mb-0">Scan to Order</p>
<div class="mt-2 small text-break text-muted" id="qrLink" style="font-size: 0.7rem;"></div>
</div>
<div class="modal-footer justify-content-center">
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Close</button>
<button type="button" class="btn btn-primary" onclick="printQR()">
<div class="modal-body text-center p-5">
<h4 id="qrTableNumber" class="mb-4 fw-bold"></h4>
<div id="qrcode" class="mb-4 d-flex justify-content-center"></div>
<p class="text-muted small">Scan this QR to order from this table</p>
<div class="d-grid gap-2">
<button type="button" class="btn btn-outline-primary" onclick="downloadQR()">
<i class="bi bi-download"></i> Download QR
</button>
<button type="button" class="btn btn-outline-secondary" onclick="printQR()">
<i class="bi bi-printer"></i> Print
</button>
</div>
</div>
</div>
</div>
</div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/qrcodejs/1.0.0/qrcode.min.js"></script>
<script>
let qrGenerator = null;
const baseUrl = '<?= $baseUrl ?>';
function prepareAddForm() {
document.getElementById('tableModalTitle').innerText = 'Add New Table';
document.getElementById('tableAction').value = 'add_table';
document.getElementById('tableForm').reset();
document.getElementById('tableId').value = '';
document.getElementById('tableNumber').value = '';
document.getElementById('tableAreaId').value = '';
document.getElementById('tableCapacity').value = '2';
document.getElementById('tableStatus').value = 'available';
}
function prepareEditForm(table) {
if (!table) return;
document.getElementById('tableModalTitle').innerText = 'Edit Table';
document.getElementById('tableAction').value = 'edit_table';
document.getElementById('tableId').value = table.id;
document.getElementById('tableNumber').value = table.table_number || '';
document.getElementById('tableAreaId').value = table.area_id || '';
document.getElementById('tableCapacity').value = table.capacity || 2;
document.getElementById('tableCapacity').value = table.capacity || '2';
document.getElementById('tableStatus').value = table.status || 'available';
}
function showTableQR(id, number) {
const baseUrl = '<?= $baseUrl ?>';
const qorderUrl = baseUrl + '/qorder.php?table_id=' + id;
const qrCodeUrl = "https://api.qrserver.com/v1/create-qr-code/?size=300x300&data=" + encodeURIComponent(qorderUrl);
document.getElementById('qrTableNumber').innerText = 'Table ' + number;
document.getElementById('qrContainer').innerHTML = '<img src="' + qrCodeUrl + '" alt="QR Code" class="img-fluid shadow-sm" style="max-width: 200px;">';
document.getElementById('qrLink').innerText = qorderUrl;
const qrContainer = document.getElementById('qrcode');
qrContainer.innerHTML = '';
const modal = new bootstrap.Modal(document.getElementById('qrModal'));
modal.show();
const url = baseUrl + '/qorder.php?table=' + id;
qrGenerator = new QRCode(qrContainer, {
text: url,
width: 256,
height: 256,
colorDark: "#000000",
colorLight: "#ffffff",
correctLevel: QRCode.CorrectLevel.H
});
new bootstrap.Modal(document.getElementById('qrModal')).show();
}
function downloadQR() {
const img = document.querySelector('#qrcode img');
if (img) {
const link = document.createElement('a');
link.download = 'table-qr.png';
link.href = img.src;
link.click();
}
}
function printQR() {
const content = document.getElementById('printableQR').innerHTML;
const printWindow = window.open('', '_blank');
printWindow.document.write('<html><head><title>Print Table QR</title>');
printWindow.document.write('<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet">');
printWindow.document.write('<style>body { text-align: center; padding: 50px; }</style>');
printWindow.document.write('</head><body>');
printWindow.document.write(content);
printWindow.document.write('</body></html>');
printWindow.document.close();
setTimeout(() => {
printWindow.print();
printWindow.close();
}, 500);
const img = document.querySelector('#qrcode img');
if (!img) return;
const win = window.open('', '_blank');
win.document.write('<html><body style="text-align:center;padding:50px;">');
win.document.write('<h1>' + document.getElementById('qrTableNumber').innerText + '</h1>');
win.document.write('<img src="' + img.src + '" style="width:300px;">');
win.document.write('<p style="font-family:sans-serif;margin-top:20px;">Scan to Order</p>');
win.document.write('</body></html>');
win.document.close();
win.print();
win.close();
}
</script>

View File

@ -1,5 +1,6 @@
<?php
require_once __DIR__ . "/../includes/functions.php";
require_once __DIR__ . "/../db/config.php";
require_permission("user_groups_view");
require_once __DIR__ . '/../db/config.php';
$pdo = db();

View File

@ -1,5 +1,6 @@
<?php
require_once __DIR__ . "/../includes/functions.php";
require_once __DIR__ . "/../db/config.php";
require_permission("users_view");
require_once __DIR__ . '/../db/config.php';
$pdo = db();

View File

@ -467,7 +467,7 @@ document.addEventListener('DOMContentLoaded', () => {
const statusClass = table.is_occupied ? "btn-outline-danger" : "btn-outline-success";
col.innerHTML = `
<button class=\"btn ${statusClass} w-100 py-3 rounded-3 position-relative\" onclick=\"selectTable(${table.id}, '${table.name}')\">
<div class=\"fw-bold\">${table.name}</div>
<div class=\"fw-bold">${table.name}</div>
<div class=\"small\" style=\"font-size: 0.7rem;\">Cap: ${table.capacity}</div>
${table.is_occupied ? '<span class="position-absolute top-0 start-100 translate-middle p-1 bg-danger border border-light rounded-circle" title="Occupied"></span>' : ''}
</button>
@ -516,7 +516,7 @@ document.addEventListener('DOMContentLoaded', () => {
price: parseFloat(product.price), base_price: parseFloat(product.price),
hasVariants: false, quantity: 1, variant_id: null, variant_name: null,
is_loyalty: parseInt(product.is_loyalty) === 1,
vat_percent: parseFloat(product.vat_percent || 0)
vat_percent: parseFloat(product.vat_percent || settings.vat_rate || 0)
});
}
};
@ -540,7 +540,7 @@ document.addEventListener('DOMContentLoaded', () => {
price: finalPrice, base_price: parseFloat(product.price),
hasVariants: true, quantity: 1, variant_id: v.id, variant_name: v.name,
is_loyalty: parseInt(product.is_loyalty) === 1,
vat_percent: parseFloat(product.vat_percent || 0)
vat_percent: parseFloat(product.vat_percent || settings.vat_rate || 0)
});
variantSelectionModal.hide();
};
@ -587,7 +587,8 @@ document.addEventListener('DOMContentLoaded', () => {
cartItemsContainer.innerHTML = `<div class="text-center text-muted mt-5"><i class="bi bi-basket3 fs-1 text-light"></i><p class="mt-2">${_t('cart_empty')}</p></div>`;
if (cartSubtotal) cartSubtotal.innerText = formatCurrency(0);
if (cartVatAmount) cartVatAmount.innerText = formatCurrency(0);
if (cartVatRow) cartVatRow.classList.add('d-none');
// Always show VAT row even if 0
if (cartVatRow) cartVatRow.classList.remove('d-none');
if (cartTotalPrice) cartTotalPrice.innerText = formatCurrency(0);
if (quickOrderBtn) { quickOrderBtn.disabled = true; quickOrderBtn.innerText = _t('quick_pay'); }
if (placeOrderBtn) { placeOrderBtn.disabled = true; placeOrderBtn.innerText = _t('save_bill'); }
@ -629,10 +630,8 @@ document.addEventListener('DOMContentLoaded', () => {
if (cartSubtotal) cartSubtotal.innerText = formatCurrency(subtotal);
if (cartVatAmount) cartVatAmount.innerText = formatCurrency(totalVat);
if (cartVatRow) {
if (totalVat > 0) cartVatRow.classList.remove('d-none');
else cartVatRow.classList.add('d-none');
}
// Always show VAT row
if (cartVatRow) cartVatRow.classList.remove('d-none');
if (cartVatInput) cartVatInput.value = totalVat.toFixed(3);
const total = subtotal + totalVat;
@ -806,6 +805,8 @@ document.addEventListener('DOMContentLoaded', () => {
const logoHtml = settings.logo_url ? `<img src="${BASE_URL}${settings.logo_url}" style="max-height: 80px; max-width: 150px; margin-bottom: 10px;">` : '';
const vatRate = settings.vat_rate || 0;
const html = `
<html dir="ltr">
<head>
@ -860,7 +861,7 @@ document.addEventListener('DOMContentLoaded', () => {
<div class="totals">
<table style="width: 100%">
<tr><td>Subtotal / ${tr['Subtotal']}</td><td style="text-align: right">${formatCurrency(data.subtotal)}</td></tr>
${Math.abs(data.vat) > 0 ? `<tr><td>VAT / ${tr['VAT']}</td><td style="text-align: right">${formatCurrency(Math.abs(data.vat))}</td></tr>` : ''}
<tr><td>VAT (${vatRate}%) / ${tr['VAT']}</td><td style="text-align: right">${formatCurrency(Math.abs(data.vat))}</td></tr>
<tr style="font-weight: bold; font-size: 18px;"><td style="padding-top: 10px;">TOTAL / ${tr['TOTAL']}</td><td style="text-align: right; padding-top: 10px;">${formatCurrency(data.total)}</td></tr>
</table>
</div>

45
db/migrate.php Normal file
View File

@ -0,0 +1,45 @@
<?php
require_once __DIR__ . '/config.php';
$pdo = db();
// Ensure migrations table exists
$pdo->exec("CREATE TABLE IF NOT EXISTS migrations (
id INT AUTO_INCREMENT PRIMARY KEY,
migration_name VARCHAR(255) NOT NULL UNIQUE,
applied_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
)");
$migrations_folder = array_diff(scandir(__DIR__ . '/migrations'), ['.', '..']);
sort($migrations_folder);
$applied_migrations = $pdo->query("SELECT migration_name FROM migrations")->fetchAll(PDO::FETCH_COLUMN);
foreach ($migrations_folder as $migration) {
if (!in_array($migration, $applied_migrations)) {
echo "Applying migration: $migration\n";
$sql = file_get_contents(__DIR__ . '/migrations/' . $migration);
try {
if (!empty(trim($sql))) {
$pdo->exec($sql);
}
$stmt = $pdo->prepare("INSERT INTO migrations (migration_name) VALUES (?)");
$stmt->execute([$migration]);
echo "Successfully applied $migration\n";
} catch (Exception $e) {
echo "Error applying $migration: " . $e->getMessage() . "\n";
// If it's a "Duplicate column name" error, we can assume it was applied manually and just record it
if (strpos($e->getMessage(), 'Duplicate column name') !== false || strpos($e->getMessage(), 'already exists') !== false) {
echo "Column/Table already exists, recording as applied.\n";
$stmt = $pdo->prepare("INSERT INTO migrations (migration_name) VALUES (?)");
$stmt->execute([$migration]);
} else {
// For other errors, we might want to stop or continue
echo "Stopping migrations due to error.\n";
break;
}
}
}
}
echo "Migration process finished.\n";

View File

@ -108,6 +108,7 @@ function t($key, $lang = null) {
'orders' => 'Orders',
'kitchen' => 'Kitchen',
'switch' => 'Switch',
'are_you_sure' => 'Are you sure?',
]
];

View File

@ -75,6 +75,8 @@ $loyalty_settings = $loyalty_stmt->fetch(PDO::FETCH_ASSOC);
if (!$loyalty_settings) {
$loyalty_settings = ['is_enabled' => 0, 'points_per_order' => 0, 'points_for_free_meal' => 0];
}
$vat_rate = (float)($settings['vat_rate'] ?? 0);
?>
<!doctype html>
<html lang="en" dir="ltr">
@ -327,7 +329,7 @@ if (!$loyalty_settings) {
<span class="fw-bold small" id="cart-subtotal"><?= format_currency(0) ?></span>
</div>
<div class="d-flex justify-content-between mb-1" id="cart-vat-row">
<span class="text-muted small">VAT</span>
<span class="text-muted small">VAT (<?= $vat_rate ?>%)</span>
<span class="fw-bold small" id="cart-vat-amount"><?= format_currency(0) ?></span>
<input type="hidden" id="cart-vat-input" value="0">
</div>

File diff suppressed because it is too large Load Diff