feat: add contract extension request functionality

- Implemented  method in  to allow customers to request an extension for expired contracts.
- Added  component in  for users to initiate extension requests.
- Updated  to include logic for handling extension requests for expired contracts.
- Enhanced  to display extension request options and status.
- Created migration to add  and  columns to the contracts table.
- Added unit tests for contract extension request and handling in .
- Defined DTOs for request and extension in .
- Updated types in  to include new fields related to contract extensions.
This commit is contained in:
marshal
2026-09-06 12:33:40 +00:00
parent f225721e89
commit 75b75e3d4e
32 changed files with 1582 additions and 179 deletions

View File

@@ -171,7 +171,7 @@ describe('wagon-plan.util', () => {
expect(validateContainerPlacements([booking], plan, placements)).toEqual([]);
});
it('rejects 20ft container over max individual weight', () => {
it('rejects a container over its line weight-limit-rule capacity', () => {
const booking = makeContainerBooking('c20', [{ quantity: 2, wagonsRequired: 1, vgmPerUnitTons: 35 }]);
const units = expandBookingContainerUnits([booking]);
const placements = units.map((unit, index) => ({
@@ -182,11 +182,29 @@ describe('wagon-plan.util', () => {
}));
const violations = validate20ftContainerRules(units, placements, {
max20ftContainerWeightTons: 30,
maxContainerWeightTonsByLineId: { [units[0]!.bookingContainerId]: 30 },
max20ftPairWeightDiffTons: 10,
});
expect(violations.some((v) => v.includes('exceeds max 30T'))).toBe(true);
expect(violations.filter((v) => v.includes('weight limit rule capacity of 30T'))).toHaveLength(2);
});
it('applies no per-box ceiling to a line without a weight-limit-rule capacity', () => {
const booking = makeContainerBooking('c20b', [{ quantity: 2, wagonsRequired: 1, vgmPerUnitTons: 35 }]);
const units = expandBookingContainerUnits([booking]);
const placements = units.map((unit, index) => ({
bookingContainerId: unit.bookingContainerId,
unitIndex: unit.unitIndex,
sequenceNo: 1,
containerNumber: `CNTR-${index + 1}`,
}));
const violations = validate20ftContainerRules(units, placements, {
maxContainerWeightTonsByLineId: {},
max20ftPairWeightDiffTons: 10,
});
expect(violations).toEqual([]);
});
it('rejects 20ft pair when weight difference exceeds limit', () => {
@@ -204,7 +222,6 @@ describe('wagon-plan.util', () => {
}));
const violations = validate20ftContainerRules(units, placements, {
max20ftContainerWeightTons: 30,
max20ftPairWeightDiffTons: 10,
});