Fix addClient bug

This commit is contained in:
SepehrYahyaee
2026-06-03 14:00:40 +03:30
parent dc14698823
commit d92231e517
3 changed files with 370 additions and 328 deletions

View File

@@ -945,354 +945,375 @@ export class RequestManagementService {
}
async initialFormV2(requestId: string, body: AddPlateDto, user: any) {
const req = await this.blameRequestDbService.findById(requestId);
if (!req) {
throw new NotFoundException("Request not found");
}
try {
const req = await this.blameRequestDbService.findById(requestId);
if (!req) {
throw new NotFoundException("Request not found");
}
if (!req.workflow?.currentStep) {
throw new BadRequestException("Request workflow is not initialized");
}
if (!req.workflow?.currentStep) {
throw new BadRequestException("Request workflow is not initialized");
}
const stepKey = req.workflow.currentStep as WorkflowStep;
if (
stepKey !== WorkflowStep.FIRST_INITIAL_FORM &&
stepKey !== WorkflowStep.SECOND_INITIAL_FORM
) {
throw new BadRequestException(
`Invalid step. Expected FIRST_INITIAL_FORM or SECOND_INITIAL_FORM but got ${stepKey}`,
);
}
// Detect which party from step
const role = this.stepKeyToPartyRole(stepKey);
const idx = this.getPartyIndex(req, role);
if (idx === -1) {
throw new BadRequestException(`${role} party not found on request`);
}
const party = req.parties[idx];
this.assertPartyOwner(
party,
user,
`Only ${role} party can submit this step`,
);
// Set userId for party if not already set (important for second party)
if (!party.person) party.person = {} as any;
if (!party.person.userId && user?.sub) {
party.person.userId = Types.ObjectId.isValid(user.sub)
? new Types.ObjectId(user.sub)
: undefined;
}
// Validation: driver/insurer sameness rules
if (body.driverIsInsurer === false) {
const stepKey = req.workflow.currentStep as WorkflowStep;
if (
body.nationalCodeOfInsurer === body.nationalCodeOfDriver ||
body.insurerLicense === body.driverLicense
stepKey !== WorkflowStep.FIRST_INITIAL_FORM &&
stepKey !== WorkflowStep.SECOND_INITIAL_FORM
) {
throw new BadRequestException(
"Insurer and Driver should be two different persons in this mode.",
`Invalid step. Expected FIRST_INITIAL_FORM or SECOND_INITIAL_FORM but got ${stepKey}`,
);
}
} else if (body.driverIsInsurer === true) {
const sameNat = body.nationalCodeOfInsurer === body.nationalCodeOfDriver;
const sameLic = body.insurerLicense === body.driverLicense;
const sameBirthday =
body.driverBirthday == null ||
String(body.driverBirthday) === String(body.insurerBirthday);
if (!sameNat || !sameLic || !sameBirthday) {
throw new BadRequestException(
"When driverIsInsurer is true, insurer and driver data must be the same.",
);
// Detect which party from step
const role = this.stepKeyToPartyRole(stepKey);
const idx = this.getPartyIndex(req, role);
if (idx === -1) {
throw new BadRequestException(`${role} party not found on request`);
}
}
// ---- External inquiry 1: Tejarat block inquiry ----
let inquiryRaw: any;
let inquiryMapped: any;
try {
const inquiry = await this.sandHubService.getTejaratBlockInquiry({
plate: body.plate,
nationalCodeOfInsurer: body.nationalCodeOfInsurer,
});
inquiryRaw = inquiry.raw;
inquiryMapped = inquiry.mapped;
this.logger.log(
`[TEJARAT] block inquiry raw for request=${req._id}: ${JSON.stringify(inquiryRaw)}`,
const party = req.parties[idx];
this.assertPartyOwner(
party,
user,
`Only ${role} party can submit this step`,
);
this.logger.log(
`[TEJARAT] block inquiry mapped for request=${req._id}: ${JSON.stringify(inquiryMapped)}`,
);
this.recordPartyCaseInquiryStatus(req, "thirdParty", role, true, {
source: "TEJARAT_BLOCK_INQUIRY",
raw: inquiryRaw,
mapped: inquiryMapped,
});
} catch (err: any) {
this.logger.error(
`[TEJARAT] block inquiry failed for request=${req._id}: ${err?.message || err}`,
);
if (err?.response) {
this.logger.error(
`[TEJARAT] block inquiry response for request=${req._id}: status=${
err.response.status
}, data=${JSON.stringify(err.response.data)}`,
);
// Set userId for party if not already set (important for second party)
if (!party.person) party.person = {} as any;
if (!party.person.userId && user?.sub) {
party.person.userId = Types.ObjectId.isValid(user.sub)
? new Types.ObjectId(user.sub)
: undefined;
}
this.recordPartyCaseInquiryStatus(
req,
"thirdParty",
role,
false,
{},
err,
);
await (req as any).save();
throw new HttpException("Inquiry failed", HttpStatus.BAD_REQUEST);
}
if (inquiryMapped?.Error) {
this.logger.warn(
`[TEJARAT] block inquiry error for request=${req._id}: ${JSON.stringify(
inquiryMapped.Error,
)}`,
);
this.recordPartyCaseInquiryStatus(req, "thirdParty", role, false, {
source: "TEJARAT_BLOCK_INQUIRY",
raw: inquiryRaw,
mapped: inquiryMapped,
});
await (req as any).save();
throw new HttpException(
inquiryMapped.Error.Message || "Inquiry returned error",
HttpStatus.BAD_REQUEST,
);
}
// Validation: driver/insurer sameness rules
if (body.driverIsInsurer === false) {
if (
body.nationalCodeOfInsurer === body.nationalCodeOfDriver ||
body.insurerLicense === body.driverLicense
) {
throw new BadRequestException(
"Insurer and Driver should be two different persons in this mode.",
);
}
} else if (body.driverIsInsurer === true) {
const sameNat =
body.nationalCodeOfInsurer === body.nationalCodeOfDriver;
const sameLic = body.insurerLicense === body.driverLicense;
const sameBirthday =
body.driverBirthday == null ||
String(body.driverBirthday) === String(body.insurerBirthday);
if (!sameNat || !sameLic || !sameBirthday) {
throw new BadRequestException(
"When driverIsInsurer is true, insurer and driver data must be the same.",
);
}
}
// ---- External inquiry 2: personal identity check (insurer/driver nationalCode + birthDate) ----
// The form sends the birthday as a Jalali date in any of these shapes:
// - number 13770624 (packed YYYYMMDD)
// - string "1377-06-24" / "1377/06/24" / "13770624"
// We forward it as-is; sandHubService.getPersonalInquiry() handles the
// Jalali → Gregorian conversion required by the external API.
const personalNationalCode =
body.nationalCodeOfInsurer || body.nationalCodeOfDriver;
const personalBirthDate: number | string | null = (body.insurerBirthday ??
body.driverBirthday ??
null) as number | string | null;
if (
!personalNationalCode ||
personalBirthDate === null ||
personalBirthDate === undefined ||
String(personalBirthDate).trim() === ""
) {
throw new BadRequestException(
"Valid nationalCode and birthDate are required for personal inquiry.",
);
}
try {
const personalInquiry = await this.sandHubService.getPersonalInquiry(
personalNationalCode,
personalBirthDate,
);
this.recordPartyCaseInquiryStatus(
req,
"person",
role,
true,
personalInquiry,
);
this.logger.log(
`[SANDHUB] personal inquiry success request=${req._id} nationalCode=${personalNationalCode}: ${JSON.stringify(
personalInquiry,
)}`,
);
} catch (err: any) {
this.logger.error(
`[SANDHUB] personal inquiry failed request=${req._id} nationalCode=${personalNationalCode}: ${err?.message || err}`,
);
this.recordPartyCaseInquiryStatus(req, "person", role, false, {}, err);
await (req as any).save();
throw new HttpException(
"Personal identity inquiry failed",
HttpStatus.BAD_REQUEST,
);
}
// ---- External inquiry 3: driving license check (insurerLicense + nationalCode) ----
// const licenseNationalCode = body.nationalCodeOfInsurer || body.nationalCodeOfDriver;
// const licenseNumber = body.insurerLicense;
// if (!licenseNationalCode || !licenseNumber) {
// throw new BadRequestException(
// "nationalCode and insurerLicense are required for driving license inquiry.",
// );
// }
// try {
// const licenseInquiry = await this.sandHubService.getDrivingLicenseInfo(
// licenseNationalCode,
// licenseNumber,
// );
// this.logger.log(
// `[SANDHUB] license inquiry success request=${req._id} nationalCode=${licenseNationalCode}: ${JSON.stringify(
// licenseInquiry,
// )}`,
// );
// } catch (err: any) {
// this.logger.error(
// `[SANDHUB] license inquiry failed request=${req._id} nationalCode=${licenseNationalCode}: ${err?.message || err}`,
// );
// throw new HttpException(
// "Driving license inquiry failed",
// HttpStatus.BAD_REQUEST,
// );
// }
// Find client by company code
const clientName = inquiryMapped?.CompanyName;
const companyCode = inquiryMapped?.CompanyCode;
const client =
await this.clientService.findClientWithCompanyCode(+companyCode);
if (!client) {
await this.clientService.addClient({
clientName,
clientCode: companyCode,
useExpertMode: "legal",
});
}
// if (!client) {
// throw new HttpException("Client not found", HttpStatus.CONFLICT);
// }
// Persist inquiry/body data into new model
if (!party.person) party.person = {} as any;
const resolvedClientId = (client as any)?._id ?? (client as any)?._doc?._id;
party.person.clientId = resolvedClientId;
party.person.nationalCodeOfInsurer = body.nationalCodeOfInsurer;
party.person.nationalCodeOfDriver = body.nationalCodeOfDriver;
party.person.insurerLicense = body.insurerLicense;
party.person.driverLicense = body.driverLicense;
party.person.driverIsInsurer = body.driverIsInsurer;
party.person.isNewCar = body.isNewCar;
party.person.userNoCertificate = body.userNoCertificate;
party.person.insurerBirthday = body.insurerBirthday;
party.person.driverBirthday =
body.driverBirthday ??
(body.driverIsInsurer ? String(body.insurerBirthday) : null);
if (!party.vehicle) party.vehicle = {} as any;
party.vehicle.isNew = body.isNewCar;
party.vehicle.plateId =
typeof body.plateId === "string"
? body.plateId
: this.plateToPlateIdString(body.plate);
party.vehicle.name = inquiryMapped?.MapTypNam;
party.vehicle.type = `${inquiryMapped?.UsageField} / ${inquiryMapped?.MapUsageName || "-"}`;
party.vehicle.inquiry = {
source: "TEJARAT_BLOCK_INQUIRY",
raw: inquiryRaw,
mapped: inquiryMapped,
};
if (!party.insurance) party.insurance = {} as any;
party.insurance.policyNumber = inquiryMapped?.LastCompanyDocumentNumber;
party.insurance.company = clientName;
party.insurance.financialCeiling = inquiryMapped?.FinancialCvrCptl;
party.insurance.startDate = inquiryMapped?.IssueDate;
party.insurance.endDate = inquiryMapped?.EndDate;
// CAR BODY EXTERNAL API INQUIRY
if (req.type === BlameRequestType.CAR_BODY && role === PartyRole.FIRST) {
let carBodyInfo: any;
// ---- External inquiry 1: Tejarat block inquiry ----
let inquiryRaw: any;
let inquiryMapped: any;
try {
carBodyInfo = await this.sandHubService.getTejaratCarBodyInquiry({
nationalCodeOfInsurer: body.nationalCodeOfInsurer,
const inquiry = await this.sandHubService.getTejaratBlockInquiry({
plate: body.plate,
nationalCodeOfInsurer: body.nationalCodeOfInsurer,
});
this.recordPartyCaseInquiryStatus(req, "carBody", role, true, {
source: "TEJARAT_CAR_BODY_INQUIRY",
raw: carBodyInfo.raw,
mapped: carBodyInfo.mapped,
inquiryRaw = inquiry.raw;
inquiryMapped = inquiry.mapped;
this.logger.log(
`[TEJARAT] block inquiry raw for request=${req._id}: ${JSON.stringify(inquiryRaw)}`,
);
this.logger.log(
`[TEJARAT] block inquiry mapped for request=${req._id}: ${JSON.stringify(inquiryMapped)}`,
);
this.recordPartyCaseInquiryStatus(req, "thirdParty", role, true, {
source: "TEJARAT_BLOCK_INQUIRY",
raw: inquiryRaw,
mapped: inquiryMapped,
});
} catch (err: any) {
this.logger.error(
`[TEJARAT] car body inquiry failed for request=${req._id}: ${err?.message || err}`,
`[TEJARAT] block inquiry failed for request=${req._id}: ${err?.message || err}`,
);
this.recordPartyCaseInquiryStatus(req, "carBody", role, false, {}, err);
if (err?.response) {
this.logger.error(
`[TEJARAT] block inquiry response for request=${req._id}: status=${
err.response.status
}, data=${JSON.stringify(err.response.data)}`,
);
}
this.recordPartyCaseInquiryStatus(
req,
"thirdParty",
role,
false,
{},
err,
);
await (req as any).save();
throw new HttpException("Inquiry failed", HttpStatus.BAD_REQUEST);
}
if (inquiryMapped?.Error) {
this.logger.warn(
`[TEJARAT] block inquiry error for request=${req._id}: ${JSON.stringify(
inquiryMapped.Error,
)}`,
);
this.recordPartyCaseInquiryStatus(req, "thirdParty", role, false, {
source: "TEJARAT_BLOCK_INQUIRY",
raw: inquiryRaw,
mapped: inquiryMapped,
});
await (req as any).save();
throw new HttpException(
"Car body inquiry failed",
inquiryMapped.Error.Message || "Inquiry returned error",
HttpStatus.BAD_REQUEST,
);
}
// Raw + mapped stored under vehicle (same pattern as third-party inquiry)
// ---- External inquiry 2: personal identity check (insurer/driver nationalCode + birthDate) ----
// The form sends the birthday as a Jalali date in any of these shapes:
// - number 13770624 (packed YYYYMMDD)
// - string "1377-06-24" / "1377/06/24" / "13770624"
// We forward it as-is; sandHubService.getPersonalInquiry() handles the
// Jalali → Gregorian conversion required by the external API.
const personalNationalCode =
body.nationalCodeOfInsurer || body.nationalCodeOfDriver;
const personalBirthDate: number | string | null = (body.insurerBirthday ??
body.driverBirthday ??
null) as number | string | null;
if (
!personalNationalCode ||
personalBirthDate === null ||
personalBirthDate === undefined ||
String(personalBirthDate).trim() === ""
) {
throw new BadRequestException(
"Valid nationalCode and birthDate are required for personal inquiry.",
);
}
try {
const personalInquiry = await this.sandHubService.getPersonalInquiry(
personalNationalCode,
personalBirthDate,
);
this.recordPartyCaseInquiryStatus(
req,
"person",
role,
true,
personalInquiry,
);
this.logger.log(
`[SANDHUB] personal inquiry success request=${req._id} nationalCode=${personalNationalCode}: ${JSON.stringify(
personalInquiry,
)}`,
);
} catch (err: any) {
this.logger.error(
`[SANDHUB] personal inquiry failed request=${req._id} nationalCode=${personalNationalCode}: ${err?.message || err}`,
);
this.recordPartyCaseInquiryStatus(req, "person", role, false, {}, err);
await (req as any).save();
throw new HttpException(
"Personal identity inquiry failed",
HttpStatus.BAD_REQUEST,
);
}
// ---- External inquiry 3: driving license check (insurerLicense + nationalCode) ----
// const licenseNationalCode = body.nationalCodeOfInsurer || body.nationalCodeOfDriver;
// const licenseNumber = body.insurerLicense;
// if (!licenseNationalCode || !licenseNumber) {
// throw new BadRequestException(
// "nationalCode and insurerLicense are required for driving license inquiry.",
// );
// }
// try {
// const licenseInquiry = await this.sandHubService.getDrivingLicenseInfo(
// licenseNationalCode,
// licenseNumber,
// );
// this.logger.log(
// `[SANDHUB] license inquiry success request=${req._id} nationalCode=${licenseNationalCode}: ${JSON.stringify(
// licenseInquiry,
// )}`,
// );
// } catch (err: any) {
// this.logger.error(
// `[SANDHUB] license inquiry failed request=${req._id} nationalCode=${licenseNationalCode}: ${err?.message || err}`,
// );
// throw new HttpException(
// "Driving license inquiry failed",
// HttpStatus.BAD_REQUEST,
// );
// }
// Find client by company code
const clientName = inquiryMapped?.CompanyName;
if (!clientName) {
throw new BadRequestException(
`CompanyName missing from inquiry response`,
);
}
const companyCode = inquiryMapped?.CompanyCode;
let client =
await this.clientService.findClientWithCompanyCode(+companyCode);
if (!client) {
await this.clientService.addClient({
clientName: {
persian: clientName,
english: null,
},
clientCode: Number(companyCode),
useExpertMode: "legal",
});
}
// if (!client) {
// throw new HttpException("Client not found", HttpStatus.CONFLICT);
// }
// Persist inquiry/body data into new model
if (!party.person) party.person = {} as any;
const resolvedClientId =
(client as any)?._id ?? (client as any)?._doc?._id;
party.person.clientId = resolvedClientId;
party.person.nationalCodeOfInsurer = body.nationalCodeOfInsurer;
party.person.nationalCodeOfDriver = body.nationalCodeOfDriver;
party.person.insurerLicense = body.insurerLicense;
party.person.driverLicense = body.driverLicense;
party.person.driverIsInsurer = body.driverIsInsurer;
party.person.isNewCar = body.isNewCar;
party.person.userNoCertificate = body.userNoCertificate;
party.person.insurerBirthday = body.insurerBirthday;
party.person.driverBirthday =
body.driverBirthday ??
(body.driverIsInsurer ? String(body.insurerBirthday) : null);
if (!party.vehicle) party.vehicle = {} as any;
party.vehicle.isNew = body.isNewCar;
party.vehicle.plateId =
typeof body.plateId === "string"
? body.plateId
: this.plateToPlateIdString(body.plate);
party.vehicle.name = inquiryMapped?.MapTypNam;
party.vehicle.type = `${inquiryMapped?.UsageField} / ${inquiryMapped?.MapUsageName || "-"}`;
party.vehicle.inquiry = {
...party.vehicle.inquiry, // preserve existing third-party inquiry if present
carBody: {
source: "TEJARAT_CAR_BODY_INQUIRY",
raw: carBodyInfo.raw,
mapped: carBodyInfo.mapped,
source: "TEJARAT_BLOCK_INQUIRY",
raw: inquiryRaw,
mapped: inquiryMapped,
};
if (!party.insurance) party.insurance = {} as any;
party.insurance.policyNumber = inquiryMapped?.LastCompanyDocumentNumber;
party.insurance.company = clientName;
party.insurance.financialCeiling = inquiryMapped?.FinancialCvrCptl;
party.insurance.startDate = inquiryMapped?.IssueDate;
party.insurance.endDate = inquiryMapped?.EndDate;
// CAR BODY EXTERNAL API INQUIRY
if (req.type === BlameRequestType.CAR_BODY && role === PartyRole.FIRST) {
let carBodyInfo: any;
try {
carBodyInfo = await this.sandHubService.getTejaratCarBodyInquiry({
nationalCodeOfInsurer: body.nationalCodeOfInsurer,
plate: body.plate,
});
this.recordPartyCaseInquiryStatus(req, "carBody", role, true, {
source: "TEJARAT_CAR_BODY_INQUIRY",
raw: carBodyInfo.raw,
mapped: carBodyInfo.mapped,
});
} catch (err: any) {
this.logger.error(
`[TEJARAT] car body inquiry failed for request=${req._id}: ${err?.message || err}`,
);
this.recordPartyCaseInquiryStatus(
req,
"carBody",
role,
false,
{},
err,
);
await (req as any).save();
throw new HttpException(
"Car body inquiry failed",
HttpStatus.BAD_REQUEST,
);
}
// Raw + mapped stored under vehicle (same pattern as third-party inquiry)
party.vehicle.inquiry = {
...party.vehicle.inquiry, // preserve existing third-party inquiry if present
carBody: {
source: "TEJARAT_CAR_BODY_INQUIRY",
raw: carBodyInfo.raw,
mapped: carBodyInfo.mapped,
},
};
// Flat key fields extracted alongside third-party insurance fields
const m = carBodyInfo.mapped;
(party.insurance as any).carBodyInsurance = {
policyNumber: m.policyNumber ?? null,
companyId: m.companyId ?? null,
companyName: m.CompanyName ?? null,
insurerName: m.insurerName ?? null,
insurerNationalCode: m.insurerNationalCode ?? null,
ownerNationalCode: m.ownerNationalCode ?? null,
chassisNumber: m.ChassisNumberField ?? null,
vin: m.VinNumberField ?? null,
motorNumber: m.EngineNumberField ?? null,
vehicleGroup: m.vehicleGroupTitle ?? null,
vehicleSystem: m.vehicleSystemTitle ?? null,
startDate: m.StartDate ?? null,
endDate: m.EndDate ?? null,
issueDate: m.IssueDate ?? null,
noLossYearsCount: m.noLossYearsCount ?? null,
lossDocuments: m.lossDocuments ?? [],
hasEndorsement: m.hasEndorsement ?? null,
};
}
// Advance workflow
await this.advanceWorkflowToNext(req, stepKey);
// History
if (!Array.isArray(req.history)) req.history = [];
req.history.push({
type: `${stepKey}_SUBMITTED`,
actor: {
actorId: Types.ObjectId.isValid(user?.sub)
? new Types.ObjectId(user.sub)
: undefined,
actorName: user?.fullName,
actorType: "user",
},
};
metadata: {
role,
companyCode,
companyName: clientName,
},
} as any);
// Flat key fields extracted alongside third-party insurance fields
const m = carBodyInfo.mapped;
(party.insurance as any).carBodyInsurance = {
policyNumber: m.policyNumber ?? null,
companyId: m.companyId ?? null,
companyName: m.CompanyName ?? null,
insurerName: m.insurerName ?? null,
insurerNationalCode: m.insurerNationalCode ?? null,
ownerNationalCode: m.ownerNationalCode ?? null,
chassisNumber: m.ChassisNumberField ?? null,
vin: m.VinNumberField ?? null,
motorNumber: m.EngineNumberField ?? null,
vehicleGroup: m.vehicleGroupTitle ?? null,
vehicleSystem: m.vehicleSystemTitle ?? null,
startDate: m.StartDate ?? null,
endDate: m.EndDate ?? null,
issueDate: m.IssueDate ?? null,
noLossYearsCount: m.noLossYearsCount ?? null,
lossDocuments: m.lossDocuments ?? [],
hasEndorsement: m.hasEndorsement ?? null,
await (req as any).save();
return {
requestId: req._id,
publicId: req.publicId,
workflow: req.workflow,
blameStatus: req.blameStatus,
status: req.status,
};
} catch (error) {
console.log(error);
}
// Advance workflow
await this.advanceWorkflowToNext(req, stepKey);
// History
if (!Array.isArray(req.history)) req.history = [];
req.history.push({
type: `${stepKey}_SUBMITTED`,
actor: {
actorId: Types.ObjectId.isValid(user?.sub)
? new Types.ObjectId(user.sub)
: undefined,
actorName: user?.fullName,
actorType: "user",
},
metadata: {
role,
companyCode,
companyName: clientName,
},
} as any);
await (req as any).save();
return {
requestId: req._id,
publicId: req.publicId,
workflow: req.workflow,
blameStatus: req.blameStatus,
status: req.status,
};
}
async addDetailLocationV2(requestId: string, body: LocationDto, user: any) {