Colaboradores, Cargos & Endereços
Estrutura organizacional de uma organização: os colaboradores (vendedores, gestores…)
que os pedidos referenciam, seus cargos e os endereços compartilhados. Decisão de
arquitetura registrada na ADR-018 (.claude/docs/decisions.md).
Modelo
- Position (cargo) — rótulo por organização (Vendedor, Supervisor, Coordenador, Gerente…), extensível. Desacoplado do RBAC: cargo é representativo, não concede permissão — o controle de acesso continua em Roles/Permissions. Semeado com os quatro cargos padrão ao criar a organização; o admin adiciona outros (Comprador, Executante…).
- Collaborator — cadastro comercial, não é conta de login. Identificado por
externalCode(único por organização), que casa comsellerExternalCode/managerExternalCodedo pedido. Tem cargo (positionId+ snapshotpositionExternalCode), gestor direto (auto-referência, formando a hierarquia), dados de contato/documento e umattributes(JSONB) extensível. O campouserIdliga a um login quando existir (fase E-3.1). - Address — tabela compartilhada e polimórfica: um endereço pertence a um dono via
ownerType(CLIENT/POINT_OF_SALE/COLLABORATOR) +ownerId, sem chave estrangeira. Hoje é gerenciada embutida no colaborador; o módulo Comercial (Clientes/PDV) reusa a mesma tabela.
Organization ──< Position ──< Collaborator >── Collaborator (manager, auto-ref)
│
└── Address (ownerType=COLLABORATOR, ownerId)
Cargo ≠ RBAC
Um ponto deliberado: cargo não é papel de acesso. "Gerente" é um rótulo de estrutura
comercial; quem pode ver/editar o quê continua definido pelas permissões (collaborator:read,
position:write, …) atribuídas via Roles. Isso evita acoplar a hierarquia de vendas ao controle
de segurança — os dois evoluem separados.
Endereços polimórficos
Address não tem FK para o dono: o vínculo é (orgId, ownerType, ownerId). Ao editar um
colaborador, o conjunto de endereços é sincronizado por substituição (apaga e recria) dentro
da mesma transação — simples e previsível. No máximo um endereço fica marcado como isMain. Por
não haver cascade, a remoção do dono limpa seus endereços explicitamente.
Endpoints (API NestJS)
Todas as rotas exigem organização ativa (ActiveOrgGuard) e a permissão indicada. O master
de plataforma faz bypass do RBAC ao entrar numa organização.
| Método | Rota | Permissão | Descrição |
|---|---|---|---|
| GET | /api/positions | position:read | Lista cargos (com contagem de colaboradores). |
| POST | /api/positions | position:write | Cria cargo. |
| PATCH | /api/positions/:id | position:write | Atualiza cargo. |
| DELETE | /api/positions/:id | position:write | Remove cargo (bloqueado se estiver em uso). |
| GET | /api/collaborators | collaborator:read | Lista colaboradores (cargo/gestor resolvidos). |
| GET | /api/collaborators/:id | collaborator:read | Detalhe (com cargo, gestor e endereços). |
| POST | /api/collaborators | collaborator:write | Cria colaborador (endereços embutidos). |
| PATCH | /api/collaborators/:id | collaborator:write | Atualiza (endereços sincronizados se enviados). |
| DELETE | /api/collaborators/:id | collaborator:write | Remove colaborador e seus endereços. |
Schemas completos na Referência da API (Scalar) — tags Estrutura · Cargos e Estrutura · Colaboradores.
Regras de validação
Collaborator.externalCodeé único por organização (409 em conflito).positionId/managerIdinformados devem existir na mesma organização (400 caso contrário).- Um colaborador não pode ser o próprio gestor.
- Sem ciclos na hierarquia: o gestor escolhido não pode ter o próprio colaborador na sua cadeia de gestores (ex.: A gestor de B e B gestor de A). A API sobe a cadeia a partir do gestor proposto e rejeita com 400 se reencontrar o colaborador — impede relacionamento cruzado direto (A↔B) ou indireto (A→B→C→A).
- Ao remover um cargo em uso, a API bloqueia (reatribua os colaboradores antes).
- FKs
positionIdemanagerIdsãoON DELETE SET NULL: remover um cargo ou um gestor não apaga os colaboradores — apenas zera o vínculo.
Frontend (Next.js)
Dois itens no menu da organização: Colaboradores e Cargos (visíveis conforme as
permissões collaborator:read / position:read). As telas trazem CRUD em tabela + modal;
o formulário de colaborador inclui um editor de endereços embutido e seletores de cargo e
gestor. As chamadas passam por route handlers autenticados (proxy com o cookie httpOnly),
como no restante do app.
Seed de cargos
Ao criar uma organização, os cargos padrão são semeados na mesma transação:
| Cargo | externalCode | rank |
|---|---|---|
| Gerente | GERENTE | 1 |
| Coordenador | COORDENADOR | 2 |
| Supervisor | SUPERVISOR | 3 |
| Vendedor | VENDEDOR | 4 |
rank menor = mais alto na hierarquia. Os externalCode acompanham o enum hierarchy do ERP
de origem, facilitando o casamento na sincronização futura.