jws-backend/src/controllers/07-task-controller.ts
Methapon2001 3455ae604a
All checks were successful
Spell Check / Spell Check with Typos (push) Successful in 4s
feat: more notification related to task and request
2025-07-21 11:17:23 +07:00

1266 lines
38 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 {
Prisma,
QuotationStatus,
RequestDataStatus,
RequestWorkStatus,
Status,
TaskOrderStatus,
TaskStatus,
UserTaskStatus,
} 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,
deleteFolder,
fileLocation,
getFile,
getPresigned,
listFile,
setFile,
} from "../utils/minio";
import { queryOrNot, whereDateQuery } from "../utils/relation";
const MANAGE_ROLES = [
"system",
"head_of_admin",
"admin",
"executive",
"accountant",
"branch_admin",
"branch_manager",
"branch_accountant",
"data_entry",
];
function globalAllow(user: RequestWithUser["user"]) {
const listAllowed = ["system", "head_of_admin", "admin", "executive", "accountant"];
return user.roles?.some((v) => listAllowed.includes(v)) || false;
}
const permissionCondCompany = createPermCondition((_) => true);
const permissionCheck = createPermCheck(globalAllow);
const permissionCheckCompany = createPermCheck((_) => true);
@Route("/api/v1/task-order")
@Tags("Task Order")
export class TaskController extends Controller {
@Get("stats")
@Security("keycloak")
async getTaskOrderStats(@Request() req: RequestWithUser) {
const task = await prisma.taskOrder.groupBy({
where: { registeredBranch: { OR: permissionCondCompany(req.user) } },
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,
@Query() startDate?: Date,
@Query() endDate?: Date,
) {
return this.getTaskOrderListByCriteria(
req,
query,
page,
pageSize,
assignedByUserId,
taskOrderStatus,
startDate,
endDate,
);
}
@Post("list")
@Security("keycloak")
async getTaskOrderListByCriteria(
@Request() req: RequestWithUser,
@Query() query: string = "",
@Query() page = 1,
@Query() pageSize = 30,
@Query() assignedUserId?: string,
@Query() taskOrderStatus?: TaskOrderStatus,
@Query() startDate?: Date,
@Query() endDate?: Date,
@Body() body?: { code?: string[] },
) {
const where = {
taskOrderStatus,
registeredBranch: { OR: permissionCondCompany(req.user) },
taskList: assignedUserId
? {
some: {
requestWorkStep: { responsibleUserId: assignedUserId },
},
}
: undefined,
code: body?.code ? { in: body.code } : undefined,
OR: queryOrNot(query, [
{ code: { contains: query, mode: "insensitive" } },
{ taskName: { contains: query, mode: "insensitive" } },
{ contactName: { contains: query, mode: "insensitive" } },
{ contactTel: { contains: query, mode: "insensitive" } },
]),
...whereDateQuery(startDate, endDate),
} satisfies Prisma.TaskOrderWhereInput;
const [result, total] = await prisma.$transaction([
prisma.taskOrder.findMany({
where,
include: {
userTask: true,
taskList: true,
institution: true,
registeredBranch: true,
createdBy: true,
},
orderBy: [{ createdAt: "desc" }],
}),
prisma.taskOrder.count({ where }),
]);
return { result, total, page, pageSize };
}
@Get("{taskOrderId}")
@Security("keycloak")
async getTaskOrder(
@Request() req: RequestWithUser,
@Path() taskOrderId: string,
@Query() taskAssignedUserId?: string,
) {
const record = await prisma.taskOrder.findFirst({
where: { id: taskOrderId },
orderBy: [{ urgent: "desc" }, { createdAt: "desc" }],
include: {
userTask: true,
taskProduct: true,
taskList: {
where: {
requestWorkStep: { responsibleUserId: taskAssignedUserId },
},
orderBy: { id: "asc" },
include: {
requestWorkStep: {
include: {
responsibleUser: true,
requestWork: {
include: {
stepStatus: true,
request: {
include: {
employee: true,
quotation: {
include: {
customerBranch: {
include: {
customer: true,
},
},
},
},
},
},
productService: {
include: {
service: {
include: {
workflow: {
include: {
step: {
include: {
value: true,
responsibleGroup: true,
responsiblePerson: {
include: { user: true },
},
responsibleInstitution: true,
},
},
},
},
},
},
work: true,
product: true,
},
},
},
},
},
},
},
},
institution: true,
registeredBranch: 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;
remark?: string;
taskList: { requestWorkId: string; step: number }[];
taskProduct?: { productId: string; discount?: 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, taskProduct, ...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);
const updated = await tx.requestWorkStepStatus.updateMany({
where: {
OR: taskList,
workStatus: RequestWorkStatus.Ready,
},
data: { workStatus: RequestWorkStatus.InProgress },
});
if (updated.count !== taskList.length) {
throw new HttpError(
HttpStatus.PRECONDITION_FAILED,
"All request work to issue task order must be in ready state.",
"requestWorkMustReady",
);
}
await tx.institution.updateMany({
where: { id: body.institutionId, status: Status.CREATED },
data: { status: Status.ACTIVE },
});
const work = await tx.requestWorkStepStatus.findMany({
include: {
requestWork: {
include: {
request: {
include: {
quotation: true,
},
},
},
},
},
where: { OR: taskList },
});
return await tx.taskOrder
.create({
include: {
taskList: {
include: {
requestWorkStep: {
include: {
requestWork: {
include: {
request: {
include: {
employee: true,
quotation: {
include: {
customerBranch: {
include: {
customer: true,
},
},
},
},
},
},
productService: {
include: {
service: {
include: {
workflow: {
include: {
step: {
include: {
value: true,
responsiblePerson: {
include: { user: true },
},
responsibleInstitution: true,
},
},
},
},
},
},
work: true,
product: true,
},
},
},
},
},
},
},
},
institution: true,
createdBy: true,
},
data: {
...rest,
code,
urgent: work.some((v) => v.requestWork.request.quotation.urgent),
registeredBranchId: userAffiliatedBranch.id,
createdByUserId: req.user.sub,
taskList: { create: taskList },
taskProduct: { create: taskProduct },
},
})
.then(async (v) => {
await prisma.notification.create({
data: {
title: "ใบสั่งงานใหม่ / New Task Order",
detail: "รหัส / code : " + v.code,
groupReceiver: { create: { name: "document_checker" } },
},
});
return v;
});
});
}
@Put("{taskOrderId}")
@Security("keycloak")
async editTaskById(
@Request() req: RequestWithUser,
@Path() taskOrderId: string,
@Body()
body: {
taskName: string;
taskOrderStatus?: TaskOrderStatus;
contactName: string;
contactTel: string;
institutionId: string;
remark?: string;
taskList: { requestWorkId: string; step: number }[];
taskProduct?: { productId: string; discount?: number }[];
},
) {
const record = await prisma.taskOrder.findFirst({
where: { id: taskOrderId },
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);
if (record.taskList.some((v) => v.taskStatus !== "Pending")) {
throw new HttpError(
HttpStatus.BAD_REQUEST,
"One or more task is not pending",
"taskListNotPending",
);
}
return await prisma
.$transaction(async (tx) => {
await Promise.all(
record.taskList
.filter(
(lhs) =>
!body.taskList.find(
(rhs) => lhs.requestWorkId === rhs.requestWorkId && lhs.step === rhs.step,
),
)
.map((v) =>
tx.task.update({
where: { id: v.id },
data: {
requestWorkStep: { update: { workStatus: "Ready" } },
},
}),
),
);
await tx.requestWorkStepStatus.updateMany({
where: {
OR: body.taskList,
workStatus: RequestWorkStatus.Ready,
},
data: { workStatus: RequestWorkStatus.InProgress },
});
const work = await tx.requestWorkStepStatus.findMany({
include: {
requestWork: {
include: {
request: {
include: { quotation: true },
},
},
},
},
where: { OR: body.taskList },
});
return await tx.taskOrder.update({
where: { id: taskOrderId },
include: {
taskList: {
include: {
requestWorkStep: {
include: {
requestWork: true,
},
},
},
},
institution: true,
registeredBranch: true,
createdBy: true,
},
data: {
...body,
urgent: work.some((v) => v.requestWork.request.quotation.urgent),
taskList: {
deleteMany: record?.taskList
.filter(
(lhs) =>
!body.taskList.find(
(rhs) => lhs.requestWorkId === rhs.requestWorkId && lhs.step === rhs.step,
),
)
.map((v) => ({ id: v.id })),
createMany: {
data: body.taskList.filter(
(lhs) =>
!record?.taskList.find(
(rhs) => lhs.requestWorkId === rhs.requestWorkId && lhs.step === rhs.step,
),
),
skipDuplicates: true,
},
},
taskProduct: { deleteMany: {}, create: body.taskProduct },
},
});
})
.then(async (ret) => {
if (body.taskOrderStatus && record.taskOrderStatus !== body.taskOrderStatus) {
await prisma.notification.create({
data: {
title: "มีการส่งงาน / Task Submitted",
detail: "รหัสใบสั่งงาน / Order : " + record.code,
receiverId: record.createdByUserId,
groupReceiver: { create: { name: "document_checker" } },
},
});
}
return ret;
});
}
@Delete("{taskOrderId}")
@Security("keycloak", MANAGE_ROLES)
async deleteTask(@Request() req: RequestWithUser, @Path() taskOrderId: string) {
await prisma.$transaction(async (tx) => {
let record = await tx.taskOrder.findFirst({
where: { id: taskOrderId },
include: {
registeredBranch: {
include: branchRelationPermInclude(req.user),
},
taskList: true,
},
});
if (!record) throw notFoundError("Task Order");
await permissionCheck(req.user, record.registeredBranch);
if (record.taskList.some((v) => v.taskStatus !== "Pending")) {
throw new HttpError(
HttpStatus.BAD_REQUEST,
"One or more task is not pending",
"taskListNotPending",
);
}
await Promise.all([deleteFolder(fileLocation.task.attachment(taskOrderId))]);
await Promise.all(
record.taskList.map((v) =>
tx.task.update({
where: { id: v.id },
data: { requestWorkStep: { update: { workStatus: "Ready" } } },
}),
),
);
await tx.taskOrder.delete({ where: { id: taskOrderId } });
});
}
}
@Route("/api/v1/task-order/{taskOrderId}")
@Tags("Task Order")
export class TaskActionController extends Controller {
async #getLineToken() {
if (!process.env.LINE_MESSAGING_API_TOKEN) {
console.warn("Line Webhook Activated but LINE_MESSAGING_API_TOKEN not set.");
}
return process.env.LINE_MESSAGING_API_TOKEN;
}
@Post("set-task-status")
@Security("keycloak")
async changeTaskOrderTaskListStatus(
@Request() req: RequestWithUser,
@Path() taskOrderId: string,
@Body()
body: {
step: number;
requestWorkId: string;
taskStatus: TaskStatus;
failedType?: string;
failedComment?: string;
}[],
) {
return await prisma.$transaction(async (tx) => {
const promises = body.map(async (v) => {
const record = await tx.task.findFirst({
include: {
requestWorkStep: {
include: {
requestWork: {
include: {
request: {
include: {
quotation: true,
employee: true,
},
},
productService: {
include: {
product: true,
},
},
},
},
},
},
taskOrder: true,
},
where: {
step: v.step,
requestWorkId: v.requestWorkId,
taskOrderId: taskOrderId,
},
});
if (!record) throw notFoundError("Task List");
if (v.taskStatus === TaskStatus.Restart && record.requestWorkStep.responsibleUserId) {
await tx.userTask.updateMany({
where: {
taskOrderId: record.taskOrderId,
userId: record.requestWorkStep.responsibleUserId,
},
data: { userTaskStatus: UserTaskStatus.Restart },
});
}
if (v.taskStatus === TaskStatus.Failed) {
const taskCode = record.taskOrder.code;
const taskName = record.taskOrder.taskName;
const productCode = record.requestWorkStep.requestWork.productService.product.code;
const productName = record.requestWorkStep.requestWork.productService.product.name;
const employeeName = `${record.requestWorkStep.requestWork.request.employee.namePrefix}.${record.requestWorkStep.requestWork.request.employee.firstNameEN} ${record.requestWorkStep.requestWork.request.employee.lastNameEN}`;
await tx.notification.create({
data: {
title: "ใบรายการคำขอที่จัดการเกิดปัญหา / Task Failed",
detail: `ใบรายการคำขอรหัส ${taskCode}: ${taskName} รหัสสินค้า ${productCode}: ${productName} ของลูกจ้าง ${employeeName} เกิดข้อผิดพลาด`,
groupReceiver: { create: { name: "document_checker" } },
receiverId: record.requestWorkStep.requestWork.request.quotation.createdByUserId,
registeredBranchId: record.taskOrder.registeredBranchId,
},
});
}
return await tx.task.update({
where: { id: record.id },
data: {
taskStatus: v.taskStatus,
failedType: v.failedType,
failedComment: v.failedComment,
},
});
});
return await Promise.all(promises);
});
}
@Post("submit")
@Security("keycloak")
async submitTaskOrder(
@Request() req: RequestWithUser,
@Path() taskOrderId: string,
@Query() submitUserId?: string, // for explicit
) {
submitUserId = submitUserId ?? req.user.sub;
const record = await prisma.taskOrder.findFirst({ where: { id: taskOrderId } });
if (!record) throw notFoundError("Task Order");
await prisma.$transaction([
prisma.requestWorkStepStatus.updateMany({
where: {
task: {
some: {
taskOrderId: taskOrderId,
taskStatus: TaskStatus.Success,
requestWorkStep: { responsibleUserId: submitUserId },
},
},
},
data: { workStatus: RequestWorkStatus.Validate },
}),
prisma.task.updateMany({
where: {
taskOrderId: taskOrderId,
taskStatus: TaskStatus.Success,
requestWorkStep: { responsibleUserId: submitUserId },
},
data: {
taskStatus: TaskStatus.Validate,
},
}),
prisma.userTask.updateMany({
where: {
taskOrderId: taskOrderId,
userId: submitUserId,
},
data: { userTaskStatus: UserTaskStatus.Submit, submittedAt: new Date() },
}),
prisma.notification.create({
data: {
title: "มีการส่งงาน / Task Submitted",
detail: "รหัสใบสั่งงาน / Order : " + record.code,
receiverId: record.createdByUserId,
groupReceiver: { create: { name: "document_checker" } },
},
}),
]);
}
@Post("complete")
@Security("keycloak")
async completeTaskOrder(@Request() req: RequestWithUser, @Path() taskOrderId: string) {
const record = await prisma.taskOrder.findFirst({ where: { id: taskOrderId } });
if (!record) throw notFoundError("Task Order");
await prisma.$transaction(async (tx) => {
const last = await tx.runningNo.upsert({
where: {
key: "TASK_RI",
},
create: {
key: "TASK_RI",
value: 1,
},
update: {
value: { increment: 1 },
},
});
const current = new Date();
const year = `${current.getFullYear()}`.padStart(2, "0");
const month = `${current.getMonth() + 1}`.padStart(2, "0");
const code = `RI${year}${month}${last.value.toString().padStart(6, "0")}`;
await Promise.all([
tx.taskOrder
.update({
where: { id: taskOrderId },
data: {
urgent: false,
taskOrderStatus: TaskOrderStatus.Complete,
codeProductReceived: code,
userTask: {
updateMany: {
where: { taskOrderId },
data: {
userTaskStatus: UserTaskStatus.Submit,
},
},
},
},
})
.then(async (record) => {
await tx.notification.create({
data: {
title: "ใบงานเสร็จสิ้น / Task Complete",
detail: "รหัสใบสั่งงาน / Order : " + record.code,
receiverId: record.createdByUserId,
groupReceiver: { create: { name: "document_checker" } },
},
});
}),
tx.requestWorkStepStatus.updateMany({
where: {
task: {
some: {
taskOrderId,
taskStatus: {
notIn: [
TaskStatus.Canceled,
TaskStatus.Success,
TaskStatus.Validate,
TaskStatus.Complete,
],
},
},
},
},
data: { workStatus: RequestWorkStatus.Ready },
}),
tx.task.updateMany({
where: {
taskOrderId: taskOrderId,
taskStatus: {
notIn: [
TaskStatus.Canceled,
TaskStatus.Success,
TaskStatus.Validate,
TaskStatus.Complete,
],
},
},
data: { taskStatus: TaskStatus.Redo },
}),
tx.task.updateMany({
where: {
taskOrderId: taskOrderId,
taskStatus: TaskStatus.Validate,
},
data: { taskStatus: TaskStatus.Complete },
}),
]);
await tx.requestWorkStepStatus.updateMany({
where: {
task: {
some: { taskOrderId, taskStatus: TaskStatus.Complete },
},
},
data: { workStatus: RequestWorkStatus.Completed },
});
const requestList = await tx.requestData.findMany({
include: {
requestWork: {
include: {
productService: {
include: {
product: true,
service: true,
work: {
include: { productOnWork: true },
},
},
},
stepStatus: true,
},
},
},
where: {
requestWork: {
some: {
stepStatus: {
some: {
task: { some: { taskOrderId } },
},
},
},
},
},
});
const completed: string[] = [];
requestList.forEach((item) => {
const completeCheck = item.requestWork.every((work) => {
const stepCount =
work.productService.work?.productOnWork.find(
(v) => v.productId === work.productService.productId,
)?.stepCount || 0;
const completeCount = work.stepStatus.filter(
(v) =>
v.workStatus === RequestWorkStatus.Completed ||
v.workStatus === RequestWorkStatus.Ended ||
v.workStatus === RequestWorkStatus.Canceled,
).length;
// NOTE: step found then check if complete count equals step count
if (stepCount === completeCount && completeCount > 0) return true;
// NOTE: likely no step found and completed at least one
if (stepCount === 0 && completeCount > 0) return true;
});
if (completeCheck) completed.push(item.id);
});
await tx.requestData
.updateManyAndReturn({
where: { id: { in: completed } },
include: {
quotation: {
select: {
createdByUserId: true,
},
},
},
data: { requestDataStatus: RequestDataStatus.Completed },
})
.then(async (res) => {
await tx.notification.createMany({
data: res.map((v) => ({
title: "รายการคำขอเสร็จสิ้น / Request Complete",
detail: "รหัส / code : " + v.code + " Completed",
receiverId: v.quotation.createdByUserId,
groupReceiver: { create: { name: "document_checker" } },
})),
});
});
await tx.quotation
.updateManyAndReturn({
where: {
quotationStatus: {
notIn: [QuotationStatus.Canceled, QuotationStatus.ProcessComplete],
},
AND: [
{
requestData: {
every: {
requestDataStatus: {
in: [RequestDataStatus.Canceled, RequestDataStatus.Completed],
},
},
},
},
{
requestData: {
some: {},
},
},
],
},
data: { quotationStatus: QuotationStatus.ProcessComplete, urgent: false },
include: {
customerBranch: {
include: {
customer: {
include: {
branch: {
where: { userId: { not: null } },
},
},
},
},
},
},
})
.then(async (res) => {
await tx.notification.createMany({
data: res.map((v) => ({
title: "สถานะใบเสนอราคาเปลี่ยนแปลง / Quotation Status Updated",
detail: "รหัส / code : " + v.code + " Completed",
receiverId: v.createdByUserId,
groupReceiver: { create: { name: "document_checker" } },
})),
});
const token = await this.#getLineToken();
if (!token) return;
const textHead = "JWS ALERT:";
const textAlert = "ขอแจ้งให้ทราบว่าใบเสนอราคา";
const textAlert2 = "ได้ดำเนินการเสร็จสิ้นทุกกระบวนการเรียบร้อยแล้ว";
const textAlert3 = "หากต้องการข้อมูลเพิ่มเติม กรุณาแจ้งให้ฝ่ายที่เกี่ยวข้องทราบ 🙏";
let finalTextWork = "";
let textData = "";
let dataCustomerId: string[] = [];
let textWorkList: string[] = [];
let dataUserId: string[] = [];
if (res) {
res.forEach((data, index) => {
data.customerBranch.customer.branch.forEach((item) => {
if (!dataCustomerId?.includes(item.id) && item.userId) {
dataCustomerId.push(item.id);
dataUserId.push(item.userId);
}
});
textWorkList.push(`${index + 1}. เลขที่ใบเสนอราคา ${data.code} ${data.workName}`);
});
finalTextWork = textWorkList.join("\n");
}
textData = `${textHead}\n\n${textAlert}\n${finalTextWork}\n${textAlert2}\n\n${textAlert3}`;
const data = {
to: dataUserId,
messages: [
{
type: "text",
text: textData,
},
],
};
await fetch("https://api.line.me/v2/bot/message/multicast", {
method: "POST",
headers: {
Authorization: `Bearer ${token}`,
"Content-Type": "application/json",
},
body: JSON.stringify(data),
});
});
});
}
}
@Route("api/v1/task-order/{taskOrderId}")
@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() taskOrderId: string) {
await this.checkPermission(req.user, taskOrderId);
return await listFile(fileLocation.task.attachment(taskOrderId));
}
@Get("attachment/{name}")
@Security("keycloak")
async getAttachment(@Path() taskOrderId: string, @Path() name: string) {
return await getFile(fileLocation.task.attachment(taskOrderId, name));
}
@Head("attachment/{name}")
async headAttachment(@Path() taskOrderId: string, @Path() name: string) {
return await getPresigned("head", fileLocation.task.attachment(taskOrderId, name));
}
@Put("attachment/{name}")
@Security("keycloak")
async putAttachment(
@Request() req: RequestWithUser,
@Path() taskOrderId: string,
@Path() name: string,
) {
await this.checkPermission(req.user, taskOrderId);
return await setFile(fileLocation.task.attachment(taskOrderId, name));
}
@Delete("attachment/{name}")
@Security("keycloak")
async delAttachment(
@Request() req: RequestWithUser,
@Path() taskOrderId: string,
@Path() name: string,
) {
await this.checkPermission(req.user, taskOrderId);
return await deleteFile(fileLocation.task.attachment(taskOrderId, name));
}
}
@Route("api/v1/user-task-order")
@Tags("User Task Order")
export class UserTaskController extends Controller {
@Get()
@Security("keycloak")
async getUserTask(
@Request() req: RequestWithUser,
@Query() query: string = "",
@Query() page = 1,
@Query() pageSize = 30,
@Query() userTaskStatus?: UserTaskStatus,
@Query() startDate?: Date,
@Query() endDate?: Date,
) {
const where = {
taskList: {
some: {
requestWorkStep: { responsibleUserId: req.user.sub },
},
},
AND: userTaskStatus
? [
{
OR:
userTaskStatus === UserTaskStatus.Pending
? [
{
userTask: {
some: {
userTaskStatus: {
in: [UserTaskStatus.Pending, UserTaskStatus.Restart],
},
userId: req.user.sub,
},
},
},
{
userTask: { none: { userId: req.user.sub } },
},
]
: undefined,
userTask:
userTaskStatus !== UserTaskStatus.Pending
? {
some: {
userTaskStatus,
userId: req.user.sub,
},
}
: undefined,
},
]
: undefined,
OR: queryOrNot(query, [
{ code: { contains: query, mode: "insensitive" } },
{ taskName: { contains: query, mode: "insensitive" } },
{ contactName: { contains: query, mode: "insensitive" } },
{ contactTel: { contains: query, mode: "insensitive" } },
]),
...whereDateQuery(startDate, endDate),
} satisfies Prisma.TaskOrderWhereInput;
const [result, total] = await prisma.$transaction([
prisma.taskOrder.findMany({
where,
include: {
userTask: {
where: { userId: req.user.sub },
},
registeredBranch: true,
institution: true,
createdBy: true,
},
}),
prisma.taskOrder.count({ where }),
]);
return {
result: result.map((lhs) => ({
...lhs,
taskOrderStatus:
lhs.userTask.find((rhs) => rhs.taskOrderId === lhs.id)?.userTaskStatus ??
UserTaskStatus.Pending,
userTask: undefined,
})),
page,
pageSize,
total,
};
}
@Post("accept")
@Security("keycloak")
async acceptTaskOrder(
@Request() req: RequestWithUser,
@Body()
body: {
taskOrderId: string[];
},
) {
const record = await prisma.taskOrder.findMany({
include: {
taskList: {
orderBy: { step: "asc" },
},
},
where: { id: { in: body.taskOrderId } },
});
if (!record) throw notFoundError("Task Order");
await prisma.$transaction(async (tx) => {
const promises = body.taskOrderId.flatMap((taskOrderId) => [
tx.taskOrder
.update({
where: { id: taskOrderId },
data: {
taskOrderStatus: TaskOrderStatus.InProgress,
userTask: {
deleteMany: { userId: req.user.sub },
create: {
userId: req.user.sub,
userTaskStatus: UserTaskStatus.Accept,
acceptedAt: new Date(),
},
},
},
})
.then(async (v) => {
await tx.notification.create({
data: {
title: "สถานะใบส่งงานมีการเปลี่ยนแปลง / Order Status Changed",
detail: "รหัสใบสั่งงาน / Order : " + v.code + " InProgress",
receiverId: v.createdByUserId,
groupReceiver: { create: { name: "document_checker" } },
},
});
await tx.notification.create({
data: {
title: "มีการรับงาน / Task Accepted",
detail: "รหัสใบสั่งงาน / Order : " + v.code,
receiverId: v.createdByUserId,
groupReceiver: { create: { name: "document_checker" } },
},
});
}),
tx.task.updateMany({
where: {
taskOrderId: taskOrderId,
taskStatus: { in: [TaskStatus.Pending, TaskStatus.Restart] },
requestWorkStep: { responsibleUserId: req.user.sub },
},
data: {
taskStatus: TaskStatus.InProgress,
},
}),
tx.requestData.updateMany({
where: {
requestWork: {
some: {
stepStatus: {
some: { task: { some: { taskOrderId: taskOrderId } } },
},
},
},
},
data: { requestDataStatus: RequestDataStatus.InProgress },
}),
]);
await Promise.all(promises);
});
}
}