forked from Yara724/api
Fixed legacy requestedCounts methods and statistics + claimLink address
This commit is contained in:
@@ -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;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user