Add operational Resellers module (MVP CRUD, eden.md §1.7/§7)
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>
This commit is contained in:
@@ -5,3 +5,4 @@
|
||||
* bounded context needs to share a type with the first.
|
||||
*/
|
||||
export const DOMAIN_SHARED_PACKAGE_VERSION = "0.1.0";
|
||||
export { onlyDigits, isValidCPF, isValidCNPJ } from "./validators/br-documents.js";
|
||||
|
||||
50
packages/domain-shared/src/validators/br-documents.ts
Normal file
50
packages/domain-shared/src/validators/br-documents.ts
Normal file
@@ -0,0 +1,50 @@
|
||||
/**
|
||||
* CPF/CNPJ format validation (digit-check only — never confirms a document
|
||||
* actually exists or belongs to whoever claims it). Ported from the
|
||||
* standard Brazilian algorithm, matching the legacy's isValidCPF/
|
||||
* isValidCNPJ (eden.md §1 companies, §3 client registration, §1.7
|
||||
* resellers).
|
||||
*/
|
||||
export function onlyDigits(value: string): string {
|
||||
return value.replace(/\D/g, "");
|
||||
}
|
||||
|
||||
export function isValidCPF(value: string): boolean {
|
||||
const cpf = onlyDigits(value);
|
||||
if (cpf.length !== 11 || /^(\d)\1{10}$/.test(cpf)) return false;
|
||||
|
||||
const digits = cpf.split("").map(Number);
|
||||
const checkDigit = (length: number): number => {
|
||||
let sum = 0;
|
||||
for (let i = 0; i < length; i++) {
|
||||
sum += digits[i]! * (length + 1 - i);
|
||||
}
|
||||
const remainder = (sum * 10) % 11;
|
||||
return remainder === 10 ? 0 : remainder;
|
||||
};
|
||||
|
||||
return checkDigit(9) === digits[9] && checkDigit(10) === digits[10];
|
||||
}
|
||||
|
||||
export function isValidCNPJ(value: string): boolean {
|
||||
const cnpj = onlyDigits(value);
|
||||
if (cnpj.length !== 14 || /^(\d)\1{13}$/.test(cnpj)) return false;
|
||||
|
||||
const digits = cnpj.split("").map(Number);
|
||||
// Standard CNPJ weight sequences — the second is the first with a
|
||||
// leading 6, not a generic slice (easy to get subtly wrong otherwise).
|
||||
const WEIGHTS_12 = [5, 4, 3, 2, 9, 8, 7, 6, 5, 4, 3, 2];
|
||||
const WEIGHTS_13 = [6, 5, 4, 3, 2, 9, 8, 7, 6, 5, 4, 3, 2];
|
||||
const checkDigit = (length: number, weights: number[]): number => {
|
||||
let sum = 0;
|
||||
for (let i = 0; i < length; i++) {
|
||||
sum += digits[i]! * weights[i]!;
|
||||
}
|
||||
const remainder = sum % 11;
|
||||
return remainder < 2 ? 0 : 11 - remainder;
|
||||
};
|
||||
|
||||
return (
|
||||
checkDigit(12, WEIGHTS_12) === digits[12] && checkDigit(13, WEIGHTS_13) === digits[13]
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user