150 lines
4.4 KiB
Markdown
150 lines
4.4 KiB
Markdown
# Cost Breakup Table Architecture
|
|
|
|
## Overview
|
|
|
|
This document describes the enhanced architecture for storing cost breakups in the Dealer Claim Management system. Instead of storing cost breakups as JSONB arrays, we now use a dedicated relational table for better querying, reporting, and data integrity.
|
|
|
|
## Architecture Decision
|
|
|
|
### Previous Approach (JSONB)
|
|
- **Storage**: Cost breakups stored as JSONB array in `dealer_proposal_details.cost_breakup`
|
|
- **Limitations**:
|
|
- Difficult to query individual cost items
|
|
- Hard to update specific items
|
|
- Not ideal for reporting and analytics
|
|
- No referential integrity
|
|
|
|
### New Approach (Separate Table)
|
|
- **Storage**: Dedicated `dealer_proposal_cost_items` table
|
|
- **Benefits**:
|
|
- Better querying and filtering capabilities
|
|
- Easier to update individual cost items
|
|
- Better for analytics and reporting
|
|
- Maintains referential integrity
|
|
- Supports proper ordering of items
|
|
|
|
## Database Schema
|
|
|
|
### Table: `dealer_proposal_cost_items`
|
|
|
|
```sql
|
|
CREATE TABLE dealer_proposal_cost_items (
|
|
cost_item_id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
proposal_id UUID NOT NULL REFERENCES dealer_proposal_details(proposal_id) ON DELETE CASCADE,
|
|
request_id UUID NOT NULL REFERENCES workflow_requests(request_id) ON DELETE CASCADE,
|
|
item_description VARCHAR(500) NOT NULL,
|
|
amount DECIMAL(15, 2) NOT NULL,
|
|
item_order INTEGER NOT NULL DEFAULT 0,
|
|
created_at TIMESTAMP NOT NULL DEFAULT NOW(),
|
|
updated_at TIMESTAMP NOT NULL DEFAULT NOW()
|
|
);
|
|
```
|
|
|
|
**Indexes**:
|
|
- `idx_proposal_cost_items_proposal_id` on `proposal_id`
|
|
- `idx_proposal_cost_items_request_id` on `request_id`
|
|
- `idx_proposal_cost_items_proposal_order` on `(proposal_id, item_order)`
|
|
|
|
## Backward Compatibility
|
|
|
|
The system maintains backward compatibility by:
|
|
1. **Dual Storage**: Still saves cost breakups to JSONB field for backward compatibility
|
|
2. **Smart Retrieval**: When fetching proposal details:
|
|
- First tries to get cost items from the new table
|
|
- Falls back to JSONB field if table is empty
|
|
3. **Migration**: Automatically migrates existing JSONB data to the new table during migration
|
|
|
|
## API Response Format
|
|
|
|
The API always returns cost breakups as an array, regardless of storage method:
|
|
|
|
```json
|
|
{
|
|
"proposalDetails": {
|
|
"proposalId": "uuid",
|
|
"costBreakup": [
|
|
{
|
|
"description": "Item 1",
|
|
"amount": 10000
|
|
},
|
|
{
|
|
"description": "Item 2",
|
|
"amount": 20000
|
|
}
|
|
],
|
|
"costItems": [
|
|
{
|
|
"costItemId": "uuid",
|
|
"itemDescription": "Item 1",
|
|
"amount": 10000,
|
|
"itemOrder": 0
|
|
}
|
|
]
|
|
}
|
|
}
|
|
```
|
|
|
|
## Implementation Details
|
|
|
|
### Saving Cost Items
|
|
|
|
When a proposal is submitted:
|
|
|
|
1. Save proposal details to `dealer_proposal_details` (with JSONB for backward compatibility)
|
|
2. Delete existing cost items for the proposal (if updating)
|
|
3. Insert new cost items into `dealer_proposal_cost_items` table
|
|
4. Items are ordered by `itemOrder` field
|
|
|
|
### Retrieving Cost Items
|
|
|
|
When fetching proposal details:
|
|
|
|
1. Query `dealer_proposal_details` with `include` for `costItems`
|
|
2. If cost items exist in the table, use them
|
|
3. If not, fall back to parsing JSONB `costBreakup` field
|
|
4. Always return as a normalized array format
|
|
|
|
## Migration
|
|
|
|
The migration (`20251210-create-proposal-cost-items-table.ts`):
|
|
1. Creates the new table
|
|
2. Creates indexes for performance
|
|
3. Migrates existing JSONB data to the new table automatically
|
|
4. Handles errors gracefully (doesn't fail if migration of existing data fails)
|
|
|
|
## Model Associations
|
|
|
|
```typescript
|
|
DealerProposalDetails.hasMany(DealerProposalCostItem, {
|
|
as: 'costItems',
|
|
foreignKey: 'proposalId',
|
|
sourceKey: 'proposalId'
|
|
});
|
|
|
|
DealerProposalCostItem.belongsTo(DealerProposalDetails, {
|
|
as: 'proposal',
|
|
foreignKey: 'proposalId',
|
|
targetKey: 'proposalId'
|
|
});
|
|
```
|
|
|
|
## Benefits for Frontend
|
|
|
|
1. **Consistent Format**: Always receives cost breakups as an array
|
|
2. **No Changes Required**: Frontend code doesn't need to change
|
|
3. **Better Performance**: Can query specific cost items if needed
|
|
4. **Future Extensibility**: Easy to add features like:
|
|
- Cost item categories
|
|
- Approval status per item
|
|
- Historical tracking of cost changes
|
|
|
|
## Future Enhancements
|
|
|
|
Potential future improvements:
|
|
- Add `category` field to cost items
|
|
- Add `approved_amount` vs `requested_amount` for budget approval workflows
|
|
- Add `notes` field for item-level comments
|
|
- Add audit trail for cost item changes
|
|
- Add `is_approved` flag for individual item approval
|
|
|