Compare commits

...

207 commits

Author SHA1 Message Date
harid
15ac8d0514 ส่งรายชื่อไปออกคำสั่ง C-PM-25, C-PM-26 ให้ปั๊ม commandCode #2377
All checks were successful
Build & Deploy on Dev / build (push) Successful in 1m3s
2026-04-03 15:36:25 +07:00
97e5b8abc3 fix bug
All checks were successful
Build & Deploy on Dev / build (push) Successful in 1m12s
2026-04-02 18:14:10 +07:00
harid
2f834d3644 Merge branch 'develop' into develop-Bright
All checks were successful
Build & Deploy on Dev / build (push) Successful in 1m6s
2026-04-02 17:02:54 +07:00
harid
2fd99aaa94 fix ประวัติคนครอง #2405 2026-04-02 17:02:20 +07:00
58dd4cfd60 fix isDelete = false check by isActive = true
All checks were successful
Build & Deploy on Dev / build (push) Successful in 1m3s
2026-04-02 17:00:36 +07:00
f7e8729e60 fix create user isDelete = false 2026-04-02 16:58:37 +07:00
fb4196cfa2 fix keycloak user & update isDelete
All checks were successful
Build & Deploy on Dev / build (push) Successful in 1m1s
2026-04-02 16:32:29 +07:00
adisak
212360a764 #2387 update
All checks were successful
Build & Deploy on Dev / build (push) Successful in 1m5s
2026-04-02 15:01:38 +07:00
adisak
2de708ae37 Merge branch 'develop' into adiDev
All checks were successful
Build & Deploy on Dev / build (push) Successful in 58s
2026-04-02 14:18:31 +07:00
adisak
da6fd7a396 #2387 update 2026-04-02 14:18:08 +07:00
harid
c38da229da Merge branch 'develop' into develop-Bright 2026-04-02 13:53:37 +07:00
harid
a40d98c5a9 Migrate update profile &profileEmployee add isDelete 2026-04-02 13:52:39 +07:00
adisak
34e8ec8434 Merge branch 'develop' into adiDev
All checks were successful
Build & Deploy on Dev / build (push) Successful in 1m1s
2026-04-02 12:04:55 +07:00
adisak
38e5ed0e91 #2387 [กทม]. ระบบโครงสร้างอัตรากำลัง >> กรณีนั่งทับตำแหน่ง 2026-04-02 12:04:33 +07:00
harid
b69f8a6c08 fix ส่งออกคำสั่งรายชื่อผู้สอบผ่าน รายชื่อไม่แสดงในโครงสร้าง #2402
All checks were successful
Build & Deploy on Dev / build (push) Successful in 1m14s
2026-04-02 11:27:39 +07:00
harid
d553c1406c [Fix] กรณีพ้นราชการแก้ไขเป็นไม่ต้อง clear ฟิลด์ keycloak #228
All checks were successful
Build & Deploy on Dev / build (push) Successful in 1m7s
2026-04-01 17:50:38 +07:00
harid
dc31ec0d7d api สร้างข้อมูลการมาสาย/ขาดราชการ (no token)
All checks were successful
Build & Deploy on Dev / build (push) Successful in 1m7s
2026-03-31 10:48:49 +07:00
harid
264c134838 api list รายชื่อ (no token)
All checks were successful
Build & Deploy on Dev / build (push) Successful in 1m3s
2026-03-30 14:48:54 +07:00
harid
e01a4f22c5 fix report kk1
All checks were successful
Build & Deploy on Dev / build (push) Successful in 1m11s
2026-03-25 17:55:02 +07:00
harid
effc782460 fix report kk1
All checks were successful
Build & Deploy on Dev / build (push) Successful in 1m4s
2026-03-25 15:53:26 +07:00
Suphonchai Phoonsawat
702eb13782 Merge branch 'develop' into working
All checks were successful
Build & Deploy on Dev / build (push) Successful in 1m0s
2026-03-25 14:34:15 +07:00
Suphonchai Phoonsawat
936b28a9f4 Enhance OrganizationDotnetController: Add orgRevision to current_holders and improve profile mapping 2026-03-25 14:33:44 +07:00
harid
ef0d6ea1b5 Migrate add absentLateHistory
All checks were successful
Build & Deploy on Dev / build (push) Successful in 1m41s
2026-03-25 11:48:22 +07:00
Suphonchai Phoonsawat
9760c4f667 Merge branch 'develop' into working 2026-03-25 10:07:08 +07:00
harid
ecb3cb1d2a no message
All checks were successful
Build & Deploy on Dev / build (push) Successful in 1m16s
2026-03-24 17:47:45 +07:00
harid
e7b1bb13bb รายงาน ทปอ.สามัญ (แก้ไขเพิ่มเติม) #2360
All checks were successful
Build & Deploy on Dev / build (push) Successful in 1m17s
2026-03-24 16:44:38 +07:00
Suphonchai Phoonsawat
d83c8241fa Implement feature X to enhance user experience and fix bug Y in module Z 2026-03-24 15:25:11 +07:00
harid
9f9fd612d3 fix report kk1
All checks were successful
Build & Deploy on Dev / build (push) Successful in 2m48s
2026-03-23 17:44:11 +07:00
harid
a76bda34b4 Migration ฟิลด์ตาราง profileLeave, profileLeaveHistory & สร้างตาราง profileAbsentLate, profileEmployeeAbsentLate
All checks were successful
Build & Deploy on Dev / build (push) Successful in 1m52s
2026-03-23 15:36:54 +07:00
Suphonchai Phoonsawat
41ad2a44e8 Implement feature X to enhance user experience and optimize performance #1555
All checks were successful
Build & Deploy on Dev / build (push) Successful in 1m19s
2026-03-23 10:35:24 +07:00
harid
5a4b7c92a3 fix blank row report kk1
All checks were successful
Build & Deploy on Dev / build (push) Successful in 1m40s
2026-03-20 17:51:39 +07:00
harid
2fdf5e5854 แก้ไข middleware log ให้ใช้จาก token #223
All checks were successful
Build & Deploy on Dev / build (push) Successful in 1m21s
2026-03-20 16:55:27 +07:00
harid
251e87d2b2 fix ถ้า USER เป็น STAFF จึงจะกรองสิทธิ์ #2359
All checks were successful
Build & Deploy on Dev / build (push) Successful in 1m18s
2026-03-20 13:26:39 +07:00
harid
866afe5ee1 Merge branch 'develop' into develop-Bright
All checks were successful
Build & Deploy on Dev / build (push) Successful in 1m33s
2026-03-20 11:03:29 +07:00
harid
bc83a2bc40 แก้ข้อความ/ตัดข้อมูลรายงาน ทปอ.สามัญ #2274 2026-03-20 11:02:53 +07:00
ff752da7dd fix remove await and addLogSequence of exprofile system
All checks were successful
Build & Deploy on Dev / build (push) Successful in 1m27s
2026-03-19 11:13:27 +07:00
harid
66d8ba089d addLogSequence 2026-03-19 10:16:09 +07:00
harid
5d68a245a8 fix bug ผู้ดำเนินการแสดงซ้ำ #2355
All checks were successful
Build & Deploy on Dev / build (push) Successful in 1m17s
2026-03-13 13:24:49 +07:00
harid
41f3c5ca54 no message
All checks were successful
Build & Deploy on Dev / build (push) Successful in 1m33s
2026-03-12 18:31:24 +07:00
harid
6b3d1dc67b API ลบข้อมูลการฝึกอบรม/ดูงาน และ การพัฒนารายบุคคล IDP รายบุคคล
All checks were successful
Build & Deploy on Dev / build (push) Successful in 1m17s
2026-03-12 18:01:15 +07:00
harid
5ef5f7d4ed fix bug #2183
All checks were successful
Build & Deploy on Dev / build (push) Successful in 1m22s
2026-03-12 14:53:45 +07:00
harid
6a07841763 fix bug ออกคำสั่งยกเลิกลาออก #2183 + api แก้ไขเปลี่ยนผู้สร้างคำสั่ง #1551
All checks were successful
Build & Deploy on Dev / build (push) Successful in 1m20s
2026-03-10 18:12:58 +07:00
baa8496a69 refactor api act/{id}
All checks were successful
Build & Deploy on Dev / build (push) Successful in 1m25s
2026-03-10 11:45:25 +07:00
5102805278 comment update dna(keycloak) when command 2026-03-09 16:17:26 +07:00
060ac81532 #2345
All checks were successful
Build & Deploy on Dev / build (push) Successful in 3m14s
2026-03-09 14:44:38 +07:00
91887ec63d fix ignore file 2026-03-07 21:14:50 +07:00
76fc488d25 fix sync to keycloak 2026-03-06 19:11:20 +07:00
harid
81288f8db3 list ปลัด และรองปลัดใน popup ผู้บังคับบัญชา และผู้มีอำนาจเพิ่ม
All checks were successful
Build & Deploy on Dev / build (push) Successful in 1m20s
2026-03-06 14:34:41 +07:00
harid
f8bb9e7cab เพิ่มฟิลด์ที่ทำให้รู้ว่าการร้องขอนี้มาจากสำนักปลัด #2222
All checks were successful
Build & Deploy on Dev / build (push) Successful in 1m27s
2026-03-04 17:41:37 +07:00
f59a5eec80 fix tag version
All checks were successful
Build & Deploy on Dev / build (push) Successful in 1m30s
2026-03-04 17:02:19 +07:00
0ecd354152 remove action on build tag
All checks were successful
Build & Deploy on Dev / build (push) Successful in 1m33s
2026-03-04 16:55:07 +07:00
e50b95e9bd fix build from tag
Some checks failed
Build / build (push) Has been cancelled
Build & Deploy on Dev / build (push) Has been cancelled
2026-03-04 16:53:09 +07:00
harid
c7c2f3a6c2 รายการออกคำสั่ง owner เห็นหมด #1551 & Fix Bug ลูกจ้างประจำสมัครสอบเป็น ขรก. #2343
All checks were successful
Build & Deploy on Dev / build (push) Successful in 1m38s
2026-03-04 14:39:02 +07:00
2951630b7b fix update prefix and profileId
All checks were successful
Build & Deploy on Dev / build (push) Successful in 1m45s
2026-02-27 15:24:51 +07:00
e4f46a1762 fix condition org revision current id of perm and term
All checks were successful
Build & Deploy on Dev / build (push) Successful in 1m48s
2026-02-27 11:57:27 +07:00
49a8494a8d fix emp temp and add script clear org dna at keycloak 2026-02-27 11:40:56 +07:00
b714dfe239 add script update org and sync keycloak, setup time run cronjob
All checks were successful
Build & Deploy on Dev / build (push) Successful in 1m26s
2026-02-27 10:05:13 +07:00
911d9b6bc5 fix bug error
All checks were successful
Build & Deploy on Dev / build (push) Successful in 2m29s
2026-02-27 09:30:46 +07:00
35b5d16292 fix
All checks were successful
Build & Deploy on Dev / build (push) Successful in 2m25s
2026-02-27 07:56:04 +07:00
c5c19b6d5e Revert "fix: sync and script keycloak"
All checks were successful
Build & Deploy on Dev / build (push) Successful in 2m11s
This reverts commit d667ad9173.
2026-02-26 23:16:43 +07:00
d667ad9173 fix: sync and script keycloak
All checks were successful
Build & Deploy on Dev / build (push) Successful in 1m59s
2026-02-26 23:09:22 +07:00
1c629cc6e0 Revert "Merge branch 'feat/keyloak-token-data' into develop"
All checks were successful
Build & Deploy on Dev / build (push) Successful in 2m21s
This reverts commit 1b3806c6f7, reversing
changes made to 79dbba2c89.
2026-02-26 22:37:03 +07:00
30fd08fc85 fix
All checks were successful
Build & Deploy on Dev / build (push) Successful in 2m34s
2026-02-26 22:31:30 +07:00
aad0a03a17 fix
All checks were successful
Build & Deploy on Dev / build (push) Successful in 2m46s
2026-02-26 22:23:38 +07:00
693afddc22 fix
All checks were successful
Build & Deploy on Dev / build (push) Successful in 2m29s
2026-02-26 22:10:33 +07:00
f1f4717b5b fix
All checks were successful
Build & Deploy on Dev / build (push) Successful in 2m29s
2026-02-26 22:04:02 +07:00
cae5aeae47 test
All checks were successful
Build & Deploy on Dev / build (push) Successful in 2m19s
2026-02-26 21:55:51 +07:00
26bcfd728e fix: handle error
All checks were successful
Build & Deploy on Dev / build (push) Successful in 2m0s
2026-02-26 21:47:10 +07:00
1b3806c6f7 Merge branch 'feat/keyloak-token-data' into develop
All checks were successful
Build & Deploy on Dev / build (push) Successful in 2m18s
* feat/keyloak-token-data:
  completed sync  script update keycloak
  setup auth middleware and sync code
  init claude overview project
  add: sync-users-to-keycloak
2026-02-26 21:25:43 +07:00
625885973e completed sync script update keycloak 2026-02-26 21:25:07 +07:00
harid
79dbba2c89 Fix #2343
All checks were successful
Build & Deploy on Dev / build (push) Successful in 2m17s
2026-02-26 17:53:59 +07:00
harid
9946b7b7c3 เรียงรายชื่อตามเลขที่ตำแหน่ง #2325
All checks were successful
Build & Deploy on Dev / build (push) Successful in 1m28s
2026-02-26 11:09:08 +07:00
harid
ea25979374 Fix bug Order posExecutiveNameOrg
All checks were successful
Build & Deploy on Dev / build (push) Successful in 1m14s
2026-02-25 10:46:06 +07:00
harid
4268f93ed3 Merge branch 'develop' into develop-Bright
All checks were successful
Build & Deploy on Dev / build (push) Successful in 1m27s
2026-02-24 17:19:03 +07:00
harid
d7b45c322d เลือกตำแหน่งเพื่อแต่งตั้งลูกจ้างชั่วคราว แสดงตำแหน่งที่ติดเงื่อน 2026-02-24 17:18:57 +07:00
harid
34fb5321b1 เลือกตำแหน่งเพื่อแต่งตั้งลูกจ้างชั่วคราว แสดงตำแหน่งที่ติดเงื่อน 2026-02-24 17:17:12 +07:00
4018b9f7ce fix:save dna
All checks were successful
Build & Deploy on Dev / build (push) Successful in 1m20s
2026-02-24 17:12:23 +07:00
dda36a05a9 Merge branch 'develop' of github.com:Frappet/hrms-api-org into develop
All checks were successful
Build & Deploy on Dev / build (push) Successful in 1m11s
2026-02-24 14:57:24 +07:00
61c92088b1 fix 2026-02-24 14:57:15 +07:00
harid
cdbdfce7af Migrate update employeePosLevel.posLevelName Change Int to Number
All checks were successful
Build & Deploy on Dev / build (push) Successful in 1m18s
2026-02-24 13:28:57 +07:00
e8890133bb Merge branch 'develop' of github.com:Frappet/hrms-api-org into develop
All checks were successful
Build & Deploy on Dev / build (push) Successful in 1m11s
2026-02-23 16:46:30 +07:00
d85d245273 #2221 2026-02-23 16:37:50 +07:00
harid
f6c726baa5 Migrate อัตรากำลังลูกจ้างประจำ เพิ่มเมนูคัดลอก/จัดการตำแหน่งติดเงื่อนไข #2316
All checks were successful
Build & Deploy on Dev / build (push) Successful in 1m25s
&& Fix Bug กดลบถาวรคำสั่งแล้ว error #216
2026-02-23 15:06:32 +07:00
harid
673da9940d Insert ประวัติแก้ไขข้อมูลส่วนตัว กรณีแก้ไขครั้งแรก Task #2314
All checks were successful
Build & Deploy on Dev / build (push) Successful in 1m25s
2026-02-23 09:22:27 +07:00
4e396b454d closed#2190 post to exprofile if production only
All checks were successful
Build & Deploy on Dev / build (push) Successful in 1m24s
2026-02-20 15:17:09 +07:00
harid
637e995915 Fix bug #54
All checks were successful
Build & Deploy on Dev / build (push) Successful in 1m34s
2026-02-20 11:46:46 +07:00
8497e5df57 fix: case clear position all in draft to public to current
All checks were successful
Build & Deploy on Dev / build (push) Successful in 1m34s
2026-02-19 16:29:36 +07:00
92e20966d0 Merge branch 'fix/org-chart' into develop
All checks were successful
Build & Deploy on Dev / build (push) Successful in 1m34s
* fix/org-chart:
  fix: bug query
2026-02-19 15:44:19 +07:00
a80fe85032 fix: bug query 2026-02-19 15:41:49 +07:00
harid
caacf07c76 แก้ไขสิทธิ์ PARENT ให้เห็นข้อมูลทั้งหมดทุกหน่วยงาน #54
All checks were successful
Build & Deploy on Dev / build (push) Successful in 1m18s
2026-02-19 14:27:02 +07:00
harid
da75287882 no message
All checks were successful
Build & Deploy on Dev / build (push) Successful in 1m22s
2026-02-19 13:36:07 +07:00
harid
f1d9831055 comment call leave service 2026-02-19 13:35:12 +07:00
525a885e13 migration and #2317(2)
All checks were successful
Build & Deploy on Dev / build (push) Successful in 1m27s
2026-02-19 10:36:32 +07:00
harid
cc6e61f4de Merge branch 'develop' into develop-Bright
All checks were successful
Build & Deploy on Dev / build (push) Successful in 1m25s
2026-02-19 09:33:26 +07:00
harid
c8ed816a1f filter case call leave server error #2304 & เพิ่มตัวเลือกการครอง-ไม่ครองตำแหน่ง #2311 2026-02-19 09:32:17 +07:00
71dcba33e9 migration and #2317
All checks were successful
Build & Deploy on Dev / build (push) Successful in 1m22s
2026-02-18 18:09:20 +07:00
df2f1c5b12 fix
All checks were successful
Build & Deploy on Dev / build (push) Successful in 1m12s
2026-02-18 15:57:15 +07:00
c84b992c0c #2315
All checks were successful
Build & Deploy on Dev / build (push) Successful in 1m9s
2026-02-18 15:50:55 +07:00
c5241b7a63 #1542
All checks were successful
Build & Deploy on Dev / build (push) Successful in 1m12s
2026-02-18 14:53:50 +07:00
harid
9382482f06 api soft delete 2026-02-16 10:30:15 +07:00
17f7fc5d84 fix registry employer Invalid time value
All checks were successful
Build & Deploy on Dev / build (push) Successful in 1m23s
2026-02-13 17:45:44 +07:00
1a9947d362 fix error Invalid time value
All checks were successful
Build & Deploy on Dev / build (push) Successful in 1m16s
2026-02-13 17:03:43 +07:00
0a3deb4293 fix
All checks were successful
Build & Deploy on Dev / build (push) Successful in 1m11s
2026-02-13 16:56:56 +07:00
7029b18a97 fix: error Invalid time value
All checks were successful
Build & Deploy on Dev / build (push) Successful in 1m16s
2026-02-13 16:49:00 +07:00
DESKTOP-1R2VSQH\Lenovo ThinkPad E490
307be83574 fix: path url attachmentPayloads == 'sub-file'
All checks were successful
Build & Deploy on Dev / build (push) Successful in 1m14s
2026-02-13 15:10:01 +07:00
a16ae79c7e fix: ไฟล์แนบท้ายดาวน์โหลดไม่ได้
All checks were successful
Build & Deploy on Dev / build (push) Successful in 1m20s
2026-02-13 12:50:35 +07:00
74b2694aef Merge branch 'develop' into task/#1542 2026-02-13 09:30:06 +07:00
cfd5ced28a feat: isAllRoot 2026-02-13 09:29:31 +07:00
harid
9a1acc0b7d อัปเดตจำนวนสิทธิ์การลาเมื่อผ่านทดลองงานฯ #2304
All checks were successful
Build & Deploy on Dev / build (push) Successful in 1m19s
2026-02-12 18:31:24 +07:00
d555c70af9 fix: script insert
All checks were successful
Build & Deploy on Dev / build (push) Successful in 1m23s
2026-02-12 18:18:29 +07:00
0f4bee4489 fix script 2026-02-12 18:06:46 +07:00
22fd9152bf fix: new root
All checks were successful
Build & Deploy on Dev / build (push) Successful in 1m19s
2026-02-12 17:10:44 +07:00
d916334537 fix bug: add ancestorDNA org all leavel
All checks were successful
Build & Deploy on Dev / build (push) Successful in 1m11s
2026-02-12 15:39:16 +07:00
82ecf2cb81 fix: save posMasterHistory null
All checks were successful
Build & Deploy on Dev / build (push) Successful in 1m19s
2026-02-12 14:06:07 +07:00
ed70999eac Merge branch 'feat/org-move-draf-current' into develop
All checks were successful
Build & Deploy on Dev / build (push) Successful in 1m18s
* feat/org-move-draf-current:
  fix: bug save posMasterHistory, tuning performance script
2026-02-12 13:17:50 +07:00
ef17236eb0 fix: bug save posMasterHistory, tuning performance script 2026-02-12 13:16:43 +07:00
harid
3e684df6c5 Merge branch 'develop' into develop-Bright
All checks were successful
Build & Deploy on Dev / build (push) Successful in 1m19s
2026-02-12 12:12:25 +07:00
harid
64aca4f5fa Migrate move isDeleted in profileSalary 2026-02-12 12:12:19 +07:00
harid
f03ccb78ac Migrate move isDeleted in profileSalary 2026-02-12 12:10:14 +07:00
9927c73547 Merge branch 'develop' of github.com:Frappet/bma-ehr-organization into develop
All checks were successful
Build & Deploy on Dev / build (push) Successful in 1m10s
* 'develop' of github.com:Frappet/bma-ehr-organization:
  Migration add fields isDeleted #210
2026-02-12 11:53:55 +07:00
13fa8cbf24 fix: script case nextholder id null 2026-02-12 11:53:24 +07:00
harid
65e3740cc2 Migration add fields isDeleted #210 2026-02-12 11:47:53 +07:00
3c9e3a1bb6 fix: script org move draf to current save posMasterHistory
All checks were successful
Build & Deploy on Dev / build (push) Successful in 1m57s
2026-02-12 10:38:16 +07:00
7694a83d5a fix
All checks were successful
Build & Deploy on Dev / build (push) Successful in 1m29s
2026-02-11 16:50:32 +07:00
harid
b11d7e45e2 Fix bug แก้ไขอัตรากำลัง แล้วระบบแจ้ง error #211
All checks were successful
Build & Deploy on Dev / build (push) Successful in 1m12s
2026-02-11 15:45:03 +07:00
1f809d3e22 fix: #2234
All checks were successful
Build & Deploy on Dev / build (push) Successful in 1m26s
2026-02-11 15:13:40 +07:00
17760212d1 Merge branch 'feat/org-move-draf-current' into develop
All checks were successful
Build & Deploy on Dev / build (push) Successful in 1m47s
* feat/org-move-draf-current:
  fix time out
2026-02-11 13:11:10 +07:00
c5e600900c fix time out 2026-02-11 13:10:42 +07:00
harid
a3d9d40a52 api getProfile (No token)
All checks were successful
Build & Deploy on Dev / build (push) Successful in 1m15s
2026-02-11 11:31:09 +07:00
harid
073da70a68 move isProbatin
All checks were successful
Build & Deploy on Dev / build (push) Successful in 1m22s
2026-02-11 10:40:01 +07:00
harid
5bee360280 add orgDnaId
All checks were successful
Build & Deploy on Dev / build (push) Successful in 1m17s
2026-02-11 09:52:35 +07:00
harid
4f900ba4d2 no message
All checks were successful
Build & Deploy on Dev / build (push) Successful in 1m20s
2026-02-11 09:47:45 +07:00
520b42f2c7 fix: script update profile
All checks were successful
Build & Deploy on Dev / build (push) Successful in 1m10s
2026-02-10 17:43:01 +07:00
c344804936 Merge branch 'develop' into feat/org-move-draf-current
* develop:
  Fix Script #2292
  Fix Script #2292
  comment ห้ามลบเจ้าหน้าที่ลำดับที่ 1 #2220
  optimize sort #2260
2026-02-10 16:24:34 +07:00
3b97e52bd6 complete script move draf to current 2026-02-10 16:23:52 +07:00
harid
19d7799b5a Fix Script #2292
All checks were successful
Build & Deploy on Dev / build (push) Successful in 50s
2026-02-10 13:19:09 +07:00
harid
ecfb65e159 Fix Script #2292
All checks were successful
Build & Deploy on Dev / build (push) Successful in 56s
2026-02-10 11:33:50 +07:00
harid
d6bb9be93d comment ห้ามลบเจ้าหน้าที่ลำดับที่ 1 #2220
All checks were successful
Build & Deploy on Dev / build (push) Successful in 3m3s
2026-02-10 10:44:58 +07:00
harid
47f7f4d55e optimize sort #2260
All checks were successful
Build & Deploy on Dev / build (push) Successful in 52s
2026-02-09 17:50:53 +07:00
1cab2b3afc Merge branch 'develop' into feat/org-move-draf-current
* develop:
  add fields
2026-02-09 17:46:37 +07:00
f9d626a499 add test, and fix script 2026-02-09 17:45:50 +07:00
638362df1c feat: improve move-draft-to-current with differential sync
Implement differential sync for organization structure and positions
instead of delete-all-and-insert-all approach.

Changes:
- Add OrgIdMapping and AllOrgMappings interfaces for tracking ID mappings
- Implement syncOrgLevel() helper for differential sync per org level
- Add syncPositionsForPosMaster() helper for position table sync
- Process org levels bottom-up (Child4→Child3→Child2→Child1→Root)
- Use ancestorDNA matching with Like operator for descendant sync
- Cascade delete positions before deleting org nodes
- Batch DELETE/UPDATE/INSERT operations for better performance
- Track draft→current ID mappings for position updates
- Add comprehensive documentation in docs/move-draft-to-current.md

Benefits:
- Preserve IDs for unchanged nodes (better tracking)
- More efficient (fewer database operations)
- Better data integrity with proper FK handling
- Sync all descendants under given rootDnaId

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-09 12:35:59 +07:00
harid
f2ab1ec91e add fields
All checks were successful
Build & Deploy on Dev / build (push) Successful in 54s
2026-02-09 11:13:15 +07:00
22fc43fe17 Merge branch 'develop' into feat/org-move-draf-current
* develop:
  fix: ช่วยรายการและรักษาการ endDate null
  fix salaryId set null
  fix script move positionSalaryTemp to positionSalary
2026-02-09 09:55:30 +07:00
922a0ab1c2 fix: ช่วยรายการและรักษาการ endDate null
All checks were successful
Build & Deploy on Dev / build (push) Successful in 51s
2026-02-06 22:11:07 +07:00
e750b39639 fix salaryId set null
All checks were successful
Build & Deploy on Dev / build (push) Successful in 1m2s
2026-02-06 21:58:45 +07:00
2627c58244 fix script move positionSalaryTemp to positionSalary
All checks were successful
Build & Deploy on Dev / build (push) Successful in 52s
2026-02-06 21:47:56 +07:00
3177ffc42f merge 2026-02-06 21:46:41 +07:00
9aa0a97a53 fix script
All checks were successful
Build & Deploy on Dev / build (push) Successful in 51s
2026-02-06 17:02:08 +07:00
528f8f75c1 add query 2026-02-06 16:50:31 +07:00
256296672d privilege
All checks were successful
Build & Deploy on Dev / build (push) Successful in 48s
2026-02-06 15:25:54 +07:00
77b545d392 fix child privilege
All checks were successful
Build & Deploy on Dev / build (push) Successful in 48s
2026-02-06 15:12:52 +07:00
8a649086f7 fix: #2239
All checks were successful
Build & Deploy on Dev / build (push) Successful in 48s
2026-02-06 14:55:59 +07:00
harid
1696890f74 เพิ่มข้อมูล "อายุราชการ (กทม.)" #2285
All checks were successful
Build & Deploy on Dev / build (push) Successful in 49s
2026-02-06 14:47:47 +07:00
harid
5b726e69c8 Migrate add table profileSalaryBackup & เพิ่ม insert รักษาการ และช่วยราชการ #2292 2026-02-06 14:47:13 +07:00
203ec6cb84 fix: run temp to profileSalary
All checks were successful
Build & Deploy on Dev / build (push) Successful in 53s
2026-02-05 17:18:50 +07:00
af466df0d0 fix mode profileSalaryTemp to profileSalary
All checks were successful
Build & Deploy on Dev / build (push) Successful in 53s
2026-02-05 16:46:32 +07:00
631d634074 #2239
All checks were successful
Build & Deploy on Dev / build (push) Successful in 49s
2026-02-05 13:37:28 +07:00
d0241016fb disciption
All checks were successful
Build & Deploy on Dev / build (push) Successful in 1m3s
2026-02-05 11:45:10 +07:00
561dc7f66c change method 2026-02-05 11:43:59 +07:00
39a07482cd add api find dna by keycloak
All checks were successful
Build & Deploy on Dev / build (push) Successful in 1m0s
2026-02-05 11:23:38 +07:00
harid
dbc46e2fb9 เพิ่ม return สถานะการทดลองงาน
All checks were successful
Build & Deploy on Dev / build (push) Successful in 56s
2026-02-05 10:14:49 +07:00
a487b73c3b setup auth middleware and sync code 2026-02-04 16:52:39 +07:00
harid
55085ab8d8 กรอง isDeleted
All checks were successful
Build & Deploy on Dev / build (push) Successful in 50s
2026-02-04 16:32:25 +07:00
e76e361981 init claude overview project 2026-02-04 16:18:45 +07:00
DESKTOP-1R2VSQH\Lenovo ThinkPad E490
22639e72c6 #migrate : add email phone To table issues
All checks were successful
Build & Deploy on Dev / build (push) Successful in 52s
2026-02-04 10:43:11 +07:00
0b09a99e42 add: sync-users-to-keycloak 2026-02-03 18:03:19 +07:00
harid
30bf5ad9e3 migrate add column isDeleted + API ลบข้อมูลฝึกอบรม/ดูงาน + การพัฒนารายบุคคล idp + รักษาการ Task #2276, #2279, #2278
All checks were successful
Build & Deploy on Dev / build (push) Successful in 50s
2026-02-03 17:44:30 +07:00
harid
bb18fed9ae migrate add commandOperator.orderNo & api ส่วนเจ้าหน้าที่ดำเนินการที่คำสั่ง #2220
All checks were successful
Build & Deploy on Dev / build (push) Successful in 49s
2026-02-03 12:22:55 +07:00
4ec334f0d4 fix: cronjob publish fail
All checks were successful
Build & Deploy on Dev / build (push) Successful in 48s
2026-02-03 10:27:48 +07:00
harid
ec04da5611 migrate create table commandOperator #2220 2026-02-02 18:33:30 +07:00
e5e407e122 fix: PUT /org/workflow/commander/operate isAct = true not response posExecutiveNameOrg
All checks were successful
Build & Deploy on Dev / build (push) Successful in 51s
2026-02-02 17:50:48 +07:00
0a3f0d9170 fix: return status of act position
All checks were successful
Build & Deploy on Dev / build (push) Successful in 50s
2026-02-02 16:30:24 +07:00
d7164b603a Merge branch 'develop' of github.com:Frappet/bma-ehr-organization into develop
All checks were successful
Build & Deploy on Dev / build (push) Successful in 49s
* 'develop' of github.com:Frappet/bma-ehr-organization:
  #2166
2026-02-02 16:18:44 +07:00
9507040f75 created: script active act position 2026-02-02 16:18:32 +07:00
8b46a2f0f2 #2166
All checks were successful
Build & Deploy on Dev / build (push) Successful in 51s
2026-02-02 09:25:13 +07:00
e92321d360 fix: type avatar & avatarName, clear value null
All checks were successful
Build & Deploy on Dev / build (push) Successful in 1m39s
2026-01-31 12:37:25 +07:00
DESKTOP-1R2VSQH\Lenovo ThinkPad E490
74752361be fix: avatar : string
All checks were successful
Build & Deploy on Dev / build (push) Successful in 54s
2026-01-30 18:22:07 +07:00
DESKTOP-1R2VSQH\Lenovo ThinkPad E490
89a34f38ef fix avatar? 2026-01-30 18:20:13 +07:00
DESKTOP-1R2VSQH\Lenovo ThinkPad E490
33122a4b7e Merge branch 'dev' of https://192.168.1.60/hrms-bangkok/hrms-api-org into dev
All checks were successful
Build & Deploy on Dev / build (push) Successful in 54s
2026-01-30 17:39:57 +07:00
DESKTOP-1R2VSQH\Lenovo ThinkPad E490
217f9f64f9 Merge branch 'develop' into dev
# Conflicts:
#	src/controllers/ProfileAvatarEmployeeController.ts
2026-01-30 17:39:31 +07:00
b463a1f7af Merge branch 'develop' into dev
All checks were successful
Build & Deploy on Dev / build (push) Successful in 54s
* develop:
  fix
2026-01-30 17:30:53 +07:00
510aaee0ee fix 2026-01-30 17:30:34 +07:00
3b40c4c659 Merge branch 'dev' of https://192.168.1.60/hrms-bangkok/hrms-api-org into dev
All checks were successful
Build & Deploy on Dev / build (push) Successful in 53s
* 'dev' of https://192.168.1.60/hrms-bangkok/hrms-api-org:
  fix: avatar : string

# Conflicts:
#	src/controllers/ProfileAvatarEmployeeController.ts
2026-01-30 17:23:47 +07:00
84fb85ef3a fix: test entity avatar 2026-01-30 17:22:25 +07:00
DESKTOP-1R2VSQH\Lenovo ThinkPad E490
cd68478945 fix:avatar:string 2026-01-30 17:06:23 +07:00
DESKTOP-1R2VSQH\Lenovo ThinkPad E490
79372e803c fix: avatar : string
All checks were successful
Build & Deploy on Dev / build (push) Successful in 56s
2026-01-30 17:05:16 +07:00
DESKTOP-1R2VSQH\Lenovo ThinkPad E490
109caf7a0d fix: Type ProfileAvatar string | null
All checks were successful
Build & Deploy on Dev / build (push) Successful in 1m20s
2026-01-30 16:15:26 +07:00
DESKTOP-1R2VSQH\Lenovo ThinkPad E490
633ccd4906 fix:delete profileAvatar
All checks were successful
Build & Deploy on Dev / build (push) Successful in 53s
2026-01-30 14:47:46 +07:00
harid
e461f43604 Fix Bug #2243
All checks were successful
Build & Deploy on Dev / build (push) Successful in 54s
2026-01-30 12:01:38 +07:00
harid
12d2eb1ee9 ข้อมูลทะเบียนประวัติไม่อัปเดตหลัง "ยืนยันข้อมูลถูกต้อง" #2243
All checks were successful
Build & Deploy on Dev / build (push) Successful in 55s
2026-01-30 10:20:54 +07:00
d36c4c931c #2259
All checks were successful
Build & Deploy on Dev / build (push) Successful in 50s
2026-01-29 14:10:11 +07:00
harid
69acf3bb0b add api
All checks were successful
Build & Deploy on Dev / build (push) Successful in 52s
2026-01-29 13:18:21 +07:00
harid
328b5b8001 Fix เมนูสิทธิ์แก้ไขข้อมูลทะเบียนประวัติตำแหน่ง/เงินเดือน Error
All checks were successful
Build & Deploy on Dev / build (push) Successful in 59s
2026-01-29 09:34:12 +07:00
34f4a01d31 Merge branch 'fix/optimization-detailSuperAdmin' into develop
All checks were successful
Build & Deploy on Dev / build (push) Successful in 1m5s
* fix/optimization-detailSuperAdmin:
  Changed LogMemoryStore from active refresh (setInterval) to passive refresh on-access (60 min TTL)
  fix: extend OrgStructureCache TTL and add graceful shutdown cleanup
  add: docs and backup file
2026-01-29 00:31:26 +07:00
656c2e7341 Changed LogMemoryStore from active refresh (setInterval) to passive refresh on-access (60 min TTL) 2026-01-29 00:30:34 +07:00
7955c855bc fix: extend OrgStructureCache TTL and add graceful shutdown cleanup
- Extended OrgStructureCache TTL from 10 to 30 minutes (reduce cleanup frequency)
- Added orgStructureCache.destroy() in graceful shutdown handler
- Updated documentation to reflect changes

Co-Authored-By: Claude (glm-4.7) <noreply@anthropic.com>
2026-01-29 00:05:56 +07:00
e4cfac2eb2 add: docs and backup file 2026-01-28 23:10:50 +07:00
07535c9c53 Merge branch 'fix/optimization-detailSuperAdmin' into develop
All checks were successful
Build & Deploy on Dev / build (push) Successful in 1m2s
* fix/optimization-detailSuperAdmin:
  fix: api /super-admin/{id} memory cache
  fix: query use Promise all
  fix: Api GET /super-admin/{id}
  fix: เพิ่ม Graceful Shutdown - ป้องกัน connection in app file, Log Mnddleware + Memory Store
  fix: connection pool settings
  feat: optimize detailSuperAdmin API to fix database connection issue
2026-01-28 18:26:41 +07:00
1a324af483 fix: api /super-admin/{id} memory cache 2026-01-28 18:26:03 +07:00
7c70229579 fix: query use Promise all 2026-01-28 17:48:28 +07:00
5dcb59632f fix: Api GET /super-admin/{id} 2026-01-28 17:43:26 +07:00
e068aafe3a fix: เพิ่ม Graceful Shutdown - ป้องกัน connection in app file, Log Mnddleware + Memory Store 2026-01-28 17:22:10 +07:00
a194d8594b fix: connection pool settings 2026-01-28 16:59:09 +07:00
14c26cce72 add api get profileId
All checks were successful
Build & Deploy on Dev / build (push) Successful in 1m0s
2026-01-28 14:17:31 +07:00
bca25a7a52 feat: optimize detailSuperAdmin API to fix database connection issue
ปัญหา: API GET /api/v1/org/super-admin/{id} ทำให้ระบบดับเพราะ N+1 queries
- เดิม: >1,000,000 queries (100 orgRoots × 10 children × 10 counts/level)
- ใหม่: ~10 queries (query รวมครั้งเดียว + 5 org queries)

การเปลี่ยนแปลง:
1. สร้าง OrganizationController-optimized.ts
   - getPositionCounts(): query posMaster ทั้งหมดครั้งเดียว
   - สร้าง maps (orgRootMap, orgChild1Map, etc.) สำหรับ lookup
   - ลด queries จาก 1,000,000+ → ~10 queries

2. เพิ่ม import สำหรับ helper functions ใน OrganizationController.ts
   - import { getPositionCounts, getCounts, getRootCounts }
   - ต้อง replace ฟังก์ชัน detailSuperAdmin ด้วย optimized version
   - ดู OPTIMIZED_FUNCTION.ts สำหรับฟังก์ชันใหม่

ไฟล์ที่เพิ่ม:
- src/controllers/OrganizationController-optimized.ts (helper functions)
- OPTIMIZED_FUNCTION.ts (optimized function reference)
- src/utils/log-memory-store.ts (from earlier log middleware fix)

หมายเหตุ: ฟังก์ชัน detailSuperAdmin ใน OrganizationController.ts
ยังไม่ถูก replace (ต้องทำ manual) - ดู OPTIMIZED_FUNCTION.ts

Co-Authored-By: Claude (glm-4.7) <noreply@anthropic.com>
2026-01-28 13:45:52 +07:00
b64a8bb26d API Get Profile For Logs
All checks were successful
Build & Deploy on Dev / build (push) Successful in 1m0s
@Get("user-logs/{keycloakId}")
2026-01-28 12:04:49 +07:00
192 changed files with 21981 additions and 4422 deletions

View file

@ -1,11 +1,11 @@
name: Build
on:
push:
tags:
- "v[0-9]+.[0-9]+.[0-9]+"
- "v[0-9]+.[0-9]+.[0-9]+*"
workflow_dispatch:
# on:
# push:
# tags:
# - "v[0-9]+.[0-9]+.[0-9]+"
# - "v[0-9]+.[0-9]+.[0-9]+*"
# workflow_dispatch:
env:
REGISTRY: ${{ vars.CONTAINER_REGISTRY }}

View file

@ -3,8 +3,8 @@ name: Build & Deploy on Dev
on:
push:
branches:
- dev
tags:
- "v[0-9]+.[0-9]+.[0-9]+"
workflow_dispatch:
env:
@ -29,7 +29,11 @@ jobs:
ca=["/etc/ssl/certs/ca-certificates.crt"]
- name: Tag Version
run: |
echo "IMAGE_VERSION=latest"
if [ "${{ github.ref_type }}" == "tag" ]; then
echo "IMAGE_VERSION=${{ github.ref_name }}" >> $GITHUB_ENV
else
echo "IMAGE_VERSION=latest" >> $GITHUB_ENV
fi
- name: Login in to registry
uses: docker/login-action@v2
with:

2
.gitignore vendored
View file

@ -131,3 +131,5 @@ dist
.yarn/build-state.yml
.yarn/install-state.gz
.pnp.*
.claude

View file

@ -14,6 +14,12 @@ All notable changes to this project will be documented in this file.
- แก้ชนิด type ที่ reques
### ⚡ Performance
- Extended OrgStructureCache TTL from 10 to 30 minutes (reduce cleanup frequency)
- Added OrgStructureCache.destroy() in graceful shutdown handler
- Changed LogMemoryStore from active refresh (setInterval) to passive refresh on-access (60 min TTL)
### ⚙️ Miscellaneous Tasks
- Git-cliff changelog

View file

@ -0,0 +1,287 @@
# สรุปการปรับปรุง
## Branch: `fix/optimization-detailSuperAdmin`
---
## 📋 ภาพรวม
การแก้ไขครั้งนี้มุ่งเน้นปรับปรุงประสิทธิภาพและความมั่นคงของ API `GET /super-admin/{id}` ซึ่งมีปัญหาเรื่อง:
- Query ฐานข้อมูลซ้ำซ้อนหลายครั้ง
- การใช้งาน database connection ไม่มีประสิทธิภาพ
- ขาดระบบ caching ที่เหมาะสม
- ขาดระบบ Graceful Shutdown
---
## 🔧 รายละเอียดการแก้ไขแต่ละส่วน
### 1. Connection Pool Settings (`data-source.ts`)
**ไฟล์:** `src/database/data-source.ts`
**การแก้ไข:**
```typescript
// เพิ่ม connection pool settings
extra: {
connectionLimit: +(process.env.DB_CONNECTION_LIMIT || 50),
maxIdle: +(process.env.DB_MAX_IDLE || 10),
idleTimeout: +(process.env.DB_IDLE_TIMEOUT || 60000),
timezone: "+07:00",
},
poolSize: +(process.env.DB_POOL_SIZE || 10),
maxQueryExecutionTime: +(process.env.DB_MAX_QUERY_TIME || 3000),
```
**คำอธิบายเชิงเทคนิค:**
- `connectionLimit: 50` - จำกัดจำนวน connection สูงสุดที่เปิดพร้อมกัน
- `maxIdle: 10` - จำนวน idle connection ที่เก็บไว้ reuse
- `idleTimeout: 60000` - เวลา (ms) ที่ idle connection จะถูกปิดอัตโนมัติ
- `poolSize: 10` - ขนาด connection pool ของ TypeORM
- `maxQueryExecutionTime: 3000` - แจ้งเตือนเมื่อ query ช้ากว่า 3 วินาที
**ประโยชน์:** ป้องกัน connection exhaustion และปรับปรุงการใช้งานทรัพยากรฐานข้อมูล
---
### 2. Graceful Shutdown (`app.ts`)
**ไฟล์:** `src/app.ts` (บรรทัด 123-162)
**การแก้ไข:**
```typescript
const gracefulShutdown = async (signal: string) => {
console.log(`\n[APP] ${signal} received. Starting graceful shutdown...`);
// 1. หยุดรับ connection ใหม่
server.close(() => {
console.log("[APP] HTTP server closed");
});
// 2. ปิด database connections
if (AppDataSource.isInitialized) {
await AppDataSource.destroy();
console.log("[APP] Database connections closed");
}
// 3. ทำลาย cache instances
logMemoryStore.destroy();
console.log("[APP] LogMemoryStore destroyed");
// Destroy OrgStructureCache
orgStructureCache.destroy();
console.log("[APP] OrgStructureCache destroyed");
// 4. บังคับปิดหลังจาก 30 วินาที (หาก shutdown ค้าง)
const shutdownTimeout = setTimeout(() => {
process.exit(1);
}, 30000);
};
// ดักจับ signals
process.on("SIGTERM", () => gracefulShutdown("SIGTERM"));
process.on("SIGINT", () => gracefulShutdown("SIGINT"));
```
**คำอธิบายเชิงเทคนิค:**
- `SIGTERM` - signal ที่ระบบส่งมาเมื่อต้องการ stop service
- `SIGINT` - signal จากการกด Ctrl+C
- ปิดทีละขั้นตอน: HTTP Server → Database → Cache
- Timeout 30 วินาทีป้องกันการ hang ถ้า shutdown ไม่สำเร็จ
**ประโยชน์:** ป้องกัน connection หลุดและ data loss เมื่อระบบ restart
---
### 3. Log Middleware & Memory Store
#### 3.1 Log Memory Store (`src/utils/LogMemoryStore.ts`)
**คุณสมบัติ:**
```typescript
class LogMemoryStore {
private cache: {
currentRevision: OrgRevision | null,
profileCache: Map<string, Profile>, // keycloak → Profile
rootIdCache: Map<string, string>, // profileId → rootId
};
private readonly CACHE_TTL = 60 * 60 * 1000; // 60 นาที
}
```
**การทำงาน:**
- Passive cache refresh - ตรวจสอบและ refresh cache เมื่อมีการเข้าถึงข้อมูล (on-access)
- หาก cache เก่าเกิน 60 นาที จะทำการ refresh อัตโนมัติ
- Lazy load `profileCache` และ `rootIdCache` (โหลดเมื่อถูกเรียกใช้)
- Method `getProfileByKeycloak()` - ดึง profile จาก cache หรือ database
- Method `getRootIdByProfileId()` - ดึง rootId จาก cache หรือ database
- ไม่มี setInterval (ลดการใช้งาน timer)
#### 3.2 Log Middleware (`src/middlewares/logs.ts`)
**การเปลี่ยนแปลงหลัก:**
```typescript
// ก่อน: Query ทุกครั้งที่มี request
const profile = await AppDataSource.getRepository(Profile)
.findOne({ where: { keycloak } });
// หลัง: ใช้ cache
const profile = await logMemoryStore.getProfileByKeycloak(keycloak);
```
**ประโยชน์:** ลดจำนวน query สำหรับ log middleware อย่างมาก
---
### 4. OrgStructureCache (`src/utils/OrgStructureCache.ts`)
**ไฟล์ใหม่:** `src/utils/OrgStructureCache.ts`
**คุณสมบัติ:**
```typescript
class OrgStructureCache {
private cache: Map<string, CacheEntry>;
private readonly CACHE_TTL = 30 * 60 * 1000; // 30 นาที
// Key format: org-structure-{revisionId}-{rootId}
private generateKey(revisionId: string, rootId?: string): string
async get(revisionId: string, rootId?: string): Promise<any>
async set(revisionId: string, rootId: string, data: any): Promise<void>
invalidate(revisionId: string): void
}
```
**การทำงาน:**
- Cache ผลลัพธ์ของ org structure ตาม `revisionId` และ `rootId`
- TTL 30 นาที - ข้อมูลเก่าจะถูกลบอัตโนมัติ (ปรับจาก 10 นาที เพื่อลด cleanup frequency)
- Method `invalidate()` - ลบ cache เมื่อมีการอัปเดต revision
- Auto cleanup ทุก 30 นาที
**การใช้งานใน API:**
```typescript
// OrganizationController.ts - detailSuperAdmin()
const cached = await orgStructureCache.get(revisionId, rootId);
if (cached) return cached;
// ... query และคำนวณข้อมูล ...
await orgStructureCache.set(revisionId, rootId, result);
```
---
### 5. API Optimization - Promise.all
**ไฟล์:** `src/controllers/OrganizationController.ts`
**ก่อนแก้ไข:**
```typescript
// Query ทีละตัว - sequential
const rootOrg = await this.orgRootRepository.findOne(...);
const position = await this.posMasterRepository.findOne(...);
const ancestors = await this.orgRootRepository.find(...);
// ... อีกหลาย query ...
```
**หลังแก้ไข:**
```typescript
// Query พร้อมกัน - parallel
const [rootOrg, position, ancestors, ...] = await Promise.all([
this.orgRootRepository.findOne(...),
this.posMasterRepository.findOne(...),
this.orgRootRepository.find(...),
// ... อีกหลาย query ...
]);
```
**คำอธิบายเชิงเทคนิค:**
- `Promise.all()` ทำให้ query ที่ไม่ depended กันรัน parallel
- ลดเวลา total จาก `t1 + t2 + t3 + ...` เหลือ `max(t1, t2, t3, ...)`
- ตัวอย่าง: ถ้ามี 10 query ใช้เวลา 100ms แต่ละตัว
- Sequential: 10 × 100ms = 1,000ms
- Parallel: ~100ms (เร็วขึ้น 10 เท่า)
**ประโยชน์:** ลด response time อย่างมาก
---
### 6. OrganizationService Refactoring (ตอนนี้ไม่ได้ใช้เพราะตัด total position counts ออก)
**ไฟล์:** `src/services/OrganizationService.ts`
**ฟังก์ชัน `getPositionCounts()`:**
- Query ข้อมูล position ทั้งหมดใน revision ครั้งเดียว
- สร้าง Map สำหรับ aggregate counts แต่ละระดับ (orgRoot, orgChild1-4)
- Return ผลลัพธ์เป็น Map structure ที่พร้อมใช้งาน
**ประโยชน์:** ลดจำนวน query จากหลายร้อยครั้งเหลือ 1 ครั้ง
---
## 📊 สรุปการเปลี่ยนแปลง
| ไฟล์ | การเปลี่ยนแปลง | ผลกระทบ |
|------|------------------|---------|
| `src/database/data-source.ts` | +12 บรรทัด | Connection Pool Settings |
| `src/app.ts` | +40 บรรทัด | Graceful Shutdown |
| `src/middlewares/logs.ts` | +2 บรรทัด | Use Memory Cache |
| `src/utils/LogMemoryStore.ts` | New File | Profile/RootId Cache |
| `src/utils/OrgStructureCache.ts` | New File | Org Structure Cache |
| `src/controllers/OrganizationController.ts` | -1006 บรรทัด | Refactor + Promise.all |
| `src/services/OrganizationService.ts` | New File (Not Used) | getPositionCounts Helper |
---
## 🎯 ผลลัพธ์
### ประสิทธิภาพ
- ⚡ Response time ลดลงอย่างมีนัยสำคัญจากการใช้ `Promise.all`
- 💾 จำนวน database query ลดลง 80-90%
- 🔄 Cache hit rate เพิ่มขึ้นสำหรับ request ซ้ำ
### ความมั่นคง
- 🛡️ ป้องกัน connection exhaustion ด้วย connection pool
- 🔌 Graceful shutdown ป้องกัน data loss
- 📝 Log tracking ดีขึ้นด้วย memory store
### Code Quality
- 🧹 Code ลดลง >1,000 บรรทัดจากการ refactoring
- 📦 ฟังก์ชันแยกเป็น module ที่ชัดเจน
- 🔧 ง่ายต่อการ maintain และ test
---
## 🚀 วิธีการ Deploy
1. **ตรวจสอบ Environment Variables:**
```bash
DB_CONNECTION_LIMIT=50
DB_MAX_IDLE=10
DB_IDLE_TIMEOUT=60000
DB_POOL_SIZE=10
DB_MAX_QUERY_TIME=3000
```
2. **ตรวจสอบ Logs:**
```
[LogMemoryStore] Initialized with 600 second refresh interval
[OrgStructureCache] Initialized
[APP] Application is running on: http://localhost:3000
```
---
## 📝 Commits ที่เกี่ยวข้อง
```
1a324af4 fix: api /super-admin/{id} memory cache
7c702295 fix: query use Promise all
5dcb5963 fix: Api GET /super-admin/{id}
e068aafe fix: เพิ่ม Graceful Shutdown - ป้องกัน connection in app file, Log Mnddleware + Memory Store
a194d859 fix: connection pool settings
```
---
**วันที่สร้างเอกสาร:** 28 มกราคม 2026
**Branch:** fix/optimization-detailSuperAdmin
**ผู้ดำเนินการ:** Warunee.T

View file

@ -0,0 +1,356 @@
# Move Draft to Current - Differential Sync Implementation
## Overview
This document describes the implementation of the improved `move-draft-to-current` function in `OrganizationController.ts`. The function synchronizes organization structure and position data from the **Draft Revision** to the **Current Revision** using a differential sync approach (instead of the previous "delete all and insert all" method).
**API Endpoint:** `POST /api/v1/org/move-draft-to-current/{rootDnaId}`
---
## Architecture
### Data Models
The organization structure consists of 5 hierarchical levels:
```
OrgRoot (Level 0)
└── OrgChild1 (Level 1)
└── OrgChild2 (Level 2)
└── OrgChild3 (Level 3)
└── OrgChild4 (Level 4)
```
Each level has:
- Organization nodes with `ancestorDNA` for hierarchical tracking
- Foreign key relationships to parent levels
- Associated position records (`PosMaster`)
### Type Definitions
Located in `src/interfaces/OrgMapping.ts`:
```typescript
interface OrgIdMapping {
byAncestorDNA: Map<string, string>; // ancestorDNA → current ID
byDraftId: Map<string, string>; // draft ID → current ID
}
interface AllOrgMappings {
orgRoot: OrgIdMapping;
orgChild1: OrgIdMapping;
orgChild2: OrgIdMapping;
orgChild3: OrgIdMapping;
orgChild4: OrgIdMapping;
}
```
---
## Implementation Workflow
### Phase 0: Preparation
1. **Get Revision IDs**
- Fetch Draft Revision (`orgRevisionIsDraft: true`)
- Fetch Current Revision (`orgRevisionIsCurrent: true`)
2. **Validate rootDnaId**
- Check if rootDnaId exists in Draft Revision
- Return error if not found
### Phase 1: Sync Organization Structure (Bottom-Up)
**Processing Order:** `OrgChild4 → OrgChild3 → OrgChild2 → OrgChild1 → OrgRoot`
**Why Bottom-Up?** Child nodes have no dependent children (only parent references), allowing safe deletion without FK violations.
#### For Each Organization Level
The `syncOrgLevel()` helper performs:
1. **FETCH** - Get all draft and current nodes under `rootDnaId`
```typescript
where: { ancestorDNA: Like(`${rootDnaId}%`) } // All descendants
```
2. **DELETE** - Remove current nodes not in draft
- Cascade delete positions first (via `cascadeDeletePositions()`)
- Delete the organization node
3. **UPDATE** - Update nodes that exist in both (matched by `ancestorDNA`)
- Map parent IDs using `parentMappings`
- Preserve original node ID
4. **INSERT** - Add draft nodes not in current
- Create new node with mapped parent IDs
- Return new ID for tracking
5. **RETURN** - Return `OrgIdMapping` for next level
**Result:** `allMappings` contains draft ID → current ID mappings for all org levels
### Phase 2: Sync Position Data
#### Step 2.1: Clear current_holderId
```typescript
// Clear holders for positions that will have new holders
await queryRunner.manager.update(PosMaster,
{ current_holderId: In(nextHolderIds) },
{ current_holderId: null, isSit: false }
)
```
#### Step 2.2: Fetch Draft and Current Positions
- Get draft positions using `draftOrgIds` from `allMappings`
- Get current positions using `currentOrgIds` from `allMappings`
#### Step 2.3: Batch DELETE
```typescript
// Delete current positions not in draft (cascade delete positions first)
await queryRunner.manager.delete(Position, { posMasterId: In(toDeleteIds) })
await queryRunner.manager.delete(PosMaster, toDeleteIds)
```
#### Step 2.4: Process UPDATE or INSERT
For each draft position:
1. Map organization IDs using `resolveOrgId()`
2. If exists in current → **UPDATE**
3. If not exists → **INSERT**
4. Track `draftPosMasterId → currentPosMasterId` mapping
#### Step 2.5: Sync Position Table
For each mapped PosMaster:
```typescript
await syncPositionsForPosMaster(
queryRunner,
draftPosMasterId,
currentPosMasterId,
draftRevisionId,
currentRevisionId
)
```
---
## Helper Functions
### `resolveOrgId(draftId, mapping)`
Maps a draft organization ID to its current ID.
```typescript
private resolveOrgId(
draftId: string | null,
mapping: OrgIdMapping
): string | null {
if (!draftId) return null;
return mapping.byDraftId.get(draftId) ?? null;
}
```
### `cascadeDeletePositions(queryRunner, node, entityClass)`
Deletes positions associated with an organization node before deleting the node itself.
```typescript
private async cascadeDeletePositions(
queryRunner: any,
node: any,
entityClass: any
): Promise<void> {
const whereClause = { orgRevisionId: node.orgRevisionId };
// Set FK field based on entity type
if (entityClass === OrgRoot) whereClause.orgRootId = node.id;
else if (entityClass === OrgChild1) whereClause.orgChild1Id = node.id;
// ... etc
await queryRunner.manager.delete(PosMaster, whereClause);
}
```
### `syncOrgLevel(...)`
Generic differential sync for each organization level.
**Parameters:**
- `queryRunner` - Database query runner
- `entityClass` - Organization entity class (OrgRoot, OrgChild1, etc.)
- `repository` - Repository for the entity
- `draftRevisionId` - Draft revision ID
- `currentRevisionId` - Current revision ID
- `rootDnaId` - Root DNA ID to sync under
- `parentMappings` - Mappings from child levels (for FK resolution)
**Returns:** `OrgIdMapping` for this level
### `syncPositionsForPosMaster(...)`
Syncs positions for a PosMaster record.
**Parameters:**
- `queryRunner` - Database query runner
- `draftPosMasterId` - Draft PosMaster ID
- `currentPosMasterId` - Current PosMaster ID
- `draftRevisionId` - Draft revision ID
- `currentRevisionId` - Current revision ID
**Process:**
1. Fetch draft and current positions
2. Delete current positions not in draft (by `orderNo`)
3. Update existing positions
4. Insert new positions
---
## Transaction Management
All operations are wrapped in a transaction:
```typescript
const queryRunner = AppDataSource.createQueryRunner();
await queryRunner.connect();
await queryRunner.startTransaction();
try {
// ... all sync operations
await queryRunner.commitTransaction();
} catch (error) {
await queryRunner.rollbackTransaction();
throw new HttpError(HttpStatusCode.INTERNAL_SERVER_ERROR, "...");
}
```
---
## Benefits of Differential Sync
| Aspect | Old Approach (Delete All + Insert) | New Approach (Differential Sync) |
|--------|-----------------------------------|----------------------------------|
| **ID Preservation** | All IDs changed | Unchanged nodes keep original IDs |
| **Performance** | N deletes + N inserts | Only changed data processed |
| **Tracking** | Cannot track what changed | Can track additions/updates/deletes |
| **Data Integrity** | Higher risk of data loss | Better integrity with cascade deletes |
| **Scalability** | Poor for large datasets | Efficient with batch operations |
---
## Database Schema Relationships
```
orgRevision
├── orgRoot (FK: orgRevisionId)
│ ├── orgChild1 (FK: orgRootId, orgRevisionId)
│ │ ├── orgChild2 (FK: orgChild1Id, orgRootId, orgRevisionId)
│ │ │ ├── orgChild3 (FK: orgChild2Id, orgChild1Id, orgRootId, orgRevisionId)
│ │ │ │ └── orgChild4 (FK: orgChild3Id, orgChild2Id, orgChild1Id, orgRootId, orgRevisionId)
│ │ │ │
│ │ │ └── posMaster (FK: orgRootId, orgChild1Id, orgChild2Id, orgChild3Id, orgChild4Id)
│ │ │ └── position (FK: posMasterId)
│ │ │
│ │ └── posMaster
│ │
│ └── posMaster
└── (current revision similar structure)
```
---
## Error Handling
| Error Condition | Response |
|----------------|----------|
| Draft/Current revision not found | 404 NOT_FOUND |
| rootDnaId not found in draft | 404 NOT_FOUND |
| No positions in draft structure | 404 NOT_FOUND |
| Database error during sync | 500 INTERNAL_SERVER_ERROR (rollback) |
---
## Files Modified/Created
```
src/
├── interfaces/
│ └── OrgMapping.ts [NEW] Type definitions
├── controllers/
│ └── OrganizationController.ts [MODIFIED] moveDraftToCurrent function + helpers
docs/
└── move-draft-to-current.md [NEW] This documentation
```
---
## Testing Considerations
### Unit Tests
- Test `syncOrgLevel()` for each org level with various scenarios
- Test `resolveOrgId()` mapping function
- Test `cascadeDeletePositions()` function
- Test `syncPositionsForPosMaster()` function
### Integration Tests
1. **Empty Draft** - Sync with no draft data
2. **Full Replacement** - All nodes changed
3. **Partial Update** - Some nodes added, some updated, some deleted
4. **Position Sync** - Verify position table syncs correctly
5. **Foreign Key Constraints** - Verify all FK relationships maintained
### Manual Testing Flow
1. Create draft structure with various changes:
- Add new department
- Modify existing department
- Remove existing department
- Add/modify/remove positions
2. Call `move-draft-to-current` API
3. Verify:
- New departments appear in current
- Modified departments are updated (not recreated)
- Removed departments are gone (with positions cascade deleted)
- Positions have correct new org IDs
- Position table records sync correctly
- All foreign key constraints satisfied
---
## Performance Optimization
- **Batch Operations** - Use `In()` clause for multiple IDs
- **Map Lookups** - Use `Map` for O(1) lookups instead of array searches
- **Bottom-Up Processing** - Minimize FK constraint checks
- **Parallel Queries** - Use `Promise.all()` for independent queries
---
## Future Improvements
1. **Parallel Processing** - Process independent org branches in parallel
2. **Incremental Sync** - Only sync changed subtrees
3. **Caching** - Cache org mappings for repeated operations
4. **Audit Log** - Track all changes for audit purposes
5. **Validation** - Add pre-sync validation to catch errors early
---
## References
- TypeORM Documentation: https://typeorm.io/
- TSOA Documentation: https://tsoa-community.github.io/
- Project Repository: [Internal Git]
---
**Last Updated:** 2025-02-09
**Author:** Claude Code
**Version:** 1.0.0

27
jest.config.js Normal file
View file

@ -0,0 +1,27 @@
module.exports = {
preset: 'ts-jest',
testEnvironment: 'node',
roots: ['<rootDir>/src'],
testMatch: ['**/__tests__/**/*.spec.ts', '**/__tests__/**/*.test.ts'],
transform: {
'^.+\\.ts$': ['ts-jest', {
diagnostics: {
ignoreCodes: [151002],
},
}],
},
collectCoverageFrom: [
'src/**/*.ts',
'!src/**/*.d.ts',
'!src/app.ts',
'!src/database/**',
'!src/__tests__/**',
],
coverageDirectory: 'coverage',
moduleNameMapper: {
'^@/(.*)$': '<rootDir>/src/$1',
},
setupFilesAfterEnv: ['<rootDir>/src/__tests__/setup.ts'],
testTimeout: 10000,
moduleFileExtensions: ['ts', 'tsx', 'js', 'jsx', 'json', 'node'],
};

3473
package-lock.json generated

File diff suppressed because it is too large Load diff

View file

@ -10,7 +10,10 @@
"format": "prettier --write .",
"build": "tsoa spec-and-routes && tsc",
"migration:generate": "typeorm-ts-node-commonjs migration:generate -d src/database/data-source.ts",
"migration:run": "typeorm-ts-node-commonjs migration:run -d src/database/data-source.ts"
"migration:run": "typeorm-ts-node-commonjs migration:run -d src/database/data-source.ts",
"test": "jest",
"test:watch": "jest --watch",
"test:coverage": "jest --coverage"
},
"keywords": [],
"author": "",
@ -19,12 +22,15 @@
"@types/amqplib": "^0.10.5",
"@types/cors": "^2.8.17",
"@types/express": "^4.17.21",
"@types/jest": "^29.5.11",
"@types/node": "^20.11.5",
"@types/node-cron": "^3.0.11",
"@types/swagger-ui-express": "^4.1.6",
"@types/ws": "^8.5.14",
"jest": "^29.7.0",
"nodemon": "^3.0.3",
"prettier": "^3.2.2",
"ts-jest": "^29.1.1",
"ts-node": "^10.9.2",
"typescript": "^5.3.3"
},

17
src/__tests__/setup.ts Normal file
View file

@ -0,0 +1,17 @@
// Test setup file for Jest
// Mock environment variables
process.env.NODE_ENV = 'test';
process.env.DB_HOST = 'localhost';
process.env.DB_PORT = '3306';
process.env.DB_USERNAME = 'test';
process.env.DB_PASSWORD = 'test';
process.env.DB_DATABASE = 'test_db';
// Mock console methods to reduce noise in tests
global.console = {
...console,
error: jest.fn(),
warn: jest.fn(),
info: jest.fn(),
log: jest.fn(),
};

View file

@ -0,0 +1,54 @@
/**
* Unit tests for move-draft-to-current helper functions
*/
import { OrgIdMapping, AllOrgMappings } from '../../interfaces/OrgMapping';
// Mock dependencies
jest.mock('../../database/data-source', () => ({
AppDataSource: {
createQueryRunner: jest.fn(),
},
}));
describe('OrgMapping Interfaces', () => {
describe('OrgIdMapping', () => {
it('should create a valid OrgIdMapping', () => {
const mapping: OrgIdMapping = {
byAncestorDNA: new Map(),
byDraftId: new Map(),
};
expect(mapping.byAncestorDNA).toBeInstanceOf(Map);
expect(mapping.byDraftId).toBeInstanceOf(Map);
});
it('should store and retrieve values correctly', () => {
const mapping: OrgIdMapping = {
byAncestorDNA: new Map([['dna1', 'id1']]),
byDraftId: new Map([['draftId1', 'currentId1']]),
};
expect(mapping.byAncestorDNA.get('dna1')).toBe('id1');
expect(mapping.byDraftId.get('draftId1')).toBe('currentId1');
});
});
describe('AllOrgMappings', () => {
it('should create a valid AllOrgMappings', () => {
const mappings: AllOrgMappings = {
orgRoot: { byAncestorDNA: new Map(), byDraftId: new Map() },
orgChild1: { byAncestorDNA: new Map(), byDraftId: new Map() },
orgChild2: { byAncestorDNA: new Map(), byDraftId: new Map() },
orgChild3: { byAncestorDNA: new Map(), byDraftId: new Map() },
orgChild4: { byAncestorDNA: new Map(), byDraftId: new Map() },
};
expect(mappings.orgRoot).toBeDefined();
expect(mappings.orgChild1).toBeDefined();
expect(mappings.orgChild2).toBeDefined();
expect(mappings.orgChild3).toBeDefined();
expect(mappings.orgChild4).toBeDefined();
});
});
});

View file

@ -0,0 +1,460 @@
/**
* Unit tests for OrganizationController move-draft-to-current helper functions
*/
import { OrgIdMapping } from '../../interfaces/OrgMapping';
// Mock typeorm
jest.mock('typeorm', () => ({
Entity: jest.fn(),
Column: jest.fn(),
ManyToOne: jest.fn(),
JoinColumn: jest.fn(),
OneToMany: jest.fn(),
In: jest.fn((val: any) => val),
Like: jest.fn((val: any) => val),
IsNull: jest.fn(),
Not: jest.fn(),
}));
// Mock entities
jest.mock('../../entities/OrgRoot', () => ({ OrgRoot: {} }));
jest.mock('../../entities/OrgChild1', () => ({ OrgChild1: {} }));
jest.mock('../../entities/OrgChild2', () => ({ OrgChild2: {} }));
jest.mock('../../entities/OrgChild3', () => ({ OrgChild3: {} }));
jest.mock('../../entities/OrgChild4', () => ({ OrgChild4: {} }));
jest.mock('../../entities/PosMaster', () => ({ PosMaster: {} }));
jest.mock('../../entities/Position', () => ({ Position: {} }));
// Import after mocking
import { In, Like } from 'typeorm';
import { OrgRoot } from '../../entities/OrgRoot';
import { OrgChild1 } from '../../entities/OrgChild1';
import { OrgChild2 } from '../../entities/OrgChild2';
import { OrgChild3 } from '../../entities/OrgChild3';
import { OrgChild4 } from '../../entities/OrgChild4';
import { PosMaster } from '../../entities/PosMaster';
import { Position } from '../../entities/Position';
describe('OrganizationController - Helper Functions', () => {
let mockQueryRunner: any;
let mockController: any;
beforeEach(() => {
// Mock queryRunner
mockQueryRunner = {
manager: {
find: jest.fn(),
delete: jest.fn(),
update: jest.fn(),
create: jest.fn(),
save: jest.fn(),
},
};
// Import the controller class (we'll need to mock the private methods)
// Since we're testing private methods, we'll create a test class
});
afterEach(() => {
jest.clearAllMocks();
});
describe('resolveOrgId()', () => {
it('should return null when draftId is null', () => {
const mapping: OrgIdMapping = {
byAncestorDNA: new Map(),
byDraftId: new Map(),
};
// Simulate the function logic
const resolveOrgId = (draftId: string | null, mapping: OrgIdMapping): string | null => {
if (!draftId) return null;
return mapping.byDraftId.get(draftId) ?? null;
};
expect(resolveOrgId(null, mapping)).toBeNull();
});
it('should return null when draftId is undefined', () => {
const mapping: OrgIdMapping = {
byAncestorDNA: new Map(),
byDraftId: new Map(),
};
const resolveOrgId = (draftId: string | null | undefined, mapping: OrgIdMapping): string | null => {
if (!draftId) return null;
return mapping.byDraftId.get(draftId) ?? null;
};
expect(resolveOrgId(undefined, mapping)).toBeNull();
});
it('should return mapped ID when draftId exists in mapping', () => {
const mapping: OrgIdMapping = {
byAncestorDNA: new Map(),
byDraftId: new Map([['draft1', 'current1']]),
};
const resolveOrgId = (draftId: string | null, mapping: OrgIdMapping): string | null => {
if (!draftId) return null;
return mapping.byDraftId.get(draftId) ?? null;
};
expect(resolveOrgId('draft1', mapping)).toBe('current1');
});
it('should return null when draftId does not exist in mapping', () => {
const mapping: OrgIdMapping = {
byAncestorDNA: new Map(),
byDraftId: new Map(),
};
const resolveOrgId = (draftId: string | null, mapping: OrgIdMapping): string | null => {
if (!draftId) return null;
return mapping.byDraftId.get(draftId) ?? null;
};
expect(resolveOrgId('nonexistent', mapping)).toBeNull();
});
});
describe('cascadeDeletePositions()', () => {
it('should delete positions with orgRootId when entityClass is OrgRoot', async () => {
const node = {
id: 'node1',
orgRevisionId: 'rev1',
};
await mockQueryRunner.manager.delete(PosMaster, {
orgRevisionId: 'rev1',
orgRootId: 'node1',
});
expect(mockQueryRunner.manager.delete).toHaveBeenCalledWith(PosMaster, {
orgRevisionId: 'rev1',
orgRootId: 'node1',
});
});
it('should delete positions with orgChild1Id when entityClass is OrgChild1', async () => {
const node = {
id: 'node1',
orgRevisionId: 'rev1',
};
await mockQueryRunner.manager.delete(PosMaster, {
orgRevisionId: 'rev1',
orgChild1Id: 'node1',
});
expect(mockQueryRunner.manager.delete).toHaveBeenCalledWith(PosMaster, {
orgRevisionId: 'rev1',
orgChild1Id: 'node1',
});
});
it('should delete positions with orgChild2Id when entityClass is OrgChild2', async () => {
const node = {
id: 'node1',
orgRevisionId: 'rev1',
};
await mockQueryRunner.manager.delete(PosMaster, {
orgRevisionId: 'rev1',
orgChild2Id: 'node1',
});
expect(mockQueryRunner.manager.delete).toHaveBeenCalledWith(PosMaster, {
orgRevisionId: 'rev1',
orgChild2Id: 'node1',
});
});
it('should delete positions with orgChild3Id when entityClass is OrgChild3', async () => {
const node = {
id: 'node1',
orgRevisionId: 'rev1',
};
await mockQueryRunner.manager.delete(PosMaster, {
orgRevisionId: 'rev1',
orgChild3Id: 'node1',
});
expect(mockQueryRunner.manager.delete).toHaveBeenCalledWith(PosMaster, {
orgRevisionId: 'rev1',
orgChild3Id: 'node1',
});
});
it('should delete positions with orgChild4Id when entityClass is OrgChild4', async () => {
const node = {
id: 'node1',
orgRevisionId: 'rev1',
};
await mockQueryRunner.manager.delete(PosMaster, {
orgRevisionId: 'rev1',
orgChild4Id: 'node1',
});
expect(mockQueryRunner.manager.delete).toHaveBeenCalledWith(PosMaster, {
orgRevisionId: 'rev1',
orgChild4Id: 'node1',
});
});
});
describe('syncOrgLevel()', () => {
beforeEach(() => {
mockQueryRunner.manager.find.mockResolvedValue([]);
mockQueryRunner.manager.delete.mockResolvedValue({ affected: 0 });
mockQueryRunner.manager.update.mockResolvedValue({ affected: 0 });
mockQueryRunner.manager.create.mockReturnValue({});
mockQueryRunner.manager.save.mockResolvedValue({});
});
it('should fetch draft and current nodes with Like filter', async () => {
const repository = {
find: jest.fn().mockResolvedValue([]),
};
await repository.find({
where: {
orgRevisionId: 'draftRev1',
ancestorDNA: Like('root-dna%'),
},
});
await repository.find({
where: {
orgRevisionId: 'currentRev1',
ancestorDNA: Like('root-dna%'),
},
});
expect(repository.find).toHaveBeenCalledTimes(2);
});
it('should build lookup maps from draft and current nodes', () => {
const draftNodes = [
{ id: 'draft1', ancestorDNA: 'root-dna/child1' },
{ id: 'draft2', ancestorDNA: 'root-dna/child2' },
];
const currentNodes = [
{ id: 'current1', ancestorDNA: 'root-dna/child1' },
];
const draftByDNA = new Map(draftNodes.map(n => [n.ancestorDNA, n]));
const currentByDNA = new Map(currentNodes.map(n => [n.ancestorDNA, n]));
expect(draftByDNA.size).toBe(2);
expect(currentByDNA.size).toBe(1);
expect(draftByDNA.get('root-dna/child1')).toEqual(draftNodes[0]);
expect(currentByDNA.get('root-dna/child1')).toEqual(currentNodes[0]);
});
it('should identify nodes to delete (in current but not in draft)', () => {
const draftNodes = [
{ id: 'draft1', ancestorDNA: 'root-dna/child1' },
];
const currentNodes = [
{ id: 'current1', ancestorDNA: 'root-dna/child1' },
{ id: 'current2', ancestorDNA: 'root-dna/child2' }, // Not in draft
];
const draftByDNA = new Map(draftNodes.map((n: any) => [n.ancestorDNA, n]));
const toDelete = currentNodes.filter((curr: any) => !draftByDNA.has(curr.ancestorDNA));
expect(toDelete).toHaveLength(1);
expect(toDelete[0].id).toBe('current2');
});
it('should identify nodes to update (in both draft and current)', () => {
const draftNodes = [
{ id: 'draft1', ancestorDNA: 'root-dna/child1' },
{ id: 'draft2', ancestorDNA: 'root-dna/child2' },
];
const currentNodes = [
{ id: 'current1', ancestorDNA: 'root-dna/child1' },
];
const currentByDNA = new Map(currentNodes.map((n: any) => [n.ancestorDNA, n]));
const toUpdate = draftNodes.filter((draft: any) => currentByDNA.has(draft.ancestorDNA));
expect(toUpdate).toHaveLength(1);
expect(toUpdate[0].id).toBe('draft1');
});
it('should identify nodes to insert (in draft but not in current)', () => {
const draftNodes = [
{ id: 'draft1', ancestorDNA: 'root-dna/child1' },
{ id: 'draft2', ancestorDNA: 'root-dna/child2' },
];
const currentNodes = [
{ id: 'current1', ancestorDNA: 'root-dna/child1' },
];
const currentByDNA = new Map(currentNodes.map((n: any) => [n.ancestorDNA, n]));
const toInsert = draftNodes.filter((draft: any) => !currentByDNA.has(draft.ancestorDNA));
expect(toInsert).toHaveLength(1);
expect(toInsert[0].id).toBe('draft2');
});
it('should return correct mapping after sync', () => {
const mapping: OrgIdMapping = {
byAncestorDNA: new Map([
['root-dna/child1', 'current1'],
['root-dna/child2', 'current2'],
]),
byDraftId: new Map([
['draft1', 'current1'],
['draft2', 'current2'],
]),
};
expect(mapping.byAncestorDNA.get('root-dna/child1')).toBe('current1');
expect(mapping.byDraftId.get('draft1')).toBe('current1');
expect(mapping.byDraftId.get('draft2')).toBe('current2');
});
});
describe('syncPositionsForPosMaster()', () => {
beforeEach(() => {
mockQueryRunner.manager.find.mockResolvedValue([]);
mockQueryRunner.manager.delete.mockResolvedValue({ affected: 0 });
mockQueryRunner.manager.update.mockResolvedValue({ affected: 0 });
mockQueryRunner.manager.create.mockReturnValue({});
mockQueryRunner.manager.save.mockResolvedValue({});
});
it('should fetch draft and current positions for a posMaster', async () => {
const draftPosMasterId = 'draft-pos-1';
const currentPosMasterId = 'current-pos-1';
mockQueryRunner.manager.find
.mockResolvedValueOnce([{ id: 'pos1', posMasterId: draftPosMasterId }])
.mockResolvedValueOnce([{ id: 'pos2', posMasterId: currentPosMasterId }]);
await mockQueryRunner.manager.find(Position, {
where: { posMasterId: draftPosMasterId },
order: { orderNo: 'ASC' },
});
await mockQueryRunner.manager.find(Position, {
where: { posMasterId: currentPosMasterId },
});
expect(mockQueryRunner.manager.find).toHaveBeenCalledTimes(2);
});
it('should delete all current positions when no draft positions exist', async () => {
const currentPositions = [
{ id: 'pos1', orderNo: 1 },
{ id: 'pos2', orderNo: 2 },
];
mockQueryRunner.manager.find
.mockResolvedValueOnce([]) // No draft positions
.mockResolvedValueOnce(currentPositions);
await mockQueryRunner.manager.delete(Position, ['pos1', 'pos2']);
expect(mockQueryRunner.manager.delete).toHaveBeenCalledWith(Position, ['pos1', 'pos2']);
});
it('should delete positions not in draft (by orderNo)', async () => {
const draftPositions = [
{ id: 'dpos1', orderNo: 1 },
{ id: 'dpos2', orderNo: 2 },
];
const currentPositions = [
{ id: 'cpos1', orderNo: 1 },
{ id: 'cpos2', orderNo: 2 },
{ id: 'cpos3', orderNo: 3 }, // Not in draft
];
mockQueryRunner.manager.find
.mockResolvedValueOnce(draftPositions)
.mockResolvedValueOnce(currentPositions);
const draftOrderNos = new Set(draftPositions.map((p: any) => p.orderNo));
const toDelete = currentPositions.filter((p: any) => !draftOrderNos.has(p.orderNo));
expect(toDelete).toHaveLength(1);
expect(toDelete[0].id).toBe('cpos3');
});
it('should update existing positions (matched by orderNo)', async () => {
const draftPositions = [
{
id: 'dpos1',
orderNo: 1,
positionName: 'Updated Name',
positionField: 'field1',
posTypeId: 'type1',
posLevelId: 'level1',
},
];
const currentPositions = [
{ id: 'cpos1', orderNo: 1, positionName: 'Old Name' },
];
const currentByOrderNo = new Map(currentPositions.map((p: any) => [p.orderNo, p]));
const draftPos = draftPositions[0];
const current = currentByOrderNo.get(draftPos.orderNo);
expect(current).toBeDefined();
if (current) {
const updateData = {
positionName: draftPos.positionName,
positionField: draftPos.positionField,
posTypeId: draftPos.posTypeId,
posLevelId: draftPos.posLevelId,
};
await mockQueryRunner.manager.update(Position, current.id, updateData);
expect(mockQueryRunner.manager.update).toHaveBeenCalledWith(
Position,
'cpos1',
expect.objectContaining({ positionName: 'Updated Name' })
);
}
});
it('should insert new positions not in current', async () => {
const draftPositions = [
{ id: 'dpos1', orderNo: 1, positionName: 'New Position' },
];
const currentPositions: any[] = [];
const currentByOrderNo = new Map(currentPositions.map((p: any) => [p.orderNo, p]));
const draftPos = draftPositions[0];
const current = currentByOrderNo.get(draftPos.orderNo);
const currentPosMasterId = 'current-pos-1';
expect(current).toBeUndefined();
if (!current) {
const newPosition = {
...draftPos,
id: undefined,
posMasterId: currentPosMasterId,
};
await mockQueryRunner.manager.create(Position, newPosition);
await mockQueryRunner.manager.save(newPosition);
expect(mockQueryRunner.manager.create).toHaveBeenCalledWith(Position, {
...draftPos,
id: undefined,
posMasterId: currentPosMasterId,
});
}
});
});
});

View file

@ -11,8 +11,11 @@ import { AppDataSource } from "./database/data-source";
import { RegisterRoutes } from "./routes";
import { OrganizationController } from "./controllers/OrganizationController";
import logMiddleware from "./middlewares/logs";
import { logMemoryStore } from "./utils/LogMemoryStore";
import { orgStructureCache } from "./utils/OrgStructureCache";
import { CommandController } from "./controllers/CommandController";
import { ProfileSalaryController } from "./controllers/ProfileSalaryController";
import { ScriptProfileOrgController } from "./controllers/ScriptProfileOrgController";
import { DateSerializer } from "./interfaces/date-serializer";
import { initWebSocket } from "./services/webSocket";
@ -20,6 +23,12 @@ import { initWebSocket } from "./services/webSocket";
async function main() {
await AppDataSource.initialize();
// Initialize LogMemoryStore after database is ready
logMemoryStore.initialize();
// Initialize OrgStructureCache after database is ready
orgStructureCache.initialize();
// Setup custom Date serialization for local timezone
DateSerializer.setupDateSerialization();
@ -44,19 +53,8 @@ async function main() {
const APP_HOST = process.env.APP_HOST || "0.0.0.0";
const APP_PORT = +(process.env.APP_PORT || 3000);
const cronTime = "0 0 3 * * *"; // ตั้งเวลาทุกวันเวลา 08:00:00
// const cronTime = "*/10 * * * * *";
cron.schedule(cronTime, async () => {
try {
const orgController = new OrganizationController();
await orgController.cronjobRevision();
} catch (error) {
console.error("Error executing function from controller:", error);
}
});
const cronTime_command = "0 0 2 * * *";
// const cronTime_command = "*/10 * * * * *";
// Cron job for executing command - every day at 00:30:00
const cronTime_command = "0 30 0 * * *";
cron.schedule(cronTime_command, async () => {
try {
const commandController = new CommandController();
@ -66,7 +64,19 @@ async function main() {
}
});
const cronTime_Oct = "0 0 1 10 *";
// Cron job for updating org revision - every day at 01:00:00
const cronTime = "0 0 1 * * *";
cron.schedule(cronTime, async () => {
try {
const orgController = new OrganizationController();
await orgController.cronjobRevision();
} catch (error) {
console.error("Error executing function from controller:", error);
}
});
// Cron job for updating retirement status - every day at 02:00:00 on the 1st of October
const cronTime_Oct = "0 0 2 10 *";
cron.schedule(cronTime_Oct, async () => {
try {
const commandController = new CommandController();
@ -76,7 +86,19 @@ async function main() {
}
});
const cronTime_Tenure = "0 0 0 * * *";
// Cron job for updating org DNA - every day at 03:00:00
const cronTime_UpdateOrg = "0 0 3 * * *";
cron.schedule(cronTime_UpdateOrg, async () => {
try {
const scriptProfileOrgController = new ScriptProfileOrgController();
await scriptProfileOrgController.cronjobUpdateOrg({} as any);
} catch (error) {
console.error("Error executing cronjobUpdateOrg:", error);
}
});
// Cron job for updating tenure - every day at 04:00:00
const cronTime_Tenure = "0 0 4 * * *";
cron.schedule(cronTime_Tenure, async () => {
try {
const profileSalaryController = new ProfileSalaryController();
@ -93,7 +115,7 @@ async function main() {
});
// app.listen(APP_PORT, APP_HOST, () => console.log(`Listening on: http://localhost:${APP_PORT}`));
app.listen(
const server = app.listen(
APP_PORT,
APP_HOST,
() => (
@ -111,6 +133,50 @@ async function main() {
}
runMessageQueue();
// Graceful Shutdown
const gracefulShutdown = async (signal: string) => {
console.log(`\n[APP] ${signal} received. Starting graceful shutdown...`);
// Stop accepting new connections
server.close(() => {
console.log("[APP] HTTP server closed");
});
// Force close after timeout
const shutdownTimeout = setTimeout(() => {
console.error("[APP] Forced shutdown after timeout");
process.exit(1);
}, 30000); // 30 seconds timeout
try {
// Close database connections
if (AppDataSource.isInitialized) {
await AppDataSource.destroy();
console.log("[APP] Database connections closed");
}
// Destroy LogMemoryStore
logMemoryStore.destroy();
console.log("[APP] LogMemoryStore destroyed");
// Destroy OrgStructureCache
orgStructureCache.destroy();
console.log("[APP] OrgStructureCache destroyed");
clearTimeout(shutdownTimeout);
console.log("[APP] Graceful shutdown completed");
process.exit(0);
} catch (error) {
console.error("[APP] Error during shutdown:", error);
clearTimeout(shutdownTimeout);
process.exit(1);
}
};
// Listen for shutdown signals
process.on("SIGTERM", () => gracefulShutdown("SIGTERM"));
process.on("SIGINT", () => gracefulShutdown("SIGINT"));
}
main();

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,224 @@
import {
Controller,
Post,
Delete,
Route,
Security,
Tags,
Body,
Path,
Request,
Response,
Get
} from "tsoa";
import { LessThan, MoreThan } from "typeorm";
import { AppDataSource } from "../database/data-source";
import HttpSuccess from "../interfaces/http-success";
import HttpStatusCode from "../interfaces/http-status";
import HttpError from "../interfaces/http-error";
import { Command } from "../entities/Command";
import { CommandOperator, CreateCommandOperatorDto } from "../entities/CommandOperator";
import { RequestWithUser } from "../middlewares/user";
@Route("api/v1/org/commandOperator")
@Tags("CommandOperator")
@Security("bearerAuth")
@Response(
HttpStatusCode.INTERNAL_SERVER_ERROR,
"เกิดข้อผิดพลาด ไม่สามารถแสดงรายการได้ กรุณาลองใหม่ในภายหลัง",
)
export class CommandOperatorController extends Controller {
private commandRepo = AppDataSource.getRepository(Command);
private commandOperatorRepo = AppDataSource.getRepository(CommandOperator);
/**
* API
* @summary API
* @param commandId
*/
@Get("{commandId}")
async getCommandOperatorByCommandId(
@Path() commandId: string
) {
const command = await this.commandRepo.findOne({
where: { id: commandId },
select: { id: true },
});
if (!command) {
throw new HttpError(HttpStatusCode.NOT_FOUND, "ไม่พบข้อมูลคำสั่งนี้");
}
const commandOperators = await this.commandOperatorRepo.find({
where: { commandId: command.id },
order: { orderNo: "ASC" },
});
return new HttpSuccess(commandOperators);
}
/**
* API (UP / DOWN)
* @summary API (UP / DOWN)
* @param direction (UP / DOWN)
* @param operatorId
*/
@Get("swap/{direction}/{operatorId}")
async swapCommandOperator(
@Path() direction: string,
@Path() operatorId: string,
) {
const source = await this.commandOperatorRepo.findOne({
where: { id: operatorId },
});
if (!source) {
throw new HttpError(HttpStatusCode.NOT_FOUND, "ไม่พบข้อมูลเจ้าหน้าที่");
}
const sourceOrder = source.orderNo;
const isUp = direction.trim().toUpperCase() === "UP";
let dest: CommandOperator | null;
if (isUp) {
dest = await this.commandOperatorRepo.findOne({
where: {
commandId: source.commandId,
orderNo: LessThan(sourceOrder),
},
order: { orderNo: "DESC" },
});
} else {
dest = await this.commandOperatorRepo.findOne({
where: {
commandId: source.commandId,
orderNo: MoreThan(sourceOrder),
},
order: { orderNo: "ASC" },
});
}
// ถ้าไม่มีตัวให้สลับ (บนสุด / ล่างสุด)
if (!dest) {
return new HttpSuccess();
}
// swap
const temp = source.orderNo;
source.orderNo = dest.orderNo;
dest.orderNo = temp;
await Promise.all([
this.commandOperatorRepo.save(source),
this.commandOperatorRepo.save(dest),
]);
return new HttpSuccess();
}
/**
* API
* @summary API
* @param commandId
*/
@Post("{commandId}")
async createCommandOperators(
@Path() commandId: string,
@Request() request: RequestWithUser,
@Body() body: CreateCommandOperatorDto,
) {
const command = await this.commandRepo.findOne({
where: { id: commandId },
select: { id: true },
});
if (!command) {
throw new HttpError(HttpStatusCode.NOT_FOUND, "ไม่พบข้อมูลคำสั่งนี้");
}
const lastOrderNo = await this.commandOperatorRepo.findOne({
where: { commandId: commandId },
order: { orderNo: "DESC" },
select: { orderNo: true },
});
const nextOrderNo = (lastOrderNo?.orderNo ?? 1) + 1;
const now = new Date();
const operator = Object.assign(
new CommandOperator(),
{
...body,
commandId: command.id,
orderNo: nextOrderNo,
createdUserId: request.user.sub,
createdFullName: request.user.name,
createdAt: now,
lastUpdateUserId: request.user.sub,
lastUpdateFullName: request.user.name,
lastUpdatedAt: now,
}
);
await this.commandOperatorRepo.save(operator);
return new HttpSuccess();
}
/**
* API
* @summary API
* @param commandId
* @param operatorId
*/
@Delete("{commandId}/{operatorId}")
public async deleteCommandOperator(
@Path() commandId: string,
@Path() operatorId: string,
) {
const queryRunner = AppDataSource.createQueryRunner();
await queryRunner.connect();
await queryRunner.startTransaction();
try {
// 1. หา operator
const operator = await queryRunner.manager.findOne(CommandOperator, {
where: {
id: operatorId,
commandId: commandId,
},
});
if (!operator) {
throw new HttpError(HttpStatusCode.NOT_FOUND, "ไม่พบเจ้าหน้าที่ดำเนินการ");
}
// // 2. ห้ามลบ orderNo = 1
// if (operator.orderNo === 1) {
// throw new HttpError(
// HttpStatusCode.BAD_REQUEST,
// "ไม่สามารถลบเจ้าหน้าที่ลำดับที่ 1 ได้"
// );
// }
const removedOrderNo = operator.orderNo;
// 3. ลบ
await queryRunner.manager.remove(operator);
// 4. re orderNumber ตัวที่เหลือ
await queryRunner.manager
.createQueryBuilder()
.update(CommandOperator)
.set({
orderNo: () => "orderNo - 1",
})
.where("commandId = :commandId", { commandId })
.andWhere("orderNo > :removedOrderNo", { removedOrderNo })
.execute();
await queryRunner.commitTransaction();
return new HttpSuccess(true);
} catch (error) {
await queryRunner.rollbackTransaction();
throw error;
} finally {
await queryRunner.release();
}
}
}

View file

@ -165,7 +165,8 @@ export class DevelopmentRequestController extends Controller {
data.child1 != undefined && data.child1 != null
? data.child1[0] != null
? `current_holders.orgChild1Id IN (:...child1)`
: `current_holders.orgChild1Id is ${data.privilege == "PARENT" ? "not null" : "null"}`
// : `current_holders.orgChild1Id is ${data.privilege == "PARENT" ? "not null" : "null"}`
: `current_holders.orgChild1Id is null`
: "1=1",
{
child1: data.child1,

View file

@ -43,6 +43,7 @@ import {
CreatePosMasterHistoryOfficer,
} from "../services/PositionService";
import { PosMasterEmployeeHistory } from "../entities/PosMasterEmployeeHistory";
import { KeycloakAttributeService } from "../services/KeycloakAttributeService";
@Route("api/v1/org/employee/pos")
@Tags("Employee")
@Security("bearerAuth")
@ -65,6 +66,7 @@ export class EmployeePositionController extends Controller {
private child3Repository = AppDataSource.getRepository(OrgChild3);
private child4Repository = AppDataSource.getRepository(OrgChild4);
private authRoleRepo = AppDataSource.getRepository(AuthRole);
private keycloakAttributeService = new KeycloakAttributeService();
/**
* API
@ -679,6 +681,11 @@ export class EmployeePositionController extends Controller {
posMaster.lastUpdateFullName = request.user.name;
posMaster.lastUpdatedAt = new Date();
await this.employeePosMasterRepository.save(posMaster, { data: request });
const saved = await this.employeePosMasterRepository.save(posMaster, { data: request });
saved.ancestorDNA = saved.id;
await this.employeePosMasterRepository.save(saved, { data: request });
setLogDataDiff(request, { before, after: posMaster });
await Promise.all(
requestBody.positions.map(async (x: any) => {
@ -940,6 +947,35 @@ export class EmployeePositionController extends Controller {
return new HttpSuccess(posMaster.id);
}
/**
* @summary (ADMIN)
*/
@Put("master/position-condition/{id}")
async updatePositionCondition(
@Path() id: string,
@Body()
requestBody: {
isCondition: boolean | null;
conditionReason: string | null;
},
@Request() request: RequestWithUser,
) {
await new permission().PermissionUpdate(request, "SYS_POS_CONDITION");
const posMaster = await this.employeePosMasterRepository.findOne({
where: { id: id },
});
if (!posMaster) {
throw new HttpError(HttpStatusCode.NOT_FOUND, "ไม่พบข้อมูล");
}
Object.assign(posMaster, requestBody);
posMaster.lastUpdateUserId = request.user.sub;
posMaster.lastUpdateFullName = request.user.name;
posMaster.lastUpdatedAt = new Date();
await this.employeePosMasterRepository.save(posMaster);
return new HttpSuccess();
}
/**
* API
*
@ -1154,7 +1190,8 @@ export class EmployeePositionController extends Controller {
_data.child1 != undefined && _data.child1 != null
? _data.child1[0] != null
? `posMaster.orgChild1Id IN (:...child1)`
: `posMaster.orgChild1Id is ${_data.privilege == "PARENT" ? "not null" : "null"}`
// : `posMaster.orgChild1Id is ${_data.privilege == "PARENT" ? "not null" : "null"}`
: `posMaster.orgChild1Id is null`
: "1=1",
{
child1: _data.child1,
@ -1343,6 +1380,7 @@ export class EmployeePositionController extends Controller {
return {
id: posMaster.id,
ancestorDNA: posMaster.ancestorDNA,
current_holderId: posMaster.current_holderId,
orgRootId: posMaster.orgRootId,
orgChild1Id: posMaster.orgChild1Id,
@ -1368,6 +1406,8 @@ export class EmployeePositionController extends Controller {
profilePoslevel:
level == null || type == null ? null : `${type.posTypeShortName} ${level.posLevelName}`,
authRoleId: posMaster.authRoleId,
isCondition: posMaster.isCondition,
conditionReason: posMaster.conditionReason,
authRoleName:
authRoleName == null || authRoleName.roleName == null ? null : authRoleName.roleName,
positions: positions.map((position) => ({
@ -2397,6 +2437,12 @@ export class EmployeePositionController extends Controller {
// await this.profileRepository.save(profile);
// }
// }
if (dataMaster.current_holderId) {
await this.keycloakAttributeService.clearOrgDnaAttributes(
[dataMaster.current_holderId],
"PROFILE_EMPLOYEE",
);
}
await this.employeePosMasterRepository.update(id, {
isSit: false,

View file

@ -43,6 +43,7 @@ import permission from "../interfaces/permission";
import { setLogDataDiff } from "../interfaces/utils";
import { CreatePosMasterHistoryEmployeeTemp } from "../services/PositionService";
import { PosMasterEmployeeTempHistory } from "../entities/PosMasterEmployeeTempHistory";
import { KeycloakAttributeService } from "../services/KeycloakAttributeService";
@Route("api/v1/org/employee-temp/pos")
@Tags("Employee")
@Security("bearerAuth")
@ -65,6 +66,7 @@ export class EmployeeTempPositionController extends Controller {
private child3Repository = AppDataSource.getRepository(OrgChild3);
private child4Repository = AppDataSource.getRepository(OrgChild4);
private authRoleRepo = AppDataSource.getRepository(AuthRole);
private keycloakAttributeService = new KeycloakAttributeService();
/**
* API
@ -251,7 +253,7 @@ export class EmployeeTempPositionController extends Controller {
switch (type) {
case "positionName":
findData = await this.employeePosDictRepository.find({
where: { posDictName: Like(`%${keyword}%`), posLevel: { posLevelName: 1 } },
where: { posDictName: Like(`%${keyword}%`), posLevel: { posLevelName: "1" } },
relations: ["posType", "posLevel"],
order: {
posDictName: "ASC",
@ -274,7 +276,7 @@ export class EmployeeTempPositionController extends Controller {
select: ["id"],
});
findData = await this.employeePosDictRepository.find({
where: { posTypeId: In(findEmpTypes.map((x) => x.id)), posLevel: { posLevelName: 1 } },
where: { posTypeId: In(findEmpTypes.map((x) => x.id)), posLevel: { posLevelName: "1" } },
relations: ["posType", "posLevel"],
order: {
posDictName: "ASC",
@ -292,19 +294,19 @@ export class EmployeeTempPositionController extends Controller {
break;
case "positionLevel":
if (!isNaN(Number(keyword))) {
if (!keyword) {
let findEmpLevels;
if (Number(keyword) === 0) {
if (keyword === "0") {
findEmpLevels = await this.employeePosLevelRepository.find();
} else {
findEmpLevels = await this.employeePosLevelRepository.find({
where: { posLevelName: Number(keyword) },
where: { posLevelName: keyword },
});
}
findData = await this.employeePosDictRepository.find({
where: {
posLevelId: In(findEmpLevels.map((x) => x.id)),
posLevel: { posLevelName: 1 },
posLevel: { posLevelName: "1" },
},
relations: ["posType", "posLevel"],
order: {
@ -323,7 +325,7 @@ export class EmployeeTempPositionController extends Controller {
} else {
//กรณีเลือกค้นหาจาก"ระดับชั้นงาน" แต่กรอกไม่ใช่ number ให้ปล่อยมาหมดเลย
findData = await this.employeePosDictRepository.find({
where: { posLevel: { posLevelName: 1 } },
where: { posLevel: { posLevelName: "1" } },
relations: ["posType", "posLevel"],
order: {
posDictName: "ASC",
@ -343,7 +345,7 @@ export class EmployeeTempPositionController extends Controller {
default:
findData = await this.employeePosDictRepository.find({
where: { posLevel: { posLevelName: 1 } },
where: { posLevel: { posLevelName: "1" } },
relations: ["posType", "posLevel"],
order: {
posDictName: "ASC",
@ -546,6 +548,11 @@ export class EmployeeTempPositionController extends Controller {
posMaster.lastUpdateFullName = request.user.name;
posMaster.lastUpdatedAt = new Date();
await this.employeeTempPosMasterRepository.save(posMaster, { data: request });
const saved = await this.employeeTempPosMasterRepository.save(posMaster, { data: request });
saved.ancestorDNA = saved.id;
await this.employeeTempPosMasterRepository.save(saved, { data: request });
setLogDataDiff(request, { before, after: posMaster });
await Promise.all(
requestBody.positions.map(async (x: any) => {
@ -901,7 +908,8 @@ export class EmployeeTempPositionController extends Controller {
_data.child1 != undefined && _data.child1 != null
? _data.child1[0] != null
? `posMaster.orgChild1Id IN (:...child1)`
: `posMaster.orgChild1Id is ${_data.privilege == "PARENT" ? "not null" : "null"}`
// : `posMaster.orgChild1Id is ${_data.privilege == "PARENT" ? "not null" : "null"}`
: `posMaster.orgChild1Id is null`
: "1=1",
{
child1: _data.child1,
@ -1090,6 +1098,7 @@ export class EmployeeTempPositionController extends Controller {
return {
id: posMaster.id,
ancestorDNA: posMaster.ancestorDNA,
current_holderId: posMaster.current_holderId,
orgRootId: posMaster.orgRootId,
orgChild1Id: posMaster.orgChild1Id,
@ -2134,6 +2143,13 @@ export class EmployeeTempPositionController extends Controller {
// }
// }
if (dataMaster.current_holderId) {
await this.keycloakAttributeService.clearOrgDnaAttributes(
[dataMaster.current_holderId],
"PROFILE_EMPLOYEE",
);
}
await this.employeeTempPosMasterRepository.update(id, {
isSit: false,
next_holderId: null,

View file

@ -14,6 +14,7 @@ import {
} from "tsoa";
import HttpError from "../interfaces/http-error";
import HttpStatusCode from "../interfaces/http-status";
import { addLogSequence } from "../interfaces/utils";
interface CachedToken {
token: string;
@ -171,6 +172,7 @@ async function getToken(ClientID: string, ClientSecret: string): Promise<string>
// function post retire data to exprofile system
export async function PostRetireToExprofile(
request: any,
citizenID: string,
prefix: string,
firstName: string,
@ -183,6 +185,12 @@ export async function PostRetireToExprofile(
organizeName: string, // child4Name child3Name child2Name child1Name rootName
retireTypeName: string, // เช่น เกษียณ, ขอโอนออก, ลาออก, ปลดออก, ไล่ออก, ...
) {
// check NODE_ENV ถ้าเป็น production ถึงจะทำการส่งข้อมูลไปยัง exprofile
const NODE_ENV = process.env.NODE_ENV || "development";
if (NODE_ENV !== "production") {
return;
}
let retryCount = 0;
const maxRetries = 2;
@ -226,6 +234,18 @@ export async function PostRetireToExprofile(
retryCount++;
continue;
}
addLogSequence(request, {
action: "request",
status: "error",
description: "unconnected to exprofile api",
request: {
method: "POST",
url: API_URL_BANGKOK + "/importData",
response: JSON.stringify(error),
},
});
throw new HttpError(HttpStatusCode.INTERNAL_SERVER_ERROR, "ไม่สามารถติดต่อ API ได้");
}
}

View file

@ -395,12 +395,14 @@ export class ImportDataController extends Controller {
}
var positionType = "";
var positionLevel = 0;
// var positionLevel = 0;
var positionLevel = "0";
const workLevel = item.WORK_LEVEL;
const part1 = workLevel.split("/")[0]; // "ส 2"
const value2 = part1.split(" ")[1]; // "2"
if (value2) {
positionLevel = parseInt(value2);
// positionLevel = parseInt(value2);
positionLevel = value2;
}
if (item.CATEGORY_SAL_CODE == "11") {
positionType = "บริการพื้นฐาน";
@ -530,12 +532,14 @@ export class ImportDataController extends Controller {
}
var positionType = "";
var positionLevel = 0;
// var positionLevel = 0;
var positionLevel = "0";
const value2 = item.POSITION_LEVEL;
// const part1 = workLevel.split("/")[0]; // "ส 2"
// const value2 = part1.split(" ")[1]; // "2"
if (value2) {
positionLevel = parseInt(value2);
// positionLevel = parseInt(value2);
positionLevel = value2;
}
if (item.CATEGORY_SAL_CODE == "11") {
positionType = "บริการพื้นฐาน";
@ -2475,8 +2479,8 @@ export class ImportDataController extends Controller {
});
const educationLevel = await this.profileEducationRepo.findOne({
select: ["id", "level", "profileId"],
where: { profileId: _item.id },
select: ["id", "level", "profileId", "isDeleted"],
where: { profileId: _item.id, isDeleted: false },
order: { level: "DESC" },
});
@ -2607,8 +2611,8 @@ export class ImportDataController extends Controller {
});
const educationLevel = await this.profileEducationRepo.findOne({
select: ["id", "level", "profileEmployeeId"],
where: { profileEmployeeId: _item.id },
select: ["id", "level", "profileEmployeeId", "isDeleted"],
where: { profileEmployeeId: _item.id, isDeleted: false },
order: { level: "DESC" },
});
@ -2740,8 +2744,8 @@ export class ImportDataController extends Controller {
});
const educationLevel = await this.profileEducationRepo.findOne({
select: ["id", "level", "profileEmployeeId"],
where: { profileEmployeeId: _item.id },
select: ["id", "level", "profileEmployeeId", "isDeleted"],
where: { profileEmployeeId: _item.id, isDeleted: false },
order: { level: "DESC" },
});
@ -4340,12 +4344,14 @@ export class ImportDataController extends Controller {
let position = new EmployeePosition();
var positionType = "";
var positionLevel = 0;
// var positionLevel = 0;
var positionLevel = "0";
const workLevel = item.WORK_LEVEL;
const part1 = workLevel.split("/")[0]; // "ส 2"
const value2 = part1.split(" ")[1]; // "2"
if (value2) {
positionLevel = parseInt(value2);
// positionLevel = parseInt(value2);
positionLevel = value2;
}
if (item.CATEGORY_SAL_CODE == "11") {
positionType = "บริการพื้นฐาน";
@ -5799,7 +5805,7 @@ export class ImportDataController extends Controller {
},
});
const eduLevel = await this.profileEducationRepo.findOne({
where: { profileId: _item.id },
where: { profileId: _item.id, isDeleted: false },
order: {
startDate: "DESC",
},

View file

@ -0,0 +1,318 @@
import {
Controller,
Post,
Get,
Route,
Security,
Tags,
Path,
Request,
Response,
Query,
Body,
} from "tsoa";
import { KeycloakAttributeService } from "../services/KeycloakAttributeService";
import HttpSuccess from "../interfaces/http-success";
import HttpStatus from "../interfaces/http-status";
import HttpError from "../interfaces/http-error";
import { RequestWithUser } from "../middlewares/user";
@Route("api/v1/org/keycloak-sync")
@Tags("Keycloak Sync")
@Security("bearerAuth")
@Response(
HttpStatus.INTERNAL_SERVER_ERROR,
"เกิดข้อผิดพลาด ไม่สามารถดำเนินการได้ กรุณาลองใหม่ในภายหลัง",
)
export class KeycloakSyncController extends Controller {
private keycloakAttributeService = new KeycloakAttributeService();
/**
* Sync attributes for the current logged-in user
*
* @summary Sync profileId and rootDnaId to Keycloak for current user
*/
@Post("sync-me")
async syncCurrentUser(@Request() request: RequestWithUser) {
const keycloakUserId = request.user.sub;
if (!keycloakUserId) {
throw new HttpError(HttpStatus.UNAUTHORIZED, "ไม่พบ Keycloak user ID");
}
// Get attributes from database before sync
const dbAttrs = await this.keycloakAttributeService.getUserProfileAttributes(keycloakUserId);
const success = await this.keycloakAttributeService.syncUserAttributes(keycloakUserId);
if (!success) {
throw new HttpError(
HttpStatus.INTERNAL_SERVER_ERROR,
"ไม่สามารถ sync ข้อมูลไปยัง Keycloak ได้ กรุณาติดต่อผู้ดูแลระบบ",
);
}
// Verify sync by fetching attributes from Keycloak after update
const kcAttrsAfter =
await this.keycloakAttributeService.getCurrentKeycloakAttributes(keycloakUserId);
return new HttpSuccess({
message: "Sync ข้อมูลสำเร็จ",
syncedToKeycloak: !!kcAttrsAfter?.profileId,
databaseAttributes: dbAttrs,
keycloakAttributesAfter: kcAttrsAfter,
});
}
/**
* Get current attributes of the logged-in user
*
* @summary Get current profileId and rootDnaId from Keycloak
*/
@Get("my-attributes")
async getMyAttributes(@Request() request: RequestWithUser) {
const keycloakUserId = request.user.sub;
if (!keycloakUserId) {
throw new HttpError(HttpStatus.UNAUTHORIZED, "ไม่พบ Keycloak user ID");
}
const keycloakAttributes =
await this.keycloakAttributeService.getCurrentKeycloakAttributes(keycloakUserId);
const dbAttributes =
await this.keycloakAttributeService.getUserProfileAttributes(keycloakUserId);
return new HttpSuccess({
keycloakAttributes,
databaseAttributes: dbAttributes,
});
}
/**
* Sync attributes for a specific profile (Admin only)
*
* @summary Sync profileId and rootDnaId to Keycloak by profile ID (ADMIN)
*
* @param {string} profileId Profile ID
* @param {string} profileType Profile type (PROFILE or PROFILE_EMPLOYEE)
*/
@Post("sync-profile/:profileId")
async syncByProfileId(
@Path() profileId: string,
@Query() profileType: "PROFILE" | "PROFILE_EMPLOYEE" = "PROFILE",
) {
if (!["PROFILE", "PROFILE_EMPLOYEE"].includes(profileType)) {
throw new HttpError(
HttpStatus.BAD_REQUEST,
"profileType ต้องเป็น PROFILE หรือ PROFILE_EMPLOYEE เท่านั้น",
);
}
const success = await this.keycloakAttributeService.syncOnOrganizationChange(
profileId,
profileType,
);
if (!success) {
throw new HttpError(
HttpStatus.INTERNAL_SERVER_ERROR,
"ไม่สามารถ sync ข้อมูลไปยัง Keycloak ได้ หรือไม่พบข้อมูล profile",
);
}
return new HttpSuccess({ message: "Sync ข้อมูลสำเร็จ" });
}
/**
* Batch sync attributes for multiple profiles (Admin only)
*
* @summary Batch sync profileId and rootDnaId to Keycloak for multiple profiles (ADMIN)
*
* @param {request} request Request body containing profileIds array and profileType
*/
@Post("sync-profiles-batch")
async syncByProfileIds(
@Body() request: { profileIds: string[]; profileType: "PROFILE" | "PROFILE_EMPLOYEE" },
) {
const { profileIds, profileType } = request;
// Validate profileIds
if (!profileIds || profileIds.length === 0) {
throw new HttpError(HttpStatus.BAD_REQUEST, "profileIds ต้องไม่ว่างเปล่า");
}
// Validate profileType
if (!["PROFILE", "PROFILE_EMPLOYEE"].includes(profileType)) {
throw new HttpError(
HttpStatus.BAD_REQUEST,
"profileType ต้องเป็น PROFILE หรือ PROFILE_EMPLOYEE เท่านั้น",
);
}
const result = {
total: profileIds.length,
success: 0,
failed: 0,
details: [] as Array<{ profileId: string; status: "success" | "failed"; error?: string }>,
};
// Process each profileId
for (const profileId of profileIds) {
try {
const success = await this.keycloakAttributeService.syncOnOrganizationChange(
profileId,
profileType,
);
if (success) {
result.success++;
result.details.push({ profileId, status: "success" });
} else {
result.failed++;
result.details.push({
profileId,
status: "failed",
error: "Sync returned false - ไม่พบข้อมูล profile หรือ Keycloak user ID",
});
}
} catch (error: any) {
result.failed++;
result.details.push({ profileId, status: "failed", error: error.message });
}
}
return new HttpSuccess({
message: "Batch sync เสร็จสิ้น",
...result,
});
}
/**
* Clear org DNA attributes for profiles (Admin only)
*
* @summary Clear org DNA attributes in Keycloak for given profiles (ADMIN)
*
* @description
* This endpoint will:
* - Clear all org DNA fields (orgRootDnaId, orgChild1-4DnaId) by setting them to empty strings
* - Use when an employee leaves their position (current_holderId becomes null)
*
* @param {request} request Request body containing profileIds array and profileType
*/
@Post("clear-org-dna")
async clearOrgDna(
@Body() request: { profileIds: string[]; profileType: "PROFILE" | "PROFILE_EMPLOYEE" },
) {
const { profileIds, profileType } = request;
// Validate profileIds
if (!profileIds || profileIds.length === 0) {
throw new HttpError(HttpStatus.BAD_REQUEST, "profileIds ต้องไม่ว่างเปล่า");
}
// Validate profileType
if (!["PROFILE", "PROFILE_EMPLOYEE"].includes(profileType)) {
throw new HttpError(
HttpStatus.BAD_REQUEST,
"profileType ต้องเป็น PROFILE หรือ PROFILE_EMPLOYEE เท่านั้น",
);
}
const result = await this.keycloakAttributeService.clearOrgDnaAttributes(
profileIds,
profileType,
);
return new HttpSuccess({
message: "Clear org DNA attributes เสร็จสิ้น",
...result,
});
}
/**
* Batch sync all users (Admin only)
*
* @summary Batch sync all users to Keycloak without limit (ADMIN)
*
* @description Syncs profileId and orgRootDnaId to Keycloak for all users
* that have a keycloak ID. Uses parallel processing for better performance.
*
* Features:
* - Resume from checkpoint after failures (use resume=true)
* - Automatic retry with exponential backoff
* - Rate limiting to avoid overwhelming Keycloak
* - Progress tracking and persistence
*
* @param resume - Resume from last checkpoint (default: false)
* @param maxRetries - Maximum retry attempts for failed operations (default: 3)
* @param rateLimit - Requests per second rate limit (default: 10)
* @param clearProgress - Clear existing progress and start fresh (default: false)
*/
@Post("sync-all")
async syncAll(
@Query() resume: boolean = false,
@Query() maxRetries: number = 3,
@Query() rateLimit: number = 10,
@Query() clearProgress: boolean = false,
) {
const result = await this.keycloakAttributeService.batchSyncUsers({
resume,
maxRetries,
rateLimit,
clearProgress,
});
return new HttpSuccess({
message: "Batch sync เสร็จสิ้น",
total: result.total,
success: result.success,
failed: result.failed,
details: result.details,
resumed: result.resumed,
});
}
/**
* Ensure Keycloak users exist for all profiles (Admin only)
*
* @summary Create or verify Keycloak users for all profiles in Profile and ProfileEmployee tables (ADMIN)
*
* @description
* This endpoint will:
* - Create new Keycloak users for profiles without a keycloak ID
* - Create new Keycloak users for profiles where the stored keycloak ID doesn't exist in Keycloak
* - Verify existing Keycloak users
* - Skip profiles without a citizenId
*/
@Post("ensure-users")
async ensureAllUsers() {
const result = await this.keycloakAttributeService.batchEnsureKeycloakUsers();
return new HttpSuccess({
message: "Batch ensure Keycloak users เสร็จสิ้น",
...result,
});
}
/**
* Clear orphaned Keycloak users (Admin only)
*
* @summary Delete Keycloak users that are not in the database (ADMIN)
*
* @description
* This endpoint will:
* - Find users in Keycloak that are not referenced in Profile or ProfileEmployee tables
* - Delete those orphaned users from Keycloak
* - Skip protected users (super_admin, admin_issue)
*
* @param {request} request Request body containing skipUsernames array
*/
@Post("clear-orphaned-users")
async clearOrphanedUsers(@Body() request?: { skipUsernames?: string[] }) {
const skipUsernames = request?.skipUsernames || ["super_admin", "admin_issue"];
const result = await this.keycloakAttributeService.clearOrphanedKeycloakUsers(skipUsernames);
return new HttpSuccess({
message: "Clear orphaned Keycloak users เสร็จสิ้น",
...result,
});
}
}

View file

@ -1,4 +1,3 @@
import { profile } from "console";
import { Controller, Get, Post, Query, Route, Security, Tags } from "tsoa";
import { calculateGovAge } from "../interfaces/utils";
import HttpSuccess from "../interfaces/http-success";
@ -14,7 +13,7 @@ export class AppController extends Controller {
@Post()
public async Post(@Query() profileId: string) {
const result = calculateGovAge(profileId,"OFFICER");
const result = calculateGovAge(profileId, "OFFICER");
return new HttpSuccess(result);
}
}

View file

@ -204,6 +204,9 @@ export class OrgChild1Controller {
child1.orgChild1Order =
order == null || order.orgChild1Order == null ? 1 : order.orgChild1Order + 1;
await this.child1Repository.save(child1, { data: request });
// update ancestorDNA = id row
child1.ancestorDNA = child1.id;
await this.child1Repository.save(child1, { data: request });
setLogDataDiff(request, { before, after: child1 });
return new HttpSuccess();
}

View file

@ -164,6 +164,9 @@ export class OrgChild2Controller extends Controller {
child2.orgChild2Order =
order == null || order.orgChild2Order == null ? 1 : order.orgChild2Order + 1;
await this.child2Repository.save(child2, { data: request });
// update ancestorDNA = id row
child2.ancestorDNA = child2.id;
await this.child2Repository.save(child2, { data: request });
setLogDataDiff(request, { before, after: child2 });
return new HttpSuccess();
}

View file

@ -132,6 +132,9 @@ export class OrgChild3Controller {
child3.orgChild3Order =
order == null || order.orgChild3Order == null ? 1 : order.orgChild3Order + 1;
await this.child3Repository.save(child3, { data: request });
// update ancestorDNA = id row
child3.ancestorDNA = child3.id;
await this.child3Repository.save(child3, { data: request });
setLogDataDiff(request, { before, after: child3 });
return new HttpSuccess();
}

View file

@ -163,6 +163,9 @@ export class OrgChild4Controller extends Controller {
child4.orgChild4Order =
order == null || order.orgChild4Order == null ? 1 : order.orgChild4Order + 1;
await this.child4Repository.save(child4, { data: request });
// update ancestorDNA = id row
child4.ancestorDNA = child4.id;
await this.child4Repository.save(child4, { data: request });
setLogDataDiff(request, { before, after: child4 });
return new HttpSuccess();

View file

@ -203,6 +203,9 @@ export class OrgRootController extends Controller {
orgRoot.lastUpdatedAt = new Date();
orgRoot.orgRootOrder = order == null || order.orgRootOrder == null ? 1 : order.orgRootOrder + 1;
await this.orgRootRepository.save(orgRoot, { data: request });
// update ancestorDNA = id row
orgRoot.ancestorDNA = orgRoot.id;
await this.orgRootRepository.save(orgRoot, { data: request });
setLogDataDiff(request, { before, after: orgRoot });
return new HttpSuccess();

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

View file

@ -734,8 +734,10 @@ export class PermissionController extends Controller {
};
} else if (privilege == "PARENT") {
data = {
root: [x.orgRootId],
child1: [null],
// root: [x.orgRootId],
// child1: [null],
root: null,
child1: null,
child2: null,
child3: null,
child4: null,

View file

@ -448,13 +448,13 @@ export class PermissionProfileController extends Controller {
orgRootId: _data.orgRootId,
isCheck: _data.isCheck,
isEdit: _data.isEdit,
orgNew: _data.orgRootTree.orgRootName,
avatar: _data.profileTree.avatar,
avatarName: _data.profileTree.avatarName,
prefix: _data.profileTree.prefix,
rank: _data.profileTree.rank,
firstName: _data.profileTree.firstName,
lastName: _data.profileTree.lastName,
orgNew: _data.orgRootTree?.orgRootName,
avatar: _data.profileTree?.avatar,
avatarName: _data.profileTree?.avatarName,
prefix: _data.profileTree?.prefix,
rank: _data.profileTree?.rank,
firstName: _data.profileTree?.firstName,
lastName: _data.profileTree?.lastName,
org:
(_child4 == null ? "" : _child4 + "\n") +
(_child3 == null ? "" : _child3 + "\n") +
@ -462,10 +462,10 @@ export class PermissionProfileController extends Controller {
(_child1 == null ? "" : _child1 + "\n") +
(_root == null ? "" : _root),
posNo: shortName,
position: _data.profileTree.position,
posType: _data.profileTree.posType == null ? null : _data.profileTree.posType.posTypeName,
position: _data.profileTree?.position,
posType: _data.profileTree?.posType == null ? null : _data.profileTree?.posType.posTypeName,
posLevel:
_data.profileTree.posLevel == null ? null : _data.profileTree.posLevel.posLevelName,
_data.profileTree?.posLevel == null ? null : _data.profileTree?.posLevel.posLevelName,
};
}),
);

View file

@ -17,9 +17,13 @@ import HttpStatusCode from "../interfaces/http-status";
import HttpError from "../interfaces/http-error";
import { PosMasterAct } from "../entities/PosMasterAct";
import { PosMaster } from "../entities/PosMaster";
import { Brackets, LessThan, MoreThan } from "typeorm";
import { Brackets, In, IsNull, LessThan, MoreThan, Not } from "typeorm";
import permission from "../interfaces/permission";
import { OrgRevision } from "../entities/OrgRevision";
import Extension from "../interfaces/extension";
import { ProfileActposition } from "../entities/ProfileActposition";
import { RequestWithUser } from "../middlewares/user";
import { escape } from "querystring";
@Route("api/v1/org/pos/act")
@Tags("PosMasterAct")
@ -32,6 +36,7 @@ export class PosMasterActController extends Controller {
private orgRevisionRepository = AppDataSource.getRepository(OrgRevision);
private posMasterActRepository = AppDataSource.getRepository(PosMasterAct);
private posMasterRepository = AppDataSource.getRepository(PosMaster);
private actpositionRepository = AppDataSource.getRepository(ProfileActposition);
/**
* API
@ -87,6 +92,195 @@ export class PosMasterActController extends Controller {
return new HttpSuccess(posMasterAct);
}
/**
* API .
*
* @summary .
*
*/
@Post("search")
async searchAct(
@Request() request: RequestWithUser,
@Body()
body: {
posmasterId: string;
isAll: boolean;
isAllRoot?: boolean;
page?: number;
pageSize?: number;
keyword?: string;
},
) {
await new permission().PermissionGet(request, "SYS_ACTING");
const { page = 1, pageSize = 100, keyword } = body;
const posMasterMain = await this.posMasterRepository.findOne({
where: { id: body.posmasterId },
relations: ["posMasterActs"],
});
if (!posMasterMain) {
throw new HttpError(HttpStatusCode.NOT_FOUND, "ไม่พบข้อมูลประเภทตำแหน่งนี้");
}
let posId: any[] = posMasterMain.posMasterActs.map(
(x) => x.posMasterChildId
);
posId.push(body.posmasterId);
const query = await AppDataSource.getRepository(PosMaster)
.createQueryBuilder("posMaster")
.leftJoinAndSelect("posMaster.orgRoot", "orgRoot")
.leftJoinAndSelect("posMaster.orgChild1", "orgChild1")
.leftJoinAndSelect("posMaster.orgChild2", "orgChild2")
.leftJoinAndSelect("posMaster.orgChild3", "orgChild3")
.leftJoinAndSelect("posMaster.orgChild4", "orgChild4")
.leftJoinAndSelect("posMaster.current_holder", "current_holder")
.leftJoinAndSelect("current_holder.posLevel", "posLevel")
.leftJoinAndSelect("current_holder.posType", "posType")
.where("posMaster.current_holderId IS NOT NULL")
.andWhere("posMaster.id NOT IN (:...posId)", { posId });
if (!body.isAllRoot) {
if (body.isAll) {
if (posMasterMain.orgChild4Id) {
query.andWhere("posMaster.orgChild4Id = :id", {
id: posMasterMain.orgChild4Id,
});
} else if (posMasterMain.orgChild3Id) {
query.andWhere("posMaster.orgChild3Id = :id", {
id: posMasterMain.orgChild3Id,
});
} else if (posMasterMain.orgChild2Id) {
query.andWhere("posMaster.orgChild2Id = :id", {
id: posMasterMain.orgChild2Id,
});
} else if (posMasterMain.orgChild1Id) {
query.andWhere("posMaster.orgChild1Id = :id", {
id: posMasterMain.orgChild1Id,
});
} else {
query.andWhere("posMaster.orgRootId = :id", {
id: posMasterMain.orgRootId,
});
}
} else {
query
.andWhere(
posMasterMain.orgRootId == null
? "posMaster.orgRootId IS NULL"
: "posMaster.orgRootId = :orgRootId",
{ orgRootId: posMasterMain.orgRootId }
)
.andWhere(
posMasterMain.orgChild1Id == null
? "posMaster.orgChild1Id IS NULL"
: "posMaster.orgChild1Id = :orgChild1Id",
{ orgChild1Id: posMasterMain.orgChild1Id }
)
.andWhere(
posMasterMain.orgChild2Id == null
? "posMaster.orgChild2Id IS NULL"
: "posMaster.orgChild2Id = :orgChild2Id",
{ orgChild2Id: posMasterMain.orgChild2Id }
)
.andWhere(
posMasterMain.orgChild3Id == null
? "posMaster.orgChild3Id IS NULL"
: "posMaster.orgChild3Id = :orgChild3Id",
{ orgChild3Id: posMasterMain.orgChild3Id }
)
.andWhere(
posMasterMain.orgChild4Id == null
? "posMaster.orgChild4Id IS NULL"
: "posMaster.orgChild4Id = :orgChild4Id",
{ orgChild4Id: posMasterMain.orgChild4Id }
);
}
} else {
query.andWhere("posMaster.orgRootId = :orgRootId", {
orgRootId: posMasterMain.orgRootId,
});
}
if (keyword) {
query.andWhere(
new Brackets((qb) => {
qb.where(
`CONCAT(current_holder.prefix, current_holder.firstName, ' ', current_holder.lastName) LIKE :keyword`,
{ keyword: `%${keyword}%` }
)
.orWhere(`current_holder.citizenId LIKE :keyword`, {
keyword: `%${keyword}%`,
})
.orWhere(
`CONCAT(
CASE
WHEN orgChild4.id IS NOT NULL THEN orgChild4.orgChild4ShortName
WHEN orgChild3.id IS NOT NULL THEN orgChild3.orgChild3ShortName
WHEN orgChild2.id IS NOT NULL THEN orgChild2.orgChild2ShortName
WHEN orgChild1.id IS NOT NULL THEN orgChild1.orgChild1ShortName
WHEN orgRoot.id IS NOT NULL THEN orgRoot.orgRootShortName
ELSE ''
END,
' ',
posMaster.posMasterNo
) LIKE :keyword`,
{ keyword: `%${keyword}%` }
)
.orWhere(`posLevel.posLevelName LIKE :keyword`, {
keyword: `%${keyword}%`,
})
.orWhere(`posType.posTypeName LIKE :keyword`, {
keyword: `%${keyword}%`,
})
.orWhere(`current_holder.position LIKE :keyword`, {
keyword: `%${keyword}%`,
})
})
);
}
query.skip((page - 1) * pageSize).take(pageSize);
const [posMaster, total] = await query.getManyAndCount();
const data = await Promise.all(
posMaster
.sort((a, b) => a.posMasterOrder - b.posMasterOrder)
.map((item) => {
const shortName =
item.orgChild4 != null
? `${item.orgChild4.orgChild4ShortName} ${item.posMasterNo}`
: item?.orgChild3 != null
? `${item.orgChild3.orgChild3ShortName} ${item.posMasterNo}`
: item?.orgChild2 != null
? `${item.orgChild2.orgChild2ShortName} ${item.posMasterNo}`
: item?.orgChild1 != null
? `${item.orgChild1.orgChild1ShortName} ${item.posMasterNo}`
: item?.orgRoot != null
? `${item.orgRoot.orgRootShortName} ${item.posMasterNo}`
: null;
return {
id: item.id,
citizenId: item.current_holder?.citizenId ?? null,
isDirector: item.isDirector ?? null,
prefix: item.current_holder?.prefix ?? null,
firstName: item.current_holder?.firstName ?? null,
lastName: item.current_holder?.lastName ?? null,
posLevel: item.current_holder?.posLevel?.posLevelName ?? null,
posType: item.current_holder?.posType?.posTypeName ?? null,
position: item.current_holder?.position ?? null,
posNo: shortName,
};
}),
);
return new HttpSuccess({ data: data, total });
}
/**
* API
*
@ -385,6 +579,7 @@ export class PosMasterActController extends Controller {
posType: item.posMasterChild?.current_holder?.posType?.posTypeName ?? null,
position: item.posMasterChild?.current_holder?.position ?? null,
posNo: shortName,
statusReport: item.statusReport,
};
}),
);
@ -495,12 +690,12 @@ export class PosMasterActController extends Controller {
x.posMasterChild?.orgRoot?.orgRootShortName,
].find((name) => !!name) && x.posMasterChild?.posMasterNo
? `${[
x.posMasterChild?.orgChild4?.orgChild4ShortName,
x.posMasterChild?.orgChild3?.orgChild3ShortName,
x.posMasterChild?.orgChild2?.orgChild2ShortName,
x.posMasterChild?.orgChild1?.orgChild1ShortName,
x.posMasterChild?.orgRoot?.orgRootShortName,
].find((name) => !!name)} ${x.posMasterChild.posMasterNo}`
x.posMasterChild?.orgChild4?.orgChild4ShortName,
x.posMasterChild?.orgChild3?.orgChild3ShortName,
x.posMasterChild?.orgChild2?.orgChild2ShortName,
x.posMasterChild?.orgChild1?.orgChild1ShortName,
x.posMasterChild?.orgRoot?.orgRootShortName,
].find((name) => !!name)} ${x.posMasterChild.posMasterNo}`
: x.posMasterChild?.posMasterNo || null;
const orgShortNameAct =
[
@ -535,4 +730,110 @@ export class PosMasterActController extends Controller {
return new HttpSuccess(_posMaster);
}
/**
* API active
* @summary active (SUPER ADMIN)
* @param {string} id Id
*/
@Post("{id}")
async activePosMasterAct(@Path() id: string, @Request() req: { user: Record<string, any> }) {
const posMasterActs = await this.posMasterActRepository
.createQueryBuilder("posMasterAct")
.leftJoinAndSelect("posMasterAct.posMaster", "posMaster")
.leftJoinAndSelect("posMaster.orgRoot", "orgRoot")
.leftJoinAndSelect("posMaster.orgChild1", "orgChild1")
.leftJoinAndSelect("posMaster.orgChild2", "orgChild2")
.leftJoinAndSelect("posMaster.orgChild3", "orgChild3")
.leftJoinAndSelect("posMaster.orgChild4", "orgChild4")
.leftJoinAndSelect("posMaster.current_holder", "current_holder")
.leftJoinAndSelect("posMasterAct.posMasterChild", "posMasterChild")
.where("posMaster.orgRootId = :orgRootId", { orgRootId: id })
.andWhere("posMasterAct.statusReport = :statusReport", { statusReport: "PENDING" })
.select([
"posMasterAct.id",
"posMasterAct.statusReport",
"posMaster.posMasterNo",
"orgRoot.orgRootShortName",
"orgChild1.orgChild1ShortName",
"orgChild2.orgChild2ShortName",
"orgChild3.orgChild3ShortName",
"orgChild4.orgChild4ShortName",
"current_holder.position",
"posMasterChild.current_holderId",
])
.getMany();
if (posMasterActs.length === 0) {
throw new HttpError(HttpStatusCode.NOT_FOUND, "ไม่พบข้อมูลรักษาการในตำแหน่งของหน่วยงานนี้");
}
await Promise.all(
posMasterActs.map(async (posMasterAct) => {
const orgShortName =
[
posMasterAct.posMaster?.orgChild4?.orgChild4ShortName,
posMasterAct.posMaster?.orgChild3?.orgChild3ShortName,
posMasterAct.posMaster?.orgChild2?.orgChild2ShortName,
posMasterAct.posMaster?.orgChild1?.orgChild1ShortName,
posMasterAct.posMaster?.orgRoot?.orgRootShortName,
].find(Boolean) ?? "";
const profileId = posMasterAct.posMasterChild?.current_holderId;
if (profileId) {
const existingActivePositions = await this.actpositionRepository.find({
select: [
"id",
"status",
"lastUpdateUserId",
"lastUpdateFullName",
"lastUpdatedAt",
"dateEnd",
"isDeleted"
],
where: { profileId, status: true, isDeleted: false },
});
if (existingActivePositions.length > 0) {
await Promise.all(
existingActivePositions.map(async (pos) => {
Object.assign(pos, {
status: false,
lastUpdateUserId: req.user?.sub ?? null,
lastUpdateFullName: req.user?.name ?? null,
lastUpdatedAt: new Date(),
dateEnd: new Date(),
});
await this.actpositionRepository.save(pos);
}),
);
}
}
const dataAct = new ProfileActposition();
Object.assign(dataAct, {
profileId: profileId ?? null,
dateStart: new Date(),
posNo:
orgShortName && posMasterAct.posMaster?.posMasterNo
? `${orgShortName} ${posMasterAct.posMaster.posMasterNo}`
: posMasterAct.posMaster?.posMasterNo ?? "-",
position: posMasterAct.posMaster?.current_holder?.position ?? null,
posNoAbb: orgShortName,
status: true,
createdUserId: req.user?.sub ?? null,
createdFullName: req.user?.name ?? null,
lastUpdateUserId: req.user?.sub ?? null,
lastUpdateFullName: req.user?.name ?? null,
});
await this.actpositionRepository.save(dataAct);
posMasterAct.statusReport = "DONE";
await this.posMasterActRepository.save(posMasterAct);
}),
);
return new HttpSuccess();
}
}

View file

@ -38,12 +38,13 @@ import { EmployeePosLevel } from "../entities/EmployeePosLevel";
import { AuthRole } from "../entities/AuthRole";
import { RequestWithUser } from "../middlewares/user";
import permission from "../interfaces/permission";
import { setLogDataDiff } from "../interfaces/utils";
import { resolveNodeLevel, setLogDataDiff } from "../interfaces/utils";
import { PosMasterAssign } from "../entities/PosMasterAssign";
import { Assign } from "../entities/Assign";
import { ProfileEmployee } from "../entities/ProfileEmployee";
import { PosMasterHistory } from "../entities/PosMasterHistory";
import { CreatePosMasterHistoryOfficer } from "../services/PositionService";
import { KeycloakAttributeService } from "../services/KeycloakAttributeService";
@Route("api/v1/org/pos")
@Tags("Position")
@Security("bearerAuth")
@ -73,6 +74,7 @@ export class PositionController extends Controller {
private authRoleRepo = AppDataSource.getRepository(AuthRole);
private posMasterAssignRepo = AppDataSource.getRepository(PosMasterAssign);
private assignRepo = AppDataSource.getRepository(Assign);
private keycloakAttributeService = new KeycloakAttributeService();
/**
* API
@ -1449,7 +1451,8 @@ export class PositionController extends Controller {
}),
);
if (posMaster.orgRevision?.orgRevisionIsCurrent == true) {
// ถ้าไม่ใช่ตำแหน่งนั่งทับ (isSit = false) ถึงจะอัพเดทตำแหน่งในทะเบียนประวัติ
if (posMaster.orgRevision?.orgRevisionIsCurrent == true && !posMaster.isSit) {
const _position = requestBody.positions.find((p) => p.positionIsSelected == true);
if (_position) {
const current_holderId: any = posMaster.current_holderId;
@ -1510,7 +1513,8 @@ export class PositionController extends Controller {
posMaster.orgRootId !== null &&
posMaster.orgChild1Id == null &&
posMaster.orgChild2Id == null &&
posMaster.orgChild3Id == null
posMaster.orgChild3Id == null &&
posMaster.orgChild4Id == null
) {
shortName = posMaster.orgRoot.orgRootShortName;
orgId = posMaster.orgRootId;
@ -1518,7 +1522,8 @@ export class PositionController extends Controller {
posMaster.orgRootId !== null &&
posMaster.orgChild1Id !== null &&
posMaster.orgChild2Id == null &&
posMaster.orgChild3Id == null
posMaster.orgChild3Id == null &&
posMaster.orgChild4Id == null
) {
shortName = posMaster.orgChild1.orgChild1ShortName;
orgId = posMaster.orgChild1Id;
@ -1526,7 +1531,8 @@ export class PositionController extends Controller {
posMaster.orgRootId !== null &&
posMaster.orgChild1Id !== null &&
posMaster.orgChild2Id !== null &&
posMaster.orgChild3Id == null
posMaster.orgChild3Id == null &&
posMaster.orgChild4Id == null
) {
shortName = posMaster.orgChild2.orgChild2ShortName;
orgId = posMaster.orgChild2Id;
@ -1534,7 +1540,8 @@ export class PositionController extends Controller {
posMaster.orgRootId !== null &&
posMaster.orgChild1Id !== null &&
posMaster.orgChild2Id !== null &&
posMaster.orgChild3Id !== null
posMaster.orgChild3Id !== null &&
posMaster.orgChild4Id == null
) {
shortName = posMaster.orgChild3.orgChild3ShortName;
orgId = posMaster.orgChild3Id;
@ -1542,7 +1549,8 @@ export class PositionController extends Controller {
posMaster.orgRootId !== null &&
posMaster.orgChild1Id !== null &&
posMaster.orgChild2Id !== null &&
posMaster.orgChild3Id !== null
posMaster.orgChild3Id !== null &&
posMaster.orgChild4Id !== null
) {
shortName = posMaster.orgChild4.orgChild4ShortName;
orgId = posMaster.orgChild4Id;
@ -1998,7 +2006,7 @@ export class PositionController extends Controller {
posMaster.orgChild3Id == null
) {
body.type = 0;
shortName = posMaster.orgRoot.orgRootShortName;
shortName = posMaster.orgRoot?.orgRootShortName;
} else if (
posMaster.orgRootId !== null &&
posMaster.orgChild1Id !== null &&
@ -2006,7 +2014,7 @@ export class PositionController extends Controller {
posMaster.orgChild3Id == null
) {
body.type = 1;
shortName = posMaster.orgChild1.orgChild1ShortName;
shortName = posMaster.orgChild1?.orgChild1ShortName;
} else if (
posMaster.orgRootId !== null &&
posMaster.orgChild1Id !== null &&
@ -2014,7 +2022,7 @@ export class PositionController extends Controller {
posMaster.orgChild3Id == null
) {
body.type = 2;
shortName = posMaster.orgChild2.orgChild2ShortName;
shortName = posMaster.orgChild2?.orgChild2ShortName;
} else if (
posMaster.orgRootId !== null &&
posMaster.orgChild1Id !== null &&
@ -2022,7 +2030,7 @@ export class PositionController extends Controller {
posMaster.orgChild3Id !== null
) {
body.type = 3;
shortName = posMaster.orgChild3.orgChild3ShortName;
shortName = posMaster.orgChild3?.orgChild3ShortName;
} else if (
posMaster.orgRootId !== null &&
posMaster.orgChild1Id !== null &&
@ -2030,7 +2038,7 @@ export class PositionController extends Controller {
posMaster.orgChild3Id !== null
) {
body.type = 4;
shortName = posMaster.orgChild4.orgChild4ShortName;
shortName = posMaster.orgChild4?.orgChild4ShortName;
}
return {
@ -2385,7 +2393,8 @@ export class PositionController extends Controller {
_data.child1 != undefined && _data.child1 != null
? _data.child1[0] != null
? "posMaster.orgChild1Id IN (:...child1)"
: `posMaster.orgChild1Id is ${_data.privilege == "PARENT" ? "not null" : "null"}`
// : `posMaster.orgChild1Id is ${_data.privilege == "PARENT" ? "not null" : "null"}`
: `posMaster.orgChild1Id is null`
: "1=1",
{ child1: _data.child1 }
)
@ -3818,11 +3827,14 @@ export class PositionController extends Controller {
where: { id: requestBody.position, posMasterId: requestBody.posMaster },
});
if (_position) {
_profile.position = _position.positionName;
_profile.posTypeId = _position.posTypeId;
_profile.posLevelId = _position.posLevelId;
await this.profileRepository.save(_profile);
setLogDataDiff(request, { before, after: _profile });
// ถ้าไม่ใช่ตำแหน่งนั่งทับ (isSit = false) ถึงจะอัพเดทตำแหน่งในทะเบียนประวัติ
if(!dataMaster.isSit){
_profile.position = _position.positionName;
_profile.posTypeId = _position.posTypeId;
_profile.posLevelId = _position.posLevelId;
await this.profileRepository.save(_profile);
setLogDataDiff(request, { before, after: _profile });
}
}
}
dataMaster.current_holderId = requestBody.profileId;
@ -3862,6 +3874,13 @@ export class PositionController extends Controller {
await CreatePosMasterHistoryOfficer(dataMaster.id, request, "DELETE");
}
if (dataMaster.current_holderId) {
await this.keycloakAttributeService.clearOrgDnaAttributes(
[dataMaster.current_holderId],
"PROFILE",
);
}
let _profileId: string = "";
if (dataMaster?.current_holderId) {
_profileId = dataMaster?.current_holderId;
@ -4717,6 +4736,8 @@ export class PositionController extends Controller {
posMaster.next_holder == null
? null
: `${posMaster.next_holder.prefix}${posMaster.next_holder.firstName} ${posMaster.next_holder.lastName}`,
isCondition: posMaster.isCondition,
conditionReason: posMaster.conditionReason,
positions: posMaster.positions.map((position) => ({
id: position.id,
positionName: position.positionName,
@ -4938,101 +4959,6 @@ export class PositionController extends Controller {
return new HttpSuccess({ data: formattedData, total });
}
/**
* API .
*
* @summary .
*
*/
@Post("act/search")
async searchAct(
@Request() request: RequestWithUser,
@Body()
body: {
posmasterId: string;
isAll: boolean;
},
) {
await new permission().PermissionGet(request, "SYS_ACTING");
const posMasterMain = await this.posMasterRepository.findOne({
where: { id: body.posmasterId },
relations: ["posMasterActs"],
});
if (posMasterMain == null) {
throw new HttpError(HttpStatusCode.NOT_FOUND, "ไม่พบข้อมูลประเภทตำแหน่งนี้");
}
let posId: any = posMasterMain.posMasterActs.map((x) => x.posMasterChildId);
posId.push(body.posmasterId);
let typeCondition: any = {};
if (body.isAll == true) {
typeCondition = {
orgRootId: posMasterMain.orgRootId,
orgChild1Id: posMasterMain.orgChild1Id,
orgChild2Id: posMasterMain.orgChild2Id,
orgChild3Id: posMasterMain.orgChild3Id,
orgChild4Id: posMasterMain.orgChild4Id,
current_holderId: Not(IsNull()),
id: Not(In(posId)),
};
} else {
typeCondition = {
orgRootId: posMasterMain.orgRootId == null ? IsNull() : posMasterMain.orgRootId,
orgChild1Id: posMasterMain.orgChild1Id == null ? IsNull() : posMasterMain.orgChild1Id,
orgChild2Id: posMasterMain.orgChild2Id == null ? IsNull() : posMasterMain.orgChild2Id,
orgChild3Id: posMasterMain.orgChild3Id == null ? IsNull() : posMasterMain.orgChild3Id,
orgChild4Id: posMasterMain.orgChild4Id == null ? IsNull() : posMasterMain.orgChild4Id,
current_holderId: Not(IsNull()),
id: Not(In(posId)),
};
}
const posMaster = await this.posMasterRepository.find({
where: typeCondition,
relations: [
"orgRoot",
"orgChild1",
"orgChild2",
"orgChild3",
"orgChild4",
"current_holder",
"current_holder.posLevel",
"current_holder.posType",
],
});
const data = await Promise.all(
posMaster
.sort((a, b) => a.posMasterOrder - b.posMasterOrder)
.map((item) => {
const shortName =
item.orgChild4 != null
? `${item.orgChild4.orgChild4ShortName} ${item.posMasterNo}`
: item?.orgChild3 != null
? `${item.orgChild3.orgChild3ShortName} ${item.posMasterNo}`
: item?.orgChild2 != null
? `${item.orgChild2.orgChild2ShortName} ${item.posMasterNo}`
: item?.orgChild1 != null
? `${item.orgChild1.orgChild1ShortName} ${item.posMasterNo}`
: item?.orgRoot != null
? `${item.orgRoot.orgRootShortName} ${item.posMasterNo}`
: null;
return {
id: item.id,
citizenId: item.current_holder?.citizenId ?? null,
isDirector: item.isDirector ?? null,
prefix: item.current_holder?.prefix ?? null,
firstName: item.current_holder?.firstName ?? null,
lastName: item.current_holder?.lastName ?? null,
posLevel: item.current_holder?.posLevel?.posLevelName ?? null,
posType: item.current_holder?.posType?.posTypeName ?? null,
position: item.current_holder?.position ?? null,
posNo: shortName,
};
}),
);
return new HttpSuccess(data);
}
/**
* API
*
@ -5272,9 +5198,41 @@ export class PositionController extends Controller {
let searchShortName3 = `CONCAT(orgChild3.orgChild3ShortName," ",COALESCE(posMaster.posMasterNoPrefix, ""),posMaster.posMasterNo,COALESCE(posMaster.posMasterNoSuffix, ""))`;
let searchShortName4 = `CONCAT(orgChild4.orgChild4ShortName," ",COALESCE(posMaster.posMasterNoPrefix, ""),posMaster.posMasterNo,COALESCE(posMaster.posMasterNoSuffix, ""))`;
let _data = await new permission().PermissionOrgList(request, "SYS_POS_CONDITION");
const orgDna = await new permission().checkDna(request, request.user.sub);
let level: any = resolveNodeLevel(orgDna);
const cannotViewRootPosMaster =
(_data.privilege === "PARENT") ||
(_data.privilege === "BROTHER" && level > 1) ||
(_data.privilege === "CHILD" && level > 0) ||
(_data.privilege === "NORMAL" && level != 0);
const cannotViewChild1PosMaster =
(_data.privilege === "PARENT" && level > 1) ||
(_data.privilege === "BROTHER" && level > 2) ||
(_data.privilege === "CHILD" && level > 1) ||
(_data.privilege === "NORMAL" && level !== 1);
const cannotViewChild2PosMaster =
(_data.privilege === "PARENT" && level > 2) ||
(_data.privilege === "BROTHER" && level > 3) ||
(_data.privilege === "CHILD" && level > 2) ||
(_data.privilege === "NORMAL" && level !== 2);
const cannotViewChild3PosMaster =
(_data.privilege === "PARENT" && level > 3) ||
(_data.privilege === "BROTHER" && level > 4) ||
(_data.privilege === "CHILD" && level > 3) ||
(_data.privilege === "NORMAL" && level !== 3);
const cannotViewChild4PosMaster =
(_data.privilege === "PARENT" && level > 4) ||
(_data.privilege === "CHILD" && level > 4) ||
(_data.privilege === "NORMAL" && level !== 4);
if (body.type === 0) {
typeCondition = {
orgRootId: body.id,
...(cannotViewRootPosMaster ? { orgRootId: null } : { orgRootId: body.id }),
};
if (!body.isAll) {
checkChildConditions = {
@ -5285,7 +5243,7 @@ export class PositionController extends Controller {
}
} else if (body.type === 1) {
typeCondition = {
orgChild1Id: body.id,
...(cannotViewChild1PosMaster ? { orgChild1Id: null } : { orgChild1Id: body.id }),
};
if (!body.isAll) {
checkChildConditions = {
@ -5296,7 +5254,7 @@ export class PositionController extends Controller {
}
} else if (body.type === 2) {
typeCondition = {
orgChild2Id: body.id,
...(cannotViewChild2PosMaster ? { orgChild2Id: null } : { orgChild2Id: body.id }),
};
if (!body.isAll) {
checkChildConditions = {
@ -5307,7 +5265,7 @@ export class PositionController extends Controller {
}
} else if (body.type === 3) {
typeCondition = {
orgChild3Id: body.id,
...(cannotViewChild3PosMaster ? { orgChild3Id: null } : { orgChild3Id: body.id }),
};
if (!body.isAll) {
checkChildConditions = {
@ -5318,7 +5276,7 @@ export class PositionController extends Controller {
}
} else if (body.type === 4) {
typeCondition = {
orgChild4Id: body.id,
...(cannotViewChild4PosMaster ? { orgChild4Id: null } : { orgChild4Id: body.id }),
};
searchShortName = `CONCAT(orgChild4.orgChild4ShortName," ",COALESCE(posMaster.posMasterNoPrefix, ""),posMaster.posMasterNo,COALESCE(posMaster.posMasterNoSuffix, "")) like '%${body.keyword}%'`;
}
@ -5403,56 +5361,56 @@ export class PositionController extends Controller {
.leftJoinAndSelect("current_holder.posType", "posType")
.leftJoinAndSelect("current_holder.posLevel", "posLevel")
.where(conditions)
.andWhere(
_data.root != undefined && _data.root != null
? _data.root[0] != null
? `posMaster.orgRootId IN (:...root)`
: `posMaster.orgRootId is null`
: "1=1",
{
root: _data.root,
},
)
.andWhere(
_data.child1 != undefined && _data.child1 != null
? _data.child1[0] != null
? `posMaster.orgChild1Id IN (:...child1)`
: `posMaster.orgChild1Id is ${_data.privilege == "PARENT" ? "not null" : "null"}`
: "1=1",
{
child1: _data.child1,
},
)
.andWhere(
_data.child2 != undefined && _data.child2 != null
? _data.child2[0] != null
? `posMaster.orgChild2Id IN (:...child2)`
: `posMaster.orgChild2Id is null`
: "1=1",
{
child2: _data.child2,
},
)
.andWhere(
_data.child3 != undefined && _data.child3 != null
? _data.child3[0] != null
? `posMaster.orgChild3Id IN (:...child3)`
: `posMaster.orgChild3Id is null`
: "1=1",
{
child3: _data.child3,
},
)
.andWhere(
_data.child4 != undefined && _data.child4 != null
? _data.child4[0] != null
? `posMaster.orgChild4Id IN (:...child4)`
: `posMaster.orgChild4Id is null`
: "1=1",
{
child4: _data.child4,
},
)
// .andWhere(
// _data.root != undefined && _data.root != null
// ? _data.root[0] != null
// ? `posMaster.orgRootId IN (:...root)`
// : `posMaster.orgRootId is null`
// : "1=1",
// {
// root: _data.root,
// },
// )
// .andWhere(
// _data.child1 != undefined && _data.child1 != null
// ? _data.child1[0] != null
// ? `posMaster.orgChild1Id IN (:...child1)`
// : `posMaster.orgChild1Id is ${_data.privilege == "PARENT" ? "not null" : "null"}`
// : "1=1",
// {
// child1: _data.child1,
// },
// )
// .andWhere(
// _data.child2 != undefined && _data.child2 != null
// ? _data.child2[0] != null
// ? `posMaster.orgChild2Id IN (:...child2)`
// : `posMaster.orgChild2Id is null`
// : "1=1",
// {
// child2: _data.child2,
// },
// )
// .andWhere(
// _data.child3 != undefined && _data.child3 != null
// ? _data.child3[0] != null
// ? `posMaster.orgChild3Id IN (:...child3)`
// : `posMaster.orgChild3Id is null`
// : "1=1",
// {
// child3: _data.child3,
// },
// )
// .andWhere(
// _data.child4 != undefined && _data.child4 != null
// ? _data.child4[0] != null
// ? `posMaster.orgChild4Id IN (:...child4)`
// : `posMaster.orgChild4Id is null`
// : "1=1",
// {
// child4: _data.child4,
// },
// )
.orWhere(
new Brackets((qb) => {
qb.andWhere(

View file

@ -40,7 +40,7 @@ export class ProfileAbilityController extends Controller {
throw new HttpError(HttpStatus.BAD_REQUEST, "ไม่พบ profile ดังกล่าว");
}
const getProfileAbilityId = await this.profileAbilityRepo.find({
where: { profileId: profile.id },
where: { profileId: profile.id, isDeleted: false },
order: { createdAt: "ASC" },
});
if (!getProfileAbilityId) {
@ -55,7 +55,7 @@ export class ProfileAbilityController extends Controller {
if (_workflow == false)
await new permission().PermissionOrgUserGet(req, "SYS_REGISTRY_OFFICER", profileId);
const getProfileAbilityId = await this.profileAbilityRepo.find({
where: { profileId: profileId },
where: { profileId: profileId, isDeleted: false },
order: { createdAt: "ASC" },
});
if (!getProfileAbilityId) {
@ -174,6 +174,45 @@ export class ProfileAbilityController extends Controller {
return new HttpSuccess();
}
/**
* API
* @summary API
* @param abilityId
*/
@Patch("update-delete/{abilityId}")
public async updateIsDeleted(
@Request() req: RequestWithUser,
@Path() abilityId: string,
) {
const record = await this.profileAbilityRepo.findOneBy({ id: abilityId });
if (!record) throw new HttpError(HttpStatus.NOT_FOUND, "ไม่พบข้อมูล");
if (record.isDeleted === true) {
return new HttpSuccess();
}
await new permission().PermissionOrgUserDelete(req, "SYS_REGISTRY_OFFICER", record.profileId);
const before = structuredClone(record);
const history = new ProfileAbilityHistory();
const now = new Date();
record.isDeleted = true;
record.lastUpdateUserId = req.user.sub;
record.lastUpdateFullName = req.user.name;
record.lastUpdatedAt = now;
Object.assign(history, { ...record, id: undefined });
history.profileAbilityId = abilityId;
history.createdUserId = req.user.sub;
history.createdFullName = req.user.name;
history.createdAt = now;
await Promise.all([
this.profileAbilityRepo.save(record, { data: req }),
setLogDataDiff(req, { before, after: record }),
this.profileAbilityHistoryRepo.save(history, { data: req }),
]);
return new HttpSuccess();
}
@Delete("{abilityId}")
public async deleteProfileAbility(@Path() abilityId: string, @Request() req: RequestWithUser) {
const _record = await this.profileAbilityRepo.findOneBy({ id: abilityId });

View file

@ -40,7 +40,7 @@ export class ProfileAbilityEmployeeController extends Controller {
throw new HttpError(HttpStatus.BAD_REQUEST, "ไม่พบ profile ดังกล่าว");
}
const getProfileAbilityId = await this.profileAbilityRepo.find({
where: { profileEmployeeId: profile.id },
where: { profileEmployeeId: profile.id, isDeleted: false },
order: { createdAt: "ASC" },
});
if (!getProfileAbilityId) {
@ -58,7 +58,7 @@ export class ProfileAbilityEmployeeController extends Controller {
if (_workflow == false)
await new permission().PermissionOrgUserGet(req, "SYS_REGISTRY_EMP", profileEmployeeId);
const getProfileAbilityId = await this.profileAbilityRepo.find({
where: { profileEmployeeId: profileEmployeeId },
where: { profileEmployeeId: profileEmployeeId, isDeleted: false },
order: { createdAt: "ASC" },
});
if (!getProfileAbilityId) {
@ -183,6 +183,45 @@ export class ProfileAbilityEmployeeController extends Controller {
return new HttpSuccess();
}
/**
* API
* @summary API
* @param abilityId
*/
@Patch("update-delete/{abilityId}")
public async updateIsDeleted(
@Request() req: RequestWithUser,
@Path() abilityId: string,
) {
const record = await this.profileAbilityRepo.findOneBy({ id: abilityId });
if (!record) throw new HttpError(HttpStatus.NOT_FOUND, "ไม่พบข้อมูล");
if (record.isDeleted === true) {
return new HttpSuccess();
}
await new permission().PermissionOrgUserDelete(req, "SYS_REGISTRY_EMP", record.profileEmployeeId);
const before = structuredClone(record);
const history = new ProfileAbilityHistory();
const now = new Date();
record.isDeleted = true;
record.lastUpdateUserId = req.user.sub;
record.lastUpdateFullName = req.user.name;
record.lastUpdatedAt = now;
Object.assign(history, { ...record, id: undefined });
history.profileAbilityId = abilityId;
history.createdUserId = req.user.sub;
history.createdFullName = req.user.name;
history.createdAt = now;
await Promise.all([
this.profileAbilityRepo.save(record, { data: req }),
setLogDataDiff(req, { before, after: record }),
this.profileAbilityHistoryRepo.save(history, { data: req }),
]);
return new HttpSuccess();
}
@Delete("{abilityId}")
public async deleteProfileAbility(@Path() abilityId: string, @Request() req: RequestWithUser) {
const _record = await this.profileAbilityRepo.findOneBy({ id: abilityId });

View file

@ -40,7 +40,7 @@ export class ProfileAbilityEmployeeTempController extends Controller {
throw new HttpError(HttpStatus.BAD_REQUEST, "ไม่พบ profile ดังกล่าว");
}
const getProfileAbilityId = await this.profileAbilityRepo.find({
where: { profileEmployeeId: profile.id },
where: { profileEmployeeId: profile.id, isDeleted: false },
order: { createdAt: "ASC" },
});
if (!getProfileAbilityId) {
@ -57,7 +57,7 @@ export class ProfileAbilityEmployeeTempController extends Controller {
let _workflow = await new permission().Workflow(req, profileEmployeeId, "SYS_REGISTRY_TEMP");
if (_workflow == false) await new permission().PermissionGet(req, "SYS_REGISTRY_TEMP");
const getProfileAbilityId = await this.profileAbilityRepo.find({
where: { profileEmployeeId: profileEmployeeId },
where: { profileEmployeeId: profileEmployeeId, isDeleted: false },
order: { createdAt: "ASC" },
});
if (!getProfileAbilityId) {
@ -173,6 +173,45 @@ export class ProfileAbilityEmployeeTempController extends Controller {
return new HttpSuccess();
}
/**
* API
* @summary API
* @param abilityId
*/
@Patch("update-delete/{abilityId}")
public async updateIsDeleted(
@Request() req: RequestWithUser,
@Path() abilityId: string,
) {
const record = await this.profileAbilityRepo.findOneBy({ id: abilityId });
if (!record) throw new HttpError(HttpStatus.NOT_FOUND, "ไม่พบข้อมูล");
if (record.isDeleted === true) {
return new HttpSuccess();
}
await new permission().PermissionDelete(req, "SYS_REGISTRY_TEMP");
const before = structuredClone(record);
const history = new ProfileAbilityHistory();
const now = new Date();
record.isDeleted = true;
record.lastUpdateUserId = req.user.sub;
record.lastUpdateFullName = req.user.name;
record.lastUpdatedAt = now;
Object.assign(history, { ...record, id: undefined });
history.profileAbilityId = abilityId;
history.createdUserId = req.user.sub;
history.createdFullName = req.user.name;
history.createdAt = now;
await Promise.all([
this.profileAbilityRepo.save(record, { data: req }),
setLogDataDiff(req, { before, after: record }),
this.profileAbilityHistoryRepo.save(history, { data: req }),
]);
return new HttpSuccess();
}
@Delete("{abilityId}")
public async deleteProfileAbility(@Path() abilityId: string, @Request() req: RequestWithUser) {
const _record = await this.profileAbilityRepo.findOneBy({ id: abilityId });

View file

@ -0,0 +1,277 @@
import {
Body,
Controller,
Get,
Patch,
Path,
Post,
Request,
Route,
Security,
Tags,
} from "tsoa";
import { AppDataSource } from "../database/data-source";
import { In } from "typeorm";
import {
ProfileAbsentLate,
CreateProfileAbsentLate,
CreateProfileAbsentLateBatch,
UpdateProfileAbsentLate,
} from "../entities/ProfileAbsentLate";
import { ProfileAbsentLateHistory } from "../entities/ProfileAbsentLateHistory";
import HttpSuccess from "../interfaces/http-success";
import HttpStatus from "../interfaces/http-status";
import HttpError from "../interfaces/http-error";
import { RequestWithUser } from "../middlewares/user";
import { Profile } from "../entities/Profile";
import permission from "../interfaces/permission";
import { setLogDataDiff } from "../interfaces/utils";
@Route("api/v1/org/profile/absent-late")
@Tags("ProfileAbsentLate")
@Security("bearerAuth")
export class ProfileAbsentLateController extends Controller {
private profileRepo = AppDataSource.getRepository(Profile);
private absentLateRepo = AppDataSource.getRepository(ProfileAbsentLate);
private historyRepo = AppDataSource.getRepository(ProfileAbsentLateHistory);
/**
* API / user
* @summary API / user
*/
@Get("user")
public async getAbsentLateUser(@Request() request: { user: Record<string, any> }) {
const profile = await this.profileRepo.findOneBy({ keycloak: request.user.sub });
if (!profile) {
throw new HttpError(HttpStatus.BAD_REQUEST, "ไม่พบ profile ดังกล่าว");
}
const record = await this.absentLateRepo.find({
where: { profileId: profile.id, isDeleted: false },
order: { stampDate: "DESC" },
});
return new HttpSuccess(record);
}
/**
* API / profileId
* @summary API / profileId
* @param profileId profile
*/
@Get("{profileId}")
public async getAbsentLate(@Path() profileId: string, @Request() req: RequestWithUser) {
let _workflow = await new permission().Workflow(req, profileId, "SYS_REGISTRY_OFFICER");
if (_workflow == false)
await new permission().PermissionOrgUserGet(req, "SYS_REGISTRY_OFFICER", profileId);
const record = await this.absentLateRepo.find({
where: { profileId, isDeleted: false },
order: { stampDate: "DESC" },
});
return new HttpSuccess(record);
}
/**
* API /
* @summary API /
*/
@Post()
public async newAbsentLate(
@Request() req: RequestWithUser,
@Body() body: CreateProfileAbsentLate,
) {
if (!body.profileId) {
throw new HttpError(HttpStatus.BAD_REQUEST, "กรุณากรอก profileId");
}
const profile = await this.profileRepo.findOneBy({ id: body.profileId });
if (!profile) {
throw new HttpError(HttpStatus.BAD_REQUEST, "ไม่พบ profile ดังกล่าว");
}
await new permission().PermissionOrgUserUpdate(req, "SYS_REGISTRY_OFFICER", profile.id);
const before = null;
const data = new ProfileAbsentLate();
const meta = {
createdUserId: req.user.sub,
createdFullName: req.user.name,
lastUpdateUserId: req.user.sub,
lastUpdateFullName: req.user.name,
createdAt: new Date(),
lastUpdatedAt: new Date(),
};
Object.assign(data, { ...body, ...meta });
// บันทึก history
const history = new ProfileAbsentLateHistory();
Object.assign(history, { ...data, id: undefined });
await this.absentLateRepo.save(data, { data: req });
setLogDataDiff(req, { before, after: data });
history.profileAbsentLateId = data.id;
await this.historyRepo.save(history, { data: req });
return new HttpSuccess(data.id);
}
/**
* API / ( Job)
* @summary API / ( Job)
*/
@Post("batch")
public async newAbsentLateBatch(
@Request() req: RequestWithUser,
@Body() body: CreateProfileAbsentLateBatch,
) {
// กรณีไม่มีข้อมูลส่งมา (วันที่ไม่มีคนขาด/มาสาย)
if (!body.records || body.records.length === 0) {
return new HttpSuccess({ count: 0, ids: [] });
}
const profileIds = [...new Set(body.records.map((r) => r.profileId))];
const profiles = await this.profileRepo.findBy({
id: In(profileIds),
});
const foundProfileIds = new Set(profiles.map((p) => p.id));
const validRecords = body.records.filter((r) => foundProfileIds.has(r.profileId));
// กรณีไม่พบ profile เลย
if (validRecords.length === 0) {
return new HttpSuccess({ count: 0, ids: [] });
}
const meta = {
createdUserId: req.user.sub,
createdFullName: req.user.name,
lastUpdateUserId: req.user.sub,
lastUpdateFullName: req.user.name,
createdAt: new Date(),
lastUpdatedAt: new Date(),
};
const records = validRecords.map((item) => {
const data = new ProfileAbsentLate();
Object.assign(data, { ...item, ...meta });
return data;
});
const result = await this.absentLateRepo.save(records, { data: req });
// บันทึก history สำหรับแต่ละ record
const historyRecords = result.map((data) => {
const history = new ProfileAbsentLateHistory();
Object.assign(history, { ...data, id: undefined });
history.profileAbsentLateId = data.id;
return history;
});
await this.historyRepo.save(historyRecords, { data: req });
return new HttpSuccess({ count: result.length, ids: result.map((r) => r.id) });
}
/**
* API /
* @summary API /
* @param absentLateId /
*/
@Patch("{absentLateId}")
public async editAbsentLate(
@Request() req: RequestWithUser,
@Body() body: UpdateProfileAbsentLate,
@Path() absentLateId: string,
) {
const record = await this.absentLateRepo.findOneBy({ id: absentLateId });
if (!record) throw new HttpError(HttpStatus.NOT_FOUND, "ไม่พบข้อมูล");
await new permission().PermissionOrgUserUpdate(
req,
"SYS_REGISTRY_OFFICER",
record.profileId,
);
const before = structuredClone(record);
const history = new ProfileAbsentLateHistory();
Object.assign(history, { ...record, id: undefined });
Object.assign(record, body);
Object.assign(history, { ...record, id: undefined });
history.profileAbsentLateId = absentLateId;
record.lastUpdateUserId = req.user.sub;
record.lastUpdateFullName = req.user.name;
record.lastUpdatedAt = new Date();
history.lastUpdateUserId = req.user.sub;
history.lastUpdateFullName = req.user.name;
history.createdUserId = req.user.sub;
history.createdFullName = req.user.name;
history.createdAt = new Date();
history.lastUpdatedAt = new Date();
await Promise.all([
this.absentLateRepo.save(record, { data: req }),
setLogDataDiff(req, { before, after: record }),
this.historyRepo.save(history, { data: req }),
]);
return new HttpSuccess();
}
/**
* API / (Soft Delete)
* @summary API / (Soft Delete)
* @param absentLateId /
*/
@Patch("update-delete/{absentLateId}")
public async updateIsDeleted(
@Request() req: RequestWithUser,
@Path() absentLateId: string,
) {
const record = await this.absentLateRepo.findOneBy({ id: absentLateId });
if (!record) throw new HttpError(HttpStatus.NOT_FOUND, "ไม่พบข้อมูล");
if (record.isDeleted === true) {
return new HttpSuccess();
}
await new permission().PermissionOrgUserDelete(req, "SYS_REGISTRY_OFFICER", record.profileId);
const before = structuredClone(record);
const history = new ProfileAbsentLateHistory();
Object.assign(history, { ...record, id: undefined });
record.isDeleted = true;
record.lastUpdateUserId = req.user.sub;
record.lastUpdateFullName = req.user.name;
record.lastUpdatedAt = new Date();
history.profileAbsentLateId = absentLateId;
history.isDeleted = true;
history.lastUpdateUserId = req.user.sub;
history.lastUpdateFullName = req.user.name;
history.lastUpdatedAt = new Date();
await Promise.all([
this.absentLateRepo.save(record, { data: req }),
setLogDataDiff(req, { before, after: record }),
this.historyRepo.save(history, { data: req }),
]);
return new HttpSuccess();
}
/**
* API /
* @summary API /
* @param absentLateId /
*/
@Get("history/{absentLateId}")
public async getHistory(@Path() absentLateId: string, @Request() req: RequestWithUser) {
const record = await this.absentLateRepo.findOneBy({ id: absentLateId });
if (!record) throw new HttpError(HttpStatus.NOT_FOUND, "ไม่พบข้อมูล");
await new permission().PermissionOrgUserGet(req, "SYS_REGISTRY_OFFICER", record.profileId);
const history = await this.historyRepo.find({
where: { profileAbsentLateId: absentLateId },
order: { createdAt: "DESC" },
});
return new HttpSuccess(history);
}
}

View file

@ -40,7 +40,7 @@ export class ProfileActpositionController extends Controller {
throw new HttpError(HttpStatus.BAD_REQUEST, "ไม่พบ profile ดังกล่าว");
}
const getProfileActpositionId = await this.profileActpositionRepo.find({
where: { profileId: profile.id },
where: { profileId: profile.id, isDeleted: false },
order: { createdAt: "ASC" },
});
if (!getProfileActpositionId) {
@ -58,7 +58,7 @@ export class ProfileActpositionController extends Controller {
if (_workflow == false)
await new permission().PermissionOrgUserGet(req, "SYS_REGISTRY_OFFICER", profileId);
const getProfileActpositionId = await this.profileActpositionRepo.find({
where: { profileId: profileId },
where: { profileId: profileId, isDeleted: false },
order: { createdAt: "ASC" },
});
if (!getProfileActpositionId) {
@ -201,6 +201,44 @@ export class ProfileActpositionController extends Controller {
return new HttpSuccess();
}
/**
* API
* @summary API
* @param actpositionId
*/
@Patch("update-delete/{actpositionId}")
public async updateIsDeletedTraining(
@Request() req: RequestWithUser,
@Path() actpositionId: string,
) {
const record = await this.profileActpositionRepo.findOneBy({ id: actpositionId });
if (!record) throw new HttpError(HttpStatus.NOT_FOUND, "ไม่พบข้อมูล");
if (record.isDeleted === true) {
return new HttpSuccess();
}
await new permission().PermissionOrgUserDelete(req, "SYS_REGISTRY_OFFICER", record.profileId);
const before = structuredClone(record);
const history = new ProfileActpositionHistory();
const now = new Date();
record.isDeleted = true;
record.lastUpdateUserId = req.user.sub;
record.lastUpdateFullName = req.user.name;
record.lastUpdatedAt = now;
Object.assign(history, { ...record, id: undefined });
history.createdUserId = req.user.sub;
history.createdFullName = req.user.name;
history.createdAt = now;
await Promise.all([
this.profileActpositionRepo.save(record, { data: req }),
setLogDataDiff(req, { before, after: record }),
this.profileActpositionHistoryRepo.save(history, { data: req }),
]);
return new HttpSuccess();
}
@Delete("{actpositionId}")
public async deleteProfileActposition(
@Path() actpositionId: string,

View file

@ -40,7 +40,7 @@ export class ProfileActpositionEmployeeController extends Controller {
throw new HttpError(HttpStatus.BAD_REQUEST, "ไม่พบ profile ดังกล่าว");
}
const getProfileActpositionId = await this.profileActpositionRepo.find({
where: { profileEmployeeId: profile.id },
where: { profileEmployeeId: profile.id, isDeleted: false },
order: { createdAt: "ASC" },
});
if (!getProfileActpositionId) {
@ -58,7 +58,7 @@ export class ProfileActpositionEmployeeController extends Controller {
if (_workflow == false)
await new permission().PermissionOrgUserGet(req, "SYS_REGISTRY_EMP", profileEmployeeId);
const getProfileActpositionId = await this.profileActpositionRepo.find({
where: { profileEmployeeId: profileEmployeeId },
where: { profileEmployeeId: profileEmployeeId, isDeleted: false },
order: { createdAt: "ASC" },
});
if (!getProfileActpositionId) {

View file

@ -40,7 +40,7 @@ export class ProfileActpositionEmployeeTempController extends Controller {
throw new HttpError(HttpStatus.BAD_REQUEST, "ไม่พบ profile ดังกล่าว");
}
const getProfileActpositionId = await this.profileActpositionRepo.find({
where: { profileEmployeeId: profile.id },
where: { profileEmployeeId: profile.id, isDeleted: false },
order: { createdAt: "ASC" },
});
if (!getProfileActpositionId) {
@ -57,7 +57,7 @@ export class ProfileActpositionEmployeeTempController extends Controller {
let _workflow = await new permission().Workflow(req, profileEmployeeId, "SYS_REGISTRY_TEMP");
if (_workflow == false) await new permission().PermissionGet(req, "SYS_REGISTRY_TEMP");
const getProfileActpositionId = await this.profileActpositionRepo.find({
where: { profileEmployeeId: profileEmployeeId },
where: { profileEmployeeId: profileEmployeeId, isDeleted: false },
order: { createdAt: "ASC" },
});
if (!getProfileActpositionId) {

View file

@ -41,7 +41,7 @@ export class ProfileAssessmentsController extends Controller {
throw new HttpError(HttpStatus.BAD_REQUEST, "ไม่พบ profile ดังกล่าว");
}
const getProfileAssessments = await this.profileAssessmentsRepository.find({
where: { profileId: profile.id },
where: { profileId: profile.id, isDeleted: false },
order: { createdAt: "ASC" },
});
if (!getProfileAssessments) {
@ -59,7 +59,7 @@ export class ProfileAssessmentsController extends Controller {
if (_workflow == false)
await new permission().PermissionOrgUserGet(req, "SYS_REGISTRY_OFFICER", profileId);
const getProfileAssessments = await this.profileAssessmentsRepository.find({
where: { profileId: profileId },
where: { profileId: profileId, isDeleted: false },
order: { createdAt: "ASC" },
});
if (!getProfileAssessments) {
@ -186,6 +186,45 @@ export class ProfileAssessmentsController extends Controller {
return new HttpSuccess();
}
/**
* API
* @summary API
* @param assessmentId
*/
@Patch("update-delete/{assessmentId}")
public async updateIsDeleted(
@Request() req: RequestWithUser,
@Path() assessmentId: string,
) {
const record = await this.profileAssessmentsRepository.findOneBy({ id: assessmentId });
if (!record) throw new HttpError(HttpStatus.NOT_FOUND, "ไม่พบข้อมูล");
if (record.isDeleted === true) {
return new HttpSuccess();
}
await new permission().PermissionOrgUserDelete(req, "SYS_REGISTRY_OFFICER", record.profileId);
const before = structuredClone(record);
const history = new ProfileAssessmentHistory();
const now = new Date();
record.isDeleted = true;
record.lastUpdateUserId = req.user.sub;
record.lastUpdateFullName = req.user.name;
record.lastUpdatedAt = now;
Object.assign(history, { ...record, id: undefined });
history.profileAssessmentId = assessmentId;
history.createdUserId = req.user.sub;
history.createdFullName = req.user.name;
history.createdAt = now;
await Promise.all([
this.profileAssessmentsRepository.save(record, { data: req }),
setLogDataDiff(req, { before, after: record }),
this.profileAssessmentsHistoryRepository.save(history, { data: req }),
]);
return new HttpSuccess();
}
@Delete("{assessmentId}")
public async deleteProfileAssessment(
@Path() assessmentId: string,

View file

@ -41,7 +41,7 @@ export class ProfileAssessmentsEmployeeController extends Controller {
throw new HttpError(HttpStatus.BAD_REQUEST, "ไม่พบ profile ดังกล่าว");
}
const getProfileAssessments = await this.profileAssessmentsRepository.find({
where: { profileEmployeeId: profile.id },
where: { profileEmployeeId: profile.id, isDeleted: false },
order: { createdAt: "ASC" },
});
if (!getProfileAssessments) {
@ -61,6 +61,7 @@ export class ProfileAssessmentsEmployeeController extends Controller {
const getProfileAssessments = await this.profileAssessmentsRepository.find({
where: {
profileEmployeeId: profileEmployeeId,
isDeleted: false
},
order: { createdAt: "ASC" },
});
@ -192,6 +193,45 @@ export class ProfileAssessmentsEmployeeController extends Controller {
return new HttpSuccess();
}
/**
* API
* @summary API
* @param assessmentId
*/
@Patch("update-delete/{assessmentId}")
public async updateIsDeleted(
@Request() req: RequestWithUser,
@Path() assessmentId: string,
) {
const record = await this.profileAssessmentsRepository.findOneBy({ id: assessmentId });
if (!record) throw new HttpError(HttpStatus.NOT_FOUND, "ไม่พบข้อมูล");
if (record.isDeleted === true) {
return new HttpSuccess();
}
await new permission().PermissionOrgUserDelete(req, "SYS_REGISTRY_EMP", record.profileEmployeeId);
const before = structuredClone(record);
const history = new ProfileAssessmentHistory();
const now = new Date();
record.isDeleted = true;
record.lastUpdateUserId = req.user.sub;
record.lastUpdateFullName = req.user.name;
record.lastUpdatedAt = now;
Object.assign(history, { ...record, id: undefined });
history.profileAssessmentId = assessmentId;
history.createdUserId = req.user.sub;
history.createdFullName = req.user.name;
history.createdAt = now;
await Promise.all([
this.profileAssessmentsRepository.save(record, { data: req }),
setLogDataDiff(req, { before, after: record }),
this.profileAssessmentsHistoryRepository.save(history, { data: req }),
]);
return new HttpSuccess();
}
@Delete("{assessmentId}")
public async deleteProfileAssessment(
@Path() assessmentId: string,

View file

@ -41,7 +41,7 @@ export class ProfileAssessmentsEmployeeTempController extends Controller {
throw new HttpError(HttpStatus.BAD_REQUEST, "ไม่พบ profile ดังกล่าว");
}
const getProfileAssessments = await this.profileAssessmentsRepository.find({
where: { profileEmployeeId: profile.id },
where: { profileEmployeeId: profile.id, isDeleted: false },
order: { createdAt: "ASC" },
});
if (!getProfileAssessments) {
@ -60,6 +60,7 @@ export class ProfileAssessmentsEmployeeTempController extends Controller {
const getProfileAssessments = await this.profileAssessmentsRepository.find({
where: {
profileEmployeeId: profileEmployeeId,
isDeleted: false
},
order: { createdAt: "ASC" },
});
@ -180,6 +181,45 @@ export class ProfileAssessmentsEmployeeTempController extends Controller {
return new HttpSuccess();
}
/**
* API
* @summary API
* @param assessmentId
*/
@Patch("update-delete/{assessmentId}")
public async updateIsDeleted(
@Request() req: RequestWithUser,
@Path() assessmentId: string,
) {
const record = await this.profileAssessmentsRepository.findOneBy({ id: assessmentId });
if (!record) throw new HttpError(HttpStatus.NOT_FOUND, "ไม่พบข้อมูล");
if (record.isDeleted === true) {
return new HttpSuccess();
}
await new permission().PermissionDelete(req, "SYS_REGISTRY_TEMP");
const before = structuredClone(record);
const history = new ProfileAssessmentHistory();
const now = new Date();
record.isDeleted = true;
record.lastUpdateUserId = req.user.sub;
record.lastUpdateFullName = req.user.name;
record.lastUpdatedAt = now;
Object.assign(history, { ...record, id: undefined });
history.profileAssessmentId = assessmentId;
history.createdUserId = req.user.sub;
history.createdFullName = req.user.name;
history.createdAt = now;
await Promise.all([
this.profileAssessmentsRepository.save(record, { data: req }),
setLogDataDiff(req, { before, after: record }),
this.profileAssessmentsHistoryRepository.save(history, { data: req }),
]);
return new HttpSuccess();
}
@Delete("{assessmentId}")
public async deleteProfileAssessment(
@Path() assessmentId: string,

View file

@ -40,7 +40,7 @@ export class ProfileAssistanceController extends Controller {
throw new HttpError(HttpStatus.BAD_REQUEST, "ไม่พบ profile ดังกล่าว");
}
const getProfileAssistanceId = await this.profileAssistanceRepo.find({
where: { profileId: profile.id },
where: { profileId: profile.id, isDeleted: false },
order: { createdAt: "ASC" },
});
if (!getProfileAssistanceId) {
@ -55,7 +55,7 @@ export class ProfileAssistanceController extends Controller {
// if (_workflow == false)
// await new permission().PermissionOrgUserGet(req, "SYS_REGISTRY_OFFICER", profileId);
const getProfileAssistanceId = await this.profileAssistanceRepo.find({
where: { profileId: profileId },
where: { profileId: profileId, isDeleted: false },
order: { createdAt: "ASC" },
});
if (!getProfileAssistanceId) {
@ -175,6 +175,45 @@ export class ProfileAssistanceController extends Controller {
return new HttpSuccess();
}
/**
* API
* @summary API
* @param assistanceId
*/
@Patch("update-delete/{assistanceId}")
public async updateIsDeleted(
@Request() req: RequestWithUser,
@Path() assistanceId: string,
) {
const record = await this.profileAssistanceRepo.findOneBy({ id: assistanceId });
if (!record) throw new HttpError(HttpStatus.NOT_FOUND, "ไม่พบข้อมูล");
if (record.isDeleted === true) {
return new HttpSuccess();
}
await new permission().PermissionOrgUserDelete(req, "SYS_REGISTRY_OFFICER", record.profileId);
const before = structuredClone(record);
const history = new ProfileAssistanceHistory();
const now = new Date();
record.isDeleted = true;
record.lastUpdateUserId = req.user.sub;
record.lastUpdateFullName = req.user.name;
record.lastUpdatedAt = now;
Object.assign(history, { ...record, id: undefined });
history.profileAssistanceId = assistanceId;
history.createdUserId = req.user.sub;
history.createdFullName = req.user.name;
history.createdAt = now;
await Promise.all([
this.profileAssistanceRepo.save(record, { data: req }),
setLogDataDiff(req, { before, after: record }),
this.profileAssistanceHistoryRepo.save(history, { data: req }),
]);
return new HttpSuccess();
}
@Delete("{assistanceId}")
public async deleteProfileAssistance(
@Path() assistanceId: string,

View file

@ -40,7 +40,7 @@ export class ProfileAssistanceEmployeeController extends Controller {
throw new HttpError(HttpStatus.BAD_REQUEST, "ไม่พบ profile ดังกล่าว");
}
const getProfileAssistanceId = await this.profileAssistanceRepo.find({
where: { profileEmployeeId: profile.id },
where: { profileEmployeeId: profile.id, isDeleted: false },
order: { createdAt: "ASC" },
});
if (!getProfileAssistanceId) {
@ -58,7 +58,7 @@ export class ProfileAssistanceEmployeeController extends Controller {
if (_workflow == false)
await new permission().PermissionOrgUserGet(req, "SYS_REGISTRY_EMP", profileEmployeeId);
const getProfileAssistanceId = await this.profileAssistanceRepo.find({
where: { profileEmployeeId: profileEmployeeId },
where: { profileEmployeeId: profileEmployeeId, isDeleted: false },
order: { createdAt: "ASC" },
});
if (!getProfileAssistanceId) {
@ -183,6 +183,46 @@ export class ProfileAssistanceEmployeeController extends Controller {
return new HttpSuccess();
}
/**
* API
* @summary API
* @param assistanceId
*/
@Patch("update-delete/{assistanceId}")
public async updateIsDeleted(
@Request() req: RequestWithUser,
@Path() assistanceId: string,
) {
const record = await this.profileAssistanceRepo.findOneBy({ id: assistanceId });
if (!record) throw new HttpError(HttpStatus.NOT_FOUND, "ไม่พบข้อมูล");
if (record.isDeleted === true) {
return new HttpSuccess();
}
await new permission().PermissionOrgUserDelete(req, "SYS_REGISTRY_EMP", record.profileEmployeeId);
const before = structuredClone(record);
const history = new ProfileAssistanceHistory();
const now = new Date();
record.isDeleted = true;
record.lastUpdateUserId = req.user.sub;
record.lastUpdateFullName = req.user.name;
record.lastUpdatedAt = now;
Object.assign(history, { ...record, id: undefined });
history.profileAssistanceId = assistanceId;
history.createdUserId = req.user.sub;
history.createdFullName = req.user.name;
history.createdAt = now;
await Promise.all([
this.profileAssistanceRepo.save(record, { data: req }),
setLogDataDiff(req, { before, after: record }),
this.profileAssistanceHistoryRepo.save(history, { data: req }),
]);
return new HttpSuccess();
}
@Delete("{assistanceId}")
public async deleteProfileAssistance(
@Path() assistanceId: string,

View file

@ -40,7 +40,7 @@ export class ProfileAssistanceEmployeeTempController extends Controller {
throw new HttpError(HttpStatus.BAD_REQUEST, "ไม่พบ profile ดังกล่าว");
}
const getProfileAssistanceId = await this.profileAssistanceRepo.find({
where: { profileEmployeeId: profile.id },
where: { profileEmployeeId: profile.id, isDeleted: false },
order: { createdAt: "ASC" },
});
if (!getProfileAssistanceId) {
@ -57,7 +57,7 @@ export class ProfileAssistanceEmployeeTempController extends Controller {
let _workflow = await new permission().Workflow(req, profileEmployeeId, "SYS_REGISTRY_TEMP");
if (_workflow == false) await new permission().PermissionGet(req, "SYS_REGISTRY_TEMP");
const getProfileAssistanceId = await this.profileAssistanceRepo.find({
where: { profileEmployeeId: profileEmployeeId },
where: { profileEmployeeId: profileEmployeeId, isDeleted: false },
order: { createdAt: "ASC" },
});
if (!getProfileAssistanceId) {
@ -173,6 +173,45 @@ export class ProfileAssistanceEmployeeTempController extends Controller {
return new HttpSuccess();
}
/**
* API
* @summary API
* @param assistanceId
*/
@Patch("update-delete/{assistanceId}")
public async updateIsDeleted(
@Request() req: RequestWithUser,
@Path() assistanceId: string,
) {
const record = await this.profileAssistanceRepo.findOneBy({ id: assistanceId });
if (!record) throw new HttpError(HttpStatus.NOT_FOUND, "ไม่พบข้อมูล");
if (record.isDeleted === true) {
return new HttpSuccess();
}
await new permission().PermissionDelete(req, "SYS_REGISTRY_TEMP");
const before = structuredClone(record);
const history = new ProfileAssistanceHistory();
const now = new Date();
record.isDeleted = true;
record.lastUpdateUserId = req.user.sub;
record.lastUpdateFullName = req.user.name;
record.lastUpdatedAt = now;
Object.assign(history, { ...record, id: undefined });
history.profileAssistanceId = assistanceId;
history.createdUserId = req.user.sub;
history.createdFullName = req.user.name;
history.createdAt = now;
await Promise.all([
this.profileAssistanceRepo.save(record, { data: req }),
setLogDataDiff(req, { before, after: record }),
this.profileAssistanceHistoryRepo.save(history, { data: req }),
]);
return new HttpSuccess();
}
@Delete("{assistanceId}")
public async deleteProfileAssistance(
@Path() assistanceId: string,

View file

@ -8,6 +8,7 @@ import { Profile } from "../entities/Profile";
import { CreateProfileAvatar, ProfileAvatar } from "../entities/ProfileAvatar";
import permission from "../interfaces/permission";
import { setLogDataDiff } from "../interfaces/utils";
import CallAPI from "../interfaces/call-api";
@Route("api/v1/org/profile/avatar")
@Tags("ProfileAvatar")
@Security("bearerAuth")
@ -158,10 +159,24 @@ export class ProfileAvatarController extends Controller {
"SYS_REGISTRY_OFFICER",
_record.profileId,
);
if (_record.isActive) {
const profile = await this.profileRepository.findOne({
where: { id: _record.profileId },
});
if (profile) {
profile.avatar = null;
profile.avatarName = null;
await this.profileRepository.save(profile, { data: req });
}
}
await new CallAPI().DeleteFile(req, `${_record?.avatar}/${_record?.avatarName}`);
}
if (!_record) {
throw new HttpError(HttpStatus.NOT_FOUND, "ไม่พบข้อมูล");
}
await this.avatarRepository.remove(_record, { data: req });
return new HttpSuccess();

View file

@ -8,6 +8,8 @@ import { CreateProfileEmployeeAvatar, ProfileAvatar } from "../entities/ProfileA
import { ProfileEmployee } from "../entities/ProfileEmployee";
import permission from "../interfaces/permission";
import { setLogDataDiff } from "../interfaces/utils";
import CallAPI from "../interfaces/call-api";
@Route("api/v1/org/profile-employee/avatar")
@Tags("ProfileAvatar")
@Security("bearerAuth")
@ -153,6 +155,18 @@ export class ProfileAvatarEmployeeController extends Controller {
"SYS_REGISTRY_EMP",
_record.profileEmployeeId,
);
if (_record.isActive) {
const profile = await this.profileRepository.findOne({
where: { id: _record.profileEmployeeId },
});
if (profile) {
profile.avatar = null;
profile.avatarName = null;
await this.profileRepository.save(profile, { data: req });
}
}
await new CallAPI().DeleteFile(req, `${_record?.avatar}/${_record?.avatarName}`);
}
if (!_record) {
throw new HttpError(HttpStatus.NOT_FOUND, "ไม่พบข้อมูล");

View file

@ -8,6 +8,7 @@ import { CreateProfileEmployeeAvatar, ProfileAvatar } from "../entities/ProfileA
import { ProfileEmployee } from "../entities/ProfileEmployee";
import permission from "../interfaces/permission";
import { setLogDataDiff } from "../interfaces/utils";
import CallAPI from "../interfaces/call-api";
@Route("api/v1/org/profile-temp/avatar")
@Tags("ProfileAvatar")
@Security("bearerAuth")
@ -147,6 +148,19 @@ export class ProfileAvatarEmployeeTempController extends Controller {
public async deleteAvatarEmployee(@Path() avatarId: string, @Request() req: RequestWithUser) {
await new permission().PermissionDelete(req, "SYS_REGISTRY_TEMP");
const _record = await this.avatarRepository.findOneBy({ id: avatarId });
if (_record) {
if (_record.isActive) {
const profile = await this.profileRepository.findOne({
where: { id: _record.profileEmployeeId },
});
if (profile) {
profile.avatar = "";
profile.avatarName = "";
await this.profileRepository.save(profile, { data: req });
}
}
}
await new CallAPI().DeleteFile(req, `${_record?.avatar}/${_record?.avatarName}`);
if (!_record) {
throw new HttpError(HttpStatus.NOT_FOUND, "ไม่พบข้อมูล");
}

View file

@ -40,7 +40,7 @@ export class ProfileCertificateController extends Controller {
throw new HttpError(HttpStatus.BAD_REQUEST, "ไม่พบ profile ดังกล่าว");
}
const record = await this.certificateRepo.find({
where: { profileId: profile.id },
where: { profileId: profile.id, isDeleted: false },
order: { createdAt: "ASC" },
});
return new HttpSuccess(record);
@ -52,7 +52,7 @@ export class ProfileCertificateController extends Controller {
if (_workflow == false)
await new permission().PermissionOrgUserGet(req, "SYS_REGISTRY_OFFICER", profileId);
const record = await this.certificateRepo.find({
where: { profileId: profileId },
where: { profileId: profileId, isDeleted: false },
order: { createdAt: "ASC" },
});
return new HttpSuccess(record);
@ -166,6 +166,45 @@ export class ProfileCertificateController extends Controller {
return new HttpSuccess();
}
/**
* API
* @summary API
* @param certificateId
*/
@Patch("update-delete/{certificateId}")
public async updateIsDeleted(
@Request() req: RequestWithUser,
@Path() certificateId: string,
) {
const record = await this.certificateRepo.findOneBy({ id: certificateId });
if (!record) throw new HttpError(HttpStatus.NOT_FOUND, "ไม่พบข้อมูล");
if (record.isDeleted === true) {
return new HttpSuccess();
}
await new permission().PermissionOrgUserDelete(req, "SYS_REGISTRY_OFFICER", record.profileId);
const before = structuredClone(record);
const history = new ProfileCertificateHistory();
const now = new Date();
record.isDeleted = true;
record.lastUpdateUserId = req.user.sub;
record.lastUpdateFullName = req.user.name;
record.lastUpdatedAt = now;
Object.assign(history, { ...record, id: undefined });
history.profileCertificateId = certificateId;
history.createdUserId = req.user.sub;
history.createdFullName = req.user.name;
history.createdAt = now;
await Promise.all([
this.certificateRepo.save(record, { data: req }),
setLogDataDiff(req, { before, after: record }),
this.certificateHistoryRepo.save(history, { data: req }),
]);
return new HttpSuccess();
}
@Delete("{certificateId}")
public async deleteCertificate(@Path() certificateId: string, @Request() req: RequestWithUser) {
const _record = await this.certificateRepo.findOneBy({ id: certificateId });

View file

@ -40,7 +40,7 @@ export class ProfileCertificateEmployeeController extends Controller {
throw new HttpError(HttpStatus.BAD_REQUEST, "ไม่พบ profile ดังกล่าว");
}
const record = await this.certificateRepo.find({
where: { profileEmployeeId: profile.id },
where: { profileEmployeeId: profile.id, isDeleted: false },
order: { createdAt: "ASC" },
});
return new HttpSuccess(record);
@ -52,7 +52,7 @@ export class ProfileCertificateEmployeeController extends Controller {
if (_workflow == false)
await new permission().PermissionOrgUserGet(req, "SYS_REGISTRY_EMP", profileEmployeeId);
const record = await this.certificateRepo.find({
where: { profileEmployeeId: profileEmployeeId },
where: { profileEmployeeId: profileEmployeeId, isDeleted: false },
order: { createdAt: "ASC" },
});
return new HttpSuccess(record);
@ -174,6 +174,45 @@ export class ProfileCertificateEmployeeController extends Controller {
return new HttpSuccess();
}
/**
* API
* @summary API
* @param certificateId
*/
@Patch("update-delete/{certificateId}")
public async updateIsDeleted(
@Request() req: RequestWithUser,
@Path() certificateId: string,
) {
const record = await this.certificateRepo.findOneBy({ id: certificateId });
if (!record) throw new HttpError(HttpStatus.NOT_FOUND, "ไม่พบข้อมูล");
if (record.isDeleted === true) {
return new HttpSuccess();
}
await new permission().PermissionOrgUserDelete(req, "SYS_REGISTRY_EMP", record.profileEmployeeId);
const before = structuredClone(record);
const history = new ProfileCertificateHistory();
const now = new Date();
record.isDeleted = true;
record.lastUpdateUserId = req.user.sub;
record.lastUpdateFullName = req.user.name;
record.lastUpdatedAt = now;
Object.assign(history, { ...record, id: undefined });
history.profileCertificateId = certificateId;
history.createdUserId = req.user.sub;
history.createdFullName = req.user.name;
history.createdAt = now;
await Promise.all([
this.certificateRepo.save(record, { data: req }),
setLogDataDiff(req, { before, after: record }),
this.certificateHistoryRepo.save(history, { data: req }),
]);
return new HttpSuccess();
}
@Delete("{certificateId}")
public async deleteCertificate(@Path() certificateId: string, @Request() req: RequestWithUser) {
const _record = await this.certificateRepo.findOneBy({ id: certificateId });

View file

@ -40,7 +40,7 @@ export class ProfileCertificateEmployeeTempController extends Controller {
throw new HttpError(HttpStatus.BAD_REQUEST, "ไม่พบ profile ดังกล่าว");
}
const record = await this.certificateRepo.find({
where: { profileEmployeeId: profile.id },
where: { profileEmployeeId: profile.id, isDeleted: false },
order: { createdAt: "ASC" },
});
return new HttpSuccess(record);
@ -51,7 +51,7 @@ export class ProfileCertificateEmployeeTempController extends Controller {
let _workflow = await new permission().Workflow(req, profileEmployeeId, "SYS_REGISTRY_TEMP");
if (_workflow == false) await new permission().PermissionGet(req, "SYS_REGISTRY_TEMP");
const record = await this.certificateRepo.find({
where: { profileEmployeeId },
where: { profileEmployeeId, isDeleted: false },
order: { createdAt: "ASC" },
});
return new HttpSuccess(record);
@ -162,6 +162,45 @@ export class ProfileCertificateEmployeeTempController extends Controller {
return new HttpSuccess();
}
/**
* API
* @summary API
* @param certificateId
*/
@Patch("update-delete/{certificateId}")
public async updateIsDeleted(
@Request() req: RequestWithUser,
@Path() certificateId: string,
) {
const record = await this.certificateRepo.findOneBy({ id: certificateId });
if (!record) throw new HttpError(HttpStatus.NOT_FOUND, "ไม่พบข้อมูล");
if (record.isDeleted === true) {
return new HttpSuccess();
}
await new permission().PermissionDelete(req, "SYS_REGISTRY_TEMP");
const before = structuredClone(record);
const history = new ProfileCertificateHistory();
const now = new Date();
record.isDeleted = true;
record.lastUpdateUserId = req.user.sub;
record.lastUpdateFullName = req.user.name;
record.lastUpdatedAt = now;
Object.assign(history, { ...record, id: undefined });
history.profileCertificateId = certificateId;
history.createdUserId = req.user.sub;
history.createdFullName = req.user.name;
history.createdAt = now;
await Promise.all([
this.certificateRepo.save(record, { data: req }),
setLogDataDiff(req, { before, after: record }),
this.certificateHistoryRepo.save(history, { data: req }),
]);
return new HttpSuccess();
}
@Delete("{certificateId}")
public async deleteCertificate(@Path() certificateId: string, @Request() req: RequestWithUser) {
await new permission().PermissionDelete(req, "SYS_REGISTRY_TEMP");

View file

@ -41,7 +41,7 @@ export class ProfileChangeNameController extends Controller {
throw new HttpError(HttpStatus.BAD_REQUEST, "ไม่พบ profile ดังกล่าว");
}
const lists = await this.changeNameRepository.find({
where: { profileId: profile.id },
where: { profileId: profile.id, isDeleted: false },
order: { createdAt: "ASC" },
});
return new HttpSuccess(lists);
@ -53,7 +53,7 @@ export class ProfileChangeNameController extends Controller {
if (_workflow == false)
await new permission().PermissionOrgUserGet(req, "SYS_REGISTRY_OFFICER", profileId);
const lists = await this.changeNameRepository.find({
where: { profileId: profileId },
where: { profileId: profileId, isDeleted: false },
order: { createdAt: "ASC" },
});
return new HttpSuccess(lists);
@ -115,8 +115,13 @@ export class ProfileChangeNameController extends Controller {
await this.profileRepository.save(profile, { data: req });
setLogDataDiff(req, { before, after: profile });
if (profile != null && profile.keycloak != null) {
const result = await updateName(profile.keycloak, profile.firstName, profile.lastName);
if (profile != null && profile.keycloak != null && profile.isDelete === false) {
const result = await updateName(
profile.keycloak,
profile.firstName,
profile.lastName,
profile.prefix,
);
if (!result) {
throw new Error(result.errorMessage);
}
@ -181,8 +186,13 @@ export class ProfileChangeNameController extends Controller {
}
// ปิดไว้ก่อนเพราะ error ต้องใช้ keycloak ที่มีสิทธิ์ในการ update //update 17/07
if (profile != null && profile.keycloak != null) {
const result = await updateName(profile.keycloak, profile.firstName, profile.lastName);
if (profile != null && profile.keycloak != null && profile.isDelete === false) {
const result = await updateName(
profile.keycloak,
profile.firstName,
profile.lastName,
profile.prefix,
);
if (!result) {
throw new Error(result.errorMessage);
}
@ -191,6 +201,42 @@ export class ProfileChangeNameController extends Controller {
return new HttpSuccess();
}
/**
* API -
* @summary API -
* @param trainingId -
*/
@Patch("update-delete/{changeNameId}")
public async updateIsDeleted(@Request() req: RequestWithUser, @Path() changeNameId: string) {
const record = await this.changeNameRepository.findOneBy({ id: changeNameId });
if (!record) throw new HttpError(HttpStatus.NOT_FOUND, "ไม่พบข้อมูล");
if (record.isDeleted === true) {
return new HttpSuccess();
}
await new permission().PermissionOrgUserDelete(req, "SYS_REGISTRY_OFFICER", record.profileId);
const before = structuredClone(record);
const history = new ProfileChangeNameHistory();
const now = new Date();
record.isDeleted = true;
record.lastUpdateUserId = req.user.sub;
record.lastUpdateFullName = req.user.name;
record.lastUpdatedAt = now;
Object.assign(history, { ...record, id: undefined });
history.profileChangeNameId = changeNameId;
history.createdUserId = req.user.sub;
history.createdFullName = req.user.name;
history.createdAt = now;
await Promise.all([
this.changeNameRepository.save(record, { data: req }),
setLogDataDiff(req, { before, after: record }),
this.changeNameHistoryRepository.save(history, { data: req }),
]);
return new HttpSuccess();
}
@Delete("{changeNameId}")
public async deleteTraning(@Path() changeNameId: string, @Request() req: RequestWithUser) {
const _record = await this.changeNameRepository.findOneBy({ id: changeNameId });

View file

@ -41,7 +41,7 @@ export class ProfileChangeNameEmployeeController extends Controller {
throw new HttpError(HttpStatus.BAD_REQUEST, "ไม่พบ profile ดังกล่าว");
}
const lists = await this.changeNameRepository.find({
where: { profileEmployeeId: profile.id },
where: { profileEmployeeId: profile.id, isDeleted: false },
order: { createdAt: "ASC" },
});
return new HttpSuccess(lists);
@ -53,7 +53,7 @@ export class ProfileChangeNameEmployeeController extends Controller {
if (_workflow == false)
await new permission().PermissionOrgUserGet(req, "SYS_REGISTRY_EMP", profileEmployeeId);
const lists = await this.changeNameRepository.find({
where: { profileEmployeeId: profileEmployeeId },
where: { profileEmployeeId: profileEmployeeId, isDeleted: false },
order: { createdAt: "ASC" },
});
return new HttpSuccess(lists);
@ -121,8 +121,13 @@ export class ProfileChangeNameEmployeeController extends Controller {
await this.profileEmployeeRepo.save(profile, { data: req });
setLogDataDiff(req, { before, after: profile });
if (profile != null && profile.keycloak != null) {
const result = await updateName(profile.keycloak, profile.firstName, profile.lastName);
if (profile != null && profile.keycloak != null && profile.isDelete === false) {
const result = await updateName(
profile.keycloak,
profile.firstName,
profile.lastName,
profile.prefix,
);
if (!result) {
throw new Error(result.errorMessage);
}
@ -189,6 +194,46 @@ export class ProfileChangeNameEmployeeController extends Controller {
return new HttpSuccess();
}
/**
* API -
* @summary API -
* @param trainingId -
*/
@Patch("update-delete/{changeNameId}")
public async updateIsDeleted(@Request() req: RequestWithUser, @Path() changeNameId: string) {
const record = await this.changeNameRepository.findOneBy({ id: changeNameId });
if (!record) throw new HttpError(HttpStatus.NOT_FOUND, "ไม่พบข้อมูล");
if (record.isDeleted === true) {
return new HttpSuccess();
}
await new permission().PermissionOrgUserDelete(
req,
"SYS_REGISTRY_EMP",
record.profileEmployeeId,
);
const before = structuredClone(record);
const history = new ProfileChangeNameHistory();
const now = new Date();
record.isDeleted = true;
record.lastUpdateUserId = req.user.sub;
record.lastUpdateFullName = req.user.name;
record.lastUpdatedAt = now;
Object.assign(history, { ...record, id: undefined });
history.profileChangeNameId = changeNameId;
history.createdUserId = req.user.sub;
history.createdFullName = req.user.name;
history.createdAt = now;
await Promise.all([
this.changeNameRepository.save(record, { data: req }),
setLogDataDiff(req, { before, after: record }),
this.changeNameHistoryRepository.save(history, { data: req }),
]);
return new HttpSuccess();
}
@Delete("{changeNameId}")
public async deleteTraning(@Path() changeNameId: string, @Request() req: RequestWithUser) {
const _record = await this.changeNameRepository.findOneBy({ id: changeNameId });

View file

@ -41,7 +41,7 @@ export class ProfileChangeNameEmployeeTempController extends Controller {
throw new HttpError(HttpStatus.BAD_REQUEST, "ไม่พบ profile ดังกล่าว");
}
const lists = await this.changeNameRepository.find({
where: { profileEmployeeId: profile.id },
where: { profileEmployeeId: profile.id, isDeleted: false },
order: { createdAt: "ASC" },
});
return new HttpSuccess(lists);
@ -52,7 +52,7 @@ export class ProfileChangeNameEmployeeTempController extends Controller {
let _workflow = await new permission().Workflow(req, profileEmployeeId, "SYS_REGISTRY_TEMP");
if (_workflow == false) await new permission().PermissionGet(req, "SYS_REGISTRY_TEMP");
const lists = await this.changeNameRepository.find({
where: { profileEmployeeId: profileEmployeeId },
where: { profileEmployeeId: profileEmployeeId, isDeleted: false },
order: { createdAt: "ASC" },
});
return new HttpSuccess(lists);
@ -113,8 +113,13 @@ export class ProfileChangeNameEmployeeTempController extends Controller {
await this.profileEmployeeRepo.save(profile, { data: req });
setLogDataDiff(req, { before, after: profile });
if (profile != null && profile.keycloak != null) {
const result = await updateName(profile.keycloak, profile.firstName, profile.lastName);
if (profile != null && profile.keycloak != null && profile.isDelete === false) {
const result = await updateName(
profile.keycloak,
profile.firstName,
profile.lastName,
profile.prefix,
);
if (!result) {
throw new Error(result.errorMessage);
}
@ -181,6 +186,42 @@ export class ProfileChangeNameEmployeeTempController extends Controller {
return new HttpSuccess();
}
/**
* API -
* @summary API -
* @param trainingId -
*/
@Patch("update-delete/{changeNameId}")
public async updateIsDeleted(@Request() req: RequestWithUser, @Path() changeNameId: string) {
const record = await this.changeNameRepository.findOneBy({ id: changeNameId });
if (!record) throw new HttpError(HttpStatus.NOT_FOUND, "ไม่พบข้อมูล");
if (record.isDeleted === true) {
return new HttpSuccess();
}
await new permission().PermissionDelete(req, "SYS_REGISTRY_TEMP");
const before = structuredClone(record);
const history = new ProfileChangeNameHistory();
const now = new Date();
record.isDeleted = true;
record.lastUpdateUserId = req.user.sub;
record.lastUpdateFullName = req.user.name;
record.lastUpdatedAt = now;
Object.assign(history, { ...record, id: undefined });
history.profileChangeNameId = changeNameId;
history.createdUserId = req.user.sub;
history.createdFullName = req.user.name;
history.createdAt = now;
await Promise.all([
this.changeNameRepository.save(record, { data: req }),
setLogDataDiff(req, { before, after: record }),
this.changeNameHistoryRepository.save(history, { data: req }),
]);
return new HttpSuccess();
}
@Delete("{changeNameId}")
public async deleteTraning(@Path() changeNameId: string, @Request() req: RequestWithUser) {
await new permission().PermissionDelete(req, "SYS_REGISTRY_TEMP");

View file

@ -41,7 +41,7 @@ export class ProfileChildrenController extends Controller {
throw new HttpError(HttpStatus.BAD_REQUEST, "ไม่พบ profile ดังกล่าว");
}
const lists = await this.childrenRepository.find({
where: { profileId: profile.id },
where: { profileId: profile.id, isDeleted: false },
order: { createdAt: "ASC" },
});
return new HttpSuccess(lists);
@ -53,7 +53,7 @@ export class ProfileChildrenController extends Controller {
if (_workflow == false)
await new permission().PermissionOrgUserGet(req, "SYS_REGISTRY_OFFICER", profileId);
const lists = await this.childrenRepository.find({
where: { profileId: profileId },
where: { profileId: profileId, isDeleted: false },
order: { createdAt: "ASC" },
});
return new HttpSuccess(lists);
@ -143,6 +143,45 @@ export class ProfileChildrenController extends Controller {
return new HttpSuccess();
}
/**
* API
* @summary API
* @param trainingId
*/
@Patch("update-delete/{childrenId}")
public async updateIsDeleted(
@Request() req: RequestWithUser,
@Path() childrenId: string,
) {
const record = await this.childrenRepository.findOneBy({ id: childrenId });
if (!record) throw new HttpError(HttpStatus.NOT_FOUND, "ไม่พบข้อมูล");
if (record.isDeleted === true) {
return new HttpSuccess();
}
await new permission().PermissionOrgUserDelete(req, "SYS_REGISTRY_OFFICER", record.profileId);
const before = structuredClone(record);
const history = new ProfileChildrenHistory();
const now = new Date();
record.isDeleted = true;
record.lastUpdateUserId = req.user.sub;
record.lastUpdateFullName = req.user.name;
record.lastUpdatedAt = now;
Object.assign(history, { ...record, id: undefined });
history.profileChildrenId = childrenId;
history.createdUserId = req.user.sub;
history.createdFullName = req.user.name;
history.createdAt = now;
await Promise.all([
this.childrenRepository.save(record, { data: req }),
setLogDataDiff(req, { before, after: record }),
this.childrenHistoryRepository.save(history, { data: req }),
]);
return new HttpSuccess();
}
@Delete("{childrenId}")
public async deleteTraning(@Path() childrenId: string, @Request() req: RequestWithUser) {
const _record = await this.childrenRepository.findOneBy({ id: childrenId });

View file

@ -41,7 +41,7 @@ export class ProfileChildrenEmployeeController extends Controller {
throw new HttpError(HttpStatus.BAD_REQUEST, "ไม่พบ profile ดังกล่าว");
}
const lists = await this.childrenRepository.find({
where: { profileEmployeeId: profile.id },
where: { profileEmployeeId: profile.id, isDeleted: false },
order: { createdAt: "ASC" },
});
return new HttpSuccess(lists);
@ -53,7 +53,7 @@ export class ProfileChildrenEmployeeController extends Controller {
if (_workflow == false)
await new permission().PermissionOrgUserGet(req, "SYS_REGISTRY_EMP", profileEmployeeId);
const lists = await this.childrenRepository.find({
where: { profileEmployeeId: profileEmployeeId },
where: { profileEmployeeId: profileEmployeeId, isDeleted: false },
order: { createdAt: "ASC" },
});
return new HttpSuccess(lists);
@ -156,6 +156,45 @@ export class ProfileChildrenEmployeeController extends Controller {
return new HttpSuccess();
}
/**
* API
* @summary API
* @param trainingId
*/
@Patch("update-delete/{childrenId}")
public async updateIsDeleted(
@Request() req: RequestWithUser,
@Path() childrenId: string,
) {
const record = await this.childrenRepository.findOneBy({ id: childrenId });
if (!record) throw new HttpError(HttpStatus.NOT_FOUND, "ไม่พบข้อมูล");
if (record.isDeleted === true) {
return new HttpSuccess();
}
await new permission().PermissionOrgUserDelete(req, "SYS_REGISTRY_EMP", record.profileEmployeeId);
const before = structuredClone(record);
const history = new ProfileChildrenHistory();
const now = new Date();
record.isDeleted = true;
record.lastUpdateUserId = req.user.sub;
record.lastUpdateFullName = req.user.name;
record.lastUpdatedAt = now;
Object.assign(history, { ...record, id: undefined });
history.profileChildrenId = childrenId;
history.createdUserId = req.user.sub;
history.createdFullName = req.user.name;
history.createdAt = now;
await Promise.all([
this.childrenRepository.save(record, { data: req }),
setLogDataDiff(req, { before, after: record }),
this.childrenHistoryRepository.save(history, { data: req }),
]);
return new HttpSuccess();
}
@Delete("{childrenId}")
public async deleteTraning(@Path() childrenId: string, @Request() req: RequestWithUser) {
const _record = await this.childrenRepository.findOneBy({ id: childrenId });

View file

@ -41,7 +41,7 @@ export class ProfileChildrenEmployeeTempController extends Controller {
throw new HttpError(HttpStatus.BAD_REQUEST, "ไม่พบ profile ดังกล่าว");
}
const lists = await this.childrenRepository.find({
where: { profileEmployeeId: profile.id },
where: { profileEmployeeId: profile.id, isDeleted: false },
order: { createdAt: "ASC" },
});
return new HttpSuccess(lists);
@ -52,7 +52,7 @@ export class ProfileChildrenEmployeeTempController extends Controller {
let _workflow = await new permission().Workflow(req, profileEmployeeId, "SYS_REGISTRY_TEMP");
if (_workflow == false) await new permission().PermissionGet(req, "SYS_REGISTRY_TEMP");
const lists = await this.childrenRepository.find({
where: { profileEmployeeId: profileEmployeeId },
where: { profileEmployeeId: profileEmployeeId, isDeleted: false },
order: { createdAt: "ASC" },
});
return new HttpSuccess(lists);
@ -143,6 +143,45 @@ export class ProfileChildrenEmployeeTempController extends Controller {
return new HttpSuccess();
}
/**
* API
* @summary API
* @param trainingId
*/
@Patch("update-delete/{childrenId}")
public async updateIsDeleted(
@Request() req: RequestWithUser,
@Path() childrenId: string,
) {
const record = await this.childrenRepository.findOneBy({ id: childrenId });
if (!record) throw new HttpError(HttpStatus.NOT_FOUND, "ไม่พบข้อมูล");
if (record.isDeleted === true) {
return new HttpSuccess();
}
await new permission().PermissionDelete(req, "SYS_REGISTRY_TEMP");
const before = structuredClone(record);
const history = new ProfileChildrenHistory();
const now = new Date();
record.isDeleted = true;
record.lastUpdateUserId = req.user.sub;
record.lastUpdateFullName = req.user.name;
record.lastUpdatedAt = now;
Object.assign(history, { ...record, id: undefined });
history.profileChildrenId = childrenId;
history.createdUserId = req.user.sub;
history.createdFullName = req.user.name;
history.createdAt = now;
await Promise.all([
this.childrenRepository.save(record, { data: req }),
setLogDataDiff(req, { before, after: record }),
this.childrenHistoryRepository.save(history, { data: req }),
]);
return new HttpSuccess();
}
@Delete("{childrenId}")
public async deleteTraning(@Path() childrenId: string, @Request() req: RequestWithUser) {
await new permission().PermissionDelete(req, "SYS_REGISTRY_TEMP");

File diff suppressed because it is too large Load diff

View file

@ -28,6 +28,7 @@ import permission from "../interfaces/permission";
import { DevelopmentProject } from "../entities/DevelopmentProject";
import { In, Brackets } from "typeorm";
import { DevelopmentRequest } from "../entities/DevelopmentRequest";
import { setLogDataDiff } from "../interfaces/utils";
@Route("api/v1/org/profile/development")
@Tags("ProfileDevelopment")
@Security("bearerAuth")
@ -45,7 +46,7 @@ export class ProfileDevelopmentController extends Controller {
throw new HttpError(HttpStatus.BAD_REQUEST, "ไม่พบ profile ดังกล่าว");
}
const lists = await this.developmentRepository.find({
where: { profileId: profile.id },
where: { profileId: profile.id, isDeleted: false },
order: { createdAt: "ASC" },
});
return new HttpSuccess(lists);
@ -66,7 +67,7 @@ export class ProfileDevelopmentController extends Controller {
await new permission().PermissionOrgUserGet(req, "SYS_REGISTRY_OFFICER", profileId);
let query = await AppDataSource.getRepository(ProfileDevelopment)
.createQueryBuilder("profileDevelopment")
.where({ profileId: profileId })
.where({ profileId: profileId, isDeleted: false })
.andWhere(
new Brackets((qb) => {
qb.where(
@ -329,6 +330,44 @@ export class ProfileDevelopmentController extends Controller {
return new HttpSuccess();
}
/**
* API IDP
* @summary API IDP
* @param developmentId IDP
*/
@Patch("update-delete/{developmentId}")
public async updateIsDeletedTraining(
@Request() req: RequestWithUser,
@Path() developmentId: string,
) {
const record = await this.developmentRepository.findOneBy({ id: developmentId });
if (!record) throw new HttpError(HttpStatus.NOT_FOUND, "ไม่พบข้อมูล");
if (record.isDeleted === true) {
return new HttpSuccess();
}
await new permission().PermissionOrgUserDelete(req, "SYS_REGISTRY_OFFICER", record.profileId);
const before = structuredClone(record);
const history = new ProfileDevelopmentHistory();
const now = new Date();
record.isDeleted = true;
record.lastUpdateUserId = req.user.sub;
record.lastUpdateFullName = req.user.name;
record.lastUpdatedAt = now;
Object.assign(history, { ...record, id: undefined });
history.createdUserId = req.user.sub;
history.createdFullName = req.user.name;
history.createdAt = now;
await Promise.all([
this.developmentRepository.save(record, { data: req }),
setLogDataDiff(req, { before, after: record }),
this.developmentHistoryRepository.save(history, { data: req }),
]);
return new HttpSuccess();
}
@Delete("{developmentId}")
public async deleteDevelopment(@Path() developmentId: string, @Request() req: RequestWithUser) {
const _record = await this.developmentRepository.findOneBy({ id: developmentId });

View file

@ -27,6 +27,7 @@ import {
import permission from "../interfaces/permission";
import { DevelopmentProject } from "../entities/DevelopmentProject";
import { In, Brackets } from "typeorm";
import { setLogDataDiff } from "../interfaces/utils";
@Route("api/v1/org/profile-employee/development")
@Tags("ProfileDevelopment")
@Security("bearerAuth")
@ -43,7 +44,7 @@ export class ProfileDevelopmentEmployeeController extends Controller {
throw new HttpError(HttpStatus.BAD_REQUEST, "ไม่พบ profile ดังกล่าว");
}
const lists = await this.developmentRepository.find({
where: { profileEmployeeId: profile.id },
where: { profileEmployeeId: profile.id, isDeleted: false },
order: { createdAt: "ASC" },
});
return new HttpSuccess(lists);
@ -65,7 +66,7 @@ export class ProfileDevelopmentEmployeeController extends Controller {
let query = await AppDataSource.getRepository(ProfileDevelopment)
.createQueryBuilder("profileDevelopment")
.where({ profileEmployeeId: profileId })
.where({ profileEmployeeId: profileId, isDeleted: false })
.andWhere(
new Brackets((qb) => {
qb.where(
@ -273,6 +274,44 @@ export class ProfileDevelopmentEmployeeController extends Controller {
return new HttpSuccess();
}
/**
* API IDP
* @summary API IDP
* @param developmentId IDP
*/
@Patch("update-delete/{developmentId}")
public async updateIsDeletedTraining(
@Request() req: RequestWithUser,
@Path() developmentId: string,
) {
const record = await this.developmentRepository.findOneBy({ id: developmentId });
if (!record) throw new HttpError(HttpStatus.NOT_FOUND, "ไม่พบข้อมูล");
if (record.isDeleted === true) {
return new HttpSuccess();
}
await new permission().PermissionOrgUserDelete(req, "SYS_REGISTRY_EMP", record.profileEmployeeId);
const before = structuredClone(record);
const history = new ProfileDevelopmentHistory();
const now = new Date();
record.isDeleted = true;
record.lastUpdateUserId = req.user.sub;
record.lastUpdateFullName = req.user.name;
record.lastUpdatedAt = now;
Object.assign(history, { ...record, id: undefined });
history.createdUserId = req.user.sub;
history.createdFullName = req.user.name;
history.createdAt = now;
await Promise.all([
this.developmentRepository.save(record, { data: req }),
setLogDataDiff(req, { before, after: record }),
this.developmentHistoryRepository.save(history, { data: req }),
]);
return new HttpSuccess();
}
@Delete("{developmentId}")
public async deleteDevelopment(@Path() developmentId: string, @Request() req: RequestWithUser) {
const _record = await this.developmentRepository.findOneBy({ id: developmentId });

View file

@ -41,7 +41,7 @@ export class ProfileDevelopmentEmployeeTempController extends Controller {
throw new HttpError(HttpStatus.BAD_REQUEST, "ไม่พบ profile ดังกล่าว");
}
const lists = await this.developmentRepository.find({
where: { profileEmployeeId: profile.id },
where: { profileEmployeeId: profile.id, isDeleted: false },
order: { createdAt: "ASC" },
});
return new HttpSuccess(lists);
@ -52,7 +52,7 @@ export class ProfileDevelopmentEmployeeTempController extends Controller {
let _workflow = await new permission().Workflow(req, profileId, "SYS_REGISTRY_TEMP");
if (_workflow == false) await new permission().PermissionGet(req, "SYS_REGISTRY_TEMP");
const lists = await this.developmentRepository.find({
where: { profileEmployeeId: profileId },
where: { profileEmployeeId: profileId, isDeleted: false },
order: { createdAt: "ASC" },
});
return new HttpSuccess(lists);

View file

@ -40,7 +40,7 @@ export class ProfileDisciplineController extends Controller {
throw new HttpError(HttpStatus.BAD_REQUEST, "ไม่พบ profile ดังกล่าว");
}
const lists = await this.disciplineRepository.find({
where: { profileId: profile.id },
where: { profileId: profile.id, isDeleted: false },
order: { createdAt: "ASC" },
});
return new HttpSuccess(lists);
@ -52,7 +52,7 @@ export class ProfileDisciplineController extends Controller {
if (_workflow == false)
await new permission().PermissionOrgUserGet(req, "SYS_REGISTRY_OFFICER", profileId);
const lists = await this.disciplineRepository.find({
where: { profileId: profileId },
where: { profileId: profileId, isDeleted: false },
order: { createdAt: "ASC" },
});
return new HttpSuccess(lists);
@ -63,7 +63,7 @@ export class ProfileDisciplineController extends Controller {
let _workflow = await new permission().Workflow(req, profileId, "SYS_SALARY_OFFICER");
if (_workflow == false) await new permission().PermissionGet(req, "SYS_SALARY_OFFICER");
const lists = await this.disciplineRepository.find({
where: { profileId: profileId },
where: { profileId: profileId, isDeleted: false },
order: { createdAt: "ASC" },
});
return new HttpSuccess(lists);
@ -175,6 +175,45 @@ export class ProfileDisciplineController extends Controller {
return new HttpSuccess();
}
/**
* API
* @summary API
* @param disciplineId
*/
@Patch("update-delete/{disciplineId}")
public async updateIsDeleted(
@Request() req: RequestWithUser,
@Path() disciplineId: string,
) {
const record = await this.disciplineRepository.findOneBy({ id: disciplineId });
if (!record) throw new HttpError(HttpStatus.NOT_FOUND, "ไม่พบข้อมูล");
if (record.isDeleted === true) {
return new HttpSuccess();
}
await new permission().PermissionOrgUserDelete(req, "SYS_REGISTRY_OFFICER", record.profileId);
const before = structuredClone(record);
const history = new ProfileDisciplineHistory();
const now = new Date();
record.isDeleted = true;
record.lastUpdateUserId = req.user.sub;
record.lastUpdateFullName = req.user.name;
record.lastUpdatedAt = now;
Object.assign(history, { ...record, id: undefined });
history.profileDisciplineId = disciplineId;
history.createdUserId = req.user.sub;
history.createdFullName = req.user.name;
history.createdAt = now;
await Promise.all([
this.disciplineRepository.save(record, { data: req }),
setLogDataDiff(req, { before, after: record }),
this.disciplineHistoryRepository.save(history, { data: req }),
]);
return new HttpSuccess();
}
@Delete("{disciplineId}")
public async deleteDiscipline(@Path() disciplineId: string, @Request() req: RequestWithUser) {
const _record = await this.disciplineRepository.findOneBy({ id: disciplineId });

View file

@ -40,7 +40,7 @@ export class ProfileDisciplineEmployeeController extends Controller {
throw new HttpError(HttpStatus.BAD_REQUEST, "ไม่พบ profile ดังกล่าว");
}
const lists = await this.disciplineRepository.find({
where: { profileEmployeeId: profile.id },
where: { profileEmployeeId: profile.id, isDeleted: false },
order: { createdAt: "ASC" },
});
return new HttpSuccess(lists);
@ -52,7 +52,7 @@ export class ProfileDisciplineEmployeeController extends Controller {
if (_workflow == false)
await new permission().PermissionOrgUserGet(req, "SYS_REGISTRY_EMP", profileId);
const lists = await this.disciplineRepository.find({
where: { profileEmployeeId: profileId },
where: { profileEmployeeId: profileId, isDeleted: false },
order: { createdAt: "ASC" },
});
return new HttpSuccess(lists);
@ -63,7 +63,7 @@ export class ProfileDisciplineEmployeeController extends Controller {
let _workflow = await new permission().Workflow(req, profileId, "SYS_WAGE");
if (_workflow == false) await new permission().PermissionGet(req, "SYS_WAGE");
const lists = await this.disciplineRepository.find({
where: { profileEmployeeId: profileId },
where: { profileEmployeeId: profileId, isDeleted: false },
order: { createdAt: "ASC" },
});
return new HttpSuccess(lists);
@ -179,6 +179,45 @@ export class ProfileDisciplineEmployeeController extends Controller {
return new HttpSuccess();
}
/**
* API
* @summary API
* @param disciplineId
*/
@Patch("update-delete/{disciplineId}")
public async updateIsDeleted(
@Request() req: RequestWithUser,
@Path() disciplineId: string,
) {
const record = await this.disciplineRepository.findOneBy({ id: disciplineId });
if (!record) throw new HttpError(HttpStatus.NOT_FOUND, "ไม่พบข้อมูล");
if (record.isDeleted === true) {
return new HttpSuccess();
}
await new permission().PermissionOrgUserDelete(req, "SYS_REGISTRY_EMP", record.profileEmployeeId);
const before = structuredClone(record);
const history = new ProfileDisciplineHistory();
const now = new Date();
record.isDeleted = true;
record.lastUpdateUserId = req.user.sub;
record.lastUpdateFullName = req.user.name;
record.lastUpdatedAt = now;
Object.assign(history, { ...record, id: undefined });
history.profileDisciplineId = disciplineId;
history.createdUserId = req.user.sub;
history.createdFullName = req.user.name;
history.createdAt = now;
await Promise.all([
this.disciplineRepository.save(record, { data: req }),
setLogDataDiff(req, { before, after: record }),
this.disciplineHistoryRepository.save(history, { data: req }),
]);
return new HttpSuccess();
}
@Delete("{disciplineId}")
public async deleteDiscipline(@Path() disciplineId: string, @Request() req: RequestWithUser) {
const _record = await this.disciplineRepository.findOneBy({ id: disciplineId });

View file

@ -40,7 +40,7 @@ export class ProfileDisciplineEmployeeTempController extends Controller {
throw new HttpError(HttpStatus.BAD_REQUEST, "ไม่พบ profile ดังกล่าว");
}
const lists = await this.disciplineRepository.find({
where: { profileEmployeeId: profile.id },
where: { profileEmployeeId: profile.id, isDeleted: false },
order: { createdAt: "ASC" },
});
return new HttpSuccess(lists);
@ -51,7 +51,7 @@ export class ProfileDisciplineEmployeeTempController extends Controller {
let _workflow = await new permission().Workflow(req, profileId, "SYS_REGISTRY_TEMP");
if (_workflow == false) await new permission().PermissionGet(req, "SYS_REGISTRY_TEMP");
const lists = await this.disciplineRepository.find({
where: { profileEmployeeId: profileId },
where: { profileEmployeeId: profileId, isDeleted: false },
order: { createdAt: "ASC" },
});
return new HttpSuccess(lists);
@ -62,7 +62,7 @@ export class ProfileDisciplineEmployeeTempController extends Controller {
let _workflow = await new permission().Workflow(req, profileId, "SYS_WAGE");
if (_workflow == false) await new permission().PermissionGet(req, "SYS_WAGE");
const lists = await this.disciplineRepository.find({
where: { profileEmployeeId: profileId },
where: { profileEmployeeId: profileId, isDeleted: false },
order: { createdAt: "ASC" },
});
return new HttpSuccess(lists);
@ -169,6 +169,45 @@ export class ProfileDisciplineEmployeeTempController extends Controller {
return new HttpSuccess();
}
/**
* API
* @summary API
* @param disciplineId
*/
@Patch("update-delete/{disciplineId}")
public async updateIsDeleted(
@Request() req: RequestWithUser,
@Path() disciplineId: string,
) {
const record = await this.disciplineRepository.findOneBy({ id: disciplineId });
if (!record) throw new HttpError(HttpStatus.NOT_FOUND, "ไม่พบข้อมูล");
if (record.isDeleted === true) {
return new HttpSuccess();
}
await new permission().PermissionDelete(req, "SYS_REGISTRY_TEMP");
const before = structuredClone(record);
const history = new ProfileDisciplineHistory();
const now = new Date();
record.isDeleted = true;
record.lastUpdateUserId = req.user.sub;
record.lastUpdateFullName = req.user.name;
record.lastUpdatedAt = now;
Object.assign(history, { ...record, id: undefined });
history.profileDisciplineId = disciplineId;
history.createdUserId = req.user.sub;
history.createdFullName = req.user.name;
history.createdAt = now;
await Promise.all([
this.disciplineRepository.save(record, { data: req }),
setLogDataDiff(req, { before, after: record }),
this.disciplineHistoryRepository.save(history, { data: req }),
]);
return new HttpSuccess();
}
@Delete("{disciplineId}")
public async deleteDiscipline(@Path() disciplineId: string, @Request() req: RequestWithUser) {
await new permission().PermissionDelete(req, "SYS_REGISTRY_TEMP");

View file

@ -36,7 +36,7 @@ export class ProfileDutyController extends Controller {
throw new HttpError(HttpStatus.BAD_REQUEST, "ไม่พบ profile ดังกล่าว");
}
const lists = await this.dutyRepository.find({
where: { profileId: profile.id },
where: { profileId: profile.id, isDeleted: false },
order: { createdAt: "ASC" },
});
return new HttpSuccess(lists);
@ -48,7 +48,7 @@ export class ProfileDutyController extends Controller {
if (_workflow == false)
await new permission().PermissionOrgUserGet(req, "SYS_REGISTRY_OFFICER", profileId);
const lists = await this.dutyRepository.find({
where: { profileId: profileId },
where: { profileId: profileId, isDeleted: false },
order: { createdAt: "ASC" },
});
return new HttpSuccess(lists);
@ -150,6 +150,45 @@ export class ProfileDutyController extends Controller {
return new HttpSuccess();
}
/**
* API
* @summary API
* @param dutyId
*/
@Patch("update-delete/{dutyId}")
public async updateIsDeleted(
@Request() req: RequestWithUser,
@Path() dutyId: string,
) {
const record = await this.dutyRepository.findOneBy({ id: dutyId });
if (!record) throw new HttpError(HttpStatus.NOT_FOUND, "ไม่พบข้อมูล");
if (record.isDeleted === true) {
return new HttpSuccess();
}
await new permission().PermissionOrgUserDelete(req, "SYS_REGISTRY_OFFICER", record.profileId);
const before = structuredClone(record);
const history = new ProfileDutyHistory();
const now = new Date();
record.isDeleted = true;
record.lastUpdateUserId = req.user.sub;
record.lastUpdateFullName = req.user.name;
record.lastUpdatedAt = now;
Object.assign(history, { ...record, id: undefined });
history.profileDutyId = dutyId;
history.createdUserId = req.user.sub;
history.createdFullName = req.user.name;
history.createdAt = now;
await Promise.all([
this.dutyRepository.save(record, { data: req }),
setLogDataDiff(req, { before, after: record }),
this.dutyHistoryRepository.save(history, { data: req }),
]);
return new HttpSuccess();
}
@Delete("{dutyId}")
public async deleteDuty(@Path() dutyId: string, @Request() req: RequestWithUser) {
const _record = await this.dutyRepository.findOneBy({ id: dutyId });

View file

@ -36,7 +36,7 @@ export class ProfileDutyEmployeeController extends Controller {
throw new HttpError(HttpStatus.BAD_REQUEST, "ไม่พบ profile ดังกล่าว");
}
const lists = await this.dutyRepository.find({
where: { profileEmployeeId: profile.id },
where: { profileEmployeeId: profile.id, isDeleted: false },
order: { createdAt: "ASC" },
});
return new HttpSuccess(lists);
@ -48,7 +48,7 @@ export class ProfileDutyEmployeeController extends Controller {
if (_workflow == false)
await new permission().PermissionOrgUserGet(req, "SYS_REGISTRY_EMP", profileId);
const lists = await this.dutyRepository.find({
where: { profileEmployeeId: profileId },
where: { profileEmployeeId: profileId, isDeleted: false },
order: { createdAt: "ASC" },
});
return new HttpSuccess(lists);
@ -159,6 +159,45 @@ export class ProfileDutyEmployeeController extends Controller {
return new HttpSuccess();
}
/**
* API
* @summary API
* @param dutyId
*/
@Patch("update-delete/{dutyId}")
public async updateIsDeleted(
@Request() req: RequestWithUser,
@Path() dutyId: string,
) {
const record = await this.dutyRepository.findOneBy({ id: dutyId });
if (!record) throw new HttpError(HttpStatus.NOT_FOUND, "ไม่พบข้อมูล");
if (record.isDeleted === true) {
return new HttpSuccess();
}
await new permission().PermissionOrgUserDelete(req, "SYS_REGISTRY_EMP", record.profileEmployeeId);
const before = structuredClone(record);
const history = new ProfileDutyHistory();
const now = new Date();
record.isDeleted = true;
record.lastUpdateUserId = req.user.sub;
record.lastUpdateFullName = req.user.name;
record.lastUpdatedAt = now;
Object.assign(history, { ...record, id: undefined });
history.profileDutyId = dutyId;
history.createdUserId = req.user.sub;
history.createdFullName = req.user.name;
history.createdAt = now;
await Promise.all([
this.dutyRepository.save(record, { data: req }),
setLogDataDiff(req, { before, after: record }),
this.dutyHistoryRepository.save(history, { data: req }),
]);
return new HttpSuccess();
}
@Delete("{dutyId}")
public async deleteDuty(@Path() dutyId: string, @Request() req: RequestWithUser) {
const _record = await this.dutyRepository.findOneBy({ id: dutyId });

View file

@ -36,7 +36,7 @@ export class ProfileDutyEmployeeTempController extends Controller {
throw new HttpError(HttpStatus.BAD_REQUEST, "ไม่พบ profile ดังกล่าว");
}
const lists = await this.dutyRepository.find({
where: { profileEmployeeId: profile.id },
where: { profileEmployeeId: profile.id, isDeleted: false },
order: { createdAt: "ASC" },
});
return new HttpSuccess(lists);
@ -47,7 +47,7 @@ export class ProfileDutyEmployeeTempController extends Controller {
let _workflow = await new permission().Workflow(req, profileId, "SYS_REGISTRY_TEMP");
if (_workflow == false) await new permission().PermissionGet(req, "SYS_REGISTRY_TEMP");
const lists = await this.dutyRepository.find({
where: { profileEmployeeId: profileId },
where: { profileEmployeeId: profileId, isDeleted: false },
order: { createdAt: "ASC" },
});
return new HttpSuccess(lists);
@ -148,6 +148,45 @@ export class ProfileDutyEmployeeTempController extends Controller {
return new HttpSuccess();
}
/**
* API
* @summary API
* @param dutyId
*/
@Patch("update-delete/{dutyId}")
public async updateIsDeleted(
@Request() req: RequestWithUser,
@Path() dutyId: string,
) {
const record = await this.dutyRepository.findOneBy({ id: dutyId });
if (!record) throw new HttpError(HttpStatus.NOT_FOUND, "ไม่พบข้อมูล");
if (record.isDeleted === true) {
return new HttpSuccess();
}
await new permission().PermissionDelete(req, "SYS_REGISTRY_TEMP");
const before = structuredClone(record);
const history = new ProfileDutyHistory();
const now = new Date();
record.isDeleted = true;
record.lastUpdateUserId = req.user.sub;
record.lastUpdateFullName = req.user.name;
record.lastUpdatedAt = now;
Object.assign(history, { ...record, id: undefined });
history.profileDutyId = dutyId;
history.createdUserId = req.user.sub;
history.createdFullName = req.user.name;
history.createdAt = now;
await Promise.all([
this.dutyRepository.save(record, { data: req }),
setLogDataDiff(req, { before, after: record }),
this.dutyHistoryRepository.save(history, { data: req }),
]);
return new HttpSuccess();
}
@Delete("{dutyId}")
public async deleteDuty(@Path() dutyId: string, @Request() req: RequestWithUser) {
await new permission().PermissionDelete(req, "SYS_REGISTRY_TEMP");

View file

@ -24,6 +24,7 @@ import CallAPI from "../interfaces/call-api";
import permission from "../interfaces/permission";
import { OrgRevision } from "../entities/OrgRevision";
import { OrgRoot } from "../entities/OrgRoot";
import { PosMaster } from "../entities/PosMaster";
@Route("api/v1/org/profile/edit")
@Tags("ProfileEdit")
@Security("bearerAuth")
@ -32,6 +33,7 @@ export class ProfileEditController extends Controller {
private profileEditRepo = AppDataSource.getRepository(ProfileEdit);
private orgRevisionRepository = AppDataSource.getRepository(OrgRevision);
private orgRootRepo = AppDataSource.getRepository(OrgRoot);
private posMasterRepo = AppDataSource.getRepository(PosMaster);
@Get("user")
public async detailProfileEditUser(
@ -151,7 +153,8 @@ export class ProfileEditController extends Controller {
data.child1 != undefined && data.child1 != null
? data.child1[0] != null
? `current_holders.orgChild1Id IN (:...child1)`
: `current_holders.orgChild1Id is ${data.privilege == "PARENT" ? "not null" : "null"}`
// : `current_holders.orgChild1Id is ${data.privilege == "PARENT" ? "not null" : "null"}`
: `current_holders.orgChild1Id is null`
: "1=1",
{
child1: data.child1,
@ -271,6 +274,22 @@ export class ProfileEditController extends Controller {
if (!getProfileEdit) {
throw new HttpError(HttpStatus.NOT_FOUND, "ไม่พบข้อมูล");
}
let orgRoot: OrgRoot | null = null;
if(getProfileEdit.profile) {
const empPosMaster = await this.posMasterRepo.findOne({
where: {
current_holderId: getProfileEdit.profile.id,
orgRevision: { orgRevisionIsCurrent: true, orgRevisionIsDraft: false }
},
relations: { orgRevision: true }
});
if(empPosMaster) {
orgRoot = await this.orgRootRepo.findOne({
select: { isDeputy: true },
where: { id: empPosMaster.orgRootId ?? "" }
});
}
}
const _data = {
id: getProfileEdit.id,
topic: getProfileEdit.topic,
@ -288,6 +307,7 @@ export class ProfileEditController extends Controller {
(getProfileEdit?.profile?.firstName ?? "") +
" " +
(getProfileEdit?.profile?.lastName ?? ""),
isDeputy: orgRoot?.isDeputy ?? false
};
return new HttpSuccess(_data);
}

View file

@ -28,6 +28,7 @@ import permission from "../interfaces/permission";
import { OrgRevision } from "../entities/OrgRevision";
import { OrgRoot } from "../entities/OrgRoot";
import CallAPI from "../interfaces/call-api";
import { EmployeePosMaster } from "../entities/EmployeePosMaster";
@Route("api/v1/org/profile-employee/edit")
@Tags("ProfileEmployeeEdit")
@Security("bearerAuth")
@ -36,6 +37,7 @@ export class ProfileEditEmployeeController extends Controller {
private profileEditRepository = AppDataSource.getRepository(ProfileEdit);
private orgRevisionRepository = AppDataSource.getRepository(OrgRevision);
private orgRootRepo = AppDataSource.getRepository(OrgRoot);
private empPosMasterRepo = AppDataSource.getRepository(EmployeePosMaster);
@Get("user")
public async detailProfileEditUserEmp(
@ -150,7 +152,8 @@ export class ProfileEditEmployeeController extends Controller {
data.child1 != undefined && data.child1 != null
? data.child1[0] != null
? `current_holders.orgChild1Id IN (:...child1)`
: `current_holders.orgChild1Id is ${data.privilege == "PARENT" ? "not null" : "null"}`
// : `current_holders.orgChild1Id is ${data.privilege == "PARENT" ? "not null" : "null"}`
: `current_holders.orgChild1Id is null`
: "1=1",
{
child1: data.child1,
@ -270,6 +273,22 @@ export class ProfileEditEmployeeController extends Controller {
if (!getProfileEdit) {
throw new HttpError(HttpStatus.NOT_FOUND, "ไม่พบข้อมูล");
}
let orgRoot: OrgRoot | null = null;
if(getProfileEdit.profileEmployee) {
const empPosMaster = await this.empPosMasterRepo.findOne({
where: {
current_holderId: getProfileEdit.profileEmployee.id,
orgRevision: { orgRevisionIsCurrent: true, orgRevisionIsDraft: false }
},
relations: { orgRevision: true }
});
if(empPosMaster) {
orgRoot = await this.orgRootRepo.findOne({
select: { isDeputy: true },
where: { id: empPosMaster.orgRootId ?? "" }
});
}
}
const _data = {
id: getProfileEdit.id,
topic: getProfileEdit.topic,
@ -287,6 +306,7 @@ export class ProfileEditEmployeeController extends Controller {
(getProfileEdit?.profileEmployee?.firstName ?? "") +
" " +
(getProfileEdit?.profileEmployee?.lastName ?? ""),
isDeputy: orgRoot?.isDeputy ?? false
};
return new HttpSuccess(_data);
}

View file

@ -42,7 +42,7 @@ export class ProfileEducationsController extends Controller {
throw new HttpError(HttpStatus.BAD_REQUEST, "ไม่พบ profile ดังกล่าว");
}
const getProfileEducation = await this.profileEducationRepo.find({
where: { profileId: profile.id },
where: { profileId: profile.id, isDeleted: false },
order: { level: "ASC" },
});
if (!getProfileEducation) {
@ -57,7 +57,7 @@ export class ProfileEducationsController extends Controller {
if (_workflow == false)
await new permission().PermissionOrgUserGet(req, "SYS_REGISTRY_OFFICER", profileId);
const getProfileEducation = await this.profileEducationRepo.find({
where: { profileId: profileId },
where: { profileId: profileId, isDeleted: false },
order: { level: "ASC" },
});
if (!getProfileEducation) {
@ -208,6 +208,45 @@ export class ProfileEducationsController extends Controller {
return new HttpSuccess();
}
/**
* API
* @summary API /
* @param educationId
*/
@Patch("update-delete/{educationId}")
public async updateIsDeleted(
@Request() req: RequestWithUser,
@Path() educationId: string,
) {
const record = await this.profileEducationRepo.findOneBy({ id: educationId });
if (!record) throw new HttpError(HttpStatus.NOT_FOUND, "ไม่พบข้อมูล");
if (record.isDeleted === true) {
return new HttpSuccess();
}
await new permission().PermissionOrgUserDelete(req, "SYS_REGISTRY_OFFICER", record.profileId);
const before = structuredClone(record);
const history = new ProfileEducationHistory();
const now = new Date();
record.isDeleted = true;
record.lastUpdateUserId = req.user.sub;
record.lastUpdateFullName = req.user.name;
record.lastUpdatedAt = now;
Object.assign(history, { ...record, id: undefined });
history.profileEducationId = educationId;
history.createdUserId = req.user.sub;
history.createdFullName = req.user.name;
history.createdAt = now;
await Promise.all([
this.profileEducationRepo.save(record, { data: req }),
setLogDataDiff(req, { before, after: record }),
this.profileEducationHistoryRepo.save(history, { data: req }),
]);
return new HttpSuccess();
}
@Delete("{educationId}")
public async deleteProfileEducation(
@Path() educationId: string,

View file

@ -42,7 +42,7 @@ export class ProfileEducationsEmployeeController extends Controller {
throw new HttpError(HttpStatus.BAD_REQUEST, "ไม่พบ profile ดังกล่าว");
}
const getProfileEducation = await this.profileEducationRepo.find({
where: { profileEmployeeId: profile.id },
where: { profileEmployeeId: profile.id, isDeleted: false },
order: { level: "ASC" },
});
if (!getProfileEducation) {
@ -60,7 +60,7 @@ export class ProfileEducationsEmployeeController extends Controller {
if (_workflow == false)
await new permission().PermissionOrgUserGet(req, "SYS_REGISTRY_EMP", profileEmployeeId);
const getProfileEducation = await this.profileEducationRepo.find({
where: { profileEmployeeId: profileEmployeeId },
where: { profileEmployeeId: profileEmployeeId, isDeleted: false },
order: { level: "ASC" },
});
if (!getProfileEducation) {
@ -220,6 +220,45 @@ export class ProfileEducationsEmployeeController extends Controller {
return new HttpSuccess();
}
/**
* API
* @summary API /
* @param educationId
*/
@Patch("update-delete/{educationId}")
public async updateIsDeleted(
@Request() req: RequestWithUser,
@Path() educationId: string,
) {
const record = await this.profileEducationRepo.findOneBy({ id: educationId });
if (!record) throw new HttpError(HttpStatus.NOT_FOUND, "ไม่พบข้อมูล");
if (record.isDeleted === true) {
return new HttpSuccess();
}
await new permission().PermissionOrgUserDelete(req, "SYS_REGISTRY_EMP", record.profileEmployeeId);
const before = structuredClone(record);
const history = new ProfileEducationHistory();
const now = new Date();
record.isDeleted = true;
record.lastUpdateUserId = req.user.sub;
record.lastUpdateFullName = req.user.name;
record.lastUpdatedAt = now;
Object.assign(history, { ...record, id: undefined });
history.profileEducationId = educationId;
history.createdUserId = req.user.sub;
history.createdFullName = req.user.name;
history.createdAt = now;
await Promise.all([
this.profileEducationRepo.save(record, { data: req }),
setLogDataDiff(req, { before, after: record }),
this.profileEducationHistoryRepo.save(history, { data: req }),
]);
return new HttpSuccess();
}
@Delete("{educationId}")
public async deleteProfileEducation(
@Path() educationId: string,

View file

@ -42,7 +42,7 @@ export class ProfileEducationsEmployeeTempController extends Controller {
throw new HttpError(HttpStatus.BAD_REQUEST, "ไม่พบ profile ดังกล่าว");
}
const getProfileEducation = await this.profileEducationRepo.find({
where: { profileEmployeeId: profile.id },
where: { profileEmployeeId: profile.id, isDeleted: false },
order: { level: "ASC" },
});
if (!getProfileEducation) {
@ -59,7 +59,7 @@ export class ProfileEducationsEmployeeTempController extends Controller {
let _workflow = await new permission().Workflow(req, profileEmployeeId, "SYS_REGISTRY_TEMP");
if (_workflow == false) await new permission().PermissionGet(req, "SYS_REGISTRY_TEMP");
const getProfileEducation = await this.profileEducationRepo.find({
where: { profileEmployeeId: profileEmployeeId },
where: { profileEmployeeId: profileEmployeeId, isDeleted: false },
order: { level: "ASC" },
});
if (!getProfileEducation) {
@ -210,6 +210,45 @@ export class ProfileEducationsEmployeeTempController extends Controller {
return new HttpSuccess();
}
/**
* API
* @summary API /
* @param educationId
*/
@Patch("update-delete/{educationId}")
public async updateIsDeleted(
@Request() req: RequestWithUser,
@Path() educationId: string,
) {
const record = await this.profileEducationRepo.findOneBy({ id: educationId });
if (!record) throw new HttpError(HttpStatus.NOT_FOUND, "ไม่พบข้อมูล");
if (record.isDeleted === true) {
return new HttpSuccess();
}
await new permission().PermissionDelete(req, "SYS_REGISTRY_TEMP");
const before = structuredClone(record);
const history = new ProfileEducationHistory();
const now = new Date();
record.isDeleted = true;
record.lastUpdateUserId = req.user.sub;
record.lastUpdateFullName = req.user.name;
record.lastUpdatedAt = now;
Object.assign(history, { ...record, id: undefined });
history.profileEducationId = educationId;
history.createdUserId = req.user.sub;
history.createdFullName = req.user.name;
history.createdAt = now;
await Promise.all([
this.profileEducationRepo.save(record, { data: req }),
setLogDataDiff(req, { before, after: record }),
this.profileEducationHistoryRepo.save(history, { data: req }),
]);
return new HttpSuccess();
}
@Delete("{educationId}")
public async deleteProfileEducation(
@Path() educationId: string,

View file

@ -0,0 +1,277 @@
import {
Body,
Controller,
Get,
Patch,
Path,
Post,
Request,
Route,
Security,
Tags,
} from "tsoa";
import { AppDataSource } from "../database/data-source";
import { In } from "typeorm";
import {
ProfileEmployeeAbsentLate,
CreateProfileEmployeeAbsentLate,
CreateProfileEmployeeAbsentLateBatch,
UpdateProfileEmployeeAbsentLate,
} from "../entities/ProfileEmployeeAbsentLate";
import { ProfileEmployeeAbsentLateHistory } from "../entities/ProfileEmployeeAbsentLateHistory";
import HttpSuccess from "../interfaces/http-success";
import HttpStatus from "../interfaces/http-status";
import HttpError from "../interfaces/http-error";
import { RequestWithUser } from "../middlewares/user";
import { ProfileEmployee } from "../entities/ProfileEmployee";
import permission from "../interfaces/permission";
import { setLogDataDiff } from "../interfaces/utils";
@Route("api/v1/org/profile-employee/absent-late")
@Tags("ProfileEmployeeAbsentLate")
@Security("bearerAuth")
export class ProfileEmployeeAbsentLateController extends Controller {
private profileRepo = AppDataSource.getRepository(ProfileEmployee);
private absentLateRepo = AppDataSource.getRepository(ProfileEmployeeAbsentLate);
private historyRepo = AppDataSource.getRepository(ProfileEmployeeAbsentLateHistory);
/**
* API / user
* @summary API / user
*/
@Get("user")
public async getAbsentLateUser(@Request() request: { user: Record<string, any> }) {
const profile = await this.profileRepo.findOneBy({ keycloak: request.user.sub });
if (!profile) {
throw new HttpError(HttpStatus.BAD_REQUEST, "ไม่พบ profile ดังกล่าว");
}
const record = await this.absentLateRepo.find({
where: { profileEmployeeId: profile.id, isDeleted: false },
order: { stampDate: "DESC" },
});
return new HttpSuccess(record);
}
/**
* API / profileId
* @summary API / profileId
* @param profileId profile
*/
@Get("{profileId}")
public async getAbsentLate(@Path() profileId: string, @Request() req: RequestWithUser) {
let _workflow = await new permission().Workflow(req, profileId, "SYS_REGISTRY_EMP");
if (_workflow == false)
await new permission().PermissionOrgUserGet(req, "SYS_REGISTRY_EMP", profileId);
const record = await this.absentLateRepo.find({
where: { profileEmployeeId: profileId, isDeleted: false },
order: { stampDate: "DESC" },
});
return new HttpSuccess(record);
}
/**
* API /
* @summary API /
*/
@Post()
public async newAbsentLate(
@Request() req: RequestWithUser,
@Body() body: CreateProfileEmployeeAbsentLate,
) {
if (!body.profileEmployeeId) {
throw new HttpError(HttpStatus.BAD_REQUEST, "กรุณากรอก profileEmployeeId");
}
const profile = await this.profileRepo.findOneBy({ id: body.profileEmployeeId });
if (!profile) {
throw new HttpError(HttpStatus.BAD_REQUEST, "ไม่พบ profile ดังกล่าว");
}
await new permission().PermissionOrgUserUpdate(req, "SYS_REGISTRY_EMP", profile.id);
const before = null;
const data = new ProfileEmployeeAbsentLate();
const meta = {
createdUserId: req.user.sub,
createdFullName: req.user.name,
lastUpdateUserId: req.user.sub,
lastUpdateFullName: req.user.name,
createdAt: new Date(),
lastUpdatedAt: new Date(),
};
Object.assign(data, { ...body, ...meta });
// บันทึก history
const history = new ProfileEmployeeAbsentLateHistory();
Object.assign(history, { ...data, id: undefined });
await this.absentLateRepo.save(data, { data: req });
setLogDataDiff(req, { before, after: data });
history.profileEmployeeAbsentLateId = data.id;
await this.historyRepo.save(history, { data: req });
return new HttpSuccess(data.id);
}
/**
* API / ( Job)
* @summary API / ( Job)
*/
@Post("batch")
public async newAbsentLateBatch(
@Request() req: RequestWithUser,
@Body() body: CreateProfileEmployeeAbsentLateBatch,
) {
// กรณีไม่มีข้อมูลส่งมา (วันที่ไม่มีคนขาด/มาสาย)
if (!body.records || body.records.length === 0) {
return new HttpSuccess({ count: 0, ids: [] });
}
const profileIds = [...new Set(body.records.map((r) => r.profileEmployeeId))];
const profiles = await this.profileRepo.findBy({
id: In(profileIds),
});
const foundProfileIds = new Set(profiles.map((p) => p.id));
const validRecords = body.records.filter((r) => foundProfileIds.has(r.profileEmployeeId));
// กรณีไม่พบ profile เลย
if (validRecords.length === 0) {
return new HttpSuccess({ count: 0, ids: [] });
}
const meta = {
createdUserId: req.user.sub,
createdFullName: req.user.name,
lastUpdateUserId: req.user.sub,
lastUpdateFullName: req.user.name,
createdAt: new Date(),
lastUpdatedAt: new Date(),
};
const records = validRecords.map((item) => {
const data = new ProfileEmployeeAbsentLate();
Object.assign(data, { ...item, ...meta });
return data;
});
const result = await this.absentLateRepo.save(records, { data: req });
// บันทึก history สำหรับแต่ละ record
const historyRecords = result.map((data) => {
const history = new ProfileEmployeeAbsentLateHistory();
Object.assign(history, { ...data, id: undefined });
history.profileEmployeeAbsentLateId = data.id;
return history;
});
await this.historyRepo.save(historyRecords, { data: req });
return new HttpSuccess({ count: result.length, ids: result.map((r) => r.id) });
}
/**
* API /
* @summary API /
* @param absentLateId /
*/
@Patch("{absentLateId}")
public async editAbsentLate(
@Request() req: RequestWithUser,
@Body() body: UpdateProfileEmployeeAbsentLate,
@Path() absentLateId: string,
) {
const record = await this.absentLateRepo.findOneBy({ id: absentLateId });
if (!record) throw new HttpError(HttpStatus.NOT_FOUND, "ไม่พบข้อมูล");
await new permission().PermissionOrgUserUpdate(
req,
"SYS_REGISTRY_EMP",
record.profileEmployeeId,
);
const before = structuredClone(record);
const history = new ProfileEmployeeAbsentLateHistory();
Object.assign(history, { ...record, id: undefined });
Object.assign(record, body);
Object.assign(history, { ...record, id: undefined });
history.profileEmployeeAbsentLateId = absentLateId;
record.lastUpdateUserId = req.user.sub;
record.lastUpdateFullName = req.user.name;
record.lastUpdatedAt = new Date();
history.lastUpdateUserId = req.user.sub;
history.lastUpdateFullName = req.user.name;
history.createdUserId = req.user.sub;
history.createdFullName = req.user.name;
history.createdAt = new Date();
history.lastUpdatedAt = new Date();
await Promise.all([
this.absentLateRepo.save(record, { data: req }),
setLogDataDiff(req, { before, after: record }),
this.historyRepo.save(history, { data: req }),
]);
return new HttpSuccess();
}
/**
* API / (Soft Delete)
* @summary API / (Soft Delete)
* @param absentLateId /
*/
@Patch("update-delete/{absentLateId}")
public async updateIsDeleted(
@Request() req: RequestWithUser,
@Path() absentLateId: string,
) {
const record = await this.absentLateRepo.findOneBy({ id: absentLateId });
if (!record) throw new HttpError(HttpStatus.NOT_FOUND, "ไม่พบข้อมูล");
if (record.isDeleted === true) {
return new HttpSuccess();
}
await new permission().PermissionOrgUserDelete(req, "SYS_REGISTRY_EMP", record.profileEmployeeId);
const before = structuredClone(record);
const history = new ProfileEmployeeAbsentLateHistory();
Object.assign(history, { ...record, id: undefined });
record.isDeleted = true;
record.lastUpdateUserId = req.user.sub;
record.lastUpdateFullName = req.user.name;
record.lastUpdatedAt = new Date();
history.profileEmployeeAbsentLateId = absentLateId;
history.isDeleted = true;
history.lastUpdateUserId = req.user.sub;
history.lastUpdateFullName = req.user.name;
history.lastUpdatedAt = new Date();
await Promise.all([
this.absentLateRepo.save(record, { data: req }),
setLogDataDiff(req, { before, after: record }),
this.historyRepo.save(history, { data: req }),
]);
return new HttpSuccess();
}
/**
* API /
* @summary API /
* @param absentLateId /
*/
@Get("history/{absentLateId}")
public async getHistory(@Path() absentLateId: string, @Request() req: RequestWithUser) {
const record = await this.absentLateRepo.findOneBy({ id: absentLateId });
if (!record) throw new HttpError(HttpStatus.NOT_FOUND, "ไม่พบข้อมูล");
await new permission().PermissionOrgUserGet(req, "SYS_REGISTRY_EMP", record.profileEmployeeId);
const history = await this.historyRepo.find({
where: { profileEmployeeAbsentLateId: absentLateId },
order: { createdAt: "DESC" },
});
return new HttpSuccess(history);
}
}

File diff suppressed because it is too large Load diff

View file

@ -289,8 +289,16 @@ export class ProfileEmployeeTempController extends Controller {
},
];
const educations = await this.profileEducationRepo.find({
select: ["startDate", "endDate", "educationLevel", "degree", "field", "institute"],
where: { profileEmployeeId: id },
select: [
"startDate",
"endDate",
"educationLevel",
"degree",
"field",
"institute",
"isDeleted",
],
where: { profileEmployeeId: id, isDeleted: false },
order: { level: "ASC" },
});
const Education =
@ -547,8 +555,8 @@ export class ProfileEmployeeTempController extends Controller {
let _child4 = child4?.orgChild4Name;
const cert_raw = await this.certificateRepository.find({
where: { profileEmployeeId: id },
select: ["certificateType", "issuer", "certificateNo", "issueDate"],
select: ["certificateType", "issuer", "certificateNo", "issueDate", "isDeleted"],
where: { profileEmployeeId: id, isDeleted: false },
order: { createdAt: "ASC" },
});
const certs =
@ -627,8 +635,16 @@ export class ProfileEmployeeTempController extends Controller {
];
const education_raw = await this.profileEducationRepo.find({
select: ["startDate", "endDate", "educationLevel", "degree", "field", "institute"],
where: { profileEmployeeId: id },
select: [
"startDate",
"endDate",
"educationLevel",
"degree",
"field",
"institute",
"isDeleted",
],
where: { profileEmployeeId: id, isDeleted: false },
// order: { lastUpdatedAt: "DESC" },
order: { level: "ASC" },
});
@ -734,7 +750,7 @@ export class ProfileEmployeeTempController extends Controller {
insigniaType: true,
},
},
where: { profileEmployeeId: id },
where: { profileEmployeeId: id, isDeleted: false },
order: { receiveDate: "ASC" },
});
const insignias =
@ -774,7 +790,7 @@ export class ProfileEmployeeTempController extends Controller {
const leave_raw = await this.profileLeaveRepository.find({
relations: { leaveType: true },
where: { profileEmployeeId: id },
where: { profileEmployeeId: id, isDeleted: false },
order: { dateLeaveStart: "ASC" },
});
const leaves =
@ -1110,7 +1126,8 @@ export class ProfileEmployeeTempController extends Controller {
_dataOrg.child1 != undefined && _dataOrg.child1 != null
? _dataOrg.child1[0] != null
? `current_holderTemps.orgChild1Id IN (:...child1)`
: `current_holderTemps.orgChild1Id is ${_dataOrg.privilege == "PARENT" ? "not null" : "null"}`
: // : `current_holderTemps.orgChild1Id is ${_dataOrg.privilege == "PARENT" ? "not null" : "null"}`
`current_holders.orgChild1Id is null`
: "1=1",
{
child1: _dataOrg.child1,
@ -1277,20 +1294,33 @@ export class ProfileEmployeeTempController extends Controller {
*/
@Get("history/user")
async getHistoryProfileByUser(@Request() request: RequestWithUser) {
const historyProfile = await this.profileHistoryRepo.find({
relations: {
posLevel: true,
posType: true,
},
const profile = await this.profileRepo.findOne({
where: { keycloak: request.user.sub },
order: {
createdAt: "ASC",
},
});
if (!profile) throw new HttpError(HttpStatus.NOT_FOUND, "ไม่พบข้อมูล");
const profileHistory = await this.profileHistoryRepo.find({
where: { profileEmployeeId: profile.id },
order: { createdAt: "ASC" },
});
if (!historyProfile) throw new HttpError(HttpStatus.NOT_FOUND, "ไม่พบข้อมูล");
if (profileHistory.length == 0) {
await this.profileHistoryRepo.save(
Object.assign(new ProfileEmployeeHistory(), {
...profile,
birthDateOld: profile?.birthDate,
profileEmployeeId: profile.id,
id: undefined,
}),
);
const firstRecord = await this.profileHistoryRepo.find({
where: { profileEmployeeId: profile.id },
order: { createdAt: "ASC" },
});
return new HttpSuccess(firstRecord);
}
return new HttpSuccess(historyProfile);
return new HttpSuccess(profileHistory);
}
/**
@ -1541,8 +1571,8 @@ export class ProfileEmployeeTempController extends Controller {
let query = await this.profileRepo
.createQueryBuilder("profileEmployee")
.leftJoinAndSelect("profileEmployee.posLevel", "posLevel")
.leftJoinAndSelect("profileEmployee.posType", "posType")
.leftJoinAndSelect("profileEmployee.posLevelTemp", "posLevelTemp")
.leftJoinAndSelect("profileEmployee.posTypeTemp", "posTypeTemp")
.leftJoinAndSelect("profileEmployee.current_holderTemps", "current_holderTemps")
.leftJoinAndSelect("profileEmployee.profileEmployeeEmployment", "profileEmployeeEmployment")
.leftJoinAndSelect("current_holderTemps.positions", "positions")
@ -1565,7 +1595,8 @@ export class ProfileEmployeeTempController extends Controller {
_data.child1 != undefined && _data.child1 != null
? _data.child1[0] != null
? `current_holderTemps.orgChild1Id IN (:...child1)`
: `current_holderTemps.orgChild1Id is ${_data.privilege == "PARENT" ? "not null" : "null"}`
: // : `current_holderTemps.orgChild1Id is ${_data.privilege == "PARENT" ? "not null" : "null"}`
`current_holders.orgChild1Id is null`
: "1=1",
{
child1: _data.child1,
@ -1611,7 +1642,7 @@ export class ProfileEmployeeTempController extends Controller {
)
.andWhere(
posType != undefined && posType != null && posType != ""
? "posType.posTypeName LIKE :keyword1"
? "posTypeTemp.posTypeName LIKE :keyword1"
: "1=1",
{
keyword1: `${posType}`,
@ -1619,7 +1650,7 @@ export class ProfileEmployeeTempController extends Controller {
)
.andWhere(
posLevel != undefined && posLevel != null && posLevel != ""
? "posLevel.posLevelName LIKE :keyword2"
? "posLevelTemp.posLevelName LIKE :keyword2"
: "1=1",
{
keyword2: `${posLevel}`,
@ -1654,8 +1685,8 @@ export class ProfileEmployeeTempController extends Controller {
);
if (sortBy) {
if (sortBy == "posLevel") {
query = query.orderBy(`posLevel.posLevelName`, descending ? "DESC" : "ASC");
if (sortBy == "posLevelTemp") {
query = query.orderBy(`posLevelTemp.posLevelName`, descending ? "DESC" : "ASC");
} else if (sortBy == "posType") {
query = query.orderBy(`posType.posTypeName`, descending ? "DESC" : "ASC");
} else if (sortBy == "govAge") {
@ -1712,11 +1743,13 @@ export class ProfileEmployeeTempController extends Controller {
firstName: _data.firstName,
lastName: _data.lastName,
citizenId: _data.citizenId,
posLevel: _data.posLevel == null ? null : _data.posLevel.posLevelName,
posType: _data.posType == null ? null : _data.posType.posTypeName,
posTypeShortName: _data.posType == null ? null : _data.posType.posTypeShortName,
posLevelId: _data.posLevel == null ? null : _data.posLevel.id,
posTypeId: _data.posType == null ? null : _data.posType.id,
// posLevel: _data.posLevel == null ? null : _data.posLevel.posLevelName,
posLevel: _data.posLevelTemp == null ? null : _data.posLevelTemp.posLevelName,
// posType: _data.posType == null ? null : _data.posType.posTypeName,
posType: _data.posTypeTemp == null ? null : _data.posTypeTemp.posTypeName,
posTypeShortName: _data.posTypeTemp == null ? null : _data.posTypeTemp.posTypeShortName,
posLevelId: _data.posLevelTemp == null ? null : _data.posLevelTemp.id,
posTypeId: _data.posTypeTemp == null ? null : _data.posTypeTemp.id,
positionId: _data.positionIdTemp,
posmasterId: _data.posmasterIdTemp,
position: _data.position,
@ -1794,20 +1827,33 @@ export class ProfileEmployeeTempController extends Controller {
@Get("history/{id}")
async getProfileHistory(@Path() id: string, @Request() req: RequestWithUser) {
// await new permission().PermissionGet(req, "SYS_REGISTRY_TEMP");//ไม่แน่ใจTEMPปิดไว้ก่อน
const profile = await this.profileHistoryRepo.find({
relations: {
posLevel: true,
posType: true,
},
where: { profileEmployeeId: id },
order: {
createdAt: "ASC",
},
const profile = await this.profileRepo.findOne({
where: { id: id },
});
if (!profile) throw new HttpError(HttpStatus.NOT_FOUND, "ไม่พบข้อมูล");
return new HttpSuccess(profile);
const profileHistory = await this.profileHistoryRepo.find({
where: { profileEmployeeId: id },
order: { createdAt: "ASC" },
});
if (profileHistory.length == 0) {
await this.profileHistoryRepo.save(
Object.assign(new ProfileEmployeeHistory(), {
...profile,
birthDateOld: profile?.birthDate,
profileEmployeeId: id,
id: undefined,
}),
);
const firstRecord = await this.profileHistoryRepo.find({
where: { profileEmployeeId: id },
order: { createdAt: "ASC" },
});
return new HttpSuccess(firstRecord);
}
return new HttpSuccess(profileHistory);
}
/**
@ -2274,7 +2320,8 @@ export class ProfileEmployeeTempController extends Controller {
_data.child1 != undefined && _data.child1 != null
? _data.child1[0] != null
? `current_holderTemps.orgChild1Id IN (:...child1)`
: `current_holderTemps.orgChild1Id is ${_data.privilege == "PARENT" ? "not null" : "null"}`
: // : `current_holderTemps.orgChild1Id is ${_data.privilege == "PARENT" ? "not null" : "null"}`
`current_holders.orgChild1Id is null`
: "1=1",
{ child1: _data.child1 },
)
@ -2370,7 +2417,7 @@ export class ProfileEmployeeTempController extends Controller {
)?.posMasterNo;
const latestProfileEducation = await this.profileEducationRepo.findOne({
where: { profileEmployeeId: item.id },
where: { profileEmployeeId: item.id, isDeleted: false },
order: { level: "ASC" },
});
@ -3412,9 +3459,9 @@ export class ProfileEmployeeTempController extends Controller {
}
/**
* API
* API
*
* @summary (ADMIN)
* @summary (ADMIN)
*
* @param {string} id Id
*/
@ -3536,12 +3583,14 @@ export class ProfileEmployeeTempController extends Controller {
// profile.position = _null;
// profile.posLevelId = _null;
// profile.posTypeId = _null;
if (profile.keycloak != null) {
if (profile.keycloak != null && profile.keycloak != "" && profile.isDelete == false) {
const delUserKeycloak = await deleteUser(profile.keycloak);
if (delUserKeycloak) {
profile.keycloak = _null;
// Task #228
// profile.keycloak = _null;
profile.roleKeycloaks = [];
profile.isActive = false;
profile.isDelete = true;
}
}
await this.profileRepo.save(profile);
@ -3559,29 +3608,19 @@ export class ProfileEmployeeTempController extends Controller {
].filter(Boolean);
organizeName = names.join(" ");
}
await PostRetireToExprofile(
// profile.citizenId ?? "",
// profile.prefix ?? "",
// profile.firstName ?? "",
// profile.lastName ?? "",
// requestBody.dateLeave?.getFullYear().toString() ?? "",
// profile.position,
// profile.posType?.posTypeName ?? "",
// `${profile.posType?.posTypeShortName} ${profile.posLevel?.posLevelName}`,
// requestBody.dateLeave ?? new Date(),
// organizeName,
// "ถึงแก่กรรม",
"310190004095X",
"จ.ส.อ.",
"dev",
"hrms",
"2026",
"เจ้าหน้าที่จัดเก็บรายได้",
"อื่นๆ",
"C 3",
new Date(2026, 0, 1),
"สำนักงานเขตบางกอกใหญ่",
"เกษียณ"
PostRetireToExprofile(
request,
profile.citizenId ?? "",
profile.prefix ?? "",
profile.firstName ?? "",
profile.lastName ?? "",
requestBody.dateLeave?.getFullYear().toString() ?? "",
profile.position,
profile.posType?.posTypeName ?? "",
`${profile.posType?.posTypeShortName} ${profile.posLevel?.posLevelName}`,
requestBody.dateLeave ?? new Date(),
organizeName,
"ถึงแก่กรรม",
);
return new HttpSuccess();
}
@ -3950,7 +3989,7 @@ export class ProfileEmployeeTempController extends Controller {
case "citizenId":
[findProfile, total] = await this.profileRepo.findAndCount({
where: {
keycloak: IsNull(),
isActive: false,
citizenId: Like(`%${body.keyword}%`),
},
relations: ["posType", "posLevel", "current_holders"],
@ -3962,7 +4001,7 @@ export class ProfileEmployeeTempController extends Controller {
case "firstname":
[findProfile, total] = await this.profileRepo.findAndCount({
where: {
keycloak: IsNull(),
isActive: false,
firstName: Like(`%${body.keyword}%`),
},
relations: ["posType", "posLevel", "current_holders"],
@ -3974,7 +4013,7 @@ export class ProfileEmployeeTempController extends Controller {
case "lastname":
[findProfile, total] = await this.profileRepo.findAndCount({
where: {
keycloak: IsNull(),
isActive: false,
lastName: Like(`%${body.keyword}%`),
},
relations: ["posType", "posLevel", "current_holders"],
@ -3986,7 +4025,7 @@ export class ProfileEmployeeTempController extends Controller {
default:
[findProfile, total] = await this.profileRepo.findAndCount({
where: {
keycloak: IsNull(),
isActive: false,
},
relations: ["posType", "posLevel", "current_holders"],
skip,
@ -4043,7 +4082,7 @@ export class ProfileEmployeeTempController extends Controller {
)?.posMasterNo;
const latestProfileEducation = await this.profileEducationRepo.findOne({
where: { profileEmployeeId: item.id },
where: { profileEmployeeId: item.id, isDeleted: false },
order: { level: "ASC" },
});

View file

@ -110,23 +110,25 @@ export class ProfileGovernmentHistoryController extends Controller {
orgShortName = posMaster.orgChild4?.orgChild4ShortName;
}
}
//posMaster?.isSit แก้ไขชั่วคราว
const data = {
org: org, //สังกัด
positionField: position == null ? null : position.positionField, //สายงาน
positionField: position == null || posMaster?.isSit ? null : position.positionField, //สายงาน
position: record.position, //ตำแหน่ง
posLevel: record.posLevel == null ? null : record.posLevel.posLevelName, //ระดับ
posMasterNo: posMaster == null ? null : `${orgShortName} ${posMaster.posMasterNo}`, //เลขที่ตำแหน่ง
posType: record.posType == null ? null : record.posType.posTypeName, //ประเภท
posExecutive:
position == null || position.posExecutive == null
position == null || position.posExecutive == null || posMaster?.isSit
? null
: position.posExecutive.posExecutiveName, //ตำแหน่งทางการบริหาร
positionArea: position == null ? null : position.positionArea, //ด้าน/สาขา
positionExecutiveField: position == null ? null : position.positionExecutiveField, //ด้านทางการบริหาร
positionArea: position == null || posMaster?.isSit ? null : position.positionArea, //ด้าน/สาขา
positionExecutiveField: position == null || posMaster?.isSit ? null : position.positionExecutiveField, //ด้านทางการบริหาร
dateLeave: record.birthDate == null ? null : calculateRetireDate(record.birthDate),
dateRetireLaw: record.dateRetireLaw ?? null,
// govAge: record.dateStart == null ? null : calculateAge(record.dateStart),
govAge: await calculateGovAge(profile.id, "OFFICER"),
govAgeBkk: await calculateGovAge(profile.id, "OFFICER", true),
dateAppoint: record.dateAppoint,
dateStart: record.dateStart,
govAgeAbsent: record.govAgeAbsent,
@ -286,9 +288,10 @@ export class ProfileGovernmentHistoryController extends Controller {
}
}
const orgLeave = _OrgLeave.filter((x: any) => x !== undefined && x !== null).join("\n");
//posMaster?.isSit แก้ไขชั่วคราว
const data = {
org: record?.isLeave == false ? org : orgLeave, //สังกัด
positionField: position == null ? null : position.positionField, //สายงาน
positionField: position == null || posMaster?.isSit ? null : position.positionField, //สายงาน
position: record?.position, //ตำแหน่ง
posLevel: record?.posLevel == null ? null : record?.posLevel.posLevelName, //ระดับ
posMasterNo:
@ -301,15 +304,16 @@ export class ProfileGovernmentHistoryController extends Controller {
: null, //เลขที่ตำแหน่ง
posType: record?.posType == null ? null : record?.posType.posTypeName, //ประเภท
posExecutive:
position == null || position.posExecutive == null
position == null || position.posExecutive == null || posMaster?.isSit
? null
: position.posExecutive.posExecutiveName, //ตำแหน่งทางการบริหาร
positionArea: position == null ? null : position.positionArea, //ด้าน/สาขา
positionExecutiveField: position == null ? null : position.positionExecutiveField, //ด้านทางการบริหาร
positionArea: position == null || posMaster?.isSit ? null : position.positionArea, //ด้าน/สาขา
positionExecutiveField: position == null || posMaster?.isSit ? null : position.positionExecutiveField, //ด้านทางการบริหาร
dateLeave: record?.birthDate == null ? null : calculateRetireDate(record?.birthDate),
dateRetireLaw: record?.dateRetireLaw ?? null,
// govAge: record?.dateStart == null ? null : calculateAge(record?.dateStart),
govAge: await calculateGovAge(profileId, "OFFICER"),
govAgeBkk: await calculateGovAge(profileId, "OFFICER", true),
dateAppoint: record?.dateAppoint,
dateStart: record?.dateStart,
govAgeAbsent: record?.govAgeAbsent,
@ -459,9 +463,10 @@ export class ProfileGovernmentHistoryController extends Controller {
}
}
const orgLeave = _OrgLeave.filter((x: any) => x !== undefined && x !== null).join("\n");
//posMaster?.isSit แก้ไขชั่วคราว
const data = {
org: record?.isLeave == false ? org : orgLeave, //สังกัด
positionField: position == null ? null : position.positionField, //สายงาน
positionField: position == null || posMaster?.isSit ? null : position.positionField, //สายงาน
position: record?.position, //ตำแหน่ง
posLevel: record?.posLevel == null ? null : record?.posLevel.posLevelName, //ระดับ
posMasterNo:
@ -474,15 +479,16 @@ export class ProfileGovernmentHistoryController extends Controller {
: null, //เลขที่ตำแหน่ง
posType: record?.posType == null ? null : record?.posType.posTypeName, //ประเภท
posExecutive:
position == null || position.posExecutive == null
position == null || position.posExecutive == null || posMaster?.isSit
? null
: position.posExecutive.posExecutiveName, //ตำแหน่งทางการบริหาร
positionArea: position == null ? null : position.positionArea, //ด้าน/สาขา
positionExecutiveField: position == null ? null : position.positionExecutiveField, //ด้านทางการบริหาร
positionArea: position == null || posMaster?.isSit ? null : position.positionArea, //ด้าน/สาขา
positionExecutiveField: position == null || posMaster?.isSit ? null : position.positionExecutiveField, //ด้านทางการบริหาร
dateLeave: record?.birthDate == null ? null : calculateRetireDate(record?.birthDate),
dateRetireLaw: record?.dateRetireLaw ?? null,
// govAge: record?.dateStart == null ? null : calculateAge(record?.dateStart),
govAge: await calculateGovAge(profileId, "OFFICER"),
govAgeBkk: await calculateGovAge(profileId, "OFFICER", true),
dateAppoint: record?.dateAppoint,
dateStart: record?.dateStart,
govAgeAbsent: record?.govAgeAbsent,

View file

@ -121,6 +121,7 @@ export class ProfileGovernmentEmployeeController extends Controller {
dateRetireLaw: record.dateRetireLaw ?? null,
// govAge: record.dateStart == null ? null : calculateAge(record.dateStart),
govAge: await calculateGovAge(profile.id, "EMPLOYEE"),
govAgeBkk: await calculateGovAge(profile.id, "EMPLOYEE", true),
dateAppoint: record.dateAppoint,
dateStart: record.dateStart,
govAgeAbsent: record.govAgeAbsent,
@ -292,6 +293,7 @@ export class ProfileGovernmentEmployeeController extends Controller {
dateRetire: record?.dateRetire ?? null, //วันครบเกษียณอายุ
// govAge: record?.dateStart == null ? null : calculateAge(record?.dateStart), //อายุราชการ
govAge: await calculateGovAge(profileEmployeeId, "EMPLOYEE"),
govAgeBkk: await calculateGovAge(profileEmployeeId, "EMPLOYEE", true),
govAgeAbsent: record?.govAgeAbsent ?? null, // ขาดราชการ
govAgePlus: record?.govAgePlus, // อายุราชการเกื้อกูล
dateRetireLaw: record?.dateRetireLaw ?? null, // วันที่เกษียฯอายุราชการตามกฎหมาย
@ -451,6 +453,7 @@ export class ProfileGovernmentEmployeeController extends Controller {
dateRetire: record?.dateRetire ?? null, //วันครบเกษียณอายุ
// govAge: record?.dateStart == null ? null : calculateAge(record?.dateStart), //อายุราชการ
govAge: await calculateGovAge(profileEmployeeId, "EMPLOYEE"),
govAgeBkk: await calculateGovAge(profileEmployeeId, "EMPLOYEE", true),
govAgeAbsent: record?.govAgeAbsent ?? null, // ขาดราชการ
govAgePlus: record?.govAgePlus, // อายุราชการเกื้อกูล
dateRetireLaw: record?.dateRetireLaw ?? null, // วันที่เกษียฯอายุราชการตามกฎหมาย

View file

@ -36,7 +36,7 @@ export class ProfileHonorController extends Controller {
throw new HttpError(HttpStatus.BAD_REQUEST, "ไม่พบ profile ดังกล่าว");
}
const record = await this.honorRepo.find({
where: { profileId: profile.id },
where: { profileId: profile.id, isDeleted: false },
order: { createdAt: "ASC" },
});
return new HttpSuccess(record);
@ -48,7 +48,7 @@ export class ProfileHonorController extends Controller {
if (_workflow == false)
await new permission().PermissionOrgUserGet(req, "SYS_REGISTRY_OFFICER", profileId);
const record = await this.honorRepo.find({
where: { profileId: profileId },
where: { profileId: profileId, isDeleted: false },
order: { createdAt: "ASC" },
});
return new HttpSuccess(record);
@ -176,6 +176,45 @@ export class ProfileHonorController extends Controller {
return new HttpSuccess();
}
/**
* API
* @summary API
* @param honorId
*/
@Patch("update-delete/{honorId}")
public async updateIsDeleted(
@Request() req: RequestWithUser,
@Path() honorId: string,
) {
const record = await this.honorRepo.findOneBy({ id: honorId });
if (!record) throw new HttpError(HttpStatus.NOT_FOUND, "ไม่พบข้อมูล");
if (record.isDeleted === true) {
return new HttpSuccess();
}
await new permission().PermissionOrgUserDelete(req, "SYS_REGISTRY_OFFICER", record.profileId);
const before = structuredClone(record);
const history = new ProfileHonorHistory();
const now = new Date();
record.isDeleted = true;
record.lastUpdateUserId = req.user.sub;
record.lastUpdateFullName = req.user.name;
record.lastUpdatedAt = now;
Object.assign(history, { ...record, id: undefined });
history.profileHonorId = honorId;
history.createdUserId = req.user.sub;
history.createdFullName = req.user.name;
history.createdAt = now;
await Promise.all([
this.honorRepo.save(record, { data: req }),
setLogDataDiff(req, { before, after: record }),
this.honorHistoryRepo.save(history, { data: req }),
]);
return new HttpSuccess();
}
@Delete("{honorId}")
public async deleteTraning(@Path() honorId: string, @Request() req: RequestWithUser) {
const _record = await this.honorRepo.findOneBy({ id: honorId });

View file

@ -40,7 +40,7 @@ export class ProfileHonorEmployeeController extends Controller {
throw new HttpError(HttpStatus.BAD_REQUEST, "ไม่พบ profile ดังกล่าว");
}
const record = await this.honorRepo.find({
where: { profileEmployeeId: profile.id },
where: { profileEmployeeId: profile.id, isDeleted: false },
order: { createdAt: "ASC" },
});
return new HttpSuccess(record);
@ -52,7 +52,7 @@ export class ProfileHonorEmployeeController extends Controller {
if (_workflow == false)
await new permission().PermissionOrgUserGet(req, "SYS_REGISTRY_EMP", profileEmployeeId);
const record = await this.honorRepo.find({
where: { profileEmployeeId: profileEmployeeId },
where: { profileEmployeeId: profileEmployeeId, isDeleted: false },
order: { createdAt: "ASC" },
});
return new HttpSuccess(record);
@ -188,6 +188,45 @@ export class ProfileHonorEmployeeController extends Controller {
return new HttpSuccess();
}
/**
* API
* @summary API
* @param honorId
*/
@Patch("update-delete/{honorId}")
public async updateIsDeleted(
@Request() req: RequestWithUser,
@Path() honorId: string,
) {
const record = await this.honorRepo.findOneBy({ id: honorId });
if (!record) throw new HttpError(HttpStatus.NOT_FOUND, "ไม่พบข้อมูล");
if (record.isDeleted === true) {
return new HttpSuccess();
}
await new permission().PermissionOrgUserDelete(req, "SYS_REGISTRY_EMP", record.profileEmployeeId);
const before = structuredClone(record);
const history = new ProfileHonorHistory();
const now = new Date();
record.isDeleted = true;
record.lastUpdateUserId = req.user.sub;
record.lastUpdateFullName = req.user.name;
record.lastUpdatedAt = now;
Object.assign(history, { ...record, id: undefined });
history.profileHonorId = honorId;
history.createdUserId = req.user.sub;
history.createdFullName = req.user.name;
history.createdAt = now;
await Promise.all([
this.honorRepo.save(record, { data: req }),
setLogDataDiff(req, { before, after: record }),
this.honorHistoryRepo.save(history, { data: req }),
]);
return new HttpSuccess();
}
@Delete("{honorId}")
public async deleteTraning(@Path() honorId: string, @Request() req: RequestWithUser) {
const _record = await this.honorRepo.findOneBy({ id: honorId });

View file

@ -40,7 +40,7 @@ export class ProfileHonorEmployeeTempController extends Controller {
throw new HttpError(HttpStatus.BAD_REQUEST, "ไม่พบ profile ดังกล่าว");
}
const record = await this.honorRepo.find({
where: { profileEmployeeId: profile.id },
where: { profileEmployeeId: profile.id, isDeleted: false },
order: { createdAt: "ASC" },
});
return new HttpSuccess(record);
@ -51,7 +51,7 @@ export class ProfileHonorEmployeeTempController extends Controller {
let _workflow = await new permission().Workflow(req, profileEmployeeId, "SYS_REGISTRY_TEMP");
if (_workflow == false) await new permission().PermissionGet(req, "SYS_REGISTRY_TEMP");
const record = await this.honorRepo.find({
where: { profileEmployeeId: profileEmployeeId },
where: { profileEmployeeId: profileEmployeeId, isDeleted: false },
order: { createdAt: "ASC" },
});
return new HttpSuccess(record);
@ -176,6 +176,45 @@ export class ProfileHonorEmployeeTempController extends Controller {
return new HttpSuccess();
}
/**
* API
* @summary API
* @param honorId
*/
@Patch("update-delete/{honorId}")
public async updateIsDeleted(
@Request() req: RequestWithUser,
@Path() honorId: string,
) {
const record = await this.honorRepo.findOneBy({ id: honorId });
if (!record) throw new HttpError(HttpStatus.NOT_FOUND, "ไม่พบข้อมูล");
if (record.isDeleted === true) {
return new HttpSuccess();
}
await new permission().PermissionDelete(req, "SYS_REGISTRY_TEMP");
const before = structuredClone(record);
const history = new ProfileHonorHistory();
const now = new Date();
record.isDeleted = true;
record.lastUpdateUserId = req.user.sub;
record.lastUpdateFullName = req.user.name;
record.lastUpdatedAt = now;
Object.assign(history, { ...record, id: undefined });
history.profileHonorId = honorId;
history.createdUserId = req.user.sub;
history.createdFullName = req.user.name;
history.createdAt = now;
await Promise.all([
this.honorRepo.save(record, { data: req }),
setLogDataDiff(req, { before, after: record }),
this.honorHistoryRepo.save(history, { data: req }),
]);
return new HttpSuccess();
}
@Delete("{honorId}")
public async deleteTraning(@Path() honorId: string, @Request() req: RequestWithUser) {
await new permission().PermissionDelete(req, "SYS_REGISTRY_TEMP");

View file

@ -50,7 +50,7 @@ export class ProfileInsigniaController extends Controller {
insigniaType: true,
},
},
where: { profileId: profile.id },
where: { profileId: profile.id, isDeleted: false },
order: { createdAt: "ASC" },
});
return new HttpSuccess(record);
@ -67,7 +67,7 @@ export class ProfileInsigniaController extends Controller {
insigniaType: true,
},
},
where: { profileId },
where: { profileId, isDeleted: false },
order: { createdAt: "ASC" },
});
return new HttpSuccess(record);
@ -199,6 +199,45 @@ export class ProfileInsigniaController extends Controller {
return new HttpSuccess();
}
/**
* API
* @summary API
* @param insigniaId
*/
@Patch("update-delete/{insigniaId}")
public async updateIsDeleted(
@Request() req: RequestWithUser,
@Path() insigniaId: string,
) {
const record = await this.insigniaRepo.findOneBy({ id: insigniaId });
if (!record) throw new HttpError(HttpStatus.NOT_FOUND, "ไม่พบข้อมูล");
if (record.isDeleted === true) {
return new HttpSuccess();
}
await new permission().PermissionOrgUserDelete(req, "SYS_REGISTRY_OFFICER", record.profileId);
const before = structuredClone(record);
const history = new ProfileInsigniaHistory();
const now = new Date();
record.isDeleted = true;
record.lastUpdateUserId = req.user.sub;
record.lastUpdateFullName = req.user.name;
record.lastUpdatedAt = now;
Object.assign(history, { ...record, id: undefined });
history.profileInsigniaId = insigniaId;
history.createdUserId = req.user.sub;
history.createdFullName = req.user.name;
history.createdAt = now;
await Promise.all([
this.insigniaRepo.save(record, { data: req }),
setLogDataDiff(req, { before, after: record }),
this.insigniaHistoryRepo.save(history, { data: req }),
]);
return new HttpSuccess();
}
@Delete("{insigniaId}")
public async deleteInsignia(@Path() insigniaId: string, @Request() req: RequestWithUser) {
const _record = await this.insigniaRepo.findOneBy({ id: insigniaId });

View file

@ -47,7 +47,7 @@ export class ProfileInsigniaEmployeeController extends Controller {
insigniaType: true,
},
},
where: { profileEmployeeId: profile.id },
where: { profileEmployeeId: profile.id, isDeleted: false },
order: { createdAt: "ASC" },
});
return new HttpSuccess(record);
@ -64,7 +64,7 @@ export class ProfileInsigniaEmployeeController extends Controller {
insigniaType: true,
},
},
where: { profileEmployeeId },
where: { profileEmployeeId, isDeleted: false },
order: { createdAt: "ASC" },
});
return new HttpSuccess(record);
@ -207,6 +207,45 @@ export class ProfileInsigniaEmployeeController extends Controller {
return new HttpSuccess();
}
/**
* API
* @summary API
* @param insigniaId
*/
@Patch("update-delete/{insigniaId}")
public async updateIsDeleted(
@Request() req: RequestWithUser,
@Path() insigniaId: string,
) {
const record = await this.insigniaRepo.findOneBy({ id: insigniaId });
if (!record) throw new HttpError(HttpStatus.NOT_FOUND, "ไม่พบข้อมูล");
if (record.isDeleted === true) {
return new HttpSuccess();
}
await new permission().PermissionOrgUserDelete(req, "SYS_REGISTRY_EMP", record.profileEmployeeId);
const before = structuredClone(record);
const history = new ProfileInsigniaHistory();
const now = new Date();
record.isDeleted = true;
record.lastUpdateUserId = req.user.sub;
record.lastUpdateFullName = req.user.name;
record.lastUpdatedAt = now;
Object.assign(history, { ...record, id: undefined });
history.profileInsigniaId = insigniaId;
history.createdUserId = req.user.sub;
history.createdFullName = req.user.name;
history.createdAt = now;
await Promise.all([
this.insigniaRepo.save(record, { data: req }),
setLogDataDiff(req, { before, after: record }),
this.insigniaHistoryRepo.save(history, { data: req }),
]);
return new HttpSuccess();
}
@Delete("{insigniaId}")
public async deleteInsignia(@Path() insigniaId: string, @Request() req: RequestWithUser) {
const _record = await this.insigniaRepo.findOneBy({ id: insigniaId });

View file

@ -47,7 +47,7 @@ export class ProfileInsigniaEmployeeTempController extends Controller {
insigniaType: true,
},
},
where: { profileEmployeeId: profile.id },
where: { profileEmployeeId: profile.id, isDeleted: false },
order: { createdAt: "ASC" },
});
return new HttpSuccess(record);
@ -63,7 +63,7 @@ export class ProfileInsigniaEmployeeTempController extends Controller {
insigniaType: true,
},
},
where: { profileEmployeeId },
where: { profileEmployeeId, isDeleted: false },
order: { createdAt: "ASC" },
});
return new HttpSuccess(record);
@ -196,6 +196,45 @@ export class ProfileInsigniaEmployeeTempController extends Controller {
return new HttpSuccess();
}
/**
* API
* @summary API
* @param insigniaId
*/
@Patch("update-delete/{insigniaId}")
public async updateIsDeleted(
@Request() req: RequestWithUser,
@Path() insigniaId: string,
) {
const record = await this.insigniaRepo.findOneBy({ id: insigniaId });
if (!record) throw new HttpError(HttpStatus.NOT_FOUND, "ไม่พบข้อมูล");
if (record.isDeleted === true) {
return new HttpSuccess();
}
await new permission().PermissionDelete(req, "SYS_REGISTRY_TEMP");
const before = structuredClone(record);
const history = new ProfileInsigniaHistory();
const now = new Date();
record.isDeleted = true;
record.lastUpdateUserId = req.user.sub;
record.lastUpdateFullName = req.user.name;
record.lastUpdatedAt = now;
Object.assign(history, { ...record, id: undefined });
history.profileInsigniaId = insigniaId;
history.createdUserId = req.user.sub;
history.createdFullName = req.user.name;
history.createdAt = now;
await Promise.all([
this.insigniaRepo.save(record, { data: req }),
setLogDataDiff(req, { before, after: record }),
this.insigniaHistoryRepo.save(history, { data: req }),
]);
return new HttpSuccess();
}
@Delete("{insigniaId}")
public async deleteInsignia(@Path() insigniaId: string, @Request() req: RequestWithUser) {
await new permission().PermissionDelete(req, "SYS_REGISTRY_TEMP");

View file

@ -120,7 +120,7 @@ export class ProfileLeaveController extends Controller {
}
const record = await this.leaveRepo.find({
relations: { leaveType: true },
where: { profileId: profile.id },
where: { profileId: profile.id, isDeleted: false },
order: { createdAt: "ASC" },
});
return new HttpSuccess(record);
@ -136,6 +136,7 @@ export class ProfileLeaveController extends Controller {
where: {
profileId: profileId,
// status: Not("cancel")
isDeleted: false
},
order: { createdAt: "ASC" },
});
@ -148,7 +149,7 @@ export class ProfileLeaveController extends Controller {
if (_workflow == false) await new permission().PermissionGet(req, "SYS_SALARY_OFFICER");
const record = await this.leaveRepo.find({
relations: { leaveType: true },
where: { profileId },
where: { profileId, isDeleted: false },
order: { createdAt: "ASC" },
});
return new HttpSuccess(record);
@ -264,6 +265,45 @@ export class ProfileLeaveController extends Controller {
return new HttpSuccess();
}
/**
* API
* @summary API
* @param leaveId
*/
@Patch("update-delete/{leaveId}")
public async updateIsDeleted(
@Request() req: RequestWithUser,
@Path() leaveId: string,
) {
const record = await this.leaveRepo.findOneBy({ id: leaveId });
if (!record) throw new HttpError(HttpStatus.NOT_FOUND, "ไม่พบข้อมูล");
if (record.isDeleted === true) {
return new HttpSuccess();
}
await new permission().PermissionOrgUserDelete(req, "SYS_REGISTRY_OFFICER", record.profileId);
const before = structuredClone(record);
const history = new ProfileLeaveHistory();
const now = new Date();
record.isDeleted = true;
record.lastUpdateUserId = req.user.sub;
record.lastUpdateFullName = req.user.name;
record.lastUpdatedAt = now;
Object.assign(history, { ...record, id: undefined });
history.profileLeaveId = leaveId;
history.createdUserId = req.user.sub;
history.createdFullName = req.user.name;
history.createdAt = now;
await Promise.all([
this.leaveRepo.save(record, { data: req }),
setLogDataDiff(req, { before, after: record }),
this.leaveHistoryRepo.save(history, { data: req }),
]);
return new HttpSuccess();
}
@Patch("cancel/{leaveId}")
public async updateCancel(
@Request() req: RequestWithUser,

View file

@ -43,7 +43,7 @@ export class ProfileLeaveEmployeeController extends Controller {
}
const record = await this.leaveRepo.find({
relations: { leaveType: true },
where: { profileEmployeeId: profile.id },
where: { profileEmployeeId: profile.id, isDeleted: false },
order: { createdAt: "ASC" },
});
return new HttpSuccess(record);
@ -56,7 +56,7 @@ export class ProfileLeaveEmployeeController extends Controller {
await new permission().PermissionOrgUserGet(req, "SYS_REGISTRY_EMP", profileId);
const record = await this.leaveRepo.find({
relations: { leaveType: true },
where: { profileEmployeeId: profileId },
where: { profileEmployeeId: profileId, isDeleted: false },
order: { createdAt: "ASC" },
});
return new HttpSuccess(record);
@ -68,7 +68,7 @@ export class ProfileLeaveEmployeeController extends Controller {
if (_workflow == false) await new permission().PermissionGet(req, "SYS_WAGE");
const record = await this.leaveRepo.find({
relations: { leaveType: true },
where: { profileEmployeeId: profileId },
where: { profileEmployeeId: profileId, isDeleted: false },
order: { createdAt: "ASC" },
});
return new HttpSuccess(record);
@ -192,6 +192,45 @@ export class ProfileLeaveEmployeeController extends Controller {
return new HttpSuccess();
}
/**
* API
* @summary API
* @param leaveId
*/
@Patch("update-delete/{leaveId}")
public async updateIsDeleted(
@Request() req: RequestWithUser,
@Path() leaveId: string,
) {
const record = await this.leaveRepo.findOneBy({ id: leaveId });
if (!record) throw new HttpError(HttpStatus.NOT_FOUND, "ไม่พบข้อมูล");
if (record.isDeleted === true) {
return new HttpSuccess();
}
await new permission().PermissionOrgUserDelete(req, "SYS_REGISTRY_EMP", record.profileEmployeeId);
const before = structuredClone(record);
const history = new ProfileLeaveHistory();
const now = new Date();
record.isDeleted = true;
record.lastUpdateUserId = req.user.sub;
record.lastUpdateFullName = req.user.name;
record.lastUpdatedAt = now;
Object.assign(history, { ...record, id: undefined });
history.profileLeaveId = leaveId;
history.createdUserId = req.user.sub;
history.createdFullName = req.user.name;
history.createdAt = now;
await Promise.all([
this.leaveRepo.save(record, { data: req }),
setLogDataDiff(req, { before, after: record }),
this.leaveHistoryRepo.save(history, { data: req }),
]);
return new HttpSuccess();
}
@Delete("{leaveId}")
public async deleteLeave(@Path() leaveId: string, @Request() req: RequestWithUser) {
const _record = await this.leaveRepo.findOneBy({ id: leaveId });

View file

@ -43,7 +43,7 @@ export class ProfileLeaveEmployeeTempController extends Controller {
}
const record = await this.leaveRepo.find({
relations: { leaveType: true },
where: { profileEmployeeId: profile.id },
where: { profileEmployeeId: profile.id, isDeleted: false },
order: { createdAt: "ASC" },
});
return new HttpSuccess(record);
@ -55,7 +55,7 @@ export class ProfileLeaveEmployeeTempController extends Controller {
if (_workflow == false) await new permission().PermissionGet(req, "SYS_REGISTRY_TEMP");
const record = await this.leaveRepo.find({
relations: { leaveType: true },
where: { profileEmployeeId: profileId },
where: { profileEmployeeId: profileId, isDeleted: false },
order: { createdAt: "ASC" },
});
return new HttpSuccess(record);
@ -67,7 +67,7 @@ export class ProfileLeaveEmployeeTempController extends Controller {
if (_workflow == false) await new permission().PermissionGet(req, "SYS_WAGE");
const record = await this.leaveRepo.find({
relations: { leaveType: true },
where: { profileEmployeeId: profileId },
where: { profileEmployeeId: profileId, isDeleted: false },
order: { createdAt: "ASC" },
});
return new HttpSuccess(record);
@ -179,6 +179,45 @@ export class ProfileLeaveEmployeeTempController extends Controller {
return new HttpSuccess();
}
/**
* API
* @summary API
* @param leaveId
*/
@Patch("update-delete/{leaveId}")
public async updateIsDeleted(
@Request() req: RequestWithUser,
@Path() leaveId: string,
) {
const record = await this.leaveRepo.findOneBy({ id: leaveId });
if (!record) throw new HttpError(HttpStatus.NOT_FOUND, "ไม่พบข้อมูล");
if (record.isDeleted === true) {
return new HttpSuccess();
}
await new permission().PermissionDelete(req, "SYS_REGISTRY_TEMP");
const before = structuredClone(record);
const history = new ProfileLeaveHistory();
const now = new Date();
record.isDeleted = true;
record.lastUpdateUserId = req.user.sub;
record.lastUpdateFullName = req.user.name;
record.lastUpdatedAt = now;
Object.assign(history, { ...record, id: undefined });
history.profileLeaveId = leaveId;
history.createdUserId = req.user.sub;
history.createdFullName = req.user.name;
history.createdAt = now;
await Promise.all([
this.leaveRepo.save(record, { data: req }),
setLogDataDiff(req, { before, after: record }),
this.leaveHistoryRepo.save(history, { data: req }),
]);
return new HttpSuccess();
}
@Delete("{leaveId}")
public async deleteLeave(@Path() leaveId: string, @Request() req: RequestWithUser) {
await new permission().PermissionDelete(req, "SYS_REGISTRY_TEMP");

View file

@ -36,7 +36,7 @@ export class ProfileNopaidController extends Controller {
throw new HttpError(HttpStatus.BAD_REQUEST, "ไม่พบ profile ดังกล่าว");
}
const lists = await this.nopaidRepository.find({
where: { profileId: profile.id },
where: { profileId: profile.id, isDeleted: false },
order: { createdAt: "ASC" },
});
return new HttpSuccess(lists);
@ -48,7 +48,7 @@ export class ProfileNopaidController extends Controller {
if (_workflow == false)
await new permission().PermissionOrgUserGet(req, "SYS_REGISTRY_OFFICER", profileId);
const lists = await this.nopaidRepository.find({
where: { profileId },
where: { profileId, isDeleted: false },
order: { createdAt: "ASC" },
});
return new HttpSuccess(lists);
@ -140,6 +140,45 @@ export class ProfileNopaidController extends Controller {
return new HttpSuccess();
}
/**
* API
* @summary API
* @param nopaidId
*/
@Patch("update-delete/{nopaidId}")
public async updateIsDeleted(
@Request() req: RequestWithUser,
@Path() nopaidId: string,
) {
const record = await this.nopaidRepository.findOneBy({ id: nopaidId });
if (!record) throw new HttpError(HttpStatus.NOT_FOUND, "ไม่พบข้อมูล");
if (record.isDeleted === true) {
return new HttpSuccess();
}
await new permission().PermissionOrgUserDelete(req, "SYS_REGISTRY_OFFICER", record.profileId);
const before = structuredClone(record);
const history = new ProfileNopaidHistory();
const now = new Date();
record.isDeleted = true;
record.lastUpdateUserId = req.user.sub;
record.lastUpdateFullName = req.user.name;
record.lastUpdatedAt = now;
Object.assign(history, { ...record, id: undefined });
history.profileNopaidId = nopaidId;
history.createdUserId = req.user.sub;
history.createdFullName = req.user.name;
history.createdAt = now;
await Promise.all([
this.nopaidRepository.save(record, { data: req }),
setLogDataDiff(req, { before, after: record }),
this.nopaidHistoryRepository.save(history, { data: req }),
]);
return new HttpSuccess();
}
@Delete("{nopaidId}")
public async deleteNopaid(@Path() nopaidId: string, @Request() req: RequestWithUser) {
const _record = await this.nopaidRepository.findOneBy({ id: nopaidId });

View file

@ -40,7 +40,7 @@ export class ProfileNopaidEmployeeController extends Controller {
throw new HttpError(HttpStatus.BAD_REQUEST, "ไม่พบ profile ดังกล่าว");
}
const lists = await this.nopaidRepository.find({
where: { profileEmployeeId: profile.id },
where: { profileEmployeeId: profile.id, isDeleted: false },
order: { createdAt: "ASC" },
});
return new HttpSuccess(lists);
@ -51,7 +51,7 @@ export class ProfileNopaidEmployeeController extends Controller {
let _workflow = await new permission().Workflow(req, profileId, "SYS_REGISTRY_EMP");
if (_workflow == false) await new permission().PermissionGet(req, "SYS_REGISTRY_EMP");
const lists = await this.nopaidRepository.find({
where: { profileEmployeeId: profileId },
where: { profileEmployeeId: profileId, isDeleted: false },
order: { createdAt: "ASC" },
});
return new HttpSuccess(lists);
@ -147,6 +147,45 @@ export class ProfileNopaidEmployeeController extends Controller {
return new HttpSuccess();
}
/**
* API
* @summary API
* @param nopaidId
*/
@Patch("update-delete/{nopaidId}")
public async updateIsDeleted(
@Request() req: RequestWithUser,
@Path() nopaidId: string,
) {
const record = await this.nopaidRepository.findOneBy({ id: nopaidId });
if (!record) throw new HttpError(HttpStatus.NOT_FOUND, "ไม่พบข้อมูล");
if (record.isDeleted === true) {
return new HttpSuccess();
}
await new permission().PermissionOrgUserDelete(req, "SYS_REGISTRY_EMP", record.profileEmployeeId);
const before = structuredClone(record);
const history = new ProfileNopaidHistory();
const now = new Date();
record.isDeleted = true;
record.lastUpdateUserId = req.user.sub;
record.lastUpdateFullName = req.user.name;
record.lastUpdatedAt = now;
Object.assign(history, { ...record, id: undefined });
history.profileNopaidId = nopaidId;
history.createdUserId = req.user.sub;
history.createdFullName = req.user.name;
history.createdAt = now;
await Promise.all([
this.nopaidRepository.save(record, { data: req }),
setLogDataDiff(req, { before, after: record }),
this.nopaidHistoryRepository.save(history, { data: req }),
]);
return new HttpSuccess();
}
@Delete("{nopaidId}")
public async deleteNopaid(@Path() nopaidId: string, @Request() req: RequestWithUser) {
const _record = await this.nopaidRepository.findOneBy({ id: nopaidId });

View file

@ -40,7 +40,7 @@ export class ProfileNopaidEmployeeTempController extends Controller {
throw new HttpError(HttpStatus.BAD_REQUEST, "ไม่พบ profile ดังกล่าว");
}
const lists = await this.nopaidRepository.find({
where: { profileEmployeeId: profile.id },
where: { profileEmployeeId: profile.id, isDeleted: false },
order: { createdAt: "ASC" },
});
return new HttpSuccess(lists);
@ -51,7 +51,7 @@ export class ProfileNopaidEmployeeTempController extends Controller {
let _workflow = await new permission().Workflow(req, profileId, "SYS_REGISTRY_TEMP");
if (_workflow == false) await new permission().PermissionGet(req, "SYS_REGISTRY_TEMP");
const lists = await this.nopaidRepository.find({
where: { profileEmployeeId: profileId },
where: { profileEmployeeId: profileId, isDeleted: false },
order: { createdAt: "ASC" },
});
return new HttpSuccess(lists);
@ -144,6 +144,45 @@ export class ProfileNopaidEmployeeTempController extends Controller {
return new HttpSuccess();
}
/**
* API
* @summary API
* @param nopaidId
*/
@Patch("update-delete/{nopaidId}")
public async updateIsDeleted(
@Request() req: RequestWithUser,
@Path() nopaidId: string,
) {
const record = await this.nopaidRepository.findOneBy({ id: nopaidId });
if (!record) throw new HttpError(HttpStatus.NOT_FOUND, "ไม่พบข้อมูล");
if (record.isDeleted === true) {
return new HttpSuccess();
}
await new permission().PermissionDelete(req, "SYS_REGISTRY_TEMP");
const before = structuredClone(record);
const history = new ProfileNopaidHistory();
const now = new Date();
record.isDeleted = true;
record.lastUpdateUserId = req.user.sub;
record.lastUpdateFullName = req.user.name;
record.lastUpdatedAt = now;
Object.assign(history, { ...record, id: undefined });
history.profileNopaidId = nopaidId;
history.createdUserId = req.user.sub;
history.createdFullName = req.user.name;
history.createdAt = now;
await Promise.all([
this.nopaidRepository.save(record, { data: req }),
setLogDataDiff(req, { before, after: record }),
this.nopaidHistoryRepository.save(history, { data: req }),
]);
return new HttpSuccess();
}
@Delete("{nopaidId}")
public async deleteNopaid(@Path() nopaidId: string, @Request() req: RequestWithUser) {
await new permission().PermissionDelete(req, "SYS_REGISTRY_TEMP");

View file

@ -37,7 +37,7 @@ export class ProfileOtherController extends Controller {
throw new HttpError(HttpStatus.BAD_REQUEST, "ไม่พบ profile ดังกล่าว");
}
const lists = await this.otherRepository.find({
where: { profileId: profile.id },
where: { profileId: profile.id, isDeleted: false },
order: { createdAt: "ASC" },
});
return new HttpSuccess(lists);
@ -49,7 +49,7 @@ export class ProfileOtherController extends Controller {
if (_workflow == false)
await new permission().PermissionOrgUserGet(req, "SYS_REGISTRY_OFFICER", profileId);
const lists = await this.otherRepository.find({
where: { profileId: profileId },
where: { profileId: profileId, isDeleted: false },
order: { createdAt: "ASC" },
});
return new HttpSuccess(lists);
@ -149,6 +149,45 @@ export class ProfileOtherController extends Controller {
return new HttpSuccess();
}
/**
* API
* @summary API
* @param otherId
*/
@Patch("update-delete/{otherId}")
public async updateIsDeleted(
@Request() req: RequestWithUser,
@Path() otherId: string,
) {
const record = await this.otherRepository.findOneBy({ id: otherId });
if (!record) throw new HttpError(HttpStatus.NOT_FOUND, "ไม่พบข้อมูล");
if (record.isDeleted === true) {
return new HttpSuccess();
}
await new permission().PermissionOrgUserDelete(req, "SYS_REGISTRY_OFFICER", record.profileId);
const before = structuredClone(record);
const history = new ProfileOtherHistory();
const now = new Date();
record.isDeleted = true;
record.lastUpdateUserId = req.user.sub;
record.lastUpdateFullName = req.user.name;
record.lastUpdatedAt = now;
Object.assign(history, { ...record, id: undefined });
history.profileOtherId = otherId;
history.createdUserId = req.user.sub;
history.createdFullName = req.user.name;
history.createdAt = now;
await Promise.all([
this.otherRepository.save(record, { data: req }),
setLogDataDiff(req, { before, after: record }),
this.otherHistoryRepository.save(history, { data: req }),
]);
return new HttpSuccess();
}
@Delete("{otherId}")
public async deleteOther(@Path() otherId: string, @Request() req: RequestWithUser) {
const _record = await this.otherRepository.findOneBy({ id: otherId });

View file

@ -40,7 +40,7 @@ export class ProfileOtherEmployeeController extends Controller {
throw new HttpError(HttpStatus.BAD_REQUEST, "ไม่พบ profile ดังกล่าว");
}
const lists = await this.otherRepository.find({
where: { profileEmployeeId: profile.id },
where: { profileEmployeeId: profile.id, isDeleted: false },
order: { createdAt: "ASC" },
});
return new HttpSuccess(lists);
@ -52,7 +52,7 @@ export class ProfileOtherEmployeeController extends Controller {
if (_workflow == false)
await new permission().PermissionOrgUserGet(req, "SYS_REGISTRY_EMP", profileId);
const lists = await this.otherRepository.find({
where: { profileEmployeeId: profileId },
where: { profileEmployeeId: profileId, isDeleted: false },
order: { createdAt: "ASC" },
});
return new HttpSuccess(lists);
@ -160,6 +160,45 @@ export class ProfileOtherEmployeeController extends Controller {
return new HttpSuccess();
}
/**
* API
* @summary API
* @param otherId
*/
@Patch("update-delete/{otherId}")
public async updateIsDeleted(
@Request() req: RequestWithUser,
@Path() otherId: string,
) {
const record = await this.otherRepository.findOneBy({ id: otherId });
if (!record) throw new HttpError(HttpStatus.NOT_FOUND, "ไม่พบข้อมูล");
if (record.isDeleted === true) {
return new HttpSuccess();
}
await new permission().PermissionOrgUserDelete(req, "SYS_REGISTRY_EMP", record.profileEmployeeId);
const before = structuredClone(record);
const history = new ProfileOtherHistory();
const now = new Date();
record.isDeleted = true;
record.lastUpdateUserId = req.user.sub;
record.lastUpdateFullName = req.user.name;
record.lastUpdatedAt = now;
Object.assign(history, { ...record, id: undefined });
history.profileOtherId = otherId;
history.createdUserId = req.user.sub;
history.createdFullName = req.user.name;
history.createdAt = now;
await Promise.all([
this.otherRepository.save(record, { data: req }),
setLogDataDiff(req, { before, after: record }),
this.otherHistoryRepository.save(history, { data: req }),
]);
return new HttpSuccess();
}
@Delete("{otherId}")
public async deleteOther(@Path() otherId: string, @Request() req: RequestWithUser) {
const _record = await this.otherRepository.findOneBy({ id: otherId });

View file

@ -40,7 +40,7 @@ export class ProfileOtherEmployeeTempController extends Controller {
throw new HttpError(HttpStatus.BAD_REQUEST, "ไม่พบ profile ดังกล่าว");
}
const lists = await this.otherRepository.find({
where: { profileEmployeeId: profile.id },
where: { profileEmployeeId: profile.id, isDeleted: false },
order: { createdAt: "ASC" },
});
return new HttpSuccess(lists);
@ -51,7 +51,7 @@ export class ProfileOtherEmployeeTempController extends Controller {
let _workflow = await new permission().Workflow(req, profileId, "SYS_REGISTRY_TEMP");
if (_workflow == false) await new permission().PermissionGet(req, "SYS_REGISTRY_TEMP");
const lists = await this.otherRepository.find({
where: { profileEmployeeId: profileId },
where: { profileEmployeeId: profileId, isDeleted: false },
order: { createdAt: "ASC" },
});
return new HttpSuccess(lists);
@ -148,6 +148,45 @@ export class ProfileOtherEmployeeTempController extends Controller {
return new HttpSuccess();
}
/**
* API
* @summary API
* @param otherId
*/
@Patch("update-delete/{otherId}")
public async updateIsDeleted(
@Request() req: RequestWithUser,
@Path() otherId: string,
) {
const record = await this.otherRepository.findOneBy({ id: otherId });
if (!record) throw new HttpError(HttpStatus.NOT_FOUND, "ไม่พบข้อมูล");
if (record.isDeleted === true) {
return new HttpSuccess();
}
await new permission().PermissionDelete(req, "SYS_REGISTRY_TEMP");
const before = structuredClone(record);
const history = new ProfileOtherHistory();
const now = new Date();
record.isDeleted = true;
record.lastUpdateUserId = req.user.sub;
record.lastUpdateFullName = req.user.name;
record.lastUpdatedAt = now;
Object.assign(history, { ...record, id: undefined });
history.profileOtherId = otherId;
history.createdUserId = req.user.sub;
history.createdFullName = req.user.name;
history.createdAt = now;
await Promise.all([
this.otherRepository.save(record, { data: req }),
setLogDataDiff(req, { before, after: record }),
this.otherHistoryRepository.save(history, { data: req }),
]);
return new HttpSuccess();
}
@Delete("{otherId}")
public async deleteOther(@Path() otherId: string, @Request() req: RequestWithUser) {
await new permission().PermissionDelete(req, "SYS_REGISTRY_TEMP");

View file

@ -397,8 +397,7 @@ export class ProfileSalaryController extends Controller {
const record = await this.salaryRepo.find({
where: {
profileId: profile.id,
// commandCode: In(["5", "6", "7"])
commandCode: In(["5", "6", "7"]),
commandCode: In(["5", "6", "7", "19"]),
},
order: { order: "ASC" },
});
@ -477,7 +476,7 @@ export class ProfileSalaryController extends Controller {
if (_workflow == false)
await new permission().PermissionOrgUserGet(req, "SYS_REGISTRY_OFFICER", profileId);
const record = await this.salaryRepo.find({
where: { profileId: profileId, commandCode: In(["5", "6", "7"]) },
where: { profileId: profileId, commandCode: In(["5", "6", "7", "19"]) },
order: { order: "ASC" },
});
// const result = record.map((r) => ({
@ -842,7 +841,7 @@ export class ProfileSalaryController extends Controller {
let _workflow = await new permission().Workflow(req, profileId, "SYS_SALARY_OFFICER");
if (_workflow == false) await new permission().PermissionGet(req, "SYS_SALARY_OFFICER");
const record = await this.salaryRepo.find({
where: { profileId: profileId, commandCode: In(["5", "6", "7"]) },
where: { profileId: profileId, commandCode: In(["5", "6", "7", "19"]) },
order: { order: "ASC" },
});
return new HttpSuccess(record);
@ -909,6 +908,7 @@ export class ProfileSalaryController extends Controller {
if (body.commandCode == "7") body.commandName = "เงินพิเศษอื่น ๆ";
else if (body.commandCode == "6") body.commandName = "เลื่อนเงินเดือนกรณีอื่น ๆ";
else if (body.commandCode == "5") body.commandName = "เลื่อนเงินเดือนตามปกติ";
else if (body.commandCode == "19") body.commandName = "ไม่ได้เลื่อนเงินเดือน/ค่าจ้าง";
}
Object.assign(data, { ...body, ...meta });
const history = new ProfileSalaryHistory();
@ -1032,6 +1032,7 @@ export class ProfileSalaryController extends Controller {
if (body.commandCode == "7") body.commandName = "เงินพิเศษอื่น ๆ";
else if (body.commandCode == "6") body.commandName = "เลื่อนเงินเดือนกรณีอื่น ๆ";
else if (body.commandCode == "5") body.commandName = "เลื่อนเงินเดือนตามปกติ";
else if (body.commandCode == "19") body.commandName = "ไม่ได้เลื่อนเงินเดือน/ค่าจ้าง";
}
Object.assign(record, body);
Object.assign(history, { ...record, id: undefined });

View file

@ -49,7 +49,7 @@ export class ProfileSalaryEmployeeController extends Controller {
throw new HttpError(HttpStatus.BAD_REQUEST, "ไม่พบ profile ดังกล่าว");
}
const record = await this.salaryRepo.find({
where: { profileEmployeeId: profile.id, commandCode: In(["5", "6", "7"]) },
where: { profileEmployeeId: profile.id, commandCode: In(["5", "6", "7", "19"]) },
order: { order: "ASC" },
});
return new HttpSuccess(record);
@ -96,7 +96,7 @@ export class ProfileSalaryEmployeeController extends Controller {
if (_workflow == false)
await new permission().PermissionOrgUserGet(req, "SYS_REGISTRY_EMP", profileId);
const record = await this.salaryRepo.find({
where: { profileEmployeeId: profileId, commandCode: In(["5", "6", "7"]) },
where: { profileEmployeeId: profileId, commandCode: In(["5", "6", "7", "19"]) },
order: { order: "ASC" },
});
return new HttpSuccess(record);
@ -322,7 +322,7 @@ export class ProfileSalaryEmployeeController extends Controller {
let _workflow = await new permission().Workflow(req, profileId, "SYS_WAGE");
if (_workflow == false) await new permission().PermissionGet(req, "SYS_WAGE");
const record = await this.salaryRepo.find({
where: { profileEmployeeId: profileId, commandCode: In(["5", "6", "7"]) },
where: { profileEmployeeId: profileId, commandCode: In(["5", "6", "7", "19"]) },
order: { order: "ASC" },
});
return new HttpSuccess(record);
@ -395,6 +395,7 @@ export class ProfileSalaryEmployeeController extends Controller {
if (body.commandCode == "7") body.commandName = "เงินพิเศษอื่น ๆ";
else if (body.commandCode == "6") body.commandName = "เลื่อนเงินเดือนกรณีอื่น ๆ";
else if (body.commandCode == "5") body.commandName = "เลื่อนเงินเดือนตามปกติ";
else if (body.commandCode == "19") body.commandName = "ไม่ได้เลื่อนเงินเดือน/ค่าจ้าง";
}
Object.assign(data, { ...body, ...meta });
const history = new ProfileSalaryHistory();
@ -528,6 +529,7 @@ export class ProfileSalaryEmployeeController extends Controller {
if (body.commandCode == "7") body.commandName = "เงินพิเศษอื่น ๆ";
else if (body.commandCode == "6") body.commandName = "เลื่อนเงินเดือนกรณีอื่น ๆ";
else if (body.commandCode == "5") body.commandName = "เลื่อนเงินเดือนตามปกติ";
else if (body.commandCode == "19") body.commandName = "ไม่ได้เลื่อนเงินเดือน/ค่าจ้าง";
}
Object.assign(record, body);
Object.assign(history, { ...record, id: undefined });

View file

@ -35,6 +35,19 @@ import {
CreatePositionSalaryEditHistory,
PositionSalaryEditHistory,
} from "../entities/PositionSalaryEditHistory";
import { ProfileSalaryBackup } from "../entities/ProfileSalaryBackup";
import { ProfileActposition } from "../entities/ProfileActposition";
import { ProfileAssistance } from "../entities/ProfileAssistance";
const SALARY_COMMAND_CODES = {
ACT_POSITION: "17", // รักษาการ
ASSISTANCE: "18", // ช่วยราชการ
} as const;
class ConfirmDoneSalaryDto {
profileId: string;
type: "OFFICER" | "EMPLOYEE";
}
@Route("api/v1/org/profile/salaryTemp")
@Tags("ProfileSalaryTemp")
@ -120,7 +133,8 @@ export class ProfileSalaryTempController extends Controller {
_data.child1 != undefined && _data.child1 != null
? _data.child1[0] != null
? `current_holders.orgChild1Id IN (:...child1)`
: `current_holders.orgChild1Id is ${_data.privilege == "PARENT" ? "not null" : "null"}`
// : `current_holders.orgChild1Id is ${_data.privilege == "PARENT" ? "not null" : "null"}`
: `current_holders.orgChild1Id is null`
: "1=1",
{
child1: _data.child1,
@ -531,7 +545,8 @@ export class ProfileSalaryTempController extends Controller {
_data.child1 != undefined && _data.child1 != null
? _data.child1[0] != null
? `current_holders.orgChild1Id IN (:...child1)`
: `current_holders.orgChild1Id is ${_data.privilege == "PARENT" ? "not null" : "null"}`
// : `current_holders.orgChild1Id is ${_data.privilege == "PARENT" ? "not null" : "null"}`
: `current_holders.orgChild1Id is null`
: "1=1",
{
child1: _data.child1,
@ -1011,7 +1026,10 @@ export class ProfileSalaryTempController extends Controller {
if (!salary) {
throw new HttpError(HttpStatus.BAD_REQUEST, "ไม่พบ profile ดังกล่าว");
}
return new HttpSuccess({ salaryNew: salary, salaryOld: salary.profileSalary });
return new HttpSuccess({
salaryNew: salary,
salaryOld: salary.profileSalary,
});
}
/**
@ -1240,7 +1258,9 @@ export class ProfileSalaryTempController extends Controller {
profile.statusCheckEdit = "PENDING";
await this.profileRepo.save(profile);
} else {
const profile = await this.profileEmployeeRepo.findOneBy({ id: body.profileId });
const profile = await this.profileEmployeeRepo.findOneBy({
id: body.profileId,
});
if (!profile) {
throw new HttpError(HttpStatus.BAD_REQUEST, "ไม่พบ profile ดังกล่าว");
}
@ -1269,7 +1289,9 @@ export class ProfileSalaryTempController extends Controller {
profile.statusCheckEdit = "EDITED";
await this.profileRepo.save(profile);
} else {
const profile = await this.profileEmployeeRepo.findOneBy({ id: body.profileId });
const profile = await this.profileEmployeeRepo.findOneBy({
id: body.profileId,
});
if (!profile) {
throw new HttpError(HttpStatus.BAD_REQUEST, "ไม่พบ profile ดังกล่าว");
}
@ -1288,24 +1310,185 @@ export class ProfileSalaryTempController extends Controller {
@Post("confirm-done")
public async confirmDoneSalary(
@Request() req: RequestWithUser,
@Body() body: { profileId: string; type: string },
@Body() body: ConfirmDoneSalaryDto,
) {
if (body.type.toLocaleUpperCase() == "OFFICER") {
const profile = await this.profileRepo.findOneBy({ id: body.profileId });
const queryRunner = AppDataSource.createQueryRunner();
await queryRunner.connect();
await queryRunner.startTransaction();
try {
const isOfficer = body.type.toUpperCase() === "OFFICER";
/* =========================
* 1. Load Profile
* ========================= */
const profile = isOfficer
? await queryRunner.manager.findOne(Profile, {
where: { id: body.profileId },
})
: await queryRunner.manager.findOne(ProfileEmployee, {
where: { id: body.profileId },
});
if (!profile) {
throw new HttpError(HttpStatus.BAD_REQUEST, "ไม่พบ profile ดังกล่าว");
throw new HttpError(HttpStatus.BAD_REQUEST, "ไม่พบข้อมูล profile");
}
profile.statusCheckEdit = "CHECKED";
await this.profileRepo.save(profile);
} else {
const profile = await this.profileEmployeeRepo.findOneBy({ id: body.profileId });
if (!profile) {
throw new HttpError(HttpStatus.BAD_REQUEST, "ไม่พบ profile ดังกล่าว");
await queryRunner.manager.save(profile);
/* =========================
* 2. Load Salary Temp
* ========================= */
const salaryTemps = await queryRunner.manager.find(ProfileSalaryTemp, {
where: isOfficer ? { profileId: body.profileId } : { profileEmployeeId: body.profileId },
});
if (salaryTemps.length === 0) {
throw new HttpError(HttpStatus.NOT_FOUND, "ไม่พบข้อมูลตำแหน่ง/เงินเดือนของ profile นี้");
}
profile.statusCheckEdit = "CHECKED";
await this.profileEmployeeRepo.save(profile);
/* =========================
* 3. Temp restore
* ========================= */
const originalTempRows = salaryTemps; // เก็บไว้ restore หลัง insert ProfileSalary เสร็จ
const toInsert = salaryTemps.filter((t) => !t.isDelete);
/* =========================
* 4. ProfileSalaryTemp ProfileSalary
* ========================= */
await queryRunner.manager.delete(ProfileSalaryTemp, {
...(isOfficer ? { profileId: body.profileId } : { profileEmployeeId: body.profileId }),
});
// backup profile salary before delete
const profileSalaryBeforeDelete = await queryRunner.manager.find(ProfileSalary, {
where: {
...(isOfficer ? { profileId: body.profileId } : { profileEmployeeId: body.profileId }),
},
});
// insert ProfileSalary backup to ProfileSalaryBackup
if (profileSalaryBeforeDelete.length > 0) {
const backupRows = profileSalaryBeforeDelete.map(({ id, ...salary }) =>
queryRunner.manager.create(ProfileSalaryBackup, {
...salary,
profileSalaryId: id,
}),
);
await queryRunner.manager.insert(ProfileSalaryBackup, backupRows);
}
// delete profile salary
await queryRunner.manager.delete(ProfileSalary, {
...(isOfficer ? { profileId: body.profileId } : { profileEmployeeId: body.profileId }),
});
/* =========================
* 5. INSERT (bulk)
* ========================= */
if (toInsert.length > 0) {
const dateNow = new Date();
const metaCreated = {
createdUserId: req.user.sub,
createdFullName: req.user.name,
createdAt: dateNow,
lastUpdateUserId: req.user.sub,
lastUpdateFullName: req.user.name,
lastUpdatedAt: dateNow,
};
// แยกข้อมูลตามประเภทการ insert
const { salaryRows, actPositionRows, assistanceRows } = toInsert.reduce<{
salaryRows: ProfileSalaryTemp[];
actPositionRows: ProfileSalaryTemp[];
assistanceRows: ProfileSalaryTemp[];
}>(
(acc, x) => {
if (x.commandCode === SALARY_COMMAND_CODES.ACT_POSITION) acc.actPositionRows.push(x);
else if (x.commandCode === SALARY_COMMAND_CODES.ASSISTANCE) acc.assistanceRows.push(x);
else acc.salaryRows.push(x);
return acc;
},
{ salaryRows: [], actPositionRows: [], assistanceRows: [] },
);
// Insert ProfileSalary
if (salaryRows.length) {
await queryRunner.manager.insert(
ProfileSalary,
salaryRows.map(({ id, ...data }) => ({
...data,
...metaCreated,
})),
);
}
// Insert ProfileActposition
if (actPositionRows.length) {
await queryRunner.manager.insert(
ProfileActposition,
actPositionRows.map((x) => ({
profileId: x.profileId,
profileEmployeeId: x.profileEmployeeId,
dateStart: x.commandDateAffect,
dateEnd: null,
posNo: `${x.posNoAbb} ${x.posNo}`,
position: x.positionName,
commandId: x.commandId,
refCommandNo: `${x.commandNo}/${x.commandYear}`,
refCommandDate: x.commandDateAffect,
status: false,
isDeleted: false,
...metaCreated,
})),
);
}
// Insert ProfileAssistance
if (assistanceRows.length) {
await queryRunner.manager.insert(
ProfileAssistance,
assistanceRows.map((x) => ({
profileId: x.profileId,
profileEmployeeId: x.profileEmployeeId,
agency: x.orgRoot,
dateStart: x.commandDateAffect,
dateEnd: null,
commandId: x.commandId,
commandNo: `${x.commandNo}/${x.commandYear}`,
commandName: x.commandName ?? "ให้ช่วยราชการ",
refCommandDate: x.commandDateSign,
refId: x.refId,
status: "DONE",
isUpload: false,
...metaCreated,
})),
);
}
}
/* =========================
* Restore ProfileSalaryTemp ( ProfileSalary insert )
* ========================= */
if (originalTempRows.length > 0) {
await queryRunner.manager.insert(
ProfileSalaryTemp,
originalTempRows.map(({ id, ...data }) => ({
...data,
salaryId: null,
})),
);
}
await queryRunner.commitTransaction();
return new HttpSuccess();
} catch (error) {
await queryRunner.rollbackTransaction();
throw error;
} finally {
await queryRunner.release();
}
return new HttpSuccess();
}
/**
@ -1390,7 +1573,9 @@ export class ProfileSalaryTempController extends Controller {
salaryId: string,
@Request() req: RequestWithUser,
) {
const source_item = await this.salaryRepo.findOne({ where: { id: salaryId } });
const source_item = await this.salaryRepo.findOne({
where: { id: salaryId },
});
// if (source_item) {
//await new permission().PermissionOrgUserGet(req,"SYS_REGISTRY_OFFICER",source_item.profileId,); //ไม่แน่ใจOFFปิดไว้ก่อน
// }
@ -1398,7 +1583,10 @@ export class ProfileSalaryTempController extends Controller {
const sourceOrder = source_item.order;
if (direction.trim().toUpperCase() == "UP") {
const dest_item = await this.salaryRepo.findOne({
where: { profileId: source_item.profileId, order: LessThan(sourceOrder) },
where: {
profileId: source_item.profileId,
order: LessThan(sourceOrder),
},
order: { order: "DESC" },
});
if (dest_item == null) return new HttpSuccess();
@ -1410,7 +1598,10 @@ export class ProfileSalaryTempController extends Controller {
await Promise.all([this.salaryRepo.save(source_item), this.salaryRepo.save(dest_item)]);
} else {
const dest_item = await this.salaryRepo.findOne({
where: { profileId: source_item.profileId, order: MoreThan(sourceOrder) },
where: {
profileId: source_item.profileId,
order: MoreThan(sourceOrder),
},
order: { order: "ASC" },
});
if (dest_item == null) return new HttpSuccess();
@ -1441,7 +1632,9 @@ export class ProfileSalaryTempController extends Controller {
profile = await this.profileRepo.findOneBy({ id: profileId });
if (!profile) {
profileEmployee = await this.profileEmployeeRepo.findOneBy({ id: profileId });
profileEmployee = await this.profileEmployeeRepo.findOneBy({
id: profileId,
});
if (!profileEmployee) {
throw new HttpError(HttpStatus.NOT_FOUND, "ไม่พบข้อมูล");
}

View file

@ -23,15 +23,23 @@ import HttpError from "../interfaces/http-error";
import { ProfileTrainingHistory } from "../entities/ProfileTrainingHistory";
import { RequestWithUser } from "../middlewares/user";
import { Profile } from "../entities/Profile";
import { ProfileEmployee } from "../entities/ProfileEmployee";
import permission from "../interfaces/permission";
import { setLogDataDiff } from "../interfaces/utils";
import { ProfileDevelopment } from "../entities/ProfileDevelopment";
import { ProfileDevelopmentHistory } from "../entities/ProfileDevelopmentHistory";
import { In } from "typeorm";
@Route("api/v1/org/profile/training")
@Tags("ProfileTraining")
@Security("bearerAuth")
export class ProfileTrainingController extends Controller {
private profileRepo = AppDataSource.getRepository(Profile);
private profileEmployeeRepo = AppDataSource.getRepository(ProfileEmployee);
private trainingRepo = AppDataSource.getRepository(ProfileTraining);
private trainingHistoryRepo = AppDataSource.getRepository(ProfileTrainingHistory);
private developmentRepo = AppDataSource.getRepository(ProfileDevelopment);
private developmentHistoryRepo = AppDataSource.getRepository(ProfileDevelopmentHistory);
@Get("user")
public async getTrainingUser(@Request() request: { user: Record<string, any> }) {
@ -40,7 +48,7 @@ export class ProfileTrainingController extends Controller {
throw new HttpError(HttpStatus.BAD_REQUEST, "ไม่พบ profile ดังกล่าว");
}
const record = await this.trainingRepo.find({
where: { profileId: profile.id },
where: { profileId: profile.id, isDeleted: false },
order: { createdAt: "ASC" },
});
return new HttpSuccess(record);
@ -52,7 +60,7 @@ export class ProfileTrainingController extends Controller {
if (_workflow == false)
await new permission().PermissionOrgUserGet(req, "SYS_REGISTRY_OFFICER", profileId);
const record = await this.trainingRepo.find({
where: { profileId },
where: { profileId: profileId, isDeleted: false },
order: { createdAt: "ASC" },
});
return new HttpSuccess(record);
@ -178,4 +186,156 @@ export class ProfileTrainingController extends Controller {
return new HttpSuccess();
}
/**
* API /
* @summary API /
* @param trainingId /
*/
@Patch("update-delete/{trainingId}")
public async updateIsDeletedTraining(
@Request() req: RequestWithUser,
@Path() trainingId: string,
) {
const record = await this.trainingRepo.findOneBy({ id: trainingId });
if (!record) throw new HttpError(HttpStatus.NOT_FOUND, "ไม่พบข้อมูล");
if (record.isDeleted === true) {
return new HttpSuccess();
}
await new permission().PermissionOrgUserDelete(req, "SYS_REGISTRY_OFFICER", record.profileId);
const before = structuredClone(record);
const history = new ProfileTrainingHistory();
const now = new Date();
record.isDeleted = true;
record.lastUpdateUserId = req.user.sub;
record.lastUpdateFullName = req.user.name;
record.lastUpdatedAt = now;
Object.assign(history, { ...record, id: undefined });
history.profileTrainingId = trainingId;
history.createdUserId = req.user.sub;
history.createdFullName = req.user.name;
history.createdAt = now;
await Promise.all([
this.trainingRepo.save(record, { data: req }),
setLogDataDiff(req, { before, after: record }),
this.trainingHistoryRepo.save(history, { data: req }),
]);
return new HttpSuccess();
}
/**
* API / IDP
* @summary API / IDP
*/
@Post("delete-all")
public async deleteAllTraining(
@Body() reqBody: { developmentId: string },
@Request() req: RequestWithUser
) {
const trainings = await this.trainingRepo.find({
select: { id: true },
where: { developmentId: reqBody.developmentId },
});
if (trainings.length > 0) {
const trainingIds = trainings.map((x) => x.id);
await this.trainingHistoryRepo.delete({
profileTrainingId: In(trainingIds),
});
await this.trainingRepo.delete({
developmentId: reqBody.developmentId,
});
}
await this.developmentHistoryRepo.delete({
kpiDevelopmentId: reqBody.developmentId,
});
await this.developmentRepo.delete({
kpiDevelopmentId: reqBody.developmentId
});
return new HttpSuccess();
}
/**
* API / IDP
* @summary API / IDP
*/
@Post("delete-byId")
public async deleteById(
@Body() reqBody: {
type: string;
profileId: string;
developmentId: string;
},
@Request() req: RequestWithUser
) {
const type = reqBody.type?.trim().toUpperCase();
// 1. validate profile
if (type === "OFFICER") {
const profile = await this.profileRepo.findOneBy({ id: reqBody.profileId });
if (!profile) {
throw new HttpError(HttpStatus.BAD_REQUEST, "ไม่พบ profile ดังกล่าว");
}
} else {
const profile = await this.profileEmployeeRepo.findOneBy({ id: reqBody.profileId });
if (!profile) {
throw new HttpError(HttpStatus.BAD_REQUEST, "ไม่พบ profile ดังกล่าว");
}
}
const profileField = type === "OFFICER" ? "profileId" : "profileEmployeeId";
// 2. หา ProfileTraining
const trainings = await this.trainingRepo.find({
select: { id: true },
where: {
developmentId: reqBody.developmentId,
[profileField]: reqBody.profileId,
},
});
if (trainings.length > 0) {
const trainingIds = trainings.map(x => x.id);
// 3. ลบ TrainingHistory
await this.trainingHistoryRepo.delete({
profileTrainingId: In(trainingIds),
});
// 4. ลบ ProfileTraining
await this.trainingRepo.delete({
id: In(trainingIds),
});
}
// 5. หา ProfileDevelopment
const developments = await this.developmentRepo.find({
select: { id: true },
where: {
kpiDevelopmentId: reqBody.developmentId,
[profileField]: reqBody.profileId,
},
});
if (developments.length > 0) {
const devIds = developments.map(x => x.id);
// 6. ลบ DevelopmentHistory
await this.developmentHistoryRepo.delete({
profileDevelopmentId: In(devIds),
});
// 7. ลบ ProfileDevelopment
await this.developmentRepo.delete({
id: In(devIds),
});
}
return new HttpSuccess();
}
}

View file

@ -40,7 +40,7 @@ export class ProfileTrainingEmployeeController extends Controller {
throw new HttpError(HttpStatus.BAD_REQUEST, "ไม่พบ profile ดังกล่าว");
}
const record = await this.trainingRepo.find({
where: { profileEmployeeId: profile.id },
where: { profileEmployeeId: profile.id, isDeleted: false },
order: { createdAt: "ASC" },
});
return new HttpSuccess(record);
@ -52,7 +52,7 @@ export class ProfileTrainingEmployeeController extends Controller {
if (_workflow == false)
await new permission().PermissionOrgUserGet(req, "SYS_REGISTRY_EMP", profileEmployeeId);
const record = await this.trainingRepo.find({
where: { profileEmployeeId },
where: { profileEmployeeId, isDeleted: false },
order: { createdAt: "ASC" },
});
return new HttpSuccess(record);
@ -168,6 +168,45 @@ export class ProfileTrainingEmployeeController extends Controller {
return new HttpSuccess();
}
/**
* API /
* @summary API /
* @param trainingId /
*/
@Patch("update-delete/{trainingId}")
public async updateIsDeletedTraining(
@Request() req: RequestWithUser,
@Path() trainingId: string,
) {
const record = await this.trainingRepo.findOneBy({ id: trainingId });
if (!record) throw new HttpError(HttpStatus.NOT_FOUND, "ไม่พบข้อมูล");
if (record.isDeleted === true) {
return new HttpSuccess();
}
await new permission().PermissionOrgUserDelete(req, "SYS_REGISTRY_EMP", record.profileEmployeeId);
const before = structuredClone(record);
const history = new ProfileTrainingHistory();
const now = new Date();
record.isDeleted = true;
record.lastUpdateUserId = req.user.sub;
record.lastUpdateFullName = req.user.name;
record.lastUpdatedAt = now;
Object.assign(history, { ...record, id: undefined });
history.profileTrainingId = trainingId;
history.createdUserId = req.user.sub;
history.createdFullName = req.user.name;
history.createdAt = now;
await Promise.all([
this.trainingRepo.save(record, { data: req }),
setLogDataDiff(req, { before, after: record }),
this.trainingHistoryRepo.save(history, { data: req }),
]);
return new HttpSuccess();
}
@Delete("{trainingId}")
public async deleteTraining(@Path() trainingId: string, @Request() req: RequestWithUser) {
const _record = await this.trainingRepo.findOneBy({ id: trainingId });

View file

@ -40,7 +40,7 @@ export class ProfileTrainingEmployeeTempController extends Controller {
throw new HttpError(HttpStatus.BAD_REQUEST, "ไม่พบ profile ดังกล่าว");
}
const record = await this.trainingRepo.find({
where: { profileEmployeeId: profile.id },
where: { profileEmployeeId: profile.id, isDeleted: false },
order: { createdAt: "ASC" },
});
return new HttpSuccess(record);
@ -51,7 +51,7 @@ export class ProfileTrainingEmployeeTempController extends Controller {
let _workflow = await new permission().Workflow(req, profileEmployeeId, "SYS_REGISTRY_TEMP");
if (_workflow == false) await new permission().PermissionGet(req, "SYS_REGISTRY_TEMP");
const record = await this.trainingRepo.find({
where: { profileEmployeeId },
where: { profileEmployeeId, isDeleted: false },
order: { createdAt: "ASC" },
});
return new HttpSuccess(record);
@ -156,6 +156,45 @@ export class ProfileTrainingEmployeeTempController extends Controller {
return new HttpSuccess();
}
/**
* API /
* @summary API /
* @param trainingId /
*/
@Patch("update-delete/{trainingId}")
public async updateIsDeletedTraining(
@Request() req: RequestWithUser,
@Path() trainingId: string,
) {
const record = await this.trainingRepo.findOneBy({ id: trainingId });
if (!record) throw new HttpError(HttpStatus.NOT_FOUND, "ไม่พบข้อมูล");
if (record.isDeleted === true) {
return new HttpSuccess();
}
await new permission().PermissionDelete(req, "SYS_REGISTRY_TEMP");
const before = structuredClone(record);
const history = new ProfileTrainingHistory();
const now = new Date();
record.isDeleted = true;
record.lastUpdateUserId = req.user.sub;
record.lastUpdateFullName = req.user.name;
record.lastUpdatedAt = now;
Object.assign(history, { ...record, id: undefined });
history.profileTrainingId = trainingId;
history.createdUserId = req.user.sub;
history.createdFullName = req.user.name;
history.createdAt = now;
await Promise.all([
this.trainingRepo.save(record, { data: req }),
setLogDataDiff(req, { before, after: record }),
this.trainingHistoryRepo.save(history, { data: req }),
]);
return new HttpSuccess();
}
@Delete("{trainingId}")
public async deleteTraining(@Path() trainingId: string, @Request() req: RequestWithUser) {
await new permission().PermissionDelete(req, "SYS_REGISTRY_TEMP");

Some files were not shown because too many files have changed in this diff Show more