Repository navigation
feat(chemicals-web): PR E3 — inventory, history, exports (W1, W2, W3, W6) - #1437
Merged
Merged
Conversation
…iter Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0122jmSDSB5HSHQK36Bnt5Wy
…tries and alert log; xlsx writer Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0122jmSDSB5HSHQK36Bnt5Wy
…ys, xml-invalid chars, removed placements, remaining actions Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0122jmSDSB5HSHQK36Bnt5Wy
…days, drops XML-invalid chars, skips removed placements Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0122jmSDSB5HSHQK36Bnt5Wy
…emporarily restore the old implementation to prove red The two tests failed on arrange (removal before registration, stock not enabled), not on behaviour. The next commit restores the fix. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0122jmSDSB5HSHQK36Bnt5Wy
…ays, XML-safe xlsx text, removed placements) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0122jmSDSB5HSHQK36Bnt5Wy
…ent of its arrange Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0122jmSDSB5HSHQK36Bnt5Wy
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0122jmSDSB5HSHQK36Bnt5Wy
…st PDF, Excel, formatter, licence) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0122jmSDSB5HSHQK36Bnt5Wy
…y/removed/history Excel exports Removed history details now show the folded write-off amount; QuestPDF Community licence set once at start-up. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0122jmSDSB5HSHQK36Bnt5Wy
…ation tests run the licence start-up Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0122jmSDSB5HSHQK36Bnt5Wy
…er, page count, real history assertions Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0122jmSDSB5HSHQK36Bnt5Wy
… filter in the PDF, SDS address on paper Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0122jmSDSB5HSHQK36Bnt5Wy
…lookup, one xlsx tail, one PDF column set Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0122jmSDSB5HSHQK36Bnt5Wy
…e-only period and first-user gate Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0122jmSDSB5HSHQK36Bnt5Wy
…ller) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0122jmSDSB5HSHQK36Bnt5Wy
…hape, checks View, hides unexpected messages Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0122jmSDSB5HSHQK36Bnt5Wy
…View, hide unexpected messages Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0122jmSDSB5HSHQK36Bnt5Wy
…tests Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0122jmSDSB5HSHQK36Bnt5Wy
…ng menu child, no-redirect root and deferred URL write (red) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0122jmSDSB5HSHQK36Bnt5Wy
…rts and register dialog Beholdning is the Kemi root (no redirect, M-3) with its own menu child; the container writes ?propertyId= only once no navigation is in flight (R-2); every export button toasts its own failure and shows a busy state (m-2). The container specs read the Router.currentNavigation signal instead of the deprecated getCurrentNavigation() (mock member rename only). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0122jmSDSB5HSHQK36Bnt5Wy
…Model needs its value accessor) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0122jmSDSB5HSHQK36Bnt5Wy
…ing, /chemicals/inventory with root redirect, menu renumbering (red) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0122jmSDSB5HSHQK36Bnt5Wy
… placement wording, /chemicals/inventory - I-1 loading and failed states; nothing exportable before the inventory loads - I-2 a new search drops the pick; stale search answers are ignored - I-3 placement texts re-translated as registered chemicals in 24 locales - I-4 W1 at /chemicals/inventory; the Kemi root redirects there with its query - m-1 property left to the container; m-2 chip off drops ?filter (R-2 safe) - m-3 newest inventory answer wins; m-4..m-9 validation, barcode package, a11y, ids, row labels, export reasons; m-13 menu sync makes room on ties Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0122jmSDSB5HSHQK36Bnt5Wy
… a technical error message Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0122jmSDSB5HSHQK36Bnt5Wy
finalize frees the export button; summary, active locations and columns are set with the inventory instead of per change detection; shared spec fixtures; TODO(Task 13) on openDetails; dead CSS classes dropped. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0122jmSDSB5HSHQK36Bnt5Wy
Union-resolve localization.json (chemicals + archive folder keys). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0122jmSDSB5HSHQK36Bnt5Wy
…-06/E-20, N-m1, N-m4 (red) Specs only: helper placementStockHistory (server balanceAfter, origin labels) and placementsOfChemical; the five new components; page chaining and row actions; the drawer and row-menu templates; i18n key list and meaning guards. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0122jmSDSB5HSHQK36Bnt5Wy
… remove dialogs - Side panel (overview, placements, stock history with the server balance and move / removal write-off labels, E-06) chains move/stock/remove into their dialogs; W1 row menu gains move, note, stock (stock on) and remove. - Stock dialog refuses an adjustment to the current balance (E-20). - Register dialog drops a stale pick when a search answers (N-m1). - Export toast never shows a raw HttpErrorResponse/Error message (N-m4). - 45 new keys in 26 locales, MT from disambiguated sources, hand-fixed. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0122jmSDSB5HSHQK36Bnt5Wy
I-1 move meaning guard and M11 translation guards; M2 busy cancel, M3 amount limits, M4 move empty state, M5 discard confirm, M6/M7 drawer close and name, M8 tab pattern, M9 radio names; M10 boundaries, drawer tabs, N-m4 Error branch; R1/R2 shared SDS and save helpers. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0122jmSDSB5HSHQK36Bnt5Wy
Every reload bumps a generation; an order save that answers after it no longer takes over its older location snapshot, so an archive (or an edit) done meanwhile stays visible (Copilot 4306102463). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0122jmSDSB5HSHQK36Bnt5Wy
…once Red test for Copilot review 4306130349: while a new search is pending the old results must not be pickable (Save could register one). Also fixes the W3 stale-order test, which miscounted the archived locations (there are two); its red assertion on the visible rows is unchanged. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0122jmSDSB5HSHQK36Bnt5Wy
While a new search was pending, an old result could still be picked and saved. The results, total, "no match" state and searched barcode now go at once; the answer fills them again (Copilot 4306130349). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0122jmSDSB5HSHQK36Bnt5Wy
Member
Author
|
Copilot round 2, findings that appear only in the review bodies:
|
This was referenced Oct 20, 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.

