Merge branch 'develop'
All checks were successful
Spell Check / Spell Check with Typos (push) Successful in 6s

This commit is contained in:
Methapon2001 2025-03-19 09:20:18 +07:00
commit 5ce356648d
11 changed files with 224 additions and 57 deletions

View file

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

View file

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

View file

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

View file

@ -61,6 +61,13 @@ const quotationData = (id: string) =>
service: true,
},
},
createdBy: {
include: {
province: true,
district: true,
subDistrict: true,
},
},
},
});

View file

@ -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,
}));
}
}

View file

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

View file

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

View file

@ -146,8 +146,8 @@ export class CreditNoteController extends Controller {
) {
const where = {
OR: queryOrNot<Prisma.CreditNoteWhereInput[]>(query, [
{ code: { contains: query, mode: "insensitive" } },
{
code: { contains: query, mode: "insensitive" },
requestWork: {
some: {
request: {

View file

@ -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");

View file

@ -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`));
}
}

View file

@ -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 || ""}`,
},
};