mirror of
https://github.com/Tria-plc/edr-platform.git
synced 2026-08-29 14:08:11 +00:00
add company stamp upload functionality for contract signing
- Introduced StampUpload component for uploading company stamp images. - Integrated stamp upload in contract signing modal, supporting PNG and JPG formats. - Implemented validation for file type and size (max 5 MB). - Added visual feedback for drag-and-drop functionality. - Updated contract-related pages to handle duplicate contract alerts and pricing notices. - Enhanced contract expiry management with a nightly sweep service. - Added unit tests for new features and updated existing tests for contract handling.
This commit is contained in:
@@ -0,0 +1,70 @@
|
||||
import { allowedRateUnits, isBulkQuantityUnit } from "./rate-unit.util";
|
||||
|
||||
/**
|
||||
* A bulk rate's weighting unit follows how its commodity is counted: wheat is
|
||||
* weighed (per ton), machinery is counted (per item). Per-wagon is offered
|
||||
* either way.
|
||||
*/
|
||||
describe("allowedRateUnits — bulk unit of measure", () => {
|
||||
it("offers per-ton for a weighed commodity", () => {
|
||||
expect(
|
||||
allowedRateUnits({
|
||||
appliesTo: "BULK",
|
||||
trigger: "ALWAYS",
|
||||
cargoUnitOfMeasure: "PER_TON",
|
||||
}),
|
||||
).toEqual(["PER_TON", "PER_WAGON"]);
|
||||
});
|
||||
|
||||
it("offers per-item for a counted commodity", () => {
|
||||
expect(
|
||||
allowedRateUnits({
|
||||
appliesTo: "BULK",
|
||||
trigger: "ALWAYS",
|
||||
cargoUnitOfMeasure: "PER_ITEM",
|
||||
}),
|
||||
).toEqual(["PER_ITEM", "PER_WAGON"]);
|
||||
});
|
||||
|
||||
it("falls back to per-ton when the rate is not scoped to a commodity", () => {
|
||||
expect(allowedRateUnits({ appliesTo: "BULK", trigger: "ALWAYS" })).toEqual([
|
||||
"PER_TON",
|
||||
"PER_WAGON",
|
||||
]);
|
||||
});
|
||||
|
||||
it("swaps the per-ton slot for counted commodities on every bulk-capable shape", () => {
|
||||
expect(
|
||||
allowedRateUnits({
|
||||
appliesTo: "OTHER",
|
||||
trigger: "CUSTOMS_CLEARANCE",
|
||||
cargoKind: "BULK",
|
||||
cargoUnitOfMeasure: "PER_ITEM",
|
||||
}),
|
||||
).toEqual(["PER_ITEM", "PER_WAGON"]);
|
||||
expect(
|
||||
allowedRateUnits({
|
||||
appliesTo: "INTERCITY",
|
||||
trigger: "ALWAYS",
|
||||
cargoUnitOfMeasure: "PER_ITEM",
|
||||
}),
|
||||
).toEqual(["PER_CONTAINER", "PER_ITEM", "PER_WAGON", "PER_KM"]);
|
||||
});
|
||||
|
||||
it("never offers per-item for overweight, which is always per excess ton", () => {
|
||||
expect(
|
||||
allowedRateUnits({
|
||||
appliesTo: "OTHER",
|
||||
trigger: "OVERWEIGHT",
|
||||
cargoUnitOfMeasure: "PER_TON",
|
||||
}),
|
||||
).toEqual(["PER_TON"]);
|
||||
});
|
||||
|
||||
it("treats per-ton and per-item as the same booking quantity", () => {
|
||||
expect(isBulkQuantityUnit("PER_TON")).toBe(true);
|
||||
expect(isBulkQuantityUnit("PER_ITEM")).toBe(true);
|
||||
expect(isBulkQuantityUnit("PER_WAGON")).toBe(false);
|
||||
expect(isBulkQuantityUnit("FLAT")).toBe(false);
|
||||
});
|
||||
});
|
||||
@@ -1,5 +1,17 @@
|
||||
import type { RateAppliesTo, RateTrigger, RateUnit } from './rate.entity';
|
||||
|
||||
/** How the bulk commodity a rate is scoped to is counted (cargo_types.unit_of_measure). */
|
||||
export type CargoUom = 'PER_TON' | 'PER_ITEM' | null | undefined;
|
||||
|
||||
/**
|
||||
* Units billed against a booking's bulk quantity. That quantity is recorded in
|
||||
* the commodity's own unit — tonnes for a PER_TON commodity, item count for a
|
||||
* PER_ITEM one — so both units scale off the same field and only differ in what
|
||||
* they are called.
|
||||
*/
|
||||
export const isBulkQuantityUnit = (unit: string): boolean =>
|
||||
unit === 'PER_TON' || unit === 'PER_ITEM';
|
||||
|
||||
/**
|
||||
* Which rate units make sense for a given rate shape. The weighting basis is
|
||||
* driven by the *type* of thing being billed — a container leg bills per
|
||||
@@ -8,6 +20,10 @@ import type { RateAppliesTo, RateTrigger, RateUnit } from './rate.entity';
|
||||
* ton. This keeps the rate table dynamic yet non-conflicting: the admin can
|
||||
* only pick a unit the pricing engine knows how to apply.
|
||||
*
|
||||
* A rate scoped to a break-bulk commodity (unit_of_measure = PER_ITEM) offers
|
||||
* PER_ITEM wherever a weighed commodity offers PER_TON — machinery is priced
|
||||
* per unit shipped, wheat per tonne. Per-wagon is offered either way.
|
||||
*
|
||||
* Returned lists are ordered with the most natural/default unit first.
|
||||
*/
|
||||
export function allowedRateUnits(input: {
|
||||
@@ -15,6 +31,19 @@ export function allowedRateUnits(input: {
|
||||
trigger: RateTrigger;
|
||||
/** CUSTOMS_CLEARANCE only: which cargo kind the fee covers. */
|
||||
cargoKind?: 'CONTAINER' | 'BULK' | null;
|
||||
/** Unit of measure of the bulk commodity the rate is scoped to, when any. */
|
||||
cargoUnitOfMeasure?: CargoUom;
|
||||
}): RateUnit[] {
|
||||
const units = unitsForShape(input);
|
||||
return input.cargoUnitOfMeasure === 'PER_ITEM'
|
||||
? units.map((u) => (u === 'PER_TON' ? 'PER_ITEM' : u))
|
||||
: units;
|
||||
}
|
||||
|
||||
function unitsForShape(input: {
|
||||
appliesTo: RateAppliesTo;
|
||||
trigger: RateTrigger;
|
||||
cargoKind?: 'CONTAINER' | 'BULK' | null;
|
||||
}): RateUnit[] {
|
||||
const { appliesTo, trigger } = input;
|
||||
|
||||
@@ -81,6 +110,7 @@ export function isRateUnitAllowed(input: {
|
||||
appliesTo: RateAppliesTo;
|
||||
trigger: RateTrigger;
|
||||
cargoKind?: 'CONTAINER' | 'BULK' | null;
|
||||
cargoUnitOfMeasure?: CargoUom;
|
||||
unit: RateUnit;
|
||||
}): boolean {
|
||||
return allowedRateUnits(input).includes(input.unit);
|
||||
|
||||
@@ -34,6 +34,9 @@ export type RateStatus = typeof RATE_STATUSES[number];
|
||||
export const RATE_UNITS = [
|
||||
'PER_WAGON',
|
||||
'PER_TON',
|
||||
// Break-bulk commodities are counted, not weighed (cargo_types.unit_of_measure
|
||||
// = PER_ITEM) — their rates bill per item off the same booking quantity field.
|
||||
'PER_ITEM',
|
||||
'PER_CONTAINER',
|
||||
'PER_KM',
|
||||
'PER_INVOICE',
|
||||
|
||||
Reference in New Issue
Block a user