Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
-- Distribuição interna: reserva, expedição, recebimento e divergência rastreáveis.
create table if not exists sigov.almoxarifado_entrega(
id bigint generated always as identity primary key, tenant_id bigint not null, entidade_id bigint not null,
requisicao_id bigint not null references sigov.almoxarifado_requisicao(id), almoxarifado_id bigint not null references sigov.almoxarifado_local(id),
status varchar(24) not null default 'SEPARADA', idempotency_key varchar(100) not null, observacao text,
created_at timestamptz not null default now(), created_by bigint not null, expedida_em timestamptz, expedida_por bigint, recebida_em timestamptz, recebida_por bigint,
Comment on lines +3 to +6

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Persist the required multi-sphere context

The new persisted delivery entity records only tenant and entity IDs, omitting the mandatory esfera_governo, tipo_entidade, administrative hierarchy, managing/executing units, and territorial scope. Deliveries therefore cannot retain or query the required municipal, state, or federal context independently, contrary to the repository's schema invariant for every entity; add the prescribed context columns and constraints to this new schema.

AGENTS.md reference: AGENTS.md:L34-L37

Useful? React with 👍 / 👎.

constraint ck_almox_entrega_status check(status in('SEPARADA','EXPEDIDA','RECEBIDA_PARCIAL','RECEBIDA','DIVERGENCIA','CANCELADA')),
constraint ux_almox_entrega_idempotencia unique(tenant_id,entidade_id,idempotency_key));
create table if not exists sigov.almoxarifado_entrega_item(
id bigint generated always as identity primary key, tenant_id bigint not null, entidade_id bigint not null,
entrega_id bigint not null references sigov.almoxarifado_entrega(id), requisicao_item_id bigint not null references sigov.almoxarifado_requisicao_item(id),
quantidade_separada numeric(18,4) not null, quantidade_recebida numeric(18,4) not null default 0, quantidade_recusada numeric(18,4) not null default 0, lote varchar(100), divergencia text,
constraint ck_almox_entrega_item_qtd check(quantidade_separada>0 and quantidade_recebida>=0 and quantidade_recusada>=0 and quantidade_recebida+quantidade_recusada<=quantidade_separada),
constraint ux_almox_entrega_item unique(entrega_id,requisicao_item_id,lote));
create table if not exists sigov.almoxarifado_entrega_evento(
id bigint generated always as identity primary key, tenant_id bigint not null, entidade_id bigint not null, entrega_id bigint not null references sigov.almoxarifado_entrega(id),
tipo varchar(30) not null, dados jsonb, usuario_id bigint not null, correlation_id varchar(100) not null, ocorrido_em timestamptz not null default now());
create index if not exists ix_almox_entrega_fila on sigov.almoxarifado_entrega(tenant_id,entidade_id,status,created_at,id);
create index if not exists ix_almox_entrega_req on sigov.almoxarifado_entrega(tenant_id,entidade_id,requisicao_id,id);
insert into sigov.permissao(modulo,chave,recurso,acao,descricao,ativo,is_deleted)
select 'almoxarifado','almoxarifado.recebimento.confirmar','almoxarifado.recebimento','confirmar','Confirmar recebimento e divergências',true,false
where not exists(select 1 from sigov.permissao where chave='almoxarifado.recebimento.confirmar');
insert into sigov.perfil_permissao(perfil_acesso_id,permissao_id,efeito,ativo,is_deleted)
select pa.id,p.id,'PERMITIR',true,false from sigov.perfil_acesso pa join sigov.permissao p on p.chave='almoxarifado.recebimento.confirmar'
where pa.codigo_externo='SUPERADMIN' and pa.sistemico and pa.ativo and not pa.is_deleted
on conflict(perfil_acesso_id,permissao_id) do update set efeito='PERMITIR',ativo=true,is_deleted=false;
13 changes: 13 additions & 0 deletions database/postgres/migrations/manifest.json
Original file line number Diff line number Diff line change
Expand Up @@ -2397,6 +2397,19 @@
"20260914130000"
],
"postConditionSql": "select exists(select 1 from information_schema.columns where table_schema='sigov' and table_name='industria_apontamento' and column_name='idempotency_key') and exists(select 1 from pg_indexes where schemaname='sigov' and indexname='ux_industria_apontamento_idempotencia')"
},
{
"version": "20260915160000",
"description": "Distribuição interna de materiais com separação, expedição, recebimento e divergência",
"category": "schema",
"file": "20260915160000_distribuicao_interna_materiais.sql",
"checksum": "5bb738d3c03cee7186222151065f2483eb223a728f09b0eecf664e3016a9b4dc",
"applyAutomatically": true,
"includeInBaseline": true,
"dependencies": [
"20260915120000"
],
"postConditionSql": "select to_regclass('sigov.almoxarifado_entrega') is not null and to_regclass('sigov.almoxarifado_entrega_item') is not null and to_regclass('sigov.almoxarifado_entrega_evento') is not null"
}
],
"compatibilityAfterAll": [
Expand Down
28 changes: 28 additions & 0 deletions database/postgres/script_completo.sql
Original file line number Diff line number Diff line change
Expand Up @@ -31167,3 +31167,31 @@ do $$ begin
and coalesce(quantidade_aprovada,0) + coalesce(quantidade_rejeitada,0) <= quantidade_inspecionada));
end if;
end $$;

