Additional Services & Charges
CRUD subsystem for managing optional add-on services and mandatory surcharges on a shipping service, with 19 pricing operator types, range-based plan definitions, and condition-based activation for charges.
Overview
Each shipping service can have two categories of add-ons, both stored in the same table and managed through the same API and UI:
- Additional Services (
mandatory = false): Optional features that can be enabled/disabled per shipment (e.g., insurance, signature required, extended area delivery). - Additional Charges (
mandatory = true): Surcharges automatically applied when activation conditions are met (e.g., overweight fee, oversized package, remote area surcharge).
The distinction is controlled by a single mandatory flag. Additional Charges have an extra conditions layer that defines when the charge triggers.
Key Concepts
| Concept | Description |
|---|---|
| Additional Service | An add-on with mandatory = false, toggleable per shipment |
| Additional Charge | An add-on with mandatory = true, auto-applied when conditions match |
| Operator | One of 19 pricing calculation methods (flat, percentage, weight-based, range-based, etc.) |
| Plan Definitions | Range-based pricing rows for operators 4, 15, 19 (weight ranges or amount ranges with per-range rates) |
| Condition | A rule that determines when an additional charge is applied: reference_value operator value (e.g., weight > 30) |
| Apply To | Scope of charge calculation: shipment (once), package (per package), or unit (per item) |
| Include VAT | Whether VAT is already included in the configured amount (for fiscal reporting) |
Operator Reference
| ID | Name | Amount Fields | Description |
|---|---|---|---|
| 1 | Flat | amount | Fixed fee in service currency |
| 2 | Percentage | amount (%) | Percentage of shipment value |
| 3 | Highest: Flat or % | amount (%), minimum_amount | Higher of percentage or flat minimum |
| 4 | Range Price Plans | plan_definitions | Different rates per weight range |
| 5 | Min + Commission | minimum_amount, amount (%) | Base minimum plus percentage of insurance |
| 6 | Web Service % | amount (%) | Percentage of external web service price |
| 7 | Web Service Response | — | Amount returned by external service |
| 8 | Min or User Input | minimum_amount | User enters amount, minimum enforced |
| 9 | Flat USD | amount (USD) | Fixed fee in US dollars |
| 10 | Package Weight | amount (per kg) | Multiplied by package weight |
| 11 | Highest: Flat or % (COD) | amount (%), minimum_amount | Same as 3, for cash on delivery |
| 12 | Flat + Insurance | amount | Flat fee plus insurance-based calculation |
| 13 | Highest: Flat or % (USD) | amount (%), minimum_amount (USD) | Same as 3 with USD minimum |
| 14 | Weight Over Allowed | amount (per kg) | Applied to weight exceeding limit |
| 15 | Range Plans (Amount) | plan_definitions | Different rates per declared value range |
| 16 | Package Weight or Min | minimum_amount, amount (per kg) | Higher of minimum or weight × rate |
| 17 | Base + Package Weight | base_amount, amount (per kg) | Fixed base plus weight × rate |
| 18 | Higher: Carrier or % | amount (%) | Higher of carrier surcharge or percentage |
| 19 | Range Plans (Insurance) | plan_definitions | Different rates per insurance value range |
Percentage operators store the amount as a decimal (0.15 = 15%). The UI converts for display.
Range-based operators (4, 15, 19) use plan_definitions instead of the main amount fields.
Data Flow
List and Toggle Flow
Create Flow
Database
Tables
| Table | Purpose |
|---|---|
additional_service_prices | Main record: service_id, operator, amounts, mandatory flag, apply_to, tax config, active |
additional_service_plan_definitions | Range rows for operators 4/15/19: min_value, max_value, operation_id, amount |
additional_service_conditions | Activation conditions for charges: reference_value, operator, value, active |
catalog_additional_services | Catalog of available service types (name, description) |
catalog_additional_service_operators | Catalog of operators (id, name, description) |
Key Fields (additional_service_prices)
| Column | Type | Description |
|---|---|---|
id | INT (PK) | Auto-increment |
service_id | INT (FK) | Reference to services |
additional_service_id | INT (FK) | Reference to catalog |
operation_id | INT | Operator type (1–19) |
mandatory | TINYINT | 0 = additional service, 1 = additional charge |
apply_to | ENUM | shipment, package, or unit |
amount | DECIMAL | Main amount (meaning depends on operator) |
minimum_amount | DECIMAL | Minimum amount (for composite operators) |
maximum_amount | DECIMAL | Maximum cap (optional) |
base_amount | DECIMAL | Base amount (for operator 17) |
editable | TINYINT | Whether amount is editable per shipment |
force_save | TINYINT | Force save even if already present |
active | TINYINT | 0 = inactive, 1 = active |
include_vat | TINYINT | Whether VAT is included in amount |
tax_percentage_included | DECIMAL | Tax rate to apply (from service tax rules) |
Entity Relationships
Condition Fields
| Column | Type | Description |
|---|---|---|
id | INT (PK) | Auto-increment |
reference_value | VARCHAR | Property to evaluate (weight, length, zone, etc.) |
condition | VARCHAR | Comparison operator (>, >=, <, <=, =, !=) |
value | DECIMAL/VARCHAR | Threshold value |
active | TINYINT | 0 = disabled, 1 = enabled |
Available reference values: weight, rawWeight, length, width, height, dimensionsSum, dimensionsCubic, insurance, declaredValueUSD, zone, shipmentWeight, companyId, custom.
Key Decisions
| Decision | Reasoning | Alternatives Considered |
|---|---|---|
| Single table for services + charges | mandatory flag is the only behavioral difference. Avoids table duplication. | Separate tables (redundant schema, duplicate API) |
| 19 operator types | Covers all carrier billing models encountered across 50+ carriers in production | Generic formula engine (too complex for operators to configure), fewer operators (insufficient coverage) |
| Operator locked after creation | Changing operator would require migrating amount fields and plan definitions — error-prone | Allow operator change with data migration (complex, risky) |
| Plan definitions for range-based operators | Ranges with different rates per interval is a common carrier pricing model | JSON blob (not queryable), dynamic columns (schema nightmare) |
| Conditions as separate table | Multiple conditions per charge, independently toggleable, extensible | JSON array in main table (loses queryability), single condition field (insufficient) |
Related Documentation
- API Endpoints — Additional services/charges endpoint reference
- UI Screens & Flows — Offcanvas, tables, and CRUD flows
- User Guide — How to manage additional services and charges
