Csv file changes added

This commit is contained in:
unknown 2025-12-11 15:22:14 +05:30
parent 180f34d483
commit 87f9b87a0d
5 changed files with 112 additions and 73 deletions

View File

@ -217,7 +217,12 @@ const filePath = uploadedPath;
const cleanRow = {}; const cleanRow = {};
for (const key in row) { for (const key in row) {
const normalizedKey = key.replace(/[\s\W]+/g, "_").trim().toLowerCase(); let cleanedHeader = key
.replace(/\*/g, "")
.replace(/\(.*?\)/g, "")
.trim();
const normalizedKey = cleanedHeader.replace(/[\s\W]+/g, "_").trim().toLowerCase();
let mappedKey = normalizedKey; let mappedKey = normalizedKey;
if (/^hs.*code$/.test(normalizedKey) || normalizedKey === "hscode") { if (/^hs.*code$/.test(normalizedKey) || normalizedKey === "hscode") {
@ -281,7 +286,10 @@ const filePath = uploadedPath;
errors.push({ row: index + 1, error: "HS Code must be max 10 digits" }); errors.push({ row: index + 1, error: "HS Code must be max 10 digits" });
continue; continue;
} }
if (productName.length > 1000) {
errors.push({ row: index + 1, error: "Product Name is too long. Maximum allowed length is 1000 characters." });
continue;
}
if (description.length > 1000) { if (description.length > 1000) {
errors.push({ row: index + 1, error: "HS Description is too long. Maximum allowed length is 1000 characters." }); errors.push({ row: index + 1, error: "HS Description is too long. Maximum allowed length is 1000 characters." });
continue; continue;

View File

@ -209,7 +209,7 @@ exports.uploadUnitMasterFromCSV = async (req, res) => {
}); });
} }
const results = []; let results = [];
fs.createReadStream(filePath) fs.createReadStream(filePath)
.pipe(csv()) .pipe(csv())
@ -224,24 +224,68 @@ exports.uploadUnitMasterFromCSV = async (req, res) => {
}); });
} }
//Expected columns // -------------------------------------------------------------------
const requiredCols = ["Unit Name", "Description"]; // NORMALIZE HEADERS (Very Important!)
const headers = Object.keys(results[0] || {}); // -------------------------------------------------------------------
const missingCols = requiredCols.filter((col) => !headers.includes(col)); const normalizeHeader = (h) => {
const extraCols = headers.filter((col) => !requiredCols.includes(col)); return h
.replace(/\*/g, "") // remove *
.replace(/\(.*?\)/g, "") // remove (Mandatory)
.trim()
.replace(/[\s\W]+/g, "_") // spaces & special chars -> _
.toLowerCase();
};
// Required normalized fields
const mappedRequiredCols = {
unit_name: "Unit Name",
description: "Description",
};
const incomingHeaders = Object.keys(results[0] || {});
const normalizedIncoming = incomingHeaders.map(h => normalizeHeader(h));
// Check missing columns
const missingCols = Object.keys(mappedRequiredCols).filter(
req => !normalizedIncoming.includes(req)
);
// Check unexpected columns
const extraCols = normalizedIncoming.filter(
col => !Object.keys(mappedRequiredCols).includes(col)
);
if (missingCols.length > 0 || extraCols.length > 0) { if (missingCols.length > 0 || extraCols.length > 0) {
fs.unlinkSync(filePath); fs.unlinkSync(filePath);
let msg = ""; let msg = "";
if (missingCols.length > 0) if (missingCols.length > 0)
msg += `Missing required columns: ${missingCols.join(", ")}. `; msg += `Missing required columns: ${missingCols.map(c => mappedRequiredCols[c]).join(", ")}. `;
if (extraCols.length > 0) if (extraCols.length > 0)
msg += `Unexpected columns found: ${extraCols.join( msg += `Unexpected columns found: ${extraCols.join(", ")}. Only 'Unit Name' and 'Description' are allowed.`;
", "
)}. Only 'Unit Name' and 'Description' are allowed.`; return res.status(400).send({
return res.status(400).send({ status: "failed", message: msg.trim() }); status: "failed",
message: msg.trim(),
});
} }
// Remap row keys to clean headers
results = results.map(row => {
const newRow = {};
for (const key in row) {
const normalized = normalizeHeader(key);
const mapped = mappedRequiredCols[normalized];
if (mapped) newRow[mapped] = row[key];
}
return newRow;
});
// -------------------------------------------------------------------
// VALIDATION AND PROCESSING
// -------------------------------------------------------------------
const inserted = []; const inserted = [];
const duplicates = []; const duplicates = [];
const errors = []; const errors = [];
@ -251,7 +295,7 @@ exports.uploadUnitMasterFromCSV = async (req, res) => {
const generateShortName = (unitName, existingShorts = new Set()) => { const generateShortName = (unitName, existingShorts = new Set()) => {
const cleaned = unitName.replace(/[^a-zA-Z]/g, "").toUpperCase(); const cleaned = unitName.replace(/[^a-zA-Z]/g, "").toUpperCase();
// Predefined abbreviations
const abbreviationMap = { const abbreviationMap = {
METER: "MT", METER: "MT",
METRE: "MT", METRE: "MT",
@ -272,54 +316,59 @@ exports.uploadUnitMasterFromCSV = async (req, res) => {
UNIT: "UNT", UNIT: "UNT",
}; };
//if Unit Name is ≤ 5 characters, just use it in uppercase
if (cleaned.length <= 5) return cleaned; if (cleaned.length <= 5) return cleaned;
// Otherwise, use abbreviation map or generate automatically
let shortName = abbreviationMap[cleaned] || cleaned.substring(0, 3); let shortName = abbreviationMap[cleaned] || cleaned.substring(0, 3);
if (shortName.length < 2) shortName = shortName.padEnd(2, "X"); if (shortName.length < 2) shortName = shortName.padEnd(2, "X");
// Random 2-letter suffix for uniqueness
const randomLetters = () => const randomLetters = () =>
Array.from({ length: 2 }, () => Array.from({ length: 2 }, () =>
String.fromCharCode(65 + Math.floor(Math.random() * 26)) String.fromCharCode(65 + Math.floor(Math.random() * 26))
).join(""); ).join("");
let finalShort = `${shortName}${randomLetters()}`; let final = `${shortName}${randomLetters()}`;
while (existingShorts.has(finalShort.toLowerCase())) { while (existingShorts.has(final.toLowerCase())) {
finalShort = `${shortName}${randomLetters()}`; final = `${shortName}${randomLetters()}`;
} }
return finalShort.substring(0, 5);
return final.substring(0, 5);
}; };
//Validate & prepare records // Row validation
for (const [index, row] of results.entries()) { for (const [i, row] of results.entries()) {
const uom = row["Unit Name"]?.trim(); const uom = row["Unit Name"]?.trim();
const description = row["Description"]?.trim() || null; const description = row["Description"]?.trim() || null;
if (description.length > 1000) {
errors.push({ row: i + 1, reason: "HS Description is too long. Maximum allowed length is 1000 characters." });
continue;
}
if (!uom) { if (!uom) {
errors.push({ row: index + 1, reason: "Missing 'Unit Name'." }); errors.push({ row: i + 1, reason: "Missing 'Unit Name'." });
continue; continue;
} }
const uomKey = uom.toLowerCase(); const uomKey = uom.toLowerCase();
if (seenUnitNames.has(uomKey)) { if (seenUnitNames.has(uomKey)) {
fileDuplicates.push({ fileDuplicates.push({
row: index + 1, row: i + 1,
reason: "Duplicate Unit Name in file.", reason: "Duplicate Unit Name in file.",
uom, uom,
}); });
continue; continue;
} }
let uomShort = generateShortName(uom, seenShortNames); const uomShort = generateShortName(uom, seenShortNames);
seenUnitNames.add(uomKey); seenUnitNames.add(uomKey);
seenShortNames.add(uomShort.toLowerCase()); seenShortNames.add(uomShort.toLowerCase());
results[index]._generatedShort = uomShort;
results[index]._description = description; results[i]._generatedShort = uomShort;
results[i]._description = description;
} }
// Stop if file has internal duplicates
if (fileDuplicates.length > 0) { if (fileDuplicates.length > 0) {
fs.unlinkSync(filePath); fs.unlinkSync(filePath);
return res.status(400).send({ return res.status(400).send({
@ -329,10 +378,10 @@ exports.uploadUnitMasterFromCSV = async (req, res) => {
}); });
} }
//Insert into DB with safe duplicate handling // Insert into DB
for (const [index, row] of results.entries()) { for (const [i, row] of results.entries()) {
try { try {
const uom = row["Unit Name"]?.trim(); const uom = row["Unit Name"].trim();
let uomShort = row._generatedShort; let uomShort = row._generatedShort;
const description = row._description; const description = row._description;
@ -352,34 +401,12 @@ exports.uploadUnitMasterFromCSV = async (req, res) => {
}); });
if (existing) { if (existing) {
// Skip if same Unit Name already exists duplicates.push({
if (existing.uom.toLowerCase() === uom.toLowerCase()) { id: existing.id,
duplicates.push({ uom: existing.uom,
id: existing.id, uom_short_name: existing.uom_short_name,
uom: existing.uom, });
uom_short_name: existing.uom_short_name, continue;
});
continue;
}
//Safe regeneration using ternary inside while loop
let newShort = uomShort;
let existsInDb;
do {
existsInDb = await UnitMaster.findOne({
where: Sequelize.where(
Sequelize.fn("LOWER", Sequelize.col("uom_short_name")),
newShort.toLowerCase()
),
});
newShort = existsInDb
? `${uomShort}${Math.floor(Math.random() * 1000)}`.substring(0, 5)
: newShort;
} while (existsInDb);
uomShort = newShort;
} }
const newUnit = await UnitMaster.create({ const newUnit = await UnitMaster.create({
@ -391,13 +418,18 @@ exports.uploadUnitMasterFromCSV = async (req, res) => {
}); });
inserted.push(newUnit); inserted.push(newUnit);
} catch (err) { } catch (err) {
errors.push({ row: index + 1, reason: err.message }); errors.push({ row: i + 1, reason: err.message });
} }
} }
fs.unlinkSync(filePath); fs.unlinkSync(filePath);
// -------------------------------------------------------------------
// FINAL RESPONSE
// -------------------------------------------------------------------
let finalStatus = "success"; let finalStatus = "success";
let message = `${inserted.length} units inserted successfully.`; let message = `${inserted.length} units inserted successfully.`;
@ -421,6 +453,7 @@ exports.uploadUnitMasterFromCSV = async (req, res) => {
duplicates, duplicates,
error_details: errors, error_details: errors,
}); });
} catch (err) { } catch (err) {
if (fs.existsSync(filePath)) fs.unlinkSync(filePath); if (fs.existsSync(filePath)) fs.unlinkSync(filePath);
return res.status(500).send({ return res.status(500).send({
@ -431,7 +464,9 @@ exports.uploadUnitMasterFromCSV = async (req, res) => {
}); });
} catch (error) { } catch (error) {
console.error("Error uploading Unit Master CSV:", error); console.error("Error uploading Unit Master CSV:", error);
if (req.file && fs.existsSync(path.resolve(req.file.path))) fs.unlinkSync(path.resolve(req.file.path)); if (req.file && fs.existsSync(path.resolve(req.file.path)))
fs.unlinkSync(path.resolve(req.file.path));
return res.status(500).send({ return res.status(500).send({
status: "failed", status: "failed",
message: "Error processing CSV file.", message: "Error processing CSV file.",
@ -440,15 +475,6 @@ exports.uploadUnitMasterFromCSV = async (req, res) => {
} }
}; };
// exports.downloadUnitMasterFile = async (req, res) => {
// try {
// const filePath = path.join(__dirname, "../uploads/unit_master_sample.csv");
// return res.download(filePath, "unit_master_sample.csv");
// } catch (error) {
// return res.status(500).send({ status: "failed", message: error.message });
// }
// };
exports.downloadUnitMasterFile = async (req, res) => { exports.downloadUnitMasterFile = async (req, res) => {
try { try {
const safeBasePath = path.resolve(__dirname, "../downloads_csv"); const safeBasePath = path.resolve(__dirname, "../downloads_csv");

View File

@ -1 +1 @@
HS Code,Product Name,Unit,Description HS Code * (Mandatory),Product Name * (Mandatory),Unit * (Mandatory),Description

1 HS Code HS Code * (Mandatory) Product Name Product Name * (Mandatory) Unit Unit * (Mandatory) Description

View File

@ -1 +1 @@
Unit Name,Description Unit Name * (Mandatory),Description * (Mandatory)

1 Unit Name Unit Name * (Mandatory) Description Description * (Mandatory)

View File

@ -31,6 +31,11 @@ module.exports = (sequelize, DataTypes) => {
description: { description: {
type: DataTypes.STRING, type: DataTypes.STRING,
allowNull: true, allowNull: true,
validate: {
len: {
args: [0, 1000],
msg: "Description is too long. Maximum allowed length is 1000 characters."
}}
}, },
is_active: { is_active: {
type: DataTypes.BOOLEAN, type: DataTypes.BOOLEAN,