1266 lines
38 KiB
TypeScript
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);
|
|
});
|
|
}
|
|
}
|