From 555a68e90c933a3e9b0bd9e114a2afdf0a23cef1 Mon Sep 17 00:00:00 2001 From: Antony Kurniawan Date: Fri, 5 Dec 2025 18:18:50 +0700 Subject: [PATCH] repair: brand + errorcode --- controllers/brand.controller.js | 20 +-- controllers/error_code.controller.js | 40 +++-- routes/error_code.route.js | 2 +- services/brand.service.js | 249 ++------------------------- services/error_code.service.js | 47 +++-- validate/brand.schema.js | 81 +-------- validate/error_code.schema.js | 71 ++++++++ 7 files changed, 151 insertions(+), 359 deletions(-) create mode 100644 validate/error_code.schema.js diff --git a/controllers/brand.controller.js b/controllers/brand.controller.js index c84a8bd..7eb1437 100644 --- a/controllers/brand.controller.js +++ b/controllers/brand.controller.js @@ -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); diff --git a/controllers/error_code.controller.js b/controllers/error_code.controller.js index d3e2404..a263397 100644 --- a/controllers/error_code.controller.js +++ b/controllers/error_code.controller.js @@ -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); diff --git a/routes/error_code.route.js b/routes/error_code.route.js index c7ba80c..c65860b 100644 --- a/routes/error_code.route.js +++ b/routes/error_code.route.js @@ -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); diff --git a/services/brand.service.js b/services/brand.service.js index 256c676..a50b8b4 100644 --- a/services/brand.service.js +++ b/services/brand.service.js @@ -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); } } } diff --git a/services/error_code.service.js b/services/error_code.service.js index 6c8a53c..2576c38 100644 --- a/services/error_code.service.js +++ b/services/error_code.service.js @@ -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) { diff --git a/validate/brand.schema.js b/validate/brand.schema.js index 57fe595..954cf19 100644 --- a/validate/brand.schema.js +++ b/validate/brand.schema.js @@ -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 = { diff --git a/validate/error_code.schema.js b/validate/error_code.schema.js new file mode 100644 index 0000000..957b718 --- /dev/null +++ b/validate/error_code.schema.js @@ -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, +}; \ No newline at end of file