Explorar el Código

add feature epic 2 & 3

pearlgw hace 2 meses
padre
commit
7925561717
Se han modificado 77 ficheros con 3512 adiciones y 221 borrados
  1. 5 2
      entrypoint.sh
  2. 8 0
      index.js
  3. 21 0
      package-lock.json
  4. 2 0
      package.json
  5. 18 0
      prisma/migrations/20250626035040_add_table_vendor/migration.sql
  6. 21 0
      prisma/migrations/20250627125223_add_table_vendor_history/migration.sql
  7. 18 0
      prisma/migrations/20250627171433_add_table_executives_histories/migration.sql
  8. 8 0
      prisma/migrations/20250628023321_update_table_vendor_history/migration.sql
  9. 5 0
      prisma/migrations/20250628054921_update_field_in_vendor/migration.sql
  10. 81 28
      prisma/schema.prisma
  11. 9 4
      prisma/seeders/DatabaseSeeder.js
  12. 131 46
      prisma/seeders/HospitalSeeder.js
  13. 41 0
      prisma/seeders/UserAreaSeeder.js
  14. 141 44
      prisma/seeders/UserSeeder.js
  15. 57 0
      prisma/seeders/VendorSeeder.js
  16. 57 0
      src/controllers/admin/ExecutivesHistoryController.js
  17. 2 2
      src/controllers/admin/HospitalController.js
  18. 60 0
      src/controllers/admin/VendorController.js
  19. 57 0
      src/controllers/admin/VendorHistoryController.js
  20. 16 0
      src/controllers/sales/AreaController.js
  21. 57 0
      src/controllers/sales/ExecutivesHistoryController.js
  22. 2 2
      src/controllers/sales/HospitalController.js
  23. 59 0
      src/controllers/sales/VendorController.js
  24. 57 0
      src/controllers/sales/VendorHistoryController.js
  25. 18 0
      src/controllers/superadmin/LogController.js
  26. 124 0
      src/repository/admin/ExecutivesHistoryRepository.js
  27. 33 1
      src/repository/admin/HospitalRepository.js
  28. 154 0
      src/repository/admin/VendorHistoryRepository.js
  29. 134 0
      src/repository/admin/VendorRepository.js
  30. 33 0
      src/repository/sales/AreaRepository.js
  31. 124 0
      src/repository/sales/ExecutivesHistoryRepository.js
  32. 1 1
      src/repository/sales/HospitalRepository.js
  33. 154 0
      src/repository/sales/VendorHistoryRepository.js
  34. 117 0
      src/repository/sales/VendorRepository.js
  35. 24 0
      src/repository/superadmin/LogRepository.js
  36. 2 2
      src/routes/admin/CityRoute.js
  37. 16 0
      src/routes/admin/HospitalRoute.js
  38. 13 0
      src/routes/admin/VendorRoute.js
  39. 9 0
      src/routes/sales/AreaRoute.js
  40. 16 0
      src/routes/sales/HospitalRoute.js
  41. 9 0
      src/routes/sales/VendorRoute.js
  42. 10 0
      src/routes/superadmin/LogRoute.js
  43. 15 4
      src/services/admin/CityService.js
  44. 141 0
      src/services/admin/ExecutivesHistoryService.js
  45. 24 13
      src/services/admin/HospitalService.js
  46. 14 2
      src/services/admin/ProvinceService.js
  47. 78 3
      src/services/admin/SalesService.js
  48. 163 0
      src/services/admin/VendorHistoryService.js
  49. 105 0
      src/services/admin/VendorService.js
  50. 20 0
      src/services/sales/AreaService.js
  51. 141 0
      src/services/sales/ExecutivesHistoryService.js
  52. 89 32
      src/services/sales/HospitalService.js
  53. 163 0
      src/services/sales/VendorHistoryService.js
  54. 113 0
      src/services/sales/VendorService.js
  55. 18 0
      src/services/superadmin/LogService.js
  56. 47 0
      src/utils/FormatDate.js
  57. 14 3
      src/utils/PaginationParams.js
  58. 63 22
      src/utils/SearchFilter.js
  59. 77 0
      src/validators/admin/executives_history/ExecutivesHistoriValidators.js
  60. 10 10
      src/validators/admin/hospital/HospitalValidators.js
  61. 35 0
      src/validators/admin/vendor/VendorValidators.js
  62. 73 0
      src/validators/admin/vendor_history/VendorHistoriValidators.js
  63. 77 0
      src/validators/sales/executives_history/ExecutivesHistoriValidators.js
  64. 35 0
      src/validators/sales/vendor/VendorValidators.js
  65. 73 0
      src/validators/sales/vendor_history/VendorHistoriValidators.js
  66. BIN
      storage/img/1750918428600-237588933.jpeg
  67. BIN
      storage/img/1750918458736-664806797.jpeg
  68. BIN
      storage/img/1750918486962-92985809.jpeg
  69. BIN
      storage/img/1750918639876-436618340.jpeg
  70. BIN
      storage/img/1750929217833-522408200.jpeg
  71. BIN
      storage/img/1751005890656-200597968.jpeg
  72. BIN
      storage/img/1751006026954-794671964.jpeg
  73. BIN
      storage/img/1751006443877-732477045.png
  74. BIN
      storage/img/1751006451755-38518667.png
  75. BIN
      storage/img/1751006482002-378131488.png
  76. BIN
      storage/img/1751008243197-639287171.jpeg
  77. BIN
      storage/img/1751076562821-359984929.jpeg

+ 5 - 2
entrypoint.sh

@@ -6,8 +6,11 @@ npx prisma generate
6 6
 echo "🔃 Menjalankan migrasi dev..."
7 7
 npx prisma migrate dev --name init
8 8
 
9
-echo "♻️ Reset database dan menjalankan seeder..."
10
-npx prisma migrate reset --force
9
+# echo "♻️ Reset database dan menjalankan seeder..."
10
+# npx prisma migrate reset --force
11
+
12
+echo "🌱 Menjalankan seeder (aman)..."
13
+npx prisma db seed
11 14
 
12 15
 echo "🚀 Menjalankan aplikasi..."
13 16
 exec node index.js

+ 8 - 0
index.js

@@ -12,6 +12,10 @@ const salesRoutes = require('./src/routes/admin/SalesRoute.js')
12 12
 const adminRoutes = require('./src/routes/superadmin/AdminRoute.js')
13 13
 const hospitalRoutes = require('./src/routes/admin/HospitalRoute.js')
14 14
 const salesHospitalRoutes = require('./src/routes/sales/HospitalRoute.js')
15
+const vendorRoutes = require('./src/routes/admin/VendorRoute.js')
16
+const logRoutes = require('./src/routes/superadmin/LogRoute.js')
17
+const areaRoutes = require('./src/routes/sales/AreaRoute.js')
18
+const vendorSalesRoutes = require('./src/routes/sales/VendorRoute.js')
15 19
 const { port } = require('./config/config.js')
16 20
 
17 21
 app.use(cors())
@@ -29,6 +33,10 @@ apiV1.use('/sales', salesRoutes);
29 33
 apiV1.use('/admin', adminRoutes);
30 34
 apiV1.use('/hospital', hospitalRoutes);
31 35
 apiV1.use('/hospital-area', salesHospitalRoutes);
36
+apiV1.use('/vendor', vendorRoutes);
37
+apiV1.use('/logs', logRoutes);
38
+apiV1.use('/area', areaRoutes);
39
+apiV1.use('/vendor-sales', vendorSalesRoutes);
32 40
 
33 41
 app.get('/', (req, res) => {
34 42
     res.send('Selamat Datang di API Radar Farmagitechs');

+ 21 - 0
package-lock.json

@@ -14,6 +14,8 @@
14 14
         "axios": "^1.10.0",
15 15
         "bcrypt": "^6.0.0",
16 16
         "cors": "^2.8.5",
17
+        "date-fns": "^4.1.0",
18
+        "date-fns-tz": "^3.2.0",
17 19
         "dayjs": "^1.11.13",
18 20
         "dayjs-plugin-utc": "^0.1.2",
19 21
         "dotenv": "^16.5.0",
@@ -335,6 +337,25 @@
335 337
         "node": ">= 0.10"
336 338
       }
337 339
     },
340
+    "node_modules/date-fns": {
341
+      "version": "4.1.0",
342
+      "resolved": "https://registry.npmjs.org/date-fns/-/date-fns-4.1.0.tgz",
343
+      "integrity": "sha512-Ukq0owbQXxa/U3EGtsdVBkR1w7KOQ5gIBqdH2hkvknzZPYvBxb/aa6E8L7tmjFtkwZBu3UXBbjIgPo/Ez4xaNg==",
344
+      "license": "MIT",
345
+      "funding": {
346
+        "type": "github",
347
+        "url": "https://github.com/sponsors/kossnocorp"
348
+      }
349
+    },
350
+    "node_modules/date-fns-tz": {
351
+      "version": "3.2.0",
352
+      "resolved": "https://registry.npmjs.org/date-fns-tz/-/date-fns-tz-3.2.0.tgz",
353
+      "integrity": "sha512-sg8HqoTEulcbbbVXeg84u5UnlsQa8GS5QXMqjjYIhS4abEVVKIUwe0/l/UhrZdKaL/W5eWZNlbTeEIiOXTcsBQ==",
354
+      "license": "MIT",
355
+      "peerDependencies": {
356
+        "date-fns": "^3.0.0 || ^4.0.0"
357
+      }
358
+    },
338 359
     "node_modules/dayjs": {
339 360
       "version": "1.11.13",
340 361
       "resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.11.13.tgz",

+ 2 - 0
package.json

@@ -18,6 +18,8 @@
18 18
     "axios": "^1.10.0",
19 19
     "bcrypt": "^6.0.0",
20 20
     "cors": "^2.8.5",
21
+    "date-fns": "^4.1.0",
22
+    "date-fns-tz": "^3.2.0",
21 23
     "dayjs": "^1.11.13",
22 24
     "dayjs-plugin-utc": "^0.1.2",
23 25
     "dotenv": "^16.5.0",

+ 18 - 0
prisma/migrations/20250626035040_add_table_vendor/migration.sql

@@ -0,0 +1,18 @@
1
+-- CreateTable
2
+CREATE TABLE "vendors" (
3
+    "id" TEXT NOT NULL,
4
+    "name" TEXT NOT NULL,
5
+    "name_pt" TEXT NOT NULL,
6
+    "strengths" TEXT NOT NULL,
7
+    "weaknesses" TEXT NOT NULL,
8
+    "website" TEXT NOT NULL,
9
+    "created_by" TEXT NOT NULL,
10
+    "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
11
+    "updatedAt" TIMESTAMP(3) NOT NULL,
12
+    "deletedAt" TIMESTAMP(3),
13
+
14
+    CONSTRAINT "vendors_pkey" PRIMARY KEY ("id")
15
+);
16
+
17
+-- AddForeignKey
18
+ALTER TABLE "vendors" ADD CONSTRAINT "vendors_created_by_fkey" FOREIGN KEY ("created_by") REFERENCES "users"("id") ON DELETE RESTRICT ON UPDATE CASCADE;

+ 21 - 0
prisma/migrations/20250627125223_add_table_vendor_history/migration.sql

@@ -0,0 +1,21 @@
1
+-- CreateTable
2
+CREATE TABLE "vendor_histories" (
3
+    "id" TEXT NOT NULL,
4
+    "hospital_id" TEXT NOT NULL,
5
+    "vendor_id" TEXT NOT NULL,
6
+    "vendor_impression" TEXT,
7
+    "status" TEXT,
8
+    "contract_date" TIMESTAMP(3),
9
+    "contract_expired_date" TIMESTAMP(3),
10
+    "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
11
+    "updatedAt" TIMESTAMP(3) NOT NULL,
12
+    "deletedAt" TIMESTAMP(3),
13
+
14
+    CONSTRAINT "vendor_histories_pkey" PRIMARY KEY ("id")
15
+);
16
+
17
+-- AddForeignKey
18
+ALTER TABLE "vendor_histories" ADD CONSTRAINT "vendor_histories_hospital_id_fkey" FOREIGN KEY ("hospital_id") REFERENCES "hospitals"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
19
+
20
+-- AddForeignKey
21
+ALTER TABLE "vendor_histories" ADD CONSTRAINT "vendor_histories_vendor_id_fkey" FOREIGN KEY ("vendor_id") REFERENCES "vendors"("id") ON DELETE RESTRICT ON UPDATE CASCADE;

+ 18 - 0
prisma/migrations/20250627171433_add_table_executives_histories/migration.sql

@@ -0,0 +1,18 @@
1
+-- CreateTable
2
+CREATE TABLE "executives_histories" (
3
+    "id" TEXT NOT NULL,
4
+    "hospital_id" TEXT NOT NULL,
5
+    "executive_name" TEXT,
6
+    "contact" TEXT,
7
+    "status" TEXT,
8
+    "start_term" TIMESTAMP(3),
9
+    "end_term" TIMESTAMP(3),
10
+    "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
11
+    "updatedAt" TIMESTAMP(3) NOT NULL,
12
+    "deletedAt" TIMESTAMP(3),
13
+
14
+    CONSTRAINT "executives_histories_pkey" PRIMARY KEY ("id")
15
+);
16
+
17
+-- AddForeignKey
18
+ALTER TABLE "executives_histories" ADD CONSTRAINT "executives_histories_hospital_id_fkey" FOREIGN KEY ("hospital_id") REFERENCES "hospitals"("id") ON DELETE RESTRICT ON UPDATE CASCADE;

+ 8 - 0
prisma/migrations/20250628023321_update_table_vendor_history/migration.sql

@@ -0,0 +1,8 @@
1
+-- DropForeignKey
2
+ALTER TABLE "vendor_histories" DROP CONSTRAINT "vendor_histories_vendor_id_fkey";
3
+
4
+-- AlterTable
5
+ALTER TABLE "vendor_histories" ALTER COLUMN "vendor_id" DROP NOT NULL;
6
+
7
+-- AddForeignKey
8
+ALTER TABLE "vendor_histories" ADD CONSTRAINT "vendor_histories_vendor_id_fkey" FOREIGN KEY ("vendor_id") REFERENCES "vendors"("id") ON DELETE SET NULL ON UPDATE CASCADE;

+ 5 - 0
prisma/migrations/20250628054921_update_field_in_vendor/migration.sql

@@ -0,0 +1,5 @@
1
+-- AlterTable
2
+ALTER TABLE "vendors" ALTER COLUMN "name_pt" DROP NOT NULL,
3
+ALTER COLUMN "strengths" DROP NOT NULL,
4
+ALTER COLUMN "weaknesses" DROP NOT NULL,
5
+ALTER COLUMN "website" DROP NOT NULL;

+ 81 - 28
prisma/schema.prisma

@@ -56,6 +56,7 @@ model User {
56 56
   deletedAt  DateTime?
57 57
   hospitals  Hospital[]
58 58
   user_areas UserArea[]
59
+  vendors    Vendor[]
59 60
 
60 61
   @@map("users")
61 62
 }
@@ -88,39 +89,91 @@ model City {
88 89
 }
89 90
 
90 91
 model Hospital {
91
-  id              String       @id @default(uuid())
92
-  name            String
93
-  hospital_code   String?
94
-  type            String?
95
-  ownership       String?
96
-  province_id     String
97
-  city_id         String
98
-  address         String?
99
-  simrs_type      String?
100
-  contact         String?
101
-  image           String?
102
-  progress_status ProgressStatus
103
-  note            String?
104
-  created_by      String
105
-  createdAt       DateTime     @default(now())
106
-  updatedAt       DateTime     @updatedAt
107
-  deletedAt       DateTime?
108
-  province        Province     @relation(fields: [province_id], references: [id])
109
-  city            City         @relation(fields: [city_id], references: [id])
110
-  user            User         @relation(fields: [created_by], references: [id])
92
+  id                   String              @id @default(uuid())
93
+  name                 String
94
+  hospital_code        String?
95
+  type                 String?
96
+  ownership            String?
97
+  province_id          String
98
+  city_id              String
99
+  address              String?
100
+  simrs_type           String?
101
+  contact              String?
102
+  image                String?
103
+  progress_status      ProgressStatus
104
+  note                 String?
105
+  created_by           String
106
+  createdAt            DateTime            @default(now())
107
+  updatedAt            DateTime            @updatedAt
108
+  deletedAt            DateTime?
109
+  province             Province            @relation(fields: [province_id], references: [id])
110
+  city                 City                @relation(fields: [city_id], references: [id])
111
+  user                 User                @relation(fields: [created_by], references: [id])
112
+  vendor_histories     VendorHistory[]
113
+  executives_histories ExecutivesHistory[]
111 114
 
112 115
   @@map("hospitals")
113 116
 }
114 117
 
118
+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
+  user             User            @relation(fields: [created_by], references: [id])
130
+  vendor_histories VendorHistory[]
131
+
132
+  @@map("vendors")
133
+}
134
+
115 135
 model UserArea {
116
-  id            String       @id @default(uuid())
117
-  user_id       String
118
-  province_id   String
119
-  user          User         @relation(fields: [user_id], references: [id])
120
-  province      Province     @relation(fields: [province_id], references: [id])
121
-  createdAt     DateTime     @default(now())
122
-  updatedAt     DateTime     @updatedAt
123
-  deletedAt     DateTime?
136
+  id          String    @id @default(uuid())
137
+  user_id     String
138
+  province_id String
139
+  user        User      @relation(fields: [user_id], references: [id])
140
+  province    Province  @relation(fields: [province_id], references: [id])
141
+  createdAt   DateTime  @default(now())
142
+  updatedAt   DateTime  @updatedAt
143
+  deletedAt   DateTime?
124 144
 
125 145
   @@map("user_areas")
126 146
 }
147
+
148
+model VendorHistory {
149
+  id                    String    @id @default(uuid())
150
+  hospital_id           String
151
+  vendor_id             String?
152
+  vendor_impression     String?   @db.Text
153
+  status                String?
154
+  contract_date         DateTime?
155
+  contract_expired_date DateTime?
156
+  hospital              Hospital  @relation(fields: [hospital_id], references: [id])
157
+  vendor                Vendor?   @relation(fields: [vendor_id], references: [id])
158
+  createdAt             DateTime  @default(now())
159
+  updatedAt             DateTime  @updatedAt
160
+  deletedAt             DateTime?
161
+
162
+  @@map("vendor_histories")
163
+}
164
+
165
+model ExecutivesHistory {
166
+  id             String    @id @default(uuid())
167
+  hospital_id    String
168
+  executive_name String?
169
+  contact        String?
170
+  status         String?
171
+  start_term     DateTime?
172
+  end_term       DateTime?
173
+  hospital       Hospital  @relation(fields: [hospital_id], references: [id])
174
+  createdAt      DateTime  @default(now())
175
+  updatedAt      DateTime  @updatedAt
176
+  deletedAt      DateTime?
177
+
178
+  @@map("executives_histories")
179
+}

+ 9 - 4
prisma/seeders/DatabaseSeeder.js

@@ -38,8 +38,10 @@ const { seedSulawesiUtaraCities } = require('./city/SulawesiUtaraCitySeeder');
38 38
 const { seedSumateraBaratCities } = require('./city/SumateraBaratCitySeeder');
39 39
 const { seedSumateraSelatanCities } = require('./city/SumateraSelatanCitySeeder');
40 40
 const { seedSumateraUtaraCities } = require('./city/SumateraUtaraCitySeeder');
41
-const { seedHospital } = require('./HospitalSeeder.js');
42
-const { seedSales } = require('./UserSeeder.js');
41
+const { seedUsers } = require('./UserSeeder.js');
42
+const { seedVendors } = require('./VendorSeeder.js');
43
+const seedUserAreas = require('./UserAreaSeeder.js');
44
+const { seedHospitals } = require('./HospitalSeeder.js');
43 45
 
