Browse Source

update vendor experience in epic 6

pearlgw 1 month ago
parent
commit
434b7d0c02

+ 1 - 0
.gitignore

@@ -3,3 +3,4 @@ node_modules
3
 .env
3
 .env
4
 
4
 
5
 /generated/prisma
5
 /generated/prisma
6
+/storage/img/*

+ 1 - 0
index.js

@@ -18,6 +18,7 @@ const areaRoutes = require('./src/routes/sales/AreaRoute.js')
18
 const vendorSalesRoutes = require('./src/routes/sales/VendorRoute.js')
18
 const vendorSalesRoutes = require('./src/routes/sales/VendorRoute.js')
19
 const { port } = require('./config/config.js')
19
 const { port } = require('./config/config.js')
20
 const keycloak = require('./src/middleware/Keycloak.js');
20
 const keycloak = require('./src/middleware/Keycloak.js');
21
+require("./src/utils/Scheduler.js")
21
 
22
 
22
 app.use(cors())
23
 app.use(cors())
23
 app.use(keycloak.middleware());
24
 app.use(keycloak.middleware());

+ 10 - 0
package-lock.json

@@ -23,6 +23,7 @@
23
         "jsonwebtoken": "^9.0.2",
23
         "jsonwebtoken": "^9.0.2",
24
         "keycloak-connect": "^26.1.1",
24
         "keycloak-connect": "^26.1.1",
25
         "multer": "^2.0.1",
25
         "multer": "^2.0.1",
26
+        "node-cron": "^4.2.1",
26
         "pg": "^8.16.2",
27
         "pg": "^8.16.2",
27
         "qs": "^6.14.0"
28
         "qs": "^6.14.0"
28
       },
29
       },
@@ -1521,6 +1522,15 @@
1521
         "node": "^18 || ^20 || >= 21"
1522
         "node": "^18 || ^20 || >= 21"
1522
       }
1523
       }
1523
     },
1524
     },
1525
+    "node_modules/node-cron": {
1526
+      "version": "4.2.1",
1527
+      "resolved": "https://registry.npmjs.org/node-cron/-/node-cron-4.2.1.tgz",
1528
+      "integrity": "sha512-lgimEHPE/QDgFlywTd8yTR61ptugX3Qer29efeyWw2rv259HtGBNn1vZVmp8lB9uo9wC0t/AT4iGqXxia+CJFg==",
1529
+      "license": "ISC",
1530
+      "engines": {
1531
+        "node": ">=6.0.0"
1532
+      }
1533
+    },
1524
     "node_modules/node-gyp-build": {
1534
     "node_modules/node-gyp-build": {
1525
       "version": "4.8.4",
1535
       "version": "4.8.4",
1526
       "resolved": "https://registry.npmjs.org/node-gyp-build/-/node-gyp-build-4.8.4.tgz",
1536
       "resolved": "https://registry.npmjs.org/node-gyp-build/-/node-gyp-build-4.8.4.tgz",

+ 1 - 0
package.json

@@ -27,6 +27,7 @@
27
     "jsonwebtoken": "^9.0.2",
27
     "jsonwebtoken": "^9.0.2",
28
     "keycloak-connect": "^26.1.1",
28
     "keycloak-connect": "^26.1.1",
29
     "multer": "^2.0.1",
29
     "multer": "^2.0.1",
30
+    "node-cron": "^4.2.1",
30
     "pg": "^8.16.2",
31
     "pg": "^8.16.2",
31
     "qs": "^6.14.0"
32
     "qs": "^6.14.0"
32
   },
33
   },

+ 31 - 0
prisma/migrations/20250714060228_/migration.sql

@@ -0,0 +1,31 @@
1
+/*
2
+  Warnings:
3
+
4
+  - You are about to drop the column `createdAt` on the `users` table. All the data in the column will be lost.
5
+  - You are about to drop the column `deletedAt` on the `users` table. All the data in the column will be lost.
6
+  - You are about to drop the column `email` on the `users` table. All the data in the column will be lost.
7
+  - You are about to drop the column `firstname` on the `users` table. All the data in the column will be lost.
8
+  - You are about to drop the column `lastname` on the `users` table. All the data in the column will be lost.
9
+  - You are about to drop the column `password` on the `users` table. All the data in the column will be lost.
10
+  - You are about to drop the column `role` on the `users` table. All the data in the column will be lost.
11
+  - You are about to drop the column `updatedAt` on the `users` table. All the data in the column will be lost.
12
+  - You are about to drop the column `username` on the `users` table. All the data in the column will be lost.
13
+
14
+*/
15
+-- DropForeignKey
16
+ALTER TABLE "status_histories" DROP CONSTRAINT "status_histories_user_id_fkey";
17
+
18
+-- DropForeignKey
19
+ALTER TABLE "vendors" DROP CONSTRAINT "vendors_created_by_fkey";
20
+
21
+-- AlterTable
22
+ALTER TABLE "users" DROP COLUMN "createdAt",
23
+DROP COLUMN "deletedAt",
24
+DROP COLUMN "email",
25
+DROP COLUMN "firstname",
26
+DROP COLUMN "lastname",
27
+DROP COLUMN "password",
28
+DROP COLUMN "role",
29
+DROP COLUMN "updatedAt",
30
+DROP COLUMN "username",
31
+ADD COLUMN     "fullname" TEXT NOT NULL DEFAULT 'unknown';

+ 38 - 0
prisma/migrations/20250714060514_add_keycloak_user/migration.sql

@@ -0,0 +1,38 @@
1
+/*
2
+  Warnings:
3
+
4
+  - You are about to drop the column `fullname` on the `users` table. All the data in the column will be lost.
5
+  - Added the required column `email` to the `users` table without a default value. This is not possible if the table is not empty.
6
+  - Added the required column `firstname` to the `users` table without a default value. This is not possible if the table is not empty.
7
+  - Added the required column `lastname` to the `users` table without a default value. This is not possible if the table is not empty.
8
+  - Added the required column `password` to the `users` table without a default value. This is not possible if the table is not empty.
9
+  - Added the required column `role` to the `users` table without a default value. This is not possible if the table is not empty.
10
+  - Added the required column `updatedAt` to the `users` table without a default value. This is not possible if the table is not empty.
11
+  - Added the required column `username` to the `users` table without a default value. This is not possible if the table is not empty.
12
+
13
+*/
14
+-- DropForeignKey
15
+ALTER TABLE "hospitals" DROP CONSTRAINT "hospitals_created_by_fkey";
16
+
17
+-- AlterTable
18
+ALTER TABLE "users" DROP COLUMN "fullname",
19
+ADD COLUMN     "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
20
+ADD COLUMN     "deletedAt" TIMESTAMP(3),
21
+ADD COLUMN     "email" TEXT NOT NULL,
22
+ADD COLUMN     "firstname" TEXT NOT NULL,
23
+ADD COLUMN     "lastname" TEXT NOT NULL,
24
+ADD COLUMN     "password" TEXT NOT NULL,
25
+ADD COLUMN     "role" TEXT NOT NULL,
26
+ADD COLUMN     "updatedAt" TIMESTAMP(3) NOT NULL,
27
+ADD COLUMN     "username" TEXT NOT NULL;
28
+
29
+-- CreateTable
30
+CREATE TABLE "keycloak_users" (
31
+    "id" TEXT NOT NULL,
32
+    "fullname" TEXT NOT NULL,
33
+
34
+    CONSTRAINT "keycloak_users_pkey" PRIMARY KEY ("id")
35
+);
36
+
37
+-- AddForeignKey
38
+ALTER TABLE "hospitals" ADD CONSTRAINT "hospitals_created_by_fkey" FOREIGN KEY ("created_by") REFERENCES "keycloak_users"("id") ON DELETE RESTRICT ON UPDATE CASCADE;

+ 2 - 0
prisma/migrations/20250714061327_update_again_user_hospital/migration.sql

@@ -0,0 +1,2 @@
1
+-- DropForeignKey
2
+ALTER TABLE "hospitals" DROP CONSTRAINT "hospitals_created_by_fkey";

+ 44 - 0
prisma/migrations/20250714081536_add_table_vendor_experience/migration.sql

