firms and collections #1

Merged
cheapnud merged 9 commits from d/fi1-firms into master 2026-08-09 16:40:04 +00:00
Owner

Adds firm legal entities with a default-firm invariant, a collection registry that routes orders to firms, and real firm identity on invoice, credit note and purchase order documents. Includes a firms admin page and a draft-editable firm select on orders.

Adds firm legal entities with a default-firm invariant, a collection registry that routes orders to firms, and real firm identity on invoice, credit note and purchase order documents. Includes a firms admin page and a draft-editable firm select on orders.
cheapnud merged commit 73eab82d80 into master 2026-08-09 16:40:04 +00:00

🤖 Claude AI Code Review

1. Summary

Adds firm legal entities with a default-firm invariant, collection→firm routing, and firm identity on invoice/credit-note/PO documents, plus an admin page and order firm-select. Well-structured with thorough test coverage.

2. Code Quality

Excellent test coverage across services, PDF/XML export, and components, with clear intent comments explaining routing and fallback rules.

3. Issues

🟡 FirmService.cs — default-firm invariant broken on delete. DeleteFirmAsync allows deleting a non-default firm freely, but nothing promotes a remaining firm to default when the default firm is deleted. The guard only blocks deleting the default while others exist, and permits deleting the default when it's the sole firm. That's fine — but if you delete all non-default firms then the default, you end with zero firms (acceptable). The real gap: there's no path that ever leaves multiple firms with none defaulted, so this is actually safe. No action needed — but worth confirming SetDefaultAsync/add flows are the only default mutators (they are).

🟡 FirmService.SetDefaultAsync / AddFirmAsync — TOCTOU on default-firm invariant under concurrency. The "exactly one default" and unique-code checks are read-then-write without a transaction or unique constraint on IsDefault. Two concurrent AddFirmAsync calls on an empty table could both set IsDefault = true. This matches the existing single-user "Mode B" IOUs documented in TODO.md, so it's consistent with the codebase's stated posture — flagging only for awareness.

🟢 CollectionsDialog.razor — rename UX is confusing. RenameAsync reuses the shared _newName add field and no-ops with a hint if blank, per the inline comment. Not a bug, but the coupling between the Add form and per-row rename will surprise users. (Skip if intentional.)

4. Verdict

APPROVE

No blocking bugs, resource leaks, or breaking changes. The concurrency gaps are pre-existing, documented tradeoffs consistent with the single-user model. Migration, model, service, and export changes are internally consistent and well-tested.


📊 Tokens: 72594 input + 764 output | 💰 Cost: ~$0.3821
Generated by Claude Opus 4.8 via Gitea Actions

## 🤖 Claude AI Code Review ## 1. Summary Adds firm legal entities with a default-firm invariant, collection→firm routing, and firm identity on invoice/credit-note/PO documents, plus an admin page and order firm-select. Well-structured with thorough test coverage. ## 2. Code Quality ⭐⭐⭐⭐☆ Excellent test coverage across services, PDF/XML export, and components, with clear intent comments explaining routing and fallback rules. ## 3. Issues 🟡 **FirmService.cs — default-firm invariant broken on delete.** `DeleteFirmAsync` allows deleting a non-default firm freely, but nothing promotes a remaining firm to default when the *default* firm is deleted. The guard only blocks deleting the default while others exist, and permits deleting the default when it's the sole firm. That's fine — but if you delete all non-default firms then the default, you end with zero firms (acceptable). The real gap: there's no path that ever leaves multiple firms with none defaulted, so this is actually safe. No action needed — but worth confirming `SetDefaultAsync`/add flows are the only default mutators (they are). 🟡 **FirmService.SetDefaultAsync / AddFirmAsync — TOCTOU on default-firm invariant under concurrency.** The "exactly one default" and unique-code checks are read-then-write without a transaction or unique constraint on `IsDefault`. Two concurrent `AddFirmAsync` calls on an empty table could both set `IsDefault = true`. This matches the existing single-user "Mode B" IOUs documented in TODO.md, so it's consistent with the codebase's stated posture — flagging only for awareness. 🟢 **CollectionsDialog.razor — rename UX is confusing.** `RenameAsync` reuses the shared `_newName` add field and no-ops with a hint if blank, per the inline comment. Not a bug, but the coupling between the Add form and per-row rename will surprise users. (Skip if intentional.) ## 4. Verdict **APPROVE** No blocking bugs, resource leaks, or breaking changes. The concurrency gaps are pre-existing, documented tradeoffs consistent with the single-user model. Migration, model, service, and export changes are internally consistent and well-tested. --- 📊 **Tokens**: 72594 input + 764 output | 💰 **Cost**: ~$0.3821 *Generated by Claude Opus 4.8 via Gitea Actions*
Sign in to join this conversation.
No reviewers
No labels
No milestone
No project
No assignees
2 participants
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
cheapnud/CheapFurniturePlanner!1
No description provided.