Tax Module Documentation
Overview
The Tax Module is the centralized calculation engine for the ERP. Instead of scattering tax logic across Front Desk (Rooms), Restaurant (Food), and Accounting (Bills), a single Tax Engine handles all jurisdiction logic. It supports compound taxes, multiple jurisdictions (City/State/Federal), and granular reporting.
Key Features
- Centralized Rules: Define "VAT" once, apply it to "Rooms", "Food", and "Services".
- Jurisdiction Aware: Supports multi-layer taxes (e.g., 5% City Tax + 8% State Tax).
- Granular Storage: Saves the exact tax breakdown for every single line item in the database (
tax_calculations). - Audit Ready: Every calculated cent is traceable back to the specific Rate and Rule used.
Architecture
Domain Layer (app/Domain/Tax)
Models (4 Models)
TaxJurisdiction (TaxJurisdiction.php)
- Table:
tax_jurisdictions - Description: Geographic scope (e.g., "Mogadishu City", "Benadir Region", "Federal").
- Key Fields:
name: Label.type: CITY | STATE | FEDERAL.
TaxRate (TaxRate.php)
- Table:
tax_rates - Description: The numeric definition.
- Key Fields:
code: Unique code (e.g., VAT-STD).rate: Decimal format (0.18 for 18%).is_compound: Boolean (Does it apply on top of other taxes?).
TaxRule (TaxRule.php)
- Table:
tax_rules - Description: The "Logic" that connects a Rate to an Item Type.
- Key Fields:
applies_to: Target Enum (ROOM, FOOD, BEVERAGE, SERVICE, ALL).tax_jurisdiction_id: Scope.priority: Calculation order.
TaxCalculation (TaxCalculation.php)
- Table:
tax_calculations - Description: The persistent result of a calculation.
- Key Fields:
source_table: Polymorphic (e.g.,folio_lines).source_line_id: ID of the item.tax_rate_id: Which tax this chunk represents.taxable_amount: Base.tax_amount: Calculated Value.
Services
TaxEngine (TaxEngine.php)
Purpose: Stateless service processing all tax math.
Key Methods:
calculate(itemType, amount, jurisdictionId)
Returns a breakdown of applicable taxes.
- Logic:
- Queries active
TaxRules whereapplies_to == itemType. - Loads linked
TaxRates. - Iterates:
tax = amount * rate. - Returns Collection:
[ { rate_name: 'VAT', amount: 18.00 }, ... ].
- Note: Currently implements simple additive tax. Compound logic is stubbed.
- Queries active
storeCalculations(sourceTable, sourceId, calculations)
Persists the math for audit trails.
- Logic:
- Idempotency: Deletes ANY existing rows for this
source_table/source_idpair. - Insert: Bulk creates new
TaxCalculationrecords. - Context: Uses
app('current_property_id')to scope data.
- Idempotency: Deletes ANY existing rows for this
Integration Workflows
1. Room Charge Posting (Front Desk)
- Event: Night Audit runs room posting.
- Call:
TaxEngine::calculate('ROOM', 100.00). - Result: Engine returns
[{'VAT', 10.00}, {'City', 2.00}]. - Action:
- FrontDesk creates
FolioLine(Amount: 100.00, Tax: 12.00). - FrontDesk calls
TaxEngine::storeCalculations('folio_lines', lineId, result).
- FrontDesk creates
2. Restaurant Bill (POS)
- Event: Waiter prints check for Pasta ($20).
- Call:
TaxEngine::calculate('FOOD', 20.00). - Result: Engine returns
[{'VAT', 2.00}]. - Action: POS displays "Tax: $2.00".
Audit Findings & Improvements
Strengths
- Data Granularity: Unlike many systems that just store "Tax: $5.00", this module stores exactly which taxes made up that $5.00. This is crucial for filing returns where City and Federal taxes must be remitted separately.
- Idempotent Storage: The
storeCalculationsmethod safely handles re-calculations (e.g., if a bill is edited) by clearing old records first.
Issues Identified
Major
- Missing Compound Logic: The
calculatemethod blindly iterates and multipliesamount * rate. It ignores theis_compoundflag onTaxRate, meaning taxes that should be "Tax on Tax" are currently calculated as simple tax.- Impact: Potential under-collection of tax if compound rules differ by jurisdiction.
- Unsafe Property Scope:
storeCalculationsfalls back toapp('current_property_id') ?? 1. In a queued job (e.g., Night Audit running in background),app()might not have the Tenant Context set, incorrectly assigning taxes to Property ID 1.
Minor
- Testing Complexity: Because
storeCalculationsperforms a Delete + Insert, customized "Audit Logs" on thetax_calculationstable would be noisy (showing delete/create pairs).
Configuration
Config: None. Data-driven via tax_rules table.
Module Version: 1.0 Status: Beta (Compound Logic Pending)