Summary
Plan E, PR E3 of the Kemi (chemicals) web admin: the inventory, location, history and export screens on top of E1 (W4 Rettigheder, #1430) and E2 (W5 Indstillinger, #1434). Tasks 9–16 of the plan (flutter-chemistry spec §5.1, §6, §11).
Screens (Angular,
modules/chemicals/)/chemicals/inventory): the property's open placements in a grid with hazard pictograms, deadlines and (stock on) the balance. "Kræver handling" chip, banner and red/amber rows follow the same rules as the server's alert job. "Registrér placering" searches the BMD register. The row menu opens details, move, note, stock, remove and the safety data sheet. The page has explicit loading and load-failed states, so it never looks empty while loading./chemicals/locations): create, edit with photo, reorder by drag or up/down buttons (the order is saved and the app uses it), archive after a confirm (refused while the location has open placements), "show archived"./chemicals/history): the immutable log derived from the version tables, the stock ledger andChemicalAlertLog. It is paged on the server and filtered by period (Europe/Copenhagen days), user, action and location, with an Excel export of the whole filtered period./chemicals?propertyId=…&filter=requires-actionredirects to W1 with the property and the chip set.Server (
BackendConfiguration.Pn)ChemicalReportsControlleradds five routes underapi/backend-configuration-pn/chemicals/properties/{propertyId}:POST historyPOST history/exportGET exports/authority-listPOST exports/inventoryGET exports/removed-placementsEach route requires the plugin policy and
[FirstUserOnly](TODO chemistry-GA), and checksIChemicalPermissionServiceon the property.Success=falsewith ": ".ChemicalHistoryServiceandChemicalExportService: every query is keyed on the property. One ledger fold (BalanceAfterByEntry, Id order) is shared with the inventory, and the removal write-off is folded into the Removed event.Excel cells are always inline strings (never formulas), and control characters are stripped.
ChemicalBase:BaseUrlbuilds the SDS links; the file name is URL-escaped.QuestPDF licence (E-08):
QuestPDF.Settings.License = LicenseType.Communityis set once inAddChemicalReportingand pinned by a test. Microting is within the Community limits (owner, 2026-10-14), so no commercial licence is needed.Menu sync: all five Kemi children
PluginMenuSyncService(from SeedDatabase, keyed on the menu template E2EId) adds the missing children on an existing tenant's next startup: Beholdning 0, Lokationer 1, Rettigheder 2, Indstillinger 3, Historik 4. It makes room when an admin has renumbered the group, never re-adds what an admin deleted in the menu editor, and never rewrites a kept row. No plugin disable/enable is needed.Date fix (T16)
The host's
DateInterceptorturns every ISO date-time string in a JSON answer into aDate, and W1 crashed on a deadline (value.substring is not a function).restoreIsoDatesat the Kemi service boundary turns those back into ISO UTC text (…000Z) on every JSON method. Offset-less server times are read as UTC, so the day is kept. This is pinned by a round trip through the realDateInterceptor.Why W6 Details is formatted twice (E-13)
W6 Details is formatted twice: in
ChemicalHistoryDetailsFormatter(C#, for the Excel the server builds) and informatHistoryDetails(TS, for the page). The page copy lets W6 re-translate live without a round trip, and the paged API stays structured data. Parity is pinned by the same input→output vectors, copied into each suite (C# and TS), and byChemicalHistoryWordsParityTests(the same words in all 26 locales).Tests
restoreIsoDates, page load and failure states, dialogs, download toasts.Owner items (outside this PR)
HttpErrorInterceptorre-sends non-idempotent requests. Its default branch re-runs any request up to 5 times, 15 s apart, including POST and PUT, and then ends with no answer.Success=false, so only infrastructure 5xx trigger it..flex-column { flex: 1 0 0 }(hoststyles.scss). It collapses any scroll container with that class inside a flex column. Kemi's register dialog is fixed locally; other plugin screens may be exposed in the same way.DateInterceptor.Date.restoreIsoDates, compliance report, adhoc history).HttpContextTokenopt-out and an anchored regex.ArchiveConcurrencyTests("not blocked on the folder lock within 30 s"), also red on stable's own push run 38906317104;UpdatePlanning_AreaRuleMonths24_ReturnsSuccess(MariaDB 1205 lock wait)./chemicalsas Beholdning's link. Check tenant 855 or any test tenant, and fix that row by hand.🤖 Generated with Claude Code
https://claude.ai/code/session_0122jmSDSB5HSHQK36Bnt5Wy