Skip to main content
The POS (Point of Sale) Interface is the core transaction processing module, allowing you to quickly add products to a cart, apply discounts, and complete sales.

Overview

The POS interface is split into two main sections:
  • Product Grid: Browse and select products to add to cart
  • Shopping Cart: Review items, adjust quantities, apply discounts, and checkout

Fast Product Selection

Click any product to add it to the cart instantly

Flexible Discounts

Apply percentage or fixed amount discounts

Thermal Receipts

Generate print-ready receipts for customers

Cart Management

Adjust quantities or remove items on the fly

Product Selection

Products are displayed in a responsive grid, with each product showing its name and price:
js/pos.js
import { getProducts, addSale } from './db.js';
import { money, showToast } from './shared.js';

let cart = [];

async function renderProducts() {
  const products = await getProducts();
  
  if (products.length === 0) {
    productGrid.innerHTML = '<div class="col-span-full text-center text-gray-500 py-8">No products available</div>';
    return;
  }

  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 hover:bg-indigo-50 transition text-left">
      <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('');

  document.querySelectorAll('.add-product').forEach(btn => {
    btn.addEventListener('click', () => {
      const prod = {
        id: Number(btn.dataset.id),
        name: btn.dataset.name,
        price: Number(btn.dataset.price)
      };
      addToCart(prod);
    });
  });
}
Products highlight on hover with an indigo border, providing clear visual feedback that they’re clickable.

Cart Management

The cart automatically handles duplicate products by incrementing quantity:
js/pos.js
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');
}

Cart Display

Each cart item shows quantity controls and a remove button:
js/pos.js
function renderCart() {
  if (cart.length === 0) {
    cartItems.innerHTML = '<div class="text-center text-gray-500 py-4">Cart is empty</div>';
    itemCountEl.textContent = '0';
    subtotalEl.textContent = '₹0.00';
    discountAmountEl.textContent = '-₹0.00';
    grandTotalEl.textContent = '₹0.00';
    return;
  }

  let itemCount = 0;
  let subtotal = 0;

  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 w-8 h-8 rounded border border-gray-300 hover:bg-gray-100">−</button>
          <button data-idx="${idx}" class="inc-btn w-8 h-8 rounded border border-gray-300 hover:bg-gray-100">+</button>
          <button data-idx="${idx}" class="rem-btn w-8 h-8 rounded border border-red-300 text-red-600 hover:bg-red-50">×</button>
        </div>
      </div>
    `;
  }).join('');

  const discount = calculateDiscount(subtotal);
  const total = Math.max(0, subtotal - discount);

  itemCountEl.textContent = itemCount;
  subtotalEl.textContent = `₹${money(subtotal)}`;
  discountAmountEl.textContent = `-₹${money(discount)}`;
  grandTotalEl.textContent = `₹${money(total)}`;
}
Quantities can be increased or decreased, but the minimum quantity is always 1. Use the remove button (×) to delete items entirely.

Discount System

The POS supports two discount types that update in real-time:
js/pos.js
function calculateDiscount(subtotal) {
  const value = parseFloat(discountValueEl.value) || 0;
  const type = discountTypeEl.value;
  
  if (type === 'percent') {
    return (subtotal * (value / 100));
  }
  return value;
}

// Add discount input listeners
[discountValueEl, discountTypeEl].forEach(el => {
  el.addEventListener('input', renderCart);
});

Discount Types

Enter a percentage value (e.g., “10” for 10% off). The discount amount is calculated based on the subtotal.Example: ₹1000 subtotal with 10% = ₹100 discount

Checkout Process

Two checkout options are available:
js/pos.js
async function processSale(withPrint = false) {
  if (cart.length === 0) {
    showToast('Cart is empty', 'error');
    return;
  }

  const subtotal = Number(subtotalEl.textContent.slice(1));
  const discount = Number(discountAmountEl.textContent.slice(2));
  const total = Number(grandTotalEl.textContent.slice(1));

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

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

// Checkout with bill printing
document.getElementById('printBillBtn').addEventListener('click', () => processSale(true));
Use the quick checkout for digital-only transactions, or print bill for customers who need physical receipts.

Thermal Receipt Generation

The system generates formatted thermal printer receipts:
js/pos.js
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));
  
  // Receipt is 32 characters wide for thermal printer
  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;
}
Receipts are formatted to 32 characters width, optimized for standard 80mm thermal printers.

Cart Controls

Clear Cart

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

Quantity Adjustments

js/pos.js
// Decrease quantity (minimum 1)
document.querySelectorAll('.dec-btn').forEach(btn => {
  btn.addEventListener('click', () => {
    const idx = Number(btn.dataset.idx);
    cart[idx].qty = Math.max(1, cart[idx].qty - 1);
    renderCart();
  });
});

// Increase quantity
document.querySelectorAll('.inc-btn').forEach(btn => {
  btn.addEventListener('click', () => {
    const idx = Number(btn.dataset.idx);
    cart[idx].qty += 1;
    renderCart();
  });
});

// Remove item
document.querySelectorAll('.rem-btn').forEach(btn => {
  btn.addEventListener('click', () => {
    const idx = Number(btn.dataset.idx);
    cart.splice(idx, 1);
    renderCart();
  });
});

User Workflow

  1. Products load from database on page load
  2. Click product cards to add them to cart
  3. Cart updates automatically showing quantity and price
  4. Adjust quantities using +/- buttons as needed
  5. Optionally apply a discount (percentage or fixed)
  6. Review the grand total
  7. Choose checkout option:
    • Quick Checkout: Saves sale without printing
    • Print Bill: Saves sale and opens print dialog
  8. Cart clears automatically after checkout
  9. Success message confirms completion

Best Practices

  1. Review Before Checkout: Always verify cart contents and totals
  2. Discount Clarity: Communicate discount type clearly to customers
  3. Receipt Printing: Test thermal printer settings for optimal output
  4. Cart Management: Use clear cart sparingly to avoid accidental data loss
  5. Quick Sales: For regular customers, quick checkout saves time

Build docs developers (and LLMs) love