This commit is contained in:
Vins 2024-09-09 05:45:17 +02:00
parent f8e123b9f5
commit 3e64472821
19 changed files with 906 additions and 40 deletions

View File

@ -59,7 +59,7 @@
"file-type-checker": "^1.0.8", "file-type-checker": "^1.0.8",
"fp-ts": "^2.16.1", "fp-ts": "^2.16.1",
"jsonwebtoken": "^9.0.0", "jsonwebtoken": "^9.0.0",
"le-coffre-resources": "git@github.com:smart-chain-fr/leCoffre-resources.git#v2.151", "le-coffre-resources": "git@github.com:smart-chain-fr/leCoffre-resources.git#v2.160",
"module-alias": "^2.2.2", "module-alias": "^2.2.2",
"monocle-ts": "^2.3.13", "monocle-ts": "^2.3.13",
"multer": "^1.4.5-lts.1", "multer": "^1.4.5-lts.1",

View File

@ -7,13 +7,14 @@ import { Customer } from "le-coffre-resources/dist/Notary";
import { validateOrReject } from "class-validator"; import { validateOrReject } from "class-validator";
import authHandler from "@App/middlewares/AuthHandler"; import authHandler from "@App/middlewares/AuthHandler";
import ruleHandler from "@App/middlewares/RulesHandler"; import ruleHandler from "@App/middlewares/RulesHandler";
import { Prisma } from "@prisma/client"; import { Documents, Prisma } from "@prisma/client";
import customerHandler from "@App/middlewares/OfficeMembershipHandlers/CustomerHandler"; import customerHandler from "@App/middlewares/OfficeMembershipHandlers/CustomerHandler";
import DocumentsService from "@Services/notary/DocumentsService/DocumentsService";
@Controller() @Controller()
@Service() @Service()
export default class CustomersController extends ApiController { export default class CustomersController extends ApiController {
constructor(private customersService: CustomersService) { constructor(private customersService: CustomersService, private documentsService: DocumentsService) {
super(); super();
} }
@ -215,4 +216,64 @@ export default class CustomersController extends ApiController {
return; return;
} }
} }
/**
* @description Send a reminder to a specific customer by uid to signe specific documents
*/
@Post("/api/v1/notary/customers/:uid/send_reminder", [authHandler, ruleHandler, customerHandler])
protected async sendReminder(req: Request, response: Response) {
try {
const uid = req.params["uid"];
if (!uid) {
this.httpBadRequest(response, "No uid provided");
return;
}
const documentsUid = req.body.documentsUid;
if (!documentsUid || !Array.isArray(documentsUid)) {
this.httpBadRequest(response, "Invalid or missing documents");
return;
}
const documentEntities : Documents[] = [];
//For each document uid, use DocumentsService.getByUid to get the document entity and add it to the documents array
for (const documentUid of documentsUid) {
console.log("documentUid", documentUid);
const documentEntity = await this.documentsService.getByUid(documentUid, { document_type : true, folder : true });
console.log("documentEntity", documentEntity);
if (!documentEntity) {
this.httpBadRequest(response, "Document not found");
return;
}
documentEntities.push(documentEntity);
}
const customerEntity = await this.customersService.getByUid(uid, { contact: true, office : true });
console.log("customerEntity", customerEntity);
if (!customerEntity) {
this.httpNotFoundRequest(response, "customer not found");
return;
}
//Hydrate ressource with prisma entity
const customer = Customer.hydrate<Customer>(customerEntity, { strategy: "excludeAll" });
console.log("customer", customer);
// Call service to send reminder with documents
const reminder = await this.customersService.sendDocumentsReminder(customer, documentEntities);
console.log("Reminder sent", reminder);
// Success
this.httpSuccess(response, customer);
} catch (error) {
this.httpInternalError(response, error);
return;
}
}
} }

View File

