Skip to main content

Overview

The POS component (pos.js) is the core transaction processing interface. It handles product selection, cart management, discount calculations, checkout operations, and thermal receipt generation.

Responsibilities

  • Display available products for sale
  • Manage shopping cart (add, update, remove items)
  • Calculate subtotals, discounts, and grand totals
  • Process sales and save to database
  • Generate and print thermal receipts
  • Handle both quick checkout and print-with-checkout flows

State Management

Cart State

let cart = [];
The cart is an array of cart items with structure:
{
  id: number,
  name: string,
  price: number,
  qty: number
}

DOM Elements

  • #productGrid - Container for product buttons
  • #cartItems - Container for cart item list
  • #itemCount - Display total item quantity
  • #subtotal - Display subtotal amount
  • #discountAmount - Display calculated discount
  • #grandTotal - Display final total
  • #discountValue - Input for discount value
  • #discountType - Select for discount type (percent/fixed)
  • #clearCart - Button to clear entire cart
  • #checkoutBtn - Quick checkout button
  • #printBillBtn - Checkout with print button

Functions

renderProducts()

Fetches and displays all available products as clickable buttons.
async function renderProducts()
Behavior:
  • Fetches products from database
  • Shows empty state if no products
  • Renders product grid with name and price
  • Attaches click handlers to add products to cart
Example Output:
productGrid.innerHTML = products.map(p => `
  <button data-id="${p.id}" data-name="${p.name}" data-price="${p.price}" 
          class="add-product border-2 border-gray-200 rounded-lg p-4 hover:border-indigo-500">
    <div class="font-semibold text-gray-900 mb-1">${p.name}</div>
    <div class="text-lg font-bold text-indigo-600">₹${money(p.price)}</div>
  </button>
`).join('');

addToCart(prod)

Adds a product to the cart or increments quantity if already present.
prod
object
required
Product object with id, name, and price
function addToCart(prod) {
  const found = cart.find(c => c.id === prod.id);
  if (found) found.qty += 1;
  else cart.push({ ...prod, qty: 1 });
  renderCart();
  showToast(`${prod.name} added to cart`, 'success');
}

calculateDiscount(subtotal)

Calculates discount amount based on type and value.
subtotal
number
required
The cart subtotal before discount
Returns: number - Discount amount
function calculateDiscount(subtotal) {
  const value = parseFloat(discountValueEl.value) || 0;
  const type = discountTypeEl.value;
  
  if (type === 'percent') {
    return (subtotal * (value / 100));
  }
  return value;
}
Discount Types:
  • percent - Percentage-based discount (e.g., 10% off)
  • fixed - Fixed amount discount (e.g., ₹50 off)

renderCart()

Renders the shopping cart UI and updates all totals.
function renderCart()
Behavior:
  1. Shows empty state if cart has no items
  2. Renders each cart item with quantity controls
  3. Calculates subtotal, discount, and grand total
  4. Attaches event handlers for increment, decrement, and remove buttons
Cart Item Controls:
  • Decrement (−): Reduces quantity (minimum 1)
  • Increment (+): Increases quantity
  • Remove (×): Removes item from cart
Example Cart Item Rendering:
cartItems.innerHTML = cart.map((c, idx) => {
  itemCount += c.qty;
  subtotal += c.qty * c.price;
  return `
    <div class="flex items-center justify-between p-3 bg-gray-50 rounded-lg">
      <div class="flex-1">
        <div class="font-medium text-gray-900">${c.name}</div>
        <div class="text-sm text-gray-600">₹${money(c.price)} × ${c.qty}</div>
      </div>
      <div class="flex items-center gap-2">
        <button data-idx="${idx}" class="dec-btn">−</button>
        <button data-idx="${idx}" class="inc-btn">+</button>
        <button data-idx="${idx}" class="rem-btn">×</button>
      </div>
    </div>
  `;
}).join('');

generateThermalReceipt()

Generates a formatted thermal receipt string (32 characters wide). Returns: string - Formatted receipt text
function generateThermalReceipt() {
  const date = new Date().toLocaleString();
  const subtotal = Number(subtotalEl.textContent.slice(1));
  const discount = Number(discountAmountEl.textContent.slice(2));
  const total = Number(grandTotalEl.textContent.slice(1));
  
  let receipt = [
    'Mini POS - Codecraft by Syed',
    '--------------------------------',
    `Date: ${date}`,
    '--------------------------------',
    'Items:',
    ...cart.map(item => [
      item.name,
      `${item.qty} x ₹${money(item.price)}${money(item.qty * item.price)}`
    ]).flat(),
    '--------------------------------',
    `Subtotal:          ₹${money(subtotal)}`,
    `Discount:         -₹${money(discount)}`,
    '--------------------------------',
    `Total:             ₹${money(total)}`,
    '--------------------------------',
    '',
    '       Thank You!',
    '    Please Visit Again',
    ''
  ].join('\\n');

  return receipt;
}

printReceipt(receipt)

Opens a print dialog with formatted thermal receipt.
receipt
string
required
The formatted receipt text from generateThermalReceipt()
Behavior:
  • Opens a 300x600px popup window
  • Injects receipt HTML with thermal printer styling (80mm width)
  • Automatically triggers print dialog
  • Closes window after printing
function printReceipt(receipt) {
  const printWindow = window.open('', '', 'width=300,height=600');
  printWindow.document.write(`
    <html>
      <head>
        <style>
          body {
            font-family: 'Courier New', monospace;
            font-size: 12px;
            width: 80mm;
            margin: 0;
            padding: 10px;
          }
        </style>
      </head>
      <body>
        <pre>${receipt}</pre>
        <script>
          window.onload = function() {
            window.print();
            window.onafterprint = function() {
              window.close();
            };
          };
        </script>
      </body>
    </html>
  `);
}

processSale(withPrint)

Processes the current cart as a sale and optionally prints receipt.
withPrint
boolean
default:"false"
Whether to print a thermal receipt after checkout
Returns: Promise<void> Behavior:
  1. Validates cart is not empty
  2. Collects subtotal, discount, and total from DOM
  3. Creates sale object with timestamp and items
  4. Saves sale to database via addSale()
  5. Optionally generates and prints receipt
  6. Clears cart and shows success toast
async function processSale(withPrint = false) {
  if (cart.length === 0) {
    showToast('Cart is empty', 'error');
    return;
  }

  const sale = {
    date: new Date().toISOString(),
    items: cart.map(({ id, name, price, qty }) => ({ id, name, price, qty })),
    subtotal,
    discount,
    total
  };

  await addSale(sale);
  
  if (withPrint) {
    const receipt = generateThermalReceipt();
    printReceipt(receipt);
  }
  
  cart = [];
  renderCart();
  showToast('Sale completed successfully!', 'success');
}

Event Handlers

Clear Cart

clearCartBtn.addEventListener('click', () => {
  if (cart.length > 0 && confirm('Clear cart?')) {
    cart = [];
    renderCart();
    showToast('Cart cleared');
  }
});

Discount Input Changes

Auto-recalculates totals when discount value or type changes:
[discountValueEl, discountTypeEl].forEach(el => {
  el.addEventListener('input', renderCart);
});

Checkout Buttons

// Quick checkout without printing
checkoutBtn.addEventListener('click', () => processSale(false));

// Checkout with bill printing
document.getElementById('printBillBtn').addEventListener('click', () => processSale(true));

Dependencies

db.js
module
Provides getProducts() and addSale() functions
shared.js
module
Provides money() formatting and showToast() notification functions

Auto-Execution

On module load:
renderProducts();
renderCart();

Build docs developers (and LLMs) love