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

@@ -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);
}

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();
}
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);
}

View File

@@ -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;
}
@@ -190,11 +167,4 @@ DamageExpertDbSchema.pre("save", function (next) {
this.username = this.email;
}
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 { 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();
});

View File

@@ -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();
});
SchemaFactory.createForClass(FieldExpertModel);

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 { 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 {}