adding loyalty
This commit is contained in:
parent
41c061c036
commit
8d434a18ee
31
db/migrations/20260218_modern_loyalty_system.sql
Normal file
31
db/migrations/20260218_modern_loyalty_system.sql
Normal file
@ -0,0 +1,31 @@
|
||||
-- Modern Loyalty System Migration
|
||||
ALTER TABLE customers
|
||||
ADD COLUMN IF NOT EXISTS loyalty_tier ENUM('bronze', 'silver', 'gold') DEFAULT 'bronze',
|
||||
ADD COLUMN IF NOT EXISTS total_spent DECIMAL(15, 3) DEFAULT 0.000;
|
||||
|
||||
CREATE TABLE IF NOT EXISTS loyalty_transactions (
|
||||
id INT AUTO_INCREMENT PRIMARY KEY,
|
||||
customer_id INT NOT NULL,
|
||||
transaction_id INT NULL,
|
||||
points_change DECIMAL(15, 3) NOT NULL,
|
||||
transaction_type ENUM('earned', 'redeemed', 'adjustment') NOT NULL,
|
||||
description TEXT,
|
||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
FOREIGN KEY (customer_id) REFERENCES customers(id) ON DELETE CASCADE
|
||||
);
|
||||
|
||||
-- Update existing total_spent based on previous transactions if possible
|
||||
UPDATE customers c
|
||||
SET c.total_spent = (
|
||||
SELECT COALESCE(SUM(total_with_vat), 0)
|
||||
FROM invoices
|
||||
WHERE customer_id = c.id AND type = 'sale'
|
||||
);
|
||||
|
||||
-- Initial tier update based on existing spent amount
|
||||
UPDATE customers
|
||||
SET loyalty_tier = CASE
|
||||
WHEN total_spent >= 1500 THEN 'gold'
|
||||
WHEN total_spent >= 500 THEN 'silver'
|
||||
ELSE 'bronze'
|
||||
END;
|
||||
317
index.php
317
index.php
@ -39,6 +39,58 @@ function getPromotionalPrice($item) {
|
||||
return $price;
|
||||
}
|
||||
|
||||
function getLoyaltyMultiplier($tier) {
|
||||
return match($tier) {
|
||||
'silver' => 1.2,
|
||||
'gold' => 1.5,
|
||||
default => 1.0,
|
||||
};
|
||||
}
|
||||
|
||||
function updateCustomerLoyalty($customer_id, $spent_amount, $points_earned, $loyalty_redeemed_value, $invoice_id = null) {
|
||||
$db = db();
|
||||
|
||||
// Fetch settings for dynamic rates
|
||||
$settings_res = $db->query("SELECT * FROM settings WHERE `key` IN ('loyalty_enabled', 'loyalty_redeem_points_per_unit')")->fetchAll(PDO::FETCH_ASSOC);
|
||||
$settings = [];
|
||||
foreach ($settings_res as $s) $settings[$s['key']] = $s['value'];
|
||||
|
||||
if (($settings['loyalty_enabled'] ?? '0') !== '1') return; // System disabled
|
||||
|
||||
$redeem_rate = (float)($settings['loyalty_redeem_points_per_unit'] ?? 100);
|
||||
$points_redeemed = (float)$loyalty_redeemed_value * $redeem_rate;
|
||||
|
||||
// Update points and total_spent
|
||||
$stmt = $db->prepare("UPDATE customers SET loyalty_points = loyalty_points - ? + ?, total_spent = total_spent + ? WHERE id = ?");
|
||||
$stmt->execute([(float)$points_redeemed, (float)$points_earned, (float)$spent_amount, $customer_id]);
|
||||
|
||||
// Fetch updated total_spent to check for tier upgrade
|
||||
$stmt = $db->prepare("SELECT total_spent, loyalty_tier FROM customers WHERE id = ?");
|
||||
$stmt->execute([$customer_id]);
|
||||
$customer = $stmt->fetch();
|
||||
|
||||
$new_tier = 'bronze';
|
||||
if ($customer['total_spent'] >= 1500) $new_tier = 'gold';
|
||||
elseif ($customer['total_spent'] >= 500) $new_tier = 'silver';
|
||||
|
||||
if ($new_tier !== $customer['loyalty_tier']) {
|
||||
$stmt = $db->prepare("UPDATE customers SET loyalty_tier = ? WHERE id = ?");
|
||||
$stmt->execute([$new_tier, $customer_id]);
|
||||
}
|
||||
|
||||
// Log Earned Points
|
||||
if ($points_earned > 0) {
|
||||
$stmt = $db->prepare("INSERT INTO loyalty_transactions (customer_id, transaction_id, points_change, transaction_type, description) VALUES (?, ?, ?, 'earned', ?)");
|
||||
$stmt->execute([$customer_id, $invoice_id, $points_earned, "Earned from transaction #$invoice_id (Tier: " . strtoupper($customer['loyalty_tier']) . ")"]);
|
||||
}
|
||||
|
||||
// Log Redeemed Points
|
||||
if ($points_redeemed > 0) {
|
||||
$stmt = $db->prepare("INSERT INTO loyalty_transactions (customer_id, transaction_id, points_change, transaction_type, description) VALUES (?, ?, ?, 'redeemed', ?)");
|
||||
$stmt->execute([$customer_id, $invoice_id, -$points_redeemed, "Redeemed $points_redeemed pts (Value: " . number_format((float)$loyalty_redeemed_value, 3) . " OMR) in transaction #$invoice_id"]);
|
||||
}
|
||||
}
|
||||
|
||||
function numberToWords($num) {
|
||||
$num = (int)$num;
|
||||
if ($num === 0) return "Zero";
|
||||
@ -275,6 +327,13 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
||||
$loyalty_redeemed = (float)($_POST['loyalty_redeemed'] ?? 0);
|
||||
$items = json_decode($_POST['items'] ?? '[]', true);
|
||||
|
||||
// Fetch settings
|
||||
$settings_res = $db->query("SELECT * FROM settings WHERE `key` IN ('loyalty_enabled', 'loyalty_points_per_unit')")->fetchAll(PDO::FETCH_ASSOC);
|
||||
$app_settings = [];
|
||||
foreach ($settings_res as $s) $app_settings[$s['key']] = $s['value'];
|
||||
$loyalty_enabled = ($app_settings['loyalty_enabled'] ?? '0') === '1';
|
||||
$points_per_unit = (float)($app_settings['loyalty_points_per_unit'] ?? 1);
|
||||
|
||||
if (empty($items)) {
|
||||
throw new Exception("Cart is empty");
|
||||
}
|
||||
@ -282,8 +341,15 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
||||
$net_amount = (float)($total_amount - $discount_amount - $loyalty_redeemed);
|
||||
if ($net_amount < 0) $net_amount = 0;
|
||||
|
||||
// Loyalty Calculation: 1 point per 1 OMR spent on net amount
|
||||
$loyalty_earned = floor($net_amount);
|
||||
// Loyalty Calculation: Based on Tier Multiplier
|
||||
$loyalty_multiplier = 1.0;
|
||||
if ($customer_id && $loyalty_enabled) {
|
||||
$stmtTier = $db->prepare("SELECT loyalty_tier FROM customers WHERE id = ?");
|
||||
$stmtTier->execute([$customer_id]);
|
||||
$tier = $stmtTier->fetchColumn() ?: 'bronze';
|
||||
$loyalty_multiplier = getLoyaltyMultiplier($tier);
|
||||
}
|
||||
$loyalty_earned = $loyalty_enabled ? floor($net_amount * $points_per_unit * $loyalty_multiplier) : 0;
|
||||
|
||||
// Check if credit is used for walk-in or exceeds limit
|
||||
$credit_total = 0;
|
||||
@ -371,8 +437,14 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
||||
}
|
||||
}
|
||||
|
||||
$stmt = $db->prepare("UPDATE customers SET loyalty_points = loyalty_points - ? + ?, balance = balance - ? WHERE id = ?");
|
||||
$stmt->execute([(float)$loyalty_redeemed, (float)$loyalty_earned, (float)$credit_total, $customer_id]);
|
||||
// New Modern Loyalty Logic
|
||||
updateCustomerLoyalty($customer_id, (float)$net_amount, (float)$loyalty_earned, (float)$loyalty_redeemed, (int)$invoice_id);
|
||||
|
||||
// Update Balance separately if credit used
|
||||
if ($credit_total > 0) {
|
||||
$stmt = $db->prepare("UPDATE customers SET balance = balance - ? WHERE id = ?");
|
||||
$stmt->execute([(float)$credit_total, $customer_id]);
|
||||
}
|
||||
}
|
||||
|
||||
// Add Payments
|
||||
@ -2129,6 +2201,26 @@ switch ($page) {
|
||||
$data['payroll'] = db()->query("SELECT p.*, e.name as emp_name FROM hr_payroll p JOIN hr_employees e ON p.employee_id = e.id WHERE p.payroll_month = $month AND p.payroll_year = $year ORDER BY p.id DESC")->fetchAll();
|
||||
$data['employees'] = db()->query("SELECT id, name, salary FROM hr_employees WHERE status = 'active' ORDER BY name ASC")->fetchAll();
|
||||
break;
|
||||
case 'loyalty_history':
|
||||
$where = ["1=1"];
|
||||
$params = [];
|
||||
if (!empty($_GET['customer_id'])) {
|
||||
$where[] = "lt.customer_id = ?";
|
||||
$params[] = (int)$_GET['customer_id'];
|
||||
}
|
||||
if (!empty($_GET['type'])) {
|
||||
$where[] = "lt.transaction_type = ?";
|
||||
$params[] = $_GET['type'];
|
||||
}
|
||||
$whereSql = implode(" AND ", $where);
|
||||
$stmt = db()->prepare("SELECT lt.*, c.name as customer_name, c.loyalty_tier, c.loyalty_points
|
||||
FROM loyalty_transactions lt
|
||||
JOIN customers c ON lt.customer_id = c.id
|
||||
WHERE $whereSql
|
||||
ORDER BY lt.created_at DESC");
|
||||
$stmt->execute($params);
|
||||
$data['loyalty_transactions'] = $stmt->fetchAll();
|
||||
break;
|
||||
case 'devices':
|
||||
$data['devices'] = db()->query("SELECT * FROM hr_biometric_devices ORDER BY id DESC")->fetchAll();
|
||||
break;
|
||||
@ -2312,6 +2404,9 @@ $projectDescription = $_SERVER['PROJECT_DESCRIPTION'] ?? 'Accounting System';
|
||||
<a href="index.php?page=low_stock_report" class="nav-link <?= isset($_GET['page']) && $_GET['page'] === 'low_stock_report' ? 'active' : '' ?>">
|
||||
<i class="bi bi-graph-down-arrow"></i> <span data-en="Low Stock Report" data-ar="تقرير نواقص المخزون">Low Stock Report</span>
|
||||
</a>
|
||||
<a href="index.php?page=loyalty_history" class="nav-link <?= isset($_GET['page']) && $_GET['page'] === 'loyalty_history' ? 'active' : '' ?>">
|
||||
<i class="bi bi-star"></i> <span data-en="Loyalty History" data-ar="سجل الولاء">Loyalty History</span>
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<!-- Configuration Section -->
|
||||
@ -2381,6 +2476,7 @@ $projectDescription = $_SERVER['PROJECT_DESCRIPTION'] ?? 'Accounting System';
|
||||
'hr_attendance' => ['en' => 'HR Attendance', 'ar' => 'حضور الموارد البشرية'],
|
||||
'hr_payroll' => ['en' => 'HR Payroll', 'ar' => 'رواتب الموارد البشرية'],
|
||||
'cashflow_report' => ['en' => 'Cashflow Statement', 'ar' => 'قائمة التدفقات النقدية'],
|
||||
'loyalty_history' => ['en' => 'Loyalty History', 'ar' => 'سجل الولاء'],
|
||||
];
|
||||
$currTitle = $titles[$page] ?? $titles['dashboard'];
|
||||
?>
|
||||
@ -3200,6 +3296,90 @@ $projectDescription = $_SERVER['PROJECT_DESCRIPTION'] ?? 'Accounting System';
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<?php elseif ($page === 'loyalty_history'): ?>
|
||||
<div class="card p-4">
|
||||
<div class="d-flex justify-content-between align-items-center mb-4">
|
||||
<h5 class="m-0" data-en="Loyalty Transaction History" data-ar="سجل عمليات الولاء">Loyalty Transaction History</h5>
|
||||
<button class="btn btn-outline-primary btn-sm d-print-none" onclick="window.print()">
|
||||
<i class="bi bi-printer"></i> <span data-en="Print" data-ar="طباعة">Print</span>
|
||||
</button>
|
||||
</div>
|
||||
<div class="bg-light p-3 rounded mb-4 d-print-none">
|
||||
<form method="GET" class="row g-3 align-items-end">
|
||||
<input type="hidden" name="page" value="loyalty_history">
|
||||
<div class="col-md-4">
|
||||
<label class="form-label small fw-bold" data-en="Customer" data-ar="العميل">Customer</label>
|
||||
<select name="customer_id" class="form-select select2">
|
||||
<option value="">All Customers</option>
|
||||
<?php foreach ($data['customers_list'] as $c): ?>
|
||||
<option value="<?= $c['id'] ?>" <?= (($_GET['customer_id'] ?? '') == $c['id']) ? 'selected' : '' ?>><?= htmlspecialchars($c['name']) ?></option>
|
||||
<?php endforeach; ?>
|
||||
</select>
|
||||
</div>
|
||||
<div class="col-md-3">
|
||||
<label class="form-label small fw-bold" data-en="Type" data-ar="النوع">Type</label>
|
||||
<select name="type" class="form-select">
|
||||
<option value="">All Types</option>
|
||||
<option value="earned" <?= (($_GET['type'] ?? '') == 'earned') ? 'selected' : '' ?>>Earned</option>
|
||||
<option value="redeemed" <?= (($_GET['type'] ?? '') == 'redeemed') ? 'selected' : '' ?>>Redeemed</option>
|
||||
<option value="adjustment" <?= (($_GET['type'] ?? '') == 'adjustment') ? 'selected' : '' ?>>Adjustment</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="col-md-2">
|
||||
<button type="submit" class="btn btn-primary w-100">
|
||||
<i class="bi bi-filter"></i> <span data-en="Filter" data-ar="تصفية">Filter</span>
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
<div class="table-responsive">
|
||||
<table class="table table-hover align-middle">
|
||||
<thead>
|
||||
<tr>
|
||||
<th data-en="Date" data-ar="التاريخ">Date</th>
|
||||
<th data-en="Customer" data-ar="العميل">Customer</th>
|
||||
<th data-en="Tier" data-ar="الفئة">Tier</th>
|
||||
<th data-en="Type" data-ar="النوع">Type</th>
|
||||
<th data-en="Points" data-ar="النقاط">Points</th>
|
||||
<th data-en="Description" data-ar="الوصف">Description</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<?php if (empty($data['loyalty_transactions'])): ?>
|
||||
<tr><td colspan="6" class="text-center py-4 text-muted">No transactions found.</td></tr>
|
||||
<?php endif; ?>
|
||||
<?php foreach ($data['loyalty_transactions'] as $lt): ?>
|
||||
<tr>
|
||||
<td><?= date('Y-m-d H:i', strtotime($lt['created_at'])) ?></td>
|
||||
<td>
|
||||
<div class="fw-bold"><?= htmlspecialchars($lt['customer_name']) ?></div>
|
||||
<div class="smaller text-muted">Current Balance: <?= number_format($lt['loyalty_points'], 0) ?> pts</div>
|
||||
</td>
|
||||
<td>
|
||||
<?php
|
||||
$tier = $lt['loyalty_tier'];
|
||||
$badge = ($tier === 'gold') ? 'bg-warning text-dark' : (($tier === 'silver') ? 'bg-info text-dark' : 'bg-secondary');
|
||||
?>
|
||||
<span class="badge text-uppercase <?= $badge ?>"><?= $tier ?></span>
|
||||
</td>
|
||||
<td>
|
||||
<?php
|
||||
$type = $lt['transaction_type'];
|
||||
$typeBadge = ($type === 'earned') ? 'bg-success' : (($type === 'redeemed') ? 'bg-danger' : 'bg-info');
|
||||
?>
|
||||
<span class="badge <?= $typeBadge ?>"><?= ucfirst($type) ?></span>
|
||||
</td>
|
||||
<td class="fw-bold <?= (float)$lt['points_change'] > 0 ? 'text-success' : 'text-danger' ?>">
|
||||
<?= (float)$lt['points_change'] > 0 ? '+' : '' ?><?= number_format($lt['points_change'], 0) ?>
|
||||
</td>
|
||||
<td class="small"><?= htmlspecialchars($lt['description']) ?></td>
|
||||
</tr>
|
||||
<?php endforeach; ?>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<?php elseif ($page === 'pos'): ?>
|
||||
<?php
|
||||
$products_raw = db()->query("SELECT * FROM stock_items WHERE stock_quantity > 0 ORDER BY name_en ASC")->fetchAll(PDO::FETCH_ASSOC);
|
||||
@ -3270,17 +3450,32 @@ $projectDescription = $_SERVER['PROJECT_DESCRIPTION'] ?? 'Accounting System';
|
||||
<select id="posCustomer" class="form-select form-select-sm" onchange="cart.onCustomerChange()">
|
||||
<option value="">Walk-in Customer</option>
|
||||
<?php foreach ($customers as $c): ?>
|
||||
<option value="<?= $c['id'] ?>"><?= htmlspecialchars($c['name']) ?></option>
|
||||
<option value="<?= $c['id'] ?>"
|
||||
data-points="<?= $c['loyalty_points'] ?>"
|
||||
data-tier="<?= $c['loyalty_tier'] ?>"
|
||||
data-multiplier="<?= getLoyaltyMultiplier($c['loyalty_tier'] ?? 'bronze') ?>"
|
||||
data-spent="<?= $c['total_spent'] ?>">
|
||||
<?= htmlspecialchars($c['name']) ?>
|
||||
</option>
|
||||
<?php endforeach; ?>
|
||||
</select>
|
||||
<button class="btn btn-sm btn-primary" data-bs-toggle="modal" data-bs-target="#addCustomerModal"><i class="bi bi-plus"></i></button>
|
||||
</div>
|
||||
<div id="loyaltyDisplay" class="smaller text-success mt-1" style="display:none">
|
||||
Points: <span id="customerPoints">0</span>
|
||||
<div class="form-check form-switch d-inline-block ms-2">
|
||||
<input class="form-check-input" type="checkbox" id="redeemLoyalty" onchange="cart.render()">
|
||||
<label class="form-check-label" for="redeemLoyalty">Redeem</label>
|
||||
<div id="loyaltyDisplay" class="mt-2 p-2 rounded bg-light border border-primary-subtle" style="display:none">
|
||||
<div class="d-flex justify-content-between align-items-center mb-1">
|
||||
<div>
|
||||
<span id="tierBadge" class="badge text-uppercase">Bronze</span>
|
||||
<span class="fw-bold ms-1 text-primary"><span id="customerPoints">0</span> pts</span>
|
||||
</div>
|
||||
<div class="form-check form-switch">
|
||||
<input class="form-check-input" type="checkbox" id="redeemLoyalty" onchange="cart.render()">
|
||||
<label class="form-check-label small fw-bold" for="redeemLoyalty">Redeem</label>
|
||||
</div>
|
||||
</div>
|
||||
<div class="progress" style="height: 4px;">
|
||||
<div id="tierProgress" class="progress-bar bg-primary" role="progressbar" style="width: 0%"></div>
|
||||
</div>
|
||||
<div id="nextTierInfo" class="smaller text-muted mt-1">Spend more to unlock Silver</div>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
@ -3324,6 +3519,11 @@ $projectDescription = $_SERVER['PROJECT_DESCRIPTION'] ?? 'Accounting System';
|
||||
customerPoints: 0,
|
||||
selectedPaymentMethod: 'cash',
|
||||
payments: [],
|
||||
loyaltySettings: {
|
||||
enabled: <?= json_encode($data['settings']['loyalty_enabled'] ?? '0') ?>,
|
||||
pointsPerUnit: parseFloat(<?= json_encode($data['settings']['loyalty_points_per_unit'] ?? '1') ?>),
|
||||
redeemPointsPerUnit: parseFloat(<?= json_encode($data['settings']['loyalty_redeem_points_per_unit'] ?? '100') ?>)
|
||||
},
|
||||
allCreditCustomers: <?php
|
||||
$custData = [];
|
||||
foreach ($customers as $c) {
|
||||
@ -3369,24 +3569,49 @@ $projectDescription = $_SERVER['PROJECT_DESCRIPTION'] ?? 'Accounting System';
|
||||
if (loyaltyDisplay) loyaltyDisplay.style.display = 'none';
|
||||
this.render();
|
||||
},
|
||||
async onCustomerChange() {
|
||||
const id = document.getElementById('posCustomer').value;
|
||||
onCustomerChange() {
|
||||
const select = document.getElementById('posCustomer');
|
||||
const option = select.options[select.selectedIndex];
|
||||
const display = document.getElementById('loyaltyDisplay');
|
||||
if (!id) {
|
||||
display.style.display = 'none';
|
||||
|
||||
if (!select.value || this.loyaltySettings.enabled !== '1') {
|
||||
if (display) display.style.display = 'none';
|
||||
this.customerPoints = 0;
|
||||
document.getElementById('redeemLoyalty').checked = false;
|
||||
this.customerTier = 'bronze';
|
||||
this.customerMultiplier = 1.0;
|
||||
const redeemSwitch = document.getElementById('redeemLoyalty');
|
||||
if (redeemSwitch) redeemSwitch.checked = false;
|
||||
this.render();
|
||||
return;
|
||||
}
|
||||
try {
|
||||
const resp = await fetch(`index.php?action=get_customer_loyalty&customer_id=${id}`);
|
||||
const res = await resp.json();
|
||||
this.customerPoints = res.points || 0;
|
||||
document.getElementById('customerPoints').innerText = this.customerPoints.toFixed(3);
|
||||
display.style.display = 'block';
|
||||
this.render();
|
||||
} catch (err) { console.error(err); }
|
||||
|
||||
this.customerPoints = parseFloat(option.dataset.points) || 0;
|
||||
this.customerTier = option.dataset.tier || 'bronze';
|
||||
this.customerMultiplier = parseFloat(option.dataset.multiplier) || 1.0;
|
||||
const spent = parseFloat(option.dataset.spent) || 0;
|
||||
|
||||
document.getElementById('customerPoints').innerText = Math.floor(this.customerPoints);
|
||||
const badge = document.getElementById('tierBadge');
|
||||
badge.innerText = this.customerTier;
|
||||
badge.className = 'badge text-uppercase ' + (this.customerTier === 'gold' ? 'bg-warning text-dark' : (this.customerTier === 'silver' ? 'bg-info text-dark' : 'bg-secondary'));
|
||||
|
||||
const progressBar = document.getElementById('tierProgress');
|
||||
const nextTierInfo = document.getElementById('nextTierInfo');
|
||||
let progress = 0;
|
||||
if (this.customerTier === 'bronze') {
|
||||
progress = (spent / 500) * 100;
|
||||
nextTierInfo.innerText = `Spend ${(500 - spent).toFixed(3)} OMR more for Silver (1.2x points)`;
|
||||
} else if (this.customerTier === 'silver') {
|
||||
progress = ((spent - 500) / 1000) * 100;
|
||||
nextTierInfo.innerText = `Spend ${(1500 - spent).toFixed(3)} OMR more for Gold (1.5x points)`;
|
||||
} else {
|
||||
progress = 100;
|
||||
nextTierInfo.innerText = 'You are a Gold member! (1.5x points)';
|
||||
}
|
||||
progressBar.style.width = Math.min(100, progress) + '%';
|
||||
|
||||
display.style.display = 'block';
|
||||
this.render();
|
||||
},
|
||||
async applyDiscount() {
|
||||
const code = document.getElementById('discountCode').value.trim();
|
||||
@ -3549,19 +3774,27 @@ $projectDescription = $_SERVER['PROJECT_DESCRIPTION'] ?? 'Accounting System';
|
||||
}
|
||||
}
|
||||
|
||||
let loyaltyRedeemed = 0;
|
||||
let loyaltyRedeemedValue = 0;
|
||||
const redeemSwitch = document.getElementById('redeemLoyalty');
|
||||
if (redeemSwitch && redeemSwitch.checked) {
|
||||
loyaltyRedeemed = Math.min(subtotal - discountAmount, this.customerPoints);
|
||||
const maxRedeemValue = subtotal - discountAmount;
|
||||
const availableRedeemValue = this.customerPoints / 100;
|
||||
loyaltyRedeemedValue = Math.min(maxRedeemValue, availableRedeemValue);
|
||||
}
|
||||
|
||||
const total = subtotal - discountAmount - loyaltyRedeemed;
|
||||
const total = subtotal - discountAmount - loyaltyRedeemedValue;
|
||||
const pointsToEarn = Math.floor(total * (this.customerMultiplier || 1.0));
|
||||
|
||||
document.getElementById('posSubtotal').innerText = 'OMR ' + subtotal.toFixed(3);
|
||||
|
||||
let totalHtml = '';
|
||||
if (discountAmount > 0) totalHtml += `<div class="smaller text-danger">- Disc: OMR ${discountAmount.toFixed(3)}</div>`;
|
||||
if (loyaltyRedeemed > 0) totalHtml += `<div class="smaller text-success">- Loyalty: OMR ${loyaltyRedeemed.toFixed(3)}</div>`;
|
||||
if (loyaltyRedeemedValue > 0) totalHtml += `<div class="smaller text-success">- Loyalty: OMR ${loyaltyRedeemedValue.toFixed(3)}</div>`;
|
||||
|
||||
if (document.getElementById('posCustomer').value) {
|
||||
totalHtml += `<div class="smaller text-info">+ Earn: ${pointsToEarn} pts</div>`;
|
||||
}
|
||||
|
||||
totalHtml += 'OMR ' + total.toFixed(3);
|
||||
|
||||
document.getElementById('posTotal').innerHTML = totalHtml;
|
||||
@ -3580,8 +3813,8 @@ $projectDescription = $_SERVER['PROJECT_DESCRIPTION'] ?? 'Accounting System';
|
||||
discountAmount = this.discount.type === 'percentage' ? subtotal * (parseFloat(this.discount.value) / 100) : parseFloat(this.discount.value);
|
||||
}
|
||||
const redeemSwitch = document.getElementById('redeemLoyalty');
|
||||
let loyaltyRedeemed = (redeemSwitch && redeemSwitch.checked) ? Math.min(subtotal - discountAmount, this.customerPoints) : 0;
|
||||
const total = subtotal - discountAmount - loyaltyRedeemed;
|
||||
let loyaltyRedeemedValue = (redeemSwitch && redeemSwitch.checked) ? Math.min(subtotal - discountAmount, this.customerPoints / 100) : 0;
|
||||
const total = subtotal - discountAmount - loyaltyRedeemedValue;
|
||||
|
||||
this.payments = [];
|
||||
this.renderPayments();
|
||||
@ -3652,8 +3885,8 @@ $projectDescription = $_SERVER['PROJECT_DESCRIPTION'] ?? 'Accounting System';
|
||||
discountAmount = this.discount.type === 'percentage' ? subtotal * (parseFloat(this.discount.value) / 100) : parseFloat(this.discount.value);
|
||||
}
|
||||
const redeemSwitch = document.getElementById('redeemLoyalty');
|
||||
let loyaltyRedeemed = (redeemSwitch && redeemSwitch.checked) ? Math.min(subtotal - discountAmount, this.customerPoints) : 0;
|
||||
return subtotal - discountAmount - loyaltyRedeemed;
|
||||
let loyaltyRedeemedValue = (redeemSwitch && redeemSwitch.checked) ? Math.min(subtotal - discountAmount, this.customerPoints / this.loyaltySettings.redeemPointsPerUnit) : 0;
|
||||
return subtotal - discountAmount - loyaltyRedeemedValue;
|
||||
},
|
||||
getRemaining() {
|
||||
const total = this.getGrandTotal();
|
||||
@ -3754,7 +3987,7 @@ $projectDescription = $_SERVER['PROJECT_DESCRIPTION'] ?? 'Accounting System';
|
||||
discountAmount = this.discount.type === 'percentage' ? subtotal * (parseFloat(this.discount.value) / 100) : parseFloat(this.discount.value);
|
||||
}
|
||||
const redeemSwitch = document.getElementById('redeemLoyalty');
|
||||
let loyaltyRedeemed = (redeemSwitch && redeemSwitch.checked) ? Math.min(subtotal - discountAmount, this.customerPoints) : 0;
|
||||
let loyaltyRedeemed = (redeemSwitch && redeemSwitch.checked) ? Math.min(subtotal - discountAmount, this.customerPoints / this.loyaltySettings.redeemPointsPerUnit) : 0;
|
||||
|
||||
const formData = new FormData();
|
||||
formData.append('action', 'save_pos_transaction');
|
||||
@ -5897,6 +6130,26 @@ $projectDescription = $_SERVER['PROJECT_DESCRIPTION'] ?? 'Accounting System';
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
<div class="col-md-12 mt-4">
|
||||
<h5 class="mb-3" data-en="Loyalty Configuration" data-ar="إعدادات الولاء">Loyalty Configuration</h5>
|
||||
<div class="row g-3">
|
||||
<div class="col-md-4">
|
||||
<label class="form-label" data-en="Loyalty System" data-ar="نظام الولاء">Loyalty System</label>
|
||||
<select name="settings[loyalty_enabled]" class="form-select">
|
||||
<option value="0" <?= ($data['settings']['loyalty_enabled'] ?? '0') === '0' ? 'selected' : '' ?> data-en="Disabled" data-ar="معطل">Disabled</option>
|
||||
<option value="1" <?= ($data['settings']['loyalty_enabled'] ?? '0') === '1' ? 'selected' : '' ?> data-en="Enabled" data-ar="مفعل">Enabled</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="col-md-4">
|
||||
<label class="form-label" data-en="Points per 1 OMR" data-ar="النقاط لكل 1 ريال">Points per 1 OMR</label>
|
||||
<input type="number" step="0.01" name="settings[loyalty_points_per_unit]" class="form-control" value="<?= htmlspecialchars($data['settings']['loyalty_points_per_unit'] ?? '1') ?>">
|
||||
</div>
|
||||
<div class="col-md-4">
|
||||
<label class="form-label" data-en="Points for 1 OMR Discount" data-ar="النقاط لخصم 1 ريال">Points for 1 OMR Discount</label>
|
||||
<input type="number" step="0.01" name="settings[loyalty_redeem_points_per_unit]" class="form-control" value="<?= htmlspecialchars($data['settings']['loyalty_redeem_points_per_unit'] ?? '100') ?>">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-12 mt-4">
|
||||
<button type="submit" name="update_settings" class="btn btn-primary">
|
||||
<i class="bi bi-save"></i> <span data-en="Save Changes" data-ar="حفظ التغييرات">Save Changes</span>
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user