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/express": "^4.17.21",
|
||||
"@types/morgan": "^1.9.9",
|
||||
"@types/multer": "^1.4.12",
|
||||
"@types/node": "^20.17.10",
|
||||
"@types/nodemailer": "^6.4.17",
|
||||
"nodemon": "^3.1.9",
|
||||
|
|
@ -46,12 +47,14 @@
|
|||
"dayjs-plugin-utc": "^0.1.2",
|
||||
"docx-templates": "^4.13.0",
|
||||
"dotenv": "^16.4.7",
|
||||
"exceljs": "^4.4.0",
|
||||
"express": "^4.21.2",
|
||||
"fast-jwt": "^5.0.5",
|
||||
"json-2-csv": "^5.5.8",
|
||||
"kysely": "^0.27.5",
|
||||
"minio": "^8.0.2",
|
||||
"morgan": "^1.10.0",
|
||||
"multer": "^1.4.5-lts.2",
|
||||
"nodemailer": "^6.10.0",
|
||||
"prisma-extension-kysely": "^3.0.0",
|
||||
"promise.any": "^2.0.6",
|
||||
|
|
|
|||
|
|
@ -11,6 +11,7 @@ import {
|
|||
Security,
|
||||
Tags,
|
||||
Query,
|
||||
UploadedFile,
|
||||
} from "tsoa";
|
||||
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 { isUsedError, notFoundError, relationError } from "../utils/error";
|
||||
import { queryOrNot, whereDateQuery } from "../utils/relation";
|
||||
import spreadsheet from "../utils/spreadsheet";
|
||||
|
||||
const MANAGE_ROLES = [
|
||||
"system",
|
||||
|
|
@ -447,6 +449,132 @@ export class ProductController extends Controller {
|
|||
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}")
|
||||
|
|
|
|||
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