1
0
forked from Yara724/api

Fixed legacy requestedCounts methods and statistics + claimLink address

This commit is contained in:
SepehrYahyaee
2026-04-27 15:29:44 +03:30
parent 362e02ddc4
commit c2f996cc28
17 changed files with 441 additions and 394 deletions

View File

@@ -67,6 +67,11 @@ import {
import { PublicIdService } from "src/utils/public-id/public-id.service"; import { PublicIdService } from "src/utils/public-id/public-id.service";
import { ImageRequiredModel } from "./entites/schema/image-required.schema"; import { ImageRequiredModel } from "./entites/schema/image-required.schema";
import { DamageExpertDbService } from "src/users/entities/db-service/damage-expert.db.service"; import { DamageExpertDbService } from "src/users/entities/db-service/damage-expert.db.service";
import { ExpertFileActivityDbService } from "src/users/entities/db-service/expert-file-activity.db.service";
import {
ExpertFileActivityType,
ExpertFileKind,
} from "src/users/entities/schema/expert-file-activity.schema";
import { FactorStatus } from "src/Types&Enums/claim-request-management/factor-status.enum"; import { FactorStatus } from "src/Types&Enums/claim-request-management/factor-status.enum";
import { BranchDbService } from "src/client/entities/db-service/branch.db.service"; import { BranchDbService } from "src/client/entities/db-service/branch.db.service";
import { CreationMethod } from "src/request-management/entities/schema/request-management.schema"; import { CreationMethod } from "src/request-management/entities/schema/request-management.schema";
@@ -117,6 +122,7 @@ export class ClaimRequestManagementService {
private readonly claimRequestManagementDbService: ClaimRequestManagementDbService, private readonly claimRequestManagementDbService: ClaimRequestManagementDbService,
private readonly claimFactorsImageDbService: ClaimFactorsImageDbService, private readonly claimFactorsImageDbService: ClaimFactorsImageDbService,
private readonly damageExpertDbService: DamageExpertDbService, private readonly damageExpertDbService: DamageExpertDbService,
private readonly expertFileActivityDbService: ExpertFileActivityDbService,
private readonly branchDbService: BranchDbService, private readonly branchDbService: BranchDbService,
private readonly claimRequiredDocumentDbService: ClaimRequiredDocumentDbService, private readonly claimRequiredDocumentDbService: ClaimRequiredDocumentDbService,
private readonly publicIdService: PublicIdService, private readonly publicIdService: PublicIdService,
@@ -2577,11 +2583,20 @@ export class ClaimRequestManagementService {
}, },
); );
await this.damageExpertDbService.updateStats( if (
actorDetail.sub, Types.ObjectId.isValid(String(actorDetail.sub)) &&
"handled", Types.ObjectId.isValid(String(request.userClientKey)) &&
requestId, Types.ObjectId.isValid(String(requestId))
); ) {
await this.expertFileActivityDbService.recordEvent({
expertId: String(actorDetail.sub),
tenantId: String(request.userClientKey),
fileId: String(requestId),
fileType: ExpertFileKind.CLAIM,
eventType: ExpertFileActivityType.HANDLED,
idempotencyKey: `claim:${requestId}:handled:inperson:${actorDetail.sub}`,
});
}
return updated; return updated;
} }

View File

@@ -2,7 +2,5 @@ import { ExecutionContext, createParamDecorator } from "@nestjs/common";
export const CustomHeader = createParamDecorator( export const CustomHeader = createParamDecorator(
(data, ctx: ExecutionContext) => { (data, ctx: ExecutionContext) => {
console.log(ctx.switchToHttp().getRequest());
console.log(ctx.getType());
}, },
); );

View File

