File manager - Edit - /home2/zetasolve/poss.shayantraders.com/scripts/billing_fixed.js
Back
let allSkus = []; let cart = []; document.addEventListener('DOMContentLoaded', async () => { // Set today's date document.getElementById('transactionDate').valueAsDate = new Date(); // Load SKUs await loadSkus(); }); async function loadSkus() { try { allSkus = await app.apiGet('/skus'); const skuSelect = document.getElementById('skuSelect'); skuSelect.innerHTML = '<option value="">Select a product...</option>' + allSkus.map(sku => ` <option value="${sku.id}" data-price="${sku.price}" data-stock="${sku.current_stock}"> ${sku.sku_code} - ${sku.product_name} ${sku.flavour} (${sku.weight}) - ${app.formatCurrency(sku.price)} [Stock: ${sku.current_stock}] </option> `).join(''); } catch (error) { console.error('Error loading SKUs:', error); app.showNotification('Failed to load products', 'error'); } } function addItemToCart() { const skuSelect = document.getElementById('skuSelect'); const quantity = parseInt(document.getElementById('itemQuantity').value); if (!skuSelect.value) { app.showNotification('Please select a product', 'error'); return; } if (quantity < 1) { app.showNotification('Quantity must be at least 1', 'error'); return; } const skuId = parseInt(skuSelect.value); const sku = allSkus.find(s => s.id === skuId); const selectedOption = skuSelect.options[skuSelect.selectedIndex]; const availableStock = parseInt(selectedOption.dataset.stock); // Check if already in cart const existingItem = cart.find(item => item.sku_id === skuId); const totalQuantity = existingItem ? existingItem.quantity + quantity : quantity; if (totalQuantity > availableStock) { app.showNotification(`Insufficient stock! Available: ${availableStock}`, 'error'); return; } if (existingItem) { existingItem.quantity = totalQuantity; } else { cart.push({ sku_id: skuId, sku_code: sku.sku_code, product_name: sku.product_name, flavour: sku.flavour, weight: sku.weight, unit_price: sku.price, quantity: quantity }); } // Reset selection skuSelect.value = ''; document.getElementById('itemQuantity').value = 1; updateCartDisplay(); } function removeItemFromCart(index) { cart.splice(index, 1); updateCartDisplay(); } function updateCartDisplay() { const cartContainer = document.getElementById('cartItems'); const submitBtn = document.getElementById('submitBtn'); if (cart.length === 0) { cartContainer.innerHTML = '<p class="text-muted" style="text-align: center; padding: 2rem;">No items added yet</p>'; submitBtn.disabled = true; updateBillSummary(); return; } submitBtn.disabled = false; cartContainer.innerHTML = ` <div style="background: var(--bg-tertiary); border-radius: var(--radius-md); padding: 1rem;"> ${cart.map((item, index) => ` <div style="display: flex; justify-content: space-between; align-items: center; padding: 0.75rem 0; ${index < cart.length - 1 ? 'border-bottom: 1px solid var(--glass-border);' : ''}"> <div style="flex: 1;"> <div style="font-weight: 600; color: var(--text-primary);">${item.sku_code}</div> <div style="font-size: 0.875rem; color: var(--text-muted);">${item.product_name} ${item.flavour} (${item.weight})</div> </div> <div style="display: flex; align-items: center; gap: 1rem;"> <div style="text-align: right;"> <div style="color: var(--text-secondary);">${item.quantity} × ${app.formatCurrency(item.unit_price)}</div> <div style="font-weight: 600; color: var(--text-primary);">${app.formatCurrency(item.quantity * item.unit_price)}</div> </div> <button type="button" class="btn btn-danger" onclick="removeItemFromCart(${index})" style="padding: 0.5rem 0.75rem; font-size: 0.75rem;"> Remove </button> </div> </div> `).join('')} </div> `; updateBillSummary(); } function updateBillSummary() { const itemCount = cart.reduce((sum, item) => sum + item.quantity, 0); const subtotal = cart.reduce((sum, item) => sum + (item.quantity * item.unit_price), 0); // Get user role to determine if discount is allowed const userRole = localStorage.getItem('role'); const discountAllowed = ['Admin', 'Manager'].includes(userRole); // Calculate discount based on rules: Bill ≥ 2000 → 10% discount (only if authorized) let discountPercentage = 0; let discountAmount = 0; let finalTotal = subtotal; if (subtotal >= 2000 && discountAllowed) { discountPercentage = 10; discountAmount = subtotal * (discountPercentage / 100); finalTotal = subtotal - discountAmount; } document.getElementById('summaryItemCount').textContent = itemCount; document.getElementById('summarySubtotal').textContent = app.formatCurrency(subtotal); document.getElementById('summaryDiscount').textContent = app.formatCurrency(discountAmount); document.getElementById('summaryTotal').textContent = app.formatCurrency(finalTotal); } function toggleInitialPayment() { const isCredit = document.querySelector('input[name="paymentType"]:checked').value === 'Credit'; const initialPaymentGroup = document.getElementById('initialPaymentGroup'); const initialPaymentInput = document.getElementById('initialPayment'); initialPaymentGroup.style.display = isCredit ? 'block' : 'none'; if (!isCredit) { initialPaymentInput.value = ''; } } async function processSale(event) { event.preventDefault(); if (cart.length === 0) { app.showNotification('Please add items to the cart', 'error'); return; } // Get user role to determine if discount is allowed const userRole = localStorage.getItem('role'); const discountAllowed = ['Admin', 'Manager'].includes(userRole); // Calculate discount for the transaction (manual input takes precedence over automatic) const subtotal = cart.reduce((sum, item) => sum + (item.quantity * item.unit_price), 0); let discountPercentage = 0; let discountAmount = 0; // Check if manual discount is entered const discountPercentageInput = document.getElementById('discountPercentage'); if (discountPercentageInput && discountPercentageInput.value) { // Validate discount authorization if (!discountAllowed) { app.showNotification('Only Admin or Manager can apply manual discounts', 'error'); return; } const manualDiscount = getManualDiscount(); discountPercentage = manualDiscount.percentage; discountAmount = manualDiscount.amount; } else { // Apply automatic discount if applicable if (subtotal >= 2000 && discountAllowed) { discountPercentage = 10; discountAmount = subtotal * (discountPercentage / 100); } else if (subtotal >= 2000 && !discountAllowed) { // If discount threshold is met but user is not authorized, show error app.showNotification('Discounts can only be applied by Admin or Manager', 'error'); return; } } const finalTotal = subtotal - discountAmount; const paymentType = document.querySelector('input[name="paymentType"]:checked').value; let amountPaid = finalTotal; // Default to full payment for Cash if (paymentType === 'Credit') { const initialPaymentVal = document.getElementById('initialPayment').value; amountPaid = initialPaymentVal ? parseFloat(initialPaymentVal) : 0; if (amountPaid > finalTotal) { app.showNotification('Initial payment cannot exceed total amount', 'error'); return; } } const transactionData = { transaction_date: document.getElementById('transactionDate').value, customer_name: document.getElementById('customerName').value || 'Walk-in Customer', payment_type: paymentType, items: cart, discount_percentage: discountPercentage, discount_amount: discountAmount, amount_paid: amountPaid }; try { const result = await app.apiPost('/transactions', transactionData); app.showNotification(`Sale completed! Total: ${app.formatCurrency(result.final_total || result.total_amount)}`, 'success'); // Reset form cart = []; document.getElementById('billingForm').reset(); document.getElementById('transactionDate').valueAsDate = new Date(); updateCartDisplay(); // Reload SKUs to update stock levels await loadSkus(); } catch (error) { console.error('Error processing sale:', error); app.showNotification('Failed to process sale', 'error'); } } async function viewSalesHistory() { document.getElementById('salesHistorySection').style.display = 'block'; await loadSalesHistory(); } function closeSalesHistory() { document.getElementById('salesHistorySection').style.display = 'none'; } async function loadSalesHistory(filters = {}) { try { let url = '/transactions'; const params = new URLSearchParams(); if (filters.start_date) params.append('start_date', filters.start_date); if (filters.end_date) params.append('end_date', filters.end_date); if (filters.payment_type) params.append('payment_type', filters.payment_type); if (params.toString()) url += '?' + params.toString(); const transactions = await app.apiGet(url); const tbody = document.getElementById('salesHistoryBody'); if (transactions.length === 0) { tbody.innerHTML = '<tr><td colspan="5" class="loading">No transactions found</td></tr>'; return; } tbody.innerHTML = transactions.map(transaction => ` <tr class="fade-in"> <td>${app.formatDate(transaction.transaction_date)}</td> <td>${transaction.customer_name}</td> <td><span class="badge badge-${transaction.payment_type.toLowerCase()}">${transaction.payment_type}</span></td> <td> <div><strong>${app.formatCurrency(transaction.total_amount)}</strong></div> ${transaction.discount_amount > 0 ? `<div style="font-size: 0.8rem; color: var(--success);">-${transaction.discount_percentage}% (-${app.formatCurrency(transaction.discount_amount)})</div>` : ''} </td> <td> <button class="btn btn-secondary" onclick="viewTransactionDetails(${transaction.id})" style="padding: 0.5rem 1rem; font-size: 0.75rem;"> View Details </button> <button class="btn btn-danger" onclick="deleteTransaction(${transaction.id})" style="padding: 0.5rem 1rem; font-size: 0.75rem; margin-left: 0.5rem;"> Void </button> </td> </tr> `).join(''); } catch (error) { console.error('Error loading sales history:', error); document.getElementById('salesHistoryBody').innerHTML = '<tr><td colspan="5" class="loading">Failed to load sales history</td></tr>'; } } function filterSalesHistory() { const filters = { start_date: document.getElementById('filterStartDate').value, end_date: document.getElementById('filterEndDate').value, payment_type: document.getElementById('filterPaymentType').value }; loadSalesHistory(filters); } async function viewTransactionDetails(transactionId) { try { const transaction = await app.apiGet(`/transactions/${transactionId}`); const content = ` <div style="margin-bottom: 1rem;"> <div style="display: grid; grid-template-columns: 1fr 1fr; gap: 1rem; margin-bottom: 1rem;"> <div> <strong>Date:</strong> ${app.formatDate(transaction.transaction_date)} </div> <div> <strong>Customer:</strong> ${transaction.customer_name} </div> <div> <strong>Payment:</strong> <span class="badge badge-${transaction.payment_type.toLowerCase()}">${transaction.payment_type}</span> </div> <div> <strong>Total:</strong> <span style="color: var(--danger); font-weight: 700;">${app.formatCurrency(transaction.total_amount)}</span> </div> ${transaction.discount_amount > 0 ? ` <div> <strong>Discount:</strong> <span style="color: var(--success); font-weight: 700;">${transaction.discount_percentage}% (-${app.formatCurrency(transaction.discount_amount)})</span> </div> <div> <strong>Final Total:</strong> <span style="color: var(--danger); font-weight: 700;">${app.formatCurrency(transaction.total_amount - transaction.discount_amount)}</span> </div> ` : ''} </div> </div> <h4 style="margin: 1.5rem 0 1rem;">Items</h4> <div class="table-container"> <table> <thead> <tr> <th>SKU</th> <th>Product</th> <th>Qty</th> <th>Price</th> <th>Subtotal</th> </tr> </thead> <tbody> ${transaction.items.map(item => ` <tr> <td>${item.sku_code}</td> <td>${item.product_name} ${item.flavour} (${item.weight})</td> <td>${item.quantity}</td> <td>${app.formatCurrency(item.unit_price)}</td> <td><strong>${app.formatCurrency(item.subtotal)}</strong></td> </tr> `).join('')} </tbody> </table> </div> `; document.getElementById('transactionDetailsContent').innerHTML = content; document.getElementById('transactionDetailsModal').classList.add('active'); } catch (error) { console.error('Error loading transaction details:', error); app.showNotification('Failed to load transaction details', 'error'); } } function closeTransactionDetails() { document.getElementById('transactionDetailsModal').classList.remove('active'); } async function deleteTransaction(transactionId) { if (confirm('Are you sure you want to VOID this transaction? This will reverse stock changes.')) { try { await app.apiDelete(`/transactions/${transactionId}`); app.showNotification('Transaction voided successfully', 'success'); await loadSalesHistory(); // Refresh history await loadSkus(); // Refresh stock } catch (error) { console.error('Error voiding transaction:', error); app.showNotification('Failed to void transaction', 'error'); } } } / / D i s c o u n t f u n c t i o n s f o r b i l l i n g s y s t e m f u n c t i o n t o g g l e D i s c o u n t I n p u t ( ) { c o n s t u s e r R o l e = l o c a l S t o r a g e . g e t I t e m ( '