From 7a4074098a1a90bb9785b4a73523ce57c39f253b Mon Sep 17 00:00:00 2001 From: kittapath <> Date: Wed, 22 Oct 2025 23:20:40 +0700 Subject: [PATCH 01/17] update timezone --- src/app.ts | 4 ++++ src/interfaces/date-serializer.ts | 24 ++++++++++++++++++++++++ 2 files changed, 28 insertions(+) create mode 100644 src/interfaces/date-serializer.ts diff --git a/src/app.ts b/src/app.ts index 7b3f7e0..af15c9d 100644 --- a/src/app.ts +++ b/src/app.ts @@ -9,10 +9,14 @@ import error from "./middlewares/error"; import { AppDataSource } from "./database/data-source"; import { RegisterRoutes } from "./routes"; import logMiddleware from "./middlewares/logs"; +import { DateSerializer } from "./interfaces/date-serializer"; async function main() { await AppDataSource.initialize(); + // Setup custom Date serialization for local timezone + DateSerializer.setupDateSerialization(); + const app = express(); app.use( diff --git a/src/interfaces/date-serializer.ts b/src/interfaces/date-serializer.ts new file mode 100644 index 0000000..5c876ff --- /dev/null +++ b/src/interfaces/date-serializer.ts @@ -0,0 +1,24 @@ +// Custom Date serializer for local timezone +export class DateSerializer { + static toLocalTime(date: Date): string | null { + if (!date) return null; + + // Convert UTC date to Thailand timezone (+07:00) + const offset = 7 * 60; // Thailand is UTC+7 + const localTime = new Date(date.getTime() + offset * 60 * 1000); + + // Format as ISO string but replace Z with +07:00 + const isoString = localTime.toISOString(); + return isoString.replace("Z", "+07:00"); + } + + static setupDateSerialization() { + // Override Date.prototype.toJSON to use local time + Date.prototype.toJSON = function () { + const offset = 7 * 60; // Thailand timezone offset in minutes + const localTime = new Date(this.getTime() + offset * 60 * 1000); + const isoString = localTime.toISOString(); + return isoString.replace("Z", "+07:00"); + }; + } +} From 810e782a390021305026163927171e82ea4c7a75 Mon Sep 17 00:00:00 2001 From: Adisak Date: Tue, 28 Oct 2025 10:22:45 +0700 Subject: [PATCH 02/17] fix --- src/controllers/KpiCapacityController.ts | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/src/controllers/KpiCapacityController.ts b/src/controllers/KpiCapacityController.ts index 7fc7146..ab7c9ca 100644 --- a/src/controllers/KpiCapacityController.ts +++ b/src/controllers/KpiCapacityController.ts @@ -438,11 +438,11 @@ export class kpiCapacityController extends Controller { async listKpiCapacity( @Request() request: RequestWithUser, @Query("page") page: number = 1, - @Query("pageSize") pageSize: number = 10, + @Query("pageSize") pageSize?: number, @Query("type") type?: string, @Query("keyword") keyword?: string, ) { - const [kpiCapacity, total] = await AppDataSource.getRepository(KpiCapacity) + let query = await AppDataSource.getRepository(KpiCapacity) .createQueryBuilder("kpiCapacity") .leftJoinAndSelect("kpiCapacity.kpiCapacityDetails", "kpiCapacityDetail") .andWhere( @@ -452,8 +452,13 @@ export class kpiCapacityController extends Controller { ) .andWhere(type == undefined ? "1=1" : { type: type }) .orderBy("kpiCapacity.createdAt", "ASC") - .skip((page - 1) * pageSize) - .take(pageSize) + + if (pageSize) { + query = query.skip((page - 1) * pageSize) + .take(pageSize) + } + + const [kpiCapacity, total] = await query .getManyAndCount(); const mapFormula = kpiCapacity.map((item) => ({ From 8155565186c6e78d25dbf4ad9722136b44780796 Mon Sep 17 00:00:00 2001 From: Adisak Date: Tue, 28 Oct 2025 14:30:00 +0700 Subject: [PATCH 03/17] try catch --- .../KpiUserEvaluationController.ts | 115 ++++++++++-------- 1 file changed, 64 insertions(+), 51 deletions(-) diff --git a/src/controllers/KpiUserEvaluationController.ts b/src/controllers/KpiUserEvaluationController.ts index b311bae..9816fb0 100644 --- a/src/controllers/KpiUserEvaluationController.ts +++ b/src/controllers/KpiUserEvaluationController.ts @@ -1318,60 +1318,73 @@ export class KpiUserEvaluationController extends Controller { @Body() requestBody: { reason: string; actor: string }, @Request() request: RequestWithUser, ) { - const kpiUserEvaluation = await this.kpiUserEvalutionRepository.findOne({ - where: { id: id }, - }); - if (!kpiUserEvaluation) { + try{ + const kpiUserEvaluation = await this.kpiUserEvalutionRepository.findOne({ + where: { id: id }, + }); + if (!kpiUserEvaluation) { + throw new HttpError( + HttpStatusCode.NOT_FOUND, + "ไม่พบข้อมูลรายการประเมินผลการปฏิบัติราชการระดับบุคคลนี้", + ); + } + await new CallAPI() + .PostData(request, "/placement/noti/profiles", { + subject: `${kpiUserEvaluation.prefix}${kpiUserEvaluation.firstName} ${kpiUserEvaluation.lastName} มีความเห็นต่างเนื่องจาก: ${requestBody.reason}`, + body: `${kpiUserEvaluation.prefix}${kpiUserEvaluation.firstName} ${kpiUserEvaluation.lastName} มีความเห็นต่างเนื่องจาก: ${requestBody.reason}`, + receiverUserIds: [ + { + receiverUserId: kpiUserEvaluation.evaluatorId, + notiLink: `${process.env.VITE_URL_USER}/KPI-evaluator/${kpiUserEvaluation.id}`, + }, + ], + payload: "", + isSendMail: true, + isSendInbox: true, + isSendNotification: true, + }) + .then(() => {}) + .catch(() => {}); + const before = structuredClone(kpiUserEvaluation); + let _null: any = null; + kpiUserEvaluation.evaluationStatus = "EVALUATING_EVALUATOR"; + kpiUserEvaluation.isReasonCommander = _null; + kpiUserEvaluation.reasonCommander = _null; + kpiUserEvaluation.reasonReject = requestBody.reason; + kpiUserEvaluation.actorReject = requestBody.actor; + kpiUserEvaluation.actorNameReject = request.user.name; + kpiUserEvaluation.lastUpdateUserId = request.user.sub; + kpiUserEvaluation.lastUpdateFullName = request.user.name; + kpiUserEvaluation.lastUpdatedAt = new Date(); + let kpiReject = { + kpiUserEvaluationId: kpiUserEvaluation.id, + reason: requestBody.reason, + actor: requestBody.actor, + fullname: `${kpiUserEvaluation.prefixEvaluator}${kpiUserEvaluation.firstNameEvaluator} ${kpiUserEvaluation.lastNameEvaluator}`, + profileId: kpiUserEvaluation.evaluatorId, + createdUserId: request.user.sub, + createdFullName: request.user.name, + lastUpdateUserId: request.user.sub, + lastUpdateFullName: request.user.name, + createdAt: new Date(), + lastUpdatedAt: new Date(), + }; + await this.kpiUserEvalutionRepository.save(kpiUserEvaluation, { data: request }); + await this.kpiUserRejectResultRepository.save(kpiReject); + setLogDataDiff(request, { before, after: kpiUserEvaluation }); + return new HttpSuccess(kpiUserEvaluation.id); + } catch (error: any) { + console.error("เกิดข้อผิดพลาดระหว่างการประมวลผล:", error); + + if (error instanceof HttpError) { + throw error; + } + throw new HttpError( - HttpStatusCode.NOT_FOUND, - "ไม่พบข้อมูลรายการประเมินผลการปฏิบัติราชการระดับบุคคลนี้", + HttpStatusCode.INTERNAL_SERVER_ERROR, + "เกิดข้อผิดพลาดภายในระบบ กรุณาลองใหม่อีกครั้ง", ); } - await new CallAPI() - .PostData(request, "/placement/noti/profiles", { - subject: `${kpiUserEvaluation.prefix}${kpiUserEvaluation.firstName} ${kpiUserEvaluation.lastName} มีความเห็นต่างเนื่องจาก: ${requestBody.reason}`, - body: `${kpiUserEvaluation.prefix}${kpiUserEvaluation.firstName} ${kpiUserEvaluation.lastName} มีความเห็นต่างเนื่องจาก: ${requestBody.reason}`, - receiverUserIds: [ - { - receiverUserId: kpiUserEvaluation.evaluatorId, - notiLink: `${process.env.VITE_URL_USER}/KPI-evaluator/${kpiUserEvaluation.id}`, - }, - ], - payload: "", - isSendMail: true, - isSendInbox: true, - isSendNotification: true, - }) - .then(() => {}) - .catch(() => {}); - const before = structuredClone(kpiUserEvaluation); - let _null: any = null; - kpiUserEvaluation.evaluationStatus = "EVALUATING_EVALUATOR"; - kpiUserEvaluation.isReasonCommander = _null; - kpiUserEvaluation.reasonCommander = _null; - kpiUserEvaluation.reasonReject = requestBody.reason; - kpiUserEvaluation.actorReject = requestBody.actor; - kpiUserEvaluation.actorNameReject = request.user.name; - kpiUserEvaluation.lastUpdateUserId = request.user.sub; - kpiUserEvaluation.lastUpdateFullName = request.user.name; - kpiUserEvaluation.lastUpdatedAt = new Date(); - let kpiReject = { - kpiUserEvaluationId: kpiUserEvaluation.id, - reason: requestBody.reason, - actor: requestBody.actor, - fullname: `${kpiUserEvaluation.prefixEvaluator}${kpiUserEvaluation.firstNameEvaluator} ${kpiUserEvaluation.lastNameEvaluator}`, - profileId: kpiUserEvaluation.evaluatorId, - createdUserId: request.user.sub, - createdFullName: request.user.name, - lastUpdateUserId: request.user.sub, - lastUpdateFullName: request.user.name, - createdAt: new Date(), - lastUpdatedAt: new Date(), - }; - await this.kpiUserEvalutionRepository.save(kpiUserEvaluation, { data: request }); - await this.kpiUserRejectResultRepository.save(kpiReject); - setLogDataDiff(request, { before, after: kpiUserEvaluation }); - return new HttpSuccess(kpiUserEvaluation.id); } /** From 9a149a8d7e8aaeb365a693bac1ee5e5ee6750df1 Mon Sep 17 00:00:00 2001 From: Adisak Date: Tue, 28 Oct 2025 14:46:56 +0700 Subject: [PATCH 04/17] test --- .../KpiUserEvaluationController.ts | 34 +++++++++---------- 1 file changed, 17 insertions(+), 17 deletions(-) diff --git a/src/controllers/KpiUserEvaluationController.ts b/src/controllers/KpiUserEvaluationController.ts index 9816fb0..0e69f88 100644 --- a/src/controllers/KpiUserEvaluationController.ts +++ b/src/controllers/KpiUserEvaluationController.ts @@ -1328,23 +1328,23 @@ export class KpiUserEvaluationController extends Controller { "ไม่พบข้อมูลรายการประเมินผลการปฏิบัติราชการระดับบุคคลนี้", ); } - await new CallAPI() - .PostData(request, "/placement/noti/profiles", { - subject: `${kpiUserEvaluation.prefix}${kpiUserEvaluation.firstName} ${kpiUserEvaluation.lastName} มีความเห็นต่างเนื่องจาก: ${requestBody.reason}`, - body: `${kpiUserEvaluation.prefix}${kpiUserEvaluation.firstName} ${kpiUserEvaluation.lastName} มีความเห็นต่างเนื่องจาก: ${requestBody.reason}`, - receiverUserIds: [ - { - receiverUserId: kpiUserEvaluation.evaluatorId, - notiLink: `${process.env.VITE_URL_USER}/KPI-evaluator/${kpiUserEvaluation.id}`, - }, - ], - payload: "", - isSendMail: true, - isSendInbox: true, - isSendNotification: true, - }) - .then(() => {}) - .catch(() => {}); + // await new CallAPI() + // .PostData(request, "/placement/noti/profiles", { + // subject: `${kpiUserEvaluation.prefix}${kpiUserEvaluation.firstName} ${kpiUserEvaluation.lastName} มีความเห็นต่างเนื่องจาก: ${requestBody.reason}`, + // body: `${kpiUserEvaluation.prefix}${kpiUserEvaluation.firstName} ${kpiUserEvaluation.lastName} มีความเห็นต่างเนื่องจาก: ${requestBody.reason}`, + // receiverUserIds: [ + // { + // receiverUserId: kpiUserEvaluation.evaluatorId, + // notiLink: `${process.env.VITE_URL_USER}/KPI-evaluator/${kpiUserEvaluation.id}`, + // }, + // ], + // payload: "", + // isSendMail: true, + // isSendInbox: true, + // isSendNotification: true, + // }) + // .then(() => {}) + // .catch(() => {}); const before = structuredClone(kpiUserEvaluation); let _null: any = null; kpiUserEvaluation.evaluationStatus = "EVALUATING_EVALUATOR"; From ef2476bade591336293cb018743b07bbb6a90a69 Mon Sep 17 00:00:00 2001 From: Adisak Date: Tue, 28 Oct 2025 16:58:59 +0700 Subject: [PATCH 05/17] revert --- .../KpiUserEvaluationController.ts | 34 +++++++++---------- 1 file changed, 17 insertions(+), 17 deletions(-) diff --git a/src/controllers/KpiUserEvaluationController.ts b/src/controllers/KpiUserEvaluationController.ts index 0e69f88..9816fb0 100644 --- a/src/controllers/KpiUserEvaluationController.ts +++ b/src/controllers/KpiUserEvaluationController.ts @@ -1328,23 +1328,23 @@ export class KpiUserEvaluationController extends Controller { "ไม่พบข้อมูลรายการประเมินผลการปฏิบัติราชการระดับบุคคลนี้", ); } - // await new CallAPI() - // .PostData(request, "/placement/noti/profiles", { - // subject: `${kpiUserEvaluation.prefix}${kpiUserEvaluation.firstName} ${kpiUserEvaluation.lastName} มีความเห็นต่างเนื่องจาก: ${requestBody.reason}`, - // body: `${kpiUserEvaluation.prefix}${kpiUserEvaluation.firstName} ${kpiUserEvaluation.lastName} มีความเห็นต่างเนื่องจาก: ${requestBody.reason}`, - // receiverUserIds: [ - // { - // receiverUserId: kpiUserEvaluation.evaluatorId, - // notiLink: `${process.env.VITE_URL_USER}/KPI-evaluator/${kpiUserEvaluation.id}`, - // }, - // ], - // payload: "", - // isSendMail: true, - // isSendInbox: true, - // isSendNotification: true, - // }) - // .then(() => {}) - // .catch(() => {}); + await new CallAPI() + .PostData(request, "/placement/noti/profiles", { + subject: `${kpiUserEvaluation.prefix}${kpiUserEvaluation.firstName} ${kpiUserEvaluation.lastName} มีความเห็นต่างเนื่องจาก: ${requestBody.reason}`, + body: `${kpiUserEvaluation.prefix}${kpiUserEvaluation.firstName} ${kpiUserEvaluation.lastName} มีความเห็นต่างเนื่องจาก: ${requestBody.reason}`, + receiverUserIds: [ + { + receiverUserId: kpiUserEvaluation.evaluatorId, + notiLink: `${process.env.VITE_URL_USER}/KPI-evaluator/${kpiUserEvaluation.id}`, + }, + ], + payload: "", + isSendMail: true, + isSendInbox: true, + isSendNotification: true, + }) + .then(() => {}) + .catch(() => {}); const before = structuredClone(kpiUserEvaluation); let _null: any = null; kpiUserEvaluation.evaluationStatus = "EVALUATING_EVALUATOR"; From f3e90ef95555b6f677c281c300b27b44cf828bc8 Mon Sep 17 00:00:00 2001 From: Adisak Date: Wed, 29 Oct 2025 09:54:37 +0700 Subject: [PATCH 06/17] test safeStringify --- src/interfaces/call-api.ts | 14 +++++++------- src/interfaces/utils.ts | 34 ++++++++++++++++++++++++++++++++-- 2 files changed, 39 insertions(+), 9 deletions(-) diff --git a/src/interfaces/call-api.ts b/src/interfaces/call-api.ts index 73f6c90..3ae4a7c 100644 --- a/src/interfaces/call-api.ts +++ b/src/interfaces/call-api.ts @@ -12,7 +12,7 @@ import { Path, } from "tsoa"; import axios from "axios"; -import { addLogSequence } from "./utils"; +import { addLogSequence, safeStringify } from "./utils"; class CallAPI { //Get @@ -34,7 +34,7 @@ class CallAPI { request: { method: "GET", url: url, - response: JSON.stringify(response.data.result), + response: safeStringify(response.data.result), }, }); return response.data.result; @@ -46,7 +46,7 @@ class CallAPI { request: { method: "GET", url: url, - response: JSON.stringify(error), + response: safeStringify(error), }, }); throw error; @@ -71,8 +71,8 @@ class CallAPI { request: { method: "POST", url: url, - payload: JSON.stringify(sendData), - response: JSON.stringify(response.data.result), + payload: safeStringify(sendData), + response: safeStringify(response.data.result), }, }); return response.data.result; @@ -84,8 +84,8 @@ class CallAPI { request: { method: "POST", url: url, - payload: JSON.stringify(sendData), - response: JSON.stringify(error), + payload: safeStringify(sendData), + response: safeStringify(error), }, }); throw error; diff --git a/src/interfaces/utils.ts b/src/interfaces/utils.ts index c667f54..259b113 100644 --- a/src/interfaces/utils.ts +++ b/src/interfaces/utils.ts @@ -27,8 +27,8 @@ export function setLogDataDiff(req: RequestWithUser, data: DataDiff) { typeof data.after === "object" ) { req.app.locals.logData.dataDiff = { - before: JSON.stringify(data.before), - after: JSON.stringify(data.after), + before: safeStringify(data.before), + after: safeStringify(data.after), }; } else { console.error("Invalid data provided: both before and after must be valid objects."); @@ -45,3 +45,33 @@ export function addLogSequence(req: RequestWithUser, data: LogSequence) { export function editLogSequence(req: RequestWithUser, index: number, data: LogSequence) { req.app.locals.logData.sequence[index] = data; } + +/** + * ปลอดภัยกว่า JSON.stringify() + * - กัน circular reference + * - จำกัดความลึก + * - return string แน่นอนเสมอ + */ +export function safeStringify(obj: any, space?: number): string { + const cache = new WeakSet(); + + try { + return JSON.stringify( + obj, + (key, value) => { + // ป้องกัน circular reference + if (typeof value === "object" && value !== null) { + if (cache.has(value)) { + return "[Circular]"; + } + cache.add(value); + } + return value; + }, + space, + ); + } catch (err) { + console.error("⚠️ safeStringify error:", err); + return "[Unserializable object]"; + } +} From cb33979bd33f21dfde977710f0bfe0d643fd5935 Mon Sep 17 00:00:00 2001 From: Adisak Date: Wed, 29 Oct 2025 11:15:29 +0700 Subject: [PATCH 07/17] revert checkOrg --- src/interfaces/permission.ts | 121 ++++++++++++++++++++++++++--------- 1 file changed, 89 insertions(+), 32 deletions(-) diff --git a/src/interfaces/permission.ts b/src/interfaces/permission.ts index 6ff8977..9980858 100644 --- a/src/interfaces/permission.ts +++ b/src/interfaces/permission.ts @@ -183,40 +183,97 @@ class CheckAuth { return false; }); } - public async checkOrg(token: any, keycloakId: string) { - const redisClient = await this.redis.createClient({ - host: process.env.REDIS_HOST, - port: process.env.REDIS_PORT, - }) - const getAsync = promisify(redisClient.get).bind(redisClient) - try { - let reply = await getAsync("org_" + keycloakId) - if (reply != null) { - reply = JSON.parse(reply) - } else { - if (!keycloakId) throw new Error("No KeycloakId provided") - const x = await new CallAPI().GetData( - { - headers: { authorization: token }, - }, - `/org/permission/checkOrg/${keycloakId}`, - false - ) + // public async checkOrg(token: any, keycloakId: string) { + // const redisClient = await this.redis.createClient({ + // host: process.env.REDIS_HOST, + // port: process.env.REDIS_PORT, + // }) + // const getAsync = promisify(redisClient.get).bind(redisClient) + // try { + // let reply = await getAsync("org_" + keycloakId) + // if (reply != null) { + // reply = JSON.parse(reply) + // } else { + // if (!keycloakId) throw new Error("No KeycloakId provided") + // const x = await new CallAPI().GetData( + // { + // headers: { authorization: token }, + // }, + // `/org/permission/checkOrg/${keycloakId}`, + // false + // ) - const data = { - orgRootId: x.orgRootId, - orgChild1Id: x.orgChild1Id, - orgChild2Id: x.orgChild2Id, - orgChild3Id: x.orgChild3Id, - orgChild4Id: x.orgChild4Id, - } + // const data = { + // orgRootId: x.orgRootId, + // orgChild1Id: x.orgChild1Id, + // orgChild2Id: x.orgChild2Id, + // orgChild3Id: x.orgChild3Id, + // orgChild4Id: x.orgChild4Id, + // } - return data - } - } catch (error) { - console.error("Error calling API:", error) - throw error - } + // return data + // } + // } catch (error) { + // console.error("Error calling API:", error) + // throw error + // } + // } + public async checkOrg(token: any, keycloakId: string) { + try { + // Validate required environment variables + const REDIS_HOST = process.env.REDIS_HOST; + const REDIS_PORT = process.env.REDIS_PORT ? Number(process.env.REDIS_PORT) : 6379; + + if (!REDIS_HOST) { + throw new Error("REDIS_HOST is not set in environment variables"); + } + + console.log(`[REDIS] Connecting to Redis at ${REDIS_HOST}:${REDIS_PORT}`); + + // Create Redis client + const redisClient = this.redis.createClient({ + socket: { + host: REDIS_HOST, + port: REDIS_PORT, + }, + }); + + redisClient.on("error", (err: any) => { + console.error("[REDIS] Connection error:", err.message); + }); + + await redisClient.connect(); + console.log("[REDIS] Connected successfully!"); + + const getAsync = promisify(redisClient.get).bind(redisClient); + + let reply = await getAsync("org_" + keycloakId); + if (reply != null) { + reply = JSON.parse(reply); + } else { + if (!keycloakId) throw new Error("No KeycloakId provided"); + const x = await new CallAPI().GetData( + { + headers: { authorization: token }, + }, + `/org/permission/checkOrg/${keycloakId}`, + false, + ); + + const data = { + orgRootId: x.orgRootId, + orgChild1Id: x.orgChild1Id, + orgChild2Id: x.orgChild2Id, + orgChild3Id: x.orgChild3Id, + orgChild4Id: x.orgChild4Id, + }; + + return data; + } + } catch (error) { + console.error("Error calling API:", error); + throw error; + } } public async PermissionCreate(req: RequestWithUser, system: string) { return await this.Permission(req, system, "CREATE"); From ca52b5b077febb14ac49b3cb3c521f421753c710 Mon Sep 17 00:00:00 2001 From: Adisak Date: Wed, 29 Oct 2025 11:19:07 +0700 Subject: [PATCH 08/17] revert code to normal --- src/interfaces/call-api.ts | 14 +++++++------- src/interfaces/permission.ts | 37 +----------------------------------- src/interfaces/utils.ts | 34 ++------------------------------- 3 files changed, 10 insertions(+), 75 deletions(-) diff --git a/src/interfaces/call-api.ts b/src/interfaces/call-api.ts index 3ae4a7c..73f6c90 100644 --- a/src/interfaces/call-api.ts +++ b/src/interfaces/call-api.ts @@ -12,7 +12,7 @@ import { Path, } from "tsoa"; import axios from "axios"; -import { addLogSequence, safeStringify } from "./utils"; +import { addLogSequence } from "./utils"; class CallAPI { //Get @@ -34,7 +34,7 @@ class CallAPI { request: { method: "GET", url: url, - response: safeStringify(response.data.result), + response: JSON.stringify(response.data.result), }, }); return response.data.result; @@ -46,7 +46,7 @@ class CallAPI { request: { method: "GET", url: url, - response: safeStringify(error), + response: JSON.stringify(error), }, }); throw error; @@ -71,8 +71,8 @@ class CallAPI { request: { method: "POST", url: url, - payload: safeStringify(sendData), - response: safeStringify(response.data.result), + payload: JSON.stringify(sendData), + response: JSON.stringify(response.data.result), }, }); return response.data.result; @@ -84,8 +84,8 @@ class CallAPI { request: { method: "POST", url: url, - payload: safeStringify(sendData), - response: safeStringify(error), + payload: JSON.stringify(sendData), + response: JSON.stringify(error), }, }); throw error; diff --git a/src/interfaces/permission.ts b/src/interfaces/permission.ts index 9980858..cfe76e6 100644 --- a/src/interfaces/permission.ts +++ b/src/interfaces/permission.ts @@ -183,42 +183,7 @@ class CheckAuth { return false; }); } - // public async checkOrg(token: any, keycloakId: string) { - // const redisClient = await this.redis.createClient({ - // host: process.env.REDIS_HOST, - // port: process.env.REDIS_PORT, - // }) - // const getAsync = promisify(redisClient.get).bind(redisClient) - // try { - // let reply = await getAsync("org_" + keycloakId) - // if (reply != null) { - // reply = JSON.parse(reply) - // } else { - // if (!keycloakId) throw new Error("No KeycloakId provided") - // const x = await new CallAPI().GetData( - // { - // headers: { authorization: token }, - // }, - // `/org/permission/checkOrg/${keycloakId}`, - // false - // ) - - // const data = { - // orgRootId: x.orgRootId, - // orgChild1Id: x.orgChild1Id, - // orgChild2Id: x.orgChild2Id, - // orgChild3Id: x.orgChild3Id, - // orgChild4Id: x.orgChild4Id, - // } - - // return data - // } - // } catch (error) { - // console.error("Error calling API:", error) - // throw error - // } - // } - public async checkOrg(token: any, keycloakId: string) { + public async checkOrg(token: any, keycloakId: string) { try { // Validate required environment variables const REDIS_HOST = process.env.REDIS_HOST; diff --git a/src/interfaces/utils.ts b/src/interfaces/utils.ts index 259b113..c667f54 100644 --- a/src/interfaces/utils.ts +++ b/src/interfaces/utils.ts @@ -27,8 +27,8 @@ export function setLogDataDiff(req: RequestWithUser, data: DataDiff) { typeof data.after === "object" ) { req.app.locals.logData.dataDiff = { - before: safeStringify(data.before), - after: safeStringify(data.after), + before: JSON.stringify(data.before), + after: JSON.stringify(data.after), }; } else { console.error("Invalid data provided: both before and after must be valid objects."); @@ -45,33 +45,3 @@ export function addLogSequence(req: RequestWithUser, data: LogSequence) { export function editLogSequence(req: RequestWithUser, index: number, data: LogSequence) { req.app.locals.logData.sequence[index] = data; } - -/** - * ปลอดภัยกว่า JSON.stringify() - * - กัน circular reference - * - จำกัดความลึก - * - return string แน่นอนเสมอ - */ -export function safeStringify(obj: any, space?: number): string { - const cache = new WeakSet(); - - try { - return JSON.stringify( - obj, - (key, value) => { - // ป้องกัน circular reference - if (typeof value === "object" && value !== null) { - if (cache.has(value)) { - return "[Circular]"; - } - cache.add(value); - } - return value; - }, - space, - ); - } catch (err) { - console.error("⚠️ safeStringify error:", err); - return "[Unserializable object]"; - } -} From ce83b8d0a9a4ea5f7c618cd38d768a43ee0099fb Mon Sep 17 00:00:00 2001 From: Adisak Date: Fri, 31 Oct 2025 10:40:27 +0700 Subject: [PATCH 09/17] #28 --- src/controllers/KpiUserEvaluationController.ts | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/controllers/KpiUserEvaluationController.ts b/src/controllers/KpiUserEvaluationController.ts index 9816fb0..ba36285 100644 --- a/src/controllers/KpiUserEvaluationController.ts +++ b/src/controllers/KpiUserEvaluationController.ts @@ -667,6 +667,14 @@ export class KpiUserEvaluationController extends Controller { @Body() requestBody: createKpiUserEvaluation, @Request() request: RequestWithUser, ) { + if(requestBody.evaluatorId == requestBody.commanderId || + requestBody.commanderId == requestBody.commanderHighId || + requestBody.evaluatorId == requestBody.commanderHighId){ + throw new HttpError( + HttpStatusCode.NOT_FOUND, + "ไม่พบข้อมูลรอบการประเมินผลการปฏิบัติหน้าที่ราชการนี้", + ); + } // await new permission().PermissionCreate(request, "SYS_KPI_LIST"); const kpiPeriod = await this.kpiPeriodRepository.findOne({ where: { id: requestBody.kpiPeriodId }, From fd000303579c4d3208cc5daa8176fda6625efa1f Mon Sep 17 00:00:00 2001 From: Adisak Date: Fri, 31 Oct 2025 11:00:27 +0700 Subject: [PATCH 10/17] fix word --- src/controllers/KpiUserEvaluationController.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/controllers/KpiUserEvaluationController.ts b/src/controllers/KpiUserEvaluationController.ts index ba36285..2ecd854 100644 --- a/src/controllers/KpiUserEvaluationController.ts +++ b/src/controllers/KpiUserEvaluationController.ts @@ -672,7 +672,7 @@ export class KpiUserEvaluationController extends Controller { requestBody.evaluatorId == requestBody.commanderHighId){ throw new HttpError( HttpStatusCode.NOT_FOUND, - "ไม่พบข้อมูลรอบการประเมินผลการปฏิบัติหน้าที่ราชการนี้", + "ไม่สามารถเลือกผู้ประเมินหรือผู้บังคับบัญชาซ้ำกันได้", ); } // await new permission().PermissionCreate(request, "SYS_KPI_LIST"); From 7ada384b836f53aca6e17bd98922a701e6b53441 Mon Sep 17 00:00:00 2001 From: Adisak Date: Fri, 31 Oct 2025 14:25:42 +0700 Subject: [PATCH 11/17] add noti and test --- .../KpiUserEvaluationController.ts | 70 +++++++++++++++++++ 1 file changed, 70 insertions(+) diff --git a/src/controllers/KpiUserEvaluationController.ts b/src/controllers/KpiUserEvaluationController.ts index 2ecd854..adfbff7 100644 --- a/src/controllers/KpiUserEvaluationController.ts +++ b/src/controllers/KpiUserEvaluationController.ts @@ -1125,6 +1125,25 @@ export class KpiUserEvaluationController extends Controller { "ไม่พบข้อมูลรายการประเมินผลการปฏิบัติราชการระดับบุคคลนี้", ); } + if (requestBody.status.trim().toUpperCase() == "EVALUATOR") { + await new CallAPI() + .PostData(request, "/placement/noti/profiles", { + subject: `${kpiUserEvaluation.prefix}${kpiUserEvaluation.firstName} ${kpiUserEvaluation.lastName} ส่งคำขออนุมัติแก้ไขข้อตกลงการประเมินผลการปฏิบัติราชการระดับบุคคลให้พิจารณา`, + body: `${kpiUserEvaluation.prefix}${kpiUserEvaluation.firstName} ${kpiUserEvaluation.lastName} ส่งคำขออนุมัติแก้ไขข้อตกลงการประเมินผลการปฏิบัติราชการระดับบุคคลให้พิจารณา`, + receiverUserIds: [ + { + receiverUserId: kpiUserEvaluation.evaluatorId, + notiLink: `${process.env.VITE_URL_USER}/KPI-evaluator/${kpiUserEvaluation.id}`, + }, + ], + payload: "", + isSendMail: true, + isSendInbox: true, + isSendNotification: true, + }) + .then(() => {}) + .catch(() => {}); + } const before = structuredClone(kpiUserEvaluation); kpiUserEvaluation.evaluationReqEdit = requestBody.status.trim().toUpperCase(); kpiUserEvaluation.lastUpdateUserId = request.user.sub; @@ -1838,6 +1857,23 @@ export class KpiUserEvaluationController extends Controller { item.evaluationStatus = "NEW"; } else { item.evaluationReqEdit = "COMMANDER"; + await new CallAPI() + .PostData(request, "/placement/noti/profiles", { + subject: `${item.prefix}${item.firstName} ${item.lastName} ส่งคำขออนุมัติแก้ไขข้อตกลงการประเมินผลการปฏิบัติราชการระดับบุคคลให้พิจารณา`, + body: `${item.prefix}${item.firstName} ${item.lastName} ส่งคำขออนุมัติแก้ไขข้อตกลงการประเมินผลการปฏิบัติราชการระดับบุคคลให้พิจารณา`, + receiverUserIds: [ + { + receiverUserId: item.commanderId, + notiLink: `${process.env.VITE_URL_USER}/KPI-evaluator/${item.id}`, + }, + ], + payload: "", + isSendMail: true, + isSendInbox: true, + isSendNotification: true, + }) + .then(() => {}) + .catch(() => {}); } } } else if (role == "COMMANDER") { @@ -1847,11 +1883,45 @@ export class KpiUserEvaluationController extends Controller { item.evaluationStatus = "NEW"; } else { item.evaluationReqEdit = "COMMANDER_HIGH"; + await new CallAPI() + .PostData(request, "/placement/noti/profiles", { + subject: `${item.prefix}${item.firstName} ${item.lastName} ส่งคำขออนุมัติแก้ไขข้อตกลงการประเมินผลการปฏิบัติราชการระดับบุคคลให้พิจารณา`, + body: `${item.prefix}${item.firstName} ${item.lastName} ส่งคำขออนุมัติแก้ไขข้อตกลงการประเมินผลการปฏิบัติราชการระดับบุคคลให้พิจารณา`, + receiverUserIds: [ + { + receiverUserId: item.commanderHighId, + notiLink: `${process.env.VITE_URL_USER}/KPI-evaluator/${item.id}`, + }, + ], + payload: "", + isSendMail: true, + isSendInbox: true, + isSendNotification: true, + }) + .then(() => {}) + .catch(() => {}); } } } else { item.evaluationReqEdit = requestBody.status.trim().toUpperCase(); item.evaluationStatus = "NEW"; + await new CallAPI() + .PostData(request, "/placement/noti/profiles", { + subject: `คำขออนุมัติแก้ไขข้อตกลงการประเมินผลการปฏิบัติราชการระดับบุคคลได้รับการพิจารณาแล้ว`, + body: `คำขออนุมัติแก้ไขข้อตกลงการประเมินผลการปฏิบัติราชการระดับบุคคลได้รับการพิจารณาแล้ว`, + receiverUserIds: [ + { + receiverUserId: item.profileId, + notiLink: `${process.env.VITE_URL_USER}/KPI-evaluator/${item.id}`, + }, + ], + payload: "", + isSendMail: true, + isSendInbox: true, + isSendNotification: true, + }) + .then(() => {}) + .catch(() => {}); } } else { item.evaluationReqEdit = requestBody.status.trim().toUpperCase(); From ca1a8d2c8f2b71402b10ddcf68984655963dcf40 Mon Sep 17 00:00:00 2001 From: Adisak Date: Fri, 31 Oct 2025 14:34:32 +0700 Subject: [PATCH 12/17] fix word --- src/controllers/KpiUserEvaluationController.ts | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/src/controllers/KpiUserEvaluationController.ts b/src/controllers/KpiUserEvaluationController.ts index adfbff7..02dec2b 100644 --- a/src/controllers/KpiUserEvaluationController.ts +++ b/src/controllers/KpiUserEvaluationController.ts @@ -1128,8 +1128,8 @@ export class KpiUserEvaluationController extends Controller { if (requestBody.status.trim().toUpperCase() == "EVALUATOR") { await new CallAPI() .PostData(request, "/placement/noti/profiles", { - subject: `${kpiUserEvaluation.prefix}${kpiUserEvaluation.firstName} ${kpiUserEvaluation.lastName} ส่งคำขออนุมัติแก้ไขข้อตกลงการประเมินผลการปฏิบัติราชการระดับบุคคลให้พิจารณา`, - body: `${kpiUserEvaluation.prefix}${kpiUserEvaluation.firstName} ${kpiUserEvaluation.lastName} ส่งคำขออนุมัติแก้ไขข้อตกลงการประเมินผลการปฏิบัติราชการระดับบุคคลให้พิจารณา`, + subject: `${kpiUserEvaluation.prefix}${kpiUserEvaluation.firstName} ${kpiUserEvaluation.lastName} ส่งคำขอแก้ไขข้อตกลงการประเมินผลการปฏิบัติราชการระดับบุคคลให้พิจารณา`, + body: `${kpiUserEvaluation.prefix}${kpiUserEvaluation.firstName} ${kpiUserEvaluation.lastName} ส่งคำขอแก้ไขข้อตกลงการประเมินผลการปฏิบัติราชการระดับบุคคลให้พิจารณา`, receiverUserIds: [ { receiverUserId: kpiUserEvaluation.evaluatorId, @@ -1859,8 +1859,8 @@ export class KpiUserEvaluationController extends Controller { item.evaluationReqEdit = "COMMANDER"; await new CallAPI() .PostData(request, "/placement/noti/profiles", { - subject: `${item.prefix}${item.firstName} ${item.lastName} ส่งคำขออนุมัติแก้ไขข้อตกลงการประเมินผลการปฏิบัติราชการระดับบุคคลให้พิจารณา`, - body: `${item.prefix}${item.firstName} ${item.lastName} ส่งคำขออนุมัติแก้ไขข้อตกลงการประเมินผลการปฏิบัติราชการระดับบุคคลให้พิจารณา`, + subject: `${item.prefix}${item.firstName} ${item.lastName} ส่งคำขอแก้ไขข้อตกลงการประเมินผลการปฏิบัติราชการระดับบุคคลให้พิจารณา`, + body: `${item.prefix}${item.firstName} ${item.lastName} ส่งคำขอแก้ไขข้อตกลงการประเมินผลการปฏิบัติราชการระดับบุคคลให้พิจารณา`, receiverUserIds: [ { receiverUserId: item.commanderId, @@ -1885,8 +1885,8 @@ export class KpiUserEvaluationController extends Controller { item.evaluationReqEdit = "COMMANDER_HIGH"; await new CallAPI() .PostData(request, "/placement/noti/profiles", { - subject: `${item.prefix}${item.firstName} ${item.lastName} ส่งคำขออนุมัติแก้ไขข้อตกลงการประเมินผลการปฏิบัติราชการระดับบุคคลให้พิจารณา`, - body: `${item.prefix}${item.firstName} ${item.lastName} ส่งคำขออนุมัติแก้ไขข้อตกลงการประเมินผลการปฏิบัติราชการระดับบุคคลให้พิจารณา`, + subject: `${item.prefix}${item.firstName} ${item.lastName} ส่งคำขอแก้ไขข้อตกลงการประเมินผลการปฏิบัติราชการระดับบุคคลให้พิจารณา`, + body: `${item.prefix}${item.firstName} ${item.lastName} ส่งคำขอแก้ไขข้อตกลงการประเมินผลการปฏิบัติราชการระดับบุคคลให้พิจารณา`, receiverUserIds: [ { receiverUserId: item.commanderHighId, @@ -1907,8 +1907,8 @@ export class KpiUserEvaluationController extends Controller { item.evaluationStatus = "NEW"; await new CallAPI() .PostData(request, "/placement/noti/profiles", { - subject: `คำขออนุมัติแก้ไขข้อตกลงการประเมินผลการปฏิบัติราชการระดับบุคคลได้รับการพิจารณาแล้ว`, - body: `คำขออนุมัติแก้ไขข้อตกลงการประเมินผลการปฏิบัติราชการระดับบุคคลได้รับการพิจารณาแล้ว`, + subject: `คำขอแก้ไขข้อตกลงการประเมินผลการปฏิบัติราชการระดับบุคคลได้รับการพิจารณาแล้ว`, + body: `คำขอแก้ไขข้อตกลงการประเมินผลการปฏิบัติราชการระดับบุคคลได้รับการพิจารณาแล้ว`, receiverUserIds: [ { receiverUserId: item.profileId, From 4d33c8fc9f83a25615f22b65c73d62a94db42c36 Mon Sep 17 00:00:00 2001 From: Adisak Date: Fri, 31 Oct 2025 14:37:52 +0700 Subject: [PATCH 13/17] add \n orgName --- src/controllers/KpiUserEvaluationController.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/controllers/KpiUserEvaluationController.ts b/src/controllers/KpiUserEvaluationController.ts index 02dec2b..4a95262 100644 --- a/src/controllers/KpiUserEvaluationController.ts +++ b/src/controllers/KpiUserEvaluationController.ts @@ -421,7 +421,7 @@ export class KpiUserEvaluationController extends Controller { const organization = fullNameParts .filter((part) => part !== undefined && part !== null) - .join(" "); + .join("\n"); return { id: item.id, From c31af516b3aec267c9e7c813d721b4b7f27e1694 Mon Sep 17 00:00:00 2001 From: Adisak Date: Fri, 31 Oct 2025 15:08:22 +0700 Subject: [PATCH 14/17] add \n --- src/controllers/KpiUserDevelopmentController.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/controllers/KpiUserDevelopmentController.ts b/src/controllers/KpiUserDevelopmentController.ts index 5512e8f..897aa26 100644 --- a/src/controllers/KpiUserDevelopmentController.ts +++ b/src/controllers/KpiUserDevelopmentController.ts @@ -533,7 +533,7 @@ export class KpiUserDevelopmentController extends Controller { const organization = fullNameParts .filter((part) => part !== undefined && part !== null) - .join(" "); + .join("\n"); return { id: item.id, From f026ffde89e7b20275e782296734a6b2ae5d296a Mon Sep 17 00:00:00 2001 From: Adisak Date: Mon, 3 Nov 2025 10:01:43 +0700 Subject: [PATCH 15/17] test throw report api --- src/controllers/ReportController.ts | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/controllers/ReportController.ts b/src/controllers/ReportController.ts index 49fa8c3..e7121c2 100644 --- a/src/controllers/ReportController.ts +++ b/src/controllers/ReportController.ts @@ -12,6 +12,7 @@ import { KpiCapacity } from "../entities/kpiCapacity"; import { KpiPlan } from "../entities/kpiPlan"; import { KpiRole } from "../entities/kpiRole"; import CallAPI from "../interfaces/call-api"; +import { throws } from "assert"; @Route("api/v1/kpi/report") @Tags("Report") @Security("bearerAuth") @@ -775,6 +776,9 @@ export class ReportController extends Controller { relations: ["kpiPeriod"], where: { id: In(profileEvaluationIds.map((evaluation) => evaluation.id)) }, }); + if(!profileEvaluation || profileEvaluation.length === 0){ + throw new HttpError(HttpStatusCode.NOT_FOUND, "ไม่พบข้อมูลรายงานการประเมินผลฯ ระดับบุคคลของบุคคลนี้"); + } const combinedData: KPIData = profileEvaluation.reduce( (acc: KPIData, x) => { From e706475d8106f1670e37e2ed99b2d19c778555e2 Mon Sep 17 00:00:00 2001 From: Adisak Date: Mon, 3 Nov 2025 10:11:02 +0700 Subject: [PATCH 16/17] #29 --- src/controllers/ReportController.ts | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/controllers/ReportController.ts b/src/controllers/ReportController.ts index e7121c2..87bfa13 100644 --- a/src/controllers/ReportController.ts +++ b/src/controllers/ReportController.ts @@ -1159,6 +1159,9 @@ export class ReportController extends Controller { relations: ["kpiPeriod"], where: { id: In(profileEvaluationIds.map((evaluation) => evaluation.id)) }, }); + if(!profileEvaluation || profileEvaluation.length === 0){ + throw new HttpError(HttpStatusCode.NOT_FOUND, "ไม่พบข้อมูลรายงานการประเมินผลฯ ระดับบุคคลของบุคคลนี้"); + } const combinedData: KPIData = profileEvaluation.reduce( (acc: KPIData, x) => { @@ -1675,7 +1678,7 @@ export class ReportController extends Controller { const date = new Date(); formattedData = { root: data && data.rootName != null ? data.rootName : "-", - period: data?.durationKPI == "APR" ? "๑" : data?.durationKPI == "OCT" ? "๒" : "-", + period: data?.durationKPI == "APR" ? "๑ เมษายน" : data?.durationKPI == "OCT" ? "๑ ตุลาคม" : "-", year: data.year ? Extension.ToThaiNumber((data.year + 543).toString()) : "-", date: Extension.ToThaiNumber(Extension.ToThaiFullDate2(date).toString()), userEvaluations: From 43aeb144d4fba6eaf5fc077663c7292eccabd5ae Mon Sep 17 00:00:00 2001 From: Adisak Date: Mon, 3 Nov 2025 10:29:25 +0700 Subject: [PATCH 17/17] update --- src/controllers/ReportController.ts | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/controllers/ReportController.ts b/src/controllers/ReportController.ts index 87bfa13..e3349f3 100644 --- a/src/controllers/ReportController.ts +++ b/src/controllers/ReportController.ts @@ -572,7 +572,9 @@ export class ReportController extends Controller { "AVG(kpiUserEvaluation.summaryPoint) as avgSummaryPoint" ]) .getRawMany(); - + if(!profileEvaluationNowYearIds || profileEvaluationNowYearIds.length === 0){ + throw new HttpError(HttpStatusCode.NOT_FOUND, "ไม่พบข้อมูลรายงานการประเมินผลฯ ระดับบุคคลของบุคคลนี้"); + } // const profileEvaluations = await this.kpiUserEvaluationRepository.find({ // relations: ["kpiPeriod"], // where: { id: In(profileEvaluationNowYearIds.map((evaluation) => evaluation.id)) }, @@ -1500,6 +1502,9 @@ export class ReportController extends Controller { .groupBy("kpiUserEvaluation.kpiPeriodId") .select("MIN(kpiUserEvaluation.id) as id") .getRawMany(); + if(!profileEvaluationIds || profileEvaluationIds.length === 0){ + throw new HttpError(HttpStatusCode.NOT_FOUND, "ไม่พบข้อมูลรายงานการประเมินผลฯ ระดับบุคคลของบุคคลนี้"); + } if (profileEvaluationIds.length > 0) { userInfo = await this.kpiUserEvaluationRepository.find({ where: { @@ -1532,7 +1537,6 @@ export class ReportController extends Controller { const dev20text = "การเรียนรู้จากผู้อื่น (Coach/Mentor/Consulting)"; const dev70text = "การลงมือปฏิบัติ (โดยผู้บังคับบัญชามอบหมาย)"; const combianText = [dev10text, dev20text, dev70text]; - formattedUserDevelopmentLists = userDevelopmentLists.map( (development: any, index: number) => ({ no: Extension.ToThaiNumber((index + 1).toString()),