-- Migration 20260915160000: distribuicao interna de materiais
-- Distribuição interna: reserva, expedição, recebimento e divergência rastreáveis.
create table if not exists sigov.almoxarifado_entrega(
id bigint generated always as identity primary key, tenant_id bigint not null, entidade_id bigint not null,
requisicao_id bigint not null references sigov.almoxarifado_requisicao(id), almoxarifado_id bigint not null references sigov.almoxarifado_local(id),
status varchar(24) not null default 'SEPARADA', idempotency_key varchar(100) not null, observacao text,
created_at timestamptz not null default now(), created_by bigint not null, expedida_em timestamptz, expedida_por bigint, recebida_em timestamptz, recebida_por bigint,
constraint ck_almox_entrega_status check(status in('SEPARADA','EXPEDIDA','RECEBIDA_PARCIAL','RECEBIDA','DIVERGENCIA','CANCELADA')),
constraint ux_almox_entrega_idempotencia unique(tenant_id,entidade_id,idempotency_key));
create table if not exists sigov.almoxarifado_entrega_item(
id bigint generated always as identity primary key, tenant_id bigint not null, entidade_id bigint not null,
entrega_id bigint not null references sigov.almoxarifado_entrega(id), requisicao_item_id bigint not null references sigov.almoxarifado_requisicao_item(id),
quantidade_separada numeric(18,4) not null, quantidade_recebida numeric(18,4) not null default 0, quantidade_recusada numeric(18,4) not null default 0, lote varchar(100), divergencia text,
constraint ck_almox_entrega_item_qtd check(quantidade_separada>0 and quantidade_recebida>=0 and quantidade_recusada>=0 and quantidade_recebida+quantidade_recusada<=quantidade_separada),
constraint ux_almox_entrega_item unique(entrega_id,requisicao_item_id,lote));
create table if not exists sigov.almoxarifado_entrega_evento(
id bigint generated always as identity primary key, tenant_id bigint not null, entidade_id bigint not null, entrega_id bigint not null references sigov.almoxarifado_entrega(id),
tipo varchar(30) not null, dados jsonb, usuario_id bigint not null, correlation_id varchar(100) not null, ocorrido_em timestamptz not null default now());
create index if not exists ix_almox_entrega_fila on sigov.almoxarifado_entrega(tenant_id,entidade_id,status,created_at,id);
create index if not exists ix_almox_entrega_req on sigov.almoxarifado_entrega(tenant_id,entidade_id,requisicao_id,id);
insert into sigov.permissao(modulo,chave,recurso,acao,descricao,ativo,is_deleted)
select 'almoxarifado','almoxarifado.recebimento.confirmar','almoxarifado.recebimento','confirmar','Confirmar recebimento e divergências',true,false
where not exists(select 1 from sigov.permissao where chave='almoxarifado.recebimento.confirmar');
insert into sigov.perfil_permissao(perfil_acesso_id,permissao_id,efeito,ativo,is_deleted)
select pa.id,p.id,'PERMITIR',true,false from sigov.perfil_acesso pa join sigov.permissao p on p.chave='almoxarifado.recebimento.confirmar'
where pa.codigo_externo='SUPERADMIN' and pa.sistemico and pa.ativo and not pa.is_deleted
on conflict(perfil_acesso_id,permissao_id) do update set efeito='PERMITIR',ativo=true,is_deleted=false;
28 changes: 28 additions & 0 deletions database/postgres/script_completo_dev.sql
Original file line number Diff line number Diff line change
Expand Up @@ -31345,3 +31345,31 @@ do $$ begin
and coalesce(quantidade_aprovada,0) + coalesce(quantidade_rejeitada,0) <= quantidade_inspecionada));
end if;
end $$;

