forked from Yara724/api
Merge pull request 'YARA-985, YARA-1244' (#275) from s.yahyaee/yara724-api:main into main
Reviewed-on: Yara724/api#275
This commit is contained in:
@@ -2,143 +2,13 @@
|
|||||||
"clientCode": 8,
|
"clientCode": 8,
|
||||||
"branches": [
|
"branches": [
|
||||||
{
|
{
|
||||||
"code": "100100",
|
"code": "210120",
|
||||||
"name": "واحدصدورالکترونيکي",
|
"name": "شعبه والفجر",
|
||||||
"fullName": "واحدصدورالکترونيکي(100100)",
|
"fullName": "شعبه والفجر(210120)",
|
||||||
"city": "تهران",
|
"city": "تهران",
|
||||||
"state": "تهران",
|
"state": "تهران",
|
||||||
"address": "خيابان وليعصر_بلوار ميرداماد_پلاک 22",
|
"address": "تهران، اميرآبادشمالي، شهرک والفجر، ضلع جنوب غربي ميدان استادخسرو سينايي",
|
||||||
"phoneNumber": "8259",
|
"phoneNumber": "86051332",
|
||||||
"isActive": true
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"code": "110011",
|
|
||||||
"name": "ستاد مرکزي",
|
|
||||||
"fullName": "ستاد مرکزي(110011)",
|
|
||||||
"city": "تهران",
|
|
||||||
"state": "تهران",
|
|
||||||
"address": "تهران، خيابان وليعصر، بالاتراز ميرداماد، خيابان قباديان غربي، پلاك22",
|
|
||||||
"phoneNumber": "8259",
|
|
||||||
"isActive": true
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"code": "111130",
|
|
||||||
"name": "شعبه ويژه ميرداماد",
|
|
||||||
"fullName": "شعبه ويژه ميرداماد(111130)",
|
|
||||||
"city": "تهران",
|
|
||||||
"state": "تهران",
|
|
||||||
"address": "تهران، خيابان وليعصر، بالاتراز ميرداماد، خيابان قباديان غربي، پلاك22",
|
|
||||||
"phoneNumber": "8259",
|
|
||||||
"isActive": true
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"code": "120021",
|
|
||||||
"name": "سرپرستي منطقه يک كشور",
|
|
||||||
"fullName": "سرپرستي منطقه يک كشور(120021)",
|
|
||||||
"city": "تهران",
|
|
||||||
"state": "تهران",
|
|
||||||
"address": "تهران،خيابان وليعصر ،خيابان قباديان غربي ،پلاک 22 ، طبقه همکف",
|
|
||||||
"phoneNumber": "0218259",
|
|
||||||
"isActive": true
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"code": "130031",
|
|
||||||
"name": "سرپرستي منطقه مركزي كشور",
|
|
||||||
"fullName": "سرپرستي منطقه مركزي كشور(130031)",
|
|
||||||
"city": "تهران",
|
|
||||||
"state": "تهران",
|
|
||||||
"address": "اصفهان، خيابان امام خميني (ره) - بعد از چهارراه شريف - کوچه شهيد احمدي (85)",
|
|
||||||
"phoneNumber": "03133328257",
|
|
||||||
"isActive": true
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"code": "140041",
|
|
||||||
"name": "سرپرستي منطقه شمالغرب کشور",
|
|
||||||
"fullName": "سرپرستي منطقه شمالغرب کشور(140041)",
|
|
||||||
"city": "تهران",
|
|
||||||
"state": "تهران",
|
|
||||||
"address": "تبريز- خيابان ائل گلي - فلکه خيام - نبش فلکه رجائي - بيمه پارسيان",
|
|
||||||
"phoneNumber": "04133832289",
|
|
||||||
"isActive": true
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"code": "150051",
|
|
||||||
"name": "سرپرستي منطقه جنوب كشور",
|
|
||||||
"fullName": "سرپرستي منطقه جنوب كشور(150051)",
|
|
||||||
"city": "تهران",
|
|
||||||
"state": "تهران",
|
|
||||||
"address": "شيراز ـ فلکه فرودگاه (ميدان بسيج) ـ ابتداي بلوار سياحتگر",
|
|
||||||
"phoneNumber": "01738315473",
|
|
||||||
"isActive": true
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"code": "150053",
|
|
||||||
"name": "سرپرست منطقه جنوب شرقي کشور",
|
|
||||||
"fullName": "سرپرست منطقه جنوب شرقي کشور(150053)",
|
|
||||||
"city": "کرمان",
|
|
||||||
"state": "کرمان",
|
|
||||||
"address": "کرمان، حافظ، بعد از چهارراه جامي، پلاک 153",
|
|
||||||
"phoneNumber": "03432718000",
|
|
||||||
"isActive": true
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"code": "160061",
|
|
||||||
"name": "سرپرستي منطقه شرق كشور",
|
|
||||||
"fullName": "سرپرستي منطقه شرق كشور(160061)",
|
|
||||||
"city": "تهران",
|
|
||||||
"state": "تهران",
|
|
||||||
"address": "مشهد ـ خيام شمالي ـ نبش خيام شمالي 36",
|
|
||||||
"phoneNumber": "05137659005",
|
|
||||||
"isActive": true
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"code": "170071",
|
|
||||||
"name": "سرپرستي منطقه غرب كشور",
|
|
||||||
"fullName": "سرپرستي منطقه غرب كشور(170071)",
|
|
||||||
"city": "تهران",
|
|
||||||
"state": "تهران",
|
|
||||||
"address": "کرمانشاه . ميدان مرکزي خيابان خرم نبش کوي بسيج ساختمان عرفان",
|
|
||||||
"phoneNumber": "08338431017",
|
|
||||||
"isActive": true
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"code": "180081",
|
|
||||||
"name": "سرپرستي منطقه شمال شرق کشور",
|
|
||||||
"fullName": "سرپرستي منطقه شمال شرق کشور(180081)",
|
|
||||||
"city": "ساري",
|
|
||||||
"state": "مازندران",
|
|
||||||
"address": "ساري، شعبه ساري",
|
|
||||||
"phoneNumber": "01133207241",
|
|
||||||
"isActive": true
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"code": "180083",
|
|
||||||
"name": "سرپرست منطقه شمال کشوري",
|
|
||||||
"fullName": "سرپرست منطقه شمال کشوري(180083)",
|
|
||||||
"city": "رشت",
|
|
||||||
"state": "گيلان",
|
|
||||||
"address": "رشت، بلوار آيت اله رودباري،کدپستي:4144761893",
|
|
||||||
"phoneNumber": "01333512135",
|
|
||||||
"isActive": true
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"code": "190092",
|
|
||||||
"name": "سرپرستي جنوب غربي کشور",
|
|
||||||
"fullName": "سرپرستي جنوب غربي کشور(190092)",
|
|
||||||
"city": "اهواز",
|
|
||||||
"state": "خوزستان",
|
|
||||||
"address": "اهواز،تقاطع بلوار ساحلي گلستان (خيابان فروردين)،نبش خيابان نصرت شمالي ،پلاک 701 کد پستي 6155977139",
|
|
||||||
"phoneNumber": "06133743877",
|
|
||||||
"isActive": true
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"code": "210040",
|
|
||||||
"name": "شعبه شرق تهران",
|
|
||||||
"fullName": "شعبه شرق تهران(210040)",
|
|
||||||
"city": "تهران",
|
|
||||||
"state": "تهران",
|
|
||||||
"address": "تهران، خيابان دماوند، بعداز چهارراه تهرانپارس، روبروي تعميرگاه مرکزي شماره يک سايپا، پلاک129",
|
|
||||||
"phoneNumber": "77393783-4",
|
|
||||||
"isActive": true
|
"isActive": true
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -151,6 +21,26 @@
|
|||||||
"phoneNumber": "66021968",
|
"phoneNumber": "66021968",
|
||||||
"isActive": true
|
"isActive": true
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"code": "110011",
|
||||||
|
"name": "ستاد مرکزي",
|
||||||
|
"fullName": "ستاد مرکزي(110011)",
|
||||||
|
"city": "تهران",
|
||||||
|
"state": "تهران",
|
||||||
|
"address": "تهران، خيابان وليعصر، بالاتراز ميرداماد، خيابان قباديان غربي، پلاك22",
|
||||||
|
"phoneNumber": "8259",
|
||||||
|
"isActive": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"code": "210040",
|
||||||
|
"name": "شعبه شرق تهران",
|
||||||
|
"fullName": "شعبه شرق تهران(210040)",
|
||||||
|
"city": "تهران",
|
||||||
|
"state": "تهران",
|
||||||
|
"address": "تهران، خيابان دماوند، بعداز چهارراه تهرانپارس، روبروي تعميرگاه مرکزي شماره يک سايپا، پلاک129",
|
||||||
|
"phoneNumber": "77393783-4",
|
||||||
|
"isActive": true
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"code": "210110",
|
"code": "210110",
|
||||||
"name": "شعبه پونک",
|
"name": "شعبه پونک",
|
||||||
@@ -162,23 +52,13 @@
|
|||||||
"isActive": true
|
"isActive": true
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"code": "210120",
|
"code": "111130",
|
||||||
"name": "شعبه والفجر",
|
"name": "شعبه ويژه ميرداماد",
|
||||||
"fullName": "شعبه والفجر(210120)",
|
"fullName": "شعبه ويژه ميرداماد(111130)",
|
||||||
"city": "تهران",
|
"city": "تهران",
|
||||||
"state": "تهران",
|
"state": "تهران",
|
||||||
"address": "تهران، اميرآبادشمالي، شهرک والفجر، ضلع جنوب غربي ميدان استادخسرو سينايي",
|
"address": "تهران، خيابان وليعصر، بالاتراز ميرداماد، خيابان قباديان غربي، پلاك22",
|
||||||
"phoneNumber": "86051332",
|
"phoneNumber": "8259",
|
||||||
"isActive": true
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"code": "210150",
|
|
||||||
"name": "شعبه شمال شرق تهران",
|
|
||||||
"fullName": "شعبه شمال شرق تهران(210150)",
|
|
||||||
"city": "تهران",
|
|
||||||
"state": "تهران",
|
|
||||||
"address": "تهران، ضلع شمال غربي ميدان بني هاشم، نبش خيابان كشوري، پلاك13",
|
|
||||||
"phoneNumber": "26244319",
|
|
||||||
"isActive": true
|
"isActive": true
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -1,13 +1,16 @@
|
|||||||
/**
|
/**
|
||||||
* One-time seed for Parsian (clientCode=8) Tehran branches + field experts.
|
* One-time seed for Parsian (clientCode=8) Tehran branches + file reviewers + file makers.
|
||||||
*
|
*
|
||||||
* Usage (before starting the app):
|
* Usage (before starting the app):
|
||||||
* npm run seed:parsian-tehran
|
* npm run seed:parsian-tehran
|
||||||
*
|
*
|
||||||
* Optional env:
|
* Optional env:
|
||||||
|
* SEED_PARSIAN_TEHRAN_DEFAULT_PASSWORD=Parsian@724
|
||||||
|
*
|
||||||
|
* Backward-compatible env alias:
|
||||||
* SEED_FIELD_EXPERT_DEFAULT_PASSWORD=Parsian@724
|
* SEED_FIELD_EXPERT_DEFAULT_PASSWORD=Parsian@724
|
||||||
*/
|
*/
|
||||||
import { readFileSync, existsSync } from "node:fs";
|
import { existsSync, readFileSync } from "node:fs";
|
||||||
import { join } from "node:path";
|
import { join } from "node:path";
|
||||||
import * as crypto from "node:crypto";
|
import * as crypto from "node:crypto";
|
||||||
import mongoose, { Schema, Types } from "mongoose";
|
import mongoose, { Schema, Types } from "mongoose";
|
||||||
@@ -23,17 +26,29 @@ type BranchSeed = {
|
|||||||
isActive?: boolean;
|
isActive?: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
type FieldExpertSeed = {
|
type ExpertLocationSeed = {
|
||||||
|
id: string;
|
||||||
|
name: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
type FileReviewerSeed = {
|
||||||
nationalCode: string;
|
nationalCode: string;
|
||||||
mobile?: string;
|
mobile?: string;
|
||||||
firstName: string;
|
firstName: string;
|
||||||
lastName: string;
|
lastName: string;
|
||||||
branchCode: string;
|
locations: ExpertLocationSeed[];
|
||||||
branchName?: string;
|
ThirdPartyExpertiseClaim?: string;
|
||||||
city?: string;
|
CarBodyExpertiseClaim?: string;
|
||||||
state?: string;
|
};
|
||||||
title?: string;
|
|
||||||
expertCode?: string;
|
type FileMakerSeed = {
|
||||||
|
nationalCode: string;
|
||||||
|
mobile?: string;
|
||||||
|
firstName: string;
|
||||||
|
lastName: string;
|
||||||
|
locations: ExpertLocationSeed[];
|
||||||
|
ThirdPartyClaimExpertId?: string;
|
||||||
|
CarBodyClaimExpertId?: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
function stripQuotes(value: string): string {
|
function stripQuotes(value: string): string {
|
||||||
@@ -76,7 +91,6 @@ function loadEnvFile() {
|
|||||||
process.env[key] = stripQuotes(value);
|
process.env[key] = stripQuotes(value);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Expand ${VAR} placeholders (same as Nest ConfigModule expandVariables).
|
|
||||||
for (let pass = 0; pass < 5; pass++) {
|
for (let pass = 0; pass < 5; pass++) {
|
||||||
let changed = false;
|
let changed = false;
|
||||||
for (const key of Object.keys(process.env)) {
|
for (const key of Object.keys(process.env)) {
|
||||||
@@ -110,7 +124,7 @@ function resolveMongoUri(): string {
|
|||||||
return uri;
|
return uri;
|
||||||
}
|
}
|
||||||
|
|
||||||
async function ensureFieldExpertIndexes(collection: mongoose.Collection) {
|
async function ensureUserIndexes(collection: mongoose.Collection) {
|
||||||
const indexes = await collection.indexes();
|
const indexes = await collection.indexes();
|
||||||
const emailIndex = indexes.find((idx) => idx.key?.email === 1);
|
const emailIndex = indexes.find((idx) => idx.key?.email === 1);
|
||||||
if (emailIndex && !emailIndex.sparse) {
|
if (emailIndex && !emailIndex.sparse) {
|
||||||
@@ -134,6 +148,17 @@ function hashPassword(password: string): Promise<string> {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function normalizeLocations(locations: ExpertLocationSeed[]): ExpertLocationSeed[] {
|
||||||
|
const deduped = new Map<string, ExpertLocationSeed>();
|
||||||
|
for (const location of locations ?? []) {
|
||||||
|
const id = String(location?.id ?? "").trim();
|
||||||
|
const name = String(location?.name ?? "").trim();
|
||||||
|
if (!id || !name || deduped.has(id)) continue;
|
||||||
|
deduped.set(id, { id, name });
|
||||||
|
}
|
||||||
|
return [...deduped.values()];
|
||||||
|
}
|
||||||
|
|
||||||
const ClientSchema = new Schema(
|
const ClientSchema = new Schema(
|
||||||
{
|
{
|
||||||
clientName: { type: Object, required: true },
|
clientName: { type: Object, required: true },
|
||||||
@@ -159,7 +184,15 @@ const BranchSchema = new Schema(
|
|||||||
|
|
||||||
BranchSchema.index({ clientKey: 1, code: 1 }, { unique: true });
|
BranchSchema.index({ clientKey: 1, code: 1 }, { unique: true });
|
||||||
|
|
||||||
const FieldExpertSchema = new Schema(
|
const ExpertLocationSchema = new Schema(
|
||||||
|
{
|
||||||
|
id: { type: String, required: true },
|
||||||
|
name: { type: String, required: true },
|
||||||
|
},
|
||||||
|
{ _id: false, id: false, versionKey: false },
|
||||||
|
);
|
||||||
|
|
||||||
|
const FileReviewerSchema = new Schema(
|
||||||
{
|
{
|
||||||
firstName: { type: String, required: true },
|
firstName: { type: String, required: true },
|
||||||
lastName: { type: String, required: true },
|
lastName: { type: String, required: true },
|
||||||
@@ -168,21 +201,180 @@ const FieldExpertSchema = new Schema(
|
|||||||
nationalCode: { type: String, index: true, sparse: true },
|
nationalCode: { type: String, index: true, sparse: true },
|
||||||
clientKey: { type: Schema.Types.ObjectId, index: true },
|
clientKey: { type: Schema.Types.ObjectId, index: true },
|
||||||
branchId: { type: Schema.Types.ObjectId, index: true },
|
branchId: { type: Schema.Types.ObjectId, index: true },
|
||||||
|
locations: { type: [ExpertLocationSchema], default: [] },
|
||||||
password: { type: String, required: true },
|
password: { type: String, required: true },
|
||||||
mobile: { type: String },
|
mobile: { type: String },
|
||||||
phone: { type: String },
|
phone: { type: String },
|
||||||
role: { type: String, default: "field_expert" },
|
role: { type: String, default: "file_reviewer" },
|
||||||
otp: { type: String, default: "" },
|
otp: { type: String, default: "" },
|
||||||
expertCode: { type: String, required: false },
|
expertCode: { type: String, required: false },
|
||||||
|
ThirdPartyExpertiseClaim: { type: String, required: false },
|
||||||
|
CarBodyExpertiseClaim: { type: String, required: false },
|
||||||
},
|
},
|
||||||
{ collection: "field-expert", versionKey: false, timestamps: true },
|
{ collection: "file-reviewer", versionKey: false, timestamps: true },
|
||||||
);
|
);
|
||||||
|
|
||||||
FieldExpertSchema.index(
|
FileReviewerSchema.index(
|
||||||
{ clientKey: 1, nationalCode: 1 },
|
{ clientKey: 1, nationalCode: 1 },
|
||||||
{ unique: true, sparse: true },
|
{ unique: true, sparse: true },
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const FileMakerSchema = new Schema(
|
||||||
|
{
|
||||||
|
firstName: { type: String, required: true },
|
||||||
|
lastName: { type: String, required: true },
|
||||||
|
email: { type: String, unique: true, sparse: true },
|
||||||
|
username: { type: String },
|
||||||
|
nationalCode: { type: String, index: true, sparse: true },
|
||||||
|
clientKey: { type: Schema.Types.ObjectId, index: true },
|
||||||
|
branchId: { type: Schema.Types.ObjectId, index: true },
|
||||||
|
locations: { type: [ExpertLocationSchema], default: [] },
|
||||||
|
password: { type: String, required: true },
|
||||||
|
mobile: { type: String },
|
||||||
|
phone: { type: String },
|
||||||
|
role: { type: String, default: "file_maker" },
|
||||||
|
otp: { type: String, default: "" },
|
||||||
|
expertCode: { type: String, required: false },
|
||||||
|
ThirdPartyClaimExpertId: { type: String, required: false },
|
||||||
|
CarBodyClaimExpertId: { type: String, required: false },
|
||||||
|
},
|
||||||
|
{ collection: "file-maker", versionKey: false, timestamps: true },
|
||||||
|
);
|
||||||
|
|
||||||
|
FileMakerSchema.index(
|
||||||
|
{ clientKey: 1, nationalCode: 1 },
|
||||||
|
{ unique: true, sparse: true },
|
||||||
|
);
|
||||||
|
|
||||||
|
function resolveSeedLocations(
|
||||||
|
seedLocations: ExpertLocationSeed[],
|
||||||
|
branchMetaByCode: Map<string, { _id: Types.ObjectId; name: string }>,
|
||||||
|
) {
|
||||||
|
const resolved: { id: string; name: string; branchId: Types.ObjectId }[] = [];
|
||||||
|
const skippedCodes: string[] = [];
|
||||||
|
|
||||||
|
for (const location of normalizeLocations(seedLocations)) {
|
||||||
|
const branch = branchMetaByCode.get(location.id);
|
||||||
|
if (!branch) {
|
||||||
|
skippedCodes.push(location.id);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
resolved.push({
|
||||||
|
id: location.id,
|
||||||
|
name: branch.name || location.name,
|
||||||
|
branchId: branch._id,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
resolved,
|
||||||
|
skippedCodes,
|
||||||
|
primaryBranchId: resolved[0]?.branchId,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
async function upsertRoleUsers({
|
||||||
|
label,
|
||||||
|
seeds,
|
||||||
|
model,
|
||||||
|
clientKey,
|
||||||
|
hashedPassword,
|
||||||
|
branchMetaByCode,
|
||||||
|
role,
|
||||||
|
codeFields,
|
||||||
|
}: {
|
||||||
|
label: string;
|
||||||
|
seeds: Array<Record<string, any>>;
|
||||||
|
model: mongoose.Model<any>;
|
||||||
|
clientKey: Types.ObjectId;
|
||||||
|
hashedPassword: string;
|
||||||
|
branchMetaByCode: Map<string, { _id: Types.ObjectId; name: string }>;
|
||||||
|
role: string;
|
||||||
|
codeFields: string[];
|
||||||
|
}) {
|
||||||
|
let created = 0;
|
||||||
|
let updated = 0;
|
||||||
|
let skipped = 0;
|
||||||
|
|
||||||
|
for (const seed of seeds) {
|
||||||
|
const { resolved, skippedCodes, primaryBranchId } = resolveSeedLocations(
|
||||||
|
seed.locations,
|
||||||
|
branchMetaByCode,
|
||||||
|
);
|
||||||
|
|
||||||
|
if (skippedCodes.length > 0) {
|
||||||
|
console.warn(
|
||||||
|
`Skipping unknown ${label} locations for ${seed.nationalCode}: ${skippedCodes.join(
|
||||||
|
", ",
|
||||||
|
)}`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!primaryBranchId || resolved.length === 0) {
|
||||||
|
console.warn(
|
||||||
|
`Skipping ${label} ${seed.nationalCode}: no valid branch locations remained`,
|
||||||
|
);
|
||||||
|
skipped++;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
const setPayload: Record<string, unknown> = {
|
||||||
|
firstName: seed.firstName,
|
||||||
|
lastName: seed.lastName,
|
||||||
|
username: seed.nationalCode,
|
||||||
|
nationalCode: seed.nationalCode,
|
||||||
|
clientKey,
|
||||||
|
branchId: primaryBranchId,
|
||||||
|
locations: resolved.map(({ id, name }) => ({ id, name })),
|
||||||
|
role,
|
||||||
|
otp: "",
|
||||||
|
};
|
||||||
|
|
||||||
|
if (seed.mobile) {
|
||||||
|
setPayload.mobile = seed.mobile;
|
||||||
|
}
|
||||||
|
|
||||||
|
const unsetPayload: Record<string, ""> = {
|
||||||
|
expertCode: "",
|
||||||
|
};
|
||||||
|
|
||||||
|
for (const field of codeFields) {
|
||||||
|
if (seed[field]) {
|
||||||
|
setPayload[field] = seed[field];
|
||||||
|
} else {
|
||||||
|
unsetPayload[field] = "";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const existing = await model.findOne({
|
||||||
|
clientKey,
|
||||||
|
nationalCode: seed.nationalCode,
|
||||||
|
});
|
||||||
|
|
||||||
|
if (existing) {
|
||||||
|
await model.updateOne(
|
||||||
|
{ _id: existing._id },
|
||||||
|
{
|
||||||
|
$set: {
|
||||||
|
...setPayload,
|
||||||
|
password: existing.password,
|
||||||
|
},
|
||||||
|
$unset: unsetPayload,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
updated++;
|
||||||
|
} else {
|
||||||
|
await model.create({
|
||||||
|
...setPayload,
|
||||||
|
password: hashedPassword,
|
||||||
|
});
|
||||||
|
created++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return { created, updated, skipped };
|
||||||
|
}
|
||||||
|
|
||||||
async function main() {
|
async function main() {
|
||||||
loadEnvFile();
|
loadEnvFile();
|
||||||
const mongoUri = resolveMongoUri();
|
const mongoUri = resolveMongoUri();
|
||||||
@@ -191,12 +383,24 @@ async function main() {
|
|||||||
const branchesFile = JSON.parse(
|
const branchesFile = JSON.parse(
|
||||||
readFileSync(join(dataDir, "branches.json"), "utf8"),
|
readFileSync(join(dataDir, "branches.json"), "utf8"),
|
||||||
) as { clientCode: number; branches: BranchSeed[] };
|
) as { clientCode: number; branches: BranchSeed[] };
|
||||||
const expertsFile = JSON.parse(
|
const fileReviewersFile = JSON.parse(
|
||||||
readFileSync(join(dataDir, "field-experts.json"), "utf8"),
|
readFileSync(join(dataDir, "file-reviewers.json"), "utf8"),
|
||||||
) as { clientCode: number; fieldExperts: FieldExpertSeed[] };
|
) as { clientCode: number; fileReviewers: FileReviewerSeed[] };
|
||||||
|
const fileMakersFile = JSON.parse(
|
||||||
|
readFileSync(join(dataDir, "file-makers.json"), "utf8"),
|
||||||
|
) as { clientCode: number; fileMakers: FileMakerSeed[] };
|
||||||
|
|
||||||
|
if (
|
||||||
|
branchesFile.clientCode !== fileReviewersFile.clientCode ||
|
||||||
|
branchesFile.clientCode !== fileMakersFile.clientCode
|
||||||
|
) {
|
||||||
|
throw new Error("Seed data clientCode mismatch between branch/reviewer/maker files");
|
||||||
|
}
|
||||||
|
|
||||||
const defaultPassword =
|
const defaultPassword =
|
||||||
process.env.SEED_FIELD_EXPERT_DEFAULT_PASSWORD ?? "123321";
|
process.env.SEED_PARSIAN_TEHRAN_DEFAULT_PASSWORD ??
|
||||||
|
process.env.SEED_FIELD_EXPERT_DEFAULT_PASSWORD ??
|
||||||
|
"123321";
|
||||||
const hashedPassword = await hashPassword(defaultPassword);
|
const hashedPassword = await hashPassword(defaultPassword);
|
||||||
|
|
||||||
await mongoose.connect(mongoUri, {
|
await mongoose.connect(mongoUri, {
|
||||||
@@ -204,11 +408,14 @@ async function main() {
|
|||||||
tlsAllowInvalidCertificates:
|
tlsAllowInvalidCertificates:
|
||||||
process.env.MONGO_TLS_ALLOW_INVALID_CERTS === "true",
|
process.env.MONGO_TLS_ALLOW_INVALID_CERTS === "true",
|
||||||
});
|
});
|
||||||
|
|
||||||
const Client = mongoose.model("ClientSeedClient", ClientSchema);
|
const Client = mongoose.model("ClientSeedClient", ClientSchema);
|
||||||
const Branch = mongoose.model("ClientSeedBranch", BranchSchema);
|
const Branch = mongoose.model("ClientSeedBranch", BranchSchema);
|
||||||
const FieldExpert = mongoose.model("ClientSeedFieldExpert", FieldExpertSchema);
|
const FileReviewer = mongoose.model("ClientSeedFileReviewer", FileReviewerSchema);
|
||||||
|
const FileMaker = mongoose.model("ClientSeedFileMaker", FileMakerSchema);
|
||||||
|
|
||||||
await ensureFieldExpertIndexes(FieldExpert.collection);
|
await ensureUserIndexes(FileReviewer.collection);
|
||||||
|
await ensureUserIndexes(FileMaker.collection);
|
||||||
|
|
||||||
const client = await Client.findOne({
|
const client = await Client.findOne({
|
||||||
clientCode: branchesFile.clientCode,
|
clientCode: branchesFile.clientCode,
|
||||||
@@ -220,7 +427,7 @@ async function main() {
|
|||||||
}
|
}
|
||||||
const clientKey = new Types.ObjectId(String(client._id));
|
const clientKey = new Types.ObjectId(String(client._id));
|
||||||
|
|
||||||
const branchIdByCode = new Map<string, Types.ObjectId>();
|
const branchMetaByCode = new Map<string, { _id: Types.ObjectId; name: string }>();
|
||||||
let branchesCreated = 0;
|
let branchesCreated = 0;
|
||||||
let branchesUpdated = 0;
|
let branchesUpdated = 0;
|
||||||
|
|
||||||
@@ -239,67 +446,45 @@ async function main() {
|
|||||||
phoneNumber: branch.phoneNumber,
|
phoneNumber: branch.phoneNumber,
|
||||||
isActive: branch.isActive ?? true,
|
isActive: branch.isActive ?? true,
|
||||||
};
|
};
|
||||||
|
|
||||||
if (existing) {
|
if (existing) {
|
||||||
await Branch.updateOne({ _id: existing._id }, { $set: payload });
|
await Branch.updateOne({ _id: existing._id }, { $set: payload });
|
||||||
branchIdByCode.set(branch.code, existing._id as Types.ObjectId);
|
branchMetaByCode.set(branch.code, {
|
||||||
|
_id: existing._id as Types.ObjectId,
|
||||||
|
name: branch.name,
|
||||||
|
});
|
||||||
branchesUpdated++;
|
branchesUpdated++;
|
||||||
} else {
|
} else {
|
||||||
const created = await Branch.create(payload);
|
const created = await Branch.create(payload);
|
||||||
branchIdByCode.set(branch.code, created._id as Types.ObjectId);
|
branchMetaByCode.set(branch.code, {
|
||||||
|
_id: created._id as Types.ObjectId,
|
||||||
|
name: branch.name,
|
||||||
|
});
|
||||||
branchesCreated++;
|
branchesCreated++;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let expertsCreated = 0;
|
const fileReviewersResult = await upsertRoleUsers({
|
||||||
let expertsUpdated = 0;
|
label: "file-reviewer",
|
||||||
let expertsSkipped = 0;
|
seeds: fileReviewersFile.fileReviewers,
|
||||||
|
model: FileReviewer,
|
||||||
|
clientKey,
|
||||||
|
hashedPassword,
|
||||||
|
branchMetaByCode,
|
||||||
|
role: "file_reviewer",
|
||||||
|
codeFields: ["ThirdPartyExpertiseClaim", "CarBodyExpertiseClaim"],
|
||||||
|
});
|
||||||
|
|
||||||
for (const expert of expertsFile.fieldExperts) {
|
const fileMakersResult = await upsertRoleUsers({
|
||||||
const branchId = branchIdByCode.get(expert.branchCode);
|
label: "file-maker",
|
||||||
if (!branchId) {
|
seeds: fileMakersFile.fileMakers,
|
||||||
console.warn(
|
model: FileMaker,
|
||||||
`Skipping ${expert.nationalCode}: unknown branch ${expert.branchCode}`,
|
clientKey,
|
||||||
);
|
hashedPassword,
|
||||||
expertsSkipped++;
|
branchMetaByCode,
|
||||||
continue;
|
role: "file_maker",
|
||||||
}
|
codeFields: ["ThirdPartyClaimExpertId", "CarBodyClaimExpertId"],
|
||||||
|
});
|
||||||
const payload = {
|
|
||||||
firstName: expert.firstName,
|
|
||||||
lastName: expert.lastName,
|
|
||||||
username: expert.nationalCode,
|
|
||||||
nationalCode: expert.nationalCode,
|
|
||||||
clientKey,
|
|
||||||
branchId,
|
|
||||||
password: hashedPassword,
|
|
||||||
mobile: expert.mobile,
|
|
||||||
role: "field_expert",
|
|
||||||
otp: "",
|
|
||||||
expertCode: expert.expertCode,
|
|
||||||
};
|
|
||||||
|
|
||||||
const existing = await FieldExpert.findOne({
|
|
||||||
clientKey,
|
|
||||||
nationalCode: expert.nationalCode,
|
|
||||||
});
|
|
||||||
|
|
||||||
if (existing) {
|
|
||||||
await FieldExpert.updateOne(
|
|
||||||
{ _id: existing._id },
|
|
||||||
{
|
|
||||||
$set: {
|
|
||||||
...payload,
|
|
||||||
// Do not rotate password on re-seed unless explicitly desired.
|
|
||||||
password: existing.password,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
);
|
|
||||||
expertsUpdated++;
|
|
||||||
} else {
|
|
||||||
await FieldExpert.create(payload);
|
|
||||||
expertsCreated++;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
console.log("Parsian Tehran seed completed.");
|
console.log("Parsian Tehran seed completed.");
|
||||||
console.log({
|
console.log({
|
||||||
@@ -307,11 +492,15 @@ async function main() {
|
|||||||
clientKey: String(clientKey),
|
clientKey: String(clientKey),
|
||||||
branchesCreated,
|
branchesCreated,
|
||||||
branchesUpdated,
|
branchesUpdated,
|
||||||
expertsCreated,
|
fileReviewersCreated: fileReviewersResult.created,
|
||||||
expertsUpdated,
|
fileReviewersUpdated: fileReviewersResult.updated,
|
||||||
expertsSkipped,
|
fileReviewersSkipped: fileReviewersResult.skipped,
|
||||||
|
fileMakersCreated: fileMakersResult.created,
|
||||||
|
fileMakersUpdated: fileMakersResult.updated,
|
||||||
|
fileMakersSkipped: fileMakersResult.skipped,
|
||||||
defaultPassword,
|
defaultPassword,
|
||||||
loginHint: "Use nationalCode + password on POST /actor/login with role field_expert",
|
loginHint:
|
||||||
|
"Use nationalCode + password on POST /actor/login with role file_reviewer or file_maker",
|
||||||
});
|
});
|
||||||
|
|
||||||
await mongoose.disconnect();
|
await mongoose.disconnect();
|
||||||
|
|||||||
@@ -309,7 +309,7 @@ export class ExpertInsurerController {
|
|||||||
@ApiOperation({
|
@ApiOperation({
|
||||||
summary: "Activity timeline for a case",
|
summary: "Activity timeline for a case",
|
||||||
description:
|
description:
|
||||||
"Returns a chronological list of all history events for the blame and/or claim associated with the given publicId. Each event has: source, type, timestamp, actor, metadata.",
|
"Returns a chronological list of all history events for the blame and/or claim associated with the given publicId. Each event includes localized `faLabel`, enriched `actor`/`actorName`, the specific `performedBy` role, plus `performedByCategory` and `performedByFaLabel` for UI rendering.",
|
||||||
})
|
})
|
||||||
async getFileTimeline(@CurrentUser() insurer, @Param("publicId") publicId: string) {
|
async getFileTimeline(@CurrentUser() insurer, @Param("publicId") publicId: string) {
|
||||||
return await this.expertInsurerService.getFileTimeline(insurer.clientKey, publicId);
|
return await this.expertInsurerService.getFileTimeline(insurer.clientKey, publicId);
|
||||||
|
|||||||
@@ -29,6 +29,9 @@ import { ExpertDbService } from "src/users/entities/db-service/expert.db.service
|
|||||||
import { FieldExpertDbService } from "src/users/entities/db-service/field-expert.db.service";
|
import { FieldExpertDbService } from "src/users/entities/db-service/field-expert.db.service";
|
||||||
import { FileMakerDbService } from "src/users/entities/db-service/file-maker.db.service";
|
import { FileMakerDbService } from "src/users/entities/db-service/file-maker.db.service";
|
||||||
import { FileReviewerDbService } from "src/users/entities/db-service/file-reviewer.db.service";
|
import { FileReviewerDbService } from "src/users/entities/db-service/file-reviewer.db.service";
|
||||||
|
import { RegistrarDbService } from "src/users/entities/db-service/registrar.db.service";
|
||||||
|
import { CallCenterAgentDbService } from "src/users/entities/db-service/call-center-agent.db.service";
|
||||||
|
import { UserDbService } from "src/users/entities/db-service/user.db.service";
|
||||||
import { ExpertFileActivityDbService } from "src/users/entities/db-service/expert-file-activity.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";
|
import { ExpertFileActivityType } from "src/users/entities/schema/expert-file-activity.schema";
|
||||||
import { HashService } from "src/utils/hash/hash.service";
|
import { HashService } from "src/utils/hash/hash.service";
|
||||||
@@ -96,6 +99,9 @@ export class ExpertInsurerService {
|
|||||||
private readonly claimSignDbService: ClaimSignDbService,
|
private readonly claimSignDbService: ClaimSignDbService,
|
||||||
private readonly fileMakerDbService: FileMakerDbService,
|
private readonly fileMakerDbService: FileMakerDbService,
|
||||||
private readonly fileReviewerDbService: FileReviewerDbService,
|
private readonly fileReviewerDbService: FileReviewerDbService,
|
||||||
|
private readonly registrarDbService: RegistrarDbService,
|
||||||
|
private readonly callCenterAgentDbService: CallCenterAgentDbService,
|
||||||
|
private readonly userDbService: UserDbService,
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -2150,18 +2156,308 @@ export class ExpertInsurerService {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static readonly TIMELINE_PERFORMER_FA_LABELS: Record<string, string> = {
|
||||||
|
user: "کاربر",
|
||||||
|
field_expert: "کارشناس میدانی",
|
||||||
|
damage_expert: "کارشناس خسارت",
|
||||||
|
file_maker: "پروندهساز",
|
||||||
|
file_reviewer: "بازبین پرونده",
|
||||||
|
registrar: "ثبتکننده",
|
||||||
|
call_center: "اپراتور کالسنتر",
|
||||||
|
system: "سیستم",
|
||||||
|
expert: "کارشناس",
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Maps a raw history `actorType` string to either `"user"` or `"expert"`.
|
* Returns the most specific actor role recorded in history so the frontend can
|
||||||
* - `"user"` → the action was taken by the car-owner / damaged party
|
* distinguish between user/system/back-office/expert actions.
|
||||||
* - `"expert"` → taken by any expert, system, or back-office actor
|
|
||||||
* - `null` → actorType was absent (unknown)
|
|
||||||
*/
|
*/
|
||||||
private static resolveTimelinePerformedBy(
|
private static resolveTimelinePerformedBy(
|
||||||
actorType: string | undefined,
|
actorType: string | undefined,
|
||||||
): "user" | "expert" | null {
|
): string | null {
|
||||||
|
if (!actorType?.trim()) return null;
|
||||||
|
return actorType.trim().toLowerCase();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static resolveTimelinePerformedByCategory(
|
||||||
|
actorType: string | undefined,
|
||||||
|
): "user" | "expert" | "staff" | "system" | null {
|
||||||
|
const performedBy = ExpertInsurerService.resolveTimelinePerformedBy(actorType);
|
||||||
|
if (!performedBy) return null;
|
||||||
|
if (performedBy === "user") return "user";
|
||||||
|
if (performedBy === "system") return "system";
|
||||||
|
if (
|
||||||
|
performedBy === "field_expert" ||
|
||||||
|
performedBy === "damage_expert" ||
|
||||||
|
performedBy === "expert"
|
||||||
|
) {
|
||||||
|
return "expert";
|
||||||
|
}
|
||||||
|
return "staff";
|
||||||
|
}
|
||||||
|
|
||||||
|
private static resolveTimelinePerformedByFaLabel(
|
||||||
|
actorType: string | undefined,
|
||||||
|
): string | null {
|
||||||
|
const performedBy = ExpertInsurerService.resolveTimelinePerformedBy(actorType);
|
||||||
|
if (!performedBy) return null;
|
||||||
|
return (
|
||||||
|
ExpertInsurerService.TIMELINE_PERFORMER_FA_LABELS[performedBy] ??
|
||||||
|
performedBy
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static readonly TIMELINE_PLACEHOLDER_ACTOR_NAMES = new Set([
|
||||||
|
"user",
|
||||||
|
"actor",
|
||||||
|
"expert",
|
||||||
|
"unknown expert",
|
||||||
|
"field_expert",
|
||||||
|
"damage_expert",
|
||||||
|
"file_maker",
|
||||||
|
"file_reviewer",
|
||||||
|
"registrar",
|
||||||
|
"call_center",
|
||||||
|
"system",
|
||||||
|
]);
|
||||||
|
|
||||||
|
private static getDisplayNameFromRecord(record: any): string | null {
|
||||||
|
if (!record) return null;
|
||||||
|
|
||||||
|
const fullName =
|
||||||
|
typeof record.fullName === "string" ? record.fullName.trim() : "";
|
||||||
|
if (fullName) return fullName;
|
||||||
|
|
||||||
|
const firstName =
|
||||||
|
typeof record.firstName === "string" ? record.firstName.trim() : "";
|
||||||
|
const lastName =
|
||||||
|
typeof record.lastName === "string" ? record.lastName.trim() : "";
|
||||||
|
const combined = [firstName, lastName].filter(Boolean).join(" ").trim();
|
||||||
|
return combined || null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static isMeaningfulTimelineActorName(name: string | undefined): boolean {
|
||||||
|
const trimmed = typeof name === "string" ? name.trim() : "";
|
||||||
|
if (!trimmed) return false;
|
||||||
|
return !ExpertInsurerService.TIMELINE_PLACEHOLDER_ACTOR_NAMES.has(
|
||||||
|
trimmed.toLowerCase(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static resolveBlamePartyName(sourceDoc: any, partyRole?: string): string | null {
|
||||||
|
const parties = Array.isArray(sourceDoc?.parties) ? sourceDoc.parties : [];
|
||||||
|
const normalizedPartyRole =
|
||||||
|
typeof partyRole === "string" ? partyRole.trim().toUpperCase() : "";
|
||||||
|
|
||||||
|
const matchedParty = normalizedPartyRole
|
||||||
|
? parties.find(
|
||||||
|
(party: any) =>
|
||||||
|
String(party?.role ?? "").trim().toUpperCase() === normalizedPartyRole,
|
||||||
|
)
|
||||||
|
: null;
|
||||||
|
|
||||||
|
const matchedName = matchedParty?.person?.fullName?.trim?.();
|
||||||
|
if (matchedName) return matchedName;
|
||||||
|
|
||||||
|
if (parties.length === 1) {
|
||||||
|
return parties[0]?.person?.fullName?.trim?.() || null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private async resolveTimelineActualActorType(
|
||||||
|
actor: any,
|
||||||
|
cache: Map<string, string | null> = new Map(),
|
||||||
|
): Promise<string | null> {
|
||||||
|
const rawActorType = ExpertInsurerService.resolveTimelinePerformedBy(
|
||||||
|
actor?.actorType,
|
||||||
|
);
|
||||||
|
const actorId = actor?.actorId ? String(actor.actorId).trim() : "";
|
||||||
|
|
||||||
|
if (!actorId || !Types.ObjectId.isValid(actorId)) {
|
||||||
|
return rawActorType;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (cache.has(actorId)) {
|
||||||
|
return cache.get(actorId) ?? rawActorType ?? null;
|
||||||
|
}
|
||||||
|
|
||||||
|
const objectId = new Types.ObjectId(actorId);
|
||||||
|
const [fileMaker, fileReviewer, registrar, callCenter, fieldExpert, damageExpert, expert, user] = await Promise.all([
|
||||||
|
this.fileMakerDbService.findById(actorId),
|
||||||
|
this.fileReviewerDbService.findById(actorId),
|
||||||
|
this.registrarDbService.findById(actorId),
|
||||||
|
this.callCenterAgentDbService.findById(actorId),
|
||||||
|
this.fieldExpertDbService.findById(actorId),
|
||||||
|
this.damageExpertDbService.findById(actorId),
|
||||||
|
this.expertDbService.findOne({ _id: objectId }),
|
||||||
|
this.userDbService.findOne({ _id: objectId }),
|
||||||
|
]);
|
||||||
|
|
||||||
|
const resolvedActorType = fileMaker
|
||||||
|
? "file_maker"
|
||||||
|
: fileReviewer
|
||||||
|
? "file_reviewer"
|
||||||
|
: registrar
|
||||||
|
? "registrar"
|
||||||
|
: callCenter
|
||||||
|
? "call_center"
|
||||||
|
: fieldExpert
|
||||||
|
? "field_expert"
|
||||||
|
: damageExpert
|
||||||
|
? "damage_expert"
|
||||||
|
: expert
|
||||||
|
? "expert"
|
||||||
|
: user
|
||||||
|
? "user"
|
||||||
|
: rawActorType;
|
||||||
|
|
||||||
|
cache.set(actorId, resolvedActorType ?? null);
|
||||||
|
return resolvedActorType ?? null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private async resolveTimelineActorNameFromId(
|
||||||
|
actorType: string,
|
||||||
|
actorId: string,
|
||||||
|
): Promise<string | null> {
|
||||||
|
if (!Types.ObjectId.isValid(actorId)) return null;
|
||||||
|
|
||||||
|
const objectId = new Types.ObjectId(actorId);
|
||||||
|
|
||||||
|
switch (actorType) {
|
||||||
|
case "user": {
|
||||||
|
const user = await this.userDbService.findOne({ _id: objectId });
|
||||||
|
return ExpertInsurerService.getDisplayNameFromRecord(user);
|
||||||
|
}
|
||||||
|
case "field_expert": {
|
||||||
|
const [fieldExpert, expert] = await Promise.all([
|
||||||
|
this.fieldExpertDbService.findById(actorId),
|
||||||
|
this.expertDbService.findOne({ _id: objectId }),
|
||||||
|
]);
|
||||||
|
return (
|
||||||
|
ExpertInsurerService.getDisplayNameFromRecord(fieldExpert) ??
|
||||||
|
ExpertInsurerService.getDisplayNameFromRecord(expert)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
case "damage_expert": {
|
||||||
|
const [damageExpert, expert] = await Promise.all([
|
||||||
|
this.damageExpertDbService.findById(actorId),
|
||||||
|
this.expertDbService.findOne({ _id: objectId }),
|
||||||
|
]);
|
||||||
|
return (
|
||||||
|
ExpertInsurerService.getDisplayNameFromRecord(damageExpert) ??
|
||||||
|
ExpertInsurerService.getDisplayNameFromRecord(expert)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
case "file_maker": {
|
||||||
|
const fileMaker = await this.fileMakerDbService.findById(actorId);
|
||||||
|
return ExpertInsurerService.getDisplayNameFromRecord(fileMaker);
|
||||||
|
}
|
||||||
|
case "file_reviewer": {
|
||||||
|
const [fileReviewer, expert] = await Promise.all([
|
||||||
|
this.fileReviewerDbService.findById(actorId),
|
||||||
|
this.expertDbService.findOne({ _id: objectId }),
|
||||||
|
]);
|
||||||
|
return (
|
||||||
|
ExpertInsurerService.getDisplayNameFromRecord(fileReviewer) ??
|
||||||
|
ExpertInsurerService.getDisplayNameFromRecord(expert)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
case "registrar": {
|
||||||
|
const registrar = await this.registrarDbService.findById(actorId);
|
||||||
|
return ExpertInsurerService.getDisplayNameFromRecord(registrar);
|
||||||
|
}
|
||||||
|
case "call_center": {
|
||||||
|
const agent = await this.callCenterAgentDbService.findById(actorId);
|
||||||
|
return ExpertInsurerService.getDisplayNameFromRecord(agent);
|
||||||
|
}
|
||||||
|
case "expert": {
|
||||||
|
const expert = await this.expertDbService.findOne({ _id: objectId });
|
||||||
|
return ExpertInsurerService.getDisplayNameFromRecord(expert);
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private buildTimelineActor(
|
||||||
|
actor: any,
|
||||||
|
actorName: string | null,
|
||||||
|
actorType: string | null,
|
||||||
|
): { actorId?: string; actorName?: string; actorType?: string } | null {
|
||||||
|
const actorId = actor?.actorId ? String(actor.actorId).trim() : "";
|
||||||
|
|
||||||
|
if (!actorType && !actorId && !actorName) return null;
|
||||||
|
|
||||||
|
return {
|
||||||
|
...(actorId ? { actorId } : {}),
|
||||||
|
...(actorName ? { actorName } : {}),
|
||||||
|
...(actorType ? { actorType } : {}),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private async resolveTimelineActorName(
|
||||||
|
actor: any,
|
||||||
|
actorType: string | null,
|
||||||
|
sourceDoc?: any,
|
||||||
|
event?: any,
|
||||||
|
cache: Map<string, string | null> = new Map(),
|
||||||
|
): Promise<string | null> {
|
||||||
|
const explicitName =
|
||||||
|
typeof actor?.actorName === "string" ? actor.actorName.trim() : "";
|
||||||
|
if (ExpertInsurerService.isMeaningfulTimelineActorName(explicitName)) {
|
||||||
|
return explicitName;
|
||||||
|
}
|
||||||
|
|
||||||
if (!actorType) return null;
|
if (!actorType) return null;
|
||||||
if (actorType === "user") return "user";
|
|
||||||
return "expert";
|
if (actorType === "system") {
|
||||||
|
return ExpertInsurerService.resolveTimelinePerformedByFaLabel(actorType);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (actorType === "user") {
|
||||||
|
const ownerName = sourceDoc?.owner?.fullName?.trim?.();
|
||||||
|
if (ownerName) return ownerName;
|
||||||
|
|
||||||
|
const blamePartyName = ExpertInsurerService.resolveBlamePartyName(
|
||||||
|
sourceDoc,
|
||||||
|
event?.metadata?.partyRole,
|
||||||
|
);
|
||||||
|
if (blamePartyName) return blamePartyName;
|
||||||
|
}
|
||||||
|
|
||||||
|
const actorId = actor?.actorId ? String(actor.actorId).trim() : "";
|
||||||
|
if (actorId) {
|
||||||
|
const cacheKey = `${actorType}:${actorId}`;
|
||||||
|
if (cache.has(cacheKey)) {
|
||||||
|
return cache.get(cacheKey) ?? null;
|
||||||
|
}
|
||||||
|
|
||||||
|
let resolvedName = await this.resolveTimelineActorNameFromId(
|
||||||
|
actorType,
|
||||||
|
actorId,
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!resolvedName && actorType === "user") {
|
||||||
|
resolvedName =
|
||||||
|
sourceDoc?.owner?.fullName?.trim?.() ??
|
||||||
|
ExpertInsurerService.resolveBlamePartyName(
|
||||||
|
sourceDoc,
|
||||||
|
event?.metadata?.partyRole,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!resolvedName) {
|
||||||
|
resolvedName =
|
||||||
|
ExpertInsurerService.resolveTimelinePerformedByFaLabel(actorType);
|
||||||
|
}
|
||||||
|
|
||||||
|
cache.set(cacheKey, resolvedName ?? null);
|
||||||
|
return resolvedName ?? null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return ExpertInsurerService.resolveTimelinePerformedByFaLabel(actorType);
|
||||||
}
|
}
|
||||||
|
|
||||||
async getFileTimeline(
|
async getFileTimeline(
|
||||||
@@ -2190,6 +2486,8 @@ export class ExpertInsurerService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const events: object[] = [];
|
const events: object[] = [];
|
||||||
|
const actorNameCache = new Map<string, string | null>();
|
||||||
|
const actorTypeCache = new Map<string, string | null>();
|
||||||
|
|
||||||
if (blame) {
|
if (blame) {
|
||||||
const blameDoc = await this.blameRequestDbService.findById(
|
const blameDoc = await this.blameRequestDbService.findById(
|
||||||
@@ -2197,13 +2495,29 @@ export class ExpertInsurerService {
|
|||||||
);
|
);
|
||||||
const blameHistory: any[] = (blameDoc as any)?.history ?? [];
|
const blameHistory: any[] = (blameDoc as any)?.history ?? [];
|
||||||
for (const ev of blameHistory) {
|
for (const ev of blameHistory) {
|
||||||
|
const performedBy = await this.resolveTimelineActualActorType(
|
||||||
|
ev.actor,
|
||||||
|
actorTypeCache,
|
||||||
|
);
|
||||||
|
const actorName = await this.resolveTimelineActorName(
|
||||||
|
ev.actor,
|
||||||
|
performedBy,
|
||||||
|
blameDoc,
|
||||||
|
ev,
|
||||||
|
actorNameCache,
|
||||||
|
);
|
||||||
events.push({
|
events.push({
|
||||||
source: "blame",
|
source: "blame",
|
||||||
type: ev.type,
|
type: ev.type,
|
||||||
faLabel: getEventFaLabel(ev),
|
faLabel: getEventFaLabel(ev),
|
||||||
timestamp: ev.timestamp,
|
timestamp: ev.timestamp,
|
||||||
actor: ev.actor ?? null,
|
actor: this.buildTimelineActor(ev.actor, actorName, performedBy),
|
||||||
performedBy: ExpertInsurerService.resolveTimelinePerformedBy(ev.actor?.actorType),
|
actorName,
|
||||||
|
performedBy,
|
||||||
|
performedByCategory:
|
||||||
|
ExpertInsurerService.resolveTimelinePerformedByCategory(performedBy),
|
||||||
|
performedByFaLabel:
|
||||||
|
ExpertInsurerService.resolveTimelinePerformedByFaLabel(performedBy),
|
||||||
metadata: ev.metadata ?? null,
|
metadata: ev.metadata ?? null,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -2213,17 +2527,34 @@ export class ExpertInsurerService {
|
|||||||
const claimId = String((claim as any)._id);
|
const claimId = String((claim as any)._id);
|
||||||
const rows = (await this.claimCaseDbService.find(
|
const rows = (await this.claimCaseDbService.find(
|
||||||
{ _id: new Types.ObjectId(claimId) },
|
{ _id: new Types.ObjectId(claimId) },
|
||||||
{ lean: true, select: "history createdAt" },
|
{ lean: true, select: "history createdAt owner" },
|
||||||
)) as Record<string, unknown>[];
|
)) as Record<string, unknown>[];
|
||||||
const claimHistory: any[] = (rows[0] as any)?.history ?? [];
|
const claimDoc = rows[0] as any;
|
||||||
|
const claimHistory: any[] = claimDoc?.history ?? [];
|
||||||
for (const ev of claimHistory) {
|
for (const ev of claimHistory) {
|
||||||
|
const performedBy = await this.resolveTimelineActualActorType(
|
||||||
|
ev.actor,
|
||||||
|
actorTypeCache,
|
||||||
|
);
|
||||||
|
const actorName = await this.resolveTimelineActorName(
|
||||||
|
ev.actor,
|
||||||
|
performedBy,
|
||||||
|
claimDoc,
|
||||||
|
ev,
|
||||||
|
actorNameCache,
|
||||||
|
);
|
||||||
events.push({
|
events.push({
|
||||||
source: "claim",
|
source: "claim",
|
||||||
type: ev.type,
|
type: ev.type,
|
||||||
faLabel: getEventFaLabel(ev),
|
faLabel: getEventFaLabel(ev),
|
||||||
timestamp: ev.timestamp,
|
timestamp: ev.timestamp,
|
||||||
actor: ev.actor ?? null,
|
actor: this.buildTimelineActor(ev.actor, actorName, performedBy),
|
||||||
performedBy: ExpertInsurerService.resolveTimelinePerformedBy(ev.actor?.actorType),
|
actorName,
|
||||||
|
performedBy,
|
||||||
|
performedByCategory:
|
||||||
|
ExpertInsurerService.resolveTimelinePerformedByCategory(performedBy),
|
||||||
|
performedByFaLabel:
|
||||||
|
ExpertInsurerService.resolveTimelinePerformedByFaLabel(performedBy),
|
||||||
metadata: ev.metadata ?? null,
|
metadata: ev.metadata ?? null,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -19,9 +19,11 @@ export const EVENT_TYPE_FA_LABELS: Record<string, string> = {
|
|||||||
// File creation
|
// File creation
|
||||||
FILE_CREATED_BY_REGISTRAR: "پرونده توسط ثبتکننده ایجاد شد",
|
FILE_CREATED_BY_REGISTRAR: "پرونده توسط ثبتکننده ایجاد شد",
|
||||||
FILE_CREATED_BY_FIELD_EXPERT: "پرونده توسط کارشناس میدانی ایجاد شد",
|
FILE_CREATED_BY_FIELD_EXPERT: "پرونده توسط کارشناس میدانی ایجاد شد",
|
||||||
|
FILE_CREATED_BY_CALL_CENTER: "پرونده توسط اپراتور کالسنتر ایجاد شد",
|
||||||
|
|
||||||
// Expert link / OTP flow
|
// Expert link / OTP flow
|
||||||
LINK_SENT: "لینک ارسال شد",
|
LINK_SENT: "لینک ارسال شد",
|
||||||
|
CALL_CENTER_LINK_SENT: "لینک توسط اپراتور کالسنتر ارسال شد",
|
||||||
PARTY_OTP_SENT: "کد تأیید ارسال شد",
|
PARTY_OTP_SENT: "کد تأیید ارسال شد",
|
||||||
PARTY_OTP_VERIFIED: "کد تأیید تأیید شد",
|
PARTY_OTP_VERIFIED: "کد تأیید تأیید شد",
|
||||||
PARTY_OTPS_VERIFIED: "کدهای تأیید هر دو طرف تأیید شدند",
|
PARTY_OTPS_VERIFIED: "کدهای تأیید هر دو طرف تأیید شدند",
|
||||||
@@ -30,6 +32,7 @@ export const EVENT_TYPE_FA_LABELS: Record<string, string> = {
|
|||||||
|
|
||||||
// Confession / accident type
|
// Confession / accident type
|
||||||
FIRST_BLAME_CONFESSION_SUBMITTED: "اقرار اولیه طرف اول ثبت شد",
|
FIRST_BLAME_CONFESSION_SUBMITTED: "اقرار اولیه طرف اول ثبت شد",
|
||||||
|
CALL_CENTER_INQUIRY_COMPLETED: "استعلام کالسنتر تکمیل شد",
|
||||||
CAR_BODY_ACCIDENT_TYPE_SUBMITTED: "نوع تصادف بدنه خودرو ثبت شد",
|
CAR_BODY_ACCIDENT_TYPE_SUBMITTED: "نوع تصادف بدنه خودرو ثبت شد",
|
||||||
AUTO_ADVANCED_TO_CAR_BODY_FORM: "پیشرفت خودکار به فرم بدنه خودرو",
|
AUTO_ADVANCED_TO_CAR_BODY_FORM: "پیشرفت خودکار به فرم بدنه خودرو",
|
||||||
AUTO_CONFESSION_SKIPPED: "مرحله اقرار بهصورت خودکار رد شد",
|
AUTO_CONFESSION_SKIPPED: "مرحله اقرار بهصورت خودکار رد شد",
|
||||||
@@ -41,6 +44,7 @@ export const EVENT_TYPE_FA_LABELS: Record<string, string> = {
|
|||||||
SECOND_PARTY_INVITED: "طرف دوم دعوت شد",
|
SECOND_PARTY_INVITED: "طرف دوم دعوت شد",
|
||||||
|
|
||||||
// Accident fields / expert in-person completion
|
// Accident fields / expert in-person completion
|
||||||
|
ACCIDENT_FIELDS_SAVED: "اطلاعات تصادف ثبت شد",
|
||||||
ACCIDENT_FIELDS_SAVED_ADVANCED_TO_SIGNATURES:
|
ACCIDENT_FIELDS_SAVED_ADVANCED_TO_SIGNATURES:
|
||||||
"اطلاعات تصادف ذخیره و به مرحله امضاها پیشرفت شد",
|
"اطلاعات تصادف ذخیره و به مرحله امضاها پیشرفت شد",
|
||||||
EXPERT_COMPLETED_CAR_BODY_FORM_V2: "کارشناس فرم بدنه خودرو را تکمیل کرد",
|
EXPERT_COMPLETED_CAR_BODY_FORM_V2: "کارشناس فرم بدنه خودرو را تکمیل کرد",
|
||||||
@@ -72,12 +76,15 @@ export const EVENT_TYPE_FA_LABELS: Record<string, string> = {
|
|||||||
V3_PARTY_VOICE_UPLOADED: "صدای طرف بارگذاری شد",
|
V3_PARTY_VOICE_UPLOADED: "صدای طرف بارگذاری شد",
|
||||||
V3_PARTY_LOCATION_SAVED: "موقعیت مکانی طرف ذخیره شد",
|
V3_PARTY_LOCATION_SAVED: "موقعیت مکانی طرف ذخیره شد",
|
||||||
V3_PARTY_DESCRIPTION_SAVED: "توضیحات طرف ذخیره شد",
|
V3_PARTY_DESCRIPTION_SAVED: "توضیحات طرف ذخیره شد",
|
||||||
|
V4_PARTY_DESCRIPTION_SAVED: "توضیحات طرف ذخیره شد",
|
||||||
V3_CAR_BODY_ACCIDENT_TYPE_SAVED: "نوع تصادف بدنه خودرو ذخیره شد",
|
V3_CAR_BODY_ACCIDENT_TYPE_SAVED: "نوع تصادف بدنه خودرو ذخیره شد",
|
||||||
|
|
||||||
// V5 blame steps
|
// V5 blame steps
|
||||||
V5_BLAME_ACCIDENT_VIDEO_UPLOADED: "ویدیوی تصادف (نسخه ۵) بارگذاری شد",
|
V5_BLAME_ACCIDENT_VIDEO_UPLOADED: "ویدیوی تصادف (نسخه ۵) بارگذاری شد",
|
||||||
V5_FILE_MAKER_APPROVED: "پروندهساز پرونده را تأیید کرد",
|
V5_FILE_MAKER_APPROVED: "پروندهساز پرونده را تأیید کرد",
|
||||||
V5_FILE_MAKER_REJECTED: "پروندهساز پرونده را رد کرد",
|
V5_FILE_MAKER_REJECTED: "پروندهساز پرونده را رد کرد",
|
||||||
|
V5_FILE_MAKER_REJECTED_BLAME_RESET:
|
||||||
|
"پروندهساز پرونده را رد کرد و پرونده به مرحله قبل بازگردانده شد",
|
||||||
|
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
// Claim-phase events (recorded on ClaimCase.history)
|
// Claim-phase events (recorded on ClaimCase.history)
|
||||||
@@ -102,6 +109,8 @@ export const EVENT_TYPE_FA_LABELS: Record<string, string> = {
|
|||||||
// Documents & media
|
// Documents & media
|
||||||
DOCUMENT_UPLOADED: "مدرک بارگذاری شد",
|
DOCUMENT_UPLOADED: "مدرک بارگذاری شد",
|
||||||
VIDEO_CAPTURE_UPLOADED: "تصویر ویدیویی بارگذاری شد",
|
VIDEO_CAPTURE_UPLOADED: "تصویر ویدیویی بارگذاری شد",
|
||||||
|
PART_CAPTURED: "تصویر قطعه آسیبدیده بارگذاری شد",
|
||||||
|
ANGLE_CAPTURED: "تصویر زاویه خودرو بارگذاری شد",
|
||||||
ALL_FACTORS_UPLOADED_PENDING_VALIDATION:
|
ALL_FACTORS_UPLOADED_PENDING_VALIDATION:
|
||||||
"تمام فاکتورها بارگذاری شدند و در انتظار تأیید هستند",
|
"تمام فاکتورها بارگذاری شدند و در انتظار تأیید هستند",
|
||||||
|
|
||||||
|
|||||||
@@ -135,6 +135,26 @@ export class RequestManagementService {
|
|||||||
throw new BadRequestException(`Step ${stepKey} is not a party-scoped step`);
|
throw new BadRequestException(`Step ${stepKey} is not a party-scoped step`);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private resolveBlameHistoryActorType(actor: any): string {
|
||||||
|
switch (actor?.role) {
|
||||||
|
case RoleEnum.FILE_MAKER:
|
||||||
|
return "file_maker";
|
||||||
|
case RoleEnum.FILE_REVIEWER:
|
||||||
|
return "file_reviewer";
|
||||||
|
case RoleEnum.REGISTRAR:
|
||||||
|
return "registrar";
|
||||||
|
case RoleEnum.CALL_CENTER:
|
||||||
|
return "call_center";
|
||||||
|
case RoleEnum.DAMAGE_EXPERT:
|
||||||
|
return "damage_expert";
|
||||||
|
case RoleEnum.EXPERT:
|
||||||
|
return "expert";
|
||||||
|
case RoleEnum.FIELD_EXPERT:
|
||||||
|
default:
|
||||||
|
return "field_expert";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Reverse map: Fanavaran/Tejarat numeric letter code → Persian plate letter.
|
* Reverse map: Fanavaran/Tejarat numeric letter code → Persian plate letter.
|
||||||
* Tejarat inquiry stores Plk2 as a numeric code (e.g. 12 → "م", 5 → "د").
|
* Tejarat inquiry stores Plk2 as a numeric code (e.g. 12 → "م", 5 → "د").
|
||||||
@@ -4992,7 +5012,7 @@ export class RequestManagementService {
|
|||||||
actor: {
|
actor: {
|
||||||
actorId: expertId,
|
actorId: expertId,
|
||||||
actorName: `${expert.firstName || ""} ${expert.lastName || ""}`.trim(),
|
actorName: `${expert.firstName || ""} ${expert.lastName || ""}`.trim(),
|
||||||
actorType: "field_expert",
|
actorType: this.resolveBlameHistoryActorType(expert),
|
||||||
},
|
},
|
||||||
metadata: { creationMethod: dto.creationMethod, type: dto.type },
|
metadata: { creationMethod: dto.creationMethod, type: dto.type },
|
||||||
});
|
});
|
||||||
@@ -5093,7 +5113,7 @@ export class RequestManagementService {
|
|||||||
actor: {
|
actor: {
|
||||||
actorId: new Types.ObjectId(expert.sub),
|
actorId: new Types.ObjectId(expert.sub),
|
||||||
actorName: `${expert.firstName || ""} ${expert.lastName || ""}`.trim(),
|
actorName: `${expert.firstName || ""} ${expert.lastName || ""}`.trim(),
|
||||||
actorType: "field_expert",
|
actorType: this.resolveBlameHistoryActorType(expert),
|
||||||
},
|
},
|
||||||
metadata: { sentTo, template: "yara-field-expert-link" },
|
metadata: { sentTo, template: "yara-field-expert-link" },
|
||||||
});
|
});
|
||||||
@@ -5279,8 +5299,7 @@ export class RequestManagementService {
|
|||||||
actor: {
|
actor: {
|
||||||
actorId: new Types.ObjectId(expert.sub),
|
actorId: new Types.ObjectId(expert.sub),
|
||||||
actorName: `${expert.firstName || ""} ${expert.lastName || ""}`.trim(),
|
actorName: `${expert.firstName || ""} ${expert.lastName || ""}`.trim(),
|
||||||
actorType:
|
actorType: this.resolveBlameHistoryActorType(expert),
|
||||||
expert?.role === RoleEnum.REGISTRAR ? "registrar" : "field_expert",
|
|
||||||
},
|
},
|
||||||
metadata: {
|
metadata: {
|
||||||
firstPartyVerified: true,
|
firstPartyVerified: true,
|
||||||
@@ -5375,7 +5394,7 @@ export class RequestManagementService {
|
|||||||
actor: {
|
actor: {
|
||||||
actorId: new Types.ObjectId(actor.sub),
|
actorId: new Types.ObjectId(actor.sub),
|
||||||
actorName: `${actor.firstName || ""} ${actor.lastName || ""}`.trim(),
|
actorName: `${actor.firstName || ""} ${actor.lastName || ""}`.trim(),
|
||||||
actorType: "field_expert",
|
actorType: this.resolveBlameHistoryActorType(actor),
|
||||||
},
|
},
|
||||||
metadata: { phoneNumber: phone },
|
metadata: { phoneNumber: phone },
|
||||||
} as any);
|
} as any);
|
||||||
@@ -5516,7 +5535,7 @@ export class RequestManagementService {
|
|||||||
actor: {
|
actor: {
|
||||||
actorId: new Types.ObjectId(actor.sub),
|
actorId: new Types.ObjectId(actor.sub),
|
||||||
actorName: `${actor.firstName || ""} ${actor.lastName || ""}`.trim(),
|
actorName: `${actor.firstName || ""} ${actor.lastName || ""}`.trim(),
|
||||||
actorType: "field_expert",
|
actorType: this.resolveBlameHistoryActorType(actor),
|
||||||
},
|
},
|
||||||
metadata: { partyRole: role, phoneNumber: phone },
|
metadata: { partyRole: role, phoneNumber: phone },
|
||||||
} as any);
|
} as any);
|
||||||
@@ -5558,8 +5577,7 @@ export class RequestManagementService {
|
|||||||
actorId: new Types.ObjectId(actor.sub),
|
actorId: new Types.ObjectId(actor.sub),
|
||||||
actorName:
|
actorName:
|
||||||
`${actor.firstName || ""} ${actor.lastName || ""}`.trim(),
|
`${actor.firstName || ""} ${actor.lastName || ""}`.trim(),
|
||||||
actorType:
|
actorType: this.resolveBlameHistoryActorType(actor),
|
||||||
actor?.role === RoleEnum.REGISTRAR ? "registrar" : "field_expert",
|
|
||||||
},
|
},
|
||||||
metadata: { advancedTo: WorkflowStep.CAR_BODY_ACCIDENT_TYPE },
|
metadata: { advancedTo: WorkflowStep.CAR_BODY_ACCIDENT_TYPE },
|
||||||
} as any);
|
} as any);
|
||||||
@@ -5593,10 +5611,8 @@ export class RequestManagementService {
|
|||||||
type: "AUTO_CONFESSION_SKIPPED",
|
type: "AUTO_CONFESSION_SKIPPED",
|
||||||
actor: {
|
actor: {
|
||||||
actorId: new Types.ObjectId(actor.sub),
|
actorId: new Types.ObjectId(actor.sub),
|
||||||
actorName:
|
actorName: `${actor.firstName || ""} ${actor.lastName || ""}`.trim(),
|
||||||
`${actor.firstName || ""} ${actor.lastName || ""}`.trim(),
|
actorType: this.resolveBlameHistoryActorType(actor),
|
||||||
actorType:
|
|
||||||
actor?.role === RoleEnum.REGISTRAR ? "registrar" : "field_expert",
|
|
||||||
},
|
},
|
||||||
metadata: {
|
metadata: {
|
||||||
reason:
|
reason:
|
||||||
@@ -5621,9 +5637,9 @@ export class RequestManagementService {
|
|||||||
type: "SECOND_PARTY_OTP_VERIFIED_ADVANCED",
|
type: "SECOND_PARTY_OTP_VERIFIED_ADVANCED",
|
||||||
actor: {
|
actor: {
|
||||||
actorId: new Types.ObjectId(actor.sub),
|
actorId: new Types.ObjectId(actor.sub),
|
||||||
actorName: `${actor.firstName || ""} ${actor.lastName || ""}`.trim(),
|
actorName:
|
||||||
actorType:
|
`${actor.firstName || ""} ${actor.lastName || ""}`.trim(),
|
||||||
actor?.role === RoleEnum.REGISTRAR ? "registrar" : "field_expert",
|
actorType: this.resolveBlameHistoryActorType(actor),
|
||||||
},
|
},
|
||||||
metadata: {
|
metadata: {
|
||||||
phoneNumber: phone,
|
phoneNumber: phone,
|
||||||
@@ -6383,7 +6399,7 @@ export class RequestManagementService {
|
|||||||
actor: {
|
actor: {
|
||||||
actorId: new Types.ObjectId(expert.sub),
|
actorId: new Types.ObjectId(expert.sub),
|
||||||
actorName: `${expert.firstName || ""} ${expert.lastName || ""}`.trim(),
|
actorName: `${expert.firstName || ""} ${expert.lastName || ""}`.trim(),
|
||||||
actorType: "field_expert",
|
actorType: this.resolveBlameHistoryActorType(expert),
|
||||||
},
|
},
|
||||||
metadata: {},
|
metadata: {},
|
||||||
} as any);
|
} as any);
|
||||||
@@ -6589,7 +6605,7 @@ export class RequestManagementService {
|
|||||||
actor: {
|
actor: {
|
||||||
actorId: new Types.ObjectId(expert.sub),
|
actorId: new Types.ObjectId(expert.sub),
|
||||||
actorName: `${expert.firstName || ""} ${expert.lastName || ""}`.trim(),
|
actorName: `${expert.firstName || ""} ${expert.lastName || ""}`.trim(),
|
||||||
actorType: "field_expert",
|
actorType: this.resolveBlameHistoryActorType(expert),
|
||||||
},
|
},
|
||||||
metadata: { guiltyPartyPhoneNumber: formData.guiltyPartyPhoneNumber },
|
metadata: { guiltyPartyPhoneNumber: formData.guiltyPartyPhoneNumber },
|
||||||
} as any);
|
} as any);
|
||||||
@@ -6653,8 +6669,7 @@ export class RequestManagementService {
|
|||||||
actor: {
|
actor: {
|
||||||
actorId: new Types.ObjectId(expert.sub),
|
actorId: new Types.ObjectId(expert.sub),
|
||||||
actorName: `${expert.firstName || ""} ${expert.lastName || ""}`.trim(),
|
actorName: `${expert.firstName || ""} ${expert.lastName || ""}`.trim(),
|
||||||
actorType:
|
actorType: this.resolveBlameHistoryActorType(expert),
|
||||||
expert?.role === RoleEnum.REGISTRAR ? "registrar" : "field_expert",
|
|
||||||
},
|
},
|
||||||
metadata: {
|
metadata: {
|
||||||
hasLocation: true,
|
hasLocation: true,
|
||||||
@@ -6714,7 +6729,7 @@ export class RequestManagementService {
|
|||||||
actor: {
|
actor: {
|
||||||
actorId: new Types.ObjectId(expert.sub),
|
actorId: new Types.ObjectId(expert.sub),
|
||||||
actorName: `${expert.firstName || ""} ${expert.lastName || ""}`.trim(),
|
actorName: `${expert.firstName || ""} ${expert.lastName || ""}`.trim(),
|
||||||
actorType: "field_expert",
|
actorType: this.resolveBlameHistoryActorType(expert),
|
||||||
},
|
},
|
||||||
metadata: { videoId: firstParty.evidence.videoId },
|
metadata: { videoId: firstParty.evidence.videoId },
|
||||||
} as any);
|
} as any);
|
||||||
@@ -6771,7 +6786,7 @@ export class RequestManagementService {
|
|||||||
actor: {
|
actor: {
|
||||||
actorId: new Types.ObjectId(expert.sub),
|
actorId: new Types.ObjectId(expert.sub),
|
||||||
actorName: `${expert.firstName || ""} ${expert.lastName || ""}`.trim(),
|
actorName: `${expert.firstName || ""} ${expert.lastName || ""}`.trim(),
|
||||||
actorType: "field_expert",
|
actorType: this.resolveBlameHistoryActorType(expert),
|
||||||
},
|
},
|
||||||
metadata: {},
|
metadata: {},
|
||||||
} as any);
|
} as any);
|
||||||
@@ -7001,7 +7016,7 @@ export class RequestManagementService {
|
|||||||
actorId: new Types.ObjectId(String(expert.sub)),
|
actorId: new Types.ObjectId(String(expert.sub)),
|
||||||
actorName:
|
actorName:
|
||||||
`${expert.firstName || ""} ${expert.lastName || ""}`.trim(),
|
`${expert.firstName || ""} ${expert.lastName || ""}`.trim(),
|
||||||
actorType: "field_expert",
|
actorType: this.resolveBlameHistoryActorType(expert),
|
||||||
},
|
},
|
||||||
metadata: {
|
metadata: {
|
||||||
accidentWay: fields.accidentWay,
|
accidentWay: fields.accidentWay,
|
||||||
@@ -9065,7 +9080,7 @@ export class RequestManagementService {
|
|||||||
actorId: new Types.ObjectId(actor.sub),
|
actorId: new Types.ObjectId(actor.sub),
|
||||||
actorName:
|
actorName:
|
||||||
`${actor.firstName || ""} ${actor.lastName || ""}`.trim(),
|
`${actor.firstName || ""} ${actor.lastName || ""}`.trim(),
|
||||||
actorType: "field_expert",
|
actorType: this.resolveBlameHistoryActorType(actor),
|
||||||
},
|
},
|
||||||
timestamp: new Date(),
|
timestamp: new Date(),
|
||||||
metadata: {
|
metadata: {
|
||||||
@@ -9904,7 +9919,7 @@ export class RequestManagementService {
|
|||||||
actor: {
|
actor: {
|
||||||
actorId: new Types.ObjectId(expert.sub),
|
actorId: new Types.ObjectId(expert.sub),
|
||||||
actorName: `${expert.firstName || ""} ${expert.lastName || ""}`.trim(),
|
actorName: `${expert.firstName || ""} ${expert.lastName || ""}`.trim(),
|
||||||
actorType: "field_expert",
|
actorType: this.resolveBlameHistoryActorType(expert),
|
||||||
},
|
},
|
||||||
metadata: { partyRole, accepted: isAccept },
|
metadata: { partyRole, accepted: isAccept },
|
||||||
} as any);
|
} as any);
|
||||||
@@ -9971,7 +9986,7 @@ export class RequestManagementService {
|
|||||||
actor: {
|
actor: {
|
||||||
actorId: new Types.ObjectId(String(expert.sub)),
|
actorId: new Types.ObjectId(String(expert.sub)),
|
||||||
actorName: `${expert.firstName || ""} ${expert.lastName || ""}`.trim(),
|
actorName: `${expert.firstName || ""} ${expert.lastName || ""}`.trim(),
|
||||||
actorType: "field_expert",
|
actorType: this.resolveBlameHistoryActorType(expert),
|
||||||
},
|
},
|
||||||
metadata: { accidentWay: fields.accidentWay },
|
metadata: { accidentWay: fields.accidentWay },
|
||||||
} as any);
|
} as any);
|
||||||
@@ -10089,11 +10104,11 @@ export class RequestManagementService {
|
|||||||
|
|
||||||
if (!Array.isArray(req.history)) req.history = [];
|
if (!Array.isArray(req.history)) req.history = [];
|
||||||
req.history.push({
|
req.history.push({
|
||||||
type: "V3_BLAME_ACCIDENT_VIDEO_UPLOADED",
|
type: "V5_BLAME_ACCIDENT_VIDEO_UPLOADED",
|
||||||
actor: {
|
actor: {
|
||||||
actorId: new Types.ObjectId(expert.sub),
|
actorId: new Types.ObjectId(expert.sub),
|
||||||
actorName: `${expert.firstName || ""} ${expert.lastName || ""}`.trim(),
|
actorName: `${expert.firstName || ""} ${expert.lastName || ""}`.trim(),
|
||||||
actorType: "field_expert",
|
actorType: this.resolveBlameHistoryActorType(expert),
|
||||||
},
|
},
|
||||||
metadata: { videoId: videoId ?? null },
|
metadata: { videoId: videoId ?? null },
|
||||||
} as any);
|
} as any);
|
||||||
@@ -10118,7 +10133,7 @@ export class RequestManagementService {
|
|||||||
actorId: new Types.ObjectId(expert.sub),
|
actorId: new Types.ObjectId(expert.sub),
|
||||||
actorName:
|
actorName:
|
||||||
`${expert.firstName || ""} ${expert.lastName || ""}`.trim(),
|
`${expert.firstName || ""} ${expert.lastName || ""}`.trim(),
|
||||||
actorType: "field_expert",
|
actorType: this.resolveBlameHistoryActorType(expert),
|
||||||
},
|
},
|
||||||
timestamp: new Date(),
|
timestamp: new Date(),
|
||||||
metadata: {
|
metadata: {
|
||||||
@@ -10232,7 +10247,7 @@ export class RequestManagementService {
|
|||||||
actor: {
|
actor: {
|
||||||
actorId: new Types.ObjectId(actor.sub),
|
actorId: new Types.ObjectId(actor.sub),
|
||||||
actorName: `${actor.firstName || ""} ${actor.lastName || ""}`.trim(),
|
actorName: `${actor.firstName || ""} ${actor.lastName || ""}`.trim(),
|
||||||
actorType: "field_expert",
|
actorType: this.resolveBlameHistoryActorType(actor),
|
||||||
},
|
},
|
||||||
metadata: { partyRole: role, voiceId: String((voiceDoc as any)._id) },
|
metadata: { partyRole: role, voiceId: String((voiceDoc as any)._id) },
|
||||||
} as any);
|
} as any);
|
||||||
@@ -10275,7 +10290,7 @@ export class RequestManagementService {
|
|||||||
actor: {
|
actor: {
|
||||||
actorId: new Types.ObjectId(actor.sub),
|
actorId: new Types.ObjectId(actor.sub),
|
||||||
actorName: `${actor.firstName || ""} ${actor.lastName || ""}`.trim(),
|
actorName: `${actor.firstName || ""} ${actor.lastName || ""}`.trim(),
|
||||||
actorType: "field_expert",
|
actorType: this.resolveBlameHistoryActorType(actor),
|
||||||
},
|
},
|
||||||
metadata: { partyRole: role, location: body },
|
metadata: { partyRole: role, location: body },
|
||||||
} as any);
|
} as any);
|
||||||
@@ -10309,7 +10324,7 @@ export class RequestManagementService {
|
|||||||
actor: {
|
actor: {
|
||||||
actorId: new Types.ObjectId(actor.sub),
|
actorId: new Types.ObjectId(actor.sub),
|
||||||
actorName: `${actor.firstName || ""} ${actor.lastName || ""}`.trim(),
|
actorName: `${actor.firstName || ""} ${actor.lastName || ""}`.trim(),
|
||||||
actorType: "field_expert",
|
actorType: this.resolveBlameHistoryActorType(actor),
|
||||||
},
|
},
|
||||||
metadata: { partyRole: role },
|
metadata: { partyRole: role },
|
||||||
} as any);
|
} as any);
|
||||||
@@ -10369,7 +10384,7 @@ export class RequestManagementService {
|
|||||||
actor: {
|
actor: {
|
||||||
actorId: new Types.ObjectId(actor.sub),
|
actorId: new Types.ObjectId(actor.sub),
|
||||||
actorName: `${actor.firstName || ""} ${actor.lastName || ""}`.trim(),
|
actorName: `${actor.firstName || ""} ${actor.lastName || ""}`.trim(),
|
||||||
actorType: "field_expert",
|
actorType: this.resolveBlameHistoryActorType(actor),
|
||||||
},
|
},
|
||||||
metadata: {
|
metadata: {
|
||||||
partyRole: role,
|
partyRole: role,
|
||||||
@@ -10425,7 +10440,7 @@ export class RequestManagementService {
|
|||||||
actor: {
|
actor: {
|
||||||
actorId: new Types.ObjectId(actor.sub),
|
actorId: new Types.ObjectId(actor.sub),
|
||||||
actorName: `${actor.firstName || ""} ${actor.lastName || ""}`.trim(),
|
actorName: `${actor.firstName || ""} ${actor.lastName || ""}`.trim(),
|
||||||
actorType: "field_expert",
|
actorType: this.resolveBlameHistoryActorType(actor),
|
||||||
},
|
},
|
||||||
metadata: { car: body.car, object: body.object },
|
metadata: { car: body.car, object: body.object },
|
||||||
} as any);
|
} as any);
|
||||||
|
|||||||
@@ -2,6 +2,19 @@ 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, versionKey: false })
|
||||||
|
export class FileMakerLocationModel {
|
||||||
|
@Prop({ type: String, required: true })
|
||||||
|
id: string;
|
||||||
|
|
||||||
|
@Prop({ type: String, required: true })
|
||||||
|
name: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const FileMakerLocationDbSchema = SchemaFactory.createForClass(
|
||||||
|
FileMakerLocationModel,
|
||||||
|
);
|
||||||
|
|
||||||
@Schema({
|
@Schema({
|
||||||
collection: "file-maker",
|
collection: "file-maker",
|
||||||
versionKey: false,
|
versionKey: false,
|
||||||
@@ -31,6 +44,9 @@ export class FileMakerModel {
|
|||||||
@Prop({ type: Types.ObjectId, index: true })
|
@Prop({ type: Types.ObjectId, index: true })
|
||||||
branchId?: Types.ObjectId;
|
branchId?: Types.ObjectId;
|
||||||
|
|
||||||
|
@Prop({ type: [FileMakerLocationDbSchema], default: [] })
|
||||||
|
locations?: FileMakerLocationModel[];
|
||||||
|
|
||||||
@Prop({ required: true })
|
@Prop({ required: true })
|
||||||
password: string;
|
password: string;
|
||||||
|
|
||||||
@@ -49,6 +65,12 @@ export class FileMakerModel {
|
|||||||
@Prop({ type: "string", required: false })
|
@Prop({ type: "string", required: false })
|
||||||
expertCode?: string;
|
expertCode?: string;
|
||||||
|
|
||||||
|
@Prop({ type: String, required: false })
|
||||||
|
ThirdPartyClaimExpertId?: string;
|
||||||
|
|
||||||
|
@Prop({ type: String, required: false })
|
||||||
|
CarBodyClaimExpertId?: string;
|
||||||
|
|
||||||
createdAt: Date;
|
createdAt: Date;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -2,6 +2,19 @@ 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, versionKey: false })
|
||||||
|
export class FileReviewerLocationModel {
|
||||||
|
@Prop({ type: String, required: true })
|
||||||
|
id: string;
|
||||||
|
|
||||||
|
@Prop({ type: String, required: true })
|
||||||
|
name: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const FileReviewerLocationDbSchema = SchemaFactory.createForClass(
|
||||||
|
FileReviewerLocationModel,
|
||||||
|
);
|
||||||
|
|
||||||
@Schema({
|
@Schema({
|
||||||
collection: "file-reviewer",
|
collection: "file-reviewer",
|
||||||
versionKey: false,
|
versionKey: false,
|
||||||
@@ -31,6 +44,9 @@ export class FileReviewerModel {
|
|||||||
@Prop({ type: Types.ObjectId, index: true })
|
@Prop({ type: Types.ObjectId, index: true })
|
||||||
branchId?: Types.ObjectId;
|
branchId?: Types.ObjectId;
|
||||||
|
|
||||||
|
@Prop({ type: [FileReviewerLocationDbSchema], default: [] })
|
||||||
|
locations?: FileReviewerLocationModel[];
|
||||||
|
|
||||||
@Prop({ required: true })
|
@Prop({ required: true })
|
||||||
password: string;
|
password: string;
|
||||||
|
|
||||||
@@ -49,6 +65,12 @@ export class FileReviewerModel {
|
|||||||
@Prop({ type: "string", required: false })
|
@Prop({ type: "string", required: false })
|
||||||
expertCode?: string;
|
expertCode?: string;
|
||||||
|
|
||||||
|
@Prop({ type: String, required: false })
|
||||||
|
ThirdPartyExpertiseClaim?: string;
|
||||||
|
|
||||||
|
@Prop({ type: String, required: false })
|
||||||
|
CarBodyExpertiseClaim?: string;
|
||||||
|
|
||||||
createdAt: Date;
|
createdAt: Date;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user