394 lines
16 KiB
Markdown
394 lines
16 KiB
Markdown
<!-- Generated from docs_source/business by docs/tools/render_business_docs.py. -->
|
|
|
|
# Fees
|
|
|
|
Language: `en`
|
|
Mirror page: [fees.md](fees.md)
|
|
Status: `partial migration`
|
|
|
|
See also the historical technical page: `../fees.md`.
|
|
|
|
<div style="border-left:0.28rem solid #4051b5; background:#f5f7ff; padding:0.85rem 1rem; margin:1rem 0 1.4rem 0; border-radius:0.35rem;"><strong style="display:block; margin-bottom:0.35rem;">Operational summary</strong><span>A fee carries an amount, but its quantity must remain aligned with the lots it applies to. <code>Per packing</code> is the only exception: it follows packing units, not lot weight.</span></div>
|
|
|
|
|
|
<table style="width:100%; border-collapse:collapse; margin:1rem 0 1.5rem 0; font-size:0.95rem;">
|
|
<thead>
|
|
<tr style="background:#eef3ff;">
|
|
<th style="text-align:left; padding:0.55rem 0.7rem; border-bottom:2px solid #4051b5;">Marker</th>
|
|
<th style="text-align:left; padding:0.55rem 0.7rem; border-bottom:2px solid #4051b5;">Topic</th>
|
|
<th style="text-align:left; padding:0.55rem 0.7rem; border-bottom:2px solid #4051b5;">Short rule</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
<tr style="border-bottom:1px solid #e0e0e0;">
|
|
<td style="vertical-align:top; padding:0.5rem 0.7rem;">Quantity</td>
|
|
<td style="vertical-align:top; padding:0.5rem 0.7rem;"><code>fee.quantity</code></td>
|
|
<td style="vertical-align:top; padding:0.5rem 0.7rem;">Sum of linked lots in <code>fee.lots</code>.</td>
|
|
</tr>
|
|
<tr style="border-bottom:1px solid #e0e0e0;">
|
|
<td style="vertical-align:top; padding:0.5rem 0.7rem;">State</td>
|
|
<td style="vertical-align:top; padding:0.5rem 0.7rem;">empty <code>fee.qt_state</code></td>
|
|
<td style="vertical-align:top; padding:0.5rem 0.7rem;">Uses the contract <code>weight basis</code>.</td>
|
|
</tr>
|
|
<tr style="border-bottom:1px solid #e0e0e0;">
|
|
<td style="vertical-align:top; padding:0.5rem 0.7rem;">State</td>
|
|
<td style="vertical-align:top; padding:0.5rem 0.7rem;">filled <code>fee.qt_state</code></td>
|
|
<td style="vertical-align:top; padding:0.5rem 0.7rem;">Uses that state, capped by the <code>weight basis</code>.</td>
|
|
</tr>
|
|
<tr style="border-bottom:1px solid #e0e0e0;">
|
|
<td style="vertical-align:top; padding:0.5rem 0.7rem;">Fallback</td>
|
|
<td style="vertical-align:top; padding:0.5rem 0.7rem;">state missing on lot</td>
|
|
<td style="vertical-align:top; padding:0.5rem 0.7rem;">Uses the closest previous state by <code>sequence</code>.</td>
|
|
</tr>
|
|
<tr style="border-bottom:1px solid #e0e0e0;">
|
|
<td style="vertical-align:top; padding:0.5rem 0.7rem;">Net / gross</td>
|
|
<td style="vertical-align:top; padding:0.5rem 0.7rem;"><code>fee.weight_type</code></td>
|
|
<td style="vertical-align:top; padding:0.5rem 0.7rem;"><code>net</code> reads <code>quantity</code>, <code>brut</code> reads <code>gross_quantity</code>.</td>
|
|
</tr>
|
|
<tr style="border-bottom:1px solid #e0e0e0;">
|
|
<td style="vertical-align:top; padding:0.5rem 0.7rem;">Packing</td>
|
|
<td style="vertical-align:top; padding:0.5rem 0.7rem;"><code>ppack</code></td>
|
|
<td style="vertical-align:top; padding:0.5rem 0.7rem;">Packing quantity, decoupled from weight.</td>
|
|
</tr>
|
|
<tr style="border-bottom:1px solid #e0e0e0;">
|
|
<td style="vertical-align:top; padding:0.5rem 0.7rem;">Control</td>
|
|
<td style="vertical-align:top; padding:0.5rem 0.7rem;">Python + SQL</td>
|
|
<td style="vertical-align:top; padding:0.5rem 0.7rem;">Application guard and SQL diagnostic.</td>
|
|
</tr>
|
|
</tbody>
|
|
</table>
|
|
|
|
## 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
|
|
|
|
<ul style="margin:0.65rem 0 1rem 1.1rem; padding-left:1rem; list-style-type:disc;">
|
|
<li style="margin:0.38rem 0;">Find the physical lot from the invoice.
|
|
</li>
|
|
<li style="margin:0.38rem 0;">Find its <code>shipment_in</code>.
|
|
</li>
|
|
<li style="margin:0.38rem 0;">Search the <code>fee.fee</code> with <code>product.name = 'Maritime freight'</code>.
|
|
</li>
|
|
<li style="margin:0.38rem 0;">Use <code>fee.get_amount()</code>.
|
|
</li>
|
|
</ul>
|
|
|
|
### 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
|
|
|
|
<ul style="margin:0.65rem 0 1rem 1.1rem; padding-left:1rem; list-style-type:disc;">
|
|
<li style="margin:0.38rem 0;">Do not remove the virtual lot link: it remains the fallback.
|
|
</li>
|
|
<li style="margin:0.38rem 0;">Effective lots are:
|
|
<ul style="margin:0.65rem 0 1rem 1.35rem; padding-left:1rem; list-style-type:circle;">
|
|
<li style="margin:0.38rem 0;">physical lots if at least one physical lot is linked;
|
|
</li>
|
|
<li style="margin:0.38rem 0;">otherwise virtual lots.
|
|
</li>
|
|
</ul>
|
|
</li>
|
|
<li style="margin:0.38rem 0;">The same selection applies to fee PnL.
|
|
</li>
|
|
<li style="margin:0.38rem 0;">Synchronization points:
|
|
<ul style="margin:0.65rem 0 1rem 1.35rem; padding-left:1rem; list-style-type:circle;">
|
|
<li style="margin:0.38rem 0;">fee creation;
|
|
</li>
|
|
<li style="margin:0.38rem 0;"><code>fee.lots</code> link;
|
|
</li>
|
|
<li style="margin:0.38rem 0;"><code>quantity_theorical</code> change;
|
|
</li>
|
|
<li style="margin:0.38rem 0;">weighing;
|
|
</li>
|
|
<li style="margin:0.38rem 0;">physical lot removal.
|
|
</li>
|
|
</ul>
|
|
</li>
|
|
</ul>
|
|
|
|
### 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
|
|
|
|
<pre style="background:#263238; color:#eef7ff; padding:1rem; border-radius:0.35rem; overflow:auto;"><code>fee.quantity = sum(applicable quantity of effective fee.lots)</code></pre>
|
|
|
|
#### Quantity State Selection
|
|
|
|
<table style="width:100%; border-collapse:collapse; margin:1rem 0 1.5rem 0; font-size:0.95rem;">
|
|
<thead>
|
|
<tr style="background:#eef3ff;">
|
|
<th style="text-align:left; padding:0.55rem 0.7rem; border-bottom:2px solid #4051b5;">Case</th>
|
|
<th style="text-align:left; padding:0.55rem 0.7rem; border-bottom:2px solid #4051b5;">Target state</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
<tr style="border-bottom:1px solid #e0e0e0;">
|
|
<td style="vertical-align:top; padding:0.5rem 0.7rem;">empty <code>fee.qt_state</code></td>
|
|
<td style="vertical-align:top; padding:0.5rem 0.7rem;"><code>weight basis</code> of the fee carrier contract</td>
|
|
</tr>
|
|
<tr style="border-bottom:1px solid #e0e0e0;">
|
|
<td style="vertical-align:top; padding:0.5rem 0.7rem;">filled <code>fee.qt_state</code></td>
|
|
<td style="vertical-align:top; padding:0.5rem 0.7rem;"><code>fee.qt_state</code>, unless later than the <code>weight basis</code></td>
|
|
</tr>
|
|
<tr style="border-bottom:1px solid #e0e0e0;">
|
|
<td style="vertical-align:top; padding:0.5rem 0.7rem;"><code>fee.qt_state</code> later than <code>weight basis</code></td>
|
|
<td style="vertical-align:top; padding:0.5rem 0.7rem;"><code>weight basis</code></td>
|
|
</tr>
|
|
<tr style="border-bottom:1px solid #e0e0e0;">
|
|
<td style="vertical-align:top; padding:0.5rem 0.7rem;">target state missing on lot</td>
|
|
<td style="vertical-align:top; padding:0.5rem 0.7rem;">closest previous state by <code>lot.qt.type.sequence</code></td>
|
|
</tr>
|
|
<tr style="border-bottom:1px solid #e0e0e0;">
|
|
<td style="vertical-align:top; padding:0.5rem 0.7rem;">no target state available</td>
|
|
<td style="vertical-align:top; padding:0.5rem 0.7rem;">current lot weight, only when no <code>weight basis</code> applies</td>
|
|
</tr>
|
|
</tbody>
|
|
</table>
|
|
|
|
#### Weight Basis Source
|
|
|
|
<table style="width:100%; border-collapse:collapse; margin:1rem 0 1.5rem 0; font-size:0.95rem;">
|
|
<thead>
|
|
<tr style="background:#eef3ff;">
|
|
<th style="text-align:left; padding:0.55rem 0.7rem; border-bottom:2px solid #4051b5;">Fee</th>
|
|
<th style="text-align:left; padding:0.55rem 0.7rem; border-bottom:2px solid #4051b5;"><code>weight basis</code></th>
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
<tr style="border-bottom:1px solid #e0e0e0;">
|
|
<td style="vertical-align:top; padding:0.5rem 0.7rem;">Purchase fee</td>
|
|
<td style="vertical-align:top; padding:0.5rem 0.7rem;"><code>fee.line.purchase.wb.qt_type</code></td>
|
|
</tr>
|
|
<tr style="border-bottom:1px solid #e0e0e0;">
|
|
<td style="vertical-align:top; padding:0.5rem 0.7rem;">Sale fee</td>
|
|
<td style="vertical-align:top; padding:0.5rem 0.7rem;"><code>fee.sale_line.sale.wb.qt_type</code></td>
|
|
</tr>
|
|
<tr style="border-bottom:1px solid #e0e0e0;">
|
|
<td style="vertical-align:top; padding:0.5rem 0.7rem;">Shipment fee</td>
|
|
<td style="vertical-align:top; padding:0.5rem 0.7rem;">purchase weight basis of the lot when present</td>
|
|
</tr>
|
|
<tr style="border-bottom:1px solid #e0e0e0;">
|
|
<td style="vertical-align:top; padding:0.5rem 0.7rem;">Shipment fee without purchase</td>
|
|
<td style="vertical-align:top; padding:0.5rem 0.7rem;">sale weight basis of the lot when present</td>
|
|
</tr>
|
|
</tbody>
|
|
</table>
|
|
|
|
#### Net / Gross
|
|
|
|
<ul style="margin:0.65rem 0 1rem 1.1rem; padding-left:1rem; list-style-type:disc;">
|
|
<li style="margin:0.38rem 0;">If <code>fee.weight_type = net</code>:
|
|
<ul style="margin:0.65rem 0 1rem 1.35rem; padding-left:1rem; list-style-type:circle;">
|
|
<li style="margin:0.38rem 0;">read <code>lot.qt.hist.quantity</code>.
|
|
</li>
|
|
</ul>
|
|
</li>
|
|
<li style="margin:0.38rem 0;">If <code>fee.weight_type = brut</code>:
|
|
<ul style="margin:0.65rem 0 1rem 1.35rem; padding-left:1rem; list-style-type:circle;">
|
|
<li style="margin:0.38rem 0;">read <code>lot.qt.hist.gross_quantity</code>.
|
|
</li>
|
|
</ul>
|
|
</li>
|
|
</ul>
|
|
|
|
#### Per Packing
|
|
|
|
<ul style="margin:0.65rem 0 1rem 1.1rem; padding-left:1rem; list-style-type:disc;">
|
|
<li style="margin:0.38rem 0;"><code>mode = ppack</code> is excluded from the weight rule.
|
|
</li>
|
|
<li style="margin:0.38rem 0;"><code>fee.quantity</code> represents a packing quantity.
|
|
</li>
|
|
<li style="margin:0.38rem 0;">This quantity may be decoupled from net or gross weight.
|
|
</li>
|
|
</ul>
|
|
|
|
#### Lump Sum
|
|
|
|
<ul style="margin:0.65rem 0 1rem 1.1rem; padding-left:1rem; list-style-type:disc;">
|
|
<li style="margin:0.38rem 0;"><code>mode = lumpsum</code> also follows the quantity rule.
|
|
</li>
|
|
<li style="margin:0.38rem 0;">The amount remains fixed.
|
|
</li>
|
|
<li style="margin:0.38rem 0;">The quantity gives a more accurate per-ton price.
|
|
</li>
|
|
</ul>
|
|
|
|
### 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
|
|
|
|
<ul style="margin:0.65rem 0 1rem 1.1rem; padding-left:1rem; list-style-type:disc;">
|
|
<li style="margin:0.38rem 0;">Formula: <code>amount = unit_price * quantity * (price / 100) * fin_int_delta / 360</code>.
|
|
</li>
|
|
<li style="margin:0.38rem 0;">Delta source: <code>Estimated date</code> line with <code>trigger = bldate</code>.
|
|
</li>
|
|
<li style="margin:0.38rem 0;">If no <code>bldate</code> line exists, do not calculate a <code>% rate</code> amount.
|
|
</li>
|
|
</ul>
|
|
|
|
## Developer Section
|
|
|
|
### Key Fields
|
|
|
|
<ul style="margin:0.65rem 0 1rem 1.1rem; padding-left:1rem; list-style-type:disc;">
|
|
<li style="margin:0.38rem 0;">Fee: <code>fee.fee</code>
|
|
</li>
|
|
<li style="margin:0.38rem 0;">Fee lots: <code>fee.lots</code>
|
|
</li>
|
|
<li style="margin:0.38rem 0;">Fee quantity: <code>fee.fee.quantity</code>
|
|
</li>
|
|
<li style="margin:0.38rem 0;">Mode: <code>fee.fee.mode</code>
|
|
</li>
|
|
<li style="margin:0.38rem 0;">Optional quantity state: <code>fee.fee.qt_state</code>
|
|
</li>
|
|
<li style="margin:0.38rem 0;">Net / gross: <code>fee.fee.weight_type</code>
|
|
</li>
|
|
<li style="margin:0.38rem 0;">Lot quantity state: <code>lot.qt.hist.quantity_type</code>
|
|
</li>
|
|
<li style="margin:0.38rem 0;">Net quantity: <code>lot.qt.hist.quantity</code>
|
|
</li>
|
|
<li style="margin:0.38rem 0;">Gross quantity: <code>lot.qt.hist.gross_quantity</code>
|
|
</li>
|
|
<li style="margin:0.38rem 0;">State order: <code>lot.qt.type.sequence</code>
|
|
</li>
|
|
<li style="margin:0.38rem 0;">Purchase / sale weight basis: <code>purchase.weight.basis.qt_type</code>
|
|
</li>
|
|
</ul>
|
|
|
|
### Calculation Functions
|
|
|
|
<ul style="margin:0.65rem 0 1rem 1.1rem; padding-left:1rem; list-style-type:disc;">
|
|
<li style="margin:0.38rem 0;"><code>Fee._get_effective_fee_lots()</code>:
|
|
<ul style="margin:0.65rem 0 1rem 1.35rem; padding-left:1rem; list-style-type:circle;">
|
|
<li style="margin:0.38rem 0;">selects physical lots, then virtual lots.
|
|
</li>
|
|
</ul>
|
|
</li>
|
|
<li style="margin:0.38rem 0;"><code>Fee._target_qt_type_for_lot()</code>:
|
|
<ul style="margin:0.65rem 0 1rem 1.35rem; padding-left:1rem; list-style-type:circle;">
|
|
<li style="margin:0.38rem 0;">chooses <code>fee.qt_state</code> or the <code>weight basis</code>;
|
|
</li>
|
|
<li style="margin:0.38rem 0;">caps the state at the <code>weight basis</code>.
|
|
</li>
|
|
</ul>
|
|
</li>
|
|
<li style="margin:0.38rem 0;"><code>Fee._select_lot_qt_type()</code>:
|
|
<ul style="margin:0.65rem 0 1rem 1.35rem; padding-left:1rem; list-style-type:circle;">
|
|
<li style="margin:0.38rem 0;">takes the exact state when it exists;
|
|
</li>
|
|
<li style="margin:0.38rem 0;">otherwise takes the closest previous state by <code>sequence</code>.
|
|
</li>
|
|
</ul>
|
|
</li>
|
|
<li style="margin:0.38rem 0;"><code>Fee._get_lot_fee_quantity()</code>:
|
|
<ul style="margin:0.65rem 0 1rem 1.35rem; padding-left:1rem; list-style-type:circle;">
|
|
<li style="margin:0.38rem 0;">reads net or gross depending on <code>weight_type</code>.
|
|
</li>
|
|
</ul>
|
|
</li>
|
|
<li style="margin:0.38rem 0;"><code>Fee.sync_quantity_from_lots()</code>:
|
|
<ul style="margin:0.65rem 0 1rem 1.35rem; padding-left:1rem; list-style-type:circle;">
|
|
<li style="margin:0.38rem 0;">resynchronizes <code>fee.quantity</code>.
|
|
</li>
|
|
</ul>
|
|
</li>
|
|
</ul>
|
|
|
|
### Python Guards
|
|
|
|
<ul style="margin:0.65rem 0 1rem 1.1rem; padding-left:1rem; list-style-type:disc;">
|
|
<li style="margin:0.38rem 0;">Central check:
|
|
<ul style="margin:0.65rem 0 1rem 1.35rem; padding-left:1rem; list-style-type:circle;">
|
|
<li style="margin:0.38rem 0;"><code>fee.fee.assert_quantity_consistency()</code>
|
|
</li>
|
|
<li style="margin:0.38rem 0;"><code>fee.fee.assert_quantities_consistency()</code>
|
|
</li>
|
|
</ul>
|
|
</li>
|
|
<li style="margin:0.38rem 0;">Trigger points:
|
|
<ul style="margin:0.65rem 0 1rem 1.35rem; padding-left:1rem; list-style-type:circle;">
|
|
<li style="margin:0.38rem 0;">fee creation;
|
|
</li>
|
|
<li style="margin:0.38rem 0;">fee modification;
|
|
</li>
|
|
<li style="margin:0.38rem 0;"><code>fee.lots</code> creation / modification / deletion;
|
|
</li>
|
|
<li style="margin:0.38rem 0;">weighing through linked fee resynchronization.
|
|
</li>
|
|
</ul>
|
|
</li>
|
|
<li style="margin:0.38rem 0;">Exception:
|
|
<ul style="margin:0.65rem 0 1rem 1.35rem; padding-left:1rem; list-style-type:circle;">
|
|
<li style="margin:0.38rem 0;"><code>mode = ppack</code> is not controlled as weight.
|
|
</li>
|
|
</ul>
|
|
</li>
|
|
</ul>
|
|
|
|
### Known Gap
|
|
|
|
<ul style="margin:0.65rem 0 1rem 1.1rem; padding-left:1rem; list-style-type:disc;">
|
|
<li style="margin:0.38rem 0;">Lot split / merge:
|
|
<ul style="margin:0.65rem 0 1rem 1.35rem; padding-left:1rem; list-style-type:circle;">
|
|
<li style="margin:0.38rem 0;">the workflow may clone or create physical lots;
|
|
</li>
|
|
<li style="margin:0.38rem 0;">rewiring <code>fee.lots</code> to the new lots is not guaranteed yet;
|
|
</li>
|
|
<li style="margin:0.38rem 0;">affected fees must therefore be checked with the SQL diagnostic after this workflow is used.
|
|
</li>
|
|
</ul>
|
|
</li>
|
|
</ul>
|
|
|
|
### SQL Diagnostic
|
|
|
|
<ul style="margin:0.65rem 0 1rem 1.1rem; padding-left:1rem; list-style-type:disc;">
|
|
<li style="margin:0.38rem 0;">Script:
|
|
<ul style="margin:0.65rem 0 1rem 1.35rem; padding-left:1rem; list-style-type:circle;">
|
|
<li style="margin:0.38rem 0;"><a href="sql/fee_quantity_consistency_checks.sql">sql/fee_quantity_consistency_checks.sql</a>
|
|
</li>
|
|
</ul>
|
|
</li>
|
|
<li style="margin:0.38rem 0;">Usage:
|
|
<ul style="margin:0.65rem 0 1rem 1.35rem; padding-left:1rem; list-style-type:circle;">
|
|
<li style="margin:0.38rem 0;">audit test databases;
|
|
</li>
|
|
<li style="margin:0.38rem 0;">audit historical data;
|
|
</li>
|
|
<li style="margin:0.38rem 0;">qualify data before correction.
|
|
</li>
|
|
</ul>
|
|
</li>
|
|
<li style="margin:0.38rem 0;">The script returns non-<code>ppack</code> fees where <code>fee.quantity</code> does not match the sum of effective lots according to the applicable quantity state.
|
|
</li>
|
|
</ul>
|