@ -0,0 +1,207 @@
import { Response, Request } from "express";
import { Controller, Delete, Get, Post } from "@ControllerPattern/index";
import ApiController from "@Common/system/controller-pattern/ApiController";
import { Service } from "typedi";
import DocumentsNotaryService from "@Services/notary/DocumentsNotaryService/DocumentsNotaryService";
import { DocumentsNotary, Prisma } from "@prisma/client";
import { Document, DocumentNotary, FileNotary } from "le-coffre-resources/dist/Notary";
import authHandler from "@App/middlewares/AuthHandler";
import ruleHandler from "@App/middlewares/RulesHandler";
import documentHandler from "@App/middlewares/OfficeMembershipHandlers/DocumentHandler";
import OfficeFoldersService from "@Services/notary/OfficeFoldersService/OfficeFoldersService";
import CustomersService from "@Services/admin/CustomersService/CustomersService";
import UsersService from "@Services/notary/UsersService/UsersService";
import FilesService from "@Services/common/FilesService/FilesService";
// import NotificationBuilder from "@Common/notifications/NotificationBuilder";
@Controller()
@Service()
export default class DocumentsNotaryController extends ApiController {
constructor(
private documentsNotaryService: DocumentsNotaryService,
private officeFoldersService: OfficeFoldersService,
private customerService: CustomersService,
private userService: UsersService,
private filesService: FilesService,
) {
super();
}
/**
* @description Get all documents
* @returns IDocument[] list of documents
*/
@Get("/api/v1/notary/documents_notary", [authHandler, ruleHandler])
protected async get(req: Request, response: Response) {
try {
//get query
let query: Prisma.DocumentsNotaryFindManyArgs = {};
if (req.query["q"]) {
query = JSON.parse(req.query["q"] as string);
if (query.where?.uid) {
this.httpBadRequest(response, "You can't filter by uid");
return;
}
}
const officeId: string = req.body.user.office_Id;
const officeWhereInput: Prisma.OfficesWhereInput = { uid: officeId };
if (!query.where) query.where = { folder: { office: officeWhereInput } };
query.where.folder!.office = officeWhereInput;
//call service to get prisma entity
const documentEntities = await this.documentsNotaryService.get(query);
//Hydrate ressource with prisma entity
const documents = Document.hydrateArray<Document>(documentEntities, { strategy: "excludeAll" });
//success
this.httpSuccess(response, documents);
} catch (error) {
this.httpInternalError(response, error);
return;
}
}
/**
* @description Create a new document
* @returns IDocument created
*/
@Post("/api/v1/notary/documents_notary", [authHandler])
protected async post(req: Request, response: Response) {
try {
if(!req.file) return;
const customer = await this.customerService.getByUid(req.body.customerUid);
if (!customer) return;
console.log("customer", customer);
const folder = await this.officeFoldersService.getByUid(req.body.folderUid);
if (!folder) return;
console.log("folder", folder);
const user = await this.userService.getByUid(req.body.user.userId);
if (!user) return;
const documentNotaryEntity = DocumentNotary.hydrate<DocumentNotary>({
customer: customer,
folder: folder,
depositor: user,
});
const documentNotaryEntityCreated = await this.documentsNotaryService.create(documentNotaryEntity);
console.log("documentNotaryEntityCreated", documentNotaryEntityCreated);
const query = JSON.stringify({ document: { uid: documentNotaryEntityCreated.uid } });
const fileEntity = FileNotary.hydrate<FileNotary>(JSON.parse(query));
const fileEntityCreated = await this.filesService.createFileNotary(fileEntity, req.file);
console.log("fileEntityCreated", fileEntityCreated);
// //init Document resource with request body values
// const documentNotaryEntity = DocumentNotary.hydrate<DocumentNotary>(req.body);
// console.log(documentNotaryEntity);
// const folder = await this.officeFoldersService.getByUid(documentNotaryEntity.folder?.uid!, {
// folder_anchor: true,
// });
// if (!folder) {
// this.httpBadRequest(response, "Folder not found");
// return;
// }
// const folderRessource = OfficeFolder.hydrate<OfficeFolder>(folder);
// if (folderRessource.folder_anchor) {
// this.httpBadRequest(response, "Cannot add document on an anchored or anchoring folder");
// return;
// }
// //validate document
// await validateOrReject(documentNotaryEntity, { groups: ["createDocument"], forbidUnknownValues: false });
// //call service to get prisma entity
// const documentNotaryEntityCreated = await this.documentsNotaryService.create(documentNotaryEntity);
// //Hydrate ressource with prisma entity
// const documentNotary = DocumentNotary.hydrate<DocumentNotary>(documentNotaryEntityCreated, {
// strategy: "excludeAll",
// });
// //success
// this.httpCreated(response, documentNotary);
} catch (error) {
this.httpInternalError(response, error);
return;
}
}
/**
* @description Delete a specific document
*/
@Delete("/api/v1/notary/documents_notary/:uid", [authHandler, ruleHandler, documentHandler])
protected async delete(req: Request, response: Response) {
try {
const uid = req.params["uid"];
if (!uid) {
this.httpBadRequest(response, "No uid provided");
return;
}
const documentFound = await this.documentsNotaryService.getByUid(uid);
if (!documentFound) {
this.httpNotFoundRequest(response, "document not found");
return;
}
//call service to get prisma entity
const documentEntity: DocumentsNotary = await this.documentsNotaryService.delete(uid);
//Hydrate ressource with prisma entity
const document = Document.hydrate<Document>(documentEntity, { strategy: "excludeAll" });
//success
this.httpSuccess(response, document);
} catch (error) {
this.httpInternalError(response, error);
return;
}
}
/**
* @description Get a specific document by uid
*/
@Get("/api/v1/notary/documents_notary/:uid", [authHandler, ruleHandler, documentHandler])
protected async getOneByUid(req: Request, response: Response) {
try {
const uid = req.params["uid"];
if (!uid) {
this.httpBadRequest(response, "No uid provided");
return;
}
//get query
let query;
if (req.query["q"]) {
query = JSON.parse(req.query["q"] as string);
}
const documentEntity = await this.documentsNotaryService.getByUid(uid, query);
if (!documentEntity) {
this.httpNotFoundRequest(response, "document not found");
return;
}
//Hydrate ressource with prisma entity
const document = Document.hydrate<Document>(documentEntity, { strategy: "excludeAll" });
//success
this.httpSuccess(response, document);
} catch (error) {
this.httpInternalError(response, error);
return;
}
}
}

View File

