Merge pull request #21 from Frappet/feat/properties
All checks were successful
Spell Check / Spell Check with Typos (push) Successful in 6s

feat: property management
This commit is contained in:
Methapon Metanipat 2025-03-10 15:40:28 +07:00 committed by GitHub
commit 542c260aba
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
4 changed files with 222 additions and 0 deletions

View file

@ -0,0 +1,16 @@
-- CreateTable
CREATE TABLE "Property" (
"id" TEXT NOT NULL,
"registeredBranchId" TEXT NOT NULL,
"name" TEXT NOT NULL,
"nameEN" TEXT NOT NULL,
"type" JSONB NOT NULL,
"status" "Status" NOT NULL DEFAULT 'CREATED',
"statusOrder" INTEGER NOT NULL DEFAULT 0,
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
CONSTRAINT "Property_pkey" PRIMARY KEY ("id")
);
-- AddForeignKey
ALTER TABLE "Property" ADD CONSTRAINT "Property_registeredBranchId_fkey" FOREIGN KEY ("registeredBranchId") REFERENCES "Branch"("id") ON DELETE RESTRICT ON UPDATE CASCADE;

View file

@ -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

View file

@ -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<string, any>;
registeredBranchId?: string;
status?: Status;
};
const permissionCondCompany = createPermCondition((_) => true);
const permissionCheckCompany = createPermCheck((_) => true);
@Route("api/v1/property")
@Tags("Property")
@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 },
});
}
}

View file

@ -41,6 +41,7 @@
{ "name": "Employee Other Info" },
{ "name": "Institution" },
{ "name": "Workflow" },
{ "name": "Property" },
{ "name": "Product Group" },
{ "name": "Product" },
{ "name": "Work" },