implement contract cancellation feature and update contract statuses

- Added functionality to cancel contracts, allowing users to provide a reason for cancellation.
- Updated contract statuses to include SUSPENDED and changed CLOSED to COMPLETED.
- Enhanced the UI to reflect the new cancellation option and updated messaging for contract statuses.
- Refactored contract booking actions to accommodate changes in booking logic for ONE_TIME and GENERAL contracts.
- Removed clearance document management from the contract detail page, as it is now handled per booking.
- Introduced a SQL script to reset bookings and train schedules for development purposes.
This commit is contained in:
Marshal
2026-07-28 05:02:58 +00:00
parent 481878e553
commit 2429f6b629
83 changed files with 3083 additions and 3729 deletions

View File

@@ -256,27 +256,40 @@ function round3(value: number): number {
}
/**
* Effective pull limits for a train set with multiple locomotives: the weakest
* locomotive caps the train, so take the minimum pull weight and minimum length
* across all assigned locomotives. Returns null when no locomotives are given.
* Effective limits for a train set, per axis:
*
* - **Pull weight ADDS UP.** Locomotives haul together, so two 1750T units pull
* 3500T. Only CONFIGURED pull weights are summed; a set with none configured
* reports Infinity (no opinion), exactly as before.
* - **Weight tolerance ADDS UP**, following its axis — each locomotive brings its
* own overage allowance, so 2 × 90T gives the set 180T. Unset abstains (0).
* - **Length takes the MINIMUM.** Train length is a siding/loop constraint, not
* a haulage one: coupling a second locomotive does not lengthen the track, so
* the most restrictive locomotive still governs (and its tolerance with it).
*
* Returns null when no locomotives are given.
*/
export function minLocomotiveLimits(
export function combinedLocomotiveLimits(
locomotives: Array<
Pick<LocomotiveLimits, 'maxPullWeightTons' | 'maxTrainLengthMeters'> &
Partial<Pick<LocomotiveLimits, 'overageToleranceTons' | 'overageToleranceMeters'>>
>,
): LocomotiveLimits | null {
if (!locomotives.length) return null;
const configuredPulls = locomotives
.map((l) => num(l.maxPullWeightTons))
.filter((v) => v > 0);
return {
maxPullWeightTons: Math.min(
...locomotives.map((l) => num(l.maxPullWeightTons, Infinity) || Infinity),
),
maxPullWeightTons: configuredPulls.length
? round3(configuredPulls.reduce((sum, v) => sum + v, 0))
: Infinity,
maxTrainLengthMeters: Math.min(
...locomotives.map((l) => num(l.maxTrainLengthMeters, Infinity) || Infinity),
),
// Weakest CONFIGURED tolerance governs the set — a locomotive with no
// tolerance set has no opinion, it does not zero out the others.
overageToleranceTons: minConfigured(locomotives.map((l) => l.overageToleranceTons)),
overageToleranceTons: sumConfigured(locomotives.map((l) => l.overageToleranceTons)),
// Paired with the length axis, so it stays the weakest CONFIGURED value — a
// locomotive with no tolerance set has no opinion, it does not zero the others.
overageToleranceMeters: minConfigured(locomotives.map((l) => l.overageToleranceMeters)),
};
}
@@ -286,10 +299,16 @@ function minConfigured(values: Array<number | null | undefined>): number {
return configured.length ? Math.min(...configured) : 0;
}
function sumConfigured(values: Array<number | null | undefined>): number {
const configured = values.filter((v) => v != null).map((v) => num(v));
return configured.length ? round3(configured.reduce((sum, v) => sum + v, 0)) : 0;
}
/**
* Effective limits for a whole train set: min across its linked locomotives,
* falling back to the legacy single `locomotive` column for sets created
* before multi-loco support. Null when the set has no locomotive at all.
* Effective limits for a whole train set: {@link combinedLocomotiveLimits} over
* its linked locomotives, falling back to the legacy single `locomotive` column
* for sets created before multi-loco support. Null when the set has no
* locomotive at all.
*/
export function trainSetLocomotiveLimits(
trainSet?: {
@@ -306,7 +325,7 @@ export function trainSetLocomotiveLimits(
: trainSet.locomotive
? [trainSet.locomotive]
: [];
return minLocomotiveLimits(pool);
return combinedLocomotiveLimits(pool);
}
/** Per-booking train length from wagon count and freight-specific wagon type length. */