Database Schema Reference
This document provides comprehensive documentation for the LUStores database schema, including all tables, relationships, and data types.
Overview
The LUStores application uses PostgreSQL with Drizzle ORM for type-safe database operations. The schema is defined in shared/schema.ts and uses the following key design principles:
Referential Integrity: Foreign key constraints ensure data consistency
Audit Trails: All critical operations are logged with user and timestamp information
Soft Deletes: Items are marked as inactive rather than permanently deleted
Flexible Permissions: Granular permission system for different user roles
VAT Support: Built-in VAT calculation and tracking for sales
Core Tables
Users Table
CREATE TABLE users (
id VARCHAR PRIMARY KEY,
email VARCHAR UNIQUE NOT NULL,
password_hash VARCHAR, -- NULL for SSO users
first_name VARCHAR,
last_name VARCHAR,
role VARCHAR NOT NULL DEFAULT 'user', -- 'user', 'manager', 'admin', 'superuser'
is_active BOOLEAN NOT NULL DEFAULT true,
must_change_password BOOLEAN NOT NULL DEFAULT false,
last_login TIMESTAMP,
profile_image_url VARCHAR,
show_picking_list BOOLEAN NOT NULL DEFAULT true, -- User preference for displaying picking lists
created_at TIMESTAMP DEFAULT NOW(),
updated_at TIMESTAMP DEFAULT NOW()
);
Key Features: - Supports both local authentication (with password hash) and SSO (password_hash = NULL) - Role-based access control with four levels: user, manager, admin, superuser - Audit fields for tracking login activity
Categories Table
CREATE TABLE categories (
id SERIAL PRIMARY KEY,
name VARCHAR(100) UNIQUE NOT NULL,
description TEXT,
icon VARCHAR(50) NOT NULL DEFAULT 'fas fa-box',
color VARCHAR(50) NOT NULL DEFAULT 'blue',
created_at TIMESTAMP DEFAULT NOW(),
updated_at TIMESTAMP DEFAULT NOW()
);
Key Features: - Visual customization with icons and colors for UI display - Unique category names prevent duplicates - Default values ensure consistent appearance
Items Table
CREATE TABLE items (
id SERIAL PRIMARY KEY,
name VARCHAR(200) NOT NULL,
sku VARCHAR(100) UNIQUE NOT NULL,
description TEXT,
category_id INTEGER NOT NULL REFERENCES categories(id),
price DECIMAL(10,2) NOT NULL,
vat_rate DECIMAL(5,4) NOT NULL DEFAULT 0.2000, -- 20% VAT default
vat_included BOOLEAN NOT NULL DEFAULT true,
current_stock INTEGER NOT NULL DEFAULT 0,
minimum_stock INTEGER NOT NULL DEFAULT 0,
is_active BOOLEAN NOT NULL DEFAULT true,
created_by VARCHAR REFERENCES users(id),
updated_by VARCHAR REFERENCES users(id),
created_at TIMESTAMP DEFAULT NOW(),
updated_at TIMESTAMP DEFAULT NOW()
);
Key Features:
- SKU (Stock Keeping Unit) must be unique across all items
- Built-in VAT rate and calculation support
- Stock level tracking with minimum threshold alerts
- Soft delete using is_active flag
- Full audit trail with created_by and updated_by fields
Stock Movements Table
CREATE TABLE stock_movements (
id SERIAL PRIMARY KEY,
item_id INTEGER NOT NULL REFERENCES items(id),
type VARCHAR(20) NOT NULL, -- 'in', 'out', 'adjustment'
quantity INTEGER NOT NULL,
previous_stock INTEGER NOT NULL,
new_stock INTEGER NOT NULL,
reason TEXT,
performed_by VARCHAR REFERENCES users(id),
created_at TIMESTAMP DEFAULT NOW()
);
Movement Types: - in: Stock received (adds to current stock) - out: Stock issued (subtracts from current stock) - adjustment: Manual correction (sets stock to exact quantity)
Notes Table
CREATE TABLE notes (
id SERIAL PRIMARY KEY,
text TEXT NOT NULL,
reference_type VARCHAR(50) NOT NULL, -- 'item', 'sale', 'order', 'charge_code', 'vendor'
reference_id VARCHAR(255) NOT NULL, -- ID of the referenced entity
created_by VARCHAR REFERENCES users(id),
created_at TIMESTAMP DEFAULT NOW(),
updated_at TIMESTAMP DEFAULT NOW()
);
CREATE INDEX idx_notes_reference ON notes(reference_type, reference_id);
CREATE INDEX idx_notes_created_by ON notes(created_by);
Key Features: - Flexible annotation system for any entity type - Polymorphic reference design (reference_type + reference_id) - Full audit trail with creator and timestamps - Efficient querying with composite index on reference fields
Supported Reference Types: - item: Notes on inventory items (stock issues, supplier notes, etc.) - sale: Notes on completed sales (special instructions, customer feedback) - order: Notes on purchase orders (delivery instructions, supplier communications) - charge_code: Notes on charge codes (usage restrictions, budget notes) - vendor: Notes on suppliers/vendors (contact notes, quality issues)
Use Cases: - Track item-specific issues or handling instructions - Record customer requests or delivery notes for sales - Document supplier communications for orders - Add usage restrictions or budget notes for charge codes - Record vendor quality issues or preferred contact methods
Sales and Quotes Tables
Sales Table
CREATE TABLE sales (
id SERIAL PRIMARY KEY,
sale_id VARCHAR(50) UNIQUE NOT NULL, -- Generated ID like S202501291234
charge_code VARCHAR(100) NOT NULL,
subtotal_amount DECIMAL(12,2) NOT NULL, -- Amount before VAT
vat_amount DECIMAL(12,2) NOT NULL DEFAULT 0.00,
total_amount DECIMAL(12,2) NOT NULL, -- Total including VAT
vat_applied BOOLEAN NOT NULL DEFAULT true,
customer_info JSONB,
notes TEXT,
status VARCHAR(20) NOT NULL DEFAULT 'completed',
is_paid BOOLEAN NOT NULL DEFAULT false, -- Payment reconciliation status
delivered_to VARCHAR(200), -- Name of recipient who received items
delivered_to_email VARCHAR(200), -- Email of recipient
delivered_at TIMESTAMP, -- When delivery was confirmed
processed_by VARCHAR REFERENCES users(id),
created_at TIMESTAMP DEFAULT NOW(),
updated_at TIMESTAMP DEFAULT NOW()
);
Sale Items Table
CREATE TABLE sale_items (
id SERIAL PRIMARY KEY,
sale_id INTEGER NOT NULL REFERENCES sales(id),
item_id INTEGER NOT NULL REFERENCES items(id),
item_name VARCHAR(200) NOT NULL, -- Snapshot at time of sale
item_sku VARCHAR(100) NOT NULL, -- Snapshot at time of sale
unit_price DECIMAL(10,2) NOT NULL,
vat_rate DECIMAL(5,4) NOT NULL,
vat_amount DECIMAL(10,2) NOT NULL DEFAULT 0.00,
quantity INTEGER NOT NULL,
subtotal DECIMAL(12,2) NOT NULL,
total_with_vat DECIMAL(12,2) NOT NULL,
created_at TIMESTAMP DEFAULT NOW()
);
Quotes Table
CREATE TABLE quotes (
id SERIAL PRIMARY KEY,
quote_id VARCHAR(50) UNIQUE NOT NULL, -- Generated ID like Q202501291234
charge_code VARCHAR(100) NOT NULL,
subtotal_amount DECIMAL(12,2) NOT NULL,
vat_amount DECIMAL(12,2) NOT NULL DEFAULT 0.00,
total_amount DECIMAL(12,2) NOT NULL,
vat_applied BOOLEAN NOT NULL DEFAULT true,
customer_info JSONB,
notes TEXT,
status VARCHAR(20) NOT NULL DEFAULT 'draft', -- 'draft', 'processed', 'deleted'
created_by VARCHAR REFERENCES users(id),
processed_by VARCHAR REFERENCES users(id),
processed_at TIMESTAMP,
created_at TIMESTAMP DEFAULT NOW(),
updated_at TIMESTAMP DEFAULT NOW()
);
Quote Items Table
CREATE TABLE quote_items (
id SERIAL PRIMARY KEY,
quote_id INTEGER NOT NULL REFERENCES quotes(id),
item_id INTEGER NOT NULL REFERENCES items(id),
item_name VARCHAR(200) NOT NULL,
item_sku VARCHAR(100) NOT NULL,
unit_price DECIMAL(10,2) NOT NULL,
vat_rate DECIMAL(5,4) NOT NULL,
vat_amount DECIMAL(10,2) NOT NULL DEFAULT 0.00,
quantity INTEGER NOT NULL,
subtotal DECIMAL(12,2) NOT NULL,
total_with_vat DECIMAL(12,2) NOT NULL,
created_at TIMESTAMP DEFAULT NOW()
);
Procurement Tables
Suppliers Table
CREATE TABLE suppliers (
id VARCHAR PRIMARY KEY,
name VARCHAR NOT NULL,
contact VARCHAR,
email VARCHAR,
phone VARCHAR,
address VARCHAR,
created_at TIMESTAMP DEFAULT NOW(),
updated_at TIMESTAMP DEFAULT NOW()
);
Sources Table (Item-Supplier Relationship)
CREATE TABLE sources (
id SERIAL PRIMARY KEY,
item_id INTEGER NOT NULL REFERENCES items(id),
supplier_id VARCHAR NOT NULL REFERENCES suppliers(id),
price DECIMAL(10,2),
notes TEXT,
created_at TIMESTAMP DEFAULT NOW()
);
Orders Table
CREATE TABLE orders (
id SERIAL PRIMARY KEY,
order_id VARCHAR(50) UNIQUE NOT NULL, -- Generated ID like O202501291234
supplier_id VARCHAR REFERENCES suppliers(id),
status VARCHAR(20) NOT NULL DEFAULT 'pending', -- 'pending', 'received', 'cancelled'
notes TEXT,
total_amount DECIMAL(12,2),
created_by VARCHAR REFERENCES users(id),
received_by VARCHAR REFERENCES users(id),
received_at TIMESTAMP,
created_at TIMESTAMP DEFAULT NOW(),
updated_at TIMESTAMP DEFAULT NOW()
);
Order Items Table
CREATE TABLE order_items (
id SERIAL PRIMARY KEY,
order_id INTEGER NOT NULL REFERENCES orders(id),
item_id INTEGER REFERENCES items(id), -- Optional for new items
item_name VARCHAR(200) NOT NULL,
item_sku VARCHAR(100) NOT NULL,
item_description TEXT,
category_id INTEGER REFERENCES categories(id),
unit_cost DECIMAL(10,2) NOT NULL,
quantity INTEGER NOT NULL,
total_cost DECIMAL(12,2) NOT NULL,
received BOOLEAN NOT NULL DEFAULT false,
created_at TIMESTAMP DEFAULT NOW()
);
Charge Codes and Exclusions
Charge Codes Table
CREATE TABLE chargecodes (
code VARCHAR PRIMARY KEY,
title VARCHAR NOT NULL,
authorised_by VARCHAR REFERENCES users(id),
valid_from TIMESTAMP,
valid_until TIMESTAMP,
pin VARCHAR,
cost_centre VARCHAR,
created_at TIMESTAMP DEFAULT NOW(),
updated_at TIMESTAMP DEFAULT NOW()
);
Charge Code Exclusions Table
CREATE TABLE charge_code_exclusions (
id SERIAL PRIMARY KEY,
charge_code VARCHAR NOT NULL REFERENCES chargecodes(code),
category_id INTEGER NOT NULL REFERENCES categories(id),
created_by VARCHAR REFERENCES users(id),
created_at TIMESTAMP DEFAULT NOW()
);
Purpose: Defines which categories a charge code cannot be used for, enabling budget control and compliance.
System Configuration Tables
System Settings Table
CREATE TABLE system_settings (
id SERIAL PRIMARY KEY,
key VARCHAR(100) UNIQUE NOT NULL,
value JSONB NOT NULL,
description TEXT,
category VARCHAR(50) NOT NULL DEFAULT 'general',
is_system BOOLEAN NOT NULL DEFAULT false, -- System settings cannot be deleted
created_at TIMESTAMP DEFAULT NOW(),
updated_at TIMESTAMP DEFAULT NOW()
);
User Permissions Table
CREATE TABLE user_permissions (
id SERIAL PRIMARY KEY,
user_id VARCHAR NOT NULL REFERENCES users(id),
permission VARCHAR(100) NOT NULL,
granted BOOLEAN NOT NULL DEFAULT true,
granted_by VARCHAR NOT NULL REFERENCES users(id),
created_at TIMESTAMP DEFAULT NOW(),
updated_at TIMESTAMP DEFAULT NOW(),
UNIQUE(user_id, permission)
);
Permission Definitions Table
CREATE TABLE permission_definitions (
id SERIAL PRIMARY KEY,
name VARCHAR(100) UNIQUE NOT NULL,
description TEXT NOT NULL,
category VARCHAR(50) NOT NULL,
default_roles JSONB NOT NULL, -- Array of roles that get this permission by default
is_system BOOLEAN NOT NULL DEFAULT false,
created_at TIMESTAMP DEFAULT NOW(),
updated_at TIMESTAMP DEFAULT NOW()
);
System Monitoring Tables
System Tests Table
CREATE TABLE system_tests (
id SERIAL PRIMARY KEY,
test_type VARCHAR(32) UNIQUE NOT NULL, -- 'unit', 'integration', 'sales'
status VARCHAR(32) NOT NULL, -- 'running', 'completed', 'failed'
output TEXT,
start_time TIMESTAMP NOT NULL,
end_time TIMESTAMP,
duration INTEGER, -- milliseconds
updated_at TIMESTAMP DEFAULT NOW()
);
Purpose: Stores the latest test results for system monitoring and health checks.
Session Storage Table
CREATE TABLE sessions (
sid VARCHAR PRIMARY KEY,
sess JSONB NOT NULL,
expire TIMESTAMP NOT NULL
);
CREATE INDEX IDX_session_expire ON sessions(expire);
Purpose: Required for Replit Auth and session management. This table is mandatory and should not be dropped.
Entity Relationships
Primary Relationships
users ||--o{ items : created_by/updated_by
users ||--o{ stock_movements : performed_by
users ||--o{ sales : processed_by
users ||--o{ quotes : created_by/processed_by
users ||--o{ orders : created_by/received_by
users ||--o{ chargecodes : authorised_by
users ||--o{ user_permissions : user_id/granted_by
categories ||--o{ items : category_id
categories ||--o{ charge_code_exclusions : category_id
categories ||--o{ order_items : category_id
items ||--o{ stock_movements : item_id
items ||--o{ sale_items : item_id
items ||--o{ quote_items : item_id
items ||--o{ sources : item_id
items ||--o{ order_items : item_id
suppliers ||--o{ sources : supplier_id
suppliers ||--o{ orders : supplier_id
sales ||--o{ sale_items : sale_id
quotes ||--o{ quote_items : quote_id
orders ||--o{ order_items : order_id
chargecodes ||--o{ charge_code_exclusions : charge_code
Data Types and Constraints
Common Data Types
VARCHAR: Variable-length strings with optional length limits
TEXT: Unlimited length text fields
INTEGER/SERIAL: Integer values, SERIAL for auto-incrementing primary keys
DECIMAL(p,s): Fixed-precision decimal numbers (precision, scale)
BOOLEAN: True/false values
TIMESTAMP: Date and time values
JSONB: Binary JSON for flexible structured data
Key Constraints
Primary Keys: All tables have auto-incrementing or explicit primary keys
Unique Constraints: SKUs, email addresses, charge codes must be unique
Foreign Key Constraints: Maintain referential integrity between related tables
Check Constraints: Validate data ranges and formats (implemented in application layer)
Not Null Constraints: Critical fields cannot be empty
Indexes
-- Session management
CREATE INDEX IDX_session_expire ON sessions(expire);
-- Performance indexes for common queries
CREATE INDEX idx_items_category ON items(category_id);
CREATE INDEX idx_items_active ON items(is_active);
CREATE INDEX idx_stock_movements_item ON stock_movements(item_id);
CREATE INDEX idx_sale_items_sale ON sale_items(sale_id);
CREATE INDEX idx_quote_items_quote ON quote_items(quote_id);
Schema Evolution
Migration Strategy
The schema uses Drizzle ORM migrations for version control:
Migration Files: Located in
migrations/directoryIncremental Changes: Each migration represents a single schema change
Rollback Support: Migrations can be rolled back if needed
Environment Sync: Ensures development, testing, and production schemas match
Common Migration Operations
// Add new column
await db.schema.alterTable('items')
.addColumn('new_field', 'varchar(100)')
.execute();
// Create new table
await db.schema.createTable('new_table')
.addColumn('id', 'serial', (col) => col.primaryKey())
.addColumn('name', 'varchar(200)', (col) => col.notNull())
.execute();
// Add foreign key constraint
await db.schema.alterTable('child_table')
.addForeignKeyConstraint('fk_parent', ['parent_id'], 'parent_table', ['id'])
.execute();
Best Practices
Schema Design Guidelines
Consistent Naming: Use snake_case for database fields, camelCase in TypeScript
Audit Fields: Include created_at, updated_at, created_by where appropriate
Soft Deletes: Use is_active flags instead of hard deletes for important data
Referential Integrity: Always use foreign key constraints
Data Snapshots: Store item details in sale/quote items for historical accuracy
Performance Considerations
Indexing: Add indexes for frequently queried fields
Query Optimization: Use appropriate joins and limit result sets
Connection Pooling: Configure connection pools for high-traffic scenarios
Batch Operations: Use transactions for multiple related operations
Security Measures
Role-Based Access: Implement proper role checks at application level
Input Validation: Validate all inputs using Zod schemas
SQL Injection Prevention: Use parameterized queries through Drizzle ORM
Audit Logging: Track all critical data changes
Data Encryption: Encrypt sensitive fields like password hashes