@@ -0,0 +1,44 @@
1
+/*
2
+  Warnings:
3
+
4
+  - You are about to drop the column `simrs_type` on the `hospitals` table. All the data in the column will be lost.
5
+  - You are about to drop the `vendor_histories` table. If the table is not empty, all the data it contains will be lost.
6
+
7
+*/
8
+-- DropForeignKey
9
+ALTER TABLE "vendor_histories" DROP CONSTRAINT "vendor_histories_hospital_id_fkey";
10
+
11
+-- DropForeignKey
12
+ALTER TABLE "vendor_histories" DROP CONSTRAINT "vendor_histories_vendor_id_fkey";
13
+
14
+-- AlterTable
15
+ALTER TABLE "hospitals" DROP COLUMN "simrs_type";
16
+
17
+-- DropTable
18
+DROP TABLE "vendor_histories";
19
+
20
+-- CreateTable
21
+CREATE TABLE "vendor_experiences" (
22
+    "id" TEXT NOT NULL,
23
+    "hospital_id" TEXT NOT NULL,
24
+    "vendor_id" TEXT,
25
+    "status" TEXT,
26
+    "contract_start_date" TIMESTAMP(3),
27
+    "contract_expired_date" TIMESTAMP(3),
28
+    "contract_value_min" BIGINT,
29
+    "contract_value_max" BIGINT,
30
+    "positive_notes" TEXT,
31
+    "negative_notes" TEXT,
32
+    "simrs_type" TEXT NOT NULL,
33
+    "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
34
+    "updatedAt" TIMESTAMP(3) NOT NULL,
35
+    "deletedAt" TIMESTAMP(3),
36
+
37
+    CONSTRAINT "vendor_experiences_pkey" PRIMARY KEY ("id")
38
+);
39
+
40
+-- AddForeignKey
41
+ALTER TABLE "vendor_experiences" ADD CONSTRAINT "vendor_experiences_hospital_id_fkey" FOREIGN KEY ("hospital_id") REFERENCES "hospitals"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
42
+
43
+-- AddForeignKey
44
+ALTER TABLE "vendor_experiences" ADD CONSTRAINT "vendor_experiences_vendor_id_fkey" FOREIGN KEY ("vendor_id") REFERENCES "vendors"("id") ON DELETE SET NULL ON UPDATE CASCADE;

+ 55 - 27
prisma/schema.prisma

@@ -44,20 +44,27 @@ model ActivityLog {
44
 }
44
 }
45
 
45
 
46
 model User {
46
 model User {
47
-  id         String     @id @default(uuid())
48
-  username   String
49
-  email      String
50
-  password   String
51
-  firstname  String
52
-  lastname   String
53
-  role       String
54
-  createdAt  DateTime   @default(now())
55
-  updatedAt  DateTime   @updatedAt
56
-  deletedAt  DateTime?
47
+  id        String    @id @default(uuid())
48
+  username  String
49
+  email     String
50
+  password  String
51
+  firstname String
52
+  lastname  String
53
+  role      String
54
+  createdAt DateTime  @default(now())
55
+  updatedAt DateTime  @updatedAt
56
+  deletedAt DateTime?
57
 
57
 
58
   @@map("users")
58
   @@map("users")
59
 }
59
 }
60
 
60
 
