Initial commit after migration to gitea

This commit is contained in:
2026-01-18 11:27:43 +03:30
parent a21039410c
commit ea4b8eb543
196 changed files with 45567 additions and 9 deletions

View File

@@ -0,0 +1,211 @@
import { ApiProperty, ApiPropertyOptional } from "@nestjs/swagger";
import { Types } from "mongoose";
import { AddPlateDto } from "src/profile/dto/user/AddPlateDto";
import { StepsEnum } from "src/Types&Enums/blame-request-management/steps.enum";
import {
WeatherCondition,
RoadCondition,
LightCondition,
} from "src/Types&Enums/blame-request-management/accident-conditions.enum";
export class InitialFormDto {
@ApiProperty({ required: false, default: false })
expertOpinion?: boolean;
@ApiProperty({ required: false, default: false })
imDamaged?: boolean;
@ApiProperty({ required: true, default: false })
imGuilty?: boolean;
}
export class CarBodyFormDto {
@ApiProperty({ required: false, default: false })
car?: boolean;
@ApiProperty({ required: false, default: false })
object?: boolean;
}
export class CarBodySecondForm {
@ApiProperty({ required: false, default: false })
imDamaged?: boolean;
@ApiProperty({ required: true, default: false })
imGuilty?: boolean;
}
export class FirstPartyFileDto {
@ApiProperty()
descriptions?: string;
@ApiProperty()
voices?: string;
@ApiPropertyOptional({ type: String })
firstPartyVideoId?: string;
}
export class DescriptionDto {
@ApiProperty()
desc: string;
// CAR_BODY specific fields
@ApiPropertyOptional({
description: "Date of accident (for CAR_BODY type only)",
example: "2025-12-08"
})
accidentDate?: Date;
@ApiPropertyOptional({
description: "Time of accident (for CAR_BODY type only)",
example: "14:30"
})
accidentTime?: string;
@ApiPropertyOptional({
enum: WeatherCondition,
description: "Weather condition at time of accident (for CAR_BODY type only)",
example: WeatherCondition.CLEAR
})
weatherCondition?: WeatherCondition;
@ApiPropertyOptional({
enum: RoadCondition,
description: "Road condition at time of accident (for CAR_BODY type only)",
example: RoadCondition.DRY
})
roadCondition?: RoadCondition;
@ApiPropertyOptional({
enum: LightCondition,
description: "Light condition at time of accident (for CAR_BODY type only)",
example: LightCondition.DAYLIGHT
})
lightCondition?: LightCondition;
}
export class FirstPartyDetail {
// @ApiProperty({ type: String })
firstPartyId?: Types.ObjectId;
// @ApiProperty({ type: String })
firstPartyPhoneNumber?: string;
// @ApiProperty({ type: String })
firstPartyClientId?: string;
@ApiProperty({
type: AddPlateDto,
description: "first add plate and send plate id to this field",
})
firstPartyPlate?: AddPlateDto;
@ApiProperty()
firstPartyFile?: FirstPartyFileDto;
}
export class SecondPartyDetail {
@ApiProperty({ type: String })
secondPartyId?: Types.ObjectId;
@ApiProperty({ type: String })
secondPartyPhoneNumber?: string;
@ApiProperty({ type: String })
secondPartyClientId?: string;
@ApiProperty({
type: String,
description: "first add plate and send plate id to this field",
})
secondPartyPlateId?: Types.ObjectId;
@ApiProperty({ type: String })
secondPartyVideoId?: string;
}
export class LocationDto {
@ApiProperty({ type: Number, required: true })
lat: number;
@ApiProperty({ type: Number, required: true })
lon: number;
}
export class CreateRequestDto {
@ApiProperty({ type: String, required: false })
type?: "THIRD_PARTY" | "CAR_BODY";
}
// export class DocsOfThisFile {
// @ApiProperty()
// firstPartyFile?: FirstPartyFileDto;
// }
export class CreateRequestManagementDto {
@ApiPropertyOptional({ required: false, description: "first step" })
initialForm?: InitialFormDto;
@ApiPropertyOptional({ required: false, description: "first step" })
firstPartyDetails?: FirstPartyDetail;
// @ApiPropertyOptional({ required: false, description: "second step call" })
// docsOFirstParty?: DocsOfThisFile;
@ApiPropertyOptional({ required: false })
secondPartyDetails?: SecondPartyDetail;
@ApiPropertyOptional({ required: false, description: "third step call" })
locationForm?: LocationDto;
@ApiProperty({ type: "string", format: "binary", required: false })
firstPartyVideoId: Express.Multer.File;
}
// type: "object",
// properties: {
// file: {
// type: "string",
// format: "binary",
// },
// },
export class CreateRequestManagementInitialFormStep {
step?: StepsEnum;
userId?: Types.ObjectId;
nextStep?: StepsEnum;
message?: string;
requestId?: Types.ObjectId;
@ApiPropertyOptional({ required: false, description: "first step" })
initialForm?: InitialFormDto;
constructor(userId, reqId, step, nextStep, message?) {
this.requestId = reqId;
this.userId = userId;
this.step = step;
this.nextStep = nextStep;
this.message = message;
}
}
export class RequestManagementDtoRs {
step?: StepsEnum;
firstPartyUserId?: Types.ObjectId;
secondPartyUserId?: Types.ObjectId | null;
nextStep?: StepsEnum;
message?: string;
requestId?: Types.ObjectId;
claimFile?: Types.ObjectId | null;
constructor(
firstPartyUser,
secondPartyUserId,
step,
nextStep,
message?,
requestId?,
claimFile = null,
) {
this.requestId = requestId;
this.firstPartyUserId = firstPartyUser;
this.secondPartyUserId = secondPartyUserId;
this.step = step;
this.nextStep = nextStep;
this.message = message;
this.claimFile = claimFile;
}
}

View File

@@ -0,0 +1,26 @@
import { ApiProperty } from "@nestjs/swagger";
import { AccidentWayIF, AccidentReasonIF } from "src/expert-blame/dto/reply.dto";
/**
* DTO for expert to add accident fields to an expert-initiated file
*/
export class ExpertAccidentFieldsDto {
@ApiProperty({
type: AccidentWayIF,
description: "Accident way information (id and label)",
})
accidentWay: AccidentWayIF;
@ApiProperty({
type: AccidentReasonIF,
description: "Accident reason information (id, label, and fanavaran)",
})
accidentReason: AccidentReasonIF;
@ApiProperty({
type: AccidentWayIF,
description: "Accident type information (id and label)",
})
accidentType: AccidentWayIF;
}

View File

