Prepara circuito configuravel de admissao e exclusao para validacao
This commit is contained in:
@@ -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.
|
||||
@@ -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<string, string> = {
|
||||
finance: 'Finanças',
|
||||
department_head: 'Chefia do departamento',
|
||||
};
|
||||
const outcomes: Record<Outcome, string> = {
|
||||
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<string[]>([]);
|
||||
const [required, setRequired] = useState('');
|
||||
const [supplier, setSupplier] = useState('Concorrente A');
|
||||
const [outcome, setOutcome] = useState<Outcome>('admit');
|
||||
const [reason, setReason] = useState('');
|
||||
const [considered, setConsidered] = useState(false);
|
||||
const [attempt, setAttempt] = useState<Attempt | null>(null);
|
||||
const [history, setHistory] = useState<Attempt[]>([]);
|
||||
const [reviewReasons, setReviewReasons] = useState<Record<number, string>>(
|
||||
{},
|
||||
);
|
||||
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 (
|
||||
<>
|
||||
<Head title="Proposta de admissão e exclusão" />
|
||||
<div className="mx-auto flex max-w-7xl flex-col gap-6 p-5 lg:p-8">
|
||||
<PageTitle
|
||||
eyebrow="Proposta para validação"
|
||||
title="Admissão e exclusão de propostas"
|
||||
description="Experimente uma decisão manual com conferência independente e configuração própria da empresa."
|
||||
/>
|
||||
<div className="rounded-xl border border-amber-200 bg-amber-50 p-4 text-sm text-amber-950">
|
||||
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.
|
||||
</div>
|
||||
<Panel
|
||||
title="1. Circuito escolhido pela empresa"
|
||||
description="Não existe número de confirmações ou perfil seleccionado por defeito. Nesta demonstração existem três conferentes fictícios, distintos do operador de Compras que propõe a decisão."
|
||||
>
|
||||
<fieldset className="space-y-3">
|
||||
<legend className="mb-3 text-sm font-medium">
|
||||
Perfis de conferência admitidos pela política
|
||||
</legend>
|
||||
{Object.entries(roles).map(([value, label]) => (
|
||||
<Label
|
||||
key={value}
|
||||
className="flex items-center gap-3"
|
||||
>
|
||||
<input
|
||||
type="checkbox"
|
||||
disabled={attempt !== null}
|
||||
checked={selectedRoles.includes(value)}
|
||||
onChange={(event) =>
|
||||
setSelectedRoles(
|
||||
event.target.checked
|
||||
? [...selectedRoles, value]
|
||||
: selectedRoles.filter(
|
||||
(entry) =>
|
||||
entry !== value,
|
||||
),
|
||||
)
|
||||
}
|
||||
/>
|
||||
{label}
|
||||
</Label>
|
||||
))}
|
||||
</fieldset>
|
||||
<Label className="mt-4 max-w-md">
|
||||
Confirmações de pessoas distintas necessárias
|
||||
<select
|
||||
disabled={attempt !== null}
|
||||
className={selectClass}
|
||||
value={required}
|
||||
onChange={(event) =>
|
||||
setRequired(event.target.value)
|
||||
}
|
||||
>
|
||||
<option value="">A empresa deve definir</option>
|
||||
{[1, 2, 3].map((count) => (
|
||||
<option key={count} value={count}>
|
||||
{count}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
</Label>
|
||||
<p className="mt-3 text-xs text-muted-foreground">
|
||||
{eligible.length} intervenientes fictícios elegíveis com
|
||||
os perfis seleccionados. Uma mesma pessoa conta apenas
|
||||
uma vez.
|
||||
</p>
|
||||
{required && Number(required) > eligible.length && (
|
||||
<p className="mt-3 text-sm text-red-700">
|
||||
Não existem intervenientes elegíveis suficientes
|
||||
para a quantidade escolhida.
|
||||
</p>
|
||||
)}
|
||||
</Panel>
|
||||
<Panel
|
||||
title="2. Proposta manual de decisão por Compras"
|
||||
description="A decisão deverá referenciar a versão dos requisitos, as propostas e as análises apreciadas. As pendências não são resolvidas por um cálculo automático."
|
||||
>
|
||||
<div className="grid gap-4 sm:grid-cols-2">
|
||||
<Label>
|
||||
Proposta
|
||||
<select
|
||||
disabled={attempt !== null}
|
||||
className={selectClass}
|
||||
value={supplier}
|
||||
onChange={(event) =>
|
||||
setSupplier(event.target.value)
|
||||
}
|
||||
>
|
||||
{[
|
||||
'Concorrente A',
|
||||
'Concorrente B',
|
||||
'Concorrente C',
|
||||
].map((name) => (
|
||||
<option key={name}>{name}</option>
|
||||
))}
|
||||
</select>
|
||||
</Label>
|
||||
<Label>
|
||||
Decisão proposta
|
||||
<select
|
||||
disabled={attempt !== null}
|
||||
className={selectClass}
|
||||
value={outcome}
|
||||
onChange={(event) =>
|
||||
setOutcome(event.target.value as Outcome)
|
||||
}
|
||||
>
|
||||
{Object.entries(outcomes).map(
|
||||
([value, label]) => (
|
||||
<option key={value} value={value}>
|
||||
{label}
|
||||
</option>
|
||||
),
|
||||
)}
|
||||
</select>
|
||||
</Label>
|
||||
</div>
|
||||
<p className="mt-4 rounded-lg border p-3 text-sm text-muted-foreground">
|
||||
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.
|
||||
</p>
|
||||
{outcome === 'admit' && !admissionReady && (
|
||||
<p className="mt-3 text-sm text-red-700">
|
||||
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.
|
||||
</p>
|
||||
)}
|
||||
<Label className="mt-4">
|
||||
Fundamento da decisão e apreciação das pendências
|
||||
<textarea
|
||||
disabled={attempt !== null}
|
||||
className="min-h-28 rounded-md border bg-background p-3 text-sm"
|
||||
maxLength={4000}
|
||||
value={reason}
|
||||
onChange={(event) => setReason(event.target.value)}
|
||||
/>
|
||||
</Label>
|
||||
<Label className="mt-4 flex items-start gap-3">
|
||||
<input
|
||||
type="checkbox"
|
||||
disabled={attempt !== null}
|
||||
checked={considered}
|
||||
onChange={(event) =>
|
||||
setConsidered(event.target.checked)
|
||||
}
|
||||
/>
|
||||
<span>
|
||||
Na simulação, o fundamento aprecia os resultados,
|
||||
pendências e divergências relevantes. Isto não
|
||||
representa uma dispensa de requisitos obrigatórios.
|
||||
</span>
|
||||
</Label>
|
||||
{!attempt && (
|
||||
<Button
|
||||
className="mt-5"
|
||||
disabled={!valid}
|
||||
onClick={propose}
|
||||
>
|
||||
Simular proposta de decisão
|
||||
</Button>
|
||||
)}
|
||||
</Panel>
|
||||
{attempt && (
|
||||
<Panel
|
||||
title={`3. Conferência independente · Tentativa ${attempt.number}`}
|
||||
description={`${attempt.supplier} · ${outcomes[attempt.outcome]} · ${state(attempt)}`}
|
||||
>
|
||||
<p className="text-sm whitespace-pre-wrap">
|
||||
{attempt.reason}
|
||||
</p>
|
||||
<p className="mt-3 text-sm">
|
||||
{
|
||||
attempt.reviews.filter(
|
||||
(entry) => entry.decision === 'approve',
|
||||
).length
|
||||
}
|
||||
/{attempt.required} confirmações independentes.
|
||||
</p>
|
||||
<div className="mt-5 grid gap-4 lg:grid-cols-3">
|
||||
{reviewers
|
||||
.filter((entry) =>
|
||||
attempt.roles.includes(entry.role),
|
||||
)
|
||||
.map((reviewer) => {
|
||||
const existing = attempt.reviews.find(
|
||||
(entry) => entry.actor === reviewer.id,
|
||||
);
|
||||
return (
|
||||
<div
|
||||
key={reviewer.id}
|
||||
className="space-y-3 rounded-xl border p-4"
|
||||
>
|
||||
<h3 className="text-sm font-semibold">
|
||||
{reviewer.name}
|
||||
</h3>
|
||||
<p className="text-xs text-muted-foreground">
|
||||
{roles[reviewer.role]} ·
|
||||
Interveniente fictício
|
||||
</p>
|
||||
{existing ? (
|
||||
<>
|
||||
<p className="text-sm font-medium">
|
||||
{existing.decision ===
|
||||
'approve'
|
||||
? 'Conferência favorável'
|
||||
: 'Devolvido para revisão'}
|
||||
</p>
|
||||
<p className="text-sm whitespace-pre-wrap">
|
||||
{existing.reason}
|
||||
</p>
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<Label>
|
||||
Fundamento da
|
||||
conferência
|
||||
<textarea
|
||||
disabled={final}
|
||||
className="min-h-24 rounded-md border bg-background p-3 text-sm"
|
||||
value={
|
||||
reviewReasons[
|
||||
reviewer.id
|
||||
] ?? ''
|
||||
}
|
||||
maxLength={2000}
|
||||
onChange={(event) =>
|
||||
setReviewReasons(
|
||||
{
|
||||
...reviewReasons,
|
||||
[reviewer.id]:
|
||||
event
|
||||
.target
|
||||
.value,
|
||||
},
|
||||
)
|
||||
}
|
||||
/>
|
||||
</Label>
|
||||
<div className="flex flex-wrap gap-2">
|
||||
<Button
|
||||
variant="outline"
|
||||
disabled={
|
||||
final ||
|
||||
(
|
||||
reviewReasons[
|
||||
reviewer
|
||||
.id
|
||||
] ?? ''
|
||||
).trim()
|
||||
.length < 10
|
||||
}
|
||||
onClick={() =>
|
||||
review(
|
||||
reviewer.id,
|
||||
'approve',
|
||||
)
|
||||
}
|
||||
>
|
||||
Simular confirmação
|
||||
</Button>
|
||||
<Button
|
||||
variant="ghost"
|
||||
disabled={
|
||||
final ||
|
||||
(
|
||||
reviewReasons[
|
||||
reviewer
|
||||
.id
|
||||
] ?? ''
|
||||
).trim()
|
||||
.length < 10
|
||||
}
|
||||
onClick={() =>
|
||||
review(
|
||||
reviewer.id,
|
||||
'return',
|
||||
)
|
||||
}
|
||||
>
|
||||
Simular devolução
|
||||
</Button>
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
<p className="mt-4 text-sm text-muted-foreground">
|
||||
Uma devolução rejeita esta proposta de decisão; não
|
||||
exclui o concorrente por si. Admissão também não
|
||||
significa adjudicação.
|
||||
</p>
|
||||
<Button
|
||||
className="mt-5"
|
||||
variant="outline"
|
||||
onClick={next}
|
||||
>
|
||||
Preservar esta tentativa e preparar outra
|
||||
</Button>
|
||||
</Panel>
|
||||
)}
|
||||
<Panel
|
||||
title="Efeitos propostos para a aplicação"
|
||||
description="Pendentes de validação; nenhum está activo nesta demonstração."
|
||||
>
|
||||
<ul className="list-inside list-disc space-y-2 text-sm text-muted-foreground">
|
||||
<li>
|
||||
Política explícita e versionada por empresa, sem
|
||||
aplicar um quórum universal.
|
||||
</li>
|
||||
<li>
|
||||
Proponente, solicitante e conferentes sujeitos a
|
||||
identidade, elegibilidade, departamento e conflitos
|
||||
verificados no servidor.
|
||||
</li>
|
||||
<li>
|
||||
Requisitos ou propostas alterados exigem nova
|
||||
apreciação; confirmações anteriores não são
|
||||
copiadas.
|
||||
</li>
|
||||
<li>
|
||||
Integração com a adjudicação apenas em processos que
|
||||
adoptem expressamente o regime; nenhuma alteração
|
||||
retroactiva de ordens existentes.
|
||||
</li>
|
||||
<li>
|
||||
Excepções a requisitos obrigatórios e conflitos de
|
||||
interesses não são dispensados por esta simulação.
|
||||
</li>
|
||||
</ul>
|
||||
</Panel>
|
||||
{history.length > 0 && (
|
||||
<Panel
|
||||
title="Tentativas anteriores desta simulação"
|
||||
description="Conservadas apenas enquanto esta página estiver aberta. Recarregar elimina os dados locais."
|
||||
>
|
||||
{history.map((entry) => (
|
||||
<details
|
||||
key={entry.number}
|
||||
className="border-b py-3"
|
||||
>
|
||||
<summary className="cursor-pointer text-sm font-medium">
|
||||
Tentativa {entry.number} · {entry.supplier}{' '}
|
||||
· {outcomes[entry.outcome]} · {state(entry)}
|
||||
</summary>
|
||||
<p className="mt-3 text-sm whitespace-pre-wrap">
|
||||
{entry.reason}
|
||||
</p>
|
||||
<p className="mt-2 text-xs text-muted-foreground">
|
||||
Política simulada:{' '}
|
||||
{entry.roles
|
||||
.map((role) => roles[role])
|
||||
.join(', ')}{' '}
|
||||
· {entry.required} confirmações.
|
||||
</p>
|
||||
<ul className="mt-3 space-y-2 text-sm">
|
||||
{entry.reviews.map((entryReview) => (
|
||||
<li key={entryReview.actor}>
|
||||
{
|
||||
reviewers.find(
|
||||
(actor) =>
|
||||
actor.id ===
|
||||
entryReview.actor,
|
||||
)?.name
|
||||
}
|
||||
:{' '}
|
||||
{entryReview.decision === 'approve'
|
||||
? 'Favorável'
|
||||
: 'Devolução'}{' '}
|
||||
· {entryReview.reason}
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</details>
|
||||
))}
|
||||
</Panel>
|
||||
)}
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
}
|
||||
@@ -7,6 +7,7 @@ use Inertia\Inertia;
|
||||
// Public visual consultation. No business records or authenticated identities.
|
||||
Route::inertia('proposta/principal', 'preview/principal', ['auth' => ['user' => null]])->name('preview.principal');
|
||||
Route::inertia('proposta/qualificacao', 'preview/qualification', ['auth' => ['user' => null]])->name('preview.qualification');
|
||||
Route::inertia('proposta/admissao', 'preview/admission', ['auth' => ['user' => null]])->name('preview.admission');
|
||||
|
||||
Route::get('consulta', function (Request $request) {
|
||||
$screen = $request->query('ecra', 'overview');
|
||||
|
||||
Reference in New Issue
Block a user