mirror of
https://github.com/Tria-plc/edr-platform.git
synced 2026-08-27 00:52:50 +00:00
Merge pull request #1230 from Tria-plc/freight_feature/usermanagement
feat(wagons): enforce wagon availability limits in transfer requests
This commit is contained in:
@@ -44,9 +44,8 @@ const clampInt = (v: number | string, max: number): number => {
|
||||
|
||||
/**
|
||||
* NumberInput + Slider + All/Half presets, kept in sync. `max` bounds the field
|
||||
* for actions that move real wagons; omit it for a transfer REQUEST, which may
|
||||
* legitimately ask for more than the yard holds today (OCC fulfils it in
|
||||
* instalments) — the slider then just tracks the current value.
|
||||
* to the wagons on hand; omitting it leaves the field unbounded and the slider
|
||||
* simply tracks the current value.
|
||||
*/
|
||||
const QuantityField = ({
|
||||
value,
|
||||
@@ -434,9 +433,14 @@ const WagonYardWorkspaceModal = ({ opened, onClose }: WagonYardWorkspaceModalPro
|
||||
{availableCount} available
|
||||
</Badge>
|
||||
</Group>
|
||||
{/* No max: the request may exceed what the yard holds
|
||||
today — OCC fulfils it in instalments. */}
|
||||
<QuantityField value={transferQty} onChange={setTransferQty} />
|
||||
{/* Capped at the wagons actually available in this yard
|
||||
right now (uncoupled + Available) — a request may not
|
||||
ask for more than the yard can hand over. */}
|
||||
<QuantityField
|
||||
value={transferQty}
|
||||
onChange={setTransferQty}
|
||||
max={availableCount}
|
||||
/>
|
||||
</div>
|
||||
<Select
|
||||
label="Destination yard"
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { Freight } from "@edr/types";
|
||||
import {
|
||||
Alert,
|
||||
Button,
|
||||
@@ -41,6 +42,30 @@ function useTransferOptions(enabled: boolean) {
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Wagons the source yard can hand over right now — AVAILABLE and not coupled to
|
||||
* a built train. Mirrors `countAvailable` on the API, which rejects any request
|
||||
* asking for more than this, so the field must not let one be filed.
|
||||
*/
|
||||
function useAvailableCount(
|
||||
enabled: boolean,
|
||||
fromYardId: string | null,
|
||||
wagonTypeId: string | null,
|
||||
) {
|
||||
const { data: wagons = [] } = useQuery({
|
||||
...api.wagons.list.queryOptions({ input: {} }),
|
||||
enabled: enabled && Boolean(fromYardId && wagonTypeId),
|
||||
});
|
||||
if (!fromYardId || !wagonTypeId) return null;
|
||||
return wagons.filter(
|
||||
(w) =>
|
||||
w.currentYardId === fromYardId &&
|
||||
w.wagonTypeId === wagonTypeId &&
|
||||
w.status === Freight.WagonStatus.Available &&
|
||||
!w.trainId,
|
||||
).length;
|
||||
}
|
||||
|
||||
export interface TransferRequestFormModalProps {
|
||||
opened: boolean;
|
||||
onClose: () => void;
|
||||
@@ -54,9 +79,9 @@ export interface TransferRequestFormModalProps {
|
||||
}
|
||||
|
||||
/**
|
||||
* File a wagon-transfer request. The count is deliberately NOT capped by what
|
||||
* the source yard holds today — OCC fulfils in instalments, so asking for 50
|
||||
* where 20 sit is a normal request.
|
||||
* File a wagon-transfer request. The count is capped by what the source yard
|
||||
* has available right now; the API enforces the same ceiling, so a larger ask
|
||||
* is rejected rather than queued.
|
||||
*/
|
||||
export function TransferRequestFormModal({
|
||||
opened,
|
||||
@@ -83,10 +108,23 @@ export function TransferRequestFormModal({
|
||||
|
||||
const create = useMutation(api.wagonTransferRequests.create.mutationOptions());
|
||||
|
||||
const available = useAvailableCount(opened, fromYardId, wagonTypeId);
|
||||
|
||||
// A prefilled outstanding count (or a count typed before the yard was picked)
|
||||
// can exceed what the chosen source yard actually has — pull it back down so
|
||||
// the field never holds a value the API would reject.
|
||||
useEffect(() => {
|
||||
if (available == null) return;
|
||||
setQuantity((q) => (Number(q) > available ? available : q));
|
||||
}, [available]);
|
||||
|
||||
const sameYard = Boolean(fromYardId && fromYardId === toYardId);
|
||||
const overAvailable = available != null && Number(quantity) > available;
|
||||
const valid =
|
||||
Boolean(fromYardId && toYardId && wagonTypeId && reason.trim()) &&
|
||||
!sameYard &&
|
||||
!overAvailable &&
|
||||
available !== 0 &&
|
||||
Number(quantity) >= 1;
|
||||
|
||||
const submit = async () => {
|
||||
@@ -155,10 +193,25 @@ export function TransferRequestFormModal({
|
||||
/>
|
||||
<NumberInput
|
||||
label="How many"
|
||||
description="Can exceed what the yard holds today — OCC delivers in instalments"
|
||||
description={
|
||||
available == null
|
||||
? "Pick a source yard and wagon type to see what is available"
|
||||
: `${available} wagon(s) available in the source yard`
|
||||
}
|
||||
min={1}
|
||||
max={available ?? undefined}
|
||||
clampBehavior={available == null ? "none" : "strict"}
|
||||
allowNegative={false}
|
||||
value={quantity}
|
||||
onChange={setQuantity}
|
||||
disabled={available === 0}
|
||||
error={
|
||||
available === 0
|
||||
? "This yard has no wagons of that type available"
|
||||
: overAvailable
|
||||
? `Only ${available} available`
|
||||
: undefined
|
||||
}
|
||||
required
|
||||
/>
|
||||
<Textarea
|
||||
|
||||
Reference in New Issue
Block a user