diff --git a/package-lock.json b/package-lock.json index 1a5914d..cccda29 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "ff-admin-server", - "version": "1.7.6", + "version": "1.8.0-beta1", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "ff-admin-server", - "version": "1.7.6", + "version": "1.8.0-beta1", "license": "AGPL-3.0-only", "dependencies": { "cors": "^2.8.5", @@ -43,6 +43,7 @@ "validator": "^13.15.15" }, "devDependencies": { + "@socket.io/admin-ui": "^0.5.1", "@types/cors": "^2.8.19", "@types/express": "^5.0.3", "@types/ip": "^1.1.3", @@ -764,6 +765,39 @@ "node": ">=18" } }, + "node_modules/@socket.io/admin-ui": { + "version": "0.5.1", + "resolved": "https://registry.npmjs.org/@socket.io/admin-ui/-/admin-ui-0.5.1.tgz", + "integrity": "sha512-1dlGL2FGm6T+uL1e6iDvbo2eCINwvW7iVbjIblwh5kPPRM1SP8lmZrbFZf4QNJ/cqQ+JLcx49eXGM9WAB4TK7w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/bcryptjs": "^2.4.2", + "bcryptjs": "^2.4.3", + "debug": "~4.3.1" + }, + "peerDependencies": { + "socket.io": ">=3.1.0" + } + }, + "node_modules/@socket.io/admin-ui/node_modules/debug": { + "version": "4.3.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.7.tgz", + "integrity": "sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, "node_modules/@socket.io/component-emitter": { "version": "3.1.2", "resolved": "https://registry.npmjs.org/@socket.io/component-emitter/-/component-emitter-3.1.2.tgz", @@ -821,6 +855,13 @@ "devOptional": true, "license": "MIT" }, + "node_modules/@types/bcryptjs": { + "version": "2.4.6", + "resolved": "https://registry.npmjs.org/@types/bcryptjs/-/bcryptjs-2.4.6.tgz", + "integrity": "sha512-9xlo6R2qDs5uixm0bcIqCeMCE6HiQsIyel9KQySStiyqNl2tnj2mP3DX1Nf56MD6KMenNNlBBsy3LJ7gUEQPXQ==", + "dev": true, + "license": "MIT" + }, "node_modules/@types/body-parser": { "version": "1.19.5", "resolved": "https://registry.npmjs.org/@types/body-parser/-/body-parser-1.19.5.tgz", @@ -1421,6 +1462,13 @@ "node": ">=10.0.0" } }, + "node_modules/bcryptjs": { + "version": "2.4.3", + "resolved": "https://registry.npmjs.org/bcryptjs/-/bcryptjs-2.4.3.tgz", + "integrity": "sha512-V/Hy/X9Vt7f3BbPJEi8BdVFMByHi+jNXrYkW3huaybV/kQ0KJg0Y6PkEMbn+zeT+i+SiKZ/HMqJGIIt4LZDqNQ==", + "dev": true, + "license": "MIT" + }, "node_modules/bindings": { "version": "1.5.0", "resolved": "https://registry.npmjs.org/bindings/-/bindings-1.5.0.tgz", diff --git a/package.json b/package.json index f1dd2ee..078c151 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "ff-admin-server", - "version": "1.7.6", + "version": "1.8.0-beta1", "description": "Feuerwehr/Verein Mitgliederverwaltung Server", "main": "dist/index.js", "scripts": { @@ -59,6 +59,7 @@ "validator": "^13.15.15" }, "devDependencies": { + "@socket.io/admin-ui": "^0.5.1", "@types/cors": "^2.8.19", "@types/express": "^5.0.3", "@types/ip": "^1.1.3", diff --git a/src/command/unit/damageReportCommand.ts b/src/command/unit/damageReportCommand.ts new file mode 100644 index 0000000..aa47edf --- /dev/null +++ b/src/command/unit/damageReportCommand.ts @@ -0,0 +1,27 @@ +export interface CreateDamageReportCommand { + title: string; + description: string; + location: string; + noteByReporter: string; + reportedBy: string; + images: string[]; + affectedId?: string; + affected?: "equipment" | "vehicle" | "wearable"; +} + +export interface UpdateDamageReportCommand { + id: string; + status: string; + noteByWorker: string; + user: { id: string; firstname: string; lastname: string }; + done: boolean; +} + +export interface UpdateDamageReportRelatedRepairCommand { + id: string; + repairId: string; +} + +export interface DeleteDamageReportCommand { + id: string; +} diff --git a/src/command/unit/damageReportCommandHandler.ts b/src/command/unit/damageReportCommandHandler.ts new file mode 100644 index 0000000..86771d5 --- /dev/null +++ b/src/command/unit/damageReportCommandHandler.ts @@ -0,0 +1,163 @@ +import { EntityManager, In, UpdateResult } from "typeorm"; +import { dataSource } from "../../data-source"; +import { damageReport } from "../../entity/unit/damageReport"; +import DatabaseActionException from "../../exceptions/databaseActionException"; +import { + CreateDamageReportCommand, + UpdateDamageReportCommand, + DeleteDamageReportCommand, + UpdateDamageReportRelatedRepairCommand, +} from "./damageReportCommand"; +import DamageReportService from "../../service/unit/damageReportService"; + +export default abstract class DamageReportCommandHandler { + /** + * @description create damageReport + * @param {CreateDamageReportCommand} createDamageReport + * @returns {Promise} + */ + static async create(createDamageReport: CreateDamageReportCommand): Promise { + return await dataSource + .createQueryBuilder() + .insert() + .into(damageReport) + .values({ + status: "eingereicht", + title: createDamageReport.title, + description: createDamageReport.description, + location: createDamageReport.location, + noteByReporter: createDamageReport.noteByReporter, + reportedBy: createDamageReport.reportedBy, + images: createDamageReport.images, + equipmentId: createDamageReport.affected == "equipment" ? createDamageReport.affectedId : null, + vehicleId: createDamageReport.affected == "vehicle" ? createDamageReport.affectedId : null, + wearableId: createDamageReport.affected == "wearable" ? createDamageReport.affectedId : null, + }) + .execute() + .then((result) => { + return result.identifiers[0].id; + }) + .catch((err) => { + throw new DatabaseActionException("CREATE", "damageReport", err); + }); + } + + /** + * @description update damageReport + * @param {UpdateDamageReportCommand} updateDamageReport + * @returns {Promise} + */ + static async update(updateDamageReport: UpdateDamageReportCommand): Promise { + return await dataSource + .createQueryBuilder() + .update(damageReport) + .set({ + status: updateDamageReport.status, + noteByWorker: updateDamageReport.noteByWorker, + closedAt: updateDamageReport.done ? new Date() : null, + closedById: updateDamageReport.done ? updateDamageReport.user.id : null, + closedByString: updateDamageReport.done + ? `${updateDamageReport.user.firstname} ${updateDamageReport.user.lastname}` + : null, + }) + .where("id = :id", { id: updateDamageReport.id }) + .execute() + .then(() => {}) + .catch((err) => { + throw new DatabaseActionException("UPDATE", "damageReport", err); + }); + } + + /** + * @description update damageReport related maintenance + * @param {UpdateDamageReportCommand} updateDamageReport + * @returns {Promise} + */ + static async updateRelatedMaintenance(updateDamageReport: UpdateDamageReportRelatedRepairCommand): Promise { + return await dataSource + .createQueryBuilder() + .update(damageReport) + .set({ + repairId: updateDamageReport.repairId, + }) + .where("id = :id", { id: updateDamageReport.id }) + .execute() + .then(() => {}) + .catch((err) => { + throw new DatabaseActionException("UPDATE", "damageReport->maintenance", err); + }); + } + + /** + * @description update damageReport related maintenance + * @returns {Promise} + */ + static async updateRelatedMaintenanceMulti(repairId: string, reports: Array): Promise { + let [related] = await DamageReportService.getAllForRepair(repairId, { noLimit: true }); + return await dataSource + .transaction(async (manager) => { + let added = reports.filter((id) => !related.some((r) => r.id === id)); + let removed = related.map((r) => r.id).filter((id) => !reports.includes(id)); + + await manager + .createQueryBuilder() + .update(damageReport) + .set({ + repairId: repairId, + }) + .where({ id: In(added) }) + .execute(); + + await manager + .createQueryBuilder() + .update(damageReport) + .set({ + repairId: null, + }) + .where({ id: In(removed) }) + .execute(); + }) + .then(() => {}) + .catch((err) => { + throw new DatabaseActionException("UPDATE", "damageReport->maintenance", err); + }); + } + + /** + * @description update damageReport related maintenance in transaction + * @param {UpdateDamageReportCommand} updateDamageReport + * @returns {Promise} + */ + static async updateRelatedMaintenanceTransaction( + manager: EntityManager, + updateDamageReport: UpdateDamageReportRelatedRepairCommand + ): Promise { + return await manager + .createQueryBuilder() + .update(damageReport) + .set({ + repairId: updateDamageReport.repairId, + }) + .where("id = :id", { id: updateDamageReport.id }) + .execute(); + } + + /** + * @description delete damageReport + * @param {DeleteDamageReportCommand} deleteDamageReport + * @returns {Promise} + */ + static async delete(deleteDamageReport: DeleteDamageReportCommand): Promise { + // TODO: remove related images + return await dataSource + .createQueryBuilder() + .delete() + .from(damageReport) + .where("id = :id", { id: deleteDamageReport.id }) + .execute() + .then(() => {}) + .catch((err) => { + throw new DatabaseActionException("DELETE", "damageReport", err); + }); + } +} diff --git a/src/command/unit/equipment/equipmentCommand.ts b/src/command/unit/equipment/equipmentCommand.ts new file mode 100644 index 0000000..500d4c2 --- /dev/null +++ b/src/command/unit/equipment/equipmentCommand.ts @@ -0,0 +1,20 @@ +export interface CreateEquipmentCommand { + code?: string; + name: string; + location: string; + commissioned: Date; + equipmentTypeId: string; +} + +export interface UpdateEquipmentCommand { + id: string; + code?: string; + name: string; + location: string; + commissioned: Date; + decommissioned?: Date; +} + +export interface DeleteEquipmentCommand { + id: string; +} diff --git a/src/command/unit/equipment/equipmentCommandHandler.ts b/src/command/unit/equipment/equipmentCommandHandler.ts new file mode 100644 index 0000000..0aef534 --- /dev/null +++ b/src/command/unit/equipment/equipmentCommandHandler.ts @@ -0,0 +1,74 @@ +import { dataSource } from "../../../data-source"; +import { equipment } from "../../../entity/unit/equipment/equipment"; +import DatabaseActionException from "../../../exceptions/databaseActionException"; +import { CreateEquipmentCommand, UpdateEquipmentCommand, DeleteEquipmentCommand } from "./equipmentCommand"; + +export default abstract class EquipmentCommandHandler { + /** + * @description create equipment + * @param {CreateEquipmentCommand} createEquipment + * @returns {Promise} + */ + static async create(createEquipment: CreateEquipmentCommand): Promise { + return await dataSource + .createQueryBuilder() + .insert() + .into(equipment) + .values({ + code: createEquipment.code, + name: createEquipment.name, + location: createEquipment.location, + commissioned: createEquipment.commissioned, + equipmentTypeId: createEquipment.equipmentTypeId, + }) + .execute() + .then((result) => { + return result.identifiers[0].id; + }) + .catch((err) => { + throw new DatabaseActionException("CREATE", "equipment", err); + }); + } + + /** + * @description update equipment + * @param {UpdateEquipmentCommand} updateEquipment + * @returns {Promise} + */ + static async update(updateEquipment: UpdateEquipmentCommand): Promise { + return await dataSource + .createQueryBuilder() + .update(equipment) + .set({ + code: updateEquipment.code, + name: updateEquipment.name, + location: updateEquipment.location, + commissioned: updateEquipment.commissioned, + decommissioned: updateEquipment.decommissioned, + }) + .where("id = :id", { id: updateEquipment.id }) + .execute() + .then(() => {}) + .catch((err) => { + throw new DatabaseActionException("UPDATE", "equipment", err); + }); + } + + /** + * @description delete equipment + * @param {DeleteEquipmentCommand} deleteEquipment + * @returns {Promise} + */ + static async delete(deleteEquipment: DeleteEquipmentCommand): Promise { + return await dataSource + .createQueryBuilder() + .delete() + .from(equipment) + .where("id = :id", { id: deleteEquipment.id }) + .execute() + .then(() => {}) + .catch((err) => { + throw new DatabaseActionException("DELETE", "equipment", err); + }); + } +} diff --git a/src/command/unit/equipment/equipmentTypeCommand.ts b/src/command/unit/equipment/equipmentTypeCommand.ts new file mode 100644 index 0000000..7d5c2f7 --- /dev/null +++ b/src/command/unit/equipment/equipmentTypeCommand.ts @@ -0,0 +1,14 @@ +export interface CreateEquipmentTypeCommand { + type: string; + description: string; +} + +export interface UpdateEquipmentTypeCommand { + id: string; + type: string; + description: string; +} + +export interface DeleteEquipmentTypeCommand { + id: string; +} diff --git a/src/command/unit/equipment/equipmentTypeCommandHandler.ts b/src/command/unit/equipment/equipmentTypeCommandHandler.ts new file mode 100644 index 0000000..47caec3 --- /dev/null +++ b/src/command/unit/equipment/equipmentTypeCommandHandler.ts @@ -0,0 +1,72 @@ +import { dataSource } from "../../../data-source"; +import { equipmentType } from "../../../entity/unit/equipment/equipmentType"; +import DatabaseActionException from "../../../exceptions/databaseActionException"; +import { + CreateEquipmentTypeCommand, + UpdateEquipmentTypeCommand, + DeleteEquipmentTypeCommand, +} from "./equipmentTypeCommand"; + +export default abstract class EquipmentTypeCommandHandler { + /** + * @description create equipmentType + * @param {CreateEquipmentTypeCommand} createEquipmentType + * @returns {Promise} + */ + static async create(createEquipmentType: CreateEquipmentTypeCommand): Promise { + return await dataSource + .createQueryBuilder() + .insert() + .into(equipmentType) + .values({ + type: createEquipmentType.type, + description: createEquipmentType.description, + }) + .execute() + .then((result) => { + return result.identifiers[0].id; + }) + .catch((err) => { + throw new DatabaseActionException("CREATE", "equipmentType", err); + }); + } + + /** + * @description update equipmentType + * @param {UpdateEquipmentTypeCommand} updateEquipmentType + * @returns {Promise} + */ + static async update(updateEquipmentType: UpdateEquipmentTypeCommand): Promise { + return await dataSource + .createQueryBuilder() + .update(equipmentType) + .set({ + type: updateEquipmentType.type, + description: updateEquipmentType.description, + }) + .where("id = :id", { id: updateEquipmentType.id }) + .execute() + .then(() => {}) + .catch((err) => { + throw new DatabaseActionException("UPDATE", "equipmentType", err); + }); + } + + /** + * @description delete equipmentType + * @param {DeleteEquipmentTypeCommand} deleteEquipmentType + * @returns {Promise} + */ + static async delete(deleteEquipmentType: DeleteEquipmentTypeCommand): Promise { + return await dataSource + .createQueryBuilder() + .delete() + .from(equipmentType) + .where("id = :id", { id: deleteEquipmentType.id }) + .execute() + .then(() => {}) + .catch((err) => { + throw new DatabaseActionException("DELETE", "equipmentType", err); + }); + } +} diff --git a/src/command/unit/inspection/inspectionCommand.ts b/src/command/unit/inspection/inspectionCommand.ts new file mode 100644 index 0000000..16deedd --- /dev/null +++ b/src/command/unit/inspection/inspectionCommand.ts @@ -0,0 +1,22 @@ +export interface CreateInspectionCommand { + context: string; + nextInspection?: Date; + inspectionPlanId: string; + relatedId: string; + assigned: "vehicle" | "equipment" | "wearable"; +} + +export interface UpdateInspectionCommand { + id: string; + context: string; + nextInspection?: Date; +} + +export interface FinishInspectionCommand { + id: string; + user: { id: string; firstname: string; lastname: string }; +} + +export interface DeleteInspectionCommand { + id: string; +} diff --git a/src/command/unit/inspection/inspectionCommandHandler.ts b/src/command/unit/inspection/inspectionCommandHandler.ts new file mode 100644 index 0000000..8a17af2 --- /dev/null +++ b/src/command/unit/inspection/inspectionCommandHandler.ts @@ -0,0 +1,156 @@ +import { IsNull, Not } from "typeorm"; +import { dataSource } from "../../../data-source"; +import { inspection } from "../../../entity/unit/inspection/inspection"; +import DatabaseActionException from "../../../exceptions/databaseActionException"; +import InspectionService from "../../../service/unit/inspection/inspectionService"; +import InspectionVersionedPlanService from "../../../service/unit/inspection/inspectionVersionedPlanService"; +import { + CreateInspectionCommand, + UpdateInspectionCommand, + DeleteInspectionCommand, + FinishInspectionCommand, +} from "./inspectionCommand"; + +export default abstract class InspectionCommandHandler { + /** + * @description create inspection + * @param {CreateInspectionCommand} createInspection + * @returns {Promise} + */ + static async create(createInspection: CreateInspectionCommand): Promise { + let latestVersionedPlan = await InspectionVersionedPlanService.getLatestForInspectionPlan( + createInspection.inspectionPlanId + ); + let insertId = ""; + return await dataSource + .transaction(async (manager) => { + await manager + .createQueryBuilder() + .update(inspection) + .set({ + hasNewer: true, + }) + .where({ + inspectionPlanId: createInspection.inspectionPlanId, + equipmentId: createInspection.assigned == "equipment" ? createInspection.relatedId : IsNull(), + vehicleId: createInspection.assigned == "vehicle" ? createInspection.relatedId : IsNull(), + wearableId: createInspection.assigned == "wearable" ? createInspection.relatedId : IsNull(), + }) + .execute(); + + await manager + .createQueryBuilder() + .insert() + .into(inspection) + .values({ + context: createInspection.context, + nextInspection: createInspection.nextInspection, + inspectionPlanId: createInspection.inspectionPlanId, + inspectionVersionedPlanId: latestVersionedPlan.id, + equipmentId: createInspection.assigned == "equipment" ? createInspection.relatedId : null, + vehicleId: createInspection.assigned == "vehicle" ? createInspection.relatedId : null, + wearableId: createInspection.assigned == "wearable" ? createInspection.relatedId : null, + }) + .execute() + .then((result) => { + insertId = result.identifiers[0].id; + }); + }) + .then(() => { + return insertId; + }) + .catch((err) => { + throw new DatabaseActionException("CREATE", "inspection", err); + }); + } + + /** + * @description update inspection + * @param {UpdateInspectionCommand} updateInspection + * @returns {Promise} + */ + static async update(updateInspection: UpdateInspectionCommand): Promise { + return await dataSource + .createQueryBuilder() + .update(inspection) + .set({ + context: updateInspection.context, + nextInspection: updateInspection.nextInspection, + }) + .where("id = :id", { id: updateInspection.id }) + .execute() + .then(() => {}) + .catch((err) => { + throw new DatabaseActionException("UPDATE", "inspection", err); + }); + } + + /** + * @description finish inspection + * @param {FinishInspectionCommand} finishInspection + * @returns {Promise} + */ + static async finish(finishInspection: FinishInspectionCommand): Promise { + return await dataSource + .createQueryBuilder() + .update(inspection) + .set({ + finishedAt: new Date(), + finishedById: finishInspection.user.id, + finishedByString: `${finishInspection.user.firstname} ${finishInspection.user.lastname}`, + }) + .where("id = :id", { id: finishInspection.id }) + .execute() + .then(() => {}) + .catch((err) => { + throw new DatabaseActionException("FINISH", "inspection", err); + }); + } + + /** + * @description delete inspection + * @param {DeleteInspectionCommand} deleteInspection + * @returns {Promise} + */ + static async delete(deleteInspection: DeleteInspectionCommand): Promise { + let deleteInspectionData = await InspectionService.getById(deleteInspection.id); + return await dataSource + .transaction(async (manager) => { + let latestInspection = await manager + .createQueryBuilder() + .from(inspection, "sub") + .where({ + inspectionPlanId: deleteInspectionData.inspectionPlanId, + inspectionVersionedPlanId: deleteInspectionData.inspectionVersionedPlanId, + equipmentId: deleteInspectionData.equipmentId ?? IsNull(), + vehicleId: deleteInspectionData.vehicleId ?? IsNull(), + wearableId: deleteInspectionData.wearableId ?? IsNull(), + }) + .andWhere({ id: Not(deleteInspection.id) }) + .orderBy("sub.createdAt", "DESC") + .limit(1) + .getOne(); + + if (latestInspection) + await manager + .createQueryBuilder() + .update(inspection) + .set({ + hasNewer: false, + }) + .where({ id: latestInspection.id }) + .execute(); + + await manager + .createQueryBuilder() + .delete() + .from(inspection) + .where("id = :id", { id: deleteInspection.id }) + .execute(); + }) + .then(() => {}) + .catch((err) => { + throw new DatabaseActionException("DELETE", "inspection", err); + }); + } +} diff --git a/src/command/unit/inspection/inspectionPlanCommand.ts b/src/command/unit/inspection/inspectionPlanCommand.ts new file mode 100644 index 0000000..58593ae --- /dev/null +++ b/src/command/unit/inspection/inspectionPlanCommand.ts @@ -0,0 +1,20 @@ +import { PlanTimeDefinition } from "../../../viewmodel/admin/unit/inspection/inspectionPlan.models"; + +export interface CreateInspectionPlanCommand { + title: string; + inspectionInterval: PlanTimeDefinition; + remindTime: PlanTimeDefinition; + relatedId: string; + assigned: "vehicle" | "equipment" | "wearable"; +} + +export interface UpdateInspectionPlanCommand { + id: string; + title: string; + inspectionInterval: PlanTimeDefinition; + remindTime?: PlanTimeDefinition; +} + +export interface DeleteInspectionPlanCommand { + id: string; +} diff --git a/src/command/unit/inspection/inspectionPlanCommandHandler.ts b/src/command/unit/inspection/inspectionPlanCommandHandler.ts new file mode 100644 index 0000000..e140f64 --- /dev/null +++ b/src/command/unit/inspection/inspectionPlanCommandHandler.ts @@ -0,0 +1,77 @@ +import { dataSource } from "../../../data-source"; +import { inspectionPlan } from "../../../entity/unit/inspection/inspectionPlan"; +import DatabaseActionException from "../../../exceptions/databaseActionException"; +import { + CreateInspectionPlanCommand, + UpdateInspectionPlanCommand, + DeleteInspectionPlanCommand, +} from "./inspectionPlanCommand"; + +export default abstract class InspectionPlanCommandHandler { + /** + * @description create inspectionPlan + * @param {CreateInspectionPlanCommand} createInspectionPlan + * @returns {Promise} + */ + static async create(createInspectionPlan: CreateInspectionPlanCommand): Promise { + return await dataSource + .createQueryBuilder() + .insert() + .into(inspectionPlan) + .values({ + title: createInspectionPlan.title, + inspectionInterval: createInspectionPlan.inspectionInterval, + remindTime: createInspectionPlan.remindTime, + equipmentTypeId: createInspectionPlan.assigned == "equipment" ? createInspectionPlan.relatedId : null, + vehicleTypeId: createInspectionPlan.assigned == "vehicle" ? createInspectionPlan.relatedId : null, + wearableTypeId: createInspectionPlan.assigned == "wearable" ? createInspectionPlan.relatedId : null, + }) + .execute() + .then((result) => { + return result.identifiers[0].id; + }) + .catch((err) => { + throw new DatabaseActionException("CREATE", "inspectionPlan", err); + }); + } + + /** + * @description update inspectionPlan + * @param {UpdateInspectionPlanCommand} updateInspectionPlan + * @returns {Promise} + */ + static async update(updateInspectionPlan: UpdateInspectionPlanCommand): Promise { + return await dataSource + .createQueryBuilder() + .update(inspectionPlan) + .set({ + title: updateInspectionPlan.title, + inspectionInterval: updateInspectionPlan.inspectionInterval, + remindTime: updateInspectionPlan.remindTime, + }) + .where("id = :id", { id: updateInspectionPlan.id }) + .execute() + .then(() => {}) + .catch((err) => { + throw new DatabaseActionException("UPDATE", "inspectionPlan", err); + }); + } + + /** + * @description delete inspectionPlan + * @param {DeleteInspectionPlanCommand} deleteInspectionPlan + * @returns {Promise} + */ + static async delete(deleteInspectionPlan: DeleteInspectionPlanCommand): Promise { + return await dataSource + .createQueryBuilder() + .delete() + .from(inspectionPlan) + .where("id = :id", { id: deleteInspectionPlan.id }) + .execute() + .then(() => {}) + .catch((err) => { + throw new DatabaseActionException("DELETE", "inspectionPlan", err); + }); + } +} diff --git a/src/command/unit/inspection/inspectionPointCommand.ts b/src/command/unit/inspection/inspectionPointCommand.ts new file mode 100644 index 0000000..8163bae --- /dev/null +++ b/src/command/unit/inspection/inspectionPointCommand.ts @@ -0,0 +1,13 @@ +import { InspectionPointEnum } from "../../../enums/inspectionEnum"; + +export interface CreateInspectionPointCommand { + id?: string; + title: string; + description: string; + type: InspectionPointEnum; + min?: number; + max?: number; + others?: string; + sort: number; + versionedPointId?: string; +} diff --git a/src/command/unit/inspection/inspectionPointCommandHandler.ts b/src/command/unit/inspection/inspectionPointCommandHandler.ts new file mode 100644 index 0000000..a704f04 --- /dev/null +++ b/src/command/unit/inspection/inspectionPointCommandHandler.ts @@ -0,0 +1,74 @@ +import { dataSource } from "../../../data-source"; +import { inspectionPoint } from "../../../entity/unit/inspection/inspectionPoint"; +import DatabaseActionException from "../../../exceptions/databaseActionException"; +import InspectionPointService from "../../../service/unit/inspection/inspectionPointService"; +import { CreateInspectionPointCommand } from "./inspectionPointCommand"; + +export default abstract class InspectionPointCommandHandler { + /** + * @description create inspectionPoint + * @param {CreateInspectionPointCommand} createInspectionPoint + * @returns {Promise} + */ + static async create(createInspectionPoint: CreateInspectionPointCommand): Promise { + return await dataSource + .createQueryBuilder() + .insert() + .into(inspectionPoint) + .values({ + title: createInspectionPoint.title, + description: createInspectionPoint.description, + type: createInspectionPoint.type, + min: createInspectionPoint.min, + max: createInspectionPoint.max, + sort: createInspectionPoint.sort, + versionedPlanId: createInspectionPoint.versionedPointId, + }) + .execute() + .then((result) => { + return result.identifiers[0].id; + }) + .catch((err) => { + throw new DatabaseActionException("CREATE", "inspectionPoint", err); + }); + } + + /** + * @description sync points + * @param {string} versionedPlanId + * @param {Array} sync + * @returns {Promise} + */ + static async sync(versionedPlanId: string, sync: Array): Promise { + let points = await InspectionPointService.getAllForVersionedPlan(versionedPlanId); + await dataSource + .transaction(async (manager) => { + let remove = points.filter((r) => !sync.some((cp) => cp.id == r.id)); + await manager + .createQueryBuilder() + .insert() + .into(inspectionPoint) + .values( + sync.map((s) => ({ + ...s, + id: points.some((p) => p.id == s.id) ? s.id : undefined, + versionedPlanId, + })) + ) + .orUpdate(["title", "description", "min", "max", "others", "sort"], ["id"]) + .execute(); + + if (remove.length != 0) + await manager + .createQueryBuilder() + .delete() + .from(inspectionPoint) + .where("id IN (:...ids)", { ids: remove.map((r) => r.id) }) + .andWhere({ versionedPlanId }) + .execute(); + }) + .catch((err) => { + throw new DatabaseActionException("SYNC", "inspectionPoint", err); + }); + } +} diff --git a/src/command/unit/inspection/inspectionPointResultCommand.ts b/src/command/unit/inspection/inspectionPointResultCommand.ts new file mode 100644 index 0000000..c1893d3 --- /dev/null +++ b/src/command/unit/inspection/inspectionPointResultCommand.ts @@ -0,0 +1,5 @@ +export interface CreateOrUpdateInspectionPointResultCommand { + inspectionId: string; + inspectionPointId: string; + value: string; +} diff --git a/src/command/unit/inspection/inspectionPointResultCommandHandler.ts b/src/command/unit/inspection/inspectionPointResultCommandHandler.ts new file mode 100644 index 0000000..8ffeb6a --- /dev/null +++ b/src/command/unit/inspection/inspectionPointResultCommandHandler.ts @@ -0,0 +1,54 @@ +import { dataSource } from "../../../data-source"; +import { inspectionPointResult } from "../../../entity/unit/inspection/inspectionPointResult"; +import DatabaseActionException from "../../../exceptions/databaseActionException"; +import { CreateOrUpdateInspectionPointResultCommand } from "./inspectionPointResultCommand"; + +export default abstract class InspectionPointResultCommandHandler { + /** + * @description create inspectionPointResult + * @param {CreateOrUpdateInspectionPointResultCommand} createInspectionPointResult + * @returns {Promise} + */ + static async createOrUpdate( + createInspectionPointResult: CreateOrUpdateInspectionPointResultCommand + ): Promise { + return await dataSource + .createQueryBuilder() + .insert() + .into(inspectionPointResult) + .values({ + inspectionId: createInspectionPointResult.inspectionId, + inspectionPointId: createInspectionPointResult.inspectionPointId, + value: createInspectionPointResult.value, + }) + .orUpdate(["value"], ["inspectionId", "inspectionPointId"]) + .execute() + .then((result) => { + return result.identifiers[0].id; + }) + .catch((err) => { + throw new DatabaseActionException("CREATE or UPDATE", "inspectionPointResult", err); + }); + } + + /** + * @description create inspectionPointResult + * @param {Array} results + * @returns {Promise} + */ + static async createOrUpdateMultiple(results: Array): Promise { + return await dataSource + .createQueryBuilder() + .insert() + .into(inspectionPointResult) + .values(results) + .orUpdate(["value"], ["inspectionId", "inspectionPointId"]) + .execute() + .then((result) => { + return result.identifiers[0].id; + }) + .catch((err) => { + throw new DatabaseActionException("CREATE or UPDATE", "inspectionPointResult", err); + }); + } +} diff --git a/src/command/unit/inspection/inspectionVersionedPlanCommand.ts b/src/command/unit/inspection/inspectionVersionedPlanCommand.ts new file mode 100644 index 0000000..825de12 --- /dev/null +++ b/src/command/unit/inspection/inspectionVersionedPlanCommand.ts @@ -0,0 +1,3 @@ +export interface CreateInspectionVersionedPlanCommand { + inspectionPlanId: string; +} diff --git a/src/command/unit/inspection/inspectionVersionedPlanCommandHandler.ts b/src/command/unit/inspection/inspectionVersionedPlanCommandHandler.ts new file mode 100644 index 0000000..151aa16 --- /dev/null +++ b/src/command/unit/inspection/inspectionVersionedPlanCommandHandler.ts @@ -0,0 +1,62 @@ +import { dataSource } from "../../../data-source"; +import { inspectionPoint } from "../../../entity/unit/inspection/inspectionPoint"; +import { inspectionVersionedPlan } from "../../../entity/unit/inspection/inspectionVersionedPlan"; +import DatabaseActionException from "../../../exceptions/databaseActionException"; +import InspectionVersionedPlanService from "../../../service/unit/inspection/inspectionVersionedPlanService"; +import { CreateInspectionPointCommand } from "./inspectionPointCommand"; +import { CreateInspectionVersionedPlanCommand } from "./inspectionVersionedPlanCommand"; + +export default abstract class InspectionVersionedPlanCommandHandler { + /** + * @description create inspectionVersionedPlan + * @param {CreateInspectionVersionedPlanCommand} createInspectionVersionedPlan + * @returns {Promise} + */ + static async create( + createInspectionVersionedPlan: CreateInspectionVersionedPlanCommand, + inspectionPoints: Array + ): Promise { + let count = await InspectionVersionedPlanService.countForPlanId(createInspectionVersionedPlan.inspectionPlanId); + let returnId = ""; + + return await dataSource + .transaction(async (manager) => { + await manager + .createQueryBuilder() + .insert() + .into(inspectionVersionedPlan) + .values({ + inspectionPlanId: createInspectionVersionedPlan.inspectionPlanId, + version: count, + }) + .execute() + .then((result) => { + returnId = result.identifiers[0].id; + }); + + await manager + .createQueryBuilder() + .insert() + .into(inspectionPoint) + .values( + inspectionPoints.map((ip) => ({ + title: ip.title, + description: ip.description, + type: ip.type, + min: ip.min, + max: ip.max, + others: ip.others, + sort: ip.sort, + versionedPlanId: returnId, + })) + ) + .execute(); + }) + .then(() => { + return returnId; + }) + .catch((err) => { + throw new DatabaseActionException("CREATE", "inspectionVersionedPlan", err); + }); + } +} diff --git a/src/command/unit/maintenanceCommand.ts b/src/command/unit/maintenanceCommand.ts new file mode 100644 index 0000000..637ed3b --- /dev/null +++ b/src/command/unit/maintenanceCommand.ts @@ -0,0 +1,16 @@ +export interface CreateMaintenanceCommand { + description: string; + affectedId: string; + affected: "equipment" | "vehicle" | "wearable"; +} + +export interface UpdateMaintenanceCommand { + id: string; + status: string; + done: boolean; + description: string; +} + +export interface DeleteMaintenanceCommand { + id: string; +} diff --git a/src/command/unit/maintenanceCommandHandler.ts b/src/command/unit/maintenanceCommandHandler.ts new file mode 100644 index 0000000..96819dd --- /dev/null +++ b/src/command/unit/maintenanceCommandHandler.ts @@ -0,0 +1,72 @@ +import { dataSource } from "../../data-source"; +import { maintenance } from "../../entity/unit/maintenance"; +import DatabaseActionException from "../../exceptions/databaseActionException"; +import { CreateMaintenanceCommand, UpdateMaintenanceCommand, DeleteMaintenanceCommand } from "./maintenanceCommand"; + +export default abstract class MaintenanceCommandHandler { + /** + * @description create maintenance + * @param {CreateMaintenanceCommand} createMaintenance + * @returns {Promise} + */ + static async create(createMaintenance: CreateMaintenanceCommand): Promise { + return await dataSource + .createQueryBuilder() + .insert() + .into(maintenance) + .values({ + status: "gestartet", + description: createMaintenance.description, + equipmentId: createMaintenance.affected == "equipment" ? createMaintenance.affectedId : null, + vehicleId: createMaintenance.affected == "vehicle" ? createMaintenance.affectedId : null, + wearableId: createMaintenance.affected == "wearable" ? createMaintenance.affectedId : null, + }) + .execute() + .then((result) => { + return result.identifiers[0].id; + }) + .catch((err) => { + throw new DatabaseActionException("CREATE", "maintenance", err); + }); + } + + /** + * @description update maintenance + * @param {UpdateMaintenanceCommand} updateMaintenance + * @returns {Promise} + */ + static async update(updateMaintenance: UpdateMaintenanceCommand): Promise { + return await dataSource + .createQueryBuilder() + .update(maintenance) + .set({ + status: updateMaintenance.status, + done: updateMaintenance.done, + description: updateMaintenance.description, + }) + .where("id = :id", { id: updateMaintenance.id }) + .execute() + .then(() => {}) + .catch((err) => { + throw new DatabaseActionException("UPDATE", "maintenance", err); + }); + } + + /** + * @description delete maintenance + * @param {DeleteMaintenanceCommand} deleteMaintenance + * @returns {Promise} + */ + static async delete(deleteMaintenance: DeleteMaintenanceCommand): Promise { + return await dataSource + .createQueryBuilder() + .delete() + .from(maintenance) + .where("id = :id", { id: deleteMaintenance.id }) + .execute() + .then(() => {}) + .catch((err) => { + throw new DatabaseActionException("DELETE", "maintenance", err); + }); + } +} diff --git a/src/command/unit/repairCommand.ts b/src/command/unit/repairCommand.ts new file mode 100644 index 0000000..4b071d5 --- /dev/null +++ b/src/command/unit/repairCommand.ts @@ -0,0 +1,26 @@ +export interface CreateRepairCommand { + title: string; + description: string; + responsible: string; + affectedId: string; + affected: "equipment" | "vehicle" | "wearable"; + reports: string[]; +} + +export interface UpdateRepairCommand { + id: string; + title: string; + description: string; + responsible: string; +} + +export interface UpdateRepairStatusCommand { + id: string; + status: string; + user: { id: string; firstname: string; lastname: string }; + done: boolean; +} + +export interface DeleteRepairCommand { + id: string; +} diff --git a/src/command/unit/repairCommandHandler.ts b/src/command/unit/repairCommandHandler.ts new file mode 100644 index 0000000..711bb05 --- /dev/null +++ b/src/command/unit/repairCommandHandler.ts @@ -0,0 +1,117 @@ +import { dataSource } from "../../data-source"; +import { repair } from "../../entity/unit/repair"; +import DatabaseActionException from "../../exceptions/databaseActionException"; +import DamageReportCommandHandler from "./damageReportCommandHandler"; +import { + CreateRepairCommand, + UpdateRepairCommand, + DeleteRepairCommand, + UpdateRepairStatusCommand, +} from "./repairCommand"; + +export default abstract class RepairCommandHandler { + /** + * @description create repair + * @param {CreateRepairCommand} createRepair + * @returns {Promise} + */ + static async create(createRepair: CreateRepairCommand): Promise { + let resultId = ""; + return await dataSource + .transaction(async (manager) => { + await manager + .createQueryBuilder() + .insert() + .into(repair) + .values({ + status: "in Arbeit", + title: createRepair.title, + description: createRepair.description, + responsible: createRepair.responsible, + equipmentId: createRepair.affected == "equipment" ? createRepair.affectedId : null, + vehicleId: createRepair.affected == "vehicle" ? createRepair.affectedId : null, + wearableId: createRepair.affected == "wearable" ? createRepair.affectedId : null, + }) + .execute() + .then((result) => { + resultId = result.identifiers[0].id; + }); + + for (const report of createRepair.reports) { + await DamageReportCommandHandler.updateRelatedMaintenanceTransaction(manager, { + id: report, + repairId: resultId, + }); + } + }) + .then(() => { + return resultId; + }) + .catch((err) => { + throw new DatabaseActionException("CREATE", "repair", err); + }); + } + + /** + * @description update repair + * @param {UpdateRepairCommand} updateRepair + * @returns {Promise} + */ + static async update(updateRepair: UpdateRepairCommand): Promise { + return await dataSource + .createQueryBuilder() + .update(repair) + .set({ + title: updateRepair.title, + description: updateRepair.description, + responsible: updateRepair.responsible, + }) + .where("id = :id", { id: updateRepair.id }) + .execute() + .then(() => {}) + .catch((err) => { + throw new DatabaseActionException("UPDATE", "repair", err); + }); + } + + /** + * @description update repair + * @param {UpdateRepairStatusCommand} updateRepair + * @returns {Promise} + */ + static async updateStatus(updateRepair: UpdateRepairStatusCommand): Promise { + return await dataSource + .createQueryBuilder() + .update(repair) + .set({ + status: updateRepair.status, + finishedAt: updateRepair.done ? new Date() : null, + finishedById: updateRepair.done ? updateRepair.user.id : null, + finishedByString: updateRepair.done ? `${updateRepair.user.firstname} ${updateRepair.user.lastname}` : null, + }) + .where("id = :id", { id: updateRepair.id }) + .execute() + .then(() => {}) + .catch((err) => { + throw new DatabaseActionException("UPDATE", "repair", err); + }); + } + + /** + * @description delete repair + * @param {DeleteRepairCommand} deleteRepair + * @returns {Promise} + */ + static async delete(deleteRepair: DeleteRepairCommand): Promise { + return await dataSource + .createQueryBuilder() + .delete() + .from(repair) + .where("id = :id", { id: deleteRepair.id }) + .execute() + .then(() => {}) + .catch((err) => { + throw new DatabaseActionException("DELETE", "repair", err); + }); + } +} diff --git a/src/command/unit/vehicle/vehicleCommand.ts b/src/command/unit/vehicle/vehicleCommand.ts new file mode 100644 index 0000000..1310c0b --- /dev/null +++ b/src/command/unit/vehicle/vehicleCommand.ts @@ -0,0 +1,20 @@ +export interface CreateVehicleCommand { + code?: string; + name: string; + location: string; + commissioned: Date; + vehicleTypeId: string; +} + +export interface UpdateVehicleCommand { + id: string; + code?: string; + name: string; + location: string; + commissioned: Date; + decommissioned?: Date; +} + +export interface DeleteVehicleCommand { + id: string; +} diff --git a/src/command/unit/vehicle/vehicleCommandHandler.ts b/src/command/unit/vehicle/vehicleCommandHandler.ts new file mode 100644 index 0000000..db493f6 --- /dev/null +++ b/src/command/unit/vehicle/vehicleCommandHandler.ts @@ -0,0 +1,73 @@ +import { dataSource } from "../../../data-source"; +import { vehicle } from "../../../entity/unit/vehicle/vehicle"; +import DatabaseActionException from "../../../exceptions/databaseActionException"; +import { CreateVehicleCommand, UpdateVehicleCommand, DeleteVehicleCommand } from "./vehicleCommand"; + +export default abstract class VehicleCommandHandler { + /** + * @description create vehicle + * @param {CreateVehicleCommand} createVehicle + * @returns {Promise} + */ + static async create(createVehicle: CreateVehicleCommand): Promise { + return await dataSource + .createQueryBuilder() + .insert() + .into(vehicle) + .values({ + code: createVehicle.code, + name: createVehicle.name, + location: createVehicle.location, + commissioned: createVehicle.commissioned, + vehicleTypeId: createVehicle.vehicleTypeId, + }) + .execute() + .then((result) => { + return result.identifiers[0].id; + }) + .catch((err) => { + throw new DatabaseActionException("CREATE", "vehicle", err); + }); + } + + /** + * @description update vehicle + * @param {UpdateVehicleCommand} updateVehicle + * @returns {Promise} + */ + static async update(updateVehicle: UpdateVehicleCommand): Promise { + return await dataSource + .createQueryBuilder() + .update(vehicle) + .set({ + code: updateVehicle.code, + name: updateVehicle.name, + location: updateVehicle.location, + commissioned: updateVehicle.commissioned, + }) + .where("id = :id", { id: updateVehicle.id }) + .execute() + .then(() => {}) + .catch((err) => { + throw new DatabaseActionException("UPDATE", "vehicle", err); + }); + } + + /** + * @description delete vehicle + * @param {DeleteVehicleCommand} deleteVehicle + * @returns {Promise} + */ + static async delete(deleteVehicle: DeleteVehicleCommand): Promise { + return await dataSource + .createQueryBuilder() + .delete() + .from(vehicle) + .where("id = :id", { id: deleteVehicle.id }) + .execute() + .then(() => {}) + .catch((err) => { + throw new DatabaseActionException("DELETE", "vehicle", err); + }); + } +} diff --git a/src/command/unit/vehicle/vehicleTypeCommand.ts b/src/command/unit/vehicle/vehicleTypeCommand.ts new file mode 100644 index 0000000..d155fe8 --- /dev/null +++ b/src/command/unit/vehicle/vehicleTypeCommand.ts @@ -0,0 +1,14 @@ +export interface CreateVehicleTypeCommand { + type: string; + description: string; +} + +export interface UpdateVehicleTypeCommand { + id: string; + type: string; + description: string; +} + +export interface DeleteVehicleTypeCommand { + id: string; +} diff --git a/src/command/unit/vehicle/vehicleTypeCommandHandler.ts b/src/command/unit/vehicle/vehicleTypeCommandHandler.ts new file mode 100644 index 0000000..de25fd1 --- /dev/null +++ b/src/command/unit/vehicle/vehicleTypeCommandHandler.ts @@ -0,0 +1,68 @@ +import { dataSource } from "../../../data-source"; +import { vehicleType } from "../../../entity/unit/vehicle/vehicleType"; +import DatabaseActionException from "../../../exceptions/databaseActionException"; +import { CreateVehicleTypeCommand, UpdateVehicleTypeCommand, DeleteVehicleTypeCommand } from "./vehicleTypeCommand"; + +export default abstract class VehicleTypeCommandHandler { + /** + * @description create vehicleType + * @param {CreateVehicleTypeCommand} createVehicleType + * @returns {Promise} + */ + static async create(createVehicleType: CreateVehicleTypeCommand): Promise { + return await dataSource + .createQueryBuilder() + .insert() + .into(vehicleType) + .values({ + type: createVehicleType.type, + description: createVehicleType.description, + }) + .execute() + .then((result) => { + return result.identifiers[0].id; + }) + .catch((err) => { + throw new DatabaseActionException("CREATE", "vehicleType", err); + }); + } + + /** + * @description update vehicleType + * @param {UpdateVehicleTypeCommand} updateVehicleType + * @returns {Promise} + */ + static async update(updateVehicleType: UpdateVehicleTypeCommand): Promise { + return await dataSource + .createQueryBuilder() + .update(vehicleType) + .set({ + type: updateVehicleType.type, + description: updateVehicleType.description, + }) + .where("id = :id", { id: updateVehicleType.id }) + .execute() + .then(() => {}) + .catch((err) => { + throw new DatabaseActionException("UPDATE", "vehicleType", err); + }); + } + + /** + * @description delete vehicleType + * @param {DeleteVehicleTypeCommand} deleteVehicleType + * @returns {Promise} + */ + static async delete(deleteVehicleType: DeleteVehicleTypeCommand): Promise { + return await dataSource + .createQueryBuilder() + .delete() + .from(vehicleType) + .where("id = :id", { id: deleteVehicleType.id }) + .execute() + .then(() => {}) + .catch((err) => { + throw new DatabaseActionException("DELETE", "vehicleType", err); + }); + } +} diff --git a/src/command/unit/wearable/wearableCommand.ts b/src/command/unit/wearable/wearableCommand.ts new file mode 100644 index 0000000..c58eeb7 --- /dev/null +++ b/src/command/unit/wearable/wearableCommand.ts @@ -0,0 +1,22 @@ +export interface CreateWearableCommand { + code?: string; + name: string; + location: string; + commissioned: Date; + wearableTypeId: string; + wearerId?: string; +} + +export interface UpdateWearableCommand { + id: string; + code?: string; + name: string; + location: string; + commissioned: Date; + decommissioned?: Date; + wearerId?: string; +} + +export interface DeleteWearableCommand { + id: string; +} diff --git a/src/command/unit/wearable/wearableCommandHandler.ts b/src/command/unit/wearable/wearableCommandHandler.ts new file mode 100644 index 0000000..a13b773 --- /dev/null +++ b/src/command/unit/wearable/wearableCommandHandler.ts @@ -0,0 +1,76 @@ +import { dataSource } from "../../../data-source"; +import { wearable } from "../../../entity/unit/wearable/wearable"; +import DatabaseActionException from "../../../exceptions/databaseActionException"; +import { CreateWearableCommand, UpdateWearableCommand, DeleteWearableCommand } from "./wearableCommand"; + +export default abstract class WearableCommandHandler { + /** + * @description create wearable + * @param {CreateWearableCommand} createWearable + * @returns {Promise} + */ + static async create(createWearable: CreateWearableCommand): Promise { + return await dataSource + .createQueryBuilder() + .insert() + .into(wearable) + .values({ + code: createWearable.code, + name: createWearable.name, + location: createWearable.location, + commissioned: createWearable.commissioned, + wearableTypeId: createWearable.wearableTypeId, + wearerId: createWearable.wearerId, + }) + .execute() + .then((result) => { + return result.identifiers[0].id; + }) + .catch((err) => { + throw new DatabaseActionException("CREATE", "wearable", err); + }); + } + + /** + * @description update wearable + * @param {UpdateWearableCommand} updateWearable + * @returns {Promise} + */ + static async update(updateWearable: UpdateWearableCommand): Promise { + return await dataSource + .createQueryBuilder() + .update(wearable) + .set({ + code: updateWearable.code, + name: updateWearable.name, + location: updateWearable.location, + commissioned: updateWearable.commissioned, + decommissioned: updateWearable.decommissioned, + wearerId: updateWearable.wearerId, + }) + .where("id = :id", { id: updateWearable.id }) + .execute() + .then(() => {}) + .catch((err) => { + throw new DatabaseActionException("UPDATE", "wearable", err); + }); + } + + /** + * @description delete wearable + * @param {DeleteWearableCommand} deleteWearable + * @returns {Promise} + */ + static async delete(deleteWearable: DeleteWearableCommand): Promise { + return await dataSource + .createQueryBuilder() + .delete() + .from(wearable) + .where("id = :id", { id: deleteWearable.id }) + .execute() + .then(() => {}) + .catch((err) => { + throw new DatabaseActionException("DELETE", "wearable", err); + }); + } +} diff --git a/src/command/unit/wearable/wearableTypeCommand.ts b/src/command/unit/wearable/wearableTypeCommand.ts new file mode 100644 index 0000000..45cbc41 --- /dev/null +++ b/src/command/unit/wearable/wearableTypeCommand.ts @@ -0,0 +1,14 @@ +export interface CreateWearableTypeCommand { + type: string; + description: string; +} + +export interface UpdateWearableTypeCommand { + id: string; + type: string; + description: string; +} + +export interface DeleteWearableTypeCommand { + id: string; +} diff --git a/src/command/unit/wearable/wearableTypeCommandHandler.ts b/src/command/unit/wearable/wearableTypeCommandHandler.ts new file mode 100644 index 0000000..b8f0f6f --- /dev/null +++ b/src/command/unit/wearable/wearableTypeCommandHandler.ts @@ -0,0 +1,68 @@ +import { dataSource } from "../../../data-source"; +import { wearableType } from "../../../entity/unit/wearable/wearableType"; +import DatabaseActionException from "../../../exceptions/databaseActionException"; +import { CreateWearableTypeCommand, UpdateWearableTypeCommand, DeleteWearableTypeCommand } from "./wearableTypeCommand"; + +export default abstract class WearableTypeCommandHandler { + /** + * @description create wearableType + * @param {CreateWearableTypeCommand} createWearableType + * @returns {Promise} + */ + static async create(createWearableType: CreateWearableTypeCommand): Promise { + return await dataSource + .createQueryBuilder() + .insert() + .into(wearableType) + .values({ + type: createWearableType.type, + description: createWearableType.description, + }) + .execute() + .then((result) => { + return result.identifiers[0].id; + }) + .catch((err) => { + throw new DatabaseActionException("CREATE", "wearableType", err); + }); + } + + /** + * @description update wearableType + * @param {UpdateWearableTypeCommand} updateWearableType + * @returns {Promise} + */ + static async update(updateWearableType: UpdateWearableTypeCommand): Promise { + return await dataSource + .createQueryBuilder() + .update(wearableType) + .set({ + type: updateWearableType.type, + description: updateWearableType.description, + }) + .where("id = :id", { id: updateWearableType.id }) + .execute() + .then(() => {}) + .catch((err) => { + throw new DatabaseActionException("UPDATE", "wearableType", err); + }); + } + + /** + * @description delete wearableType + * @param {DeleteWearableTypeCommand} deleteWearableType + * @returns {Promise} + */ + static async delete(deleteWearableType: DeleteWearableTypeCommand): Promise { + return await dataSource + .createQueryBuilder() + .delete() + .from(wearableType) + .where("id = :id", { id: deleteWearableType.id }) + .execute() + .then(() => {}) + .catch((err) => { + throw new DatabaseActionException("DELETE", "wearableType", err); + }); + } +} diff --git a/src/controller/admin/unit/damageReportController.ts b/src/controller/admin/unit/damageReportController.ts new file mode 100644 index 0000000..87f9a65 --- /dev/null +++ b/src/controller/admin/unit/damageReportController.ts @@ -0,0 +1,169 @@ +import { Request, Response } from "express"; +import DamageReportService from "../../../service/unit/damageReportService"; +import DamageReportFactory from "../../../factory/admin/unit/damageReport"; +import { CreateDamageReportCommand, UpdateDamageReportCommand } from "../../../command/unit/damageReportCommand"; +import DamageReportCommandHandler from "../../../command/unit/damageReportCommandHandler"; +import BadRequestException from "../../../exceptions/badRequestException"; +import { FileSystemHelper } from "../../../helpers/fileSystemHelper"; + +/** + * @description get all damageReports by status + * @param req {Request} Express req object + * @param res {Response} Express res object + * @returns {Promise<*>} + */ +export async function getAllDamageReportsByStatus(req: Request, res: Response): Promise { + let done = req.query.done === "true"; + let offset = parseInt((req.query.offset as string) ?? "0"); + let count = parseInt((req.query.count as string) ?? "25"); + let noLimit = req.query.noLimit === "true"; + + let [damageReports, total] = await DamageReportService.getAllByStatus(done, { offset, count, noLimit }); + + res.json({ + damageReports: DamageReportFactory.mapToBase(damageReports), + total: total, + offset: offset, + count: count, + }); +} + +/** + * @description get all damageReports for related id + * @param req {Request} Express req object + * @param res {Response} Express res object + * @returns {Promise<*>} + */ +export async function getAllDamageReportsForRelated(req: Request, res: Response): Promise { + let relation = req.params.related as "vehicle" | "equipment" | "wearable"; + let relationId = req.params.relatedId as string; + let offset = parseInt((req.query.offset as string) ?? "0"); + let count = parseInt((req.query.count as string) ?? "25"); + let noLimit = req.query.noLimit === "true"; + + let where; + if (relation == "equipment") { + where = { equipmentId: relationId }; + } else if (relation == "vehicle") { + where = { vehicleId: relationId }; + } else { + where = { wearableId: relationId }; + } + let [damageReports, total] = await DamageReportService.getAllForRelated(where, { offset, count, noLimit }); + + res.json({ + damageReports: DamageReportFactory.mapToBase(damageReports), + total: total, + offset: offset, + count: count, + }); +} + +/** + * @description get damageReport by id + * @param req {Request} Express req object + * @param res {Response} Express res object + * @returns {Promise<*>} + */ +export async function getDamageReportById(req: Request, res: Response): Promise { + const damageReportId = req.params.id; + let damageReport = await DamageReportService.getById(damageReportId); + + res.json(DamageReportFactory.mapToSingle(damageReport)); +} + +/** + * @description get reports by Ids + * @param req {Request} Express req object + * @param res {Response} Express res object + * @returns {Promise<*>} + */ +export async function getDamageReportsByIds(req: Request, res: Response): Promise { + let ids = req.body.ids as Array; + + let [damageReports, total] = await DamageReportService.getAll({ noLimit: true, ids }); + + res.json({ + damageReports: DamageReportFactory.mapToBase(damageReports), + total: total, + offset: 0, + count: total, + }); +} + +/** + * @description provide uploaded image for damage report + * @param req {Request} Express req object + * @param res {Response} Express res object + * @returns {Promise<*>} + */ +export async function provideDamageReportImageUpload(req: Request, res: Response): Promise { + const damageReportId = req.params.id; + const filename = req.params.filename; + + let filepath = FileSystemHelper.formatPath("damageReport", filename); + + res.sendFile(filepath); +} + +/** + * @description create damageReport + * @param req {Request} Express req object + * @param res {Response} Express res object + * @returns {Promise<*>} + */ +export async function createDamageReport(req: Request, res: Response): Promise { + const title = req.body.title; + const description = req.body.description; + const location = req.body.location; + const note = req.body.note; + const reportedBy = req.body.reportedBy; + const images = req.files as Express.Multer.File[]; + const affectedId = req.body.affectedId; + const affected = req.body.affected; + + if (affected != "equipment" && affected != "vehicle" && affected != "wearable") + throw new BadRequestException("set assigned to equipment or vehicle or wearable"); + + let createDamageReport: CreateDamageReportCommand = { + title, + description, + location, + noteByReporter: note, + reportedBy, + images: images.map((i) => i.filename), + affectedId, + affected, + }; + let damageReportId = await DamageReportCommandHandler.create(createDamageReport); + + res.status(200).send(damageReportId); +} + +/** + * @description update damageReport by id + * @param req {Request} Express req object + * @param res {Response} Express res object + * @returns {Promise<*>} + */ +export async function updateDamageReportById(req: Request, res: Response): Promise { + const damageReportId = req.params.id; + const status = req.body.status; + const noteByWorker = req.body.noteByWorker; + const done = req.body.done; + + let updateDamageReport: UpdateDamageReportCommand = { + id: damageReportId, + status, + noteByWorker, + done, + user: { + id: req.userId, + firstname: req.firstname, + lastname: req.lastname, + }, + }; + await DamageReportCommandHandler.update(updateDamageReport); + + res.sendStatus(204); +} diff --git a/src/controller/admin/unit/equipmentController.ts b/src/controller/admin/unit/equipmentController.ts new file mode 100644 index 0000000..caee218 --- /dev/null +++ b/src/controller/admin/unit/equipmentController.ts @@ -0,0 +1,133 @@ +import { Request, Response } from "express"; +import EquipmentService from "../../../service/unit/equipment/equipmentService"; +import EquipmentFactory from "../../../factory/admin/unit/equipment/equipment"; +import { + CreateEquipmentCommand, + DeleteEquipmentCommand, + UpdateEquipmentCommand, +} from "../../../command/unit/equipment/equipmentCommand"; +import EquipmentCommandHandler from "../../../command/unit/equipment/equipmentCommandHandler"; + +/** + * @description get all equipments + * @param req {Request} Express req object + * @param res {Response} Express res object + * @returns {Promise<*>} + */ +export async function getAllEquipments(req: Request, res: Response): Promise { + let offset = parseInt((req.query.offset as string) ?? "0"); + let count = parseInt((req.query.count as string) ?? "25"); + let search = (req.query.search as string) ?? ""; + let noLimit = req.query.noLimit === "true"; + let ids = ((req.query.ids ?? "") as string).split(",").filter((i) => i); + + let [equipments, total] = await EquipmentService.getAll({ offset, count, search, noLimit, ids }); + + res.json({ + equipments: EquipmentFactory.mapToBase(equipments), + total: total, + offset: offset, + count: count, + }); +} + +/** + * @description get equipment by id + * @param req {Request} Express req object + * @param res {Response} Express res object + * @returns {Promise<*>} + */ +export async function getEquipmentById(req: Request, res: Response): Promise { + const equipmentId = req.params.id; + let equipment = await EquipmentService.getById(equipmentId); + + res.json(EquipmentFactory.mapToSingle(equipment)); +} + +/** + * @description get equipment by Ids + * @param req {Request} Express req object + * @param res {Response} Express res object + * @returns {Promise<*>} + */ +export async function getEquipmentsByIds(req: Request, res: Response): Promise { + let ids = req.body.ids as Array; + + let [equipments, total] = await EquipmentService.getAll({ noLimit: true, ids }); + + res.json({ + equipments: EquipmentFactory.mapToBase(equipments), + total: total, + offset: 0, + count: total, + }); +} + +/** + * @description create equipment + * @param req {Request} Express req object + * @param res {Response} Express res object + * @returns {Promise<*>} + */ +export async function createEquipment(req: Request, res: Response): Promise { + const name = req.body.name; + const code = req.body.code || null; + const location = req.body.location; + const commissioned = req.body.commissioned; + const equipmentTypeId = req.body.equipmentTypeId; + + let createEquipment: CreateEquipmentCommand = { + code, + name, + location, + commissioned, + equipmentTypeId, + }; + let equipmentId = await EquipmentCommandHandler.create(createEquipment); + + res.status(200).send(equipmentId); +} + +/** + * @description update equipment by id + * @param req {Request} Express req object + * @param res {Response} Express res object + * @returns {Promise<*>} + */ +export async function updateEquipmentById(req: Request, res: Response): Promise { + const equipmentId = req.params.id; + const name = req.body.name; + const code = req.body.code || null; + const location = req.body.location; + const commissioned = req.body.commissioned; + const decommissioned = req.body.decommissioned || null; + + let updateEquipment: UpdateEquipmentCommand = { + id: equipmentId, + name, + code, + location, + commissioned, + decommissioned, + }; + await EquipmentCommandHandler.update(updateEquipment); + + res.sendStatus(204); +} + +/** + * @description delete equipment by id + * @param req {Request} Express req object + * @param res {Response} Express res object + * @returns {Promise<*>} + */ +export async function deleteEquipmentById(req: Request, res: Response): Promise { + const equipmentId = req.params.id; + + let deleteEquipment: DeleteEquipmentCommand = { + id: equipmentId, + }; + await EquipmentCommandHandler.delete(deleteEquipment); + + res.sendStatus(204); +} diff --git a/src/controller/admin/unit/equipmentTypeController.ts b/src/controller/admin/unit/equipmentTypeController.ts new file mode 100644 index 0000000..cfae069 --- /dev/null +++ b/src/controller/admin/unit/equipmentTypeController.ts @@ -0,0 +1,101 @@ +import { Request, Response } from "express"; +import EquipmentTypeService from "../../../service/unit/equipment/equipmentTypeService"; +import EquipmentTypeFactory from "../../../factory/admin/unit/equipment/equipmentType"; +import { + CreateEquipmentTypeCommand, + DeleteEquipmentTypeCommand, + UpdateEquipmentTypeCommand, +} from "../../../command/unit/equipment/equipmentTypeCommand"; +import EquipmentTypeCommandHandler from "../../../command/unit/equipment/equipmentTypeCommandHandler"; + +/** + * @description get all equipmentTypes + * @param req {Request} Express req object + * @param res {Response} Express res object + * @returns {Promise<*>} + */ +export async function getAllEquipmentTypes(req: Request, res: Response): Promise { + let offset = parseInt((req.query.offset as string) ?? "0"); + let count = parseInt((req.query.count as string) ?? "25"); + let search = (req.query.search as string) ?? ""; + let noLimit = req.query.noLimit === "true"; + + let [equipmentTypes, total] = await EquipmentTypeService.getAll({ offset, count, search, noLimit }); + + res.json({ + equipmentTypes: EquipmentTypeFactory.mapToBase(equipmentTypes), + total: total, + offset: offset, + count: count, + }); +} + +/** + * @description get equipmentType by id + * @param req {Request} Express req object + * @param res {Response} Express res object + * @returns {Promise<*>} + */ +export async function getEquipmentTypeById(req: Request, res: Response): Promise { + const equipmentTypeId = req.params.id; + let equipmentType = await EquipmentTypeService.getById(equipmentTypeId); + + res.json(EquipmentTypeFactory.mapToSingle(equipmentType)); +} + +/** + * @description create equipmentType + * @param req {Request} Express req object + * @param res {Response} Express res object + * @returns {Promise<*>} + */ +export async function createEquipmentType(req: Request, res: Response): Promise { + const type = req.body.type; + const description = req.body.description; + + let createEquipmentType: CreateEquipmentTypeCommand = { + type, + description, + }; + let equipmentTypeId = await EquipmentTypeCommandHandler.create(createEquipmentType); + + res.status(200).send(equipmentTypeId); +} + +/** + * @description update equipmentType by id + * @param req {Request} Express req object + * @param res {Response} Express res object + * @returns {Promise<*>} + */ +export async function updateEquipmentTypeById(req: Request, res: Response): Promise { + const equipmentTypeId = req.params.id; + const type = req.body.type; + const description = req.body.description; + + let updateEquipmentType: UpdateEquipmentTypeCommand = { + id: equipmentTypeId, + type, + description, + }; + await EquipmentTypeCommandHandler.update(updateEquipmentType); + + res.sendStatus(204); +} + +/** + * @description delete equipmentType by id + * @param req {Request} Express req object + * @param res {Response} Express res object + * @returns {Promise<*>} + */ +export async function deleteEquipmentTypeById(req: Request, res: Response): Promise { + const equipmentTypeId = req.params.id; + + let deleteEquipmentType: DeleteEquipmentTypeCommand = { + id: equipmentTypeId, + }; + await EquipmentTypeCommandHandler.delete(deleteEquipmentType); + + res.sendStatus(204); +} diff --git a/src/controller/admin/unit/inspectionController.ts b/src/controller/admin/unit/inspectionController.ts new file mode 100644 index 0000000..211ea10 --- /dev/null +++ b/src/controller/admin/unit/inspectionController.ts @@ -0,0 +1,402 @@ +import { Request, Response } from "express"; +import InspectionService from "../../../service/unit/inspection/inspectionService"; +import InspectionFactory from "../../../factory/admin/unit/inspection/inspection"; +import { + CreateInspectionCommand, + DeleteInspectionCommand, + FinishInspectionCommand, + UpdateInspectionCommand, +} from "../../../command/unit/inspection/inspectionCommand"; +import InspectionCommandHandler from "../../../command/unit/inspection/inspectionCommandHandler"; +import BadRequestException from "../../../exceptions/badRequestException"; +import ForbiddenRequestException from "../../../exceptions/forbiddenRequestException"; +import { CreateOrUpdateInspectionPointResultCommand } from "../../../command/unit/inspection/inspectionPointResultCommand"; +import InspectionPointResultCommandHandler from "../../../command/unit/inspection/inspectionPointResultCommandHandler"; +import { InspectionPointEnum } from "../../../enums/inspectionEnum"; +import { FileSystemHelper } from "../../../helpers/fileSystemHelper"; +import { PdfExport } from "../../../helpers/pdfExport"; +import { PDFDocument } from "pdf-lib"; +import sharp from "sharp"; +import InspectionPointResultService from "../../../service/unit/inspection/inspectionPointResultService"; +import InspectionPlanService from "../../../service/unit/inspection/inspectionPlanService"; + +/** + * @description get all inspections sorted by id not having newer inspection + * @param req {Request} Express req object + * @param res {Response} Express res object + * @returns {Promise<*>} + */ +export async function getAllInspectionsSortedNotHavingNewer(req: Request, res: Response): Promise { + let offset = parseInt((req.query.offset as string) ?? "0"); + let count = parseInt((req.query.count as string) ?? "25"); + let noLimit = req.query.noLimit === "true"; + + let [inspections, total] = await InspectionService.getAllSortedNotHavingNewer({ offset, count, noLimit }); + + res.json({ + inspections: InspectionFactory.mapToBaseNext(inspections), + total: total, + offset: offset, + count: count, + }); +} + +/** + * @description get all inspections running + * @param req {Request} Express req object + * @param res {Response} Express res object + * @returns {Promise<*>} + */ +export async function getAllInspectionsRunning(req: Request, res: Response): Promise { + let offset = parseInt((req.query.offset as string) ?? "0"); + let count = parseInt((req.query.count as string) ?? "25"); + let noLimit = req.query.noLimit === "true"; + + let [inspections, total] = await InspectionService.getAllRunning({ offset, count, noLimit }); + + res.json({ + inspections: InspectionFactory.mapToBaseMinified(inspections), + total: total, + offset: offset, + count: count, + }); +} + +/** + * @description get all inspections for related id + * @param req {Request} Express req object + * @param res {Response} Express res object + * @returns {Promise<*>} + */ +export async function getAllInspectionsForRelated(req: Request, res: Response): Promise { + let relation = req.params.related as "vehicle" | "equipment" | "wearable"; + let relationId = req.params.relatedId as string; + let offset = parseInt((req.query.offset as string) ?? "0"); + let count = parseInt((req.query.count as string) ?? "25"); + let noLimit = req.query.noLimit === "true"; + + let where; + if (relation == "equipment") { + where = { equipmentId: relationId }; + } else if (relation == "vehicle") { + where = { vehicleId: relationId }; + } else { + where = { wearableId: relationId }; + } + let [inspections, total] = await InspectionService.getAllForRelated(where, { offset, count, noLimit }); + + res.json({ + inspections: InspectionFactory.mapToBaseMinified(inspections), + total: total, + offset: offset, + count: count, + }); +} + +/** + * @description get inspection by id + * @param req {Request} Express req object + * @param res {Response} Express res object + * @returns {Promise<*>} + */ +export async function getInspectionPrintoutById(req: Request, res: Response): Promise { + const inspectionId = req.params.id; + let inspection = await InspectionService.getById(inspectionId); + + if (inspection.finishedAt == null) + throw new ForbiddenRequestException("this inspection has not been finished yet and it so does not have a printout"); + + let filepath = FileSystemHelper.formatPath("inspection", inspection.id, "printout.pdf"); + + res.sendFile(filepath, { + headers: { + "Content-Type": "application/pdf", + }, + }); +} + +/** + * @description get inspection by id + * @param req {Request} Express req object + * @param res {Response} Express res object + * @returns {Promise<*>} + */ +export async function getInspectionPointUpload(req: Request, res: Response): Promise { + const inspectionId = req.params.id; + const inspectionPointId = req.params.pointId; + let result = await InspectionPointResultService.getForInspectionAndPoint(inspectionId, inspectionPointId); + + let filepath = FileSystemHelper.formatPath("inspection", inspectionId, result.value); + + if (result.inspectionPoint.others === "pdf") { + res.sendFile(filepath, { + headers: { + "Content-Type": "application/pdf", + }, + }); + } else { + let image = await sharp(filepath).png().toBuffer(); + res.set({ + "Content-Type": "image/png", + }); + res.send(image); + } +} + +/** + * @description get inspection by id + * @param req {Request} Express req object + * @param res {Response} Express res object + * @returns {Promise<*>} + */ +export async function getInspectionById(req: Request, res: Response): Promise { + const inspectionId = req.params.id; + let inspection = await InspectionService.getById(inspectionId); + + res.json(InspectionFactory.mapToSingle(inspection)); +} + +/** + * @description create inspection + * @param req {Request} Express req object + * @param res {Response} Express res object + * @returns {Promise<*>} + */ +export async function createInspection(req: Request, res: Response): Promise { + const context = req.body.context; + const inspectionPlanId = req.body.inspectionPlanId; + const relatedId = req.body.relatedId; + const assigned = req.body.assigned; + const nextInspection = req.body.nextInspection || null; + + let inspectionPlan = await InspectionPlanService.getById(inspectionPlanId); + if (inspectionPlan.inspectionInterval && !nextInspection) + throw new BadRequestException("inspection has to have nextInspection date"); + + if (assigned != "equipment" && assigned != "vehicle" && assigned != "wearable") + throw new BadRequestException("set assigned to equipment or vehicle or wearable"); + + if (relatedId == null) throw new BadRequestException("provide related equipment or vehicle or wearable"); + + let existsUnfinished = await InspectionService.existsUnfinishedInspectionToPlan( + inspectionPlanId, + assigned, + relatedId + ); + if (existsUnfinished) throw new ForbiddenRequestException("there is already an unfinished inspection existing"); + + let createInspection: CreateInspectionCommand = { + context, + nextInspection, + inspectionPlanId, + relatedId, + assigned, + }; + let inspectionId = await InspectionCommandHandler.create(createInspection); + + res.status(200).send(inspectionId); +} + +/** + * @description update inspection by id + * @param req {Request} Express req object + * @param res {Response} Express res object + * @returns {Promise<*>} + */ +export async function updateInspectionById(req: Request, res: Response): Promise { + const inspectionId = req.params.id; + const context = req.body.context; + const nextInspection = req.body.nextInspection || null; + + let updateInspection: UpdateInspectionCommand = { + id: inspectionId, + context, + nextInspection, + }; + await InspectionCommandHandler.update(updateInspection); + + res.sendStatus(204); +} + +/** + * @description update inspection by id + * @param req {Request} Express req object + * @param res {Response} Express res object + * @returns {Promise<*>} + */ +export async function updateInspectionResults(req: Request, res: Response): Promise { + const inspectionId = req.params.id; + const pointResults = JSON.parse(req.body.results) as Array<{ inspectionPointId: string; value: string }>; + const pointFiles = req.files as Array; + + let inspection = await InspectionService.getById(inspectionId); + + let updateResults: Array = pointResults.map((pr) => ({ + inspectionPointId: pr.inspectionPointId, + value: + inspection.inspectionVersionedPlan.inspectionPoints.find((ip) => ip.id == pr.inspectionPointId).type == + InspectionPointEnum.file && pr.value == "set" + ? pointFiles.find((f) => f.filename.startsWith(pr.inspectionPointId))?.filename + : pr.value, + inspectionId, + })); + await InspectionPointResultCommandHandler.createOrUpdateMultiple(updateResults); + + res.sendStatus(204); +} + +/** + * @description finish inspection by id + * @param req {Request} Express req object + * @param res {Response} Express res object + * @returns {Promise<*>} + */ +export async function finishInspection(req: Request, res: Response): Promise { + const inspectionId = req.params.id; + + let inspection = await InspectionService.getById(inspectionId); + + function getValueToInspectionPoint(inspectionPointId: string) { + return inspection.pointResults.find((c) => c.inspectionPointId == inspectionPointId)?.value; + } + + let everythingFilled = inspection.inspectionVersionedPlan.inspectionPoints.every((p) => { + if (p.type == InspectionPointEnum.file) { + return getValueToInspectionPoint(p.id); + } else if (p.type == InspectionPointEnum.oknok) { + let value = getValueToInspectionPoint(p.id); + return (["true", "false"].includes(value) ? (value as "true" | "false") : "") != ""; + } else { + return !!getValueToInspectionPoint(p.id); + } + }); + if (!everythingFilled) throw new ForbiddenRequestException("fill out every field before finishing inspection"); + + let formattedInspection = InspectionFactory.mapToSingle(inspection); + let title = `Prüf-Ausdruck_${[formattedInspection.related.code ?? "", formattedInspection.related.name].join("_")}_${ + formattedInspection.inspectionPlan.title + }_${new Date(formattedInspection.finishedAt ?? "").toLocaleDateString("de-de")}`; + + let inspectionPoints = []; + for (const ip of formattedInspection.inspectionVersionedPlan.inspectionPoints.sort( + (a, b) => (a.sort ?? 0) - (b.sort ?? 0) + )) { + let value = formattedInspection.checks.find((c) => c.inspectionPointId == ip.id).value; + let image = ""; + if (ip.type == InspectionPointEnum.file && ip.others == "img") { + const imagePath = FileSystemHelper.formatPath("inspection", inspection.id, value); + let pngImageBytes = await sharp(imagePath).png().toBuffer(); + image = `data:image/png;base64,${pngImageBytes.toString("base64")}`; + } else if (ip.type == InspectionPointEnum.oknok) { + value = value ? "OK" : "Nicht OK"; + } + inspectionPoints.push({ + title: ip.title, + description: ip.description, + type: ip.type, + min: ip.min, + max: ip.max, + others: ip.others, + value: value, + image: image, + }); + } + + let pdf = await PdfExport.renderFile({ + template: "inspection", + title, + saveToDisk: false, + data: { + inspector: `${req.lastname}, ${req.firstname}`, + context: formattedInspection.context || "---", + createdAt: formattedInspection.created, + finishedAt: formattedInspection.finishedAt ?? new Date(), + nextInspection: formattedInspection.nextInspection, + related: formattedInspection.related, + plan: formattedInspection.inspectionPlan, + planVersion: formattedInspection.inspectionVersionedPlan.version, + planTitle: formattedInspection.inspectionPlan.title, + checks: inspectionPoints, + }, + }); + + const finalDocument = await PDFDocument.create(); + const printout = await PDFDocument.load(pdf); + const copiedPages = await finalDocument.copyPages(printout, printout.getPageIndices()); + copiedPages.forEach((page) => finalDocument.addPage(page)); + + let resultsForAppend = inspectionPoints.filter((ip) => ip.type == InspectionPointEnum.file && ip.others == "pdf"); + + if (resultsForAppend.length !== 0) { + const appendixPage = finalDocument.addPage(); + const { width, height } = appendixPage.getSize(); + appendixPage.drawText("Anhang:", { + x: 50, + y: height - 50, + size: 24, + }); + } + + for (const appendix of resultsForAppend) { + const appendixPdfBytes = FileSystemHelper.readFileAsBase64("inspection", inspection.id, appendix.value); + const appendixPdf = await PDFDocument.load(appendixPdfBytes); + const appendixPages = await finalDocument.copyPages(appendixPdf, appendixPdf.getPageIndices()); + appendixPages.forEach((page) => finalDocument.addPage(page)); + + /** print image + const imagePath = FileSystemHelper.formatPath("inspection", inspection.id, checkValue); + let pngImageBytes = await sharp(imagePath).png().toBuffer(); + let image = await finalDocument.embedPng(pngImageBytes); + let dims = image.scale(1); + if (image) { + const page = finalDocument.addPage(); + const { width, height } = page.getSize(); + const x = (width - dims.width) / 2; + const y = (height - dims.height) / 2; + page.drawImage(image, { + x, + y, + width: dims.width, + height: dims.height, + }); + } + */ + } + + const mergedPdfBytes = await finalDocument.save(); + FileSystemHelper.writeFile(`inspection/${inspection.id}`, `printout.pdf`, mergedPdfBytes); + + let finish: FinishInspectionCommand = { + id: inspectionId, + user: { + id: req.userId, + firstname: req.firstname, + lastname: req.lastname, + }, + }; + await InspectionCommandHandler.finish(finish); + + res.sendStatus(204); +} + +/** + * @description delete inspection by id + * @param req {Request} Express req object + * @param res {Response} Express res object + * @returns {Promise<*>} + */ +export async function deleteInspectionById(req: Request, res: Response): Promise { + const inspectionId = req.params.id; + + let deleteInspectionData = await InspectionService.getById(inspectionId); + if (deleteInspectionData.finishedAt != null) { + throw new ForbiddenRequestException("Cannot delete as inspection is already finished"); + } + + let deleteInspection: DeleteInspectionCommand = { + id: inspectionId, + }; + await InspectionCommandHandler.delete(deleteInspection); + + res.sendStatus(204); +} diff --git a/src/controller/admin/unit/inspectionPlanController.ts b/src/controller/admin/unit/inspectionPlanController.ts new file mode 100644 index 0000000..394f541 --- /dev/null +++ b/src/controller/admin/unit/inspectionPlanController.ts @@ -0,0 +1,216 @@ +import { Request, Response } from "express"; +import InspectionPlanService from "../../../service/unit/inspection/inspectionPlanService"; +import InspectionPlanFactory from "../../../factory/admin/unit/inspection/inspectionPlan"; +import { + CreateInspectionPlanCommand, + DeleteInspectionPlanCommand, + UpdateInspectionPlanCommand, +} from "../../../command/unit/inspection/inspectionPlanCommand"; +import InspectionPlanCommandHandler from "../../../command/unit/inspection/inspectionPlanCommandHandler"; +import BadRequestException from "../../../exceptions/badRequestException"; +import TypeTester from "../../../helpers/typeTester"; +import InspectionPointService from "../../../service/unit/inspection/inspectionPointService"; +import InspectionPointFactory from "../../../factory/admin/unit/inspection/inspectionPoint"; +import InspectionService from "../../../service/unit/inspection/inspectionService"; +import InspectionVersionedPlanCommandHandler from "../../../command/unit/inspection/inspectionVersionedPlanCommandHandler"; +import InspectionPointCommandHandler from "../../../command/unit/inspection/inspectionPointCommandHandler"; + +/** + * @description get all inspectionPlans + * @param req {Request} Express req object + * @param res {Response} Express res object + * @returns {Promise<*>} + */ +export async function getAllInspectionPlans(req: Request, res: Response): Promise { + let offset = parseInt((req.query.offset as string) ?? "0"); + let count = parseInt((req.query.count as string) ?? "25"); + let search = (req.query.search as string) ?? ""; + let noLimit = req.query.noLimit === "true"; + let ids = ((req.query.ids ?? "") as string).split(",").filter((i) => i); + + let [inspectionPlans, total] = await InspectionPlanService.getAll({ + offset, + count, + search, + noLimit, + ids, + }); + + res.json({ + inspectionPlans: InspectionPlanFactory.mapToBase(inspectionPlans), + total: total, + offset: offset, + count: count, + }); +} + +/** + * @description get all inspectionPlans + * @param req {Request} Express req object + * @param res {Response} Express res object + * @returns {Promise<*>} + */ +export async function getAllInspectionPlansForRelated(req: Request, res: Response): Promise { + let relation = req.params.related as "vehicleType" | "equipmentType" | "wearableType"; + let relationTypeId = req.params.relatedTypeId as string; + let offset = parseInt((req.query.offset as string) ?? "0"); + let count = parseInt((req.query.count as string) ?? "25"); + let search = (req.query.search as string) ?? ""; + let noLimit = req.query.noLimit === "true"; + let ids = ((req.query.ids ?? "") as string).split(",").filter((i) => i); + + let where; + if (relation == "equipmentType") { + where = { equipmentTypeId: relationTypeId }; + } else if (relation == "vehicleType") { + where = { vehicleTypeId: relationTypeId }; + } else { + where = { wearableTypeId: relationTypeId }; + } + let [inspectionPlans, total] = await InspectionPlanService.getAllForRelated(where, { + offset, + count, + search, + noLimit, + ids, + }); + + res.json({ + inspectionPlans: inspectionPlans, + total: total, + offset: offset, + count: count, + }); +} + +/** + * @description get inspectionPoints by planid + * @param req {Request} Express req object + * @param res {Response} Express res object + * @returns {Promise<*>} + */ +export async function getInspectionPointsByPlanId(req: Request, res: Response): Promise { + const inspectionPlanId = req.params.id; + let inspectionPlan = await InspectionPlanService.getById(inspectionPlanId); + + if (!inspectionPlan.latestVersionedPlan) { + res.json([]); + } else { + let inspectionPoints = await InspectionPointService.getAllForVersionedPlan(inspectionPlan.latestVersionedPlan.id); + + res.json(InspectionPointFactory.mapToBase(inspectionPoints)); + } +} + +/** + * @description get inspectionPlan by id + * @param req {Request} Express req object + * @param res {Response} Express res object + * @returns {Promise<*>} + */ +export async function getInspectionPlanById(req: Request, res: Response): Promise { + const inspectionPlanId = req.params.id; + let inspectionPlan = await InspectionPlanService.getById(inspectionPlanId); + + res.json(InspectionPlanFactory.mapToSingle(inspectionPlan)); +} + +/** + * @description create inspectionPlan + * @param req {Request} Express req object + * @param res {Response} Express res object + * @returns {Promise<*>} + */ +export async function createInspectionPlan(req: Request, res: Response): Promise { + const title = req.body.title; + const inspectionInterval = req.body.inspectionInterval || null; + const remindTime = req.body.remindTime || null; + const relatedId = req.body.relatedId; + const assigned = req.body.assigned; + + TypeTester.testPlanTimeDefinition(inspectionInterval, { key: "inspectionInterval", throwErr: true, allowNull: true }); + TypeTester.testPlanTimeDefinition(remindTime, { key: "inspectionInterval", throwErr: true, allowNull: true }); + + if (assigned != "equipment" && assigned != "vehicle" && assigned != "wearable") + throw new BadRequestException("set assigned to equipmenttype or vehicletype or wearabletype"); + + if (relatedId == null) throw new BadRequestException("provide related equipmenttype or vehicletype or wearabletype"); + + let createInspectionPlan: CreateInspectionPlanCommand = { + title, + inspectionInterval, + remindTime, + relatedId, + assigned, + }; + let inspectionPlanId = await InspectionPlanCommandHandler.create(createInspectionPlan); + + res.status(200).send(inspectionPlanId); +} + +/** + * @description update inspectionPlan by id + * @param req {Request} Express req object + * @param res {Response} Express res object + * @returns {Promise<*>} + */ +export async function updateInspectionPlanById(req: Request, res: Response): Promise { + const inspectionPlanId = req.params.id; + const title = req.body.title; + const inspectionInterval = req.body.inspectionInterval || null; + const remindTime = req.body.remindTime || null; + + TypeTester.testPlanTimeDefinition(inspectionInterval, { key: "inspectionInterval", throwErr: true, allowNull: true }); + TypeTester.testPlanTimeDefinition(remindTime, { key: "inspectionInterval", throwErr: true, allowNull: true }); + + let updateInspectionPlan: UpdateInspectionPlanCommand = { + id: inspectionPlanId, + title, + inspectionInterval, + remindTime, + }; + await InspectionPlanCommandHandler.update(updateInspectionPlan); + + res.sendStatus(204); +} + +/** + * @description get inspectionPoints by planid + * @param req {Request} Express req object + * @param res {Response} Express res object + * @returns {Promise<*>} + */ +export async function updateInspectionPointsByPlanId(req: Request, res: Response): Promise { + const inspectionPlanId = req.params.id; + const inspectionPoints = req.body; + let inspectionPlan = await InspectionPlanService.getById(inspectionPlanId); + + let usedVersionedPlan = inspectionPlan?.latestVersionedPlan?.id + ? await InspectionService.usesVersionedInspectionPlan(inspectionPlan.latestVersionedPlan.id) + : true; + + if (usedVersionedPlan) { + await InspectionVersionedPlanCommandHandler.create({ inspectionPlanId }, inspectionPoints); + } else { + await InspectionPointCommandHandler.sync(inspectionPlan.latestVersionedPlan.id, inspectionPoints); + } + + res.sendStatus(204); +} + +/** + * @description delete inspectionPlan by id + * @param req {Request} Express req object + * @param res {Response} Express res object + * @returns {Promise<*>} + */ +export async function deleteInspectionPlanById(req: Request, res: Response): Promise { + const inspectionPlanId = req.params.id; + + let deleteInspectionPlan: DeleteInspectionPlanCommand = { + id: inspectionPlanId, + }; + await InspectionPlanCommandHandler.delete(deleteInspectionPlan); + + res.sendStatus(204); +} diff --git a/src/controller/admin/unit/maintenanceController.ts b/src/controller/admin/unit/maintenanceController.ts new file mode 100644 index 0000000..558a8c2 --- /dev/null +++ b/src/controller/admin/unit/maintenanceController.ts @@ -0,0 +1,119 @@ +import { Request, Response } from "express"; +import MaintenanceService from "../../../service/unit/maintenanceService"; +import MaintenanceFactory from "../../../factory/admin/unit/maintenance"; +import { CreateMaintenanceCommand, UpdateMaintenanceCommand } from "../../../command/unit/maintenanceCommand"; +import MaintenanceCommandHandler from "../../../command/unit/maintenanceCommandHandler"; +import BadRequestException from "../../../exceptions/badRequestException"; + +/** + * @description get all maintenances by status + * @param req {Request} Express req object + * @param res {Response} Express res object + * @returns {Promise<*>} + */ +export async function getAllMaintenancesByStatus(req: Request, res: Response): Promise { + let done = req.query.done === "true"; + let offset = parseInt((req.query.offset as string) ?? "0"); + let count = parseInt((req.query.count as string) ?? "25"); + let noLimit = req.query.noLimit === "true"; + + let [maintenances, total] = await MaintenanceService.getAllByDone(done, { offset, count, noLimit }); + + res.json({ + maintenances: MaintenanceFactory.mapToBase(maintenances), + total: total, + offset: offset, + count: count, + }); +} + +/** + * @description get all maintenances for related id + * @param req {Request} Express req object + * @param res {Response} Express res object + * @returns {Promise<*>} + */ +export async function getAllMaintenancesForRelated(req: Request, res: Response): Promise { + let relation = req.params.related as "vehicle" | "equipment" | "wearable"; + let relationId = req.params.relatedId as string; + let offset = parseInt((req.query.offset as string) ?? "0"); + let count = parseInt((req.query.count as string) ?? "25"); + let noLimit = req.query.noLimit === "true"; + + let where; + if (relation == "equipment") { + where = { equipmentId: relationId }; + } else if (relation == "vehicle") { + where = { vehicleId: relationId }; + } else { + where = { wearableId: relationId }; + } + let [maintenances, total] = await MaintenanceService.getAllForRelated(where, { offset, count, noLimit }); + + res.json({ + maintenances: MaintenanceFactory.mapToBase(maintenances), + total: total, + offset: offset, + count: count, + }); +} + +/** + * @description get maintenance by id + * @param req {Request} Express req object + * @param res {Response} Express res object + * @returns {Promise<*>} + */ +export async function getMaintenanceById(req: Request, res: Response): Promise { + const maintenanceId = req.params.id; + let maintenance = await MaintenanceService.getById(maintenanceId); + + res.json(MaintenanceFactory.mapToSingle(maintenance)); +} + +/** + * @description create maintenance + * @param req {Request} Express req object + * @param res {Response} Express res object + * @returns {Promise<*>} + */ +export async function createMaintenance(req: Request, res: Response): Promise { + const description = req.body.description; + const affectedId = req.body.affectedId; + const affected = req.body.affected; + + if (affected != "equipment" && affected != "vehicle" && affected != "wearable") + throw new BadRequestException("set assigned to equipment or vehicle or wearable"); + + let createMaintenance: CreateMaintenanceCommand = { + description, + affectedId, + affected, + }; + let maintenanceId = await MaintenanceCommandHandler.create(createMaintenance); + + res.status(200).send(maintenanceId); +} + +/** + * @description update maintenance by id + * @param req {Request} Express req object + * @param res {Response} Express res object + * @returns {Promise<*>} + */ +export async function updateMaintenanceById(req: Request, res: Response): Promise { + const maintenanceId = req.params.id; + const description = req.body.description; + const status = req.body.status; + const done = req.body.done; + + let updateMaintenance: UpdateMaintenanceCommand = { + id: maintenanceId, + description, + status, + done, + }; + await MaintenanceCommandHandler.update(updateMaintenance); + + res.sendStatus(204); +} diff --git a/src/controller/admin/unit/repairController.ts b/src/controller/admin/unit/repairController.ts new file mode 100644 index 0000000..e3a80cc --- /dev/null +++ b/src/controller/admin/unit/repairController.ts @@ -0,0 +1,173 @@ +import { Request, Response } from "express"; +import RepairService from "../../../service/unit/repairService"; +import RepairFactory from "../../../factory/admin/unit/repair"; +import { + CreateRepairCommand, + UpdateRepairCommand, + UpdateRepairStatusCommand, +} from "../../../command/unit/repairCommand"; +import RepairCommandHandler from "../../../command/unit/repairCommandHandler"; +import BadRequestException from "../../../exceptions/badRequestException"; +import { FileSystemHelper } from "../../../helpers/fileSystemHelper"; +import { UpdateDamageReportRelatedRepairCommand } from "../../../command/unit/damageReportCommand"; +import DamageReportCommandHandler from "../../../command/unit/damageReportCommandHandler"; + +/** + * @description get all repairs by status + * @param req {Request} Express req object + * @param res {Response} Express res object + * @returns {Promise<*>} + */ +export async function getAllRepairsByStatus(req: Request, res: Response): Promise { + let done = req.query.done === "true"; + let offset = parseInt((req.query.offset as string) ?? "0"); + let count = parseInt((req.query.count as string) ?? "25"); + let noLimit = req.query.noLimit === "true"; + + let [repairs, total] = await RepairService.getAllByDone(done, { offset, count, noLimit }); + + res.json({ + repairs: RepairFactory.mapToBase(repairs), + total: total, + offset: offset, + count: count, + }); +} + +/** + * @description get all repairs for related id + * @param req {Request} Express req object + * @param res {Response} Express res object + * @returns {Promise<*>} + */ +export async function getAllRepairsForRelated(req: Request, res: Response): Promise { + let relation = req.params.related as "vehicle" | "equipment" | "wearable"; + let relationId = req.params.relatedId as string; + let offset = parseInt((req.query.offset as string) ?? "0"); + let count = parseInt((req.query.count as string) ?? "25"); + let noLimit = req.query.noLimit === "true"; + + let where; + if (relation == "equipment") { + where = { equipmentId: relationId }; + } else if (relation == "vehicle") { + where = { vehicleId: relationId }; + } else { + where = { wearableId: relationId }; + } + let [repairs, total] = await RepairService.getAllForRelated(where, { offset, count, noLimit }); + + res.json({ + repairs: RepairFactory.mapToBase(repairs), + total: total, + offset: offset, + count: count, + }); +} + +/** + * @description get repair by id + * @param req {Request} Express req object + * @param res {Response} Express res object + * @returns {Promise<*>} + */ +export async function getRepairById(req: Request, res: Response): Promise { + const repairId = req.params.id; + let repair = await RepairService.getById(repairId); + + res.json(RepairFactory.mapToSingle(repair)); +} + +/** + * @description create repair + * @param req {Request} Express req object + * @param res {Response} Express res object + * @returns {Promise<*>} + */ +export async function createRepair(req: Request, res: Response): Promise { + const title = req.body.title; + const description = req.body.description; + const responsible = req.body.responsible; + const reports = req.body.reports; + const affectedId = req.body.affectedId; + const affected = req.body.affected; + + if (affected != "equipment" && affected != "vehicle" && affected != "wearable") + throw new BadRequestException("set assigned to equipment or vehicle or wearable"); + + let createRepair: CreateRepairCommand = { + title, + description, + affectedId, + affected, + responsible, + reports, + }; + let repairId = await RepairCommandHandler.create(createRepair); + + res.status(200).send(repairId); +} + +/** + * @description update repair by id + * @param req {Request} Express req object + * @param res {Response} Express res object + * @returns {Promise<*>} + */ +export async function updateRepairById(req: Request, res: Response): Promise { + const repairId = req.params.id; + const title = req.body.title; + const description = req.body.description; + const responsible = req.body.responsible; + + let updateRepair: UpdateRepairCommand = { + id: repairId, + title, + description, + responsible, + }; + await RepairCommandHandler.update(updateRepair); + + res.sendStatus(204); +} + +/** + * @description update repair by id + * @param req {Request} Express req object + * @param res {Response} Express res object + * @returns {Promise<*>} + */ +export async function updateRepairReportsById(req: Request, res: Response): Promise { + const repairId = req.params.id; + const reports = req.body.reports as Array; + + await DamageReportCommandHandler.updateRelatedMaintenanceMulti(repairId, reports); + + res.sendStatus(204); +} + +/** + * @description update repair by id + * @param req {Request} Express req object + * @param res {Response} Express res object + * @returns {Promise<*>} + */ +export async function updateRepairStatusById(req: Request, res: Response): Promise { + const repairId = req.params.id; + const status = req.body.status; + const done = req.body.done; + + let updateRepair: UpdateRepairStatusCommand = { + id: repairId, + status, + done, + user: { + id: req.userId, + firstname: req.firstname, + lastname: req.lastname, + }, + }; + await RepairCommandHandler.updateStatus(updateRepair); + + res.sendStatus(204); +} diff --git a/src/controller/admin/unit/vehicleController.ts b/src/controller/admin/unit/vehicleController.ts new file mode 100644 index 0000000..a54d2c1 --- /dev/null +++ b/src/controller/admin/unit/vehicleController.ts @@ -0,0 +1,133 @@ +import { Request, Response } from "express"; +import VehicleService from "../../../service/unit/vehicle/vehicleService"; +import VehicleFactory from "../../../factory/admin/unit/vehicle/vehicle"; +import { + CreateVehicleCommand, + DeleteVehicleCommand, + UpdateVehicleCommand, +} from "../../../command/unit/vehicle/vehicleCommand"; +import VehicleCommandHandler from "../../../command/unit/vehicle/vehicleCommandHandler"; + +/** + * @description get all vehicles + * @param req {Request} Express req object + * @param res {Response} Express res object + * @returns {Promise<*>} + */ +export async function getAllVehicles(req: Request, res: Response): Promise { + let offset = parseInt((req.query.offset as string) ?? "0"); + let count = parseInt((req.query.count as string) ?? "25"); + let search = (req.query.search as string) ?? ""; + let noLimit = req.query.noLimit === "true"; + let ids = ((req.query.ids ?? "") as string).split(",").filter((i) => i); + + let [vehicles, total] = await VehicleService.getAll({ offset, count, search, noLimit, ids }); + + res.json({ + vehicles: VehicleFactory.mapToBase(vehicles), + total: total, + offset: offset, + count: count, + }); +} + +/** + * @description get vehicle by id + * @param req {Request} Express req object + * @param res {Response} Express res object + * @returns {Promise<*>} + */ +export async function getVehicleById(req: Request, res: Response): Promise { + const vehicleId = req.params.id; + let vehicle = await VehicleService.getById(vehicleId); + + res.json(VehicleFactory.mapToSingle(vehicle)); +} + +/** + * @description get vehicle by Ids + * @param req {Request} Express req object + * @param res {Response} Express res object + * @returns {Promise<*>} + */ +export async function getVehiclesByIds(req: Request, res: Response): Promise { + let ids = req.body.ids as Array; + + let [vehicles, total] = await VehicleService.getAll({ noLimit: true, ids }); + + res.json({ + vehicles: VehicleFactory.mapToBase(vehicles), + total: total, + offset: 0, + count: total, + }); +} + +/** + * @description create vehicle + * @param req {Request} Express req object + * @param res {Response} Express res object + * @returns {Promise<*>} + */ +export async function createVehicle(req: Request, res: Response): Promise { + const name = req.body.name; + const code = req.body.code || null; + const location = req.body.location; + const commissioned = req.body.commissioned; + const vehicleTypeId = req.body.vehicleTypeId; + + let createVehicle: CreateVehicleCommand = { + code, + name, + location, + commissioned, + vehicleTypeId, + }; + let vehicleId = await VehicleCommandHandler.create(createVehicle); + + res.status(200).send(vehicleId); +} + +/** + * @description update vehicle by id + * @param req {Request} Express req object + * @param res {Response} Express res object + * @returns {Promise<*>} + */ +export async function updateVehicleById(req: Request, res: Response): Promise { + const vehicleId = req.params.id; + const name = req.body.name; + const code = req.body.code || null; + const location = req.body.location; + const commissioned = req.body.commissioned; + const decommissioned = req.body.decommissioned || null; + + let updateVehicle: UpdateVehicleCommand = { + id: vehicleId, + code, + name, + location, + commissioned, + decommissioned, + }; + await VehicleCommandHandler.update(updateVehicle); + + res.sendStatus(204); +} + +/** + * @description delete vehicle by id + * @param req {Request} Express req object + * @param res {Response} Express res object + * @returns {Promise<*>} + */ +export async function deleteVehicleById(req: Request, res: Response): Promise { + const vehicleId = req.params.id; + + let deleteVehicle: DeleteVehicleCommand = { + id: vehicleId, + }; + await VehicleCommandHandler.delete(deleteVehicle); + + res.sendStatus(204); +} diff --git a/src/controller/admin/unit/vehicleTypeController.ts b/src/controller/admin/unit/vehicleTypeController.ts new file mode 100644 index 0000000..b105ea1 --- /dev/null +++ b/src/controller/admin/unit/vehicleTypeController.ts @@ -0,0 +1,101 @@ +import { Request, Response } from "express"; +import VehicleTypeService from "../../../service/unit/vehicle/vehicleTypeService"; +import VehicleTypeFactory from "../../../factory/admin/unit/vehicle/vehicleType"; +import { + CreateVehicleTypeCommand, + DeleteVehicleTypeCommand, + UpdateVehicleTypeCommand, +} from "../../../command/unit/vehicle/vehicleTypeCommand"; +import VehicleTypeCommandHandler from "../../../command/unit/vehicle/vehicleTypeCommandHandler"; + +/** + * @description get all vehicleTypes + * @param req {Request} Express req object + * @param res {Response} Express res object + * @returns {Promise<*>} + */ +export async function getAllVehicleTypes(req: Request, res: Response): Promise { + let offset = parseInt((req.query.offset as string) ?? "0"); + let count = parseInt((req.query.count as string) ?? "25"); + let search = (req.query.search as string) ?? ""; + let noLimit = req.query.noLimit === "true"; + + let [vehicleTypes, total] = await VehicleTypeService.getAll({ offset, count, search, noLimit }); + + res.json({ + vehicleTypes: VehicleTypeFactory.mapToBase(vehicleTypes), + total: total, + offset: offset, + count: count, + }); +} + +/** + * @description get vehicleType by id + * @param req {Request} Express req object + * @param res {Response} Express res object + * @returns {Promise<*>} + */ +export async function getVehicleTypeById(req: Request, res: Response): Promise { + const vehicleTypeId = req.params.id; + let vehicleType = await VehicleTypeService.getById(vehicleTypeId); + + res.json(VehicleTypeFactory.mapToSingle(vehicleType)); +} + +/** + * @description create vehicleType + * @param req {Request} Express req object + * @param res {Response} Express res object + * @returns {Promise<*>} + */ +export async function createVehicleType(req: Request, res: Response): Promise { + const type = req.body.type; + const description = req.body.description; + + let createVehicleType: CreateVehicleTypeCommand = { + type, + description, + }; + let vehicleTypeId = await VehicleTypeCommandHandler.create(createVehicleType); + + res.status(200).send(vehicleTypeId); +} + +/** + * @description update vehicleType by id + * @param req {Request} Express req object + * @param res {Response} Express res object + * @returns {Promise<*>} + */ +export async function updateVehicleTypeById(req: Request, res: Response): Promise { + const vehicleTypeId = req.params.id; + const type = req.body.type; + const description = req.body.description; + + let updateVehicleType: UpdateVehicleTypeCommand = { + id: vehicleTypeId, + type, + description, + }; + await VehicleTypeCommandHandler.update(updateVehicleType); + + res.sendStatus(204); +} + +/** + * @description delete vehicleType by id + * @param req {Request} Express req object + * @param res {Response} Express res object + * @returns {Promise<*>} + */ +export async function deleteVehicleTypeById(req: Request, res: Response): Promise { + const vehicleTypeId = req.params.id; + + let deleteVehicleType: DeleteVehicleTypeCommand = { + id: vehicleTypeId, + }; + await VehicleTypeCommandHandler.delete(deleteVehicleType); + + res.sendStatus(204); +} diff --git a/src/controller/admin/unit/wearableController.ts b/src/controller/admin/unit/wearableController.ts new file mode 100644 index 0000000..b28fb46 --- /dev/null +++ b/src/controller/admin/unit/wearableController.ts @@ -0,0 +1,137 @@ +import { Request, Response } from "express"; +import WearableService from "../../../service/unit/wearable/wearableService"; +import WearableFactory from "../../../factory/admin/unit/wearable/wearable"; +import { + CreateWearableCommand, + DeleteWearableCommand, + UpdateWearableCommand, +} from "../../../command/unit/wearable/wearableCommand"; +import WearableCommandHandler from "../../../command/unit/wearable/wearableCommandHandler"; + +/** + * @description get all wearables + * @param req {Request} Express req object + * @param res {Response} Express res object + * @returns {Promise<*>} + */ +export async function getAllWearables(req: Request, res: Response): Promise { + let offset = parseInt((req.query.offset as string) ?? "0"); + let count = parseInt((req.query.count as string) ?? "25"); + let search = (req.query.search as string) ?? ""; + let noLimit = req.query.noLimit === "true"; + let ids = ((req.query.ids ?? "") as string).split(",").filter((i) => i); + + let [wearables, total] = await WearableService.getAll({ offset, count, search, noLimit, ids }); + + res.json({ + wearables: WearableFactory.mapToBase(wearables), + total: total, + offset: offset, + count: count, + }); +} + +/** + * @description get wearable by id + * @param req {Request} Express req object + * @param res {Response} Express res object + * @returns {Promise<*>} + */ +export async function getWearableById(req: Request, res: Response): Promise { + const wearableId = req.params.id; + let wearable = await WearableService.getById(wearableId); + + res.json(WearableFactory.mapToSingle(wearable)); +} + +/** + * @description get wearable by Ids + * @param req {Request} Express req object + * @param res {Response} Express res object + * @returns {Promise<*>} + */ +export async function getWearablesByIds(req: Request, res: Response): Promise { + let ids = req.body.ids as Array; + + let [wearables, total] = await WearableService.getAll({ noLimit: true, ids }); + + res.json({ + wearables: WearableFactory.mapToBase(wearables), + total: total, + offset: 0, + count: total, + }); +} + +/** + * @description create wearable + * @param req {Request} Express req object + * @param res {Response} Express res object + * @returns {Promise<*>} + */ +export async function createWearable(req: Request, res: Response): Promise { + const name = req.body.name; + const code = req.body.code || null; + const location = req.body.location; + const commissioned = req.body.commissioned; + const wearableTypeId = req.body.wearableTypeId; + const wearerId = req.body.wearerId || null; + + let createWearable: CreateWearableCommand = { + code, + name, + location, + commissioned, + wearableTypeId, + wearerId, + }; + let wearableId = await WearableCommandHandler.create(createWearable); + + res.status(200).send(wearableId); +} + +/** + * @description update wearable by id + * @param req {Request} Express req object + * @param res {Response} Express res object + * @returns {Promise<*>} + */ +export async function updateWearableById(req: Request, res: Response): Promise { + const wearableId = req.params.id; + const name = req.body.name; + const code = req.body.code || null; + const location = req.body.location; + const commissioned = req.body.commissioned; + const decommissioned = req.body.decommissioned || null; + const wearerId = req.body.wearerId || null; + + let updateWearable: UpdateWearableCommand = { + id: wearableId, + code, + name, + location, + commissioned, + decommissioned, + wearerId, + }; + await WearableCommandHandler.update(updateWearable); + + res.sendStatus(204); +} + +/** + * @description delete wearable by id + * @param req {Request} Express req object + * @param res {Response} Express res object + * @returns {Promise<*>} + */ +export async function deleteWearableById(req: Request, res: Response): Promise { + const wearableId = req.params.id; + + let deleteWearable: DeleteWearableCommand = { + id: wearableId, + }; + await WearableCommandHandler.delete(deleteWearable); + + res.sendStatus(204); +} diff --git a/src/controller/admin/unit/wearableTypeController.ts b/src/controller/admin/unit/wearableTypeController.ts new file mode 100644 index 0000000..8597916 --- /dev/null +++ b/src/controller/admin/unit/wearableTypeController.ts @@ -0,0 +1,101 @@ +import { Request, Response } from "express"; +import WearableTypeService from "../../../service/unit/wearable/wearableTypeService"; +import WearableTypeFactory from "../../../factory/admin/unit/wearable/wearableType"; +import { + CreateWearableTypeCommand, + DeleteWearableTypeCommand, + UpdateWearableTypeCommand, +} from "../../../command/unit/wearable/wearableTypeCommand"; +import WearableTypeCommandHandler from "../../../command/unit/wearable/wearableTypeCommandHandler"; + +/** + * @description get all wearableTypes + * @param req {Request} Express req object + * @param res {Response} Express res object + * @returns {Promise<*>} + */ +export async function getAllWearableTypes(req: Request, res: Response): Promise { + let offset = parseInt((req.query.offset as string) ?? "0"); + let count = parseInt((req.query.count as string) ?? "25"); + let search = (req.query.search as string) ?? ""; + let noLimit = req.query.noLimit === "true"; + + let [wearableTypes, total] = await WearableTypeService.getAll({ offset, count, search, noLimit }); + + res.json({ + wearableTypes: WearableTypeFactory.mapToBase(wearableTypes), + total: total, + offset: offset, + count: count, + }); +} + +/** + * @description get wearableType by id + * @param req {Request} Express req object + * @param res {Response} Express res object + * @returns {Promise<*>} + */ +export async function getWearableTypeById(req: Request, res: Response): Promise { + const wearableTypeId = req.params.id; + let wearableType = await WearableTypeService.getById(wearableTypeId); + + res.json(WearableTypeFactory.mapToSingle(wearableType)); +} + +/** + * @description create wearableType + * @param req {Request} Express req object + * @param res {Response} Express res object + * @returns {Promise<*>} + */ +export async function createWearableType(req: Request, res: Response): Promise { + const type = req.body.type; + const description = req.body.description; + + let createWearableType: CreateWearableTypeCommand = { + type, + description, + }; + let wearableTypeId = await WearableTypeCommandHandler.create(createWearableType); + + res.status(200).send(wearableTypeId); +} + +/** + * @description update wearableType by id + * @param req {Request} Express req object + * @param res {Response} Express res object + * @returns {Promise<*>} + */ +export async function updateWearableTypeById(req: Request, res: Response): Promise { + const wearableTypeId = req.params.id; + const type = req.body.type; + const description = req.body.description; + + let updateWearableType: UpdateWearableTypeCommand = { + id: wearableTypeId, + type, + description, + }; + await WearableTypeCommandHandler.update(updateWearableType); + + res.sendStatus(204); +} + +/** + * @description delete wearableType by id + * @param req {Request} Express req object + * @param res {Response} Express res object + * @returns {Promise<*>} + */ +export async function deleteWearableTypeById(req: Request, res: Response): Promise { + const wearableTypeId = req.params.id; + + let deleteWearableType: DeleteWearableTypeCommand = { + id: wearableTypeId, + }; + await WearableTypeCommandHandler.delete(deleteWearableType); + + res.sendStatus(204); +} diff --git a/src/controller/publicController.ts b/src/controller/publicController.ts index e48c008..8eba6f9 100644 --- a/src/controller/publicController.ts +++ b/src/controller/publicController.ts @@ -9,6 +9,20 @@ import SettingHelper from "../helpers/settingsHelper"; import sharp from "sharp"; import ico from "sharp-ico"; import { FileSystemHelper } from "../helpers/fileSystemHelper"; +import { SocketConnectionTypes } from "../enums/socketEnum"; +import SocketServer from "../websocket"; +import BadRequestException from "../exceptions/badRequestException"; +import EquipmentService from "../service/unit/equipment/equipmentService"; +import VehicleService from "../service/unit/vehicle/vehicleService"; +import WearableService from "../service/unit/wearable/wearableService"; +import EquipmentFactory from "../factory/admin/unit/equipment/equipment"; +import VehicleFactory from "../factory/admin/unit/vehicle/vehicle"; +import WearableFactory from "../factory/admin/unit/wearable/wearable"; +import { MinifiedEquipmentViewModel } from "../viewmodel/admin/unit/equipment/equipment.models"; +import { MinifiedVehicleViewModel } from "../viewmodel/admin/unit/vehicle/vehicle.models"; +import { MinifiedWearableViewModel } from "../viewmodel/admin/unit/wearable/wearable.models"; +import DamageReportCommandHandler from "../command/unit/damageReportCommandHandler"; +import { CreateDamageReportCommand } from "../command/unit/damageReportCommand"; /** * @description get all calendar items by types or nscdr @@ -54,6 +68,80 @@ export async function getCalendarItemsByTypes(req: Request, res: Response): Prom } } +/** + * @description check if scanner session exists + * @summary existance is checked by room exists + * @param req {Request} Express req object + * @param res {Response} Express res object + * @returns {Promise<*>} + */ +export async function checkScannerRoomExists(req: Request, res: Response): Promise { + let roomId = req.body.roomId; + + const socketsInOtherRoom = await SocketServer.server.of(SocketConnectionTypes.scanner).in(roomId).fetchSockets(); + const count = socketsInOtherRoom.length; + + if (count != 0) { + res.sendStatus(204); + } else { + throw new BadRequestException("room does not exists"); + } +} + +/** + * @description get equipment, vehicle, wearable by code + * @param req {Request} Express req object + * @param res {Response} Express res object + * @returns {Promise<*>} + */ +export async function searchStuffByCode(req: Request, res: Response): Promise { + let code = req.query.code.toString(); + + let e = await EquipmentService.getAllByCode(code); + let v = await VehicleService.getAllByCode(code); + let w = await WearableService.getAllByCode(code); + + res.json([ + ...EquipmentFactory.mapToBaseMinifed(e), + ...VehicleFactory.mapToBaseMinifed(v), + ...WearableFactory.mapToBaseMinifed(w), + ]); +} + +/** + * @description create damagereport to equipment, vehicle, wearable + * @param req {Request} Express req object + * @param res {Response} Express res object + * @returns {Promise<*>} + */ +export async function createDamageReport(req: Request, res: Response): Promise { + const related = (req.body.related ? JSON.parse(req.body.related) : undefined) as + | undefined + | MinifiedEquipmentViewModel + | MinifiedVehicleViewModel + | MinifiedWearableViewModel; + const title = req.body.title; + const description = req.body.description; + const location = req.body.location; + const note = req.body.note; + const reportedBy = req.body.reportedBy; + const images = req.files as Express.Multer.File[]; + + let createDamageReport: CreateDamageReportCommand = { + title: title, + description: description, + location: location, + noteByReporter: note, + reportedBy: reportedBy, + images: images.map((i) => i.filename), + affectedId: related ? related.id : undefined, + affected: related ? related.assigned : undefined, + }; + await DamageReportCommandHandler.create(createDamageReport); + + res.sendStatus(204); +} + /** * @description get configuration of UI * @param req {Request} Express req object @@ -68,6 +156,7 @@ export async function getApplicationConfig(req: Request, res: Response): Promise "club.website": SettingHelper.getSetting("club.website"), "app.custom_login_message": SettingHelper.getSetting("app.custom_login_message"), "app.show_link_to_calendar": SettingHelper.getSetting("app.show_link_to_calendar"), + "app.show_link_to_damagereport": SettingHelper.getSetting("app.show_link_to_damagereport"), }; res.json(config); diff --git a/src/data-source.ts b/src/data-source.ts index 74399a3..3f337f1 100644 --- a/src/data-source.ts +++ b/src/data-source.ts @@ -20,6 +20,7 @@ import { member } from "./entity/club/member/member"; import { memberAwards } from "./entity/club/member/memberAwards"; import { memberExecutivePositions } from "./entity/club/member/memberExecutivePositions"; import { memberQualifications } from "./entity/club/member/memberQualifications"; +import { memberEducations } from "./entity/club/member/memberEducations"; import { membership } from "./entity/club/member/membership"; import { protocol } from "./entity/club/protocol/protocol"; import { protocolAgenda } from "./entity/club/protocol/protocolAgenda"; @@ -46,11 +47,26 @@ import { webapiPermission } from "./entity/management/webapi_permission"; import { salutation } from "./entity/configuration/salutation"; import { setting } from "./entity/management/setting"; import { education } from "./entity/configuration/education"; -import { memberEducations } from "./entity/club/member/memberEducations"; + +import { equipmentType } from "./entity/unit/equipment/equipmentType"; +import { equipment } from "./entity/unit/equipment/equipment"; +import { vehicleType } from "./entity/unit/vehicle/vehicleType"; +import { vehicle } from "./entity/unit/vehicle/vehicle"; +import { wearableType } from "./entity/unit/wearable/wearableType"; +import { wearable } from "./entity/unit/wearable/wearable"; +import { damageReport } from "./entity/unit/damageReport"; +import { inspectionPlan } from "./entity/unit/inspection/inspectionPlan"; +import { inspectionVersionedPlan } from "./entity/unit/inspection/inspectionVersionedPlan"; +import { inspectionPoint } from "./entity/unit/inspection/inspectionPoint"; +import { inspection } from "./entity/unit/inspection/inspection"; +import { inspectionPointResult } from "./entity/unit/inspection/inspectionPointResult"; +import { maintenance } from "./entity/unit/maintenance"; import { BackupAndResetDatabase1749296262915 } from "./migrations/1749296262915-BackupAndResetDatabase"; import { CreateSchema1749296280721 } from "./migrations/1749296280721-CreateSchema"; import { UpdateNewsletterQueryRelation1752502069178 } from "./migrations/1752502069178-updateNewsletterQueryRelation"; +import { UnitBase1752914551204 } from "./migrations/1752914551204-UnitBase"; +import { repair } from "./entity/unit/repair"; configCheck(); @@ -108,10 +124,25 @@ const dataSource = new DataSource({ webapi, webapiPermission, setting, + equipmentType, + equipment, + vehicleType, + vehicle, + wearableType, + wearable, + damageReport, + maintenance, + repair, + inspectionPlan, + inspectionVersionedPlan, + inspectionPoint, + inspection, + inspectionPointResult, ], migrations: [ BackupAndResetDatabase1749296262915, CreateSchema1749296280721, + UnitBase1752914551204, UpdateNewsletterQueryRelation1752502069178, ], migrationsRun: true, diff --git a/src/entity/unit/damageReport.ts b/src/entity/unit/damageReport.ts new file mode 100644 index 0000000..6109343 --- /dev/null +++ b/src/entity/unit/damageReport.ts @@ -0,0 +1,108 @@ +import { Column, ColumnType, CreateDateColumn, Entity, ManyToOne, PrimaryGeneratedColumn } from "typeorm"; +import { equipment } from "./equipment/equipment"; +import { wearable } from "./wearable/wearable"; +import { vehicle } from "./vehicle/vehicle"; +import { repair } from "./repair"; +import { getTypeByORM } from "../../migrations/ormHelper"; +import { user } from "../management/user"; + +@Entity() +export class damageReport { + @PrimaryGeneratedColumn("uuid") + id: string; + + @CreateDateColumn() + reportedAt: Date; + + @Column({ type: getTypeByORM("datetime").type as ColumnType, nullable: true, default: null }) + closedAt?: Date; + + @Column({ nullable: true, default: null }) + closedById?: string; + + @Column({ type: "varchar", length: 255, nullable: true, default: null }) + closedByString?: string; + + @Column({ type: "varchar", length: 255 }) + status: string; + + @Column({ type: "varchar", length: 255 }) + title: string; + + @Column({ type: "text" }) + description: string; + + @Column({ type: "text", nullable: true, default: null }) + location: string; + + @Column({ type: "varchar", length: 255, nullable: true, default: null }) + reportedBy: string; + + @Column({ type: "text", nullable: true, default: null }) + noteByReporter: string; + + @Column({ type: "text", nullable: true, default: null }) + noteByWorker: string; + + @Column({ + type: "text", + nullable: true, + default: null, + transformer: { + from(value: string): Array { + return (value ?? "").split(",").filter((i) => !!i); + }, + to(value: Array = []): string { + return value.join(","); + }, + }, + }) + images: string[]; + + @Column({ nullable: true, default: null }) + equipmentId?: string; + + @Column({ nullable: true, default: null }) + vehicleId?: string; + + @Column({ nullable: true, default: null }) + wearableId?: string; + + @Column({ nullable: true, default: null }) + repairId?: string; + + @ManyToOne(() => user, { + nullable: true, + onDelete: "SET NULL", + onUpdate: "RESTRICT", + }) + closedBy?: user; + + @ManyToOne(() => equipment, (e) => e.reports, { + nullable: true, + onDelete: "CASCADE", + onUpdate: "RESTRICT", + }) + equipment?: equipment; + + @ManyToOne(() => vehicle, (v) => v.reports, { + nullable: true, + onDelete: "CASCADE", + onUpdate: "RESTRICT", + }) + vehicle?: vehicle; + + @ManyToOne(() => wearable, (w) => w.reports, { + nullable: true, + onDelete: "CASCADE", + onUpdate: "RESTRICT", + }) + wearable?: wearable; + + @ManyToOne(() => repair, (m) => m.reports, { + nullable: true, + onDelete: "SET NULL", + onUpdate: "RESTRICT", + }) + repair?: repair; +} diff --git a/src/entity/unit/equipment/equipment.ts b/src/entity/unit/equipment/equipment.ts new file mode 100644 index 0000000..21f5022 --- /dev/null +++ b/src/entity/unit/equipment/equipment.ts @@ -0,0 +1,50 @@ +import { Column, ColumnType, Entity, ManyToOne, OneToMany, PrimaryGeneratedColumn } from "typeorm"; +import { getTypeByORM } from "../../../migrations/ormHelper"; +import { equipmentType } from "./equipmentType"; +import { damageReport } from "../damageReport"; +import { inspection } from "../inspection/inspection"; +import { maintenance } from "../maintenance"; +import { repair } from "../repair"; + +@Entity() +export class equipment { + @PrimaryGeneratedColumn("uuid") + id: string; + + @Column({ type: "varchar", length: 255, nullable: true, default: null, unique: true }) + code?: string; + + @Column({ type: "varchar", length: 255 }) + name: string; + + @Column({ type: "varchar", length: 255 }) + location: string; + + @Column({ type: getTypeByORM("date").type as ColumnType }) + commissioned: Date; + + @Column({ type: getTypeByORM("date").type as ColumnType, nullable: true, default: null }) + decommissioned?: Date; + + @Column() + equipmentTypeId: string; + + @ManyToOne(() => equipmentType, { + nullable: false, + onDelete: "RESTRICT", + onUpdate: "RESTRICT", + }) + equipmentType: equipmentType; + + @OneToMany(() => damageReport, (d) => d.equipment, { cascade: ["insert"] }) + reports: damageReport[]; + + @OneToMany(() => repair, (d) => d.equipment, { cascade: ["insert"] }) + repairs: repair[]; + + @OneToMany(() => maintenance, (m) => m.equipment, { cascade: ["insert"] }) + maintenances: maintenance[]; + + @OneToMany(() => inspection, (i) => i.equipment, { cascade: ["insert"] }) + inspections: inspection[]; +} diff --git a/src/entity/unit/equipment/equipmentType.ts b/src/entity/unit/equipment/equipmentType.ts new file mode 100644 index 0000000..40c363b --- /dev/null +++ b/src/entity/unit/equipment/equipmentType.ts @@ -0,0 +1,23 @@ +import { Column, Entity, OneToMany, PrimaryGeneratedColumn } from "typeorm"; +import { equipment } from "./equipment"; +import { inspectionPlan } from "../inspection/inspectionPlan"; + +@Entity() +export class equipmentType { + @PrimaryGeneratedColumn("uuid") + id: string; + + @Column({ type: "varchar", length: 255, unique: true }) + type: string; + + @Column({ type: "text", nullable: true }) + description: string; + + @OneToMany(() => equipment, (e) => e.equipmentType, { cascade: ["insert"] }) + equipment: equipment[]; + + @OneToMany(() => inspectionPlan, (ip) => ip.equipmentType, { cascade: ["insert"] }) + inspectionPlans: inspectionPlan[]; + + equipmentCount: number; +} diff --git a/src/entity/unit/inspection/inspection.ts b/src/entity/unit/inspection/inspection.ts new file mode 100644 index 0000000..68b5420 --- /dev/null +++ b/src/entity/unit/inspection/inspection.ts @@ -0,0 +1,96 @@ +import { Column, ColumnType, CreateDateColumn, Entity, ManyToOne, OneToMany, PrimaryGeneratedColumn } from "typeorm"; +import { inspectionPlan } from "./inspectionPlan"; +import { inspectionVersionedPlan } from "./inspectionVersionedPlan"; +import { getTypeByORM } from "../../../migrations/ormHelper"; +import { vehicle } from "../vehicle/vehicle"; +import { equipment } from "../equipment/equipment"; +import { inspectionPointResult } from "./inspectionPointResult"; +import { wearable } from "../wearable/wearable"; +import { user } from "../../management/user"; + +@Entity() +export class inspection { + @PrimaryGeneratedColumn("uuid") + id: string; + + @Column({ type: "text" }) + context: string; + + @CreateDateColumn() + createdAt: Date; + + @Column({ type: getTypeByORM("datetime").type as ColumnType, nullable: true, default: null }) + finishedAt?: Date; + + @Column({ nullable: true, default: null }) + finishedById?: string; + + @Column({ type: "varchar", length: 255, nullable: true, default: null }) + finishedByString?: string; + + @Column({ type: getTypeByORM("date").type as ColumnType, nullable: true, default: null }) + nextInspection?: Date; + + @Column({ type: "boolean", default: false }) + hasNewer: boolean; + + @Column() + inspectionPlanId: string; + + @Column() + inspectionVersionedPlanId: string; + + @Column({ nullable: true, default: null }) + equipmentId?: string; + + @Column({ nullable: true, default: null }) + vehicleId?: string; + + @Column({ nullable: true, default: null }) + wearableId?: string; + + @ManyToOne(() => user, { + nullable: true, + onDelete: "SET NULL", + onUpdate: "RESTRICT", + }) + finishedBy?: user; + + @ManyToOne(() => inspectionPlan, { + nullable: false, + onDelete: "RESTRICT", + onUpdate: "RESTRICT", + }) + inspectionPlan: inspectionPlan; + + @ManyToOne(() => inspectionVersionedPlan, { + nullable: false, + onDelete: "RESTRICT", + onUpdate: "RESTRICT", + }) + inspectionVersionedPlan: inspectionVersionedPlan; + + @ManyToOne(() => equipment, { + nullable: true, + onDelete: "CASCADE", + onUpdate: "RESTRICT", + }) + equipment: equipment; + + @ManyToOne(() => vehicle, { + nullable: true, + onDelete: "CASCADE", + onUpdate: "RESTRICT", + }) + vehicle: vehicle; + + @ManyToOne(() => wearable, { + nullable: true, + onDelete: "CASCADE", + onUpdate: "RESTRICT", + }) + wearable: wearable; + + @OneToMany(() => inspectionPointResult, (ipr) => ipr.inspection, { cascade: ["insert"] }) + pointResults: inspectionPointResult[]; +} diff --git a/src/entity/unit/inspection/inspectionPlan.ts b/src/entity/unit/inspection/inspectionPlan.ts new file mode 100644 index 0000000..3b954a4 --- /dev/null +++ b/src/entity/unit/inspection/inspectionPlan.ts @@ -0,0 +1,61 @@ +import { Column, CreateDateColumn, Entity, ManyToOne, OneToMany, PrimaryGeneratedColumn } from "typeorm"; +import { PlanTimeDefinition } from "../../../viewmodel/admin/unit/inspection/inspectionPlan.models"; +import { inspectionVersionedPlan } from "./inspectionVersionedPlan"; +import { equipmentType } from "../equipment/equipmentType"; +import { vehicleType } from "../vehicle/vehicleType"; +import { wearableType } from "../wearable/wearableType"; + +@Entity() +export class inspectionPlan { + @PrimaryGeneratedColumn("uuid") + id: string; + + @Column({ type: "varchar", length: 255 }) + title: string; + + @Column({ type: "varchar", length: 255, nullable: true, default: null }) + inspectionInterval?: PlanTimeDefinition; + + @Column({ type: "varchar", length: 255, nullable: true, default: null }) + remindTime?: PlanTimeDefinition; + + @CreateDateColumn() + createdAt: Date; + + @Column({ nullable: true, default: null }) + equipmentTypeId?: string; + + @Column({ nullable: true, default: null }) + vehicleTypeId?: string; + + @Column({ nullable: true, default: null }) + wearableTypeId?: string; + + @ManyToOne(() => equipmentType, { + nullable: true, + onDelete: "CASCADE", + onUpdate: "RESTRICT", + }) + equipmentType?: equipmentType; + + @ManyToOne(() => vehicleType, { + nullable: true, + onDelete: "CASCADE", + onUpdate: "RESTRICT", + }) + vehicleType?: vehicleType; + + @ManyToOne(() => wearableType, { + nullable: true, + onDelete: "CASCADE", + onUpdate: "RESTRICT", + }) + wearableType?: wearableType; + + @OneToMany(() => inspectionVersionedPlan, (ivp) => ivp.inspectionPlan, { + cascade: ["insert"], + }) + versionedPlans: inspectionVersionedPlan[]; + + latestVersionedPlan?: inspectionVersionedPlan; +} diff --git a/src/entity/unit/inspection/inspectionPoint.ts b/src/entity/unit/inspection/inspectionPoint.ts new file mode 100644 index 0000000..84c0a80 --- /dev/null +++ b/src/entity/unit/inspection/inspectionPoint.ts @@ -0,0 +1,51 @@ +import { Column, Entity, ManyToOne, PrimaryGeneratedColumn, Unique } from "typeorm"; +import { InspectionPointEnum } from "../../../enums/inspectionEnum"; +import { inspectionVersionedPlan } from "./inspectionVersionedPlan"; + +@Entity() +export class inspectionPoint { + @PrimaryGeneratedColumn("uuid") + id: string; + + @Column({ type: "varchar", length: 255 }) + title: string; + + @Column({ type: "text" }) + description: string; + + @Column({ + type: "varchar", + length: 255, + transformer: { + to(value: InspectionPointEnum) { + return value.toString(); + }, + from(value: string) { + return InspectionPointEnum[value as keyof typeof InspectionPointEnum]; + }, + }, + }) + type: InspectionPointEnum; + + @Column({ type: "int", nullable: true, default: null }) + min?: number; + + @Column({ type: "int", nullable: true, default: null }) + max?: number; + + @Column({ type: "varchar", length: 255, default: null }) + others?: string; + + @Column({ type: "int", default: 0 }) + sort: number; + + @Column() + versionedPlanId: string; + + @ManyToOne(() => inspectionVersionedPlan, { + nullable: false, + onDelete: "CASCADE", + onUpdate: "RESTRICT", + }) + versionedPlan: inspectionVersionedPlan; +} diff --git a/src/entity/unit/inspection/inspectionPointResult.ts b/src/entity/unit/inspection/inspectionPointResult.ts new file mode 100644 index 0000000..4f98e74 --- /dev/null +++ b/src/entity/unit/inspection/inspectionPointResult.ts @@ -0,0 +1,29 @@ +import { Column, Entity, ManyToOne, PrimaryColumn } from "typeorm"; +import { inspection } from "./inspection"; +import { inspectionPoint } from "./inspectionPoint"; + +@Entity() +export class inspectionPointResult { + @PrimaryColumn() + inspectionId: string; + + @PrimaryColumn() + inspectionPointId: string; + + @Column({ type: "text" }) + value: string; + + @ManyToOne(() => inspection, { + nullable: false, + onDelete: "CASCADE", + onUpdate: "RESTRICT", + }) + inspection: inspection; + + @ManyToOne(() => inspectionPoint, { + nullable: false, + onDelete: "RESTRICT", + onUpdate: "RESTRICT", + }) + inspectionPoint: inspectionPoint; +} diff --git a/src/entity/unit/inspection/inspectionVersionedPlan.ts b/src/entity/unit/inspection/inspectionVersionedPlan.ts new file mode 100644 index 0000000..59d8d4a --- /dev/null +++ b/src/entity/unit/inspection/inspectionVersionedPlan.ts @@ -0,0 +1,31 @@ +import { Column, CreateDateColumn, Entity, ManyToOne, OneToMany, PrimaryGeneratedColumn, Unique } from "typeorm"; +import { inspectionPlan } from "./inspectionPlan"; +import { inspectionPoint } from "./inspectionPoint"; + +@Entity() +@Unique("unique_version", ["version", "inspectionPlanId"]) +export class inspectionVersionedPlan { + @PrimaryGeneratedColumn("uuid") + id: string; + + @Column({ type: "int", default: 0 }) + version: number; + + @CreateDateColumn() + createdAt: Date; + + @Column() + inspectionPlanId: string; + + @ManyToOne(() => inspectionPlan, { + nullable: false, + onDelete: "CASCADE", + onUpdate: "RESTRICT", + }) + inspectionPlan: inspectionPlan; + + @OneToMany(() => inspectionPoint, (ip) => ip.versionedPlan, { + cascade: ["insert"], + }) + inspectionPoints: inspectionPoint[]; +} diff --git a/src/entity/unit/maintenance.ts b/src/entity/unit/maintenance.ts new file mode 100644 index 0000000..ff1da10 --- /dev/null +++ b/src/entity/unit/maintenance.ts @@ -0,0 +1,92 @@ +import { Column, ColumnType, CreateDateColumn, Entity, ManyToOne, OneToMany, PrimaryGeneratedColumn } from "typeorm"; +import { equipment } from "./equipment/equipment"; +import { wearable } from "./wearable/wearable"; +import { vehicle } from "./vehicle/vehicle"; +import { damageReport } from "./damageReport"; +import { getTypeByORM } from "../../migrations/ormHelper"; +import { user } from "../management/user"; + +@Entity() +export class maintenance { + @PrimaryGeneratedColumn("uuid") + id: string; + + @CreateDateColumn() + createdAt: Date; + + @Column({ type: getTypeByORM("datetime").type as ColumnType, nullable: true, default: null }) + finishedAt?: Date; + + @Column({ nullable: true, default: null }) + finishedById?: string; + + @Column({ type: "varchar", length: 255, nullable: true, default: null }) + finishedByString?: string; + + @Column({ type: "varchar", length: 255 }) + status: string; + + @Column({ type: "varchar", length: 255 }) + title: string; + + @Column({ type: "text" }) + description: string; + + @Column({ type: "varchar", length: 255, nullable: true, default: null }) + responsible: string; + + @Column({ + type: "text", + nullable: true, + default: null, + transformer: { + from(value: string): Array { + return (value ?? "").split(",").filter((i) => !!i); + }, + to(value: Array = []): string { + return value.join(","); + }, + }, + }) + images: string[]; + + @Column({ type: "varchar", length: 255, nullable: true, default: null }) + reportDocument?: string; + + @Column({ nullable: true, default: null }) + equipmentId?: string; + + @Column({ nullable: true, default: null }) + vehicleId?: string; + + @Column({ nullable: true, default: null }) + wearableId?: string; + + @ManyToOne(() => user, { + nullable: true, + onDelete: "SET NULL", + onUpdate: "RESTRICT", + }) + finishedBy?: user; + + @ManyToOne(() => equipment, (e) => e.maintenances, { + nullable: true, + onDelete: "CASCADE", + onUpdate: "RESTRICT", + }) + equipment?: equipment; + + @ManyToOne(() => vehicle, (v) => v.maintenances, { + nullable: true, + onDelete: "CASCADE", + onUpdate: "RESTRICT", + }) + vehicle?: vehicle; + + @ManyToOne(() => wearable, (w) => w.maintenances, { + nullable: true, + onDelete: "CASCADE", + onUpdate: "RESTRICT", + }) + wearable?: wearable; +} diff --git a/src/entity/unit/repair.ts b/src/entity/unit/repair.ts new file mode 100644 index 0000000..c79f448 --- /dev/null +++ b/src/entity/unit/repair.ts @@ -0,0 +1,95 @@ +import { Column, ColumnType, CreateDateColumn, Entity, ManyToOne, OneToMany, PrimaryGeneratedColumn } from "typeorm"; +import { equipment } from "./equipment/equipment"; +import { wearable } from "./wearable/wearable"; +import { vehicle } from "./vehicle/vehicle"; +import { damageReport } from "./damageReport"; +import { getTypeByORM } from "../../migrations/ormHelper"; +import { user } from "../management/user"; + +@Entity() +export class repair { + @PrimaryGeneratedColumn("uuid") + id: string; + + @CreateDateColumn() + createdAt: Date; + + @Column({ type: getTypeByORM("datetime").type as ColumnType, nullable: true, default: null }) + finishedAt?: Date; + + @Column({ nullable: true, default: null }) + finishedById?: string; + + @Column({ type: "varchar", length: 255, nullable: true, default: null }) + finishedByString?: string; + + @Column({ type: "varchar", length: 255 }) + status: string; + + @Column({ type: "varchar", length: 255 }) + title: string; + + @Column({ type: "text" }) + description: string; + + @Column({ type: "varchar", length: 255, nullable: true, default: null }) + responsible: string; + + @Column({ + type: "text", + nullable: true, + default: null, + transformer: { + from(value: string): Array { + return (value ?? "").split(",").filter((i) => !!i); + }, + to(value: Array = []): string { + return value.join(","); + }, + }, + }) + images: string[]; + + @Column({ type: "varchar", length: 255, nullable: true, default: null }) + reportDocument?: string; + + @Column({ nullable: true, default: null }) + equipmentId?: string; + + @Column({ nullable: true, default: null }) + vehicleId?: string; + + @Column({ nullable: true, default: null }) + wearableId?: string; + + @ManyToOne(() => user, { + nullable: true, + onDelete: "SET NULL", + onUpdate: "RESTRICT", + }) + finishedBy?: user; + + @ManyToOne(() => equipment, (e) => e.maintenances, { + nullable: true, + onDelete: "CASCADE", + onUpdate: "RESTRICT", + }) + equipment?: equipment; + + @ManyToOne(() => vehicle, (v) => v.maintenances, { + nullable: true, + onDelete: "CASCADE", + onUpdate: "RESTRICT", + }) + vehicle?: vehicle; + + @ManyToOne(() => wearable, (w) => w.maintenances, { + nullable: true, + onDelete: "CASCADE", + onUpdate: "RESTRICT", + }) + wearable?: wearable; + + @OneToMany(() => damageReport, (dr) => dr.repair) + reports: damageReport[]; +} diff --git a/src/entity/unit/vehicle/vehicle.ts b/src/entity/unit/vehicle/vehicle.ts new file mode 100644 index 0000000..8102bf0 --- /dev/null +++ b/src/entity/unit/vehicle/vehicle.ts @@ -0,0 +1,50 @@ +import { Column, ColumnType, Entity, ManyToOne, OneToMany, PrimaryGeneratedColumn } from "typeorm"; +import { getTypeByORM } from "../../../migrations/ormHelper"; +import { vehicleType } from "./vehicleType"; +import { damageReport } from "../damageReport"; +import { inspection } from "../inspection/inspection"; +import { maintenance } from "../maintenance"; +import { repair } from "../repair"; + +@Entity() +export class vehicle { + @PrimaryGeneratedColumn("uuid") + id: string; + + @Column({ type: "varchar", length: 255, nullable: true, default: null, unique: true }) + code?: string; + + @Column({ type: "varchar", length: 255 }) + name: string; + + @Column({ type: "varchar", length: 255 }) + location: string; + + @Column({ type: getTypeByORM("date").type as ColumnType }) + commissioned: Date; + + @Column({ type: getTypeByORM("date").type as ColumnType, nullable: true, default: null }) + decommissioned?: Date; + + @Column() + vehicleTypeId: string; + + @ManyToOne(() => vehicleType, { + nullable: false, + onDelete: "RESTRICT", + onUpdate: "RESTRICT", + }) + vehicleType: vehicleType; + + @OneToMany(() => damageReport, (d) => d.vehicle, { cascade: ["insert"] }) + reports: damageReport[]; + + @OneToMany(() => repair, (d) => d.vehicle, { cascade: ["insert"] }) + repairs: repair[]; + + @OneToMany(() => maintenance, (m) => m.vehicle, { cascade: ["insert"] }) + maintenances: maintenance[]; + + @OneToMany(() => inspection, (i) => i.vehicle, { cascade: ["insert"] }) + inspections: inspection[]; +} diff --git a/src/entity/unit/vehicle/vehicleType.ts b/src/entity/unit/vehicle/vehicleType.ts new file mode 100644 index 0000000..2ed071a --- /dev/null +++ b/src/entity/unit/vehicle/vehicleType.ts @@ -0,0 +1,23 @@ +import { Column, Entity, OneToMany, PrimaryGeneratedColumn } from "typeorm"; +import { vehicle } from "./vehicle"; +import { inspectionPlan } from "../inspection/inspectionPlan"; + +@Entity() +export class vehicleType { + @PrimaryGeneratedColumn("uuid") + id: string; + + @Column({ type: "varchar", length: 255, unique: true }) + type: string; + + @Column({ type: "text", nullable: true }) + description: string; + + @OneToMany(() => vehicle, (e) => e.vehicleType, { cascade: ["insert"] }) + vehicle: vehicle[]; + + @OneToMany(() => inspectionPlan, (ip) => ip.vehicleType, { cascade: ["insert"] }) + inspectionPlans: inspectionPlan[]; + + vehicleCount: number; +} diff --git a/src/entity/unit/wearable/wearable.ts b/src/entity/unit/wearable/wearable.ts new file mode 100644 index 0000000..e4ecf22 --- /dev/null +++ b/src/entity/unit/wearable/wearable.ts @@ -0,0 +1,61 @@ +import { Column, ColumnType, Entity, ManyToOne, OneToMany, PrimaryGeneratedColumn } from "typeorm"; +import { getTypeByORM } from "../../../migrations/ormHelper"; +import { wearableType } from "./wearableType"; +import { damageReport } from "../damageReport"; +import { member } from "../../club/member/member"; +import { inspection } from "../inspection/inspection"; +import { maintenance } from "../maintenance"; +import { repair } from "../repair"; + +@Entity() +export class wearable { + @PrimaryGeneratedColumn("uuid") + id: string; + + @Column({ type: "varchar", length: 255, nullable: true, default: null, unique: true }) + code?: string; + + @Column({ type: "varchar", length: 255 }) + name: string; + + @Column({ type: "varchar", length: 255 }) + location: string; + + @Column({ type: getTypeByORM("date").type as ColumnType }) + commissioned: Date; + + @Column({ type: getTypeByORM("date").type as ColumnType, nullable: true, default: null }) + decommissioned?: Date; + + @Column() + wearableTypeId: string; + + @Column({ nullable: true, default: null }) + wearerId?: string; + + @ManyToOne(() => wearableType, { + nullable: false, + onDelete: "RESTRICT", + onUpdate: "RESTRICT", + }) + wearableType: wearableType; + + @ManyToOne(() => member, { + nullable: true, + onDelete: "SET NULL", + onUpdate: "RESTRICT", + }) + wearer?: member; + + @OneToMany(() => damageReport, (d) => d.wearable, { cascade: ["insert"] }) + reports: damageReport[]; + + @OneToMany(() => repair, (d) => d.wearable, { cascade: ["insert"] }) + repairs: repair[]; + + @OneToMany(() => maintenance, (m) => m.wearable, { cascade: ["insert"] }) + maintenances: maintenance[]; + + @OneToMany(() => inspection, (i) => i.wearable, { cascade: ["insert"] }) + inspections: inspection[]; +} diff --git a/src/entity/unit/wearable/wearableType.ts b/src/entity/unit/wearable/wearableType.ts new file mode 100644 index 0000000..a7d7538 --- /dev/null +++ b/src/entity/unit/wearable/wearableType.ts @@ -0,0 +1,23 @@ +import { Column, Entity, OneToMany, PrimaryGeneratedColumn } from "typeorm"; +import { wearable as wearable } from "./wearable"; +import { inspectionPlan } from "../inspection/inspectionPlan"; + +@Entity() +export class wearableType { + @PrimaryGeneratedColumn("uuid") + id: string; + + @Column({ type: "varchar", length: 255, unique: true }) + type: string; + + @Column({ type: "text", nullable: true }) + description: string; + + @OneToMany(() => wearable, (e) => e.wearableType, { cascade: ["insert"] }) + wearable: wearable[]; + + @OneToMany(() => inspectionPlan, (ip) => ip.wearableType, { cascade: ["insert"] }) + inspectionPlans: inspectionPlan[]; + + wearableCount: number; +} diff --git a/src/enums/inspectionEnum.ts b/src/enums/inspectionEnum.ts new file mode 100644 index 0000000..58143e6 --- /dev/null +++ b/src/enums/inspectionEnum.ts @@ -0,0 +1,6 @@ +export enum InspectionPointEnum { + oknok = "oknok", + text = "text", + number = "number", + file = "file", +} diff --git a/src/enums/socketEnum.ts b/src/enums/socketEnum.ts new file mode 100644 index 0000000..4aed946 --- /dev/null +++ b/src/enums/socketEnum.ts @@ -0,0 +1,4 @@ +export enum SocketConnectionTypes { + scanner = "/scanner", + pscanner = "/public_scanner", +} diff --git a/src/factory/admin/club/member/member.ts b/src/factory/admin/club/member/member.ts index 9dd157f..a331d11 100644 --- a/src/factory/admin/club/member/member.ts +++ b/src/factory/admin/club/member/member.ts @@ -15,7 +15,7 @@ export default abstract class MemberFactory { public static mapToSingle(record: member): MemberViewModel { return { id: record?.id, - salutation: SalutationFactory.mapToSingle(record?.salutation), + salutation: record?.salutation ? SalutationFactory.mapToSingle(record?.salutation) : null, firstname: record?.firstname, lastname: record?.lastname, nameaffix: record?.nameaffix, diff --git a/src/factory/admin/unit/damageReport.ts b/src/factory/admin/unit/damageReport.ts new file mode 100644 index 0000000..f9f2e31 --- /dev/null +++ b/src/factory/admin/unit/damageReport.ts @@ -0,0 +1,69 @@ +import { damageReport } from "../../../entity/unit/damageReport"; +import { DamageReportAssigned, DamageReportViewModel } from "../../../viewmodel/admin/unit/damageReport.models"; +import EquipmentFactory from "./equipment/equipment"; +import MaintenanceFactory from "./maintenance"; +import RepairFactory from "./repair"; +import VehicleFactory from "./vehicle/vehicle"; +import WearableFactory from "./wearable/wearable"; + +export default abstract class DamageReportFactory { + /** + * @description map record to damageReport + * @param {damageReport} record + * @returns {DamageReportViewModel} + */ + public static mapToSingle(record: damageReport): DamageReportViewModel { + let assigned: DamageReportAssigned; + if (record?.equipmentId) { + assigned = { + relatedId: record.equipmentId, + assigned: "equipment", + related: record.equipment ? EquipmentFactory.mapToSingle(record.equipment) : undefined, + }; + } else if (record?.vehicleId) { + assigned = { + relatedId: record.vehicleId, + assigned: "vehicle", + related: record.vehicle ? VehicleFactory.mapToSingle(record.vehicle) : undefined, + }; + } else if (record?.wearableId) { + assigned = { + relatedId: record.wearableId, + assigned: "wearable", + related: record.wearable ? WearableFactory.mapToSingle(record.wearable) : undefined, + }; + } else { + assigned = { + relatedId: undefined, + assigned: undefined, + related: undefined, + }; + } + + return { + id: record.id, + reportedAt: record.reportedAt, + status: record.status, + closedAt: record.closedAt, + closedBy: record?.closedBy ? record.closedBy.firstname + " " + record.closedBy.lastname : record.closedByString, + title: record.title, + description: record.description, + location: record.location, + noteByReporter: record.noteByReporter, + noteByWorker: record.noteByWorker, + images: record.images, + reportedBy: record?.reportedBy, + ...assigned, + repair: record.repair ? RepairFactory.mapToSingle(record.repair) : null, + }; + } + + /** + * @description map records to damageReport + * @param {Array} records + * @returns {Array} + */ + public static mapToBase(records: Array): Array { + return records.map((r) => this.mapToSingle(r)); + } +} diff --git a/src/factory/admin/unit/equipment/equipment.ts b/src/factory/admin/unit/equipment/equipment.ts new file mode 100644 index 0000000..a35ebc1 --- /dev/null +++ b/src/factory/admin/unit/equipment/equipment.ts @@ -0,0 +1,59 @@ +import { equipment } from "../../../../entity/unit/equipment/equipment"; +import { + EquipmentViewModel, + MinifiedEquipmentViewModel, +} from "../../../../viewmodel/admin/unit/equipment/equipment.models"; +import EquipmentTypeFactory from "./equipmentType"; + +export default abstract class EquipmentFactory { + /** + * @description map record to equipment + * @param {equipment} record + * @returns {EquipmentViewModel} + */ + public static mapToSingle(record: equipment): EquipmentViewModel { + return { + id: record.id, + code: record?.code, + name: record.name, + location: record.location, + commissioned: record.commissioned, + decommissioned: record?.decommissioned, + equipmentTypeId: record?.equipmentTypeId, + equipmentType: record?.equipmentType ? EquipmentTypeFactory.mapToSingle(record.equipmentType) : null, + }; + } + + /** + * @description map records to equipment + * @param {Array} records + * @returns {Array} + */ + public static mapToBase(records: Array): Array { + return records.map((r) => this.mapToSingle(r)); + } + + /** + * @description map record to minifed equipment + * @param {equipment} record + * @returns {MinifiedEquipmentViewModel} + */ + public static mapToSingleMinified(record: equipment): MinifiedEquipmentViewModel { + return { + id: record.id, + code: record?.code, + name: record.name, + type: record?.equipmentType.type, + assigned: "equipment", + }; + } + + /** + * @description map records to minified equipment + * @param {Array} records + * @returns {Array} + */ + public static mapToBaseMinifed(records: Array): Array { + return records.map((r) => this.mapToSingleMinified(r)); + } +} diff --git a/src/factory/admin/unit/equipment/equipmentType.ts b/src/factory/admin/unit/equipment/equipmentType.ts new file mode 100644 index 0000000..6906215 --- /dev/null +++ b/src/factory/admin/unit/equipment/equipmentType.ts @@ -0,0 +1,27 @@ +import { equipmentType } from "../../../../entity/unit/equipment/equipmentType"; +import { EquipmentTypeViewModel } from "../../../../viewmodel/admin/unit/equipment/equipmentType.models"; + +export default abstract class EquipmentTypeFactory { + /** + * @description map record to equipmentType + * @param {equipmentType} record + * @returns {EquipmentTypeViewModel} + */ + public static mapToSingle(record: equipmentType): EquipmentTypeViewModel { + return { + id: record.id, + type: record.type, + description: record.description, + equipmentCount: record.equipmentCount, + }; + } + + /** + * @description map records to equipmentType + * @param {Array} records + * @returns {Array} + */ + public static mapToBase(records: Array): Array { + return records.map((r) => this.mapToSingle(r)); + } +} diff --git a/src/factory/admin/unit/inspection/inspection.ts b/src/factory/admin/unit/inspection/inspection.ts new file mode 100644 index 0000000..fc38634 --- /dev/null +++ b/src/factory/admin/unit/inspection/inspection.ts @@ -0,0 +1,145 @@ +import { inspection } from "../../../../entity/unit/inspection/inspection"; +import { + InspectionNextViewModel, + InspectionRelated, + InspectionViewModel, + MinifiedInspectionViewModel, +} from "../../../../viewmodel/admin/unit/inspection/inspection.models"; +import EquipmentFactory from "../equipment/equipment"; +import VehicleFactory from "../vehicle/vehicle"; +import WearableFactory from "../wearable/wearable"; +import InspectionPlanFactory from "./inspectionPlan"; +import InspectionPointResultFactory from "./inspectionPointResult"; +import InspectionVersionedPlanFactory from "./inspectionVersionedPlan"; + +export default abstract class InspectionFactory { + /** + * @description map record to inspection + * @param {inspection} record + * @returns {InspectionViewModel} + */ + public static mapRelated(record: inspection): InspectionRelated { + let related: InspectionRelated; + if (record?.equipmentId) { + related = { + relatedId: record.equipmentId, + assigned: "equipment", + related: record.equipment ? EquipmentFactory.mapToSingle(record.equipment) : undefined, + }; + } else if (record?.vehicleId) { + related = { + relatedId: record.vehicleId, + assigned: "vehicle", + related: record.vehicle ? VehicleFactory.mapToSingle(record.vehicle) : undefined, + }; + } else if (record?.wearableId) { + related = { + relatedId: record.wearableId, + assigned: "wearable", + related: record.wearable ? WearableFactory.mapToSingle(record.wearable) : undefined, + }; + } else { + related = { + relatedId: undefined, + assigned: undefined, + related: undefined, + }; + } + return related; + } + + /** + * @description map record to inspection + * @param {inspection} record + * @returns {InspectionViewModel} + */ + public static mapToSingle(record: inspection): InspectionViewModel { + let related = this.mapRelated(record); + + return { + id: record.id, + inspectionPlanId: record.inspectionPlanId, + inspectionPlan: InspectionPlanFactory.mapToSingle(record.inspectionPlan), + inspectionVersionedPlanId: record.inspectionVersionedPlanId, + inspectionVersionedPlan: InspectionVersionedPlanFactory.mapToSingle(record.inspectionVersionedPlan), + context: record.context, + created: record.createdAt, + finishedAt: record?.finishedAt, + finishedBy: record?.finishedBy + ? record.finishedBy.firstname + " " + record.finishedBy.lastname + : record.finishedByString, + isOpen: record?.finishedAt == undefined, + nextInspection: record?.nextInspection, + checks: InspectionPointResultFactory.mapToBase(record.pointResults), + ...related, + }; + } + + /** + * @description map records to inspection + * @param {Array} records + * @returns {Array} + */ + public static mapToBase(records: Array): Array { + return records.map((r) => this.mapToSingle(r)); + } + + /** + * @description map record to minified inspection + * @param {inspection} record + * @returns {MinifiedInspectionViewModel} + */ + public static mapToSingleMinified(record: inspection): MinifiedInspectionViewModel { + let related = this.mapRelated(record); + + return { + id: record.id, + inspectionPlanId: record.inspectionPlanId, + inspectionPlan: InspectionPlanFactory.mapToSingle(record.inspectionPlan), + context: record.context, + created: record.createdAt, + finishedAt: record?.finishedAt, + finishedBy: record?.finishedBy + ? record.finishedBy.firstname + " " + record.finishedBy.lastname + : record.finishedByString, + isOpen: record?.finishedAt == undefined, + nextInspection: record?.nextInspection, + ...related, + }; + } + + /** + * @description map records to minified inspection + * @param {Array} records + * @returns {Array} + */ + public static mapToBaseMinified(records: Array): Array { + return records.map((r) => this.mapToSingleMinified(r)); + } + + /** + * @description map record to next inspection + * @param {inspection} record + * @returns {InspectionNextViewModel} + */ + public static mapToSingleNext(record: inspection): InspectionNextViewModel { + let related = this.mapRelated(record); + + return { + id: record.id, + inspectionPlanId: record.inspectionPlanId, + inspectionPlan: InspectionPlanFactory.mapToSingle(record.inspectionPlan), + dueDate: record?.nextInspection, + ...related, + }; + } + + /** + * @description map records to next inspection + * @param {Array} records + * @returns {Array} + */ + public static mapToBaseNext(records: Array): Array { + return records.map((r) => this.mapToSingleNext(r)); + } +} diff --git a/src/factory/admin/unit/inspection/inspectionPlan.ts b/src/factory/admin/unit/inspection/inspectionPlan.ts new file mode 100644 index 0000000..0d6e231 --- /dev/null +++ b/src/factory/admin/unit/inspection/inspectionPlan.ts @@ -0,0 +1,69 @@ +import { inspectionPlan } from "../../../../entity/unit/inspection/inspectionPlan"; +import { + InspectionPlanRelated, + InspectionPlanViewModel, +} from "../../../../viewmodel/admin/unit/inspection/inspectionPlan.models"; +import EquipmentFactory from "../equipment/equipment"; +import EquipmentTypeFactory from "../equipment/equipmentType"; +import VehicleFactory from "../vehicle/vehicle"; +import VehicleTypeFactory from "../vehicle/vehicleType"; +import WearableTypeFactory from "../wearable/wearableType"; +import InspectionPointFactory from "./inspectionPoint"; + +export default abstract class InspectionPlanFactory { + /** + * @description map record to inspectionPlan + * @param {inspectionPlan} record + * @returns {InspectionPlanViewModel} + */ + public static mapToSingle(record: inspectionPlan): InspectionPlanViewModel { + let related: InspectionPlanRelated; + if (record?.equipmentTypeId) { + related = { + relatedId: record.equipmentTypeId, + assigned: "equipment", + related: record.equipmentType ? EquipmentTypeFactory.mapToSingle(record.equipmentType) : undefined, + }; + } else if (record?.vehicleTypeId) { + related = { + relatedId: record.vehicleTypeId, + assigned: "vehicle", + related: record.vehicleType ? VehicleTypeFactory.mapToSingle(record.vehicleType) : undefined, + }; + } else if (record?.wearableTypeId) { + related = { + relatedId: record.wearableTypeId, + assigned: "wearable", + related: record.wearableType ? WearableTypeFactory.mapToSingle(record.wearableType) : undefined, + }; + } else { + related = { + relatedId: undefined, + assigned: undefined, + related: undefined, + }; + } + + return { + id: record.id, + title: record.title, + inspectionInterval: record.inspectionInterval, + remindTime: record.remindTime, + version: record?.latestVersionedPlan?.version ?? 0, + created: record.createdAt, + inspectionPoints: record.latestVersionedPlan + ? InspectionPointFactory.mapToBase(record.latestVersionedPlan.inspectionPoints) + : [], + ...related, + }; + } + + /** + * @description map records to inspectionPlan + * @param {Array} records + * @returns {Array} + */ + public static mapToBase(records: Array): Array { + return records.map((r) => this.mapToSingle(r)); + } +} diff --git a/src/factory/admin/unit/inspection/inspectionPoint.ts b/src/factory/admin/unit/inspection/inspectionPoint.ts new file mode 100644 index 0000000..b494542 --- /dev/null +++ b/src/factory/admin/unit/inspection/inspectionPoint.ts @@ -0,0 +1,31 @@ +import { inspectionPoint } from "../../../../entity/unit/inspection/inspectionPoint"; +import { InspectionPointViewModel } from "../../../../viewmodel/admin/unit/inspection/inspectionPlan.models"; + +export default abstract class InspectionPointFactory { + /** + * @description map record to inspectionPoint + * @param {inspectionPoint} record + * @returns {InspectionPointViewModel} + */ + public static mapToSingle(record: inspectionPoint): InspectionPointViewModel { + return { + id: record.id, + title: record.title, + description: record.description, + type: record.type, + sort: record.sort, + min: record?.min, + max: record?.max, + others: record?.others, + }; + } + + /** + * @description map records to inspectionPoint + * @param {Array} records + * @returns {Array} + */ + public static mapToBase(records: Array): Array { + return records.map((r) => this.mapToSingle(r)); + } +} diff --git a/src/factory/admin/unit/inspection/inspectionPointResult.ts b/src/factory/admin/unit/inspection/inspectionPointResult.ts new file mode 100644 index 0000000..e210e57 --- /dev/null +++ b/src/factory/admin/unit/inspection/inspectionPointResult.ts @@ -0,0 +1,28 @@ +import { inspectionPointResult } from "../../../../entity/unit/inspection/inspectionPointResult"; +import { InspectionPointResultViewModel } from "../../../../viewmodel/admin/unit/inspection/inspection.models"; +import InspectionPointFactory from "./inspectionPoint"; + +export default abstract class InspectionPointResultFactory { + /** + * @description map record to inspectionPointResult + * @param {inspectionPointResult} record + * @returns {InspectionPointResultViewModel} + */ + public static mapToSingle(record: inspectionPointResult): InspectionPointResultViewModel { + return { + inspectionId: record.inspectionId, + inspectionPointId: record.inspectionPointId, + inspectionPoint: InspectionPointFactory.mapToSingle(record.inspectionPoint), + value: record.value, + }; + } + + /** + * @description map records to inspectionPointResult + * @param {Array} records + * @returns {Array} + */ + public static mapToBase(records: Array): Array { + return records.map((r) => this.mapToSingle(r)); + } +} diff --git a/src/factory/admin/unit/inspection/inspectionVersionedPlan.ts b/src/factory/admin/unit/inspection/inspectionVersionedPlan.ts new file mode 100644 index 0000000..c655342 --- /dev/null +++ b/src/factory/admin/unit/inspection/inspectionVersionedPlan.ts @@ -0,0 +1,28 @@ +import { inspectionVersionedPlan } from "../../../../entity/unit/inspection/inspectionVersionedPlan"; +import { InspectionVersionedPlanViewModel } from "../../../../viewmodel/admin/unit/inspection/inspectionPlan.models"; +import InspectionPointFactory from "./inspectionPoint"; + +export default abstract class InspectionVersionedPlanFactory { + /** + * @description map record to inspectionVersionedPlan + * @param {inspectionVersionedPlan} record + * @returns {InspectionVersionedPlanViewModel} + */ + public static mapToSingle(record: inspectionVersionedPlan): InspectionVersionedPlanViewModel { + return { + id: record.id, + version: record.version, + created: record.createdAt, + inspectionPoints: InspectionPointFactory.mapToBase(record.inspectionPoints), + }; + } + + /** + * @description map records to inspectionVersionedPlan + * @param {Array} records + * @returns {Array} + */ + public static mapToBase(records: Array): Array { + return records.map((r) => this.mapToSingle(r)); + } +} diff --git a/src/factory/admin/unit/maintenance.ts b/src/factory/admin/unit/maintenance.ts new file mode 100644 index 0000000..a831097 --- /dev/null +++ b/src/factory/admin/unit/maintenance.ts @@ -0,0 +1,59 @@ +import { maintenance } from "../../../entity/unit/maintenance"; +import { MaintenanceAssigned, MaintenanceViewModel } from "../../../viewmodel/admin/unit/maintenance.models"; +import DamageReportFactory from "./damageReport"; +import EquipmentFactory from "./equipment/equipment"; +import VehicleFactory from "./vehicle/vehicle"; +import WearableFactory from "./wearable/wearable"; + +export default abstract class MaintenanceFactory { + /** + * @description map record to maintenance + * @param {maintenance} record + * @returns {MaintenanceViewModel} + */ + public static mapToSingle(record: maintenance): MaintenanceViewModel { + let assigned: MaintenanceAssigned; + if (record?.equipmentId) { + assigned = { + relatedId: record.equipmentId, + assigned: "equipment", + related: record.equipment ? EquipmentFactory.mapToSingle(record.equipment) : undefined, + }; + } else if (record?.vehicleId) { + assigned = { + relatedId: record.vehicleId, + assigned: "vehicle", + related: record.vehicle ? VehicleFactory.mapToSingle(record.vehicle) : undefined, + }; + } else if (record?.wearableId) { + assigned = { + relatedId: record.wearableId, + assigned: "wearable", + related: record.wearable ? WearableFactory.mapToSingle(record.wearable) : undefined, + }; + } else { + assigned = { + relatedId: undefined, + assigned: undefined, + related: undefined, + }; + } + + return { + id: record.id, + createdAt: record.createdAt, + status: record.status, + description: record.description, + ...assigned, + }; + } + + /** + * @description map records to maintenance + * @param {Array} records + * @returns {Array} + */ + public static mapToBase(records: Array): Array { + return records.map((r) => this.mapToSingle(r)); + } +} diff --git a/src/factory/admin/unit/repair.ts b/src/factory/admin/unit/repair.ts new file mode 100644 index 0000000..0e2d8d4 --- /dev/null +++ b/src/factory/admin/unit/repair.ts @@ -0,0 +1,68 @@ +import { repair } from "../../../entity/unit/repair"; +import { RepairAssigned, RepairViewModel } from "../../../viewmodel/admin/unit/repair.models"; +import DamageReportFactory from "./damageReport"; +import EquipmentFactory from "./equipment/equipment"; +import VehicleFactory from "./vehicle/vehicle"; +import WearableFactory from "./wearable/wearable"; + +export default abstract class RepairFactory { + /** + * @description map record to repair + * @param {repair} record + * @returns {RepairViewModel} + */ + public static mapToSingle(record: repair): RepairViewModel { + let assigned: RepairAssigned; + if (record?.equipmentId) { + assigned = { + relatedId: record.equipmentId, + assigned: "equipment", + related: record.equipment ? EquipmentFactory.mapToSingle(record.equipment) : undefined, + }; + } else if (record?.vehicleId) { + assigned = { + relatedId: record.vehicleId, + assigned: "vehicle", + related: record.vehicle ? VehicleFactory.mapToSingle(record.vehicle) : undefined, + }; + } else if (record?.wearableId) { + assigned = { + relatedId: record.wearableId, + assigned: "wearable", + related: record.wearable ? WearableFactory.mapToSingle(record.wearable) : undefined, + }; + } else { + assigned = { + relatedId: undefined, + assigned: undefined, + related: undefined, + }; + } + + return { + id: record.id, + createdAt: record.createdAt, + finishedAt: record.finishedAt, + finishedBy: record?.finishedBy + ? record.finishedBy.firstname + " " + record.finishedBy.lastname + : record.finishedByString, + status: record.status, + responsible: record.responsible, + title: record.title, + description: record.description, + images: record.images, + reportDocument: record.reportDocument, + reports: record.reports ? DamageReportFactory.mapToBase(record.reports) : [], + ...assigned, + }; + } + + /** + * @description map records to repair + * @param {Array} records + * @returns {Array} + */ + public static mapToBase(records: Array): Array { + return records.map((r) => this.mapToSingle(r)); + } +} diff --git a/src/factory/admin/unit/vehicle/vehicle.ts b/src/factory/admin/unit/vehicle/vehicle.ts new file mode 100644 index 0000000..70e0575 --- /dev/null +++ b/src/factory/admin/unit/vehicle/vehicle.ts @@ -0,0 +1,56 @@ +import { vehicle } from "../../../../entity/unit/vehicle/vehicle"; +import { MinifiedVehicleViewModel, VehicleViewModel } from "../../../../viewmodel/admin/unit/vehicle/vehicle.models"; +import VehicleTypeFactory from "./vehicleType"; + +export default abstract class VehicleFactory { + /** + * @description map record to vehicle + * @param {vehicle} record + * @returns {VehicleViewModel} + */ + public static mapToSingle(record: vehicle): VehicleViewModel { + return { + id: record.id, + code: record?.code, + name: record.name, + location: record.location, + commissioned: record.commissioned, + decommissioned: record?.decommissioned, + vehicleTypeId: record?.vehicleTypeId, + vehicleType: record?.vehicleType ? VehicleTypeFactory.mapToSingle(record.vehicleType) : null, + }; + } + + /** + * @description map records to vehicle + * @param {Array} records + * @returns {Array} + */ + public static mapToBase(records: Array): Array { + return records.map((r) => this.mapToSingle(r)); + } + + /** + * @description map record to minifed vehicle + * @param {vehicle} record + * @returns {MinifiedVehicleViewModel} + */ + public static mapToSingleMinified(record: vehicle): MinifiedVehicleViewModel { + return { + id: record.id, + code: record?.code, + name: record.name, + type: record?.vehicleType.type, + assigned: "vehicle", + }; + } + + /** + * @description map records to minified vehicle + * @param {Array} records + * @returns {Array} + */ + public static mapToBaseMinifed(records: Array): Array { + return records.map((r) => this.mapToSingleMinified(r)); + } +} diff --git a/src/factory/admin/unit/vehicle/vehicleType.ts b/src/factory/admin/unit/vehicle/vehicleType.ts new file mode 100644 index 0000000..b282b79 --- /dev/null +++ b/src/factory/admin/unit/vehicle/vehicleType.ts @@ -0,0 +1,27 @@ +import { vehicleType } from "../../../../entity/unit/vehicle/vehicleType"; +import { VehicleTypeViewModel } from "../../../../viewmodel/admin/unit/vehicle/vehicleType.models"; + +export default abstract class VehicleTypeFactory { + /** + * @description map record to vehicleType + * @param {vehicleType} record + * @returns {VehicleTypeViewModel} + */ + public static mapToSingle(record: vehicleType): VehicleTypeViewModel { + return { + id: record.id, + type: record.type, + description: record.description, + vehicleCount: record.vehicleCount, + }; + } + + /** + * @description map records to vehicleType + * @param {Array} records + * @returns {Array} + */ + public static mapToBase(records: Array): Array { + return records.map((r) => this.mapToSingle(r)); + } +} diff --git a/src/factory/admin/unit/wearable/wearable.ts b/src/factory/admin/unit/wearable/wearable.ts new file mode 100644 index 0000000..4f58e2d --- /dev/null +++ b/src/factory/admin/unit/wearable/wearable.ts @@ -0,0 +1,62 @@ +import { wearable } from "../../../../entity/unit/wearable/wearable"; +import { + MinifiedWearableViewModel, + WearableViewModel, +} from "../../../../viewmodel/admin/unit/wearable/wearable.models"; +import MemberFactory from "../../club/member/member"; +import WearableTypeFactory from "./wearableType"; + +export default abstract class WearableFactory { + /** + * @description map record to wearable + * @param {wearable} record + * @returns {WearableViewModel} + */ + public static mapToSingle(record: wearable): WearableViewModel { + return { + id: record.id, + code: record?.code, + name: record.name, + location: record.location, + commissioned: record.commissioned, + decommissioned: record?.decommissioned, + wearableTypeId: record?.wearableTypeId, + wearableType: record?.wearableType ? WearableTypeFactory.mapToSingle(record.wearableType) : null, + wearerId: record?.wearerId, + wearer: record?.wearer ? MemberFactory.mapToSingle(record.wearer) : null, + }; + } + + /** + * @description map records to wearable + * @param {Array} records + * @returns {Array} + */ + public static mapToBase(records: Array): Array { + return records.map((r) => this.mapToSingle(r)); + } + + /** + * @description map record to minifed wearable + * @param {wearable} record + * @returns {MinifiedWearableViewModel} + */ + public static mapToSingleMinified(record: wearable): MinifiedWearableViewModel { + return { + id: record.id, + code: record?.code, + name: record.name, + type: record?.wearableType.type, + assigned: "wearable", + }; + } + + /** + * @description map records to minified wearable + * @param {Array} records + * @returns {Array} + */ + public static mapToBaseMinifed(records: Array): Array { + return records.map((r) => this.mapToSingleMinified(r)); + } +} diff --git a/src/factory/admin/unit/wearable/wearableType.ts b/src/factory/admin/unit/wearable/wearableType.ts new file mode 100644 index 0000000..89c1066 --- /dev/null +++ b/src/factory/admin/unit/wearable/wearableType.ts @@ -0,0 +1,27 @@ +import { wearableType } from "../../../../entity/unit/wearable/wearableType"; +import { WearableTypeViewModel } from "../../../../viewmodel/admin/unit/wearable/wearableType.models"; + +export default abstract class WearableTypeFactory { + /** + * @description map record to wearableType + * @param {wearableType} record + * @returns {WearableTypeViewModel} + */ + public static mapToSingle(record: wearableType): WearableTypeViewModel { + return { + id: record.id, + type: record.type, + description: record.description, + wearableCount: record.wearableCount, + }; + } + + /** + * @description map records to wearableType + * @param {Array} records + * @returns {Array} + */ + public static mapToBase(records: Array): Array { + return records.map((r) => this.mapToSingle(r)); + } +} diff --git a/src/handlebars.config.ts b/src/handlebars.config.ts index 19b32ad..6494994 100644 --- a/src/handlebars.config.ts +++ b/src/handlebars.config.ts @@ -59,3 +59,7 @@ Handlebars.registerHelper("longdatetimeWithWeekday", function (aString) { Handlebars.registerHelper("json", function (context) { return JSON.stringify(context); }); + +Handlebars.registerHelper("eq", function (p, q, options) { + return p == q ? options.fn(this) : options.inverse(this); +}); diff --git a/src/helpers/backupHelper.ts b/src/helpers/backupHelper.ts index 2a4f678..b986cea 100644 --- a/src/helpers/backupHelper.ts +++ b/src/helpers/backupHelper.ts @@ -20,7 +20,9 @@ export type BackupSection = | "template" | "user" | "webapi" - | "settings"; + | "settings" + | "unitBase" + | "unitInstances"; export type BackupSectionRefered = { [key in BackupSection]?: Array; @@ -34,7 +36,7 @@ export type BackupFileContentSection = Array | { [key: string]: Array export default abstract class BackupHelper { // ! Order matters because of foreign keys private static readonly backupSection: Array<{ type: BackupSection; orderOnInsert: number; orderOnClear: number }> = [ - { type: "member", orderOnInsert: 2, orderOnClear: 2 }, // CLEAR depends on protcol INSERT depends on Base + { type: "member", orderOnInsert: 2, orderOnClear: 2 }, // CLEAR depends on protcol and wearables INSERT depends on Base { type: "memberBase", orderOnInsert: 1, orderOnClear: 3 }, // CLEAR depends on member { type: "protocol", orderOnInsert: 3, orderOnClear: 1 }, // INSERT depends on member { type: "newsletter", orderOnInsert: 3, orderOnClear: 1 }, // INSERT depends on member & query & calendar @@ -45,6 +47,8 @@ export default abstract class BackupHelper { { type: "user", orderOnInsert: 1, orderOnClear: 1 }, { type: "webapi", orderOnInsert: 1, orderOnClear: 1 }, { type: "settings", orderOnInsert: 1, orderOnClear: 1 }, + { type: "unitBase", orderOnInsert: 1, orderOnClear: 2 }, + { type: "unitInstances", orderOnInsert: 3, orderOnClear: 1 }, // INSERT depends on member in wearable ]; private static readonly backupSectionRefered: BackupSectionRefered = { @@ -82,6 +86,8 @@ export default abstract class BackupHelper { user: ["user", "user_permission", "role", "role_permission", "invite"], webapi: ["webapi", "webapi_permission"], settings: ["setting"], + unitBase: ["equipment_type", "vehicle_type", "wearable_type"], + unitInstances: ["equipment", "vehicle", "wearable"], }; private static transactionManager: EntityManager; @@ -228,6 +234,10 @@ export default abstract class BackupHelper { return await this.getWebapi(); case "settings": return await this.getSettings(); + case "unitBase": + return await this.getUnitBase(); + case "unitInstances": + return await this.getUnitInstances(); default: return []; } @@ -489,6 +499,28 @@ export default abstract class BackupHelper { .select(["setting.topic", "setting.key", "setting.value"]) .getMany(); } + private static async getUnitBase(): Promise<{ [key: string]: Array }> { + return { + equipment_type: await dataSource.getRepository("equipment_type").find(), + vehicle_type: await dataSource.getRepository("vehicle_type").find(), + wearable_type: await dataSource.getRepository("wearable_type").find(), + inspection_plan: await dataSource.getRepository("inspection_plan").find(), + inspection_versioned_plan: await dataSource.getRepository("inspection_versioned_plan").find(), + inspection_point: await dataSource.getRepository("inspection_point").find(), + }; + } + private static async getUnitInstances(): Promise<{ [key: string]: Array }> { + return { + equipment: await dataSource.getRepository("equipment").find(), + vehicle: await dataSource.getRepository("vehicle").find(), + wearable: await dataSource.getRepository("wearable").find(), + maintenance: await dataSource.getRepository("maintenance").find(), + damage_report: await dataSource.getRepository("damage_report").find(), + repair: await dataSource.getRepository("repair").find(), + inspection: await dataSource.getRepository("inspection").find(), + inspection_point_result: await dataSource.getRepository("inspection_point_result").find(), + }; + } private static async setSectionData( section: BackupSection, @@ -506,6 +538,8 @@ export default abstract class BackupHelper { if (section == "user" && !Array.isArray(data)) await this.setUser(data); if (section == "webapi" && Array.isArray(data)) await this.setWebapi(data); if (section == "settings" && Array.isArray(data)) await this.setSettings(data); + if (section == "unitBase" && !Array.isArray(data)) await this.setUnitBase(data); + if (section == "unitInstances" && !Array.isArray(data)) await this.setUnitInstances(data); } private static async setMemberData(data: Array): Promise { @@ -869,4 +903,27 @@ export default abstract class BackupHelper { private static async setSettings(data: Array): Promise { await this.transactionManager.getRepository("setting").save(data); } + private static async setUnitBase(data: { [key: string]: Array }): Promise { + await this.transactionManager.getRepository("equipment_type").save(data["equipment_type"] ?? []); + await this.transactionManager.getRepository("vehicle_type").save(data["vehicle_type"] ?? []); + await this.transactionManager.getRepository("wearable_type").save(data["wearable_type"] ?? []); + + await this.transactionManager.getRepository("inspection_plan").save(data["inspection_plan"] ?? []); + await this.transactionManager + .getRepository("inspection_versioned_plan") + .save(data["inspection_versioned_plan"] ?? []); + await this.transactionManager.getRepository("inspection_point").save(data["inspection_point"] ?? []); + } + private static async setUnitInstances(data: { [key: string]: Array }): Promise { + await this.transactionManager.getRepository("equipment").save(data["equipment"] ?? []); + await this.transactionManager.getRepository("vehicle").save(data["vehicle"] ?? []); + await this.transactionManager.getRepository("wearable").save(data["wearable"] ?? []); + + await this.transactionManager.getRepository("repair").save(data["repair"] ?? []); + await this.transactionManager.getRepository("damage_report").save(data["damage_report"] ?? []); + await this.transactionManager.getRepository("maintenance").save(data["maintenance"] ?? []); + + await this.transactionManager.getRepository("inspection").save(data["inspection"] ?? []); + await this.transactionManager.getRepository("inspection_point_result").save(data["inspection_point_result"] ?? []); + } } diff --git a/src/helpers/fileSystemHelper.ts b/src/helpers/fileSystemHelper.ts index 9bc409a..c97bf4f 100644 --- a/src/helpers/fileSystemHelper.ts +++ b/src/helpers/fileSystemHelper.ts @@ -15,7 +15,7 @@ export abstract class FileSystemHelper { return readFileSync(this.formatPath(...filePath), "utf8"); } - static readFileasBase64(...filePath: string[]) { + static readFileAsBase64(...filePath: string[]) { this.createFolder(...filePath); return readFileSync(this.formatPath(...filePath), "base64"); } diff --git a/src/helpers/pdfExport.ts b/src/helpers/pdfExport.ts index 429f551..b42564e 100644 --- a/src/helpers/pdfExport.ts +++ b/src/helpers/pdfExport.ts @@ -127,7 +127,7 @@ export abstract class PdfExport { const mergedPdf = await PDFDocument.create(); for (const pdfPath of pdfFilePaths) { - const pdfBytes = FileSystemHelper.readFileasBase64(inputFolder, pdfPath); + const pdfBytes = FileSystemHelper.readFileAsBase64(inputFolder, pdfPath); const pdf = await PDFDocument.load(pdfBytes); const copiedPages = await mergedPdf.copyPages(pdf, pdf.getPageIndices()); copiedPages.forEach((page) => mergedPdf.addPage(page)); diff --git a/src/helpers/settingsHelper.ts b/src/helpers/settingsHelper.ts index d4c21d8..b3eb5f8 100644 --- a/src/helpers/settingsHelper.ts +++ b/src/helpers/settingsHelper.ts @@ -14,8 +14,6 @@ import { UrlConverter, } from "./convertHelper"; import cloneDeep from "lodash.clonedeep"; -import { rejects } from "assert"; -import InternalException from "../exceptions/internalException"; import MailHelper from "./mailHelper"; export default abstract class SettingHelper { diff --git a/src/helpers/typeTester.ts b/src/helpers/typeTester.ts new file mode 100644 index 0000000..d81f8eb --- /dev/null +++ b/src/helpers/typeTester.ts @@ -0,0 +1,22 @@ +import { PlanTimeDefinition } from "../viewmodel/admin/unit/inspection/inspectionPlan.models"; + +export default abstract class TypeTester { + static testPlanTimeDefinition( + val: string | null, + { key = "", throwErr = false, allowNull = false }: { key?: string; throwErr?: boolean; allowNull?: boolean } + ): PlanTimeDefinition | null { + if (val == null && allowNull) return null; + if (/^(\d+-(d|m|y)|\d+\/(\d+|\*))$/.test(val)) { + return val as PlanTimeDefinition; + } else if (throwErr) { + throw Error( + [ + key, + "provided String does not match PlanTimeDefinition Format: ${number}-${'d'|'m'|'y'} or ${number}/${number|'*'}", + ].join(": ") + ); + } else { + return null; + } + } +} diff --git a/src/index.ts b/src/index.ts index 13de2ee..250ef17 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,6 +1,7 @@ import "dotenv/config"; import "./handlebars.config"; import express from "express"; +import { createServer } from "http"; import { configCheck } from "./env.defaults"; configCheck(); @@ -11,6 +12,8 @@ declare global { export interface Request { userId: string; username: string; + firstname: string; + lastname: string; isOwner: boolean; permissions: PermissionObject; isPWA: boolean; @@ -35,7 +38,10 @@ dataSource.initialize().then(async () => { const app = express(); import router from "./routes/index"; router(app); -app.listen(process.env.NODE_ENV ? process.env.SERVER_PORT ?? 5000 : 5000, () => { +const httpServer = createServer(app); +import SocketServer from "./websocket"; +SocketServer.init(httpServer); +httpServer.listen(process.env.NODE_ENV ? process.env.SERVER_PORT ?? 5000 : 5000, () => { console.log( `${new Date().toISOString()}: listening on port ${process.env.NODE_ENV ? process.env.SERVER_PORT ?? 5000 : 5000}` ); diff --git a/src/middleware/authenticate.ts b/src/middleware/authenticate.ts index abeb832..c961b19 100644 --- a/src/middleware/authenticate.ts +++ b/src/middleware/authenticate.ts @@ -35,6 +35,8 @@ export default async function authenticate(req: Request, res: Response, next: Ne req.userId = decoded.userId; req.username = decoded.username; + req.firstname = decoded.firstname; + req.lastname = decoded.lastname; req.isOwner = decoded.isOwner; req.permissions = decoded.permissions; req.isWebApiRequest = decoded?.sub == "webapi_access_token"; diff --git a/src/middleware/authenticateSocket.ts b/src/middleware/authenticateSocket.ts new file mode 100644 index 0000000..d8718c4 --- /dev/null +++ b/src/middleware/authenticateSocket.ts @@ -0,0 +1,53 @@ +import jwt from "jsonwebtoken"; +import BadRequestException from "../exceptions/badRequestException"; +import InternalException from "../exceptions/internalException"; +import UnauthorizedRequestException from "../exceptions/unauthorizedRequestException"; +import { JWTHelper } from "../helpers/jwtHelper"; +import { SocketMap } from "../storage/socketMap"; +import { Socket } from "socket.io"; + +export default async function authenticateSocket(socket: Socket, next: Function) { + try { + const token = socket.handshake.auth.token; + + if (!token) { + throw new BadRequestException("Provide valid Authorization Header"); + } + + let decoded: string | jwt.JwtPayload; + await JWTHelper.validate(token) + .then((result) => { + decoded = result; + }) + .catch((err) => { + if (err == "jwt expired") { + throw new UnauthorizedRequestException("Token expired", err); + } else { + throw new BadRequestException("Failed Authorization Header decoding", err); + } + }); + + if (typeof decoded == "string" || !decoded) { + throw new InternalException("process failed"); + } + + if (decoded?.sub == "api_token_retrieve") { + throw new BadRequestException("This token is only authorized to get temporary access tokens via GET /api/webapi"); + } + + SocketMap.write(socket.id, { + socketId: socket.id, + userId: decoded.userId, + username: decoded.username, + firstname: decoded.firstname, + lastname: decoded.lastname, + isOwner: decoded.isOwner, + permissions: decoded.permissions, + isWebApiRequest: decoded?.sub == "webapi_access_token", + }); + + next(); + } catch (err) { + next(err); + } +} diff --git a/src/middleware/checkSocketExists.ts b/src/middleware/checkSocketExists.ts new file mode 100644 index 0000000..db43554 --- /dev/null +++ b/src/middleware/checkSocketExists.ts @@ -0,0 +1,11 @@ +import { Event, Socket } from "socket.io"; +import UnauthorizedRequestException from "../exceptions/unauthorizedRequestException"; +import { SocketMap } from "../storage/socketMap"; + +export default async (socket: Socket, [event, ...args]: Event, next: any) => { + if (SocketMap.exists(socket.id)) { + next(); + } else { + next(new UnauthorizedRequestException("not authorized for connection")); + } +}; diff --git a/src/middleware/multer.ts b/src/middleware/multer.ts index ebcec4e..321439e 100644 --- a/src/middleware/multer.ts +++ b/src/middleware/multer.ts @@ -2,7 +2,9 @@ import multer from "multer"; import { FileSystemHelper } from "../helpers/fileSystemHelper"; import path from "path"; import BadRequestException from "../exceptions/badRequestException"; +import { v4 as uuid } from "uuid"; +/**Settings image upload */ export const clubImageStorage = multer.diskStorage({ destination: FileSystemHelper.formatPath("/app"), filename: function (req, file, cb) { @@ -33,3 +35,52 @@ export const clubImageUpload = clubImageMulter.fields([ { name: "icon", maxCount: 1 }, { name: "logo", maxCount: 1 }, ]); + +/**Inspection file upload */ +export const inspectionFileStorage = multer.diskStorage({ + destination: function (req, file, cb) { + FileSystemHelper.createFolder("inspection", req.params.id); + cb(null, FileSystemHelper.formatPath("inspection", req.params.id)); + }, + filename: function (req, file, cb) { + cb(null, file.originalname); + }, +}); + +export const inspectionFileMulter = multer({ + storage: inspectionFileStorage, + fileFilter(req, file, cb) { + if (file.mimetype.startsWith("image/") || file.mimetype === "application/pdf") { + cb(null, true); + } else { + cb(new BadRequestException("Wrong file format")); + } + }, +}); + +export const inspectionFileUpload = inspectionFileMulter.array("files"); + +/**public damage report upload */ +export const pDamageReportFileStorage = multer.diskStorage({ + destination: function (req, file, cb) { + FileSystemHelper.createFolder("damageReport"); + cb(null, FileSystemHelper.formatPath("damageReport")); + }, + filename: function (req, file, cb) { + const fileExtension = path.extname(file.originalname).toLowerCase(); + cb(null, uuid() + fileExtension); + }, +}); + +export const pDamageReportFileMulter = multer({ + storage: pDamageReportFileStorage, + fileFilter(req, file, cb) { + if (file.mimetype.startsWith("image/")) { + cb(null, true); + } else { + cb(new BadRequestException("Wrong file format")); + } + }, +}); + +export const pDamageReportFileUpload = pDamageReportFileMulter.array("images"); diff --git a/src/migrations/1752914551204-UnitBase.ts b/src/migrations/1752914551204-UnitBase.ts new file mode 100644 index 0000000..685bf42 --- /dev/null +++ b/src/migrations/1752914551204-UnitBase.ts @@ -0,0 +1,82 @@ +import { MigrationInterface, QueryRunner } from "typeorm"; +import { + inspection_plan_table, + inspection_versioned_plan_table, + inspection_point_table, + inspection_table, + inspection_point_result_table, +} from "./baseSchemaTables/inspection"; +import { + equipment_type_table, + equipment_table, + vehicle_type_table, + vehicle_table, + wearable_type_table, + wearable_table, +} from "./baseSchemaTables/unit"; +import { maintenance_table, damage_report_table, repair_table } from "./baseSchemaTables/unit_extend"; +import { availableTemplates } from "../type/templateTypes"; +import { template_usage_table } from "./baseSchemaTables/query_template"; + +export class UnitBase1752914551204 implements MigrationInterface { + name = "UnitBase1752914551204"; + + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.createTable(equipment_type_table, true, true, true); + await queryRunner.createTable(equipment_table, true, true, true); + await queryRunner.createTable(vehicle_type_table, true, true, true); + await queryRunner.createTable(vehicle_table, true, true, true); + await queryRunner.createTable(wearable_type_table, true, true, true); + await queryRunner.createTable(wearable_table, true, true, true); + + await queryRunner.createTable(maintenance_table, true, true, true); + await queryRunner.createTable(repair_table, true, true, true); + await queryRunner.createTable(damage_report_table, true, true, true); + + await queryRunner.createTable(inspection_plan_table, true, true, true); + await queryRunner.createTable(inspection_versioned_plan_table, true, true, true); + await queryRunner.createTable(inspection_point_table, true, true, true); + + await queryRunner.createTable(inspection_table, true, true, true); + await queryRunner.createTable(inspection_point_result_table, true, true, true); + + await queryRunner.manager + .createQueryBuilder() + .insert() + .into(template_usage_table.name) + .values( + availableTemplates.map((at) => ({ + scope: at, + })) + ) + .orIgnore() + .execute(); + } + + public async down(queryRunner: QueryRunner): Promise { + await queryRunner.dropTable(inspection_point_result_table, true, true, true); + await queryRunner.dropTable(inspection_table, true, true, true); + + await queryRunner.dropTable(inspection_point_table, true, true, true); + await queryRunner.dropTable(inspection_versioned_plan_table, true, true, true); + await queryRunner.dropTable(inspection_plan_table, true, true, true); + + await queryRunner.dropTable(damage_report_table, true, true, true); + await queryRunner.dropTable(repair_table, true, true, true); + await queryRunner.dropTable(maintenance_table, true, true, true); + + await queryRunner.dropTable(wearable_table, true, true, true); + await queryRunner.dropTable(wearable_type_table, true, true, true); + await queryRunner.dropTable(vehicle_table, true, true, true); + await queryRunner.dropTable(vehicle_type_table, true, true, true); + await queryRunner.dropTable(equipment_table, true, true, true); + await queryRunner.dropTable(equipment_type_table, true, true, true); + + await queryRunner.manager + .createQueryBuilder() + .delete() + .from(template_usage_table.name) + .where({ scope: "inspection" }) + .execute(); + } +} diff --git a/src/migrations/baseSchemaTables/inspection.ts b/src/migrations/baseSchemaTables/inspection.ts new file mode 100644 index 0000000..6a71127 --- /dev/null +++ b/src/migrations/baseSchemaTables/inspection.ts @@ -0,0 +1,176 @@ +import { Table, TableForeignKey, TableUnique } from "typeorm"; +import { getTypeByORM, isUUIDPrimary, getDefaultByORM } from "../ormHelper"; + +export const inspection_plan_table = new Table({ + name: "inspection_plan", + columns: [ + { name: "id", ...getTypeByORM("uuid"), ...isUUIDPrimary }, + { name: "title", ...getTypeByORM("varchar") }, + { name: "inspectionInterval", ...getTypeByORM("varchar", true) }, + { name: "remindTime", ...getTypeByORM("varchar", true) }, + { name: "createdAt", ...getTypeByORM("datetime"), default: getDefaultByORM("currentTimestamp") }, + { name: "equipmentTypeId", ...getTypeByORM("uuid", true) }, + { name: "vehicleTypeId", ...getTypeByORM("uuid", true) }, + { name: "wearableTypeId", ...getTypeByORM("uuid", true) }, + ], + foreignKeys: [ + new TableForeignKey({ + columnNames: ["equipmentTypeId"], + referencedColumnNames: ["id"], + referencedTableName: "equipment_type", + onDelete: "CASCADE", + onUpdate: "RESTRICT", + }), + new TableForeignKey({ + columnNames: ["vehicleTypeId"], + referencedColumnNames: ["id"], + referencedTableName: "vehicle_type", + onDelete: "CASCADE", + onUpdate: "RESTRICT", + }), + new TableForeignKey({ + columnNames: ["wearableTypeId"], + referencedColumnNames: ["id"], + referencedTableName: "wearable_type", + onDelete: "CASCADE", + onUpdate: "RESTRICT", + }), + ], +}); + +export const inspection_versioned_plan_table = new Table({ + name: "inspection_versioned_plan", + columns: [ + { name: "id", ...getTypeByORM("uuid"), ...isUUIDPrimary }, + { name: "version", ...getTypeByORM("int"), default: getDefaultByORM("number", 0) }, + { name: "createdAt", ...getTypeByORM("datetime"), default: getDefaultByORM("currentTimestamp") }, + { name: "inspectionPlanId", ...getTypeByORM("uuid") }, + ], + foreignKeys: [ + new TableForeignKey({ + columnNames: ["inspectionPlanId"], + referencedColumnNames: ["id"], + referencedTableName: "inspection_plan", + onDelete: "CASCADE", + onUpdate: "RESTRICT", + }), + ], + uniques: [ + new TableUnique({ + name: "unique_version", + columnNames: ["version", "inspectionPlanId"], + }), + ], +}); + +export const inspection_point_table = new Table({ + name: "inspection_point", + columns: [ + { name: "id", ...getTypeByORM("uuid"), ...isUUIDPrimary }, + { name: "title", ...getTypeByORM("varchar") }, + { name: "description", ...getTypeByORM("text") }, + { name: "type", ...getTypeByORM("varchar") }, + { name: "min", ...getTypeByORM("int", true) }, + { name: "max", ...getTypeByORM("int", true) }, + { name: "others", ...getTypeByORM("varchar", true) }, + { name: "sort", ...getTypeByORM("int"), default: getDefaultByORM("number", 0) }, + { name: "versionedPlanId", ...getTypeByORM("uuid") }, + ], + foreignKeys: [ + new TableForeignKey({ + columnNames: ["versionedPlanId"], + referencedColumnNames: ["id"], + referencedTableName: "inspection_versioned_plan", + onDelete: "CASCADE", + onUpdate: "RESTRICT", + }), + ], +}); + +export const inspection_table = new Table({ + name: "inspection", + columns: [ + { name: "id", ...getTypeByORM("uuid"), ...isUUIDPrimary }, + { name: "context", ...getTypeByORM("text") }, + { name: "createdAt", ...getTypeByORM("datetime"), default: getDefaultByORM("currentTimestamp") }, + { name: "finishedAt", ...getTypeByORM("datetime", true) }, + { name: "finishedById", ...getTypeByORM("uuid", true) }, + { name: "finishedByString", ...getTypeByORM("varchar", true) }, + { name: "nextInspection", ...getTypeByORM("date", true) }, + { name: "hasNewer", ...getTypeByORM("boolean"), default: getDefaultByORM("boolean", false) }, + { name: "inspectionPlanId", ...getTypeByORM("uuid") }, + { name: "inspectionVersionedPlanId", ...getTypeByORM("uuid") }, + { name: "equipmentId", ...getTypeByORM("uuid", true) }, + { name: "vehicleId", ...getTypeByORM("uuid", true) }, + { name: "wearableId", ...getTypeByORM("uuid", true) }, + ], + foreignKeys: [ + new TableForeignKey({ + columnNames: ["finishedById"], + referencedColumnNames: ["id"], + referencedTableName: "user", + onDelete: "SET NULL", + onUpdate: "RESTRICT", + }), + new TableForeignKey({ + columnNames: ["inspectionPlanId"], + referencedColumnNames: ["id"], + referencedTableName: "inspection_plan", + onDelete: "RESTRICT", + onUpdate: "RESTRICT", + }), + new TableForeignKey({ + columnNames: ["inspectionVersionedPlanId"], + referencedColumnNames: ["id"], + referencedTableName: "inspection_versioned_plan", + onDelete: "RESTRICT", + onUpdate: "RESTRICT", + }), + new TableForeignKey({ + columnNames: ["equipmentId"], + referencedColumnNames: ["id"], + referencedTableName: "equipment", + onDelete: "CASCADE", + onUpdate: "RESTRICT", + }), + new TableForeignKey({ + columnNames: ["vehicleId"], + referencedColumnNames: ["id"], + referencedTableName: "vehicle", + onDelete: "CASCADE", + onUpdate: "RESTRICT", + }), + new TableForeignKey({ + columnNames: ["wearableId"], + referencedColumnNames: ["id"], + referencedTableName: "wearable", + onDelete: "CASCADE", + onUpdate: "RESTRICT", + }), + ], +}); + +export const inspection_point_result_table = new Table({ + name: "inspection_point_result", + columns: [ + { name: "inspectionId", ...getTypeByORM("uuid"), isPrimary: true }, + { name: "inspectionPointId", ...getTypeByORM("uuid"), isPrimary: true }, + { name: "value", ...getTypeByORM("text") }, + ], + foreignKeys: [ + new TableForeignKey({ + columnNames: ["inspectionId"], + referencedColumnNames: ["id"], + referencedTableName: "inspection", + onDelete: "CASCADE", + onUpdate: "RESTRICT", + }), + new TableForeignKey({ + columnNames: ["inspectionPointId"], + referencedColumnNames: ["id"], + referencedTableName: "inspection_point", + onDelete: "RESTRICT", + onUpdate: "RESTRICT", + }), + ], +}); diff --git a/src/migrations/baseSchemaTables/unit.ts b/src/migrations/baseSchemaTables/unit.ts new file mode 100644 index 0000000..4d18e11 --- /dev/null +++ b/src/migrations/baseSchemaTables/unit.ts @@ -0,0 +1,103 @@ +import { Table, TableForeignKey } from "typeorm"; +import { getTypeByORM, isUUIDPrimary, getDefaultByORM } from "../ormHelper"; + +export const equipment_table = new Table({ + name: "equipment", + columns: [ + { name: "id", ...getTypeByORM("uuid"), ...isUUIDPrimary }, + { name: "code", ...getTypeByORM("varchar", true), isUnique: true }, + { name: "name", ...getTypeByORM("varchar") }, + { name: "location", ...getTypeByORM("varchar") }, + { name: "commissioned", ...getTypeByORM("date") }, + { name: "decommissioned", ...getTypeByORM("date", true) }, + { name: "equipmentTypeId", ...getTypeByORM("uuid") }, + ], + foreignKeys: [ + new TableForeignKey({ + columnNames: ["equipmentTypeId"], + referencedColumnNames: ["id"], + referencedTableName: "equipment_type", + onDelete: "RESTRICT", + onUpdate: "RESTRICT", + }), + ], +}); + +export const equipment_type_table = new Table({ + name: "equipment_type", + columns: [ + { name: "id", ...getTypeByORM("uuid"), ...isUUIDPrimary }, + { name: "type", ...getTypeByORM("varchar"), isUnique: true }, + { name: "description", ...getTypeByORM("text", true) }, + ], +}); + +export const vehicle_table = new Table({ + name: "vehicle", + columns: [ + { name: "id", ...getTypeByORM("uuid"), ...isUUIDPrimary }, + { name: "code", ...getTypeByORM("varchar", true), isUnique: true }, + { name: "name", ...getTypeByORM("varchar") }, + { name: "location", ...getTypeByORM("varchar") }, + { name: "commissioned", ...getTypeByORM("date") }, + { name: "decommissioned", ...getTypeByORM("date", true) }, + { name: "vehicleTypeId", ...getTypeByORM("uuid") }, + ], + foreignKeys: [ + new TableForeignKey({ + columnNames: ["vehicleTypeId"], + referencedColumnNames: ["id"], + referencedTableName: "vehicle_type", + onDelete: "RESTRICT", + onUpdate: "RESTRICT", + }), + ], +}); + +export const vehicle_type_table = new Table({ + name: "vehicle_type", + columns: [ + { name: "id", ...getTypeByORM("uuid"), ...isUUIDPrimary }, + { name: "type", ...getTypeByORM("varchar"), isUnique: true }, + { name: "description", ...getTypeByORM("text", true) }, + ], +}); + +export const wearable_table = new Table({ + name: "wearable", + columns: [ + { name: "id", ...getTypeByORM("uuid"), ...isUUIDPrimary }, + { name: "code", ...getTypeByORM("varchar", true), isUnique: true }, + { name: "name", ...getTypeByORM("varchar") }, + { name: "location", ...getTypeByORM("varchar") }, + { name: "commissioned", ...getTypeByORM("date") }, + { name: "decommissioned", ...getTypeByORM("date", true) }, + { name: "wearableTypeId", ...getTypeByORM("uuid") }, + { name: "wearerId", ...getTypeByORM("uuid", true) }, + ], + foreignKeys: [ + new TableForeignKey({ + columnNames: ["wearableTypeId"], + referencedColumnNames: ["id"], + referencedTableName: "wearable_type", + onDelete: "RESTRICT", + onUpdate: "RESTRICT", + }), + new TableForeignKey({ + columnNames: ["wearerId"], + referencedColumnNames: ["id"], + referencedTableName: "member", + onDelete: "SET NULL", + onUpdate: "RESTRICT", + }), + ], +}); + +export const wearable_type_table = new Table({ + name: "wearable_type", + columns: [ + { name: "id", ...getTypeByORM("uuid"), ...isUUIDPrimary }, + { name: "type", ...getTypeByORM("varchar"), isUnique: true }, + { name: "description", ...getTypeByORM("text", true) }, + ], +}); diff --git a/src/migrations/baseSchemaTables/unit_extend.ts b/src/migrations/baseSchemaTables/unit_extend.ts new file mode 100644 index 0000000..65d0738 --- /dev/null +++ b/src/migrations/baseSchemaTables/unit_extend.ts @@ -0,0 +1,162 @@ +import { Table, TableForeignKey } from "typeorm"; +import { getTypeByORM, isUUIDPrimary, getDefaultByORM } from "../ormHelper"; + +export const damage_report_table = new Table({ + name: "damage_report", + columns: [ + { name: "id", ...getTypeByORM("uuid"), ...isUUIDPrimary }, + { name: "reportedAt", ...getTypeByORM("datetime"), default: getDefaultByORM("currentTimestamp") }, + { name: "closedAt", ...getTypeByORM("datetime", true) }, + { name: "closedById", ...getTypeByORM("uuid", true) }, + { name: "closedByString", ...getTypeByORM("varchar", true) }, + { name: "status", ...getTypeByORM("varchar") }, + { name: "title", ...getTypeByORM("varchar") }, + { name: "description", ...getTypeByORM("text") }, + { name: "location", ...getTypeByORM("text", true) }, + { name: "noteByReporter", ...getTypeByORM("text", true) }, + { name: "noteByWorker", ...getTypeByORM("text", true) }, + { name: "reportedBy", ...getTypeByORM("varchar", true) }, + { name: "images", ...getTypeByORM("text", true) }, + { name: "equipmentId", ...getTypeByORM("uuid", true) }, + { name: "vehicleId", ...getTypeByORM("uuid", true) }, + { name: "wearableId", ...getTypeByORM("uuid", true) }, + { name: "repairId", ...getTypeByORM("uuid", true) }, + ], + foreignKeys: [ + new TableForeignKey({ + columnNames: ["closedById"], + referencedColumnNames: ["id"], + referencedTableName: "user", + onDelete: "SET NULL", + onUpdate: "RESTRICT", + }), + new TableForeignKey({ + columnNames: ["equipmentId"], + referencedColumnNames: ["id"], + referencedTableName: "equipment", + onDelete: "CASCADE", + onUpdate: "RESTRICT", + }), + new TableForeignKey({ + columnNames: ["vehicleId"], + referencedColumnNames: ["id"], + referencedTableName: "vehicle", + onDelete: "CASCADE", + onUpdate: "RESTRICT", + }), + new TableForeignKey({ + columnNames: ["wearableId"], + referencedColumnNames: ["id"], + referencedTableName: "wearable", + onDelete: "CASCADE", + onUpdate: "RESTRICT", + }), + new TableForeignKey({ + columnNames: ["repairId"], + referencedColumnNames: ["id"], + referencedTableName: "repair", + onDelete: "SET NULL", + onUpdate: "RESTRICT", + }), + ], +}); + +export const maintenance_table = new Table({ + name: "maintenance", + columns: [ + { name: "id", ...getTypeByORM("uuid"), ...isUUIDPrimary }, + { name: "createdAt", ...getTypeByORM("datetime"), default: getDefaultByORM("currentTimestamp") }, + { name: "finishedAt", ...getTypeByORM("datetime", true) }, + { name: "finishedById", ...getTypeByORM("uuid", true) }, + { name: "finishedByString", ...getTypeByORM("varchar", true) }, + { name: "status", ...getTypeByORM("varchar") }, + { name: "title", ...getTypeByORM("varchar") }, + { name: "description", ...getTypeByORM("text") }, + { name: "responsible", ...getTypeByORM("varchar", true) }, + { name: "images", ...getTypeByORM("text", true) }, + { name: "reportDocument", ...getTypeByORM("varchar", true) }, + { name: "equipmentId", ...getTypeByORM("uuid", true) }, + { name: "vehicleId", ...getTypeByORM("uuid", true) }, + { name: "wearableId", ...getTypeByORM("uuid", true) }, + ], + foreignKeys: [ + new TableForeignKey({ + columnNames: ["finishedById"], + referencedColumnNames: ["id"], + referencedTableName: "user", + onDelete: "SET NULL", + onUpdate: "RESTRICT", + }), + new TableForeignKey({ + columnNames: ["equipmentId"], + referencedColumnNames: ["id"], + referencedTableName: "equipment", + onDelete: "CASCADE", + onUpdate: "RESTRICT", + }), + new TableForeignKey({ + columnNames: ["vehicleId"], + referencedColumnNames: ["id"], + referencedTableName: "vehicle", + onDelete: "CASCADE", + onUpdate: "RESTRICT", + }), + new TableForeignKey({ + columnNames: ["wearableId"], + referencedColumnNames: ["id"], + referencedTableName: "wearable", + onDelete: "CASCADE", + onUpdate: "RESTRICT", + }), + ], +}); + +export const repair_table = new Table({ + name: "repair", + columns: [ + { name: "id", ...getTypeByORM("uuid"), ...isUUIDPrimary }, + { name: "createdAt", ...getTypeByORM("datetime"), default: getDefaultByORM("currentTimestamp") }, + { name: "finishedAt", ...getTypeByORM("datetime", true) }, + { name: "finishedById", ...getTypeByORM("uuid", true) }, + { name: "finishedByString", ...getTypeByORM("varchar", true) }, + { name: "status", ...getTypeByORM("varchar") }, + { name: "title", ...getTypeByORM("varchar") }, + { name: "description", ...getTypeByORM("text") }, + { name: "responsible", ...getTypeByORM("varchar", true) }, + { name: "images", ...getTypeByORM("text", true) }, + { name: "reportDocument", ...getTypeByORM("varchar", true) }, + { name: "equipmentId", ...getTypeByORM("uuid", true) }, + { name: "vehicleId", ...getTypeByORM("uuid", true) }, + { name: "wearableId", ...getTypeByORM("uuid", true) }, + ], + foreignKeys: [ + new TableForeignKey({ + columnNames: ["finishedById"], + referencedColumnNames: ["id"], + referencedTableName: "user", + onDelete: "SET NULL", + onUpdate: "RESTRICT", + }), + new TableForeignKey({ + columnNames: ["equipmentId"], + referencedColumnNames: ["id"], + referencedTableName: "equipment", + onDelete: "CASCADE", + onUpdate: "RESTRICT", + }), + new TableForeignKey({ + columnNames: ["vehicleId"], + referencedColumnNames: ["id"], + referencedTableName: "vehicle", + onDelete: "CASCADE", + onUpdate: "RESTRICT", + }), + new TableForeignKey({ + columnNames: ["wearableId"], + referencedColumnNames: ["id"], + referencedTableName: "wearable", + onDelete: "CASCADE", + onUpdate: "RESTRICT", + }), + ], +}); diff --git a/src/routes/admin/index.ts b/src/routes/admin/index.ts index b306bfc..a3e8b33 100644 --- a/src/routes/admin/index.ts +++ b/src/routes/admin/index.ts @@ -2,6 +2,7 @@ import express from "express"; import PermissionHelper from "../../helpers/permissionHelper"; import preventWebapiAccess from "../../middleware/preventWebApiAccess"; +/** configuration */ import award from "./configuration/award"; import communicationType from "./configuration/communicationType"; import executivePosition from "./configuration/executivePosition"; @@ -15,6 +16,7 @@ import template from "./configuration/template"; import templateUsage from "./configuration/templateUsage"; import newsletterConfig from "./configuration/newsletterConfig"; +/** club */ import member from "./club/member"; import protocol from "./club/protocol"; import calendar from "./club/calendar"; @@ -22,6 +24,7 @@ import queryBuilder from "./club/queryBuilder"; import newsletter from "./club/newsletter"; import listprint from "./club/listprint"; +/** management */ import role from "./management/role"; import user from "./management/user"; import invite from "./management/invite"; @@ -29,8 +32,22 @@ import api from "./management/webapi"; import backup from "./management/backup"; import setting from "./management/setting"; +/** unit */ +import equipment from "./unit/equipment"; +import equipmentType from "./unit/equipmentType"; +import vehicle from "./unit/vehicle"; +import vehicleType from "./unit/vehicleType"; +import wearable from "./unit/wearable"; +import wearableType from "./unit/wearableType"; +import inspection from "./unit/inspection"; +import inspectionPlan from "./unit/inspectionPlan"; +import damageReport from "./unit/damageReport"; +import maintenance from "./unit/maintenance"; +import repair from "./unit/repair"; + var router = express.Router({ mergeParams: true }); +/** configuration */ router.use( "/award", PermissionHelper.passCheckSomeMiddleware([ @@ -121,6 +138,7 @@ router.use( newsletterConfig ); +/** club */ router.use("/member", PermissionHelper.passCheckMiddleware("read", "club", "member"), member); router.use( "/protocol", @@ -152,6 +170,7 @@ router.use( ); router.use("/listprint", PermissionHelper.passCheckMiddleware("read", "club", "listprint"), listprint); +/** management */ router.use("/role", PermissionHelper.passCheckMiddleware("read", "management", "role"), role); router.use( "/user", @@ -171,4 +190,109 @@ router.use( ); router.use("/setting", PermissionHelper.passCheckMiddleware("read", "management", "setting"), setting); +/** unit */ +router.use( + "/equipment", + PermissionHelper.passCheckSomeMiddleware([ + { requiredPermission: "read", section: "unit", module: "equipment" }, + { requiredPermission: "read", section: "unit", module: "inspection_plan" }, + ]), + equipment +); +router.use( + "/equipmenttype", + PermissionHelper.passCheckSomeMiddleware([ + { requiredPermission: "read", section: "unit", module: "equipment_type" }, + { requiredPermission: "read", section: "unit", module: "equipment" }, + { requiredPermission: "read", section: "unit", module: "inspection_plan" }, + ]), + equipmentType +); +router.use( + "/vehicle", + PermissionHelper.passCheckSomeMiddleware([ + { requiredPermission: "read", section: "unit", module: "vehicle" }, + { requiredPermission: "read", section: "unit", module: "inspection_plan" }, + ]), + vehicle +); +router.use( + "/vehicletype", + PermissionHelper.passCheckSomeMiddleware([ + { requiredPermission: "read", section: "unit", module: "vehicle_type" }, + { requiredPermission: "read", section: "unit", module: "vehicle" }, + { requiredPermission: "read", section: "unit", module: "inspection_plan" }, + ]), + vehicleType +); +router.use( + "/wearable", + PermissionHelper.passCheckSomeMiddleware([ + { requiredPermission: "read", section: "unit", module: "wearable" }, + { requiredPermission: "read", section: "unit", module: "inspection_plan" }, + ]), + wearable +); +router.use( + "/wearabletype", + PermissionHelper.passCheckSomeMiddleware([ + { requiredPermission: "read", section: "unit", module: "wearable_type" }, + { requiredPermission: "read", section: "unit", module: "wearable" }, + { requiredPermission: "read", section: "unit", module: "inspection_plan" }, + ]), + wearableType +); +router.use( + "/inspection", + PermissionHelper.passCheckSomeMiddleware([ + { requiredPermission: "read", section: "unit", module: "inspection" }, + { requiredPermission: "read", section: "unit", module: "equipment" }, + { requiredPermission: "read", section: "unit", module: "vehicle" }, + { requiredPermission: "read", section: "unit", module: "wearable" }, + ]), + inspection +); +router.use( + "/inspectionplan", + PermissionHelper.passCheckSomeMiddleware([ + { requiredPermission: "read", section: "unit", module: "inspection_plan" }, + { requiredPermission: "read", section: "unit", module: "inspection" }, + { requiredPermission: "read", section: "unit", module: "equipment_type" }, + { requiredPermission: "read", section: "unit", module: "vehicle_type" }, + { requiredPermission: "read", section: "unit", module: "wearable_type" }, + ]), + inspectionPlan +); +router.use( + "/damagereport", + PermissionHelper.passCheckSomeMiddleware([ + { requiredPermission: "read", section: "unit", module: "damage_report" }, + { requiredPermission: "read", section: "unit", module: "maintenance" }, + { requiredPermission: "read", section: "unit", module: "equipment" }, + { requiredPermission: "read", section: "unit", module: "vehicle" }, + { requiredPermission: "read", section: "unit", module: "wearable" }, + ]), + damageReport +); +router.use( + "/maintenance", + PermissionHelper.passCheckSomeMiddleware([ + { requiredPermission: "read", section: "unit", module: "maintenance" }, + { requiredPermission: "read", section: "unit", module: "equipment" }, + { requiredPermission: "read", section: "unit", module: "vehicle" }, + { requiredPermission: "read", section: "unit", module: "wearable" }, + ]), + maintenance +); +router.use( + "/repair", + PermissionHelper.passCheckSomeMiddleware([ + { requiredPermission: "read", section: "unit", module: "repair" }, + { requiredPermission: "read", section: "unit", module: "equipment" }, + { requiredPermission: "read", section: "unit", module: "vehicle" }, + { requiredPermission: "read", section: "unit", module: "wearable" }, + ]), + repair +); + export default router; diff --git a/src/routes/admin/unit/damageReport.ts b/src/routes/admin/unit/damageReport.ts new file mode 100644 index 0000000..85bd319 --- /dev/null +++ b/src/routes/admin/unit/damageReport.ts @@ -0,0 +1,62 @@ +import express, { Request, Response } from "express"; +import PermissionHelper from "../../../helpers/permissionHelper"; +import { + createInspection, + deleteInspectionById, + getAllInspectionsForRelated, + getInspectionById, + updateInspectionById, +} from "../../../controller/admin/unit/inspectionController"; +import { + getAllDamageReportsByStatus, + getAllDamageReportsForRelated, + getDamageReportById, + getDamageReportsByIds, + provideDamageReportImageUpload, + updateDamageReportById, +} from "../../../controller/admin/unit/damageReportController"; +import preventWebapiAccess from "../../../middleware/preventWebApiAccess"; + +var router = express.Router({ mergeParams: true }); + +router.get("/", async (req: Request, res: Response) => { + await getAllDamageReportsByStatus(req, res); +}); + +router.get( + ["/vehicle/:relatedId", "/equipment/:relatedId", "/wearable/:relatedId"], + async (req: Request, res: Response) => { + if (req.path.startsWith("/vehicle")) { + req.params.related = "vehicle"; + } else if (req.path.startsWith("/equipment")) { + req.params.related = "equipment"; + } else { + req.params.related = "wearable"; + } + + await getAllDamageReportsForRelated(req, res); + } +); + +router.get("/:id", async (req: Request, res: Response) => { + await getDamageReportById(req, res); +}); + +router.post("/ids", async (req: Request, res: Response) => { + await getDamageReportsByIds(req, res); +}); + +router.get("/:id/:filename", async (req: Request, res: Response) => { + await provideDamageReportImageUpload(req, res); +}); + +router.patch( + "/:id", + preventWebapiAccess, + PermissionHelper.passCheckMiddleware("update", "unit", "damage_report"), + async (req: Request, res: Response) => { + await updateDamageReportById(req, res); + } +); + +export default router; diff --git a/src/routes/admin/unit/equipment.ts b/src/routes/admin/unit/equipment.ts new file mode 100644 index 0000000..4e45a52 --- /dev/null +++ b/src/routes/admin/unit/equipment.ts @@ -0,0 +1,50 @@ +import express, { Request, Response } from "express"; +import PermissionHelper from "../../../helpers/permissionHelper"; +import { + createEquipment, + deleteEquipmentById, + getAllEquipments, + getEquipmentById, + getEquipmentsByIds, + updateEquipmentById, +} from "../../../controller/admin/unit/equipmentController"; + +var router = express.Router({ mergeParams: true }); + +router.get("/", async (req: Request, res: Response) => { + await getAllEquipments(req, res); +}); + +router.get("/:id", async (req: Request, res: Response) => { + await getEquipmentById(req, res); +}); + +router.post("/ids", async (req: Request, res: Response) => { + await getEquipmentsByIds(req, res); +}); + +router.post( + "/", + PermissionHelper.passCheckMiddleware("create", "unit", "equipment"), + async (req: Request, res: Response) => { + await createEquipment(req, res); + } +); + +router.patch( + "/:id", + PermissionHelper.passCheckMiddleware("update", "unit", "equipment"), + async (req: Request, res: Response) => { + await updateEquipmentById(req, res); + } +); + +router.delete( + "/:id", + PermissionHelper.passCheckMiddleware("delete", "unit", "equipment"), + async (req: Request, res: Response) => { + await deleteEquipmentById(req, res); + } +); + +export default router; diff --git a/src/routes/admin/unit/equipmentType.ts b/src/routes/admin/unit/equipmentType.ts new file mode 100644 index 0000000..e197e34 --- /dev/null +++ b/src/routes/admin/unit/equipmentType.ts @@ -0,0 +1,45 @@ +import express, { Request, Response } from "express"; +import PermissionHelper from "../../../helpers/permissionHelper"; +import { + createEquipmentType, + deleteEquipmentTypeById, + getAllEquipmentTypes, + getEquipmentTypeById, + updateEquipmentTypeById, +} from "../../../controller/admin/unit/equipmentTypeController"; + +var router = express.Router({ mergeParams: true }); + +router.get("/", async (req: Request, res: Response) => { + await getAllEquipmentTypes(req, res); +}); + +router.get("/:id", async (req: Request, res: Response) => { + await getEquipmentTypeById(req, res); +}); + +router.post( + "/", + PermissionHelper.passCheckMiddleware("create", "unit", "equipment_type"), + async (req: Request, res: Response) => { + await createEquipmentType(req, res); + } +); + +router.patch( + "/:id", + PermissionHelper.passCheckMiddleware("update", "unit", "equipment_type"), + async (req: Request, res: Response) => { + await updateEquipmentTypeById(req, res); + } +); + +router.delete( + "/:id", + PermissionHelper.passCheckMiddleware("delete", "unit", "equipment_type"), + async (req: Request, res: Response) => { + await deleteEquipmentTypeById(req, res); + } +); + +export default router; diff --git a/src/routes/admin/unit/inspection.ts b/src/routes/admin/unit/inspection.ts new file mode 100644 index 0000000..426e6c3 --- /dev/null +++ b/src/routes/admin/unit/inspection.ts @@ -0,0 +1,98 @@ +import express, { Request, Response } from "express"; +import PermissionHelper from "../../../helpers/permissionHelper"; +import { + createInspection, + deleteInspectionById, + getAllInspectionsSortedNotHavingNewer, + getAllInspectionsForRelated, + getInspectionById, + updateInspectionById, + getAllInspectionsRunning, + updateInspectionResults, + getInspectionPrintoutById, + finishInspection, + getInspectionPointUpload, +} from "../../../controller/admin/unit/inspectionController"; +import { inspectionFileUpload } from "../../../middleware/multer"; +import preventWebapiAccess from "../../../middleware/preventWebApiAccess"; + +var router = express.Router({ mergeParams: true }); + +router.get("/next", async (req: Request, res: Response) => { + await getAllInspectionsSortedNotHavingNewer(req, res); +}); + +router.get("/running", async (req: Request, res: Response) => { + await getAllInspectionsRunning(req, res); +}); + +router.get( + ["/vehicle/:relatedId", "/equipment/:relatedId", "/wearable/:relatedId"], + async (req: Request, res: Response) => { + if (req.path.startsWith("/vehicle")) { + req.params.related = "vehicle"; + } else if (req.path.startsWith("/equipment")) { + req.params.related = "equipment"; + } else { + req.params.related = "wearable"; + } + + await getAllInspectionsForRelated(req, res); + } +); + +router.get("/:id", async (req: Request, res: Response) => { + await getInspectionById(req, res); +}); + +router.get("/:id/printout", async (req: Request, res: Response) => { + await getInspectionPrintoutById(req, res); +}); + +router.get("/:id/:pointId/upload", async (req: Request, res: Response) => { + await getInspectionPointUpload(req, res); +}); + +router.post( + "/", + PermissionHelper.passCheckMiddleware("create", "unit", "inspection"), + async (req: Request, res: Response) => { + await createInspection(req, res); + } +); + +router.patch( + "/:id", + PermissionHelper.passCheckMiddleware("update", "unit", "inspection"), + async (req: Request, res: Response) => { + await updateInspectionById(req, res); + } +); + +router.patch( + "/:id/results", + PermissionHelper.passCheckMiddleware("update", "unit", "inspection"), + inspectionFileUpload, + async (req: Request, res: Response) => { + await updateInspectionResults(req, res); + } +); + +router.patch( + "/:id/finish", + preventWebapiAccess, + PermissionHelper.passCheckMiddleware("update", "unit", "inspection"), + async (req: Request, res: Response) => { + await finishInspection(req, res); + } +); + +router.delete( + "/:id", + PermissionHelper.passCheckMiddleware("delete", "unit", "inspection"), + async (req: Request, res: Response) => { + await deleteInspectionById(req, res); + } +); + +export default router; diff --git a/src/routes/admin/unit/inspectionPlan.ts b/src/routes/admin/unit/inspectionPlan.ts new file mode 100644 index 0000000..914d9d3 --- /dev/null +++ b/src/routes/admin/unit/inspectionPlan.ts @@ -0,0 +1,75 @@ +import express, { Request, Response } from "express"; +import PermissionHelper from "../../../helpers/permissionHelper"; +import { + createInspectionPlan, + deleteInspectionPlanById, + getAllInspectionPlans, + getAllInspectionPlansForRelated, + getInspectionPlanById, + getInspectionPointsByPlanId, + updateInspectionPlanById, + updateInspectionPointsByPlanId, +} from "../../../controller/admin/unit/inspectionPlanController"; + +var router = express.Router({ mergeParams: true }); + +router.get("/", async (req: Request, res: Response) => { + await getAllInspectionPlans(req, res); +}); + +router.get("/:id/points", async (req: Request, res: Response) => { + await getInspectionPointsByPlanId(req, res); +}); + +router.get( + ["/vehicleType/:relatedTypeId", "/equipmentType/:relatedTypeId", "/wearableType/:relatedTypeId"], + async (req: Request, res: Response) => { + if (req.path.startsWith("/vehicleType")) { + req.params.related = "vehicleType"; + } else if (req.path.startsWith("/equipmentType")) { + req.params.related = "equipmentType"; + } else { + req.params.related = "wearableType"; + } + + await getAllInspectionPlansForRelated(req, res); + } +); + +router.get("/:id", async (req: Request, res: Response) => { + await getInspectionPlanById(req, res); +}); + +router.post( + "/", + PermissionHelper.passCheckMiddleware("create", "unit", "inspection_plan"), + async (req: Request, res: Response) => { + await createInspectionPlan(req, res); + } +); + +router.patch( + "/:id", + PermissionHelper.passCheckMiddleware("update", "unit", "inspection_plan"), + async (req: Request, res: Response) => { + await updateInspectionPlanById(req, res); + } +); + +router.patch( + "/:id/points", + PermissionHelper.passCheckMiddleware("update", "unit", "inspection_plan"), + async (req: Request, res: Response) => { + await updateInspectionPointsByPlanId(req, res); + } +); + +router.delete( + "/:id", + PermissionHelper.passCheckMiddleware("delete", "unit", "inspection_plan"), + async (req: Request, res: Response) => { + await deleteInspectionPlanById(req, res); + } +); + +export default router; diff --git a/src/routes/admin/unit/maintenance.ts b/src/routes/admin/unit/maintenance.ts new file mode 100644 index 0000000..3719eee --- /dev/null +++ b/src/routes/admin/unit/maintenance.ts @@ -0,0 +1,43 @@ +import express, { Request, Response } from "express"; +import PermissionHelper from "../../../helpers/permissionHelper"; +import { + getAllMaintenancesByStatus, + getAllMaintenancesForRelated, + getMaintenanceById, + updateMaintenanceById, +} from "../../../controller/admin/unit/maintenanceController"; + +var router = express.Router({ mergeParams: true }); + +router.get("/", async (req: Request, res: Response) => { + await getAllMaintenancesByStatus(req, res); +}); + +router.get( + ["/vehicle/:relatedId", "/equipment/:relatedId", "/wearable/:relatedId"], + async (req: Request, res: Response) => { + if (req.path.startsWith("/vehicle")) { + req.params.related = "vehicle"; + } else if (req.path.startsWith("/equipment")) { + req.params.related = "equipment"; + } else { + req.params.related = "wearable"; + } + + await getAllMaintenancesForRelated(req, res); + } +); + +router.get("/:id", async (req: Request, res: Response) => { + await getMaintenanceById(req, res); +}); + +router.patch( + "/:id", + PermissionHelper.passCheckMiddleware("update", "unit", "maintenance"), + async (req: Request, res: Response) => { + await updateMaintenanceById(req, res); + } +); + +export default router; diff --git a/src/routes/admin/unit/repair.ts b/src/routes/admin/unit/repair.ts new file mode 100644 index 0000000..89c0a82 --- /dev/null +++ b/src/routes/admin/unit/repair.ts @@ -0,0 +1,72 @@ +import express, { Request, Response } from "express"; +import PermissionHelper from "../../../helpers/permissionHelper"; +import { + createRepair, + getAllRepairsByStatus, + getAllRepairsForRelated, + getRepairById, + updateRepairById, + updateRepairReportsById, + updateRepairStatusById, +} from "../../../controller/admin/unit/repairController"; +import preventWebapiAccess from "../../../middleware/preventWebApiAccess"; + +var router = express.Router({ mergeParams: true }); + +router.get("/", async (req: Request, res: Response) => { + await getAllRepairsByStatus(req, res); +}); + +router.get( + ["/vehicle/:relatedId", "/equipment/:relatedId", "/wearable/:relatedId"], + async (req: Request, res: Response) => { + if (req.path.startsWith("/vehicle")) { + req.params.related = "vehicle"; + } else if (req.path.startsWith("/equipment")) { + req.params.related = "equipment"; + } else { + req.params.related = "wearable"; + } + + await getAllRepairsForRelated(req, res); + } +); + +router.get("/:id", async (req: Request, res: Response) => { + await getRepairById(req, res); +}); + +router.post( + "/", + PermissionHelper.passCheckMiddleware("update", "unit", "repair"), + async (req: Request, res: Response) => { + await createRepair(req, res); + } +); + +router.patch( + "/:id", + PermissionHelper.passCheckMiddleware("update", "unit", "repair"), + async (req: Request, res: Response) => { + await updateRepairById(req, res); + } +); + +router.patch( + "/:id/reports", + PermissionHelper.passCheckMiddleware("update", "unit", "repair"), + async (req: Request, res: Response) => { + await updateRepairReportsById(req, res); + } +); + +router.patch( + "/:id/status", + preventWebapiAccess, + PermissionHelper.passCheckMiddleware("update", "unit", "repair"), + async (req: Request, res: Response) => { + await updateRepairStatusById(req, res); + } +); + +export default router; diff --git a/src/routes/admin/unit/vehicle.ts b/src/routes/admin/unit/vehicle.ts new file mode 100644 index 0000000..7ca588c --- /dev/null +++ b/src/routes/admin/unit/vehicle.ts @@ -0,0 +1,50 @@ +import express, { Request, Response } from "express"; +import PermissionHelper from "../../../helpers/permissionHelper"; +import { + createVehicle, + deleteVehicleById, + getAllVehicles, + getVehicleById, + getVehiclesByIds, + updateVehicleById, +} from "../../../controller/admin/unit/vehicleController"; + +var router = express.Router({ mergeParams: true }); + +router.get("/", async (req: Request, res: Response) => { + await getAllVehicles(req, res); +}); + +router.get("/:id", async (req: Request, res: Response) => { + await getVehicleById(req, res); +}); + +router.post("/ids", async (req: Request, res: Response) => { + await getVehiclesByIds(req, res); +}); + +router.post( + "/", + PermissionHelper.passCheckMiddleware("create", "unit", "vehicle"), + async (req: Request, res: Response) => { + await createVehicle(req, res); + } +); + +router.patch( + "/:id", + PermissionHelper.passCheckMiddleware("update", "unit", "vehicle"), + async (req: Request, res: Response) => { + await updateVehicleById(req, res); + } +); + +router.delete( + "/:id", + PermissionHelper.passCheckMiddleware("delete", "unit", "vehicle"), + async (req: Request, res: Response) => { + await deleteVehicleById(req, res); + } +); + +export default router; diff --git a/src/routes/admin/unit/vehicleType.ts b/src/routes/admin/unit/vehicleType.ts new file mode 100644 index 0000000..03f2989 --- /dev/null +++ b/src/routes/admin/unit/vehicleType.ts @@ -0,0 +1,45 @@ +import express, { Request, Response } from "express"; +import PermissionHelper from "../../../helpers/permissionHelper"; +import { + createVehicleType, + deleteVehicleTypeById, + getAllVehicleTypes, + getVehicleTypeById, + updateVehicleTypeById, +} from "../../../controller/admin/unit/vehicleTypeController"; + +var router = express.Router({ mergeParams: true }); + +router.get("/", async (req: Request, res: Response) => { + await getAllVehicleTypes(req, res); +}); + +router.get("/:id", async (req: Request, res: Response) => { + await getVehicleTypeById(req, res); +}); + +router.post( + "/", + PermissionHelper.passCheckMiddleware("create", "unit", "vehicle_type"), + async (req: Request, res: Response) => { + await createVehicleType(req, res); + } +); + +router.patch( + "/:id", + PermissionHelper.passCheckMiddleware("update", "unit", "vehicle_type"), + async (req: Request, res: Response) => { + await updateVehicleTypeById(req, res); + } +); + +router.delete( + "/:id", + PermissionHelper.passCheckMiddleware("delete", "unit", "vehicle_type"), + async (req: Request, res: Response) => { + await deleteVehicleTypeById(req, res); + } +); + +export default router; diff --git a/src/routes/admin/unit/wearable.ts b/src/routes/admin/unit/wearable.ts new file mode 100644 index 0000000..e1c1730 --- /dev/null +++ b/src/routes/admin/unit/wearable.ts @@ -0,0 +1,50 @@ +import express, { Request, Response } from "express"; +import PermissionHelper from "../../../helpers/permissionHelper"; +import { + createWearable, + deleteWearableById, + getAllWearables, + getWearableById, + getWearablesByIds, + updateWearableById, +} from "../../../controller/admin/unit/wearableController"; + +var router = express.Router({ mergeParams: true }); + +router.get("/", async (req: Request, res: Response) => { + await getAllWearables(req, res); +}); + +router.get("/:id", async (req: Request, res: Response) => { + await getWearableById(req, res); +}); + +router.post("/ids", async (req: Request, res: Response) => { + await getWearablesByIds(req, res); +}); + +router.post( + "/", + PermissionHelper.passCheckMiddleware("create", "unit", "wearable"), + async (req: Request, res: Response) => { + await createWearable(req, res); + } +); + +router.patch( + "/:id", + PermissionHelper.passCheckMiddleware("update", "unit", "wearable"), + async (req: Request, res: Response) => { + await updateWearableById(req, res); + } +); + +router.delete( + "/:id", + PermissionHelper.passCheckMiddleware("delete", "unit", "wearable"), + async (req: Request, res: Response) => { + await deleteWearableById(req, res); + } +); + +export default router; diff --git a/src/routes/admin/unit/wearableType.ts b/src/routes/admin/unit/wearableType.ts new file mode 100644 index 0000000..983e401 --- /dev/null +++ b/src/routes/admin/unit/wearableType.ts @@ -0,0 +1,45 @@ +import express, { Request, Response } from "express"; +import PermissionHelper from "../../../helpers/permissionHelper"; +import { + createWearableType, + deleteWearableTypeById, + getAllWearableTypes, + getWearableTypeById, + updateWearableTypeById, +} from "../../../controller/admin/unit/wearableTypeController"; + +var router = express.Router({ mergeParams: true }); + +router.get("/", async (req: Request, res: Response) => { + await getAllWearableTypes(req, res); +}); + +router.get("/:id", async (req: Request, res: Response) => { + await getWearableTypeById(req, res); +}); + +router.post( + "/", + PermissionHelper.passCheckMiddleware("create", "unit", "wearable_type"), + async (req: Request, res: Response) => { + await createWearableType(req, res); + } +); + +router.patch( + "/:id", + PermissionHelper.passCheckMiddleware("update", "unit", "wearable_type"), + async (req: Request, res: Response) => { + await updateWearableTypeById(req, res); + } +); + +router.delete( + "/:id", + PermissionHelper.passCheckMiddleware("delete", "unit", "wearable_type"), + async (req: Request, res: Response) => { + await deleteWearableTypeById(req, res); + } +); + +export default router; diff --git a/src/routes/public.ts b/src/routes/public.ts index d557dd7..ac39669 100644 --- a/src/routes/public.ts +++ b/src/routes/public.ts @@ -1,12 +1,16 @@ import express from "express"; import { + checkScannerRoomExists, + createDamageReport, getApplicationConfig, getApplicationFavicon, getApplicationIcon, getApplicationLogo, getApplicationManifest, getCalendarItemsByTypes, + searchStuffByCode, } from "../controller/publicController"; +import { pDamageReportFileUpload } from "../middleware/multer"; var router = express.Router({ mergeParams: true }); @@ -14,6 +18,18 @@ router.get("/calendar", async (req, res) => { await getCalendarItemsByTypes(req, res); }); +router.get("/reportdamage", async (req, res) => { + await searchStuffByCode(req, res); +}); + +router.post("/reportdamage", pDamageReportFileUpload, async (req, res) => { + await createDamageReport(req, res); +}); + +router.post("/checkscannerroom", async (req, res) => { + await checkScannerRoomExists(req, res); +}); + router.get("/configuration", async (req, res) => { await getApplicationConfig(req, res); }); diff --git a/src/service/unit/damageReportService.ts b/src/service/unit/damageReportService.ts new file mode 100644 index 0000000..2a56e27 --- /dev/null +++ b/src/service/unit/damageReportService.ts @@ -0,0 +1,167 @@ +import { In, IsNull, Not } from "typeorm"; +import { dataSource } from "../../data-source"; +import { damageReport } from "../../entity/unit/damageReport"; +import DatabaseActionException from "../../exceptions/databaseActionException"; + +export default abstract class DamageReportService { + private static query = () => + dataSource + .getRepository(damageReport) + .createQueryBuilder("damageReport") + .leftJoinAndSelect("damageReport.equipment", "equipment") + .leftJoinAndSelect("damageReport.vehicle", "vehicle") + .leftJoinAndSelect("damageReport.wearable", "wearable") + .leftJoinAndSelect("damageReport.repair", "repair") + .leftJoinAndSelect("damageReport.closedBy", "user"); + + /** + * @description get all damageReports By done + * @returns {Promise<[Array, number]>} + */ + static async getAll({ + offset = 0, + count = 25, + noLimit = false, + ids = [], + }: { + offset?: number; + count?: number; + noLimit?: boolean; + ids?: Array; + }): Promise<[Array, number]> { + let query = this.query(); + + if (ids.length != 0) { + query = query.where({ id: In(ids) }); + } + + if (!noLimit) { + query = query.offset(offset).limit(count); + } + + return await query + .orderBy("damageReport.reportedAt", "ASC") + .getManyAndCount() + .then((res) => { + return res; + }) + .catch((err) => { + throw new DatabaseActionException("SELECT", "damageReport", err); + }); + } + + /** + * @description get all damageReports By done + * @returns {Promise<[Array, number]>} + */ + static async getAllByStatus( + done = false, + { + offset = 0, + count = 25, + noLimit = false, + }: { + offset?: number; + count?: number; + noLimit?: boolean; + } + ): Promise<[Array, number]> { + let query = this.query().where({ closedAt: done ? Not(IsNull()) : IsNull() }); + + if (!noLimit) { + query = query.offset(offset).limit(count); + } + + return await query + .orderBy("damageReport.reportedAt", "ASC") + .getManyAndCount() + .then((res) => { + return res; + }) + .catch((err) => { + throw new DatabaseActionException("SELECT", "damageReport", err); + }); + } + + /** + * @description get all damageReports By related + * @returns {Promise<[Array, number]>} + */ + static async getAllForRelated( + where: { equipmentId: string } | { vehicleId: string } | { wearableId: string }, + { + offset = 0, + count = 25, + noLimit = false, + }: { + offset?: number; + count?: number; + noLimit?: boolean; + } + ): Promise<[Array, number]> { + let query = this.query().where(where); + + if (!noLimit) { + query = query.offset(offset).limit(count); + } + + return await query + .orderBy("damageReport.reportedAt", "ASC") + .getManyAndCount() + .then((res) => { + return res; + }) + .catch((err) => { + throw new DatabaseActionException("SELECT", "damageReport", err); + }); + } + + /** + * @description get all damageReport for repair + * @returns {Promise<[Array, number]>} + */ + static async getAllForRepair( + repairId: string, + { + offset = 0, + count = 25, + noLimit = false, + }: { + offset?: number; + count?: number; + noLimit?: boolean; + } + ): Promise<[Array, number]> { + let query = this.query().where({ repairId }); + + if (!noLimit) { + query = query.offset(offset).limit(count); + } + + return await query + .orderBy("damageReport.reportedAt", "ASC") + .getManyAndCount() + .then((res) => { + return res; + }) + .catch((err) => { + throw new DatabaseActionException("SELECT", "damageReport", err); + }); + } + + /** + * @description get damageReport by id + * @returns {Promise} + */ + static async getById(id: string): Promise { + return await this.query() + .where({ id }) + .getOneOrFail() + .then((res) => { + return res; + }) + .catch((err) => { + throw new DatabaseActionException("SELECT", "damageReport", err); + }); + } +} diff --git a/src/service/unit/equipment/equipmentService.ts b/src/service/unit/equipment/equipmentService.ts new file mode 100644 index 0000000..cb0cb2c --- /dev/null +++ b/src/service/unit/equipment/equipmentService.ts @@ -0,0 +1,99 @@ +import { In, Like } from "typeorm"; +import { dataSource } from "../../../data-source"; +import { equipment } from "../../../entity/unit/equipment/equipment"; +import DatabaseActionException from "../../../exceptions/databaseActionException"; + +export default abstract class EquipmentService { + /** + * @description get all equipment + * @returns {Promise<[Array, number]>} + */ + static async getAll({ + offset = 0, + count = 25, + search = "", + noLimit = false, + ids = [], + }: { + offset?: number; + count?: number; + search?: string; + noLimit?: boolean; + ids?: Array; + }): Promise<[Array, number]> { + let query = dataSource + .getRepository(equipment) + .createQueryBuilder("equipment") + .leftJoinAndSelect("equipment.equipmentType", "equipmenttype"); + + if (search != "") { + query = query + .where({ + code: Like(`%${search}%`), + }) + .orWhere({ + name: Like(`%${search}%`), + }) + .orWhere({ + location: Like(`%${search}%`), + }); + } + + if (ids.length != 0) { + query = query.where({ id: In(ids) }); + } + + if (!noLimit) { + query = query.offset(offset).limit(count); + } + + return await query + .orderBy("name", "ASC") + .getManyAndCount() + .then((res) => { + return res; + }) + .catch((err) => { + throw new DatabaseActionException("SELECT", "equipment", err); + }); + } + + /** + * @description get equipment by code + * @returns {Promise>} + */ + static async getAllByCode(code: string): Promise> { + return await dataSource + .getRepository(equipment) + .createQueryBuilder("equipment") + .leftJoinAndSelect("equipment.equipmentType", "equipmenttype") + .where({ code: Like(`%${code}%`) }) + .orderBy("name", "ASC") + .getMany() + .then((res) => { + return res; + }) + .catch((err) => { + throw new DatabaseActionException("SELECT", "equipment", err); + }); + } + + /** + * @description get equipment by id + * @returns {Promise} + */ + static async getById(id: string): Promise { + return await dataSource + .getRepository(equipment) + .createQueryBuilder("equipment") + .leftJoinAndSelect("equipment.equipmentType", "equipmenttype") + .where({ id }) + .getOneOrFail() + .then((res) => { + return res; + }) + .catch((err) => { + throw new DatabaseActionException("SELECT", "equipment", err); + }); + } +} diff --git a/src/service/unit/equipment/equipmentTypeService.ts b/src/service/unit/equipment/equipmentTypeService.ts new file mode 100644 index 0000000..6fa4bfd --- /dev/null +++ b/src/service/unit/equipment/equipmentTypeService.ts @@ -0,0 +1,66 @@ +import { Like, In } from "typeorm"; +import { dataSource } from "../../../data-source"; +import { equipmentType } from "../../../entity/unit/equipment/equipmentType"; +import DatabaseActionException from "../../../exceptions/databaseActionException"; + +export default abstract class EquipmentTypeService { + /** + * @description get all equipmentTypes + * @returns {Promise<[Array, number]>} + */ + static async getAll({ + offset = 0, + count = 25, + search = "", + noLimit = false, + }: { + offset?: number; + count?: number; + search?: string; + noLimit?: boolean; + }): Promise<[Array, number]> { + let query = dataSource + .getRepository(equipmentType) + .createQueryBuilder("equipmentType") + .loadRelationCountAndMap("equipmentType.equipmentCount", "equipmentType.equipment"); + + if (search != "") { + query = query.where({ + type: Like(`%${search}%`), + }); + } + + if (!noLimit) { + query = query.offset(offset).limit(count); + } + + return await query + .orderBy("type", "ASC") + .getManyAndCount() + .then((res) => { + return res; + }) + .catch((err) => { + throw new DatabaseActionException("SELECT", "equipmentType", err); + }); + } + + /** + * @description get equipmentType by id + * @returns {Promise} + */ + static async getById(id: string): Promise { + return await dataSource + .getRepository(equipmentType) + .createQueryBuilder("equipmentType") + .loadRelationCountAndMap("equipmentType.equipmentCount", "equipmentType.equipment") + .where({ id }) + .getOneOrFail() + .then((res) => { + return res; + }) + .catch((err) => { + throw new DatabaseActionException("SELECT", "equipmentType", err); + }); + } +} diff --git a/src/service/unit/inspection/inspectionPlanService.ts b/src/service/unit/inspection/inspectionPlanService.ts new file mode 100644 index 0000000..0ee2e89 --- /dev/null +++ b/src/service/unit/inspection/inspectionPlanService.ts @@ -0,0 +1,128 @@ +import { Like, In } from "typeorm"; +import { dataSource } from "../../../data-source"; +import { inspectionPlan } from "../../../entity/unit/inspection/inspectionPlan"; +import DatabaseActionException from "../../../exceptions/databaseActionException"; + +export default abstract class InspectionPlanService { + private static query = () => + dataSource + .getRepository(inspectionPlan) + .createQueryBuilder("inspectionPlan") + .leftJoinAndMapOne( + "inspectionPlan.latestVersionedPlan", + "inspectionPlan.versionedPlans", + "latestVersionedPlan", + 'latestVersionedPlan.inspectionPlanId = inspectionPlan.id AND latestVersionedPlan.version = (SELECT MAX("ivp"."version") FROM "inspection_versioned_plan" "ivp" WHERE "ivp"."inspectionPlanId" = "inspectionPlan"."id")' + ) + .leftJoinAndSelect("latestVersionedPlan.inspectionPoints", "inspectionPoints") + .leftJoinAndSelect("inspectionPlan.equipmentType", "equipmentType") + .leftJoinAndSelect("inspectionPlan.vehicleType", "vehicleType") + .leftJoinAndSelect("inspectionPlan.wearableType", "wearableType"); + + /** + * @description get all inspectionPlans for related + * @returns {Promise<[Array, number]>} + */ + static async getAll({ + offset = 0, + count = 25, + search = "", + noLimit = false, + ids = [], + }: { + offset?: number; + count?: number; + search?: string; + noLimit?: boolean; + ids?: Array; + }): Promise<[Array, number]> { + let query = this.query(); + + if (search != "") { + query = query.where({ + title: Like(`%${search}%`), + }); + } + + if (ids.length != 0) { + query = query.where({ id: In(ids) }); + } + + if (!noLimit) { + query = query.offset(offset).limit(count); + } + + return await query + .orderBy("inspectionPlan.title", "ASC") + .getManyAndCount() + .then((res) => { + return res; + }) + .catch((err) => { + throw new DatabaseActionException("SELECT", "inspectionPlan", err); + }); + } + + /** + * @description get all inspectionPlans for related + * @returns {Promise<[Array, number]>} + */ + static async getAllForRelated( + where: { equipmentTypeId: string } | { vehicleTypeId: string } | { wearableTypeId: string }, + { + offset = 0, + count = 25, + search = "", + noLimit = false, + ids = [], + }: { + offset?: number; + count?: number; + search?: string; + noLimit?: boolean; + ids?: Array; + } + ): Promise<[Array, number]> { + let query = this.query().where(where); + + if (search != "") { + query = query.andWhere({ + title: Like(`%${search}%`), + }); + } + + if (ids.length != 0) { + query = query.andWhere({ id: In(ids) }); + } + + if (!noLimit) { + query = query.offset(offset).limit(count); + } + + return await query + .orderBy("inspectionPlan.title", "ASC") + .getManyAndCount() + .then((res) => { + return res; + }) + .catch((err) => { + throw new DatabaseActionException("SELECT", "inspectionPlan", err); + }); + } + + /** + * @description get inspectionPlan by id + * @returns {Promise} + */ + static async getById(id: string): Promise { + return await this.query() + .where({ id }) + .getOneOrFail() + .then((res) => { + return res; + }) + .catch((err) => { + throw new DatabaseActionException("SELECT", "inspectionPlan", err); + }); + } +} diff --git a/src/service/unit/inspection/inspectionPointResultService.ts b/src/service/unit/inspection/inspectionPointResultService.ts new file mode 100644 index 0000000..deb56a8 --- /dev/null +++ b/src/service/unit/inspection/inspectionPointResultService.ts @@ -0,0 +1,45 @@ +import { dataSource } from "../../../data-source"; +import { inspectionPointResult } from "../../../entity/unit/inspection/inspectionPointResult"; +import DatabaseActionException from "../../../exceptions/databaseActionException"; + +export default abstract class InspectionPointResultService { + /** + * @description get all inspectionPointResults + * @returns {Promise>} + */ + static async getAllForInspection(inspectionId: string): Promise> { + return await dataSource + .getRepository(inspectionPointResult) + .createQueryBuilder("inspectionPointResult") + .leftJoinAndSelect("inspectionPointResult.inspectionPoint", "inspectionPoint") + .where({ inspectionId }) + .getMany() + .then((res) => { + return res; + }) + .catch((err) => { + throw new DatabaseActionException("SELECT", "inspectionPointResult", err); + }); + } + /** + * @description get inspectionPointResults by inspection and point + * @returns {Promise>} + */ + static async getForInspectionAndPoint( + inspectionId: string, + inspectionPointId: string + ): Promise { + return await dataSource + .getRepository(inspectionPointResult) + .createQueryBuilder("inspectionPointResult") + .leftJoinAndSelect("inspectionPointResult.inspectionPoint", "inspectionPoint") + .where({ inspectionId, inspectionPointId }) + .getOneOrFail() + .then((res) => { + return res; + }) + .catch((err) => { + throw new DatabaseActionException("SELECT", "inspectionPointResult", err); + }); + } +} diff --git a/src/service/unit/inspection/inspectionPointService.ts b/src/service/unit/inspection/inspectionPointService.ts new file mode 100644 index 0000000..0285c9f --- /dev/null +++ b/src/service/unit/inspection/inspectionPointService.ts @@ -0,0 +1,42 @@ +import { dataSource } from "../../../data-source"; +import { inspectionPoint } from "../../../entity/unit/inspection/inspectionPoint"; +import DatabaseActionException from "../../../exceptions/databaseActionException"; + +export default abstract class InspectionPointService { + /** + * @description get all inspectionPoints + * @returns {Promise>} + */ + static async getAllForVersionedPlan(versionedPlanId: string): Promise> { + return await dataSource + .getRepository(inspectionPoint) + .createQueryBuilder("inspectionPoint") + .where({ versionedPlanId }) + .orderBy("sort", "ASC") + .getMany() + .then((res) => { + return res; + }) + .catch((err) => { + throw new DatabaseActionException("SELECT", "inspectionPoint", err); + }); + } + + /** + * @description get inspectionPoint by id + * @returns {Promise} + */ + static async getById(id: string): Promise { + return await dataSource + .getRepository(inspectionPoint) + .createQueryBuilder("inspectionPoint") + .where({ id }) + .getOneOrFail() + .then((res) => { + return res; + }) + .catch((err) => { + throw new DatabaseActionException("SELECT", "inspectionPoint", err); + }); + } +} diff --git a/src/service/unit/inspection/inspectionService.ts b/src/service/unit/inspection/inspectionService.ts new file mode 100644 index 0000000..b4df294 --- /dev/null +++ b/src/service/unit/inspection/inspectionService.ts @@ -0,0 +1,192 @@ +import { IsNull, Not } from "typeorm"; +import { dataSource } from "../../../data-source"; +import { inspection } from "../../../entity/unit/inspection/inspection"; +import DatabaseActionException from "../../../exceptions/databaseActionException"; + +export default abstract class InspectionService { + private static query = () => + dataSource + .getRepository(inspection) + .createQueryBuilder("inspection") + .leftJoinAndSelect("inspection.inspectionPlan", "inspectionPlan") + .leftJoinAndSelect("inspectionPlan.equipmentType", "equipmentType") + .leftJoinAndSelect("inspectionPlan.vehicleType", "vehicleType") + .leftJoinAndSelect("inspectionPlan.wearableType", "wearableType") + .leftJoinAndSelect("inspection.inspectionVersionedPlan", "inspectionVersionedPlan") + .leftJoinAndSelect("inspectionVersionedPlan.inspectionPoints", "inspectionPoints") + .leftJoinAndSelect("inspection.pointResults", "pointResults") + .leftJoinAndSelect("pointResults.inspectionPoint", "inspectionPoint") + .leftJoinAndSelect("inspection.equipment", "equipment") + .leftJoinAndSelect("inspection.vehicle", "vehicle") + .leftJoinAndSelect("inspection.wearable", "wearable") + .leftJoinAndSelect("inspection.finishedBy", "user"); + + private static minifiedQuery = () => + dataSource + .getRepository(inspection) + .createQueryBuilder("inspection") + .leftJoinAndSelect("inspection.inspectionPlan", "inspectionPlan") + .leftJoinAndSelect("inspectionPlan.equipmentType", "equipmentType") + .leftJoinAndSelect("inspectionPlan.vehicleType", "vehicleType") + .leftJoinAndSelect("inspectionPlan.wearableType", "wearableType") + .leftJoinAndSelect("inspection.equipment", "equipment") + .leftJoinAndSelect("inspection.vehicle", "vehicle") + .leftJoinAndSelect("inspection.wearable", "wearable"); + + /** + * @description get all inspections sorted by next inspection not having newer + * @returns {Promise>} + */ + static async getAllSortedNotHavingNewer({ + offset = 0, + count = 25, + noLimit = false, + }: { + offset?: number; + count?: number; + noLimit?: boolean; + }): Promise<[Array, number]> { + let query = this.query().where({ hasNewer: false }); + + if (!noLimit) { + query = query.offset(offset).limit(count); + } + + return await query + .orderBy("inspection.nextInspection", "ASC", "NULLS LAST") + .getManyAndCount() + .then((res) => { + return res; + }) + .catch((err) => { + throw new DatabaseActionException("SELECT", "inspection", err); + }); + } + + /** + * @description get all inspections running + * @returns {Promise>} + */ + static async getAllRunning({ + offset = 0, + count = 25, + noLimit = false, + }: { + offset?: number; + count?: number; + noLimit?: boolean; + }): Promise<[Array, number]> { + let query = this.minifiedQuery().where({ finishedAt: IsNull() }); + + if (!noLimit) { + query = query.offset(offset).limit(count); + } + + return await query + .orderBy("inspection.createdAt", "ASC") + .getManyAndCount() + .then((res) => { + return res; + }) + .catch((err) => { + throw new DatabaseActionException("SELECT", "inspection", err); + }); + } + + /** + * @description get all inspections for related + * @returns {Promise>} + */ + static async getAllForRelated( + where: { equipmentId: string } | { vehicleId: string } | { wearableId: string }, + { + offset = 0, + count = 25, + noLimit = false, + }: { + offset?: number; + count?: number; + noLimit?: boolean; + } + ): Promise<[Array, number]> { + let query = this.minifiedQuery().where(where); + + if (!noLimit) { + query = query.offset(offset).limit(count); + } + + return await query + .orderBy("inspection.createdAt", "DESC") + .getManyAndCount() + .then((res) => { + return res; + }) + .catch((err) => { + throw new DatabaseActionException("SELECT", "inspection", err); + }); + } + + /** + * @description get inspection by id + * @returns {Promise} + */ + static async getById(id: string): Promise { + return await this.query() + .where({ id }) + .getOneOrFail() + .then((res) => { + return res; + }) + .catch((err) => { + throw new DatabaseActionException("SELECT", "inspection", err); + }); + } + + /** + * @description uses versionedPlan + * @returns {Promise} + */ + static async existsUnfinishedInspectionToPlan( + inspectionPlanId: string, + relation: "vehicle" | "equipment" | "wearable", + relationId: string + ): Promise { + let where: { equipmentId: string } | { vehicleId: string } | { wearableId: string }; + if (relation == "equipment") { + where = { equipmentId: relationId }; + } else if (relation == "vehicle") { + where = { vehicleId: relationId }; + } else { + where = { wearableId: relationId }; + } + return await dataSource + .getRepository(inspection) + .createQueryBuilder("inspection") + .where({ inspectionPlanId, finishedAt: IsNull(), ...where }) + .getExists() + .then((res) => { + return res; + }) + .catch((err) => { + throw new DatabaseActionException("SELECT", "used inspection", err); + }); + } + + /** + * @description uses versionedPlan + * @returns {Promise} + */ + static async usesVersionedInspectionPlan(inspectionVersionedPlanId: string): Promise { + return await dataSource + .getRepository(inspection) + .createQueryBuilder("inspection") + .where({ inspectionVersionedPlanId }) + .getExists() + .then((res) => { + return res; + }) + .catch((err) => { + throw new DatabaseActionException("SELECT", "used inspection", err); + }); + } +} diff --git a/src/service/unit/inspection/inspectionVersionedPlanService.ts b/src/service/unit/inspection/inspectionVersionedPlanService.ts new file mode 100644 index 0000000..00f7500 --- /dev/null +++ b/src/service/unit/inspection/inspectionVersionedPlanService.ts @@ -0,0 +1,83 @@ +import { dataSource } from "../../../data-source"; +import { inspectionVersionedPlan } from "../../../entity/unit/inspection/inspectionVersionedPlan"; +import DatabaseActionException from "../../../exceptions/databaseActionException"; + +export default abstract class InspectionVersionedPlanService { + /** + * @description get all inspectionVersionedPlans + * @returns {Promise>} + */ + static async getAllForInspectionPlan(inspectionPlanId: string): Promise> { + return await dataSource + .getRepository(inspectionVersionedPlan) + .createQueryBuilder("inspectionVersionedPlan") + .leftJoinAndSelect("inspectionVersionedPlan.inspectionPoints", "inspectionPoints") + .where({ inspectionPlanId }) + .orderBy("version", "ASC") + .getMany() + .then((res) => { + return res; + }) + .catch((err) => { + throw new DatabaseActionException("SELECT", "inspectionVersionedPlan", err); + }); + } + + /** + * @description get latest inspectionVersionedPlan for plan + * @returns {Promise>} + */ + static async getLatestForInspectionPlan(inspectionPlanId: string): Promise { + return await dataSource + .getRepository(inspectionVersionedPlan) + .createQueryBuilder("inspectionVersionedPlan") + .leftJoinAndSelect("inspectionVersionedPlan.inspectionPoints", "inspectionPoints") + .where({ inspectionPlanId }) + .orderBy("inspectionVersionedPlan.version", "DESC") + .limit(1) + .getOneOrFail() + .then((res) => { + return res; + }) + .catch((err) => { + throw new DatabaseActionException("SELECT", "inspectionVersionedPlan", err); + }); + } + + /** + * @description get inspectionVersionedPlan by id + * @returns {Promise} + */ + static async getById(id: string): Promise { + return await dataSource + .getRepository(inspectionVersionedPlan) + .createQueryBuilder("inspectionVersionedPlan") + .leftJoinAndSelect("inspectionVersionedPlan.inspectionPoints", "inspectionPoints") + .where({ id }) + .getOneOrFail() + .then((res) => { + return res; + }) + .catch((err) => { + throw new DatabaseActionException("SELECT", "inspectionVersionedPlan", err); + }); + } + + /** + * @description count for plan id + * @returns {Promise} + */ + static async countForPlanId(planId: string): Promise { + return await dataSource + .getRepository(inspectionVersionedPlan) + .createQueryBuilder("inspectionVersionedPlan") + .where({ inspectionPlanId: planId }) + .getCount() + .then((res) => { + return res; + }) + .catch((err) => { + throw new DatabaseActionException("SELECT", "inspectionVersionedPlan", err); + }); + } +} diff --git a/src/service/unit/maintenanceService.ts b/src/service/unit/maintenanceService.ts new file mode 100644 index 0000000..fa2adbe --- /dev/null +++ b/src/service/unit/maintenanceService.ts @@ -0,0 +1,96 @@ +import { Not, IsNull } from "typeorm"; +import { dataSource } from "../../data-source"; +import { maintenance } from "../../entity/unit/maintenance"; +import DatabaseActionException from "../../exceptions/databaseActionException"; + +export default abstract class MaintenanceService { + private static query = () => + dataSource + .getRepository(maintenance) + .createQueryBuilder("maintenance") + .leftJoinAndSelect("maintenance.equipment", "equipment") + .leftJoinAndSelect("maintenance.vehicle", "vehicle") + .leftJoinAndSelect("maintenance.wearable", "wearable") + .leftJoinAndSelect("maintenance.finishedBy", "user"); + + /** + * @description get all maintenances + * @returns {Promise<[Array, number]>} + */ + static async getAllByDone( + done = false, + { + offset = 0, + count = 25, + noLimit = false, + }: { + offset?: number; + count?: number; + noLimit?: boolean; + } + ): Promise<[Array, number]> { + let query = this.query().where({ finishedAt: done ? Not(IsNull()) : IsNull() }); + if (!noLimit) { + query = query.offset(offset).limit(count); + } + + return await query + .orderBy("maintenance.createdAt", "ASC") + .getManyAndCount() + .then((res) => { + return res; + }) + .catch((err) => { + throw new DatabaseActionException("SELECT", "maintenance", err); + }); + } + + /** + * @description get all maintenances By related + * @returns {Promise<[Array, number]>} + */ + static async getAllForRelated( + where: { equipmentId: string } | { vehicleId: string } | { wearableId: string }, + { + offset = 0, + count = 25, + noLimit = false, + }: { + offset?: number; + count?: number; + noLimit?: boolean; + } + ): Promise<[Array, number]> { + let query = this.query().where(where); + + if (!noLimit) { + query = query.offset(offset).limit(count); + } + + return await query + .orderBy("maintenance.createdAt", "ASC") + .getManyAndCount() + .then((res) => { + return res; + }) + .catch((err) => { + throw new DatabaseActionException("SELECT", "maintenance", err); + }); + } + + /** + * @description get maintenance by id + * @returns {Promise} + */ + static async getById(id: string): Promise { + return await this.query() + .where({ id }) + .getOneOrFail() + .then((res) => { + return res; + }) + .catch((err) => { + throw new DatabaseActionException("SELECT", "maintenance", err); + }); + } +} diff --git a/src/service/unit/repairService.ts b/src/service/unit/repairService.ts new file mode 100644 index 0000000..5e443ea --- /dev/null +++ b/src/service/unit/repairService.ts @@ -0,0 +1,97 @@ +import { IsNull, Not } from "typeorm"; +import { dataSource } from "../../data-source"; +import { repair } from "../../entity/unit/repair"; +import DatabaseActionException from "../../exceptions/databaseActionException"; + +export default abstract class RepairService { + private static query = () => + dataSource + .getRepository(repair) + .createQueryBuilder("repair") + .leftJoinAndSelect("repair.equipment", "equipment") + .leftJoinAndSelect("repair.vehicle", "vehicle") + .leftJoinAndSelect("repair.wearable", "wearable") + .leftJoinAndSelect("repair.reports", "reports") + .leftJoinAndSelect("repair.finishedBy", "user"); + + /** + * @description get all repairs + * @returns {Promise<[Array, number]>} + */ + static async getAllByDone( + done = false, + { + offset = 0, + count = 25, + noLimit = false, + }: { + offset?: number; + count?: number; + noLimit?: boolean; + } + ): Promise<[Array, number]> { + let query = this.query().where({ finishedAt: done ? Not(IsNull()) : IsNull() }); + if (!noLimit) { + query = query.offset(offset).limit(count); + } + + return await query + .orderBy("repair.createdAt", "ASC") + .getManyAndCount() + .then((res) => { + return res; + }) + .catch((err) => { + throw new DatabaseActionException("SELECT", "repair", err); + }); + } + + /** + * @description get all repairs By related + * @returns {Promise<[Array, number]>} + */ + static async getAllForRelated( + where: { equipmentId: string } | { vehicleId: string } | { wearableId: string }, + { + offset = 0, + count = 25, + noLimit = false, + }: { + offset?: number; + count?: number; + noLimit?: boolean; + } + ): Promise<[Array, number]> { + let query = this.query().where(where); + + if (!noLimit) { + query = query.offset(offset).limit(count); + } + + return await query + .orderBy("repair.createdAt", "ASC") + .getManyAndCount() + .then((res) => { + return res; + }) + .catch((err) => { + throw new DatabaseActionException("SELECT", "repair", err); + }); + } + + /** + * @description get repair by id + * @returns {Promise} + */ + static async getById(id: string): Promise { + return await this.query() + .where({ id }) + .getOneOrFail() + .then((res) => { + return res; + }) + .catch((err) => { + throw new DatabaseActionException("SELECT", "repair", err); + }); + } +} diff --git a/src/service/unit/vehicle/vehicleService.ts b/src/service/unit/vehicle/vehicleService.ts new file mode 100644 index 0000000..5f9c3ca --- /dev/null +++ b/src/service/unit/vehicle/vehicleService.ts @@ -0,0 +1,99 @@ +import { Like, In } from "typeorm"; +import { dataSource } from "../../../data-source"; +import { vehicle } from "../../../entity/unit/vehicle/vehicle"; +import DatabaseActionException from "../../../exceptions/databaseActionException"; + +export default abstract class VehicleService { + /** + * @description get all vehicles + * @returns {Promise<[Array, number]>} + */ + static async getAll({ + offset = 0, + count = 25, + search = "", + noLimit = false, + ids = [], + }: { + offset?: number; + count?: number; + search?: string; + noLimit?: boolean; + ids?: Array; + }): Promise<[Array, number]> { + let query = dataSource + .getRepository(vehicle) + .createQueryBuilder("vehicle") + .leftJoinAndSelect("vehicle.vehicleType", "vehicletype"); + + if (search != "") { + query = query + .where({ + code: Like(`%${search}%`), + }) + .orWhere({ + name: Like(`%${search}%`), + }) + .orWhere({ + location: Like(`%${search}%`), + }); + } + + if (ids.length != 0) { + query = query.where({ id: In(ids) }); + } + + if (!noLimit) { + query = query.offset(offset).limit(count); + } + + return await query + .orderBy("name", "ASC") + .getManyAndCount() + .then((res) => { + return res; + }) + .catch((err) => { + throw new DatabaseActionException("SELECT", "vehicle", err); + }); + } + + /** + * @description get vehicle by code + * @returns {Promise>} + */ + static async getAllByCode(code: string): Promise> { + return await dataSource + .getRepository(vehicle) + .createQueryBuilder("vehicle") + .leftJoinAndSelect("vehicle.vehicleType", "vehicletype") + .where({ code: Like(`%${code}%`) }) + .orderBy("name", "ASC") + .getMany() + .then((res) => { + return res; + }) + .catch((err) => { + throw new DatabaseActionException("SELECT", "vehicle", err); + }); + } + + /** + * @description get vehicle by id + * @returns {Promise} + */ + static async getById(id: string): Promise { + return await dataSource + .getRepository(vehicle) + .createQueryBuilder("vehicle") + .leftJoinAndSelect("vehicle.vehicleType", "vehicletype") + .where({ id }) + .getOneOrFail() + .then((res) => { + return res; + }) + .catch((err) => { + throw new DatabaseActionException("SELECT", "vehicle", err); + }); + } +} diff --git a/src/service/unit/vehicle/vehicleTypeService.ts b/src/service/unit/vehicle/vehicleTypeService.ts new file mode 100644 index 0000000..c1aa7dd --- /dev/null +++ b/src/service/unit/vehicle/vehicleTypeService.ts @@ -0,0 +1,66 @@ +import { Like, In } from "typeorm"; +import { dataSource } from "../../../data-source"; +import { vehicleType } from "../../../entity/unit/vehicle/vehicleType"; +import DatabaseActionException from "../../../exceptions/databaseActionException"; + +export default abstract class VehicleTypeService { + /** + * @description get all vehicleTypes + * @returns {Promise<[Array, number]>} + */ + static async getAll({ + offset = 0, + count = 25, + search = "", + noLimit = false, + }: { + offset?: number; + count?: number; + search?: string; + noLimit?: boolean; + }): Promise<[Array, number]> { + let query = dataSource + .getRepository(vehicleType) + .createQueryBuilder("vehicleType") + .loadRelationCountAndMap("vehicleType.vehicleCount", "vehicleType.vehicle"); + + if (search != "") { + query = query.where({ + type: Like(`%${search}%`), + }); + } + + if (!noLimit) { + query = query.offset(offset).limit(count); + } + + return await query + .orderBy("type", "ASC") + .getManyAndCount() + .then((res) => { + return res; + }) + .catch((err) => { + throw new DatabaseActionException("SELECT", "vehicleType", err); + }); + } + + /** + * @description get vehicleType by id + * @returns {Promise} + */ + static async getById(id: string): Promise { + return await dataSource + .getRepository(vehicleType) + .createQueryBuilder("vehicleType") + .loadRelationCountAndMap("vehicleType.vehicleCount", "vehicleType.vehicle") + .where({ id }) + .getOneOrFail() + .then((res) => { + return res; + }) + .catch((err) => { + throw new DatabaseActionException("SELECT", "vehicleType", err); + }); + } +} diff --git a/src/service/unit/wearable/wearableService.ts b/src/service/unit/wearable/wearableService.ts new file mode 100644 index 0000000..557a9f9 --- /dev/null +++ b/src/service/unit/wearable/wearableService.ts @@ -0,0 +1,101 @@ +import { Like, In } from "typeorm"; +import { dataSource } from "../../../data-source"; +import { wearable } from "../../../entity/unit/wearable/wearable"; +import DatabaseActionException from "../../../exceptions/databaseActionException"; + +export default abstract class WearableService { + /** + * @description get all wearables + * @returns {Promise<[Array, number]>} + */ + static async getAll({ + offset = 0, + count = 25, + search = "", + noLimit = false, + ids = [], + }: { + offset?: number; + count?: number; + search?: string; + noLimit?: boolean; + ids?: Array; + }): Promise<[Array, number]> { + let query = dataSource + .getRepository(wearable) + .createQueryBuilder("wearable") + .leftJoinAndSelect("wearable.wearableType", "wearabletype") + .leftJoinAndSelect("wearable.wearer", "wearer"); + + if (search != "") { + query = query + .where({ + code: Like(`%${search}%`), + }) + .orWhere({ + name: Like(`%${search}%`), + }) + .orWhere({ + location: Like(`%${search}%`), + }); + } + + if (ids.length != 0) { + query = query.where({ id: In(ids) }); + } + + if (!noLimit) { + query = query.offset(offset).limit(count); + } + + return await query + .orderBy("name", "ASC") + .getManyAndCount() + .then((res) => { + return res; + }) + .catch((err) => { + throw new DatabaseActionException("SELECT", "wearable", err); + }); + } + + /** + * @description get wearable by code + * @returns {Promise>} + */ + static async getAllByCode(code: string): Promise> { + return await dataSource + .getRepository(wearable) + .createQueryBuilder("wearable") + .leftJoinAndSelect("wearable.wearableType", "wearabletype") + .where({ code: Like(`%${code}%`) }) + .orderBy("name", "ASC") + .getMany() + .then((res) => { + return res; + }) + .catch((err) => { + throw new DatabaseActionException("SELECT", "wearable", err); + }); + } + + /** + * @description get wearable by id + * @returns {Promise} + */ + static async getById(id: string): Promise { + return await dataSource + .getRepository(wearable) + .createQueryBuilder("wearable") + .leftJoinAndSelect("wearable.wearableType", "wearabletype") + .leftJoinAndSelect("wearable.wearer", "wearer") + .where({ id }) + .getOneOrFail() + .then((res) => { + return res; + }) + .catch((err) => { + throw new DatabaseActionException("SELECT", "wearable", err); + }); + } +} diff --git a/src/service/unit/wearable/wearableTypeService.ts b/src/service/unit/wearable/wearableTypeService.ts new file mode 100644 index 0000000..e699a2f --- /dev/null +++ b/src/service/unit/wearable/wearableTypeService.ts @@ -0,0 +1,66 @@ +import { In, Like } from "typeorm"; +import { dataSource } from "../../../data-source"; +import { wearableType } from "../../../entity/unit/wearable/wearableType"; +import DatabaseActionException from "../../../exceptions/databaseActionException"; + +export default abstract class WearableTypeService { + /** + * @description get all wearableTypes + * @returns {Promise<[Array, number]>} + */ + static async getAll({ + offset = 0, + count = 25, + search = "", + noLimit = false, + }: { + offset?: number; + count?: number; + search?: string; + noLimit?: boolean; + }): Promise<[Array, number]> { + let query = dataSource + .getRepository(wearableType) + .createQueryBuilder("wearableType") + .loadRelationCountAndMap("wearableType.wearableCount", "wearableType.wearable"); + + if (search != "") { + query = query.where({ + type: Like(`%${search}%`), + }); + } + + if (!noLimit) { + query = query.offset(offset).limit(count); + } + + return await query + .orderBy("type", "ASC") + .getManyAndCount() + .then((res) => { + return res; + }) + .catch((err) => { + throw new DatabaseActionException("SELECT", "wearableType", err); + }); + } + + /** + * @description get wearableType by id + * @returns {Promise} + */ + static async getById(id: string): Promise { + return await dataSource + .getRepository(wearableType) + .createQueryBuilder("wearableType") + .loadRelationCountAndMap("wearableType.wearableCount", "wearableType.wearable") + .where({ id }) + .getOneOrFail() + .then((res) => { + return res; + }) + .catch((err) => { + throw new DatabaseActionException("SELECT", "wearableType", err); + }); + } +} diff --git a/src/storage/socketMap.ts b/src/storage/socketMap.ts new file mode 100644 index 0000000..e5bfeec --- /dev/null +++ b/src/storage/socketMap.ts @@ -0,0 +1,35 @@ +import { PermissionObject } from "../type/permissionTypes"; + +export interface socketStoreModel { + socketId: string; + userId: string; + username: string; + firstname: string; + lastname: string; + isOwner: string; + permissions: PermissionObject; + isWebApiRequest: boolean; +} + +/** + * @description store credentials to socket to prevent auth data change while connected + */ +export abstract class SocketMap { + private static store = new Map(); + + public static write(identifier: string, data: socketStoreModel, overwrite: boolean = false): void { + if (!this.exists(identifier) || overwrite) this.store.set(identifier, data); + } + + public static read(identifier: string): socketStoreModel { + return this.store.get(identifier); + } + + public static exists(identifier: string): boolean { + return this.store.has(identifier); + } + + public static delete(identifier: string): void { + this.store.delete(identifier); + } +} diff --git a/src/templates/inspection.body.template.html b/src/templates/inspection.body.template.html new file mode 100644 index 0000000..cc2a6e5 --- /dev/null +++ b/src/templates/inspection.body.template.html @@ -0,0 +1,69 @@ + + + + + Prüfung + + +

{{planTitle}} {{related.name}} {{#if related.code}} {{related.code}}{{/if}}

+

Prüfer: {{inspector}}

+

durchgeführt am: {{longdate finishedAt}}

+ {{#if nextInspection}} +

nächste Prüfung am: {{longdate nextInspection}}

+ {{/if}} +
+

{{related.name}} in Betrieb genommen am: {{date related.commissioned}}

+
+

Kontext: {{context}}

+
+ +

Prüfpunkte:

+ + {{#each checks}} + + + + + {{/each}} +
+ {{this.title}} +
+ {{#if this.description}} > {{this.description}} +
+ {{/if}} + (Typ: {{this.type}}) +
+ {{#eq this.type "file"}} {{#eq this.others "img"}} + + {{else}} siehe Anhang {{/eq}} {{else}} {{this.value}} {{/eq}} +
+ + + diff --git a/src/type/permissionTypes.ts b/src/type/permissionTypes.ts index 013615c..091c0c3 100644 --- a/src/type/permissionTypes.ts +++ b/src/type/permissionTypes.ts @@ -1,28 +1,46 @@ -export type PermissionSection = "club" | "configuration" | "management"; +export type PermissionSection = "club" | "unit" | "configuration" | "management"; export type PermissionModule = + // club | "member" | "calendar" | "newsletter" - | "newsletter_config" | "protocol" + | "query" | "listprint" + // unit + | "equipment" + | "equipment_type" + | "vehicle" + | "vehicle_type" + | "wearable" + | "wearable_type" + | "inspection" + | "inspection_plan" + | "respiratory_gear" + | "respiratory_wearer" + | "respiratory_mission" + | "damage_report" + | "maintenance" + | "repair" + // configuration | "qualification" | "award" | "executive_position" | "communication_type" | "membership_status" + | "newsletter_config" | "salutation" | "education" | "calendar_type" - | "user" - | "role" - | "webapi" - | "query" | "query_store" | "template" | "template_usage" | "backup" + // management + | "user" + | "role" + | "webapi" | "setting"; export type PermissionType = "read" | "create" | "update" | "delete"; @@ -57,35 +75,69 @@ export type SectionsAndModulesObject = { }>; }; -export const permissionSections: Array = ["club", "configuration", "management"]; +export const permissionSections: Array = ["club", "unit", "configuration", "management"]; export const permissionModules: Array = [ + // club "member", "calendar", "newsletter", - "newsletter_config", "protocol", + "query", "listprint", + // unit + "equipment", + "equipment_type", + "vehicle", + "vehicle_type", + "wearable", + "wearable_type", + "inspection", + "inspection_plan", + "respiratory_gear", + "respiratory_wearer", + "respiratory_mission", + "damage_report", + "maintenance", + "repair", + // configuration "qualification", "award", "executive_position", "communication_type", "membership_status", + "newsletter_config", "salutation", "education", "calendar_type", - "user", - "role", - "webapi", - "query", "query_store", "template", "template_usage", "backup", + // management + "user", + "role", + "webapi", "setting", ]; export const permissionTypes: Array = ["read", "create", "update", "delete"]; export const sectionsAndModules: SectionsAndModulesObject = { club: ["member", "calendar", "newsletter", "protocol", "query", "listprint"], + unit: [ + "equipment", + "equipment_type", + "vehicle", + "vehicle_type", + "wearable", + "wearable_type", + "inspection", + "inspection_plan", + "respiratory_gear", + "respiratory_wearer", + "respiratory_mission", + "damage_report", + "maintenance", + "repair", + ], configuration: [ "qualification", "award", diff --git a/src/type/settingTypes.ts b/src/type/settingTypes.ts index ff6e728..08d8fbc 100644 --- a/src/type/settingTypes.ts +++ b/src/type/settingTypes.ts @@ -10,6 +10,7 @@ export type SettingString = | "club.website" | "app.custom_login_message" | "app.show_link_to_calendar" + | "app.show_link_to_damagereport" | "session.jwt_expiration" | "session.refresh_expiration" | "session.pwa_refresh_expiration" @@ -34,6 +35,7 @@ export type SettingValueMapping = { "club.website": string; "app.custom_login_message": string; "app.show_link_to_calendar": boolean; + "app.show_link_to_damagereport": boolean; "session.jwt_expiration": ms.StringValue; "session.refresh_expiration": ms.StringValue; "session.pwa_refresh_expiration": ms.StringValue; @@ -68,6 +70,7 @@ export const settingsType: SettingsSchema = { "club.website": { type: "url", optional: true }, "app.custom_login_message": { type: "string", optional: true }, "app.show_link_to_calendar": { type: "boolean", default: true }, + "app.show_link_to_damagereport": { type: "boolean", default: false }, "session.jwt_expiration": { type: "ms", default: "15m" }, "session.refresh_expiration": { type: "ms", default: "1d" }, "session.pwa_refresh_expiration": { type: "ms", default: "5d" }, diff --git a/src/type/templateTypes.ts b/src/type/templateTypes.ts index cede2bb..2f260e7 100644 --- a/src/type/templateTypes.ts +++ b/src/type/templateTypes.ts @@ -8,4 +8,5 @@ export const availableTemplates: Array = [ "listprint.member", "newsletter", "protocol", + "inspection", ]; diff --git a/src/viewmodel/admin/unit/damageReport.models.ts b/src/viewmodel/admin/unit/damageReport.models.ts new file mode 100644 index 0000000..6e798cb --- /dev/null +++ b/src/viewmodel/admin/unit/damageReport.models.ts @@ -0,0 +1,37 @@ +import { EquipmentViewModel } from "./equipment/equipment.models"; +import { RepairViewModel } from "./repair.models"; +import { VehicleViewModel } from "./vehicle/vehicle.models"; +import { WearableViewModel } from "./wearable/wearable.models"; + +export type DamageReportAssigned = { + relatedId: string; +} & ( + | { + assigned: "equipment"; + related: EquipmentViewModel; + } + | { + assigned: "vehicle"; + related: VehicleViewModel; + } + | { + assigned: "wearable"; + related: WearableViewModel; + } +); + +export type DamageReportViewModel = { + id: string; + title: string; + reportedAt: Date; + closedAt?: Date; + closedBy?: string; + status: string; + description: string; + location: string; + noteByReporter: string; + noteByWorker: string; + images: string[]; + reportedBy: string; + repair?: RepairViewModel; +} & DamageReportAssigned; diff --git a/src/viewmodel/admin/unit/equipment/equipment.models.ts b/src/viewmodel/admin/unit/equipment/equipment.models.ts new file mode 100644 index 0000000..625d482 --- /dev/null +++ b/src/viewmodel/admin/unit/equipment/equipment.models.ts @@ -0,0 +1,20 @@ +import type { EquipmentTypeViewModel } from "./equipmentType.models"; + +export interface EquipmentViewModel { + id: string; + code?: string; + name: string; + location: string; + commissioned: Date; + decommissioned?: Date; + equipmentTypeId: string; + equipmentType: EquipmentTypeViewModel; +} + +export interface MinifiedEquipmentViewModel { + id: string; + code?: string; + name: string; + type: string; + assigned: "equipment"; +} diff --git a/src/viewmodel/admin/unit/equipment/equipmentType.models.ts b/src/viewmodel/admin/unit/equipment/equipmentType.models.ts new file mode 100644 index 0000000..b7a1648 --- /dev/null +++ b/src/viewmodel/admin/unit/equipment/equipmentType.models.ts @@ -0,0 +1,6 @@ +export interface EquipmentTypeViewModel { + id: string; + type: string; + description: string; + equipmentCount: number; +} diff --git a/src/viewmodel/admin/unit/inspection/inspection.models.ts b/src/viewmodel/admin/unit/inspection/inspection.models.ts new file mode 100644 index 0000000..4923701 --- /dev/null +++ b/src/viewmodel/admin/unit/inspection/inspection.models.ts @@ -0,0 +1,69 @@ +import type { EquipmentViewModel } from "../equipment/equipment.models"; +import type { + InspectionPlanViewModel, + InspectionPointViewModel, + InspectionVersionedPlanViewModel, +} from "./inspectionPlan.models"; +import type { VehicleViewModel } from "../vehicle/vehicle.models"; +import { WearableViewModel } from "../wearable/wearable.models"; + +export type InspectionRelated = { + relatedId: string; +} & ( + | { + assigned: "equipment"; + related: EquipmentViewModel; + } + | { + assigned: "vehicle"; + related: VehicleViewModel; + } + | { + assigned: "wearable"; + related: WearableViewModel; + } +); + +export type InspectionViewModel = { + id: string; + inspectionPlanId: string; + inspectionPlan: InspectionPlanViewModel; + inspectionVersionedPlanId: string; + inspectionVersionedPlan: InspectionVersionedPlanViewModel; + context: string; + created: Date; + finishedAt?: Date; + finishedBy?: string; + isOpen: boolean; + nextInspection?: Date; + checks: Array; +} & InspectionRelated; + +export type MinifiedInspectionViewModel = { + id: string; + inspectionPlanId: string; + inspectionPlan: InspectionPlanViewModel; + context: string; + created: Date; + finished?: Date; + isOpen: boolean; + finishedAt?: Date; + finishedBy?: string; + nextInspection?: Date; + relatedId: string; +} & InspectionRelated; + +export type InspectionNextViewModel = { + id: string; + inspectionPlanId: string; + inspectionPlan: InspectionPlanViewModel; + dueDate: Date; + relatedId: string; +} & InspectionRelated; + +export interface InspectionPointResultViewModel { + inspectionId: string; + inspectionPointId: string; + inspectionPoint: InspectionPointViewModel; + value: string; +} diff --git a/src/viewmodel/admin/unit/inspection/inspectionPlan.models.ts b/src/viewmodel/admin/unit/inspection/inspectionPlan.models.ts new file mode 100644 index 0000000..67d7957 --- /dev/null +++ b/src/viewmodel/admin/unit/inspection/inspectionPlan.models.ts @@ -0,0 +1,53 @@ +import { InspectionPointEnum } from "../../../../enums/inspectionEnum"; +import type { EquipmentViewModel } from "../equipment/equipment.models"; +import { EquipmentTypeViewModel } from "../equipment/equipmentType.models"; +import type { VehicleViewModel } from "../vehicle/vehicle.models"; +import { VehicleTypeViewModel } from "../vehicle/vehicleType.models"; +import { WearableTypeViewModel } from "../wearable/wearableType.models"; + +export type PlanTimeDefinition = `${number}-${"d" | "m" | "y"}` | `${number}/${number | "*"}`; + +export type InspectionPlanRelated = { + relatedId: string; +} & ( + | { + assigned: "equipment"; + related: EquipmentTypeViewModel; + } + | { + assigned: "vehicle"; + related: VehicleTypeViewModel; + } + | { + assigned: "wearable"; + related: WearableTypeViewModel; + } +); + +export type InspectionPlanViewModel = { + id: string; + title: string; + inspectionInterval: PlanTimeDefinition; + remindTime: PlanTimeDefinition; + version: number; + created: Date; + inspectionPoints: InspectionPointViewModel[]; +} & InspectionPlanRelated; + +export interface InspectionVersionedPlanViewModel { + id: string; + version: number; + created: Date; + inspectionPoints: InspectionPointViewModel[]; +} + +export interface InspectionPointViewModel { + id: string; + title: string; + description: string; + type: InspectionPointEnum; + min?: number; + max?: number; + others?: string; + sort: number; +} diff --git a/src/viewmodel/admin/unit/maintenance.models.ts b/src/viewmodel/admin/unit/maintenance.models.ts new file mode 100644 index 0000000..7f85029 --- /dev/null +++ b/src/viewmodel/admin/unit/maintenance.models.ts @@ -0,0 +1,27 @@ +import { EquipmentViewModel } from "./equipment/equipment.models"; +import { VehicleViewModel } from "./vehicle/vehicle.models"; +import { WearableViewModel } from "./wearable/wearable.models"; + +export type MaintenanceAssigned = { + relatedId: string; +} & ( + | { + assigned: "equipment"; + related: EquipmentViewModel; + } + | { + assigned: "vehicle"; + related: VehicleViewModel; + } + | { + assigned: "wearable"; + related: WearableViewModel; + } +); + +export type MaintenanceViewModel = { + id: string; + createdAt: Date; + status: string; + description: string; +} & MaintenanceAssigned; diff --git a/src/viewmodel/admin/unit/repair.models.ts b/src/viewmodel/admin/unit/repair.models.ts new file mode 100644 index 0000000..74c983d --- /dev/null +++ b/src/viewmodel/admin/unit/repair.models.ts @@ -0,0 +1,35 @@ +import { DamageReportViewModel } from "./damageReport.models"; +import { EquipmentViewModel } from "./equipment/equipment.models"; +import { VehicleViewModel } from "./vehicle/vehicle.models"; +import { WearableViewModel } from "./wearable/wearable.models"; + +export type RepairAssigned = { + relatedId: string; +} & ( + | { + assigned: "equipment"; + related: EquipmentViewModel; + } + | { + assigned: "vehicle"; + related: VehicleViewModel; + } + | { + assigned: "wearable"; + related: WearableViewModel; + } +); + +export type RepairViewModel = { + id: string; + createdAt: Date; + finishedAt?: Date; + finishedBy?: string; + status: string; + responsible: string; + title: string; + description: string; + images: string[]; + reportDocument?: string; + reports: DamageReportViewModel[]; +} & RepairAssigned; diff --git a/src/viewmodel/admin/unit/respiratory/respiratoryGear.models.ts b/src/viewmodel/admin/unit/respiratory/respiratoryGear.models.ts new file mode 100644 index 0000000..07f4ec8 --- /dev/null +++ b/src/viewmodel/admin/unit/respiratory/respiratoryGear.models.ts @@ -0,0 +1,16 @@ +import type { EquipmentViewModel } from "../equipment/equipment.models"; + +export interface RespiratoryGearViewModel { + id: string; + equipmentId: string; + equipment: EquipmentViewModel; +} + +export interface CreateRespiratoryGearViewModel { + equipmentId: string; +} + +export interface UpdateRespiratoryGearViewModel { + id: string; + equipmentId: string; +} diff --git a/src/viewmodel/admin/unit/respiratory/respiratoryMission.models.ts b/src/viewmodel/admin/unit/respiratory/respiratoryMission.models.ts new file mode 100644 index 0000000..49c6e7f --- /dev/null +++ b/src/viewmodel/admin/unit/respiratory/respiratoryMission.models.ts @@ -0,0 +1,20 @@ +export interface RespiratoryMissionViewModel { + id: string; + date: Date; + title: string; + description: string; + // refs to used respiratory gear and wearers +} + +export interface CreateRespiratoryMissionViewModel { + date: Date; + title: string; + description: string; +} + +export interface UpdateRespiratoryMissionViewModel { + id: string; + date: Date; + title: string; + description: string; +} diff --git a/src/viewmodel/admin/unit/respiratory/respiratoryWearer.models.ts b/src/viewmodel/admin/unit/respiratory/respiratoryWearer.models.ts new file mode 100644 index 0000000..2957408 --- /dev/null +++ b/src/viewmodel/admin/unit/respiratory/respiratoryWearer.models.ts @@ -0,0 +1,16 @@ +import { MemberViewModel } from "../../club/member/member.models"; + +export interface RespiratoryWearerViewModel { + id: string; + memberId: string; + member: MemberViewModel; +} + +export interface CreateRespiratoryWearerViewModel { + memberId: string; +} + +export interface UpdateRespiratoryWearerViewModel { + id: string; + memberId: string; +} diff --git a/src/viewmodel/admin/unit/vehicle/vehicle.models.ts b/src/viewmodel/admin/unit/vehicle/vehicle.models.ts new file mode 100644 index 0000000..b82b190 --- /dev/null +++ b/src/viewmodel/admin/unit/vehicle/vehicle.models.ts @@ -0,0 +1,20 @@ +import type { VehicleTypeViewModel } from "./vehicleType.models"; + +export interface VehicleViewModel { + id: string; + code?: string; + name: string; + location: string; + commissioned: Date; + decommissioned?: Date; + vehicleTypeId: string; + vehicleType: VehicleTypeViewModel; +} + +export interface MinifiedVehicleViewModel { + id: string; + code?: string; + name: string; + type: string; + assigned: "vehicle"; +} diff --git a/src/viewmodel/admin/unit/vehicle/vehicleType.models.ts b/src/viewmodel/admin/unit/vehicle/vehicleType.models.ts new file mode 100644 index 0000000..a005853 --- /dev/null +++ b/src/viewmodel/admin/unit/vehicle/vehicleType.models.ts @@ -0,0 +1,6 @@ +export interface VehicleTypeViewModel { + id: string; + type: string; + description: string; + vehicleCount: number; +} diff --git a/src/viewmodel/admin/unit/wearable/wearable.models.ts b/src/viewmodel/admin/unit/wearable/wearable.models.ts new file mode 100644 index 0000000..de982e7 --- /dev/null +++ b/src/viewmodel/admin/unit/wearable/wearable.models.ts @@ -0,0 +1,23 @@ +import { MemberViewModel } from "../../club/member/member.models"; +import type { WearableTypeViewModel } from "./wearableType.models"; + +export interface WearableViewModel { + id: string; + code?: string; + name: string; + location: string; + commissioned: Date; + decommissioned?: Date; + wearerId?: string; + wearer?: MemberViewModel; + wearableTypeId: string; + wearableType: WearableTypeViewModel; +} + +export interface MinifiedWearableViewModel { + id: string; + code?: string; + name: string; + type: string; + assigned: "wearable"; +} diff --git a/src/viewmodel/admin/unit/wearable/wearableType.models.ts b/src/viewmodel/admin/unit/wearable/wearableType.models.ts new file mode 100644 index 0000000..0c15a26 --- /dev/null +++ b/src/viewmodel/admin/unit/wearable/wearableType.models.ts @@ -0,0 +1,6 @@ +export interface WearableTypeViewModel { + id: string; + type: string; + description: string; + wearableCount: number; +} diff --git a/src/websocket/base.ts b/src/websocket/base.ts new file mode 100644 index 0000000..8243717 --- /dev/null +++ b/src/websocket/base.ts @@ -0,0 +1,35 @@ +import { Server, Socket } from "socket.io"; +import CustomRequestException from "../exceptions/customRequestException"; +import { SocketMap } from "../storage/socketMap"; + +export default (io: Server, socket: Socket) => { + socket.on("ping", (callback: Function) => { + callback(); + }); + + socket.on("error", (err) => { + let status = 500; + let msg = "Internal Server Error"; + + if (err instanceof CustomRequestException) { + status = err.statusCode; + msg = err.message; + } + + if (err instanceof CustomRequestException) { + console.log("WS Custom Handler", status, msg); + } else { + console.log("WS Error Handler", err); + } + + socket.emit("error", msg); + }); + + socket.on("disconnecting", () => { + console.log("socket disconnection: ", socket.id); + + SocketMap.delete(socket.id); + }); + + socket.on("disconnect", () => {}); +}; diff --git a/src/websocket/handleEvent.ts b/src/websocket/handleEvent.ts new file mode 100644 index 0000000..c36c601 --- /dev/null +++ b/src/websocket/handleEvent.ts @@ -0,0 +1,94 @@ +import { Server, Socket } from "socket.io"; +import { PermissionObject, PermissionType, PermissionSection, PermissionModule } from "../type/permissionTypes"; +import PermissionHelper from "../helpers/permissionHelper"; +import ForbiddenRequestException from "../exceptions/forbiddenRequestException"; +import { SocketMap } from "../storage/socketMap"; +import { SocketConnectionTypes } from "../enums/socketEnum"; +import SocketServer from "."; + +export type EventResponseType = { + answer: any; + type: + | `package-${string}` + | `status-${string}:${string}` + | "display" + | "warning" + | "reload" + | "deleted" + | "action required"; + room?: string; +}; + +type PermissionPass = + | { + type: PermissionType; + section: PermissionSection; + module?: PermissionModule; + } + | "admin" + | "noPermissionsRequired"; + +export let handleEvent = ( + permissions: PermissionPass, + handler: (...args: any[]) => Promise, + socket: Socket +) => { + return async (...args: any[]) => { + try { + const socketData = SocketMap.read(socket.id); + if (permissions == "admin") { + if (!socketData.isOwner && !socketData.permissions.admin) { + throw new ForbiddenRequestException(`missing admin permission`); + } + } else if (permissions != "noPermissionsRequired") { + if ( + !socketData.isOwner && + !PermissionHelper.can(socketData.permissions, permissions.type, permissions.section, permissions.module) + ) { + throw new ForbiddenRequestException(`missing required permission`); + } + } + + const { answer, type, room } = await handler(...args); + if (room === undefined || room == "") { + socket.emit(type, answer); + } else { + socket.to(room).emit(type, answer); + } + } catch (e) { + socket.emit("error", e.message); + } + }; +}; + +export let emitEvent = ( + event: EventResponseType & { namespace?: SocketConnectionTypes }, + socket: Socket, + io: Server +) => { + try { + const { answer, type, room, namespace } = event; + if (room === undefined || room == "") { + socket.emit(type, answer); + } else if (namespace === undefined) { + socket.to(room).emit(type, answer); + } else { + io.of(namespace).emit(type, answer); + } + } catch (e) { + socket.emit("error", e.message); + } +}; + +/** + socket.on( + "event", + { permissions } + handleResponse( + async (data:any) => { + throw new Error("failed") + }, + socket, + ) + ); + */ diff --git a/src/websocket/index.ts b/src/websocket/index.ts new file mode 100644 index 0000000..82c7a8f --- /dev/null +++ b/src/websocket/index.ts @@ -0,0 +1,57 @@ +import helmet from "helmet"; +import { Server as httpServerType } from "http"; +import { Server } from "socket.io"; +import { instrument } from "@socket.io/admin-ui"; + +import authenticateSocket from "../middleware/authenticateSocket"; +import checkSocketExists from "../middleware/checkSocketExists"; +import { SocketConnectionTypes } from "../enums/socketEnum"; + +import base from "./base"; +import scanner from "./scanner"; +import pScanner from "./pScanner"; + +export default abstract class SocketServer { + private static io: Server; + + static get server() { + return this.io; + } + + public static init(httpServer: httpServerType) { + this.io = new Server(httpServer, { + cors: { + origin: "*", + methods: ["GET", "POST"], + credentials: true, + }, + }); + + if (process.env.NODE_ENV) { + instrument(this.io, { + auth: false, + mode: "development", + }); + } + + this.io.engine.use(helmet()); + + this.io + .of(SocketConnectionTypes.scanner) + .use(authenticateSocket) + .on("connection", (socket) => { + console.log("socket connection: ", socket.id); + socket.use((packet, next) => checkSocketExists(socket, packet, next)); + + base(this.io, socket); + scanner(this.io, socket); + }); + + this.io.of(SocketConnectionTypes.pscanner).on("connection", (socket) => { + console.log("socket connection: ", socket.id); + + base(this.io, socket); + pScanner(this.io, socket); + }); + } +} diff --git a/src/websocket/pScanner/index.ts b/src/websocket/pScanner/index.ts new file mode 100644 index 0000000..19526c4 --- /dev/null +++ b/src/websocket/pScanner/index.ts @@ -0,0 +1,116 @@ +import { Server, Socket } from "socket.io"; +import { emitEvent, handleEvent } from "../handleEvent"; +import { SocketConnectionTypes } from "../../enums/socketEnum"; + +export default (io: Server, socket: Socket) => { + socket.on( + "session:join", + handleEvent( + "noPermissionsRequired", + async (room: string) => { + const socketsInOtherRoom = await io.of(SocketConnectionTypes.scanner).in(room).fetchSockets(); + const count = socketsInOtherRoom.length; + + if (count == 0) { + return { + type: "status-session:join", + answer: { status: "failed" }, + }; + } else { + socket.join(room); + emitEvent( + { + type: "package-scanner_join", + answer: socket.id, + room: room, + namespace: SocketConnectionTypes.scanner, + }, + socket, + io + ); + + return { + type: "status-session:join", + answer: { status: "success" }, + }; + } + }, + socket + ) + ); + + socket.on( + "session:leave", + handleEvent( + "noPermissionsRequired", + async () => { + console.log("called leave"); + const rooms = Array.from(socket.rooms).filter((r) => r !== socket.id); + const room = rooms[0]; + + socket.leave(room); + emitEvent( + { + type: "package-scanner_leave", + answer: socket.id, + room: room, + namespace: SocketConnectionTypes.scanner, + }, + socket, + io + ); + + return { + type: "status-session:leave", + answer: { status: "success" }, + }; + }, + socket + ) + ); + + socket.on( + "scan:send", + handleEvent( + "noPermissionsRequired", + async (result: string) => { + const rooms = Array.from(socket.rooms).filter((r) => r !== socket.id); + const room = rooms[0]; + + emitEvent( + { + type: "package-scan_receive", + answer: result, + room: room, + namespace: SocketConnectionTypes.scanner, + }, + socket, + io + ); + + return { + type: "status-scan:send", + answer: { status: "success" }, + }; + }, + socket + ) + ); + + socket.on("disconnecting", () => { + const rooms = Array.from(socket.rooms).filter((r) => r !== socket.id); + const room = rooms[0]; + + socket.leave(room); + emitEvent( + { + type: "package-scanner_leave", + answer: socket.id, + room: room, + namespace: SocketConnectionTypes.scanner, + }, + socket, + io + ); + }); +}; diff --git a/src/websocket/scanner/index.ts b/src/websocket/scanner/index.ts new file mode 100644 index 0000000..2c8aada --- /dev/null +++ b/src/websocket/scanner/index.ts @@ -0,0 +1,63 @@ +import { Server, Socket } from "socket.io"; +import { emitEvent, handleEvent } from "../handleEvent"; +import { SocketConnectionTypes } from "../../enums/socketEnum"; + +export default (io: Server, socket: Socket) => { + socket.on( + "session:create", + handleEvent( + "noPermissionsRequired", + async (room: string) => { + socket.join(room); + return { + type: "status-session:create", + answer: { status: "success" }, + }; + }, + socket + ) + ); + + socket.on( + "session:close", + handleEvent( + "noPermissionsRequired", + async () => { + const rooms = Array.from(socket.rooms).filter((r) => r !== socket.id); + const room = rooms[0]; + socket.leave(room); + emitEvent( + { + type: "package-host_leave", + answer: "host_leave", + room: room, + namespace: SocketConnectionTypes.pscanner, + }, + socket, + io + ); + return { + type: "status-session:close", + answer: { status: "success" }, + }; + }, + socket + ) + ); + + socket.on("disconnecting", () => { + const rooms = Array.from(socket.rooms).filter((r) => r !== socket.id); + const room = rooms[0]; + // io.of(SocketConnectionTypes.pscanner).in(room).disconnectSockets(); + emitEvent( + { + type: "package-host_leave", + answer: "host_leave", + room: room, + namespace: SocketConnectionTypes.pscanner, + }, + socket, + io + ); + }); +};