forked from Yara724/api
YARA-899
This commit is contained in:
@@ -327,6 +327,102 @@ export class RequestManagementService {
|
||||
private readonly userAuthService: UserAuthService,
|
||||
) { }
|
||||
|
||||
/**
|
||||
* Reject CAR_BODY submissions whose accident is older than the per-client
|
||||
* window (see `ClientService.getCarBodyAccidentMaxAgeDays`). The check is
|
||||
* scoped to CAR_BODY because THIRD_PARTY files do not record accidentDate
|
||||
* during the description step.
|
||||
*
|
||||
* `clientId` is best-effort — pass `party.person.clientId`,
|
||||
* `firstPartyDetails.firstPartyClient.clientId`, the SandHub-resolved
|
||||
* `client._id`, etc., whatever is available at the call site. When the
|
||||
* value is missing or invalid the helper falls back to the system default
|
||||
* window so the gate is still enforced.
|
||||
*
|
||||
* Throws `BadRequestException` when the accident instant cannot be parsed,
|
||||
* is in the future, or is older than the configured window.
|
||||
*/
|
||||
private async assertCarBodyAccidentWithinWindow(params: {
|
||||
clientId?: string | Types.ObjectId | null;
|
||||
accidentDate: Date | string | null | undefined;
|
||||
accidentTime?: string | null;
|
||||
}): Promise<void> {
|
||||
const { clientId, accidentDate, accidentTime } = params;
|
||||
if (accidentDate == null) {
|
||||
throw new BadRequestException({
|
||||
code: "CAR_BODY_ACCIDENT_DATE_REQUIRED",
|
||||
message:
|
||||
"CAR_BODY files require an accident date to enforce the submission window.",
|
||||
});
|
||||
}
|
||||
|
||||
const instant = this.parseAccidentInstant(
|
||||
accidentDate,
|
||||
accidentTime ?? undefined,
|
||||
);
|
||||
if (!instant || Number.isNaN(instant.getTime())) {
|
||||
throw new BadRequestException({
|
||||
code: "CAR_BODY_ACCIDENT_DATE_INVALID",
|
||||
message: `Invalid accidentDate/accidentTime: "${String(accidentDate)}"${
|
||||
accidentTime ? ` "${accidentTime}"` : ""
|
||||
}.`,
|
||||
});
|
||||
}
|
||||
|
||||
const ageMs = Date.now() - instant.getTime();
|
||||
if (ageMs < 0) {
|
||||
throw new BadRequestException({
|
||||
code: "CAR_BODY_ACCIDENT_DATE_IN_FUTURE",
|
||||
message: "Accident date cannot be in the future.",
|
||||
});
|
||||
}
|
||||
|
||||
const maxAgeDays = await this.clientService.getCarBodyAccidentMaxAgeDays(
|
||||
clientId ?? undefined,
|
||||
);
|
||||
const ageDays = ageMs / (24 * 60 * 60 * 1000);
|
||||
if (ageDays > maxAgeDays) {
|
||||
throw new BadRequestException({
|
||||
code: "CAR_BODY_ACCIDENT_TOO_OLD",
|
||||
message:
|
||||
`CAR_BODY files must be submitted within ${maxAgeDays} day(s) of the accident. ` +
|
||||
`This accident occurred about ${Math.floor(ageDays)} day(s) ago.`,
|
||||
maxAgeDays,
|
||||
ageDays: Math.floor(ageDays),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Best-effort parser that combines a date input with an optional `HH:MM`
|
||||
* time. Accepts a `Date`, an ISO datetime string, or an ISO date-only
|
||||
* string so DTO payloads (which currently send `"YYYY-MM-DD"` + `"HH:MM"`)
|
||||
* can be passed through directly. Returns `null` when the result is not a
|
||||
* finite instant.
|
||||
*/
|
||||
private parseAccidentInstant(
|
||||
date: Date | string,
|
||||
time?: string,
|
||||
): Date | null {
|
||||
if (date instanceof Date) {
|
||||
return Number.isNaN(date.getTime()) ? null : date;
|
||||
}
|
||||
|
||||
const raw = String(date).trim();
|
||||
if (!raw) return null;
|
||||
|
||||
// Already a full datetime — trust it and ignore the separate time field.
|
||||
if (raw.includes("T") || /\s\d{1,2}:\d{2}/.test(raw)) {
|
||||
const d = new Date(raw);
|
||||
return Number.isNaN(d.getTime()) ? null : d;
|
||||
}
|
||||
|
||||
const cleanTime = (time ?? "").trim();
|
||||
const iso = cleanTime ? `${raw}T${cleanTime}` : raw;
|
||||
const d = new Date(iso);
|
||||
return Number.isNaN(d.getTime()) ? null : d;
|
||||
}
|
||||
|
||||
/**
|
||||
* Linked claim cases: block user on damage flow while blame awaits document resend.
|
||||
*/
|
||||
@@ -1226,6 +1322,13 @@ export class RequestManagementService {
|
||||
"CAR_BODY description step requires desc, accidentDate, accidentTime, weatherCondition, roadCondition, and lightCondition.",
|
||||
);
|
||||
}
|
||||
// Gate stale CAR_BODY submissions per the party's client window
|
||||
// (falls back to the system default when no client is attached yet).
|
||||
await this.assertCarBodyAccidentWithinWindow({
|
||||
clientId: party.person?.clientId,
|
||||
accidentDate: body.accidentDate,
|
||||
accidentTime: body.accidentTime,
|
||||
});
|
||||
party.statement.accidentDate = body.accidentDate as any;
|
||||
party.statement.accidentTime = body.accidentTime;
|
||||
(party.statement as any).weatherCondition = body.weatherCondition;
|
||||
@@ -4506,6 +4609,17 @@ export class RequestManagementService {
|
||||
throw new NotFoundException(`Client not found for company: ${clientName}`);
|
||||
}
|
||||
|
||||
// Gate stale CAR_BODY submissions using the resolved client's window
|
||||
// (V2 expert-initiated path always supplies accidentDate/accidentTime
|
||||
// via formData.expertDescription).
|
||||
if (formData?.expertDescription?.accidentDate) {
|
||||
await this.assertCarBodyAccidentWithinWindow({
|
||||
clientId: (client as any)?._id,
|
||||
accidentDate: formData.expertDescription.accidentDate,
|
||||
accidentTime: formData.expertDescription.accidentTime,
|
||||
});
|
||||
}
|
||||
|
||||
const carBodyInfo = await this.mockCarBodyInsuranceInquiry(
|
||||
requestId,
|
||||
firstPartyPlate.plate,
|
||||
|
||||
Reference in New Issue
Block a user