@@ -0,0 +1,33 @@
import { ApiProperty } from "@nestjs/swagger";
import { AddPlateDto } from "src/profile/dto/user/AddPlateDto";
import { LocationDto } from "./create-request-management.dto";
import { DescriptionDto } from "./create-request-management.dto";
import { InitialFormDto, CarBodyFormDto } from "./create-request-management.dto";
/**
* Comprehensive form for experts to fill all information at once for IN_PERSON CAR_BODY files
*/
export class ExpertCompleteCarBodyFormDto {
// First Party Information
@ApiProperty({
description: "First party phone number",
example: "09123456789",
})
firstPartyPhoneNumber: string;
@ApiProperty({ type: InitialFormDto })
firstPartyInitialForm: InitialFormDto;
@ApiProperty({ type: CarBodyFormDto })
carBodyForm: CarBodyFormDto;
@ApiProperty({ type: AddPlateDto })
firstPartyPlate: AddPlateDto;
@ApiProperty({ type: LocationDto })
firstPartyLocation: LocationDto;
@ApiProperty({ type: DescriptionDto })
firstPartyDescription: DescriptionDto;
}

View File

@@ -0,0 +1,64 @@
import { ApiProperty } from "@nestjs/swagger";
import { AddPlateDto } from "src/profile/dto/user/AddPlateDto";
import { LocationDto } from "./create-request-management.dto";
import { DescriptionDto } from "./create-request-management.dto";
import { InitialFormDto } from "./create-request-management.dto";
/**
* Second party information for expert-completed THIRD_PARTY forms
*/
export class ExpertSecondPartyInfoDto {
@ApiProperty({
description: "Second party phone number",
example: "09123456789",
})
phoneNumber: string;
@ApiProperty({ type: InitialFormDto })
initialForm: InitialFormDto;
@ApiProperty({ type: AddPlateDto })
plate: AddPlateDto;
@ApiProperty({ type: LocationDto })
location: LocationDto;
@ApiProperty({ type: DescriptionDto })
description: DescriptionDto;
}
/**
* Comprehensive form for experts to fill all information at once for IN_PERSON THIRD_PARTY files
*/
export class ExpertCompleteThirdPartyFormDto {
// First Party Information
@ApiProperty({
description: "First party phone number",
example: "09123456789",
})
firstPartyPhoneNumber: string;
@ApiProperty({ type: InitialFormDto })
firstPartyInitialForm: InitialFormDto;
@ApiProperty({ type: AddPlateDto })
firstPartyPlate: AddPlateDto;
@ApiProperty({ type: LocationDto })
firstPartyLocation: LocationDto;
@ApiProperty({ type: DescriptionDto })
firstPartyDescription: DescriptionDto;
// Second Party Information
@ApiProperty({ type: ExpertSecondPartyInfoDto })
secondParty: ExpertSecondPartyInfoDto;
// Guilty Party
@ApiProperty({
description: "Phone number of the guilty party. Must match either first or second party phone number.",
example: "09123456789",
})
guiltyPartyPhoneNumber: string;
}

View File

@@ -0,0 +1,39 @@
import { ApiProperty, ApiPropertyOptional } from "@nestjs/swagger";
import { IsEnum, IsOptional, IsString } from "class-validator";
import { CreationMethod } from "../entities/schema/request-management.schema";
export class CreateExpertInitiatedFileDto {
@ApiProperty({
description: "Type of blame file",
enum: ["THIRD_PARTY", "CAR_BODY"],
example: "THIRD_PARTY",
})
@IsEnum(["THIRD_PARTY", "CAR_BODY"])
type: "THIRD_PARTY" | "CAR_BODY";
@ApiProperty({
description: "Method of creation",
enum: CreationMethod,
example: CreationMethod.IN_PERSON,
})
@IsEnum(CreationMethod)
creationMethod: CreationMethod;
// Phone numbers for LINK method - users will access files via normal login
@ApiPropertyOptional({
description: "First party phone number. Required for LINK method.",
example: "09123456789",
})
@IsOptional()
@IsString()
firstPartyPhoneNumber?: string;
@ApiPropertyOptional({
description: "Second party phone number. Required for LINK method when type is THIRD_PARTY.",
example: "09187654321",
})
@IsOptional()
@IsString()
secondPartyPhoneNumber?: string;
}

View File

@@ -0,0 +1,52 @@
import { ApiProperty } from "@nestjs/swagger";
import { RequestManagementModel } from "../entities/schema/request-management.schema";
export class UserReplyDto {
public _id: string;
@ApiProperty({ required: true })
isAccept: boolean;
@ApiProperty({ required: true })
signId: string;
constructor(requestId: string) {
this._id = requestId;
}
}
export class DetailsReplyDtoRs {
public fields: Object;
public actorFullName: string;
public description: string;
public dateOfReply: string;
public timeOfReply: string;
public isGuiltyUser: boolean;
public firstPartyAccept: boolean;
public secondPartyAccept: boolean;
public status: string;
public expertResendReply: any;
public expertSubmitReply: any;
public expertSubmitReplyFinal: any;
public theParties: any;
public type: string;
constructor(request: RequestManagementModel, details: any) {
const finalReply = request.expertSubmitReply;
this.fields = finalReply.fields;
this.description = finalReply.description;
this.firstPartyAccept = finalReply?.firstPartyComment?.isAccept;
this.secondPartyAccept = finalReply?.secondPartyComment?.isAccept;
this.actorFullName = details.actorDetail.CheckedRequest.fullName;
this.dateOfReply = details.actorDetail.Date.toLocaleDateString("fa-IR");
this.timeOfReply = details.actorDetail.Date.toLocaleTimeString("fa-IR");
this.isGuiltyUser = details.isGuilty;
this.status = request.blameStatus;
this.expertResendReply = request.expertResendReply;
this.expertSubmitReply = request.expertSubmitReply;
this.expertSubmitReplyFinal = request.expertSubmitReplyFinal;
this.theParties = details.theParties;
this.type = request.type || "THIRD_PARTY"; // Include type field
}
}

View File

@@ -0,0 +1,36 @@
import { Injectable } from '@nestjs/common';
import { InjectModel } from '@nestjs/mongoose';
import { FilterQuery, Model, UpdateQuery } from 'mongoose';
import { BlameDocumentModel } from '../schema/blame-document.schema';
@Injectable()
export class BlameDocumentDbService {
constructor(
@InjectModel(BlameDocumentModel.name)
private readonly blameDocumentModel: Model<BlameDocumentModel>,
) {}
async create(createDto: Partial<BlameDocumentModel>): Promise<BlameDocumentModel> {
const newDocument = new this.blameDocumentModel(createDto);
return newDocument.save();
}
async findOne(filter: FilterQuery<BlameDocumentModel>): Promise<BlameDocumentModel | null> {
return this.blameDocumentModel.findOne(filter).lean();
}
async findAll(filter: FilterQuery<BlameDocumentModel>): Promise<BlameDocumentModel[]> {
return this.blameDocumentModel.find(filter).lean();
}
async findById(id: string): Promise<BlameDocumentModel | null> {
return this.blameDocumentModel.findById(id).lean();
}
async findByIdAndUpdate(
id: string,
updateDto: UpdateQuery<BlameDocumentModel>,
): Promise<BlameDocumentModel | null> {
return this.blameDocumentModel.findByIdAndUpdate(id, updateDto, { new: true }).lean();
}
}

