diff --git a/package.json b/package.json index eafc24f..6133081 100644 --- a/package.json +++ b/package.json @@ -8,6 +8,7 @@ "dev": "nodemon", "check": "tsc --noEmit", "format": "prettier --write .", + "debug": "nodemon", "build": "tsoa spec-and-routes && tsc", "changelog:generate": "git-cliff -o CHANGELOG.md && git add CHANGELOG.md && git commit -m 'Update CHANGELOG.md'", "db:generate": "prisma generate", diff --git a/prisma/migrations/20250312093832_add_reject_cancel_field/migration.sql b/prisma/migrations/20250312093832_add_reject_cancel_field/migration.sql new file mode 100644 index 0000000..3ec125a --- /dev/null +++ b/prisma/migrations/20250312093832_add_reject_cancel_field/migration.sql @@ -0,0 +1,7 @@ +-- AlterTable +ALTER TABLE "RequestData" ADD COLUMN "rejectRequestCancel" BOOLEAN, +ADD COLUMN "rejectRequestCancelReason" TEXT; + +-- AlterTable +ALTER TABLE "RequestWork" ADD COLUMN "rejectRequestCancel" BOOLEAN, +ADD COLUMN "rejectRequestCancelReason" TEXT; diff --git a/prisma/schema.prisma b/prisma/schema.prisma index 2286ca9..acc8cec 100644 --- a/prisma/schema.prisma +++ b/prisma/schema.prisma @@ -1464,6 +1464,8 @@ model RequestData { customerRequestCancel Boolean? customerRequestCancelReason String? + rejectRequestCancel Boolean? + rejectRequestCancelReason String? flow Json? @@ -1502,6 +1504,8 @@ model RequestWork { customerRequestCancel Boolean? customerRequestCancelReason String? + rejectRequestCancel Boolean? + rejectRequestCancelReason String? creditNote CreditNote? @relation(fields: [creditNoteId], references: [id], onDelete: SetNull) creditNoteId String? diff --git a/src/controllers/00-doc-template-controller.ts b/src/controllers/00-doc-template-controller.ts index 99dca2a..a809787 100644 --- a/src/controllers/00-doc-template-controller.ts +++ b/src/controllers/00-doc-template-controller.ts @@ -61,6 +61,13 @@ const quotationData = (id: string) => service: true, }, }, + createdBy: { + include: { + province: true, + district: true, + subDistrict: true, + }, + }, }, }); diff --git a/src/controllers/00-stats-controller.ts b/src/controllers/00-stats-controller.ts index 8de7899..b4aef31 100644 --- a/src/controllers/00-stats-controller.ts +++ b/src/controllers/00-stats-controller.ts @@ -2,13 +2,9 @@ import config from "../config.json"; import { Customer, CustomerBranch, - ProductGroup, QuotationStatus, RequestWorkStatus, PaymentStatus, - User, - Invoice, - CustomerType, } from "@prisma/client"; import { Controller, Get, Query, Request, Route, Security, Tags } from "tsoa"; import prisma from "../db"; @@ -64,6 +60,7 @@ export class StatsController extends Controller { code: true, quotationStatus: true, customerBranch: { + omit: { otpCode: true, otpExpires: true, userId: true }, include: { customer: true }, }, finalPrice: true, @@ -127,7 +124,10 @@ export class StatsController extends Controller { code: true, quotation: { select: { - customerBranch: { include: { customer: true } }, + customerBranch: { + omit: { otpCode: true, otpExpires: true, userId: true }, + include: { customer: true }, + }, }, }, payment: { @@ -198,7 +198,12 @@ export class StatsController extends Controller { invoice: { select: { quotation: { - select: { customerBranch: { include: { customer: true } } }, + select: { + customerBranch: { + omit: { otpCode: true, otpExpires: true, userId: true }, + include: { customer: true }, + }, + }, }, }, }, @@ -402,6 +407,7 @@ export class StatsController extends Controller { include: { createdBy: true, customerBranch: { + omit: { otpCode: true, otpExpires: true, userId: true }, include: { customer: true }, }, }, @@ -430,9 +436,9 @@ export class StatsController extends Controller { }); return list.reduce<{ - byProductGroup: (ProductGroup & { _count: number })[]; - bySale: (User & { _count: number })[]; - byCustomer: ((CustomerBranch & { customer: Customer }) & { _count: number })[]; + byProductGroup: ((typeof list)[number]["product"]["productGroup"] & { _count: number })[]; + bySale: ((typeof list)[number]["quotation"]["createdBy"] & { _count: number })[]; + byCustomer: ((typeof list)[number]["quotation"]["customerBranch"] & { _count: number })[]; }>( (a, c) => { { @@ -683,7 +689,9 @@ export class StatsController extends Controller { { paid: number; unpaid: number; - customerBranch: CustomerBranch & { customer: Customer }; + customerBranch: CustomerBranch & { + customer: Customer; + }; }[] >((acc, item) => { const exists = acc.find((v) => v.customerBranch.id === item.customerBranch!.id); @@ -702,6 +710,15 @@ export class StatsController extends Controller { return acc; }, []) - .map((v) => ({ ...v, _quotation: undefined })); + .map((v) => ({ + ...v, + customerBranch: { + ...v.customerBranch, + userId: undefined, + otpCode: undefined, + otpExpires: undefined, + }, + _quotation: undefined, + })); } } diff --git a/src/controllers/06-request-list-controller.ts b/src/controllers/06-request-list-controller.ts index b0d78e9..4bf6710 100644 --- a/src/controllers/06-request-list-controller.ts +++ b/src/controllers/06-request-list-controller.ts @@ -239,9 +239,84 @@ export class RequestDataController extends Controller { @Route("/api/v1/request-data/{requestDataId}") @Tags("Request List") export class RequestDataActionController extends Controller { + @Post("reject-request-cancel") + @Security("keycloak") + async rejectRequestCancel( + @Request() req: RequestWithUser, + @Path() requestDataId: string, + @Body() + body: { + reason?: string; + }, + ) { + const result = await prisma.requestData.updateManyAndReturn({ + where: { + id: requestDataId, + quotation: { + registeredBranch: { + OR: permissionCond(req.user), + }, + }, + }, + data: { + rejectRequestCancel: true, + rejectRequestCancelReason: body.reason || "", + }, + }); + + if (result.length <= 0) throw notFoundError("Request Data"); + + return result[0]; + } + + @Post("request-work/{requestWorkId}/reject-request-cancel") + @Security("keycloak") + async rejectWorkRequestCancel( + @Request() req: RequestWithUser, + @Path() requestWorkId: string, + @Body() + body: { + reason?: string; + }, + ) { + const result = await prisma.requestWork.updateManyAndReturn({ + where: { + id: requestWorkId, + request: { + quotation: { + registeredBranch: { + OR: permissionCond(req.user), + }, + }, + }, + }, + data: { + rejectRequestCancel: true, + rejectRequestCancelReason: body.reason || "", + }, + }); + + if (result.length <= 0) throw notFoundError("Request Data"); + + return result[0]; + } + @Post("cancel") @Security("keycloak") - async cancelRequestData(@Path() requestDataId: string) { + async cancelRequestData(@Request() req: RequestWithUser, @Path() requestDataId: string) { + const result = await prisma.requestData.findFirst({ + where: { + id: requestDataId, + quotation: { + registeredBranch: { + OR: permissionCond(req.user), + }, + }, + }, + }); + + if (!result) throw notFoundError("Request Data"); + await prisma.$transaction(async (tx) => { const workStepCondition = { requestWork: { requestDataId }, @@ -945,13 +1020,22 @@ export class RequestListController extends Controller { quotationStatus: { notIn: [QuotationStatus.Canceled, QuotationStatus.ProcessComplete], }, - requestData: { - every: { - requestDataStatus: { - in: [RequestDataStatus.Canceled, RequestDataStatus.Completed], + AND: [ + { + requestData: { + every: { + requestDataStatus: { + in: [RequestDataStatus.Canceled, RequestDataStatus.Completed], + }, + }, }, }, - }, + { + requestData: { + some: {}, + }, + }, + ], }, data: { quotationStatus: QuotationStatus.ProcessComplete, urgent: false }, include: { diff --git a/src/controllers/07-task-controller.ts b/src/controllers/07-task-controller.ts index d4bcfb8..2da73b2 100644 --- a/src/controllers/07-task-controller.ts +++ b/src/controllers/07-task-controller.ts @@ -819,13 +819,22 @@ export class TaskActionController extends Controller { quotationStatus: { notIn: [QuotationStatus.Canceled, QuotationStatus.ProcessComplete], }, - requestData: { - every: { - requestDataStatus: { - in: [RequestDataStatus.Canceled, RequestDataStatus.Completed], + AND: [ + { + requestData: { + every: { + requestDataStatus: { + in: [RequestDataStatus.Canceled, RequestDataStatus.Completed], + }, + }, }, }, - }, + { + requestData: { + some: {}, + }, + }, + ], }, data: { quotationStatus: QuotationStatus.ProcessComplete, urgent: false }, include: { diff --git a/src/controllers/08-credit-note-controller.ts b/src/controllers/08-credit-note-controller.ts index f0e3fbd..e5d2759 100644 --- a/src/controllers/08-credit-note-controller.ts +++ b/src/controllers/08-credit-note-controller.ts @@ -146,8 +146,8 @@ export class CreditNoteController extends Controller { ) { const where = { OR: queryOrNot(query, [ + { code: { contains: query, mode: "insensitive" } }, { - code: { contains: query, mode: "insensitive" }, requestWork: { some: { request: { diff --git a/src/controllers/09-line-controller.ts b/src/controllers/09-line-controller.ts index 4e4910d..cf8d7f3 100644 --- a/src/controllers/09-line-controller.ts +++ b/src/controllers/09-line-controller.ts @@ -784,7 +784,7 @@ export class LineController extends Controller { async customerRequestCancel( @Path() requestDataId: string, @Request() req: RequestWithLineUser, - @Body() body: { reason: string }, + @Body() body: { cancel: boolean; reason?: string }, ) { const result = await prisma.requestData.updateMany({ where: { @@ -803,8 +803,10 @@ export class LineController extends Controller { }, }, data: { - customerRequestCancel: true, - customerRequestCancelReason: body.reason, + customerRequestCancel: body.cancel, + customerRequestCancelReason: body.reason || null, + rejectRequestCancel: false, + rejectRequestCancelReason: null, }, }); if (result.count <= 0) throw notFoundError("Request Data"); @@ -815,7 +817,7 @@ export class LineController extends Controller { async customerRequestCancelWork( @Path() requestWorkId: string, @Request() req: RequestWithLineUser, - @Body() body: { reason: string }, + @Body() body: { cancel: boolean; reason?: string }, ) { const result = await prisma.requestWork.updateMany({ where: { @@ -836,8 +838,10 @@ export class LineController extends Controller { }, }, data: { - customerRequestCancel: true, - customerRequestCancelReason: body.reason, + customerRequestCancel: body.cancel, + customerRequestCancelReason: body.reason || null, + rejectRequestCancel: false, + rejectRequestCancelReason: null, }, }); if (result.count <= 0) throw notFoundError("Request Data"); diff --git a/src/controllers/10-manual-controller.ts b/src/controllers/10-manual-controller.ts new file mode 100644 index 0000000..f4e9a0e --- /dev/null +++ b/src/controllers/10-manual-controller.ts @@ -0,0 +1,25 @@ +import express from "express"; +import { Controller, Get, Path, Request, Route } from "tsoa"; +import { getFile } from "../utils/minio"; + +@Route("api/v1/manual") +export class ManualController extends Controller { + @Get() + async get(@Request() req: express.Request) { + return req.res?.redirect(await getFile(".manual/toc.json")); + } + + @Get("{category}/assets/{name}") + async getAsset(@Request() req: express.Request, @Path() category: string, @Path() name: string) { + return req.res?.redirect(await getFile(`.manual/${category}/assets/${name}`)); + } + + @Get("{category}/page/{page}") + async getContent( + @Request() req: express.Request, + @Path() category: string, + @Path() page: string, + ) { + return req.res?.redirect(await getFile(`.manual/${category}/${page}.md`)); + } +} diff --git a/src/utils/minio.ts b/src/utils/minio.ts index ba02cac..dc5e244 100644 --- a/src/utils/minio.ts +++ b/src/utils/minio.ts @@ -55,71 +55,80 @@ export async function deleteFolder(path: string) { }); } +const ROOT = ".system"; + export const fileLocation = { branch: { - line: (branchId: string) => `branch/line-qr-${branchId}`, - bank: (branchId: string, bankId: string) => `branch/bank-qr-${branchId}-${bankId}`, - img: (branchId: string, name?: string) => `branch/img-${branchId}/${name || ""}`, - attachment: (branchId: string, name?: string) => `branch/attachment-${branchId}/${name || ""}`, + line: (branchId: string) => `${ROOT}/branch/line-qr-${branchId}`, + bank: (branchId: string, bankId: string) => `${ROOT}/branch/bank-qr-${branchId}-${bankId}`, + img: (branchId: string, name?: string) => `${ROOT}/branch/img-${branchId}/${name || ""}`, + attachment: (branchId: string, name?: string) => + `${ROOT}/branch/attachment-${branchId}/${name || ""}`, }, user: { - profile: (userId: string, name?: string) => `user/profile-image-${userId}/${name || ""}`, - attachment: (userId: string, name?: string) => `user/attachment-${userId}/${name || ""}`, + profile: (userId: string, name?: string) => + `${ROOT}/user/profile-image-${userId}/${name || ""}`, + attachment: (userId: string, name?: string) => + `${ROOT}/user/attachment-${userId}/${name || ""}`, }, customer: { - img: (customerId: string, name?: string) => `customer/img-${customerId}/${name || ""}`, + img: (customerId: string, name?: string) => `${ROOT}/customer/img-${customerId}/${name || ""}`, }, customerBranch: { attachment: (customerBranchId: string, name?: string) => - `customer-branch/attachment-${customerBranchId}/${name || ""}`, + `${ROOT}/customer-branch/attachment-${customerBranchId}/${name || ""}`, citizen: (customerBranchId: string, citizenId?: string) => - `customer-branch/citizen-${customerBranchId}/${citizenId || ""}`, + `${ROOT}/customer-branch/citizen-${customerBranchId}/${citizenId || ""}`, houseRegistration: (customerBranchId: string, houseRegistrationId?: string) => - `customer-branch/house-registration-${customerBranchId}/${houseRegistrationId || ""}`, + `${ROOT}/customer-branch/house-registration-${customerBranchId}/${houseRegistrationId || ""}`, commercialRegistration: (customerBranchId: string, commercialRegistrationId?: string) => - `customer-branch/commercial-registration-${customerBranchId}/${commercialRegistrationId || ""}`, + `${ROOT}/customer-branch/commercial-registration-${customerBranchId}/${commercialRegistrationId || ""}`, vatRegistration: (customerBranchId: string, vatRegistrationId?: string) => - `customer-branch/vat-registration-${customerBranchId}/${vatRegistrationId || ""}`, + `${ROOT}/customer-branch/vat-registration-${customerBranchId}/${vatRegistrationId || ""}`, powerOfAttorney: (customerBranchId: string, powerOfAttorneyId?: string) => - `customer-branch/power-of-attorney-${customerBranchId}/${powerOfAttorneyId || ""}`, + `${ROOT}/customer-branch/power-of-attorney-${customerBranchId}/${powerOfAttorneyId || ""}`, }, employee: { - img: (employeeId: string, name?: string) => `employee/img-${employeeId}/${name || ""}`, + img: (employeeId: string, name?: string) => `${ROOT}/employee/img-${employeeId}/${name || ""}`, attachment: (employeeId: string, name?: string) => - `employee/attachment-${employeeId}/${name || ""}`, - visa: (employeeId: string, visaId?: string) => `employee/visa-${employeeId}/${visaId || ""}`, + `${ROOT}/employee/attachment-${employeeId}/${name || ""}`, + visa: (employeeId: string, visaId?: string) => + `${ROOT}/employee/visa-${employeeId}/${visaId || ""}`, passport: (employeeId: string, passportId?: string) => - `employee/passport-${employeeId}/${passportId || ""}`, + `${ROOT}/employee/passport-${employeeId}/${passportId || ""}`, inCountryNotice: (employeeId: string, noticeId?: string) => - `employee/in-country-notice-${employeeId}/${noticeId || ""}`, + `${ROOT}/employee/in-country-notice-${employeeId}/${noticeId || ""}`, }, product: { - img: (productId: string, name?: string) => `product/img-${productId}/${name || ""}`, + img: (productId: string, name?: string) => `${ROOT}/product/img-${productId}/${name || ""}`, }, service: { - img: (serviceId: string, name?: string) => `service/img-${serviceId}/${name || ""}`, + img: (serviceId: string, name?: string) => `${ROOT}/service/img-${serviceId}/${name || ""}`, }, quotation: { attachment: (quotationId: string, name?: string) => - `quotation/attachment-${quotationId}/${name || ""}`, + `${ROOT}/quotation/attachment-${quotationId}/${name || ""}`, payment: (quotationId: string, paymentId: string, name?: string) => - `quotation/payment-${quotationId}/${paymentId}/${name || ""}`, + `${ROOT}/quotation/payment-${quotationId}/${paymentId}/${name || ""}`, }, request: { attachment: (requestId: string, step: number, name?: string) => - `request/attachment-${requestId}-${step}/${name || ""}`, + `${ROOT}/request/attachment-${requestId}-${step}/${name || ""}`, }, institution: { attachment: (institutionId: string, name?: string) => - `institution/attachment-${institutionId}/${name || ""}`, - img: (institutionId: string, name?: string) => `institution/img-${institutionId}/${name || ""}`, + `${ROOT}/institution/attachment-${institutionId}/${name || ""}`, + img: (institutionId: string, name?: string) => + `${ROOT}/institution/img-${institutionId}/${name || ""}`, }, task: { - attachment: (taskId: string, name?: string) => `task/attachment-${taskId}/${name || ""}`, + attachment: (taskId: string, name?: string) => + `${ROOT}/task/attachment-${taskId}/${name || ""}`, }, creditNote: { - slip: (creditNoteId: string, name?: string) => `credit-note/slip-${creditNoteId}/${name || ""}`, + slip: (creditNoteId: string, name?: string) => + `${ROOT}/credit-note/slip-${creditNoteId}/${name || ""}`, attachment: (creditNoteId: string, name?: string) => - `credit-note/attachment-${creditNoteId}/${name || ""}`, + `${ROOT}/credit-note/attachment-${creditNoteId}/${name || ""}`, }, };