Relational Database Schema & Entity Architecture
This document details the relational database architecture, entity relationships, schema migrations, indexing strategies, and transactional locking mechanisms powering Eniceberny Bakery and Culinary Hub.
1. Entity Relationship Diagram (ERD)
erDiagram
users ||--o{ orders : places
users ||--o{ addresses : has
users ||--o| customer_profiles : has
categories ||--o{ products : contains
categories ||--o{ categories : "parent / children"
products ||--o{ product_variants : has
products ||--o{ product_options : has
products ||--o{ product_images : has
products ||--o{ inventory_movements : tracks
orders ||--o{ order_items : contains
orders ||--o{ order_status_histories : logs
orders ||--o{ payment_transactions : records
carts ||--o{ cart_items : contains
catering_services ||--o{ catering_packages : includes
catering_inquiries ||--o| quotes : generates
quotes ||--o{ quote_items : contains
quotes ||--o| orders : converts_to
2. Table Specifications & Column Definitions
2.1 Identity, Access & Customers
users:id(bigint, PK)name(string)email(string, unique, index)phone(string, nullable, index)password(string, Bcrypt hash)role(string, default:'customer', index)email_verified_at(timestamp, nullable)otp_code(string, nullable)otp_expires_at(timestamp, nullable)is_active(boolean, default:true, index)permissions(json, nullable)
customer_profiles:id(bigint, PK)user_id(bigint, FK ->users.id, unique)total_spent(decimal 10,2, default:0.00)total_orders(integer, default:0)notes(text, nullable)
addresses:id(bigint, PK)user_id(bigint, FK ->users.id)recipient_name(string)phone(string)street(string)city(string)region(string)landmark(string, nullable)digital_address(string, nullable, GhanaPostGPS code)is_default(boolean, default:false)
2.2 Catalog & Inventory
categories:id(bigint, PK)name(string)slug(string, unique, index)description(text, nullable)image_path(string, nullable)parent_id(bigint, nullable, FK ->categories.id)sort_order(integer, default:0)is_active(boolean, default:true, index)
products:id(bigint, PK)category_id(bigint, FK ->categories.id, index)name(string)slug(string, unique, index)sku(string, unique, index)description(text, nullable)price(decimal 10,2)sale_price(decimal 10,2, nullable)track_inventory(boolean, default:false)stock_quantity(integer, default:0)is_available(boolean, default:true, index)is_featured(boolean, default:false, index)is_bestseller(boolean, default:false, index)prep_time_minutes(integer, default:15)
product_variants:id(bigint, PK)product_id(bigint, FK ->products.id, cascade delete)name(string, e.g. "8-inch", "Party Pan")price(decimal 10,2)sku(string, nullable)stock_quantity(integer, default:0)
inventory_movements:id(bigint, PK)product_id(bigint, FK ->products.id)user_id(bigint, nullable, FK ->users.id)movement_type(string,'sale','pos_sale','restock','adjustment')quantity_change(integer, positive or negative)quantity_after(integer)reason(string, nullable)
2.3 Orders & Commerce Ledger
orders:id(bigint, PK)order_number(string, unique, index, e.g.EB-20260927-1402)user_id(bigint, nullable, FK ->users.id, index)customer_name(string)customer_email(string)customer_phone(string, index)source(string,'storefront','pos','catering', index)order_type(string,'delivery','pickup','dine_in','takeaway')status(string,'pending','confirmed','preparing','ready_for_pickup','out_for_delivery','delivered','cancelled', index)payment_status(string,'pending','paid','failed','refunded', index)payment_method(string,'cash','momo','paystack')subtotal(decimal 10,2)delivery_fee(decimal 10,2, default:0.00)discount_amount(decimal 10,2, default:0.00)tax_amount(decimal 10,2, default:0.00)total(decimal 10,2)delivery_address_json(json, nullable)
order_items:id(bigint, PK)order_id(bigint, FK ->orders.id, cascade delete)product_id(bigint, nullable, FK ->products.id)product_name(string)variant_name(string, nullable)unit_price(decimal 10,2)quantity(integer)subtotal(decimal 10,2)custom_options(json, nullable)
payment_transactions:id(bigint, PK)order_id(bigint, FK ->orders.id)gateway(string,'cash','momo','paystack', index)reference(string, unique, index)amount(decimal 10,2)currency(string, default:'GHS')status(string,'pending','success','failed')provider_response(json, nullable)
2.4 Catering & Quotation Subsystem
catering_inquiries:id(bigint, PK)name(string)email(string)phone(string)event_type(string)event_date(date)guest_count(integer)budget(decimal 10,2, nullable)message(text, nullable)status(string,'pending','under_review','quoted','confirmed','declined', index)
quotes:id(bigint, PK)quote_number(string, unique, index, e.g.QT-260927-4401)catering_inquiry_id(bigint, nullable, FK ->catering_inquiries.id)customer_name(string)customer_email(string)customer_phone(string)event_date(date)headcount(integer)subtotal(decimal 10,2)discount_amount(decimal 10,2, default:0.00)tax_amount(decimal 10,2, default:0.00)total(decimal 10,2)status(string,'draft','sent','accepted','converted','expired', index)terms(text, nullable)
quote_items:id(bigint, PK)quote_id(bigint, FK ->quotes.id, cascade delete)description(string)quantity(integer)unit_price(decimal 10,2)subtotal(decimal 10,2)
3. Database Indexes & Performance Optimizations
- Composite & Foreign Indexes:
orders(status, created_at): Powers the executive dashboard live orders table and daily aggregation metrics.products(category_id, is_available): Optimizes public storefront shop filtering and category page rendering.
- Transaction Isolation:
- Critical checkout and stock deduction routines are locked within
DB::transaction()to eliminate race conditions and overselling during simultaneous online and counter POS sales.
- Critical checkout and stock deduction routines are locked within