refactor: employee

This commit is contained in:
Methapon Metanipat 2024-09-13 17:39:12 +07:00
parent 77739da154
commit c51a403f2a
7 changed files with 637 additions and 277 deletions

View file

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

View file

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

View file

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

View file

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

View file

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