From 140cb59ebb00b37c4d9d50f2f8117b33de704597 Mon Sep 17 00:00:00 2001 From: Methapon2001 <61303214+Methapon2001@users.noreply.github.com> Date: Mon, 10 Mar 2025 15:12:30 +0700 Subject: [PATCH] feat: add property management endpoint --- prisma/schema.prisma | 18 ++ src/controllers/04-properties-controller.ts | 187 ++++++++++++++++++++ tsoa.json | 1 + 3 files changed, 206 insertions(+) create mode 100644 src/controllers/04-properties-controller.ts diff --git a/prisma/schema.prisma b/prisma/schema.prisma index 71d3b60..2286ca9 100644 --- a/prisma/schema.prisma +++ b/prisma/schema.prisma @@ -318,6 +318,7 @@ model Branch { workflowTemplate WorkflowTemplate[] taskOrder TaskOrder[] notification Notification[] + property Property[] } model BranchBank { @@ -1004,6 +1005,23 @@ model Institution { taskOrder TaskOrder[] } +model Property { + id String @id @default(cuid()) + + registeredBranch Branch @relation(fields: [registeredBranchId], references: [id]) + registeredBranchId String + + name String + nameEN String + + type Json + + status Status @default(CREATED) + statusOrder Int @default(0) + + createdAt DateTime @default(now()) +} + model WorkflowTemplate { id String @id @default(cuid()) name String diff --git a/src/controllers/04-properties-controller.ts b/src/controllers/04-properties-controller.ts new file mode 100644 index 0000000..7318f68 --- /dev/null +++ b/src/controllers/04-properties-controller.ts @@ -0,0 +1,187 @@ +import { + Body, + Controller, + Delete, + Get, + Path, + Post, + Put, + Query, + Request, + Route, + Security, + Tags, +} from "tsoa"; +import { RequestWithUser } from "../interfaces/user"; +import prisma from "../db"; +import { Prisma, Status } from "@prisma/client"; +import { + branchRelationPermInclude, + createPermCheck, + createPermCondition, +} from "../services/permission"; +import HttpError from "../interfaces/http-error"; +import HttpStatus from "../interfaces/http-status"; +import { notFoundError } from "../utils/error"; +import { filterStatus } from "../services/prisma"; +import { queryOrNot } from "../utils/relation"; + +type PropertyPayload = { + name: string; + nameEN: string; + type: Record; + registeredBranchId?: string; + status?: Status; +}; + +const permissionCondCompany = createPermCondition((_) => true); +const permissionCheckCompany = createPermCheck((_) => true); + +@Route("api/v1/properties") +@Tags("Properties") +@Security("keycloak") +export class PropertiesController extends Controller { + @Get() + async getProperties( + @Request() req: RequestWithUser, + @Query() page: number = 1, + @Query() pageSize: number = 30, + @Query() status?: Status, + @Query() query = "", + @Query() activeOnly?: boolean, + ) { + const where = { + OR: queryOrNot(query, [{ name: { contains: query } }, { nameEN: { contains: query } }]), + AND: { + ...filterStatus(activeOnly ? Status.ACTIVE : status), + registeredBranch: { + OR: permissionCondCompany(req.user, { activeOnly: true }), + }, + }, + } satisfies Prisma.PropertyWhereInput; + const [result, total] = await prisma.$transaction([ + prisma.property.findMany({ + where, + orderBy: [{ statusOrder: "asc" }, { createdAt: "asc" }], + take: pageSize, + skip: (page - 1) * pageSize, + }), + prisma.property.count({ where }), + ]); + + return { + result, + page, + pageSize, + total, + }; + } + + @Get("{propertyId}") + async getPropertyById(@Request() _req: RequestWithUser, @Path() propertyId: string) { + const record = await prisma.property.findFirst({ + where: { id: propertyId }, + orderBy: { createdAt: "asc" }, + }); + + if (!record) throw notFoundError("Property"); + + return record; + } + + @Post() + async createProperty(@Request() req: RequestWithUser, @Body() body: PropertyPayload) { + const where = { + OR: [{ name: { contains: body.name } }, { nameEN: { contains: body.nameEN } }], + AND: { + registeredBranch: { + OR: permissionCondCompany(req.user), + }, + }, + } satisfies Prisma.PropertyWhereInput; + + const exists = await prisma.property.findFirst({ where }); + + if (exists) { + throw new HttpError( + HttpStatus.BAD_REQUEST, + "Property with this name already exists", + "samePropertyNameExists", + ); + } + + const userAffiliatedBranch = await prisma.branch.findFirst({ + include: branchRelationPermInclude(req.user), + where: body.registeredBranchId + ? { id: body.registeredBranchId } + : { + user: { some: { userId: req.user.sub } }, + }, + }); + if (!userAffiliatedBranch) { + throw new HttpError( + HttpStatus.BAD_REQUEST, + "You must be affilated with at least one branch or specify branch to be registered (System permission required).", + "reqMinAffilatedBranch", + ); + } + + await permissionCheckCompany(req.user, userAffiliatedBranch); + + return await prisma.property.create({ + data: { + ...body, + statusOrder: +(body.status === "INACTIVE"), + registeredBranchId: userAffiliatedBranch.id, + }, + }); + } + + @Put("{propertyId}") + async updatePropertyById( + @Request() req: RequestWithUser, + @Path() propertyId: string, + @Body() body: PropertyPayload, + ) { + const record = await prisma.property.findUnique({ + where: { id: propertyId }, + include: { + registeredBranch: { + include: branchRelationPermInclude(req.user), + }, + }, + }); + + if (!record) throw notFoundError("Property"); + + await permissionCheckCompany(req.user, record.registeredBranch); + + return await prisma.property.update({ + where: { id: propertyId }, + data: { + ...body, + statusOrder: +(body.status === "INACTIVE"), + }, + }); + } + + @Delete("{propertyId}") + async deletePropertyById(@Request() req: RequestWithUser, @Path() propertyId: string) { + const record = await prisma.property.findUnique({ + where: { id: propertyId }, + include: { + registeredBranch: { + include: branchRelationPermInclude(req.user), + }, + }, + }); + + if (!record) throw notFoundError("Property"); + + await permissionCheckCompany(req.user, record.registeredBranch); + + return await prisma.property.delete({ + where: { id: propertyId }, + }); + } +} diff --git a/tsoa.json b/tsoa.json index f9a463c..dec28e5 100644 --- a/tsoa.json +++ b/tsoa.json @@ -41,6 +41,7 @@ { "name": "Employee Other Info" }, { "name": "Institution" }, { "name": "Workflow" }, + { "name": "Property" }, { "name": "Product Group" }, { "name": "Product" }, { "name": "Work" },