Global Database & Normalization Strategy
To support an enterprise-grade multi-tenant SaaS environment, these rules strictly apply across all 86 sub-modules.
EVERY table (except global configs) MUST contain
tenant_id (UUID) and branch_id (BIGINT).
The API middleware layer automatically intercepts every request and injects WHERE tenant_id = ?. This prevents cross-tenant data leaks.
Financial records (Journals, Invoices, Receipts) are NEVER executed via SQL
DELETE. They are updated to status='VOIDED'.
All POST/PUT/DELETE API calls trigger an async write to sys_audit_logs recording JSON diffs.
FLOAT/DOUBLE are forbidden. Monetary fields use DECIMAL(19,4).
Transactions store foreign_amount, exchange_rate, and base_amount. Ledgers balance strictly on base_amount.
AR and AP modules do NOT write directly to the GL. They emit domain events (e.g.,
InvoicePostedEvent). A separate GoLang Ledger Service consumes these events to generate mathematically balanced Double-Entry journals.
Module 0: System, Security & Access Control
1. Module Purpose & Core Connectivity
Purpose: The foundational gatekeeper of the SaaS. It manages tenant provisioning, multi-branch physical layouts, irrefutable audit trails, user authentication (AuthN), and granular role-based authorization (AuthZ).
Travel Industry Relevance: Travel agencies operate across multiple branches (e.g., Dhaka HQ, Dubai Branch). A ticketing agent in Dhaka must not see Dubai's B2B invoices. Furthermore, travel systems are high-fraud environments; an immutable audit trail is required by IATA and financial auditors to track exactly who altered a ticket price.
core_tenants.base_currency is the mathematical anchor for the entire SaaS. If Mod 2 (AR) issues an invoice in USD, it queries Mod 7 (FX Rates) to convert it to the base currency defined here before posting to Mod 1 (GL).
The
gl_fiscal_periods table acts as the master lock. By closing a period here, the system enforces GAAP/IFRS standards by physically blocking all APIs from writing or voiding records in that historical timeframe.
2. Complete Database Schema (System & Security)
A. Tenant, Branch & System Currency Schema
| Table: core_tenants | ||
|---|---|---|
| tenant_id | UUID | PK. Unique Agency ID. |
| company_name | VARCHAR(100) | e.g., 'Global Travel Ltd' |
| base_currency | CHAR(3) | e.g., 'BDT'. Drives all GL logic. |
| tax_reg_no | VARCHAR(50) | Used for Govt Tax Reports (Mod 12) |
| Table: core_branches | ||
|---|---|---|
| branch_id | BIGINT | PK, Auto-increment |
| tenant_id | UUID | FK -> core_tenants |
| branch_code | VARCHAR(10) | e.g., 'HQ-DAC', 'BR-DXB' |
| branch_name | VARCHAR(100) | Physical location name |
| Table: gl_fiscal_periods | ||
|---|---|---|
| period_id | BIGINT | PK |
| tenant_id | UUID | FK |
| start_date | DATE | Boundary start |
| end_date | DATE | Boundary end |
| status | ENUM | 'OPEN', 'CLOSED' |
B. RBAC (Role-Based Access Control) & Users
| Table: sys_permissions (System Global) | ||
|---|---|---|
| perm_id | VARCHAR(50) | PK. e.g., 'ar.invoice.edit' |
| module | VARCHAR(50) | e.g., 'Accounts Receivable' |
| resource | VARCHAR(50) | e.g., 'Invoice', 'Journal', 'Customer' |
| action_type | ENUM | 'VIEW', 'CREATE', 'EDIT', 'DELETE', 'VOID', 'APPROVE' |
| description | VARCHAR(255) | 'Allows editing an existing AR invoice' |
| Table: sys_roles (Tenant Specific) | ||
|---|---|---|
| role_id | BIGINT | PK |
| tenant_id | UUID | FK |
| role_name | VARCHAR(50) | 'Ticketing Agent', 'CFO' |
| hierarchy_level | INT | 1 (Admin) to 99 (View-only) |
| Table: sys_role_permissions (Mapping) | ||
|---|---|---|
| role_id | BIGINT | Composite PK / FK |
| perm_id | VARCHAR(50) | Composite PK / FK |
C. Users, Auth & Audit
| Table: sys_users | ||
|---|---|---|
| user_id | BIGINT | PK |
| tenant_id | UUID | FK |
| role_id | BIGINT | FK -> sys_roles |
| VARCHAR(100) | Idx: UNIQUE(tenant, email) | |
| pwd_hash | VARCHAR(255) | Argon2 / Bcrypt |
| status | ENUM | 'ACTIVE', 'LOCKED' |
| Table: sys_user_branches (Isolation) | ||
|---|---|---|
| user_id | BIGINT | Composite PK / FK |
| branch_id | BIGINT | Composite PK / FK |
| is_default | BOOLEAN | Default branch on login |
| Table: sys_audit_logs (Immutability) | ||
|---|---|---|
| log_id | BIGINT | PK |
| user_id | BIGINT | FK |
| action | VARCHAR(20) | 'CREATE', 'UPDATE', 'VOID' |
| table_name | VARCHAR(50) | e.g., 'ar_invoices' |
| old_data | JSON | Record state before action |
| new_data | JSON | Record state after action |
Sample Data: RBAC Permission Mapping (Granular Sub-Tasks)
| tenant_id | role_name | hierarchy_level | Mapped Permissions (sys_role_permissions) | Business Logic & Sub-task Control |
|---|---|---|---|---|
| Akij-UUID | Agency Admin | 1 | * (All Permissions) | Full system access. Can CREATE, VIEW, EDIT, DELETE, and APPROVE across all modules. |
| Akij-UUID | Chief Accountant | 10 | gl.journal.view, gl.journal.approve, sys.period.edit | Can VIEW journals and APPROVE them, but cannot CREATE them. Can EDIT fiscal period status to CLOSED. |
| Akij-UUID | Ticketing Agent | 50 | ar.invoice.view, ar.invoice.create | Can VIEW and CREATE invoices. CANNOT EDIT, DELETE, or VOID them. If an error is made, they must request an accountant to void it. |
3. Authentication & Authorization Workflows
AuthN (Authentication) & Brute Force Prevention
sys_users.status = 'LOCKED'. Subsequent attempts return HTTP 423 Locked. An Admin (Level 1) must unlock the account.
AuthZ (Granular Action Authorization & Branch Data Isolation)
When the JWT is generated, it contains
{ tenant_id, role_id, permissions: ['ar.invoice.view', 'ar.invoice.create'], allowed_branch_ids: [1, 2] }.1. Action Check (Sub-task): If a user attempts a
PUT /api/v1/invoices/99 (Edit), middleware verifies 'ar.invoice.edit' exists in the JWT. If missing, it immediately throws HTTP 403 Forbidden.2. Branch Scope Check: The middleware appends branch isolation to the database query:
UPDATE ar_invoices ... WHERE tenant_id = JWT.tenant_id AND branch_id IN (JWT.allowed_branch_ids).
4. Core API Payloads & Form Fields
User Setup Form (POST /api/v1/system/users)
| Field / Param | UI / Data Type | Req | Validation & Allowed Values | Accounting & System Impact |
|---|---|---|---|---|
email | Email / String | Y | Standard email regex. UNIQUE(tenant_id, email). | Primary login identifier. Must be verified. |
password | Password | Y* | *Req on creation. Min 8 chars, 1 Upper, 1 Num. | Never stored plain-text. Hashes immediately upon receipt. |
role_id | Dropdown | Y | Must exist in sys_roles for this tenant_id. | Injects permissions into user session. Dictates UI rendering. |
branch_ids | Multi-Select | Y | Array of BIGINT. Must belong to tenant. | Inserts into sys_user_branches. Sets row-level physical data boundaries. |
Fiscal Period Management (POST /api/v1/system/fiscal-periods)
| Field / Param | UI / Data Type | Req | Validation & Logic | Accounting Impact |
|---|---|---|---|---|
start_date | Date Picker | Y | Must be exactly 1 day after previous period's end_date. | Creates the chronological boundary for Journal Entries. |
end_date | Date Picker | Y | Must be >= start_date. Usually Month-End. | Defines the cut-off for Trial Balance generation. |
status | Toggle | Y | Requires sys.period.close permission. | CRITICAL: Once 'CLOSED', the DB triggers/middleware reject ANY financial write dated within this range. |
5. Reporting Impact: System Audit Log & Access Review
Report Objective: Provide compliance officers, IATA regulators, and internal auditors a transparent view of all system activities, specifically highlighting unauthorized edits or voided tickets.
- Data Source Tables:
sys_audit_logsJOINsys_users. - Filters & Parameters: Date Range, User ID, Table Name (e.g.,
ar_invoices), Action Type (UPDATE). - Business Decision Impact: Identifies internal fraud. If an auditor sees an agent frequently editing PNR base fares after an invoice is issued, management can restrict the
ar.invoice.editpermission via Mod 0.2 and enforce the issuance of Credit Notes (Mod 2.5) instead. - Sample Output Preview (JSON Diff):
[2026-05-26 14:32:11] User: agent_john | Action: UPDATE | Table: ar_invoices | Record ID: 9942 Old_Data: {"total_base": 10000.00, "status": "POSTED"} New_Data: {"total_base": 8000.00, "status": "POSTED"}
Module 1: General Ledger (GL) Engine
Purpose & Travel Relevance: The mathematical core. In travel, high-volume ticketing requires an automated ledger that maps AR/AP events into balanced debits and credits without human intervention.
Sub-modules: 1.1 Chart of Accounts, 1.2 Journal Entries, 1.3 GL Viewer, 1.4 Consolidation, 1.5 Trial Balance, 1.6 Adjustments.
1. Database Schema
| Table: gl_accounts (COA) | ||
|---|---|---|
| account_id | BIGINT | PK |
| parent_id | BIGINT | FK (Self) Hierarchy |
| account_code | VARCHAR(20) | e.g., '1000' |
| account_type | ENUM | ASSET, LIAB, EQUITY, REV, EXP |
| is_leaf | BOOLEAN | True = Accepts entries |
| Table: gl_journal_entries (Headers) | ||
|---|---|---|
| journal_id | BIGINT | PK |
| entry_date | DATE | Checked vs Fiscal Period |
| reference_no | VARCHAR(50) | Link to AR/AP Invoice |
| source | VARCHAR(20) | 'MANUAL', 'AR', 'AP' |
| status | ENUM | 'DRAFT', 'POSTED', 'VOID' |
| Table: gl_journal_lines (The Ledger) | ||
|---|---|---|
| line_id | BIGINT | PK |
| journal_id | BIGINT | FK -> Header |
| account_id | BIGINT | FK -> gl_accounts |
| base_debit | DECIMAL(19,4) | Converted to base currency |
| base_credit | DECIMAL(19,4) | Converted to base currency |
2. Workflow & Approvals (Sub-module 1.2: Journal Entries)
3. API Payload & Accounting Logic
Sum(base_debit) - Sum(base_credit) != 0.0000, the API returns HTTP 422 and rolls back the MySQL transaction.
POST /api/v1/gl/journals
{
"entry_date": "2026-05-26", "currency_code": "USD", "exchange_rate": 110.50,
"reference_no": "MANUAL-ADJ-001", "notes": "BSP Month End Adjustment",
"lines": [
{ "account_id": 5010, "debit_base": 11050.00, "credit_base": 0.00, "memo": "Cost of Sales" },
{ "account_id": 2010, "debit_base": 0.00, "credit_base": 11050.00, "memo": "BSP Payable" }
]
}
Module 2: Sales & Accounts Receivable (AR)
Purpose & Travel Relevance: B2B/B2C billing. Travel invoices require capturing Passenger Name Records (PNRs), IATA Ticket Numbers, Base Fares, and complex Airline Taxes (YQ/XT) separately from Agency Fees.
Sub-modules: 2.1 Customer Master, 2.2 Invoicing, 2.3 Sales Register, 2.4 Receipts, 2.5 Credit Notes, 2.6 Refund, 2.7 Aging, 2.8 Multi-Currency AR.
1. Database Schema (Travel-Specific Design)
| Table: ar_invoices (Header) | ||
|---|---|---|
| invoice_id | BIGINT | PK |
| customer_id | BIGINT | FK |
| invoice_no | VARCHAR(50) | Idx: UNIQUE |
| total_base | DECIMAL(19,4) | |
| gl_journal_id | BIGINT | Link to auto-posted GL |
| Table: ar_invoice_lines (Metadata) | ||
|---|---|---|
| line_id | BIGINT | PK |
| invoice_id | BIGINT | FK |
| pnr_no | VARCHAR(10) | 'X7B9Q2' |
| ticket_no | VARCHAR(20) | IATA: '176-1234567890' |
| base_fare | DECIMAL | Cost of ticket |
| supplier_tax | DECIMAL | YQ/XT taxes |
| agency_fee | DECIMAL | Agency markup |
| Table: ar_receipts (Payments) | ||
|---|---|---|
| receipt_id | BIGINT | PK |
| customer_id | BIGINT | FK |
| bank_acct_id | BIGINT | FK -> Treasury |
| amount | DECIMAL | Total received |
| allocated | DECIMAL | Tracks Knock-off |
2. Form Parameters (Sub-module 2.2: Customer Invoicing)
| Field Name | UI / Data Type | Req | Validation & Logic | Financial Impact |
|---|---|---|---|---|
customer_id | Dropdown | Y | IF (Current AR + New Invoice > Credit Limit) THEN Warn Manager. | Determines AR sub-ledger mapping. |
pnr_no | Text | Y* | *Req for Air. Regex: ^[A-Z0-9]{6}$. | Used for profitability reports (Mod 13.4). |
ticket_no | Text | Y* | DB Index: UNIQUE(tenant_id, ticket_no) blocks double billing. | Critical for BSP Recon (Mod 3.2). |
3. Automation Logic: Invoice to GL Mapping
DR
1200 - Accounts Receivable (Customer) : Total Invoice AmountCR
4010 - Air Ticket Sales Revenue : Base Fare + Agency FeeCR
2010 - BSP Payable (Liability) : Base Fare + Airline Taxes (Owed to Airline)CR
2050 - Output VAT Payable : Govt VAT calculated on Agency Fee (Mod 12)
Module 3: Accounts Payable (AP) & BSP Settlements
Purpose & Travel Relevance: Tracking money owed to Suppliers (Airlines, GDS, Hotels), managing BSP reconciliations, and calculating PLB (Productivity Linked Bonus) commissions.
Sub-modules: 3.1 Supplier Master, 3.2 Supplier Invoices, 3.3 Purchase Register, 3.4 Commission, 3.5 Vendor Credits, 3.6 Debit Notes, 3.7 Payment Runs, 3.8 Multi-Currency AP.
1. Database Schema
| Table: ap_suppliers | ||
|---|---|---|
| supplier_id | BIGINT | PK |
| supplier_type | ENUM | 'BSP', 'NON-BSP', 'HOTEL' |
| credit_days | INT | Auto-calculates Due Date |
| Table: ap_invoices (BSP Bills) | ||
|---|---|---|
| bill_id | BIGINT | PK |
| supplier_id | BIGINT | FK |
| billing_period | VARCHAR | 'BSP-MAY-P1' |
| total_amount | DECIMAL | Amount owed to airline |
| Table: ap_commissions (PLB) | ||
|---|---|---|
| comm_id | BIGINT | PK |
| supplier_id | BIGINT | FK |
| target_volume | DECIMAL | Segment target for PLB |
| accrued_amount | DECIMAL | Unrealized back-end revenue |
2. Workflow: BSP Settlement Automation (Sub-module 3.2)
ar_invoice_lines →
4. Variance Check Engine →
5. Auto-Post AP Bill & GL Entry
3. Exception Handling (ADM Risk)
IF Billed_Fare (from Airline) > Accrued_Fare (from AR Invoice) THEN Flag as 'ADM_RISK'. The agency undercharged the customer. The AP Bill is held. If valid, an ADM (Module 4.4) is generated: DR Agency Debit Memo Expense, CR BSP Payable.
Modules 4 & 5: Classifications & Fixed Assets
Sub-modules: 4.1 Service Type, 4.2 Purpose, 4.3 Txn Mapping, 4.4 ADM, 4.5 ACM, 5.1 Asset Register, 5.2 Depreciation, 5.3 Disposal, 5.4 Capitalization
1. Database Schema
| Table: travel_adm_acm (Debit/Credit Memos) | ||
|---|---|---|
| memo_id | BIGINT | PK |
| memo_type | ENUM | 'ADM' (Expense), 'ACM' (Income) |
| supplier_id | BIGINT | FK -> Airline issuing memo |
| ticket_no | VARCHAR(20) | Link to original ticket |
| amount | DECIMAL(19,4) | |
| Table: fa_assets (Asset Register) | ||
|---|---|---|
| asset_id | BIGINT | PK |
| asset_name | VARCHAR(100) | e.g., 'Dell Server Rack' |
| purchase_value | DECIMAL(19,4) | Capitalized amount |
| depreciation_method | ENUM | 'STRAIGHT_LINE' |
| accum_depreciation | DECIMAL(19,4) | Updated monthly by Cron |
2. Automation Logic (Sub-module 5.2: Depreciation)
Iterates
fa_assets where status = 'ACTIVE'.Calc:
Monthly_Depr = (purchase_value - salvage_value) / useful_life_months.Auto-posts Journal: DR
Depreciation Expense | CR Accumulated Depreciation.
Modules 6 & 7: Treasury & Multi-Currency FX
Sub-modules: 6.1 Bank Master, 6.2 Cash Mgmt, 6.3 Bank Recon, 6.4 Forecasting, 6.5 MFS, 6.6 Payment Gateways | 7.1 FX Rates, 7.2 FX Gains/Losses, 7.3 Hedging
1. Database Schema
| Table: tr_bank_accounts | ||
|---|---|---|
| bank_acct_id | BIGINT | PK |
| gl_account_id | BIGINT | FK -> Exact GL mapping |
| account_type | ENUM | 'BANK', 'MFS', 'GATEWAY' |
| Table: tr_bank_statements (Uploads) | ||
|---|---|---|
| stmt_id | BIGINT | PK |
| bank_acct_id | BIGINT | FK |
| amount | DECIMAL | (+) Deposit, (-) Withdrawal |
| matched_gl_id | BIGINT | FK -> gl_journal_lines. |
| Table: fx_exchange_rates | ||
|---|---|---|
| rate_id | BIGINT | PK |
| currency_pair | CHAR(7) | e.g., 'USD/BDT' |
| rate_date | DATE | Fetched daily via API |
| rate_value | DECIMAL(15,6) | High precision |
2. Financial Algorithms
IF Stmt.Amount == GL.Amount AND Stmt.Ref == GL.Ref THEN match(). Match Rule 2 (Tolerance): IF Stmt.Amount == GL.Amount AND GL.Date within 3 days of Stmt.Date THEN soft_match().FX Gain/Loss Revaluation (7.2): Month-end. Finds unpaid
ar_invoices in foreign currency. Compares original exchange_rate to today's fx_exchange_rates. Auto-generates GL Journal: DR AR, CR Unrealized FX Gain (if base value increased).
Modules 8 & 9: Budgeting & Enterprise Procurement
Sub-modules: 8.1 Budget Plan, 8.2 Budget vs Actual | 9.1 Vendor Master, 9.2 PR, 9.3 PO, 9.4 Goods Receipt, 9.5 3-Way Match
1. Database Schema (3-Way Match Pattern)
| Table: prc_purchase_orders (PO) | ||
|---|---|---|
| po_id | BIGINT | PK |
| vendor_id | BIGINT | FK |
| total_amount | DECIMAL | Encumbers budget in Mod 8 |
| Table: prc_po_lines (Order) | ||
|---|---|---|
| po_line_id | BIGINT | PK |
| qty_ordered | INT | |
| unit_price | DECIMAL | Approved price |
| Table: prc_goods_receipts (GR) | ||
|---|---|---|
| gr_line_id | BIGINT | PK |
| qty_received | INT | Actual delivery qty |
2. Workflow: 3-Way Invoice Matching (Sub-module 9.5)
po_lines -> gr_lines -> ap_invoice_lines. If (Invoice.UnitPrice > PO.UnitPrice * 1.05), payment is blocked and routed for executive approval.
Modules 10, 11 & 12: Settlements, Vendors & Global Tax
Sub-modules: 10.1 PG Recon, 10.3 Agent Commission, 10.5 Escrow | 11.2 Contracts/SLA, 11.4 KPIs | 12.1 VAT/GST, 12.2 TDS/AIT, 12.4 IFRS
1. Database Schema (Tax & SLA)
| Table: tax_configurations (Rules Engine) | ||
|---|---|---|
| tax_id | BIGINT | PK |
| tax_code | VARCHAR(20) | 'VAT-15', 'TDS-5' |
| tax_type | ENUM | 'INPUT_VAT', 'OUTPUT_VAT', 'TDS' |
| rate_percentage | DECIMAL(5,2) | e.g., 15.00 |
| gl_account_id | BIGINT | FK -> Liability (VAT Payable) |
| Table: vnd_contracts (SLA Management) | ||
|---|---|---|
| contract_id | BIGINT | PK |
| supplier_id | BIGINT | FK |
| start_date / end_date | DATE | Renewal alerts (Mod 14.4) |
| commission_tier | JSON | e.g., {"target": 100000, "rate": 2.5} |
2. API & Logic (Sub-module 12.2: TDS / Withholding Tax)
POST /api/v1/tax/calculate-tds
// Payload: Paying a $1,000 invoice to IT Supplier (Requires 5% TDS)
{ "supplier_id": 8802, "invoice_amount": 1000.00 }
// Response
{
"net_payment_to_supplier": 950.00, "tax_deducted": 50.00,
"gl_postings": [
{ "account": "2060 (TDS Payable Authority)", "credit": 50.00 },
{ "account": "1010 (Bank Account)", "credit": 950.00 },
{ "account": "2010 (Accounts Payable)", "debit": 1000.00 }
]
}
Modules 13 & 14: Analytics, Reporting & Dashboards
Sub-modules: 13.1 P&L, 13.2 Balance Sheet, 13.3 Cash Flow, 13.4 Rev by Booking, 13.7 KPIs | 14.1 Exec Dash, 14.2 AR/AP Aging Widget
1. Database Schema (Materialized Reporting Layer)
| Table: gl_monthly_balances (Core View) | ||
|---|---|---|
| tenant_id | UUID | Composite PK 1 |
| account_id | BIGINT | Composite PK 2 |
| period_month | CHAR(7) | '2026-05' Composite PK 3 |
| opening_balance | DECIMAL | Rolled over from prev month |
| net_debit / net_credit | DECIMAL | Sum of month's activity |
| closing_balance | DECIMAL | Formula: Open + Net_DR - Net_CR |
| Table: ar_aging_summary (Cron Generated) | ||
|---|---|---|
| customer_id | BIGINT | PK |
| current_due | DECIMAL | Not yet due |
| days_1_30 | DECIMAL | Overdue 1-30 days |
| days_90_plus | DECIMAL | High risk receivables |
2. Dashboard Architecture & Data Flow (Mod 14)
ar_invoices or gl_journal_lines directly.Workflow:
1. A GoLang background worker runs every 15 minutes.
2. It queries the Materialized Views (
gl_monthly_balances, ar_aging_summary).3. It pushes aggregated JSON payloads to Redis (Key:
tenant:{id}:dash_stats).4. The React frontend fetches from Redis, achieving ~20ms response times for Executive KPI rendering.
3. Financial Report Definitions (Mod 13)
- 13.1 Profit & Loss: Filters
gl_monthly_balancesforaccount_type IN ('REVENUE', 'EXPENSE'). Calc: Net Profit = Total Revenue - Total COGS - Total Opex. - 13.2 Balance Sheet: Filters
gl_monthly_balancesforaccount_type IN ('ASSET', 'LIABILITY', 'EQUITY'). Validation: Total Assets MUST = (Total Liabilities + Total Equity + Current Net Profit). - 13.4 Revenue by Booking: Joins
ar_invoice_linesanddict_services. Groups profitability by PNR, Sector (Domestic vs Intl), and Corporate Client.