Each (valid) line can have one or more detailed lines (children). These lines represent the actual sub-charges that are caused by the parent line.
Example:
If a line has:
Usage of 200 units
Tiered pricing:
Tier1: 1 - 50 units cost flat $300
Tier2: 51 - 100 units cost flat $400
Tier3: 100 - 150 units cost flat $400 + $1/unit
Tier4: more than 150 units cost $15/unit
This would yield the following lines:
Line with quantity=200
Line quantity=1 per_unit_amount=300 total=300 (Tier1)
Line quantity=1 per_unit_amount=400 total=400 (Tier2)
Line quantity=1 per_unit_amount=400 total=400 (Tier3, flat component)
Line quantity=50 per_unit_amount=1 total=50 (Tier3, per unit price)
Line quantity=50 per_unit_amount=15 total=759 (Tier4)
Apps can choose to syncronize the original line (if the upstream system understands our pricing model) or can use the sublines to syncronize individual lines without having to understand billing details.
Detailed Lines vs Splitting
When we are dealing with a split line, the calculation of the quantity is by taking the meter's quantity for the whole line period ([parent.period.start, splitline.period.end]) and the amount before the period (parent.period.start, splitline.period.start).
When substracting the two we get the delta for the period (this gets the delta for all supported meter types except of Min and Avg).
We execute the pricing logic (e.g. tiered pricing) for the line qty, while considering the before usage, as it reflects the already billed for items.
Corner cases:
Graduating tiered prices cannot be billed mid-billing period (always arrears, as the calculation cannot be split into multiple items)
Min, Avg meters are always billed arrears as we cannot calculate the delta.
Detailed line persisting
In order for the calculation logic, to not to have to deal with the contents of the database, it is (mostly) the adapter layer's responsibility to understand what have changed and persist only that data to the database.
If an entity has changed compared to the database fetch, it will be updated
If a child line, discount gets removed, it will be removed from the database (in case of lines with all sub-entities)
If an entity doesn't have an ID a new entity will be generated by the database
For idempotent entity sources (detailed lines and discounts for now), we have also added a field called ChildUniqueReferenceID which can be used to detect entities serving the same purpose.
ChildUniqueReferenceID example
Let's say we have an usage-based line whose detailed lines are persisted to the database, but then we would want to change the quantity of the line.
First we load the existing detailed lines from the database, and save the database versions of the entities in memory.
We execute the calculation for the new quantity that yields new detailed lines without database IDs.
The entity's ChildrenWithIDReuse call can be used to facilitate the line reuse by assigning the known IDs to the yielded lines where the ChildUniqueReferenceID is set.
Then the adapter layer will use those IDs to make decisions if they want to persist or recreate the records.
We could do the same logic in the adapter layer, but this approach makes it more flexible on the calculation layer if we want to generate new lines or not. If this becomes a burden we can do the same matching logic as part of the upsert logic in adapter.
Overview
Each (
valid
) line can have one or more detailed lines (children). These lines represent the actual sub-charges that are caused by the parent line.Example:
This would yield the following lines:
Apps can choose to syncronize the original line (if the upstream system understands our pricing model) or can use the sublines to syncronize individual lines without having to understand billing details.
Detailed Lines vs Splitting
When we are dealing with a split line, the calculation of the quantity is by taking the meter's quantity for the whole line period ([
parent.period.start
,splitline.period.end
]) and the amount before the period (parent.period.start
,splitline.period.start
).When substracting the two we get the delta for the period (this gets the delta for all supported meter types except of Min and Avg).
We execute the pricing logic (e.g. tiered pricing) for the line qty, while considering the before usage, as it reflects the already billed for items.
Corner cases:
Detailed line persisting
In order for the calculation logic, to not to have to deal with the contents of the database, it is (mostly) the adapter layer's responsibility to understand what have changed and persist only that data to the database.
In practice the high level rules are the following (see adapter/invoicelinediff_test.go for examples):
For idempotent entity sources (detailed lines and discounts for now), we have also added a field called
ChildUniqueReferenceID
which can be used to detect entities serving the same purpose.ChildUniqueReferenceID example
Let's say we have an usage-based line whose detailed lines are persisted to the database, but then we would want to change the quantity of the line.
First we load the existing detailed lines from the database, and save the database versions of the entities in memory.
We execute the calculation for the new quantity that yields new detailed lines without database IDs.
The entity's
ChildrenWithIDReuse
call can be used to facilitate the line reuse by assigning the known IDs to the yielded lines where theChildUniqueReferenceID
is set.Then the adapter layer will use those IDs to make decisions if they want to persist or recreate the records.
We could do the same logic in the adapter layer, but this approach makes it more flexible on the calculation layer if we want to generate new lines or not. If this becomes a burden we can do the same matching logic as part of the upsert logic in adapter.