From f797685a4f180fcfe268287be97a9f7fdfaaf868 Mon Sep 17 00:00:00 2001 From: Antony Kurniawan Date: Tue, 2 Dec 2025 15:22:20 +0700 Subject: [PATCH] add: error_code api --- controllers/error_code.controller.js | 105 ++++++++++ db/brand_code.db.js | 66 ++++++ routes/error_code.route.js | 19 ++ routes/index.js | 2 + services/brand.service.js | 57 +----- services/error_code.service.js | 294 +++++++++++++++++++++++++++ 6 files changed, 488 insertions(+), 55 deletions(-) create mode 100644 controllers/error_code.controller.js create mode 100644 routes/error_code.route.js create mode 100644 services/error_code.service.js diff --git a/controllers/error_code.controller.js b/controllers/error_code.controller.js new file mode 100644 index 0000000..07ff945 --- /dev/null +++ b/controllers/error_code.controller.js @@ -0,0 +1,105 @@ +const ErrorCodeService = require('../services/error_code.service'); +const { setResponse, setResponsePaging } = require('../helpers/utils'); +const { ErrorHandler } = require('../helpers/error'); + +class ErrorCodeController { + // Get all error codes with pagination and search + static async getAll(req, res) { + try { + const queryParams = req.query; + const results = await ErrorCodeService.getAllErrorCodes(queryParams); + const response = await setResponsePaging(queryParams, results, 'Error codes found'); + res.status(response.statusCode).json(response); + } catch (error) { + const response = setResponse(error.message, error.statusCode || 500); + res.status(response.statusCode).json(response); + } + } + + // Get error code by ID + static async getById(req, res) { + try { + const { id } = req.params; + const result = await ErrorCodeService.getErrorCodeById(id); + const response = setResponse(result, 200, 'Error code found'); + res.status(response.statusCode).json(response); + } catch (error) { + const response = setResponse(error.message, error.statusCode || 500); + res.status(response.statusCode).json(response); + } + } + + // Get error codes by brand ID with pagination and search + static async getByBrandId(req, res) { + try { + const { brandId } = req.params; + const queryParams = { + ...req.query, + brand_id: brandId + }; + const results = await ErrorCodeService.getAllErrorCodes(queryParams); + const response = await setResponsePaging(queryParams, results, 'Error codes found'); + res.status(response.statusCode).json(response); + } catch (error) { + const response = setResponse(error.message, error.statusCode || 500); + res.status(response.statusCode).json(response); + } + } + + // Create error code with solutions and spareparts + static async create(req, res) { + try { + const { brandId } = req.params; + const createdBy = req.user?.user_id || null; + + const data = { + ...req.body, + created_by: createdBy + }; + + const result = await ErrorCodeService.createErrorCodeWithFullData(brandId, data); + const response = setResponse(result, 201, 'Error code created successfully'); + res.status(response.statusCode).json(response); + } catch (error) { + const response = setResponse(error.message, error.statusCode || 500); + res.status(response.statusCode).json(response); + } + } + + // Update error code with solutions and spareparts + static async update(req, res) { + try { + const { brandId, errorCode } = req.params; + const updatedBy = req.user?.user_id || null; + + const data = { + ...req.body, + updated_by: updatedBy + }; + + const result = await ErrorCodeService.updateErrorCodeWithFullData(brandId, errorCode, data); + const response = setResponse(result, 200, 'Error code updated successfully'); + res.status(response.statusCode).json(response); + } catch (error) { + const response = setResponse(error.message, error.statusCode || 500); + res.status(response.statusCode).json(response); + } + } + + // Soft delete error code + static async delete(req, res) { + try { + const { brandId, errorCode } = req.params; + const deletedBy = req.user?.user_id || null; + + const result = await ErrorCodeService.deleteErrorCode(brandId, errorCode, deletedBy); + const response = setResponse(result, 200, 'Error code deleted successfully'); + res.status(response.statusCode).json(response); + } catch (error) { + const response = setResponse(error.message, error.statusCode || 500); + res.status(response.statusCode).json(response); + } + } +} + +module.exports = ErrorCodeController; \ No newline at end of file diff --git a/db/brand_code.db.js b/db/brand_code.db.js index 4d8925a..1a0e3e3 100644 --- a/db/brand_code.db.js +++ b/db/brand_code.db.js @@ -64,10 +64,76 @@ const getErrorCodeByIdDb = async (error_code_id) => { return result.recordset[0]; }; +const getErrorCodeByBrandAndCodeDb = async (brandId, errorCode) => { + const queryText = ` + SELECT + a.* + FROM brand_code a + WHERE a.brand_id = $1 AND a.error_code = $2 AND a.deleted_at IS NULL + `; + const result = await pool.query(queryText, [brandId, errorCode]); + return result.recordset[0]; +}; + +// Get all error codes with pagination and search +const getAllErrorCodesDb = async (searchParams = {}) => { + let queryParams = []; + + // Pagination + if (searchParams.limit) { + const page = Number(searchParams.page ?? 1) - 1; + queryParams = [Number(searchParams.limit ?? 10), page]; + } + + // Search across multiple columns + const { whereOrConditions, whereParamOr } = pool.buildStringOrIlike( + ["a.error_code", "a.error_code_name", "a.error_code_description"], + searchParams.criteria, + queryParams + ); + + queryParams = whereParamOr ? whereParamOr : queryParams; + + // Filter conditions + const { whereConditions, whereParamAnd } = pool.buildFilterQuery( + [ + { column: "a.is_active", param: searchParams.status, type: "string" }, + { column: "a.brand_id", param: searchParams.brand_id, type: "number" }, + ], + queryParams + ); + + queryParams = whereParamAnd ? whereParamAnd : queryParams; + + const queryText = ` + SELECT + COUNT(*) OVER() AS total_data, + a.*, + b.brand_name, + b.brand_type, + b.brand_manufacture, + b.brand_model + FROM brand_code a + LEFT JOIN m_brands b ON a.brand_id = b.brand_id + WHERE a.deleted_at IS NULL + ${whereConditions.length > 0 ? `AND ${whereConditions.join(' AND ')}` : ''} + ${whereOrConditions ? whereOrConditions : ''} + ORDER BY a.error_code_id DESC + ${searchParams.limit ? `OFFSET $2 * $1 ROWS FETCH NEXT $1 ROWS ONLY` : ''} + `; + + const result = await pool.query(queryText, queryParams); + const total = result?.recordset.length > 0 ? parseInt(result.recordset[0].total_data, 10) : 0; + + return { data: result.recordset, total }; +}; + module.exports = { getErrorCodesByBrandIdDb, getErrorCodeByIdDb, + getErrorCodeByBrandAndCodeDb, createErrorCodeDb, updateErrorCodeDb, deleteErrorCodeDb, + getAllErrorCodesDb, }; \ No newline at end of file diff --git a/routes/error_code.route.js b/routes/error_code.route.js new file mode 100644 index 0000000..c7ba80c --- /dev/null +++ b/routes/error_code.route.js @@ -0,0 +1,19 @@ +const express = require('express'); +const ErrorCodeController = require('../controllers/error_code.controller'); +const verifyToken = require('../middleware/verifyToken'); +const verifyAccess = require('../middleware/verifyAccess'); + +const router = express.Router(); + +router.route('/brand/:brandId') + .get(verifyToken.verifyAccessToken, ErrorCodeController.getByBrandId) + .post(verifyToken.verifyAccessToken, verifyAccess(), ErrorCodeController.create); + +router.route('/brand/:brandId/:errorCode') + .put(verifyToken.verifyAccessToken, verifyAccess(), ErrorCodeController.update) + .delete(verifyToken.verifyAccessToken, verifyAccess(), ErrorCodeController.delete); + +router.route('/:id') + .get(verifyToken.verifyAccessToken, ErrorCodeController.getById); + +module.exports = router; \ No newline at end of file diff --git a/routes/index.js b/routes/index.js index ee0e37e..e6121bf 100644 --- a/routes/index.js +++ b/routes/index.js @@ -18,6 +18,7 @@ const notificationError = require("./notification_error.route") const notificationErrorSparepart = require("./notification_error_sparepart.route") const sparepart = require("./sparepart.route") const notificationErrorLog = require("./notification_error_log.route") +const errorCode = require("./error_code.route") router.use("/auth", auth); router.use("/user", users); @@ -38,5 +39,6 @@ router.use("/notification", notificationError) router.use("/notification-sparepart", notificationErrorSparepart) router.use("/sparepart", sparepart) router.use("/notification-log", notificationErrorLog) +router.use("/error-code", errorCode) module.exports = router; diff --git a/services/brand.service.js b/services/brand.service.js index 867f55a..256c676 100644 --- a/services/brand.service.js +++ b/services/brand.service.js @@ -51,66 +51,13 @@ class BrandService { } } - // Get brand by ID with complete data + // Get brand by ID (without error codes) static async getBrandById(id) { try { const brand = await getBrandByIdDb(id); if (!brand) throw new ErrorHandler(404, "Brand not found"); - const errorCodes = await getErrorCodesByBrandIdDb(brand.brand_id); - - const errorCodesWithSolutionsAndSpareparts = await Promise.all( - errorCodes.map(async (errorCode) => { - const solutions = await getSolutionsByErrorCodeIdDb( - errorCode.error_code_id - ); - - // Get spareparts for this error code - const errorCodeSpareparts = await getSparepartsByErrorCodeIdDb(errorCode.error_code_id); - - const solutionsWithFiles = await Promise.all( - solutions.map(async (solution) => { - let fileData = null; - // console.log('Processing solution:', { - // solution_id: solution.brand_code_solution_id, - // path_solution: solution.path_solution, - // type_solution: solution.type_solution - // }); - - if (solution.path_solution && solution.type_solution !== "text") { - fileData = await getFileUploadByPathDb(solution.path_solution); - console.log("File data found:", fileData); - } - - const enhancedSolution = { - ...solution, - file_upload_name: fileData?.file_upload_name || null, - path_document: fileData?.path_document || null, - }; - - // console.log('Enhanced solution:', { - // solution_id: enhancedSolution.brand_code_solution_id, - // original_path_solution: enhancedSolution.path_solution, - // path_document: enhancedSolution.path_document, - // file_upload_name: enhancedSolution.file_upload_name - // }); - - return enhancedSolution; - }) - ); - - return { - ...errorCode, - solution: solutionsWithFiles, - spareparts: errorCodeSpareparts, - }; - }) - ); - - return { - ...brand, - error_code: errorCodesWithSolutionsAndSpareparts, - }; + return brand; } catch (error) { throw new ErrorHandler(error.statusCode, error.message); } diff --git a/services/error_code.service.js b/services/error_code.service.js new file mode 100644 index 0000000..c8f03ba --- /dev/null +++ b/services/error_code.service.js @@ -0,0 +1,294 @@ +const { ErrorHandler } = require("../helpers/error"); +const { + getErrorCodesByBrandIdDb, + getErrorCodeByIdDb, + getErrorCodeByBrandAndCodeDb, + createErrorCodeDb, + updateErrorCodeDb, + deleteErrorCodeDb, + getAllErrorCodesDb, +} = require("../db/brand_code.db"); + +const { + getSolutionsByErrorCodeIdDb, + createSolutionDb, + updateSolutionDb, + deleteSolutionDb, +} = require("../db/brand_code_solution.db"); + +const { + getSparepartsByErrorCodeIdDb, + insertMultipleErrorCodeSparepartsDb, + updateErrorCodeSparepartsDb, +} = require("../db/brand_sparepart.db"); + +const { getFileUploadByPathDb } = require("../db/file_uploads.db"); + +class ErrorCodeService { + // Get all error codes with pagination and search + static async getAllErrorCodes(param) { + try { + const results = await getAllErrorCodesDb(param); + + // Enhance with solutions and spareparts for each error code + const errorCodesWithDetails = await Promise.all( + results.data.map(async (errorCode) => { + const solutions = await getSolutionsByErrorCodeIdDb(errorCode.error_code_id); + const spareparts = await getSparepartsByErrorCodeIdDb(errorCode.error_code_id); + + const solutionsWithFiles = await Promise.all( + solutions.map(async (solution) => { + let fileData = null; + + if (solution.path_solution && solution.type_solution !== "text") { + fileData = await getFileUploadByPathDb(solution.path_solution); + } + + return { + ...solution, + file_upload_name: fileData?.file_upload_name || null, + path_document: fileData?.path_document || null, + }; + }) + ); + + return { + ...errorCode, + solution: solutionsWithFiles, + spareparts: spareparts, + }; + }) + ); + + return { + ...results, + data: errorCodesWithDetails, + }; + } catch (error) { + throw new ErrorHandler(error.statusCode, error.message); + } + } + + // Get error code by ID with complete data + static async getErrorCodeById(id) { + try { + const errorCode = await getErrorCodeByIdDb(id); + if (!errorCode) throw new ErrorHandler(404, "Error code not found"); + + const solutions = await getSolutionsByErrorCodeIdDb(errorCode.error_code_id); + const spareparts = await getSparepartsByErrorCodeIdDb(errorCode.error_code_id); + + const solutionsWithFiles = await Promise.all( + solutions.map(async (solution) => { + let fileData = null; + + if (solution.path_solution && solution.type_solution !== "text") { + fileData = await getFileUploadByPathDb(solution.path_solution); + } + + return { + ...solution, + file_upload_name: fileData?.file_upload_name || null, + path_document: fileData?.path_document || null, + }; + }) + ); + + return { + ...errorCode, + solution: solutionsWithFiles, + spareparts: spareparts, + }; + } catch (error) { + throw new ErrorHandler(error.statusCode, error.message); + } + } + + // Get error codes by brand ID + static async getErrorCodesByBrandId(brandId) { + try { + const errorCodes = await getErrorCodesByBrandIdDb(brandId); + + const errorCodesWithDetails = await Promise.all( + errorCodes.map(async (errorCode) => { + const solutions = await getSolutionsByErrorCodeIdDb(errorCode.error_code_id); + const spareparts = await getSparepartsByErrorCodeIdDb(errorCode.error_code_id); + + const solutionsWithFiles = await Promise.all( + solutions.map(async (solution) => { + let fileData = null; + + if (solution.path_solution && solution.type_solution !== "text") { + fileData = await getFileUploadByPathDb(solution.path_solution); + } + + return { + ...solution, + file_upload_name: fileData?.file_upload_name || null, + path_document: fileData?.path_document || null, + }; + }) + ); + + return { + ...errorCode, + solution: solutionsWithFiles, + spareparts: spareparts, + }; + }) + ); + + return errorCodesWithDetails; + } catch (error) { + throw new ErrorHandler(error.statusCode, error.message); + } + } + + // Create error code with solutions and spareparts + static async createErrorCodeWithFullData(brandId, data) { + try { + if (!data || typeof data !== "object") data = {}; + + if ( + !data.solution || + !Array.isArray(data.solution) || + data.solution.length === 0 + ) { + throw new ErrorHandler( + 400, + "Error code must have at least 1 solution" + ); + } + + const errorId = await createErrorCodeDb(brandId, { + error_code: data.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, + created_by: data.created_by, + }); + + if (!errorId) { + throw new Error("Failed to create error code"); + } + + // Create sparepart relationships for this error code + if (data.spareparts && Array.isArray(data.spareparts)) { + await insertMultipleErrorCodeSparepartsDb(errorId, data.spareparts, data.created_by); + } + + // Create solutions for this error code + if (data.solution && Array.isArray(data.solution)) { + for (const solutionData of data.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 createdErrorCode = await this.getErrorCodeById(errorId); + return createdErrorCode; + } catch (error) { + throw new ErrorHandler(500, `Create error code failed: ${error.message}`); + } + } + + // Update error code with solutions and spareparts + static async updateErrorCodeWithFullData(brandId, errorCode, data) { + try { + const existingErrorCode = await getErrorCodeByBrandAndCodeDb(brandId, errorCode); + if (!existingErrorCode) throw new ErrorHandler(404, "Error code not found"); + + // Update error code + 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, + }); + + // Update spareparts if provided + if (data.spareparts && Array.isArray(data.spareparts)) { + await updateErrorCodeSparepartsDb(existingErrorCode.error_code_id, data.spareparts, data.updated_by); + } + + // Update solutions if provided + if (data.solution && Array.isArray(data.solution)) { + const existingSolutions = await getSolutionsByErrorCodeIdDb(existingErrorCode.error_code_id); + const incomingSolutionNames = data.solution.map((s) => s.solution_name); + + // Update or create solutions + for (const solutionData of data.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(existingErrorCode.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); + } + } + } + + const updatedErrorCode = await this.getErrorCodeById(existingErrorCode.error_code_id); + return updatedErrorCode; + } catch (error) { + throw new ErrorHandler(500, `Update error code failed: ${error.message}`); + } + } + + // Soft delete error code + static async deleteErrorCode(brandId, errorCode, deletedBy) { + try { + const errorCodeExist = await getErrorCodeByBrandAndCodeDb(brandId, errorCode); + + if (!errorCodeExist) { + throw new ErrorHandler(404, "Error code not found"); + } + + const result = await deleteErrorCodeDb(brandId, errorCode, deletedBy); + + return result; + } catch (error) { + throw new ErrorHandler(error.statusCode, error.message); + } + } +} + +module.exports = ErrorCodeService; \ No newline at end of file