feat(admin): single Sales role, welcome-email password setup, Director=sales
All checks were successful
Build & Push Docker Images / lint (push) Successful in 3m5s
Build & Push Docker Images / build-and-push (push) Successful in 9m24s

- Collapse the two sales roles in the create-user dropdown to one "Sales"
  (sales_manager relabelled). Hide super_admin + sales_agent from selection
  via NON_ASSIGNABLE_ROLE_NAMES; the form keeps a user's *current* role even
  if hidden so existing assignments stay editable.
- Director becomes a senior-title twin of Sales: DIRECTOR_PERMISSIONS now
  equals SALES_MANAGER_PERMISSIONS (no admin/settings — Super-Admin only).
  Migration 0097 updates the existing global director row (idempotent,
  data-only; 0 users assigned on prod, so no blast radius).
- Admin create-user defaults to emailing a set-password link instead of an
  inline password (manual entry still available via a toggle). createUserSchema:
  password optional + sendSetupEmail; createUser provisions with a throwaway
  password then triggers the set-password email.
- New users get a dedicated, unique WELCOME email (crmWelcomeEmail), not the
  self-service "reset your password" email. A pending-welcome flag routes the
  shared better-auth sendResetPassword callback via account-setup-email.ts.
- Phone confirmed already optional for staff accounts (no change needed).

Tests: +welcome-routing, +create-user-setup; permission-matrix director block
realigned to no-admin. 1662 vitest pass; tsc + eslint clean.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-06-22 12:40:55 +02:00
parent 5b9560531e
commit 93989b1e1d
16 changed files with 593 additions and 156 deletions

View File

@@ -9,7 +9,7 @@
* - viewer can read but not write
* - sales_agent can manage own clients/interests but not admin features
* - sales_manager has elevated but non-admin access
* - director has near-full access
* - director mirrors sales (full sales access, no admin)
* - deepMerge correctly applies port-level overrides
*/
import { describe, it, expect, vi } from 'vitest';
@@ -190,17 +190,25 @@ describe('Permission Matrix - sales_manager', () => {
});
});
// ─── director ─────────────────────────────────────────────────────────────────
// ─── director (senior-title twin of Sales: full sales, no admin) ──────────────
describe('Permission Matrix - director', () => {
const ctx = makeCtx({ permissions: makeDirectorPermissions() });
it('can manage webhooks', async () => {
expect(await checkPermission(ctx, 'admin', 'manage_webhooks')).toBe(200);
it('has full sales access (create clients)', async () => {
expect(await checkPermission(ctx, 'clients', 'create')).toBe(200);
});
it('can manage users', async () => {
expect(await checkPermission(ctx, 'admin', 'manage_users')).toBe(200);
it('can manage tags', async () => {
expect(await checkPermission(ctx, 'admin', 'manage_tags')).toBe(200);
});
it('cannot manage users', async () => {
expect(await checkPermission(ctx, 'admin', 'manage_users')).toBe(403);
});
it('cannot manage settings', async () => {
expect(await checkPermission(ctx, 'admin', 'manage_settings')).toBe(403);
});
it('cannot perform system_backup', async () => {