Edge cases and gotchas

State Usergroup Rules

Edge cases and gotchas

The module is small, so most surprises come from misunderstandings about WHEN the rule fires and WHAT it does (and does not) clean up.

1. The address state must match the rule code exactly

The rule matches on exact string equality: state_code = $1. An address saved with state = "ca" or state = "California" will not match a rule keyed on CA. The admin form always stores the seeded state code; the API stores whatever you send — passing {"state_code":"ca"} through POST /api/v1/admin/state-usergroup-rules saves a rule that only matches addresses whose state is exactly ca.

Guard: make sure your storefront address form saves state codes, and send the same codes to the API.

2. The rule is additive — old groups stick

Customer was in CA (auto-assigned to customers-ca), moves to NY. The rule fires for NY → adds customers-ny. It does NOT remove customers-ca. The customer is now in both groups, which often means the wrong price tier applies (depending on which group's tier wins).

If you want exclusive groups, build an explicit unassign step at the address-update site BEFORE calling ApplyStateUsergroups.

3. Multiple rules per state stack

Two rules: CA → customers-ca and CA → wholesale-ca, both auto_assign=true. A customer with a CA address gets BOTH groups. By design. Confusing if the two groups have conflicting price tiers.

Audit your rules periodically — SELECT state_code, COUNT(*) FROM state_usergroup_rules GROUP BY state_code HAVING COUNT(*) > 1 flags multi-rule states.

4. Only address-book writes fire the rule

The rule fires only where ApplyStateUsergroups is called: CreateAddress and UpdateAddress in the users service (account address book and admin customer page). Addresses entered only on an order at checkout, or written by custom code that bypasses the service, don't trigger it.

Search the backend for ApplyStateUsergroups and confirm every address-write path you rely on calls it.

5. auto_assign=false rules are silent no-ops

A rule stored with auto_assign=false (API only) is visible in the list but never fires. Operators expecting "I created the rule, it should work" can stare at the customer profile wondering why the group didn't land.

Tip: when in doubt, open /admin/state-usergroup-rules first and confirm Auto-assign says Yes before debugging downstream.

6. Module disabled → silent skip, no error

Disable the state-usergroup-rules module under /admin/modules. Customers continue to save addresses. Nothing throws. But no group ever gets assigned. The only signal is a debug log line state-usergroup-rules module disabled for tenant, skipping auto-assign which is easy to miss.

Document module ON/OFF state in your runbook so ops doesn't accidentally disable a critical pricing-tier dependency.

7. Rules pointing at a deleted usergroup

Someone deletes a usergroup without also dropping the rules that reference it. Next time the rule fires, the insert into usergroup_links can fail. The address write itself commits regardless (the assignment runs after the address is saved).

The customer ends up addressed but ungrouped. Hard to spot without log tailing. Repair: delete the orphan rule.

8. Bulk imports bypass the rule

Importing customers by inserting into the user and address tables directly skips the service layer entirely — ApplyStateUsergroups never runs. The imported customers have correct addresses but no auto-assigned groups.

If you bulk-load, either go through the API (slower but correct) or follow up with a back-fill query:

INSERT INTO usergroup_links (user_id, usergroup_id, status)
SELECT a.user_id, r.usergroup_id, 'A'
FROM user_addresses a
JOIN state_usergroup_rules r ON r.state_code = a.state AND r.auto_assign = true
ON CONFLICT (user_id, usergroup_id) DO NOTHING;

9. Race when admin and storefront write addresses simultaneously

Two concurrent writes (storefront customer + admin operator) on the same customer with different states can leave the customer in both groups — both rules fire, last-writer-wins on the address row but BOTH groups stick. Rare in practice; the additive behaviour means data isn't corrupted, just over-assigned.

10. The CS-Cart importer doesn't fire the rule

Migration tooling that bulk-imports users and addresses does so via direct INSERT for speed. Customers imported this way are NOT auto-assigned. Plan a back-fill SQL step as part of the migration runbook.