Price Plan Matrix
The PricePlanMatrix is the modern, versioned pricing structure in Opencell v12+. It replaces the older single PricePlan object with a multi-layered, versioned structure.
Structure Overview
PricePlanMatrix (parent container with filtering criteria)
└── PricePlanMatrixVersion (versioned, status: DRAFT/PUBLISHED/CLOSED)
├── validity: DatePeriod (when active)
├── isMatrix: boolean (single price vs matrix)
├── price: BigDecimal (if not matrix)
├── columns: Set<PricePlanMatrixColumn> (matrix dimensions)
└── lines: Set<PricePlanMatrixLine> (matrix rows)
├── priority: int (lower = higher)
├── ratingAccuracy: int (specificity score)
└── values: Set<PricePlanMatrixValue> (cell values)
PricePlanMatrix (Container)
The parent entity that links to ChargeTemplates and defines filtering criteria.
Key Fields:
| Field | Type | Description |
|---|---|---|
code | String | Unique identifier |
chargeTemplates | Set | Linked ChargeTemplates (many-to-many) |
seller | Seller | Filter by seller |
offerTemplate | OfferTemplate | Filter by offer |
tradingCountry | Country | Filter by country |
tradingCurrency | Currency | Filter by currency |
criteria1-3 | String | Custom criteria (EL expressions) |
minQuantity | BigDecimal | Minimum quantity for this plan |
maxQuantity | BigDecimal | Maximum quantity for this plan |
subscriptionDateRange | DatePeriod | Filter by subscription date |
ratingDateRange | DatePeriod | Filter by rating date |
Charge Template Linkage:
@ManyToMany(fetch = FetchType.LAZY)
@JoinTable(name = "cat_price_plan_charge",
joinColumns = @JoinColumn(name = "price_plan_id"),
inverseJoinColumns = @JoinColumn(name = "charge_id"))
private Set<ChargeTemplate> chargeTemplates;
PricePlanMatrixVersion
Versioned pricing with lifecycle status.
Status Lifecycle:
DRAFT → PUBLISHED → CLOSED
Key Fields:
| Field | Type | Description |
|---|---|---|
validity | DatePeriod | When this version is active |
status | VersionStatusEnum | DRAFT, PUBLISHED, CLOSED |
isMatrix | boolean | false = single price, true = matrix |
price | BigDecimal | Price if not matrix |
columns | Set | Matrix dimensions (if matrix) |
lines | Set | Matrix rows (if matrix) |
PricePlanMatrixLine
Individual price lines within a matrix.
Key Fields:
| Field | Type | Description |
|---|---|---|
priority | int | Lower = matched first |
ratingAccuracy | int | Specificity score (0 = default/fallback) |
values | Set | Column values for this line |
price | BigDecimal | Price for this line |
amountWithoutTaxEL | String | EL expression for dynamic pricing |
amountWithTaxEL | String | EL expression for dynamic pricing |
scriptInstance | ScriptInstance | Script for complex pricing |
ratingAccuracy Field
The ratingAccuracy indicates specificity (number of non-null column values):
ratingAccuracy=0: Default/fallback line (matches anything)ratingAccuracy=1: Line with 1 specific column valueratingAccuracy=3: Line with 3 specific column values (most specific)
Selection Priority:
priorityASC (explicit priority)ratingAccuracyDESC (more specific lines preferred)idASC (deterministic order)
PricePlanMatrixColumn
Defines dimensions for matrix pricing.
Column Types (ColumnTypeEnum):
| Type | Description |
|---|---|
STRING | Exact text match |
LIST_TEXT | Match any in list |
LIST_MULTIPLE_TEXT | Match all in list |
EXPRESSION_LANGUAGE | EL evaluation |
LONG | Numeric equality |
LIST_NUMERIC | Numeric list match |
DOUBLE | Decimal with precision |
Range_Date | Date falls within from/to |
Range_Numeric | Value falls within from/to |
Boolean | Boolean equality |
Version Selection During Rating
The rating service selects the appropriate price through a 3-step process:
Step 1: Find PricePlanMatrix Container
SELECT ppm FROM PricePlanMatrix ppm
WHERE ppm.chargeTemplates CONTAINS :chargeTemplate
AND (ppm.seller IS NULL OR ppm.seller = :seller)
AND (ppm.offerTemplate IS NULL OR ppm.offerTemplate = :offer)
AND (ppm.tradingCountry IS NULL OR ppm.tradingCountry = :country)
AND (ppm.tradingCurrency IS NULL OR ppm.tradingCurrency = :currency)
AND (:quantity BETWEEN ppm.minQuantity AND ppm.maxQuantity)
ORDER BY ppm.priority ASC
Step 2: Select PUBLISHED Version
SELECT ppmv FROM PricePlanMatrixVersion ppmv
WHERE ppmv.pricePlanMatrix = :ppm
AND ppmv.status = 'PUBLISHED'
AND :operationDate BETWEEN ppmv.validity.from AND ppmv.validity.to
Step 3: Match Line (if matrix)
if (version.isMatrix()) {
// Load all lines ordered by priority ASC, ratingAccuracy DESC
List<PricePlanMatrixLine> lines = version.getLines()
.stream()
.sorted(Comparator.comparing(PricePlanMatrixLine::getPriority)
.thenComparing(PricePlanMatrixLine::getRatingAccuracy, Comparator.reverseOrder()))
.collect(Collectors.toList());
for (PricePlanMatrixLine line : lines) {
if (matchesAllColumns(line, ratingContext)) {
return line.getPrice();
}
}
// Fallback to default line (ratingAccuracy=0)
return findDefaultLine(lines).getPrice();
} else {
return version.getPrice();
}