add import file product
All checks were successful
Spell Check / Spell Check with Typos (push) Successful in 7s
All checks were successful
Spell Check / Spell Check with Typos (push) Successful in 7s
This commit is contained in:
parent
fd7833a592
commit
05d16f22de
3 changed files with 236 additions and 0 deletions
|
|
@ -24,6 +24,7 @@
|
||||||
"@types/cors": "^2.8.17",
|
"@types/cors": "^2.8.17",
|
||||||
"@types/express": "^4.17.21",
|
"@types/express": "^4.17.21",
|
||||||
"@types/morgan": "^1.9.9",
|
"@types/morgan": "^1.9.9",
|
||||||
|
"@types/multer": "^1.4.12",
|
||||||
"@types/node": "^20.17.10",
|
"@types/node": "^20.17.10",
|
||||||
"@types/nodemailer": "^6.4.17",
|
"@types/nodemailer": "^6.4.17",
|
||||||
"nodemon": "^3.1.9",
|
"nodemon": "^3.1.9",
|
||||||
|
|
@ -46,12 +47,14 @@
|
||||||
"dayjs-plugin-utc": "^0.1.2",
|
"dayjs-plugin-utc": "^0.1.2",
|
||||||
"docx-templates": "^4.13.0",
|
"docx-templates": "^4.13.0",
|
||||||
"dotenv": "^16.4.7",
|
"dotenv": "^16.4.7",
|
||||||
|
"exceljs": "^4.4.0",
|
||||||
"express": "^4.21.2",
|
"express": "^4.21.2",
|
||||||
"fast-jwt": "^5.0.5",
|
"fast-jwt": "^5.0.5",
|
||||||
"json-2-csv": "^5.5.8",
|
"json-2-csv": "^5.5.8",
|
||||||
"kysely": "^0.27.5",
|
"kysely": "^0.27.5",
|
||||||
"minio": "^8.0.2",
|
"minio": "^8.0.2",
|
||||||
"morgan": "^1.10.0",
|
"morgan": "^1.10.0",
|
||||||
|
"multer": "^1.4.5-lts.2",
|
||||||
"nodemailer": "^6.10.0",
|
"nodemailer": "^6.10.0",
|
||||||
"prisma-extension-kysely": "^3.0.0",
|
"prisma-extension-kysely": "^3.0.0",
|
||||||
"promise.any": "^2.0.6",
|
"promise.any": "^2.0.6",
|
||||||
|
|
|
||||||
|
|
@ -11,6 +11,7 @@ import {
|
||||||
Security,
|
Security,
|
||||||
Tags,
|
Tags,
|
||||||
Query,
|
Query,
|
||||||
|
UploadedFile,
|
||||||
} from "tsoa";
|
} from "tsoa";
|
||||||
import { Prisma, Product, Status } from "@prisma/client";
|
import { Prisma, Product, Status } from "@prisma/client";
|
||||||
|
|
||||||
|
|
@ -28,6 +29,7 @@ import { filterStatus } from "../services/prisma";
|
||||||
import { deleteFile, deleteFolder, fileLocation, getFile, listFile, setFile } from "../utils/minio";
|
import { deleteFile, deleteFolder, fileLocation, getFile, listFile, setFile } from "../utils/minio";
|
||||||
import { isUsedError, notFoundError, relationError } from "../utils/error";
|
import { isUsedError, notFoundError, relationError } from "../utils/error";
|
||||||
import { queryOrNot, whereDateQuery } from "../utils/relation";
|
import { queryOrNot, whereDateQuery } from "../utils/relation";
|
||||||
|
import spreadsheet from "../utils/spreadsheet";
|
||||||
|
|
||||||
const MANAGE_ROLES = [
|
const MANAGE_ROLES = [
|
||||||
"system",
|
"system",
|
||||||
|
|
@ -447,6 +449,132 @@ export class ProductController extends Controller {
|
||||||
where: { id: productId },
|
where: { id: productId },
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Post("uploadedFile")
|
||||||
|
@Security("keycloak", MANAGE_ROLES)
|
||||||
|
async importProduct(
|
||||||
|
@Request() req: RequestWithUser,
|
||||||
|
@UploadedFile() file: Express.Multer.File,
|
||||||
|
@Query() productGroupId: string,
|
||||||
|
) {
|
||||||
|
if (!file?.buffer) throw notFoundError("File");
|
||||||
|
|
||||||
|
const buffer = new Uint8Array(file.buffer).buffer;
|
||||||
|
const dataFile = await spreadsheet.readExcel(buffer, {
|
||||||
|
header: true,
|
||||||
|
worksheet: "Sheet1",
|
||||||
|
});
|
||||||
|
|
||||||
|
let dataName: string[] = [];
|
||||||
|
const data = await dataFile.map((item: any) => {
|
||||||
|
dataName.push(item.name);
|
||||||
|
return {
|
||||||
|
...item,
|
||||||
|
expenseType:
|
||||||
|
item.expenseType === "ค่าธรรมเนียม"
|
||||||
|
? "fee"
|
||||||
|
: item.expenseType === "ค่าบริการ"
|
||||||
|
? "serviceFee"
|
||||||
|
: "processingFee",
|
||||||
|
shared: item.shared === "ใช่" ? true : false,
|
||||||
|
calcVat: item.calcVat === "ใช่" ? true : false,
|
||||||
|
vatIncluded: item.vatIncluded === "รวม" ? true : false,
|
||||||
|
agentPriceCalcVat: item.agentPriceCalcVat === "ใช่" ? true : false,
|
||||||
|
agentPriceVatIncluded: item.agentPriceVatIncluded === "รวม" ? true : false,
|
||||||
|
serviceChargeCalcVat: item.serviceChargeCalcVat === "ใช่" ? true : false,
|
||||||
|
serviceChargeVatIncluded: item.serviceChargeVatIncluded === "รวม" ? true : false,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
const [productGroup, productSameName] = await prisma.$transaction([
|
||||||
|
prisma.productGroup.findFirst({
|
||||||
|
include: {
|
||||||
|
registeredBranch: {
|
||||||
|
include: branchRelationPermInclude(req.user),
|
||||||
|
},
|
||||||
|
createdBy: true,
|
||||||
|
updatedBy: true,
|
||||||
|
},
|
||||||
|
where: { id: productGroupId },
|
||||||
|
}),
|
||||||
|
prisma.product.findMany({
|
||||||
|
where: {
|
||||||
|
productGroup: {
|
||||||
|
registeredBranch: {
|
||||||
|
OR: permissionCondCompany(req.user),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
name: { in: dataName },
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
]);
|
||||||
|
|
||||||
|
if (!productGroup) throw relationError("Product Group");
|
||||||
|
|
||||||
|
await permissionCheck(req.user, productGroup.registeredBranch);
|
||||||
|
let dataProduct: ProductCreate[] = [];
|
||||||
|
|
||||||
|
const record = await prisma.$transaction(
|
||||||
|
async (tx) => {
|
||||||
|
const branch = productGroup.registeredBranch;
|
||||||
|
const company = (branch.headOffice || branch).code;
|
||||||
|
console.log(branch, company);
|
||||||
|
for (const item of data) {
|
||||||
|
const dataDuplicate = productSameName.some(
|
||||||
|
(v) => v.code.slice(0, -3) === item.code.toUpperCase() && v.name === item.name,
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!dataDuplicate) {
|
||||||
|
const last = await tx.runningNo.upsert({
|
||||||
|
where: {
|
||||||
|
key: `PRODUCT_${company}_${item.code.toLocaleUpperCase()}`,
|
||||||
|
},
|
||||||
|
create: {
|
||||||
|
key: `PRODUCT_${company}_${item.code.toLocaleUpperCase()}`,
|
||||||
|
value: 1,
|
||||||
|
},
|
||||||
|
update: { value: { increment: 1 } },
|
||||||
|
});
|
||||||
|
|
||||||
|
dataProduct.push({
|
||||||
|
...item,
|
||||||
|
code: `${item.code.toLocaleUpperCase()}${last.value.toString().padStart(3, "0")}`,
|
||||||
|
createdByUserId: req.user.sub,
|
||||||
|
updatedByUserId: req.user.sub,
|
||||||
|
productGroupId: productGroupId,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
console.log("dataProduct", dataProduct);
|
||||||
|
|
||||||
|
return await prisma.product.createManyAndReturn({
|
||||||
|
data: dataProduct,
|
||||||
|
include: {
|
||||||
|
createdBy: true,
|
||||||
|
updatedBy: true,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
},
|
||||||
|
{
|
||||||
|
isolationLevel: Prisma.TransactionIsolationLevel.Serializable,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
if (productGroup.status === "CREATED") {
|
||||||
|
await prisma.productGroup.update({
|
||||||
|
include: {
|
||||||
|
createdBy: true,
|
||||||
|
updatedBy: true,
|
||||||
|
},
|
||||||
|
where: { id: productGroupId },
|
||||||
|
data: { status: Status.ACTIVE },
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
this.setStatus(HttpStatus.CREATED);
|
||||||
|
|
||||||
|
return record;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Route("api/v1/product/{productId}")
|
@Route("api/v1/product/{productId}")
|
||||||
|
|
|
||||||
105
src/utils/spreadsheet.ts
Normal file
105
src/utils/spreadsheet.ts
Normal file
|
|
@ -0,0 +1,105 @@
|
||||||
|
import Excel from "exceljs";
|
||||||
|
|
||||||
|
export default class spreadsheet {
|
||||||
|
static async readCsv() {
|
||||||
|
// TODO: read csv
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This function read data from excel file.
|
||||||
|
*
|
||||||
|
* @param buffer - Excel file.
|
||||||
|
* @param opts.header - Interprets the first row as the names of the fields.
|
||||||
|
* @param opts.worksheet - Specifies the worksheet to read. Can be the worksheet's name or its 1-based index.
|
||||||
|
*
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
static async readExcel<T extends unknown>(
|
||||||
|
buffer: Excel.Buffer,
|
||||||
|
opts?: { header?: boolean; worksheet?: number | string },
|
||||||
|
): Promise<T[]> {
|
||||||
|
const workbook = new Excel.Workbook();
|
||||||
|
await workbook.xlsx.load(buffer);
|
||||||
|
const worksheet = workbook.getWorksheet(opts?.worksheet ?? 1);
|
||||||
|
|
||||||
|
if (!worksheet) return [];
|
||||||
|
|
||||||
|
const header: Record<number, string | number> = {};
|
||||||
|
const values: any[] = [];
|
||||||
|
|
||||||
|
worksheet.eachRow((row, rowId) => {
|
||||||
|
if (rowId === 1 && opts?.header !== false) {
|
||||||
|
row.eachCell((cell, cellId) => {
|
||||||
|
if (typeof cell.value === "string") {
|
||||||
|
header[cellId] = nameValue(cell.value);
|
||||||
|
} else {
|
||||||
|
header[cellId] = cellId.toString();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
const data: Record<string | number, Excel.CellValue> = {};
|
||||||
|
row.eachCell((cell, cellId) => {
|
||||||
|
data[opts?.header !== false ? header[cellId] : cellId - 1] = cell.value;
|
||||||
|
});
|
||||||
|
values.push(opts?.header !== false ? data : Object.values(data));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return values;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function nameValue(value: string) {
|
||||||
|
let code: string;
|
||||||
|
switch (value) {
|
||||||
|
case "ชื่อสินค้าและบริการ":
|
||||||
|
code = "name";
|
||||||
|
break;
|
||||||
|
case "ระยะเวลาดำเนินการ":
|
||||||
|
code = "process";
|
||||||
|
break;
|
||||||
|
case "ประเภทค่าใช้จ่าย":
|
||||||
|
code = "expenseType";
|
||||||
|
break;
|
||||||
|
case "รายละเอียด":
|
||||||
|
code = "detail";
|
||||||
|
break;
|
||||||
|
case "หมายเหตุ":
|
||||||
|
code = "remark";
|
||||||
|
break;
|
||||||
|
case "ใช้งานร่วมกัน":
|
||||||
|
code = "shared";
|
||||||
|
break;
|
||||||
|
case "คำนวณภาษีราคาขาย":
|
||||||
|
code = "calcVat";
|
||||||
|
break;
|
||||||
|
case "รวม VAT ราคาขาย":
|
||||||
|
code = "vatIncluded";
|
||||||
|
break;
|
||||||
|
case "ราคาต่อหน่วย (บาท) ราคาขาย":
|
||||||
|
code = "price";
|
||||||
|
break;
|
||||||
|
case "คำนวณภาษีราคาตัวแทน":
|
||||||
|
code = "agentPriceCalcVat";
|
||||||
|
break;
|
||||||
|
case "รวม VAT ราคาตัวแทน":
|
||||||
|
code = "agentPriceVatIncluded";
|
||||||
|
break;
|
||||||
|
case "ราคาต่อหน่วย (บาท) ราคาตัวแทน":
|
||||||
|
code = "agentPrice";
|
||||||
|
break;
|
||||||
|
case "คำนวณภาษีราคาดำเนินการ":
|
||||||
|
code = "serviceChargeCalcVat";
|
||||||
|
break;
|
||||||
|
case "รวม VAT ราคาดำเนินการ":
|
||||||
|
code = "serviceChargeVatIncluded";
|
||||||
|
break;
|
||||||
|
case "ราคาต่อหน่วย (บาท) ราคาดำเนินการ":
|
||||||
|
code = "serviceCharge";
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
code = "code";
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
return code;
|
||||||
|
}
|
||||||
Loading…
Add table
Add a link
Reference in a new issue