@ -0,0 +1,87 @@
import { Response, Request } from "express";
import { Controller, Get } from "@ControllerPattern/index";
import ApiController from "@Common/system/controller-pattern/ApiController";
import { Service } from "typedi";
import { Prisma } from "@prisma/client";
import { DocumentReminder } from "le-coffre-resources/dist/Notary";
import authHandler from "@App/middlewares/AuthHandler";
import DocumentsReminderService from "@Services/notary/DocumentsReminder/DocumentsReminder";
// import NotificationBuilder from "@Common/notifications/NotificationBuilder";
@Controller()
@Service()
export default class DocumentsReminderController extends ApiController {
constructor(
private documentsReminderService: DocumentsReminderService,
) {
super();
}
/**
* @description Get all documents
* @returns IDocument[] list of documents
*/
@Get("/api/v1/notary/document_reminders", [authHandler])
protected async get(req: Request, response: Response) {
try {
//get query
let query: Prisma.DocumentsReminderFindManyArgs = {};
if (req.query["q"]) {
query = JSON.parse(req.query["q"] as string);
if(query.where?.uid) {
this.httpBadRequest(response, "You can't filter by uid");
return;
}
}
//call service to get prisma entity
const documentReminderEntities = await this.documentsReminderService.get(query);
//Hydrate ressource with prisma entity
const documentReminders = DocumentReminder.hydrateArray<DocumentReminder>(documentReminderEntities, { strategy: "excludeAll" });
//success
this.httpSuccess(response, documentReminders);
} catch (error) {
this.httpInternalError(response, error);
return;
}
}
// /**
// * @description Get a specific document by uid
// */
// @Get("/api/v1/notary/document_reminders/:uid", [authHandler, ruleHandler, documentHandler])
// protected async getOneByUid(req: Request, response: Response) {
// try {
// const uid = req.params["uid"];
// if (!uid) {
// this.httpBadRequest(response, "No uid provided");
// return;
// }
// //get query
// let query;
// if (req.query["q"]) {
// query = JSON.parse(req.query["q"] as string);
// }
// const documentEntity = await this.documentsNotaryService.getByUid(uid, query);
// if (!documentEntity) {
// this.httpNotFoundRequest(response, "document not found");
// return;
// }
// //Hydrate ressource with prisma entity
// const document = Document.hydrate<Document>(documentEntity, { strategy: "excludeAll" });
// //success
// this.httpSuccess(response, document);
// } catch (error) {
// this.httpInternalError(response, error);
// return;
// }
// }
}

View File

@ -55,6 +55,8 @@ import StripeWebhooks from "@Common/webhooks/stripeWebhooks";
import RulesGroupsController from "./api/admin/RulesGroupsController"; import RulesGroupsController from "./api/admin/RulesGroupsController";
import NotesController from "./api/customer/NotesController"; import NotesController from "./api/customer/NotesController";
import MailchimpController from "./api/notary/MailchimpController"; import MailchimpController from "./api/notary/MailchimpController";
import DocumentsReminderController from "./api/notary/DocumentsReminderController";
import DocumentsNotaryController from "./api/notary/DocumentsNotaryController";
/** /**
* @description This allow to declare all controllers used in the application * @description This allow to declare all controllers used in the application
@ -118,5 +120,7 @@ export default {
Container.get(RulesGroupsController); Container.get(RulesGroupsController);
Container.get(NotesController); Container.get(NotesController);
Container.get(MailchimpController); Container.get(MailchimpController);
Container.get(DocumentsReminderController)
Container.get(DocumentsNotaryController)
}, },
}; };

View File

@ -0,0 +1,45 @@
-- CreateTable
CREATE TABLE "documents_notary" (
"uid" TEXT NOT NULL,
"folder_uid" VARCHAR(255) NOT NULL,
"depositor_uid" VARCHAR(255) NOT NULL,
"created_at" TIMESTAMP(3) DEFAULT CURRENT_TIMESTAMP,
"updated_at" TIMESTAMP(3),
CONSTRAINT "documents_notary_pkey" PRIMARY KEY ("uid")
);
-- CreateTable
CREATE TABLE "files_notary" (
"uid" TEXT NOT NULL,
"document_uid" VARCHAR(255) NOT NULL,
"file_path" VARCHAR(255) NOT NULL,
"file_name" VARCHAR(255) NOT NULL,
"mimetype" VARCHAR(255) NOT NULL,
"hash" VARCHAR(255) NOT NULL,
"size" INTEGER NOT NULL,
"archived_at" TIMESTAMP(3),
"key" VARCHAR(255),
"created_at" TIMESTAMP(3) DEFAULT CURRENT_TIMESTAMP,
"updated_at" TIMESTAMP(3),
CONSTRAINT "files_notary_pkey" PRIMARY KEY ("uid")
);
-- CreateIndex
CREATE UNIQUE INDEX "documents_notary_uid_key" ON "documents_notary"("uid");
-- CreateIndex
CREATE UNIQUE INDEX "files_notary_uid_key" ON "files_notary"("uid");
-- CreateIndex
CREATE UNIQUE INDEX "files_notary_file_path_key" ON "files_notary"("file_path");
-- AddForeignKey
ALTER TABLE "documents_notary" ADD CONSTRAINT "documents_notary_folder_uid_fkey" FOREIGN KEY ("folder_uid") REFERENCES "office_folders"("uid") ON DELETE RESTRICT ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "documents_notary" ADD CONSTRAINT "documents_notary_depositor_uid_fkey" FOREIGN KEY ("depositor_uid") REFERENCES "users"("uid") ON DELETE CASCADE ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "files_notary" ADD CONSTRAINT "files_notary_document_uid_fkey" FOREIGN KEY ("document_uid") REFERENCES "documents_notary"("uid") ON DELETE CASCADE ON UPDATE CASCADE;

View File

@ -0,0 +1,16 @@
-- CreateTable
CREATE TABLE "documents_reminder" (
"uid" TEXT NOT NULL,
"document_uid" VARCHAR(255) NOT NULL,
"reminder_date" TIMESTAMP(3) NOT NULL,
"created_at" TIMESTAMP(3) DEFAULT CURRENT_TIMESTAMP,
"updated_at" TIMESTAMP(3),
CONSTRAINT "documents_reminder_pkey" PRIMARY KEY ("uid")
);
-- CreateIndex
CREATE UNIQUE INDEX "documents_reminder_uid_key" ON "documents_reminder"("uid");
-- AddForeignKey
ALTER TABLE "documents_reminder" ADD CONSTRAINT "documents_reminder_document_uid_fkey" FOREIGN KEY ("document_uid") REFERENCES "documents"("uid") ON DELETE CASCADE ON UPDATE CASCADE;

View File

@ -0,0 +1,17 @@
/*
Warnings:
- You are about to drop the column `archived_at` on the `files_notary` table. All the data in the column will be lost.
- You are about to drop the column `key` on the `files_notary` table. All the data in the column will be lost.
- Added the required column `customer_uid` to the `documents_notary` table without a default value. This is not possible if the table is not empty.
*/
-- AlterTable
ALTER TABLE "documents_notary" ADD COLUMN "customer_uid" VARCHAR(255) NOT NULL;
-- AlterTable
ALTER TABLE "files_notary" DROP COLUMN "archived_at",
DROP COLUMN "key";
-- AddForeignKey
ALTER TABLE "documents_notary" ADD CONSTRAINT "documents_notary_customer_uid_fkey" FOREIGN KEY ("customer_uid") REFERENCES "customers"("uid") ON DELETE CASCADE ON UPDATE CASCADE;