View File

@@ -0,0 +1,26 @@
import { InjectModel } from "@nestjs/mongoose";
import { Model, Types } from "mongoose";
import { BlameVideosModel } from "src/request-management/entities/schema/blame-video.schema";
export class BlameVideoDbService {
constructor(
@InjectModel(BlameVideosModel.name)
private readonly blameVideoModel: Model<BlameVideosModel>,
) {}
async create(video: BlameVideosModel): Promise<BlameVideosModel> {
return await this.blameVideoModel.create(video);
}
async findOne(id: string): Promise<BlameVideosModel> {
return await this.blameVideoModel.findOne(new Types.ObjectId(id));
}
async findOneByRequestId(id: string): Promise<BlameVideosModel> {
return await this.blameVideoModel.findById(id);
}
async findById(id: string): Promise<BlameVideosModel | null> {
return this.blameVideoModel.findById(id).lean();
}
}

View File

@@ -0,0 +1,22 @@
import { InjectModel } from "@nestjs/mongoose";
import { Model, Types } from "mongoose";
import { BlameVoicesModel } from "../schema/blame-voice.schema";
export class BlameVoiceDbService {
constructor(
@InjectModel(BlameVoicesModel.name)
private readonly blameVoiceModel: Model<BlameVoicesModel>,
) {}
async create(Voice: BlameVoicesModel): Promise<BlameVoicesModel> {
return await this.blameVoiceModel.create(Voice);
}
async findOne(id: string): Promise<BlameVoicesModel> {
return await this.blameVoiceModel.findOne(new Types.ObjectId(id));
}
async findById(id: string): Promise<BlameVoicesModel | null> {
return this.blameVoiceModel.findById(id).lean();
}
}

View File

@@ -0,0 +1,94 @@
import { InjectModel } from "@nestjs/mongoose";
import { FilterQuery, Model, Types, UpdateQuery } from "mongoose";
import { RequestManagementModel } from "../schema/request-management.schema";
export class RequestManagementDbService {
constructor(
@InjectModel(RequestManagementModel.name)
private readonly requestModel: Model<RequestManagementModel>,
) {}
async create(
request: RequestManagementModel,
): Promise<RequestManagementModel> {
return await this.requestModel.create(request);
}
async findOne(id: string) {
return await this.requestModel.findOne(new Types.ObjectId(id));
}
async findAll(filter?: FilterQuery<RequestManagementModel>) {
return await this.requestModel.find({ ...filter });
}
async findAllAndPagination(query, currentPage, resPerPage?) {
const responsePerPage = resPerPage | 1;
const skipPge = responsePerPage * (Number(currentPage) - 1);
return await this.requestModel
.find(query)
.limit(responsePerPage)
.skip(skipPge);
}
async countByFilter(
filter: FilterQuery<RequestManagementModel>,
): Promise<number> {
return await this.requestModel.countDocuments(filter);
}
async findAndUpdate(
filter: FilterQuery<RequestManagementModel>,
update: UpdateQuery<RequestManagementModel>,
) {
return await this.requestModel.findByIdAndUpdate(filter, update);
}
async findOneAndUpdate(
filter: FilterQuery<RequestManagementModel>,
update: UpdateQuery<RequestManagementModel>,
options: {
new?: boolean;
upsert?: boolean;
runValidators?: boolean;
} = {},
): Promise<RequestManagementModel | null> {
try {
const updatedDoc = await this.requestModel.findOneAndUpdate(
filter,
update,
{
new: options.new ?? true, // Return updated doc by default
upsert: options.upsert ?? false, // Dont create new if not found
runValidators: options.runValidators ?? true,
},
);
return updatedDoc;
} catch (error) {
console.error(
`❌ [RequestManagementDbService] findOneAndUpdate failed`,
error,
);
throw error;
}
}
async findByIdAndUpdate(
id: string,
update: UpdateQuery<RequestManagementModel>,
) {
return await this.requestModel.findByIdAndUpdate(
{ _id: new Types.ObjectId(id) },
update,
);
}
async aggregate(filter?) {
return await this.requestModel.aggregate(filter);
}
async updateMany(filter?, update?) {
return await this.requestModel.updateMany(filter, update);
}
}

View File

@@ -0,0 +1,23 @@
import { InjectModel } from "@nestjs/mongoose";
import { Model, Types } from "mongoose";
import { UserSignModel } from "../schema/sign.schema";
export class UserSignDbService {
constructor(
@InjectModel(UserSignModel.name)
private readonly blameVoiceModel: Model<UserSignModel>,
) {}
async create(sign: UserSignModel): Promise<UserSignModel> {
return await this.blameVoiceModel.create(sign);
}
async findOne(id: string): Promise<UserSignModel> {
return await this.blameVoiceModel.findOne(new Types.ObjectId(id));
}
async findById(id: string): Promise<UserSignModel | null> {
// Use your correct Model type
return this.blameVoiceModel.findById(id).lean();
}
}

View File

@@ -0,0 +1,26 @@
import { Prop, Schema, SchemaFactory } from "@nestjs/mongoose";
import { Types } from "mongoose";
export enum BlameDocumentType {
NationalCertificate = "nationalCertificate",
CarCertificate = "carCertificate",
DrivingLicence = "drivingLicense",
CarGreenCard = "carGreenCard",
}
@Schema({ collection: "blame-documents", versionKey: false, timestamps: true })
export class BlameDocumentModel {
@Prop({ required: true })
path: string;
@Prop({ required: true })
requestId: Types.ObjectId;
@Prop({ required: true })
fileName: string;
@Prop({ required: true, type: String, enum: BlameDocumentType })
documentType: BlameDocumentType;
}
export const BlameDocumentSchema =
SchemaFactory.createForClass(BlameDocumentModel);

View File

@@ -0,0 +1,15 @@
import { Prop, Schema, SchemaFactory } from "@nestjs/mongoose";
import { Types } from "mongoose";
@Schema({ collection: "blame-videos", versionKey: false })
export class BlameVideosModel {
@Prop()
path: string;
@Prop()
requestId: Types.ObjectId;
@Prop()
fileName: string;
}
export const BlameVideoSchema = SchemaFactory.createForClass(BlameVideosModel);

View File