-- Migration 20260915160000: distribuicao interna de materiais
-- Distribuição interna: reserva, expedição, recebimento e divergência rastreáveis.
create table if not exists sigov.almoxarifado_entrega(
id bigint generated always as identity primary key, tenant_id bigint not null, entidade_id bigint not null,
requisicao_id bigint not null references sigov.almoxarifado_requisicao(id), almoxarifado_id bigint not null references sigov.almoxarifado_local(id),
status varchar(24) not null default 'SEPARADA', idempotency_key varchar(100) not null, observacao text,
created_at timestamptz not null default now(), created_by bigint not null, expedida_em timestamptz, expedida_por bigint, recebida_em timestamptz, recebida_por bigint,
constraint ck_almox_entrega_status check(status in('SEPARADA','EXPEDIDA','RECEBIDA_PARCIAL','RECEBIDA','DIVERGENCIA','CANCELADA')),
constraint ux_almox_entrega_idempotencia unique(tenant_id,entidade_id,idempotency_key));
create table if not exists sigov.almoxarifado_entrega_item(
id bigint generated always as identity primary key, tenant_id bigint not null, entidade_id bigint not null,
entrega_id bigint not null references sigov.almoxarifado_entrega(id), requisicao_item_id bigint not null references sigov.almoxarifado_requisicao_item(id),
quantidade_separada numeric(18,4) not null, quantidade_recebida numeric(18,4) not null default 0, quantidade_recusada numeric(18,4) not null default 0, lote varchar(100), divergencia text,
constraint ck_almox_entrega_item_qtd check(quantidade_separada>0 and quantidade_recebida>=0 and quantidade_recusada>=0 and quantidade_recebida+quantidade_recusada<=quantidade_separada),
constraint ux_almox_entrega_item unique(entrega_id,requisicao_item_id,lote));
create table if not exists sigov.almoxarifado_entrega_evento(
id bigint generated always as identity primary key, tenant_id bigint not null, entidade_id bigint not null, entrega_id bigint not null references sigov.almoxarifado_entrega(id),
tipo varchar(30) not null, dados jsonb, usuario_id bigint not null, correlation_id varchar(100) not null, ocorrido_em timestamptz not null default now());
create index if not exists ix_almox_entrega_fila on sigov.almoxarifado_entrega(tenant_id,entidade_id,status,created_at,id);
create index if not exists ix_almox_entrega_req on sigov.almoxarifado_entrega(tenant_id,entidade_id,requisicao_id,id);
insert into sigov.permissao(modulo,chave,recurso,acao,descricao,ativo,is_deleted)
select 'almoxarifado','almoxarifado.recebimento.confirmar','almoxarifado.recebimento','confirmar','Confirmar recebimento e divergências',true,false
where not exists(select 1 from sigov.permissao where chave='almoxarifado.recebimento.confirmar');
insert into sigov.perfil_permissao(perfil_acesso_id,permissao_id,efeito,ativo,is_deleted)
select pa.id,p.id,'PERMITIR',true,false from sigov.perfil_acesso pa join sigov.permissao p on p.chave='almoxarifado.recebimento.confirmar'
where pa.codigo_externo='SUPERADMIN' and pa.sistemico and pa.ativo and not pa.is_deleted
on conflict(perfil_acesso_id,permissao_id) do update set efeito='PERMITIR',ativo=true,is_deleted=false;
28 changes: 28 additions & 0 deletions database/script_completo.sql
Original file line number Diff line number Diff line change
Expand Up @@ -31167,3 +31167,31 @@ do $$ begin
and coalesce(quantidade_aprovada,0) + coalesce(quantidade_rejeitada,0) <= quantidade_inspecionada));
end if;
end $$;

