repair: brand + errorcode

This commit is contained in:
2025-12-05 18:18:50 +07:00
parent dc7712a79f
commit 555a68e90c
7 changed files with 151 additions and 359 deletions

View File

@@ -29,7 +29,7 @@ class BrandController {
res.status(response.statusCode).json(response);
}
// Create brand with nested error codes and solutions
// Create brand
static async create(req, res) {
const { error, value } = await checkValidate(insertBrandSchema, req);
@@ -39,7 +39,7 @@ class BrandController {
value.created_by = req.user?.user_id || null;
const results = await BrandService.createBrandWithFullData(value);
const results = await BrandService.createBrand(value);
const response = await setResponse(results, 'Brand created successfully');
return res.status(response.statusCode).json(response);
@@ -49,29 +49,15 @@ class BrandController {
static async update(req, res) {
const { id } = req.params;
// Debug logging untuk lihat request body
console.log('🔍 BE Raw Request Body:', req.body);
console.log('🔍 BE Request Headers:', req.headers);
console.log('🔍 BE Request Method:', req.method);
const { error, value } = await checkValidate(updateBrandSchema, req);
if (error) {
console.log('❌ BE Validation Error:', {
error,
details: error.details?.map(d => ({
field: d.path.join('.'),
message: d.message,
value: d.context?.value
})),
requestBody: req.body
});
return res.status(400).json(setResponse(error, 'Validation failed', 400));
}
value.updated_by = req.user?.user_id || null;
const results = await BrandService.updateBrandWithFullData(id, value);
const results = await BrandService.updateBrand(id, value);
const response = await setResponse(results, 'Brand updated successfully');
res.status(response.statusCode).json(response);

View File

@@ -1,5 +1,9 @@
const ErrorCodeService = require('../services/error_code.service');
const { setResponse, setResponsePaging } = require('../helpers/utils');
const { setResponse, setResponsePaging, checkValidate } = require('../helpers/utils');
const {
insertErrorCodeSchema,
updateErrorCodeSchema,
} = require('../validate/error_code.schema');
class ErrorCodeController {
static async getByBrandId(req, res) {
@@ -23,15 +27,16 @@ class ErrorCodeController {
// Create error code with solutions and spareparts
static async create(req, res) {
const { error, value } = await checkValidate(insertErrorCodeSchema, req);
if (error) {
return res.status(400).json(setResponse(error, 'Validation failed', 400));
}
const { brandId } = req.params;
const createdBy = req.user?.user_id || null;
value.created_by = req.user?.user_id || null;
const data = {
...req.body,
created_by: createdBy
};
const result = await ErrorCodeService.createErrorCodeWithFullData(brandId, data);
const result = await ErrorCodeService.createErrorCodeWithFullData(brandId, value);
const response = setResponse(result, 'Error code created successfully');
res.status(response.statusCode).json(response);
@@ -39,15 +44,16 @@ class ErrorCodeController {
// Update error code with solutions and spareparts
static async update(req, res) {
const { brandId, errorCode } = req.params;
const updatedBy = req.user?.user_id || null;
const { error, value } = await checkValidate(updateErrorCodeSchema, req);
const data = {
...req.body,
updated_by: updatedBy
};
if (error) {
return res.status(400).json(setResponse(error, 'Validation failed', 400));
}
const result = await ErrorCodeService.updateErrorCodeWithFullData(brandId, errorCode, data);
const { brandId, errorCodeId } = req.params;
value.updated_by = req.user?.user_id || null;
const result = await ErrorCodeService.updateErrorCodeWithFullData(brandId, errorCodeId, value);
const response = setResponse(result, 'Error code updated successfully');
res.status(response.statusCode).json(response);
@@ -55,10 +61,10 @@ class ErrorCodeController {
// Soft delete error code
static async delete(req, res) {
const { brandId, errorCode } = req.params;
const { brandId, errorCodeId } = req.params;
const deletedBy = req.user?.user_id || null;
const result = await ErrorCodeService.deleteErrorCode(brandId, errorCode, deletedBy);
const result = await ErrorCodeService.deleteErrorCode(brandId, errorCodeId, deletedBy);
const response = setResponse(result, 'Error code deleted successfully');
res.status(response.statusCode).json(response);

View File

@@ -9,7 +9,7 @@ router.route('/brand/:brandId')
.get(verifyToken.verifyAccessToken, ErrorCodeController.getByBrandId)
.post(verifyToken.verifyAccessToken, verifyAccess(), ErrorCodeController.create);
router.route('/brand/:brandId/:errorCode')
router.route('/brand/:brandId/:errorCodeId')
.put(verifyToken.verifyAccessToken, verifyAccess(), ErrorCodeController.update)
.delete(verifyToken.verifyAccessToken, verifyAccess(), ErrorCodeController.delete);

View File

@@ -7,32 +7,6 @@ const {
deleteBrandDb,
checkBrandNameExistsDb,
} = require("../db/brand.db");
const {
insertMultipleErrorCodeSparepartsDb,
updateErrorCodeSparepartsDb,
getSparepartsByErrorCodeIdDb,
} = require("../db/brand_sparepart.db");
// Error code operations
const {
getErrorCodesByBrandIdDb,
createErrorCodeDb,
updateErrorCodeDb,
deleteErrorCodeDb,
} = require("../db/brand_code.db");
// Sparepart operations
const { getSparepartsByIdsDb } = require("../db/sparepart.db");
// Solution operations
const {
getSolutionsByErrorCodeIdDb,
createSolutionDb,
updateSolutionDb,
deleteSolutionDb,
} = require("../db/brand_code_solution.db");
const { getFileUploadByPathDb } = require("../db/file_uploads.db");
const { ErrorHandler } = require("../helpers/error");
class BrandService {
@@ -41,7 +15,6 @@ class BrandService {
try {
const results = await getAllBrandsDb(param);
// Return brands data - spareparts are now associated with error codes, not brands
return {
...results,
data: results.data
@@ -65,10 +38,8 @@ class BrandService {
// Create brand
static async createBrandWithFullData(data) {
static async createBrand(data) {
try {
if (!data || typeof data !== "object") data = {};
if (data.brand_name) {
const brandExists = await checkBrandNameExistsDb(data.brand_name);
if (brandExists) {
@@ -76,85 +47,23 @@ class BrandService {
}
}
if (
!data.error_code ||
!Array.isArray(data.error_code) ||
data.error_code.length === 0
) {
throw new ErrorHandler(
400,
"Brand must have at least 1 error code with solution"
);
}
for (const errorCode of data.error_code) {
if (
!errorCode.solution ||
!Array.isArray(errorCode.solution) ||
errorCode.solution.length === 0
) {
throw new ErrorHandler(
400,
`Error code ${errorCode.error_code} must have at least 1 solution`
);
}
}
const brandData = {
brand_name: data.brand_name,
brand_type: data.brand_type,
brand_manufacture: data.brand_manufacture,
brand_model: data.brand_model,
is_active: data.is_active,
is_active: data.is_active !== undefined ? data.is_active : true,
created_by: data.created_by,
};
const createdBrand = await createBrandDb(brandData);
if (!createdBrand) {
throw new Error("Failed to create brand");
throw new ErrorHandler(500, "Failed to create brand");
}
const brandId = createdBrand.brand_id;
for (const errorCodeData of data.error_code) {
const errorId = await createErrorCodeDb(brandId, {
error_code: errorCodeData.error_code,
error_code_name: errorCodeData.error_code_name,
error_code_description: errorCodeData.error_code_description,
error_code_color: errorCodeData.error_code_color,
path_icon: errorCodeData.path_icon,
is_active: errorCodeData.is_active,
created_by: data.created_by,
});
if (!errorId) {
throw new Error("Failed to create error code");
}
// Create sparepart relationships for this error code
if (errorCodeData.spareparts && Array.isArray(errorCodeData.spareparts)) {
await insertMultipleErrorCodeSparepartsDb(errorId, errorCodeData.spareparts, data.created_by);
}
// Create solutions for this error code
if (errorCodeData.solution && Array.isArray(errorCodeData.solution)) {
for (const solutionData of errorCodeData.solution) {
await createSolutionDb(errorId, {
solution_name: solutionData.solution_name,
type_solution: solutionData.type_solution,
text_solution: solutionData.text_solution || null,
path_solution: solutionData.path_solution || null,
is_active: solutionData.is_active,
created_by: data.created_by,
});
}
}
}
const createdBrandWithSpareparts = await this.getBrandById(brandId);
return createdBrandWithSpareparts;
return createdBrand;
} catch (error) {
throw new ErrorHandler(500, `Bulk insert failed: ${error.message}`);
throw new ErrorHandler(error.statusCode || 500, error.message);
}
}
@@ -177,12 +86,11 @@ class BrandService {
// Update brand
static async updateBrandWithFullData(id, data) {
static async updateBrand(id, data) {
try {
const existingBrand = await getBrandByIdDb(id);
if (!existingBrand) throw new ErrorHandler(404, "Brand not found");
if (data.brand_name && data.brand_name !== existingBrand.brand_name) {
const brandExists = await checkBrandNameExistsDb(data.brand_name, id);
if (brandExists) {
@@ -191,147 +99,22 @@ class BrandService {
}
const brandData = {
brand_name: data.brand_name,
brand_type: data.brand_type,
brand_manufacture: data.brand_manufacture,
brand_model: data.brand_model,
is_active: data.is_active,
brand_name: data.brand_name || existingBrand.brand_name,
brand_type: data.brand_type !== undefined ? data.brand_type : existingBrand.brand_type,
brand_manufacture: data.brand_manufacture !== undefined ? data.brand_manufacture : existingBrand.brand_manufacture,
brand_model: data.brand_model !== undefined ? data.brand_model : existingBrand.brand_model,
is_active: data.is_active !== undefined ? data.is_active : existingBrand.is_active,
updated_by: data.updated_by,
};
await updateBrandDb(existingBrand.brand_name, brandData);
if (data.error_code && Array.isArray(data.error_code)) {
const existingErrorCodes = await getErrorCodesByBrandIdDb(id);
const incomingErrorCodes = data.error_code.map((ec) => ec.error_code);
// Create/update/delete error codes
for (const errorCodeData of data.error_code) {
// Check if error code already exists
const existingEC = existingErrorCodes.find(
(ec) => ec.error_code === errorCodeData.error_code
);
if (existingEC) {
// Update existing error code using separate db function
await updateErrorCodeDb(
existingEC.brand_id,
existingEC.error_code,
{
error_code_name: errorCodeData.error_code_name,
error_code_description: errorCodeData.error_code_description,
error_code_color: errorCodeData.error_code_color,
path_icon: errorCodeData.path_icon,
is_active: errorCodeData.is_active,
updated_by: data.updated_by,
}
);
if (errorCodeData.spareparts && Array.isArray(errorCodeData.spareparts)) {
await updateErrorCodeSparepartsDb(existingEC.error_code_id, errorCodeData.spareparts, data.updated_by);
}
if (
errorCodeData.solution &&
Array.isArray(errorCodeData.solution)
) {
const existingSolutions = await getSolutionsByErrorCodeIdDb(
existingEC.error_code_id
);
const incomingSolutionNames = errorCodeData.solution.map(
(s) => s.solution_name
);
// Update or create solutions
for (const solutionData of errorCodeData.solution) {
const existingSolution = existingSolutions.find(
(s) => s.solution_name === solutionData.solution_name
);
if (existingSolution) {
// Update existing solution
await updateSolutionDb(
existingSolution.brand_code_solution_id,
{
solution_name: solutionData.solution_name,
type_solution: solutionData.type_solution,
text_solution: solutionData.text_solution || null,
path_solution: solutionData.path_solution || null,
is_active: solutionData.is_active,
updated_by: data.updated_by,
}
);
} else {
// Create new solution
await createSolutionDb(existingEC.error_code_id, {
solution_name: solutionData.solution_name,
type_solution: solutionData.type_solution,
text_solution: solutionData.text_solution || null,
path_solution: solutionData.path_solution || null,
is_active: solutionData.is_active,
created_by: data.updated_by,
});
}
}
// Delete solutions that are not in the incoming request
for (const existingSolution of existingSolutions) {
if (
!incomingSolutionNames.includes(
existingSolution.solution_name
)
) {
await deleteSolutionDb(
existingSolution.brand_code_solution_id,
data.updated_by
);
}
}
}
} else {
const errorId = await createErrorCodeDb(id, {
error_code: errorCodeData.error_code,
error_code_name: errorCodeData.error_code_name,
error_code_description: errorCodeData.error_code_description,
error_code_color: errorCodeData.error_code_color,
path_icon: errorCodeData.path_icon,
is_active: errorCodeData.is_active,
created_by: data.updated_by,
});
if (errorCodeData.spareparts && Array.isArray(errorCodeData.spareparts)) {
await insertMultipleErrorCodeSparepartsDb(errorId, errorCodeData.spareparts, data.updated_by);
}
if (
errorCodeData.solution &&
Array.isArray(errorCodeData.solution)
) {
for (const solutionData of errorCodeData.solution) {
await createSolutionDb(errorId, {
solution_name: solutionData.solution_name,
type_solution: solutionData.type_solution,
text_solution: solutionData.text_solution || null,
path_solution: solutionData.path_solution || null,
is_active: solutionData.is_active,
created_by: data.updated_by,
});
}
}
}
}
for (const existingEC of existingErrorCodes) {
if (!incomingErrorCodes.includes(existingEC.error_code)) {
await deleteErrorCodeDb(id, existingEC.error_code, data.updated_by);
}
}
const updatedBrand = await updateBrandDb(existingBrand.brand_name, brandData);
if (!updatedBrand) {
throw new ErrorHandler(500, "Failed to update brand");
}
const updatedBrandWithSpareparts = await this.getBrandById(id);
return updatedBrandWithSpareparts;
return updatedBrand;
} catch (error) {
throw new ErrorHandler(500, `Update failed: ${error.message}`);
throw new ErrorHandler(error.statusCode || 500, error.message);
}
}
}

View File

@@ -25,7 +25,6 @@ const {
const { getFileUploadByPathDb } = require("../db/file_uploads.db");
class ErrorCodeService {
// Get all error codes with pagination and search (without solutions and spareparts)
static async getAllErrorCodes(param) {
try {
const results = await getAllErrorCodesDb(param);
@@ -169,19 +168,34 @@ class ErrorCodeService {
}
// Update error code with solutions and spareparts
static async updateErrorCodeWithFullData(brandId, errorCode, data) {
static async updateErrorCodeWithFullData(brandId, errorCodeId, data) {
try {
const existingErrorCode = await getErrorCodeByBrandAndCodeDb(brandId, errorCode);
const existingErrorCode = await getErrorCodeByIdDb(errorCodeId);
if (!existingErrorCode) throw new ErrorHandler(404, "Error code not found");
await updateErrorCodeDb(brandId, errorCode, {
error_code_name: data.error_code_name,
error_code_description: data.error_code_description,
error_code_color: data.error_code_color,
path_icon: data.path_icon,
is_active: data.is_active,
updated_by: data.updated_by,
});
// Verify the error code belongs to the specified brand
if (existingErrorCode.brand_id !== parseInt(brandId)) {
throw new ErrorHandler(403, "Error code does not belong to specified brand");
}
// Check if there are any error code fields to update
const hasMainFieldUpdate =
data.error_code_name !== undefined ||
data.error_code_description !== undefined ||
data.error_code_color !== undefined ||
data.path_icon !== undefined ||
data.is_active !== undefined;
if (hasMainFieldUpdate) {
await updateErrorCodeDb(brandId, existingErrorCode.error_code, {
error_code_name: data.error_code_name,
error_code_description: data.error_code_description,
error_code_color: data.error_code_color,
path_icon: data.path_icon,
is_active: data.is_active,
updated_by: data.updated_by,
});
}
if (data.spareparts && Array.isArray(data.spareparts)) {
await updateErrorCodeSparepartsDb(existingErrorCode.error_code_id, data.spareparts, data.updated_by);
@@ -235,15 +249,20 @@ class ErrorCodeService {
}
// Soft delete error code
static async deleteErrorCode(brandId, errorCode, deletedBy) {
static async deleteErrorCode(brandId, errorCodeId, deletedBy) {
try {
const errorCodeExist = await getErrorCodeByBrandAndCodeDb(brandId, errorCode);
const errorCodeExist = await getErrorCodeByIdDb(errorCodeId);
if (!errorCodeExist) {
throw new ErrorHandler(404, "Error code not found");
}
const result = await deleteErrorCodeDb(brandId, errorCode, deletedBy);
// Verify the error code belongs to the specified brand
if (errorCodeExist.brand_id !== parseInt(brandId)) {
throw new ErrorHandler(403, "Error code does not belong to specified brand");
}
const result = await deleteErrorCodeDb(brandId, errorCodeExist.error_code, deletedBy);
return result;
} catch (error) {

View File

@@ -8,91 +8,18 @@ const insertBrandSchema = Joi.object({
brand_type: Joi.string().max(50).optional().allow(""),
brand_manufacture: Joi.string().max(100).required(),
brand_model: Joi.string().max(100).optional().allow(""),
is_active: Joi.boolean().required(),
is_active: Joi.boolean().optional().default(true),
description: Joi.string().max(255).optional().allow(""),
error_code: Joi.array()
.items(
Joi.object({
error_code: Joi.string().max(100).required(),
error_code_name: Joi.string().max(100).required(),
error_code_description: Joi.string().optional().allow(""),
error_code_color: Joi.string().optional().allow(""),
path_icon: Joi.string().optional().allow(""),
is_active: Joi.boolean().required(),
what_action_to_take: Joi.string().optional().allow(""),
spareparts: Joi.array().items(Joi.number().integer()).optional(),
solution: Joi.array()
.items(
Joi.object({
solution_name: Joi.string().max(100).required(),
type_solution: Joi.string()
.valid("text", "pdf", "image", "video", "link")
.required(),
text_solution: Joi.when("type_solution", {
is: "text",
then: Joi.string().required(),
otherwise: Joi.string().optional().allow(""),
}),
path_solution: Joi.when("type_solution", {
is: "text",
then: Joi.string().optional().allow(""),
otherwise: Joi.string().required(),
}),
is_active: Joi.boolean().required(),
})
)
.min(1)
.required(),
})
)
.min(1)
.required(),
});
// Update Brand Validation
const updateBrandSchema = Joi.object({
brand_name: Joi.string().max(100).required(),
brand_name: Joi.string().max(100).optional(),
brand_type: Joi.string().max(50).optional().allow(""),
brand_manufacture: Joi.string().max(100).required(),
brand_manufacture: Joi.string().max(100).optional(),
brand_model: Joi.string().max(100).optional().allow(""),
is_active: Joi.boolean().required(),
is_active: Joi.boolean().optional(),
description: Joi.string().max(255).optional().allow(""),
error_code: Joi.array()
.items(
Joi.object({
error_code: Joi.string().max(100).required(),
error_code_name: Joi.string().max(100).required(),
error_code_description: Joi.string().optional().allow(""),
error_code_color: Joi.string().optional().allow(""),
path_icon: Joi.string().optional().allow(""),
is_active: Joi.boolean().required(),
what_action_to_take: Joi.string().optional().allow(""),
spareparts: Joi.array().items(Joi.number().integer()).optional(),
solution: Joi.array()
.items(
Joi.object({
solution_name: Joi.string().max(100).required(),
type_solution: Joi.string()
.valid("text", "pdf", "image", "video", "link")
.required(),
text_solution: Joi.when("type_solution", {
is: "text",
then: Joi.string().required(),
otherwise: Joi.string().optional().allow(""),
}),
path_solution: Joi.when("type_solution", {
is: "text",
then: Joi.string().optional().allow(""),
otherwise: Joi.string().required(),
}),
is_active: Joi.boolean().optional(),
})
)
.min(1)
.required(),
})
)
.optional(),
}).min(1);
module.exports = {

View File

@@ -0,0 +1,71 @@
const Joi = require("joi");
// ========================
// Error Code Validation
// ========================
const solutionSchema = Joi.object({
solution_name: Joi.string().max(100).required(),
type_solution: Joi.string()
.valid("text", "pdf", "image", "video", "link")
.required(),
text_solution: Joi.when("type_solution", {
is: "text",
then: Joi.string().required(),
otherwise: Joi.string().optional().allow(""),
}),
path_solution: Joi.when("type_solution", {
is: "text",
then: Joi.string().optional().allow(""),
otherwise: Joi.string().required(),
}),
is_active: Joi.boolean().default(true),
});
const insertErrorCodeSchema = Joi.object({
error_code: Joi.string().max(100).required(),
error_code_name: Joi.string().max(100).required(),
error_code_description: Joi.string().optional().allow(""),
error_code_color: Joi.string().optional().allow(""),
path_icon: Joi.string().optional().allow(""),
is_active: Joi.boolean().default(true),
solution: Joi.array()
.items(solutionSchema)
.min(1)
.required()
.messages({
"array.min": "Error code must have at least 1 solution",
}),
spareparts: Joi.array()
.items(Joi.number().integer())
.optional(),
}).messages({
"object.unknown": "{{#child}} is not allowed",
});
const updateErrorCodeSchema = Joi.object({
error_code_name: Joi.string().max(100).optional(),
error_code_description: Joi.string().optional().allow(""),
error_code_color: Joi.string().optional().allow(""),
path_icon: Joi.string().optional().allow(""),
is_active: Joi.boolean().optional(),
solution: Joi.array()
.items(solutionSchema)
.min(1)
.optional()
.messages({
"array.min": "Error code must have at least 1 solution",
}),
spareparts: Joi.array()
.items(Joi.number().integer())
.optional(),
}).min(1).messages({
"object.min": "At least one field must be provided for update",
"object.unknown": "{{#child}} is not allowed",
});
module.exports = {
insertErrorCodeSchema,
updateErrorCodeSchema,
solutionSchema,
};