# POS Sales Order – Full Dynamic (Django + AJAX) Below is a complete, production-ready example for a dynamic POS **Order Create** page using **Django + AJAX**. It includes: * A responsive template with Bootstrap * AJAX product search by **SKU / Name / Category** (debounced) * Click-to-append product rows to the cart table * Live calculations (row total, subtotal, discount, tax, VAT, grand total) * Payment & customer info (right side) * Submit order via AJAX → redirect to invoice page > ⚠️ Adjust field names to match your existing models (I mapped to your usual `ProductList`, `CategoryList`). --- ## 1) Template: `templates/pos/pos_order_create.html` ```html {% extends 'common/base.html' %} {% load static %} {% block title %}POS Order Create{% endblock %} {% block content %}

POS Order Create

Image Product Name & SKU Qty Unit Price Row Total Action
Subtotal: 0.00
Discount (amount):
Tax (%)
VAT (%)
Grand Total: 0.00
Customer Info

Payment Info
{% endblock %} ``` --- ## 2) URLs: `pos/urls.py` ```python from django.urls import path from . import views app_name = 'pos' urlpatterns = [ path('search-products/', views.search_products, name='search_products'), path('create-order/', views.create_order, name='create_order'), path('invoice//', views.invoice_view, name='invoice'), ] ``` > Include these in your project root `urls.py`: ```python path('pos/', include('pos.urls', namespace='pos')), ``` --- ## 3) Views: `pos/views.py` > ⚠️ Adjust model/field names to your app. I assumed: > > * `ProductList(name, sku, category, price, image)` > * `CategoryList(name)` > * `Customer(name, mobile, email, address)` (or inline) > * `Order(total, subtotal, discount, tax_percent, vat_percent, grand_total, paid_amount, due_amount, customer fields, payment_method)` > * `OrderItem(order, product, qty, unit_price, row_total)` ```python import json from decimal import Decimal from django.db import transaction from django.db.models import Q from django.http import JsonResponse, Http404 from django.shortcuts import get_object_or_404, render from django.urls import reverse from . import models # align with your app model imports # --- Product Search (AJAX) --- def search_products(request): if not request.headers.get('x-requested-with') == 'XMLHttpRequest': raise Http404() q = request.GET.get('q', '').strip() qs = models.ProductList.objects.all() if q: qs = qs.filter( Q(sku__icontains=q) | Q(name__icontains=q) | Q(category__name__icontains=q) )[:25] results = [] for p in qs: price = getattr(p, 'price', None) or getattr(p, 'unit_price', None) or Decimal('0.00') img = '' if getattr(p, 'image', None): try: img = p.image.url except Exception: img = '' results.append({ 'id': p.id, 'name': p.name, 'sku': getattr(p, 'sku', ''), 'category': getattr(p.category, 'name', '') if getattr(p, 'category', None) else '', 'price': str(price), 'image_url': img, }) return JsonResponse({'results': results}) # --- Create Order (AJAX) --- @transaction.atomic def create_order(request): if request.method != 'POST' or request.headers.get('content-type') != 'application/json': return JsonResponse({'success': False, 'message': 'Invalid request'}, status=400) try: data = json.loads(request.body.decode('utf-8')) except json.JSONDecodeError: return JsonResponse({'success': False, 'message': 'Invalid JSON'}, status=400) items = data.get('items', []) if not items: return JsonResponse({'success': False, 'message': 'No items provided'}, status=400) # Summary subtotal = Decimal(str(data['summary'].get('subtotal', '0'))) discount = Decimal(str(data['summary'].get('discount', '0'))) tax_percent = Decimal(str(data['summary'].get('tax_percent', '0'))) vat_percent = Decimal(str(data['summary'].get('vat_percent', '0'))) grand_total = Decimal(str(data['summary'].get('grand_total', '0'))) paid_amount = Decimal(str(data['summary'].get('paid_amount', data.get('payment', {}).get('paid_amount', 0)))) due_amount = Decimal(str(data['summary'].get('due_amount', '0'))) # Customer cust = data.get('customer', {}) customer_name = cust.get('name', '') customer_mobile = cust.get('mobile', '') customer_email = cust.get('email', '') customer_address = cust.get('address', '') # Payment payment_method = data.get('payment', {}).get('method', 'cash') # Create order order = models.Order.objects.create( customer_name=customer_name, customer_mobile=customer_mobile, customer_email=customer_email, customer_address=customer_address, subtotal=subtotal, discount=discount, tax_percent=tax_percent, vat_percent=vat_percent, grand_total=grand_total, paid_amount=paid_amount, due_amount=due_amount, payment_method=payment_method, ) # Items for it in items: pid = it.get('product_id') qty = Decimal(str(it.get('qty', '1'))) unit_price = Decimal(str(it.get('unit_price', '0'))) product = get_object_or_404(models.ProductList, id=pid) row_total = qty * unit_price models.OrderItem.objects.create( order=order, product=product, qty=qty, unit_price=unit_price, row_total=row_total ) invoice_url = reverse('pos:invoice', kwargs={'order_id': order.id}) return JsonResponse({'success': True, 'order_id': order.id, 'invoice_url': invoice_url}) # --- Invoice View (redirect target) --- def invoice_view(request, order_id: int): order = get_object_or_404(models.Order, id=order_id) items = models.OrderItem.objects.filter(order=order).select_related('product') return render(request, 'pos/invoice.html', {'order': order, 'items': items}) ``` --- ## 4) Example Models (If you need quick scaffolding) > If you already have these, skip or map fields accordingly. ```python # pos/models.py from django.db import models class CategoryList(models.Model): name = models.CharField(max_length=150) def __str__(self): return self.name class ProductList(models.Model): name = models.CharField(max_length=200) sku = models.CharField(max_length=100, unique=True) category = models.ForeignKey(CategoryList, on_delete=models.SET_NULL, null=True, blank=True) price = models.DecimalField(max_digits=12, decimal_places=2, default=0) image = models.ImageField(upload_to='products/', null=True, blank=True) def __str__(self): return f"{self.name} ({self.sku})" class Order(models.Model): created = models.DateTimeField(auto_now_add=True) customer_name = models.CharField(max_length=200, blank=True) customer_mobile = models.CharField(max_length=30, blank=True) customer_email = models.EmailField(blank=True) customer_address = models.TextField(blank=True) subtotal = models.DecimalField(max_digits=12, decimal_places=2, default=0) discount = models.DecimalField(max_digits=12, decimal_places=2, default=0) tax_percent = models.DecimalField(max_digits=5, decimal_places=2, default=0) vat_percent = models.DecimalField(max_digits=5, decimal_places=2, default=0) grand_total = models.DecimalField(max_digits=12, decimal_places=2, default=0) paid_amount = models.DecimalField(max_digits=12, decimal_places=2, default=0) due_amount = models.DecimalField(max_digits=12, decimal_places=2, default=0) payment_method = models.CharField(max_length=30, default='cash') def __str__(self): return f"Order #{self.id}" class OrderItem(models.Model): order = models.ForeignKey(Order, on_delete=models.CASCADE) product = models.ForeignKey(ProductList, on_delete=models.PROTECT) qty = models.DecimalField(max_digits=12, decimal_places=2, default=1) unit_price = models.DecimalField(max_digits=12, decimal_places=2, default=0) row_total = models.DecimalField(max_digits=12, decimal_places=2, default=0) ``` --- ## 5) Minimal Invoice Template: `templates/pos/invoice.html` ```html {% extends 'common/base.html' %} {% block title %}Invoice #{{ order.id }}{% endblock %} {% block content %}

