feat(crm): country dropdown + name guard for customer create/edit

Replace the free-text 2-char country code field on the inline customer
create form and the customer detail page with a dropdown that shows
localized country names (Intl.DisplayNames, no hardcoded map) while
still storing the ISO 3166-1 alpha-2 code. The create form now seeds the
default country from the business profile instead of leaving it blank or
guessing CH/FL. The free-text countryName override is kept for the rare
case where an operator wants a custom display string.

Standardize Liechtenstein on the ISO code LI instead of the colloquial
plate code FL so it matches the PDF renderer's locale-aware lookup and
the new dropdown. Migration 110 normalizes existing FL rows to LI on
customer_accounts and business_profile (idempotent, case-insensitive).

Require at least one human-readable identifier (company name or a
contact name) at create time so the form can't produce a nameless row
that's impossible to recognise in lists later. Enforced on both the
frontend (isValid + toast) and the backend POST /admin/customers
validator so the API can't be bypassed.

i18n: en + de updated; other locales fall back to inline English
defaults and should get a native review before release.
This commit is contained in:
Luca
2026-06-02 01:28:00 +02:00
parent 840df52581
commit db2c482ae9
9 changed files with 202 additions and 26 deletions
+11
View File
@@ -245,6 +245,17 @@ router.post('/', [
body('prefill.country_code').optional({ nullable: true }).isString().isLength({ max: 2 }),
body('prefill.country_name').optional({ nullable: true }).isString().isLength({ max: 120 }),
body('prefill.preferred_language').optional({ nullable: true }).isString().isLength({ min: 2, max: 8 }),
// At least one human-readable identifier so the record isn't a
// nameless row that's impossible to recognise in lists later.
body('prefill').custom((prefill) => {
const p = prefill || {};
const hasName = ['company_name', 'display_name', 'first_name', 'last_name']
.some((k) => typeof p[k] === 'string' && p[k].trim());
if (!hasName) {
throw new Error('At least a company name or a contact name is required');
}
return true;
}),
], handleAsync(async (req, res) => {
validateRequest(req);
const { id } = await customerAccountsService.createDirect({