61
+model UserKeycloak {
62
+  id       String @id @default(uuid())
63
+  fullname String
64
+
65
+  @@map("keycloak_users")
66
+}
67
+
61
 model Province {
68
 model Province {
62
   id         String     @id @default(uuid())
69
   id         String     @id @default(uuid())
63
   name       String     @unique
70
   name       String     @unique
@@ -94,7 +101,7 @@ model Hospital {
94
   province_id          String
101
   province_id          String
95
   city_id              String
102
   city_id              String
96
   address              String?
103
   address              String?
97
-  simrs_type           String?
104
+  // simrs_type           String?
98
   contact              String?
105
   contact              String?
99
   image                String?
106
   image                String?
100
   progress_status      ProgressStatus
107
   progress_status      ProgressStatus
@@ -108,7 +115,7 @@ model Hospital {
108
   deletedAt            DateTime?
115
   deletedAt            DateTime?
109
   province             Province            @relation(fields: [province_id], references: [id])
116
   province             Province            @relation(fields: [province_id], references: [id])
110
   city                 City                @relation(fields: [city_id], references: [id])
117
   city                 City                @relation(fields: [city_id], references: [id])
111
-  vendor_histories     VendorHistory[]
118
+  vendor_experiences   VendorExperience[]
112
   executives_histories ExecutivesHistory[]
119
   executives_histories ExecutivesHistory[]
113
   status_histories     StatusHistory[]
120
   status_histories     StatusHistory[]
114
 
121
 
@@ -116,17 +123,17 @@ model Hospital {
116
 }
123
 }
117
 
124
 
118
 model Vendor {
125
 model Vendor {
119
-  id               String          @id @default(uuid())
120
-  name             String
121
-  name_pt          String?
122
-  strengths        String?
123
-  weaknesses       String?
124
-  website          String?
125
-  created_by       String
126
-  createdAt        DateTime        @default(now())
127
-  updatedAt        DateTime        @updatedAt
128
-  deletedAt        DateTime?
129
-  vendor_histories VendorHistory[]
126
+  id                 String             @id @default(uuid())
127
+  name               String
128
+  name_pt            String?
129
+  strengths          String?
130
+  weaknesses         String?
131
+  website            String?
132
+  created_by         String
133
+  createdAt          DateTime           @default(now())
134
+  updatedAt          DateTime           @updatedAt
135
+  deletedAt          DateTime?
136
+  vendor_experiences VendorExperience[]
130
 
137
 
131
   @@map("vendors")
138
   @@map("vendors")
132
 }
139
 }
@@ -143,21 +150,42 @@ model UserArea {
143
   @@map("user_areas")
150
   @@map("user_areas")
144
 }
151
 }
145
 
152
 
146
-model VendorHistory {
153
+// model VendorHistory {
154
+//   id                    String    @id @default(uuid())
155
+//   hospital_id           String
156
+//   vendor_id             String?
157
+//   vendor_impression     String?   @db.Text
158
+//   status                String?
159
+//   contract_date         DateTime?
160
+//   contract_expired_date DateTime?
161
+//   hospital              Hospital  @relation(fields: [hospital_id], references: [id])
162
+//   vendor                Vendor?   @relation(fields: [vendor_id], references: [id])
163
+//   createdAt             DateTime  @default(now())
164
+//   updatedAt             DateTime  @updatedAt
165
+//   deletedAt             DateTime?
166
+
167
+//   @@map("vendor_histories")
168
+// }
169
+
170
+model VendorExperience {
147
   id                    String    @id @default(uuid())
171
   id                    String    @id @default(uuid())
148
   hospital_id           String
172
   hospital_id           String
149
   vendor_id             String?
173
   vendor_id             String?
150
-  vendor_impression     String?   @db.Text
151
   status                String?
174
   status                String?
152
-  contract_date         DateTime?
175
+  contract_start_date   DateTime?
153
   contract_expired_date DateTime?
176
   contract_expired_date DateTime?
177
+  contract_value_min    BigInt?
178
+  contract_value_max    BigInt?
179
+  positive_notes        String?   @db.Text
180
+  negative_notes        String?   @db.Text
181
+  simrs_type            String
154
   hospital              Hospital  @relation(fields: [hospital_id], references: [id])
182
   hospital              Hospital  @relation(fields: [hospital_id], references: [id])
155
   vendor                Vendor?   @relation(fields: [vendor_id], references: [id])
183
   vendor                Vendor?   @relation(fields: [vendor_id], references: [id])
156
   createdAt             DateTime  @default(now())
184
   createdAt             DateTime  @default(now())
157
   updatedAt             DateTime  @updatedAt
185
   updatedAt             DateTime  @updatedAt
158
   deletedAt             DateTime?
186
   deletedAt             DateTime?
159
 
187
 
160
-  @@map("vendor_histories")
188
+  @@map("vendor_experiences")
161
 }
189
 }
162
 
190
 
163
 model ExecutivesHistory {
191
 model ExecutivesHistory {

+ 6 - 6
prisma/seeders/HospitalSeeder.js

@@ -34,7 +34,7 @@ async function seedHospitals() {
34
                 type: 'Tipe B',
34
                 type: 'Tipe B',
35
                 ownership: 'Swasta',
35
                 ownership: 'Swasta',
36
                 address: 'Jl. Kesehatan No. 1',
36
                 address: 'Jl. Kesehatan No. 1',
37
-                simrs_type: 'MediSoft',
37
+                // simrs_type: 'MediSoft',
38
                 contact: '021-123456',
38
                 contact: '021-123456',
39
                 image: null,
39
                 image: null,
40
                 progress_status: 'cari_data',
40
                 progress_status: 'cari_data',
@@ -49,7 +49,7 @@ async function seedHospitals() {
49
                 type: 'Tipe C',
49
                 type: 'Tipe C',
50
                 ownership: 'Pemerintah',
50
                 ownership: 'Pemerintah',
51
                 address: 'Jl. Harapan No. 2',
51
                 address: 'Jl. Harapan No. 2',
52
-                simrs_type: 'Hospicare',
52
+                // simrs_type: 'Hospicare',
53
                 contact: '021-654321',
53
                 contact: '021-654321',
54
                 image: null,
54
                 image: null,
55
                 progress_status: 'cari_data',
55
                 progress_status: 'cari_data',
@@ -64,7 +64,7 @@ async function seedHospitals() {
64
                 type: 'Tipe D',
64
                 type: 'Tipe D',
65
                 ownership: 'Swasta',
65
                 ownership: 'Swasta',
66
                 address: 'Jl. Mitra No. 3',
66
                 address: 'Jl. Mitra No. 3',
67
-                simrs_type: 'SimRS Platinum',
67
+                // simrs_type: 'SimRS Platinum',
68
                 contact: '021-999999',
68
                 contact: '021-999999',
69
                 image: null,
69
                 image: null,
70
                 progress_status: 'cari_data',
70
                 progress_status: 'cari_data',
@@ -79,7 +79,7 @@ async function seedHospitals() {
79
                 type: 'Tipe B',
79
                 type: 'Tipe B',
80
                 ownership: 'Swasta',
80
                 ownership: 'Swasta',
81
                 address: 'Jl. Bersama No. 4',
81
                 address: 'Jl. Bersama No. 4',
82
-                simrs_type: 'MediSoft',
82
+                // simrs_type: 'MediSoft',
83
                 contact: '0274-111111',
83
                 contact: '0274-111111',
84
                 image: null,
84
                 image: null,
85
                 progress_status: 'cari_data',
85
                 progress_status: 'cari_data',
@@ -94,7 +94,7 @@ async function seedHospitals() {
94
                 type: 'Tipe C',
94
                 type: 'Tipe C',
95
                 ownership: 'Yayasan',
95
                 ownership: 'Yayasan',
96
                 address: 'Jl. Kasih No. 5',
96
                 address: 'Jl. Kasih No. 5',
97
-                simrs_type: 'Hospicare',
97
+                // simrs_type: 'Hospicare',
98
                 contact: '0274-222222',
98
                 contact: '0274-222222',
99
                 image: null,
99
                 image: null,
100
                 progress_status: 'cari_data',
100
                 progress_status: 'cari_data',
@@ -109,7 +109,7 @@ async function seedHospitals() {
109
                 type: 'Tipe D',
109
                 type: 'Tipe D',
110
                 ownership: 'Swasta',
110
                 ownership: 'Swasta',
111
                 address: 'Jl. Bhakti No. 6',
111
                 address: 'Jl. Bhakti No. 6',
112
-                simrs_type: 'SimRS Platinum',
112
+                // simrs_type: 'SimRS Platinum',
113
                 contact: '0274-333333',
113
                 contact: '0274-333333',
114
                 image: null,
114
                 image: null,
115
                 progress_status: 'cari_data',
115
                 progress_status: 'cari_data',

+ 4 - 4
src/controllers/admin/VendorHistoryController.js

@@ -1,9 +1,9 @@
1
-const { VendorHistoriCollection } = require('../../resources/admin/vendor_history/VendorHistoriCollection.js');
2
-const { VendorHistoriResource } = require('../../resources/admin/vendor_history/VendorHistoriResource.js');
3
-const vendorHistoryService = require('../../services/admin/VendorHistoryService.js');
1
+const { VendorHistoriCollection } = require('../../resources/admin/vendor_history/VendorExperienceCollection.js');
2
+const { VendorHistoriResource } = require('../../resources/admin/vendor_history/VendorExperienceResource.js');
3
+const vendorHistoryService = require('../../services/admin/VendorExperienceService.js');
4
 const { PaginationParam } = require('../../utils/PaginationParams.js');
4
 const { PaginationParam } = require('../../utils/PaginationParams.js');
5
 const { errorResponse, messageSuccessResponse } = require('../../utils/Response.js');
5
 const { errorResponse, messageSuccessResponse } = require('../../utils/Response.js');
6
-const { validateStoreVendorHistoryRequest, validateUpdateVendorHistoryRequest } = require('../../validators/admin/vendor_history/VendorHistoriValidators.js');
6
+const { validateStoreVendorHistoryRequest, validateUpdateVendorHistoryRequest } = require('../../validators/admin/vendor_experience/VendorExperienceValidators.js');
7
 
7
 
8
 exports.getAllVendorHistory = async (req, res) => {
8
 exports.getAllVendorHistory = async (req, res) => {
9
     try {
9
     try {

+ 4 - 4
src/repository/admin/HospitalRepository.js

@@ -16,7 +16,7 @@ const HospitalRepository = {
16
                 province: { select: { id: true, name: true } },
16
                 province: { select: { id: true, name: true } },
17
                 city: { select: { id: true, name: true } },
17
                 city: { select: { id: true, name: true } },
18
                 address: true,
18
                 address: true,
19
-                simrs_type: true,
19
+                // simrs_type: true,
20
                 contact: true,
20
                 contact: true,
21
                 image: true,
21
                 image: true,
22
                 progress_status: true,
22
                 progress_status: true,
@@ -27,7 +27,7 @@ const HospitalRepository = {
27
                 created_by: true,
27
                 created_by: true,
28
                 createdAt: true,
28
                 createdAt: true,
29
                 updatedAt: true,
29
                 updatedAt: true,
30
-                vendor_histories: {
30
+                vendor_experiences: {
31
                     where: {
31
                     where: {
32
                         status: "active",
32
                         status: "active",
33
                         deletedAt: null
33
                         deletedAt: null
@@ -76,7 +76,7 @@ const HospitalRepository = {
76
                 province: { select: { id: true, name: true } },
76
                 province: { select: { id: true, name: true } },
77
                 city: { select: { id: true, name: true } },
77
                 city: { select: { id: true, name: true } },
78
                 address: true,
78
                 address: true,
79
-                simrs_type: true,
79
+                // simrs_type: true,
80
                 contact: true,
80
                 contact: true,
81
                 image: true,
81
                 image: true,
82
                 progress_status: true,
82
                 progress_status: true,
@@ -87,7 +87,7 @@ const HospitalRepository = {
87
                 created_by: true,
87
                 created_by: true,
88
                 createdAt: true,
88
                 createdAt: true,
89
                 updatedAt: true,
89
                 updatedAt: true,
90
-                vendor_histories: {
90
+                vendor_experiences: {
91
                     where: {
91
                     where: {
92
                         status: "active",
92
                         status: "active",
93
                         deletedAt: null
93
                         deletedAt: null

+ 19 - 11
src/repository/admin/VendorHistoryRepository.js

@@ -1,8 +1,8 @@
1
 const prisma = require('../../prisma/PrismaClient.js');
1
 const prisma = require('../../prisma/PrismaClient.js');
2
 
2
 
3
-const VendorHistoryRepository = {
3
+const VendorExperienceRepository = {
4
     findAll: async ({ skip, take, where, orderBy }) => {
4
     findAll: async ({ skip, take, where, orderBy }) => {
5
-        return prisma.vendorHistory.findMany({
5
+        return prisma.vendorExperience.findMany({
6
             where,
6
             where,
7
             skip,
7
             skip,
8
             take,
8
             take,
@@ -48,10 +48,14 @@ const VendorHistoryRepository = {
48
                         created_by: true,
48
                         created_by: true,
49
                     }
49
                     }
50
                 },
50
                 },
51
-                vendor_impression: true,
52
                 status: true,
51
                 status: true,
53
-                contract_date: true,
52
+                simrs_type: true,
53
+                contract_value_min: true,
54
+                contract_value_max: true,
55
+                contract_start_date: true,
54
                 contract_expired_date: true,
56
                 contract_expired_date: true,
57
+                positive_notes: true,
58
+                negative_notes: true,
55
                 createdAt: true,
59
                 createdAt: true,
56
                 updatedAt: true,
60
                 updatedAt: true,
57
             },
61
             },
@@ -59,11 +63,11 @@ const VendorHistoryRepository = {
59
     },
63
     },
60
 
64
 
61
     countAll: async (where) => {
65
     countAll: async (where) => {
62
-        return prisma.vendorHistory.count({ where });
66
+        return prisma.vendorExperience.count({ where });
63
     },
67
     },
64
 
68
 
65
     findById: async (id) => {
69
     findById: async (id) => {
66
-        return prisma.vendorHistory.findFirst({
70
+        return prisma.vendorExperience.findFirst({
67
             where: {
71
             where: {
68
                 id,
72
                 id,
69
                 deletedAt: null
73
                 deletedAt: null
@@ -109,10 +113,14 @@ const VendorHistoryRepository = {
109
                         created_by: true,
113
                         created_by: true,
110
                     }
114
                     }
111
                 },
115
                 },
112
-                vendor_impression: true,
113
                 status: true,
116
                 status: true,
114
-                contract_date: true,
117
+                simrs_type: true,
118
+                contract_value_min: true,
119
+                contract_value_max: true,
120
+                contract_start_date: true,
115
                 contract_expired_date: true,
121
                 contract_expired_date: true,
122
+                positive_notes: true,
123
+                negative_notes: true,
116
                 createdAt: true,
124
                 createdAt: true,
117
                 updatedAt: true,
125
                 updatedAt: true,
118
             },
126
             },
@@ -120,15 +128,15 @@ const VendorHistoryRepository = {
120
     },
128
     },
121
 
129
 
122
     create: async (data) => {
130
     create: async (data) => {
123
-        return prisma.vendorHistory.create({ data });
131
+        return prisma.vendorExperience.create({ data });
124
     },
132
     },
125
 
133
 
126
     update: async (id, data) => {
134
     update: async (id, data) => {
127
-        return prisma.vendorHistory.update({
135
+        return prisma.vendorExperience.update({
128
             where: { id },
136
             where: { id },
129
             data
137
             data
130
         });
138
         });
131
     },
139
     },
132
 };
140
 };
133
 
141
 
134
-module.exports = VendorHistoryRepository;
142
+module.exports = VendorExperienceRepository;

+ 5 - 5
src/repository/admin/VendorRepository.js

@@ -18,7 +18,7 @@ const VendorRepository = {
18
                 created_by: true,
18
                 created_by: true,
19
                 _count: {
19
                 _count: {
20
                     select: {
20
                     select: {
21
-                        vendor_histories: {
21
+                        vendor_experiences: {
22
                             where: {
22
                             where: {
23
                                 deletedAt: null
23
                                 deletedAt: null
24
                             }
24
                             }
@@ -34,7 +34,7 @@ const VendorRepository = {
34
             const { _count, ...rest } = v;
34
             const { _count, ...rest } = v;
35
             return {
35
             return {
36
                 ...rest,
36
                 ...rest,
37
-                count_hospitals: _count.vendor_histories,
37
+                count_hospitals: _count.vendor_experiences,
38
             };
38
             };
39
         });
39
         });
40
 
40
 
@@ -61,7 +61,7 @@ const VendorRepository = {
61
                 created_by: true,
61
                 created_by: true,
62
                 _count: {
62
                 _count: {
63
                     select: {
63
                     select: {
64
-                        vendor_histories: {
64
+                        vendor_experiences: {
65
                             where: {
65
                             where: {
66
                                 deletedAt: null
66
                                 deletedAt: null
67
                             }
67
                             }
@@ -76,7 +76,7 @@ const VendorRepository = {
76
         const { _count, ...rest } = vendor;
76
         const { _count, ...rest } = vendor;
77
         return {
77
         return {
78
             ...rest,
78
             ...rest,
79
-            count_hospitals: _count.vendor_histories
79
+            count_hospitals: _count.vendor_experiences
80
         };
80
         };
81
     },
81
     },
82
 
82
 
@@ -101,7 +101,7 @@ const VendorRepository = {
101
         });
101
         });
102
 
102
 
103
         // Unlink vendor_id di vendor_histories
103
         // Unlink vendor_id di vendor_histories
104
-        await prisma.vendorHistory.updateMany({
104
+        await prisma.vendorExperience.updateMany({
105
             where: {
105
             where: {
106
                 vendor_id: id,
106
                 vendor_id: id,
107
                 deletedAt: null
107
                 deletedAt: null

+ 4 - 2
src/resources/admin/vendor_history/VendorHistoriCollection.js

@@ -1,9 +1,11 @@
1
-const { ListResponse } = require("../../../utils/ListResponse");
1
+const { ListResponse } = require("../../../utils/ListResponse.js");
2
 const { formatISOWithoutTimezone, formatDateOnly } = require("../../../utils/FormatDate.js");
2
 const { formatISOWithoutTimezone, formatDateOnly } = require("../../../utils/FormatDate.js");
3
 
3
 
4
 const formatItem = (item) => ({
4
 const formatItem = (item) => ({
5
     ...item,
5
     ...item,
6
-    contract_date: formatDateOnly(item.contract_date),
6
+    contract_value_min: item.contract_value_min !== null ? Number(item.contract_value_min) : null,
7
+    contract_value_max: item.contract_value_max !== null ? Number(item.contract_value_max) : null,
8
+    contract_start_date: formatDateOnly(item.contract_start_date),
7
     contract_expired_date: formatDateOnly(item.contract_expired_date),
9
     contract_expired_date: formatDateOnly(item.contract_expired_date),
8
     createdAt: formatISOWithoutTimezone(item.createdAt),
10
     createdAt: formatISOWithoutTimezone(item.createdAt),
9
     updatedAt: formatISOWithoutTimezone(item.updatedAt),
11
     updatedAt: formatISOWithoutTimezone(item.updatedAt),

+ 3 - 1
src/resources/admin/vendor_history/VendorHistoriResource.js

@@ -2,7 +2,9 @@ const { formatISOWithoutTimezone, formatDateOnly } = require("../../../utils/For
2
 
2
 
3
 const formatItem = (item) => ({
3
 const formatItem = (item) => ({
4
     ...item,
4
     ...item,
5
-    contract_date: formatDateOnly(item.contract_date),
5
+    contract_value_min: item.contract_value_min !== null ? Number(item.contract_value_min) : null,
6
+    contract_value_max: item.contract_value_max !== null ? Number(item.contract_value_max) : null,
7
+    contract_start_date: formatDateOnly(item.contract_start_date),
6
     contract_expired_date: formatDateOnly(item.contract_expired_date),
8
     contract_expired_date: formatDateOnly(item.contract_expired_date),
7
     createdAt: formatISOWithoutTimezone(item.createdAt),
9
     createdAt: formatISOWithoutTimezone(item.createdAt),
8
     updatedAt: formatISOWithoutTimezone(item.updatedAt),
10
     updatedAt: formatISOWithoutTimezone(item.updatedAt),

+ 11 - 11
src/routes/admin/HospitalRoute.js

@@ -1,7 +1,7 @@
1
 const express = require('express')
1
 const express = require('express')
2
 const router = express.Router()
2
 const router = express.Router()
3
 const hospitalController = require('../../controllers/admin/HospitalController.js')
3
 const hospitalController = require('../../controllers/admin/HospitalController.js')
4
-const vendorHistoryController = require('../../controllers/admin/VendorHistoryController.js')
4
+const vendorExperienceController = require('../../controllers/admin/VendorExperienceController.js')
5
 const executivesHistoryController = require('../../controllers/admin/ExecutivesHistoryController.js')
5
 const executivesHistoryController = require('../../controllers/admin/ExecutivesHistoryController.js')
6
 const statusHistoriesController = require('../../controllers/admin/StatusHistoryController.js')
6
 const statusHistoriesController = require('../../controllers/admin/StatusHistoryController.js')
7
 // const verifyJWT = require('../../middleware/VerifyJWT.js');
7
 // const verifyJWT = require('../../middleware/VerifyJWT.js');
@@ -19,11 +19,11 @@ const checkRoles = require('../../middleware/CheckRoles.js');
19
 // router.delete('/:id', verifyJWT, checkRole(['admin']), hospitalController.deleteHospital);
19
 // router.delete('/:id', verifyJWT, checkRole(['admin']), hospitalController.deleteHospital);
20
 
20
 
21
 // // Vendor History
21
 // // Vendor History
22
-// router.get('/:id/vendor-history', verifyJWT, checkRole(['admin']), vendorHistoryController.getAllVendorHistory);
23
-// router.post('/:id/vendor-history', verifyJWT, checkRole(['admin']), vendorHistoryController.storeVendorHistory);
24
-// router.get('/:id/vendor-history/:id_vendor_history', verifyJWT, checkRole(['admin']), vendorHistoryController.showVendorHistory);
25
-// router.patch('/:id/vendor-history/:id_vendor_history', verifyJWT, checkRole(['admin']), vendorHistoryController.updateVendorHistory);
26
-// router.delete('/:id/vendor-history/:id_vendor_history', verifyJWT, checkRole(['admin']), vendorHistoryController.deleteVendorHistory);
22
+// router.get('/:id/vendor-history', verifyJWT, checkRole(['admin']), vendorExperienceController.getAllVendorHistory);
23
+// router.post('/:id/vendor-history', verifyJWT, checkRole(['admin']), vendorExperienceController.storeVendorHistory);
24
+// router.get('/:id/vendor-history/:id_vendor_history', verifyJWT, checkRole(['admin']), vendorExperienceController.showVendorHistory);
25
+// router.patch('/:id/vendor-history/:id_vendor_history', verifyJWT, checkRole(['admin']), vendorExperienceController.updateVendorHistory);
26
+// router.delete('/:id/vendor-history/:id_vendor_history', verifyJWT, checkRole(['admin']), vendorExperienceController.deleteVendorHistory);
27
 
27
 
28
 // // Executives History
28
 // // Executives History
29
 // router.get('/:id/executives-history', verifyJWT, checkRole(['admin']), executivesHistoryController.getAllExecutivesHistory);
29
 // router.get('/:id/executives-history', verifyJWT, checkRole(['admin']), executivesHistoryController.getAllExecutivesHistory);
@@ -39,11 +39,11 @@ router.patch('/:id', [keycloak.protect(), extractToken, checkRoles(["admin", "ad
39
 router.delete('/:id', [keycloak.protect(), extractToken, checkRoles(["admin"])], hospitalController.deleteHospital);
39
 router.delete('/:id', [keycloak.protect(), extractToken, checkRoles(["admin"])], hospitalController.deleteHospital);
40
 
40
 
41
 // Vendor History
41
 // Vendor History
42
-router.get('/:id/vendor-history', [keycloak.protect(), extractToken, checkRoles(["admin"])], vendorHistoryController.getAllVendorHistory);
43
-router.post('/:id/vendor-history', [keycloak.protect(), extractToken, checkRoles(["admin"])], vendorHistoryController.storeVendorHistory);
44
-router.get('/:id/vendor-history/:id_vendor_history', [keycloak.protect(), extractToken, checkRoles(["admin"])], vendorHistoryController.showVendorHistory);
45
-router.patch('/:id/vendor-history/:id_vendor_history', [keycloak.protect(), extractToken, checkRoles(["admin"])], vendorHistoryController.updateVendorHistory);
46
-router.delete('/:id/vendor-history/:id_vendor_history', [keycloak.protect(), extractToken, checkRoles(["admin"])], vendorHistoryController.deleteVendorHistory);
42
+router.get('/:id/vendor-experience', [keycloak.protect(), extractToken, checkRoles(["admin"])], vendorExperienceController.getAllVendorHistory);
43
+router.post('/:id/vendor-experience', [keycloak.protect(), extractToken, checkRoles(["admin"])], vendorExperienceController.storeVendorHistory);
44
+router.get('/:id/vendor-experience/:id_vendor_experience', [keycloak.protect(), extractToken, checkRoles(["admin"])], vendorExperienceController.showVendorHistory);
45
+router.patch('/:id/vendor-experience/:id_vendor_experience', [keycloak.protect(), extractToken, checkRoles(["admin"])], vendorExperienceController.updateVendorHistory);
46
+router.delete('/:id/vendor-experience/:id_vendor_experience', [keycloak.protect(), extractToken, checkRoles(["admin"])], vendorExperienceController.deleteVendorHistory);
47
 
47
 
48
 // Executives History
48
 // Executives History
49
 router.get('/:id/executives-history', [keycloak.protect(), extractToken, checkRoles(["admin"])], executivesHistoryController.getAllExecutivesHistory);
49
 router.get('/:id/executives-history', [keycloak.protect(), extractToken, checkRoles(["admin"])], executivesHistoryController.getAllExecutivesHistory);

+ 5 - 5
src/routes/sales/HospitalRoute.js

@@ -37,11 +37,11 @@ router.patch('/:id', [keycloak.protect(), extractToken, checkRoles(['sales'])],
37
 router.get('/:id', [keycloak.protect(), extractToken, checkRoles(['sales'])], hospitalController.showHospital);
37
 router.get('/:id', [keycloak.protect(), extractToken, checkRoles(['sales'])], hospitalController.showHospital);
38
 
38
 
39
 // Vendor History
39
 // Vendor History
40
-router.get('/:id/vendor-history', [keycloak.protect(), extractToken, checkRoles(['sales'])], vendorHistoryController.getAllVendorHistory);
41
-router.post('/:id/vendor-history', [keycloak.protect(), extractToken, checkRoles(['sales'])], vendorHistoryController.storeVendorHistory);
42
-router.get('/:id/vendor-history/:id_vendor_history', [keycloak.protect(), extractToken, checkRoles(['sales'])], vendorHistoryController.showVendorHistory);
43
-router.patch('/:id/vendor-history/:id_vendor_history', [keycloak.protect(), extractToken, checkRoles(['sales'])], vendorHistoryController.updateVendorHistory);
44
-router.delete('/:id/vendor-history/:id_vendor_history', [keycloak.protect(), extractToken, checkRoles(['sales'])], vendorHistoryController.deleteVendorHistory);
40
+router.get('/:id/vendor-experience', [keycloak.protect(), extractToken, checkRoles(['sales'])], vendorHistoryController.getAllVendorHistory);
41
+router.post('/:id/vendor-experience', [keycloak.protect(), extractToken, checkRoles(['sales'])], vendorHistoryController.storeVendorHistory);
42
+router.get('/:id/vendor-experience/:id_vendor_experience', [keycloak.protect(), extractToken, checkRoles(['sales'])], vendorHistoryController.showVendorHistory);
43
+router.patch('/:id/vendor-experience/:id_vendor_experience', [keycloak.protect(), extractToken, checkRoles(['sales'])], vendorHistoryController.updateVendorHistory);
44
+router.delete('/:id/vendor-experience/:id_vendor_experience', [keycloak.protect(), extractToken, checkRoles(['sales'])], vendorHistoryController.deleteVendorHistory);
45
 
45
 
46
 // Executives History
46
 // Executives History
47
 router.get('/:id/executives-history', [keycloak.protect(), extractToken, checkRoles(['sales'])], executivesHistoryController.getAllExecutivesHistory);
47
 router.get('/:id/executives-history', [keycloak.protect(), extractToken, checkRoles(['sales'])], executivesHistoryController.getAllExecutivesHistory);

+ 264 - 0
src/services/admin/VendorExperienceService.js

@@ -0,0 +1,264 @@
1
+const HttpException = require('../../utils/HttpException.js');
2
+const prisma = require('../../prisma/PrismaClient.js');
3
+const { SearchFilter } = require('../../utils/SearchFilter.js');
4
+const timeLocal = require('../../utils/TimeLocal.js');
5
+const { createLog, updateLog, deleteLog } = require('../../utils/LogActivity.js');
6
+const { formatDateOnly, formatISOWithoutTimezone } = require('../../utils/FormatDate.js');
7
+const VendorExperienceRepository = require('../../repository/admin/VendorExperienceRepository.js');
8
+
9
+exports.getAllVendorHistoryService = async ({ page, limit, search, sortBy, orderBy }, req) => {
10
+    const skip = (page - 1) * limit;
11
+
12
+    const hospitalId = req.params.id;
13
+    const hospital = await prisma.hospital.findFirst({
14
+        where: {
15
+            id: hospitalId
16
+        }
17
+    })
18
+    if (!hospital) {
19
+        throw new HttpException("Hospital not found", 404)
20
+    }
21
+
22
+    const where = {
23
+        // ...SearchFilter(search, ['name', 'name_pt']),
24
+        hospital_id: req.params.id,
25
+        deletedAt: null
26
+    };
27
+
28
+    const [vendor_histories, total] = await Promise.all([
29
+        VendorExperienceRepository.findAll({ skip, take: limit, where, orderBy: { [sortBy]: orderBy } }),
30
+        VendorExperienceRepository.countAll(where)
31
+    ]);
32
+
33
+    return { vendor_histories, total };
34
+};
35
+
36
+exports.showVendorHistoryService = async (req) => {
37
+    const id_hospital = req.params.id;
38
+    const id_vendor_experience = req.params.id_vendor_experience;
39
+
40
+    const hospital = await prisma.hospital.findFirst({
41
+        where: {
42
+            id: id_hospital
43
+        }
44
+    })
45
+    if (!hospital) {
46
+        throw new HttpException("Hospital not found", 404)
47
+    }
48
+
49
+    const vendorHistory = await VendorExperienceRepository.findById(id_vendor_experience);
50
+    if (!vendorHistory) {
51
+        throw new HttpException("Vendor history not found", 404);
52
+    }
53
+
54
+    return vendorHistory;
55
+};
56
+
57
+exports.storeVendorHistoryService = async (validateData, req) => {
58
+    const hospitalId = req.params.id;
59
+
60
+    const hospital = await prisma.hospital.findFirst({
61
+        where: {
62
+            id: hospitalId
63
+        }
64
+    });
65
+
66
+    if (!hospital) {
67
+        throw new HttpException("Hospital not found", 404)
68
+    }
69
+
70
+    if (validateData.vendor_id) {
71
+        const vendor = await prisma.vendor.findFirst({
72
+            where: {
73
+                id: validateData.vendor_id
74
+            }
75
+        });
76
+
77
+        if (!vendor) {
78
+            throw new HttpException("Vendor not found", 404)
79
+        }
80
+    }
81
+
82
+    const SimrsType = ["vendor", "in house", "gratis"];
83
+    if (validateData.simrs_type && !SimrsType.includes(validateData.simrs_type)) {
84
+        throw new HttpException("Simrs type must be vendor, in house, or gratis", 400);
85
+    }
86
+
87
+    if (validateData.contract_start_date && validateData.contract_expired_date) {
88
+        if (validateData.contract_start_date >= validateData.contract_expired_date) {
89
+            throw new HttpException("Contract expired date must be after contract date", 400)
90
+        }
91
+    }
92
+
93
+    if (validateData.contract_value_min && validateData.contract_value_max) {
94
+        if (validateData.contract_value_min >= validateData.contract_value_max) {
95
+            throw new HttpException("Contract value max must be after contract value min", 400)
96
+        }
97
+    }
98
+
99
+    // await prisma.hospital.update({
100
+    //     where: { id: hospitalId },
101
+    //     data: {
102
+    //         simrs_type: validateData.simrs_type
103
+    //     }
104
+    // });
105
+
106
+    if (validateData.simrs_type) {
107
+        const existingActiveVendors = await prisma.vendorExperience.findMany({
108
+            where: {
109
+                hospital_id: hospitalId,
110
+                status: "active",
111
+                deletedAt: null
112
+            }
113
+        });
114
+
115
+        if (existingActiveVendors.length > 0) {
116
+            await prisma.vendorExperience.updateMany({
117
+                where: {
118
+                    hospital_id: hospitalId,
119
+                    status: "active",
120
+                    deletedAt: null
121
+                },
122
+                data: {
123
+                    status: "inactive"
124
+                }
125
+            });
126
+        }
127
+
128
+        validateData.status = "active";
129
+    }
130
+
131
+    const payload = {
132
+        vendor_id: validateData.vendor_id,
133
+        simrs_type: validateData.simrs_type,
134
+        status: validateData.status,
135
+        contract_start_date: validateData.contract_start_date ? new Date(validateData.contract_start_date) : null,
136
+        contract_expired_date: validateData.contract_expired_date ? new Date(validateData.contract_expired_date) : null,
137
+        contract_value_min: validateData.contract_value_min ? Number(validateData.contract_value_min) : null,
138
+        contract_value_max: validateData.contract_value_max ? Number(validateData.contract_value_max) : null,
139
+        positive_notes: validateData.positive_notes,
140
+        negative_notes: validateData.negative_notes,
141
+        hospital_id: hospitalId
142
+    };
143
+
144
+    const data = await VendorExperienceRepository.create(payload);
145
+    await createLog(req, data);
146
+};
147
+
148
+exports.updateVendorHistoryService = async (validateData, req) => {
149
+    const id_hospital = req.params.id;
150
+    const id_vendor_experience = req.params.id_vendor_experience;
151
+
152
+    const hospital = await prisma.hospital.findFirst({
153
+        where: {
154
+            id: id_hospital
155
+        }
156
+    })
157
+    if (!hospital) {
158
+        throw new HttpException("Hospital not found", 404)
159
+    }
160
+
161
+    const vendorHistory = await VendorExperienceRepository.findById(id_vendor_experience);
162
+    if (!vendorHistory) {
163
+        throw new HttpException("Vendor history not found", 404);
164
+    }
165
+
166
+    if (validateData.vendor_id) {
167
+        const existVendor = await prisma.vendor.findFirst({
168
+            where: {
169
+                id: validateData.vendor_id
170
+            }
171
+        });
172
+        if (!existVendor) {
173
+            throw new HttpException("Vendor not found", 404)
174
+        }
175
+    }
176
+
177
+    const SimrsType = ["vendor", "in house", "gratis"];
178
+    if (validateData.simrs_type && !SimrsType.includes(validateData.simrs_type)) {
179
+        throw new HttpException("Simrs type must be vendor, in house, or gratis", 400);
180
+    }
181
+
182
+    if (
183
+        validateData.simrs_type &&
184
+        vendorHistory.simrs_type === "vendor" &&
185
+        vendorHistory.vendor_id !== null &&
186
+        validateData.simrs_type !== "vendor"
187
+    ) {
188
+        await prisma.vendorExperience.update({
189
+            where: {
190
+                id: id_vendor_experience,
191
+                deletedAt: null
192
+            },
193
+            data: {
194
+                vendor_id: null
195
+            }
196
+        });
197
+    }
198
+
199
+    if (validateData.contract_start_date && validateData.contract_expired_date) {
200
+        if (validateData.contract_start_date >= validateData.contract_expired_date) {
201
+            throw new HttpException("Contract expired date must be after contract date", 400)
202
+        }
203
+    }
204
+
205
+    if (validateData.contract_value_min && validateData.contract_value_max) {
206
+        if (validateData.contract_value_min >= validateData.contract_value_max) {
207
+            throw new HttpException("Contract value max must be after contract value min", 400)
208
+        }
209
+    }
210
+
211
+    if (validateData.status === "active") {
212
+        await prisma.vendorExperience.updateMany({
213
+            where: {
214
+                hospital_id: id_hospital,
215
+                status: "active",
216
+                deletedAt: null,
217
+                NOT: { id: id_vendor_experience },
218
+            },
219
+            data: {
220
+                status: "inactive",
221
+            },
222
+        });
223
+    }
224
+
225
+    const payload = {
226
+        status: validateData.status,
227
+        contract_start_date: validateData.contract_start_date ? new Date(validateData.contract_start_date) : vendorHistory.contract_start_date,
228
+        contract_expired_date: validateData.contract_expired_date ? new Date(validateData.contract_expired_date) : vendorHistory.contract_expired_date,
229
+        contract_value_min: validateData.contract_value_min ? Number(validateData.contract_value_min) : vendorHistory.contract_value_min,
230
+        contract_value_max: validateData.contract_value_max ? Number(validateData.contract_value_max) : vendorHistory.contract_value_max,
231
+        positive_notes: validateData.positive_notes,
232
+        negative_notes: validateData.negative_notes,
233
+        simrs_type: validateData.simrs_type,
234
+        vendor_id: validateData.vendor_id,
235
+    };
236
+
237
+    const data = await VendorExperienceRepository.update(id_vendor_experience, payload);
238
+    await updateLog(req, data);
239
+};
240
+
241
+exports.deleteVendorHistoryService = async (req) => {
242
+    const id_hospital = req.params.id;
243
+    const id_vendor_experience = req.params.id_vendor_experience;
244
+
245
+    const hospital = await prisma.hospital.findFirst({
246
+        where: {
247
+            id: id_hospital
248
+        }
249
+    })
250
+    if (!hospital) {
251
+        throw new HttpException("Hospital not found", 404)
252
+    }
253
+
254
+    const vendor = await VendorExperienceRepository.findById(id_vendor_experience);
255
+    if (!vendor) {
256
+        throw new HttpException("Vendor history not found", 404);
257
+    }
258
+
259
+    const data = await VendorExperienceRepository.update(id_vendor_experience, {
260
+        deletedAt: timeLocal.now().toDate()
261
+    });
262
+
263
+    await deleteLog(req, data);
264
+};

+ 0 - 217
src/services/admin/VendorHistoryService.js

@@ -1,217 +0,0 @@
1
-const VendorHistoryRepository = require('../../repository/admin/VendorHistoryRepository.js');
2
-const HttpException = require('../../utils/HttpException.js');
3
-const prisma = require('../../prisma/PrismaClient.js');
4
-const { SearchFilter } = require('../../utils/SearchFilter.js');
5
-const timeLocal = require('../../utils/TimeLocal.js');
6
-const { createLog, updateLog, deleteLog } = require('../../utils/LogActivity.js');
7
-const { formatDateOnly, formatISOWithoutTimezone } = require('../../utils/FormatDate.js');
8
-
9
-exports.getAllVendorHistoryService = async ({ page, limit, search, sortBy, orderBy }, req) => {
10
-    const skip = (page - 1) * limit;
11
-
12
-    const hospitalId = req.params.id;
13
-    const hospital = await prisma.hospital.findFirst({
14
-        where: {
15
-            id: hospitalId
16
-        }
17
-    })
18
-    if (!hospital) {
19
-        throw new HttpException("Hospital not found", 404)
20
-    }
21
-
22
-    const where = {
23
-        // ...SearchFilter(search, ['name', 'name_pt']),
24
-        hospital_id: req.params.id,
25
-        deletedAt: null
26
-    };
27
-
28
-    const [vendor_histories, total] = await Promise.all([
29
-        VendorHistoryRepository.findAll({ skip, take: limit, where, orderBy: { [sortBy]: orderBy } }),
30
-        VendorHistoryRepository.countAll(where)
31
-    ]);
32
-
33
-    return { vendor_histories, total };
34
-};
35
-
36
-exports.showVendorHistoryService = async (req) => {
37
-    const id_hospital = req.params.id;
38
-    const id_vendor_history = req.params.id_vendor_history;
39
-
40
-    const hospital = await prisma.hospital.findFirst({
41
-        where: {
42
-            id: id_hospital
43
-        }
44
-    })
45
-    if (!hospital) {
46
-        throw new HttpException("Hospital not found", 404)
47
-    }
48
-
49
-    const vendorHistory = await VendorHistoryRepository.findById(id_vendor_history);
50
-    if (!vendorHistory) {
51
-        throw new HttpException("Vendor history not found", 404);
52
-    }
53
-
54
-    return vendorHistory;
55
-};
56
-
57
-exports.storeVendorHistoryService = async (validateData, req) => {
58
-    const hospitalId = req.params.id;
59
-
60
-    const hospital = await prisma.hospital.findFirst({
61
-        where: {
62
-            id: hospitalId
63
-        }
64
-    });
65
-
66
-    if (!hospital) {
67
-        throw new HttpException("Hospital not found", 404)
68
-    }
69
-
70
-    const vendor = await prisma.vendor.findFirst({
71
-        where: {
72
-            id: validateData.vendor_id
73
-        }
74
-    });
75
-    if (!vendor) {
76
-        throw new HttpException("Vendor not found", 404)
77
-    }
78
-
79
-    await prisma.hospital.update({
80
-        where: { id: hospitalId },
81
-        data: {
82
-            simrs_type: validateData.simrs_type
83
-        }
84
-    });
85
-
86
-    if (validateData.simrs_type) {
87
-        const existingActiveVendors = await prisma.vendorHistory.findMany({
88
-            where: {
89
-                hospital_id: hospitalId,
90
-                status: "active",
91
-                deletedAt: null
92
-            }
93
-        });
94
-
95
-        if (existingActiveVendors.length > 0) {
96
-            await prisma.vendorHistory.updateMany({
97
-                where: {
98
-                    hospital_id: hospitalId,
99
-                    status: "active",
100
-                    deletedAt: null
101
-                },
102
-                data: {
103
-                    status: "inactive"
104
-                }
105
-            });
106
-        }
107
-
108
-        validateData.status = "active";
109
-    }
110
-
111
-    const payload = {
112
-        vendor_id: validateData.vendor_id,
113
-        vendor_impression: validateData.vendor_impression,
114
-        status: validateData.status,
115
-        contract_date: validateData.contract_date,
116
-        contract_expired_date: validateData.contract_expired_date,
117
-        hospital_id: hospitalId
118
-    };
119
-
120
-    const data = await VendorHistoryRepository.create(payload);
121
-    await createLog(req, data);
122
-};
123
-
124
-exports.updateVendorHistoryService = async (validateData, req) => {
125
-    const id_hospital = req.params.id;
126
-    const id_vendor_history = req.params.id_vendor_history;
127
-
128
-    const hospital = await prisma.hospital.findFirst({
129
-        where: {
130
-            id: id_hospital
131
-        }
132
-    })
133
-    if (!hospital) {
134
-        throw new HttpException("Hospital not found", 404)
135
-    }
136
-
137
-    const vendorHistory = await VendorHistoryRepository.findById(id_vendor_history);
138
-    if (!vendorHistory) {
139
-        throw new HttpException("Vendor history not found", 404);
140
-    }
141
-
142
-    if (validateData.vendor_id) {
143
-        const existVendor = await prisma.vendor.findFirst({
144
-            where: {
145
-                id: validateData.vendor_id
146
-            }
147
-        });
148
-        if (!existVendor) {
149
-            throw new HttpException("Vendor not found", 404)
150
-        }
151
-    }
152
-
153
-    if (validateData.simrs_type) {
154
-        await prisma.hospital.update({
155
-            where: { id: id_hospital },
156
-            data: {
157
-                simrs_type: validateData.simrs_type
158
-            },
159
-        });
160
-    }
161
-
162
-    if (validateData.contract_date && validateData.contract_expired_date) {
163
-        if (validateData.contract_date >= validateData.contract_expired_date) {
164
-            throw new HttpException("Contract expired date must be after contract date", 400)
165
-        }
166
-    }
167
-
168
-    if (validateData.status === "active") {
169
-        await prisma.vendorHistory.updateMany({
170
-            where: {
171
-                hospital_id: id_hospital,
172
-                status: "active",
173
-                deletedAt: null,
174
-                NOT: { id: id_vendor_history },
175
-            },
176
-            data: {
177
-                status: "inactive",
178
-            },
179
-        });
180
-    }
181
-
182
-    const payload = {
183
-        vendor_id: validateData.vendor_id,
184
-        vendor_impression: validateData.vendor_impression,
185
-        status: validateData.status,
186
-        contract_date: validateData.contract_date ? new Date(validateData.contract_date) : vendorHistory.contract_date,
187
-        contract_expired_date: validateData.contract_expired_date ? new Date(validateData.contract_expired_date) : vendorHistory.contract_expired_date,
188
-    };
189
-
190
-    const data = await VendorHistoryRepository.update(id_vendor_history, payload);
191
-    await updateLog(req, data);
192
-};
193
-
194
-exports.deleteVendorHistoryService = async (req) => {
195
-    const id_hospital = req.params.id;
196
-    const id_vendor_history = req.params.id_vendor_history;
197
-
198
-    const hospital = await prisma.hospital.findFirst({
199
-        where: {
200
-            id: id_hospital
201
-        }
202
-    })
203
-    if (!hospital) {
204
-        throw new HttpException("Hospital not found", 404)
205
-    }
206
-
207
-    const vendor = await VendorHistoryRepository.findById(id_vendor_history);
208
-    if (!vendor) {
209
-        throw new HttpException("Vendor history not found", 404);
210
-    }
211
-
212
-    const data = await VendorHistoryRepository.update(id_vendor_history, {
213
-        deletedAt: timeLocal.now().toDate()
214
-    });
215
-
216
-    await deleteLog(req, data);
217
-};

+ 9 - 11
src/services/sales/VendorHistoryService.js

@@ -1,10 +1,8 @@
1
 const VendorHistoryRepository = require('../../repository/sales/VendorHistoryRepository.js');
1
 const VendorHistoryRepository = require('../../repository/sales/VendorHistoryRepository.js');
2
 const HttpException = require('../../utils/HttpException.js');
2
 const HttpException = require('../../utils/HttpException.js');
3
 const prisma = require('../../prisma/PrismaClient.js');
3
 const prisma = require('../../prisma/PrismaClient.js');
4
-const { SearchFilter } = require('../../utils/SearchFilter.js');
5
 const timeLocal = require('../../utils/TimeLocal.js');
4
 const timeLocal = require('../../utils/TimeLocal.js');
6
 const { createLog, updateLog, deleteLog } = require('../../utils/LogActivity.js');
5
 const { createLog, updateLog, deleteLog } = require('../../utils/LogActivity.js');
7
-const { formatDateOnly, formatISOWithoutTimezone } = require('../../utils/FormatDate.js');
8
 
6
 
9
 exports.getAllVendorHistoryService = async ({ page, limit, search, sortBy, orderBy }, req) => {
7
 exports.getAllVendorHistoryService = async ({ page, limit, search, sortBy, orderBy }, req) => {
10
     const skip = (page - 1) * limit;
8
     const skip = (page - 1) * limit;
@@ -35,7 +33,7 @@ exports.getAllVendorHistoryService = async ({ page, limit, search, sortBy, order
35
 
33
 
36
 exports.showVendorHistoryService = async (req) => {
34
 exports.showVendorHistoryService = async (req) => {
37
     const id_hospital = req.params.id;
35
     const id_hospital = req.params.id;
38
-    const id_vendor_history = req.params.id_vendor_history;
36
+    const id_vendor_experience = req.params.id_vendor_experience;
39
 
37
 
40
     const hospital = await prisma.hospital.findFirst({
38
     const hospital = await prisma.hospital.findFirst({
41
         where: {
39
         where: {
@@ -46,7 +44,7 @@ exports.showVendorHistoryService = async (req) => {
46
         throw new HttpException("Hospital not found", 404)
44
         throw new HttpException("Hospital not found", 404)
47
     }
45
     }
48
 
46
 
49
-    const vendorHistory = await VendorHistoryRepository.findById(id_vendor_history);
47
+    const vendorHistory = await VendorHistoryRepository.findById(id_vendor_experience);
50
     if (!vendorHistory) {
48
     if (!vendorHistory) {
51
         throw new HttpException("Vendor history not found", 404);
49
         throw new HttpException("Vendor history not found", 404);
52
     }
50
     }
@@ -122,7 +120,7 @@ exports.storeVendorHistoryService = async (validateData, req) => {
122
 
120
 
123
 exports.updateVendorHistoryService = async (validateData, req) => {
121
 exports.updateVendorHistoryService = async (validateData, req) => {
124
     const id_hospital = req.params.id;
122
     const id_hospital = req.params.id;
125
-    const id_vendor_history = req.params.id_vendor_history;
123
+    const id_vendor_experience = req.params.id_vendor_experience;
126
 
124
 
127
     const hospital = await prisma.hospital.findFirst({
125
     const hospital = await prisma.hospital.findFirst({
128
         where: {
126
         where: {
@@ -133,7 +131,7 @@ exports.updateVendorHistoryService = async (validateData, req) => {
133
         throw new HttpException("Hospital not found", 404)
131
         throw new HttpException("Hospital not found", 404)
134
     }
132
     }
135
 
133
 
136
-    const vendorHistory = await VendorHistoryRepository.findById(id_vendor_history);
134
+    const vendorHistory = await VendorHistoryRepository.findById(id_vendor_experience);
137
     if (!vendorHistory) {
135
     if (!vendorHistory) {
138
         throw new HttpException("Vendor history not found", 404);
136
         throw new HttpException("Vendor history not found", 404);
139
     }
137
     }
@@ -168,7 +166,7 @@ exports.updateVendorHistoryService = async (validateData, req) => {
168
                 hospital_id: id_hospital,
166
                 hospital_id: id_hospital,
169
                 status: "active",
167
                 status: "active",
170
                 deletedAt: null,
168
                 deletedAt: null,
171
-                NOT: { id: id_vendor_history },
169
+                NOT: { id: id_vendor_experience },
172
             },
170
             },
173
             data: {
171
             data: {
174
                 status: "inactive",
172
                 status: "inactive",
@@ -184,13 +182,13 @@ exports.updateVendorHistoryService = async (validateData, req) => {
184
         contract_expired_date: validateData.contract_expired_date ? new Date(validateData.contract_expired_date) : vendorHistory.contract_expired_date,
182
         contract_expired_date: validateData.contract_expired_date ? new Date(validateData.contract_expired_date) : vendorHistory.contract_expired_date,
185
     };
183
     };
186
 
184
 
187
-    const data = await VendorHistoryRepository.update(id_vendor_history, payload);
185
+    const data = await VendorHistoryRepository.update(id_vendor_experience, payload);
188
     await updateLog(req, data);
186
     await updateLog(req, data);
189
 };
187
 };
190
 
188
 
191
 exports.deleteVendorHistoryService = async (req) => {
189
 exports.deleteVendorHistoryService = async (req) => {
192
     const id_hospital = req.params.id;
190
     const id_hospital = req.params.id;
193
-    const id_vendor_history = req.params.id_vendor_history;
191
+    const id_vendor_experience = req.params.id_vendor_experience;
194
 
192
 
195
     const hospital = await prisma.hospital.findFirst({
193
     const hospital = await prisma.hospital.findFirst({
196
         where: {
194
         where: {
@@ -201,12 +199,12 @@ exports.deleteVendorHistoryService = async (req) => {
201
         throw new HttpException("Hospital not found", 404)
199
         throw new HttpException("Hospital not found", 404)
202
     }
200
     }
203
 
201
 
204
-    const vendor = await VendorHistoryRepository.findById(id_vendor_history);
202
+    const vendor = await VendorHistoryRepository.findById(id_vendor_experience);
205
     if (!vendor) {
203
     if (!vendor) {
206
         throw new HttpException("Vendor history not found", 404);
204
         throw new HttpException("Vendor history not found", 404);
207
     }
205
     }
208
 
206
 
209
-    const data = await VendorHistoryRepository.update(id_vendor_history, {
207
+    const data = await VendorHistoryRepository.update(id_vendor_experience, {
210
         deletedAt: timeLocal.now().toDate()
208
         deletedAt: timeLocal.now().toDate()
211
     });
209
     });
212
 
210
 

+ 69 - 0
src/utils/Scheduler.js

@@ -0,0 +1,69 @@
1
+const cron = require('node-cron');
2
+const axios = require('axios');
3
+const dayjs = require('dayjs');
4
+const prisma = require('../prisma/PrismaClient.js');
5
+
6
+const getAdminToken = async () => {
7
+    const response = await axios.post(`${process.env.KEYCLOAK_URL}/realms/master/protocol/openid-connect/token`, new URLSearchParams({
8
+        client_id: 'admin-cli',
9
+        username: process.env.KEYCLOAK_ADMIN_USERNAME,
10
+        password: process.env.KEYCLOAK_ADMIN_PASSWORD,
11
+        grant_type: 'password'
12
+    }), {
13
+        headers: {
14
+            'Content-Type': 'application/x-www-form-urlencoded'
15
+        }
16
+    });
17
+
18
+    return response.data.access_token;
19
+};
20
+
21
+async function getAllUsers(token) {
22
+    const res = await axios.get(`${process.env.KEYCLOAK_URL}/admin/realms/${process.env.KEYCLOAK_REALM}/users`, {
23
+        headers: {
24
+            Authorization: `Bearer ${token}`
25
+        }
26
+    });
27
+
28
+    return res.data;
29
+}
30
+
31
+async function syncUserToDB(user) {
32
+    const fullname = `${user.firstName || ''} ${user.lastName || ''}`.trim();
33
+    const existing = await prisma.userKeycloak.findUnique({ where: { id: user.id } });
34
+    if (!existing) {
35
+        await prisma.userKeycloak.create({
36
+            data: {
37
+                id: user.id,
38
+                fullname
39
+            }
40
+        });
41
+        console.log('✔️ Synced user:', fullname);
42
+    }
43
+}
44
+
45
+// Cron job setiap 1 menit
46
+cron.schedule('* * * * *', async () => {
47
+    console.log('[SYNC] Checking for new users...');
48
+
49
+    try {
50
+        const token = await getAdminToken();
51
+        const users = await getAllUsers(token);
52
+
53
+        const now = dayjs();
54
+        const oneMinuteAgo = now.subtract(1, 'minute');
55
+
56
+        // Filter user yang dibuat dalam 1 menit terakhir
57
+        const newUsers = users.filter(u => u.createdTimestamp && dayjs(u.createdTimestamp).isAfter(oneMinuteAgo));
58
+
59
+        for (const user of newUsers) {
60
+            await syncUserToDB(user);
61
+        }
62
+
63
+        if (newUsers.length === 0) {
64
+            console.log('ℹ️ No new users.');
65
+        }
66
+    } catch (err) {
67
+        console.error('❌ Sync error:', err.message);
68
+    }
69
+});

+ 29 - 0
src/validators/admin/vendor_experience/VendorExperienceValidators.js

@@ -0,0 +1,29 @@
1
+const HttpException = require('../../../utils/HttpException.js');
2
+
3
+exports.validateStoreVendorHistoryRequest = (body) => {
4
+    const { simrs_type, vendor_id, status, contract_start_date, contract_expired_date, contract_value_min, contract_value_max, positive_notes, negative_notes } = body;
5
+
6
+    const errors = {};
7
+
8
+    if (!simrs_type || simrs_type.trim() === '') {
9
+        errors.simrs_type = ['simrs type is required'];
10
+    }
11
+
12
+    if (!status || status.trim() === '') {
13
+        errors.status = ['Status is required'];
14
+    }
15
+
16
+    if (Object.keys(errors).length > 0) {
17
+        throw new HttpException(errors, 422);
18
+    }
19
+
20
+    return { simrs_type, vendor_id, status, contract_start_date, contract_expired_date, contract_value_min, contract_value_max, positive_notes, negative_notes };
21
+}
22
+
23
+exports.validateUpdateVendorHistoryRequest = (body) => {
24
+    const { simrs_type, vendor_id, status, contract_start_date, contract_expired_date, contract_value_min, contract_value_max, positive_notes, negative_notes } = body;
25
+
26
+    return {
27
+        simrs_type, vendor_id, status, contract_start_date, contract_expired_date, contract_value_min, contract_value_max, positive_notes, negative_notes
28
+    };
29
+}

+ 0 - 95
src/validators/admin/vendor_history/VendorHistoriValidators.js

@@ -1,95 +0,0 @@
1
-const HttpException = require('../../../utils/HttpException.js');
2
-
3
-exports.validateStoreVendorHistoryRequest = (body) => {
4
-    const { simrs_type, vendor_id, vendor_impression, status, contract_date, contract_expired_date } = body;
5
-
6
-    const errors = {};
7
-
8
-    if (!simrs_type || simrs_type.trim() === '') {
9
-        errors.simrs_type = ['simrs type is required'];
10
-    } else if (!['vendor', 'gratis', 'in house'].includes(simrs_type.trim().toLowerCase())) {
11
-        errors.simrs_type = ['Simrs type must be either "vendor", "gratis", or "in house"'];
12
-    }
13
-
14
-    // if (!vendor_id || vendor_id.trim() === '') {
15
-    //     errors.vendor_id = ['Vendor id is required'];
16
-    // }
17
-
18
-    // if (!vendor_impression || vendor_impression.trim() === '') {
19
-    //     errors.vendor_impression = ['Vendor impression is required'];
20
-    // }
21
-
22
-    if (!['active', 'inactive'].includes(status.trim().toLowerCase())) {
23
-        errors.status = ['Status must be either "active" or "inactive"'];
24
-    }
25
-
26
-    // if (!status || status.trim() === '') {
27
-    //     errors.status = ['Status is required'];
28
-    // } else if (!['active', 'inactive'].includes(status.trim().toLowerCase())) {
29
-    //     errors.status = ['Status must be either "active" or "inactive"'];
30
-    // }
31
-
32
-    if (contract_date !== null && contract_date !== '' && isNaN(Date.parse(contract_date))) {
33
-        errors.contract_date = ['Contract date must be a valid date'];
34
-    }
35
-
36
-
37
-    // if (!contract_date || contract_date.trim() === '') {
38
-    //     errors.contract_date = ['Contract date is required'];
39
-    // } else if (isNaN(Date.parse(contract_date))) {
40
-    //     errors.contract_date = ['Contract date must be a valid date'];
41
-    // }
42
-
43
-    if (contract_expired_date !== null && contract_expired_date !== '' && isNaN(Date.parse(contract_expired_date))) {
44
-        errors.contract_expired_date = ['Contract expired date must be a valid date'];
45
-    }
46
-
47
-    // if (!contract_expired_date || contract_expired_date.trim() === '') {
48
-    //     errors.contract_expired_date = ['Contract expired date is required'];
49
-    // } else if (isNaN(Date.parse(contract_expired_date))) {
50
-    //     errors.contract_expired_date = ['Contract expired date must be a valid date'];
51
-    // }
52
-
53
-    if (
54
-        !errors.contract_date &&
55
-        !errors.contract_expired_date &&
56
-        new Date(contract_expired_date) <= new Date(contract_date)
57
-    ) {
58
-        errors.contract_expired_date = ['Contract expired date must be after contract date'];
59
-    }
60
-
61
-    if (Object.keys(errors).length > 0) {
62
-        throw new HttpException(errors, 422);
63
-    }
64
-
65
-    return {
66
-        simrs_type: simrs_type.trim(),
67
-        vendor_id: vendor_id ? vendor_id.trim() : null,
68
-        vendor_impression: vendor_impression ? vendor_impression.trim() : null,
69
-        status: status.trim().toLowerCase(),
70
-        contract_date: contract_date ? new Date(contract_date) : null,
71
-        contract_expired_date: contract_expired_date ? new Date(contract_expired_date) : null,
72
-    };
73
-};
74
-
75
-exports.validateUpdateVendorHistoryRequest = (body) => {
76
-    const { simrs_type, vendor_id, vendor_impression, status, contract_date, contract_expired_date } = body;
77
-
78
-    if (simrs_type !== undefined && simrs_type !== null && simrs_type.trim() !== '') {
79
-        const allowedTypes = ['vendor', 'gratis', 'in house'];
80
-        const simrsTypeValue = simrs_type.trim().toLowerCase();
81
-
82
-        if (!allowedTypes.includes(simrsTypeValue)) {
83
-            throw new HttpException('Simrs type must be either "vendor", "gratis", or "in house"', 422);
84
-        }
85
-    }
86
-
87
-    return {
88
-        simrs_type,
89
-        vendor_id,
90
-        vendor_impression,
91
-        status,
92
-        contract_date,
93
-        contract_expired_date
94
-    };
95
-}