forked from Yara724/api
Validation for prices and objection
This commit is contained in:
@@ -116,9 +116,11 @@ import {
|
||||
type DamageSelectedPartV2,
|
||||
migrateExpertReplyCarPartDamageToUnified,
|
||||
normalizeDamageSelectedParts,
|
||||
partIdentityKey,
|
||||
parseCatalogPartIdInput,
|
||||
partLookupKey,
|
||||
resolvePartCaptureIndex,
|
||||
} from "src/helpers/outer-damage-parts";
|
||||
import { normalizeMoneyAmountString } from "src/utils/unicode-digits";
|
||||
|
||||
import {
|
||||
CAPTURE_PHASE_DAMAGED_PARTY_DOC_KEYS,
|
||||
@@ -5544,19 +5546,20 @@ export class ClaimRequestManagementService {
|
||||
);
|
||||
}
|
||||
|
||||
const pricedPartIdSet =
|
||||
const pricedCatalogIds =
|
||||
activeExpert?.parts?.length ?
|
||||
new Set(
|
||||
activeExpert.parts
|
||||
.filter((p) => p.factorNeeded !== true)
|
||||
.map((p) => String((p as { partId?: string }).partId ?? ""))
|
||||
.filter(Boolean),
|
||||
.map((p) => parseCatalogPartIdInput(p.partId))
|
||||
.filter((id): id is number => id != null),
|
||||
)
|
||||
: new Set<string>();
|
||||
: new Set<number>();
|
||||
|
||||
if (partsIn.length && pricingObjectionEligible) {
|
||||
for (const op of partsIn) {
|
||||
if (!pricedPartIdSet.has(String(op.partId))) {
|
||||
const catalogId = parseCatalogPartIdInput(op.partId);
|
||||
if (catalogId == null || !pricedCatalogIds.has(catalogId)) {
|
||||
throw new BadRequestException(
|
||||
`Only priced repair lines can be disputed (${String(op.partId)} is factor-only or unknown).`,
|
||||
);
|
||||
@@ -5565,19 +5568,28 @@ export class ClaimRequestManagementService {
|
||||
}
|
||||
|
||||
const objectionParts = partsIn.map((p) => ({
|
||||
partId: p.partId,
|
||||
reason: p.reason,
|
||||
partPrice: p.partPrice,
|
||||
partSalary: p.partSalary,
|
||||
partId: parseCatalogPartIdInput(p.partId)!,
|
||||
reason: p.reason?.trim() || undefined,
|
||||
partPrice:
|
||||
p.partPrice != null && String(p.partPrice).trim() !== ""
|
||||
? normalizeMoneyAmountString(String(p.partPrice))
|
||||
: undefined,
|
||||
partSalary:
|
||||
p.partSalary != null && String(p.partSalary).trim() !== ""
|
||||
? normalizeMoneyAmountString(String(p.partSalary))
|
||||
: undefined,
|
||||
typeOfDamage: p.typeOfDamage != null ? String(p.typeOfDamage) : undefined,
|
||||
carPartDamage: p.carPartDamage,
|
||||
side: p.side,
|
||||
carPartDamage: p.carPartDamage?.trim() || undefined,
|
||||
side: p.side?.trim() || undefined,
|
||||
}));
|
||||
|
||||
const newParts = newPartsIn.map((p) => ({
|
||||
partId: p.partId ? String(p.partId) : new Types.ObjectId().toString(),
|
||||
partName: p.partName,
|
||||
side: p.side,
|
||||
partId:
|
||||
p.partId != null && parseCatalogPartIdInput(p.partId) != null
|
||||
? parseCatalogPartIdInput(p.partId)!
|
||||
: new Types.ObjectId().toString(),
|
||||
partName: p.partName.trim(),
|
||||
side: p.side?.trim() || undefined,
|
||||
}));
|
||||
|
||||
const objectionCarType = claimCase.vehicle?.carType as
|
||||
@@ -5588,7 +5600,7 @@ export class ClaimRequestManagementService {
|
||||
objectionCarType,
|
||||
(claimCase.damage as any)?.selectedOuterParts,
|
||||
);
|
||||
const mergedKeys = new Set(mergedNorm.map((p) => partIdentityKey(p)));
|
||||
const mergedKeys = new Set(mergedNorm.map((p) => partLookupKey(p)));
|
||||
for (const np of newParts) {
|
||||
const name = np.partName?.trim();
|
||||
if (!name) continue;
|
||||
@@ -5599,7 +5611,7 @@ export class ClaimRequestManagementService {
|
||||
side,
|
||||
label_fa: name,
|
||||
};
|
||||
const k = partIdentityKey(row);
|
||||
const k = partLookupKey(row);
|
||||
if (!mergedKeys.has(k)) {
|
||||
mergedNorm.push(row);
|
||||
mergedKeys.add(k);
|
||||
@@ -6112,7 +6124,7 @@ export class ClaimRequestManagementService {
|
||||
);
|
||||
const displayParts = [...selectedNormDetails];
|
||||
const seenDetailKeys = new Set(
|
||||
selectedNormDetails.map((p) => partIdentityKey(p)),
|
||||
selectedNormDetails.map((p) => partLookupKey(p)),
|
||||
);
|
||||
for (const rk of resendPartKeys) {
|
||||
const extra =
|
||||
@@ -6124,7 +6136,7 @@ export class ClaimRequestManagementService {
|
||||
label_fa: rk,
|
||||
catalogKey: rk,
|
||||
};
|
||||
const k = partIdentityKey(extra);
|
||||
const k = partLookupKey(extra);
|
||||
if (!seenDetailKeys.has(k)) {
|
||||
displayParts.push(extra);
|
||||
seenDetailKeys.add(k);
|
||||
|
||||
@@ -1,14 +1,21 @@
|
||||
import { ApiPropertyOptional } from "@nestjs/swagger";
|
||||
import { Type } from "class-transformer";
|
||||
import { IsArray, IsOptional, ValidateNested } from "class-validator";
|
||||
import {
|
||||
IsArray,
|
||||
IsOptional,
|
||||
Validate,
|
||||
ValidateNested,
|
||||
} from "class-validator";
|
||||
import { HasObjectionEntriesConstraint } from "src/common/validators/has-objection-entries.validator";
|
||||
import { NewPartDto, UserObjectionPartDto } from "./user-objection.dto";
|
||||
|
||||
/**
|
||||
* V2 user objection body — same shape as v1 {@link import("./user-objection.dto").UserObjectionDto}
|
||||
* V2 user objection body — same shape as v1 {@link UserObjectionDto}
|
||||
* with nested validation enabled for the v2 controller pipeline.
|
||||
*/
|
||||
export class UserObjectionV2Dto {
|
||||
@ApiPropertyOptional({ type: [UserObjectionPartDto] })
|
||||
@Validate(HasObjectionEntriesConstraint)
|
||||
@IsOptional()
|
||||
@IsArray()
|
||||
@ValidateNested({ each: true })
|
||||
@@ -16,6 +23,7 @@ export class UserObjectionV2Dto {
|
||||
objectionParts?: UserObjectionPartDto[];
|
||||
|
||||
@ApiPropertyOptional({ type: [NewPartDto] })
|
||||
@Validate(HasObjectionEntriesConstraint)
|
||||
@IsOptional()
|
||||
@IsArray()
|
||||
@ValidateNested({ each: true })
|
||||
|
||||
@@ -1,47 +1,113 @@
|
||||
import { ApiProperty } from "@nestjs/swagger";
|
||||
import { ApiProperty, ApiPropertyOptional } from "@nestjs/swagger";
|
||||
import { Type } from "class-transformer";
|
||||
import {
|
||||
IsArray,
|
||||
IsEnum,
|
||||
IsInt,
|
||||
IsNotEmpty,
|
||||
IsOptional,
|
||||
IsString,
|
||||
MaxLength,
|
||||
MinLength,
|
||||
Validate,
|
||||
ValidateNested,
|
||||
} from "class-validator";
|
||||
import { HasObjectionEntriesConstraint } from "src/common/validators/has-objection-entries.validator";
|
||||
import { IsRepairLineAmountToman } from "src/common/validators/repair-line-amount-toman.validator";
|
||||
import { ObjectionPartHasContentConstraint } from "src/common/validators/objection-part-content.validator";
|
||||
import { TypeOfDamage } from "src/Types&Enums/claim-request-management/type-of-damage.enum";
|
||||
|
||||
export class UserObjectionPartDto {
|
||||
@ApiProperty()
|
||||
partId: string;
|
||||
@ApiProperty({
|
||||
example: 201,
|
||||
description: "Numeric catalog part id of the priced line being disputed.",
|
||||
})
|
||||
@Validate(ObjectionPartHasContentConstraint)
|
||||
@IsInt()
|
||||
partId: number;
|
||||
|
||||
@ApiProperty({ required: false })
|
||||
@ApiPropertyOptional({
|
||||
description:
|
||||
"Why the owner disagrees (min 3 chars when provided). Required unless partPrice or partSalary is sent.",
|
||||
})
|
||||
@IsOptional()
|
||||
@IsString()
|
||||
@MaxLength(2000)
|
||||
reason?: string;
|
||||
|
||||
@ApiProperty({ required: false })
|
||||
@ApiPropertyOptional({
|
||||
description: "Owner-proposed part price (Toman, integer string).",
|
||||
})
|
||||
@IsOptional()
|
||||
@IsString()
|
||||
@MaxLength(32)
|
||||
@IsRepairLineAmountToman()
|
||||
partPrice?: string;
|
||||
|
||||
@ApiProperty({ required: false })
|
||||
@ApiPropertyOptional({
|
||||
description: "Owner-proposed salary / labor (Toman, integer string).",
|
||||
})
|
||||
@IsOptional()
|
||||
@IsString()
|
||||
@MaxLength(32)
|
||||
@IsRepairLineAmountToman()
|
||||
partSalary?: string;
|
||||
|
||||
@ApiProperty({ required: false })
|
||||
@ApiPropertyOptional({ enum: TypeOfDamage })
|
||||
@IsOptional()
|
||||
@IsEnum(TypeOfDamage)
|
||||
typeOfDamage?: TypeOfDamage;
|
||||
|
||||
@ApiProperty({ required: false })
|
||||
@ApiPropertyOptional()
|
||||
@IsOptional()
|
||||
@IsString()
|
||||
@MaxLength(500)
|
||||
carPartDamage?: string;
|
||||
|
||||
@ApiProperty({ required: false })
|
||||
@ApiPropertyOptional()
|
||||
@IsOptional()
|
||||
@IsString()
|
||||
@MaxLength(64)
|
||||
side?: string;
|
||||
}
|
||||
|
||||
export class NewPartDto {
|
||||
@ApiProperty({ required: false, nullable: true })
|
||||
partId: string | null;
|
||||
@ApiPropertyOptional({
|
||||
nullable: true,
|
||||
description: "Optional catalog id when the new part exists in the outer catalog.",
|
||||
})
|
||||
@IsOptional()
|
||||
@IsInt()
|
||||
partId?: number | null;
|
||||
|
||||
@ApiProperty()
|
||||
@ApiProperty({ example: "سپر جلو" })
|
||||
@IsString()
|
||||
@IsNotEmpty()
|
||||
@MinLength(1)
|
||||
@MaxLength(200)
|
||||
partName: string;
|
||||
|
||||
@ApiProperty({ required: false })
|
||||
@ApiPropertyOptional({ example: "front" })
|
||||
@IsOptional()
|
||||
@IsString()
|
||||
@MaxLength(64)
|
||||
side?: string;
|
||||
}
|
||||
|
||||
export class UserObjectionDto {
|
||||
@ApiProperty({ type: [UserObjectionPartDto], required: false })
|
||||
@ApiPropertyOptional({ type: [UserObjectionPartDto] })
|
||||
@Validate(HasObjectionEntriesConstraint)
|
||||
@IsOptional()
|
||||
@IsArray()
|
||||
@ValidateNested({ each: true })
|
||||
@Type(() => UserObjectionPartDto)
|
||||
objectionParts?: UserObjectionPartDto[];
|
||||
|
||||
@ApiProperty({ type: [NewPartDto], required: false })
|
||||
@ApiPropertyOptional({ type: [NewPartDto] })
|
||||
@Validate(HasObjectionEntriesConstraint)
|
||||
@IsOptional()
|
||||
@IsArray()
|
||||
@ValidateNested({ each: true })
|
||||
@Type(() => NewPartDto)
|
||||
newParts?: NewPartDto[];
|
||||
}
|
||||
|
||||
@@ -141,8 +141,8 @@ export const ClaimExpertReplySchema =
|
||||
/** One line item the user disputes on an expert-priced part */
|
||||
@Schema({ _id: false })
|
||||
export class ClaimUserObjectionPart {
|
||||
@Prop({ type: String, required: true })
|
||||
partId: string;
|
||||
@Prop({ type: Number, required: true })
|
||||
partId: number;
|
||||
|
||||
@Prop({ type: String })
|
||||
reason?: string;
|
||||
@@ -167,8 +167,9 @@ export const ClaimUserObjectionPartSchema =
|
||||
|
||||
@Schema({ _id: false })
|
||||
export class ClaimUserObjectionNewPart {
|
||||
@Prop({ type: String })
|
||||
partId?: string;
|
||||
/** Catalog id when known; otherwise a generated string id for the new line. */
|
||||
@Prop({ type: MongooseSchema.Types.Mixed })
|
||||
partId?: number | string;
|
||||
|
||||
@Prop({ type: String, required: true })
|
||||
partName: string;
|
||||
|
||||
Reference in New Issue
Block a user