-- Migration 20260915160000: distribuicao interna de materiais
-- Distribuição interna: reserva, expedição, recebimento e divergência rastreáveis.
create table if not exists sigov.almoxarifado_entrega(
id bigint generated always as identity primary key, tenant_id bigint not null, entidade_id bigint not null,
requisicao_id bigint not null references sigov.almoxarifado_requisicao(id), almoxarifado_id bigint not null references sigov.almoxarifado_local(id),
status varchar(24) not null default 'SEPARADA', idempotency_key varchar(100) not null, observacao text,
created_at timestamptz not null default now(), created_by bigint not null, expedida_em timestamptz, expedida_por bigint, recebida_em timestamptz, recebida_por bigint,
constraint ck_almox_entrega_status check(status in('SEPARADA','EXPEDIDA','RECEBIDA_PARCIAL','RECEBIDA','DIVERGENCIA','CANCELADA')),
constraint ux_almox_entrega_idempotencia unique(tenant_id,entidade_id,idempotency_key));
create table if not exists sigov.almoxarifado_entrega_item(
id bigint generated always as identity primary key, tenant_id bigint not null, entidade_id bigint not null,
entrega_id bigint not null references sigov.almoxarifado_entrega(id), requisicao_item_id bigint not null references sigov.almoxarifado_requisicao_item(id),
quantidade_separada numeric(18,4) not null, quantidade_recebida numeric(18,4) not null default 0, quantidade_recusada numeric(18,4) not null default 0, lote varchar(100), divergencia text,
constraint ck_almox_entrega_item_qtd check(quantidade_separada>0 and quantidade_recebida>=0 and quantidade_recusada>=0 and quantidade_recebida+quantidade_recusada<=quantidade_separada),
constraint ux_almox_entrega_item unique(entrega_id,requisicao_item_id,lote));
create table if not exists sigov.almoxarifado_entrega_evento(
id bigint generated always as identity primary key, tenant_id bigint not null, entidade_id bigint not null, entrega_id bigint not null references sigov.almoxarifado_entrega(id),
tipo varchar(30) not null, dados jsonb, usuario_id bigint not null, correlation_id varchar(100) not null, ocorrido_em timestamptz not null default now());
create index if not exists ix_almox_entrega_fila on sigov.almoxarifado_entrega(tenant_id,entidade_id,status,created_at,id);
create index if not exists ix_almox_entrega_req on sigov.almoxarifado_entrega(tenant_id,entidade_id,requisicao_id,id);
insert into sigov.permissao(modulo,chave,recurso,acao,descricao,ativo,is_deleted)
select 'almoxarifado','almoxarifado.recebimento.confirmar','almoxarifado.recebimento','confirmar','Confirmar recebimento e divergências',true,false
where not exists(select 1 from sigov.permissao where chave='almoxarifado.recebimento.confirmar');
insert into sigov.perfil_permissao(perfil_acesso_id,permissao_id,efeito,ativo,is_deleted)
select pa.id,p.id,'PERMITIR',true,false from sigov.perfil_acesso pa join sigov.permissao p on p.chave='almoxarifado.recebimento.confirmar'
where pa.codigo_externo='SUPERADMIN' and pa.sistemico and pa.ativo and not pa.is_deleted
on conflict(perfil_acesso_id,permissao_id) do update set efeito='PERMITIR',ativo=true,is_deleted=false;
40 changes: 40 additions & 0 deletions docs/entregas/RC51-02-DISTRIBUICAO-INTERNA.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
# RC51.02 — distribuição interna de materiais

Data: 2026-09-15. Estado: **implementada sem validação runtime / BLOCKED**.

## Estado inicial e classificação

