# Fees Language: `en` Mirror page: [fees.md](fees.md) Status: `partial migration` See also the historical technical page: `../fees.md`. > **Operational summary** > A fee carries an amount, but its quantity must remain aligned with the lots > it applies to. `Per packing` is the only exception: it follows packing units, > not lot weight. | Marker | Topic | Short rule | | --- | --- | --- | | Quantity | `fee.quantity` | Sum of linked lots in `fee.lots`. | | State | empty `fee.qt_state` | Uses the contract `weight basis`. | | State | filled `fee.qt_state` | Uses that state, capped by the `weight basis`. | | Fallback | state missing on lot | Uses the closest previous state by `sequence`. | | Net / gross | `fee.weight_type` | `net` reads `quantity`, `brut` reads `gross_quantity`. | | Packing | `ppack` | Packing quantity, decoupled from weight. | | Control | Python + SQL | Application guard and SQL diagnostic. | ## Consultant Rules ### BR-PT-FEE-001 - Freight value from shipment fee Source: `BR-PT-003` #### Consultant Rule The freight value printed on invoice documents comes from the maritime freight fee on the shipment, not from a direct invoice field. #### Developer Notes - Find the physical lot from the invoice. - Find its `shipment_in`. - Search the `fee.fee` with `product.name = 'Maritime freight'`. - Use `fee.get_amount()`. ### BR-PT-FEE-002 - Effective lots for fees Source: `BR-PT-021` #### Consultant Rule A fee follows the virtual lot until a physical lot is linked. As soon as a physical lot is linked, physical lots become the calculation basis for the fee. #### Developer Notes - Do not remove the virtual lot link: it remains the fallback. - Effective lots are: - physical lots if at least one physical lot is linked; - otherwise virtual lots. - The same selection applies to fee PnL. - Synchronization points: - fee creation; - `fee.lots` link; - `quantity_theorical` change; - weighing; - physical lot removal. ### BR-PT-FEE-003 - Fee quantity #### Consultant Rule The quantity of a fee follows the quantity life cycle of its linked lots. It must represent the sum of those lots in the authorized contractual quantity state. #### Short Rule ```text fee.quantity = sum(applicable quantity of effective fee.lots) ``` #### Quantity State Selection | Case | Target state | | --- | --- | | empty `fee.qt_state` | `weight basis` of the fee carrier contract | | filled `fee.qt_state` | `fee.qt_state`, unless later than the `weight basis` | | `fee.qt_state` later than `weight basis` | `weight basis` | | target state missing on lot | closest previous state by `lot.qt.type.sequence` | | no target state available | current lot weight, only when no `weight basis` applies | #### Weight Basis Source | Fee | `weight basis` | | --- | --- | | Purchase fee | `fee.line.purchase.wb.qt_type` | | Sale fee | `fee.sale_line.sale.wb.qt_type` | | Shipment fee | purchase weight basis of the lot when present | | Shipment fee without purchase | sale weight basis of the lot when present | #### Net / Gross - If `fee.weight_type = net`: - read `lot.qt.hist.quantity`. - If `fee.weight_type = brut`: - read `lot.qt.hist.gross_quantity`. #### Per Packing - `mode = ppack` is excluded from the weight rule. - `fee.quantity` represents a packing quantity. - This quantity may be decoupled from net or gross weight. #### Lump Sum - `mode = lumpsum` also follows the quantity rule. - The amount remains fixed. - The quantity gives a more accurate per-ton price. ### BR-PT-FEE-004 - `% rate` fees from financing delta Source: historical `BR-PT-016` and `2026-04-30` notes #### Consultant Rule Percentage financial fees are calculated with the financing delta from the `BL date` estimated line, not with the current date. #### Developer Notes - Formula: `amount = unit_price * quantity * (price / 100) * fin_int_delta / 360`. - Delta source: `Estimated date` line with `trigger = bldate`. - If no `bldate` line exists, do not calculate a `% rate` amount. ## Developer Section ### Key Fields - Fee: `fee.fee` - Fee lots: `fee.lots` - Fee quantity: `fee.fee.quantity` - Mode: `fee.fee.mode` - Optional quantity state: `fee.fee.qt_state` - Net / gross: `fee.fee.weight_type` - Lot quantity state: `lot.qt.hist.quantity_type` - Net quantity: `lot.qt.hist.quantity` - Gross quantity: `lot.qt.hist.gross_quantity` - State order: `lot.qt.type.sequence` - Purchase / sale weight basis: `purchase.weight.basis.qt_type` ### Calculation Functions - `Fee._get_effective_fee_lots()`: - selects physical lots, then virtual lots. - `Fee._target_qt_type_for_lot()`: - chooses `fee.qt_state` or the `weight basis`; - caps the state at the `weight basis`. - `Fee._select_lot_qt_type()`: - takes the exact state when it exists; - otherwise takes the closest previous state by `sequence`. - `Fee._get_lot_fee_quantity()`: - reads net or gross depending on `weight_type`. - `Fee.sync_quantity_from_lots()`: - resynchronizes `fee.quantity`. ### Python Guards - Central check: - `fee.fee.assert_quantity_consistency()` - `fee.fee.assert_quantities_consistency()` - Trigger points: - fee creation; - fee modification; - `fee.lots` creation / modification / deletion; - weighing through linked fee resynchronization. - Exception: - `mode = ppack` is not controlled as weight. ### Known Gap - Lot split / merge: - the workflow may clone or create physical lots; - rewiring `fee.lots` to the new lots is not guaranteed yet; - affected fees must therefore be checked with the SQL diagnostic after this workflow is used. ### SQL Diagnostic - Script: - [sql/fee_quantity_consistency_checks.sql](sql/fee_quantity_consistency_checks.sql) - Usage: - audit test databases; - audit historical data; - qualify data before correction. - The script returns non-`ppack` fees where `fee.quantity` does not match the sum of effective lots according to the applicable quantity state.