View File

@ -69,6 +69,7 @@ model Users {
votes Votes[] votes Votes[]
user_notifications UserNotifications[] user_notifications UserNotifications[]
seats Seats[] seats Seats[]
documents_notary DocumentsNotary[]
@@map("users") @@map("users")
} }
@ -129,6 +130,7 @@ model Customers {
office Offices @relation(fields: [office_uid], references: [uid], onDelete: Cascade) office Offices @relation(fields: [office_uid], references: [uid], onDelete: Cascade)
office_uid String @db.VarChar(255) office_uid String @db.VarChar(255)
notes Notes[] notes Notes[]
documents_notary DocumentsNotary[]
@@map("customers") @@map("customers")
} }
@ -170,6 +172,7 @@ model OfficeFolders {
stakeholders Users[] @relation("OfficeFolderHasStakeholders") stakeholders Users[] @relation("OfficeFolderHasStakeholders")
customers Customers[] @relation("OfficeFolderHasCustomers") customers Customers[] @relation("OfficeFolderHasCustomers")
documents Documents[] documents Documents[]
documents_notary DocumentsNotary[]
folder_anchor OfficeFolderAnchors? @relation(fields: [folder_anchor_uid], references: [uid]) folder_anchor OfficeFolderAnchors? @relation(fields: [folder_anchor_uid], references: [uid])
folder_anchor_uid String? @unique @db.VarChar(255) folder_anchor_uid String? @unique @db.VarChar(255)
@ -215,10 +218,37 @@ model Documents {
updated_at DateTime? @updatedAt updated_at DateTime? @updatedAt
files Files[] files Files[]
document_history DocumentHistory[] document_history DocumentHistory[]
reminders DocumentsReminder[]
@@map("documents") @@map("documents")
} }
model DocumentsNotary {
uid String @id @unique @default(uuid())
folder OfficeFolders @relation(fields: [folder_uid], references: [uid])
folder_uid String @db.VarChar(255)
depositor Users @relation(fields: [depositor_uid], references: [uid], onDelete: Cascade)
depositor_uid String @db.VarChar(255)
created_at DateTime? @default(now())
updated_at DateTime? @updatedAt
files FilesNotary[]
customer Customers @relation(fields: [customer_uid], references: [uid], onDelete: Cascade)
customer_uid String @db.VarChar(255)
@@map("documents_notary")
}
model DocumentsReminder {
uid String @id @unique @default(uuid())
document Documents @relation(fields: [document_uid], references: [uid], onDelete: Cascade)
document_uid String @db.VarChar(255)
reminder_date DateTime
created_at DateTime? @default(now())
updated_at DateTime? @updatedAt
@@map("documents_reminder")
}
model DocumentHistory { model DocumentHistory {
uid String @id @unique @default(uuid()) uid String @id @unique @default(uuid())
document_status EDocumentStatus @default(ASKED) document_status EDocumentStatus @default(ASKED)
@ -248,6 +278,21 @@ model Files {
@@map("files") @@map("files")
} }
model FilesNotary {
uid String @id @unique @default(uuid())
document_notary DocumentsNotary @relation(fields: [document_uid], references: [uid], onDelete: Cascade)
document_uid String @db.VarChar(255)
file_path String @unique @db.VarChar(255)
file_name String @db.VarChar(255)
mimetype String @db.VarChar(255)
hash String @db.VarChar(255)
size Int
created_at DateTime? @default(now())
updated_at DateTime? @updatedAt
@@map("files_notary")
}
model DocumentTypes { model DocumentTypes {
uid String @id @unique @default(uuid()) uid String @id @unique @default(uuid())
name String @db.VarChar(255) name String @db.VarChar(255)

View File

@ -7,6 +7,7 @@ import MailchimpService from "@Services/common/MailchimpService/MailchimpService
import { BackendVariables } from "@Common/config/variables/Variables"; import { BackendVariables } from "@Common/config/variables/Variables";
import UsersService from "@Services/super-admin/UsersService/UsersService"; import UsersService from "@Services/super-admin/UsersService/UsersService";
import User from "le-coffre-resources/dist/SuperAdmin"; import User from "le-coffre-resources/dist/SuperAdmin";
import { Customer } from "le-coffre-resources/dist/Notary";
@Service() @Service()
export default class EmailBuilder { export default class EmailBuilder {
@ -138,4 +139,31 @@ export default class EmailBuilder {
} }
public async sendReminder(customer: Customer, documents: Documents[]) {
const to = customer.contact!.email;
const templateVariables = {
office_name: customer.office?.name,
last_name: customer.contact!.last_name,
first_name: customer.contact!.first_name,
link: this.variables.APP_HOST,
};
const templateName = ETemplates.DOCUMENT_REMINDER;
const subject = "Vous avez des documents à déposer pour votre dossier.";
this.mailchimpService.create({
templateName,
to,
subject,
templateVariables,
uid: "",
from: null,
cc: [],
cci: [],
sentAt: null,
nbTrySend: null,
lastTrySendDate: null,
});
}
} }

View File

@ -3,4 +3,5 @@ export const ETemplates = {
DOCUMENT_REFUSED: "DOCUMENT_REFUSED", DOCUMENT_REFUSED: "DOCUMENT_REFUSED",
DOCUMENT_RECAP: "DOCUMENT_RECAP", DOCUMENT_RECAP: "DOCUMENT_RECAP",
SUBSCRIPTION_INVITATION: "SUBSCRIPTION_INVITATION", SUBSCRIPTION_INVITATION: "SUBSCRIPTION_INVITATION",
DOCUMENT_REMINDER: "DOCUMENT_REMINDER",
}; };

View File

@ -0,0 +1,102 @@
import Database from "@Common/databases/database";
import BaseRepository from "@Repositories/BaseRepository";
import { Service } from "typedi";
import { DocumentsNotary, Prisma } from "@prisma/client";
import { DocumentNotary } from "le-coffre-resources/dist/Notary";
@Service()
export default class DocumentsNotaryRepository extends BaseRepository {
constructor(private database: Database) {
super();
}
protected get model() {
return this.database.getClient().documentsNotary;
}
protected get instanceDb() {
return this.database.getClient();
}
/**
* @description : Find many documents
*/
public async findMany(query: Prisma.DocumentsNotaryFindManyArgs) {
query.take = Math.min(query.take || this.defaultFetchRows, this.maxFetchRows);
return this.model.findMany(query);
}
/**
* @description : Create a document
*/
public async create(document: DocumentNotary): Promise<DocumentsNotary> {
const createArgs: Prisma.DocumentsNotaryCreateArgs = {
data: {
folder: {
connect: {
uid: document.folder!.uid,
},
},
depositor: {
connect: {
uid: document.depositor!.uid,
},
},
customer: {
connect: {
uid: document.customer!.uid,
},
},
},
};
const documentCreated = await this.model.create({ ...createArgs });
return documentCreated;
}
/**
* @description : Delete a document
*/
public async delete(uid: string): Promise<DocumentsNotary> {
return this.model.delete({
where: {
uid: uid,
},
});
}
/**
* @description : Find unique document
*/
public async findOneByUid(uid: string, query?: Prisma.DocumentsNotaryInclude): Promise<DocumentsNotary | null> {
return this.model.findUnique({
where: {
uid: uid,
},
include: query,
});
}
/**
* @description : Find unique document with relations
*/
public async findOneByUidWithOffice(uid: string) {
return this.model.findUnique({
where: {
uid: uid,
},
include: { folder: { include: { office: true } } },
});
}
/**
* @description : Find unique document with relations
*/
public async findOneByUidWithFiles(uid: string) {
return this.model.findUnique({
where: {
uid: uid,
},
include: { files: true },
});
}
}

View File

@ -0,0 +1,46 @@
import Database from "@Common/databases/database";
import BaseRepository from "@Repositories/BaseRepository";
import { Service } from "typedi";
import { DocumentsReminder, Prisma } from "@prisma/client";
import { DocumentReminder } from "le-coffre-resources/dist/Notary";
@Service()
export default class DocumentsReminderRepository extends BaseRepository {
constructor(private database: Database) {
super();
}
protected get model() {
return this.database.getClient().documentsReminder;
}
protected get instanceDb() {
return this.database.getClient();
}
/**
* @description : Find many documents
*/
public async findMany(query: Prisma.DocumentsReminderFindManyArgs) {
query.take = Math.min(query.take || this.defaultFetchRows, this.maxFetchRows);
return this.model.findMany(query);
}
/**
* @description : Create a document
*/
public async create(documentReminder: DocumentReminder): Promise<DocumentsReminder> {
const createArgs: Prisma.DocumentsReminderCreateArgs = {
data: {
document: {
connect: {
uid: documentReminder.document!.uid,
},
},
reminder_date: new Date(),
},
};
const documentReminderCreated = await this.model.create({ ...createArgs });
return documentReminderCreated;
}
}

View File

@ -0,0 +1,83 @@
import Database from "@Common/databases/database";
import BaseRepository from "@Repositories/BaseRepository";
import { Service } from "typedi";
import { FilesNotary, Prisma } from "@prisma/client";
import { FileNotary } from "le-coffre-resources/dist/Notary";
@Service()
export default class FilesNotaryRepository extends BaseRepository {
constructor(private database: Database) {
super();
}
protected get model() {
return this.database.getClient().filesNotary;
}
protected get instanceDb() {
return this.database.getClient();
}
/**
* @description : Find many files
*/
public async findMany(query: Prisma.FilesNotaryFindManyArgs) {
query.take = Math.min(query.take || this.defaultFetchRows, this.maxFetchRows);
return this.model.findMany(query);
}
/**
* @description : Create a file linked to a document
*/
public async create(file: FileNotary, key: string): Promise<FilesNotary> {
const createArgs: Prisma.FilesNotaryCreateArgs = {
data: {
document_notary: {
connect: {
uid: file.document!.uid,
},
},
file_name: file.file_name,
file_path: file.file_path,
mimetype: file.mimetype,
hash: file.hash,
size: file.size,
},
};
return this.model.create({ ...createArgs, include: { document_notary: true } });
}
/**
* @description : Find unique file
*/
public async findOneByUid(uid: string, query?: Prisma.FilesNotaryInclude) {
return this.model.findUnique({
where: {
uid: uid,
},
include: query,
});
}
/**
* @description : Find unique file with office
*/
public async findOneByUidWithOffice(uid: string) {
return this.model.findUnique({
where: {
uid: uid,
},
include: { document_notary: { include: { folder: { include: { office: true } } } } },
});
}
/**
* @description : Find unique file with document
*/
public async findOneByUidWithDocument(uid: string) {
return this.model.findUnique({
where: {
uid: uid,
},
include: { document_notary: true },
});
}
}

View File

@ -8,6 +8,8 @@ import { BackendVariables } from "@Common/config/variables/Variables";
import { Readable } from "stream"; import { Readable } from "stream";
import { v4 } from "uuid"; import { v4 } from "uuid";
import { Files, Prisma } from "@prisma/client"; import { Files, Prisma } from "@prisma/client";
import { FileNotary } from "le-coffre-resources/dist/Notary";
import FilesNotaryRepository from "@Repositories/FilesNotaryRepository";
@Service() @Service()
export default class FilesService extends BaseService { export default class FilesService extends BaseService {
@ -16,6 +18,7 @@ export default class FilesService extends BaseService {
private ipfsService: IpfsService, private ipfsService: IpfsService,
private variables: BackendVariables, private variables: BackendVariables,
private cryptoService: CryptoService, private cryptoService: CryptoService,
private filesNotaryRepository: FilesNotaryRepository
) { ) {
super(); super();
} }
@ -85,6 +88,27 @@ export default class FilesService extends BaseService {
return this.filesRepository.create(fileToCreate, key); return this.filesRepository.create(fileToCreate, key);
} }
/**
* @description : Create a new file
* @throws {Error} If file cannot be created
*/
public async createFileNotary(file: FileNotary, fileData: Express.Multer.File) {
const key = v4();
const encryptedFile = await this.cryptoService.encrypt(fileData.buffer, key);
const hash = await this.cryptoService.getHash(fileData.buffer);
const upload = await this.ipfsService.pinFile(Readable.from(encryptedFile), fileData.originalname);
let fileToCreate: FileNotary = file;
fileToCreate.file_name = fileData.originalname;
fileToCreate.file_path = this.variables.PINATA_GATEWAY.concat(upload.IpfsHash);
fileToCreate.mimetype = fileData.mimetype;
fileToCreate.size = fileData.size;
fileToCreate.hash = hash;
fileToCreate.archived_at = null;
return this.filesNotaryRepository.create(fileToCreate, key);
}
/** /**
* @description : Modify a new file * @description : Modify a new file
* @throws {Error} If file cannot be modified * @throws {Error} If file cannot be modified

View File

@ -124,14 +124,12 @@ export default class IdNotService extends BaseService {
const token = await fetch(this.variables.IDNOT_BASE_URL + this.variables.IDNOT_CONNEXION_URL + "?" + query, { method: "POST" }); const token = await fetch(this.variables.IDNOT_BASE_URL + this.variables.IDNOT_CONNEXION_URL + "?" + query, { method: "POST" });
if(token.status !== 200) console.error(await token.text()); if (token.status !== 200) console.error(await token.text());
const decodedToken = (await token.json()) as IIdNotToken; const decodedToken = (await token.json()) as IIdNotToken;
const decodedIdToken = jwt.decode(decodedToken.id_token) as IdNotJwtPayload; const decodedIdToken = jwt.decode(decodedToken.id_token) as IdNotJwtPayload;
return decodedIdToken; return decodedIdToken;
} }
@ -297,7 +295,8 @@ export default class IdNotService extends BaseService {
const searchParams = new URLSearchParams({ const searchParams = new URLSearchParams({
key: this.variables.IDNOT_API_KEY, key: this.variables.IDNOT_API_KEY,
}); });
const officeRawData = await fetch(`${this.variables.IDNOT_API_BASE_URL}/api/pp/v2/entities/${office.idNot}?` + searchParams, {
const officeRawData = await fetch(`${this.variables.IDNOT_API_BASE_URL}/api/pp/v2/entites/${office.idNot}?` + searchParams, {
method: "GET", method: "GET",
}); });
if (officeRawData.status === 404) { if (officeRawData.status === 404) {
@ -378,7 +377,7 @@ export default class IdNotService extends BaseService {
}, },
}; };
if(!userToAdd.contact.email) { if (!userToAdd.contact.email) {
return null; return null;
} }
@ -388,7 +387,7 @@ export default class IdNotService extends BaseService {
userHydrated = User.hydrate<User>(user); userHydrated = User.hydrate<User>(user);
const userOfficeHydrated = Office.hydrate<Office>(userOffice!); const userOfficeHydrated = Office.hydrate<Office>(userOffice!);
if(office.length === 0) { if (office.length === 0) {
const officeRoles = await this.officeRolesService.get({ const officeRoles = await this.officeRolesService.get({
where: { office: { idNot: "0000" } }, where: { office: { idNot: "0000" } },
include: { office: true, rules: true }, include: { office: true, rules: true },
@ -406,7 +405,6 @@ export default class IdNotService extends BaseService {
const deedTypesHydrated = DeedType.hydrateArray<DeedType>(deedTypes); const deedTypesHydrated = DeedType.hydrateArray<DeedType>(deedTypes);
const documentTypesHydrated = DocumentType.hydrateArray<DocumentType>(documentTypes); const documentTypesHydrated = DocumentType.hydrateArray<DocumentType>(documentTypes);
await this.duplicateOfficeRoles(officeRolesHydrated, userOfficeHydrated); await this.duplicateOfficeRoles(officeRolesHydrated, userOfficeHydrated);
const documentTypesCreated = await this.duplicateDocumentTypes(documentTypesHydrated, userOfficeHydrated); const documentTypesCreated = await this.duplicateDocumentTypes(documentTypesHydrated, userOfficeHydrated);
await this.duplicateDeedTypes(deedTypesHydrated, documentTypesCreated, userOfficeHydrated); await this.duplicateDeedTypes(deedTypesHydrated, documentTypesCreated, userOfficeHydrated);
@ -427,37 +425,35 @@ export default class IdNotService extends BaseService {
public async duplicateDocumentTypes(documentTypes: DocumentType[], office: Office): Promise<DocumentType[]> { public async duplicateDocumentTypes(documentTypes: DocumentType[], office: Office): Promise<DocumentType[]> {
let newDocumentTypes: DocumentType[] = []; let newDocumentTypes: DocumentType[] = [];
for(const documentType of documentTypes) { for (const documentType of documentTypes) {
documentType.office = office; documentType.office = office;
const documentTypeCreated = await this.documentTypesService.create(documentType); const documentTypeCreated = await this.documentTypesService.create(documentType);
newDocumentTypes.push(DocumentType.hydrate<DocumentType>(documentTypeCreated)); newDocumentTypes.push(DocumentType.hydrate<DocumentType>(documentTypeCreated));
}; }
return newDocumentTypes; return newDocumentTypes;
} }
public async duplicateDeedTypes(deedTypes: DeedType[], documentTypes: DocumentType[], office: Office) { public async duplicateDeedTypes(deedTypes: DeedType[], documentTypes: DocumentType[], office: Office) {
for (const deedType of deedTypes) { for (const deedType of deedTypes) {
let newDocumentTypes: DocumentType[] = []; let newDocumentTypes: DocumentType[] = [];
for (const document of deedType.document_types!) { for (const document of deedType.document_types!) {
const newDocumentType = documentTypes.find((documentType) => documentType.name === document.name); const newDocumentType = documentTypes.find((documentType) => documentType.name === document.name);
if(!newDocumentType) continue; if (!newDocumentType) continue;
newDocumentTypes.push(newDocumentType!); newDocumentTypes.push(newDocumentType!);
}; }
deedType.document_types = newDocumentTypes; deedType.document_types = newDocumentTypes;
deedType.office = office; deedType.office = office;
await this.deedTypesService.create(deedType); await this.deedTypesService.create(deedType);
}; }
} }
public async duplicateOfficeRoles(officeRoles: OfficeRole[], office: Office){ public async duplicateOfficeRoles(officeRoles: OfficeRole[], office: Office) {
for(const officeRole of officeRoles) { for (const officeRole of officeRoles) {
officeRole.office = office; officeRole.office = office;
await this.officeRolesService.create(officeRole); await this.officeRolesService.create(officeRole);
}; }
} }
public async updateUsers() { public async updateUsers() {
const usersReq = await this.userService.getUsersToBeChecked(); const usersReq = await this.userService.getUsersToBeChecked();
const users = User.hydrateArray<User>(usersReq); const users = User.hydrateArray<User>(usersReq);

View File

@ -1,12 +1,14 @@
import { Customers, Prisma } from "@prisma/client"; import EmailBuilder from "@Common/emails/EmailBuilder";
import { Customers, Documents, Prisma } from "@prisma/client";
import CustomersRepository from "@Repositories/CustomersRepository"; import CustomersRepository from "@Repositories/CustomersRepository";
import BaseService from "@Services/BaseService"; import BaseService from "@Services/BaseService";
import { Customer } from "le-coffre-resources/dist/Notary"; import { Customer, DocumentReminder } from "le-coffre-resources/dist/Notary";
import { Service } from "typedi"; import { Service } from "typedi";
import DocumentsReminderService from "../DocumentsReminder/DocumentsReminder";
@Service() @Service()
export default class CustomersService extends BaseService { export default class CustomersService extends BaseService {
constructor(private customerRepository: CustomersRepository) { constructor(private customerRepository: CustomersRepository, private emailBuilder: EmailBuilder, private documentsReminderService : DocumentsReminderService) {
super(); super();
} }
@ -49,4 +51,17 @@ export default class CustomersService extends BaseService {
public async getByContact(contactUid: string): Promise<Customers | null> { public async getByContact(contactUid: string): Promise<Customers | null> {
return this.customerRepository.findOneByContact(contactUid); return this.customerRepository.findOneByContact(contactUid);
} }
public async sendDocumentsReminder(customer: Customer, documents: Documents[]): Promise<void> {
//Call email builder to send mail
const email = this.emailBuilder.sendReminder(customer, documents);
//Call DocumentsReminder service to create add the reminder in database
if (!email) return;
for (const document of documents) {
//Create document reminder
const documentReminder = new DocumentReminder();
documentReminder.document = document;
await this.documentsReminderService.create(documentReminder);
}
}
} }

View File

@ -0,0 +1,61 @@
import { DocumentsNotary, Prisma } from "@prisma/client";
import { Document, DocumentNotary } from "le-coffre-resources/dist/Notary";
import DocumentsNotaryRepository from "@Repositories/DocumentsNotaryRepository";
import BaseService from "@Services/BaseService";
import { Service } from "typedi";
@Service()
export default class DocumentsService extends BaseService {
constructor(private documentsNotaryRepository: DocumentsNotaryRepository) {
super();
}
/**
* @description : Get all documents
* @throws {Error} If documents cannot be get
*/
public async get(query: Prisma.DocumentsNotaryFindManyArgs) {
return this.documentsNotaryRepository.findMany(query);
}
/**
* @description : Create a new document
* @throws {Error} If document cannot be created
*/
public async create(document: DocumentNotary): Promise<DocumentsNotary> {
return this.documentsNotaryRepository.create(document);
}
/**
* @description : Delete a document
* @throws {Error} If document cannot be deleted
*/
public async delete(uid: string): Promise<DocumentsNotary> {
const documentEntity = await this.documentsNotaryRepository.findOneByUid(uid, { files: true });
if (!documentEntity) throw new Error("document not found");
const document = Document.hydrate<Document>(documentEntity, { strategy: "excludeAll" });
const isDocumentEmpty = document.files && !document!.files.find((file) => file.archived_at === null);
if (!isDocumentEmpty && document.document_status !== "REFUSED") {
throw new Error("Can't delete a document with file");
}
return this.documentsNotaryRepository.delete(uid);
}
/**
* @description : Get a document by uid
* @throws {Error} If document cannot be get by uid
*/
public async getByUid(uid: string, query?: Prisma.DocumentsNotaryInclude): Promise<DocumentsNotary | null> {
return this.documentsNotaryRepository.findOneByUid(uid, query);
}
/**
* @description : Get a document by uid
* @throws {Error} If document cannot be get by uid
*/
public async getByUidWithOffice(uid: string) {
return this.documentsNotaryRepository.findOneByUidWithOffice(uid);
}
}

View File

@ -0,0 +1,28 @@
import { DocumentsReminder, Prisma } from "@prisma/client";
import { DocumentReminder } from "le-coffre-resources/dist/Notary";
import BaseService from "@Services/BaseService";
import { Service } from "typedi";
import DocumentsReminderRepository from "@Repositories/DocumentsReminderRepository";
@Service()
export default class DocumentsReminderService extends BaseService {
constructor(private documentsReminderRepository: DocumentsReminderRepository) {
super();
}
/**
* @description : Get all documents
* @throws {Error} If documents cannot be get
*/
public async get(query: Prisma.DocumentsReminderFindManyArgs) {
return this.documentsReminderRepository.findMany(query);
}
/**
* @description : Create a new document
* @throws {Error} If document cannot be created
*/
public async create(document: DocumentReminder): Promise<DocumentsReminder> {
return this.documentsReminderRepository.create(document);
}
}