Skip to content

ARCH-001 Phase 24: shrink Admin CurrencyController/LanguageController - #825

Merged
KrzysztofPajak merged 1 commit into
developfrom
arch001/phase24-currency-language-cleanup
Sep 11, 2026
Merged

ARCH-001 Phase 24: shrink Admin CurrencyController/LanguageController#825
KrzysztofPajak merged 1 commit into
developfrom
arch001/phase24-currency-language-cleanup

Conversation

@KrzysztofPajak

@KrzysztofPajak KrzysztofPajak commented Sep 10, 2026

Copy link
Copy Markdown
Member

Type: refactor

Issue

ARCH-001 (see repo memory) previously surveyed Currency/Language as a "config-shaped, lower-priority" candidate distinct from every other completed phase: Store's CurrencyController/LanguageController implement a genuinely different feature (per-store currency/language assignment + default picker) than Admin's full CRUD editor, so there is no real cross-host duplication to remove with the usual IAdminDataScope<TEntity> pattern.

Fresh read of both Admin controllers found a smaller, real opportunity instead: business-rule validation and setting+cache-clear orchestration was living directly in the controllers even though both already inject a shared ICurrencyViewModelService/ILanguageViewModelService (Grand.Web.AdminShared) that was the natural home for it.

Solution

Moved into the already-injected view-model services, no new abstractions:

  • MarkAsPrimaryExchangeRateCurrency/MarkAsPrimaryStoreCurrency: setting-save + cache-clear now live in CurrencyViewModelService. Removes CurrencyController's private ClearCache() helper and its now-unused ICacheBase dependency.
  • Edit(POST)'s "last published currency/language" guard and Delete's "can't delete primary / last published" guards are extracted as separate service methods per entity (ValidateCurrencyUnpublish/ValidateCurrencyDelete, ValidateLanguageUnpublish/ValidateLanguageDelete) — deliberately not merged into one shared method, since Edit's guard is also conditioned on !model.Published while Delete's is not; merging would have re-derived a similar-looking but wrong condition (a mistake this initiative has hit before on other phases).

No behavior change for callers — same messages, same redirects.

One disclosed side effect: Delete's primary/exchange-currency rejection no longer round-trips through a thrown GrandException, so it no longer triggers BaseController.Error(Exception)'s LogException call for this expected, validation-only outcome. This aligns with .ai/standards/csharp-style.md's "prefer result objects over exceptions for expected business failures" and removes ERROR-level log noise for a routine user action, but is a real (intentional) behavior delta worth a second pair of eyes.

Store's CurrencyController/LanguageController are untouched — out of scope, as explained above.

Breaking changes

None. Public HTTP surface, messages, and redirects are unchanged. ICurrencyViewModelService/ILanguageViewModelService gained new methods (additive); no existing interface members changed signature.

Testing

  1. dotnet build GrandNode.sln — 0 errors, 0 new warnings.
  2. New unit tests added (no prior coverage existed for either controller/service): src/Tests/Grand.Web.Admin.Tests/Services/CurrencyViewModelServiceTests.cs, LanguageViewModelServiceTests.cs — happy path + every extracted guard condition (primary currency, exchange-rate currency, last-published, still-published short-circuit).
  3. dotnet test src/Tests/Grand.Web.Admin.Tests/Grand.Web.Admin.Tests.csproj — 1347/1347 green (includes the 14 new tests).
  4. Live smoke test run against a real Kestrel instance on the shared dev DB, via curl-based crafted antiforgery-tokened requests (browser automation was unavailable this session, per the established fallback from TaxCategory/Shipping phases). All 6 extracted methods verified, both success and failure branches:
    • MarkAsPrimaryExchangeRateCurrency/MarkAsPrimaryStoreCurrency: set Euro as primary (both flags), grid reflected it immediately (setting + cache-clear both took effect), reverted to US Dollar.
    • ValidateCurrencyDelete: blocked deleting the primary store currency ("The primary store currency can't be deleted.") and the primary exchange-rate currency ("The primary exchange rate currency can't be deleted.") — exact resource strings, entities confirmed untouched. Success path also verified: created and cleanly deleted a throwaway currency.
    • ValidateCurrencyUnpublish: unpublishing a non-last currency succeeded; unpublishing the resulting last-published currency was correctly blocked ("At least one published currency is required."), with the entity's Published state confirmed unchanged (rejected, not partially applied).
    • ValidateLanguageDelete/ValidateLanguageUnpublish: this dev DB has exactly one language (English) — verified the most important edge case directly, both Delete and Edit(POST) unpublish blocked on the sole published language, same resource string as Currency's. Success path verified via a throwaway second language (create → unpublish English while throwaway stays published → succeeds → delete throwaway → succeeds).
    • All test data reverted to original values by the end (both currency primaries, Euro/English published state, English's flag image). One harmless, disclosed drift left in place: Euro's DisplayLocale changed from "" to null after a test round-trip — a pre-existing, unrelated Mongo/mapping quirk (not touched by this change), functionally identical in the UI (both render as "Select locale" unselected).
    • No cross-tenant/access-control surface exists in this change (Admin-only, no Store/Vendor consolidation), so this live test exercised business-rule correctness and cache/setting round-tripping rather than tenant isolation.

🤖 Generated with Claude Code

Not a cross-host consolidation (Store's Currency/Language controllers
implement a different feature - store assignment/default picker, not
CRUD - so there is no real Admin/Store duplication to remove here).

Moves business-rule validation and setting+cache-clear orchestration
out of the two controllers and into the already-injected
ICurrencyViewModelService/ILanguageViewModelService (already shared
via Grand.Web.AdminShared):

- MarkAsPrimaryExchangeRateCurrency/MarkAsPrimaryStoreCurrency: setting
  save + cache clear now live in the service; removes the controller's
  private ClearCache() helper and its now-unused ICacheBase dependency.
- Edit(POST)'s 'last published currency/language' guard and Delete's
  'can't delete primary/last published' guards are extracted as
  separate service methods (ValidateCurrencyUnpublish/ValidateCurrencyDelete,
  ValidateLanguageUnpublish/ValidateLanguageDelete) matching each
  original condition 1:1 - not merged into one shared method, since
  Edit's guard also checks !model.Published while Delete's doesn't.

No behavior change for callers: same messages, same redirects. One
disclosed side effect: Delete's primary/exchange-currency rejection no
longer round-trips through a thrown GrandException, so it no longer
triggers BaseController.Error(Exception)'s LogException call for this
expected, validation-only outcome (aligns with .ai/standards/csharp-style.md's
'prefer result objects over exceptions for expected business failures').

New unit tests for the four extracted service methods in
Grand.Web.Admin.Tests/Services (CurrencyViewModelServiceTests,
LanguageViewModelServiceTests) - no test coverage existed for either
controller/service before this change.

Verified: dotnet build GrandNode.sln (0 errors); Grand.Web.Admin.Tests
1347/1347 green.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DjfBG3opo33qNHQYVbgH9Q
Copilot AI lite review requested due to automatic review settings September 10, 2026 13:55

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

@KrzysztofPajak
KrzysztofPajak merged commit ecb7f73 into develop Sep 11, 2026
6 checks passed
@KrzysztofPajak
KrzysztofPajak deleted the arch001/phase24-currency-language-cleanup branch September 11, 2026 00:59
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.

2 participants