Pricing & Costs UI
Frontend screens, components, and user flows for the cost and pricing matrix subsystem.
Component Overview
| Component | Description |
|---|---|
Costs.vue | Operational cost section: form + matrix + extra weights + CSV + bulk |
Pricing.vue | Pricing section: form + tabbed matrix + CSV + bulk + margin indicators |
useServicePricing.js | Composable encapsulating all pricing logic (~820 lines) |
Bulk.modal.vue | Shared modal for bulk cell operations (add/sub/replace) |
UpgradePricing.modal.vue | Modal to recalculate pricing from costs by percentage |
MatrixView | Shared component (components/interface/Matrix/MatrixView.vue) for editable zone × range grids |
Costs Section (Costs.vue)
Layout
┌──────────────────────────────────────────────────────────────┐
│ 💰 Operational Costs [Upload ▼] [Save] │
│ Subtitle: Configure carrier costs... ├─ Download Costs │
│ └─ Upgrade Pricing │
├──────────────────────────────────────────────────────────────┤
│ FormGenerator: │
│ [ Locale ▼ ] [ Currency (disabled) ] [ Weight Unit ▼ ] │
│ [ Fuel Type ▼ ] [ Fuel Percentage % ] │
├──────────────────────────────────────────────────────────────┤
│ MatrixView: "Costs" │
│ ┌────────┬──────┬──────┬──────┬──────┐ │
│ │ Weight │Zone 1│Zone 2│Zone 3│Zone 4│ │
│ ├────────┼──────┼──────┼──────┼──────┤ │
│ │ 0-1 KG │$120 │$150 │$180 │$210 │ │
│ │ 1-5 KG │$180 │$220 │$260 │$300 │ │
│ └────────┴──────┴──────┴──────┴──────┘ │
├──────────────────────────────────────────────────────────────┤
│ MatrixView: "Extra Weight Cost" │
│ ┌────────┬──────┬──────┬──────┬──────┐ │
│ │ 5 KG + │ $15 │ $18 │ $20 │ $25 │ │
│ └────────┴──────┴──────┴──────┴──────┘ │
├──────────────────────────────────────────────────────────────┤
│ [Save] ▸ │
└──────────────────────────────────────────────────────────────┘Data Flow
- Load:
onMounted→GET /services/{id}/costs→matrixDataref + store - Edit: Cell changes update
matrixDatalocally viahandleCellUpdate - Bulk: Select cells →
Bulk.modal→handleBulkActionApplyupdates cells in memory - CSV Upload: Parse CSV → rebuild
matrixData(zones, ranges, extra_weights) → update store - Save: Validate form → build payload from
MatrixView.getMatrixData()→Swal.question()→PUT /services/{id}/costs→ reload
Form Fields
| Field | Type | Source |
|---|---|---|
locale_id | Select | GET /catalog/locales |
currency | Input (disabled) | Auto-filled from locale |
weight_unit_code | Select | Static: KG, LB |
fuel_type | Select | Static: flat, dynamic |
fuel_percentage | Input-group (%) | User input |
CSV Structure
| Column | Required | Description |
|---|---|---|
service_id | Yes | Service identifier |
zone | Yes | Zone number |
weight_extra_cost | Yes | Per-unit extra weight cost |
weight_max | Yes | Weight range upper bound |
cost | Yes | Cost value |
Pricing Section (Pricing.vue)
Layout
┌──────────────────────────────────────────────────────────────┐
│ 📈 Pricing [Upload ▼] [Save ▼] │
│ Subtitle: Configure selling prices... └─ Download Pricing │
├──────────────────────────────────────────────────────────────┤
│ FormGenerator: │
│ [ Locale ▼ ] [ Currency (disabled) ] [ Weight Unit ▼ ] │
│ [ Operator ▼ ] [ Extended Zone $ ] │
├──────────────────────────────────────────────────────────────┤
│ Tabs: [ Basic | Pro | Enterprise | Corporate ] │
│ │
│ Toggle: [ Markup | Margin ] │
│ │
│ ⚠️ Danger margin alert (if any cell < 15%) │
│ ℹ️ Disclaimer: Margin = (price - cost) / price × 100 │
│ │
│ MatrixView: "Pricing Matrix" │
│ ┌────────┬────────────┬────────────┬────────────┐ │
│ │ Weight │ Zone 1 │ Zone 2 │ Zone 3 │ │
│ ├────────┼────────────┼────────────┼────────────┤ │
│ │ 0-1 KG │ $180 │ $220 │ $260 │ │
│ │ │ 25% ✅ │ 30% ✅ │ 20% ⚠️ │ │
│ ├────────┼────────────┼────────────┼────────────┤ │
│ │ 1-5 KG │ $250 │ $300 │ $350 │ │
│ │ │ 22% ⚠️ │ 18% ⚠️ │ 12% 🔴 │ │
│ └────────┴────────────┴────────────┴────────────┘ │
│ │
│ MatrixView: "Extra Weight Cost" │
│ ┌────────┬──────┬──────┬──────┐ │
│ │ 5 KG + │ $22 │ $26 │ $30 │ │
│ └────────┴──────┴──────┴──────┘ │
├──────────────────────────────────────────────────────────────┤
│ [Save All Tabs] [Save Current Tab] ▸ │
└──────────────────────────────────────────────────────────────┘Cell Formatting
Each pricing cell displays HTML content (:cell-html-content="true") with two lines:
Flat operator (1):
- Line 1: Selling price (with strikethrough if changed from initial)
- Line 2: Margin/markup % with color class
Margin operator (2):
- Line 1: Calculated selling price =
graduated_cost × (1 + markup%/100) - Line 2: Markup or margin % depending on
viewTypetoggle
The formatCell function in useServicePricing handles all formatting logic, including:
- Fetching cost from
costMatrixby row/col index - Computing graduated cost (cost + tax + fuel)
- Calculating margin/markup based on
viewType - Generating HTML with strikethrough for changed values
Margin Color Coding
Defined in constants/pricing.constant.js:
SECURE_MARGINS = {
DANGER: { MAX: 15 }, // text-danger (red)
WARNING: { MIN: 15, MAX: 25 }, // text-warning (yellow)
SAFE: { MIN: 25 } // text-success (green)
}CSV Structure
| Column | Required | Description |
|---|---|---|
service_id | Yes | Service identifier |
zone | Yes | Zone number |
weight_extra_cost | Yes | Per-unit extra weight price |
weight_max | Yes | Weight range upper bound |
basic_plan | No | Basic tariff price/markup |
pro_plan | No | Pro tariff price/markup |
enterprise_plan | No | Enterprise tariff price/markup |
corporate_plan | No | Corporate tariff price/markup |
Only tariff columns present in the CSV are updated; missing columns are preserved as-is.
useServicePricing Composable
The composable encapsulates all pricing state and logic. Used exclusively by Pricing.vue.
State
| Ref/Computed | Type | Description |
|---|---|---|
matrixData | ref | Current pricing data (zones, ranges per tab, extra_weights per tab) |
locales | ref | Locale options for the form |
loading / error | ref | Loading and error state |
tabs | computed | 4 tabs from RATE_TYPES |
activeTab | ref | Currently selected tab name |
viewType | ref | 'markup' or 'margin' |
costMatrix | computed | Cost data from store (for margin calculations) |
taxes | computed | Default tax rule from store |
Key Methods
| Method | Description |
|---|---|
loadData() | Fetch pricing from API, update store |
handleCellUpdate(event, tab, type) | Immutably update a cell in matrixData |
handleBulkActionApply(action) | Apply bulk operation to selected cells |
handleSaveActiveTab() | Save current tab only |
handleSaveAllTabs() | Save all 4 tabs sequentially |
buildPayloadForTab(tab) | Build API payload from MatrixView refs |
handleUpgradePricing(event) | Recalculate all tabs from cost matrix + percentages |
handleLoad(data, modalRef) | Parse CSV and merge into matrixData |
downloadPricing() | Export all tabs as multi-sheet Excel |
formatCell(value, meta) | Format cell as HTML with price + margin |
formatCellExtraWeight(value, meta) | Format extra weight cell as HTML |
hasDangerMarginInTab(tab) | Check if any cell has margin < 15% |
parseCsvToMatrixData(data, base) | Parse CSV rows into matrix structure |
Bulk Actions Modal (Bulk.modal.vue)
Shared between Costs and Pricing. Operates on selected cells from a MatrixView.
| Field | Options |
|---|---|
| Operation | Add, Subtract, Replace |
| Type | Fixed amount, Percentage |
| Value | Numeric input |
| Decimal Config | Round, Truncate |
| Decimal Places | 0–10 |
The modal emits apply with the operation config and the selected cell coordinates. The parent component applies the transformation to each cell.
Upgrade Pricing Modal (UpgradePricing.modal.vue)
Accessible from the Costs section dropdown. Configures bulk pricing recalculation.
| Setting | Description |
|---|---|
| Operator | Flat or Margin (determines calculation method) |
| Decimal Config | Round or Truncate |
| Decimal Places | Global default |
| Per tariff type | Enable/disable, global %, or per-zone % with per-zone decimals |
Emits apply via EventBus('upgrade-pricing') which is caught by useServicePricing.handleUpgradePricing().
Key Patterns
- MatrixView refs by tab:
setMatrixViewRef(tabName, el, type)stores refs inmatrixViewsandmatrixViewExtraCostsobjects, keyed by tab name - Immutable state updates:
handleCellUpdatecreates new arrays instead of mutating in place, ensuring Vue reactivity - Cross-component communication:
EventBusfor Upgrade Pricing (Costs → Pricing), avoiding tight coupling - Locale-currency sync: Watching
locale_idchanges to auto-fillcurrencyfrom locale data - Operator-matrixData sync: Watching
operatorchanges to keepmatrixData.operatorandserviceDetails.pricing.operatorin sync