@@ -0,0 +1,24 @@
import { Prop, Schema, SchemaFactory } from "@nestjs/mongoose";
import { Types } from "mongoose";
export enum UploadContext {
INITIAL = 'INITIAL_UPLOAD',
EXPERT_RESEND = 'EXPERT_RESEND',
}
@Schema({ collection: "blame-voice", versionKey: false })
export class BlameVoicesModel {
@Prop()
path: string;
@Prop()
requestId: Types.ObjectId;
@Prop()
fileName: string;
@Prop({ type: String, enum: UploadContext, default: UploadContext.INITIAL })
context?: UploadContext;
}
export const BlameVoiceSchema = SchemaFactory.createForClass(BlameVoicesModel);

View File

@@ -0,0 +1,465 @@
import { Prop, Schema, SchemaFactory } from "@nestjs/mongoose";
import { Types } from "mongoose";
import {
ResendFirstPartyDto,
ResendSecondPartyDto,
} from "src/expert-blame/dto/reply.dto";
import { AddPlateDto } from "src/profile/dto/user/AddPlateDto";
import { ReqBlameStatus } from "src/Types&Enums/blame-request-management/status.enum";
import { StepsEnum } from "src/Types&Enums/blame-request-management/steps.enum";
export class InitialForm {
@Prop({ required: false, default: false })
expertOpinion?: boolean;
@Prop({ required: false, default: false })
imDamaged?: boolean;
@Prop({ required: false, default: false })
imGuilty?: boolean;
}
export class FirstPartyFile {
@Prop()
descriptions?: string[];
@Prop()
voices?: string[];
@Prop()
firstPartyVideoId?: string;
// CAR_BODY specific accident details
@Prop({ type: Date })
accidentDate?: Date;
@Prop()
accidentTime?: string;
@Prop()
weatherCondition?: string;
@Prop()
roadCondition?: string;
@Prop()
lightCondition?: string;
}
export class CarDetail {
carName?: string;
carModel?: string;
carType?: string;
isNewCar?: boolean;
insurerBirthday?: string;
driverBirthday?: string | null;
}
export class InsuranceDetail {
insuranceId?: string;
insuranceCompany?: string;
financialCommitmentCeiling?: string;
startDate?: string;
endDate?: string;
}
// CAR_BODY specific insurance details for the damaged party
export class CarBodyInsuranceDetail {
@Prop()
policyNumber?: string;
@Prop()
startDate?: string;
@Prop()
endDate?: string;
@Prop()
insurerCompany?: string;
@Prop({ type: [String] })
coverages?: string[];
}
export class SecondPartyFile {
@Prop()
descriptions?: string[];
@Prop()
voices?: string[];
@Prop()
secondPartyVideoId?: string;
}
export class FirstPartyClient {
clientName?: string;
clientId?: Types.ObjectId;
}
export class SecondPartyClient {
clientName?: string;
clientId?: Types.ObjectId;
}
export class FirstPartyDetail {
@Prop({ type: String })
firstPartyId?: Types.ObjectId;
@Prop({ type: String })
firstPartyPhoneNumber?: string;
@Prop({ type: String })
firstPartyClientId?: string;
@Prop({
type: AddPlateDto,
description: "first add plate and send plate id to this field",
})
firstPartyPlate?: AddPlateDto;
@Prop({ type: CarDetail })
firstPartyCarDetail?: CarDetail;
@Prop({ type: InsuranceDetail })
firstPartyInsuranceDetail?: InsuranceDetail;
// Only used for CAR_BODY type requests car body insurance info
@Prop({ type: CarBodyInsuranceDetail })
firstPartyCarBodyInsuranceDetail?: CarBodyInsuranceDetail;
@Prop({ type: FirstPartyFile })
firstPartyClient?: FirstPartyClient;
@Prop({ type: FirstPartyFile })
firstPartyFile?: FirstPartyFile;
@Prop({ type: FirstPartyFile })
firstPartyInitialForm?: InitialForm;
@Prop({ type: Boolean })
firstPartyCertificate: boolean | null;
}
export class SecondPartyDetail {
@Prop({ type: String })
secondPartyId?: Types.ObjectId;
@Prop({ type: String })
secondPartyPhoneNumber?: string;
@Prop({ type: SecondPartyClient })
secondPartyClient?: SecondPartyClient;
@Prop({
type: AddPlateDto,
description: "secondParty add plate and send plate id to this field",
})
secondPartyPlate?: AddPlateDto;
@Prop()
secondPartyCarDetail?: CarDetail;
@Prop({ type: InsuranceDetail })
secondPartyInsuranceDetail?: InsuranceDetail;
@Prop()
secondPartyPlateId?: string;
@Prop({ type: InitialForm })
secondPartyInitialForm: InitialForm;
@Prop({ type: SecondPartyFile })
secondPartyFiles?: SecondPartyFile;
}
export class LocationDto {
@Prop({ type: Number, required: true })
lat: number;
@Prop({ type: Number, required: true })
lon: number;
}
export class AccidentWayIF {
@Prop({ required: true })
id: string;
@Prop({ required: true })
label: string;
}
export class AccidentReasonIF {
@Prop({ required: true })
id: string;
@Prop({ required: true })
label: string;
@Prop({ required: true })
fanavaran: number;
}
export class FieldsInterface {
@Prop({ required: true, type: AccidentWayIF })
accidentWay: AccidentWayIF;
@Prop({ required: true, type: AccidentReasonIF })
accidentReason: AccidentReasonIF;
@Prop({ required: true, type: AccidentWayIF })
accidentType: AccidentWayIF;
}
export class SignDetail {
@Prop()
fileId: string;
@Prop()
fileName: string;
@Prop()
fileUrl: string;
}
export class PartiesComment {
@Prop({ required: true, type: Boolean })
isAccept?: boolean;
@Prop({ required: true })
signDetail?: SignDetail;
}
export class SubmitReply {
@Prop({ required: true })
description: string;
@Prop({ required: true, type: String })
guiltyUserId: Types.ObjectId;
@Prop({ required: true })
fields: FieldsInterface;
@Prop({ required: true, default: () => new Date() })
submitTime: Date;
@Prop({ type: PartiesComment })
firstPartyComment?: PartiesComment;
@Prop({ type: PartiesComment })
secondPartyComment?: PartiesComment;
@Prop()
systemResponse: string;
}
export class ExpertResendParties {
userId?: Types.ObjectId;
description?: string;
}
export class ExpertResendReply {
// TODO change DTOs to dynamic Exmple SubmitReply Parties comment
@Prop()
firstParty?: ResendFirstPartyDto;
@Prop()
secondParty?: ResendSecondPartyDto;
}
export class ActorLockDetails {
@Prop({ type: String })
fullName?: string;
@Prop({ type: Types.ObjectId })
actorId?: Types.ObjectId;
}
export enum CreationMethod {
NORMAL = "NORMAL",
LINK = "LINK",
IN_PERSON = "IN_PERSON",
}
export enum FilledBy {
CUSTOMER = "customer",
EXPERT = "expert",
}
export class ExpertLinkInfo {
@Prop({ type: String, required: true })
token: string;
@Prop({ type: Date, required: true })
expiresAt: Date;
@Prop({ type: Boolean, default: false })
isUsed: boolean;
@Prop({ type: Boolean, default: false })
isExpired: boolean;
@Prop({ type: Date })
generatedAt: Date;
@Prop({ type: String })
sentTo?: string; // Phone number or email
@Prop({ type: Number, default: 0 })
openCount: number;
@Prop({ type: Date })
lastOpenedAt?: Date;
@Prop({ type: Date })
usedAt?: Date;
}
export class FileHistoryEvent {
@Prop({ type: String, required: true })
eventType: string; // "LINK_SENT", "LINK_OPENED", "FILLED_BY_CUSTOMER", "FILLED_BY_EXPERT", "ASSIGNED_TO_EXPERT", "EVALUATION_STARTED", "EVALUATION_COMPLETED", "USER_ACCEPTED", "USER_REJECTED", "SENT_TO_FANAVARAN", "CODE_RECEIVED"
@Prop({ type: Types.ObjectId })
actorId?: Types.ObjectId;
@Prop({ type: String })
actorName?: string;
@Prop({ type: String })
actorType?: string; // "expert", "damage_expert", "customer"
@Prop({ type: Date, default: () => new Date() })
timestamp: Date;
@Prop({ type: Object })
metadata?: any; // Additional event-specific data
}
@Schema({ _id: false })
export class FileRating {
@Prop({ type: Number, min: 0, max: 5 })
collisionMethodAccuracy: number;
@Prop({ type: Number, min: 0, max: 5 })
evaluationTimeliness: number;
@Prop({ type: Number, min: 0, max: 5 })
accidentCauseAccuracy: number;
@Prop({ type: Number, min: 0, max: 5 })
guiltyVehicleIdentification: number;
}
// TODO clean all sub Object to other files
export type RequestManagementDocs = RequestManagementModel & Document;
@Schema({
collection: "requests-management",
versionKey: false,
timestamps: true,
autoIndex: true,
_id: true,
})
export class RequestManagementModel {
@Prop({ type: String, required: false })
type: string;
@Prop({ type: String, required: true, index: { unique: true } })
requestNumber: string;
@Prop()
blameStatus: ReqBlameStatus | StepsEnum;
@Prop()
steps?: StepsEnum[];
@Prop()
userComment?: string;
@Prop()
currentStep: StepsEnum;
@Prop()
nextStep?: StepsEnum;
@Prop({ description: "first step" })
initialForm?: InitialForm;
@Prop({ required: false })
firstPartyDetails?: FirstPartyDetail;
@Prop({ required: false, type: Types.ObjectId })
sanHubId?: Types.ObjectId;
@Prop({ required: false })
secondPartyDetails?: SecondPartyDetail;
@Prop({ required: false, description: "third step call" })
firstPartyLocation?: LocationDto;
@Prop({ required: false })
secondPartyLocation?: LocationDto;
@Prop({ type: Date, default: Date.now })
createdAt?: Date | string;
@Prop({ type: Date, default: Date.now })
updatedAt?: Date;
@Prop()
lockFile?: boolean;
@Prop({ type: Date })
lockTime?: Date | string | number;
@Prop({ type: Date })
unlockTime?: Date | number;
@Prop()
actorLocked?: ActorLockDetails;
@Prop()
actorsChecker?: [];
@Prop({ required: false })
expertSubmitReply?: SubmitReply | null;
@Prop()
expertResendReply?: ExpertResendReply;
@Prop()
expertSubmitReplyFinal?: ExpertResendReply;
@Prop({ type: FileRating, required: false })
rating?: FileRating;
@Prop({ type: Date, required: false })
autoCloseTriggerTime?: Date;
@Prop({ type: Boolean, default: false })
isHandledStatsUpdated?: boolean;
// Expert-initiated file tracking
@Prop({ type: Boolean, default: false })
expertInitiated?: boolean;
@Prop({ type: Types.ObjectId })
initiatedBy?: Types.ObjectId; // Expert who started the process
@Prop({ type: String, enum: FilledBy })
filledBy?: FilledBy; // Who filled the information
@Prop({ type: String, enum: CreationMethod, default: CreationMethod.NORMAL })
creationMethod?: CreationMethod; // How the file was created
@Prop({ type: ExpertLinkInfo })
expertLink?: ExpertLinkInfo; // Link information if created via link
@Prop({ type: [FileHistoryEvent], default: [] })
history?: FileHistoryEvent[]; // Timeline of events
@Prop({ type: Types.ObjectId })
assignedExpertId?: Types.ObjectId; // Expert assigned for evaluation (should be same as initiatedBy for expert-initiated files)
}
export const RequestManagementSchema = SchemaFactory.createForClass(
RequestManagementModel,
);

