Compare commits

...

297 Commits

Author SHA1 Message Date
c09d51591d Merge pull request 'connect mqtt ws colo' (#38) from wisdom into main
Reviewed-on: #38
2026-01-08 08:10:02 +00:00
b680a249d5 connect mqtt ws colo 2026-01-08 15:07:47 +07:00
2a2df58b7d Merge pull request 'wisdom' (#37) from wisdom into main
Reviewed-on: #37
2026-01-08 07:43:58 +00:00
b1cf4ff624 optional solution error code 2026-01-08 14:32:43 +07:00
747a96ac30 fixing verify token redirect 2026-01-08 14:17:11 +07:00
026a88a9a9 remove validation at least 1 solution 2026-01-08 13:16:13 +07:00
ef491995f9 Merge pull request 'fixing verify access' (#36) from wisdom into main
Reviewed-on: #36
2026-01-08 05:38:22 +00:00
4d2c18edfb fixing verify access 2026-01-08 12:16:16 +07:00
d9975b832b Merge pull request 'add log in resend notif wa' (#35) from wisdom into main
Reviewed-on: #35
2026-01-08 04:51:46 +00:00
a4d8d55dbf add log in resend notif wa 2026-01-08 11:30:41 +07:00
205cb9d7cc Merge pull request 'Test push' (#34) from wisdom into main
Reviewed-on: #34
2026-01-08 03:48:11 +00:00
a6075174f5 Test push 2026-01-08 10:47:39 +07:00
41f5cc9011 Merge branch 'wisdom'
# Conflicts:
#	services/notification_error.service.js
2026-01-08 10:45:02 +07:00
3d163f4507 repair resend notif wa 2026-01-08 09:35:52 +07:00
bc265ccc33 repair resend notif wa 2026-01-08 09:35:18 +07:00
6885443bc2 repair resend notif wa 2026-01-08 09:32:20 +07:00
cecdc09719 fixing notification 2026-01-07 16:09:53 +07:00
f5494fb4a1 Merge pull request 'repair: resend wa' (#29) from wisdom into main
Reviewed-on: #29
2026-01-07 05:18:12 +00:00
2a25339478 repair: resend wa 2026-01-07 12:16:53 +07:00
ed77576958 Merge pull request 'repair: resend notif wa' (#28) from wisdom into main
Reviewed-on: #28
2026-01-07 05:01:44 +00:00
3faa3656c1 repair: resend notif wa 2026-01-07 11:59:27 +07:00
9e862d1a48 Merge pull request 'repair: service & controller notif and notif user' (#27) from wisdom into main
Reviewed-on: #27
2026-01-07 03:10:33 +00:00
aa7cd2fbaa repair: service & controller notif and notif user 2026-01-07 09:34:59 +07:00
9913724d08 Merge pull request 'wisdom' (#26) from wisdom into main
Reviewed-on: #26
2026-01-06 11:41:36 +00:00
f3abaeac39 repair: change error update is_read true to success 2026-01-06 16:28:37 +07:00
b025c5ea82 repair: body message wa in notif 2026-01-06 14:37:52 +07:00
63a646fce3 Merge pull request 'add: self-signed certificate' (#25) from wisdom into main
Reviewed-on: #25
2026-01-06 05:38:46 +00:00
6f4d171537 add: self-signed certificate 2026-01-06 11:39:33 +07:00
8d947a818b Merge pull request 'repair: change message wa notification' (#24) from wisdom into main
Reviewed-on: #24
2026-01-06 03:05:33 +00:00
71c5e94f42 repair: change message wa notification 2026-01-05 16:10:29 +07:00
81e07ed927 Merge pull request 'wisdom' (#23) from wisdom into main
Reviewed-on: #23
2026-01-05 07:06:16 +00:00
0c16b2a3fd add: is_send & updated_at in notif error user at notif error 2026-01-05 11:31:43 +07:00
402c1c0705 add: is_read & updated_at in notif error user at notif error 2026-01-05 11:25:25 +07:00
019c79d5bc Merge pull request 'wisdom' (#22) from wisdom into main
Reviewed-on: #22
2026-01-05 04:25:09 +00:00
adaa9fda9a repair: update is read in notification error 2026-01-05 10:08:53 +07:00
3eb403c557 repair: resend chat all user & by user 2026-01-04 19:20:43 +07:00
bedc948a74 add: params contact_phone and validation resend chat wa in notif error user 2026-01-01 18:31:09 +07:00
889aa04808 update is_read in notification error 2025-12-31 15:38:47 +07:00
25ba80ab7e add: resend chat wa notif error user 2025-12-31 15:11:34 +07:00
fae6bb7a43 Merge pull request 'repair: notification error log created_by' (#21) from wisdom into main
Reviewed-on: #21
2025-12-31 03:20:14 +00:00
a704eb3235 repair: notification error log created_by 2025-12-23 17:54:14 +07:00
8ecb00a4d3 Merge pull request 'wisdom' (#20) from wisdom into main
Reviewed-on: #20
2025-12-23 05:19:45 +00:00
10b7ac5e68 add: list user berdasarkan notifcation_error_id 2025-12-23 11:53:12 +07:00
37185a9fbc fixing redirect token wa 2025-12-22 17:23:22 +07:00
b62ca35185 Merge pull request 'wisdom' (#19) from wisdom into main
Reviewed-on: #19
2025-12-22 09:18:17 +00:00
c9dba276bb repair: notification_log 2025-12-18 17:32:21 +07:00
706fd401f4 rapair: info device notification error 2025-12-18 16:36:48 +07:00
518d6ff427 repair: detail notif get with error_channel 2025-12-18 16:19:46 +07:00
9f7a73e149 add: plant sub section name, device, brand name in detail notification 2025-12-18 15:18:20 +07:00
bba50177e9 repair: change readers to users 2025-12-18 14:38:58 +07:00
85750b397b repair: change readers to users in detail notification 2025-12-18 14:37:47 +07:00
Athif
1496b80fdf Update Value Report. 2025-12-18 13:37:47 +07:00
Albani Rajata Malik
c112ff165a update value report 2025-12-18 13:26:48 +07:00
5e7de6d144 delete: api is ready & not read 2025-12-18 13:04:57 +07:00
dd5e1cc713 add: api is read & not read in notification 2025-12-18 12:43:44 +07:00
1aa7b1bc08 add: reader in notification detail & update notification 2025-12-18 11:39:24 +07:00
f2c8c3818d repair: ErrorCode brand-device 2025-12-18 10:51:19 +07:00
907f5767c1 notification wa by mqtt broker 2025-12-18 10:05:50 +07:00
2b93baa648 add: crud notif error user 2025-12-09 16:30:19 +07:00
fb3061e0d1 add: add notification 2025-12-09 16:04:32 +07:00
d063478fc2 add: path icon & error code color in all notif error 2025-12-08 11:29:16 +07:00
555a68e90c repair: brand + errorcode 2025-12-05 18:18:50 +07:00
dc7712a79f add: sparepart in detail notification error 2025-12-05 09:21:04 +07:00
20d035a1ca add: sparepart in detail notification error 2025-12-05 09:13:41 +07:00
1d3de9ae41 repair: notification error sparepart 2025-12-04 16:07:04 +07:00
198346ff0b repair: device_description not required 2025-12-04 15:44:27 +07:00
a8eb785a5b repair get error code by brandId 2025-12-04 15:38:19 +07:00
096fe9461d add: field listen_channel m_device 2025-12-04 15:37:48 +07:00
31daa470b7 repair: notification error sparepart 2025-12-04 14:34:32 +07:00
be80206056 repair: notification error sparepart 2025-12-04 14:31:41 +07:00
73b4abfd68 repair: notification error sparepart 2025-12-04 14:30:57 +07:00
8b4cc89be5 repair: change mandatory to opsional 2025-12-04 09:28:39 +07:00
6fc21160f5 repair: get errorcodeby brand id 2025-12-04 00:51:41 +07:00
c8b2387e4f izin ubah be dikit 2025-12-03 16:45:24 +07:00
cecb88b838 update: delete path sollution 2025-12-03 16:15:18 +07:00
1ec69b388d repair: update sparepart_qty validation to allow zero 2025-12-03 13:27:18 +07:00
867976030a repair: get error code 2025-12-02 15:58:06 +07:00
790b949302 repair: error code detail 2025-12-02 15:29:43 +07:00
f797685a4f add: error_code api 2025-12-02 15:22:20 +07:00
feff905d8f repair: sparepart per error code 2025-12-02 14:45:29 +07:00
097030029f env imagekit 2025-12-02 10:45:40 +07:00
28e99c2a0d add: import , repair: change save local storage to imagekit 2025-12-02 10:44:07 +07:00
920b24bfd2 repair: brand device connect to sparepart 2025-12-01 10:35:25 +07:00
5e74122b9e Merge pull request 'wisdom' (#18) from wisdom into main
Reviewed-on: #18
2025-11-28 05:09:58 +00:00
462cf6e94b add: relation m_brand & m_sparepart 2025-11-28 00:32:17 +07:00
9926e2b181 repair: sparepart to brand device 2025-11-28 00:31:36 +07:00
fdb560985c repair: change mandatory to opsional 2025-11-27 13:51:51 +07:00
c9713910b3 repair: delete double api export 2025-11-26 09:10:03 +07:00
380864b1ab Merge branch 'wisdom' of https://gitea.idetama.id/yogiedigital/cod-api into wisdom 2025-11-25 14:58:44 +07:00
5a9ef3356d add: install package exceljs 2025-11-25 14:58:38 +07:00
662cf6f5f3 repair: notification error log 2025-11-25 14:56:49 +07:00
78a813e9ec add: notif error log 2025-11-25 11:59:44 +07:00
44107c9753 add: export excell sparepart 2025-11-25 11:58:57 +07:00
ca973d931f add: export excell sparepart 2025-11-25 11:52:51 +07:00
778d6f98e8 repair: get notif error byid 2025-11-25 11:49:53 +07:00
050543dbbf Merge pull request 'wisdom' (#17) from wisdom into main
Reviewed-on: #17
2025-11-25 03:50:08 +00:00
9dc165e15d repair: sparepart controller & service 2025-11-25 10:17:56 +07:00
70817df361 repair: sparepart service and controller 2025-11-25 10:16:05 +07:00
6de0d5d149 repair: notification error 2025-11-25 10:10:29 +07:00
fd07481fb0 repair: replace path_foto to sparepart_foto 2025-11-25 09:21:35 +07:00
8dcdddb42f repair: get detail sparepart 2025-11-24 16:59:16 +07:00
833b1bee82 crud: sparepart 2025-11-24 16:53:10 +07:00
8bab066dd0 crud: sparepart 2025-11-24 16:44:34 +07:00
4253e29889 repair: reset brand device without sparepart 2025-11-24 14:56:33 +07:00
e1b397e1d3 Merge pull request 'add brand id' (#16) from wisdom into main
Reviewed-on: #16
2025-11-20 03:42:49 +00:00
98a1b15669 add brand id 2025-11-19 15:53:10 +07:00
34db6b8d89 Merge pull request 'wisdom' (#15) from wisdom into main
Reviewed-on: #15
2025-11-19 01:04:37 +00:00
5e4a992834 repair: crud notification error sparepart 2025-11-18 13:50:23 +07:00
46c69aafa0 repair get notification error detail 2025-11-18 13:35:58 +07:00
919824bb41 crud: notification error sparepart 2025-11-18 13:13:55 +07:00
de5051bfaf fix: brand device service 2025-11-18 10:06:21 +07:00
5d1b6daef6 Merge pull request 'repair brand device: add brand sparepart in 2 step' (#14) from wisdom into main
Reviewed-on: #14
2025-11-18 00:25:14 +00:00
8176eb2bdd repair brand device: add brand sparepart in 2 step 2025-11-17 16:01:29 +07:00
30431be379 Merge pull request 'wisdom' (#13) from wisdom into main
Reviewed-on: #13
2025-11-17 08:27:07 +00:00
ff4176f8f9 repair: get all notification 2025-11-17 11:14:36 +07:00
ac68a380dd repair: message_error_issue 2025-11-14 18:32:24 +07:00
b8dd4a61f8 repair: get all notification 2025-11-14 08:53:58 +07:00
c8149c2359 crud: brand sparepart 2025-11-13 14:08:27 +07:00
342c06e49c crud: notification 2025-11-13 11:29:35 +07:00
d2d6ce2ab1 add: crud contact 2025-11-12 14:22:08 +07:00
361f750330 Update ecosystem.config.js 2025-11-04 06:14:58 +00:00
31f50d05ab Update .gitignore 2025-11-04 06:12:17 +00:00
961f0d6314 Update ecosystem.config.js 2025-11-04 05:50:27 +00:00
d87fc07a8e Update ecosystem.config.js 2025-11-04 05:46:55 +00:00
95e0c90a16 Merge pull request 'wisdom' (#11) from wisdom into main
Reviewed-on: #11
2025-10-28 09:46:55 +00:00
93b6b1b15e update, column error code color & path icon 2025-10-28 13:20:26 +07:00
d96cbd5e19 fixing api history event alarm 2025-10-28 12:24:52 +07:00
aa8aad94cf format number value history 2025-10-28 12:12:05 +07:00
55e8a6d9ca Merge pull request 'repair: params user_schedule' (#10) from wisdom into main
Reviewed-on: #10
2025-10-28 04:48:25 +00:00
a37906470b repair: params user_schedule 2025-10-27 13:27:08 +07:00
253d83357f Merge pull request 'add: tag_description in tags schema' (#9) from wisdom into main
Reviewed-on: #9
2025-10-27 04:07:05 +00:00
85fca5613e add: tag_description in tags schema 2025-10-27 11:04:11 +07:00
88a0404af0 Merge pull request 'wisdom' (#8) from wisdom into main
Reviewed-on: #8
2025-10-27 03:48:36 +00:00
85ee767451 api history report 2025-10-27 10:29:16 +07:00
afebb64e47 dummy pdf 2025-10-26 18:27:06 +07:00
cd77fda212 fix: file uploads api 2025-10-26 18:26:58 +07:00
a3b7f79546 update verify token 2025-10-26 18:26:38 +07:00
409e2d3750 fix: brand api 2025-10-26 18:26:22 +07:00
2fa263e9e4 api history list alarm 2025-10-25 23:50:49 +07:00
02a2561274 repair: json all user_schedule 2025-10-25 21:31:36 +07:00
d11207aedb Merge pull request 'wisdom' (#7) from wisdom into main
Reviewed-on: #7
2025-10-25 09:19:08 +00:00
3633590a8f fixing validate unit and tag 2025-10-25 15:35:15 +07:00
Muhammad Afif
3b9f3474a6 repair json body user_schedule and validasi user_id tidak boleh sama di satu shift_id 2025-10-24 16:21:20 +07:00
26f7420688 fix: plant sub section validate 2025-10-24 12:46:34 +07:00
d7044521bd Merge pull request 'wisdom' (#6) from wisdom into main
Reviewed-on: #6
2025-10-24 05:42:48 +00:00
ea2905d558 add: brand validate 2025-10-24 11:09:50 +07:00
893f177abd add: crud brand 2025-10-24 11:09:30 +07:00
3a95cdf315 add: uploads api 2025-10-24 10:54:26 +07:00
e2a008c2e1 Merge pull request 'wisdom' (#5) from wisdom into main
Reviewed-on: #5
2025-10-24 03:35:12 +00:00
Muhammad Afif
23f447188f replace sub_section to plant_sub_section 2025-10-24 10:31:27 +07:00
Muhammad Afif
cec23e4777 repair: query user_schedule db 2025-10-23 12:39:29 +07:00
Muhammad Afif
f6b29bb80e repair: query user_schedule db 2025-10-23 12:37:14 +07:00
6d575f649a Merge pull request 'wisdom' (#4) from wisdom into main
Reviewed-on: #4
2025-10-23 04:51:40 +00:00
Muhammad Afif
cf482a293c repair: description to unit_description in unit 2025-10-23 11:10:55 +07:00
9211570f2c fix: device_id optional 2025-10-22 14:49:11 +07:00
6fc31fe987 update: tag required 2025-10-22 14:23:16 +07:00
e8fd307a05 Merge pull request 'wisdom' (#3) from wisdom into main
Reviewed-on: #3
2025-10-22 05:27:56 +00:00
Muhammad Afif
519c37e82c add: description in unit & value in plant sub section 2025-10-22 12:21:14 +07:00
Muhammad Afif
06582f51be add: filter schedule data harian/mingguan/bulanan 2025-10-22 10:21:27 +07:00
9c08e51d31 fixing pagination 2025-10-21 20:05:15 +07:00
Muhammad Afif
a9ad9cccc7 add: CRUD user schedule 2025-10-21 10:59:25 +07:00
0de69aa219 update: add new fields 2025-10-20 16:55:35 +07:00
906a624700 add: expires token 12h 2025-10-20 13:31:34 +07:00
00239db472 Merge pull request 'wisdom' (#2) from wisdom into main
Reviewed-on: #2
2025-10-20 03:26:32 +00:00
8761f1e07e check connection db 2025-10-20 10:21:24 +07:00
Muhammad Afif
c772b7a9df repair: shift schema 2025-10-17 13:08:29 +07:00
Muhammad Afif
d0394f27eb add: looping days in schedule_date 2025-10-17 13:02:50 +07:00
c71da2d3bb fix: tag validate 2025-10-17 12:41:19 +07:00
4083e8544e add: unit routes 2025-10-17 11:31:10 +07:00
90f529bfde add: crud unit 2025-10-17 11:30:55 +07:00
f2109e5fdf add: validate unit 2025-10-17 11:30:46 +07:00
7e769a1fac add: unit db 2025-10-17 11:30:24 +07:00
1aec3825e7 add: validate is_active 2025-10-17 11:30:09 +07:00
2bb8712430 add: validate is_active 2025-10-17 10:44:26 +07:00
b146fe3ffa add: status route 2025-10-16 10:05:55 +07:00
4d080946a6 add crud m_status 2025-10-16 10:05:38 +07:00
5c44b8c3f0 add: validation 2025-10-16 10:05:26 +07:00
c4be580448 add: status db 2025-10-16 10:03:38 +07:00
Muhammad Afif
ba8c7a5346 repair shift: schmea & db 2025-10-16 16:26:37 +07:00
Muhammad Afif
b2399d486e revisi db tags: add device_code_name 2025-10-16 15:25:01 +07:00
a036326792 fix: device status to is_active 2025-10-16 08:10:26 +07:00
bf9cdf5eeb add: props register 2025-10-16 07:53:50 +07:00
d56d23cd71 fix: query device_status to is_active 2025-10-16 07:52:41 +07:00
b1feffe39e fix: device.db.js 2025-10-16 13:12:57 +07:00
7364c3b9c1 add: column query device _id 2025-10-16 12:29:47 +07:00
2907cb6347 update validation schema 2025-10-16 11:24:30 +07:00
1be51f634b fix: default create user approve : 2 2025-10-15 12:10:41 +07:00
c76953bf89 add: reject user 2025-10-15 12:09:01 +07:00
7ad8c6b3fe add: rejectuserdb + default approve 2 2025-10-15 12:08:39 +07:00
7e05fc589b fix: hash password for change user password 2025-10-15 11:14:32 +07:00
Muhammad Afif
32d25cef6a repair device db: device code - device name 2025-10-15 15:50:56 +07:00
7aaf35f7e9 env example 2025-10-15 12:07:18 +07:00
Muhammad Afif
243416fbd6 repair: tags schema 2025-10-15 10:24:29 +07:00
Muhammad Afif
7f06416abf repair: tags db 2025-10-14 17:37:20 +07:00
Muhammad Afif
3896f4103d add: CRUD schedule 2025-10-14 11:11:56 +07:00
2c295ffd36 fix: join table name 2025-10-14 11:02:04 +07:00
110c73ae9d fix create user 2025-10-13 12:37:33 +07:00
Muhammad Afif
9b8cb9d752 repair: shift db & roles 2025-10-13 11:52:09 +07:00
Muhammad Afif
8de83c2716 repair: tags db 2025-10-13 10:56:47 +07:00
Muhammad Afif
662038d953 add: CRUD shift 2025-10-13 10:54:39 +07:00
95189e2014 add: sub section sroute 2025-10-11 16:21:38 +07:00
d5c53b2953 add: crud subsection 2025-10-11 16:21:14 +07:00
ace419fa3e add: validate sub section 2025-10-11 16:20:54 +07:00
7c55e786f3 add: sub section db 2025-10-11 16:20:22 +07:00
Muhammad Afif
47051766e3 add: schedule db 2025-10-11 12:16:24 +07:00
Muhammad Afif
988bcaf301 add: schedule db 2025-10-11 12:13:08 +07:00
e581f5b5bb fix: crud user 2025-10-11 02:26:17 +07:00
751cd0911e fix: auth 2025-10-11 02:26:00 +07:00
cc4d135a53 add: brand db 2025-10-10 14:26:12 +07:00
62823524bc update: auth 2025-10-10 14:24:41 +07:00
Muhammad Afif
36a2609512 repair: device_location to ip_address 2025-10-10 20:48:24 +07:00
7302a3320c fix: user route 2025-10-10 13:39:55 +07:00
Muhammad Afif
425b1ed554 add: CRUD tags 2025-10-10 20:06:56 +07:00
ee30308112 update crud user 2025-10-10 19:49:48 +07:00
Muhammad Afif
c51c686cce add & repair: roles schema,roles service & roles router, roles controller 2025-10-10 17:24:32 +07:00
6c7d92deae update: validate route 2025-10-10 16:31:19 +07:00
05668b4a39 move to folder validate 2025-10-10 16:31:05 +07:00
c5b5f2ba10 add: user validate 2025-10-10 16:30:52 +07:00
82563469bd update validate route 2025-10-10 16:30:37 +07:00
e13cb845bc add: auth validate 2025-10-10 16:30:14 +07:00
bdd8ec02dd push 2025-10-10 14:55:07 +07:00
ab3b38eb49 example for template master device 2025-10-10 14:53:43 +07:00
Muhammad Afif
6eed13bc4f repair: tags db 2025-10-10 11:09:32 +07:00
Muhammad Afif
8add2618ce add: tags db 2025-10-10 09:23:18 +07:00
Muhammad Afif
fa91ce124a add : tag db 2025-10-10 09:21:20 +07:00
Muhammad Afif
6b419495f7 repair: roles controllers 2025-10-10 09:00:23 +07:00
3fd4a4c1b7 fix: change password 2025-10-09 09:01:08 +07:00
ad0f44669b add validation new_password 2025-10-09 09:00:51 +07:00
9c23dbe97b add: updatedById 2025-10-09 08:10:20 +07:00
Muhammad Afif
c81e8ef22d add : CRUD roles 2025-10-09 13:22:41 +07:00
5d27056906 update device 2025-10-09 03:56:48 +07:00
0ae2903035 update: auth login with username or email 2025-10-09 03:36:17 +07:00
fdeb8eb26d fix verify access 2025-10-09 03:23:18 +07:00
7d7891f6ca update: captcha header 2025-10-09 03:19:49 +07:00
1b384a56b5 fix: veryfy access 2025-10-09 03:19:13 +07:00
a6c2e7fc7e add: role db 2025-10-08 11:37:40 +07:00
45968832f0 add: device.db 2025-10-08 11:37:33 +07:00
cf37732ebe add: brand.db.js 2025-10-08 11:37:19 +07:00
20b70edaa6 add: user validation 2025-10-08 11:36:56 +07:00
b16e65463d add: is_approve null, validate is_approve login 2025-10-08 11:36:39 +07:00
58cb0c8425 update: user service 2025-10-08 11:35:50 +07:00
0ae39aa504 update: user controller 2025-10-08 11:35:38 +07:00
9ad16fcff7 fix: user route 2025-10-08 11:35:15 +07:00
453b5eb5af update: userdb 2025-10-08 11:34:27 +07:00
76c5eef2f7 fix: req.user, delete verifyrefreshtoken 2025-10-08 11:30:48 +07:00
f9d3bd913f fix: is_approve validations 2025-10-07 23:12:49 +07:00
4b60f922ee fix: auth 2025-10-07 15:45:14 +07:00
a4ef76e74e add: is approve validation 2025-10-07 15:44:48 +07:00
33e70721d9 update verifyacces 2025-10-07 15:15:00 +07:00
8fca2d3cd2 fix: update device 2025-10-07 15:14:44 +07:00
a632791a4d delete 2025-10-07 15:12:54 +07:00
2eec70b7e3 add verify role 2025-10-07 15:12:49 +07:00
ba7f746433 update: device db 2025-10-07 15:12:40 +07:00
ddf9784213 update: auth 2025-10-07 15:12:27 +07:00
48cb3af91d add verifyAccess 2025-10-07 13:48:31 +07:00
3e6877ee07 update user db 2025-10-07 13:46:55 +07:00
857e9ecf63 add: role db 2025-10-07 13:45:48 +07:00
315537fc6e add: brand db 2025-10-07 13:45:38 +07:00
fe5241a1e1 fix: refresh token 2025-10-02 15:40:09 +07:00
8375c0c2f2 update: response error 2025-10-02 15:39:56 +07:00
1987508887 fix: cookie dev 2025-10-02 15:39:26 +07:00
f4580c42ee update: verifytoken 2025-10-02 15:39:08 +07:00
ec81b4b311 fix: get all device 2025-10-01 14:48:43 +07:00
373caf307b add: device search db 2025-10-01 14:48:17 +07:00
cdb9a7e0ef fix: get all device 2025-10-01 14:47:58 +07:00
8d2a8565ff add: update device schema 2025-10-01 14:46:55 +07:00
446e393ee8 add: device service 2025-10-01 10:20:09 +07:00
e577179358 update: auth 2025-10-01 10:20:01 +07:00
a2124ce5ea update 2025-10-01 10:19:51 +07:00
18cf2dd73e add: device route 2025-10-01 10:19:32 +07:00
c1ff968c19 add: device 2025-10-01 10:19:18 +07:00
ed92e20033 add: refreshtoken route 2025-10-01 10:19:09 +07:00
909c6d5fba fix: token 2025-10-01 10:18:44 +07:00
1cadf8c69d update: verifyRole 2025-10-01 10:18:32 +07:00
0590773d64 update: validation 2025-10-01 10:18:06 +07:00
15851e5853 update: user db 2025-10-01 10:17:43 +07:00
8e3c2df276 add: device db 2025-10-01 10:17:29 +07:00
86cd21ca0b add: device controller 2025-10-01 10:17:22 +07:00
27d4541cfc fix: auth 2025-10-01 10:17:05 +07:00
4bd50c7a4c add: captcha 2025-10-01 10:16:38 +07:00
c0aca9ea87 update: routes 2025-09-26 11:06:04 +07:00
7b2509ffa1 update: auth routes 2025-09-26 11:05:49 +07:00
050529cf78 auth.route.js 2025-09-26 11:05:35 +07:00
d41c0421c4 fix: verify token 2025-09-26 11:05:19 +07:00
373b707954 move to auth validation 2025-09-26 11:04:51 +07:00
b022e86e02 fix: set response 2025-09-26 11:04:04 +07:00
6f66f0c2f3 add: auth validation 2025-09-26 11:03:47 +07:00
a28c3c22d9 update: user db 2025-09-26 11:03:35 +07:00
8142ceda30 fix: auth controller 2025-09-26 10:59:47 +07:00
ce64671551 hide package-lock 2025-09-26 10:58:56 +07:00
251f7148b6 Merge pull request 'wisdom' (#1) from wisdom into main
Reviewed-on: #1
2025-09-17 08:40:15 +00:00
6212048aec Add skeleton 2025-09-17 13:29:29 +07:00
3feb9aa723 Add skeleton 2025-09-17 13:29:20 +07:00
e4d3c8e8d5 Add skeleton 2025-09-17 13:29:12 +07:00
2c546a2ec7 Add skeleton 2025-09-17 13:28:58 +07:00
945e0083d2 Add skeleton 2025-09-17 13:28:50 +07:00
f4ab31b436 Add skeleton 2025-09-17 13:28:36 +07:00
134 changed files with 10401 additions and 4 deletions

48
.env.example Normal file
View File

@@ -0,0 +1,48 @@
# SQL DB Connection Colo
SQL_HOST=117.102.231.130
SQL_DATABASE=cod_piu
SQL_USERNAME=sa
SQL_PASSWORD=@R3M4niA.
SQL_PORT=1433
# SQL_HOST=203.153.114.226
# SQL_PORT=1112
# SQL_DATABASE=piu
# SQL_USERNAME=sa
# SQL_PASSWORD=piu123
# Application Port - express server listens on this port (default 9000).
PORT=9530
ENDPOINT_WA=http://203.153.114.226:9529/send
# ENDPOINT_WA=http://localhost:9529/send
ENDPOINT_FE=http://203.153.114.226:9527
# JWT access secret
SECRET=secret
# JWT refresh secret
REFRESH_SECRET=refreshsecret
# IMAGEKIT
IMAGEKIT_URL_ENDPOINT=https://ik.imagekit.io/j0hxk7x3p
IMAGEKIT_PUBLIC_KEY=public_iMPQFBnXmdQy73TTB9w4SMQO4Jk=
IMAGEKIT_PRIVATE_KEY=private_vhO/jXHnEoaVYptOHIuZDPMbxIA=
# mail server settings
# SMTP_FROM=youremail
# SMTP_USER=youremail
# Stripe secret key - https://stripe.com/docs/keys
# STRIPE_SECRET_KEY=sk_test_4eC39HqLyjWDarjtT1zdp7dc
# Google OAuth2.0 settings for sign in with Google - https://console.developers.google.com/
# OAUTH_CLIENT_ID=287280guajkxxxxxxx.apps.googleusercontent.com
# OAUTH_CLIENT_SECRET=xxxxxxxxxxxxxxxxxxxx
# OAUTH_REFRESH_TOKEN=1//XXXXXXXXXX
# Google OAuth2.0 settings for sending emails - https://console.developers.google.com/
# CLIENT_ID=938729280guajk-xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx.apps.googleusercontent.com
# CLIENT_SECRET=xxxxxxxxxxxxxxxxxxxxxxxx
# REFRESH_TOKEN=1//XXXXXXXX
VITE_KEY_SESSION=PetekRombonganPetekMorekMorakMarek

20
.eslintrc.js Normal file
View File

@@ -0,0 +1,20 @@
module.exports = {
env: {
commonjs: true,
es2021: true,
node: true,
jest: true,
},
extends: ["eslint:recommended"],
parserOptions: {
ecmaVersion: 12,
},
parser: "babel-eslint",
plugins: ["babel", "prettier"],
rules: {
"no-console": "warn",
eqeqeq: "error",
// "object-curly-spacing": ["error", "always"],
// "arrow-spacing": ["error", { before: true, after: true }],
},
};

10
.gitignore vendored
View File

@@ -1,5 +1,7 @@
.env
node_modules
log
tmp
/public/**
!public/.gitkeep
.vscode
request.http
*.rest
package-lock.json
*.log

8
.prettierrc Normal file
View File

@@ -0,0 +1,8 @@
{
"semi": true,
"tabWidth": 2,
"printWidth": 80,
"singleQuote": false,
"trailingComma": "es5",
"endOfLine": "lf"
}

1
Procfile Normal file
View File

@@ -0,0 +1 @@
web: node index.js

57
app.js Normal file
View File

@@ -0,0 +1,57 @@
const express = require("express");
require("express-async-errors");
const cors = require("cors");
const morgan = require("morgan");
const cookieParser = require("cookie-parser");
const routes = require("./routes");
const helmet = require("helmet");
const compression = require("compression");
const unknownEndpoint = require("./middleware/unKnownEndpoint");
const { handleError } = require("./helpers/error");
const { checkConnection, mqttClient } = require("./config");
const { onNotification } = require("./services/notifikasi-wa.service");
const app = express();
app.set("trust proxy", 1);
app.use(cors({ credentials: true, origin: true }));
app.use(express.json());
app.use(morgan("dev"));
app.use(compression());
app.use(helmet());
app.use(cookieParser());
app.use("/api", routes);
app.get("/", (req, res) =>
res.send("<h1 style='text-align: center'>HAHALO</h1>")
);
app.get("/check-db", async (req, res) => {
try {
const isConnected = await checkConnection();
res.json({
success: isConnected,
message: isConnected
? "Koneksi database OK"
: "Koneksi database gagal",
});
} catch (error) {
res.status(500).json({
success: false,
message: "Terjadi kesalahan saat cek koneksi database",
error: error.message,
});
}
});
app.use(unknownEndpoint);
app.use(handleError);
// Saat pesan diterima
mqttClient.on('message', (topic, message) => {
console.log(`Received message on topic "${topic}":`, message.toString());
onNotification(topic, message);
});
module.exports = app;

329
config/index.js Normal file
View File

@@ -0,0 +1,329 @@
require("dotenv").config();
const { default: mqtt } = require("mqtt");
const sql = require("mssql");
const isProduction = process.env.NODE_ENV === "production";
const endPointWhatsapp = process.env.ENDPOINT_WHATSAPP;
// Config SQL Server
const config = {
user: process.env.SQL_USERNAME,
password: process.env.SQL_PASSWORD,
database: process.env.SQL_DATABASE,
server: process.env.SQL_HOST,
port: parseInt(process.env.SQL_PORT, 10),
options: {
encrypt: false, // true kalau Azure
trustServerCertificate: true,
},
};
// Buat pool global
const poolPromise = new sql.ConnectionPool(config)
.connect()
.then(pool => {
console.log("✅ Koneksi SQL Server berhasil");
return pool;
})
.catch(err => {
console.error("❌ Gagal koneksi SQL Server:", err);
process.exit(1);
});
async function checkConnection() {
try {
const pool = await poolPromise;
await pool.request().query("SELECT 1 AS isConnected");
console.log("🔍 SQL Server terkoneksi dengan baik");
return true;
} catch (error) {
console.error("⚠️ Gagal cek koneksi SQL Server:", error);
return false;
}
}
/**
* Wrapper query (auto konversi $1 → @p1)
*/
async function query(text, params = []) {
const pool = await poolPromise;
const request = pool.request();
params.forEach((param, i) => {
request.input(`p${i + 1}`, param);
});
// Ubah $1, $2 jadi @p1, @p2
const sqlText = text.replace(/\$(\d+)/g, (_, num) => `@p${num}`);
console.log(sqlText, params);
return request.query(sqlText);
}
/**
* Validasi tanggal
*/
function isValidDate(dateStr) {
const d = new Date(dateStr);
return !isNaN(d.getTime());
}
/**
* Build filter query (AND)
*/
function buildFilterQuery(filterQuery = [], fixedParams = []) {
let whereConditions = [];
let queryParams = [...fixedParams];
filterQuery.forEach((f) => {
if (f.param === undefined || f.param === null || f.param === "") return;
switch (f.type) {
case "string":
queryParams.push(`%${f.param}%`);
whereConditions.push(
`${f.column} LIKE $${queryParams.length} COLLATE SQL_Latin1_General_CP1_CI_AS`
);
break;
case "number":
queryParams.push(f.param);
whereConditions.push(`${f.column} = $${queryParams.length}`);
break;
case "boolean":
queryParams.push(f.param ? 1 : 0);
whereConditions.push(`${f.column} = $${queryParams.length}`);
break;
case "between":
if (Array.isArray(f.param) && f.param.length === 2) {
const [from, to] = f.param;
if (isValidDate(from) && isValidDate(to)) {
queryParams.push(from);
queryParams.push(to);
whereConditions.push(
`CAST(${f.column} AS DATE) BETWEEN $${queryParams.length - 1} AND $${queryParams.length}`
);
}
}
break;
}
});
return { whereConditions, whereParamAnd: queryParams };
}
/**
* Build OR ILIKE (SQL Server pakai LIKE + COLLATE)
*/
function buildStringOrIlike(columnParam, criteria, fixedParams = []) {
if (!criteria) return { whereOrConditions: "", whereParamOr: fixedParams };
let orStringConditions = [];
let queryParams = [...fixedParams];
columnParam.forEach((column) => {
if (!column) return;
queryParams.push(`%${criteria}%`);
orStringConditions.push(
`${column} LIKE $${queryParams.length} COLLATE SQL_Latin1_General_CP1_CI_AS`
);
});
const whereClause = orStringConditions.length
? `AND (${orStringConditions.join(" OR ")})`
: "";
return { whereOrConditions: whereClause, whereParamOr: queryParams };
}
/**
* Build Date Filter (harian / mingguan / bulanan)
*/
function buildDateFilter(column, type, dateValue, fixedParams = []) {
let whereCondition = "";
let queryParams = [...fixedParams];
if (!dateValue && type !== "monthly") {
return { whereDateCondition: "", whereDateParams: queryParams };
}
switch (type) {
case "daily": {
queryParams.push(dateValue);
whereCondition = `CAST(${column} AS DATE) = $${queryParams.length}`;
break;
}
case "weekly": {
const startDate = new Date(dateValue);
if (!isNaN(startDate.getTime())) {
const endDate = new Date(startDate);
endDate.setDate(startDate.getDate() + 6);
queryParams.push(startDate.toISOString().split("T")[0]);
queryParams.push(endDate.toISOString().split("T")[0]);
whereCondition = `CAST(${column} AS DATE) BETWEEN $${queryParams.length - 1} AND $${queryParams.length}`;
}
break;
}
case "monthly": {
const [year, month] = dateValue.split("-");
if (year && month) {
queryParams.push(parseInt(year), parseInt(month));
whereCondition = `YEAR(${column}) = $${queryParams.length - 1} AND MONTH(${column}) = $${queryParams.length}`;
}
break;
}
default:
whereCondition = "";
}
return { whereDateCondition: whereCondition, whereDateParams: queryParams };
}
/**
* Build dynamic UPDATE
*/
function buildDynamicUpdate(table, data, where) {
data.updated_by = data.userId;
delete data.userId;
const setParts = [];
const values = [];
let index = 1;
for (const [key, value] of Object.entries(data)) {
if (value !== undefined && value !== null) {
setParts.push(`${key} = $${index++}`);
values.push(value);
}
}
if (setParts.length === 0) {
throw new Error("Tidak ada kolom untuk diupdate");
}
setParts.push(`updated_at = CURRENT_TIMESTAMP`);
const whereParts = [];
for (const [key, value] of Object.entries(where)) {
whereParts.push(`${key} = $${index++}`);
values.push(value);
}
const query = `
UPDATE ${table}
SET ${setParts.join(", ")}
WHERE ${whereParts.join(" AND ")}
`;
return { query, values };
}
/**
* Build dynamic INSERT
*/
function buildDynamicInsert(table, data) {
data.created_by = data.userId;
data.updated_by = data.userId;
delete data.userId;
const columns = [];
const placeholders = [];
const values = [];
let index = 1;
for (const [key, value] of Object.entries(data)) {
if (value !== undefined && value !== null) {
columns.push(key);
placeholders.push(`$${index++}`);
values.push(value);
}
}
if (columns.length === 0) {
throw new Error("Tidak ada kolom untuk diinsert");
}
columns.push("created_at", "updated_at");
placeholders.push("CURRENT_TIMESTAMP", "CURRENT_TIMESTAMP");
const query = `
INSERT INTO ${table} (${columns.join(", ")})
VALUES (${placeholders.join(", ")});
SELECT SCOPE_IDENTITY() as inserted_id;
`;
return { query, values };
}
/**
* Generate kode otomatis
*/
async function generateKode(prefix, tableName, columnName) {
const pool = await poolPromise;
const result = await pool.request()
.input("prefix", sql.VarChar, prefix + "%")
.query(`
SELECT TOP 1 ${columnName} as kode
FROM ${tableName}
WHERE ${columnName} LIKE @prefix
ORDER BY ${columnName} DESC
`);
let nextNumber = 1;
if (result.recordset.length > 0) {
const lastKode = result.recordset[0].kode;
const lastNumber = parseInt(lastKode.replace(prefix, ""), 10);
nextNumber = lastNumber + 1;
}
return prefix + String(nextNumber).padStart(3, "0");
}
// Koneksi ke broker MQTT
const mqttOptions = {
clientId: 'express_mqtt_client_' + Math.random().toString(16).substr(2, 8),
clean: true,
connectTimeout: 4000,
username: 'morekmorekmorek', // jika ada
password: 'morek888', // jika ada
};
const mqttUrl = 'ws://117.102.231.130:7001'; // Ganti dengan broker kamu
const topic = process.env.TOPIC_COD ?? 'morek';
const mqttClient = mqtt.connect(mqttUrl, mqttOptions);
// Saat terkoneksi
mqttClient.on('connect', () => {
console.log('MQTT connected');
// Subscribe ke topik tertentu
mqttClient.subscribe(topic, (err) => {
if (!err) {
console.log(`Subscribed to topic "${topic}"`);
} else {
console.error('Subscribe error:', err);
}
});
});
module.exports = {
checkConnection,
query,
buildFilterQuery,
buildDateFilter,
buildStringOrIlike,
buildDynamicInsert,
buildDynamicUpdate,
generateKode,
endPointWhatsapp,
mqttClient
};

View File

@@ -0,0 +1,138 @@
const AuthService = require('../services/auth.service');
const { setResponse, checkValidate } = require('../helpers/utils');
const { registerSchema, loginSchema } = require('../validate/auth.schema');
const { createCaptcha } = require('../utils/captcha');
const JWTService = require('../utils/jwt');
const CryptoJS = require('crypto-js');
class AuthController {
// Register
static async register(req, res) {
const { error, value } = await checkValidate(registerSchema, req);
if (error) {
return res.status(400).json(setResponse(error, 'Validation failed', 400));
}
// Format nomor HP Indonesia
if (value.user_phone && value.user_phone.startsWith('0')) {
value.user_phone = '+62' + value.user_phone.slice(1);
}
const results = await AuthService.register(value);
const response = await setResponse(
{
user: { ...results.user, approved: false },
},
'User registered successfully. Waiting for admin approval.'
);
res.status(response.statusCode).json(response);
}
// Login
static async login(req, res) {
const { error, value } = await checkValidate(loginSchema, req);
if (error) {
return res.status(400).json(setResponse(error, 'Validation failed', 400));
}
const results = await AuthService.login(value);
// Simpan refresh token di cookie
res.cookie('refreshToken', results.tokens.refreshToken, {
httpOnly: true,
secure: false,
sameSite: 'lax',
maxAge: 7 * 24 * 60 * 60 * 1000
});
const response = await setResponse(
{
user: { ...results.user, approved: true },
accessToken: results.tokens.accessToken
},
'Login successful'
);
res.status(response.statusCode).json(response);
}
// Refresh Token
static async refreshToken(req, res) {
const refreshToken = req.cookies?.refreshToken;
if (!refreshToken) {
return res.status(401).json(setResponse(null, 'Refresh token is required', 401));
}
const results = await AuthService.refreshToken(refreshToken);
const response = await setResponse(results, 'Token refreshed successfully');
res.status(response.statusCode).json(response);
}
// Logout
static async logout(req, res) {
res.clearCookie('refreshToken', {
httpOnly: true,
sameSite: 'none',
secure: true
});
const response = await setResponse(null, 'Logged out successfully');
res.status(response.statusCode).json(response);
}
// Captcha
static async generateCaptcha(req, res) {
const { svg, text } = createCaptcha();
// Tampilkan captcha di header untuk dev
res.setHeader('X-Captcha-Text', text);
const response = await setResponse({ svg, text }, 'Captcha generated');
res.status(response.statusCode).json(response);
}
static async verifyTokenRedirect(req, res) {
const { tokenRedirect } = req.body;
const bytes = CryptoJS.AES.decrypt(tokenRedirect, process.env.VITE_KEY_SESSION);
const decrypted = JSON.parse(bytes.toString(CryptoJS.enc.Utf8));
const userPhone = decrypted?.user_phone
const userName = decrypted?.user_name
const idData = decrypted?.id
const payload = {
user_id: userPhone,
user_fullname: userName,
};
const tokens = JWTService.generateTokenPair(payload);
// Simpan refresh token di cookie
res.cookie('refreshToken', tokens.refreshToken, {
httpOnly: true,
secure: false,
sameSite: 'lax',
maxAge: 7 * 24 * 60 * 60 * 1000
});
const response = await setResponse(
{
accessToken: tokens.accessToken,
idData
},
'Verify successful'
);
res.status(response.statusCode).json(response);
}
}
module.exports = AuthController;

View File

@@ -0,0 +1,77 @@
const BrandService = require('../services/brand.service');
const { setResponse, setResponsePaging, checkValidate } = require('../helpers/utils');
const {
insertBrandSchema,
updateBrandSchema,
} = require('../validate/brand.schema');
class BrandController {
// Get all brands
static async getAll(req, res) {
const queryParams = req.query;
const results = await BrandService.getAllBrands(queryParams);
const response = await setResponsePaging(queryParams, results, 'Brand found');
res.status(response.statusCode).json(response);
}
// Get brand by ID
static async getById(req, res) {
const { id } = req.params;
const results = await BrandService.getBrandById(id);
// console.log('Brand response structure:', JSON.stringify(results, null, 2));
const response = await setResponse(results, 'Brand found');
res.status(response.statusCode).json(response);
}
// Create brand
static async create(req, res) {
const { error, value } = await checkValidate(insertBrandSchema, req);
if (error) {
return res.status(400).json(setResponse(error, 'Validation failed', 400));
}
value.created_by = req.user?.user_id || null;
const results = await BrandService.createBrand(value);
const response = await setResponse(results, 'Brand created successfully');
return res.status(response.statusCode).json(response);
}
// Update brand
static async update(req, res) {
const { id } = req.params;
const { error, value } = await checkValidate(updateBrandSchema, req);
if (error) {
return res.status(400).json(setResponse(error, 'Validation failed', 400));
}
value.updated_by = req.user?.user_id || null;
const results = await BrandService.updateBrand(id, value);
const response = await setResponse(results, 'Brand updated successfully');
res.status(response.statusCode).json(response);
}
// Soft delete brand by ID
static async delete(req, res) {
const { id } = req.params;
const results = await BrandService.deleteBrand(id, req.user.user_id);
const response = await setResponse(results, 'Brand deleted successfully');
res.status(response.statusCode).json(response);
}
}
module.exports = BrandController;

View File

@@ -0,0 +1,71 @@
const ContactService = require('../services/contact.service');
const { setResponse, setResponsePaging, checkValidate } = require('../helpers/utils');
const { insertContactSchema, updateContactSchema } = require('../validate/contact.schema');
class ContactController {
// Get all contact
static async getAll(req, res) {
const queryParams = req.query;
const results = await ContactService.getAllContact(queryParams);
const response = await setResponsePaging(queryParams, results, 'Contact found')
res.status(response.statusCode).json(response);
}
// Get contact by ID
static async getById(req, res) {
const { id } = req.params;
const results = await ContactService.getContactById(id);
const response = await setResponse(results, 'Contact found')
res.status(response.statusCode).json(response);
}
// Create contact
static async create(req, res) {
const { error, value } = await checkValidate(insertContactSchema, req)
if (error) {
return res.status(400).json(setResponse(error, 'Validation failed', 400));
}
value.userId = req.user.user_id
const results = await ContactService.createContact(value);
const response = await setResponse(results, 'Contact created successfully')
return res.status(response.statusCode).json(response);
}
// Update contact
static async update(req, res) {
const { id } = req.params;
const { error, value } = checkValidate(updateContactSchema, req)
if (error) {
return res.status(400).json(setResponse(error, 'Validation failed', 400));
}
value.userId = req.user.user_id
const results = await ContactService.updateContact(id, value);
const response = await setResponse(results, 'Contact updated successfully')
res.status(response.statusCode).json(response);
}
// Soft delete contact
static async delete(req, res) {
const { id } = req.params;
const results = await ContactService.deleteContact(id, req.user.user_id);
const response = await setResponse(results, 'Contact deleted successfully')
res.status(response.statusCode).json(response);
}
}
module.exports = ContactController;

View File

@@ -0,0 +1,71 @@
const DeviceService = require('../services/device.service');
const { setResponse, setResponsePaging, checkValidate } = require('../helpers/utils');
const { insertDeviceSchema, updateDeviceSchema } = require('../validate/device.schema');
class DeviceController {
// Get all devices
static async getAll(req, res) {
const queryParams = req.query;
const results = await DeviceService.getAllDevices(queryParams);
const response = await setResponsePaging(queryParams, results, 'Device found')
res.status(response.statusCode).json(response);
}
// Get device by ID
static async getById(req, res) {
const { id } = req.params;
const results = await DeviceService.getDeviceById(id);
const response = await setResponse(results, 'Device found')
res.status(response.statusCode).json(response);
}
// Create device
static async create(req, res) {
const { error, value } = await checkValidate(insertDeviceSchema, req)
if (error) {
return res.status(400).json(setResponse(error, 'Validation failed', 400));
}
value.userId = req.user.user_id
const results = await DeviceService.createDevice(value);
const response = await setResponse(results, 'Device created successfully')
return res.status(response.statusCode).json(response);
}
// Update device
static async update(req, res) {
const { id } = req.params;
const { error, value } = checkValidate(updateDeviceSchema, req)
if (error) {
return res.status(400).json(setResponse(error, 'Validation failed', 400));
}
value.userId = req.user.user_id
const results = await DeviceService.updateDevice(id, value);
const response = await setResponse(results, 'Device updated successfully')
res.status(response.statusCode).json(response);
}
// Soft delete device
static async delete(req, res) {
const { id } = req.params;
const results = await DeviceService.deleteDevice(id, req.user.user_id);
const response = await setResponse(results, 'Device deleted successfully')
res.status(response.statusCode).json(response);
}
}
module.exports = DeviceController;

View File

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

View File

@@ -0,0 +1,117 @@
const path = require("path");
const fs = require("fs");
const { setResponse } = require("../helpers/utils");
const {
createFileUploadDb,
deleteFileUploadByPathDb,
} = require("../db/file_uploads.db");
const uploadFile = async (req, res) => {
try {
if (!req.file) {
const response = await setResponse([], "Tidak ada file yang diunggah", 400);
return res.status(400).json(response);
}
const file = req.file;
const ext = path.extname(file.originalname).toLowerCase();
const typeDoc = ext === ".pdf" ? "PDF" : "IMAGE";
const folder = typeDoc === "PDF" ? "pdf" : "images";
const pathDocument = `${folder}/${file.filename}`;
const fileData = {
file_upload_name: file.originalname,
path_document: pathDocument,
type_document: typeDoc,
createdBy: req.user?.user_id || null,
};
await createFileUploadDb(fileData);
const response = await setResponse(
{
file_upload_name: file.originalname,
path_document: pathDocument
},
"File berhasil diunggah"
);
res.status(200).json(response);
} catch (error) {
const response = await setResponse([], error.message, 500);
res.status(500).json(response);
}
};
const getFileByPath = async (req, res) => {
try {
const { folder, filename } = req.params;
// Decode filename from URL encoding
const decodedFilename = decodeURIComponent(filename);
const filePath = path.join(__dirname, "../uploads", folder, decodedFilename);
console.log('getFileByPath Debug:', {
folder,
originalFilename: filename,
decodedFilename,
filePath
});
if (!fs.existsSync(filePath)) {
console.log('File not found at path:', filePath);
// try {
// const folderPath = path.join(__dirname, "../uploads", folder);
// const availableFiles = fs.readdirSync(folderPath);
// console.log('Available files in', folderPath, ':', availableFiles);
// } catch (listError) {
// console.log('Could not list files in folder:', listError.message);
// }
const response = await setResponse([], "File tidak ditemukan", 404);
return res.status(404).json(response);
}
res.sendFile(filePath);
} catch (error) {
console.error('getFileByPath Error:', error);
const response = await setResponse([], error.message, 500);
res.status(500).json(response);
}
};
const deleteFileByPath = async (req, res) => {
try {
const { folder, filename } = req.params;
// Decode filename from URL encoding
const decodedFilename = decodeURIComponent(filename);
const filePath = path.join(__dirname, "../uploads", folder, decodedFilename);
if (!fs.existsSync(filePath)) {
const response = await setResponse([], "File tidak ditemukan", 404);
return res.status(404).json(response);
}
// Delete physical file
fs.unlinkSync(filePath);
const pathDocument = `${folder}/${decodedFilename}`;
const deletedBy = req.user?.user_id || null;
await deleteFileUploadByPathDb(pathDocument, deletedBy);
const response = await setResponse([], "File berhasil dihapus");
res.status(200).json(response);
} catch (error) {
const response = await setResponse([], error.message, 500);
res.status(500).json(response);
}
};
module.exports = {
uploadFile,
getFileByPath,
deleteFileByPath,
};

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -0,0 +1,71 @@
const SubSectionService = require('../services/plant_sub_section.service');
const { setResponse, setResponsePaging, checkValidate } = require('../helpers/utils');
const { insertSubSectionSchema, updateSubSectionSchema } = require('../validate/plant_sub_section.schema');
class SubSectionController {
// Get all sub sections
static async getAll(req, res) {
const queryParams = req.query;
const results = await SubSectionService.getAll(queryParams);
const response = await setResponsePaging(queryParams, results, 'Sub section found');
res.status(response.statusCode).json(response);
}
// Get sub section by ID
static async getById(req, res) {
const { id } = req.params;
const results = await SubSectionService.getById(id);
const response = await setResponse(results, 'Sub section found');
res.status(response.statusCode).json(response);
}
// Create sub section
static async create(req, res) {
const { error, value } = await checkValidate(insertSubSectionSchema, req);
if (error) {
return res.status(400).json(setResponse(error, 'Validation failed', 400));
}
value.userId = req.user.user_id;
const results = await SubSectionService.create(value);
const response = await setResponse(results, 'Sub section created successfully');
return res.status(response.statusCode).json(response);
}
// Update sub section
static async update(req, res) {
const { id } = req.params;
const { error, value } = checkValidate(updateSubSectionSchema, req);
if (error) {
return res.status(400).json(setResponse(error, 'Validation failed', 400));
}
value.userId = req.user.user_id;
const results = await SubSectionService.update(id, value);
const response = await setResponse(results, 'Sub section updated successfully');
res.status(response.statusCode).json(response);
}
// Soft delete sub section
static async delete(req, res) {
const { id } = req.params;
const results = await SubSectionService.delete(id, req.user.user_id);
const response = await setResponse(results, 'Sub section deleted successfully');
res.status(response.statusCode).json(response);
}
}
module.exports = SubSectionController;

View File

@@ -0,0 +1,71 @@
const RolesService = require('../services/roles.service');
const { setResponse, setResponsePaging, checkValidate } = require('../helpers/utils');
const { updateRolesSchema, insertRolesSchema } = require('../validate/roles.schema');
class RolesController {
// Get all Roles
static async getAll(req, res) {
const queryParams = req.query;
const results = await RolesService.getAllRoles(queryParams);
const response = await setResponsePaging(queryParams, results, 'Roles found')
res.status(response.statusCode).json(response);
}
// Get Roles by ID
static async getById(req, res) {
const { id } = req.params;
const results = await RolesService.getRolesById(id);
const response = await setResponse(results, 'Roles found')
res.status(response.statusCode).json(response);
}
// Create Roles
static async create(req, res) {
const { error, value } = await checkValidate(insertRolesSchema, req)
if (error) {
return res.status(400).json(setResponse(error, 'Validation failed', 400));
}
value.userId = req.user.user_id
const results = await RolesService.createRoles(value);
const response = await setResponse(results, 'Roles created successfully')
return res.status(response.statusCode).json(response);
}
// Update Roles
static async update(req, res) {
const { id } = req.params;
const { error, value } = checkValidate(updateRolesSchema, req)
if (error) {
return res.status(400).json(setResponse(error, 'Validation failed', 400));
}
value.userId = req.user.user_id
const results = await RolesService.updateRoles(id, value);
const response = await setResponse(results, 'Roles updated successfully')
res.status(response.statusCode).json(response);
}
// Soft delete Roles
static async delete(req, res) {
const { id } = req.params;
const results = await RolesService.deleteRoles(id, req.user.user_id);
const response = await setResponse(results, 'Roles deleted successfully')
res.status(response.statusCode).json(response);
}
}
module.exports = RolesController;

View File

@@ -0,0 +1,71 @@
const ScheduleService = require('../services/schedule.service');
const { setResponse, setResponsePaging, checkValidate } = require('../helpers/utils');
const { updateScheduleSchema, insertScheduleSchema } = require('../validate/schedule.schema');
class ScheduleController {
// Get all Schedule
static async getAll(req, res) {
const queryParams = req.query;
const results = await ScheduleService.getAllSchedule(queryParams);
const response = await setResponsePaging(queryParams, results, 'Schedule found')
res.status(response.statusCode).json(response);
}
// Get Schedule by ID
static async getById(req, res) {
const { id } = req.params;
const results = await ScheduleService.getScheduleById(id);
const response = await setResponse(results, 'Schedule found')
res.status(response.statusCode).json(response);
}
// Create Schedule
static async create(req, res) {
const { error, value } = await checkValidate(insertScheduleSchema, req)
if (error) {
return res.status(400).json(setResponse(error, 'Validation failed', 400));
}
value.userId = req.user.user_id
const results = await ScheduleService.insertScheduleDb(value);
const response = await setResponse(results, 'Schedule created successfully')
return res.status(response.statusCode).json(response);
}
// Update Schedule
static async update(req, res) {
const { id } = req.params;
const { error, value } = checkValidate(updateScheduleSchema, req)
if (error) {
return res.status(400).json(setResponse(error, 'Validation failed', 400));
}
value.userId = req.user.user_id
const results = await ScheduleService.updateSchedule(id, value);
const response = await setResponse(results, 'Schedule updated successfully')
res.status(response.statusCode).json(response);
}
// Soft delete Schedule
static async delete(req, res) {
const { id } = req.params;
const results = await ScheduleService.deleteSchedule(id, req.user.user_id);
const response = await setResponse(results, 'Schedule deleted successfully')
res.status(response.statusCode).json(response);
}
}
module.exports = ScheduleController;

View File

@@ -0,0 +1,71 @@
const ShiftService = require('../services/shift.service');
const { setResponse, setResponsePaging, checkValidate } = require('../helpers/utils');
const { updateShiftSchema, insertShiftSchema } = require('../validate/shift.schema');
class ShiftController {
// Get all Shift
static async getAll(req, res) {
const queryParams = req.query;
const results = await ShiftService.getAllShift(queryParams);
const response = await setResponsePaging(queryParams, results, 'Shift found')
res.status(response.statusCode).json(response);
}
// Get Shift by ID
static async getById(req, res) {
const { id } = req.params;
const results = await ShiftService.getShiftById(id);
const response = await setResponse(results, 'Shift found')
res.status(response.statusCode).json(response);
}
// Create Shift
static async create(req, res) {
const { error, value } = await checkValidate(insertShiftSchema, req)
if (error) {
return res.status(400).json(setResponse(error, 'Validation failed', 400));
}
value.userId = req.user.user_id
const results = await ShiftService.createShift(value);
const response = await setResponse(results, 'Shift created successfully')
return res.status(response.statusCode).json(response);
}
// Update Shift
static async update(req, res) {
const { id } = req.params;
const { error, value } = checkValidate(updateShiftSchema, req)
if (error) {
return res.status(400).json(setResponse(error, 'Validation failed', 400));
}
value.userId = req.user.user_id
const results = await ShiftService.updateShift(id, value);
const response = await setResponse(results, 'Shift updated successfully')
res.status(response.statusCode).json(response);
}
// Soft delete Shift
static async delete(req, res) {
const { id } = req.params;
const results = await ShiftService.deleteShift(id, req.user.user_id);
const response = await setResponse(results, 'Shift deleted successfully')
res.status(response.statusCode).json(response);
}
}
module.exports = ShiftController;

View File

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

View File

@@ -0,0 +1,73 @@
const StatusService = require('../services/status.service');
const { setResponse, setResponsePaging, checkValidate } = require('../helpers/utils');
const { insertStatusSchema, updateStatusSchema } = require('../validate/status.schema');
class StatusController {
// Get all status
static async getAll(req, res) {
const queryParams = req.query;
const results = await StatusService.getAllStatus(queryParams);
const response = await setResponsePaging(queryParams, results, 'Status found');
res.status(response.statusCode).json(response);
}
// Get status by ID
static async getById(req, res) {
const { id } = req.params;
const results = await StatusService.getStatusById(id);
const response = await setResponse(results, 'Status found');
res.status(response.statusCode).json(response);
}
// Create status
static async create(req, res) {
const { error, value } = await checkValidate(insertStatusSchema, req);
if (error) {
return res.status(400).json(setResponse(error, 'Validation failed', 400));
}
value.userId = req.user.user_id;
const results = await StatusService.createStatus(value);
const response = await setResponse(results, 'Status created successfully');
return res.status(response.statusCode).json(response);
}
// Update status
static async update(req, res) {
const { id } = req.params;
console.log("REQ BODY:", req.body);
const { error, value } = await checkValidate(updateStatusSchema, req);
if (error) {
return res.status(400).json(setResponse(error, 'Validation failed', 400));
}
value.userId = req.user.user_id;
const results = await StatusService.updateStatus(id, value);
const response = await setResponse(results, 'Status updated successfully');
res.status(response.statusCode).json(response);
}
// Soft delete status
static async delete(req, res) {
const { id } = req.params;
const results = await StatusService.deleteStatus(id, req.user.user_id);
const response = await setResponse(results, 'Status deleted successfully');
res.status(response.statusCode).json(response);
}
}
module.exports = StatusController;

View File

@@ -0,0 +1,71 @@
const TagsService = require('../services/tags.service');
const { setResponse, setResponsePaging, checkValidate } = require('../helpers/utils');
const { insertTagsSchema, updateTagsSchema } = require('../validate/tags.schema');
class TagsController {
// Get all devices
static async getAll(req, res) {
const queryParams = req.query;
const results = await TagsService.getAllTags(queryParams);
const response = await setResponsePaging(queryParams, results, 'Tags found')
res.status(response.statusCode).json(response);
}
// Get device by ID
static async getById(req, res) {
const { id } = req.params;
const results = await TagsService.getTagByID(id);
const response = await setResponse(results, 'Tags found')
res.status(response.statusCode).json(response);
}
// Create device
static async create(req, res) {
const { error, value } = await checkValidate(insertTagsSchema, req)
if (error) {
return res.status(400).json(setResponse(error, 'Validation failed', 400));
}
value.userId = req.user.user_id
const results = await TagsService.createTags(value);
const response = await setResponse(results, 'Tags created successfully')
return res.status(response.statusCode).json(response);
}
// Update device
static async update(req, res) {
const { id } = req.params;
const { error, value } = checkValidate(updateTagsSchema, req)
if (error) {
return res.status(400).json(setResponse(error, 'Validation failed', 400));
}
value.userId = req.user.user_id
const results = await TagsService.updateTags(id, value);
const response = await setResponse(results, 'Tags updated successfully')
res.status(response.statusCode).json(response);
}
// Soft delete device
static async delete(req, res) {
const { id } = req.params;
const results = await TagsService.deleteTags(id, req.user.user_id);
const response = await setResponse(results, 'Tags deleted successfully')
res.status(response.statusCode).json(response);
}
}
module.exports = TagsController;

View File

@@ -0,0 +1,71 @@
const UnitService = require('../services/unit.service');
const { setResponse, setResponsePaging, checkValidate } = require('../helpers/utils');
const { insertUnitSchema, updateUnitSchema } = require('../validate/unit.schema');
class UnitController {
// Get all units
static async getAll(req, res) {
const queryParams = req.query;
const results = await UnitService.getAllUnits(queryParams);
const response = await setResponsePaging(queryParams, results, 'Unit found');
res.status(response.statusCode).json(response);
}
// Get unit by ID
static async getById(req, res) {
const { id } = req.params;
const results = await UnitService.getUnitById(id);
const response = await setResponse(results, 'Unit found');
res.status(response.statusCode).json(response);
}
// Create unit
static async create(req, res) {
const { error, value } = await checkValidate(insertUnitSchema, req);
if (error) {
return res.status(400).json(setResponse(error, 'Validation failed', 400));
}
value.created_by = req.user.user_id;
const results = await UnitService.createUnit(value);
const response = await setResponse(results, 'Unit created successfully');
return res.status(response.statusCode).json(response);
}
// Update unit
static async update(req, res) {
const { id } = req.params;
const { error, value } = checkValidate(updateUnitSchema, req);
if (error) {
return res.status(400).json(setResponse(error, 'Validation failed', 400));
}
value.updated_by = req.user.user_id;
const results = await UnitService.updateUnit(id, value);
const response = await setResponse(results, 'Unit updated successfully');
res.status(response.statusCode).json(response);
}
// Soft delete unit
static async delete(req, res) {
const { id } = req.params;
const results = await UnitService.deleteUnit(id, req.user.user_id);
const response = await setResponse(results, 'Unit deleted successfully');
res.status(response.statusCode).json(response);
}
}
module.exports = UnitController;

View File

@@ -0,0 +1,71 @@
const UserScheduleService = require('../services/user_schedule.service');
const { setResponse, setResponsePaging, checkValidate } = require('../helpers/utils');
const { insertUserScheduleSchema, updateUserScheduleSchema } = require('../validate/user_schedule.schema');
class UserScheduleController {
// Get all User Schedule
static async getAll(req, res) {
const queryParams = req.query;
const results = await UserScheduleService.getAllUserScheduleDb(queryParams);
const response = await setResponsePaging(queryParams, results, 'User Schedule found')
res.status(response.statusCode).json(response);
}
// Get User Schedule by ID
static async getById(req, res) {
const { id } = req.params;
const results = await UserScheduleService.getUserScheduleByID(id);
const response = await setResponse(results, 'User Schedule found')
res.status(response.statusCode).json(response);
}
// Create User Schedule
static async create(req, res) {
const { error, value } = await checkValidate(insertUserScheduleSchema, req)
if (error) {
return res.status(400).json(setResponse(error, 'Validation failed', 400));
}
value.userId = req.user.user_id
const results = await UserScheduleService.createUserSchedules(value);
const response = await setResponse(results, 'User Schedule created successfully')
return res.status(response.statusCode).json(response);
}
// Update User Schedule
static async update(req, res) {
const { id } = req.params;
const { error, value } = checkValidate(updateUserScheduleSchema, req)
if (error) {
return res.status(400).json(setResponse(error, 'Validation failed', 400));
}
value.userId = req.user.user_id
const results = await UserScheduleService.updateUserSchedules(id, value);
const response = await setResponse(results, 'User Schedule updated successfully')
res.status(response.statusCode).json(response);
}
// Soft delete User Schedule
static async delete(req, res) {
const { id } = req.params;
const results = await UserScheduleService.deleteUserSchedules(id, req.user.user_id);
const response = await setResponse(results, 'User Schedule deleted successfully')
res.status(response.statusCode).json(response);
}
}
module.exports = UserScheduleController;

View File

@@ -0,0 +1,108 @@
const UserService = require("../services/user.service");
const { setResponse, setResponsePaging, checkValidate } = require("../helpers/utils");
const { userSchema, updateUserSchema, newPasswordSchema } = require("../validate/user.schema");
class UserController {
// Get all users
static async getAll(req, res) {
const queryParams = req.query;
const results = await UserService.getAllUsers(queryParams);
const response = await setResponsePaging(queryParams, results, 'Users found');
res.status(response.statusCode).json(response);
}
// Get user by ID
static async getById(req, res) {
const { id } = req.params;
const results = await UserService.getUserById(id);
const response = await setResponse(results, 'User found');
res.status(response.statusCode).json(response);
}
// Create user
static async create(req, res) {
const { error, value } = await checkValidate(userSchema, req);
if (error) {
return res.status(400).json(setResponse(error, 'Validation failed', 400));
}
value.approved_by = req.user.user_id;
const results = await UserService.createUser(value);
const response = await setResponse(results, 'User created successfully');
res.status(response.statusCode).json(response);
}
// Update user
static async update(req, res) {
const { id } = req.params;
const { error, value } = await checkValidate(updateUserSchema, req);
if (error) {
return res.status(400).json(setResponse(error, 'Validation failed', 400));
}
value.userId = req.user.user_id;
const results = await UserService.updateUser(id, value);
const response = await setResponse(results, 'User updated successfully');
res.status(response.statusCode).json(response);
}
// Approve user
static async approve(req, res) {
const { id } = req.params;
const approverId = req.user.user_id;
const updatedUser = await UserService.approveUser(id, approverId);
const response = await setResponse(updatedUser, 'User approved successfully');
return res.status(response.statusCode).json(response);
}
// Reject user
static async reject(req, res) {
const { id } = req.params;
const approverId = req.user.user_id;
const updatedUser = await UserService.rejectUser(id, approverId);
const response = await setResponse(updatedUser, 'User rejected successfully');
return res.status(response.statusCode).json(response);
}
// Soft delete user
static async delete(req, res) {
const { id } = req.params;
const results = await UserService.deleteUser(id, req.user.user_id);
const response = await setResponse(results, 'User deleted successfully');
res.status(response.statusCode).json(response);
}
// Change password
static async changePassword(req, res) {
const { id } = req.params;
const { error, value } = await checkValidate(newPasswordSchema, req);
if (error) {
return res.status(400).json(setResponse(error, 'Validation failed', 400));
}
const results = await UserService.changeUserPassword(id, value.new_password);
const response = await setResponse(results, 'Password changed successfully');
res.status(response.statusCode).json(response);
}
}
module.exports = UserController;

143
db/brand.db.js Normal file
View File

@@ -0,0 +1,143 @@
const pool = require("../config");
// Get all brands
const getAllBrandsDb = async (searchParams = {}) => {
let queryParams = [];
// Pagination
if (searchParams.limit) {
const page = Number(searchParams.page ?? 1) - 1;
queryParams = [Number(searchParams.limit ?? 10), page];
}
// Search
const { whereOrConditions, whereParamOr } = pool.buildStringOrIlike(
["a.brand_name", "a.brand_type", "a.brand_manufacture", "a.brand_model", "a.brand_code"],
searchParams.criteria,
queryParams
);
queryParams = whereParamOr ? whereParamOr : queryParams;
// Filter
const { whereConditions, whereParamAnd } = pool.buildFilterQuery(
[
{ column: "a.brand_type", param: searchParams.type, type: "string" },
{ column: "a.brand_manufacture", param: searchParams.manufacture, type: "string" },
{ column: "a.brand_model", param: searchParams.model, type: "string" },
{ column: "a.is_active", param: searchParams.status, type: "string" },
],
queryParams
);
queryParams = whereParamAnd ? whereParamAnd : queryParams;
const queryText = `
SELECT
COUNT(*) OVER() AS total_data,
a.*
FROM m_brands a
WHERE a.deleted_at IS NULL
${whereConditions.length > 0 ? `AND ${whereConditions.join(' AND ')}` : ''}
${whereOrConditions ? whereOrConditions : ''}
ORDER BY a.brand_id ASC
${searchParams.limit ? `OFFSET $2 * $1 ROWS FETCH NEXT $1 ROWS ONLY` : ''}
`;
const result = await pool.query(queryText, queryParams);
const total =
result?.recordset.length > 0
? parseInt(result.recordset[0].total_data, 10)
: 0;
return { data: result.recordset, total };
};
// Get brand by name (path-based)
const getBrandByNameDb = async (brandName) => {
const queryText = `
SELECT
a.*
FROM m_brands a
WHERE a.brand_name = $1 AND a.deleted_at IS NULL
`;
const result = await pool.query(queryText, [brandName]);
return result.recordset[0];
};
// Get brand by ID (for internal use)
const getBrandByIdDb = async (id) => {
const queryText = `
SELECT
a.*
FROM m_brands a
WHERE a.brand_id = $1 AND a.deleted_at IS NULL
`;
const result = await pool.query(queryText, [id]);
return result.recordset[0];
};
// Create brand
const createBrandDb = async (data) => {
const newCode = await pool.generateKode("BRD", "m_brands", "brand_code");
const store = {
...data,
brand_code: newCode,
};
const { query: queryText, values } = pool.buildDynamicInsert("m_brands", store);
const result = await pool.query(queryText, values);
const insertedId = result.recordset[0]?.inserted_id;
return insertedId ? await getBrandByIdDb(insertedId) : null;
};
// Update brand by name
const updateBrandDb = async (brandName, data) => {
const store = { ...data };
const whereData = { brand_name: brandName };
const { query: queryText, values } = pool.buildDynamicUpdate("m_brands", store, whereData);
await pool.query(`${queryText} AND deleted_at IS NULL`, values);
return getBrandByNameDb(brandName);
};
// Soft delete brand
const deleteBrandDb = async (id, deletedBy) => {
const queryText = `
UPDATE m_brands
SET deleted_at = CURRENT_TIMESTAMP, deleted_by = $1
WHERE brand_id = $2 AND deleted_at IS NULL
`;
await pool.query(queryText, [deletedBy, id]);
return true;
};
// Check if brand name exists (for validation)
const checkBrandNameExistsDb = async (brandName, excludeId = null) => {
let queryText = `
SELECT brand_id
FROM m_brands
WHERE brand_name = $1 AND deleted_at IS NULL
`;
let values = [brandName];
if (excludeId) {
queryText += ` AND brand_id != $2`;
values.push(excludeId);
}
const result = await pool.query(queryText, values);
return result.recordset.length > 0;
};
module.exports = {
getAllBrandsDb,
getBrandByNameDb,
getBrandByIdDb,
createBrandDb,
updateBrandDb,
deleteBrandDb,
checkBrandNameExistsDb,
};

174
db/brand_code.db.js Normal file
View File

@@ -0,0 +1,174 @@
const pool = require("../config");
// Get error codes by brand ID
const getErrorCodesByBrandIdDb = async (brandId, searchParams = {}) => {
let queryParams = [brandId];
// Pagination
if (searchParams.limit) {
const page = Number(searchParams.page ?? 1) - 1;
queryParams = [brandId, Number(searchParams.limit ?? 10), page];
}
// Search across multiple columns
const { whereOrConditions, whereParamOr } = pool.buildStringOrIlike(
["a.error_code", "a.error_code_name", "a.error_code_description"],
searchParams.criteria,
queryParams
);
queryParams = whereParamOr ? whereParamOr : queryParams;
// Filter conditions
const { whereConditions, whereParamAnd } = pool.buildFilterQuery(
[
{ column: "a.is_active", param: searchParams.status, type: "string" },
],
queryParams
);
queryParams = whereParamAnd ? whereParamAnd : queryParams;
const queryText = `
SELECT
COUNT(*) OVER() AS total_data,
a.*
FROM brand_code a
WHERE a.brand_id = $1 AND a.deleted_at IS NULL
${whereConditions.length > 0 ? `AND ${whereConditions.join(' AND ')}` : ''}
${whereOrConditions ? whereOrConditions : ''}
ORDER BY a.error_code_id DESC
${searchParams.limit ? `OFFSET $3 * $2 ROWS FETCH NEXT $2 ROWS ONLY` : ''}
`;
const result = await pool.query(queryText, queryParams);
// Return paginated format if limit is provided
if (searchParams.limit) {
const total = result?.recordset.length > 0 ? parseInt(result.recordset[0].total_data, 10) : 0;
return { data: result.recordset, total };
}
// Return simple array for backward compatibility
return result.recordset;
};
const createErrorCodeDb = async (brandId, data) => {
const store = {
brand_id: brandId,
error_code: data.error_code,
error_code_name: data.error_code_name,
error_code_description: data.error_code_description,
error_code_color: data.error_code_color,
path_icon: data.path_icon,
is_active: data.is_active,
created_by: data.created_by
};
const { query: queryText, values } = pool.buildDynamicInsert("brand_code", store);
const result = await pool.query(queryText, values);
const insertedId = result.recordset[0]?.inserted_id;
return insertedId;
};
const updateErrorCodeDb = async (brandId, errorCode, data) => {
const store = { ...data };
const whereData = {
brand_id: brandId,
error_code: errorCode
};
const { query: queryText, values } = pool.buildDynamicUpdate("brand_code", store, whereData);
await pool.query(`${queryText} AND deleted_at IS NULL`, values);
return true;
};
const deleteErrorCodeDb = async (brandId, errorCode, deletedBy) => {
const queryText = `
UPDATE brand_code
SET deleted_at = CURRENT_TIMESTAMP, deleted_by = $1
WHERE brand_id = $2 AND error_code = $3 AND deleted_at IS NULL
`;
await pool.query(queryText, [deletedBy, brandId, errorCode]);
return true;
};
const getErrorCodeByIdDb = async (error_code_id) => {
const queryText = `
SELECT
a.*
FROM brand_code a
WHERE a.error_code_id = $1 AND a.deleted_at IS NULL
`;
const result = await pool.query(queryText, [error_code_id]);
return result.recordset[0];
};
const getErrorCodeByBrandAndCodeDb = async (brandId, errorCode) => {
const queryText = `
SELECT
a.*
FROM brand_code a
WHERE a.brand_id = $1 AND a.error_code = $2 AND a.deleted_at IS NULL
`;
const result = await pool.query(queryText, [brandId, errorCode]);
return result.recordset[0];
};
// Get all error codes with pagination and search
const getAllErrorCodesDb = async (searchParams = {}) => {
let queryParams = [];
// Pagination
if (searchParams.limit) {
const page = Number(searchParams.page ?? 1) - 1;
queryParams = [Number(searchParams.limit ?? 10), page];
}
// Search across multiple columns
const { whereOrConditions, whereParamOr } = pool.buildStringOrIlike(
["a.error_code", "a.error_code_name", "a.error_code_description"],
searchParams.criteria,
queryParams
);
queryParams = whereParamOr ? whereParamOr : queryParams;
// Filter conditions
const { whereConditions, whereParamAnd } = pool.buildFilterQuery(
[
{ column: "a.is_active", param: searchParams.status, type: "string" },
{ column: "a.brand_id", param: searchParams.brand_id, type: "number" },
],
queryParams
);
queryParams = whereParamAnd ? whereParamAnd : queryParams;
const queryText = `
SELECT
COUNT(*) OVER() AS total_data,
a.*
FROM brand_code a
WHERE a.deleted_at IS NULL
${whereConditions.length > 0 ? `AND ${whereConditions.join(' AND ')}` : ''}
${whereOrConditions ? whereOrConditions : ''}
ORDER BY a.error_code_id DESC
${searchParams.limit ? `OFFSET $2 * $1 ROWS FETCH NEXT $1 ROWS ONLY` : ''}
`;
const result = await pool.query(queryText, queryParams);
const total = result?.recordset.length > 0 ? parseInt(result.recordset[0].total_data, 10) : 0;
return { data: result.recordset, total };
};
module.exports = {
getErrorCodesByBrandIdDb,
getErrorCodeByIdDb,
getErrorCodeByBrandAndCodeDb,
createErrorCodeDb,
updateErrorCodeDb,
deleteErrorCodeDb,
getAllErrorCodesDb,
};

View File

@@ -0,0 +1,60 @@
const pool = require("../config");
// Get solutions by error code ID
const getSolutionsByErrorCodeIdDb = async (errorCodeId) => {
const queryText = `
SELECT
a.*
FROM brand_code_solution a
WHERE a.error_code_id = $1 AND a.deleted_at IS NULL
ORDER BY a.brand_code_solution_id
`;
const result = await pool.query(queryText, [errorCodeId]);
return result.recordset;
};
// Create solution for error code
const createSolutionDb = async (errorCodeId, data) => {
const store = {
error_code_id: errorCodeId,
solution_name: data.solution_name,
type_solution: data.type_solution,
text_solution: data.text_solution,
path_solution: data.path_solution,
is_active: data.is_active,
created_by: data.created_by
};
const { query: queryText, values } = pool.buildDynamicInsert("brand_code_solution", store);
const result = await pool.query(queryText, values);
const insertedId = result.recordset[0]?.inserted_id;
return insertedId;
};
// Update solution
const updateSolutionDb = async (solutionId, data) => {
const store = { ...data };
const whereData = { brand_code_solution_id: solutionId };
const { query: queryText, values } = pool.buildDynamicUpdate("brand_code_solution", store, whereData);
await pool.query(`${queryText} AND deleted_at IS NULL`, values);
return true;
};
// Soft delete solution
const deleteSolutionDb = async (solutionId, deletedBy) => {
const queryText = `
UPDATE brand_code_solution
SET deleted_at = CURRENT_TIMESTAMP, deleted_by = $1
WHERE brand_code_solution_id = $2 AND deleted_at IS NULL
`;
await pool.query(queryText, [deletedBy, solutionId]);
return true;
};
module.exports = {
getSolutionsByErrorCodeIdDb,
createSolutionDb,
updateSolutionDb,
deleteSolutionDb,
};

187
db/brand_sparepart.db.js Normal file
View File

@@ -0,0 +1,187 @@
const pool = require("../config");
// Get spareparts by error_code_id
const getSparepartsByErrorCodeIdDb = async (errorCodeId) => {
const queryText = `
SELECT
s.sparepart_id,
s.sparepart_name,
s.sparepart_code,
s.sparepart_description,
s.sparepart_model,
s.sparepart_foto,
s.sparepart_item_type,
s.sparepart_qty,
s.sparepart_unit,
s.sparepart_merk,
s.sparepart_stok,
bs.created_at,
bs.created_by
FROM brand_sparepart bs
JOIN m_sparepart s ON bs.sparepart_id = s.sparepart_id
WHERE bs.error_code_id = $1
AND s.deleted_at IS NULL
ORDER BY s.sparepart_name
`;
const result = await pool.query(queryText, [errorCodeId]);
return result.recordset;
};
// Get error codes by sparepart_id
const getErrorCodesBySparepartIdDb = async (sparepartId) => {
const queryText = `
SELECT
ec.error_code_id,
ec.error_code,
ec.error_code_name,
ec.error_code_description,
ec.error_code_color,
ec.path_icon,
ec.is_active,
ec.created_at,
ec.updated_at
FROM brand_sparepart bs
JOIN m_error_codes ec ON bs.error_code_id = ec.error_code_id
WHERE bs.sparepart_id = $1
AND ec.deleted_at IS NULL
ORDER BY ec.error_code
`;
const result = await pool.query(queryText, [sparepartId]);
return result.recordset;
};
// Insert error_code-spareparts relationship
const insertErrorCodeSparepartDb = async (errorCodeId, sparepartId, createdBy) => {
const queryText = `
INSERT INTO brand_sparepart (error_code_id, sparepart_id, created_by, created_at)
VALUES ($1, $2, $3, CURRENT_TIMESTAMP)
`;
const result = await pool.query(queryText, [errorCodeId, sparepartId, createdBy]);
return result.recordset;
};
// Insert multiple error_code-spareparts relationships
const insertMultipleErrorCodeSparepartsDb = async (errorCodeId, sparepartIds, createdBy) => {
if (!sparepartIds || sparepartIds.length === 0) return [];
const values = sparepartIds.map((_, index) => `($1, $${index + 2}, $${sparepartIds.length + 2}, CURRENT_TIMESTAMP)`).join(', ');
const queryText = `
INSERT INTO brand_sparepart (error_code_id, sparepart_id, created_by, created_at)
VALUES ${values}
`;
const params = [errorCodeId, ...sparepartIds, createdBy];
const result = await pool.query(queryText, params);
return result.recordset;
};
// Delete specific error_code-sparepart relationship
const deleteErrorCodeSparepartDb = async (errorCodeId, sparepartId) => {
const queryText = `
DELETE FROM brand_sparepart
WHERE error_code_id = $1 AND sparepart_id = $2
`;
const result = await pool.query(queryText, [errorCodeId, sparepartId]);
return result.rowsAffected > 0;
};
// Delete all spareparts for an error_code
const deleteAllErrorCodeSparepartsDb = async (errorCodeId) => {
const queryText = `
DELETE FROM brand_sparepart
WHERE error_code_id = $1
`;
const result = await pool.query(queryText, [errorCodeId]);
return result.rowsAffected > 0;
};
// Update error_code-spareparts (replace all)
const updateErrorCodeSparepartsDb = async (errorCodeId, sparepartIds, updatedBy) => {
// Delete existing relationships
await deleteAllErrorCodeSparepartsDb(errorCodeId);
// Insert new relationships
if (sparepartIds && sparepartIds.length > 0) {
return await insertMultipleErrorCodeSparepartsDb(errorCodeId, sparepartIds, updatedBy);
}
return true;
};
const checkErrorCodeSparepartExistsDb = async (errorCodeId, sparepartId) => {
const queryText = `
SELECT 1
FROM brand_spareparts
WHERE error_code_id = $1 AND sparepart_id = $2
`;
const result = await pool.query(queryText, [errorCodeId, sparepartId]);
return result.recordset.length > 0;
};
const getSparepartsByBrandIdDb = async (brandId) => {
const queryText = `
SELECT DISTINCT
s.sparepart_id,
s.sparepart_name,
s.sparepart_code,
s.sparepart_description,
s.sparepart_model,
s.sparepart_foto,
s.sparepart_item_type,
s.sparepart_qty,
s.sparepart_unit,
s.sparepart_merk,
s.sparepart_stok,
s.created_at,
s.updated_at
FROM brand_sparepart bs
JOIN m_sparepart s ON bs.sparepart_id = s.sparepart_id
JOIN m_error_codes ec ON bs.error_code_id = ec.error_code_id
WHERE ec.brand_id = $1
AND s.deleted_at IS NULL
AND ec.deleted_at IS NULL
ORDER BY s.sparepart_name
`;
const result = await pool.query(queryText, [brandId]);
return result.recordset;
};
// Get brands by sparepart_id (now using error_code_id mapping)
const getBrandsBySparepartIdDb = async (sparepartId) => {
const queryText = `
SELECT DISTINCT
b.brand_id,
b.brand_name,
b.brand_type,
b.brand_manufacture,
b.brand_model,
b.brand_code,
b.is_active,
b.created_at,
b.updated_at
FROM brand_sparepart bs
JOIN m_sparepart s ON bs.sparepart_id = s.sparepart_id
JOIN m_error_codes ec ON bs.error_code_id = ec.error_code_id
JOIN m_brands b ON ec.brand_id = b.brand_id
WHERE bs.sparepart_id = $1
AND s.deleted_at IS NULL
AND ec.deleted_at IS NULL
AND b.deleted_at IS NULL
ORDER BY b.brand_name
`;
const result = await pool.query(queryText, [sparepartId]);
return result.recordset;
};
// Deprecated functions removed - table structure changed to use error_code_id instead of brand_id
module.exports = {
// New functions using error_code_id
getSparepartsByErrorCodeIdDb,
getErrorCodesBySparepartIdDb,
insertErrorCodeSparepartDb,
insertMultipleErrorCodeSparepartsDb,
deleteErrorCodeSparepartDb,
deleteAllErrorCodeSparepartsDb,
updateErrorCodeSparepartsDb,
checkErrorCodeSparepartExistsDb,
};

106
db/contact.db.js Normal file
View File

@@ -0,0 +1,106 @@
const pool = require("../config");
// Get all Contact
const getAllContactDb = async (searchParams = {}) => {
let queryParams = [];
if (searchParams.limit) {
const page = Number(searchParams.page ?? 1) - 1;
queryParams = [Number(searchParams.limit ?? 10), page];
}
const { whereOrConditions, whereParamOr } = pool.buildStringOrIlike(
[
"a.contact_name",
"a.contact_type",
],
searchParams.criteria,
queryParams
);
if (whereParamOr) queryParams = whereParamOr;
const { whereConditions, whereParamAnd } = pool.buildFilterQuery(
[
{ column: "a.contact_name", param: searchParams.name, type: "string" },
{ column: "a.contact_type", param: searchParams.code, type: "string" },
{ column: "a.is_active", param: searchParams.active, type: "boolean" },
],
queryParams
);
if (whereParamAnd) queryParams = whereParamAnd;
const queryText = `
SELECT
COUNT(*) OVER() AS total_data,
a.*
FROM contact a
WHERE a.deleted_at IS NULL
${whereConditions.length > 0 ? ` AND ${whereConditions.join(" AND ")}` : ""}
${whereOrConditions ? ` ${whereOrConditions}` : ""}
ORDER BY a.contact_id ASC
${searchParams.limit ? `OFFSET $2 * $1 ROWS FETCH NEXT $1 ROWS ONLY` : ''}
`;
const result = await pool.query(queryText, queryParams);
const total =
result?.recordset?.length > 0
? parseInt(result.recordset[0].total_data, 10)
: 0;
return { data: result.recordset, total };
};
const getContactByIdDb = async (id) => {
const queryText = `
SELECT
a.*
FROM contact a
WHERE a.contact_id = $1 AND a.deleted_at IS NULL
`;
const result = await pool.query(queryText, [id]);
return result.recordset;
};
const createContactDb = async (store) => {
const { query: queryText, values } = pool.buildDynamicInsert("contact", store);
const result = await pool.query(queryText, values);
const insertedId = result.recordset?.[0]?.inserted_id;
return insertedId ? await getContactByIdDb(insertedId) : null;
};
const updateContactDb = async (id, data) => {
const store = { ...data };
const whereData = { contact_id: id };
const { query: queryText, values } = pool.buildDynamicUpdate(
"contact",
store,
whereData
);
await pool.query(`${queryText} AND deleted_at IS NULL`, values);
return getContactByIdDb(id);
};
// Soft delete tag
const deleteContactDb = async (id, deletedBy) => {
const queryText = `
UPDATE contact
SET deleted_at = CURRENT_TIMESTAMP, deleted_by = $1
WHERE contact_id = $2 AND deleted_at IS NULL
`;
await pool.query(queryText, [deletedBy, id]);
return true;
};
module.exports = {
getAllContactDb,
getContactByIdDb,
createContactDb,
updateContactDb,
deleteContactDb,
};

133
db/device.db.js Normal file
View File

@@ -0,0 +1,133 @@
const pool = require("../config");
// Get all devices
const getAllDevicesDb = async (searchParams = {}) => {
let queryParams = [];
// Pagination
if (searchParams.limit) {
const page = Number(searchParams.page ?? 1) - 1;
queryParams = [Number(searchParams.limit ?? 10), page];
}
// Search
const { whereOrConditions, whereParamOr } = pool.buildStringOrIlike(
[
"a.device_name",
"a.device_code",
"a.device_location",
"a.ip_address",
"b.brand_name",
],
searchParams.criteria,
queryParams
);
queryParams = whereParamOr ? whereParamOr : queryParams;
// Filter
const { whereConditions, whereParamAnd } = pool.buildFilterQuery(
[
{ column: "a.device_code", param: searchParams.code, type: "string" },
{ column: "a.device_location", param: searchParams.location, type: "string" },
{ column: "b.brand_name", param: searchParams.brand, type: "string" },
{ column: "a.is_active", param: searchParams.status, type: "string" },
],
queryParams
);
queryParams = whereParamAnd ? whereParamAnd : queryParams;
const queryText = `
SELECT
COUNT(*) OVER() AS total_data,
a.*,
b.brand_name,
COALESCE(a.device_code, '') + ' - ' + COALESCE(a.device_name, '') AS device_code_name
FROM m_device a
LEFT JOIN m_brands b ON a.brand_id = b.brand_id
WHERE a.deleted_at IS NULL
${whereConditions.length > 0 ? `AND ${whereConditions.join(' AND ')}` : ''}
${whereOrConditions ? whereOrConditions : ''}
ORDER BY a.device_id ASC
${searchParams.limit ? `OFFSET $2 * $1 ROWS FETCH NEXT $1 ROWS ONLY` : ''}
`;
const result = await pool.query(queryText, queryParams);
const total =
result?.recordset.length > 0
? parseInt(result.recordset[0].total_data, 10)
: 0;
return { data: result.recordset, total };
};
const getDeviceByIdDb = async (id) => {
const queryText = `
SELECT
a.*,
b.brand_name,
COALESCE(a.device_code, '') + ' - ' + COALESCE(a.device_name, '') AS device_code_name
FROM m_device a
LEFT JOIN m_brands b ON a.brand_id = b.brand_id
WHERE a.device_id = $1 AND a.deleted_at IS NULL
`;
const result = await pool.query(queryText, [id]);
return result.recordset;
};
const createDeviceDb = async (data) => {
const newCode = await pool.generateKode("DVC", "m_device", "device_code");
const store = {
...data,
device_code: newCode,
};
const { query: queryText, values } = pool.buildDynamicInsert(
"m_device",
store
);
const result = await pool.query(queryText, values);
const insertedId = result.recordset[0]?.inserted_id;
return insertedId ? await getDeviceByIdDb(insertedId) : null;
};
const updateDeviceDb = async (id, data) => {
const store = {
...data,
};
// Kondisi WHERE
const whereData = {
device_id: id,
};
const { query: queryText, values } = pool.buildDynamicUpdate(
"m_device",
store,
whereData
);
await pool.query(`${queryText} AND deleted_at IS NULL`, values);
return getDeviceByIdDb(id);
};
const deleteDeviceDb = async (id, deletedBy) => {
const queryText = `
UPDATE m_device
SET deleted_at = CURRENT_TIMESTAMP, deleted_by = $1
WHERE device_id = $2 AND deleted_at IS NULL
`;
await pool.query(queryText, [deletedBy, id]);
return true;
};
module.exports = {
getAllDevicesDb,
getDeviceByIdDb,
createDeviceDb,
updateDeviceDb,
deleteDeviceDb,
};

92
db/file_uploads.db.js Normal file
View File

@@ -0,0 +1,92 @@
const pool = require("../config");
// Get file upload by path
const getFileUploadByPathDb = async (path) => {
const queryText = `
SELECT
file_upload_id,
file_upload_name,
type_document,
path_document,
created_by,
updated_by,
deleted_by,
created_at,
updated_at,
deleted_at
FROM file_upload
WHERE path_document = $1 AND deleted_at IS NULL
`;
const result = await pool.query(queryText, [path]);
return result.recordset[0];
};
// Create file upload
const createFileUploadDb = async (data) => {
const store = {
file_upload_name: data.file_upload_name,
};
// Add path_document if exists
if (data.path_document) {
store.path_document = data.path_document;
}
// Add type_document if exists
if (data.type_document) {
store.type_document = data.type_document;
}
if (data.createdBy) {
store.created_by = data.createdBy;
}
console.log('Data to insert:', store);
const queryText = `
INSERT INTO file_upload (file_upload_name, path_document, type_document, created_by, created_at, updated_at)
VALUES ($1, $2, $3, $4, CURRENT_TIMESTAMP, CURRENT_TIMESTAMP);
SELECT SCOPE_IDENTITY() as inserted_id;
`;
const values = [
store.file_upload_name,
store.path_document,
store.type_document,
store.created_by || null
];
// console.log('Manual Query:', queryText);
// console.log('Manual Values:', values);
const result = await pool.query(queryText, values);
return result.recordset[0];
};
// Soft delete file upload by path
const deleteFileUploadByPathDb = async (path, deletedBy = null) => {
const store = {
deleted_at: new Date(),
};
if (deletedBy) {
store.deleted_by = deletedBy;
}
const whereData = {
path_document: path,
};
const { query: queryText, values } = pool.buildDynamicUpdate(
"file_upload",
store,
whereData
);
await pool.query(`${queryText} AND deleted_at IS NULL`, values);
return true;
};
module.exports = {
getFileUploadByPathDb,
createFileUploadDb,
deleteFileUploadByPathDb,
};

568
db/history_value.db.js Normal file
View File

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

210
db/notification_error.db.js Normal file
View File

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

View File

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

View File

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

View File

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

62
db/notification_wa.db.js Normal file
View File

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

108
db/plant_sub_section.db.js Normal file
View File

@@ -0,0 +1,108 @@
const pool = require("../config");
// Get all sub sections
const getAllSubSectionsDb = async (searchParams = {}) => {
let queryParams = [];
if (searchParams.limit) {
const page = Number(searchParams.page ?? 1) - 1;
queryParams = [Number(searchParams.limit ?? 10), page];
}
// OR condition (pencarian bebas)
const { whereOrConditions, whereParamOr } = pool.buildStringOrIlike(
["a.plant_sub_section_code", "a.plant_sub_section_name"],
searchParams.criteria,
queryParams
);
queryParams = whereParamOr ?? queryParams;
// AND condition (filter spesifik)
const { whereConditions, whereParamAnd } = pool.buildFilterQuery(
[
{ column: "a.plant_sub_section_code", param: searchParams.code, type: "string" },
{ column: "a.plant_sub_section_name", param: searchParams.name, type: "string" },
],
queryParams
);
queryParams = whereParamAnd ?? queryParams;
// Query utama
const queryText = `
SELECT COUNT(*) OVER() AS total_data, a.*
FROM m_plant_sub_section a
WHERE a.deleted_at IS NULL
${whereConditions.length > 0 ? ` AND ${whereConditions.join(" AND ")}` : ""}
${whereOrConditions ? whereOrConditions : ""}
ORDER BY a.plant_sub_section_id ASC
${searchParams.limit ? `OFFSET $2 * $1 ROWS FETCH NEXT $1 ROWS ONLY` : ''}
`;
const result = await pool.query(queryText, queryParams);
const total =
result?.recordset.length > 0
? parseInt(result.recordset[0].total_data, 10)
: 0;
return { data: result.recordset, total };
};
// Get sub section by ID
const getSubSectionByIdDb = async (id) => {
const queryText = `
SELECT a.*
FROM m_plant_sub_section a
WHERE a.plant_sub_section_id = $1 AND a.deleted_at IS NULL
`;
const result = await pool.query(queryText, [id]);
return result.recordset;
};
// Create new sub section
const createSubSectionDb = async (data) => {
// Generate kode otomatis
const newCode = await pool.generateKode("SUB", "m_plant_sub_section", "plant_sub_section_code");
const store = {
...data,
plant_sub_section_code: newCode
};
const { query: queryText, values } = pool.buildDynamicInsert("m_plant_sub_section", store);
const result = await pool.query(queryText, values);
const insertedId = result.recordset[0]?.inserted_id;
return insertedId ? await getSubSectionByIdDb(insertedId) : null;
};
// Update sub section
const updateSubSectionDb = async (id, data) => {
const store = { ...data };
const whereData = { plant_sub_section_id: id };
const { query: queryText, values } = pool.buildDynamicUpdate("m_plant_sub_section", store, whereData);
await pool.query(`${queryText} AND deleted_at IS NULL`, values);
return getSubSectionByIdDb(id);
};
// Soft delete sub section
const deleteSubSectionDb = async (id, deletedBy) => {
const queryText = `
UPDATE m_plant_sub_section
SET deleted_at = CURRENT_TIMESTAMP, deleted_by = $1
WHERE plant_sub_section_id = $2 AND deleted_at IS NULL
`;
await pool.query(queryText, [deletedBy, id]);
return true;
};
module.exports = {
getAllSubSectionsDb,
getSubSectionByIdDb,
createSubSectionDb,
updateSubSectionDb,
deleteSubSectionDb,
};

99
db/roles.db.js Normal file
View File

@@ -0,0 +1,99 @@
const pool = require("../config");
const getAllRolesDb = async (searchParams = {}) => {
let queryParams = [];
// Handle pagination
if (searchParams.limit) {
const page = Number(searchParams.page ?? 1) - 1;
queryParams = [Number(searchParams.limit ?? 10), page];
}
const { whereOrConditions, whereParamOr } = pool.buildStringOrIlike(
["a.role_name", "a.role_level", "a.role_description"],
searchParams.criteria,
queryParams
);
if (whereParamOr) queryParams = whereParamOr;
const { whereConditions, whereParamAnd } = pool.buildFilterQuery(
[
{ column: "a.role_name", param: searchParams.role_name, type: "string" },
{ column: "a.role_level", param: searchParams.start_time, type: "string" },
{ column: "a.role_description", param: searchParams.role_description, type: "string" },
],
queryParams
);
if (whereParamAnd) queryParams = whereParamAnd;
const queryText = `
SELECT
COUNT(*) OVER() AS total_data,
a.*
FROM m_roles a
WHERE a.deleted_at IS NULL
${whereConditions.length > 0 ? ` AND ${whereConditions.join(" AND ")}` : ""}
${whereOrConditions ? ` ${whereOrConditions}` : ""}
ORDER BY a.role_id ASC
${searchParams.limit ? `OFFSET $2 * $1 ROWS FETCH NEXT $1 ROWS ONLY` : ''}
`;
const result = await pool.query(queryText, queryParams);
const total =
result?.recordset?.length > 0
? parseInt(result.recordset[0].total_data, 10)
: 0;
return { data: result.recordset, total };
};
const getRolesByIdDb = async (id) => {
const queryText = `
SELECT
a.*
FROM m_roles a
WHERE a.role_id = $1 AND a.deleted_at IS NULL
`;
const result = await pool.query(queryText, [id]);
return result.recordset?.[0] || null;
};
const insertRolesDb = async (store) => {
const { query: queryText, values } = pool.buildDynamicInsert("m_roles", store);
const result = await pool.query(queryText, values);
const insertedId = result.recordset?.[0]?.inserted_id;
return insertedId ? await getRolesByIdDb(insertedId) : null;
};
const updateRolesDb = async (id, data) => {
const store = { ...data };
const whereData = { role_id: id };
const { query: queryText, values } = pool.buildDynamicUpdate(
"m_roles",
store,
whereData
);
await pool.query(`${queryText} AND deleted_at IS NULL`, values);
return getRolesByIdDb(id);
};
const deleteRolesDb = async (id, deletedBy) => {
const queryText = `
UPDATE m_roles
SET deleted_at = CURRENT_TIMESTAMP, deleted_by = $1
WHERE role_id = $2 AND deleted_at IS NULL
`;
await pool.query(queryText, [deletedBy, id]);
return true;
};
module.exports = {
getAllRolesDb,
getRolesByIdDb,
insertRolesDb,
updateRolesDb,
deleteRolesDb,
};

168
db/schedule.db.js Normal file
View File

@@ -0,0 +1,168 @@
const pool = require("../config");
const { formattedDate } = require("../utils/date");
const normalizeClause = (clause) => {
if (!clause) return "";
return clause.replace(/^\s*(?:AND|WHERE)\s*/i, "").trim();
};
const getAllScheduleDb = async (searchParams = {}) => {
let queryParams = [];
if (searchParams.limit) {
const page = Number(searchParams.page ?? 1) - 1;
queryParams = [Number(searchParams.limit ?? 10), page];
}
const { whereOrConditions, whereParamOr } = pool.buildStringOrIlike(
["a.schedule_date"],
searchParams.criteria,
queryParams
);
if (whereParamOr) queryParams = whereParamOr;
const { whereConditions, whereParamAnd } = pool.buildFilterQuery(
[
{
column: "a.schedule_date",
param: searchParams.name,
type: "date",
},
],
queryParams
);
if (whereParamAnd) queryParams = whereParamAnd;
const { whereDateCondition, whereDateParams } = pool.buildDateFilter(
"a.schedule_date",
searchParams.dateType,
searchParams.dateValue,
queryParams
);
if (whereDateParams) queryParams = whereDateParams;
const whereParts = [];
whereParts.push("a.deleted_at IS NULL");
if (Array.isArray(whereConditions) && whereConditions.length > 0) {
const joined = whereConditions.join(" AND ");
const norm = normalizeClause(joined);
if (norm) whereParts.push(norm);
} else if (typeof whereConditions === "string" && whereConditions.trim()) {
const norm = normalizeClause(whereConditions);
if (norm) whereParts.push(norm);
}
if (whereOrConditions && String(whereOrConditions).trim()) {
const norm = normalizeClause(whereOrConditions);
if (norm) whereParts.push(norm);
}
if (whereDateCondition && String(whereDateCondition).trim()) {
const norm = normalizeClause(whereDateCondition);
if (norm) whereParts.push(norm);
}
const whereClause =
whereParts.length > 0 ? `WHERE ${whereParts.join(" AND ")}` : "";
const queryText = `
SELECT
COUNT(*) OVER() AS total_data,
a.*,
b.shift_name,
b.start_time,
b.end_time
FROM schedule a
LEFT JOIN m_shift b ON a.shift_id = b.shift_id
${whereClause}
ORDER BY a.schedule_id ASC
${searchParams.limit ? `OFFSET $2 * $1 ROWS FETCH NEXT $1 ROWS ONLY` : ""}
`;
const result = await pool.query(queryText, queryParams);
const total =
result?.recordset?.length > 0
? parseInt(result.recordset[0].total_data, 10)
: 0;
return { data: result.recordset, total };
};
// Get by ID
const getScheduleByIdDb = async (id) => {
const queryText = `
SELECT
a.*,
b.shift_name,
b.start_time,
b.end_time
FROM schedule a
LEFT JOIN m_shift b ON a.shift_id = b.shift_id
WHERE a.schedule_id = $1 AND a.deleted_at IS NULL
`;
const result = await pool.query(queryText, [id]);
return result.recordset?.[0] || null;
};
// Insert (bisa multi hari)
const insertScheduleDb = async (store) => {
const nextDays = Number(store.next_day ?? 0);
const insertedRecords = [];
for (let i = 0; i <= nextDays; i++) {
const nextDate = new Date(store.schedule_date);
nextDate.setDate(nextDate.getDate() + i);
const formatted = formattedDate(nextDate);
const newStore = { ...store, schedule_date: formatted };
delete newStore.next_day;
const { query: queryText, values } = pool.buildDynamicInsert("schedule", newStore);
const result = await pool.query(queryText, values);
const insertedId = result.recordset?.[0]?.inserted_id;
if (insertedId) {
const record = await getScheduleByIdDb(insertedId);
insertedRecords.push(record);
}
}
return insertedRecords;
};
// Update
const updateScheduleDb = async (id, data) => {
const store = { ...data };
const whereData = { schedule_id: id };
const { query: queryText, values } = pool.buildDynamicUpdate(
"schedule",
store,
whereData
);
await pool.query(`${queryText} AND deleted_at IS NULL`, values);
return getScheduleByIdDb(id);
};
// Soft delete
const deleteScheduleDb = async (id, deletedBy) => {
const queryText = `
UPDATE schedule
SET deleted_at = CURRENT_TIMESTAMP, deleted_by = $1
WHERE schedule_id = $2 AND deleted_at IS NULL
`;
await pool.query(queryText, [deletedBy, id]);
return true;
};
module.exports = {
getAllScheduleDb,
getScheduleByIdDb,
insertScheduleDb,
updateScheduleDb,
deleteScheduleDb,
};

100
db/shift.db.js Normal file
View File

@@ -0,0 +1,100 @@
const pool = require("../config");
const getAllShiftDb = async (searchParams = {}) => {
let queryParams = [];
// Handle pagination
if (searchParams.limit) {
const page = Number(searchParams.page ?? 1) - 1;
queryParams = [Number(searchParams.limit ?? 10), page];
}
const { whereOrConditions, whereParamOr } = pool.buildStringOrIlike(
["a.shift_name", "a.start_time", "a.end_time"],
searchParams.criteria,
queryParams
);
if (whereParamOr) queryParams = whereParamOr;
const { whereConditions, whereParamAnd } = pool.buildFilterQuery(
[
{ column: "a.shift_name", param: searchParams.name, type: "string" },
{ column: "a.start_time", param: searchParams.start_time, type: "time" },
{ column: "a.end_time", param: searchParams.end_time, type: "time" },
{ column: "a.is_active", param: searchParams.status, type: "string" },
],
queryParams
);
if (whereParamAnd) queryParams = whereParamAnd;
const queryText = `
SELECT
COUNT(*) OVER() AS total_data,
a.*
FROM m_shift a
WHERE a.deleted_at IS NULL
${whereConditions.length > 0 ? ` AND ${whereConditions.join(" AND ")}` : ""}
${whereOrConditions ? ` ${whereOrConditions}` : ""}
ORDER BY a.shift_id ASC
${searchParams.limit ? `OFFSET $2 * $1 ROWS FETCH NEXT $1 ROWS ONLY` : ''}
`;
const result = await pool.query(queryText, queryParams);
const total =
result?.recordset?.length > 0
? parseInt(result.recordset[0].total_data, 10)
: 0;
return { data: result.recordset, total };
};
const getShiftByIdDb = async (id) => {
const queryText = `
SELECT
a.*
FROM m_shift a
WHERE a.shift_id = $1 AND a.deleted_at IS NULL
`;
const result = await pool.query(queryText, [id]);
return result.recordset?.[0] || null;
};
const insertShiftDb = async (store) => {
const { query: queryText, values } = pool.buildDynamicInsert("m_shift", store);
const result = await pool.query(queryText, values);
const insertedId = result.recordset?.[0]?.inserted_id;
return insertedId ? await getShiftByIdDb(insertedId) : null;
};
const updateShiftDb = async (id, data) => {
const store = { ...data };
const whereData = { shift_id: id };
const { query: queryText, values } = pool.buildDynamicUpdate(
"m_shift",
store,
whereData
);
await pool.query(`${queryText} AND deleted_at IS NULL`, values);
return getShiftByIdDb(id);
};
const deleteShiftDb = async (id, deletedBy) => {
const queryText = `
UPDATE m_shift
SET deleted_at = CURRENT_TIMESTAMP, deleted_by = $1
WHERE shift_id = $2 AND deleted_at IS NULL
`;
await pool.query(queryText, [deletedBy, id]);
return true;
};
module.exports = {
getAllShiftDb,
getShiftByIdDb,
insertShiftDb,
updateShiftDb,
deleteShiftDb,
};

187
db/sparepart.db.js Normal file
View File

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

117
db/status.db.js Normal file
View File

@@ -0,0 +1,117 @@
const pool = require("../config");
// Get all status
const getAllStatusDb = async (searchParams = {}) => {
let queryParams = [];
// Pagination
if (searchParams.limit) {
const page = Number(searchParams.page ?? 1) - 1;
queryParams = [Number(searchParams.limit ?? 10), page];
}
// Search
const { whereOrConditions, whereParamOr } = pool.buildStringOrIlike(
["a.status_name", "a.status_description"],
searchParams.criteria,
queryParams
);
queryParams = whereParamOr ? whereParamOr : queryParams;
// Filter
const { whereConditions, whereParamAnd } = pool.buildFilterQuery(
[
{ column: "a.status_number", param: searchParams.number, type: "number" },
{ column: "a.is_active", param: searchParams.is_active, type: "boolean" },
],
queryParams
);
queryParams = whereParamAnd ? whereParamAnd : queryParams;
const queryText = `
SELECT
COUNT(*) OVER() AS total_data,
a.*
FROM m_status a
WHERE a.deleted_at IS NULL
${whereConditions.length > 0 ? `AND ${whereConditions.join(' AND ')}` : ''}
${whereOrConditions ? whereOrConditions : ''}
ORDER BY a.status_id ASC
${searchParams.limit ? `OFFSET $2 * $1 ROWS FETCH NEXT $1 ROWS ONLY` : ''}
`;
const result = await pool.query(queryText, queryParams);
const total =
result?.recordset.length > 0
? parseInt(result.recordset[0].total_data, 10)
: 0;
return { data: result.recordset, total };
};
const getStatusByIdDb = async (id) => {
const queryText = `
SELECT *
FROM m_status a
WHERE a.status_id = $1 AND a.deleted_at IS NULL
`;
const result = await pool.query(queryText, [id]);
return result.recordset;
};
// Check if status_number already exists
const checkStatusNumberExistsDb = async (status_number) => {
const queryText = `
SELECT 1
FROM m_status
WHERE status_number = $1 AND deleted_at IS NULL
`;
const result = await pool.query(queryText, [status_number]);
return result.recordset.length > 0;
};
const createStatusDb = async (data) => {
const { query: queryText, values } = pool.buildDynamicInsert(
"m_status",
data
);
const result = await pool.query(queryText, values);
const insertedId = result.recordset[0]?.inserted_id;
return insertedId ? await getStatusByIdDb(insertedId) : null;
};
const updateStatusDb = async (id, data) => {
const store = { ...data };
const whereData = { status_id: id };
const { query: queryText, values } = pool.buildDynamicUpdate(
"m_status",
store,
whereData
);
await pool.query(`${queryText} AND deleted_at IS NULL`, values);
return getStatusByIdDb(id);
};
const deleteStatusDb = async (id, deletedBy) => {
const queryText = `
UPDATE m_status
SET deleted_at = CURRENT_TIMESTAMP, deleted_by = $1
WHERE status_id = $2 AND deleted_at IS NULL
`;
await pool.query(queryText, [deletedBy, id]);
return true;
};
module.exports = {
getAllStatusDb,
getStatusByIdDb,
createStatusDb,
updateStatusDb,
deleteStatusDb,
checkStatusNumberExistsDb,
};

140
db/tags.db.js Normal file
View File

@@ -0,0 +1,140 @@
const pool = require("../config");
// Get all tags
const getAllTagsDb = async (searchParams = {}) => {
let queryParams = [];
if (searchParams.limit) {
const page = Number(searchParams.page ?? 1) - 1;
queryParams = [Number(searchParams.limit ?? 10), page];
}
const { whereOrConditions, whereParamOr } = pool.buildStringOrIlike(
[
"a.tag_name",
"a.tag_code",
"a.tag_number",
"a.data_type",
"a.unit",
"b.device_name",
"c.plant_sub_section_name",
],
searchParams.criteria,
queryParams
);
if (whereParamOr) queryParams = whereParamOr;
const { whereConditions, whereParamAnd } = pool.buildFilterQuery(
[
{ column: "a.tag_name", param: searchParams.name, type: "string" },
{ column: "a.tag_code", param: searchParams.code, type: "string" },
{ column: "a.data_type", param: searchParams.data, type: "string" },
{ column: "a.unit", param: searchParams.unit, type: "string" },
{ column: "b.device_name", param: searchParams.device, type: "string" },
{
column: "b.device_code",
param: searchParams.device,
type: "string",
},
{
column: "c.plant_sub_section_name",
param: searchParams.subsection,
type: "string",
},
],
queryParams
);
if (whereParamAnd) queryParams = whereParamAnd;
const queryText = `
SELECT
COUNT(*) OVER() AS total_data,
a.*,
b.device_name,
b.device_code,
c.plant_sub_section_name
FROM m_tags a
LEFT JOIN m_device b ON a.device_id = b.device_id
LEFT JOIN m_plant_sub_section c ON a.plant_sub_section_id = c.plant_sub_section_id
WHERE a.deleted_at IS NULL
${whereConditions.length > 0 ? ` AND ${whereConditions.join(" AND ")}` : ""}
${whereOrConditions ? ` ${whereOrConditions}` : ""}
ORDER BY a.tag_id ASC
${searchParams.limit ? `OFFSET $2 * $1 ROWS FETCH NEXT $1 ROWS ONLY` : ''}
`;
const result = await pool.query(queryText, queryParams);
const total =
result?.recordset?.length > 0
? parseInt(result.recordset[0].total_data, 10)
: 0;
return { data: result.recordset, total };
};
const getTagsByIdDb = async (id) => {
const queryText = `
SELECT
a.*,
b.device_name,
b.device_code,
c.plant_sub_section_name
FROM m_tags a
LEFT JOIN m_device b ON a.device_id = b.device_id
LEFT JOIN m_plant_sub_section c ON a.plant_sub_section_id = c.plant_sub_section_id
WHERE a.tag_id = $1 AND a.deleted_at IS NULL
`;
const result = await pool.query(queryText, [id]);
return result.recordset;
};
const createTagsDb = async (data) => {
const newCode = await pool.generateKode("TAG", "m_tags", "tag_code");
const store = {
...data,
tag_code: newCode,
};
const { query: queryText, values } = pool.buildDynamicInsert("m_tags", store);
const result = await pool.query(queryText, values);
const insertedId = result.recordset[0]?.inserted_id;
return insertedId ? await getTagsByIdDb(insertedId) : null;
};
const updateTagsDb = async (id, data) => {
const store = { ...data };
const whereData = { tag_id: id };
const { query: queryText, values } = pool.buildDynamicUpdate(
"m_tags",
store,
whereData
);
await pool.query(`${queryText} AND deleted_at IS NULL`, values);
return getTagsByIdDb(id);
};
// Soft delete tag
const deleteTagsDb = async (id, deletedBy) => {
const queryText = `
UPDATE m_tags
SET deleted_at = CURRENT_TIMESTAMP, deleted_by = $1
WHERE tag_id = $2 AND deleted_at IS NULL
`;
await pool.query(queryText, [deletedBy, id]);
return true;
};
module.exports = {
getAllTagsDb,
getTagsByIdDb,
createTagsDb,
updateTagsDb,
deleteTagsDb,
};

119
db/unit.db.js Normal file
View File

@@ -0,0 +1,119 @@
const pool = require("../config");
// Get all units
const getAllUnitsDb = async (searchParams = {}) => {
let queryParams = [];
// Pagination
if (searchParams.limit) {
const page = Number(searchParams.page ?? 1) - 1;
queryParams = [Number(searchParams.limit ?? 10), page];
}
// Search
const { whereOrConditions, whereParamOr } = pool.buildStringOrIlike(
["a.unit_code", "a.unit_name"],
searchParams.criteria,
queryParams
);
queryParams = whereParamOr ? whereParamOr : queryParams;
// Filter
const { whereConditions, whereParamAnd } = pool.buildFilterQuery(
[
{ column: "a.unit_code", param: searchParams.code, type: "string" },
{ column: "a.unit_name", param: searchParams.name, type: "string" },
{ column: "a.tag_id", param: searchParams.tag, type: "number" },
{ column: "a.is_active", param: searchParams.status, type: "string" },
],
queryParams
);
queryParams = whereParamAnd ? whereParamAnd : queryParams;
const queryText = `
SELECT
COUNT(*) OVER() AS total_data,
a.*,
COALESCE(a.unit_code, '') + ' - ' + COALESCE(a.unit_name, '') AS unit_code_name
FROM m_unit a
WHERE a.deleted_at IS NULL
${whereConditions.length > 0 ? `AND ${whereConditions.join(" AND ")}` : ""}
${whereOrConditions ? whereOrConditions : ""}
ORDER BY a.unit_id ASC
${searchParams.limit ? `OFFSET $2 * $1 ROWS FETCH NEXT $1 ROWS ONLY` : ''}
`;
const result = await pool.query(queryText, queryParams);
const total =
result?.recordset.length > 0
? parseInt(result.recordset[0].total_data, 10)
: 0;
return { data: result.recordset, total };
};
// Get unit by ID
const getUnitByIdDb = async (id) => {
const queryText = `
SELECT
a.*,
COALESCE(a.unit_code, '') + ' - ' + COALESCE(a.unit_name, '') AS unit_code_name
FROM m_unit a
WHERE a.unit_id = $1 AND a.deleted_at IS NULL
`;
const result = await pool.query(queryText, [id]);
return result.recordset;
};
// Create unit
const createUnitDb = async (data) => {
const newCode = await pool.generateKode("UNT", "m_unit", "unit_code");
const store = {
...data,
unit_code: newCode,
};
const { query: queryText, values } = pool.buildDynamicInsert("m_unit", store);
const result = await pool.query(queryText, values);
const insertedId = result.recordset[0]?.inserted_id;
return insertedId ? await getUnitByIdDb(insertedId) : null;
};
// Update unit
const updateUnitDb = async (id, data) => {
const store = { ...data };
const whereData = { unit_id: id };
const { query: queryText, values } = pool.buildDynamicUpdate(
"m_unit",
store,
whereData
);
await pool.query(`${queryText} AND deleted_at IS NULL`, values);
return getUnitByIdDb(id);
};
// Soft delete unit
const deleteUnitDb = async (id, deletedBy) => {
const queryText = `
UPDATE m_unit
SET deleted_at = CURRENT_TIMESTAMP, deleted_by = $1
WHERE unit_id = $2 AND deleted_at IS NULL
`;
await pool.query(queryText, [deletedBy, id]);
return true;
};
// Export
module.exports = {
getAllUnitsDb,
getUnitByIdDb,
createUnitDb,
updateUnitDb,
deleteUnitDb,
};

204
db/user.db.js Normal file
View File

@@ -0,0 +1,204 @@
const pool = require("../config");
// Get all users
const getAllUsersDb = async (searchParams = {}) => {
let queryParams = [];
// Pagination
if (searchParams.limit) {
const page = Number(searchParams.page ?? 1) - 1;
queryParams = [Number(searchParams.limit ?? 10), page];
}
// Search
const { whereOrConditions, whereParamOr } = pool.buildStringOrIlike(
[
"u.user_fullname",
"u.user_name",
"u.user_email",
"r.role_name"
],
searchParams.criteria,
queryParams
);
queryParams = whereParamOr ? whereParamOr : queryParams;
// Filter
const { whereConditions, whereParamAnd } = pool.buildFilterQuery(
[
{ column: "u.user_fullname", param: searchParams.fullname, type: "string" },
{ column: "u.user_name", param: searchParams.username, type: "string" },
{ column: "u.user_email", param: searchParams.email, type: "string" },
{ column: "r.role_name", param: searchParams.role, type: "string" },
],
queryParams
);
queryParams = whereParamAnd ? whereParamAnd : queryParams;
const queryText = `
SELECT
COUNT(*) OVER() AS total_data,
u.user_id, u.user_fullname, u.user_name, u.user_email, u.user_phone,
u.is_active, u.is_sa, u.is_approve, u.approved_by,
approver.user_fullname AS approved_by_name,
u.approved_at, u.created_at, u.updated_at, u.deleted_at,
u.updated_by, u.deleted_by,
r.role_id, r.role_name, r.role_description, r.role_level
FROM m_users u
LEFT JOIN m_roles r ON u.role_id = r.role_id
LEFT JOIN m_users approver ON u.approved_by = approver.user_id
WHERE u.deleted_at IS NULL
${whereConditions.length > 0 ? ` AND ${whereConditions.join(' AND ')}` : ''}
${whereOrConditions ? whereOrConditions : ''}
ORDER BY u.user_id ASC
${searchParams.limit ? `OFFSET $2 * $1 ROWS FETCH NEXT $1 ROWS ONLY` : ''}
`;
const result = await pool.query(queryText, queryParams);
const total =
result?.recordset.length > 0
? parseInt(result.recordset[0].total_data, 10)
: 0;
return { data: result.recordset, total };
};
// Get user by ID
const getUserByIdDb = async (id) => {
const queryText = `
SELECT
u.user_id, u.user_fullname, u.user_name, u.user_email, u.user_phone,
u.is_active, u.is_sa, u.is_approve, u.approved_by,
approver.user_fullname AS approved_by_name,
u.approved_at, u.created_at, u.updated_at, u.deleted_at,
u.updated_by, u.deleted_by,
r.role_id, r.role_name, r.role_description, r.role_level
FROM m_users u
LEFT JOIN m_roles r ON u.role_id = r.role_id
LEFT JOIN m_users approver ON u.approved_by = approver.user_id
WHERE u.user_id = $1 AND u.deleted_at IS NULL
`;
const result = await pool.query(queryText, [id]);
return result.recordset[0];
};
// Get user by email
const getUserByUserEmailDb = async (email) => {
const queryText = `
SELECT
u.user_id, u.user_fullname, u.user_name, u.user_email, u.user_phone,
u.user_password, u.is_active, u.is_sa, u.is_approve, u.role_id,
r.role_name, r.role_description, r.role_level
FROM m_users u
LEFT JOIN m_roles r ON u.role_id = r.role_id
WHERE u.user_email = $1 AND u.deleted_at IS NULL
`;
const result = await pool.query(queryText, [email]);
return result.recordset[0];
};
// Get user by username
const getUserByUsernameDb = async (username) => {
const queryText = `
SELECT
u.user_id, u.user_fullname, u.user_name, u.user_email, u.user_phone,
u.user_password, u.is_active, u.is_sa, u.is_approve, u.role_id,
r.role_name, r.role_description, r.role_level
FROM m_users u
LEFT JOIN m_roles r ON u.role_id = r.role_id
WHERE u.user_name = $1 AND u.deleted_at IS NULL
`;
const result = await pool.query(queryText, [username]);
return result.recordset[0];
};
// Create user
const createUserDb = async (data) => {
const { query: queryText, values } = pool.buildDynamicInsert("m_users", data);
const result = await pool.query(queryText, values);
const insertedId = result.recordset[0]?.inserted_id;
return insertedId ? await getUserByIdDb(insertedId) : null;
};
// Update user
const updateUserDb = async (userId, data) => {
const { query: queryText, values } = pool.buildDynamicUpdate("m_users", data, {
user_id: userId,
});
await pool.query(`${queryText} AND deleted_at IS NULL`, values);
return getUserByIdDb(userId);
};
// Approve user
const approveUserDb = async (userId, approverId) => {
const queryText = `
UPDATE m_users
SET
is_approve = 2,
approved_by = $1,
approved_at = CURRENT_TIMESTAMP,
updated_by = $1,
updated_at = CURRENT_TIMESTAMP
WHERE user_id = $2 AND deleted_at IS NULL
`;
await pool.query(queryText, [approverId, userId]);
return true;
};
// Reject user
const rejectUserDb = async (userId, approverId) => {
const queryText = `
UPDATE m_users
SET
is_approve = 0,
approved_by = $1,
approved_at = CURRENT_TIMESTAMP,
updated_by = $1,
updated_at = CURRENT_TIMESTAMP
WHERE user_id = $2 AND deleted_at IS NULL
`;
await pool.query(queryText, [approverId, userId]);
return true;
}
// Change user password
const changeUserPasswordDb = async (userId, newPassword) => {
const queryText = `
UPDATE m_users
SET user_password = $1, updated_at = CURRENT_TIMESTAMP
WHERE user_id = $2 AND deleted_at IS NULL
`;
await pool.query(queryText, [newPassword, userId]);
return true;
};
// Soft delete user
const deleteUserDb = async (userId, deletedBy) => {
const queryText = `
UPDATE m_users
SET
deleted_at = CURRENT_TIMESTAMP,
deleted_by = $1,
is_active = 0
WHERE user_id = $2
AND deleted_at IS NULL
`;
await pool.query(queryText, [deletedBy, userId]);
return true;
};
module.exports = {
getAllUsersDb,
getUserByIdDb,
getUserByUserEmailDb,
getUserByUsernameDb,
createUserDb,
updateUserDb,
approveUserDb,
rejectUserDb,
changeUserPasswordDb,
deleteUserDb,
};

170
db/user_schedule.db.js Normal file
View File

@@ -0,0 +1,170 @@
const pool = require("../config");
const getAllUserScheduleDb = async (searchParams = {}) => {
let queryParams = [];
// Handle pagination
if (searchParams.limit) {
const page = Number(searchParams.page ?? 1) - 1;
queryParams = [Number(searchParams.limit ?? 10), page];
}
const { whereOrConditions, whereParamOr } = pool.buildStringOrIlike(
["a.user_id", "a.user_schedule_id", "a.schedule_id", "a.user_schedule_id", "a.shift_id"],
searchParams.criteria,
queryParams
);
if (whereParamOr) queryParams = whereParamOr;
const { whereConditions, whereParamAnd } = pool.buildFilterQuery(
[
{ column: "a.user_id", param: searchParams.user_id, type: "int" },
{
column: "a.user_schedule_id",
param: searchParams.user_schedule_id,
type: "int",
},
{ column: "a.schedule_id", param: searchParams.schedule_id, type: "int" },
{ column: "a.shift_id", param: searchParams.shift_id, type: "int" },
],
queryParams
);
if (whereParamAnd) queryParams = whereParamAnd;
const queryText = `
SELECT
COUNT(*) OVER() AS total_data,
a.*,
c.shift_name,
c.start_time,
c.end_time,
d.user_fullname,
d.user_name,
d.user_phone
FROM user_schedule a
LEFT JOIN schedule b ON a.schedule_id = b.schedule_id
LEFT JOIN m_shift c ON a.shift_id = c.shift_id
LEFT JOIN m_users d ON a.user_id = d.user_id
WHERE a.deleted_at IS NULL
${
whereConditions.length > 0
? ` AND ${whereConditions.join(" AND ")}`
: ""
}
${whereOrConditions ? ` ${whereOrConditions}` : ""}
ORDER BY c.shift_id ASC
${searchParams.limit ? `OFFSET $2 * $1 ROWS FETCH NEXT $1 ROWS ONLY` : ""}
`;
const result = await pool.query(queryText, queryParams);
const records = result.recordset || [];
const total = records.length > 0 ? parseInt(records[0].total_data, 10) : 0;
const groupedShift = {};
records.forEach((row) => {
if (!groupedShift[row.shift_id]) {
groupedShift[row.shift_id] = {
shift: {
shift_id: row.shift_id,
shift_name: row.shift_name,
start_time: row.start_time,
end_time: row.end_time,
users: [],
},
};
}
groupedShift[row.shift_id].shift.users.push({
user_schedule_id: row.user_schedule_id,
user_id: row.user_id,
user_fullname: row.user_fullname,
user_name: row.user_name,
user_phone: row.user_phone,
});
});
const data = Object.values(groupedShift);
return { data, total };
};
const getUserScheduleById = async (userId, shiftId) => {
const queryText = `
SELECT
a.user_schedule_id,
a.user_id,
a.shift_id
FROM user_schedule a
WHERE a.user_id = $1
AND a.shift_id = $2
AND a.deleted_at IS NULL
`;
const result = await pool.query(queryText, [userId, shiftId]);
return result.recordset || [];
};
const getUserScheduleByIdDb = async (id) => {
const queryText = `
SELECT
a.*,
b.schedule_date,
c.shift_name,
c.start_time,
c.end_time,
d.user_fullname,
d.user_name,
d.user_phone
FROM user_schedule a
LEFT JOIN schedule b ON a.schedule_id = b.schedule_id
LEFT JOIN m_shift c ON a.shift_id = c.shift_id
LEFT JOIN m_users d ON a.user_id = d.user_id
WHERE a.user_schedule_id = $1 AND a.deleted_at IS NULL
`;
const result = await pool.query(queryText, [id]);
return result.recordset?.[0] || null;
};
const insertUserScheduleDb = async (store) => {
const { query: queryText, values } = pool.buildDynamicInsert(
"user_schedule",
store
);
const result = await pool.query(queryText, values);
const insertedId = result.recordset?.[0]?.inserted_id;
return insertedId ? await getUserScheduleByIdDb(insertedId) : null;
};
const updateUserScheduleDb = async (id, data) => {
const store = { ...data };
const whereData = { user_schedule_id: id };
const { query: queryText, values } = pool.buildDynamicUpdate(
"user_schedule",
store,
whereData
);
await pool.query(`${queryText} AND deleted_at IS NULL`, values);
return getUserScheduleByIdDb(id);
};
const deleteUserScheduleDb = async (id, deletedBy) => {
const queryText = `
UPDATE user_schedule
SET deleted_at = CURRENT_TIMESTAMP, deleted_by = $1
WHERE user_schedule_id = $2 AND deleted_at IS NULL
`;
await pool.query(queryText, [deletedBy, id]);
return true;
};
module.exports = {
getAllUserScheduleDb,
getUserScheduleByIdDb,
insertUserScheduleDb,
updateUserScheduleDb,
deleteUserScheduleDb,
getUserScheduleById,
};

22
ecosystem.config.js Normal file
View File

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

24
helpers/error.js Normal file
View File

@@ -0,0 +1,24 @@
const { logger } = require("../utils/logger");
class ErrorHandler extends Error {
constructor(statusCode, message) {
super();
this.status = "error";
this.statusCode = statusCode;
this.message = message;
}
}
const handleError = (err, req, res, next) => {
const { statusCode, message } = err;
logger.error(err);
res.status(statusCode || 500).json({
status: "error",
statusCode: statusCode || 500,
message: statusCode === 500 ? "An error occurred" : message,
});
next();
};
module.exports = {
ErrorHandler,
handleError,
};

12
helpers/hashPassword.js Normal file
View File

@@ -0,0 +1,12 @@
const bcrypt = require("bcrypt");
const hashPassword = async (password) => {
const salt = await bcrypt.genSalt();
const hashedPassword = await bcrypt.hash(password, salt);
return hashedPassword;
};
const comparePassword = async (password, passwordHash) =>
await bcrypt.compare(password, passwordHash);
module.exports = { hashPassword, comparePassword };

13
helpers/test_helper.js Normal file
View File

@@ -0,0 +1,13 @@
const pool = require("../config");
const usersInDb = async () => {
const users = await pool.query("SELECT * FROM USERS");
return users.rows;
};
const productsInDb = async () => {
const products = await pool.query("SELECT * FROM products");
return products.rows;
};
module.exports = { usersInDb, productsInDb };

90
helpers/utils.js Normal file
View File

@@ -0,0 +1,90 @@
const setResponse = (data = null, message = "success", statusCode = 200) => {
const total = Array.isArray(data) ? data.length : null;
return {
message,
statusCode,
rows: total,
data,
};
};
const setResponsePaging = async (queryParam, data = [], message = "success", statusCode = 200) => {
const totalPages = Math.ceil(data?.total / Number(queryParam.limit ?? 0));
const response = {
message,
statusCode,
rows: data?.data?.length,
paging: {
current_limit: Number(queryParam.limit ?? 0),
current_page: Number(queryParam.page ?? 0),
total_limit: data?.total,
total_page: totalPages
},
data: data?.data ?? []
}
return response
};
function convertId(items, id, fieldId = "id", fieldName = "name") {
var match = ""
items.forEach(element => {
if (element[fieldId] == id) {
match = element[fieldName]
}
});
return match
}
function formatToYYYYMMDD(date) {
return new Intl.DateTimeFormat('id-ID', {
timeZone: 'Asia/Jakarta',
year: 'numeric',
month: '2-digit',
day: '2-digit',
}).format(date).split('/').reverse().join('-'); // Ubah format dari dd/mm/yyyy ke yyyy-mm-dd
}
function ensureArray(param) {
return Array.isArray(param) ? param : [param];
}
function orderByClauseQuery(orderParams) {
orderParams = ensureArray(orderParams)
// Transform order parameters to SQL ORDER BY syntax
const validDirections = ['ASC', 'DESC'];
const orderConditions = orderParams.map(param => {
const [field, direction] = param.split(':');
if (!validDirections.includes(direction.toUpperCase())) {
throw new Error(`Invalid direction: ${direction}`);
}
return `${field} ${direction}`;
});
// Gabungkan dengan koma untuk digunakan dalam query
const orderByClause = orderConditions.join(', ');
return orderByClause
}
const checkValidate = (validateSchema, req) => {
const { error, value } = validateSchema.validate(req.body || {}, { abortEarly: false });
if (error) {
const errors = error.details.reduce((acc, cur) => {
const field = Array.isArray(cur.path) ? cur.path.join('.') : String(cur.path);
if (!acc[field]) acc[field] = [];
acc[field].push(cur.message);
return acc;
}, {});
return { error: errors, value }
}
return { error, value }
}
module.exports = { setResponse, setResponsePaging, convertId, formatToYYYYMMDD, orderByClauseQuery, checkValidate };

10
index.js Normal file
View File

@@ -0,0 +1,10 @@
require("dotenv").config({ path: __dirname + "/.env" });
const http = require("http");
const app = require("./app");
const { logger } = require("./utils/logger");
const server = http.createServer(app);
const PORT = process.env.PORT || 9533;
server.listen(PORT, () => logger.info(`Magic happening on port: ${PORT}`));

8
middleware/imagekit.js Normal file
View File

@@ -0,0 +1,8 @@
const Imagekit = require("imagekit");
const { IMAGEKIT_URL_ENDPOINT, IMAGEKIT_PUBLIC_KEY, IMAGEKIT_PRIVATE_KEY } = process.env;
module.exports = new Imagekit({
publicKey: IMAGEKIT_PUBLIC_KEY,
privateKey: IMAGEKIT_PRIVATE_KEY,
urlEndpoint: IMAGEKIT_URL_ENDPOINT,
});

View File

@@ -0,0 +1,8 @@
const { ErrorHandler } = require("../helpers/error");
// eslint-disable-next-line no-unused-vars
const unknownEndpoint = (request, response) => {
throw new ErrorHandler(401, "unknown endpoint");
};
module.exports = unknownEndpoint;

5
middleware/upload.js Normal file
View File

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

69
middleware/uploads.js Normal file
View File

@@ -0,0 +1,69 @@
const multer = require("multer");
const path = require("path");
const { randomUUID } = require("crypto");
const fs = require("fs");
// Fungsi untuk tentuin folder berdasarkan file type
function getFolderByType(mimetype) {
if (mimetype === "application/pdf") {
return "pdf";
} else if (mimetype.startsWith("image/")) {
return "images";
}
return "file";
}
const storage = multer.diskStorage({
destination: (req, file, cb) => {
const folderName = getFolderByType(file.mimetype);
const folderPath = path.join(__dirname, "../uploads", folderName);
fs.mkdirSync(folderPath, { recursive: true });
cb(null, folderPath);
},
filename: (req, file, cb) => {
const ext = path.extname(file.originalname);
const timestamp = Date.now();
const date = new Date(timestamp);
const year = date.getFullYear();
const month = String(date.getMonth() + 1).padStart(2, "0");
const day = String(date.getDate()).padStart(2, "0");
const hours = String(date.getHours()).padStart(2, "0");
const minutes = String(date.getMinutes()).padStart(2, "0");
const seconds = String(date.getSeconds()).padStart(2, "0");
const formattedDate = `${year}-${month}-${day}_${hours}-${minutes}-${seconds}`;
// Prefix berdasarkan tipe file
const prefix = file.mimetype === "application/pdf" ? "pdf" : "img";
const uniqueId = randomUUID().slice(0, 8);
cb(null, `${prefix}-${uniqueId}-${formattedDate}${ext}`);
},
});
const upload = multer({
storage,
fileFilter: (req, file, cb) => {
const allowedTypes = /jpeg|jpg|png|pdf/;
const allowedMimeTypes = /image\/(jpeg|jpg|png)|application\/pdf/;
const extname = allowedTypes.test(
path.extname(file.originalname).toLowerCase()
);
const mimetype = allowedMimeTypes.test(file.mimetype);
if (extname && mimetype) {
return cb(null, true);
} else {
cb(new Error("File type not allowed. Only PDF and Images (JPEG, JPG, PNG) are accepted."));
}
},
limits: {
fileSize: 10 * 1024 * 1024, // 10MB max file size
},
});
module.exports = upload;

View File

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

53
middleware/verifyToken.js Normal file
View File

@@ -0,0 +1,53 @@
const JWTService = require('../utils/jwt');
const { ErrorHandler } = require('../helpers/error');
function setUser(req, decoded) {
req.user = {
userId: decoded.user_id,
fullname: decoded.user_fullname,
username: decoded.user_name,
email: decoded.user_email,
roleId: decoded.role_id,
roleName: decoded.role_name,
is_sa: decoded.is_sa
};
}
function verifyAccessToken(req, res, next) {
try {
let token = req.cookies?.accessToken;
if (!token) {
const authHeader = req.headers.authorization;
if (authHeader && authHeader.startsWith('Bearer')) {
token = authHeader.split(' ')[1];
} else {
token = req.query.token;
}
}
if (!token) {
throw new ErrorHandler(401, 'Access Token is required');
}
const decoded = JWTService.verifyToken(token);
req.user = decoded;
next();
} catch (error) {
if (error.name === 'TokenExpiredError') {
return next(new ErrorHandler(401, 'Access token expired'));
}
if (error.name === 'JsonWebTokenError') {
return next(new ErrorHandler(401, 'Invalid access token'));
}
return next(new ErrorHandler(500, 'Internal authentication error'));
}
}
module.exports = {
verifyAccessToken
};

66
package.json Normal file
View File

@@ -0,0 +1,66 @@
{
"name": "server",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"start": "cross-env NODE_ENV=production node index",
"dev": "cross-env NODE_ENV=development && nodemon --legacy-watch",
"test": "cross-env NODE_ENV=test jest --verbose --runInBand",
"test:watch": "cross-env NODE_ENV=test jest --verbose --runInBand --watch",
"lint": "eslint .",
"lint:fix": "eslint . --fix",
"format": "prettier --write ."
},
"jest": {
"testEnvironment": "node",
"coveragePathIgnorePatterns": [
"/node_modules/"
]
},
"keywords": [],
"author": "",
"license": "ISC",
"dependencies": {
"axios": "^1.9.0",
"bcrypt": "^5.1.1",
"compression": "^1.7.4",
"cookie-parser": "^1.4.6",
"cors": "^2.8.5",
"crypto": "^1.0.1",
"crypto-js": "^4.2.0",
"dotenv": "^8.2.0",
"exceljs": "^4.4.0",
"express": "^4.18.2",
"express-async-errors": "^3.1.1",
"google-auth-library": "^8.7.0",
"googleapis": "^112.0.0",
"helmet": "^4.4.1",
"imagekit": "^6.0.0",
"joi": "^17.13.3",
"jsonwebtoken": "^8.5.1",
"moment": "^2.29.4",
"morgan": "^1.10.0",
"mqtt": "^5.14.0",
"mssql": "^11.0.1",
"multer": "^1.4.5-lts.2",
"nodemailer": "^6.8.0",
"pg": "^8.8.0",
"pino": "^6.11.3",
"stripe": "^8.138.0",
"svg-captcha": "^1.4.0",
"swagger-ui-express": "^4.6.0",
"uuid": "^11.1.0"
},
"devDependencies": {
"babel-eslint": "^10.1.0",
"cross-env": "^7.0.3",
"eslint": "^7.32.0",
"eslint-plugin-babel": "^5.3.1",
"eslint-plugin-prettier": "^4.2.1",
"nodemon": "^2.0.20",
"pino-pretty": "^4.8.0",
"prettier": "^2.8.1",
"supertest": "^6.3.3"
}
}

12
routes/auth.route.js Normal file
View File

@@ -0,0 +1,12 @@
const express = require('express');
const AuthController = require("../controllers/auth.controller");
const router = express.Router();
router.post('/login', AuthController.login);
router.post('/register', AuthController.register);
router.get('/generate-captcha', AuthController.generateCaptcha);
router.post('/refresh-token', AuthController.refreshToken);
router.post('/verify-redirect', AuthController.verifyTokenRedirect);
module.exports = router;

17
routes/brand.route.js Normal file
View File

@@ -0,0 +1,17 @@
const express = require('express');
const BrandController = require('../controllers/brand.controller');
const verifyToken = require('../middleware/verifyToken');
const verifyAccess = require('../middleware/verifyAccess');
const router = express.Router();
router.route('/')
.get(verifyToken.verifyAccessToken, BrandController.getAll)
.post(verifyToken.verifyAccessToken, verifyAccess(), BrandController.create);
router.route('/:id')
.get(verifyToken.verifyAccessToken, BrandController.getById)
.put(verifyToken.verifyAccessToken, verifyAccess(), BrandController.update)
.delete(verifyToken.verifyAccessToken, verifyAccess(), BrandController.delete);
module.exports = router;

17
routes/contact.route.js Normal file
View File

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

17
routes/device.route.js Normal file
View File

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

View File

@@ -0,0 +1,19 @@
const express = require('express');
const ErrorCodeController = require('../controllers/error_code.controller');
const verifyToken = require('../middleware/verifyToken');
const verifyAccess = require('../middleware/verifyAccess');
const router = express.Router();
router.route('/brand/:brandId')
.get(verifyToken.verifyAccessToken, ErrorCodeController.getByBrandId)
.post(verifyToken.verifyAccessToken, verifyAccess(), ErrorCodeController.create);
router.route('/brand/:brandId/:errorCodeId')
.put(verifyToken.verifyAccessToken, verifyAccess(), ErrorCodeController.update)
.delete(verifyToken.verifyAccessToken, verifyAccess(), ErrorCodeController.delete);
router.route('/:id')
.get(verifyToken.verifyAccessToken, ErrorCodeController.getById);
module.exports = router;

View File

@@ -0,0 +1,17 @@
const router = require("express").Router();
const upload = require("../middleware/uploads");
const verifyToken = require("../middleware/verifyToken");
const verifyAccess = require("../middleware/verifyAccess");
const {
uploadFile,
getFileByPath,
deleteFileByPath,
} = require("../controllers/file_uploads.controller");
router.post("/", verifyToken.verifyAccessToken, verifyAccess(), upload.single("file"), uploadFile);
router.route("/:folder/:filename")
.get(verifyToken.verifyAccessToken, getFileByPath)
.delete(verifyToken.verifyAccessToken, verifyAccess(), deleteFileByPath);
module.exports = router;

View File

@@ -0,0 +1,19 @@
const express = require('express');
const HistoryValueController = require('../controllers/history_value.controller');
const verifyToken = require('../middleware/verifyToken');
const verifyAccess = require('../middleware/verifyAccess');
const router = express.Router();
router.route('/alarm')
.get(verifyToken.verifyAccessToken, HistoryValueController.getAllHistoryAlarm)
router.route('/event')
.get(verifyToken.verifyAccessToken, HistoryValueController.getAllHistoryEvent)
router.route('/value-report')
.get(verifyToken.verifyAccessToken, HistoryValueController.getHistoryValueReport)
router.route('/value-report-pivot')
.get(verifyToken.verifyAccessToken, HistoryValueController.getHistoryValueReportPivot)
router.route('/value-trending')
.get(verifyToken.verifyAccessToken, HistoryValueController.getHistoryValueTrendingPivot)
module.exports = router;

46
routes/index.js Normal file
View File

@@ -0,0 +1,46 @@
const router = require("express").Router();
const auth = require("./auth.route");
const users = require("./users.route");
const device = require('./device.route');
const roles = require('./roles.route');
const tags = require("./tags.route");
const brand = require("./brand.route");
const subSection = require("./plant_sub_section.route");
const fileUploads = require("./file_uploads.route");
const shift = require("./shift.route");
const schedule = require("./schedule.route");
const status = require("./status.route");
const unit = require("./unit.route")
const UserSchedule = require("./user_schedule.route")
const historyValue = require("./history_value.route")
const contact = require("./contact.route")
const notificationError = require("./notification_error.route")
const notificationErrorSparepart = require("./notification_error_sparepart.route")
const sparepart = require("./sparepart.route")
const notificationErrorLog = require("./notification_error_log.route")
const notificationErrorUser = require("./notification_error_user.route")
const errorCode = require("./error_code.route")
router.use("/auth", auth);
router.use("/user", users);
router.use("/device", device);
router.use("/roles", roles);
router.use("/tags", tags);
router.use("/brand", brand);
router.use("/plant-sub-section", subSection);
router.use("/file-uploads", fileUploads);
router.use("/shift", shift);
router.use("/schedule", schedule);
router.use("/status", status);
router.use("/unit", unit);
router.use("/user-schedule", UserSchedule)
router.use("/history", historyValue)
router.use("/contact", contact)
router.use("/notification", notificationError)
router.use("/notification-sparepart", notificationErrorSparepart)
router.use("/sparepart", sparepart)
router.use("/notification-log", notificationErrorLog)
router.use("/notification-user", notificationErrorUser)
router.use("/error-code", errorCode)
module.exports = router;

View File

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

View File

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

View File

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

View File

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

View File

@@ -0,0 +1,17 @@
const express = require('express');
const PlantSubSectionController = require('../controllers/plant_sub_section.controller');
const verifyToken = require('../middleware/verifyToken');
const verifyAccess = require('../middleware/verifyAccess');
const router = express.Router();
router.route('/')
.get(verifyToken.verifyAccessToken, PlantSubSectionController.getAll)
.post(verifyToken.verifyAccessToken, verifyAccess(), PlantSubSectionController.create);
router.route('/:id')
.get(verifyToken.verifyAccessToken, PlantSubSectionController.getById)
.put(verifyToken.verifyAccessToken, verifyAccess(), PlantSubSectionController.update)
.delete(verifyToken.verifyAccessToken, verifyAccess(), PlantSubSectionController.delete);
module.exports = router;

17
routes/roles.route.js Normal file
View File

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

17
routes/schedule.route.js Normal file
View File

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

17
routes/shift.route.js Normal file
View File

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

48
routes/sparepart.route.js Normal file
View File

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

17
routes/status.route.js Normal file
View File

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

17
routes/tags.route.js Normal file
View File

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

17
routes/unit.route.js Normal file
View File

@@ -0,0 +1,17 @@
const express = require('express');
const UnitController = require('../controllers/unit.controller');
const verifyToken = require('../middleware/verifyToken');
const verifyAccess = require('../middleware/verifyAccess');
const router = express.Router();
router.route('/')
.get(verifyToken.verifyAccessToken, UnitController.getAll)
.post(verifyToken.verifyAccessToken, verifyAccess(), UnitController.create);
router.route('/:id')
.get(verifyToken.verifyAccessToken, UnitController.getById)
.put(verifyToken.verifyAccessToken, verifyAccess(), UnitController.update)
.delete(verifyToken.verifyAccessToken, verifyAccess(), UnitController.delete);
module.exports = router;

View File

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

26
routes/users.route.js Normal file
View File

@@ -0,0 +1,26 @@
const express = require('express');
const UserController = require('../controllers/users.controller');
const verifyToken = require('../middleware/verifyToken');
const verifyAccess = require('../middleware/verifyAccess');
const router = express.Router();
router.route('/')
.get(verifyToken.verifyAccessToken, UserController.getAll)
.post(verifyToken.verifyAccessToken, verifyAccess(), UserController.create);
router.route('/:id')
.get(verifyToken.verifyAccessToken, UserController.getById)
.put(verifyToken.verifyAccessToken, verifyAccess(), UserController.update)
.delete(verifyToken.verifyAccessToken, verifyAccess(), UserController.delete);
router.route('/change-password/:id')
.put(verifyToken.verifyAccessToken, verifyAccess(), UserController.changePassword);
router.route('/:id/approve')
.put(verifyToken.verifyAccessToken, verifyAccess(), UserController.approve);
router.route('/:id/reject')
.put(verifyToken.verifyAccessToken, verifyAccess(), UserController.reject);
module.exports = router;

130
services/auth.service.js Normal file
View File

@@ -0,0 +1,130 @@
const {
getUserByUserEmailDb,
createUserDb,
getUserByUsernameDb
} = require('../db/user.db');
const { hashPassword, comparePassword } = require('../helpers/hashPassword');
const { ErrorHandler } = require('../helpers/error');
const JWTService = require('../utils/jwt');
class AuthService {
// Register
static async register(data) {
try {
const existingEmail = await getUserByUserEmailDb(data.user_email);
const existingUsername = await getUserByUsernameDb(data.user_name);
if (existingUsername) {
throw new ErrorHandler(400, 'Username is already taken');
}
if (existingEmail) {
throw new ErrorHandler(400, 'Email is already taken');
}
const hashedPassword = await hashPassword(data.user_password);
const userId = await createUserDb({
user_fullname: data.user_fullname,
user_name: data.user_name,
user_email: data.user_email,
user_phone: data.user_phone,
user_password: hashedPassword,
is_sa: 0,
is_active: 1,
is_approve: 1,
});
const newUser = {
user_id: userId,
user_fullname: data.user_fullname,
user_name: data.user_name,
user_email: data.user_email,
user_phone: data.user_phone
};
return { user: newUser };
} catch (error) {
throw new ErrorHandler(error.statusCode, error.message);
}
}
// Login
static async login(data) {
try {
const { identifier, password, captcha, captchaText } = data;
if (!captcha || captcha.toLowerCase() !== captchaText.toLowerCase()) {
throw new ErrorHandler(400, 'Invalid captcha');
}
let user;
if (identifier.includes('@')) {
user = await getUserByUserEmailDb(identifier);
} else {
user = await getUserByUsernameDb(identifier);
}
if (!user) throw new ErrorHandler(401, 'Invalid credentials');
const passwordMatch = await comparePassword(password, user.user_password);
if (!passwordMatch) throw new ErrorHandler(401, 'Invalid credentials');
if (!user.is_active) throw new ErrorHandler(403, 'User is inactive');
if (!user.is_approve) throw new ErrorHandler(403, 'Your account has not been approved by admin yet.');
const payload = {
user_id: user.user_id,
user_fullname: user.user_fullname,
user_name: user.user_name,
user_email: user.user_email,
user_phone: user.user_phone,
role_id: user.role_id,
role_name: user.role_name,
is_sa: user.is_sa
};
const tokens = JWTService.generateTokenPair(payload);
return { user: payload, tokens };
} catch (error) {
throw new ErrorHandler(error.statusCode, error.message);
}
}
// Refresh Token
static async refreshToken(refreshToken) {
try {
let decoded;
try {
decoded = JWTService.verifyRefreshToken(refreshToken);
} catch (err) {
if (err.message.includes('expired')) {
throw new ErrorHandler(401, 'Refresh token expired');
}
throw new ErrorHandler(401, 'Invalid refresh token');
}
const payload = {
user_id: decoded.user_id,
user_fullname: decoded.user_fullname,
user_name: decoded.user_name,
user_email: decoded.user_email,
user_phone: decoded.user_phone,
role_id: decoded.role_id,
role_name: decoded.role_name,
is_sa: decoded.is_sa
};
const accessToken = JWTService.generateAccessToken(payload);
return {
accessToken,
tokenType: 'Bearer',
expiresIn: 900
};
} catch (error) {
throw new ErrorHandler(error.statusCode, error.message);
}
}
}
module.exports = AuthService;

122
services/brand.service.js Normal file
View File

@@ -0,0 +1,122 @@
// Brand operations
const {
getAllBrandsDb,
getBrandByIdDb,
createBrandDb,
updateBrandDb,
deleteBrandDb,
checkBrandNameExistsDb,
} = require("../db/brand.db");
const { ErrorHandler } = require("../helpers/error");
class BrandService {
// Get all brands
static async getAllBrands(param) {
try {
const results = await getAllBrandsDb(param);
return {
...results,
data: results.data
};
} catch (error) {
throw new ErrorHandler(error.statusCode, error.message);
}
}
// Get brand by ID (without error codes)
static async getBrandById(id) {
try {
const brand = await getBrandByIdDb(id);
if (!brand) throw new ErrorHandler(404, "Brand not found");
return brand;
} catch (error) {
throw new ErrorHandler(error.statusCode, error.message);
}
}
// Create brand
static async createBrand(data) {
try {
if (data.brand_name) {
const brandExists = await checkBrandNameExistsDb(data.brand_name);
if (brandExists) {
throw new ErrorHandler(400, "Brand name already exists");
}
}
const brandData = {
brand_name: data.brand_name,
brand_type: data.brand_type,
brand_manufacture: data.brand_manufacture,
brand_model: data.brand_model,
is_active: data.is_active !== undefined ? data.is_active : true,
created_by: data.created_by,
};
const createdBrand = await createBrandDb(brandData);
if (!createdBrand) {
throw new ErrorHandler(500, "Failed to create brand");
}
return createdBrand;
} catch (error) {
throw new ErrorHandler(error.statusCode || 500, error.message);
}
}
// Soft delete brand by ID
static async deleteBrand(id, userId) {
try {
const brandExist = await getBrandByIdDb(id);
if (!brandExist) {
throw new ErrorHandler(404, "Brand not found");
}
const result = await deleteBrandDb(id, userId);
return result;
} catch (error) {
throw new ErrorHandler(error.statusCode, error.message);
}
}
// Update brand
static async updateBrand(id, data) {
try {
const existingBrand = await getBrandByIdDb(id);
if (!existingBrand) throw new ErrorHandler(404, "Brand not found");
if (data.brand_name && data.brand_name !== existingBrand.brand_name) {
const brandExists = await checkBrandNameExistsDb(data.brand_name, id);
if (brandExists) {
throw new ErrorHandler(400, "Brand name already exists");
}
}
const brandData = {
brand_name: data.brand_name || existingBrand.brand_name,
brand_type: data.brand_type !== undefined ? data.brand_type : existingBrand.brand_type,
brand_manufacture: data.brand_manufacture !== undefined ? data.brand_manufacture : existingBrand.brand_manufacture,
brand_model: data.brand_model !== undefined ? data.brand_model : existingBrand.brand_model,
is_active: data.is_active !== undefined ? data.is_active : existingBrand.is_active,
updated_by: data.updated_by,
};
const updatedBrand = await updateBrandDb(existingBrand.brand_name, brandData);
if (!updatedBrand) {
throw new ErrorHandler(500, "Failed to update brand");
}
return updatedBrand;
} catch (error) {
throw new ErrorHandler(error.statusCode || 500, error.message);
}
}
}
module.exports = BrandService;

View File

@@ -0,0 +1,88 @@
const {
getAllContactDb,
getContactByIdDb,
createContactDb,
updateContactDb,
deleteContactDb
} = require('../db/contact.db');
const { ErrorHandler } = require('../helpers/error');
class ContactService {
// Get all Contact
static async getAllContact(param) {
try {
const results = await getAllContactDb(param);
results.data.map(element => {
});
return results
} catch (error) {
throw new ErrorHandler(error.statusCode, error.message);
}
}
// Get Contact by ID
static async getContactById(id) {
try {
const result = await getContactByIdDb(id);
if (result.length < 1) throw new ErrorHandler(404, 'Contact not found');
return result;
} catch (error) {
throw new ErrorHandler(error.statusCode, error.message);
}
}
// Create Contact
static async createContact(data) {
try {
if (!data || typeof data !== 'object') data = {};
const result = await createContactDb(data);
return result;
} catch (error) {
throw new ErrorHandler(error.statusCode, error.message);
}
}
// Update Contact
static async updateContact(id, data) {
try {
if (!data || typeof data !== 'object') data = {};
const dataExist = await getContactByIdDb(id);
if (dataExist.length < 1) {
throw new ErrorHandler(404, 'Contact not found');
}
const result = await updateContactDb(id, data);
return result;
} catch (error) {
throw new ErrorHandler(error.statusCode, error.message);
}
}
// Soft delete Contact
static async deleteContact(id, userId) {
try {
const dataExist = await getContactByIdDb(id);
if (dataExist.length < 1) {
throw new ErrorHandler(404, 'Contact not found');
}
const result = await deleteContactDb(id, userId);
return result;
} catch (error) {
throw new ErrorHandler(error.statusCode, error.message);
}
}
}
module.exports = ContactService;

View File

@@ -0,0 +1,88 @@
const {
getAllDevicesDb,
getDeviceByIdDb,
createDeviceDb,
updateDeviceDb,
deleteDeviceDb
} = require('../db/device.db');
const { ErrorHandler } = require('../helpers/error');
class DeviceService {
// Get all devices
static async getAllDevices(param) {
try {
const results = await getAllDevicesDb(param);
results.data.map(element => {
});
return results
} catch (error) {
throw new ErrorHandler(error.statusCode, error.message);
}
}
// Get device by ID
static async getDeviceById(id) {
try {
const result = await getDeviceByIdDb(id);
if (result.length < 1) throw new ErrorHandler(404, 'Device not found');
return result;
} catch (error) {
throw new ErrorHandler(error.statusCode, error.message);
}
}
// Create device
static async createDevice(data) {
try {
if (!data || typeof data !== 'object') data = {};
const result = await createDeviceDb(data);
return result;
} catch (error) {
throw new ErrorHandler(error.statusCode, error.message);
}
}
// Update device
static async updateDevice(id, data) {
try {
if (!data || typeof data !== 'object') data = {};
const dataExist = await getDeviceByIdDb(id);
if (dataExist.length < 1) {
throw new ErrorHandler(404, 'Device not found');
}
const result = await updateDeviceDb(id, data);
return result;
} catch (error) {
throw new ErrorHandler(error.statusCode, error.message);
}
}
// Soft delete device
static async deleteDevice(id, userId) {
try {
const dataExist = await getDeviceByIdDb(id);
if (dataExist.length < 1) {
throw new ErrorHandler(404, 'Device not found');
}
const result = await deleteDeviceDb(id, userId);
return result;
} catch (error) {
throw new ErrorHandler(error.statusCode, error.message);
}
}
}
module.exports = DeviceService;

View File

@@ -0,0 +1,265 @@
const { ErrorHandler } = require("../helpers/error");
const {
getErrorCodesByBrandIdDb,
getErrorCodeByIdDb,
getErrorCodeByBrandAndCodeDb,
createErrorCodeDb,
updateErrorCodeDb,
deleteErrorCodeDb,
getAllErrorCodesDb,
} = require("../db/brand_code.db");
const {
getSolutionsByErrorCodeIdDb,
createSolutionDb,
updateSolutionDb,
deleteSolutionDb,
} = require("../db/brand_code_solution.db");
const {
getSparepartsByErrorCodeIdDb,
insertMultipleErrorCodeSparepartsDb,
updateErrorCodeSparepartsDb,
} = require("../db/brand_sparepart.db");
const { getFileUploadByPathDb } = require("../db/file_uploads.db");
class ErrorCodeService {
static async getAllErrorCodes(param) {
try {
const results = await getAllErrorCodesDb(param);
return results;
} catch (error) {
throw new ErrorHandler(error.statusCode, error.message);
}
}
// Get error code by ID with complete data
static async getErrorCodeById(id) {
try {
const errorCode = await getErrorCodeByIdDb(id);
if (!errorCode) throw new ErrorHandler(404, "Error code not found");
const solutions = await getSolutionsByErrorCodeIdDb(errorCode.error_code_id);
const spareparts = await getSparepartsByErrorCodeIdDb(errorCode.error_code_id);
const solutionsWithFiles = await Promise.all(
solutions.map(async (solution) => {
let fileData = null;
if (solution.path_solution && solution.type_solution !== "text") {
fileData = await getFileUploadByPathDb(solution.path_solution);
}
return {
...solution,
file_upload_name: fileData?.file_upload_name || null,
path_document: fileData?.path_document || null,
};
})
);
return {
...errorCode,
solution: solutionsWithFiles,
spareparts: spareparts,
};
} catch (error) {
throw new ErrorHandler(error.statusCode, error.message);
}
}
// Get error codes by brand ID
static async getErrorCodesByBrandId(brandId, queryParams = {}) {
try {
const results = await getErrorCodesByBrandIdDb(brandId, queryParams);
if (results.data && results.total !== undefined) {
return results;
}
const errorCodesWithDetails = await Promise.all(
results.map(async (errorCode) => {
const solutions = await getSolutionsByErrorCodeIdDb(errorCode.error_code_id);
const spareparts = await getSparepartsByErrorCodeIdDb(errorCode.error_code_id);
const solutionsWithFiles = await Promise.all(
solutions.map(async (solution) => {
let fileData = null;
if (solution.path_solution && solution.type_solution !== "text") {
fileData = await getFileUploadByPathDb(solution.path_solution);
}
return {
...solution,
file_upload_name: fileData?.file_upload_name || null,
path_document: fileData?.path_document || null,
};
})
);
return {
...errorCode,
solution: solutionsWithFiles,
spareparts: spareparts,
};
})
);
return errorCodesWithDetails;
} catch (error) {
throw new ErrorHandler(error.statusCode, error.message);
}
}
// Create error code with solutions and spareparts
static async createErrorCodeWithFullData(brandId, data) {
try {
if (!data || typeof data !== "object") data = {};
const errorId = await createErrorCodeDb(brandId, {
error_code: data.error_code,
error_code_name: data.error_code_name,
error_code_description: data.error_code_description,
error_code_color: data.error_code_color,
path_icon: data.path_icon,
is_active: data.is_active,
created_by: data.created_by,
});
if (!errorId) {
throw new Error("Failed to create error code");
}
if (data.spareparts && Array.isArray(data.spareparts)) {
await insertMultipleErrorCodeSparepartsDb(errorId, data.spareparts, data.created_by);
}
if (data.solution && Array.isArray(data.solution)) {
for (const solutionData of data.solution) {
await createSolutionDb(errorId, {
solution_name: solutionData.solution_name,
type_solution: solutionData.type_solution,
text_solution: solutionData.text_solution || null,
path_solution: solutionData.path_solution || null,
is_active: solutionData.is_active,
created_by: data.created_by,
});
}
}
const createdErrorCode = await this.getErrorCodeById(errorId);
return createdErrorCode;
} catch (error) {
throw new ErrorHandler(500, `Create error code failed: ${error.message}`);
}
}
// Update error code with solutions and spareparts
static async updateErrorCodeWithFullData(brandId, errorCodeId, data) {
try {
const existingErrorCode = await getErrorCodeByIdDb(errorCodeId);
if (!existingErrorCode) throw new ErrorHandler(404, "Error code not found");
// Verify the error code belongs to the specified brand
if (existingErrorCode.brand_id !== parseInt(brandId)) {
throw new ErrorHandler(403, "Error code does not belong to specified brand");
}
// Check if there are any error code fields to update
const hasMainFieldUpdate =
data.error_code !== undefined ||
data.error_code_name !== undefined ||
data.error_code_description !== undefined ||
data.error_code_color !== undefined ||
data.path_icon !== undefined ||
data.is_active !== undefined;
if (hasMainFieldUpdate) {
await updateErrorCodeDb(brandId, existingErrorCode.error_code, {
error_code: data.error_code,
error_code_name: data.error_code_name,
error_code_description: data.error_code_description,
error_code_color: data.error_code_color,
path_icon: data.path_icon,
is_active: data.is_active,
updated_by: data.updated_by,
});
}
if (data.spareparts && Array.isArray(data.spareparts)) {
await updateErrorCodeSparepartsDb(existingErrorCode.error_code_id, data.spareparts, data.updated_by);
}
if (data.solution && Array.isArray(data.solution)) {
const existingSolutions = await getSolutionsByErrorCodeIdDb(existingErrorCode.error_code_id);
const incomingSolutionNames = data.solution.map((s) => s.solution_name);
for (const solutionData of data.solution) {
const existingSolution = existingSolutions.find(
(s) => s.solution_name === solutionData.solution_name
);
if (existingSolution) {
await updateSolutionDb(
existingSolution.brand_code_solution_id,
{
solution_name: solutionData.solution_name,
type_solution: solutionData.type_solution,
text_solution: solutionData.text_solution || null,
path_solution: solutionData.path_solution || null,
is_active: solutionData.is_active,
updated_by: data.updated_by,
}
);
} else {
await createSolutionDb(existingErrorCode.error_code_id, {
solution_name: solutionData.solution_name,
type_solution: solutionData.type_solution,
text_solution: solutionData.text_solution || null,
path_solution: solutionData.path_solution || null,
is_active: solutionData.is_active,
created_by: data.updated_by,
});
}
}
for (const existingSolution of existingSolutions) {
if (!incomingSolutionNames.includes(existingSolution.solution_name)) {
await deleteSolutionDb(existingSolution.brand_code_solution_id, data.updated_by);
}
}
}
const updatedErrorCode = await this.getErrorCodeById(existingErrorCode.error_code_id);
return updatedErrorCode;
} catch (error) {
throw new ErrorHandler(500, `Update error code failed: ${error.message}`);
}
}
// Soft delete error code
static async deleteErrorCode(brandId, errorCodeId, deletedBy) {
try {
const errorCodeExist = await getErrorCodeByIdDb(errorCodeId);
if (!errorCodeExist) {
throw new ErrorHandler(404, "Error code not found");
}
// Verify the error code belongs to the specified brand
if (errorCodeExist.brand_id !== parseInt(brandId)) {
throw new ErrorHandler(403, "Error code does not belong to specified brand");
}
const result = await deleteErrorCodeDb(brandId, errorCodeExist.error_code, deletedBy);
return result;
} catch (error) {
throw new ErrorHandler(error.statusCode, error.message);
}
}
}
module.exports = ErrorCodeService;

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -0,0 +1,87 @@
const {
getAllSubSectionsDb,
getSubSectionByIdDb,
createSubSectionDb,
updateSubSectionDb,
deleteSubSectionDb
} = require('../db/plant_sub_section.db');
const { ErrorHandler } = require('../helpers/error');
class SubSectionService {
// Get all sub sections
static async getAll(param) {
try {
const results = await getAllSubSectionsDb(param);
results.data.map(el => {});
return results;
} catch (error) {
throw new ErrorHandler(error.statusCode, error.message);
}
}
// Get sub section by ID
static async getById(id) {
try {
const result = await getSubSectionByIdDb(id);
if (result.length < 1) throw new ErrorHandler(404, 'Sub section not found');
return result;
} catch (error) {
throw new ErrorHandler(error.statusCode, error.message);
}
}
// Create sub section
static async create(data) {
try {
if (!data || typeof data !== 'object') data = {};
const result = await createSubSectionDb(data);
return result;
} catch (error) {
throw new ErrorHandler(error.statusCode, error.message);
}
}
// Update sub section
static async update(id, data) {
try {
if (!data || typeof data !== 'object') data = {};
const dataExist = await getSubSectionByIdDb(id);
if (dataExist.length < 1) {
throw new ErrorHandler(404, 'Sub section not found');
}
const result = await updateSubSectionDb(id, data);
return result;
} catch (error) {
throw new ErrorHandler(error.statusCode, error.message);
}
}
// Soft delete sub section
static async delete(id, userId) {
try {
const dataExist = await getSubSectionByIdDb(id);
if (dataExist.length < 1) {
throw new ErrorHandler(404, 'Sub section not found');
}
const result = await deleteSubSectionDb(id, userId);
return result;
} catch (error) {
throw new ErrorHandler(error.statusCode, error.message);
}
}
}
module.exports = SubSectionService;

88
services/roles.service.js Normal file
View File

@@ -0,0 +1,88 @@
const {
getAllRolesDb,
getRolesByIdDb,
insertRolesDb,
updateRolesDb,
deleteRolesDb
} = require('../db/roles.db');
const { ErrorHandler } = require('../helpers/error');
class RolesService {
// Get all Roles
static async getAllRoles(param) {
try {
const results = await getAllRolesDb(param);
results.data.map(element => {
});
return results
} catch (error) {
throw new ErrorHandler(error.statusCode, error.message);
}
}
// Get Roles by ID
static async getRolesById(id) {
try {
const result = await getRolesByIdDb(id);
if (result.length < 1) throw new ErrorHandler(404, 'Roles not found');
return result;
} catch (error) {
throw new ErrorHandler(error.statusCode, error.message);
}
}
// Create Roles
static async createRoles(data) {
try {
if (!data || typeof data !== 'object') data = {};
const result = await insertRolesDb(data);
return result;
} catch (error) {
throw new ErrorHandler(error.statusCode, error.message);
}
}
// Update Roles
static async updateRoles(id, data) {
try {
if (!data || typeof data !== 'object') data = {};
const dataExist = await getRolesByIdDb(id);
if (dataExist.length < 1) {
throw new ErrorHandler(404, 'Roles not found');
}
const result = await updateRolesDb(id, data);
return result;
} catch (error) {
throw new ErrorHandler(error.statusCode, error.message);
}
}
// Soft delete Roles
static async deleteRoles(id, userId) {
try {
const dataExist = await getRolesByIdDb(id);
if (dataExist.length < 1) {
throw new ErrorHandler(404, 'Roles not found');
}
const result = await deleteRolesDb(id, userId);
return result;
} catch (error) {
throw new ErrorHandler(error.statusCode, error.message);
}
}
}
module.exports = RolesService;

View File

@@ -0,0 +1,88 @@
const {
getAllScheduleDb,
getScheduleByIdDb,
insertScheduleDb,
updateScheduleDb,
deleteScheduleDb
} = require('../db/schedule.db');
const { ErrorHandler } = require('../helpers/error');
class ScheduleService {
// Get all Schedule
static async getAllSchedule(param) {
try {
const results = await getAllScheduleDb(param);
results.data.map(element => {
});
return results
} catch (error) {
throw new ErrorHandler(error.statusCode, error.message);
}
}
// Get Schedule by ID
static async getScheduleById(id) {
try {
const result = await getScheduleByIdDb(id);
if (result.length < 1) throw new ErrorHandler(404, 'Schedule not found');
return result;
} catch (error) {
throw new ErrorHandler(error.statusCode, error.message);
}
}
// Create Schedule
static async insertScheduleDb(data) {
try {
if (!data || typeof data !== 'object') data = {};
const result = await insertScheduleDb(data);
return result;
} catch (error) {
throw new ErrorHandler(error.statusCode, error.message);
}
}
// Update Schedule
static async updateSchedule(id, data) {
try {
if (!data || typeof data !== 'object') data = {};
const dataExist = await getScheduleByIdDb(id);
if (dataExist.length < 1) {
throw new ErrorHandler(404, 'Schedule not found');
}
const result = await updateScheduleDb(id, data);
return result;
} catch (error) {
throw new ErrorHandler(error.statusCode, error.message);
}
}
// Soft delete Schedule
static async deleteSchedule(id, userId) {
try {
const dataExist = await getScheduleByIdDb(id);
if (dataExist.length < 1) {
throw new ErrorHandler(404, 'Schedule not found');
}
const result = await deleteScheduleDb(id, userId);
return result;
} catch (error) {
throw new ErrorHandler(error.statusCode, error.message);
}
}
}
module.exports = ScheduleService;

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