@@ -47,6 +47,11 @@ import { snapshotFromFieldExpert } from "src/helpers/expert-profile-snapshot";
import { ExpertModel } from "src/users/entities/schema/expert.schema"; import { ExpertModel } from "src/users/entities/schema/expert.schema";
import { SmsOrchestrationService } from "src/sms-orchestration/sms-orchestration.service"; import { SmsOrchestrationService } from "src/sms-orchestration/sms-orchestration.service";
import { PartyRole } from "src/request-management/entities/schema/partyRole.enum"; import { PartyRole } from "src/request-management/entities/schema/partyRole.enum";
import { ExpertFileActivityDbService } from "src/users/entities/db-service/expert-file-activity.db.service";
import {
ExpertFileActivityType,
ExpertFileKind,
} from "src/users/entities/schema/expert-file-activity.schema";
interface CheckedRequestEntry { interface CheckedRequestEntry {
CheckedRequest?: { CheckedRequest?: {
@@ -88,6 +93,7 @@ export class ExpertBlameService {
private readonly userSignDbService: UserSignDbService, private readonly userSignDbService: UserSignDbService,
private readonly requestManagementService: RequestManagementService, private readonly requestManagementService: RequestManagementService,
private readonly smsOrchestrationService: SmsOrchestrationService, private readonly smsOrchestrationService: SmsOrchestrationService,
private readonly expertFileActivityDbService: ExpertFileActivityDbService,
) { } ) { }
/** Load immutable profile fields from `expert` for the acting field expert. */ /** Load immutable profile fields from `expert` for the acting field expert. */
@@ -482,21 +488,22 @@ export class ExpertBlameService {
} }
if (!request.expert?.decision && lockedById) { if (!request.expert?.decision && lockedById) {
const rid = new Types.ObjectId(requestId).toString();
try { try {
await this.expertDbService.findOneAndUpdate( await this.recordBlameExpertActivity({
{ _id: new Types.ObjectId(String(lockedById)) }, expertId: String(lockedById),
{ requestId: String(requestId),
$inc: { "requestStats.totalChecked": -1 }, eventType: ExpertFileActivityType.UNCHECKED,
$pull: { countedRequests: rid } as any, tenantId:
}, String(request.parties?.[0]?.person?.clientId ?? "") ||
); String(request.parties?.[1]?.person?.clientId ?? ""),
idempotencyKey: `blame:${requestId}:unchecked:expired:${lockedById}`,
});
this.logger.warn( this.logger.warn(
`Blame case ${requestId} workflow lock expired; unlocked in DB and rolled back checked stat for expert ${lockedById}`, `Blame case ${requestId} workflow lock expired; unlocked in DB and emitted unchecked activity for expert ${lockedById}`,
); );
} catch (e) { } catch (e) {
this.logger.warn( this.logger.warn(
`Expert stat rollback failed after blame lock expiry ${requestId}`, `Expert activity emit failed after blame lock expiry ${requestId}`,
e, e,
); );
} }
@@ -521,13 +528,15 @@ export class ExpertBlameService {
if (shouldDecrementChecked) { if (shouldDecrementChecked) {
updateExp.blameStatus = ReqBlameStatus.UnChecked; updateExp.blameStatus = ReqBlameStatus.UnChecked;
await this.expertDbService.findOneAndUpdate( await this.recordBlameExpertActivity({
{ _id: new Types.ObjectId(r.actorLocked.actorId) }, expertId: String(r.actorLocked.actorId),
{ requestId: String(r._id),
$inc: { "requestStats.totalChecked": -1 }, eventType: ExpertFileActivityType.UNCHECKED,
$pull: { countedRequests: r._id.toString() }, tenantId:
}, String(r.firstPartyDetails?.firstPartyClient?.clientId ?? "") ||
); String(r.secondPartyDetails?.secondPartyClient?.clientId ?? ""),
idempotencyKey: `blame:${String(r._id)}:unchecked:unlock:${String(r.actorLocked.actorId)}`,
});
this.logger.warn( this.logger.warn(
`Request ${r._id} unlocked without reply — expert stats rolled back.`, `Request ${r._id} unlocked without reply — expert stats rolled back.`,
@@ -577,13 +586,15 @@ export class ExpertBlameService {
if (shouldRollbackStats) { if (shouldRollbackStats) {
update.blameStatus = ReqBlameStatus.UnChecked; update.blameStatus = ReqBlameStatus.UnChecked;
await this.expertDbService.findOneAndUpdate( await this.recordBlameExpertActivity({
{ _id: new Types.ObjectId(current.actorLocked.actorId) }, expertId: String(current.actorLocked.actorId),
{ requestId: String(current._id),
$inc: { "requestStats.totalChecked": -1 }, eventType: ExpertFileActivityType.UNCHECKED,
$pull: { countedRequests: current._id.toString() }, tenantId:
}, String(current.firstPartyDetails?.firstPartyClient?.clientId ?? "") ||
); String(current.secondPartyDetails?.secondPartyClient?.clientId ?? ""),
idempotencyKey: `blame:${String(current._id)}:unchecked:auto-unlock:${String(current.actorLocked.actorId)}`,
});
this.logger.warn( this.logger.warn(
`Request ${current._id} auto-unlocked (no reply) — expert stats rolled back.`, `Request ${current._id} auto-unlocked (no reply) — expert stats rolled back.`,
@@ -912,8 +923,16 @@ export class ExpertBlameService {
throw new BadRequestException("Request already locked or invalid status"); throw new BadRequestException("Request already locked or invalid status");
} }
// Update expert stats atomically (use findOneAndUpdate with conditions) // Record expert check activity
await this.updateDamageExpertStats(actorDetail.sub, requestId, "checked"); await this.recordBlameExpertActivity({
expertId: String(actorDetail.sub),
requestId: String(requestId),
eventType: ExpertFileActivityType.CHECKED,
tenantId:
String(updateResult?.firstPartyDetails?.firstPartyClient?.clientId ?? "") ||
String(updateResult?.secondPartyDetails?.secondPartyClient?.clientId ?? ""),
idempotencyKey: `blame:${requestId}:checked:${actorDetail.sub}`,
});
this.scheduleUnlock(updateResult); this.scheduleUnlock(updateResult);
return { _id: requestId, lock: true }; return { _id: requestId, lock: true };
@@ -1002,8 +1021,15 @@ export class ExpertBlameService {
throw new InternalServerErrorException("Failed to lock the request"); throw new InternalServerErrorException("Failed to lock the request");
} }
// Update expert stats (reusing existing helper) await this.recordBlameExpertActivity({
await this.updateDamageExpertStats(actorDetail.sub, requestId, "checked"); expertId: String(actorDetail.sub),
requestId: String(requestId),
eventType: ExpertFileActivityType.CHECKED,
tenantId:
String(request.parties?.[0]?.person?.clientId ?? "") ||
String(request.parties?.[1]?.person?.clientId ?? ""),
idempotencyKey: `blame:${requestId}:checked:${actorDetail.sub}`,
});
if (request.type === BlameRequestType.THIRD_PARTY) { if (request.type === BlameRequestType.THIRD_PARTY) {
const phones = (request.parties || []) const phones = (request.parties || [])
@@ -1177,6 +1203,16 @@ export class ExpertBlameService {
); );
} }
await this.recordBlameExpertActivity({
expertId: String(actorId),
requestId: String(requestId),
eventType: ExpertFileActivityType.UNCHECKED,
tenantId:
String(request.parties?.[0]?.person?.clientId ?? "") ||
String(request.parties?.[1]?.person?.clientId ?? ""),
idempotencyKey: `blame:${requestId}:unchecked:resend:${actorId}`,
});
await this.requestManagementService.applyLinkedClaimsBlameResendStarted( await this.requestManagementService.applyLinkedClaimsBlameResendStarted(
requestId, requestId,
); );
@@ -1379,6 +1415,16 @@ export class ExpertBlameService {
); );
} }
await this.recordBlameExpertActivity({
expertId: String(actorId),
requestId: String(requestId),
eventType: ExpertFileActivityType.HANDLED,
tenantId:
String(request.parties?.[0]?.person?.clientId ?? "") ||
String(request.parties?.[1]?.person?.clientId ?? ""),
idempotencyKey: `blame:${requestId}:handled:${actorId}`,
});
// THIRD_PARTY: both parties must sign; send each their deep link (mutual-agreement uses yara-blame-agreement elsewhere). // THIRD_PARTY: both parties must sign; send each their deep link (mutual-agreement uses yara-blame-agreement elsewhere).
if (request.type === BlameRequestType.THIRD_PARTY) { if (request.type === BlameRequestType.THIRD_PARTY) {
const requestIdToken = String(request._id); const requestIdToken = String(request._id);
@@ -1543,65 +1589,32 @@ export class ExpertBlameService {
} }
} }
private async updateDamageExpertStats( private async recordBlameExpertActivity(args: {
expertId: string, expertId: string;
requestId: string, requestId: string;
type: "checked" | "handled", eventType: ExpertFileActivityType;
) { tenantId?: string;
if (!expertId || !requestId || !["checked", "handled"].includes(type)) { idempotencyKey?: string;
console.warn("Invalid expertId, requestId, or type"); }) {
const tenantId =
args.tenantId && Types.ObjectId.isValid(args.tenantId)
? args.tenantId
: undefined;
if (
!Types.ObjectId.isValid(args.expertId) ||
!Types.ObjectId.isValid(args.requestId) ||
!tenantId
) {
return; return;
} }
await this.expertFileActivityDbService.recordEvent({
const expert = await this.expertDbService.findOne({ expertId: args.expertId,
_id: new Types.ObjectId(expertId), tenantId,
fileId: args.requestId,
fileType: ExpertFileKind.BLAME,
eventType: args.eventType,
idempotencyKey: args.idempotencyKey,
}); });
if (!expert) {
console.warn("Expert not found:", expertId);
return;
}
const requestIdStr = new Types.ObjectId(requestId).toString();
const countedRequestIds =
expert.countedRequests?.map((id) => id.toString()) || [];
if (type === "checked" && countedRequestIds.includes(requestIdStr)) {
console.log(
`Request ${requestIdStr} already checked for expert ${expertId}`,
);
return;
}
const update: any = { $inc: {}, $push: {} };
if (type === "checked") {
update.$inc["requestStats.totalChecked"] = 1;
update.$push["countedRequests"] = requestIdStr;
} else if (type === "handled") {
update.$inc["requestStats.totalHandled"] = 1;
if (countedRequestIds.includes(requestIdStr)) {
update.$inc["requestStats.totalChecked"] = -1;
}
if (!countedRequestIds.includes(requestIdStr)) {
update.$push["countedRequests"] = requestIdStr;
} else {
delete update.$push;
}
}
const updateResult = await this.expertDbService.findOneAndUpdate(
{ _id: new Types.ObjectId(expertId) },
update,
);
if (!updateResult) {
console.warn("Failed to update expert stats for:", expertId);
} else {
console.log(`Expert stats updated (${type}) for expert:`, expertId);
}
} }
async replyRequest(requestId: string, reply: SubmitReplyDto, userId: string) { async replyRequest(requestId: string, reply: SubmitReplyDto, userId: string) {
@@ -1884,11 +1897,15 @@ export class ExpertBlameService {
}, },
); );
await this.expertDbService.updateStats( await this.recordBlameExpertActivity({
actorDetail.sub, expertId: String(actorDetail.sub),
"handled", requestId: String(requestId),
requestId, eventType: ExpertFileActivityType.HANDLED,
); tenantId:
String(request.firstPartyDetails?.firstPartyClient?.clientId ?? "") ||
String(request.secondPartyDetails?.secondPartyClient?.clientId ?? ""),
idempotencyKey: `blame:${requestId}:handled:inperson:${actorDetail.sub}`,
});
return updated; return updated;
} }

View File

@@ -67,6 +67,11 @@ import { resendRequestHasPayload } from "src/helpers/claim-expert-resend";
import { snapshotFromDamageExpert } from "src/helpers/expert-profile-snapshot"; import { snapshotFromDamageExpert } from "src/helpers/expert-profile-snapshot";
import { DamageExpertModel } from "src/users/entities/schema/damage-expert.schema"; import { DamageExpertModel } from "src/users/entities/schema/damage-expert.schema";
import { SmsOrchestrationService } from "src/sms-orchestration/sms-orchestration.service"; import { SmsOrchestrationService } from "src/sms-orchestration/sms-orchestration.service";
import { ExpertFileActivityDbService } from "src/users/entities/db-service/expert-file-activity.db.service";
import {
ExpertFileActivityType,
ExpertFileKind,
} from "src/users/entities/schema/expert-file-activity.schema";
@Injectable() @Injectable()
export class ExpertClaimService { export class ExpertClaimService {
@@ -191,6 +196,7 @@ export class ExpertClaimService {
private readonly branchDbService: BranchDbService, private readonly branchDbService: BranchDbService,
private readonly smsOrchestrationService: SmsOrchestrationService, private readonly smsOrchestrationService: SmsOrchestrationService,
private readonly userDbService: UserDbService, private readonly userDbService: UserDbService,
private readonly expertFileActivityDbService: ExpertFileActivityDbService,
) { } ) { }
/** Load immutable profile fields from `damage-expert` for the acting expert. */ /** Load immutable profile fields from `damage-expert` for the acting expert. */
@@ -199,6 +205,30 @@ export class ExpertClaimService {
return snapshotFromDamageExpert(doc as DamageExpertModel); return snapshotFromDamageExpert(doc as DamageExpertModel);
} }
private async recordClaimExpertActivity(args: {
expertId: string;
tenantId: string;
claimId: string;
eventType: ExpertFileActivityType;
idempotencyKey?: string;
}): Promise<void> {
if (
!Types.ObjectId.isValid(args.expertId) ||
!Types.ObjectId.isValid(args.tenantId) ||
!Types.ObjectId.isValid(args.claimId)
) {
return;
}
await this.expertFileActivityDbService.recordEvent({
expertId: args.expertId,
tenantId: args.tenantId,
fileId: args.claimId,
fileType: ExpertFileKind.CLAIM,
eventType: args.eventType,
idempotencyKey: args.idempotencyKey,
});
}
/** Owner mobile: linked blame party phone when available, else `users.mobile`. */ /** Owner mobile: linked blame party phone when available, else `users.mobile`. */
private async resolveClaimOwnerPhone(claim: any): Promise<string | undefined> { private async resolveClaimOwnerPhone(claim: any): Promise<string | undefined> {
if (!claim?.owner?.userId) return undefined; if (!claim?.owner?.userId) return undefined;
@@ -366,7 +396,6 @@ export class ExpertClaimService {
} }
async getClaimRequestsListForExpert(actor): Promise<ClaimListDtoRs> { async getClaimRequestsListForExpert(actor): Promise<ClaimListDtoRs> {
console.log(actor);
// const requests = await this.claimRequestManagementDbService.findAllByStatus( // const requests = await this.claimRequestManagementDbService.findAllByStatus(
// { // {
// claimStatus: { // claimStatus: {
@@ -545,11 +574,13 @@ export class ExpertClaimService {
}, },
); );
await this.damageExpertDbService.updateStats( await this.recordClaimExpertActivity({
actorDetail.sub, expertId: String(actorDetail.sub),
"checked", tenantId: String(request.userClientKey ?? actorDetail.clientKey ?? ""),
requestId, claimId: String(requestId),
); eventType: ExpertFileActivityType.CHECKED,
idempotencyKey: `claim:${requestId}:checked:${actorDetail.sub}`,
});
this.unlockApi(request, fifteenMinutes.getTime() - Date.now()); this.unlockApi(request, fifteenMinutes.getTime() - Date.now());
@@ -1420,11 +1451,13 @@ export class ExpertClaimService {
); );
if (!isObjection) { if (!isObjection) {
await this.damageExpertDbService.updateStats( await this.recordClaimExpertActivity({
userId.sub, expertId: String(userId.sub),
"handled", tenantId: String(request.userClientKey ?? userId.clientKey ?? ""),
requestId, claimId: String(requestId),
); eventType: ExpertFileActivityType.HANDLED,
idempotencyKey: `claim:${requestId}:handled:${userId.sub}`,
});
} }
return { return {
@@ -1492,7 +1525,6 @@ export class ExpertClaimService {
async streamServiceV2(requestId, query, res, header) { async streamServiceV2(requestId, query, res, header) {
const request = await this.claimCaseDbService.findById(requestId); const request = await this.claimCaseDbService.findById(requestId);
console.log(request)
// const blameCase = await this.blameCaseDbService // const blameCase = await this.blameCaseDbService
return request; return request;
// let videoPath: string = ""; // let videoPath: string = "";
@@ -1942,6 +1974,14 @@ export class ExpertClaimService {
$set, $set,
}); });
await this.recordClaimExpertActivity({
expertId: String(actor.sub),
tenantId: String(actor.clientKey),
claimId: String(claimRequestId),
eventType: ExpertFileActivityType.CHECKED,
idempotencyKey: `claim:${claimRequestId}:checked:${actor.sub}`,
});
const updatedClaim = await this.claimCaseDbService.findById(claimRequestId); const updatedClaim = await this.claimCaseDbService.findById(claimRequestId);
const updatedReply = const updatedReply =
replyField === "damageExpertReplyFinal" replyField === "damageExpertReplyFinal"
@@ -2034,6 +2074,14 @@ export class ExpertClaimService {
}, },
}); });
await this.recordClaimExpertActivity({
expertId: String(actor.sub),
tenantId: String(actor.clientKey),
claimId: String(claimRequestId),
eventType: ExpertFileActivityType.UNCHECKED,
idempotencyKey: `claim:${claimRequestId}:unchecked:resend:${actor.sub}`,
});
const ownerPhoneFactors = await this.resolveClaimOwnerPhone(claim); const ownerPhoneFactors = await this.resolveClaimOwnerPhone(claim);
if (ownerPhoneFactors) { if (ownerPhoneFactors) {
const expertLastName = const expertLastName =
@@ -2076,11 +2124,13 @@ export class ExpertClaimService {
}, },
); );
await this.damageExpertDbService.updateStats( await this.recordClaimExpertActivity({
actorDetail.sub, expertId: String(actorDetail.sub),
"handled", tenantId: String(request.userClientKey ?? actorDetail.clientKey ?? ""),
requestId, claimId: String(requestId),
); eventType: ExpertFileActivityType.HANDLED,
idempotencyKey: `claim:${requestId}:handled:${actorDetail.sub}`,
});
return updated; return updated;
} }
@@ -2459,6 +2509,14 @@ export class ExpertClaimService {
await this.claimCaseDbService.findByIdAndUpdate(claimRequestId, updatePayload); await this.claimCaseDbService.findByIdAndUpdate(claimRequestId, updatePayload);
await this.recordClaimExpertActivity({
expertId: String(actor.sub),
tenantId: String(actor.clientKey),
claimId: String(claimRequestId),
eventType: ExpertFileActivityType.HANDLED,
idempotencyKey: `claim:${claimRequestId}:handled:${actor.sub}`,
});
// Same effective step as WAITING_FOR_INSURER_APPROVAL @ INSURER_REVIEW: owner reviews/signs (yara-signature). // Same effective step as WAITING_FOR_INSURER_APPROVAL @ INSURER_REVIEW: owner reviews/signs (yara-signature).
if (!needsFactorUpload) { if (!needsFactorUpload) {
const ownerPhone = await this.resolveClaimOwnerPhone(claim); const ownerPhone = await this.resolveClaimOwnerPhone(claim);
@@ -2556,6 +2614,14 @@ export class ExpertClaimService {
}, },
}); });
await this.recordClaimExpertActivity({
expertId: String(actor.sub),
tenantId: String(actor.clientKey),
claimId: String(claimRequestId),
eventType: ExpertFileActivityType.HANDLED,
idempotencyKey: `claim:${claimRequestId}:handled:inperson:${actor.sub}`,
});
return { return {
claimRequestId, claimRequestId,
status: claim.status, status: claim.status,
@@ -2992,6 +3058,15 @@ export class ExpertClaimService {
update as any, update as any,
{ new: false }, { new: false },
); );
if (cleared && lockedById && claim.owner?.userClientKey) {
await this.recordClaimExpertActivity({
expertId: String(lockedById),
tenantId: String(claim.owner.userClientKey),
claimId: String(claimRequestId),
eventType: ExpertFileActivityType.UNCHECKED,
idempotencyKey: `claim:${claimRequestId}:unchecked:expired:${lockedById}`,
});
}
return !!cleared; return !!cleared;
} }

