jws-backend/src/controllers/00-doc-template-controller.ts

389 lines
15 KiB
TypeScript

import createReport from "docx-templates";
import ThaiBahtText from "thai-baht-text";
import { District, Province, SubDistrict } from "@prisma/client";
import { Readable } from "node:stream";
import { Controller, Get, Path, Query, Route } from "tsoa";
import prisma from "../db";
import { notFoundError } from "../utils/error";
import HttpError from "../interfaces/http-error";
import HttpStatus from "../interfaces/http-status";
import { getFileBuffer, listFile } from "../utils/minio";
import { dateFormat } from "../utils/datetime";
const quotationData = (id: string) =>
prisma.quotation.findFirst({
where: { id, isDebitNote: false },
include: {
registeredBranch: {
include: {
province: true,
district: true,
subDistrict: true,
headOffice: {
include: {
province: true,
district: true,
subDistrict: true,
},
},
},
},
customerBranch: {
include: {
customer: true,
province: true,
district: true,
subDistrict: true,
},
},
worker: {
include: {
employee: {
include: {
province: true,
district: true,
subDistrict: true,
employeePassport: {
orderBy: { expireDate: "desc" },
},
employeeWork: true,
},
},
},
},
productServiceList: {
include: {
work: true,
product: true,
service: true,
},
},
},
});
@Route("api/v1/doc-template")
export class DocTemplateController extends Controller {
@Get()
async getTemplate() {
return await listFile(`doc-template/`);
}
@Get("{documentTemplate}")
async getDocument(
@Path() documentTemplate: string,
@Query() data: string,
@Query() dataId: string,
@Query() dataOnly?: boolean,
): Promise<Readable | Record<string, any>> {
let record: Record<string, any>;
switch (data) {
case "quotation":
record = await quotationData(dataId).then(async (quotation) =>
replaceEmptyField({
quotation,
customerBranch: quotation?.customerBranch,
registeredBranch: quotation?.registeredBranch,
employee: quotation?.worker.map((item) => item.employee),
employeeCount: {
all: quotation?.worker.length,
male: quotation?.worker.filter((item) => item.employee.gender === "male").length,
female: quotation?.worker.filter((item) => item.employee.gender === "female").length,
},
employmentOffice:
quotation && quotation.customerBranch.districtId
? await prisma.employmentOffice.findFirst({
where: {
OR: [
{
province: {
district: { some: { id: quotation.customerBranch.districtId } },
},
district: { none: {} },
},
{
district: {
some: { districtId: quotation.customerBranch.districtId },
},
},
],
},
orderBy: [{ provinceId: "asc" }, { id: "asc" }],
})
: undefined,
}),
);
break;
default:
throw new HttpError(HttpStatus.BAD_REQUEST, "No data for template", "noDataTemplate");
}
if (!data) throw notFoundError("Data");
if (dataOnly) return record;
const template = await getFileBuffer(`doc-template/${documentTemplate}`);
if (!data) Readable.from(template);
const report = await createReport({
template,
data: record,
additionalJsContext: {
date: (date: string, locale?: string) => dateFormat({ date, locale }),
dateTime: (date: string, locale?: string) => dateFormat({ date, withTime: true, locale }),
dateLong: (date: string, locale?: string) =>
dateFormat({ date, locale, monthStyle: "long" }),
dateLongTime: (date: string, locale?: string) =>
dateFormat({ date, withTime: true, locale, monthStyle: "long" }),
dateTH: (date: string) => dateFormat({ date, locale: "th-TH" }),
dateTimeTH: (date: string) => dateFormat({ date, withTime: true, locale: "th-TH" }),
dateLongTH: (date: string) => dateFormat({ date, locale: "th-TH", monthStyle: "long" }),
dateTimeLongTH: (date: string) =>
dateFormat({ date, withTime: true, locale: "th-TH", monthStyle: "long" }),
dateEN: (date: string) => dateFormat({ date, locale: "en-US" }),
dateTimeEN: (date: string) =>
dateFormat({ date, withTime: true, locale: "en-US", monthStyle: "long" }),
dateLongEN: (date: string) => dateFormat({ date, locale: "en-US" }),
dateTimeLongEN: (date: string) =>
dateFormat({ date, withTime: true, locale: "en-US", monthStyle: "long" }),
address,
addressTH: (addr: FullAddress) => address(addr, "th"),
addressEN: (addr: FullAddress) => address(addr, "en"),
addressFull,
addressFullTH: (addr: FullAddress) => addressFull(addr, "th"),
addressFullEN: (addr: FullAddress) => addressFull(addr, "en"),
gender,
genderTH: (text: string) => gender(text, "th"),
genderEN: (text: string) => gender(text, "en"),
businessType,
businessTypeEN: (text: string) => businessType(text, "en"),
businessTypeTH: (text: string) => businessType(text, "th"),
namePrefix,
namePrefixEN: (text: string) => namePrefix(text, "en"),
namePrefixTH: (text: string) => namePrefix(text, "th"),
jobPosition,
jobPositionEN: (text: string) => jobPosition(text, "en"),
jobPositionTH: (text: string) => jobPosition(text, "th"),
nationality,
nationalityEN: (text: string) => nationality(text, "en"),
nationalityTH: (text: string) => nationality(text, "th"),
thaiBahtText: (input: string | number) => {
ThaiBahtText(typeof input === "string" ? input.replaceAll(",", "") : input);
},
},
}).then(Buffer.from);
return Readable.from(report);
}
}
function replaceEmptyField<T>(data: T): T {
return JSON.parse(JSON.stringify(data).replace(/null|\"\"/g, '"\-"'));
}
type FullAddress = {
address: string;
addressEN: string;
moo?: string;
mooEN?: string;
soi?: string;
soiEN?: string;
street?: string;
streetEN?: string;
province?: Province | null;
district?: District | null;
subDistrict?: SubDistrict | null;
en?: boolean;
};
function address(addr: FullAddress, lang: "th" | "en" = "en") {
let fragments: string[];
switch (lang) {
case "th":
fragments = [`${addr.address},`];
if (addr.moo) fragments.push(`หมู่ ${addr.moo},`);
if (addr.soi) fragments.push(`ซอย ${addr.soi},`);
if (addr.street) fragments.push(`ถนน${addr.street},`);
break;
default:
fragments = [`${addr.addressEN},`];
if (addr.mooEN) fragments.push(`Moo ${addr.mooEN},`);
if (addr.soiEN) fragments.push(`Soi ${addr.soiEN},`);
if (addr.streetEN) fragments.push(`${addr.streetEN} Rd.`);
break;
}
return fragments.join(" ");
}
function addressFull(addr: FullAddress, lang: "th" | "en" = "en") {
let fragments: string[];
switch (lang) {
case "th":
fragments = [`${addr.address},`];
if (addr.moo) fragments.push(`หมู่ ${addr.moo},`);
if (addr.soi) fragments.push(`ซอย ${addr.soi},`);
if (addr.street) fragments.push(`ถนน${addr.street},`);
if (addr.subDistrict) {
fragments.push(`${addr.province?.id === "10" ? "แขวง" : "ตำบล"}${addr.subDistrict.name},`);
}
if (addr.district) {
fragments.push(`${addr.province?.id === "10" ? "เขต" : "อำเภอ"}${addr.district.name},`);
}
if (addr.province) fragments.push(`จังหวัด${addr.province.name},`);
break;
default:
fragments = [`${addr.addressEN},`];
if (addr.mooEN) fragments.push(`Moo ${addr.mooEN},`);
if (addr.soiEN) fragments.push(`Soi ${addr.soiEN},`);
if (addr.streetEN) fragments.push(`${addr.streetEN} Rd.`);
if (addr.subDistrict) {
fragments.push(`${addr.subDistrict.nameEN} sub-district,`);
}
if (addr.district) fragments.push(`${addr.district.nameEN} district,`);
if (addr.province) fragments.push(`${addr.province.nameEN},`);
break;
}
return fragments.join(" ");
}
function gender(text: string, lang: "th" | "en" = "en") {
switch (lang) {
case "th":
return { male: "ชาย", female: "หญิง" }[text] || text;
default:
text.charAt(0).toUpperCase() + text.slice(1);
}
}
function businessType(text: string, lang: "th" | "en" = "en") {
switch (lang) {
case "th":
return (
{
["fisheries"]: "ประมง",
["continuous-fisheries"]: "ต่อเนื่องประมงทะเล",
["agriculture"]: "เกษตรและปศุสัตว์",
["construction"]: "กิจการก่อสร้าง",
["domesticHelper"]: "ผู้รับใช้ในบ้าน",
["continuousAgriculture"]: "กิจการต่อเนื่องการเกษตร",
["continuousButchery"]: "ต่อเนื่องปศุสัตว์โรงฆ่าสัตว์ ชำแหละ",
["recycling"]: "กิจการรีไซเคิล",
["mining"]: "เหมืองแร่/เหมืองหิน",
["metal"]: "จำหน่ายผลิตภัณฑ์โลหะ",
["food"]: "จำหน่ายอาหารและเครื่องดื่ม",
["soilBasedProducts"]: "ผลิตหรือจำหน่ายผลิตภัณฑ์จากดิน",
["constructionMaterials"]: "ผลิตหรือจำหน่ายวัสดุก่อสร้าง",
["stone"]: "แปรรูปหิน",
["cloth"]: "ผลิตหรือจำหน่ายเสื้อผ้าสำเร็จรูป",
["plastic"]: "ผลิตหรือจำหน่ายผลิตภัณฑ์พลาสติก",
["paper"]: "ผลิตหรือจำหน่ายผลิตภัณฑ์กระดาษ",
["electronics"]: "ผลิตหรือจำหน่ายผลิตภัณฑ์อิเล็กทรอนิกส์",
["transport"]: "ขนถ่ายสินค้าทางบก น้ำ คลังสินค้า",
["market"]: "ค้าส่ง ค้าปลีก แผงลอย",
["car"]: "อู่ซ่อมรถ ล้าง อัดฉีด",
["fuel"]: "สถานีบริการน้ำมัน แก้ส เชื้อเพลิง",
["institution"]: "สถานศึกษา มูลนิธิ สมาคม สถานพยาบาล",
["service"]: "การให้บริการต่างๆ",
["coordinator"]: "งานผู้ประสานงานด้านภาษากัมพูชา ลาว หรือเมียนมา",
["seafood"]: "แปรรูปสัตว์น้ำ",
}[text] || text
);
default:
return (
{
["fisheries"]: "Fisheries",
["continuous-fisheries"]: "Continuous fisheries",
["agriculture"]: "Agriculture and livestock",
["construction"]: "Construction business",
["domesticHelper"]: "Domestic helper",
["continuousAgriculture"]: "Continuous agricultural operation",
["continuousButchery"]: "Continuous livestock slaughter and processing ",
["recycling"]: "Recycling business",
["mining"]: "Mining/quarry",
["metal"]: "Metal products distribution",
["food"]: "Food and beverage distribution",
["soilBasedProducts"]: "Manufacture or sell products made from soil",
["constructionMaterials"]: "Manufacture or sell construction materials",
["stone"]: "Stone processing",
["cloth"]: "Manufacture or sell ready-to-wear clothing",
["plastic"]: "Manufacture or sell plastic products",
["paper"]: "Manufacture or sell paper products",
["electronics"]: "Manufacture or sell electronic products",
["transport"]: "Transport goods by land, water, and operate warehouses",
["market"]: "Wholesale, retail, floating panels",
["car"]: "Auto repair shop, car wash, and detailing",
["fuel"]: "Gas station, fuel station, and service station",
["institution"]: "Educational institution, foundation, association, hospital",
["service"]: "Various services",
["coordinator"]: "Coordinator for Khmer, Laos, or Myanmar language services",
["seafood"]: "Processing seafood",
}[text] || text
);
}
}
function namePrefix(text: string, lang: "th" | "en" = "en") {
switch (lang) {
case "th":
return { mr: "นาย", mrs: "นาง", miss: "นางสาว" }[text] || text;
default:
text.charAt(0).toUpperCase() + text.slice(1);
}
}
function nationality(text: string, lang: "th" | "en" = "en") {
switch (lang) {
case "th":
return (
{
["THA"]: "ไทย",
["MMR"]: "เมียนมา",
["LAO"]: "ลาว",
["KHM"]: "กัมพูชา",
["VNM"]: "เวียดนาม",
["PHL"]: "ฟิลิปปินส์",
["CHN"]: "จีน",
}[text] || text
);
default:
return (
{
["THA"]: "Thai",
["MMR"]: "Myanmar",
["LAO"]: "Laos",
["KHM"]: "Khmer",
["VNM"]: "Vietnam",
["PHL"]: "Philippines",
["CHN"]: "China",
}[text] || text
);
}
}
function jobPosition(text: string, lang: "th" | "en" = "en") {
switch (lang) {
case "th":
return (
{
["labourer"]: "กรรมกร",
["boatsMechanic"]: "ช่างเครื่องยนต์ในเรือประมงทะเล",
["domesticHelper"]: "ผู้รับใช้ในบ้าน",
["coordinator"]: "งานผู้ประสานงานด้านภาษากัมพูชา ลาว หรือเมียนมา",
}[text] || text
);
default:
return (
{
labourer: "Labourer",
boatsMechanic: "Marine engine mechanic on fishing boats",
domesticHelper: "Domestic helper",
coordinator: "Coordinator for Khmer, Laos, or Myanmar language services",
}[text] || text
);
}
}