diff --git a/cron/archive.js b/cron/archive.js new file mode 100644 index 0000000..9d8b3e7 --- /dev/null +++ b/cron/archive.js @@ -0,0 +1,181 @@ +import mongoose from "mongoose"; + +const permitsModel = mongoose.model( + "permit", + new mongoose.Schema( + { + tenantId: { + type: String, + required: true, + }, + pid: { + type: String, + unique: true, + }, + permitNumber: String, + county: Object, + client: { + type: mongoose.Types.ObjectId, + ref: "organization", + }, + clientData: Object, + permitDate: Date, + stage: new mongoose.Schema( + { + pipeline: Array, + currentStage: Number, + }, + { _id: false } + ), + status: String, + assignedTo: { + type: mongoose.Types.ObjectId, + ref: "user", + }, + link: String, + address: Object, + recordType: String, + description: String, + applicationDetails: Object, + applicationInfo: Object, + applicationInfoTable: Object, + conditions: Array, + ownerDetails: String, + parcelInfo: Object, + paymentData: Object, + professionalsList: Array, + inspections: Object, + createdAt: Date, + createdBy: { + type: mongoose.Types.ObjectId, + ref: "user", + }, + newProcessingStatus: Array, + newPayment: Array, + newConditions: Array, + professionals: Object, + recordId: String, + relatedRecords: Object, + accelaStatus: String, + openDate: Date, + lastUpdateDate: Date, + statusUpdated: Date, + }, + { strict: false } + ) +); + +const processedModel = mongoose.model( + "processed", + new mongoose.Schema( + { + tenantId: { + type: String, + required: true, + }, + pid: { + type: String, + unique: true, + }, + permitNumber: String, + county: Object, + client: { + type: mongoose.Types.ObjectId, + ref: "organization", + }, + clientData: Object, + permitDate: Date, + stage: new mongoose.Schema( + { + pipeline: Array, + currentStage: Number, + }, + { _id: false } + ), + status: String, + assignedTo: { + type: mongoose.Types.ObjectId, + ref: "user", + }, + link: String, + address: Object, + recordType: String, + description: String, + applicationDetails: Object, + applicationInfo: Object, + applicationInfoTable: Object, + conditions: Array, + ownerDetails: String, + parcelInfo: Object, + paymentData: Object, + professionalsList: Array, + inspections: Object, + createdAt: Date, + createdBy: { + type: mongoose.Types.ObjectId, + ref: "user", + }, + newProcessingStatus: Array, + newPayment: Array, + newConditions: Array, + professionals: Object, + recordId: String, + relatedRecords: Object, + accelaStatus: String, + openDate: Date, + lastUpdateDate: Date, + statusUpdated: Date, + transferDate: Date, + }, + { strict: false } + ), + "processed" +); + +(async () => { + await mongoose.connect(process.env.DB_URI); + + const processedPermits = await permitsModel.find({ + status: { $in: ["Cancel", "Complete", "Issued", "Withdrawn"] }, + }); + + console.log(`Found ${processedPermits.length} to archive`); + + let count = 0; + for (const permit of processedPermits) { + await processedModel.create({ + tenantId: permit.tenantId, + pid: permit.pid, + permitNumber: permit.permitNumber, + county: permit.county, + client: permit.client, + clientData: permit.clientData, + status: permit.status, + assignedTo: permit.assignedTo, + link: permit.link, + address: permit.address, + recordType: permit.recordType, + description: permit.description, + createdAt: permit.createdAt, + createdBy: permit.createdBy, + newProcessingStatus: permit.newProcessingStatus, + newPayment: permit.newPayment, + newConditions: permit.newConditions, + professionals: permit.professionals, + recordId: permit.recordId, + relatedRecords: permit.relatedRecords, + accelaStatus: permit.accelaStatus, + openDate: permit.openDate, + lastUpdateDate: permit.lastUpdateDate, + statusUpdated: permit.statusUpdated, + transferDate: new Date(), + }); + + await permit.deleteOne(); + count++; + } + + console.log(`${count} permits archived`); + + await mongoose.connection.close(); +})().catch((err) => console.log(err)); diff --git a/src/processed/processed.route.ts b/src/processed/processed.route.ts new file mode 100644 index 0000000..10c6968 --- /dev/null +++ b/src/processed/processed.route.ts @@ -0,0 +1,27 @@ +import { FastifyInstance, FastifyReply, FastifyRequest } from "fastify"; +import { PageQueryParams } from "../pagination"; +import { listProcessedPermits } from "./processed.service"; +import { $processed } from "./processed.schema"; + +export async function processedRoutes(fastify: FastifyInstance) { + fastify.get( + "/", + { + schema: { + querystring: $processed("pageQueryParams"), + }, + config: { requiredClaims: ["permit:read"] }, + preHandler: [fastify.authorize], + }, + async (req: FastifyRequest, res: FastifyReply) => { + const params = req.query as PageQueryParams; + + try { + const permits = await listProcessedPermits(params, req.user.tenantId); + return res.code(200).send(permits); + } catch (err) { + return err; + } + } + ); +} diff --git a/src/processed/processed.schema.ts b/src/processed/processed.schema.ts new file mode 100644 index 0000000..0d2a7cc --- /dev/null +++ b/src/processed/processed.schema.ts @@ -0,0 +1,80 @@ +import mongoose from "mongoose"; +import { pageQueryParams } from "../pagination"; +import { buildJsonSchemas } from "fastify-zod"; + +const processedSchema = new mongoose.Schema({ + tenantId: { + type: String, + required: true, + }, + pid: { + type: String, + unique: true, + }, + permitNumber: String, + county: Object, + client: { + type: mongoose.Types.ObjectId, + ref: "organization", + }, + clientData: Object, + permitDate: Date, + stage: new mongoose.Schema( + { + pipeline: Array, + currentStage: Number, + }, + { _id: false } + ), + status: String, + assignedTo: { + type: mongoose.Types.ObjectId, + ref: "user", + }, + link: String, + address: Object, + recordType: String, + description: String, + applicationDetails: Object, + applicationInfo: Object, + applicationInfoTable: Object, + conditions: Array, + ownerDetails: String, + parcelInfo: Object, + paymentData: Object, + professionalsList: Array, + inspections: Object, + createdAt: Date, + createdBy: { + type: mongoose.Types.ObjectId, + ref: "user", + }, + newProcessingStatus: Array, + newPayment: Array, + newConditions: Array, + professionals: Object, + recordId: String, + relatedRecords: Object, + accelaStatus: String, + openDate: Date, + lastUpdateDate: Date, + statusUpdated: Date, + transferDate: Date, +}).index({ tenantId: 1, permitNumber: 1 }, { unique: true }); + +export const processedFields = Object.keys(processedSchema.paths).filter( + (path) => path !== "__v" +); + +export const processedModel = mongoose.model( + "processed", + processedSchema, + "processed" +); + +export const { schemas: processedSchemas, $ref: $processed } = buildJsonSchemas( + { + pageQueryParams, + }, + { $id: "processed" } +); diff --git a/src/processed/processed.service.ts b/src/processed/processed.service.ts new file mode 100644 index 0000000..d9d04f7 --- /dev/null +++ b/src/processed/processed.service.ts @@ -0,0 +1,115 @@ +import { getFilterObject, getSortObject, PageQueryParams } from "../pagination"; +import { processedFields, processedModel } from "./processed.schema"; + +export async function listProcessedPermits( + params: PageQueryParams, + tenantId: string +) { + const page = params.page || 1; + const pageSize = params.pageSize || 10; + const sortObj = getSortObject(params, processedFields); + const filterObj = getFilterObject(params, processedFields); + + const pipeline: any = [ + { + $match: { $and: [{ tenantId: tenantId }, ...filterObj] }, + }, + ]; + + if (params.searchToken && params.searchToken != "") { + const regex = new RegExp(params.searchToken, "i"); + pipeline.push({ + $match: { + $or: [ + { permitNumber: { $regex: regex } }, + { link: { $regex: regex } }, + { "address.full_address": { $regex: regex } }, + ], + }, + }); + } + + pipeline.push( + ...[ + { + $lookup: { + from: "users", + localField: "assignedTo", + foreignField: "_id", + as: "assignedRec", + }, + }, + { + $project: { + _id: 1, + pid: 1, + permitNumber: 1, + permitDate: 1, + stage: 1, + status: 1, + link: 1, + address: 1, + recordType: 1, + description: 1, + applicationDetails: 1, + applicationInfo: 1, + applicationInfoTable: 1, + conditions: 1, + ownerDetails: 1, + parcelInfo: 1, + paymentData: 1, + inspections: 1, + newProcessingStatus: 1, + newPayment: 1, + newConditions: 1, + professionals: 1, + recordid: 1, + relatedRecords: 1, + accelaStatus: 1, + createdAt: 1, + county: 1, + client: 1, + clientData: 1, + openDate: 1, + lastUpdateDate: 1, + statusUpdated: 1, + assignedTo: { + $let: { + vars: { assigned: { $arrayElemAt: ["$assignedRec", 0] } }, + in: { + _id: "$$assigned._id", + pid: "$$assigned.pid", + name: "$$assigned.name", + avatar: "$$assigned.avatar", + }, + }, + }, + }, + }, + { + $facet: { + metadata: [{ $count: "count" }], + data: [ + { $skip: (page - 1) * pageSize }, + { $limit: pageSize }, + { $sort: sortObj }, + ], + }, + }, + ] + ); + + const permitsList = await processedModel.aggregate(pipeline); + + if (permitsList[0].data.length === 0) + return { permits: [], metadata: { count: 0, page, pageSize } }; + + return { + permits: permitsList[0]?.data, + metadata: { + count: permitsList[0].metadata[0].count, + page, + pageSize, + }, + }; +} diff --git a/src/routes.ts b/src/routes.ts index 5e39bae..5063dac 100644 --- a/src/routes.ts +++ b/src/routes.ts @@ -12,6 +12,7 @@ import { notificationRoutes } from "./notification/notification.route"; import { configRoutes } from "./config/config.route"; import { mailProxyRoutes } from "./mailProxy/mailProxy.route"; import { viewRoutes } from "./view/view.route"; +import { processedRoutes } from "./processed/processed.route"; export default async function routes(fastify: FastifyInstance) { fastify.addHook("preHandler", authHandler); @@ -26,5 +27,6 @@ export default async function routes(fastify: FastifyInstance) { fastify.register(mailProxyRoutes, { prefix: "/proxy" }); fastify.register(configRoutes, { prefix: "/config" }); fastify.register(viewRoutes, { prefix: "/views" }); + fastify.register(processedRoutes, { prefix: "/processed" }); fastify.register(realTimeRoutes); } diff --git a/src/server.ts b/src/server.ts index ab18017..5799809 100644 --- a/src/server.ts +++ b/src/server.ts @@ -20,6 +20,7 @@ import { webAuthnRoutes } from "./webauthn/webauthn.route"; import { configSchemas } from "./config/config.schema"; import { mailSchemas } from "./mailProxy/mailProxy.schema"; import { viewSchemas } from "./view/view.schema"; +import { processedSchemas } from "./processed/processed.schema"; const app = fastify({ logger: true, trustProxy: true }); @@ -32,7 +33,7 @@ oauth(app); app.decorate("authorize", authorize); app.setErrorHandler(errorHandler); app.register(cors, { - origin: [process.env.UI_DOMAIN || ""], + origin: [process.env.UI_DOMAIN || "", "http://localhost:8000"], credentials: true, }); app.register(multipart, { limits: { fileSize: 50000000 } }); @@ -53,6 +54,7 @@ for (const schema of [ ...configSchemas, ...mailSchemas, ...viewSchemas, + ...processedSchemas, ]) { app.addSchema(schema); }