Repository navigation
fix(catalog): refuse stale product and packed-unit edits, and order daily-entry grade lines - #1256
Merged
Merged
Conversation
Product and egg-unit-conversion updates sent no base Version, so two admins editing the same price or eggs-per-unit silently overwrote each other. Both commands now carry the loaded Version (required, like UpdateCustomer), the handlers compare it and return a VersionMismatch 409, and the SPA sends it and rebinds the dialog to the winner's values on a conflict.
The version-conflict rebind took ProductsPage past the 500-line file limit; the Packed units table and inspector move out unchanged.
Every DailyEntryRepository read included Grades without an ORDER BY, so lines came back in physical row order, which differs between databases and reordered History's Graded column. One ordered Include now sorts them by the grade's SortOrder, then Name, then id, as EggGradeRepository lists grades.
The rebind treated every 409 except a duplicate name as a stale version, so the unique-index race and an in-flight idempotency key also discarded the typed form, showed the wrong message and dropped a key that had to be kept. Only VersionMismatch and the EF race's Concurrency conflict rebind now.
… ordering The two IgnoreQueryFilters write lookups ordered their lines through the shared WithGrades helper, so its EggGrades subqueries ran without the tenant filter and outside the bypass guard's member hash. Neither caller shows its lines to anyone, so both return to main's plain Include and main's allow-list rows. WithGrades now also matches the grade's AccountId to the line's.
…rade-order # Conflicts: # web/scripts/verify-sw.mjs
Owner
Author
|
Screenshots of a save conflict on the Products screen. The capture opens the edit dialog and types a change. Another session then saves the same row through the API at its current version, and the dialog's Save is clicked. The farm is Captured at
No page errors were recorded during the runs. |
…rade-order # Conflicts: # web/scripts/verify-sw.mjs # web/src/api/cluckwork.ts
mforce
added a commit
that referenced
this pull request
Oct 12, 2026
This was referenced Oct 12, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.








Two data-correctness fixes found during the #1215 split work. No issue tracks them, so this body records them.
1. Product and packed-unit edits overwrote each other
PUT /products/{id}andPUT /egg-unit-conversions/{id}sent no base version. When two admins edited the same product price or eggs-per-unit, the later save silently replaced the earlier one. Eggs-per-unit drives stock math.version, following theUpdateCustomerpattern. An omitted version is a 400 (*.Version.Required), never a silent 0.Product.VersionMismatch/EggUnitConversion.VersionMismatchas 409. The EFVersiontoken still returns 409 when two requests overlap. Both aggregates already bumpedVersion.Data conflict, an idempotency key still in flight) show their own error and keep the typed form and the key.2. Daily-entry grade lines came back in physical row order
DailyEntryRepositoryincludedGradeswith no ORDER BY. The History "Graded" column therefore changed order between databases. The three filtered reads (GetByIdAsync,GetReadOnlyAsync,ListAsync) now order lines by the grade'sSortOrder, thenName, then id. This is the orderEggGradeRepositorylists grades in. The grade lookup also matches the line'sAccountId.The two filter-free write lookups (
GetByIdForFlockScopedWriteAsync,FindByNaturalKeyForFlockScopedWriteAsync) keepmain's plainInclude. Their callers return an id, a Guid orId/Status/EggLotIds, never grade lines. With a plainInclude, they do not readEggGradeswithout the tenant filter, andBypassAllowList.csis unchanged frommain. No client code re-sorts grade lines; History renders the array as received.Evidence
Race tests (
CatalogTests):Mutations, each red then restored:
Product_StaleBaseVersion_Is409_AndChangesNothingfailsConversion_StaleBaseVersion_Is409_AndChangesNothingfailsProduct.Update'sVersion++Includein the filtered readsReads_ReturnGradeLinesInCatalogOrder_NotPhysicalOrderfailsWithGradesTenantBypassRealTreeTestsfails (unexcused bypass)Grade order test: the test rewrites two grade rows so they sit on disk in reverse catalog order, and asserts that order with
ORDER BY ctid. It then checks the order from bothGET /daily-entries/{id}and the list.SQL check: a temporary
ToQueryStringdump, not committed, confirms the following. Every grade subquery in the three filtered reads carriesAccountId. Neither write lookup referencesEggGrades.Callers outside CI (#394)
tools/simulation/k6/bundles.jsonly readsGET /products?includeInactive=true.tools/simulation/ui/specs/phone.spec.tsonly navigates to/products.The in-repo callers now send
version: the SPA, andCatalogTests,SalesProductTests,ProductAuditPayloadTestsandCurrencyLockRaceTests.Other changes
ProductsPage.tsxgrew past the 500-line file limit, so the Packed units table and inspector moved unchanged intoPackedUnitsPanel.tsx.Review
Two local review rounds. Round 1 found two problems, both fixed in new commits:
be5d1e8c).5c754b02).Round 2 was clean.
Tests run locally
dotnet build Cluckwork.slnx: clean.CatalogTests,DailyEntryGradeTests,SalesProductTests,ProductAuditPayloadTestsandCurrencyLockRaceTests(61 passed). Earlier runs also covered SubmitDailyEntry, DailyEntryAdjust, FlockScope, TenantIsolation and Mcp.ProductsPage.test.tsx,src/apiand the i18n suites (551 passed),npm run build,npm run verify:sw. Theversionfields live inweb/src/api/cluckwork/catalog.tssince refactor(web): split the API wrappers out of cluckwork.ts by area #1264 split the API client.Docs-impact: none — two error messages (en/es/tl) for a save conflict; no new concept, control or label, so the glossary and Help page are unchanged.
Screenshots follow in a comment.