Migration 1788439142825_resellers.cjs: `resellers` table plus nullable
`users.reseller_id` (scope=reseller per ADR-0004), indexes on
resellers.active and users.reseller_id.
packages/database/src/repositories/resellers.ts: list/get/create/update,
COALESCE-based partial update, mirroring the companies repository
pattern. Also adds repositories/audit.ts (hash-chained appendAuditEvent
over the existing audit_log table) and repositories/users.ts
(assignUserToReseller) - foundational pieces this module depends on that
hadn't landed in this worktree yet; audit.ts mirrors the sibling
worktree's already-built implementation verbatim to minimize merge
friction, and users.ts intentionally contains only the one function the
task asked to append (the fuller users repository belongs to the
auth/identity module being built in parallel).
apps/api/src/resellers/*: controller/service/module/dto mirroring the
Companies module's conventions - read open to any authenticated user,
write gated by @RequirePermission('resellers', 'edit'). Deliberate
deviation from legacy noted in code comments: the legacy hardcoded this
CRUD to requireAdmin and exposed reseller<->user assignment as
`PATCH /users/:id`; EDEN generalizes to the permission model (ADR-0004)
and adds `PATCH /v1/resellers/:id/assign-user` instead. Audits
reseller.created / reseller.updated / reseller.user_assigned.
packages/domain-shared/src/validators/br-documents.ts: CPF/CNPJ
digit-check validators (mirrors the sibling worktree's implementation
verbatim), used for the reseller `document` field the same way Companies
validates cnpj.
Known integration gap (by design, not a bug in this module): this
worktree doesn't yet have apps/api/src/auth/* or the companies-phase
exports in packages/database/src/index.ts - both are being built in
sibling worktrees and are explicitly out of scope here (per the task's
boundaries, app.module.ts and packages/database/src/index.ts are left
untouched for the user to wire up at merge time). @eden/database
typecheck/build pass cleanly; @eden/api typecheck/build currently fail
with exactly ten "cannot find module ../auth/..." /
"no exported member resellersRepo/usersRepo/auditRepo/Reseller" errors,
all attributable to those two integration points and nothing else.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
60 lines
2.9 KiB
JavaScript
60 lines
2.9 KiB
JavaScript
/**
|
|
* Commercial bounded context, first slice: `resellers` — the operational
|
|
* reseller/partner registry (eden.md §1.7). Pre-dates the "Programa de
|
|
* Canais" approval workflow with public tokens described in eden.md §6 —
|
|
* that workflow (client_registrations/reseller_registrations, public
|
|
* approval tokens) is explicitly out of scope for this migration and
|
|
* lands in its own later phase. This is just the simple operational CRUD
|
|
* table eden.md §1.7 describes, already referenced by `quotes.reseller_id`
|
|
* conceptually (quotes themselves aren't modeled yet either — Commercial
|
|
* phase lands module by module).
|
|
*
|
|
* Also adds `users.reseller_id` (nullable) — how a user gets scoped to a
|
|
* reseller, per eden.md §7 and the `scope=reseller` permission model
|
|
* (docs/adr/0004-permission-model.md). Lives in this migration rather
|
|
* than a separate one so the FK to `resellers(id)` can be added in the
|
|
* same statement the table is created in, without a two-step migration.
|
|
*/
|
|
|
|
exports.shorthands = undefined;
|
|
|
|
exports.up = (pgm) => {
|
|
pgm.sql(`
|
|
CREATE TABLE resellers (
|
|
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
|
|
updated_at TIMESTAMPTZ NOT NULL DEFAULT now(),
|
|
created_by UUID REFERENCES users(id),
|
|
name TEXT NOT NULL,
|
|
document TEXT,
|
|
contact_email TEXT,
|
|
active BOOLEAN NOT NULL DEFAULT true
|
|
);
|
|
COMMENT ON TABLE resellers IS
|
|
'Cadastro operacional de revenda — eden.md §1.7. document é CPF/CNPJ livre (sem CHECK de dígito no banco, validado na camada de aplicação, igual companies.cnpj). Leitura aberta a qualquer autenticado (usado em seletores, ex. Orçamentos); escrita restrita à permissão resellers:edit.';
|
|
|
|
CREATE TRIGGER trg_resellers_updated_at BEFORE UPDATE ON resellers
|
|
FOR EACH ROW EXECUTE FUNCTION set_updated_at();
|
|
|
|
CREATE INDEX idx_resellers_active ON resellers(active);
|
|
|
|
-- =====================================================================
|
|
-- users.reseller_id — scopes a user to a reseller (eden.md §7,
|
|
-- docs/adr/0004-permission-model.md scope=reseller)
|
|
-- =====================================================================
|
|
ALTER TABLE users ADD COLUMN reseller_id UUID REFERENCES resellers(id);
|
|
COMMENT ON COLUMN users.reseller_id IS
|
|
'Nullable — vincula o usuário a uma revenda (vendedor de revenda), escopando o que ele vê (eden.md §7). Atribuído via PATCH /v1/resellers/:id/assign-user, nunca aceito de campo vindo do cliente para decisão de autorização (docs/architecture.md, ADR-0004).';
|
|
|
|
CREATE INDEX idx_users_reseller ON users(reseller_id);
|
|
`);
|
|
};
|
|
|
|
exports.down = (pgm) => {
|
|
pgm.sql(`
|
|
DROP INDEX IF EXISTS idx_users_reseller;
|
|
ALTER TABLE users DROP COLUMN IF EXISTS reseller_id;
|
|
DROP TABLE IF EXISTS resellers;
|
|
`);
|
|
};
|