diff --git a/docs/proposta-admissao-exclusao.md b/docs/proposta-admissao-exclusao.md new file mode 100644 index 0000000..010fec5 --- /dev/null +++ b/docs/proposta-admissao-exclusao.md @@ -0,0 +1,112 @@ +# Proposta de âmbito para admissão e exclusão de propostas + +Estado: proposta para validação. Não implementa decisões formais na base de +negócio e não activa regras de exclusão ou adjudicação. Data: 28 de Outubro +de 2026. + +## Resultado preparado para revisão + +`/proposta/admissao` demonstra configuração de conferentes e quantidade de +confirmações, proposta manual por Compras, conferência fundamentada por +pessoas distintas e conservação local de tentativas anteriores. Os dados são +fictícios e desaparecem ao recarregar. Trocar o conferente na simulação não +autentica pessoas diferentes e não comprova independência real. + +O número de confirmações e os perfis começam por definir. A demonstração +oferece três conferentes fictícios, mas essa quantidade não é um limite nem +um quórum imposto às empresas. Nenhum valor financeiro dos exemplos locais +é utilizado. Os casos A/B/C são exemplos de análise sem pendências, requisito +obrigatório não cumprido e pendência/divergência, respectivamente. + +## Primeiro circuito proposto + +1. Administração da empresa configura e activa expressamente uma política + versionada: perfis de conferência e quantidade de pessoas distintas exigida. + No primeiro âmbito, os perfis suportados são Finanças e chefia autorizada do + departamento. A quantidade é definida pela empresa, sem valor universal. + A activação pelo administrador fica identificada e fundamentada na auditoria; + não representa uma validação automática da política fora do software. +2. Um processo adopta expressamente esse regime antes da adjudicação, guardando + a política aplicável. Sem essa adopção, mantém-se o circuito actual. A + política fica preservada para esse processo; uma alteração geral não reescreve + os seus registos. Aplicar outra versão ao processo exige adopção expressa, + antes de adjudicar, e novas propostas de decisão e conferências. +3. Compras propõe, por proposta recebida, admitir, excluir ou solicitar + esclarecimento, com fundamento e referência à versão dos requisitos, + propostas e análises apreciadas. O proponente deve ser distinto do + solicitante. Esclarecimento é um registo interno; não inclui envio de email, + publicação, comunicação ao concorrente ou prazos legais presumidos. +4. A admissão neste primeiro âmbito exige base documental actual e análise + sem pendências ou divergências não resolvidas. Requisito obrigatório não + cumprido ou declarado não aplicável sem resolução impede admitir neste + circuito inicial. Não se propõe uma dispensa de requisito por simples + justificação, maioria de votos ou alteração de pontuação. Excepções e + dispensas ficam fora do âmbito, sujeitas a política própria futura. +5. Os conferentes devem continuar elegíveis no momento de actuar: mesma + empresa, perfil permitido, departamento quando aplicável, ausência de + conflito e identidade distinta do solicitante, do proponente e de outros + conferentes contabilizados. Uma pessoa conta uma vez. Não se pressupõe + que dois perfis correspondam a duas pessoas. +6. O número de conferências favoráveis da política conclui a decisão proposta. + Uma devolução impede a conclusão dessa tentativa; não exclui a proposta por + si. Corrigir exige outra tentativa fundamentada, preservando a anterior. + Bases de propostas/requisitos alteradas exigem reapreciação; confirmações + antigas não são copiadas para uma base nova. A decisão concluída conserva + a identidade e elegibilidade verificadas na sua data, sem reescrever história. +7. Nos processos que adoptem o regime, só propostas admitidas sob a base + aplicável podem entrar na selecção de adjudicação. Uma exclusão ou pedido + de esclarecimento não substitui admissão. Admissão não autoriza compra: + mantêm-se pontuação, conflitos, orçamento e aprovação do valor final. + A implementação deverá guardar a base de admissão na adjudicação e + verificar coerência antes da emissão. Não se alteram ordens já emitidas. + +Estas são escolhas propostas para o primeiro circuito suportado, não normas +legais ou obrigações universais deduzidas dos modelos históricos. Cada empresa +continua responsável pela activação, pelos seus intervenientes e regras. A +validação solicitada é do âmbito do produto; não publica políticas reais. + +## Fora do âmbito + +- Aplicação retroactiva a adjudicações ou ordens existentes. +- Exclusão automática por resultado da qualificação ou por pontuação. +- Dispensa de requisitos obrigatórios, tratamento jurídico de recursos ou + prazos, publicação e comunicação externa aos concorrentes. +- Substituir a comparação ponderada por outro método de classificação. +- Alterar preços, reservas, compromissos, facturas ou pagamentos por uma + decisão de admissão/exclusão. +- Cancelamento de ordens, devoluções, estornos ou reversões financeiras. + +## Verificações previstas para a implementação + +Identidade e segregação no servidor; política/base preservadas; requisitos +pendentes ou obrigatórios não resolvidos; conferências repetidas; devolução; +perfil/departamento revogado; empresas diferentes; tentativas desactualizadas; +admissão versus adjudicação/emissão; rollback de auditoria e concorrência +PostgreSQL. A demonstração não substitui estes testes da futura implementação. + +## Decisão solicitada + +Validar o circuito inicial: **decisão manual por Compras, confirmações +independentes em quantidade/perfis definidos pela empresa e integração na +adjudicação apenas por adopção explícita do regime**, sem admitir propostas +com pendências ou requisitos obrigatórios não resolvidos neste primeiro âmbito. + +Pausa-se a implementação com dados reais para esta validação, conforme a +orientação de continuidade: «definir com a empresa o próximo âmbito pendente +antes de o implementar», em `docs/orientacoes-continuidade.md`. A autorização +anterior da qualificação era documental e não activava admissão/exclusão. +As alterações locais dos três documentos de continuidade foram preservadas. + +## Verificações da preparação + +TypeScript, build, Pint e lint/formatação dos ficheiros desta proposta +passaram. Os três testes existentes de consulta pública passaram, com 39 +asserções. A rota respondeu HTTP 200 com componente `preview/admission`, +utilizador nulo e apenas propriedades gerais da consulta pública. Não houve +migração, escrita na base de negócio ou publicação de política real. + +Não se repetiu a bateria completa dos módulos de negócio, que não foram +alterados. O navegador devolveu `The browser access host is unavailable`; +a revisão visual e os testes de interacção no navegador continuam pendentes. +A formatação global de `docs/compatibilidade.md`, já modificado fora deste +âmbito, não foi corrigida nem incluída nesta preparação. diff --git a/resources/js/pages/preview/admission.tsx b/resources/js/pages/preview/admission.tsx new file mode 100644 index 0000000..3bb4bfb --- /dev/null +++ b/resources/js/pages/preview/admission.tsx @@ -0,0 +1,498 @@ +import { Head } from '@inertiajs/react'; +import { useState } from 'react'; +import { PageTitle, Panel } from '@/components/procurement'; +import { Button } from '@/components/ui/button'; +import { Label } from '@/components/ui/label'; + +type Outcome = 'admit' | 'exclude' | 'clarify'; +type Review = { actor: number; decision: 'approve' | 'return'; reason: string }; +type Attempt = { + number: number; + supplier: string; + outcome: Outcome; + reason: string; + roles: string[]; + required: number; + reviews: Review[]; +}; +const reviewers = [ + { id: 1, name: 'Conferente financeiro A', role: 'finance' }, + { id: 2, name: 'Conferente financeiro B', role: 'finance' }, + { id: 3, name: 'Chefia do departamento', role: 'department_head' }, +]; +const roles: Record = { + finance: 'Finanças', + department_head: 'Chefia do departamento', +}; +const outcomes: Record = { + admit: 'Admitir a proposta', + exclude: 'Excluir a proposta', + clarify: 'Solicitar esclarecimento', +}; +const selectClass = 'h-10 w-full rounded-md border bg-background px-3 text-sm'; +const state = (attempt: Attempt) => + attempt.reviews.some((review) => review.decision === 'return') + ? 'Devolvida para revisão' + : attempt.reviews.length >= attempt.required + ? 'Decisão confirmada na simulação' + : 'Aguarda conferência independente'; + +export default function AdmissionProposal() { + const [selectedRoles, setSelectedRoles] = useState([]); + const [required, setRequired] = useState(''); + const [supplier, setSupplier] = useState('Concorrente A'); + const [outcome, setOutcome] = useState('admit'); + const [reason, setReason] = useState(''); + const [considered, setConsidered] = useState(false); + const [attempt, setAttempt] = useState(null); + const [history, setHistory] = useState([]); + const [reviewReasons, setReviewReasons] = useState>( + {}, + ); + const eligible = reviewers.filter((reviewer) => + selectedRoles.includes(reviewer.role), + ); + const admissionReady = supplier === 'Concorrente A'; + const valid = + selectedRoles.length > 0 && + Number(required) >= 1 && + Number(required) <= eligible.length && + reason.trim().length >= 10 && + considered && + (outcome !== 'admit' || admissionReady); + const final = + attempt !== null && + (attempt.reviews.some((review) => review.decision === 'return') || + attempt.reviews.length >= attempt.required); + const propose = () => { + if (valid) { + setAttempt({ + number: history.length + 1, + supplier, + outcome, + reason, + roles: [...selectedRoles], + required: Number(required), + reviews: [], + }); + setReviewReasons({}); + } + }; + const review = (actor: number, decision: Review['decision']) => { + if ( + !attempt || + final || + attempt.reviews.some((entry) => entry.actor === actor) || + !reviewers.some( + (entry) => + entry.id === actor && attempt.roles.includes(entry.role), + ) || + (reviewReasons[actor] ?? '').trim().length < 10 + ) + return; + setAttempt({ + ...attempt, + reviews: [ + ...attempt.reviews, + { actor, decision, reason: reviewReasons[actor].trim() }, + ], + }); + }; + const next = () => { + if (attempt) setHistory([...history, structuredClone(attempt)]); + setAttempt(null); + setReason(''); + setConsidered(false); + setReviewReasons({}); + }; + return ( + <> + +
+ +
+ Demonstração com intervenientes e concorrentes fictícios. + Não guarda dados, publica políticas, exclui fornecedores ou + altera adjudicações. Simular outro conferente não autentica + outra pessoa. +
+ +
+ + Perfis de conferência admitidos pela política + + {Object.entries(roles).map(([value, label]) => ( + + ))} +
+ +

+ {eligible.length} intervenientes fictícios elegíveis com + os perfis seleccionados. Uma mesma pessoa conta apenas + uma vez. +

+ {required && Number(required) > eligible.length && ( +

+ Não existem intervenientes elegíveis suficientes + para a quantidade escolhida. +

+ )} +
+ +
+ + +
+

+ Base fictícia: requisitos v1. O caso A tem análise sem + pendências assinaladas; o caso B tem requisito + obrigatório não cumprido; o caso C tem pendência ou + divergência. “Solicitar esclarecimento” não envia + mensagens. +

+ {outcome === 'admit' && !admissionReady && ( +

+ Neste primeiro âmbito proposto, a admissão aguarda + resolução das pendências, divergências ou requisitos + obrigatórios não cumpridos. Não se propõe uma + dispensa automática. +

+ )} +