mirror of
https://github.com/Tria-plc/edr-platform.git
synced 2026-08-29 18:48:11 +00:00
feat(procurement): validate acquisition lease fields and enforce bulk-receive capacity
Reject lease start/end and monthly payment on PURCHASE acquisitions (create and update, validated against the resulting record). Add asset_acquisitions.item_name column + migration. Enforce warehouse/yard/zone capacity on bulk receive and apply capacity-counter deltas on save. Adds acquisition-guard spec. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -61,6 +61,7 @@ const clean = <T extends Record<string, unknown>>(obj: T): Partial<T> =>
|
||||
) as Partial<T>;
|
||||
|
||||
const emptyAcquisition = {
|
||||
itemName: "",
|
||||
vehicleId: "",
|
||||
vendorId: "",
|
||||
acquisitionType: "PURCHASE" as AcquisitionType,
|
||||
@@ -239,6 +240,7 @@ export default function ProcurementPage() {
|
||||
<Table striped highlightOnHover>
|
||||
<Table.Thead>
|
||||
<Table.Tr>
|
||||
<Table.Th>Item / Asset</Table.Th>
|
||||
<Table.Th>Vehicle</Table.Th>
|
||||
<Table.Th>Type</Table.Th>
|
||||
<Table.Th>Date</Table.Th>
|
||||
@@ -249,7 +251,7 @@ export default function ProcurementPage() {
|
||||
<Table.Tbody>
|
||||
{loadingAcquisitions ? (
|
||||
<Table.Tr>
|
||||
<Table.Td colSpan={5}>
|
||||
<Table.Td colSpan={6}>
|
||||
<Group justify="center" py="md">
|
||||
<Loader size="sm" />
|
||||
</Group>
|
||||
@@ -257,7 +259,7 @@ export default function ProcurementPage() {
|
||||
</Table.Tr>
|
||||
) : acquisitions.length === 0 ? (
|
||||
<Table.Tr>
|
||||
<Table.Td colSpan={5}>
|
||||
<Table.Td colSpan={6}>
|
||||
<Text c="dimmed" ta="center" py="md">
|
||||
No acquisitions recorded yet.
|
||||
</Text>
|
||||
@@ -266,6 +268,7 @@ export default function ProcurementPage() {
|
||||
) : null}
|
||||
{acquisitions.map((a: AssetAcquisition) => (
|
||||
<Table.Tr key={a.id}>
|
||||
<Table.Td>{a.itemName || "—"}</Table.Td>
|
||||
<Table.Td>{vehicleLabel(a.vehicle, a.vehicleId)}</Table.Td>
|
||||
<Table.Td>
|
||||
<Badge size="sm" color={typeBadgeColor(a.acquisitionType)}>
|
||||
@@ -411,31 +414,51 @@ export default function ProcurementPage() {
|
||||
size="lg"
|
||||
>
|
||||
<Stack gap="md">
|
||||
<TextInput
|
||||
label="Item / Asset"
|
||||
placeholder="What was acquired — e.g. brake pads, tyres, truck 3-15288"
|
||||
value={acqForm.itemName}
|
||||
onChange={(e) => setAcqForm({ ...acqForm, itemName: e.currentTarget.value })}
|
||||
required
|
||||
/>
|
||||
<Select
|
||||
label="Vehicle"
|
||||
placeholder="Select vehicle"
|
||||
label="Related vehicle (optional)"
|
||||
description="Only when the acquisition is a fleet vehicle itself — parts and general procurement stay unlinked."
|
||||
placeholder="Not tied to a vehicle"
|
||||
data={vehicleOptions}
|
||||
value={acqForm.vehicleId}
|
||||
onChange={(val) => setAcqForm({ ...acqForm, vehicleId: val || "" })}
|
||||
searchable
|
||||
clearable
|
||||
/>
|
||||
<Select
|
||||
label="Vendor"
|
||||
placeholder="Select vendor"
|
||||
data={vendorOptions}
|
||||
value={acqForm.vendorId}
|
||||
onChange={(val) => setAcqForm({ ...acqForm, vendorId: val || "" })}
|
||||
searchable
|
||||
clearable
|
||||
/>
|
||||
<Group gap="xs" align="flex-end" wrap="nowrap">
|
||||
<Select
|
||||
style={{ flex: 1 }}
|
||||
label="Vendor"
|
||||
placeholder="Select vendor"
|
||||
data={vendorOptions}
|
||||
value={acqForm.vendorId}
|
||||
onChange={(val) => setAcqForm({ ...acqForm, vendorId: val || "" })}
|
||||
searchable
|
||||
clearable
|
||||
/>
|
||||
<Button variant="light" size="sm" onClick={() => setVendorModalOpen(true)}>
|
||||
Register vendor
|
||||
</Button>
|
||||
</Group>
|
||||
<Select
|
||||
label="Acquisition Type"
|
||||
data={ACQUISITION_TYPES}
|
||||
value={acqForm.acquisitionType}
|
||||
onChange={(val) =>
|
||||
setAcqForm({ ...acqForm, acquisitionType: (val as AcquisitionType) || "PURCHASE" })
|
||||
}
|
||||
onChange={(val) => {
|
||||
const acquisitionType = (val as AcquisitionType) || "PURCHASE";
|
||||
// Lease terms are invalid on a purchase — drop them on switch.
|
||||
setAcqForm(
|
||||
acquisitionType === "PURCHASE"
|
||||
? { ...acqForm, acquisitionType, leaseStart: "", leaseEnd: "", monthlyPayment: undefined }
|
||||
: { ...acqForm, acquisitionType },
|
||||
);
|
||||
}}
|
||||
required
|
||||
/>
|
||||
<TextInput
|
||||
@@ -471,28 +494,32 @@ export default function ProcurementPage() {
|
||||
decimalScale={2}
|
||||
min={0}
|
||||
/>
|
||||
<TextInput
|
||||
label="Lease Start"
|
||||
type="date"
|
||||
value={acqForm.leaseStart}
|
||||
onChange={(e) => setAcqForm({ ...acqForm, leaseStart: e.currentTarget.value })}
|
||||
/>
|
||||
<TextInput
|
||||
label="Lease End"
|
||||
type="date"
|
||||
value={acqForm.leaseEnd}
|
||||
onChange={(e) => setAcqForm({ ...acqForm, leaseEnd: e.currentTarget.value })}
|
||||
/>
|
||||
<NumberInput
|
||||
label="Monthly Payment"
|
||||
placeholder="0.00"
|
||||
value={acqForm.monthlyPayment}
|
||||
onChange={(val) =>
|
||||
setAcqForm({ ...acqForm, monthlyPayment: val as number | undefined })
|
||||
}
|
||||
decimalScale={2}
|
||||
min={0}
|
||||
/>
|
||||
{acqForm.acquisitionType !== "PURCHASE" && (
|
||||
<>
|
||||
<TextInput
|
||||
label="Lease Start"
|
||||
type="date"
|
||||
value={acqForm.leaseStart}
|
||||
onChange={(e) => setAcqForm({ ...acqForm, leaseStart: e.currentTarget.value })}
|
||||
/>
|
||||
<TextInput
|
||||
label="Lease End"
|
||||
type="date"
|
||||
value={acqForm.leaseEnd}
|
||||
onChange={(e) => setAcqForm({ ...acqForm, leaseEnd: e.currentTarget.value })}
|
||||
/>
|
||||
<NumberInput
|
||||
label="Monthly Payment"
|
||||
placeholder="0.00"
|
||||
value={acqForm.monthlyPayment}
|
||||
onChange={(val) =>
|
||||
setAcqForm({ ...acqForm, monthlyPayment: val as number | undefined })
|
||||
}
|
||||
decimalScale={2}
|
||||
min={0}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
<Select
|
||||
label="Status"
|
||||
data={ACQUISITION_STATUSES}
|
||||
@@ -514,7 +541,7 @@ export default function ProcurementPage() {
|
||||
<Button
|
||||
onClick={() => createAcquisition.mutate()}
|
||||
loading={createAcquisition.isPending}
|
||||
disabled={!acqForm.acquisitionDate}
|
||||
disabled={!acqForm.acquisitionDate || acqForm.itemName.trim().length < 2}
|
||||
>
|
||||
Save Acquisition
|
||||
</Button>
|
||||
|
||||
@@ -20,6 +20,7 @@ export interface Vendor {
|
||||
|
||||
export interface AssetAcquisition {
|
||||
id: string;
|
||||
itemName?: string | null;
|
||||
vehicleId?: string | null;
|
||||
vendorId?: string | null;
|
||||
acquisitionType: AcquisitionType;
|
||||
|
||||
Reference in New Issue
Block a user