235 lines
7.7 KiB
TypeScript
235 lines
7.7 KiB
TypeScript
import { parse } from "csv-parse";
|
|
import fs from "fs";
|
|
import HttpStatus from "./src/interfaces/http-status";
|
|
import HttpError from "./src/interfaces/http-error";
|
|
import { PrismaClient } from "@prisma/client";
|
|
import { CustomerType, Status } from "@prisma/client"; // enum จาก Prisma
|
|
|
|
const prisma = new PrismaClient({
|
|
datasourceUrl: process.env.TEST_DATABASE_URL || process.env.DATABASE_URL,
|
|
});
|
|
|
|
type CsvRow = {
|
|
workerId: string;
|
|
fullName: string;
|
|
firstName: string;
|
|
gender: string;
|
|
dateOfBirth: string;
|
|
age: string;
|
|
ppBirthCountry: string;
|
|
nationality: string;
|
|
ppNumber: string;
|
|
ppIssuePlace: string;
|
|
ppIssueCountry: string;
|
|
ppIssueDate: string;
|
|
ppExpireDate: string;
|
|
visaNumber: string;
|
|
visaIssuePlace: string;
|
|
visaType: string;
|
|
visaIssueDate: string;
|
|
visaExpireDate: string;
|
|
visaArrivalTM: string;
|
|
visaArrivalAt: string;
|
|
visaArrivalTMNo: string;
|
|
visaReportDate: string;
|
|
identityNo: string;
|
|
workPermitNo: string;
|
|
nameListNo: string;
|
|
nrcNo: string;
|
|
nameOfId: string;
|
|
fatherFirstNameEN: string;
|
|
jobNo: string;
|
|
companyFullName: string;
|
|
companyFullNameEN: string;
|
|
address: string;
|
|
jobDescription: string;
|
|
contactName: string;
|
|
contactTel: string;
|
|
};
|
|
|
|
type UniqueCompany = {
|
|
companyFullName: string;
|
|
address: string;
|
|
jobDescription: string;
|
|
};
|
|
|
|
async function importEmployeeCsv(filePath: string) {
|
|
const parser = fs.createReadStream(filePath).pipe(parse({ columns: true, trim: true }));
|
|
|
|
const rows: CsvRow[] = [];
|
|
for await (const row of parser) {
|
|
rows.push(row as CsvRow);
|
|
}
|
|
|
|
const uniqueMap = new Map<string, UniqueCompany>();
|
|
|
|
for (const row of rows) {
|
|
const key = `${row.companyFullName}|${row.address}|${row.jobDescription}`;
|
|
if (!uniqueMap.has(key)) {
|
|
uniqueMap.set(key, {
|
|
companyFullName: row.companyFullName,
|
|
address: row.address,
|
|
jobDescription: row.jobDescription,
|
|
});
|
|
}
|
|
}
|
|
|
|
const uniqueCompanies: UniqueCompany[] = Array.from(uniqueMap.values());
|
|
console.log("Unique Companies:", uniqueCompanies);
|
|
|
|
const getCustomerBranches = await prisma.customerBranch.findMany({
|
|
where: {
|
|
OR: uniqueCompanies.map((c) => ({
|
|
AND: [{ authorizedName: c.companyFullName }],
|
|
})),
|
|
},
|
|
});
|
|
|
|
if (getCustomerBranches.length > 0) {
|
|
// map สำหรับ lookup customerBranchId
|
|
const branchMap = new Map<string, (typeof getCustomerBranches)[0]>();
|
|
getCustomerBranches.forEach((branch) => {
|
|
branchMap.set(branch.authorizedName || "", branch);
|
|
});
|
|
|
|
await prisma.$transaction(async (tx) => {
|
|
for (const row of rows) {
|
|
const branch = branchMap.get(row.companyFullName);
|
|
if (!branch) continue;
|
|
|
|
const gender = row.gender?.toLowerCase();
|
|
|
|
const addressParts = parseThaiAddress(row.address);
|
|
|
|
// โหลดทั้งหมดจาก DB
|
|
const provinces = await prisma.province.findMany();
|
|
const districts = await prisma.district.findMany();
|
|
const subDistricts = await prisma.subDistrict.findMany();
|
|
|
|
// สร้าง map สำหรับ lookup
|
|
const provinceMap = new Map<string, string>();
|
|
provinces.forEach((p) => provinceMap.set(p.name, p.id));
|
|
|
|
const districtMap = new Map<string, string>();
|
|
districts.forEach((d) => districtMap.set(d.name, d.id));
|
|
|
|
const subDistrictMap = new Map<string, string>();
|
|
subDistricts.forEach((s) => subDistrictMap.set(s.name, s.id));
|
|
|
|
const provinceId = provinceMap.get(addressParts.province ?? "") ?? null;
|
|
const districtId = districtMap.get(addressParts.district ?? "") ?? null;
|
|
const subDistrictId = subDistrictMap.get(addressParts.subDistrict ?? "") ?? null;
|
|
|
|
const last = await tx.runningNo.upsert({
|
|
where: {
|
|
key: `EMPLOYEE_${branch.id}-${`${new Date().getFullYear()}`.slice(-2).padStart(2, "0")}`,
|
|
},
|
|
create: {
|
|
key: `EMPLOYEE_${branch.id}-${`${new Date().getFullYear()}`.slice(-2).padStart(2, "0")}`,
|
|
value: 1,
|
|
},
|
|
update: { value: { increment: 1 } },
|
|
});
|
|
|
|
// 1. Employee
|
|
const employee = await tx.employee.create({
|
|
data: {
|
|
code: `${branch.code}-${`${new Date().getFullYear()}`.slice(-2).padStart(2, "0")}${`${last.value}`.padStart(7, "0")}`,
|
|
customerBranchId: branch.id,
|
|
firstName: row.firstName,
|
|
firstNameEN: "",
|
|
gender: gender,
|
|
dateOfBirth: row.dateOfBirth ? new Date(row.dateOfBirth) : null,
|
|
nationality: row.nationality,
|
|
nrcNo: row.nrcNo,
|
|
workerStatus: null,
|
|
status: Status.CREATED,
|
|
|
|
address: addressParts.houseNo,
|
|
street: addressParts.street,
|
|
soi: addressParts.soi,
|
|
moo: addressParts.moo,
|
|
provinceId: provinceId,
|
|
districtId: districtId,
|
|
subDistrictId: subDistrictId,
|
|
},
|
|
});
|
|
|
|
// 2. EmployeeOtherInfo
|
|
await tx.employeeOtherInfo.create({
|
|
data: {
|
|
employeeId: employee.id,
|
|
citizenId: row.identityNo,
|
|
fatherFirstNameEN: row.fatherFirstNameEN,
|
|
},
|
|
});
|
|
|
|
// 3. EmployeePassport
|
|
await tx.employeePassport.create({
|
|
data: {
|
|
employeeId: employee.id,
|
|
number: row.ppNumber,
|
|
type: "PASSPORT",
|
|
issueDate: row.ppIssueDate ? new Date(row.ppIssueDate) : "",
|
|
expireDate: row.ppExpireDate ? new Date(row.ppExpireDate) : "",
|
|
issueCountry: row.ppIssueCountry,
|
|
issuePlace: row.ppIssuePlace,
|
|
firstName: row.firstName,
|
|
firstNameEN: "",
|
|
gender: gender,
|
|
birthCountry: row.ppBirthCountry,
|
|
birthDate: row.dateOfBirth,
|
|
nationality: row.nationality,
|
|
workerStatus: "normal",
|
|
},
|
|
});
|
|
|
|
// 4. EmployeeVisa
|
|
await tx.employeeVisa.create({
|
|
data: {
|
|
employeeId: employee.id,
|
|
number: row.visaNumber,
|
|
type: row.visaType,
|
|
entryCount: 1,
|
|
issuePlace: row.visaIssuePlace,
|
|
issueCountry: "",
|
|
issueDate: row.visaIssueDate ? new Date(row.visaIssueDate) : "",
|
|
expireDate: row.visaExpireDate ? new Date(row.visaExpireDate) : "",
|
|
reportDate: row.visaReportDate ? new Date(row.visaReportDate) : undefined,
|
|
arrivalTM: row.visaArrivalTM,
|
|
arrivalTMNo: row.visaArrivalTMNo,
|
|
arrivalAt: row.visaArrivalAt,
|
|
},
|
|
});
|
|
}
|
|
});
|
|
|
|
console.log("Import completed ✅");
|
|
} else {
|
|
console.warn("No customer branches found for the CSV companies.");
|
|
}
|
|
}
|
|
|
|
function parseThaiAddress(address: string) {
|
|
const regex =
|
|
/^(?<houseNo>[\d\-\/]+)?\s*(?:หมู่\s*(?<moo>\d+))?\s*(?:ซอย\s*(?<soi>[^ ]+))?\s*(?<street>.*?)\s*(?:ตำบล|แขวง)?\s*(?<subDistrict>[^ ]+)?\s*(?:อำเภอ|เขต)?\s*(?<district>[^ ]+)?\s*จังหวัด\s*(?<province>[^ ]+)?\s*(?<postalCode>\d{5})?$/;
|
|
const match = address.match(regex);
|
|
if (!match || !match.groups) return {};
|
|
return {
|
|
houseNo: match.groups.houseNo?.trim(),
|
|
moo: match.groups.moo?.trim(),
|
|
soi: match.groups.soi?.trim(),
|
|
street: match.groups.street?.trim(),
|
|
subDistrict: match.groups.subDistrict?.trim(),
|
|
district: match.groups.district?.trim(),
|
|
province: match.groups.province?.trim(),
|
|
postalCode: match.groups.postalCode?.trim(),
|
|
};
|
|
}
|
|
|
|
importEmployeeCsv("/home/hamu/Downloads/JWS - import employee template example 3.csv")
|
|
.then(() => console.log("Import finished ✅"))
|
|
.catch((err) => {
|
|
console.error("Import failed:", err);
|
|
process.exit(1);
|
|
});
|