Branch `work`, HEAD inicial `0ab62eb5edc323271b774d825109d3d89b47497b`, árvore limpa, sem remoto e sem upstream. SDK normativo `10.0.100`, C# 14, ASP.NET Core MVC/Razor/API, PostgreSQL 16+ e Dapper. Somente Git, Node 20, npm 11 e Python estavam disponíveis; `dotnet`, `psql`, PostgreSQL, Docker, PowerShell e navegador estavam ausentes.

| Jornada | Estado inicial | Resultado desta fatia |
|---|---|---|
| Catálogo, locais, saldo e movimento | parcial | preservado como estoque canônico |
| Solicitação interna | parcial | fluxo existente rascunho → envio → autorização preservado; detalhe ganhou pendências e entregas |
| Autorização | parcial | política persistida existente preservada; nenhuma autoaprovação |
| Separação/reserva | ausente | persistida por entrega e item, com disponibilidade descontando outras separações |
| Expedição | atendimento monolítico parcial | saída, saldo, movimento e quantidade atendida passam pela mesma transação |
| Recebimento e divergência | ausente | conferência parcial/idempotente e divergência sem ajuste automático |
| Transferência, devolução e estorno | parcial em outros modelos / ausente nesta jornada | não foram tratados como distribuição nem implementados por falta de política canônica confirmada |
| Relatórios completos | parcial | consultas existentes preservadas; novos relatórios continuam pendentes |
| Formulários | implementados sem validação | Razor real, antiforgery, erros operacionais e impressão; browser indisponível |

## Contrato operacional aplicado

- **Solicitação:** solicitante autenticado e contexto vêm da sessão; itens positivos e materiais/locais do mesmo tenant e entidade são validados no banco. Rascunho não movimenta nem reserva.
- **Autorização:** `ENVIADA → APROVADA` exige a permissão canônica. A separação recusa qualquer outro estado.
- **Separação:** cria `SEPARADA`; a reserva é a soma persistida de itens em entregas separadas. Disponibilidade é saldo físico menos reservas concorrentes. A requisição e seus itens são bloqueados durante a decisão; chave idempotente evita duplicação.
- **Expedição:** somente `SEPARADA`; bloqueia entrega e saldo, baixa fisicamente a origem, cria movimento ligado à requisição e incrementa atendimento. Repetição após `EXPEDIDA` não duplica saída. Se restar quantidade, a solicitação continua `APROVADA`; caso contrário fica `ATENDIDA`. Não foi criado estoque em trânsito.
- **Recebimento:** somente entrega expedida ou parcialmente conferida; recebido + recusado nunca excede expedido. Confirmações são serializadas e idempotentes. Recebimento de unidade consumidora não cria saldo. Divergência/recusa é registrada e não gera ajuste, descarte ou devolução automática.
- **Devolução e estorno:** permanecem pendentes de política e permissão específicas; movimento não é apagado e divergência não é devolvida silenciosamente ao disponível.

## Persistência e concorrência

A migration forward-only cria entrega, itens e eventos com PK `bigint identity`, isolamento por tenant/entidade, constraints quantitativas, índices de fila e idempotência. A saída reutiliza `almoxarifado_estoque` e `almoxarifado_movimentacao`; não existe segundo saldo. Todas as escritas operacionais usam SQL parametrizado e transação Dapper.

## Validação e bloqueios

Passaram JSON do manifesto, verificação estática de whitespace/diff e busca por conflitos. Restore locked, build Release, Razor, testes, Swagger, rotas Web, PostgreSQL vazio/upgrade/reexecução, dois tenants, concorrência real, screenshots e acessibilidade nas larguras solicitadas ficaram **BLOCKED** pela ausência de SDK, banco e navegador. Não há declaração de homologação.

## Pendências e próximo backlog

A unidade solicitante ainda não possui catálogo seletor autorizado no formulário legado; relatórios paginados/exportações de pendências e divergências, cancelamento de separação, devolução e estorno requerem política persistida. Próximo item exato: implementar cancelamento concorrente de entrega `SEPARADA` que libere exclusivamente sua reserva, seguido de política persistida de devolução/estorno e prova PostgreSQL 16.
Loading
Loading