Files
tradon/modules/purchase_trade/docs/business/sql/README.md
2026-05-15 11:26:39 +02:00

126 lines
5.6 KiB
Markdown

<!-- Generated from docs_source/business by docs/tools/render_business_docs.py. -->
# SQL diagnostics for purchase_trade business rules
These scripts are read-only diagnostics for a PostgreSQL test database.
They exist to support the same business rules enforced by Python guards. The
expected workflow is:
1. write the consultant/developer rule in the thematic documentation;
2. enforce the invariant in the application code when feasible;
3. provide a read-only SQL diagnostic to audit existing data.
## quantity_consistency_checks.sql
Checks the two core lot quantity invariants documented in
`lots-and-quantities.md` and `lots-and-quantities.en.md`.
Zero `lot_qt` rows are ignored completely. They are treated as legitimate
memory of an open quantity consumed by a physical lot. This is required because
`lot_qt` represents usable open forecast and stops at zero, while the virtual
lot may become negative to compensate the difference between theoretical and
executed quantity. A non-zero `lot_qt` row without both `lot_p` and `lot_s` is
reported as anomalous.
When a physical lot overconsumes the selected forecast after a tolerance
confirmation, `lot_qt` stops at zero but the virtual lot absorbs the full
physical quantity. The SQL diagnostic therefore reports only open forecast
lower than the positive virtual lot. Open forecast greater than the virtual lot
is accepted as a consequence of this overconsumption rule.
Run it on a restored test database:
<pre style="background:#263238; color:#eef7ff; padding:1rem; border-radius:0.35rem; overflow:auto;"><code>\i modules/purchase_trade/docs/business/sql/quantity_consistency_checks.sql</code></pre>
The script returns rows only when it finds a potential issue.
Main columns:
<ul style="margin:0.65rem 0 1rem 1.1rem; padding-left:1rem; list-style-type:disc;">
<li style="margin:0.38rem 0;"><code>check_name</code>: invariant or diagnostic that failed.
</li>
<li style="margin:0.38rem 0;"><code>contract_model</code>: <code>purchase.purchase</code> or <code>sale.sale</code>.
</li>
<li style="margin:0.38rem 0;"><code>contract_id</code>: database id of the contract.
</li>
<li style="margin:0.38rem 0;"><code>contract_number</code>: purchase or sale contract number.
</li>
<li style="margin:0.38rem 0;"><code>line_id</code>: <code>purchase.line</code> or <code>sale.line</code> id depending on the check.
</li>
<li style="margin:0.38rem 0;"><code>virtual_lot_id</code>: virtual lot involved in the inconsistency.
</li>
<li style="margin:0.38rem 0;"><code>observed_value</code>: value found in the database.
</li>
<li style="margin:0.38rem 0;"><code>expected_value</code>: value required by the business rule.
</li>
<li style="margin:0.38rem 0;"><code>diff</code>: observed minus expected.
</li>
<li style="margin:0.38rem 0;"><code>detail</code>: human-readable explanation.
</li>
</ul>
Cross-category UoM rows are reported as manual-review diagnostics because the
Python code may pass explicit conversion factors that cannot be inferred safely
from SQL alone.
## fee_quantity_consistency_checks.sql
Checks the fee quantity invariant documented in `fees.md` and `fees.en.md`.
The script ignores `mode = 'ppack'` because Per packing follows a packing
quantity, not the lot net/gross weight.
For every other fee, it recomputes the expected quantity from the effective
`fee.lots`:
<ul style="margin:0.65rem 0 1rem 1.1rem; padding-left:1rem; list-style-type:disc;">
<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>
<li style="margin:0.38rem 0;"><code>fee.qt_state</code> when filled, capped by the contract <code>weight basis</code>;
</li>
<li style="margin:0.38rem 0;">otherwise the contract <code>weight basis</code>;
</li>
<li style="margin:0.38rem 0;">closest previous <code>lot.qt.hist</code> state by <code>lot.qt.type.sequence</code> when the exact state is missing;
</li>
<li style="margin:0.38rem 0;"><code>lot.qt.hist.quantity</code> for net fees and <code>lot.qt.hist.gross_quantity</code> for gross fees.
</li>
</ul>
The diagnostic is grouped by `fee_id`. Purchase and sale columns are displayed
only to help identify related contracts; they do not split the expected
quantity. This matters for purchase fees whose linked lots may also be matched
to several sale lines.
Run it on a restored test database:
<pre style="background:#263238; color:#eef7ff; padding:1rem; border-radius:0.35rem; overflow:auto;"><code>\i modules/purchase_trade/docs/business/sql/fee_quantity_consistency_checks.sql</code></pre>
The script returns rows only when it finds a potential issue.
Main columns:
<ul style="margin:0.65rem 0 1rem 1.1rem; padding-left:1rem; list-style-type:disc;">
<li style="margin:0.38rem 0;"><code>check_name</code>: failed invariant or manual-review diagnostic.
</li>
<li style="margin:0.38rem 0;"><code>fee_id</code>: <code>fee.fee</code> id.
</li>
<li style="margin:0.38rem 0;"><code>purchase_id</code> / <code>purchase_number</code>: related purchase contract when available.
</li>
<li style="margin:0.38rem 0;"><code>sale_id</code> / <code>sale_number</code>: related sale contract when available.
</li>
<li style="margin:0.38rem 0;"><code>observed_value</code>: current <code>fee.quantity</code>.
</li>
<li style="margin:0.38rem 0;"><code>expected_value</code>: recomputed quantity from effective lots.
</li>
<li style="margin:0.38rem 0;"><code>diff</code>: observed minus expected.
</li>
<li style="margin:0.38rem 0;"><code>detail</code>: human-readable explanation.
</li>
</ul>
Rows reported as `fee_quantity_uom_manual_review` need manual analysis because
SQL cannot safely infer cross-category conversion factors.