A comprehensive, production-ready Point of Sale (POS) management system built with Django for Galos Gadget Hub β featuring multi-branch inventory, installment credit management, warranty claims, real-time sales analytics, audit trail logging, email notifications, CSV exports, and role-based employee access.
- Executive Summary
- Features
- Tech Stack
- Project Structure
- Setup & Installation
- Database Models
- User Roles & Permissions
- URL Endpoints
- Key Features Deep Dive
- Configuration & Settings
- Troubleshooting
- Development Guidelines
- Production Deployment
- Contributing
- License & Support
The Django POS Scanner System is a full-featured retail management platform designed for Galos Gadget Hub β a multi-branch electronics gadget store. It replaces manual sales tracking with a barcode-driven POS terminal, automates installment credit approvals, streamlines warranty processing, and provides real-time analytics for managers.
| Metric | Benefit |
|---|---|
| Sales Speed | Barcode scan β checkout in seconds |
| Credit Risk | Credit Officer approval workflow for every installment |
| Warranty Handling | Structured Repair vs Replacement with cost tracking |
| Inventory Visibility | Per-branch low-stock alerts with minimum thresholds |
| Reporting | Weekly / Monthly / Yearly revenue, commissions, and outstanding balances |
| Accountability | Employee login/logout tracking, session timestamps, and full audit trail |
| Data Export | One-click CSV exports for sales, inventory, installments, employees, warranties, audit logs |
| Email Automation | HTML receipts on checkout + payment due reminders via scheduled tasks |
- Barcode-driven POS Terminal β scan products to add them to cart in real time
- Cash Checkout β automatic change calculation, receipt generation, invoice OR number
- Installment Checkout β configurable term (months), monthly due, and balance tracking
- Order Management β create, view, soft-delete, and archive sales orders
- Customer Lookup β phone-based customer identification (optional for cash sales)
- Warranty Claims β file Repair or Replacement claims with serial number tracking
- Repair: 30-day window
- Replacement: 7-day window with old/new serial swap record
- Installment Plans β multi-month schedules with automatic next-due-date progression
- Invoice Generation β unique OR numbers, VAT computation, issued-by tracking
- Commission Tracking β per-agent rate Γ total sales, cumulative earnings
- Defective Inventory β log faulty units with reason, disposal status, and branch
- Admin Dashboard β real-time KPIs: revenue, cost, gross profit, transaction count
- Branch Revenue Chart β bar chart comparison of all branches
- Employee Sales Chart β individual sales agent performance
- Top 5 Products Chart β top-selling products by units sold (last 30 days)
- Weekly / Monthly / Yearly Totals β period-based revenue aggregation
- Outstanding Balance Monitor β total owed from pending installments
- Payment Method Breakdown β cash vs installment revenue split
- Average Order Value (AOV) β computed from the last 30-day transaction window
- Warranty Claims Summary β counts by status (Pending / In-Progress / Completed / Released)
- Repair vs Replacement Breakdown β claim counts and total cost impact per type
- Stock Health Overview β healthy / low-stock / out-of-stock item counts + items restocked in last 24 hrs
- Overdue Installments Counter β number of installment plans past their due date
- Today's Collections β expected collection amount and payment progress percentage
- Day-over-Day Revenue Growth β today vs yesterday percentage change
- Active Staff Panel β list of currently logged-in employees
- Sales Export β full filtered order list downloadable as
sales_data.csv - Branch Inventory Export β current stock levels as
branch_inventory.csv - Installment Export β filtered installment records as
installment_data.csv - Employee List Export β active employee roster as
employee_list.csv - Warranty List Export β filtered warranty claims as
warranty_list.csv - Audit Log Export β filtered audit trail as
audit_logs.csv
- AuditTrail Model β logs every CREATE / UPDATE / DELETE action on key models (Product, BranchInventory, Order, OrderItem, InstallmentPlan, Employee, WarrantyClaims, Supplier)
- Django Signals β automatic audit capture via
pre_save,post_save, andpost_deletereceivers - AuditMiddleware β thread-local user capture so signals know which employee made the change
- Audit Logs View (
/audit_logs/) β paginated, filterable by date range and action type; Manager / Superuser only - Audit CSV Export β downloadable audit log
- Cash Receipt Email β HTML-formatted receipt sent to customer email on cash checkout
- Installment Confirmation Email β HTML installment plan summary sent on installment checkout
- Payment Due Reminder β scheduled task (
tasks.py: send_due_reminders()) sends reminder 5 days before next due date - Configurable via environment variables (
EMAIL_HOST,EMAIL_PORT,EMAIL_HOST_USER, etc.)
- Role-based access: Manager, Sales Agent, Credit Officer
- Login/logout timestamps (
last_login_time,last_logout_time,is_logged_in) - Soft-delete with cascading Django
User.is_active = False - Profile pictures, email, phone, branch assignment, hire date
- Inventory tracked per branch (
BranchInventory) - Orders tagged to the processing branch
- Branch-level revenue reporting
- Soft-deletable branches
- Contract start/expiration with automatic
contract_statusproperty - Supplier archive/restore actions in admin
- Products linked to suppliers with cascade behavior
| Component | Technology | Version |
|---|---|---|
| Web Framework | Django | 6.0.2 |
| WSGI Server | asgiref | 3.11.1 |
| Database | SQLite (dev) | β |
| Date Utilities | python-dateutil | 2.9.0.post0 |
| Slug Utilities | python-slugify | 8.0.4 |
| SQL Parsing | sqlparse | 0.5.5 |
| Timezone Data | tzdata | 2025.3 |
| Component | Technology |
|---|---|
| Template Engine | Django Templates |
| Styling | HTML/CSS (Tailwind utility classes) |
| Barcode Scanning | JavaScript (posScanner.js) |
| POS UI | JavaScript (posTerminal.js, posUI.js, posCalculations.js, posInit.js) |
| Image Preview | JavaScript (image-preview.js) |
| Package | Version | Purpose |
|---|---|---|
| django-admin-interface | 0.32.0 | Enhanced admin panel UI |
| django-colorfield | 0.14.0 | Color picker for admin interface |
| django-filter | 25.2 | Advanced queryset filtering |
| django-q | β | Background task queue (scheduled email reminders) |
| Pillow | 12.1.1 | Image processing for product/profile pics |
| six | 1.17.0 | Python 2/3 compatibility utility |
| text-unidecode | 1.3 | Unicode slug support |
| whitenoise | β | Efficient static file serving in production |
django-pos-scanner/
β
βββ accounts/ # Main application
β βββ migrations/ # 37 database migrations
β β βββ 0001_initial.py
β β βββ ... (0002β0037)
β βββ templates/
β β βββ accounts/ # Page templates
β β β βββ login.html # Authentication page
β β β βββ main.html # Sales agent home
β β β βββ dashboard.html # Manager/admin dashboard
β β β βββ pos_terminal.html # POS scanner interface
β β β βββ branch_inventory.html
β β β βββ inventory_update.html
β β β βββ sales_display.html # Order list with filters
β β β βββ admin_reports.html # Analytics & charts
β β β βββ admin_installment.html
β β β βββ manage_installment.html
β β β βββ employee_list.html
β β β βββ employee_form.html
β β β βββ employee_profile.html
β β β βββ manage_employee.html
β β β βββ inst_calculator.html
β β β βββ emp_receipt.html # Receipt/invoice print view
β β β βββ warranty.html # File a warranty claim
β β β βββ warranty_list.html # List of all claims
β β β βββ audit_logs.html # Audit trail viewer
β β β βββ navbar.html
β β βββ components/ # Partial/reusable templates
β β β βββ cart.html
β β β βββ cash_modal.html
β β β βββ installment_modal.html
β β β βββ pos_product_list.html
β β β βββ sales_edit.html
β β βββ emails/ # HTML email templates
β β βββ cash_receipt.html # Cash purchase receipt
β β βββ installment_receipt.html # Installment plan confirmation
β β βββ due_reminder.html # Payment due reminder
β βββ admin.py # Django admin registrations & customizations
β βββ apps.py # App configuration
β βββ decorators.py # @unauthenticated_user decorator
β βββ filters.py # django-filter FilterSet classes
β βββ forms.py # ModelForm definitions
β βββ middleware.py # AuditMiddleware β thread-local user capture
β βββ models.py # All 15 data models
β βββ signals.py # Django signals for automatic audit trail
β βββ tasks.py # Scheduled tasks (payment due reminders)
β βββ urls.py # App-level URL patterns
β βββ views.py # All view functions and class-based views
β
βββ pos/ # Django project configuration
β βββ settings.py # Application settings
β βββ urls.py # Root URL configuration
β βββ wsgi.py # WSGI entry point
β βββ asgi.py # ASGI entry point
β
βββ static/ # Static assets
β βββ images/ # Logos and static images
β β βββ ggh-logo.png
β β βββ logo.PNG
β βββ js/ # JavaScript modules
β βββ posTerminal.js # Main POS terminal controller
β βββ posScanner.js # Barcode scanning logic
β βββ posUI.js # UI state management
β βββ posInit.js # POS initialization
β βββ posCalculations.js # Cart total calculations
β βββ image-preview.js # Profile/product image preview
β
βββ templates/ # Global templates
β βββ admin/
β βββ base_site.html # Custom admin branding
β
βββ media/ # User-uploaded files (gitignored)
β βββ media/ # Product images and profile pictures
β
βββ admin-interface/ # django-admin-interface assets
β
βββ .github/
β βββ workflows/
β βββ django.ci.yml # CI: system check + flake8 linting
β
βββ manage.py # Django management CLI
βββ requirements.txt # Python dependencies
βββ .gitignore # Ignores db.sqlite3, media, env, backups
Django User (auth)
β 1:1
βΌ
Employee ββββ Branch (ForeignKey)
β 1:1 β 1:M
ββββΊ SalesAgent ββββΊ BranchInventory βββ Product βββ Supplier
β 1:1
ββββΊ CreditOfficer
β 1:M
βΌ
InstallmentPlan βββββββββββββββββββ Payment βββ Order βββ Customer
β
βΌ
OrderItem βββΊ Product
β
ββββΊ WarrantyClaims
β β 1:1
β ββββΊ ReplacementRecord
β
Invoice (OR#)
Product βββΊ DefectiveInventory βββ Branch
AuditTrail βββΊ Employee (user)
βββΊ ContentType (GenericFK β any audited model)
| Requirement | Minimum Version |
|---|---|
| Python | 3.10+ |
| pip | 23.0+ |
| Git | 2.x |
| Virtual Environment | venv / virtualenv |
1. Clone the repository
git clone https://github.com/johnaljennegalos/django-pos-scanner.git
cd django-pos-scanner2. Create and activate a virtual environment
# Create
python -m venv .venv
# Activate β Windows (PowerShell)
.\.venv\Scripts\Activate.ps1
# Activate β Windows (CMD)
.\.venv\Scripts\activate.bat
# Activate β macOS / Linux
source .venv/bin/activate3. Install all dependencies
pip install --upgrade pip
pip install -r requirements.txt4. Initialize the database
python manage.py migrate5. Create a superuser account
python manage.py createsuperuser
# Follow the prompts: username, email, password6. (Optional) Load sample data
python manage.py loaddata data_backup_filtered.json7. Collect static files (required for production; optional for dev)
python manage.py collectstatic8. Start the development server
python manage.py runserverAccess the app at: http://127.0.0.1:8000/ Access the admin at: http://127.0.0.1:8000/admin/
# Django system check (model integrity, URL conflicts, settings validation)
python manage.py check
# Syntax/style linting
pip install flake8
flake8 . --count --select=E9,F63,F7,F82 --show-source --statistics| Field | Type | Notes |
|---|---|---|
name |
CharField(100) | Required |
email |
EmailField(100) | Optional |
address |
CharField(100) | Optional |
phone |
CharField(100) | Used for quick lookup at POS |
date_created |
DateField | Auto-set on creation |
is_active |
BooleanField | Soft delete flag |
| Field | Type | Notes |
|---|---|---|
name |
CharField(100) | Branch display name |
address |
CharField(100) | Optional |
phone_number |
CharField(100) | Optional |
is_active |
BooleanField | Soft delete via overridden delete() |
| Field | Type | Notes |
|---|---|---|
user |
OneToOneField(User) | Linked Django auth user |
branch |
ForeignKey(Branch) | Required assignment |
name |
CharField(100) | Display name |
role |
CharField | Sales Agent / Credit Officer / Manager |
email |
EmailField | Optional contact |
phone |
CharField(20) | Optional contact |
profile_pic |
ImageField | Uploaded to media/ |
hire_date |
DateField | Auto-set on creation |
is_active |
BooleanField | Cascades user.is_active = False |
is_logged_in |
BooleanField | Real-time session tracking |
last_login_time |
DateTimeField | Updated on each successful login |
last_logout_time |
DateTimeField | Updated on logout |
# Soft delete cascades to Django user
def save(self, *args, **kwargs):
if not self.is_active and self.user.is_active:
self.user.is_active = False
self.user.save()
super().save(*args, **kwargs)| Field | Type | Notes |
|---|---|---|
employee |
OneToOneField(Employee) | Active employees only |
commission_rate |
DecimalField(5,2) | Percentage, e.g. 5.00 = 5% |
total_commission_earned |
DecimalField(12,2) | Cumulative |
total_sales |
DecimalField(12,2) | Cumulative gross sales |
| Field | Type | Notes |
|---|---|---|
employee |
OneToOneField(Employee) | Active employees only |
approval_limit |
DecimalField(12,2) | Max installment they can approve |
security_level |
IntegerField | Authorization tier (default 1) |
is_active |
BooleanField |
| Field | Type | Notes |
|---|---|---|
name |
CharField(100) | |
contact_person |
CharField(100) | |
phone |
CharField(100) | |
contract_expiration |
DateField | |
contract_start |
DateField | Auto-set |
status |
CharField | Active / Renewed / Opted Out / Expired |
is_active |
BooleanField | Soft delete via soft_delete() |
Computed properties:
contract_statusβ returns'Expired'if today > expiration, elsestatuscontract_periodβ formatted year range, e.g."2024 - 2026"
| Field | Type | Notes |
|---|---|---|
supplier |
ForeignKey(Supplier) | |
product_name |
CharField(100) | |
category |
CharField | Laptop / Android / iPhone / Printer |
base_price |
DecimalField(10,2) | Selling price |
barcode |
CharField(100) | Unique, used for POS scanning |
cost_price |
DecimalField(10,2) | For gross profit calculation |
min_stock_level |
PositiveIntegerField | Low-stock threshold (default 3) |
image |
ImageField | Uploaded to media/ |
is_active |
BooleanField | Soft delete flag |
deleted_at |
DateTimeField | Timestamp when soft-deleted |
def soft_delete(self):
self.is_active = False
self.deleted_at = timezone.now()
self.save()| Field | Type | Notes |
|---|---|---|
branch |
ForeignKey(Branch) | Active branches only |
product |
ForeignKey(Product) | |
quantity |
IntegerField | Current stock count |
date_added |
DateTimeField | Auto-set, ordered descending |
| Field | Type | Notes |
|---|---|---|
customer |
ForeignKey(Customer) | Optional (null for anonymous) |
employee |
ForeignKey(Employee) | Processing employee |
branch |
ForeignKey(Branch) | Originating branch |
order_date |
DateField | Auto-set on creation |
total_amount |
DecimalField(10,2) | Sum of all order items |
order_status |
CharField | Pending / Completed / Cancelled |
payment_method |
CharField | CASH / INSTALLMENT |
is_active |
BooleanField | Soft delete flag |
deleted_at |
DateTimeField | Timestamp when archived |
| Field | Type | Notes |
|---|---|---|
order |
ForeignKey(Order) | Active orders only |
product |
ForeignKey(Product) | |
quantity |
IntegerField | |
unit_price |
DecimalField(10,2) | Price at time of sale |
cost_price |
DecimalField(10,2) | Cost at time of sale |
Computed property: line_total = quantity Γ unit_price
| Field | Type | Notes |
|---|---|---|
order |
ForeignKey(Order) | Protected from deletion |
amount_paid |
DecimalField(10,2) | |
date_paid |
DateField | |
payment_type |
CharField | CASH / INSTALLMENT |
| Field | Type | Notes |
|---|---|---|
payment |
OneToOneField(Payment) | |
cash_received |
DecimalField(10,2) | Amount tendered by customer |
change_given |
DecimalField(10,2) | Change returned |
| Field | Type | Notes |
|---|---|---|
payment |
OneToOneField(Payment) | |
credit_officer |
ForeignKey(CreditOfficer) | Approving officer (nullable) |
term_months |
IntegerField | Duration in months |
monthly_due |
DecimalField(10,2) | Computed: total Γ· months |
remaining_balance |
DecimalField(10,2) | Decremented on each payment |
next_due_date |
DateField | Advanced by 1 month per payment |
payment_status |
CharField | Pending / Completed / Cancelled |
| Field | Type | Notes |
|---|---|---|
order |
ForeignKey(Order) | Protected, active orders only |
or_number |
CharField(100) | Unique official receipt number |
invoice_date |
DateField | |
vat_amount |
DecimalField(10,2) | |
grand_total |
DecimalField(10,2) | |
issued_by |
ForeignKey(Employee) |
| Field | Type | Notes |
|---|---|---|
order_item |
ForeignKey(OrderItem) | Related sale item |
claim_type |
CharField | Repair / Replacement |
faulty_serial |
CharField(100) | Serial number of defective unit |
issue_description |
TextField | Customer-reported problem |
status |
CharField | Pending β In-Progress β Completed β Released |
date_filed |
DateTimeField | Auto-set |
resolution_date |
DateTimeField | When resolved |
cost_impact |
DecimalField(10,2) | Financial cost to the store |
handled_by |
ForeignKey(Employee) | Assigned active employee |
| Field | Type | Notes |
|---|---|---|
warranty_claims |
OneToOneField(WarrantyClaims) | |
old_serial |
CharField(100) | Faulty unit serial |
new_serial |
CharField(100) | Replacement unit serial |
replacement_date |
DateTimeField | Auto-set |
| Field | Type | Notes |
|---|---|---|
product |
ForeignKey(Product) | |
branch |
ForeignKey(Branch) | |
faulty_serial |
CharField(100) | |
reason |
TextField | |
date_received |
DateTimeField | Auto-set |
is_disposed |
BooleanField | Whether disposed of (default True) |
| Field | Type | Notes |
|---|---|---|
user |
ForeignKey(Employee) | Employee who performed the action (nullable) |
action |
CharField | CREATE / UPDATE / DELETE |
content_type |
ForeignKey(ContentType) | Django ContentType of the affected model |
object_id |
PositiveIntegerField | Primary key of the affected record |
content_object |
GenericForeignKey | Generic relation to the affected object |
change_log |
JSONField | Field-level diff: {"field": {"old": "...", "new": "..."}} |
ip_address |
GenericIPAddressField | Requester IP (nullable) |
timestamp |
DateTimeField | Auto-set on creation |
Automatically populated via Django signals (signals.py) for the following models: Product, BranchInventory, Order, OrderItem, InstallmentPlan, Employee, WarrantyClaims, Supplier.
- Full access to Django Admin panel at
/admin/ - Branded as "Galos Gadget Hub Admin" / "Galos POS Portal"
- Can create, archive, and restore: Branches, Employees, Products, Suppliers, Orders
- Read-only access to: CashPayments, Payments, Invoices, OrderItems
- Custom admin actions replace the default
delete_selectedto prevent hard deletes
- Redirected to
dashboardon login - Access to:
- Admin Reports (
/admin_reports/) β revenue charts, KPI cards - Installment Management (
/admin_installment/) β all plans with filters - Sales Display (
/sales_display/) β full order history - Employee List (
/employee_list) β view all staff - Warranty List (
/warrnty_list/) β all warranty claims - Branch Inventory (
/branch_inventory) β stock across all branches - Audit Logs (
/audit_logs/) β full system activity trail - CSV exports for all of the above
- Admin Reports (
- Redirected to
home(main.html) on login - Access to:
- POS Terminal (
/pos_terminal/) β full barcode scanning checkout - Sales Display β their own orders
- Employee Profile β update email, phone, profile picture
- File Warranty Claims (
/warranty/<pk>/) - View personal receipt (
/accounts/<pk>/emp_receipt/) - Installment Calculator (
/inst_calculator/)
- POS Terminal (
- Redirected to
homeon login - Access to:
- Manage Installment (
/accounts/<pk>/manage_installment) β process payments - View installment plan details and update
remaining_balance/next_due_date - Access to payment forms
- Manage Installment (
| URL | View | Description |
|---|---|---|
GET/POST /login/ |
loginPage |
Employee login with session tracking |
POST /logout/ |
logoutPage |
Employee logout, updates last_logout_time |
| URL | View | Description |
|---|---|---|
GET / |
home |
Sales agent dashboard |
GET /dashboard/ |
dashboard |
Manager analytics dashboard |
GET /employee_profile |
employeeProfile |
View/edit own profile |
GET /manage_employee/<pk>/ |
manageEmployee |
Manager: edit employee |
| URL | View | Description |
|---|---|---|
GET /pos_terminal/ |
posTerminal |
POS interface with product grid |
GET /scan_product/ |
scanProduct |
JSON: lookup product by barcode |
POST /checkout/cash/ |
checkout_cash |
Process cash transaction |
POST /checkout/installment/ |
installment_checkout |
Create installment order |
| URL | View | Description |
|---|---|---|
GET /branch_inventory |
branchInventory |
View stock with filters |
GET/POST /accounts/<pk>/inventory_update |
InventoryUpdateView |
Update stock quantity |
| URL | View | Description |
|---|---|---|
GET /employee_list |
EmployeeList |
List all active employees |
GET/POST /employee_form |
EmployeeCreate |
Create new employee + user |
| URL | View | Description |
|---|---|---|
GET /sales_display/ |
salesDisplay |
Filtered order list |
GET/POST /components/<pk>/sales_edit |
salesUpdateView |
Edit order status |
POST /delete-order/<pk>/ |
delete_order |
Soft-delete an order |
POST /delete-product/<pk>/ |
delete_product |
Soft-delete a product |
GET /accounts/<pk>/emp_receipt/ |
emp_receipt |
Print/view receipt |
| URL | View | Description |
|---|---|---|
GET /inst_calculator/ |
instCalculator |
Calculate installment terms |
GET /admin_installment/ |
admin_installment |
Manager: all installment plans |
GET/POST /accounts/<pk>/manage_installment |
manage_installment |
Process monthly payment |
| URL | View | Description |
|---|---|---|
GET/POST /warranty/<pk>/ |
warranty |
File a warranty claim |
GET /warrnty_list/ |
warranty_list |
List all warranty claims |
POST /update_claim_status/<pk>/ |
update_claim_status |
Advance claim status |
| URL | View | Description |
|---|---|---|
GET /admin_reports/ |
admin_reports |
Revenue analytics, charts, top products |
GET /audit_logs/ |
audit_logs |
System audit trail (Manager/Superuser only) |
GET /audit_logs/export_audit_logs/ |
audit_logs_export_csv |
Download audit logs as CSV |
| URL | View | Description |
|---|---|---|
GET /sales_display/export/ |
export_sales_csv |
Sales data as CSV |
GET /branch_inventory/export/ |
export_branch_inventory_csv |
Branch inventory as CSV |
GET /admin_installment/export_data_csv/ |
admin_installment_export_csv |
Installment records as CSV |
GET /employee_list/export_employee_csv/ |
employee_list_export_csv |
Employee roster as CSV |
GET /warrnty_list/export_warranty_list/ |
warranty_list_export_csv |
Warranty claims as CSV |
| URL | Description |
|---|---|
/admin/ |
Django Admin Panel |
/admin/accounts/branch/ |
Branch management |
/admin/accounts/employee/ |
Employee management |
/admin/accounts/product/ |
Product catalog |
/admin/accounts/order/ |
Order management |
/admin/accounts/supplier/ |
Supplier contracts |
/admin/accounts/creditofficer/ |
Credit officer management |
/admin/accounts/salesagent/ |
Sales agent management |
/admin/accounts/warrantyclaims/ |
Warranty claims |
/admin/accounts/defectiveinventory/ |
Defective stock |
On every successful login, the system:
- Authenticates using Django's
authenticate() - Sets
employee.is_logged_in = True - Records
employee.last_login_time = timezone.now() - Redirects based on role: Managers/Superusers β
dashboard, others βhome
if user is not None:
login(request, user)
if hasattr(user, 'employee'):
employee = user.employee
employee.is_logged_in = True
employee.last_login_time = timezone.now()
employee.save()
is_manager = (user.employee.role == 'Manager') if hasattr(user, 'employee') else False
return redirect('dashboard' if (user.is_superuser or is_manager) else 'home')On logout, last_logout_time is set and is_logged_in is cleared.
The POS terminal at /pos_terminal/ works as follows:
- The JavaScript scanner (
posScanner.js) listens for barcode input - It calls
/scan_product/?barcode=<value>which returns JSON with product info posUI.jsrenders the item in the cart;posCalculations.jsupdates totals- The operator selects Cash or Installment checkout
POST /checkout/cash/
β Validate cart data (JSON)
β Create/look up Customer by phone
β Create Order (CASH, PendingβCompleted)
β Create OrderItem(s), deduct BranchInventory
β Create Payment + CashPayment (cash_received, change_given)
β Create Invoice with unique OR number
β Update SalesAgent.total_sales + commission
β Return invoice PK for receipt redirect
POST /checkout/installment/
β Validate cart + customer + term
β Create Customer (if new)
β Create Order (INSTALLMENT, Pending)
β Create OrderItem(s), deduct BranchInventory
β Create Payment + InstallmentPlan
- monthly_due = total / term_months
- next_due_date = today + 1 month
- remaining_balance = total
β Create Invoice
β Update SalesAgent totals
- Filing: Sales agent goes to
/warranty/<order_item_pk>/, selects claim type, enters serial number and issue description - Repair flow: Status:
PendingβIn-ProgressβCompletedβReleased - Replacement flow: Same status progression; on completion a
ReplacementRecordis created with old/new serial numbers; defective unit added toDefectiveInventory - Cost impact: Tracked per claim for financial reporting
- Each
Producthas amin_stock_levelthreshold BranchInventorytracksquantityper branchInventoryFiltersupports filtering by: stock status (low/out/in stock), product name, barcode, branch- Stock is automatically decremented during checkout (wrapped in
transaction.atomic())
When an order is completed, the system updates the Sales Agent's profile:
# Automatic commission update on sale completion
if agent:
agent.total_sales += order.total_amount
agent.total_commission_earned += (order.total_amount * agent.commission_rate / 100)
agent.save()Invoices use a unique OR number generated via get_random_string() with a standardized prefix. VAT is computed and stored separately. The receipt view at /accounts/<pk>/emp_receipt/ renders a printable invoice.
The admin_reports view computes:
- 30-day transaction window
- Total revenue, total cost, and gross profit (via
Sum(F('unit_price') * F('quantity'))) - Average Order Value (AOV)
- Weekly / Monthly / Yearly totals
- Outstanding installment balances
- Cash vs installment payment breakdown
- Per-branch and per-employee revenue for chart rendering
- Top 5 products by units sold β bar chart powered by aggregated
OrderItemquantity sums
The dashboard view provides an at-a-glance operations overview:
| Widget | Data |
|---|---|
| Revenue | Weekly / Monthly / Yearly totals |
| Warranty Claims | Counts by status + cost impact by type |
| Stock Health | Healthy / Low / Out-of-stock + last 24-hr restocks |
| Overdue Installments | Plans past their next due date |
| Collections Today | Expected vs paid amount + progress % |
| Day-over-Day Revenue | Today vs yesterday with growth percentage |
| Active Staff | Employees currently logged in |
Every change to a key model is automatically captured:
Employee modifies a Product price
β AuditMiddleware stores user in thread-local
β pre_save signal fires
β AuditTrail record created:
action: UPDATE
change_log: {"base_price": {"old": "25000", "new": "24500"}}
user: <Employee>
timestamp: <now>
Audited models: Product, BranchInventory, Order, OrderItem, InstallmentPlan, Employee, WarrantyClaims, Supplier
Managers and superusers can view the log at /audit_logs/ with filters for date range and action type, and export the filtered results as CSV.
The system sends HTML emails at three points:
| Trigger | Template | Recipient |
|---|---|---|
| Cash checkout | emails/cash_receipt.html |
Customer (if email provided) |
| Installment checkout | emails/installment_receipt.html |
Customer (if email provided) |
| 5 days before due date | emails/due_reminder.html |
Customer (if email provided) |
The due-date reminder is delivered by the send_due_reminders() function in tasks.py, intended to be scheduled via django-q.
Every major list view includes a one-click export button:
| View | Export URL | File |
|---|---|---|
| Sales Display | /sales_display/export/ |
sales_data.csv |
| Branch Inventory | /branch_inventory/export/ |
branch_inventory.csv |
| Admin Installment | /admin_installment/export_data_csv/ |
installment_data.csv |
| Employee List | /employee_list/export_employee_csv/ |
employee_list.csv |
| Warranty List | /warrnty_list/export_warranty_list/ |
warranty_list.csv |
| Audit Logs | /audit_logs/export_audit_logs/ |
audit_logs.csv |
Exports respect the same filters applied in the list view (date range, status, search query, etc.).
# Timezone β set to Philippine Standard Time
TIME_ZONE = 'Asia/Manila'
USE_TZ = True
# Database
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.sqlite3',
'NAME': BASE_DIR / 'db.sqlite3',
}
}
# Static & Media
STATIC_URL = 'static/'
STATICFILES_DIRS = [BASE_DIR / 'static']
MEDIA_URL = '/media/'
MEDIA_ROOT = os.path.join(BASE_DIR, 'media')
# Installed Apps
INSTALLED_APPS = [
"admin_interface", # Must be before django.contrib.admin
"colorfield",
'django_q',
'django.contrib.admin',
...
'accounts',
'django_filters',
'django.contrib.humanize'
]
# Middleware β includes custom AuditMiddleware
MIDDLEWARE = [
'whitenoise.middleware.WhiteNoiseMiddleware',
...
'accounts.middleware.AuditMiddleware',
]| Variable | Description | Example |
|---|---|---|
EMAIL_BACKEND |
Django email backend | django.core.mail.backends.smtp.EmailBackend |
EMAIL_HOST |
SMTP server host | smtp.mailprovider.com |
EMAIL_PORT |
SMTP port | 587 |
EMAIL_USE_TLS |
Enable TLS | True |
EMAIL_USE_SSL |
Enable SSL | False |
EMAIL_HOST_USER |
SMTP username / from address | noreply@yourdomain.com |
EMAIL_HOST_PASSWORD |
SMTP password | (keep secret) |
DEFAULT_FROM_EMAIL |
Sender display email | Galos Gadget Hub <noreply@yourdomain.com> |
In development,
EMAIL_BACKENDdefaults toconsoleβ emails are printed to the terminal instead of sent.
django-q is used for scheduled background tasks such as send_due_reminders():
Q_CLUSTER = {
'name': 'GalosGadgetHub',
'workers': 4,
'recycle': 500,
'timeout': 60,
'compress': True,
'save_limit': 250,
'queue_limit': 500,
'label': 'Django Q',
'orm': 'default', # Uses the same database β no Redis required
}Start the task worker alongside the web server:
python manage.py qclusterFor production, set these via environment variables or a .env file (never commit them):
| Variable | Description | Example |
|---|---|---|
SECRET_KEY |
Django secret key | A 50-char random string |
DEBUG |
Debug mode flag | False in production |
ALLOWED_HOSTS |
Comma-separated allowed hosts | yourdomain.com,www.yourdomain.com |
DATABASE_URL |
Production DB connection | postgres://user:pass@host/db |
DB_ENGINE |
Django database engine | django.db.backends.postgresql |
DB_NAME |
Database name | galos_pos |
DB_USER |
Database user | dbadmin |
DB_PASSWORD |
Database password | (keep secret) |
DB_HOST |
Database host | your-db-host.com |
DB_PORT |
Database port | 5432 |
EMAIL_HOST |
SMTP host | smtp.mailprovider.com |
EMAIL_PORT |
SMTP port | 587 |
EMAIL_USE_TLS |
Enable TLS | True |
EMAIL_HOST_USER |
SMTP login | noreply@yourdomain.com |
EMAIL_HOST_PASSWORD |
SMTP password | (keep secret) |
DEFAULT_FROM_EMAIL |
Sender email | noreply@yourdomain.com |
# Install: pip install psycopg2-binary
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.postgresql',
'NAME': os.environ.get('DB_NAME'),
'USER': os.environ.get('DB_USER'),
'PASSWORD': os.environ.get('DB_PASSWORD'),
'HOST': os.environ.get('DB_HOST', 'localhost'),
'PORT': os.environ.get('DB_PORT', '5432'),
}
}Problem: django.db.utils.OperationalError: no such table
# Reset and re-migrate
rm db.sqlite3
python manage.py migrate
python manage.py createsuperuserProblem: Migration conflicts
python manage.py migrate --run-syncdb
# Or reset migrations for the accounts app:
python manage.py migrate accounts zero
python manage.py migrate accountsProblem: User cannot log in despite correct credentials
Checklist:
- Verify
user.is_active = Truein Django Admin β Users - Verify
employee.is_active = Truefor the corresponding Employee record - Ensure the Employee has a
branchassigned - Check terminal output for
Status update failed:errors
Problem: User logs in but sees wrong page
- Managers must have
employee.role == 'Manager' - Superusers always go to
dashboard - All other roles go to
home
Problem: RelatedObjectDoesNotExist: User has no employee
- The
userexists in Django Auth but has no linkedEmployeerecord - Fix: Create the Employee in Django Admin and link it to the user
Problem: Profile pictures or product images not displaying
# Ensure MEDIA_ROOT exists
mkdir -p media/media
# Verify settings.py
MEDIA_URL = '/media/'
MEDIA_ROOT = os.path.join(BASE_DIR, 'media')
# Verify pos/urls.py has static serving in debug mode
if settings.DEBUG:
urlpatterns += static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)python manage.py collectstatic --noinput
# Ensure STATICFILES_DIRS points to the correct path- Verify the
manage_installmentview is being called viaPOST - Check that
remaining_balance>=monthly_duebefore processing - Ensure the
InstallmentPlanhaspayment_status = 'Pending'
- Follow PEP 8 for Python code style
- Use Django class-based views for list/update operations, function-based views for complex logic
- Wrap multi-step database operations in
transaction.atomic() - Use
select_related()andprefetch_related()to avoid N+1 queries
All major models use soft deletes β never hard-delete production data:
# Pattern used across Branch, Employee, Product, Order, Supplier
def soft_delete(self):
self.is_active = False
self.deleted_at = timezone.now() # where applicable
self.save()
# Always filter active records in querysets
Product.objects.filter(is_active=True)
Order.objects.filter(is_active=True)from django.db import transaction
@transaction.atomic
def checkout_cash(request):
# All operations succeed or all roll back
order = Order.objects.create(...)
for item in cart_items:
OrderItem.objects.create(order=order, ...)
BranchInventory.objects.filter(...).update(quantity=F('quantity') - item['qty'])
Payment.objects.create(...)
CashPayment.objects.create(...)
Invoice.objects.create(...)# commission_rate is stored as a percentage (e.g., 5.00 = 5%)
commission_earned = order.total_amount * Decimal(agent.commission_rate) / Decimal('100')
agent.total_commission_earned += commission_earned
agent.total_sales += order.total_amount
agent.save()| Claim Type | Time Window | Action Required |
|---|---|---|
| Repair | 30 days from sale | Log faulty serial, track cost impact |
| Replacement | 7 days from sale | Create ReplacementRecord, log DefectiveInventory |
Status flow: Pending β In-Progress β Completed β Released
The system uses django-filter for dynamic queryset filtering:
# filters.py
class InventoryFilter(django_filters.FilterSet):
stock_status = django_filters.ChoiceFilter(method='filter_stock_status')
def filter_stock_status(self, queryset, name, value):
if value == 'low':
return queryset.filter(quantity__gt=0, quantity__lte=F('product__min_stock_level'))
elif value == 'out':
return queryset.filter(quantity__lte=0)
elif value == 'in stock':
return queryset.filter(quantity__gt=F('product__min_stock_level'))
return queryset- Set
DEBUG = False - Generate a new
SECRET_KEY(never use the development key) - Set
ALLOWED_HOSTSto your domain(s) - Switch to PostgreSQL or another production database
- Configure a reverse proxy (nginx/Apache)
- Set up HTTPS/TLS certificate
- Configure
SECURE_SSL_REDIRECT = True - Set
SESSION_COOKIE_SECURE = True - Set
CSRF_COOKIE_SECURE = True - Run
python manage.py check --deploy
# 1. Set environment variables
export SECRET_KEY="your-new-secure-secret-key"
export DEBUG=False
export ALLOWED_HOSTS="yourdomain.com,www.yourdomain.com"
# 2. Install dependencies
pip install -r requirements.txt
# 3. Apply database migrations
python manage.py migrate
# 4. Collect static files
python manage.py collectstatic --noinput
# 5. Create superuser (first time only)
python manage.py createsuperuser
# 6. Run system checks
python manage.py check --deploy
# 7. Start the web server (example with gunicorn)
pip install gunicorn
gunicorn pos.wsgi:application --bind 0.0.0.0:8000 --workers 3
# 8. Start the django-q worker (separate process, for email reminders)
python manage.py qclusterThe application is configured for Microsoft Azure App Service (Japan West). The startup.sh and Procfile handle the WSGI startup. The ALLOWED_HOSTS and CSRF_TRUSTED_ORIGINS in settings.py already include Azure domains.
Key Azure-specific settings that are pre-configured:
USE_X_FORWARDED_HOST = True
SECURE_PROXY_SSL_HEADER = ('HTTP_X_FORWARDED_PROTO', 'https')
CSRF_TRUSTED_ORIGINS = ['https://*.azurewebsites.net', ...]
STATICFILES_STORAGE = 'whitenoise.storage.CompressedManifestStaticFilesStorage'# Security headers
SECURE_SSL_REDIRECT = True
SESSION_COOKIE_SECURE = True
CSRF_COOKIE_SECURE = True
SECURE_BROWSER_XSS_FILTER = True
SECURE_CONTENT_TYPE_NOSNIFF = True
X_FRAME_OPTIONS = 'DENY'
# Static files (serve via nginx in production)
STATIC_ROOT = BASE_DIR / 'staticfiles'
# Logging
LOGGING = {
'version': 1,
'disable_existing_loggers': False,
'handlers': {
'file': {
'level': 'WARNING',
'class': 'logging.FileHandler',
'filename': BASE_DIR / 'logs/django.log',
},
},
'loggers': {
'django': {
'handlers': ['file'],
'level': 'WARNING',
'propagate': True,
},
},
}feature/<short-description> # New features
fix/<short-description> # Bug fixes
chore/<short-description> # Maintenance, refactoring
docs/<short-description> # Documentation only
-
Fork the repository (external contributors) or create a branch (team members)
git checkout -b feature/your-feature-name
-
Make changes following the coding standards above
-
Verify locally
python manage.py check flake8 . --count --select=E9,F63,F7,F82 --show-source --statistics -
Commit with a clear message
git commit -m "feat: add commission breakdown to admin reports" -
Push and open a Pull Request to
maingit push origin feature/your-feature-name
-
PR will trigger the Django CI Check workflow automatically:
python manage.py checkflake8syntax/undefined-name check
<type>: <short summary>
Types: feat | fix | docs | style | refactor | chore | test
This project is licensed under the CC BY-NC-SA 4.0.
- Non-Commercial: You may not use this material for commercial purposes.
- Attribution: You must give appropriate credit to the original author.
- ShareAlike: If you remix or transform the material, you must distribute your contributions under the same license.
| Channel | Details |
|---|---|
| Live Demo | galosgadgethubpos.systems |
| Issues | GitHub Issues |
| Repository | github.com/johnaljennegalos/django-pos-scanner |
When opening an issue, please include:
- Python and Django version (
python --version,django-admin --version) - Steps to reproduce
- Expected vs actual behavior
- Relevant error messages or stack traces
Galos Gadget Hub POS System β’ Built with β€οΈ using Django 6.0.2
Last Updated: April 2026 β’ Version 1.0.0