forked from Yara724/api
Fixed legacy requestedCounts methods and statistics + claimLink address
This commit is contained in:
@@ -67,6 +67,11 @@ import {
|
||||
import { PublicIdService } from "src/utils/public-id/public-id.service";
|
||||
import { ImageRequiredModel } from "./entites/schema/image-required.schema";
|
||||
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 { BranchDbService } from "src/client/entities/db-service/branch.db.service";
|
||||
import { CreationMethod } from "src/request-management/entities/schema/request-management.schema";
|
||||
@@ -117,6 +122,7 @@ export class ClaimRequestManagementService {
|
||||
private readonly claimRequestManagementDbService: ClaimRequestManagementDbService,
|
||||
private readonly claimFactorsImageDbService: ClaimFactorsImageDbService,
|
||||
private readonly damageExpertDbService: DamageExpertDbService,
|
||||
private readonly expertFileActivityDbService: ExpertFileActivityDbService,
|
||||
private readonly branchDbService: BranchDbService,
|
||||
private readonly claimRequiredDocumentDbService: ClaimRequiredDocumentDbService,
|
||||
private readonly publicIdService: PublicIdService,
|
||||
@@ -2577,11 +2583,20 @@ export class ClaimRequestManagementService {
|
||||
},
|
||||
);
|
||||
|
||||
await this.damageExpertDbService.updateStats(
|
||||
actorDetail.sub,
|
||||
"handled",
|
||||
requestId,
|
||||
);
|
||||
if (
|
||||
Types.ObjectId.isValid(String(actorDetail.sub)) &&
|
||||
Types.ObjectId.isValid(String(request.userClientKey)) &&
|
||||
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;
|
||||
}
|
||||
|
||||
@@ -2,7 +2,5 @@ import { ExecutionContext, createParamDecorator } from "@nestjs/common";
|
||||
|
||||
export const CustomHeader = createParamDecorator(
|
||||
(data, ctx: ExecutionContext) => {
|
||||
console.log(ctx.switchToHttp().getRequest());
|
||||
console.log(ctx.getType());
|
||||
},
|
||||
);
|
||||
|
||||
@@ -47,6 +47,11 @@ import { snapshotFromFieldExpert } from "src/helpers/expert-profile-snapshot";
|
||||
import { ExpertModel } from "src/users/entities/schema/expert.schema";
|
||||
import { SmsOrchestrationService } from "src/sms-orchestration/sms-orchestration.service";
|
||||
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 {
|
||||
CheckedRequest?: {
|
||||
@@ -88,6 +93,7 @@ export class ExpertBlameService {
|
||||
private readonly userSignDbService: UserSignDbService,
|
||||
private readonly requestManagementService: RequestManagementService,
|
||||
private readonly smsOrchestrationService: SmsOrchestrationService,
|
||||
private readonly expertFileActivityDbService: ExpertFileActivityDbService,
|
||||
) { }
|
||||
|
||||
/** Load immutable profile fields from `expert` for the acting field expert. */
|
||||
@@ -482,21 +488,22 @@ export class ExpertBlameService {
|
||||
}
|
||||
|
||||
if (!request.expert?.decision && lockedById) {
|
||||
const rid = new Types.ObjectId(requestId).toString();
|
||||
try {
|
||||
await this.expertDbService.findOneAndUpdate(
|
||||
{ _id: new Types.ObjectId(String(lockedById)) },
|
||||
{
|
||||
$inc: { "requestStats.totalChecked": -1 },
|
||||
$pull: { countedRequests: rid } as any,
|
||||
},
|
||||
);
|
||||
await this.recordBlameExpertActivity({
|
||||
expertId: String(lockedById),
|
||||
requestId: String(requestId),
|
||||
eventType: ExpertFileActivityType.UNCHECKED,
|
||||
tenantId:
|
||||
String(request.parties?.[0]?.person?.clientId ?? "") ||
|
||||
String(request.parties?.[1]?.person?.clientId ?? ""),
|
||||
idempotencyKey: `blame:${requestId}:unchecked:expired:${lockedById}`,
|
||||
});
|
||||
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) {
|
||||
this.logger.warn(
|
||||
`Expert stat rollback failed after blame lock expiry ${requestId}`,
|
||||
`Expert activity emit failed after blame lock expiry ${requestId}`,
|
||||
e,
|
||||
);
|
||||
}
|
||||
@@ -521,13 +528,15 @@ export class ExpertBlameService {
|
||||
if (shouldDecrementChecked) {
|
||||
updateExp.blameStatus = ReqBlameStatus.UnChecked;
|
||||
|
||||
await this.expertDbService.findOneAndUpdate(
|
||||
{ _id: new Types.ObjectId(r.actorLocked.actorId) },
|
||||
{
|
||||
$inc: { "requestStats.totalChecked": -1 },
|
||||
$pull: { countedRequests: r._id.toString() },
|
||||
},
|
||||
);
|
||||
await this.recordBlameExpertActivity({
|
||||
expertId: String(r.actorLocked.actorId),
|
||||
requestId: String(r._id),
|
||||
eventType: ExpertFileActivityType.UNCHECKED,
|
||||
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(
|
||||
`Request ${r._id} unlocked without reply — expert stats rolled back.`,
|
||||
@@ -577,13 +586,15 @@ export class ExpertBlameService {
|
||||
if (shouldRollbackStats) {
|
||||
update.blameStatus = ReqBlameStatus.UnChecked;
|
||||
|
||||
await this.expertDbService.findOneAndUpdate(
|
||||
{ _id: new Types.ObjectId(current.actorLocked.actorId) },
|
||||
{
|
||||
$inc: { "requestStats.totalChecked": -1 },
|
||||
$pull: { countedRequests: current._id.toString() },
|
||||
},
|
||||
);
|
||||
await this.recordBlameExpertActivity({
|
||||
expertId: String(current.actorLocked.actorId),
|
||||
requestId: String(current._id),
|
||||
eventType: ExpertFileActivityType.UNCHECKED,
|
||||
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(
|
||||
`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");
|
||||
}
|
||||
|
||||
// Update expert stats atomically (use findOneAndUpdate with conditions)
|
||||
await this.updateDamageExpertStats(actorDetail.sub, requestId, "checked");
|
||||
// Record expert check activity
|
||||
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);
|
||||
return { _id: requestId, lock: true };
|
||||
@@ -1002,8 +1021,15 @@ export class ExpertBlameService {
|
||||
throw new InternalServerErrorException("Failed to lock the request");
|
||||
}
|
||||
|
||||
// Update expert stats (reusing existing helper)
|
||||
await this.updateDamageExpertStats(actorDetail.sub, requestId, "checked");
|
||||
await this.recordBlameExpertActivity({
|
||||
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) {
|
||||
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(
|
||||
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).
|
||||
if (request.type === BlameRequestType.THIRD_PARTY) {
|
||||
const requestIdToken = String(request._id);
|
||||
@@ -1543,65 +1589,32 @@ export class ExpertBlameService {
|
||||
}
|
||||
}
|
||||
|
||||
private async updateDamageExpertStats(
|
||||
expertId: string,
|
||||
requestId: string,
|
||||
type: "checked" | "handled",
|
||||
) {
|
||||
if (!expertId || !requestId || !["checked", "handled"].includes(type)) {
|
||||
console.warn("Invalid expertId, requestId, or type");
|
||||
private async recordBlameExpertActivity(args: {
|
||||
expertId: string;
|
||||
requestId: string;
|
||||
eventType: ExpertFileActivityType;
|
||||
tenantId?: string;
|
||||
idempotencyKey?: string;
|
||||
}) {
|
||||
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;
|
||||
}
|
||||
|
||||
const expert = await this.expertDbService.findOne({
|
||||
_id: new Types.ObjectId(expertId),
|
||||
await this.expertFileActivityDbService.recordEvent({
|
||||
expertId: args.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) {
|
||||
@@ -1884,11 +1897,15 @@ export class ExpertBlameService {
|
||||
},
|
||||
);
|
||||
|
||||
await this.expertDbService.updateStats(
|
||||
actorDetail.sub,
|
||||
"handled",
|
||||
requestId,
|
||||
);
|
||||
await this.recordBlameExpertActivity({
|
||||
expertId: String(actorDetail.sub),
|
||||
requestId: String(requestId),
|
||||
eventType: ExpertFileActivityType.HANDLED,
|
||||
tenantId:
|
||||
String(request.firstPartyDetails?.firstPartyClient?.clientId ?? "") ||
|
||||
String(request.secondPartyDetails?.secondPartyClient?.clientId ?? ""),
|
||||
idempotencyKey: `blame:${requestId}:handled:inperson:${actorDetail.sub}`,
|
||||
});
|
||||
|
||||
return updated;
|
||||
}
|
||||
|
||||
@@ -67,6 +67,11 @@ import { resendRequestHasPayload } from "src/helpers/claim-expert-resend";
|
||||
import { snapshotFromDamageExpert } from "src/helpers/expert-profile-snapshot";
|
||||
import { DamageExpertModel } from "src/users/entities/schema/damage-expert.schema";
|
||||
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()
|
||||
export class ExpertClaimService {
|
||||
@@ -191,6 +196,7 @@ export class ExpertClaimService {
|
||||
private readonly branchDbService: BranchDbService,
|
||||
private readonly smsOrchestrationService: SmsOrchestrationService,
|
||||
private readonly userDbService: UserDbService,
|
||||
private readonly expertFileActivityDbService: ExpertFileActivityDbService,
|
||||
) { }
|
||||
|
||||
/** Load immutable profile fields from `damage-expert` for the acting expert. */
|
||||
@@ -199,6 +205,30 @@ export class ExpertClaimService {
|
||||
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`. */
|
||||
private async resolveClaimOwnerPhone(claim: any): Promise<string | undefined> {
|
||||
if (!claim?.owner?.userId) return undefined;
|
||||
@@ -366,7 +396,6 @@ export class ExpertClaimService {
|
||||
}
|
||||
|
||||
async getClaimRequestsListForExpert(actor): Promise<ClaimListDtoRs> {
|
||||
console.log(actor);
|
||||
// const requests = await this.claimRequestManagementDbService.findAllByStatus(
|
||||
// {
|
||||
// claimStatus: {
|
||||
@@ -545,11 +574,13 @@ export class ExpertClaimService {
|
||||
},
|
||||
);
|
||||
|
||||
await this.damageExpertDbService.updateStats(
|
||||
actorDetail.sub,
|
||||
"checked",
|
||||
requestId,
|
||||
);
|
||||
await this.recordClaimExpertActivity({
|
||||
expertId: String(actorDetail.sub),
|
||||
tenantId: String(request.userClientKey ?? actorDetail.clientKey ?? ""),
|
||||
claimId: String(requestId),
|
||||
eventType: ExpertFileActivityType.CHECKED,
|
||||
idempotencyKey: `claim:${requestId}:checked:${actorDetail.sub}`,
|
||||
});
|
||||
|
||||
this.unlockApi(request, fifteenMinutes.getTime() - Date.now());
|
||||
|
||||
@@ -1420,11 +1451,13 @@ export class ExpertClaimService {
|
||||
);
|
||||
|
||||
if (!isObjection) {
|
||||
await this.damageExpertDbService.updateStats(
|
||||
userId.sub,
|
||||
"handled",
|
||||
requestId,
|
||||
);
|
||||
await this.recordClaimExpertActivity({
|
||||
expertId: String(userId.sub),
|
||||
tenantId: String(request.userClientKey ?? userId.clientKey ?? ""),
|
||||
claimId: String(requestId),
|
||||
eventType: ExpertFileActivityType.HANDLED,
|
||||
idempotencyKey: `claim:${requestId}:handled:${userId.sub}`,
|
||||
});
|
||||
}
|
||||
|
||||
return {
|
||||
@@ -1492,7 +1525,6 @@ export class ExpertClaimService {
|
||||
|
||||
async streamServiceV2(requestId, query, res, header) {
|
||||
const request = await this.claimCaseDbService.findById(requestId);
|
||||
console.log(request)
|
||||
// const blameCase = await this.blameCaseDbService
|
||||
return request;
|
||||
// let videoPath: string = "";
|
||||
@@ -1942,6 +1974,14 @@ export class ExpertClaimService {
|
||||
$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 updatedReply =
|
||||
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);
|
||||
if (ownerPhoneFactors) {
|
||||
const expertLastName =
|
||||
@@ -2076,11 +2124,13 @@ export class ExpertClaimService {
|
||||
},
|
||||
);
|
||||
|
||||
await this.damageExpertDbService.updateStats(
|
||||
actorDetail.sub,
|
||||
"handled",
|
||||
requestId,
|
||||
);
|
||||
await this.recordClaimExpertActivity({
|
||||
expertId: String(actorDetail.sub),
|
||||
tenantId: String(request.userClientKey ?? actorDetail.clientKey ?? ""),
|
||||
claimId: String(requestId),
|
||||
eventType: ExpertFileActivityType.HANDLED,
|
||||
idempotencyKey: `claim:${requestId}:handled:${actorDetail.sub}`,
|
||||
});
|
||||
|
||||
return updated;
|
||||
}
|
||||
@@ -2459,6 +2509,14 @@ export class ExpertClaimService {
|
||||
|
||||
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).
|
||||
if (!needsFactorUpload) {
|
||||
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 {
|
||||
claimRequestId,
|
||||
status: claim.status,
|
||||
@@ -2992,6 +3058,15 @@ export class ExpertClaimService {
|
||||
update as any,
|
||||
{ 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;
|
||||
}
|
||||
|
||||
|
||||
@@ -98,7 +98,7 @@ export class ExpertInsurerController {
|
||||
}
|
||||
|
||||
@ApiBody({
|
||||
description: "Detailed expert rating",
|
||||
description: "Detailed expert rating by insurer",
|
||||
schema: {
|
||||
type: "object",
|
||||
properties: {
|
||||
@@ -135,7 +135,7 @@ export class ExpertInsurerController {
|
||||
minimum: 0,
|
||||
maximum: 5,
|
||||
example: 4,
|
||||
description: "برای امتیاز دادن به بات",
|
||||
description: "برای امتیاز دادن به عملکرد بات",
|
||||
},
|
||||
},
|
||||
required: [
|
||||
|
||||
@@ -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 { DamageExpertDbService } from "src/users/entities/db-service/damage-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()
|
||||
export class ExpertInsurerService {
|
||||
constructor(
|
||||
private readonly expertDbService: ExpertDbService,
|
||||
private readonly damageExpertDbService: DamageExpertDbService,
|
||||
private readonly expertFileActivityDbService: ExpertFileActivityDbService,
|
||||
private readonly blameRequestDbService: BlameRequestDbService,
|
||||
private readonly claimCaseDbService: ClaimCaseDbService,
|
||||
private readonly branchDbService: BranchDbService,
|
||||
@@ -37,6 +40,43 @@ export class ExpertInsurerService {
|
||||
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 {
|
||||
if (!Types.ObjectId.isValid(value)) {
|
||||
throw new BadRequestException(`Invalid ${label}`);
|
||||
@@ -141,6 +181,11 @@ export class ExpertInsurerService {
|
||||
]);
|
||||
|
||||
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 expertRatingsByCategoryMap: Record<string, Record<string, number[]>> = {};
|
||||
|
||||
@@ -193,7 +238,10 @@ export class ExpertInsurerService {
|
||||
fullName: `${expert.firstName} ${expert.lastName}`,
|
||||
role: expert.role,
|
||||
type: expert.userType,
|
||||
requestStats: expert.requestStats,
|
||||
requestStats: expertActivityStatsMap[expertIdStr] ?? {
|
||||
totalHandled: 0,
|
||||
totalChecked: 0,
|
||||
},
|
||||
createdAt: expert.createdAt,
|
||||
overallAverageRating,
|
||||
averageRatingsByCategory,
|
||||
|
||||
@@ -47,10 +47,6 @@ export class ProfileController {
|
||||
@UseGuards(GlobalGuard)
|
||||
@ApiBody({ type: AddPlateProfileDto })
|
||||
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);
|
||||
}
|
||||
|
||||
|
||||
@@ -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 { BlameStatus } from "src/Types&Enums/blame-request-management/blameStatus.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 { HashService } from "src/utils/hash/hash.service";
|
||||
import { UserAuthService } from "src/auth/auth-services/user.auth.service";
|
||||
@@ -309,6 +314,7 @@ export class RequestManagementService {
|
||||
private readonly userSign: UserSignDbService,
|
||||
private readonly smsOrchestrationService: SmsOrchestrationService,
|
||||
private readonly expertDbService: ExpertDbService,
|
||||
private readonly expertFileActivityDbService: ExpertFileActivityDbService,
|
||||
private readonly autoCloseRequestService: AutoCloseRequestService,
|
||||
private readonly claimRequestManagementDbService: ClaimRequestManagementDbService,
|
||||
private readonly claimCaseDbService: ClaimCaseDbService,
|
||||
@@ -2370,9 +2376,6 @@ export class RequestManagementService {
|
||||
}
|
||||
|
||||
if (p.firstPartyDetails.firstPartyPhoneNumber == phoneNumber) {
|
||||
console.log(
|
||||
new Date(p.createdAt).toLocaleTimeString("fa-IR").split(","),
|
||||
);
|
||||
res.listOfFirstParty.push({
|
||||
id: p.id,
|
||||
blameStatus: p.blameStatus,
|
||||
@@ -2746,76 +2749,28 @@ export class RequestManagementService {
|
||||
return null;
|
||||
}
|
||||
|
||||
private async updateDamageExpertStats(
|
||||
expertId: string,
|
||||
requestId: string,
|
||||
type: "checked" | "handled",
|
||||
) {
|
||||
if (!expertId || !requestId || !["checked", "handled"].includes(type)) {
|
||||
console.warn("Invalid expertId, requestId, or type");
|
||||
private async recordBlameExpertActivity(args: {
|
||||
expertId: string;
|
||||
tenantId: string;
|
||||
requestId: string;
|
||||
eventType: ExpertFileActivityType;
|
||||
idempotencyKey?: string;
|
||||
}): Promise<void> {
|
||||
if (
|
||||
!Types.ObjectId.isValid(args.expertId) ||
|
||||
!Types.ObjectId.isValid(args.tenantId) ||
|
||||
!Types.ObjectId.isValid(args.requestId)
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Validate that both expertId and requestId are valid ObjectId strings
|
||||
if (!Types.ObjectId.isValid(expertId)) {
|
||||
console.warn("Invalid ObjectId format for expertId:", expertId);
|
||||
return;
|
||||
}
|
||||
|
||||
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),
|
||||
await this.expertFileActivityDbService.recordEvent({
|
||||
expertId: args.expertId,
|
||||
tenantId: args.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);
|
||||
}
|
||||
}
|
||||
|
||||
private async handleDisagreement(
|
||||
@@ -3006,11 +2961,17 @@ export class RequestManagementService {
|
||||
request._id.toString() :
|
||||
String(request._id);
|
||||
|
||||
await this.updateDamageExpertStats(
|
||||
actorIdStr,
|
||||
requestIdStr,
|
||||
"handled",
|
||||
);
|
||||
const tenantIdStr =
|
||||
request.firstPartyDetails?.firstPartyClient?.clientId?.toString?.() ||
|
||||
request.secondPartyDetails?.secondPartyClient?.clientId?.toString?.() ||
|
||||
"";
|
||||
await this.recordBlameExpertActivity({
|
||||
expertId: actorIdStr,
|
||||
tenantId: tenantIdStr,
|
||||
requestId: requestIdStr,
|
||||
eventType: ExpertFileActivityType.HANDLED,
|
||||
idempotencyKey: `blame:${requestIdStr}:handled:${actorIdStr}`,
|
||||
});
|
||||
}
|
||||
await this.requestManagementDbService.findByIdAndUpdate(request._id, {
|
||||
$set: { isHandledStatsUpdated: true },
|
||||
|
||||
@@ -47,7 +47,7 @@ export class SmsOrchestrationService implements OnModuleInit {
|
||||
}
|
||||
|
||||
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> {
|
||||
|
||||
@@ -46,64 +46,6 @@ export class DamageExpertDbService {
|
||||
.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) {
|
||||
return this.damageExpertModel.updateOne(filter, update);
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -33,50 +33,6 @@ export class ExpertDbService {
|
||||
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) {
|
||||
return this.expertModel.updateOne(filter, update);
|
||||
}
|
||||
|
||||
@@ -8,16 +8,6 @@ import {
|
||||
import { RoleEnum } from "src/Types&Enums/role.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 })
|
||||
export class PersonalInfo {
|
||||
@@ -166,19 +156,6 @@ export class DamageExpertModel {
|
||||
@Prop({ type: "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;
|
||||
}
|
||||
|
||||
@@ -191,10 +168,3 @@ DamageExpertDbSchema.pre("save", function (next) {
|
||||
}
|
||||
next();
|
||||
});
|
||||
|
||||
DamageExpertDbSchema.pre("save", function (next) {
|
||||
if (!this.requestStats) {
|
||||
this.requestStats = { totalChecked: 0, totalHandled: 0 };
|
||||
}
|
||||
next();
|
||||
});
|
||||
|
||||
52
src/users/entities/schema/expert-file-activity.schema.ts
Normal file
52
src/users/entities/schema/expert-file-activity.schema.ts
Normal 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 } } },
|
||||
);
|
||||
@@ -3,17 +3,6 @@ import { Degrees } from "src/Types&Enums/degrees.enum";
|
||||
import { RoleEnum } from "src/Types&Enums/role.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 })
|
||||
export class ExpertModel {
|
||||
@Prop({})
|
||||
@@ -79,27 +68,8 @@ export class ExpertModel {
|
||||
@Prop({ type: "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;
|
||||
}
|
||||
|
||||
export const ExpertDbSchema = SchemaFactory.createForClass(ExpertModel);
|
||||
|
||||
ExpertDbSchema.pre("save", function (next) {
|
||||
if (!this.requestStats) {
|
||||
this.requestStats = { totalChecked: 0, totalHandled: 0 };
|
||||
}
|
||||
next();
|
||||
});
|
||||
|
||||
@@ -2,17 +2,6 @@ import { Prop, Schema, SchemaFactory } from "@nestjs/mongoose";
|
||||
import { Types } from "mongoose";
|
||||
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({
|
||||
collection: "field-expert",
|
||||
versionKey: false,
|
||||
@@ -48,31 +37,8 @@ export class FieldExpertModel {
|
||||
@Prop({ type: "string", default: "" })
|
||||
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;
|
||||
}
|
||||
|
||||
export const FieldExpertDbSchema =
|
||||
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();
|
||||
});
|
||||
|
||||
@@ -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 { RegistrarDbService } from "./entities/db-service/registrar.db.service";
|
||||
import { UserDbService } from "./entities/db-service/user.db.service";
|
||||
import { ExpertFileActivityDbService } from "./entities/db-service/expert-file-activity.db.service";
|
||||
import {
|
||||
DamageExpertDbSchema,
|
||||
DamageExpertModel,
|
||||
} from "./entities/schema/damage-expert.schema";
|
||||
import {
|
||||
ExpertFileActivity,
|
||||
ExpertFileActivitySchema,
|
||||
} from "./entities/schema/expert-file-activity.schema";
|
||||
import { ExpertDbSchema } from "./entities/schema/expert.schema";
|
||||
import {
|
||||
FieldExpertDbSchema,
|
||||
@@ -37,6 +42,7 @@ import { UserModel, UserDbSchema } from "./entities/schema/user.schema";
|
||||
{ name: FieldExpertModel.name, schema: FieldExpertDbSchema },
|
||||
{ name: InsurerExpertModel.name, schema: InsurerExpertDbSchema },
|
||||
{ name: RegistrarModel.name, schema: RegistrarDbSchema },
|
||||
{ name: ExpertFileActivity.name, schema: ExpertFileActivitySchema },
|
||||
]),
|
||||
OtpModule,
|
||||
HashModule,
|
||||
@@ -49,6 +55,7 @@ import { UserModel, UserDbSchema } from "./entities/schema/user.schema";
|
||||
FieldExpertDbService,
|
||||
InsurerExpertDbService,
|
||||
RegistrarDbService,
|
||||
ExpertFileActivityDbService,
|
||||
],
|
||||
exports: [
|
||||
UserDbService,
|
||||
@@ -57,6 +64,7 @@ import { UserModel, UserDbSchema } from "./entities/schema/user.schema";
|
||||
FieldExpertDbService,
|
||||
InsurerExpertDbService,
|
||||
RegistrarDbService,
|
||||
ExpertFileActivityDbService,
|
||||
],
|
||||
})
|
||||
export class UsersModule {}
|
||||
|
||||
Reference in New Issue
Block a user