44 46
 async function main() {
45 47
     await seedProvinces();
@@ -80,8 +82,11 @@ async function main() {
80 82
     await seedSumateraBaratCities();
81 83
     await seedSumateraSelatanCities();
82 84
     await seedSumateraUtaraCities();
83
-    // await seedSales();
84
-    // await seedHospital();
85
+    await seedUsers();
86
+    await seedVendors();
87
+    await seedUserAreas();
88
+    await seedHospitals();
89
+    await seedVendors();
85 90
 }
86 91
 
87 92
 main().then(() => {

+ 131 - 46
prisma/seeders/HospitalSeeder.js

@@ -1,49 +1,134 @@
1
-const prisma = require('../../src/prisma/PrismaClient.js');
2
-const { faker } = require('@faker-js/faker');
3
-const timeLocal = require('../../src/utils/TimeLocal.js');
4
-
5
-exports.seedHospital = async () => {
6
-    // 1. Ambil semua provinsi
7
-    const allProvinces = await prisma.province.findMany();
8
-    const shuffled = allProvinces.sort(() => 0.5 - Math.random());
9
-    const selectedProvinces = shuffled.slice(0, 5); // ambil 5 random
10
-
11
-    for (const province of selectedProvinces) {
12
-        const cities = await prisma.city.findMany({
13
-            where: { province_id: province.id }
14
-        });
15
-
16
-        if (cities.length === 0) continue; // skip kalau tidak ada kota
17
-
18
-        const randomCity = cities[Math.floor(Math.random() * cities.length)];
19
-
20
-        await prisma.hospital.create({
21
-            data: {
22
-                name: `RS ${faker.company.name()}`,
23
-                hospital_code: faker.string.alphanumeric(6).toUpperCase(),
24
-                type: faker.helpers.arrayElement(['Tipe A', 'Tipe B', 'Tipe C']),
25
-                ownership: faker.helpers.arrayElement(['Pemerintah', 'Swasta']),
26
-                address: faker.location.streetAddress(),
27
-                simrs_type: faker.helpers.arrayElement(['SIMRS A', 'SIMRS B', 'SIMRS C']),
28
-                contact: faker.phone.number('08##########'),
29
-                image: '/storage/img/dummy.jpg',
30
-                note: faker.lorem.sentence(),
1
+const { PrismaClient } = require('@prisma/client');
2
+const prisma = new PrismaClient();
3
+
4
+async function seedHospitals() {
5
+    try {
6
+        const sales1 = await prisma.user.findFirst({ where: { username: 'sales1' } });
7
+        const sales2 = await prisma.user.findFirst({ where: { username: 'sales2' } });
8
+
9
+        if (!sales1 || !sales2) {
10
+            throw new Error('User sales1 or sales2 not found');
11
+        }
12
+
13
+        // Ambil province_id dari user_areas
14
+        const sales1Area = await prisma.userArea.findFirst({ where: { user_id: sales1.id } });
15
+        const sales2Area = await prisma.userArea.findFirst({ where: { user_id: sales2.id } });
16
+
17
+        if (!sales1Area || !sales2Area) {
18
+            throw new Error('UserArea for sales1 or sales2 not found');
19
+        }
20
+
21
+        // Ambil city yang berada di province_id tersebut
22
+        const city1 = await prisma.city.findFirst({ where: { province_id: sales1Area.province_id } });
23
+        const city2 = await prisma.city.findFirst({ where: { province_id: sales2Area.province_id } });
24
+
25
+        if (!city1 || !city2) {
26
+            throw new Error('City not found for one of province id');
27
+        }
28
+
29
+        // Data rumah sakit
30
+        const hospitals = [
31
+            {
32
+                name: 'RS Sehat Selalu',
33
+                hospital_code: 'RSS001',
34
+                type: 'Tipe B',
35
+                ownership: 'Swasta',
36
+                address: 'Jl. Kesehatan No. 1',
37
+                simrs_type: 'MediSoft',
38
+                contact: '021-123456',
39
+                image: null,
31 40
                 progress_status: 'cari_data',
32
-                createdAt: timeLocal.now().toDate(),
33
-                province: { connect: { id: province.id } },
34
-                city: { connect: { id: randomCity.id } },
35
-                // user: { connect: { id: await getRandomUserId() } }, // opsional: assign user
36
-                user: null
37
-            }
38
-        });
39
-    }
41
+                note: 'Calon potensial',
42
+                province_id: sales1Area.province_id,
43
+                city_id: city1.id,
44
+                created_by: sales1.id,
45
+            },
46
+            {
47
+                name: 'RS Harapan Bangsa',
48
+                hospital_code: 'RSB002',
49
+                type: 'Tipe C',
50
+                ownership: 'Pemerintah',
51
+                address: 'Jl. Harapan No. 2',
52
+                simrs_type: 'Hospicare',
53
+                contact: '021-654321',
54
+                image: null,
55
+                progress_status: 'cari_data',
56
+                note: null,
57
+                province_id: sales1Area.province_id,
58
+                city_id: city1.id,
59
+                created_by: sales1.id,
60
+            },
61
+            {
62
+                name: 'RS Mitra Medika',
63
+                hospital_code: 'RSM003',
64
+                type: 'Tipe D',
65
+                ownership: 'Swasta',
66
+                address: 'Jl. Mitra No. 3',
67
+                simrs_type: 'SimRS Platinum',
68
+                contact: '021-999999',
69
+                image: null,
70
+                progress_status: 'cari_data',
71
+                note: 'Baru dihubungi',
72
+                province_id: sales1Area.province_id,
73
+                city_id: city1.id,
74
+                created_by: sales1.id,
75
+            },
76
+            {
77
+                name: 'RS Sembuh Bersama',
78
+                hospital_code: 'RSB004',
79
+                type: 'Tipe B',
80
+                ownership: 'Swasta',
81
+                address: 'Jl. Bersama No. 4',
82
+                simrs_type: 'MediSoft',
83
+                contact: '0274-111111',
84
+                image: null,
85
+                progress_status: 'cari_data',
86
+                note: null,
87
+                province_id: sales2Area.province_id,
88
+                city_id: city2.id,
89
+                created_by: sales2.id,
90
+            },
91
+            {
92
+                name: 'RS Kasih Ibu',
93
+                hospital_code: 'RSK005',
94
+                type: 'Tipe C',
95
+                ownership: 'Yayasan',
96
+                address: 'Jl. Kasih No. 5',
97
+                simrs_type: 'Hospicare',
98
+                contact: '0274-222222',
99
+                image: null,
100
+                progress_status: 'cari_data',
101
+                note: 'Perlu follow up',
102
+                province_id: sales2Area.province_id,
103
+                city_id: city2.id,
104
+                created_by: sales2.id,
105
+            },
106
+            {
107
+                name: 'RS Bhakti Sehat',
108
+                hospital_code: 'RSB006',
109
+                type: 'Tipe D',
110
+                ownership: 'Swasta',
111
+                address: 'Jl. Bhakti No. 6',
112
+                simrs_type: 'SimRS Platinum',
113
+                contact: '0274-333333',
114
+                image: null,
115
+                progress_status: 'cari_data',
116
+                note: null,
117
+                province_id: sales2Area.province_id,
118
+                city_id: city2.id,
119
+                created_by: sales2.id,
120
+            },
121
+        ];
122
+
123
+        // Masukkan ke DB
124
+        for (const hospital of hospitals) {
125
+            await prisma.hospital.create({ data: hospital });
126
+        }
40 127
 
41
-    console.log('✅ Hospital seeder done!');
42
-};
128
+        console.log('✅ Hospital seeded!');
129
+    } catch (err) {
130
+        console.error('❌ Error seeding hospital:', err.message);
131
+    }
132
+}
43 133
 
44
-// Fungsi ambil 1 user id random dari tabel user
45
-async function getRandomUserId() {
46
-    const users = await prisma.user.findMany({ where: { deletedAt: null } });
47
-    if (users.length === 0) throw new Error('No users found for seeding hospital.');
48
-    return users[Math.floor(Math.random() * users.length)].id;
49
-}
134
+module.exports = { seedHospitals };

+ 41 - 0
prisma/seeders/UserAreaSeeder.js

@@ -0,0 +1,41 @@
1
+const prisma = require('../../src/prisma/PrismaClient.js');
2
+
3
+async function seedUserAreas() {
4
+    try {
5
+        // Ambil user dengan role 'sales1' dan 'sales2'
6
+        const sales1 = await prisma.user.findFirst({ where: { username: 'sales1' } });
7
+        const sales2 = await prisma.user.findFirst({ where: { username: 'sales2' } });
8
+
9
+        if (!sales1 || !sales2) {
10
+            throw new Error('User sales1 or sales2 not found');
11
+        }
12
+
13
+        // Ambil provinsi
14
+        const jawaTengah = await prisma.province.findFirst({ where: { name: 'Jawa Tengah' } });
15
+        const diy = await prisma.province.findFirst({ where: { name: 'DI Yogyakarta' } });
16
+
17
+        if (!jawaTengah || !diy) {
18
+            throw new Error('Province Jawa Tengah or DI Yogyakarta not found');
19
+        }
20
+
21
+        // Buat user_areas
22
+        await prisma.userArea.createMany({
23
+            data: [
24
+                {
25
+                    user_id: sales1.id,
26
+                    province_id: jawaTengah.id,
27
+                },
28
+                {
29
+                    user_id: sales2.id,
30
+                    province_id: diy.id,
31
+                },
32
+            ],
33
+        });
34
+
35
+        console.log('✅ User area seeded');
36
+    } catch (error) {
37
+        console.error('❌ Error seeding user area:', error);
38
+    }
39
+}
40
+
41
+module.exports = seedUserAreas;

+ 141 - 44
prisma/seeders/UserSeeder.js

@@ -1,51 +1,148 @@
1
-const { KeycloakRepository, UserRepository, UserAreaRepository } = require('../../src/repository/admin/SalesRepository.js');
2
-const prisma = require('../../src/prisma/PrismaClient');
3
-const { faker } = require('@faker-js/faker');
4
-
5
-exports.seedSales = async () => {
6
-    try {
7
-        const provinces = await prisma.province.findMany();
8
-        if (provinces.length === 0) throw new Error('No provinces found');
9
-
10
-        const users = [
11
-            {
12
-                username: 'sales1',
13
-                email: 'sales1@example.com',
14
-                firstname: 'Sales',
15
-                lastname: 'One',
16
-                password: 'Sales@123',
17
-            },
18
-            {
19
-                username: 'sales2',
20
-                email: 'sales2@example.com',
21
-                firstname: 'Sales',
22
-                lastname: 'Two',
23
-                password: 'Sales@123',
24
-            },
25
-            {
26
-                username: 'sales3',
27
-                email: 'sales3@example.com',
28
-                firstname: 'Sales',
29
-                lastname: 'Three',
30
-                password: 'Sales@123',
31
-            },
32
-        ];
1
+const axios = require('axios');
2
+const qs = require('qs');
3
+const bcrypt = require('bcrypt');
4
+const prisma = require('../../src/prisma/PrismaClient.js');
5
+const {
6
+    KEYCLOAK_TOKEN_URL,
7
+    KEYCLOAK_ADMIN_URL,
8
+    KEYCLOAK_REALM,
9
+    CLIENT_ID,
10
+    CLIENT_SECRET,
11
+} = require('../../config/keycloak.js');
33 12
 
34
-        for (const user of users) {
35
-            const shuffled = provinces.sort(() => 0.5 - Math.random());
36
-            const selectedProvinces = shuffled.slice(0, Math.floor(Math.random() * 3) + 1);
37
-            const province_ids = selectedProvinces.map(p => p.id);
13
+// Ambil token admin Keycloak
14
+const getAdminToken = async () => {
15
+    const tokenParams = qs.stringify({
16
+        grant_type: 'client_credentials',
17
+        client_id: CLIENT_ID,
18
+        client_secret: CLIENT_SECRET,
19
+    });
38 20
 
39
-            user.province_ids = province_ids;
21
+    const { data } = await axios.post(KEYCLOAK_TOKEN_URL, tokenParams, {
22
+        headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
23
+    });
40 24
 
41
-            const userId = await KeycloakRepository.createUser(user);
42
-            await KeycloakRepository.assignSalesRole(userId);
43
-            await UserRepository.createUser(userId, user);
44
-            await UserAreaRepository.createMany(userId, province_ids);
25
+    return data.access_token;
26
+};
27
+
28
+// Buat user di Keycloak
29
+const createUserInKeycloak = async (user, token) => {
30
+    const checkUser = await axios.get(
31
+        `${KEYCLOAK_ADMIN_URL}/admin/realms/${KEYCLOAK_REALM}/users?username=${user.username}`,
32
+        { headers: { Authorization: `Bearer ${token}` } }
33
+    );
34
+
35
+    if (checkUser.data.length > 0) {
36
+        console.log(`⚠️  User ${user.username} found in Keycloak`);
37
+        return checkUser.data[0].id;
38
+    }
39
+
40
+    await axios.post(
41
+        `${KEYCLOAK_ADMIN_URL}/admin/realms/${KEYCLOAK_REALM}/users`,
42
+        {
43
+            username: user.username,
44
+            email: user.email,
45
+            firstName: user.firstname,
46
+            lastName: user.lastname,
47
+            enabled: true,
48
+            credentials: [
49
+                {
50
+                    type: 'password',
51
+                    value: user.password,
52
+                    temporary: false,
53
+                },
54
+            ],
55
+        },
56
+        {
57
+            headers: { Authorization: `Bearer ${token}` },
58
+        }
59
+    );
60
+
61
+    const { data } = await axios.get(
62
+        `${KEYCLOAK_ADMIN_URL}/admin/realms/${KEYCLOAK_REALM}/users?username=${user.username}`,
63
+        {
64
+            headers: { Authorization: `Bearer ${token}` },
65
+        }
66
+    );
67
+
68
+    return data[0].id;
69
+};
45 70
 
46
-            console.log(`✅ Seeded user ${user.username}`);
71
+// Assign role ke user di Keycloak
72
+const assignRole = async (userId, roleName, token) => {
73
+    const { data: roles } = await axios.get(
74
+        `${KEYCLOAK_ADMIN_URL}/admin/realms/${KEYCLOAK_REALM}/roles`,
75
+        {
76
+            headers: { Authorization: `Bearer ${token}` },
47 77
         }
48
-    } catch (err) {
49
-        console.error('❌ Failed to seed sales:', err.message);
78
+    );
79
+
80
+    const role = roles.find((r) => r.name === roleName);
81
+    if (!role) throw new Error(`Role "${roleName}" not found in Keycloak`);
82
+
83
+    await axios.post(
84
+        `${KEYCLOAK_ADMIN_URL}/admin/realms/${KEYCLOAK_REALM}/users/${userId}/role-mappings/realm`,
85
+        [role],
86
+        {
87
+            headers: { Authorization: `Bearer ${token}` },
88
+        }
89
+    );
90
+};
91
+
92
+// Data user yang ingin dibuat
93
+const users = [
94
+    {
95
+        username: 'admin1',
96
+        email: 'admin1@gmail.com',
97
+        password: 'password123',
98
+        firstname: 'Admin',
99
+        lastname: 'Satu',
100
+        role: 'admin',
101
+    },
102
+    {
103
+        username: 'sales1',
104
+        email: 'sales1@gmail.com',
105
+        password: 'password123',
106
+        firstname: 'Sales',
107
+        lastname: 'Satu',
108
+        role: 'sales',
109
+    },
110
+    {
111
+        username: 'sales2',
112
+        email: 'sales2@gmail.com',
113
+        password: 'password123',
114
+        firstname: 'Sales',
115
+        lastname: 'Dua',
116
+        role: 'sales',
117
+    },
118
+];
119
+
120
+// Fungsi utama yang bisa dipanggil dari seeder utama
121
+const seedUsers = async () => {
122
+    const token = await getAdminToken();
123
+
124
+    for (const user of users) {
125
+        const userId = await createUserInKeycloak(user, token);
126
+        await assignRole(userId, user.role, token);
127
+
128
+        const hashedPassword = await bcrypt.hash(user.password, 10);
129
+
130
+        await prisma.user.upsert({
131
+            where: { id: userId },
132
+            update: {},
133
+            create: {
134
+                id: userId,
135
+                username: user.username,
136
+                email: user.email,
137
+                firstname: user.firstname,
138
+                lastname: user.lastname,
139
+                password: hashedPassword,
140
+                role: user.role,
141
+            },
142
+        });
143
+
144
+        console.log(`✅ Success seed user ${user.role}: ${user.username}`);
50 145
     }
51 146
 };
147
+
148
+module.exports = { seedUsers };

+ 57 - 0
prisma/seeders/VendorSeeder.js

@@ -0,0 +1,57 @@
1
+const prisma = require('../../src/prisma/PrismaClient.js');
2
+
3
+async function seedVendors() {
4
+    try {
5
+        // cari user role admin
6
+        const adminUser = await prisma.user.findFirst({
7
+            where: {
8
+                username: 'admin1',
9
+            },
10
+        });
11
+
12
+        if (!adminUser) {
13
+            throw new Error('User with role admin not exists');
14
+        }
15
+
16
+        // Data vendor yang akan dimasukkan
17
+        const vendors = [
18
+            {
19
+                name: 'MediSoft SIMRS',
20
+                name_pt: 'PT Medisoft Technology',
21
+                strengths: 'Antarmuka user-friendly, support lengkap untuk modul RS',
22
+                weaknesses: 'Harga relatif tinggi',
23
+                website: 'https://medisoft.co.id',
24
+            },
25
+            {
26
+                name: 'SimRS Platinum',
27
+                name_pt: 'PT Platinum Healthtech',
28
+                strengths: 'Integrasi BPJS dan telemedicine',
29
+                weaknesses: 'Customisasi sulit',
30
+                website: 'https://simrsplatinum.id',
31
+            },
32
+            {
33
+                name: 'Hospicare System',
34
+                name_pt: 'PT Hospicare Nusantara',
35
+                strengths: 'Stabil dan dokumentasi lengkap',
36
+                weaknesses: 'Kurang inovatif',
37
+                website: 'https://hospicare.co.id',
38
+            },
39
+        ];
40
+
41
+        // Insert vendor dengan created_by dari adminUser.id
42
+        for (const vendor of vendors) {
43
+            await prisma.vendor.create({
44
+                data: {
45
+                    ...vendor,
46
+                    created_by: adminUser.id,
47
+                },
48
+            });
49
+        }
50
+
51
+        console.log('✅ Vendor seeded!');
52
+    } catch (error) {
53
+        console.error('❌ Error seeding vendor:', error);
54
+    }
55
+}
56
+
57
+module.exports = { seedVendors };

+ 57 - 0
src/controllers/admin/ExecutivesHistoryController.js

@@ -0,0 +1,57 @@
1
+const executivesHistoryService = require('../../services/admin/ExecutivesHistoryService.js');
2
+const { ListResponse } = require('../../utils/ListResponse.js');
3
+const { PaginationParam } = require('../../utils/PaginationParams.js');
4
+const { errorResponse, successResponse, messageSuccessResponse } = require('../../utils/Response.js');
5
+const { validateStoreExecutivesHistoryRequest } = require('../../validators/sales/executives_history/ExecutivesHistoriValidators.js');
6
+
7
+exports.getAllExecutivesHistory = async (req, res) => {
8
+    try {
9
+        const { page, limit, search, sortBy, orderBy } = PaginationParam(req);
10
+
11
+        const { vendor_histories, total } = await executivesHistoryService.getAllExecutivesHistoryService({
12
+            page, limit, search, sortBy, orderBy
13
+        }, req);
14
+
15
+        return ListResponse({ req, res, data: vendor_histories, page, limit, total, message: 'Executives history successfully retrieved' });
16
+    } catch (err) {
17
+        return errorResponse(res, err);
18
+    }
19
+};
20
+
21
+exports.showExecutivesHistory = async (req, res) => {
22
+    try {
23
+        const data = await executivesHistoryService.showExecutivesHistoryService(req);
24
+        return successResponse(res, data, 'Success show executives history');
25
+    } catch (err) {
26
+        return errorResponse(res, err);
27
+    }
28
+};
29
+
30
+exports.storeExecutivesHistory = async (req, res) => {
31
+    try {
32
+        const validatedData = validateStoreExecutivesHistoryRequest(req.body);
33
+        await executivesHistoryService.storeExecutivesHistoryService(validatedData, req);
34
+        return messageSuccessResponse(res, 'Success added executives history', 201);
35
+    } catch (err) {
36
+        return errorResponse(res, err);
37
+    }
38
+}
39
+
40
+exports.updateExecutivesHistory = async (req, res) => {
41
+    try {
42
+        const validatedData = validateStoreExecutivesHistoryRequest(req.body);
43
+        await executivesHistoryService.updateExecutivesHistoryService(validatedData, req);
44
+        return messageSuccessResponse(res, 'Success update executives history');
45
+    } catch (err) {
46
+        return errorResponse(res, err);
47
+    }
48
+}
49
+
50
+exports.deleteExecutivesHistory = async (req, res) => {
51
+    try {
52
+        await executivesHistoryService.deleteExecutivesHistoryService(req);
53
+        return messageSuccessResponse(res, 'Success delete executives history');
54
+    } catch (err) {
55
+        return errorResponse(res, err);
56
+    }
57
+};

+ 2 - 2
src/controllers/admin/HospitalController.js

@@ -6,10 +6,10 @@ const { validateStoreHospitalRequest, validateUpdateHospitalRequest } = require(
6 6
 
7 7
 exports.getAllHospital = async (req, res) => {
8 8
     try {
9
-        const { page, limit, search, sortBy, orderBy } = PaginationParam(req);
9
+        const { page, limit, search, sortBy, orderBy, province, city, type, ownership, progress_status } = PaginationParam(req, ['province', 'city', 'type', 'ownership', 'progress_status']);
10 10
 
11 11
         const { hospitals, total } = await hospitalService.getAllHospitalService({
12
-            page, limit, search, sortBy, orderBy
12
+            page, limit, search, sortBy, orderBy, province, city, type, ownership, progress_status
13 13
         });
14 14
 
15 15
         return ListResponse({ req, res, data: hospitals, page, limit, total, message: 'Hospital data successfully retrieved' });

+ 60 - 0
src/controllers/admin/VendorController.js

@@ -0,0 +1,60 @@
1
+const vendorService = require('../../services/admin/VendorService.js');
2
+const { ListResponse } = require('../../utils/ListResponse.js');
3
+const { PaginationParam } = require('../../utils/PaginationParams.js');
4
+const { errorResponse, successResponse, messageSuccessResponse } = require('../../utils/Response.js');
5
+const { validateStoreVendorRequest } = require('../../validators/sales/vendor/VendorValidators.js');
6
+
7
+exports.getAllVendor = async (req, res) => {
8
+    try {
9
+        const { page, limit, search, sortBy, orderBy } = PaginationParam(req);
10
+
11
+        const { vendors, total } = await vendorService.getAllVendorService({
12
+            page, limit, search, sortBy, orderBy
13
+        });
14
+
15
+        return ListResponse({ req, res, data: vendors, page, limit, total, message: 'Vendor data successfully retrieved' });
16
+    } catch (err) {
17
+        return errorResponse(res, err);
18
+    }
19
+};
20
+
21
+exports.showVendor = async (req, res) => {
22
+    try {
23
+        const id = req.params.id;
24
+        const data = await vendorService.showVendorService(id);
25
+        return successResponse(res, data, 'Success show vendor');
26
+    } catch (err) {
27
+        return errorResponse(res, err);
28
+    }
29
+};
30
+
31
+exports.storeVendor = async (req, res) => {
32
+    try {
33
+        const validatedData = validateStoreVendorRequest(req.body);
34
+        await vendorService.storeVendorService(validatedData, req);
35
+        return messageSuccessResponse(res, 'Success added vendor', 201);
36
+    } catch (err) {
37
+        return errorResponse(res, err);
38
+    }
39
+}
40
+
41
+exports.updateVendor = async (req, res) => {
42
+    try {
43
+        const id = req.params.id;
44
+        const validatedData = validateStoreVendorRequest(req.body);
45
+        await vendorService.updateVendorService(validatedData, id, req);
46
+        return messageSuccessResponse(res, 'Success update vendor');
47
+    } catch (err) {
48
+        return errorResponse(res, err);
49
+    }
50
+}
51
+
52
+exports.deleteVendor = async (req, res) => {
53
+    try {
54
+        const id = req.params.id;
55
+        await vendorService.deleteVendorService(id, req);
56
+        return messageSuccessResponse(res, 'Success delete vendor');
57
+    } catch (err) {
58
+        return errorResponse(res, err);
59
+    }
60
+};

+ 57 - 0
src/controllers/admin/VendorHistoryController.js

@@ -0,0 +1,57 @@
1
+const vendorHistoryService = require('../../services/admin/VendorHistoryService.js');
2
+const { ListResponse } = require('../../utils/ListResponse.js');
3
+const { PaginationParam } = require('../../utils/PaginationParams.js');
4
+const { errorResponse, successResponse, messageSuccessResponse } = require('../../utils/Response.js');
5
+const { validateStoreVendorHistoryRequest } = require('../../validators/sales/vendor_history/VendorHistoriValidators.js');
6
+
7
+exports.getAllVendorHistory = async (req, res) => {
8
+    try {
9
+        const { page, limit, search, sortBy, orderBy } = PaginationParam(req);
10
+
11
+        const { vendor_histories, total } = await vendorHistoryService.getAllVendorHistoryService({
12
+            page, limit, search, sortBy, orderBy
13
+        }, req);
14
+
15
+        return ListResponse({ req, res, data: vendor_histories, page, limit, total, message: 'Vendor history successfully retrieved' });
16
+    } catch (err) {
17
+        return errorResponse(res, err);
18
+    }
19
+};
20
+
21
+exports.showVendorHistory = async (req, res) => {
22
+    try {
23
+        const data = await vendorHistoryService.showVendorHistoryService(req);
24
+        return successResponse(res, data, 'Success show vendor history');
25
+    } catch (err) {
26
+        return errorResponse(res, err);
27
+    }
28
+};
29
+
30
+exports.storeVendorHistory = async (req, res) => {
31
+    try {
32
+        const validatedData = validateStoreVendorHistoryRequest(req.body);
33
+        await vendorHistoryService.storeVendorHistoryService(validatedData, req);
34
+        return messageSuccessResponse(res, 'Success added vendor history', 201);
35
+    } catch (err) {
36
+        return errorResponse(res, err);
37
+    }
38
+}
39
+
40
+exports.updateVendorHistory = async (req, res) => {
41
+    try {
42
+        const validatedData = validateStoreVendorHistoryRequest(req.body);
43
+        await vendorHistoryService.updateVendorHistoryService(validatedData, req);
44
+        return messageSuccessResponse(res, 'Success update vendor history');
45
+    } catch (err) {
46
+        return errorResponse(res, err);
47
+    }
48
+}
49
+
50
+exports.deleteVendorHistory = async (req, res) => {
51
+    try {
52
+        await vendorHistoryService.deleteVendorHistoryService(req);
53
+        return messageSuccessResponse(res, 'Success delete vendor history');
54
+    } catch (err) {
55
+        return errorResponse(res, err);
56
+    }
57
+};

+ 16 - 0
src/controllers/sales/AreaController.js

@@ -0,0 +1,16 @@
1
+const { getAllAreaByUserService } = require("../../services/sales/AreaService.js");
2
+const { ListResponse } = require("../../utils/ListResponse");
3
+const { PaginationParam } = require("../../utils/PaginationParams");
4
+const { errorResponse } = require("../../utils/Response");
5
+
6
+exports.getAllAreaByUser = async (req, res) => {
7
+    try {
8
+        const { page, limit, search, sortBy, orderBy } = PaginationParam(req);
9
+
10
+        const { areas, total } = await getAllAreaByUserService({ page, limit, search, sortBy, orderBy }, req);
11
+
12
+        return ListResponse({ req, res, data: areas, page, limit, total, message: 'Area city successfully retrieved' });
13
+    } catch (err) {
14
+        return errorResponse(res, err);
15
+    }
16
+};

+ 57 - 0
src/controllers/sales/ExecutivesHistoryController.js

@@ -0,0 +1,57 @@
1
+const executivesHistoryService = require('../../services/sales/ExecutivesHistoryService.js');
2
+const { ListResponse } = require('../../utils/ListResponse.js');
3
+const { PaginationParam } = require('../../utils/PaginationParams.js');
4
+const { errorResponse, successResponse, messageSuccessResponse } = require('../../utils/Response.js');
5
+const { validateStoreExecutivesHistoryRequest } = require('../../validators/admin/executives_history/ExecutivesHistoriValidators.js');
6
+
7
+exports.getAllExecutivesHistory = async (req, res) => {
8
+    try {
9
+        const { page, limit, search, sortBy, orderBy } = PaginationParam(req);
10
+
11
+        const { vendor_histories, total } = await executivesHistoryService.getAllExecutivesHistoryService({
12
+            page, limit, search, sortBy, orderBy
13
+        }, req);
14
+
15
+        return ListResponse({ req, res, data: vendor_histories, page, limit, total, message: 'Executives history successfully retrieved' });
16
+    } catch (err) {
17
+        return errorResponse(res, err);
18
+    }
19
+};
20
+
21
+exports.showExecutivesHistory = async (req, res) => {
22
+    try {
23
+        const data = await executivesHistoryService.showExecutivesHistoryService(req);
24
+        return successResponse(res, data, 'Success show executives history');
25
+    } catch (err) {
26
+        return errorResponse(res, err);
27
+    }
28
+};
29
+
30
+exports.storeExecutivesHistory = async (req, res) => {
31
+    try {
32
+        const validatedData = validateStoreExecutivesHistoryRequest(req.body);
33
+        await executivesHistoryService.storeExecutivesHistoryService(validatedData, req);
34
+        return messageSuccessResponse(res, 'Success added executives history', 201);
35
+    } catch (err) {
36
+        return errorResponse(res, err);
37
+    }
38
+}
39
+
40
+exports.updateExecutivesHistory = async (req, res) => {
41
+    try {
42
+        const validatedData = validateStoreExecutivesHistoryRequest(req.body);
43
+        await executivesHistoryService.updateExecutivesHistoryService(validatedData, req);
44
+        return messageSuccessResponse(res, 'Success update executives history');
45
+    } catch (err) {
46
+        return errorResponse(res, err);
47
+    }
48
+}
49
+
50
+exports.deleteExecutivesHistory = async (req, res) => {
51
+    try {
52
+        await executivesHistoryService.deleteExecutivesHistoryService(req);
53
+        return messageSuccessResponse(res, 'Success delete executives history');
54
+    } catch (err) {
55
+        return errorResponse(res, err);
56
+    }
57
+};

+ 2 - 2
src/controllers/sales/HospitalController.js

@@ -6,9 +6,9 @@ const { validateStoreHospitalRequest, validateUpdateHospitalRequest } = require(
6 6
 
7 7
 exports.getAllHospitalByArea = async (req, res) => {
8 8
     try {
9
-        const { page, limit, search, sortBy, orderBy } = PaginationParam(req);
9
+        const { page, limit, search, sortBy, orderBy, province, city, type, ownership, progress_status } = PaginationParam(req, ['province', 'city', 'type', 'ownership', 'progress_status']);
10 10
 
11
-        const { hospitals, total } = await getAllHospitalByAreaService({ page, limit, search, sortBy, orderBy, }, req);
11
+        const { hospitals, total } = await getAllHospitalByAreaService({ page, limit, search, sortBy, orderBy, province, city, type, ownership, progress_status }, req);
12 12
 
13 13
         return ListResponse({ req, res, data: hospitals, page, limit, total, message: 'Hospital data successfully retrieved' });
14 14
     } catch (err) {

+ 59 - 0
src/controllers/sales/VendorController.js

@@ -0,0 +1,59 @@
1
+const vendorService = require('../../services/sales/VendorService.js');
2
+const { ListResponse } = require('../../utils/ListResponse.js');
3
+const { PaginationParam } = require('../../utils/PaginationParams.js');
4
+const { errorResponse, successResponse, messageSuccessResponse } = require('../../utils/Response.js');
5
+
6
+exports.getAllVendor = async (req, res) => {
7
+    try {
8
+        const { page, limit, search, sortBy, orderBy } = PaginationParam(req);
9
+
10
+        const { vendors, total } = await vendorService.getAllVendorService({
11
+            page, limit, search, sortBy, orderBy
12
+        }, req);
13
+
14
+        return ListResponse({ req, res, data: vendors, page, limit, total, message: 'Vendor data sales successfully retrieved' });
15
+    } catch (err) {
16
+        return errorResponse(res, err);
17
+    }
18
+};
19
+
20
+// exports.showVendor = async (req, res) => {
21
+//     try {
22
+//         const id = req.params.id;
23
+//         const data = await vendorService.showVendorService(id);
24
+//         return successResponse(res, data, 'Success show vendor');
25
+//     } catch (err) {
26
+//         return errorResponse(res, err);
27
+//     }
28
+// };
29
+
30
+// exports.storeVendor = async (req, res) => {
31
+//     try {
32
+//         const validatedData = validateStoreVendorRequest(req.body);
33
+//         await vendorService.storeVendorService(validatedData, req);
34
+//         return messageSuccessResponse(res, 'Success added vendor', 201);
35
+//     } catch (err) {
36
+//         return errorResponse(res, err);
37
+//     }
38
+// }
39
+
40
+// exports.updateVendor = async (req, res) => {
41
+//     try {
42
+//         const id = req.params.id;
43
+//         const validatedData = validateStoreVendorRequest(req.body);
44
+//         await vendorService.updateVendorService(validatedData, id, req);
45
+//         return messageSuccessResponse(res, 'Success update vendor');
46
+//     } catch (err) {
47
+//         return errorResponse(res, err);
48
+//     }
49
+// }
50
+
51
+// exports.deleteVendor = async (req, res) => {
52
+//     try {
53
+//         const id = req.params.id;
54
+//         await vendorService.deleteVendorService(id, req);
55
+//         return messageSuccessResponse(res, 'Success delete vendor');
56
+//     } catch (err) {
57
+//         return errorResponse(res, err);
58
+//     }
59
+// };

+ 57 - 0
src/controllers/sales/VendorHistoryController.js

@@ -0,0 +1,57 @@
1
+const vendorHistoryService = require('../../services/sales/VendorHistoryService.js');
2
+const { ListResponse } = require('../../utils/ListResponse.js');
3
+const { PaginationParam } = require('../../utils/PaginationParams.js');
4
+const { errorResponse, successResponse, messageSuccessResponse } = require('../../utils/Response.js');
5
+const { validateStoreVendorHistoryRequest } = require('../../validators/admin/vendor_history/VendorHistoriValidators.js');
6
+
7
+exports.getAllVendorHistory = async (req, res) => {
8
+    try {
9
+        const { page, limit, search, sortBy, orderBy } = PaginationParam(req);
10
+
11
+        const { vendor_histories, total } = await vendorHistoryService.getAllVendorHistoryService({
12
+            page, limit, search, sortBy, orderBy
13
+        }, req);
14
+
15
+        return ListResponse({ req, res, data: vendor_histories, page, limit, total, message: 'Vendor history successfully retrieved' });
16
+    } catch (err) {
17
+        return errorResponse(res, err);
18
+    }
19
+};
20
+
21
+exports.showVendorHistory = async (req, res) => {
22
+    try {
23
+        const data = await vendorHistoryService.showVendorHistoryService(req);
24
+        return successResponse(res, data, 'Success show vendor history');
25
+    } catch (err) {
26
+        return errorResponse(res, err);
27
+    }
28
+};
29
+
30
+exports.storeVendorHistory = async (req, res) => {
31
+    try {
32
+        const validatedData = validateStoreVendorHistoryRequest(req.body);
33
+        await vendorHistoryService.storeVendorHistoryService(validatedData, req);
34
+        return messageSuccessResponse(res, 'Success added vendor history', 201);
35
+    } catch (err) {
36
+        return errorResponse(res, err);
37
+    }
38
+}
39
+
40
+exports.updateVendorHistory = async (req, res) => {
41
+    try {
42
+        const validatedData = validateStoreVendorHistoryRequest(req.body);
43
+        await vendorHistoryService.updateVendorHistoryService(validatedData, req);
44
+        return messageSuccessResponse(res, 'Success update vendor history');
45
+    } catch (err) {
46
+        return errorResponse(res, err);
47
+    }
48
+}
49
+
50
+exports.deleteVendorHistory = async (req, res) => {
51
+    try {
52
+        await vendorHistoryService.deleteVendorHistoryService(req);
53
+        return messageSuccessResponse(res, 'Success delete vendor history');
54
+    } catch (err) {
55
+        return errorResponse(res, err);
56
+    }
57
+};

+ 18 - 0
src/controllers/superadmin/LogController.js

@@ -0,0 +1,18 @@
1
+const logService = require('../../services/superadmin/LogService.js');
2
+const { ListResponse } = require('../../utils/ListResponse.js');
3
+const { PaginationParam } = require('../../utils/PaginationParams.js');
4
+const { errorResponse } = require('../../utils/Response.js');
5
+
6
+exports.getAllLogs = async (req, res) => {
7
+    try {
8
+        const { page, limit, search, sortBy, orderBy } = PaginationParam(req);
9
+
10
+        const { logs, total } = await logService.getAllLogsService({
11
+            page, limit, search, sortBy, orderBy
12
+        });
13
+
14
+        return ListResponse({ req, res, data: logs, page, limit, total, message: 'Logs data successfully retrieved' });
15
+    } catch (err) {
16
+        return errorResponse(res, err);
17
+    }
18
+};

+ 124 - 0
src/repository/admin/ExecutivesHistoryRepository.js

@@ -0,0 +1,124 @@
1
+const prisma = require('../../prisma/PrismaClient.js');
2
+
3
+const ExecutivesHistoryRepository = {
4
+    findAll: async ({ skip, take, where, orderBy }) => {
5
+        return prisma.executivesHistory.findMany({
6
+            where,
7
+            skip,
8
+            take,
9
+            orderBy,
10
+            select: {
11
+                id: true,
12
+                hospital: {
13
+                    select: {
14
+                        id: true,
15
+                        name: true,
16
+                        hospital_code: true,
17
+                        type: true,
18
+                        ownership: true,
19
+                        province: {
20
+                            select: {
21
+                                id: true,
22
+                                name: true
23
+                            }
24
+                        },
25
+                        city: {
26
+                            select: {
27
+                                id: true,
28
+                                name: true
29
+                            }
30
+                        },
31
+                        address: true,
32
+                        simrs_type: true,
33
+                        contact: true,
34
+                        image: true,
35
+                        progress_status: true,
36
+                        note: true,
37
+                        user: {
38
+                            select: {
39
+                                id: true,
40
+                                username: true
41
+                            }
42
+                        }
43
+                    }
44
+                },
45
+                executive_name: true,
46
+                contact: true,
47
+                status: true,
48
+                start_term: true,
49
+                end_term: true,
50
+                createdAt: true,
51
+                updatedAt: true,
52
+            },
53
+        });
54
+    },
55
+
56
+    countAll: async (where) => {
57
+        return prisma.executivesHistory.count({ where });
58
+    },
59
+
60
+    findById: async (id) => {
61
+        return prisma.executivesHistory.findFirst({
62
+            where: {
63
+                id,
64
+                deletedAt: null
65
+            },
66
+            select: {
67
+                id: true,
68
+                hospital: {
69
+                    select: {
70
+                        id: true,
71
+                        name: true,
72
+                        hospital_code: true,
73
+                        type: true,
74
+                        ownership: true,
75
+                        province: {
76
+                            select: {
77
+                                id: true,
78
+                                name: true
79
+                            }
80
+                        },
81
+                        city: {
82
+                            select: {
83
+                                id: true,
84
+                                name: true
85
+                            }
86
+                        },
87
+                        address: true,
88
+                        simrs_type: true,
89
+                        contact: true,
90
+                        image: true,
91
+                        progress_status: true,
92
+                        note: true,
93
+                        user: {
94
+                            select: {
95
+                                id: true,
96
+                                username: true
97
+                            }
98
+                        }
99
+                    }
100
+                },
101
+                executive_name: true,
102
+                contact: true,
103
+                status: true,
104
+                start_term: true,
105
+                end_term: true,
106
+                createdAt: true,
107
+                updatedAt: true,
108
+            },
109
+        });
110
+    },
111
+
112
+    create: async (data) => {
113
+        return prisma.executivesHistory.create({ data });
114
+    },
115
+
116
+    update: async (id, data) => {
117
+        return prisma.executivesHistory.update({
118
+            where: { id },
119
+            data
120
+        });
121
+    },
122
+};
123
+
124
+module.exports = ExecutivesHistoryRepository;

+ 33 - 1
src/repository/admin/HospitalRepository.js

@@ -24,6 +24,22 @@ const HospitalRepository = {
24 24
                 user: { select: { id: true, username: true } },
25 25
                 createdAt: true,
26 26
                 updatedAt: true,
27
+                // vendor_histories: {
28
+                //     where: { deletedAt: null },
29
+                //     orderBy: { createdAt: 'desc' },
30
+                //     select: {
31
+                //         vendor: {
32
+                //             select: {
33
+                //                 id: true,
34
+                //                 name: true,
35
+                //                 name_pt: true,
36
+                //                 strengths: true,
37
+                //                 weaknesses: true,
38
+                //                 website: true,
39
+                //             }
40
+                //         }
41
+                //     }
42
+                // }
27 43
             }
28 44
         });
29 45
     },
@@ -55,6 +71,22 @@ const HospitalRepository = {
55 71
                 user: { select: { id: true, username: true } },
56 72
                 createdAt: true,
57 73
                 updatedAt: true,
74
+                // vendor_histories: {
75
+                //     where: { deletedAt: null },
76
+                //     orderBy: { createdAt: 'desc' },
77
+                //     select: {
78
+                //         vendor: {
79
+                //             select: {
80
+                //                 id: true,
81
+                //                 name: true,
82
+                //                 name_pt: true,
83
+                //                 strengths: true,
84
+                //                 weaknesses: true,
85
+                //                 website: true,
86
+                //             }
87
+                //         }
88
+                //     }
89
+                // }
58 90
             }
59 91
         });
60 92
     },
@@ -74,7 +106,7 @@ const HospitalRepository = {
74 106
                 province: { connect: { id: data.province_id } },
75 107
                 city: { connect: { id: data.city_id } },
76 108
                 address: data.address,
77
-                simrs_type: data.simrs_type,
109
+                // simrs_type: data.simrs_type,
78 110
                 contact: data.contact,
79 111
                 note: data.note,
80 112
                 image: data.image,

+ 154 - 0
src/repository/admin/VendorHistoryRepository.js

@@ -0,0 +1,154 @@
1
+const prisma = require('../../prisma/PrismaClient.js');
2
+
3
+const VendorHistoryRepository = {
4
+    findAll: async ({ skip, take, where, orderBy }) => {
5
+        return prisma.vendorHistory.findMany({
6
+            where,
7
+            skip,
8
+            take,
9
+            orderBy,
10
+            select: {
11
+                id: true,
12
+                hospital: {
13
+                    select: {
14
+                        id: true,
15
+                        name: true,
16
+                        hospital_code: true,
17
+                        type: true,
18
+                        ownership: true,
19
+                        province: {
20
+                            select: {
21
+                                id: true,
22
+                                name: true
23
+                            }
24
+                        },
25
+                        city: {
26
+                            select: {
27
+                                id: true,
28
+                                name: true
29
+                            }
30
+                        },
31
+                        address: true,
32
+                        simrs_type: true,
33
+                        contact: true,
34
+                        image: true,
35
+                        progress_status: true,
36
+                        note: true,
37
+                        user: {
38
+                            select: {
39
+                                id: true,
40
+                                username: true
41
+                            }
42
+                        }
43
+                    }
44
+                },
45
+                vendor: {
46
+                    select: {
47
+                        id: true,
48
+                        name: true,
49
+                        name_pt: true,
50
+                        strengths: true,
51
+                        weaknesses: true,
52
+                        website: true,
53
+                        user: {
54
+                            select: {
55
+                                id: true,
56
+                                username: true
57
+                            }
58
+                        }
59
+                    }
60
+                },
61
+                vendor_impression: true,
62
+                status: true,
63
+                contract_date: true,
64
+                contract_expired_date: true,
65
+                createdAt: true,
66
+                updatedAt: true,
67
+            },
68
+        });
69
+    },
70
+
71
+    countAll: async (where) => {
72
+        return prisma.vendorHistory.count({ where });
73
+    },
74
+
75
+    findById: async (id) => {
76
+        return prisma.vendorHistory.findFirst({
77
+            where: {
78
+                id,
79
+                deletedAt: null
80
+            },
81
+            select: {
82
+                id: true,
83
+                hospital: {
84
+                    select: {
85
+                        id: true,
86
+                        name: true,
87
+                        hospital_code: true,
88
+                        type: true,
89
+                        ownership: true,
90
+                        province: {
91
+                            select: {
92
+                                id: true,
93
+                                name: true
94
+                            }
95
+                        },
96
+                        city: {
97
+                            select: {
98
+                                id: true,
99
+                                name: true
100
+                            }
101
+                        },
102
+                        address: true,
103
+                        simrs_type: true,
104
+                        contact: true,
105
+                        image: true,
106
+                        progress_status: true,
107
+                        note: true,
108
+                        user: {
109
+                            select: {
110
+                                id: true,
111
+                                username: true
112
+                            }
113
+                        }
114
+                    }
115
+                },
116
+                vendor: {
117
+                    select: {
118
+                        id: true,
119
+                        name: true,
120
+                        name_pt: true,
121
+                        strengths: true,
122
+                        weaknesses: true,
123
+                        website: true,
124
+                        user: {
125
+                            select: {
126
+                                id: true,
127
+                                username: true
128
+                            }
129
+                        }
130
+                    }
131
+                },
132
+                vendor_impression: true,
133
+                status: true,
134
+                contract_date: true,
135
+                contract_expired_date: true,
136
+                createdAt: true,
137
+                updatedAt: true,
138
+            },
139
+        });
140
+    },
141
+
142
+    create: async (data) => {
143
+        return prisma.vendorHistory.create({ data });
144
+    },
145
+
146
+    update: async (id, data) => {
147
+        return prisma.vendorHistory.update({
148
+            where: { id },
149
+            data
150
+        });
151
+    },
152
+};
153
+
154
+module.exports = VendorHistoryRepository;

+ 134 - 0
src/repository/admin/VendorRepository.js

@@ -0,0 +1,134 @@
1
+const prisma = require('../../prisma/PrismaClient.js');
2
+const timeLocal = require('../../utils/TimeLocal.js');
3
+
4
+const VendorRepository = {
5
+    findAll: async ({ skip, take, where, orderBy }) => {
6
+        const vendors = await prisma.vendor.findMany({
7
+            where,
8
+            skip,
9
+            take,
10
+            orderBy,
11
+            select: {
12
+                id: true,
13
+                name: true,
14
+                name_pt: true,
15
+                strengths: true,
16
+                weaknesses: true,
17
+                website: true,
18
+                user: {
19
+                    select: {
20
+                        id: true,
21
+                        username: true,
22
+                        email: true,
23
+                        firstname: true,
24
+                        lastname: true,
25
+                    }
26
+                },
27
+                _count: {
28
+                    select: {
29
+                        vendor_histories: {
30
+                            where: {
31
+                                deletedAt: null
32
+                            }
33
+                        }
34
+                    }
35
+                },
36
+                createdAt: true,
37
+                updatedAt: true,
38
+            },
39
+        });
40
+
41
+        const formattedVendors = vendors.map(v => {
42
+            const { _count, ...rest } = v;
43
+            return {
44
+                ...rest,
45
+                count_hospitals: _count.vendor_histories,
46
+            };
47
+        });
48
+
49
+        return formattedVendors;
50
+    },
51
+
52
+    countAll: async (where) => {
53
+        return prisma.vendor.count({ where });
54
+    },
55
+
56
+    findById: async (id) => {
57
+        const vendor = await prisma.vendor.findFirst({
58
+            where: {
59
+                id,
60
+                deletedAt: null
61
+            },
62
+            select: {
63
+                id: true,
64
+                name: true,
65
+                name_pt: true,
66
+                strengths: true,
67
+                weaknesses: true,
68
+                website: true,
69
+                user: {
70
+                    select: {
71
+                        id: true,
72
+                        username: true,
73
+                        email: true,
74
+                        firstname: true,
75
+                        lastname: true,
76
+                    }
77
+                },
78
+                _count: {
79
+                    select: {
80
+                        vendor_histories: {
81
+                            where: {
82
+                                deletedAt: null
83
+                            }
84
+                        }
85
+                    }
86
+                },
87
+                createdAt: true,
88
+                updatedAt: true,
89
+            },
90
+        });
91
+
92
+        const { _count, ...rest } = vendor;
93
+        return {
94
+            ...rest,
95
+            count_hospitals: _count.vendor_histories
96
+        };
97
+    },
98
+
99
+    create: async (data) => {
100
+        return prisma.vendor.create({ data });
101
+    },
102
+
103
+    update: async (id, data) => {
104
+        return prisma.vendor.update({
105
+            where: { id },
106
+            data
107
+        });
108
+    },
109
+
110
+    delete: async (id) => {
111
+        // delete vendor
112
+        const vendor = await prisma.vendor.update({
113
+            where: { id },
114
+            data: {
115
+                deletedAt: timeLocal.now().toDate()
116
+            }
117
+        });
118
+
119
+        // Unlink vendor_id di vendor_histories
120
+        await prisma.vendorHistory.updateMany({
121
+            where: {
122
+                vendor_id: id,
123
+                deletedAt: null
124
+            },
125
+            data: {
126
+                vendor_id: null
127
+            }
128
+        });
129
+
130
+        return vendor;
131
+    },
132
+};
133
+
134
+module.exports = VendorRepository;

+ 33 - 0
src/repository/sales/AreaRepository.js

@@ -0,0 +1,33 @@
1
+const prisma = require('../../prisma/PrismaClient.js');
2
+
3
+const AreaRepository = {
4
+    findAll: async ({ skip, take, where, orderBy }) => {
5
+        return await prisma.userArea.findMany({
6
+            where,
7
+            skip,
8
+            take,
9
+            orderBy,
10
+            select: {
11
+                id: true,
12
+                user: {
13
+                    select: {
14
+                        id: true,
15
+                        username: true
16
+                    }
17
+                },
18
+                province: {
19
+                    select: {
20
+                        id: true,
21
+                        name: true
22
+                    }
23
+                },
24
+            }
25
+        });
26
+    },
27
+
28
+    countAll: async (where) => {
29
+        return prisma.userArea.count({ where });
30
+    },
31
+};
32
+
33
+module.exports = AreaRepository;

+ 124 - 0
src/repository/sales/ExecutivesHistoryRepository.js

@@ -0,0 +1,124 @@
1
+const prisma = require('../../prisma/PrismaClient.js');
2
+
3
+const ExecutivesHistoryRepository = {
4
+    findAll: async ({ skip, take, where, orderBy }) => {
5
+        return prisma.executivesHistory.findMany({
6
+            where,
7
+            skip,
8
+            take,
9
+            orderBy,
10
+            select: {
11
+                id: true,
12
+                hospital: {
13
+                    select: {
14
+                        id: true,
15
+                        name: true,
16
+                        hospital_code: true,
17
+                        type: true,
18
+                        ownership: true,
19
+                        province: {
20
+                            select: {
21
+                                id: true,
22
+                                name: true
23
+                            }
24
+                        },
25
+                        city: {
26
+                            select: {
27
+                                id: true,
28
+                                name: true
29
+                            }
30
+                        },
31
+                        address: true,
32
+                        simrs_type: true,
33
+                        contact: true,
34
+                        image: true,
35
+                        progress_status: true,
36
+                        note: true,
37
+                        user: {
38
+                            select: {
39
+                                id: true,
40
+                                username: true
41
+                            }
42
+                        }
43
+                    }
44
+                },
45
+                executive_name: true,
46
+                contact: true,
47
+                status: true,
48
+                start_term: true,
49
+                end_term: true,
50
+                createdAt: true,
51
+                updatedAt: true,
52
+            },
53
+        });
54
+    },
55
+
56
+    countAll: async (where) => {
57
+        return prisma.executivesHistory.count({ where });
58
+    },
59
+
60
+    findById: async (id) => {
61
+        return prisma.executivesHistory.findFirst({
62
+            where: {
63
+                id,
64
+                deletedAt: null
65
+            },
66
+            select: {
67
+                id: true,
68
+                hospital: {
69
+                    select: {
70
+                        id: true,
71
+                        name: true,
72
+                        hospital_code: true,
73
+                        type: true,
74
+                        ownership: true,
75
+                        province: {
76
+                            select: {
77
+                                id: true,
78
+                                name: true
79
+                            }
80
+                        },
81
+                        city: {
82
+                            select: {
83
+                                id: true,
84
+                                name: true
85
+                            }
86
+                        },
87
+                        address: true,
88
+                        simrs_type: true,
89
+                        contact: true,
90
+                        image: true,
91
+                        progress_status: true,
92
+                        note: true,
93
+                        user: {
94
+                            select: {
95
+                                id: true,
96
+                                username: true
97
+                            }
98
+                        }
99
+                    }
100
+                },
101
+                executive_name: true,
102
+                contact: true,
103
+                status: true,
104
+                start_term: true,
105
+                end_term: true,
106
+                createdAt: true,
107
+                updatedAt: true,
108
+            },
109
+        });
110
+    },
111
+
112
+    create: async (data) => {
113
+        return prisma.executivesHistory.create({ data });
114
+    },
115
+
116
+    update: async (id, data) => {
117
+        return prisma.executivesHistory.update({
118
+            where: { id },
119
+            data
120
+        });
121
+    },
122
+};
123
+
124
+module.exports = ExecutivesHistoryRepository;

+ 1 - 1
src/repository/sales/HospitalRepository.js

@@ -74,7 +74,7 @@ const HospitalRepository = {
74 74
                 province: { connect: { id: data.province_id } },
75 75
                 city: { connect: { id: data.city_id } },
76 76
                 address: data.address,
77
-                simrs_type: data.simrs_type,
77
+                // simrs_type: data.simrs_type,
78 78
                 contact: data.contact,
79 79
                 note: data.note,
80 80
                 image: data.image,

+ 154 - 0
src/repository/sales/VendorHistoryRepository.js

@@ -0,0 +1,154 @@
1
+const prisma = require('../../prisma/PrismaClient.js');
2
+
3
+const VendorHistoryRepository = {
4
+    findAll: async ({ skip, take, where, orderBy }) => {
5
+        return prisma.vendorHistory.findMany({
6
+            where,
7
+            skip,
8
+            take,
9
+            orderBy,
10
+            select: {
11
+                id: true,
12
+                hospital: {
13
+                    select: {
14
+                        id: true,
15
+                        name: true,
16
+                        hospital_code: true,
17
+                        type: true,
18
+                        ownership: true,
19
+                        province: {
20
+                            select: {
21
+                                id: true,
22
+                                name: true
23
+                            }
24
+                        },
25
+                        city: {
26
+                            select: {
27
+                                id: true,
28
+                                name: true
29
+                            }
30
+                        },
31
+                        address: true,
32
+                        simrs_type: true,
33
+                        contact: true,
34
+                        image: true,
35
+                        progress_status: true,
36
+                        note: true,
37
+                        user: {
38
+                            select: {
39
+                                id: true,
40
+                                username: true
41
+                            }
42
+                        }
43
+                    }
44
+                },
45
+                vendor: {
46
+                    select: {
47
+                        id: true,
48
+                        name: true,
49
+                        name_pt: true,
50
+                        strengths: true,
51
+                        weaknesses: true,
52
+                        website: true,
53
+                        user: {
54
+                            select: {
55
+                                id: true,
56
+                                username: true
57
+                            }
58
+                        }
59
+                    }
60
+                },
61
+                vendor_impression: true,
62
+                status: true,
63
+                contract_date: true,
64
+                contract_expired_date: true,
65
+                createdAt: true,
66
+                updatedAt: true,
67
+            },
68
+        });
69
+    },
70
+
71
+    countAll: async (where) => {
72
+        return prisma.vendorHistory.count({ where });
73
+    },
74
+
75
+    findById: async (id) => {
76
+        return prisma.vendorHistory.findFirst({
77
+            where: {
78
+                id,
79
+                deletedAt: null
80
+            },
81
+            select: {
82
+                id: true,
83
+                hospital: {
84
+                    select: {
85
+                        id: true,
86
+                        name: true,
87
+                        hospital_code: true,
88
+                        type: true,
89
+                        ownership: true,
90
+                        province: {
91
+                            select: {
92
+                                id: true,
93
+                                name: true
94
+                            }
95
+                        },
96
+                        city: {
97
+                            select: {
98
+                                id: true,
99
+                                name: true
100
+                            }
101
+                        },
102
+                        address: true,
103
+                        simrs_type: true,
104
+                        contact: true,
105
+                        image: true,
106
+                        progress_status: true,
107
+                        note: true,
108
+                        user: {
109
+                            select: {
110
+                                id: true,
111
+                                username: true
112
+                            }
113
+                        }
114
+                    }
115
+                },
116
+                vendor: {
117
+                    select: {
118
+                        id: true,
119
+                        name: true,
120
+                        name_pt: true,
121
+                        strengths: true,
122
+                        weaknesses: true,
123
+                        website: true,
124
+                        user: {
125
+                            select: {
126
+                                id: true,
127
+                                username: true
128
+                            }
129
+                        }
130
+                    }
131
+                },
132
+                vendor_impression: true,
133
+                status: true,
134
+                contract_date: true,
135
+                contract_expired_date: true,
136
+                createdAt: true,
137
+                updatedAt: true,
138
+            },
139
+        });
140
+    },
141
+
142
+    create: async (data) => {
143
+        return prisma.vendorHistory.create({ data });
144
+    },
145
+
146
+    update: async (id, data) => {
147
+        return prisma.vendorHistory.update({
148
+            where: { id },
149
+            data
150
+        });
151
+    },
152
+};
153
+
154
+module.exports = VendorHistoryRepository;

+ 117 - 0
src/repository/sales/VendorRepository.js

@@ -0,0 +1,117 @@
1
+const prisma = require('../../prisma/PrismaClient.js');
2
+const { formatDateOnly } = require('../../utils/FormatDate.js');
3
+
4
+const VendorRepository = {
5
+    findAll: async ({ skip, take, where, orderBy }) => {
6
+        const rawVendors = await prisma.vendor.findMany({
7
+            where,
8
+            skip,
9
+            take,
10
+            orderBy,
11
+            select: {
12
+                id: true,
13
+                name: true,
14
+                name_pt: true,
15
+                strengths: true,
16
+                weaknesses: true,
17
+                website: true,
18
+                user: {
19
+                    select: {
20
+                        id: true,
21
+                        username: true,
22
+                        email: true,
23
+                        firstname: true,
24
+                        lastname: true,
25
+                    }
26
+                },
27
+                createdAt: true,
28
+                updatedAt: true,
29
+                vendor_histories: {
30
+                    where: {
31
+                        deletedAt: null,
32
+                        status: 'active',
33
+                        hospital: {
34
+                            deletedAt: null
35
+                        }
36
+                    },
37
+                    select: {
38
+                        vendor_impression: true,
39
+                        contract_date: true,
40
+                        contract_expired_date: true,
41
+                        hospital: {
42
+                            select: {
43
+                                id: true,
44
+                                name: true,
45
+                                hospital_code: true,
46
+                                address: true,
47
+                                city: {
48
+                                    select: { name: true }
49
+                                },
50
+                                province: {
51
+                                    select: { name: true }
52
+                                }
53
+                            }
54
+                        }
55
+                    }
56
+                }
57
+            },
58
+        });
59
+
60
+        // Format tanggal di dalam vendor_histories
61
+        const vendors = rawVendors.map(vendor => ({
62
+            ...vendor,
63
+            vendor_histories: vendor.vendor_histories.map(vh => ({
64
+                ...vh,
65
+                contract_date: formatDateOnly(vh.contract_date),
66
+                contract_expired_date: formatDateOnly(vh.contract_expired_date),
67
+            }))
68
+        }));
69
+
70
+        return vendors;
71
+    },
72
+
73
+    countAll: async (where) => {
74
+        return prisma.vendor.count({ where });
75
+    }
76
+
77
+    // findById: async (id) => {
78
+    //     return prisma.vendor.findFirst({
79
+    //         where: {
80
+    //             id,
81
+    //             deletedAt: null
82
+    //         },
83
+    //         select: {
84
+    //             id: true,
85
+    //             name: true,
86
+    //             name_pt: true,
87
+    //             strengths: true,
88
+    //             weaknesses: true,
89
+    //             website: true,
90
+    //             user: {
91
+    //                 select: {
92
+    //                     id: true,
93
+    //                     username: true,
94
+    //                     email: true,
95
+    //                     firstname: true,
96
+    //                     lastname: true,
97
+    //                 }
98
+    //             },
99
+    //             createdAt: true,
100
+    //             updatedAt: true,
101
+    //         },
102
+    //     });
103
+    // },
104
+
105
+    // create: async (data) => {
106
+    //     return prisma.vendor.create({ data });
107
+    // },
108
+
109
+    // update: async (id, data) => {
110
+    //     return prisma.vendor.update({
111
+    //         where: { id },
112
+    //         data
113
+    //     });
114
+    // },
115
+};
116
+
117
+module.exports = VendorRepository;

+ 24 - 0
src/repository/superadmin/LogRepository.js

@@ -0,0 +1,24 @@
1
+const prisma = require('../../prisma/PrismaClient.js');
2
+
3
+const LogRepository = {
4
+    findAll: async ({ skip, take, where, orderBy }) => {
5
+        return prisma.activityLog.findMany({
6
+            where,
7
+            skip,
8
+            take,
9
+            orderBy,
10
+            select: {
11
+                id: true,
12
+                username: true,
13
+                action: true,
14
+                createdAt: true,
15
+            },
16
+        });
17
+    },
18
+
19
+    countAll: async (where) => {
20
+        return prisma.activityLog.count({ where });
21
+    },
22
+}
23
+
24
+module.exports = LogRepository;

+ 2 - 2
src/routes/admin/CityRoute.js

@@ -4,9 +4,9 @@ const cityController = require('../../controllers/admin/CityController.js')
4 4
 const verifyJWT = require('../../middleware/VerifyJWT.js');
5 5
 const checkRole = require('../../middleware/CheckRole.js');
6 6
 
7
-router.get('/', verifyJWT, checkRole(['admin']), cityController.getAllCity);
7
+router.get('/', verifyJWT, checkRole(['admin', 'sales']), cityController.getAllCity);
8 8
 router.post('/', verifyJWT, checkRole(['admin']), cityController.storeCity);
9
-router.get('/:id', verifyJWT, checkRole(['admin']), cityController.showCity);
9
+router.get('/:id', verifyJWT, checkRole(['admin', 'sales']), cityController.showCity);
10 10
 router.patch('/:id', verifyJWT, checkRole(['admin']), cityController.updateCity);
11 11
 router.delete('/:id', verifyJWT, checkRole(['admin']), cityController.deleteCity);
12 12
 

+ 16 - 0
src/routes/admin/HospitalRoute.js

@@ -1,6 +1,8 @@
1 1
 const express = require('express')
2 2
 const router = express.Router()
3 3
 const hospitalController = require('../../controllers/admin/HospitalController.js')
4
+const vendorHistoryController = require('../../controllers/admin/VendorHistoryController.js')
5
+const executivesHistoryController = require('../../controllers/admin/ExecutivesHistoryController.js')
4 6
 const verifyJWT = require('../../middleware/VerifyJWT.js');
5 7
 const checkRole = require('../../middleware/CheckRole.js');
6 8
 const upload = require('../../middleware/UploadImage.js');
@@ -11,4 +13,18 @@ router.get('/:id', verifyJWT, checkRole(['admin']), hospitalController.showHospi
11 13
 router.patch('/:id', verifyJWT, upload.single('image'), checkRole(['admin']), hospitalController.updateHospital);
12 14
 router.delete('/:id', verifyJWT, checkRole(['admin']), hospitalController.deleteHospital);
13 15
 
16
+// Vendor History
17
+router.get('/:id/vendor-history', verifyJWT, checkRole(['admin']), vendorHistoryController.getAllVendorHistory);
18
+router.post('/:id/vendor-history', verifyJWT, checkRole(['admin']), vendorHistoryController.storeVendorHistory);
19
+router.get('/:id/vendor-history/:id_vendor_history', verifyJWT, checkRole(['admin']), vendorHistoryController.showVendorHistory);
20
+router.patch('/:id/vendor-history/:id_vendor_history', verifyJWT, checkRole(['admin']), vendorHistoryController.updateVendorHistory);
21
+router.delete('/:id/vendor-history/:id_vendor_history', verifyJWT, checkRole(['admin']), vendorHistoryController.deleteVendorHistory);
22
+
23
+// Executives History
24
+router.get('/:id/executives-history', verifyJWT, checkRole(['admin']), executivesHistoryController.getAllExecutivesHistory);
25
+router.post('/:id/executives-history', verifyJWT, checkRole(['admin']), executivesHistoryController.storeExecutivesHistory);
26
+router.get('/:id/executives-history/:id_executives_history', verifyJWT, checkRole(['admin']), executivesHistoryController.showExecutivesHistory);
27
+router.patch('/:id/executives-history/:id_executives_history', verifyJWT, checkRole(['admin']), executivesHistoryController.updateExecutivesHistory);
28
+router.delete('/:id/executives-history/:id_executives_history', verifyJWT, checkRole(['admin']), executivesHistoryController.deleteExecutivesHistory);
29
+
14 30
 module.exports = router;

+ 13 - 0
src/routes/admin/VendorRoute.js

@@ -0,0 +1,13 @@
1
+const express = require('express')
2
+const router = express.Router()
3
+const vendorController = require('../../controllers/admin/VendorController.js')
4
+const verifyJWT = require('../../middleware/VerifyJWT.js');
5
+const checkRole = require('../../middleware/CheckRole.js');
6
+
7
+router.get('/', verifyJWT, checkRole(['admin', 'sales']), vendorController.getAllVendor);
8
+router.post('/', verifyJWT, checkRole(['admin', 'sales']), vendorController.storeVendor);
9
+router.get('/:id', verifyJWT, checkRole(['admin', 'sales']), vendorController.showVendor);
10
+router.patch('/:id', verifyJWT, checkRole(['admin', 'sales']), vendorController.updateVendor);
11
+router.delete('/:id', verifyJWT, checkRole(['admin']), vendorController.deleteVendor);
12
+
13
+module.exports = router;

+ 9 - 0
src/routes/sales/AreaRoute.js

@@ -0,0 +1,9 @@
1
+const express = require('express')
2
+const router = express.Router()
3
+const areaController = require('../../controllers/sales/AreaController.js')
4
+const verifyJWT = require('../../middleware/VerifyJWT.js');
5
+const checkRole = require('../../middleware/CheckRole.js');
6
+const upload = require('../../middleware/UploadImage.js');
7
+
8
+router.get('/', verifyJWT, checkRole(['sales']), areaController.getAllAreaByUser);
9
+module.exports = router;

+ 16 - 0
src/routes/sales/HospitalRoute.js

@@ -1,6 +1,8 @@
1 1
 const express = require('express')
2 2
 const router = express.Router()
3 3
 const hospitalController = require('../../controllers/sales/HospitalController.js')
4
+const vendorHistoryController = require('../../controllers/sales/VendorHistoryController.js')
5
+const executivesHistoryController = require('../../controllers/sales/ExecutivesHistoryController.js')
4 6
 const verifyJWT = require('../../middleware/VerifyJWT.js');
5 7
 const checkRole = require('../../middleware/CheckRole.js');
6 8
 const upload = require('../../middleware/UploadImage.js');
@@ -10,4 +12,18 @@ router.post('/', verifyJWT, upload.single('image'), checkRole(['sales']), hospit
10 12
 router.patch('/:id', verifyJWT, upload.single('image'), checkRole(['sales']), hospitalController.updateHospital);
11 13
 router.get('/:id', verifyJWT, checkRole(['sales']), hospitalController.showHospital);
12 14
 
15
+// Vendor History
16
+router.get('/:id/vendor-history', verifyJWT, checkRole(['sales']), vendorHistoryController.getAllVendorHistory);
17
+router.post('/:id/vendor-history', verifyJWT, checkRole(['sales']), vendorHistoryController.storeVendorHistory);
18
+router.get('/:id/vendor-history/:id_vendor_history', verifyJWT, checkRole(['sales']), vendorHistoryController.showVendorHistory);
19
+router.patch('/:id/vendor-history/:id_vendor_history', verifyJWT, checkRole(['sales']), vendorHistoryController.updateVendorHistory);
20
+router.delete('/:id/vendor-history/:id_vendor_history', verifyJWT, checkRole(['sales']), vendorHistoryController.deleteVendorHistory);
21
+
22
+// Executives History
23
+router.get('/:id/executives-history', verifyJWT, checkRole(['sales']), executivesHistoryController.getAllExecutivesHistory);
24
+router.post('/:id/executives-history', verifyJWT, checkRole(['sales']), executivesHistoryController.storeExecutivesHistory);
25
+router.get('/:id/executives-history/:id_executives_history', verifyJWT, checkRole(['sales']), executivesHistoryController.showExecutivesHistory);
26
+router.patch('/:id/executives-history/:id_executives_history', verifyJWT, checkRole(['sales']), executivesHistoryController.updateExecutivesHistory);
27
+router.delete('/:id/executives-history/:id_executives_history', verifyJWT, checkRole(['sales']), executivesHistoryController.deleteExecutivesHistory);
28
+
13 29
 module.exports = router;

+ 9 - 0
src/routes/sales/VendorRoute.js

@@ -0,0 +1,9 @@
1
+const express = require('express')
2
+const router = express.Router()
3
+const vendorController = require('../../controllers/sales/VendorController.js')
4
+const verifyJWT = require('../../middleware/VerifyJWT.js');
5
+const checkRole = require('../../middleware/CheckRole.js');
6
+
7
+router.get('/', verifyJWT, checkRole(['sales']), vendorController.getAllVendor);
8
+
9
+module.exports = router;

+ 10 - 0
src/routes/superadmin/LogRoute.js

@@ -0,0 +1,10 @@
1
+const express = require('express');
2
+const logControllers = require('../../controllers/superadmin/LogController.js');
3
+const verifyJWT = require('../../middleware/VerifyJWT.js');
4
+const checkRole = require('../../middleware/CheckRole.js');
5
+
6
+const router = express.Router();
7
+
8
+router.get('/', verifyJWT, checkRole(['superadmin']), logControllers.getAllLogs);
9
+
10
+module.exports = router;

+ 15 - 4
src/services/admin/CityService.js

@@ -1,16 +1,16 @@
1 1
 const CityRepository = require('../../repository/admin/CityRepository.js');
2 2
 const HttpException = require('../../utils/HttpException.js');
3
-const prisma = require('../../prisma/PrismaClient.js');
4 3
 const { SearchFilter } = require('../../utils/SearchFilter.js');
5 4
 const timeLocal = require('../../utils/TimeLocal.js');
6 5
 const { createLog, updateLog, deleteLog } = require('../../utils/LogActivity.js');
7 6
 const ProvinceRepository = require('../../repository/admin/ProvinceRepository.js');
7
+const { formatISOWithoutTimezone } = require('../../utils/FormatDate.js');
8 8
 
9 9
 exports.getAllCityService = async ({ page, limit, search, sortBy, orderBy }) => {
10 10
     const skip = (page - 1) * limit;
11 11
 
12 12
     const where = {
13
-        ...SearchFilter(search, ['name']),
13
+        ...SearchFilter(search, ['name', 'province.id']),
14 14
         deletedAt: null
15 15
     };
16 16
 
@@ -19,7 +19,13 @@ exports.getAllCityService = async ({ page, limit, search, sortBy, orderBy }) =>
19 19
         CityRepository.countAll(where)
20 20
     ]);
21 21
 
22
-    return { cities, total };
22
+    const formatted = cities.map(v => ({
23
+        ...v,
24
+        createdAt: formatISOWithoutTimezone(v.createdAt),
25
+        updatedAt: formatISOWithoutTimezone(v.updatedAt),
26
+    }));
27
+
28
+    return { cities: formatted, total };
23 29
 };
24 30
 
25 31
 exports.showCityService = async (id) => {
@@ -27,7 +33,12 @@ exports.showCityService = async (id) => {
27 33
     if (!city) {
28 34
         throw new HttpException("Data city not found", 404);
29 35
     }
30
-    return city;
36
+
37
+    return {
38
+        ...city,
39
+        createdAt: formatISOWithoutTimezone(city.createdAt),
40
+        updatedAt: formatISOWithoutTimezone(city.updatedAt),
41
+    };
31 42
 };
32 43
 
33 44
 exports.storeCityService = async (validateData, req) => {

+ 141 - 0
src/services/admin/ExecutivesHistoryService.js

@@ -0,0 +1,141 @@
1
+const ExecutivesHistoryRepository = require('../../repository/admin/ExecutivesHistoryRepository.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.getAllExecutivesHistoryService = 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
+        ExecutivesHistoryRepository.findAll({ skip, take: limit, where, orderBy: { [sortBy]: orderBy } }),
30
+        ExecutivesHistoryRepository.countAll(where)
31
+    ]);
32
+
33
+    const formatted = vendor_histories.map(v => ({
34
+        ...v,
35
+        start_term: formatDateOnly(v.start_term),
36
+        end_term: formatDateOnly(v.end_term),
37
+        createdAt: formatISOWithoutTimezone(v.createdAt),
38
+        updatedAt: formatISOWithoutTimezone(v.updatedAt),
39
+    }));
40
+
41
+    return { vendor_histories: formatted, total };
42
+};
43
+
44
+exports.showExecutivesHistoryService = async (req) => {
45
+    const id_hospital = req.params.id;
46
+    const id_executives_history = req.params.id_executives_history;
47
+
48
+    const hospital = await prisma.hospital.findFirst({
49
+        where: {
50
+            id: id_hospital
51
+        }
52
+    })
53
+    if (!hospital) {
54
+        throw new HttpException("Hospital not found", 404)
55
+    }
56
+
57
+    const executivesHistory = await ExecutivesHistoryRepository.findById(id_executives_history);
58
+    if (!executivesHistory) {
59
+        throw new HttpException("Executives history not found", 404);
60
+    }
61
+
62
+    return {
63
+        ...executivesHistory,
64
+        start_term: formatDateOnly(executivesHistory.start_term),
65
+        end_term: formatDateOnly(executivesHistory.end_term),
66
+        createdAt: formatISOWithoutTimezone(executivesHistory.createdAt),
67
+        updatedAt: formatISOWithoutTimezone(executivesHistory.updatedAt),
68
+    };
69
+};
70
+
71
+exports.storeExecutivesHistoryService = async (validateData, req) => {
72
+    const hospitalId = req.params.id;
73
+
74
+    const hospital = await prisma.hospital.findFirst({
75
+        where: {
76
+            id: hospitalId
77
+        }
78
+    })
79
+    if (!hospital) {
80
+        throw new HttpException("Hospital not found", 404)
81
+    }
82
+
83
+    const payload = {
84
+        ...validateData,
85
+        hospital_id: hospitalId
86
+    };
87
+
88
+    const data = await ExecutivesHistoryRepository.create(payload);
89
+    await createLog(req, data);
90
+};
91
+
92
+exports.updateExecutivesHistoryService = async (validateData, req) => {
93
+    const id_hospital = req.params.id;
94
+    const id_executives_history = req.params.id_executives_history;
95
+
96
+    const hospital = await prisma.hospital.findFirst({
97
+        where: {
98
+            id: id_hospital
99
+        }
100
+    })
101
+    if (!hospital) {
102
+        throw new HttpException("Hospital not found", 404)
103
+    }
104
+
105
+    const executivesHistory = await ExecutivesHistoryRepository.findById(id_executives_history);
106
+    if (!executivesHistory) {
107
+        throw new HttpException("Executives history not found", 404);
108
+    }
109
+
110
+    const payload = {
111
+        ...validateData
112
+    };
113
+
114
+    const data = await ExecutivesHistoryRepository.update(id_executives_history, payload);
115
+    await updateLog(req, data);
116
+};
117
+
118
+exports.deleteExecutivesHistoryService = async (req) => {
119
+    const id_hospital = req.params.id;
120
+    const id_executives_history = req.params.id_executives_history;
121
+
122
+    const hospital = await prisma.hospital.findFirst({
123
+        where: {
124
+            id: id_hospital
125
+        }
126
+    })
127
+    if (!hospital) {
128
+        throw new HttpException("Hospital not found", 404)
129
+    }
130
+
131
+    const executivesHistory = await ExecutivesHistoryRepository.findById(id_executives_history);
132
+    if (!executivesHistory) {
133
+        throw new HttpException("Executives history not found", 404);
134
+    }
135
+
136
+    const data = await ExecutivesHistoryRepository.update(id_executives_history, {
137
+        deletedAt: timeLocal.now().toDate()
138
+    });
139
+
140
+    await deleteLog(req, data);
141
+};

+ 24 - 13
src/services/admin/HospitalService.js

@@ -6,12 +6,19 @@ const { createLog, updateLog, deleteLog } = require('../../utils/LogActivity.js'
6 6
 const ProvinceRepository = require('../../repository/admin/ProvinceRepository.js');
7 7
 const CityRepository = require('../../repository/admin/CityRepository.js');
8 8
 const { BASE_URL } = require('../../../config/config.js');
9
+const prisma = require('../../prisma/PrismaClient.js');
10
+const { formatISOWithoutTimezone } = require('../../utils/FormatDate.js');
9 11
 
10
-exports.getAllHospitalService = async ({ page, limit, search, sortBy, orderBy }) => {
12
+exports.getAllHospitalService = async ({ page, limit, search, sortBy, orderBy, province, city, type, ownership, progress_status }) => {
11 13
     const skip = (page - 1) * limit;
12 14
 
13 15
     const where = {
14
-        ...SearchFilter(search, ['name',]),
16
+        ...SearchFilter(search, ['name', 'province.id', 'city.id', 'type', 'ownership', 'simrs_type']),
17
+        ...(province ? { province_id: province } : {}),
18
+        ...(city ? { city_id: city } : {}),
19
+        ...(type ? { type: type } : {}),
20
+        ...(ownership ? { ownership: ownership } : {}),
21
+        ...(progress_status ? { progress_status: progress_status } : {}),
15 22
         deletedAt: null
16 23
     };
17 24
 
@@ -20,7 +27,13 @@ exports.getAllHospitalService = async ({ page, limit, search, sortBy, orderBy })
20 27
         HospitalRepository.countAll(where)
21 28
     ]);
22 29
 
23
-    return { hospitals, total };
30
+    const formatted = hospitals.map(v => ({
31
+        ...v,
32
+        createdAt: formatISOWithoutTimezone(v.createdAt),
33
+        updatedAt: formatISOWithoutTimezone(v.updatedAt),
34
+    }));
35
+
36
+    return { hospitals: formatted, total };
24 37
 };
25 38
 
26 39
 exports.showHospitalService = async (id) => {
@@ -28,7 +41,12 @@ exports.showHospitalService = async (id) => {
28 41
     if (!hospital) {
29 42
         throw new HttpException("Data hospital not found", 404);
30 43
     }
31
-    return hospital;
44
+
45
+    return {
46
+        ...hospital,
47
+        createdAt: formatISOWithoutTimezone(hospital.createdAt),
48
+        updatedAt: formatISOWithoutTimezone(hospital.updatedAt),
49
+    };
32 50
 };
33 51
 
34 52
 exports.storeHospitalService = async (validateData, req) => {
@@ -65,6 +83,7 @@ exports.storeHospitalService = async (validateData, req) => {
65 83
         ...validateData,
66 84
         image: imagePath,
67 85
         progress_status: "cari_data",
86
+        // simrs_type: "-",
68 87
         created_by: creatorId
69 88
     };
70 89
 
@@ -75,14 +94,6 @@ exports.storeHospitalService = async (validateData, req) => {
75 94
 const validProgressStatuses = ['cari_data', 'dihubungi', 'negosiasi', 'follow_up', 'mou', 'onboarded', 'tidak_berminat'];
76 95
 
77 96
 exports.updateHospitalService = async (validateData, id, req) => {
78
-    // const hospital = await HospitalRepository.findById(id);
79
-    // if (!hospital) {
80
-    //     throw new HttpException("Data hospital not found", 404);
81
-    // }
82
-
83
-    // const data = await HospitalRepository.update(id, validateData);
84
-    // await updateLog(req, data);
85
-
86 97
     const hospital = await HospitalRepository.findById(id);
87 98
     if (!hospital) {
88 99
         throw new HttpException("Hospital data not found", 404);
@@ -126,7 +137,7 @@ exports.updateHospitalService = async (validateData, id, req) => {
126 137
     const payload = {
127 138
         ...validateData,
128 139
         image: imagePath,
129
-        updated_by: req.user.id,
140
+        // created_by: req.user.id,
130 141
     };
131 142
 
132 143
     const data = await HospitalRepository.update(id, payload);

+ 14 - 2
src/services/admin/ProvinceService.js

@@ -1,4 +1,5 @@
1 1
 const ProvinceRepository = require('../../repository/admin/ProvinceRepository.js');
2
+const { formatISOWithoutTimezone } = require('../../utils/FormatDate.js');
2 3
 const HttpException = require('../../utils/HttpException.js');
3 4
 const { createLog, updateLog, deleteLog } = require('../../utils/LogActivity.js');
4 5
 const { SearchFilter } = require('../../utils/SearchFilter.js');
@@ -17,7 +18,13 @@ exports.getAllProvinceService = async ({ page, limit, search, sortBy, orderBy })
17 18
         ProvinceRepository.countAll(where)
18 19
     ]);
19 20
 
20
-    return { provinces, total };
21
+    const formatted = provinces.map(v => ({
22
+        ...v,
23
+        createdAt: formatISOWithoutTimezone(v.createdAt),
24
+        updatedAt: formatISOWithoutTimezone(v.updatedAt),
25
+    }));
26
+
27
+    return { provinces: formatted, total };
21 28
 };
22 29
 
23 30
 exports.showProvinceService = async (id) => {
@@ -25,7 +32,12 @@ exports.showProvinceService = async (id) => {
25 32
     if (!province) {
26 33
         throw new HttpException("Data province not found", 404);
27 34
     }
28
-    return province;
35
+    
36
+    return {
37
+        ...province,
38
+        createdAt: formatISOWithoutTimezone(province.createdAt),
39
+        updatedAt: formatISOWithoutTimezone(province.updatedAt),
40
+    };
29 41
 };
30 42
 
31 43
 exports.storeProvinceService = async (validateData, req) => {

+ 78 - 3
src/services/admin/SalesService.js

@@ -2,13 +2,15 @@ const SalesRepository = require('../../repository/admin/SalesRepository.js');
2 2
 const HttpException = require('../../utils/HttpException.js');
3 3
 const { SearchFilter } = require('../../utils/SearchFilter.js');
4 4
 const timeLocal = require('../../utils/TimeLocal.js');
5
+const prisma = require('../../prisma/PrismaClient.js');
5 6
 const { createLog, updateLog, deleteLog } = require('../../utils/LogActivity.js');
7
+const { formatISOWithoutTimezone } = require('../../utils/FormatDate.js');
6 8
 
7 9
 exports.getAllSalesService = async ({ page, limit, search, sortBy, orderBy }) => {
8 10
     const skip = (page - 1) * limit;
9 11
 
10 12
     const where = {
11
-        ...SearchFilter(search, ['username', 'email', 'firstname', 'lastname']),
13
+        ...SearchFilter(search, ['username', 'user_areas.province.name'], ['user_areas']),
12 14
         deletedAt: null
13 15
     };
14 16
 
@@ -17,7 +19,13 @@ exports.getAllSalesService = async ({ page, limit, search, sortBy, orderBy }) =>
17 19
         SalesRepository.UserRepository.countAll(where)
18 20
     ]);
19 21
 
20
-    return { sales, total };
22
+    const formatted = sales.map(v => ({
23
+        ...v,
24
+        createdAt: formatISOWithoutTimezone(v.createdAt),
25
+        updatedAt: formatISOWithoutTimezone(v.updatedAt),
26
+    }));
27
+
28
+    return { sales: formatted, total };
21 29
 };
22 30
 
23 31
 exports.showSalesService = async (id) => {
@@ -25,14 +33,39 @@ exports.showSalesService = async (id) => {
25 33
     if (!sales) {
26 34
         throw new HttpException("Data sales not found", 404);
27 35
     }
28
-    return sales;
36
+    
37
+    return {
38
+        ...sales,
39
+        createdAt: formatISOWithoutTimezone(sales.createdAt),
40
+        updatedAt: formatISOWithoutTimezone(sales.updatedAt),
41
+    };
29 42
 };
30 43
 
31 44
 exports.storeSalesService = async (userData, req) => {
45
+    // Validasi province_ids
46
+    if (userData.province_ids && userData.province_ids.length > 0) {
47
+        const validProvinces = await prisma.province.findMany({
48
+            where: { id: { in: userData.province_ids } },
49
+            select: { id: true },
50
+        });
51
+
52
+        const validProvinceIds = validProvinces.map(p => p.id);
53
+        const invalidProvinceIds = userData.province_ids.filter(id => !validProvinceIds.includes(id));
54
+
55
+        if (invalidProvinceIds.length > 0) {
56
+            throw new HttpException(
57
+                `Province ID not found: ${invalidProvinceIds.join(', ')}`,
58
+                404
59
+            );
60
+        }
61
+    }
62
+
63
+    // Create user dan assign role
32 64
     const userId = await SalesRepository.KeycloakRepository.createUser(userData);
33 65
     await SalesRepository.KeycloakRepository.assignSalesRole(userId);
34 66
     const data = await SalesRepository.UserRepository.createUser(userId, userData);
35 67
 
68
+    // Insert ke user_areas
36 69
     if (userData.province_ids && userData.province_ids.length > 0) {
37 70
         await SalesRepository.UserAreaRepository.createMany(userId, userData.province_ids);
38 71
     }
@@ -41,12 +74,47 @@ exports.storeSalesService = async (userData, req) => {
41 74
 };
42 75
 
43 76
 exports.updateSalesService = async (userData, id, req) => {
77
+    // const sales = await SalesRepository.UserRepository.findById(id);
78
+    // if (!sales) throw new HttpException('Sales not found', 404);
79
+
80
+    // await SalesRepository.KeycloakRepository.updateUser(id, userData);
81
+    // const data = await SalesRepository.UserRepository.updateUser(id, userData);
82
+
83
+    // if (userData.province_ids && userData.province_ids.length > 0) {
84
+    //     await SalesRepository.UserAreaRepository.deleteByUserId(id);
85
+    //     await SalesRepository.UserAreaRepository.createMany(id, userData.province_ids);
86
+    // }
87
+
88
+    // await updateLog(req, data);
89
+
44 90
     const sales = await SalesRepository.UserRepository.findById(id);
45 91
     if (!sales) throw new HttpException('Sales not found', 404);
46 92
 
93
+    // Validasi province_ids
94
+    if (userData.province_ids && userData.province_ids.length > 0) {
95
+        const validProvinces = await prisma.province.findMany({
96
+            where: { id: { in: userData.province_ids } },
97
+            select: { id: true },
98
+        });
99
+
100
+        const validProvinceIds = validProvinces.map(p => p.id);
101
+        const invalidProvinceIds = userData.province_ids.filter(id => !validProvinceIds.includes(id));
102
+
103
+        if (invalidProvinceIds.length > 0) {
104
+            throw new HttpException(
105
+                `Province ID not found: ${invalidProvinceIds.join(', ')}`,
106
+                404
107
+            );
108
+        }
109
+    }
110
+
111
+    // Update ke Keycloak
47 112
     await SalesRepository.KeycloakRepository.updateUser(id, userData);
113
+
114
+    // Update ke DB lokal
48 115
     const data = await SalesRepository.UserRepository.updateUser(id, userData);
49 116
 
117
+    // Update relasi user_areas
50 118
     if (userData.province_ids && userData.province_ids.length > 0) {
51 119
         await SalesRepository.UserAreaRepository.deleteByUserId(id);
52 120
         await SalesRepository.UserAreaRepository.createMany(id, userData.province_ids);
@@ -64,5 +132,12 @@ exports.deleteSalesService = async (id, req) => {
64 132
     await SalesRepository.KeycloakRepository.deleteUser(id);
65 133
     const data = await SalesRepository.UserRepository.deleteUser(id, { deletedAt: timeLocal.now().toDate() });
66 134
 
135
+    await prisma.userArea.updateMany({
136
+        where: { user_id: id },
137
+        data: {
138
+            deletedAt: timeLocal.now().toDate()
139
+        }
140
+    });
141
+
67 142
     await deleteLog(req, data);
68 143
 };

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

@@ -0,0 +1,163 @@
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
+    const formatted = vendor_histories.map(v => ({
34
+        ...v,
35
+        contract_date: formatDateOnly(v.contract_date),
36
+        contract_expired_date: formatDateOnly(v.contract_expired_date),
37
+        createdAt: formatISOWithoutTimezone(v.createdAt),
38
+        updatedAt: formatISOWithoutTimezone(v.updatedAt),
39
+    }));
40
+
41
+    return { vendor_histories: formatted, total };
42
+};
43
+
44
+exports.showVendorHistoryService = async (req) => {
45
+    const id_hospital = req.params.id;
46
+    const id_vendor_history = req.params.id_vendor_history;
47
+
48
+    const hospital = await prisma.hospital.findFirst({
49
+        where: {
50
+            id: id_hospital
51
+        }
52
+    })
53
+    if (!hospital) {
54
+        throw new HttpException("Hospital not found", 404)
55
+    }
56
+
57
+    const vendorHistory = await VendorHistoryRepository.findById(id_vendor_history);
58
+    if (!vendorHistory) {
59
+        throw new HttpException("Vendor history not found", 404);
60
+    }
61
+
62
+    return {
63
+        ...vendorHistory,
64
+        contract_date: formatDateOnly(vendorHistory.contract_date),
65
+        contract_expired_date: formatDateOnly(vendorHistory.contract_expired_date),
66
+        createdAt: formatISOWithoutTimezone(vendorHistory.createdAt),
67
+        updatedAt: formatISOWithoutTimezone(vendorHistory.updatedAt),
68
+    };
69
+};
70
+
71
+exports.storeVendorHistoryService = async (validateData, req) => {
72
+    const hospitalId = req.params.id;
73
+
74
+    const hospital = await prisma.hospital.findFirst({
75
+        where: {
76
+            id: hospitalId
77
+        }
78
+    })
79
+    if (!hospital) {
80
+        throw new HttpException("Hospital not found", 404)
81
+    }
82
+
83
+    await prisma.hospital.update({
84
+        where: { id: hospitalId },
85
+        data: {
86
+            simrs_type: validateData.simrs_type
87
+        }
88
+    });
89
+
90
+    const payload = {
91
+        vendor_id: validateData.vendor_id,
92
+        vendor_impression: validateData.vendor_impression,
93
+        status: validateData.status,
94
+        contract_date: validateData.contract_date,
95
+        contract_expired_date: validateData.contract_expired_date,
96
+        hospital_id: hospitalId
97
+    };
98
+
99
+    const data = await VendorHistoryRepository.create(payload);
100
+    await createLog(req, data);
101
+};
102
+
103
+exports.updateVendorHistoryService = async (validateData, req) => {
104
+    const id_hospital = req.params.id;
105
+    const id_vendor_history = req.params.id_vendor_history;
106
+
107
+    const hospital = await prisma.hospital.findFirst({
108
+        where: {
109
+            id: id_hospital
110
+        }
111
+    })
112
+    if (!hospital) {
113
+        throw new HttpException("Hospital not found", 404)
114
+    }
115
+
116
+    const vendor = await VendorHistoryRepository.findById(id_vendor_history);
117
+    if (!vendor) {
118
+        throw new HttpException("Vendor history not found", 404);
119
+    }
120
+
121
+    await prisma.hospital.update({
122
+        where: { id: id_hospital },
123
+        data: {
124
+            simrs_type: validateData.simrs_type
125
+        }
126
+    });
127
+
128
+    const payload = {
129
+        vendor_id: validateData.vendor_id,
130
+        vendor_impression: validateData.vendor_impression,
131
+        status: validateData.status,
132
+        contract_date: validateData.contract_date,
133
+        contract_expired_date: validateData.contract_expired_date
134
+    };
135
+
136
+    const data = await VendorHistoryRepository.update(id_vendor_history, payload);
137
+    await updateLog(req, data);
138
+};
139
+
140
+exports.deleteVendorHistoryService = async (req) => {
141
+    const id_hospital = req.params.id;
142
+    const id_vendor_history = req.params.id_vendor_history;
143
+
144
+    const hospital = await prisma.hospital.findFirst({
145
+        where: {
146
+            id: id_hospital
147
+        }
148
+    })
149
+    if (!hospital) {
150
+        throw new HttpException("Hospital not found", 404)
151
+    }
152
+
153
+    const vendor = await VendorHistoryRepository.findById(id_vendor_history);
154
+    if (!vendor) {
155
+        throw new HttpException("Vendor history not found", 404);
156
+    }
157
+
158
+    const data = await VendorHistoryRepository.update(id_vendor_history, {
159
+        deletedAt: timeLocal.now().toDate()
160
+    });
161
+
162
+    await deleteLog(req, data);
163
+};

+ 105 - 0
src/services/admin/VendorService.js

@@ -0,0 +1,105 @@
1
+const VendorRepository = require('../../repository/admin/VendorRepository.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 { formatISOWithoutTimezone } = require('../../utils/FormatDate.js');
8
+
9
+exports.getAllVendorService = async ({ page, limit, search, sortBy, orderBy }) => {
10
+    const skip = (page - 1) * limit;
11
+
12
+    const where = {
13
+        ...SearchFilter(search, ['name', 'name_pt']),
14
+        deletedAt: null
15
+    };
16
+
17
+    const [vendors, total] = await Promise.all([
18
+        VendorRepository.findAll({ skip, take: limit, where, orderBy: { [sortBy]: orderBy } }),
19
+        VendorRepository.countAll(where)
20
+    ]);
21
+
22
+    const formatted = vendors.map(v => ({
23
+        ...v,
24
+        createdAt: formatISOWithoutTimezone(v.createdAt),
25
+        updatedAt: formatISOWithoutTimezone(v.updatedAt),
26
+    }));
27
+
28
+    return { vendors: formatted, total };
29
+};
30
+
31
+exports.showVendorService = async (id) => {
32
+    const vendor = await VendorRepository.findById(id);
33
+    if (!vendor) {
34
+        throw new HttpException("Data vendor not found", 404);
35
+    }
36
+    
37
+    return {
38
+        ...vendor,
39
+        createdAt: formatISOWithoutTimezone(vendor.createdAt),
40
+        updatedAt: formatISOWithoutTimezone(vendor.updatedAt),
41
+    };
42
+};
43
+
44
+exports.storeVendorService = async (validateData, req) => {
45
+    const creatorId = req.user.id;
46
+
47
+    const name_vendor = await prisma.vendor.findFirst({
48
+        where: {
49
+            name: validateData.name,
50
+            deletedAt: null
51
+        }
52
+    });
53
+
54
+    if (name_vendor) {
55
+        throw new HttpException("Vendor name must be unique", 400);
56
+    }
57
+
58
+    const payload = {
59
+        ...validateData,
60
+        created_by: creatorId
61
+    };
62
+
63
+    const data = await VendorRepository.create(payload);
64
+    await createLog(req, data);
65
+};
66
+
67
+exports.updateVendorService = async (validateData, id, req) => {
68
+    const creatorId = req.user.id;
69
+
70
+    const vendor = await VendorRepository.findById(id);
71
+    if (!vendor) {
72
+        throw new HttpException("Data vendor not found", 404);
73
+    }
74
+
75
+    const name_vendor = await prisma.vendor.findFirst({
76
+        where: {
77
+            name: validateData.name,
78
+            deletedAt: null
79
+        }
80
+    });
81
+
82
+    if (name_vendor) {
83
+        throw new HttpException("Vendor name must be unique", 400);
84
+    }
85
+
86
+    const payload = {
87
+        ...validateData,
88
+        created_by: creatorId
89
+    };
90
+
91
+    const data = await VendorRepository.update(id, payload);
92
+    await updateLog(req, data);
93
+};
94
+
95
+exports.deleteVendorService = async (id, req) => {
96
+    const vendor = await VendorRepository.findById(id);
97
+
98
+    if (!vendor) {
99
+        throw new HttpException('Vendor not found', 404);
100
+    }
101
+
102
+    const data = await VendorRepository.delete(id);
103
+
104
+    await deleteLog(req, data);
105
+};

+ 20 - 0
src/services/sales/AreaService.js

@@ -0,0 +1,20 @@
1
+const areaRepository = require('../../repository/sales/AreaRepository.js');
2
+const { SearchFilter } = require('../../utils/SearchFilter.js');
3
+const prisma = require('../../prisma/PrismaClient.js');
4
+
5
+exports.getAllAreaByUserService = async ({ page, limit, search, sortBy, orderBy }, req) => {
6
+    const skip = (page - 1) * limit;
7
+
8
+    const where = {
9
+        user_id: req.user.id,
10
+        deletedAt: null,
11
+        ...SearchFilter(search, ['province.name']),
12
+    };
13
+
14
+    const [areas, total] = await Promise.all([
15
+        areaRepository.findAll({ skip, take: limit, where, orderBy: { [sortBy]: orderBy } }),
16
+        areaRepository.countAll(where),
17
+    ]);
18
+
19
+    return { areas, total };
20
+};

+ 141 - 0
src/services/sales/ExecutivesHistoryService.js

@@ -0,0 +1,141 @@
1
+const ExecutivesHistoryRepository = require('../../repository/sales/ExecutivesHistoryRepository.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.getAllExecutivesHistoryService = 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
+        ExecutivesHistoryRepository.findAll({ skip, take: limit, where, orderBy: { [sortBy]: orderBy } }),
30
+        ExecutivesHistoryRepository.countAll(where)
31
+    ]);
32
+
33
+    const formatted = vendor_histories.map(v => ({
34
+        ...v,
35
+        start_term: formatDateOnly(v.start_term),
36
+        end_term: formatDateOnly(v.end_term),
37
+        createdAt: formatISOWithoutTimezone(v.createdAt),
38
+        updatedAt: formatISOWithoutTimezone(v.updatedAt),
39
+    }));
40
+
41
+    return { vendor_histories: formatted, total };
42
+};
43
+
44
+exports.showExecutivesHistoryService = async (req) => {
45
+    const id_hospital = req.params.id;
46
+    const id_executives_history = req.params.id_executives_history;
47
+
48
+    const hospital = await prisma.hospital.findFirst({
49
+        where: {
50
+            id: id_hospital
51
+        }
52
+    })
53
+    if (!hospital) {
54
+        throw new HttpException("Hospital not found", 404)
55
+    }
56
+
57
+    const executivesHistory = await ExecutivesHistoryRepository.findById(id_executives_history);
58
+    if (!executivesHistory) {
59
+        throw new HttpException("Executives history not found", 404);
60
+    }
61
+
62
+    return {
63
+        ...executivesHistory,
64
+        start_term: formatDateOnly(executivesHistory.start_term),
65
+        end_term: formatDateOnly(executivesHistory.end_term),
66
+        createdAt: formatISOWithoutTimezone(executivesHistory.createdAt),
67
+        updatedAt: formatISOWithoutTimezone(executivesHistory.updatedAt),
68
+    };
69
+};
70
+
71
+exports.storeExecutivesHistoryService = async (validateData, req) => {
72
+    const hospitalId = req.params.id;
73
+
74
+    const hospital = await prisma.hospital.findFirst({
75
+        where: {
76
+            id: hospitalId
77
+        }
78
+    })
79
+    if (!hospital) {
80
+        throw new HttpException("Hospital not found", 404)
81
+    }
82
+
83
+    const payload = {
84
+        ...validateData,
85
+        hospital_id: hospitalId
86
+    };
87
+
88
+    const data = await ExecutivesHistoryRepository.create(payload);
89
+    await createLog(req, data);
90
+};
91
+
92
+exports.updateExecutivesHistoryService = async (validateData, req) => {
93
+    const id_hospital = req.params.id;
94
+    const id_executives_history = req.params.id_executives_history;
95
+
96
+    const hospital = await prisma.hospital.findFirst({
97
+        where: {
98
+            id: id_hospital
99
+        }
100
+    })
101
+    if (!hospital) {
102
+        throw new HttpException("Hospital not found", 404)
103
+    }
104
+
105
+    const executivesHistory = await ExecutivesHistoryRepository.findById(id_executives_history);
106
+    if (!executivesHistory) {
107
+        throw new HttpException("Executives history not found", 404);
108
+    }
109
+
110
+    const payload = {
111
+        ...validateData
112
+    };
113
+
114
+    const data = await ExecutivesHistoryRepository.update(id_executives_history, payload);
115
+    await updateLog(req, data);
116
+};
117
+
118
+exports.deleteExecutivesHistoryService = async (req) => {
119
+    const id_hospital = req.params.id;
120
+    const id_executives_history = req.params.id_executives_history;
121
+
122
+    const hospital = await prisma.hospital.findFirst({
123
+        where: {
124
+            id: id_hospital
125
+        }
126
+    })
127
+    if (!hospital) {
128
+        throw new HttpException("Hospital not found", 404)
129
+    }
130
+
131
+    const executivesHistory = await ExecutivesHistoryRepository.findById(id_executives_history);
132
+    if (!executivesHistory) {
133
+        throw new HttpException("Executives history not found", 404);
134
+    }
135
+
136
+    const data = await ExecutivesHistoryRepository.update(id_executives_history, {
137
+        deletedAt: timeLocal.now().toDate()
138
+    });
139
+
140
+    await deleteLog(req, data);
141
+};

+ 89 - 32
src/services/sales/HospitalService.js

@@ -6,8 +6,9 @@ const ProvinceRepository = require('../../repository/admin/ProvinceRepository.js
6 6
 const CityRepository = require('../../repository/admin/CityRepository.js');
7 7
 const HttpException = require('../../utils/HttpException.js');
8 8
 const HospitalRepository = require('../../repository/admin/HospitalRepository.js');
9
+const { formatISOWithoutTimezone } = require('../../utils/FormatDate.js');
9 10
 
10
-exports.getAllHospitalByAreaService = async ({ page, limit, search, sortBy, orderBy }, req) => {
11
+exports.getAllHospitalByAreaService = async ({ page, limit, search, sortBy, orderBy, province, city, type, ownership, progress_status }, req) => {
11 12
     const skip = (page - 1) * limit;
12 13
 
13 14
     const userAreas = await prisma.userArea.findMany({
@@ -17,28 +18,14 @@ exports.getAllHospitalByAreaService = async ({ page, limit, search, sortBy, orde
17 18
 
18 19
     const provinceIds = userAreas.map(ua => ua.province_id);
19 20
 
20
-    // const where = {
21
-    //     ...SearchFilter(search, ['username', 'email', 'firstname', 'lastname']),
22
-    //     deletedAt: null
23
-    // };
24
-
25 21
     const where = {
26
-        ...SearchFilter(search, [
27
-            'name',
28
-            'hospital_code',
29
-            'type',
30
-            'ownership',
31
-            'address',
32
-            'simrs_type',
33
-            'contact',
34
-            'image',
35
-            'progress_status',
36
-            'note',
37
-            'province.name',
38
-            'city.name',
39
-            'user.username'
40
-        ]),
22
+        ...SearchFilter(search, ['name', 'province.id', 'city.id', 'type', 'ownership', 'simrs_type']),
41 23
         province_id: { in: provinceIds },
24
+        ...(province ? { province_id: province } : {}),
25
+        ...(city ? { city_id: city } : {}),
26
+        ...(type ? { type: type } : {}),
27
+        ...(ownership ? { ownership: ownership } : {}),
28
+        ...(progress_status ? { progress_status: progress_status } : {}),
42 29
         deletedAt: null
43 30
     };
44 31
 
@@ -47,9 +34,70 @@ exports.getAllHospitalByAreaService = async ({ page, limit, search, sortBy, orde
47 34
         salesHospitalRepository.countAll(where)
48 35
     ]);
49 36
 
50
-    return { hospitals, total };
37
+    const formatted = hospitals.map(v => ({
38
+        ...v,
39
+        createdAt: formatISOWithoutTimezone(v.createdAt),
40
+        updatedAt: formatISOWithoutTimezone(v.updatedAt),
41
+    }));
42
+
43
+    return { hospitals: formatted, total };
51 44
 };
52 45
 
46
+// exports.getAllHospitalByAreaService = async (
47
+//     { page, limit, search, sortBy, orderBy, province, city, type, ownership, progress_status },
48
+//     req
49
+// ) => {
50
+//     const skip = (page - 1) * limit;
51
+
52
+//     // 1. Ambil daftar provinsi yang dimiliki user
53
+//     const userAreas = await prisma.userArea.findMany({
54
+//         where: {
55
+//             user_id: req.user.id,
56
+//             deletedAt: null,
57
+//         },
58
+//         select: { province_id: true },
59
+//     });
60
+
61
+//     const areaProvinceIds = userAreas.map((ua) => ua.province_id);
62
+
63
+//     if (areaProvinceIds.length === 0) {
64
+//         return { hospitals: [], total: 0 };
65
+//     }
66
+
67
+//     // 2. Bangun filter where
68
+//     const where = {
69
+//         ...SearchFilter(search, [
70
+//             'name',
71
+//             'province.name',
72
+//             'city.name',
73
+//             'type',
74
+//             'ownership',
75
+//             'simrs_type',
76
+//         ]),
77
+//         province_id: province
78
+//             ? province // jika user filter 1 provinsi → override area
79
+//             : { in: areaProvinceIds }, // kalau tidak → gunakan semua area user
80
+//         ...(city ? { city_id: city } : {}),
81
+//         ...(type ? { type } : {}),
82
+//         ...(ownership ? { ownership } : {}),
83
+//         ...(progress_status ? { progress_status } : {}),
84
+//         deletedAt: null,
85
+//     };
86
+
87
+//     const [hospitals, total] = await Promise.all([
88
+//         salesHospitalRepository.findAll({ skip, take: limit, where, orderBy: { [sortBy]: orderBy } }),
89
+//         salesHospitalRepository.countAll(where),
90
+//     ]);
91
+
92
+//     const formatted = hospitals.map((v) => ({
93
+//         ...v,
94
+//         createdAt: formatISOWithoutTimezone(v.createdAt),
95
+//         updatedAt: formatISOWithoutTimezone(v.updatedAt),
96
+//     }));
97
+
98
+//     return { hospitals: formatted, total };
99
+// };
100
+
53 101
 exports.storeHospitalService = async (validateData, req) => {
54 102
     const creatorId = req.user.id;
55 103
 
@@ -96,6 +144,7 @@ exports.storeHospitalService = async (validateData, req) => {
96 144
         ...validateData,
97 145
         image: imagePath,
98 146
         progress_status: "cari_data",
147
+        simrs_type: "-",
99 148
         created_by: creatorId
100 149
     };
101 150
 
@@ -106,19 +155,22 @@ exports.storeHospitalService = async (validateData, req) => {
106 155
 const validProgressStatuses = ['cari_data', 'dihubungi', 'negosiasi', 'follow_up', 'mou', 'onboarded', 'tidak_berminat'];
107 156
 
108 157
 exports.updateHospitalService = async (validateData, id, req) => {
109
-    // const hospital = await HospitalRepository.findById(id);
110
-    // if (!hospital) {
111
-    //     throw new HttpException("Data hospital not found", 404);
112
-    // }
113
-
114
-    // const data = await HospitalRepository.update(id, validateData);
115
-    // await updateLog(req, data);
116
-
117 158
     const hospital = await HospitalRepository.findById(id);
118 159
     if (!hospital) {
119 160
         throw new HttpException("Hospital data not found", 404);
120 161
     }
121 162
 
163
+    const userArea = await prisma.userArea.findFirst({
164
+        where: {
165
+            user_id: req.user.id,
166
+            province_id: validateData.province_id
167
+        }
168
+    });
169
+
170
+    if (!userArea) {
171
+        throw new HttpException("You are not authorized to update hospital in this province", 403);
172
+    }
173
+
122 174
     const province = await ProvinceRepository.findById(validateData.province_id);
123 175
     if (!province) {
124 176
         throw new HttpException('Province not found', 404);
@@ -157,7 +209,7 @@ exports.updateHospitalService = async (validateData, id, req) => {
157 209
     const payload = {
158 210
         ...validateData,
159 211
         image: imagePath,
160
-        updated_by: req.user.id,
212
+        // created_by: req.user.id,
161 213
     };
162 214
 
163 215
     const data = await salesHospitalRepository.update(id, payload);
@@ -169,5 +221,10 @@ exports.showHospitalService = async (id) => {
169 221
     if (!hospital) {
170 222
         throw new HttpException("Data hospital not found", 404);
171 223
     }
172
-    return hospital;
224
+
225
+    return {
226
+        ...hospital,
227
+        createdAt: formatISOWithoutTimezone(hospital.createdAt),
228
+        updatedAt: formatISOWithoutTimezone(hospital.updatedAt),
229
+    };
173 230
 };

+ 163 - 0
src/services/sales/VendorHistoryService.js

@@ -0,0 +1,163 @@
1
+const VendorHistoryRepository = require('../../repository/sales/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
+    const formatted = vendor_histories.map(v => ({
34
+        ...v,
35
+        contract_date: formatDateOnly(v.contract_date),
36
+        contract_expired_date: formatDateOnly(v.contract_expired_date),
37
+        createdAt: formatISOWithoutTimezone(v.createdAt),
38
+        updatedAt: formatISOWithoutTimezone(v.updatedAt),
39
+    }));
40
+
41
+    return { vendor_histories: formatted, total };
42
+};
43
+
44
+exports.showVendorHistoryService = async (req) => {
45
+    const id_hospital = req.params.id;
46
+    const id_vendor_history = req.params.id_vendor_history;
47
+
48
+    const hospital = await prisma.hospital.findFirst({
49
+        where: {
50
+            id: id_hospital
51
+        }
52
+    })
53
+    if (!hospital) {
54
+        throw new HttpException("Hospital not found", 404)
55
+    }
56
+
57
+    const vendorHistory = await VendorHistoryRepository.findById(id_vendor_history);
58
+    if (!vendorHistory) {
59
+        throw new HttpException("Vendor history not found", 404);
60
+    }
61
+
62
+    return {
63
+        ...vendorHistory,
64
+        contract_date: formatDateOnly(vendorHistory.contract_date),
65
+        contract_expired_date: formatDateOnly(vendorHistory.contract_expired_date),
66
+        createdAt: formatISOWithoutTimezone(vendorHistory.createdAt),
67
+        updatedAt: formatISOWithoutTimezone(vendorHistory.updatedAt),
68
+    };
69
+};
70
+
71
+exports.storeVendorHistoryService = async (validateData, req) => {
72
+    const hospitalId = req.params.id;
73
+
74
+    const hospital = await prisma.hospital.findFirst({
75
+        where: {
76
+            id: hospitalId
77
+        }
78
+    })
79
+    if (!hospital) {
80
+        throw new HttpException("Hospital not found", 404)
81
+    }
82
+
83
+    await prisma.hospital.update({
84
+        where: { id: hospitalId },
85
+        data: {
86
+            simrs_type: validateData.simrs_type
87
+        }
88
+    });
89
+
90
+    const payload = {
91
+        vendor_id: validateData.vendor_id,
92
+        vendor_impression: validateData.vendor_impression,
93
+        status: validateData.status,
94
+        contract_date: validateData.contract_date,
95
+        contract_expired_date: validateData.contract_expired_date,
96
+        hospital_id: hospitalId
97
+    };
98
+
99
+    const data = await VendorHistoryRepository.create(payload);
100
+    await createLog(req, data);
101
+};
102
+
103
+exports.updateVendorHistoryService = async (validateData, req) => {
104
+    const id_hospital = req.params.id;
105
+    const id_vendor_history = req.params.id_vendor_history;
106
+
107
+    const hospital = await prisma.hospital.findFirst({
108
+        where: {
109
+            id: id_hospital
110
+        }
111
+    })
112
+    if (!hospital) {
113
+        throw new HttpException("Hospital not found", 404)
114
+    }
115
+
116
+    const vendor = await VendorHistoryRepository.findById(id_vendor_history);
117
+    if (!vendor) {
118
+        throw new HttpException("Vendor history not found", 404);
119
+    }
120
+
121
+    await prisma.hospital.update({
122
+        where: { id: id_hospital },
123
+        data: {
124
+            simrs_type: validateData.simrs_type
125
+        }
126
+    });
127
+
128
+    const payload = {
129
+        vendor_id: validateData.vendor_id,
130
+        vendor_impression: validateData.vendor_impression,
131
+        status: validateData.status,
132
+        contract_date: validateData.contract_date,
133
+        contract_expired_date: validateData.contract_expired_date
134
+    };
135
+
136
+    const data = await VendorHistoryRepository.update(id_vendor_history, payload);
137
+    await updateLog(req, data);
138
+};
139
+
140
+exports.deleteVendorHistoryService = async (req) => {
141
+    const id_hospital = req.params.id;
142
+    const id_vendor_history = req.params.id_vendor_history;
143
+
144
+    const hospital = await prisma.hospital.findFirst({
145
+        where: {
146
+            id: id_hospital
147
+        }
148
+    })
149
+    if (!hospital) {
150
+        throw new HttpException("Hospital not found", 404)
151
+    }
152
+
153
+    const vendor = await VendorHistoryRepository.findById(id_vendor_history);
154
+    if (!vendor) {
155
+        throw new HttpException("Vendor history not found", 404);
156
+    }
157
+
158
+    const data = await VendorHistoryRepository.update(id_vendor_history, {
159
+        deletedAt: timeLocal.now().toDate()
160
+    });
161
+
162
+    await deleteLog(req, data);
163
+};

+ 113 - 0
src/services/sales/VendorService.js

@@ -0,0 +1,113 @@
1
+const VendorRepository = require('../../repository/sales/VendorRepository.js');
2
+const HttpException = require('../../utils/HttpException.js');
3
+const prisma = require('../../prisma/PrismaClient.js');
4
+const { SearchFilter } = require('../../utils/SearchFilter.js');
5
+const { formatISOWithoutTimezone, formatDateOnly } = require('../../utils/FormatDate.js');
6
+
7
+exports.getAllVendorService = async ({ page, limit, search, sortBy, orderBy }, req) => {
8
+    const skip = (page - 1) * limit;
9
+    const userId = req.user.id;
10
+
11
+    // 1. Ambil provinsi yang menjadi area kerja sales
12
+    const userAreas = await prisma.userArea.findMany({
13
+        where: {
14
+            user_id: userId,
15
+            deletedAt: null,
16
+        },
17
+        select: { province_id: true }
18
+    });
19
+
20
+    const provinceIds = userAreas.map(area => area.province_id);
21
+
22
+    if (provinceIds.length === 0) {
23
+        return { vendors: [], total: 0 };
24
+    }
25
+
26
+    // 2. Ambil vendor_id dari vendor_histories RS di provinsi tersebut
27
+    const vendorHistories = await prisma.vendorHistory.findMany({
28
+        where: {
29
+            deletedAt: null,
30
+            status: 'active',
31
+            vendor_id: { not: null },
32
+            hospital: {
33
+                deletedAt: null,
34
+                province_id: { in: provinceIds }
35
+            }
36
+        },
37
+        select: { vendor_id: true },
38
+        distinct: ['vendor_id']
39
+    });
40
+
41
+    const vendorIds = vendorHistories.map(v => v.vendor_id);
42
+
43
+    // 3. Filter vendor berdasarkan hasil vendor_id
44
+    const where = {
45
+        ...SearchFilter(search, ['name', 'name_pt']),
46
+        deletedAt: null,
47
+        id: { in: vendorIds }
48
+    };
49
+
50
+    const [vendors, total] = await Promise.all([
51
+        VendorRepository.findAll({ skip, take: limit, where, orderBy: { [sortBy]: orderBy } }),
52
+        VendorRepository.countAll(where)
53
+    ]);
54
+
55
+    const formatted = vendors.map(v => ({
56
+        ...v,
57
+        createdAt: formatISOWithoutTimezone(v.createdAt),
58
+        updatedAt: formatISOWithoutTimezone(v.updatedAt),
59
+    }));
60
+
61
+    return { vendors: formatted, total };
62
+};
63
+
64
+// exports.showVendorService = async (id) => {
65
+//     const vendor = await VendorRepository.findById(id);
66
+//     if (!vendor) {
67
+//         throw new HttpException("Data vendor not found", 404);
68
+//     }
69
+//     return vendor;
70
+// };
71
+
72
+// exports.storeVendorService = async (validateData, req) => {
73
+//     const creatorId = req.user.id;
74
+
75
+//     const payload = {
76
+//         ...validateData,
77
+//         created_by: creatorId
78
+//     };
79
+
80
+//     const data = await VendorRepository.create(payload);
81
+//     await createLog(req, data);
82
+// };
83
+
84
+// exports.updateVendorService = async (validateData, id, req) => {
85
+//     const creatorId = req.user.id;
86
+
87
+//     const vendor = await VendorRepository.findById(id);
88
+//     if (!vendor) {
89
+//         throw new HttpException("Data vendor not found", 404);
90
+//     }
91
+
92
+//     const payload = {
93
+//         ...validateData,
94
+//         created_by: creatorId
95
+//     };
96
+
97
+//     const data = await VendorRepository.update(id, payload);
98
+//     await updateLog(req, data);
99
+// };
100
+
101
+// exports.deleteVendorService = async (id, req) => {
102
+//     const vendor = await VendorRepository.findById(id);
103
+
104
+//     if (!vendor) {
105
+//         throw new HttpException('Vendor not found', 404);
106
+//     }
107
+
108
+//     const data = await VendorRepository.update(id, {
109
+//         deletedAt: timeLocal.now().toDate()
110
+//     });
111
+
112
+//     await deleteLog(req, data);
113
+// };

+ 18 - 0
src/services/superadmin/LogService.js

@@ -0,0 +1,18 @@
1
+const LogRepository = require('../../repository/superadmin/LogRepository.js');
2
+const { SearchFilter } = require('../../utils/SearchFilter.js');
3
+
4
+exports.getAllLogsService = async ({ page, limit, search, sortBy, orderBy }) => {
5
+    const skip = (page - 1) * limit;
6
+
7
+    const where = {
8
+        ...SearchFilter(search, ['username', 'action']),
9
+        deletedAt: null
10
+    };
11
+
12
+    const [logs, total] = await Promise.all([
13
+        LogRepository.findAll({ skip, take: limit, where, orderBy: { [sortBy]: orderBy } }),
14
+        LogRepository.countAll(where)
15
+    ]);
16
+
17
+    return { logs, total };
18
+};

+ 47 - 0
src/utils/FormatDate.js

@@ -0,0 +1,47 @@
1
+const { format, toZonedTime } = require('date-fns-tz');
2
+const { id } = require('date-fns/locale');
3
+
4
+/**
5
+ * Mengubah format tanggal & waktu lengkap ke format Indonesia (DD MMMM YYYY HH:mm:ss)
6
+ * Contoh: "27 Juni 2025 21:35:46"
7
+ */
8
+const formatFullDateTime = (dateInput) => {
9
+    if (!dateInput) return null;
10
+
11
+    const timeZone = 'Asia/Jakarta';
12
+    const zonedDate = toZonedTime(new Date(dateInput), timeZone);
13
+
14
+    return format(zonedDate, 'dd MMMM yyyy HH:mm:ss', { locale: id });
15
+};
16
+
17
+/**
18
+ * Mengubah hanya tanggal (tanpa jam) ke format YYYY-MM-DD
19
+ * Contoh: "2025-06-27"
20
+ */
21
+const formatDateOnly = (dateInput) => {
22
+    if (!dateInput) return null;
23
+
24
+    const timeZone = 'Asia/Jakarta';
25
+    const zonedDate = toZonedTime(new Date(dateInput), timeZone);
26
+
27
+    return format(zonedDate, 'yyyy-MM-dd');
28
+};
29
+
30
+/**
31
+ * Mengubah timestamp ISO ke format ISO tanpa zona waktu
32
+ * Contoh: dari "2025-06-27T14:35:46.376Z" → "2025-06-27 21:35:46"
33
+ */
34
+const formatISOWithoutTimezone = (dateInput) => {
35
+    if (!dateInput) return null;
36
+
37
+    const timeZone = 'Asia/Jakarta';
38
+    const zonedDate = toZonedTime(new Date(dateInput), timeZone);
39
+
40
+    return format(zonedDate, "yyyy-MM-dd HH:mm:ss");
41
+};
42
+
43
+module.exports = {
44
+    formatFullDateTime,
45
+    formatDateOnly,
46
+    formatISOWithoutTimezone,
47
+};

+ 14 - 3
src/utils/PaginationParams.js

@@ -1,10 +1,21 @@
1
-exports.PaginationParam = (req) => {
1
+exports.PaginationParam = (req, extraFields = []) => {
2 2
     const page = parseInt(req.query.page) || 1;
3 3
     const limit = parseInt(req.query.limit) || 10;
4 4
     const search = req.query.search || '';
5 5
     const sortBy = req.query.sort_by || 'createdAt';
6
-
7 6
     const orderBy = req.query.order_by || (sortBy === 'name' ? 'asc' : 'desc');
8 7
 
9
-    return { page, limit, search, sortBy, orderBy };
8
+    const extra = {};
9
+    for (const field of extraFields) {
10
+        extra[field] = req.query[field] ?? null;
11
+    }
12
+
13
+    return {
14
+        page,
15
+        limit,
16
+        search,
17
+        sortBy,
18
+        orderBy,
19
+        ...extra
20
+    };
10 21
 };

+ 63 - 22
src/utils/SearchFilter.js

@@ -11,34 +11,75 @@
11 11
 //     };
12 12
 // };
13 13
 
14
-exports.SearchFilter = (search = '', fields = []) => {
15
-    if (!search || fields.length === 0) return {};
14
+// exports.SearchFilter = (search = '', fields = []) => {
15
+//     if (!search || fields.length === 0) return {};
16 16
 
17
-    const or = [];
17
+//     const or = [];
18 18
 
19
-    for (const field of fields) {
20
-        const parts = field.split('.');
19
+//     for (const field of fields) {
20
+//         const parts = field.split('.');
21
+//         if (parts.length === 1) {
22
+//             or.push({
23
+//                 [field]: {
24
+//                     contains: search,
25
+//                     mode: 'insensitive',
26
+//                 },
27
+//             });
28
+//         } else if (parts.length === 2) {
29
+//             const [relation, subfield] = parts;
30
+//             or.push({
31
+//                 [relation]: {
32
+//                     is: {
33
+//                         [subfield]: {
34
+//                             contains: search,
35
+//                             mode: 'insensitive',
36
+//                         }
37
+//                     }
38
+//                 }
39
+//             });
40
+//         }
41
+//     }
42
+
43
+//     return { OR: or };
44
+// };
45
+
46
+exports.SearchFilter = (search = '', fields = [], arrayRelations = []) => {
47
+    if (!search || fields.length === 0) return {};
48
+
49
+    const buildNestedFilter = (parts, value) => {
21 50
         if (parts.length === 1) {
22
-            or.push({
23
-                [field]: {
24
-                    contains: search,
51
+            return {
52
+                [parts[0]]: {
53
+                    contains: value,
25 54
                     mode: 'insensitive',
26 55
                 },
27
-            });
28
-        } else if (parts.length === 2) {
29
-            const [relation, subfield] = parts;
30
-            or.push({
31
-                [relation]: {
32
-                    is: {
33
-                        [subfield]: {
34
-                            contains: search,
35
-                            mode: 'insensitive',
36
-                        }
37
-                    }
38
-                }
39
-            });
56
+            };
40 57
         }
41
-    }
58
+
59
+        const [current, ...rest] = parts;
60
+
61
+        const nested = buildNestedFilter(rest, value);
62
+
63
+        // Jika current adalah array relation
64
+        if (arrayRelations.includes(current)) {
65
+            return {
66
+                [current]: {
67
+                    some: nested,
68
+                },
69
+            };
70
+        } else {
71
+            return {
72
+                [current]: {
73
+                    is: nested,
74
+                },
75
+            };
76
+        }
77
+    };
78
+
79
+    const or = fields.map(field => {
80
+        const parts = field.split('.');
81
+        return buildNestedFilter(parts, search);
82
+    });
42 83
 
43 84
     return { OR: or };
44 85
 };

+ 77 - 0
src/validators/admin/executives_history/ExecutivesHistoriValidators.js

@@ -0,0 +1,77 @@
1
+const HttpException = require('../../../utils/HttpException.js');
2
+
3
+exports.validateStoreExecutivesHistoryRequest = (body) => {
4
+    const { executive_name, contact, status, start_term, end_term } = body;
5
+
6
+    const errors = {};
7
+
8
+    // if (!executive_name || executive_name.trim() === '') {
9
+    //     errors.executive_name = ['Executive name is required'];
10
+    // }
11
+
12
+    // if (!contact || contact.trim() === '') {
13
+    //     errors.contact = ['Contact is required'];
14
+    // }
15
+
16
+    // if (!status || status.trim() === '') {
17
+    //     errors.status = ['Status is required'];
18
+    // } else if (!['active', 'inactive'].includes(status.trim().toLowerCase())) {
19
+    //     errors.status = ['Status must be either "active" or "inactive"'];
20
+    // }
21
+
22
+    // if (!start_term || start_term.trim() === '') {
23
+    //     errors.start_term = ['Start term date is required'];
24
+    // } else if (isNaN(Date.parse(start_term))) {
25
+    //     errors.start_term = ['Start term date must be a valid date'];
26
+    // }
27
+
28
+    // if (!end_term || end_term.trim() === '') {
29
+    //     errors.end_term = ['End term expired date is required'];
30
+    // } else if (isNaN(Date.parse(end_term))) {
31
+    //     errors.end_term = ['End term expired date must be a valid date'];
32
+    // }
33
+
34
+    // if (
35
+    //     !errors.start_term &&
36
+    //     !errors.end_term &&
37
+    //     new Date(end_term) <= new Date(start_term)
38
+    // ) {
39
+    //     errors.end_term = ['End term expired date must be after start term date'];
40
+    // }
41
+
42
+    if (!status || status.trim() === '') {
43
+        errors.status = ['Status is required'];
44
+    } else if (!['active', 'inactive'].includes(status.trim().toLowerCase())) {
45
+        errors.status = ['Status must be either "active" or "inactive"'];
46
+    }
47
+
48
+    if (start_term && isNaN(Date.parse(start_term))) {
49
+        errors.start_term = ['Start term date must be a valid date'];
50
+    }
51
+
52
+    if (end_term && isNaN(Date.parse(end_term))) {
53
+        errors.end_term = ['End term expired date must be a valid date'];
54
+    }
55
+
56
+    if (
57
+        start_term &&
58
+        end_term &&
59
+        !errors.start_term &&
60
+        !errors.end_term &&
61
+        new Date(end_term) <= new Date(start_term)
62
+    ) {
63
+        errors.end_term = ['End term expired date must be after start term date'];
64
+    }
65
+
66
+    if (Object.keys(errors).length > 0) {
67
+        throw new HttpException(errors, 422);
68
+    }
69
+
70
+    return {
71
+        executive_name: executive_name?.trim() || null,
72
+        contact: contact?.trim() || null,
73
+        status: status.trim().toLowerCase(),
74
+        start_term: start_term ? new Date(start_term) : null,
75
+        end_term: end_term ? new Date(end_term) : null,
76
+    };
77
+};

+ 10 - 10
src/validators/admin/hospital/HospitalValidators.js

@@ -1,7 +1,7 @@
1 1
 const HttpException = require('../../../utils/HttpException.js');
2 2
 
3 3
 exports.validateStoreHospitalRequest = (body) => {
4
-    const { name, hospital_code, type, ownership, province_id, city_id, address, simrs_type, contact, note } = body;
4
+    const { name, hospital_code, type, ownership, province_id, city_id, address, contact, note } = body;
5 5
 
6 6
     const errors = {};
7 7
 
@@ -33,9 +33,9 @@ exports.validateStoreHospitalRequest = (body) => {
33 33
         errors.address = ['address is required'];
34 34
     }
35 35
 
36
-    if (!simrs_type || simrs_type.trim() === '') {
37
-        errors.simrs_type = ['simrs type is required'];
38
-    }
36
+    // if (!simrs_type || simrs_type.trim() === '') {
37
+    //     errors.simrs_type = ['simrs type is required'];
38
+    // }
39 39
 
40 40
     if (!contact || contact.trim() === '') {
41 41
         errors.contact = ['contact is required'];
@@ -57,14 +57,14 @@ exports.validateStoreHospitalRequest = (body) => {
57 57
         province_id: province_id.trim(),
58 58
         city_id: city_id.trim(),
59 59
         address: address.trim(),
60
-        simrs_type: simrs_type.trim(),
60
+        // simrs_type: simrs_type.trim(),
61 61
         contact: contact.trim(),
62 62
         note: note.trim(),
63 63
     };
64 64
 };
65 65
 
66 66
 exports.validateUpdateHospitalRequest = (body) => {
67
-    const { name, hospital_code, type, ownership, province_id, city_id, address, simrs_type, progress_status, contact, note } = body;
67
+    const { name, hospital_code, type, ownership, province_id, city_id, address, progress_status, contact, note } = body;
68 68
 
69 69
     const errors = {};
70 70
 
@@ -96,9 +96,9 @@ exports.validateUpdateHospitalRequest = (body) => {
96 96
         errors.address = ['address is required'];
97 97
     }
98 98
 
99
-    if (!simrs_type || simrs_type.trim() === '') {
100
-        errors.simrs_type = ['simrs type is required'];
101
-    }
99
+    // if (!simrs_type || simrs_type.trim() === '') {
100
+    //     errors.simrs_type = ['simrs type is required'];
101
+    // }
102 102
 
103 103
     if (!progress_status || progress_status.trim() === '') {
104 104
         errors.progress_status = ['progress status is required'];
@@ -124,7 +124,7 @@ exports.validateUpdateHospitalRequest = (body) => {
124 124
         province_id: province_id.trim(),
125 125
         city_id: city_id.trim(),
126 126
         address: address.trim(),
127
-        simrs_type: simrs_type.trim(),
127
+        // simrs_type: simrs_type.trim(),
128 128
         progress_status: progress_status.trim(),
129 129
         contact: contact.trim(),
130 130
         note: note.trim(),

+ 35 - 0
src/validators/admin/vendor/VendorValidators.js

@@ -0,0 +1,35 @@
1
+const HttpException = require('../../../utils/HttpException.js');
2
+
3
+exports.validateStoreVendorRequest = (body) => {
4
+    const { name, name_pt, strengths, weaknesses, website } = body;
5
+
6
+    const errors = {};
7
+
8
+    if (!name || name.trim() === '') {
9
+        errors.name = ['Vendor name is required'];
10
+    }
11
+    if (!name_pt || name_pt.trim() === '') {
12
+        errors.name_pt = ['Vendor name pt is required'];
13
+    }
14
+    if (!strengths || strengths.trim() === '') {
15
+        errors.strengths = ['Vendor strengths is required'];
16
+    }
17
+    if (!weaknesses || weaknesses.trim() === '') {
18
+        errors.weaknesses = ['Vendor weaknesses is required'];
19
+    }
20
+    if (!website || website.trim() === '') {
21
+        errors.website = ['Vendor website is required'];
22
+    }
23
+
24
+    if (Object.keys(errors).length > 0) {
25
+        throw new HttpException(errors, 422);
26
+    }
27
+
28
+    return {
29
+        name: name.trim(),
30
+        name_pt: name_pt.trim(),
31
+        strengths: strengths.trim(),
32
+        weaknesses: weaknesses.trim(),
33
+        website: website.trim(),
34
+    };
35
+};

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

@@ -0,0 +1,73 @@
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
+};

+ 77 - 0
src/validators/sales/executives_history/ExecutivesHistoriValidators.js

@@ -0,0 +1,77 @@
1
+const HttpException = require('../../../utils/HttpException.js');
2
+
3
+exports.validateStoreExecutivesHistoryRequest = (body) => {
4
+    const { executive_name, contact, status, start_term, end_term } = body;
5
+
6
+    const errors = {};
7
+
8
+    // if (!executive_name || executive_name.trim() === '') {
9
+    //     errors.executive_name = ['Executive name is required'];
10
+    // }
11
+
12
+    // if (!contact || contact.trim() === '') {
13
+    //     errors.contact = ['Contact is required'];
14
+    // }
15
+
16
+    // if (!status || status.trim() === '') {
17
+    //     errors.status = ['Status is required'];
18
+    // } else if (!['active', 'inactive'].includes(status.trim().toLowerCase())) {
19
+    //     errors.status = ['Status must be either "active" or "inactive"'];
20
+    // }
21
+
22
+    // if (!start_term || start_term.trim() === '') {
23
+    //     errors.start_term = ['Start term date is required'];
24
+    // } else if (isNaN(Date.parse(start_term))) {
25
+    //     errors.start_term = ['Start term date must be a valid date'];
26
+    // }
27
+
28
+    // if (!end_term || end_term.trim() === '') {
29
+    //     errors.end_term = ['End term expired date is required'];
30
+    // } else if (isNaN(Date.parse(end_term))) {
31
+    //     errors.end_term = ['End term expired date must be a valid date'];
32
+    // }
33
+
34
+    // if (
35
+    //     !errors.start_term &&
36
+    //     !errors.end_term &&
37
+    //     new Date(end_term) <= new Date(start_term)
38
+    // ) {
39
+    //     errors.end_term = ['End term expired date must be after start term date'];
40
+    // }
41
+
42
+    if (!status || status.trim() === '') {
43
+        errors.status = ['Status is required'];
44
+    } else if (!['active', 'inactive'].includes(status.trim().toLowerCase())) {
45
+        errors.status = ['Status must be either "active" or "inactive"'];
46
+    }
47
+
48
+    if (start_term && isNaN(Date.parse(start_term))) {
49
+        errors.start_term = ['Start term date must be a valid date'];
50
+    }
51
+
52
+    if (end_term && isNaN(Date.parse(end_term))) {
53
+        errors.end_term = ['End term expired date must be a valid date'];
54
+    }
55
+
56
+    if (
57
+        start_term &&
58
+        end_term &&
59
+        !errors.start_term &&
60
+        !errors.end_term &&
61
+        new Date(end_term) <= new Date(start_term)
62
+    ) {
63
+        errors.end_term = ['End term expired date must be after start term date'];
64
+    }
65
+
66
+    if (Object.keys(errors).length > 0) {
67
+        throw new HttpException(errors, 422);
68
+    }
69
+
70
+    return {
71
+        executive_name: executive_name?.trim() || null,
72
+        contact: contact?.trim() || null,
73
+        status: status.trim().toLowerCase(),
74
+        start_term: start_term ? new Date(start_term) : null,
75
+        end_term: end_term ? new Date(end_term) : null,
76
+    };
77
+};

+ 35 - 0
src/validators/sales/vendor/VendorValidators.js

@@ -0,0 +1,35 @@
1
+const HttpException = require('../../../utils/HttpException.js');
2
+
3
+exports.validateStoreVendorRequest = (body) => {
4
+    const { name, name_pt, strengths, weaknesses, website } = body;
5
+
6
+    const errors = {};
7
+
8
+    if (!name || name.trim() === '') {
9
+        errors.name = ['Vendor name is required'];
10
+    }
11
+    // if (!name_pt || name_pt.trim() === '') {
12
+    //     errors.name_pt = ['Vendor name pt is required'];
13
+    // }
14
+    // if (!strengths || strengths.trim() === '') {
15
+    //     errors.strengths = ['Vendor strengths is required'];
16
+    // }
17
+    // if (!weaknesses || weaknesses.trim() === '') {
18
+    //     errors.weaknesses = ['Vendor weaknesses is required'];
19
+    // }
20
+    // if (!website || website.trim() === '') {
21
+    //     errors.website = ['Vendor website is required'];
22
+    // }
23
+
24
+    if (Object.keys(errors).length > 0) {
25
+        throw new HttpException(errors, 422);
26
+    }
27
+
28
+    return {
29
+        name: name.trim(),
30
+        name_pt: name_pt.trim(),
31
+        strengths: strengths.trim(),
32
+        weaknesses: weaknesses.trim(),
33
+        website: website.trim(),
34
+    };
35
+};

+ 73 - 0
src/validators/sales/vendor_history/VendorHistoriValidators.js

@@ -0,0 +1,73 @@
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
+};

BIN
storage/img/1750918428600-237588933.jpeg


BIN
storage/img/1750918458736-664806797.jpeg


BIN
storage/img/1750918486962-92985809.jpeg


BIN
storage/img/1750918639876-436618340.jpeg


BIN
storage/img/1750929217833-522408200.jpeg


BIN
storage/img/1751005890656-200597968.jpeg


BIN
storage/img/1751006026954-794671964.jpeg


BIN
storage/img/1751006443877-732477045.png


BIN
storage/img/1751006451755-38518667.png


BIN
storage/img/1751006482002-378131488.png


BIN
storage/img/1751008243197-639287171.jpeg


BIN
storage/img/1751076562821-359984929.jpeg