forked from Yara724/api
756 lines
24 KiB
TypeScript
756 lines
24 KiB
TypeScript
import {
|
|
BadRequestException,
|
|
ForbiddenException,
|
|
Injectable,
|
|
Logger,
|
|
NotFoundException,
|
|
} from "@nestjs/common";
|
|
import { RequestManagementDbService } from "src/request-management/entities/db-service/request-management.db.service";
|
|
import { AllRequestDtoRs } from "./dto/all-request.dto";
|
|
import { UserType } from "src/Types&Enums/userType.enum";
|
|
import { SubmitReplyDto } from "./dto/reply.dto";
|
|
import { Types } from "mongoose";
|
|
import { BlameVideoDbService } from "src/request-management/entities/db-service/blame-video.db.service";
|
|
import { BlameVoiceDbService } from "src/request-management/entities/db-service/blame.voice.db.service";
|
|
import { ClientDbService } from "src/client/entities/db-service/client.db.service";
|
|
import { ReqBlameStatus } from "src/Types&Enums/blame-request-management/status.enum";
|
|
import { buildFileLink } from "src/helpers/urlCreator";
|
|
import { readFile } from "fs/promises";
|
|
import { ExpertDbService } from "src/users/entities/db-service/expert.db.service";
|
|
import { BlameDocumentDbService } from "src/request-management/entities/db-service/blame-document.db.service";
|
|
import { UserSignDbService } from "src/request-management/entities/db-service/sign.db.service";
|
|
|
|
interface CheckedRequestEntry {
|
|
CheckedRequest?: {
|
|
actorId: string;
|
|
fullName: string;
|
|
};
|
|
[key: string]: any;
|
|
}
|
|
|
|
@Injectable()
|
|
export class ExpertBlameService {
|
|
private readonly logger = new Logger(ExpertBlameService.name);
|
|
constructor(
|
|
private readonly requestManagementDbService: RequestManagementDbService,
|
|
private readonly clientDbService: ClientDbService,
|
|
private readonly blameVideoDbService: BlameVideoDbService,
|
|
private readonly blameVoiceDbService: BlameVoiceDbService,
|
|
private readonly expertDbService: ExpertDbService,
|
|
private readonly blameDocumentDbService: BlameDocumentDbService,
|
|
private readonly userSignDbService: UserSignDbService,
|
|
) {}
|
|
|
|
async findAll(actor: any): Promise<AllRequestDtoRs> {
|
|
// 1. Fetch all potentially relevant requests from the database.
|
|
// Exclude CAR_BODY type requests as they are automatically handled and don't need expert review
|
|
const allRequests = await this.requestManagementDbService.findAll({
|
|
"firstPartyDetails.firstPartyPlate": { $ne: null },
|
|
"secondPartyDetails.secondPartyPlate": { $ne: null },
|
|
blameStatus: {
|
|
$in: [
|
|
ReqBlameStatus.UnChecked,
|
|
ReqBlameStatus.CloseRequest,
|
|
ReqBlameStatus.CheckAgain,
|
|
ReqBlameStatus.ReviewRequest,
|
|
],
|
|
},
|
|
// Both parties must have submitted their initial forms
|
|
"firstPartyDetails.firstPartyInitialForm": { $exists: true },
|
|
"secondPartyDetails.secondPartyInitialForm": { $exists: true },
|
|
type: { $ne: "CAR_BODY" }, // Exclude CAR_BODY type requests
|
|
});
|
|
|
|
// 2. Filter requests that need expert review based on initial form logic
|
|
// Expert is needed when there's a conflict (both claim damaged, both claim guilty, etc.)
|
|
// Expert is NOT needed when one says imDamaged and the other says imGuilty (auto-resolved)
|
|
const requestsNeedingExpert = [];
|
|
for (const request of allRequests) {
|
|
const firstPartyForm = request.firstPartyDetails?.firstPartyInitialForm;
|
|
const secondPartyForm = request.secondPartyDetails?.secondPartyInitialForm;
|
|
|
|
if (!firstPartyForm || !secondPartyForm) {
|
|
continue; // Skip if forms are not complete
|
|
}
|
|
|
|
// Check if this can be auto-resolved (one says damaged, other says guilty)
|
|
const canAutoResolve =
|
|
(firstPartyForm.imDamaged && secondPartyForm.imGuilty) ||
|
|
(secondPartyForm.imDamaged && firstPartyForm.imGuilty);
|
|
|
|
// If it can be auto-resolved, skip it (no expert needed)
|
|
if (canAutoResolve) {
|
|
continue;
|
|
}
|
|
|
|
// Otherwise, expert is needed (both damaged, both guilty, or other conflicts)
|
|
requestsNeedingExpert.push(request);
|
|
}
|
|
|
|
// 3. Filter the requests in memory based on the expert's specific access rights.
|
|
const visibleRequests = [];
|
|
for (const request of requestsNeedingExpert) {
|
|
// For expert-initiated files, only show to the initiating expert
|
|
if (request.expertInitiated && request.initiatedBy) {
|
|
if (String(request.initiatedBy) !== actor.sub) {
|
|
continue; // Skip if not the initiating expert
|
|
}
|
|
// Expert-initiated files are always visible to the initiating expert
|
|
visibleRequests.push(request);
|
|
continue;
|
|
}
|
|
|
|
// For normal files, use existing client-based filtering
|
|
const firstPartyClientId =
|
|
request.firstPartyDetails?.firstPartyClient?.clientId?.toString();
|
|
const secondPartyClientId =
|
|
request.secondPartyDetails?.secondPartyClient?.clientId?.toString();
|
|
|
|
const partyClientIds = [firstPartyClientId, secondPartyClientId]
|
|
.filter(Boolean)
|
|
.map((id) => new Types.ObjectId(id));
|
|
|
|
if (partyClientIds.length === 0) {
|
|
continue;
|
|
}
|
|
|
|
let clientQuery: any = { _id: { $in: partyClientIds } };
|
|
|
|
if (actor.userType === UserType.LEGAL) {
|
|
clientQuery = {
|
|
$and: [
|
|
{ _id: { $in: partyClientIds } },
|
|
{ _id: new Types.ObjectId(actor.clientKey) },
|
|
],
|
|
};
|
|
}
|
|
|
|
const client = await this.clientDbService.findOne(clientQuery);
|
|
|
|
if (!client) {
|
|
continue;
|
|
}
|
|
|
|
const isExpertTypeMatch = client.useExpertMode === actor.userType;
|
|
if (!isExpertTypeMatch) {
|
|
continue;
|
|
}
|
|
|
|
if (request.blameStatus === ReqBlameStatus.CheckAgain) {
|
|
if (String(request.actorLocked?.actorId) === actor.sub) {
|
|
visibleRequests.push(request);
|
|
}
|
|
} else {
|
|
visibleRequests.push(request);
|
|
}
|
|
}
|
|
|
|
return new AllRequestDtoRs(visibleRequests);
|
|
}
|
|
|
|
public unlockApi(request, timer) {
|
|
return setTimeout(async () => {
|
|
try {
|
|
const r = await this.requestManagementDbService.findOne(request._id);
|
|
|
|
const updateExp: any = {
|
|
lockFile: false,
|
|
unlockTime: null,
|
|
};
|
|
|
|
const shouldDecrementChecked =
|
|
r.blameStatus === ReqBlameStatus.ReviewRequest &&
|
|
!r.expertSubmitReply &&
|
|
r.actorLocked?.actorId;
|
|
|
|
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() },
|
|
},
|
|
);
|
|
|
|
this.logger.warn(
|
|
`Request ${r._id} unlocked without reply — expert stats rolled back.`,
|
|
);
|
|
}
|
|
|
|
await this.requestManagementDbService.findByIdAndUpdate(
|
|
r._id.toString(),
|
|
updateExp,
|
|
);
|
|
this.logger.log(`Unlock completed for request: ${r._id}`);
|
|
} catch (error) {
|
|
this.logger.error(`Failed to unlock request ${request._id}`, error);
|
|
}
|
|
}, timer);
|
|
}
|
|
|
|
public scheduleUnlock(request) {
|
|
const unlockDelay = new Date(request.unlockTime).getTime() - Date.now();
|
|
if (unlockDelay <= 0) return; // already expired
|
|
|
|
setTimeout(async () => {
|
|
try {
|
|
// Double-check latest state before unlocking
|
|
const current = await this.requestManagementDbService.findOne(
|
|
request._id,
|
|
);
|
|
|
|
if (!current.lockFile || current.expertSubmitReply) {
|
|
// Already unlocked or replied
|
|
return;
|
|
}
|
|
|
|
// If expiry passed
|
|
if (current.unlockTime && new Date(current.unlockTime) <= new Date()) {
|
|
const shouldRollbackStats =
|
|
current.blameStatus === ReqBlameStatus.ReviewRequest &&
|
|
!current.expertSubmitReply &&
|
|
current.actorLocked?.actorId;
|
|
|
|
const update: any = {
|
|
lockFile: false,
|
|
unlockTime: null,
|
|
lockTime: null,
|
|
};
|
|
|
|
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() },
|
|
},
|
|
);
|
|
|
|
this.logger.warn(
|
|
`Request ${current._id} auto-unlocked (no reply) — expert stats rolled back.`,
|
|
);
|
|
}
|
|
|
|
await this.requestManagementDbService.findByIdAndUpdate(
|
|
String(current._id),
|
|
update,
|
|
);
|
|
|
|
this.logger.log(`Auto-unlock completed for request: ${current._id}`);
|
|
}
|
|
} catch (err) {
|
|
this.logger.error(`Auto-unlock failed for ${request._id}`, err);
|
|
}
|
|
}, unlockDelay);
|
|
}
|
|
|
|
async findOne(requestId: string, actorId: string) {
|
|
// 1. Fetch the main request document
|
|
const request = await this.requestManagementDbService.findOne(requestId);
|
|
if (!request) {
|
|
throw new NotFoundException("Request not found");
|
|
}
|
|
|
|
// 1.5. Reject CAR_BODY type requests as they don't need expert review
|
|
if (request.type === "CAR_BODY") {
|
|
throw new ForbiddenException(
|
|
"CAR_BODY type requests are automatically handled and do not require expert review.",
|
|
);
|
|
}
|
|
|
|
// 2. Initial validation to ensure the expert has access
|
|
// Check if locked by current expert and lock is still active
|
|
const isLockedByCurrentExpert =
|
|
String(request?.actorLocked?.actorId) === actorId && request.lockFile;
|
|
|
|
// Check if lock has expired
|
|
let isLockExpired = false;
|
|
if (request.unlockTime) {
|
|
const unlockTime = new Date(request.unlockTime).getTime();
|
|
const now = Date.now();
|
|
isLockExpired = now >= unlockTime;
|
|
}
|
|
|
|
if (isLockedByCurrentExpert && !isLockExpired) {
|
|
// This is the correct expert, and the file is locked to them, which is fine.
|
|
// They can access it even if they closed the browser and came back.
|
|
} else if (
|
|
(request.lockFile && !isLockExpired) ||
|
|
request.blameStatus === ReqBlameStatus.ReviewRequest
|
|
) {
|
|
// The file is locked by someone else, or lock expired but status hasn't updated yet
|
|
// Only block if lock is still active and not by current expert
|
|
if (request.lockFile && !isLockExpired && !isLockedByCurrentExpert) {
|
|
throw new BadRequestException("Request is locked by another expert");
|
|
}
|
|
}
|
|
|
|
// 3. Populate the resend links if the data exists
|
|
if (request.expertResendReply) {
|
|
const populatePartyLinks = async (
|
|
partyKey: "firstParty" | "secondParty",
|
|
) => {
|
|
const partyReply = request.expertResendReply[partyKey];
|
|
if (!partyReply) return;
|
|
|
|
// Populate the voice link
|
|
if (partyReply.voice) {
|
|
const voiceDoc = await this.userSignDbService.findById(
|
|
partyReply.voice.toString(),
|
|
);
|
|
if (voiceDoc) {
|
|
partyReply.voice = buildFileLink(voiceDoc.path);
|
|
}
|
|
}
|
|
|
|
// Populate the document links
|
|
if (partyReply.documents) {
|
|
for (const docType in partyReply.documents) {
|
|
const docId = partyReply.documents[docType];
|
|
if (docId) {
|
|
const doc = await this.blameDocumentDbService.findById(
|
|
docId.toString(),
|
|
);
|
|
if (doc) {
|
|
partyReply.documents[docType] = buildFileLink(doc.path); // Replace ID with URL
|
|
}
|
|
}
|
|
}
|
|
}
|
|
};
|
|
|
|
await populatePartyLinks("firstParty");
|
|
await populatePartyLinks("secondParty");
|
|
}
|
|
|
|
// 4. Populate the Signature Links from the correct reply object
|
|
// First, determine which reply object is the final, authoritative one.
|
|
const finalReply =
|
|
request.expertSubmitReplyFinal || request.expertSubmitReply;
|
|
|
|
if (finalReply) {
|
|
const populateSignatureLink = async (
|
|
commentField: "firstPartyComment" | "secondPartyComment",
|
|
) => {
|
|
const comment = finalReply[commentField];
|
|
// Check if the comment and its signDetail with a fileId exist
|
|
if (comment?.signDetail?.fileId) {
|
|
const signDoc = await this.userSignDbService.findById(
|
|
comment.signDetail.fileId.toString(),
|
|
);
|
|
if (signDoc) {
|
|
// Add a new 'fileUrl' property to the signDetail object
|
|
(comment.signDetail as any).fileUrl = buildFileLink(signDoc.path);
|
|
}
|
|
}
|
|
};
|
|
// Run the population for both parties' signatures on the correct reply object.
|
|
await populateSignatureLink("firstPartyComment");
|
|
await populateSignatureLink("secondPartyComment");
|
|
}
|
|
|
|
// 5. Format the date for display with Iran timezone (Asia/Tehran)
|
|
if (request.createdAt) {
|
|
const formattingOptions: Intl.DateTimeFormatOptions = {
|
|
timeZone: "Asia/Tehran",
|
|
year: "numeric",
|
|
month: "2-digit",
|
|
day: "2-digit",
|
|
hour: "2-digit",
|
|
minute: "2-digit",
|
|
second: "2-digit",
|
|
};
|
|
request.createdAt = new Date(request.createdAt).toLocaleString(
|
|
"fa-IR",
|
|
formattingOptions,
|
|
);
|
|
}
|
|
|
|
// 6. Return the fully populated request object
|
|
return request;
|
|
}
|
|
|
|
async lockRequest(requestId: string, actorDetail) {
|
|
const fifteenMinutes = new Date(Date.now() + 15 * 60 * 1000);
|
|
|
|
const updateResult = await this.requestManagementDbService.findOneAndUpdate(
|
|
{
|
|
_id: requestId,
|
|
lockFile: false,
|
|
blameStatus: { $ne: ReqBlameStatus.UserPending },
|
|
},
|
|
{
|
|
$set: {
|
|
lockFile: true,
|
|
blameStatus: ReqBlameStatus.ReviewRequest,
|
|
unlockTime: fifteenMinutes,
|
|
lockTime: new Date(),
|
|
actorLocked: {
|
|
fullName: actorDetail.fullName,
|
|
actorId: new Types.ObjectId(actorDetail.sub),
|
|
},
|
|
},
|
|
$push: {
|
|
actorsChecker: {
|
|
[ReqBlameStatus.ReviewRequest]: {
|
|
fullName: actorDetail.fullName,
|
|
actorId: new Types.ObjectId(actorDetail.sub),
|
|
},
|
|
Date: new Date(),
|
|
},
|
|
},
|
|
},
|
|
{ new: true },
|
|
);
|
|
|
|
if (!updateResult) {
|
|
throw new BadRequestException("Request already locked or invalid status");
|
|
}
|
|
|
|
// Update expert stats atomically (use findOneAndUpdate with conditions)
|
|
await this.updateDamageExpertStats(actorDetail.sub, requestId, "checked");
|
|
|
|
this.scheduleUnlock(updateResult);
|
|
return { _id: requestId, lock: true };
|
|
}
|
|
|
|
private async updateDamageExpertStats(
|
|
expertId: string,
|
|
requestId: string,
|
|
type: "checked" | "handled",
|
|
) {
|
|
if (!expertId || !requestId || !["checked", "handled"].includes(type)) {
|
|
console.warn("Invalid expertId, requestId, or type");
|
|
return;
|
|
}
|
|
|
|
const expert = await this.expertDbService.findOne({
|
|
_id: new Types.ObjectId(expertId),
|
|
});
|
|
|
|
if (!expert) {
|
|
console.warn("Expert not found:", expertId);
|
|
return;
|
|
}
|
|
|
|
const requestIdStr = new Types.ObjectId(requestId).toString();
|
|
const countedRequestIds =
|
|
expert.countedRequests?.map((id) => id.toString()) || [];
|
|
|
|
if (type === "checked" && countedRequestIds.includes(requestIdStr)) {
|
|
console.log(
|
|
`Request ${requestIdStr} already checked for expert ${expertId}`,
|
|
);
|
|
return;
|
|
}
|
|
|
|
const update: any = { $inc: {}, $push: {} };
|
|
|
|
if (type === "checked") {
|
|
update.$inc["requestStats.totalChecked"] = 1;
|
|
update.$push["countedRequests"] = requestIdStr;
|
|
} else if (type === "handled") {
|
|
update.$inc["requestStats.totalHandled"] = 1;
|
|
|
|
if (countedRequestIds.includes(requestIdStr)) {
|
|
update.$inc["requestStats.totalChecked"] = -1;
|
|
}
|
|
|
|
if (!countedRequestIds.includes(requestIdStr)) {
|
|
update.$push["countedRequests"] = requestIdStr;
|
|
} else {
|
|
delete update.$push;
|
|
}
|
|
}
|
|
|
|
const updateResult = await this.expertDbService.findOneAndUpdate(
|
|
{ _id: new Types.ObjectId(expertId) },
|
|
update,
|
|
);
|
|
|
|
if (!updateResult) {
|
|
console.warn("Failed to update expert stats for:", expertId);
|
|
} else {
|
|
console.log(`Expert stats updated (${type}) for expert:`, expertId);
|
|
}
|
|
}
|
|
|
|
async replyRequest(requestId: string, reply: SubmitReplyDto, userId: string) {
|
|
const request = await this.requestManagementDbService.findOne(requestId);
|
|
|
|
if (!request) {
|
|
throw new NotFoundException("Request not found");
|
|
}
|
|
if (String(request.actorLocked?.actorId) !== userId) {
|
|
throw new ForbiddenException(
|
|
"Access denied to this request. You are not the locked expert.",
|
|
);
|
|
}
|
|
|
|
// Check if lock has expired (unlockTime has passed)
|
|
if (request.unlockTime) {
|
|
const unlockTime = new Date(request.unlockTime).getTime();
|
|
const now = Date.now();
|
|
if (now >= unlockTime) {
|
|
throw new ForbiddenException("Your lock time has expired.");
|
|
}
|
|
} else if (request.unlockTime == null) {
|
|
throw new ForbiddenException("Your lock time has expired.");
|
|
}
|
|
|
|
if (!request.lockFile) {
|
|
throw new ForbiddenException(
|
|
"You must lock the request before submitting a reply.",
|
|
);
|
|
}
|
|
|
|
const isObjection = !!request.expertResendReply;
|
|
const replyField = isObjection
|
|
? "expertSubmitReplyFinal"
|
|
: "expertSubmitReply";
|
|
|
|
if (!isObjection && request.expertSubmitReply) {
|
|
throw new ForbiddenException(
|
|
"This request already has an initial expert reply.",
|
|
);
|
|
}
|
|
if (isObjection && request.expertSubmitReplyFinal) {
|
|
throw new ForbiddenException(
|
|
"This request already has a final expert reply.",
|
|
);
|
|
}
|
|
|
|
const newReplyObject = {
|
|
description: reply.description,
|
|
submitTime: new Date(),
|
|
guiltyUserId: reply.guiltyUserId,
|
|
fields: {
|
|
accidentWay: {
|
|
id: reply.fields.accidentWay.id,
|
|
label: reply.fields.accidentWay.label,
|
|
},
|
|
accidentReason: {
|
|
id: reply.fields.accidentReason.id,
|
|
label: reply.fields.accidentReason.label,
|
|
fanavaran: reply.fields.accidentReason.fanavaran,
|
|
},
|
|
accidentType: {
|
|
id: reply.fields.accidentType.id,
|
|
label: reply.fields.accidentType.label,
|
|
},
|
|
},
|
|
firstPartyComment: request.expertSubmitReply?.firstPartyComment || null,
|
|
secondPartyComment: request.expertSubmitReply?.secondPartyComment || null,
|
|
};
|
|
|
|
const updatePayload: any = {
|
|
$set: {
|
|
lockFile: false,
|
|
blameStatus: ReqBlameStatus.CheckedRequest,
|
|
[replyField]: newReplyObject,
|
|
},
|
|
$push: {
|
|
actorsChecker: {
|
|
[ReqBlameStatus.CheckedRequest]: request.actorLocked,
|
|
Date: new Date(),
|
|
},
|
|
},
|
|
};
|
|
|
|
if (isObjection) {
|
|
updatePayload.$set.expertSubmitReply = newReplyObject;
|
|
}
|
|
|
|
try {
|
|
await this.requestManagementDbService.findAndUpdate(
|
|
{ _id: requestId },
|
|
updatePayload,
|
|
);
|
|
return {
|
|
requestId: request._id,
|
|
blameStatus: ReqBlameStatus.CheckedRequest,
|
|
};
|
|
} catch (error) {
|
|
this.logger.error("Failed to submit expert reply:", error);
|
|
throw new Error("Failed to submit expert reply");
|
|
}
|
|
}
|
|
|
|
async sendAgainRequest(
|
|
requestId: string,
|
|
resend: any,
|
|
userId: string,
|
|
req: any,
|
|
) {
|
|
const request = await this.requestManagementDbService.findOne(requestId);
|
|
if (!request) {
|
|
throw new NotFoundException("Request not found");
|
|
}
|
|
if (String(request.actorLocked?.actorId) !== userId) {
|
|
throw new ForbiddenException("Access denied to this request");
|
|
}
|
|
if (request.expertSubmitReply) {
|
|
throw new ForbiddenException("Request already has an expert reply");
|
|
}
|
|
if (request.unlockTime == null) {
|
|
throw new ForbiddenException("Your lock time has expired or was not set");
|
|
}
|
|
|
|
const partyType = req.route.path.split("/")[4];
|
|
|
|
switch (partyType) {
|
|
case "first": {
|
|
if (request.expertResendReply?.firstParty) {
|
|
throw new ForbiddenException(
|
|
"Request has an expert resend reply for the first party",
|
|
);
|
|
}
|
|
|
|
const { firstPartyId, firstPartyDescription } = resend;
|
|
|
|
try {
|
|
await this.requestManagementDbService.findAndUpdate(
|
|
{ _id: requestId },
|
|
{
|
|
lockFile: false,
|
|
blameStatus: ReqBlameStatus.UserPending,
|
|
"expertResendReply.firstParty.firstPartyId": firstPartyId,
|
|
"expertResendReply.firstParty.firstPartyDescription":
|
|
firstPartyDescription,
|
|
$push: {
|
|
actorsChecker: {
|
|
[ReqBlameStatus.UserPending]: request.actorLocked,
|
|
Date: new Date(),
|
|
},
|
|
},
|
|
},
|
|
);
|
|
} catch (error) {
|
|
this.logger.error("Failed to update for first party:", error);
|
|
throw error;
|
|
}
|
|
|
|
return {
|
|
requestId: request._id,
|
|
blameStatus: ReqBlameStatus.UserPending,
|
|
};
|
|
}
|
|
|
|
case "second": {
|
|
if (request.expertResendReply?.secondParty) {
|
|
throw new ForbiddenException(
|
|
"Request has an expert resend reply for the second party",
|
|
);
|
|
}
|
|
|
|
const { secondPartyId, secondPartyDescription } = resend;
|
|
|
|
try {
|
|
await this.requestManagementDbService.findAndUpdate(
|
|
{ _id: requestId },
|
|
{
|
|
lockFile: false,
|
|
blameStatus: ReqBlameStatus.UserPending,
|
|
"expertResendReply.secondParty.secondPartyId": secondPartyId,
|
|
"expertResendReply.secondParty.secondPartyDescription":
|
|
secondPartyDescription,
|
|
$push: {
|
|
actorsChecker: {
|
|
[ReqBlameStatus.UserPending]: request.actorLocked,
|
|
Date: new Date(),
|
|
},
|
|
},
|
|
},
|
|
);
|
|
} catch (error) {
|
|
this.logger.error("Failed to update for second party:", error);
|
|
throw error;
|
|
}
|
|
|
|
// TODO notification for user parties
|
|
// TODO send SMS notification
|
|
// TODO send URI For USER
|
|
return {
|
|
requestId: request._id,
|
|
blameStatus: ReqBlameStatus.UserPending,
|
|
};
|
|
}
|
|
|
|
default:
|
|
throw new BadRequestException(
|
|
`Invalid party type in URL: ${partyType}`,
|
|
);
|
|
}
|
|
}
|
|
|
|
/// VIDEO SERVICE && VOICE SERVICE
|
|
// TODO add video service to Object Storage
|
|
async streamVideo(requestId): Promise<string> {
|
|
const request = await this.requestManagementDbService.findOne(requestId);
|
|
const video_path = await this.blameVideoDbService.findOne(
|
|
String(request.firstPartyDetails.firstPartyFile.firstPartyVideoId),
|
|
);
|
|
return buildFileLink(video_path.path);
|
|
}
|
|
|
|
async streamVoice(requestId, voiceId) {
|
|
try {
|
|
const voice = await this.blameVoiceDbService.findOne(voiceId);
|
|
if (!voice) throw new NotFoundException("not found voice");
|
|
if (String(voice.requestId) === requestId) {
|
|
return buildFileLink(voice.path);
|
|
} else {
|
|
throw new ForbiddenException(
|
|
"Can Not Access To This Voice Because Voice is Not Assign to RequestID",
|
|
);
|
|
}
|
|
} catch (er) {
|
|
if (er) throw new NotFoundException("voice not found ", er);
|
|
}
|
|
}
|
|
|
|
async getAccidentField() {
|
|
try {
|
|
const ac_reason = await readFile(
|
|
"src/static/ACCIDENT_REASON.json",
|
|
"utf-8",
|
|
);
|
|
const ac_type = await readFile("src/static/ACCIDENT_TYPE.json", "utf-8");
|
|
const ac_way = await readFile("src/static/ACCIDENT_WAY.json", "utf-8");
|
|
return {
|
|
accidentReason: JSON.parse(ac_reason),
|
|
accidentType: JSON.parse(ac_type),
|
|
accidentWay: JSON.parse(ac_way),
|
|
};
|
|
} catch (err) {
|
|
this.logger.error(err);
|
|
}
|
|
}
|
|
|
|
async inPersonVisit(requestId: string, actorDetail: any) {
|
|
const request = await this.requestManagementDbService.findOne(requestId);
|
|
if (!request) {
|
|
throw new NotFoundException("Blame not found");
|
|
}
|
|
|
|
const updated = await this.requestManagementDbService.findAndUpdate(
|
|
{ _id: new Types.ObjectId(requestId) },
|
|
{
|
|
blameStatus: ReqBlameStatus.InPersonVisit,
|
|
},
|
|
);
|
|
|
|
await this.expertDbService.updateStats(
|
|
actorDetail.sub,
|
|
"handled",
|
|
requestId,
|
|
);
|
|
|
|
return updated;
|
|
}
|
|
}
|