Additional Services & Charges UI
Frontend screens, components, and user flows for managing additional services and additional charges.
Component Overview
| Component | Description |
|---|---|
AdditionalServices.vue | Table of optional services (mandatory=false) with toggle and actions |
AdditionalCharges.vue | Table of mandatory charges (mandatory=true) with toggle and actions |
AdditionalService.offcanvas.vue | Shared offcanvas for add/edit (both services and charges) |
Both table components are nearly identical — the key differences are:
- The API call uses
?mandatory=falsevs?mandatory=true AdditionalChargesopens the offcanvas withshowAddForCharge()(setsmandatory=trueby default)- The offcanvas shows a Conditions section only when
mandatory=true
Table Layout
Both AdditionalServices.vue and AdditionalCharges.vue share the same layout:
┌──────────────────────────────────────────────────────────────┐
│ 🧩 Additional Services [+ Add] │
│ Subtitle: Optional add-on services... │
├──────────────────────────────────────────────────────────────┤
│ SimpleTable: │
│ ┌─────────┬──────────┬────────┬─────┬─────┬────────┬──────┐│
│ │Descript.│ Operator │ Amount │ Min │ Max │ Active │Action││
│ ├─────────┼──────────┼────────┼─────┼─────┼────────┼──────┤│
│ │Insurance│Min+Comm │ 3% │ $50 │ — │ [✓] │[Edit]││
│ │Ext.Area │Range Plan│2 ranges│ — │ — │ [✓] │[Edit]││
│ │Signature│Flat │ $35 │ — │ — │ [ ] │[Edit]││
│ └─────────┴──────────┴────────┴─────┴─────┴────────┴──────┘│
└──────────────────────────────────────────────────────────────┘Cell Formatting
The formatCellValue function handles smart display based on operator type:
| Operator Type | Amount Display |
|---|---|
| Percentage operators (2, 3, 5, 6, 11, 13, 18) | 15% (value × 100) |
| Flat USD (9) | $25.00 USD |
| Highest Flat/% USD (13) | minimum_amount shows in USD |
| Package Weight (10, 16) | $5.00 / kg |
| Base + Weight (17) | $100.00 + $5.00 / kg |
| Range-based (4, 15, 19) | "2 ranges" (count of plan_definitions) |
| All others | Currency formatted amount |
Toggle Active
The toggle switch calls PATCH /services/{id}/additionals/{itemId} with { active: 0|1 } immediately on change. No confirmation dialog — the change is instant with a toast notification.
On error, the toggle reverts to its previous state via nextTick.
Offcanvas (AdditionalService.offcanvas.vue)
The shared offcanvas handles both add and edit for both services and charges. It opens at 66% width and contains 3 form sections plus an optional conditions section.
Entry Points
| Method | Caller | Behavior |
|---|---|---|
showAdd() | AdditionalServices.vue | Opens in add mode, mandatory=false |
showAddForCharge() | AdditionalCharges.vue | Opens in add mode, mandatory=true |
showEdit(item) | Both tables | Opens in edit mode, pre-fills all fields |
Form Structure
┌──────────────────────────────────────────────────────────────┐
│ Add Additional Service [Save] │
├──────────────────────────────────────────────────────────────┤
│ [Service Type ▼] (add mode only) │
│ │
│ ─── General Configuration ────────────────────────────── │
│ [ Apply To ▼ ] │
│ [ ] Mandatory (info: auto-applied when conditions met) │
│ [ ] Editable (info: amount can be modified per shipment)│
│ [ ] Force Save (info: save even if already present) │
│ [ ] Active (info: enable or disable) │
│ │
│ ─── Taxes ────────────────────────────────────────────── │
│ [ ] Include VAT (info: VAT included in amount) │
│ [ Tax % ▼ ] (options from service tax rules) │
│ │
│ ─── Pricing ──────────────────────────────────────────── │
│ [ Operator ▼ ] (locked in edit mode) │
│ [ Amount fields ] (dynamic, based on operator) │
│ │
│ ┌─ Plan Definitions (operators 4, 15, 19 only) ──────────┐ │
│ │ [+ Add Range] │ │
│ │ ┌─────┬─────┬──────────┬────────┬─────────┬──────┐ │ │
│ │ │ Min │ Max │ Rate Type│ Amount │ Min Amt │ 🗑️ │ │ │
│ │ ├─────┼─────┼──────────┼────────┼─────────┼──────┤ │ │
│ │ │ 0 │ 5 │ Flat │ $85 │ — │ [x] │ │ │
│ │ │ 5 │ 30 │ Highest │ 15% │ $85 │ [x] │ │ │
│ │ └─────┴─────┴──────────┴────────┴─────────┴──────┘ │ │
│ └─────────────────────────────────────────────────────────┘ │
│ │
│ ┌─ Conditions (mandatory=true only) ──────────────────────┐ │
│ │ [+ Add Condition] │ │
│ │ ┌───────────┬──────────┬───────┬────────┬──────┐ │ │
│ │ │ Reference │ Operator │ Value │ Active │ 🗑️ │ │ │
│ │ ├───────────┼──────────┼───────┼────────┼──────┤ │ │
│ │ │ weight │ > │ 30 │ [✓] │ [x] │ │ │
│ │ │ zone │ = │ 8 │ [✓] │ [x] │ │ │
│ │ └───────────┴──────────┴───────┴────────┴──────┘ │ │
│ └─────────────────────────────────────────────────────────┘ │
└──────────────────────────────────────────────────────────────┘Dynamic Amount Fields
Amount fields change based on the selected operator. The mapping is defined in OPERATOR_FIELDS in pricing.constant.js:
| Operator | Fields Shown |
|---|---|
| 1 (Flat) | amount (currency) |
| 2 (Percentage) | amount (%) |
| 3 (Highest) | amount (%), minimum_amount (currency) |
| 5 (Min+Commission) | minimum_amount (currency), amount (%) |
| 9 (Flat USD) | amount (USD) |
| 10 (Package Weight) | amount (currency/kg) |
| 16 (Weight or Min) | minimum_amount (currency), amount (currency/kg) |
| 17 (Base+Weight) | base_amount (currency), amount (currency/kg) |
| 4, 15, 19 (Range) | No amount fields — uses plan_definitions table instead |
| 7 (Web Service) | No amount fields |
Plan Definitions Table
Visible only for operators 4, 15, and 19. Each row represents a pricing range:
| Column | Op 4 (Weight) | Op 15/19 (Amount/Insurance) |
|---|---|---|
| Min Value | Weight in kg | Amount in currency |
| Max Value | Weight in kg | Amount in currency |
| Rate Type | Flat (1), % (2), Highest (3) | Same |
| Amount | Rate for this range | Rate for this range |
| Min Amount | Flat fallback (op 3 only) | Flat fallback (op 3 only) |
Rate type operators within plan definitions:
- 1 (Flat): Amount is a fixed fee
- 2 (Percentage): Amount is stored as decimal (0.15), displayed as 15%
- 3 (Highest): Higher of percentage or minimum_amount
Conditions Table
Visible only when mandatory=true (additional charges). Each row is an activation condition:
| Column | Component | Description |
|---|---|---|
| Reference Value | Select2 | Property to evaluate (weight, zone, length, etc.) |
| Operator | Select2 | Comparison: >, >=, <, <=, =, != |
| Value | Text input | Threshold value |
| Active | Switch | Enable/disable this condition |
| Actions | Button | Remove row |
Payload Building
The buildPayloadForOperator function handles operator-specific payload construction:
- Reads form state from
FormGenerator - Converts percentage amounts from display (15) to API decimal (0.15) via
toDecimalPct - For range-based operators, calls
buildPlanDefinitionsPayload()to serialize plan definitions - For mandatory items, calls
buildConditionsPayload()to serialize conditions - Maps boolean switches to 0/1 integers
Validation
- Service type (add mode): Required, validated on submit
- Operator: Required, validated on submit
- Plan definitions (ops 4/15/19): At least one row required, each row must have min_value, max_value, operation_id, amount
- Form fields: Validated via FormGenerator + Vuelidate
- Field-level validation errors are shown inline with
is-invalidclasses
Constants (pricing.constant.js)
Key exports used by this subsystem:
| Export | Description |
|---|---|
OPERATOR_IDS | Named constants for all 19 operators |
OPERATOR_FIELDS | Per-operator form field definitions |
PERCENTAGE_AMOUNT_OPERATOR_IDS | Set of operators that store amount as decimal |
NO_AMOUNT_FIELDS_OPERATOR_IDS | Set of operators with no amount form fields |
PLAN_DEFINITIONS_OPERATOR_IDS | Array of operators using plan_definitions (4, 15, 19) |
PLAN_ROW_PERCENTAGE_IDS | Set of plan definition rate types that use percentage (2, 3) |
CONDITION_REFERENCE_VALUES | Array of available condition reference values |
CONDITION_OPERATORS | Array of condition comparison operators |
APPLY_TO_OPTIONS | Apply-to scope options (unit, package, shipment) |
Key Patterns
- Shared offcanvas: Single component handles both add and edit for both services and charges, reducing duplication
- Dynamic form sections:
FormGeneratorwith computedformWithGeneralConfigthat rebuilds when operator changes - Operator-locked edit:
operation_idselect is disabled in edit mode to prevent data model conflicts - Percentage conversion: UI shows percentages as integers (15%), API stores as decimals (0.15); conversion happens in
buildPayloadForOperatorandformBaseForOperator - Catalog lazy-load: Operators and service types are fetched once on mount and cached in component refs
- Teleport-free: Unlike Organization Settings, this offcanvas doesn't need teleport since it's not inside another overlay
