Resolves a conflict with the CRM merge (#555) that landed on beta
between when this branch was cut and now.
Two conflict regions in backend/src/routes/adminCustomers.js:
1. **Require block** — both branches added new requires after
customerAccountsService. Kept both: this branch's
emailNormalization import AND beta's customerHoursService +
invoiceService imports (the CRM merge added the hours-billing +
invoice-creation paths to this router).
2. **Edit-customer validators** — both branches changed the same set
of body() validators in the PUT /:id handler. This branch added
the IDENTITY_PRESERVING_NORMALIZE_EMAIL options arg to
normalizeEmail; beta changed every body() to optional({ nullable:
true }) so passive-customer records that store nulls for missing
profile fields don't reject on save. Kept both: the nullable
pattern from beta + the email-normalization options from this
branch. Preserved beta's explanatory comment about the nullable
choice.
Also patched one NEW normalizeEmail site the CRM merge introduced:
- backend/src/routes/adminCustomers.js:231 — POST /admin/customers
now exists (CRM-era customer-create endpoint). Same options arg
applied.
backend/src/routes/adminBusinessProfile.js has an isEmail() WITHOUT
normalizeEmail() on the issuer email — intentional (no normalization
means no risk of the Gmail dot-strip bug for that field), no change
needed.
All 18 normalizeEmail sites now pass IDENTITY_PRESERVING_NORMALIZE_EMAIL.
7/7 regression tests still pass. Lint clean on the merged file.