View File

@@ -0,0 +1,19 @@
import { Prop, Schema, SchemaFactory } from "@nestjs/mongoose";
import { Types } from "mongoose";
@Schema({ collection: "blame-sign", versionKey: false })
export class UserSignModel {
@Prop()
path: string;
@Prop()
userId: string;
@Prop()
requestId: Types.ObjectId;
@Prop()
fileName: string;
}
export const UserSignSchema = SchemaFactory.createForClass(UserSignModel);

View File

@@ -0,0 +1,275 @@
import { extname } from "node:path";
import {
Controller,
Post,
Body,
Param,
UseGuards,
Get,
Query,
UseInterceptors,
UploadedFile,
} from "@nestjs/common";
import {
FileInterceptor,
} from "@nestjs/platform-express";
import { diskStorage } from "multer";
import {
ApiBearerAuth,
ApiBody,
ApiParam,
ApiQuery,
ApiTags,
ApiOperation,
ApiResponse,
ApiConsumes,
} from "@nestjs/swagger";
import { LocalActorAuthGuard } from "src/auth/guards/actor-local.guard";
import { RolesGuard } from "src/auth/guards/role.guard";
import { CurrentUser } from "src/decorators/user.decorator";
import { Roles } from "src/decorators/roles.decorator";
import { RoleEnum } from "src/Types&Enums/role.enum";
import { RequestManagementService } from "./request-management.service";
import { CreateExpertInitiatedFileDto } from "./dto/expert-initiated.dto";
import { ExpertCompleteThirdPartyFormDto } from "./dto/expert-complete-third-party-form.dto";
import { ExpertCompleteCarBodyFormDto } from "./dto/expert-complete-car-body-form.dto";
import { ExpertAccidentFieldsDto } from "./dto/expert-accident-fields.dto";
@ApiTags("expert-initiated-blame")
@Controller("expert-initiated-blame")
@ApiBearerAuth()
@UseGuards(LocalActorAuthGuard, RolesGuard)
@Roles(RoleEnum.EXPERT, RoleEnum.DAMAGE_EXPERT)
export class ExpertInitiatedController {
constructor(
private readonly requestManagementService: RequestManagementService,
) {}
@Post("create")
@ApiOperation({
summary: "Create expert-initiated blame file",
description:
"Expert creates an initial incomplete blame file. Can be IN_PERSON (expert fills all info) or LINK (expert sends link to users).",
})
@ApiBody({ type: CreateExpertInitiatedFileDto })
@ApiResponse({
status: 201,
description: "File created successfully",
schema: {
type: "object",
properties: {
requestId: { type: "string" },
},
},
})
async createExpertInitiatedFile(
@CurrentUser() expert: any,
@Body() dto: CreateExpertInitiatedFileDto,
) {
return await this.requestManagementService.createExpertInitiatedFile(
expert,
dto,
);
}
@Post("complete-form-third-party/:requestId")
@ApiOperation({
summary: "Expert completes all information for IN_PERSON THIRD_PARTY file",
description:
"Expert fills all information at once for IN_PERSON THIRD_PARTY files. " +
"Includes first party, second party, and guilty party selection. " +
"Video and voice uploads are handled separately.",
})
@ApiParam({
name: "requestId",
description: "ID of the expert-initiated IN_PERSON THIRD_PARTY file",
})
@ApiBody({ type: ExpertCompleteThirdPartyFormDto })
@ApiResponse({
status: 200,
description: "Form completed successfully. File is now ready for user signatures.",
})
async completeThirdPartyForm(
@CurrentUser() expert: any,
@Param("requestId") requestId: string,
@Body() formData: ExpertCompleteThirdPartyFormDto,
) {
return await this.requestManagementService.expertCompleteThirdPartyForm(
expert,
requestId,
formData,
);
}
@Post("complete-form-car-body/:requestId")
@ApiOperation({
summary: "Expert completes all information for IN_PERSON CAR_BODY file",
description:
"Expert fills all information at once for IN_PERSON CAR_BODY files. " +
"Includes first party data and carBodyForm. " +
"Video and voice uploads are handled separately.",
})
@ApiParam({
name: "requestId",
description: "ID of the expert-initiated IN_PERSON CAR_BODY file",
})
@ApiBody({ type: ExpertCompleteCarBodyFormDto })
@ApiResponse({
status: 200,
description: "Form completed successfully. File is now ready for user signature.",
})
async completeCarBodyForm(
@CurrentUser() expert: any,
@Param("requestId") requestId: string,
@Body() formData: ExpertCompleteCarBodyFormDto,
) {
return await this.requestManagementService.expertCompleteCarBodyForm(
expert,
requestId,
formData,
);
}
@Post("upload-video/:requestId")
@ApiOperation({
summary: "Expert uploads video for expert-initiated file",
description:
"Expert uploads a single video file for the entire blame file. " +
"Only one video is needed per file (not per party). " +
"Only works for expert-initiated files.",
})
@ApiParam({
name: "requestId",
description: "ID of the expert-initiated file",
})
@ApiConsumes("multipart/form-data")
@ApiBody({
schema: {
type: "object",
properties: {
file: {
type: "string",
format: "binary",
},
},
},
})
@UseInterceptors(
FileInterceptor("file", {
limits: {
fileSize: 20 * 1024 * 1024, // 20MB
},
storage: diskStorage({
destination: "./files/video",
filename: (req, file, callback) => {
const unique = Date.now();
const ex = extname(file.originalname);
const filename = `expert-${file.originalname}-${unique}${ex}`;
callback(null, filename);
},
}),
}),
)
@ApiResponse({
status: 200,
description: "Video uploaded successfully",
})
async uploadVideo(
@CurrentUser() expert: any,
@Param("requestId") requestId: string,
@UploadedFile() file?: Express.Multer.File,
) {
return await this.requestManagementService.expertUploadVideo(
expert,
requestId,
file,
);
}
@Post("upload-voice/:requestId")
@ApiOperation({
summary: "Expert uploads voice for expert-initiated file",
description:
"Expert uploads a single voice file for the entire blame file. " +
"Only one voice is needed per file (not per party). " +
"Only works for expert-initiated files.",
})
@ApiParam({
name: "requestId",
description: "ID of the expert-initiated file",
})
@ApiConsumes("multipart/form-data")
@ApiBody({
schema: {
type: "object",
properties: {
voice: {
type: "string",
format: "binary",
},
},
},
})
@UseInterceptors(
FileInterceptor("voice", {
limits: {
fileSize: 10 * 1024 * 1024, // 10MB
},
storage: diskStorage({
destination: "./files/voice",
filename: (req, file, callback) => {
const unique = Date.now();
const ex = extname(file.originalname);
const flname = file.originalname.split(".")[0];
const filename = `expert-${flname}-${unique}${ex}`;
callback(null, filename);
},
}),
}),
)
@ApiResponse({
status: 200,
description: "Voice uploaded successfully",
})
async uploadVoice(
@CurrentUser() expert: any,
@Param("requestId") requestId: string,
@UploadedFile() voice?: Express.Multer.File,
) {
return await this.requestManagementService.expertUploadVoice(
expert,
requestId,
voice,
);
}
@Post("add-accident-fields/:requestId")
@ApiOperation({
summary: "Expert adds accident fields to expert-initiated file",
description:
"Expert adds accident way, reason, and type fields to an expert-initiated file. " +
"Can be used for both IN_PERSON and LINK-based files. " +
"If expertSubmitReply already exists, updates the fields. Otherwise, creates a new expertSubmitReply.",
})
@ApiParam({
name: "requestId",
description: "ID of the expert-initiated file",
})
@ApiBody({ type: ExpertAccidentFieldsDto })
@ApiResponse({
status: 200,
description: "Accident fields added successfully",
})
async addAccidentFields(
@CurrentUser() expert: any,
@Param("requestId") requestId: string,
@Body() fields: ExpertAccidentFieldsDto,
) {
return await this.requestManagementService.expertAddAccidentFields(
expert,
requestId,
fields,
);
}
}

