Files
compras/resources/js/pages/budgets/index.tsx
T

180 lines
7.3 KiB
TypeScript

import { Head, router, useForm, usePage } from '@inertiajs/react';
import {
Panel,
PageTitle,
FormErrors,
controlClass,
} from '@/components/procurement';
import { Button } from '@/components/ui/button';
import { Input } from '@/components/ui/input';
import { money } from '@/types/procurement';
type Budget = {
id: number;
cost_center_id: number;
year: number;
amount_minor: number;
reserved_minor: number;
committed_minor: number;
available_minor: number;
created_by: number;
approved_by: number | null;
reason: string;
includes_tax: boolean;
};
export default function Budgets({
centers,
budgets,
}: {
centers: { id: number; code: string; name: string }[];
budgets: Budget[];
}) {
const { auth } = usePage().props;
const form = useForm({
cost_center_id: '',
year: new Date().getFullYear(),
amount: '',
reason: '',
includes_tax: '',
});
return (
<>
<Head title="Orçamentos" />
<div className="mx-auto grid w-full max-w-6xl gap-6 p-6">
<PageTitle
eyebrow="SGC · Finanças"
title="Orçamentos"
description="Dotação anual por empresa e centro de custo, em MZN. A empresa define a base de IVA."
/>
<p className="rounded-lg border border-amber-300 p-4 text-sm">
Desenvolvimento. O orçamento continua sem validação
operacional. Centros sem dotação continuam no circuito
anterior, sem controlo orçamental. Facturas e pagamentos
ainda não implementados.
</p>
<Panel
title="Preparar dotação"
description="Outra pessoa de Administração ou Finanças deve aprovar. Não existem alterações ou reforços nesta etapa."
>
<form
className="grid gap-4"
onSubmit={(e) => {
e.preventDefault();
form.transform((data) => ({
...data,
includes_tax: data.includes_tax === '1',
}));
form.post('/orcamentos');
}}
>
<FormErrors errors={form.errors} />
<label>
Base de controlo
<select
required
className={controlClass}
value={form.data.includes_tax}
onChange={(e) =>
form.setData('includes_tax', e.target.value)
}
>
<option value="">Decisão da empresa</option>
<option value="1">Com IVA</option>
<option value="0">Sem IVA</option>
</select>
</label>
<label>
Centro de custo
<select
required
className={controlClass}
value={form.data.cost_center_id}
onChange={(e) =>
form.setData(
'cost_center_id',
e.target.value,
)
}
>
<option value="">Seleccionar</option>
{centers.map((c) => (
<option key={c.id} value={c.id}>
{c.code} · {c.name}
</option>
))}
</select>
</label>
<label>
Exercício
<Input
required
type="number"
min="2026"
max="2100"
value={form.data.year}
onChange={(e) =>
form.setData('year', Number(e.target.value))
}
/>
</label>
<label>
Dotação na base seleccionada (MZN)
<Input
required
inputMode="decimal"
value={form.data.amount}
onChange={(e) =>
form.setData('amount', e.target.value)
}
/>
</label>
<label>
Fundamento
<textarea
required
className="w-full rounded border p-3"
value={form.data.reason}
onChange={(e) =>
form.setData('reason', e.target.value)
}
/>
</label>
<Button disabled={form.processing}>
Guardar para aprovação independente
</Button>
</form>
</Panel>
{budgets.map((b) => (
<Panel
key={b.id}
title={`${centers.find((c) => c.id === b.cost_center_id)?.code ?? b.cost_center_id} · ${b.year}`}
>
<p>
{b.approved_by
? 'Aprovado'
: 'Pendente de aprovação'}{' '}
· Dotação: {money(b.amount_minor)} · Base:{' '}
{b.includes_tax ? 'com IVA' : 'sem IVA'}
</p>
<p>
Reservado: {money(b.reserved_minor)} · Comprometido:{' '}
{money(b.committed_minor)} · Disponível:{' '}
{money(b.available_minor)}
</p>
<p className="my-3">{b.reason}</p>
{!b.approved_by && b.created_by !== auth.user.id && (
<Button
onClick={() =>
router.post(`/orcamentos/${b.id}/aprovar`)
}
>
Aprovar dotação
</Button>
)}
</Panel>
))}
</div>
</>
);
}