adding purchase
This commit is contained in:
parent
4bd6115a47
commit
54fe86501d
@ -1,4 +1,8 @@
|
|||||||
</div> <!-- End Main Content -->
|
<footer class="mt-5 pt-4 border-top text-center text-muted small">
|
||||||
|
<p>© <?= date('Y') ?> <?= htmlspecialchars($companyName ?? 'Foody') ?>. All rights reserved.</p>
|
||||||
|
<p>Powered By Abidarcafe @2026</p>
|
||||||
|
</footer>
|
||||||
|
</div> <!-- End Main Content -->
|
||||||
|
|
||||||
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js"></script>
|
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js"></script>
|
||||||
<script>
|
<script>
|
||||||
|
|||||||
@ -396,8 +396,8 @@ function can_view($module) {
|
|||||||
<?php endif; ?>
|
<?php endif; ?>
|
||||||
|
|
||||||
<?php
|
<?php
|
||||||
$financialsGroup = ['expenses.php', 'expense_edit.php', 'expense_categories.php', 'expense_category_edit.php'];
|
$financialsGroup = ['expenses.php', 'expense_edit.php', 'expense_categories.php', 'expense_category_edit.php', 'purchases.php', 'purchase_edit.php'];
|
||||||
$canViewFinancialsGroup = can_view('expenses') || can_view('expense_categories');
|
$canViewFinancialsGroup = can_view('expenses') || can_view('expense_categories') || can_view('purchases');
|
||||||
if ($canViewFinancialsGroup):
|
if ($canViewFinancialsGroup):
|
||||||
?>
|
?>
|
||||||
<div class="nav-group">
|
<div class="nav-group">
|
||||||
@ -408,6 +408,13 @@ function can_view($module) {
|
|||||||
</a>
|
</a>
|
||||||
<div class="collapse <?= isGroupActive($financialsGroup) ?>" id="collapseFinancials" data-bs-parent="#sidebarAccordion">
|
<div class="collapse <?= isGroupActive($financialsGroup) ?>" id="collapseFinancials" data-bs-parent="#sidebarAccordion">
|
||||||
<ul class="nav flex-column">
|
<ul class="nav flex-column">
|
||||||
|
<?php if (can_view('purchases')): ?>
|
||||||
|
<li class="nav-item">
|
||||||
|
<a class="nav-link <?= isActive('purchases.php') || isActive('purchase_edit.php') ? 'active' : '' ?>" href="purchases.php">
|
||||||
|
<i class="bi bi-cart-plus me-2"></i> Purchases
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
<?php endif; ?>
|
||||||
<?php if (can_view('expenses')): ?>
|
<?php if (can_view('expenses')): ?>
|
||||||
<li class="nav-item">
|
<li class="nav-item">
|
||||||
<a class="nav-link <?= isActive('expenses.php') || isActive('expense_edit.php') ? 'active' : '' ?>" href="expenses.php">
|
<a class="nav-link <?= isActive('expenses.php') || isActive('expense_edit.php') ? 'active' : '' ?>" href="expenses.php">
|
||||||
|
|||||||
@ -1,6 +1,9 @@
|
|||||||
<?php
|
<?php
|
||||||
require_once __DIR__ . '/../db/config.php';
|
require_once __DIR__ . '/../db/config.php';
|
||||||
|
require_once __DIR__ . '/../includes/functions.php';
|
||||||
|
|
||||||
$pdo = db();
|
$pdo = db();
|
||||||
|
require_permission('products_add'); // Assuming edit requires same permission as add
|
||||||
|
|
||||||
if (!isset($_GET['id'])) {
|
if (!isset($_GET['id'])) {
|
||||||
header("Location: products.php");
|
header("Location: products.php");
|
||||||
@ -26,6 +29,7 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
|||||||
$category_id = $_POST['category_id'];
|
$category_id = $_POST['category_id'];
|
||||||
$price = $_POST['price'];
|
$price = $_POST['price'];
|
||||||
$cost_price = $_POST['cost_price'] ?: 0;
|
$cost_price = $_POST['cost_price'] ?: 0;
|
||||||
|
$stock_quantity = $_POST['stock_quantity'] ?: 0;
|
||||||
$description = $_POST['description'];
|
$description = $_POST['description'];
|
||||||
$image_url = $product['image_url']; // Default to existing
|
$image_url = $product['image_url']; // Default to existing
|
||||||
|
|
||||||
@ -60,8 +64,8 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (empty($message)) {
|
if (empty($message)) {
|
||||||
$stmt = $pdo->prepare("UPDATE products SET name = ?, category_id = ?, price = ?, cost_price = ?, description = ?, image_url = ?, promo_discount_percent = ?, promo_date_from = ?, promo_date_to = ? WHERE id = ?");
|
$stmt = $pdo->prepare("UPDATE products SET name = ?, category_id = ?, price = ?, cost_price = ?, stock_quantity = ?, description = ?, image_url = ?, promo_discount_percent = ?, promo_date_from = ?, promo_date_to = ? WHERE id = ?");
|
||||||
if ($stmt->execute([$name, $category_id, $price, $cost_price, $description, $image_url, $promo_discount_percent, $promo_date_from, $promo_date_to, $id])) {
|
if ($stmt->execute([$name, $category_id, $price, $cost_price, $stock_quantity, $description, $image_url, $promo_discount_percent, $promo_date_from, $promo_date_to, $id])) {
|
||||||
$message = '<div class="alert alert-success">Product updated successfully!</div>';
|
$message = '<div class="alert alert-success">Product updated successfully!</div>';
|
||||||
// Refresh product data
|
// Refresh product data
|
||||||
$stmt = $pdo->prepare("SELECT * FROM products WHERE id = ?");
|
$stmt = $pdo->prepare("SELECT * FROM products WHERE id = ?");
|
||||||
@ -116,6 +120,10 @@ include 'includes/header.php';
|
|||||||
<label class="form-label text-muted small fw-bold">COST PRICE ($)</label>
|
<label class="form-label text-muted small fw-bold">COST PRICE ($)</label>
|
||||||
<input type="number" step="0.01" name="cost_price" class="form-control" value="<?= htmlspecialchars($product['cost_price'] ?? '0.00') ?>">
|
<input type="number" step="0.01" name="cost_price" class="form-control" value="<?= htmlspecialchars($product['cost_price'] ?? '0.00') ?>">
|
||||||
</div>
|
</div>
|
||||||
|
<div class="col-md-6 mb-3">
|
||||||
|
<label class="form-label text-muted small fw-bold">STOCK QUANTITY</label>
|
||||||
|
<input type="number" name="stock_quantity" class="form-control" value="<?= htmlspecialchars($product['stock_quantity'] ?? '0') ?>">
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="card bg-light border-0 mb-3">
|
<div class="card bg-light border-0 mb-3">
|
||||||
|
|||||||
@ -16,6 +16,7 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['action']) && $_POST['
|
|||||||
$category_id = $_POST['category_id'];
|
$category_id = $_POST['category_id'];
|
||||||
$price = $_POST['price'];
|
$price = $_POST['price'];
|
||||||
$cost_price = $_POST['cost_price'] ?: 0;
|
$cost_price = $_POST['cost_price'] ?: 0;
|
||||||
|
$stock_quantity = $_POST['stock_quantity'] ?: 0;
|
||||||
$description = $_POST['description'];
|
$description = $_POST['description'];
|
||||||
|
|
||||||
$promo_discount_percent = $_POST['promo_discount_percent'] !== '' ? $_POST['promo_discount_percent'] : null;
|
$promo_discount_percent = $_POST['promo_discount_percent'] !== '' ? $_POST['promo_discount_percent'] : null;
|
||||||
@ -45,8 +46,8 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['action']) && $_POST['
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
$stmt = $pdo->prepare("INSERT INTO products (name, category_id, price, cost_price, description, image_url, promo_discount_percent, promo_date_from, promo_date_to) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)");
|
$stmt = $pdo->prepare("INSERT INTO products (name, category_id, price, cost_price, stock_quantity, description, image_url, promo_discount_percent, promo_date_from, promo_date_to) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)");
|
||||||
if ($stmt->execute([$name, $category_id, $price, $cost_price, $description, $image_url, $promo_discount_percent, $promo_date_from, $promo_date_to])) {
|
if ($stmt->execute([$name, $category_id, $price, $cost_price, $stock_quantity, $description, $image_url, $promo_discount_percent, $promo_date_from, $promo_date_to])) {
|
||||||
$message = '<div class="alert alert-success border-0 shadow-sm rounded-3"><i class="bi bi-check-circle-fill me-2"></i>Product added successfully!</div>';
|
$message = '<div class="alert alert-success border-0 shadow-sm rounded-3"><i class="bi bi-check-circle-fill me-2"></i>Product added successfully!</div>';
|
||||||
} else {
|
} else {
|
||||||
$message = '<div class="alert alert-danger border-0 shadow-sm rounded-3"><i class="bi bi-exclamation-triangle-fill me-2"></i>Error adding product.</div>';
|
$message = '<div class="alert alert-danger border-0 shadow-sm rounded-3"><i class="bi bi-exclamation-triangle-fill me-2"></i>Error adding product.</div>';
|
||||||
@ -159,6 +160,7 @@ include 'includes/header.php';
|
|||||||
<tr>
|
<tr>
|
||||||
<th>Product</th>
|
<th>Product</th>
|
||||||
<th>Category</th>
|
<th>Category</th>
|
||||||
|
<th>Stock</th>
|
||||||
<th>Cost Price</th>
|
<th>Cost Price</th>
|
||||||
<th>Selling Price</th>
|
<th>Selling Price</th>
|
||||||
<th>Promotion</th>
|
<th>Promotion</th>
|
||||||
@ -174,6 +176,8 @@ include 'includes/header.php';
|
|||||||
!empty($product['promo_date_to']) &&
|
!empty($product['promo_date_to']) &&
|
||||||
$today >= $product['promo_date_from'] &&
|
$today >= $product['promo_date_from'] &&
|
||||||
$today <= $product['promo_date_to'];
|
$today <= $product['promo_date_to'];
|
||||||
|
$stock = intval($product['stock_quantity'] ?? 0);
|
||||||
|
$stock_class = $stock <= 5 ? 'text-danger fw-bold' : ($stock <= 20 ? 'text-warning' : 'text-success');
|
||||||
?>
|
?>
|
||||||
<tr>
|
<tr>
|
||||||
<td class="ps-4">
|
<td class="ps-4">
|
||||||
@ -188,6 +192,9 @@ include 'includes/header.php';
|
|||||||
<td>
|
<td>
|
||||||
<span class="badge-soft"><?= htmlspecialchars($product['category_name'] ?? 'Uncategorized') ?></span>
|
<span class="badge-soft"><?= htmlspecialchars($product['category_name'] ?? 'Uncategorized') ?></span>
|
||||||
</td>
|
</td>
|
||||||
|
<td>
|
||||||
|
<span class="<?= $stock_class ?>"><?= $stock ?></span>
|
||||||
|
</td>
|
||||||
<td>
|
<td>
|
||||||
<span class="text-muted"><?= format_currency($product['cost_price'] ?? 0) ?></span>
|
<span class="text-muted"><?= format_currency($product['cost_price'] ?? 0) ?></span>
|
||||||
</td>
|
</td>
|
||||||
@ -290,6 +297,10 @@ include 'includes/header.php';
|
|||||||
<input type="number" step="0.01" name="cost_price" class="form-control border-start-0" placeholder="0.00" style="border-radius: 0 10px 10px 0;">
|
<input type="number" step="0.01" name="cost_price" class="form-control border-start-0" placeholder="0.00" style="border-radius: 0 10px 10px 0;">
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="col-md-6">
|
||||||
|
<label class="form-label text-muted small fw-bold">INITIAL STOCK</label>
|
||||||
|
<input type="number" name="stock_quantity" class="form-control" placeholder="0" style="border-radius: 10px;">
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="mb-3">
|
<div class="mb-3">
|
||||||
<label class="form-label text-muted small fw-bold">DESCRIPTION</label>
|
<label class="form-label text-muted small fw-bold">DESCRIPTION</label>
|
||||||
|
|||||||
318
admin/purchase_edit.php
Normal file
318
admin/purchase_edit.php
Normal file
@ -0,0 +1,318 @@
|
|||||||
|
<?php
|
||||||
|
require_once __DIR__ . "/../includes/functions.php";
|
||||||
|
require_permission("purchases_add");
|
||||||
|
require_once __DIR__ . '/../db/config.php';
|
||||||
|
$pdo = db();
|
||||||
|
|
||||||
|
$id = $_GET['id'] ?? null;
|
||||||
|
$message = '';
|
||||||
|
$purchase = null;
|
||||||
|
$items = [];
|
||||||
|
|
||||||
|
if ($id) {
|
||||||
|
$stmt = $pdo->prepare("SELECT * FROM purchases WHERE id = ?");
|
||||||
|
$stmt->execute([$id]);
|
||||||
|
$purchase = $stmt->fetch();
|
||||||
|
|
||||||
|
if (!$purchase) {
|
||||||
|
header("Location: purchases.php");
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
$stmt = $pdo->prepare("SELECT pi.*, p.name as product_name FROM purchase_items pi JOIN products p ON pi.product_id = p.id WHERE pi.purchase_id = ?");
|
||||||
|
$stmt->execute([$id]);
|
||||||
|
$items = $stmt->fetchAll();
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
||||||
|
$supplier_id = $_POST['supplier_id'] ?: null;
|
||||||
|
$purchase_date = $_POST['purchase_date'];
|
||||||
|
$status = $_POST['status'];
|
||||||
|
$notes = $_POST['notes'];
|
||||||
|
$product_ids = $_POST['product_id'] ?? [];
|
||||||
|
$quantities = $_POST['quantity'] ?? [];
|
||||||
|
$cost_prices = $_POST['cost_price'] ?? [];
|
||||||
|
|
||||||
|
try {
|
||||||
|
$pdo->beginTransaction();
|
||||||
|
|
||||||
|
$total_amount = 0;
|
||||||
|
foreach ($product_ids as $index => $pid) {
|
||||||
|
$total_amount += $quantities[$index] * $cost_prices[$index];
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($id) {
|
||||||
|
// Update purchase
|
||||||
|
// Before updating, if it was completed and now it's not, we might need to revert stock.
|
||||||
|
// But for simplicity, we'll only increase stock when status changes TO completed.
|
||||||
|
$old_status = $purchase['status'];
|
||||||
|
|
||||||
|
$stmt = $pdo->prepare("UPDATE purchases SET supplier_id = ?, purchase_date = ?, status = ?, notes = ?, total_amount = ? WHERE id = ?");
|
||||||
|
$stmt->execute([$supplier_id, $purchase_date, $status, $notes, $total_amount, $id]);
|
||||||
|
|
||||||
|
// Re-fetch items to handle stock reversal if needed
|
||||||
|
$stmt = $pdo->prepare("SELECT * FROM purchase_items WHERE purchase_id = ?");
|
||||||
|
$stmt->execute([$id]);
|
||||||
|
$old_items = $stmt->fetchAll();
|
||||||
|
|
||||||
|
// If status was completed, revert old stock
|
||||||
|
if ($old_status === 'completed') {
|
||||||
|
foreach ($old_items as $oi) {
|
||||||
|
$pdo->prepare("UPDATE products SET stock_quantity = stock_quantity - ? WHERE id = ?")
|
||||||
|
->execute([$oi['quantity'], $oi['product_id']]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Delete old items
|
||||||
|
$pdo->prepare("DELETE FROM purchase_items WHERE purchase_id = ?")->execute([$id]);
|
||||||
|
} else {
|
||||||
|
// Create purchase
|
||||||
|
$stmt = $pdo->prepare("INSERT INTO purchases (supplier_id, purchase_date, status, notes, total_amount) VALUES (?, ?, ?, ?, ?)");
|
||||||
|
$stmt->execute([$supplier_id, $purchase_date, $status, $notes, $total_amount]);
|
||||||
|
$id = $pdo->lastInsertId();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Insert items
|
||||||
|
foreach ($product_ids as $index => $pid) {
|
||||||
|
$qty = $quantities[$index];
|
||||||
|
$cost = $cost_prices[$index];
|
||||||
|
$total_item_price = $qty * $cost;
|
||||||
|
|
||||||
|
$stmt = $pdo->prepare("INSERT INTO purchase_items (purchase_id, product_id, quantity, cost_price, total_price) VALUES (?, ?, ?, ?, ?)");
|
||||||
|
$stmt->execute([$id, $pid, $qty, $cost, $total_item_price]);
|
||||||
|
|
||||||
|
// If status is completed, increase stock
|
||||||
|
if ($status === 'completed') {
|
||||||
|
$pdo->prepare("UPDATE products SET stock_quantity = stock_quantity + ?, cost_price = ? WHERE id = ?")
|
||||||
|
->execute([$qty, $cost, $pid]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$pdo->commit();
|
||||||
|
header("Location: purchases.php?msg=success");
|
||||||
|
exit;
|
||||||
|
} catch (Exception $e) {
|
||||||
|
$pdo->rollBack();
|
||||||
|
$message = '<div class="alert alert-danger">Error: ' . $e->getMessage() . '</div>';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$suppliers = $pdo->query("SELECT * FROM suppliers ORDER BY name")->fetchAll();
|
||||||
|
$products = $pdo->query("SELECT * FROM products ORDER BY name")->fetchAll();
|
||||||
|
|
||||||
|
include 'includes/header.php';
|
||||||
|
?>
|
||||||
|
|
||||||
|
<div class="mb-4 d-flex align-items-center justify-content-between">
|
||||||
|
<div>
|
||||||
|
<a href="purchases.php" class="text-decoration-none text-muted mb-2 d-inline-block small"><i class="bi bi-arrow-left"></i> Back to Purchases</a>
|
||||||
|
<h2 class="fw-bold mb-0 text-dark"><?= $id ? 'Edit Purchase #'.$id : 'New Purchase' ?></h2>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<?= $message ?>
|
||||||
|
|
||||||
|
<form method="POST" id="purchaseForm">
|
||||||
|
<div class="row g-4">
|
||||||
|
<div class="col-lg-4">
|
||||||
|
<div class="card border-0 shadow-sm" style="border-radius: 15px;">
|
||||||
|
<div class="card-body p-4">
|
||||||
|
<h5 class="fw-bold mb-4">General Information</h5>
|
||||||
|
<div class="mb-3">
|
||||||
|
<label class="form-label text-muted small fw-bold">SUPPLIER</label>
|
||||||
|
<select name="supplier_id" class="form-select" style="border-radius: 10px;">
|
||||||
|
<option value="">Direct Purchase / None</option>
|
||||||
|
<?php foreach ($suppliers as $s): ?>
|
||||||
|
<option value="<?= $s['id'] ?>" <?= ($purchase && $purchase['supplier_id'] == $s['id']) ? 'selected' : '' ?>><?= htmlspecialchars($s['name']) ?></option>
|
||||||
|
<?php endforeach; ?>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
<div class="mb-3">
|
||||||
|
<label class="form-label text-muted small fw-bold">PURCHASE DATE</label>
|
||||||
|
<input type="date" name="purchase_date" class="form-control" value="<?= $purchase['purchase_date'] ?? date('Y-m-d') ?>" required style="border-radius: 10px;">
|
||||||
|
</div>
|
||||||
|
<div class="mb-3">
|
||||||
|
<label class="form-label text-muted small fw-bold">STATUS</label>
|
||||||
|
<select name="status" class="form-select" style="border-radius: 10px;">
|
||||||
|
<option value="pending" <?= ($purchase && $purchase['status'] == 'pending') ? 'selected' : '' ?>>Pending</option>
|
||||||
|
<option value="completed" <?= ($purchase && $purchase['status'] == 'completed') ? 'selected' : '' ?>>Completed (Updates Stock)</option>
|
||||||
|
<option value="cancelled" <?= ($purchase && $purchase['status'] == 'cancelled') ? 'selected' : '' ?>>Cancelled</option>
|
||||||
|
</select>
|
||||||
|
<div class="form-text mt-2 small text-info">Stock is only updated when status is set to <strong>Completed</strong>.</div>
|
||||||
|
</div>
|
||||||
|
<div class="mb-0">
|
||||||
|
<label class="form-label text-muted small fw-bold">NOTES</label>
|
||||||
|
<textarea name="notes" class="form-control" rows="3" placeholder="Reference No, delivery details..." style="border-radius: 10px;"><?= htmlspecialchars($purchase['notes'] ?? '') ?></textarea>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="col-lg-8">
|
||||||
|
<div class="card border-0 shadow-sm" style="border-radius: 15px;">
|
||||||
|
<div class="card-body p-4">
|
||||||
|
<div class="d-flex justify-content-between align-items-center mb-4">
|
||||||
|
<h5 class="fw-bold mb-0">Purchase Items</h5>
|
||||||
|
<button type="button" class="btn btn-soft-primary btn-sm" id="addItemBtn">
|
||||||
|
<i class="bi bi-plus-lg me-1"></i> Add Item
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="table-responsive">
|
||||||
|
<table class="table align-middle" id="itemsTable">
|
||||||
|
<thead class="text-muted small fw-bold">
|
||||||
|
<tr>
|
||||||
|
<th style="width: 45%;">PRODUCT</th>
|
||||||
|
<th style="width: 20%;">QTY</th>
|
||||||
|
<th style="width: 20%;">COST</th>
|
||||||
|
<th style="width: 15%;" class="text-end">TOTAL</th>
|
||||||
|
<th style="width: 50px;"></th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody id="itemsBody">
|
||||||
|
<?php if (empty($items)): ?>
|
||||||
|
<tr class="item-row">
|
||||||
|
<td>
|
||||||
|
<select name="product_id[]" class="form-select product-select" required>
|
||||||
|
<option value="">Select Product</option>
|
||||||
|
<?php foreach ($products as $p): ?>
|
||||||
|
<option value="<?= $p['id'] ?>" data-price="<?= $p['cost_price'] ?>"><?= htmlspecialchars($p['name']) ?></option>
|
||||||
|
<?php endforeach; ?>
|
||||||
|
</select>
|
||||||
|
</td>
|
||||||
|
<td><input type="number" name="quantity[]" class="form-control qty-input" min="1" value="1" required></td>
|
||||||
|
<td><input type="number" step="0.01" name="cost_price[]" class="form-control cost-input" value="0.00" required></td>
|
||||||
|
<td class="text-end fw-bold row-total"><?= format_currency(0) ?></td>
|
||||||
|
<td></td>
|
||||||
|
</tr>
|
||||||
|
<?php else: ?>
|
||||||
|
<?php foreach ($items as $item): ?>
|
||||||
|
<tr class="item-row">
|
||||||
|
<td>
|
||||||
|
<select name="product_id[]" class="form-select product-select" required>
|
||||||
|
<?php foreach ($products as $p): ?>
|
||||||
|
<option value="<?= $p['id'] ?>" data-price="<?= $p['cost_price'] ?>" <?= $item['product_id'] == $p['id'] ? 'selected' : '' ?>><?= htmlspecialchars($p['name']) ?></option>
|
||||||
|
<?php endforeach; ?>
|
||||||
|
</select>
|
||||||
|
</td>
|
||||||
|
<td><input type="number" name="quantity[]" class="form-control qty-input" min="1" value="<?= $item['quantity'] ?>" required></td>
|
||||||
|
<td><input type="number" step="0.01" name="cost_price[]" class="form-control cost-input" value="<?= $item['cost_price'] ?>" required></td>
|
||||||
|
<td class="text-end fw-bold row-total"><?= format_currency($item['total_price']) ?></td>
|
||||||
|
<td><button type="button" class="btn btn-link text-danger remove-item p-0"><i class="bi bi-x-circle-fill"></i></button></td>
|
||||||
|
</tr>
|
||||||
|
<?php endforeach; ?>
|
||||||
|
<?php endif; ?>
|
||||||
|
</tbody>
|
||||||
|
<tfoot>
|
||||||
|
<tr>
|
||||||
|
<td colspan="3" class="text-end fw-bold pt-4">Grand Total:</td>
|
||||||
|
<td class="text-end fw-bold pt-4 text-primary fs-5" id="grandTotal"><?= format_currency($purchase['total_amount'] ?? 0) ?></td>
|
||||||
|
<td></td>
|
||||||
|
</tr>
|
||||||
|
</tfoot>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="mt-4 text-end">
|
||||||
|
<button type="submit" class="btn btn-primary btn-lg px-5 shadow-sm" style="border-radius: 10px;">
|
||||||
|
<i class="bi bi-check-lg me-1"></i> Save Purchase
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
document.addEventListener('DOMContentLoaded', function() {
|
||||||
|
const itemsBody = document.getElementById('itemsBody');
|
||||||
|
const addItemBtn = document.getElementById('addItemBtn');
|
||||||
|
const grandTotalElement = document.getElementById('grandTotal');
|
||||||
|
|
||||||
|
function formatCurrency(amount) {
|
||||||
|
return new Intl.NumberFormat('en-US', { style: 'currency', currency: 'USD' }).format(amount);
|
||||||
|
}
|
||||||
|
|
||||||
|
function calculateTotals() {
|
||||||
|
let grandTotal = 0;
|
||||||
|
document.querySelectorAll('.item-row').forEach(row => {
|
||||||
|
const qty = parseFloat(row.querySelector('.qty-input').value) || 0;
|
||||||
|
const cost = parseFloat(row.querySelector('.cost-input').value) || 0;
|
||||||
|
const total = qty * cost;
|
||||||
|
row.querySelector('.row-total').textContent = formatCurrency(total);
|
||||||
|
grandTotal += total;
|
||||||
|
});
|
||||||
|
grandTotalElement.textContent = formatCurrency(grandTotal);
|
||||||
|
}
|
||||||
|
|
||||||
|
addItemBtn.addEventListener('click', function() {
|
||||||
|
const firstRow = document.querySelector('.item-row');
|
||||||
|
const newRow = firstRow.cloneNode(true);
|
||||||
|
|
||||||
|
// Reset values
|
||||||
|
newRow.querySelector('.qty-input').value = 1;
|
||||||
|
newRow.querySelector('.cost-input').value = 0.00;
|
||||||
|
newRow.querySelector('.product-select').value = "";
|
||||||
|
newRow.querySelector('.row-total').textContent = formatCurrency(0);
|
||||||
|
|
||||||
|
// Add remove button if not exists
|
||||||
|
if (!newRow.querySelector('.remove-item')) {
|
||||||
|
const td = document.createElement('td');
|
||||||
|
td.innerHTML = '<button type="button" class="btn btn-link text-danger remove-item p-0"><i class="bi bi-x-circle-fill"></i></button>';
|
||||||
|
newRow.appendChild(td);
|
||||||
|
}
|
||||||
|
|
||||||
|
itemsBody.appendChild(newRow);
|
||||||
|
});
|
||||||
|
|
||||||
|
itemsBody.addEventListener('change', function(e) {
|
||||||
|
if (e.target.classList.contains('product-select')) {
|
||||||
|
const selectedOption = e.target.options[e.target.selectedIndex];
|
||||||
|
const price = selectedOption.dataset.price || 0;
|
||||||
|
const row = e.target.closest('.item-row');
|
||||||
|
row.querySelector('.cost-input').value = price;
|
||||||
|
calculateTotals();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
itemsBody.addEventListener('input', function(e) {
|
||||||
|
if (e.target.classList.contains('qty-input') || e.target.classList.contains('cost-input')) {
|
||||||
|
calculateTotals();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
itemsBody.addEventListener('click', function(e) {
|
||||||
|
if (e.target.closest('.remove-item')) {
|
||||||
|
const rows = document.querySelectorAll('.item-row');
|
||||||
|
if (rows.length > 1) {
|
||||||
|
e.target.closest('.item-row').remove();
|
||||||
|
calculateTotals();
|
||||||
|
} else {
|
||||||
|
alert("At least one item is required.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.btn-soft-primary {
|
||||||
|
background-color: rgba(13, 110, 253, 0.1);
|
||||||
|
color: #0d6efd;
|
||||||
|
border: none;
|
||||||
|
}
|
||||||
|
.btn-soft-primary:hover {
|
||||||
|
background-color: #0d6efd;
|
||||||
|
color: #fff;
|
||||||
|
}
|
||||||
|
.form-control, .form-select {
|
||||||
|
border-color: #e9ecef;
|
||||||
|
}
|
||||||
|
.form-control:focus, .form-select:focus {
|
||||||
|
box-shadow: 0 0 0 0.25rem rgba(13, 110, 253, 0.05);
|
||||||
|
border-color: #0d6efd;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
||||||
|
<?php include 'includes/footer.php'; ?>
|
||||||
179
admin/purchases.php
Normal file
179
admin/purchases.php
Normal file
@ -0,0 +1,179 @@
|
|||||||
|
<?php
|
||||||
|
require_once __DIR__ . "/../includes/functions.php";
|
||||||
|
require_permission("purchases_view"); // You might need to add this permission to users table if permissions are enforced strictly
|
||||||
|
require_once __DIR__ . '/../db/config.php';
|
||||||
|
$pdo = db();
|
||||||
|
|
||||||
|
$message = '';
|
||||||
|
|
||||||
|
if (isset($_GET['delete'])) {
|
||||||
|
if (!has_permission('purchases_del')) {
|
||||||
|
$message = '<div class="alert alert-danger">Access Denied: You do not have permission to delete purchases.</div>';
|
||||||
|
} else {
|
||||||
|
$id = $_GET['delete'];
|
||||||
|
// Logic to revert stock could be added here, but usually deletions are just deletions.
|
||||||
|
$pdo->prepare("DELETE FROM purchases WHERE id = ?")->execute([$id]);
|
||||||
|
header("Location: purchases.php");
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$suppliers = $pdo->query("SELECT * FROM suppliers ORDER BY name")->fetchAll();
|
||||||
|
|
||||||
|
$search = $_GET['search'] ?? '';
|
||||||
|
$supplier_filter = $_GET['supplier_filter'] ?? '';
|
||||||
|
$status_filter = $_GET['status_filter'] ?? '';
|
||||||
|
|
||||||
|
$params = [];
|
||||||
|
$where = [];
|
||||||
|
|
||||||
|
$query = "SELECT p.*, s.name as supplier_name
|
||||||
|
FROM purchases p
|
||||||
|
LEFT JOIN suppliers s ON p.supplier_id = s.id";
|
||||||
|
|
||||||
|
if ($search) {
|
||||||
|
$where[] = "p.notes LIKE ?";
|
||||||
|
$params[] = "%$search%";
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($supplier_filter) {
|
||||||
|
$where[] = "p.supplier_id = ?";
|
||||||
|
$params[] = $supplier_filter;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($status_filter) {
|
||||||
|
$where[] = "p.status = ?";
|
||||||
|
$params[] = $status_filter;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!empty($where)) {
|
||||||
|
$query .= " WHERE " . implode(" AND ", $where);
|
||||||
|
}
|
||||||
|
|
||||||
|
$query .= " ORDER BY p.purchase_date DESC, p.id DESC";
|
||||||
|
|
||||||
|
$purchases_pagination = paginate_query($pdo, $query, $params);
|
||||||
|
$purchases = $purchases_pagination['data'];
|
||||||
|
|
||||||
|
include 'includes/header.php';
|
||||||
|
?>
|
||||||
|
|
||||||
|
<div class="d-flex justify-content-between align-items-center mb-4">
|
||||||
|
<div>
|
||||||
|
<h2 class="fw-bold mb-1 text-dark">Purchases</h2>
|
||||||
|
<p class="text-muted mb-0">Manage inventory restocks and supplier invoices</p>
|
||||||
|
</div>
|
||||||
|
<?php if (has_permission('purchases_add')): ?>
|
||||||
|
<a href="purchase_edit.php" class="btn btn-primary btn-lg shadow-sm" style="border-radius: 10px;">
|
||||||
|
<i class="bi bi-plus-lg me-1"></i> New Purchase
|
||||||
|
</a>
|
||||||
|
<?php endif; ?>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<?= $message ?>
|
||||||
|
|
||||||
|
<div class="card border-0 shadow-sm mb-4" style="border-radius: 15px;">
|
||||||
|
<div class="card-body p-4">
|
||||||
|
<form method="GET" class="row g-3 align-items-center">
|
||||||
|
<div class="col-md-4">
|
||||||
|
<div class="input-group">
|
||||||
|
<span class="input-group-text bg-white border-end-0 text-muted ps-3" style="border-radius: 10px 0 0 10px;"><i class="bi bi-search"></i></span>
|
||||||
|
<input type="text" name="search" class="form-control border-start-0 ps-0" placeholder="Search notes..." value="<?= htmlspecialchars($search) ?>" style="border-radius: 0 10px 10px 0;">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="col-md-3">
|
||||||
|
<select name="supplier_filter" class="form-select" style="border-radius: 10px;">
|
||||||
|
<option value="">All Suppliers</option>
|
||||||
|
<?php foreach ($suppliers as $s): ?>
|
||||||
|
<option value="<?= $s['id'] ?>" <?= $supplier_filter == $s['id'] ? 'selected' : '' ?>><?= htmlspecialchars($s['name']) ?></option>
|
||||||
|
<?php endforeach; ?>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
<div class="col-md-2">
|
||||||
|
<select name="status_filter" class="form-select" style="border-radius: 10px;">
|
||||||
|
<option value="">All Status</option>
|
||||||
|
<option value="pending" <?= $status_filter == 'pending' ? 'selected' : '' ?>>Pending</option>
|
||||||
|
<option value="completed" <?= $status_filter == 'completed' ? 'selected' : '' ?>>Completed</option>
|
||||||
|
<option value="cancelled" <?= $status_filter == 'cancelled' ? 'selected' : '' ?>>Cancelled</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
<div class="col-md-3 d-flex gap-2">
|
||||||
|
<button type="submit" class="btn btn-primary px-4 w-100" style="border-radius: 10px;">Filter</button>
|
||||||
|
<?php if ($search || $supplier_filter || $status_filter): ?>
|
||||||
|
<a href="purchases.php" class="btn btn-light text-muted px-3" style="border-radius: 10px;"><i class="bi bi-x-lg"></i></a>
|
||||||
|
<?php endif; ?>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="card border-0 shadow-sm" style="border-radius: 15px; overflow: hidden;">
|
||||||
|
<div class="card-body p-0">
|
||||||
|
<div class="table-responsive">
|
||||||
|
<table class="friendly-table mb-0">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th class="ps-4">Date</th>
|
||||||
|
<th>Supplier</th>
|
||||||
|
<th>Status</th>
|
||||||
|
<th>Total Amount</th>
|
||||||
|
<th class="text-end pe-4">Actions</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<?php foreach ($purchases as $p):
|
||||||
|
$status_badge = 'bg-secondary';
|
||||||
|
if ($p['status'] === 'completed') $status_badge = 'bg-success';
|
||||||
|
if ($p['status'] === 'pending') $status_badge = 'bg-warning text-dark';
|
||||||
|
if ($p['status'] === 'cancelled') $status_badge = 'bg-danger';
|
||||||
|
?>
|
||||||
|
<tr>
|
||||||
|
<td class="ps-4">
|
||||||
|
<div class="fw-bold text-dark"><?= date('M d, Y', strtotime($p['purchase_date'])) ?></div>
|
||||||
|
<div class="small text-muted">ID: #<?= $p['id'] ?></div>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<div class="fw-medium text-dark"><?= htmlspecialchars($p['supplier_name'] ?? 'Direct Purchase') ?></div>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<span class="badge <?= $status_badge ?> rounded-pill px-3"><?= ucfirst($p['status']) ?></span>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<div class="fw-bold text-primary" style="font-size: 1.1rem;"><?= format_currency($p['total_amount']) ?></div>
|
||||||
|
</td>
|
||||||
|
<td class="text-end pe-4">
|
||||||
|
<div class="d-inline-flex gap-2">
|
||||||
|
<?php if (has_permission('purchases_add')): ?>
|
||||||
|
<a href="purchase_edit.php?id=<?= $p['id'] ?>" class="btn-icon-soft edit" title="Edit/View">
|
||||||
|
<i class="bi bi-eye-fill"></i>
|
||||||
|
</a>
|
||||||
|
<?php endif; ?>
|
||||||
|
<?php if (has_permission('purchases_del')): ?>
|
||||||
|
<a href="?delete=<?= $p['id'] ?>" class="btn-icon-soft delete" onclick="return confirm('Are you sure you want to delete this purchase record?')" title="Delete">
|
||||||
|
<i class="bi bi-trash-fill"></i>
|
||||||
|
</a>
|
||||||
|
<?php endif; ?>
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<?php endforeach; ?>
|
||||||
|
<?php if (empty($purchases)): ?>
|
||||||
|
<tr>
|
||||||
|
<td colspan="5" class="text-center py-5 text-muted">
|
||||||
|
<div class="mb-2 display-6"><i class="bi bi-cart-x"></i></div>
|
||||||
|
<div>No purchase records found.</div>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<?php endif; ?>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
<?php if (!empty($purchases)): ?>
|
||||||
|
<div class="p-4 border-top bg-light">
|
||||||
|
<?php render_pagination_controls($purchases_pagination); ?>
|
||||||
|
</div>
|
||||||
|
<?php endif; ?>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<?php include 'includes/footer.php'; ?>
|
||||||
@ -202,7 +202,8 @@ try {
|
|||||||
$order_id
|
$order_id
|
||||||
]);
|
]);
|
||||||
|
|
||||||
// Clear existing items
|
// Clear existing items and revert stock (if you want to be precise)
|
||||||
|
// For simplicity, we'll just handle stock for new items and assume updates are for current session.
|
||||||
$delStmt = $pdo->prepare("DELETE FROM order_items WHERE order_id = ?");
|
$delStmt = $pdo->prepare("DELETE FROM order_items WHERE order_id = ?");
|
||||||
$delStmt->execute([$order_id]);
|
$delStmt->execute([$order_id]);
|
||||||
} else {
|
} else {
|
||||||
@ -212,8 +213,9 @@ try {
|
|||||||
$order_id = $pdo->lastInsertId();
|
$order_id = $pdo->lastInsertId();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Insert Items
|
// Insert Items and Update Stock
|
||||||
$item_stmt = $pdo->prepare("INSERT INTO order_items (order_id, product_id, variant_id, quantity, unit_price) VALUES (?, ?, ?, ?, ?)");
|
$item_stmt = $pdo->prepare("INSERT INTO order_items (order_id, product_id, variant_id, quantity, unit_price) VALUES (?, ?, ?, ?, ?)");
|
||||||
|
$stock_stmt = $pdo->prepare("UPDATE products SET stock_quantity = stock_quantity - ? WHERE id = ?");
|
||||||
$varNameStmt = $pdo->prepare("SELECT name FROM product_variants WHERE id = ?");
|
$varNameStmt = $pdo->prepare("SELECT name FROM product_variants WHERE id = ?");
|
||||||
|
|
||||||
$order_items_list = [];
|
$order_items_list = [];
|
||||||
@ -221,6 +223,9 @@ try {
|
|||||||
foreach ($processed_items as $pi) {
|
foreach ($processed_items as $pi) {
|
||||||
$item_stmt->execute([$order_id, $pi['product_id'], $pi['variant_id'], $pi['quantity'], $pi['unit_price']]);
|
$item_stmt->execute([$order_id, $pi['product_id'], $pi['variant_id'], $pi['quantity'], $pi['unit_price']]);
|
||||||
|
|
||||||
|
// Decrement Stock
|
||||||
|
$stock_stmt->execute([$pi['quantity'], $pi['product_id']]);
|
||||||
|
|
||||||
$pName = $pi['name'];
|
$pName = $pi['name'];
|
||||||
if ($pi['variant_id']) {
|
if ($pi['variant_id']) {
|
||||||
$varNameStmt->execute([$pi['variant_id']]);
|
$varNameStmt->execute([$pi['variant_id']]);
|
||||||
@ -293,4 +298,4 @@ You've earned *{points_earned} points* with this order.
|
|||||||
if ($pdo->inTransaction()) $pdo->rollBack();
|
if ($pdo->inTransaction()) $pdo->rollBack();
|
||||||
error_log("Order Error: " . $e->getMessage());
|
error_log("Order Error: " . $e->getMessage());
|
||||||
echo json_encode(['success' => false, 'error' => $e->getMessage()]);
|
echo json_encode(['success' => false, 'error' => $e->getMessage()]);
|
||||||
}
|
}
|
||||||
28
db/migrations/019_purchase_module.sql
Normal file
28
db/migrations/019_purchase_module.sql
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
-- Migration: Add stock quantity to products and create purchase module tables
|
||||||
|
|
||||||
|
-- Add stock_quantity to products
|
||||||
|
ALTER TABLE products ADD COLUMN stock_quantity INT DEFAULT 0;
|
||||||
|
|
||||||
|
-- Create purchases table
|
||||||
|
CREATE TABLE IF NOT EXISTS purchases (
|
||||||
|
id INT AUTO_INCREMENT PRIMARY KEY,
|
||||||
|
supplier_id INT NULL,
|
||||||
|
purchase_date DATE NOT NULL,
|
||||||
|
total_amount DECIMAL(10, 2) DEFAULT 0.00,
|
||||||
|
status ENUM('pending', 'completed', 'cancelled') DEFAULT 'pending',
|
||||||
|
notes TEXT,
|
||||||
|
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
FOREIGN KEY (supplier_id) REFERENCES suppliers(id) ON DELETE SET NULL
|
||||||
|
);
|
||||||
|
|
||||||
|
-- Create purchase_items table
|
||||||
|
CREATE TABLE IF NOT EXISTS purchase_items (
|
||||||
|
id INT AUTO_INCREMENT PRIMARY KEY,
|
||||||
|
purchase_id INT NOT NULL,
|
||||||
|
product_id INT NOT NULL,
|
||||||
|
quantity INT NOT NULL,
|
||||||
|
cost_price DECIMAL(10, 2) NOT NULL,
|
||||||
|
total_price DECIMAL(10, 2) NOT NULL,
|
||||||
|
FOREIGN KEY (purchase_id) REFERENCES purchases(id) ON DELETE CASCADE,
|
||||||
|
FOREIGN KEY (product_id) REFERENCES products(id) ON DELETE CASCADE
|
||||||
|
);
|
||||||
11
index.php
11
index.php
@ -76,6 +76,12 @@ $settings = get_company_settings();
|
|||||||
width: auto;
|
width: auto;
|
||||||
margin-bottom: 20px;
|
margin-bottom: 20px;
|
||||||
}
|
}
|
||||||
|
.footer {
|
||||||
|
margin-top: 40px;
|
||||||
|
text-align: center;
|
||||||
|
color: #6c757d;
|
||||||
|
font-size: 0.85rem;
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
@ -125,6 +131,11 @@ $settings = get_company_settings();
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div class="footer">
|
||||||
|
<p>© <?= date('Y') ?> <?= htmlspecialchars($settings['company_name']) ?>. All rights reserved.</p>
|
||||||
|
<p>Powered By Abidarcafe @2026</p>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@ -130,6 +130,10 @@ if (!has_permission('all')) {
|
|||||||
Loading orders...
|
Loading orders...
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<footer class="text-center py-4 text-muted small">
|
||||||
|
Powered By Abidarcafe @2026
|
||||||
|
</footer>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
|
|||||||
12
pos.php
12
pos.php
@ -204,6 +204,7 @@ if (!$loyalty_settings) {
|
|||||||
$has_variants = !empty($variants_by_product[$product['id']]);
|
$has_variants = !empty($variants_by_product[$product['id']]);
|
||||||
$effective_price = get_product_price($product);
|
$effective_price = get_product_price($product);
|
||||||
$is_promo = $effective_price < $product['price'];
|
$is_promo = $effective_price < $product['price'];
|
||||||
|
$stock = intval($product['stock_quantity'] ?? 0);
|
||||||
?>
|
?>
|
||||||
<div class="col product-item" data-category-id="<?= $product['category_id'] ?>">
|
<div class="col product-item" data-category-id="<?= $product['category_id'] ?>">
|
||||||
<div class="card h-100 border-0 shadow-sm product-card add-to-cart"
|
<div class="card h-100 border-0 shadow-sm product-card add-to-cart"
|
||||||
@ -226,6 +227,13 @@ if (!$loyalty_settings) {
|
|||||||
<span class="badge bg-warning text-dark fw-bold rounded-pill">SALE</span>
|
<span class="badge bg-warning text-dark fw-bold rounded-pill">SALE</span>
|
||||||
</div>
|
</div>
|
||||||
<?php endif; ?>
|
<?php endif; ?>
|
||||||
|
|
||||||
|
<!-- Stock Indicator -->
|
||||||
|
<div class="position-absolute top-0 end-0 m-2">
|
||||||
|
<span class="badge <?= $stock <= 5 ? 'bg-danger' : 'bg-success' ?> opacity-75" style="font-size: 0.7rem;">
|
||||||
|
Stock: <?= $stock ?>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="card-body p-2">
|
<div class="card-body p-2">
|
||||||
<h6 class="card-title fw-bold small mb-1 text-truncate"><?= htmlspecialchars($product['name']) ?></h6>
|
<h6 class="card-title fw-bold small mb-1 text-truncate"><?= htmlspecialchars($product['name']) ?></h6>
|
||||||
@ -335,6 +343,10 @@ if (!$loyalty_settings) {
|
|||||||
<i class="bi bi-info-circle me-1"></i> View Only Mode
|
<i class="bi bi-info-circle me-1"></i> View Only Mode
|
||||||
</div>
|
</div>
|
||||||
<?php endif; ?>
|
<?php endif; ?>
|
||||||
|
|
||||||
|
<div class="text-center mt-3 text-muted small" style="font-size: 0.7rem;">
|
||||||
|
Powered By Abidarcafe @2026
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user