264 lines
11 KiB
TypeScript
264 lines
11 KiB
TypeScript
import { Head, Link, useForm } from '@inertiajs/react';
|
|
import {
|
|
FormErrors,
|
|
Panel,
|
|
PageTitle,
|
|
controlClass,
|
|
} from '@/components/procurement';
|
|
import { Button } from '@/components/ui/button';
|
|
import { Input } from '@/components/ui/input';
|
|
|
|
type Detail = {
|
|
id: number;
|
|
revision: number;
|
|
bank_name: string;
|
|
country: string;
|
|
currency: string;
|
|
beneficiary: string;
|
|
account_identifier: string;
|
|
evidence_reference: string;
|
|
reason: string;
|
|
};
|
|
type Props = {
|
|
supplier: { id: number; name: string };
|
|
current: Detail | null;
|
|
currentId: number;
|
|
state: string;
|
|
history: {
|
|
id: number;
|
|
revision: number;
|
|
created_by: number;
|
|
created_at: string;
|
|
}[];
|
|
reviews: {
|
|
id: number;
|
|
supplier_bank_detail_id: number;
|
|
reviewed_by: number;
|
|
decision: string;
|
|
}[];
|
|
canPropose: boolean;
|
|
canReview: boolean;
|
|
};
|
|
export default function Bank({
|
|
supplier,
|
|
current,
|
|
currentId,
|
|
state,
|
|
history,
|
|
reviews,
|
|
canPropose,
|
|
canReview,
|
|
}: Props) {
|
|
const form = useForm({
|
|
previous_id: currentId,
|
|
bank_name: '',
|
|
country: '',
|
|
currency: '',
|
|
beneficiary: '',
|
|
account_identifier: '',
|
|
evidence_reference: '',
|
|
reason: '',
|
|
});
|
|
const review = useForm({
|
|
decision: 'approved',
|
|
reason: '',
|
|
independently_verified: false,
|
|
});
|
|
const labels = {
|
|
bank_name: 'Banco',
|
|
country: 'Código do país (2 letras)',
|
|
currency: 'Moeda da conta (3 letras)',
|
|
beneficiary: 'Titular / beneficiário',
|
|
account_identifier: 'Identificador da conta fornecido pelo banco',
|
|
evidence_reference:
|
|
'Referência da evidência e confirmação independente',
|
|
reason: 'Fundamento da alteração',
|
|
};
|
|
const states: Record<string, string> = {
|
|
missing: 'Sem dados',
|
|
pending: 'Pendente de duas validações',
|
|
approved: 'Duplamente validado',
|
|
rejected: 'Rejeitado',
|
|
blocked: 'Bloqueado por elegibilidade ou conflito',
|
|
};
|
|
return (
|
|
<>
|
|
<Head title="Dados bancários" />
|
|
<div className="mx-auto grid w-full max-w-5xl gap-6 p-6">
|
|
<PageTitle
|
|
eyebrow="SGC · Fornecedores"
|
|
title={`Dados bancários · ${supplier.name}`}
|
|
description="Duas pessoas distintas de Finanças validam cada versão. Não autoriza transferências ou pagamentos."
|
|
/>
|
|
<Link href={`/fornecedores/${supplier.id}`}>
|
|
← Ficha do fornecedor
|
|
</Link>
|
|
<Panel
|
|
title={states[state] ?? state}
|
|
description="A versão mais recente substitui a anterior para efeitos de disponibilidade. A aprovação não confirma a titularidade junto do banco nem valida formatos bancários nacionais."
|
|
>
|
|
{current && (
|
|
<dl className="grid gap-4 text-sm sm:grid-cols-2">
|
|
{Object.entries(labels).map(([key, label]) => (
|
|
<div key={key}>
|
|
<dt className="text-muted-foreground">
|
|
{label}
|
|
</dt>
|
|
<dd className="break-all whitespace-pre-wrap">
|
|
{current[key as keyof typeof labels]}
|
|
</dd>
|
|
</div>
|
|
))}
|
|
</dl>
|
|
)}
|
|
{!current && (
|
|
<p className="text-sm">
|
|
Os identificadores bancários são apresentados apenas
|
|
a Procurement e Finanças. Administração consulta o
|
|
estado e o histórico.
|
|
</p>
|
|
)}
|
|
</Panel>
|
|
{canReview && current && state === 'pending' && (
|
|
<Panel title="Conferência independente">
|
|
<form
|
|
className="grid gap-4"
|
|
onSubmit={(e) => {
|
|
e.preventDefault();
|
|
review.post(
|
|
`/fornecedores/${supplier.id}/dados-bancarios/${current.id}/validar`,
|
|
);
|
|
}}
|
|
>
|
|
<FormErrors errors={review.errors} />
|
|
<label>
|
|
Decisão
|
|
<select
|
|
className={controlClass}
|
|
value={review.data.decision}
|
|
onChange={(e) =>
|
|
review.setData(
|
|
'decision',
|
|
e.target.value,
|
|
)
|
|
}
|
|
>
|
|
<option value="approved">Aprovar</option>
|
|
<option value="rejected">Rejeitar</option>
|
|
</select>
|
|
</label>
|
|
<label>
|
|
Fundamento da conferência
|
|
<textarea
|
|
required
|
|
className="w-full rounded border p-3"
|
|
value={review.data.reason}
|
|
onChange={(e) =>
|
|
review.setData('reason', e.target.value)
|
|
}
|
|
/>
|
|
</label>
|
|
<label className="flex gap-3">
|
|
<input
|
|
required
|
|
type="checkbox"
|
|
checked={review.data.independently_verified}
|
|
onChange={(e) =>
|
|
review.setData(
|
|
'independently_verified',
|
|
e.target.checked,
|
|
)
|
|
}
|
|
/>
|
|
Confirmo que conferi os dados e a evidência de
|
|
forma independente.
|
|
</label>
|
|
<Button disabled={review.processing}>
|
|
Guardar decisão imutável
|
|
</Button>
|
|
</form>
|
|
</Panel>
|
|
)}
|
|
{canPropose && (
|
|
<Panel
|
|
title="Propor nova versão"
|
|
description="Preencha integralmente os dados confirmados. A nova versão exige duas validações novas; a versão anterior permanece no histórico."
|
|
>
|
|
<form
|
|
className="grid gap-4"
|
|
onSubmit={(e) => {
|
|
e.preventDefault();
|
|
form.post(
|
|
`/fornecedores/${supplier.id}/dados-bancarios`,
|
|
{
|
|
onSuccess: (page) => {
|
|
form.reset();
|
|
form.setData(
|
|
'previous_id',
|
|
Number(page.props.currentId),
|
|
);
|
|
},
|
|
},
|
|
);
|
|
}}
|
|
>
|
|
<FormErrors errors={form.errors} />
|
|
{Object.entries(labels).map(([key, label]) => (
|
|
<label key={key}>
|
|
{label}
|
|
<Input
|
|
required
|
|
autoComplete="off"
|
|
value={
|
|
form.data[
|
|
key as keyof typeof labels
|
|
]
|
|
}
|
|
onChange={(e) =>
|
|
form.setData(
|
|
key as keyof typeof labels,
|
|
e.target.value,
|
|
)
|
|
}
|
|
/>
|
|
</label>
|
|
))}
|
|
<Button disabled={form.processing}>
|
|
Enviar para dupla validação
|
|
</Button>
|
|
</form>
|
|
</Panel>
|
|
)}
|
|
<Panel title="Histórico de versões e decisões">
|
|
<div className="space-y-4 text-sm">
|
|
{history.map((h) => (
|
|
<article key={h.id} className="rounded border p-3">
|
|
<p>
|
|
Versão {h.revision} · Autor #{h.created_by}{' '}
|
|
· {h.created_at}
|
|
</p>
|
|
{reviews
|
|
.filter(
|
|
(r) =>
|
|
r.supplier_bank_detail_id === h.id,
|
|
)
|
|
.map((r) => (
|
|
<p key={r.id}>
|
|
{r.decision === 'approved'
|
|
? 'Aprovado'
|
|
: 'Rejeitado'}{' '}
|
|
· Finanças #{r.reviewed_by}
|
|
</p>
|
|
))}
|
|
</article>
|
|
))}
|
|
{history.length === 0 && (
|
|
<p>Sem versões bancárias registadas.</p>
|
|
)}
|
|
</div>
|
|
</Panel>
|
|
</div>
|
|
</>
|
|
);
|
|
}
|