Overview
POS Kasir uses PostgreSQL as its primary database with a well-structured relational schema designed for:- Data integrity - Foreign keys, constraints, and ACID transactions
- Performance - Strategic indexes on commonly queried columns
- Auditability - Automatic timestamps and activity logging
- Type safety - Custom ENUM types for controlled values
sqlc/migrations/
Initial Schema: 000001_init.up.sql
Entity Relationship Diagram
Core Tables
users
User accounts with role-based access control Location:sqlc/migrations/000001_init.up.sql:23-33
id- UUID primary key (auto-generated)username- Unique username for loginemail- Unique email addresspassword_hash- bcrypt hashed passwordrole- User role (admin, cashier, manager)is_active- Account statusavatar- Profile image URL (Cloudflare R2)deleted_at- Soft delete timestamp (added in migration000007)refresh_token- JWT refresh token hash (added in migration000010)
- One-to-many with
orders(cashier who created order) - One-to-many with
activity_logs(user actions) - One-to-many with
shifts(cashier shifts) - One-to-many with
cash_transactions
categories
Product categorization Location:sqlc/migrations/000001_init.up.sql:35-40
id- Auto-incrementing integername- Unique category namecreated_at- Creation timestampupdated_at- Last update timestamp (auto-updated via trigger)
- One-to-many with
products
products
Product inventory and pricing Location:sqlc/migrations/000001_init.up.sql:42-51
id- UUID primary keyname- Product namecategory_id- Foreign key to categories (nullable)image_url- Product image (Cloudflare R2)price- Base price (12 digits, 2 decimals)stock- Current inventory quantitydeleted_at- Soft delete timestamp (added in migration000005)cost_price- Purchase cost (added in migration000016)
price >= 0- Prevent negative pricesstock >= 0- Prevent negative stock
idx_products_name- Fast product name search
- Many-to-one with
categories - One-to-many with
product_options - One-to-many with
order_items - One-to-many with
stock_history
product_options
Product variants (size, flavor, etc.) Location:sqlc/migrations/000001_init.up.sql:53-61
id- UUID primary keyproduct_id- Parent productname- Option name (e.g., “Large”, “Extra Shot”)additional_price- Price modifierimage_url- Option-specific imagedeleted_at- Soft delete (added in migration000006)
- Deleted when parent product is deleted
- Many-to-one with
products - One-to-many with
order_item_options
orders
Customer orders and transactions Location:sqlc/migrations/000001_init.up.sql:114-131
id- UUID primary keyuser_id- Cashier who created ordertype- Order type:dine_in,takeawaystatus- Order status:open,in_progress,served,paid,cancelledgross_total- Total before discountsdiscount_amount- Total discount appliednet_total- Final amount to payapplied_promotion_id- Promotion usedpayment_method_id- Payment methodpayment_gateway_reference- Midtrans transaction IDcash_received- Cash payment amountchange_due- Change to returncancellation_reason_id- Why order was cancelledcancellation_notes- Additional cancellation infopayment_url- Midtrans payment link (added in migration000012)
idx_orders_status- Filter by statusidx_orders_created_at- Date range queriesidx_orders_payment_gateway_reference- Midtrans webhook lookup
- Many-to-one with
users(cashier) - Many-to-one with
promotions - Many-to-one with
payment_methods - Many-to-one with
cancellation_reasons - One-to-many with
order_items
order_items
Line items in an order Location:sqlc/migrations/000001_init.up.sql:133-142
id- UUID primary keyorder_id- Parent orderproduct_id- Product purchasedquantity- Number of unitsprice_at_sale- Product price at time of sale (historical)subtotal- Quantity × Price (before item discount)discount_amount- Item-level discountnet_subtotal- Final line item total
- Deleted when order is deleted
- Cannot delete product if used in orders (
ON DELETE RESTRICT)
- Many-to-one with
orders - Many-to-one with
products - One-to-many with
order_item_options
order_item_options
Selected product options for order items Location:sqlc/migrations/000001_init.up.sql:144-149
id- UUID primary keyorder_item_id- Parent order itemproduct_option_id- Selected optionprice_at_sale- Option price at time of sale
Payment & Transaction Tables
payment_methods
Available payment options Location:sqlc/migrations/000001_init.up.sql:63-69
cancellation_reasons
Predefined cancellation reasons Location:sqlc/migrations/000001_init.up.sql:71-78
shifts
Cashier shift management Location:sqlc/migrations/000014_create_shifts_and_cash_transactions_tables.up.sql:3-14
id- UUID primary keyuser_id- Cashierstart_time- Shift start timestampend_time- Shift end timestamp (NULL if open)start_cash- Opening cash drawer amount (in cents)expected_cash_end- Calculated expected cash at endactual_cash_end- Actual counted cash at endstatus- Shift status:open,closed
idx_shifts_user_open- A user can have only one open shift at a time
cash_transactions
Cash drawer adjustments Location:sqlc/migrations/000014_create_shifts_and_cash_transactions_tables.up.sql:21-30
type- Transaction type:cash_in,cash_outcategory- Transaction category: expense, drop, float, loanamount- Transaction amount (in cents)description- Notes
- Cash drop (moving cash to safe):
cash_out, category:drop - Add float (adding change):
cash_in, category:float - Pay expense:
cash_out, category:expense
Promotion Tables
promotions
Marketing promotions and discounts Location:sqlc/migrations/000001_init.up.sql:80-93
scope- Promotion scope:ORDER- Discount on entire orderITEM- Discount on specific products
discount_type- Discount type:percentage- Percentage discount (e.g., 10%)fixed_amount- Fixed discount (e.g., $5 off)
discount_value- Discount amountmax_discount_amount- Cap on percentage discountsstart_date/end_date- Validity perioddeleted_at- Soft delete (added in migration000011)
idx_promotions_dates- Fast date range queries
promotion_rules
Conditions for promotion eligibility Location:sqlc/migrations/000001_init.up.sql:95-103
MINIMUM_ORDER_AMOUNT- Order total must exceed amountREQUIRED_PRODUCT- Must include specific productREQUIRED_CATEGORY- Must include product from categoryALLOWED_PAYMENT_METHOD- Only for specific payment methodALLOWED_ORDER_TYPE- Only for dine-in or takeaway
promotion_targets
Products/categories eligible for promotion Location:sqlc/migrations/000001_init.up.sql:105-112
PRODUCT- Specific product UUIDCATEGORY- Entire category ID
Audit & Analytics Tables
activity_logs
Complete audit trail Location:sqlc/migrations/000001_init.up.sql:151-159
action_type- Action performed:- CREATE, UPDATE, DELETE, CANCEL, APPLY_PROMOTION, PROCESS_PAYMENT
- Additional actions from migration
000017
entity_type- Entity affected:- PRODUCT, CATEGORY, PROMOTION, ORDER, USER
entity_id- ID of affected entitydetails- JSONB with change details
idx_activity_logs_user_id- User activity historyidx_activity_logs_entity- Entity change history
stock_history
Product inventory tracking Location:sqlc/migrations/000015_create_stock_history_table.up.sql:3-14
change_amount- Stock change (positive or negative)previous_stock- Stock before changecurrent_stock- Stock after changechange_type- Change reason:sale- Sold to customerrestock- Inventory replenishmentcorrection- Manual adjustmentreturn- Customer returndamage- Damaged/expired product
reference_id- Related entity (e.g., order_id for sales)created_by- User who made the change
idx_stock_history_product_id- Product historyidx_stock_history_created_at- Time-based queries
settings
Application configuration Location:sqlc/migrations/000013_create_settings_table.up.sql:1-6
000013:9-13):
app_name- Application nameapp_logo- Logo URLfooter_text- Footer texttheme_color- Primary color (hex)
- Printer settings (IP, port, paper width)
- Receipt format
- Tax rates
- Business information
Enums (Custom Types)
Location:sqlc/migrations/000001_init.up.sql:2-20
user_role
- admin - Full access
- manager - Product, reports, settings
- cashier - Orders, POS operations
order_type
order_status
open- Order createdin_progress- Being preparedserved- Ready for customerpaid- Payment completedcancelled- Order cancelled
discount_type
promotion_scope
promotion_target_type
promotion_rule_type
log_action_type
log_entity_type
shift_status
cash_transaction_type
stock_change_type
Database Functions & Triggers
Auto-Update Timestamps
Location:sqlc/migrations/000001_init.up.sql:172-199
Function:
updated_at column:
updated_at - it’s automatic!
UUID Generation
Location:sqlc/migrations/000001_init.up.sql:202-214
Extension:
Indexes for Performance
Product Search
Promotion Date Filtering
Order Queries
- Status filtering (all paid orders)
- Date range reports
- Midtrans webhook lookups
Activity Logs
- User activity history
- Entity change tracking
Stock History
- Product inventory reports
- Historical stock analysis
Shift Management
- Ensuring one open shift per user
- Fast active shift lookup
Constraints & Data Integrity
Foreign Key Cascades
CASCADE (delete children):order_items→orders(delete items when order deleted)order_item_options→order_itemsproduct_options→productspromotion_rules→promotionspromotion_targets→promotions
order_items→products(cannot delete products used in orders)order_item_options→product_options
orders→users(keep orders if user deleted)orders→promotions(keep orders if promotion deleted)products→categories(keep products if category deleted)activity_logs→users(keep logs if user deleted)
Check Constraints
Prevent Negative Values:- Quantities must be positive
- Prices cannot be negative
- Stock cannot go below zero
Unique Constraints
users.username- Unique usernamesusers.email- Unique emailscategories.name- No duplicate category namespayment_methods.name- Unique payment method namescancellation_reasons.reason- Unique cancellation reasons
Migration Management
Migration Files
Migrations are stored in:sqlc/migrations/
Naming Convention: <version>_<description>.<up|down>.sql
Examples:
000001_init.up.sql- Initial schema000005_alter_products_add_deleted_at.up.sql- Soft delete products000012_alter_orders_add_payment_url.up.sql- Add Midtrans URL000014_create_shifts_and_cash_transactions_tables.up.sql- Shift management
Running Migrations
Apply All Migrations:Embedded Migrations
Migrations are embedded in the Go binary: Location:server/server.go:102
Querying with sqlc
How sqlc Works
- Write SQL queries in
.sqlfiles - Add sqlc annotations
- Run
make sqlc-generate - Get type-safe Go code
Example Query
SQL File:sqlc/queries/users.sql
internal/user/repository/users.sql.go
Transaction Pattern
Data Types & Precision
NUMERIC(12,2)
Used for: All monetary values- 12 digits total (including decimals)
- 2 decimal places
- Range: -9999999999.99 to 9999999999.99
- No floating-point errors
BIGINT
Used for: Cash amounts in cents- 8-byte integer
- Range: -9,223,372,036,854,775,808 to 9,223,372,036,854,775,807
- Store cents to avoid decimals
TIMESTAMPTZ
Used for: All timestamps- Stores with timezone
- Converts to UTC internally
- Returns in client’s timezone
JSONB
Used for: activity_logs.details- Binary JSON storage
- Indexable
- Queryable with JSON operators
- Flexible schema
UUID
Used for: Primary keys (except serials)- 128-bit identifier
- Globally unique
- Version 4 (random)
- Better for distributed systems
Database Connection
Connection Pooling
Driver: pgx/v5 Pool Configuration:pkg/database/database.go
Environment Variables
From.env:
Best Practices
1. Always Use Transactions
- Use
store.ExecTx()for multi-step operations - Ensures data consistency
2. Soft Deletes
- Products, users, promotions use
deleted_at - Preserves historical data
- Allows restoration
3. Historical Pricing
order_items.price_at_salecaptures price at time of order- Product price changes don’t affect past orders
4. Audit Everything
- Log all significant actions to
activity_logs - Track who, what, when for compliance
5. Use Enums
- Ensures valid values
- Self-documenting schema
- Type-safe in Go
6. Index Strategically
- Index foreign keys
- Index frequently filtered columns
- Don’t over-index (slows writes)
Next Steps
- Architecture - Understand the application structure
- Tech Stack - Explore the technologies used
- Contributing Guide - Set up your development environment