diff --git a/prisma/migrations/20240913103903_refactor_employee/migration.sql b/prisma/migrations/20240913103903_refactor_employee/migration.sql new file mode 100644 index 0000000..c1b0a00 --- /dev/null +++ b/prisma/migrations/20240913103903_refactor_employee/migration.sql @@ -0,0 +1,99 @@ +/* + Warnings: + + - You are about to drop the column `entryDate` on the `Employee` table. All the data in the column will be lost. + - You are about to drop the column `passportExpiryDate` on the `Employee` table. All the data in the column will be lost. + - You are about to drop the column `passportIssueDate` on the `Employee` table. All the data in the column will be lost. + - You are about to drop the column `passportIssuingCountry` on the `Employee` table. All the data in the column will be lost. + - You are about to drop the column `passportIssuingPlace` on the `Employee` table. All the data in the column will be lost. + - You are about to drop the column `passportNumber` on the `Employee` table. All the data in the column will be lost. + - You are about to drop the column `passportType` on the `Employee` table. All the data in the column will be lost. + - You are about to drop the column `previousPassportReference` on the `Employee` table. All the data in the column will be lost. + - You are about to drop the column `tm6Number` on the `Employee` table. All the data in the column will be lost. + - You are about to drop the column `visaExpiryDate` on the `Employee` table. All the data in the column will be lost. + - You are about to drop the column `visaIssueDate` on the `Employee` table. All the data in the column will be lost. + - You are about to drop the column `visaIssuingPlace` on the `Employee` table. All the data in the column will be lost. + - You are about to drop the column `visaNumber` on the `Employee` table. All the data in the column will be lost. + - You are about to drop the column `visaStayUntilDate` on the `Employee` table. All the data in the column will be lost. + - You are about to drop the column `visaType` on the `Employee` table. All the data in the column will be lost. + +*/ +-- AlterTable +ALTER TABLE "Employee" DROP COLUMN "entryDate", +DROP COLUMN "passportExpiryDate", +DROP COLUMN "passportIssueDate", +DROP COLUMN "passportIssuingCountry", +DROP COLUMN "passportIssuingPlace", +DROP COLUMN "passportNumber", +DROP COLUMN "passportType", +DROP COLUMN "previousPassportReference", +DROP COLUMN "tm6Number", +DROP COLUMN "visaExpiryDate", +DROP COLUMN "visaIssueDate", +DROP COLUMN "visaIssuingPlace", +DROP COLUMN "visaNumber", +DROP COLUMN "visaStayUntilDate", +DROP COLUMN "visaType", +ADD COLUMN "workerType" TEXT; + +-- CreateTable +CREATE TABLE "EmployeePassport" ( + "id" TEXT NOT NULL, + "number" TEXT NOT NULL, + "type" TEXT NOT NULL, + "issueDate" DATE NOT NULL, + "expireDate" DATE NOT NULL, + "issueCountry" TEXT NOT NULL, + "issuePlace" TEXT NOT NULL, + "previousPassportRef" TEXT, + "employeeId" TEXT NOT NULL, + "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, + "updatedAt" TIMESTAMP(3) NOT NULL, + + CONSTRAINT "EmployeePassport_pkey" PRIMARY KEY ("id") +); + +-- CreateTable +CREATE TABLE "EmployeeVisa" ( + "id" TEXT NOT NULL, + "number" TEXT NOT NULL, + "type" TEXT NOT NULL, + "entryCount" INTEGER NOT NULL, + "issueCountry" TEXT NOT NULL, + "issuePlace" TEXT NOT NULL, + "issueDate" DATE NOT NULL, + "expireDate" DATE NOT NULL, + "mrz" TEXT, + "remark" TEXT, + "employeeId" TEXT NOT NULL, + "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, + "updatedAt" TIMESTAMP(3) NOT NULL, + + CONSTRAINT "EmployeeVisa_pkey" PRIMARY KEY ("id") +); + +-- CreateTable +CREATE TABLE "EmployeeInCountryNotice" ( + "id" TEXT NOT NULL, + "noticeNumber" TEXT NOT NULL, + "noticeDate" TEXT NOT NULL, + "nextNoticeDate" DATE NOT NULL, + "tmNumber" TEXT NOT NULL, + "entryDate" DATE NOT NULL, + "travelBy" TEXT NOT NULL, + "travelFrom" TEXT NOT NULL, + "employeeId" TEXT NOT NULL, + "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, + "updatedAt" TIMESTAMP(3) NOT NULL, + + CONSTRAINT "EmployeeInCountryNotice_pkey" PRIMARY KEY ("id") +); + +-- AddForeignKey +ALTER TABLE "EmployeePassport" ADD CONSTRAINT "EmployeePassport_employeeId_fkey" FOREIGN KEY ("employeeId") REFERENCES "Employee"("id") ON DELETE RESTRICT ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "EmployeeVisa" ADD CONSTRAINT "EmployeeVisa_employeeId_fkey" FOREIGN KEY ("employeeId") REFERENCES "Employee"("id") ON DELETE RESTRICT ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "EmployeeInCountryNotice" ADD CONSTRAINT "EmployeeInCountryNotice_employeeId_fkey" FOREIGN KEY ("employeeId") REFERENCES "Employee"("id") ON DELETE RESTRICT ON UPDATE CASCADE; diff --git a/prisma/schema.prisma b/prisma/schema.prisma index 87147f2..ab42f85 100644 --- a/prisma/schema.prisma +++ b/prisma/schema.prisma @@ -552,23 +552,8 @@ model Employee { subDistrict SubDistrict? @relation(fields: [subDistrictId], references: [id], onDelete: SetNull) subDistrictId String? - passportType String - passportNumber String - passportIssueDate DateTime @db.Date - passportExpiryDate DateTime @db.Date - passportIssuingCountry String - passportIssuingPlace String - previousPassportReference String? - - visaType String? - visaNumber String? - visaIssueDate DateTime? @db.Date - visaExpiryDate DateTime? @db.Date - visaIssuingPlace String? - visaStayUntilDate DateTime? @db.Date - tm6Number String? - entryDate DateTime? @db.Date - workerStatus String? + workerType String? + workerStatus String? customerBranch CustomerBranch @relation(fields: [customerBranchId], references: [id], onDelete: Cascade) customerBranchId String @@ -584,9 +569,12 @@ model Employee { updatedBy User? @relation(name: "EmployeeUpdatedByUser", fields: [updatedByUserId], references: [id], onDelete: SetNull) updatedByUserId String? - employeeCheckup EmployeeCheckup[] - employeeWork EmployeeWork[] - employeeOtherInfo EmployeeOtherInfo? + employeePassport EmployeePassport[] + employeeVisa EmployeeVisa[] + employeeInCountryNotice EmployeeInCountryNotice[] + employeeCheckup EmployeeCheckup[] + employeeWork EmployeeWork[] + employeeOtherInfo EmployeeOtherInfo? editHistory EmployeeHistory[] quotationWorker QuotationWorker[] @@ -606,6 +594,63 @@ model EmployeeHistory { master Employee @relation(fields: [masterId], references: [id], onDelete: Cascade) } +model EmployeePassport { + id String @id @default(cuid()) + + number String + type String + issueDate DateTime @db.Date + expireDate DateTime @db.Date + issueCountry String + issuePlace String + previousPassportRef String? + + employee Employee @relation(fields: [employeeId], references: [id]) + employeeId String + + createdAt DateTime @default(now()) + updatedAt DateTime @updatedAt +} + +model EmployeeVisa { + id String @id @default(cuid()) + + number String + type String + entryCount Int + issueCountry String + issuePlace String + issueDate DateTime @db.Date + expireDate DateTime @db.Date + mrz String? + remark String? + + employee Employee @relation(fields: [employeeId], references: [id]) + employeeId String + + createdAt DateTime @default(now()) + updatedAt DateTime @updatedAt +} + +model EmployeeInCountryNotice { + id String @id @default(cuid()) + + noticeNumber String + noticeDate String + nextNoticeDate DateTime @db.Date + tmNumber String + + entryDate DateTime @db.Date + travelBy String + travelFrom String + + employee Employee @relation(fields: [employeeId], references: [id]) + employeeId String + + createdAt DateTime @default(now()) + updatedAt DateTime @updatedAt +} + model EmployeeCheckup { id String @id @default(cuid()) diff --git a/src/controllers/03-employee-controller.ts b/src/controllers/03-employee-controller.ts index 144a62d..fd86306 100644 --- a/src/controllers/03-employee-controller.ts +++ b/src/controllers/03-employee-controller.ts @@ -26,7 +26,7 @@ import { } from "../services/permission"; import { connectOrDisconnect, connectOrNot, whereAddressQuery } from "../utils/relation"; import { notFoundError, relationError } from "../utils/error"; -import { deleteFile, fileLocation, getFile, listFile, setFile } from "../utils/minio"; +import { deleteFile, deleteFolder, fileLocation, getFile, listFile, setFile } from "../utils/minio"; if (!process.env.MINIO_BUCKET) { throw Error("Require MinIO bucket."); @@ -77,69 +77,13 @@ type EmployeeCreate = { street?: string | null; streetEN?: string | null; - passportType: 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; districtId?: string | null; provinceId?: string | null; + + workerType?: string | null; + workerStatus?: string | null; selectedImage?: 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 = { @@ -169,71 +113,13 @@ type EmployeeUpdate = { street?: string | null; streetEN?: string | null; - passportType?: 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; + workerType?: string | null; workerStatus?: string | null; selectedImage?: string | null; subDistrictId?: string | null; districtId?: 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/v1/employee") @@ -306,7 +192,6 @@ export class EmployeeController extends Controller { { firstNameEN: { contains: query } }, { lastName: { contains: query } }, { lastNameEN: { contains: query } }, - { passportNumber: { contains: query } }, ...whereAddressQuery(query), ], AND: { @@ -403,35 +288,7 @@ export class EmployeeController extends Controller { await permissionCheck(req.user, customerBranch.customer.registeredBranch); - const { - provinceId, - districtId, - subDistrictId, - customerBranchId, - employeeWork, - employeeCheckup, - employeeOtherInfo, - ...rest - } = body; - - const listProvinceId = employeeCheckup?.reduce((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 (listProvince.length !== listProvinceId.length) { - throw new HttpError( - HttpStatus.BAD_REQUEST, - "Some province cannot be found.", - "someProvinceNotFound", - ); - } - } + const { provinceId, districtId, subDistrictId, customerBranchId, ...rest } = body; const record = await prisma.$transaction( async (tx) => { @@ -465,23 +322,6 @@ export class EmployeeController extends Controller { ...rest, statusOrder: +(rest.status === "INACTIVE"), code: `${customerBranch.code}-${`${new Date().getFullYear()}`.slice(-2).padStart(2, "0")}${`${last.value}`.padStart(7, "0")}`, - employeeWork: { - createMany: { - data: employeeWork || [], - }, - }, - employeeCheckup: { - createMany: { - data: - employeeCheckup?.map((v) => ({ - ...v, - provinceId: !!v.provinceId ? null : v.provinceId, - })) || [], - }, - }, - employeeOtherInfo: { - create: employeeOtherInfo, - }, province: connectOrNot(provinceId), district: connectOrNot(districtId), subDistrict: connectOrNot(subDistrictId), @@ -568,35 +408,7 @@ export class EmployeeController extends Controller { await permissionCheck(req.user, customerBranch.customer.registeredBranch); } - const { - provinceId, - districtId, - subDistrictId, - customerBranchId, - employeeWork, - employeeCheckup, - employeeOtherInfo, - ...rest - } = body; - - const listProvinceId = employeeCheckup?.reduce((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 (listProvince.length !== listProvinceId.length) { - throw new HttpError( - HttpStatus.BAD_REQUEST, - "Some province cannot be found.", - "someProvinceNotFound", - ); - } - } + const { provinceId, districtId, subDistrictId, customerBranchId, ...rest } = body; const record = await prisma.$transaction(async (tx) => { let code: string | undefined; @@ -640,53 +452,6 @@ export class EmployeeController extends Controller { statusOrder: +(rest.status === "INACTIVE"), code, customerBranch: connectOrNot(customerBranchId), - 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, - createdByUserId: req.user.sub, - updatedByUserId: req.user.sub, - id: undefined, - }, - update: { - ...v, - updatedByUserId: req.user.sub, - }, - })), - } - : 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, - createdByUserId: req.user.sub, - updatedByUserId: req.user.sub, - id: undefined, - }, - update: { - ...v, - updatedByUserId: req.user.sub, - }, - })), - } - : undefined, - employeeOtherInfo: employeeOtherInfo ? { update: employeeOtherInfo } : undefined, province: connectOrDisconnect(provinceId), district: connectOrDisconnect(districtId), subDistrict: connectOrDisconnect(subDistrictId), @@ -698,13 +463,7 @@ export class EmployeeController extends Controller { const historyEntries: { field: string; valueBefore: string; valueAfter: string }[] = []; - 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; - + for (const field of Object.keys(body) as (keyof typeof body)[]) { let valueBefore = employee[field]; let valueAfter = body[field]; @@ -757,6 +516,14 @@ export class EmployeeController extends Controller { throw new HttpError(HttpStatus.FORBIDDEN, "Employee is in used.", "employeeInUsed"); } + await Promise.all([ + deleteFolder(fileLocation.employee.img(employeeId)), + deleteFolder(fileLocation.employee.attachment(employeeId)), + deleteFolder(fileLocation.employee.passport(employeeId)), + deleteFolder(fileLocation.employee.visa(employeeId)), + deleteFolder(fileLocation.employee.inCountryNotice(employeeId)), + ]); + return await prisma.employee.delete({ include: { createdBy: true, @@ -876,4 +643,111 @@ export class EmployeeFileController extends Controller { await this.checkPermission(req.user, employeeId); return await deleteFile(fileLocation.employee.attachment(employeeId, name)); } + + @Get("file-passport") + @Security("keycloak") + async listPassport(@Request() req: RequestWithUser, @Path() employeeId: string) { + await this.checkPermission(req.user, employeeId); + return await listFile(fileLocation.employee.passport(employeeId)); + } + + @Get("file-passport/{passportId}") + @Security("keycloak") + async getPassport(@Path() employeeId: string, @Path() passportId: string) { + return await getFile(fileLocation.employee.passport(employeeId, passportId)); + } + + @Put("file-passport/{passportId}") + @Security("keycloak") + async putPassport( + @Request() req: RequestWithUser, + @Path() employeeId: string, + @Path() passportId: string, + ) { + await this.checkPermission(req.user, employeeId); + return req.res?.redirect(await setFile(fileLocation.employee.passport(employeeId, passportId))); + } + + @Delete("file-passport/{passportId}") + @Security("keycloak") + async delPassport( + @Request() req: RequestWithUser, + @Path() employeeId: string, + @Path() passportId: string, + ) { + await this.checkPermission(req.user, employeeId); + return await deleteFile(fileLocation.employee.passport(employeeId, passportId)); + } + + @Get("file-visa") + @Security("keycloak") + async listVisa(@Request() req: RequestWithUser, @Path() employeeId: string) { + await this.checkPermission(req.user, employeeId); + return await listFile(fileLocation.employee.visa(employeeId)); + } + + @Get("file-visa/{visaId}") + @Security("keycloak") + async getVisa(@Path() employeeId: string, @Path() visaId: string) { + return await getFile(fileLocation.employee.visa(employeeId, visaId)); + } + + @Put("file-visa/{visaId}") + @Security("keycloak") + async putVisa( + @Request() req: RequestWithUser, + @Path() employeeId: string, + @Path() visaId: string, + ) { + await this.checkPermission(req.user, employeeId); + return req.res?.redirect(await setFile(fileLocation.employee.visa(employeeId, visaId))); + } + + @Delete("file-visa/{visaId}") + @Security("keycloak") + async delVisa( + @Request() req: RequestWithUser, + @Path() employeeId: string, + @Path() visaId: string, + ) { + await this.checkPermission(req.user, employeeId); + return await deleteFile(fileLocation.employee.visa(employeeId, visaId)); + } + + @Get("file-in-country-notice") + @Security("keycloak") + async listNotice(@Request() req: RequestWithUser, @Path() employeeId: string) { + await this.checkPermission(req.user, employeeId); + return await listFile(fileLocation.employee.inCountryNotice(employeeId)); + } + + @Get("file-in-country-notice/{noticeId}") + @Security("keycloak") + async getNotice(@Path() employeeId: string, @Path() noticeId: string) { + return await getFile(fileLocation.employee.inCountryNotice(employeeId, noticeId)); + } + + @Put("file-in-country-notice/{noticeId}") + @Security("keycloak") + async putNotice( + @Request() req: RequestWithUser, + @Path() employeeId: string, + @Path() noticeId: string, + ) { + await this.checkPermission(req.user, employeeId); + return req.res?.redirect( + await setFile(fileLocation.employee.inCountryNotice(employeeId, noticeId)), + ); + } + + @Delete("file-in-country-notice/{noticeId}") + @Security("keycloak") + async delNotice( + @Request() req: RequestWithUser, + @Path() employeeId: string, + @Path() noticeId: string, + ) { + await this.checkPermission(req.user, employeeId); + return await deleteFile(fileLocation.employee.inCountryNotice(employeeId, noticeId)); + } } diff --git a/src/controllers/03-employee-in-country-notice-controller.ts b/src/controllers/03-employee-in-country-notice-controller.ts new file mode 100644 index 0000000..e9f2a9d --- /dev/null +++ b/src/controllers/03-employee-in-country-notice-controller.ts @@ -0,0 +1,116 @@ +import { + Body, + Controller, + Delete, + Get, + Middlewares, + Path, + Post, + Put, + Route, + Security, + Tags, +} from "tsoa"; +import { RequestWithUser } from "../interfaces/user"; +import prisma from "../db"; +import HttpStatus from "../interfaces/http-status"; +import { permissionCheck } from "../middlewares/employee"; +import { notFoundError } from "../utils/error"; + +const MANAGE_ROLES = [ + "system", + "head_of_admin", + "admin", + "head_of_account", + "account", + "head_of_sale", +]; + +function globalAllow(user: RequestWithUser["user"]) { + const allowList = ["system", "head_of_admin", "admin", "head_of_account", "head_of_sale"]; + return allowList.some((v) => user.roles?.includes(v)); +} + +type EmployeeInCountryNoticePayload = { + noticeNumber: string; + noticeDate: string; + nextNoticeDate: Date; + tmNumber: string; + entryDate: Date; + travelBy: string; + travelFrom: string; +}; + +@Route("api/v1/employee/{employeeId}/work") +@Tags("Employee Work") +@Middlewares(permissionCheck(globalAllow)) +export class EmployeeInCountryNoticeController extends Controller { + @Get() + @Security("keycloak") + async list(@Path() employeeId: string) { + return prisma.employeeInCountryNotice.findMany({ + orderBy: { createdAt: "asc" }, + where: { employeeId }, + }); + } + + @Get("{passportId}") + @Security("keycloak") + async getById(@Path() employeeId: string, @Path() passportId: string) { + const record = await prisma.employeeInCountryNotice.findFirst({ + where: { id: passportId, employeeId }, + }); + if (!record) throw notFoundError("Employee Work"); + return record; + } + + @Post() + @Security("keycloak", MANAGE_ROLES) + async create(@Path() employeeId: string, @Body() body: EmployeeInCountryNoticePayload) { + const record = await prisma.employeeInCountryNotice.create({ + data: { + ...body, + employee: { connect: { id: employeeId } }, + }, + }); + + this.setStatus(HttpStatus.CREATED); + + return record; + } + + @Put("{passportId}") + @Security("keycloak", MANAGE_ROLES) + async editById( + @Path() employeeId: string, + @Path() passportId: string, + @Body() body: EmployeeInCountryNoticePayload, + ) { + const work = await prisma.employeeInCountryNotice.findUnique({ + where: { id: passportId, employeeId }, + }); + + if (!work) throw notFoundError("Employee Work"); + + const record = await prisma.employeeInCountryNotice.update({ + where: { id: passportId, employeeId }, + data: { ...body }, + }); + + this.setStatus(HttpStatus.CREATED); + + return record; + } + + @Delete("{passportId}") + @Security("keycloak", MANAGE_ROLES) + async deleteById(@Path() employeeId: string, @Path() passportId: string) { + const record = await prisma.employeeInCountryNotice.findFirst({ + where: { id: passportId, employeeId }, + }); + + if (!record) throw notFoundError("Employee Work"); + + return await prisma.employeeInCountryNotice.delete({ where: { id: passportId, employeeId } }); + } +} diff --git a/src/controllers/03-employee-passport-controller.ts b/src/controllers/03-employee-passport-controller.ts new file mode 100644 index 0000000..daabdd0 --- /dev/null +++ b/src/controllers/03-employee-passport-controller.ts @@ -0,0 +1,116 @@ +import { + Body, + Controller, + Delete, + Get, + Middlewares, + Path, + Post, + Put, + Route, + Security, + Tags, +} from "tsoa"; +import { RequestWithUser } from "../interfaces/user"; +import prisma from "../db"; +import HttpStatus from "../interfaces/http-status"; +import { permissionCheck } from "../middlewares/employee"; +import { notFoundError } from "../utils/error"; + +const MANAGE_ROLES = [ + "system", + "head_of_admin", + "admin", + "head_of_account", + "account", + "head_of_sale", +]; + +function globalAllow(user: RequestWithUser["user"]) { + const allowList = ["system", "head_of_admin", "admin", "head_of_account", "head_of_sale"]; + return allowList.some((v) => user.roles?.includes(v)); +} + +type EmployeePassportPayload = { + number: string; + type: string; + issueDate: Date; + expireDate: Date; + issueCountry: string; + issuePlace: string; + previousPassportRef?: string | null; +}; + +@Route("api/v1/employee/{employeeId}/work") +@Tags("Employee Work") +@Middlewares(permissionCheck(globalAllow)) +export class EmployeePassportController extends Controller { + @Get() + @Security("keycloak") + async list(@Path() employeeId: string) { + return prisma.employeePassport.findMany({ + orderBy: { createdAt: "asc" }, + where: { employeeId }, + }); + } + + @Get("{passportId}") + @Security("keycloak") + async getById(@Path() employeeId: string, @Path() passportId: string) { + const record = await prisma.employeePassport.findFirst({ + where: { id: passportId, employeeId }, + }); + if (!record) throw notFoundError("Employee Work"); + return record; + } + + @Post() + @Security("keycloak", MANAGE_ROLES) + async create(@Path() employeeId: string, @Body() body: EmployeePassportPayload) { + const record = await prisma.employeePassport.create({ + data: { + ...body, + employee: { connect: { id: employeeId } }, + }, + }); + + this.setStatus(HttpStatus.CREATED); + + return record; + } + + @Put("{passportId}") + @Security("keycloak", MANAGE_ROLES) + async editById( + @Path() employeeId: string, + @Path() passportId: string, + @Body() body: EmployeePassportPayload, + ) { + const work = await prisma.employeePassport.findUnique({ + where: { id: passportId, employeeId }, + }); + + if (!work) throw notFoundError("Employee Work"); + + const record = await prisma.employeePassport.update({ + where: { id: passportId, employeeId }, + data: { ...body }, + }); + + this.setStatus(HttpStatus.CREATED); + + return record; + } + + @Delete("{passportId}") + @Security("keycloak", MANAGE_ROLES) + async deleteById(@Path() employeeId: string, @Path() passportId: string) { + const record = await prisma.employeePassport.findFirst({ + where: { id: passportId, employeeId }, + }); + + if (!record) throw notFoundError("Employee Work"); + + return await prisma.employeePassport.delete({ where: { id: passportId, employeeId } }); + } +} diff --git a/src/controllers/03-employee-visa-controller.ts b/src/controllers/03-employee-visa-controller.ts new file mode 100644 index 0000000..ef0d528 --- /dev/null +++ b/src/controllers/03-employee-visa-controller.ts @@ -0,0 +1,118 @@ +import { + Body, + Controller, + Delete, + Get, + Middlewares, + Path, + Post, + Put, + Route, + Security, + Tags, +} from "tsoa"; +import { RequestWithUser } from "../interfaces/user"; +import prisma from "../db"; +import HttpStatus from "../interfaces/http-status"; +import { permissionCheck } from "../middlewares/employee"; +import { notFoundError } from "../utils/error"; + +const MANAGE_ROLES = [ + "system", + "head_of_admin", + "admin", + "head_of_account", + "account", + "head_of_sale", +]; + +function globalAllow(user: RequestWithUser["user"]) { + const allowList = ["system", "head_of_admin", "admin", "head_of_account", "head_of_sale"]; + return allowList.some((v) => user.roles?.includes(v)); +} + +type EmployeeVisaPayload = { + number: string; + type: string; + entryCount: number; + issueCountry: string; + issuePlace: string; + issueDate: Date; + expireDate: Date; + mrz?: string; + remark?: string; +}; + +@Route("api/v1/employee/{employeeId}/work") +@Tags("Employee Work") +@Middlewares(permissionCheck(globalAllow)) +export class EmployeeVisaController extends Controller { + @Get() + @Security("keycloak") + async list(@Path() employeeId: string) { + return prisma.employeeVisa.findMany({ + orderBy: { createdAt: "asc" }, + where: { employeeId }, + }); + } + + @Get("{passportId}") + @Security("keycloak") + async getById(@Path() employeeId: string, @Path() passportId: string) { + const record = await prisma.employeeVisa.findFirst({ + where: { id: passportId, employeeId }, + }); + if (!record) throw notFoundError("Employee Work"); + return record; + } + + @Post() + @Security("keycloak", MANAGE_ROLES) + async create(@Path() employeeId: string, @Body() body: EmployeeVisaPayload) { + const record = await prisma.employeeVisa.create({ + data: { + ...body, + employee: { connect: { id: employeeId } }, + }, + }); + + this.setStatus(HttpStatus.CREATED); + + return record; + } + + @Put("{passportId}") + @Security("keycloak", MANAGE_ROLES) + async editById( + @Path() employeeId: string, + @Path() passportId: string, + @Body() body: EmployeeVisaPayload, + ) { + const work = await prisma.employeeVisa.findUnique({ + where: { id: passportId, employeeId }, + }); + + if (!work) throw notFoundError("Employee Work"); + + const record = await prisma.employeeVisa.update({ + where: { id: passportId, employeeId }, + data: { ...body }, + }); + + this.setStatus(HttpStatus.CREATED); + + return record; + } + + @Delete("{passportId}") + @Security("keycloak", MANAGE_ROLES) + async deleteById(@Path() employeeId: string, @Path() passportId: string) { + const record = await prisma.employeeVisa.findFirst({ + where: { id: passportId, employeeId }, + }); + + if (!record) throw notFoundError("Employee Work"); + + return await prisma.employeeVisa.delete({ where: { id: passportId, employeeId } }); + } +} diff --git a/src/controllers/05-quotation-controller.ts b/src/controllers/05-quotation-controller.ts index 8f130c3..2d1a019 100644 --- a/src/controllers/05-quotation-controller.ts +++ b/src/controllers/05-quotation-controller.ts @@ -113,14 +113,6 @@ type QuotationUpdate = { addressEN: string; address: string; zipCode: string; - - passportType: string; - passportNumber: string; - passportIssueDate: Date; - passportExpiryDate: Date; - passportIssuingCountry: string; - passportIssuingPlace: string; - previousPassportReference?: string; } )[];