GWM : 500 error in product creation
This commit is contained in:
parent
0a2b20213c
commit
050cecbee2
@ -14,19 +14,69 @@ const cleanString = (value) =>
|
|||||||
? sanitize(value, { allowedTags: [], allowedAttributes: {} })
|
? sanitize(value, { allowedTags: [], allowedAttributes: {} })
|
||||||
: value;
|
: value;
|
||||||
|
|
||||||
/** Parses optional products.weight_in_ib (nullable integer, non-negative). */
|
/** Matches products.weight_in_ib DECIMAL(18,10): max 8 digits before the decimal. */
|
||||||
|
const WEIGHT_DECIMAL_PLACES = 10;
|
||||||
|
const WEIGHT_MAX_BEFORE_DECIMAL = 8;
|
||||||
|
const WEIGHT_MAX =
|
||||||
|
Number("9".repeat(WEIGHT_MAX_BEFORE_DECIMAL) + "." + "9".repeat(WEIGHT_DECIMAL_PLACES));
|
||||||
|
|
||||||
|
/** Parses optional products.weight_in_ib (nullable non-negative decimal). */
|
||||||
function parseWeightInIb(value) {
|
function parseWeightInIb(value) {
|
||||||
if (value === undefined || value === null || value === "") {
|
if (value === undefined || value === null || value === "") {
|
||||||
return { ok: true, value: null };
|
return { ok: true, value: null };
|
||||||
}
|
}
|
||||||
const n = parseInt(String(value).trim(), 10);
|
const str = String(value).trim().replace(",", ".");
|
||||||
if (Number.isNaN(n)) {
|
const n = Number(str);
|
||||||
return { ok: false, message: "weight_in_ib must be a valid integer." };
|
if (!Number.isFinite(n)) {
|
||||||
|
return { ok: false, message: "weight_in_ib must be a valid number." };
|
||||||
}
|
}
|
||||||
if (n < 0) {
|
if (n < 0) {
|
||||||
return { ok: false, message: "weight_in_ib must be non-negative." };
|
return { ok: false, message: "weight_in_ib must be non-negative." };
|
||||||
}
|
}
|
||||||
return { ok: true, value: n };
|
const rounded = Number(n.toFixed(WEIGHT_DECIMAL_PLACES));
|
||||||
|
if (rounded > WEIGHT_MAX) {
|
||||||
|
return {
|
||||||
|
ok: false,
|
||||||
|
message: `weight_in_ib is too large. Maximum is ${WEIGHT_MAX} (${WEIGHT_MAX_BEFORE_DECIMAL} digits before the decimal).`,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
return { ok: true, value: rounded };
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Returns true if a response was sent (4xx). */
|
||||||
|
function tryRespondProductPersistenceError(error, res) {
|
||||||
|
if (!error || res.headersSent) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (error.name === "SequelizeUniqueConstraintError") {
|
||||||
|
const field = error.errors?.[0]?.path ?? "field";
|
||||||
|
res.status(400).json({
|
||||||
|
status: "failed",
|
||||||
|
message: `${field} already exists.`,
|
||||||
|
});
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
const sqlMessage = error.parent?.sqlMessage || error.original?.sqlMessage || "";
|
||||||
|
const errno = error.parent?.errno ?? error.original?.errno;
|
||||||
|
const combined = `${error.message} ${sqlMessage}`;
|
||||||
|
|
||||||
|
if (error.name === "SequelizeDatabaseError") {
|
||||||
|
if (
|
||||||
|
combined.includes("weight_in_ib") ||
|
||||||
|
(errno === 1264 && /weight/i.test(combined))
|
||||||
|
) {
|
||||||
|
res.status(400).json({
|
||||||
|
status: "failed",
|
||||||
|
message:
|
||||||
|
"weight_in_ib is outside the range allowed by your database column (MySQL DECIMAL precision/scale). For example, DECIMAL(12,10) only allows values below 100. Either use a smaller weight, or widen the column (e.g. ALTER TABLE products MODIFY COLUMN weight_in_ib DECIMAL(18,10) NULL).",
|
||||||
|
});
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
exports.createProduct = async (req, res) => {
|
exports.createProduct = async (req, res) => {
|
||||||
@ -95,6 +145,9 @@ exports.createProduct = async (req, res) => {
|
|||||||
} catch (error) {
|
} catch (error) {
|
||||||
logger.error(error.message);
|
logger.error(error.message);
|
||||||
logger.error(`Stack trace: ${error.stack}`);
|
logger.error(`Stack trace: ${error.stack}`);
|
||||||
|
if (tryRespondProductPersistenceError(error, res)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
res.status(500).send({'status':"failed",'message': "Internal server error" });
|
res.status(500).send({'status':"failed",'message': "Internal server error" });
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@ -188,6 +241,9 @@ exports.updateProduct = async (req, res) => {
|
|||||||
} catch (error) {
|
} catch (error) {
|
||||||
logger.error(error.message);
|
logger.error(error.message);
|
||||||
logger.error(`Stack trace: ${error.stack}`);
|
logger.error(`Stack trace: ${error.stack}`);
|
||||||
|
if (tryRespondProductPersistenceError(error, res)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
res.status(500).send({'status':"failed",'message': "Internal server error" });
|
res.status(500).send({'status':"failed",'message': "Internal server error" });
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@ -441,22 +497,15 @@ exports.uploadProductsFromCSV = async (req, res) => {
|
|||||||
row.weight_in_ib !== null &&
|
row.weight_in_ib !== null &&
|
||||||
String(row.weight_in_ib).trim() !== ""
|
String(row.weight_in_ib).trim() !== ""
|
||||||
) {
|
) {
|
||||||
const w = parseInt(String(row.weight_in_ib).trim(), 10);
|
const wp = parseWeightInIb(row.weight_in_ib);
|
||||||
if (Number.isNaN(w)) {
|
if (!wp.ok) {
|
||||||
validationErrors.push({
|
validationErrors.push({
|
||||||
row: rowNumber,
|
row: rowNumber,
|
||||||
error: "weight_in_ib must be numeric",
|
error: wp.message,
|
||||||
});
|
});
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
if (w < 0) {
|
weightInIb = wp.value;
|
||||||
validationErrors.push({
|
|
||||||
row: rowNumber,
|
|
||||||
error: "weight_in_ib must be non-negative",
|
|
||||||
});
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
weightInIb = w;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Validate required fields
|
// Validate required fields
|
||||||
@ -664,9 +713,12 @@ exports.uploadProductsFromCSV = async (req, res) => {
|
|||||||
logger.error("CSV processing error: " + processingError.message);
|
logger.error("CSV processing error: " + processingError.message);
|
||||||
logger.error(`Stack trace: ${processingError.stack}`);
|
logger.error(`Stack trace: ${processingError.stack}`);
|
||||||
}
|
}
|
||||||
return res.status(500).send({
|
if (tryRespondProductPersistenceError(processingError, res)) {
|
||||||
status: "failed",
|
return;
|
||||||
message: processingError.message
|
}
|
||||||
|
return res.status(500).send({
|
||||||
|
status: "failed",
|
||||||
|
message: "Internal server error",
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|||||||
@ -39,7 +39,7 @@ module.exports = (sequelize, DataTypes) => {
|
|||||||
allowNull: true,
|
allowNull: true,
|
||||||
},
|
},
|
||||||
weight_in_ib: {
|
weight_in_ib: {
|
||||||
type: DataTypes.INTEGER,
|
type: DataTypes.DECIMAL(18, 10),
|
||||||
allowNull: true,
|
allowNull: true,
|
||||||
},
|
},
|
||||||
is_active: {
|
is_active: {
|
||||||
|
|||||||
@ -1281,9 +1281,9 @@ router.post("/trigger-establishment-users-welcome-email",[verifySignature, verif
|
|||||||
* type: integer
|
* type: integer
|
||||||
* example: "select data from unit master"
|
* example: "select data from unit master"
|
||||||
* weight_in_ib:
|
* weight_in_ib:
|
||||||
* type: integer
|
* type: number
|
||||||
* description: Optional weight used in IPI weighting (integer).
|
* description: Optional weight used in IPI weighting (decimal, DECIMAL(18,10) in DB).
|
||||||
* example: 100
|
* example: 100.655214
|
||||||
* is_active:
|
* is_active:
|
||||||
* type: boolean
|
* type: boolean
|
||||||
* example: true
|
* example: true
|
||||||
@ -1368,9 +1368,9 @@ router.get("/products/:id", [verifySignature, verifyToken],productController.get
|
|||||||
* type: integer
|
* type: integer
|
||||||
* example: "select data from unit master"
|
* example: "select data from unit master"
|
||||||
* weight_in_ib:
|
* weight_in_ib:
|
||||||
* type: integer
|
* type: number
|
||||||
* description: Optional weight used in IPI weighting (integer). Omit to leave unchanged; send null to clear.
|
* description: Optional weight used in IPI weighting (decimal). Omit to leave unchanged; send null to clear.
|
||||||
* example: 100
|
* example: 100.655214
|
||||||
* is_active:
|
* is_active:
|
||||||
* type: boolean
|
* type: boolean
|
||||||
* example: true
|
* example: true
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user