View File

@@ -98,7 +98,7 @@ export class ExpertInsurerController {
} }
@ApiBody({ @ApiBody({
description: "Detailed expert rating", description: "Detailed expert rating by insurer",
schema: { schema: {
type: "object", type: "object",
properties: { properties: {
@@ -135,7 +135,7 @@ export class ExpertInsurerController {
minimum: 0, minimum: 0,
maximum: 5, maximum: 5,
example: 4, example: 4,
description: "برای امتیاز دادن به بات", description: "برای امتیاز دادن به عملکرد بات",
}, },
}, },
required: [ required: [

View File

@@ -18,12 +18,15 @@ import { FileRating } from "src/request-management/entities/schema/request-manag
import { CaseStatus } from "src/Types&Enums/blame-request-management/caseStatus.enum"; import { CaseStatus } from "src/Types&Enums/blame-request-management/caseStatus.enum";
import { DamageExpertDbService } from "src/users/entities/db-service/damage-expert.db.service"; import { DamageExpertDbService } from "src/users/entities/db-service/damage-expert.db.service";
import { ExpertDbService } from "src/users/entities/db-service/expert.db.service"; import { ExpertDbService } from "src/users/entities/db-service/expert.db.service";
import { ExpertFileActivityDbService } from "src/users/entities/db-service/expert-file-activity.db.service";
import { ExpertFileActivityType } from "src/users/entities/schema/expert-file-activity.schema";
@Injectable() @Injectable()
export class ExpertInsurerService { export class ExpertInsurerService {
constructor( constructor(
private readonly expertDbService: ExpertDbService, private readonly expertDbService: ExpertDbService,
private readonly damageExpertDbService: DamageExpertDbService, private readonly damageExpertDbService: DamageExpertDbService,
private readonly expertFileActivityDbService: ExpertFileActivityDbService,
private readonly blameRequestDbService: BlameRequestDbService, private readonly blameRequestDbService: BlameRequestDbService,
private readonly claimCaseDbService: ClaimCaseDbService, private readonly claimCaseDbService: ClaimCaseDbService,
private readonly branchDbService: BranchDbService, private readonly branchDbService: BranchDbService,
@@ -37,6 +40,43 @@ export class ExpertInsurerService {
return new Types.ObjectId(raw); return new Types.ObjectId(raw);
} }
private async buildExpertActivityStatsMap(
tenantId: Types.ObjectId,
expertIds: string[],
): Promise<Record<string, { totalHandled: number; totalChecked: number }>> {
const events = await this.expertFileActivityDbService.findByTenantAndExperts(
tenantId,
expertIds,
);
const stateByExpertFile = new Map<string, { checked: boolean; handled: boolean }>();
for (const e of events) {
const key = `${String(e.expertId)}:${String(e.fileId)}`;
const prev = stateByExpertFile.get(key) ?? { checked: false, handled: false };
if (e.eventType === ExpertFileActivityType.CHECKED) {
if (!prev.handled) prev.checked = true;
} else if (e.eventType === ExpertFileActivityType.UNCHECKED) {
prev.checked = false;
} else if (e.eventType === ExpertFileActivityType.HANDLED) {
prev.handled = true;
prev.checked = false;
}
stateByExpertFile.set(key, prev);
}
const result: Record<string, { totalHandled: number; totalChecked: number }> = {};
for (const expertId of expertIds) {
result[expertId] = { totalHandled: 0, totalChecked: 0 };
}
for (const [key, st] of stateByExpertFile.entries()) {
const [expertId] = key.split(":");
if (!result[expertId]) result[expertId] = { totalHandled: 0, totalChecked: 0 };
if (st.handled) result[expertId].totalHandled += 1;
else if (st.checked) result[expertId].totalChecked += 1;
}
return result;
}
private parseObjectId(value: string, label: string): Types.ObjectId { private parseObjectId(value: string, label: string): Types.ObjectId {
if (!Types.ObjectId.isValid(value)) { if (!Types.ObjectId.isValid(value)) {
throw new BadRequestException(`Invalid ${label}`); throw new BadRequestException(`Invalid ${label}`);
@@ -141,6 +181,11 @@ export class ExpertInsurerService {
]); ]);
const allExpertsRaw = [...experts, ...damageExperts]; const allExpertsRaw = [...experts, ...damageExperts];
const expertIds = allExpertsRaw.map((e) => String(e._id));
const expertActivityStatsMap = await this.buildExpertActivityStatsMap(
clientObjectId,
expertIds,
);
const expertTotalRatingsMap: Record<string, number[]> = {}; const expertTotalRatingsMap: Record<string, number[]> = {};
const expertRatingsByCategoryMap: Record<string, Record<string, number[]>> = {}; const expertRatingsByCategoryMap: Record<string, Record<string, number[]>> = {};
@@ -193,7 +238,10 @@ export class ExpertInsurerService {
fullName: `${expert.firstName} ${expert.lastName}`, fullName: `${expert.firstName} ${expert.lastName}`,
role: expert.role, role: expert.role,
type: expert.userType, type: expert.userType,
requestStats: expert.requestStats, requestStats: expertActivityStatsMap[expertIdStr] ?? {
totalHandled: 0,
totalChecked: 0,
},
createdAt: expert.createdAt, createdAt: expert.createdAt,
overallAverageRating, overallAverageRating,
averageRatingsByCategory, averageRatingsByCategory,

View File

@@ -47,10 +47,6 @@ export class ProfileController {
@UseGuards(GlobalGuard) @UseGuards(GlobalGuard)
@ApiBody({ type: AddPlateProfileDto }) @ApiBody({ type: AddPlateProfileDto })
async addPlate(@CurrentUser() user, @Body() body: AddPlateDto) { async addPlate(@CurrentUser() user, @Body() body: AddPlateDto) {
console.log(
"🚀 ~ file: profile.controller.ts:50 ~ ProfileController ~ addPlate ~ user:",
user,
);
return await this.plateService.addPlate(user.sub, body); return await this.plateService.addPlate(user.sub, body);
} }

View File

@@ -71,6 +71,11 @@ import { WorkflowStepDbService } from "src/workflow-step-management/entities/db-
import { WorkflowStepModel } from "src/workflow-step-management/entities/schema/workflow-step.schema"; import { WorkflowStepModel } from "src/workflow-step-management/entities/schema/workflow-step.schema";
import { BlameStatus } from "src/Types&Enums/blame-request-management/blameStatus.enum"; import { BlameStatus } from "src/Types&Enums/blame-request-management/blameStatus.enum";
import { CaseStatus } from "src/Types&Enums/blame-request-management/caseStatus.enum"; import { CaseStatus } from "src/Types&Enums/blame-request-management/caseStatus.enum";
import { ExpertFileActivityDbService } from "src/users/entities/db-service/expert-file-activity.db.service";
import {
ExpertFileActivityType,
ExpertFileKind,
} from "src/users/entities/schema/expert-file-activity.schema";
import { UploadContext } from "./entities/schema/blame-voice.schema"; import { UploadContext } from "./entities/schema/blame-voice.schema";
import { HashService } from "src/utils/hash/hash.service"; import { HashService } from "src/utils/hash/hash.service";
import { UserAuthService } from "src/auth/auth-services/user.auth.service"; import { UserAuthService } from "src/auth/auth-services/user.auth.service";
@@ -309,6 +314,7 @@ export class RequestManagementService {
private readonly userSign: UserSignDbService, private readonly userSign: UserSignDbService,
private readonly smsOrchestrationService: SmsOrchestrationService, private readonly smsOrchestrationService: SmsOrchestrationService,
private readonly expertDbService: ExpertDbService, private readonly expertDbService: ExpertDbService,
private readonly expertFileActivityDbService: ExpertFileActivityDbService,
private readonly autoCloseRequestService: AutoCloseRequestService, private readonly autoCloseRequestService: AutoCloseRequestService,
private readonly claimRequestManagementDbService: ClaimRequestManagementDbService, private readonly claimRequestManagementDbService: ClaimRequestManagementDbService,
private readonly claimCaseDbService: ClaimCaseDbService, private readonly claimCaseDbService: ClaimCaseDbService,
@@ -2370,9 +2376,6 @@ export class RequestManagementService {
} }
if (p.firstPartyDetails.firstPartyPhoneNumber == phoneNumber) { if (p.firstPartyDetails.firstPartyPhoneNumber == phoneNumber) {
console.log(
new Date(p.createdAt).toLocaleTimeString("fa-IR").split(","),
);
res.listOfFirstParty.push({ res.listOfFirstParty.push({
id: p.id, id: p.id,
blameStatus: p.blameStatus, blameStatus: p.blameStatus,
@@ -2746,76 +2749,28 @@ export class RequestManagementService {
return null; return null;
} }
private async updateDamageExpertStats( private async recordBlameExpertActivity(args: {
expertId: string, expertId: string;
requestId: string, tenantId: string;
type: "checked" | "handled", requestId: string;
) { eventType: ExpertFileActivityType;
if (!expertId || !requestId || !["checked", "handled"].includes(type)) { idempotencyKey?: string;
console.warn("Invalid expertId, requestId, or type"); }): Promise<void> {
if (
!Types.ObjectId.isValid(args.expertId) ||
!Types.ObjectId.isValid(args.tenantId) ||
!Types.ObjectId.isValid(args.requestId)
) {
return; return;
} }
await this.expertFileActivityDbService.recordEvent({
// Validate that both expertId and requestId are valid ObjectId strings expertId: args.expertId,
if (!Types.ObjectId.isValid(expertId)) { tenantId: args.tenantId,
console.warn("Invalid ObjectId format for expertId:", expertId); fileId: args.requestId,
return; fileType: ExpertFileKind.BLAME,
} eventType: args.eventType,
idempotencyKey: args.idempotencyKey,
if (!Types.ObjectId.isValid(requestId)) {
console.warn("Invalid ObjectId format for requestId:", requestId);
return;
}
const expert = await this.expertDbService.findOne({
_id: new Types.ObjectId(expertId),
}); });
if (!expert) {
console.warn("Expert not found:", expertId);
return;
}
const requestIdStr = new Types.ObjectId(requestId).toString();
const countedRequestIds =
expert.countedRequests?.map((id) => id.toString()) || [];
if (type === "checked" && countedRequestIds.includes(requestIdStr)) {
console.log(
`Request ${requestIdStr} already checked for expert ${expertId}`,
);
return;
}
const update: any = { $inc: {}, $push: {} };
if (type === "checked") {
update.$inc["requestStats.totalChecked"] = 1;
update.$push["countedRequests"] = requestIdStr;
} else if (type === "handled") {
update.$inc["requestStats.totalHandled"] = 1;
if (countedRequestIds.includes(requestIdStr)) {
update.$inc["requestStats.totalChecked"] = -1;
}
if (!countedRequestIds.includes(requestIdStr)) {
update.$push["countedRequests"] = requestIdStr;
} else {
delete update.$push;
}
}
const updateResult = await this.expertDbService.findOneAndUpdate(
{ _id: new Types.ObjectId(expertId) },
update,
);
if (!updateResult) {
console.warn("Failed to update expert stats for:", expertId);
} else {
console.log(`Expert stats updated (${type}) for expert:`, expertId);
}
} }
private async handleDisagreement( private async handleDisagreement(
@@ -3006,11 +2961,17 @@ export class RequestManagementService {
request._id.toString() : request._id.toString() :
String(request._id); String(request._id);
await this.updateDamageExpertStats( const tenantIdStr =
actorIdStr, request.firstPartyDetails?.firstPartyClient?.clientId?.toString?.() ||
requestIdStr, request.secondPartyDetails?.secondPartyClient?.clientId?.toString?.() ||
"handled", "";
); await this.recordBlameExpertActivity({
expertId: actorIdStr,
tenantId: tenantIdStr,
requestId: requestIdStr,
eventType: ExpertFileActivityType.HANDLED,
idempotencyKey: `blame:${requestIdStr}:handled:${actorIdStr}`,
});
} }
await this.requestManagementDbService.findByIdAndUpdate(request._id, { await this.requestManagementDbService.findByIdAndUpdate(request._id, {
$set: { isHandledStatsUpdated: true }, $set: { isHandledStatsUpdated: true },

View File

@@ -47,7 +47,7 @@ export class SmsOrchestrationService implements OnModuleInit {
} }
buildClaimLink(claimRequestId: string): string { buildClaimLink(claimRequestId: string): string {
return `${process.env.URL}/claim?token=${claimRequestId}`; return `${process.env.URL}/caseClaim?token=${claimRequestId}`;
} }
async sendInviteLink(phoneNumber: string, link: string): Promise<boolean> { async sendInviteLink(phoneNumber: string, link: string): Promise<boolean> {

View File

@@ -46,64 +46,6 @@ export class DamageExpertDbService {
.exec(); .exec();
} }
async updateStats(
expertId: string,
type: "checked" | "handled",
requestId: string,
) {
if (!expertId || !requestId || !["checked", "handled"].includes(type)) {
console.warn("Invalid expertId, requestId, or type");
return;
}
const expert = await this.damageExpertModel.findById(expertId);
if (!expert) {
console.warn("Expert not found:", expertId);
return;
}
const requestIdStr = new Types.ObjectId(requestId).toString();
const countedRequests =
expert.countedRequests?.map((r) => r.toString()) || [];
if (type === "checked" && countedRequests.includes(requestIdStr)) {
console.log(
`Request ${requestIdStr} already checked for expert ${expertId}`,
);
return;
}
const update: any = { $inc: {}, $push: {} };
if (type === "checked") {
update.$inc["requestStats.totalChecked"] = 1;
update.$push["countedRequests"] = requestIdStr;
} else if (type === "handled") {
update.$inc["requestStats.totalHandled"] = 1;
if (countedRequests.includes(requestIdStr)) {
update.$inc["requestStats.totalChecked"] = -1;
}
if (!countedRequests.includes(requestIdStr)) {
update.$push["countedRequests"] = requestIdStr;
} else {
delete update.$push;
}
}
const result = await this.damageExpertModel.findByIdAndUpdate(
expertId,
update,
);
if (!result) {
console.warn("Failed to update stats for expert:", expertId);
} else {
console.log(`Stats updated (${type}) for expert:`, expertId);
}
}
async updateOne(filter: any, update: any) { async updateOne(filter: any, update: any) {
return this.damageExpertModel.updateOne(filter, update); return this.damageExpertModel.updateOne(filter, update);
} }

View File

@@ -0,0 +1,73 @@
import { Injectable } from "@nestjs/common";
import { InjectModel } from "@nestjs/mongoose";
import { Model, Types } from "mongoose";
import {
ExpertFileActivity,
ExpertFileActivityDocument,
ExpertFileActivityType,
ExpertFileKind,
} from "../schema/expert-file-activity.schema";
@Injectable()
export class ExpertFileActivityDbService {
constructor(
@InjectModel(ExpertFileActivity.name)
private readonly activityModel: Model<ExpertFileActivityDocument>,
) {}
async recordEvent(args: {
expertId: string | Types.ObjectId;
tenantId: string | Types.ObjectId;
fileId: string | Types.ObjectId;
fileType: ExpertFileKind;
eventType: ExpertFileActivityType;
occurredAt?: Date;
idempotencyKey?: string;
}): Promise<void> {
const payload = {
expertId: new Types.ObjectId(String(args.expertId)),
tenantId: new Types.ObjectId(String(args.tenantId)),
fileId: new Types.ObjectId(String(args.fileId)),
fileType: args.fileType,
eventType: args.eventType,
occurredAt: args.occurredAt ?? new Date(),
...(args.idempotencyKey ? { idempotencyKey: args.idempotencyKey } : {}),
};
if (args.idempotencyKey) {
await this.activityModel.updateOne(
{ idempotencyKey: args.idempotencyKey },
{ $setOnInsert: payload },
{ upsert: true },
);
return;
}
await this.activityModel.create(payload);
}
async findByTenantAndExperts(
tenantId: string | Types.ObjectId,
expertIds: Array<string | Types.ObjectId>,
): Promise<
Array<{
expertId: Types.ObjectId;
fileId: Types.ObjectId;
eventType: ExpertFileActivityType;
occurredAt: Date;
}>
> {
if (expertIds.length === 0) return [];
const ids = expertIds.map((id) => new Types.ObjectId(String(id)));
return this.activityModel
.find(
{
tenantId: new Types.ObjectId(String(tenantId)),
expertId: { $in: ids },
},
{ expertId: 1, fileId: 1, eventType: 1, occurredAt: 1 },
)
.sort({ occurredAt: 1, _id: 1 })
.lean();
}
}

View File

@@ -33,50 +33,6 @@ export class ExpertDbService {
return await this.expertModel.find(filter).lean(); return await this.expertModel.find(filter).lean();
} }
async updateStats(
expertId: string,
type: "checked" | "handled",
requestId: string,
) {
if (!expertId || !requestId || !["checked", "handled"].includes(type)) {
console.warn("Invalid expertId, requestId, or type");
return;
}
const expert = await this.expertModel.findById(expertId);
if (!expert) {
console.warn("Expert not found:", expertId);
return;
}
const requestIdStr = new Types.ObjectId(requestId).toString();
const counted = (expert.countedRequests || []).map((r) => r.toString());
if (counted.includes(requestIdStr)) {
console.log("Request already counted for expert:", requestIdStr);
return;
}
const updateField =
type === "handled"
? {
"requestStats.totalHandled": 1,
"requestStats.totalChecked": -1,
}
: { "requestStats.totalChecked": 1 };
const result = await this.expertModel.findByIdAndUpdate(expertId, {
$inc: updateField,
$push: { countedRequests: requestIdStr },
});
if (!result) {
console.warn("Failed to update expert stats for:", expertId);
} else {
console.log("Updated stats for expert:", expertId);
}
}
async updateOne(filter: any, update: any) { async updateOne(filter: any, update: any) {
return this.expertModel.updateOne(filter, update); return this.expertModel.updateOne(filter, update);
} }

View File

@@ -8,16 +8,6 @@ import {
import { RoleEnum } from "src/Types&Enums/role.enum"; import { RoleEnum } from "src/Types&Enums/role.enum";
import { UserType } from "src/Types&Enums/userType.enum"; import { UserType } from "src/Types&Enums/userType.enum";
@Schema({ _id: false })
export class RequestStats {
@Prop({ default: 0 })
totalHandled: number;
@Prop({ default: 0 })
totalChecked: number;
}
const RequestStatsSchema = SchemaFactory.createForClass(RequestStats);
@Schema({ _id: false }) @Schema({ _id: false })
export class PersonalInfo { export class PersonalInfo {
@@ -166,19 +156,6 @@ export class DamageExpertModel {
@Prop({ type: "string" }) @Prop({ type: "string" })
city: string; city: string;
@Prop({ type: RequestStatsSchema, default: () => ({}) })
requestStats: RequestStats;
@Prop({
type: [{ type: String }],
default: [],
validate: {
validator: (arr: any[]) => arr.every((val) => typeof val === "string"),
message: "All countedRequests must be strings",
},
})
countedRequests?: string[];
createdAt: Date; createdAt: Date;
} }
@@ -190,11 +167,4 @@ DamageExpertDbSchema.pre("save", function (next) {
this.username = this.email; this.username = this.email;
} }
next(); next();
}); });
DamageExpertDbSchema.pre("save", function (next) {
if (!this.requestStats) {
this.requestStats = { totalChecked: 0, totalHandled: 0 };
}
next();
});

View File

@@ -0,0 +1,52 @@
import { Prop, Schema, SchemaFactory } from "@nestjs/mongoose";
import { HydratedDocument, Types } from "mongoose";
export type ExpertFileActivityDocument = HydratedDocument<ExpertFileActivity>;
export enum ExpertFileActivityType {
CHECKED = "checked",
HANDLED = "handled",
UNCHECKED = "unchecked",
}
export enum ExpertFileKind {
BLAME = "blame",
CLAIM = "claim",
}
@Schema({ collection: "expertFileActivities", timestamps: true })
export class ExpertFileActivity {
@Prop({ type: Types.ObjectId, required: true, index: true })
expertId: Types.ObjectId;
@Prop({ type: Types.ObjectId, required: true, index: true })
tenantId: Types.ObjectId;
@Prop({ type: Types.ObjectId, required: true, index: true })
fileId: Types.ObjectId;
@Prop({ type: String, enum: ExpertFileKind, required: true, index: true })
fileType: ExpertFileKind;
@Prop({
type: String,
enum: ExpertFileActivityType,
required: true,
index: true,
})
eventType: ExpertFileActivityType;
@Prop({ type: Date, default: Date.now, index: true })
occurredAt: Date;
@Prop({ type: String, required: false, index: true })
idempotencyKey?: string;
}
export const ExpertFileActivitySchema =
SchemaFactory.createForClass(ExpertFileActivity);
ExpertFileActivitySchema.index(
{ idempotencyKey: 1 },
{ unique: true, partialFilterExpression: { idempotencyKey: { $exists: true } } },
);

View File

@@ -3,17 +3,6 @@ import { Degrees } from "src/Types&Enums/degrees.enum";
import { RoleEnum } from "src/Types&Enums/role.enum"; import { RoleEnum } from "src/Types&Enums/role.enum";
import { UserType } from "src/Types&Enums/userType.enum"; import { UserType } from "src/Types&Enums/userType.enum";
@Schema({ _id: false })
export class RequestStats {
@Prop({ default: 0 })
totalHandled: number;
@Prop({ default: 0 })
totalChecked: number;
}
const RequestStatsSchema = SchemaFactory.createForClass(RequestStats);
@Schema({ collection: "expert", versionKey: false, timestamps: true }) @Schema({ collection: "expert", versionKey: false, timestamps: true })
export class ExpertModel { export class ExpertModel {
@Prop({}) @Prop({})
@@ -79,27 +68,8 @@ export class ExpertModel {
@Prop({ type: "string" }) @Prop({ type: "string" })
otp: string; otp: string;
@Prop({ type: RequestStatsSchema, default: () => ({}) })
requestStats: RequestStats;
@Prop({
type: [{ type: String }],
default: [],
validate: {
validator: (arr: any[]) => arr.every((val) => typeof val === "string"),
message: "All countedRequests must be strings",
},
})
countedRequests?: string[];
createdAt: Date; createdAt: Date;
} }
export const ExpertDbSchema = SchemaFactory.createForClass(ExpertModel); export const ExpertDbSchema = SchemaFactory.createForClass(ExpertModel);
ExpertDbSchema.pre("save", function (next) {
if (!this.requestStats) {
this.requestStats = { totalChecked: 0, totalHandled: 0 };
}
next();
});

View File

@@ -2,17 +2,6 @@ import { Prop, Schema, SchemaFactory } from "@nestjs/mongoose";
import { Types } from "mongoose"; import { Types } from "mongoose";
import { RoleEnum } from "src/Types&Enums/role.enum"; import { RoleEnum } from "src/Types&Enums/role.enum";
@Schema({ _id: false })
export class RequestStats {
@Prop({ default: 0 })
totalHandled: number;
@Prop({ default: 0 })
totalChecked: number;
}
const RequestStatsSchema = SchemaFactory.createForClass(RequestStats);
@Schema({ @Schema({
collection: "field-expert", collection: "field-expert",
versionKey: false, versionKey: false,
@@ -48,31 +37,8 @@ export class FieldExpertModel {
@Prop({ type: "string", default: "" }) @Prop({ type: "string", default: "" })
otp: string; otp: string;
@Prop({ type: RequestStatsSchema, default: () => ({}) })
requestStats: RequestStats;
@Prop({
type: [{ type: String }],
default: [],
validate: {
validator: (arr: any[]) => arr.every((val) => typeof val === "string"),
message: "All countedRequests must be strings",
},
})
countedRequests?: string[];
createdAt: Date; createdAt: Date;
} }
export const FieldExpertDbSchema = export const FieldExpertDbSchema =
SchemaFactory.createForClass(FieldExpertModel); SchemaFactory.createForClass(FieldExpertModel);
FieldExpertDbSchema.pre("save", function (next) {
if (!this.username) {
this.username = this.email;
}
if (!this.requestStats) {
this.requestStats = { totalChecked: 0, totalHandled: 0 };
}
next();
});

View File

@@ -9,10 +9,15 @@ import { FieldExpertDbService } from "./entities/db-service/field-expert.db.serv
import { InsurerExpertDbService } from "./entities/db-service/insurer-expert.db.service"; import { InsurerExpertDbService } from "./entities/db-service/insurer-expert.db.service";
import { RegistrarDbService } from "./entities/db-service/registrar.db.service"; import { RegistrarDbService } from "./entities/db-service/registrar.db.service";
import { UserDbService } from "./entities/db-service/user.db.service"; import { UserDbService } from "./entities/db-service/user.db.service";
import { ExpertFileActivityDbService } from "./entities/db-service/expert-file-activity.db.service";
import { import {
DamageExpertDbSchema, DamageExpertDbSchema,
DamageExpertModel, DamageExpertModel,
} from "./entities/schema/damage-expert.schema"; } from "./entities/schema/damage-expert.schema";
import {
ExpertFileActivity,
ExpertFileActivitySchema,
} from "./entities/schema/expert-file-activity.schema";
import { ExpertDbSchema } from "./entities/schema/expert.schema"; import { ExpertDbSchema } from "./entities/schema/expert.schema";
import { import {
FieldExpertDbSchema, FieldExpertDbSchema,
@@ -37,6 +42,7 @@ import { UserModel, UserDbSchema } from "./entities/schema/user.schema";
{ name: FieldExpertModel.name, schema: FieldExpertDbSchema }, { name: FieldExpertModel.name, schema: FieldExpertDbSchema },
{ name: InsurerExpertModel.name, schema: InsurerExpertDbSchema }, { name: InsurerExpertModel.name, schema: InsurerExpertDbSchema },
{ name: RegistrarModel.name, schema: RegistrarDbSchema }, { name: RegistrarModel.name, schema: RegistrarDbSchema },
{ name: ExpertFileActivity.name, schema: ExpertFileActivitySchema },
]), ]),
OtpModule, OtpModule,
HashModule, HashModule,
@@ -49,6 +55,7 @@ import { UserModel, UserDbSchema } from "./entities/schema/user.schema";
FieldExpertDbService, FieldExpertDbService,
InsurerExpertDbService, InsurerExpertDbService,
RegistrarDbService, RegistrarDbService,
ExpertFileActivityDbService,
], ],
exports: [ exports: [
UserDbService, UserDbService,
@@ -57,6 +64,7 @@ import { UserModel, UserDbSchema } from "./entities/schema/user.schema";
FieldExpertDbService, FieldExpertDbService,
InsurerExpertDbService, InsurerExpertDbService,
RegistrarDbService, RegistrarDbService,
ExpertFileActivityDbService,
], ],
}) })
export class UsersModule {} export class UsersModule {}