jws-backend/src/controllers/07-task-controller.ts

464 lines
12 KiB
TypeScript

import {
Body,
Controller,
Delete,
Get,
Head,
Path,
Post,
Put,
Query,
Request,
Route,
Security,
Tags,
} from "tsoa";
import prisma from "../db";
import { notFoundError } from "../utils/error";
import { RequestDataStatus, TaskOrderStatus, TaskStatus } from "@prisma/client";
import { RequestWithUser } from "../interfaces/user";
import {
branchRelationPermInclude,
createPermCheck,
createPermCondition,
} from "../services/permission";
import HttpError from "../interfaces/http-error";
import HttpStatus from "../interfaces/http-status";
import { deleteFile, fileLocation, getFile, getPresigned, listFile, setFile } from "../utils/minio";
const MANAGE_ROLES = ["system", "head_of_admin", "admin", "document_checker"];
function globalAllow(user: RequestWithUser["user"]) {
const allowList = ["system", "head_of_admin"];
return allowList.some((v) => user.roles?.includes(v));
}
const permissionCondCompany = createPermCondition((_) => true);
const permissionCheck = createPermCheck(globalAllow);
const permissionCheckCompany = createPermCheck((_) => true);
@Route("/api/v1/task")
@Tags("Task Order")
export class TaskController extends Controller {
@Get("stats")
async getTaskOrderStats() {
const task = await prisma.taskOrder.groupBy({
by: ["taskOrderStatus"],
_count: true,
});
return task.reduce<Record<TaskOrderStatus, number>>(
(a, c) => Object.assign(a, { [c.taskOrderStatus]: c._count }),
{
[TaskOrderStatus.Pending]: 0,
[TaskOrderStatus.InProgress]: 0,
[TaskOrderStatus.Validate]: 0,
[TaskOrderStatus.Complete]: 0,
[TaskOrderStatus.Canceled]: 0,
},
);
}
@Get()
@Security("keycloak")
async getTaskOrderList(
@Request() req: RequestWithUser,
@Query() query: string = "",
@Query() page = 1,
@Query() pageSize = 30,
@Query() assignedByUserId?: string,
@Query() taskOrderStatus?: TaskOrderStatus,
) {
return this.getTaskOrderListByCriteria(
req,
query,
page,
pageSize,
assignedByUserId,
taskOrderStatus,
);
}
@Post("list")
@Security("keycloak")
async getTaskOrderListByCriteria(
@Request() req: RequestWithUser,
@Query() query: string = "",
@Query() page = 1,
@Query() pageSize = 30,
@Query() assignedByUserId?: string,
@Query() taskOrderStatus?: TaskOrderStatus,
@Body() body?: { code?: string[] },
) {
const [result, total] = await prisma.$transaction([
prisma.taskOrder.findMany({
where: {
taskOrderStatus,
registeredBranch: { OR: permissionCondCompany(req.user) },
taskList: assignedByUserId
? {
some: {
requestWorkStep: { responsibleUserId: assignedByUserId },
},
}
: undefined,
code: body?.code ? { in: body.code } : undefined,
OR: [
{ code: { contains: query, mode: "insensitive" } },
{ taskName: { contains: query } },
{ contactName: { contains: query } },
{ contactTel: { contains: query } },
],
},
include: {
institution: true,
createdBy: true,
},
}),
prisma.taskOrder.count(),
]);
return { result, total, page, pageSize };
}
@Get("{taskId}")
@Security("keycloak")
async getTaskOrder(
@Request() req: RequestWithUser,
@Path() taskId: string,
@Query() taskAssignedByUserId?: string,
) {
const record = await prisma.taskOrder.findFirst({
where: { id: taskId, registeredBranch: { OR: permissionCondCompany(req.user) } },
include: {
taskList: {
where: {
requestWorkStep: { responsibleUserId: taskAssignedByUserId },
},
include: {
requestWorkStep: {
include: {
requestWork: {
include: {
request: {
include: {
employee: true,
quotation: true,
},
},
productService: {
include: {
service: true,
work: true,
product: true,
},
},
},
},
},
},
},
},
institution: true,
createdBy: true,
},
});
if (!record) throw notFoundError("Task Order");
return record;
}
@Post()
@Security("keycloak", MANAGE_ROLES)
async createTaskOrderList(
@Request() req: RequestWithUser,
@Body()
body: {
taskName: string;
contactName: string;
contactTel: string;
institutionId: string;
registeredBranchId?: string;
taskList: { requestWorkId: string; step: number }[];
},
) {
return await prisma.$transaction(async (tx) => {
const last = await tx.runningNo.upsert({
where: {
key: "TASK",
},
create: {
key: "TASK",
value: 1,
},
update: {
value: { increment: 1 },
},
});
const current = new Date();
const year = `${current.getFullYear()}`.slice(-2).padStart(2, "0");
const month = `${current.getMonth() + 1}`.padStart(2, "0");
const code = `PO${year}${month}${last.value.toString().padStart(6, "0")}`;
const { taskList, ...rest } = body;
const userAffiliatedBranch = await tx.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 tx.taskOrder.create({
include: {
taskList: {
include: {
requestWorkStep: {
include: {
requestWork: {
include: {
request: {
include: {
employee: true,
quotation: true,
},
},
productService: {
include: {
service: true,
work: true,
product: true,
},
},
},
},
},
},
},
},
institution: true,
createdBy: true,
},
data: {
...rest,
code,
registeredBranchId: userAffiliatedBranch.id,
createdByUserId: req.user.sub,
taskList: { create: taskList },
},
});
});
}
@Put("{taskId}")
@Security("keycloak")
async editTaskById(
@Request() req: RequestWithUser,
@Path() taskId: string,
@Body()
body: {
taskName: string;
taskOrderStatus: TaskOrderStatus;
contactName: string;
contactTel: string;
institutionId: string;
taskList: { requestWorkId: string; step: number }[];
},
) {
const record = await prisma.taskOrder.findFirst({
where: { id: taskId },
include: {
registeredBranch: { include: branchRelationPermInclude(req.user) },
taskList: {
include: {
requestWorkStep: {
include: { requestWork: true },
},
},
},
institution: true,
createdBy: true,
},
});
if (!record) throw notFoundError("Task Order");
await permissionCheckCompany(req.user, record.registeredBranch);
await prisma.taskOrder.update({
where: { id: taskId },
include: {
taskList: {
include: {
requestWorkStep: {
include: {
requestWork: true,
},
},
},
},
institution: true,
registeredBranch: true,
createdBy: true,
},
data: {
...body,
taskList: {
deleteMany: record?.taskList.filter(
(lhs) =>
!body.taskList.find(
(rhs) => lhs.requestWorkId === rhs.requestWorkId && lhs.step === rhs.step,
),
),
createMany: {
data: body.taskList,
skipDuplicates: true,
},
},
},
});
}
@Delete("{taskId}")
@Security("keycloak", MANAGE_ROLES)
async deleteTask(@Request() req: RequestWithUser, @Path() taskId: string) {
await prisma.$transaction(async (tx) => {
let record = await tx.taskOrder.findFirst({
where: { id: taskId },
include: {
registeredBranch: {
include: branchRelationPermInclude(req.user),
},
},
});
if (!record) throw notFoundError("Task Order");
await permissionCheck(req.user, record.registeredBranch);
});
}
}
@Route("/api/v1/task/{taskId}")
@Tags("Task Order")
export class TaskActionController extends Controller {
@Post("accept")
@Security("keycloak")
async acceptTaskOrder(@Request() req: RequestWithUser, @Path() taskId: string) {
const record = await prisma.taskOrder.findFirst({
include: {
taskList: {
orderBy: { step: "asc" },
},
},
where: { id: taskId },
});
if (!record) throw notFoundError("Task Order");
return await prisma.$transaction(async (tx) => {
await tx.taskOrder.update({
where: { id: taskId },
data: {
taskOrderStatus: TaskOrderStatus.InProgress,
taskList: {
updateMany: {
where: { taskOrderId: taskId },
data: { taskStatus: TaskStatus.InProgress },
},
},
},
});
await tx.requestData.updateMany({
where: {
requestWork: {
some: {
stepStatus: {
some: { task: { some: { taskOrderId: taskId } } },
},
},
},
},
data: { requestDataStatus: RequestDataStatus.InProgress },
});
});
}
@Post("submit")
@Security("keycloak")
async submitTaskOrder(@Request() req: RequestWithUser, @Path() taskId: string) {}
@Post("complete")
@Security("keycloak")
async completeTaskOrder(@Request() req: RequestWithUser, @Path() taskId: string) {}
}
@Route("api/v1/task/{taskId}")
@Tags("Task Order")
export class TaskOrderAttachmentController extends Controller {
private async checkPermission(user: RequestWithUser["user"], id: string) {
const data = await prisma.taskOrder.findUnique({
include: { registeredBranch: { include: branchRelationPermInclude(user) } },
where: { id },
});
if (!data) throw notFoundError("Task Order");
await permissionCheck(user, data.registeredBranch);
}
@Get("attachment")
@Security("keycloak")
async listAttachment(@Request() req: RequestWithUser, @Path() taskId: string) {
await this.checkPermission(req.user, taskId);
return await listFile(fileLocation.task.attachment(taskId));
}
@Get("attachment/{name}")
@Security("keycloak")
async getAttachment(@Path() taskId: string, @Path() name: string) {
return await getFile(fileLocation.task.attachment(taskId, name));
}
@Head("attachment/{name}")
async headAttachment(@Path() taskId: string, @Path() name: string) {
return await getPresigned("head", fileLocation.task.attachment(taskId, name));
}
@Put("attachment/{name}")
@Security("keycloak")
async putAttachment(
@Request() req: RequestWithUser,
@Path() taskId: string,
@Path() name: string,
) {
await this.checkPermission(req.user, taskId);
return await setFile(fileLocation.task.attachment(taskId, name));
}
@Delete("attachment/{name}")
@Security("keycloak")
async delAttachment(
@Request() req: RequestWithUser,
@Path() taskId: string,
@Path() name: string,
) {
await this.checkPermission(req.user, taskId);
return await deleteFile(fileLocation.task.attachment(taskId, name));
}
}