View File

@@ -0,0 +1,22 @@
import { Test, TestingModule } from "@nestjs/testing";
import { RequestManagementController } from "./request-management.controller";
import { RequestManagementService } from "./request-management.service";
describe("RequestManagementController", () => {
let controller: RequestManagementController;
beforeEach(async () => {
const module: TestingModule = await Test.createTestingModule({
controllers: [RequestManagementController],
providers: [RequestManagementService],
}).compile();
controller = module.get<RequestManagementController>(
RequestManagementController,
);
});
it("should be defined", () => {
expect(controller).toBeDefined();
});
});

View File

@@ -0,0 +1,403 @@
import { extname } from "node:path";
import {
Controller,
Get,
Post,
Body,
Param,
UseGuards,
UseInterceptors,
UploadedFile,
Req,
Put,
UploadedFiles,
Patch,
Query,
} from "@nestjs/common";
import {
FileFieldsInterceptor,
FileInterceptor,
} from "@nestjs/platform-express";
import {
ApiBearerAuth,
ApiBody,
ApiConsumes,
ApiParam,
ApiQuery,
ApiTags,
} from "@nestjs/swagger";
import { Request } from "express";
import { diskStorage } from "multer";
import { GlobalGuard } from "src/auth/guards/global.guard";
import { RolesGuard } from "src/auth/guards/role.guard";
import { CurrentUser } from "src/decorators/user.decorator";
import { Roles } from "src/decorators/roles.decorator";
import { AddPlateDto } from "src/profile/dto/user/AddPlateDto";
import { RoleEnum } from "src/Types&Enums/role.enum";
import { BlameDocumentType } from "./entities/schema/blame-document.schema";
import {
CarBodyFormDto,
CarBodySecondForm,
CreateRequestDto,
DescriptionDto,
InitialFormDto,
LocationDto,
} from "./dto/create-request-management.dto";
import { RequestManagementService } from "./request-management.service";
import { InPersonVisitDto } from "src/claim-request-management/dto/in-person-visit.dto";
@Controller("blame-request-management")
@ApiTags("blame-request-management")
@ApiBearerAuth()
@UseGuards(GlobalGuard, RolesGuard)
@Roles(RoleEnum.USER)
export class RequestManagementController {
constructor(
private readonly requestManagementService: RequestManagementService,
) {}
@Post()
@UseGuards(GlobalGuard)
async createRequest(
@CurrentUser() user,
@Body() createRequestDto: CreateRequestDto,
) {
return await this.requestManagementService.createRequest(
user,
createRequestDto.type,
);
}
@Post("/initial-form/:requestId")
@ApiParam({ name: "requestId" })
@ApiBody({ type: InitialFormDto })
@UseGuards(GlobalGuard)
async initialFrom(
@Body()
body: InitialFormDto,
@CurrentUser() user,
@Param("requestId") requestId,
) {
return await this.requestManagementService.initialFormStep(
body,
user,
requestId,
);
}
@ApiBody({
schema: {
type: "object",
properties: {
file: {
type: "string",
format: "binary",
},
},
},
})
@ApiConsumes("multipart/form-data")
@UseInterceptors(
FileInterceptor("file", {
limits: {
fileSize: 20 * 1024 * 1024,
},
storage: diskStorage({
destination: "./files/video",
filename: (req, file, callback) => {
const unique = Date.now();
const ex = extname(file.originalname);
const filename = `${file.originalname}-${unique}${ex}`;
callback(null, filename);
},
}),
}),
)
@UseGuards(GlobalGuard)
@ApiParam({ name: "requestId" })
@Post("upload-video/:requestId")
@UseGuards(GlobalGuard)
async videoUploader(
@Param("requestId") requestId,
@CurrentUser() user,
@UploadedFile() file?: Express.Multer.File,
) {
return await this.requestManagementService.videoUploadStep(
file,
user,
requestId,
);
}
@Post("/add-plate/:requestId")
@ApiParam({ name: "requestId" })
@ApiBody({ type: AddPlateDto })
@UseGuards(GlobalGuard)
async addPlate(
@Body()
body: AddPlateDto,
@CurrentUser() user,
@Param("requestId") requestId,
) {
return await this.requestManagementService.addPlateToRequest(
user,
body,
requestId,
);
}
@Post("/carbody-form/:requestId")
@ApiParam({ name: "requestId" })
@ApiBody({ type: CarBodyFormDto })
@UseGuards(GlobalGuard)
async carBodyForm(
@Body()
body: CarBodyFormDto,
@CurrentUser() user,
@Param("requestId") requestId,
) {
return await this.requestManagementService.carBodyFormStep(
body,
user,
requestId,
);
}
@Post("/carbody-secondform/:requestId")
@ApiParam({ name: "requestId" })
@ApiBody({ type: CarBodySecondForm })
@UseGuards(GlobalGuard)
async carBodySecondForm(
@Body()
body: CarBodySecondForm,
@CurrentUser() user,
@Param("requestId") requestId,
) {
return await this.requestManagementService.carBodySecondForm(
body,
user,
requestId,
);
}
@Post("/add-detail-location/:requestId")
@ApiParam({ name: "requestId" })
@ApiBody({ type: LocationDto })
@UseGuards(GlobalGuard)
async addLocation(
@Body()
body: LocationDto,
@CurrentUser() user,
@Param("requestId") requestId,
) {
return await this.requestManagementService.addDetailToRequestLocation(
user.sub,
body,
requestId,
user, // Pass user object for expert verification
);
}
@ApiBody({
schema: {
type: "object",
properties: {
file: {
type: "string",
format: "binary",
},
},
},
})
@ApiConsumes("multipart/form-data")
@UseInterceptors(
FileInterceptor("file", {
limits: {
fileSize: 10 * 1024 * 1024,
},
storage: diskStorage({
destination: "./files/voice",
filename: (req, file, callback) => {
const unique = Date.now();
const ex = extname(file.originalname);
const flname = file.originalname.split(".")[0];
const filename = `${flname}-${unique}${ex}`;
callback(null, filename);
},
}),
}),
)
@ApiParam({ name: "requestId" })
@Post("upload-voice/:requestId")
@UseGuards(GlobalGuard)
async voiceUploader(
@Param("requestId") requestId,
@CurrentUser() user,
@UploadedFile() voice?: Express.Multer.File,
) {
return await this.requestManagementService.voiceUploadStep(
user,
voice,
requestId,
);
}
@Post("/add-detail-description/:requestId")
@ApiParam({ name: "requestId" })
@ApiBody({ type: DescriptionDto })
@UseGuards(GlobalGuard)
async addDescription(
@Body()
body: DescriptionDto,
@Param("requestId") requestId,
@CurrentUser() user,
) {
return await this.requestManagementService.addDescriptionToRequest(
user,
body,
requestId,
);
}
@Post("add-second-party-details/:phoneNumber/:requestId/:frontendRoutes")
@ApiParam({ name: "phoneNumber" })
@ApiParam({ name: "requestId" })
@ApiParam({ name: "frontendRoutes" })
@UseGuards(GlobalGuard)
async addSecondPartyDetailsToRequest(
@Req() req: Request,
@Param("phoneNumber") phoneNumber,
@Param("requestId") requestId,
@Param("frontendRoutes") frontendRoutes,
@CurrentUser() firstPartyUser,
) {
return await this.requestManagementService.addSecondPartyDetailsToRequest(
phoneNumber,
requestId,
firstPartyUser,
frontendRoutes,
req,
);
}
@Get("requests")
async requests(@CurrentUser() user) {
return await this.requestManagementService.myRequests(user.username);
}
@Get("creatable-claims")
@UseGuards(GlobalGuard)
async getCreatableClaims(@CurrentUser() user) {
return await this.requestManagementService.getCreatableClaims(user);
}
@Get("request/:requestId")
@ApiParam({ name: "requestId" })
@Roles(RoleEnum.USER)
async checkParties(@CurrentUser() user, @Param("requestId") requestId) {
return await this.requestManagementService.requestDetails(user, requestId);
}
@ApiBody({
schema: {
type: "object",
properties: {
sign: {
type: "string",
format: "binary",
},
isAccept: {
type: "Boolean",
},
},
},
})
@ApiConsumes("multipart/form-data")
@UseInterceptors(
FileInterceptor("sign", {
limits: {
fileSize: 10 * 1024 * 1024,
},
storage: diskStorage({
destination: "./files/signs",
filename: (req, file, callback) => {
const unique = Date.now();
const ex = extname(file.originalname);
const filename = `${file.originalname.split(/[.,\s-]/)[0]}-${unique}${ex}`;
callback(null, filename);
},
}),
}),
)
@Put("request/reply/:requestId")
@ApiParam({ name: "requestId" })
@UseGuards(GlobalGuard)
async userReply(
@Body("isAccept") isAccept: boolean,
@Param("requestId") requestId: string,
@CurrentUser() user,
@UploadedFile() sign?: Express.Multer.File,
) {
const props = { requestId, user };
return await this.requestManagementService.userReply(
props,
Boolean(isAccept),
sign,
);
}
@ApiBody({
schema: {
type: "object",
properties: {
nationalCertificate: { type: "string", format: "binary" },
carCertificate: { type: "string", format: "binary" },
drivingLicense: { type: "string", format: "binary" },
carGreenCard: { type: "string", format: "binary" },
voice: { type: "string", format: "binary" },
description: { type: "string" },
},
},
})
@ApiConsumes("multipart/form-data")
@UseInterceptors(
FileFieldsInterceptor(
[
{ name: "nationalCertificate", maxCount: 1 },
{ name: "carCertificate", maxCount: 1 },
{ name: "drivingLicense", maxCount: 1 },
{ name: "carGreenCard", maxCount: 1 },
{ name: "voice", maxCount: 1 },
],
{
limits: { fileSize: 10 * 1024 * 1024 },
storage: diskStorage({
destination: "./files/blame-resend-docs",
filename: (req, file, callback) => {
const unique = Date.now();
const ex = extname(file.originalname);
const filename = `${file.fieldname}-${unique}${ex}`;
callback(null, filename);
},
}),
},
),
)
@UseGuards(GlobalGuard)
@ApiParam({ name: "requestId" })
@Put("request/resend/:requestId")
userResendReply(
@Body("description") description: string,
@Param("requestId") requestId: string,
@CurrentUser() user: any,
@UploadedFiles()
files: { [key in BlameDocumentType | "voice"]?: Express.Multer.File[] },
) {
return this.requestManagementService.userResend(
files,
user,
requestId,
description,
);
}
}