Invoice #{{ order.id }}

Customer

{{ order.customer_name }}

{{ order.customer_mobile }}

{{ order.customer_email }}

{{ order.customer_address }}

Order Info

Date: {{ order.created|date:"Y-m-d H:i" }}

Payment: {{ order.payment_method|title }}

{% for it in items %} {% endfor %}
# Product Qty Unit Price Total
{{ forloop.counter }} {{ it.product.name }} {{ it.product.sku }} {{ it.qty }} {{ it.unit_price }} {{ it.row_total }}
Subtotal {{ order.subtotal }}
Discount {{ order.discount }}
Tax (%) {{ order.tax_percent }}
VAT (%) {{ order.vat_percent }}
Grand Total {{ order.grand_total }}
Paid {{ order.paid_amount }}
Due {{ order.due_amount }}
{% endblock %} ``` --- ## 6) Notes & Tips * **CSRF**: The template parses `{% csrf_token %}` from a hidden input to use in `fetch` header. If you prefer, add a meta tag `` and read from it instead. * **Security**: Always validate pricing server-side (recalculate server totals if required) to avoid tampering. * **Performance**: Add indexes on `ProductList.sku` and `ProductList.name` for faster search. * **Extensibility**: You can add barcode scanner input and trigger `addToCart` once a product is resolved. --- ## 7) Quick Server-Side Recalculation (Optional but recommended) To make tampering impossible, recompute totals in `create_order` instead of trusting client: ```python # After loading items calc_subtotal = Decimal('0') for it in items: product = get_object_or_404(models.ProductList, id=it['product_id']) qty = Decimal(str(it['qty'])) unit_price = Decimal(str(it['unit_price'])) # or use product.price to lock calc_subtotal += qty * unit_price subtotal = calc_subtotal # Then compute discount/tax/vat/grand on server using same formula ```