sales.js

20.98 KB
27/04/2025 16:32
JS
sales.js
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 = '<td colspan="8" class="text-center">No sales found</td>';
    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 = `
      <td>${sale.reference_no}</td>
      <td>${dateTime}</td>
      <td>${sale.customer_name || 'Walk-in Customer'}</td>
      <td>${sale.item_count}</td>
      <td>${formatCurrency(sale.grand_total)}</td>
      <td>${capitalizeFirstLetter(sale.payment_method.replace('_', ' '))}</td>
      <td><span class="badge ${statusBadgeClass}">${sale.payment_status}</span></td>
      <td class="actions">
        <button class="btn btn-sm btn-info view-sale" data-id="${sale.id}">
          <i class="icon-preview"></i>
        </button>
        <button class="btn btn-sm btn-secondary print-receipt" data-id="${sale.id}">
          <i class="icon-print"></i>
        </button>
        ${sale.payment_status !== 'voided' ?
        `<button class="btn btn-sm btn-danger void-sale" data-id="${sale.id}">
            <i class="icon-ban"></i>
          </button>` : ''}
      </td>
    `;

    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 = '<i class="icon-move_left"></i>';
  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 = '<i class="icon-move_right"></i>';
  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 = `
      <td>${item.product_name}</td>
      <td>${item.sku}</td>
      <td>${item.quantity}</td>
      <td>${formatCurrency(item.unit_price)}</td>
      <td>${formatCurrency(item.discount)}</td>
      <td>${formatCurrency(item.total)}</td>
    `;
    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 += `
      <tr>
        <td>${item.product_name}</td>
        <td>${item.quantity}</td>
        <td>${formatCurrency(item.unit_price)}</td>
        <td>${formatCurrency(item.total)}</td>
      </tr>
    `;
  });

  // Create print window
  const printWindow = window.open('', '_blank');

  // Generate receipt HTML
  printWindow.document.write(`
    <!DOCTYPE html>
    <html>
    <head>
      <title>Receipt #${sale.reference_no}</title>
      <style>
        body {
          font-family: 'Courier New', monospace;
          font-size: 12px;
          margin: 0;
          padding: 20px;
          width: 80mm;
          margin: 0 auto;
        }
        .receipt-header {
          text-align: center;
          margin-bottom: 20px;
        }
        .receipt-header h2 {
          margin: 0;
          font-size: 16px;
        }
        .receipt-header p {
          margin: 5px 0;
        }
        .receipt-info {
          margin-bottom: 15px;
        }
        .receipt-info p {
          margin: 3px 0;
        }
        .receipt-table {
          width: 100%;
          border-collapse: collapse;
          margin-bottom: 15px;
        }
        .receipt-table th, .receipt-table td {
          text-align: left;
          padding: 3px 0;
        }
        .receipt-table th {
          border-bottom: 1px dashed #000;
        }
        .receipt-table td:last-child, .receipt-table th:last-child {
          text-align: right;
        }
        .receipt-summary {
          margin-top: 10px;
        }
        .summary-row {
          display: flex;
          justify-content: space-between;
          margin-bottom: 5px;
        }
        .total-row {
          font-weight: 400;
          border-top: 1px dashed #000;
          padding-top: 5px;
          margin-top: 5px;
        }
        .receipt-footer {
          text-align: center;
          margin-top: 20px;
          padding-top: 10px;
          border-top: 1px dashed #000;
        }
        .receipt-footer p {
          margin: 5px 0;
        }
        .voided-stamp {
          position: absolute;
          top: 50%;
          left: 50%;
          transform: translate(-50%, -50%) rotate(-30deg);
          font-size: 48px;
          opacity: 0.5;
          color: #dc3545;
          border: 5px solid #dc3545;
          padding: 10px;
          border-radius: 10px;
          font-weight: 400;
          pointer-events: none;
        }
      </style>
    </head>
    <body>
      <div class="receipt-header">
        <h2>My POS Store</h2>
        <p>123 Main Street, Anytown</p>
        <p>Tel: 123-456-7890</p>
      </div>

      <div class="receipt-info">
        <p><strong>Receipt #:</strong> ${sale.reference_no}</p>
        <p><strong>Date:</strong> ${new Date(sale.created_at).toLocaleString()}</p>
        <p><strong>Cashier:</strong> ${sale.user_full_name || sale.user_name}</p>
        <p><strong>Customer:</strong> ${sale.customer_name || 'Walk-in Customer'}</p>
      </div>

      <table class="receipt-table">
        <thead>
          <tr>
            <th>Item</th>
            <th>Qty</th>
            <th>Price</th>
            <th>Total</th>
          </tr>
        </thead>
        <tbody>
          ${itemsHtml}
        </tbody>
      </table>

      <div class="receipt-summary">
        <div class="summary-row">
          <div>Subtotal:</div>
          <div>${formatCurrency(sale.total_amount)}</div>
        </div>
        <div class="summary-row">
          <div>Discount:</div>
          <div>${formatCurrency(sale.discount_amount)}</div>
        </div>
        <div class="summary-row">
          <div>Tax:</div>
          <div>${formatCurrency(sale.tax_amount)}</div>
        </div>
        <div class="summary-row total-row">
          <div>Total:</div>
          <div>${formatCurrency(sale.grand_total)}</div>
        </div>
        <div class="summary-row">
          <div>Payment Method:</div>
          <div>${capitalizeFirstLetter(sale.payment_method.replace('_', ' '))}</div>
        </div>
      </div>

      <div class="receipt-footer">
        <p>Thank you for your purchase!</p>
        <p>${new Date().getFullYear()} My POS Store</p>
      </div>

      ${sale.payment_status === 'voided' ? '<div class="voided-stamp">VOIDED</div>' : ''}
    </body>
    </html>
  `);

  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(' ');
}