Geetha : unitmaster file upload
This commit is contained in:
parent
69efa5ee04
commit
01d5c7a1c0
@ -2,6 +2,8 @@ const db = require("../models");
|
|||||||
const { Op } = require("sequelize");
|
const { Op } = require("sequelize");
|
||||||
const { Sequelize } = require("sequelize");
|
const { Sequelize } = require("sequelize");
|
||||||
const { UnitMaster } = require("../models");
|
const { UnitMaster } = require("../models");
|
||||||
|
const fs = require("fs");
|
||||||
|
const csv = require("csv-parser");
|
||||||
|
|
||||||
// Create new Unit
|
// Create new Unit
|
||||||
exports.createUnit = async (req, res) => {
|
exports.createUnit = async (req, res) => {
|
||||||
@ -72,3 +74,232 @@ exports.deleteUnit = async (req, res) => {
|
|||||||
res.status(500).json({ status: "error", message: err.message });
|
res.status(500).json({ status: "error", message: err.message });
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
exports.uploadUnitMasterFromCSV = async (req, res) => {
|
||||||
|
try {
|
||||||
|
if (!req.file) {
|
||||||
|
return res.status(400).send({
|
||||||
|
status: "failed",
|
||||||
|
message: "No file uploaded.",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const filePath = req.file.path;
|
||||||
|
|
||||||
|
// Validate file type
|
||||||
|
if (!req.file.originalname.endsWith(".csv")) {
|
||||||
|
fs.unlinkSync(filePath);
|
||||||
|
return res.status(400).send({
|
||||||
|
status: "failed",
|
||||||
|
message: "Invalid file type. Only CSV allowed.",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Validate created_by
|
||||||
|
if (!req.body.created_by || isNaN(req.body.created_by)) {
|
||||||
|
fs.unlinkSync(filePath);
|
||||||
|
return res.status(400).send({
|
||||||
|
status: "failed",
|
||||||
|
message: "Invalid or missing created_by.",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if file is empty
|
||||||
|
const stats = fs.statSync(filePath);
|
||||||
|
if (stats.size === 0) {
|
||||||
|
fs.unlinkSync(filePath);
|
||||||
|
return res.status(400).send({
|
||||||
|
status: "failed",
|
||||||
|
message: "Uploaded file is empty.",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const results = [];
|
||||||
|
fs.createReadStream(filePath)
|
||||||
|
.pipe(csv())
|
||||||
|
.on("data", (row) => results.push(row))
|
||||||
|
.on("end", async () => {
|
||||||
|
try {
|
||||||
|
if (results.length === 0) {
|
||||||
|
fs.unlinkSync(filePath);
|
||||||
|
return res.status(400).send({
|
||||||
|
status: "failed",
|
||||||
|
message: "CSV file is empty or invalid.",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const requiredCols = ["Display Name", "Unit Name", "Description"];
|
||||||
|
const headers = Object.keys(results[0] || {});
|
||||||
|
|
||||||
|
//Check for missing or extra columns
|
||||||
|
const missingCols = requiredCols.filter((col) => !headers.includes(col));
|
||||||
|
const extraCols = headers.filter((col) => !requiredCols.includes(col));
|
||||||
|
|
||||||
|
if (missingCols.length > 0 || extraCols.length > 0) {
|
||||||
|
fs.unlinkSync(filePath);
|
||||||
|
let msg = "";
|
||||||
|
if (missingCols.length > 0)
|
||||||
|
msg += `Missing required columns: ${missingCols.join(", ")}. `;
|
||||||
|
if (extraCols.length > 0)
|
||||||
|
msg += `Unexpected columns found: ${extraCols.join(", ")}. Only 'Display Name', 'Unit Name', and 'Description' are allowed.`;
|
||||||
|
return res.status(400).send({ status: "failed", message: msg.trim() });
|
||||||
|
}
|
||||||
|
|
||||||
|
const inserted = [];
|
||||||
|
const duplicates = []; // DB duplicates
|
||||||
|
const errors = [];
|
||||||
|
|
||||||
|
//Detect duplicates within uploaded file
|
||||||
|
const seenShortNames = new Set();
|
||||||
|
const seenUnitNames = new Set();
|
||||||
|
const fileDuplicates = [];
|
||||||
|
|
||||||
|
for (const [index, row] of results.entries()) {
|
||||||
|
const uomShort = row["Display Name"]?.trim().toUpperCase();
|
||||||
|
const uom = row["Unit Name"]?.trim();
|
||||||
|
const description = row["Description"]?.trim();
|
||||||
|
|
||||||
|
if (!uomShort || !uom) {
|
||||||
|
errors.push({ row: index + 1, reason: "Missing required fields (Display Name or Unit Name)." });
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!/^[A-Z]+$/i.test(uomShort)) {
|
||||||
|
errors.push({ row: index + 1, reason: "Display Name must contain only alphabetic characters (A–Z)." });
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (uomShort.length > 5) {
|
||||||
|
errors.push({ row: index + 1, reason: "Display Name exceeds 5 characters." });
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
const shortKey = uomShort.toLowerCase();
|
||||||
|
const uomKey = uom.toLowerCase();
|
||||||
|
|
||||||
|
if (seenShortNames.has(shortKey) || seenUnitNames.has(uomKey)) {
|
||||||
|
fileDuplicates.push({ row: index + 1, uom_short_name: uomShort, uom });
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
seenShortNames.add(shortKey);
|
||||||
|
seenUnitNames.add(uomKey);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Stop if file has duplicate rows
|
||||||
|
if (fileDuplicates.length > 0) {
|
||||||
|
fs.unlinkSync(filePath);
|
||||||
|
return res.status(400).send({
|
||||||
|
status: "failed",
|
||||||
|
message: "Duplicate entries found within uploaded file.",
|
||||||
|
duplicate_rows: fileDuplicates,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Process each row and check against DB
|
||||||
|
for (const [index, row] of results.entries()) {
|
||||||
|
try {
|
||||||
|
const uomShort = row["Display Name"]?.trim().toUpperCase();
|
||||||
|
const uom = row["Unit Name"]?.trim();
|
||||||
|
const description = row["Description"]?.trim();
|
||||||
|
|
||||||
|
if (!uomShort || !uom) {
|
||||||
|
errors.push({ row: index + 1, reason: "Missing required fields (Display Name or Unit Name)." });
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (uomShort.length > 5) {
|
||||||
|
errors.push({ row: index + 1, reason: "Display Name exceeds 5 characters." });
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
//Check for existing record (duplicate) in DB
|
||||||
|
const existing = await UnitMaster.findOne({
|
||||||
|
where: {
|
||||||
|
[Op.or]: [
|
||||||
|
Sequelize.where(
|
||||||
|
Sequelize.fn("LOWER", Sequelize.col("uom_short_name")),
|
||||||
|
uomShort.toLowerCase()
|
||||||
|
),
|
||||||
|
Sequelize.where(
|
||||||
|
Sequelize.fn("LOWER", Sequelize.col("uom")),
|
||||||
|
uom.toLowerCase()
|
||||||
|
),
|
||||||
|
],
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
if (existing) {
|
||||||
|
duplicates.push({
|
||||||
|
id: existing.id,
|
||||||
|
uom_short_name: existing.uom_short_name,
|
||||||
|
uom: existing.uom,
|
||||||
|
});
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Insert record
|
||||||
|
const newUnit = await UnitMaster.create({
|
||||||
|
uom_short_name: uomShort,
|
||||||
|
uom: uom,
|
||||||
|
description: description || null,
|
||||||
|
created_by: parseInt(req.body.created_by),
|
||||||
|
created_at: new Date(),
|
||||||
|
});
|
||||||
|
|
||||||
|
inserted.push(newUnit);
|
||||||
|
} catch (err) {
|
||||||
|
errors.push({
|
||||||
|
row: index + 1,
|
||||||
|
reason: err.message,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Cleanup file
|
||||||
|
fs.unlinkSync(filePath);
|
||||||
|
|
||||||
|
// Final response summary
|
||||||
|
let finalStatus = "success";
|
||||||
|
let message = `${inserted.length} units inserted successfully.`;
|
||||||
|
|
||||||
|
if (errors.length > 0 || duplicates.length > 0) {
|
||||||
|
finalStatus = inserted.length > 0 ? "partial_success" : "failed";
|
||||||
|
if (finalStatus === "partial_success") {
|
||||||
|
message = `${inserted.length} units inserted, ${duplicates.length} duplicates skipped, ${errors.length} validation errors found.`;
|
||||||
|
} else {
|
||||||
|
message = `No units imported. ${duplicates.length} duplicates and ${errors.length} validation errors found.`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return res.status(200).send({
|
||||||
|
status: finalStatus,
|
||||||
|
message,
|
||||||
|
summary: {
|
||||||
|
total_records: results.length,
|
||||||
|
imported: inserted.length,
|
||||||
|
skipped: duplicates.length,
|
||||||
|
errors: errors.length,
|
||||||
|
},
|
||||||
|
duplicates,
|
||||||
|
error_details: errors,
|
||||||
|
});
|
||||||
|
} catch (err) {
|
||||||
|
if (fs.existsSync(filePath)) fs.unlinkSync(filePath);
|
||||||
|
return res.status(500).send({
|
||||||
|
status: "failed",
|
||||||
|
message: err.message,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Error uploading Unit Master CSV:", error);
|
||||||
|
if (req.file && fs.existsSync(req.file.path)) fs.unlinkSync(req.file.path);
|
||||||
|
return res.status(500).send({
|
||||||
|
status: "failed",
|
||||||
|
message: "Error processing CSV file.",
|
||||||
|
error: error.message,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|||||||
@ -1774,6 +1774,40 @@ router.put("/unit_master/:id",[verifySignature, verifyToken], unitMasterControll
|
|||||||
*/
|
*/
|
||||||
router.delete("/unit_master/:id",[verifySignature, verifyToken], unitMasterController.deleteUnit);
|
router.delete("/unit_master/:id",[verifySignature, verifyToken], unitMasterController.deleteUnit);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @swagger
|
||||||
|
* /api/unit-master/uploadCSV:
|
||||||
|
* post:
|
||||||
|
* summary: Upload products in bulk using CSV file
|
||||||
|
* description: This API accepts CSV file and inserts multiple products in bulk. CSV header columns must match Product table columns.
|
||||||
|
* tags: [Unit Master]
|
||||||
|
* security:
|
||||||
|
* - appSignature: []
|
||||||
|
* - bearerAuth: []
|
||||||
|
* requestBody:
|
||||||
|
* required: true
|
||||||
|
* content:
|
||||||
|
* multipart/form-data:
|
||||||
|
* schema:
|
||||||
|
* type: object
|
||||||
|
* properties:
|
||||||
|
* file:
|
||||||
|
* type: string
|
||||||
|
* format: binary
|
||||||
|
* description: CSV file to upload
|
||||||
|
* created_by:
|
||||||
|
* type: integer
|
||||||
|
* description: ID of the user performing the upload
|
||||||
|
* responses:
|
||||||
|
* 201:
|
||||||
|
* description: Unit Master file uploaded successfully
|
||||||
|
* 400:
|
||||||
|
* description: No file uploaded
|
||||||
|
* 500:
|
||||||
|
* description: Server error
|
||||||
|
*/
|
||||||
|
router.post("/unit-master/uploadCSV",[ verifySignature, verifyToken, upload.single("file")], unitMasterController.uploadUnitMasterFromCSV);
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user