Compare commits
No commits in common. "develop" and "version-0.11.10" have entirely different histories.
develop
...
version-0.
67 changed files with 818 additions and 5241 deletions
35
Dockerfile
35
Dockerfile
|
|
@ -1,22 +1,33 @@
|
||||||
FROM node:20-slim
|
FROM node:23-slim AS base
|
||||||
|
|
||||||
RUN apt-get update -y \
|
ENV PNPM_HOME="/pnpm"
|
||||||
&& apt-get install -y openssl \
|
ENV PATH="$PNPM_HOME:$PATH"
|
||||||
&& npm install -g pnpm \
|
|
||||||
&& apt-get clean \
|
RUN corepack enable
|
||||||
&& rm -rf /var/lib/apt/lists/*
|
RUN apt-get update && apt-get install -y openssl
|
||||||
|
RUN pnpm i -g prisma prisma-kysely
|
||||||
|
|
||||||
WORKDIR /app
|
WORKDIR /app
|
||||||
|
|
||||||
COPY package.json pnpm-lock.yaml ./
|
|
||||||
RUN pnpm install --frozen-lockfile
|
|
||||||
|
|
||||||
COPY . .
|
COPY . .
|
||||||
|
|
||||||
|
FROM base AS deps
|
||||||
|
RUN --mount=type=cache,id=pnpm,target=/pnpm/store pnpm install --prod --frozen-lockfile
|
||||||
|
RUN pnpm prisma generate
|
||||||
|
|
||||||
|
FROM base AS build
|
||||||
|
RUN --mount=type=cache,id=pnpm,target=/pnpm/store pnpm install --frozen-lockfile
|
||||||
RUN pnpm prisma generate
|
RUN pnpm prisma generate
|
||||||
RUN pnpm run build
|
RUN pnpm run build
|
||||||
|
|
||||||
COPY entrypoint.sh /entrypoint.sh
|
FROM base AS prod
|
||||||
RUN chmod +x /entrypoint.sh
|
|
||||||
|
|
||||||
ENTRYPOINT ["/entrypoint.sh"]
|
ENV NODE_ENV="production"
|
||||||
|
|
||||||
|
COPY --from=deps /app/node_modules /app/node_modules
|
||||||
|
COPY --from=build /app/dist /app/dist
|
||||||
|
COPY --from=base /app/static /app/static
|
||||||
|
|
||||||
|
RUN chmod u+x ./entrypoint.sh
|
||||||
|
|
||||||
|
ENTRYPOINT ["./entrypoint.sh"]
|
||||||
|
|
|
||||||
19
package.json
19
package.json
|
|
@ -7,7 +7,6 @@
|
||||||
"start": "node ./dist/app.js",
|
"start": "node ./dist/app.js",
|
||||||
"dev": "nodemon",
|
"dev": "nodemon",
|
||||||
"check": "tsc --noEmit",
|
"check": "tsc --noEmit",
|
||||||
"test": "vitest",
|
|
||||||
"format": "prettier --write .",
|
"format": "prettier --write .",
|
||||||
"debug": "nodemon",
|
"debug": "nodemon",
|
||||||
"build": "tsoa spec-and-routes && tsc",
|
"build": "tsoa spec-and-routes && tsc",
|
||||||
|
|
@ -25,45 +24,35 @@
|
||||||
"@types/cors": "^2.8.17",
|
"@types/cors": "^2.8.17",
|
||||||
"@types/express": "^4.17.21",
|
"@types/express": "^4.17.21",
|
||||||
"@types/morgan": "^1.9.9",
|
"@types/morgan": "^1.9.9",
|
||||||
"@types/multer": "^1.4.12",
|
|
||||||
"@types/node": "^20.17.10",
|
"@types/node": "^20.17.10",
|
||||||
"@types/nodemailer": "^6.4.17",
|
"@types/nodemailer": "^6.4.17",
|
||||||
"@vitest/ui": "^3.1.4",
|
|
||||||
"nodemon": "^3.1.9",
|
"nodemon": "^3.1.9",
|
||||||
"prettier": "^3.4.2",
|
"prettier": "^3.4.2",
|
||||||
"prisma": "6.16.2",
|
"prisma": "^6.3.0",
|
||||||
"prisma-kysely": "^1.8.0",
|
"prisma-kysely": "^1.8.0",
|
||||||
"ts-node": "^10.9.2",
|
"ts-node": "^10.9.2",
|
||||||
"typescript": "^5.7.2",
|
"typescript": "^5.7.2"
|
||||||
"vitest": "^3.1.4"
|
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@elastic/elasticsearch": "^8.17.0",
|
"@elastic/elasticsearch": "^8.17.0",
|
||||||
"@fast-csv/parse": "^5.0.2",
|
"@fast-csv/parse": "^5.0.2",
|
||||||
"@prisma/client": "6.16.2",
|
"@prisma/client": "^6.3.0",
|
||||||
"@scalar/express-api-reference": "^0.4.182",
|
"@scalar/express-api-reference": "^0.4.182",
|
||||||
"@tsoa/runtime": "^6.6.0",
|
"@tsoa/runtime": "^6.6.0",
|
||||||
"@types/html-to-text": "^9.0.4",
|
"barcode": "^0.1.0",
|
||||||
"canvas": "^3.1.0",
|
|
||||||
"cors": "^2.8.5",
|
"cors": "^2.8.5",
|
||||||
"cron": "^3.3.1",
|
"cron": "^3.3.1",
|
||||||
"csv-parse": "^6.1.0",
|
|
||||||
"dayjs": "^1.11.13",
|
"dayjs": "^1.11.13",
|
||||||
"dayjs-plugin-utc": "^0.1.2",
|
"dayjs-plugin-utc": "^0.1.2",
|
||||||
"docx-templates": "^4.13.0",
|
"docx-templates": "^4.13.0",
|
||||||
"dotenv": "^16.4.7",
|
"dotenv": "^16.4.7",
|
||||||
"exceljs": "^4.4.0",
|
|
||||||
"express": "^4.21.2",
|
"express": "^4.21.2",
|
||||||
"fast-jwt": "^5.0.5",
|
"fast-jwt": "^5.0.5",
|
||||||
"html-to-text": "^9.0.5",
|
|
||||||
"jsbarcode": "^3.11.6",
|
|
||||||
"json-2-csv": "^5.5.8",
|
"json-2-csv": "^5.5.8",
|
||||||
"kysely": "^0.27.5",
|
"kysely": "^0.27.5",
|
||||||
"minio": "^8.0.2",
|
"minio": "^8.0.2",
|
||||||
"morgan": "^1.10.0",
|
"morgan": "^1.10.0",
|
||||||
"multer": "^1.4.5-lts.2",
|
|
||||||
"nodemailer": "^6.10.0",
|
"nodemailer": "^6.10.0",
|
||||||
"pnpm": "^10.18.3",
|
|
||||||
"prisma-extension-kysely": "^3.0.0",
|
"prisma-extension-kysely": "^3.0.0",
|
||||||
"promise.any": "^2.0.6",
|
"promise.any": "^2.0.6",
|
||||||
"thai-baht-text": "^2.0.5",
|
"thai-baht-text": "^2.0.5",
|
||||||
|
|
|
||||||
1782
pnpm-lock.yaml
generated
1782
pnpm-lock.yaml
generated
File diff suppressed because it is too large
Load diff
|
|
@ -1,3 +0,0 @@
|
||||||
-- AlterTable
|
|
||||||
ALTER TABLE "User" ADD COLUMN "contactName" TEXT,
|
|
||||||
ADD COLUMN "contactTel" TEXT;
|
|
||||||
|
|
@ -1,3 +0,0 @@
|
||||||
-- AlterTable
|
|
||||||
ALTER TABLE "User" ALTER COLUMN "firstName" DROP NOT NULL,
|
|
||||||
ALTER COLUMN "lastName" DROP NOT NULL;
|
|
||||||
|
|
@ -1,2 +0,0 @@
|
||||||
-- AlterTable
|
|
||||||
ALTER TABLE "TaskOrder" ADD COLUMN "codeProductReceived" TEXT;
|
|
||||||
|
|
@ -1,18 +0,0 @@
|
||||||
-- AlterTable
|
|
||||||
ALTER TABLE "Institution" ADD COLUMN "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
|
||||||
ADD COLUMN "createdByUserId" TEXT,
|
|
||||||
ADD COLUMN "updatedAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
|
||||||
ADD COLUMN "updatedByUserId" TEXT;
|
|
||||||
|
|
||||||
-- AlterTable
|
|
||||||
ALTER TABLE "Payment" ADD COLUMN "updatedAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
|
||||||
ADD COLUMN "updatedByUserId" TEXT;
|
|
||||||
|
|
||||||
-- AddForeignKey
|
|
||||||
ALTER TABLE "Institution" ADD CONSTRAINT "Institution_createdByUserId_fkey" FOREIGN KEY ("createdByUserId") REFERENCES "User"("id") ON DELETE SET NULL ON UPDATE CASCADE;
|
|
||||||
|
|
||||||
-- AddForeignKey
|
|
||||||
ALTER TABLE "Institution" ADD CONSTRAINT "Institution_updatedByUserId_fkey" FOREIGN KEY ("updatedByUserId") REFERENCES "User"("id") ON DELETE SET NULL ON UPDATE CASCADE;
|
|
||||||
|
|
||||||
-- AddForeignKey
|
|
||||||
ALTER TABLE "Payment" ADD CONSTRAINT "Payment_updatedByUserId_fkey" FOREIGN KEY ("updatedByUserId") REFERENCES "User"("id") ON DELETE SET NULL ON UPDATE CASCADE;
|
|
||||||
|
|
@ -1,2 +0,0 @@
|
||||||
-- AlterTable
|
|
||||||
ALTER TABLE "Employee" ALTER COLUMN "lastNameEN" DROP NOT NULL;
|
|
||||||
|
|
@ -1,11 +0,0 @@
|
||||||
-- CreateTable
|
|
||||||
CREATE TABLE "WorkflowTemplateStepGroup" (
|
|
||||||
"id" TEXT NOT NULL,
|
|
||||||
"group" TEXT NOT NULL,
|
|
||||||
"workflowTemplateStepId" TEXT NOT NULL,
|
|
||||||
|
|
||||||
CONSTRAINT "WorkflowTemplateStepGroup_pkey" PRIMARY KEY ("id")
|
|
||||||
);
|
|
||||||
|
|
||||||
-- AddForeignKey
|
|
||||||
ALTER TABLE "WorkflowTemplateStepGroup" ADD CONSTRAINT "WorkflowTemplateStepGroup_workflowTemplateStepId_fkey" FOREIGN KEY ("workflowTemplateStepId") REFERENCES "WorkflowTemplateStep"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
|
||||||
|
|
@ -1,20 +0,0 @@
|
||||||
/*
|
|
||||||
Warnings:
|
|
||||||
|
|
||||||
- You are about to drop the column `importNationality` on the `User` table. All the data in the column will be lost.
|
|
||||||
|
|
||||||
*/
|
|
||||||
-- AlterTable
|
|
||||||
ALTER TABLE "User" DROP COLUMN "importNationality";
|
|
||||||
|
|
||||||
-- CreateTable
|
|
||||||
CREATE TABLE "UserImportNationality" (
|
|
||||||
"id" TEXT NOT NULL,
|
|
||||||
"name" TEXT NOT NULL,
|
|
||||||
"userId" TEXT NOT NULL,
|
|
||||||
|
|
||||||
CONSTRAINT "UserImportNationality_pkey" PRIMARY KEY ("id")
|
|
||||||
);
|
|
||||||
|
|
||||||
-- AddForeignKey
|
|
||||||
ALTER TABLE "UserImportNationality" ADD CONSTRAINT "UserImportNationality_userId_fkey" FOREIGN KEY ("userId") REFERENCES "User"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
|
|
||||||
|
|
@ -1,2 +0,0 @@
|
||||||
-- AlterTable
|
|
||||||
ALTER TABLE "Employee" ADD COLUMN "otherNationality" TEXT;
|
|
||||||
|
|
@ -1,2 +0,0 @@
|
||||||
-- AlterTable
|
|
||||||
ALTER TABLE "EmployeePassport" ADD COLUMN "otherNationality" TEXT;
|
|
||||||
|
|
@ -1,3 +0,0 @@
|
||||||
-- AlterTable
|
|
||||||
ALTER TABLE "QuotationWorker" ADD COLUMN "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
|
||||||
ADD COLUMN "updatedAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP;
|
|
||||||
|
|
@ -1,5 +0,0 @@
|
||||||
-- DropForeignKey
|
|
||||||
ALTER TABLE "UserImportNationality" DROP CONSTRAINT "UserImportNationality_userId_fkey";
|
|
||||||
|
|
||||||
-- AddForeignKey
|
|
||||||
ALTER TABLE "UserImportNationality" ADD CONSTRAINT "UserImportNationality_userId_fkey" FOREIGN KEY ("userId") REFERENCES "User"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
|
||||||
|
|
@ -1,5 +0,0 @@
|
||||||
-- AlterTable
|
|
||||||
ALTER TABLE "Quotation" ADD COLUMN "sellerId" TEXT;
|
|
||||||
|
|
||||||
-- AddForeignKey
|
|
||||||
ALTER TABLE "Quotation" ADD CONSTRAINT "Quotation_sellerId_fkey" FOREIGN KEY ("sellerId") REFERENCES "User"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
|
||||||
|
|
@ -1,36 +0,0 @@
|
||||||
/*
|
|
||||||
Warnings:
|
|
||||||
|
|
||||||
- You are about to drop the column `businessType` on the `CustomerBranch` table. All the data in the column will be lost.
|
|
||||||
- You are about to drop the column `customerName` on the `CustomerBranch` table. All the data in the column will be lost.
|
|
||||||
|
|
||||||
*/
|
|
||||||
-- AlterTable
|
|
||||||
ALTER TABLE "CustomerBranch" DROP COLUMN "businessType",
|
|
||||||
DROP COLUMN "customerName",
|
|
||||||
ADD COLUMN "businessTypeId" TEXT;
|
|
||||||
|
|
||||||
-- AlterTable
|
|
||||||
ALTER TABLE "EmployeeVisa" ADD COLUMN "reportDate" DATE;
|
|
||||||
|
|
||||||
-- CreateTable
|
|
||||||
CREATE TABLE "BusinessType" (
|
|
||||||
"id" TEXT NOT NULL,
|
|
||||||
"name" TEXT NOT NULL,
|
|
||||||
"nameEN" TEXT NOT NULL,
|
|
||||||
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
|
||||||
"createdByUserId" TEXT,
|
|
||||||
"updatedAt" TIMESTAMP(3) NOT NULL,
|
|
||||||
"updatedByUserId" TEXT,
|
|
||||||
|
|
||||||
CONSTRAINT "BusinessType_pkey" PRIMARY KEY ("id")
|
|
||||||
);
|
|
||||||
|
|
||||||
-- AddForeignKey
|
|
||||||
ALTER TABLE "CustomerBranch" ADD CONSTRAINT "CustomerBranch_businessTypeId_fkey" FOREIGN KEY ("businessTypeId") REFERENCES "BusinessType"("id") ON DELETE SET NULL ON UPDATE CASCADE;
|
|
||||||
|
|
||||||
-- AddForeignKey
|
|
||||||
ALTER TABLE "BusinessType" ADD CONSTRAINT "BusinessType_createdByUserId_fkey" FOREIGN KEY ("createdByUserId") REFERENCES "User"("id") ON DELETE SET NULL ON UPDATE CASCADE;
|
|
||||||
|
|
||||||
-- AddForeignKey
|
|
||||||
ALTER TABLE "BusinessType" ADD CONSTRAINT "BusinessType_updatedByUserId_fkey" FOREIGN KEY ("updatedByUserId") REFERENCES "User"("id") ON DELETE SET NULL ON UPDATE CASCADE;
|
|
||||||
|
|
@ -1,6 +0,0 @@
|
||||||
-- AlterTable
|
|
||||||
ALTER TABLE "User" ADD COLUMN "addressForeign" BOOLEAN NOT NULL DEFAULT false,
|
|
||||||
ADD COLUMN "districtText" TEXT,
|
|
||||||
ADD COLUMN "provinceText" TEXT,
|
|
||||||
ADD COLUMN "subDistrictText" TEXT,
|
|
||||||
ADD COLUMN "zipCodeText" TEXT;
|
|
||||||
|
|
@ -1,4 +0,0 @@
|
||||||
-- AlterTable
|
|
||||||
ALTER TABLE "User" ADD COLUMN "districtTextEN" TEXT,
|
|
||||||
ADD COLUMN "provinceTextEN" TEXT,
|
|
||||||
ADD COLUMN "subDistrictTextEN" TEXT;
|
|
||||||
|
|
@ -1,2 +0,0 @@
|
||||||
-- AlterTable
|
|
||||||
ALTER TABLE "RequestWorkStepStatus" ADD COLUMN "updatedAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP;
|
|
||||||
|
|
@ -1,4 +0,0 @@
|
||||||
-- AlterTable
|
|
||||||
ALTER TABLE "Payment" ADD COLUMN "account" TEXT,
|
|
||||||
ADD COLUMN "channel" TEXT,
|
|
||||||
ADD COLUMN "reference" TEXT;
|
|
||||||
|
|
@ -1,3 +0,0 @@
|
||||||
-- AlterTable
|
|
||||||
ALTER TABLE "public"."Product" ADD COLUMN "flowAccountProductIdAgentPrice" TEXT,
|
|
||||||
ADD COLUMN "flowAccountProductIdSellPrice" TEXT;
|
|
||||||
|
|
@ -366,24 +366,16 @@ enum UserType {
|
||||||
AGENCY
|
AGENCY
|
||||||
}
|
}
|
||||||
|
|
||||||
model UserImportNationality {
|
|
||||||
id String @id @default(cuid())
|
|
||||||
name String
|
|
||||||
|
|
||||||
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
|
|
||||||
userId String
|
|
||||||
}
|
|
||||||
|
|
||||||
model User {
|
model User {
|
||||||
id String @id @default(cuid())
|
id String @id @default(cuid())
|
||||||
|
|
||||||
code String?
|
code String?
|
||||||
namePrefix String?
|
namePrefix String?
|
||||||
firstName String?
|
firstName String
|
||||||
firstNameEN String
|
firstNameEN String
|
||||||
middleName String?
|
middleName String?
|
||||||
middleNameEN String?
|
middleNameEN String?
|
||||||
lastName String?
|
lastName String
|
||||||
lastNameEN String
|
lastNameEN String
|
||||||
username String
|
username String
|
||||||
gender String
|
gender String
|
||||||
|
|
@ -398,24 +390,14 @@ model User {
|
||||||
street String?
|
street String?
|
||||||
streetEN String?
|
streetEN String?
|
||||||
|
|
||||||
addressForeign Boolean @default(false)
|
province Province? @relation(fields: [provinceId], references: [id], onDelete: SetNull)
|
||||||
|
provinceId String?
|
||||||
|
|
||||||
provinceText String?
|
district District? @relation(fields: [districtId], references: [id], onDelete: SetNull)
|
||||||
provinceTextEN String?
|
districtId String?
|
||||||
province Province? @relation(fields: [provinceId], references: [id], onDelete: SetNull)
|
|
||||||
provinceId String?
|
|
||||||
|
|
||||||
districtText String?
|
subDistrict SubDistrict? @relation(fields: [subDistrictId], references: [id], onDelete: SetNull)
|
||||||
districtTextEN String?
|
subDistrictId String?
|
||||||
district District? @relation(fields: [districtId], references: [id], onDelete: SetNull)
|
|
||||||
districtId String?
|
|
||||||
|
|
||||||
subDistrictText String?
|
|
||||||
subDistrictTextEN String?
|
|
||||||
subDistrict SubDistrict? @relation(fields: [subDistrictId], references: [id], onDelete: SetNull)
|
|
||||||
subDistrictId String?
|
|
||||||
|
|
||||||
zipCodeText String?
|
|
||||||
|
|
||||||
email String
|
email String
|
||||||
telephoneNo String
|
telephoneNo String
|
||||||
|
|
@ -442,7 +424,7 @@ model User {
|
||||||
licenseExpireDate DateTime? @db.Date
|
licenseExpireDate DateTime? @db.Date
|
||||||
|
|
||||||
sourceNationality String?
|
sourceNationality String?
|
||||||
importNationality UserImportNationality[]
|
importNationality String?
|
||||||
|
|
||||||
trainingPlace String?
|
trainingPlace String?
|
||||||
responsibleArea UserResponsibleArea[]
|
responsibleArea UserResponsibleArea[]
|
||||||
|
|
@ -502,17 +484,12 @@ model User {
|
||||||
flowCreated WorkflowTemplate[] @relation("FlowCreatedByUser")
|
flowCreated WorkflowTemplate[] @relation("FlowCreatedByUser")
|
||||||
flowUpdated WorkflowTemplate[] @relation("FlowUpdatedByUser")
|
flowUpdated WorkflowTemplate[] @relation("FlowUpdatedByUser")
|
||||||
invoiceCreated Invoice[]
|
invoiceCreated Invoice[]
|
||||||
paymentCreated Payment[] @relation("PaymentCreatedByUser")
|
paymentCreated Payment[]
|
||||||
paymentUpdated Payment[] @relation("PaymentUpdatedByUser")
|
|
||||||
notificationReceive Notification[] @relation("NotificationReceiver")
|
notificationReceive Notification[] @relation("NotificationReceiver")
|
||||||
notificationRead Notification[] @relation("NotificationRead")
|
notificationRead Notification[] @relation("NotificationRead")
|
||||||
notificationDelete Notification[] @relation("NotificationDelete")
|
notificationDelete Notification[] @relation("NotificationDelete")
|
||||||
taskOrderCreated TaskOrder[] @relation("TaskOrderCreatedByUser")
|
taskOrderCreated TaskOrder[] @relation("TaskOrderCreatedByUser")
|
||||||
creditNoteCreated CreditNote[] @relation("CreditNoteCreatedByUser")
|
creditNoteCreated CreditNote[] @relation("CreditNoteCreatedByUser")
|
||||||
institutionCreated Institution[] @relation("InstitutionCreatedByUser")
|
|
||||||
institutionUpdated Institution[] @relation("InstitutionUpdatedByUser")
|
|
||||||
businessTypeCreated BusinessType[] @relation("BusinessTypeCreatedByUser")
|
|
||||||
businessTypeUpdated BusinessType[] @relation("BusinessTypeUpdatedByUser")
|
|
||||||
|
|
||||||
requestWorkStepStatus RequestWorkStepStatus[]
|
requestWorkStepStatus RequestWorkStepStatus[]
|
||||||
userTask UserTask[]
|
userTask UserTask[]
|
||||||
|
|
@ -520,10 +497,6 @@ model User {
|
||||||
|
|
||||||
remark String?
|
remark String?
|
||||||
agencyStatus String?
|
agencyStatus String?
|
||||||
|
|
||||||
contactName String?
|
|
||||||
contactTel String?
|
|
||||||
quotation Quotation[]
|
|
||||||
}
|
}
|
||||||
|
|
||||||
model UserResponsibleArea {
|
model UserResponsibleArea {
|
||||||
|
|
@ -562,9 +535,10 @@ model Customer {
|
||||||
}
|
}
|
||||||
|
|
||||||
model CustomerBranch {
|
model CustomerBranch {
|
||||||
id String @id @default(cuid())
|
id String @id @default(cuid())
|
||||||
customer Customer @relation(fields: [customerId], references: [id], onDelete: Cascade)
|
customer Customer @relation(fields: [customerId], references: [id], onDelete: Cascade)
|
||||||
customerId String
|
customerId String
|
||||||
|
customerName String?
|
||||||
|
|
||||||
code String
|
code String
|
||||||
codeCustomer String
|
codeCustomer String
|
||||||
|
|
@ -626,8 +600,7 @@ model CustomerBranch {
|
||||||
agentUser User? @relation(fields: [agentUserId], references: [id], onDelete: SetNull)
|
agentUser User? @relation(fields: [agentUserId], references: [id], onDelete: SetNull)
|
||||||
|
|
||||||
// NOTE: Business
|
// NOTE: Business
|
||||||
businessTypeId String?
|
businessType String
|
||||||
businessType BusinessType? @relation(fields: [businessTypeId], references: [id], onDelete: SetNull)
|
|
||||||
jobPosition String
|
jobPosition String
|
||||||
jobDescription String
|
jobDescription String
|
||||||
payDate String
|
payDate String
|
||||||
|
|
@ -786,21 +759,6 @@ model CustomerBranchVatRegis {
|
||||||
customerBranch CustomerBranch @relation(fields: [customerBranchId], references: [id], onDelete: Cascade)
|
customerBranch CustomerBranch @relation(fields: [customerBranchId], references: [id], onDelete: Cascade)
|
||||||
}
|
}
|
||||||
|
|
||||||
model BusinessType {
|
|
||||||
id String @id @default(cuid())
|
|
||||||
name String
|
|
||||||
nameEN String
|
|
||||||
|
|
||||||
createdAt DateTime @default(now())
|
|
||||||
createdBy User? @relation(name: "BusinessTypeCreatedByUser", fields: [createdByUserId], references: [id], onDelete: SetNull)
|
|
||||||
createdByUserId String?
|
|
||||||
updatedAt DateTime @updatedAt
|
|
||||||
updatedBy User? @relation(name: "BusinessTypeUpdatedByUser", fields: [updatedByUserId], references: [id], onDelete: SetNull)
|
|
||||||
updatedByUserId String?
|
|
||||||
|
|
||||||
customerBranch CustomerBranch[]
|
|
||||||
}
|
|
||||||
|
|
||||||
model Employee {
|
model Employee {
|
||||||
id String @id @default(cuid())
|
id String @id @default(cuid())
|
||||||
|
|
||||||
|
|
@ -813,12 +771,11 @@ model Employee {
|
||||||
middleName String?
|
middleName String?
|
||||||
middleNameEN String?
|
middleNameEN String?
|
||||||
lastName String?
|
lastName String?
|
||||||
lastNameEN String?
|
lastNameEN String
|
||||||
|
|
||||||
dateOfBirth DateTime? @db.Date
|
dateOfBirth DateTime? @db.Date
|
||||||
gender String
|
gender String
|
||||||
nationality String
|
nationality String
|
||||||
otherNationality String?
|
|
||||||
|
|
||||||
address String?
|
address String?
|
||||||
addressEN String?
|
addressEN String?
|
||||||
|
|
@ -893,19 +850,18 @@ model EmployeePassport {
|
||||||
issuePlace String
|
issuePlace String
|
||||||
previousPassportRef String?
|
previousPassportRef String?
|
||||||
|
|
||||||
workerStatus String?
|
workerStatus String?
|
||||||
nationality String?
|
nationality String?
|
||||||
otherNationality String?
|
namePrefix String?
|
||||||
namePrefix String?
|
firstName String?
|
||||||
firstName String?
|
firstNameEN String?
|
||||||
firstNameEN String?
|
middleName String?
|
||||||
middleName String?
|
middleNameEN String?
|
||||||
middleNameEN String?
|
lastName String?
|
||||||
lastName String?
|
lastNameEN String?
|
||||||
lastNameEN String?
|
gender String?
|
||||||
gender String?
|
birthDate String?
|
||||||
birthDate String?
|
birthCountry String?
|
||||||
birthCountry String?
|
|
||||||
|
|
||||||
employee Employee @relation(fields: [employeeId], references: [id], onDelete: Cascade)
|
employee Employee @relation(fields: [employeeId], references: [id], onDelete: Cascade)
|
||||||
employeeId String
|
employeeId String
|
||||||
|
|
@ -922,9 +878,8 @@ model EmployeeVisa {
|
||||||
entryCount Int
|
entryCount Int
|
||||||
issueCountry String
|
issueCountry String
|
||||||
issuePlace String
|
issuePlace String
|
||||||
issueDate DateTime @db.Date
|
issueDate DateTime @db.Date
|
||||||
expireDate DateTime @db.Date
|
expireDate DateTime @db.Date
|
||||||
reportDate DateTime? @db.Date
|
|
||||||
mrz String?
|
mrz String?
|
||||||
remark String?
|
remark String?
|
||||||
|
|
||||||
|
|
@ -1057,13 +1012,6 @@ model Institution {
|
||||||
contactEmail String?
|
contactEmail String?
|
||||||
contactTel String?
|
contactTel String?
|
||||||
|
|
||||||
createdAt DateTime @default(now())
|
|
||||||
createdBy User? @relation(name: "InstitutionCreatedByUser", fields: [createdByUserId], references: [id], onDelete: SetNull)
|
|
||||||
createdByUserId String?
|
|
||||||
updatedAt DateTime @default(now()) @updatedAt
|
|
||||||
updatedBy User? @relation(name: "InstitutionUpdatedByUser", fields: [updatedByUserId], references: [id], onDelete: SetNull)
|
|
||||||
updatedByUserId String?
|
|
||||||
|
|
||||||
bank InstitutionBank[]
|
bank InstitutionBank[]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -1128,15 +1076,6 @@ model WorkflowTemplateStepInstitution {
|
||||||
workflowTemplateStepId String
|
workflowTemplateStepId String
|
||||||
}
|
}
|
||||||
|
|
||||||
model WorkflowTemplateStepGroup {
|
|
||||||
id String @id @default(cuid())
|
|
||||||
|
|
||||||
group String
|
|
||||||
|
|
||||||
workflowTemplateStep WorkflowTemplateStep @relation(fields: [workflowTemplateStepId], references: [id], onDelete: Cascade)
|
|
||||||
workflowTemplateStepId String
|
|
||||||
}
|
|
||||||
|
|
||||||
model WorkflowTemplateStep {
|
model WorkflowTemplateStep {
|
||||||
id String @id @default(cuid())
|
id String @id @default(cuid())
|
||||||
|
|
||||||
|
|
@ -1147,7 +1086,6 @@ model WorkflowTemplateStep {
|
||||||
value WorkflowTemplateStepValue[] // NOTE: For enum or options type
|
value WorkflowTemplateStepValue[] // NOTE: For enum or options type
|
||||||
responsiblePerson WorkflowTemplateStepUser[]
|
responsiblePerson WorkflowTemplateStepUser[]
|
||||||
responsibleInstitution WorkflowTemplateStepInstitution[]
|
responsibleInstitution WorkflowTemplateStepInstitution[]
|
||||||
responsibleGroup WorkflowTemplateStepGroup[]
|
|
||||||
messengerByArea Boolean @default(false)
|
messengerByArea Boolean @default(false)
|
||||||
|
|
||||||
attributes Json?
|
attributes Json?
|
||||||
|
|
@ -1243,9 +1181,6 @@ model Product {
|
||||||
productGroup ProductGroup @relation(fields: [productGroupId], references: [id], onDelete: Cascade)
|
productGroup ProductGroup @relation(fields: [productGroupId], references: [id], onDelete: Cascade)
|
||||||
productGroupId String
|
productGroupId String
|
||||||
|
|
||||||
flowAccountProductIdSellPrice String?
|
|
||||||
flowAccountProductIdAgentPrice String?
|
|
||||||
|
|
||||||
workProduct WorkProduct[]
|
workProduct WorkProduct[]
|
||||||
quotationProductServiceList QuotationProductServiceList[]
|
quotationProductServiceList QuotationProductServiceList[]
|
||||||
taskProduct TaskProduct[]
|
taskProduct TaskProduct[]
|
||||||
|
|
@ -1418,9 +1353,6 @@ model Quotation {
|
||||||
|
|
||||||
invoice Invoice[]
|
invoice Invoice[]
|
||||||
creditNote CreditNote[]
|
creditNote CreditNote[]
|
||||||
|
|
||||||
seller User? @relation(fields: [sellerId], references: [id], onDelete: Cascade)
|
|
||||||
sellerId String?
|
|
||||||
}
|
}
|
||||||
|
|
||||||
model QuotationPaySplit {
|
model QuotationPaySplit {
|
||||||
|
|
@ -1445,9 +1377,6 @@ model QuotationWorker {
|
||||||
employeeId String
|
employeeId String
|
||||||
quotation Quotation @relation(fields: [quotationId], references: [id], onDelete: Cascade)
|
quotation Quotation @relation(fields: [quotationId], references: [id], onDelete: Cascade)
|
||||||
quotationId String
|
quotationId String
|
||||||
|
|
||||||
createdAt DateTime @default(now())
|
|
||||||
updatedAt DateTime @default(now()) @updatedAt
|
|
||||||
}
|
}
|
||||||
|
|
||||||
model QuotationProductServiceList {
|
model QuotationProductServiceList {
|
||||||
|
|
@ -1527,19 +1456,12 @@ model Payment {
|
||||||
|
|
||||||
paymentStatus PaymentStatus
|
paymentStatus PaymentStatus
|
||||||
|
|
||||||
amount Float
|
amount Float
|
||||||
date DateTime?
|
date DateTime?
|
||||||
channel String?
|
|
||||||
account String?
|
|
||||||
reference String?
|
|
||||||
|
|
||||||
createdAt DateTime @default(now())
|
createdAt DateTime @default(now())
|
||||||
createdBy User? @relation(name: "PaymentCreatedByUser", fields: [createdByUserId], references: [id], onDelete: SetNull)
|
createdBy User? @relation(fields: [createdByUserId], references: [id], onDelete: SetNull)
|
||||||
createdByUserId String?
|
createdByUserId String?
|
||||||
|
|
||||||
updatedAt DateTime @default(now()) @updatedAt
|
|
||||||
updatedBy User? @relation(name: "PaymentUpdatedByUser", fields: [updatedByUserId], references: [id], onDelete: SetNull)
|
|
||||||
updatedByUserId String?
|
|
||||||
}
|
}
|
||||||
|
|
||||||
enum RequestDataStatus {
|
enum RequestDataStatus {
|
||||||
|
|
@ -1618,7 +1540,6 @@ model RequestWork {
|
||||||
model RequestWorkStepStatus {
|
model RequestWorkStepStatus {
|
||||||
step Int
|
step Int
|
||||||
workStatus RequestWorkStatus @default(Pending)
|
workStatus RequestWorkStatus @default(Pending)
|
||||||
updatedAt DateTime @default(now()) @updatedAt
|
|
||||||
|
|
||||||
requestWork RequestWork @relation(fields: [requestWorkId], references: [id], onDelete: Cascade)
|
requestWork RequestWork @relation(fields: [requestWorkId], references: [id], onDelete: Cascade)
|
||||||
requestWorkId String
|
requestWorkId String
|
||||||
|
|
@ -1693,8 +1614,7 @@ model TaskProduct {
|
||||||
model TaskOrder {
|
model TaskOrder {
|
||||||
id String @id @default(cuid())
|
id String @id @default(cuid())
|
||||||
|
|
||||||
code String
|
code String
|
||||||
codeProductReceived String?
|
|
||||||
|
|
||||||
taskName String
|
taskName String
|
||||||
taskOrderStatus TaskOrderStatus @default(Pending)
|
taskOrderStatus TaskOrderStatus @default(Pending)
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,4 @@
|
||||||
import { createCanvas } from "canvas";
|
import barcode from "barcode";
|
||||||
import JsBarcode from "jsbarcode";
|
|
||||||
import createReport from "docx-templates";
|
import createReport from "docx-templates";
|
||||||
import ThaiBahtText from "thai-baht-text";
|
import ThaiBahtText from "thai-baht-text";
|
||||||
import { District, Province, SubDistrict } from "@prisma/client";
|
import { District, Province, SubDistrict } from "@prisma/client";
|
||||||
|
|
@ -34,14 +33,8 @@ const quotationData = (id: string) =>
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
customerBranch: {
|
customerBranch: {
|
||||||
omit: {
|
|
||||||
otpCode: true,
|
|
||||||
otpExpires: true,
|
|
||||||
userId: true,
|
|
||||||
},
|
|
||||||
include: {
|
include: {
|
||||||
customer: true,
|
customer: true,
|
||||||
businessType: true,
|
|
||||||
province: true,
|
province: true,
|
||||||
district: true,
|
district: true,
|
||||||
subDistrict: true,
|
subDistrict: true,
|
||||||
|
|
@ -118,12 +111,12 @@ export class DocTemplateController extends Controller {
|
||||||
) {
|
) {
|
||||||
const ret = await edmList(
|
const ret = await edmList(
|
||||||
"file",
|
"file",
|
||||||
templateGroup ? [...DOCUMENT_PATH, templateGroup] : DOCUMENT_PATH,
|
templateGroup ? [templateGroup, ...DOCUMENT_PATH] : DOCUMENT_PATH,
|
||||||
);
|
);
|
||||||
if (ret) return ret.map((v) => v.fileName);
|
if (ret) return ret.map((v) => v.fileName);
|
||||||
}
|
}
|
||||||
return await listFile(
|
return await listFile(
|
||||||
(templateGroup ? [...DOCUMENT_PATH, templateGroup] : DOCUMENT_PATH).join("/") + "/",
|
(templateGroup ? [templateGroup, ...DOCUMENT_PATH] : DOCUMENT_PATH).join("/") + "/",
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -260,23 +253,13 @@ export class DocTemplateController extends Controller {
|
||||||
thaiBahtText: (input: string | number) => {
|
thaiBahtText: (input: string | number) => {
|
||||||
ThaiBahtText(typeof input === "string" ? input.replaceAll(",", "") : input);
|
ThaiBahtText(typeof input === "string" ? input.replaceAll(",", "") : input);
|
||||||
},
|
},
|
||||||
barcode: async (data: string, width?: number, height?: number) =>
|
barcode: async (data: string) =>
|
||||||
new Promise<{
|
new Promise<string>((resolve, reject) =>
|
||||||
width: number;
|
barcode("code39", { data, width: 400, height: 100 }).getBase64((err, data) => {
|
||||||
height: number;
|
if (!err) return resolve(data);
|
||||||
data: string;
|
return reject(err);
|
||||||
extension: string;
|
}),
|
||||||
}>((resolve) => {
|
),
|
||||||
const canvas = createCanvas(400, 100);
|
|
||||||
JsBarcode(canvas, data);
|
|
||||||
|
|
||||||
resolve({
|
|
||||||
width: width ?? 8,
|
|
||||||
height: height ?? 3,
|
|
||||||
data: canvas.toDataURL("image/jpeg").slice("data:image/jpeg;base64".length),
|
|
||||||
extension: ".jpeg",
|
|
||||||
});
|
|
||||||
}),
|
|
||||||
},
|
},
|
||||||
}).then(Buffer.from);
|
}).then(Buffer.from);
|
||||||
|
|
||||||
|
|
@ -293,7 +276,6 @@ function replaceEmptyField<T>(data: T): T {
|
||||||
}
|
}
|
||||||
|
|
||||||
type FullAddress = {
|
type FullAddress = {
|
||||||
addressForeign?: boolean;
|
|
||||||
address: string;
|
address: string;
|
||||||
addressEN: string;
|
addressEN: string;
|
||||||
moo?: string;
|
moo?: string;
|
||||||
|
|
@ -302,14 +284,8 @@ type FullAddress = {
|
||||||
soiEN?: string;
|
soiEN?: string;
|
||||||
street?: string;
|
street?: string;
|
||||||
streetEN?: string;
|
streetEN?: string;
|
||||||
provinceText?: string | null;
|
|
||||||
provinceTextEN?: string | null;
|
|
||||||
province?: Province | null;
|
province?: Province | null;
|
||||||
districtText?: string | null;
|
|
||||||
districtTextEN?: string | null;
|
|
||||||
district?: District | null;
|
district?: District | null;
|
||||||
subDistrictText?: string | null;
|
|
||||||
subDistrictTextEN?: string | null;
|
|
||||||
subDistrict?: SubDistrict | null;
|
subDistrict?: SubDistrict | null;
|
||||||
en?: boolean;
|
en?: boolean;
|
||||||
};
|
};
|
||||||
|
|
@ -343,22 +319,13 @@ function addressFull(addr: FullAddress, lang: "th" | "en" = "en") {
|
||||||
if (addr.soi) fragments.push(`ซอย ${addr.soi},`);
|
if (addr.soi) fragments.push(`ซอย ${addr.soi},`);
|
||||||
if (addr.street) fragments.push(`ถนน${addr.street},`);
|
if (addr.street) fragments.push(`ถนน${addr.street},`);
|
||||||
|
|
||||||
if (!addr.addressForeign && addr.subDistrict) {
|
if (addr.subDistrict) {
|
||||||
fragments.push(`${addr.province?.id === "10" ? "แขวง" : "ตำบล"}${addr.subDistrict.name}`);
|
fragments.push(`${addr.province?.id === "10" ? "แขวง" : "ตำบล"}${addr.subDistrict.name},`);
|
||||||
}
|
}
|
||||||
if (addr.addressForeign && addr.subDistrictText) {
|
if (addr.district) {
|
||||||
fragments.push(`ตำบล${addr.subDistrictText}`);
|
fragments.push(`${addr.province?.id === "10" ? "เขต" : "อำเภอ"}${addr.district.name},`);
|
||||||
}
|
}
|
||||||
|
if (addr.province) fragments.push(`จังหวัด${addr.province.name},`);
|
||||||
if (!addr.addressForeign && addr.district) {
|
|
||||||
fragments.push(`${addr.province?.id === "10" ? "เขต" : "อำเภอ"}${addr.district.name}`);
|
|
||||||
}
|
|
||||||
if (addr.addressForeign && addr.districtText) {
|
|
||||||
fragments.push(`อำเภอ${addr.districtText}`);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!addr.addressForeign && addr.province) fragments.push(`จังหวัด${addr.province.name}`);
|
|
||||||
if (addr.addressForeign && addr.provinceText) fragments.push(`จังหวัด${addr.provinceText}`);
|
|
||||||
|
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
|
|
@ -367,31 +334,14 @@ function addressFull(addr: FullAddress, lang: "th" | "en" = "en") {
|
||||||
if (addr.soiEN) fragments.push(`Soi ${addr.soiEN},`);
|
if (addr.soiEN) fragments.push(`Soi ${addr.soiEN},`);
|
||||||
if (addr.streetEN) fragments.push(`${addr.streetEN} Rd.`);
|
if (addr.streetEN) fragments.push(`${addr.streetEN} Rd.`);
|
||||||
|
|
||||||
if (!addr.addressForeign && addr.subDistrict) {
|
if (addr.subDistrict) {
|
||||||
fragments.push(`${addr.subDistrict.nameEN} sub-district,`);
|
fragments.push(`${addr.subDistrict.nameEN} sub-district,`);
|
||||||
}
|
}
|
||||||
if (addr.addressForeign && addr.subDistrictTextEN) {
|
if (addr.district) fragments.push(`${addr.district.nameEN} district,`);
|
||||||
fragments.push(`${addr.subDistrictTextEN} sub-district,`);
|
if (addr.province) fragments.push(`${addr.province.nameEN},`);
|
||||||
}
|
|
||||||
|
|
||||||
if (!addr.addressForeign && addr.district) {
|
|
||||||
fragments.push(`${addr.district.nameEN} district,`);
|
|
||||||
}
|
|
||||||
if (addr.addressForeign && addr.districtTextEN) {
|
|
||||||
fragments.push(`${addr.districtTextEN} district,`);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!addr.addressForeign && addr.province) {
|
|
||||||
fragments.push(`${addr.province.nameEN},`);
|
|
||||||
}
|
|
||||||
if (addr.addressForeign && addr.provinceTextEN) {
|
|
||||||
fragments.push(`${addr.provinceTextEN} district,`);
|
|
||||||
}
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (addr.subDistrict) fragments.push(addr.subDistrict.zipCode);
|
|
||||||
|
|
||||||
return fragments.join(" ");
|
return fragments.join(" ");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -404,9 +354,6 @@ function gender(text: string, lang: "th" | "en" = "en") {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* @deprecated
|
|
||||||
*/
|
|
||||||
function businessType(text: string, lang: "th" | "en" = "en") {
|
function businessType(text: string, lang: "th" | "en" = "en") {
|
||||||
switch (lang) {
|
switch (lang) {
|
||||||
case "th":
|
case "th":
|
||||||
|
|
|
||||||
|
|
@ -12,39 +12,6 @@ export class EmploymentOfficeController extends Controller {
|
||||||
return this.getEmploymentOfficeListByCriteria(districtId, query);
|
return this.getEmploymentOfficeListByCriteria(districtId, query);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Post("list-same-office-area")
|
|
||||||
async getSameOfficeArea(@Body() body: { districtId: string }) {
|
|
||||||
const office = await prisma.employmentOffice.findFirst({
|
|
||||||
include: {
|
|
||||||
province: {
|
|
||||||
include: {
|
|
||||||
district: true,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
district: true,
|
|
||||||
},
|
|
||||||
where: {
|
|
||||||
OR: [
|
|
||||||
{
|
|
||||||
province: { district: { some: { id: body.districtId } } },
|
|
||||||
district: { none: {} },
|
|
||||||
},
|
|
||||||
{
|
|
||||||
district: {
|
|
||||||
some: { districtId: body.districtId },
|
|
||||||
},
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
});
|
|
||||||
if (!office) return [];
|
|
||||||
|
|
||||||
return [
|
|
||||||
...office.district.map((v) => v.districtId),
|
|
||||||
...office.province.district.map((v) => v.id),
|
|
||||||
];
|
|
||||||
}
|
|
||||||
|
|
||||||
@Post("list")
|
@Post("list")
|
||||||
async getEmploymentOfficeListByCriteria(
|
async getEmploymentOfficeListByCriteria(
|
||||||
@Query() districtId?: string,
|
@Query() districtId?: string,
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
import { Body, Controller, Delete, Get, Path, Post, Query, Route, Security, Tags } from "tsoa";
|
import { Body, Controller, Delete, Get, Path, Post, Route, Security, Tags } from "tsoa";
|
||||||
import { addUserRoles, getGroup, listRole, removeUserRoles } from "../services/keycloak";
|
import { addUserRoles, listRole, removeUserRoles } from "../services/keycloak";
|
||||||
|
|
||||||
@Route("api/v1/keycloak")
|
@Route("api/v1/keycloak")
|
||||||
@Tags("Single-Sign On")
|
@Tags("Single-Sign On")
|
||||||
|
|
@ -44,13 +44,4 @@ export class KeycloakController extends Controller {
|
||||||
);
|
);
|
||||||
if (!result) throw new Error("Failed. Cannot remove user's role.");
|
if (!result) throw new Error("Failed. Cannot remove user's role.");
|
||||||
}
|
}
|
||||||
|
|
||||||
@Get("group")
|
|
||||||
async getGroup(@Query() query: string = "") {
|
|
||||||
const querySearch = query === "" ? "q" : `search=${query}`;
|
|
||||||
const group = await getGroup(querySearch);
|
|
||||||
if (!Array.isArray(group)) throw new Error("Failed. Cannot get group(s) data from the server.");
|
|
||||||
|
|
||||||
return group;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -618,22 +618,9 @@ export class StatsController extends Controller {
|
||||||
startDate = dayjs(startDate).startOf("month").add(1, "month").toDate();
|
startDate = dayjs(startDate).startOf("month").add(1, "month").toDate();
|
||||||
}
|
}
|
||||||
|
|
||||||
const invoices = await tx.invoice.findMany({
|
|
||||||
select: { id: true },
|
|
||||||
where: {
|
|
||||||
quotation: {
|
|
||||||
quotationStatus: { notIn: [QuotationStatus.Canceled] },
|
|
||||||
registeredBranch: { OR: permissionCondCompany(req.user) },
|
|
||||||
},
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
if (invoices.length === 0) return [];
|
|
||||||
|
|
||||||
return await Promise.all(
|
return await Promise.all(
|
||||||
months.map(async (v) => {
|
months.map(async (v) => {
|
||||||
const date = dayjs(v);
|
const date = dayjs(v);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
month: date.format("MM"),
|
month: date.format("MM"),
|
||||||
year: date.format("YYYY"),
|
year: date.format("YYYY"),
|
||||||
|
|
@ -642,7 +629,11 @@ export class StatsController extends Controller {
|
||||||
_sum: { amount: true },
|
_sum: { amount: true },
|
||||||
where: {
|
where: {
|
||||||
createdAt: { gte: v, lte: date.endOf("month").toDate() },
|
createdAt: { gte: v, lte: date.endOf("month").toDate() },
|
||||||
invoiceId: { in: invoices.map((v) => v.id) },
|
invoice: {
|
||||||
|
quotation: {
|
||||||
|
registeredBranch: { OR: permissionCondCompany(req.user) },
|
||||||
|
},
|
||||||
|
},
|
||||||
},
|
},
|
||||||
by: "paymentStatus",
|
by: "paymentStatus",
|
||||||
})
|
})
|
||||||
|
|
|
||||||
|
|
@ -39,7 +39,6 @@ import {
|
||||||
connectOrNot,
|
connectOrNot,
|
||||||
queryOrNot,
|
queryOrNot,
|
||||||
whereAddressQuery,
|
whereAddressQuery,
|
||||||
whereDateQuery,
|
|
||||||
} from "../utils/relation";
|
} from "../utils/relation";
|
||||||
import { isUsedError, notFoundError, relationError } from "../utils/error";
|
import { isUsedError, notFoundError, relationError } from "../utils/error";
|
||||||
|
|
||||||
|
|
@ -47,20 +46,16 @@ if (!process.env.MINIO_BUCKET) {
|
||||||
throw Error("Require MinIO bucket.");
|
throw Error("Require MinIO bucket.");
|
||||||
}
|
}
|
||||||
|
|
||||||
const MANAGE_ROLES = [
|
const MANAGE_ROLES = ["system", "head_of_admin"];
|
||||||
"system",
|
|
||||||
"head_of_admin",
|
|
||||||
"admin",
|
|
||||||
"executive",
|
|
||||||
"accountant",
|
|
||||||
"branch_admin",
|
|
||||||
"branch_manager",
|
|
||||||
"branch_accountant",
|
|
||||||
];
|
|
||||||
|
|
||||||
function globalAllow(user: RequestWithUser["user"]) {
|
function globalAllow(user: RequestWithUser["user"]) {
|
||||||
const listAllowed = ["system", "head_of_admin", "admin", "executive", "accountant"];
|
return MANAGE_ROLES.some((v) => user.roles?.includes(v));
|
||||||
return user.roles?.some((v) => listAllowed.includes(v)) || false;
|
}
|
||||||
|
|
||||||
|
function globalAllowView(user: RequestWithUser["user"]) {
|
||||||
|
return MANAGE_ROLES.concat("head_of_accountant", "head_of_sale").some((v) =>
|
||||||
|
user.roles?.includes(v),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
type BranchCreate = {
|
type BranchCreate = {
|
||||||
|
|
@ -151,7 +146,7 @@ type BranchUpdate = {
|
||||||
}[];
|
}[];
|
||||||
};
|
};
|
||||||
|
|
||||||
const permissionCond = createPermCondition(globalAllow);
|
const permissionCond = createPermCondition(globalAllowView);
|
||||||
const permissionCheck = createPermCheck(globalAllow);
|
const permissionCheck = createPermCheck(globalAllow);
|
||||||
|
|
||||||
@Route("api/v1/branch")
|
@Route("api/v1/branch")
|
||||||
|
|
@ -255,8 +250,6 @@ export class BranchController extends Controller {
|
||||||
@Query() query: string = "",
|
@Query() query: string = "",
|
||||||
@Query() page: number = 1,
|
@Query() page: number = 1,
|
||||||
@Query() pageSize: number = 30,
|
@Query() pageSize: number = 30,
|
||||||
@Query() startDate?: Date,
|
|
||||||
@Query() endDate?: Date,
|
|
||||||
) {
|
) {
|
||||||
const where = {
|
const where = {
|
||||||
AND: {
|
AND: {
|
||||||
|
|
@ -292,7 +285,6 @@ export class BranchController extends Controller {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
]),
|
]),
|
||||||
...whereDateQuery(startDate, endDate),
|
|
||||||
} satisfies Prisma.BranchWhereInput;
|
} satisfies Prisma.BranchWhereInput;
|
||||||
|
|
||||||
const [result, total] = await prisma.$transaction([
|
const [result, total] = await prisma.$transaction([
|
||||||
|
|
@ -323,14 +315,13 @@ export class BranchController extends Controller {
|
||||||
{ telephoneNo: { contains: query, mode: "insensitive" } },
|
{ telephoneNo: { contains: query, mode: "insensitive" } },
|
||||||
...whereAddressQuery(query),
|
...whereAddressQuery(query),
|
||||||
],
|
],
|
||||||
...whereDateQuery(startDate, endDate),
|
|
||||||
},
|
},
|
||||||
include: {
|
include: {
|
||||||
province: true,
|
province: true,
|
||||||
district: true,
|
district: true,
|
||||||
subDistrict: true,
|
subDistrict: true,
|
||||||
},
|
},
|
||||||
orderBy: [{ statusOrder: "asc" }, { code: "asc" }],
|
orderBy: { code: "asc" },
|
||||||
}
|
}
|
||||||
: false,
|
: false,
|
||||||
bank: true,
|
bank: true,
|
||||||
|
|
@ -374,7 +365,7 @@ export class BranchController extends Controller {
|
||||||
bank: true,
|
bank: true,
|
||||||
contact: includeContact,
|
contact: includeContact,
|
||||||
},
|
},
|
||||||
orderBy: [{ statusOrder: "asc" }, { code: "asc" }],
|
orderBy: { code: "asc" },
|
||||||
},
|
},
|
||||||
bank: true,
|
bank: true,
|
||||||
contact: includeContact,
|
contact: includeContact,
|
||||||
|
|
@ -387,14 +378,6 @@ export class BranchController extends Controller {
|
||||||
return record;
|
return record;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Get("{branchId}/bank")
|
|
||||||
@Security("keycloak")
|
|
||||||
async getBranchBankById(@Path() branchId: string) {
|
|
||||||
return await prisma.branchBank.findMany({
|
|
||||||
where: { branchId },
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
@Post()
|
@Post()
|
||||||
@Security("keycloak", MANAGE_ROLES)
|
@Security("keycloak", MANAGE_ROLES)
|
||||||
async createBranch(@Request() req: RequestWithUser, @Body() body: BranchCreate) {
|
async createBranch(@Request() req: RequestWithUser, @Body() body: BranchCreate) {
|
||||||
|
|
|
||||||
|
|
@ -18,21 +18,12 @@ import HttpError from "../interfaces/http-error";
|
||||||
import HttpStatus from "../interfaces/http-status";
|
import HttpStatus from "../interfaces/http-status";
|
||||||
import { RequestWithUser } from "../interfaces/user";
|
import { RequestWithUser } from "../interfaces/user";
|
||||||
import { branchRelationPermInclude, createPermCheck } from "../services/permission";
|
import { branchRelationPermInclude, createPermCheck } from "../services/permission";
|
||||||
import { queryOrNot, whereDateQuery } from "../utils/relation";
|
import { queryOrNot } from "../utils/relation";
|
||||||
|
|
||||||
const MANAGE_ROLES = [
|
const MANAGE_ROLES = ["system", "head_of_admin", "admin", "branch_manager"];
|
||||||
"system",
|
|
||||||
"head_of_admin",
|
|
||||||
"admin",
|
|
||||||
"executive",
|
|
||||||
"accountant",
|
|
||||||
"branch_admin",
|
|
||||||
"branch_manager",
|
|
||||||
"branch_accountant",
|
|
||||||
];
|
|
||||||
|
|
||||||
function globalAllow(user: RequestWithUser["user"]) {
|
function globalAllow(user: RequestWithUser["user"]) {
|
||||||
const listAllowed = ["system", "head_of_admin", "admin", "executive", "accountant"];
|
const listAllowed = ["system", "head_of_admin", "admin"];
|
||||||
return user.roles?.some((v) => listAllowed.includes(v)) || false;
|
return user.roles?.some((v) => listAllowed.includes(v)) || false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -106,8 +97,6 @@ export class UserBranchController extends Controller {
|
||||||
@Query() query: string = "",
|
@Query() query: string = "",
|
||||||
@Query() page: number = 1,
|
@Query() page: number = 1,
|
||||||
@Query() pageSize: number = 30,
|
@Query() pageSize: number = 30,
|
||||||
@Query() startDate?: Date,
|
|
||||||
@Query() endDate?: Date,
|
|
||||||
) {
|
) {
|
||||||
const where = {
|
const where = {
|
||||||
AND: {
|
AND: {
|
||||||
|
|
@ -118,7 +107,6 @@ export class UserBranchController extends Controller {
|
||||||
{ branch: { name: { contains: query, mode: "insensitive" } } },
|
{ branch: { name: { contains: query, mode: "insensitive" } } },
|
||||||
{ branch: { nameEN: { contains: query, mode: "insensitive" } } },
|
{ branch: { nameEN: { contains: query, mode: "insensitive" } } },
|
||||||
]),
|
]),
|
||||||
...whereDateQuery(startDate, endDate),
|
|
||||||
} satisfies Prisma.BranchUserWhereInput;
|
} satisfies Prisma.BranchUserWhereInput;
|
||||||
|
|
||||||
const [result, total] = await prisma.$transaction([
|
const [result, total] = await prisma.$transaction([
|
||||||
|
|
@ -162,8 +150,6 @@ export class BranchUserController extends Controller {
|
||||||
@Query() query: string = "",
|
@Query() query: string = "",
|
||||||
@Query() page: number = 1,
|
@Query() page: number = 1,
|
||||||
@Query() pageSize: number = 30,
|
@Query() pageSize: number = 30,
|
||||||
@Query() startDate?: Date,
|
|
||||||
@Query() endDate?: Date,
|
|
||||||
) {
|
) {
|
||||||
const where = {
|
const where = {
|
||||||
AND: {
|
AND: {
|
||||||
|
|
@ -178,7 +164,6 @@ export class BranchUserController extends Controller {
|
||||||
{ user: { email: { contains: query, mode: "insensitive" } } },
|
{ user: { email: { contains: query, mode: "insensitive" } } },
|
||||||
{ user: { telephoneNo: { contains: query, mode: "insensitive" } } },
|
{ user: { telephoneNo: { contains: query, mode: "insensitive" } } },
|
||||||
],
|
],
|
||||||
...whereDateQuery(startDate, endDate),
|
|
||||||
} satisfies Prisma.BranchUserWhereInput;
|
} satisfies Prisma.BranchUserWhereInput;
|
||||||
|
|
||||||
const [result, total] = await prisma.$transaction([
|
const [result, total] = await prisma.$transaction([
|
||||||
|
|
|
||||||
|
|
@ -27,7 +27,6 @@ import {
|
||||||
listRole,
|
listRole,
|
||||||
getUserRoles,
|
getUserRoles,
|
||||||
removeUserRoles,
|
removeUserRoles,
|
||||||
getGroupUser,
|
|
||||||
} from "../services/keycloak";
|
} from "../services/keycloak";
|
||||||
import { isSystem } from "../utils/keycloak";
|
import { isSystem } from "../utils/keycloak";
|
||||||
import {
|
import {
|
||||||
|
|
@ -52,7 +51,6 @@ import {
|
||||||
connectOrNot,
|
connectOrNot,
|
||||||
queryOrNot,
|
queryOrNot,
|
||||||
whereAddressQuery,
|
whereAddressQuery,
|
||||||
whereDateQuery,
|
|
||||||
} from "../utils/relation";
|
} from "../utils/relation";
|
||||||
import { isUsedError, notFoundError, relationError } from "../utils/error";
|
import { isUsedError, notFoundError, relationError } from "../utils/error";
|
||||||
import { retry } from "../utils/func";
|
import { retry } from "../utils/func";
|
||||||
|
|
@ -61,17 +59,10 @@ if (!process.env.MINIO_BUCKET) {
|
||||||
throw Error("Require MinIO bucket.");
|
throw Error("Require MinIO bucket.");
|
||||||
}
|
}
|
||||||
|
|
||||||
const MANAGE_ROLES = [
|
const MANAGE_ROLES = ["system", "head_of_admin", "admin", "branch_manager"];
|
||||||
"system",
|
|
||||||
"head_of_admin",
|
|
||||||
"admin",
|
|
||||||
"executive",
|
|
||||||
"branch_admin",
|
|
||||||
"branch_manager",
|
|
||||||
];
|
|
||||||
|
|
||||||
function globalAllow(user: RequestWithUser["user"]) {
|
function globalAllow(user: RequestWithUser["user"]) {
|
||||||
const listAllowed = ["system", "head_of_admin", "admin", "executive"];
|
const listAllowed = ["system", "head_of_admin"];
|
||||||
return user.roles?.some((v) => listAllowed.includes(v)) || false;
|
return user.roles?.some((v) => listAllowed.includes(v)) || false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -88,11 +79,11 @@ type UserCreate = {
|
||||||
citizenExpire?: Date | null;
|
citizenExpire?: Date | null;
|
||||||
|
|
||||||
namePrefix?: string | null;
|
namePrefix?: string | null;
|
||||||
firstName?: string;
|
firstName: string;
|
||||||
firstNameEN: string;
|
firstNameEN: string;
|
||||||
middleName?: string | null;
|
middleName?: string | null;
|
||||||
middleNameEN?: string | null;
|
middleNameEN?: string | null;
|
||||||
lastName?: string;
|
lastName: string;
|
||||||
lastNameEN: string;
|
lastNameEN: string;
|
||||||
gender: string;
|
gender: string;
|
||||||
|
|
||||||
|
|
@ -106,12 +97,11 @@ type UserCreate = {
|
||||||
licenseIssueDate?: Date | null;
|
licenseIssueDate?: Date | null;
|
||||||
licenseExpireDate?: Date | null;
|
licenseExpireDate?: Date | null;
|
||||||
sourceNationality?: string | null;
|
sourceNationality?: string | null;
|
||||||
importNationality?: string[] | null;
|
importNationality?: string | null;
|
||||||
trainingPlace?: string | null;
|
trainingPlace?: string | null;
|
||||||
responsibleArea?: string[] | null;
|
responsibleArea?: string[] | null;
|
||||||
birthDate?: Date | null;
|
birthDate?: Date | null;
|
||||||
|
|
||||||
addressForeign?: boolean;
|
|
||||||
address: string;
|
address: string;
|
||||||
addressEN: string;
|
addressEN: string;
|
||||||
soi?: string | null;
|
soi?: string | null;
|
||||||
|
|
@ -123,16 +113,9 @@ type UserCreate = {
|
||||||
email: string;
|
email: string;
|
||||||
telephoneNo: string;
|
telephoneNo: string;
|
||||||
|
|
||||||
subDistrictText?: string | null;
|
|
||||||
subDistrictTextEN?: string | null;
|
|
||||||
subDistrictId?: string | null;
|
subDistrictId?: string | null;
|
||||||
districtText?: string | null;
|
|
||||||
districtTextEN?: string | null;
|
|
||||||
districtId?: string | null;
|
districtId?: string | null;
|
||||||
provinceText?: string | null;
|
|
||||||
provinceTextEN?: string | null;
|
|
||||||
provinceId?: string | null;
|
provinceId?: string | null;
|
||||||
zipCodeText?: string | null;
|
|
||||||
|
|
||||||
selectedImage?: string;
|
selectedImage?: string;
|
||||||
|
|
||||||
|
|
@ -140,9 +123,6 @@ type UserCreate = {
|
||||||
|
|
||||||
remark?: string;
|
remark?: string;
|
||||||
agencyStatus?: string;
|
agencyStatus?: string;
|
||||||
|
|
||||||
contactName?: string | null;
|
|
||||||
contactTel?: string | null;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
type UserUpdate = {
|
type UserUpdate = {
|
||||||
|
|
@ -176,12 +156,11 @@ type UserUpdate = {
|
||||||
licenseIssueDate?: Date | null;
|
licenseIssueDate?: Date | null;
|
||||||
licenseExpireDate?: Date | null;
|
licenseExpireDate?: Date | null;
|
||||||
sourceNationality?: string | null;
|
sourceNationality?: string | null;
|
||||||
importNationality?: string[] | null;
|
importNationality?: string | null;
|
||||||
trainingPlace?: string | null;
|
trainingPlace?: string | null;
|
||||||
responsibleArea?: string[] | null;
|
responsibleArea?: string[] | null;
|
||||||
birthDate?: Date | null;
|
birthDate?: Date | null;
|
||||||
|
|
||||||
addressForeign?: boolean;
|
|
||||||
address?: string;
|
address?: string;
|
||||||
addressEN?: string;
|
addressEN?: string;
|
||||||
soi?: string | null;
|
soi?: string | null;
|
||||||
|
|
@ -195,24 +174,14 @@ type UserUpdate = {
|
||||||
|
|
||||||
selectedImage?: string;
|
selectedImage?: string;
|
||||||
|
|
||||||
subDistrictText?: string | null;
|
|
||||||
subDistrictTextEN?: string | null;
|
|
||||||
subDistrictId?: string | null;
|
subDistrictId?: string | null;
|
||||||
districtText?: string | null;
|
|
||||||
districtTextEN?: string | null;
|
|
||||||
districtId?: string | null;
|
districtId?: string | null;
|
||||||
provinceText?: string | null;
|
|
||||||
provinceTextEN?: string | null;
|
|
||||||
provinceId?: string | null;
|
provinceId?: string | null;
|
||||||
zipCodeText?: string | null;
|
|
||||||
|
|
||||||
branchId?: string | string[];
|
branchId?: string | string[];
|
||||||
|
|
||||||
remark?: string;
|
remark?: string;
|
||||||
agencyStatus?: string;
|
agencyStatus?: string;
|
||||||
|
|
||||||
contactName?: string | null;
|
|
||||||
contactTel?: string | null;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const permissionCondCompany = createPermCondition((_) => true);
|
const permissionCondCompany = createPermCondition((_) => true);
|
||||||
|
|
@ -304,8 +273,6 @@ export class UserController extends Controller {
|
||||||
@Query() status?: Status,
|
@Query() status?: Status,
|
||||||
@Query() responsibleDistrictId?: string,
|
@Query() responsibleDistrictId?: string,
|
||||||
@Query() activeBranchOnly?: boolean,
|
@Query() activeBranchOnly?: boolean,
|
||||||
@Query() startDate?: Date,
|
|
||||||
@Query() endDate?: Date,
|
|
||||||
) {
|
) {
|
||||||
return this.getUserByCriteria(
|
return this.getUserByCriteria(
|
||||||
req,
|
req,
|
||||||
|
|
@ -317,8 +284,6 @@ export class UserController extends Controller {
|
||||||
status,
|
status,
|
||||||
responsibleDistrictId,
|
responsibleDistrictId,
|
||||||
activeBranchOnly,
|
activeBranchOnly,
|
||||||
startDate,
|
|
||||||
endDate,
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -334,8 +299,6 @@ export class UserController extends Controller {
|
||||||
@Query() status?: Status,
|
@Query() status?: Status,
|
||||||
@Query() responsibleDistrictId?: string,
|
@Query() responsibleDistrictId?: string,
|
||||||
@Query() activeBranchOnly?: boolean,
|
@Query() activeBranchOnly?: boolean,
|
||||||
@Query() startDate?: Date,
|
|
||||||
@Query() endDate?: Date,
|
|
||||||
@Body()
|
@Body()
|
||||||
body?: {
|
body?: {
|
||||||
userId?: string[];
|
userId?: string[];
|
||||||
|
|
@ -399,14 +362,12 @@ export class UserController extends Controller {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
...whereDateQuery(startDate, endDate),
|
|
||||||
} satisfies Prisma.UserWhereInput;
|
} satisfies Prisma.UserWhereInput;
|
||||||
|
|
||||||
const [result, total] = await prisma.$transaction([
|
const [result, total] = await prisma.$transaction([
|
||||||
prisma.user.findMany({
|
prisma.user.findMany({
|
||||||
orderBy: [{ statusOrder: "asc" }, { createdAt: "asc" }],
|
orderBy: [{ statusOrder: "asc" }, { createdAt: "asc" }],
|
||||||
include: {
|
include: {
|
||||||
importNationality: true,
|
|
||||||
responsibleArea: true,
|
responsibleArea: true,
|
||||||
province: true,
|
province: true,
|
||||||
district: true,
|
district: true,
|
||||||
|
|
@ -425,7 +386,6 @@ export class UserController extends Controller {
|
||||||
return {
|
return {
|
||||||
result: result.map((v) => ({
|
result: result.map((v) => ({
|
||||||
...v,
|
...v,
|
||||||
importNationality: v.importNationality.map((v) => v.name),
|
|
||||||
responsibleArea: v.responsibleArea.map((v) => v.area),
|
responsibleArea: v.responsibleArea.map((v) => v.area),
|
||||||
branch: includeBranch ? v.branch.map((a) => a.branch) : undefined,
|
branch: includeBranch ? v.branch.map((a) => a.branch) : undefined,
|
||||||
})),
|
})),
|
||||||
|
|
@ -440,7 +400,6 @@ export class UserController extends Controller {
|
||||||
async getUserById(@Path() userId: string) {
|
async getUserById(@Path() userId: string) {
|
||||||
const record = await prisma.user.findFirst({
|
const record = await prisma.user.findFirst({
|
||||||
include: {
|
include: {
|
||||||
importNationality: true,
|
|
||||||
province: true,
|
province: true,
|
||||||
district: true,
|
district: true,
|
||||||
subDistrict: true,
|
subDistrict: true,
|
||||||
|
|
@ -452,11 +411,7 @@ export class UserController extends Controller {
|
||||||
|
|
||||||
if (!record) throw notFoundError("User");
|
if (!record) throw notFoundError("User");
|
||||||
|
|
||||||
const { importNationality, ...rest } = record;
|
return record;
|
||||||
|
|
||||||
return Object.assign(rest, {
|
|
||||||
importNationality: importNationality.map((v) => v.name),
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Post()
|
@Post()
|
||||||
|
|
@ -522,8 +477,8 @@ export class UserController extends Controller {
|
||||||
}
|
}
|
||||||
|
|
||||||
const userId = await createUser(username, username, {
|
const userId = await createUser(username, username, {
|
||||||
firstName: body.firstNameEN,
|
firstName: body.firstName,
|
||||||
lastName: body.lastNameEN,
|
lastName: body.lastName,
|
||||||
email: body.email,
|
email: body.email,
|
||||||
requiredActions: ["UPDATE_PASSWORD"],
|
requiredActions: ["UPDATE_PASSWORD"],
|
||||||
enabled: rest.status !== "INACTIVE",
|
enabled: rest.status !== "INACTIVE",
|
||||||
|
|
@ -558,9 +513,6 @@ export class UserController extends Controller {
|
||||||
create: rest.responsibleArea.map((v) => ({ area: v })),
|
create: rest.responsibleArea.map((v) => ({ area: v })),
|
||||||
}
|
}
|
||||||
: undefined,
|
: undefined,
|
||||||
importNationality: {
|
|
||||||
createMany: { data: rest.importNationality?.map((v) => ({ name: v })) || [] },
|
|
||||||
},
|
|
||||||
statusOrder: +(rest.status === "INACTIVE"),
|
statusOrder: +(rest.status === "INACTIVE"),
|
||||||
username,
|
username,
|
||||||
userRole: role.name,
|
userRole: role.name,
|
||||||
|
|
@ -716,7 +668,6 @@ export class UserController extends Controller {
|
||||||
|
|
||||||
const record = await prisma.user.update({
|
const record = await prisma.user.update({
|
||||||
include: {
|
include: {
|
||||||
importNationality: true,
|
|
||||||
province: true,
|
province: true,
|
||||||
district: true,
|
district: true,
|
||||||
subDistrict: true,
|
subDistrict: true,
|
||||||
|
|
@ -731,10 +682,6 @@ export class UserController extends Controller {
|
||||||
create: rest.responsibleArea.map((v) => ({ area: v })),
|
create: rest.responsibleArea.map((v) => ({ area: v })),
|
||||||
}
|
}
|
||||||
: undefined,
|
: undefined,
|
||||||
importNationality: {
|
|
||||||
deleteMany: {},
|
|
||||||
createMany: { data: rest.importNationality?.map((v) => ({ name: v })) || [] },
|
|
||||||
},
|
|
||||||
statusOrder: +(rest.status === "INACTIVE"),
|
statusOrder: +(rest.status === "INACTIVE"),
|
||||||
userRole,
|
userRole,
|
||||||
province: connectOrDisconnect(provinceId),
|
province: connectOrDisconnect(provinceId),
|
||||||
|
|
@ -986,17 +933,3 @@ export class UserSignatureController extends Controller {
|
||||||
await deleteFile(fileLocation.user.signature(userId));
|
await deleteFile(fileLocation.user.signature(userId));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Route("api/v1/user/{userId}/group")
|
|
||||||
@Tags("User")
|
|
||||||
@Security("keycloak")
|
|
||||||
export class UserGroupController extends Controller {
|
|
||||||
@Get()
|
|
||||||
async getUserGroup(@Path() userId: string) {
|
|
||||||
const groupUser = await getGroupUser(userId);
|
|
||||||
if (!Array.isArray(groupUser))
|
|
||||||
throw new Error("Failed. Cannot get user group(s) data from the server.");
|
|
||||||
|
|
||||||
return groupUser;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
||||||
|
|
@ -23,16 +23,15 @@ const MANAGE_ROLES = [
|
||||||
"system",
|
"system",
|
||||||
"head_of_admin",
|
"head_of_admin",
|
||||||
"admin",
|
"admin",
|
||||||
"executive",
|
"head_of_accountant",
|
||||||
"accountant",
|
"accountant",
|
||||||
"branch_admin",
|
"head_of_sale",
|
||||||
"branch_manager",
|
"sale",
|
||||||
"branch_accountant",
|
|
||||||
];
|
];
|
||||||
|
|
||||||
function globalAllow(user: RequestWithUser["user"]) {
|
function globalAllow(user: RequestWithUser["user"]) {
|
||||||
const listAllowed = MANAGE_ROLES;
|
const allowList = ["system", "head_of_admin", "admin", "head_of_accountant", "head_of_sale"];
|
||||||
return user.roles?.some((v) => listAllowed.includes(v)) || false;
|
return allowList.some((v) => user.roles?.includes(v));
|
||||||
}
|
}
|
||||||
|
|
||||||
type CustomerBranchCitizenPayload = {
|
type CustomerBranchCitizenPayload = {
|
||||||
|
|
|
||||||
|
|
@ -30,7 +30,6 @@ import {
|
||||||
connectOrNot,
|
connectOrNot,
|
||||||
queryOrNot,
|
queryOrNot,
|
||||||
whereAddressQuery,
|
whereAddressQuery,
|
||||||
whereDateQuery,
|
|
||||||
} from "../utils/relation";
|
} from "../utils/relation";
|
||||||
import { isUsedError, notFoundError, relationError } from "../utils/error";
|
import { isUsedError, notFoundError, relationError } from "../utils/error";
|
||||||
import {
|
import {
|
||||||
|
|
@ -47,18 +46,15 @@ const MANAGE_ROLES = [
|
||||||
"system",
|
"system",
|
||||||
"head_of_admin",
|
"head_of_admin",
|
||||||
"admin",
|
"admin",
|
||||||
"executive",
|
"head_of_accountant",
|
||||||
"accountant",
|
"accountant",
|
||||||
"branch_admin",
|
|
||||||
"branch_manager",
|
|
||||||
"branch_accountant",
|
|
||||||
"head_of_sale",
|
"head_of_sale",
|
||||||
"sale",
|
"sale",
|
||||||
];
|
];
|
||||||
|
|
||||||
function globalAllow(user: RequestWithUser["user"]) {
|
function globalAllow(user: RequestWithUser["user"]) {
|
||||||
const listAllowed = MANAGE_ROLES;
|
const allowList = ["system", "head_of_admin", "head_of_accountant", "head_of_sale"];
|
||||||
return user.roles?.some((v) => listAllowed.includes(v)) || false;
|
return allowList.some((v) => user.roles?.includes(v));
|
||||||
}
|
}
|
||||||
|
|
||||||
const permissionCondCompany = createPermCondition((_) => true);
|
const permissionCondCompany = createPermCondition((_) => true);
|
||||||
|
|
@ -87,6 +83,7 @@ export type CustomerBranchCreate = {
|
||||||
authorizedCapital?: string;
|
authorizedCapital?: string;
|
||||||
authorizedName?: string;
|
authorizedName?: string;
|
||||||
authorizedNameEN?: string;
|
authorizedNameEN?: string;
|
||||||
|
customerName?: string;
|
||||||
|
|
||||||
telephoneNo: string;
|
telephoneNo: string;
|
||||||
|
|
||||||
|
|
@ -110,7 +107,7 @@ export type CustomerBranchCreate = {
|
||||||
contactName: string;
|
contactName: string;
|
||||||
agentUserId?: string;
|
agentUserId?: string;
|
||||||
|
|
||||||
businessTypeId?: string;
|
businessType: string;
|
||||||
jobPosition: string;
|
jobPosition: string;
|
||||||
jobDescription: string;
|
jobDescription: string;
|
||||||
payDate: string;
|
payDate: string;
|
||||||
|
|
@ -144,6 +141,7 @@ export type CustomerBranchUpdate = {
|
||||||
authorizedCapital?: string;
|
authorizedCapital?: string;
|
||||||
authorizedName?: string;
|
authorizedName?: string;
|
||||||
authorizedNameEN?: string;
|
authorizedNameEN?: string;
|
||||||
|
customerName?: string;
|
||||||
|
|
||||||
telephoneNo: string;
|
telephoneNo: string;
|
||||||
|
|
||||||
|
|
@ -167,7 +165,7 @@ export type CustomerBranchUpdate = {
|
||||||
contactName?: string;
|
contactName?: string;
|
||||||
agentUserId?: string;
|
agentUserId?: string;
|
||||||
|
|
||||||
businessTypeId?: string;
|
businessType?: string;
|
||||||
jobPosition?: string;
|
jobPosition?: string;
|
||||||
jobDescription?: string;
|
jobDescription?: string;
|
||||||
payDate?: string;
|
payDate?: string;
|
||||||
|
|
@ -197,11 +195,10 @@ export class CustomerBranchController extends Controller {
|
||||||
@Query() page: number = 1,
|
@Query() page: number = 1,
|
||||||
@Query() pageSize: number = 30,
|
@Query() pageSize: number = 30,
|
||||||
@Query() activeRegisBranchOnly?: boolean,
|
@Query() activeRegisBranchOnly?: boolean,
|
||||||
@Query() startDate?: Date,
|
|
||||||
@Query() endDate?: Date,
|
|
||||||
) {
|
) {
|
||||||
const where = {
|
const where = {
|
||||||
OR: queryOrNot<Prisma.CustomerBranchWhereInput[]>(query, [
|
OR: queryOrNot<Prisma.CustomerBranchWhereInput[]>(query, [
|
||||||
|
{ customerName: { contains: query, mode: "insensitive" } },
|
||||||
{ registerName: { contains: query, mode: "insensitive" } },
|
{ registerName: { contains: query, mode: "insensitive" } },
|
||||||
{ registerNameEN: { contains: query, mode: "insensitive" } },
|
{ registerNameEN: { contains: query, mode: "insensitive" } },
|
||||||
{ email: { contains: query, mode: "insensitive" } },
|
{ email: { contains: query, mode: "insensitive" } },
|
||||||
|
|
@ -232,17 +229,11 @@ export class CustomerBranchController extends Controller {
|
||||||
subDistrict: zipCode ? { zipCode } : undefined,
|
subDistrict: zipCode ? { zipCode } : undefined,
|
||||||
...filterStatus(activeRegisBranchOnly ? Status.ACTIVE : status),
|
...filterStatus(activeRegisBranchOnly ? Status.ACTIVE : status),
|
||||||
},
|
},
|
||||||
...whereDateQuery(startDate, endDate),
|
|
||||||
} satisfies Prisma.CustomerBranchWhereInput;
|
} satisfies Prisma.CustomerBranchWhereInput;
|
||||||
|
|
||||||
const [result, total] = await prisma.$transaction([
|
const [result, total] = await prisma.$transaction([
|
||||||
prisma.customerBranch.findMany({
|
prisma.customerBranch.findMany({
|
||||||
orderBy: [{ code: "asc" }, { statusOrder: "asc" }, { createdAt: "asc" }],
|
orderBy: [{ code: "asc" }, { statusOrder: "asc" }, { createdAt: "asc" }],
|
||||||
omit: {
|
|
||||||
otpCode: true,
|
|
||||||
otpExpires: true,
|
|
||||||
userId: true,
|
|
||||||
},
|
|
||||||
include: {
|
include: {
|
||||||
customer: includeCustomer,
|
customer: includeCustomer,
|
||||||
province: true,
|
province: true,
|
||||||
|
|
@ -251,7 +242,6 @@ export class CustomerBranchController extends Controller {
|
||||||
createdBy: true,
|
createdBy: true,
|
||||||
updatedBy: true,
|
updatedBy: true,
|
||||||
_count: true,
|
_count: true,
|
||||||
businessType: true,
|
|
||||||
},
|
},
|
||||||
where,
|
where,
|
||||||
take: pageSize,
|
take: pageSize,
|
||||||
|
|
@ -267,11 +257,6 @@ export class CustomerBranchController extends Controller {
|
||||||
@Security("keycloak")
|
@Security("keycloak")
|
||||||
async getById(@Path() branchId: string) {
|
async getById(@Path() branchId: string) {
|
||||||
const record = await prisma.customerBranch.findFirst({
|
const record = await prisma.customerBranch.findFirst({
|
||||||
omit: {
|
|
||||||
otpCode: true,
|
|
||||||
otpExpires: true,
|
|
||||||
userId: true,
|
|
||||||
},
|
|
||||||
include: {
|
include: {
|
||||||
customer: true,
|
customer: true,
|
||||||
province: true,
|
province: true,
|
||||||
|
|
@ -279,7 +264,6 @@ export class CustomerBranchController extends Controller {
|
||||||
subDistrict: true,
|
subDistrict: true,
|
||||||
createdBy: true,
|
createdBy: true,
|
||||||
updatedBy: true,
|
updatedBy: true,
|
||||||
businessType: true,
|
|
||||||
},
|
},
|
||||||
where: { id: branchId },
|
where: { id: branchId },
|
||||||
});
|
});
|
||||||
|
|
@ -301,8 +285,6 @@ export class CustomerBranchController extends Controller {
|
||||||
@Query() visa?: boolean,
|
@Query() visa?: boolean,
|
||||||
@Query() page: number = 1,
|
@Query() page: number = 1,
|
||||||
@Query() pageSize: number = 30,
|
@Query() pageSize: number = 30,
|
||||||
@Query() startDate?: Date,
|
|
||||||
@Query() endDate?: Date,
|
|
||||||
) {
|
) {
|
||||||
const where = {
|
const where = {
|
||||||
OR: queryOrNot<Prisma.EmployeeWhereInput[]>(query, [
|
OR: queryOrNot<Prisma.EmployeeWhereInput[]>(query, [
|
||||||
|
|
@ -318,7 +300,6 @@ export class CustomerBranchController extends Controller {
|
||||||
subDistrict: zipCode ? { zipCode } : undefined,
|
subDistrict: zipCode ? { zipCode } : undefined,
|
||||||
gender,
|
gender,
|
||||||
},
|
},
|
||||||
...whereDateQuery(startDate, endDate),
|
|
||||||
} satisfies Prisma.EmployeeWhereInput;
|
} satisfies Prisma.EmployeeWhereInput;
|
||||||
|
|
||||||
const [result, total] = await prisma.$transaction([
|
const [result, total] = await prisma.$transaction([
|
||||||
|
|
@ -362,11 +343,6 @@ export class CustomerBranchController extends Controller {
|
||||||
include: branchRelationPermInclude(req.user),
|
include: branchRelationPermInclude(req.user),
|
||||||
},
|
},
|
||||||
branch: {
|
branch: {
|
||||||
omit: {
|
|
||||||
otpCode: true,
|
|
||||||
otpExpires: true,
|
|
||||||
userId: true,
|
|
||||||
},
|
|
||||||
take: 1,
|
take: 1,
|
||||||
orderBy: { createdAt: "asc" },
|
orderBy: { createdAt: "asc" },
|
||||||
},
|
},
|
||||||
|
|
@ -395,15 +371,7 @@ export class CustomerBranchController extends Controller {
|
||||||
(v) => (v.headOffice || v).code,
|
(v) => (v.headOffice || v).code,
|
||||||
);
|
);
|
||||||
|
|
||||||
const {
|
const { provinceId, districtId, subDistrictId, customerId, agentUserId, ...rest } = body;
|
||||||
provinceId,
|
|
||||||
districtId,
|
|
||||||
subDistrictId,
|
|
||||||
customerId,
|
|
||||||
agentUserId,
|
|
||||||
businessTypeId,
|
|
||||||
...rest
|
|
||||||
} = body;
|
|
||||||
|
|
||||||
const record = await prisma.$transaction(
|
const record = await prisma.$transaction(
|
||||||
async (tx) => {
|
async (tx) => {
|
||||||
|
|
@ -446,7 +414,6 @@ export class CustomerBranchController extends Controller {
|
||||||
subDistrict: true,
|
subDistrict: true,
|
||||||
createdBy: true,
|
createdBy: true,
|
||||||
updatedBy: true,
|
updatedBy: true,
|
||||||
businessType: true,
|
|
||||||
},
|
},
|
||||||
data: {
|
data: {
|
||||||
...rest,
|
...rest,
|
||||||
|
|
@ -458,7 +425,6 @@ export class CustomerBranchController extends Controller {
|
||||||
province: connectOrNot(provinceId),
|
province: connectOrNot(provinceId),
|
||||||
district: connectOrNot(districtId),
|
district: connectOrNot(districtId),
|
||||||
subDistrict: connectOrNot(subDistrictId),
|
subDistrict: connectOrNot(subDistrictId),
|
||||||
businessType: connectOrNot(businessTypeId),
|
|
||||||
createdBy: { connect: { id: req.user.sub } },
|
createdBy: { connect: { id: req.user.sub } },
|
||||||
updatedBy: { connect: { id: req.user.sub } },
|
updatedBy: { connect: { id: req.user.sub } },
|
||||||
},
|
},
|
||||||
|
|
@ -489,7 +455,6 @@ export class CustomerBranchController extends Controller {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
businessType: true,
|
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
@ -534,15 +499,7 @@ export class CustomerBranchController extends Controller {
|
||||||
await permissionCheck(req.user, customer.registeredBranch);
|
await permissionCheck(req.user, customer.registeredBranch);
|
||||||
}
|
}
|
||||||
|
|
||||||
const {
|
const { provinceId, districtId, subDistrictId, customerId, agentUserId, ...rest } = body;
|
||||||
provinceId,
|
|
||||||
districtId,
|
|
||||||
subDistrictId,
|
|
||||||
customerId,
|
|
||||||
agentUserId,
|
|
||||||
businessTypeId,
|
|
||||||
...rest
|
|
||||||
} = body;
|
|
||||||
|
|
||||||
return await prisma.customerBranch.update({
|
return await prisma.customerBranch.update({
|
||||||
where: { id: branchId },
|
where: { id: branchId },
|
||||||
|
|
@ -552,7 +509,6 @@ export class CustomerBranchController extends Controller {
|
||||||
subDistrict: true,
|
subDistrict: true,
|
||||||
createdBy: true,
|
createdBy: true,
|
||||||
updatedBy: true,
|
updatedBy: true,
|
||||||
businessType: true,
|
|
||||||
},
|
},
|
||||||
data: {
|
data: {
|
||||||
...rest,
|
...rest,
|
||||||
|
|
@ -562,7 +518,6 @@ export class CustomerBranchController extends Controller {
|
||||||
province: connectOrDisconnect(provinceId),
|
province: connectOrDisconnect(provinceId),
|
||||||
district: connectOrDisconnect(districtId),
|
district: connectOrDisconnect(districtId),
|
||||||
subDistrict: connectOrDisconnect(subDistrictId),
|
subDistrict: connectOrDisconnect(subDistrictId),
|
||||||
businessType: connectOrNot(businessTypeId),
|
|
||||||
updatedBy: { connect: { id: req.user.sub } },
|
updatedBy: { connect: { id: req.user.sub } },
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
@ -581,7 +536,6 @@ export class CustomerBranchController extends Controller {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
businessType: true,
|
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
@ -634,11 +588,10 @@ export class CustomerBranchFileController extends Controller {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
businessType: true,
|
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
if (!data) throw notFoundError("Customer Branch");
|
if (!data) throw notFoundError("Customer Branch");
|
||||||
await permissionCheckCompany(user, data.customer.registeredBranch);
|
await permissionCheck(user, data.customer.registeredBranch);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Get("attachment")
|
@Get("attachment")
|
||||||
|
|
|
||||||
|
|
@ -36,25 +36,21 @@ import {
|
||||||
setFile,
|
setFile,
|
||||||
} from "../utils/minio";
|
} from "../utils/minio";
|
||||||
import { isUsedError, notFoundError, relationError } from "../utils/error";
|
import { isUsedError, notFoundError, relationError } from "../utils/error";
|
||||||
import { connectOrNot, queryOrNot, whereDateQuery } from "../utils/relation";
|
import { connectOrNot, queryOrNot } from "../utils/relation";
|
||||||
import { json2csv } from "json-2-csv";
|
|
||||||
|
|
||||||
const MANAGE_ROLES = [
|
const MANAGE_ROLES = [
|
||||||
"system",
|
"system",
|
||||||
"head_of_admin",
|
"head_of_admin",
|
||||||
"admin",
|
"admin",
|
||||||
"executive",
|
"head_of_accountant",
|
||||||
"accountant",
|
"accountant",
|
||||||
"branch_admin",
|
|
||||||
"branch_manager",
|
|
||||||
"branch_accountant",
|
|
||||||
"head_of_sale",
|
"head_of_sale",
|
||||||
"sale",
|
"sale",
|
||||||
];
|
];
|
||||||
|
|
||||||
function globalAllow(user: RequestWithUser["user"]) {
|
function globalAllow(user: RequestWithUser["user"]) {
|
||||||
const listAllowed = MANAGE_ROLES;
|
const allowList = ["system", "head_of_admin", "head_of_accountant", "head_of_sale"];
|
||||||
return user.roles?.some((v) => listAllowed.includes(v)) || false;
|
return allowList.some((v) => user.roles?.includes(v));
|
||||||
}
|
}
|
||||||
|
|
||||||
const permissionCondCompany = createPermCondition((_) => true);
|
const permissionCondCompany = createPermCondition((_) => true);
|
||||||
|
|
@ -86,6 +82,7 @@ export type CustomerCreate = {
|
||||||
authorizedCapital?: string;
|
authorizedCapital?: string;
|
||||||
authorizedName?: string;
|
authorizedName?: string;
|
||||||
authorizedNameEN?: string;
|
authorizedNameEN?: string;
|
||||||
|
customerName?: string;
|
||||||
|
|
||||||
telephoneNo: string;
|
telephoneNo: string;
|
||||||
|
|
||||||
|
|
@ -109,7 +106,7 @@ export type CustomerCreate = {
|
||||||
contactName: string;
|
contactName: string;
|
||||||
agentUserId?: string;
|
agentUserId?: string;
|
||||||
|
|
||||||
businessTypeId?: string | null;
|
businessType: string;
|
||||||
jobPosition: string;
|
jobPosition: string;
|
||||||
jobDescription: string;
|
jobDescription: string;
|
||||||
payDate: string;
|
payDate: string;
|
||||||
|
|
@ -168,16 +165,11 @@ export class CustomerController extends Controller {
|
||||||
@Query() includeBranch: boolean = false,
|
@Query() includeBranch: boolean = false,
|
||||||
@Query() company: boolean = false,
|
@Query() company: boolean = false,
|
||||||
@Query() activeBranchOnly?: boolean,
|
@Query() activeBranchOnly?: boolean,
|
||||||
@Query() startDate?: Date,
|
|
||||||
@Query() endDate?: Date,
|
|
||||||
@Query() businessTypeId?: string,
|
|
||||||
@Query() provinceId?: string,
|
|
||||||
@Query() districtId?: string,
|
|
||||||
@Query() subDistrictId?: string,
|
|
||||||
) {
|
) {
|
||||||
const where = {
|
const where = {
|
||||||
OR: queryOrNot<Prisma.CustomerWhereInput[]>(query, [
|
OR: queryOrNot<Prisma.CustomerWhereInput[]>(query, [
|
||||||
{ branch: { some: { namePrefix: { contains: query, mode: "insensitive" } } } },
|
{ branch: { some: { namePrefix: { contains: query, mode: "insensitive" } } } },
|
||||||
|
{ branch: { some: { customerName: { contains: query, mode: "insensitive" } } } },
|
||||||
{ branch: { some: { registerName: { contains: query, mode: "insensitive" } } } },
|
{ branch: { some: { registerName: { contains: query, mode: "insensitive" } } } },
|
||||||
{ branch: { some: { registerNameEN: { contains: query, mode: "insensitive" } } } },
|
{ branch: { some: { registerNameEN: { contains: query, mode: "insensitive" } } } },
|
||||||
{ branch: { some: { firstName: { contains: query, mode: "insensitive" } } } },
|
{ branch: { some: { firstName: { contains: query, mode: "insensitive" } } } },
|
||||||
|
|
@ -196,36 +188,6 @@ export class CustomerController extends Controller {
|
||||||
: permissionCond(req.user, { activeOnly: activeBranchOnly }),
|
: permissionCond(req.user, { activeOnly: activeBranchOnly }),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
branch: {
|
|
||||||
some: {
|
|
||||||
AND: [
|
|
||||||
businessTypeId
|
|
||||||
? {
|
|
||||||
OR: [{ businessType: { id: businessTypeId } }],
|
|
||||||
}
|
|
||||||
: {},
|
|
||||||
|
|
||||||
provinceId
|
|
||||||
? {
|
|
||||||
OR: [{ province: { id: provinceId } }],
|
|
||||||
}
|
|
||||||
: {},
|
|
||||||
|
|
||||||
districtId
|
|
||||||
? {
|
|
||||||
OR: [{ district: { id: districtId } }],
|
|
||||||
}
|
|
||||||
: {},
|
|
||||||
|
|
||||||
subDistrictId
|
|
||||||
? {
|
|
||||||
OR: [{ subDistrict: { id: subDistrictId } }],
|
|
||||||
}
|
|
||||||
: {},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
},
|
|
||||||
...whereDateQuery(startDate, endDate),
|
|
||||||
} satisfies Prisma.CustomerWhereInput;
|
} satisfies Prisma.CustomerWhereInput;
|
||||||
|
|
||||||
const [result, total] = await prisma.$transaction([
|
const [result, total] = await prisma.$transaction([
|
||||||
|
|
@ -235,16 +197,10 @@ export class CustomerController extends Controller {
|
||||||
branch: includeBranch
|
branch: includeBranch
|
||||||
? {
|
? {
|
||||||
include: {
|
include: {
|
||||||
businessType: true,
|
|
||||||
province: true,
|
province: true,
|
||||||
district: true,
|
district: true,
|
||||||
subDistrict: true,
|
subDistrict: true,
|
||||||
},
|
},
|
||||||
omit: {
|
|
||||||
otpCode: true,
|
|
||||||
otpExpires: true,
|
|
||||||
userId: true,
|
|
||||||
},
|
|
||||||
orderBy: [{ statusOrder: "asc" }, { createdAt: "asc" }],
|
orderBy: [{ statusOrder: "asc" }, { createdAt: "asc" }],
|
||||||
}
|
}
|
||||||
: {
|
: {
|
||||||
|
|
@ -253,17 +209,11 @@ export class CustomerController extends Controller {
|
||||||
district: true,
|
district: true,
|
||||||
subDistrict: true,
|
subDistrict: true,
|
||||||
},
|
},
|
||||||
omit: {
|
|
||||||
otpCode: true,
|
|
||||||
otpExpires: true,
|
|
||||||
userId: true,
|
|
||||||
},
|
|
||||||
take: 1,
|
take: 1,
|
||||||
orderBy: { createdAt: "asc" },
|
orderBy: { createdAt: "asc" },
|
||||||
},
|
},
|
||||||
createdBy: true,
|
createdBy: true,
|
||||||
updatedBy: true,
|
updatedBy: true,
|
||||||
// businessType:true
|
|
||||||
},
|
},
|
||||||
orderBy: [{ statusOrder: "asc" }, { createdAt: "asc" }],
|
orderBy: [{ statusOrder: "asc" }, { createdAt: "asc" }],
|
||||||
where,
|
where,
|
||||||
|
|
@ -288,11 +238,6 @@ export class CustomerController extends Controller {
|
||||||
district: true,
|
district: true,
|
||||||
subDistrict: true,
|
subDistrict: true,
|
||||||
},
|
},
|
||||||
omit: {
|
|
||||||
otpCode: true,
|
|
||||||
otpExpires: true,
|
|
||||||
userId: true,
|
|
||||||
},
|
|
||||||
orderBy: { createdAt: "asc" },
|
orderBy: { createdAt: "asc" },
|
||||||
},
|
},
|
||||||
createdBy: true,
|
createdBy: true,
|
||||||
|
|
@ -364,11 +309,6 @@ export class CustomerController extends Controller {
|
||||||
district: true,
|
district: true,
|
||||||
subDistrict: true,
|
subDistrict: true,
|
||||||
},
|
},
|
||||||
omit: {
|
|
||||||
otpCode: true,
|
|
||||||
otpExpires: true,
|
|
||||||
userId: true,
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
createdBy: true,
|
createdBy: true,
|
||||||
updatedBy: true,
|
updatedBy: true,
|
||||||
|
|
@ -380,8 +320,6 @@ export class CustomerController extends Controller {
|
||||||
...v,
|
...v,
|
||||||
code: `${runningKey.replace(`CUSTOMER_BRANCH_${company}_`, "")}-${`${last.value - branch.length + i}`.padStart(2, "0")}`,
|
code: `${runningKey.replace(`CUSTOMER_BRANCH_${company}_`, "")}-${`${last.value - branch.length + i}`.padStart(2, "0")}`,
|
||||||
codeCustomer: runningKey.replace(`CUSTOMER_BRANCH_${company}_`, ""),
|
codeCustomer: runningKey.replace(`CUSTOMER_BRANCH_${company}_`, ""),
|
||||||
businessType: connectOrNot(v.businessTypeId),
|
|
||||||
businessTypeId: undefined,
|
|
||||||
agentUser: connectOrNot(v.agentUserId),
|
agentUser: connectOrNot(v.agentUserId),
|
||||||
agentUserId: undefined,
|
agentUserId: undefined,
|
||||||
province: connectOrNot(v.provinceId),
|
province: connectOrNot(v.provinceId),
|
||||||
|
|
@ -468,11 +406,6 @@ export class CustomerController extends Controller {
|
||||||
district: true,
|
district: true,
|
||||||
subDistrict: true,
|
subDistrict: true,
|
||||||
},
|
},
|
||||||
omit: {
|
|
||||||
otpCode: true,
|
|
||||||
otpExpires: true,
|
|
||||||
userId: true,
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
createdBy: true,
|
createdBy: true,
|
||||||
updatedBy: true,
|
updatedBy: true,
|
||||||
|
|
@ -511,13 +444,7 @@ export class CustomerController extends Controller {
|
||||||
await deleteFolder(`customer/${customerId}`);
|
await deleteFolder(`customer/${customerId}`);
|
||||||
const data = await tx.customer.delete({
|
const data = await tx.customer.delete({
|
||||||
include: {
|
include: {
|
||||||
branch: {
|
branch: true,
|
||||||
omit: {
|
|
||||||
otpCode: true,
|
|
||||||
otpExpires: true,
|
|
||||||
userId: true,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
registeredBranch: {
|
registeredBranch: {
|
||||||
include: {
|
include: {
|
||||||
headOffice: true,
|
headOffice: true,
|
||||||
|
|
@ -612,52 +539,3 @@ export class CustomerImageController extends Controller {
|
||||||
await deleteFile(fileLocation.customer.img(customerId, name));
|
await deleteFile(fileLocation.customer.img(customerId, name));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Route("api/v1/customer-export")
|
|
||||||
@Tags("Customer")
|
|
||||||
export class CustomerExportController extends CustomerController {
|
|
||||||
@Get()
|
|
||||||
@Security("keycloak")
|
|
||||||
async exportCustomer(
|
|
||||||
@Request() req: RequestWithUser,
|
|
||||||
@Query() customerType?: CustomerType,
|
|
||||||
@Query() query: string = "",
|
|
||||||
@Query() status?: Status,
|
|
||||||
@Query() page: number = 1,
|
|
||||||
@Query() pageSize: number = 30,
|
|
||||||
@Query() includeBranch: boolean = false,
|
|
||||||
@Query() company: boolean = false,
|
|
||||||
@Query() activeBranchOnly?: boolean,
|
|
||||||
@Query() startDate?: Date,
|
|
||||||
@Query() endDate?: Date,
|
|
||||||
@Query() businessTypeId?: string,
|
|
||||||
@Query() provinceId?: string,
|
|
||||||
@Query() districtId?: string,
|
|
||||||
@Query() subDistrictId?: string,
|
|
||||||
) {
|
|
||||||
const ret = await this.list(
|
|
||||||
req,
|
|
||||||
customerType,
|
|
||||||
query,
|
|
||||||
status,
|
|
||||||
page,
|
|
||||||
pageSize,
|
|
||||||
includeBranch,
|
|
||||||
company,
|
|
||||||
activeBranchOnly,
|
|
||||||
startDate,
|
|
||||||
endDate,
|
|
||||||
businessTypeId,
|
|
||||||
provinceId,
|
|
||||||
districtId,
|
|
||||||
subDistrictId,
|
|
||||||
);
|
|
||||||
|
|
||||||
this.setHeader("Content-Type", "text/csv");
|
|
||||||
|
|
||||||
return json2csv(
|
|
||||||
ret.result.map((v) => Object.assign(v, { branch: v.branch.at(0) ?? null })),
|
|
||||||
{ useDateIso8601Format: true, expandNestedObjects: true },
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
||||||
|
|
@ -23,18 +23,14 @@ const MANAGE_ROLES = [
|
||||||
"system",
|
"system",
|
||||||
"head_of_admin",
|
"head_of_admin",
|
||||||
"admin",
|
"admin",
|
||||||
"executive",
|
"head_of_accountant",
|
||||||
"accountant",
|
"accountant",
|
||||||
"branch_admin",
|
|
||||||
"branch_manager",
|
|
||||||
"branch_accountant",
|
|
||||||
"head_of_sale",
|
"head_of_sale",
|
||||||
"sale",
|
|
||||||
];
|
];
|
||||||
|
|
||||||
function globalAllow(user: RequestWithUser["user"]) {
|
function globalAllow(user: RequestWithUser["user"]) {
|
||||||
const listAllowed = MANAGE_ROLES;
|
const allowList = ["system", "head_of_admin", "head_of_accountant", "head_of_sale"];
|
||||||
return user.roles?.some((v) => listAllowed.includes(v)) || false;
|
return allowList.some((v) => user.roles?.includes(v));
|
||||||
}
|
}
|
||||||
|
|
||||||
type EmployeeCheckupPayload = {
|
type EmployeeCheckupPayload = {
|
||||||
|
|
|
||||||
|
|
@ -30,7 +30,6 @@ import {
|
||||||
connectOrNot,
|
connectOrNot,
|
||||||
queryOrNot,
|
queryOrNot,
|
||||||
whereAddressQuery,
|
whereAddressQuery,
|
||||||
whereDateQuery,
|
|
||||||
} from "../utils/relation";
|
} from "../utils/relation";
|
||||||
import { isUsedError, notFoundError, relationError } from "../utils/error";
|
import { isUsedError, notFoundError, relationError } from "../utils/error";
|
||||||
import {
|
import {
|
||||||
|
|
@ -42,7 +41,6 @@ import {
|
||||||
listFile,
|
listFile,
|
||||||
setFile,
|
setFile,
|
||||||
} from "../utils/minio";
|
} from "../utils/minio";
|
||||||
import { json2csv } from "json-2-csv";
|
|
||||||
|
|
||||||
if (!process.env.MINIO_BUCKET) {
|
if (!process.env.MINIO_BUCKET) {
|
||||||
throw Error("Require MinIO bucket.");
|
throw Error("Require MinIO bucket.");
|
||||||
|
|
@ -52,23 +50,17 @@ const MANAGE_ROLES = [
|
||||||
"system",
|
"system",
|
||||||
"head_of_admin",
|
"head_of_admin",
|
||||||
"admin",
|
"admin",
|
||||||
"executive",
|
"head_of_accountant",
|
||||||
"accountant",
|
"accountant",
|
||||||
"branch_admin",
|
|
||||||
"branch_manager",
|
|
||||||
"branch_accountant",
|
|
||||||
"head_of_sale",
|
"head_of_sale",
|
||||||
"sale",
|
|
||||||
];
|
];
|
||||||
|
|
||||||
function globalAllow(user: RequestWithUser["user"]) {
|
function globalAllow(user: RequestWithUser["user"]) {
|
||||||
const listAllowed = MANAGE_ROLES;
|
const allowList = ["system", "head_of_admin", "head_of_accountant", "head_of_sale"];
|
||||||
return user.roles?.some((v) => listAllowed.includes(v)) || false;
|
return allowList.some((v) => user.roles?.includes(v));
|
||||||
}
|
}
|
||||||
|
|
||||||
const permissionCondCompany = createPermCondition((_) => true);
|
|
||||||
const permissionCond = createPermCondition(globalAllow);
|
const permissionCond = createPermCondition(globalAllow);
|
||||||
const permissionCheckCompany = createPermCheck((_) => true);
|
|
||||||
const permissionCheck = createPermCheck(globalAllow);
|
const permissionCheck = createPermCheck(globalAllow);
|
||||||
|
|
||||||
type EmployeeCreate = {
|
type EmployeeCreate = {
|
||||||
|
|
@ -81,7 +73,6 @@ type EmployeeCreate = {
|
||||||
dateOfBirth?: Date | null;
|
dateOfBirth?: Date | null;
|
||||||
gender: string;
|
gender: string;
|
||||||
nationality: string;
|
nationality: string;
|
||||||
otherNationality?: string | null;
|
|
||||||
|
|
||||||
namePrefix?: string | null;
|
namePrefix?: string | null;
|
||||||
firstName?: string;
|
firstName?: string;
|
||||||
|
|
@ -115,10 +106,9 @@ type EmployeeUpdate = {
|
||||||
|
|
||||||
nrcNo?: string | null;
|
nrcNo?: string | null;
|
||||||
|
|
||||||
dateOfBirth?: Date | null;
|
dateOfBirth?: Date;
|
||||||
gender?: string;
|
gender?: string;
|
||||||
nationality?: string;
|
nationality?: string;
|
||||||
otherNationality?: string | null;
|
|
||||||
|
|
||||||
namePrefix?: string | null;
|
namePrefix?: string | null;
|
||||||
firstName?: string;
|
firstName?: string;
|
||||||
|
|
@ -126,7 +116,7 @@ type EmployeeUpdate = {
|
||||||
middleName?: string | null;
|
middleName?: string | null;
|
||||||
middleNameEN?: string | null;
|
middleNameEN?: string | null;
|
||||||
lastName?: string;
|
lastName?: string;
|
||||||
lastNameEN?: string;
|
lastNameEN: string;
|
||||||
|
|
||||||
addressEN?: string;
|
addressEN?: string;
|
||||||
address?: string;
|
address?: string;
|
||||||
|
|
@ -151,18 +141,9 @@ type EmployeeUpdate = {
|
||||||
export class EmployeeController extends Controller {
|
export class EmployeeController extends Controller {
|
||||||
@Get("stats")
|
@Get("stats")
|
||||||
@Security("keycloak")
|
@Security("keycloak")
|
||||||
async getEmployeeStats(@Request() req: RequestWithUser, @Query() customerBranchId?: string) {
|
async getEmployeeStats(@Query() customerBranchId?: string) {
|
||||||
return await prisma.employee.count({
|
return await prisma.employee.count({
|
||||||
where: {
|
where: { customerBranchId },
|
||||||
customerBranchId,
|
|
||||||
customerBranch: {
|
|
||||||
customer: isSystem(req.user)
|
|
||||||
? undefined
|
|
||||||
: {
|
|
||||||
registeredBranch: { OR: permissionCond(req.user) },
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -173,8 +154,6 @@ export class EmployeeController extends Controller {
|
||||||
@Query() customerBranchId?: string,
|
@Query() customerBranchId?: string,
|
||||||
@Query() status?: Status,
|
@Query() status?: Status,
|
||||||
@Query() query: string = "",
|
@Query() query: string = "",
|
||||||
@Query() startDate?: Date,
|
|
||||||
@Query() endDate?: Date,
|
|
||||||
) {
|
) {
|
||||||
return await prisma.employee
|
return await prisma.employee
|
||||||
.groupBy({
|
.groupBy({
|
||||||
|
|
@ -204,7 +183,6 @@ export class EmployeeController extends Controller {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
...whereDateQuery(startDate, endDate),
|
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
.then((res) =>
|
.then((res) =>
|
||||||
|
|
@ -230,8 +208,6 @@ export class EmployeeController extends Controller {
|
||||||
@Query() page: number = 1,
|
@Query() page: number = 1,
|
||||||
@Query() pageSize: number = 30,
|
@Query() pageSize: number = 30,
|
||||||
@Query() activeOnly?: boolean,
|
@Query() activeOnly?: boolean,
|
||||||
@Query() startDate?: Date,
|
|
||||||
@Query() endDate?: Date,
|
|
||||||
) {
|
) {
|
||||||
return this.listByCriteria(
|
return this.listByCriteria(
|
||||||
req,
|
req,
|
||||||
|
|
@ -246,10 +222,9 @@ export class EmployeeController extends Controller {
|
||||||
page,
|
page,
|
||||||
pageSize,
|
pageSize,
|
||||||
activeOnly,
|
activeOnly,
|
||||||
startDate,
|
|
||||||
endDate,
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Post("list")
|
@Post("list")
|
||||||
@Security("keycloak")
|
@Security("keycloak")
|
||||||
async listByCriteria(
|
async listByCriteria(
|
||||||
|
|
@ -265,8 +240,6 @@ export class EmployeeController extends Controller {
|
||||||
@Query() page: number = 1,
|
@Query() page: number = 1,
|
||||||
@Query() pageSize: number = 30,
|
@Query() pageSize: number = 30,
|
||||||
@Query() activeOnly?: boolean,
|
@Query() activeOnly?: boolean,
|
||||||
@Query() startDate?: Date,
|
|
||||||
@Query() endDate?: Date,
|
|
||||||
@Body()
|
@Body()
|
||||||
body?: {
|
body?: {
|
||||||
passport?: string[];
|
passport?: string[];
|
||||||
|
|
@ -315,7 +288,6 @@ export class EmployeeController extends Controller {
|
||||||
subDistrict: zipCode ? { zipCode } : undefined,
|
subDistrict: zipCode ? { zipCode } : undefined,
|
||||||
gender,
|
gender,
|
||||||
},
|
},
|
||||||
...whereDateQuery(startDate, endDate),
|
|
||||||
} satisfies Prisma.EmployeeWhereInput;
|
} satisfies Prisma.EmployeeWhereInput;
|
||||||
|
|
||||||
const [result, total] = await prisma.$transaction([
|
const [result, total] = await prisma.$transaction([
|
||||||
|
|
@ -671,7 +643,7 @@ export class EmployeeFileController extends Controller {
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
if (!data) throw notFoundError("Employee");
|
if (!data) throw notFoundError("Employee");
|
||||||
await permissionCheckCompany(user, data.customerBranch.customer.registeredBranch);
|
await permissionCheck(user, data.customerBranch.customer.registeredBranch);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Get("image")
|
@Get("image")
|
||||||
|
|
@ -927,55 +899,3 @@ export class EmployeeFileController extends Controller {
|
||||||
return await deleteFile(fileLocation.employee.inCountryNotice(employeeId, noticeId));
|
return await deleteFile(fileLocation.employee.inCountryNotice(employeeId, noticeId));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Route("api/v1/employee-export")
|
|
||||||
@Tags("Employee")
|
|
||||||
export class EmployeeExportController extends EmployeeController {
|
|
||||||
@Get()
|
|
||||||
@Security("keycloak")
|
|
||||||
async exportEmployee(
|
|
||||||
@Request() req: RequestWithUser,
|
|
||||||
@Query() zipCode?: string,
|
|
||||||
@Query() gender?: string,
|
|
||||||
@Query() status?: Status,
|
|
||||||
@Query() visa?: boolean,
|
|
||||||
@Query() passport?: boolean,
|
|
||||||
@Query() customerId?: string,
|
|
||||||
@Query() customerBranchId?: string,
|
|
||||||
@Query() query: string = "",
|
|
||||||
@Query() page: number = 1,
|
|
||||||
@Query() pageSize: number = 30,
|
|
||||||
@Query() activeOnly?: boolean,
|
|
||||||
@Query() startDate?: Date,
|
|
||||||
@Query() endDate?: Date,
|
|
||||||
) {
|
|
||||||
const ret = await this.listByCriteria(
|
|
||||||
req,
|
|
||||||
zipCode,
|
|
||||||
gender,
|
|
||||||
status,
|
|
||||||
visa,
|
|
||||||
passport,
|
|
||||||
customerId,
|
|
||||||
customerBranchId,
|
|
||||||
query,
|
|
||||||
page,
|
|
||||||
pageSize,
|
|
||||||
activeOnly,
|
|
||||||
startDate,
|
|
||||||
endDate,
|
|
||||||
);
|
|
||||||
|
|
||||||
this.setHeader("Content-Type", "text/csv");
|
|
||||||
|
|
||||||
return json2csv(
|
|
||||||
ret.result.map((v) =>
|
|
||||||
Object.assign(v, {
|
|
||||||
employeePassport: v.employeePassport?.at(0) ?? null,
|
|
||||||
employeeVisa: v.employeeVisa?.at(0) ?? null,
|
|
||||||
}),
|
|
||||||
),
|
|
||||||
{ useDateIso8601Format: true },
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
||||||
|
|
@ -23,18 +23,14 @@ const MANAGE_ROLES = [
|
||||||
"system",
|
"system",
|
||||||
"head_of_admin",
|
"head_of_admin",
|
||||||
"admin",
|
"admin",
|
||||||
"executive",
|
"head_of_accountant",
|
||||||
"accountant",
|
"accountant",
|
||||||
"branch_admin",
|
|
||||||
"branch_manager",
|
|
||||||
"branch_accountant",
|
|
||||||
"head_of_sale",
|
"head_of_sale",
|
||||||
"sale",
|
|
||||||
];
|
];
|
||||||
|
|
||||||
function globalAllow(user: RequestWithUser["user"]) {
|
function globalAllow(user: RequestWithUser["user"]) {
|
||||||
const listAllowed = MANAGE_ROLES;
|
const allowList = ["system", "head_of_admin", "head_of_accountant", "head_of_sale"];
|
||||||
return user.roles?.some((v) => listAllowed.includes(v)) || false;
|
return allowList.some((v) => user.roles?.includes(v));
|
||||||
}
|
}
|
||||||
|
|
||||||
type EmployeeOtherInfoPayload = {
|
type EmployeeOtherInfoPayload = {
|
||||||
|
|
|
||||||
|
|
@ -22,18 +22,14 @@ const MANAGE_ROLES = [
|
||||||
"system",
|
"system",
|
||||||
"head_of_admin",
|
"head_of_admin",
|
||||||
"admin",
|
"admin",
|
||||||
"executive",
|
"head_of_accountant",
|
||||||
"accountant",
|
"accountant",
|
||||||
"branch_admin",
|
|
||||||
"branch_manager",
|
|
||||||
"branch_accountant",
|
|
||||||
"head_of_sale",
|
"head_of_sale",
|
||||||
"sale",
|
|
||||||
];
|
];
|
||||||
|
|
||||||
function globalAllow(user: RequestWithUser["user"]) {
|
function globalAllow(user: RequestWithUser["user"]) {
|
||||||
const listAllowed = MANAGE_ROLES;
|
const allowList = ["system", "head_of_admin", "head_of_accountant", "head_of_sale"];
|
||||||
return user.roles?.some((v) => listAllowed.includes(v)) || false;
|
return allowList.some((v) => user.roles?.includes(v));
|
||||||
}
|
}
|
||||||
|
|
||||||
type EmployeePassportPayload = {
|
type EmployeePassportPayload = {
|
||||||
|
|
@ -47,7 +43,6 @@ type EmployeePassportPayload = {
|
||||||
|
|
||||||
workerStatus: string;
|
workerStatus: string;
|
||||||
nationality: string;
|
nationality: string;
|
||||||
otherNationality?: string | null;
|
|
||||||
namePrefix?: string | null;
|
namePrefix?: string | null;
|
||||||
firstName: string;
|
firstName: string;
|
||||||
firstNameEN: string;
|
firstNameEN: string;
|
||||||
|
|
|
||||||
|
|
@ -22,18 +22,14 @@ const MANAGE_ROLES = [
|
||||||
"system",
|
"system",
|
||||||
"head_of_admin",
|
"head_of_admin",
|
||||||
"admin",
|
"admin",
|
||||||
"executive",
|
"head_of_accountant",
|
||||||
"accountant",
|
"accountant",
|
||||||
"branch_admin",
|
|
||||||
"branch_manager",
|
|
||||||
"branch_accountant",
|
|
||||||
"head_of_sale",
|
"head_of_sale",
|
||||||
"sale",
|
|
||||||
];
|
];
|
||||||
|
|
||||||
function globalAllow(user: RequestWithUser["user"]) {
|
function globalAllow(user: RequestWithUser["user"]) {
|
||||||
const listAllowed = MANAGE_ROLES;
|
const allowList = ["system", "head_of_admin", "head_of_accountant", "head_of_sale"];
|
||||||
return user.roles?.some((v) => listAllowed.includes(v)) || false;
|
return allowList.some((v) => user.roles?.includes(v));
|
||||||
}
|
}
|
||||||
|
|
||||||
type EmployeeVisaPayload = {
|
type EmployeeVisaPayload = {
|
||||||
|
|
@ -44,7 +40,6 @@ type EmployeeVisaPayload = {
|
||||||
issuePlace: string;
|
issuePlace: string;
|
||||||
issueDate: Date;
|
issueDate: Date;
|
||||||
expireDate: Date;
|
expireDate: Date;
|
||||||
reportDate?: Date | null;
|
|
||||||
mrz?: string | null;
|
mrz?: string | null;
|
||||||
remark?: string | null;
|
remark?: string | null;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -22,18 +22,14 @@ const MANAGE_ROLES = [
|
||||||
"system",
|
"system",
|
||||||
"head_of_admin",
|
"head_of_admin",
|
||||||
"admin",
|
"admin",
|
||||||
"executive",
|
"head_of_accountant",
|
||||||
"accountant",
|
"accountant",
|
||||||
"branch_admin",
|
|
||||||
"branch_manager",
|
|
||||||
"branch_accountant",
|
|
||||||
"head_of_sale",
|
"head_of_sale",
|
||||||
"sale",
|
|
||||||
];
|
];
|
||||||
|
|
||||||
function globalAllow(user: RequestWithUser["user"]) {
|
function globalAllow(user: RequestWithUser["user"]) {
|
||||||
const listAllowed = MANAGE_ROLES;
|
const allowList = ["system", "head_of_admin", "head_of_accountant", "head_of_sale"];
|
||||||
return user.roles?.some((v) => listAllowed.includes(v)) || false;
|
return allowList.some((v) => user.roles?.includes(v));
|
||||||
}
|
}
|
||||||
|
|
||||||
type EmployeeWorkPayload = {
|
type EmployeeWorkPayload = {
|
||||||
|
|
|
||||||
|
|
@ -24,7 +24,7 @@ import HttpError from "../interfaces/http-error";
|
||||||
import HttpStatus from "../interfaces/http-status";
|
import HttpStatus from "../interfaces/http-status";
|
||||||
import { notFoundError } from "../utils/error";
|
import { notFoundError } from "../utils/error";
|
||||||
import { filterStatus } from "../services/prisma";
|
import { filterStatus } from "../services/prisma";
|
||||||
import { queryOrNot, whereDateQuery } from "../utils/relation";
|
import { queryOrNot } from "../utils/relation";
|
||||||
|
|
||||||
type WorkflowPayload = {
|
type WorkflowPayload = {
|
||||||
name: string;
|
name: string;
|
||||||
|
|
@ -37,37 +37,20 @@ type WorkflowPayload = {
|
||||||
attributes?: { [key: string]: any };
|
attributes?: { [key: string]: any };
|
||||||
responsiblePersonId?: string[];
|
responsiblePersonId?: string[];
|
||||||
responsibleInstitution?: string[];
|
responsibleInstitution?: string[];
|
||||||
responsibleGroup?: string[];
|
|
||||||
messengerByArea?: boolean;
|
messengerByArea?: boolean;
|
||||||
}[];
|
}[];
|
||||||
registeredBranchId?: string;
|
registeredBranchId?: string;
|
||||||
status?: Status;
|
status?: Status;
|
||||||
};
|
};
|
||||||
|
|
||||||
const MANAGE_ROLES = [
|
const permissionCondCompany = createPermCondition((_) => true);
|
||||||
"system",
|
const permissionCheckCompany = createPermCheck((_) => true);
|
||||||
"head_of_admin",
|
|
||||||
"admin",
|
|
||||||
"executive",
|
|
||||||
"accountant",
|
|
||||||
"branch_admin",
|
|
||||||
"branch_manager",
|
|
||||||
"branch_accountant",
|
|
||||||
];
|
|
||||||
|
|
||||||
function globalAllow(user: RequestWithUser["user"]) {
|
|
||||||
const listAllowed = MANAGE_ROLES;
|
|
||||||
return user.roles?.some((v) => listAllowed.includes(v)) || false;
|
|
||||||
}
|
|
||||||
|
|
||||||
const permissionCondCompany = createPermCondition(globalAllow);
|
|
||||||
const permissionCheckCompany = createPermCheck(globalAllow);
|
|
||||||
|
|
||||||
@Route("api/v1/workflow-template")
|
@Route("api/v1/workflow-template")
|
||||||
@Tags("Workflow")
|
@Tags("Workflow")
|
||||||
|
@Security("keycloak")
|
||||||
export class FlowTemplateController extends Controller {
|
export class FlowTemplateController extends Controller {
|
||||||
@Get()
|
@Get()
|
||||||
@Security("keycloak")
|
|
||||||
async getFlowTemplate(
|
async getFlowTemplate(
|
||||||
@Request() req: RequestWithUser,
|
@Request() req: RequestWithUser,
|
||||||
@Query() page: number = 1,
|
@Query() page: number = 1,
|
||||||
|
|
@ -75,8 +58,6 @@ export class FlowTemplateController extends Controller {
|
||||||
@Query() status?: Status,
|
@Query() status?: Status,
|
||||||
@Query() query = "",
|
@Query() query = "",
|
||||||
@Query() activeOnly?: boolean,
|
@Query() activeOnly?: boolean,
|
||||||
@Query() startDate?: Date,
|
|
||||||
@Query() endDate?: Date,
|
|
||||||
) {
|
) {
|
||||||
const where = {
|
const where = {
|
||||||
OR: queryOrNot(query, [
|
OR: queryOrNot(query, [
|
||||||
|
|
@ -93,7 +74,6 @@ export class FlowTemplateController extends Controller {
|
||||||
OR: permissionCondCompany(req.user, { activeOnly: true }),
|
OR: permissionCondCompany(req.user, { activeOnly: true }),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
...whereDateQuery(startDate, endDate),
|
|
||||||
} satisfies Prisma.WorkflowTemplateWhereInput;
|
} satisfies Prisma.WorkflowTemplateWhereInput;
|
||||||
const [result, total] = await prisma.$transaction([
|
const [result, total] = await prisma.$transaction([
|
||||||
prisma.workflowTemplate.findMany({
|
prisma.workflowTemplate.findMany({
|
||||||
|
|
@ -106,7 +86,6 @@ export class FlowTemplateController extends Controller {
|
||||||
include: { user: true },
|
include: { user: true },
|
||||||
},
|
},
|
||||||
responsibleInstitution: true,
|
responsibleInstitution: true,
|
||||||
responsibleGroup: true,
|
|
||||||
},
|
},
|
||||||
orderBy: { order: "asc" },
|
orderBy: { order: "asc" },
|
||||||
},
|
},
|
||||||
|
|
@ -124,7 +103,6 @@ export class FlowTemplateController extends Controller {
|
||||||
step: r.step.map((v) => ({
|
step: r.step.map((v) => ({
|
||||||
...v,
|
...v,
|
||||||
responsibleInstitution: v.responsibleInstitution.map((institution) => institution.group),
|
responsibleInstitution: v.responsibleInstitution.map((institution) => institution.group),
|
||||||
responsibleGroup: v.responsibleGroup.map((group) => group.group),
|
|
||||||
})),
|
})),
|
||||||
})),
|
})),
|
||||||
page,
|
page,
|
||||||
|
|
@ -134,7 +112,6 @@ export class FlowTemplateController extends Controller {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Get("{templateId}")
|
@Get("{templateId}")
|
||||||
@Security("keycloak")
|
|
||||||
async getFlowTemplateById(@Request() _req: RequestWithUser, @Path() templateId: string) {
|
async getFlowTemplateById(@Request() _req: RequestWithUser, @Path() templateId: string) {
|
||||||
const record = await prisma.workflowTemplate.findFirst({
|
const record = await prisma.workflowTemplate.findFirst({
|
||||||
include: {
|
include: {
|
||||||
|
|
@ -146,7 +123,6 @@ export class FlowTemplateController extends Controller {
|
||||||
include: { user: true },
|
include: { user: true },
|
||||||
},
|
},
|
||||||
responsibleInstitution: true,
|
responsibleInstitution: true,
|
||||||
responsibleGroup: true,
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
@ -161,13 +137,11 @@ export class FlowTemplateController extends Controller {
|
||||||
step: record.step.map((v) => ({
|
step: record.step.map((v) => ({
|
||||||
...v,
|
...v,
|
||||||
responsibleInstitution: v.responsibleInstitution.map((institution) => institution.group),
|
responsibleInstitution: v.responsibleInstitution.map((institution) => institution.group),
|
||||||
responsibleGroup: v.responsibleGroup.map((group) => group.group),
|
|
||||||
})),
|
})),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@Post()
|
@Post()
|
||||||
@Security("keycloak", MANAGE_ROLES)
|
|
||||||
async createFlowTemplate(@Request() req: RequestWithUser, @Body() body: WorkflowPayload) {
|
async createFlowTemplate(@Request() req: RequestWithUser, @Body() body: WorkflowPayload) {
|
||||||
const where = {
|
const where = {
|
||||||
OR: [
|
OR: [
|
||||||
|
|
@ -238,9 +212,6 @@ export class FlowTemplateController extends Controller {
|
||||||
responsibleInstitution: {
|
responsibleInstitution: {
|
||||||
create: v.responsibleInstitution?.map((group) => ({ group })),
|
create: v.responsibleInstitution?.map((group) => ({ group })),
|
||||||
},
|
},
|
||||||
responsibleGroup: {
|
|
||||||
create: v.responsibleGroup?.map((group) => ({ group })),
|
|
||||||
},
|
|
||||||
})),
|
})),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
@ -248,7 +219,6 @@ export class FlowTemplateController extends Controller {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Put("{templateId}")
|
@Put("{templateId}")
|
||||||
@Security("keycloak", MANAGE_ROLES)
|
|
||||||
async updateFlowTemplate(
|
async updateFlowTemplate(
|
||||||
@Request() req: RequestWithUser,
|
@Request() req: RequestWithUser,
|
||||||
@Path() templateId: string,
|
@Path() templateId: string,
|
||||||
|
|
@ -322,10 +292,6 @@ export class FlowTemplateController extends Controller {
|
||||||
deleteMany: {},
|
deleteMany: {},
|
||||||
create: v.responsibleInstitution?.map((group) => ({ group })),
|
create: v.responsibleInstitution?.map((group) => ({ group })),
|
||||||
},
|
},
|
||||||
responsibleGroup: {
|
|
||||||
deleteMany: {},
|
|
||||||
create: v.responsibleGroup?.map((group) => ({ group })),
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
})),
|
})),
|
||||||
},
|
},
|
||||||
|
|
@ -334,7 +300,6 @@ export class FlowTemplateController extends Controller {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Delete("{templateId}")
|
@Delete("{templateId}")
|
||||||
@Security("keycloak", MANAGE_ROLES)
|
|
||||||
async deleteFlowTemplateById(@Request() req: RequestWithUser, @Path() templateId: string) {
|
async deleteFlowTemplateById(@Request() req: RequestWithUser, @Path() templateId: string) {
|
||||||
const record = await prisma.workflowTemplate.findUnique({
|
const record = await prisma.workflowTemplate.findUnique({
|
||||||
where: { id: templateId },
|
where: { id: templateId },
|
||||||
|
|
|
||||||
|
|
@ -17,7 +17,7 @@ import {
|
||||||
} from "tsoa";
|
} from "tsoa";
|
||||||
import prisma from "../db";
|
import prisma from "../db";
|
||||||
import { isUsedError, notFoundError } from "../utils/error";
|
import { isUsedError, notFoundError } from "../utils/error";
|
||||||
import { queryOrNot, whereDateQuery } from "../utils/relation";
|
import { queryOrNot } from "../utils/relation";
|
||||||
import { RequestWithUser } from "../interfaces/user";
|
import { RequestWithUser } from "../interfaces/user";
|
||||||
import { deleteFile, fileLocation, getFile, getPresigned, listFile, setFile } from "../utils/minio";
|
import { deleteFile, fileLocation, getFile, getPresigned, listFile, setFile } from "../utils/minio";
|
||||||
import HttpError from "../interfaces/http-error";
|
import HttpError from "../interfaces/http-error";
|
||||||
|
|
@ -95,17 +95,6 @@ type InstitutionUpdatePayload = {
|
||||||
}[];
|
}[];
|
||||||
};
|
};
|
||||||
|
|
||||||
const MANAGE_ROLES = [
|
|
||||||
"system",
|
|
||||||
"head_of_admin",
|
|
||||||
"admin",
|
|
||||||
"executive",
|
|
||||||
"accountant",
|
|
||||||
"branch_admin",
|
|
||||||
"branch_manager",
|
|
||||||
"branch_accountant",
|
|
||||||
];
|
|
||||||
|
|
||||||
@Route("api/v1/institution")
|
@Route("api/v1/institution")
|
||||||
@Tags("Institution")
|
@Tags("Institution")
|
||||||
export class InstitutionController extends Controller {
|
export class InstitutionController extends Controller {
|
||||||
|
|
@ -119,19 +108,8 @@ export class InstitutionController extends Controller {
|
||||||
@Query() status?: Status,
|
@Query() status?: Status,
|
||||||
@Query() activeOnly?: boolean,
|
@Query() activeOnly?: boolean,
|
||||||
@Query() group?: string,
|
@Query() group?: string,
|
||||||
@Query() startDate?: Date,
|
|
||||||
@Query() endDate?: Date,
|
|
||||||
) {
|
) {
|
||||||
return this.getInstitutionListByCriteria(
|
return this.getInstitutionListByCriteria(query, page, pageSize, status, activeOnly, group);
|
||||||
query,
|
|
||||||
page,
|
|
||||||
pageSize,
|
|
||||||
status,
|
|
||||||
activeOnly,
|
|
||||||
group,
|
|
||||||
startDate,
|
|
||||||
endDate,
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Post("list")
|
@Post("list")
|
||||||
|
|
@ -144,8 +122,6 @@ export class InstitutionController extends Controller {
|
||||||
@Query() status?: Status,
|
@Query() status?: Status,
|
||||||
@Query() activeOnly?: boolean,
|
@Query() activeOnly?: boolean,
|
||||||
@Query() group?: string,
|
@Query() group?: string,
|
||||||
@Query() startDate?: Date,
|
|
||||||
@Query() endDate?: Date,
|
|
||||||
@Body()
|
@Body()
|
||||||
body?: {
|
body?: {
|
||||||
group?: string[];
|
group?: string[];
|
||||||
|
|
@ -158,7 +134,6 @@ export class InstitutionController extends Controller {
|
||||||
{ name: { contains: query, mode: "insensitive" } },
|
{ name: { contains: query, mode: "insensitive" } },
|
||||||
{ code: { contains: query, mode: "insensitive" } },
|
{ code: { contains: query, mode: "insensitive" } },
|
||||||
]),
|
]),
|
||||||
...whereDateQuery(startDate, endDate),
|
|
||||||
} satisfies Prisma.InstitutionWhereInput;
|
} satisfies Prisma.InstitutionWhereInput;
|
||||||
|
|
||||||
const [result, total] = await prisma.$transaction([
|
const [result, total] = await prisma.$transaction([
|
||||||
|
|
@ -196,14 +171,13 @@ export class InstitutionController extends Controller {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Post()
|
@Post()
|
||||||
@Security("keycloak", MANAGE_ROLES)
|
@Security("keycloak")
|
||||||
@OperationId("createInstitution")
|
@OperationId("createInstitution")
|
||||||
async createInstitution(
|
async createInstitution(
|
||||||
@Body()
|
@Body()
|
||||||
body: InstitutionPayload & {
|
body: InstitutionPayload & {
|
||||||
status?: Status;
|
status?: Status;
|
||||||
},
|
},
|
||||||
@Request() req: RequestWithUser,
|
|
||||||
) {
|
) {
|
||||||
return await prisma.$transaction(async (tx) => {
|
return await prisma.$transaction(async (tx) => {
|
||||||
const last = await tx.runningNo.upsert({
|
const last = await tx.runningNo.upsert({
|
||||||
|
|
@ -220,8 +194,6 @@ export class InstitutionController extends Controller {
|
||||||
return await tx.institution.create({
|
return await tx.institution.create({
|
||||||
include: {
|
include: {
|
||||||
bank: true,
|
bank: true,
|
||||||
createdBy: true,
|
|
||||||
updatedBy: true,
|
|
||||||
},
|
},
|
||||||
data: {
|
data: {
|
||||||
...body,
|
...body,
|
||||||
|
|
@ -232,15 +204,13 @@ export class InstitutionController extends Controller {
|
||||||
data: body.bank ?? [],
|
data: body.bank ?? [],
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
createdByUserId: req.user.sub,
|
|
||||||
updatedByUserId: req.user.sub,
|
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@Put("{institutionId}")
|
@Put("{institutionId}")
|
||||||
@Security("keycloak", MANAGE_ROLES)
|
@Security("keycloak")
|
||||||
@OperationId("updateInstitution")
|
@OperationId("updateInstitution")
|
||||||
async updateInstitution(
|
async updateInstitution(
|
||||||
@Path() institutionId: string,
|
@Path() institutionId: string,
|
||||||
|
|
@ -289,7 +259,7 @@ export class InstitutionController extends Controller {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Delete("{institutionId}")
|
@Delete("{institutionId}")
|
||||||
@Security("keycloak", MANAGE_ROLES)
|
@Security("keycloak")
|
||||||
@OperationId("deleteInstitution")
|
@OperationId("deleteInstitution")
|
||||||
async deleteInstitution(@Path() institutionId: string) {
|
async deleteInstitution(@Path() institutionId: string) {
|
||||||
return await prisma.$transaction(async (tx) => {
|
return await prisma.$transaction(async (tx) => {
|
||||||
|
|
@ -361,7 +331,7 @@ export class InstitutionFileController extends Controller {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Put("image/{name}")
|
@Put("image/{name}")
|
||||||
@Security("keycloak", MANAGE_ROLES)
|
@Security("keycloak")
|
||||||
async putImage(
|
async putImage(
|
||||||
@Request() req: RequestWithUser,
|
@Request() req: RequestWithUser,
|
||||||
@Path() institutionId: string,
|
@Path() institutionId: string,
|
||||||
|
|
@ -375,7 +345,7 @@ export class InstitutionFileController extends Controller {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Delete("image/{name}")
|
@Delete("image/{name}")
|
||||||
@Security("keycloak", MANAGE_ROLES)
|
@Security("keycloak")
|
||||||
async delImage(
|
async delImage(
|
||||||
@Request() req: RequestWithUser,
|
@Request() req: RequestWithUser,
|
||||||
@Path() institutionId: string,
|
@Path() institutionId: string,
|
||||||
|
|
@ -405,7 +375,7 @@ export class InstitutionFileController extends Controller {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Put("attachment/{name}")
|
@Put("attachment/{name}")
|
||||||
@Security("keycloak", MANAGE_ROLES)
|
@Security("keycloak")
|
||||||
async putAttachment(
|
async putAttachment(
|
||||||
@Request() req: RequestWithUser,
|
@Request() req: RequestWithUser,
|
||||||
@Path() institutionId: string,
|
@Path() institutionId: string,
|
||||||
|
|
@ -416,7 +386,7 @@ export class InstitutionFileController extends Controller {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Delete("attachment/{name}")
|
@Delete("attachment/{name}")
|
||||||
@Security("keycloak", MANAGE_ROLES)
|
@Security("keycloak")
|
||||||
async delAttachment(
|
async delAttachment(
|
||||||
@Request() req: RequestWithUser,
|
@Request() req: RequestWithUser,
|
||||||
@Path() institutionId: string,
|
@Path() institutionId: string,
|
||||||
|
|
@ -447,7 +417,7 @@ export class InstitutionFileController extends Controller {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Put("bank-qr/{bankId}")
|
@Put("bank-qr/{bankId}")
|
||||||
@Security("keycloak", MANAGE_ROLES)
|
@Security("keycloak")
|
||||||
async putBankImage(
|
async putBankImage(
|
||||||
@Request() req: RequestWithUser,
|
@Request() req: RequestWithUser,
|
||||||
@Path() institutionId: string,
|
@Path() institutionId: string,
|
||||||
|
|
@ -461,7 +431,7 @@ export class InstitutionFileController extends Controller {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Delete("bank-qr/{bankId}")
|
@Delete("bank-qr/{bankId}")
|
||||||
@Security("keycloak", MANAGE_ROLES)
|
@Security("keycloak")
|
||||||
async delBankImage(
|
async delBankImage(
|
||||||
@Request() req: RequestWithUser,
|
@Request() req: RequestWithUser,
|
||||||
@Path() institutionId: string,
|
@Path() institutionId: string,
|
||||||
|
|
|
||||||
|
|
@ -21,7 +21,6 @@ import {
|
||||||
createPermCondition,
|
createPermCondition,
|
||||||
} from "../services/permission";
|
} from "../services/permission";
|
||||||
import { PaymentStatus } from "../generated/kysely/types";
|
import { PaymentStatus } from "../generated/kysely/types";
|
||||||
import { whereDateQuery } from "../utils/relation";
|
|
||||||
|
|
||||||
type InvoicePayload = {
|
type InvoicePayload = {
|
||||||
quotationId: string;
|
quotationId: string;
|
||||||
|
|
@ -29,23 +28,14 @@ type InvoicePayload = {
|
||||||
installmentNo: number[];
|
installmentNo: number[];
|
||||||
};
|
};
|
||||||
|
|
||||||
const MANAGE_ROLES = [
|
const MANAGE_ROLES = ["system", "head_of_admin", "admin", "head_of_accountant", "accountant"];
|
||||||
"system",
|
|
||||||
"head_of_admin",
|
|
||||||
"admin",
|
|
||||||
"executive",
|
|
||||||
"accountant",
|
|
||||||
"branch_admin",
|
|
||||||
"branch_manager",
|
|
||||||
"branch_accountant",
|
|
||||||
];
|
|
||||||
|
|
||||||
function globalAllow(user: RequestWithUser["user"]) {
|
function globalAllow(user: RequestWithUser["user"]) {
|
||||||
const listAllowed = ["system", "head_of_admin", "admin", "executive", "accountant"];
|
const allowList = ["system", "head_of_admin", "head_of_accountant"];
|
||||||
return user.roles?.some((v) => listAllowed.includes(v)) || false;
|
return allowList.some((v) => user.roles?.includes(v));
|
||||||
}
|
}
|
||||||
|
|
||||||
const permissionCondCompany = createPermCondition(globalAllow);
|
const permissionCondCompany = createPermCondition((_) => true);
|
||||||
const permissionCheck = createPermCheck(globalAllow);
|
const permissionCheck = createPermCheck(globalAllow);
|
||||||
|
|
||||||
@Route("/api/v1/invoice")
|
@Route("/api/v1/invoice")
|
||||||
|
|
@ -105,8 +95,6 @@ export class InvoiceController extends Controller {
|
||||||
@Query() quotationId?: string,
|
@Query() quotationId?: string,
|
||||||
@Query() debitNoteId?: string,
|
@Query() debitNoteId?: string,
|
||||||
@Query() pay?: boolean,
|
@Query() pay?: boolean,
|
||||||
@Query() startDate?: Date,
|
|
||||||
@Query() endDate?: Date,
|
|
||||||
) {
|
) {
|
||||||
const where: Prisma.InvoiceWhereInput = {
|
const where: Prisma.InvoiceWhereInput = {
|
||||||
OR: [
|
OR: [
|
||||||
|
|
@ -117,6 +105,7 @@ export class InvoiceController extends Controller {
|
||||||
customerBranch: {
|
customerBranch: {
|
||||||
OR: [
|
OR: [
|
||||||
{ code: { contains: query, mode: "insensitive" } },
|
{ code: { contains: query, mode: "insensitive" } },
|
||||||
|
{ customerName: { contains: query, mode: "insensitive" } },
|
||||||
{ registerName: { contains: query, mode: "insensitive" } },
|
{ registerName: { contains: query, mode: "insensitive" } },
|
||||||
{ registerNameEN: { contains: query, mode: "insensitive" } },
|
{ registerNameEN: { contains: query, mode: "insensitive" } },
|
||||||
{ firstName: { contains: query, mode: "insensitive" } },
|
{ firstName: { contains: query, mode: "insensitive" } },
|
||||||
|
|
@ -143,7 +132,6 @@ export class InvoiceController extends Controller {
|
||||||
OR: permissionCondCompany(req.user),
|
OR: permissionCondCompany(req.user),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
...whereDateQuery(startDate, endDate),
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const [result, total] = await prisma.$transaction([
|
const [result, total] = await prisma.$transaction([
|
||||||
|
|
@ -192,7 +180,7 @@ export class InvoiceController extends Controller {
|
||||||
|
|
||||||
@Post()
|
@Post()
|
||||||
@OperationId("createInvoice")
|
@OperationId("createInvoice")
|
||||||
@Security("keycloak", MANAGE_ROLES.concat(["head_of_sale", "sale"]))
|
@Security("keycloak", MANAGE_ROLES)
|
||||||
async createInvoice(@Request() req: RequestWithUser, @Body() body: InvoicePayload) {
|
async createInvoice(@Request() req: RequestWithUser, @Body() body: InvoicePayload) {
|
||||||
const [quotation] = await prisma.$transaction([
|
const [quotation] = await prisma.$transaction([
|
||||||
prisma.quotation.findUnique({
|
prisma.quotation.findUnique({
|
||||||
|
|
@ -237,7 +225,7 @@ export class InvoiceController extends Controller {
|
||||||
title: "ใบแจ้งหนี้ใหม่ / New Invoice",
|
title: "ใบแจ้งหนี้ใหม่ / New Invoice",
|
||||||
detail: "รหัส / code : " + record.code,
|
detail: "รหัส / code : " + record.code,
|
||||||
registeredBranchId: record.registeredBranchId,
|
registeredBranchId: record.registeredBranchId,
|
||||||
groupReceiver: { create: { name: "branch_accountant" } },
|
groupReceiver: { create: { name: "accountant" } },
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -11,7 +11,6 @@ import {
|
||||||
Security,
|
Security,
|
||||||
Tags,
|
Tags,
|
||||||
Query,
|
Query,
|
||||||
UploadedFile,
|
|
||||||
} from "tsoa";
|
} from "tsoa";
|
||||||
import { Prisma, Product, Status } from "@prisma/client";
|
import { Prisma, Product, Status } from "@prisma/client";
|
||||||
|
|
||||||
|
|
@ -28,25 +27,20 @@ import { isSystem } from "../utils/keycloak";
|
||||||
import { filterStatus } from "../services/prisma";
|
import { filterStatus } from "../services/prisma";
|
||||||
import { deleteFile, deleteFolder, fileLocation, getFile, listFile, setFile } from "../utils/minio";
|
import { deleteFile, deleteFolder, fileLocation, getFile, listFile, setFile } from "../utils/minio";
|
||||||
import { isUsedError, notFoundError, relationError } from "../utils/error";
|
import { isUsedError, notFoundError, relationError } from "../utils/error";
|
||||||
import { queryOrNot, whereDateQuery } from "../utils/relation";
|
import { queryOrNot } from "../utils/relation";
|
||||||
import spreadsheet from "../utils/spreadsheet";
|
|
||||||
import flowAccount from "../services/flowaccount";
|
|
||||||
import { json2csv } from "json-2-csv";
|
|
||||||
|
|
||||||
const MANAGE_ROLES = [
|
const MANAGE_ROLES = [
|
||||||
"system",
|
"system",
|
||||||
"head_of_admin",
|
"head_of_admin",
|
||||||
"admin",
|
"admin",
|
||||||
"executive",
|
"head_of_accountant",
|
||||||
"accountant",
|
"accountant",
|
||||||
"branch_admin",
|
"head_of_sale",
|
||||||
"branch_manager",
|
|
||||||
"branch_accountant",
|
|
||||||
];
|
];
|
||||||
|
|
||||||
function globalAllow(user: RequestWithUser["user"]) {
|
function globalAllow(user: RequestWithUser["user"]) {
|
||||||
const listAllowed = MANAGE_ROLES;
|
const allowList = ["system", "head_of_admin", "head_of_accountant", "head_of_sale"];
|
||||||
return user.roles?.some((v) => listAllowed.includes(v)) || false;
|
return allowList.some((v) => user.roles?.includes(v));
|
||||||
}
|
}
|
||||||
|
|
||||||
const permissionCondCompany = createPermCondition((_) => true);
|
const permissionCondCompany = createPermCondition((_) => true);
|
||||||
|
|
@ -78,7 +72,6 @@ type ProductCreate = {
|
||||||
|
|
||||||
type ProductUpdate = {
|
type ProductUpdate = {
|
||||||
status?: "ACTIVE" | "INACTIVE";
|
status?: "ACTIVE" | "INACTIVE";
|
||||||
code?: string;
|
|
||||||
name?: string;
|
name?: string;
|
||||||
detail?: string;
|
detail?: string;
|
||||||
process?: number;
|
process?: number;
|
||||||
|
|
@ -146,8 +139,6 @@ export class ProductController extends Controller {
|
||||||
@Query() orderField?: keyof Product,
|
@Query() orderField?: keyof Product,
|
||||||
@Query() orderBy?: "asc" | "desc",
|
@Query() orderBy?: "asc" | "desc",
|
||||||
@Query() activeOnly?: boolean,
|
@Query() activeOnly?: boolean,
|
||||||
@Query() startDate?: Date,
|
|
||||||
@Query() endDate?: Date,
|
|
||||||
) {
|
) {
|
||||||
// NOTE: will be used to scope product within product group that is shared between branch but not company when select shared product if user is system
|
// NOTE: will be used to scope product within product group that is shared between branch but not company when select shared product if user is system
|
||||||
const targetGroup =
|
const targetGroup =
|
||||||
|
|
@ -203,7 +194,6 @@ export class ProductController extends Controller {
|
||||||
: []),
|
: []),
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
...whereDateQuery(startDate, endDate),
|
|
||||||
} satisfies Prisma.ProductWhereInput;
|
} satisfies Prisma.ProductWhereInput;
|
||||||
|
|
||||||
const [result, total] = await prisma.$transaction([
|
const [result, total] = await prisma.$transaction([
|
||||||
|
|
@ -302,21 +292,13 @@ export class ProductController extends Controller {
|
||||||
},
|
},
|
||||||
update: { value: { increment: 1 } },
|
update: { value: { increment: 1 } },
|
||||||
});
|
});
|
||||||
|
return await prisma.product.create({
|
||||||
const listId = await flowAccount.createProducts(
|
|
||||||
`${body.code.toLocaleUpperCase()}${last.value.toString().padStart(3, "0")}`,
|
|
||||||
body,
|
|
||||||
);
|
|
||||||
|
|
||||||
return await tx.product.create({
|
|
||||||
include: {
|
include: {
|
||||||
createdBy: true,
|
createdBy: true,
|
||||||
updatedBy: true,
|
updatedBy: true,
|
||||||
},
|
},
|
||||||
data: {
|
data: {
|
||||||
...body,
|
...body,
|
||||||
flowAccountProductIdAgentPrice: `${listId.data.productIdAgentPrice}`,
|
|
||||||
flowAccountProductIdSellPrice: `${listId.data.productIdSellPrice}`,
|
|
||||||
document: body.document
|
document: body.document
|
||||||
? {
|
? {
|
||||||
createMany: { data: body.document.map((v) => ({ name: v })) },
|
createMany: { data: body.document.map((v) => ({ name: v })) },
|
||||||
|
|
@ -390,30 +372,6 @@ export class ProductController extends Controller {
|
||||||
await permissionCheck(req.user, productGroup.registeredBranch);
|
await permissionCheck(req.user, productGroup.registeredBranch);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (
|
|
||||||
product.flowAccountProductIdSellPrice !== null &&
|
|
||||||
product.flowAccountProductIdAgentPrice !== null
|
|
||||||
) {
|
|
||||||
const mergedBody = {
|
|
||||||
...body,
|
|
||||||
code: body.code ?? product.code,
|
|
||||||
price: body.price ?? product.price,
|
|
||||||
agentPrice: body.agentPrice ?? product.agentPrice,
|
|
||||||
serviceCharge: body.serviceCharge ?? product.serviceCharge,
|
|
||||||
vatIncluded: body.vatIncluded ?? product.vatIncluded,
|
|
||||||
agentPriceVatIncluded: body.agentPriceVatIncluded ?? product.agentPriceVatIncluded,
|
|
||||||
serviceChargeVatIncluded: body.serviceChargeVatIncluded ?? product.serviceChargeVatIncluded,
|
|
||||||
};
|
|
||||||
|
|
||||||
await flowAccount.editProducts(
|
|
||||||
product.flowAccountProductIdSellPrice,
|
|
||||||
product.flowAccountProductIdAgentPrice,
|
|
||||||
mergedBody,
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
throw notFoundError("FlowAccountProductId");
|
|
||||||
}
|
|
||||||
|
|
||||||
const record = await prisma.product.update({
|
const record = await prisma.product.update({
|
||||||
include: {
|
include: {
|
||||||
productGroup: true,
|
productGroup: true,
|
||||||
|
|
@ -476,18 +434,6 @@ export class ProductController extends Controller {
|
||||||
|
|
||||||
if (record.status !== Status.CREATED) throw isUsedError("Product");
|
if (record.status !== Status.CREATED) throw isUsedError("Product");
|
||||||
|
|
||||||
if (
|
|
||||||
record.flowAccountProductIdSellPrice !== null &&
|
|
||||||
record.flowAccountProductIdAgentPrice !== null
|
|
||||||
) {
|
|
||||||
await Promise.all([
|
|
||||||
flowAccount.deleteProduct(record.flowAccountProductIdSellPrice),
|
|
||||||
flowAccount.deleteProduct(record.flowAccountProductIdAgentPrice),
|
|
||||||
]);
|
|
||||||
} else {
|
|
||||||
throw notFoundError("FlowAccountProductId");
|
|
||||||
}
|
|
||||||
|
|
||||||
await deleteFolder(fileLocation.product.img(productId));
|
await deleteFolder(fileLocation.product.img(productId));
|
||||||
|
|
||||||
return await prisma.product.delete({
|
return await prisma.product.delete({
|
||||||
|
|
@ -498,146 +444,6 @@ export class ProductController extends Controller {
|
||||||
where: { id: productId },
|
where: { id: productId },
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@Post("import-product")
|
|
||||||
@Security("keycloak", MANAGE_ROLES)
|
|
||||||
async importProduct(
|
|
||||||
@Request() req: RequestWithUser,
|
|
||||||
@UploadedFile() file: Express.Multer.File,
|
|
||||||
@Query() productGroupId: string,
|
|
||||||
) {
|
|
||||||
if (!file?.buffer) throw notFoundError("File");
|
|
||||||
|
|
||||||
const buffer = new Uint8Array(file.buffer).buffer;
|
|
||||||
const dataFile = await spreadsheet.readExcel(buffer, {
|
|
||||||
header: true,
|
|
||||||
worksheet: "Sheet1",
|
|
||||||
});
|
|
||||||
|
|
||||||
let dataName: string[] = [];
|
|
||||||
const data = dataFile.map((item: any) => {
|
|
||||||
dataName.push(item.name);
|
|
||||||
return {
|
|
||||||
...item,
|
|
||||||
expenseType:
|
|
||||||
item.expenseType === "ค่าธรรมเนียม"
|
|
||||||
? "fee"
|
|
||||||
: item.expenseType === "ค่าบริการ"
|
|
||||||
? "serviceFee"
|
|
||||||
: "processingFee",
|
|
||||||
shared: item.shared === "ใช่" ? true : false,
|
|
||||||
price:
|
|
||||||
typeof item.price === "number"
|
|
||||||
? item.price
|
|
||||||
: +parseFloat(item.price?.replace(",", "") || "0").toFixed(6),
|
|
||||||
calcVat: item.calcVat === "ใช่" ? true : false,
|
|
||||||
vatIncluded: item.vatIncluded === "รวม" ? true : false,
|
|
||||||
agentPrice:
|
|
||||||
typeof item.agentPrice === "number"
|
|
||||||
? item.agentPrice
|
|
||||||
: +parseFloat(item.agentPrice?.replace(",", "") || "0").toFixed(6),
|
|
||||||
agentPriceCalcVat: item.agentPriceCalcVat === "ใช่" ? true : false,
|
|
||||||
agentPriceVatIncluded: item.agentPriceVatIncluded === "รวม" ? true : false,
|
|
||||||
serviceCharge:
|
|
||||||
typeof item.serviceCharge === "number"
|
|
||||||
? item.serviceCharge
|
|
||||||
: +parseFloat(item.serviceCharge?.replace(",", "") || "0").toFixed(6),
|
|
||||||
serviceChargeCalcVat: item.serviceChargeCalcVat === "ใช่" ? true : false,
|
|
||||||
serviceChargeVatIncluded: item.serviceChargeVatIncluded === "รวม" ? true : false,
|
|
||||||
};
|
|
||||||
});
|
|
||||||
|
|
||||||
const [productGroup, productSameName] = await prisma.$transaction([
|
|
||||||
prisma.productGroup.findFirst({
|
|
||||||
include: {
|
|
||||||
registeredBranch: {
|
|
||||||
include: branchRelationPermInclude(req.user),
|
|
||||||
},
|
|
||||||
createdBy: true,
|
|
||||||
updatedBy: true,
|
|
||||||
},
|
|
||||||
where: { id: productGroupId },
|
|
||||||
}),
|
|
||||||
prisma.product.findMany({
|
|
||||||
where: {
|
|
||||||
productGroup: {
|
|
||||||
id: productGroupId,
|
|
||||||
registeredBranch: {
|
|
||||||
OR: permissionCondCompany(req.user),
|
|
||||||
},
|
|
||||||
},
|
|
||||||
name: { in: dataName },
|
|
||||||
},
|
|
||||||
}),
|
|
||||||
]);
|
|
||||||
|
|
||||||
if (!productGroup) throw relationError("Product Group");
|
|
||||||
|
|
||||||
await permissionCheck(req.user, productGroup.registeredBranch);
|
|
||||||
let dataProduct: ProductCreate[] = [];
|
|
||||||
|
|
||||||
const record = await prisma.$transaction(
|
|
||||||
async (tx) => {
|
|
||||||
const branch = productGroup.registeredBranch;
|
|
||||||
const company = (branch.headOffice || branch).code;
|
|
||||||
|
|
||||||
await Promise.all(
|
|
||||||
data.map(async (item) => {
|
|
||||||
const dataDuplicate = productSameName.some(
|
|
||||||
(v) => v.code.slice(0, -3) === item.code.toUpperCase() && v.name === item.name,
|
|
||||||
);
|
|
||||||
|
|
||||||
if (!dataDuplicate) {
|
|
||||||
const last = await tx.runningNo.upsert({
|
|
||||||
where: {
|
|
||||||
key: `PRODUCT_${company}_${item.code.toLocaleUpperCase()}`,
|
|
||||||
},
|
|
||||||
create: {
|
|
||||||
key: `PRODUCT_${company}_${item.code.toLocaleUpperCase()}`,
|
|
||||||
value: 1,
|
|
||||||
},
|
|
||||||
update: { value: { increment: 1 } },
|
|
||||||
});
|
|
||||||
|
|
||||||
dataProduct.push({
|
|
||||||
...item,
|
|
||||||
code: `${item.code.toLocaleUpperCase()}${last.value.toString().padStart(3, "0")}`,
|
|
||||||
createdByUserId: req.user.sub,
|
|
||||||
updatedByUserId: req.user.sub,
|
|
||||||
productGroupId: productGroupId,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
|
|
||||||
return await prisma.product.createManyAndReturn({
|
|
||||||
data: dataProduct,
|
|
||||||
include: {
|
|
||||||
createdBy: true,
|
|
||||||
updatedBy: true,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
},
|
|
||||||
{
|
|
||||||
isolationLevel: Prisma.TransactionIsolationLevel.Serializable,
|
|
||||||
},
|
|
||||||
);
|
|
||||||
|
|
||||||
if (productGroup.status === "CREATED") {
|
|
||||||
await prisma.productGroup.update({
|
|
||||||
include: {
|
|
||||||
createdBy: true,
|
|
||||||
updatedBy: true,
|
|
||||||
},
|
|
||||||
where: { id: productGroupId },
|
|
||||||
data: { status: Status.ACTIVE },
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
this.setStatus(HttpStatus.CREATED);
|
|
||||||
|
|
||||||
return record;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Route("api/v1/product/{productId}")
|
@Route("api/v1/product/{productId}")
|
||||||
|
|
@ -689,43 +495,3 @@ export class ProductFileController extends Controller {
|
||||||
return await deleteFile(fileLocation.product.img(productId, name));
|
return await deleteFile(fileLocation.product.img(productId, name));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Route("api/v1/product-export")
|
|
||||||
@Tags("Product")
|
|
||||||
export class ProductExportController extends ProductController {
|
|
||||||
@Get()
|
|
||||||
@Security("keycloak")
|
|
||||||
async exportCustomer(
|
|
||||||
@Request() req: RequestWithUser,
|
|
||||||
@Query() status?: Status,
|
|
||||||
@Query() shared?: boolean,
|
|
||||||
@Query() productGroupId?: string,
|
|
||||||
@Query() query: string = "",
|
|
||||||
@Query() page: number = 1,
|
|
||||||
@Query() pageSize: number = 30,
|
|
||||||
@Query() orderField?: keyof Product,
|
|
||||||
@Query() orderBy?: "asc" | "desc",
|
|
||||||
@Query() activeOnly?: boolean,
|
|
||||||
@Query() startDate?: Date,
|
|
||||||
@Query() endDate?: Date,
|
|
||||||
) {
|
|
||||||
const ret = await this.getProduct(
|
|
||||||
req,
|
|
||||||
status,
|
|
||||||
shared,
|
|
||||||
productGroupId,
|
|
||||||
query,
|
|
||||||
page,
|
|
||||||
pageSize,
|
|
||||||
orderField,
|
|
||||||
orderBy,
|
|
||||||
activeOnly,
|
|
||||||
startDate,
|
|
||||||
endDate,
|
|
||||||
);
|
|
||||||
|
|
||||||
this.setHeader("Content-Type", "text/csv");
|
|
||||||
|
|
||||||
return json2csv(ret.result, { useDateIso8601Format: true, expandNestedObjects: true });
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
||||||
|
|
@ -27,7 +27,7 @@ import {
|
||||||
} from "../services/permission";
|
} from "../services/permission";
|
||||||
import { filterStatus } from "../services/prisma";
|
import { filterStatus } from "../services/prisma";
|
||||||
import { isUsedError, notFoundError, relationError } from "../utils/error";
|
import { isUsedError, notFoundError, relationError } from "../utils/error";
|
||||||
import { queryOrNot, whereDateQuery } from "../utils/relation";
|
import { queryOrNot } from "../utils/relation";
|
||||||
|
|
||||||
type ProductGroupCreate = {
|
type ProductGroupCreate = {
|
||||||
name: string;
|
name: string;
|
||||||
|
|
@ -35,7 +35,7 @@ type ProductGroupCreate = {
|
||||||
remark: string;
|
remark: string;
|
||||||
status?: Status;
|
status?: Status;
|
||||||
shared?: boolean;
|
shared?: boolean;
|
||||||
registeredBranchId?: string;
|
registeredBranchId: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
type ProductGroupUpdate = {
|
type ProductGroupUpdate = {
|
||||||
|
|
@ -51,16 +51,14 @@ const MANAGE_ROLES = [
|
||||||
"system",
|
"system",
|
||||||
"head_of_admin",
|
"head_of_admin",
|
||||||
"admin",
|
"admin",
|
||||||
"executive",
|
"head_of_accountant",
|
||||||
"accountant",
|
"accountant",
|
||||||
"branch_admin",
|
"head_of_sale",
|
||||||
"branch_manager",
|
|
||||||
"branch_accountant",
|
|
||||||
];
|
];
|
||||||
|
|
||||||
function globalAllow(user: RequestWithUser["user"]) {
|
function globalAllow(user: RequestWithUser["user"]) {
|
||||||
const listAllowed = MANAGE_ROLES;
|
const allowList = ["system", "head_of_admin", "head_of_accountant", "head_of_sale"];
|
||||||
return user.roles?.some((v) => listAllowed.includes(v)) || false;
|
return allowList.some((v) => user.roles?.includes(v));
|
||||||
}
|
}
|
||||||
|
|
||||||
const permissionCond = createPermCondition((_) => true);
|
const permissionCond = createPermCondition((_) => true);
|
||||||
|
|
@ -92,8 +90,6 @@ export class ProductGroup extends Controller {
|
||||||
@Query() page: number = 1,
|
@Query() page: number = 1,
|
||||||
@Query() pageSize: number = 30,
|
@Query() pageSize: number = 30,
|
||||||
@Query() activeOnly?: boolean,
|
@Query() activeOnly?: boolean,
|
||||||
@Query() startDate?: Date,
|
|
||||||
@Query() endDate?: Date,
|
|
||||||
) {
|
) {
|
||||||
const where = {
|
const where = {
|
||||||
OR: queryOrNot<Prisma.ProductGroupWhereInput[]>(query, [
|
OR: queryOrNot<Prisma.ProductGroupWhereInput[]>(query, [
|
||||||
|
|
@ -109,7 +105,6 @@ export class ProductGroup extends Controller {
|
||||||
: { OR: permissionCond(req.user, { activeOnly }) },
|
: { OR: permissionCond(req.user, { activeOnly }) },
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
...whereDateQuery(startDate, endDate),
|
|
||||||
} satisfies Prisma.ProductGroupWhereInput;
|
} satisfies Prisma.ProductGroupWhereInput;
|
||||||
|
|
||||||
const [result, total] = await prisma.$transaction([
|
const [result, total] = await prisma.$transaction([
|
||||||
|
|
@ -159,23 +154,7 @@ export class ProductGroup extends Controller {
|
||||||
@Post()
|
@Post()
|
||||||
@Security("keycloak", MANAGE_ROLES)
|
@Security("keycloak", MANAGE_ROLES)
|
||||||
async createProductGroup(@Request() req: RequestWithUser, @Body() body: ProductGroupCreate) {
|
async createProductGroup(@Request() req: RequestWithUser, @Body() body: ProductGroupCreate) {
|
||||||
const userAffiliatedBranch = await prisma.branch.findFirst({
|
let company = await permissionCheck(req.user, body.registeredBranchId).then(
|
||||||
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",
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
let company = await permissionCheck(req.user, userAffiliatedBranch).then(
|
|
||||||
(v) => (v.headOffice || v).code,
|
(v) => (v.headOffice || v).code,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
@ -199,7 +178,6 @@ export class ProductGroup extends Controller {
|
||||||
},
|
},
|
||||||
data: {
|
data: {
|
||||||
...body,
|
...body,
|
||||||
registeredBranchId: userAffiliatedBranch.id,
|
|
||||||
statusOrder: +(body.status === "INACTIVE"),
|
statusOrder: +(body.status === "INACTIVE"),
|
||||||
code: `G${last.value.toString().padStart(2, "0")}`,
|
code: `G${last.value.toString().padStart(2, "0")}`,
|
||||||
createdByUserId: req.user.sub,
|
createdByUserId: req.user.sub,
|
||||||
|
|
|
||||||
|
|
@ -24,7 +24,7 @@ import HttpError from "../interfaces/http-error";
|
||||||
import HttpStatus from "../interfaces/http-status";
|
import HttpStatus from "../interfaces/http-status";
|
||||||
import { notFoundError } from "../utils/error";
|
import { notFoundError } from "../utils/error";
|
||||||
import { filterStatus } from "../services/prisma";
|
import { filterStatus } from "../services/prisma";
|
||||||
import { queryOrNot, whereDateQuery } from "../utils/relation";
|
import { queryOrNot } from "../utils/relation";
|
||||||
|
|
||||||
type PropertyPayload = {
|
type PropertyPayload = {
|
||||||
name: string;
|
name: string;
|
||||||
|
|
@ -49,8 +49,6 @@ export class PropertiesController extends Controller {
|
||||||
@Query() status?: Status,
|
@Query() status?: Status,
|
||||||
@Query() query = "",
|
@Query() query = "",
|
||||||
@Query() activeOnly?: boolean,
|
@Query() activeOnly?: boolean,
|
||||||
@Query() startDate?: Date,
|
|
||||||
@Query() endDate?: Date,
|
|
||||||
) {
|
) {
|
||||||
const where = {
|
const where = {
|
||||||
OR: queryOrNot(query, [
|
OR: queryOrNot(query, [
|
||||||
|
|
@ -63,7 +61,6 @@ export class PropertiesController extends Controller {
|
||||||
OR: permissionCondCompany(req.user, { activeOnly: true }),
|
OR: permissionCondCompany(req.user, { activeOnly: true }),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
...whereDateQuery(startDate, endDate),
|
|
||||||
} satisfies Prisma.PropertyWhereInput;
|
} satisfies Prisma.PropertyWhereInput;
|
||||||
const [result, total] = await prisma.$transaction([
|
const [result, total] = await prisma.$transaction([
|
||||||
prisma.property.findMany({
|
prisma.property.findMany({
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,6 @@ import { Prisma } from "@prisma/client";
|
||||||
import { notFoundError } from "../utils/error";
|
import { notFoundError } from "../utils/error";
|
||||||
import { RequestWithUser } from "../interfaces/user";
|
import { RequestWithUser } from "../interfaces/user";
|
||||||
import { createPermCondition } from "../services/permission";
|
import { createPermCondition } from "../services/permission";
|
||||||
import { whereDateQuery } from "../utils/relation";
|
|
||||||
|
|
||||||
const permissionCondCompany = createPermCondition((_) => true);
|
const permissionCondCompany = createPermCondition((_) => true);
|
||||||
|
|
||||||
|
|
@ -22,8 +21,6 @@ export class ReceiptController extends Controller {
|
||||||
@Query() quotationId?: string,
|
@Query() quotationId?: string,
|
||||||
@Query() debitNoteId?: string,
|
@Query() debitNoteId?: string,
|
||||||
@Query() debitNoteOnly?: boolean,
|
@Query() debitNoteOnly?: boolean,
|
||||||
@Query() startDate?: Date,
|
|
||||||
@Query() endDate?: Date,
|
|
||||||
) {
|
) {
|
||||||
const where: Prisma.PaymentWhereInput = {
|
const where: Prisma.PaymentWhereInput = {
|
||||||
paymentStatus: "PaymentSuccess",
|
paymentStatus: "PaymentSuccess",
|
||||||
|
|
@ -36,7 +33,6 @@ export class ReceiptController extends Controller {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
...whereDateQuery(startDate, endDate),
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const [result, total] = await prisma.$transaction([
|
const [result, total] = await prisma.$transaction([
|
||||||
|
|
|
||||||
|
|
@ -36,22 +36,20 @@ import {
|
||||||
listFile,
|
listFile,
|
||||||
setFile,
|
setFile,
|
||||||
} from "../utils/minio";
|
} from "../utils/minio";
|
||||||
import { queryOrNot, whereDateQuery } from "../utils/relation";
|
import { queryOrNot } from "../utils/relation";
|
||||||
|
|
||||||
const MANAGE_ROLES = [
|
const MANAGE_ROLES = [
|
||||||
"system",
|
"system",
|
||||||
"head_of_admin",
|
"head_of_admin",
|
||||||
"admin",
|
"admin",
|
||||||
"executive",
|
"head_of_accountant",
|
||||||
"accountant",
|
"accountant",
|
||||||
"branch_admin",
|
"head_of_sale",
|
||||||
"branch_manager",
|
|
||||||
"branch_accountant",
|
|
||||||
];
|
];
|
||||||
|
|
||||||
function globalAllow(user: RequestWithUser["user"]) {
|
function globalAllow(user: RequestWithUser["user"]) {
|
||||||
const listAllowed = MANAGE_ROLES;
|
const allowList = ["system", "head_of_admin", "head_of_accountant", "head_of_sale"];
|
||||||
return user.roles?.some((v) => listAllowed.includes(v)) || false;
|
return allowList.some((v) => user.roles?.includes(v));
|
||||||
}
|
}
|
||||||
|
|
||||||
const permissionCondCompany = createPermCondition((_) => true);
|
const permissionCondCompany = createPermCondition((_) => true);
|
||||||
|
|
@ -166,8 +164,6 @@ export class ServiceController extends Controller {
|
||||||
@Query() fullDetail?: boolean,
|
@Query() fullDetail?: boolean,
|
||||||
@Query() activeOnly?: boolean,
|
@Query() activeOnly?: boolean,
|
||||||
@Query() shared?: boolean,
|
@Query() shared?: boolean,
|
||||||
@Query() startDate?: Date,
|
|
||||||
@Query() endDate?: Date,
|
|
||||||
) {
|
) {
|
||||||
// NOTE: will be used to scope product within product group that is shared between branch but not company when select shared product if user is system
|
// NOTE: will be used to scope product within product group that is shared between branch but not company when select shared product if user is system
|
||||||
const targetGroup =
|
const targetGroup =
|
||||||
|
|
@ -223,7 +219,6 @@ export class ServiceController extends Controller {
|
||||||
: []),
|
: []),
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
...whereDateQuery(startDate, endDate),
|
|
||||||
} satisfies Prisma.ServiceWhereInput;
|
} satisfies Prisma.ServiceWhereInput;
|
||||||
|
|
||||||
const [result, total] = await prisma.$transaction([
|
const [result, total] = await prisma.$transaction([
|
||||||
|
|
|
||||||
|
|
@ -18,7 +18,6 @@ import prisma from "../db";
|
||||||
import { RequestWithUser } from "../interfaces/user";
|
import { RequestWithUser } from "../interfaces/user";
|
||||||
import HttpStatus from "../interfaces/http-status";
|
import HttpStatus from "../interfaces/http-status";
|
||||||
import { isUsedError, notFoundError } from "../utils/error";
|
import { isUsedError, notFoundError } from "../utils/error";
|
||||||
import { whereDateQuery } from "../utils/relation";
|
|
||||||
|
|
||||||
type WorkCreate = {
|
type WorkCreate = {
|
||||||
order: number;
|
order: number;
|
||||||
|
|
@ -46,12 +45,9 @@ export class WorkController extends Controller {
|
||||||
@Query() query: string = "",
|
@Query() query: string = "",
|
||||||
@Query() page: number = 1,
|
@Query() page: number = 1,
|
||||||
@Query() pageSize: number = 30,
|
@Query() pageSize: number = 30,
|
||||||
@Query() startDate?: Date,
|
|
||||||
@Query() endDate?: Date,
|
|
||||||
) {
|
) {
|
||||||
const where = {
|
const where = {
|
||||||
OR: [{ name: { contains: query }, serviceId: baseOnly ? null : undefined }],
|
OR: [{ name: { contains: query }, serviceId: baseOnly ? null : undefined }],
|
||||||
...whereDateQuery(startDate, endDate),
|
|
||||||
} satisfies Prisma.WorkWhereInput;
|
} satisfies Prisma.WorkWhereInput;
|
||||||
|
|
||||||
const [result, total] = await prisma.$transaction([
|
const [result, total] = await prisma.$transaction([
|
||||||
|
|
|
||||||
|
|
@ -26,20 +26,11 @@ import flowAccount from "../services/flowaccount";
|
||||||
import HttpError from "../interfaces/http-error";
|
import HttpError from "../interfaces/http-error";
|
||||||
import HttpStatus from "../interfaces/http-status";
|
import HttpStatus from "../interfaces/http-status";
|
||||||
|
|
||||||
const MANAGE_ROLES = [
|
const MANAGE_ROLES = ["system", "head_of_admin", "admin", "head_of_accountant", "accountant"];
|
||||||
"system",
|
|
||||||
"head_of_admin",
|
|
||||||
"admin",
|
|
||||||
"executive",
|
|
||||||
"accountant",
|
|
||||||
"branch_admin",
|
|
||||||
"branch_manager",
|
|
||||||
"branch_accountant",
|
|
||||||
];
|
|
||||||
|
|
||||||
function globalAllow(user: RequestWithUser["user"]) {
|
function globalAllow(user: RequestWithUser["user"]) {
|
||||||
const listAllowed = ["system", "head_of_admin", "admin", "executive", "accountant"];
|
const allowList = ["system", "head_of_admin", "head_of_accountant"];
|
||||||
return user.roles?.some((v) => listAllowed.includes(v)) || false;
|
return allowList.some((v) => user.roles?.includes(v));
|
||||||
}
|
}
|
||||||
|
|
||||||
const permissionCondCompany = createPermCondition((_) => true);
|
const permissionCondCompany = createPermCondition((_) => true);
|
||||||
|
|
@ -110,19 +101,10 @@ export class QuotationPayment extends Controller {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Put("{paymentId}")
|
@Put("{paymentId}")
|
||||||
@Security("keycloak", MANAGE_ROLES.concat(["head_of_sale", "sale"]))
|
@Security("keycloak", MANAGE_ROLES)
|
||||||
async updatePayment(
|
async updatePayment(
|
||||||
@Path() paymentId: string,
|
@Path() paymentId: string,
|
||||||
@Body()
|
@Body() body: { amount?: number; date?: Date; paymentStatus?: PaymentStatus },
|
||||||
body: {
|
|
||||||
amount?: number;
|
|
||||||
date?: Date;
|
|
||||||
paymentStatus?: PaymentStatus;
|
|
||||||
channel?: string | null;
|
|
||||||
account?: string | null;
|
|
||||||
reference?: string | null;
|
|
||||||
},
|
|
||||||
@Request() req: RequestWithUser,
|
|
||||||
) {
|
) {
|
||||||
const record = await prisma.payment.findUnique({
|
const record = await prisma.payment.findUnique({
|
||||||
where: { id: paymentId },
|
where: { id: paymentId },
|
||||||
|
|
@ -152,18 +134,7 @@ export class QuotationPayment extends Controller {
|
||||||
|
|
||||||
if (!record) throw notFoundError("Payment");
|
if (!record) throw notFoundError("Payment");
|
||||||
|
|
||||||
if (record.paymentStatus === "PaymentSuccess") {
|
if (record.paymentStatus === "PaymentSuccess") return record;
|
||||||
const { channel, account, reference } = body;
|
|
||||||
return await prisma.payment.update({
|
|
||||||
where: { id: paymentId, invoice: { quotationId: record.invoice.quotationId } },
|
|
||||||
data: {
|
|
||||||
channel,
|
|
||||||
account,
|
|
||||||
reference,
|
|
||||||
updatedByUserId: req.user.sub,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
return await prisma.$transaction(async (tx) => {
|
return await prisma.$transaction(async (tx) => {
|
||||||
const current = new Date();
|
const current = new Date();
|
||||||
|
|
@ -193,7 +164,6 @@ export class QuotationPayment extends Controller {
|
||||||
code: lastReceipt
|
code: lastReceipt
|
||||||
? `RE${year}${month}${lastReceipt.value.toString().padStart(6, "0")}`
|
? `RE${year}${month}${lastReceipt.value.toString().padStart(6, "0")}`
|
||||||
: undefined,
|
: undefined,
|
||||||
updatedByUserId: req.user.sub,
|
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
@ -209,7 +179,6 @@ export class QuotationPayment extends Controller {
|
||||||
|
|
||||||
await tx.quotation
|
await tx.quotation
|
||||||
.update({
|
.update({
|
||||||
include: { requestData: true },
|
|
||||||
where: { id: quotation.id },
|
where: { id: quotation.id },
|
||||||
data: {
|
data: {
|
||||||
quotationStatus:
|
quotationStatus:
|
||||||
|
|
@ -267,17 +236,6 @@ export class QuotationPayment extends Controller {
|
||||||
receiverId: res.createdByUserId,
|
receiverId: res.createdByUserId,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
if (quotation.quotationStatus === "PaymentInProcess") {
|
|
||||||
await prisma.notification.create({
|
|
||||||
data: {
|
|
||||||
title: "รายการคำขอใหม่ / New Request",
|
|
||||||
detail: "รหัส / code : " + res.requestData.map((v) => v.code).join(", "),
|
|
||||||
registeredBranchId: res.registeredBranchId,
|
|
||||||
groupReceiver: { create: { name: "document_checker" } },
|
|
||||||
},
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
|
|
||||||
return payment;
|
return payment;
|
||||||
|
|
|
||||||
|
|
@ -25,7 +25,7 @@ import {
|
||||||
import { isSystem } from "../utils/keycloak";
|
import { isSystem } from "../utils/keycloak";
|
||||||
import { isUsedError, notFoundError, relationError } from "../utils/error";
|
import { isUsedError, notFoundError, relationError } from "../utils/error";
|
||||||
import { precisionRound } from "../utils/arithmetic";
|
import { precisionRound } from "../utils/arithmetic";
|
||||||
import { queryOrNot, whereDateQuery } from "../utils/relation";
|
import { queryOrNot } from "../utils/relation";
|
||||||
import { deleteFile, fileLocation, getFile, getPresigned, listFile, setFile } from "../utils/minio";
|
import { deleteFile, fileLocation, getFile, getPresigned, listFile, setFile } from "../utils/minio";
|
||||||
import HttpError from "../interfaces/http-error";
|
import HttpError from "../interfaces/http-error";
|
||||||
import HttpStatus from "../interfaces/http-status";
|
import HttpStatus from "../interfaces/http-status";
|
||||||
|
|
@ -55,14 +55,13 @@ type QuotationCreate = {
|
||||||
dateOfBirth: Date;
|
dateOfBirth: Date;
|
||||||
gender: string;
|
gender: string;
|
||||||
nationality: string;
|
nationality: string;
|
||||||
otherNationality?: string | null;
|
|
||||||
namePrefix?: string;
|
namePrefix?: string;
|
||||||
firstName: string;
|
firstName: string;
|
||||||
firstNameEN: string;
|
firstNameEN: string;
|
||||||
middleName?: string;
|
middleName?: string;
|
||||||
middleNameEN?: string;
|
middleNameEN?: string;
|
||||||
lastName: string;
|
lastName: string;
|
||||||
lastNameEN?: string;
|
lastNameEN: string;
|
||||||
}
|
}
|
||||||
)[];
|
)[];
|
||||||
|
|
||||||
|
|
@ -84,8 +83,6 @@ type QuotationCreate = {
|
||||||
installmentNo?: number;
|
installmentNo?: number;
|
||||||
workerIndex?: number[];
|
workerIndex?: number[];
|
||||||
}[];
|
}[];
|
||||||
|
|
||||||
sellerId?: string;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
type QuotationUpdate = {
|
type QuotationUpdate = {
|
||||||
|
|
@ -115,15 +112,14 @@ type QuotationUpdate = {
|
||||||
dateOfBirth: Date;
|
dateOfBirth: Date;
|
||||||
gender: string;
|
gender: string;
|
||||||
nationality: string;
|
nationality: string;
|
||||||
otherNationality?: string | null;
|
|
||||||
|
|
||||||
namePrefix?: string;
|
namePrefix?: string;
|
||||||
firstName?: string;
|
firstName: string;
|
||||||
firstNameEN: string;
|
firstNameEN: string;
|
||||||
middleName?: string;
|
middleName?: string;
|
||||||
middleNameEN?: string;
|
middleNameEN?: string;
|
||||||
lastName?: string;
|
lastName: string;
|
||||||
lastNameEN?: string;
|
lastNameEN: string;
|
||||||
}
|
}
|
||||||
)[];
|
)[];
|
||||||
|
|
||||||
|
|
@ -144,8 +140,6 @@ type QuotationUpdate = {
|
||||||
installmentNo?: number;
|
installmentNo?: number;
|
||||||
workerIndex?: number[];
|
workerIndex?: number[];
|
||||||
}[];
|
}[];
|
||||||
|
|
||||||
sellerId?: string;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const VAT_DEFAULT = config.vat;
|
const VAT_DEFAULT = config.vat;
|
||||||
|
|
@ -154,16 +148,15 @@ const MANAGE_ROLES = [
|
||||||
"system",
|
"system",
|
||||||
"head_of_admin",
|
"head_of_admin",
|
||||||
"admin",
|
"admin",
|
||||||
"executive",
|
"head_of_accountant",
|
||||||
"accountant",
|
"accountant",
|
||||||
"branch_admin",
|
"head_of_sale",
|
||||||
"branch_manager",
|
"sale",
|
||||||
"branch_accountant",
|
|
||||||
];
|
];
|
||||||
|
|
||||||
function globalAllow(user: RequestWithUser["user"]) {
|
function globalAllow(user: RequestWithUser["user"]) {
|
||||||
const listAllowed = ["system", "head_of_admin", "admin", "executive", "accountant"];
|
const allowList = ["system", "head_of_admin", "head_of_accountant", "head_of_sale"];
|
||||||
return user.roles?.some((v) => listAllowed.includes(v)) || false;
|
return allowList.some((v) => user.roles?.includes(v));
|
||||||
}
|
}
|
||||||
|
|
||||||
const permissionCheckCompany = createPermCheck((_) => true);
|
const permissionCheckCompany = createPermCheck((_) => true);
|
||||||
|
|
@ -213,9 +206,6 @@ export class QuotationController extends Controller {
|
||||||
@Query() forDebitNote?: boolean,
|
@Query() forDebitNote?: boolean,
|
||||||
@Query() code?: string,
|
@Query() code?: string,
|
||||||
@Query() query = "",
|
@Query() query = "",
|
||||||
@Query() startDate?: Date,
|
|
||||||
@Query() endDate?: Date,
|
|
||||||
@Query() sellerId?: string,
|
|
||||||
) {
|
) {
|
||||||
const where = {
|
const where = {
|
||||||
OR: queryOrNot<Prisma.QuotationWhereInput[]>(query, [
|
OR: queryOrNot<Prisma.QuotationWhereInput[]>(query, [
|
||||||
|
|
@ -225,6 +215,7 @@ export class QuotationController extends Controller {
|
||||||
customerBranch: {
|
customerBranch: {
|
||||||
OR: [
|
OR: [
|
||||||
{ code: { contains: query, mode: "insensitive" } },
|
{ code: { contains: query, mode: "insensitive" } },
|
||||||
|
{ customerName: { contains: query, mode: "insensitive" } },
|
||||||
{ firstName: { contains: query, mode: "insensitive" } },
|
{ firstName: { contains: query, mode: "insensitive" } },
|
||||||
{ firstNameEN: { contains: query, mode: "insensitive" } },
|
{ firstNameEN: { contains: query, mode: "insensitive" } },
|
||||||
{ lastName: { contains: query, mode: "insensitive" } },
|
{ lastName: { contains: query, mode: "insensitive" } },
|
||||||
|
|
@ -262,8 +253,6 @@ export class QuotationController extends Controller {
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
: undefined,
|
: undefined,
|
||||||
...whereDateQuery(startDate, endDate),
|
|
||||||
sellerId: sellerId,
|
|
||||||
} satisfies Prisma.QuotationWhereInput;
|
} satisfies Prisma.QuotationWhereInput;
|
||||||
|
|
||||||
const [result, total] = await prisma.$transaction([
|
const [result, total] = await prisma.$transaction([
|
||||||
|
|
@ -421,7 +410,7 @@ export class QuotationController extends Controller {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Post()
|
@Post()
|
||||||
@Security("keycloak", MANAGE_ROLES.concat(["head_of_sale", "sale"]))
|
@Security("keycloak", MANAGE_ROLES)
|
||||||
async createQuotation(@Request() req: RequestWithUser, @Body() body: QuotationCreate) {
|
async createQuotation(@Request() req: RequestWithUser, @Body() body: QuotationCreate) {
|
||||||
const ids = {
|
const ids = {
|
||||||
employee: body.worker.filter((v) => typeof v === "string"),
|
employee: body.worker.filter((v) => typeof v === "string"),
|
||||||
|
|
@ -527,15 +516,16 @@ export class QuotationController extends Controller {
|
||||||
const vatIncluded = body.agentPrice ? p.agentPriceVatIncluded : p.vatIncluded;
|
const vatIncluded = body.agentPrice ? p.agentPriceVatIncluded : p.vatIncluded;
|
||||||
|
|
||||||
const originalPrice = body.agentPrice ? p.agentPrice : p.price;
|
const originalPrice = body.agentPrice ? p.agentPrice : p.price;
|
||||||
const finalPrice = precisionRound(
|
const finalPriceWithVat = precisionRound(
|
||||||
originalPrice + (vatIncluded ? 0 : originalPrice * VAT_DEFAULT),
|
originalPrice + (vatIncluded ? 0 : originalPrice * VAT_DEFAULT),
|
||||||
);
|
);
|
||||||
const pricePerUnit = finalPrice / (1 + VAT_DEFAULT);
|
|
||||||
|
const price = finalPriceWithVat;
|
||||||
|
const pricePerUnit = price / (1 + VAT_DEFAULT);
|
||||||
const vat = (body.agentPrice ? p.agentPriceCalcVat : p.calcVat)
|
const vat = (body.agentPrice ? p.agentPriceCalcVat : p.calcVat)
|
||||||
? ((pricePerUnit * (1 + VAT_DEFAULT) * v.amount - (v.discount || 0)) /
|
? (pricePerUnit * v.amount - (v.discount || 0)) * VAT_DEFAULT
|
||||||
(1 + VAT_DEFAULT)) *
|
|
||||||
VAT_DEFAULT
|
|
||||||
: 0;
|
: 0;
|
||||||
|
|
||||||
return {
|
return {
|
||||||
order: i + 1,
|
order: i + 1,
|
||||||
productId: v.productId,
|
productId: v.productId,
|
||||||
|
|
@ -556,13 +546,13 @@ export class QuotationController extends Controller {
|
||||||
|
|
||||||
const price = list.reduce(
|
const price = list.reduce(
|
||||||
(a, c) => {
|
(a, c) => {
|
||||||
const vat = c.vat ? VAT_DEFAULT : 0;
|
a.totalPrice = precisionRound(a.totalPrice + c.pricePerUnit * c.amount);
|
||||||
const price = c.pricePerUnit * c.amount * (1 + vat) - c.discount;
|
|
||||||
|
|
||||||
a.totalPrice = precisionRound(a.totalPrice + price / (1 + vat) + c.discount);
|
|
||||||
a.totalDiscount = precisionRound(a.totalDiscount + c.discount);
|
a.totalDiscount = precisionRound(a.totalDiscount + c.discount);
|
||||||
a.vat = precisionRound(a.vat + c.vat);
|
a.vat = precisionRound(a.vat + c.vat);
|
||||||
a.vatExcluded = c.vat === 0 ? precisionRound(a.vatExcluded + price) : a.vatExcluded;
|
a.vatExcluded =
|
||||||
|
c.vat === 0
|
||||||
|
? precisionRound(a.vatExcluded + (c.pricePerUnit * c.amount - (c.discount || 0)))
|
||||||
|
: a.vatExcluded;
|
||||||
a.finalPrice = precisionRound(
|
a.finalPrice = precisionRound(
|
||||||
Math.max(a.totalPrice - a.totalDiscount + a.vat - (body.discount || 0), 0),
|
Math.max(a.totalPrice - a.totalDiscount + a.vat - (body.discount || 0), 0),
|
||||||
);
|
);
|
||||||
|
|
@ -663,14 +653,7 @@ export class QuotationController extends Controller {
|
||||||
title: "ใบเสนอราคาใหม่ / New Quotation",
|
title: "ใบเสนอราคาใหม่ / New Quotation",
|
||||||
detail: "รหัส / code : " + ret.code,
|
detail: "รหัส / code : " + ret.code,
|
||||||
registeredBranchId: ret.registeredBranchId,
|
registeredBranchId: ret.registeredBranchId,
|
||||||
groupReceiver: {
|
groupReceiver: { create: [{ name: "sale" }, { name: "head_of_sale" }] },
|
||||||
create: [
|
|
||||||
{ name: "sale" },
|
|
||||||
{ name: "head_of_sale" },
|
|
||||||
{ name: "accountant" },
|
|
||||||
{ name: "branch_accountant" },
|
|
||||||
],
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
@ -678,7 +661,7 @@ export class QuotationController extends Controller {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Put("{quotationId}")
|
@Put("{quotationId}")
|
||||||
@Security("keycloak", MANAGE_ROLES.concat(["head_of_sale", "sale"]))
|
@Security("keycloak", MANAGE_ROLES)
|
||||||
async editQuotation(
|
async editQuotation(
|
||||||
@Request() req: RequestWithUser,
|
@Request() req: RequestWithUser,
|
||||||
@Path() quotationId: string,
|
@Path() quotationId: string,
|
||||||
|
|
@ -814,14 +797,14 @@ export class QuotationController extends Controller {
|
||||||
const vatIncluded = record.agentPrice ? p.agentPriceVatIncluded : p.vatIncluded;
|
const vatIncluded = record.agentPrice ? p.agentPriceVatIncluded : p.vatIncluded;
|
||||||
|
|
||||||
const originalPrice = record.agentPrice ? p.agentPrice : p.price;
|
const originalPrice = record.agentPrice ? p.agentPrice : p.price;
|
||||||
const finalPrice = precisionRound(
|
const finalPriceWithVat = precisionRound(
|
||||||
originalPrice + (vatIncluded ? 0 : originalPrice * VAT_DEFAULT),
|
originalPrice + (vatIncluded ? 0 : originalPrice * VAT_DEFAULT),
|
||||||
);
|
);
|
||||||
const pricePerUnit = finalPrice / (1 + VAT_DEFAULT);
|
|
||||||
|
const price = finalPriceWithVat;
|
||||||
|
const pricePerUnit = price / (1 + VAT_DEFAULT);
|
||||||
const vat = (record.agentPrice ? p.agentPriceCalcVat : p.calcVat)
|
const vat = (record.agentPrice ? p.agentPriceCalcVat : p.calcVat)
|
||||||
? ((pricePerUnit * (1 + VAT_DEFAULT) * v.amount - (v.discount || 0)) /
|
? (pricePerUnit * v.amount - (v.discount || 0)) * VAT_DEFAULT
|
||||||
(1 + VAT_DEFAULT)) *
|
|
||||||
VAT_DEFAULT
|
|
||||||
: 0;
|
: 0;
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
|
@ -844,13 +827,15 @@ export class QuotationController extends Controller {
|
||||||
|
|
||||||
const price = list?.reduce(
|
const price = list?.reduce(
|
||||||
(a, c) => {
|
(a, c) => {
|
||||||
const vat = c.vat ? VAT_DEFAULT : 0;
|
a.totalPrice = precisionRound(a.totalPrice + c.pricePerUnit * c.amount);
|
||||||
const price = c.pricePerUnit * c.amount * (1 + vat) - c.discount;
|
|
||||||
|
|
||||||
a.totalPrice = precisionRound(a.totalPrice + price / (1 + vat) + c.discount);
|
|
||||||
a.totalDiscount = precisionRound(a.totalDiscount + c.discount);
|
a.totalDiscount = precisionRound(a.totalDiscount + c.discount);
|
||||||
a.vat = precisionRound(a.vat + c.vat);
|
a.vat = precisionRound(a.vat + c.vat);
|
||||||
a.vatExcluded = c.vat === 0 ? precisionRound(a.vatExcluded + price) : a.vatExcluded;
|
a.vatExcluded =
|
||||||
|
c.vat === 0
|
||||||
|
? precisionRound(
|
||||||
|
a.vatExcluded + (c.pricePerUnit * c.amount - (c.discount || 0)) * VAT_DEFAULT,
|
||||||
|
)
|
||||||
|
: a.vatExcluded;
|
||||||
a.finalPrice = precisionRound(
|
a.finalPrice = precisionRound(
|
||||||
Math.max(a.totalPrice - a.totalDiscount + a.vat - (body.discount || 0), 0),
|
Math.max(a.totalPrice - a.totalDiscount + a.vat - (body.discount || 0), 0),
|
||||||
);
|
);
|
||||||
|
|
@ -866,7 +851,6 @@ export class QuotationController extends Controller {
|
||||||
finalPrice: 0,
|
finalPrice: 0,
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
const changed = list?.some((lhs) => {
|
const changed = list?.some((lhs) => {
|
||||||
const found = record.productServiceList.find((rhs) => {
|
const found = record.productServiceList.find((rhs) => {
|
||||||
return (
|
return (
|
||||||
|
|
@ -900,20 +884,6 @@ export class QuotationController extends Controller {
|
||||||
}),
|
}),
|
||||||
]);
|
]);
|
||||||
|
|
||||||
if (customerBranch) {
|
|
||||||
await tx.customerBranch.update({
|
|
||||||
where: { id: customerBranch.id },
|
|
||||||
data: {
|
|
||||||
customer: {
|
|
||||||
update: {
|
|
||||||
status: Status.ACTIVE,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
status: Status.ACTIVE,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
return await tx.quotation.update({
|
return await tx.quotation.update({
|
||||||
include: {
|
include: {
|
||||||
productServiceList: {
|
productServiceList: {
|
||||||
|
|
@ -1035,7 +1005,6 @@ export class QuotationActionController extends Controller {
|
||||||
dateOfBirth: Date;
|
dateOfBirth: Date;
|
||||||
gender: string;
|
gender: string;
|
||||||
nationality: string;
|
nationality: string;
|
||||||
otherNationality?: string | null;
|
|
||||||
namePrefix?: string;
|
namePrefix?: string;
|
||||||
firstName: string;
|
firstName: string;
|
||||||
firstNameEN: string;
|
firstNameEN: string;
|
||||||
|
|
@ -1058,7 +1027,6 @@ export class QuotationActionController extends Controller {
|
||||||
dateOfBirth: Date;
|
dateOfBirth: Date;
|
||||||
gender: string;
|
gender: string;
|
||||||
nationality: string;
|
nationality: string;
|
||||||
otherNationality?: string | null;
|
|
||||||
namePrefix?: string;
|
namePrefix?: string;
|
||||||
firstName: string;
|
firstName: string;
|
||||||
firstNameEN: string;
|
firstNameEN: string;
|
||||||
|
|
|
||||||
|
|
@ -27,12 +27,11 @@ import {
|
||||||
createPermCheck,
|
createPermCheck,
|
||||||
createPermCondition,
|
createPermCondition,
|
||||||
} from "../services/permission";
|
} from "../services/permission";
|
||||||
import { queryOrNot, whereDateQuery } from "../utils/relation";
|
import { queryOrNot } from "../utils/relation";
|
||||||
import { notFoundError } from "../utils/error";
|
import { notFoundError } from "../utils/error";
|
||||||
import { deleteFile, fileLocation, getFile, getPresigned, listFile, setFile } from "../utils/minio";
|
import { deleteFile, fileLocation, getFile, getPresigned, listFile, setFile } from "../utils/minio";
|
||||||
import HttpError from "../interfaces/http-error";
|
import HttpError from "../interfaces/http-error";
|
||||||
import HttpStatus from "../interfaces/http-status";
|
import HttpStatus from "../interfaces/http-status";
|
||||||
import { getGroupUser } from "../services/keycloak";
|
|
||||||
|
|
||||||
// User in company can edit.
|
// User in company can edit.
|
||||||
const permissionCheck = createPermCheck((_) => true);
|
const permissionCheck = createPermCheck((_) => true);
|
||||||
|
|
@ -81,9 +80,6 @@ export class RequestDataController extends Controller {
|
||||||
@Query() requestDataStatus?: RequestDataStatus,
|
@Query() requestDataStatus?: RequestDataStatus,
|
||||||
@Query() quotationId?: string,
|
@Query() quotationId?: string,
|
||||||
@Query() code?: string,
|
@Query() code?: string,
|
||||||
@Query() incomplete?: boolean,
|
|
||||||
@Query() startDate?: Date,
|
|
||||||
@Query() endDate?: Date,
|
|
||||||
) {
|
) {
|
||||||
const where = {
|
const where = {
|
||||||
OR: queryOrNot<Prisma.RequestDataWhereInput[]>(query, [
|
OR: queryOrNot<Prisma.RequestDataWhereInput[]>(query, [
|
||||||
|
|
@ -95,6 +91,7 @@ export class RequestDataController extends Controller {
|
||||||
customerBranch: {
|
customerBranch: {
|
||||||
OR: [
|
OR: [
|
||||||
{ code: { contains: query, mode: "insensitive" } },
|
{ code: { contains: query, mode: "insensitive" } },
|
||||||
|
{ customerName: { contains: query, mode: "insensitive" } },
|
||||||
{ registerName: { contains: query, mode: "insensitive" } },
|
{ registerName: { contains: query, mode: "insensitive" } },
|
||||||
{ registerNameEN: { contains: query, mode: "insensitive" } },
|
{ registerNameEN: { contains: query, mode: "insensitive" } },
|
||||||
{ firstName: { contains: query, mode: "insensitive" } },
|
{ firstName: { contains: query, mode: "insensitive" } },
|
||||||
|
|
@ -104,8 +101,6 @@ export class RequestDataController extends Controller {
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
|
||||||
{
|
|
||||||
employee: {
|
employee: {
|
||||||
OR: [
|
OR: [
|
||||||
{
|
{
|
||||||
|
|
@ -123,11 +118,7 @@ export class RequestDataController extends Controller {
|
||||||
},
|
},
|
||||||
]),
|
]),
|
||||||
code,
|
code,
|
||||||
requestDataStatus: incomplete
|
requestDataStatus,
|
||||||
? {
|
|
||||||
notIn: [RequestDataStatus.Completed, RequestDataStatus.Canceled],
|
|
||||||
}
|
|
||||||
: requestDataStatus,
|
|
||||||
requestWork: responsibleOnly
|
requestWork: responsibleOnly
|
||||||
? {
|
? {
|
||||||
some: {
|
some: {
|
||||||
|
|
@ -136,24 +127,9 @@ export class RequestDataController extends Controller {
|
||||||
workflow: {
|
workflow: {
|
||||||
step: {
|
step: {
|
||||||
some: {
|
some: {
|
||||||
OR: [
|
responsiblePerson: {
|
||||||
{
|
some: { userId: req.user.sub },
|
||||||
responsiblePerson: {
|
},
|
||||||
some: { userId: req.user.sub },
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
responsibleGroup: {
|
|
||||||
some: {
|
|
||||||
group: {
|
|
||||||
in: await getGroupUser(req.user.sub).then((r) =>
|
|
||||||
r.map(({ name }: { name: string }) => name),
|
|
||||||
),
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
@ -166,7 +142,6 @@ export class RequestDataController extends Controller {
|
||||||
id: quotationId,
|
id: quotationId,
|
||||||
registeredBranch: { OR: permissionCond(req.user) },
|
registeredBranch: { OR: permissionCond(req.user) },
|
||||||
},
|
},
|
||||||
...whereDateQuery(startDate, endDate),
|
|
||||||
} satisfies Prisma.RequestDataWhereInput;
|
} satisfies Prisma.RequestDataWhereInput;
|
||||||
|
|
||||||
const [result, total] = await prisma.$transaction([
|
const [result, total] = await prisma.$transaction([
|
||||||
|
|
@ -189,7 +164,6 @@ export class RequestDataController extends Controller {
|
||||||
include: { user: true },
|
include: { user: true },
|
||||||
},
|
},
|
||||||
responsibleInstitution: true,
|
responsibleInstitution: true,
|
||||||
responsibleGroup: true,
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
@ -208,20 +182,6 @@ export class RequestDataController extends Controller {
|
||||||
employeePassport: {
|
employeePassport: {
|
||||||
orderBy: { expireDate: "desc" },
|
orderBy: { expireDate: "desc" },
|
||||||
},
|
},
|
||||||
customerBranch: {
|
|
||||||
include: {
|
|
||||||
province: {
|
|
||||||
include: {
|
|
||||||
employmentOffice: true,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
district: {
|
|
||||||
include: {
|
|
||||||
employmentOffice: true,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
@ -232,24 +192,7 @@ export class RequestDataController extends Controller {
|
||||||
prisma.requestData.count({ where }),
|
prisma.requestData.count({ where }),
|
||||||
]);
|
]);
|
||||||
|
|
||||||
const dataRequestData = result.map((item) => {
|
return { result, page, pageSize, total };
|
||||||
const employee = item.employee;
|
|
||||||
const dataOffice =
|
|
||||||
employee.customerBranch.district?.employmentOffice.at(0) ??
|
|
||||||
employee.customerBranch.province?.employmentOffice.at(0);
|
|
||||||
|
|
||||||
return {
|
|
||||||
...item,
|
|
||||||
dataOffice,
|
|
||||||
};
|
|
||||||
});
|
|
||||||
|
|
||||||
return {
|
|
||||||
result: dataRequestData,
|
|
||||||
page,
|
|
||||||
pageSize,
|
|
||||||
total,
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Get("{requestDataId}")
|
@Get("{requestDataId}")
|
||||||
|
|
@ -288,67 +231,39 @@ export class RequestDataController extends Controller {
|
||||||
return record;
|
return record;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Post("update-messenger")
|
@Post("updata-messenger")
|
||||||
@Security("keycloak")
|
@Security("keycloak")
|
||||||
async updateRequestData(
|
async updateRequestData(
|
||||||
@Request() req: RequestWithUser,
|
@Request() req: RequestWithUser,
|
||||||
@Body()
|
@Body()
|
||||||
body: {
|
boby: {
|
||||||
defaultMessengerId: string;
|
defaultMessengerId: string;
|
||||||
requestDataId: string[];
|
requestDataId: string[];
|
||||||
},
|
},
|
||||||
) {
|
) {
|
||||||
if (body.requestDataId.length === 0) return;
|
const record = await prisma.requestData.updateManyAndReturn({
|
||||||
|
where: {
|
||||||
return await prisma.$transaction(async (tx) => {
|
id: { in: boby.requestDataId },
|
||||||
const record = await tx.requestData.updateManyAndReturn({
|
quotation: {
|
||||||
where: {
|
registeredBranch: {
|
||||||
id: { in: body.requestDataId },
|
OR: permissionCond(req.user),
|
||||||
quotation: {
|
|
||||||
registeredBranch: {
|
|
||||||
OR: permissionCond(req.user),
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
data: {
|
},
|
||||||
defaultMessengerId: body.defaultMessengerId,
|
data: {
|
||||||
},
|
defaultMessengerId: boby.defaultMessengerId,
|
||||||
});
|
},
|
||||||
|
|
||||||
if (record.length <= 0) throw notFoundError("Request Data");
|
|
||||||
|
|
||||||
await tx.requestWorkStepStatus.updateMany({
|
|
||||||
where: {
|
|
||||||
workStatus: {
|
|
||||||
in: [
|
|
||||||
RequestWorkStatus.Pending,
|
|
||||||
RequestWorkStatus.Waiting,
|
|
||||||
RequestWorkStatus.InProgress,
|
|
||||||
],
|
|
||||||
},
|
|
||||||
requestWork: {
|
|
||||||
requestDataId: { in: body.requestDataId },
|
|
||||||
},
|
|
||||||
},
|
|
||||||
data: { responsibleUserId: body.defaultMessengerId },
|
|
||||||
});
|
|
||||||
|
|
||||||
return record[0];
|
|
||||||
});
|
});
|
||||||
|
|
||||||
|
if (record.length <= 0) throw notFoundError("Request Data");
|
||||||
|
|
||||||
|
return record[0];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Route("/api/v1/request-data/{requestDataId}")
|
@Route("/api/v1/request-data/{requestDataId}")
|
||||||
@Tags("Request List")
|
@Tags("Request List")
|
||||||
export class RequestDataActionController extends Controller {
|
export class RequestDataActionController 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("reject-request-cancel")
|
@Post("reject-request-cancel")
|
||||||
@Security("keycloak")
|
@Security("keycloak")
|
||||||
async rejectRequestCancel(
|
async rejectRequestCancel(
|
||||||
|
|
@ -423,17 +338,6 @@ export class RequestDataActionController extends Controller {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
include: {
|
|
||||||
quotation: {
|
|
||||||
include: {
|
|
||||||
customerBranch: {
|
|
||||||
include: {
|
|
||||||
customer: { include: { branch: { where: { userId: { not: null } } } } },
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!result) throw notFoundError("Request Data");
|
if (!result) throw notFoundError("Request Data");
|
||||||
|
|
@ -476,88 +380,23 @@ export class RequestDataActionController extends Controller {
|
||||||
data: { quotationStatus: QuotationStatus.Canceled, urgent: false },
|
data: { quotationStatus: QuotationStatus.Canceled, urgent: false },
|
||||||
})
|
})
|
||||||
.then(async (res) => {
|
.then(async (res) => {
|
||||||
await Promise.all(
|
await tx.notification.createMany({
|
||||||
res.map((v) =>
|
data: res.map((v) => ({
|
||||||
tx.notification.create({
|
title: "สถานะใบเสนอราคาเปลี่ยนแปลง / Quotation Status Updated",
|
||||||
data: {
|
detail: "รหัส / code : " + v.code + " Canceled",
|
||||||
title: "สถานะใบเสนอราคาเปลี่ยนแปลง / Quotation Status Updated",
|
receiverId: v.createdByUserId,
|
||||||
detail: "รหัส / code : " + v.code + " Canceled",
|
})),
|
||||||
receiverId: v.createdByUserId,
|
});
|
||||||
registeredBranchId: v.registeredBranchId,
|
|
||||||
groupReceiver: { create: { name: "document_checker" } },
|
|
||||||
},
|
|
||||||
}),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}),
|
}),
|
||||||
tx.taskOrder
|
tx.taskOrder.updateMany({
|
||||||
.updateManyAndReturn({
|
where: {
|
||||||
where: {
|
taskList: {
|
||||||
taskList: {
|
every: { taskStatus: TaskStatus.Canceled },
|
||||||
every: { taskStatus: TaskStatus.Canceled },
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
data: { taskOrderStatus: TaskStatus.Canceled },
|
|
||||||
})
|
|
||||||
.then(async (res) => {
|
|
||||||
await Promise.all(
|
|
||||||
res.map((v) =>
|
|
||||||
tx.notification.create({
|
|
||||||
data: {
|
|
||||||
title: "สถานะใบเสนอราคาเปลี่ยนแปลง / Quotation Status Updated",
|
|
||||||
detail: "รหัส / code : " + v.code + " Canceled",
|
|
||||||
receiverId: v.createdByUserId,
|
|
||||||
registeredBranchId: v.registeredBranchId,
|
|
||||||
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 dataUserId: string[] = [];
|
|
||||||
|
|
||||||
result.quotation.customerBranch.customer.branch.forEach((item) => {
|
|
||||||
if (!dataCustomerId?.includes(item.id) && item.userId) {
|
|
||||||
dataCustomerId.push(item.id);
|
|
||||||
dataUserId.push(item.userId);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
finalTextWork = `เลขที่ใบเสนอราคา: ${result.code} ${result.quotation.workName}`;
|
|
||||||
|
|
||||||
textData = `${textHead}\n\n${textAlert}\n${finalTextWork}\n${textAlert2}\n\n${textAlert3}`;
|
|
||||||
|
|
||||||
const data = {
|
|
||||||
to: dataUserId,
|
|
||||||
messages: [
|
|
||||||
{
|
|
||||||
type: "text",
|
|
||||||
text: textData,
|
|
||||||
},
|
},
|
||||||
],
|
data: { taskOrderStatus: TaskStatus.Canceled },
|
||||||
};
|
}),
|
||||||
|
]);
|
||||||
await fetch("https://api.line.me/v2/bot/message/multicast", {
|
|
||||||
method: "POST",
|
|
||||||
headers: {
|
|
||||||
Authorization: `Bearer ${token}`,
|
|
||||||
"Content-Type": "application/json",
|
|
||||||
},
|
|
||||||
body: JSON.stringify(data),
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -689,19 +528,13 @@ export class RequestDataActionController extends Controller {
|
||||||
data: { quotationStatus: QuotationStatus.Canceled, urgent: false },
|
data: { quotationStatus: QuotationStatus.Canceled, urgent: false },
|
||||||
})
|
})
|
||||||
.then(async (res) => {
|
.then(async (res) => {
|
||||||
await Promise.all(
|
await tx.notification.createMany({
|
||||||
res.map((v) =>
|
data: res.map((v) => ({
|
||||||
tx.notification.create({
|
title: "สถานะใบเสนอราคาเปลี่ยนแปลง / Quotation Status Updated",
|
||||||
data: {
|
detail: "รหัส / code : " + v.code + " Canceled",
|
||||||
title: "สถานะใบเสนอราคาเปลี่ยนแปลง / Quotation Status Updated",
|
receiverId: v.createdByUserId,
|
||||||
detail: "รหัส / code : " + v.code + " Canceled",
|
})),
|
||||||
receiverId: v.createdByUserId,
|
});
|
||||||
registeredBranchId: v.registeredBranchId,
|
|
||||||
groupReceiver: { create: { name: "document_checker" } },
|
|
||||||
},
|
|
||||||
}),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}),
|
}),
|
||||||
tx.taskOrder.updateMany({
|
tx.taskOrder.updateMany({
|
||||||
where: {
|
where: {
|
||||||
|
|
@ -784,83 +617,14 @@ export class RequestDataActionController extends Controller {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
data: { quotationStatus: QuotationStatus.ProcessComplete, urgent: false },
|
data: { quotationStatus: QuotationStatus.ProcessComplete, urgent: false },
|
||||||
include: {
|
|
||||||
customerBranch: {
|
|
||||||
include: {
|
|
||||||
customer: {
|
|
||||||
include: {
|
|
||||||
branch: {
|
|
||||||
where: { userId: { not: null } },
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
})
|
})
|
||||||
.then(async (res) => {
|
.then(async (res) => {
|
||||||
await Promise.all(
|
await tx.notification.createMany({
|
||||||
res.map((v) =>
|
data: res.map((v) => ({
|
||||||
tx.notification.create({
|
title: "สถานะใบเสนอราคาเปลี่ยนแปลง / Quotation Status Updated",
|
||||||
data: {
|
detail: "รหัส / code : " + v.code + " Completed",
|
||||||
title: "สถานะใบเสนอราคาเปลี่ยนแปลง / Quotation Status Updated",
|
receiverId: v.createdByUserId,
|
||||||
detail: "รหัส / code : " + v.code + " Completed",
|
})),
|
||||||
receiverId: v.createdByUserId,
|
|
||||||
registeredBranchId: v.registeredBranchId,
|
|
||||||
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),
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
// dataRecord.push(record);
|
// dataRecord.push(record);
|
||||||
|
|
@ -984,7 +748,6 @@ export class RequestListController extends Controller {
|
||||||
include: { user: true },
|
include: { user: true },
|
||||||
},
|
},
|
||||||
responsibleInstitution: true,
|
responsibleInstitution: true,
|
||||||
responsibleGroup: true,
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
@ -1045,7 +808,6 @@ export class RequestListController extends Controller {
|
||||||
include: { user: true },
|
include: { user: true },
|
||||||
},
|
},
|
||||||
responsibleInstitution: true,
|
responsibleInstitution: true,
|
||||||
responsibleGroup: true,
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
@ -1155,7 +917,7 @@ export class RequestListController extends Controller {
|
||||||
});
|
});
|
||||||
|
|
||||||
if (record.responsibleUserId === null) {
|
if (record.responsibleUserId === null) {
|
||||||
await tx.requestWorkStepStatus.update({
|
await prisma.requestWorkStepStatus.update({
|
||||||
where: {
|
where: {
|
||||||
step_requestWorkId: {
|
step_requestWorkId: {
|
||||||
step: step,
|
step: step,
|
||||||
|
|
@ -1217,19 +979,13 @@ export class RequestListController extends Controller {
|
||||||
data: { quotationStatus: QuotationStatus.Canceled, urgent: false },
|
data: { quotationStatus: QuotationStatus.Canceled, urgent: false },
|
||||||
})
|
})
|
||||||
.then(async (res) => {
|
.then(async (res) => {
|
||||||
await Promise.all(
|
await tx.notification.createMany({
|
||||||
res.map((v) =>
|
data: res.map((v) => ({
|
||||||
tx.notification.create({
|
title: "สถานะใบเสนอราคาเปลี่ยนแปลง / Quotation Status Updated",
|
||||||
data: {
|
detail: "รหัส / code : " + v.code + " Canceled",
|
||||||
title: "สถานะใบเสนอราคาเปลี่ยนแปลง / Quotation Status Updated",
|
receiverId: v.createdByUserId,
|
||||||
detail: "รหัส / code : " + v.code + " Canceled",
|
})),
|
||||||
receiverId: v.createdByUserId,
|
});
|
||||||
registeredBranchId: v.registeredBranchId,
|
|
||||||
groupReceiver: { create: { name: "document_checker" } },
|
|
||||||
},
|
|
||||||
}),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}),
|
}),
|
||||||
tx.taskOrder.updateMany({
|
tx.taskOrder.updateMany({
|
||||||
where: {
|
where: {
|
||||||
|
|
@ -1337,19 +1093,13 @@ export class RequestListController extends Controller {
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
.then(async (res) => {
|
.then(async (res) => {
|
||||||
await Promise.all(
|
await tx.notification.createMany({
|
||||||
res.map((v) =>
|
data: res.map((v) => ({
|
||||||
tx.notification.create({
|
title: "สถานะใบเสนอราคาเปลี่ยนแปลง / Quotation Status Updated",
|
||||||
data: {
|
detail: "รหัส / code : " + v.code + " Completed",
|
||||||
title: "สถานะใบเสนอราคาเปลี่ยนแปลง / Quotation Status Updated",
|
receiverId: v.createdByUserId,
|
||||||
detail: "รหัส / code : " + v.code + " Completed",
|
})),
|
||||||
receiverId: v.createdByUserId,
|
});
|
||||||
registeredBranchId: v.registeredBranchId,
|
|
||||||
groupReceiver: { create: { name: "document_checker" } },
|
|
||||||
},
|
|
||||||
}),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
const token = await this.#getLineToken();
|
const token = await this.#getLineToken();
|
||||||
if (!token) return;
|
if (!token) return;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -42,23 +42,13 @@ import {
|
||||||
listFile,
|
listFile,
|
||||||
setFile,
|
setFile,
|
||||||
} from "../utils/minio";
|
} from "../utils/minio";
|
||||||
import { queryOrNot, whereDateQuery } from "../utils/relation";
|
import { queryOrNot } from "../utils/relation";
|
||||||
|
|
||||||
const MANAGE_ROLES = [
|
const MANAGE_ROLES = ["system", "head_of_admin", "admin", "document_checker"];
|
||||||
"system",
|
|
||||||
"head_of_admin",
|
|
||||||
"admin",
|
|
||||||
"executive",
|
|
||||||
"accountant",
|
|
||||||
"branch_admin",
|
|
||||||
"branch_manager",
|
|
||||||
"branch_accountant",
|
|
||||||
"data_entry",
|
|
||||||
];
|
|
||||||
|
|
||||||
function globalAllow(user: RequestWithUser["user"]) {
|
function globalAllow(user: RequestWithUser["user"]) {
|
||||||
const listAllowed = ["system", "head_of_admin", "admin", "executive", "accountant"];
|
const allowList = ["system", "head_of_admin"];
|
||||||
return user.roles?.some((v) => listAllowed.includes(v)) || false;
|
return allowList.some((v) => user.roles?.includes(v));
|
||||||
}
|
}
|
||||||
|
|
||||||
const permissionCondCompany = createPermCondition((_) => true);
|
const permissionCondCompany = createPermCondition((_) => true);
|
||||||
|
|
@ -70,14 +60,11 @@ const permissionCheckCompany = createPermCheck((_) => true);
|
||||||
@Tags("Task Order")
|
@Tags("Task Order")
|
||||||
export class TaskController extends Controller {
|
export class TaskController extends Controller {
|
||||||
@Get("stats")
|
@Get("stats")
|
||||||
@Security("keycloak")
|
async getTaskOrderStats() {
|
||||||
async getTaskOrderStats(@Request() req: RequestWithUser) {
|
|
||||||
const task = await prisma.taskOrder.groupBy({
|
const task = await prisma.taskOrder.groupBy({
|
||||||
where: { registeredBranch: { OR: permissionCondCompany(req.user) } },
|
|
||||||
by: ["taskOrderStatus"],
|
by: ["taskOrderStatus"],
|
||||||
_count: true,
|
_count: true,
|
||||||
});
|
});
|
||||||
|
|
||||||
return task.reduce<Record<TaskOrderStatus, number>>(
|
return task.reduce<Record<TaskOrderStatus, number>>(
|
||||||
(a, c) => Object.assign(a, { [c.taskOrderStatus]: c._count }),
|
(a, c) => Object.assign(a, { [c.taskOrderStatus]: c._count }),
|
||||||
{
|
{
|
||||||
|
|
@ -99,8 +86,6 @@ export class TaskController extends Controller {
|
||||||
@Query() pageSize = 30,
|
@Query() pageSize = 30,
|
||||||
@Query() assignedByUserId?: string,
|
@Query() assignedByUserId?: string,
|
||||||
@Query() taskOrderStatus?: TaskOrderStatus,
|
@Query() taskOrderStatus?: TaskOrderStatus,
|
||||||
@Query() startDate?: Date,
|
|
||||||
@Query() endDate?: Date,
|
|
||||||
) {
|
) {
|
||||||
return this.getTaskOrderListByCriteria(
|
return this.getTaskOrderListByCriteria(
|
||||||
req,
|
req,
|
||||||
|
|
@ -109,8 +94,6 @@ export class TaskController extends Controller {
|
||||||
pageSize,
|
pageSize,
|
||||||
assignedByUserId,
|
assignedByUserId,
|
||||||
taskOrderStatus,
|
taskOrderStatus,
|
||||||
startDate,
|
|
||||||
endDate,
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -123,8 +106,6 @@ export class TaskController extends Controller {
|
||||||
@Query() pageSize = 30,
|
@Query() pageSize = 30,
|
||||||
@Query() assignedUserId?: string,
|
@Query() assignedUserId?: string,
|
||||||
@Query() taskOrderStatus?: TaskOrderStatus,
|
@Query() taskOrderStatus?: TaskOrderStatus,
|
||||||
@Query() startDate?: Date,
|
|
||||||
@Query() endDate?: Date,
|
|
||||||
@Body() body?: { code?: string[] },
|
@Body() body?: { code?: string[] },
|
||||||
) {
|
) {
|
||||||
const where = {
|
const where = {
|
||||||
|
|
@ -144,7 +125,6 @@ export class TaskController extends Controller {
|
||||||
{ contactName: { contains: query, mode: "insensitive" } },
|
{ contactName: { contains: query, mode: "insensitive" } },
|
||||||
{ contactTel: { contains: query, mode: "insensitive" } },
|
{ contactTel: { contains: query, mode: "insensitive" } },
|
||||||
]),
|
]),
|
||||||
...whereDateQuery(startDate, endDate),
|
|
||||||
} satisfies Prisma.TaskOrderWhereInput;
|
} satisfies Prisma.TaskOrderWhereInput;
|
||||||
|
|
||||||
const [result, total] = await prisma.$transaction([
|
const [result, total] = await prisma.$transaction([
|
||||||
|
|
@ -213,7 +193,6 @@ export class TaskController extends Controller {
|
||||||
step: {
|
step: {
|
||||||
include: {
|
include: {
|
||||||
value: true,
|
value: true,
|
||||||
responsibleGroup: true,
|
|
||||||
responsiblePerson: {
|
responsiblePerson: {
|
||||||
include: { user: true },
|
include: { user: true },
|
||||||
},
|
},
|
||||||
|
|
@ -265,12 +244,6 @@ export class TaskController extends Controller {
|
||||||
taskProduct?: { productId: string; discount?: number }[];
|
taskProduct?: { productId: string; discount?: number }[];
|
||||||
},
|
},
|
||||||
) {
|
) {
|
||||||
if (body.taskList.length < 1 || !body.registeredBranchId)
|
|
||||||
throw new HttpError(
|
|
||||||
HttpStatus.BAD_REQUEST,
|
|
||||||
"Your created invalid task order",
|
|
||||||
"taskOrderInvalid",
|
|
||||||
);
|
|
||||||
return await prisma.$transaction(async (tx) => {
|
return await prisma.$transaction(async (tx) => {
|
||||||
const last = await tx.runningNo.upsert({
|
const last = await tx.runningNo.upsert({
|
||||||
where: {
|
where: {
|
||||||
|
|
@ -320,8 +293,8 @@ export class TaskController extends Controller {
|
||||||
if (updated.count !== taskList.length) {
|
if (updated.count !== taskList.length) {
|
||||||
throw new HttpError(
|
throw new HttpError(
|
||||||
HttpStatus.PRECONDITION_FAILED,
|
HttpStatus.PRECONDITION_FAILED,
|
||||||
"all request work to issue task order must be in ready state.",
|
"All request work to issue task order must be in ready state.",
|
||||||
"requestworkmustready",
|
"requestWorkMustReady",
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
await tx.institution.updateMany({
|
await tx.institution.updateMany({
|
||||||
|
|
@ -344,51 +317,49 @@ export class TaskController extends Controller {
|
||||||
where: { OR: taskList },
|
where: { OR: taskList },
|
||||||
});
|
});
|
||||||
|
|
||||||
return await tx.taskOrder
|
return await tx.taskOrder.create({
|
||||||
.create({
|
include: {
|
||||||
include: {
|
taskList: {
|
||||||
taskList: {
|
include: {
|
||||||
include: {
|
requestWorkStep: {
|
||||||
requestWorkStep: {
|
include: {
|
||||||
include: {
|
requestWork: {
|
||||||
requestWork: {
|
include: {
|
||||||
include: {
|
request: {
|
||||||
request: {
|
include: {
|
||||||
include: {
|
employee: true,
|
||||||
employee: true,
|
quotation: {
|
||||||
quotation: {
|
include: {
|
||||||
include: {
|
customerBranch: {
|
||||||
customerBranch: {
|
include: {
|
||||||
include: {
|
customer: true,
|
||||||
customer: true,
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
productService: {
|
},
|
||||||
include: {
|
productService: {
|
||||||
service: {
|
include: {
|
||||||
include: {
|
service: {
|
||||||
workflow: {
|
include: {
|
||||||
include: {
|
workflow: {
|
||||||
step: {
|
include: {
|
||||||
include: {
|
step: {
|
||||||
value: true,
|
include: {
|
||||||
responsiblePerson: {
|
value: true,
|
||||||
include: { user: true },
|
responsiblePerson: {
|
||||||
},
|
include: { user: true },
|
||||||
responsibleInstitution: true,
|
|
||||||
},
|
},
|
||||||
|
responsibleInstitution: true,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
work: true,
|
|
||||||
product: true,
|
|
||||||
},
|
},
|
||||||
|
work: true,
|
||||||
|
product: true,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
@ -396,30 +367,20 @@ export class TaskController extends Controller {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
institution: true,
|
|
||||||
createdBy: true,
|
|
||||||
},
|
},
|
||||||
data: {
|
institution: true,
|
||||||
...rest,
|
createdBy: true,
|
||||||
code,
|
},
|
||||||
urgent: work.some((v) => v.requestWork.request.quotation.urgent),
|
data: {
|
||||||
registeredBranchId: userAffiliatedBranch.id,
|
...rest,
|
||||||
createdByUserId: req.user.sub,
|
code,
|
||||||
taskList: { create: taskList },
|
urgent: work.some((v) => v.requestWork.request.quotation.urgent),
|
||||||
taskProduct: { create: taskProduct },
|
registeredBranchId: userAffiliatedBranch.id,
|
||||||
},
|
createdByUserId: req.user.sub,
|
||||||
})
|
taskList: { create: taskList },
|
||||||
.then(async (v) => {
|
taskProduct: { create: taskProduct },
|
||||||
await prisma.notification.create({
|
},
|
||||||
data: {
|
});
|
||||||
title: "ใบสั่งงานใหม่ / New Task Order",
|
|
||||||
detail: "รหัส / code : " + v.code,
|
|
||||||
registeredBranchId: v.registeredBranchId,
|
|
||||||
groupReceiver: { create: { name: "document_checker" } },
|
|
||||||
},
|
|
||||||
});
|
|
||||||
return v;
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -562,8 +523,6 @@ export class TaskController extends Controller {
|
||||||
title: "มีการส่งงาน / Task Submitted",
|
title: "มีการส่งงาน / Task Submitted",
|
||||||
detail: "รหัสใบสั่งงาน / Order : " + record.code,
|
detail: "รหัสใบสั่งงาน / Order : " + record.code,
|
||||||
receiverId: record.createdByUserId,
|
receiverId: record.createdByUserId,
|
||||||
registeredBranchId: record.registeredBranchId,
|
|
||||||
groupReceiver: { create: { name: "document_checker" } },
|
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
@ -639,28 +598,7 @@ export class TaskActionController extends Controller {
|
||||||
return await prisma.$transaction(async (tx) => {
|
return await prisma.$transaction(async (tx) => {
|
||||||
const promises = body.map(async (v) => {
|
const promises = body.map(async (v) => {
|
||||||
const record = await tx.task.findFirst({
|
const record = await tx.task.findFirst({
|
||||||
include: {
|
include: { requestWorkStep: true },
|
||||||
requestWorkStep: {
|
|
||||||
include: {
|
|
||||||
requestWork: {
|
|
||||||
include: {
|
|
||||||
request: {
|
|
||||||
include: {
|
|
||||||
quotation: true,
|
|
||||||
employee: true,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
productService: {
|
|
||||||
include: {
|
|
||||||
product: true,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
taskOrder: true,
|
|
||||||
},
|
|
||||||
where: {
|
where: {
|
||||||
step: v.step,
|
step: v.step,
|
||||||
requestWorkId: v.requestWorkId,
|
requestWorkId: v.requestWorkId,
|
||||||
|
|
@ -678,25 +616,6 @@ export class TaskActionController extends Controller {
|
||||||
data: { userTaskStatus: UserTaskStatus.Restart },
|
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({
|
return await tx.task.update({
|
||||||
where: { id: record.id },
|
where: { id: record.id },
|
||||||
data: {
|
data: {
|
||||||
|
|
@ -758,8 +677,6 @@ export class TaskActionController extends Controller {
|
||||||
title: "มีการส่งงาน / Task Submitted",
|
title: "มีการส่งงาน / Task Submitted",
|
||||||
detail: "รหัสใบสั่งงาน / Order : " + record.code,
|
detail: "รหัสใบสั่งงาน / Order : " + record.code,
|
||||||
receiverId: record.createdByUserId,
|
receiverId: record.createdByUserId,
|
||||||
registeredBranchId: record.registeredBranchId,
|
|
||||||
groupReceiver: { create: { name: "document_checker" } },
|
|
||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
]);
|
]);
|
||||||
|
|
@ -773,53 +690,22 @@ export class TaskActionController extends Controller {
|
||||||
if (!record) throw notFoundError("Task Order");
|
if (!record) throw notFoundError("Task Order");
|
||||||
|
|
||||||
await prisma.$transaction(async (tx) => {
|
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([
|
await Promise.all([
|
||||||
tx.taskOrder
|
tx.taskOrder.update({
|
||||||
.update({
|
where: { id: taskOrderId },
|
||||||
where: { id: taskOrderId },
|
data: {
|
||||||
data: {
|
urgent: false,
|
||||||
urgent: false,
|
taskOrderStatus: TaskOrderStatus.Complete,
|
||||||
taskOrderStatus: TaskOrderStatus.Complete,
|
userTask: {
|
||||||
codeProductReceived: code,
|
updateMany: {
|
||||||
userTask: {
|
where: { taskOrderId },
|
||||||
updateMany: {
|
data: {
|
||||||
where: { taskOrderId },
|
userTaskStatus: UserTaskStatus.Submit,
|
||||||
data: {
|
|
||||||
userTaskStatus: UserTaskStatus.Submit,
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
})
|
},
|
||||||
.then(async (record) => {
|
}),
|
||||||
await tx.notification.create({
|
|
||||||
data: {
|
|
||||||
title: "ใบงานเสร็จสิ้น / Task Complete",
|
|
||||||
detail: "รหัสใบสั่งงาน / Order : " + record.code,
|
|
||||||
receiverId: record.createdByUserId,
|
|
||||||
registeredBranchId: record.registeredBranchId,
|
|
||||||
groupReceiver: { create: { name: "document_checker" } },
|
|
||||||
},
|
|
||||||
});
|
|
||||||
}),
|
|
||||||
tx.requestWorkStepStatus.updateMany({
|
tx.requestWorkStepStatus.updateMany({
|
||||||
where: {
|
where: {
|
||||||
task: {
|
task: {
|
||||||
|
|
@ -923,34 +809,10 @@ export class TaskActionController extends Controller {
|
||||||
if (completeCheck) completed.push(item.id);
|
if (completeCheck) completed.push(item.id);
|
||||||
});
|
});
|
||||||
|
|
||||||
await tx.requestData
|
await tx.requestData.updateMany({
|
||||||
.updateManyAndReturn({
|
where: { id: { in: completed } },
|
||||||
where: { id: { in: completed } },
|
data: { requestDataStatus: RequestDataStatus.Completed },
|
||||||
include: {
|
});
|
||||||
quotation: {
|
|
||||||
select: {
|
|
||||||
registeredBranchId: true,
|
|
||||||
createdByUserId: true,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
data: { requestDataStatus: RequestDataStatus.Completed },
|
|
||||||
})
|
|
||||||
.then(async (res) => {
|
|
||||||
await Promise.all(
|
|
||||||
res.map((v) =>
|
|
||||||
tx.notification.create({
|
|
||||||
data: {
|
|
||||||
title: "สถานะใบเสนอราคาเปลี่ยนแปลง / Quotation Status Updated",
|
|
||||||
detail: "รหัส / code : " + v.code + " Completed",
|
|
||||||
receiverId: v.quotation.createdByUserId,
|
|
||||||
registeredBranchId: v.quotation.registeredBranchId,
|
|
||||||
groupReceiver: { create: { name: "document_checker" } },
|
|
||||||
},
|
|
||||||
}),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
});
|
|
||||||
await tx.quotation
|
await tx.quotation
|
||||||
.updateManyAndReturn({
|
.updateManyAndReturn({
|
||||||
where: {
|
where: {
|
||||||
|
|
@ -990,19 +852,13 @@ export class TaskActionController extends Controller {
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
.then(async (res) => {
|
.then(async (res) => {
|
||||||
await Promise.all(
|
await tx.notification.createMany({
|
||||||
res.map((v) =>
|
data: res.map((v) => ({
|
||||||
tx.notification.create({
|
title: "สถานะใบเสนอราคาเปลี่ยนแปลง / Quotation Status Updated",
|
||||||
data: {
|
detail: "รหัส / code : " + v.code + " Completed",
|
||||||
title: "สถานะใบเสนอราคาเปลี่ยนแปลง / Quotation Status Updated",
|
receiverId: v.createdByUserId,
|
||||||
detail: "รหัส / code : " + v.code + " Completed",
|
})),
|
||||||
receiverId: v.createdByUserId,
|
});
|
||||||
registeredBranchId: v.registeredBranchId,
|
|
||||||
groupReceiver: { create: { name: "document_checker" } },
|
|
||||||
},
|
|
||||||
}),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
|
|
||||||
const token = await this.#getLineToken();
|
const token = await this.#getLineToken();
|
||||||
|
|
||||||
|
|
@ -1123,8 +979,6 @@ export class UserTaskController extends Controller {
|
||||||
@Query() page = 1,
|
@Query() page = 1,
|
||||||
@Query() pageSize = 30,
|
@Query() pageSize = 30,
|
||||||
@Query() userTaskStatus?: UserTaskStatus,
|
@Query() userTaskStatus?: UserTaskStatus,
|
||||||
@Query() startDate?: Date,
|
|
||||||
@Query() endDate?: Date,
|
|
||||||
) {
|
) {
|
||||||
const where = {
|
const where = {
|
||||||
taskList: {
|
taskList: {
|
||||||
|
|
@ -1171,7 +1025,6 @@ export class UserTaskController extends Controller {
|
||||||
{ contactName: { contains: query, mode: "insensitive" } },
|
{ contactName: { contains: query, mode: "insensitive" } },
|
||||||
{ contactTel: { contains: query, mode: "insensitive" } },
|
{ contactTel: { contains: query, mode: "insensitive" } },
|
||||||
]),
|
]),
|
||||||
...whereDateQuery(startDate, endDate),
|
|
||||||
} satisfies Prisma.TaskOrderWhereInput;
|
} satisfies Prisma.TaskOrderWhereInput;
|
||||||
|
|
||||||
const [result, total] = await prisma.$transaction([
|
const [result, total] = await prisma.$transaction([
|
||||||
|
|
@ -1241,23 +1094,19 @@ export class UserTaskController extends Controller {
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
.then(async (v) => {
|
.then(async (v) => {
|
||||||
await tx.notification.create({
|
await tx.notification.createMany({
|
||||||
data: {
|
data: [
|
||||||
title: "สถานะใบส่งงานมีการเปลี่ยนแปลง / Order Status Changed",
|
{
|
||||||
detail: "รหัสใบสั่งงาน / Order : " + v.code + " InProgress",
|
title: "สถานะใบส่งงานมีการเปลี่ยนแปลง / Order Status Changed",
|
||||||
receiverId: v.createdByUserId,
|
detail: "รหัสใบสั่งงาน / Order : " + v.code + " InProgress",
|
||||||
registeredBranchId: v.registeredBranchId,
|
receiverId: v.createdByUserId,
|
||||||
groupReceiver: { create: { name: "document_checker" } },
|
},
|
||||||
},
|
{
|
||||||
});
|
title: "มีการรับงาน / Task Accepted",
|
||||||
await tx.notification.create({
|
detail: "รหัสใบสั่งงาน / Order : " + v.code,
|
||||||
data: {
|
receiverId: v.createdByUserId,
|
||||||
title: "มีการรับงาน / Task Accepted",
|
},
|
||||||
detail: "รหัสใบสั่งงาน / Order : " + v.code,
|
],
|
||||||
receiverId: v.createdByUserId,
|
|
||||||
registeredBranchId: v.registeredBranchId,
|
|
||||||
groupReceiver: { create: { name: "document_checker" } },
|
|
||||||
},
|
|
||||||
});
|
});
|
||||||
}),
|
}),
|
||||||
tx.task.updateMany({
|
tx.task.updateMany({
|
||||||
|
|
|
||||||
|
|
@ -13,7 +13,6 @@ import {
|
||||||
Security,
|
Security,
|
||||||
Tags,
|
Tags,
|
||||||
} from "tsoa";
|
} from "tsoa";
|
||||||
import config from "../config.json";
|
|
||||||
|
|
||||||
import prisma from "../db";
|
import prisma from "../db";
|
||||||
|
|
||||||
|
|
@ -36,28 +35,29 @@ import {
|
||||||
} from "../utils/minio";
|
} from "../utils/minio";
|
||||||
import { notFoundError } from "../utils/error";
|
import { notFoundError } from "../utils/error";
|
||||||
import { CreditNotePaybackType, CreditNoteStatus, Prisma, RequestDataStatus } from "@prisma/client";
|
import { CreditNotePaybackType, CreditNoteStatus, Prisma, RequestDataStatus } from "@prisma/client";
|
||||||
import { queryOrNot, whereDateQuery } from "../utils/relation";
|
import { queryOrNot } from "../utils/relation";
|
||||||
import { PaybackStatus, RequestWorkStatus } from "../generated/kysely/types";
|
import { PaybackStatus, RequestWorkStatus } from "../generated/kysely/types";
|
||||||
|
|
||||||
const MANAGE_ROLES = [
|
const MANAGE_ROLES = [
|
||||||
"system",
|
"system",
|
||||||
"head_of_admin",
|
"head_of_admin",
|
||||||
"admin",
|
"admin",
|
||||||
"executive",
|
"head_of_accountant",
|
||||||
"accountant",
|
"accountant",
|
||||||
"branch_admin",
|
"head_of_sale",
|
||||||
"branch_manager",
|
"sale",
|
||||||
"branch_accountant",
|
|
||||||
];
|
];
|
||||||
|
|
||||||
function globalAllow(user: RequestWithUser["user"]) {
|
function globalAllow(user: RequestWithUser["user"]) {
|
||||||
const listAllowed = ["system", "head_of_admin", "admin", "executive", "accountant"];
|
const allowList = ["system", "head_of_admin", "head_of_accountant", "head_of_sale"];
|
||||||
return user.roles?.some((v) => listAllowed.includes(v)) || false;
|
return allowList.some((v) => user.roles?.includes(v));
|
||||||
}
|
}
|
||||||
const VAT_DEFAULT = config.vat;
|
|
||||||
|
|
||||||
|
// NOTE: permission condition/check in requestWork -> requestData -> quotation -> registeredBranch
|
||||||
const permissionCond = createPermCondition(globalAllow);
|
const permissionCond = createPermCondition(globalAllow);
|
||||||
|
const permissionCondCompany = createPermCondition((_) => true);
|
||||||
const permissionCheck = createPermCheck(globalAllow);
|
const permissionCheck = createPermCheck(globalAllow);
|
||||||
|
const permissionCheckCompany = createPermCheck((_) => true);
|
||||||
|
|
||||||
type CreditNoteCreate = {
|
type CreditNoteCreate = {
|
||||||
requestWorkId: string[];
|
requestWorkId: string[];
|
||||||
|
|
@ -85,14 +85,6 @@ type CreditNoteUpdate = {
|
||||||
@Route("api/v1/credit-note")
|
@Route("api/v1/credit-note")
|
||||||
@Tags("Credit Note")
|
@Tags("Credit Note")
|
||||||
export class CreditNoteController extends Controller {
|
export class CreditNoteController 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;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Get("stats")
|
@Get("stats")
|
||||||
@Security("keycloak")
|
@Security("keycloak")
|
||||||
async getCreditNoteStats(@Request() req: RequestWithUser, @Query() quotationId?: string) {
|
async getCreditNoteStats(@Request() req: RequestWithUser, @Query() quotationId?: string) {
|
||||||
|
|
@ -102,7 +94,7 @@ export class CreditNoteController extends Controller {
|
||||||
request: {
|
request: {
|
||||||
quotationId,
|
quotationId,
|
||||||
quotation: {
|
quotation: {
|
||||||
registeredBranch: { OR: permissionCond(req.user) },
|
registeredBranch: { OR: permissionCondCompany(req.user) },
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
@ -129,8 +121,6 @@ export class CreditNoteController extends Controller {
|
||||||
@Query() query: string = "",
|
@Query() query: string = "",
|
||||||
@Query() quotationId?: string,
|
@Query() quotationId?: string,
|
||||||
@Query() creditNoteStatus?: CreditNoteStatus,
|
@Query() creditNoteStatus?: CreditNoteStatus,
|
||||||
@Query() startDate?: Date,
|
|
||||||
@Query() endDate?: Date,
|
|
||||||
) {
|
) {
|
||||||
return await this.getCreditNoteListByCriteria(
|
return await this.getCreditNoteListByCriteria(
|
||||||
req,
|
req,
|
||||||
|
|
@ -139,8 +129,6 @@ export class CreditNoteController extends Controller {
|
||||||
query,
|
query,
|
||||||
quotationId,
|
quotationId,
|
||||||
creditNoteStatus,
|
creditNoteStatus,
|
||||||
startDate,
|
|
||||||
endDate,
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -154,8 +142,7 @@ export class CreditNoteController extends Controller {
|
||||||
@Query() query: string = "",
|
@Query() query: string = "",
|
||||||
@Query() quotationId?: string,
|
@Query() quotationId?: string,
|
||||||
@Query() creditNoteStatus?: CreditNoteStatus,
|
@Query() creditNoteStatus?: CreditNoteStatus,
|
||||||
@Query() startDate?: Date,
|
@Body() body?: {},
|
||||||
@Query() endDate?: Date,
|
|
||||||
) {
|
) {
|
||||||
const where = {
|
const where = {
|
||||||
OR: queryOrNot<Prisma.CreditNoteWhereInput[]>(query, [
|
OR: queryOrNot<Prisma.CreditNoteWhereInput[]>(query, [
|
||||||
|
|
@ -172,6 +159,7 @@ export class CreditNoteController extends Controller {
|
||||||
customerBranch: {
|
customerBranch: {
|
||||||
OR: [
|
OR: [
|
||||||
{ code: { contains: query, mode: "insensitive" } },
|
{ code: { contains: query, mode: "insensitive" } },
|
||||||
|
{ customerName: { contains: query, mode: "insensitive" } },
|
||||||
{ firstName: { contains: query, mode: "insensitive" } },
|
{ firstName: { contains: query, mode: "insensitive" } },
|
||||||
{ firstNameEN: { contains: query, mode: "insensitive" } },
|
{ firstNameEN: { contains: query, mode: "insensitive" } },
|
||||||
{ lastName: { contains: query, mode: "insensitive" } },
|
{ lastName: { contains: query, mode: "insensitive" } },
|
||||||
|
|
@ -206,19 +194,16 @@ export class CreditNoteController extends Controller {
|
||||||
request: {
|
request: {
|
||||||
quotationId,
|
quotationId,
|
||||||
quotation: {
|
quotation: {
|
||||||
registeredBranch: { OR: permissionCond(req.user) },
|
registeredBranch: { OR: permissionCondCompany(req.user) },
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
...whereDateQuery(startDate, endDate),
|
|
||||||
} satisfies Prisma.CreditNoteWhereInput;
|
} satisfies Prisma.CreditNoteWhereInput;
|
||||||
|
|
||||||
const [result, total] = await prisma.$transaction([
|
const [result, total] = await prisma.$transaction([
|
||||||
prisma.creditNote.findMany({
|
prisma.creditNote.findMany({
|
||||||
where,
|
where,
|
||||||
take: pageSize,
|
|
||||||
skip: (page - 1) * pageSize,
|
|
||||||
include: {
|
include: {
|
||||||
quotation: {
|
quotation: {
|
||||||
include: {
|
include: {
|
||||||
|
|
@ -251,7 +236,7 @@ export class CreditNoteController extends Controller {
|
||||||
some: {
|
some: {
|
||||||
request: {
|
request: {
|
||||||
quotation: {
|
quotation: {
|
||||||
registeredBranch: { OR: permissionCond(req.user) },
|
registeredBranch: { OR: permissionCondCompany(req.user) },
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
@ -349,8 +334,9 @@ export class CreditNoteController extends Controller {
|
||||||
).length;
|
).length;
|
||||||
|
|
||||||
const price =
|
const price =
|
||||||
c.productService.pricePerUnit * (1 + (c.productService.vat > 0 ? VAT_DEFAULT : 0)) -
|
c.productService.pricePerUnit -
|
||||||
c.productService.discount;
|
c.productService.discount / c.productService.amount +
|
||||||
|
c.productService.vat / c.productService.amount;
|
||||||
|
|
||||||
if (serviceChargeStepCount && successCount) {
|
if (serviceChargeStepCount && successCount) {
|
||||||
return a + price - c.productService.product.serviceCharge * successCount;
|
return a + price - c.productService.product.serviceCharge * successCount;
|
||||||
|
|
@ -376,98 +362,40 @@ export class CreditNoteController extends Controller {
|
||||||
update: { value: { increment: 1 } },
|
update: { value: { increment: 1 } },
|
||||||
});
|
});
|
||||||
|
|
||||||
return await prisma.creditNote
|
return await prisma.creditNote.create({
|
||||||
.create({
|
include: {
|
||||||
include: {
|
requestWork: {
|
||||||
requestWork: {
|
include: {
|
||||||
include: {
|
request: true,
|
||||||
request: true,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
quotation: {
|
|
||||||
include: {
|
|
||||||
customerBranch: {
|
|
||||||
include: {
|
|
||||||
customer: { include: { branch: { where: { userId: { not: null } } } } },
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
data: {
|
quotation: true,
|
||||||
reason: body.reason,
|
},
|
||||||
detail: body.detail,
|
data: {
|
||||||
remark: body.remark,
|
reason: body.reason,
|
||||||
paybackType: body.paybackType,
|
detail: body.detail,
|
||||||
paybackBank: body.paybackBank,
|
remark: body.remark,
|
||||||
paybackAccount: body.paybackAccount,
|
paybackType: body.paybackType,
|
||||||
paybackAccountName: body.paybackAccountName,
|
paybackBank: body.paybackBank,
|
||||||
code: `CN${currentYear.toString().padStart(2, "0")}${currentMonth.toString().padStart(2, "0")}${last.value.toString().padStart(6, "0")}`,
|
paybackAccount: body.paybackAccount,
|
||||||
value,
|
paybackAccountName: body.paybackAccountName,
|
||||||
requestWork: {
|
code: `CN${currentYear.toString().padStart(2, "0")}${currentMonth.toString().padStart(2, "0")}${last.value.toString().padStart(6, "0")}`,
|
||||||
connect: body.requestWorkId.map((v) => ({
|
value,
|
||||||
id: v,
|
requestWork: {
|
||||||
})),
|
connect: body.requestWorkId.map((v) => ({
|
||||||
},
|
id: v,
|
||||||
quotationId: body.quotationId,
|
})),
|
||||||
},
|
},
|
||||||
})
|
quotationId: body.quotationId,
|
||||||
.then(async (res) => {
|
},
|
||||||
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 dataUserId: string[] = [];
|
|
||||||
|
|
||||||
if (res) {
|
|
||||||
res.quotation.customerBranch.customer.branch.forEach((item) => {
|
|
||||||
if (!dataCustomerId?.includes(item.id) && item.userId) {
|
|
||||||
dataCustomerId.push(item.id);
|
|
||||||
dataUserId.push(item.userId);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
finalTextWork = `จำนวนเงิน ${res.value.toFixed(2)} บาท `;
|
|
||||||
}
|
|
||||||
|
|
||||||
textData = `${textHead}\n\n${textAlert}\n${finalTextWork}${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),
|
|
||||||
});
|
|
||||||
|
|
||||||
return res;
|
|
||||||
});
|
|
||||||
},
|
},
|
||||||
{ isolationLevel: Prisma.TransactionIsolationLevel.Serializable },
|
{ isolationLevel: Prisma.TransactionIsolationLevel.Serializable },
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Put("{creditNoteId}")
|
@Put("{creditNoteId}")
|
||||||
@Security("keycloak")
|
@Security("keycloak", MANAGE_ROLES)
|
||||||
async updateCreditNote(
|
async updateCreditNote(
|
||||||
@Request() req: RequestWithUser,
|
@Request() req: RequestWithUser,
|
||||||
@Path() creditNoteId: string,
|
@Path() creditNoteId: string,
|
||||||
|
|
@ -542,8 +470,9 @@ export class CreditNoteController extends Controller {
|
||||||
).length;
|
).length;
|
||||||
|
|
||||||
const price =
|
const price =
|
||||||
c.productService.pricePerUnit * (1 + (c.productService.vat > 0 ? VAT_DEFAULT : 0)) -
|
c.productService.pricePerUnit -
|
||||||
c.productService.discount;
|
c.productService.discount / c.productService.amount +
|
||||||
|
c.productService.vat / c.productService.amount;
|
||||||
|
|
||||||
if (serviceChargeStepCount && successCount) {
|
if (serviceChargeStepCount && successCount) {
|
||||||
return a + price - c.productService.product.serviceCharge * successCount;
|
return a + price - c.productService.product.serviceCharge * successCount;
|
||||||
|
|
@ -640,14 +569,6 @@ export class CreditNoteActionController extends Controller {
|
||||||
return creditNoteData;
|
return creditNoteData;
|
||||||
}
|
}
|
||||||
|
|
||||||
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("accept")
|
@Post("accept")
|
||||||
@Security("keycloak", MANAGE_ROLES)
|
@Security("keycloak", MANAGE_ROLES)
|
||||||
async acceptCreditNote(@Request() req: RequestWithUser, @Path() creditNoteId: string) {
|
async acceptCreditNote(@Request() req: RequestWithUser, @Path() creditNoteId: string) {
|
||||||
|
|
@ -666,81 +587,23 @@ export class CreditNoteActionController extends Controller {
|
||||||
@Body() body: { paybackStatus: PaybackStatus },
|
@Body() body: { paybackStatus: PaybackStatus },
|
||||||
) {
|
) {
|
||||||
await this.#checkPermission(req.user, creditNoteId);
|
await this.#checkPermission(req.user, creditNoteId);
|
||||||
return await prisma.creditNote
|
return await prisma.creditNote.update({
|
||||||
.update({
|
where: { id: creditNoteId },
|
||||||
where: { id: creditNoteId },
|
include: {
|
||||||
include: {
|
requestWork: {
|
||||||
requestWork: {
|
include: {
|
||||||
include: {
|
request: true,
|
||||||
request: true,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
quotation: {
|
|
||||||
include: {
|
|
||||||
customerBranch: {
|
|
||||||
include: {
|
|
||||||
customer: { include: { branch: { where: { userId: { not: null } } } } },
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
data: {
|
quotation: true,
|
||||||
creditNoteStatus:
|
},
|
||||||
body.paybackStatus === PaybackStatus.Done ? CreditNoteStatus.Success : undefined,
|
data: {
|
||||||
paybackStatus: body.paybackStatus,
|
creditNoteStatus:
|
||||||
paybackDate: body.paybackStatus === PaybackStatus.Done ? new Date() : undefined,
|
body.paybackStatus === PaybackStatus.Done ? CreditNoteStatus.Success : undefined,
|
||||||
},
|
paybackStatus: body.paybackStatus,
|
||||||
})
|
paybackDate: body.paybackStatus === PaybackStatus.Done ? new Date() : undefined,
|
||||||
.then(async (res) => {
|
},
|
||||||
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.quotation.customerBranch.customer.branch.forEach((item) => {
|
|
||||||
if (!dataCustomerId?.includes(item.id) && item.userId) {
|
|
||||||
dataCustomerId.push(item.id);
|
|
||||||
dataUserId.push(item.userId);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
finalTextWork = `จำนวนเงิน ${res.value.toFixed(2)} บาท `;
|
|
||||||
}
|
|
||||||
|
|
||||||
textData = `${textHead}\n\n${textAlert}\n${finalTextWork}${textAlert2}\n\n${textAlert3}`;
|
|
||||||
|
|
||||||
const data = {
|
|
||||||
to: dataUserId,
|
|
||||||
messages: [
|
|
||||||
{
|
|
||||||
type: "text",
|
|
||||||
text: textData,
|
|
||||||
},
|
|
||||||
],
|
|
||||||
};
|
|
||||||
body.paybackStatus === PaybackStatus.Done
|
|
||||||
? await fetch("https://api.line.me/v2/bot/message/multicast", {
|
|
||||||
method: "POST",
|
|
||||||
headers: {
|
|
||||||
Authorization: `Bearer ${token}`,
|
|
||||||
"Content-Type": "application/json",
|
|
||||||
},
|
|
||||||
body: JSON.stringify(data),
|
|
||||||
})
|
|
||||||
: undefined;
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -36,7 +36,7 @@ import {
|
||||||
setFile,
|
setFile,
|
||||||
} from "../utils/minio";
|
} from "../utils/minio";
|
||||||
import { isUsedError, notFoundError, relationError } from "../utils/error";
|
import { isUsedError, notFoundError, relationError } from "../utils/error";
|
||||||
import { queryOrNot, whereDateQuery } from "../utils/relation";
|
import { queryOrNot } from "../utils/relation";
|
||||||
import { isSystem } from "../utils/keycloak";
|
import { isSystem } from "../utils/keycloak";
|
||||||
import { precisionRound } from "../utils/arithmetic";
|
import { precisionRound } from "../utils/arithmetic";
|
||||||
|
|
||||||
|
|
@ -44,20 +44,22 @@ const MANAGE_ROLES = [
|
||||||
"system",
|
"system",
|
||||||
"head_of_admin",
|
"head_of_admin",
|
||||||
"admin",
|
"admin",
|
||||||
"executive",
|
"head_of_accountant",
|
||||||
"accountant",
|
"accountant",
|
||||||
"branch_admin",
|
"head_of_sale",
|
||||||
"branch_manager",
|
"sale",
|
||||||
"branch_accountant",
|
|
||||||
];
|
];
|
||||||
|
|
||||||
function globalAllow(user: RequestWithUser["user"]) {
|
function globalAllow(user: RequestWithUser["user"]) {
|
||||||
const listAllowed = ["system", "head_of_admin", "admin", "executive", "accountant"];
|
const allowList = ["system", "head_of_admin", "head_of_accountant", "head_of_sale"];
|
||||||
return user.roles?.some((v) => listAllowed.includes(v)) || false;
|
return allowList.some((v) => user.roles?.includes(v));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// NOTE: permission condition/check in registeredBranch
|
||||||
const permissionCond = createPermCondition(globalAllow);
|
const permissionCond = createPermCondition(globalAllow);
|
||||||
|
const permissionCondCompany = createPermCondition((_) => true);
|
||||||
const permissionCheck = createPermCheck(globalAllow);
|
const permissionCheck = createPermCheck(globalAllow);
|
||||||
|
const permissionCheckCompany = createPermCheck((_) => true);
|
||||||
|
|
||||||
type DebitNoteCreate = {
|
type DebitNoteCreate = {
|
||||||
quotationId: string;
|
quotationId: string;
|
||||||
|
|
@ -74,7 +76,6 @@ type DebitNoteCreate = {
|
||||||
dateOfBirth: Date;
|
dateOfBirth: Date;
|
||||||
gender: string;
|
gender: string;
|
||||||
nationality: string;
|
nationality: string;
|
||||||
otherNationality?: string | null;
|
|
||||||
namePrefix?: string;
|
namePrefix?: string;
|
||||||
firstName: string;
|
firstName: string;
|
||||||
firstNameEN: string;
|
firstNameEN: string;
|
||||||
|
|
@ -110,14 +111,13 @@ type DebitNoteUpdate = {
|
||||||
dateOfBirth: Date;
|
dateOfBirth: Date;
|
||||||
gender: string;
|
gender: string;
|
||||||
nationality: string;
|
nationality: string;
|
||||||
otherNationality?: string | null;
|
|
||||||
namePrefix?: string;
|
namePrefix?: string;
|
||||||
firstName?: string;
|
firstName: string;
|
||||||
firstNameEN: string;
|
firstNameEN: string;
|
||||||
middleName?: string;
|
middleName?: string;
|
||||||
middleNameEN?: string;
|
middleNameEN?: string;
|
||||||
lastName?: string;
|
lastName: string;
|
||||||
lastNameEN?: string;
|
lastNameEN: string;
|
||||||
}
|
}
|
||||||
)[];
|
)[];
|
||||||
|
|
||||||
|
|
@ -168,8 +168,6 @@ export class DebitNoteController extends Controller {
|
||||||
@Query() payCondition?: PayCondition,
|
@Query() payCondition?: PayCondition,
|
||||||
@Query() includeRegisteredBranch?: boolean,
|
@Query() includeRegisteredBranch?: boolean,
|
||||||
@Query() code?: string,
|
@Query() code?: string,
|
||||||
@Query() startDate?: Date,
|
|
||||||
@Query() endDate?: Date,
|
|
||||||
) {
|
) {
|
||||||
return await this.getDebitNoteListByCriteria(
|
return await this.getDebitNoteListByCriteria(
|
||||||
req,
|
req,
|
||||||
|
|
@ -181,8 +179,6 @@ export class DebitNoteController extends Controller {
|
||||||
payCondition,
|
payCondition,
|
||||||
includeRegisteredBranch,
|
includeRegisteredBranch,
|
||||||
code,
|
code,
|
||||||
startDate,
|
|
||||||
endDate,
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -199,8 +195,6 @@ export class DebitNoteController extends Controller {
|
||||||
@Query() payCondition?: PayCondition,
|
@Query() payCondition?: PayCondition,
|
||||||
@Query() includeRegisteredBranch?: boolean,
|
@Query() includeRegisteredBranch?: boolean,
|
||||||
@Query() code?: string,
|
@Query() code?: string,
|
||||||
@Query() startDate?: Date,
|
|
||||||
@Query() endDate?: Date,
|
|
||||||
@Body() body?: {},
|
@Body() body?: {},
|
||||||
) {
|
) {
|
||||||
const where = {
|
const where = {
|
||||||
|
|
@ -211,6 +205,7 @@ export class DebitNoteController extends Controller {
|
||||||
customerBranch: {
|
customerBranch: {
|
||||||
OR: [
|
OR: [
|
||||||
{ code: { contains: query, mode: "insensitive" } },
|
{ code: { contains: query, mode: "insensitive" } },
|
||||||
|
{ customerName: { contains: query, mode: "insensitive" } },
|
||||||
{ firstName: { contains: query, mode: "insensitive" } },
|
{ firstName: { contains: query, mode: "insensitive" } },
|
||||||
{ firstNameEN: { contains: query, mode: "insensitive" } },
|
{ firstNameEN: { contains: query, mode: "insensitive" } },
|
||||||
{ lastName: { contains: query, mode: "insensitive" } },
|
{ lastName: { contains: query, mode: "insensitive" } },
|
||||||
|
|
@ -225,7 +220,6 @@ export class DebitNoteController extends Controller {
|
||||||
debitNoteQuotationId: quotationId,
|
debitNoteQuotationId: quotationId,
|
||||||
registeredBranch: isSystem(req.user) ? undefined : { OR: permissionCond(req.user) },
|
registeredBranch: isSystem(req.user) ? undefined : { OR: permissionCond(req.user) },
|
||||||
quotationStatus: status,
|
quotationStatus: status,
|
||||||
...whereDateQuery(startDate, endDate),
|
|
||||||
} satisfies Prisma.QuotationWhereInput;
|
} satisfies Prisma.QuotationWhereInput;
|
||||||
|
|
||||||
const [result, total] = await prisma.$transaction([
|
const [result, total] = await prisma.$transaction([
|
||||||
|
|
@ -430,18 +424,12 @@ export class DebitNoteController extends Controller {
|
||||||
|
|
||||||
const list = body.productServiceList.map((v, i) => {
|
const list = body.productServiceList.map((v, i) => {
|
||||||
const p = product.find((p) => p.id === v.productId)!;
|
const p = product.find((p) => p.id === v.productId)!;
|
||||||
|
const price = body.agentPrice ? p.agentPrice : p.price;
|
||||||
const vatIncluded = body.agentPrice ? p.agentPriceVatIncluded : p.vatIncluded;
|
const pricePerUnit = p.vatIncluded ? price / (1 + VAT_DEFAULT) : price;
|
||||||
|
const vat = p.calcVat
|
||||||
const originalPrice = body.agentPrice ? p.agentPrice : p.price;
|
? (pricePerUnit * (v.discount ? v.amount : 1) - (v.discount || 0)) *
|
||||||
const finalPrice = precisionRound(
|
VAT_DEFAULT *
|
||||||
originalPrice + (vatIncluded ? 0 : originalPrice * VAT_DEFAULT),
|
(!v.discount ? v.amount : 1)
|
||||||
);
|
|
||||||
const pricePerUnit = finalPrice / (1 + VAT_DEFAULT);
|
|
||||||
const vat = (body.agentPrice ? p.agentPriceCalcVat : p.calcVat)
|
|
||||||
? ((pricePerUnit * (1 + VAT_DEFAULT) * v.amount - (v.discount || 0)) /
|
|
||||||
(1 + VAT_DEFAULT)) *
|
|
||||||
VAT_DEFAULT
|
|
||||||
: 0;
|
: 0;
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
|
@ -464,13 +452,15 @@ export class DebitNoteController extends Controller {
|
||||||
|
|
||||||
const price = list.reduce(
|
const price = list.reduce(
|
||||||
(a, c) => {
|
(a, c) => {
|
||||||
const vat = c.vat ? VAT_DEFAULT : 0;
|
a.totalPrice = precisionRound(a.totalPrice + c.pricePerUnit * c.amount);
|
||||||
const price = c.pricePerUnit * c.amount * (1 + vat) - c.discount;
|
|
||||||
|
|
||||||
a.totalPrice = precisionRound(a.totalPrice + price / (1 + vat) + c.discount);
|
|
||||||
a.totalDiscount = precisionRound(a.totalDiscount + c.discount);
|
a.totalDiscount = precisionRound(a.totalDiscount + c.discount);
|
||||||
a.vat = precisionRound(a.vat + c.vat);
|
a.vat = precisionRound(a.vat + c.vat);
|
||||||
a.vatExcluded = c.vat === 0 ? precisionRound(a.vatExcluded + price) : a.vatExcluded;
|
a.vatExcluded =
|
||||||
|
c.vat === 0
|
||||||
|
? precisionRound(
|
||||||
|
a.vatExcluded + (c.pricePerUnit * c.amount - (c.discount || 0)) * VAT_DEFAULT,
|
||||||
|
)
|
||||||
|
: a.vatExcluded;
|
||||||
a.finalPrice = precisionRound(
|
a.finalPrice = precisionRound(
|
||||||
Math.max(a.totalPrice - a.totalDiscount + a.vat - (body.discount || 0), 0),
|
Math.max(a.totalPrice - a.totalDiscount + a.vat - (body.discount || 0), 0),
|
||||||
);
|
);
|
||||||
|
|
@ -582,7 +572,7 @@ export class DebitNoteController extends Controller {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Put("{debitNoteId}")
|
@Put("{debitNoteId}")
|
||||||
@Security("keycloak")
|
@Security("keycloak", MANAGE_ROLES)
|
||||||
async updateDebitNote(
|
async updateDebitNote(
|
||||||
@Request() req: RequestWithUser,
|
@Request() req: RequestWithUser,
|
||||||
@Path() debitNoteId: string,
|
@Path() debitNoteId: string,
|
||||||
|
|
@ -606,7 +596,7 @@ export class DebitNoteController extends Controller {
|
||||||
|
|
||||||
if (!record) throw notFoundError("Debit Note");
|
if (!record) throw notFoundError("Debit Note");
|
||||||
|
|
||||||
await permissionCheck(req.user, record.registeredBranch);
|
await permissionCheckCompany(req.user, record.registeredBranch);
|
||||||
|
|
||||||
const { productServiceList: _productServiceList, ...rest } = body;
|
const { productServiceList: _productServiceList, ...rest } = body;
|
||||||
const ids = {
|
const ids = {
|
||||||
|
|
@ -677,18 +667,12 @@ export class DebitNoteController extends Controller {
|
||||||
}
|
}
|
||||||
const list = body.productServiceList.map((v, i) => {
|
const list = body.productServiceList.map((v, i) => {
|
||||||
const p = product.find((p) => p.id === v.productId)!;
|
const p = product.find((p) => p.id === v.productId)!;
|
||||||
|
const price = body.agentPrice ? p.agentPrice : p.price;
|
||||||
const vatIncluded = record.agentPrice ? p.agentPriceVatIncluded : p.vatIncluded;
|
const pricePerUnit = p.vatIncluded ? price / (1 + VAT_DEFAULT) : price;
|
||||||
|
const vat = p.calcVat
|
||||||
const originalPrice = record.agentPrice ? p.agentPrice : p.price;
|
? (pricePerUnit * (v.discount ? v.amount : 1) - (v.discount || 0)) *
|
||||||
const finalPrice = precisionRound(
|
VAT_DEFAULT *
|
||||||
originalPrice + (vatIncluded ? 0 : originalPrice * VAT_DEFAULT),
|
(!v.discount ? v.amount : 1)
|
||||||
);
|
|
||||||
const pricePerUnit = finalPrice / (1 + VAT_DEFAULT);
|
|
||||||
const vat = (record.agentPrice ? p.agentPriceCalcVat : p.calcVat)
|
|
||||||
? ((pricePerUnit * (1 + VAT_DEFAULT) * v.amount - (v.discount || 0)) /
|
|
||||||
(1 + VAT_DEFAULT)) *
|
|
||||||
VAT_DEFAULT
|
|
||||||
: 0;
|
: 0;
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
|
@ -711,13 +695,15 @@ export class DebitNoteController extends Controller {
|
||||||
|
|
||||||
const price = list.reduce(
|
const price = list.reduce(
|
||||||
(a, c) => {
|
(a, c) => {
|
||||||
const vat = c.vat ? VAT_DEFAULT : 0;
|
a.totalPrice = precisionRound(a.totalPrice + c.pricePerUnit * c.amount);
|
||||||
const price = c.pricePerUnit * c.amount * (1 + vat) - c.discount;
|
|
||||||
|
|
||||||
a.totalPrice = precisionRound(a.totalPrice + price / (1 + vat) + c.discount);
|
|
||||||
a.totalDiscount = precisionRound(a.totalDiscount + c.discount);
|
a.totalDiscount = precisionRound(a.totalDiscount + c.discount);
|
||||||
a.vat = precisionRound(a.vat + c.vat);
|
a.vat = precisionRound(a.vat + c.vat);
|
||||||
a.vatExcluded = c.vat === 0 ? precisionRound(a.vatExcluded + price) : a.vatExcluded;
|
a.vatExcluded =
|
||||||
|
c.vat === 0
|
||||||
|
? precisionRound(
|
||||||
|
a.vatExcluded + (c.pricePerUnit * c.amount - (c.discount || 0)) * VAT_DEFAULT,
|
||||||
|
)
|
||||||
|
: a.vatExcluded;
|
||||||
a.finalPrice = precisionRound(
|
a.finalPrice = precisionRound(
|
||||||
Math.max(a.totalPrice - a.totalDiscount + a.vat - (body.discount || 0), 0),
|
Math.max(a.totalPrice - a.totalDiscount + a.vat - (body.discount || 0), 0),
|
||||||
);
|
);
|
||||||
|
|
|
||||||
|
|
@ -25,7 +25,7 @@ import {
|
||||||
TaskStatus,
|
TaskStatus,
|
||||||
RequestWorkStatus,
|
RequestWorkStatus,
|
||||||
} from "@prisma/client";
|
} from "@prisma/client";
|
||||||
import { queryOrNot, whereAddressQuery, whereDateQuery } from "../utils/relation";
|
import { queryOrNot, whereAddressQuery } from "../utils/relation";
|
||||||
import { filterStatus } from "../services/prisma";
|
import { filterStatus } from "../services/prisma";
|
||||||
// import { RequestWorkStatus } from "../generated/kysely/types";
|
// import { RequestWorkStatus } from "../generated/kysely/types";
|
||||||
import { deleteFile, fileLocation, getFile, getPresigned, listFile, setFile } from "../utils/minio";
|
import { deleteFile, fileLocation, getFile, getPresigned, listFile, setFile } from "../utils/minio";
|
||||||
|
|
@ -51,8 +51,6 @@ export class LineController extends Controller {
|
||||||
@Query() page: number = 1,
|
@Query() page: number = 1,
|
||||||
@Query() pageSize: number = 30,
|
@Query() pageSize: number = 30,
|
||||||
@Query() activeOnly?: boolean,
|
@Query() activeOnly?: boolean,
|
||||||
@Query() startDate?: Date,
|
|
||||||
@Query() endDate?: Date,
|
|
||||||
) {
|
) {
|
||||||
const where = {
|
const where = {
|
||||||
OR: !!query
|
OR: !!query
|
||||||
|
|
@ -89,7 +87,6 @@ export class LineController extends Controller {
|
||||||
subDistrict: zipCode ? { zipCode } : undefined,
|
subDistrict: zipCode ? { zipCode } : undefined,
|
||||||
gender,
|
gender,
|
||||||
},
|
},
|
||||||
...whereDateQuery(startDate, endDate),
|
|
||||||
} satisfies Prisma.EmployeeWhereInput;
|
} satisfies Prisma.EmployeeWhereInput;
|
||||||
|
|
||||||
const [result, total] = await prisma.$transaction([
|
const [result, total] = await prisma.$transaction([
|
||||||
|
|
@ -176,8 +173,6 @@ export class LineController extends Controller {
|
||||||
@Query() requestDataStatus?: RequestDataStatus,
|
@Query() requestDataStatus?: RequestDataStatus,
|
||||||
@Query() quotationId?: string,
|
@Query() quotationId?: string,
|
||||||
@Query() code?: string,
|
@Query() code?: string,
|
||||||
@Query() startDate?: Date,
|
|
||||||
@Query() endDate?: Date,
|
|
||||||
) {
|
) {
|
||||||
const where = {
|
const where = {
|
||||||
OR: queryOrNot<Prisma.RequestDataWhereInput[]>(query, [
|
OR: queryOrNot<Prisma.RequestDataWhereInput[]>(query, [
|
||||||
|
|
@ -189,6 +184,7 @@ export class LineController extends Controller {
|
||||||
customerBranch: {
|
customerBranch: {
|
||||||
OR: [
|
OR: [
|
||||||
{ code: { contains: query, mode: "insensitive" } },
|
{ code: { contains: query, mode: "insensitive" } },
|
||||||
|
{ customerName: { contains: query, mode: "insensitive" } },
|
||||||
{ registerName: { contains: query, mode: "insensitive" } },
|
{ registerName: { contains: query, mode: "insensitive" } },
|
||||||
{ registerNameEN: { contains: query, mode: "insensitive" } },
|
{ registerNameEN: { contains: query, mode: "insensitive" } },
|
||||||
{ firstName: { contains: query, mode: "insensitive" } },
|
{ firstName: { contains: query, mode: "insensitive" } },
|
||||||
|
|
@ -251,7 +247,6 @@ export class LineController extends Controller {
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
...whereDateQuery(startDate, endDate),
|
|
||||||
} satisfies Prisma.RequestDataWhereInput;
|
} satisfies Prisma.RequestDataWhereInput;
|
||||||
|
|
||||||
const [result, total] = await prisma.$transaction([
|
const [result, total] = await prisma.$transaction([
|
||||||
|
|
@ -609,26 +604,41 @@ export class LineController extends Controller {
|
||||||
@Query() includeRegisteredBranch?: boolean,
|
@Query() includeRegisteredBranch?: boolean,
|
||||||
@Query() code?: string,
|
@Query() code?: string,
|
||||||
@Query() query = "",
|
@Query() query = "",
|
||||||
@Query() startDate?: Date,
|
|
||||||
@Query() endDate?: Date,
|
|
||||||
) {
|
) {
|
||||||
const where = {
|
const where = {
|
||||||
OR: queryOrNot<Prisma.QuotationWhereInput[]>(query, [
|
OR:
|
||||||
{ code: { contains: query, mode: "insensitive" } },
|
query || pendingOnly
|
||||||
{ workName: { contains: query, mode: "insensitive" } },
|
? [
|
||||||
{
|
...(queryOrNot<Prisma.QuotationWhereInput[]>(query, [
|
||||||
customerBranch: {
|
{ code: { contains: query, mode: "insensitive" } },
|
||||||
OR: [
|
{ workName: { contains: query, mode: "insensitive" } },
|
||||||
{ code: { contains: query, mode: "insensitive" } },
|
{
|
||||||
{ registerName: { contains: query, mode: "insensitive" } },
|
customerBranch: {
|
||||||
{ firstName: { contains: query, mode: "insensitive" } },
|
OR: [
|
||||||
{ firstNameEN: { contains: query, mode: "insensitive" } },
|
{ code: { contains: query, mode: "insensitive" } },
|
||||||
{ lastName: { contains: query, mode: "insensitive" } },
|
{ customerName: { contains: query, mode: "insensitive" } },
|
||||||
{ lastNameEN: { contains: query, mode: "insensitive" } },
|
{ firstName: { contains: query, mode: "insensitive" } },
|
||||||
],
|
{ firstNameEN: { contains: query, mode: "insensitive" } },
|
||||||
},
|
{ lastName: { contains: query, mode: "insensitive" } },
|
||||||
},
|
{ lastNameEN: { contains: query, mode: "insensitive" } },
|
||||||
]),
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
]) || []),
|
||||||
|
...(queryOrNot<Prisma.QuotationWhereInput[]>(!!pendingOnly, [
|
||||||
|
{
|
||||||
|
requestData: {
|
||||||
|
some: {
|
||||||
|
requestDataStatus: "Pending",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
requestData: { none: {} },
|
||||||
|
},
|
||||||
|
]) || []),
|
||||||
|
]
|
||||||
|
: undefined,
|
||||||
isDebitNote: false,
|
isDebitNote: false,
|
||||||
code,
|
code,
|
||||||
payCondition,
|
payCondition,
|
||||||
|
|
@ -650,23 +660,6 @@ export class LineController extends Controller {
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
: undefined,
|
: undefined,
|
||||||
AND: pendingOnly
|
|
||||||
? {
|
|
||||||
OR: [
|
|
||||||
{
|
|
||||||
requestData: {
|
|
||||||
some: {
|
|
||||||
requestDataStatus: "Pending",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
requestData: { none: {} },
|
|
||||||
},
|
|
||||||
],
|
|
||||||
}
|
|
||||||
: undefined,
|
|
||||||
...whereDateQuery(startDate, endDate),
|
|
||||||
} satisfies Prisma.QuotationWhereInput;
|
} satisfies Prisma.QuotationWhereInput;
|
||||||
|
|
||||||
const [result, total] = await prisma.$transaction([
|
const [result, total] = await prisma.$transaction([
|
||||||
|
|
|
||||||
|
|
@ -90,7 +90,7 @@ export class WebHookController extends Controller {
|
||||||
firstNameEN: true,
|
firstNameEN: true,
|
||||||
lastName: true,
|
lastName: true,
|
||||||
lastNameEN: true,
|
lastNameEN: true,
|
||||||
registerName: true,
|
customerName: true,
|
||||||
customer: {
|
customer: {
|
||||||
select: {
|
select: {
|
||||||
customerType: true,
|
customerType: true,
|
||||||
|
|
@ -133,13 +133,13 @@ export class WebHookController extends Controller {
|
||||||
let textData = "";
|
let textData = "";
|
||||||
|
|
||||||
if (dataEmployee.length > 0) {
|
if (dataEmployee.length > 0) {
|
||||||
const registerName =
|
const customerName =
|
||||||
dataEmployee[0]?.employee?.customerBranch?.registerName ?? "ไม่ระบุ";
|
dataEmployee[0]?.employee?.customerBranch?.customerName ?? "ไม่ระบุ";
|
||||||
const telephoneNo =
|
const telephoneNo =
|
||||||
dataEmployee[0]?.employee?.customerBranch?.customer.registeredBranch.telephoneNo ??
|
dataEmployee[0]?.employee?.customerBranch?.customer.registeredBranch.telephoneNo ??
|
||||||
"ไม่ระบุ";
|
"ไม่ระบุ";
|
||||||
|
|
||||||
const textEmployer = `เรียน คุณ${registerName}`;
|
const textEmployer = `เรียน คุณ${customerName}`;
|
||||||
const textAlert = "ขอแจ้งให้ทราบว่าหนังสือเดินทางของลูกจ้าง";
|
const textAlert = "ขอแจ้งให้ทราบว่าหนังสือเดินทางของลูกจ้าง";
|
||||||
const textAlert2 = "และจำเป็นต้องดำเนินการต่ออายุในเร็ว ๆ นี้";
|
const textAlert2 = "และจำเป็นต้องดำเนินการต่ออายุในเร็ว ๆ นี้";
|
||||||
const textExpDate =
|
const textExpDate =
|
||||||
|
|
|
||||||
|
|
@ -1,113 +0,0 @@
|
||||||
import {
|
|
||||||
Body,
|
|
||||||
Controller,
|
|
||||||
Delete,
|
|
||||||
Get,
|
|
||||||
Path,
|
|
||||||
Post,
|
|
||||||
Put,
|
|
||||||
Query,
|
|
||||||
Request,
|
|
||||||
Route,
|
|
||||||
Security,
|
|
||||||
Tags,
|
|
||||||
} from "tsoa";
|
|
||||||
import { RequestWithUser } from "../interfaces/user";
|
|
||||||
import prisma from "../db";
|
|
||||||
import { Prisma } from "@prisma/client";
|
|
||||||
import { queryOrNot } from "../utils/relation";
|
|
||||||
import { notFoundError } from "../utils/error";
|
|
||||||
|
|
||||||
type BusinessTypePayload = {
|
|
||||||
name: string;
|
|
||||||
nameEN: string;
|
|
||||||
};
|
|
||||||
|
|
||||||
@Route("api/v1/business-type")
|
|
||||||
@Tags("Business Type")
|
|
||||||
export class businessTypeController extends Controller {
|
|
||||||
@Get()
|
|
||||||
@Security("keycloak")
|
|
||||||
async getList(
|
|
||||||
@Request() req: RequestWithUser,
|
|
||||||
@Query() query: string = "",
|
|
||||||
@Query() page: number = 1,
|
|
||||||
@Query() pageSize: number = 30,
|
|
||||||
) {
|
|
||||||
const where = {
|
|
||||||
OR: queryOrNot<Prisma.BusinessTypeWhereInput[]>(query, [
|
|
||||||
{ name: { contains: query, mode: "insensitive" } },
|
|
||||||
{ nameEN: { contains: query, mode: "insensitive" } },
|
|
||||||
]),
|
|
||||||
} satisfies Prisma.BusinessTypeWhereInput;
|
|
||||||
|
|
||||||
const [result, total] = await prisma.$transaction([
|
|
||||||
prisma.businessType.findMany({
|
|
||||||
where,
|
|
||||||
take: pageSize,
|
|
||||||
skip: (page - 1) * pageSize,
|
|
||||||
}),
|
|
||||||
prisma.businessType.count({ where }),
|
|
||||||
]);
|
|
||||||
|
|
||||||
return { result, page, pageSize, total };
|
|
||||||
}
|
|
||||||
|
|
||||||
@Post()
|
|
||||||
@Security("keycloak")
|
|
||||||
async createBusinessType(@Request() req: RequestWithUser, @Body() body: BusinessTypePayload) {
|
|
||||||
return await prisma.businessType.create({
|
|
||||||
data: {
|
|
||||||
...body,
|
|
||||||
createdByUserId: req.user.sub,
|
|
||||||
updatedByUserId: req.user.sub,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
@Get(":businessTypeId")
|
|
||||||
@Security("keycloak")
|
|
||||||
async getBusinessTypeById(@Path() businessTypeId: string) {
|
|
||||||
return await prisma.businessType.findUnique({
|
|
||||||
where: { id: businessTypeId },
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
@Put(":businessTypeId")
|
|
||||||
@Security("keycloak")
|
|
||||||
async updateBusinessType(
|
|
||||||
@Request() req: RequestWithUser,
|
|
||||||
@Path() businessTypeId: string,
|
|
||||||
@Body() body: BusinessTypePayload,
|
|
||||||
) {
|
|
||||||
return await prisma.$transaction(async (tx) => {
|
|
||||||
const record = await tx.businessType.findUnique({
|
|
||||||
where: { id: businessTypeId },
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!record) throw notFoundError("BusinessType");
|
|
||||||
return await tx.businessType.update({
|
|
||||||
where: { id: businessTypeId },
|
|
||||||
data: {
|
|
||||||
...body,
|
|
||||||
updatedByUserId: req.user.sub,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
@Delete(":businessTypeId")
|
|
||||||
@Security("keycloak")
|
|
||||||
async deleteBusinessType(@Path() businessTypeId: string) {
|
|
||||||
return await prisma.$transaction(async (tx) => {
|
|
||||||
const record = await tx.businessType.findUnique({
|
|
||||||
where: { id: businessTypeId },
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!record) throw notFoundError("BusinessType");
|
|
||||||
return await tx.businessType.delete({
|
|
||||||
where: { id: businessTypeId },
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,25 +0,0 @@
|
||||||
import express from "express";
|
|
||||||
import { Controller, Get, Path, Request, Route } from "tsoa";
|
|
||||||
import { getFile } from "../utils/minio";
|
|
||||||
|
|
||||||
@Route("api/v1/troubleshooting")
|
|
||||||
export class TroubleshootingController extends Controller {
|
|
||||||
@Get()
|
|
||||||
async get(@Request() req: express.Request) {
|
|
||||||
return req.res?.redirect(await getFile(".troubleshooting/toc.json"));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Get("{category}/assets/{name}")
|
|
||||||
async getAsset(@Request() req: express.Request, @Path() category: string, @Path() name: string) {
|
|
||||||
return req.res?.redirect(await getFile(`.troubleshooting/${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(`.troubleshooting/${category}/${page}.md`));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,10 +1,6 @@
|
||||||
import prisma from "../db";
|
import prisma from "../db";
|
||||||
import config from "../config.json";
|
import config from "../config.json";
|
||||||
import { CustomerType, PayCondition } from "@prisma/client";
|
import { CustomerType, PayCondition } from "@prisma/client";
|
||||||
import { convertTemplate } from "../utils/string-template";
|
|
||||||
import { htmlToText } from "html-to-text";
|
|
||||||
import { JsonObject } from "@prisma/client/runtime/library";
|
|
||||||
import { precisionRound } from "../utils/arithmetic";
|
|
||||||
|
|
||||||
if (!process.env.FLOW_ACCOUNT_URL) throw new Error("Require FLOW_ACCOUNT_URL");
|
if (!process.env.FLOW_ACCOUNT_URL) throw new Error("Require FLOW_ACCOUNT_URL");
|
||||||
if (!process.env.FLOW_ACCOUNT_CLIENT_ID) throw new Error("Require FLOW_ACCOUNT_CLIENT_ID");
|
if (!process.env.FLOW_ACCOUNT_CLIENT_ID) throw new Error("Require FLOW_ACCOUNT_CLIENT_ID");
|
||||||
|
|
@ -236,29 +232,6 @@ const flowAccount = {
|
||||||
installments: true,
|
installments: true,
|
||||||
quotation: {
|
quotation: {
|
||||||
include: {
|
include: {
|
||||||
paySplit: true,
|
|
||||||
worker: {
|
|
||||||
select: {
|
|
||||||
employee: {
|
|
||||||
select: {
|
|
||||||
employeePassport: {
|
|
||||||
select: {
|
|
||||||
number: true,
|
|
||||||
},
|
|
||||||
orderBy: {
|
|
||||||
expireDate: "desc",
|
|
||||||
},
|
|
||||||
take: 1,
|
|
||||||
},
|
|
||||||
namePrefix: true,
|
|
||||||
firstName: true,
|
|
||||||
lastName: true,
|
|
||||||
firstNameEN: true,
|
|
||||||
lastNameEN: true,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
registeredBranch: {
|
registeredBranch: {
|
||||||
include: {
|
include: {
|
||||||
province: true,
|
province: true,
|
||||||
|
|
@ -289,58 +262,19 @@ const flowAccount = {
|
||||||
|
|
||||||
const quotation = data.quotation;
|
const quotation = data.quotation;
|
||||||
const customer = quotation.customerBranch;
|
const customer = quotation.customerBranch;
|
||||||
|
const product =
|
||||||
const summary = {
|
|
||||||
subTotal: 0,
|
|
||||||
discountAmount: 0,
|
|
||||||
vatableAmount: 0,
|
|
||||||
exemptAmount: 0,
|
|
||||||
vatAmount: 0,
|
|
||||||
grandTotal: 0,
|
|
||||||
};
|
|
||||||
|
|
||||||
const products = (
|
|
||||||
quotation.payCondition === PayCondition.BillFull ||
|
quotation.payCondition === PayCondition.BillFull ||
|
||||||
quotation.payCondition === PayCondition.Full
|
quotation.payCondition === PayCondition.Full
|
||||||
? quotation.productServiceList
|
? quotation.productServiceList
|
||||||
: quotation.productServiceList.filter((lhs) =>
|
: quotation.productServiceList.filter((lhs) =>
|
||||||
data.installments.some((rhs) => rhs.no === lhs.installmentNo),
|
data.installments.some((rhs) => rhs.no === lhs.installmentNo),
|
||||||
)
|
);
|
||||||
).map((v) => {
|
|
||||||
// TODO: Use product's VAT field (not implemented) instead.
|
|
||||||
const VAT_RATE = VAT_DEFAULT;
|
|
||||||
|
|
||||||
summary.subTotal +=
|
|
||||||
precisionRound(v.pricePerUnit * (1 + (v.vat > 0 ? VAT_RATE : 0))) * v.amount;
|
|
||||||
summary.discountAmount += v.discount;
|
|
||||||
|
|
||||||
const total =
|
|
||||||
precisionRound(v.pricePerUnit * (1 + (v.vat > 0 ? VAT_RATE : 0))) * v.amount -
|
|
||||||
(v.discount ?? 0);
|
|
||||||
|
|
||||||
if (v.vat > 0) {
|
|
||||||
summary.vatableAmount += precisionRound(total / (1 + VAT_RATE));
|
|
||||||
summary.vatAmount += v.vat;
|
|
||||||
} else {
|
|
||||||
summary.exemptAmount += total;
|
|
||||||
}
|
|
||||||
|
|
||||||
summary.grandTotal += total;
|
|
||||||
|
|
||||||
return {
|
|
||||||
type: ProductAndServiceType.ProductNonInv,
|
|
||||||
name: v.product.name,
|
|
||||||
pricePerUnit: precisionRound(v.pricePerUnit),
|
|
||||||
quantity: v.amount,
|
|
||||||
discountAmount: v.discount,
|
|
||||||
vatRate: v.vat === 0 ? 0 : Math.round(VAT_RATE * 100),
|
|
||||||
total,
|
|
||||||
};
|
|
||||||
});
|
|
||||||
|
|
||||||
const payload = {
|
const payload = {
|
||||||
contactCode: customer.code,
|
contactCode: customer.code,
|
||||||
contactName: customer.contactName || "-",
|
contactName:
|
||||||
|
(customer.customer.customerType === CustomerType.PERS
|
||||||
|
? [customer.firstName, customer.lastName].join(" ").trim()
|
||||||
|
: customer.registerName) || "-",
|
||||||
contactAddress: [
|
contactAddress: [
|
||||||
customer.address,
|
customer.address,
|
||||||
!!customer.moo ? "หมู่ " + customer.moo : null,
|
!!customer.moo ? "หมู่ " + customer.moo : null,
|
||||||
|
|
@ -349,10 +283,11 @@ const flowAccount = {
|
||||||
(customer.province?.id === "10" ? "แขวง" : "อำเภอ") + customer.subDistrict?.name,
|
(customer.province?.id === "10" ? "แขวง" : "อำเภอ") + customer.subDistrict?.name,
|
||||||
(customer.province?.id === "10" ? "เขต" : "ตำบล") + customer.district?.name,
|
(customer.province?.id === "10" ? "เขต" : "ตำบล") + customer.district?.name,
|
||||||
"จังหวัด" + customer.province?.name,
|
"จังหวัด" + customer.province?.name,
|
||||||
|
customer.subDistrict?.zipCode,
|
||||||
]
|
]
|
||||||
.filter(Boolean)
|
.filter(Boolean)
|
||||||
.join(" "),
|
.join(" "),
|
||||||
contactTaxId: customer.citizenId || customer.legalPersonNo || "-",
|
contactTaxId: customer.citizenId || customer.code,
|
||||||
contactBranch:
|
contactBranch:
|
||||||
(customer.customer.customerType === CustomerType.PERS
|
(customer.customer.customerType === CustomerType.PERS
|
||||||
? [customer.firstName, customer.lastName].join(" ").trim()
|
? [customer.firstName, customer.lastName].join(" ").trim()
|
||||||
|
|
@ -370,35 +305,36 @@ const flowAccount = {
|
||||||
isVat: true,
|
isVat: true,
|
||||||
|
|
||||||
useReceiptDeduction: false,
|
useReceiptDeduction: false,
|
||||||
useInlineVat: true,
|
|
||||||
|
|
||||||
discounPercentage: 0,
|
discounPercentage: 0,
|
||||||
discountAmount: quotation.totalDiscount,
|
discountAmount: quotation.totalDiscount,
|
||||||
|
|
||||||
subTotal: summary.subTotal,
|
subTotal:
|
||||||
totalAfterDiscount: summary.subTotal - summary.discountAmount,
|
quotation.payCondition === "BillSplitCustom" || quotation.payCondition === "SplitCustom"
|
||||||
vatableAmount: summary.vatableAmount,
|
? 0
|
||||||
exemptAmount: summary.exemptAmount,
|
: quotation.totalPrice,
|
||||||
vatAmount: summary.vatAmount,
|
totalAfterDiscount:
|
||||||
grandTotal: summary.grandTotal,
|
quotation.payCondition === "BillSplitCustom" || quotation.payCondition === "SplitCustom"
|
||||||
|
? 0
|
||||||
|
: quotation.finalPrice,
|
||||||
|
vatAmount:
|
||||||
|
quotation.payCondition === "BillSplitCustom" || quotation.payCondition === "SplitCustom"
|
||||||
|
? 0
|
||||||
|
: quotation.vat,
|
||||||
|
grandTotal:
|
||||||
|
quotation.payCondition === "BillSplitCustom" || quotation.payCondition === "SplitCustom"
|
||||||
|
? data.installments.reduce((a, c) => a + c.amount, 0)
|
||||||
|
: quotation.finalPrice,
|
||||||
|
|
||||||
remarks: htmlToText(
|
items: product.map((v) => ({
|
||||||
convertTemplate(quotation.remark ?? "", {
|
type: ProductAndServiceType.ProductNonInv,
|
||||||
"quotation-payment": {
|
name: v.product.name,
|
||||||
paymentType: quotation?.payCondition || "Full",
|
pricePerUnit: v.pricePerUnit,
|
||||||
amount: quotation.finalPrice,
|
quantity: v.amount,
|
||||||
installments: quotation?.paySplit,
|
discountAmount: v.discount,
|
||||||
},
|
total: (v.pricePerUnit - (v.discount || 0)) * v.amount + v.vat,
|
||||||
"quotation-labor": {
|
vatRate: v.vat === 0 ? 0 : Math.round(VAT_DEFAULT * 100),
|
||||||
name: quotation.worker.map(
|
})),
|
||||||
(v, i) =>
|
|
||||||
`${i + 1}. ` +
|
|
||||||
`${v.employee.employeePassport.length !== 0 ? v.employee.employeePassport[0].number + "_" : ""}${v.employee.namePrefix}. ${v.employee.firstNameEN ? `${v.employee.firstNameEN} ${v.employee.lastNameEN}` : `${v.employee.firstName} ${v.employee.lastName}`} `.toUpperCase(),
|
|
||||||
),
|
|
||||||
},
|
|
||||||
}),
|
|
||||||
),
|
|
||||||
items: products,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
return await flowAccountAPI.createReceipt(payload, false);
|
return await flowAccountAPI.createReceipt(payload, false);
|
||||||
|
|
@ -411,219 +347,6 @@ const flowAccount = {
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
},
|
},
|
||||||
|
|
||||||
// flowAccount GET Product list
|
|
||||||
async getProducts() {
|
|
||||||
const { token } = await flowAccountAPI.auth();
|
|
||||||
|
|
||||||
const res = await fetch(api + "/products", {
|
|
||||||
method: "GET",
|
|
||||||
headers: {
|
|
||||||
["Content-Type"]: `application/json`,
|
|
||||||
["Authorization"]: `Bearer ${token}`,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
return {
|
|
||||||
ok: res.ok,
|
|
||||||
status: res.status,
|
|
||||||
body: await res.json(),
|
|
||||||
};
|
|
||||||
},
|
|
||||||
|
|
||||||
// flowAccount GET Product by id
|
|
||||||
async getProductsById(recordId: string) {
|
|
||||||
const { token } = await flowAccountAPI.auth();
|
|
||||||
|
|
||||||
const res = await fetch(api + `/products/${recordId}`, {
|
|
||||||
method: "GET",
|
|
||||||
headers: {
|
|
||||||
["Content-Type"]: `application/json`,
|
|
||||||
["Authorization"]: `Bearer ${token}`,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
const data = await res.json();
|
|
||||||
|
|
||||||
return {
|
|
||||||
ok: res.ok,
|
|
||||||
status: res.status,
|
|
||||||
list: data.data.list,
|
|
||||||
total: data.data.total,
|
|
||||||
};
|
|
||||||
},
|
|
||||||
|
|
||||||
// flowAccount POST create Product
|
|
||||||
async createProducts(code: string, body: JsonObject) {
|
|
||||||
const { token } = await flowAccountAPI.auth();
|
|
||||||
|
|
||||||
const commonBody = {
|
|
||||||
productStructureType: null,
|
|
||||||
type: 3,
|
|
||||||
name: body.name,
|
|
||||||
sellDescription: body.detail,
|
|
||||||
sellVatType: 3,
|
|
||||||
buyPrice: body.serviceCharge,
|
|
||||||
buyVatType: body.serviceChargeVatIncluded ? 1 : 3,
|
|
||||||
buyDescription: body.detail,
|
|
||||||
};
|
|
||||||
|
|
||||||
const createProduct = async (name: string, price: any, vatIncluded: boolean) => {
|
|
||||||
try {
|
|
||||||
const res = await fetch(`${api}/products`, {
|
|
||||||
method: "POST",
|
|
||||||
headers: {
|
|
||||||
"Content-Type": "application/json",
|
|
||||||
Authorization: `Bearer ${token}`,
|
|
||||||
},
|
|
||||||
body: JSON.stringify({
|
|
||||||
...commonBody,
|
|
||||||
name,
|
|
||||||
sellPrice: price,
|
|
||||||
sellVatType: vatIncluded ? 1 : 3,
|
|
||||||
}),
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!res.ok) throw new Error(`HTTP ${res.status}: Failed to create product`);
|
|
||||||
|
|
||||||
const json = await res.json().catch(() => {
|
|
||||||
throw new Error("Invalid JSON response from FlowAccount API");
|
|
||||||
});
|
|
||||||
|
|
||||||
return json?.data?.list?.[0]?.id ?? null;
|
|
||||||
} catch (err) {
|
|
||||||
console.error("createProduct error:", err);
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const deleteProduct = async (id: string) => {
|
|
||||||
try {
|
|
||||||
await fetch(`${api}/products/${id}`, {
|
|
||||||
method: "DELETE",
|
|
||||||
headers: { Authorization: `Bearer ${token}` },
|
|
||||||
});
|
|
||||||
} catch (err) {
|
|
||||||
console.error("Rollback delete failed:", err);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const [sellResult, agentResult] = await Promise.allSettled([
|
|
||||||
createProduct(`${code} ${body.name}`, body.price, /true/.test(`${body.vatIncluded}`)),
|
|
||||||
createProduct(
|
|
||||||
`${code} ${body.name} (ราคาตัวแทน)`,
|
|
||||||
body.agentPrice,
|
|
||||||
/true/.test(`${body.agentPriceVatIncluded}`),
|
|
||||||
),
|
|
||||||
]);
|
|
||||||
|
|
||||||
const sellId = sellResult.status === "fulfilled" ? sellResult.value : null;
|
|
||||||
const agentId = agentResult.status === "fulfilled" ? agentResult.value : null;
|
|
||||||
|
|
||||||
// --- validation ---
|
|
||||||
if (!sellId && !agentId) {
|
|
||||||
throw new Error("FlowAccountProductError.BOTH_CREATION_FAILED");
|
|
||||||
}
|
|
||||||
if (!sellId && agentId) {
|
|
||||||
await deleteProduct(agentId);
|
|
||||||
throw new Error("FlowAccountProductError.SELL_PRICE_CREATION_FAILED");
|
|
||||||
}
|
|
||||||
if (sellId && !agentId) {
|
|
||||||
await deleteProduct(sellId);
|
|
||||||
throw new Error("FlowAccountProductError.AGENT_PRICE_CREATION_FAILED");
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
ok: true,
|
|
||||||
status: 200,
|
|
||||||
data: {
|
|
||||||
productIdSellPrice: sellId,
|
|
||||||
productIdAgentPrice: agentId,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
},
|
|
||||||
|
|
||||||
// flowAccount PUT edit Product
|
|
||||||
async editProducts(sellPriceId: String, agentPriceId: String, body: JsonObject) {
|
|
||||||
const { token } = await flowAccountAPI.auth();
|
|
||||||
|
|
||||||
const commonBody = {
|
|
||||||
productStructureType: null,
|
|
||||||
type: 3,
|
|
||||||
name: body.name,
|
|
||||||
sellDescription: body.detail,
|
|
||||||
sellVatType: 3,
|
|
||||||
buyPrice: body.serviceCharge,
|
|
||||||
buyVatType: body.serviceChargeVatIncluded ? 1 : 3,
|
|
||||||
buyDescription: body.detail,
|
|
||||||
};
|
|
||||||
|
|
||||||
const editProduct = async (id: String, name: String, price: any, vatIncluded: boolean) => {
|
|
||||||
try {
|
|
||||||
const res = await fetch(api + `/products/${id}`, {
|
|
||||||
method: "PUT",
|
|
||||||
headers: {
|
|
||||||
"Content-Type": "application/json",
|
|
||||||
Authorization: `Bearer ${token}`,
|
|
||||||
},
|
|
||||||
body: JSON.stringify({
|
|
||||||
...commonBody,
|
|
||||||
name: name,
|
|
||||||
sellPrice: price,
|
|
||||||
sellVatType: vatIncluded ? 1 : 3,
|
|
||||||
}),
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!res.ok) {
|
|
||||||
throw new Error(`Request failed with status ${res.status} ${res}`);
|
|
||||||
}
|
|
||||||
|
|
||||||
let json: any = null;
|
|
||||||
try {
|
|
||||||
json = await res.json();
|
|
||||||
} catch {
|
|
||||||
throw new Error("Response is not valid JSON");
|
|
||||||
}
|
|
||||||
|
|
||||||
return json?.data?.list?.[0]?.id ?? null;
|
|
||||||
} catch (err) {
|
|
||||||
console.error("createProduct error:", err);
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
await Promise.all([
|
|
||||||
editProduct(
|
|
||||||
sellPriceId,
|
|
||||||
`${body.code} ${body.name}`,
|
|
||||||
body.price,
|
|
||||||
/true/.test(`${body.vatIncluded}`),
|
|
||||||
),
|
|
||||||
editProduct(
|
|
||||||
agentPriceId,
|
|
||||||
`${body.code} ${body.name} (ราคาตัวแทน)`,
|
|
||||||
body.agentPrice,
|
|
||||||
/true/.test(`${body.agentPriceVatIncluded}`),
|
|
||||||
),
|
|
||||||
]);
|
|
||||||
},
|
|
||||||
|
|
||||||
// flowAccount DELETE Product
|
|
||||||
async deleteProduct(recordId: string) {
|
|
||||||
const { token } = await flowAccountAPI.auth();
|
|
||||||
|
|
||||||
const res = await fetch(api + `/products/${recordId}`, {
|
|
||||||
method: "DELETE",
|
|
||||||
headers: {
|
|
||||||
["Authorization"]: `Bearer ${token}`,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
return {
|
|
||||||
ok: res.ok,
|
|
||||||
status: res.status,
|
|
||||||
};
|
|
||||||
},
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export default flowAccount;
|
export default flowAccount;
|
||||||
|
|
|
||||||
|
|
@ -346,64 +346,6 @@ export async function removeUserRoles(userId: string, roles: { id: string; name:
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function getGroup(query: string) {
|
|
||||||
const res = await fetch(`${KC_URL}/admin/realms/${KC_REALM}/groups?${query}`, {
|
|
||||||
headers: {
|
|
||||||
authorization: `Bearer ${await getToken()}`,
|
|
||||||
"content-type": `application/json`,
|
|
||||||
},
|
|
||||||
method: "GET",
|
|
||||||
});
|
|
||||||
|
|
||||||
const dataMainGroup = await res.json();
|
|
||||||
const fetchSubGroups = async (group: any) => {
|
|
||||||
let fullSubGroup = await Promise.all(
|
|
||||||
group.subGroups.map((subGroupsData: any) => {
|
|
||||||
if (group.subGroupCount > 0) {
|
|
||||||
return fetchSubGroups(subGroupsData);
|
|
||||||
} else {
|
|
||||||
return {
|
|
||||||
id: subGroupsData.id,
|
|
||||||
name: subGroupsData.name,
|
|
||||||
path: subGroupsData.path,
|
|
||||||
subGroupCount: subGroupsData.subGroupCount,
|
|
||||||
subGroups: [],
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
return {
|
|
||||||
id: group.id,
|
|
||||||
name: group.name,
|
|
||||||
path: group.path,
|
|
||||||
subGroupCount: group.subGroupCount,
|
|
||||||
subGroups: fullSubGroup,
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
const fullMainGroup = await Promise.all(dataMainGroup.map(fetchSubGroups));
|
|
||||||
return fullMainGroup;
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function getGroupUser(userId: string) {
|
|
||||||
const res = await fetch(`${KC_URL}/admin/realms/${KC_REALM}/users/${userId}/groups`, {
|
|
||||||
headers: {
|
|
||||||
authorization: `Bearer ${await getToken()}`,
|
|
||||||
"content-type": `application/json`,
|
|
||||||
},
|
|
||||||
method: "GET",
|
|
||||||
});
|
|
||||||
|
|
||||||
const data = await res.json();
|
|
||||||
return data.map((item: any) => {
|
|
||||||
return {
|
|
||||||
id: item.id,
|
|
||||||
name: item.name,
|
|
||||||
path: item.path,
|
|
||||||
};
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
createUser,
|
createUser,
|
||||||
listRole,
|
listRole,
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,6 @@ import dayjs from "dayjs";
|
||||||
import { CronJob } from "cron";
|
import { CronJob } from "cron";
|
||||||
|
|
||||||
import prisma from "../db";
|
import prisma from "../db";
|
||||||
import { Prisma } from "@prisma/client";
|
|
||||||
|
|
||||||
const jobs = [
|
const jobs = [
|
||||||
CronJob.from({
|
CronJob.from({
|
||||||
|
|
@ -39,162 +38,6 @@ const jobs = [
|
||||||
.catch((e) => console.error("[ERR]: Update expired quotation status, FAILED.", e));
|
.catch((e) => console.error("[ERR]: Update expired quotation status, FAILED.", e));
|
||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
CronJob.from({
|
|
||||||
cronTime: "0 0 0 * * *",
|
|
||||||
runOnInit: true,
|
|
||||||
onTick: async () => {
|
|
||||||
const employeeExpireData = await prisma.employee.findMany({
|
|
||||||
include: {
|
|
||||||
employeePassport: {
|
|
||||||
orderBy: {
|
|
||||||
expireDate: "desc",
|
|
||||||
},
|
|
||||||
take: 1,
|
|
||||||
},
|
|
||||||
customerBranch: {
|
|
||||||
include: {
|
|
||||||
customer: true,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
quotationWorker: {
|
|
||||||
include: {
|
|
||||||
quotation: true,
|
|
||||||
},
|
|
||||||
orderBy: {
|
|
||||||
createdAt: "desc",
|
|
||||||
},
|
|
||||||
take: 1,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
where: {
|
|
||||||
employeePassport: {
|
|
||||||
some: {
|
|
||||||
expireDate: dayjs().add(90, "day").toDate(),
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
await Promise.all(
|
|
||||||
employeeExpireData.map(async (record) => {
|
|
||||||
const fullName = `${record.namePrefix}.${record.firstNameEN} ${record.lastNameEN}`;
|
|
||||||
const expireDate = `${dayjs(record.employeePassport[0].expireDate).format("DD/MM")}/${dayjs(record.employeePassport[0].expireDate).year() + 543}`;
|
|
||||||
const textDetail = `ลูกจ้างรหัส / code : ${record.code} ชื่อ : ${fullName} หนังสือเดินทางจะหมดอายุในวันที่ ${expireDate}`;
|
|
||||||
const duplicateText = await prisma.notification.findFirst({
|
|
||||||
where: {
|
|
||||||
detail: textDetail,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
const dataNotification: Prisma.NotificationCreateArgs["data"] = {
|
|
||||||
title: "หนังสือเดินทางลูกจ้างหมดอายุ / Employee Passport Expire",
|
|
||||||
detail: textDetail,
|
|
||||||
};
|
|
||||||
|
|
||||||
if (record.quotationWorker && record.quotationWorker.length > 0) {
|
|
||||||
dataNotification.receiverId = record.quotationWorker[0].quotation.updatedByUserId;
|
|
||||||
dataNotification.registeredBranchId =
|
|
||||||
record.quotationWorker[0].quotation.registeredBranchId;
|
|
||||||
} else {
|
|
||||||
(dataNotification.groupReceiver = {
|
|
||||||
create: [{ name: "sale" }, { name: "head_of_sale" }],
|
|
||||||
}),
|
|
||||||
(dataNotification.registeredBranchId =
|
|
||||||
record.customerBranch.customer.registeredBranchId);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!duplicateText) {
|
|
||||||
await prisma.notification
|
|
||||||
.create({
|
|
||||||
data: dataNotification,
|
|
||||||
})
|
|
||||||
.then(() => console.log("[INFO]: Create notification employee passport expired, OK."))
|
|
||||||
.catch((e) =>
|
|
||||||
console.error("[ERR]: Create notification employee passport expired, FAILED.", e),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
},
|
|
||||||
}),
|
|
||||||
CronJob.from({
|
|
||||||
cronTime: "0 0 0 * * *",
|
|
||||||
runOnInit: true,
|
|
||||||
onTick: async () => {
|
|
||||||
const employeeVisaData = await prisma.employee.findMany({
|
|
||||||
include: {
|
|
||||||
employeeVisa: {
|
|
||||||
orderBy: {
|
|
||||||
expireDate: "desc",
|
|
||||||
},
|
|
||||||
take: 1,
|
|
||||||
},
|
|
||||||
customerBranch: {
|
|
||||||
include: {
|
|
||||||
customer: true,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
quotationWorker: {
|
|
||||||
include: {
|
|
||||||
quotation: true,
|
|
||||||
},
|
|
||||||
orderBy: {
|
|
||||||
createdAt: "desc",
|
|
||||||
},
|
|
||||||
take: 1,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
where: {
|
|
||||||
employeeVisa: {
|
|
||||||
some: {
|
|
||||||
expireDate: dayjs().add(90, "day").toDate(),
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
await Promise.all(
|
|
||||||
employeeVisaData.map(async (record) => {
|
|
||||||
const fullName = `${record.namePrefix}.${record.firstNameEN} ${record.lastNameEN}`;
|
|
||||||
const expireDate = `${dayjs(record.employeeVisa[0].expireDate).format("DD/MM")}/${dayjs(record.employeeVisa[0].expireDate).year() + 543}`;
|
|
||||||
const textDetail = `ลูกจ้างรหัส / code : ${record.code} ชื่อ : ${fullName} ข้อมูลการตรวจลงตราจะหมดอายุในวันที่ ${expireDate}`;
|
|
||||||
const duplicateText = await prisma.notification.findFirst({
|
|
||||||
where: {
|
|
||||||
detail: textDetail,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
const dataNotification: Prisma.NotificationCreateArgs["data"] = {
|
|
||||||
title: "ข้อมูลการตรวจลงตราลูกจ้างหมดอายุ / Employee Visa Expire",
|
|
||||||
detail: textDetail,
|
|
||||||
};
|
|
||||||
|
|
||||||
if (record.quotationWorker && record.quotationWorker.length > 0) {
|
|
||||||
dataNotification.receiverId = record.quotationWorker[0].quotation.updatedByUserId;
|
|
||||||
dataNotification.registeredBranchId =
|
|
||||||
record.quotationWorker[0].quotation.registeredBranchId;
|
|
||||||
} else {
|
|
||||||
(dataNotification.groupReceiver = {
|
|
||||||
create: [{ name: "sale" }, { name: "head_of_sale" }],
|
|
||||||
}),
|
|
||||||
(dataNotification.registeredBranchId =
|
|
||||||
record.customerBranch.customer.registeredBranchId);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!duplicateText) {
|
|
||||||
await prisma.notification
|
|
||||||
.create({
|
|
||||||
data: dataNotification,
|
|
||||||
})
|
|
||||||
.then(() => console.log("[INFO]: Create notification employee visa expired, OK."))
|
|
||||||
.catch((e) =>
|
|
||||||
console.error("[ERR]: Create notification employee visa expired, FAILED.", e),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
},
|
|
||||||
}),
|
|
||||||
];
|
];
|
||||||
|
|
||||||
export function initSchedule() {
|
export function initSchedule() {
|
||||||
|
|
|
||||||
|
|
@ -10,35 +10,26 @@ export function connectOrDisconnect(id?: string | null) {
|
||||||
|
|
||||||
export function whereAddressQuery(query: string) {
|
export function whereAddressQuery(query: string) {
|
||||||
return [
|
return [
|
||||||
{ address: { contains: query, mode: "insensitive" } },
|
{ address: { contains: query } },
|
||||||
{ addressEN: { contains: query, mode: "insensitive" } },
|
{ addressEN: { contains: query } },
|
||||||
{ soi: { contains: query, mode: "insensitive" } },
|
{ soi: { contains: query } },
|
||||||
{ soiEN: { contains: query, mode: "insensitive" } },
|
{ soiEN: { contains: query } },
|
||||||
{ moo: { contains: query, mode: "insensitive" } },
|
{ moo: { contains: query } },
|
||||||
{ mooEN: { contains: query, mode: "insensitive" } },
|
{ mooEN: { contains: query } },
|
||||||
{ street: { contains: query, mode: "insensitive" } },
|
{ street: { contains: query } },
|
||||||
{ streetEN: { contains: query, mode: "insensitive" } },
|
{ streetEN: { contains: query } },
|
||||||
{ province: { name: { contains: query, mode: "insensitive" } } },
|
{ province: { name: { contains: query } } },
|
||||||
{ province: { nameEN: { contains: query, mode: "insensitive" } } },
|
{ province: { nameEN: { contains: query } } },
|
||||||
{ district: { name: { contains: query, mode: "insensitive" } } },
|
{ district: { name: { contains: query } } },
|
||||||
{ district: { nameEN: { contains: query, mode: "insensitive" } } },
|
{ district: { nameEN: { contains: query } } },
|
||||||
{ subDistrict: { name: { contains: query, mode: "insensitive" } } },
|
{ subDistrict: { name: { contains: query } } },
|
||||||
{ subDistrict: { nameEN: { contains: query, mode: "insensitive" } } },
|
{ subDistrict: { nameEN: { contains: query } } },
|
||||||
{ subDistrict: { zipCode: { contains: query, mode: "insensitive" } } },
|
{ subDistrict: { zipCode: { contains: query } } },
|
||||||
] as const;
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
export function queryOrNot<T>(query: any, where: T): T | undefined;
|
export function queryOrNot<T>(query: string | boolean, where: T): T | undefined;
|
||||||
export function queryOrNot<T, U>(query: any, where: T, fallback: U): T | U;
|
export function queryOrNot<T, U>(query: string | boolean, where: T, fallback: U): T | U;
|
||||||
export function queryOrNot<T, U>(query: any, where: T, fallback?: U) {
|
export function queryOrNot<T, U>(query: string | boolean, where: T, fallback?: U) {
|
||||||
return !!query ? where : fallback;
|
return !!query ? where : fallback;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function whereDateQuery(startDate: Date | undefined, endDate: Date | undefined) {
|
|
||||||
return {
|
|
||||||
createdAt: {
|
|
||||||
gte: startDate,
|
|
||||||
lte: endDate,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
|
||||||
|
|
@ -1,105 +0,0 @@
|
||||||
import Excel from "exceljs";
|
|
||||||
|
|
||||||
export default class spreadsheet {
|
|
||||||
static async readCsv() {
|
|
||||||
// TODO: read csv
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* This function read data from excel file.
|
|
||||||
*
|
|
||||||
* @param buffer - Excel file.
|
|
||||||
* @param opts.header - Interprets the first row as the names of the fields.
|
|
||||||
* @param opts.worksheet - Specifies the worksheet to read. Can be the worksheet's name or its 1-based index.
|
|
||||||
*
|
|
||||||
* @returns
|
|
||||||
*/
|
|
||||||
static async readExcel<T extends unknown>(
|
|
||||||
buffer: Excel.Buffer,
|
|
||||||
opts?: { header?: boolean; worksheet?: number | string },
|
|
||||||
): Promise<T[]> {
|
|
||||||
const workbook = new Excel.Workbook();
|
|
||||||
await workbook.xlsx.load(buffer);
|
|
||||||
const worksheet = workbook.getWorksheet(opts?.worksheet ?? 1);
|
|
||||||
|
|
||||||
if (!worksheet) return [];
|
|
||||||
|
|
||||||
const header: Record<number, string | number> = {};
|
|
||||||
const values: any[] = [];
|
|
||||||
|
|
||||||
worksheet.eachRow((row, rowId) => {
|
|
||||||
if (rowId === 1 && opts?.header !== false) {
|
|
||||||
row.eachCell((cell, cellId) => {
|
|
||||||
if (typeof cell.value === "string") {
|
|
||||||
header[cellId] = nameValue(cell.value);
|
|
||||||
} else {
|
|
||||||
header[cellId] = cellId.toString();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
const data: Record<string | number, Excel.CellValue> = {};
|
|
||||||
row.eachCell((cell, cellId) => {
|
|
||||||
data[opts?.header !== false ? header[cellId] : cellId - 1] = cell.value;
|
|
||||||
});
|
|
||||||
values.push(opts?.header !== false ? data : Object.values(data));
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
return values;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function nameValue(value: string) {
|
|
||||||
let code: string;
|
|
||||||
switch (value) {
|
|
||||||
case "ชื่อสินค้าและบริการ":
|
|
||||||
code = "name";
|
|
||||||
break;
|
|
||||||
case "ระยะเวลาดำเนินการ":
|
|
||||||
code = "process";
|
|
||||||
break;
|
|
||||||
case "ประเภทค่าใช้จ่าย":
|
|
||||||
code = "expenseType";
|
|
||||||
break;
|
|
||||||
case "รายละเอียด":
|
|
||||||
code = "detail";
|
|
||||||
break;
|
|
||||||
case "หมายเหตุ":
|
|
||||||
code = "remark";
|
|
||||||
break;
|
|
||||||
case "ใช้งานร่วมกัน":
|
|
||||||
code = "shared";
|
|
||||||
break;
|
|
||||||
case "คำนวณภาษีราคาขาย":
|
|
||||||
code = "calcVat";
|
|
||||||
break;
|
|
||||||
case "รวม VAT ราคาขาย":
|
|
||||||
code = "vatIncluded";
|
|
||||||
break;
|
|
||||||
case "ราคาต่อหน่วย (บาท) ราคาขาย":
|
|
||||||
code = "price";
|
|
||||||
break;
|
|
||||||
case "คำนวณภาษีราคาตัวแทน":
|
|
||||||
code = "agentPriceCalcVat";
|
|
||||||
break;
|
|
||||||
case "รวม VAT ราคาตัวแทน":
|
|
||||||
code = "agentPriceVatIncluded";
|
|
||||||
break;
|
|
||||||
case "ราคาต่อหน่วย (บาท) ราคาตัวแทน":
|
|
||||||
code = "agentPrice";
|
|
||||||
break;
|
|
||||||
case "คำนวณภาษีราคาดำเนินการ":
|
|
||||||
code = "serviceChargeCalcVat";
|
|
||||||
break;
|
|
||||||
case "รวม VAT ราคาดำเนินการ":
|
|
||||||
code = "serviceChargeVatIncluded";
|
|
||||||
break;
|
|
||||||
case "ราคาต่อหน่วย (บาท) ราคาดำเนินการ":
|
|
||||||
code = "serviceCharge";
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
code = "code";
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
return code;
|
|
||||||
}
|
|
||||||
|
|
@ -1,67 +0,0 @@
|
||||||
export function formatNumberDecimal(num: number, point: number = 2): string {
|
|
||||||
return (num || 0).toLocaleString("eng", {
|
|
||||||
minimumFractionDigits: point,
|
|
||||||
maximumFractionDigits: point,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
const templates = {
|
|
||||||
"quotation-labor": {
|
|
||||||
converter: (context?: { name: string[] }) => {
|
|
||||||
return context?.name.join("<br />") || "";
|
|
||||||
},
|
|
||||||
},
|
|
||||||
"quotation-payment": {
|
|
||||||
converter: (context?: {
|
|
||||||
paymentType: "Full" | "Split" | "SplitCustom" | "BillFull" | "BillSplit" | "BillSplitCustom";
|
|
||||||
|
|
||||||
amount?: number;
|
|
||||||
installments?: {
|
|
||||||
no: number;
|
|
||||||
amount: number;
|
|
||||||
}[];
|
|
||||||
}) => {
|
|
||||||
if (context?.paymentType === "Full") {
|
|
||||||
return [
|
|
||||||
"**** เงื่อนไขเพิ่มเติม",
|
|
||||||
"- เงื่อนไขการชำระเงิน แบบเต็มจำนวน",
|
|
||||||
` จำนวน ${formatNumberDecimal(context?.amount || 0, 2)}`,
|
|
||||||
].join("<br/>");
|
|
||||||
} else {
|
|
||||||
return [
|
|
||||||
"**** เงื่อนไขเพิ่มเติม",
|
|
||||||
`- เงื่อนไขการชำระเงิน แบบแบ่งจ่าย${context?.paymentType === "SplitCustom" ? " กำหนดเอง " : " "}${context?.installments?.length} งวด`,
|
|
||||||
...(context?.installments?.map(
|
|
||||||
(v) => ` งวดที่ ${v.no} จำนวน ${formatNumberDecimal(v.amount, 2)}`,
|
|
||||||
) || []),
|
|
||||||
].join("<br />");
|
|
||||||
}
|
|
||||||
},
|
|
||||||
},
|
|
||||||
} as const;
|
|
||||||
|
|
||||||
type Template = typeof templates;
|
|
||||||
type TemplateName = keyof Template;
|
|
||||||
type TemplateContext = {
|
|
||||||
[key in TemplateName]?: Parameters<Template[key]["converter"]>[0];
|
|
||||||
};
|
|
||||||
|
|
||||||
export function convertTemplate(
|
|
||||||
text: string,
|
|
||||||
context?: TemplateContext,
|
|
||||||
templateUse?: TemplateName[],
|
|
||||||
) {
|
|
||||||
let ret = text;
|
|
||||||
|
|
||||||
for (const [name, template] of Object.entries(templates)) {
|
|
||||||
if (templateUse && !templateUse.includes(name as TemplateName)) continue;
|
|
||||||
ret = ret.replace(
|
|
||||||
new RegExp("\\#\\[" + name.replaceAll("-", "\\-") + "\\]", "g"),
|
|
||||||
typeof template.converter === "function"
|
|
||||||
? template.converter(context?.[name as TemplateName] as any)
|
|
||||||
: template.converter,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
return ret;
|
|
||||||
}
|
|
||||||
|
|
@ -62,90 +62,85 @@ export async function initThailandAreaDatabase() {
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
await prisma.$transaction(
|
await prisma.$transaction(async (tx) => {
|
||||||
async (tx) => {
|
const meta = {
|
||||||
const meta = {
|
createdBy: null,
|
||||||
createdBy: null,
|
createdAt: new Date(),
|
||||||
createdAt: new Date(),
|
updatedBy: null,
|
||||||
updatedBy: null,
|
updatedAt: new Date(),
|
||||||
updatedAt: new Date(),
|
};
|
||||||
};
|
|
||||||
|
|
||||||
await Promise.all(
|
await Promise.all(
|
||||||
splitChunk(province, 1000, async (r) => {
|
splitChunk(province, 1000, async (r) => {
|
||||||
return await tx.$kysely
|
return await tx.$kysely
|
||||||
.insertInto("Province")
|
.insertInto("Province")
|
||||||
.columns(["id", "name", "nameEN", "createdBy", "createdAt", "updatedBy", "updatedAt"])
|
.columns(["id", "name", "nameEN", "createdBy", "createdAt", "updatedBy", "updatedAt"])
|
||||||
.values(r.map((v) => ({ ...v, ...meta })))
|
.values(r.map((v) => ({ ...v, ...meta })))
|
||||||
.onConflict((oc) =>
|
.onConflict((oc) =>
|
||||||
oc.column("id").doUpdateSet({
|
oc.column("id").doUpdateSet({
|
||||||
name: (eb) => eb.ref("excluded.name"),
|
name: (eb) => eb.ref("excluded.name"),
|
||||||
nameEN: (eb) => eb.ref("excluded.nameEN"),
|
nameEN: (eb) => eb.ref("excluded.nameEN"),
|
||||||
updatedAt: (eb) => eb.ref("excluded.updatedAt"),
|
updatedAt: (eb) => eb.ref("excluded.updatedAt"),
|
||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
.execute();
|
.execute();
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
|
|
||||||
await Promise.all(
|
await Promise.all(
|
||||||
splitChunk(district, 2000, async (r) => {
|
splitChunk(district, 2000, async (r) => {
|
||||||
return await tx.$kysely
|
return await tx.$kysely
|
||||||
.insertInto("District")
|
.insertInto("District")
|
||||||
.columns([
|
.columns([
|
||||||
"id",
|
"id",
|
||||||
"name",
|
"name",
|
||||||
"nameEN",
|
"nameEN",
|
||||||
"provinceId",
|
"provinceId",
|
||||||
"createdBy",
|
"createdBy",
|
||||||
"createdAt",
|
"createdAt",
|
||||||
"updatedBy",
|
"updatedBy",
|
||||||
"updatedAt",
|
"updatedAt",
|
||||||
])
|
])
|
||||||
.values(r.map((v) => ({ ...v, ...meta })))
|
.values(r.map((v) => ({ ...v, ...meta })))
|
||||||
.onConflict((oc) =>
|
.onConflict((oc) =>
|
||||||
oc.column("id").doUpdateSet({
|
oc.column("id").doUpdateSet({
|
||||||
name: (eb) => eb.ref("excluded.name"),
|
name: (eb) => eb.ref("excluded.name"),
|
||||||
nameEN: (eb) => eb.ref("excluded.nameEN"),
|
nameEN: (eb) => eb.ref("excluded.nameEN"),
|
||||||
provinceId: (eb) => eb.ref("excluded.provinceId"),
|
provinceId: (eb) => eb.ref("excluded.provinceId"),
|
||||||
updatedAt: (eb) => eb.ref("excluded.updatedAt"),
|
updatedAt: (eb) => eb.ref("excluded.updatedAt"),
|
||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
.execute();
|
.execute();
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
|
|
||||||
await Promise.all(
|
await Promise.all(
|
||||||
splitChunk(subDistrict, 1000, async (r) => {
|
splitChunk(subDistrict, 1000, async (r) => {
|
||||||
return await tx.$kysely
|
return await tx.$kysely
|
||||||
.insertInto("SubDistrict")
|
.insertInto("SubDistrict")
|
||||||
.columns([
|
.columns([
|
||||||
"id",
|
"id",
|
||||||
"name",
|
"name",
|
||||||
"nameEN",
|
"nameEN",
|
||||||
"districtId",
|
"districtId",
|
||||||
"createdBy",
|
"createdBy",
|
||||||
"createdAt",
|
"createdAt",
|
||||||
"updatedBy",
|
"updatedBy",
|
||||||
"updatedAt",
|
"updatedAt",
|
||||||
])
|
])
|
||||||
.values(r.map((v) => ({ ...v, ...meta })))
|
.values(r.map((v) => ({ ...v, ...meta })))
|
||||||
.onConflict((oc) =>
|
.onConflict((oc) =>
|
||||||
oc.column("id").doUpdateSet({
|
oc.column("id").doUpdateSet({
|
||||||
name: (eb) => eb.ref("excluded.name"),
|
name: (eb) => eb.ref("excluded.name"),
|
||||||
nameEN: (eb) => eb.ref("excluded.nameEN"),
|
nameEN: (eb) => eb.ref("excluded.nameEN"),
|
||||||
districtId: (eb) => eb.ref("excluded.districtId"),
|
districtId: (eb) => eb.ref("excluded.districtId"),
|
||||||
updatedAt: (eb) => eb.ref("excluded.updatedAt"),
|
updatedAt: (eb) => eb.ref("excluded.updatedAt"),
|
||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
.execute();
|
.execute();
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
},
|
});
|
||||||
{
|
|
||||||
timeout: 15_000,
|
|
||||||
},
|
|
||||||
);
|
|
||||||
|
|
||||||
console.log("[INFO]: Sync thailand province, district and subdistrict, OK.");
|
console.log("[INFO]: Sync thailand province, district and subdistrict, OK.");
|
||||||
}
|
}
|
||||||
|
|
@ -175,72 +170,67 @@ export async function initEmploymentOffice() {
|
||||||
|
|
||||||
const list = await prisma.province.findMany();
|
const list = await prisma.province.findMany();
|
||||||
|
|
||||||
await prisma.$transaction(
|
await prisma.$transaction(async (tx) => {
|
||||||
async (tx) => {
|
await Promise.all(
|
||||||
await Promise.all(
|
list
|
||||||
list
|
.map(async (province) => {
|
||||||
.map(async (province) => {
|
if (special[province.id]) {
|
||||||
if (special[province.id]) {
|
await tx.employmentOffice.deleteMany({
|
||||||
await tx.employmentOffice.deleteMany({
|
where: { provinceId: province.id, district: { none: {} } },
|
||||||
where: { provinceId: province.id, district: { none: {} } },
|
|
||||||
});
|
|
||||||
return await Promise.all(
|
|
||||||
Object.entries(special[province.id]).map(async ([key, val]) => {
|
|
||||||
const id = province.id + "-" + key.padStart(2, "0");
|
|
||||||
return tx.employmentOffice.upsert({
|
|
||||||
where: { id },
|
|
||||||
create: {
|
|
||||||
id,
|
|
||||||
name: nameSpecial(province.name, +key),
|
|
||||||
nameEN: nameSpecialEN(province.nameEN, +key),
|
|
||||||
provinceId: province.id,
|
|
||||||
district: {
|
|
||||||
createMany: {
|
|
||||||
data: val.map((districtId) => ({ districtId })),
|
|
||||||
skipDuplicates: true,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
update: {
|
|
||||||
id,
|
|
||||||
name: nameSpecial(province.name, +key),
|
|
||||||
nameEN: nameSpecialEN(province.nameEN, +key),
|
|
||||||
provinceId: province.id,
|
|
||||||
district: {
|
|
||||||
deleteMany: { districtId: { notIn: val } },
|
|
||||||
createMany: {
|
|
||||||
data: val.map((districtId) => ({ districtId })),
|
|
||||||
skipDuplicates: true,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
});
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
return tx.employmentOffice.upsert({
|
|
||||||
where: { id: province.id },
|
|
||||||
create: {
|
|
||||||
id: province.id,
|
|
||||||
name: name(province.name),
|
|
||||||
nameEN: nameEN(province.nameEN),
|
|
||||||
provinceId: province.id,
|
|
||||||
},
|
|
||||||
update: {
|
|
||||||
name: name(province.name),
|
|
||||||
nameEN: nameEN(province.nameEN),
|
|
||||||
provinceId: province.id,
|
|
||||||
},
|
|
||||||
});
|
});
|
||||||
})
|
return await Promise.all(
|
||||||
.flat(),
|
Object.entries(special[province.id]).map(async ([key, val]) => {
|
||||||
);
|
const id = province.id + "-" + key.padStart(2, "0");
|
||||||
},
|
return tx.employmentOffice.upsert({
|
||||||
{
|
where: { id },
|
||||||
timeout: 15_000,
|
create: {
|
||||||
},
|
id,
|
||||||
);
|
name: nameSpecial(province.name, +key),
|
||||||
|
nameEN: nameSpecialEN(province.nameEN, +key),
|
||||||
|
provinceId: province.id,
|
||||||
|
district: {
|
||||||
|
createMany: {
|
||||||
|
data: val.map((districtId) => ({ districtId })),
|
||||||
|
skipDuplicates: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
update: {
|
||||||
|
id,
|
||||||
|
name: nameSpecial(province.name, +key),
|
||||||
|
nameEN: nameSpecialEN(province.nameEN, +key),
|
||||||
|
provinceId: province.id,
|
||||||
|
district: {
|
||||||
|
deleteMany: { districtId: { notIn: val } },
|
||||||
|
createMany: {
|
||||||
|
data: val.map((districtId) => ({ districtId })),
|
||||||
|
skipDuplicates: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return tx.employmentOffice.upsert({
|
||||||
|
where: { id: province.id },
|
||||||
|
create: {
|
||||||
|
id: province.id,
|
||||||
|
name: name(province.name),
|
||||||
|
nameEN: nameEN(province.nameEN),
|
||||||
|
provinceId: province.id,
|
||||||
|
},
|
||||||
|
update: {
|
||||||
|
name: name(province.name),
|
||||||
|
nameEN: nameEN(province.nameEN),
|
||||||
|
provinceId: province.id,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
})
|
||||||
|
.flat(),
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
console.log("[INFO]: Sync employment office, OK.");
|
console.log("[INFO]: Sync employment office, OK.");
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,323 +0,0 @@
|
||||||
import { afterAll, beforeAll, describe, expect, it, onTestFailed } from "vitest";
|
|
||||||
import { PrismaClient } from "@prisma/client";
|
|
||||||
|
|
||||||
import { isDateString } from "./lib";
|
|
||||||
|
|
||||||
const prisma = new PrismaClient({
|
|
||||||
datasourceUrl: process.env.TEST_DATABASE_URL || process.env.DATABASE_URL,
|
|
||||||
});
|
|
||||||
const baseUrl = process.env.TEST_BASE_URL || "http://localhost";
|
|
||||||
const record: Record<string, any> = {
|
|
||||||
code: "CMT",
|
|
||||||
taxNo: "1052299402851",
|
|
||||||
name: "Chamomind",
|
|
||||||
nameEN: "Chamomind",
|
|
||||||
email: "contact@chamomind.com",
|
|
||||||
lineId: "@chamomind",
|
|
||||||
telephoneNo: "0988929248",
|
|
||||||
contactName: "John",
|
|
||||||
webUrl: "https://chamomind.com",
|
|
||||||
latitude: "",
|
|
||||||
longitude: "",
|
|
||||||
virtual: false,
|
|
||||||
permitNo: "1135182804792",
|
|
||||||
permitIssueDate: "2025-01-01T00:00:00.000Z",
|
|
||||||
permitExpireDate: "2030-01-01T00:00:00.000Z",
|
|
||||||
address: "11/3",
|
|
||||||
addressEN: "11/3",
|
|
||||||
soi: "1",
|
|
||||||
soiEN: "1",
|
|
||||||
moo: "2",
|
|
||||||
mooEN: "2",
|
|
||||||
street: "Straight",
|
|
||||||
streetEN: "Straight",
|
|
||||||
provinceId: "50",
|
|
||||||
districtId: "5001",
|
|
||||||
subDistrictId: "500107",
|
|
||||||
};
|
|
||||||
const recordList: Record<string, any>[] = [];
|
|
||||||
|
|
||||||
let token: string;
|
|
||||||
|
|
||||||
beforeAll(async () => {
|
|
||||||
const body = new URLSearchParams();
|
|
||||||
|
|
||||||
body.append("grant_type", "password");
|
|
||||||
body.append("client_id", "app");
|
|
||||||
body.append("username", process.env.TEST_USERNAME || "");
|
|
||||||
body.append("password", process.env.TEST_PASSWORD || "");
|
|
||||||
body.append("scope", "openid");
|
|
||||||
|
|
||||||
const res = await fetch(
|
|
||||||
process.env.KC_URL + "/realms/" + process.env.KC_REALM + "/protocol/openid-connect/token",
|
|
||||||
{
|
|
||||||
method: "POST",
|
|
||||||
body: body,
|
|
||||||
},
|
|
||||||
);
|
|
||||||
|
|
||||||
expect(res.ok).toBe(true);
|
|
||||||
|
|
||||||
await res.json().then((data) => {
|
|
||||||
token = data["access_token"];
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
afterAll(async () => {
|
|
||||||
if (!record["id"]) return;
|
|
||||||
|
|
||||||
await prisma.branch.deleteMany({
|
|
||||||
where: { id: { in: [record, ...recordList].map((v) => v["id"]) } },
|
|
||||||
});
|
|
||||||
await prisma.runningNo.deleteMany({
|
|
||||||
where: {
|
|
||||||
key: { in: [record, ...recordList].map((v) => `MAIN_BRANCH_${v["code"].slice(0, -5)}`) },
|
|
||||||
},
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe("branch management", () => {
|
|
||||||
it("create branch without required fields", async () => {
|
|
||||||
const requiredFields = [
|
|
||||||
"taxNo",
|
|
||||||
"name",
|
|
||||||
"nameEN",
|
|
||||||
"permitNo",
|
|
||||||
"telephoneNo",
|
|
||||||
"address",
|
|
||||||
"addressEN",
|
|
||||||
"email",
|
|
||||||
];
|
|
||||||
onTestFailed(() => console.log("Field:", requiredFields, "is required."));
|
|
||||||
|
|
||||||
for await (const field of requiredFields) {
|
|
||||||
const res = await fetch(baseUrl + "/api/v1/branch", {
|
|
||||||
method: "POST",
|
|
||||||
headers: {
|
|
||||||
["Content-Type"]: "application/json",
|
|
||||||
["Authorization"]: "Bearer " + token,
|
|
||||||
},
|
|
||||||
body: JSON.stringify({ ...record, [field]: undefined }),
|
|
||||||
});
|
|
||||||
|
|
||||||
if (res.ok) recordList.push(await res.json());
|
|
||||||
|
|
||||||
expect(res.ok).toBe(false);
|
|
||||||
expect(res.status).toBe(400);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
it("create branch", async () => {
|
|
||||||
const res = await fetch(baseUrl + "/api/v1/branch", {
|
|
||||||
method: "POST",
|
|
||||||
headers: {
|
|
||||||
["Content-Type"]: "application/json",
|
|
||||||
["Authorization"]: "Bearer " + token,
|
|
||||||
},
|
|
||||||
body: JSON.stringify(record),
|
|
||||||
});
|
|
||||||
if (!res.ok) {
|
|
||||||
const text = await res.text();
|
|
||||||
try {
|
|
||||||
console.log(JSON.parse(text));
|
|
||||||
} catch (e) {
|
|
||||||
console.log(text);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
expect(res.ok).toBe(true);
|
|
||||||
|
|
||||||
const data = await res.json();
|
|
||||||
|
|
||||||
record["id"] = data["id"]; // This field is auto generated
|
|
||||||
record["code"] = data["code"]; // This field is auto generated
|
|
||||||
|
|
||||||
recordList.push(data);
|
|
||||||
|
|
||||||
expect(data).toMatchObject(record);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("get branch list", async () => {
|
|
||||||
const res = await fetch(baseUrl + "/api/v1/branch", {
|
|
||||||
method: "GET",
|
|
||||||
headers: {
|
|
||||||
["Authorization"]: "Bearer " + token,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
if (!res.ok) {
|
|
||||||
const text = await res.text();
|
|
||||||
try {
|
|
||||||
console.log(JSON.parse(text));
|
|
||||||
} catch (e) {
|
|
||||||
console.log(text);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
expect(res.ok).toBe(true);
|
|
||||||
|
|
||||||
const data = await res.json();
|
|
||||||
|
|
||||||
expect(data).toHaveProperty("result");
|
|
||||||
expect(data).toHaveProperty("total");
|
|
||||||
expect(data).toHaveProperty("page");
|
|
||||||
expect(data).toHaveProperty("pageSize");
|
|
||||||
expect(data.result).toEqual(
|
|
||||||
expect.arrayContaining([
|
|
||||||
expect.objectContaining({
|
|
||||||
id: expect.any(String),
|
|
||||||
code: expect.any(String),
|
|
||||||
virtual: expect.any(Boolean),
|
|
||||||
name: expect.any(String),
|
|
||||||
nameEN: expect.any(String),
|
|
||||||
email: expect.any(String),
|
|
||||||
taxNo: expect.any(String),
|
|
||||||
telephoneNo: expect.any(String),
|
|
||||||
latitude: expect.any(String),
|
|
||||||
longitude: expect.any(String),
|
|
||||||
contactName: expect.toBeOneOf([expect.any(String), null]),
|
|
||||||
lineId: expect.toBeOneOf([expect.any(String), null]),
|
|
||||||
webUrl: expect.toBeOneOf([expect.any(String), null]),
|
|
||||||
remark: expect.toBeOneOf([expect.any(String), null]),
|
|
||||||
selectedImage: expect.toBeOneOf([expect.any(String), null]),
|
|
||||||
|
|
||||||
isHeadOffice: expect.any(Boolean),
|
|
||||||
|
|
||||||
permitNo: expect.any(String),
|
|
||||||
permitIssueDate: expect.toSatisfy(isDateString(true)),
|
|
||||||
permitExpireDate: expect.toSatisfy(isDateString(true)),
|
|
||||||
|
|
||||||
address: expect.any(String),
|
|
||||||
addressEN: expect.any(String),
|
|
||||||
moo: expect.toBeOneOf([expect.any(String), null]),
|
|
||||||
mooEN: expect.toBeOneOf([expect.any(String), null]),
|
|
||||||
street: expect.toBeOneOf([expect.any(String), null]),
|
|
||||||
streetEN: expect.toBeOneOf([expect.any(String), null]),
|
|
||||||
provinceId: expect.any(String),
|
|
||||||
province: expect.objectContaining({
|
|
||||||
id: expect.any(String),
|
|
||||||
name: expect.any(String),
|
|
||||||
nameEN: expect.any(String),
|
|
||||||
}),
|
|
||||||
districtId: expect.any(String),
|
|
||||||
district: expect.objectContaining({
|
|
||||||
id: expect.any(String),
|
|
||||||
name: expect.any(String),
|
|
||||||
nameEN: expect.any(String),
|
|
||||||
}),
|
|
||||||
subDistrictId: expect.any(String),
|
|
||||||
subDistrict: expect.objectContaining({
|
|
||||||
id: expect.any(String),
|
|
||||||
name: expect.any(String),
|
|
||||||
nameEN: expect.any(String),
|
|
||||||
zipCode: expect.any(String),
|
|
||||||
}),
|
|
||||||
|
|
||||||
status: expect.toBeOneOf(["CREATED", "ACTIVE", "INACTIVE"]),
|
|
||||||
statusOrder: expect.toBeOneOf([1, 0]),
|
|
||||||
|
|
||||||
createdAt: expect.toSatisfy(isDateString()),
|
|
||||||
createdByUserId: expect.toBeOneOf([expect.any(String), null]),
|
|
||||||
createdBy: expect.objectContaining({
|
|
||||||
id: expect.any(String),
|
|
||||||
username: expect.any(String),
|
|
||||||
firstName: expect.any(String),
|
|
||||||
lastName: expect.any(String),
|
|
||||||
firstNameEN: expect.any(String),
|
|
||||||
lastNameEN: expect.any(String),
|
|
||||||
}),
|
|
||||||
updatedAt: expect.toSatisfy(isDateString()),
|
|
||||||
updatedByUserId: expect.toBeOneOf([expect.any(String), null]),
|
|
||||||
updatedBy: expect.objectContaining({
|
|
||||||
id: expect.any(String),
|
|
||||||
username: expect.any(String),
|
|
||||||
firstName: expect.any(String),
|
|
||||||
lastName: expect.any(String),
|
|
||||||
firstNameEN: expect.any(String),
|
|
||||||
lastNameEN: expect.any(String),
|
|
||||||
}),
|
|
||||||
|
|
||||||
_count: expect.objectContaining({
|
|
||||||
branch: expect.any(Number),
|
|
||||||
}),
|
|
||||||
}),
|
|
||||||
]),
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("get branch by id", async () => {
|
|
||||||
const res = await fetch(baseUrl + "/api/v1/branch/" + record["id"], {
|
|
||||||
method: "GET",
|
|
||||||
headers: {
|
|
||||||
["Authorization"]: "Bearer " + token,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
if (!res.ok) {
|
|
||||||
const text = await res.text();
|
|
||||||
try {
|
|
||||||
console.log(JSON.parse(text));
|
|
||||||
} catch (e) {
|
|
||||||
console.log(text);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
expect(res.ok).toBe(true);
|
|
||||||
|
|
||||||
const data = await res.json();
|
|
||||||
|
|
||||||
expect(data).toMatchObject(record);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("update branch by id", async () => {
|
|
||||||
const res = await fetch(baseUrl + "/api/v1/branch/" + record["id"], {
|
|
||||||
method: "PUT",
|
|
||||||
headers: {
|
|
||||||
["Content-Type"]: "application/json",
|
|
||||||
["Authorization"]: "Bearer " + token,
|
|
||||||
},
|
|
||||||
body: JSON.stringify({ name: "Chamomind Intl.", nameEN: "Chamomind Intl." }),
|
|
||||||
});
|
|
||||||
|
|
||||||
record["name"] = "Chamomind Intl.";
|
|
||||||
record["nameEN"] = "Chamomind Intl.";
|
|
||||||
|
|
||||||
expect(res.ok).toBe(true);
|
|
||||||
|
|
||||||
const data = await res.json();
|
|
||||||
|
|
||||||
expect(data).toMatchObject(record);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("delete branch by id", async () => {
|
|
||||||
const res = await fetch(baseUrl + "/api/v1/branch/" + record["id"], {
|
|
||||||
method: "DELETE",
|
|
||||||
headers: {
|
|
||||||
["Content-Type"]: "application/json",
|
|
||||||
["Authorization"]: "Bearer " + token,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
if (!res.ok) {
|
|
||||||
const text = await res.text();
|
|
||||||
try {
|
|
||||||
console.log(JSON.parse(text));
|
|
||||||
} catch (e) {
|
|
||||||
console.log(text);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
expect(res.ok).toBe(true);
|
|
||||||
|
|
||||||
const data = await res.json();
|
|
||||||
|
|
||||||
expect(data).toMatchObject(record);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("get deleted branch by id", async () => {
|
|
||||||
const res = await fetch(baseUrl + "/api/v1/branch/" + record["id"], {
|
|
||||||
method: "GET",
|
|
||||||
headers: {
|
|
||||||
["Authorization"]: "Bearer " + token,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
expect(res.ok).toBe(false);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
@ -1,10 +0,0 @@
|
||||||
export function isDateString(nullable: boolean = false): (val: any) => boolean {
|
|
||||||
return (value: any) => {
|
|
||||||
try {
|
|
||||||
if (value) return !!new Date(value);
|
|
||||||
return nullable;
|
|
||||||
} catch (_) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
@ -1,5 +0,0 @@
|
||||||
import { defineConfig } from "vitest/config";
|
|
||||||
|
|
||||||
export default defineConfig({
|
|
||||||
test: {},
|
|
||||||
});
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue