Skip to content

Read the price a position actually cost off its receipts, and say when nobody wrote one down - #115

Merged
dimmus merged 1 commit into
mainfrom
claude/purchasing-price-history
Sep 24, 2026
Merged

dimmus merged 1 commit into
mainfrom
claude/purchasing-price-history

Conversation

@dimmus

@dimmus dimmus commented Sep 24, 2026

Copy link
Copy Markdown
Contributor

Closes #34 — its last open row, price history per position falls out of receiving and should not be a separate table of typed-in numbers. The other four rows were built by #96 and #112 (see the status table on the issue).

contexts/procurement/prices.py is a read over purchase_receipts.unit_price_paid: one query joining receipts to their lines over a window (365 days by default, ?days=), and a pure fold picking each position's earliest and latest priced receipt. Panel «Цены по ключевым позициям» on the purchasing screen.

What is deliberately not there, per CLAUDE.md §1

  • A receipt with no recorded price is a delivery, not a price of zero. It is counted (unpriced_receipts, footer «приёмок без записанной цены: N») and moves no figure.
  • A position with one priced receipt has earliest = change = null: a price, not a stable price. The kit's «БЫЛО» line draws as a dash there, not 0%.
  • The kit's «Средневзвешенно −9% за год» slab: a composite whose weights nobody chose.

Money gate. GET /purchasing/prices sits behind VIEW_FINANCIALS like /costs, refused whole. PurchasingPage never requests it without the permission; the screen test asserts that in the network log.

Verified

  • Backend: 7 unit + 3 API tests for this, plus the existing purchasing API tests, pass. Six gates each exit=0. Three mutations to the fold, each run and reverted: unpriced receipts priced at zero (3 tests fail), fold trusting input order (1), single point reported as a movement (1).
  • Frontend, from the lockfile: tsc --build --force, lint, test (33 files, 319 passed) and build each exit=0; the API client regenerated from the new schema and typechecked.
  • The full backend suite was not re-run locally for this branch (28 min); CI's backend job runs it.

🤖 Generated with Claude Code

…n nobody wrote one down (#34)

Closes #34. Its last open row: price history per position falls out of
receiving and must not be a table of typed-in numbers. It falls out of
`purchase_receipts.unit_price_paid`, and `contexts/procurement/prices.py` is
the read: one query joining receipts to their lines over a window (a year by
default) and a pure fold picking each position's earliest and latest priced
receipt.

Two denominator rules, both tested and both mutation-proved. A receipt with
no recorded price is a delivery, not a price of zero: it is counted so the
panel can say «приёмок без записанной цены: N», and it moves no figure. A
position with one priced receipt has no «было» and no change - null, not 0%,
which would claim a year nobody measured. The kit's weighted-average slab is
not served; its weights were never chosen.

Money, so `GET /purchasing/prices` is behind VIEW_FINANCIALS like `/costs`,
and the page never requests it without the permission - the screen test
reads the network log, not only what rendered.

Verified: 7 unit + 3 API tests plus the existing purchasing API tests pass;
three mutations to the fold each fail it (unpriced receipts at zero: 3,
trusting input order: 1, single point as a movement: 1). Backend gates all
exit=0. Frontend from the lockfile: `tsc --build --force`, lint, test (33
files, 319 passed), build, and a regenerated API client, each exit=0.

Co-Authored-By: Claude Fable 5.1 <[email protected]>
@dimmus
dimmus merged commit c9bc337 into main Sep 24, 2026
7 checks passed
@dimmus
dimmus deleted the claude/purchasing-price-history branch September 24, 2026 15:33
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

purchasing.html — no Supplier, no PurchaseOrder, nothing

1 participant