View File

@@ -0,0 +1,72 @@
import { forwardRef, Module } from "@nestjs/common";
import { MongooseModule } from "@nestjs/mongoose";
import { MulterModule } from "@nestjs/platform-express";
import { ClaimRequestManagementModule } from "src/claim-request-management/claim-request-management.module";
import { ClientModule } from "src/client/client.module";
import { PlatesModule } from "src/plates/plates.module";
import { BlameVideoDbService } from "src/request-management/entities/db-service/blame-video.db.service";
import { RequestManagementDbService } from "src/request-management/entities/db-service/request-management.db.service";
import { SandHubModule } from "src/sand-hub/sand-hub.module";
import { UsersModule } from "src/users/users.module";
import { CronModule } from "src/utils/cron/cron.module";
import { SmsManagerModule } from "src/utils/sms-manager/sms-manager.module";
import { BlameDocumentDbService } from "./entities/db-service/blame-document.db.service";
import { BlameVoiceDbService } from "./entities/db-service/blame.voice.db.service";
import { UserSignDbService } from "./entities/db-service/sign.db.service";
import { UserSignModel, UserSignSchema } from "./entities/schema/sign.schema";
import {
BlameVideosModel,
BlameVideoSchema,
} from "./entities/schema/blame-video.schema";
import {
BlameVoiceSchema,
BlameVoicesModel,
} from "./entities/schema/blame-voice.schema";
import { BlameDocumentModel, BlameDocumentSchema } from "./entities/schema/blame-document.schema";
import {
RequestManagementModel,
RequestManagementSchema,
} from "./entities/schema/request-management.schema";
import { RequestManagementService } from "./request-management.service";
import { RequestManagementController } from "./request-management.controller";
import { ExpertInitiatedController } from "./expert-initiated.controller";
@Module({
imports: [
UsersModule,
ClientModule,
SandHubModule,
SmsManagerModule,
PlatesModule,
MulterModule.register({
dest: "./files/video",
}),
MongooseModule.forFeature([
{ name: RequestManagementModel.name, schema: RequestManagementSchema },
{ name: BlameVideosModel.name, schema: BlameVideoSchema },
{ name: BlameVoicesModel.name, schema: BlameVoiceSchema },
{ name: UserSignModel.name, schema: UserSignSchema },
{ name: BlameDocumentModel.name, schema: BlameDocumentSchema },
]),
forwardRef(() => ClaimRequestManagementModule),
CronModule,
],
controllers: [RequestManagementController, ExpertInitiatedController],
providers: [
RequestManagementService,
BlameVideoDbService,
BlameVoiceDbService,
UserSignDbService,
RequestManagementDbService,
BlameDocumentDbService,
],
exports: [
RequestManagementService,
RequestManagementDbService,
BlameVideoDbService,
BlameVoiceDbService,
BlameDocumentDbService,
UserSignDbService,
],
})
export class RequestManagementModule {}

View File

@@ -0,0 +1,18 @@
import { Test, TestingModule } from "@nestjs/testing";
import { RequestManagementService } from "./request-management.service";
describe("RequestManagementService", () => {
let service: RequestManagementService;
beforeEach(async () => {
const module: TestingModule = await Test.createTestingModule({
providers: [RequestManagementService],
}).compile();
service = module.get<RequestManagementService>(RequestManagementService);
});
it("should be defined", () => {
expect(service).toBeDefined();
});
});

File diff suppressed because it is too large Load Diff