document.addEventListener('DOMContentLoaded', function() {
// Initialize sales management
initSales();
// Set default dates (current month)
setDefaultDates();
// Event listeners
document.getElementById('saleSearch').addEventListener('input', function() {
currentPage = 1;
loadSales();
});
document.getElementById('filterSalesBtn').addEventListener('click', function() {
currentPage = 1;
loadSales();
});
document.getElementById('exportSalesBtn').addEventListener('click', exportSales);
document.getElementById('printReceiptBtn').addEventListener('click', printReceipt);
document.getElementById('voidSaleBtn').addEventListener('click', showVoidSaleModal);
document.getElementById('cancelVoidBtn').addEventListener('click', hideVoidSaleModal);
document.getElementById('confirmVoidBtn').addEventListener('click', voidSale);
// Close modals when clicking on X
document.querySelectorAll('.close-modal').forEach(button => {
button.addEventListener('click', function() {
this.closest('.modal').classList.remove('show');
});
});
});
// Global variables
let currentPage = 1;
let totalPages = 1;
let itemsPerPage = 20;
let currentSaleId = null;
// Initialize sales management
function initSales() {
loadSales();
}
// Set default dates (current month)
function setDefaultDates() {
const today = new Date();
const firstDay = new Date(today.getFullYear(), today.getMonth(), 1);
const todayStr = formatDateForInput(today);
const firstDayStr = formatDateForInput(firstDay);
document.getElementById('dateFrom').value = firstDayStr;
document.getElementById('dateTo').value = todayStr;
}
// Format date for input fields (YYYY-MM-DD)
function formatDateForInput(date) {
const year = date.getFullYear();
const month = String(date.getMonth() + 1).padStart(2, '0');
const day = String(date.getDate()).padStart(2, '0');
return `${year}-${month}-${day}`;
}
// Load sales with filters and pagination
async function loadSales() {
try {
const dateFrom = document.getElementById('dateFrom').value;
const dateTo = document.getElementById('dateTo').value;
const paymentStatus = document.getElementById('paymentStatusFilter').value;
const paymentMethod = document.getElementById('paymentMethodFilter').value;
const searchTerm = document.getElementById('saleSearch').value;
const params = new URLSearchParams({
page: currentPage,
limit: itemsPerPage
});
if (dateFrom) params.append('date_from', dateFrom);
if (dateTo) params.append('date_to', dateTo);
if (paymentStatus) params.append('payment_status', paymentStatus);
if (paymentMethod) params.append('payment_method', paymentMethod);
if (searchTerm) params.append('search', searchTerm);
const response = await apiRequest(`sales/list?${params.toString()}`);
if (response.status === 'success') {
renderSalesTable(response.data.sales);
// Update pagination
totalPages = response.data.pagination.pages;
currentPage = response.data.pagination.page;
renderPagination(response.data.pagination);
} else {
showNotification(response.message || 'Failed to load sales', 'error');
}
} catch (error) {
console.error('Error loading sales:', error);
showNotification('Error loading sales', 'error');
}
}
// Render sales table
function renderSalesTable(sales) {
const tableBody = document.querySelector('#salesTable tbody');
tableBody.innerHTML = '';
if (sales.length === 0) {
const row = document.createElement('tr');
row.innerHTML = '
No sales found ';
tableBody.appendChild(row);
return;
}
sales.forEach(sale => {
const row = document.createElement('tr');
// Format date and time
const dateTime = new Date(sale.created_at).toLocaleString();
// Determine status badge class
let statusBadgeClass = '';
switch (sale.payment_status) {
case 'paid':
statusBadgeClass = 'badge-success';
break;
case 'partial':
statusBadgeClass = 'badge-warning';
break;
case 'pending':
statusBadgeClass = 'badge-info';
break;
case 'voided':
statusBadgeClass = 'badge-danger';
break;
}
row.innerHTML = `
${sale.reference_no}
${dateTime}
${sale.customer_name || 'Walk-in Customer'}
${sale.item_count}
${formatCurrency(sale.grand_total)}
${capitalizeFirstLetter(sale.payment_method.replace('_', ' '))}
${sale.payment_status}
${sale.payment_status !== 'voided' ?
`
` : ''}
`;
tableBody.appendChild(row);
});
// Add event listeners for action buttons
document.querySelectorAll('.view-sale').forEach(button => {
button.addEventListener('click', function() {
const saleId = this.dataset.id;
viewSaleDetails(saleId);
});
});
document.querySelectorAll('.print-receipt').forEach(button => {
button.addEventListener('click', function() {
const saleId = this.dataset.id;
getSaleDetails(saleId, true);
});
});
document.querySelectorAll('.void-sale').forEach(button => {
button.addEventListener('click', function() {
const saleId = this.dataset.id;
currentSaleId = saleId;
document.getElementById('voidSaleId').value = saleId;
document.getElementById('voidSaleModal').classList.add('show');
});
});
}
// Render pagination
function renderPagination(pagination) {
const paginationContainer = document.getElementById('salesPagination');
paginationContainer.innerHTML = '';
if (pagination.pages <= 1) {
return;
}
// Create pagination elements
const prevBtn = document.createElement('button');
prevBtn.classList.add('pagination-btn');
prevBtn.disabled = pagination.page === 1;
prevBtn.innerHTML = ' ';
prevBtn.addEventListener('click', () => changePage(pagination.page - 1));
const nextBtn = document.createElement('button');
nextBtn.classList.add('pagination-btn');
nextBtn.disabled = pagination.page === pagination.pages;
nextBtn.innerHTML = ' ';
nextBtn.addEventListener('click', () => changePage(pagination.page + 1));
const pageInfo = document.createElement('span');
pageInfo.classList.add('pagination-info');
pageInfo.textContent = `Page ${pagination.page} of ${pagination.pages}`;
paginationContainer.appendChild(prevBtn);
paginationContainer.appendChild(pageInfo);
paginationContainer.appendChild(nextBtn);
}
// Change page
function changePage(page) {
currentPage = page;
loadSales();
}
// View sale details
async function viewSaleDetails(saleId) {
try {
currentSaleId = saleId;
const sale = await getSaleDetails(saleId);
if (sale) {
renderSaleDetails(sale);
document.getElementById('saleDetailModal').classList.add('show');
}
} catch (error) {
console.error('Error viewing sale details:', error);
showNotification('Error loading sale details', 'error');
}
}
// Get sale details
async function getSaleDetails(saleId, printReceipt = false) {
try {
const response = await apiRequest(`sales/details?id=${saleId}`);
if (response.status === 'success') {
if (printReceipt) {
printSaleReceipt(response.data);
}
return response.data;
} else {
showNotification(response.message || 'Failed to load sale details', 'error');
return null;
}
} catch (error) {
console.error('Error getting sale details:', error);
showNotification('Error loading sale details', 'error');
return null;
}
}
// Render sale details in modal
function renderSaleDetails(sale) {
// Sale header information
document.getElementById('saleReference').textContent = sale.reference_no;
document.getElementById('saleDate').textContent = new Date(sale.created_at).toLocaleString();
document.getElementById('saleCustomer').textContent = sale.customer_name || 'Walk-in Customer';
document.getElementById('saleCashier').textContent = sale.user_full_name || sale.user_name;
// Sale status
const statusElem = document.getElementById('saleStatus');
statusElem.textContent = sale.payment_status;
statusElem.className = '';
// Add status color
switch (sale.payment_status) {
case 'paid':
statusElem.classList.add('text-success');
break;
case 'partial':
statusElem.classList.add('text-warning');
break;
case 'pending':
statusElem.classList.add('text-info');
break;
case 'voided':
statusElem.classList.add('text-danger');
break;
}
document.getElementById('salePaymentMethod').textContent = capitalizeFirstLetter(sale.payment_method.replace('_', ' '));
// Sale items
const tableBody = document.querySelector('#saleItemsTable tbody');
tableBody.innerHTML = '';
sale.items.forEach(item => {
const row = document.createElement('tr');
row.innerHTML = `
${item.product_name}
${item.sku}
${item.quantity}
${formatCurrency(item.unit_price)}
${formatCurrency(item.discount)}
${formatCurrency(item.total)}
`;
tableBody.appendChild(row);
});
// Sale summary
document.getElementById('saleSubtotal').textContent = formatCurrency(sale.total_amount);
document.getElementById('saleDiscount').textContent = formatCurrency(sale.discount_amount);
document.getElementById('saleTax').textContent = formatCurrency(sale.tax_amount);
document.getElementById('saleTotal').textContent = formatCurrency(sale.grand_total);
// Sale notes
document.getElementById('saleNotes').textContent = sale.notes || '-';
// Toggle void button visibility based on status
const voidBtn = document.getElementById('voidSaleBtn');
voidBtn.style.display = sale.payment_status === 'voided' ? 'none' : 'inline-block';
}
// Show void sale modal
function showVoidSaleModal() {
document.getElementById('voidReason').value = '';
document.getElementById('voidSaleModal').classList.add('show');
}
// Hide void sale modal
function hideVoidSaleModal() {
document.getElementById('voidSaleModal').classList.remove('show');
}
// Void sale
async function voidSale() {
try {
const saleId = document.getElementById('voidSaleId').value;
const reason = document.getElementById('voidReason').value.trim();
if (!reason) {
showNotification('Please provide a reason for voiding this sale', 'error');
return;
}
const response = await apiRequest(`sales/void?id=${saleId}`, 'POST', {reason});
if (response.status === 'success') {
showNotification('Sale voided successfully', 'success');
hideVoidSaleModal();
document.getElementById('saleDetailModal').classList.remove('show');
loadSales(); // Reload sales table
} else {
showNotification(response.message || 'Failed to void sale', 'error');
}
} catch (error) {
console.error('Error voiding sale:', error);
showNotification('Error voiding sale', 'error');
}
}
// Export sales to CSV
// Export sales to CSV
function exportSales() {
try {
// แสดง notification กำลังทำงาน
showNotification('Creating sales export...', 'info');
// รับค่าจากฟิลเตอร์ต่างๆ
const dateFrom = document.getElementById('dateFrom').value;
const dateTo = document.getElementById('dateTo').value;
const paymentStatus = document.getElementById('paymentStatusFilter').value;
const paymentMethod = document.getElementById('paymentMethodFilter').value;
const searchTerm = document.getElementById('saleSearch').value;
// สร้างพารามิเตอร์สำหรับ API request
const params = new URLSearchParams();
if (dateFrom) params.append('date_from', dateFrom);
if (dateTo) params.append('date_to', dateTo);
if (paymentStatus) params.append('payment_status', paymentStatus);
if (paymentMethod) params.append('payment_method', paymentMethod);
if (searchTerm) params.append('search', searchTerm);
// ดึงข้อมูลจาก API โดยไม่มีการจำกัดจำนวนรายการ
params.append('limit', 1000); // ตั้งค่าให้สูงเพื่อให้ได้ข้อมูลมากที่สุด
// เรียกใช้ API เพื่อดึงข้อมูล
apiRequest(`sales/list?${params.toString()}`)
.then(response => {
if (response.status === 'success') {
// สร้างเนื้อหา CSV
let csvContent = 'Reference #,Date,Customer,Items,Subtotal,Discount,Tax,Total,Payment Method,Status,Cashier,Notes\n';
// เพิ่มข้อมูลแต่ละแถว
response.data.sales.forEach(sale => {
// แก้ไขค่าที่อาจมีเครื่องหมาย comma เพื่อป้องกันปัญหากับ CSV
const escapeCSV = (text) => {
// ถ้าเป็น string และมีเครื่องหมาย " หรือ , ให้ครอบด้วย " และแทนที่ " เป็น ""
if (typeof text === 'string') {
if (text.includes('"') || text.includes(',')) {
return `"${text.replace(/"/g, '""')}"`;
}
}
return text;
};
csvContent += [
escapeCSV(sale.reference_no),
escapeCSV(sale.created_at),
escapeCSV(sale.customer_name || 'Walk-in Customer'),
sale.item_count,
sale.total_amount,
sale.discount_amount,
sale.tax_amount,
sale.grand_total,
escapeCSV(sale.payment_method),
escapeCSV(sale.payment_status),
escapeCSV(sale.user_name),
escapeCSV(sale.notes || '')
].join(',') + '\n';
});
// สร้างชื่อไฟล์
const filename = `sales_export_${formatDateForFilename(new Date())}.csv`;
// สร้าง Blob และ download link
const blob = new Blob([csvContent], {type: 'text/csv;charset=utf-8;'});
const url = URL.createObjectURL(blob);
const link = document.createElement('a');
link.setAttribute('href', url);
link.setAttribute('download', filename);
link.style.visibility = 'hidden';
// เพิ่ม link ลงใน document และคลิกเพื่อดาวน์โหลด
document.body.appendChild(link);
link.click();
document.body.removeChild(link);
// แสดง notification เมื่อสำเร็จ
showNotification('Sales data exported successfully', 'success');
} else {
showNotification(response.message || 'Failed to retrieve sales data', 'error');
}
})
.catch(error => {
console.error('Error exporting sales:', error);
showNotification('Error exporting sales data', 'error');
});
} catch (error) {
console.error('Error in export process:', error);
showNotification('Error processing export', 'error');
}
}
// Helper function to format date for filename (YYYY-MM-DD)
function formatDateForFilename(date) {
const year = date.getFullYear();
const month = String(date.getMonth() + 1).padStart(2, '0');
const day = String(date.getDate()).padStart(2, '0');
return `${year}-${month}-${day}`;
}
// Print receipt
async function printReceipt() {
if (!currentSaleId) {
showNotification('No sale selected', 'error');
return;
}
try {
const sale = await getSaleDetails(currentSaleId);
if (sale) {
printSaleReceipt(sale);
}
} catch (error) {
console.error('Error printing receipt:', error);
showNotification('Error printing receipt', 'error');
}
}
// Print sale receipt
function printSaleReceipt(sale) {
// Generate items HTML
let itemsHtml = '';
sale.items.forEach(item => {
itemsHtml += `
${item.product_name}
${item.quantity}
${formatCurrency(item.unit_price)}
${formatCurrency(item.total)}
`;
});
// Create print window
const printWindow = window.open('', '_blank');
// Generate receipt HTML
printWindow.document.write(`
Receipt #${sale.reference_no}
Receipt #: ${sale.reference_no}
Date: ${new Date(sale.created_at).toLocaleString()}
Cashier: ${sale.user_full_name || sale.user_name}
Customer: ${sale.customer_name || 'Walk-in Customer'}
Item
Qty
Price
Total
${itemsHtml}
Subtotal:
${formatCurrency(sale.total_amount)}
Discount:
${formatCurrency(sale.discount_amount)}
Tax:
${formatCurrency(sale.tax_amount)}
Total:
${formatCurrency(sale.grand_total)}
Payment Method:
${capitalizeFirstLetter(sale.payment_method.replace('_', ' '))}
${sale.payment_status === 'voided' ? 'VOIDED
' : ''}
`);
printWindow.document.close();
// Print after a small delay to ensure content is loaded
setTimeout(() => {
printWindow.print();
}, 500);
}
// Capitalize first letter of each word
function capitalizeFirstLetter(string) {
return string.split(' ')
.map(word => word.charAt(0).toUpperCase() + word.slice(1))
.join(' ');
}