Merge branch 'dev'
This commit is contained in:
commit
3d9f0881c3
50 changed files with 6949 additions and 2203 deletions
|
|
@ -1,8 +1,8 @@
|
||||||
KC_URL=http://192.168.1.20:8080
|
KC_URL=http://192.168.1.20:8080
|
||||||
KC_REALM=dev
|
KC_REALM=dev
|
||||||
|
|
||||||
KC_SERVICE_ACCOUNT_CLIENT_ID=dev-service
|
KC_ADMIN_USERNAME=admin
|
||||||
KC_SERVICE_ACCOUNT_SECRET=
|
KC_ADMIN_PASSWORD=
|
||||||
|
|
||||||
APP_HOST=0.0.0.0
|
APP_HOST=0.0.0.0
|
||||||
APP_PORT=3000
|
APP_PORT=3000
|
||||||
|
|
|
||||||
1
.gitignore
vendored
1
.gitignore
vendored
|
|
@ -1,5 +1,6 @@
|
||||||
.DS_S
|
.DS_S
|
||||||
node_modules
|
node_modules
|
||||||
|
/src/generated
|
||||||
|
|
||||||
.env
|
.env
|
||||||
.env.*
|
.env.*
|
||||||
|
|
|
||||||
|
|
@ -5,7 +5,7 @@ ENV PATH="$PNPM_HOME:$PATH"
|
||||||
|
|
||||||
RUN corepack enable
|
RUN corepack enable
|
||||||
RUN apt-get update && apt-get install -y openssl
|
RUN apt-get update && apt-get install -y openssl
|
||||||
RUN pnpm i -g prisma
|
RUN pnpm i -g prisma prisma-kysely
|
||||||
|
|
||||||
WORKDIR /app
|
WORKDIR /app
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -22,21 +22,24 @@
|
||||||
"@types/express": "^4.17.21",
|
"@types/express": "^4.17.21",
|
||||||
"@types/node": "^20.12.2",
|
"@types/node": "^20.12.2",
|
||||||
"@types/swagger-ui-express": "^4.1.6",
|
"@types/swagger-ui-express": "^4.1.6",
|
||||||
"nodemon": "^3.1.0",
|
"nodemon": "^3.1.3",
|
||||||
"prettier": "^3.2.5",
|
"prettier": "^3.2.5",
|
||||||
"prisma": "^5.12.1",
|
"prisma": "^5.16.0",
|
||||||
|
"prisma-kysely": "^1.8.0",
|
||||||
"ts-node": "^10.9.2",
|
"ts-node": "^10.9.2",
|
||||||
"typescript": "^5.4.3"
|
"typescript": "^5.4.3"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@elastic/elasticsearch": "^8.13.0",
|
"@elastic/elasticsearch": "^8.13.0",
|
||||||
"@prisma/client": "5.12.1",
|
"@prisma/client": "^5.16.0",
|
||||||
"@tsoa/runtime": "^6.2.0",
|
"@tsoa/runtime": "^6.2.0",
|
||||||
"cors": "^2.8.5",
|
"cors": "^2.8.5",
|
||||||
"dotenv": "^16.4.5",
|
"dotenv": "^16.4.5",
|
||||||
"express": "^4.19.2",
|
"express": "^4.19.2",
|
||||||
"fast-jwt": "^4.0.0",
|
"fast-jwt": "^4.0.0",
|
||||||
|
"kysely": "^0.27.3",
|
||||||
"minio": "^7.1.3",
|
"minio": "^7.1.3",
|
||||||
|
"prisma-extension-kysely": "^2.1.0",
|
||||||
"promise.any": "^2.0.6",
|
"promise.any": "^2.0.6",
|
||||||
"swagger-ui-express": "^5.0.0",
|
"swagger-ui-express": "^5.0.0",
|
||||||
"tsoa": "^6.2.0"
|
"tsoa": "^6.2.0"
|
||||||
|
|
|
||||||
4664
pnpm-lock.yaml
generated
4664
pnpm-lock.yaml
generated
File diff suppressed because it is too large
Load diff
|
|
@ -1,14 +0,0 @@
|
||||||
/*
|
|
||||||
Warnings:
|
|
||||||
|
|
||||||
- You are about to drop the column `imageUrl` on the `Customer` table. All the data in the column will be lost.
|
|
||||||
- Changed the type of `customerType` on the `Customer` table. No cast exists, the column would be dropped and recreated, which cannot be done if there is data, since the column is required.
|
|
||||||
|
|
||||||
*/
|
|
||||||
-- CreateEnum
|
|
||||||
CREATE TYPE "CustomerType" AS ENUM ('CORP', 'PERS');
|
|
||||||
|
|
||||||
-- AlterTable
|
|
||||||
ALTER TABLE "Customer" DROP COLUMN "imageUrl",
|
|
||||||
DROP COLUMN "customerType",
|
|
||||||
ADD COLUMN "customerType" "CustomerType" NOT NULL;
|
|
||||||
|
|
@ -1,3 +0,0 @@
|
||||||
-- AlterTable
|
|
||||||
ALTER TABLE "User" ADD COLUMN "birtDate" TIMESTAMP(3),
|
|
||||||
ADD COLUMN "responsibleArea" TEXT;
|
|
||||||
|
|
@ -1,9 +0,0 @@
|
||||||
/*
|
|
||||||
Warnings:
|
|
||||||
|
|
||||||
- You are about to drop the column `birtDate` on the `User` table. All the data in the column will be lost.
|
|
||||||
|
|
||||||
*/
|
|
||||||
-- AlterTable
|
|
||||||
ALTER TABLE "User" DROP COLUMN "birtDate",
|
|
||||||
ADD COLUMN "birthDate" TIMESTAMP(3);
|
|
||||||
|
|
@ -1,8 +0,0 @@
|
||||||
/*
|
|
||||||
Warnings:
|
|
||||||
|
|
||||||
- You are about to drop the column `keycloakId` on the `User` table. All the data in the column will be lost.
|
|
||||||
|
|
||||||
*/
|
|
||||||
-- AlterTable
|
|
||||||
ALTER TABLE "User" DROP COLUMN "keycloakId";
|
|
||||||
|
|
@ -1,11 +0,0 @@
|
||||||
/*
|
|
||||||
Warnings:
|
|
||||||
|
|
||||||
- You are about to drop the column `lineId` on the `BranchContact` table. All the data in the column will be lost.
|
|
||||||
|
|
||||||
*/
|
|
||||||
-- AlterTable
|
|
||||||
ALTER TABLE "Branch" ADD COLUMN "lineId" TEXT;
|
|
||||||
|
|
||||||
-- AlterTable
|
|
||||||
ALTER TABLE "BranchContact" DROP COLUMN "lineId";
|
|
||||||
|
|
@ -1,119 +0,0 @@
|
||||||
/*
|
|
||||||
Warnings:
|
|
||||||
|
|
||||||
- You are about to drop the column `telephoneNo` on the `Branch` table. All the data in the column will be lost.
|
|
||||||
|
|
||||||
*/
|
|
||||||
-- AlterTable
|
|
||||||
ALTER TABLE "Branch" DROP COLUMN "telephoneNo";
|
|
||||||
|
|
||||||
-- CreateTable
|
|
||||||
CREATE TABLE "Menu" (
|
|
||||||
"id" TEXT NOT NULL,
|
|
||||||
"caption" TEXT NOT NULL,
|
|
||||||
"captionEN" TEXT NOT NULL,
|
|
||||||
"menuType" TEXT NOT NULL,
|
|
||||||
"url" TEXT NOT NULL,
|
|
||||||
"createdBy" TEXT,
|
|
||||||
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
|
||||||
"updateBy" TEXT,
|
|
||||||
"updatedAt" TIMESTAMP(3) NOT NULL,
|
|
||||||
"parentId" TEXT,
|
|
||||||
|
|
||||||
CONSTRAINT "Menu_pkey" PRIMARY KEY ("id")
|
|
||||||
);
|
|
||||||
|
|
||||||
-- CreateTable
|
|
||||||
CREATE TABLE "RoleMenuPermission" (
|
|
||||||
"id" TEXT NOT NULL,
|
|
||||||
"userRole" TEXT NOT NULL,
|
|
||||||
"permission" TEXT NOT NULL,
|
|
||||||
"menuId" TEXT NOT NULL,
|
|
||||||
"createdBy" TEXT,
|
|
||||||
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
|
||||||
"updateBy" TEXT,
|
|
||||||
"updatedAt" TIMESTAMP(3) NOT NULL,
|
|
||||||
|
|
||||||
CONSTRAINT "RoleMenuPermission_pkey" PRIMARY KEY ("id")
|
|
||||||
);
|
|
||||||
|
|
||||||
-- CreateTable
|
|
||||||
CREATE TABLE "UserMenuPermission" (
|
|
||||||
"id" TEXT NOT NULL,
|
|
||||||
"permission" TEXT NOT NULL,
|
|
||||||
"menuId" TEXT NOT NULL,
|
|
||||||
"userId" TEXT NOT NULL,
|
|
||||||
"createdBy" TEXT,
|
|
||||||
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
|
||||||
"updateBy" TEXT,
|
|
||||||
"updatedAt" TIMESTAMP(3) NOT NULL,
|
|
||||||
|
|
||||||
CONSTRAINT "UserMenuPermission_pkey" PRIMARY KEY ("id")
|
|
||||||
);
|
|
||||||
|
|
||||||
-- CreateTable
|
|
||||||
CREATE TABLE "MenuComponent" (
|
|
||||||
"id" TEXT NOT NULL,
|
|
||||||
"componentId" TEXT NOT NULL,
|
|
||||||
"componentTag" TEXT NOT NULL,
|
|
||||||
"menuId" TEXT NOT NULL,
|
|
||||||
"createdBy" TEXT,
|
|
||||||
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
|
||||||
"updateBy" TEXT,
|
|
||||||
"updatedAt" TIMESTAMP(3) NOT NULL,
|
|
||||||
|
|
||||||
CONSTRAINT "MenuComponent_pkey" PRIMARY KEY ("id")
|
|
||||||
);
|
|
||||||
|
|
||||||
-- CreateTable
|
|
||||||
CREATE TABLE "RoleMenuComponentPermission" (
|
|
||||||
"id" TEXT NOT NULL,
|
|
||||||
"componentId" TEXT NOT NULL,
|
|
||||||
"componentTag" TEXT NOT NULL,
|
|
||||||
"menuComponentId" TEXT NOT NULL,
|
|
||||||
"permission" TEXT NOT NULL,
|
|
||||||
"createdBy" TEXT,
|
|
||||||
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
|
||||||
"updateBy" TEXT,
|
|
||||||
"updatedAt" TIMESTAMP(3) NOT NULL,
|
|
||||||
|
|
||||||
CONSTRAINT "RoleMenuComponentPermission_pkey" PRIMARY KEY ("id")
|
|
||||||
);
|
|
||||||
|
|
||||||
-- CreateTable
|
|
||||||
CREATE TABLE "UserMenuComponentPermission" (
|
|
||||||
"id" TEXT NOT NULL,
|
|
||||||
"userId" TEXT NOT NULL,
|
|
||||||
"menuComponentId" TEXT NOT NULL,
|
|
||||||
"permission" TEXT NOT NULL,
|
|
||||||
"createdBy" TEXT,
|
|
||||||
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
|
||||||
"updateBy" TEXT,
|
|
||||||
"updatedAt" TIMESTAMP(3) NOT NULL,
|
|
||||||
|
|
||||||
CONSTRAINT "UserMenuComponentPermission_pkey" PRIMARY KEY ("id")
|
|
||||||
);
|
|
||||||
|
|
||||||
-- AddForeignKey
|
|
||||||
ALTER TABLE "Menu" ADD CONSTRAINT "Menu_parentId_fkey" FOREIGN KEY ("parentId") REFERENCES "Menu"("id") ON DELETE SET NULL ON UPDATE CASCADE;
|
|
||||||
|
|
||||||
-- AddForeignKey
|
|
||||||
ALTER TABLE "RoleMenuPermission" ADD CONSTRAINT "RoleMenuPermission_menuId_fkey" FOREIGN KEY ("menuId") REFERENCES "Menu"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
|
|
||||||
|
|
||||||
-- AddForeignKey
|
|
||||||
ALTER TABLE "UserMenuPermission" ADD CONSTRAINT "UserMenuPermission_menuId_fkey" FOREIGN KEY ("menuId") REFERENCES "Menu"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
|
|
||||||
|
|
||||||
-- AddForeignKey
|
|
||||||
ALTER TABLE "UserMenuPermission" ADD CONSTRAINT "UserMenuPermission_userId_fkey" FOREIGN KEY ("userId") REFERENCES "User"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
|
|
||||||
|
|
||||||
-- AddForeignKey
|
|
||||||
ALTER TABLE "MenuComponent" ADD CONSTRAINT "MenuComponent_menuId_fkey" FOREIGN KEY ("menuId") REFERENCES "Menu"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
|
|
||||||
|
|
||||||
-- AddForeignKey
|
|
||||||
ALTER TABLE "RoleMenuComponentPermission" ADD CONSTRAINT "RoleMenuComponentPermission_menuComponentId_fkey" FOREIGN KEY ("menuComponentId") REFERENCES "MenuComponent"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
|
|
||||||
|
|
||||||
-- AddForeignKey
|
|
||||||
ALTER TABLE "UserMenuComponentPermission" ADD CONSTRAINT "UserMenuComponentPermission_userId_fkey" FOREIGN KEY ("userId") REFERENCES "User"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
|
|
||||||
|
|
||||||
-- AddForeignKey
|
|
||||||
ALTER TABLE "UserMenuComponentPermission" ADD CONSTRAINT "UserMenuComponentPermission_menuComponentId_fkey" FOREIGN KEY ("menuComponentId") REFERENCES "MenuComponent"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
|
|
||||||
|
|
@ -1,2 +0,0 @@
|
||||||
-- AlterTable
|
|
||||||
ALTER TABLE "Branch" ADD COLUMN "contactName" TEXT;
|
|
||||||
|
|
@ -1,3 +0,0 @@
|
||||||
-- AlterTable
|
|
||||||
ALTER TABLE "User" ADD COLUMN "checkpoint" TEXT,
|
|
||||||
ADD COLUMN "checkpointEN" TEXT;
|
|
||||||
|
|
@ -1,8 +0,0 @@
|
||||||
/*
|
|
||||||
Warnings:
|
|
||||||
|
|
||||||
- Added the required column `username` to the `User` table without a default value. This is not possible if the table is not empty.
|
|
||||||
|
|
||||||
*/
|
|
||||||
-- AlterTable
|
|
||||||
ALTER TABLE "User" ADD COLUMN "username" TEXT NOT NULL;
|
|
||||||
|
|
@ -1,12 +0,0 @@
|
||||||
/*
|
|
||||||
Warnings:
|
|
||||||
|
|
||||||
- You are about to drop the column `componentId` on the `RoleMenuComponentPermission` table. All the data in the column will be lost.
|
|
||||||
- You are about to drop the column `componentTag` on the `RoleMenuComponentPermission` table. All the data in the column will be lost.
|
|
||||||
- Added the required column `userRole` to the `RoleMenuComponentPermission` table without a default value. This is not possible if the table is not empty.
|
|
||||||
|
|
||||||
*/
|
|
||||||
-- AlterTable
|
|
||||||
ALTER TABLE "RoleMenuComponentPermission" DROP COLUMN "componentId",
|
|
||||||
DROP COLUMN "componentTag",
|
|
||||||
ADD COLUMN "userRole" TEXT NOT NULL;
|
|
||||||
|
|
@ -1,2 +0,0 @@
|
||||||
-- AlterTable
|
|
||||||
ALTER TABLE "Branch" ADD COLUMN "telephoneHq" TEXT;
|
|
||||||
|
|
@ -1,8 +0,0 @@
|
||||||
/*
|
|
||||||
Warnings:
|
|
||||||
|
|
||||||
- Made the column `telephoneHq` on table `Branch` required. This step will fail if there are existing NULL values in that column.
|
|
||||||
|
|
||||||
*/
|
|
||||||
-- AlterTable
|
|
||||||
ALTER TABLE "Branch" ALTER COLUMN "telephoneHq" SET NOT NULL;
|
|
||||||
|
|
@ -1,9 +0,0 @@
|
||||||
/*
|
|
||||||
Warnings:
|
|
||||||
|
|
||||||
- You are about to drop the column `telephoneHq` on the `Branch` table. All the data in the column will be lost.
|
|
||||||
|
|
||||||
*/
|
|
||||||
-- AlterTable
|
|
||||||
ALTER TABLE "Branch" DROP COLUMN "telephoneHq",
|
|
||||||
ADD COLUMN "telephoneNo" TEXT;
|
|
||||||
|
|
@ -1,8 +0,0 @@
|
||||||
/*
|
|
||||||
Warnings:
|
|
||||||
|
|
||||||
- Made the column `telephoneNo` on table `Branch` required. This step will fail if there are existing NULL values in that column.
|
|
||||||
|
|
||||||
*/
|
|
||||||
-- AlterTable
|
|
||||||
ALTER TABLE "Branch" ALTER COLUMN "telephoneNo" SET NOT NULL;
|
|
||||||
|
|
@ -4,6 +4,103 @@ CREATE TYPE "Status" AS ENUM ('CREATED', 'ACTIVE', 'INACTIVE');
|
||||||
-- CreateEnum
|
-- CreateEnum
|
||||||
CREATE TYPE "UserType" AS ENUM ('USER', 'MESSENGER', 'DELEGATE', 'AGENCY');
|
CREATE TYPE "UserType" AS ENUM ('USER', 'MESSENGER', 'DELEGATE', 'AGENCY');
|
||||||
|
|
||||||
|
-- CreateEnum
|
||||||
|
CREATE TYPE "CustomerType" AS ENUM ('CORP', 'PERS');
|
||||||
|
|
||||||
|
-- CreateTable
|
||||||
|
CREATE TABLE "Menu" (
|
||||||
|
"id" TEXT NOT NULL,
|
||||||
|
"caption" TEXT NOT NULL,
|
||||||
|
"captionEN" TEXT NOT NULL,
|
||||||
|
"menuType" TEXT NOT NULL,
|
||||||
|
"url" TEXT NOT NULL,
|
||||||
|
"createdBy" TEXT,
|
||||||
|
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
"updatedBy" TEXT,
|
||||||
|
"updatedAt" TIMESTAMP(3) NOT NULL,
|
||||||
|
"parentId" TEXT,
|
||||||
|
|
||||||
|
CONSTRAINT "Menu_pkey" PRIMARY KEY ("id")
|
||||||
|
);
|
||||||
|
|
||||||
|
-- CreateTable
|
||||||
|
CREATE TABLE "RoleMenuPermission" (
|
||||||
|
"id" TEXT NOT NULL,
|
||||||
|
"userRole" TEXT NOT NULL,
|
||||||
|
"permission" TEXT NOT NULL,
|
||||||
|
"menuId" TEXT NOT NULL,
|
||||||
|
"createdBy" TEXT,
|
||||||
|
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
"updatedBy" TEXT,
|
||||||
|
"updatedAt" TIMESTAMP(3) NOT NULL,
|
||||||
|
|
||||||
|
CONSTRAINT "RoleMenuPermission_pkey" PRIMARY KEY ("id")
|
||||||
|
);
|
||||||
|
|
||||||
|
-- CreateTable
|
||||||
|
CREATE TABLE "UserMenuPermission" (
|
||||||
|
"id" TEXT NOT NULL,
|
||||||
|
"permission" TEXT NOT NULL,
|
||||||
|
"menuId" TEXT NOT NULL,
|
||||||
|
"userId" TEXT NOT NULL,
|
||||||
|
"createdBy" TEXT,
|
||||||
|
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
"updatedBy" TEXT,
|
||||||
|
"updatedAt" TIMESTAMP(3) NOT NULL,
|
||||||
|
|
||||||
|
CONSTRAINT "UserMenuPermission_pkey" PRIMARY KEY ("id")
|
||||||
|
);
|
||||||
|
|
||||||
|
-- CreateTable
|
||||||
|
CREATE TABLE "MenuComponent" (
|
||||||
|
"id" TEXT NOT NULL,
|
||||||
|
"componentId" TEXT NOT NULL,
|
||||||
|
"componentTag" TEXT NOT NULL,
|
||||||
|
"menuId" TEXT NOT NULL,
|
||||||
|
"createdBy" TEXT,
|
||||||
|
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
"updatedBy" TEXT,
|
||||||
|
"updatedAt" TIMESTAMP(3) NOT NULL,
|
||||||
|
|
||||||
|
CONSTRAINT "MenuComponent_pkey" PRIMARY KEY ("id")
|
||||||
|
);
|
||||||
|
|
||||||
|
-- CreateTable
|
||||||
|
CREATE TABLE "RoleMenuComponentPermission" (
|
||||||
|
"id" TEXT NOT NULL,
|
||||||
|
"userRole" TEXT NOT NULL,
|
||||||
|
"permission" TEXT NOT NULL,
|
||||||
|
"menuComponentId" TEXT NOT NULL,
|
||||||
|
"createdBy" TEXT,
|
||||||
|
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
"updatedBy" TEXT,
|
||||||
|
"updatedAt" TIMESTAMP(3) NOT NULL,
|
||||||
|
|
||||||
|
CONSTRAINT "RoleMenuComponentPermission_pkey" PRIMARY KEY ("id")
|
||||||
|
);
|
||||||
|
|
||||||
|
-- CreateTable
|
||||||
|
CREATE TABLE "RunningNo" (
|
||||||
|
"key" TEXT NOT NULL,
|
||||||
|
"value" INTEGER NOT NULL,
|
||||||
|
|
||||||
|
CONSTRAINT "RunningNo_pkey" PRIMARY KEY ("key")
|
||||||
|
);
|
||||||
|
|
||||||
|
-- CreateTable
|
||||||
|
CREATE TABLE "UserMenuComponentPermission" (
|
||||||
|
"id" TEXT NOT NULL,
|
||||||
|
"userId" TEXT NOT NULL,
|
||||||
|
"menuComponentId" TEXT NOT NULL,
|
||||||
|
"permission" TEXT NOT NULL,
|
||||||
|
"createdBy" TEXT,
|
||||||
|
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
"updatedBy" TEXT,
|
||||||
|
"updatedAt" TIMESTAMP(3) NOT NULL,
|
||||||
|
|
||||||
|
CONSTRAINT "UserMenuComponentPermission_pkey" PRIMARY KEY ("id")
|
||||||
|
);
|
||||||
|
|
||||||
-- CreateTable
|
-- CreateTable
|
||||||
CREATE TABLE "Province" (
|
CREATE TABLE "Province" (
|
||||||
"id" TEXT NOT NULL,
|
"id" TEXT NOT NULL,
|
||||||
|
|
@ -11,7 +108,7 @@ CREATE TABLE "Province" (
|
||||||
"nameEN" TEXT NOT NULL,
|
"nameEN" TEXT NOT NULL,
|
||||||
"createdBy" TEXT,
|
"createdBy" TEXT,
|
||||||
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||||
"updateBy" TEXT,
|
"updatedBy" TEXT,
|
||||||
"updatedAt" TIMESTAMP(3) NOT NULL,
|
"updatedAt" TIMESTAMP(3) NOT NULL,
|
||||||
|
|
||||||
CONSTRAINT "Province_pkey" PRIMARY KEY ("id")
|
CONSTRAINT "Province_pkey" PRIMARY KEY ("id")
|
||||||
|
|
@ -25,7 +122,7 @@ CREATE TABLE "District" (
|
||||||
"provinceId" TEXT NOT NULL,
|
"provinceId" TEXT NOT NULL,
|
||||||
"createdBy" TEXT,
|
"createdBy" TEXT,
|
||||||
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||||
"updateBy" TEXT,
|
"updatedBy" TEXT,
|
||||||
"updatedAt" TIMESTAMP(3) NOT NULL,
|
"updatedAt" TIMESTAMP(3) NOT NULL,
|
||||||
|
|
||||||
CONSTRAINT "District_pkey" PRIMARY KEY ("id")
|
CONSTRAINT "District_pkey" PRIMARY KEY ("id")
|
||||||
|
|
@ -40,7 +137,7 @@ CREATE TABLE "SubDistrict" (
|
||||||
"districtId" TEXT NOT NULL,
|
"districtId" TEXT NOT NULL,
|
||||||
"createdBy" TEXT,
|
"createdBy" TEXT,
|
||||||
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||||
"updateBy" TEXT,
|
"updatedBy" TEXT,
|
||||||
"updatedAt" TIMESTAMP(3) NOT NULL,
|
"updatedAt" TIMESTAMP(3) NOT NULL,
|
||||||
|
|
||||||
CONSTRAINT "SubDistrict_pkey" PRIMARY KEY ("id")
|
CONSTRAINT "SubDistrict_pkey" PRIMARY KEY ("id")
|
||||||
|
|
@ -55,20 +152,23 @@ CREATE TABLE "Branch" (
|
||||||
"nameEN" TEXT NOT NULL,
|
"nameEN" TEXT NOT NULL,
|
||||||
"address" TEXT NOT NULL,
|
"address" TEXT NOT NULL,
|
||||||
"addressEN" TEXT NOT NULL,
|
"addressEN" TEXT NOT NULL,
|
||||||
|
"telephoneNo" TEXT NOT NULL,
|
||||||
"provinceId" TEXT,
|
"provinceId" TEXT,
|
||||||
"districtId" TEXT,
|
"districtId" TEXT,
|
||||||
"subDistrictId" TEXT,
|
"subDistrictId" TEXT,
|
||||||
"zipCode" TEXT NOT NULL,
|
"zipCode" TEXT NOT NULL,
|
||||||
"email" TEXT NOT NULL,
|
"email" TEXT NOT NULL,
|
||||||
"telephoneNo" TEXT NOT NULL,
|
"contactName" TEXT,
|
||||||
|
"lineId" TEXT,
|
||||||
"latitude" TEXT NOT NULL,
|
"latitude" TEXT NOT NULL,
|
||||||
"longitude" TEXT NOT NULL,
|
"longitude" TEXT NOT NULL,
|
||||||
"isHeadOffice" BOOLEAN NOT NULL DEFAULT false,
|
"isHeadOffice" BOOLEAN NOT NULL DEFAULT false,
|
||||||
"headOfficeId" TEXT,
|
"headOfficeId" TEXT,
|
||||||
"status" "Status" NOT NULL DEFAULT 'CREATED',
|
"status" "Status" NOT NULL DEFAULT 'CREATED',
|
||||||
|
"statusOrder" INTEGER NOT NULL DEFAULT 0,
|
||||||
"createdBy" TEXT,
|
"createdBy" TEXT,
|
||||||
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||||
"updateBy" TEXT,
|
"updatedBy" TEXT,
|
||||||
"updatedAt" TIMESTAMP(3) NOT NULL,
|
"updatedAt" TIMESTAMP(3) NOT NULL,
|
||||||
|
|
||||||
CONSTRAINT "Branch_pkey" PRIMARY KEY ("id")
|
CONSTRAINT "Branch_pkey" PRIMARY KEY ("id")
|
||||||
|
|
@ -78,11 +178,10 @@ CREATE TABLE "Branch" (
|
||||||
CREATE TABLE "BranchContact" (
|
CREATE TABLE "BranchContact" (
|
||||||
"id" TEXT NOT NULL,
|
"id" TEXT NOT NULL,
|
||||||
"telephoneNo" TEXT NOT NULL,
|
"telephoneNo" TEXT NOT NULL,
|
||||||
"lineId" TEXT NOT NULL,
|
|
||||||
"branchId" TEXT NOT NULL,
|
"branchId" TEXT NOT NULL,
|
||||||
"createdBy" TEXT,
|
"createdBy" TEXT,
|
||||||
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||||
"updateBy" TEXT,
|
"updatedBy" TEXT,
|
||||||
"updatedAt" TIMESTAMP(3) NOT NULL,
|
"updatedAt" TIMESTAMP(3) NOT NULL,
|
||||||
|
|
||||||
CONSTRAINT "BranchContact_pkey" PRIMARY KEY ("id")
|
CONSTRAINT "BranchContact_pkey" PRIMARY KEY ("id")
|
||||||
|
|
@ -95,7 +194,7 @@ CREATE TABLE "BranchUser" (
|
||||||
"userId" TEXT NOT NULL,
|
"userId" TEXT NOT NULL,
|
||||||
"createdBy" TEXT,
|
"createdBy" TEXT,
|
||||||
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||||
"updateBy" TEXT,
|
"updatedBy" TEXT,
|
||||||
"updatedAt" TIMESTAMP(3) NOT NULL,
|
"updatedAt" TIMESTAMP(3) NOT NULL,
|
||||||
|
|
||||||
CONSTRAINT "BranchUser_pkey" PRIMARY KEY ("id")
|
CONSTRAINT "BranchUser_pkey" PRIMARY KEY ("id")
|
||||||
|
|
@ -104,12 +203,12 @@ CREATE TABLE "BranchUser" (
|
||||||
-- CreateTable
|
-- CreateTable
|
||||||
CREATE TABLE "User" (
|
CREATE TABLE "User" (
|
||||||
"id" TEXT NOT NULL,
|
"id" TEXT NOT NULL,
|
||||||
"keycloakId" TEXT NOT NULL,
|
|
||||||
"code" TEXT,
|
"code" TEXT,
|
||||||
"firstName" TEXT NOT NULL,
|
"firstName" TEXT NOT NULL,
|
||||||
"firstNameEN" TEXT NOT NULL,
|
"firstNameEN" TEXT NOT NULL,
|
||||||
"lastName" TEXT NOT NULL,
|
"lastName" TEXT NOT NULL,
|
||||||
"lastNameEN" TEXT NOT NULL,
|
"lastNameEN" TEXT NOT NULL,
|
||||||
|
"username" TEXT NOT NULL,
|
||||||
"gender" TEXT NOT NULL,
|
"gender" TEXT NOT NULL,
|
||||||
"address" TEXT NOT NULL,
|
"address" TEXT NOT NULL,
|
||||||
"addressEN" TEXT NOT NULL,
|
"addressEN" TEXT NOT NULL,
|
||||||
|
|
@ -122,6 +221,8 @@ CREATE TABLE "User" (
|
||||||
"registrationNo" TEXT,
|
"registrationNo" TEXT,
|
||||||
"startDate" TIMESTAMP(3),
|
"startDate" TIMESTAMP(3),
|
||||||
"retireDate" TIMESTAMP(3),
|
"retireDate" TIMESTAMP(3),
|
||||||
|
"checkpoint" TEXT,
|
||||||
|
"checkpointEN" TEXT,
|
||||||
"userType" "UserType" NOT NULL,
|
"userType" "UserType" NOT NULL,
|
||||||
"userRole" TEXT NOT NULL,
|
"userRole" TEXT NOT NULL,
|
||||||
"discountCondition" TEXT,
|
"discountCondition" TEXT,
|
||||||
|
|
@ -131,10 +232,13 @@ CREATE TABLE "User" (
|
||||||
"sourceNationality" TEXT,
|
"sourceNationality" TEXT,
|
||||||
"importNationality" TEXT,
|
"importNationality" TEXT,
|
||||||
"trainingPlace" TEXT,
|
"trainingPlace" TEXT,
|
||||||
|
"responsibleArea" TEXT,
|
||||||
|
"birthDate" TIMESTAMP(3),
|
||||||
"status" "Status" NOT NULL DEFAULT 'CREATED',
|
"status" "Status" NOT NULL DEFAULT 'CREATED',
|
||||||
|
"statusOrder" INTEGER NOT NULL DEFAULT 0,
|
||||||
"createdBy" TEXT,
|
"createdBy" TEXT,
|
||||||
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||||
"updateBy" TEXT,
|
"updatedBy" TEXT,
|
||||||
"updatedAt" TIMESTAMP(3) NOT NULL,
|
"updatedAt" TIMESTAMP(3) NOT NULL,
|
||||||
|
|
||||||
CONSTRAINT "User_pkey" PRIMARY KEY ("id")
|
CONSTRAINT "User_pkey" PRIMARY KEY ("id")
|
||||||
|
|
@ -144,14 +248,17 @@ CREATE TABLE "User" (
|
||||||
CREATE TABLE "Customer" (
|
CREATE TABLE "Customer" (
|
||||||
"id" TEXT NOT NULL,
|
"id" TEXT NOT NULL,
|
||||||
"code" TEXT NOT NULL,
|
"code" TEXT NOT NULL,
|
||||||
"customerType" TEXT NOT NULL,
|
"personName" TEXT NOT NULL,
|
||||||
|
"personNameEN" TEXT,
|
||||||
|
"customerType" "CustomerType" NOT NULL,
|
||||||
"customerName" TEXT NOT NULL,
|
"customerName" TEXT NOT NULL,
|
||||||
"customerNameEN" TEXT NOT NULL,
|
"customerNameEN" TEXT NOT NULL,
|
||||||
"imageUrl" TEXT,
|
"taxNo" TEXT,
|
||||||
"status" "Status" NOT NULL DEFAULT 'CREATED',
|
"status" "Status" NOT NULL DEFAULT 'CREATED',
|
||||||
|
"statusOrder" INTEGER NOT NULL DEFAULT 0,
|
||||||
"createdBy" TEXT,
|
"createdBy" TEXT,
|
||||||
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||||
"updateBy" TEXT,
|
"updatedBy" TEXT,
|
||||||
"updatedAt" TIMESTAMP(3) NOT NULL,
|
"updatedAt" TIMESTAMP(3) NOT NULL,
|
||||||
|
|
||||||
CONSTRAINT "Customer_pkey" PRIMARY KEY ("id")
|
CONSTRAINT "Customer_pkey" PRIMARY KEY ("id")
|
||||||
|
|
@ -160,12 +267,13 @@ CREATE TABLE "Customer" (
|
||||||
-- CreateTable
|
-- CreateTable
|
||||||
CREATE TABLE "CustomerBranch" (
|
CREATE TABLE "CustomerBranch" (
|
||||||
"id" TEXT NOT NULL,
|
"id" TEXT NOT NULL,
|
||||||
"branchNo" TEXT NOT NULL,
|
"branchNo" INTEGER NOT NULL,
|
||||||
|
"code" TEXT NOT NULL,
|
||||||
"legalPersonNo" TEXT NOT NULL,
|
"legalPersonNo" TEXT NOT NULL,
|
||||||
"name" TEXT NOT NULL,
|
"name" TEXT NOT NULL,
|
||||||
"nameEN" TEXT NOT NULL,
|
"nameEN" TEXT NOT NULL,
|
||||||
"customerId" TEXT NOT NULL,
|
"customerId" TEXT NOT NULL,
|
||||||
"taxNo" TEXT NOT NULL,
|
"taxNo" TEXT,
|
||||||
"registerName" TEXT NOT NULL,
|
"registerName" TEXT NOT NULL,
|
||||||
"registerDate" TIMESTAMP(3) NOT NULL,
|
"registerDate" TIMESTAMP(3) NOT NULL,
|
||||||
"authorizedCapital" TEXT NOT NULL,
|
"authorizedCapital" TEXT NOT NULL,
|
||||||
|
|
@ -177,12 +285,20 @@ CREATE TABLE "CustomerBranch" (
|
||||||
"zipCode" TEXT NOT NULL,
|
"zipCode" TEXT NOT NULL,
|
||||||
"email" TEXT NOT NULL,
|
"email" TEXT NOT NULL,
|
||||||
"telephoneNo" TEXT NOT NULL,
|
"telephoneNo" TEXT NOT NULL,
|
||||||
"latitude" TEXT NOT NULL,
|
"employmentOffice" TEXT NOT NULL,
|
||||||
"longitude" TEXT NOT NULL,
|
"bussinessType" TEXT NOT NULL,
|
||||||
|
"bussinessTypeEN" TEXT NOT NULL,
|
||||||
|
"jobPosition" TEXT NOT NULL,
|
||||||
|
"jobPositionEN" TEXT NOT NULL,
|
||||||
|
"jobDescription" TEXT NOT NULL,
|
||||||
|
"saleEmployee" TEXT NOT NULL,
|
||||||
|
"payDate" TIMESTAMP(3) NOT NULL,
|
||||||
|
"wageRate" INTEGER NOT NULL,
|
||||||
"status" "Status" NOT NULL DEFAULT 'CREATED',
|
"status" "Status" NOT NULL DEFAULT 'CREATED',
|
||||||
|
"statusOrder" INTEGER NOT NULL DEFAULT 0,
|
||||||
"createdBy" TEXT,
|
"createdBy" TEXT,
|
||||||
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||||
"updateBy" TEXT,
|
"updatedBy" TEXT,
|
||||||
"updatedAt" TIMESTAMP(3) NOT NULL,
|
"updatedAt" TIMESTAMP(3) NOT NULL,
|
||||||
|
|
||||||
CONSTRAINT "CustomerBranch_pkey" PRIMARY KEY ("id")
|
CONSTRAINT "CustomerBranch_pkey" PRIMARY KEY ("id")
|
||||||
|
|
@ -200,42 +316,70 @@ CREATE TABLE "Employee" (
|
||||||
"dateOfBirth" TIMESTAMP(3) NOT NULL,
|
"dateOfBirth" TIMESTAMP(3) NOT NULL,
|
||||||
"gender" TEXT NOT NULL,
|
"gender" TEXT NOT NULL,
|
||||||
"nationality" TEXT NOT NULL,
|
"nationality" TEXT NOT NULL,
|
||||||
"address" TEXT NOT NULL,
|
"address" TEXT,
|
||||||
"addressEN" TEXT NOT NULL,
|
"addressEN" TEXT,
|
||||||
"provinceId" TEXT,
|
"provinceId" TEXT,
|
||||||
"districtId" TEXT,
|
"districtId" TEXT,
|
||||||
"subDistrictId" TEXT,
|
"subDistrictId" TEXT,
|
||||||
"zipCode" TEXT NOT NULL,
|
"zipCode" TEXT NOT NULL,
|
||||||
"email" TEXT NOT NULL,
|
"passportType" TEXT NOT NULL,
|
||||||
"telephoneNo" TEXT NOT NULL,
|
"passportNumber" TEXT NOT NULL,
|
||||||
"arrivalBarricade" TEXT NOT NULL,
|
"passportIssueDate" TIMESTAMP(3) NOT NULL,
|
||||||
"arrivalCardNo" TEXT NOT NULL,
|
"passportExpiryDate" TIMESTAMP(3) NOT NULL,
|
||||||
|
"passportIssuingCountry" TEXT NOT NULL,
|
||||||
|
"passportIssuingPlace" TEXT NOT NULL,
|
||||||
|
"previousPassportReference" TEXT,
|
||||||
|
"visaType" TEXT,
|
||||||
|
"visaNumber" TEXT,
|
||||||
|
"visaIssueDate" TIMESTAMP(3),
|
||||||
|
"visaExpiryDate" TIMESTAMP(3),
|
||||||
|
"visaIssuingPlace" TEXT,
|
||||||
|
"visaStayUntilDate" TIMESTAMP(3),
|
||||||
|
"tm6Number" TEXT,
|
||||||
|
"entryDate" TIMESTAMP(3),
|
||||||
|
"workerStatus" TEXT,
|
||||||
"customerBranchId" TEXT,
|
"customerBranchId" TEXT,
|
||||||
"status" "Status" NOT NULL DEFAULT 'CREATED',
|
"status" "Status" NOT NULL DEFAULT 'CREATED',
|
||||||
|
"statusOrder" INTEGER NOT NULL DEFAULT 0,
|
||||||
"createdBy" TEXT,
|
"createdBy" TEXT,
|
||||||
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||||
"updateBy" TEXT,
|
"updatedBy" TEXT,
|
||||||
"updatedAt" TIMESTAMP(3) NOT NULL,
|
"updatedAt" TIMESTAMP(3) NOT NULL,
|
||||||
|
|
||||||
CONSTRAINT "Employee_pkey" PRIMARY KEY ("id")
|
CONSTRAINT "Employee_pkey" PRIMARY KEY ("id")
|
||||||
);
|
);
|
||||||
|
|
||||||
|
-- CreateTable
|
||||||
|
CREATE TABLE "EmployeeHistory" (
|
||||||
|
"id" TEXT NOT NULL,
|
||||||
|
"field" TEXT NOT NULL,
|
||||||
|
"valueBefore" JSONB NOT NULL,
|
||||||
|
"valueAfter" JSONB NOT NULL,
|
||||||
|
"timestamp" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
"updatedByUserId" TEXT,
|
||||||
|
"updatedBy" TEXT,
|
||||||
|
"updatedAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
"masterId" TEXT NOT NULL,
|
||||||
|
|
||||||
|
CONSTRAINT "EmployeeHistory_pkey" PRIMARY KEY ("id")
|
||||||
|
);
|
||||||
|
|
||||||
-- CreateTable
|
-- CreateTable
|
||||||
CREATE TABLE "EmployeeCheckup" (
|
CREATE TABLE "EmployeeCheckup" (
|
||||||
"id" TEXT NOT NULL,
|
"id" TEXT NOT NULL,
|
||||||
"employeeId" TEXT NOT NULL,
|
"employeeId" TEXT NOT NULL,
|
||||||
"checkupResult" TEXT NOT NULL,
|
"checkupResult" TEXT,
|
||||||
"checkupType" TEXT NOT NULL,
|
"checkupType" TEXT,
|
||||||
"provinceId" TEXT,
|
"provinceId" TEXT,
|
||||||
"hospitalName" TEXT NOT NULL,
|
"hospitalName" TEXT,
|
||||||
"remark" TEXT NOT NULL,
|
"remark" TEXT,
|
||||||
"medicalBenefitScheme" TEXT NOT NULL,
|
"medicalBenefitScheme" TEXT,
|
||||||
"insuranceCompany" TEXT NOT NULL,
|
"insuranceCompany" TEXT,
|
||||||
"coverageStartDate" TIMESTAMP(3) NOT NULL,
|
"coverageStartDate" TIMESTAMP(3),
|
||||||
"coverageExpireDate" TIMESTAMP(3) NOT NULL,
|
"coverageExpireDate" TIMESTAMP(3),
|
||||||
"createdBy" TEXT,
|
"createdBy" TEXT,
|
||||||
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||||
"updateBy" TEXT,
|
"updatedBy" TEXT,
|
||||||
"updatedAt" TIMESTAMP(3) NOT NULL,
|
"updatedAt" TIMESTAMP(3) NOT NULL,
|
||||||
|
|
||||||
CONSTRAINT "EmployeeCheckup_pkey" PRIMARY KEY ("id")
|
CONSTRAINT "EmployeeCheckup_pkey" PRIMARY KEY ("id")
|
||||||
|
|
@ -245,17 +389,18 @@ CREATE TABLE "EmployeeCheckup" (
|
||||||
CREATE TABLE "EmployeeWork" (
|
CREATE TABLE "EmployeeWork" (
|
||||||
"id" TEXT NOT NULL,
|
"id" TEXT NOT NULL,
|
||||||
"employeeId" TEXT NOT NULL,
|
"employeeId" TEXT NOT NULL,
|
||||||
"ownerName" TEXT NOT NULL,
|
"ownerName" TEXT,
|
||||||
"positionName" TEXT NOT NULL,
|
"positionName" TEXT,
|
||||||
"jobType" TEXT NOT NULL,
|
"jobType" TEXT,
|
||||||
"workplace" TEXT NOT NULL,
|
"workplace" TEXT,
|
||||||
"workPermitNo" TEXT NOT NULL,
|
"workPermitNo" TEXT,
|
||||||
"workPermitIssuDate" TIMESTAMP(3) NOT NULL,
|
"workPermitIssuDate" TIMESTAMP(3),
|
||||||
"workPermitExpireDate" TIMESTAMP(3) NOT NULL,
|
"workPermitExpireDate" TIMESTAMP(3),
|
||||||
"workEndDate" TIMESTAMP(3) NOT NULL,
|
"workEndDate" TIMESTAMP(3),
|
||||||
|
"remark" TEXT,
|
||||||
"createdBy" TEXT,
|
"createdBy" TEXT,
|
||||||
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||||
"updateBy" TEXT,
|
"updatedBy" TEXT,
|
||||||
"updatedAt" TIMESTAMP(3) NOT NULL,
|
"updatedAt" TIMESTAMP(3) NOT NULL,
|
||||||
|
|
||||||
CONSTRAINT "EmployeeWork_pkey" PRIMARY KEY ("id")
|
CONSTRAINT "EmployeeWork_pkey" PRIMARY KEY ("id")
|
||||||
|
|
@ -265,13 +410,20 @@ CREATE TABLE "EmployeeWork" (
|
||||||
CREATE TABLE "EmployeeOtherInfo" (
|
CREATE TABLE "EmployeeOtherInfo" (
|
||||||
"id" TEXT NOT NULL,
|
"id" TEXT NOT NULL,
|
||||||
"employeeId" TEXT NOT NULL,
|
"employeeId" TEXT NOT NULL,
|
||||||
"citizenId" TEXT NOT NULL,
|
"citizenId" TEXT,
|
||||||
"fatherFullName" TEXT NOT NULL,
|
"fatherBirthPlace" TEXT,
|
||||||
"motherFullName" TEXT NOT NULL,
|
"fatherFirstName" TEXT,
|
||||||
"birthPlace" TEXT NOT NULL,
|
"fatherLastName" TEXT,
|
||||||
|
"motherBirthPlace" TEXT,
|
||||||
|
"motherFirstName" TEXT,
|
||||||
|
"motherLastName" TEXT,
|
||||||
|
"fatherFirstNameEN" TEXT,
|
||||||
|
"fatherLastNameEN" TEXT,
|
||||||
|
"motherFirstNameEN" TEXT,
|
||||||
|
"motherLastNameEN" TEXT,
|
||||||
"createdBy" TEXT,
|
"createdBy" TEXT,
|
||||||
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||||
"updateBy" TEXT,
|
"updatedBy" TEXT,
|
||||||
"updatedAt" TIMESTAMP(3) NOT NULL,
|
"updatedAt" TIMESTAMP(3) NOT NULL,
|
||||||
|
|
||||||
CONSTRAINT "EmployeeOtherInfo_pkey" PRIMARY KEY ("id")
|
CONSTRAINT "EmployeeOtherInfo_pkey" PRIMARY KEY ("id")
|
||||||
|
|
@ -283,10 +435,12 @@ CREATE TABLE "Service" (
|
||||||
"code" TEXT NOT NULL,
|
"code" TEXT NOT NULL,
|
||||||
"name" TEXT NOT NULL,
|
"name" TEXT NOT NULL,
|
||||||
"detail" TEXT NOT NULL,
|
"detail" TEXT NOT NULL,
|
||||||
|
"attributes" JSONB,
|
||||||
"status" "Status" NOT NULL DEFAULT 'CREATED',
|
"status" "Status" NOT NULL DEFAULT 'CREATED',
|
||||||
|
"statusOrder" INTEGER NOT NULL DEFAULT 0,
|
||||||
"createdBy" TEXT,
|
"createdBy" TEXT,
|
||||||
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||||
"updateBy" TEXT,
|
"updatedBy" TEXT,
|
||||||
"updatedAt" TIMESTAMP(3) NOT NULL,
|
"updatedAt" TIMESTAMP(3) NOT NULL,
|
||||||
|
|
||||||
CONSTRAINT "Service_pkey" PRIMARY KEY ("id")
|
CONSTRAINT "Service_pkey" PRIMARY KEY ("id")
|
||||||
|
|
@ -297,11 +451,13 @@ CREATE TABLE "Work" (
|
||||||
"id" TEXT NOT NULL,
|
"id" TEXT NOT NULL,
|
||||||
"order" INTEGER NOT NULL,
|
"order" INTEGER NOT NULL,
|
||||||
"name" TEXT NOT NULL,
|
"name" TEXT NOT NULL,
|
||||||
"serviceId" TEXT NOT NULL,
|
"attributes" JSONB,
|
||||||
"status" "Status" NOT NULL DEFAULT 'CREATED',
|
"status" "Status" NOT NULL DEFAULT 'CREATED',
|
||||||
|
"statusOrder" INTEGER NOT NULL DEFAULT 0,
|
||||||
|
"serviceId" TEXT,
|
||||||
"createdBy" TEXT,
|
"createdBy" TEXT,
|
||||||
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||||
"updateBy" TEXT,
|
"updatedBy" TEXT,
|
||||||
"updatedAt" TIMESTAMP(3) NOT NULL,
|
"updatedAt" TIMESTAMP(3) NOT NULL,
|
||||||
|
|
||||||
CONSTRAINT "Work_pkey" PRIMARY KEY ("id")
|
CONSTRAINT "Work_pkey" PRIMARY KEY ("id")
|
||||||
|
|
@ -309,14 +465,15 @@ CREATE TABLE "Work" (
|
||||||
|
|
||||||
-- CreateTable
|
-- CreateTable
|
||||||
CREATE TABLE "WorkProduct" (
|
CREATE TABLE "WorkProduct" (
|
||||||
"id" TEXT NOT NULL,
|
"order" INTEGER NOT NULL,
|
||||||
"workId" TEXT NOT NULL,
|
"workId" TEXT NOT NULL,
|
||||||
|
"productId" TEXT NOT NULL,
|
||||||
"createdBy" TEXT,
|
"createdBy" TEXT,
|
||||||
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||||
"updateBy" TEXT,
|
"updatedBy" TEXT,
|
||||||
"updatedAt" TIMESTAMP(3) NOT NULL,
|
"updatedAt" TIMESTAMP(3) NOT NULL,
|
||||||
|
|
||||||
CONSTRAINT "WorkProduct_pkey" PRIMARY KEY ("id")
|
CONSTRAINT "WorkProduct_pkey" PRIMARY KEY ("workId","productId")
|
||||||
);
|
);
|
||||||
|
|
||||||
-- CreateTable
|
-- CreateTable
|
||||||
|
|
@ -327,9 +484,10 @@ CREATE TABLE "ProductGroup" (
|
||||||
"detail" TEXT NOT NULL,
|
"detail" TEXT NOT NULL,
|
||||||
"remark" TEXT NOT NULL,
|
"remark" TEXT NOT NULL,
|
||||||
"status" "Status" NOT NULL DEFAULT 'CREATED',
|
"status" "Status" NOT NULL DEFAULT 'CREATED',
|
||||||
|
"statusOrder" INTEGER NOT NULL DEFAULT 0,
|
||||||
"createdBy" TEXT,
|
"createdBy" TEXT,
|
||||||
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||||
"updateBy" TEXT,
|
"updatedBy" TEXT,
|
||||||
"updatedAt" TIMESTAMP(3) NOT NULL,
|
"updatedAt" TIMESTAMP(3) NOT NULL,
|
||||||
|
|
||||||
CONSTRAINT "ProductGroup_pkey" PRIMARY KEY ("id")
|
CONSTRAINT "ProductGroup_pkey" PRIMARY KEY ("id")
|
||||||
|
|
@ -343,10 +501,12 @@ CREATE TABLE "ProductType" (
|
||||||
"detail" TEXT NOT NULL,
|
"detail" TEXT NOT NULL,
|
||||||
"remark" TEXT NOT NULL,
|
"remark" TEXT NOT NULL,
|
||||||
"status" "Status" NOT NULL DEFAULT 'CREATED',
|
"status" "Status" NOT NULL DEFAULT 'CREATED',
|
||||||
|
"statusOrder" INTEGER NOT NULL DEFAULT 0,
|
||||||
"createdBy" TEXT,
|
"createdBy" TEXT,
|
||||||
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||||
"updateBy" TEXT,
|
"updatedBy" TEXT,
|
||||||
"updatedAt" TIMESTAMP(3) NOT NULL,
|
"updatedAt" TIMESTAMP(3) NOT NULL,
|
||||||
|
"productGroupId" TEXT NOT NULL,
|
||||||
|
|
||||||
CONSTRAINT "ProductType_pkey" PRIMARY KEY ("id")
|
CONSTRAINT "ProductType_pkey" PRIMARY KEY ("id")
|
||||||
);
|
);
|
||||||
|
|
@ -357,22 +517,49 @@ CREATE TABLE "Product" (
|
||||||
"code" TEXT NOT NULL,
|
"code" TEXT NOT NULL,
|
||||||
"name" TEXT NOT NULL,
|
"name" TEXT NOT NULL,
|
||||||
"detail" TEXT NOT NULL,
|
"detail" TEXT NOT NULL,
|
||||||
"process" TEXT NOT NULL,
|
"process" INTEGER NOT NULL,
|
||||||
"price" INTEGER NOT NULL,
|
"price" DOUBLE PRECISION NOT NULL,
|
||||||
"agentPrice" INTEGER NOT NULL,
|
"agentPrice" DOUBLE PRECISION NOT NULL,
|
||||||
"serviceCharge" INTEGER NOT NULL,
|
"serviceCharge" DOUBLE PRECISION NOT NULL,
|
||||||
"imageUrl" TEXT NOT NULL,
|
|
||||||
"status" "Status" NOT NULL DEFAULT 'CREATED',
|
"status" "Status" NOT NULL DEFAULT 'CREATED',
|
||||||
|
"statusOrder" INTEGER NOT NULL DEFAULT 0,
|
||||||
|
"remark" TEXT,
|
||||||
"productTypeId" TEXT,
|
"productTypeId" TEXT,
|
||||||
"productGroupId" TEXT,
|
|
||||||
"createdBy" TEXT,
|
"createdBy" TEXT,
|
||||||
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||||
"updateBy" TEXT,
|
"updatedBy" TEXT,
|
||||||
"updatedAt" TIMESTAMP(3) NOT NULL,
|
"updatedAt" TIMESTAMP(3) NOT NULL,
|
||||||
|
|
||||||
CONSTRAINT "Product_pkey" PRIMARY KEY ("id")
|
CONSTRAINT "Product_pkey" PRIMARY KEY ("id")
|
||||||
);
|
);
|
||||||
|
|
||||||
|
-- CreateIndex
|
||||||
|
CREATE UNIQUE INDEX "RunningNo_key_key" ON "RunningNo"("key");
|
||||||
|
|
||||||
|
-- AddForeignKey
|
||||||
|
ALTER TABLE "Menu" ADD CONSTRAINT "Menu_parentId_fkey" FOREIGN KEY ("parentId") REFERENCES "Menu"("id") ON DELETE SET NULL ON UPDATE CASCADE;
|
||||||
|
|
||||||
|
-- AddForeignKey
|
||||||
|
ALTER TABLE "RoleMenuPermission" ADD CONSTRAINT "RoleMenuPermission_menuId_fkey" FOREIGN KEY ("menuId") REFERENCES "Menu"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
|
||||||
|
|
||||||
|
-- AddForeignKey
|
||||||
|
ALTER TABLE "UserMenuPermission" ADD CONSTRAINT "UserMenuPermission_menuId_fkey" FOREIGN KEY ("menuId") REFERENCES "Menu"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
|
||||||
|
|
||||||
|
-- AddForeignKey
|
||||||
|
ALTER TABLE "UserMenuPermission" ADD CONSTRAINT "UserMenuPermission_userId_fkey" FOREIGN KEY ("userId") REFERENCES "User"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
|
||||||
|
|
||||||
|
-- AddForeignKey
|
||||||
|
ALTER TABLE "MenuComponent" ADD CONSTRAINT "MenuComponent_menuId_fkey" FOREIGN KEY ("menuId") REFERENCES "Menu"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
|
||||||
|
|
||||||
|
-- AddForeignKey
|
||||||
|
ALTER TABLE "RoleMenuComponentPermission" ADD CONSTRAINT "RoleMenuComponentPermission_menuComponentId_fkey" FOREIGN KEY ("menuComponentId") REFERENCES "MenuComponent"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
|
||||||
|
|
||||||
|
-- AddForeignKey
|
||||||
|
ALTER TABLE "UserMenuComponentPermission" ADD CONSTRAINT "UserMenuComponentPermission_userId_fkey" FOREIGN KEY ("userId") REFERENCES "User"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
|
||||||
|
|
||||||
|
-- AddForeignKey
|
||||||
|
ALTER TABLE "UserMenuComponentPermission" ADD CONSTRAINT "UserMenuComponentPermission_menuComponentId_fkey" FOREIGN KEY ("menuComponentId") REFERENCES "MenuComponent"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
|
||||||
|
|
||||||
-- AddForeignKey
|
-- AddForeignKey
|
||||||
ALTER TABLE "District" ADD CONSTRAINT "District_provinceId_fkey" FOREIGN KEY ("provinceId") REFERENCES "Province"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
ALTER TABLE "District" ADD CONSTRAINT "District_provinceId_fkey" FOREIGN KEY ("provinceId") REFERENCES "Province"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||||
|
|
||||||
|
|
@ -433,6 +620,12 @@ ALTER TABLE "Employee" ADD CONSTRAINT "Employee_subDistrictId_fkey" FOREIGN KEY
|
||||||
-- AddForeignKey
|
-- AddForeignKey
|
||||||
ALTER TABLE "Employee" ADD CONSTRAINT "Employee_customerBranchId_fkey" FOREIGN KEY ("customerBranchId") REFERENCES "CustomerBranch"("id") ON DELETE SET NULL ON UPDATE CASCADE;
|
ALTER TABLE "Employee" ADD CONSTRAINT "Employee_customerBranchId_fkey" FOREIGN KEY ("customerBranchId") REFERENCES "CustomerBranch"("id") ON DELETE SET NULL ON UPDATE CASCADE;
|
||||||
|
|
||||||
|
-- AddForeignKey
|
||||||
|
ALTER TABLE "EmployeeHistory" ADD CONSTRAINT "EmployeeHistory_updatedByUserId_fkey" FOREIGN KEY ("updatedByUserId") REFERENCES "User"("id") ON DELETE SET NULL ON UPDATE CASCADE;
|
||||||
|
|
||||||
|
-- AddForeignKey
|
||||||
|
ALTER TABLE "EmployeeHistory" ADD CONSTRAINT "EmployeeHistory_masterId_fkey" FOREIGN KEY ("masterId") REFERENCES "Employee"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||||
|
|
||||||
-- AddForeignKey
|
-- AddForeignKey
|
||||||
ALTER TABLE "EmployeeCheckup" ADD CONSTRAINT "EmployeeCheckup_employeeId_fkey" FOREIGN KEY ("employeeId") REFERENCES "Employee"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
ALTER TABLE "EmployeeCheckup" ADD CONSTRAINT "EmployeeCheckup_employeeId_fkey" FOREIGN KEY ("employeeId") REFERENCES "Employee"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||||
|
|
||||||
|
|
@ -452,7 +645,10 @@ ALTER TABLE "Work" ADD CONSTRAINT "Work_serviceId_fkey" FOREIGN KEY ("serviceId"
|
||||||
ALTER TABLE "WorkProduct" ADD CONSTRAINT "WorkProduct_workId_fkey" FOREIGN KEY ("workId") REFERENCES "Work"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
ALTER TABLE "WorkProduct" ADD CONSTRAINT "WorkProduct_workId_fkey" FOREIGN KEY ("workId") REFERENCES "Work"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||||
|
|
||||||
-- AddForeignKey
|
-- AddForeignKey
|
||||||
ALTER TABLE "Product" ADD CONSTRAINT "Product_productTypeId_fkey" FOREIGN KEY ("productTypeId") REFERENCES "ProductType"("id") ON DELETE SET NULL ON UPDATE CASCADE;
|
ALTER TABLE "WorkProduct" ADD CONSTRAINT "WorkProduct_productId_fkey" FOREIGN KEY ("productId") REFERENCES "Product"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||||
|
|
||||||
-- AddForeignKey
|
-- AddForeignKey
|
||||||
ALTER TABLE "Product" ADD CONSTRAINT "Product_productGroupId_fkey" FOREIGN KEY ("productGroupId") REFERENCES "ProductGroup"("id") ON DELETE SET NULL ON UPDATE CASCADE;
|
ALTER TABLE "ProductType" ADD CONSTRAINT "ProductType_productGroupId_fkey" FOREIGN KEY ("productGroupId") REFERENCES "ProductGroup"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||||
|
|
||||||
|
-- AddForeignKey
|
||||||
|
ALTER TABLE "Product" ADD CONSTRAINT "Product_productTypeId_fkey" FOREIGN KEY ("productTypeId") REFERENCES "ProductType"("id") ON DELETE SET NULL ON UPDATE CASCADE;
|
||||||
|
|
@ -2,6 +2,11 @@ generator client {
|
||||||
provider = "prisma-client-js"
|
provider = "prisma-client-js"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
generator kysely {
|
||||||
|
provider = "prisma-kysely"
|
||||||
|
output = "../src/generated/kysely"
|
||||||
|
}
|
||||||
|
|
||||||
datasource db {
|
datasource db {
|
||||||
provider = "postgresql"
|
provider = "postgresql"
|
||||||
url = env("DATABASE_URL")
|
url = env("DATABASE_URL")
|
||||||
|
|
@ -17,7 +22,7 @@ model Menu {
|
||||||
|
|
||||||
createdBy String?
|
createdBy String?
|
||||||
createdAt DateTime @default(now())
|
createdAt DateTime @default(now())
|
||||||
updateBy String?
|
updatedBy String?
|
||||||
updatedAt DateTime @updatedAt
|
updatedAt DateTime @updatedAt
|
||||||
|
|
||||||
parent Menu? @relation(name: "MenuRelation", fields: [parentId], references: [id])
|
parent Menu? @relation(name: "MenuRelation", fields: [parentId], references: [id])
|
||||||
|
|
@ -40,7 +45,7 @@ model RoleMenuPermission {
|
||||||
|
|
||||||
createdBy String?
|
createdBy String?
|
||||||
createdAt DateTime @default(now())
|
createdAt DateTime @default(now())
|
||||||
updateBy String?
|
updatedBy String?
|
||||||
updatedAt DateTime @updatedAt
|
updatedAt DateTime @updatedAt
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -57,7 +62,7 @@ model UserMenuPermission {
|
||||||
|
|
||||||
createdBy String?
|
createdBy String?
|
||||||
createdAt DateTime @default(now())
|
createdAt DateTime @default(now())
|
||||||
updateBy String?
|
updatedBy String?
|
||||||
updatedAt DateTime @updatedAt
|
updatedAt DateTime @updatedAt
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -72,7 +77,7 @@ model MenuComponent {
|
||||||
|
|
||||||
createdBy String?
|
createdBy String?
|
||||||
createdAt DateTime @default(now())
|
createdAt DateTime @default(now())
|
||||||
updateBy String?
|
updatedBy String?
|
||||||
updatedAt DateTime @updatedAt
|
updatedAt DateTime @updatedAt
|
||||||
roleMenuComponentPermission RoleMenuComponentPermission[]
|
roleMenuComponentPermission RoleMenuComponentPermission[]
|
||||||
userMennuComponentPermission UserMenuComponentPermission[]
|
userMennuComponentPermission UserMenuComponentPermission[]
|
||||||
|
|
@ -89,10 +94,15 @@ model RoleMenuComponentPermission {
|
||||||
|
|
||||||
createdBy String?
|
createdBy String?
|
||||||
createdAt DateTime @default(now())
|
createdAt DateTime @default(now())
|
||||||
updateBy String?
|
updatedBy String?
|
||||||
updatedAt DateTime @updatedAt
|
updatedAt DateTime @updatedAt
|
||||||
}
|
}
|
||||||
|
|
||||||
|
model RunningNo {
|
||||||
|
key String @id @unique
|
||||||
|
value Int
|
||||||
|
}
|
||||||
|
|
||||||
model UserMenuComponentPermission {
|
model UserMenuComponentPermission {
|
||||||
id String @id @default(uuid())
|
id String @id @default(uuid())
|
||||||
|
|
||||||
|
|
@ -106,7 +116,7 @@ model UserMenuComponentPermission {
|
||||||
|
|
||||||
createdBy String?
|
createdBy String?
|
||||||
createdAt DateTime @default(now())
|
createdAt DateTime @default(now())
|
||||||
updateBy String?
|
updatedBy String?
|
||||||
updatedAt DateTime @updatedAt
|
updatedAt DateTime @updatedAt
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -117,7 +127,7 @@ model Province {
|
||||||
|
|
||||||
createdBy String?
|
createdBy String?
|
||||||
createdAt DateTime @default(now())
|
createdAt DateTime @default(now())
|
||||||
updateBy String?
|
updatedBy String?
|
||||||
updatedAt DateTime @updatedAt
|
updatedAt DateTime @updatedAt
|
||||||
|
|
||||||
district District[]
|
district District[]
|
||||||
|
|
@ -138,7 +148,7 @@ model District {
|
||||||
|
|
||||||
createdBy String?
|
createdBy String?
|
||||||
createdAt DateTime @default(now())
|
createdAt DateTime @default(now())
|
||||||
updateBy String?
|
updatedBy String?
|
||||||
updatedAt DateTime @updatedAt
|
updatedAt DateTime @updatedAt
|
||||||
|
|
||||||
subDistrict SubDistrict[]
|
subDistrict SubDistrict[]
|
||||||
|
|
@ -159,7 +169,7 @@ model SubDistrict {
|
||||||
|
|
||||||
createdBy String?
|
createdBy String?
|
||||||
createdAt DateTime @default(now())
|
createdAt DateTime @default(now())
|
||||||
updateBy String?
|
updatedBy String?
|
||||||
updatedAt DateTime @updatedAt
|
updatedAt DateTime @updatedAt
|
||||||
|
|
||||||
branch Branch[]
|
branch Branch[]
|
||||||
|
|
@ -207,11 +217,12 @@ model Branch {
|
||||||
headOffice Branch? @relation(name: "HeadOfficeRelation", fields: [headOfficeId], references: [id])
|
headOffice Branch? @relation(name: "HeadOfficeRelation", fields: [headOfficeId], references: [id])
|
||||||
headOfficeId String?
|
headOfficeId String?
|
||||||
|
|
||||||
status Status @default(CREATED)
|
status Status @default(CREATED)
|
||||||
|
statusOrder Int @default(0)
|
||||||
|
|
||||||
createdBy String?
|
createdBy String?
|
||||||
createdAt DateTime @default(now())
|
createdAt DateTime @default(now())
|
||||||
updateBy String?
|
updatedBy String?
|
||||||
updatedAt DateTime @updatedAt
|
updatedAt DateTime @updatedAt
|
||||||
|
|
||||||
branch Branch[] @relation(name: "HeadOfficeRelation")
|
branch Branch[] @relation(name: "HeadOfficeRelation")
|
||||||
|
|
@ -228,7 +239,7 @@ model BranchContact {
|
||||||
|
|
||||||
createdBy String?
|
createdBy String?
|
||||||
createdAt DateTime @default(now())
|
createdAt DateTime @default(now())
|
||||||
updateBy String?
|
updatedBy String?
|
||||||
updatedAt DateTime @updatedAt
|
updatedAt DateTime @updatedAt
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -243,7 +254,7 @@ model BranchUser {
|
||||||
|
|
||||||
createdBy String?
|
createdBy String?
|
||||||
createdAt DateTime @default(now())
|
createdAt DateTime @default(now())
|
||||||
updateBy String?
|
updatedBy String?
|
||||||
updatedAt DateTime @updatedAt
|
updatedAt DateTime @updatedAt
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -307,16 +318,18 @@ model User {
|
||||||
|
|
||||||
birthDate DateTime?
|
birthDate DateTime?
|
||||||
|
|
||||||
status Status @default(CREATED)
|
status Status @default(CREATED)
|
||||||
|
statusOrder Int @default(0)
|
||||||
|
|
||||||
createdBy String?
|
createdBy String?
|
||||||
createdAt DateTime @default(now())
|
createdAt DateTime @default(now())
|
||||||
updateBy String?
|
updatedBy String?
|
||||||
updatedAt DateTime @updatedAt
|
updatedAt DateTime @updatedAt
|
||||||
|
|
||||||
branch BranchUser[]
|
branch BranchUser[]
|
||||||
userMenuPermission UserMenuPermission[]
|
userMenuPermission UserMenuPermission[]
|
||||||
userMenuComponentPermission UserMenuComponentPermission[]
|
userMenuComponentPermission UserMenuComponentPermission[]
|
||||||
|
employeeHistory EmployeeHistory[]
|
||||||
}
|
}
|
||||||
|
|
||||||
enum CustomerType {
|
enum CustomerType {
|
||||||
|
|
@ -327,15 +340,19 @@ enum CustomerType {
|
||||||
model Customer {
|
model Customer {
|
||||||
id String @id @default(uuid())
|
id String @id @default(uuid())
|
||||||
code String
|
code String
|
||||||
|
personName String
|
||||||
|
personNameEN String?
|
||||||
customerType CustomerType
|
customerType CustomerType
|
||||||
customerName String
|
customerName String
|
||||||
customerNameEN String
|
customerNameEN String
|
||||||
|
taxNo String?
|
||||||
|
|
||||||
status Status @default(CREATED)
|
status Status @default(CREATED)
|
||||||
|
statusOrder Int @default(0)
|
||||||
|
|
||||||
createdBy String?
|
createdBy String?
|
||||||
createdAt DateTime @default(now())
|
createdAt DateTime @default(now())
|
||||||
updateBy String?
|
updatedBy String?
|
||||||
updatedAt DateTime @updatedAt
|
updatedAt DateTime @updatedAt
|
||||||
|
|
||||||
branch CustomerBranch[]
|
branch CustomerBranch[]
|
||||||
|
|
@ -343,7 +360,8 @@ model Customer {
|
||||||
|
|
||||||
model CustomerBranch {
|
model CustomerBranch {
|
||||||
id String @id @default(uuid())
|
id String @id @default(uuid())
|
||||||
branchNo String
|
branchNo Int
|
||||||
|
code String
|
||||||
legalPersonNo String
|
legalPersonNo String
|
||||||
|
|
||||||
name String
|
name String
|
||||||
|
|
@ -352,7 +370,7 @@ model CustomerBranch {
|
||||||
customer Customer @relation(fields: [customerId], references: [id], onDelete: Cascade)
|
customer Customer @relation(fields: [customerId], references: [id], onDelete: Cascade)
|
||||||
customerId String
|
customerId String
|
||||||
|
|
||||||
taxNo String
|
taxNo String?
|
||||||
registerName String
|
registerName String
|
||||||
registerDate DateTime
|
registerDate DateTime
|
||||||
authorizedCapital String
|
authorizedCapital String
|
||||||
|
|
@ -374,14 +392,22 @@ model CustomerBranch {
|
||||||
email String
|
email String
|
||||||
telephoneNo String
|
telephoneNo String
|
||||||
|
|
||||||
latitude String
|
employmentOffice String
|
||||||
longitude String
|
bussinessType String
|
||||||
|
bussinessTypeEN String
|
||||||
|
jobPosition String
|
||||||
|
jobPositionEN String
|
||||||
|
jobDescription String
|
||||||
|
saleEmployee String
|
||||||
|
payDate DateTime
|
||||||
|
wageRate Int
|
||||||
|
|
||||||
status Status @default(CREATED)
|
status Status @default(CREATED)
|
||||||
|
statusOrder Int @default(0)
|
||||||
|
|
||||||
createdBy String?
|
createdBy String?
|
||||||
createdAt DateTime @default(now())
|
createdAt DateTime @default(now())
|
||||||
updateBy String?
|
updatedBy String?
|
||||||
updatedAt DateTime @updatedAt
|
updatedAt DateTime @updatedAt
|
||||||
|
|
||||||
employee Employee[]
|
employee Employee[]
|
||||||
|
|
@ -401,8 +427,8 @@ model Employee {
|
||||||
gender String
|
gender String
|
||||||
nationality String
|
nationality String
|
||||||
|
|
||||||
address String
|
address String?
|
||||||
addressEN String
|
addressEN String?
|
||||||
|
|
||||||
province Province? @relation(fields: [provinceId], references: [id], onDelete: SetNull)
|
province Province? @relation(fields: [provinceId], references: [id], onDelete: SetNull)
|
||||||
provinceId String?
|
provinceId String?
|
||||||
|
|
@ -415,25 +441,57 @@ model Employee {
|
||||||
|
|
||||||
zipCode String
|
zipCode String
|
||||||
|
|
||||||
email String
|
passportType String
|
||||||
telephoneNo String
|
passportNumber String
|
||||||
|
passportIssueDate DateTime
|
||||||
|
passportExpiryDate DateTime
|
||||||
|
passportIssuingCountry String
|
||||||
|
passportIssuingPlace String
|
||||||
|
previousPassportReference String?
|
||||||
|
|
||||||
arrivalBarricade String
|
visaType String?
|
||||||
arrivalCardNo String
|
visaNumber String?
|
||||||
|
visaIssueDate DateTime?
|
||||||
|
visaExpiryDate DateTime?
|
||||||
|
visaIssuingPlace String?
|
||||||
|
visaStayUntilDate DateTime?
|
||||||
|
tm6Number String?
|
||||||
|
entryDate DateTime?
|
||||||
|
workerStatus String?
|
||||||
|
|
||||||
customerBranch CustomerBranch? @relation(fields: [customerBranchId], references: [id], onDelete: SetNull)
|
customerBranch CustomerBranch? @relation(fields: [customerBranchId], references: [id], onDelete: SetNull)
|
||||||
customerBranchId String?
|
customerBranchId String?
|
||||||
|
|
||||||
status Status @default(CREATED)
|
status Status @default(CREATED)
|
||||||
|
statusOrder Int @default(0)
|
||||||
|
|
||||||
createdBy String?
|
createdBy String?
|
||||||
createdAt DateTime @default(now())
|
createdAt DateTime @default(now())
|
||||||
updateBy String?
|
updatedBy String?
|
||||||
updatedAt DateTime @updatedAt
|
updatedAt DateTime @updatedAt
|
||||||
|
|
||||||
employeeCheckup EmployeeCheckup[]
|
employeeCheckup EmployeeCheckup[]
|
||||||
employeeWork EmployeeWork[]
|
employeeWork EmployeeWork[]
|
||||||
employeeOtherInfo EmployeeOtherInfo[]
|
employeeOtherInfo EmployeeOtherInfo[]
|
||||||
|
|
||||||
|
editHistory EmployeeHistory[]
|
||||||
|
}
|
||||||
|
|
||||||
|
model EmployeeHistory {
|
||||||
|
id String @id @default(uuid())
|
||||||
|
field String
|
||||||
|
valueBefore Json
|
||||||
|
valueAfter Json
|
||||||
|
|
||||||
|
timestamp DateTime @default(now())
|
||||||
|
|
||||||
|
updatedByUserId String?
|
||||||
|
updatedByUser User? @relation(fields: [updatedByUserId], references: [id])
|
||||||
|
updatedBy String?
|
||||||
|
updatedAt DateTime @default(now())
|
||||||
|
|
||||||
|
masterId String
|
||||||
|
master Employee @relation(fields: [masterId], references: [id], onDelete: Cascade)
|
||||||
}
|
}
|
||||||
|
|
||||||
model EmployeeCheckup {
|
model EmployeeCheckup {
|
||||||
|
|
@ -442,22 +500,22 @@ model EmployeeCheckup {
|
||||||
employee Employee @relation(fields: [employeeId], references: [id], onDelete: Cascade)
|
employee Employee @relation(fields: [employeeId], references: [id], onDelete: Cascade)
|
||||||
employeeId String
|
employeeId String
|
||||||
|
|
||||||
checkupResult String
|
checkupResult String?
|
||||||
checkupType String
|
checkupType String?
|
||||||
|
|
||||||
province Province? @relation(fields: [provinceId], references: [id], onDelete: SetNull)
|
province Province? @relation(fields: [provinceId], references: [id], onDelete: SetNull)
|
||||||
provinceId String?
|
provinceId String?
|
||||||
|
|
||||||
hospitalName String
|
hospitalName String?
|
||||||
remark String
|
remark String?
|
||||||
medicalBenefitScheme String
|
medicalBenefitScheme String?
|
||||||
insuranceCompany String
|
insuranceCompany String?
|
||||||
coverageStartDate DateTime
|
coverageStartDate DateTime?
|
||||||
coverageExpireDate DateTime
|
coverageExpireDate DateTime?
|
||||||
|
|
||||||
createdBy String?
|
createdBy String?
|
||||||
createdAt DateTime @default(now())
|
createdAt DateTime @default(now())
|
||||||
updateBy String?
|
updatedBy String?
|
||||||
updatedAt DateTime @updatedAt
|
updatedAt DateTime @updatedAt
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -467,18 +525,19 @@ model EmployeeWork {
|
||||||
employee Employee @relation(fields: [employeeId], references: [id], onDelete: Cascade)
|
employee Employee @relation(fields: [employeeId], references: [id], onDelete: Cascade)
|
||||||
employeeId String
|
employeeId String
|
||||||
|
|
||||||
ownerName String
|
ownerName String?
|
||||||
positionName String
|
positionName String?
|
||||||
jobType String
|
jobType String?
|
||||||
workplace String
|
workplace String?
|
||||||
workPermitNo String
|
workPermitNo String?
|
||||||
workPermitIssuDate DateTime
|
workPermitIssuDate DateTime?
|
||||||
workPermitExpireDate DateTime
|
workPermitExpireDate DateTime?
|
||||||
workEndDate DateTime
|
workEndDate DateTime?
|
||||||
|
remark String?
|
||||||
|
|
||||||
createdBy String?
|
createdBy String?
|
||||||
createdAt DateTime @default(now())
|
createdAt DateTime @default(now())
|
||||||
updateBy String?
|
updatedBy String?
|
||||||
updatedAt DateTime @updatedAt
|
updatedAt DateTime @updatedAt
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -488,61 +547,78 @@ model EmployeeOtherInfo {
|
||||||
employee Employee @relation(fields: [employeeId], references: [id], onDelete: Cascade)
|
employee Employee @relation(fields: [employeeId], references: [id], onDelete: Cascade)
|
||||||
employeeId String
|
employeeId String
|
||||||
|
|
||||||
citizenId String
|
citizenId String?
|
||||||
fatherFullName String
|
fatherBirthPlace String?
|
||||||
motherFullName String
|
fatherFirstName String?
|
||||||
birthPlace String
|
fatherLastName String?
|
||||||
|
motherBirthPlace String?
|
||||||
|
motherFirstName String?
|
||||||
|
motherLastName String?
|
||||||
|
|
||||||
|
fatherFirstNameEN String?
|
||||||
|
fatherLastNameEN String?
|
||||||
|
motherFirstNameEN String?
|
||||||
|
motherLastNameEN String?
|
||||||
|
|
||||||
createdBy String?
|
createdBy String?
|
||||||
createdAt DateTime @default(now())
|
createdAt DateTime @default(now())
|
||||||
updateBy String?
|
updatedBy String?
|
||||||
updatedAt DateTime @updatedAt
|
updatedAt DateTime @updatedAt
|
||||||
}
|
}
|
||||||
|
|
||||||
model Service {
|
model Service {
|
||||||
id String @id @default(uuid())
|
id String @id @default(uuid())
|
||||||
|
|
||||||
code String
|
code String
|
||||||
name String
|
name String
|
||||||
detail String
|
detail String
|
||||||
|
attributes Json?
|
||||||
|
|
||||||
status Status @default(CREATED)
|
status Status @default(CREATED)
|
||||||
|
statusOrder Int @default(0)
|
||||||
|
|
||||||
|
work Work[]
|
||||||
|
|
||||||
createdBy String?
|
createdBy String?
|
||||||
createdAt DateTime @default(now())
|
createdAt DateTime @default(now())
|
||||||
updateBy String?
|
updatedBy String?
|
||||||
updatedAt DateTime @updatedAt
|
updatedAt DateTime @updatedAt
|
||||||
work Work[]
|
|
||||||
}
|
}
|
||||||
|
|
||||||
model Work {
|
model Work {
|
||||||
id String @id @default(uuid())
|
id String @id @default(uuid())
|
||||||
|
|
||||||
order Int
|
order Int
|
||||||
name String
|
name String
|
||||||
|
attributes Json?
|
||||||
|
|
||||||
service Service @relation(fields: [serviceId], references: [id], onDelete: Cascade)
|
status Status @default(CREATED)
|
||||||
serviceId String
|
statusOrder Int @default(0)
|
||||||
|
|
||||||
status Status @default(CREATED)
|
service Service? @relation(fields: [serviceId], references: [id], onDelete: Cascade)
|
||||||
|
serviceId String?
|
||||||
createdBy String?
|
|
||||||
createdAt DateTime @default(now())
|
|
||||||
updateBy String?
|
|
||||||
updatedAt DateTime @updatedAt
|
|
||||||
WorkProduct WorkProduct[]
|
|
||||||
}
|
|
||||||
|
|
||||||
model WorkProduct {
|
|
||||||
id String @id @default(uuid())
|
|
||||||
|
|
||||||
work Work @relation(fields: [workId], references: [id], onDelete: Cascade)
|
|
||||||
workId String
|
|
||||||
|
|
||||||
createdBy String?
|
createdBy String?
|
||||||
createdAt DateTime @default(now())
|
createdAt DateTime @default(now())
|
||||||
updateBy String?
|
updatedBy String?
|
||||||
updatedAt DateTime @updatedAt
|
updatedAt DateTime @updatedAt
|
||||||
|
|
||||||
|
productOnWork WorkProduct[]
|
||||||
|
}
|
||||||
|
|
||||||
|
model WorkProduct {
|
||||||
|
order Int
|
||||||
|
work Work @relation(fields: [workId], references: [id], onDelete: Cascade)
|
||||||
|
workId String
|
||||||
|
product Product @relation(fields: [productId], references: [id], onDelete: Cascade)
|
||||||
|
productId String
|
||||||
|
|
||||||
|
createdBy String?
|
||||||
|
createdAt DateTime @default(now())
|
||||||
|
updatedBy String?
|
||||||
|
updatedAt DateTime @updatedAt
|
||||||
|
|
||||||
|
@@id([workId, productId])
|
||||||
}
|
}
|
||||||
|
|
||||||
model ProductGroup {
|
model ProductGroup {
|
||||||
|
|
@ -553,13 +629,15 @@ model ProductGroup {
|
||||||
detail String
|
detail String
|
||||||
remark String
|
remark String
|
||||||
|
|
||||||
status Status @default(CREATED)
|
status Status @default(CREATED)
|
||||||
|
statusOrder Int @default(0)
|
||||||
|
|
||||||
createdBy String?
|
createdBy String?
|
||||||
createdAt DateTime @default(now())
|
createdAt DateTime @default(now())
|
||||||
updateBy String?
|
updatedBy String?
|
||||||
updatedAt DateTime @updatedAt
|
updatedAt DateTime @updatedAt
|
||||||
Product Product[]
|
|
||||||
|
type ProductType[]
|
||||||
}
|
}
|
||||||
|
|
||||||
model ProductType {
|
model ProductType {
|
||||||
|
|
@ -570,13 +648,17 @@ model ProductType {
|
||||||
detail String
|
detail String
|
||||||
remark String
|
remark String
|
||||||
|
|
||||||
status Status @default(CREATED)
|
status Status @default(CREATED)
|
||||||
|
statusOrder Int @default(0)
|
||||||
|
|
||||||
createdBy String?
|
createdBy String?
|
||||||
createdAt DateTime @default(now())
|
createdAt DateTime @default(now())
|
||||||
updateBy String?
|
updatedBy String?
|
||||||
updatedAt DateTime @updatedAt
|
updatedAt DateTime @updatedAt
|
||||||
|
|
||||||
|
productGroup ProductGroup @relation(fields: [productGroupId], references: [id], onDelete: Cascade)
|
||||||
|
productGroupId String
|
||||||
|
|
||||||
product Product[]
|
product Product[]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -586,22 +668,23 @@ model Product {
|
||||||
code String
|
code String
|
||||||
name String
|
name String
|
||||||
detail String
|
detail String
|
||||||
process String
|
process Int
|
||||||
price Int
|
price Float
|
||||||
agentPrice Int
|
agentPrice Float
|
||||||
serviceCharge Int
|
serviceCharge Float
|
||||||
imageUrl String
|
|
||||||
|
|
||||||
status Status @default(CREATED)
|
status Status @default(CREATED)
|
||||||
|
statusOrder Int @default(0)
|
||||||
|
|
||||||
|
remark String?
|
||||||
|
|
||||||
productType ProductType? @relation(fields: [productTypeId], references: [id], onDelete: SetNull)
|
productType ProductType? @relation(fields: [productTypeId], references: [id], onDelete: SetNull)
|
||||||
productTypeId String?
|
productTypeId String?
|
||||||
|
|
||||||
productGroup ProductGroup? @relation(fields: [productGroupId], references: [id], onDelete: SetNull)
|
workProduct WorkProduct[]
|
||||||
productGroupId String?
|
|
||||||
|
|
||||||
createdBy String?
|
createdBy String?
|
||||||
createdAt DateTime @default(now())
|
createdAt DateTime @default(now())
|
||||||
updateBy String?
|
updatedBy String?
|
||||||
updatedAt DateTime @updatedAt
|
updatedAt DateTime @updatedAt
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
import { Controller, Get, Path, Route, Tags } from "tsoa";
|
import { Controller, Get, Path, Route, Tags } from "tsoa";
|
||||||
import prisma from "../db";
|
import prisma from "../db";
|
||||||
|
|
||||||
@Route("api/address")
|
@Route("api/v1/address")
|
||||||
@Tags("Address")
|
@Tags("Address")
|
||||||
export class AddressController extends Controller {
|
export class AddressController extends Controller {
|
||||||
@Get("province")
|
@Get("province")
|
||||||
|
|
|
||||||
|
|
@ -26,7 +26,7 @@ type BranchContactUpdate = {
|
||||||
telephoneNo?: string;
|
telephoneNo?: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
@Route("api/branch/{branchId}/contact")
|
@Route("api/v1/branch/{branchId}/contact")
|
||||||
@Tags("Branch Contact")
|
@Tags("Branch Contact")
|
||||||
@Security("keycloak")
|
@Security("keycloak")
|
||||||
export class BranchContactController extends Controller {
|
export class BranchContactController extends Controller {
|
||||||
|
|
@ -62,7 +62,7 @@ export class BranchContactController extends Controller {
|
||||||
throw new HttpError(
|
throw new HttpError(
|
||||||
HttpStatus.NOT_FOUND,
|
HttpStatus.NOT_FOUND,
|
||||||
"Branch contact cannot be found.",
|
"Branch contact cannot be found.",
|
||||||
"data_not_found",
|
"branchContactNotFound",
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -76,14 +76,10 @@ export class BranchContactController extends Controller {
|
||||||
@Body() body: BranchContactCreate,
|
@Body() body: BranchContactCreate,
|
||||||
) {
|
) {
|
||||||
if (!(await prisma.branch.findFirst({ where: { id: branchId } }))) {
|
if (!(await prisma.branch.findFirst({ where: { id: branchId } }))) {
|
||||||
throw new HttpError(
|
throw new HttpError(HttpStatus.BAD_REQUEST, "Branch cannot be found.", "branchBadReq");
|
||||||
HttpStatus.BAD_REQUEST,
|
|
||||||
"Branch not found.",
|
|
||||||
"missing_or_invalid_parameter",
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
const record = await prisma.branchContact.create({
|
const record = await prisma.branchContact.create({
|
||||||
data: { ...body, branchId, createdBy: req.user.name, updateBy: req.user.name },
|
data: { ...body, branchId, createdBy: req.user.name, updatedBy: req.user.name },
|
||||||
});
|
});
|
||||||
|
|
||||||
this.setStatus(HttpStatus.CREATED);
|
this.setStatus(HttpStatus.CREATED);
|
||||||
|
|
@ -106,12 +102,12 @@ export class BranchContactController extends Controller {
|
||||||
throw new HttpError(
|
throw new HttpError(
|
||||||
HttpStatus.NOT_FOUND,
|
HttpStatus.NOT_FOUND,
|
||||||
"Branch contact cannot be found.",
|
"Branch contact cannot be found.",
|
||||||
"data_not_found",
|
"branchContactNotFound",
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
const record = await prisma.branchContact.update({
|
const record = await prisma.branchContact.update({
|
||||||
data: { ...body, updateBy: req.user.name },
|
data: { ...body, updatedBy: req.user.name },
|
||||||
where: { id: contactId, branchId },
|
where: { id: contactId, branchId },
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
@ -125,7 +121,7 @@ export class BranchContactController extends Controller {
|
||||||
throw new HttpError(
|
throw new HttpError(
|
||||||
HttpStatus.NOT_FOUND,
|
HttpStatus.NOT_FOUND,
|
||||||
"Branch contact cannot be found.",
|
"Branch contact cannot be found.",
|
||||||
"data_not_found",
|
"branchContactNotFound",
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -57,7 +57,7 @@ type BranchUpdate = {
|
||||||
address?: string;
|
address?: string;
|
||||||
zipCode?: string;
|
zipCode?: string;
|
||||||
email?: string;
|
email?: string;
|
||||||
telephoneNo: string;
|
telephoneNo?: string;
|
||||||
contactName?: string;
|
contactName?: string;
|
||||||
contact?: string | string[] | null;
|
contact?: string | string[] | null;
|
||||||
lineId?: string;
|
lineId?: string;
|
||||||
|
|
@ -78,7 +78,7 @@ function branchImageLoc(id: string) {
|
||||||
return `branch/branch-img-${id}`;
|
return `branch/branch-img-${id}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Route("api/branch")
|
@Route("api/v1/branch")
|
||||||
@Tags("Branch")
|
@Tags("Branch")
|
||||||
@Security("keycloak")
|
@Security("keycloak")
|
||||||
export class BranchController extends Controller {
|
export class BranchController extends Controller {
|
||||||
|
|
@ -109,13 +109,26 @@ export class BranchController extends Controller {
|
||||||
const record = await prisma.branch.findMany({
|
const record = await prisma.branch.findMany({
|
||||||
select: {
|
select: {
|
||||||
id: true,
|
id: true,
|
||||||
|
headOfficeId: true,
|
||||||
|
isHeadOffice: true,
|
||||||
nameEN: true,
|
nameEN: true,
|
||||||
name: true,
|
name: true,
|
||||||
isHeadOffice: true,
|
|
||||||
},
|
},
|
||||||
|
orderBy: [{ isHeadOffice: "desc" }, { createdAt: "asc" }],
|
||||||
});
|
});
|
||||||
|
|
||||||
return record.map((a) =>
|
const sort = record.reduce<(typeof record)[]>((acc, curr) => {
|
||||||
|
for (const i of acc) {
|
||||||
|
if (i[0].id === curr.headOfficeId) {
|
||||||
|
i.push(curr);
|
||||||
|
return acc;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
acc.push([curr]);
|
||||||
|
return acc;
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
return sort.flat().map((a) =>
|
||||||
Object.assign(a, {
|
Object.assign(a, {
|
||||||
count: list.find((b) => b.branchId === a.id)?._count ?? 0,
|
count: list.find((b) => b.branchId === a.id)?._count ?? 0,
|
||||||
}),
|
}),
|
||||||
|
|
@ -195,7 +208,7 @@ export class BranchController extends Controller {
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!record) {
|
if (!record) {
|
||||||
throw new HttpError(HttpStatus.NOT_FOUND, "Branch cannot be found.", "data_not_found");
|
throw new HttpError(HttpStatus.NOT_FOUND, "Branch cannot be found.", "branchNotFound");
|
||||||
}
|
}
|
||||||
|
|
||||||
return Object.assign(record, {
|
return Object.assign(record, {
|
||||||
|
|
@ -216,58 +229,70 @@ export class BranchController extends Controller {
|
||||||
throw new HttpError(
|
throw new HttpError(
|
||||||
HttpStatus.BAD_REQUEST,
|
HttpStatus.BAD_REQUEST,
|
||||||
"Province cannot be found.",
|
"Province cannot be found.",
|
||||||
"missing_or_invalid_parameter",
|
"relationProvinceNotFound",
|
||||||
);
|
);
|
||||||
if (body.districtId && !district)
|
if (body.districtId && !district)
|
||||||
throw new HttpError(
|
throw new HttpError(
|
||||||
HttpStatus.BAD_REQUEST,
|
HttpStatus.BAD_REQUEST,
|
||||||
"District cannot be found.",
|
"District cannot be found.",
|
||||||
"missing_or_invalid_parameter",
|
"relationDistrictNotFound",
|
||||||
);
|
);
|
||||||
if (body.subDistrictId && !subDistrict)
|
if (body.subDistrictId && !subDistrict)
|
||||||
throw new HttpError(
|
throw new HttpError(
|
||||||
HttpStatus.BAD_REQUEST,
|
HttpStatus.BAD_REQUEST,
|
||||||
"Sub-district cannot be found.",
|
"Sub-district cannot be found.",
|
||||||
"missing_or_invalid_parameter",
|
"relationSubDistrictNotFound",
|
||||||
);
|
);
|
||||||
if (body.headOfficeId && !head)
|
if (body.headOfficeId && !head)
|
||||||
throw new HttpError(
|
throw new HttpError(
|
||||||
HttpStatus.BAD_REQUEST,
|
HttpStatus.BAD_REQUEST,
|
||||||
"Head branch cannot be found.",
|
"Headquaters cannot be found.",
|
||||||
"missing_or_invalid_parameter",
|
"relationHQNotFound",
|
||||||
);
|
);
|
||||||
|
|
||||||
const { provinceId, districtId, subDistrictId, headOfficeId, contact, ...rest } = body;
|
const { provinceId, districtId, subDistrictId, headOfficeId, contact, ...rest } = body;
|
||||||
|
|
||||||
const year = new Date().getFullYear();
|
const year = new Date().getFullYear();
|
||||||
|
|
||||||
const last = await prisma.branch.findFirst({
|
const record = await prisma.$transaction(
|
||||||
orderBy: { createdAt: "desc" },
|
async (tx) => {
|
||||||
where: { headOfficeId: headOfficeId ?? null },
|
const last = await tx.runningNo.upsert({
|
||||||
});
|
where: {
|
||||||
|
key: !headOfficeId ? `HQ${year.toString().slice(2)}` : `BR${head?.code.slice(2, 5)}`,
|
||||||
|
},
|
||||||
|
create: {
|
||||||
|
key: !headOfficeId ? `HQ${year.toString().slice(2)}` : `BR${head?.code.slice(2, 5)}`,
|
||||||
|
value: 1,
|
||||||
|
},
|
||||||
|
update: { value: { increment: 1 } },
|
||||||
|
});
|
||||||
|
|
||||||
const code = !headOfficeId
|
const code = !headOfficeId
|
||||||
? `HQ${year.toString().slice(2)}${+(last?.code.slice(-1) || 0) + 1}`
|
? `HQ${year.toString().slice(2)}${last.value}`
|
||||||
: `BR${head?.code.slice(2, 5)}${(+(last?.code.slice(-2) || 0) + 1).toString().padStart(2, "0")}`;
|
: `BR${head?.code.slice(2, 5)}${last.value.toString().padStart(2, "0")}`;
|
||||||
|
|
||||||
const record = await prisma.branch.create({
|
return await tx.branch.create({
|
||||||
include: {
|
include: {
|
||||||
province: true,
|
province: true,
|
||||||
district: true,
|
district: true,
|
||||||
subDistrict: true,
|
subDistrict: true,
|
||||||
|
},
|
||||||
|
data: {
|
||||||
|
...rest,
|
||||||
|
statusOrder: +(rest.status === "INACTIVE"),
|
||||||
|
code,
|
||||||
|
isHeadOffice: !headOfficeId,
|
||||||
|
province: { connect: provinceId ? { id: provinceId } : undefined },
|
||||||
|
district: { connect: districtId ? { id: districtId } : undefined },
|
||||||
|
subDistrict: { connect: subDistrictId ? { id: subDistrictId } : undefined },
|
||||||
|
headOffice: { connect: headOfficeId ? { id: headOfficeId } : undefined },
|
||||||
|
createdBy: req.user.name,
|
||||||
|
updatedBy: req.user.name,
|
||||||
|
},
|
||||||
|
});
|
||||||
},
|
},
|
||||||
data: {
|
{ isolationLevel: Prisma.TransactionIsolationLevel.Serializable },
|
||||||
...rest,
|
);
|
||||||
code,
|
|
||||||
isHeadOffice: !headOfficeId,
|
|
||||||
province: { connect: provinceId ? { id: provinceId } : undefined },
|
|
||||||
district: { connect: districtId ? { id: districtId } : undefined },
|
|
||||||
subDistrict: { connect: subDistrictId ? { id: subDistrictId } : undefined },
|
|
||||||
headOffice: { connect: headOfficeId ? { id: headOfficeId } : undefined },
|
|
||||||
createdBy: req.user.name,
|
|
||||||
updateBy: req.user.name,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
if (headOfficeId) {
|
if (headOfficeId) {
|
||||||
await prisma.branch.updateMany({
|
await prisma.branch.updateMany({
|
||||||
|
|
@ -313,8 +338,8 @@ export class BranchController extends Controller {
|
||||||
if (body.headOfficeId === branchId)
|
if (body.headOfficeId === branchId)
|
||||||
throw new HttpError(
|
throw new HttpError(
|
||||||
HttpStatus.BAD_REQUEST,
|
HttpStatus.BAD_REQUEST,
|
||||||
"Cannot make this as head office and branch at the same time.",
|
"Cannot make this as headquaters and branch at the same time.",
|
||||||
"missing_or_invalid_parameter",
|
"cantMakeHQAndBranchSameTime",
|
||||||
);
|
);
|
||||||
|
|
||||||
if (body.subDistrictId || body.districtId || body.provinceId || body.headOfficeId) {
|
if (body.subDistrictId || body.districtId || body.provinceId || body.headOfficeId) {
|
||||||
|
|
@ -328,38 +353,39 @@ export class BranchController extends Controller {
|
||||||
throw new HttpError(
|
throw new HttpError(
|
||||||
HttpStatus.BAD_REQUEST,
|
HttpStatus.BAD_REQUEST,
|
||||||
"Province cannot be found.",
|
"Province cannot be found.",
|
||||||
"missing_or_invalid_parameter",
|
"relationProvinceNotFound",
|
||||||
);
|
);
|
||||||
if (body.districtId && !district)
|
if (body.districtId && !district)
|
||||||
throw new HttpError(
|
throw new HttpError(
|
||||||
HttpStatus.BAD_REQUEST,
|
HttpStatus.BAD_REQUEST,
|
||||||
"District cannot be found.",
|
"District cannot be found.",
|
||||||
"missing_or_invalid_parameter",
|
"relationDistrictNotFound",
|
||||||
);
|
);
|
||||||
if (body.subDistrictId && !subDistrict)
|
if (body.subDistrictId && !subDistrict)
|
||||||
throw new HttpError(
|
throw new HttpError(
|
||||||
HttpStatus.BAD_REQUEST,
|
HttpStatus.BAD_REQUEST,
|
||||||
"Sub-district cannot be found.",
|
"Sub-district cannot be found.",
|
||||||
"missing_or_invalid_parameter",
|
"relationSubDistrictNotFound",
|
||||||
);
|
);
|
||||||
if (body.headOfficeId && !branch)
|
if (body.headOfficeId && !branch)
|
||||||
throw new HttpError(
|
throw new HttpError(
|
||||||
HttpStatus.BAD_REQUEST,
|
HttpStatus.BAD_REQUEST,
|
||||||
"Head branch cannot be found.",
|
"Headquaters cannot be found.",
|
||||||
"missing_or_invalid_parameter",
|
"relationHQNotFound",
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
const { provinceId, districtId, subDistrictId, headOfficeId, contact, ...rest } = body;
|
const { provinceId, districtId, subDistrictId, headOfficeId, contact, ...rest } = body;
|
||||||
|
|
||||||
if (!(await prisma.branch.findUnique({ where: { id: branchId } }))) {
|
if (!(await prisma.branch.findUnique({ where: { id: branchId } }))) {
|
||||||
throw new HttpError(HttpStatus.NOT_FOUND, "Branch cannot be found.", "data_not_found");
|
throw new HttpError(HttpStatus.NOT_FOUND, "Branch cannot be found.", "branchNotFound");
|
||||||
}
|
}
|
||||||
|
|
||||||
const record = await prisma.branch.update({
|
const record = await prisma.branch.update({
|
||||||
include: { province: true, district: true, subDistrict: true },
|
include: { province: true, district: true, subDistrict: true },
|
||||||
data: {
|
data: {
|
||||||
...rest,
|
...rest,
|
||||||
|
statusOrder: +(rest.status === "INACTIVE"),
|
||||||
isHeadOffice: headOfficeId !== undefined ? headOfficeId === null : undefined,
|
isHeadOffice: headOfficeId !== undefined ? headOfficeId === null : undefined,
|
||||||
province: {
|
province: {
|
||||||
connect: provinceId ? { id: provinceId } : undefined,
|
connect: provinceId ? { id: provinceId } : undefined,
|
||||||
|
|
@ -377,7 +403,7 @@ export class BranchController extends Controller {
|
||||||
connect: headOfficeId ? { id: headOfficeId } : undefined,
|
connect: headOfficeId ? { id: headOfficeId } : undefined,
|
||||||
disconnect: headOfficeId === null || undefined,
|
disconnect: headOfficeId === null || undefined,
|
||||||
},
|
},
|
||||||
updateBy: req.user.name,
|
updatedBy: req.user.name,
|
||||||
},
|
},
|
||||||
where: { id: branchId },
|
where: { id: branchId },
|
||||||
});
|
});
|
||||||
|
|
@ -421,11 +447,11 @@ export class BranchController extends Controller {
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!record) {
|
if (!record) {
|
||||||
throw new HttpError(HttpStatus.NOT_FOUND, "Branch cannot be found.", "data_not_found");
|
throw new HttpError(HttpStatus.NOT_FOUND, "Branch cannot be found.", "branchNotFound");
|
||||||
}
|
}
|
||||||
|
|
||||||
if (record.status !== Status.CREATED) {
|
if (record.status !== Status.CREATED) {
|
||||||
throw new HttpError(HttpStatus.FORBIDDEN, "Branch is in used.", "data_in_used");
|
throw new HttpError(HttpStatus.FORBIDDEN, "Branch is in used.", "branchInUsed");
|
||||||
}
|
}
|
||||||
|
|
||||||
await minio.removeObject(MINIO_BUCKET, lineImageLoc(branchId), {
|
await minio.removeObject(MINIO_BUCKET, lineImageLoc(branchId), {
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
import { Prisma, Status, UserType } from "@prisma/client";
|
import { Branch, Prisma, Status, User, UserType } from "@prisma/client";
|
||||||
import {
|
import {
|
||||||
Body,
|
Body,
|
||||||
Controller,
|
Controller,
|
||||||
|
|
@ -20,7 +20,38 @@ import { RequestWithUser } from "../interfaces/user";
|
||||||
|
|
||||||
type BranchUserBody = { user: string[] };
|
type BranchUserBody = { user: string[] };
|
||||||
|
|
||||||
@Route("api/branch/{branchId}/user")
|
async function userBranchCodeGen(branch: Branch, user: User[]) {
|
||||||
|
await prisma.$transaction(
|
||||||
|
async (tx) => {
|
||||||
|
for (const usr of user) {
|
||||||
|
if (usr.code !== null) continue;
|
||||||
|
|
||||||
|
const typ = usr.userType;
|
||||||
|
|
||||||
|
const last = await tx.runningNo.upsert({
|
||||||
|
where: {
|
||||||
|
key: `BR_USR_${branch.code.slice(4).padEnd(3, "0")}${typ !== "USER" ? typ.charAt(0).toLocaleUpperCase() : ""}`,
|
||||||
|
},
|
||||||
|
create: {
|
||||||
|
key: `BR_USR_${branch.code.slice(4).padEnd(3, "0")}${typ !== "USER" ? typ.charAt(0).toLocaleUpperCase() : ""}`,
|
||||||
|
value: 1,
|
||||||
|
},
|
||||||
|
update: { value: { increment: 1 } },
|
||||||
|
});
|
||||||
|
|
||||||
|
await prisma.user.update({
|
||||||
|
where: { id: usr.id },
|
||||||
|
data: {
|
||||||
|
code: `${last.key.slice(7)}${last.value.toString().padStart(4, "0")}`,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{ isolationLevel: Prisma.TransactionIsolationLevel.Serializable },
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Route("api/v1/branch/{branchId}/user")
|
||||||
@Tags("Branch User")
|
@Tags("Branch User")
|
||||||
@Security("keycloak")
|
@Security("keycloak")
|
||||||
export class BranchUserController extends Controller {
|
export class BranchUserController extends Controller {
|
||||||
|
|
@ -85,7 +116,7 @@ export class BranchUserController extends Controller {
|
||||||
throw new HttpError(
|
throw new HttpError(
|
||||||
HttpStatus.BAD_REQUEST,
|
HttpStatus.BAD_REQUEST,
|
||||||
"Branch cannot be found.",
|
"Branch cannot be found.",
|
||||||
"missing_or_invalid_parameter",
|
"branchBadReq",
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -93,7 +124,7 @@ export class BranchUserController extends Controller {
|
||||||
throw new HttpError(
|
throw new HttpError(
|
||||||
HttpStatus.BAD_REQUEST,
|
HttpStatus.BAD_REQUEST,
|
||||||
"One or more user cannot be found.",
|
"One or more user cannot be found.",
|
||||||
"missing_or_invalid_parameter",
|
"oneOrMoreUserBadReq",
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -109,47 +140,12 @@ export class BranchUserController extends Controller {
|
||||||
branchId,
|
branchId,
|
||||||
userId: v.id,
|
userId: v.id,
|
||||||
createdBy: req.user.name,
|
createdBy: req.user.name,
|
||||||
updateBy: req.user.name,
|
updatedBy: req.user.name,
|
||||||
})),
|
})),
|
||||||
}),
|
}),
|
||||||
]);
|
]);
|
||||||
|
|
||||||
const group: Record<UserType, string[]> = {
|
await userBranchCodeGen(branch, user);
|
||||||
USER: [],
|
|
||||||
AGENCY: [],
|
|
||||||
DELEGATE: [],
|
|
||||||
MESSENGER: [],
|
|
||||||
};
|
|
||||||
|
|
||||||
for (const u of user) group[u.userType].push(u.id);
|
|
||||||
|
|
||||||
for (const g of Object.values(UserType)) {
|
|
||||||
if (group[g].length === 0) continue;
|
|
||||||
|
|
||||||
const last = await prisma.branchUser.findFirst({
|
|
||||||
orderBy: { createdAt: "desc" },
|
|
||||||
include: { user: true },
|
|
||||||
where: {
|
|
||||||
branchId,
|
|
||||||
user: {
|
|
||||||
userType: g,
|
|
||||||
code: { startsWith: `${branch.code.slice(4).padEnd(3, "0")}` },
|
|
||||||
},
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
const code = (idx: number) =>
|
|
||||||
`${branch.code.slice(4).padEnd(3, "0")}${g !== "USER" ? g.charAt(0) : ""}${(+(last?.user.code?.slice(-4) || 0) + idx + 1).toString().padStart(4, "0")}`;
|
|
||||||
|
|
||||||
await prisma.$transaction(
|
|
||||||
group[g].map((v, i) =>
|
|
||||||
prisma.user.updateMany({
|
|
||||||
where: { id: v, code: null },
|
|
||||||
data: { code: code(i) },
|
|
||||||
}),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Delete()
|
@Delete()
|
||||||
|
|
@ -182,7 +178,7 @@ export class BranchUserController extends Controller {
|
||||||
|
|
||||||
type UserBranchBody = { branch: string[] };
|
type UserBranchBody = { branch: string[] };
|
||||||
|
|
||||||
@Route("api/user/{userId}/branch")
|
@Route("api/v1/user/{userId}/branch")
|
||||||
@Tags("Branch User")
|
@Tags("Branch User")
|
||||||
@Security("keycloak")
|
@Security("keycloak")
|
||||||
export class UserBranchController extends Controller {
|
export class UserBranchController extends Controller {
|
||||||
|
|
@ -238,7 +234,7 @@ export class UserBranchController extends Controller {
|
||||||
throw new HttpError(
|
throw new HttpError(
|
||||||
HttpStatus.BAD_REQUEST,
|
HttpStatus.BAD_REQUEST,
|
||||||
"One or more branch cannot be found.",
|
"One or more branch cannot be found.",
|
||||||
"missing_or_invalid_parameter",
|
"oneOrMoreBranchBadReq",
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -254,7 +250,7 @@ export class UserBranchController extends Controller {
|
||||||
branchId: v.id,
|
branchId: v.id,
|
||||||
userId,
|
userId,
|
||||||
createdBy: req.user.name,
|
createdBy: req.user.name,
|
||||||
updateBy: req.user.name,
|
updatedBy: req.user.name,
|
||||||
})),
|
})),
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -29,14 +29,18 @@ function imageLocation(id: string) {
|
||||||
return `employee/profile-img-${id}`;
|
return `employee/profile-img-${id}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
type CustomerBranchCreate = {
|
function attachmentLocation(customerId: string, branchId: string) {
|
||||||
|
return `customer/${customerId}/branch/${branchId}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
export type CustomerBranchCreate = {
|
||||||
customerId: string;
|
customerId: string;
|
||||||
|
|
||||||
status?: Status;
|
status?: Status;
|
||||||
|
|
||||||
legalPersonNo: string;
|
legalPersonNo: string;
|
||||||
|
|
||||||
taxNo: string;
|
taxNo: string | null;
|
||||||
name: string;
|
name: string;
|
||||||
nameEN: string;
|
nameEN: string;
|
||||||
addressEN: string;
|
addressEN: string;
|
||||||
|
|
@ -44,26 +48,34 @@ type CustomerBranchCreate = {
|
||||||
zipCode: string;
|
zipCode: string;
|
||||||
email: string;
|
email: string;
|
||||||
telephoneNo: string;
|
telephoneNo: string;
|
||||||
longitude: string;
|
|
||||||
latitude: string;
|
|
||||||
|
|
||||||
registerName: string;
|
registerName: string;
|
||||||
registerDate: Date;
|
registerDate: Date;
|
||||||
authorizedCapital: string;
|
authorizedCapital: string;
|
||||||
|
|
||||||
|
employmentOffice: string;
|
||||||
|
bussinessType: string;
|
||||||
|
bussinessTypeEN: string;
|
||||||
|
jobPosition: string;
|
||||||
|
jobPositionEN: string;
|
||||||
|
jobDescription: string;
|
||||||
|
saleEmployee: string;
|
||||||
|
payDate: Date;
|
||||||
|
wageRate: number;
|
||||||
|
|
||||||
subDistrictId?: string | null;
|
subDistrictId?: string | null;
|
||||||
districtId?: string | null;
|
districtId?: string | null;
|
||||||
provinceId?: string | null;
|
provinceId?: string | null;
|
||||||
};
|
};
|
||||||
|
|
||||||
type CustomerBranchUpdate = {
|
export type CustomerBranchUpdate = {
|
||||||
customerId?: string;
|
customerId?: string;
|
||||||
|
|
||||||
status?: "ACTIVE" | "INACTIVE";
|
status?: "ACTIVE" | "INACTIVE";
|
||||||
|
|
||||||
legalPersonNo?: string;
|
legalPersonNo?: string;
|
||||||
|
|
||||||
taxNo?: string;
|
taxNo?: string | null;
|
||||||
name?: string;
|
name?: string;
|
||||||
nameEN?: string;
|
nameEN?: string;
|
||||||
addressEN?: string;
|
addressEN?: string;
|
||||||
|
|
@ -71,44 +83,82 @@ type CustomerBranchUpdate = {
|
||||||
zipCode?: string;
|
zipCode?: string;
|
||||||
email?: string;
|
email?: string;
|
||||||
telephoneNo?: string;
|
telephoneNo?: string;
|
||||||
longitude?: string;
|
|
||||||
latitude?: string;
|
|
||||||
|
|
||||||
registerName?: string;
|
registerName?: string;
|
||||||
registerDate?: Date;
|
registerDate?: Date;
|
||||||
authorizedCapital?: string;
|
authorizedCapital?: string;
|
||||||
|
|
||||||
|
employmentOffice?: string;
|
||||||
|
bussinessType?: string;
|
||||||
|
bussinessTypeEN?: string;
|
||||||
|
jobPosition?: string;
|
||||||
|
jobPositionEN?: string;
|
||||||
|
jobDescription?: string;
|
||||||
|
saleEmployee?: string;
|
||||||
|
payDate?: Date;
|
||||||
|
wageRate?: number;
|
||||||
|
|
||||||
subDistrictId?: string | null;
|
subDistrictId?: string | null;
|
||||||
districtId?: string | null;
|
districtId?: string | null;
|
||||||
provinceId?: string | null;
|
provinceId?: string | null;
|
||||||
};
|
};
|
||||||
|
|
||||||
@Route("api/customer-branch")
|
@Route("api/v1/customer-branch")
|
||||||
@Tags("Customer Branch")
|
@Tags("Customer Branch")
|
||||||
@Security("keycloak")
|
@Security("keycloak")
|
||||||
export class CustomerBranchController extends Controller {
|
export class CustomerBranchController extends Controller {
|
||||||
@Get()
|
@Get()
|
||||||
async list(
|
async list(
|
||||||
@Query() zipCode?: string,
|
@Query() zipCode?: string,
|
||||||
|
@Query() customerId?: string,
|
||||||
|
@Query() status?: Status,
|
||||||
|
@Query() includeCustomer?: boolean,
|
||||||
@Query() query: string = "",
|
@Query() query: string = "",
|
||||||
@Query() page: number = 1,
|
@Query() page: number = 1,
|
||||||
@Query() pageSize: number = 30,
|
@Query() pageSize: number = 30,
|
||||||
) {
|
) {
|
||||||
|
const filterStatus = (val?: Status) => {
|
||||||
|
if (!val) return {};
|
||||||
|
|
||||||
|
return val !== Status.CREATED && val !== Status.ACTIVE
|
||||||
|
? { status: val }
|
||||||
|
: { OR: [{ status: Status.CREATED }, { status: Status.ACTIVE }] };
|
||||||
|
};
|
||||||
|
|
||||||
const where = {
|
const where = {
|
||||||
OR: [
|
OR: [
|
||||||
{ nameEN: { contains: query }, zipCode },
|
{ nameEN: { contains: query }, zipCode, ...filterStatus(status) },
|
||||||
{ name: { contains: query }, zipCode },
|
{ name: { contains: query }, zipCode, ...filterStatus(status) },
|
||||||
{ email: { contains: query }, zipCode },
|
{ email: { contains: query }, zipCode, ...filterStatus(status) },
|
||||||
|
{ code: { contains: query }, zipCode, ...filterStatus(status) },
|
||||||
|
{ address: { contains: query }, zipCode, ...filterStatus(status) },
|
||||||
|
{ addressEN: { contains: query }, zipCode, ...filterStatus(status) },
|
||||||
|
{ province: { name: { contains: query } }, zipCode, ...filterStatus(status) },
|
||||||
|
{ province: { nameEN: { contains: query } }, zipCode, ...filterStatus(status) },
|
||||||
|
{ district: { name: { contains: query } }, zipCode, ...filterStatus(status) },
|
||||||
|
{ district: { nameEN: { contains: query } }, zipCode, ...filterStatus(status) },
|
||||||
|
{ subDistrict: { name: { contains: query } }, zipCode, ...filterStatus(status) },
|
||||||
|
{ subDistrict: { nameEN: { contains: query } }, zipCode, ...filterStatus(status) },
|
||||||
|
{
|
||||||
|
customer: {
|
||||||
|
OR: [{ customerName: { contains: query } }, { customerNameEN: { contains: query } }],
|
||||||
|
},
|
||||||
|
zipCode,
|
||||||
|
status,
|
||||||
|
},
|
||||||
],
|
],
|
||||||
} satisfies Prisma.BranchWhereInput;
|
AND: { customerId },
|
||||||
|
} satisfies Prisma.CustomerBranchWhereInput;
|
||||||
|
|
||||||
const [result, total] = await prisma.$transaction([
|
const [result, total] = await prisma.$transaction([
|
||||||
prisma.customerBranch.findMany({
|
prisma.customerBranch.findMany({
|
||||||
orderBy: { createdAt: "asc" },
|
orderBy: [{ statusOrder: "asc" }, { createdAt: "asc" }],
|
||||||
include: {
|
include: {
|
||||||
|
customer: includeCustomer,
|
||||||
province: true,
|
province: true,
|
||||||
district: true,
|
district: true,
|
||||||
subDistrict: true,
|
subDistrict: true,
|
||||||
|
_count: true,
|
||||||
},
|
},
|
||||||
where,
|
where,
|
||||||
take: pageSize,
|
take: pageSize,
|
||||||
|
|
@ -132,7 +182,7 @@ export class CustomerBranchController extends Controller {
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!record) {
|
if (!record) {
|
||||||
throw new HttpError(HttpStatus.NOT_FOUND, "Branch cannot be found.", "data_not_found");
|
throw new HttpError(HttpStatus.NOT_FOUND, "Branch cannot be found.", "branchNotFound");
|
||||||
}
|
}
|
||||||
|
|
||||||
return record;
|
return record;
|
||||||
|
|
@ -153,7 +203,6 @@ export class CustomerBranchController extends Controller {
|
||||||
{ firstNameEN: { contains: query }, zipCode },
|
{ firstNameEN: { contains: query }, zipCode },
|
||||||
{ lastName: { contains: query }, zipCode },
|
{ lastName: { contains: query }, zipCode },
|
||||||
{ lastNameEN: { contains: query }, zipCode },
|
{ lastNameEN: { contains: query }, zipCode },
|
||||||
{ email: { contains: query }, zipCode },
|
|
||||||
],
|
],
|
||||||
} satisfies Prisma.EmployeeWhereInput;
|
} satisfies Prisma.EmployeeWhereInput;
|
||||||
|
|
||||||
|
|
@ -191,62 +240,67 @@ export class CustomerBranchController extends Controller {
|
||||||
|
|
||||||
@Post()
|
@Post()
|
||||||
async create(@Request() req: RequestWithUser, @Body() body: CustomerBranchCreate) {
|
async create(@Request() req: RequestWithUser, @Body() body: CustomerBranchCreate) {
|
||||||
if (body.provinceId || body.districtId || body.subDistrictId || body.customerId) {
|
const [province, district, subDistrict, customer] = await prisma.$transaction([
|
||||||
const [province, district, subDistrict, customer] = await prisma.$transaction([
|
prisma.province.findFirst({ where: { id: body.provinceId || undefined } }),
|
||||||
prisma.province.findFirst({ where: { id: body.provinceId || undefined } }),
|
prisma.district.findFirst({ where: { id: body.districtId || undefined } }),
|
||||||
prisma.district.findFirst({ where: { id: body.districtId || undefined } }),
|
prisma.subDistrict.findFirst({ where: { id: body.subDistrictId || undefined } }),
|
||||||
prisma.subDistrict.findFirst({ where: { id: body.subDistrictId || undefined } }),
|
prisma.customer.findFirst({ where: { id: body.customerId || undefined } }),
|
||||||
prisma.customer.findFirst({ where: { id: body.customerId || undefined } }),
|
]);
|
||||||
]);
|
if (body.provinceId && !province)
|
||||||
if (body.provinceId && !province)
|
throw new HttpError(
|
||||||
throw new HttpError(
|
HttpStatus.BAD_REQUEST,
|
||||||
HttpStatus.BAD_REQUEST,
|
"Province cannot be found.",
|
||||||
"Province cannot be found.",
|
"relationProvinceNotFound",
|
||||||
"missing_or_invalid_parameter",
|
);
|
||||||
);
|
if (body.districtId && !district)
|
||||||
if (body.districtId && !district)
|
throw new HttpError(
|
||||||
throw new HttpError(
|
HttpStatus.BAD_REQUEST,
|
||||||
HttpStatus.BAD_REQUEST,
|
"District cannot be found.",
|
||||||
"District cannot be found.",
|
"relationDistrictNotFound",
|
||||||
"missing_or_invalid_parameter",
|
);
|
||||||
);
|
if (body.subDistrictId && !subDistrict)
|
||||||
if (body.subDistrictId && !subDistrict)
|
throw new HttpError(
|
||||||
throw new HttpError(
|
HttpStatus.BAD_REQUEST,
|
||||||
HttpStatus.BAD_REQUEST,
|
"Sub-district cannot be found.",
|
||||||
"Sub-district cannot be found.",
|
"relationSubDistrictNotFound",
|
||||||
"missing_or_invalid_parameter",
|
);
|
||||||
);
|
if (!customer)
|
||||||
if (body.customerId && !customer)
|
throw new HttpError(
|
||||||
throw new HttpError(
|
HttpStatus.BAD_REQUEST,
|
||||||
HttpStatus.BAD_REQUEST,
|
"Customer cannot be found.",
|
||||||
"Customer cannot be found.",
|
"relationCustomerNotFound",
|
||||||
"missing_or_invalid_parameter",
|
);
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
const { provinceId, districtId, subDistrictId, customerId, ...rest } = body;
|
const { provinceId, districtId, subDistrictId, customerId, ...rest } = body;
|
||||||
|
|
||||||
const count = await prisma.customerBranch.count({
|
const record = await prisma.$transaction(
|
||||||
where: { customerId },
|
async (tx) => {
|
||||||
});
|
const count = await tx.customerBranch.count({
|
||||||
|
where: { customerId },
|
||||||
|
});
|
||||||
|
|
||||||
const record = await prisma.customerBranch.create({
|
return await tx.customerBranch.create({
|
||||||
include: {
|
include: {
|
||||||
province: true,
|
province: true,
|
||||||
district: true,
|
district: true,
|
||||||
subDistrict: true,
|
subDistrict: true,
|
||||||
|
},
|
||||||
|
data: {
|
||||||
|
...rest,
|
||||||
|
statusOrder: +(rest.status === "INACTIVE"),
|
||||||
|
branchNo: count + 1,
|
||||||
|
code: `${customer.code}-${(count + 1).toString().padStart(2, "0")}`,
|
||||||
|
customer: { connect: { id: customerId } },
|
||||||
|
province: { connect: provinceId ? { id: provinceId } : undefined },
|
||||||
|
district: { connect: districtId ? { id: districtId } : undefined },
|
||||||
|
subDistrict: { connect: subDistrictId ? { id: subDistrictId } : undefined },
|
||||||
|
createdBy: req.user.name,
|
||||||
|
updatedBy: req.user.name,
|
||||||
|
},
|
||||||
|
});
|
||||||
},
|
},
|
||||||
data: {
|
{ isolationLevel: Prisma.TransactionIsolationLevel.Serializable },
|
||||||
...rest,
|
);
|
||||||
branchNo: `${count + 1}`,
|
|
||||||
customer: { connect: { id: customerId } },
|
|
||||||
province: { connect: provinceId ? { id: provinceId } : undefined },
|
|
||||||
district: { connect: districtId ? { id: districtId } : undefined },
|
|
||||||
subDistrict: { connect: subDistrictId ? { id: subDistrictId } : undefined },
|
|
||||||
createdBy: req.user.name,
|
|
||||||
updateBy: req.user.name,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
await prisma.customer.updateMany({
|
await prisma.customer.updateMany({
|
||||||
where: { id: customerId, status: Status.CREATED },
|
where: { id: customerId, status: Status.CREATED },
|
||||||
|
|
@ -275,32 +329,32 @@ export class CustomerBranchController extends Controller {
|
||||||
throw new HttpError(
|
throw new HttpError(
|
||||||
HttpStatus.BAD_REQUEST,
|
HttpStatus.BAD_REQUEST,
|
||||||
"Province cannot be found.",
|
"Province cannot be found.",
|
||||||
"missing_or_invalid_parameter",
|
"relationProvinceNotFound",
|
||||||
);
|
);
|
||||||
if (body.districtId && !district)
|
if (body.districtId && !district)
|
||||||
throw new HttpError(
|
throw new HttpError(
|
||||||
HttpStatus.BAD_REQUEST,
|
HttpStatus.BAD_REQUEST,
|
||||||
"District cannot be found.",
|
"District cannot be found.",
|
||||||
"missing_or_invalid_parameter",
|
"relationDistrictNotFound",
|
||||||
);
|
);
|
||||||
if (body.subDistrictId && !subDistrict)
|
if (body.subDistrictId && !subDistrict)
|
||||||
throw new HttpError(
|
throw new HttpError(
|
||||||
HttpStatus.BAD_REQUEST,
|
HttpStatus.BAD_REQUEST,
|
||||||
"Sub-district cannot be found.",
|
"Sub-district cannot be found.",
|
||||||
"missing_or_invalid_parameter",
|
"relationSubDistrictNotFound",
|
||||||
);
|
);
|
||||||
if (body.customerId && !customer)
|
if (body.customerId && !customer)
|
||||||
throw new HttpError(
|
throw new HttpError(
|
||||||
HttpStatus.BAD_REQUEST,
|
HttpStatus.BAD_REQUEST,
|
||||||
"Customer cannot be found.",
|
"Customer cannot be found.",
|
||||||
"missing_or_invalid_parameter",
|
"relationCustomerNotFound",
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
const { provinceId, districtId, subDistrictId, customerId, ...rest } = body;
|
const { provinceId, districtId, subDistrictId, customerId, ...rest } = body;
|
||||||
|
|
||||||
if (!(await prisma.customerBranch.findUnique({ where: { id: branchId } }))) {
|
if (!(await prisma.customerBranch.findUnique({ where: { id: branchId } }))) {
|
||||||
throw new HttpError(HttpStatus.NOT_FOUND, "Branch cannot be found.", "data_not_found");
|
throw new HttpError(HttpStatus.NOT_FOUND, "Branch cannot be found.", "branchNotFound");
|
||||||
}
|
}
|
||||||
|
|
||||||
const record = await prisma.customerBranch.update({
|
const record = await prisma.customerBranch.update({
|
||||||
|
|
@ -312,6 +366,7 @@ export class CustomerBranchController extends Controller {
|
||||||
},
|
},
|
||||||
data: {
|
data: {
|
||||||
...rest,
|
...rest,
|
||||||
|
statusOrder: +(rest.status === "INACTIVE"),
|
||||||
customer: { connect: customerId ? { id: customerId } : undefined },
|
customer: { connect: customerId ? { id: customerId } : undefined },
|
||||||
province: {
|
province: {
|
||||||
connect: provinceId ? { id: provinceId } : undefined,
|
connect: provinceId ? { id: provinceId } : undefined,
|
||||||
|
|
@ -326,7 +381,7 @@ export class CustomerBranchController extends Controller {
|
||||||
disconnect: subDistrictId === null || undefined,
|
disconnect: subDistrictId === null || undefined,
|
||||||
},
|
},
|
||||||
createdBy: req.user.name,
|
createdBy: req.user.name,
|
||||||
updateBy: req.user.name,
|
updatedBy: req.user.name,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
@ -337,20 +392,143 @@ export class CustomerBranchController extends Controller {
|
||||||
|
|
||||||
@Delete("{branchId}")
|
@Delete("{branchId}")
|
||||||
async delete(@Path() branchId: string) {
|
async delete(@Path() branchId: string) {
|
||||||
const record = await prisma.customerBranch.findFirst({ where: { id: branchId } });
|
const record = await prisma.customerBranch.findFirst({
|
||||||
|
where: { id: branchId },
|
||||||
|
});
|
||||||
|
|
||||||
if (!record) {
|
if (!record) {
|
||||||
throw new HttpError(
|
throw new HttpError(
|
||||||
HttpStatus.NOT_FOUND,
|
HttpStatus.NOT_FOUND,
|
||||||
"Customer branch cannot be found.",
|
"Customer branch cannot be found.",
|
||||||
"data_not_found",
|
"customerBranchNotFound",
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (record.status !== Status.CREATED) {
|
if (record.status !== Status.CREATED) {
|
||||||
throw new HttpError(HttpStatus.FORBIDDEN, "Customer branch is in used.", "data_in_used");
|
throw new HttpError(
|
||||||
|
HttpStatus.FORBIDDEN,
|
||||||
|
"Customer branch is in used.",
|
||||||
|
"customerBranchInUsed",
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
return await prisma.customerBranch.delete({ where: { id: branchId } });
|
return await prisma.customerBranch.delete({ where: { id: branchId } }).then((v) => {
|
||||||
|
new Promise<string[]>((resolve, reject) => {
|
||||||
|
const item: string[] = [];
|
||||||
|
|
||||||
|
const stream = minio.listObjectsV2(
|
||||||
|
MINIO_BUCKET,
|
||||||
|
`${attachmentLocation(record.customerId, branchId)}/`,
|
||||||
|
);
|
||||||
|
|
||||||
|
stream.on("data", (v) => v && v.name && item.push(v.name));
|
||||||
|
stream.on("end", () => resolve(item));
|
||||||
|
stream.on("error", () => reject(new Error("MinIO error.")));
|
||||||
|
}).then((list) => {
|
||||||
|
list.map(async (v) => {
|
||||||
|
await minio.removeObject(MINIO_BUCKET, v, {
|
||||||
|
forceDelete: true,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
return v;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Route("api/v1/customer-branch/{branchId}/attachment")
|
||||||
|
@Tags("Customer Branch")
|
||||||
|
@Security("keycloak")
|
||||||
|
export class CustomerAttachmentController extends Controller {
|
||||||
|
@Get()
|
||||||
|
async listAttachment(@Path() branchId: string) {
|
||||||
|
const record = await prisma.customerBranch.findFirst({
|
||||||
|
where: { id: branchId },
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!record) {
|
||||||
|
throw new HttpError(
|
||||||
|
HttpStatus.NOT_FOUND,
|
||||||
|
"Customer branch cannot be found.",
|
||||||
|
"customerBranchNotFound",
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const list = await new Promise<string[]>((resolve, reject) => {
|
||||||
|
const item: string[] = [];
|
||||||
|
|
||||||
|
const stream = minio.listObjectsV2(
|
||||||
|
MINIO_BUCKET,
|
||||||
|
`${attachmentLocation(record.customerId, branchId)}/`,
|
||||||
|
);
|
||||||
|
|
||||||
|
stream.on("data", (v) => v && v.name && item.push(v.name));
|
||||||
|
stream.on("end", () => resolve(item));
|
||||||
|
stream.on("error", () => reject(new Error("MinIO error.")));
|
||||||
|
});
|
||||||
|
|
||||||
|
return await Promise.all(
|
||||||
|
list.map(async (v) => ({
|
||||||
|
name: v.split("/").at(-1) as string,
|
||||||
|
url: await minio.presignedGetObject(MINIO_BUCKET, v, 12 * 60 * 60),
|
||||||
|
})),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Post()
|
||||||
|
async addAttachment(@Path() branchId: string, @Body() payload: { file: string[] }) {
|
||||||
|
const record = await prisma.customerBranch.findFirst({
|
||||||
|
where: { id: branchId },
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!record) {
|
||||||
|
throw new HttpError(
|
||||||
|
HttpStatus.NOT_FOUND,
|
||||||
|
"Customer branch cannot be found.",
|
||||||
|
"customerBranchNotFound",
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return await Promise.all(
|
||||||
|
payload.file.map(async (v) => ({
|
||||||
|
name: v,
|
||||||
|
url: await minio.presignedGetObject(
|
||||||
|
MINIO_BUCKET,
|
||||||
|
`${attachmentLocation(record.customerId, branchId)}/${v}`,
|
||||||
|
),
|
||||||
|
uploadUrl: await minio.presignedPutObject(
|
||||||
|
MINIO_BUCKET,
|
||||||
|
`${attachmentLocation(record.customerId, branchId)}/${v}`,
|
||||||
|
12 * 60 * 60,
|
||||||
|
),
|
||||||
|
})),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Delete()
|
||||||
|
async deleteAttachment(@Path() branchId: string, @Body() payload: { file: string[] }) {
|
||||||
|
const record = await prisma.customerBranch.findFirst({
|
||||||
|
where: { id: branchId },
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!record) {
|
||||||
|
throw new HttpError(
|
||||||
|
HttpStatus.NOT_FOUND,
|
||||||
|
"Customer branch cannot be found.",
|
||||||
|
"customerBranchNotFound",
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
await Promise.all(
|
||||||
|
payload.file.map(async (v) => {
|
||||||
|
await minio.removeObject(
|
||||||
|
MINIO_BUCKET,
|
||||||
|
`${attachmentLocation(record.customerId, branchId)}/${v}`,
|
||||||
|
{
|
||||||
|
forceDelete: true,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -15,7 +15,7 @@ import {
|
||||||
} from "tsoa";
|
} from "tsoa";
|
||||||
import { RequestWithUser } from "../interfaces/user";
|
import { RequestWithUser } from "../interfaces/user";
|
||||||
import prisma from "../db";
|
import prisma from "../db";
|
||||||
import minio from "../services/minio";
|
import minio, { presignedGetObjectIfExist } from "../services/minio";
|
||||||
import HttpStatus from "../interfaces/http-status";
|
import HttpStatus from "../interfaces/http-status";
|
||||||
import HttpError from "../interfaces/http-error";
|
import HttpError from "../interfaces/http-error";
|
||||||
|
|
||||||
|
|
@ -27,39 +27,155 @@ const MINIO_BUCKET = process.env.MINIO_BUCKET;
|
||||||
|
|
||||||
export type CustomerCreate = {
|
export type CustomerCreate = {
|
||||||
status?: Status;
|
status?: Status;
|
||||||
|
personName: string;
|
||||||
|
personNameEN?: string;
|
||||||
customerType: CustomerType;
|
customerType: CustomerType;
|
||||||
customerName: string;
|
customerName: string;
|
||||||
customerNameEN: string;
|
customerNameEN: string;
|
||||||
|
taxNo?: string | null;
|
||||||
|
customerBranch?: {
|
||||||
|
status?: Status;
|
||||||
|
|
||||||
|
legalPersonNo: string;
|
||||||
|
|
||||||
|
taxNo: string | null;
|
||||||
|
name: string;
|
||||||
|
nameEN: string;
|
||||||
|
addressEN: string;
|
||||||
|
address: string;
|
||||||
|
zipCode: string;
|
||||||
|
email: string;
|
||||||
|
telephoneNo: string;
|
||||||
|
|
||||||
|
registerName: string;
|
||||||
|
registerDate: Date;
|
||||||
|
authorizedCapital: string;
|
||||||
|
|
||||||
|
employmentOffice: string;
|
||||||
|
bussinessType: string;
|
||||||
|
bussinessTypeEN: string;
|
||||||
|
jobPosition: string;
|
||||||
|
jobPositionEN: string;
|
||||||
|
jobDescription: string;
|
||||||
|
saleEmployee: string;
|
||||||
|
payDate: Date;
|
||||||
|
wageRate: number;
|
||||||
|
|
||||||
|
subDistrictId?: string | null;
|
||||||
|
districtId?: string | null;
|
||||||
|
provinceId?: string | null;
|
||||||
|
}[];
|
||||||
};
|
};
|
||||||
|
|
||||||
export type CustomerUpdate = {
|
export type CustomerUpdate = {
|
||||||
status?: "ACTIVE" | "INACTIVE";
|
status?: "ACTIVE" | "INACTIVE";
|
||||||
|
personName?: string;
|
||||||
|
personNameEN?: string;
|
||||||
customerType?: CustomerType;
|
customerType?: CustomerType;
|
||||||
customerName?: string;
|
customerName?: string;
|
||||||
customerNameEN?: string;
|
customerNameEN?: string;
|
||||||
|
taxNo?: string | null;
|
||||||
|
customerBranch?: {
|
||||||
|
id?: string;
|
||||||
|
|
||||||
|
status?: Status;
|
||||||
|
|
||||||
|
legalPersonNo: string;
|
||||||
|
|
||||||
|
taxNo: string | null;
|
||||||
|
name: string;
|
||||||
|
nameEN: string;
|
||||||
|
addressEN: string;
|
||||||
|
address: string;
|
||||||
|
zipCode: string;
|
||||||
|
email: string;
|
||||||
|
telephoneNo: string;
|
||||||
|
|
||||||
|
registerName: string;
|
||||||
|
registerDate: Date;
|
||||||
|
authorizedCapital: string;
|
||||||
|
|
||||||
|
employmentOffice: string;
|
||||||
|
bussinessType: string;
|
||||||
|
bussinessTypeEN: string;
|
||||||
|
jobPosition: string;
|
||||||
|
jobPositionEN: string;
|
||||||
|
jobDescription: string;
|
||||||
|
saleEmployee: string;
|
||||||
|
payDate: Date;
|
||||||
|
wageRate: number;
|
||||||
|
|
||||||
|
subDistrictId?: string | null;
|
||||||
|
districtId?: string | null;
|
||||||
|
provinceId?: string | null;
|
||||||
|
}[];
|
||||||
};
|
};
|
||||||
|
|
||||||
function imageLocation(id: string) {
|
function imageLocation(id: string) {
|
||||||
return `customer/img-${id}`;
|
return `customer/${id}/profile-image`;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Route("api/customer")
|
@Route("api/v1/customer")
|
||||||
@Tags("Customer")
|
@Tags("Customer")
|
||||||
@Security("keycloak")
|
@Security("keycloak")
|
||||||
export class CustomerController extends Controller {
|
export class CustomerController extends Controller {
|
||||||
|
@Get("type-stats")
|
||||||
|
async stat() {
|
||||||
|
const list = await prisma.customer.groupBy({
|
||||||
|
by: "customerType",
|
||||||
|
_count: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
return list.reduce<Record<CustomerType, number>>(
|
||||||
|
(a, c) => {
|
||||||
|
a[c.customerType] = c._count;
|
||||||
|
return a;
|
||||||
|
},
|
||||||
|
{
|
||||||
|
CORP: 0,
|
||||||
|
PERS: 0,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
@Get()
|
@Get()
|
||||||
async list(
|
async list(
|
||||||
|
@Query() customerType?: CustomerType,
|
||||||
@Query() query: string = "",
|
@Query() query: string = "",
|
||||||
|
@Query() status?: Status,
|
||||||
@Query() page: number = 1,
|
@Query() page: number = 1,
|
||||||
@Query() pageSize: number = 30,
|
@Query() pageSize: number = 30,
|
||||||
|
@Query() includeBranch: boolean = false,
|
||||||
) {
|
) {
|
||||||
|
const filterStatus = (val?: Status) => {
|
||||||
|
if (!val) return {};
|
||||||
|
|
||||||
|
return val !== Status.CREATED && val !== Status.ACTIVE
|
||||||
|
? { status: val }
|
||||||
|
: { OR: [{ status: Status.CREATED }, { status: Status.ACTIVE }] };
|
||||||
|
};
|
||||||
const where = {
|
const where = {
|
||||||
OR: [{ customerName: { contains: query } }, { customerNameEN: { contains: query } }],
|
OR: [
|
||||||
|
{ customerName: { contains: query }, customerType, ...filterStatus(status) },
|
||||||
|
{ customerNameEN: { contains: query }, customerType, ...filterStatus(status) },
|
||||||
|
],
|
||||||
} satisfies Prisma.CustomerWhereInput;
|
} satisfies Prisma.CustomerWhereInput;
|
||||||
|
|
||||||
const [result, total] = await prisma.$transaction([
|
const [result, total] = await prisma.$transaction([
|
||||||
prisma.customer.findMany({
|
prisma.customer.findMany({
|
||||||
orderBy: { createdAt: "asc" },
|
include: {
|
||||||
|
_count: true,
|
||||||
|
branch: includeBranch
|
||||||
|
? {
|
||||||
|
include: {
|
||||||
|
province: true,
|
||||||
|
district: true,
|
||||||
|
subDistrict: true,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
: undefined,
|
||||||
|
},
|
||||||
|
orderBy: [{ statusOrder: "asc" }, { createdAt: "asc" }],
|
||||||
where,
|
where,
|
||||||
take: pageSize,
|
take: pageSize,
|
||||||
skip: (page - 1) * pageSize,
|
skip: (page - 1) * pageSize,
|
||||||
|
|
@ -71,7 +187,11 @@ export class CustomerController extends Controller {
|
||||||
result: await Promise.all(
|
result: await Promise.all(
|
||||||
result.map(async (v) => ({
|
result.map(async (v) => ({
|
||||||
...v,
|
...v,
|
||||||
imageUrl: await minio.presignedGetObject(MINIO_BUCKET, imageLocation(v.id), 12 * 60 * 60),
|
imageUrl: await presignedGetObjectIfExist(
|
||||||
|
MINIO_BUCKET,
|
||||||
|
imageLocation(v.id),
|
||||||
|
12 * 60 * 60,
|
||||||
|
),
|
||||||
})),
|
})),
|
||||||
),
|
),
|
||||||
page,
|
page,
|
||||||
|
|
@ -82,11 +202,22 @@ export class CustomerController extends Controller {
|
||||||
|
|
||||||
@Get("{customerId}")
|
@Get("{customerId}")
|
||||||
async getById(@Path() customerId: string) {
|
async getById(@Path() customerId: string) {
|
||||||
const record = await prisma.customer.findFirst({ where: { id: customerId } });
|
const record = await prisma.customer.findFirst({
|
||||||
|
include: {
|
||||||
|
branch: {
|
||||||
|
include: {
|
||||||
|
province: true,
|
||||||
|
district: true,
|
||||||
|
subDistrict: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
where: { id: customerId },
|
||||||
|
});
|
||||||
if (!record)
|
if (!record)
|
||||||
throw new HttpError(HttpStatus.NOT_FOUND, "Customer cannot be found.", "data_not_found");
|
throw new HttpError(HttpStatus.NOT_FOUND, "Customer cannot be found.", "customerNotFound");
|
||||||
return Object.assign(record, {
|
return Object.assign(record, {
|
||||||
imageUrl: await minio.presignedGetObject(
|
imageUrl: await presignedGetObjectIfExist(
|
||||||
MINIO_BUCKET,
|
MINIO_BUCKET,
|
||||||
imageLocation(record.id),
|
imageLocation(record.id),
|
||||||
12 * 60 * 60,
|
12 * 60 * 60,
|
||||||
|
|
@ -96,26 +227,101 @@ export class CustomerController extends Controller {
|
||||||
|
|
||||||
@Post()
|
@Post()
|
||||||
async create(@Request() req: RequestWithUser, @Body() body: CustomerCreate) {
|
async create(@Request() req: RequestWithUser, @Body() body: CustomerCreate) {
|
||||||
const last = await prisma.customer.findFirst({
|
const { customerBranch, ...payload } = body;
|
||||||
orderBy: { createdAt: "desc" },
|
|
||||||
where: { customerType: body.customerType },
|
|
||||||
});
|
|
||||||
|
|
||||||
const code = `${body.customerType}${(+(last?.code.slice(-6) || 0) + 1).toString().padStart(6, "0")}`;
|
const provinceId = body.customerBranch?.reduce<string[]>((acc, cur) => {
|
||||||
|
if (cur.provinceId && !acc.includes(cur.provinceId)) return acc.concat(cur.provinceId);
|
||||||
|
return acc;
|
||||||
|
}, []);
|
||||||
|
const districtId = body.customerBranch?.reduce<string[]>((acc, cur) => {
|
||||||
|
if (cur.districtId && !acc.includes(cur.districtId)) return acc.concat(cur.districtId);
|
||||||
|
return acc;
|
||||||
|
}, []);
|
||||||
|
const subDistrictId = body.customerBranch?.reduce<string[]>((acc, cur) => {
|
||||||
|
if (cur.subDistrictId && !acc.includes(cur.subDistrictId))
|
||||||
|
return acc.concat(cur.subDistrictId);
|
||||||
|
return acc;
|
||||||
|
}, []);
|
||||||
|
|
||||||
const record = await prisma.customer.create({
|
const [province, district, subDistrict] = await prisma.$transaction([
|
||||||
data: {
|
prisma.province.findMany({ where: { id: { in: provinceId } } }),
|
||||||
...body,
|
prisma.district.findMany({ where: { id: { in: districtId } } }),
|
||||||
code,
|
prisma.subDistrict.findMany({ where: { id: { in: subDistrictId } } }),
|
||||||
createdBy: req.user.name,
|
]);
|
||||||
updateBy: req.user.name,
|
|
||||||
|
if (provinceId && province.length !== provinceId?.length) {
|
||||||
|
throw new HttpError(
|
||||||
|
HttpStatus.BAD_REQUEST,
|
||||||
|
"Some province cannot be found.",
|
||||||
|
"relationProvinceNotFound",
|
||||||
|
);
|
||||||
|
}
|
||||||
|
if (districtId && district.length !== districtId?.length) {
|
||||||
|
throw new HttpError(
|
||||||
|
HttpStatus.BAD_REQUEST,
|
||||||
|
"Some district cannot be found.",
|
||||||
|
"relationDistrictNotFound",
|
||||||
|
);
|
||||||
|
}
|
||||||
|
if (subDistrictId && subDistrict.length !== subDistrictId?.length) {
|
||||||
|
throw new HttpError(
|
||||||
|
HttpStatus.BAD_REQUEST,
|
||||||
|
"Some sub district cannot be found.",
|
||||||
|
"relationSubDistrictNotFound",
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const record = await prisma.$transaction(
|
||||||
|
async (tx) => {
|
||||||
|
const last = await tx.runningNo.upsert({
|
||||||
|
where: {
|
||||||
|
key: `CUSTOMER_${body.customerType}`,
|
||||||
|
},
|
||||||
|
create: {
|
||||||
|
key: `CUSTOMER_${body.customerType}`,
|
||||||
|
value: 1,
|
||||||
|
},
|
||||||
|
update: { value: { increment: 1 } },
|
||||||
|
});
|
||||||
|
|
||||||
|
return await prisma.customer.create({
|
||||||
|
include: {
|
||||||
|
branch: {
|
||||||
|
include: {
|
||||||
|
province: true,
|
||||||
|
district: true,
|
||||||
|
subDistrict: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
data: {
|
||||||
|
...payload,
|
||||||
|
statusOrder: +(payload.status === "INACTIVE"),
|
||||||
|
code: `${last.key.slice(9)}${last.value.toString().padStart(6, "0")}`,
|
||||||
|
branch: {
|
||||||
|
createMany: {
|
||||||
|
data:
|
||||||
|
customerBranch?.map((v, i) => ({
|
||||||
|
...v,
|
||||||
|
branchNo: i + 1,
|
||||||
|
code: `${last.key.slice(9)}${last.value.toString().padStart(6, "0")}-${(i + 1).toString().padStart(2, "0")}`,
|
||||||
|
createdBy: req.user.name,
|
||||||
|
updatedBy: req.user.name,
|
||||||
|
})) || [],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
createdBy: req.user.name,
|
||||||
|
updatedBy: req.user.name,
|
||||||
|
},
|
||||||
|
});
|
||||||
},
|
},
|
||||||
});
|
{ isolationLevel: Prisma.TransactionIsolationLevel.Serializable },
|
||||||
|
);
|
||||||
|
|
||||||
this.setStatus(HttpStatus.CREATED);
|
this.setStatus(HttpStatus.CREATED);
|
||||||
|
|
||||||
return Object.assign(record, {
|
return Object.assign(record, {
|
||||||
imageUrl: await minio.presignedGetObject(
|
imageUrl: await presignedGetObjectIfExist(
|
||||||
MINIO_BUCKET,
|
MINIO_BUCKET,
|
||||||
imageLocation(record.id),
|
imageLocation(record.id),
|
||||||
12 * 60 * 60,
|
12 * 60 * 60,
|
||||||
|
|
@ -134,21 +340,145 @@ export class CustomerController extends Controller {
|
||||||
@Request() req: RequestWithUser,
|
@Request() req: RequestWithUser,
|
||||||
@Body() body: CustomerUpdate,
|
@Body() body: CustomerUpdate,
|
||||||
) {
|
) {
|
||||||
if (!(await prisma.customer.findUnique({ where: { id: customerId } }))) {
|
const customer = await prisma.customer.findUnique({ where: { id: customerId } });
|
||||||
throw new HttpError(HttpStatus.NOT_FOUND, "Customer cannot be found.", "data_not_found");
|
|
||||||
|
if (!customer) {
|
||||||
|
throw new HttpError(HttpStatus.NOT_FOUND, "Customer cannot be found.", "customerNotFound");
|
||||||
}
|
}
|
||||||
|
|
||||||
const record = await prisma.customer.update({
|
const provinceId = body.customerBranch?.reduce<string[]>((acc, cur) => {
|
||||||
where: { id: customerId },
|
if (cur.provinceId && !acc.includes(cur.provinceId)) return acc.concat(cur.provinceId);
|
||||||
data: {
|
return acc;
|
||||||
...body,
|
}, []);
|
||||||
createdBy: req.user.name,
|
const districtId = body.customerBranch?.reduce<string[]>((acc, cur) => {
|
||||||
updateBy: req.user.name,
|
if (cur.districtId && !acc.includes(cur.districtId)) return acc.concat(cur.districtId);
|
||||||
|
return acc;
|
||||||
|
}, []);
|
||||||
|
const subDistrictId = body.customerBranch?.reduce<string[]>((acc, cur) => {
|
||||||
|
if (cur.subDistrictId && !acc.includes(cur.subDistrictId))
|
||||||
|
return acc.concat(cur.subDistrictId);
|
||||||
|
return acc;
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const [province, district, subDistrict] = await prisma.$transaction([
|
||||||
|
prisma.province.findMany({ where: { id: { in: provinceId } } }),
|
||||||
|
prisma.district.findMany({ where: { id: { in: districtId } } }),
|
||||||
|
prisma.subDistrict.findMany({ where: { id: { in: subDistrictId } } }),
|
||||||
|
]);
|
||||||
|
|
||||||
|
if (provinceId && province.length !== provinceId?.length) {
|
||||||
|
throw new HttpError(
|
||||||
|
HttpStatus.BAD_REQUEST,
|
||||||
|
"Some province cannot be found.",
|
||||||
|
"relationProvinceNotFound",
|
||||||
|
);
|
||||||
|
}
|
||||||
|
if (districtId && district.length !== districtId?.length) {
|
||||||
|
throw new HttpError(
|
||||||
|
HttpStatus.BAD_REQUEST,
|
||||||
|
"Some district cannot be found.",
|
||||||
|
"relationDistrictNotFound",
|
||||||
|
);
|
||||||
|
}
|
||||||
|
if (subDistrictId && subDistrict.length !== subDistrictId?.length) {
|
||||||
|
throw new HttpError(
|
||||||
|
HttpStatus.BAD_REQUEST,
|
||||||
|
"Some sub district cannot be found.",
|
||||||
|
"relationSubDistrictNotFound",
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const { customerBranch, ...payload } = body;
|
||||||
|
|
||||||
|
const relation = await prisma.customerBranch.findMany({
|
||||||
|
where: {
|
||||||
|
customerId,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
if (
|
||||||
|
customerBranch &&
|
||||||
|
relation.find((a) => !customerBranch.find((b) => a.id === b.id) && a.status !== "CREATED")
|
||||||
|
) {
|
||||||
|
throw new HttpError(
|
||||||
|
HttpStatus.BAD_REQUEST,
|
||||||
|
"One or more branch cannot be delete and is missing.",
|
||||||
|
"oneOrMoreBranchMissing",
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const record = await prisma.customer
|
||||||
|
.update({
|
||||||
|
include: {
|
||||||
|
branch: {
|
||||||
|
include: {
|
||||||
|
province: true,
|
||||||
|
district: true,
|
||||||
|
subDistrict: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
where: { id: customerId },
|
||||||
|
data: {
|
||||||
|
...payload,
|
||||||
|
statusOrder: +(payload.status === "INACTIVE"),
|
||||||
|
branch:
|
||||||
|
(customerBranch && {
|
||||||
|
deleteMany: {
|
||||||
|
id: {
|
||||||
|
notIn: customerBranch.map((v) => v.id).filter((v): v is string => !!v) || [],
|
||||||
|
},
|
||||||
|
status: Status.CREATED,
|
||||||
|
},
|
||||||
|
upsert: customerBranch.map((v, i) => ({
|
||||||
|
where: { id: v.id || "" },
|
||||||
|
create: {
|
||||||
|
...v,
|
||||||
|
branchNo: i + 1,
|
||||||
|
code: `${customer.code}-${(i + 1).toString().padStart(2, "0")}`,
|
||||||
|
createdBy: req.user.name,
|
||||||
|
updatedBy: req.user.name,
|
||||||
|
id: undefined,
|
||||||
|
},
|
||||||
|
update: {
|
||||||
|
...v,
|
||||||
|
branchNo: i + 1,
|
||||||
|
code: `${customer.code}-${(i + 1).toString().padStart(2, "0")}`,
|
||||||
|
updatedBy: req.user.name,
|
||||||
|
},
|
||||||
|
})),
|
||||||
|
}) ||
|
||||||
|
undefined,
|
||||||
|
updatedBy: req.user.name,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
.then((v) => {
|
||||||
|
if (customerBranch) {
|
||||||
|
relation
|
||||||
|
.filter((a) => !customerBranch.find((b) => b.id === a.id))
|
||||||
|
.forEach((deleted) => {
|
||||||
|
new Promise<string[]>((resolve, reject) => {
|
||||||
|
const item: string[] = [];
|
||||||
|
|
||||||
|
const stream = minio.listObjectsV2(MINIO_BUCKET, `customer/${deleted.id}`);
|
||||||
|
|
||||||
|
stream.on("data", (v) => v && v.name && item.push(v.name));
|
||||||
|
stream.on("end", () => resolve(item));
|
||||||
|
stream.on("error", () => reject(new Error("MinIO error.")));
|
||||||
|
}).then((list) => {
|
||||||
|
list.map(async (v) => {
|
||||||
|
await minio.removeObject(MINIO_BUCKET, v, {
|
||||||
|
forceDelete: true,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return v;
|
||||||
|
});
|
||||||
|
|
||||||
return Object.assign(record, {
|
return Object.assign(record, {
|
||||||
imageUrl: await minio.presignedGetObject(
|
imageUrl: await presignedGetObjectIfExist(
|
||||||
MINIO_BUCKET,
|
MINIO_BUCKET,
|
||||||
imageLocation(record.id),
|
imageLocation(record.id),
|
||||||
12 * 60 * 60,
|
12 * 60 * 60,
|
||||||
|
|
@ -166,13 +496,30 @@ export class CustomerController extends Controller {
|
||||||
const record = await prisma.customer.findFirst({ where: { id: customerId } });
|
const record = await prisma.customer.findFirst({ where: { id: customerId } });
|
||||||
|
|
||||||
if (!record) {
|
if (!record) {
|
||||||
throw new HttpError(HttpStatus.NOT_FOUND, "Customer cannot be found.", "data_not_found");
|
throw new HttpError(HttpStatus.NOT_FOUND, "Customer cannot be found.", "customerNotFound");
|
||||||
}
|
}
|
||||||
|
|
||||||
if (record.status !== Status.CREATED) {
|
if (record.status !== Status.CREATED) {
|
||||||
throw new HttpError(HttpStatus.FORBIDDEN, "Customer is in used.", "data_in_used");
|
throw new HttpError(HttpStatus.FORBIDDEN, "Customer is in used.", "customerInUsed");
|
||||||
}
|
}
|
||||||
|
|
||||||
return await prisma.customer.delete({ where: { id: customerId } });
|
return await prisma.customer.delete({ where: { id: customerId } }).then((v) => {
|
||||||
|
new Promise<string[]>((resolve, reject) => {
|
||||||
|
const item: string[] = [];
|
||||||
|
|
||||||
|
const stream = minio.listObjectsV2(MINIO_BUCKET, `customer/${customerId}`);
|
||||||
|
|
||||||
|
stream.on("data", (v) => v && v.name && item.push(v.name));
|
||||||
|
stream.on("end", () => resolve(item));
|
||||||
|
stream.on("error", () => reject(new Error("MinIO error.")));
|
||||||
|
}).then((list) => {
|
||||||
|
list.map(async (v) => {
|
||||||
|
await minio.removeObject(MINIO_BUCKET, v, {
|
||||||
|
forceDelete: true,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
return v;
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -16,35 +16,21 @@ import prisma from "../db";
|
||||||
import HttpStatus from "../interfaces/http-status";
|
import HttpStatus from "../interfaces/http-status";
|
||||||
import HttpError from "../interfaces/http-error";
|
import HttpError from "../interfaces/http-error";
|
||||||
|
|
||||||
type EmployeeCheckupCreate = {
|
type EmployeeCheckupPayload = {
|
||||||
checkupType: string;
|
checkupType?: string | null;
|
||||||
checkupResult: string;
|
checkupResult?: string | null;
|
||||||
|
|
||||||
provinceId?: string | null;
|
provinceId?: string | null;
|
||||||
|
|
||||||
hospitalName: string;
|
hospitalName?: string | null;
|
||||||
remark: string;
|
remark?: string | null;
|
||||||
medicalBenefitScheme: string;
|
medicalBenefitScheme?: string | null;
|
||||||
insuranceCompany: string;
|
insuranceCompany?: string | null;
|
||||||
coverageStartDate: Date;
|
coverageStartDate?: Date | null;
|
||||||
coverageExpireDate: Date;
|
coverageExpireDate?: Date | null;
|
||||||
};
|
};
|
||||||
|
|
||||||
type EmployeeCheckupEdit = {
|
@Route("api/v1/employee/{employeeId}/checkup")
|
||||||
checkupType?: string;
|
|
||||||
checkupResult?: string;
|
|
||||||
|
|
||||||
provinceId?: string | null;
|
|
||||||
|
|
||||||
hospitalName?: string;
|
|
||||||
remark?: string;
|
|
||||||
medicalBenefitScheme?: string;
|
|
||||||
insuranceCompany?: string;
|
|
||||||
coverageStartDate?: Date;
|
|
||||||
coverageExpireDate?: Date;
|
|
||||||
};
|
|
||||||
|
|
||||||
@Route("api/employee/{employeeId}/checkup")
|
|
||||||
@Tags("Employee Checkup")
|
@Tags("Employee Checkup")
|
||||||
@Security("keycloak")
|
@Security("keycloak")
|
||||||
export class EmployeeCheckupController extends Controller {
|
export class EmployeeCheckupController extends Controller {
|
||||||
|
|
@ -65,7 +51,7 @@ export class EmployeeCheckupController extends Controller {
|
||||||
throw new HttpError(
|
throw new HttpError(
|
||||||
HttpStatus.NOT_FOUND,
|
HttpStatus.NOT_FOUND,
|
||||||
"Employee checkup cannot be found.",
|
"Employee checkup cannot be found.",
|
||||||
"data_not_found",
|
"employeeCheckupNotFound",
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
return record;
|
return record;
|
||||||
|
|
@ -75,7 +61,7 @@ export class EmployeeCheckupController extends Controller {
|
||||||
async create(
|
async create(
|
||||||
@Request() req: RequestWithUser,
|
@Request() req: RequestWithUser,
|
||||||
@Path() employeeId: string,
|
@Path() employeeId: string,
|
||||||
@Body() body: EmployeeCheckupCreate,
|
@Body() body: EmployeeCheckupPayload,
|
||||||
) {
|
) {
|
||||||
if (body.provinceId || employeeId) {
|
if (body.provinceId || employeeId) {
|
||||||
const [province, employee] = await prisma.$transaction([
|
const [province, employee] = await prisma.$transaction([
|
||||||
|
|
@ -86,13 +72,13 @@ export class EmployeeCheckupController extends Controller {
|
||||||
throw new HttpError(
|
throw new HttpError(
|
||||||
HttpStatus.BAD_REQUEST,
|
HttpStatus.BAD_REQUEST,
|
||||||
"Province cannot be found.",
|
"Province cannot be found.",
|
||||||
"missing_or_invalid_parameter",
|
"provinceNotFound",
|
||||||
);
|
);
|
||||||
if (!employee)
|
if (!employee)
|
||||||
throw new HttpError(
|
throw new HttpError(
|
||||||
HttpStatus.BAD_REQUEST,
|
HttpStatus.BAD_REQUEST,
|
||||||
"Employee cannot be found.",
|
"Employee cannot be found.",
|
||||||
"missing_or_invalid_parameter",
|
"employeeNotFound",
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -105,7 +91,7 @@ export class EmployeeCheckupController extends Controller {
|
||||||
province: { connect: provinceId ? { id: provinceId } : undefined },
|
province: { connect: provinceId ? { id: provinceId } : undefined },
|
||||||
employee: { connect: { id: employeeId } },
|
employee: { connect: { id: employeeId } },
|
||||||
createdBy: req.user.name,
|
createdBy: req.user.name,
|
||||||
updateBy: req.user.name,
|
updatedBy: req.user.name,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
@ -119,7 +105,7 @@ export class EmployeeCheckupController extends Controller {
|
||||||
@Request() req: RequestWithUser,
|
@Request() req: RequestWithUser,
|
||||||
@Path() employeeId: string,
|
@Path() employeeId: string,
|
||||||
@Path() checkupId: string,
|
@Path() checkupId: string,
|
||||||
@Body() body: EmployeeCheckupEdit,
|
@Body() body: EmployeeCheckupPayload,
|
||||||
) {
|
) {
|
||||||
if (body.provinceId || employeeId) {
|
if (body.provinceId || employeeId) {
|
||||||
const [province, employee] = await prisma.$transaction([
|
const [province, employee] = await prisma.$transaction([
|
||||||
|
|
@ -130,13 +116,13 @@ export class EmployeeCheckupController extends Controller {
|
||||||
throw new HttpError(
|
throw new HttpError(
|
||||||
HttpStatus.BAD_REQUEST,
|
HttpStatus.BAD_REQUEST,
|
||||||
"Province cannot be found.",
|
"Province cannot be found.",
|
||||||
"missing_or_invalid_parameter",
|
"provinceNotFound",
|
||||||
);
|
);
|
||||||
if (!employee)
|
if (!employee)
|
||||||
throw new HttpError(
|
throw new HttpError(
|
||||||
HttpStatus.BAD_REQUEST,
|
HttpStatus.BAD_REQUEST,
|
||||||
"Employee cannot be found.",
|
"Employee cannot be found.",
|
||||||
"missing_or_invalid_parameter",
|
"employeeNotFound",
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -146,7 +132,7 @@ export class EmployeeCheckupController extends Controller {
|
||||||
throw new HttpError(
|
throw new HttpError(
|
||||||
HttpStatus.NOT_FOUND,
|
HttpStatus.NOT_FOUND,
|
||||||
"Employee checkup cannot be found.",
|
"Employee checkup cannot be found.",
|
||||||
"data_not_found",
|
"employeeCheckupNotFound",
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -157,7 +143,7 @@ export class EmployeeCheckupController extends Controller {
|
||||||
...rest,
|
...rest,
|
||||||
province: { connect: provinceId ? { id: provinceId } : undefined },
|
province: { connect: provinceId ? { id: provinceId } : undefined },
|
||||||
createdBy: req.user.name,
|
createdBy: req.user.name,
|
||||||
updateBy: req.user.name,
|
updatedBy: req.user.name,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
@ -174,7 +160,7 @@ export class EmployeeCheckupController extends Controller {
|
||||||
throw new HttpError(
|
throw new HttpError(
|
||||||
HttpStatus.NOT_FOUND,
|
HttpStatus.NOT_FOUND,
|
||||||
"Employee checkup cannot be found.",
|
"Employee checkup cannot be found.",
|
||||||
"data_not_found",
|
"employeeCheckupNotFound",
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -17,7 +17,7 @@ import { RequestWithUser } from "../interfaces/user";
|
||||||
import prisma from "../db";
|
import prisma from "../db";
|
||||||
import HttpStatus from "../interfaces/http-status";
|
import HttpStatus from "../interfaces/http-status";
|
||||||
import HttpError from "../interfaces/http-error";
|
import HttpError from "../interfaces/http-error";
|
||||||
import minio from "../services/minio";
|
import minio, { presignedGetObjectIfExist } from "../services/minio";
|
||||||
|
|
||||||
if (!process.env.MINIO_BUCKET) {
|
if (!process.env.MINIO_BUCKET) {
|
||||||
throw Error("Require MinIO bucket.");
|
throw Error("Require MinIO bucket.");
|
||||||
|
|
@ -26,7 +26,7 @@ if (!process.env.MINIO_BUCKET) {
|
||||||
const MINIO_BUCKET = process.env.MINIO_BUCKET;
|
const MINIO_BUCKET = process.env.MINIO_BUCKET;
|
||||||
|
|
||||||
function imageLocation(id: string) {
|
function imageLocation(id: string) {
|
||||||
return `employee/profile-img-${id}`;
|
return `employee/${id}/profile-image`;
|
||||||
}
|
}
|
||||||
|
|
||||||
type EmployeeCreate = {
|
type EmployeeCreate = {
|
||||||
|
|
@ -34,7 +34,6 @@ type EmployeeCreate = {
|
||||||
|
|
||||||
status?: Status;
|
status?: Status;
|
||||||
|
|
||||||
code: string;
|
|
||||||
nrcNo: string;
|
nrcNo: string;
|
||||||
|
|
||||||
dateOfBirth: Date;
|
dateOfBirth: Date;
|
||||||
|
|
@ -49,22 +48,75 @@ type EmployeeCreate = {
|
||||||
addressEN: string;
|
addressEN: string;
|
||||||
address: string;
|
address: string;
|
||||||
zipCode: string;
|
zipCode: string;
|
||||||
email: string;
|
|
||||||
telephoneNo: string;
|
|
||||||
|
|
||||||
arrivalBarricade: string;
|
passportType: string;
|
||||||
arrivalCardNo: string;
|
passportNumber: string;
|
||||||
|
passportIssueDate: Date;
|
||||||
|
passportExpiryDate: Date;
|
||||||
|
passportIssuingCountry: string;
|
||||||
|
passportIssuingPlace: string;
|
||||||
|
previousPassportReference?: string;
|
||||||
|
|
||||||
|
visaType?: string | null;
|
||||||
|
visaNumber?: string | null;
|
||||||
|
visaIssueDate?: Date | null;
|
||||||
|
visaExpiryDate?: Date | null;
|
||||||
|
visaIssuingPlace?: string | null;
|
||||||
|
visaStayUntilDate?: Date | null;
|
||||||
|
tm6Number?: string | null;
|
||||||
|
entryDate?: Date | null;
|
||||||
|
workerStatus?: string | null;
|
||||||
|
|
||||||
subDistrictId?: string | null;
|
subDistrictId?: string | null;
|
||||||
districtId?: string | null;
|
districtId?: string | null;
|
||||||
provinceId?: string | null;
|
provinceId?: string | null;
|
||||||
|
|
||||||
|
employeeWork?: {
|
||||||
|
ownerName?: string | null;
|
||||||
|
positionName?: string | null;
|
||||||
|
jobType?: string | null;
|
||||||
|
workplace?: string | null;
|
||||||
|
workPermitNo?: string | null;
|
||||||
|
workPermitIssuDate?: Date | null;
|
||||||
|
workPermitExpireDate?: Date | null;
|
||||||
|
workEndDate?: Date | null;
|
||||||
|
remark?: string | null;
|
||||||
|
}[];
|
||||||
|
|
||||||
|
employeeCheckup?: {
|
||||||
|
checkupType?: string | null;
|
||||||
|
checkupResult?: string | null;
|
||||||
|
|
||||||
|
provinceId?: string | null;
|
||||||
|
|
||||||
|
hospitalName?: string | null;
|
||||||
|
remark?: string | null;
|
||||||
|
medicalBenefitScheme?: string | null;
|
||||||
|
insuranceCompany?: string | null;
|
||||||
|
coverageStartDate?: Date | null;
|
||||||
|
coverageExpireDate?: Date | null;
|
||||||
|
}[];
|
||||||
|
|
||||||
|
employeeOtherInfo?: {
|
||||||
|
citizenId?: string | null;
|
||||||
|
fatherFirstName?: string | null;
|
||||||
|
fatherLastName?: string | null;
|
||||||
|
fatherBirthPlace?: string | null;
|
||||||
|
motherFirstName?: string | null;
|
||||||
|
motherLastName?: string | null;
|
||||||
|
motherBirthPlace?: string | null;
|
||||||
|
|
||||||
|
fatherFirstNameEN?: string | null;
|
||||||
|
fatherLastNameEN?: string | null;
|
||||||
|
motherFirstNameEN?: string | null;
|
||||||
|
motherLastNameEN?: string | null;
|
||||||
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
type EmployeeUpdate = {
|
type EmployeeUpdate = {
|
||||||
customerBranchId?: string;
|
customerBranchId?: string;
|
||||||
status?: "ACTIVE" | "INACTIVE";
|
status?: "ACTIVE" | "INACTIVE";
|
||||||
|
|
||||||
code?: string;
|
|
||||||
nrcNo?: string;
|
nrcNo?: string;
|
||||||
|
|
||||||
dateOfBirth?: Date;
|
dateOfBirth?: Date;
|
||||||
|
|
@ -79,41 +131,129 @@ type EmployeeUpdate = {
|
||||||
addressEN?: string;
|
addressEN?: string;
|
||||||
address?: string;
|
address?: string;
|
||||||
zipCode?: string;
|
zipCode?: string;
|
||||||
email?: string;
|
|
||||||
telephoneNo?: string;
|
|
||||||
|
|
||||||
arrivalBarricade?: string;
|
passportType?: string;
|
||||||
arrivalCardNo?: string;
|
passportNumber?: string;
|
||||||
|
passportIssueDate?: Date;
|
||||||
|
passportExpiryDate?: Date;
|
||||||
|
passportIssuingCountry?: string;
|
||||||
|
passportIssuingPlace?: string;
|
||||||
|
previousPassportReference?: string;
|
||||||
|
|
||||||
|
visaType?: string | null;
|
||||||
|
visaNumber?: string | null;
|
||||||
|
visaIssueDate?: Date | null;
|
||||||
|
visaExpiryDate?: Date | null;
|
||||||
|
visaIssuingPlace?: string | null;
|
||||||
|
visaStayUntilDate?: Date | null;
|
||||||
|
tm6Number?: string | null;
|
||||||
|
entryDate?: Date | null;
|
||||||
|
workerStatus?: string | null;
|
||||||
|
|
||||||
subDistrictId?: string | null;
|
subDistrictId?: string | null;
|
||||||
districtId?: string | null;
|
districtId?: string | null;
|
||||||
provinceId?: string | null;
|
provinceId?: string | null;
|
||||||
|
|
||||||
|
employeeWork?: {
|
||||||
|
id?: string;
|
||||||
|
ownerName?: string | null;
|
||||||
|
positionName?: string | null;
|
||||||
|
jobType?: string | null;
|
||||||
|
workplace?: string | null;
|
||||||
|
workPermitNo?: string | null;
|
||||||
|
workPermitIssuDate?: Date | null;
|
||||||
|
workPermitExpireDate?: Date | null;
|
||||||
|
workEndDate?: Date | null;
|
||||||
|
remark?: string | null;
|
||||||
|
}[];
|
||||||
|
|
||||||
|
employeeCheckup?: {
|
||||||
|
id?: string;
|
||||||
|
checkupType?: string | null;
|
||||||
|
checkupResult?: string | null;
|
||||||
|
|
||||||
|
provinceId?: string | null;
|
||||||
|
|
||||||
|
hospitalName?: string | null;
|
||||||
|
remark?: string | null;
|
||||||
|
medicalBenefitScheme?: string | null;
|
||||||
|
insuranceCompany?: string | null;
|
||||||
|
coverageStartDate?: Date | null;
|
||||||
|
coverageExpireDate?: Date | null;
|
||||||
|
}[];
|
||||||
|
|
||||||
|
employeeOtherInfo?: {
|
||||||
|
citizenId?: string | null;
|
||||||
|
fatherFirstName?: string | null;
|
||||||
|
fatherLastName?: string | null;
|
||||||
|
fatherBirthPlace?: string | null;
|
||||||
|
motherFirstName?: string | null;
|
||||||
|
motherLastName?: string | null;
|
||||||
|
motherBirthPlace?: string | null;
|
||||||
|
|
||||||
|
fatherFirstNameEN?: string | null;
|
||||||
|
fatherLastNameEN?: string | null;
|
||||||
|
motherFirstNameEN?: string | null;
|
||||||
|
motherLastNameEN?: string | null;
|
||||||
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
@Route("api/employee")
|
@Route("api/v1/employee")
|
||||||
@Tags("Employee")
|
@Tags("Employee")
|
||||||
@Security("keycloak")
|
@Security("keycloak")
|
||||||
export class EmployeeController extends Controller {
|
export class EmployeeController extends Controller {
|
||||||
|
@Get("stats")
|
||||||
|
async getEmployeeStats(@Query() customerBranchId?: string) {
|
||||||
|
return await prisma.employee.count({
|
||||||
|
where: { customerBranchId },
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@Get("stats/gender")
|
||||||
|
async getEmployeeStatsGender(@Query() customerBranchId?: string) {
|
||||||
|
return await prisma.employee
|
||||||
|
.groupBy({
|
||||||
|
_count: true,
|
||||||
|
by: ["gender"],
|
||||||
|
where: { customerBranchId },
|
||||||
|
})
|
||||||
|
.then((res) =>
|
||||||
|
res.reduce<Record<string, number>>((a, c) => {
|
||||||
|
a[c.gender] = c._count;
|
||||||
|
return a;
|
||||||
|
}, {}),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
@Get()
|
@Get()
|
||||||
async list(
|
async list(
|
||||||
@Query() zipCode?: string,
|
@Query() zipCode?: string,
|
||||||
|
@Query() gender?: string,
|
||||||
|
@Query() status?: Status,
|
||||||
@Query() query: string = "",
|
@Query() query: string = "",
|
||||||
@Query() page: number = 1,
|
@Query() page: number = 1,
|
||||||
@Query() pageSize: number = 30,
|
@Query() pageSize: number = 30,
|
||||||
) {
|
) {
|
||||||
|
const filterStatus = (val?: Status) => {
|
||||||
|
if (!val) return {};
|
||||||
|
|
||||||
|
return val !== Status.CREATED && val !== Status.ACTIVE
|
||||||
|
? { status: val }
|
||||||
|
: { OR: [{ status: Status.CREATED }, { status: Status.ACTIVE }] };
|
||||||
|
};
|
||||||
|
|
||||||
const where = {
|
const where = {
|
||||||
OR: [
|
OR: [
|
||||||
{ firstName: { contains: query }, zipCode },
|
{ firstName: { contains: query }, zipCode, gender, ...filterStatus(status) },
|
||||||
{ firstNameEN: { contains: query }, zipCode },
|
{ firstNameEN: { contains: query }, zipCode, gender, ...filterStatus(status) },
|
||||||
{ lastName: { contains: query }, zipCode },
|
{ lastName: { contains: query }, zipCode, gender, ...filterStatus(status) },
|
||||||
{ lastNameEN: { contains: query }, zipCode },
|
{ lastNameEN: { contains: query }, zipCode, gender, ...filterStatus(status) },
|
||||||
{ email: { contains: query }, zipCode },
|
|
||||||
],
|
],
|
||||||
} satisfies Prisma.EmployeeWhereInput;
|
} satisfies Prisma.EmployeeWhereInput;
|
||||||
|
|
||||||
const [result, total] = await prisma.$transaction([
|
const [result, total] = await prisma.$transaction([
|
||||||
prisma.employee.findMany({
|
prisma.employee.findMany({
|
||||||
orderBy: { createdAt: "asc" },
|
orderBy: [{ statusOrder: "asc" }, { createdAt: "asc" }],
|
||||||
include: {
|
include: {
|
||||||
province: true,
|
province: true,
|
||||||
district: true,
|
district: true,
|
||||||
|
|
@ -130,7 +270,7 @@ export class EmployeeController extends Controller {
|
||||||
result: await Promise.all(
|
result: await Promise.all(
|
||||||
result.map(async (v) => ({
|
result.map(async (v) => ({
|
||||||
...v,
|
...v,
|
||||||
profileImageUrl: await minio.presignedGetObject(
|
profileImageUrl: await presignedGetObjectIfExist(
|
||||||
MINIO_BUCKET,
|
MINIO_BUCKET,
|
||||||
imageLocation(v.id),
|
imageLocation(v.id),
|
||||||
12 * 60 * 60,
|
12 * 60 * 60,
|
||||||
|
|
@ -155,7 +295,7 @@ export class EmployeeController extends Controller {
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!record) {
|
if (!record) {
|
||||||
throw new HttpError(HttpStatus.NOT_FOUND, "Employee cannot be found.", "data_not_found");
|
throw new HttpError(HttpStatus.NOT_FOUND, "Employee cannot be found.", "employeeNotFound");
|
||||||
}
|
}
|
||||||
|
|
||||||
return record;
|
return record;
|
||||||
|
|
@ -163,67 +303,148 @@ export class EmployeeController extends Controller {
|
||||||
|
|
||||||
@Post()
|
@Post()
|
||||||
async create(@Request() req: RequestWithUser, @Body() body: EmployeeCreate) {
|
async create(@Request() req: RequestWithUser, @Body() body: EmployeeCreate) {
|
||||||
if (body.provinceId || body.districtId || body.subDistrictId || body.customerBranchId) {
|
const [province, district, subDistrict, customerBranch] = await prisma.$transaction([
|
||||||
const [province, district, subDistrict, customerBranch] = await prisma.$transaction([
|
prisma.province.findFirst({ where: { id: body.provinceId || undefined } }),
|
||||||
prisma.province.findFirst({ where: { id: body.provinceId || undefined } }),
|
prisma.district.findFirst({ where: { id: body.districtId || undefined } }),
|
||||||
prisma.district.findFirst({ where: { id: body.districtId || undefined } }),
|
prisma.subDistrict.findFirst({ where: { id: body.subDistrictId || undefined } }),
|
||||||
prisma.subDistrict.findFirst({ where: { id: body.subDistrictId || undefined } }),
|
prisma.customerBranch.findFirst({
|
||||||
prisma.customerBranch.findFirst({ where: { id: body.customerBranchId || undefined } }),
|
where: { id: body.customerBranchId },
|
||||||
|
include: { customer: true },
|
||||||
|
}),
|
||||||
|
]);
|
||||||
|
if (body.provinceId !== province?.id)
|
||||||
|
throw new HttpError(
|
||||||
|
HttpStatus.BAD_REQUEST,
|
||||||
|
"Province cannot be found.",
|
||||||
|
"relationProvinceNotFound",
|
||||||
|
);
|
||||||
|
if (body.districtId !== district?.id)
|
||||||
|
throw new HttpError(
|
||||||
|
HttpStatus.BAD_REQUEST,
|
||||||
|
"District cannot be found.",
|
||||||
|
"relationDistrictNotFound",
|
||||||
|
);
|
||||||
|
if (body.subDistrictId !== subDistrict?.id)
|
||||||
|
throw new HttpError(
|
||||||
|
HttpStatus.BAD_REQUEST,
|
||||||
|
"Sub-district cannot be found.",
|
||||||
|
"relationSubDistrictNotFound",
|
||||||
|
);
|
||||||
|
if (!customerBranch)
|
||||||
|
throw new HttpError(
|
||||||
|
HttpStatus.BAD_REQUEST,
|
||||||
|
"Customer Branch cannot be found.",
|
||||||
|
"relationCustomerBranchNotFound",
|
||||||
|
);
|
||||||
|
|
||||||
|
const {
|
||||||
|
provinceId,
|
||||||
|
districtId,
|
||||||
|
subDistrictId,
|
||||||
|
customerBranchId,
|
||||||
|
employeeWork,
|
||||||
|
employeeCheckup,
|
||||||
|
employeeOtherInfo,
|
||||||
|
...rest
|
||||||
|
} = body;
|
||||||
|
|
||||||
|
const listProvinceId = employeeCheckup?.reduce<string[]>((acc, cur) => {
|
||||||
|
if (cur.provinceId && !acc.includes(cur.provinceId)) return acc.concat(cur.provinceId);
|
||||||
|
if (!cur.provinceId) cur.provinceId = null;
|
||||||
|
return acc;
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
if (listProvinceId) {
|
||||||
|
const [listProvince] = await prisma.$transaction([
|
||||||
|
prisma.province.findMany({ where: { id: { in: listProvinceId } } }),
|
||||||
]);
|
]);
|
||||||
if (body.provinceId && !province)
|
if (listProvince.length !== listProvinceId.length) {
|
||||||
throw new HttpError(
|
throw new HttpError(
|
||||||
HttpStatus.BAD_REQUEST,
|
HttpStatus.BAD_REQUEST,
|
||||||
"Province cannot be found.",
|
"Some province cannot be found.",
|
||||||
"missing_or_invalid_parameter",
|
"someProvinceNotFound",
|
||||||
);
|
|
||||||
if (body.districtId && !district)
|
|
||||||
throw new HttpError(
|
|
||||||
HttpStatus.BAD_REQUEST,
|
|
||||||
"District cannot be found.",
|
|
||||||
"missing_or_invalid_parameter",
|
|
||||||
);
|
|
||||||
if (body.subDistrictId && !subDistrict)
|
|
||||||
throw new HttpError(
|
|
||||||
HttpStatus.BAD_REQUEST,
|
|
||||||
"Sub-district cannot be found.",
|
|
||||||
"missing_or_invalid_parameter",
|
|
||||||
);
|
|
||||||
if (body.customerBranchId && !customerBranch)
|
|
||||||
throw new HttpError(
|
|
||||||
HttpStatus.BAD_REQUEST,
|
|
||||||
"Customer Branch cannot be found.",
|
|
||||||
"missing_or_invalid_parameter",
|
|
||||||
);
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const { provinceId, districtId, subDistrictId, customerBranchId, ...rest } = body;
|
const record = await prisma.$transaction(
|
||||||
|
async (tx) => {
|
||||||
|
const last = await tx.runningNo.upsert({
|
||||||
|
where: {
|
||||||
|
key: `EMPLOYEE_${customerBranch.customer.code}-${customerBranch.branchNo.toString().padStart(2, "0")}-${new Date().getFullYear().toString().slice(-2).padStart(2, "0")}`,
|
||||||
|
},
|
||||||
|
create: {
|
||||||
|
key: `EMPLOYEE_${customerBranch.customer.code}-${customerBranch.branchNo.toString().padStart(2, "0")}-${new Date().getFullYear().toString().slice(-2).padStart(2, "0")}`,
|
||||||
|
value: 1,
|
||||||
|
},
|
||||||
|
update: { value: { increment: 1 } },
|
||||||
|
});
|
||||||
|
|
||||||
const record = await prisma.employee.create({
|
return await prisma.employee.create({
|
||||||
include: {
|
include: {
|
||||||
province: true,
|
province: true,
|
||||||
district: true,
|
district: true,
|
||||||
subDistrict: true,
|
subDistrict: true,
|
||||||
|
employeeOtherInfo: true,
|
||||||
|
employeeCheckup: {
|
||||||
|
include: {
|
||||||
|
province: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
employeeWork: true,
|
||||||
|
},
|
||||||
|
data: {
|
||||||
|
...rest,
|
||||||
|
statusOrder: +(rest.status === "INACTIVE"),
|
||||||
|
code: `${customerBranch.customer.code}-${customerBranch.branchNo.toString().padStart(2, "0")}-${new Date().getFullYear().toString().slice(-2).padStart(2, "0")}${last.value.toString().padStart(4, "0")}`,
|
||||||
|
employeeWork: {
|
||||||
|
createMany: {
|
||||||
|
data: employeeWork || [],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
employeeCheckup: {
|
||||||
|
createMany: {
|
||||||
|
data:
|
||||||
|
employeeCheckup?.map((v) => ({
|
||||||
|
...v,
|
||||||
|
provinceId: !!v.provinceId ? null : v.provinceId,
|
||||||
|
})) || [],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
employeeOtherInfo: {
|
||||||
|
create: employeeOtherInfo,
|
||||||
|
},
|
||||||
|
province: { connect: provinceId ? { id: provinceId } : undefined },
|
||||||
|
district: { connect: districtId ? { id: districtId } : undefined },
|
||||||
|
subDistrict: { connect: subDistrictId ? { id: subDistrictId } : undefined },
|
||||||
|
customerBranch: { connect: { id: customerBranchId } },
|
||||||
|
createdBy: req.user.name,
|
||||||
|
updatedBy: req.user.name,
|
||||||
|
},
|
||||||
|
});
|
||||||
},
|
},
|
||||||
data: {
|
{ isolationLevel: Prisma.TransactionIsolationLevel.Serializable },
|
||||||
...rest,
|
);
|
||||||
province: { connect: provinceId ? { id: provinceId } : undefined },
|
|
||||||
district: { connect: districtId ? { id: districtId } : undefined },
|
|
||||||
subDistrict: { connect: subDistrictId ? { id: subDistrictId } : undefined },
|
|
||||||
customerBranch: { connect: { id: customerBranchId } },
|
|
||||||
createdBy: req.user.name,
|
|
||||||
updateBy: req.user.name,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
await prisma.customerBranch.updateMany({
|
await prisma.customerBranch.updateMany({
|
||||||
where: { id: customerBranchId, status: Status.CREATED },
|
where: { id: customerBranchId, status: Status.CREATED },
|
||||||
data: { status: Status.ACTIVE },
|
data: { status: Status.ACTIVE },
|
||||||
});
|
});
|
||||||
|
|
||||||
|
await prisma.customer.updateMany({
|
||||||
|
where: {
|
||||||
|
branch: {
|
||||||
|
some: { id: customerBranchId },
|
||||||
|
},
|
||||||
|
status: Status.CREATED,
|
||||||
|
},
|
||||||
|
data: { status: Status.ACTIVE },
|
||||||
|
});
|
||||||
|
|
||||||
this.setStatus(HttpStatus.CREATED);
|
this.setStatus(HttpStatus.CREATED);
|
||||||
|
|
||||||
return Object.assign(record, {
|
return Object.assign(record, {
|
||||||
profileImageUrl: await minio.presignedPutObject(
|
profileImageUrl: await presignedGetObjectIfExist(
|
||||||
MINIO_BUCKET,
|
MINIO_BUCKET,
|
||||||
imageLocation(record.id),
|
imageLocation(record.id),
|
||||||
12 * 60 * 60,
|
12 * 60 * 60,
|
||||||
|
|
@ -242,71 +463,223 @@ export class EmployeeController extends Controller {
|
||||||
@Body() body: EmployeeUpdate,
|
@Body() body: EmployeeUpdate,
|
||||||
@Path() employeeId: string,
|
@Path() employeeId: string,
|
||||||
) {
|
) {
|
||||||
if (body.provinceId || body.districtId || body.subDistrictId || body.customerBranchId) {
|
const [province, district, subDistrict, customerBranch, employee] = await prisma.$transaction([
|
||||||
const [province, district, subDistrict, customerBranch] = await prisma.$transaction([
|
prisma.province.findFirst({ where: { id: body.provinceId || undefined } }),
|
||||||
prisma.province.findFirst({ where: { id: body.provinceId || undefined } }),
|
prisma.district.findFirst({ where: { id: body.districtId || undefined } }),
|
||||||
prisma.district.findFirst({ where: { id: body.districtId || undefined } }),
|
prisma.subDistrict.findFirst({ where: { id: body.subDistrictId || undefined } }),
|
||||||
prisma.subDistrict.findFirst({ where: { id: body.subDistrictId || undefined } }),
|
prisma.customerBranch.findFirst({
|
||||||
prisma.customerBranch.findFirst({ where: { id: body.customerBranchId || undefined } }),
|
where: { id: body.customerBranchId || undefined },
|
||||||
]);
|
include: { customer: true },
|
||||||
if (body.provinceId && !province)
|
}),
|
||||||
throw new HttpError(
|
prisma.employee.findFirst({ where: { id: employeeId } }),
|
||||||
HttpStatus.BAD_REQUEST,
|
]);
|
||||||
"Province cannot be found.",
|
if (body.provinceId && !province)
|
||||||
"missing_or_invalid_parameter",
|
throw new HttpError(
|
||||||
);
|
HttpStatus.BAD_REQUEST,
|
||||||
if (body.districtId && !district)
|
"Province cannot be found.",
|
||||||
throw new HttpError(
|
"relationProvinceNotFound",
|
||||||
HttpStatus.BAD_REQUEST,
|
);
|
||||||
"District cannot be found.",
|
if (body.districtId && !district)
|
||||||
"missing_or_invalid_parameter",
|
throw new HttpError(
|
||||||
);
|
HttpStatus.BAD_REQUEST,
|
||||||
if (body.subDistrictId && !subDistrict)
|
"District cannot be found.",
|
||||||
throw new HttpError(
|
"relationDistrictNotFound",
|
||||||
HttpStatus.BAD_REQUEST,
|
);
|
||||||
"Sub-district cannot be found.",
|
if (body.subDistrictId && !subDistrict)
|
||||||
"missing_or_invalid_parameter",
|
throw new HttpError(
|
||||||
);
|
HttpStatus.BAD_REQUEST,
|
||||||
if (body.customerBranchId && !customerBranch)
|
"Sub-district cannot be found.",
|
||||||
throw new HttpError(
|
"relationSubDistrictNotFound",
|
||||||
HttpStatus.BAD_REQUEST,
|
);
|
||||||
"Customer cannot be found.",
|
if (body.customerBranchId && !customerBranch)
|
||||||
"missing_or_invalid_parameter",
|
throw new HttpError(
|
||||||
);
|
HttpStatus.BAD_REQUEST,
|
||||||
|
"Customer cannot be found.",
|
||||||
|
"relationCustomerNotFound",
|
||||||
|
);
|
||||||
|
if (!employee) {
|
||||||
|
throw new HttpError(HttpStatus.NOT_FOUND, "Employee cannot be found.", "employeeNotFound");
|
||||||
}
|
}
|
||||||
|
|
||||||
const { provinceId, districtId, subDistrictId, customerBranchId, ...rest } = body;
|
const {
|
||||||
|
provinceId,
|
||||||
|
districtId,
|
||||||
|
subDistrictId,
|
||||||
|
customerBranchId,
|
||||||
|
employeeWork,
|
||||||
|
employeeCheckup,
|
||||||
|
employeeOtherInfo,
|
||||||
|
...rest
|
||||||
|
} = body;
|
||||||
|
|
||||||
const record = await prisma.employee.update({
|
const listProvinceId = employeeCheckup?.reduce<string[]>((acc, cur) => {
|
||||||
where: { id: employeeId },
|
if (cur.provinceId && !acc.includes(cur.provinceId)) return acc.concat(cur.provinceId);
|
||||||
include: {
|
if (!cur.provinceId) cur.provinceId = null;
|
||||||
province: true,
|
return acc;
|
||||||
district: true,
|
}, []);
|
||||||
subDistrict: true,
|
|
||||||
},
|
if (listProvinceId) {
|
||||||
data: {
|
const [listProvince] = await prisma.$transaction([
|
||||||
...rest,
|
prisma.province.findMany({ where: { id: { in: listProvinceId } } }),
|
||||||
customerBranch: { connect: customerBranchId ? { id: customerBranchId } : undefined },
|
]);
|
||||||
province: {
|
if (listProvince.length !== listProvinceId.length) {
|
||||||
connect: provinceId ? { id: provinceId } : undefined,
|
throw new HttpError(
|
||||||
disconnect: provinceId === null || undefined,
|
HttpStatus.BAD_REQUEST,
|
||||||
|
"Some province cannot be found.",
|
||||||
|
"someProvinceNotFound",
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const record = await prisma.$transaction(async (tx) => {
|
||||||
|
let code: string | undefined;
|
||||||
|
|
||||||
|
if (customerBranch && customerBranch.id !== employee.customerBranchId) {
|
||||||
|
const last = await tx.runningNo.upsert({
|
||||||
|
where: {
|
||||||
|
key: `EMPLOYEE_${customerBranch.customer.code}-${customerBranch.branchNo.toString().padStart(2, "0")}-${new Date().getFullYear().toString().slice(-2).padStart(2, "0")}`,
|
||||||
|
},
|
||||||
|
create: {
|
||||||
|
key: `EMPLOYEE_${customerBranch.customer.code}-${customerBranch.branchNo.toString().padStart(2, "0")}-${new Date().getFullYear().toString().slice(-2).padStart(2, "0")}`,
|
||||||
|
value: 1,
|
||||||
|
},
|
||||||
|
update: { value: { increment: 1 } },
|
||||||
|
});
|
||||||
|
code = `${customerBranch.customer.code}-${customerBranch.branchNo.toString().padStart(2, "0")}-${new Date().getFullYear().toString().slice(-2).padStart(2, "0")}${last.value.toString().padStart(4, "0")}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
return await prisma.employee.update({
|
||||||
|
where: { id: employeeId },
|
||||||
|
include: {
|
||||||
|
province: true,
|
||||||
|
district: true,
|
||||||
|
subDistrict: true,
|
||||||
|
employeeOtherInfo: true,
|
||||||
|
employeeCheckup: {
|
||||||
|
include: {
|
||||||
|
province: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
employeeWork: true,
|
||||||
},
|
},
|
||||||
district: {
|
data: {
|
||||||
connect: districtId ? { id: districtId } : undefined,
|
...rest,
|
||||||
disconnect: districtId === null || undefined,
|
statusOrder: +(rest.status === "INACTIVE"),
|
||||||
|
code,
|
||||||
|
customerBranch: { connect: customerBranchId ? { id: customerBranchId } : undefined },
|
||||||
|
employeeWork: employeeWork
|
||||||
|
? {
|
||||||
|
deleteMany: {
|
||||||
|
id: {
|
||||||
|
notIn: employeeWork.map((v) => v.id).filter((v): v is string => !!v) || [],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
upsert: employeeWork.map((v) => ({
|
||||||
|
where: { id: v.id || "" },
|
||||||
|
create: {
|
||||||
|
...v,
|
||||||
|
createdBy: req.user.name,
|
||||||
|
updatedBy: req.user.name,
|
||||||
|
id: undefined,
|
||||||
|
},
|
||||||
|
update: {
|
||||||
|
...v,
|
||||||
|
updatedBy: req.user.name,
|
||||||
|
},
|
||||||
|
})),
|
||||||
|
}
|
||||||
|
: undefined,
|
||||||
|
|
||||||
|
employeeCheckup: employeeCheckup
|
||||||
|
? {
|
||||||
|
deleteMany: {
|
||||||
|
id: {
|
||||||
|
notIn: employeeCheckup.map((v) => v.id).filter((v): v is string => !!v) || [],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
upsert: employeeCheckup.map((v) => ({
|
||||||
|
where: { id: v.id || "" },
|
||||||
|
create: {
|
||||||
|
...v,
|
||||||
|
provinceId: !v.provinceId ? undefined : v.provinceId,
|
||||||
|
createdBy: req.user.name,
|
||||||
|
updatedBy: req.user.name,
|
||||||
|
id: undefined,
|
||||||
|
},
|
||||||
|
update: {
|
||||||
|
...v,
|
||||||
|
updatedBy: req.user.name,
|
||||||
|
},
|
||||||
|
})),
|
||||||
|
}
|
||||||
|
: undefined,
|
||||||
|
employeeOtherInfo: employeeOtherInfo
|
||||||
|
? {
|
||||||
|
deleteMany: {},
|
||||||
|
create: employeeOtherInfo,
|
||||||
|
}
|
||||||
|
: undefined,
|
||||||
|
province: {
|
||||||
|
connect: provinceId ? { id: provinceId } : undefined,
|
||||||
|
disconnect: provinceId === null || undefined,
|
||||||
|
},
|
||||||
|
district: {
|
||||||
|
connect: districtId ? { id: districtId } : undefined,
|
||||||
|
disconnect: districtId === null || undefined,
|
||||||
|
},
|
||||||
|
subDistrict: {
|
||||||
|
connect: subDistrictId ? { id: subDistrictId } : undefined,
|
||||||
|
disconnect: subDistrictId === null || undefined,
|
||||||
|
},
|
||||||
|
createdBy: req.user.name,
|
||||||
|
updatedBy: req.user.name,
|
||||||
},
|
},
|
||||||
subDistrict: {
|
});
|
||||||
connect: subDistrictId ? { id: subDistrictId } : undefined,
|
|
||||||
disconnect: subDistrictId === null || undefined,
|
|
||||||
},
|
|
||||||
createdBy: req.user.name,
|
|
||||||
updateBy: req.user.name,
|
|
||||||
},
|
|
||||||
});
|
});
|
||||||
|
|
||||||
this.setStatus(HttpStatus.CREATED);
|
const historyEntries: { field: string; valueBefore: string; valueAfter: string }[] = [];
|
||||||
|
|
||||||
return record;
|
for (const k of Object.keys(body)) {
|
||||||
|
const field = k as keyof typeof body;
|
||||||
|
|
||||||
|
if (field === "employeeCheckup") continue;
|
||||||
|
if (field === "employeeOtherInfo") continue;
|
||||||
|
if (field === "employeeWork") continue;
|
||||||
|
|
||||||
|
let valueBefore = employee[field];
|
||||||
|
let valueAfter = body[field];
|
||||||
|
|
||||||
|
if (valueBefore === undefined && valueAfter === undefined) continue;
|
||||||
|
|
||||||
|
if (valueBefore instanceof Date) valueBefore = valueBefore.toISOString();
|
||||||
|
if (valueBefore === null || valueBefore === undefined) valueBefore = "";
|
||||||
|
if (valueAfter instanceof Date) valueAfter = valueAfter.toISOString();
|
||||||
|
if (valueAfter === null || valueAfter === undefined) valueAfter = "";
|
||||||
|
|
||||||
|
if (valueBefore !== valueAfter) historyEntries.push({ field, valueBefore, valueAfter });
|
||||||
|
}
|
||||||
|
|
||||||
|
await prisma.employeeHistory.createMany({
|
||||||
|
data: historyEntries.map((v) => ({
|
||||||
|
...v,
|
||||||
|
updatedByUserId: req.user.sub,
|
||||||
|
updatedBy: req.user.preferred_username,
|
||||||
|
masterId: employee.id,
|
||||||
|
})),
|
||||||
|
});
|
||||||
|
|
||||||
|
return Object.assign(record, {
|
||||||
|
profileImageUrl: await presignedGetObjectIfExist(
|
||||||
|
MINIO_BUCKET,
|
||||||
|
imageLocation(record.id),
|
||||||
|
12 * 60 * 60,
|
||||||
|
),
|
||||||
|
profileImageUploadUrl: await minio.presignedPutObject(
|
||||||
|
MINIO_BUCKET,
|
||||||
|
imageLocation(record.id),
|
||||||
|
12 * 60 * 60,
|
||||||
|
),
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@Delete("{employeeId}")
|
@Delete("{employeeId}")
|
||||||
|
|
@ -314,17 +687,20 @@ export class EmployeeController extends Controller {
|
||||||
const record = await prisma.employee.findFirst({ where: { id: employeeId } });
|
const record = await prisma.employee.findFirst({ where: { id: employeeId } });
|
||||||
|
|
||||||
if (!record) {
|
if (!record) {
|
||||||
throw new HttpError(HttpStatus.NOT_FOUND, "Employee cannot be found.", "data_not_found");
|
throw new HttpError(HttpStatus.NOT_FOUND, "Employee cannot be found.", "employeeNotFound");
|
||||||
}
|
}
|
||||||
|
|
||||||
if (record.status !== Status.CREATED) {
|
if (record.status !== Status.CREATED) {
|
||||||
throw new HttpError(
|
throw new HttpError(HttpStatus.FORBIDDEN, "Employee is in used.", "employeeInUsed");
|
||||||
HttpStatus.FORBIDDEN,
|
|
||||||
"Emplyee is in used.",
|
|
||||||
"missing_or_invalid_parameter",
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return await prisma.employee.delete({ where: { id: employeeId } });
|
return await prisma.employee.delete({ where: { id: employeeId } });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Get("{employeeId}/edit-history")
|
||||||
|
async editHistory(@Path() employeeId: string) {
|
||||||
|
return await prisma.employeeHistory.findMany({
|
||||||
|
where: { masterId: employeeId },
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,3 @@
|
||||||
import { Prisma, Status } from "@prisma/client";
|
|
||||||
import {
|
import {
|
||||||
Body,
|
Body,
|
||||||
Controller,
|
Controller,
|
||||||
|
|
@ -7,7 +6,6 @@ import {
|
||||||
Put,
|
Put,
|
||||||
Path,
|
Path,
|
||||||
Post,
|
Post,
|
||||||
Query,
|
|
||||||
Request,
|
Request,
|
||||||
Route,
|
Route,
|
||||||
Security,
|
Security,
|
||||||
|
|
@ -19,54 +17,44 @@ 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";
|
||||||
|
|
||||||
type EmployeeOtherInfoCreate = {
|
type EmployeeOtherInfoPayload = {
|
||||||
citizenId: string;
|
citizenId?: string | null;
|
||||||
fatherFullName: string;
|
fatherFirstName?: string | null;
|
||||||
motherFullName: string;
|
fatherLastName?: string | null;
|
||||||
birthPlace: string;
|
fatherBirthPlace?: string | null;
|
||||||
|
motherFirstName?: string | null;
|
||||||
|
motherLastName?: string | null;
|
||||||
|
motherBirthPlace?: string | null;
|
||||||
|
|
||||||
|
fatherFirstNameEN?: string | null;
|
||||||
|
fatherLastNameEN?: string | null;
|
||||||
|
motherFirstNameEN?: string | null;
|
||||||
|
motherLastNameEN?: string | null;
|
||||||
};
|
};
|
||||||
|
|
||||||
type EmployeeOtherInfoUpdate = {
|
@Route("api/v1/employee/{employeeId}/other-info")
|
||||||
citizenId: string;
|
|
||||||
fatherFullName: string;
|
|
||||||
motherFullName: string;
|
|
||||||
birthPlace: string;
|
|
||||||
};
|
|
||||||
|
|
||||||
@Route("api/employee/{employeeId}/other-info")
|
|
||||||
@Tags("Employee Other Info")
|
@Tags("Employee Other Info")
|
||||||
@Security("keycloak")
|
@Security("keycloak")
|
||||||
export class EmployeeOtherInfo extends Controller {
|
export class EmployeeOtherInfo extends Controller {
|
||||||
@Get()
|
@Get()
|
||||||
async list(@Path() employeeId: string) {
|
async list(@Path() employeeId: string) {
|
||||||
return prisma.employeeOtherInfo.findMany({
|
return prisma.employeeOtherInfo.findFirst({
|
||||||
orderBy: { createdAt: "asc" },
|
orderBy: { createdAt: "asc" },
|
||||||
where: { employeeId },
|
where: { employeeId },
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@Get("{otherInfoId}")
|
|
||||||
async getById(@Path() employeeId: string, @Path() otherInfoId: string) {
|
|
||||||
const record = await prisma.employeeOtherInfo.findFirst({
|
|
||||||
where: { id: otherInfoId, employeeId },
|
|
||||||
});
|
|
||||||
if (!record) {
|
|
||||||
throw new HttpError(HttpStatus.NOT_FOUND, "Employee info cannot be found.", "data_not_found");
|
|
||||||
}
|
|
||||||
return record;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Post()
|
@Post()
|
||||||
async create(
|
async create(
|
||||||
@Request() req: RequestWithUser,
|
@Request() req: RequestWithUser,
|
||||||
@Path() employeeId: string,
|
@Path() employeeId: string,
|
||||||
@Body() body: EmployeeOtherInfoCreate,
|
@Body() body: EmployeeOtherInfoPayload,
|
||||||
) {
|
) {
|
||||||
if (!(await prisma.employee.findUnique({ where: { id: employeeId } })))
|
if (!(await prisma.employee.findUnique({ where: { id: employeeId } })))
|
||||||
throw new HttpError(
|
throw new HttpError(
|
||||||
HttpStatus.BAD_REQUEST,
|
HttpStatus.BAD_REQUEST,
|
||||||
"Employee cannot be found.",
|
"Employee cannot be found.",
|
||||||
"missing_or_invalid_parameter",
|
"employeeBadReq",
|
||||||
);
|
);
|
||||||
|
|
||||||
const record = await prisma.employeeOtherInfo.create({
|
const record = await prisma.employeeOtherInfo.create({
|
||||||
|
|
@ -74,7 +62,7 @@ export class EmployeeOtherInfo extends Controller {
|
||||||
...body,
|
...body,
|
||||||
employee: { connect: { id: employeeId } },
|
employee: { connect: { id: employeeId } },
|
||||||
createdBy: req.user.name,
|
createdBy: req.user.name,
|
||||||
updateBy: req.user.name,
|
updatedBy: req.user.name,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
@ -88,19 +76,19 @@ export class EmployeeOtherInfo extends Controller {
|
||||||
@Request() req: RequestWithUser,
|
@Request() req: RequestWithUser,
|
||||||
@Path() employeeId: string,
|
@Path() employeeId: string,
|
||||||
@Path() otherInfoId: string,
|
@Path() otherInfoId: string,
|
||||||
@Body() body: EmployeeOtherInfoUpdate,
|
@Body() body: EmployeeOtherInfoPayload,
|
||||||
) {
|
) {
|
||||||
if (!(await prisma.employeeOtherInfo.findUnique({ where: { id: otherInfoId, employeeId } }))) {
|
if (!(await prisma.employeeOtherInfo.findUnique({ where: { id: otherInfoId, employeeId } }))) {
|
||||||
throw new HttpError(
|
throw new HttpError(
|
||||||
HttpStatus.NOT_FOUND,
|
HttpStatus.NOT_FOUND,
|
||||||
"Employee other info cannot be found.",
|
"Employee other info cannot be found.",
|
||||||
"data_not_found",
|
"employeeOtherNotFound",
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
const record = await prisma.employeeOtherInfo.update({
|
const record = await prisma.employeeOtherInfo.update({
|
||||||
where: { id: otherInfoId, employeeId },
|
where: { id: otherInfoId, employeeId },
|
||||||
data: { ...body, createdBy: req.user.name, updateBy: req.user.name },
|
data: { ...body, createdBy: req.user.name, updatedBy: req.user.name },
|
||||||
});
|
});
|
||||||
|
|
||||||
this.setStatus(HttpStatus.CREATED);
|
this.setStatus(HttpStatus.CREATED);
|
||||||
|
|
@ -118,7 +106,7 @@ export class EmployeeOtherInfo extends Controller {
|
||||||
throw new HttpError(
|
throw new HttpError(
|
||||||
HttpStatus.NOT_FOUND,
|
HttpStatus.NOT_FOUND,
|
||||||
"Employee other info cannot be found.",
|
"Employee other info cannot be found.",
|
||||||
"data_not_found",
|
"employeeOtherNotFound",
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
112
src/controllers/employee-work-controller.ts
Normal file
112
src/controllers/employee-work-controller.ts
Normal file
|
|
@ -0,0 +1,112 @@
|
||||||
|
import {
|
||||||
|
Body,
|
||||||
|
Controller,
|
||||||
|
Delete,
|
||||||
|
Get,
|
||||||
|
Path,
|
||||||
|
Post,
|
||||||
|
Put,
|
||||||
|
Request,
|
||||||
|
Route,
|
||||||
|
Security,
|
||||||
|
Tags,
|
||||||
|
} from "tsoa";
|
||||||
|
import { RequestWithUser } from "../interfaces/user";
|
||||||
|
import prisma from "../db";
|
||||||
|
import HttpStatus from "../interfaces/http-status";
|
||||||
|
import HttpError from "../interfaces/http-error";
|
||||||
|
|
||||||
|
type EmployeeWorkPayload = {
|
||||||
|
ownerName?: string | null;
|
||||||
|
positionName?: string | null;
|
||||||
|
jobType?: string | null;
|
||||||
|
workplace?: string | null;
|
||||||
|
workPermitNo?: string | null;
|
||||||
|
workPermitIssuDate?: Date | null;
|
||||||
|
workPermitExpireDate?: Date | null;
|
||||||
|
workEndDate?: Date | null;
|
||||||
|
remark?: string | null;
|
||||||
|
};
|
||||||
|
|
||||||
|
@Route("api/v1/employee/{employeeId}/work")
|
||||||
|
@Tags("Employee Work")
|
||||||
|
@Security("keycloak")
|
||||||
|
export class EmployeeWorkController extends Controller {
|
||||||
|
@Get()
|
||||||
|
async list(@Path() employeeId: string) {
|
||||||
|
return prisma.employeeWork.findMany({
|
||||||
|
orderBy: { createdAt: "asc" },
|
||||||
|
where: { employeeId },
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@Get("{workId}")
|
||||||
|
async getById(@Path() employeeId: string, @Path() workId: string) {
|
||||||
|
const record = await prisma.employeeWork.findFirst({
|
||||||
|
where: { id: workId, employeeId },
|
||||||
|
});
|
||||||
|
if (!record) {
|
||||||
|
throw new HttpError(HttpStatus.NOT_FOUND, "Employee work cannot be found.", "employeeWorkNotFound");
|
||||||
|
}
|
||||||
|
return record;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Post()
|
||||||
|
async create(
|
||||||
|
@Request() req: RequestWithUser,
|
||||||
|
@Path() employeeId: string,
|
||||||
|
@Body() body: EmployeeWorkPayload,
|
||||||
|
) {
|
||||||
|
if (!(await prisma.employee.findUnique({ where: { id: employeeId } })))
|
||||||
|
throw new HttpError(
|
||||||
|
HttpStatus.BAD_REQUEST,
|
||||||
|
"Employee cannot be found.",
|
||||||
|
"employeeBadReq",
|
||||||
|
);
|
||||||
|
|
||||||
|
const record = await prisma.employeeWork.create({
|
||||||
|
data: {
|
||||||
|
...body,
|
||||||
|
employee: { connect: { id: employeeId } },
|
||||||
|
createdBy: req.user.name,
|
||||||
|
updatedBy: req.user.name,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
this.setStatus(HttpStatus.CREATED);
|
||||||
|
|
||||||
|
return record;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Put("{workId}")
|
||||||
|
async editById(
|
||||||
|
@Request() req: RequestWithUser,
|
||||||
|
@Path() employeeId: string,
|
||||||
|
@Path() workId: string,
|
||||||
|
@Body() body: EmployeeWorkPayload,
|
||||||
|
) {
|
||||||
|
if (!(await prisma.employeeWork.findUnique({ where: { id: workId, employeeId } }))) {
|
||||||
|
throw new HttpError(HttpStatus.NOT_FOUND, "Employee work cannot be found.", "employeeWorkNotFound");
|
||||||
|
}
|
||||||
|
|
||||||
|
const record = await prisma.employeeWork.update({
|
||||||
|
where: { id: workId, employeeId },
|
||||||
|
data: { ...body, createdBy: req.user.name, updatedBy: req.user.name },
|
||||||
|
});
|
||||||
|
|
||||||
|
this.setStatus(HttpStatus.CREATED);
|
||||||
|
|
||||||
|
return record;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Delete("{workId}")
|
||||||
|
async deleteById(@Path() employeeId: string, @Path() workId: string) {
|
||||||
|
const record = await prisma.employeeWork.findFirst({ where: { id: workId, employeeId } });
|
||||||
|
|
||||||
|
if (!record) {
|
||||||
|
throw new HttpError(HttpStatus.NOT_FOUND, "Employee work cannot be found.", "employeeWorkNotFound");
|
||||||
|
}
|
||||||
|
|
||||||
|
return await prisma.employeeWork.delete({ where: { id: workId, employeeId } });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -8,7 +8,7 @@ import {
|
||||||
removeUserRoles,
|
removeUserRoles,
|
||||||
} from "../services/keycloak";
|
} from "../services/keycloak";
|
||||||
|
|
||||||
@Route("api/keycloak")
|
@Route("api/v1/keycloak")
|
||||||
@Tags("Single-Sign On")
|
@Tags("Single-Sign On")
|
||||||
@Security("keycloak")
|
@Security("keycloak")
|
||||||
export class KeycloakController extends Controller {
|
export class KeycloakController extends Controller {
|
||||||
|
|
|
||||||
|
|
@ -19,7 +19,7 @@ type MenuEdit = {
|
||||||
url: string;
|
url: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
@Route("v1/permission/menu")
|
@Route("api/v1/permission/menu")
|
||||||
@Tags("Permission")
|
@Tags("Permission")
|
||||||
@Security("keycloak")
|
@Security("keycloak")
|
||||||
export class MenuController extends Controller {
|
export class MenuController extends Controller {
|
||||||
|
|
@ -41,7 +41,7 @@ export class MenuController extends Controller {
|
||||||
throw new HttpError(
|
throw new HttpError(
|
||||||
HttpStatus.BAD_REQUEST,
|
HttpStatus.BAD_REQUEST,
|
||||||
"Parent menu not found.",
|
"Parent menu not found.",
|
||||||
"missing_or_invalid_parameter",
|
"parentMenuBadReq",
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -66,7 +66,7 @@ export class MenuController extends Controller {
|
||||||
})
|
})
|
||||||
.catch((e) => {
|
.catch((e) => {
|
||||||
if (e instanceof PrismaClientKnownRequestError && e.code === "P2025") {
|
if (e instanceof PrismaClientKnownRequestError && e.code === "P2025") {
|
||||||
throw new HttpError(HttpStatus.NOT_FOUND, "Menu cannot be found.", "data_not_found");
|
throw new HttpError(HttpStatus.NOT_FOUND, "Menu cannot be found.", "menuNotFound");
|
||||||
}
|
}
|
||||||
throw new Error(e);
|
throw new Error(e);
|
||||||
});
|
});
|
||||||
|
|
@ -78,7 +78,83 @@ export class MenuController extends Controller {
|
||||||
async deleteMenu(@Path("menuId") id: string) {
|
async deleteMenu(@Path("menuId") id: string) {
|
||||||
const record = await prisma.menu.deleteMany({ where: { id } });
|
const record = await prisma.menu.deleteMany({ where: { id } });
|
||||||
if (record.count <= 0) {
|
if (record.count <= 0) {
|
||||||
throw new HttpError(HttpStatus.NOT_FOUND, "Menu cannot be found.", "data_not_found");
|
throw new HttpError(HttpStatus.NOT_FOUND, "Menu cannot be found.", "menuNotFound");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type RoleMenuPermissionCreate = {
|
||||||
|
userRole: string;
|
||||||
|
permission: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
type RoleMenuPermissionEdit = {
|
||||||
|
userRole?: string;
|
||||||
|
permission?: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
@Route("api/v1/permission/menu/{menuId}/role")
|
||||||
|
@Tags("Permission")
|
||||||
|
@Security("keycloak")
|
||||||
|
export class RoleMenuController extends Controller {
|
||||||
|
@Get()
|
||||||
|
async listRoleMenu(@Path() menuId: string) {
|
||||||
|
const record = await prisma.roleMenuPermission.findMany({
|
||||||
|
where: { menuId },
|
||||||
|
orderBy: [{ userRole: "asc" }, { createdAt: "asc" }],
|
||||||
|
});
|
||||||
|
return record;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Post()
|
||||||
|
async createRoleMenu(@Path() menuId: string, @Body() body: RoleMenuPermissionCreate) {
|
||||||
|
const menu = await prisma.menu.findFirst({ where: { id: menuId } });
|
||||||
|
|
||||||
|
if (!menu) {
|
||||||
|
throw new HttpError(
|
||||||
|
HttpStatus.BAD_REQUEST,
|
||||||
|
"Menu not found.",
|
||||||
|
"menuBadReq",
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const record = await prisma.roleMenuPermission.create({
|
||||||
|
data: Object.assign(body, { menuId }),
|
||||||
|
});
|
||||||
|
|
||||||
|
this.setStatus(HttpStatus.CREATED);
|
||||||
|
|
||||||
|
return record;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Put("{roleMenuId}")
|
||||||
|
async editRoleMenu(
|
||||||
|
@Path("roleMenuId") id: string,
|
||||||
|
@Path() menuId: string,
|
||||||
|
@Body() body: RoleMenuPermissionEdit,
|
||||||
|
) {
|
||||||
|
const record = await prisma.roleMenuPermission
|
||||||
|
.update({
|
||||||
|
where: { id, menuId },
|
||||||
|
data: body,
|
||||||
|
})
|
||||||
|
.catch((e) => {
|
||||||
|
if (e instanceof PrismaClientKnownRequestError && e.code === "P2025") {
|
||||||
|
throw new HttpError(HttpStatus.NOT_FOUND, "Role menu cannot be found.", "roleMenuNotFound");
|
||||||
|
}
|
||||||
|
throw new Error(e);
|
||||||
|
});
|
||||||
|
|
||||||
|
return record;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Delete("{roleMenuId}")
|
||||||
|
async deleteRoleMenu(@Path("roleMenuId") id: string, @Path() menuId: string) {
|
||||||
|
const record = await prisma.roleMenuPermission.deleteMany({
|
||||||
|
where: { id, menuId },
|
||||||
|
});
|
||||||
|
if (record.count <= 0) {
|
||||||
|
throw new HttpError(HttpStatus.NOT_FOUND, "Role menu cannot be found.", "roleMenuNotFound");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -95,7 +171,7 @@ type MenuComponentEdit = {
|
||||||
menuId?: string;
|
menuId?: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
@Route("v1/permission/menu-component")
|
@Route("api/v1/permission/menu-component")
|
||||||
@Tags("Permission")
|
@Tags("Permission")
|
||||||
@Security("keycloak")
|
@Security("keycloak")
|
||||||
export class MenuComponentController extends Controller {
|
export class MenuComponentController extends Controller {
|
||||||
|
|
@ -117,7 +193,7 @@ export class MenuComponentController extends Controller {
|
||||||
throw new HttpError(
|
throw new HttpError(
|
||||||
HttpStatus.BAD_REQUEST,
|
HttpStatus.BAD_REQUEST,
|
||||||
"Menu not found.",
|
"Menu not found.",
|
||||||
"missing_or_invalid_parameter",
|
"menuBadReq",
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -139,7 +215,7 @@ export class MenuComponentController extends Controller {
|
||||||
throw new HttpError(
|
throw new HttpError(
|
||||||
HttpStatus.BAD_REQUEST,
|
HttpStatus.BAD_REQUEST,
|
||||||
"Menu not found.",
|
"Menu not found.",
|
||||||
"missing_or_invalid_parameter",
|
"menuBadReq",
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -155,7 +231,7 @@ export class MenuComponentController extends Controller {
|
||||||
throw new HttpError(
|
throw new HttpError(
|
||||||
HttpStatus.NOT_FOUND,
|
HttpStatus.NOT_FOUND,
|
||||||
"Menu component cannot be found.",
|
"Menu component cannot be found.",
|
||||||
"data_not_found",
|
"menuComponentNotFound",
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
throw new Error(e);
|
throw new Error(e);
|
||||||
|
|
@ -171,7 +247,97 @@ export class MenuComponentController extends Controller {
|
||||||
throw new HttpError(
|
throw new HttpError(
|
||||||
HttpStatus.NOT_FOUND,
|
HttpStatus.NOT_FOUND,
|
||||||
"Menu component cannot be found.",
|
"Menu component cannot be found.",
|
||||||
"data_not_found",
|
"menuComponentNotFound",
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type RoleMenuComponentPermissionCreate = {
|
||||||
|
userRole: string;
|
||||||
|
permission: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
type RoleMenuComponentPermissionEdit = {
|
||||||
|
userRole?: string;
|
||||||
|
permission?: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
@Route("api/v1/permission/menu-component/{menuComponentId}/role")
|
||||||
|
@Tags("Permission")
|
||||||
|
@Security("keycloak")
|
||||||
|
export class RoleMenuComponentController extends Controller {
|
||||||
|
@Get()
|
||||||
|
async listRoleMenuComponent(@Path() menuComponentId: string) {
|
||||||
|
const record = await prisma.roleMenuComponentPermission.findMany({
|
||||||
|
where: { menuComponentId },
|
||||||
|
orderBy: [{ userRole: "asc" }, { createdAt: "asc" }],
|
||||||
|
});
|
||||||
|
return record;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Post()
|
||||||
|
async createRoleMenuComponent(
|
||||||
|
@Path() menuComponentId: string,
|
||||||
|
@Body() body: RoleMenuComponentPermissionCreate,
|
||||||
|
) {
|
||||||
|
const menu = await prisma.menuComponent.findFirst({ where: { id: menuComponentId } });
|
||||||
|
|
||||||
|
if (!menu) {
|
||||||
|
throw new HttpError(
|
||||||
|
HttpStatus.BAD_REQUEST,
|
||||||
|
"Menu not found.",
|
||||||
|
"menuBadReq",
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const record = await prisma.roleMenuComponentPermission.create({
|
||||||
|
data: Object.assign(body, { menuComponentId }),
|
||||||
|
});
|
||||||
|
|
||||||
|
this.setStatus(HttpStatus.CREATED);
|
||||||
|
|
||||||
|
return record;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Put("{roleMenuComponentId}")
|
||||||
|
async editRoleMenuComponent(
|
||||||
|
@Path("roleMenuComponentId") id: string,
|
||||||
|
@Path() menuComponentId: string,
|
||||||
|
@Body() body: RoleMenuComponentPermissionEdit,
|
||||||
|
) {
|
||||||
|
const record = await prisma.roleMenuComponentPermission
|
||||||
|
.update({
|
||||||
|
where: { id, menuComponentId },
|
||||||
|
data: body,
|
||||||
|
})
|
||||||
|
.catch((e) => {
|
||||||
|
if (e instanceof PrismaClientKnownRequestError && e.code === "P2025") {
|
||||||
|
throw new HttpError(
|
||||||
|
HttpStatus.NOT_FOUND,
|
||||||
|
"Role menu component cannot be found.",
|
||||||
|
"roleMenuNotFound",
|
||||||
|
);
|
||||||
|
}
|
||||||
|
throw new Error(e);
|
||||||
|
});
|
||||||
|
|
||||||
|
return record;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Delete("{roleMenuComponentId}")
|
||||||
|
async deleteRoleMenuComponent(
|
||||||
|
@Path("roleMenuComponentId") id: string,
|
||||||
|
@Path() menuComponentId: string,
|
||||||
|
) {
|
||||||
|
const record = await prisma.roleMenuComponentPermission.deleteMany({
|
||||||
|
where: { id, menuComponentId },
|
||||||
|
});
|
||||||
|
if (record.count <= 0) {
|
||||||
|
throw new HttpError(
|
||||||
|
HttpStatus.NOT_FOUND,
|
||||||
|
"Role menu component cannot be found.",
|
||||||
|
"roleMenuNotFound",
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
100
src/controllers/product-service-controller.ts
Normal file
100
src/controllers/product-service-controller.ts
Normal file
|
|
@ -0,0 +1,100 @@
|
||||||
|
import { Controller, Get, Query, Route, Security } from "tsoa";
|
||||||
|
import prisma from "../db";
|
||||||
|
import { Prisma, Product, Service } from "@prisma/client";
|
||||||
|
|
||||||
|
@Route("/api/v1/product-service")
|
||||||
|
export class ProductServiceController extends Controller {
|
||||||
|
@Get()
|
||||||
|
@Security("keycloak")
|
||||||
|
async getProductService(
|
||||||
|
@Query() status?: "ACTIVE" | "INACTIVE",
|
||||||
|
@Query() query = "",
|
||||||
|
@Query() productTypeId?: string,
|
||||||
|
@Query() page: number = 1,
|
||||||
|
@Query() pageSize: number = 30,
|
||||||
|
) {
|
||||||
|
const union = Prisma.sql`
|
||||||
|
SELECT
|
||||||
|
"id",
|
||||||
|
"code",
|
||||||
|
"name",
|
||||||
|
"detail",
|
||||||
|
"price",
|
||||||
|
"agentPrice",
|
||||||
|
"serviceCharge",
|
||||||
|
"process",
|
||||||
|
"remark",
|
||||||
|
"status",
|
||||||
|
"statusOrder",
|
||||||
|
"productTypeId",
|
||||||
|
"createdBy",
|
||||||
|
"createdAt",
|
||||||
|
"updatedBy",
|
||||||
|
"updatedAt",
|
||||||
|
'product' as "type"
|
||||||
|
FROM "Product"
|
||||||
|
UNION ALL
|
||||||
|
SELECT
|
||||||
|
"id",
|
||||||
|
"code",
|
||||||
|
"name",
|
||||||
|
"detail",
|
||||||
|
null as "price",
|
||||||
|
null as "agentPrice",
|
||||||
|
null as "serviceCharge",
|
||||||
|
null as "process",
|
||||||
|
null as "remark",
|
||||||
|
"status",
|
||||||
|
"statusOrder",
|
||||||
|
null as "productTypeId",
|
||||||
|
"createdBy",
|
||||||
|
"createdAt",
|
||||||
|
"updatedBy",
|
||||||
|
"updatedAt",
|
||||||
|
'service' as "type"
|
||||||
|
FROM "Service"
|
||||||
|
`;
|
||||||
|
|
||||||
|
const or: Prisma.Sql[] = [];
|
||||||
|
const and: Prisma.Sql[] = [];
|
||||||
|
|
||||||
|
if (query) or.push(Prisma.sql`"name" LIKE ${`%${query}%`}`);
|
||||||
|
if (status) and.push(Prisma.sql`"status" = ${status}::"Status"`);
|
||||||
|
if (productTypeId) {
|
||||||
|
and.push(Prisma.sql`("productTypeId" = ${productTypeId} OR ("type" = 'service'))`);
|
||||||
|
}
|
||||||
|
|
||||||
|
const where = Prisma.sql`
|
||||||
|
${or.length > 0 || and.length > 0 ? Prisma.sql`WHERE ` : Prisma.empty}
|
||||||
|
${or.length > 0 ? Prisma.join(or, " OR ", "(", ")") : Prisma.empty}
|
||||||
|
${or.length > 0 && and.length > 0 ? Prisma.sql` AND ` : Prisma.empty}
|
||||||
|
${and.length > 0 ? Prisma.join(and, " AND ", "(", ")") : Prisma.empty}
|
||||||
|
`;
|
||||||
|
|
||||||
|
const [result, [{ total }]] = await prisma.$transaction([
|
||||||
|
prisma.$queryRaw<((Product & { type: "product" }) | (Service & { type: "service" }))[]>`
|
||||||
|
SELECT * FROM (${union}) AS "ProductService"
|
||||||
|
${where}
|
||||||
|
ORDER BY "ProductService"."statusOrder" ASC, "ProductService"."createdAt" ASC
|
||||||
|
LIMIT ${pageSize} OFFSET ${(page - 1) * pageSize}
|
||||||
|
`,
|
||||||
|
prisma.$queryRaw<[{ total: number }]>`
|
||||||
|
SELECT COUNT(*) AS "total" FROM (${union}) as "ProductService"
|
||||||
|
${where}
|
||||||
|
`,
|
||||||
|
]);
|
||||||
|
|
||||||
|
const work = await prisma.work.findMany({
|
||||||
|
where: { serviceId: { in: result.flatMap((v) => (v.type === "service" ? v.id : [])) } },
|
||||||
|
});
|
||||||
|
|
||||||
|
return {
|
||||||
|
result: result.map((v) =>
|
||||||
|
v.type === "service" ? { ...v, work: work.filter((w) => w.serviceId === v.id) || [] } : v,
|
||||||
|
),
|
||||||
|
page,
|
||||||
|
pageSize,
|
||||||
|
total: +String(total),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
199
src/controllers/product/group-controller.ts
Normal file
199
src/controllers/product/group-controller.ts
Normal file
|
|
@ -0,0 +1,199 @@
|
||||||
|
import {
|
||||||
|
Body,
|
||||||
|
Controller,
|
||||||
|
Delete,
|
||||||
|
Get,
|
||||||
|
Put,
|
||||||
|
Path,
|
||||||
|
Post,
|
||||||
|
Query,
|
||||||
|
Request,
|
||||||
|
Route,
|
||||||
|
Security,
|
||||||
|
Tags,
|
||||||
|
} from "tsoa";
|
||||||
|
import { Prisma, Status } from "@prisma/client";
|
||||||
|
|
||||||
|
import prisma from "../../db";
|
||||||
|
import { RequestWithUser } from "../../interfaces/user";
|
||||||
|
import HttpError from "../../interfaces/http-error";
|
||||||
|
import HttpStatus from "../../interfaces/http-status";
|
||||||
|
|
||||||
|
type ProductGroupCreate = {
|
||||||
|
name: string;
|
||||||
|
detail: string;
|
||||||
|
remark: string;
|
||||||
|
status?: Status;
|
||||||
|
};
|
||||||
|
|
||||||
|
type ProductGroupUpdate = {
|
||||||
|
name?: string;
|
||||||
|
detail?: string;
|
||||||
|
remark?: string;
|
||||||
|
status?: "ACTIVE" | "INACTIVE";
|
||||||
|
};
|
||||||
|
|
||||||
|
@Route("api/v1/product-group")
|
||||||
|
@Tags("Product Group")
|
||||||
|
@Security("keycloak")
|
||||||
|
export class ProductGroup extends Controller {
|
||||||
|
@Get("stats")
|
||||||
|
async getProductGroupStats() {
|
||||||
|
return await prisma.productGroup.count();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Get()
|
||||||
|
async getProductGroup(
|
||||||
|
@Query() query: string = "",
|
||||||
|
@Query() status?: Status,
|
||||||
|
@Query() page: number = 1,
|
||||||
|
@Query() pageSize: number = 30,
|
||||||
|
) {
|
||||||
|
const filterStatus = (val?: Status) => {
|
||||||
|
if (!val) return {};
|
||||||
|
|
||||||
|
return val !== Status.CREATED && val !== Status.ACTIVE
|
||||||
|
? { status: val }
|
||||||
|
: { OR: [{ status: Status.CREATED }, { status: Status.ACTIVE }] };
|
||||||
|
};
|
||||||
|
|
||||||
|
const where = {
|
||||||
|
OR: [
|
||||||
|
{ name: { contains: query }, ...filterStatus(status) },
|
||||||
|
{ detail: { contains: query }, ...filterStatus(status) },
|
||||||
|
],
|
||||||
|
} satisfies Prisma.ProductGroupWhereInput;
|
||||||
|
|
||||||
|
const [result, total] = await prisma.$transaction([
|
||||||
|
prisma.productGroup.findMany({
|
||||||
|
include: {
|
||||||
|
_count: {
|
||||||
|
select: {
|
||||||
|
type: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
orderBy: [{ statusOrder: "asc" }, { createdAt: "asc" }],
|
||||||
|
where,
|
||||||
|
take: pageSize,
|
||||||
|
skip: (page - 1) * pageSize,
|
||||||
|
}),
|
||||||
|
prisma.productGroup.count({ where }),
|
||||||
|
]);
|
||||||
|
|
||||||
|
const statsProduct = await prisma.productType.findMany({
|
||||||
|
include: {
|
||||||
|
_count: { select: { product: true } },
|
||||||
|
},
|
||||||
|
where: {
|
||||||
|
productGroupId: { in: result.map((v) => v.id) },
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
return {
|
||||||
|
result: result.map((v) => ({
|
||||||
|
...v,
|
||||||
|
_count: {
|
||||||
|
...v._count,
|
||||||
|
product: statsProduct.reduce(
|
||||||
|
(a, c) => (c.productGroupId === v.id ? a + c._count.product : a),
|
||||||
|
0,
|
||||||
|
),
|
||||||
|
},
|
||||||
|
})),
|
||||||
|
page,
|
||||||
|
pageSize,
|
||||||
|
total,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
@Get("{groupId}")
|
||||||
|
async getProductGroupById(@Path() groupId: string) {
|
||||||
|
const record = await prisma.productGroup.findFirst({
|
||||||
|
where: { id: groupId },
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!record)
|
||||||
|
throw new HttpError(
|
||||||
|
HttpStatus.NOT_FOUND,
|
||||||
|
"Product group cannot be found.",
|
||||||
|
"productGroupNotFound",
|
||||||
|
);
|
||||||
|
|
||||||
|
return record;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Post()
|
||||||
|
async createProductGroup(@Request() req: RequestWithUser, @Body() body: ProductGroupCreate) {
|
||||||
|
const record = await prisma.$transaction(
|
||||||
|
async (tx) => {
|
||||||
|
const last = await tx.runningNo.upsert({
|
||||||
|
where: {
|
||||||
|
key: `PRODGRP`,
|
||||||
|
},
|
||||||
|
create: {
|
||||||
|
key: `PRODGRP`,
|
||||||
|
value: 1,
|
||||||
|
},
|
||||||
|
update: { value: { increment: 1 } },
|
||||||
|
});
|
||||||
|
|
||||||
|
return await tx.productGroup.create({
|
||||||
|
data: {
|
||||||
|
...body,
|
||||||
|
statusOrder: +(body.status === "INACTIVE"),
|
||||||
|
code: `G${last.value.toString().padStart(2, "0")}`,
|
||||||
|
createdBy: req.user.name,
|
||||||
|
updatedBy: req.user.name,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
},
|
||||||
|
{ isolationLevel: Prisma.TransactionIsolationLevel.Serializable },
|
||||||
|
);
|
||||||
|
|
||||||
|
this.setStatus(HttpStatus.CREATED);
|
||||||
|
|
||||||
|
return record;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Put("{groupId}")
|
||||||
|
async editProductGroup(
|
||||||
|
@Request() req: RequestWithUser,
|
||||||
|
@Body() body: ProductGroupUpdate,
|
||||||
|
@Path() groupId: string,
|
||||||
|
) {
|
||||||
|
if (!(await prisma.productGroup.findUnique({ where: { id: groupId } }))) {
|
||||||
|
throw new HttpError(
|
||||||
|
HttpStatus.NOT_FOUND,
|
||||||
|
"Product group cannot be found.",
|
||||||
|
"productGroupNotFound",
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const record = await prisma.productGroup.update({
|
||||||
|
data: { ...body, statusOrder: +(body.status === "INACTIVE"), updatedBy: req.user.name },
|
||||||
|
where: { id: groupId },
|
||||||
|
});
|
||||||
|
|
||||||
|
return record;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Delete("{groupId}")
|
||||||
|
async deleteProductGroup(@Path() groupId: string) {
|
||||||
|
const record = await prisma.productGroup.findFirst({ where: { id: groupId } });
|
||||||
|
|
||||||
|
if (!record) {
|
||||||
|
throw new HttpError(
|
||||||
|
HttpStatus.NOT_FOUND,
|
||||||
|
"Product group cannot be found.",
|
||||||
|
"productGroupNotFound",
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (record.status !== Status.CREATED) {
|
||||||
|
throw new HttpError(HttpStatus.FORBIDDEN, "Product group is in used.", "productGroupInUsed");
|
||||||
|
}
|
||||||
|
|
||||||
|
return await prisma.productGroup.delete({ where: { id: groupId } });
|
||||||
|
}
|
||||||
|
}
|
||||||
273
src/controllers/product/product-controller.ts
Normal file
273
src/controllers/product/product-controller.ts
Normal file
|
|
@ -0,0 +1,273 @@
|
||||||
|
import {
|
||||||
|
Body,
|
||||||
|
Controller,
|
||||||
|
Delete,
|
||||||
|
Get,
|
||||||
|
Put,
|
||||||
|
Path,
|
||||||
|
Post,
|
||||||
|
Request,
|
||||||
|
Route,
|
||||||
|
Security,
|
||||||
|
Tags,
|
||||||
|
Query,
|
||||||
|
} from "tsoa";
|
||||||
|
import { Prisma, Status } from "@prisma/client";
|
||||||
|
|
||||||
|
import prisma from "../../db";
|
||||||
|
import minio, { presignedGetObjectIfExist } from "../../services/minio";
|
||||||
|
import { RequestWithUser } from "../../interfaces/user";
|
||||||
|
import HttpError from "../../interfaces/http-error";
|
||||||
|
import HttpStatus from "../../interfaces/http-status";
|
||||||
|
|
||||||
|
if (!process.env.MINIO_BUCKET) {
|
||||||
|
throw Error("Require MinIO bucket.");
|
||||||
|
}
|
||||||
|
|
||||||
|
const MINIO_BUCKET = process.env.MINIO_BUCKET;
|
||||||
|
|
||||||
|
type ProductCreate = {
|
||||||
|
status?: Status;
|
||||||
|
code: "AC" | "DO" | "ac" | "do";
|
||||||
|
name: string;
|
||||||
|
detail: string;
|
||||||
|
process: number;
|
||||||
|
price: number;
|
||||||
|
agentPrice: number;
|
||||||
|
serviceCharge: number;
|
||||||
|
productTypeId: string;
|
||||||
|
remark?: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
type ProductUpdate = {
|
||||||
|
status?: "ACTIVE" | "INACTIVE";
|
||||||
|
name?: string;
|
||||||
|
detail?: string;
|
||||||
|
process?: number;
|
||||||
|
price?: number;
|
||||||
|
agentPrice?: number;
|
||||||
|
serviceCharge?: number;
|
||||||
|
remark?: string;
|
||||||
|
productTypeId?: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
function imageLocation(id: string) {
|
||||||
|
return `product/${id}/image`;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Route("api/v1/product")
|
||||||
|
@Tags("Product")
|
||||||
|
export class ProductController extends Controller {
|
||||||
|
@Get("stats")
|
||||||
|
async getProductStats(@Query() productTypeId?: string) {
|
||||||
|
return await prisma.product.count({ where: { productTypeId } });
|
||||||
|
}
|
||||||
|
|
||||||
|
@Get()
|
||||||
|
@Security("keycloak")
|
||||||
|
async getProduct(
|
||||||
|
@Query() status?: Status,
|
||||||
|
@Query() productTypeId?: string,
|
||||||
|
@Query() query: string = "",
|
||||||
|
@Query() page: number = 1,
|
||||||
|
@Query() pageSize: number = 30,
|
||||||
|
) {
|
||||||
|
const filterStatus = (val?: Status) => {
|
||||||
|
if (!val) return {};
|
||||||
|
|
||||||
|
return val !== Status.CREATED && val !== Status.ACTIVE
|
||||||
|
? { status: val }
|
||||||
|
: { OR: [{ status: Status.CREATED }, { status: Status.ACTIVE }] };
|
||||||
|
};
|
||||||
|
|
||||||
|
const where = {
|
||||||
|
OR: [
|
||||||
|
{ name: { contains: query }, productTypeId, ...filterStatus(status) },
|
||||||
|
{ detail: { contains: query }, productTypeId, ...filterStatus(status) },
|
||||||
|
],
|
||||||
|
} satisfies Prisma.ProductWhereInput;
|
||||||
|
|
||||||
|
const [result, total] = await prisma.$transaction([
|
||||||
|
prisma.product.findMany({
|
||||||
|
orderBy: [{ statusOrder: "asc" }, { createdAt: "asc" }],
|
||||||
|
where,
|
||||||
|
take: pageSize,
|
||||||
|
skip: (page - 1) * pageSize,
|
||||||
|
}),
|
||||||
|
prisma.product.count({ where }),
|
||||||
|
]);
|
||||||
|
|
||||||
|
return {
|
||||||
|
result: await Promise.all(
|
||||||
|
result.map(async (v) => ({
|
||||||
|
...v,
|
||||||
|
imageUrl: await presignedGetObjectIfExist(
|
||||||
|
MINIO_BUCKET,
|
||||||
|
imageLocation(v.id),
|
||||||
|
12 * 60 * 60,
|
||||||
|
),
|
||||||
|
})),
|
||||||
|
),
|
||||||
|
page,
|
||||||
|
pageSize,
|
||||||
|
total,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
@Get("{productId}")
|
||||||
|
@Security("keycloak")
|
||||||
|
async getProductById(@Path() productId: string) {
|
||||||
|
const record = await prisma.product.findFirst({
|
||||||
|
where: { id: productId },
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!record) {
|
||||||
|
throw new HttpError(HttpStatus.NOT_FOUND, "Product cannot be found.", "productNotFound");
|
||||||
|
}
|
||||||
|
|
||||||
|
return Object.assign(record, {
|
||||||
|
imageUrl: await presignedGetObjectIfExist(MINIO_BUCKET, imageLocation(record.id), 60 * 60),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@Get("{productId}/image")
|
||||||
|
async getProductImageById(@Request() req: RequestWithUser, @Path() productId: string) {
|
||||||
|
const url = await presignedGetObjectIfExist(MINIO_BUCKET, imageLocation(productId), 60 * 60);
|
||||||
|
|
||||||
|
if (!url) {
|
||||||
|
throw new HttpError(HttpStatus.NOT_FOUND, "Image cannot be found", "imageNotFound");
|
||||||
|
}
|
||||||
|
|
||||||
|
return req.res?.redirect(url);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Post()
|
||||||
|
@Security("keycloak")
|
||||||
|
async createProduct(@Request() req: RequestWithUser, @Body() body: ProductCreate) {
|
||||||
|
const productType = await prisma.productType.findFirst({
|
||||||
|
where: { id: body.productTypeId },
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!productType) {
|
||||||
|
throw new HttpError(
|
||||||
|
HttpStatus.BAD_REQUEST,
|
||||||
|
"Product Type cannot be found.",
|
||||||
|
"relationProductTypeNotFound",
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const record = await prisma.$transaction(
|
||||||
|
async (tx) => {
|
||||||
|
const last = await tx.runningNo.upsert({
|
||||||
|
where: {
|
||||||
|
key: `PRODUCT_${body.code.toLocaleUpperCase()}`,
|
||||||
|
},
|
||||||
|
create: {
|
||||||
|
key: `PRODUCT_${body.code.toLocaleUpperCase()}`,
|
||||||
|
value: 1,
|
||||||
|
},
|
||||||
|
update: { value: { increment: 1 } },
|
||||||
|
});
|
||||||
|
return await prisma.product.create({
|
||||||
|
data: {
|
||||||
|
...body,
|
||||||
|
statusOrder: +(body.status === "INACTIVE"),
|
||||||
|
code: `${body.code.toLocaleUpperCase()}${last.value.toString().padStart(3, "0")}`,
|
||||||
|
createdBy: req.user.name,
|
||||||
|
updatedBy: req.user.name,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
},
|
||||||
|
{
|
||||||
|
isolationLevel: Prisma.TransactionIsolationLevel.Serializable,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
if (productType.status === "CREATED") {
|
||||||
|
await prisma.productType.update({
|
||||||
|
where: { id: body.productTypeId },
|
||||||
|
data: { status: Status.ACTIVE },
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
this.setStatus(HttpStatus.CREATED);
|
||||||
|
|
||||||
|
return Object.assign(record, {
|
||||||
|
imageUrl: await presignedGetObjectIfExist(
|
||||||
|
MINIO_BUCKET,
|
||||||
|
imageLocation(record.id),
|
||||||
|
12 * 60 * 60,
|
||||||
|
),
|
||||||
|
imageUploadUrl: await minio.presignedPutObject(
|
||||||
|
MINIO_BUCKET,
|
||||||
|
imageLocation(record.id),
|
||||||
|
12 * 60 * 60,
|
||||||
|
),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@Put("{productId}")
|
||||||
|
@Security("keycloak")
|
||||||
|
async editProduct(
|
||||||
|
@Request() req: RequestWithUser,
|
||||||
|
@Body() body: ProductUpdate,
|
||||||
|
@Path() productId: string,
|
||||||
|
) {
|
||||||
|
if (!(await prisma.product.findUnique({ where: { id: productId } }))) {
|
||||||
|
throw new HttpError(HttpStatus.NOT_FOUND, "Product cannot be found.", "productNotFound");
|
||||||
|
}
|
||||||
|
|
||||||
|
const productType = await prisma.productType.findFirst({
|
||||||
|
where: { id: body.productTypeId },
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!productType) {
|
||||||
|
throw new HttpError(
|
||||||
|
HttpStatus.BAD_REQUEST,
|
||||||
|
"Product Type cannot be found.",
|
||||||
|
"relationProductTypeNotFound",
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const record = await prisma.product.update({
|
||||||
|
data: { ...body, statusOrder: +(body.status === "INACTIVE"), updatedBy: req.user.name },
|
||||||
|
where: { id: productId },
|
||||||
|
});
|
||||||
|
|
||||||
|
if (productType.status === "CREATED") {
|
||||||
|
await prisma.productType.updateMany({
|
||||||
|
where: { id: body.productTypeId, status: Status.CREATED },
|
||||||
|
data: { status: Status.ACTIVE },
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return Object.assign(record, {
|
||||||
|
imageUrl: await presignedGetObjectIfExist(
|
||||||
|
MINIO_BUCKET,
|
||||||
|
imageLocation(record.id),
|
||||||
|
12 * 60 * 60,
|
||||||
|
),
|
||||||
|
imageUploadUrl: await minio.presignedPutObject(
|
||||||
|
MINIO_BUCKET,
|
||||||
|
imageLocation(record.id),
|
||||||
|
12 * 60 * 60,
|
||||||
|
),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@Delete("{productId}")
|
||||||
|
@Security("keycloak")
|
||||||
|
async deleteProduct(@Path() productId: string) {
|
||||||
|
const record = await prisma.product.findFirst({ where: { id: productId } });
|
||||||
|
|
||||||
|
if (!record) {
|
||||||
|
throw new HttpError(HttpStatus.NOT_FOUND, "Product cannot be found.", "productNotFound");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (record.status !== Status.CREATED) {
|
||||||
|
throw new HttpError(HttpStatus.FORBIDDEN, "Product is in used.", "productInUsed");
|
||||||
|
}
|
||||||
|
|
||||||
|
return await prisma.product.delete({ where: { id: productId } });
|
||||||
|
}
|
||||||
|
}
|
||||||
213
src/controllers/product/type-controller.ts
Normal file
213
src/controllers/product/type-controller.ts
Normal file
|
|
@ -0,0 +1,213 @@
|
||||||
|
import {
|
||||||
|
Body,
|
||||||
|
Controller,
|
||||||
|
Delete,
|
||||||
|
Get,
|
||||||
|
Put,
|
||||||
|
Path,
|
||||||
|
Post,
|
||||||
|
Query,
|
||||||
|
Request,
|
||||||
|
Route,
|
||||||
|
Security,
|
||||||
|
Tags,
|
||||||
|
} from "tsoa";
|
||||||
|
import { Prisma, Status } from "@prisma/client";
|
||||||
|
|
||||||
|
import prisma from "../../db";
|
||||||
|
import { RequestWithUser } from "../../interfaces/user";
|
||||||
|
import HttpError from "../../interfaces/http-error";
|
||||||
|
import HttpStatus from "../../interfaces/http-status";
|
||||||
|
|
||||||
|
type ProductTypeCreate = {
|
||||||
|
productGroupId: string;
|
||||||
|
name: string;
|
||||||
|
detail: string;
|
||||||
|
remark: string;
|
||||||
|
status?: Status;
|
||||||
|
};
|
||||||
|
|
||||||
|
type ProductTypeUpdate = {
|
||||||
|
productGroupId?: string;
|
||||||
|
name?: string;
|
||||||
|
detail?: string;
|
||||||
|
remark?: string;
|
||||||
|
status?: "ACTIVE" | "INACTIVE";
|
||||||
|
};
|
||||||
|
|
||||||
|
@Route("api/v1/product-type")
|
||||||
|
@Tags("Product Type")
|
||||||
|
@Security("keycloak")
|
||||||
|
export class ProductType extends Controller {
|
||||||
|
@Get("stats")
|
||||||
|
async getProductTypeStats() {
|
||||||
|
return await prisma.productType.count();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Get()
|
||||||
|
async getProductType(
|
||||||
|
@Query() query: string = "",
|
||||||
|
@Query() productGroupId?: string,
|
||||||
|
@Query() status?: Status,
|
||||||
|
@Query() page: number = 1,
|
||||||
|
@Query() pageSize: number = 30,
|
||||||
|
) {
|
||||||
|
const filterStatus = (val?: Status) => {
|
||||||
|
if (!val) return {};
|
||||||
|
|
||||||
|
return val !== Status.CREATED && val !== Status.ACTIVE
|
||||||
|
? { status: val }
|
||||||
|
: { OR: [{ status: Status.CREATED }, { status: Status.ACTIVE }] };
|
||||||
|
};
|
||||||
|
const where = {
|
||||||
|
AND: { productGroupId },
|
||||||
|
OR: [
|
||||||
|
{ name: { contains: query }, ...filterStatus(status) },
|
||||||
|
{ detail: { contains: query }, ...filterStatus(status) },
|
||||||
|
],
|
||||||
|
} satisfies Prisma.ProductTypeWhereInput;
|
||||||
|
const [result, total] = await prisma.$transaction([
|
||||||
|
prisma.productType.findMany({
|
||||||
|
include: {
|
||||||
|
_count: {
|
||||||
|
select: { product: true },
|
||||||
|
},
|
||||||
|
},
|
||||||
|
orderBy: [{ statusOrder: "asc" }, { createdAt: "asc" }],
|
||||||
|
where,
|
||||||
|
take: pageSize,
|
||||||
|
skip: (page - 1) * pageSize,
|
||||||
|
}),
|
||||||
|
prisma.productType.count({ where }),
|
||||||
|
]);
|
||||||
|
|
||||||
|
return { result, page, pageSize, total };
|
||||||
|
}
|
||||||
|
|
||||||
|
@Get("{typeId}")
|
||||||
|
async getProductTypeById(@Path() typeId: string) {
|
||||||
|
const record = await prisma.productType.findFirst({
|
||||||
|
where: { id: typeId },
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!record)
|
||||||
|
throw new HttpError(
|
||||||
|
HttpStatus.NOT_FOUND,
|
||||||
|
"Product type cannot be found.",
|
||||||
|
"productTypeNotFound",
|
||||||
|
);
|
||||||
|
|
||||||
|
return record;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Post()
|
||||||
|
async createProductType(@Request() req: RequestWithUser, @Body() body: ProductTypeCreate) {
|
||||||
|
const productGroup = await prisma.productGroup.findFirst({
|
||||||
|
where: { id: body.productGroupId },
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!productGroup) {
|
||||||
|
throw new HttpError(
|
||||||
|
HttpStatus.BAD_REQUEST,
|
||||||
|
"Product group associated cannot be found.",
|
||||||
|
"productGroupAssociatedBadReq",
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const record = await prisma.$transaction(
|
||||||
|
async (tx) => {
|
||||||
|
const last = await tx.runningNo.upsert({
|
||||||
|
where: {
|
||||||
|
key: `PRODTYP_T${productGroup.code}`,
|
||||||
|
},
|
||||||
|
create: {
|
||||||
|
key: `PRODTYP_T${productGroup.code}`,
|
||||||
|
value: 1,
|
||||||
|
},
|
||||||
|
update: { value: { increment: 1 } },
|
||||||
|
});
|
||||||
|
|
||||||
|
return await tx.productType.create({
|
||||||
|
data: {
|
||||||
|
...body,
|
||||||
|
statusOrder: +(body.status === "INACTIVE"),
|
||||||
|
code: `T${productGroup.code}${last.value.toString().padStart(2, "0")}`,
|
||||||
|
createdBy: req.user.name,
|
||||||
|
updatedBy: req.user.name,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
},
|
||||||
|
{ isolationLevel: Prisma.TransactionIsolationLevel.Serializable },
|
||||||
|
);
|
||||||
|
|
||||||
|
if (productGroup.status === "CREATED") {
|
||||||
|
await prisma.productGroup.update({
|
||||||
|
where: { id: body.productGroupId },
|
||||||
|
data: { status: Status.ACTIVE },
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
this.setStatus(HttpStatus.CREATED);
|
||||||
|
|
||||||
|
return record;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Put("{typeId}")
|
||||||
|
async editProductType(
|
||||||
|
@Request() req: RequestWithUser,
|
||||||
|
@Body() body: ProductTypeUpdate,
|
||||||
|
@Path() typeId: string,
|
||||||
|
) {
|
||||||
|
const productGroup = await prisma.productGroup.findFirst({
|
||||||
|
where: { id: body.productGroupId },
|
||||||
|
});
|
||||||
|
if (body.productGroupId && !productGroup) {
|
||||||
|
throw new HttpError(
|
||||||
|
HttpStatus.BAD_REQUEST,
|
||||||
|
"Product group cannot be found.",
|
||||||
|
"productGroupBadReq",
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!(await prisma.productType.findUnique({ where: { id: typeId } }))) {
|
||||||
|
throw new HttpError(
|
||||||
|
HttpStatus.NOT_FOUND,
|
||||||
|
"Product type cannot be found.",
|
||||||
|
"productTypeNotFound",
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const record = await prisma.productType.update({
|
||||||
|
data: { ...body, statusOrder: +(body.status === "INACTIVE"), updatedBy: req.user.name },
|
||||||
|
where: { id: typeId },
|
||||||
|
});
|
||||||
|
|
||||||
|
if (productGroup?.status === "CREATED") {
|
||||||
|
await prisma.productGroup.update({
|
||||||
|
where: { id: body.productGroupId, status: Status.CREATED },
|
||||||
|
data: { status: Status.ACTIVE },
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return record;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Delete("{typeId}")
|
||||||
|
async deleteProductType(@Path() typeId: string) {
|
||||||
|
const record = await prisma.productType.findFirst({ where: { id: typeId } });
|
||||||
|
|
||||||
|
if (!record) {
|
||||||
|
throw new HttpError(
|
||||||
|
HttpStatus.NOT_FOUND,
|
||||||
|
"Product type cannot be found.",
|
||||||
|
"productTypeNotFound",
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (record.status !== Status.CREATED) {
|
||||||
|
throw new HttpError(HttpStatus.FORBIDDEN, "Product type is in used.", "productTypeInUsed");
|
||||||
|
}
|
||||||
|
|
||||||
|
return await prisma.productType.delete({ where: { id: typeId } });
|
||||||
|
}
|
||||||
|
}
|
||||||
346
src/controllers/service/service-controller.ts
Normal file
346
src/controllers/service/service-controller.ts
Normal file
|
|
@ -0,0 +1,346 @@
|
||||||
|
import {
|
||||||
|
Body,
|
||||||
|
Controller,
|
||||||
|
Delete,
|
||||||
|
Get,
|
||||||
|
Put,
|
||||||
|
Path,
|
||||||
|
Post,
|
||||||
|
Query,
|
||||||
|
Request,
|
||||||
|
Route,
|
||||||
|
Security,
|
||||||
|
Tags,
|
||||||
|
} from "tsoa";
|
||||||
|
import { Prisma, Status } from "@prisma/client";
|
||||||
|
|
||||||
|
import prisma from "../../db";
|
||||||
|
import minio, { presignedGetObjectIfExist } from "../../services/minio";
|
||||||
|
import { RequestWithUser } from "../../interfaces/user";
|
||||||
|
import HttpError from "../../interfaces/http-error";
|
||||||
|
import HttpStatus from "../../interfaces/http-status";
|
||||||
|
|
||||||
|
if (!process.env.MINIO_BUCKET) {
|
||||||
|
throw Error("Require MinIO bucket.");
|
||||||
|
}
|
||||||
|
|
||||||
|
const MINIO_BUCKET = process.env.MINIO_BUCKET;
|
||||||
|
|
||||||
|
type ServiceCreate = {
|
||||||
|
code: "MOU" | "mou";
|
||||||
|
name: string;
|
||||||
|
detail: string;
|
||||||
|
attributes?: {
|
||||||
|
[key: string]: any;
|
||||||
|
};
|
||||||
|
status?: Status;
|
||||||
|
work?: {
|
||||||
|
name: string;
|
||||||
|
productId: string[];
|
||||||
|
attributes?: { [key: string]: any };
|
||||||
|
}[];
|
||||||
|
};
|
||||||
|
|
||||||
|
type ServiceUpdate = {
|
||||||
|
name?: string;
|
||||||
|
detail?: string;
|
||||||
|
attributes?: {
|
||||||
|
[key: string]: any;
|
||||||
|
};
|
||||||
|
status?: "ACTIVE" | "INACTIVE";
|
||||||
|
work?: {
|
||||||
|
name: string;
|
||||||
|
productId: string[];
|
||||||
|
attributes?: { [key: string]: any };
|
||||||
|
}[];
|
||||||
|
};
|
||||||
|
|
||||||
|
function imageLocation(id: string) {
|
||||||
|
return `service/${id}/service-image`;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Route("api/v1/service")
|
||||||
|
@Tags("Service")
|
||||||
|
export class ServiceController extends Controller {
|
||||||
|
@Get("stats")
|
||||||
|
@Security("keycloak")
|
||||||
|
async getServiceStats() {
|
||||||
|
return await prisma.service.count();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Get()
|
||||||
|
@Security("keycloak")
|
||||||
|
async getService(
|
||||||
|
@Query() query: string = "",
|
||||||
|
@Query() page: number = 1,
|
||||||
|
@Query() pageSize: number = 30,
|
||||||
|
@Query() status?: Status,
|
||||||
|
) {
|
||||||
|
const filterStatus = (val?: Status) => {
|
||||||
|
if (!val) return {};
|
||||||
|
|
||||||
|
return val !== Status.CREATED && val !== Status.ACTIVE
|
||||||
|
? { status: val }
|
||||||
|
: { OR: [{ status: Status.CREATED }, { status: Status.ACTIVE }] };
|
||||||
|
};
|
||||||
|
|
||||||
|
const where = {
|
||||||
|
OR: [
|
||||||
|
{ name: { contains: query }, ...filterStatus(status) },
|
||||||
|
{ detail: { contains: query }, ...filterStatus(status) },
|
||||||
|
],
|
||||||
|
} satisfies Prisma.ServiceWhereInput;
|
||||||
|
|
||||||
|
const [result, total] = await prisma.$transaction([
|
||||||
|
prisma.service.findMany({
|
||||||
|
include: {
|
||||||
|
work: true,
|
||||||
|
},
|
||||||
|
orderBy: [{ statusOrder: "asc" }, { createdAt: "asc" }],
|
||||||
|
where,
|
||||||
|
take: pageSize,
|
||||||
|
skip: (page - 1) * pageSize,
|
||||||
|
}),
|
||||||
|
prisma.service.count({ where }),
|
||||||
|
]);
|
||||||
|
|
||||||
|
return {
|
||||||
|
result: await Promise.all(
|
||||||
|
result.map(async (v) => ({
|
||||||
|
...v,
|
||||||
|
imageUrl: await presignedGetObjectIfExist(
|
||||||
|
MINIO_BUCKET,
|
||||||
|
imageLocation(v.id),
|
||||||
|
12 * 60 * 60,
|
||||||
|
),
|
||||||
|
})),
|
||||||
|
),
|
||||||
|
page,
|
||||||
|
pageSize,
|
||||||
|
total,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
@Get("{serviceId}")
|
||||||
|
@Security("keycloak")
|
||||||
|
async getServiceById(@Path() serviceId: string) {
|
||||||
|
const record = await prisma.service.findFirst({
|
||||||
|
include: {
|
||||||
|
work: {
|
||||||
|
orderBy: { order: "asc" },
|
||||||
|
include: {
|
||||||
|
productOnWork: {
|
||||||
|
include: { product: true },
|
||||||
|
orderBy: { order: "asc" },
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
where: { id: serviceId },
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!record) {
|
||||||
|
throw new HttpError(HttpStatus.NOT_FOUND, "Service cannot be found.", "serviceNotFound");
|
||||||
|
}
|
||||||
|
|
||||||
|
return Object.assign(record, {
|
||||||
|
imageUrl: await presignedGetObjectIfExist(MINIO_BUCKET, imageLocation(record.id), 60 * 60),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@Get("{serviceId}/work")
|
||||||
|
@Security("keycloak")
|
||||||
|
async getWorkOfService(
|
||||||
|
@Path() serviceId: string,
|
||||||
|
@Query() page: number = 1,
|
||||||
|
@Query() pageSize: number = 30,
|
||||||
|
) {
|
||||||
|
const where = {
|
||||||
|
serviceId,
|
||||||
|
} satisfies Prisma.WorkWhereInput;
|
||||||
|
|
||||||
|
const [result, total] = await prisma.$transaction([
|
||||||
|
prisma.work.findMany({
|
||||||
|
include: {
|
||||||
|
productOnWork: {
|
||||||
|
include: {
|
||||||
|
product: true,
|
||||||
|
},
|
||||||
|
orderBy: { order: "asc" },
|
||||||
|
},
|
||||||
|
},
|
||||||
|
where,
|
||||||
|
take: pageSize,
|
||||||
|
skip: (page - 1) * pageSize,
|
||||||
|
}),
|
||||||
|
prisma.work.count({ where }),
|
||||||
|
]);
|
||||||
|
return { result, page, pageSize, total };
|
||||||
|
}
|
||||||
|
|
||||||
|
@Get("{serviceId}/image")
|
||||||
|
async getServiceImageById(@Request() req: RequestWithUser, @Path() serviceId: string) {
|
||||||
|
const url = await presignedGetObjectIfExist(MINIO_BUCKET, imageLocation(serviceId), 60 * 60);
|
||||||
|
|
||||||
|
if (!url) {
|
||||||
|
throw new HttpError(HttpStatus.NOT_FOUND, "Image cannot be found", "imageNotFound");
|
||||||
|
}
|
||||||
|
|
||||||
|
return req.res?.redirect(url);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Post()
|
||||||
|
@Security("keycloak")
|
||||||
|
async createService(@Request() req: RequestWithUser, @Body() body: ServiceCreate) {
|
||||||
|
const { work, ...payload } = body;
|
||||||
|
|
||||||
|
const record = await prisma.$transaction(
|
||||||
|
async (tx) => {
|
||||||
|
const last = await tx.runningNo.upsert({
|
||||||
|
where: {
|
||||||
|
key: `SERVICE_${body.code.toLocaleUpperCase()}`,
|
||||||
|
},
|
||||||
|
create: {
|
||||||
|
key: `SERVICE_${body.code.toLocaleUpperCase()}`,
|
||||||
|
value: 1,
|
||||||
|
},
|
||||||
|
update: { value: { increment: 1 } },
|
||||||
|
});
|
||||||
|
|
||||||
|
const workList = await Promise.all(
|
||||||
|
(work || []).map(async (w, wIdx) =>
|
||||||
|
tx.work.create({
|
||||||
|
data: {
|
||||||
|
name: w.name,
|
||||||
|
order: wIdx + 1,
|
||||||
|
attributes: w.attributes,
|
||||||
|
productOnWork: {
|
||||||
|
createMany: {
|
||||||
|
data: w.productId.map((p, pIdx) => ({
|
||||||
|
productId: p,
|
||||||
|
order: pIdx + 1,
|
||||||
|
})),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
return tx.service.create({
|
||||||
|
include: {
|
||||||
|
work: {
|
||||||
|
include: {
|
||||||
|
productOnWork: {
|
||||||
|
include: {
|
||||||
|
product: true,
|
||||||
|
},
|
||||||
|
orderBy: { order: "asc" },
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
data: {
|
||||||
|
...payload,
|
||||||
|
statusOrder: +(body.status === "INACTIVE"),
|
||||||
|
code: `${body.code.toLocaleUpperCase()}${last.value.toString().padStart(3, "0")}`,
|
||||||
|
work: { connect: workList.map((v) => ({ id: v.id })) },
|
||||||
|
createdBy: req.user.name,
|
||||||
|
updatedBy: req.user.name,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
},
|
||||||
|
{ isolationLevel: Prisma.TransactionIsolationLevel.Serializable },
|
||||||
|
);
|
||||||
|
this.setStatus(HttpStatus.CREATED);
|
||||||
|
|
||||||
|
return Object.assign(record, {
|
||||||
|
imageUrl: await presignedGetObjectIfExist(
|
||||||
|
MINIO_BUCKET,
|
||||||
|
imageLocation(record.id),
|
||||||
|
12 * 60 * 60,
|
||||||
|
),
|
||||||
|
imageUploadUrl: await minio.presignedPutObject(
|
||||||
|
MINIO_BUCKET,
|
||||||
|
imageLocation(record.id),
|
||||||
|
12 * 60 * 60,
|
||||||
|
),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@Put("{serviceId}")
|
||||||
|
@Security("keycloak")
|
||||||
|
async editService(
|
||||||
|
@Request() req: RequestWithUser,
|
||||||
|
@Body() body: ServiceUpdate,
|
||||||
|
@Path() serviceId: string,
|
||||||
|
) {
|
||||||
|
if (!(await prisma.service.findUnique({ where: { id: serviceId } }))) {
|
||||||
|
throw new HttpError(HttpStatus.NOT_FOUND, "Service cannot be found.", "serviceNotFound");
|
||||||
|
}
|
||||||
|
const { work, ...payload } = body;
|
||||||
|
const record = await prisma.$transaction(async (tx) => {
|
||||||
|
const workList = await Promise.all(
|
||||||
|
(work || []).map(async (w, wIdx) =>
|
||||||
|
tx.work.create({
|
||||||
|
data: {
|
||||||
|
name: w.name,
|
||||||
|
order: wIdx + 1,
|
||||||
|
attributes: w.attributes,
|
||||||
|
productOnWork: {
|
||||||
|
createMany: {
|
||||||
|
data: w.productId.map((p, pIdx) => ({
|
||||||
|
productId: p,
|
||||||
|
order: pIdx + 1,
|
||||||
|
})),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
return await tx.service.update({
|
||||||
|
data: {
|
||||||
|
...payload,
|
||||||
|
statusOrder: +(payload.status === "INACTIVE"),
|
||||||
|
work: {
|
||||||
|
deleteMany: {},
|
||||||
|
connect: workList.map((v) => ({ id: v.id })),
|
||||||
|
},
|
||||||
|
updatedBy: req.user.name,
|
||||||
|
},
|
||||||
|
where: { id: serviceId },
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
return Object.assign(record, {
|
||||||
|
imageUrl: await presignedGetObjectIfExist(
|
||||||
|
MINIO_BUCKET,
|
||||||
|
imageLocation(record.id),
|
||||||
|
12 * 60 * 60,
|
||||||
|
),
|
||||||
|
imageUploadUrl: await minio.presignedPutObject(
|
||||||
|
MINIO_BUCKET,
|
||||||
|
imageLocation(record.id),
|
||||||
|
12 * 60 * 60,
|
||||||
|
),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@Delete("{serviceId}")
|
||||||
|
@Security("keycloak")
|
||||||
|
async deleteService(@Path() serviceId: string) {
|
||||||
|
const record = await prisma.service.findFirst({ where: { id: serviceId } });
|
||||||
|
|
||||||
|
if (!record) {
|
||||||
|
throw new HttpError(HttpStatus.NOT_FOUND, "Service cannot be found.", "serviceNotFound");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (record.status !== Status.CREATED) {
|
||||||
|
throw new HttpError(HttpStatus.FORBIDDEN, "Service is in used.", "serviceInUsed");
|
||||||
|
}
|
||||||
|
|
||||||
|
return await prisma.service.delete({ where: { id: serviceId } });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -15,7 +15,7 @@ import {
|
||||||
import { Prisma, Status, UserType } from "@prisma/client";
|
import { Prisma, Status, UserType } from "@prisma/client";
|
||||||
|
|
||||||
import prisma from "../db";
|
import prisma from "../db";
|
||||||
import minio from "../services/minio";
|
import minio, { presignedGetObjectIfExist } from "../services/minio";
|
||||||
import { RequestWithUser } from "../interfaces/user";
|
import { RequestWithUser } from "../interfaces/user";
|
||||||
import HttpError from "../interfaces/http-error";
|
import HttpError from "../interfaces/http-error";
|
||||||
import HttpStatus from "../interfaces/http-status";
|
import HttpStatus from "../interfaces/http-status";
|
||||||
|
|
@ -119,11 +119,11 @@ function imageLocation(id: string) {
|
||||||
return `user/profile-img-${id}`;
|
return `user/profile-img-${id}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Route("api/user")
|
@Route("api/v1/user")
|
||||||
@Tags("User")
|
@Tags("User")
|
||||||
@Security("keycloak")
|
|
||||||
export class UserController extends Controller {
|
export class UserController extends Controller {
|
||||||
@Get("type-stats")
|
@Get("type-stats")
|
||||||
|
@Security("keycloak")
|
||||||
async getUserTypeStats() {
|
async getUserTypeStats() {
|
||||||
const list = await prisma.user.groupBy({
|
const list = await prisma.user.groupBy({
|
||||||
by: "userType",
|
by: "userType",
|
||||||
|
|
@ -145,6 +145,7 @@ export class UserController extends Controller {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Get()
|
@Get()
|
||||||
|
@Security("keycloak")
|
||||||
async getUser(
|
async getUser(
|
||||||
@Query() userType?: UserType,
|
@Query() userType?: UserType,
|
||||||
@Query() zipCode?: string,
|
@Query() zipCode?: string,
|
||||||
|
|
@ -166,7 +167,7 @@ export class UserController extends Controller {
|
||||||
|
|
||||||
const [result, total] = await prisma.$transaction([
|
const [result, total] = await prisma.$transaction([
|
||||||
prisma.user.findMany({
|
prisma.user.findMany({
|
||||||
orderBy: { createdAt: "asc" },
|
orderBy: [{ statusOrder: "asc" }, { createdAt: "asc" }],
|
||||||
include: {
|
include: {
|
||||||
province: true,
|
province: true,
|
||||||
district: true,
|
district: true,
|
||||||
|
|
@ -185,7 +186,7 @@ export class UserController extends Controller {
|
||||||
result.map(async (v) => ({
|
result.map(async (v) => ({
|
||||||
...v,
|
...v,
|
||||||
branch: includeBranch ? v.branch.map((a) => a.branch) : undefined,
|
branch: includeBranch ? v.branch.map((a) => a.branch) : undefined,
|
||||||
profileImageUrl: await minio.presignedGetObject(
|
profileImageUrl: await presignedGetObjectIfExist(
|
||||||
MINIO_BUCKET,
|
MINIO_BUCKET,
|
||||||
imageLocation(v.id),
|
imageLocation(v.id),
|
||||||
12 * 60 * 60,
|
12 * 60 * 60,
|
||||||
|
|
@ -199,6 +200,7 @@ export class UserController extends Controller {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Get("{userId}")
|
@Get("{userId}")
|
||||||
|
@Security("keycloak")
|
||||||
async getUserById(@Path() userId: string) {
|
async getUserById(@Path() userId: string) {
|
||||||
const record = await prisma.user.findFirst({
|
const record = await prisma.user.findFirst({
|
||||||
include: {
|
include: {
|
||||||
|
|
@ -209,8 +211,7 @@ export class UserController extends Controller {
|
||||||
where: { id: userId },
|
where: { id: userId },
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!record)
|
if (!record) throw new HttpError(HttpStatus.NOT_FOUND, "User cannot be found.", "userNotFound");
|
||||||
throw new HttpError(HttpStatus.NOT_FOUND, "User cannot be found.", "data_not_found");
|
|
||||||
|
|
||||||
return Object.assign(record, {
|
return Object.assign(record, {
|
||||||
profileImageUrl: await minio.presignedGetObject(
|
profileImageUrl: await minio.presignedGetObject(
|
||||||
|
|
@ -222,6 +223,7 @@ export class UserController extends Controller {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Post()
|
@Post()
|
||||||
|
@Security("keycloak")
|
||||||
async createUser(@Request() req: RequestWithUser, @Body() body: UserCreate) {
|
async createUser(@Request() req: RequestWithUser, @Body() body: UserCreate) {
|
||||||
if (body.provinceId || body.districtId || body.subDistrictId) {
|
if (body.provinceId || body.districtId || body.subDistrictId) {
|
||||||
const [province, district, subDistrict] = await prisma.$transaction([
|
const [province, district, subDistrict] = await prisma.$transaction([
|
||||||
|
|
@ -233,21 +235,21 @@ export class UserController extends Controller {
|
||||||
throw new HttpError(
|
throw new HttpError(
|
||||||
HttpStatus.BAD_REQUEST,
|
HttpStatus.BAD_REQUEST,
|
||||||
"Province cannot be found.",
|
"Province cannot be found.",
|
||||||
"missing_or_invalid_parameter",
|
"relationProvinceNotFound",
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
if (body.districtId && !district) {
|
if (body.districtId && !district) {
|
||||||
throw new HttpError(
|
throw new HttpError(
|
||||||
HttpStatus.BAD_REQUEST,
|
HttpStatus.BAD_REQUEST,
|
||||||
"District cannot be found.",
|
"District cannot be found.",
|
||||||
"missing_or_invalid_parameter",
|
"relationDistrictNotFound",
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
if (body.subDistrictId && !subDistrict) {
|
if (body.subDistrictId && !subDistrict) {
|
||||||
throw new HttpError(
|
throw new HttpError(
|
||||||
HttpStatus.BAD_REQUEST,
|
HttpStatus.BAD_REQUEST,
|
||||||
"Sub-district cannot be found.",
|
"Sub-district cannot be found.",
|
||||||
"missing_or_invalid_parameter",
|
"relationSubDistrictNotFound",
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -268,6 +270,7 @@ export class UserController extends Controller {
|
||||||
firstName: body.firstName,
|
firstName: body.firstName,
|
||||||
lastName: body.lastName,
|
lastName: body.lastName,
|
||||||
requiredActions: ["UPDATE_PASSWORD"],
|
requiredActions: ["UPDATE_PASSWORD"],
|
||||||
|
enabled: rest.status !== "INACTIVE",
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!userId || typeof userId !== "string") {
|
if (!userId || typeof userId !== "string") {
|
||||||
|
|
@ -288,13 +291,14 @@ export class UserController extends Controller {
|
||||||
data: {
|
data: {
|
||||||
id: userId,
|
id: userId,
|
||||||
...rest,
|
...rest,
|
||||||
|
statusOrder: +(rest.status === "INACTIVE"),
|
||||||
username,
|
username,
|
||||||
userRole: role.name,
|
userRole: role.name,
|
||||||
province: { connect: provinceId ? { id: provinceId } : undefined },
|
province: { connect: provinceId ? { id: provinceId } : undefined },
|
||||||
district: { connect: districtId ? { id: districtId } : undefined },
|
district: { connect: districtId ? { id: districtId } : undefined },
|
||||||
subDistrict: { connect: subDistrictId ? { id: subDistrictId } : undefined },
|
subDistrict: { connect: subDistrictId ? { id: subDistrictId } : undefined },
|
||||||
createdBy: req.user.name,
|
createdBy: req.user.name,
|
||||||
updateBy: req.user.name,
|
updatedBy: req.user.name,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
@ -315,6 +319,7 @@ export class UserController extends Controller {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Put("{userId}")
|
@Put("{userId}")
|
||||||
|
@Security("keycloak")
|
||||||
async editUser(
|
async editUser(
|
||||||
@Request() req: RequestWithUser,
|
@Request() req: RequestWithUser,
|
||||||
@Body() body: UserUpdate,
|
@Body() body: UserUpdate,
|
||||||
|
|
@ -370,14 +375,23 @@ export class UserController extends Controller {
|
||||||
if (!resultAddRole) {
|
if (!resultAddRole) {
|
||||||
throw new Error("Failed. Cannot set user's role.");
|
throw new Error("Failed. Cannot set user's role.");
|
||||||
} else {
|
} else {
|
||||||
if (Array.isArray(currentRole)) await removeUserRoles(userId, currentRole);
|
if (Array.isArray(currentRole))
|
||||||
|
await removeUserRoles(
|
||||||
|
userId,
|
||||||
|
currentRole.filter(
|
||||||
|
(a) =>
|
||||||
|
!["uma_authorization", "offline_access", "default-roles"].some((b) =>
|
||||||
|
a.name.includes(b),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
userRole = role.name;
|
userRole = role.name;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (body.username) {
|
if (body.username) {
|
||||||
await editUser(userId, { username: body.username });
|
await editUser(userId, { username: body.username, enabled: body.status !== "INACTIVE" });
|
||||||
}
|
}
|
||||||
|
|
||||||
const { provinceId, districtId, subDistrictId, ...rest } = body;
|
const { provinceId, districtId, subDistrictId, ...rest } = body;
|
||||||
|
|
@ -387,7 +401,7 @@ export class UserController extends Controller {
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!user) {
|
if (!user) {
|
||||||
throw new HttpError(HttpStatus.NOT_FOUND, "Branch cannot be found.", "data_not_found");
|
throw new HttpError(HttpStatus.NOT_FOUND, "Branch cannot be found.", "branchNotFound");
|
||||||
}
|
}
|
||||||
|
|
||||||
const lastUserOfType =
|
const lastUserOfType =
|
||||||
|
|
@ -406,6 +420,7 @@ export class UserController extends Controller {
|
||||||
include: { province: true, district: true, subDistrict: true },
|
include: { province: true, district: true, subDistrict: true },
|
||||||
data: {
|
data: {
|
||||||
...rest,
|
...rest,
|
||||||
|
statusOrder: +(rest.status === "INACTIVE"),
|
||||||
userRole,
|
userRole,
|
||||||
code:
|
code:
|
||||||
(lastUserOfType &&
|
(lastUserOfType &&
|
||||||
|
|
@ -423,7 +438,7 @@ export class UserController extends Controller {
|
||||||
connect: subDistrictId ? { id: subDistrictId } : undefined,
|
connect: subDistrictId ? { id: subDistrictId } : undefined,
|
||||||
disconnect: subDistrictId === null || undefined,
|
disconnect: subDistrictId === null || undefined,
|
||||||
},
|
},
|
||||||
updateBy: req.user.name,
|
updatedBy: req.user.name,
|
||||||
},
|
},
|
||||||
where: { id: userId },
|
where: { id: userId },
|
||||||
});
|
});
|
||||||
|
|
@ -443,6 +458,7 @@ export class UserController extends Controller {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Delete("{userId}")
|
@Delete("{userId}")
|
||||||
|
@Security("keycloak")
|
||||||
async deleteUser(@Path() userId: string) {
|
async deleteUser(@Path() userId: string) {
|
||||||
const record = await prisma.user.findFirst({
|
const record = await prisma.user.findFirst({
|
||||||
include: {
|
include: {
|
||||||
|
|
@ -454,11 +470,11 @@ export class UserController extends Controller {
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!record) {
|
if (!record) {
|
||||||
throw new HttpError(HttpStatus.NOT_FOUND, "User cannot be found.", "data_not_found");
|
throw new HttpError(HttpStatus.NOT_FOUND, "User cannot be found.", "userNotFound");
|
||||||
}
|
}
|
||||||
|
|
||||||
if (record.status !== Status.CREATED) {
|
if (record.status !== Status.CREATED) {
|
||||||
throw new HttpError(HttpStatus.FORBIDDEN, "User is in used.", "data_in_used");
|
throw new HttpError(HttpStatus.FORBIDDEN, "User is in used.", "userInUsed");
|
||||||
}
|
}
|
||||||
|
|
||||||
await minio.removeObject(MINIO_BUCKET, imageLocation(userId), {
|
await minio.removeObject(MINIO_BUCKET, imageLocation(userId), {
|
||||||
|
|
@ -475,9 +491,7 @@ export class UserController extends Controller {
|
||||||
stream.on("error", () => reject(new Error("MinIO error.")));
|
stream.on("error", () => reject(new Error("MinIO error.")));
|
||||||
}).then((list) => {
|
}).then((list) => {
|
||||||
list.map(async (v) => {
|
list.map(async (v) => {
|
||||||
await minio.removeObject(MINIO_BUCKET, `${attachmentLocation(userId)}/${v}`, {
|
await minio.removeObject(MINIO_BUCKET, v, { forceDelete: true });
|
||||||
forceDelete: true,
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
@ -498,7 +512,7 @@ function attachmentLocation(uid: string) {
|
||||||
return `user-attachment/${uid}`;
|
return `user-attachment/${uid}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Route("api/user/{userId}/attachment")
|
@Route("api/v1/user/{userId}/attachment")
|
||||||
@Tags("User")
|
@Tags("User")
|
||||||
@Security("keycloak")
|
@Security("keycloak")
|
||||||
export class UserAttachmentController extends Controller {
|
export class UserAttachmentController extends Controller {
|
||||||
|
|
@ -514,7 +528,7 @@ export class UserAttachmentController extends Controller {
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!record) {
|
if (!record) {
|
||||||
throw new HttpError(HttpStatus.NOT_FOUND, "User cannot be found.", "data_not_found");
|
throw new HttpError(HttpStatus.NOT_FOUND, "User cannot be found.", "userNotFound");
|
||||||
}
|
}
|
||||||
|
|
||||||
const list = await new Promise<string[]>((resolve, reject) => {
|
const list = await new Promise<string[]>((resolve, reject) => {
|
||||||
|
|
@ -547,7 +561,7 @@ export class UserAttachmentController extends Controller {
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!record) {
|
if (!record) {
|
||||||
throw new HttpError(HttpStatus.NOT_FOUND, "User cannot be found.", "data_not_found");
|
throw new HttpError(HttpStatus.NOT_FOUND, "User cannot be found.", "userNotFound");
|
||||||
}
|
}
|
||||||
|
|
||||||
return await Promise.all(
|
return await Promise.all(
|
||||||
|
|
|
||||||
311
src/controllers/work/work-controller.ts
Normal file
311
src/controllers/work/work-controller.ts
Normal file
|
|
@ -0,0 +1,311 @@
|
||||||
|
import {
|
||||||
|
Body,
|
||||||
|
Controller,
|
||||||
|
Delete,
|
||||||
|
Get,
|
||||||
|
Put,
|
||||||
|
Path,
|
||||||
|
Post,
|
||||||
|
Query,
|
||||||
|
Request,
|
||||||
|
Route,
|
||||||
|
Security,
|
||||||
|
Tags,
|
||||||
|
} from "tsoa";
|
||||||
|
import { Prisma, Status } from "@prisma/client";
|
||||||
|
|
||||||
|
import prisma from "../../db";
|
||||||
|
import { RequestWithUser } from "../../interfaces/user";
|
||||||
|
import HttpError from "../../interfaces/http-error";
|
||||||
|
import HttpStatus from "../../interfaces/http-status";
|
||||||
|
|
||||||
|
type WorkCreate = {
|
||||||
|
order: number;
|
||||||
|
name: string;
|
||||||
|
productId: string[];
|
||||||
|
attributes?: {
|
||||||
|
[key: string]: any;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
type WorkUpdate = {
|
||||||
|
order?: number;
|
||||||
|
name?: string;
|
||||||
|
productId?: string[];
|
||||||
|
attributes?: {
|
||||||
|
[key: string]: any;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
@Route("api/v1/work")
|
||||||
|
@Tags("Work")
|
||||||
|
@Security("keycloak")
|
||||||
|
export class WorkController extends Controller {
|
||||||
|
@Get()
|
||||||
|
async getWork(
|
||||||
|
@Query() baseOnly?: boolean,
|
||||||
|
@Query() query: string = "",
|
||||||
|
@Query() page: number = 1,
|
||||||
|
@Query() pageSize: number = 30,
|
||||||
|
) {
|
||||||
|
const where = {
|
||||||
|
OR: [{ name: { contains: query }, serviceId: baseOnly ? null : undefined }],
|
||||||
|
} satisfies Prisma.WorkWhereInput;
|
||||||
|
|
||||||
|
const [result, total] = await prisma.$transaction([
|
||||||
|
prisma.work.findMany({
|
||||||
|
include: {
|
||||||
|
productOnWork: {
|
||||||
|
include: {
|
||||||
|
product: true,
|
||||||
|
},
|
||||||
|
orderBy: {
|
||||||
|
order: "asc",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
orderBy: { createdAt: "asc" },
|
||||||
|
where,
|
||||||
|
take: pageSize,
|
||||||
|
skip: (page - 1) * pageSize,
|
||||||
|
}),
|
||||||
|
prisma.work.count({ where }),
|
||||||
|
]);
|
||||||
|
|
||||||
|
return { result, page, pageSize, total };
|
||||||
|
}
|
||||||
|
|
||||||
|
@Get("{workId}")
|
||||||
|
async getWorkById(@Path() workId: string) {
|
||||||
|
const record = await prisma.work.findFirst({
|
||||||
|
where: { id: workId },
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!record) throw new HttpError(HttpStatus.NOT_FOUND, "Work cannot be found.", "workNotFound");
|
||||||
|
|
||||||
|
return record;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Get("{workId}/product")
|
||||||
|
async getProductOfWork(
|
||||||
|
@Path() workId: string,
|
||||||
|
@Query() query: string = "",
|
||||||
|
@Query() page: number = 1,
|
||||||
|
@Query() pageSize: number = 30,
|
||||||
|
) {
|
||||||
|
const where = {
|
||||||
|
AND: [
|
||||||
|
{
|
||||||
|
workProduct: { some: { workId } },
|
||||||
|
},
|
||||||
|
{
|
||||||
|
OR: [{ name: { contains: query } }],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
} satisfies Prisma.ProductWhereInput;
|
||||||
|
|
||||||
|
const [result, total] = await prisma.$transaction([
|
||||||
|
prisma.product.findMany({
|
||||||
|
where,
|
||||||
|
take: pageSize,
|
||||||
|
skip: (page - 1) * pageSize,
|
||||||
|
}),
|
||||||
|
prisma.product.count({
|
||||||
|
where,
|
||||||
|
}),
|
||||||
|
]);
|
||||||
|
|
||||||
|
return { result, page, pageSize, total };
|
||||||
|
}
|
||||||
|
|
||||||
|
@Post()
|
||||||
|
async createWork(@Request() req: RequestWithUser, @Body() body: WorkCreate) {
|
||||||
|
const { productId, ...payload } = body;
|
||||||
|
|
||||||
|
const exist = await prisma.work.findFirst({
|
||||||
|
include: {
|
||||||
|
productOnWork: {
|
||||||
|
include: {
|
||||||
|
product: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
where: {
|
||||||
|
productOnWork: {
|
||||||
|
every: {
|
||||||
|
productId: { in: productId },
|
||||||
|
},
|
||||||
|
},
|
||||||
|
NOT: {
|
||||||
|
OR: [
|
||||||
|
{
|
||||||
|
productOnWork: {
|
||||||
|
some: {
|
||||||
|
productId: { notIn: productId },
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
productOnWork: {
|
||||||
|
none: {},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
if (exist) return exist;
|
||||||
|
|
||||||
|
const productList = await prisma.product.findMany({
|
||||||
|
where: { id: { in: productId } },
|
||||||
|
});
|
||||||
|
|
||||||
|
if (productList.length !== productId.length) {
|
||||||
|
throw new HttpError(HttpStatus.BAD_REQUEST, "Some product not found.", "someProductBadReq");
|
||||||
|
}
|
||||||
|
|
||||||
|
const record = await prisma.work.create({
|
||||||
|
include: {
|
||||||
|
productOnWork: {
|
||||||
|
include: {
|
||||||
|
product: true,
|
||||||
|
},
|
||||||
|
orderBy: {
|
||||||
|
order: "asc",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
data: {
|
||||||
|
...payload,
|
||||||
|
productOnWork: {
|
||||||
|
createMany: {
|
||||||
|
data: productId.map((v, i) => ({
|
||||||
|
order: i + 1,
|
||||||
|
productId: v,
|
||||||
|
createdBy: req.user.name,
|
||||||
|
updatedBy: req.user.name,
|
||||||
|
})),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
createdBy: req.user.name,
|
||||||
|
updatedBy: req.user.name,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
await prisma.product.updateMany({
|
||||||
|
where: { id: { in: body.productId }, status: Status.CREATED },
|
||||||
|
data: { status: Status.ACTIVE },
|
||||||
|
});
|
||||||
|
|
||||||
|
this.setStatus(HttpStatus.CREATED);
|
||||||
|
|
||||||
|
return record;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Put("{workId}")
|
||||||
|
async editWork(
|
||||||
|
@Request() req: RequestWithUser,
|
||||||
|
@Body() body: WorkUpdate,
|
||||||
|
@Path() workId: string,
|
||||||
|
) {
|
||||||
|
const { productId, ...payload } = body;
|
||||||
|
|
||||||
|
if (!(await prisma.work.findUnique({ where: { id: workId } }))) {
|
||||||
|
throw new HttpError(HttpStatus.NOT_FOUND, "Work cannot be found.", "workNotFound");
|
||||||
|
}
|
||||||
|
|
||||||
|
const exist = await prisma.work.findFirst({
|
||||||
|
include: {
|
||||||
|
productOnWork: {
|
||||||
|
include: {
|
||||||
|
product: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
where: {
|
||||||
|
productOnWork: {
|
||||||
|
every: {
|
||||||
|
productId: { in: productId },
|
||||||
|
},
|
||||||
|
},
|
||||||
|
NOT: {
|
||||||
|
OR: [
|
||||||
|
{ id: workId },
|
||||||
|
{
|
||||||
|
productOnWork: {
|
||||||
|
some: {
|
||||||
|
productId: { notIn: productId },
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
productOnWork: {
|
||||||
|
none: {},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
if (exist) return exist;
|
||||||
|
|
||||||
|
const record = await prisma.work.update({
|
||||||
|
include: {
|
||||||
|
productOnWork: {
|
||||||
|
include: {
|
||||||
|
product: true,
|
||||||
|
},
|
||||||
|
orderBy: {
|
||||||
|
order: "asc",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
where: { id: workId },
|
||||||
|
data: {
|
||||||
|
...payload,
|
||||||
|
productOnWork: productId
|
||||||
|
? {
|
||||||
|
deleteMany: {
|
||||||
|
productId: { notIn: productId },
|
||||||
|
},
|
||||||
|
upsert: productId.map((v, i) => ({
|
||||||
|
where: {
|
||||||
|
workId_productId: {
|
||||||
|
workId,
|
||||||
|
productId: v,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
update: { order: i + 1 },
|
||||||
|
create: {
|
||||||
|
order: i + 1,
|
||||||
|
productId: v,
|
||||||
|
createdBy: req.user.name,
|
||||||
|
updatedBy: req.user.name,
|
||||||
|
},
|
||||||
|
})),
|
||||||
|
}
|
||||||
|
: undefined,
|
||||||
|
updatedBy: req.user.name,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
return record;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Delete("{workId}")
|
||||||
|
async deleteWork(@Path() workId: string) {
|
||||||
|
const record = await prisma.work.findFirst({ where: { id: workId } });
|
||||||
|
|
||||||
|
if (!record) {
|
||||||
|
throw new HttpError(HttpStatus.NOT_FOUND, "Work cannot be found.", "workNotFound");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (record.status !== Status.CREATED) {
|
||||||
|
throw new HttpError(HttpStatus.FORBIDDEN, "Work is in used.", "workInUsed");
|
||||||
|
}
|
||||||
|
|
||||||
|
return await prisma.work.delete({ where: { id: workId } });
|
||||||
|
}
|
||||||
|
}
|
||||||
18
src/db.ts
18
src/db.ts
|
|
@ -1,7 +1,23 @@
|
||||||
import { PrismaClient } from "@prisma/client";
|
import { PrismaClient } from "@prisma/client";
|
||||||
|
import { Kysely, PostgresAdapter, PostgresIntrospector, PostgresQueryCompiler } from "kysely";
|
||||||
|
import kyselyExtension from "prisma-extension-kysely";
|
||||||
|
import type { DB } from "./generated/kysely/types";
|
||||||
|
|
||||||
const prisma = new PrismaClient({
|
const prisma = new PrismaClient({
|
||||||
errorFormat: process.env.NODE_ENV === "production" ? "minimal" : "pretty",
|
errorFormat: process.env.NODE_ENV === "production" ? "minimal" : "pretty",
|
||||||
});
|
}).$extends(
|
||||||
|
kyselyExtension({
|
||||||
|
kysely: (driver) =>
|
||||||
|
new Kysely<DB>({
|
||||||
|
dialect: {
|
||||||
|
createDriver: () => driver,
|
||||||
|
createAdapter: () => new PostgresAdapter(),
|
||||||
|
createIntrospector: (db: Kysely<DB>) => new PostgresIntrospector(db),
|
||||||
|
createQueryCompiler: () => new PostgresQueryCompiler(),
|
||||||
|
},
|
||||||
|
plugins: [],
|
||||||
|
}),
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
export default prisma;
|
export default prisma;
|
||||||
|
|
|
||||||
|
|
@ -1,29 +1,20 @@
|
||||||
import HttpStatus from "./http-status";
|
import HttpStatus from "./http-status";
|
||||||
|
|
||||||
type DevMessage =
|
|
||||||
| "missing_or_invalid_parameter"
|
|
||||||
| "data_exists"
|
|
||||||
| "data_in_used"
|
|
||||||
| "no_permission"
|
|
||||||
| "unknown_url"
|
|
||||||
| "data_not_found"
|
|
||||||
| "unauthorized";
|
|
||||||
|
|
||||||
class HttpError extends Error {
|
class HttpError extends Error {
|
||||||
/**
|
/**
|
||||||
* HTTP Status Code
|
* HTTP Status Code
|
||||||
*/
|
*/
|
||||||
status: HttpStatus;
|
status: HttpStatus;
|
||||||
message: string;
|
message: string;
|
||||||
devMessage?: DevMessage;
|
code?: string;
|
||||||
|
|
||||||
constructor(status: HttpStatus, message: string, devMessage?: DevMessage) {
|
constructor(status: HttpStatus, message: string, code?: string) {
|
||||||
super(message);
|
super(message);
|
||||||
|
|
||||||
this.name = "HttpError";
|
this.name = "HttpError";
|
||||||
this.status = status;
|
this.status = status;
|
||||||
this.message = message;
|
this.message = message;
|
||||||
this.devMessage = devMessage;
|
this.code = code;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,7 @@ import type { Request } from "express";
|
||||||
|
|
||||||
export type RequestWithUser = Request & {
|
export type RequestWithUser = Request & {
|
||||||
user: {
|
user: {
|
||||||
|
sub: string;
|
||||||
name: string;
|
name: string;
|
||||||
given_name: string;
|
given_name: string;
|
||||||
familiy_name: string;
|
familiy_name: string;
|
||||||
|
|
|
||||||
|
|
@ -10,8 +10,15 @@ export async function expressAuthentication(
|
||||||
) {
|
) {
|
||||||
switch (securityName) {
|
switch (securityName) {
|
||||||
case "keycloak":
|
case "keycloak":
|
||||||
return keycloakAuth(request, scopes);
|
const authData = await keycloakAuth(request, scopes);
|
||||||
|
request.app.locals.logData.sessionId = authData.session_state;
|
||||||
|
request.app.locals.logData.user = authData.preffered_username;
|
||||||
|
return authData;
|
||||||
default:
|
default:
|
||||||
throw new HttpError(HttpStatus.NOT_IMPLEMENTED, "ไม่ทราบวิธียืนยันตัวตน");
|
throw new HttpError(
|
||||||
|
HttpStatus.NOT_IMPLEMENTED,
|
||||||
|
"Unknown how to verify identity.",
|
||||||
|
"unknowHowToVerify",
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -8,7 +8,7 @@ function error(error: Error, _req: Request, res: Response, _next: NextFunction)
|
||||||
return res.status(error.status).json({
|
return res.status(error.status).json({
|
||||||
status: error.status,
|
status: error.status,
|
||||||
message: error.message,
|
message: error.message,
|
||||||
devMessage: error.devMessage,
|
code: error.code,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -17,7 +17,7 @@ function error(error: Error, _req: Request, res: Response, _next: NextFunction)
|
||||||
status: HttpStatus.UNPROCESSABLE_ENTITY,
|
status: HttpStatus.UNPROCESSABLE_ENTITY,
|
||||||
message: "Validation error(s).",
|
message: "Validation error(s).",
|
||||||
detail: error.fields,
|
detail: error.fields,
|
||||||
devMessage: "missing_or_invalid_parameter",
|
code: "validateError",
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -26,7 +26,7 @@ function error(error: Error, _req: Request, res: Response, _next: NextFunction)
|
||||||
return res.status(HttpStatus.INTERNAL_SERVER_ERROR).json({
|
return res.status(HttpStatus.INTERNAL_SERVER_ERROR).json({
|
||||||
status: HttpStatus.INTERNAL_SERVER_ERROR,
|
status: HttpStatus.INTERNAL_SERVER_ERROR,
|
||||||
message: error.message,
|
message: error.message,
|
||||||
devMessage: "system_error",
|
code: "system_error",
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
import { NextFunction, Request, Response } from "express";
|
import { NextFunction, Request, Response } from "express";
|
||||||
import elasticsearch from "../services/elasticsearch";
|
import elasticsearch from "../services/elasticsearch";
|
||||||
|
import { randomUUID } from "crypto";
|
||||||
|
|
||||||
if (!process.env.ELASTICSEARCH_INDEX) {
|
if (!process.env.ELASTICSEARCH_INDEX) {
|
||||||
throw new Error("Require ELASTICSEARCH_INDEX to store log.");
|
throw new Error("Require ELASTICSEARCH_INDEX to store log.");
|
||||||
|
|
@ -50,16 +51,11 @@ async function logMiddleware(req: Request, res: Response, next: NextFunction) {
|
||||||
host: req.hostname,
|
host: req.hostname,
|
||||||
sessionId: req.headers["x-session-id"],
|
sessionId: req.headers["x-session-id"],
|
||||||
rtId: req.headers["x-rtid"],
|
rtId: req.headers["x-rtid"],
|
||||||
tId: req.headers["x-tid"],
|
tId: randomUUID(),
|
||||||
method: req.method,
|
method: req.method,
|
||||||
endpoint: req.url,
|
endpoint: req.url,
|
||||||
responseCode: res.statusCode,
|
responseCode: res.statusCode,
|
||||||
responseDescription:
|
responseDescription: data?.code,
|
||||||
data?.devMessage !== undefined
|
|
||||||
? data.devMessage
|
|
||||||
: { 200: "success", 201: "created_success", 204: "no_content", 304: "success" }[
|
|
||||||
res.statusCode
|
|
||||||
],
|
|
||||||
input: (level === 4 && JSON.stringify(req.body, null, 2)) || undefined,
|
input: (level === 4 && JSON.stringify(req.body, null, 2)) || undefined,
|
||||||
output: (level === 4 && JSON.stringify(data, null, 2)) || undefined,
|
output: (level === 4 && JSON.stringify(data, null, 2)) || undefined,
|
||||||
...req.app.locals.logData,
|
...req.app.locals.logData,
|
||||||
|
|
|
||||||
|
|
@ -5,14 +5,14 @@ import HttpStatus from "../interfaces/http-status";
|
||||||
|
|
||||||
export function role(
|
export function role(
|
||||||
role: string | string[],
|
role: string | string[],
|
||||||
errorMessage: string = "คุณไม่มีสิทธิในการเข้าถึงทรัพยากรดังกล่าว",
|
errorMessage: string = "You do not have permission to access this resource.",
|
||||||
) {
|
) {
|
||||||
return (req: RequestWithUser, _res: Response, next: NextFunction) => {
|
return (req: RequestWithUser, _res: Response, next: NextFunction) => {
|
||||||
if (!Array.isArray(role) && !req.user.role.includes(role) && !req.user.role.includes("*")) {
|
if (!Array.isArray(role) && !req.user.role.includes(role) && !req.user.role.includes("*")) {
|
||||||
throw new HttpError(HttpStatus.FORBIDDEN, errorMessage);
|
throw new HttpError(HttpStatus.FORBIDDEN, errorMessage, "noPermissionToAccess");
|
||||||
}
|
}
|
||||||
if (role !== "*" && !req.user.role.some((v) => role.includes(v))) {
|
if (role !== "*" && !req.user.role.some((v) => role.includes(v))) {
|
||||||
throw new HttpError(HttpStatus.FORBIDDEN, errorMessage);
|
throw new HttpError(HttpStatus.FORBIDDEN, errorMessage, "noPermissionToAccess");
|
||||||
}
|
}
|
||||||
return next();
|
return next();
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -2,8 +2,8 @@ import { DecodedJwt, createDecoder } from "fast-jwt";
|
||||||
|
|
||||||
const KC_URL = process.env.KC_URL;
|
const KC_URL = process.env.KC_URL;
|
||||||
const KC_REALM = process.env.KC_REALM;
|
const KC_REALM = process.env.KC_REALM;
|
||||||
const KC_CLIENT_ID = process.env.KC_SERVICE_ACCOUNT_CLIENT_ID;
|
const KC_ADMIN_USERNAME = process.env.KC_ADMIN_USERNAME;
|
||||||
const KC_SECRET = process.env.KC_SERVICE_ACCOUNT_SECRET;
|
const KC_ADMIN_PASSWORD = process.env.KC_ADMIN_PASSWORD;
|
||||||
|
|
||||||
let token: string | null = null;
|
let token: string | null = null;
|
||||||
let decoded: DecodedJwt | null = null;
|
let decoded: DecodedJwt | null = null;
|
||||||
|
|
@ -14,7 +14,7 @@ const jwtDecode = createDecoder({ complete: true });
|
||||||
* Check if token is expired or will expire in 30 seconds
|
* Check if token is expired or will expire in 30 seconds
|
||||||
* @returns true if expire or can't get exp, false otherwise
|
* @returns true if expire or can't get exp, false otherwise
|
||||||
*/
|
*/
|
||||||
export function isTokenExpired(token: string, beforeExpire: number = 30) {
|
export function isTokenExpired(token: string, beforeExpire: number = 10) {
|
||||||
decoded = jwtDecode(token);
|
decoded = jwtDecode(token);
|
||||||
|
|
||||||
if (decoded && decoded.payload.exp) {
|
if (decoded && decoded.payload.exp) {
|
||||||
|
|
@ -28,19 +28,20 @@ export function isTokenExpired(token: string, beforeExpire: number = 30) {
|
||||||
* Get token from keycloak if needed
|
* Get token from keycloak if needed
|
||||||
*/
|
*/
|
||||||
export async function getToken() {
|
export async function getToken() {
|
||||||
if (!KC_CLIENT_ID || !KC_SECRET) {
|
if (!KC_ADMIN_PASSWORD || !KC_ADMIN_USERNAME) {
|
||||||
throw new Error("KC_CLIENT_ID and KC_SECRET are required to used this feature.");
|
throw new Error("KC_ADMIN_USERNAME and KC_ADMIN_PASSWORD are required to used this feature.");
|
||||||
}
|
}
|
||||||
|
|
||||||
if (token && !isTokenExpired(token)) return token;
|
if (token && !isTokenExpired(token)) return token;
|
||||||
|
|
||||||
const body = new URLSearchParams();
|
const body = new URLSearchParams();
|
||||||
|
|
||||||
body.append("client_id", KC_CLIENT_ID);
|
body.append("client_id", "admin-cli");
|
||||||
body.append("client_secret", KC_SECRET);
|
body.append("grant_type", "password");
|
||||||
body.append("grant_type", "client_credentials");
|
body.append("username", KC_ADMIN_USERNAME);
|
||||||
|
body.append("password", KC_ADMIN_PASSWORD);
|
||||||
|
|
||||||
const res = await fetch(`${KC_URL}/realms/${KC_REALM}/protocol/openid-connect/token`, {
|
const res = await fetch(`${KC_URL}/realms/master/protocol/openid-connect/token`, {
|
||||||
method: "POST",
|
method: "POST",
|
||||||
body: body,
|
body: body,
|
||||||
}).catch((e) => console.error(e));
|
}).catch((e) => console.error(e));
|
||||||
|
|
@ -74,7 +75,7 @@ export async function createUser(username: string, password: string, opts?: Reco
|
||||||
},
|
},
|
||||||
method: "POST",
|
method: "POST",
|
||||||
body: JSON.stringify({
|
body: JSON.stringify({
|
||||||
enabled: true,
|
enabled: opts?.enabled !== undefined ? opts.enabled : true,
|
||||||
credentials: [{ type: "password", value: password }],
|
credentials: [{ type: "password", value: password }],
|
||||||
username,
|
username,
|
||||||
...opts,
|
...opts,
|
||||||
|
|
@ -109,7 +110,7 @@ export async function editUser(userId: string, opts: Record<string, any>) {
|
||||||
},
|
},
|
||||||
method: "PUT",
|
method: "PUT",
|
||||||
body: JSON.stringify({
|
body: JSON.stringify({
|
||||||
enabled: true,
|
enabled: opts?.enabled !== undefined ? opts.enabled : true,
|
||||||
credentials: (password && [{ type: "password", value: opts?.password }]) || undefined,
|
credentials: (password && [{ type: "password", value: opts?.password }]) || undefined,
|
||||||
...rest,
|
...rest,
|
||||||
}),
|
}),
|
||||||
|
|
|
||||||
|
|
@ -9,3 +9,52 @@ const minio = new Client({
|
||||||
});
|
});
|
||||||
|
|
||||||
export default minio;
|
export default minio;
|
||||||
|
|
||||||
|
// minio typescript does not support include version
|
||||||
|
type BucketItemWithVersion = {
|
||||||
|
name: string;
|
||||||
|
lastModified: string;
|
||||||
|
etag: string;
|
||||||
|
size: number;
|
||||||
|
versionId: string;
|
||||||
|
isLatest: boolean;
|
||||||
|
isDeleteMarker: boolean;
|
||||||
|
};
|
||||||
|
|
||||||
|
export async function listObjectVersion(bucket: string, obj: string) {
|
||||||
|
return await new Promise<BucketItemWithVersion[]>((resolve, reject) => {
|
||||||
|
const data: BucketItemWithVersion[] = [];
|
||||||
|
// @ts-ignore
|
||||||
|
let stream = minio.listObjects(bucket, obj, true, {
|
||||||
|
IncludeVersion: true, // type error (ts not support) - expected 3 args but got 4
|
||||||
|
});
|
||||||
|
stream.on("data", (obj) => data.push(obj as unknown as BucketItemWithVersion));
|
||||||
|
stream.on("error", (err) => reject(err));
|
||||||
|
stream.on("end", () => resolve(data));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function deleteObjectAllVersion(bucket: string, obj: string) {
|
||||||
|
const item = await listObjectVersion(bucket, obj);
|
||||||
|
|
||||||
|
return await new Promise((resolve, reject) => {
|
||||||
|
minio.removeObjects(
|
||||||
|
bucket,
|
||||||
|
// @ts-ignore
|
||||||
|
item.map(({ name, versionId }) => ({ name, versionId })), // type error (ts not support) - expected "string[]"
|
||||||
|
(e) => (e && reject(e)) || resolve(true),
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function presignedGetObjectIfExist(bucket: string, obj: string, exp?: number) {
|
||||||
|
if (
|
||||||
|
await minio.statObject(bucket, obj).catch((e) => {
|
||||||
|
if (e.code === "NotFound") return false;
|
||||||
|
throw new Error("Object storage error.");
|
||||||
|
})
|
||||||
|
) {
|
||||||
|
return await minio.presignedGetObject(bucket, obj, exp);
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -29,10 +29,11 @@
|
||||||
{ "name": "Employee Checkup" },
|
{ "name": "Employee Checkup" },
|
||||||
{ "name": "Employee Work" },
|
{ "name": "Employee Work" },
|
||||||
{ "name": "Employee Other Info" },
|
{ "name": "Employee Other Info" },
|
||||||
{ "name": "Service" },
|
{ "name": "Product Group" },
|
||||||
{ "name": "Work" },
|
|
||||||
{ "name": "Product Type" },
|
{ "name": "Product Type" },
|
||||||
{ "name": "Product Group" }
|
{ "name": "Product" },
|
||||||
|
{ "name": "Work" },
|
||||||
|
{ "name": "Service" }
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue