Compare commits

..

16 Commits

Author SHA1 Message Date
30431be379 Merge pull request 'wisdom' (#13) from wisdom into main
Reviewed-on: #13
2025-11-17 08:27:07 +00:00
361f750330 Update ecosystem.config.js 2025-11-04 06:14:58 +00:00
31f50d05ab Update .gitignore 2025-11-04 06:12:17 +00:00
961f0d6314 Update ecosystem.config.js 2025-11-04 05:50:27 +00:00
d87fc07a8e Update ecosystem.config.js 2025-11-04 05:46:55 +00:00
95e0c90a16 Merge pull request 'wisdom' (#11) from wisdom into main
Reviewed-on: #11
2025-10-28 09:46:55 +00:00
55e8a6d9ca Merge pull request 'repair: params user_schedule' (#10) from wisdom into main
Reviewed-on: #10
2025-10-28 04:48:25 +00:00
253d83357f Merge pull request 'add: tag_description in tags schema' (#9) from wisdom into main
Reviewed-on: #9
2025-10-27 04:07:05 +00:00
88a0404af0 Merge pull request 'wisdom' (#8) from wisdom into main
Reviewed-on: #8
2025-10-27 03:48:36 +00:00
d11207aedb Merge pull request 'wisdom' (#7) from wisdom into main
Reviewed-on: #7
2025-10-25 09:19:08 +00:00
d7044521bd Merge pull request 'wisdom' (#6) from wisdom into main
Reviewed-on: #6
2025-10-24 05:42:48 +00:00
e2a008c2e1 Merge pull request 'wisdom' (#5) from wisdom into main
Reviewed-on: #5
2025-10-24 03:35:12 +00:00
6d575f649a Merge pull request 'wisdom' (#4) from wisdom into main
Reviewed-on: #4
2025-10-23 04:51:40 +00:00
e8fd307a05 Merge pull request 'wisdom' (#3) from wisdom into main
Reviewed-on: #3
2025-10-22 05:27:56 +00:00
00239db472 Merge pull request 'wisdom' (#2) from wisdom into main
Reviewed-on: #2
2025-10-20 03:26:32 +00:00
251f7148b6 Merge pull request 'wisdom' (#1) from wisdom into main
Reviewed-on: #1
2025-09-17 08:40:15 +00:00
72 changed files with 1316 additions and 4135 deletions

View File

@@ -23,11 +23,6 @@ SECRET=secret
# JWT refresh secret # JWT refresh secret
REFRESH_SECRET=refreshsecret REFRESH_SECRET=refreshsecret
# IMAGEKIT
IMAGEKIT_URL_ENDPOINT=https://ik.imagekit.io/j0hxk7x3p
IMAGEKIT_PUBLIC_KEY=public_iMPQFBnXmdQy73TTB9w4SMQO4Jk=
IMAGEKIT_PRIVATE_KEY=private_vhO/jXHnEoaVYptOHIuZDPMbxIA=
# mail server settings # mail server settings
# SMTP_FROM=youremail # SMTP_FROM=youremail
# SMTP_USER=youremail # SMTP_USER=youremail
@@ -45,18 +40,4 @@ IMAGEKIT_PRIVATE_KEY=private_vhO/jXHnEoaVYptOHIuZDPMbxIA=
# CLIENT_SECRET=xxxxxxxxxxxxxxxxxxxxxxxx # CLIENT_SECRET=xxxxxxxxxxxxxxxxxxxxxxxx
# REFRESH_TOKEN=1//XXXXXXXX # REFRESH_TOKEN=1//XXXXXXXX
ENDPOINT_WHATSAPP=https://117.102.231.130:9531/send
# ENDPOINT_WHATSAPP=http://localhost:9529/send
# ENDPOINT_FE=http://localhost:8592
# ENDPOINT_FE=http://203.153.114.226:8466
ENDPOINT_FE=https://117.102.231.130:9529
# MQTT Connection
MQTT_HOST=ws://117.102.231.130:7001
MQTT_USERNAME=morekmorekmorek
MQTT_PASSWORD=morek888
TOPIC_COD=PIU_COD/ERROR_CODE
VITE_KEY_SESSION=PetekRombonganPetekMorekMorakMarek VITE_KEY_SESSION=PetekRombonganPetekMorekMorakMarek

1
.gitignore vendored
View File

@@ -4,3 +4,4 @@ node_modules
request.http request.http
*.rest *.rest
package-lock.json package-lock.json
*.log

9
app.js
View File

@@ -8,8 +8,7 @@ const helmet = require("helmet");
const compression = require("compression"); const compression = require("compression");
const unknownEndpoint = require("./middleware/unKnownEndpoint"); const unknownEndpoint = require("./middleware/unKnownEndpoint");
const { handleError } = require("./helpers/error"); const { handleError } = require("./helpers/error");
const { checkConnection, mqttClient } = require("./config"); const { checkConnection } = require("./config");
const { onNotification } = require("./services/notifikasi-wa.service");
const app = express(); const app = express();
@@ -48,10 +47,4 @@ app.get("/check-db", async (req, res) => {
app.use(unknownEndpoint); app.use(unknownEndpoint);
app.use(handleError); app.use(handleError);
// Saat pesan diterima
mqttClient.on('message', (topic, message) => {
console.log(`Received message on topic "${topic}":`, message.toString());
onNotification(topic, message);
});
module.exports = app; module.exports = app;

View File

@@ -1,11 +1,8 @@
require("dotenv").config(); require("dotenv").config();
const { default: mqtt } = require("mqtt");
const sql = require("mssql"); const sql = require("mssql");
const isProduction = process.env.NODE_ENV === "production"; const isProduction = process.env.NODE_ENV === "production";
const endPointWhatsapp = process.env.ENDPOINT_WHATSAPP;
// Config SQL Server // Config SQL Server
const config = { const config = {
user: process.env.SQL_USERNAME, user: process.env.SQL_USERNAME,
@@ -287,34 +284,6 @@ async function generateKode(prefix, tableName, columnName) {
return prefix + String(nextNumber).padStart(3, "0"); return prefix + String(nextNumber).padStart(3, "0");
} }
// Koneksi ke broker MQTT
const mqttOptions = {
clientId: 'express_mqtt_client_' + Math.random().toString(16).substr(2, 8),
clean: true,
connectTimeout: 4000,
username: process.env.MQTT_USERNAME ?? 'morekmorekmorek', // jika ada
password: process.env.MQTT_PASSWORD ?? 'morek888', // jika ada
};
const mqttUrl = process.env.MQTT_HOST; // Ganti dengan broker kamu
const topic = process.env.TOPIC_COD ?? 'morek';
const mqttClient = mqtt.connect(mqttUrl, mqttOptions);
// Saat terkoneksi
mqttClient.on('connect', () => {
console.log('MQTT connected');
// Subscribe ke topik tertentu
mqttClient.subscribe(topic, (err) => {
if (!err) {
console.log(`Subscribed to topic "${topic}"`);
} else {
console.error('Subscribe error:', err);
}
});
});
module.exports = { module.exports = {
checkConnection, checkConnection,
query, query,
@@ -324,6 +293,4 @@ module.exports = {
buildDynamicInsert, buildDynamicInsert,
buildDynamicUpdate, buildDynamicUpdate,
generateKode, generateKode,
endPointWhatsapp,
mqttClient
}; };

View File

@@ -2,9 +2,6 @@ const AuthService = require('../services/auth.service');
const { setResponse, checkValidate } = require('../helpers/utils'); const { setResponse, checkValidate } = require('../helpers/utils');
const { registerSchema, loginSchema } = require('../validate/auth.schema'); const { registerSchema, loginSchema } = require('../validate/auth.schema');
const { createCaptcha } = require('../utils/captcha'); const { createCaptcha } = require('../utils/captcha');
const JWTService = require('../utils/jwt');
const CryptoJS = require('crypto-js');
class AuthController { class AuthController {
// Register // Register
@@ -97,44 +94,6 @@ class AuthController {
const response = await setResponse({ svg, text }, 'Captcha generated'); const response = await setResponse({ svg, text }, 'Captcha generated');
res.status(response.statusCode).json(response); res.status(response.statusCode).json(response);
} }
static async verifyTokenRedirect(req, res) {
const { tokenRedirect } = req.body;
const bytes = CryptoJS.AES.decrypt(tokenRedirect, process.env.VITE_KEY_SESSION);
const decrypted = JSON.parse(bytes.toString(CryptoJS.enc.Utf8));
console.log("decrypted: ", decrypted);
const userPhone = decrypted?.user_phone
const userName = decrypted?.user_name
const idData = decrypted?.id
const payload = {
user_id: userPhone,
user_fullname: userName,
};
const tokens = JWTService.generateTokenPair(payload);
// Simpan refresh token di cookie
res.cookie('refreshToken', tokens.refreshToken, {
httpOnly: true,
secure: false,
sameSite: 'lax',
maxAge: 7 * 24 * 60 * 60 * 1000
});
const response = await setResponse(
{
accessToken: tokens.accessToken,
idData
},
'Verify successful'
);
res.status(response.statusCode).json(response);
}
} }
module.exports = AuthController; module.exports = AuthController;

View File

@@ -1,5 +1,6 @@
const BrandService = require('../services/brand.service'); const BrandService = require('../services/brand.service');
const { setResponse, setResponsePaging, checkValidate } = require('../helpers/utils'); const { setResponse, setResponsePaging, checkValidate } = require('../helpers/utils');
const { createFileUploadDb } = require('../db/file_uploads.db');
const { const {
insertBrandSchema, insertBrandSchema,
updateBrandSchema, updateBrandSchema,
@@ -29,7 +30,7 @@ class BrandController {
res.status(response.statusCode).json(response); res.status(response.statusCode).json(response);
} }
// Create brand // Create brand with nested error codes and solutions
static async create(req, res) { static async create(req, res) {
const { error, value } = await checkValidate(insertBrandSchema, req); const { error, value } = await checkValidate(insertBrandSchema, req);
@@ -39,7 +40,7 @@ class BrandController {
value.created_by = req.user?.user_id || null; value.created_by = req.user?.user_id || null;
const results = await BrandService.createBrand(value); const results = await BrandService.createBrandWithFullData(value);
const response = await setResponse(results, 'Brand created successfully'); const response = await setResponse(results, 'Brand created successfully');
return res.status(response.statusCode).json(response); return res.status(response.statusCode).json(response);
@@ -50,17 +51,50 @@ class BrandController {
const { id } = req.params; const { id } = req.params;
const { error, value } = await checkValidate(updateBrandSchema, req); const { error, value } = await checkValidate(updateBrandSchema, req);
if (error) { if (error) {
return res.status(400).json(setResponse(error, 'Validation failed', 400)); return res.status(400).json(setResponse(error, 'Validation failed', 400));
} }
value.updated_by = req.user?.user_id || null; try {
if (req.file) {
const file = req.file;
const ext = require('path').extname(file.originalname).toLowerCase();
const typeDoc = ext === ".pdf" ? "PDF" : "IMAGE";
const folder = typeDoc === "PDF" ? "pdf" : "images";
const pathDocument = `${folder}/${file.filename}`;
const results = await BrandService.updateBrand(id, value); // Insert to file_upload table
const response = await setResponse(results, 'Brand updated successfully'); const fileData = {
file_upload_name: file.originalname,
createdBy: req.user?.user_id || null,
};
await createFileUploadDb(fileData);
res.status(response.statusCode).json(response); if (value.error_code && Array.isArray(value.error_code)) {
for (const errorCode of value.error_code) {
if (errorCode.solution && Array.isArray(errorCode.solution)) {
for (const solution of errorCode.solution) {
if (solution.type_solution !== 'text' && (!solution.path_solution || solution.path_solution === '')) {
solution.path_solution = pathDocument;
solution.type_solution = typeDoc.toLowerCase();
}
}
}
}
}
}
value.updated_by = req.user?.user_id || null;
const results = await BrandService.updateBrandWithFullData(id, value);
const response = await setResponse(results, 'Brand 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 brand by ID // Soft delete brand by ID

View File

@@ -0,0 +1,99 @@
const BrandSparepartService = require('../services/brand_sparepart.service');
const { setResponse, setResponsePaging, checkValidate } = require('../helpers/utils');
const {
insertBrandSparepartSchema,
updateBrandSparepartSchema,
} = require('../validate/brand_sparepart.schema');
class BrandSparepartController {
static async getAll(req, res) {
const queryParams = req.query;
const results = await BrandSparepartService.getAllBrandSparepart(queryParams);
const response = await setResponsePaging(queryParams, results, 'Brand sparepart found');
res.status(response.statusCode).json(response);
}
static async getById(req, res) {
const { id } = req.params;
const results = await BrandSparepartService.getBrandSparepartById(id);
const response = await setResponse(results, 'Brand sparepart found');
res.status(response.statusCode).json(response);
}
static async create(req, res) {
const { error, value } = await checkValidate(insertBrandSparepartSchema, req);
if (error) {
return res.status(400).json(setResponse(error, 'Validation failed', 400));
}
try {
if (req.file) {
const file = req.file;
const ext = require('path').extname(file.originalname).toLowerCase();
const typeDoc = ext === ".pdf" ? "PDF" : "IMAGE";
const folder = typeDoc === "PDF" ? "pdf" : "images";
const pathDocument = `${folder}/${file.filename}`;
value.path_foto = pathDocument;
}
value.created_by = req.user?.user_id || null;
const results = await BrandSparepartService.createBrandSparepart(value);
const response = await setResponse(results, 'Brand sparepart created successfully');
return res.status(response.statusCode).json(response);
} catch (err) {
const response = setResponse([], err.message, err.statusCode || 500);
res.status(response.statusCode).json(response);
}
}
static async update(req, res) {
const { id } = req.params;
const { error, value } = await checkValidate(updateBrandSparepartSchema, req);
if (error) {
return res.status(400).json(setResponse(error, 'Validation failed', 400));
}
try {
if (req.file) {
const file = req.file;
const ext = require('path').extname(file.originalname).toLowerCase();
const typeDoc = ext === ".pdf" ? "PDF" : "IMAGE";
const folder = typeDoc === "PDF" ? "pdf" : "images";
const pathDocument = `${folder}/${file.filename}`;
value.path_foto = pathDocument;
}
value.updated_by = req.user?.user_id || null;
const results = await BrandSparepartService.updateBrandSparepart(id, value);
const response = await setResponse(results, 'Brand sparepart updated successfully');
res.status(response.statusCode).json(response);
} catch (err) {
const response = setResponse([], err.message, err.statusCode || 500);
res.status(response.statusCode).json(response);
}
}
static async delete(req, res) {
const { id } = req.params;
const results = await BrandSparepartService.deleteBrandSparepart(id, req.user.user_id);
const response = await setResponse(results, 'Brand sparepart deleted successfully');
res.status(response.statusCode).json(response);
}
}
module.exports = BrandSparepartController;

View File

@@ -1,74 +0,0 @@
const ErrorCodeService = require('../services/error_code.service');
const { setResponse, setResponsePaging, checkValidate } = require('../helpers/utils');
const {
insertErrorCodeSchema,
updateErrorCodeSchema,
} = require('../validate/error_code.schema');
class ErrorCodeController {
static async getByBrandId(req, res) {
const { brandId } = req.params;
const queryParams = req.query;
const results = await ErrorCodeService.getErrorCodesByBrandId(brandId, queryParams);
const response = await setResponsePaging(queryParams, results, 'Error codes found');
res.status(response.statusCode).json(response);
}
// Get error code by ID
static async getById(req, res) {
const { id } = req.params;
const result = await ErrorCodeService.getErrorCodeById(id);
const response = setResponse(result, 'Error code found');
res.status(response.statusCode).json(response);
}
// 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;
value.created_by = req.user?.user_id || null;
const result = await ErrorCodeService.createErrorCodeWithFullData(brandId, value);
const response = setResponse(result, 'Error code created successfully');
res.status(response.statusCode).json(response);
}
// Update error code with solutions and spareparts
static async update(req, res) {
const { error, value } = await checkValidate(updateErrorCodeSchema, req);
if (error) {
return res.status(400).json(setResponse(error, 'Validation failed', 400));
}
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);
}
// Soft delete error code
static async delete(req, res) {
const { brandId, errorCodeId } = req.params;
const deletedBy = req.user?.user_id || null;
const result = await ErrorCodeService.deleteErrorCode(brandId, errorCodeId, deletedBy);
const response = setResponse(result, 'Error code deleted successfully');
res.status(response.statusCode).json(response);
}
}
module.exports = ErrorCodeController;

View File

@@ -32,7 +32,8 @@ const uploadFile = async (req, res) => {
const response = await setResponse( const response = await setResponse(
{ {
file_upload_name: file.originalname, file_upload_name: file.originalname,
path_document: pathDocument path_document: pathDocument,
path_solution: pathDocument
}, },
"File berhasil diunggah" "File berhasil diunggah"
); );
@@ -44,6 +45,8 @@ const uploadFile = async (req, res) => {
}; };
const getFileByPath = async (req, res) => { const getFileByPath = async (req, res) => {
try { try {
const { folder, filename } = req.params; const { folder, filename } = req.params;

View File

@@ -4,101 +4,52 @@ const { setResponsePaging } = require('../helpers/utils');
class HistoryValueController { class HistoryValueController {
static async getAllHistoryAlarm(req, res) { static async getAllHistoryAlarm(req, res) {
try { const queryParams = req.query;
const queryParams = req.query;
const results = await HistoryValue.getAllHistoryAlarm(queryParams); const results = await HistoryValue.getAllHistoryAlarm(queryParams);
const response = await setResponsePaging(queryParams, results, 'Data found'); const response = await setResponsePaging(queryParams, results, 'Data found');
res.status(response.statusCode).json(response); res.status(response.statusCode).json(response);
} catch (error) {
const statusCode = error.statusCode || 500;
res.status(statusCode).json({
success: false,
statusCode,
message: error.message || 'Internal server error'
});
}
} }
static async getAllHistoryEvent(req, res) { static async getAllHistoryEvent(req, res) {
try { const queryParams = req.query;
const queryParams = req.query;
const results = await HistoryValue.getAllHistoryEvent(queryParams); const results = await HistoryValue.getAllHistoryEvent(queryParams);
const response = await setResponsePaging(queryParams, results, 'Data found'); const response = await setResponsePaging(queryParams, results, 'Data found');
res.status(response.statusCode).json(response); res.status(response.statusCode).json(response);
} catch (error) {
const statusCode = error.statusCode || 500;
res.status(statusCode).json({
success: false,
statusCode,
message: error.message || 'Internal server error'
});
}
} }
static async getHistoryValueReport(req, res) { static async getHistoryValueReport(req, res) {
try { const queryParams = req.query;
const queryParams = req.query;
const results = await HistoryValue.getHistoryValueReport(queryParams); const results = await HistoryValue.getHistoryValueReport(queryParams);
const response = await setResponsePaging(queryParams, results, 'Data found'); const response = await setResponsePaging(queryParams, results, 'Data found');
res.status(response.statusCode).json(response); res.status(response.statusCode).json(response);
} catch (error) {
const statusCode = error.statusCode || 500;
res.status(statusCode).json({
success: false,
statusCode,
message: error.message || 'Internal server error'
});
}
} }
static async getHistoryValueReportPivot(req, res) { static async getHistoryValueReportPivot(req, res) {
try { const queryParams = req.query;
const queryParams = req.query;
const results = await HistoryValue.getHistoryValueReportPivot(queryParams); const results = await HistoryValue.getHistoryValueReportPivot(queryParams);
const response = await setResponsePaging(queryParams, results, 'Data found'); const response = await setResponsePaging(queryParams, results, 'Data found');
if (results.column) { response.columns = results.column
response.columns = results.column;
}
res.status(response.statusCode).json(response); res.status(response.statusCode).json(response);
} catch (error) {
const statusCode = error.statusCode || 500;
res.status(statusCode).json({
success: false,
statusCode,
message: error.message || 'Internal server error'
});
}
} }
static async getHistoryValueTrendingPivot(req, res) { static async getHistoryValueTrendingPivot(req, res) {
try { const queryParams = req.query;
const queryParams = req.query;
const results = await HistoryValue.getHistoryValueTrendingPivot(queryParams); const results = await HistoryValue.getHistoryValueTrendingPivot(queryParams);
const response = await setResponsePaging(queryParams, results, 'Data found'); const response = await setResponsePaging(queryParams, results, 'Data found');
if (results.column) { response.columns = results.column
response.columns = results.column;
}
res.status(response.statusCode).json(response); res.status(response.statusCode).json(response);
} catch (error) {
const statusCode = error.statusCode || 500;
res.status(statusCode).json({
success: false,
statusCode,
message: error.message || 'Internal server error'
});
}
} }
} }

View File

@@ -0,0 +1,80 @@
const NotificationService = require('../services/notification.service');
const { setResponse, setResponsePaging, checkValidate } = require('../helpers/utils');
const { insertNotificationSchema, updateNotificationSchema } = require('../validate/notification.schema');
class NotificationController {
static async getAll(req, res) {
const queryParams = req.query;
const results = await NotificationService.getAllNotification(queryParams);
const response = await setResponsePaging(queryParams, results, 'Notification found')
res.status(response.statusCode).json(response);
}
static async getById(req, res) {
try {
const { id } = req.params;
const results = await NotificationService.getNotificationById(id);
const response = await setResponse(results, 'Notification retrieved successfully');
return res.status(response.statusCode).json(response);
} catch (err) {
console.error(" Notification Error:", err.message);
return res.status(500).json(setResponse(err, 'Failed to fetch notification', 500));
}
}
static async create(req, res) {
try {
const { error, value } = await checkValidate(insertNotificationSchema, req);
if (error) {
return res.status(400).json(setResponse(error, 'Validation failed', 400));
}
value.created_by = req.user?.user_id || 'system';
const results = await NotificationService.createNotification(value);
const response = await setResponse(results, 'Notification created successfully');
return res.status(response.statusCode).json(response);
} catch (err) {
console.error("Notification Error:", err.message);
return res.status(500).json(setResponse(err, 'Failed to create notification', 500));
}
}
static async update(req, res) {
try {
const { id } = req.params;
const { error, value } = await checkValidate(updateNotificationSchema, req);
if (error) {
return res.status(400).json(setResponse(error, 'Validation failed', 400));
}
value.updated_by = req.user?.user_id || 'system';
const results = await NotificationService.updateNotification(id, value);
const response = await setResponse(results, 'Notification updated successfully');
return res.status(response.statusCode).json(response);
} catch (err) {
console.error("Notification Error:", err.message);
return res.status(500).json(setResponse(err, 'Failed to update notification', 500));
}
}
static async delete(req, res) {
try {
const { id } = req.params;
const results = await NotificationService.deleteNotification(id, req.user?.user_id || 'system');
const response = await setResponse(results, 'Notification deleted successfully');
return res.status(response.statusCode).json(response);
} catch (err) {
console.error("Notification Error:", err.message);
return res.status(500).json(setResponse(err, 'Failed to delete notification', 500));
}
}
}
module.exports = NotificationController;

View File

@@ -1,88 +0,0 @@
const NotificationErrorService = require("../services/notification_error.service");
const {
setResponse,
setResponsePaging,
checkValidate,
} = require("../helpers/utils");
const {
insertNotificationSchema,
updateNotificationSchema,
} = require("../validate/notification.schema");
class NotificationErrorController {
static async getAll(req, res) {
const queryParams = req.query;
const results = await NotificationErrorService.getAllNotification(
queryParams
);
const response = await setResponsePaging(
queryParams,
results,
"Notification found"
);
res.status(response.statusCode).json(response);
}
static async getById(req, res) {
const { id } = req.params;
const results = await NotificationErrorService.getNotificationById(id);
const response = await setResponse(
results,
"Notification retrieved successfully"
);
return res.status(response.statusCode).json(response);
}
static async create(req, res) {
const { error, value } = await checkValidate(insertNotificationSchema, req);
if (error) {
return res.status(400).json(setResponse(error, "Validation failed", 400));
}
value.userId = req.user.user_id;
const results = await NotificationErrorService.createNotificationError(
value
);
const response = await setResponse(
results,
"Notification created successfully"
);
return res.status(response.statusCode).json(response);
}
static async update(req, res) {
const { id } = req.params;
const userId = req.user.user_id;
const results = await NotificationErrorService.updateNotificationError(
id,
userId
);
const response = await setResponse(
results,
"Notification Error updated successfully"
);
res.status(response.statusCode).json(response);
}
static async resend(req, res) {
const { id } = req.params;
const results = await NotificationErrorService.resendNotification(id);
const response = await setResponse(
results,
"Notification Error resend successfully"
);
res.status(response.statusCode).json(response);
}
}
module.exports = NotificationErrorController;

View File

@@ -1,69 +0,0 @@
const NotificationErrorLogService = require('../services/notification_error_log.service');
const { setResponse, setResponsePaging, checkValidate } = require('../helpers/utils');
const { insertNotificationErrorLogSchema } = require('../validate/notification_error_log.schema');
class NotificationErrorLogController {
// Get all notification error logs
static async getAll(req, res) {
try {
const results = await NotificationErrorLogService.getAllNotificationErrorLog();
const response = await setResponse(results, 'Notification Error Logs found')
res.status(response.statusCode).json(response);
} catch (error) {
const response = await setResponse(error, error.message, error.statusCode || 500);
res.status(response.statusCode).json(response);
}
}
// Get notification error log by ID
static async getById(req, res) {
try {
const { id } = req.params;
const results = await NotificationErrorLogService.getNotificationErrorLogById(id);
const response = await setResponse(results, 'Notification Error Log found')
res.status(response.statusCode).json(response);
} catch (error) {
const response = await setResponse(error, error.message, error.statusCode || 500);
res.status(response.statusCode).json(response);
}
}
// Get notification error logs by notification_error_id
static async getByNotificationErrorId(req, res) {
try {
const { id } = req.params;
const results = await NotificationErrorLogService.getNotificationErrorLogByNotificationErrorId(id);
const response = await setResponse(results, 'Notification Error Logs found')
res.status(response.statusCode).json(response);
} catch (error) {
const response = await setResponse(error, error.message, error.statusCode || 500);
res.status(response.statusCode).json(response);
}
}
// Create notification error log
static async create(req, res) {
try {
const { error, value } = await checkValidate(insertNotificationErrorLogSchema, req)
if (error) {
return res.status(400).json(setResponse(error, 'Validation failed', 400));
}
const results = await NotificationErrorLogService.createNotificationErrorLog(value, req.user.user_id);
const response = await setResponse(results, 'Notification Error Log created successfully')
return res.status(response.statusCode).json(response);
} catch (error) {
const response = await setResponse(error, error.message, error.statusCode || 500);
return res.status(response.statusCode).json(response);
}
}
}
module.exports = NotificationErrorLogController;

View File

@@ -1,62 +0,0 @@
const NotificationErrorSparepart = require('../services/notification_error_sparepart.service');
const { setResponse, setResponsePaging, checkValidate } = require('../helpers/utils');
const { updateNotificationErrorSparepartSchema, insertNotificationErrorSparepartSchema } = require('../validate/notification_error_sparepart.schema');
class NotificationErrorSparepartController {
static async getAll(req, res) {
const { contact_id } = req.body;
const queryParams = req.query;
const results = await NotificationErrorSparepart.getAll(queryParams, contact_id);
const response = await setResponsePaging(queryParams, results, 'Notification Error Sparepart found');
res.status(response.statusCode).json(response);
}
static async getById(req, res) {
const { id } = req.params;
const results = await NotificationErrorSparepart.getById(id);
const response = await setResponse(results, 'Notification Error Sparepart found');
res.status(response.statusCode).json(response);
}
static async create(req, res) {
const { error, value } = await checkValidate(insertNotificationErrorSparepartSchema, req);
if (error) {
return res.status(400).json(setResponse(error, 'Validation failed', 400));
}
const results = await NotificationErrorSparepart.create(value);
const response = await setResponse(results, 'Notification Error Sparepart created successfully');
res.status(response.statusCode).json(response);
}
static async update(req, res) {
const { id } = req.params;
const { error, value } = await checkValidate(updateNotificationErrorSparepartSchema, req);
if (error) {
return res.status(400).json(setResponse(error, 'Validation failed', 400));
}
const results = await NotificationErrorSparepart.update(id, value);
const response = await setResponse(results, 'Notification Error Sparepart updated successfully');
res.status(response.statusCode).json(response);
}
static async delete(req, res) {
const { id } = req.params;
const { contact_id } = req.body;
const results = await NotificationErrorSparepart.delete(id, contact_id);
const response = await setResponse(results, 'Notification Error Sparepart deleted successfully');
res.status(response.statusCode).json(response);
}
}
module.exports = NotificationErrorSparepartController;

View File

@@ -1,123 +0,0 @@
const NotificationErrorUserService = require("../services/notification_error_user.service");
const {
setResponse,
setResponsePaging,
checkValidate,
} = require("../helpers/utils");
const {
insertNotificationErrorUserSchema,
updateNotificationErrorUserSchema,
} = require("../validate/notification_error_user.schema");
class NotificationErrorUserController {
// Get all NotificationErrorUser
static async getAll(req, res) {
const queryParams = req.query;
const results =
await NotificationErrorUserService.getAllNotificationErrorUser(
queryParams
);
const response = await setResponsePaging(
queryParams,
results,
"Notification Error User found"
);
res.status(response.statusCode).json(response);
}
// Get NotificationErrorUser by ID
static async getById(req, res) {
const { id } = req.params;
const results =
await NotificationErrorUserService.getNotificationErrorUserById(id);
const response = await setResponse(
results,
"Notification Error User found"
);
res.status(response.statusCode).json(response);
}
// Create NotificationErrorUser
static async create(req, res) {
const { error, value } = await checkValidate(
insertNotificationErrorUserSchema,
req
);
if (error) {
return res.status(400).json(setResponse(error, "Validation failed", 400));
}
value.userId = req.user.user_id;
const results =
await NotificationErrorUserService.createNotificationErrorUser(value);
const response = await setResponse(
results,
"Notification Error User created successfully"
);
return res.status(response.statusCode).json(response);
}
// Update NotificationErrorUser
static async update(req, res) {
const { id } = req.params;
const { error, value } = checkValidate(
updateNotificationErrorUserSchema,
req
);
if (error) {
return res.status(400).json(setResponse(error, "Validation failed", 400));
}
value.userId = req.user.user_id;
const results =
await NotificationErrorUserService.updateNotificationErrorUser(id, value);
const response = await setResponse(
results,
"Notification Error User updated successfully"
);
res.status(response.statusCode).json(response);
}
// Soft delete Notification Error User
static async delete(req, res) {
const { id } = req.params;
const results =
await NotificationErrorUserService.deleteNotificationErrorUser(
id,
req.user.user_id
);
const response = await setResponse(
results,
"Notification Error User deleted successfully"
);
res.status(response.statusCode).json(response);
}
static async resendByUser(req, res) {
const { id, contact_phone } = req.params;
const results = await NotificationErrorUserService.resendNotificationByUser(
id,
contact_phone
);
const response = await setResponse(
results,
"Notification Error By User resend successfully"
);
res.status(response.statusCode).json(response);
}
}
module.exports = NotificationErrorUserController;

View File

@@ -1,327 +0,0 @@
const ExcelJS = require("exceljs");
const path = require("path");
const ImageKit = require("imagekit");
const imagekit = new ImageKit({
publicKey: process.env.IMAGEKIT_PUBLIC_KEY,
privateKey: process.env.IMAGEKIT_PRIVATE_KEY,
urlEndpoint: process.env.IMAGEKIT_URL_ENDPOINT,
});
const SparepartService = require("../services/sparepart.service");
const {
setResponse,
setResponsePaging,
checkValidate,
} = require("../helpers/utils");
const {
insertSparepartSchema,
updateSparepartSchema,
} = require("../validate/sparepart.schema");
class SparepartController {
static async getAll(req, res) {
const queryParams = req.query;
const results = await SparepartService.getAllSparepart(queryParams);
const response = await setResponsePaging(
queryParams,
results,
"Sparepart found"
);
res.status(response.statusCode).json(response);
}
static async getById(req, res) {
const { id } = req.params;
const results = await SparepartService.getSparepartById(id);
const response = await setResponse(results, "Sparepart found");
res.status(response.statusCode).json(response);
}
static async create(req, res) {
const { error, value } = await checkValidate(insertSparepartSchema, req);
if (error) {
return res.status(400).json(setResponse(error, "Validation failed", 400));
}
try {
if (req.file) {
const upload = await imagekit.upload({
file: req.file.buffer,
fileName: req.file.originalname,
folder: "/sparepart",
});
value.sparepart_foto = upload.url;
}
value.userId = req.user.user_id;
const results = await SparepartService.createSparepart(value);
return res
.status(201)
.json(setResponse(results, "Sparepart created successfully"));
} catch (err) {
return res
.status(err.statusCode || 500)
.json(setResponse([], err.message, err.statusCode || 500));
}
}
static async update(req, res) {
const { id } = req.params;
const { error, value } = await checkValidate(updateSparepartSchema, req);
if (error) {
return res.status(400).json(setResponse(error, "Validation failed", 400));
}
try {
if (req.file) {
const upload = await imagekit.upload({
file: req.file.buffer,
fileName: req.file.originalname,
folder: "/sparepart",
});
value.sparepart_foto = upload.url;
}
value.userId = req.user.user_id;
const results = await SparepartService.updateSparepart(id, value);
return res
.status(200)
.json(setResponse(results, "Sparepart updated successfully"));
} catch (err) {
return res
.status(err.statusCode || 500)
.json(setResponse([], err.message, err.statusCode || 500));
}
}
static async exportExcel(req, res) {
try {
const queryParams = req.query || {};
queryParams.limit = null;
const results = await SparepartService.getAllSparepart(queryParams);
const workbook = new ExcelJS.Workbook();
const worksheet = workbook.addWorksheet("Sparepart Data");
worksheet.columns = [
{ header: "Name Sparepart", key: "sparepart_name", width: 30 },
{ header: "Code Sparepart", key: "sparepart_code", width: 20 },
{ header: "QTY Sparepart", key: "sparepart_qty", width: 15 },
{ header: "Merk", key: "sparepart_merk", width: 20 },
{ header: "Model", key: "sparepart_model", width: 20 },
{ header: "Unit", key: "sparepart_unit", width: 10 },
{ header: "Stock", key: "sparepart_stok", width: 10 },
{ header: "Foto", key: "sparepart_foto", width: 25 },
{ header: "Item Type", key: "sparepart_item_type", width: 25 },
{ header: "Dibuat Pada", key: "created_at", width: 20 },
];
worksheet.addRows(
results.data.map((item) => ({
...item,
sparepart_foto: "",
}))
);
for (let i = 0; i < results.data.length; i++) {
const item = results.data[i];
const rowNumber = i + 2;
if (!item.sparepart_foto) continue;
let imageUrl = item.sparepart_foto;
if (!imageUrl.startsWith("https")) continue;
let ext = path.extname(imageUrl).toLowerCase().replace(".", "");
if (!ext) ext = "jpg";
const supported = ["jpg", "jpeg", "png"];
if (!supported.includes(ext)) {
ext = "png";
}
let buffer;
try {
const resp = await fetch(imageUrl);
buffer = Buffer.from(await resp.arrayBuffer());
} catch (e) {
continue;
}
const imageId = workbook.addImage({
buffer,
extension: ext === "jpg" ? "jpeg" : ext,
});
worksheet.addImage(imageId, {
tl: { col: 7, row: rowNumber - 1 },
ext: { width: 80, height: 80 },
});
worksheet.getRow(rowNumber).height = 70;
}
worksheet.getRow(1).eachCell((cell) => {
cell.font = { bold: true };
cell.alignment = { horizontal: "center" };
});
const buffer = await workbook.xlsx.writeBuffer();
res.setHeader(
"Content-Type",
"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"
);
res.setHeader(
"Content-Disposition",
"attachment; filename=sparepart_data.xlsx"
);
return res.send(buffer);
} catch (error) {
return res.status(500).json({ message: error.message });
}
}
static async importExcel(req, res) {
try {
const workbook = new ExcelJS.Workbook();
await workbook.xlsx.load(req.file.buffer);
const worksheet = workbook.getWorksheet(1);
const images = worksheet.getImages();
const imageMap = {};
images.forEach((imgObj) => {
const imageId = imgObj.imageId;
const range = imgObj.range;
const row = range.tl.nativeRow + 1;
const image = workbook.getImage(imageId);
imageMap[row] = image;
});
const spareparts = [];
worksheet.eachRow({ includeEmpty: false }, (row, rowNumber) => {
if (rowNumber === 1) return;
const [
sparepart_name,
sparepart_code,
sparepart_description,
sparepart_qty_excel,
sparepart_merk,
sparepart_model,
sparepart_unit,
sparepart_stok_excel,
sparepart_foto_excel,
sparepart_item_type,
] = row.values.slice(1);
if (!sparepart_name) return;
if (!sparepart_code) {
return;
}
spareparts.push({
sparepart_name: sparepart_name || "",
sparepart_code: sparepart_code || "",
sparepart_description: sparepart_description || "",
sparepart_qty: Number(sparepart_qty_excel) || 0,
sparepart_merk: sparepart_merk || "",
sparepart_model: sparepart_model || "",
sparepart_unit: sparepart_unit || "",
sparepart_stok: sparepart_stok_excel || "",
sparepart_foto: sparepart_foto_excel || "",
sparepart_item_type: sparepart_item_type || "",
rowNumber,
});
});
if (spareparts.length === 0) {
return res
.status(400)
.json(setResponse([], "Tidak ada data valid untuk diimport", 400));
}
const results = [];
for (const data of spareparts) {
let uploadedUrl = "";
try {
const image = imageMap[data.rowNumber];
if (image) {
const fileName = `sparepart_${Date.now()}_${
data.sparepart_code
}.jpg`;
const uploadResult = await imagekit.upload({
file: image.buffer,
fileName: fileName,
folder: "/sparepart",
});
uploadedUrl = uploadResult.url;
}
} catch (err) {
err;
}
data.sparepart_foto = uploadedUrl || "";
const { rowNumber, ...dbData } = data;
const created = await SparepartService.createSparepart(dbData);
if (created && created[0]) {
results.push({
sparepart_id: created[0].sparepart_id,
sparepart_name: created[0].sparepart_name,
sparepart_code: created[0].sparepart_code,
sparepart_description: created[0].sparepart_description,
sparepart_qty: created[0].sparepart_qty,
sparepart_merk: created[0].sparepart_merk,
sparepart_model: created[0].sparepart_model,
sparepart_unit: created[0].sparepart_unit,
sparepart_stok: created[0].sparepart_stok,
sparepart_foto: created[0].sparepart_foto,
sparepart_item_type: created[0].sparepart_item_type,
});
}
}
return res.json(
setResponse(results, `${results.length} Sparepart berhasil diimport`)
);
} catch (error) {
return res.status(500).json({ message: error.message });
}
}
static async delete(req, res) {
const { id } = req.params;
const results = await SparepartService.deleteSparepart(
id,
req.user.user_id
);
const response = await setResponse(
results,
"Sparepart deleted successfully"
);
res.status(response.statusCode).json(response);
}
}
module.exports = SparepartController;

View File

@@ -1,58 +1,20 @@
const pool = require("../config"); const pool = require("../config");
// Get error codes by brand ID // Get error codes by brand ID
const getErrorCodesByBrandIdDb = async (brandId, searchParams = {}) => { const getErrorCodesByBrandIdDb = async (brandId) => {
let queryParams = [brandId];
// Pagination
if (searchParams.limit) {
const page = Number(searchParams.page ?? 1) - 1;
queryParams = [brandId, 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" },
],
queryParams
);
queryParams = whereParamAnd ? whereParamAnd : queryParams;
const queryText = ` const queryText = `
SELECT SELECT
COUNT(*) OVER() AS total_data,
a.* a.*
FROM brand_code a FROM brand_code a
WHERE a.brand_id = $1 AND a.deleted_at IS NULL WHERE a.brand_id = $1 AND a.deleted_at IS NULL
${whereConditions.length > 0 ? `AND ${whereConditions.join(' AND ')}` : ''} ORDER BY a.error_code_id
${whereOrConditions ? whereOrConditions : ''}
ORDER BY a.error_code_id DESC
${searchParams.limit ? `OFFSET $3 * $2 ROWS FETCH NEXT $2 ROWS ONLY` : ''}
`; `;
const result = await pool.query(queryText, [brandId]);
const result = await pool.query(queryText, queryParams);
// Return paginated format if limit is provided
if (searchParams.limit) {
const total = result?.recordset.length > 0 ? parseInt(result.recordset[0].total_data, 10) : 0;
return { data: result.recordset, total };
}
// Return simple array for backward compatibility
return result.recordset; return result.recordset;
}; };
// Create error code for brand
const createErrorCodeDb = async (brandId, data) => { const createErrorCodeDb = async (brandId, data) => {
const store = { const store = {
brand_id: brandId, brand_id: brandId,
@@ -71,6 +33,7 @@ const createErrorCodeDb = async (brandId, data) => {
return insertedId; return insertedId;
}; };
// Update error code by brand ID and error code
const updateErrorCodeDb = async (brandId, errorCode, data) => { const updateErrorCodeDb = async (brandId, errorCode, data) => {
const store = { ...data }; const store = { ...data };
const whereData = { const whereData = {
@@ -83,6 +46,7 @@ const updateErrorCodeDb = async (brandId, errorCode, data) => {
return true; return true;
}; };
// Soft delete error code by brand ID and error code
const deleteErrorCodeDb = async (brandId, errorCode, deletedBy) => { const deleteErrorCodeDb = async (brandId, errorCode, deletedBy) => {
const queryText = ` const queryText = `
UPDATE brand_code UPDATE brand_code
@@ -93,82 +57,10 @@ const deleteErrorCodeDb = async (brandId, errorCode, deletedBy) => {
return true; return true;
}; };
const getErrorCodeByIdDb = async (error_code_id) => {
const queryText = `
SELECT
a.*
FROM brand_code a
WHERE a.error_code_id = $1 AND a.deleted_at IS NULL
`;
const result = await pool.query(queryText, [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.*
FROM brand_code a
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 = { module.exports = {
getErrorCodesByBrandIdDb, getErrorCodesByBrandIdDb,
getErrorCodeByIdDb,
getErrorCodeByBrandAndCodeDb,
createErrorCodeDb, createErrorCodeDb,
updateErrorCodeDb, updateErrorCodeDb,
deleteErrorCodeDb, deleteErrorCodeDb,
getAllErrorCodesDb,
}; };

View File

@@ -1,187 +1,126 @@
const pool = require("../config"); const pool = require("../config");
// Get spareparts by error_code_id // Get all Contact
const getSparepartsByErrorCodeIdDb = async (errorCodeId) => { const getAllBrandSparepartsDb = async (searchParams = {}) => {
const queryText = ` let queryParams = [];
SELECT
s.sparepart_id,
s.sparepart_name,
s.sparepart_code,
s.sparepart_description,
s.sparepart_model,
s.sparepart_foto,
s.sparepart_item_type,
s.sparepart_qty,
s.sparepart_unit,
s.sparepart_merk,
s.sparepart_stok,
bs.created_at,
bs.created_by
FROM brand_sparepart bs
JOIN m_sparepart s ON bs.sparepart_id = s.sparepart_id
WHERE bs.error_code_id = $1
AND s.deleted_at IS NULL
ORDER BY s.sparepart_name
`;
const result = await pool.query(queryText, [errorCodeId]);
return result.recordset;
};
// Get error codes by sparepart_id if (searchParams.limit) {
const getErrorCodesBySparepartIdDb = async (sparepartId) => { const page = Number(searchParams.page ?? 1) - 1;
const queryText = ` queryParams = [Number(searchParams.limit ?? 10), page];
SELECT
ec.error_code_id,
ec.error_code,
ec.error_code_name,
ec.error_code_description,
ec.error_code_color,
ec.path_icon,
ec.is_active,
ec.created_at,
ec.updated_at
FROM brand_sparepart bs
JOIN m_error_codes ec ON bs.error_code_id = ec.error_code_id
WHERE bs.sparepart_id = $1
AND ec.deleted_at IS NULL
ORDER BY ec.error_code
`;
const result = await pool.query(queryText, [sparepartId]);
return result.recordset;
};
// Insert error_code-spareparts relationship
const insertErrorCodeSparepartDb = async (errorCodeId, sparepartId, createdBy) => {
const queryText = `
INSERT INTO brand_sparepart (error_code_id, sparepart_id, created_by, created_at)
VALUES ($1, $2, $3, CURRENT_TIMESTAMP)
`;
const result = await pool.query(queryText, [errorCodeId, sparepartId, createdBy]);
return result.recordset;
};
// Insert multiple error_code-spareparts relationships
const insertMultipleErrorCodeSparepartsDb = async (errorCodeId, sparepartIds, createdBy) => {
if (!sparepartIds || sparepartIds.length === 0) return [];
const values = sparepartIds.map((_, index) => `($1, $${index + 2}, $${sparepartIds.length + 2}, CURRENT_TIMESTAMP)`).join(', ');
const queryText = `
INSERT INTO brand_sparepart (error_code_id, sparepart_id, created_by, created_at)
VALUES ${values}
`;
const params = [errorCodeId, ...sparepartIds, createdBy];
const result = await pool.query(queryText, params);
return result.recordset;
};
// Delete specific error_code-sparepart relationship
const deleteErrorCodeSparepartDb = async (errorCodeId, sparepartId) => {
const queryText = `
DELETE FROM brand_sparepart
WHERE error_code_id = $1 AND sparepart_id = $2
`;
const result = await pool.query(queryText, [errorCodeId, sparepartId]);
return result.rowsAffected > 0;
};
// Delete all spareparts for an error_code
const deleteAllErrorCodeSparepartsDb = async (errorCodeId) => {
const queryText = `
DELETE FROM brand_sparepart
WHERE error_code_id = $1
`;
const result = await pool.query(queryText, [errorCodeId]);
return result.rowsAffected > 0;
};
// Update error_code-spareparts (replace all)
const updateErrorCodeSparepartsDb = async (errorCodeId, sparepartIds, updatedBy) => {
// Delete existing relationships
await deleteAllErrorCodeSparepartsDb(errorCodeId);
// Insert new relationships
if (sparepartIds && sparepartIds.length > 0) {
return await insertMultipleErrorCodeSparepartsDb(errorCodeId, sparepartIds, updatedBy);
} }
const { whereOrConditions, whereParamOr } = pool.buildStringOrIlike(
[
"a.sparepart_name",
"a.brand_sparepart_description",
"a.is_active"
],
searchParams.criteria,
queryParams
);
if (whereParamOr) queryParams = whereParamOr;
const { whereConditions, whereParamAnd } = pool.buildFilterQuery(
[
{ column: "a.sparepart_name", param: searchParams.sparepart_name, type: "string" },
{ column: "a.brand_sparepart_description", param: searchParams.brand_sparepart_description, type: "string" },
{ column: "a.is_active", param: searchParams.code, type: "int" },
],
queryParams
);
if (whereParamAnd) queryParams = whereParamAnd;
const queryText = `
SELECT
COUNT(*) OVER() AS total_data,
a.*
FROM brand_sparepart a
WHERE a.deleted_at IS NULL
${whereConditions.length > 0 ? ` AND ${whereConditions.join(" AND ")}` : ""}
${whereOrConditions ? ` ${whereOrConditions}` : ""}
ORDER BY a.brand_sparepart_id ASC
${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 };
};
const getBrandSparepartByIdDb = async (id) => {
const queryText = `
SELECT
a.*
FROM brand_sparepart a
WHERE a.brand_sparepart_id = $1 AND a.deleted_at IS NULL
`;
const result = await pool.query(queryText, [id]);
return result.recordset;
};
const createBrandSparepartDb = async (store) => {
const { query: queryText, values } = pool.buildDynamicInsert("brand_sparepart", store);
const result = await pool.query(queryText, values);
const insertedId = result.recordset?.[0]?.inserted_id;
return insertedId ? await getBrandSparepartByIdDb(insertedId) : null;
};
const updateBrandSparepartDb = async (id, data) => {
const store = { ...data };
const whereData = { brand_sparepart_id: id };
const { query: queryText, values } = pool.buildDynamicUpdate(
"brand_sparepart",
store,
whereData
);
await pool.query(`${queryText} AND deleted_at IS NULL`, values);
return getBrandSparepartByIdDb(id);
};
const deleteBrandSparepartDb = async (id, deletedBy) => {
const queryText = `
UPDATE brand_sparepart
SET deleted_at = CURRENT_TIMESTAMP, deleted_by = $1
WHERE brand_sparepart_id = $2 AND deleted_at IS NULL
`;
await pool.query(queryText, [deletedBy, id]);
return true; return true;
}; };
const checkErrorCodeSparepartExistsDb = async (errorCodeId, sparepartId) => { const checkBrandSparepartNameExistsDb = async (brandSparePartName, excludeId = null) => {
const queryText = ` let queryText = `
SELECT 1 SELECT brand_sparepart_id
FROM brand_spareparts FROM brand_sparepart
WHERE error_code_id = $1 AND sparepart_id = $2 WHERE sparepart_name = $1 AND deleted_at IS NULL
`; `;
const result = await pool.query(queryText, [errorCodeId, sparepartId]); let values = [brandSparePartName];
if (excludeId) {
queryText += ` AND brand_sparepart_id != $2`;
values.push(excludeId);
}
const result = await pool.query(queryText, values);
return result.recordset.length > 0; return result.recordset.length > 0;
}; };
const getSparepartsByBrandIdDb = async (brandId) => {
const queryText = `
SELECT DISTINCT
s.sparepart_id,
s.sparepart_name,
s.sparepart_code,
s.sparepart_description,
s.sparepart_model,
s.sparepart_foto,
s.sparepart_item_type,
s.sparepart_qty,
s.sparepart_unit,
s.sparepart_merk,
s.sparepart_stok,
s.created_at,
s.updated_at
FROM brand_sparepart bs
JOIN m_sparepart s ON bs.sparepart_id = s.sparepart_id
JOIN m_error_codes ec ON bs.error_code_id = ec.error_code_id
WHERE ec.brand_id = $1
AND s.deleted_at IS NULL
AND ec.deleted_at IS NULL
ORDER BY s.sparepart_name
`;
const result = await pool.query(queryText, [brandId]);
return result.recordset;
};
// Get brands by sparepart_id (now using error_code_id mapping)
const getBrandsBySparepartIdDb = async (sparepartId) => {
const queryText = `
SELECT DISTINCT
b.brand_id,
b.brand_name,
b.brand_type,
b.brand_manufacture,
b.brand_model,
b.brand_code,
b.is_active,
b.created_at,
b.updated_at
FROM brand_sparepart bs
JOIN m_sparepart s ON bs.sparepart_id = s.sparepart_id
JOIN m_error_codes ec ON bs.error_code_id = ec.error_code_id
JOIN m_brands b ON ec.brand_id = b.brand_id
WHERE bs.sparepart_id = $1
AND s.deleted_at IS NULL
AND ec.deleted_at IS NULL
AND b.deleted_at IS NULL
ORDER BY b.brand_name
`;
const result = await pool.query(queryText, [sparepartId]);
return result.recordset;
};
// Deprecated functions removed - table structure changed to use error_code_id instead of brand_id
module.exports = { module.exports = {
// New functions using error_code_id getAllBrandSparepartsDb,
getSparepartsByErrorCodeIdDb, getBrandSparepartByIdDb,
getErrorCodesBySparepartIdDb, createBrandSparepartDb,
insertErrorCodeSparepartDb, updateBrandSparepartDb,
insertMultipleErrorCodeSparepartsDb, deleteBrandSparepartDb,
deleteErrorCodeSparepartDb, checkBrandSparepartNameExistsDb
deleteAllErrorCodeSparepartsDb,
updateErrorCodeSparepartsDb,
checkErrorCodeSparepartExistsDb,
}; };

View File

@@ -24,7 +24,6 @@ const getAllContactDb = async (searchParams = {}) => {
[ [
{ column: "a.contact_name", param: searchParams.name, type: "string" }, { column: "a.contact_name", param: searchParams.name, type: "string" },
{ column: "a.contact_type", param: searchParams.code, type: "string" }, { column: "a.contact_type", param: searchParams.code, type: "string" },
{ column: "a.is_active", param: searchParams.active, type: "boolean" },
], ],
queryParams queryParams
); );

View File

@@ -115,321 +115,160 @@ const getHistoryEventDb = async (searchParams = {}) => {
}; };
const checkTableNamedDb = async (tableName) => { const checkTableNamedDb = async (tableName) => {
try { const queryText = `
if (!tableName || !/^[a-zA-Z0-9_]+$/.test(tableName)) { SELECT *
throw new Error('Invalid table name format'); FROM INFORMATION_SCHEMA.TABLES
} WHERE TABLE_NAME = $1;`;
const result = await pool.query(queryText, [tableName]);
const queryText = ` return result.recordset;
SELECT TABLE_NAME, TABLE_SCHEMA, TABLE_TYPE
FROM INFORMATION_SCHEMA.TABLES
WHERE TABLE_NAME = $1
`;
const result = await pool.query(queryText, [tableName]);
return result.recordset;
} catch (error) {
console.error('Error in checkTableNamedDb:', error);
throw error;
}
}; };
const getHistoryValueReportDb = async (tableName, searchParams = {}) => { const getHistoryValueReportDb = async (tableName, searchParams = {}) => {
try { let queryParams = [];
if (!tableName || !/^[a-zA-Z0-9_]+$/.test(tableName)) {
throw new Error('Invalid table name format');
}
let queryParams = []; if (searchParams.limit) {
const page = Number(searchParams.page ?? 1) - 1;
queryParams = [Number(searchParams.limit ?? 10), page];
}
if (searchParams.limit) { const { whereOrConditions, whereParamOr } = pool.buildStringOrIlike(
const page = Number(searchParams.page ?? 1) - 1; [
queryParams = [Number(searchParams.limit ?? 10), page]; "b.tag_name",
} "a.tagnum"
],
searchParams.criteria,
queryParams
);
const { whereOrConditions, whereParamOr } = pool.buildStringOrIlike( if (whereParamOr) queryParams = whereParamOr;
["b.tag_name", "CAST(a.tagnum AS VARCHAR)"],
searchParams.criteria,
queryParams
);
if (whereParamOr) queryParams = whereParamOr; const { whereConditions, whereParamAnd } = pool.buildFilterQuery(
[
{ column: "b.tag_name", param: searchParams.name, type: "string" },
{ column: "b.tag_number", param: searchParams.name, type: "number" },
{ column: "a.datetime", param: [searchParams.from, searchParams.to], type: "between" },
],
queryParams
);
const { whereConditions, whereParamAnd } = pool.buildFilterQuery( if (whereParamAnd) queryParams = whereParamAnd;
[
{ column: "b.tag_name", param: searchParams.name, type: "string" },
{ column: "b.tag_number", param: searchParams.name, type: "number" },
{ column: "a.datetime", param: [searchParams.from, searchParams.to], type: "between" },
],
queryParams
);
if (whereParamAnd) queryParams = whereParamAnd; const queryText = `
SELECT
COUNT(*) OVER() AS total_data,
a.*,
b.tag_name,
b.tag_number,
b.lim_low_crash,
b.lim_low,
b.lim_high,
b.lim_high_crash,
c.status_color
FROM ${tableName} a
LEFT JOIN m_tags b ON a.tagnum = b.tag_number AND b.deleted_at IS NULL
LEFT JOIN m_status c ON a.status = c.status_number AND c.deleted_at IS NULL
WHERE a.datetime IS NOT NULL AND b.is_report = 1
${whereConditions.length > 0 ? ` AND ${whereConditions.join(" AND ")}` : ""}
${whereOrConditions ? ` ${whereOrConditions}` : ""}
ORDER BY a.datetime DESC
${searchParams.limit ? `OFFSET $2 * $1 ROWS FETCH NEXT $1 ROWS ONLY` : ''}
`;
const queryText = ` const result = await pool.query(queryText, queryParams);
SELECT
COUNT(*) OVER() AS total_data,
a.*,
b.tag_name,
b.tag_number,
b.lim_low_crash,
b.lim_low,
b.lim_high,
b.lim_high_crash,
c.status_color
FROM ${tableName} a
LEFT JOIN m_tags b ON a.tagnum = b.tag_number AND b.deleted_at IS NULL
LEFT JOIN m_status c ON a.status = c.status_number AND c.deleted_at IS NULL
WHERE a.datetime IS NOT NULL AND b.is_report = 1
${whereConditions.length > 0 ? ` AND ${whereConditions.join(" AND ")}` : ""}
${whereOrConditions ? ` ${whereOrConditions}` : ""}
ORDER BY a.datetime 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
const total = result.recordset?.length > 0
? parseInt(result.recordset[0].total_data, 10) ? parseInt(result.recordset[0].total_data, 10)
: 0; : 0;
return { data: result.recordset, total }; return { data: result.recordset, total };
} catch (error) {
console.error('Error in getHistoryValueReportDb:', error);
throw error;
}
}; };
const getHistoryValueReportPivotDb = async (tableName, searchParams = {}) => { const getHistoryValueReportPivotDb = async (tableName, searchParams = {}) => {
try { let from = searchParams.from;
if (!tableName || !/^[a-zA-Z0-9_]+$/.test(tableName)) { let to = searchParams.to;
throw new Error('Invalid table name format'); const interval = Number(searchParams.interval ?? 10); // menit
} const limit = Number(searchParams.limit ?? 10);
const page = Number(searchParams.page ?? 1);
let from = searchParams.from || ''; // --- Normalisasi tanggal
let to = searchParams.to || ''; if (from.length === 10) from += ' 00:00:00';
const interval = Math.max(1, Math.min(1440, Number(searchParams.interval ?? 10))); if (to.length === 10) to += ' 23:59:59';
if (from.length === 10 && /^\d{4}-\d{2}-\d{2}$/.test(from)) { // --- Ambil semua tag yang di-report
from += ' 00:00:00'; const tags = await pool.query(`
} SELECT tag_name
if (to.length === 10 && /^\d{4}-\d{2}-\d{2}$/.test(to)) { FROM m_tags
to += ' 23:59:59'; WHERE is_report = 1 AND deleted_at IS NULL
} `);
console.log('Table:', tableName); if (tags.recordset.length === 0) {
console.log('From:', from, '| To:', to, '| Interval:', interval); return { data: [], total: 0 };
console.log('Filters:', searchParams);
const dateRegex = /^\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}$/;
if (!dateRegex.test(from) || !dateRegex.test(to)) {
throw new Error('Invalid date format. Expected: YYYY-MM-DD or YYYY-MM-DD HH:MM:SS');
}
const fromDate = new Date(from);
const toDate = new Date(to);
const daysDiff = (toDate - fromDate) / (1000 * 60 * 60 * 24);
if (daysDiff > 365) {
throw new Error('Date range cannot exceed 1 year');
}
if (daysDiff < 0) {
throw new Error('From date must be before to date');
}
let tagQueryParams = [];
let tagWhereConditions = [];
if (searchParams.plant_sub_section_id) {
tagWhereConditions.push(`plant_sub_section_id = $${tagQueryParams.length + 1}`);
tagQueryParams.push(searchParams.plant_sub_section_id);
}
if (searchParams.plant_section_id) {
tagWhereConditions.push(`plant_section_id = $${tagQueryParams.length + 1}`);
tagQueryParams.push(searchParams.plant_section_id);
}
if (searchParams.name) {
const nameFilter = `(tag_name LIKE $${tagQueryParams.length + 1} OR CAST(tag_number AS VARCHAR) LIKE $${tagQueryParams.length + 2})`;
tagWhereConditions.push(nameFilter);
tagQueryParams.push(`%${searchParams.name}%`, `%${searchParams.name}%`);
}
if (searchParams.criteria) {
const criteriaFilter = `(tag_name LIKE $${tagQueryParams.length + 1} OR CAST(tag_number AS VARCHAR) LIKE $${tagQueryParams.length + 2})`;
tagWhereConditions.push(criteriaFilter);
tagQueryParams.push(`%${searchParams.criteria}%`, `%${searchParams.criteria}%`);
}
const tagWhereClause = tagWhereConditions.length > 0
? ` AND ${tagWhereConditions.join(" AND ")}`
: '';
const tagsQuery = `
SELECT tag_name, tag_number
FROM m_tags
WHERE is_report = 1 AND deleted_at IS NULL
${tagWhereClause}
ORDER BY tag_name
`;
console.log('Tags Query:', tagsQuery);
console.log('Tags Query Params:', tagQueryParams);
const tagsResult = await pool.query(tagsQuery, tagQueryParams);
console.log('Tags found:', tagsResult.recordset.length);
if (tagsResult.recordset.length === 0) {
return { data: [], column: '' };
}
const tagNames = tagsResult.recordset.map(r => `[${r.tag_name}]`).join(', ');
const tagNamesColumn = tagsResult.recordset.map(r => r.tag_name).join(', ');
const tagNumbers = tagsResult.recordset.map(r => r.tag_number);
console.log('Filtered tag numbers:', tagNumbers);
console.log('Filtered tag names:', tagNamesColumn);
const tagNumbersFilter = tagNumbers.length > 0
? ` AND a.tagnum IN (${tagNumbers.join(',')})`
: '';
const queryText = `
DECLARE
@fromParam DATETIME = $1,
@toParam DATETIME = $2,
@intervalParam INT = $3;
SELECT TOP 10
'DEBUG_AVERAGING' as info,
b.tag_name,
DATEADD(MINUTE,
(DATEDIFF(MINUTE, @fromParam, CAST(a.datetime AS DATETIME)) / @intervalParam) * @intervalParam,
@fromParam
) AS waktu_group,
COUNT(*) as data_points,
AVG(CAST(a.val AS FLOAT)) as avg_val,
MIN(CAST(a.val AS FLOAT)) as min_val,
MAX(CAST(a.val AS FLOAT)) as max_val
FROM ${tableName} a
INNER JOIN m_tags b ON a.tagnum = b.tag_number
AND b.deleted_at IS NULL
AND b.is_report = 1
WHERE CAST(a.datetime AS DATETIME) BETWEEN @fromParam AND @toParam
AND a.val IS NOT NULL
${tagNumbersFilter}
GROUP BY
b.tag_name,
DATEADD(MINUTE,
(DATEDIFF(MINUTE, @fromParam, CAST(a.datetime AS DATETIME)) / @intervalParam) * @intervalParam,
@fromParam
)
ORDER BY b.tag_name, waktu_group;
;WITH TimeSeries AS (
SELECT @fromParam AS waktu
UNION ALL
SELECT DATEADD(MINUTE, @intervalParam, waktu)
FROM TimeSeries
WHERE DATEADD(MINUTE, @intervalParam, waktu) <= @toParam
),
CleanData AS (
SELECT
CAST(a.datetime AS DATETIME) as datetime_clean,
a.tagnum,
CAST(a.val AS FLOAT) as val,
b.tag_name
FROM ${tableName} a
INNER JOIN m_tags b ON a.tagnum = b.tag_number
AND b.deleted_at IS NULL
AND b.is_report = 1
WHERE ISDATE(a.datetime) = 1
AND a.val IS NOT NULL
AND CAST(a.datetime AS DATETIME) BETWEEN @fromParam AND @toParam
${tagNumbersFilter}
),
Averaged AS (
SELECT
DATEADD(MINUTE,
(DATEDIFF(MINUTE, @fromParam, datetime_clean) / @intervalParam) * @intervalParam,
@fromParam
) AS waktu_group,
tag_name,
AVG(val) AS avg_val
FROM CleanData
GROUP BY
DATEADD(MINUTE,
(DATEDIFF(MINUTE, @fromParam, datetime_clean) / @intervalParam) * @intervalParam,
@fromParam
),
tag_name
),
Pivoted AS (
SELECT
waktu_group,
${tagNames}
FROM Averaged
PIVOT (
MAX(avg_val)
FOR tag_name IN (${tagNames})
) AS p
)
SELECT
CONVERT(VARCHAR(19), ts.waktu, 120) AS waktu,
${tagNames}
FROM TimeSeries ts
LEFT JOIN Pivoted p ON ts.waktu = p.waktu_group
ORDER BY ts.waktu
OPTION (MAXRECURSION 0);
`;
const result = await pool.query(queryText, [from, to, interval]);
if (result.recordsets && result.recordsets.length >= 2) {
console.log('Sample averaging data:');
result.recordsets[0].slice(0, 10).forEach(row => {
console.log(`${row.tag_name} @ ${row.waktu_group}: avg=${row.avg_val}, min=${row.min_val}, max=${row.max_val}, points=${row.data_points}`);
});
console.log('\nPivot result sample:');
if (result.recordsets[1] && result.recordsets[1].length > 0) {
result.recordsets[1].slice(0, 5).forEach(row => {
console.log(JSON.stringify(row, null, 2));
});
}
}
const rows = result.recordsets?.[1] || result.recordset;
if (!rows || rows.length === 0) {
console.log('No pivot data');
return { data: [], column: tagNamesColumn };
}
const timeKey = 'waktu';
const tagList = Object.keys(rows[0]).filter(k => k !== timeKey);
const nivoData = tagList.map(tag => ({
id: tag,
data: rows.map(row => ({
x: row[timeKey],
y: row[tag] !== null && row[tag] !== undefined ? Number(row[tag]) : null
}))
}));
nivoData.forEach(series => {
const nonNull = series.data.filter(d => d.y !== null && d.y !== 0);
const sampleVals = nonNull.slice(0, 3).map(d => d.y);
console.log(`${series.id}: ${nonNull.length} non-zero values, sample: [${sampleVals.join(', ')}]`);
});
return { data: nivoData, column: tagNamesColumn };
} catch (error) {
console.error('Error in getHistoryValueReportPivotDb:', error);
throw error;
} }
const tagNames = tags.recordset.map(r => `[${r.tag_name}]`).join(', ');
const tagNamesColumn = tags.recordset.map(r => `${r.tag_name}`).join(', ');
// --- Query utama
const queryText = `
DECLARE
@fromParam DATETIME = '${from}',
@toParam DATETIME = '${to}',
@intervalParam INT = ${interval};
;WITH TimeSeries AS (
SELECT @fromParam AS waktu
UNION ALL
SELECT DATEADD(MINUTE, @intervalParam, waktu)
FROM TimeSeries
WHERE DATEADD(MINUTE, @intervalParam, waktu) <= @toParam
),
Averaged AS (
SELECT
DATEADD(MINUTE, DATEDIFF(MINUTE, 0, CAST(a.datetime AS DATETIME)) / @intervalParam * @intervalParam, 0) AS waktu_group,
b.tag_name,
ROUND(AVG(a.val), 4) AS avg_val
FROM ${tableName} a
LEFT JOIN m_tags b ON a.tagnum = b.tag_number AND b.deleted_at IS NULL
WHERE a.datetime BETWEEN @fromParam AND @toParam
GROUP BY
DATEADD(MINUTE, DATEDIFF(MINUTE, 0, CAST(a.datetime AS DATETIME)) / @intervalParam * @intervalParam, 0),
b.tag_name
),
Pivoted AS (
SELECT
waktu_group,
${tagNames}
FROM Averaged
PIVOT (
MAX(avg_val)
FOR tag_name IN (${tagNames})
) AS p
),
FinalResult AS (
SELECT
CONVERT(VARCHAR(16), ts.waktu, 120) AS datetime,
${tagNames}
FROM TimeSeries ts
LEFT JOIN Pivoted p ON ts.waktu = p.waktu_group
)
SELECT
COUNT(*) OVER() AS total_data,
*
FROM FinalResult
ORDER BY datetime
${searchParams.limit ? `OFFSET ${(page - 1) * limit} ROWS FETCH NEXT ${limit} ROWS ONLY` : ''}
OPTION (MAXRECURSION 0);
`;
const result = await pool.query(queryText);
const total =
result?.recordset?.length > 0
? parseInt(result.recordset[0].total_data, 10)
: 0;
return { data: result.recordset, column: tagNamesColumn, total };
}; };
const getHistoryValueTrendingPivotDb = async (tableName, searchParams = {}) => { const getHistoryValueTrendingPivotDb = async (tableName, searchParams = {}) => {
@@ -443,55 +282,18 @@ const getHistoryValueTrendingPivotDb = async (tableName, searchParams = {}) => {
if (from.length === 10) from += ' 00:00:00'; if (from.length === 10) from += ' 00:00:00';
if (to.length === 10) to += ' 23:59:59'; if (to.length === 10) to += ' 23:59:59';
let tagQueryParams = []; // --- Ambil semua tag yang di-report
let tagWhereConditions = []; const tags = await pool.query(`
SELECT tag_name
if (searchParams.plant_sub_section_id) {
tagWhereConditions.push(`plant_sub_section_id = $${tagQueryParams.length + 1}`);
tagQueryParams.push(searchParams.plant_sub_section_id);
}
if (searchParams.plant_section_id) {
tagWhereConditions.push(`plant_section_id = $${tagQueryParams.length + 1}`);
tagQueryParams.push(searchParams.plant_section_id);
}
if (searchParams.name) {
const nameFilter = `(tag_name LIKE $${tagQueryParams.length + 1} OR CAST(tag_number AS VARCHAR) LIKE $${tagQueryParams.length + 2})`;
tagWhereConditions.push(nameFilter);
tagQueryParams.push(`%${searchParams.name}%`, `%${searchParams.name}%`);
}
if (searchParams.criteria) {
const criteriaFilter = `(tag_name LIKE $${tagQueryParams.length + 1} OR CAST(tag_number AS VARCHAR) LIKE $${tagQueryParams.length + 2})`;
tagWhereConditions.push(criteriaFilter);
tagQueryParams.push(`%${searchParams.criteria}%`, `%${searchParams.criteria}%`);
}
const tagWhereClause = tagWhereConditions.length > 0
? ` AND ${tagWhereConditions.join(" AND ")}`
: '';
const tagsQuery = `
SELECT tag_name, tag_number
FROM m_tags FROM m_tags
WHERE is_report = 1 AND deleted_at IS NULL WHERE is_report = 1 AND deleted_at IS NULL
${tagWhereClause} `);
ORDER BY tag_name
`;
const tags = await pool.query(tagsQuery, tagQueryParams);
if (tags.recordset.length === 0) { if (tags.recordset.length === 0) {
return { data: [] }; return { data: [] };
} }
const tagNames = tags.recordset.map(r => `[${r.tag_name}]`).join(', '); const tagNames = tags.recordset.map(r => `[${r.tag_name}]`).join(', ');
const tagNumbers = tags.recordset.map(r => r.tag_number);
const tagNumbersFilter = tagNumbers.length > 0
? ` AND a.tagnum IN (${tagNumbers.join(',')})`
: '';
const queryText = ` const queryText = `
DECLARE DECLARE
@@ -514,7 +316,6 @@ const getHistoryValueTrendingPivotDb = async (tableName, searchParams = {}) => {
FROM ${tableName} a FROM ${tableName} a
LEFT JOIN m_tags b ON a.tagnum = b.tag_number AND b.deleted_at IS NULL LEFT JOIN m_tags b ON a.tagnum = b.tag_number AND b.deleted_at IS NULL
WHERE a.datetime BETWEEN @fromParam AND @toParam WHERE a.datetime BETWEEN @fromParam AND @toParam
${tagNumbersFilter}
GROUP BY GROUP BY
DATEADD(MINUTE, DATEDIFF(MINUTE, 0, CAST(a.datetime AS DATETIME)) / @intervalParam * @intervalParam, 0), DATEADD(MINUTE, DATEDIFF(MINUTE, 0, CAST(a.datetime AS DATETIME)) / @intervalParam * @intervalParam, 0),
b.tag_name b.tag_name
@@ -558,6 +359,7 @@ const getHistoryValueTrendingPivotDb = async (tableName, searchParams = {}) => {
return { data: nivoData }; return { data: nivoData };
}; };
module.exports = { module.exports = {
getHistoryAlarmDb, getHistoryAlarmDb,
getHistoryEventDb, getHistoryEventDb,

149
db/notification.db.js Normal file
View File

@@ -0,0 +1,149 @@
const pool = require("../config");
const InsertNotificationErrorDb = async () => {
const insertQuery = `
INSERT INTO notification_error (
error_code_id,
is_active,
is_delivered,
is_read,
is_send,
message_error_issue
)
SELECT
b.error_code_id,
1 AS is_active,
1 AS is_delivered,
0 AS is_read,
1 AS is_send,
CONCAT(
COALESCE(b.error_code_description, '-'),
' pada ',
COALESCE(d.device_name, '-'),
'. Pengecekan potensi kerusakan dibutuhkan'
) AS message_error_issue
FROM brand_code b
LEFT JOIN notification_error a
ON a.error_code_id = b.error_code_id
AND a.deleted_at IS NULL
LEFT JOIN m_device d
ON b.brand_id = d.brand_id
AND d.deleted_at IS NULL
WHERE b.deleted_at IS NULL
AND a.notification_error_id IS NULL;
`;
await pool.query(insertQuery);
};
const getAllNotificationDb = async (searchParams = {}) => {
let queryParams = [];
await InsertNotificationErrorDb();
if (searchParams.limit) {
const page = Number(searchParams.page ?? 1) - 1;
queryParams = [Number(searchParams.limit ?? 10), page];
}
// Build dynamic WHERE OR
const { whereOrConditions, whereParamOr } = pool.buildStringOrIlike(
[
"b.error_code",
"b.error_code_name",
"c.solution_name",
"COALESCE(a.is_send, 0)",
"COALESCE(a.is_delivered, 0)",
"COALESCE(a.is_read, 0)",
"COALESCE(a.is_active, 0)"
],
searchParams.criteria,
queryParams
);
if (whereParamOr) queryParams = whereParamOr;
// Build dynamic WHERE AND
const { whereConditions, whereParamAnd } = pool.buildFilterQuery(
[
{ column: "COALESCE(a.is_send, 0)", param: searchParams.is_send, type: "int" },
{ column: "COALESCE(a.is_delivered, 0)", param: searchParams.is_delivered, type: "int" },
{ column: "COALESCE(a.is_read, 0)", param: searchParams.is_read, type: "int" },
{ column: "COALESCE(a.is_active, 0)", param: searchParams.is_active, type: "int" },
],
queryParams
);
if (whereParamAnd) queryParams = whereParamAnd;
const queryText = `
SELECT
COUNT(*) OVER() AS total_data,
a.notification_error_id,
a.error_code_id,
a.message_error_issue,
a.is_send,
a.is_delivered,
a.is_read,
a.is_active,
b.error_code,
b.error_code_name,
b.created_at,
c.solution_name,
c.type_solution,
c.path_solution,
d.device_name,
d.device_location,
COALESCE(d.device_name, '') + ' - ' + COALESCE(b.error_code_name, '') AS device_name_error
FROM notification_error a
LEFT JOIN brand_code b
ON a.error_code_id = b.error_code_id
AND b.deleted_at IS NULL
LEFT JOIN brand_code_solution c
ON b.error_code_id = c.error_code_id
AND c.deleted_at IS NULL
LEFT JOIN m_device d
ON b.brand_id = d.brand_id
AND d.deleted_at IS NULL
WHERE a.deleted_at IS NULL
${
whereConditions.length > 0
? ` AND ${whereConditions.join(" AND ")}`
: ""
}
${whereOrConditions ? ` ${whereOrConditions}` : ""}
ORDER BY a.notification_error_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 = {
getAllNotificationDb,
};

View File

@@ -1,212 +0,0 @@
const pool = require("../config");
const InsertNotificationErrorDb = async (store) => {
const { query: queryText, values } = pool.buildDynamicInsert(
"notification_error",
store
);
const result = await pool.query(queryText, values);
const insertedId = result.recordset?.[0]?.inserted_id;
return insertedId ? await getNotificationByIdDb(insertedId) : null;
};
const getNotificationByIdDb = async (id) => {
const queryText = `
SELECT
a.*,
b.plant_sub_section_id,
c.plant_sub_section_name,
d.device_code,
d.device_name,
d.device_location,
d.listen_channel,
e.brand_name
FROM notification_error a
LEFT JOIN m_tags b
ON a.error_chanel = b.tag_number
LEFT JOIN m_plant_sub_section c
ON b.plant_sub_section_id = c.plant_sub_section_id
LEFT JOIN m_device d
ON a.error_chanel = d.listen_channel AND d.deleted_at IS NULL
LEFT JOIN m_brands e
ON d.brand_id = e.brand_id AND d.deleted_at IS NULL
WHERE a.notification_error_id = $1 AND a.deleted_at IS NULL
`;
const result = await pool.query(queryText, [id]);
return result.recordset[0];
};
const getDeviceNotificationByIdDb = async (chanel_id) => {
const queryText = `
SELECT
brand_id,
device_code,
device_name,
device_location,
listen_channel
FROM m_device
WHERE listen_channel = $1
AND deleted_at IS NULL
`;
const result = await pool.query(queryText, [chanel_id]);
return result.recordset[0];
};
const getAllNotificationDb = async (searchParams = {}) => {
let queryParams = [];
if (searchParams.limit) {
const page = Number(searchParams.page ?? 1) - 1;
queryParams = [Number(searchParams.limit ?? 10), page];
}
const { whereOrConditions, whereParamOr } = pool.buildStringOrIlike(
[
"a.message_error_issue",
"a.is_send",
"a.is_delivered",
"a.is_read",
"a.is_active",
"b.error_code",
"b.error_code_name",
],
searchParams.criteria,
queryParams
);
if (whereParamOr) queryParams = whereParamOr;
const { whereConditions, whereParamAnd } = pool.buildFilterQuery(
[
{ column: "a.message_error_issue", param: searchParams.message_error_issue, type: "string" },
{ column: "a.is_send", param: searchParams.is_send, type: "number" },
{ column: "a.is_delivered", param: searchParams.is_delivered, type: "number" },
{ column: "a.is_read", param: searchParams.is_read, type: "number" },
{ column: "a.is_active", param: searchParams.is_active, type: "number" },
{ column: "b.error_code", param: searchParams.error_code, type: "string" },
{ column: "b.error_code_name", param: searchParams.error_code_name, type: "string" },
],
queryParams
);
queryParams = whereParamAnd ? whereParamAnd : queryParams;
const queryText = `
SELECT
COUNT(*) OVER() AS total_data,
a.notification_error_id,
a.error_code_id,
a.message_error_issue,
a.is_send,
a.is_delivered,
a.is_read,
a.is_active,
a.created_at,
b.error_code,
b.error_code_name,
b.error_code_color,
b.path_icon,
c.solution_name,
c.type_solution,
c.path_solution,
d.device_code,
d.device_name,
d.device_location,
d.listen_channel,
e.brand_name,
COALESCE(d.device_name, '') + ' - ' + COALESCE(b.error_code_name, '') AS device_name_error
FROM notification_error a
LEFT JOIN brand_code b
ON a.error_code_id = b.error_code_id AND b.deleted_at IS NULL
LEFT JOIN brand_code_solution c
ON b.error_code_id = c.error_code_id AND c.deleted_at IS NULL
LEFT JOIN m_device d
ON a.error_chanel = d.listen_channel AND d.deleted_at IS NULL
LEFT JOIN m_brands e
ON d.brand_id = e.brand_id AND d.deleted_at IS NULL
WHERE a.deleted_at IS NULL
${whereConditions.length > 0 ? ` AND ${whereConditions.join(" AND ")}` : ""}
${whereOrConditions ? ` ${whereOrConditions}` : ""}
ORDER BY a.notification_error_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 };
};
const updateNotificationErrorDb = async (id, data) => {
const store = { ...data };
const whereData = { notification_error_id: id };
const { query: queryText, values } = pool.buildDynamicUpdate(
"notification_error",
store,
whereData
);
await pool.query(`${queryText} AND deleted_at IS NULL`, values);
return getNotificationByIdDb(id);
};
const getUsersNotificationErrorDb = async (id) => {
const queryText = `
SELECT
b.notification_error_id,
a.notification_error_user_id,
a.contact_phone,
a.contact_name,
a.is_send,
a.updated_at,
c.is_active
FROM notification_error_user a
LEFT JOIN notification_error b ON a.notification_error_id = b.notification_error_id
LEFT JOIN contact c ON a.contact_phone = c.contact_phone
WHERE a.notification_error_id = $1
AND a.deleted_at IS NULL
`;
const result = await pool.query(queryText, [id]);
return result.recordset;
};
module.exports = {
getNotificationByIdDb,
getDeviceNotificationByIdDb,
getAllNotificationDb,
InsertNotificationErrorDb,
updateNotificationErrorDb,
getUsersNotificationErrorDb
};

View File

@@ -1,94 +0,0 @@
const pool = require("../config");
const getAllNotificationErrorLogDb = async () => {
const queryText = `
SELECT
a.*,
b.contact_name,
b.contact_type
FROM notification_error_log a
LEFT JOIN contact b ON a.contact_phone = b.contact_phone
WHERE a.deleted_at IS NULL
ORDER BY a.notification_error_log_id DESC
`;
const result = await pool.query(queryText);
return result.recordset;
};
const getNotificationErrorLogByIdDb = async (id) => {
const queryText = `
SELECT
a.*,
b.contact_name,
b.contact_type
FROM notification_error_log a
LEFT JOIN contact b ON a.contact_phone = b.contact_phone
WHERE a.notification_error_log_id = $1 AND a.deleted_at IS NULL
`;
const result = await pool.query(queryText, [id]);
return result.recordset[0];
};
const getNotificationErrorLogByNotificationErrorIdDb = async (notificationErrorId) => {
const queryText = `
SELECT
a.notification_error_log_description,
a.created_at,
b.contact_type,
c.user_fullname as created_by_name,
case when a.created_by is not null then c.user_fullname else b.contact_name end as contact_name,
case when a.created_by is not null then c.user_phone else a.contact_phone end as contact_phone
FROM notification_error_log a
LEFT JOIN contact b ON a.contact_phone = b.contact_phone
LEFT JOIN m_users c ON a.created_by = c.user_id
WHERE a.notification_error_id = $1 AND a.deleted_at IS NULL
ORDER BY a.created_at DESC
`;
const result = await pool.query(queryText, [notificationErrorId]);
return result.recordset;
};
const createNotificationErrorLogDb = async (store) => {
const queryText = `
INSERT INTO notification_error_log (
notification_error_id,
contact_phone,
notification_error_log_description,
created_by,
updated_by,
created_at,
updated_at
)
VALUES ($1, $2, $3, $4, $4, CURRENT_TIMESTAMP, CURRENT_TIMESTAMP);
SELECT SCOPE_IDENTITY() as inserted_id;
`;
const values = [
store.notification_error_id,
store.contact_phone,
store.notification_error_log_description,
store.created_by
];
const result = await pool.query(queryText, values);
const insertedId = result.recordset[0]?.inserted_id;
return insertedId ? await getNotificationErrorLogByIdDb(insertedId) : null;
};
const deleteNotificationErrorLogDb = async (id, deletedBy) => {
const queryText = `
UPDATE notification_error_log
SET deleted_at = CURRENT_TIMESTAMP, deleted_by = $1
WHERE notification_error_log_id = $2 AND deleted_at IS NULL
`;
await pool.query(queryText, [deletedBy, id]);
return true;
};
module.exports = {
getAllNotificationErrorLogDb,
getNotificationErrorLogByIdDb,
getNotificationErrorLogByNotificationErrorIdDb,
createNotificationErrorLogDb,
deleteNotificationErrorLogDb,
};

View File

@@ -1,176 +0,0 @@
const pool = require("../config");
const insertNotificationErrorSparepartDb = async () => {
const insertQuery = `
INSERT INTO notification_error_sparepart (
notification_error_id,
brand_sparepart_id
)
SELECT
ne.notification_error_id,
bs.brand_sparepart_id
FROM notification_error ne
INNER JOIN brand_sparepart bs
ON ne.error_code_id = bs.error_code_id
LEFT JOIN notification_error_sparepart nes
ON nes.notification_error_id = ne.notification_error_id
AND nes.brand_sparepart_id = bs.brand_sparepart_id
AND nes.deleted_at IS NULL
WHERE ne.deleted_at IS NULL
AND nes.notification_error_sparepart_id IS NULL;
`;
await pool.query(insertQuery);
};
const getAllNotificationErrorSparepartDb = async (searchParams = {}) => {
await insertNotificationErrorSparepartDb();
let queryParams = [];
if (searchParams.limit) {
const page = Number(searchParams.page ?? 1) - 1;
queryParams = [Number(searchParams.limit ?? 10), page];
}
const { whereOrConditions, whereParamOr } = pool.buildStringOrIlike(
[
"a.brand_sparepart_id",
"a.device_id",
"a.sparepart_id",
"b.sparepart_name",
"d.device_name",
],
searchParams.criteria,
queryParams
);
if (whereParamOr) queryParams = whereParamOr;
const { whereConditions, whereParamAnd } = pool.buildFilterQuery(
[
{ column: "a.brand_sparepart_id", param: searchParams.name, type: "int" },
{ column: "a.device_id", param: searchParams.code, type: "int" },
{ column: "a.unit", param: searchParams.unit, type: "string" },
{ column: "b.sparepart_name", param: searchParams.device, type: "string" },
{ column: "d.device_name", param: searchParams.device, type: "string" },
],
queryParams
);
if (whereParamAnd) queryParams = whereParamAnd;
const queryText = `
SELECT
COUNT(*) OVER() AS total_data,
a.*,
b.sparepart_name,
b.sparepart_foto,
b.sparepart_stok,
b.sparepart_qty,
b.sparepart_description,
b.sparepart_model,
b.sparepart_merk,
b.sparepart_unit,
b.sparepart_item_type,
d.device_name
FROM notification_error_sparepart a
LEFT JOIN brand_sparepart c ON a.brand_sparepart_id = c.brand_sparepart_id
LEFT JOIN m_sparepart b ON c.sparepart_id = b.sparepart_id
LEFT JOIN m_device d on c.device_id = d.device_id
WHERE a.deleted_at IS NULL
${whereConditions.length > 0 ? ` AND ${whereConditions.join(" AND ")}` : ""}
${whereOrConditions ? ` ${whereOrConditions}` : ""}
ORDER BY a.notification_error_sparepart_id ASC
${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 };
};
const getNotificationErrorSparepartByIdDb = async (id) => {
const queryText = `
SELECT
a.*,
b.sparepart_name,
b.sparepart_foto,
b.sparepart_stok,
b.sparepart_qty,
b.sparepart_description,
b.sparepart_model,
b.sparepart_merk,
b.sparepart_unit,
b.sparepart_item_type,
d.device_name
FROM notification_error_sparepart a
LEFT JOIN brand_sparepart c ON a.brand_sparepart_id = c.brand_sparepart_id
LEFT JOIN m_sparepart b ON c.sparepart_id = b.sparepart_id
LEFT JOIN m_device d on c.device_id = d.device_id
WHERE a.notification_error_sparepart_id = $1
AND a.deleted_at IS NULL
`;
const result = await pool.query(queryText, [id]);
return result.recordset?.[0] || null;
};
const createNotificationErrorSparepartDb = async (store) => {
const { query: queryText, values } = pool.buildDynamicInsert("notification_error_sparepart", store);
const result = await pool.query(queryText, values);
const insertedId = result.recordset?.[0]?.inserted_id;
return insertedId ? await getNotificationErrorSparepartByIdDb(insertedId) : null;
};
const updateNotificationErrorSparepartDb = async (id, data) => {
const store = { ...data };
const whereData = { notification_error_sparepart_id: id };
const { query: queryText, values } = pool.buildDynamicUpdate(
"notification_error_sparepart",
store,
whereData
);
await pool.query(`${queryText} AND deleted_at IS NULL`, values);
return getNotificationErrorSparepartByIdDb(id);
};
// Soft delete tag
const deleteNotificationErrorSparepartDb = async (id, deletedBy) => {
const queryText = `
UPDATE notification_error_sparepart
SET deleted_at = CURRENT_TIMESTAMP, deleted_by = $1
WHERE notification_error_sparepart_id = $2 AND deleted_at IS NULL
`;
await pool.query(queryText, [deletedBy, id]);
return true;
};
module.exports = {
getAllNotificationErrorSparepartDb,
getNotificationErrorSparepartByIdDb,
createNotificationErrorSparepartDb,
updateNotificationErrorSparepartDb,
deleteNotificationErrorSparepartDb,
};

View File

@@ -1,147 +0,0 @@
const pool = require("../config");
// Get all Notification
const getAllNotificationErrorUserDb = async (searchParams = {}) => {
let queryParams = [];
if (searchParams.limit) {
const page = Number(searchParams.page ?? 1) - 1;
queryParams = [Number(searchParams.limit ?? 10), page];
}
const { whereOrConditions, whereParamOr } = pool.buildStringOrIlike(
["a.notification_error_id", "a.contact_id"],
searchParams.criteria,
queryParams
);
if (whereParamOr) queryParams = whereParamOr;
const { whereConditions, whereParamAnd } = pool.buildFilterQuery(
[
{
column: "a.notification_error_id",
param: searchParams.name,
type: "int",
},
{ column: "a.contact_id", param: searchParams.code, type: "int" },
],
queryParams
);
if (whereParamAnd) queryParams = whereParamAnd;
const queryText = `
SELECT
COUNT(*) OVER() AS total_data,
a.*
FROM notification_error_user a
WHERE a.deleted_at IS NULL
${
whereConditions.length > 0
? ` AND ${whereConditions.join(" AND ")}`
: ""
}
${whereOrConditions ? ` ${whereOrConditions}` : ""}
ORDER BY a.notification_error_user_id ASC
${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 };
};
const getNotificationErrorUserByIdDb = async (id) => {
const queryText = `
SELECT
a.*,
b. is_active as contact_is_active,
d.error_code,
d.error_code_name,
e.device_name
FROM notification_error_user a
LEFT JOIN contact b ON a.contact_phone = b.contact_phone
LEFT JOIN notification_error c ON a.notification_error_id = c.notification_error_id
LEFT JOIN brand_code d ON d.error_code_id = c.error_code_id
LEFT JOIN m_device e ON c.error_chanel = e.listen_channel
WHERE a.notification_error_user_id = $1 AND a.deleted_at IS NULL
`;
const result = await pool.query(queryText, [id]);
return result.recordset;
};
const getNotificationErrorByIdDb = async (notification_error_id) => {
const queryText = `
SELECT
a.*,
b.is_active as contact_is_active,
c.is_read
FROM notification_error_user a
LEFT JOIN contact b ON a.contact_phone = b.contact_phone
LEFT JOIN notification_error c ON a.notification_error_id = c.notification_error_id
WHERE a.notification_error_id = $1 AND a.deleted_at IS NULL
`;
const result = await pool.query(queryText, [notification_error_id]);
return result.recordset;
};
const createNotificationErrorUserDb = async (store) => {
const { query: queryText, values } = pool.buildDynamicInsert(
"notification_error_user",
store
);
const result = await pool.query(queryText, values);
const insertedId = result.recordset?.[0]?.inserted_id;
return insertedId ? await getNotificationErrorUserByIdDb(insertedId) : null;
};
const updateNotificationErrorUserDb = async (id, data) => {
const store = { ...data };
const whereData = { notification_error_user_id: id };
const { query: queryText, values } = pool.buildDynamicUpdate(
"notification_error_user",
store,
whereData
);
await pool.query(`${queryText} AND deleted_at IS NULL`, values);
return getNotificationErrorUserByIdDb(id);
};
// Soft delete tag
const deleteNotificationErrorUserDb = async (id, deletedBy) => {
const queryText = `
UPDATE notification_error_user
SET deleted_at = CURRENT_TIMESTAMP, deleted_by = $1
WHERE notification_error_user_id = $2 AND deleted_at IS NULL
`;
await pool.query(queryText, [deletedBy, id]);
return true;
};
module.exports = {
getAllNotificationErrorUserDb,
getNotificationErrorUserByIdDb,
getNotificationErrorByIdDb,
createNotificationErrorUserDb,
updateNotificationErrorUserDb,
deleteNotificationErrorUserDb,
};

View File

@@ -1,71 +0,0 @@
// db/notification_wa.db.js
const { default: axios } = require('axios');
const CryptoJS = require('crypto-js');
const https = require('https');
const httpsAgent = new https.Agent({
rejectUnauthorized: false,
});
const generateTokenRedirect = async (userPhone, userName, id) => {
const plain = {
user_phone: userPhone,
user_name: userName,
id
}
const tokenCrypt = CryptoJS.AES.encrypt(JSON.stringify(plain), process.env.VITE_KEY_SESSION).toString();
return tokenCrypt
}
const shortUrltiny = async (encodedToken) => {
const url = `${process.env.ENDPOINT_FE}/redirect?token=${encodedToken}`
const encodedUrl = encodeURIComponent(url); // ⬅️ Encode dulu!
const response = await axios.get(`https://tinyurl.com/api-create.php?url=${encodedUrl}`, { httpsAgent });
let shortUrl = response.data;
if (!shortUrl.startsWith('http')) {
shortUrl = 'https://' + shortUrl;
}
return shortUrl
}
const sendNotifikasi = async (phone, message) => {
const payload = {
phone: phone,
message: message
};
// console.log('payload', payload);
const endPointWhatsapp = process.env.ENDPOINT_WHATSAPP;
try {
const response = await axios.post(endPointWhatsapp, payload, { httpsAgent });
return {
success: true,
statusCode: response.status, // 200, 201, dll
data: response.data
};
} catch (error) {
return {
success: false,
statusCode: error.response?.status || null,
errorCode: error.code || null, // ECONNABORTED, dll
message: error.response?.data || error.message
};
}
};
module.exports = {
generateTokenRedirect,
shortUrltiny,
sendNotifikasi,
};

View File

@@ -1,187 +0,0 @@
const pool = require("../config");
// Get all devices
const getAllSparepartDb = async (searchParams = {}) => {
let queryParams = [];
// Pagination
if (searchParams.limit) {
const page = Number(searchParams.page ?? 1) - 1;
queryParams = [Number(searchParams.limit ?? 10), page];
}
// Search
const { whereOrConditions, whereParamOr } = pool.buildStringOrIlike(
[
"a.sparepart_name",
"a.sparepart_code",
"a.sparepart_model",
"a.updated_at",
],
searchParams.criteria,
queryParams
);
queryParams = whereParamOr ? whereParamOr : queryParams;
// Filter
const { whereConditions, whereParamAnd } = pool.buildFilterQuery(
[
{ column: "a.sparepart_name", param: searchParams.code, type: "string" },
{
column: "a.sparepart_code",
param: searchParams.location,
type: "string",
},
{
column: "b.sparepart_model",
param: searchParams.brand,
type: "string",
},
{ column: "a.updated_at", param: searchParams.status, type: "string" },
],
queryParams
);
queryParams = whereParamAnd ? whereParamAnd : queryParams;
const queryText = `
SELECT
COUNT(*) OVER() AS total_data,
a.*,
COALESCE(a.sparepart_code, '') + ' - ' + COALESCE(a.sparepart_name, '') AS sparepart_code_name
FROM m_sparepart a
WHERE a.deleted_at IS NULL
${
whereConditions.length > 0 ? `AND ${whereConditions.join(" AND ")}` : ""
}
${whereOrConditions ? whereOrConditions : ""}
ORDER BY a.sparepart_id ASC
${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 };
};
const getSparepartByIdDb = async (id) => {
const queryText = `
SELECT
a.*,
COALESCE(a.sparepart_code, '') + ' - ' + COALESCE(a.sparepart_name, '') AS sparepart_code_name
FROM m_sparepart a
WHERE a.sparepart_id = $1 AND a.deleted_at IS NULL
`;
const result = await pool.query(queryText, [id]);
return result.recordset;
};
const checkSparepartNameExistsDb = async (sparePartName, excludeId = null) => {
let queryText = `
SELECT sparepart_id
FROM m_sparepart
WHERE sparepart_name = $1 AND deleted_at IS NULL
`;
let values = [sparePartName];
if (excludeId) {
queryText += ` AND sparepart_id != $2`;
values.push(excludeId);
}
const result = await pool.query(queryText, values);
return result.recordset.length > 0;
};
const createSparepartDb = async (data) => {
const newCode = await pool.generateKode(
"SPAREPART",
"m_sparepart",
"sparepart_code"
);
const store = {
...data,
sparepart_code: newCode,
};
const { query: queryText, values } = pool.buildDynamicInsert(
"m_sparepart",
store
);
const result = await pool.query(queryText, values);
const insertedId = result.recordset[0]?.inserted_id;
return insertedId ? await getSparepartByIdDb(insertedId) : null;
};
const updateSparepartDb = async (id, data) => {
const store = {
...data,
};
// Kondisi WHERE
const whereData = {
sparepart_id: id,
};
const { query: queryText, values } = pool.buildDynamicUpdate(
"m_sparepart",
store,
whereData
);
await pool.query(`${queryText} AND deleted_at IS NULL`, values);
return getSparepartByIdDb(id);
};
const deleteSparepartDb = async (id, deletedBy) => {
const queryText = `
UPDATE m_sparepart
SET deleted_at = CURRENT_TIMESTAMP, deleted_by = $1
WHERE sparepart_id = $2 AND deleted_at IS NULL
`;
await pool.query(queryText, [deletedBy, id]);
return true;
};
// Get multiple spareparts by IDs
const getSparepartsByIdsDb = async (sparepartIds) => {
if (!sparepartIds || sparepartIds.length === 0) return [];
const placeholders = sparepartIds.map((_, index) => `$${index + 1}`).join(', ');
const queryText = `
SELECT
sparepart_id,
sparepart_name,
sparepart_code,
sparepart_description,
sparepart_model,
sparepart_foto,
sparepart_item_type,
sparepart_qty,
sparepart_unit,
sparepart_merk,
sparepart_stok,
created_at,
updated_at
FROM m_sparepart
WHERE sparepart_id IN (${placeholders})
AND deleted_at IS NULL
`;
const result = await pool.query(queryText, sparepartIds);
return result.recordset;
};
module.exports = {
getAllSparepartDb,
getSparepartByIdDb,
getSparepartsByIdsDb,
checkSparepartNameExistsDb,
createSparepartDb,
updateSparepartDb,
deleteSparepartDb,
};

View File

@@ -1,7 +1,7 @@
module.exports = { module.exports = {
apps: [ apps: [
{ {
name: "bengkel-api", name: "cod-api",
script: "./index.js", // Path to your entry file script: "./index.js", // Path to your entry file
env: { env: {
NODE_ENV: "development", NODE_ENV: "development",
@@ -9,6 +9,14 @@ module.exports = {
env_production: { env_production: {
NODE_ENV: "production", NODE_ENV: "production",
}, },
// Logging configuration
// error_file: "C:\IDETAMA\pm2-log\cod-api\cod-api-error.log",
// out_file: "C:\IDETAMA\pm2-log\cod-api\cod-api-out.log",
// log_file: "C:\IDETAMA\pm2-log\cod-api\cod-api-combined.log", // optional combined file
error_file: "cod-api-error.log",
out_file: "cod-api-out.log",
log_file: "cod-api-combined.log", // optional combined file
time: true, // adds timestamps to logs
}, },
], ],
}; };

View File

@@ -1,5 +0,0 @@
const multer = require("multer");
const storage = multer.memoryStorage();
module.exports = multer({ storage });

View File

@@ -1,10 +1,6 @@
const { ErrorHandler } = require("../helpers/error"); const { ErrorHandler } = require("../helpers/error");
const { getUserByIdDb } = require("../db/user.db"); const { getUserByIdDb } = require("../db/user.db");
function isPhoneNumberID(phone) {
return /^(?:\+62|62|0)8[1-9][0-9]{6,10}$/.test(phone);
}
const verifyAccess = (minLevel = 1, allowUnapprovedReadOnly = false) => { const verifyAccess = (minLevel = 1, allowUnapprovedReadOnly = false) => {
return async (req, res, next) => { return async (req, res, next) => {
try { try {
@@ -15,31 +11,21 @@ const verifyAccess = (minLevel = 1, allowUnapprovedReadOnly = false) => {
// Super Admin bypass semua // Super Admin bypass semua
if (user.is_sa) return next(); if (user.is_sa) return next();
const fullUser = await getUserByIdDb(user.user_id);
if (!fullUser) throw new ErrorHandler(403, "Forbidden: User not found");
if (!isPhoneNumberID(user.user_id) && user.user_id) { if (!fullUser.is_approve) {
const fullUser = await getUserByIdDb(user.user_id); if (req.method !== "GET") {
if (!fullUser) throw new ErrorHandler(403, "Forbidden: User not found"); throw new ErrorHandler(403, "Account not approved — read-only access");
if (!fullUser.is_approve) {
if (req.method !== "GET") {
throw new ErrorHandler(403, "Account not approved — read-only access");
}
if (allowUnapprovedReadOnly) return next();
throw new ErrorHandler(403, "Account not approved");
} }
if (!fullUser.role_level || fullUser.role_level < minLevel) { if (allowUnapprovedReadOnly) return next();
throw new ErrorHandler(403, "Forbidden: Insufficient role level");
}
} else {
if (req.method !== 'GET' && req.baseUrl !== '/api/notification-log') {
if (req.baseUrl !== '/api/notification') {
throw new ErrorHandler(403, "Forbidden: Insufficient Access");
}
}
throw new ErrorHandler(403, "Account not approved");
}
if (!fullUser.role_level || fullUser.role_level < minLevel) {
throw new ErrorHandler(403, "Forbidden: Insufficient role level");
} }
next(); next();

View File

@@ -30,7 +30,6 @@
"crypto": "^1.0.1", "crypto": "^1.0.1",
"crypto-js": "^4.2.0", "crypto-js": "^4.2.0",
"dotenv": "^8.2.0", "dotenv": "^8.2.0",
"exceljs": "^4.4.0",
"express": "^4.18.2", "express": "^4.18.2",
"express-async-errors": "^3.1.1", "express-async-errors": "^3.1.1",
"google-auth-library": "^8.7.0", "google-auth-library": "^8.7.0",

View File

@@ -7,6 +7,5 @@ router.post('/login', AuthController.login);
router.post('/register', AuthController.register); router.post('/register', AuthController.register);
router.get('/generate-captcha', AuthController.generateCaptcha); router.get('/generate-captcha', AuthController.generateCaptcha);
router.post('/refresh-token', AuthController.refreshToken); router.post('/refresh-token', AuthController.refreshToken);
router.post('/verify-redirect', AuthController.verifyTokenRedirect);
module.exports = router; module.exports = router;

View File

@@ -2,6 +2,7 @@ const express = require('express');
const BrandController = require('../controllers/brand.controller'); const BrandController = require('../controllers/brand.controller');
const verifyToken = require('../middleware/verifyToken'); const verifyToken = require('../middleware/verifyToken');
const verifyAccess = require('../middleware/verifyAccess'); const verifyAccess = require('../middleware/verifyAccess');
const upload = require('../middleware/uploads');
const router = express.Router(); const router = express.Router();
@@ -11,7 +12,7 @@ router.route('/')
router.route('/:id') router.route('/:id')
.get(verifyToken.verifyAccessToken, BrandController.getById) .get(verifyToken.verifyAccessToken, BrandController.getById)
.put(verifyToken.verifyAccessToken, verifyAccess(), BrandController.update) .put(verifyToken.verifyAccessToken, verifyAccess(), upload.single('file'), BrandController.update)
.delete(verifyToken.verifyAccessToken, verifyAccess(), BrandController.delete); .delete(verifyToken.verifyAccessToken, verifyAccess(), BrandController.delete);
module.exports = router; module.exports = router;

View File

@@ -0,0 +1,20 @@
const express = require('express');
const BrandSparepartController = require('../controllers/brand_sparepart.controller');
const verifyToken = require('../middleware/verifyToken');
const verifyAccess = require('../middleware/verifyAccess');
const upload = require('../middleware/uploads');
const router = express.Router();
router.route('/')
.get(verifyToken.verifyAccessToken, BrandSparepartController.getAll)
.post(verifyToken.verifyAccessToken, verifyAccess(), upload.single('path_foto')
, BrandSparepartController.create);
router.route('/:id')
.get(verifyToken.verifyAccessToken, BrandSparepartController.getById)
.put(verifyToken.verifyAccessToken, verifyAccess(), upload.single('path_foto')
, BrandSparepartController.update)
.delete(verifyToken.verifyAccessToken, verifyAccess(), BrandSparepartController.delete);
module.exports = router;

View File

@@ -1,19 +0,0 @@
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/:errorCodeId')
.put(verifyToken.verifyAccessToken, verifyAccess(), ErrorCodeController.update)
.delete(verifyToken.verifyAccessToken, verifyAccess(), ErrorCodeController.delete);
router.route('/:id')
.get(verifyToken.verifyAccessToken, ErrorCodeController.getById);
module.exports = router;

View File

@@ -14,12 +14,8 @@ const unit = require("./unit.route")
const UserSchedule = require("./user_schedule.route") const UserSchedule = require("./user_schedule.route")
const historyValue = require("./history_value.route") const historyValue = require("./history_value.route")
const contact = require("./contact.route") const contact = require("./contact.route")
const notificationError = require("./notification_error.route") const brandSparePart = require("./brand_sparepart.route")
const notificationErrorSparepart = require("./notification_error_sparepart.route") const notification = require("./notification.route")
const sparepart = require("./sparepart.route")
const notificationErrorLog = require("./notification_error_log.route")
const notificationErrorUser = require("./notification_error_user.route")
const errorCode = require("./error_code.route")
router.use("/auth", auth); router.use("/auth", auth);
router.use("/user", users); router.use("/user", users);
@@ -36,11 +32,8 @@ router.use("/unit", unit);
router.use("/user-schedule", UserSchedule) router.use("/user-schedule", UserSchedule)
router.use("/history", historyValue) router.use("/history", historyValue)
router.use("/contact", contact) router.use("/contact", contact)
router.use("/notification", notificationError) router.use("/brand-sparepart", brandSparePart)
router.use("/notification-sparepart", notificationErrorSparepart) router.use("/notification", notification)
router.use("/sparepart", sparepart)
router.use("/notification-log", notificationErrorLog)
router.use("/notification-user", notificationErrorUser)
router.use("/error-code", errorCode)
module.exports = router; module.exports = router;

View File

@@ -1,23 +1,23 @@
const express = require('express'); const express = require('express');
const NotificationErrorSparepartController = require('../controllers/notification_error_sparepart.controller'); const NotificationController = require('../controllers/notification.controller');
const verifyToken = require('../middleware/verifyToken'); const verifyToken = require('../middleware/verifyToken');
const verifyAccess = require('../middleware/verifyAccess'); const verifyAccess = require('../middleware/verifyAccess');
const router = express.Router(); const router = express.Router();
// =========================== // ===========================
// Notification Erro rSparepart Routes // Notification Routes
// =========================== // ===========================
router router
.route('/') .route('/')
.get(verifyToken.verifyAccessToken, NotificationErrorSparepartController.getAll) .get(verifyToken.verifyAccessToken, NotificationController.getAll)
.post(verifyToken.verifyAccessToken, verifyAccess(), NotificationErrorSparepartController.create); .post(verifyToken.verifyAccessToken, verifyAccess(), NotificationController.create);
router router
.route('/:id') .route('/:id')
.get(verifyToken.verifyAccessToken, NotificationErrorSparepartController.getById) .get(verifyToken.verifyAccessToken, NotificationController.getById)
.put(verifyToken.verifyAccessToken, verifyAccess(), NotificationErrorSparepartController.update) .put(verifyToken.verifyAccessToken, verifyAccess(), NotificationController.update)
.delete(verifyToken.verifyAccessToken, verifyAccess(), NotificationErrorSparepartController.delete); .delete(verifyToken.verifyAccessToken, verifyAccess(), NotificationController.delete);
module.exports = router; module.exports = router;

View File

@@ -1,40 +0,0 @@
const express = require("express");
const NotificationErrorController = require("../controllers/notification_error.controller");
const verifyToken = require("../middleware/verifyToken");
const verifyAccess = require("../middleware/verifyAccess");
const router = express.Router();
router
.route("/")
.get(
verifyToken.verifyAccessToken,
verifyAccess(),
NotificationErrorController.getAll
);
router
.route("/")
.post(
verifyToken.verifyAccessToken,
verifyAccess(),
NotificationErrorController.create
);
router
.route("/:id")
.get(verifyToken.verifyAccessToken, NotificationErrorController.getById)
.put(
verifyToken.verifyAccessToken,
verifyAccess(),
NotificationErrorController.update
);
router.post(
"/resend/:id",
verifyToken.verifyAccessToken,
verifyAccess(),
NotificationErrorController.resend
);
module.exports = router;

View File

@@ -1,21 +0,0 @@
const express = require('express');
const NotificationErrorLogController = require('../controllers/notification_error_log.controller');
const verifyToken = require("../middleware/verifyToken")
const verifyAccess = require("../middleware/verifyAccess")
const router = express.Router();
router.route("/")
.get(verifyToken.verifyAccessToken, NotificationErrorLogController.getAll)
.post(
verifyToken.verifyAccessToken,
verifyAccess(),
NotificationErrorLogController.create);
router.route("/:id")
.get(verifyToken.verifyAccessToken, NotificationErrorLogController.getById);
router.route("/notification_error/:id")
.get(verifyToken.verifyAccessToken, NotificationErrorLogController.getByNotificationErrorId);
module.exports = router;

View File

@@ -1,38 +0,0 @@
const express = require("express");
const NotificationErrorUserController = require("../controllers/notification_error_user.controller");
const verifyToken = require("../middleware/verifyToken");
const verifyAccess = require("../middleware/verifyAccess");
const router = express.Router();
router
.route("/")
.get(verifyToken.verifyAccessToken, NotificationErrorUserController.getAll)
.post(
verifyToken.verifyAccessToken,
verifyAccess(),
NotificationErrorUserController.create
);
router
.route("/:id")
.get(verifyToken.verifyAccessToken, NotificationErrorUserController.getById)
.put(
verifyToken.verifyAccessToken,
verifyAccess(),
NotificationErrorUserController.update
)
.delete(
verifyToken.verifyAccessToken,
verifyAccess(),
NotificationErrorUserController.delete
);
router.post(
"/resend/:id/:contact_phone",
verifyToken.verifyAccessToken,
verifyAccess(),
NotificationErrorUserController.resendByUser
);
module.exports = router;

View File

@@ -1,48 +0,0 @@
const express = require("express");
const SparepartController = require("../controllers/sparepart.controller");
const verifyToken = require("../middleware/verifyToken");
const verifyAccess = require("../middleware/verifyAccess");
const upload = require("../middleware/upload");
const router = express.Router();
router.get(
"/export",
verifyToken.verifyAccessToken,
SparepartController.exportExcel
);
router.post(
"/import",
verifyToken.verifyAccessToken,
verifyAccess(),
upload.single("file"),
SparepartController.importExcel
);
router
.route("/")
.get(verifyToken.verifyAccessToken, SparepartController.getAll)
.post(
verifyToken.verifyAccessToken,
verifyAccess(),
upload.single("sparepart_foto"),
SparepartController.create
);
router
.route("/:id")
.get(verifyToken.verifyAccessToken, SparepartController.getById)
.put(
verifyToken.verifyAccessToken,
verifyAccess(),
upload.single("sparepart_foto"),
SparepartController.update
)
.delete(
verifyToken.verifyAccessToken,
verifyAccess(),
SparepartController.delete
);
module.exports = router;

View File

@@ -2,12 +2,30 @@
const { const {
getAllBrandsDb, getAllBrandsDb,
getBrandByIdDb, getBrandByIdDb,
getBrandByNameDb,
createBrandDb, createBrandDb,
updateBrandDb, updateBrandDb,
deleteBrandDb, deleteBrandDb,
checkBrandNameExistsDb, checkBrandNameExistsDb,
} = require("../db/brand.db"); } = require('../db/brand.db');
const { ErrorHandler } = require("../helpers/error");
// Error code operations
const {
getErrorCodesByBrandIdDb,
createErrorCodeDb,
updateErrorCodeDb,
deleteErrorCodeDb,
} = require('../db/brand_code.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 { class BrandService {
// Get all brands // Get all brands
@@ -15,35 +33,93 @@ class BrandService {
try { try {
const results = await getAllBrandsDb(param); const results = await getAllBrandsDb(param);
results.data.map(element => {
});
return results;
} catch (error) {
throw new ErrorHandler(error.statusCode, error.message);
}
}
// Get brand by ID with complete data
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 errorCodesWithSolutions = await Promise.all(
errorCodes.map(async (errorCode) => {
const solutions = await getSolutionsByErrorCodeIdDb(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
};
})
);
return { return {
...results, ...brand,
data: results.data error_code: errorCodesWithSolutions
}; };
} catch (error) { } catch (error) {
throw new ErrorHandler(error.statusCode, error.message); throw new ErrorHandler(error.statusCode, error.message);
} }
} }
// 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");
return brand;
} catch (error) {
throw new ErrorHandler(error.statusCode, error.message);
}
}
// Create brand // Create brand
static async createBrand(data) { static async createBrandWithFullData(data) {
try { try {
if (!data || typeof data !== 'object') data = {};
if (data.brand_name) { if (data.brand_name) {
const brandExists = await checkBrandNameExistsDb(data.brand_name); const brandExists = await checkBrandNameExistsDb(data.brand_name);
if (brandExists) { if (brandExists) {
throw new ErrorHandler(400, "Brand name already exists"); throw new ErrorHandler(400, 'Brand name already exists');
}
}
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`);
} }
} }
@@ -52,18 +128,51 @@ class BrandService {
brand_type: data.brand_type, brand_type: data.brand_type,
brand_manufacture: data.brand_manufacture, brand_manufacture: data.brand_manufacture,
brand_model: data.brand_model, brand_model: data.brand_model,
is_active: data.is_active !== undefined ? data.is_active : true, is_active: data.is_active,
created_by: data.created_by, created_by: data.created_by
}; };
const createdBrand = await createBrandDb(brandData); const createdBrand = await createBrandDb(brandData);
if (!createdBrand) { if (!createdBrand) {
throw new ErrorHandler(500, "Failed to create brand"); throw new Error('Failed to create brand');
} }
return createdBrand; 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 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
});
}
}
}
return await this.getBrandById(brandId);
} catch (error) { } catch (error) {
throw new ErrorHandler(error.statusCode || 500, error.message); throw new ErrorHandler(500, `Bulk insert failed: ${error.message}`);
} }
} }
@@ -73,7 +182,7 @@ class BrandService {
const brandExist = await getBrandByIdDb(id); const brandExist = await getBrandByIdDb(id);
if (!brandExist) { if (!brandExist) {
throw new ErrorHandler(404, "Brand not found"); throw new ErrorHandler(404, 'Brand not found');
} }
const result = await deleteBrandDb(id, userId); const result = await deleteBrandDb(id, userId);
@@ -84,37 +193,124 @@ class BrandService {
} }
} }
// Update brand // Update brand
static async updateBrand(id, data) { static async updateBrandWithFullData(id, data) {
try { try {
const existingBrand = await getBrandByIdDb(id); const existingBrand = await getBrandByIdDb(id);
if (!existingBrand) throw new ErrorHandler(404, "Brand not found"); if (!existingBrand) throw new ErrorHandler(404, 'Brand not found');
if (data.brand_name && data.brand_name !== existingBrand.brand_name) { if (data.brand_name && data.brand_name !== existingBrand.brand_name) {
const brandExists = await checkBrandNameExistsDb(data.brand_name, id); const brandExists = await checkBrandNameExistsDb(data.brand_name, id);
if (brandExists) { if (brandExists) {
throw new ErrorHandler(400, "Brand name already exists"); throw new ErrorHandler(400, 'Brand name already exists');
}
} }
}
const brandData = { const brandData = {
brand_name: data.brand_name || existingBrand.brand_name, brand_name: data.brand_name,
brand_type: data.brand_type !== undefined ? data.brand_type : existingBrand.brand_type, brand_type: data.brand_type,
brand_manufacture: data.brand_manufacture !== undefined ? data.brand_manufacture : existingBrand.brand_manufacture, brand_manufacture: data.brand_manufacture,
brand_model: data.brand_model !== undefined ? data.brand_model : existingBrand.brand_model, brand_model: data.brand_model,
is_active: data.is_active !== undefined ? data.is_active : existingBrand.is_active, is_active: data.is_active,
updated_by: data.updated_by, updated_by: data.updated_by
}; };
const updatedBrand = await updateBrandDb(existingBrand.brand_name, brandData); await updateBrandDb(existingBrand.brand_name, brandData);
if (!updatedBrand) {
throw new ErrorHandler(500, "Failed to update brand");
}
return updatedBrand; 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.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.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);
}
}
}
return await this.getBrandById(id);
} catch (error) { } catch (error) {
throw new ErrorHandler(error.statusCode || 500, error.message); throw new ErrorHandler(500, `Update failed: ${error.message}`);
} }
} }
} }

View File

@@ -0,0 +1,106 @@
const {
getAllBrandSparepartsDb,
getBrandSparepartByIdDb,
createBrandSparepartDb,
updateBrandSparepartDb,
deleteBrandSparepartDb,
checkBrandSparepartNameExistsDb,
} = require('../db/brand_sparepart.db');
const { ErrorHandler } = require('../helpers/error');
class BrandSparepartService {
static async getAllBrandSparepart(param) {
try {
const results = await getAllBrandSparepartsDb(param);
results.data.map((item) => {});
return results;
} catch (error) {
throw new ErrorHandler(error.statusCode || 500, error.message);
}
}
static async getBrandSparepartById(id) {
try {
const brandSparepart = await getBrandSparepartByIdDb(id);
if (!brandSparepart) throw new ErrorHandler(404, 'Brand sparepart not found');
return brandSparepart;
} catch (error) {
throw new ErrorHandler(error.statusCode || 500, error.message);
}
}
static async createBrandSparepart(data) {
try {
if (!data || typeof data !== 'object') data = {};
if (data.sparepart_name) {
const exists = await checkBrandSparepartNameExistsDb(data.sparepart_name);
if (exists) throw new ErrorHandler(400, 'Brand sparepart name already exists');
}
const insertData = {
sparepart_name: data.sparepart_name,
brand_sparepart_description: data.brand_sparepart_description,
path_foto: data.path_foto,
error_code_id: data.error_code_id,
is_active: data.is_active,
created_by: data.created_by,
};
const created = await createBrandSparepartDb(insertData);
if (!created) throw new ErrorHandler(500, 'Failed to create brand sparepart');
return created;
} catch (error) {
throw new ErrorHandler(error.statusCode || 500, error.message);
}
}
static async updateBrandSparepart(id, data) {
try {
const existing = await getBrandSparepartByIdDb(id);
if (!existing) throw new ErrorHandler(404, 'Brand sparepart not found');
if (
data.sparepart_name &&
data.sparepart_name !== existing.sparepart_name
) {
const exists = await checkBrandSparepartNameExistsDb(data.sparepart_name, id);
if (exists) throw new ErrorHandler(400, 'Brand sparepart name already exists');
}
const updateData = {
sparepart_name: data.sparepart_name,
brand_sparepart_description: data.brand_sparepart_description,
path_foto: data.path_foto || existing.path_foto,
error_code_id: data.error_code_id,
is_active: data.is_active ?? existing.is_active,
updated_by: data.updated_by || null,
};
const updated = await updateBrandSparepartDb(id, updateData);
if (!updated) throw new ErrorHandler(500, 'Failed to update brand sparepart');
return await this.getBrandSparepartById(id);
} catch (error) {
throw new ErrorHandler(error.statusCode || 500, error.message);
}
}
static async deleteBrandSparepart(id, userId) {
try {
const existing = await getBrandSparepartByIdDb(id);
if (!existing) throw new ErrorHandler(404, 'Brand sparepart not found');
const deleted = await deleteBrandSparepartDb(id, userId);
if (!deleted) throw new ErrorHandler(500, 'Failed to delete brand sparepart');
return deleted;
} catch (error) {
throw new ErrorHandler(error.statusCode || 500, error.message);
}
}
}
module.exports = BrandSparepartService;

View File

@@ -1,265 +0,0 @@
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 {
static async getAllErrorCodes(param) {
try {
const results = await getAllErrorCodesDb(param);
return results;
} 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, queryParams = {}) {
try {
const results = await getErrorCodesByBrandIdDb(brandId, queryParams);
if (results.data && results.total !== undefined) {
return results;
}
const errorCodesWithDetails = await Promise.all(
results.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 = {};
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");
}
if (data.spareparts && Array.isArray(data.spareparts)) {
await insertMultipleErrorCodeSparepartsDb(errorId, data.spareparts, data.created_by);
}
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, errorCodeId, data) {
try {
const existingErrorCode = await getErrorCodeByIdDb(errorCodeId);
if (!existingErrorCode) throw new ErrorHandler(404, "Error code not found");
// 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 !== undefined ||
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: 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,
updated_by: data.updated_by,
});
}
if (data.spareparts && Array.isArray(data.spareparts)) {
await updateErrorCodeSparepartsDb(existingErrorCode.error_code_id, data.spareparts, data.updated_by);
}
if (data.solution && Array.isArray(data.solution)) {
const existingSolutions = await getSolutionsByErrorCodeIdDb(existingErrorCode.error_code_id);
const incomingSolutionNames = data.solution.map((s) => s.solution_name);
for (const solutionData of data.solution) {
const existingSolution = existingSolutions.find(
(s) => s.solution_name === solutionData.solution_name
);
if (existingSolution) {
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 {
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,
});
}
}
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, errorCodeId, deletedBy) {
try {
const errorCodeExist = await getErrorCodeByIdDb(errorCodeId);
if (!errorCodeExist) {
throw new ErrorHandler(404, "Error code not found");
}
// 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) {
throw new ErrorHandler(error.statusCode, error.message);
}
}
}
module.exports = ErrorCodeService;

View File

@@ -1,11 +1,4 @@
const { const { getHistoryAlarmDb, getHistoryEventDb, checkTableNamedDb, getHistoryValueReportDb, getHistoryValueReportPivotDb, getHistoryValueTrendingPivotDb } = require('../db/history_value.db');
getHistoryAlarmDb,
getHistoryEventDb,
checkTableNamedDb,
getHistoryValueReportDb,
getHistoryValueReportPivotDb,
getHistoryValueTrendingPivotDb
} = require('../db/history_value.db');
const { getSubSectionByIdDb } = require('../db/plant_sub_section.db'); const { getSubSectionByIdDb } = require('../db/plant_sub_section.db');
const { ErrorHandler } = require('../helpers/error'); const { ErrorHandler } = require('../helpers/error');
@@ -14,126 +7,92 @@ class HistoryValue {
static async getAllHistoryAlarm(param) { static async getAllHistoryAlarm(param) {
try { try {
const results = await getHistoryAlarmDb(param); const results = await getHistoryAlarmDb(param);
return results;
results.data.map(element => {
});
return results
} catch (error) { } catch (error) {
throw new ErrorHandler(error.statusCode || 500, error.message || 'Error fetching alarm history'); throw new ErrorHandler(error.statusCode, error.message);
} }
} }
static async getAllHistoryEvent(param) { static async getAllHistoryEvent(param) {
try { try {
const results = await getHistoryEventDb(param); const results = await getHistoryEventDb(param);
return results;
results.data.map(element => {
});
return results
} catch (error) { } catch (error) {
throw new ErrorHandler(error.statusCode || 500, error.message || 'Error fetching event history'); throw new ErrorHandler(error.statusCode, error.message);
} }
} }
static async getHistoryValueReport(param) { static async getHistoryValueReport(param) {
try { try {
if (!param.plant_sub_section_id) {
throw new ErrorHandler(400, 'plant_sub_section_id is required');
}
const plantSubSection = await getSubSectionByIdDb(param.plant_sub_section_id); const plantSubSection = await getSubSectionByIdDb(param.plant_sub_section_id);
if (!plantSubSection || plantSubSection.length < 1) { if (plantSubSection.length < 1) throw new ErrorHandler(404, 'Plant sub section not found');
throw new ErrorHandler(404, 'Plant sub section not found');
}
const tableNameValue = plantSubSection[0]?.table_name_value; const tabelExist = await checkTableNamedDb(plantSubSection[0]?.table_name_value);
if (!tableNameValue) { if (tabelExist.length < 1) throw new ErrorHandler(404, 'Value not found');
throw new ErrorHandler(404, 'Table name not configured for this sub section');
}
const tableExist = await checkTableNamedDb(tableNameValue); const results = await getHistoryValueReportDb(tabelExist[0]?.TABLE_NAME, param);
if (!tableExist || tableExist.length < 1) { results.data.map(element => {
throw new ErrorHandler(404, `Value table '${tableNameValue}' not found`); });
}
const results = await getHistoryValueReportDb(tableExist[0].TABLE_NAME, param); return results
return results;
} catch (error) { } catch (error) {
throw new ErrorHandler( throw new ErrorHandler(error.statusCode, error.message);
error.statusCode || 500,
error.message || 'Error fetching history value report'
);
} }
} }
static async getHistoryValueReportPivot(param) { static async getHistoryValueReportPivot(param) {
try { try {
if (!param.plant_sub_section_id) {
throw new ErrorHandler(400, 'plant_sub_section_id is required');
}
if (!param.from || !param.to) {
throw new ErrorHandler(400, 'from and to date parameters are required');
}
const plantSubSection = await getSubSectionByIdDb(param.plant_sub_section_id); const plantSubSection = await getSubSectionByIdDb(param.plant_sub_section_id);
if (!plantSubSection || plantSubSection.length < 1) { if (plantSubSection.length < 1) throw new ErrorHandler(404, 'Plant sub section not found');
throw new ErrorHandler(404, 'Plant sub section not found');
}
const tableNameValue = plantSubSection[0]?.table_name_value; const tabelExist = await checkTableNamedDb(plantSubSection[0]?.table_name_value);
if (!tableNameValue) { if (tabelExist.length < 1) throw new ErrorHandler(404, 'Value not found');
throw new ErrorHandler(404, 'Table name not configured for this sub section');
}
const tableExist = await checkTableNamedDb(tableNameValue); const results = await getHistoryValueReportPivotDb(tabelExist[0]?.TABLE_NAME, param);
if (!tableExist || tableExist.length < 1) { results.data.map(element => {
throw new ErrorHandler(404, `Value table '${tableNameValue}' not found`); });
}
const results = await getHistoryValueReportPivotDb(tableExist[0].TABLE_NAME, param); return results
return results;
} catch (error) { } catch (error) {
throw new ErrorHandler( throw new ErrorHandler(error.statusCode, error.message);
error.statusCode || 500,
error.message || 'Error fetching history value report pivot'
);
} }
} }
static async getHistoryValueTrendingPivot(param) { static async getHistoryValueTrendingPivot(param) {
try { try {
if (!param.plant_sub_section_id) {
throw new ErrorHandler(400, 'plant_sub_section_id is required');
}
if (!param.from || !param.to) {
throw new ErrorHandler(400, 'from and to date parameters are required');
}
const plantSubSection = await getSubSectionByIdDb(param.plant_sub_section_id); const plantSubSection = await getSubSectionByIdDb(param.plant_sub_section_id);
if (!plantSubSection || plantSubSection.length < 1) { if (plantSubSection.length < 1) throw new ErrorHandler(404, 'Plant sub section not found');
throw new ErrorHandler(404, 'Plant sub section not found');
}
const tableNameValue = plantSubSection[0]?.table_name_value; const tabelExist = await checkTableNamedDb(plantSubSection[0]?.table_name_value);
if (!tableNameValue) { if (tabelExist.length < 1) throw new ErrorHandler(404, 'Value not found');
throw new ErrorHandler(404, 'Table name not configured for this sub section');
}
const tableExist = await checkTableNamedDb(tableNameValue); const results = await getHistoryValueTrendingPivotDb(tabelExist[0]?.TABLE_NAME, param);
if (!tableExist || tableExist.length < 1) { results.data.map(element => {
throw new ErrorHandler(404, `Value table '${tableNameValue}' not found`); });
}
const results = await getHistoryValueTrendingPivotDb(tableExist[0].TABLE_NAME, param); return results
return results;
} catch (error) { } catch (error) {
throw new ErrorHandler( throw new ErrorHandler(error.statusCode, error.message);
error.statusCode || 500,
error.message || 'Error fetching history value trending pivot'
);
} }
} }
} }

View File

@@ -0,0 +1,85 @@
const {
getAllNotificationDb,
getNotificationByIdDb,
insertNotificationDb,
updateNotificationDb,
deleteNotificationDb,
} = require('../db/notification.db');
const { ErrorHandler } = require('../helpers/error');
class NotificationService {
// Get all Notifications
static async getAllNotification(param) {
try {
const results = await getAllNotificationDb(param);
results.data.map(element => {
});
return results;
} catch (error) {
throw new ErrorHandler(error.statusCode, error.message);
}
}
// Get notification by ID
static async getNotificationById(id) {
try {
const result = await getNotificationByIdDb(id);
if (!result || (Array.isArray(result) && result.length < 1)) {
throw new ErrorHandler(404, 'Notification not found');
}
return result;
} catch (error) {
throw new ErrorHandler(error.statusCode, error.message);
}
}
static async createNotification(data) {
try {
if (!data || typeof data !== 'object') data = {};
const result = await insertNotificationDb(data);
return result;
} catch (error) {
throw new ErrorHandler(error.statusCode, error.message);
}
}
static async updateNotification(id, data) {
try {
if (!data || typeof data !== 'object') data = {};
const dataExist = await getNotificationByIdDb(id);
if (!dataExist || (Array.isArray(dataExist) && dataExist.length < 1)) {
throw new ErrorHandler(404, 'Notification not found');
}
const result = await updateNotificationDb(id, data);
return result;
} catch (error) {
throw new ErrorHandler(error.statusCode, error.message);
}
}
static async deleteNotification(id, userId) {
try {
const dataExist = await getNotificationByIdDb(id);
if (!dataExist || (Array.isArray(dataExist) && dataExist.length < 1)) {
throw new ErrorHandler(404, 'Notification not found');
}
const result = await deleteNotificationDb(id, userId);
return result;
} catch (error) {
throw new ErrorHandler(error.statusCode, error.message);
}
}
}
module.exports = NotificationService;

View File

@@ -1,266 +0,0 @@
// services/notification_error.service.js
const {
getAllNotificationDb,
getNotificationByIdDb,
InsertNotificationErrorDb,
getUsersNotificationErrorDb,
updateNotificationErrorDb,
} = require("../db/notification_error.db");
const { getErrorCodeByIdDb } = require("../db/brand_code.db");
const { getSolutionsByErrorCodeIdDb } = require("../db/brand_code_solution.db");
const {
getAllNotificationErrorLogDb,
getNotificationErrorLogByNotificationErrorIdDb,
} = require("../db/notification_error_log.db");
const { getSparepartsByErrorCodeIdDb } = require("../db/brand_sparepart.db");
const {
getNotificationErrorByIdDb,
} = require("../db/notification_error_user.db");
const { getFileUploadByPathDb } = require("../db/file_uploads.db");
const {
generateTokenRedirect,
shortUrltiny,
sendNotifikasi,
} = require("../db/notification_wa.db");
const { ErrorHandler } = require("../helpers/error");
class NotificationService {
static async getAllNotification(param) {
try {
const results = await getAllNotificationDb(param);
if (results && Array.isArray(results.data)) {
results.data = await Promise.all(
results.data.map(async (notification) => {
const usersNotification =
(await getUsersNotificationErrorDb(
notification.notification_error_id
)) || [];
return {
...notification,
users: usersNotification,
};
})
);
}
return results;
} catch (error) {
throw new ErrorHandler(error.statusCode, error.message);
}
}
static async createNotificationError(data) {
try {
if (!data || typeof data !== "object") data = {};
const result = await InsertNotificationErrorDb(data);
return result;
} catch (error) {
return error;
}
}
static async getNotificationById(id) {
try {
const notification = await getNotificationByIdDb(id);
if (
!notification ||
(Array.isArray(notification) && notification.length < 1)
) {
throw new ErrorHandler(404, "Notification not found");
}
const usersNotification = (await getUsersNotificationErrorDb(id)) || [];
// Get error code details if error_code_id exists
if (notification.error_code_id) {
const errorCode = await getErrorCodeByIdDb(notification.error_code_id);
if (errorCode) {
// Get solutions for this error code
const solutions =
(await getSolutionsByErrorCodeIdDb(errorCode.error_code_id)) || [];
const spareparts =
(await getSparepartsByErrorCodeIdDb(errorCode.error_code_id)) || [];
const solutionsWithDetails = await Promise.all(
solutions.map(async (solution) => {
let fileData = null;
if (
solution.path_solution &&
solution.type_solution &&
solution.type_solution !== "text"
) {
try {
fileData = await getFileUploadByPathDb(
solution.path_solution
);
} catch (e) {
fileData = null;
}
}
return {
...solution,
file_upload_name: fileData?.file_upload_name || null,
path_document: fileData?.path_document || null,
};
})
);
notification.error_code = {
...errorCode,
solution: solutionsWithDetails,
spareparts: spareparts,
};
}
}
// Get activity logs for this notification
const notificationLogs =
(await getNotificationErrorLogByNotificationErrorIdDb(id)) || [];
notification.users = usersNotification;
notification.activity_logs = notificationLogs;
return notification;
} catch (error) {
throw new ErrorHandler(error.statusCode, error.message);
}
}
static async updateNotificationError(notification_error_id, data) {
try {
const dataExist = await getNotificationErrorByIdDb(notification_error_id);
if (!dataExist || (Array.isArray(dataExist) && dataExist.length < 1)) {
throw new ErrorHandler(404, "Notification Error User not found");
}
const notification = Array.isArray(dataExist) ? dataExist[0] : dataExist;
if (notification.is_read === true) {
return { success: true, message: "Notification has already been read" };
}
if (!notification.is_read) {
const updateStatus = await updateNotificationErrorDb(
notification_error_id,
{ is_read: true }
);
if (!updateStatus) {
throw new ErrorHandler(500, "Failed to update notification");
}
}
return { success: true, message: "Notification marked as read" };
} catch (error) {
throw new ErrorHandler(error.statusCode || 500, error.message);
}
}
static async resendNotification(id) {
const deviceNotification = await getNotificationByIdDb(id);
if (!deviceNotification) throw new ErrorHandler(404, "Data not found");
const errorCode = await getErrorCodeByIdDb(
deviceNotification.error_code_id
);
const dataExist = await getUsersNotificationErrorDb(id);
const activeUsers =
dataExist?.filter((user) => user.is_active === true) || [];
if (activeUsers.length < 1)
throw new ErrorHandler(404, "No active contacts");
this._executeResendWa(
id,
activeUsers,
deviceNotification,
errorCode
).catch((err) => console.error("Background Process Error:", err));
return {
count: activeUsers.length,
};
}
static async _executeResendWa(
id,
activeUsers,
deviceNotification,
errorCode
) {
console.log(`user active: `, id, activeUsers);
const sendPromises = activeUsers.map(async (user) => {
try {
console.log(`user: ${user.contact_name} (${user.contact_phone})`);
const tokenRedirect = await generateTokenRedirect(
user.contact_phone,
user.contact_name,
id
);
const encodedToken = encodeURIComponent(tokenRedirect);
console.log("token: ", tokenRedirect);
const shortUrl = await shortUrltiny(encodedToken);
console.log("URL:", shortUrl);
const bodyWithUrl =
`Hai ${user.contact_name || "-"}\n` +
`Terjadi peringatan dengan kode ${errorCode?.error_code || "-"} - ${
errorCode?.error_code_name
} pada device ${deviceNotification.device_name || "-"}.\n` +
`Silahkan cek detail pada link berikut:\n ${shortUrl}`;
const resultSend = await sendNotifikasi(
user.contact_phone,
bodyWithUrl
);
console.log("notifikasi wa:", resultSend)
const isSuccess = resultSend?.success ? true : false;
await updateNotificationErrorDb(user.notification_error_id, {
is_send: isSuccess,
is_delivered: isSuccess,
});
return { phone: user.contact_phone, status: "success" };
} catch (err) {
console.error(`Gagal mengirim ke ${user.contact_phone}:`, err.message);
return {
phone: user.contact_phone,
status: "failed",
error: err.message,
};
}
});
const results = await Promise.all(sendPromises);
console.log("result akhir: ", results)
console.log(
`Resend chat: ${
results.filter((r) => r.status === "success").length
}, Gagal: ${results.filter((r) => r.status === "failed").length}`
);
}
}
module.exports = NotificationService;

View File

@@ -1,111 +0,0 @@
const {
getAllNotificationErrorLogDb,
getNotificationErrorLogByIdDb,
getNotificationErrorLogByNotificationErrorIdDb,
createNotificationErrorLogDb,
updateNotificationErrorLogDb,
deleteNotificationErrorLogDb
} = require('../db/notification_error_log.db');
const { getUserByIdDb } = require('../db/user.db');
const { ErrorHandler } = require('../helpers/error');
class NotificationErrorLogService {
// Get all Notification Error Logs
static async getAllNotificationErrorLog() {
try {
const results = await getAllNotificationErrorLogDb();
results.data.map(element => {
});
return results;
} catch (error) {
throw new ErrorHandler(error.statusCode, error.message);
}
}
// Get Notification Error Log by ID
static async getNotificationErrorLogById(id) {
try {
const result = await getNotificationErrorLogByIdDb(id);
if (!result) {
throw new ErrorHandler(404, 'Notification Error Log not found');
}
return result;
} catch (error) {
throw new ErrorHandler(error.statusCode, error.message);
}
}
// Create Notification Error Log
static async createNotificationErrorLog(data, userId) {
try {
if (!data || typeof data !== 'object') data = {};
let createdBy = null;
let contactPhone = data.contact_phone || null;
if (userId) {
try {
const user = await getUserByIdDb(userId);
if (user && user.user_id) {
createdBy = Number(user.user_id);
} else {
createdBy = null;
contactPhone = userId;
}
} catch (dbError) {
createdBy = null;
contactPhone = userId;
}
}
const store = {
notification_error_id: data.notification_error_id,
contact_phone: contactPhone,
notification_error_log_description: data.notification_error_log_description,
created_by: createdBy
};
const result = await createNotificationErrorLogDb(store);
return result;
} catch (error) {
throw new ErrorHandler(error.statusCode, error.message);
}
}
// Get Notification Error Log by notification_error_id
static async getNotificationErrorLogByNotificationErrorId(notificationErrorId) {
try {
const results = await getNotificationErrorLogByNotificationErrorIdDb(notificationErrorId);
return results;
} catch (error) {
throw new ErrorHandler(error.statusCode, error.message);
}
}
// Soft delete Notification Error Log
static async deleteNotificationErrorLog(id, userId) {
try {
const dataExist = await getNotificationErrorLogByIdDb(id);
if (!dataExist) {
throw new ErrorHandler(404, 'Notification Error Log not found');
}
const result = await deleteNotificationErrorLogDb(id, userId);
return result;
} catch (error) {
throw new ErrorHandler(error.statusCode, error.message);
}
}
}
module.exports = NotificationErrorLogService;

View File

@@ -1,76 +0,0 @@
const {
getAllNotificationErrorSparepartDb,
getNotificationErrorSparepartByIdDb,
createNotificationErrorSparepartDb,
updateNotificationErrorSparepartDb,
deleteNotificationErrorSparepartDb,
} = require("../db/notification_error_sparepart.db");
const { ErrorHandler } = require("../helpers/error");
class NotificationErrorSparepartService {
static async getAll(param) {
try {
const results = await getAllNotificationErrorSparepartDb(param);
results.data.map(element => {
});
return results
} catch (error) {
throw new ErrorHandler(error.statusCode, error.message);
}
}
static async getById(id) {
const result = await getNotificationErrorSparepartByIdDb(id);
if (result.length < 1)
throw new ErrorHandler(404, "Notification Error Sparepart not found");
return result;
}
static async create(data) {
try {
if (!data || typeof data !== 'object') data = {};
const result = await createNotificationErrorSparepartDb(data);
return result;
} catch (error) {
throw new ErrorHandler(error.statusCode, error.message);
}
}
static async update(id, data) {
try {
if (!data || typeof data !== 'object') data = {};
const dataExist = await getNotificationErrorSparepartByIdDb(id);
if (dataExist.length < 1) {
throw new ErrorHandler(404, 'Notification Error Sparepart not found');
}
const result = await updateNotificationErrorSparepartDb(id, data);
return result;
} catch (error) {
throw new ErrorHandler(error.statusCode, error.message);
}
}
static async delete(id) {
const exist = await getNotificationErrorSparepartByIdDb(id);
if (exist.length < 1)
throw new ErrorHandler(404, "Notification Error Sparepart not found");
return await deleteNotificationErrorSparepartDb(id);
}
}
module.exports = NotificationErrorSparepartService;

View File

@@ -1,195 +0,0 @@
const {
getAllNotificationErrorUserDb,
getNotificationErrorUserByIdDb,
createNotificationErrorUserDb,
updateNotificationErrorUserDb,
deleteNotificationErrorUserDb,
} = require("../db/notification_error_user.db");
const {
generateTokenRedirect,
shortUrltiny,
sendNotifikasi,
} = require("../db/notification_wa.db");
const { ErrorHandler } = require("../helpers/error");
class NotificationErrorUserService {
// Get all Contact
static async getAllNotificationErrorUser(param) {
try {
const results = await getAllNotificationErrorUserDb(param);
results.data.map((element) => {});
return results;
} catch (error) {
throw new ErrorHandler(error.statusCode, error.message);
}
}
// Get NotificationErrorUser by ID
static async getNotificationErrorUserById(id) {
try {
const result = await getNotificationErrorUserByIdDb(id);
if (result.length < 1)
throw new ErrorHandler(404, "Notification Error User not found");
return result;
} catch (error) {
throw new ErrorHandler(error.statusCode, error.message);
}
}
// Create NotificationErrorUser
static async createNotificationErrorUser(data) {
try {
if (!data || typeof data !== "object") data = {};
const result = await createNotificationErrorUserDb(data);
return result;
} catch (error) {
throw new ErrorHandler(error.statusCode, error.message);
}
}
// Update NotificationErrorUser
static async updateNotificationErrorUser(id, data) {
try {
if (!data || typeof data !== "object") data = {};
const dataExist = await getNotificationErrorUserByIdDb(id);
if (dataExist.length < 1) {
throw new ErrorHandler(404, "Notification Error User not found");
}
const result = await updateNotificationErrorUserDb(id, data);
return result;
} catch (error) {
throw new ErrorHandler(error.statusCode, error.message);
}
}
// Soft delete NotificationErrorUser
static async deleteNotificationErrorUser(id, userId) {
try {
const dataExist = await getNotificationErrorUserByIdDb(id);
if (dataExist.length < 1) {
throw new ErrorHandler(404, "Notification Error User not found");
}
const result = await deleteNotificationErrorUserDb(id, userId);
return result;
} catch (error) {
throw new ErrorHandler(error.statusCode, error.message);
}
}
static async resendNotificationByUser(id, contact_phone) {
const results = [];
const idUser = Array.isArray(id) ? id : [id];
for (const id of idUser) {
try {
const dataExist = await getNotificationErrorUserByIdDb(id);
if (!dataExist || dataExist.length < 1) {
results.push({
id,
status: "failed",
message: "Data Notification Error User not found",
});
continue;
}
const data = dataExist[0];
if (data.contact_phone !== contact_phone) {
results.push({
id,
status: "failed",
message: `Phone ${contact_phone} not found.`,
});
continue;
}
if (data.contact_is_active === false) {
results.push({
id,
status: "failed",
message: `Contact ${data.contact_phone} not active.`,
});
continue;
}
const tokenRedirect = await generateTokenRedirect(
data.contact_phone,
data.contact_name,
data.notification_error_id
);
const encodedToken = encodeURIComponent(tokenRedirect);
const shortUrl = await shortUrltiny(encodedToken);
const bodyWithUrl =
`Hai ${data.contact_name || "-"}\n` +
`Terjadi peringatan dengan kode error ${data.error_code || "-"} - ${
data.error_code_name || "-"
} ` +
`pada device ${
data.device_name || "-"
}, silahkan cek detail pada link berikut:\n` +
`${shortUrl}`;
const resultSend = await sendNotifikasi(
data.contact_phone,
bodyWithUrl
);
const isSuccess = resultSend?.success ? true : false;
const updateData = {
is_send: isSuccess,
message_error_issue: bodyWithUrl,
};
await updateNotificationErrorUserDb(id, updateData);
if (!isSuccess) {
results.push({
id,
status: "failed",
message: `WhatsApp API Gagal: ${
resultSend?.message || "Unknown Error"
}`,
});
} else {
results.push({
status: 200,
notification_error_user_id: id,
notification_error_id: data.notification_error_id,
device_name: data.device_name,
error_code: data?.error_code,
error_code_name: data?.error_code_name,
users: {
contact_name: data.contact_name,
contact_phone: data.contact_phone,
is_send: isSuccess,
},
});
}
} catch (err) {
results.push({ id, status: "error", message: err.message });
}
}
return results;
}
}
module.exports = NotificationErrorUserService;

View File

@@ -1,125 +0,0 @@
const { getAllContactDb } = require("../db/contact.db");
const { InsertNotificationErrorDb } = require("../db/notification_error.db");
const {
createNotificationErrorUserDb,
updateNotificationErrorUserDb,
} = require("../db/notification_error_user.db");
const {
generateTokenRedirect,
shortUrltiny,
sendNotifikasi,
} = require("../db/notification_wa.db");
const { getErrorCodeByBrandAndCodeDb } = require("../db/brand_code.db");
const { getDeviceNotificationByIdDb } = require("../db/notification_error.db");
class NotifikasiWaService {
async onNotification(topic, message) {
try {
const paramDb = {
limit: 100,
page: 1,
criteria: "",
active: 1,
};
// const chanel = {
// "time": "2025-12-11 11:10:58",
// "c_4501": 4,
// "c_5501": 3,
// "c_6501": 0
// }
if (topic === process.env.TOPIC_COD ?? "morek") {
const dataMqtt = JSON.parse(message);
const resultChanel = [];
Object.entries(dataMqtt).forEach(([key, value]) => {
if (key.startsWith("c_")) {
resultChanel.push({
chanel_id: Number(key.slice(2)),
value,
});
}
});
const results = await getAllContactDb(paramDb);
const dataUsers = results.data;
for (const chanel of resultChanel) {
const deviceNotification = await getDeviceNotificationByIdDb(
Number(chanel.chanel_id)
);
const errorCode = await getErrorCodeByBrandAndCodeDb(deviceNotification?.brand_id ?? 0, chanel.value);
const data = {
error_code_id: chanel.value,
error_chanel: chanel.chanel_id,
is_send: false,
is_delivered: false,
is_read: false,
is_active: true,
};
const resultNotificationError = await InsertNotificationErrorDb(data);
for (const dataUser of dataUsers) {
if (dataUser.is_active) {
const tokenRedirect = await generateTokenRedirect(
dataUser.contact_phone,
dataUser.contact_name,
resultNotificationError.notification_error_id
);
const encodedToken = encodeURIComponent(tokenRedirect);
const shortUrl = await shortUrltiny(encodedToken);
const bodyMessage =
`Hai ${dataUser.contact_name || "-"}\n` +
`Terjadi kode error ${chanel?.value ?? "-"} ${errorCode?.error_code_name ?? ""} Chanel ${chanel?.chanel_id ?? "-"} ` +
`pada device ${deviceNotification?.device_name ?? "berikut"
}, Silahkan cek detail pada link berikut:\n` +
`${shortUrl}`;
const param = {
idData: resultNotificationError.notification_error_id,
userPhone: dataUser.contact_phone,
userName: dataUser.contact_name,
bodyMessage: bodyMessage,
};
const resultNotificationErrorUser =
await createNotificationErrorUserDb({
notification_error_id: param.idData,
contact_phone: param.userPhone,
contact_name: param.userName,
message_error_issue: param.bodyMessage,
is_send: false,
});
const resultSend = await sendNotifikasi(
param.userPhone,
param.bodyMessage
);
await updateNotificationErrorUserDb(
resultNotificationErrorUser[0].notification_error_user_id,
{
is_send: resultSend?.success ? true : false,
}
);
}
}
}
}
} catch (error) {
// throw new ErrorHandler(error.statusCode, error.message);
return error;
}
}
}
module.exports = new NotifikasiWaService();

View File

@@ -1,88 +0,0 @@
const {
getAllSparepartDb,
getSparepartByIdDb,
createSparepartDb,
updateSparepartDb,
deleteSparepartDb,
checkSparepartNameExistsDb,
} = require("../db/sparepart.db");
const { ErrorHandler } = require("../helpers/error");
class SparepartService {
static async getAllSparepart(param) {
try {
const results = await getAllSparepartDb(param);
results.data.map((item) => {});
return results;
} catch (error) {
throw new ErrorHandler(error.statusCode || 500, error.message);
}
}
static async getSparepartById(id) {
try {
const sparepart = await getSparepartByIdDb(id);
if (!sparepart || sparepart.length === 0) {
throw new ErrorHandler(404, "Sparepart not found");
}
return sparepart[0];
} catch (error) {
throw new ErrorHandler(error.statusCode || 500, error.message);
}
}
static async createSparepart(data) {
try {
if (!data || typeof data !== "object") data = {};
if (data.sparepart_name) {
const exists = await checkSparepartNameExistsDb(data.sparepart_name);
if (exists)
throw new ErrorHandler(400, "Sparepart name already exists");
}
const created = await createSparepartDb(data);
if (!created) throw new ErrorHandler(500, "Failed to create Sparepart");
return created;
} catch (error) {
throw new ErrorHandler(error.statusCode || 500, error.message);
}
}
static async updateSparepart(id, data) {
try {
const existing = await getSparepartByIdDb(id);
if (!existing) throw new ErrorHandler(404, "Sparepart not found");
if (
data.sparepart_name &&
data.sparepart_name !== existing.sparepart_name
) {
const exists = await checkSparepartNameExistsDb(
data.sparepart_name,
id
);
if (exists)
throw new ErrorHandler(400, "Sparepart name already exists");
}
const updated = await updateSparepartDb(id, data);
if (!updated) throw new ErrorHandler(500, "Failed to update Sparepart");
return await this.getSparepartById(id);
} catch (error) {
throw new ErrorHandler(error.statusCode || 500, error.message);
}
}
static async deleteSparepart(id, userId) {
try {
const existing = await getSparepartByIdDb(id);
if (!existing) throw new ErrorHandler(404, "Sparepart not found");
const deleted = await deleteSparepartDb(id, userId);
if (!deleted) throw new ErrorHandler(500, "Failed to delete Sparepart");
return deleted;
} catch (error) {
throw new ErrorHandler(error.statusCode || 500, error.message);
}
}
}
module.exports = SparepartService;

View File

Before

Width:  |  Height:  |  Size: 122 KiB

After

Width:  |  Height:  |  Size: 122 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 122 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 122 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 122 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 122 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 122 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 122 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 122 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 122 KiB

View File

@@ -4,25 +4,81 @@ const Joi = require("joi");
// Brand Validation // Brand Validation
// ======================== // ========================
const insertBrandSchema = Joi.object({ const insertBrandSchema = Joi.object({
brand_name: Joi.string().max(100).required(), brand_name: Joi.string().max(100).required(),
brand_type: Joi.string().max(50).optional().allow(""), brand_type: Joi.string().max(50).optional().allow(''),
brand_manufacture: Joi.string().max(100).required(), brand_manufacture: Joi.string().max(100).required(),
brand_model: Joi.string().max(100).optional().allow(""), brand_model: Joi.string().max(100).optional().allow(''),
is_active: Joi.boolean().optional().default(true), is_active: Joi.boolean().required(),
description: Joi.string().max(255).optional().allow(""), 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(''),
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 // Update Brand Validation
const updateBrandSchema = Joi.object({ const updateBrandSchema = Joi.object({
brand_name: Joi.string().max(100).optional(), brand_name: Joi.string().max(100).required(),
brand_type: Joi.string().max(50).optional().allow(""), brand_type: Joi.string().max(50).optional().allow(''),
brand_manufacture: Joi.string().max(100).optional(), brand_manufacture: Joi.string().max(100).required(),
brand_model: Joi.string().max(100).optional().allow(""), brand_model: Joi.string().max(100).optional().allow(''),
is_active: Joi.boolean().optional(), is_active: Joi.boolean().required(),
description: Joi.string().max(255).optional().allow(""), 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(''),
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); }).min(1);
module.exports = { module.exports = {
insertBrandSchema, insertBrandSchema,
updateBrandSchema, updateBrandSchema
}; };

View File

@@ -0,0 +1,32 @@
const Joi = require("joi");
// ========================
// Brand Validation
// ========================
const insertBrandSparepartSchema = Joi.object({
sparepart_name: Joi.string().max(255).required(),
brand_sparepart_description: Joi.string().max(255).required(),
is_active: Joi.boolean().required(),
error_code_id: Joi.number().required().messages({
"any.required": "error_code_id is required",
"number.base": "error_code_id must be a number",
}),
path_foto: Joi.string().max(255).optional().allow(''),
});
// Update Brand Validation
const updateBrandSparepartSchema = Joi.object({
sparepart_name: Joi.string().max(255).required(),
brand_sparepart_description: Joi.string().max(255).required(),
is_active: Joi.boolean().optional(),
error_code_id: Joi.number().required().messages({
"any.required": "error_code_id is required",
"number.base": "error_code_id must be a number",
}),
path_foto: Joi.string().max(255).optional().allow(''),
});
module.exports = {
insertBrandSparepartSchema,
updateBrandSparepartSchema,
};

View File

@@ -13,20 +13,20 @@ const insertContactSchema = Joi.object({
"Phone number must be a valid Indonesian number in format +628XXXXXXXXX", "Phone number must be a valid Indonesian number in format +628XXXXXXXXX",
}), }),
is_active: Joi.boolean().required(), is_active: Joi.boolean().required(),
contact_type: Joi.string().max(255).optional().allow(null) contact_type: Joi.string().max(255).required()
}); });
const updateContactSchema = Joi.object({ const updateContactSchema = Joi.object({
contact_name: Joi.string().min(3).max(100).optional(), contact_name: Joi.string().min(3).max(100).required(),
contact_phone: Joi.string() contact_phone: Joi.string()
.pattern(/^(?:\+62|0)8\d{7,10}$/) .pattern(/^(?:\+62|0)8\d{7,10}$/)
.optional() .required()
.messages({ .messages({
"string.pattern.base": "string.pattern.base":
"Phone number must be a valid Indonesian number in format +628XXXXXXXXX", "Phone number must be a valid Indonesian number in format +628XXXXXXXXX",
}), }),
is_active: Joi.boolean().optional(), is_active: Joi.boolean().optional(),
contact_type: Joi.string().max(255).optional().allow(null) contact_type: Joi.string().max(255).optional()
}); });
module.exports = { module.exports = {

View File

@@ -7,33 +7,30 @@ const Joi = require("joi");
const insertDeviceSchema = Joi.object({ const insertDeviceSchema = Joi.object({
device_name: Joi.string().max(100).required(), device_name: Joi.string().max(100).required(),
is_active: Joi.boolean().required(), is_active: Joi.boolean().required(),
brand_id: Joi.number().integer().min(1),
device_location: Joi.string().max(100).required(), device_location: Joi.string().max(100).required(),
device_description: Joi.string(), device_description: Joi.string().required(),
ip_address: Joi.string() ip_address: Joi.string()
.ip({ version: ['ipv4', 'ipv6'] }) .ip({ version: ['ipv4', 'ipv6'] })
.required() .required()
.messages({ .messages({
'string.ip': 'IP address must be a valid IPv4 or IPv6 address' 'string.ip': 'IP address must be a valid IPv4 or IPv6 address'
}), })
listen_channel: Joi.string().max(100).required()
}); });
const updateDeviceSchema = Joi.object({ const updateDeviceSchema = Joi.object({
device_name: Joi.string().max(100), device_name: Joi.string().max(100),
is_active: Joi.boolean(), is_active: Joi.boolean(),
brand_id: Joi.number().integer().min(1),
device_location: Joi.string().max(100), device_location: Joi.string().max(100),
device_description: Joi.string(), device_description: Joi.string(),
ip_address: Joi.string() ip_address: Joi.string()
.ip({ version: ['ipv4', 'ipv6'] }) .ip({ version: ['ipv4', 'ipv6'] })
.messages({ .messages({
'string.ip': 'IP address must be a valid IPv4 or IPv6 address' 'string.ip': 'IP address must be a valid IPv4 or IPv6 address'
}), })
listen_channel: Joi.string().max(100)
}).min(1); }).min(1);
// ✅ Export dengan CommonJS
module.exports = { module.exports = {
insertDeviceSchema, updateDeviceSchema insertDeviceSchema, updateDeviceSchema
}; };

View File

@@ -1,73 +0,0 @@
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)
.optional(),
// .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: Joi.string().max(100).optional(),
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,
};

View File

@@ -13,8 +13,6 @@ const insertNotificationSchema = Joi.object({
"number.base": "error_code_id must be a number", "number.base": "error_code_id must be a number",
}), }),
message_error_issue: Joi.string().max(255).optional(),
is_send: Joi.boolean().required().messages({ is_send: Joi.boolean().required().messages({
"any.required": "is_send is required", "any.required": "is_send is required",
"boolean.base": "is_send must be a boolean", "boolean.base": "is_send must be a boolean",
@@ -40,11 +38,25 @@ const insertNotificationSchema = Joi.object({
// Update Notification Schema // Update Notification Schema
// ======================== // ========================
const updateNotificationSchema = Joi.object({ const updateNotificationSchema = Joi.object({
error_code_id: Joi.number().optional().messages({
"number.base": "error_code_id must be a number",
}),
is_send: Joi.boolean().optional().messages({
"boolean.base": "is_send must be a boolean",
}),
is_delivered: Joi.boolean().optional().messages({
"boolean.base": "is_delivered must be a boolean",
}),
is_read: Joi.boolean().optional().messages({ is_read: Joi.boolean().optional().messages({
"boolean.base": "is_read must be a boolean", "boolean.base": "is_read must be a boolean",
}), }),
is_active: Joi.boolean().optional().messages({
"boolean.base": "is_active must be a boolean",
}),
}); });
module.exports = { module.exports = {

View File

@@ -1,11 +0,0 @@
const Joi = require("joi");
const insertNotificationErrorLogSchema = Joi.object({
notification_error_id: Joi.number().integer().required(),
contact_phone: Joi.string().optional(),
notification_error_log_description: Joi.string().required()
});
module.exports = {
insertNotificationErrorLogSchema,
};

View File

@@ -1,30 +0,0 @@
const Joi = require("joi");
const insertNotificationErrorSparepartSchema = Joi.object({
contact_id: Joi.number().required(),
brand_sparepart_id: Joi.number().required(),
is_verification: Joi.boolean().required(),
is_available: Joi.boolean().required(),
is_active: Joi.boolean().required(),
});
const updateNotificationErrorSparepartSchema = Joi.object({
contact_id: Joi.number().required(),
brand_sparepart_id: Joi.number().optional(),
is_verification: Joi.boolean().optional(),
is_available: Joi.boolean().optional(),
is_active: Joi.boolean().optional(),
});
module.exports = {
insertNotificationErrorSparepartSchema,
updateNotificationErrorSparepartSchema,
};

View File

@@ -1,44 +0,0 @@
const Joi = require("joi");
// ========================
// Insert Notification Error Schema
// ========================
const insertNotificationErrorUserSchema = Joi.object({
notification_error_id: Joi.number().required().messages({
"any.required": "notification_error_id is required",
"number.base": "notification_error_id must be a number",
}),
contact_id: Joi.number().required().messages({
"any.required": "contact_id is required",
"number.base": "contact_id must be a number",
}),
is_send: Joi.boolean().required().messages({
"any.required": "is_send is required",
"boolean.base": "is_send must be a boolean",
}),
});
// ========================
// Update Notification Error Schema
// ========================
const updateNotificationErrorUserSchema = Joi.object({
notification_error_id: Joi.number().optional().messages({
"number.base": "notification_error_id must be a number",
}),
contact_id: Joi.number().required().messages({
"any.required": "contact_id is required",
"number.base": "contact_id must be a number",
}),
is_send: Joi.boolean().optional().messages({
"boolean.base": "is_send must be a boolean",
}),
});
module.exports = {
insertNotificationErrorUserSchema,
updateNotificationErrorUserSchema,
};

View File

@@ -1,32 +0,0 @@
const Joi = require("joi");
// ========================
// Sparepart Validation
// ========================
const insertSparepartSchema = Joi.object({
sparepart_name: Joi.string().max(255).required(),
sparepart_description: Joi.string().max(255).optional(),
sparepart_model: Joi.string().max(255).optional(),
sparepart_foto: Joi.string().max(255).optional().allow(""),
sparepart_item_type: Joi.string().max(255).optional(),
sparepart_qty: Joi.number().integer().min(0),
sparepart_unit: Joi.string().max(255).optional(),
sparepart_merk: Joi.string().max(255).optional(),
sparepart_stok: Joi.string().max(255).optional(),
});
// Update Validation
const updateSparepartSchema = Joi.object({
sparepart_name: Joi.string().max(255).optional(),
sparepart_description: Joi.string().max(255).optional(),
sparepart_model: Joi.string().max(255).optional(),
sparepart_foto: Joi.string().max(255).optional().allow(''),
sparepart_item_type: Joi.string().max(255).optional(),
sparepart_qty: Joi.number().integer().min(0),
sparepart_unit: Joi.string().max(255).optional(),
sparepart_merk: Joi.string().max(255).optional(),
sparepart_stok: Joi.string().max(255).optional(),
});
module.exports = {
insertSparepartSchema,
updateSparepartSchema,
};