Spaces:
Sleeping
Sleeping
anoderb commited on
Commit ·
ef874e6
0
Parent(s):
feat: initial commit of Tokiva POS Node backend for Hugging Face
Browse filesThis view is limited to 50 files because it contains too many changes. See raw diff
- .gitignore +35 -0
- Dockerfile +25 -0
- README.md +181 -0
- package-lock.json +1833 -0
- package.json +32 -0
- prisma/schema.prisma +538 -0
- src/app.ts +24 -0
- src/controllers/auth.controller.ts +69 -0
- src/controllers/bon.controller.ts +63 -0
- src/controllers/kategori.controller.ts +60 -0
- src/controllers/member.controller.ts +60 -0
- src/controllers/ops.controller.ts +159 -0
- src/controllers/pengguna.controller.ts +84 -0
- src/controllers/produk.controller.ts +88 -0
- src/controllers/stok.controller.ts +73 -0
- src/controllers/supplier.controller.ts +60 -0
- src/controllers/transaksi.controller.ts +118 -0
- src/controllers/upload.controller.ts +41 -0
- src/middlewares/auth.middleware.ts +56 -0
- src/middlewares/error.middleware.ts +17 -0
- src/middlewares/validate.middleware.ts +32 -0
- src/routes/auth.route.ts +13 -0
- src/routes/bon.route.ts +17 -0
- src/routes/index.ts +28 -0
- src/routes/kategori.route.ts +17 -0
- src/routes/member.route.ts +16 -0
- src/routes/ops.route.ts +30 -0
- src/routes/pengguna.route.ts +19 -0
- src/routes/produk.route.ts +19 -0
- src/routes/stok.route.ts +18 -0
- src/routes/supplier.route.ts +16 -0
- src/routes/transaksi.route.ts +18 -0
- src/routes/upload.route.ts +14 -0
- src/schemas/auth.schema.ts +14 -0
- src/schemas/bon.schema.ts +8 -0
- src/schemas/kategori.schema.ts +20 -0
- src/schemas/member.schema.ts +29 -0
- src/schemas/ops.schema.ts +33 -0
- src/schemas/pengguna.schema.ts +28 -0
- src/schemas/produk.schema.ts +55 -0
- src/schemas/stok.schema.ts +21 -0
- src/schemas/supplier.schema.ts +26 -0
- src/schemas/transaksi.schema.ts +30 -0
- src/schemas/upload.schema.ts +8 -0
- src/server.ts +10 -0
- src/services/auth.service.ts +115 -0
- src/services/bon.service.ts +188 -0
- src/services/diskon.service.ts +214 -0
- src/services/kategori.service.ts +149 -0
- src/services/member.service.ts +196 -0
.gitignore
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Dependency directories
|
| 2 |
+
node_modules/
|
| 3 |
+
|
| 4 |
+
# Optional npm cache directory
|
| 5 |
+
.npm/
|
| 6 |
+
|
| 7 |
+
# Output directory
|
| 8 |
+
dist/
|
| 9 |
+
|
| 10 |
+
# Local env files
|
| 11 |
+
.env
|
| 12 |
+
.env.local
|
| 13 |
+
.env.development.local
|
| 14 |
+
.env.test.local
|
| 15 |
+
.env.production.local
|
| 16 |
+
|
| 17 |
+
# Log files
|
| 18 |
+
npm-debug.log*
|
| 19 |
+
yarn-debug.log*
|
| 20 |
+
yarn-error.log*
|
| 21 |
+
.pnpm-debug.log*
|
| 22 |
+
trace.txt
|
| 23 |
+
|
| 24 |
+
# Editor / IDE configuration folders
|
| 25 |
+
.vscode/
|
| 26 |
+
.idea/
|
| 27 |
+
*.suo
|
| 28 |
+
*.ntvs*
|
| 29 |
+
*.njsproj
|
| 30 |
+
*.sln
|
| 31 |
+
*.swp
|
| 32 |
+
*~
|
| 33 |
+
|
| 34 |
+
# TypeScript build info cache
|
| 35 |
+
*.tsbuildinfo
|
Dockerfile
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
FROM node:18-alpine
|
| 2 |
+
|
| 3 |
+
WORKDIR /app
|
| 4 |
+
|
| 5 |
+
# Copy package files and install dependencies
|
| 6 |
+
COPY package*.json tsconfig.json ./
|
| 7 |
+
RUN npm ci
|
| 8 |
+
|
| 9 |
+
# Copy project files
|
| 10 |
+
COPY . .
|
| 11 |
+
|
| 12 |
+
# Generate Prisma Client
|
| 13 |
+
RUN npx prisma generate
|
| 14 |
+
|
| 15 |
+
# Build TypeScript to JavaScript
|
| 16 |
+
RUN npm run build
|
| 17 |
+
|
| 18 |
+
# Expose port 7860 (required by Hugging Face)
|
| 19 |
+
EXPOSE 7860
|
| 20 |
+
|
| 21 |
+
# Override PORT environment variable
|
| 22 |
+
ENV PORT=7860
|
| 23 |
+
|
| 24 |
+
# Run the backend server
|
| 25 |
+
CMD ["npm", "start"]
|
README.md
ADDED
|
@@ -0,0 +1,181 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Tokiva POS Backend (Node.js & TypeScript Edition)
|
| 2 |
+
|
| 3 |
+
Backend ini dibangun menggunakan **Node.js**, **Express**, **TypeScript**, ORM **Prisma**, dan database **MySQL**. Aplikasi ini dirancang untuk mendukung operasional sistem POS (Point of Sales) ritel modern, lengkap dengan fitur manajemen stok berbasis FEFO (First Expired First Out), kasir shift, audit logging, dan integrasi penyimpanan cloud (Supabase Storage).
|
| 4 |
+
|
| 5 |
+
---
|
| 6 |
+
|
| 7 |
+
## 🛠️ Persyaratan System (Prerequisites)
|
| 8 |
+
|
| 9 |
+
- Node.js (v18 atau lebih tinggi)
|
| 10 |
+
- npm atau yarn
|
| 11 |
+
- Database MySQL
|
| 12 |
+
|
| 13 |
+
---
|
| 14 |
+
|
| 15 |
+
## 🚀 Cara Menjalankan secara Lokal
|
| 16 |
+
|
| 17 |
+
### 1. Install Dependensi
|
| 18 |
+
Masuk ke direktori `backend-node` dan jalankan instalasi:
|
| 19 |
+
```bash
|
| 20 |
+
cd backend-node
|
| 21 |
+
npm install
|
| 22 |
+
```
|
| 23 |
+
|
| 24 |
+
### 2. Setup Environment Variable
|
| 25 |
+
Buat file `.env` di dalam folder root `backend-node` dan isi sesuai dengan kebutuhan Anda:
|
| 26 |
+
```env
|
| 27 |
+
PORT=5000
|
| 28 |
+
DATABASE_URL="mysql://username:password@127.0.0.1:3306/tokiva"
|
| 29 |
+
|
| 30 |
+
# JWT Configuration
|
| 31 |
+
JWT_SECRET_KEY="rahasia_super_kuat_anda"
|
| 32 |
+
JWT_EXPIRES_IN="24h"
|
| 33 |
+
|
| 34 |
+
# Supabase Storage Configuration (Opsional, untuk upload gambar produk/member)
|
| 35 |
+
SUPABASE_URL="https://your-supabase-url.supabase.co"
|
| 36 |
+
SUPABASE_ANON_KEY="your-anon-key"
|
| 37 |
+
SUPABASE_SERVICE_KEY="your-service-role-key"
|
| 38 |
+
```
|
| 39 |
+
|
| 40 |
+
### 3. Sinkronisasi Skema Database & Generate Prisma Client
|
| 41 |
+
Jalankan perintah berikut untuk mensinkronkan skema database lokal Anda dengan Prisma Client:
|
| 42 |
+
```bash
|
| 43 |
+
npx prisma generate
|
| 44 |
+
```
|
| 45 |
+
|
| 46 |
+
### 4. Jalankan Server Development
|
| 47 |
+
Jalankan server dalam mode development (menggunakan `ts-node-dev` untuk auto-reload):
|
| 48 |
+
```bash
|
| 49 |
+
npm run dev
|
| 50 |
+
```
|
| 51 |
+
Server akan berjalan di: **`http://localhost:5000`**
|
| 52 |
+
|
| 53 |
+
---
|
| 54 |
+
|
| 55 |
+
## 📂 Struktur Direktori Proyek
|
| 56 |
+
|
| 57 |
+
```text
|
| 58 |
+
backend-node/
|
| 59 |
+
├── prisma/ # Skema database Prisma
|
| 60 |
+
│ └── schema.prisma
|
| 61 |
+
├── src/
|
| 62 |
+
│ ├── controllers/ # Menangani HTTP request dan format response
|
| 63 |
+
│ ├── middlewares/ # Middleware Express (Auth, Zod Validation, Error handler)
|
| 64 |
+
│ ├── routes/ # Defini endpoint API Express
|
| 65 |
+
│ ├── schemas/ # Validator skema request input menggunakan Zod
|
| 66 |
+
│ ├── services/ # Logika bisnis inti aplikasi
|
| 67 |
+
│ ├── utils/ # Helper (Supabase, JWT, response formatter, audit logger)
|
| 68 |
+
│ ├── app.ts # Setup Express application
|
| 69 |
+
│ └── server.ts # Entrypoint API server
|
| 70 |
+
├── .env # File konfigurasi environment
|
| 71 |
+
├── package.json # File dependensi proyek
|
| 72 |
+
└── tsconfig.json # Konfigurasi TypeScript compiler
|
| 73 |
+
```
|
| 74 |
+
|
| 75 |
+
---
|
| 76 |
+
|
| 77 |
+
## 📘 Referensi Endpoint API
|
| 78 |
+
|
| 79 |
+
Seluruh endpoint API memiliki prefix `/api`.
|
| 80 |
+
Endpoint yang membutuhkan header **Authorization** harus melampirkan token JWT: `Bearer <token_jwt>`.
|
| 81 |
+
|
| 82 |
+
### 1. Autentikasi (`/api/auth`)
|
| 83 |
+
| Method | Endpoint | Auth | Deskripsi |
|
| 84 |
+
|---|---|---|---|
|
| 85 |
+
| `POST` | `/auth/login` | Publik | Login pengguna menggunakan username dan password |
|
| 86 |
+
| `POST` | `/auth/refresh` | Publik | Melakukan refresh JWT token |
|
| 87 |
+
|
| 88 |
+
### 2. Manajemen Pengguna / Karyawan (`/api/users`)
|
| 89 |
+
| Method | Endpoint | Auth | Deskripsi |
|
| 90 |
+
|---|---|---|---|
|
| 91 |
+
| `GET` | `/users` | Admin | Memuat daftar semua pengguna |
|
| 92 |
+
| `POST` | `/users` | Admin | Membuat akun pengguna baru |
|
| 93 |
+
| `PUT` | `/users/:id` | Admin | Mengedit informasi akun pengguna |
|
| 94 |
+
| `DELETE` | `/users/:id` | Admin | Menghapus akun pengguna (Soft-Delete) |
|
| 95 |
+
|
| 96 |
+
### 3. Kategori Produk (`/api/kategori`)
|
| 97 |
+
| Method | Endpoint | Auth | Deskripsi |
|
| 98 |
+
|---|---|---|---|
|
| 99 |
+
| `GET` | `/kategori` | Token | Memuat seluruh kategori produk |
|
| 100 |
+
| `POST` | `/kategori` | Token | Menambah kategori baru |
|
| 101 |
+
| `PUT` | `/kategori/:id` | Token | Mengubah kategori |
|
| 102 |
+
| `DELETE` | `/kategori/:id` | Token | Menghapus kategori |
|
| 103 |
+
|
| 104 |
+
### 4. Supplier / Pemasok (`/api/supplier`)
|
| 105 |
+
| Method | Endpoint | Auth | Deskripsi |
|
| 106 |
+
|---|---|---|---|
|
| 107 |
+
| `GET` | `/supplier` | Token | Memuat seluruh supplier aktif |
|
| 108 |
+
| `POST` | `/supplier` | Token | Menambah supplier baru |
|
| 109 |
+
| `PUT` | `/supplier/:id` | Token | Mengubah data supplier |
|
| 110 |
+
| `DELETE` | `/supplier/:id` | Token | Menghapus supplier |
|
| 111 |
+
|
| 112 |
+
### 5. Member / Pelanggan Loyalitas (`/api/member`)
|
| 113 |
+
| Method | Endpoint | Auth | Deskripsi |
|
| 114 |
+
|---|---|---|---|
|
| 115 |
+
| `GET` | `/member` | Token | Memuat daftar member |
|
| 116 |
+
| `POST` | `/member` | Token | Mendaftarkan member baru |
|
| 117 |
+
| `PUT` | `/member/:id` | Token | Memperbarui profil member |
|
| 118 |
+
| `DELETE` | `/member/:id` | Token | Menghapus member |
|
| 119 |
+
|
| 120 |
+
### 6. Produk (`/api/produk`)
|
| 121 |
+
| Method | Endpoint | Auth | Deskripsi |
|
| 122 |
+
|---|---|---|---|
|
| 123 |
+
| `GET` | `/produk` | Token | Memuat daftar produk (mendukung pencarian & filter) |
|
| 124 |
+
| `GET` | `/produk/:id` | Token | Memuat detail lengkap satu produk |
|
| 125 |
+
| `GET` | `/produk/scan/:barcode_or_kode` | Token | Mencari produk berdasarkan barcode/kode untuk scan cepat |
|
| 126 |
+
| `POST` | `/produk` | Token | Menambah produk baru |
|
| 127 |
+
| `PUT` | `/produk/:id` | Token | Memperbarui produk |
|
| 128 |
+
| `DELETE` | `/produk/:id` | Token | Menghapus produk (Soft-Delete) |
|
| 129 |
+
|
| 130 |
+
### 7. Manajemen Stok & Batch FEFO (`/api/stok`)
|
| 131 |
+
| Method | Endpoint | Auth | Deskripsi |
|
| 132 |
+
|---|---|---|---|
|
| 133 |
+
| `POST` | `/stok/masuk` | Token | Mencatat barang masuk/restock ke batch tertentu |
|
| 134 |
+
| `POST` | `/stok/opname` | Token | Membuat draf stok opname (penyesuaian fisik) |
|
| 135 |
+
| `POST` | `/stok/opname/:opname_id/approve` | Token | Menyetujui opname dan menyesuaikan stok utama produk |
|
| 136 |
+
| `GET` | `/stok/mutasi` | Token | Memuat kartu stok / riwayat mutasi stok produk |
|
| 137 |
+
| `GET` | `/stok/batches` | Token | Memuat batch stok produk yang masih aktif (qty sisa > 0) |
|
| 138 |
+
|
| 139 |
+
### 8. Transaksi Penjualan (`/api/transaksi`)
|
| 140 |
+
| Method | Endpoint | Auth | Deskripsi |
|
| 141 |
+
|---|---|---|---|
|
| 142 |
+
| `POST` | `/transaksi` | Token | Mencatat transaksi kasir baru (FEFO auto-deduction) |
|
| 143 |
+
| `GET` | `/transaksi` | Token | Memuat riwayat transaksi penjualan |
|
| 144 |
+
| `POST` | `/transaksi/retur` | Token | Melakukan retur barang dari suatu transaksi (refund/exchange) |
|
| 145 |
+
|
| 146 |
+
### 9. Bon & Piutang Member (`/api/bon`)
|
| 147 |
+
| Method | Endpoint | Auth | Deskripsi |
|
| 148 |
+
|---|---|---|---|
|
| 149 |
+
| `GET` | `/bon` | Token | Memuat semua daftar bon piutang member |
|
| 150 |
+
| `POST` | `/bon/:bon_id/cicil` | Token | Mencatat pembayaran cicilan bon member |
|
| 151 |
+
|
| 152 |
+
### 10. Laporan Finansial & Operasional (`/api`)
|
| 153 |
+
| Method | Endpoint | Auth | Deskripsi |
|
| 154 |
+
|---|---|---|---|
|
| 155 |
+
| `GET` | `/laporan/ringkasan` | Admin | Memuat ringkasan keuangan (pendapatan, laba kotor, dll) |
|
| 156 |
+
| `GET` | `/laporan/penjualan` | Admin | Laporan detail penjualan berdasarkan rentang tanggal |
|
| 157 |
+
| `GET` | `/laporan/stok` | Admin | Laporan analisis stok produk saat ini |
|
| 158 |
+
|
| 159 |
+
### 11. Manajemen Shift Kasir (`/api`)
|
| 160 |
+
| Method | Endpoint | Auth | Deskripsi |
|
| 161 |
+
|---|---|---|---|
|
| 162 |
+
| `POST` | `/shift/buka` | Token | Membuka shift kasir baru dan mencatat modal awal |
|
| 163 |
+
| `POST` | `/shift/tutup` | Token | Menutup shift kasir, mencatat total fisik uang, & mendeteksi selisih |
|
| 164 |
+
| `GET` | `/shift/aktif` | Token | Mendapatkan informasi shift yang sedang berjalan |
|
| 165 |
+
|
| 166 |
+
### 12. Upload File Media (`/api/upload`)
|
| 167 |
+
| Method | Endpoint | Auth | Deskripsi |
|
| 168 |
+
|---|---|---|---|
|
| 169 |
+
| `POST` | `/upload/produk` | Token | Upload gambar produk ke Supabase Storage (Base64/Form-Data) |
|
| 170 |
+
| `POST` | `/upload/member` | Token | Upload foto member ke Supabase Storage |
|
| 171 |
+
|
| 172 |
+
---
|
| 173 |
+
|
| 174 |
+
## 🧪 Jalankan Tes Integrasi CRUD
|
| 175 |
+
|
| 176 |
+
Untuk memverifikasi fungsionalitas CRUD secara end-to-end terhadap database MySQL lokal Anda:
|
| 177 |
+
1. Pastikan server backend sedang berjalan (`npm run dev`).
|
| 178 |
+
2. Jalankan perintah script uji coba:
|
| 179 |
+
```bash
|
| 180 |
+
node scratch/test-crud.js
|
| 181 |
+
```
|
package-lock.json
ADDED
|
@@ -0,0 +1,1833 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"name": "tokiva-pos-backend-node",
|
| 3 |
+
"version": "1.0.0",
|
| 4 |
+
"lockfileVersion": 3,
|
| 5 |
+
"requires": true,
|
| 6 |
+
"packages": {
|
| 7 |
+
"": {
|
| 8 |
+
"name": "tokiva-pos-backend-node",
|
| 9 |
+
"version": "1.0.0",
|
| 10 |
+
"dependencies": {
|
| 11 |
+
"@prisma/client": "^5.19.0",
|
| 12 |
+
"bcryptjs": "^2.4.3",
|
| 13 |
+
"cors": "^2.8.5",
|
| 14 |
+
"dotenv": "^16.4.5",
|
| 15 |
+
"express": "^4.21.0",
|
| 16 |
+
"jsonwebtoken": "^9.0.2",
|
| 17 |
+
"zod": "^3.23.8"
|
| 18 |
+
},
|
| 19 |
+
"devDependencies": {
|
| 20 |
+
"@types/bcryptjs": "^2.4.6",
|
| 21 |
+
"@types/cors": "^2.8.17",
|
| 22 |
+
"@types/express": "^4.17.21",
|
| 23 |
+
"@types/jsonwebtoken": "^9.0.7",
|
| 24 |
+
"@types/node": "^22.5.0",
|
| 25 |
+
"prisma": "^5.19.0",
|
| 26 |
+
"ts-node-dev": "^2.0.0",
|
| 27 |
+
"typescript": "^5.5.4"
|
| 28 |
+
}
|
| 29 |
+
},
|
| 30 |
+
"node_modules/@cspotcode/source-map-support": {
|
| 31 |
+
"version": "0.8.1",
|
| 32 |
+
"resolved": "https://registry.npmjs.org/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz",
|
| 33 |
+
"integrity": "sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==",
|
| 34 |
+
"dev": true,
|
| 35 |
+
"dependencies": {
|
| 36 |
+
"@jridgewell/trace-mapping": "0.3.9"
|
| 37 |
+
},
|
| 38 |
+
"engines": {
|
| 39 |
+
"node": ">=12"
|
| 40 |
+
}
|
| 41 |
+
},
|
| 42 |
+
"node_modules/@jridgewell/resolve-uri": {
|
| 43 |
+
"version": "3.1.2",
|
| 44 |
+
"resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz",
|
| 45 |
+
"integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==",
|
| 46 |
+
"dev": true,
|
| 47 |
+
"engines": {
|
| 48 |
+
"node": ">=6.0.0"
|
| 49 |
+
}
|
| 50 |
+
},
|
| 51 |
+
"node_modules/@jridgewell/sourcemap-codec": {
|
| 52 |
+
"version": "1.5.5",
|
| 53 |
+
"resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz",
|
| 54 |
+
"integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==",
|
| 55 |
+
"dev": true
|
| 56 |
+
},
|
| 57 |
+
"node_modules/@jridgewell/trace-mapping": {
|
| 58 |
+
"version": "0.3.9",
|
| 59 |
+
"resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.9.tgz",
|
| 60 |
+
"integrity": "sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==",
|
| 61 |
+
"dev": true,
|
| 62 |
+
"dependencies": {
|
| 63 |
+
"@jridgewell/resolve-uri": "^3.0.3",
|
| 64 |
+
"@jridgewell/sourcemap-codec": "^1.4.10"
|
| 65 |
+
}
|
| 66 |
+
},
|
| 67 |
+
"node_modules/@prisma/client": {
|
| 68 |
+
"version": "5.22.0",
|
| 69 |
+
"resolved": "https://registry.npmjs.org/@prisma/client/-/client-5.22.0.tgz",
|
| 70 |
+
"integrity": "sha512-M0SVXfyHnQREBKxCgyo7sffrKttwE6R8PMq330MIUF0pTwjUhLbW84pFDlf06B27XyCR++VtjugEnIHdr07SVA==",
|
| 71 |
+
"hasInstallScript": true,
|
| 72 |
+
"engines": {
|
| 73 |
+
"node": ">=16.13"
|
| 74 |
+
},
|
| 75 |
+
"peerDependencies": {
|
| 76 |
+
"prisma": "*"
|
| 77 |
+
},
|
| 78 |
+
"peerDependenciesMeta": {
|
| 79 |
+
"prisma": {
|
| 80 |
+
"optional": true
|
| 81 |
+
}
|
| 82 |
+
}
|
| 83 |
+
},
|
| 84 |
+
"node_modules/@prisma/debug": {
|
| 85 |
+
"version": "5.22.0",
|
| 86 |
+
"resolved": "https://registry.npmjs.org/@prisma/debug/-/debug-5.22.0.tgz",
|
| 87 |
+
"integrity": "sha512-AUt44v3YJeggO2ZU5BkXI7M4hu9BF2zzH2iF2V5pyXT/lRTyWiElZ7It+bRH1EshoMRxHgpYg4VB6rCM+mG5jQ==",
|
| 88 |
+
"devOptional": true
|
| 89 |
+
},
|
| 90 |
+
"node_modules/@prisma/engines": {
|
| 91 |
+
"version": "5.22.0",
|
| 92 |
+
"resolved": "https://registry.npmjs.org/@prisma/engines/-/engines-5.22.0.tgz",
|
| 93 |
+
"integrity": "sha512-UNjfslWhAt06kVL3CjkuYpHAWSO6L4kDCVPegV6itt7nD1kSJavd3vhgAEhjglLJJKEdJ7oIqDJ+yHk6qO8gPA==",
|
| 94 |
+
"devOptional": true,
|
| 95 |
+
"hasInstallScript": true,
|
| 96 |
+
"dependencies": {
|
| 97 |
+
"@prisma/debug": "5.22.0",
|
| 98 |
+
"@prisma/engines-version": "5.22.0-44.605197351a3c8bdd595af2d2a9bc3025bca48ea2",
|
| 99 |
+
"@prisma/fetch-engine": "5.22.0",
|
| 100 |
+
"@prisma/get-platform": "5.22.0"
|
| 101 |
+
}
|
| 102 |
+
},
|
| 103 |
+
"node_modules/@prisma/engines-version": {
|
| 104 |
+
"version": "5.22.0-44.605197351a3c8bdd595af2d2a9bc3025bca48ea2",
|
| 105 |
+
"resolved": "https://registry.npmjs.org/@prisma/engines-version/-/engines-version-5.22.0-44.605197351a3c8bdd595af2d2a9bc3025bca48ea2.tgz",
|
| 106 |
+
"integrity": "sha512-2PTmxFR2yHW/eB3uqWtcgRcgAbG1rwG9ZriSvQw+nnb7c4uCr3RAcGMb6/zfE88SKlC1Nj2ziUvc96Z379mHgQ==",
|
| 107 |
+
"devOptional": true
|
| 108 |
+
},
|
| 109 |
+
"node_modules/@prisma/fetch-engine": {
|
| 110 |
+
"version": "5.22.0",
|
| 111 |
+
"resolved": "https://registry.npmjs.org/@prisma/fetch-engine/-/fetch-engine-5.22.0.tgz",
|
| 112 |
+
"integrity": "sha512-bkrD/Mc2fSvkQBV5EpoFcZ87AvOgDxbG99488a5cexp5Ccny+UM6MAe/UFkUC0wLYD9+9befNOqGiIJhhq+HbA==",
|
| 113 |
+
"devOptional": true,
|
| 114 |
+
"dependencies": {
|
| 115 |
+
"@prisma/debug": "5.22.0",
|
| 116 |
+
"@prisma/engines-version": "5.22.0-44.605197351a3c8bdd595af2d2a9bc3025bca48ea2",
|
| 117 |
+
"@prisma/get-platform": "5.22.0"
|
| 118 |
+
}
|
| 119 |
+
},
|
| 120 |
+
"node_modules/@prisma/get-platform": {
|
| 121 |
+
"version": "5.22.0",
|
| 122 |
+
"resolved": "https://registry.npmjs.org/@prisma/get-platform/-/get-platform-5.22.0.tgz",
|
| 123 |
+
"integrity": "sha512-pHhpQdr1UPFpt+zFfnPazhulaZYCUqeIcPpJViYoq9R+D/yw4fjE+CtnsnKzPYm0ddUbeXUzjGVGIRVgPDCk4Q==",
|
| 124 |
+
"devOptional": true,
|
| 125 |
+
"dependencies": {
|
| 126 |
+
"@prisma/debug": "5.22.0"
|
| 127 |
+
}
|
| 128 |
+
},
|
| 129 |
+
"node_modules/@tsconfig/node10": {
|
| 130 |
+
"version": "1.0.12",
|
| 131 |
+
"resolved": "https://registry.npmjs.org/@tsconfig/node10/-/node10-1.0.12.tgz",
|
| 132 |
+
"integrity": "sha512-UCYBaeFvM11aU2y3YPZ//O5Rhj+xKyzy7mvcIoAjASbigy8mHMryP5cK7dgjlz2hWxh1g5pLw084E0a/wlUSFQ==",
|
| 133 |
+
"dev": true
|
| 134 |
+
},
|
| 135 |
+
"node_modules/@tsconfig/node12": {
|
| 136 |
+
"version": "1.0.11",
|
| 137 |
+
"resolved": "https://registry.npmjs.org/@tsconfig/node12/-/node12-1.0.11.tgz",
|
| 138 |
+
"integrity": "sha512-cqefuRsh12pWyGsIoBKJA9luFu3mRxCA+ORZvA4ktLSzIuCUtWVxGIuXigEwO5/ywWFMZ2QEGKWvkZG1zDMTag==",
|
| 139 |
+
"dev": true
|
| 140 |
+
},
|
| 141 |
+
"node_modules/@tsconfig/node14": {
|
| 142 |
+
"version": "1.0.3",
|
| 143 |
+
"resolved": "https://registry.npmjs.org/@tsconfig/node14/-/node14-1.0.3.tgz",
|
| 144 |
+
"integrity": "sha512-ysT8mhdixWK6Hw3i1V2AeRqZ5WfXg1G43mqoYlM2nc6388Fq5jcXyr5mRsqViLx/GJYdoL0bfXD8nmF+Zn/Iow==",
|
| 145 |
+
"dev": true
|
| 146 |
+
},
|
| 147 |
+
"node_modules/@tsconfig/node16": {
|
| 148 |
+
"version": "1.0.4",
|
| 149 |
+
"resolved": "https://registry.npmjs.org/@tsconfig/node16/-/node16-1.0.4.tgz",
|
| 150 |
+
"integrity": "sha512-vxhUy4J8lyeyinH7Azl1pdd43GJhZH/tP2weN8TntQblOY+A0XbT8DJk1/oCPuOOyg/Ja757rG0CgHcWC8OfMA==",
|
| 151 |
+
"dev": true
|
| 152 |
+
},
|
| 153 |
+
"node_modules/@types/bcryptjs": {
|
| 154 |
+
"version": "2.4.6",
|
| 155 |
+
"resolved": "https://registry.npmjs.org/@types/bcryptjs/-/bcryptjs-2.4.6.tgz",
|
| 156 |
+
"integrity": "sha512-9xlo6R2qDs5uixm0bcIqCeMCE6HiQsIyel9KQySStiyqNl2tnj2mP3DX1Nf56MD6KMenNNlBBsy3LJ7gUEQPXQ==",
|
| 157 |
+
"dev": true
|
| 158 |
+
},
|
| 159 |
+
"node_modules/@types/body-parser": {
|
| 160 |
+
"version": "1.19.6",
|
| 161 |
+
"resolved": "https://registry.npmjs.org/@types/body-parser/-/body-parser-1.19.6.tgz",
|
| 162 |
+
"integrity": "sha512-HLFeCYgz89uk22N5Qg3dvGvsv46B8GLvKKo1zKG4NybA8U2DiEO3w9lqGg29t/tfLRJpJ6iQxnVw4OnB7MoM9g==",
|
| 163 |
+
"dev": true,
|
| 164 |
+
"dependencies": {
|
| 165 |
+
"@types/connect": "*",
|
| 166 |
+
"@types/node": "*"
|
| 167 |
+
}
|
| 168 |
+
},
|
| 169 |
+
"node_modules/@types/connect": {
|
| 170 |
+
"version": "3.4.38",
|
| 171 |
+
"resolved": "https://registry.npmjs.org/@types/connect/-/connect-3.4.38.tgz",
|
| 172 |
+
"integrity": "sha512-K6uROf1LD88uDQqJCktA4yzL1YYAK6NgfsI0v/mTgyPKWsX1CnJ0XPSDhViejru1GcRkLWb8RlzFYJRqGUbaug==",
|
| 173 |
+
"dev": true,
|
| 174 |
+
"dependencies": {
|
| 175 |
+
"@types/node": "*"
|
| 176 |
+
}
|
| 177 |
+
},
|
| 178 |
+
"node_modules/@types/cors": {
|
| 179 |
+
"version": "2.8.19",
|
| 180 |
+
"resolved": "https://registry.npmjs.org/@types/cors/-/cors-2.8.19.tgz",
|
| 181 |
+
"integrity": "sha512-mFNylyeyqN93lfe/9CSxOGREz8cpzAhH+E93xJ4xWQf62V8sQ/24reV2nyzUWM6H6Xji+GGHpkbLe7pVoUEskg==",
|
| 182 |
+
"dev": true,
|
| 183 |
+
"dependencies": {
|
| 184 |
+
"@types/node": "*"
|
| 185 |
+
}
|
| 186 |
+
},
|
| 187 |
+
"node_modules/@types/express": {
|
| 188 |
+
"version": "4.17.25",
|
| 189 |
+
"resolved": "https://registry.npmjs.org/@types/express/-/express-4.17.25.tgz",
|
| 190 |
+
"integrity": "sha512-dVd04UKsfpINUnK0yBoYHDF3xu7xVH4BuDotC/xGuycx4CgbP48X/KF/586bcObxT0HENHXEU8Nqtu6NR+eKhw==",
|
| 191 |
+
"dev": true,
|
| 192 |
+
"dependencies": {
|
| 193 |
+
"@types/body-parser": "*",
|
| 194 |
+
"@types/express-serve-static-core": "^4.17.33",
|
| 195 |
+
"@types/qs": "*",
|
| 196 |
+
"@types/serve-static": "^1"
|
| 197 |
+
}
|
| 198 |
+
},
|
| 199 |
+
"node_modules/@types/express-serve-static-core": {
|
| 200 |
+
"version": "4.19.8",
|
| 201 |
+
"resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-4.19.8.tgz",
|
| 202 |
+
"integrity": "sha512-02S5fmqeoKzVZCHPZid4b8JH2eM5HzQLZWN2FohQEy/0eXTq8VXZfSN6Pcr3F6N9R/vNrj7cpgbhjie6m/1tCA==",
|
| 203 |
+
"dev": true,
|
| 204 |
+
"dependencies": {
|
| 205 |
+
"@types/node": "*",
|
| 206 |
+
"@types/qs": "*",
|
| 207 |
+
"@types/range-parser": "*",
|
| 208 |
+
"@types/send": "*"
|
| 209 |
+
}
|
| 210 |
+
},
|
| 211 |
+
"node_modules/@types/http-errors": {
|
| 212 |
+
"version": "2.0.5",
|
| 213 |
+
"resolved": "https://registry.npmjs.org/@types/http-errors/-/http-errors-2.0.5.tgz",
|
| 214 |
+
"integrity": "sha512-r8Tayk8HJnX0FztbZN7oVqGccWgw98T/0neJphO91KkmOzug1KkofZURD4UaD5uH8AqcFLfdPErnBod0u71/qg==",
|
| 215 |
+
"dev": true
|
| 216 |
+
},
|
| 217 |
+
"node_modules/@types/jsonwebtoken": {
|
| 218 |
+
"version": "9.0.10",
|
| 219 |
+
"resolved": "https://registry.npmjs.org/@types/jsonwebtoken/-/jsonwebtoken-9.0.10.tgz",
|
| 220 |
+
"integrity": "sha512-asx5hIG9Qmf/1oStypjanR7iKTv0gXQ1Ov/jfrX6kS/EO0OFni8orbmGCn0672NHR3kXHwpAwR+B368ZGN/2rA==",
|
| 221 |
+
"dev": true,
|
| 222 |
+
"dependencies": {
|
| 223 |
+
"@types/ms": "*",
|
| 224 |
+
"@types/node": "*"
|
| 225 |
+
}
|
| 226 |
+
},
|
| 227 |
+
"node_modules/@types/mime": {
|
| 228 |
+
"version": "1.3.5",
|
| 229 |
+
"resolved": "https://registry.npmjs.org/@types/mime/-/mime-1.3.5.tgz",
|
| 230 |
+
"integrity": "sha512-/pyBZWSLD2n0dcHE3hq8s8ZvcETHtEuF+3E7XVt0Ig2nvsVQXdghHVcEkIWjy9A0wKfTn97a/PSDYohKIlnP/w==",
|
| 231 |
+
"dev": true
|
| 232 |
+
},
|
| 233 |
+
"node_modules/@types/ms": {
|
| 234 |
+
"version": "2.1.0",
|
| 235 |
+
"resolved": "https://registry.npmjs.org/@types/ms/-/ms-2.1.0.tgz",
|
| 236 |
+
"integrity": "sha512-GsCCIZDE/p3i96vtEqx+7dBUGXrc7zeSK3wwPHIaRThS+9OhWIXRqzs4d6k1SVU8g91DrNRWxWUGhp5KXQb2VA==",
|
| 237 |
+
"dev": true
|
| 238 |
+
},
|
| 239 |
+
"node_modules/@types/node": {
|
| 240 |
+
"version": "22.20.0",
|
| 241 |
+
"resolved": "https://registry.npmjs.org/@types/node/-/node-22.20.0.tgz",
|
| 242 |
+
"integrity": "sha512-QWlFW2wf3nTjC13/DqRnBpR4ZO36VJH/JVBkA/vcnmbTBNQIlnObqyqZE1tUR7+Ni23Lda8R1BxMfbXRpCUx5g==",
|
| 243 |
+
"dev": true,
|
| 244 |
+
"dependencies": {
|
| 245 |
+
"undici-types": "~6.21.0"
|
| 246 |
+
}
|
| 247 |
+
},
|
| 248 |
+
"node_modules/@types/qs": {
|
| 249 |
+
"version": "6.15.1",
|
| 250 |
+
"resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.15.1.tgz",
|
| 251 |
+
"integrity": "sha512-GZHUBZR9hckSUhrxmp1nG6NwdpM9fCunJwyThLW1X3AyHgd9IlHb6VANpQQqDr2o/qQp6McZ3y/IA2rVzKzSbw==",
|
| 252 |
+
"dev": true
|
| 253 |
+
},
|
| 254 |
+
"node_modules/@types/range-parser": {
|
| 255 |
+
"version": "1.2.7",
|
| 256 |
+
"resolved": "https://registry.npmjs.org/@types/range-parser/-/range-parser-1.2.7.tgz",
|
| 257 |
+
"integrity": "sha512-hKormJbkJqzQGhziax5PItDUTMAM9uE2XXQmM37dyd4hVM+5aVl7oVxMVUiVQn2oCQFN/LKCZdvSM0pFRqbSmQ==",
|
| 258 |
+
"dev": true
|
| 259 |
+
},
|
| 260 |
+
"node_modules/@types/send": {
|
| 261 |
+
"version": "1.2.1",
|
| 262 |
+
"resolved": "https://registry.npmjs.org/@types/send/-/send-1.2.1.tgz",
|
| 263 |
+
"integrity": "sha512-arsCikDvlU99zl1g69TcAB3mzZPpxgw0UQnaHeC1Nwb015xp8bknZv5rIfri9xTOcMuaVgvabfIRA7PSZVuZIQ==",
|
| 264 |
+
"dev": true,
|
| 265 |
+
"dependencies": {
|
| 266 |
+
"@types/node": "*"
|
| 267 |
+
}
|
| 268 |
+
},
|
| 269 |
+
"node_modules/@types/serve-static": {
|
| 270 |
+
"version": "1.15.10",
|
| 271 |
+
"resolved": "https://registry.npmjs.org/@types/serve-static/-/serve-static-1.15.10.tgz",
|
| 272 |
+
"integrity": "sha512-tRs1dB+g8Itk72rlSI2ZrW6vZg0YrLI81iQSTkMmOqnqCaNr/8Ek4VwWcN5vZgCYWbg/JJSGBlUaYGAOP73qBw==",
|
| 273 |
+
"dev": true,
|
| 274 |
+
"dependencies": {
|
| 275 |
+
"@types/http-errors": "*",
|
| 276 |
+
"@types/node": "*",
|
| 277 |
+
"@types/send": "<1"
|
| 278 |
+
}
|
| 279 |
+
},
|
| 280 |
+
"node_modules/@types/serve-static/node_modules/@types/send": {
|
| 281 |
+
"version": "0.17.6",
|
| 282 |
+
"resolved": "https://registry.npmjs.org/@types/send/-/send-0.17.6.tgz",
|
| 283 |
+
"integrity": "sha512-Uqt8rPBE8SY0RK8JB1EzVOIZ32uqy8HwdxCnoCOsYrvnswqmFZ/k+9Ikidlk/ImhsdvBsloHbAlewb2IEBV/Og==",
|
| 284 |
+
"dev": true,
|
| 285 |
+
"dependencies": {
|
| 286 |
+
"@types/mime": "^1",
|
| 287 |
+
"@types/node": "*"
|
| 288 |
+
}
|
| 289 |
+
},
|
| 290 |
+
"node_modules/@types/strip-bom": {
|
| 291 |
+
"version": "3.0.0",
|
| 292 |
+
"resolved": "https://registry.npmjs.org/@types/strip-bom/-/strip-bom-3.0.0.tgz",
|
| 293 |
+
"integrity": "sha512-xevGOReSYGM7g/kUBZzPqCrR/KYAo+F0yiPc85WFTJa0MSLtyFTVTU6cJu/aV4mid7IffDIWqo69THF2o4JiEQ==",
|
| 294 |
+
"dev": true
|
| 295 |
+
},
|
| 296 |
+
"node_modules/@types/strip-json-comments": {
|
| 297 |
+
"version": "0.0.30",
|
| 298 |
+
"resolved": "https://registry.npmjs.org/@types/strip-json-comments/-/strip-json-comments-0.0.30.tgz",
|
| 299 |
+
"integrity": "sha512-7NQmHra/JILCd1QqpSzl8+mJRc8ZHz3uDm8YV1Ks9IhK0epEiTw8aIErbvH9PI+6XbqhyIQy3462nEsn7UVzjQ==",
|
| 300 |
+
"dev": true
|
| 301 |
+
},
|
| 302 |
+
"node_modules/accepts": {
|
| 303 |
+
"version": "1.3.8",
|
| 304 |
+
"resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz",
|
| 305 |
+
"integrity": "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==",
|
| 306 |
+
"dependencies": {
|
| 307 |
+
"mime-types": "~2.1.34",
|
| 308 |
+
"negotiator": "0.6.3"
|
| 309 |
+
},
|
| 310 |
+
"engines": {
|
| 311 |
+
"node": ">= 0.6"
|
| 312 |
+
}
|
| 313 |
+
},
|
| 314 |
+
"node_modules/acorn": {
|
| 315 |
+
"version": "8.17.0",
|
| 316 |
+
"resolved": "https://registry.npmjs.org/acorn/-/acorn-8.17.0.tgz",
|
| 317 |
+
"integrity": "sha512-xRQbDb9BnwDafYNn6Vwl839DYVjqXYb1XVGtWAZ1kcDc6iwAL4hg3B1dZlRiuENFeO2H53gFG3in621AdERVAg==",
|
| 318 |
+
"dev": true,
|
| 319 |
+
"bin": {
|
| 320 |
+
"acorn": "bin/acorn"
|
| 321 |
+
},
|
| 322 |
+
"engines": {
|
| 323 |
+
"node": ">=0.4.0"
|
| 324 |
+
}
|
| 325 |
+
},
|
| 326 |
+
"node_modules/acorn-walk": {
|
| 327 |
+
"version": "8.3.5",
|
| 328 |
+
"resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.3.5.tgz",
|
| 329 |
+
"integrity": "sha512-HEHNfbars9v4pgpW6SO1KSPkfoS0xVOM/9UzkJltjlsHZmJasxg8aXkuZa7SMf8vKGIBhpUsPluQSqhJFCqebw==",
|
| 330 |
+
"dev": true,
|
| 331 |
+
"dependencies": {
|
| 332 |
+
"acorn": "^8.11.0"
|
| 333 |
+
},
|
| 334 |
+
"engines": {
|
| 335 |
+
"node": ">=0.4.0"
|
| 336 |
+
}
|
| 337 |
+
},
|
| 338 |
+
"node_modules/anymatch": {
|
| 339 |
+
"version": "3.1.3",
|
| 340 |
+
"resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz",
|
| 341 |
+
"integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==",
|
| 342 |
+
"dev": true,
|
| 343 |
+
"dependencies": {
|
| 344 |
+
"normalize-path": "^3.0.0",
|
| 345 |
+
"picomatch": "^2.0.4"
|
| 346 |
+
},
|
| 347 |
+
"engines": {
|
| 348 |
+
"node": ">= 8"
|
| 349 |
+
}
|
| 350 |
+
},
|
| 351 |
+
"node_modules/arg": {
|
| 352 |
+
"version": "4.1.3",
|
| 353 |
+
"resolved": "https://registry.npmjs.org/arg/-/arg-4.1.3.tgz",
|
| 354 |
+
"integrity": "sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==",
|
| 355 |
+
"dev": true
|
| 356 |
+
},
|
| 357 |
+
"node_modules/array-flatten": {
|
| 358 |
+
"version": "1.1.1",
|
| 359 |
+
"resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz",
|
| 360 |
+
"integrity": "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg=="
|
| 361 |
+
},
|
| 362 |
+
"node_modules/balanced-match": {
|
| 363 |
+
"version": "1.0.2",
|
| 364 |
+
"resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz",
|
| 365 |
+
"integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==",
|
| 366 |
+
"dev": true
|
| 367 |
+
},
|
| 368 |
+
"node_modules/bcryptjs": {
|
| 369 |
+
"version": "2.4.3",
|
| 370 |
+
"resolved": "https://registry.npmjs.org/bcryptjs/-/bcryptjs-2.4.3.tgz",
|
| 371 |
+
"integrity": "sha512-V/Hy/X9Vt7f3BbPJEi8BdVFMByHi+jNXrYkW3huaybV/kQ0KJg0Y6PkEMbn+zeT+i+SiKZ/HMqJGIIt4LZDqNQ=="
|
| 372 |
+
},
|
| 373 |
+
"node_modules/binary-extensions": {
|
| 374 |
+
"version": "2.3.0",
|
| 375 |
+
"resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.3.0.tgz",
|
| 376 |
+
"integrity": "sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==",
|
| 377 |
+
"dev": true,
|
| 378 |
+
"engines": {
|
| 379 |
+
"node": ">=8"
|
| 380 |
+
},
|
| 381 |
+
"funding": {
|
| 382 |
+
"url": "https://github.com/sponsors/sindresorhus"
|
| 383 |
+
}
|
| 384 |
+
},
|
| 385 |
+
"node_modules/body-parser": {
|
| 386 |
+
"version": "1.20.5",
|
| 387 |
+
"resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.5.tgz",
|
| 388 |
+
"integrity": "sha512-3grm+/2tUOvu2cjJkvsIxrv/wVpfXQW4PsQHYm7yk4vfpu7Ekl6nEsYBoJUL6qDwZUx8wUhQ8tR2qz+ad9c9OA==",
|
| 389 |
+
"dependencies": {
|
| 390 |
+
"bytes": "~3.1.2",
|
| 391 |
+
"content-type": "~1.0.5",
|
| 392 |
+
"debug": "2.6.9",
|
| 393 |
+
"depd": "2.0.0",
|
| 394 |
+
"destroy": "~1.2.0",
|
| 395 |
+
"http-errors": "~2.0.1",
|
| 396 |
+
"iconv-lite": "~0.4.24",
|
| 397 |
+
"on-finished": "~2.4.1",
|
| 398 |
+
"qs": "~6.15.1",
|
| 399 |
+
"raw-body": "~2.5.3",
|
| 400 |
+
"type-is": "~1.6.18",
|
| 401 |
+
"unpipe": "~1.0.0"
|
| 402 |
+
},
|
| 403 |
+
"engines": {
|
| 404 |
+
"node": ">= 0.8",
|
| 405 |
+
"npm": "1.2.8000 || >= 1.4.16"
|
| 406 |
+
}
|
| 407 |
+
},
|
| 408 |
+
"node_modules/brace-expansion": {
|
| 409 |
+
"version": "1.1.15",
|
| 410 |
+
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.15.tgz",
|
| 411 |
+
"integrity": "sha512-EwOCDEex4quD37XhqM3omwtMoJjr//isUZz1JopUNWms+4Z2ViyM/k1YIRePpoVNnQhENnxtFjLaxNHrT7xIUg==",
|
| 412 |
+
"dev": true,
|
| 413 |
+
"dependencies": {
|
| 414 |
+
"balanced-match": "^1.0.0",
|
| 415 |
+
"concat-map": "0.0.1"
|
| 416 |
+
}
|
| 417 |
+
},
|
| 418 |
+
"node_modules/braces": {
|
| 419 |
+
"version": "3.0.3",
|
| 420 |
+
"resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz",
|
| 421 |
+
"integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==",
|
| 422 |
+
"dev": true,
|
| 423 |
+
"dependencies": {
|
| 424 |
+
"fill-range": "^7.1.1"
|
| 425 |
+
},
|
| 426 |
+
"engines": {
|
| 427 |
+
"node": ">=8"
|
| 428 |
+
}
|
| 429 |
+
},
|
| 430 |
+
"node_modules/buffer-equal-constant-time": {
|
| 431 |
+
"version": "1.0.1",
|
| 432 |
+
"resolved": "https://registry.npmjs.org/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz",
|
| 433 |
+
"integrity": "sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA=="
|
| 434 |
+
},
|
| 435 |
+
"node_modules/buffer-from": {
|
| 436 |
+
"version": "1.1.2",
|
| 437 |
+
"resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz",
|
| 438 |
+
"integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==",
|
| 439 |
+
"dev": true
|
| 440 |
+
},
|
| 441 |
+
"node_modules/bytes": {
|
| 442 |
+
"version": "3.1.2",
|
| 443 |
+
"resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz",
|
| 444 |
+
"integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==",
|
| 445 |
+
"engines": {
|
| 446 |
+
"node": ">= 0.8"
|
| 447 |
+
}
|
| 448 |
+
},
|
| 449 |
+
"node_modules/call-bind-apply-helpers": {
|
| 450 |
+
"version": "1.0.2",
|
| 451 |
+
"resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz",
|
| 452 |
+
"integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==",
|
| 453 |
+
"dependencies": {
|
| 454 |
+
"es-errors": "^1.3.0",
|
| 455 |
+
"function-bind": "^1.1.2"
|
| 456 |
+
},
|
| 457 |
+
"engines": {
|
| 458 |
+
"node": ">= 0.4"
|
| 459 |
+
}
|
| 460 |
+
},
|
| 461 |
+
"node_modules/call-bound": {
|
| 462 |
+
"version": "1.0.4",
|
| 463 |
+
"resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.4.tgz",
|
| 464 |
+
"integrity": "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==",
|
| 465 |
+
"dependencies": {
|
| 466 |
+
"call-bind-apply-helpers": "^1.0.2",
|
| 467 |
+
"get-intrinsic": "^1.3.0"
|
| 468 |
+
},
|
| 469 |
+
"engines": {
|
| 470 |
+
"node": ">= 0.4"
|
| 471 |
+
},
|
| 472 |
+
"funding": {
|
| 473 |
+
"url": "https://github.com/sponsors/ljharb"
|
| 474 |
+
}
|
| 475 |
+
},
|
| 476 |
+
"node_modules/chokidar": {
|
| 477 |
+
"version": "3.6.0",
|
| 478 |
+
"resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz",
|
| 479 |
+
"integrity": "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==",
|
| 480 |
+
"dev": true,
|
| 481 |
+
"dependencies": {
|
| 482 |
+
"anymatch": "~3.1.2",
|
| 483 |
+
"braces": "~3.0.2",
|
| 484 |
+
"glob-parent": "~5.1.2",
|
| 485 |
+
"is-binary-path": "~2.1.0",
|
| 486 |
+
"is-glob": "~4.0.1",
|
| 487 |
+
"normalize-path": "~3.0.0",
|
| 488 |
+
"readdirp": "~3.6.0"
|
| 489 |
+
},
|
| 490 |
+
"engines": {
|
| 491 |
+
"node": ">= 8.10.0"
|
| 492 |
+
},
|
| 493 |
+
"funding": {
|
| 494 |
+
"url": "https://paulmillr.com/funding/"
|
| 495 |
+
},
|
| 496 |
+
"optionalDependencies": {
|
| 497 |
+
"fsevents": "~2.3.2"
|
| 498 |
+
}
|
| 499 |
+
},
|
| 500 |
+
"node_modules/concat-map": {
|
| 501 |
+
"version": "0.0.1",
|
| 502 |
+
"resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
|
| 503 |
+
"integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==",
|
| 504 |
+
"dev": true
|
| 505 |
+
},
|
| 506 |
+
"node_modules/content-disposition": {
|
| 507 |
+
"version": "0.5.4",
|
| 508 |
+
"resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz",
|
| 509 |
+
"integrity": "sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==",
|
| 510 |
+
"dependencies": {
|
| 511 |
+
"safe-buffer": "5.2.1"
|
| 512 |
+
},
|
| 513 |
+
"engines": {
|
| 514 |
+
"node": ">= 0.6"
|
| 515 |
+
}
|
| 516 |
+
},
|
| 517 |
+
"node_modules/content-type": {
|
| 518 |
+
"version": "1.0.5",
|
| 519 |
+
"resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz",
|
| 520 |
+
"integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==",
|
| 521 |
+
"engines": {
|
| 522 |
+
"node": ">= 0.6"
|
| 523 |
+
}
|
| 524 |
+
},
|
| 525 |
+
"node_modules/cookie": {
|
| 526 |
+
"version": "0.7.2",
|
| 527 |
+
"resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.2.tgz",
|
| 528 |
+
"integrity": "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==",
|
| 529 |
+
"engines": {
|
| 530 |
+
"node": ">= 0.6"
|
| 531 |
+
}
|
| 532 |
+
},
|
| 533 |
+
"node_modules/cookie-signature": {
|
| 534 |
+
"version": "1.0.7",
|
| 535 |
+
"resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.7.tgz",
|
| 536 |
+
"integrity": "sha512-NXdYc3dLr47pBkpUCHtKSwIOQXLVn8dZEuywboCOJY/osA0wFSLlSawr3KN8qXJEyX66FcONTH8EIlVuK0yyFA=="
|
| 537 |
+
},
|
| 538 |
+
"node_modules/cors": {
|
| 539 |
+
"version": "2.8.6",
|
| 540 |
+
"resolved": "https://registry.npmjs.org/cors/-/cors-2.8.6.tgz",
|
| 541 |
+
"integrity": "sha512-tJtZBBHA6vjIAaF6EnIaq6laBBP9aq/Y3ouVJjEfoHbRBcHBAHYcMh/w8LDrk2PvIMMq8gmopa5D4V8RmbrxGw==",
|
| 542 |
+
"dependencies": {
|
| 543 |
+
"object-assign": "^4",
|
| 544 |
+
"vary": "^1"
|
| 545 |
+
},
|
| 546 |
+
"engines": {
|
| 547 |
+
"node": ">= 0.10"
|
| 548 |
+
},
|
| 549 |
+
"funding": {
|
| 550 |
+
"type": "opencollective",
|
| 551 |
+
"url": "https://opencollective.com/express"
|
| 552 |
+
}
|
| 553 |
+
},
|
| 554 |
+
"node_modules/create-require": {
|
| 555 |
+
"version": "1.1.1",
|
| 556 |
+
"resolved": "https://registry.npmjs.org/create-require/-/create-require-1.1.1.tgz",
|
| 557 |
+
"integrity": "sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==",
|
| 558 |
+
"dev": true
|
| 559 |
+
},
|
| 560 |
+
"node_modules/debug": {
|
| 561 |
+
"version": "2.6.9",
|
| 562 |
+
"resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz",
|
| 563 |
+
"integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==",
|
| 564 |
+
"dependencies": {
|
| 565 |
+
"ms": "2.0.0"
|
| 566 |
+
}
|
| 567 |
+
},
|
| 568 |
+
"node_modules/depd": {
|
| 569 |
+
"version": "2.0.0",
|
| 570 |
+
"resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz",
|
| 571 |
+
"integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==",
|
| 572 |
+
"engines": {
|
| 573 |
+
"node": ">= 0.8"
|
| 574 |
+
}
|
| 575 |
+
},
|
| 576 |
+
"node_modules/destroy": {
|
| 577 |
+
"version": "1.2.0",
|
| 578 |
+
"resolved": "https://registry.npmjs.org/destroy/-/destroy-1.2.0.tgz",
|
| 579 |
+
"integrity": "sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==",
|
| 580 |
+
"engines": {
|
| 581 |
+
"node": ">= 0.8",
|
| 582 |
+
"npm": "1.2.8000 || >= 1.4.16"
|
| 583 |
+
}
|
| 584 |
+
},
|
| 585 |
+
"node_modules/diff": {
|
| 586 |
+
"version": "4.0.4",
|
| 587 |
+
"resolved": "https://registry.npmjs.org/diff/-/diff-4.0.4.tgz",
|
| 588 |
+
"integrity": "sha512-X07nttJQkwkfKfvTPG/KSnE2OMdcUCao6+eXF3wmnIQRn2aPAHH3VxDbDOdegkd6JbPsXqShpvEOHfAT+nCNwQ==",
|
| 589 |
+
"dev": true,
|
| 590 |
+
"engines": {
|
| 591 |
+
"node": ">=0.3.1"
|
| 592 |
+
}
|
| 593 |
+
},
|
| 594 |
+
"node_modules/dotenv": {
|
| 595 |
+
"version": "16.6.1",
|
| 596 |
+
"resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.6.1.tgz",
|
| 597 |
+
"integrity": "sha512-uBq4egWHTcTt33a72vpSG0z3HnPuIl6NqYcTrKEg2azoEyl2hpW0zqlxysq2pK9HlDIHyHyakeYaYnSAwd8bow==",
|
| 598 |
+
"engines": {
|
| 599 |
+
"node": ">=12"
|
| 600 |
+
},
|
| 601 |
+
"funding": {
|
| 602 |
+
"url": "https://dotenvx.com"
|
| 603 |
+
}
|
| 604 |
+
},
|
| 605 |
+
"node_modules/dunder-proto": {
|
| 606 |
+
"version": "1.0.1",
|
| 607 |
+
"resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz",
|
| 608 |
+
"integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==",
|
| 609 |
+
"dependencies": {
|
| 610 |
+
"call-bind-apply-helpers": "^1.0.1",
|
| 611 |
+
"es-errors": "^1.3.0",
|
| 612 |
+
"gopd": "^1.2.0"
|
| 613 |
+
},
|
| 614 |
+
"engines": {
|
| 615 |
+
"node": ">= 0.4"
|
| 616 |
+
}
|
| 617 |
+
},
|
| 618 |
+
"node_modules/dynamic-dedupe": {
|
| 619 |
+
"version": "0.3.0",
|
| 620 |
+
"resolved": "https://registry.npmjs.org/dynamic-dedupe/-/dynamic-dedupe-0.3.0.tgz",
|
| 621 |
+
"integrity": "sha512-ssuANeD+z97meYOqd50e04Ze5qp4bPqo8cCkI4TRjZkzAUgIDTrXV1R8QCdINpiI+hw14+rYazvTRdQrz0/rFQ==",
|
| 622 |
+
"dev": true,
|
| 623 |
+
"dependencies": {
|
| 624 |
+
"xtend": "^4.0.0"
|
| 625 |
+
}
|
| 626 |
+
},
|
| 627 |
+
"node_modules/ecdsa-sig-formatter": {
|
| 628 |
+
"version": "1.0.11",
|
| 629 |
+
"resolved": "https://registry.npmjs.org/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.11.tgz",
|
| 630 |
+
"integrity": "sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ==",
|
| 631 |
+
"dependencies": {
|
| 632 |
+
"safe-buffer": "^5.0.1"
|
| 633 |
+
}
|
| 634 |
+
},
|
| 635 |
+
"node_modules/ee-first": {
|
| 636 |
+
"version": "1.1.1",
|
| 637 |
+
"resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz",
|
| 638 |
+
"integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow=="
|
| 639 |
+
},
|
| 640 |
+
"node_modules/encodeurl": {
|
| 641 |
+
"version": "2.0.0",
|
| 642 |
+
"resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz",
|
| 643 |
+
"integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==",
|
| 644 |
+
"engines": {
|
| 645 |
+
"node": ">= 0.8"
|
| 646 |
+
}
|
| 647 |
+
},
|
| 648 |
+
"node_modules/es-define-property": {
|
| 649 |
+
"version": "1.0.1",
|
| 650 |
+
"resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz",
|
| 651 |
+
"integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==",
|
| 652 |
+
"engines": {
|
| 653 |
+
"node": ">= 0.4"
|
| 654 |
+
}
|
| 655 |
+
},
|
| 656 |
+
"node_modules/es-errors": {
|
| 657 |
+
"version": "1.3.0",
|
| 658 |
+
"resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz",
|
| 659 |
+
"integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==",
|
| 660 |
+
"engines": {
|
| 661 |
+
"node": ">= 0.4"
|
| 662 |
+
}
|
| 663 |
+
},
|
| 664 |
+
"node_modules/es-object-atoms": {
|
| 665 |
+
"version": "1.1.2",
|
| 666 |
+
"resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.2.tgz",
|
| 667 |
+
"integrity": "sha512-HWcBoN6NileqtSydK2FqHbS/LoDd2pqrnQHLyJzBj4kOp/ky2MWMN694xOfkK8/SnUsW2DH7EfyVlydKCsm1Zw==",
|
| 668 |
+
"dependencies": {
|
| 669 |
+
"es-errors": "^1.3.0"
|
| 670 |
+
},
|
| 671 |
+
"engines": {
|
| 672 |
+
"node": ">= 0.4"
|
| 673 |
+
}
|
| 674 |
+
},
|
| 675 |
+
"node_modules/escape-html": {
|
| 676 |
+
"version": "1.0.3",
|
| 677 |
+
"resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz",
|
| 678 |
+
"integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow=="
|
| 679 |
+
},
|
| 680 |
+
"node_modules/etag": {
|
| 681 |
+
"version": "1.8.1",
|
| 682 |
+
"resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz",
|
| 683 |
+
"integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==",
|
| 684 |
+
"engines": {
|
| 685 |
+
"node": ">= 0.6"
|
| 686 |
+
}
|
| 687 |
+
},
|
| 688 |
+
"node_modules/express": {
|
| 689 |
+
"version": "4.22.2",
|
| 690 |
+
"resolved": "https://registry.npmjs.org/express/-/express-4.22.2.tgz",
|
| 691 |
+
"integrity": "sha512-IuL+Elrou2ZvCFHs18/CIzy2Nzvo25nZ1/D2eIZlz7c+QUayAcYoiM2BthCjs+EBHVpjYjcuLDAiCWgeIX3X1Q==",
|
| 692 |
+
"dependencies": {
|
| 693 |
+
"accepts": "~1.3.8",
|
| 694 |
+
"array-flatten": "1.1.1",
|
| 695 |
+
"body-parser": "~1.20.5",
|
| 696 |
+
"content-disposition": "~0.5.4",
|
| 697 |
+
"content-type": "~1.0.4",
|
| 698 |
+
"cookie": "~0.7.1",
|
| 699 |
+
"cookie-signature": "~1.0.6",
|
| 700 |
+
"debug": "2.6.9",
|
| 701 |
+
"depd": "2.0.0",
|
| 702 |
+
"encodeurl": "~2.0.0",
|
| 703 |
+
"escape-html": "~1.0.3",
|
| 704 |
+
"etag": "~1.8.1",
|
| 705 |
+
"finalhandler": "~1.3.1",
|
| 706 |
+
"fresh": "~0.5.2",
|
| 707 |
+
"http-errors": "~2.0.0",
|
| 708 |
+
"merge-descriptors": "1.0.3",
|
| 709 |
+
"methods": "~1.1.2",
|
| 710 |
+
"on-finished": "~2.4.1",
|
| 711 |
+
"parseurl": "~1.3.3",
|
| 712 |
+
"path-to-regexp": "~0.1.12",
|
| 713 |
+
"proxy-addr": "~2.0.7",
|
| 714 |
+
"qs": "~6.15.1",
|
| 715 |
+
"range-parser": "~1.2.1",
|
| 716 |
+
"safe-buffer": "5.2.1",
|
| 717 |
+
"send": "~0.19.0",
|
| 718 |
+
"serve-static": "~1.16.2",
|
| 719 |
+
"setprototypeof": "1.2.0",
|
| 720 |
+
"statuses": "~2.0.1",
|
| 721 |
+
"type-is": "~1.6.18",
|
| 722 |
+
"utils-merge": "1.0.1",
|
| 723 |
+
"vary": "~1.1.2"
|
| 724 |
+
},
|
| 725 |
+
"engines": {
|
| 726 |
+
"node": ">= 0.10.0"
|
| 727 |
+
},
|
| 728 |
+
"funding": {
|
| 729 |
+
"type": "opencollective",
|
| 730 |
+
"url": "https://opencollective.com/express"
|
| 731 |
+
}
|
| 732 |
+
},
|
| 733 |
+
"node_modules/fill-range": {
|
| 734 |
+
"version": "7.1.1",
|
| 735 |
+
"resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz",
|
| 736 |
+
"integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==",
|
| 737 |
+
"dev": true,
|
| 738 |
+
"dependencies": {
|
| 739 |
+
"to-regex-range": "^5.0.1"
|
| 740 |
+
},
|
| 741 |
+
"engines": {
|
| 742 |
+
"node": ">=8"
|
| 743 |
+
}
|
| 744 |
+
},
|
| 745 |
+
"node_modules/finalhandler": {
|
| 746 |
+
"version": "1.3.2",
|
| 747 |
+
"resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.3.2.tgz",
|
| 748 |
+
"integrity": "sha512-aA4RyPcd3badbdABGDuTXCMTtOneUCAYH/gxoYRTZlIJdF0YPWuGqiAsIrhNnnqdXGswYk6dGujem4w80UJFhg==",
|
| 749 |
+
"dependencies": {
|
| 750 |
+
"debug": "2.6.9",
|
| 751 |
+
"encodeurl": "~2.0.0",
|
| 752 |
+
"escape-html": "~1.0.3",
|
| 753 |
+
"on-finished": "~2.4.1",
|
| 754 |
+
"parseurl": "~1.3.3",
|
| 755 |
+
"statuses": "~2.0.2",
|
| 756 |
+
"unpipe": "~1.0.0"
|
| 757 |
+
},
|
| 758 |
+
"engines": {
|
| 759 |
+
"node": ">= 0.8"
|
| 760 |
+
}
|
| 761 |
+
},
|
| 762 |
+
"node_modules/forwarded": {
|
| 763 |
+
"version": "0.2.0",
|
| 764 |
+
"resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz",
|
| 765 |
+
"integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==",
|
| 766 |
+
"engines": {
|
| 767 |
+
"node": ">= 0.6"
|
| 768 |
+
}
|
| 769 |
+
},
|
| 770 |
+
"node_modules/fresh": {
|
| 771 |
+
"version": "0.5.2",
|
| 772 |
+
"resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz",
|
| 773 |
+
"integrity": "sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==",
|
| 774 |
+
"engines": {
|
| 775 |
+
"node": ">= 0.6"
|
| 776 |
+
}
|
| 777 |
+
},
|
| 778 |
+
"node_modules/fs.realpath": {
|
| 779 |
+
"version": "1.0.0",
|
| 780 |
+
"resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz",
|
| 781 |
+
"integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==",
|
| 782 |
+
"dev": true
|
| 783 |
+
},
|
| 784 |
+
"node_modules/fsevents": {
|
| 785 |
+
"version": "2.3.3",
|
| 786 |
+
"resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz",
|
| 787 |
+
"integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==",
|
| 788 |
+
"dev": true,
|
| 789 |
+
"hasInstallScript": true,
|
| 790 |
+
"optional": true,
|
| 791 |
+
"os": [
|
| 792 |
+
"darwin"
|
| 793 |
+
],
|
| 794 |
+
"engines": {
|
| 795 |
+
"node": "^8.16.0 || ^10.6.0 || >=11.0.0"
|
| 796 |
+
}
|
| 797 |
+
},
|
| 798 |
+
"node_modules/function-bind": {
|
| 799 |
+
"version": "1.1.2",
|
| 800 |
+
"resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz",
|
| 801 |
+
"integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==",
|
| 802 |
+
"funding": {
|
| 803 |
+
"url": "https://github.com/sponsors/ljharb"
|
| 804 |
+
}
|
| 805 |
+
},
|
| 806 |
+
"node_modules/get-intrinsic": {
|
| 807 |
+
"version": "1.3.0",
|
| 808 |
+
"resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz",
|
| 809 |
+
"integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==",
|
| 810 |
+
"dependencies": {
|
| 811 |
+
"call-bind-apply-helpers": "^1.0.2",
|
| 812 |
+
"es-define-property": "^1.0.1",
|
| 813 |
+
"es-errors": "^1.3.0",
|
| 814 |
+
"es-object-atoms": "^1.1.1",
|
| 815 |
+
"function-bind": "^1.1.2",
|
| 816 |
+
"get-proto": "^1.0.1",
|
| 817 |
+
"gopd": "^1.2.0",
|
| 818 |
+
"has-symbols": "^1.1.0",
|
| 819 |
+
"hasown": "^2.0.2",
|
| 820 |
+
"math-intrinsics": "^1.1.0"
|
| 821 |
+
},
|
| 822 |
+
"engines": {
|
| 823 |
+
"node": ">= 0.4"
|
| 824 |
+
},
|
| 825 |
+
"funding": {
|
| 826 |
+
"url": "https://github.com/sponsors/ljharb"
|
| 827 |
+
}
|
| 828 |
+
},
|
| 829 |
+
"node_modules/get-proto": {
|
| 830 |
+
"version": "1.0.1",
|
| 831 |
+
"resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz",
|
| 832 |
+
"integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==",
|
| 833 |
+
"dependencies": {
|
| 834 |
+
"dunder-proto": "^1.0.1",
|
| 835 |
+
"es-object-atoms": "^1.0.0"
|
| 836 |
+
},
|
| 837 |
+
"engines": {
|
| 838 |
+
"node": ">= 0.4"
|
| 839 |
+
}
|
| 840 |
+
},
|
| 841 |
+
"node_modules/glob": {
|
| 842 |
+
"version": "7.2.3",
|
| 843 |
+
"resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz",
|
| 844 |
+
"integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==",
|
| 845 |
+
"deprecated": "Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me",
|
| 846 |
+
"dev": true,
|
| 847 |
+
"dependencies": {
|
| 848 |
+
"fs.realpath": "^1.0.0",
|
| 849 |
+
"inflight": "^1.0.4",
|
| 850 |
+
"inherits": "2",
|
| 851 |
+
"minimatch": "^3.1.1",
|
| 852 |
+
"once": "^1.3.0",
|
| 853 |
+
"path-is-absolute": "^1.0.0"
|
| 854 |
+
},
|
| 855 |
+
"engines": {
|
| 856 |
+
"node": "*"
|
| 857 |
+
},
|
| 858 |
+
"funding": {
|
| 859 |
+
"url": "https://github.com/sponsors/isaacs"
|
| 860 |
+
}
|
| 861 |
+
},
|
| 862 |
+
"node_modules/glob-parent": {
|
| 863 |
+
"version": "5.1.2",
|
| 864 |
+
"resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz",
|
| 865 |
+
"integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==",
|
| 866 |
+
"dev": true,
|
| 867 |
+
"dependencies": {
|
| 868 |
+
"is-glob": "^4.0.1"
|
| 869 |
+
},
|
| 870 |
+
"engines": {
|
| 871 |
+
"node": ">= 6"
|
| 872 |
+
}
|
| 873 |
+
},
|
| 874 |
+
"node_modules/gopd": {
|
| 875 |
+
"version": "1.2.0",
|
| 876 |
+
"resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz",
|
| 877 |
+
"integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==",
|
| 878 |
+
"engines": {
|
| 879 |
+
"node": ">= 0.4"
|
| 880 |
+
},
|
| 881 |
+
"funding": {
|
| 882 |
+
"url": "https://github.com/sponsors/ljharb"
|
| 883 |
+
}
|
| 884 |
+
},
|
| 885 |
+
"node_modules/has-symbols": {
|
| 886 |
+
"version": "1.1.0",
|
| 887 |
+
"resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz",
|
| 888 |
+
"integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==",
|
| 889 |
+
"engines": {
|
| 890 |
+
"node": ">= 0.4"
|
| 891 |
+
},
|
| 892 |
+
"funding": {
|
| 893 |
+
"url": "https://github.com/sponsors/ljharb"
|
| 894 |
+
}
|
| 895 |
+
},
|
| 896 |
+
"node_modules/hasown": {
|
| 897 |
+
"version": "2.0.4",
|
| 898 |
+
"resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.4.tgz",
|
| 899 |
+
"integrity": "sha512-T2UbfbBEF32wiepXIsMlTW9+dDYC6wMh/t/vYA4tuOMKqWz/n3vr1NFSxQiyP+zk2mXsoMA/i/7qV6LKut1t1A==",
|
| 900 |
+
"dependencies": {
|
| 901 |
+
"function-bind": "^1.1.2"
|
| 902 |
+
},
|
| 903 |
+
"engines": {
|
| 904 |
+
"node": ">= 0.4"
|
| 905 |
+
}
|
| 906 |
+
},
|
| 907 |
+
"node_modules/http-errors": {
|
| 908 |
+
"version": "2.0.1",
|
| 909 |
+
"resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.1.tgz",
|
| 910 |
+
"integrity": "sha512-4FbRdAX+bSdmo4AUFuS0WNiPz8NgFt+r8ThgNWmlrjQjt1Q7ZR9+zTlce2859x4KSXrwIsaeTqDoKQmtP8pLmQ==",
|
| 911 |
+
"dependencies": {
|
| 912 |
+
"depd": "~2.0.0",
|
| 913 |
+
"inherits": "~2.0.4",
|
| 914 |
+
"setprototypeof": "~1.2.0",
|
| 915 |
+
"statuses": "~2.0.2",
|
| 916 |
+
"toidentifier": "~1.0.1"
|
| 917 |
+
},
|
| 918 |
+
"engines": {
|
| 919 |
+
"node": ">= 0.8"
|
| 920 |
+
},
|
| 921 |
+
"funding": {
|
| 922 |
+
"type": "opencollective",
|
| 923 |
+
"url": "https://opencollective.com/express"
|
| 924 |
+
}
|
| 925 |
+
},
|
| 926 |
+
"node_modules/iconv-lite": {
|
| 927 |
+
"version": "0.4.24",
|
| 928 |
+
"resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz",
|
| 929 |
+
"integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==",
|
| 930 |
+
"dependencies": {
|
| 931 |
+
"safer-buffer": ">= 2.1.2 < 3"
|
| 932 |
+
},
|
| 933 |
+
"engines": {
|
| 934 |
+
"node": ">=0.10.0"
|
| 935 |
+
}
|
| 936 |
+
},
|
| 937 |
+
"node_modules/inflight": {
|
| 938 |
+
"version": "1.0.6",
|
| 939 |
+
"resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz",
|
| 940 |
+
"integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==",
|
| 941 |
+
"deprecated": "This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful.",
|
| 942 |
+
"dev": true,
|
| 943 |
+
"dependencies": {
|
| 944 |
+
"once": "^1.3.0",
|
| 945 |
+
"wrappy": "1"
|
| 946 |
+
}
|
| 947 |
+
},
|
| 948 |
+
"node_modules/inherits": {
|
| 949 |
+
"version": "2.0.4",
|
| 950 |
+
"resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz",
|
| 951 |
+
"integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ=="
|
| 952 |
+
},
|
| 953 |
+
"node_modules/ipaddr.js": {
|
| 954 |
+
"version": "1.9.1",
|
| 955 |
+
"resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz",
|
| 956 |
+
"integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==",
|
| 957 |
+
"engines": {
|
| 958 |
+
"node": ">= 0.10"
|
| 959 |
+
}
|
| 960 |
+
},
|
| 961 |
+
"node_modules/is-binary-path": {
|
| 962 |
+
"version": "2.1.0",
|
| 963 |
+
"resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz",
|
| 964 |
+
"integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==",
|
| 965 |
+
"dev": true,
|
| 966 |
+
"dependencies": {
|
| 967 |
+
"binary-extensions": "^2.0.0"
|
| 968 |
+
},
|
| 969 |
+
"engines": {
|
| 970 |
+
"node": ">=8"
|
| 971 |
+
}
|
| 972 |
+
},
|
| 973 |
+
"node_modules/is-core-module": {
|
| 974 |
+
"version": "2.16.2",
|
| 975 |
+
"resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.16.2.tgz",
|
| 976 |
+
"integrity": "sha512-evOr8xfXKxE6qSR0hSXL2r3sd7ALj8+7jQEUvPYcm5sgZFdJ+AYzT6yNmJenvIYQBgIGwfwz08sL8zoL7yq2BA==",
|
| 977 |
+
"dev": true,
|
| 978 |
+
"dependencies": {
|
| 979 |
+
"hasown": "^2.0.3"
|
| 980 |
+
},
|
| 981 |
+
"engines": {
|
| 982 |
+
"node": ">= 0.4"
|
| 983 |
+
},
|
| 984 |
+
"funding": {
|
| 985 |
+
"url": "https://github.com/sponsors/ljharb"
|
| 986 |
+
}
|
| 987 |
+
},
|
| 988 |
+
"node_modules/is-extglob": {
|
| 989 |
+
"version": "2.1.1",
|
| 990 |
+
"resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz",
|
| 991 |
+
"integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==",
|
| 992 |
+
"dev": true,
|
| 993 |
+
"engines": {
|
| 994 |
+
"node": ">=0.10.0"
|
| 995 |
+
}
|
| 996 |
+
},
|
| 997 |
+
"node_modules/is-glob": {
|
| 998 |
+
"version": "4.0.3",
|
| 999 |
+
"resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz",
|
| 1000 |
+
"integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==",
|
| 1001 |
+
"dev": true,
|
| 1002 |
+
"dependencies": {
|
| 1003 |
+
"is-extglob": "^2.1.1"
|
| 1004 |
+
},
|
| 1005 |
+
"engines": {
|
| 1006 |
+
"node": ">=0.10.0"
|
| 1007 |
+
}
|
| 1008 |
+
},
|
| 1009 |
+
"node_modules/is-number": {
|
| 1010 |
+
"version": "7.0.0",
|
| 1011 |
+
"resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz",
|
| 1012 |
+
"integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==",
|
| 1013 |
+
"dev": true,
|
| 1014 |
+
"engines": {
|
| 1015 |
+
"node": ">=0.12.0"
|
| 1016 |
+
}
|
| 1017 |
+
},
|
| 1018 |
+
"node_modules/jsonwebtoken": {
|
| 1019 |
+
"version": "9.0.3",
|
| 1020 |
+
"resolved": "https://registry.npmjs.org/jsonwebtoken/-/jsonwebtoken-9.0.3.tgz",
|
| 1021 |
+
"integrity": "sha512-MT/xP0CrubFRNLNKvxJ2BYfy53Zkm++5bX9dtuPbqAeQpTVe0MQTFhao8+Cp//EmJp244xt6Drw/GVEGCUj40g==",
|
| 1022 |
+
"dependencies": {
|
| 1023 |
+
"jws": "^4.0.1",
|
| 1024 |
+
"lodash.includes": "^4.3.0",
|
| 1025 |
+
"lodash.isboolean": "^3.0.3",
|
| 1026 |
+
"lodash.isinteger": "^4.0.4",
|
| 1027 |
+
"lodash.isnumber": "^3.0.3",
|
| 1028 |
+
"lodash.isplainobject": "^4.0.6",
|
| 1029 |
+
"lodash.isstring": "^4.0.1",
|
| 1030 |
+
"lodash.once": "^4.0.0",
|
| 1031 |
+
"ms": "^2.1.1",
|
| 1032 |
+
"semver": "^7.5.4"
|
| 1033 |
+
},
|
| 1034 |
+
"engines": {
|
| 1035 |
+
"node": ">=12",
|
| 1036 |
+
"npm": ">=6"
|
| 1037 |
+
}
|
| 1038 |
+
},
|
| 1039 |
+
"node_modules/jsonwebtoken/node_modules/ms": {
|
| 1040 |
+
"version": "2.1.3",
|
| 1041 |
+
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
|
| 1042 |
+
"integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="
|
| 1043 |
+
},
|
| 1044 |
+
"node_modules/jwa": {
|
| 1045 |
+
"version": "2.0.1",
|
| 1046 |
+
"resolved": "https://registry.npmjs.org/jwa/-/jwa-2.0.1.tgz",
|
| 1047 |
+
"integrity": "sha512-hRF04fqJIP8Abbkq5NKGN0Bbr3JxlQ+qhZufXVr0DvujKy93ZCbXZMHDL4EOtodSbCWxOqR8MS1tXA5hwqCXDg==",
|
| 1048 |
+
"dependencies": {
|
| 1049 |
+
"buffer-equal-constant-time": "^1.0.1",
|
| 1050 |
+
"ecdsa-sig-formatter": "1.0.11",
|
| 1051 |
+
"safe-buffer": "^5.0.1"
|
| 1052 |
+
}
|
| 1053 |
+
},
|
| 1054 |
+
"node_modules/jws": {
|
| 1055 |
+
"version": "4.0.1",
|
| 1056 |
+
"resolved": "https://registry.npmjs.org/jws/-/jws-4.0.1.tgz",
|
| 1057 |
+
"integrity": "sha512-EKI/M/yqPncGUUh44xz0PxSidXFr/+r0pA70+gIYhjv+et7yxM+s29Y+VGDkovRofQem0fs7Uvf4+YmAdyRduA==",
|
| 1058 |
+
"dependencies": {
|
| 1059 |
+
"jwa": "^2.0.1",
|
| 1060 |
+
"safe-buffer": "^5.0.1"
|
| 1061 |
+
}
|
| 1062 |
+
},
|
| 1063 |
+
"node_modules/lodash.includes": {
|
| 1064 |
+
"version": "4.3.0",
|
| 1065 |
+
"resolved": "https://registry.npmjs.org/lodash.includes/-/lodash.includes-4.3.0.tgz",
|
| 1066 |
+
"integrity": "sha512-W3Bx6mdkRTGtlJISOvVD/lbqjTlPPUDTMnlXZFnVwi9NKJ6tiAk6LVdlhZMm17VZisqhKcgzpO5Wz91PCt5b0w=="
|
| 1067 |
+
},
|
| 1068 |
+
"node_modules/lodash.isboolean": {
|
| 1069 |
+
"version": "3.0.3",
|
| 1070 |
+
"resolved": "https://registry.npmjs.org/lodash.isboolean/-/lodash.isboolean-3.0.3.tgz",
|
| 1071 |
+
"integrity": "sha512-Bz5mupy2SVbPHURB98VAcw+aHh4vRV5IPNhILUCsOzRmsTmSQ17jIuqopAentWoehktxGd9e/hbIXq980/1QJg=="
|
| 1072 |
+
},
|
| 1073 |
+
"node_modules/lodash.isinteger": {
|
| 1074 |
+
"version": "4.0.4",
|
| 1075 |
+
"resolved": "https://registry.npmjs.org/lodash.isinteger/-/lodash.isinteger-4.0.4.tgz",
|
| 1076 |
+
"integrity": "sha512-DBwtEWN2caHQ9/imiNeEA5ys1JoRtRfY3d7V9wkqtbycnAmTvRRmbHKDV4a0EYc678/dia0jrte4tjYwVBaZUA=="
|
| 1077 |
+
},
|
| 1078 |
+
"node_modules/lodash.isnumber": {
|
| 1079 |
+
"version": "3.0.3",
|
| 1080 |
+
"resolved": "https://registry.npmjs.org/lodash.isnumber/-/lodash.isnumber-3.0.3.tgz",
|
| 1081 |
+
"integrity": "sha512-QYqzpfwO3/CWf3XP+Z+tkQsfaLL/EnUlXWVkIk5FUPc4sBdTehEqZONuyRt2P67PXAk+NXmTBcc97zw9t1FQrw=="
|
| 1082 |
+
},
|
| 1083 |
+
"node_modules/lodash.isplainobject": {
|
| 1084 |
+
"version": "4.0.6",
|
| 1085 |
+
"resolved": "https://registry.npmjs.org/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz",
|
| 1086 |
+
"integrity": "sha512-oSXzaWypCMHkPC3NvBEaPHf0KsA5mvPrOPgQWDsbg8n7orZ290M0BmC/jgRZ4vcJ6DTAhjrsSYgdsW/F+MFOBA=="
|
| 1087 |
+
},
|
| 1088 |
+
"node_modules/lodash.isstring": {
|
| 1089 |
+
"version": "4.0.1",
|
| 1090 |
+
"resolved": "https://registry.npmjs.org/lodash.isstring/-/lodash.isstring-4.0.1.tgz",
|
| 1091 |
+
"integrity": "sha512-0wJxfxH1wgO3GrbuP+dTTk7op+6L41QCXbGINEmD+ny/G/eCqGzxyCsh7159S+mgDDcoarnBw6PC1PS5+wUGgw=="
|
| 1092 |
+
},
|
| 1093 |
+
"node_modules/lodash.once": {
|
| 1094 |
+
"version": "4.1.1",
|
| 1095 |
+
"resolved": "https://registry.npmjs.org/lodash.once/-/lodash.once-4.1.1.tgz",
|
| 1096 |
+
"integrity": "sha512-Sb487aTOCr9drQVL8pIxOzVhafOjZN9UU54hiN8PU3uAiSV7lx1yYNpbNmex2PK6dSJoNTSJUUswT651yww3Mg=="
|
| 1097 |
+
},
|
| 1098 |
+
"node_modules/make-error": {
|
| 1099 |
+
"version": "1.3.6",
|
| 1100 |
+
"resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz",
|
| 1101 |
+
"integrity": "sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==",
|
| 1102 |
+
"dev": true
|
| 1103 |
+
},
|
| 1104 |
+
"node_modules/math-intrinsics": {
|
| 1105 |
+
"version": "1.1.0",
|
| 1106 |
+
"resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz",
|
| 1107 |
+
"integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==",
|
| 1108 |
+
"engines": {
|
| 1109 |
+
"node": ">= 0.4"
|
| 1110 |
+
}
|
| 1111 |
+
},
|
| 1112 |
+
"node_modules/media-typer": {
|
| 1113 |
+
"version": "0.3.0",
|
| 1114 |
+
"resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz",
|
| 1115 |
+
"integrity": "sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==",
|
| 1116 |
+
"engines": {
|
| 1117 |
+
"node": ">= 0.6"
|
| 1118 |
+
}
|
| 1119 |
+
},
|
| 1120 |
+
"node_modules/merge-descriptors": {
|
| 1121 |
+
"version": "1.0.3",
|
| 1122 |
+
"resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.3.tgz",
|
| 1123 |
+
"integrity": "sha512-gaNvAS7TZ897/rVaZ0nMtAyxNyi/pdbjbAwUpFQpN70GqnVfOiXpeUUMKRBmzXaSQ8DdTX4/0ms62r2K+hE6mQ==",
|
| 1124 |
+
"funding": {
|
| 1125 |
+
"url": "https://github.com/sponsors/sindresorhus"
|
| 1126 |
+
}
|
| 1127 |
+
},
|
| 1128 |
+
"node_modules/methods": {
|
| 1129 |
+
"version": "1.1.2",
|
| 1130 |
+
"resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz",
|
| 1131 |
+
"integrity": "sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w==",
|
| 1132 |
+
"engines": {
|
| 1133 |
+
"node": ">= 0.6"
|
| 1134 |
+
}
|
| 1135 |
+
},
|
| 1136 |
+
"node_modules/mime": {
|
| 1137 |
+
"version": "1.6.0",
|
| 1138 |
+
"resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz",
|
| 1139 |
+
"integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==",
|
| 1140 |
+
"bin": {
|
| 1141 |
+
"mime": "cli.js"
|
| 1142 |
+
},
|
| 1143 |
+
"engines": {
|
| 1144 |
+
"node": ">=4"
|
| 1145 |
+
}
|
| 1146 |
+
},
|
| 1147 |
+
"node_modules/mime-db": {
|
| 1148 |
+
"version": "1.52.0",
|
| 1149 |
+
"resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz",
|
| 1150 |
+
"integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==",
|
| 1151 |
+
"engines": {
|
| 1152 |
+
"node": ">= 0.6"
|
| 1153 |
+
}
|
| 1154 |
+
},
|
| 1155 |
+
"node_modules/mime-types": {
|
| 1156 |
+
"version": "2.1.35",
|
| 1157 |
+
"resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz",
|
| 1158 |
+
"integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==",
|
| 1159 |
+
"dependencies": {
|
| 1160 |
+
"mime-db": "1.52.0"
|
| 1161 |
+
},
|
| 1162 |
+
"engines": {
|
| 1163 |
+
"node": ">= 0.6"
|
| 1164 |
+
}
|
| 1165 |
+
},
|
| 1166 |
+
"node_modules/minimatch": {
|
| 1167 |
+
"version": "3.1.5",
|
| 1168 |
+
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz",
|
| 1169 |
+
"integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==",
|
| 1170 |
+
"dev": true,
|
| 1171 |
+
"dependencies": {
|
| 1172 |
+
"brace-expansion": "^1.1.7"
|
| 1173 |
+
},
|
| 1174 |
+
"engines": {
|
| 1175 |
+
"node": "*"
|
| 1176 |
+
}
|
| 1177 |
+
},
|
| 1178 |
+
"node_modules/minimist": {
|
| 1179 |
+
"version": "1.2.8",
|
| 1180 |
+
"resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz",
|
| 1181 |
+
"integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==",
|
| 1182 |
+
"dev": true,
|
| 1183 |
+
"funding": {
|
| 1184 |
+
"url": "https://github.com/sponsors/ljharb"
|
| 1185 |
+
}
|
| 1186 |
+
},
|
| 1187 |
+
"node_modules/mkdirp": {
|
| 1188 |
+
"version": "1.0.4",
|
| 1189 |
+
"resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz",
|
| 1190 |
+
"integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==",
|
| 1191 |
+
"dev": true,
|
| 1192 |
+
"bin": {
|
| 1193 |
+
"mkdirp": "bin/cmd.js"
|
| 1194 |
+
},
|
| 1195 |
+
"engines": {
|
| 1196 |
+
"node": ">=10"
|
| 1197 |
+
}
|
| 1198 |
+
},
|
| 1199 |
+
"node_modules/ms": {
|
| 1200 |
+
"version": "2.0.0",
|
| 1201 |
+
"resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
|
| 1202 |
+
"integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A=="
|
| 1203 |
+
},
|
| 1204 |
+
"node_modules/negotiator": {
|
| 1205 |
+
"version": "0.6.3",
|
| 1206 |
+
"resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz",
|
| 1207 |
+
"integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==",
|
| 1208 |
+
"engines": {
|
| 1209 |
+
"node": ">= 0.6"
|
| 1210 |
+
}
|
| 1211 |
+
},
|
| 1212 |
+
"node_modules/normalize-path": {
|
| 1213 |
+
"version": "3.0.0",
|
| 1214 |
+
"resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz",
|
| 1215 |
+
"integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==",
|
| 1216 |
+
"dev": true,
|
| 1217 |
+
"engines": {
|
| 1218 |
+
"node": ">=0.10.0"
|
| 1219 |
+
}
|
| 1220 |
+
},
|
| 1221 |
+
"node_modules/object-assign": {
|
| 1222 |
+
"version": "4.1.1",
|
| 1223 |
+
"resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz",
|
| 1224 |
+
"integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==",
|
| 1225 |
+
"engines": {
|
| 1226 |
+
"node": ">=0.10.0"
|
| 1227 |
+
}
|
| 1228 |
+
},
|
| 1229 |
+
"node_modules/object-inspect": {
|
| 1230 |
+
"version": "1.13.4",
|
| 1231 |
+
"resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.4.tgz",
|
| 1232 |
+
"integrity": "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==",
|
| 1233 |
+
"engines": {
|
| 1234 |
+
"node": ">= 0.4"
|
| 1235 |
+
},
|
| 1236 |
+
"funding": {
|
| 1237 |
+
"url": "https://github.com/sponsors/ljharb"
|
| 1238 |
+
}
|
| 1239 |
+
},
|
| 1240 |
+
"node_modules/on-finished": {
|
| 1241 |
+
"version": "2.4.1",
|
| 1242 |
+
"resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz",
|
| 1243 |
+
"integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==",
|
| 1244 |
+
"dependencies": {
|
| 1245 |
+
"ee-first": "1.1.1"
|
| 1246 |
+
},
|
| 1247 |
+
"engines": {
|
| 1248 |
+
"node": ">= 0.8"
|
| 1249 |
+
}
|
| 1250 |
+
},
|
| 1251 |
+
"node_modules/once": {
|
| 1252 |
+
"version": "1.4.0",
|
| 1253 |
+
"resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz",
|
| 1254 |
+
"integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==",
|
| 1255 |
+
"dev": true,
|
| 1256 |
+
"dependencies": {
|
| 1257 |
+
"wrappy": "1"
|
| 1258 |
+
}
|
| 1259 |
+
},
|
| 1260 |
+
"node_modules/parseurl": {
|
| 1261 |
+
"version": "1.3.3",
|
| 1262 |
+
"resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz",
|
| 1263 |
+
"integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==",
|
| 1264 |
+
"engines": {
|
| 1265 |
+
"node": ">= 0.8"
|
| 1266 |
+
}
|
| 1267 |
+
},
|
| 1268 |
+
"node_modules/path-is-absolute": {
|
| 1269 |
+
"version": "1.0.1",
|
| 1270 |
+
"resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz",
|
| 1271 |
+
"integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==",
|
| 1272 |
+
"dev": true,
|
| 1273 |
+
"engines": {
|
| 1274 |
+
"node": ">=0.10.0"
|
| 1275 |
+
}
|
| 1276 |
+
},
|
| 1277 |
+
"node_modules/path-parse": {
|
| 1278 |
+
"version": "1.0.7",
|
| 1279 |
+
"resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz",
|
| 1280 |
+
"integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==",
|
| 1281 |
+
"dev": true
|
| 1282 |
+
},
|
| 1283 |
+
"node_modules/path-to-regexp": {
|
| 1284 |
+
"version": "0.1.13",
|
| 1285 |
+
"resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.13.tgz",
|
| 1286 |
+
"integrity": "sha512-A/AGNMFN3c8bOlvV9RreMdrv7jsmF9XIfDeCd87+I8RNg6s78BhJxMu69NEMHBSJFxKidViTEdruRwEk/WIKqA=="
|
| 1287 |
+
},
|
| 1288 |
+
"node_modules/picomatch": {
|
| 1289 |
+
"version": "2.3.2",
|
| 1290 |
+
"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.2.tgz",
|
| 1291 |
+
"integrity": "sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA==",
|
| 1292 |
+
"dev": true,
|
| 1293 |
+
"engines": {
|
| 1294 |
+
"node": ">=8.6"
|
| 1295 |
+
},
|
| 1296 |
+
"funding": {
|
| 1297 |
+
"url": "https://github.com/sponsors/jonschlinkert"
|
| 1298 |
+
}
|
| 1299 |
+
},
|
| 1300 |
+
"node_modules/prisma": {
|
| 1301 |
+
"version": "5.22.0",
|
| 1302 |
+
"resolved": "https://registry.npmjs.org/prisma/-/prisma-5.22.0.tgz",
|
| 1303 |
+
"integrity": "sha512-vtpjW3XuYCSnMsNVBjLMNkTj6OZbudcPPTPYHqX0CJfpcdWciI1dM8uHETwmDxxiqEwCIE6WvXucWUetJgfu/A==",
|
| 1304 |
+
"devOptional": true,
|
| 1305 |
+
"hasInstallScript": true,
|
| 1306 |
+
"dependencies": {
|
| 1307 |
+
"@prisma/engines": "5.22.0"
|
| 1308 |
+
},
|
| 1309 |
+
"bin": {
|
| 1310 |
+
"prisma": "build/index.js"
|
| 1311 |
+
},
|
| 1312 |
+
"engines": {
|
| 1313 |
+
"node": ">=16.13"
|
| 1314 |
+
},
|
| 1315 |
+
"optionalDependencies": {
|
| 1316 |
+
"fsevents": "2.3.3"
|
| 1317 |
+
}
|
| 1318 |
+
},
|
| 1319 |
+
"node_modules/proxy-addr": {
|
| 1320 |
+
"version": "2.0.7",
|
| 1321 |
+
"resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz",
|
| 1322 |
+
"integrity": "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==",
|
| 1323 |
+
"dependencies": {
|
| 1324 |
+
"forwarded": "0.2.0",
|
| 1325 |
+
"ipaddr.js": "1.9.1"
|
| 1326 |
+
},
|
| 1327 |
+
"engines": {
|
| 1328 |
+
"node": ">= 0.10"
|
| 1329 |
+
}
|
| 1330 |
+
},
|
| 1331 |
+
"node_modules/qs": {
|
| 1332 |
+
"version": "6.15.3",
|
| 1333 |
+
"resolved": "https://registry.npmjs.org/qs/-/qs-6.15.3.tgz",
|
| 1334 |
+
"integrity": "sha512-O9gl3zCl5h5blw1KGUzQKhA5oUXSl8rwUIM5o0S3nCXMliSvy5Dzx7/DJcI+SwgICv+IneSZwhBh1oSyEHA71A==",
|
| 1335 |
+
"dependencies": {
|
| 1336 |
+
"es-define-property": "^1.0.1",
|
| 1337 |
+
"side-channel": "^1.1.1"
|
| 1338 |
+
},
|
| 1339 |
+
"engines": {
|
| 1340 |
+
"node": ">=0.6"
|
| 1341 |
+
},
|
| 1342 |
+
"funding": {
|
| 1343 |
+
"url": "https://github.com/sponsors/ljharb"
|
| 1344 |
+
}
|
| 1345 |
+
},
|
| 1346 |
+
"node_modules/range-parser": {
|
| 1347 |
+
"version": "1.2.1",
|
| 1348 |
+
"resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz",
|
| 1349 |
+
"integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==",
|
| 1350 |
+
"engines": {
|
| 1351 |
+
"node": ">= 0.6"
|
| 1352 |
+
}
|
| 1353 |
+
},
|
| 1354 |
+
"node_modules/raw-body": {
|
| 1355 |
+
"version": "2.5.3",
|
| 1356 |
+
"resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.3.tgz",
|
| 1357 |
+
"integrity": "sha512-s4VSOf6yN0rvbRZGxs8Om5CWj6seneMwK3oDb4lWDH0UPhWcxwOWw5+qk24bxq87szX1ydrwylIOp2uG1ojUpA==",
|
| 1358 |
+
"dependencies": {
|
| 1359 |
+
"bytes": "~3.1.2",
|
| 1360 |
+
"http-errors": "~2.0.1",
|
| 1361 |
+
"iconv-lite": "~0.4.24",
|
| 1362 |
+
"unpipe": "~1.0.0"
|
| 1363 |
+
},
|
| 1364 |
+
"engines": {
|
| 1365 |
+
"node": ">= 0.8"
|
| 1366 |
+
}
|
| 1367 |
+
},
|
| 1368 |
+
"node_modules/readdirp": {
|
| 1369 |
+
"version": "3.6.0",
|
| 1370 |
+
"resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz",
|
| 1371 |
+
"integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==",
|
| 1372 |
+
"dev": true,
|
| 1373 |
+
"dependencies": {
|
| 1374 |
+
"picomatch": "^2.2.1"
|
| 1375 |
+
},
|
| 1376 |
+
"engines": {
|
| 1377 |
+
"node": ">=8.10.0"
|
| 1378 |
+
}
|
| 1379 |
+
},
|
| 1380 |
+
"node_modules/resolve": {
|
| 1381 |
+
"version": "1.22.12",
|
| 1382 |
+
"resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.12.tgz",
|
| 1383 |
+
"integrity": "sha512-TyeJ1zif53BPfHootBGwPRYT1RUt6oGWsaQr8UyZW/eAm9bKoijtvruSDEmZHm92CwS9nj7/fWttqPCgzep8CA==",
|
| 1384 |
+
"dev": true,
|
| 1385 |
+
"dependencies": {
|
| 1386 |
+
"es-errors": "^1.3.0",
|
| 1387 |
+
"is-core-module": "^2.16.1",
|
| 1388 |
+
"path-parse": "^1.0.7",
|
| 1389 |
+
"supports-preserve-symlinks-flag": "^1.0.0"
|
| 1390 |
+
},
|
| 1391 |
+
"bin": {
|
| 1392 |
+
"resolve": "bin/resolve"
|
| 1393 |
+
},
|
| 1394 |
+
"engines": {
|
| 1395 |
+
"node": ">= 0.4"
|
| 1396 |
+
},
|
| 1397 |
+
"funding": {
|
| 1398 |
+
"url": "https://github.com/sponsors/ljharb"
|
| 1399 |
+
}
|
| 1400 |
+
},
|
| 1401 |
+
"node_modules/rimraf": {
|
| 1402 |
+
"version": "2.7.1",
|
| 1403 |
+
"resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.7.1.tgz",
|
| 1404 |
+
"integrity": "sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==",
|
| 1405 |
+
"deprecated": "Rimraf versions prior to v4 are no longer supported",
|
| 1406 |
+
"dev": true,
|
| 1407 |
+
"dependencies": {
|
| 1408 |
+
"glob": "^7.1.3"
|
| 1409 |
+
},
|
| 1410 |
+
"bin": {
|
| 1411 |
+
"rimraf": "bin.js"
|
| 1412 |
+
}
|
| 1413 |
+
},
|
| 1414 |
+
"node_modules/safe-buffer": {
|
| 1415 |
+
"version": "5.2.1",
|
| 1416 |
+
"resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz",
|
| 1417 |
+
"integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==",
|
| 1418 |
+
"funding": [
|
| 1419 |
+
{
|
| 1420 |
+
"type": "github",
|
| 1421 |
+
"url": "https://github.com/sponsors/feross"
|
| 1422 |
+
},
|
| 1423 |
+
{
|
| 1424 |
+
"type": "patreon",
|
| 1425 |
+
"url": "https://www.patreon.com/feross"
|
| 1426 |
+
},
|
| 1427 |
+
{
|
| 1428 |
+
"type": "consulting",
|
| 1429 |
+
"url": "https://feross.org/support"
|
| 1430 |
+
}
|
| 1431 |
+
]
|
| 1432 |
+
},
|
| 1433 |
+
"node_modules/safer-buffer": {
|
| 1434 |
+
"version": "2.1.2",
|
| 1435 |
+
"resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz",
|
| 1436 |
+
"integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg=="
|
| 1437 |
+
},
|
| 1438 |
+
"node_modules/semver": {
|
| 1439 |
+
"version": "7.8.5",
|
| 1440 |
+
"resolved": "https://registry.npmjs.org/semver/-/semver-7.8.5.tgz",
|
| 1441 |
+
"integrity": "sha512-Y7/KDsb8LjooZpwaqGyulO6DQlksgCncchHGk+sZIY4SBvUocMBEFH5Ur1fI4dV+Jvl0w6cjvucaIi40puRioA==",
|
| 1442 |
+
"bin": {
|
| 1443 |
+
"semver": "bin/semver.js"
|
| 1444 |
+
},
|
| 1445 |
+
"engines": {
|
| 1446 |
+
"node": ">=10"
|
| 1447 |
+
}
|
| 1448 |
+
},
|
| 1449 |
+
"node_modules/send": {
|
| 1450 |
+
"version": "0.19.2",
|
| 1451 |
+
"resolved": "https://registry.npmjs.org/send/-/send-0.19.2.tgz",
|
| 1452 |
+
"integrity": "sha512-VMbMxbDeehAxpOtWJXlcUS5E8iXh6QmN+BkRX1GARS3wRaXEEgzCcB10gTQazO42tpNIya8xIyNx8fll1OFPrg==",
|
| 1453 |
+
"dependencies": {
|
| 1454 |
+
"debug": "2.6.9",
|
| 1455 |
+
"depd": "2.0.0",
|
| 1456 |
+
"destroy": "1.2.0",
|
| 1457 |
+
"encodeurl": "~2.0.0",
|
| 1458 |
+
"escape-html": "~1.0.3",
|
| 1459 |
+
"etag": "~1.8.1",
|
| 1460 |
+
"fresh": "~0.5.2",
|
| 1461 |
+
"http-errors": "~2.0.1",
|
| 1462 |
+
"mime": "1.6.0",
|
| 1463 |
+
"ms": "2.1.3",
|
| 1464 |
+
"on-finished": "~2.4.1",
|
| 1465 |
+
"range-parser": "~1.2.1",
|
| 1466 |
+
"statuses": "~2.0.2"
|
| 1467 |
+
},
|
| 1468 |
+
"engines": {
|
| 1469 |
+
"node": ">= 0.8.0"
|
| 1470 |
+
}
|
| 1471 |
+
},
|
| 1472 |
+
"node_modules/send/node_modules/ms": {
|
| 1473 |
+
"version": "2.1.3",
|
| 1474 |
+
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
|
| 1475 |
+
"integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="
|
| 1476 |
+
},
|
| 1477 |
+
"node_modules/serve-static": {
|
| 1478 |
+
"version": "1.16.3",
|
| 1479 |
+
"resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.16.3.tgz",
|
| 1480 |
+
"integrity": "sha512-x0RTqQel6g5SY7Lg6ZreMmsOzncHFU7nhnRWkKgWuMTu5NN0DR5oruckMqRvacAN9d5w6ARnRBXl9xhDCgfMeA==",
|
| 1481 |
+
"dependencies": {
|
| 1482 |
+
"encodeurl": "~2.0.0",
|
| 1483 |
+
"escape-html": "~1.0.3",
|
| 1484 |
+
"parseurl": "~1.3.3",
|
| 1485 |
+
"send": "~0.19.1"
|
| 1486 |
+
},
|
| 1487 |
+
"engines": {
|
| 1488 |
+
"node": ">= 0.8.0"
|
| 1489 |
+
}
|
| 1490 |
+
},
|
| 1491 |
+
"node_modules/setprototypeof": {
|
| 1492 |
+
"version": "1.2.0",
|
| 1493 |
+
"resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz",
|
| 1494 |
+
"integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw=="
|
| 1495 |
+
},
|
| 1496 |
+
"node_modules/side-channel": {
|
| 1497 |
+
"version": "1.1.1",
|
| 1498 |
+
"resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.1.tgz",
|
| 1499 |
+
"integrity": "sha512-6x6dK6zJdpTzF4sQeNYxwtvBzf6Eg4GtlesS94HOvTudUeyK2WXAaIfmDgsyslYrRBeFIlsi54AYsFGUuhmvrQ==",
|
| 1500 |
+
"dependencies": {
|
| 1501 |
+
"es-errors": "^1.3.0",
|
| 1502 |
+
"object-inspect": "^1.13.4",
|
| 1503 |
+
"side-channel-list": "^1.0.1",
|
| 1504 |
+
"side-channel-map": "^1.0.1",
|
| 1505 |
+
"side-channel-weakmap": "^1.0.2"
|
| 1506 |
+
},
|
| 1507 |
+
"engines": {
|
| 1508 |
+
"node": ">= 0.4"
|
| 1509 |
+
},
|
| 1510 |
+
"funding": {
|
| 1511 |
+
"url": "https://github.com/sponsors/ljharb"
|
| 1512 |
+
}
|
| 1513 |
+
},
|
| 1514 |
+
"node_modules/side-channel-list": {
|
| 1515 |
+
"version": "1.0.1",
|
| 1516 |
+
"resolved": "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.1.tgz",
|
| 1517 |
+
"integrity": "sha512-mjn/0bi/oUURjc5Xl7IaWi/OJJJumuoJFQJfDDyO46+hBWsfaVM65TBHq2eoZBhzl9EchxOijpkbRC8SVBQU0w==",
|
| 1518 |
+
"dependencies": {
|
| 1519 |
+
"es-errors": "^1.3.0",
|
| 1520 |
+
"object-inspect": "^1.13.4"
|
| 1521 |
+
},
|
| 1522 |
+
"engines": {
|
| 1523 |
+
"node": ">= 0.4"
|
| 1524 |
+
},
|
| 1525 |
+
"funding": {
|
| 1526 |
+
"url": "https://github.com/sponsors/ljharb"
|
| 1527 |
+
}
|
| 1528 |
+
},
|
| 1529 |
+
"node_modules/side-channel-map": {
|
| 1530 |
+
"version": "1.0.1",
|
| 1531 |
+
"resolved": "https://registry.npmjs.org/side-channel-map/-/side-channel-map-1.0.1.tgz",
|
| 1532 |
+
"integrity": "sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==",
|
| 1533 |
+
"dependencies": {
|
| 1534 |
+
"call-bound": "^1.0.2",
|
| 1535 |
+
"es-errors": "^1.3.0",
|
| 1536 |
+
"get-intrinsic": "^1.2.5",
|
| 1537 |
+
"object-inspect": "^1.13.3"
|
| 1538 |
+
},
|
| 1539 |
+
"engines": {
|
| 1540 |
+
"node": ">= 0.4"
|
| 1541 |
+
},
|
| 1542 |
+
"funding": {
|
| 1543 |
+
"url": "https://github.com/sponsors/ljharb"
|
| 1544 |
+
}
|
| 1545 |
+
},
|
| 1546 |
+
"node_modules/side-channel-weakmap": {
|
| 1547 |
+
"version": "1.0.2",
|
| 1548 |
+
"resolved": "https://registry.npmjs.org/side-channel-weakmap/-/side-channel-weakmap-1.0.2.tgz",
|
| 1549 |
+
"integrity": "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==",
|
| 1550 |
+
"dependencies": {
|
| 1551 |
+
"call-bound": "^1.0.2",
|
| 1552 |
+
"es-errors": "^1.3.0",
|
| 1553 |
+
"get-intrinsic": "^1.2.5",
|
| 1554 |
+
"object-inspect": "^1.13.3",
|
| 1555 |
+
"side-channel-map": "^1.0.1"
|
| 1556 |
+
},
|
| 1557 |
+
"engines": {
|
| 1558 |
+
"node": ">= 0.4"
|
| 1559 |
+
},
|
| 1560 |
+
"funding": {
|
| 1561 |
+
"url": "https://github.com/sponsors/ljharb"
|
| 1562 |
+
}
|
| 1563 |
+
},
|
| 1564 |
+
"node_modules/source-map": {
|
| 1565 |
+
"version": "0.6.1",
|
| 1566 |
+
"resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
|
| 1567 |
+
"integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==",
|
| 1568 |
+
"dev": true,
|
| 1569 |
+
"engines": {
|
| 1570 |
+
"node": ">=0.10.0"
|
| 1571 |
+
}
|
| 1572 |
+
},
|
| 1573 |
+
"node_modules/source-map-support": {
|
| 1574 |
+
"version": "0.5.21",
|
| 1575 |
+
"resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz",
|
| 1576 |
+
"integrity": "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==",
|
| 1577 |
+
"dev": true,
|
| 1578 |
+
"dependencies": {
|
| 1579 |
+
"buffer-from": "^1.0.0",
|
| 1580 |
+
"source-map": "^0.6.0"
|
| 1581 |
+
}
|
| 1582 |
+
},
|
| 1583 |
+
"node_modules/statuses": {
|
| 1584 |
+
"version": "2.0.2",
|
| 1585 |
+
"resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.2.tgz",
|
| 1586 |
+
"integrity": "sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw==",
|
| 1587 |
+
"engines": {
|
| 1588 |
+
"node": ">= 0.8"
|
| 1589 |
+
}
|
| 1590 |
+
},
|
| 1591 |
+
"node_modules/strip-bom": {
|
| 1592 |
+
"version": "3.0.0",
|
| 1593 |
+
"resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz",
|
| 1594 |
+
"integrity": "sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA==",
|
| 1595 |
+
"dev": true,
|
| 1596 |
+
"engines": {
|
| 1597 |
+
"node": ">=4"
|
| 1598 |
+
}
|
| 1599 |
+
},
|
| 1600 |
+
"node_modules/strip-json-comments": {
|
| 1601 |
+
"version": "2.0.1",
|
| 1602 |
+
"resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz",
|
| 1603 |
+
"integrity": "sha512-4gB8na07fecVVkOI6Rs4e7T6NOTki5EmL7TUduTs6bu3EdnSycntVJ4re8kgZA+wx9IueI2Y11bfbgwtzuE0KQ==",
|
| 1604 |
+
"dev": true,
|
| 1605 |
+
"engines": {
|
| 1606 |
+
"node": ">=0.10.0"
|
| 1607 |
+
}
|
| 1608 |
+
},
|
| 1609 |
+
"node_modules/supports-preserve-symlinks-flag": {
|
| 1610 |
+
"version": "1.0.0",
|
| 1611 |
+
"resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz",
|
| 1612 |
+
"integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==",
|
| 1613 |
+
"dev": true,
|
| 1614 |
+
"engines": {
|
| 1615 |
+
"node": ">= 0.4"
|
| 1616 |
+
},
|
| 1617 |
+
"funding": {
|
| 1618 |
+
"url": "https://github.com/sponsors/ljharb"
|
| 1619 |
+
}
|
| 1620 |
+
},
|
| 1621 |
+
"node_modules/to-regex-range": {
|
| 1622 |
+
"version": "5.0.1",
|
| 1623 |
+
"resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz",
|
| 1624 |
+
"integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==",
|
| 1625 |
+
"dev": true,
|
| 1626 |
+
"dependencies": {
|
| 1627 |
+
"is-number": "^7.0.0"
|
| 1628 |
+
},
|
| 1629 |
+
"engines": {
|
| 1630 |
+
"node": ">=8.0"
|
| 1631 |
+
}
|
| 1632 |
+
},
|
| 1633 |
+
"node_modules/toidentifier": {
|
| 1634 |
+
"version": "1.0.1",
|
| 1635 |
+
"resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz",
|
| 1636 |
+
"integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==",
|
| 1637 |
+
"engines": {
|
| 1638 |
+
"node": ">=0.6"
|
| 1639 |
+
}
|
| 1640 |
+
},
|
| 1641 |
+
"node_modules/tree-kill": {
|
| 1642 |
+
"version": "1.2.2",
|
| 1643 |
+
"resolved": "https://registry.npmjs.org/tree-kill/-/tree-kill-1.2.2.tgz",
|
| 1644 |
+
"integrity": "sha512-L0Orpi8qGpRG//Nd+H90vFB+3iHnue1zSSGmNOOCh1GLJ7rUKVwV2HvijphGQS2UmhUZewS9VgvxYIdgr+fG1A==",
|
| 1645 |
+
"dev": true,
|
| 1646 |
+
"bin": {
|
| 1647 |
+
"tree-kill": "cli.js"
|
| 1648 |
+
}
|
| 1649 |
+
},
|
| 1650 |
+
"node_modules/ts-node": {
|
| 1651 |
+
"version": "10.9.2",
|
| 1652 |
+
"resolved": "https://registry.npmjs.org/ts-node/-/ts-node-10.9.2.tgz",
|
| 1653 |
+
"integrity": "sha512-f0FFpIdcHgn8zcPSbf1dRevwt047YMnaiJM3u2w2RewrB+fob/zePZcrOyQoLMMO7aBIddLcQIEK5dYjkLnGrQ==",
|
| 1654 |
+
"dev": true,
|
| 1655 |
+
"dependencies": {
|
| 1656 |
+
"@cspotcode/source-map-support": "^0.8.0",
|
| 1657 |
+
"@tsconfig/node10": "^1.0.7",
|
| 1658 |
+
"@tsconfig/node12": "^1.0.7",
|
| 1659 |
+
"@tsconfig/node14": "^1.0.0",
|
| 1660 |
+
"@tsconfig/node16": "^1.0.2",
|
| 1661 |
+
"acorn": "^8.4.1",
|
| 1662 |
+
"acorn-walk": "^8.1.1",
|
| 1663 |
+
"arg": "^4.1.0",
|
| 1664 |
+
"create-require": "^1.1.0",
|
| 1665 |
+
"diff": "^4.0.1",
|
| 1666 |
+
"make-error": "^1.1.1",
|
| 1667 |
+
"v8-compile-cache-lib": "^3.0.1",
|
| 1668 |
+
"yn": "3.1.1"
|
| 1669 |
+
},
|
| 1670 |
+
"bin": {
|
| 1671 |
+
"ts-node": "dist/bin.js",
|
| 1672 |
+
"ts-node-cwd": "dist/bin-cwd.js",
|
| 1673 |
+
"ts-node-esm": "dist/bin-esm.js",
|
| 1674 |
+
"ts-node-script": "dist/bin-script.js",
|
| 1675 |
+
"ts-node-transpile-only": "dist/bin-transpile.js",
|
| 1676 |
+
"ts-script": "dist/bin-script-deprecated.js"
|
| 1677 |
+
},
|
| 1678 |
+
"peerDependencies": {
|
| 1679 |
+
"@swc/core": ">=1.2.50",
|
| 1680 |
+
"@swc/wasm": ">=1.2.50",
|
| 1681 |
+
"@types/node": "*",
|
| 1682 |
+
"typescript": ">=2.7"
|
| 1683 |
+
},
|
| 1684 |
+
"peerDependenciesMeta": {
|
| 1685 |
+
"@swc/core": {
|
| 1686 |
+
"optional": true
|
| 1687 |
+
},
|
| 1688 |
+
"@swc/wasm": {
|
| 1689 |
+
"optional": true
|
| 1690 |
+
}
|
| 1691 |
+
}
|
| 1692 |
+
},
|
| 1693 |
+
"node_modules/ts-node-dev": {
|
| 1694 |
+
"version": "2.0.0",
|
| 1695 |
+
"resolved": "https://registry.npmjs.org/ts-node-dev/-/ts-node-dev-2.0.0.tgz",
|
| 1696 |
+
"integrity": "sha512-ywMrhCfH6M75yftYvrvNarLEY+SUXtUvU8/0Z6llrHQVBx12GiFk5sStF8UdfE/yfzk9IAq7O5EEbTQsxlBI8w==",
|
| 1697 |
+
"dev": true,
|
| 1698 |
+
"dependencies": {
|
| 1699 |
+
"chokidar": "^3.5.1",
|
| 1700 |
+
"dynamic-dedupe": "^0.3.0",
|
| 1701 |
+
"minimist": "^1.2.6",
|
| 1702 |
+
"mkdirp": "^1.0.4",
|
| 1703 |
+
"resolve": "^1.0.0",
|
| 1704 |
+
"rimraf": "^2.6.1",
|
| 1705 |
+
"source-map-support": "^0.5.12",
|
| 1706 |
+
"tree-kill": "^1.2.2",
|
| 1707 |
+
"ts-node": "^10.4.0",
|
| 1708 |
+
"tsconfig": "^7.0.0"
|
| 1709 |
+
},
|
| 1710 |
+
"bin": {
|
| 1711 |
+
"ts-node-dev": "lib/bin.js",
|
| 1712 |
+
"tsnd": "lib/bin.js"
|
| 1713 |
+
},
|
| 1714 |
+
"engines": {
|
| 1715 |
+
"node": ">=0.8.0"
|
| 1716 |
+
},
|
| 1717 |
+
"peerDependencies": {
|
| 1718 |
+
"node-notifier": "*",
|
| 1719 |
+
"typescript": "*"
|
| 1720 |
+
},
|
| 1721 |
+
"peerDependenciesMeta": {
|
| 1722 |
+
"node-notifier": {
|
| 1723 |
+
"optional": true
|
| 1724 |
+
}
|
| 1725 |
+
}
|
| 1726 |
+
},
|
| 1727 |
+
"node_modules/tsconfig": {
|
| 1728 |
+
"version": "7.0.0",
|
| 1729 |
+
"resolved": "https://registry.npmjs.org/tsconfig/-/tsconfig-7.0.0.tgz",
|
| 1730 |
+
"integrity": "sha512-vZXmzPrL+EmC4T/4rVlT2jNVMWCi/O4DIiSj3UHg1OE5kCKbk4mfrXc6dZksLgRM/TZlKnousKH9bbTazUWRRw==",
|
| 1731 |
+
"dev": true,
|
| 1732 |
+
"dependencies": {
|
| 1733 |
+
"@types/strip-bom": "^3.0.0",
|
| 1734 |
+
"@types/strip-json-comments": "0.0.30",
|
| 1735 |
+
"strip-bom": "^3.0.0",
|
| 1736 |
+
"strip-json-comments": "^2.0.0"
|
| 1737 |
+
}
|
| 1738 |
+
},
|
| 1739 |
+
"node_modules/type-is": {
|
| 1740 |
+
"version": "1.6.18",
|
| 1741 |
+
"resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz",
|
| 1742 |
+
"integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==",
|
| 1743 |
+
"dependencies": {
|
| 1744 |
+
"media-typer": "0.3.0",
|
| 1745 |
+
"mime-types": "~2.1.24"
|
| 1746 |
+
},
|
| 1747 |
+
"engines": {
|
| 1748 |
+
"node": ">= 0.6"
|
| 1749 |
+
}
|
| 1750 |
+
},
|
| 1751 |
+
"node_modules/typescript": {
|
| 1752 |
+
"version": "5.9.3",
|
| 1753 |
+
"resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz",
|
| 1754 |
+
"integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==",
|
| 1755 |
+
"dev": true,
|
| 1756 |
+
"bin": {
|
| 1757 |
+
"tsc": "bin/tsc",
|
| 1758 |
+
"tsserver": "bin/tsserver"
|
| 1759 |
+
},
|
| 1760 |
+
"engines": {
|
| 1761 |
+
"node": ">=14.17"
|
| 1762 |
+
}
|
| 1763 |
+
},
|
| 1764 |
+
"node_modules/undici-types": {
|
| 1765 |
+
"version": "6.21.0",
|
| 1766 |
+
"resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz",
|
| 1767 |
+
"integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==",
|
| 1768 |
+
"dev": true
|
| 1769 |
+
},
|
| 1770 |
+
"node_modules/unpipe": {
|
| 1771 |
+
"version": "1.0.0",
|
| 1772 |
+
"resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz",
|
| 1773 |
+
"integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==",
|
| 1774 |
+
"engines": {
|
| 1775 |
+
"node": ">= 0.8"
|
| 1776 |
+
}
|
| 1777 |
+
},
|
| 1778 |
+
"node_modules/utils-merge": {
|
| 1779 |
+
"version": "1.0.1",
|
| 1780 |
+
"resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz",
|
| 1781 |
+
"integrity": "sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==",
|
| 1782 |
+
"engines": {
|
| 1783 |
+
"node": ">= 0.4.0"
|
| 1784 |
+
}
|
| 1785 |
+
},
|
| 1786 |
+
"node_modules/v8-compile-cache-lib": {
|
| 1787 |
+
"version": "3.0.1",
|
| 1788 |
+
"resolved": "https://registry.npmjs.org/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.1.tgz",
|
| 1789 |
+
"integrity": "sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg==",
|
| 1790 |
+
"dev": true
|
| 1791 |
+
},
|
| 1792 |
+
"node_modules/vary": {
|
| 1793 |
+
"version": "1.1.2",
|
| 1794 |
+
"resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz",
|
| 1795 |
+
"integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==",
|
| 1796 |
+
"engines": {
|
| 1797 |
+
"node": ">= 0.8"
|
| 1798 |
+
}
|
| 1799 |
+
},
|
| 1800 |
+
"node_modules/wrappy": {
|
| 1801 |
+
"version": "1.0.2",
|
| 1802 |
+
"resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz",
|
| 1803 |
+
"integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==",
|
| 1804 |
+
"dev": true
|
| 1805 |
+
},
|
| 1806 |
+
"node_modules/xtend": {
|
| 1807 |
+
"version": "4.0.2",
|
| 1808 |
+
"resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz",
|
| 1809 |
+
"integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==",
|
| 1810 |
+
"dev": true,
|
| 1811 |
+
"engines": {
|
| 1812 |
+
"node": ">=0.4"
|
| 1813 |
+
}
|
| 1814 |
+
},
|
| 1815 |
+
"node_modules/yn": {
|
| 1816 |
+
"version": "3.1.1",
|
| 1817 |
+
"resolved": "https://registry.npmjs.org/yn/-/yn-3.1.1.tgz",
|
| 1818 |
+
"integrity": "sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==",
|
| 1819 |
+
"dev": true,
|
| 1820 |
+
"engines": {
|
| 1821 |
+
"node": ">=6"
|
| 1822 |
+
}
|
| 1823 |
+
},
|
| 1824 |
+
"node_modules/zod": {
|
| 1825 |
+
"version": "3.25.76",
|
| 1826 |
+
"resolved": "https://registry.npmjs.org/zod/-/zod-3.25.76.tgz",
|
| 1827 |
+
"integrity": "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ==",
|
| 1828 |
+
"funding": {
|
| 1829 |
+
"url": "https://github.com/sponsors/colinhacks"
|
| 1830 |
+
}
|
| 1831 |
+
}
|
| 1832 |
+
}
|
| 1833 |
+
}
|
package.json
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"name": "tokiva-pos-backend-node",
|
| 3 |
+
"version": "1.0.0",
|
| 4 |
+
"description": "Node.js Backend for Tokiva POS using Express and TypeScript",
|
| 5 |
+
"main": "dist/server.js",
|
| 6 |
+
"scripts": {
|
| 7 |
+
"dev": "ts-node-dev --respawn --transpile-only src/server.ts",
|
| 8 |
+
"build": "tsc",
|
| 9 |
+
"start": "node dist/server.js",
|
| 10 |
+
"prisma:pull": "npx prisma db pull",
|
| 11 |
+
"prisma:generate": "npx prisma generate"
|
| 12 |
+
},
|
| 13 |
+
"dependencies": {
|
| 14 |
+
"@prisma/client": "^5.19.0",
|
| 15 |
+
"bcryptjs": "^2.4.3",
|
| 16 |
+
"cors": "^2.8.5",
|
| 17 |
+
"dotenv": "^16.4.5",
|
| 18 |
+
"express": "^4.21.0",
|
| 19 |
+
"jsonwebtoken": "^9.0.2",
|
| 20 |
+
"zod": "^3.23.8"
|
| 21 |
+
},
|
| 22 |
+
"devDependencies": {
|
| 23 |
+
"@types/bcryptjs": "^2.4.6",
|
| 24 |
+
"@types/cors": "^2.8.17",
|
| 25 |
+
"@types/express": "^4.17.21",
|
| 26 |
+
"@types/jsonwebtoken": "^9.0.7",
|
| 27 |
+
"@types/node": "^22.5.0",
|
| 28 |
+
"prisma": "^5.19.0",
|
| 29 |
+
"ts-node-dev": "^2.0.0",
|
| 30 |
+
"typescript": "^5.5.4"
|
| 31 |
+
}
|
| 32 |
+
}
|
prisma/schema.prisma
ADDED
|
@@ -0,0 +1,538 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
generator client {
|
| 2 |
+
provider = "prisma-client-js"
|
| 3 |
+
}
|
| 4 |
+
|
| 5 |
+
datasource db {
|
| 6 |
+
provider = "mysql"
|
| 7 |
+
url = env("DATABASE_URL")
|
| 8 |
+
}
|
| 9 |
+
|
| 10 |
+
model alembic_version {
|
| 11 |
+
version_num String @id @db.VarChar(32)
|
| 12 |
+
}
|
| 13 |
+
|
| 14 |
+
model audit_log {
|
| 15 |
+
id Int @id @default(autoincrement())
|
| 16 |
+
user_id Int?
|
| 17 |
+
aksi String @db.VarChar(50)
|
| 18 |
+
entitas String @db.VarChar(50)
|
| 19 |
+
entitas_id Int?
|
| 20 |
+
data_lama Json?
|
| 21 |
+
data_baru Json?
|
| 22 |
+
ip_address String? @db.VarChar(45)
|
| 23 |
+
user_agent String? @db.VarChar(255)
|
| 24 |
+
created_at DateTime @default(now()) @db.DateTime(0)
|
| 25 |
+
user user? @relation(fields: [user_id], references: [id], onUpdate: NoAction, map: "audit_log_ibfk_1")
|
| 26 |
+
|
| 27 |
+
@@index([created_at], map: "idx_audit_created")
|
| 28 |
+
@@index([entitas, entitas_id], map: "idx_audit_entitas_id")
|
| 29 |
+
@@index([user_id], map: "idx_audit_user")
|
| 30 |
+
}
|
| 31 |
+
|
| 32 |
+
model bon {
|
| 33 |
+
id Int @id @default(autoincrement())
|
| 34 |
+
transaksi_id Int
|
| 35 |
+
member_id Int
|
| 36 |
+
total_bon Decimal @db.Decimal(15, 2)
|
| 37 |
+
sisa_bon Decimal @db.Decimal(15, 2)
|
| 38 |
+
jatuh_tempo DateTime? @db.Date
|
| 39 |
+
status bon_status @default(aktif)
|
| 40 |
+
reminder_sent Int @default(0) @db.SmallInt
|
| 41 |
+
created_at DateTime @default(now()) @db.DateTime(0)
|
| 42 |
+
updated_at DateTime @default(now()) @db.DateTime(0)
|
| 43 |
+
member member @relation(fields: [member_id], references: [id], onUpdate: NoAction, map: "bon_ibfk_1")
|
| 44 |
+
transaksi transaksi @relation(fields: [transaksi_id], references: [id], onDelete: Cascade, onUpdate: NoAction, map: "bon_ibfk_2")
|
| 45 |
+
bon_cicilan bon_cicilan[]
|
| 46 |
+
|
| 47 |
+
@@index([jatuh_tempo], map: "idx_bon_jatuh_tempo")
|
| 48 |
+
@@index([member_id], map: "idx_bon_member")
|
| 49 |
+
@@index([status], map: "idx_bon_status")
|
| 50 |
+
@@index([transaksi_id], map: "idx_bon_transaksi")
|
| 51 |
+
}
|
| 52 |
+
|
| 53 |
+
model bon_cicilan {
|
| 54 |
+
id Int @id @default(autoincrement())
|
| 55 |
+
bon_id Int
|
| 56 |
+
nominal Decimal @db.Decimal(15, 2)
|
| 57 |
+
metode bon_cicilan_metode
|
| 58 |
+
user_id Int
|
| 59 |
+
created_at DateTime @default(now()) @db.DateTime(0)
|
| 60 |
+
bon bon @relation(fields: [bon_id], references: [id], onDelete: Cascade, onUpdate: NoAction, map: "bon_cicilan_ibfk_1")
|
| 61 |
+
user user @relation(fields: [user_id], references: [id], onUpdate: NoAction, map: "bon_cicilan_ibfk_2")
|
| 62 |
+
|
| 63 |
+
@@index([bon_id], map: "idx_cicilan_bon")
|
| 64 |
+
@@index([user_id], map: "idx_cicilan_user")
|
| 65 |
+
}
|
| 66 |
+
|
| 67 |
+
model diskon {
|
| 68 |
+
id Int @id @default(autoincrement())
|
| 69 |
+
nama String @db.VarChar(200)
|
| 70 |
+
tipe diskon_tipe
|
| 71 |
+
nilai Decimal @db.Decimal(15, 2)
|
| 72 |
+
min_belanja Decimal @default(0.00) @db.Decimal(15, 2)
|
| 73 |
+
produk_ids Json?
|
| 74 |
+
member_only Int @default(0) @db.SmallInt
|
| 75 |
+
tgl_mulai DateTime @db.DateTime(0)
|
| 76 |
+
tgl_selesai DateTime @db.DateTime(0)
|
| 77 |
+
jam_mulai DateTime? @db.Time(0)
|
| 78 |
+
jam_selesai DateTime? @db.Time(0)
|
| 79 |
+
is_aktif Int @default(1) @db.SmallInt
|
| 80 |
+
created_at DateTime @default(now()) @db.DateTime(0)
|
| 81 |
+
diskon_bundling diskon_bundling[]
|
| 82 |
+
transaksi_detail transaksi_detail[]
|
| 83 |
+
voucher voucher[]
|
| 84 |
+
|
| 85 |
+
@@index([tgl_mulai, tgl_selesai], map: "idx_diskon_periode")
|
| 86 |
+
@@index([is_aktif], map: "idx_diskon_status")
|
| 87 |
+
}
|
| 88 |
+
|
| 89 |
+
model diskon_bundling {
|
| 90 |
+
id Int @id @default(autoincrement())
|
| 91 |
+
diskon_id Int
|
| 92 |
+
produk_id Int
|
| 93 |
+
qty Decimal @db.Decimal(10, 2)
|
| 94 |
+
diskon diskon @relation(fields: [diskon_id], references: [id], onDelete: Cascade, onUpdate: NoAction, map: "diskon_bundling_ibfk_1")
|
| 95 |
+
produk produk @relation(fields: [produk_id], references: [id], onDelete: Cascade, onUpdate: NoAction, map: "diskon_bundling_ibfk_2")
|
| 96 |
+
|
| 97 |
+
@@index([diskon_id], map: "idx_bundling_diskon")
|
| 98 |
+
@@index([produk_id], map: "idx_bundling_produk")
|
| 99 |
+
}
|
| 100 |
+
|
| 101 |
+
model harga_tingkat {
|
| 102 |
+
id Int @id @default(autoincrement())
|
| 103 |
+
produk_id Int
|
| 104 |
+
tingkat harga_tingkat_tingkat
|
| 105 |
+
min_qty Decimal @db.Decimal(10, 2)
|
| 106 |
+
harga Decimal @db.Decimal(15, 2)
|
| 107 |
+
created_at DateTime @default(now()) @db.DateTime(0)
|
| 108 |
+
produk produk @relation(fields: [produk_id], references: [id], onDelete: Cascade, onUpdate: NoAction, map: "harga_tingkat_ibfk_1")
|
| 109 |
+
|
| 110 |
+
@@unique([produk_id, tingkat], map: "uq_produk_tingkat")
|
| 111 |
+
}
|
| 112 |
+
|
| 113 |
+
model kategori {
|
| 114 |
+
id Int @id @default(autoincrement())
|
| 115 |
+
nama String @unique(map: "nama") @db.VarChar(100)
|
| 116 |
+
icon String? @db.VarChar(50)
|
| 117 |
+
aktif Int @default(1) @db.SmallInt
|
| 118 |
+
created_at DateTime @default(now()) @db.DateTime(0)
|
| 119 |
+
updated_at DateTime @default(now()) @db.DateTime(0)
|
| 120 |
+
deleted_at DateTime? @db.DateTime(0)
|
| 121 |
+
produk produk[]
|
| 122 |
+
|
| 123 |
+
@@index([nama], map: "idx_kategori_nama")
|
| 124 |
+
}
|
| 125 |
+
|
| 126 |
+
model member {
|
| 127 |
+
id Int @id @default(autoincrement())
|
| 128 |
+
kode String @unique(map: "kode") @db.VarChar(20)
|
| 129 |
+
nama String @db.VarChar(200)
|
| 130 |
+
nomor_hp String @unique(map: "nomor_hp") @db.VarChar(20)
|
| 131 |
+
alamat String? @db.Text
|
| 132 |
+
total_poin Int @default(0)
|
| 133 |
+
limit_bon Decimal @default(0.00) @db.Decimal(15, 2)
|
| 134 |
+
total_bon Decimal @default(0.00) @db.Decimal(15, 2)
|
| 135 |
+
foto_url String? @db.VarChar(500)
|
| 136 |
+
aktif Int @default(1) @db.SmallInt
|
| 137 |
+
created_at DateTime @default(now()) @db.DateTime(0)
|
| 138 |
+
updated_at DateTime @default(now()) @db.DateTime(0)
|
| 139 |
+
deleted_at DateTime? @db.DateTime(0)
|
| 140 |
+
bon bon[]
|
| 141 |
+
transaksi transaksi[]
|
| 142 |
+
|
| 143 |
+
@@index([kode], map: "idx_pelanggan_kode")
|
| 144 |
+
@@index([nomor_hp], map: "idx_pelanggan_nomor_hp")
|
| 145 |
+
}
|
| 146 |
+
|
| 147 |
+
model notifikasi {
|
| 148 |
+
id Int @id @default(autoincrement())
|
| 149 |
+
user_id Int?
|
| 150 |
+
tipe notifikasi_tipe
|
| 151 |
+
judul String @db.VarChar(255)
|
| 152 |
+
pesan String @db.Text
|
| 153 |
+
referensi_id Int?
|
| 154 |
+
is_dibaca Int @default(0) @db.SmallInt
|
| 155 |
+
was_sent Int @default(0) @db.SmallInt
|
| 156 |
+
created_at DateTime @default(now()) @db.DateTime(0)
|
| 157 |
+
user user? @relation(fields: [user_id], references: [id], onDelete: Cascade, onUpdate: NoAction, map: "notifikasi_ibfk_1")
|
| 158 |
+
|
| 159 |
+
@@index([created_at], map: "idx_notif_created")
|
| 160 |
+
@@index([user_id, is_dibaca], map: "idx_notif_user_dibaca")
|
| 161 |
+
}
|
| 162 |
+
|
| 163 |
+
model pembayaran {
|
| 164 |
+
id Int @id @default(autoincrement())
|
| 165 |
+
transaksi_id Int
|
| 166 |
+
metode pembayaran_metode
|
| 167 |
+
nominal Decimal @db.Decimal(15, 2)
|
| 168 |
+
referensi String? @db.VarChar(100)
|
| 169 |
+
created_at DateTime @default(now()) @db.DateTime(0)
|
| 170 |
+
transaksi transaksi @relation(fields: [transaksi_id], references: [id], onDelete: Cascade, onUpdate: NoAction, map: "pembayaran_ibfk_1")
|
| 171 |
+
|
| 172 |
+
@@index([metode], map: "idx_pembayaran_metode")
|
| 173 |
+
@@index([transaksi_id], map: "idx_pembayaran_transaksi")
|
| 174 |
+
}
|
| 175 |
+
|
| 176 |
+
model pengaturan {
|
| 177 |
+
id Int @id @default(autoincrement())
|
| 178 |
+
kunci String @unique(map: "kunci") @db.VarChar(100)
|
| 179 |
+
nilai String @db.Text
|
| 180 |
+
deskripsi String? @db.VarChar(255)
|
| 181 |
+
updated_at DateTime @default(now()) @db.DateTime(0)
|
| 182 |
+
|
| 183 |
+
@@index([kunci], map: "idx_pengaturan_kunci")
|
| 184 |
+
}
|
| 185 |
+
|
| 186 |
+
model produk {
|
| 187 |
+
id Int @id @default(autoincrement())
|
| 188 |
+
kode String @unique(map: "kode") @db.VarChar(50)
|
| 189 |
+
barcode String? @unique(map: "barcode") @db.VarChar(50)
|
| 190 |
+
nama String @db.VarChar(200)
|
| 191 |
+
kategori_id Int
|
| 192 |
+
supplier_id Int?
|
| 193 |
+
satuan String @default("pcs") @db.VarChar(20)
|
| 194 |
+
harga_beli Decimal @db.Decimal(15, 2)
|
| 195 |
+
harga_jual Decimal @db.Decimal(15, 2)
|
| 196 |
+
stok Decimal @default(0.00) @db.Decimal(12, 2)
|
| 197 |
+
stok_min Decimal @default(5.00) @db.Decimal(12, 2)
|
| 198 |
+
expired_date DateTime? @db.Date
|
| 199 |
+
foto_url String? @db.LongText
|
| 200 |
+
qr_url String? @db.VarChar(500)
|
| 201 |
+
deskripsi String? @db.Text
|
| 202 |
+
is_ecer Int @default(1) @db.SmallInt
|
| 203 |
+
is_aktif Int @default(1) @db.SmallInt
|
| 204 |
+
created_at DateTime @default(now()) @db.DateTime(0)
|
| 205 |
+
updated_at DateTime @default(now()) @db.DateTime(0)
|
| 206 |
+
deleted_at DateTime? @db.DateTime(0)
|
| 207 |
+
diskon_bundling diskon_bundling[]
|
| 208 |
+
harga_tingkat harga_tingkat[]
|
| 209 |
+
kategori kategori @relation(fields: [kategori_id], references: [id], onUpdate: NoAction, map: "produk_ibfk_1")
|
| 210 |
+
supplier supplier? @relation(fields: [supplier_id], references: [id], onUpdate: NoAction, map: "produk_ibfk_2")
|
| 211 |
+
stok_batch stok_batch[]
|
| 212 |
+
stok_log stok_log[]
|
| 213 |
+
stok_opname stok_opname[]
|
| 214 |
+
transaksi_detail transaksi_detail[]
|
| 215 |
+
transaksi_retur transaksi_retur[]
|
| 216 |
+
|
| 217 |
+
@@index([barcode], map: "idx_produk_barcode")
|
| 218 |
+
@@index([kategori_id], map: "idx_produk_kategori")
|
| 219 |
+
@@index([kode], map: "idx_produk_kode")
|
| 220 |
+
@@index([nama], map: "idx_produk_nama")
|
| 221 |
+
@@index([supplier_id], map: "idx_produk_supplier")
|
| 222 |
+
}
|
| 223 |
+
|
| 224 |
+
model shift {
|
| 225 |
+
id Int @id @default(autoincrement())
|
| 226 |
+
user_id Int
|
| 227 |
+
kode String @db.VarChar(30)
|
| 228 |
+
modal_awal Decimal @default(0.00) @db.Decimal(15, 2)
|
| 229 |
+
waktu_buka DateTime @db.DateTime(0)
|
| 230 |
+
waktu_tutup DateTime? @db.DateTime(0)
|
| 231 |
+
total_transaksi Int @default(0)
|
| 232 |
+
total_tunai Decimal @default(0.00) @db.Decimal(15, 2)
|
| 233 |
+
total_qris Decimal @default(0.00) @db.Decimal(15, 2)
|
| 234 |
+
total_transfer Decimal @default(0.00) @db.Decimal(15, 2)
|
| 235 |
+
total_bon Decimal @default(0.00) @db.Decimal(15, 2)
|
| 236 |
+
total_diskon Decimal @default(0.00) @db.Decimal(15, 2)
|
| 237 |
+
total_selisih Decimal @default(0.00) @db.Decimal(15, 2)
|
| 238 |
+
status shift_status @default(buka)
|
| 239 |
+
catatan String? @db.Text
|
| 240 |
+
user user @relation(fields: [user_id], references: [id], onUpdate: NoAction, map: "shift_ibfk_1")
|
| 241 |
+
transaksi transaksi[]
|
| 242 |
+
|
| 243 |
+
@@index([kode], map: "idx_shift_kode")
|
| 244 |
+
@@index([user_id], map: "idx_shift_user")
|
| 245 |
+
}
|
| 246 |
+
|
| 247 |
+
model stok_batch {
|
| 248 |
+
id Int @id @default(autoincrement())
|
| 249 |
+
produk_id Int
|
| 250 |
+
batch_no String? @db.VarChar(50)
|
| 251 |
+
qty_masuk Decimal @db.Decimal(12, 2)
|
| 252 |
+
qty_sisa Decimal @db.Decimal(12, 2)
|
| 253 |
+
harga_beli Decimal @db.Decimal(15, 2)
|
| 254 |
+
expired_date DateTime? @db.Date
|
| 255 |
+
supplier_id Int?
|
| 256 |
+
catatan String? @db.Text
|
| 257 |
+
created_at DateTime @default(now()) @db.DateTime(0)
|
| 258 |
+
produk produk @relation(fields: [produk_id], references: [id], onDelete: Cascade, onUpdate: NoAction, map: "stok_batch_ibfk_1")
|
| 259 |
+
supplier supplier? @relation(fields: [supplier_id], references: [id], onUpdate: NoAction, map: "stok_batch_ibfk_2")
|
| 260 |
+
stok_log stok_log[]
|
| 261 |
+
transaksi_detail transaksi_detail[]
|
| 262 |
+
|
| 263 |
+
@@index([expired_date], map: "idx_batch_expired")
|
| 264 |
+
@@index([produk_id], map: "idx_batch_produk")
|
| 265 |
+
@@index([supplier_id], map: "idx_batch_supplier")
|
| 266 |
+
}
|
| 267 |
+
|
| 268 |
+
model stok_log {
|
| 269 |
+
id Int @id @default(autoincrement())
|
| 270 |
+
produk_id Int
|
| 271 |
+
batch_id Int?
|
| 272 |
+
tipe stok_log_tipe
|
| 273 |
+
qty Decimal @db.Decimal(12, 2)
|
| 274 |
+
stok_sebelum Decimal @db.Decimal(12, 2)
|
| 275 |
+
stok_sesudah Decimal @db.Decimal(12, 2)
|
| 276 |
+
referensi String? @db.VarChar(50)
|
| 277 |
+
user_id Int?
|
| 278 |
+
catatan String? @db.Text
|
| 279 |
+
created_at DateTime @default(now()) @db.DateTime(0)
|
| 280 |
+
stok_batch stok_batch? @relation(fields: [batch_id], references: [id], onUpdate: NoAction, map: "stok_log_ibfk_1")
|
| 281 |
+
produk produk @relation(fields: [produk_id], references: [id], onDelete: Cascade, onUpdate: NoAction, map: "stok_log_ibfk_2")
|
| 282 |
+
user user? @relation(fields: [user_id], references: [id], onUpdate: NoAction, map: "stok_log_ibfk_3")
|
| 283 |
+
|
| 284 |
+
@@index([batch_id], map: "batch_id")
|
| 285 |
+
@@index([created_at], map: "idx_stoklog_date")
|
| 286 |
+
@@index([produk_id], map: "idx_stoklog_produk")
|
| 287 |
+
@@index([tipe], map: "idx_stoklog_tipe")
|
| 288 |
+
@@index([user_id], map: "user_id")
|
| 289 |
+
}
|
| 290 |
+
|
| 291 |
+
model stok_opname {
|
| 292 |
+
id Int @id @default(autoincrement())
|
| 293 |
+
produk_id Int
|
| 294 |
+
user_id Int
|
| 295 |
+
stok_sistem Decimal @db.Decimal(12, 2)
|
| 296 |
+
stok_fisik Decimal @db.Decimal(12, 2)
|
| 297 |
+
selisih Decimal @db.Decimal(12, 2)
|
| 298 |
+
alasan String? @db.VarChar(255)
|
| 299 |
+
status stok_opname_status @default(draft)
|
| 300 |
+
created_at DateTime @default(now()) @db.DateTime(0)
|
| 301 |
+
produk produk @relation(fields: [produk_id], references: [id], onDelete: Cascade, onUpdate: NoAction, map: "stok_opname_ibfk_1")
|
| 302 |
+
user user @relation(fields: [user_id], references: [id], onUpdate: NoAction, map: "stok_opname_ibfk_2")
|
| 303 |
+
|
| 304 |
+
@@index([produk_id], map: "idx_opname_produk")
|
| 305 |
+
@@index([status], map: "idx_opname_status")
|
| 306 |
+
@@index([user_id], map: "idx_opname_user")
|
| 307 |
+
}
|
| 308 |
+
|
| 309 |
+
model supplier {
|
| 310 |
+
id Int @id @default(autoincrement())
|
| 311 |
+
nama String @db.VarChar(200)
|
| 312 |
+
kontak String? @db.VarChar(100)
|
| 313 |
+
nomor_hp String? @db.VarChar(20)
|
| 314 |
+
alamat String? @db.Text
|
| 315 |
+
hutang Decimal @default(0.00) @db.Decimal(15, 2)
|
| 316 |
+
aktif Int @default(1) @db.SmallInt
|
| 317 |
+
created_at DateTime @default(now()) @db.DateTime(0)
|
| 318 |
+
updated_at DateTime @default(now()) @db.DateTime(0)
|
| 319 |
+
deleted_at DateTime? @db.DateTime(0)
|
| 320 |
+
produk produk[]
|
| 321 |
+
stok_batch stok_batch[]
|
| 322 |
+
|
| 323 |
+
@@index([nama], map: "idx_pemasok_nama")
|
| 324 |
+
}
|
| 325 |
+
|
| 326 |
+
model transaksi {
|
| 327 |
+
id Int @id @default(autoincrement())
|
| 328 |
+
no_transaksi String @unique(map: "no_transaksi") @db.VarChar(30)
|
| 329 |
+
shift_id Int
|
| 330 |
+
user_id Int
|
| 331 |
+
member_id Int?
|
| 332 |
+
tanggal DateTime @db.Date
|
| 333 |
+
waktu DateTime @db.Time(0)
|
| 334 |
+
subtotal Decimal @db.Decimal(15, 2)
|
| 335 |
+
diskon_total Decimal @default(0.00) @db.Decimal(15, 2)
|
| 336 |
+
diskon_member Decimal @default(0.00) @db.Decimal(15, 2)
|
| 337 |
+
pajak Decimal @default(0.00) @db.Decimal(15, 2)
|
| 338 |
+
total Decimal @db.Decimal(15, 2)
|
| 339 |
+
bayar Decimal @default(0.00) @db.Decimal(15, 2)
|
| 340 |
+
kembalian Decimal @default(0.00) @db.Decimal(15, 2)
|
| 341 |
+
status transaksi_status @default(lunas)
|
| 342 |
+
is_synced Int @default(0) @db.SmallInt
|
| 343 |
+
sync_at DateTime? @db.DateTime(0)
|
| 344 |
+
local_id String? @db.VarChar(50)
|
| 345 |
+
catatan String? @db.Text
|
| 346 |
+
created_at DateTime @default(now()) @db.DateTime(0)
|
| 347 |
+
deleted_at DateTime? @db.DateTime(0)
|
| 348 |
+
bon bon[]
|
| 349 |
+
pembayaran pembayaran[]
|
| 350 |
+
member member? @relation(fields: [member_id], references: [id], onUpdate: NoAction, map: "transaksi_ibfk_1")
|
| 351 |
+
shift shift @relation(fields: [shift_id], references: [id], onUpdate: NoAction, map: "transaksi_ibfk_2")
|
| 352 |
+
user user @relation(fields: [user_id], references: [id], onUpdate: NoAction, map: "transaksi_ibfk_3")
|
| 353 |
+
transaksi_detail transaksi_detail[]
|
| 354 |
+
transaksi_retur transaksi_retur[]
|
| 355 |
+
|
| 356 |
+
@@index([member_id], map: "idx_transaksi_member")
|
| 357 |
+
@@index([no_transaksi], map: "idx_transaksi_no")
|
| 358 |
+
@@index([shift_id], map: "idx_transaksi_shift")
|
| 359 |
+
@@index([tanggal], map: "idx_transaksi_tanggal")
|
| 360 |
+
@@index([user_id], map: "idx_transaksi_user")
|
| 361 |
+
}
|
| 362 |
+
|
| 363 |
+
model transaksi_detail {
|
| 364 |
+
id Int @id @default(autoincrement())
|
| 365 |
+
transaksi_id Int
|
| 366 |
+
produk_id Int
|
| 367 |
+
nama_produk String @db.VarChar(200)
|
| 368 |
+
qty Decimal @db.Decimal(10, 2)
|
| 369 |
+
satuan String @db.VarChar(20)
|
| 370 |
+
harga_satuan Decimal @db.Decimal(15, 2)
|
| 371 |
+
diskon_item Decimal @default(0.00) @db.Decimal(15, 2)
|
| 372 |
+
subtotal Decimal @db.Decimal(15, 2)
|
| 373 |
+
diskon_id Int?
|
| 374 |
+
batch_id Int?
|
| 375 |
+
created_at DateTime @default(now()) @db.DateTime(0)
|
| 376 |
+
stok_batch stok_batch? @relation(fields: [batch_id], references: [id], onUpdate: NoAction, map: "transaksi_detail_ibfk_1")
|
| 377 |
+
diskon diskon? @relation(fields: [diskon_id], references: [id], onUpdate: NoAction, map: "transaksi_detail_ibfk_2")
|
| 378 |
+
produk produk @relation(fields: [produk_id], references: [id], onUpdate: NoAction, map: "transaksi_detail_ibfk_3")
|
| 379 |
+
transaksi transaksi @relation(fields: [transaksi_id], references: [id], onDelete: Cascade, onUpdate: NoAction, map: "transaksi_detail_ibfk_4")
|
| 380 |
+
|
| 381 |
+
@@index([batch_id], map: "batch_id")
|
| 382 |
+
@@index([diskon_id], map: "diskon_id")
|
| 383 |
+
@@index([produk_id], map: "idx_detail_produk")
|
| 384 |
+
@@index([transaksi_id], map: "idx_detail_transaksi")
|
| 385 |
+
}
|
| 386 |
+
|
| 387 |
+
model transaksi_retur {
|
| 388 |
+
id Int @id @default(autoincrement())
|
| 389 |
+
transaksi_id Int
|
| 390 |
+
no_retur String @unique(map: "no_retur") @db.VarChar(30)
|
| 391 |
+
user_id Int
|
| 392 |
+
produk_id Int
|
| 393 |
+
qty Decimal @db.Decimal(10, 2)
|
| 394 |
+
alasan String @db.VarChar(255)
|
| 395 |
+
tipe transaksi_retur_tipe
|
| 396 |
+
nominal_kembali Decimal @default(0.00) @db.Decimal(15, 2)
|
| 397 |
+
stok_kembali Int @default(1) @db.SmallInt
|
| 398 |
+
created_at DateTime @default(now()) @db.DateTime(0)
|
| 399 |
+
produk produk @relation(fields: [produk_id], references: [id], onUpdate: NoAction, map: "transaksi_retur_ibfk_1")
|
| 400 |
+
transaksi transaksi @relation(fields: [transaksi_id], references: [id], onUpdate: NoAction, map: "transaksi_retur_ibfk_2")
|
| 401 |
+
user user @relation(fields: [user_id], references: [id], onUpdate: NoAction, map: "transaksi_retur_ibfk_3")
|
| 402 |
+
|
| 403 |
+
@@index([no_retur], map: "idx_retur_no")
|
| 404 |
+
@@index([produk_id], map: "idx_retur_produk")
|
| 405 |
+
@@index([transaksi_id], map: "idx_retur_transaksi")
|
| 406 |
+
@@index([user_id], map: "user_id")
|
| 407 |
+
}
|
| 408 |
+
|
| 409 |
+
model trash {
|
| 410 |
+
id Int @id @default(autoincrement())
|
| 411 |
+
entity_type String @db.VarChar(50)
|
| 412 |
+
entity_id Int
|
| 413 |
+
data String @db.Text
|
| 414 |
+
deleted_by Int?
|
| 415 |
+
deleted_at DateTime @default(now()) @db.DateTime(0)
|
| 416 |
+
|
| 417 |
+
@@index([deleted_at], map: "idx_trash_date")
|
| 418 |
+
@@index([entity_type, entity_id], map: "idx_trash_entity")
|
| 419 |
+
}
|
| 420 |
+
|
| 421 |
+
model user {
|
| 422 |
+
id Int @id @default(autoincrement())
|
| 423 |
+
nama String @db.VarChar(100)
|
| 424 |
+
username String @unique(map: "username") @db.VarChar(50)
|
| 425 |
+
password_hash String @db.VarChar(255)
|
| 426 |
+
role user_role
|
| 427 |
+
nomor_hp String? @db.VarChar(20)
|
| 428 |
+
foto_url String? @db.VarChar(500)
|
| 429 |
+
aktif Int @default(1) @db.SmallInt
|
| 430 |
+
created_at DateTime @default(now()) @db.DateTime(0)
|
| 431 |
+
updated_at DateTime @default(now()) @db.DateTime(0)
|
| 432 |
+
audit_log audit_log[]
|
| 433 |
+
bon_cicilan bon_cicilan[]
|
| 434 |
+
notifikasi notifikasi[]
|
| 435 |
+
shift shift[]
|
| 436 |
+
stok_log stok_log[]
|
| 437 |
+
stok_opname stok_opname[]
|
| 438 |
+
transaksi transaksi[]
|
| 439 |
+
transaksi_retur transaksi_retur[]
|
| 440 |
+
|
| 441 |
+
@@index([username], map: "idx_pengguna_username")
|
| 442 |
+
}
|
| 443 |
+
|
| 444 |
+
model voucher {
|
| 445 |
+
id Int @id @default(autoincrement())
|
| 446 |
+
kode String @unique(map: "kode") @db.VarChar(50)
|
| 447 |
+
diskon_id Int
|
| 448 |
+
kuota Int @default(1)
|
| 449 |
+
terpakai Int @default(0)
|
| 450 |
+
tgl_kadaluarsa DateTime @db.Date
|
| 451 |
+
is_aktif Int @default(1) @db.SmallInt
|
| 452 |
+
created_at DateTime @default(now()) @db.DateTime(0)
|
| 453 |
+
diskon diskon @relation(fields: [diskon_id], references: [id], onDelete: Cascade, onUpdate: NoAction, map: "voucher_ibfk_1")
|
| 454 |
+
|
| 455 |
+
@@index([diskon_id], map: "idx_voucher_diskon")
|
| 456 |
+
@@index([kode], map: "idx_voucher_kode")
|
| 457 |
+
}
|
| 458 |
+
|
| 459 |
+
enum diskon_tipe {
|
| 460 |
+
per_item_persen
|
| 461 |
+
per_item_nominal
|
| 462 |
+
total_persen
|
| 463 |
+
total_nominal
|
| 464 |
+
bundling
|
| 465 |
+
buy_x_get_y
|
| 466 |
+
member
|
| 467 |
+
flash_sale
|
| 468 |
+
}
|
| 469 |
+
|
| 470 |
+
enum harga_tingkat_tingkat {
|
| 471 |
+
ecer
|
| 472 |
+
grosir
|
| 473 |
+
partai
|
| 474 |
+
}
|
| 475 |
+
|
| 476 |
+
enum notifikasi_tipe {
|
| 477 |
+
stok_rendah
|
| 478 |
+
stok_habis
|
| 479 |
+
expired
|
| 480 |
+
bon_jatuh_tempo
|
| 481 |
+
shift_tutup
|
| 482 |
+
transaksi_void
|
| 483 |
+
info
|
| 484 |
+
}
|
| 485 |
+
|
| 486 |
+
enum pembayaran_metode {
|
| 487 |
+
tunai
|
| 488 |
+
qris
|
| 489 |
+
transfer
|
| 490 |
+
bon
|
| 491 |
+
voucher
|
| 492 |
+
}
|
| 493 |
+
|
| 494 |
+
enum bon_cicilan_metode {
|
| 495 |
+
tunai
|
| 496 |
+
transfer
|
| 497 |
+
}
|
| 498 |
+
|
| 499 |
+
enum stok_log_tipe {
|
| 500 |
+
masuk
|
| 501 |
+
keluar
|
| 502 |
+
opname
|
| 503 |
+
retur
|
| 504 |
+
expired
|
| 505 |
+
adjustment
|
| 506 |
+
}
|
| 507 |
+
|
| 508 |
+
enum user_role {
|
| 509 |
+
admin
|
| 510 |
+
kasir
|
| 511 |
+
}
|
| 512 |
+
|
| 513 |
+
enum bon_status {
|
| 514 |
+
aktif
|
| 515 |
+
lunas
|
| 516 |
+
macet
|
| 517 |
+
}
|
| 518 |
+
|
| 519 |
+
enum stok_opname_status {
|
| 520 |
+
draft
|
| 521 |
+
approved
|
| 522 |
+
}
|
| 523 |
+
|
| 524 |
+
enum transaksi_retur_tipe {
|
| 525 |
+
refund
|
| 526 |
+
exchange
|
| 527 |
+
}
|
| 528 |
+
|
| 529 |
+
enum shift_status {
|
| 530 |
+
buka
|
| 531 |
+
tutup
|
| 532 |
+
}
|
| 533 |
+
|
| 534 |
+
enum transaksi_status {
|
| 535 |
+
lunas
|
| 536 |
+
bon
|
| 537 |
+
void
|
| 538 |
+
}
|
src/app.ts
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import express, { Application, Request, Response } from 'express';
|
| 2 |
+
import cors from 'cors';
|
| 3 |
+
import router from './routes';
|
| 4 |
+
import { errorHandler } from './middlewares/error.middleware';
|
| 5 |
+
|
| 6 |
+
const app: Application = express();
|
| 7 |
+
|
| 8 |
+
// Global Middleware
|
| 9 |
+
app.use(cors());
|
| 10 |
+
app.use(express.json({ limit: '50mb' })); // Allow larger payloads (useful for base64 images)
|
| 11 |
+
app.use(express.urlencoded({ extended: true, limit: '50mb' }));
|
| 12 |
+
|
| 13 |
+
// Routing API
|
| 14 |
+
app.use('/api', router);
|
| 15 |
+
|
| 16 |
+
// Default Route
|
| 17 |
+
app.get('/', (req: Request, res: Response) => {
|
| 18 |
+
res.json({ message: 'Welcome to Tokiva POS Node.js API Server' });
|
| 19 |
+
});
|
| 20 |
+
|
| 21 |
+
// Global Error Handler
|
| 22 |
+
app.use(errorHandler);
|
| 23 |
+
|
| 24 |
+
export default app;
|
src/controllers/auth.controller.ts
ADDED
|
@@ -0,0 +1,69 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import { Request, Response, NextFunction } from 'express';
|
| 2 |
+
import { AuthService } from '../services/auth.service';
|
| 3 |
+
import { sukses } from '../utils/response';
|
| 4 |
+
import { AuthenticatedRequest } from '../middlewares/auth.middleware';
|
| 5 |
+
|
| 6 |
+
export class AuthController {
|
| 7 |
+
static async login(req: Request, res: Response, next: NextFunction) {
|
| 8 |
+
try {
|
| 9 |
+
const { username, password } = req.body;
|
| 10 |
+
const ip = (req.ip || req.socket.remoteAddress || 'unknown').replace('::ffff:', '');
|
| 11 |
+
const userAgent = (req.headers['user-agent'] || 'unknown') as string;
|
| 12 |
+
|
| 13 |
+
const result = await AuthService.login({
|
| 14 |
+
username,
|
| 15 |
+
passwordSecret: password,
|
| 16 |
+
ip,
|
| 17 |
+
userAgent,
|
| 18 |
+
});
|
| 19 |
+
|
| 20 |
+
return sukses(
|
| 21 |
+
res,
|
| 22 |
+
{
|
| 23 |
+
access_token: result.accessToken,
|
| 24 |
+
refresh_token: result.refreshToken,
|
| 25 |
+
token_type: 'bearer',
|
| 26 |
+
role: result.user.role,
|
| 27 |
+
nama: result.user.nama,
|
| 28 |
+
},
|
| 29 |
+
'Login berhasil'
|
| 30 |
+
);
|
| 31 |
+
} catch (error) {
|
| 32 |
+
next(error);
|
| 33 |
+
}
|
| 34 |
+
}
|
| 35 |
+
|
| 36 |
+
static async refresh(req: Request, res: Response, next: NextFunction) {
|
| 37 |
+
try {
|
| 38 |
+
const { refresh_token } = req.body;
|
| 39 |
+
const result = await AuthService.refresh(refresh_token);
|
| 40 |
+
|
| 41 |
+
return sukses(
|
| 42 |
+
res,
|
| 43 |
+
{
|
| 44 |
+
access_token: result.accessToken,
|
| 45 |
+
refresh_token: result.refreshToken,
|
| 46 |
+
token_type: 'bearer',
|
| 47 |
+
role: result.role,
|
| 48 |
+
},
|
| 49 |
+
'Token berhasil diperbarui'
|
| 50 |
+
);
|
| 51 |
+
} catch (error) {
|
| 52 |
+
next(error);
|
| 53 |
+
}
|
| 54 |
+
}
|
| 55 |
+
|
| 56 |
+
static async logout(req: AuthenticatedRequest, res: Response, next: NextFunction) {
|
| 57 |
+
try {
|
| 58 |
+
const ip = (req.ip || req.socket.remoteAddress || 'unknown').replace('::ffff:', '');
|
| 59 |
+
const userAgent = (req.headers['user-agent'] || 'unknown') as string;
|
| 60 |
+
const userId = req.user.id;
|
| 61 |
+
|
| 62 |
+
await AuthService.logout(userId, ip, userAgent);
|
| 63 |
+
|
| 64 |
+
return sukses(res, null, 'Logout berhasil');
|
| 65 |
+
} catch (error) {
|
| 66 |
+
next(error);
|
| 67 |
+
}
|
| 68 |
+
}
|
| 69 |
+
}
|
src/controllers/bon.controller.ts
ADDED
|
@@ -0,0 +1,63 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import { Request, Response, NextFunction } from 'express';
|
| 2 |
+
import { BonService } from '../services/bon.service';
|
| 3 |
+
import { sukses } from '../utils/response';
|
| 4 |
+
import { AuthenticatedRequest } from '../middlewares/auth.middleware';
|
| 5 |
+
|
| 6 |
+
export class BonController {
|
| 7 |
+
static async list(req: Request, res: Response, next: NextFunction) {
|
| 8 |
+
try {
|
| 9 |
+
const skip = parseInt(req.query.skip as string) || 0;
|
| 10 |
+
const limit = parseInt(req.query.limit as string) || 10;
|
| 11 |
+
const statusFilter = (req.query.status_filter as any) || undefined;
|
| 12 |
+
const memberId = req.query.member_id ? parseInt(req.query.member_id as string) : undefined;
|
| 13 |
+
|
| 14 |
+
const bonList = await BonService.getBon(skip, limit, statusFilter, memberId);
|
| 15 |
+
return sukses(res, bonList, 'Daftar kasbon berhasil dimuat');
|
| 16 |
+
} catch (error) {
|
| 17 |
+
next(error);
|
| 18 |
+
}
|
| 19 |
+
}
|
| 20 |
+
|
| 21 |
+
static async get(req: Request, res: Response, next: NextFunction) {
|
| 22 |
+
try {
|
| 23 |
+
const bonId = parseInt(req.params.id);
|
| 24 |
+
const bon = await BonService.getBonById(bonId);
|
| 25 |
+
return sukses(res, bon, 'Detail kasbon berhasil dimuat');
|
| 26 |
+
} catch (error) {
|
| 27 |
+
next(error);
|
| 28 |
+
}
|
| 29 |
+
}
|
| 30 |
+
|
| 31 |
+
static async payInstallment(req: AuthenticatedRequest, res: Response, next: NextFunction) {
|
| 32 |
+
try {
|
| 33 |
+
const bonId = parseInt(req.params.id);
|
| 34 |
+
const actorId = req.user.id;
|
| 35 |
+
const ip = (req.ip || req.socket.remoteAddress || 'unknown').replace('::ffff:', '');
|
| 36 |
+
const userAgent = (req.headers['user-agent'] || 'unknown') as string;
|
| 37 |
+
|
| 38 |
+
const { nominal, metode } = req.body;
|
| 39 |
+
const bon = await BonService.bayarCicilan({
|
| 40 |
+
bonId,
|
| 41 |
+
nominal,
|
| 42 |
+
metode,
|
| 43 |
+
actorId,
|
| 44 |
+
ip,
|
| 45 |
+
userAgent,
|
| 46 |
+
});
|
| 47 |
+
|
| 48 |
+
return sukses(res, bon, 'Cicilan kasbon berhasil dibayarkan');
|
| 49 |
+
} catch (error) {
|
| 50 |
+
next(error);
|
| 51 |
+
}
|
| 52 |
+
}
|
| 53 |
+
|
| 54 |
+
static async sendDebtReminder(req: AuthenticatedRequest, res: Response, next: NextFunction) {
|
| 55 |
+
try {
|
| 56 |
+
const bonId = parseInt(req.params.id);
|
| 57 |
+
const waLink = await BonService.kirimReminderWA(bonId);
|
| 58 |
+
return sukses(res, { wa_link: waLink }, 'WhatsApp reminder link generated successfully');
|
| 59 |
+
} catch (error) {
|
| 60 |
+
next(error);
|
| 61 |
+
}
|
| 62 |
+
}
|
| 63 |
+
}
|
src/controllers/kategori.controller.ts
ADDED
|
@@ -0,0 +1,60 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import { Request, Response, NextFunction } from 'express';
|
| 2 |
+
import { KategoriService } from '../services/kategori.service';
|
| 3 |
+
import { sukses } from '../utils/response';
|
| 4 |
+
import { AuthenticatedRequest } from '../middlewares/auth.middleware';
|
| 5 |
+
|
| 6 |
+
export class KategoriController {
|
| 7 |
+
static async list(req: Request, res: Response, next: NextFunction) {
|
| 8 |
+
try {
|
| 9 |
+
const skip = parseInt(req.query.skip as string) || 0;
|
| 10 |
+
const limit = parseInt(req.query.limit as string) || 100;
|
| 11 |
+
const pencarian = req.query.pencarian as string || undefined;
|
| 12 |
+
|
| 13 |
+
const categories = await KategoriService.getKategori(skip, limit, pencarian);
|
| 14 |
+
return sukses(res, categories, 'Daftar kategori berhasil dimuat');
|
| 15 |
+
} catch (error) {
|
| 16 |
+
next(error);
|
| 17 |
+
}
|
| 18 |
+
}
|
| 19 |
+
|
| 20 |
+
static async create(req: AuthenticatedRequest, res: Response, next: NextFunction) {
|
| 21 |
+
try {
|
| 22 |
+
const actorId = req.user.id;
|
| 23 |
+
const ip = (req.ip || req.socket.remoteAddress || 'unknown').replace('::ffff:', '');
|
| 24 |
+
const userAgent = (req.headers['user-agent'] || 'unknown') as string;
|
| 25 |
+
|
| 26 |
+
const category = await KategoriService.create(req.body, actorId, ip, userAgent);
|
| 27 |
+
return sukses(res, category, 'Kategori berhasil dibuat');
|
| 28 |
+
} catch (error) {
|
| 29 |
+
next(error);
|
| 30 |
+
}
|
| 31 |
+
}
|
| 32 |
+
|
| 33 |
+
static async update(req: AuthenticatedRequest, res: Response, next: NextFunction) {
|
| 34 |
+
try {
|
| 35 |
+
const kategoriId = parseInt(req.params.id);
|
| 36 |
+
const actorId = req.user.id;
|
| 37 |
+
const ip = (req.ip || req.socket.remoteAddress || 'unknown').replace('::ffff:', '');
|
| 38 |
+
const userAgent = (req.headers['user-agent'] || 'unknown') as string;
|
| 39 |
+
|
| 40 |
+
const category = await KategoriService.update(kategoriId, req.body, actorId, ip, userAgent);
|
| 41 |
+
return sukses(res, category, 'Kategori berhasil diperbarui');
|
| 42 |
+
} catch (error) {
|
| 43 |
+
next(error);
|
| 44 |
+
}
|
| 45 |
+
}
|
| 46 |
+
|
| 47 |
+
static async delete(req: AuthenticatedRequest, res: Response, next: NextFunction) {
|
| 48 |
+
try {
|
| 49 |
+
const kategoriId = parseInt(req.params.id);
|
| 50 |
+
const actorId = req.user.id;
|
| 51 |
+
const ip = (req.ip || req.socket.remoteAddress || 'unknown').replace('::ffff:', '');
|
| 52 |
+
const userAgent = (req.headers['user-agent'] || 'unknown') as string;
|
| 53 |
+
|
| 54 |
+
await KategoriService.delete(kategoriId, actorId, ip, userAgent);
|
| 55 |
+
return sukses(res, null, 'Kategori berhasil dinonaktifkan');
|
| 56 |
+
} catch (error) {
|
| 57 |
+
next(error);
|
| 58 |
+
}
|
| 59 |
+
}
|
| 60 |
+
}
|
src/controllers/member.controller.ts
ADDED
|
@@ -0,0 +1,60 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import { Request, Response, NextFunction } from 'express';
|
| 2 |
+
import { MemberService } from '../services/member.service';
|
| 3 |
+
import { sukses } from '../utils/response';
|
| 4 |
+
import { AuthenticatedRequest } from '../middlewares/auth.middleware';
|
| 5 |
+
|
| 6 |
+
export class MemberController {
|
| 7 |
+
static async list(req: Request, res: Response, next: NextFunction) {
|
| 8 |
+
try {
|
| 9 |
+
const skip = parseInt(req.query.skip as string) || 0;
|
| 10 |
+
const limit = parseInt(req.query.limit as string) || 10;
|
| 11 |
+
const pencarian = req.query.pencarian as string || undefined;
|
| 12 |
+
|
| 13 |
+
const members = await MemberService.getMembers(skip, limit, pencarian);
|
| 14 |
+
return sukses(res, members, 'Daftar member berhasil dimuat');
|
| 15 |
+
} catch (error) {
|
| 16 |
+
next(error);
|
| 17 |
+
}
|
| 18 |
+
}
|
| 19 |
+
|
| 20 |
+
static async create(req: AuthenticatedRequest, res: Response, next: NextFunction) {
|
| 21 |
+
try {
|
| 22 |
+
const actorId = req.user.id;
|
| 23 |
+
const ip = (req.ip || req.socket.remoteAddress || 'unknown').replace('::ffff:', '');
|
| 24 |
+
const userAgent = (req.headers['user-agent'] || 'unknown') as string;
|
| 25 |
+
|
| 26 |
+
const member = await MemberService.create(req.body, actorId, ip, userAgent);
|
| 27 |
+
return sukses(res, member, 'Member berhasil didaftarkan');
|
| 28 |
+
} catch (error) {
|
| 29 |
+
next(error);
|
| 30 |
+
}
|
| 31 |
+
}
|
| 32 |
+
|
| 33 |
+
static async update(req: AuthenticatedRequest, res: Response, next: NextFunction) {
|
| 34 |
+
try {
|
| 35 |
+
const memberId = parseInt(req.params.id);
|
| 36 |
+
const actorId = req.user.id;
|
| 37 |
+
const ip = (req.ip || req.socket.remoteAddress || 'unknown').replace('::ffff:', '');
|
| 38 |
+
const userAgent = (req.headers['user-agent'] || 'unknown') as string;
|
| 39 |
+
|
| 40 |
+
const member = await MemberService.update(memberId, req.body, actorId, ip, userAgent);
|
| 41 |
+
return sukses(res, member, 'Member berhasil diperbarui');
|
| 42 |
+
} catch (error) {
|
| 43 |
+
next(error);
|
| 44 |
+
}
|
| 45 |
+
}
|
| 46 |
+
|
| 47 |
+
static async delete(req: AuthenticatedRequest, res: Response, next: NextFunction) {
|
| 48 |
+
try {
|
| 49 |
+
const memberId = parseInt(req.params.id);
|
| 50 |
+
const actorId = req.user.id;
|
| 51 |
+
const ip = (req.ip || req.socket.remoteAddress || 'unknown').replace('::ffff:', '');
|
| 52 |
+
const userAgent = (req.headers['user-agent'] || 'unknown') as string;
|
| 53 |
+
|
| 54 |
+
await MemberService.delete(memberId, actorId, ip, userAgent);
|
| 55 |
+
return sukses(res, null, 'Member berhasil dinonaktifkan');
|
| 56 |
+
} catch (error) {
|
| 57 |
+
next(error);
|
| 58 |
+
}
|
| 59 |
+
}
|
| 60 |
+
}
|
src/controllers/ops.controller.ts
ADDED
|
@@ -0,0 +1,159 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import { Request, Response, NextFunction } from 'express';
|
| 2 |
+
import { OpsService } from '../services/ops.service';
|
| 3 |
+
import { sukses } from '../utils/response';
|
| 4 |
+
import { AuthenticatedRequest } from '../middlewares/auth.middleware';
|
| 5 |
+
|
| 6 |
+
export class OpsController {
|
| 7 |
+
// Shift Kasir
|
| 8 |
+
static async getActiveShift(req: AuthenticatedRequest, res: Response, next: NextFunction) {
|
| 9 |
+
try {
|
| 10 |
+
const userId = req.user.id;
|
| 11 |
+
const shift = await OpsService.getActiveShift(userId);
|
| 12 |
+
if (!shift) {
|
| 13 |
+
return sukses(res, null, 'Tidak ada shift aktif untuk kasir saat ini');
|
| 14 |
+
}
|
| 15 |
+
return sukses(res, shift, 'Shift aktif berhasil dimuat');
|
| 16 |
+
} catch (error) {
|
| 17 |
+
next(error);
|
| 18 |
+
}
|
| 19 |
+
}
|
| 20 |
+
|
| 21 |
+
static async openShift(req: AuthenticatedRequest, res: Response, next: NextFunction) {
|
| 22 |
+
try {
|
| 23 |
+
const actorId = req.user.id;
|
| 24 |
+
const ip = (req.ip || req.socket.remoteAddress || 'unknown').replace('::ffff:', '');
|
| 25 |
+
const userAgent = (req.headers['user-agent'] || 'unknown') as string;
|
| 26 |
+
const { modal_awal, catatan } = req.body;
|
| 27 |
+
|
| 28 |
+
const shift = await OpsService.bukaShift(modal_awal, catatan, actorId, ip, userAgent);
|
| 29 |
+
return sukses(res, shift, 'Shift berhasil dibuka');
|
| 30 |
+
} catch (error) {
|
| 31 |
+
next(error);
|
| 32 |
+
}
|
| 33 |
+
}
|
| 34 |
+
|
| 35 |
+
static async closeShift(req: AuthenticatedRequest, res: Response, next: NextFunction) {
|
| 36 |
+
try {
|
| 37 |
+
const actorId = req.user.id;
|
| 38 |
+
const ip = (req.ip || req.socket.remoteAddress || 'unknown').replace('::ffff:', '');
|
| 39 |
+
const userAgent = (req.headers['user-agent'] || 'unknown') as string;
|
| 40 |
+
const { modal_akhir_fisik, catatan } = req.body;
|
| 41 |
+
|
| 42 |
+
const shift = await OpsService.tutupShift(modal_akhir_fisik, catatan, actorId, ip, userAgent);
|
| 43 |
+
return sukses(res, shift, 'Shift kasir ditutup, selisih kas fisik berhasil dicatat');
|
| 44 |
+
} catch (error) {
|
| 45 |
+
next(error);
|
| 46 |
+
}
|
| 47 |
+
}
|
| 48 |
+
|
| 49 |
+
// Dashboard & Laporan
|
| 50 |
+
static async getDashboardSummary(req: AuthenticatedRequest, res: Response, next: NextFunction) {
|
| 51 |
+
try {
|
| 52 |
+
const summary = await OpsService.getDashboardSummary();
|
| 53 |
+
return sukses(res, summary, 'Dashboard summary berhasil dimuat');
|
| 54 |
+
} catch (error) {
|
| 55 |
+
next(error);
|
| 56 |
+
}
|
| 57 |
+
}
|
| 58 |
+
|
| 59 |
+
static async getSalesReport(req: AuthenticatedRequest, res: Response, next: NextFunction) {
|
| 60 |
+
try {
|
| 61 |
+
const tglMulai = new Date(req.query.tgl_mulai as string);
|
| 62 |
+
const tglSelesai = new Date(req.query.tgl_selesai as string);
|
| 63 |
+
|
| 64 |
+
const report = await OpsService.buatLaporanPenjualan(tglMulai, tglSelesai);
|
| 65 |
+
return sukses(res, report, 'Laporan penjualan berhasil dibuat');
|
| 66 |
+
} catch (error) {
|
| 67 |
+
next(error);
|
| 68 |
+
}
|
| 69 |
+
}
|
| 70 |
+
|
| 71 |
+
static async getProfitLossReport(req: AuthenticatedRequest, res: Response, next: NextFunction) {
|
| 72 |
+
try {
|
| 73 |
+
const tglMulai = new Date(req.query.tgl_mulai as string);
|
| 74 |
+
const tglSelesai = new Date(req.query.tgl_selesai as string);
|
| 75 |
+
|
| 76 |
+
const report = await OpsService.buatLaporanLabaRugi(tglMulai, tglSelesai);
|
| 77 |
+
return sukses(res, report, 'Laporan laba rugi berhasil dibuat');
|
| 78 |
+
} catch (error) {
|
| 79 |
+
next(error);
|
| 80 |
+
}
|
| 81 |
+
}
|
| 82 |
+
|
| 83 |
+
// Settings/Pengaturan
|
| 84 |
+
static async getSettings(req: Request, res: Response, next: NextFunction) {
|
| 85 |
+
try {
|
| 86 |
+
const settings = await OpsService.getSettings();
|
| 87 |
+
return sukses(res, settings, 'Pengaturan toko berhasil dimuat');
|
| 88 |
+
} catch (error) {
|
| 89 |
+
next(error);
|
| 90 |
+
}
|
| 91 |
+
}
|
| 92 |
+
|
| 93 |
+
static async getSettingByKey(req: Request, res: Response, next: NextFunction) {
|
| 94 |
+
try {
|
| 95 |
+
const { kunci } = req.params;
|
| 96 |
+
const value = await OpsService.getSettingByKey(kunci);
|
| 97 |
+
return sukses(res, { kunci, nilai: value }, `Pengaturan '${kunci}' berhasil dimuat`);
|
| 98 |
+
} catch (error) {
|
| 99 |
+
next(error);
|
| 100 |
+
}
|
| 101 |
+
}
|
| 102 |
+
|
| 103 |
+
static async updateSetting(req: AuthenticatedRequest, res: Response, next: NextFunction) {
|
| 104 |
+
try {
|
| 105 |
+
const { kunci } = req.params;
|
| 106 |
+
const actorId = req.user.id;
|
| 107 |
+
const ip = (req.ip || req.socket.remoteAddress || 'unknown').replace('::ffff:', '');
|
| 108 |
+
const userAgent = (req.headers['user-agent'] || 'unknown') as string;
|
| 109 |
+
|
| 110 |
+
await OpsService.updateSetting(kunci, req.body, actorId, ip, userAgent);
|
| 111 |
+
const value = await OpsService.getSettingByKey(kunci);
|
| 112 |
+
return sukses(res, { kunci, nilai: value }, 'Pengaturan berhasil diperbarui');
|
| 113 |
+
} catch (error) {
|
| 114 |
+
next(error);
|
| 115 |
+
}
|
| 116 |
+
}
|
| 117 |
+
|
| 118 |
+
// Notifikasi
|
| 119 |
+
static async listNotifications(req: Request, res: Response, next: NextFunction) {
|
| 120 |
+
try {
|
| 121 |
+
const skip = parseInt(req.query.skip as string) || 0;
|
| 122 |
+
const limit = parseInt(req.query.limit as string) || 20;
|
| 123 |
+
const dibaca = req.query.dibaca !== undefined ? parseInt(req.query.dibaca as string) : undefined;
|
| 124 |
+
|
| 125 |
+
const notifs = await OpsService.getNotifications(skip, limit, dibaca);
|
| 126 |
+
return sukses(res, notifs, 'Notifikasi berhasil dimuat');
|
| 127 |
+
} catch (error) {
|
| 128 |
+
next(error);
|
| 129 |
+
}
|
| 130 |
+
}
|
| 131 |
+
|
| 132 |
+
static async readAllNotifications(req: Request, res: Response, next: NextFunction) {
|
| 133 |
+
try {
|
| 134 |
+
const affected = await OpsService.markAllNotifAsRead();
|
| 135 |
+
return sukses(res, { jumlah_diubah: affected }, 'Semua notifikasi ditandai dibaca');
|
| 136 |
+
} catch (error) {
|
| 137 |
+
next(error);
|
| 138 |
+
}
|
| 139 |
+
}
|
| 140 |
+
|
| 141 |
+
static async readNotification(req: Request, res: Response, next: NextFunction) {
|
| 142 |
+
try {
|
| 143 |
+
const notifId = parseInt(req.params.notif_id);
|
| 144 |
+
const notif = await OpsService.markNotifAsRead(notifId);
|
| 145 |
+
return sukses(res, notif, 'Notifikasi ditandai dibaca');
|
| 146 |
+
} catch (error) {
|
| 147 |
+
next(error);
|
| 148 |
+
}
|
| 149 |
+
}
|
| 150 |
+
|
| 151 |
+
static async getUnreadNotificationCount(req: Request, res: Response, next: NextFunction) {
|
| 152 |
+
try {
|
| 153 |
+
const count = await OpsService.getUnreadNotifCount();
|
| 154 |
+
return sukses(res, { unread_count: count }, 'Jumlah notifikasi belum dibaca berhasil dihitung');
|
| 155 |
+
} catch (error) {
|
| 156 |
+
next(error);
|
| 157 |
+
}
|
| 158 |
+
}
|
| 159 |
+
}
|
src/controllers/pengguna.controller.ts
ADDED
|
@@ -0,0 +1,84 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import { Request, Response, NextFunction } from 'express';
|
| 2 |
+
import { PenggunaService } from '../services/pengguna.service';
|
| 3 |
+
import { sukses } from '../utils/response';
|
| 4 |
+
import { AuthenticatedRequest } from '../middlewares/auth.middleware';
|
| 5 |
+
|
| 6 |
+
export class PenggunaController {
|
| 7 |
+
private static formatUser(user: any) {
|
| 8 |
+
const { password_hash, ...rest } = user;
|
| 9 |
+
return rest;
|
| 10 |
+
}
|
| 11 |
+
|
| 12 |
+
static async list(req: AuthenticatedRequest, res: Response, next: NextFunction) {
|
| 13 |
+
try {
|
| 14 |
+
const skip = parseInt(req.query.skip as string) || 0;
|
| 15 |
+
const limit = parseInt(req.query.limit as string) || 10;
|
| 16 |
+
const aktif = req.query.aktif !== undefined ? parseInt(req.query.aktif as string) : undefined;
|
| 17 |
+
|
| 18 |
+
const users = await PenggunaService.getUsers(skip, limit, aktif);
|
| 19 |
+
const data = users.map((u: any) => PenggunaController.formatUser(u));
|
| 20 |
+
|
| 21 |
+
return sukses(res, data, 'Daftar pengguna berhasil dimuat');
|
| 22 |
+
} catch (error) {
|
| 23 |
+
next(error);
|
| 24 |
+
}
|
| 25 |
+
}
|
| 26 |
+
|
| 27 |
+
static async get(req: AuthenticatedRequest, res: Response, next: NextFunction) {
|
| 28 |
+
try {
|
| 29 |
+
const userId = parseInt(req.params.id);
|
| 30 |
+
const user = await PenggunaService.getUserById(userId);
|
| 31 |
+
const data = PenggunaController.formatUser(user);
|
| 32 |
+
|
| 33 |
+
return sukses(res, data, 'Detail pengguna berhasil dimuat');
|
| 34 |
+
} catch (error) {
|
| 35 |
+
next(error);
|
| 36 |
+
}
|
| 37 |
+
}
|
| 38 |
+
|
| 39 |
+
static async create(req: AuthenticatedRequest, res: Response, next: NextFunction) {
|
| 40 |
+
try {
|
| 41 |
+
const actorId = req.user.id;
|
| 42 |
+
const ip = (req.ip || req.socket.remoteAddress || 'unknown').replace('::ffff:', '');
|
| 43 |
+
const userAgent = (req.headers['user-agent'] || 'unknown') as string;
|
| 44 |
+
|
| 45 |
+
const user = await PenggunaService.create(req.body, actorId, ip, userAgent);
|
| 46 |
+
const data = PenggunaController.formatUser(user);
|
| 47 |
+
|
| 48 |
+
return sukses(res, data, 'Pengguna berhasil dibuat', null, 200);
|
| 49 |
+
} catch (error) {
|
| 50 |
+
next(error);
|
| 51 |
+
}
|
| 52 |
+
}
|
| 53 |
+
|
| 54 |
+
static async update(req: AuthenticatedRequest, res: Response, next: NextFunction) {
|
| 55 |
+
try {
|
| 56 |
+
const userId = parseInt(req.params.id);
|
| 57 |
+
const actorId = req.user.id;
|
| 58 |
+
const ip = (req.ip || req.socket.remoteAddress || 'unknown').replace('::ffff:', '');
|
| 59 |
+
const userAgent = (req.headers['user-agent'] || 'unknown') as string;
|
| 60 |
+
|
| 61 |
+
const user = await PenggunaService.update(userId, req.body, actorId, ip, userAgent);
|
| 62 |
+
const data = PenggunaController.formatUser(user);
|
| 63 |
+
|
| 64 |
+
return sukses(res, data, 'Pengguna berhasil diperbarui');
|
| 65 |
+
} catch (error) {
|
| 66 |
+
next(error);
|
| 67 |
+
}
|
| 68 |
+
}
|
| 69 |
+
|
| 70 |
+
static async delete(req: AuthenticatedRequest, res: Response, next: NextFunction) {
|
| 71 |
+
try {
|
| 72 |
+
const userId = parseInt(req.params.id);
|
| 73 |
+
const actorId = req.user.id;
|
| 74 |
+
const ip = (req.ip || req.socket.remoteAddress || 'unknown').replace('::ffff:', '');
|
| 75 |
+
const userAgent = (req.headers['user-agent'] || 'unknown') as string;
|
| 76 |
+
|
| 77 |
+
await PenggunaService.delete(userId, actorId, ip, userAgent);
|
| 78 |
+
|
| 79 |
+
return sukses(res, null, 'Pengguna berhasil dinonaktifkan');
|
| 80 |
+
} catch (error) {
|
| 81 |
+
next(error);
|
| 82 |
+
}
|
| 83 |
+
}
|
| 84 |
+
}
|
src/controllers/produk.controller.ts
ADDED
|
@@ -0,0 +1,88 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import { Request, Response, NextFunction } from 'express';
|
| 2 |
+
import { ProdukService } from '../services/produk.service';
|
| 3 |
+
import { sukses } from '../utils/response';
|
| 4 |
+
import { AuthenticatedRequest } from '../middlewares/auth.middleware';
|
| 5 |
+
|
| 6 |
+
export class ProdukController {
|
| 7 |
+
static async list(req: Request, res: Response, next: NextFunction) {
|
| 8 |
+
try {
|
| 9 |
+
const skip = parseInt(req.query.skip as string) || 0;
|
| 10 |
+
const limit = parseInt(req.query.limit as string) || 10;
|
| 11 |
+
const kategoriId = req.query.kategori_id ? parseInt(req.query.kategori_id as string) : undefined;
|
| 12 |
+
const supplierId = req.query.supplier_id ? parseInt(req.query.supplier_id as string) : undefined;
|
| 13 |
+
const pencarian = req.query.pencarian as string || undefined;
|
| 14 |
+
|
| 15 |
+
const { products, total } = await ProdukService.getProduk(skip, limit, kategoriId, supplierId, pencarian);
|
| 16 |
+
const page = Math.floor(skip / limit) + 1;
|
| 17 |
+
|
| 18 |
+
return sukses(res, products, 'Daftar produk berhasil dimuat', {
|
| 19 |
+
total,
|
| 20 |
+
page,
|
| 21 |
+
per_page: limit,
|
| 22 |
+
});
|
| 23 |
+
} catch (error) {
|
| 24 |
+
next(error);
|
| 25 |
+
}
|
| 26 |
+
}
|
| 27 |
+
|
| 28 |
+
static async get(req: Request, res: Response, next: NextFunction) {
|
| 29 |
+
try {
|
| 30 |
+
const produkId = parseInt(req.params.id);
|
| 31 |
+
const product = await ProdukService.getProdukById(produkId);
|
| 32 |
+
return sukses(res, product, 'Detail produk berhasil dimuat');
|
| 33 |
+
} catch (error) {
|
| 34 |
+
next(error);
|
| 35 |
+
}
|
| 36 |
+
}
|
| 37 |
+
|
| 38 |
+
static async scan(req: Request, res: Response, next: NextFunction) {
|
| 39 |
+
try {
|
| 40 |
+
const { barcode_or_kode } = req.params;
|
| 41 |
+
const product = await ProdukService.getProdukByBarcodeOrKode(barcode_or_kode);
|
| 42 |
+
return sukses(res, product, 'Produk terdeteksi');
|
| 43 |
+
} catch (error) {
|
| 44 |
+
next(error);
|
| 45 |
+
}
|
| 46 |
+
}
|
| 47 |
+
|
| 48 |
+
static async create(req: AuthenticatedRequest, res: Response, next: NextFunction) {
|
| 49 |
+
try {
|
| 50 |
+
const actorId = req.user.id;
|
| 51 |
+
const ip = (req.ip || req.socket.remoteAddress || 'unknown').replace('::ffff:', '');
|
| 52 |
+
const userAgent = (req.headers['user-agent'] || 'unknown') as string;
|
| 53 |
+
|
| 54 |
+
const product = await ProdukService.create(req.body, actorId, ip, userAgent);
|
| 55 |
+
return sukses(res, product, 'Produk berhasil dibuat');
|
| 56 |
+
} catch (error) {
|
| 57 |
+
next(error);
|
| 58 |
+
}
|
| 59 |
+
}
|
| 60 |
+
|
| 61 |
+
static async update(req: AuthenticatedRequest, res: Response, next: NextFunction) {
|
| 62 |
+
try {
|
| 63 |
+
const produkId = parseInt(req.params.id);
|
| 64 |
+
const actorId = req.user.id;
|
| 65 |
+
const ip = (req.ip || req.socket.remoteAddress || 'unknown').replace('::ffff:', '');
|
| 66 |
+
const userAgent = (req.headers['user-agent'] || 'unknown') as string;
|
| 67 |
+
|
| 68 |
+
const product = await ProdukService.update(produkId, req.body, actorId, ip, userAgent);
|
| 69 |
+
return sukses(res, product, 'Produk berhasil diperbarui');
|
| 70 |
+
} catch (error) {
|
| 71 |
+
next(error);
|
| 72 |
+
}
|
| 73 |
+
}
|
| 74 |
+
|
| 75 |
+
static async delete(req: AuthenticatedRequest, res: Response, next: NextFunction) {
|
| 76 |
+
try {
|
| 77 |
+
const produkId = parseInt(req.params.id);
|
| 78 |
+
const actorId = req.user.id;
|
| 79 |
+
const ip = (req.ip || req.socket.remoteAddress || 'unknown').replace('::ffff:', '');
|
| 80 |
+
const userAgent = (req.headers['user-agent'] || 'unknown') as string;
|
| 81 |
+
|
| 82 |
+
await ProdukService.delete(produkId, actorId, ip, userAgent);
|
| 83 |
+
return sukses(res, null, 'Produk berhasil dihapus (soft-delete)');
|
| 84 |
+
} catch (error) {
|
| 85 |
+
next(error);
|
| 86 |
+
}
|
| 87 |
+
}
|
| 88 |
+
}
|
src/controllers/stok.controller.ts
ADDED
|
@@ -0,0 +1,73 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import { Request, Response, NextFunction } from 'express';
|
| 2 |
+
import { StokService } from '../services/stok.service';
|
| 3 |
+
import { sukses } from '../utils/response';
|
| 4 |
+
import { AuthenticatedRequest } from '../middlewares/auth.middleware';
|
| 5 |
+
|
| 6 |
+
export class StokController {
|
| 7 |
+
static async addIncomingStock(req: AuthenticatedRequest, res: Response, next: NextFunction) {
|
| 8 |
+
try {
|
| 9 |
+
const actorId = req.user.id;
|
| 10 |
+
const ip = (req.ip || req.socket.remoteAddress || 'unknown').replace('::ffff:', '');
|
| 11 |
+
const userAgent = (req.headers['user-agent'] || 'unknown') as string;
|
| 12 |
+
|
| 13 |
+
const batch = await StokService.tambahBarangMasuk(req.body, actorId, ip, userAgent);
|
| 14 |
+
return sukses(res, batch, 'Barang masuk berhasil dicatat');
|
| 15 |
+
} catch (error) {
|
| 16 |
+
next(error);
|
| 17 |
+
}
|
| 18 |
+
}
|
| 19 |
+
|
| 20 |
+
static async createStockOpname(req: AuthenticatedRequest, res: Response, next: NextFunction) {
|
| 21 |
+
try {
|
| 22 |
+
const actorId = req.user.id;
|
| 23 |
+
const ip = (req.ip || req.socket.remoteAddress || 'unknown').replace('::ffff:', '');
|
| 24 |
+
const userAgent = (req.headers['user-agent'] || 'unknown') as string;
|
| 25 |
+
|
| 26 |
+
const opname = await StokService.buatStokOpname(req.body, actorId, ip, userAgent);
|
| 27 |
+
return sukses(res, opname, 'Draft stok opname berhasil dibuat');
|
| 28 |
+
} catch (error) {
|
| 29 |
+
next(error);
|
| 30 |
+
}
|
| 31 |
+
}
|
| 32 |
+
|
| 33 |
+
static async approveStockOpname(req: AuthenticatedRequest, res: Response, next: NextFunction) {
|
| 34 |
+
try {
|
| 35 |
+
const opnameId = parseInt(req.params.opname_id);
|
| 36 |
+
const actorId = req.user.id;
|
| 37 |
+
const ip = (req.ip || req.socket.remoteAddress || 'unknown').replace('::ffff:', '');
|
| 38 |
+
const userAgent = (req.headers['user-agent'] || 'unknown') as string;
|
| 39 |
+
|
| 40 |
+
const opname = await StokService.approveStokOpname(opnameId, actorId, ip, userAgent);
|
| 41 |
+
return sukses(res, opname, 'Stok opname disetujui, kuantitas stok produk disesuaikan');
|
| 42 |
+
} catch (error) {
|
| 43 |
+
next(error);
|
| 44 |
+
}
|
| 45 |
+
}
|
| 46 |
+
|
| 47 |
+
static async getStockMutations(req: Request, res: Response, next: NextFunction) {
|
| 48 |
+
try {
|
| 49 |
+
const skip = parseInt(req.query.skip as string) || 0;
|
| 50 |
+
const limit = parseInt(req.query.limit as string) || 20;
|
| 51 |
+
const produkId = req.query.produk_id ? parseInt(req.query.produk_id as string) : undefined;
|
| 52 |
+
const tipe = req.query.tipe as string || undefined;
|
| 53 |
+
|
| 54 |
+
const logs = await StokService.getStokLogs(skip, limit, produkId, tipe);
|
| 55 |
+
return sukses(res, logs, 'Kartu stok berhasil dimuat');
|
| 56 |
+
} catch (error) {
|
| 57 |
+
next(error);
|
| 58 |
+
}
|
| 59 |
+
}
|
| 60 |
+
|
| 61 |
+
static async getStockBatches(req: Request, res: Response, next: NextFunction) {
|
| 62 |
+
try {
|
| 63 |
+
const skip = parseInt(req.query.skip as string) || 0;
|
| 64 |
+
const limit = parseInt(req.query.limit as string) || 20;
|
| 65 |
+
const produkId = req.query.produk_id ? parseInt(req.query.produk_id as string) : undefined;
|
| 66 |
+
|
| 67 |
+
const batches = await StokService.getStokBatches(skip, limit, produkId);
|
| 68 |
+
return sukses(res, batches, 'Batch stok berhasil dimuat');
|
| 69 |
+
} catch (error) {
|
| 70 |
+
next(error);
|
| 71 |
+
}
|
| 72 |
+
}
|
| 73 |
+
}
|
src/controllers/supplier.controller.ts
ADDED
|
@@ -0,0 +1,60 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import { Request, Response, NextFunction } from 'express';
|
| 2 |
+
import { SupplierService } from '../services/supplier.service';
|
| 3 |
+
import { sukses } from '../utils/response';
|
| 4 |
+
import { AuthenticatedRequest } from '../middlewares/auth.middleware';
|
| 5 |
+
|
| 6 |
+
export class SupplierController {
|
| 7 |
+
static async list(req: Request, res: Response, next: NextFunction) {
|
| 8 |
+
try {
|
| 9 |
+
const skip = parseInt(req.query.skip as string) || 0;
|
| 10 |
+
const limit = parseInt(req.query.limit as string) || 10;
|
| 11 |
+
const pencarian = req.query.pencarian as string || undefined;
|
| 12 |
+
|
| 13 |
+
const suppliers = await SupplierService.getSuppliers(skip, limit, pencarian);
|
| 14 |
+
return sukses(res, suppliers, 'Daftar supplier berhasil dimuat');
|
| 15 |
+
} catch (error) {
|
| 16 |
+
next(error);
|
| 17 |
+
}
|
| 18 |
+
}
|
| 19 |
+
|
| 20 |
+
static async create(req: AuthenticatedRequest, res: Response, next: NextFunction) {
|
| 21 |
+
try {
|
| 22 |
+
const actorId = req.user.id;
|
| 23 |
+
const ip = (req.ip || req.socket.remoteAddress || 'unknown').replace('::ffff:', '');
|
| 24 |
+
const userAgent = (req.headers['user-agent'] || 'unknown') as string;
|
| 25 |
+
|
| 26 |
+
const supplier = await SupplierService.create(req.body, actorId, ip, userAgent);
|
| 27 |
+
return sukses(res, supplier, 'Supplier berhasil ditambahkan');
|
| 28 |
+
} catch (error) {
|
| 29 |
+
next(error);
|
| 30 |
+
}
|
| 31 |
+
}
|
| 32 |
+
|
| 33 |
+
static async update(req: AuthenticatedRequest, res: Response, next: NextFunction) {
|
| 34 |
+
try {
|
| 35 |
+
const supplierId = parseInt(req.params.id);
|
| 36 |
+
const actorId = req.user.id;
|
| 37 |
+
const ip = (req.ip || req.socket.remoteAddress || 'unknown').replace('::ffff:', '');
|
| 38 |
+
const userAgent = (req.headers['user-agent'] || 'unknown') as string;
|
| 39 |
+
|
| 40 |
+
const supplier = await SupplierService.update(supplierId, req.body, actorId, ip, userAgent);
|
| 41 |
+
return sukses(res, supplier, 'Supplier berhasil diperbarui');
|
| 42 |
+
} catch (error) {
|
| 43 |
+
next(error);
|
| 44 |
+
}
|
| 45 |
+
}
|
| 46 |
+
|
| 47 |
+
static async delete(req: AuthenticatedRequest, res: Response, next: NextFunction) {
|
| 48 |
+
try {
|
| 49 |
+
const supplierId = parseInt(req.params.id);
|
| 50 |
+
const actorId = req.user.id;
|
| 51 |
+
const ip = (req.ip || req.socket.remoteAddress || 'unknown').replace('::ffff:', '');
|
| 52 |
+
const userAgent = (req.headers['user-agent'] || 'unknown') as string;
|
| 53 |
+
|
| 54 |
+
await SupplierService.delete(supplierId, actorId, ip, userAgent);
|
| 55 |
+
return sukses(res, null, 'Supplier berhasil dinonaktifkan');
|
| 56 |
+
} catch (error) {
|
| 57 |
+
next(error);
|
| 58 |
+
}
|
| 59 |
+
}
|
| 60 |
+
}
|
src/controllers/transaksi.controller.ts
ADDED
|
@@ -0,0 +1,118 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import { Request, Response, NextFunction } from 'express';
|
| 2 |
+
import { TransaksiService } from '../services/transaksi.service';
|
| 3 |
+
import { sukses, gagal } from '../utils/response';
|
| 4 |
+
import { AuthenticatedRequest } from '../middlewares/auth.middleware';
|
| 5 |
+
import prisma from '../utils/db';
|
| 6 |
+
|
| 7 |
+
export class TransaksiController {
|
| 8 |
+
static async create(req: AuthenticatedRequest, res: Response, next: NextFunction) {
|
| 9 |
+
try {
|
| 10 |
+
const actorId = req.user.id;
|
| 11 |
+
const ip = (req.ip || req.socket.remoteAddress || 'unknown').replace('::ffff:', '');
|
| 12 |
+
const userAgent = (req.headers['user-agent'] || 'unknown') as string;
|
| 13 |
+
|
| 14 |
+
const trans = await TransaksiService.prosesTransaksi({
|
| 15 |
+
data: req.body,
|
| 16 |
+
actorId,
|
| 17 |
+
ip,
|
| 18 |
+
userAgent,
|
| 19 |
+
});
|
| 20 |
+
|
| 21 |
+
return sukses(res, trans, 'Transaksi berhasil diproses');
|
| 22 |
+
} catch (error) {
|
| 23 |
+
next(error);
|
| 24 |
+
}
|
| 25 |
+
}
|
| 26 |
+
|
| 27 |
+
static async list(req: Request, res: Response, next: NextFunction) {
|
| 28 |
+
try {
|
| 29 |
+
const skip = parseInt(req.query.skip as string) || 0;
|
| 30 |
+
const limit = parseInt(req.query.limit as string) || 20;
|
| 31 |
+
const statusFilter = (req.query.status_filter as any) || undefined;
|
| 32 |
+
const memberId = req.query.member_id ? parseInt(req.query.member_id as string) : undefined;
|
| 33 |
+
|
| 34 |
+
const where: any = {};
|
| 35 |
+
if (statusFilter) where.status = statusFilter;
|
| 36 |
+
if (memberId) where.member_id = memberId;
|
| 37 |
+
|
| 38 |
+
const total = await prisma.transaksi.count({ where });
|
| 39 |
+
const trans = await prisma.transaksi.findMany({
|
| 40 |
+
where,
|
| 41 |
+
orderBy: { created_at: 'desc' },
|
| 42 |
+
skip,
|
| 43 |
+
take: limit,
|
| 44 |
+
include: {
|
| 45 |
+
transaksi_detail: true,
|
| 46 |
+
pembayaran: true,
|
| 47 |
+
},
|
| 48 |
+
});
|
| 49 |
+
|
| 50 |
+
const page = Math.floor(skip / limit) + 1;
|
| 51 |
+
|
| 52 |
+
return sukses(res, trans, 'Daftar transaksi berhasil dimuat', {
|
| 53 |
+
total,
|
| 54 |
+
page,
|
| 55 |
+
per_page: limit,
|
| 56 |
+
});
|
| 57 |
+
} catch (error) {
|
| 58 |
+
next(error);
|
| 59 |
+
}
|
| 60 |
+
}
|
| 61 |
+
|
| 62 |
+
static async get(req: Request, res: Response, next: NextFunction) {
|
| 63 |
+
try {
|
| 64 |
+
const transaksiId = parseInt(req.params.id);
|
| 65 |
+
const trans = await prisma.transaksi.findUnique({
|
| 66 |
+
where: { id: transaksiId },
|
| 67 |
+
include: {
|
| 68 |
+
transaksi_detail: true,
|
| 69 |
+
pembayaran: true,
|
| 70 |
+
user: {
|
| 71 |
+
select: {
|
| 72 |
+
id: true,
|
| 73 |
+
nama: true,
|
| 74 |
+
username: true,
|
| 75 |
+
},
|
| 76 |
+
},
|
| 77 |
+
member: true,
|
| 78 |
+
},
|
| 79 |
+
});
|
| 80 |
+
|
| 81 |
+
if (!trans) {
|
| 82 |
+
return gagal(res, 404, 'Transaksi tidak ditemukan');
|
| 83 |
+
}
|
| 84 |
+
|
| 85 |
+
return sukses(res, trans, 'Detail transaksi berhasil dimuat');
|
| 86 |
+
} catch (error) {
|
| 87 |
+
next(error);
|
| 88 |
+
}
|
| 89 |
+
}
|
| 90 |
+
|
| 91 |
+
static async void(req: AuthenticatedRequest, res: Response, next: NextFunction) {
|
| 92 |
+
try {
|
| 93 |
+
const transaksiId = parseInt(req.params.id);
|
| 94 |
+
const actorId = req.user.id;
|
| 95 |
+
const ip = (req.ip || req.socket.remoteAddress || 'unknown').replace('::ffff:', '');
|
| 96 |
+
const userAgent = (req.headers['user-agent'] || 'unknown') as string;
|
| 97 |
+
const alasan = req.body.alasan || 'Pembatalan transaksi oleh kasir';
|
| 98 |
+
|
| 99 |
+
const trans = await TransaksiService.prosesVoidTransaksi(transaksiId, alasan, actorId, ip, userAgent);
|
| 100 |
+
return sukses(res, trans, 'Transaksi berhasil di-void');
|
| 101 |
+
} catch (error) {
|
| 102 |
+
next(error);
|
| 103 |
+
}
|
| 104 |
+
}
|
| 105 |
+
|
| 106 |
+
static async retur(req: AuthenticatedRequest, res: Response, next: NextFunction) {
|
| 107 |
+
try {
|
| 108 |
+
const actorId = req.user.id;
|
| 109 |
+
const ip = (req.ip || req.socket.remoteAddress || 'unknown').replace('::ffff:', '');
|
| 110 |
+
const userAgent = (req.headers['user-agent'] || 'unknown') as string;
|
| 111 |
+
|
| 112 |
+
const retur = await TransaksiService.prosesReturBarang(req.body, actorId, ip, userAgent);
|
| 113 |
+
return sukses(res, retur, 'Retur barang berhasil dicatat');
|
| 114 |
+
} catch (error) {
|
| 115 |
+
next(error);
|
| 116 |
+
}
|
| 117 |
+
}
|
| 118 |
+
}
|
src/controllers/upload.controller.ts
ADDED
|
@@ -0,0 +1,41 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import { Request, Response, NextFunction } from 'express';
|
| 2 |
+
import { uploadBase64ToSupabase } from '../utils/supabase';
|
| 3 |
+
import { sukses, gagal } from '../utils/response';
|
| 4 |
+
import crypto from 'crypto';
|
| 5 |
+
|
| 6 |
+
export class UploadController {
|
| 7 |
+
static async uploadProdukFoto(req: Request, res: Response, next: NextFunction) {
|
| 8 |
+
try {
|
| 9 |
+
const { images, prefix } = req.body;
|
| 10 |
+
const cleanPrefix = (prefix || 'img').replace(/[^a-zA-Z0-9-_]/g, '').toLowerCase();
|
| 11 |
+
const uploadedUrls: string[] = [];
|
| 12 |
+
|
| 13 |
+
for (let i = 0; i < images.length; i++) {
|
| 14 |
+
const imgBase64 = images[i];
|
| 15 |
+
|
| 16 |
+
if (imgBase64.startsWith('http')) {
|
| 17 |
+
uploadedUrls.push(imgBase64);
|
| 18 |
+
continue;
|
| 19 |
+
}
|
| 20 |
+
|
| 21 |
+
// Generate unique path/filename
|
| 22 |
+
const uniqueId = crypto.randomBytes(4).toString('hex'); // 8 chars
|
| 23 |
+
const timestamp = Math.floor(Date.now() / 1000);
|
| 24 |
+
const fileName = `${cleanPrefix}_${timestamp}_${uniqueId}_${i}.jpg`;
|
| 25 |
+
const filePath = `produk/${fileName}`;
|
| 26 |
+
|
| 27 |
+
const url = await uploadBase64ToSupabase('produk', filePath, imgBase64);
|
| 28 |
+
|
| 29 |
+
if (url) {
|
| 30 |
+
uploadedUrls.push(url);
|
| 31 |
+
} else {
|
| 32 |
+
return gagal(res, 500, `Gagal mengunggah foto ke-${i + 1} ke Supabase`);
|
| 33 |
+
}
|
| 34 |
+
}
|
| 35 |
+
|
| 36 |
+
return sukses(res, { urls: uploadedUrls }, 'Foto berhasil diunggah ke Supabase');
|
| 37 |
+
} catch (error) {
|
| 38 |
+
next(error);
|
| 39 |
+
}
|
| 40 |
+
}
|
| 41 |
+
}
|
src/middlewares/auth.middleware.ts
ADDED
|
@@ -0,0 +1,56 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import { Request, Response, NextFunction } from 'express';
|
| 2 |
+
import { verifyToken } from '../utils/jwt';
|
| 3 |
+
import prisma from '../utils/db';
|
| 4 |
+
import { gagal } from '../utils/response';
|
| 5 |
+
|
| 6 |
+
export interface AuthenticatedRequest extends Request {
|
| 7 |
+
user?: any;
|
| 8 |
+
}
|
| 9 |
+
|
| 10 |
+
export const authenticate = async (
|
| 11 |
+
req: AuthenticatedRequest,
|
| 12 |
+
res: Response,
|
| 13 |
+
next: NextFunction
|
| 14 |
+
) => {
|
| 15 |
+
try {
|
| 16 |
+
const authHeader = req.headers.authorization;
|
| 17 |
+
if (!authHeader || !authHeader.startsWith('Bearer ')) {
|
| 18 |
+
return gagal(res, 401, 'Token tidak ditemukan atau tidak valid');
|
| 19 |
+
}
|
| 20 |
+
|
| 21 |
+
const token = authHeader.split(' ')[1];
|
| 22 |
+
const decoded = verifyToken(token);
|
| 23 |
+
|
| 24 |
+
if (!decoded || decoded.type !== 'access') {
|
| 25 |
+
return gagal(res, 401, 'Token tidak valid atau kedaluwarsa');
|
| 26 |
+
}
|
| 27 |
+
|
| 28 |
+
const username = decoded.sub;
|
| 29 |
+
const user = await prisma.user.findFirst({
|
| 30 |
+
where: {
|
| 31 |
+
username,
|
| 32 |
+
aktif: 1,
|
| 33 |
+
},
|
| 34 |
+
});
|
| 35 |
+
|
| 36 |
+
if (!user) {
|
| 37 |
+
return gagal(res, 401, 'Pengguna tidak aktif atau tidak ditemukan');
|
| 38 |
+
}
|
| 39 |
+
|
| 40 |
+
req.user = user;
|
| 41 |
+
next();
|
| 42 |
+
} catch (error) {
|
| 43 |
+
next(error);
|
| 44 |
+
}
|
| 45 |
+
};
|
| 46 |
+
|
| 47 |
+
export const requireAdmin = (
|
| 48 |
+
req: AuthenticatedRequest,
|
| 49 |
+
res: Response,
|
| 50 |
+
next: NextFunction
|
| 51 |
+
) => {
|
| 52 |
+
if (!req.user || req.user.role !== 'admin') {
|
| 53 |
+
return gagal(res, 403, 'Akses ditolak. Peran Admin diperlukan.');
|
| 54 |
+
}
|
| 55 |
+
next();
|
| 56 |
+
};
|
src/middlewares/error.middleware.ts
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import { Request, Response, NextFunction } from 'express';
|
| 2 |
+
import { gagal } from '../utils/response';
|
| 3 |
+
|
| 4 |
+
export const errorHandler = (
|
| 5 |
+
err: any,
|
| 6 |
+
req: Request,
|
| 7 |
+
res: Response,
|
| 8 |
+
next: NextFunction
|
| 9 |
+
) => {
|
| 10 |
+
console.error('[Error Handler]:', err);
|
| 11 |
+
|
| 12 |
+
const statusCode = err.statusCode || 500;
|
| 13 |
+
const message = err.message || 'Terjadi kesalahan pada server';
|
| 14 |
+
const detail = err.detail || err.errors || message;
|
| 15 |
+
|
| 16 |
+
return gagal(res, statusCode, message, err.stack || null, detail);
|
| 17 |
+
};
|
src/middlewares/validate.middleware.ts
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import { Request, Response, NextFunction } from 'express';
|
| 2 |
+
import { AnyZodObject, ZodError } from 'zod';
|
| 3 |
+
|
| 4 |
+
export const validate = (schema: AnyZodObject) => {
|
| 5 |
+
return async (req: Request, res: Response, next: NextFunction) => {
|
| 6 |
+
try {
|
| 7 |
+
const parsed = await schema.parseAsync({
|
| 8 |
+
body: req.body,
|
| 9 |
+
query: req.query,
|
| 10 |
+
params: req.params,
|
| 11 |
+
});
|
| 12 |
+
|
| 13 |
+
if (parsed.body !== undefined) req.body = parsed.body;
|
| 14 |
+
if (parsed.query !== undefined) req.query = parsed.query;
|
| 15 |
+
if (parsed.params !== undefined) req.params = parsed.params;
|
| 16 |
+
|
| 17 |
+
return next();
|
| 18 |
+
} catch (error) {
|
| 19 |
+
if (error instanceof ZodError) {
|
| 20 |
+
return res.status(400).json({
|
| 21 |
+
sukses: false,
|
| 22 |
+
pesan: 'Validasi input gagal',
|
| 23 |
+
errors: error.errors.map((err) => ({
|
| 24 |
+
field: err.path.slice(1).join('.'),
|
| 25 |
+
message: err.message,
|
| 26 |
+
})),
|
| 27 |
+
});
|
| 28 |
+
}
|
| 29 |
+
return next(error);
|
| 30 |
+
}
|
| 31 |
+
};
|
| 32 |
+
};
|
src/routes/auth.route.ts
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import { Router } from 'express';
|
| 2 |
+
import { AuthController } from '../controllers/auth.controller';
|
| 3 |
+
import { validate } from '../middlewares/validate.middleware';
|
| 4 |
+
import { loginSchema, refreshSchema } from '../schemas/auth.schema';
|
| 5 |
+
import { authenticate } from '../middlewares/auth.middleware';
|
| 6 |
+
|
| 7 |
+
const authRouter = Router();
|
| 8 |
+
|
| 9 |
+
authRouter.post('/login', validate(loginSchema), AuthController.login);
|
| 10 |
+
authRouter.post('/refresh', validate(refreshSchema), AuthController.refresh);
|
| 11 |
+
authRouter.post('/logout', authenticate, AuthController.logout);
|
| 12 |
+
|
| 13 |
+
export default authRouter;
|
src/routes/bon.route.ts
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import { Router } from 'express';
|
| 2 |
+
import { BonController } from '../controllers/bon.controller';
|
| 3 |
+
import { authenticate } from '../middlewares/auth.middleware';
|
| 4 |
+
import { validate } from '../middlewares/validate.middleware';
|
| 5 |
+
import { bayarBonSchema } from '../schemas/bon.schema';
|
| 6 |
+
|
| 7 |
+
const bonRouter = Router();
|
| 8 |
+
|
| 9 |
+
// Rute Publik
|
| 10 |
+
bonRouter.get('/', BonController.list);
|
| 11 |
+
bonRouter.get('/:id', BonController.get);
|
| 12 |
+
|
| 13 |
+
// Rute Terproteksi Token
|
| 14 |
+
bonRouter.post('/:id/bayar', authenticate, validate(bayarBonSchema), BonController.payInstallment);
|
| 15 |
+
bonRouter.post('/:id/reminder', authenticate, BonController.sendDebtReminder);
|
| 16 |
+
|
| 17 |
+
export default bonRouter;
|
src/routes/index.ts
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import { Router } from 'express';
|
| 2 |
+
import authRouter from './auth.route';
|
| 3 |
+
import penggunaRouter from './pengguna.route';
|
| 4 |
+
import uploadRouter from './upload.route';
|
| 5 |
+
import kategoriRouter from './kategori.route';
|
| 6 |
+
import supplierRouter from './supplier.route';
|
| 7 |
+
import memberRouter from './member.route';
|
| 8 |
+
import produkRouter from './produk.route';
|
| 9 |
+
import stokRouter from './stok.route';
|
| 10 |
+
import bonRouter from './bon.route';
|
| 11 |
+
import transaksiRouter from './transaksi.route';
|
| 12 |
+
import opsRouter from './ops.route';
|
| 13 |
+
|
| 14 |
+
const router = Router();
|
| 15 |
+
|
| 16 |
+
router.use('/auth', authRouter);
|
| 17 |
+
router.use('/users', penggunaRouter);
|
| 18 |
+
router.use('/upload', uploadRouter);
|
| 19 |
+
router.use('/kategori', kategoriRouter);
|
| 20 |
+
router.use('/supplier', supplierRouter);
|
| 21 |
+
router.use('/member', memberRouter);
|
| 22 |
+
router.use('/produk', produkRouter);
|
| 23 |
+
router.use('/stok', stokRouter);
|
| 24 |
+
router.use('/bon', bonRouter);
|
| 25 |
+
router.use('/transaksi', transaksiRouter);
|
| 26 |
+
router.use('/', opsRouter);
|
| 27 |
+
|
| 28 |
+
export default router;
|
src/routes/kategori.route.ts
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import { Router } from 'express';
|
| 2 |
+
import { KategoriController } from '../controllers/kategori.controller';
|
| 3 |
+
import { authenticate } from '../middlewares/auth.middleware';
|
| 4 |
+
import { validate } from '../middlewares/validate.middleware';
|
| 5 |
+
import { createKategoriSchema, updateKategoriSchema } from '../schemas/kategori.schema';
|
| 6 |
+
|
| 7 |
+
const kategoriRouter = Router();
|
| 8 |
+
|
| 9 |
+
// Rute Publik
|
| 10 |
+
kategoriRouter.get('/', KategoriController.list);
|
| 11 |
+
|
| 12 |
+
// Rute Terproteksi Token
|
| 13 |
+
kategoriRouter.post('/', authenticate, validate(createKategoriSchema), KategoriController.create);
|
| 14 |
+
kategoriRouter.put('/:id', authenticate, validate(updateKategoriSchema), KategoriController.update);
|
| 15 |
+
kategoriRouter.delete('/:id', authenticate, KategoriController.delete);
|
| 16 |
+
|
| 17 |
+
export default kategoriRouter;
|
src/routes/member.route.ts
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import { Router } from 'express';
|
| 2 |
+
import { MemberController } from '../controllers/member.controller';
|
| 3 |
+
import { authenticate } from '../middlewares/auth.middleware';
|
| 4 |
+
import { validate } from '../middlewares/validate.middleware';
|
| 5 |
+
import { createMemberSchema, updateMemberSchema } from '../schemas/member.schema';
|
| 6 |
+
|
| 7 |
+
const memberRouter = Router();
|
| 8 |
+
|
| 9 |
+
memberRouter.get('/', MemberController.list);
|
| 10 |
+
|
| 11 |
+
// Protected routes below
|
| 12 |
+
memberRouter.post('/', authenticate, validate(createMemberSchema), MemberController.create);
|
| 13 |
+
memberRouter.put('/:id', authenticate, validate(updateMemberSchema), MemberController.update);
|
| 14 |
+
memberRouter.delete('/:id', authenticate, MemberController.delete);
|
| 15 |
+
|
| 16 |
+
export default memberRouter;
|
src/routes/ops.route.ts
ADDED
|
@@ -0,0 +1,30 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import { Router } from 'express';
|
| 2 |
+
import { OpsController } from '../controllers/ops.controller';
|
| 3 |
+
import { authenticate } from '../middlewares/auth.middleware';
|
| 4 |
+
import { validate } from '../middlewares/validate.middleware';
|
| 5 |
+
import { bukaShiftSchema, tutupShiftSchema, updatePengaturanSchema, laporanQuerySchema } from '../schemas/ops.schema';
|
| 6 |
+
|
| 7 |
+
const opsRouter = Router();
|
| 8 |
+
|
| 9 |
+
// ================= SHIFT KASIR =================
|
| 10 |
+
opsRouter.get('/shift/aktif', authenticate, OpsController.getActiveShift);
|
| 11 |
+
opsRouter.post('/shift/buka', authenticate, validate(bukaShiftSchema), OpsController.openShift);
|
| 12 |
+
opsRouter.post('/shift/tutup', authenticate, validate(tutupShiftSchema), OpsController.closeShift);
|
| 13 |
+
|
| 14 |
+
// ================= REPORT & DASHBOARD =================
|
| 15 |
+
opsRouter.get('/dashboard/summary', authenticate, OpsController.getDashboardSummary);
|
| 16 |
+
opsRouter.get('/laporan/penjualan', authenticate, validate(laporanQuerySchema), OpsController.getSalesReport);
|
| 17 |
+
opsRouter.get('/laporan/laba', authenticate, validate(laporanQuerySchema), OpsController.getProfitLossReport);
|
| 18 |
+
|
| 19 |
+
// ================= SETTINGS =================
|
| 20 |
+
opsRouter.get('/pengaturan', OpsController.getSettings);
|
| 21 |
+
opsRouter.get('/pengaturan/:kunci', OpsController.getSettingByKey);
|
| 22 |
+
opsRouter.put('/pengaturan/:kunci', authenticate, validate(updatePengaturanSchema), OpsController.updateSetting);
|
| 23 |
+
|
| 24 |
+
// ================= NOTIFIKASI =================
|
| 25 |
+
opsRouter.get('/notifikasi', OpsController.listNotifications);
|
| 26 |
+
opsRouter.get('/notifikasi/unread-count', OpsController.getUnreadNotificationCount);
|
| 27 |
+
opsRouter.post('/notifikasi/read-all', OpsController.readAllNotifications);
|
| 28 |
+
opsRouter.post('/notifikasi/:notif_id/read', OpsController.readNotification);
|
| 29 |
+
|
| 30 |
+
export default opsRouter;
|
src/routes/pengguna.route.ts
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import { Router } from 'express';
|
| 2 |
+
import { PenggunaController } from '../controllers/pengguna.controller';
|
| 3 |
+
import { authenticate, requireAdmin } from '../middlewares/auth.middleware';
|
| 4 |
+
import { validate } from '../middlewares/validate.middleware';
|
| 5 |
+
import { createPenggunaSchema, updatePenggunaSchema } from '../schemas/pengguna.schema';
|
| 6 |
+
|
| 7 |
+
const penggunaRouter = Router();
|
| 8 |
+
|
| 9 |
+
// Proteksi Admin untuk semua rute pengguna
|
| 10 |
+
penggunaRouter.use(authenticate);
|
| 11 |
+
penggunaRouter.use(requireAdmin);
|
| 12 |
+
|
| 13 |
+
penggunaRouter.get('/', PenggunaController.list);
|
| 14 |
+
penggunaRouter.get('/:id', PenggunaController.get);
|
| 15 |
+
penggunaRouter.post('/', validate(createPenggunaSchema), PenggunaController.create);
|
| 16 |
+
penggunaRouter.put('/:id', validate(updatePenggunaSchema), PenggunaController.update);
|
| 17 |
+
penggunaRouter.delete('/:id', PenggunaController.delete);
|
| 18 |
+
|
| 19 |
+
export default penggunaRouter;
|
src/routes/produk.route.ts
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import { Router } from 'express';
|
| 2 |
+
import { ProdukController } from '../controllers/produk.controller';
|
| 3 |
+
import { authenticate } from '../middlewares/auth.middleware';
|
| 4 |
+
import { validate } from '../middlewares/validate.middleware';
|
| 5 |
+
import { createProdukSchema, updateProdukSchema } from '../schemas/produk.schema';
|
| 6 |
+
|
| 7 |
+
const produkRouter = Router();
|
| 8 |
+
|
| 9 |
+
// Rute Publik
|
| 10 |
+
produkRouter.get('/', ProdukController.list);
|
| 11 |
+
produkRouter.get('/:id', ProdukController.get);
|
| 12 |
+
produkRouter.get('/scan/:barcode_or_kode', ProdukController.scan);
|
| 13 |
+
|
| 14 |
+
// Rute Terproteksi Token
|
| 15 |
+
produkRouter.post('/', authenticate, validate(createProdukSchema), ProdukController.create);
|
| 16 |
+
produkRouter.put('/:id', authenticate, validate(updateProdukSchema), ProdukController.update);
|
| 17 |
+
produkRouter.delete('/:id', authenticate, ProdukController.delete);
|
| 18 |
+
|
| 19 |
+
export default produkRouter;
|
src/routes/stok.route.ts
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import { Router } from 'express';
|
| 2 |
+
import { StokController } from '../controllers/stok.controller';
|
| 3 |
+
import { authenticate } from '../middlewares/auth.middleware';
|
| 4 |
+
import { validate } from '../middlewares/validate.middleware';
|
| 5 |
+
import { barangMasukSchema, stokOpnameSchema } from '../schemas/stok.schema';
|
| 6 |
+
|
| 7 |
+
const stokRouter = Router();
|
| 8 |
+
|
| 9 |
+
// Rute Publik
|
| 10 |
+
stokRouter.get('/mutasi', StokController.getStockMutations);
|
| 11 |
+
stokRouter.get('/batches', StokController.getStockBatches);
|
| 12 |
+
|
| 13 |
+
// Rute Terproteksi Token
|
| 14 |
+
stokRouter.post('/masuk', authenticate, validate(barangMasukSchema), StokController.addIncomingStock);
|
| 15 |
+
stokRouter.post('/opname', authenticate, validate(stokOpnameSchema), StokController.createStockOpname);
|
| 16 |
+
stokRouter.post('/opname/:opname_id/approve', authenticate, StokController.approveStockOpname);
|
| 17 |
+
|
| 18 |
+
export default stokRouter;
|
src/routes/supplier.route.ts
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import { Router } from 'express';
|
| 2 |
+
import { SupplierController } from '../controllers/supplier.controller';
|
| 3 |
+
import { authenticate } from '../middlewares/auth.middleware';
|
| 4 |
+
import { validate } from '../middlewares/validate.middleware';
|
| 5 |
+
import { createSupplierSchema, updateSupplierSchema } from '../schemas/supplier.schema';
|
| 6 |
+
|
| 7 |
+
const supplierRouter = Router();
|
| 8 |
+
|
| 9 |
+
supplierRouter.get('/', SupplierController.list);
|
| 10 |
+
|
| 11 |
+
// Protected routes below
|
| 12 |
+
supplierRouter.post('/', authenticate, validate(createSupplierSchema), SupplierController.create);
|
| 13 |
+
supplierRouter.put('/:id', authenticate, validate(updateSupplierSchema), SupplierController.update);
|
| 14 |
+
supplierRouter.delete('/:id', authenticate, SupplierController.delete);
|
| 15 |
+
|
| 16 |
+
export default supplierRouter;
|
src/routes/transaksi.route.ts
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import { Router } from 'express';
|
| 2 |
+
import { TransaksiController } from '../controllers/transaksi.controller';
|
| 3 |
+
import { authenticate } from '../middlewares/auth.middleware';
|
| 4 |
+
import { validate } from '../middlewares/validate.middleware';
|
| 5 |
+
import { createTransaksiSchema, createTransaksiReturSchema } from '../schemas/transaksi.schema';
|
| 6 |
+
|
| 7 |
+
const transaksiRouter = Router();
|
| 8 |
+
|
| 9 |
+
// Rute Publik
|
| 10 |
+
transaksiRouter.get('/', TransaksiController.list);
|
| 11 |
+
transaksiRouter.get('/:id', TransaksiController.get);
|
| 12 |
+
|
| 13 |
+
// Rute Terproteksi Token
|
| 14 |
+
transaksiRouter.post('/', authenticate, validate(createTransaksiSchema), TransaksiController.create);
|
| 15 |
+
transaksiRouter.post('/:id/void', authenticate, TransaksiController.void);
|
| 16 |
+
transaksiRouter.post('/retur', authenticate, validate(createTransaksiReturSchema), TransaksiController.retur);
|
| 17 |
+
|
| 18 |
+
export default transaksiRouter;
|
src/routes/upload.route.ts
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import { Router } from 'express';
|
| 2 |
+
import { UploadController } from '../controllers/upload.controller';
|
| 3 |
+
import { authenticate } from '../middlewares/auth.middleware';
|
| 4 |
+
import { validate } from '../middlewares/validate.middleware';
|
| 5 |
+
import { uploadSchema } from '../schemas/upload.schema';
|
| 6 |
+
|
| 7 |
+
const uploadRouter = Router();
|
| 8 |
+
|
| 9 |
+
// Proteksi token untuk semua rute upload
|
| 10 |
+
uploadRouter.use(authenticate);
|
| 11 |
+
|
| 12 |
+
uploadRouter.post('/produk', validate(uploadSchema), UploadController.uploadProdukFoto);
|
| 13 |
+
|
| 14 |
+
export default uploadRouter;
|
src/schemas/auth.schema.ts
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import { z } from 'zod';
|
| 2 |
+
|
| 3 |
+
export const loginSchema = z.object({
|
| 4 |
+
body: z.object({
|
| 5 |
+
username: z.string({ required_error: 'Username wajib diisi' }).min(1, 'Username tidak boleh kosong'),
|
| 6 |
+
password: z.string({ required_error: 'Password wajib diisi' }).min(1, 'Password tidak boleh kosong'),
|
| 7 |
+
}),
|
| 8 |
+
});
|
| 9 |
+
|
| 10 |
+
export const refreshSchema = z.object({
|
| 11 |
+
body: z.object({
|
| 12 |
+
refresh_token: z.string({ required_error: 'Refresh token wajib diisi' }).min(1, 'Refresh token tidak boleh kosong'),
|
| 13 |
+
}),
|
| 14 |
+
});
|
src/schemas/bon.schema.ts
ADDED
|
@@ -0,0 +1,8 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import { z } from 'zod';
|
| 2 |
+
|
| 3 |
+
export const bayarBonSchema = z.object({
|
| 4 |
+
body: z.object({
|
| 5 |
+
nominal: z.number({ required_error: 'Nominal pembayaran wajib diisi' }).positive('Nominal harus lebih besar dari 0'),
|
| 6 |
+
metode: z.enum(['tunai', 'transfer'], { required_error: 'Metode cicilan wajib diisi (tunai/transfer)' }),
|
| 7 |
+
}),
|
| 8 |
+
});
|
src/schemas/kategori.schema.ts
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import { z } from 'zod';
|
| 2 |
+
|
| 3 |
+
const booleanOrNumber = z.union([z.boolean(), z.number()])
|
| 4 |
+
.transform((val) => (typeof val === 'boolean' ? (val ? 1 : 0) : val));
|
| 5 |
+
|
| 6 |
+
export const createKategoriSchema = z.object({
|
| 7 |
+
body: z.object({
|
| 8 |
+
nama: z.string({ required_error: 'Nama kategori wajib diisi' }).min(1, 'Nama kategori tidak boleh kosong'),
|
| 9 |
+
icon: z.string().optional().nullable(),
|
| 10 |
+
aktif: booleanOrNumber.optional().default(1),
|
| 11 |
+
}),
|
| 12 |
+
});
|
| 13 |
+
|
| 14 |
+
export const updateKategoriSchema = z.object({
|
| 15 |
+
body: z.object({
|
| 16 |
+
nama: z.string().optional(),
|
| 17 |
+
icon: z.string().optional().nullable(),
|
| 18 |
+
aktif: booleanOrNumber.optional(),
|
| 19 |
+
}),
|
| 20 |
+
});
|
src/schemas/member.schema.ts
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import { z } from 'zod';
|
| 2 |
+
|
| 3 |
+
const booleanOrNumber = z.union([z.boolean(), z.number()])
|
| 4 |
+
.transform((val) => (typeof val === 'boolean' ? (val ? 1 : 0) : val));
|
| 5 |
+
|
| 6 |
+
export const createMemberSchema = z.object({
|
| 7 |
+
body: z.object({
|
| 8 |
+
kode: z.string({ required_error: 'Kode member wajib diisi' }).min(1, 'Kode member tidak boleh kosong'),
|
| 9 |
+
nama: z.string({ required_error: 'Nama member wajib diisi' }).min(1, 'Nama member tidak boleh kosong'),
|
| 10 |
+
nomor_hp: z.string({ required_error: 'Nomor HP wajib diisi' }).min(1, 'Nomor HP tidak boleh kosong'),
|
| 11 |
+
alamat: z.string().optional().nullable(),
|
| 12 |
+
limit_bon: z.number().optional().default(0),
|
| 13 |
+
foto_url: z.string().optional().nullable(),
|
| 14 |
+
aktif: booleanOrNumber.optional().default(1),
|
| 15 |
+
}),
|
| 16 |
+
});
|
| 17 |
+
|
| 18 |
+
export const updateMemberSchema = z.object({
|
| 19 |
+
body: z.object({
|
| 20 |
+
nama: z.string().optional(),
|
| 21 |
+
nomor_hp: z.string().optional(),
|
| 22 |
+
alamat: z.string().optional().nullable(),
|
| 23 |
+
total_poin: z.number().optional(),
|
| 24 |
+
limit_bon: z.number().optional(),
|
| 25 |
+
total_bon: z.number().optional(),
|
| 26 |
+
foto_url: z.string().optional().nullable(),
|
| 27 |
+
aktif: booleanOrNumber.optional(),
|
| 28 |
+
}),
|
| 29 |
+
});
|
src/schemas/ops.schema.ts
ADDED
|
@@ -0,0 +1,33 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import { z } from 'zod';
|
| 2 |
+
|
| 3 |
+
export const bukaShiftSchema = z.object({
|
| 4 |
+
body: z.object({
|
| 5 |
+
modal_awal: z.number({ required_error: 'Modal awal wajib diisi' }).nonnegative('Modal awal tidak boleh negatif'),
|
| 6 |
+
catatan: z.string().optional().nullable(),
|
| 7 |
+
}),
|
| 8 |
+
});
|
| 9 |
+
|
| 10 |
+
export const tutupShiftSchema = z.object({
|
| 11 |
+
body: z.object({
|
| 12 |
+
modal_akhir_fisik: z.number({ required_error: 'Modal akhir fisik wajib diisi' }).nonnegative('Modal akhir fisik tidak boleh negatif'),
|
| 13 |
+
catatan: z.string().optional().nullable(),
|
| 14 |
+
}),
|
| 15 |
+
});
|
| 16 |
+
|
| 17 |
+
export const updatePengaturanSchema = z.object({
|
| 18 |
+
body: z.object({
|
| 19 |
+
nilai: z.string({ required_error: 'Nilai pengaturan wajib diisi' }),
|
| 20 |
+
deskripsi: z.string().optional().nullable(),
|
| 21 |
+
}),
|
| 22 |
+
});
|
| 23 |
+
|
| 24 |
+
export const laporanQuerySchema = z.object({
|
| 25 |
+
query: z.object({
|
| 26 |
+
tgl_mulai: z.string({ required_error: 'tgl_mulai wajib diisi' }).refine((val) => !isNaN(Date.parse(val)), {
|
| 27 |
+
message: 'Format tgl_mulai tidak valid (harus YYYY-MM-DD)',
|
| 28 |
+
}),
|
| 29 |
+
tgl_selesai: z.string({ required_error: 'tgl_selesai wajib diisi' }).refine((val) => !isNaN(Date.parse(val)), {
|
| 30 |
+
message: 'Format tgl_selesai tidak valid (harus YYYY-MM-DD)',
|
| 31 |
+
}),
|
| 32 |
+
}),
|
| 33 |
+
});
|
src/schemas/pengguna.schema.ts
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import { z } from 'zod';
|
| 2 |
+
|
| 3 |
+
const booleanOrNumber = z.union([z.boolean(), z.number()])
|
| 4 |
+
.transform((val) => (typeof val === 'boolean' ? (val ? 1 : 0) : val));
|
| 5 |
+
|
| 6 |
+
export const createPenggunaSchema = z.object({
|
| 7 |
+
body: z.object({
|
| 8 |
+
nama: z.string({ required_error: 'Nama wajib diisi' }).min(1, 'Nama tidak boleh kosong'),
|
| 9 |
+
username: z.string({ required_error: 'Username wajib diisi' }).min(1, 'Username tidak boleh kosong'),
|
| 10 |
+
password: z.string({ required_error: 'Password wajib diisi' }).min(6, 'Password minimal 6 karakter'),
|
| 11 |
+
role: z.enum(['admin', 'kasir'], { required_error: 'Role wajib diisi (admin/kasir)' }),
|
| 12 |
+
nomor_hp: z.string().optional().nullable(),
|
| 13 |
+
foto_url: z.string().optional().nullable(),
|
| 14 |
+
aktif: booleanOrNumber.default(1),
|
| 15 |
+
}),
|
| 16 |
+
});
|
| 17 |
+
|
| 18 |
+
export const updatePenggunaSchema = z.object({
|
| 19 |
+
body: z.object({
|
| 20 |
+
nama: z.string().optional(),
|
| 21 |
+
username: z.string().optional(),
|
| 22 |
+
password: z.string().min(6, 'Password minimal 6 karakter').optional(),
|
| 23 |
+
role: z.enum(['admin', 'kasir']).optional(),
|
| 24 |
+
nomor_hp: z.string().optional().nullable(),
|
| 25 |
+
foto_url: z.string().optional().nullable(),
|
| 26 |
+
aktif: booleanOrNumber.optional(),
|
| 27 |
+
}),
|
| 28 |
+
});
|
src/schemas/produk.schema.ts
ADDED
|
@@ -0,0 +1,55 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import { z } from 'zod';
|
| 2 |
+
|
| 3 |
+
const booleanOrNumber = z.union([z.boolean(), z.number()])
|
| 4 |
+
.transform((val) => (typeof val === 'boolean' ? (val ? 1 : 0) : val));
|
| 5 |
+
|
| 6 |
+
export const createProdukSchema = z.object({
|
| 7 |
+
body: z.object({
|
| 8 |
+
kode: z.string({ required_error: 'Kode produk wajib diisi' }).min(1, 'Kode produk tidak boleh kosong'),
|
| 9 |
+
barcode: z.string().optional().nullable(),
|
| 10 |
+
nama: z.string({ required_error: 'Nama produk wajib diisi' }).min(1, 'Nama produk tidak boleh kosong'),
|
| 11 |
+
kategori_id: z.number({ required_error: 'Kategori ID wajib diisi' }),
|
| 12 |
+
supplier_id: z.number().optional().nullable(),
|
| 13 |
+
satuan: z.string().optional().default('pcs'),
|
| 14 |
+
harga_beli: z.number({ required_error: 'Harga beli wajib diisi' }).nonnegative(),
|
| 15 |
+
harga_jual: z.number({ required_error: 'Harga jual wajib diisi' }).nonnegative(),
|
| 16 |
+
stok: z.number().optional().default(0),
|
| 17 |
+
stok_min: z.number().optional().default(5),
|
| 18 |
+
expired_date: z.string().optional().nullable().transform((val) => (val ? new Date(val) : null)),
|
| 19 |
+
foto_url: z.string().optional().nullable(),
|
| 20 |
+
qr_url: z.string().optional().nullable(),
|
| 21 |
+
deskripsi: z.string().optional().nullable(),
|
| 22 |
+
is_ecer: booleanOrNumber.optional().default(1),
|
| 23 |
+
is_aktif: booleanOrNumber.optional().default(1),
|
| 24 |
+
harga_tingkat: z.array(z.object({
|
| 25 |
+
tingkat: z.enum(['ecer', 'grosir', 'partai']),
|
| 26 |
+
min_qty: z.number().positive(),
|
| 27 |
+
harga: z.number().nonnegative(),
|
| 28 |
+
})).optional(),
|
| 29 |
+
}),
|
| 30 |
+
});
|
| 31 |
+
|
| 32 |
+
export const updateProdukSchema = z.object({
|
| 33 |
+
body: z.object({
|
| 34 |
+
kode: z.string().optional(),
|
| 35 |
+
barcode: z.string().optional().nullable(),
|
| 36 |
+
nama: z.string().optional(),
|
| 37 |
+
kategori_id: z.number().optional(),
|
| 38 |
+
supplier_id: z.number().optional().nullable(),
|
| 39 |
+
satuan: z.string().optional(),
|
| 40 |
+
harga_beli: z.number().nonnegative().optional(),
|
| 41 |
+
harga_jual: z.number().nonnegative().optional(),
|
| 42 |
+
stok_min: z.number().optional(),
|
| 43 |
+
expired_date: z.string().optional().nullable().transform((val) => (val ? new Date(val) : null)),
|
| 44 |
+
foto_url: z.string().optional().nullable(),
|
| 45 |
+
qr_url: z.string().optional().nullable(),
|
| 46 |
+
deskripsi: z.string().optional().nullable(),
|
| 47 |
+
is_ecer: booleanOrNumber.optional(),
|
| 48 |
+
is_aktif: booleanOrNumber.optional(),
|
| 49 |
+
harga_tingkat: z.array(z.object({
|
| 50 |
+
tingkat: z.enum(['ecer', 'grosir', 'partai']),
|
| 51 |
+
min_qty: z.number().positive(),
|
| 52 |
+
harga: z.number().nonnegative(),
|
| 53 |
+
})).optional().nullable(),
|
| 54 |
+
}),
|
| 55 |
+
});
|
src/schemas/stok.schema.ts
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import { z } from 'zod';
|
| 2 |
+
|
| 3 |
+
export const barangMasukSchema = z.object({
|
| 4 |
+
body: z.object({
|
| 5 |
+
produk_id: z.number({ required_error: 'produk_id wajib diisi' }),
|
| 6 |
+
qty: z.number({ required_error: 'Kuantitas (qty) wajib diisi' }).positive(),
|
| 7 |
+
harga_beli: z.number({ required_error: 'Harga beli wajib diisi' }).positive(),
|
| 8 |
+
batch_no: z.string().optional().nullable(),
|
| 9 |
+
expired_date: z.string().optional().nullable().transform((val) => (val ? new Date(val) : null)),
|
| 10 |
+
supplier_id: z.number().optional().nullable(),
|
| 11 |
+
catatan: z.string().optional().nullable(),
|
| 12 |
+
}),
|
| 13 |
+
});
|
| 14 |
+
|
| 15 |
+
export const stokOpnameSchema = z.object({
|
| 16 |
+
body: z.object({
|
| 17 |
+
produk_id: z.number({ required_error: 'produk_id wajib diisi' }),
|
| 18 |
+
stok_fisik: z.number({ required_error: 'stok_fisik wajib diisi' }).nonnegative(),
|
| 19 |
+
alasan: z.string().optional().nullable(),
|
| 20 |
+
}),
|
| 21 |
+
});
|
src/schemas/supplier.schema.ts
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import { z } from 'zod';
|
| 2 |
+
|
| 3 |
+
const booleanOrNumber = z.union([z.boolean(), z.number()])
|
| 4 |
+
.transform((val) => (typeof val === 'boolean' ? (val ? 1 : 0) : val));
|
| 5 |
+
|
| 6 |
+
export const createSupplierSchema = z.object({
|
| 7 |
+
body: z.object({
|
| 8 |
+
nama: z.string({ required_error: 'Nama supplier wajib diisi' }).min(1, 'Nama supplier tidak boleh kosong'),
|
| 9 |
+
kontak: z.string().optional().nullable(),
|
| 10 |
+
nomor_hp: z.string().optional().nullable(),
|
| 11 |
+
alamat: z.string().optional().nullable(),
|
| 12 |
+
hutang: z.number().optional().default(0),
|
| 13 |
+
aktif: booleanOrNumber.optional().default(1),
|
| 14 |
+
}),
|
| 15 |
+
});
|
| 16 |
+
|
| 17 |
+
export const updateSupplierSchema = z.object({
|
| 18 |
+
body: z.object({
|
| 19 |
+
nama: z.string().optional(),
|
| 20 |
+
kontak: z.string().optional().nullable(),
|
| 21 |
+
nomor_hp: z.string().optional().nullable(),
|
| 22 |
+
alamat: z.string().optional().nullable(),
|
| 23 |
+
hutang: z.number().optional(),
|
| 24 |
+
aktif: booleanOrNumber.optional(),
|
| 25 |
+
}),
|
| 26 |
+
});
|
src/schemas/transaksi.schema.ts
ADDED
|
@@ -0,0 +1,30 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import { z } from 'zod';
|
| 2 |
+
|
| 3 |
+
export const createTransaksiSchema = z.object({
|
| 4 |
+
body: z.object({
|
| 5 |
+
member_id: z.number().optional().nullable(),
|
| 6 |
+
local_id: z.string().optional().nullable(),
|
| 7 |
+
catatan: z.string().optional().nullable(),
|
| 8 |
+
items: z.array(z.object({
|
| 9 |
+
produk_id: z.number({ required_error: 'produk_id item wajib diisi' }),
|
| 10 |
+
qty: z.number({ required_error: 'qty item wajib diisi' }).positive(),
|
| 11 |
+
})).min(1, 'Transaksi minimal harus memiliki 1 item'),
|
| 12 |
+
pembayaran: z.array(z.object({
|
| 13 |
+
metode: z.enum(['tunai', 'qris', 'transfer', 'bon', 'voucher'], { required_error: 'metode pembayaran wajib diisi' }),
|
| 14 |
+
nominal: z.number({ required_error: 'nominal pembayaran wajib diisi' }).nonnegative(),
|
| 15 |
+
referensi: z.string().optional().nullable(),
|
| 16 |
+
})).min(1, 'Transaksi minimal harus memiliki 1 metode pembayaran'),
|
| 17 |
+
}),
|
| 18 |
+
});
|
| 19 |
+
|
| 20 |
+
export const createTransaksiReturSchema = z.object({
|
| 21 |
+
body: z.object({
|
| 22 |
+
transaksi_id: z.number({ required_error: 'transaksi_id wajib diisi' }),
|
| 23 |
+
produk_id: z.number({ required_error: 'produk_id wajib diisi' }),
|
| 24 |
+
qty: z.number({ required_error: 'Kuantitas retur (qty) wajib diisi' }).positive(),
|
| 25 |
+
alasan: z.string({ required_error: 'Alasan retur wajib diisi' }).min(1),
|
| 26 |
+
tipe: z.enum(['refund', 'exchange'], { required_error: 'Tipe retur wajib diisi (refund/exchange)' }),
|
| 27 |
+
nominal_kembali: z.number().optional().default(0),
|
| 28 |
+
stok_kembali: z.number().optional().default(1),
|
| 29 |
+
}),
|
| 30 |
+
});
|
src/schemas/upload.schema.ts
ADDED
|
@@ -0,0 +1,8 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import { z } from 'zod';
|
| 2 |
+
|
| 3 |
+
export const uploadSchema = z.object({
|
| 4 |
+
body: z.object({
|
| 5 |
+
images: z.array(z.string({ required_error: 'Images array is required' })),
|
| 6 |
+
prefix: z.string().optional().default('img'),
|
| 7 |
+
}),
|
| 8 |
+
});
|
src/server.ts
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import app from './app';
|
| 2 |
+
import dotenv from 'dotenv';
|
| 3 |
+
|
| 4 |
+
dotenv.config();
|
| 5 |
+
|
| 6 |
+
const PORT = process.env.PORT || 5000;
|
| 7 |
+
|
| 8 |
+
app.listen(PORT, () => {
|
| 9 |
+
console.log(`[Server]: Server sedang berjalan di http://localhost:${PORT}`);
|
| 10 |
+
});
|
src/services/auth.service.ts
ADDED
|
@@ -0,0 +1,115 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import prisma from '../utils/db';
|
| 2 |
+
import bcrypt from 'bcryptjs';
|
| 3 |
+
import { createAccessToken, createRefreshToken, verifyToken } from '../utils/jwt';
|
| 4 |
+
import { catatAudit } from '../utils/audit';
|
| 5 |
+
import { APIError } from '../utils/errors';
|
| 6 |
+
|
| 7 |
+
export class AuthService {
|
| 8 |
+
static async login(params: {
|
| 9 |
+
username: string;
|
| 10 |
+
passwordSecret: string;
|
| 11 |
+
ip: string;
|
| 12 |
+
userAgent: string;
|
| 13 |
+
}) {
|
| 14 |
+
const { username, passwordSecret, ip, userAgent } = params;
|
| 15 |
+
|
| 16 |
+
// Search for user
|
| 17 |
+
const user = await prisma.user.findFirst({
|
| 18 |
+
where: {
|
| 19 |
+
username,
|
| 20 |
+
aktif: 1,
|
| 21 |
+
},
|
| 22 |
+
});
|
| 23 |
+
|
| 24 |
+
if (!user) {
|
| 25 |
+
// Log failed audit
|
| 26 |
+
await catatAudit({
|
| 27 |
+
aksi: 'login_gagal',
|
| 28 |
+
entitas: 'user',
|
| 29 |
+
ipAddress: ip,
|
| 30 |
+
userAgent: userAgent,
|
| 31 |
+
dataBaru: { username },
|
| 32 |
+
});
|
| 33 |
+
throw new APIError('Username atau password salah', 401);
|
| 34 |
+
}
|
| 35 |
+
|
| 36 |
+
const isMatch = await bcrypt.compare(passwordSecret, user.password_hash);
|
| 37 |
+
if (!isMatch) {
|
| 38 |
+
// Log failed audit
|
| 39 |
+
await catatAudit({
|
| 40 |
+
userId: user.id,
|
| 41 |
+
aksi: 'login_gagal',
|
| 42 |
+
entitas: 'user',
|
| 43 |
+
entitasId: user.id,
|
| 44 |
+
ipAddress: ip,
|
| 45 |
+
userAgent: userAgent,
|
| 46 |
+
dataBaru: { username },
|
| 47 |
+
});
|
| 48 |
+
throw new APIError('Username atau password salah', 401);
|
| 49 |
+
}
|
| 50 |
+
|
| 51 |
+
// Log successful login
|
| 52 |
+
await catatAudit({
|
| 53 |
+
userId: user.id,
|
| 54 |
+
aksi: 'login_sukses',
|
| 55 |
+
entitas: 'user',
|
| 56 |
+
entitasId: user.id,
|
| 57 |
+
ipAddress: ip,
|
| 58 |
+
userAgent: userAgent,
|
| 59 |
+
});
|
| 60 |
+
|
| 61 |
+
// Generate tokens
|
| 62 |
+
const userData = { sub: user.username, role: user.role };
|
| 63 |
+
const accessToken = createAccessToken(userData);
|
| 64 |
+
const refreshToken = createRefreshToken(userData);
|
| 65 |
+
|
| 66 |
+
return {
|
| 67 |
+
user,
|
| 68 |
+
accessToken,
|
| 69 |
+
refreshToken,
|
| 70 |
+
};
|
| 71 |
+
}
|
| 72 |
+
|
| 73 |
+
static async refresh(refreshToken: string) {
|
| 74 |
+
const payload = verifyToken(refreshToken);
|
| 75 |
+
if (!payload || payload.type !== 'refresh') {
|
| 76 |
+
throw new APIError('Refresh token tidak valid atau kedaluwarsa', 401);
|
| 77 |
+
}
|
| 78 |
+
|
| 79 |
+
const username = payload.sub;
|
| 80 |
+
const role = payload.role;
|
| 81 |
+
|
| 82 |
+
const user = await prisma.user.findFirst({
|
| 83 |
+
where: {
|
| 84 |
+
username,
|
| 85 |
+
aktif: 1,
|
| 86 |
+
},
|
| 87 |
+
});
|
| 88 |
+
|
| 89 |
+
if (!user) {
|
| 90 |
+
throw new APIError('Pengguna tidak aktif atau tidak ditemukan', 401);
|
| 91 |
+
}
|
| 92 |
+
|
| 93 |
+
const userData = { sub: username, role };
|
| 94 |
+
const newAccessToken = createAccessToken(userData);
|
| 95 |
+
const newRefreshToken = createRefreshToken(userData);
|
| 96 |
+
|
| 97 |
+
return {
|
| 98 |
+
role: user.role,
|
| 99 |
+
accessToken: newAccessToken,
|
| 100 |
+
refreshToken: newRefreshToken,
|
| 101 |
+
};
|
| 102 |
+
}
|
| 103 |
+
|
| 104 |
+
static async logout(userId: number, ip: string, userAgent: string) {
|
| 105 |
+
await catatAudit({
|
| 106 |
+
userId,
|
| 107 |
+
aksi: 'logout',
|
| 108 |
+
entitas: 'user',
|
| 109 |
+
entitasId: userId,
|
| 110 |
+
ipAddress: ip,
|
| 111 |
+
userAgent,
|
| 112 |
+
});
|
| 113 |
+
return true;
|
| 114 |
+
}
|
| 115 |
+
}
|
src/services/bon.service.ts
ADDED
|
@@ -0,0 +1,188 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import prisma from '../utils/db';
|
| 2 |
+
import { catatAudit } from '../utils/audit';
|
| 3 |
+
import { APIError } from '../utils/errors';
|
| 4 |
+
|
| 5 |
+
export class BonService {
|
| 6 |
+
static async getBon(skip = 0, limit = 10, statusFilter?: 'aktif' | 'lunas' | 'macet', memberId?: number) {
|
| 7 |
+
const where: any = {};
|
| 8 |
+
if (statusFilter) where.status = statusFilter;
|
| 9 |
+
if (memberId) where.member_id = memberId;
|
| 10 |
+
|
| 11 |
+
return await prisma.bon.findMany({
|
| 12 |
+
where,
|
| 13 |
+
orderBy: { created_at: 'desc' },
|
| 14 |
+
skip,
|
| 15 |
+
take: limit,
|
| 16 |
+
include: {
|
| 17 |
+
member: true,
|
| 18 |
+
transaksi: true,
|
| 19 |
+
},
|
| 20 |
+
});
|
| 21 |
+
}
|
| 22 |
+
|
| 23 |
+
static async getBonById(id: number) {
|
| 24 |
+
const bon = await prisma.bon.findUnique({
|
| 25 |
+
where: { id },
|
| 26 |
+
include: {
|
| 27 |
+
member: true,
|
| 28 |
+
transaksi: true,
|
| 29 |
+
bon_cicilan: true,
|
| 30 |
+
},
|
| 31 |
+
});
|
| 32 |
+
if (!bon) {
|
| 33 |
+
throw new APIError('Pencatatan kasbon tidak ditemukan', 404);
|
| 34 |
+
}
|
| 35 |
+
return bon;
|
| 36 |
+
}
|
| 37 |
+
|
| 38 |
+
static async bayarCicilan(params: {
|
| 39 |
+
bonId: number;
|
| 40 |
+
nominal: number;
|
| 41 |
+
metode: 'tunai' | 'transfer';
|
| 42 |
+
actorId: number;
|
| 43 |
+
ip: string;
|
| 44 |
+
userAgent: string;
|
| 45 |
+
}) {
|
| 46 |
+
const { bonId, nominal, metode, actorId, ip, userAgent } = params;
|
| 47 |
+
|
| 48 |
+
const bon = await this.getBonById(bonId);
|
| 49 |
+
if (bon.status === 'lunas') {
|
| 50 |
+
throw new APIError('Kasbon ini sudah lunas didebet sebelumnya', 400);
|
| 51 |
+
}
|
| 52 |
+
|
| 53 |
+
const sisaBon = Number(bon.sisa_bon);
|
| 54 |
+
if (nominal > sisaBon) {
|
| 55 |
+
throw new APIError(`Nominal pembayaran Rp ${nominal.toLocaleString('id-ID')} melebihi sisa tagihan Rp ${sisaBon.toLocaleString('id-ID')}`, 400);
|
| 56 |
+
}
|
| 57 |
+
|
| 58 |
+
// Pastikan kasir memiliki shift aktif
|
| 59 |
+
const shift = await prisma.shift.findFirst({
|
| 60 |
+
where: {
|
| 61 |
+
user_id: actorId,
|
| 62 |
+
status: 'buka',
|
| 63 |
+
},
|
| 64 |
+
});
|
| 65 |
+
|
| 66 |
+
if (!shift) {
|
| 67 |
+
throw new APIError('Shift kasir Anda belum dibuka. Harus membuka shift terlebih dahulu untuk menerima pembayaran.', 400);
|
| 68 |
+
}
|
| 69 |
+
|
| 70 |
+
// 1. Buat record cicilan
|
| 71 |
+
await prisma.bon_cicilan.create({
|
| 72 |
+
data: {
|
| 73 |
+
bon_id: bon.id,
|
| 74 |
+
nominal: nominal,
|
| 75 |
+
metode: metode,
|
| 76 |
+
user_id: actorId,
|
| 77 |
+
},
|
| 78 |
+
});
|
| 79 |
+
|
| 80 |
+
// 2. Kurangi sisa bon
|
| 81 |
+
const sisaLama = sisaBon;
|
| 82 |
+
const sisaBaru = sisaLama - nominal;
|
| 83 |
+
const statusBaru = sisaBaru <= 0 ? 'lunas' : bon.status;
|
| 84 |
+
|
| 85 |
+
await prisma.bon.update({
|
| 86 |
+
where: { id: bonId },
|
| 87 |
+
data: {
|
| 88 |
+
sisa_bon: sisaBaru,
|
| 89 |
+
status: statusBaru as any,
|
| 90 |
+
},
|
| 91 |
+
});
|
| 92 |
+
|
| 93 |
+
// 3. Kurangi total bon member
|
| 94 |
+
const totalBonMemberLama = Number(bon.member.total_bon);
|
| 95 |
+
const totalBonMemberBaru = Math.max(0, totalBonMemberLama - nominal);
|
| 96 |
+
|
| 97 |
+
await prisma.member.update({
|
| 98 |
+
where: { id: bon.member_id },
|
| 99 |
+
data: { total_bon: totalBonMemberBaru },
|
| 100 |
+
});
|
| 101 |
+
|
| 102 |
+
// 4. Masukkan ke kas shift
|
| 103 |
+
const updateShift: any = {};
|
| 104 |
+
if (metode === 'tunai') {
|
| 105 |
+
updateShift.total_tunai = Number(shift.total_tunai) + nominal;
|
| 106 |
+
} else if (metode === 'transfer') {
|
| 107 |
+
updateShift.total_transfer = Number(shift.total_transfer) + nominal;
|
| 108 |
+
}
|
| 109 |
+
|
| 110 |
+
await prisma.shift.update({
|
| 111 |
+
where: { id: shift.id },
|
| 112 |
+
data: updateShift,
|
| 113 |
+
});
|
| 114 |
+
|
| 115 |
+
// 5. Audit log
|
| 116 |
+
await catatAudit({
|
| 117 |
+
userId: actorId,
|
| 118 |
+
aksi: 'update',
|
| 119 |
+
entitas: 'bon',
|
| 120 |
+
entitasId: bon.id,
|
| 121 |
+
dataLama: { sisa_bon: sisaLama, status: bon.status },
|
| 122 |
+
dataBaru: { sisa_bon: sisaBaru, status: statusBaru, pembayaran_cicilan: nominal },
|
| 123 |
+
ipAddress: ip,
|
| 124 |
+
userAgent,
|
| 125 |
+
});
|
| 126 |
+
|
| 127 |
+
return await this.getBonById(bonId);
|
| 128 |
+
}
|
| 129 |
+
|
| 130 |
+
static async kirimReminderWA(bonId: number) {
|
| 131 |
+
const bon = await prisma.bon.findUnique({
|
| 132 |
+
where: { id: bonId },
|
| 133 |
+
include: { member: true },
|
| 134 |
+
});
|
| 135 |
+
|
| 136 |
+
if (!bon || !bon.member) {
|
| 137 |
+
throw new APIError('Gagal membuat pengingat WhatsApp. Data member tidak ditemukan.', 400);
|
| 138 |
+
}
|
| 139 |
+
|
| 140 |
+
const { nama, nomor_hp } = bon.member;
|
| 141 |
+
if (!nomor_hp) {
|
| 142 |
+
throw new APIError('Gagal membuat pengingat WhatsApp. Nomor HP member tidak terisi.', 400);
|
| 143 |
+
}
|
| 144 |
+
|
| 145 |
+
// Bersihkan nomor HP
|
| 146 |
+
let cleanedNo = nomor_hp.trim();
|
| 147 |
+
if (cleanedNo.startsWith('0')) {
|
| 148 |
+
cleanedNo = '62' + cleanedNo.slice(1);
|
| 149 |
+
} else if (cleanedNo.startsWith('+')) {
|
| 150 |
+
cleanedNo = cleanedNo.slice(1);
|
| 151 |
+
}
|
| 152 |
+
|
| 153 |
+
// Format pesan
|
| 154 |
+
const jatuhTempoStr = bon.jatuh_tempo
|
| 155 |
+
? new Date(bon.jatuh_tempo).toLocaleDateString('id-ID', { day: '2-digit', month: '2-digit', year: 'numeric' })
|
| 156 |
+
: 'secepatnya';
|
| 157 |
+
const sisaRupiah = Math.floor(Number(bon.sisa_bon)).toLocaleString('id-ID').replace(/,/g, '.');
|
| 158 |
+
|
| 159 |
+
const pesan =
|
| 160 |
+
`Halo *${nama}*,\n\n` +
|
| 161 |
+
`Kami dari *Tokiva POS* ingin mengingatkan bahwa Anda memiliki tagihan kasbon ` +
|
| 162 |
+
`sebesar *Rp ${sisaRupiah}* yang jatuh tempo pada tanggal *{jatuh_tempo_str}*.\n\n` +
|
| 163 |
+
`Silakan melakukan pembayaran cicilan atau pelunasan melalui kasir Tokiva. ` +
|
| 164 |
+
`Terima kasih atas kerjasamanya!`;
|
| 165 |
+
const formatPesan = pesan.replace('{jatuh_tempo_str}', jatuhTempoStr);
|
| 166 |
+
|
| 167 |
+
const encodedMessage = encodeURIComponent(formatPesan);
|
| 168 |
+
const waLink = `https://wa.me/${cleanedNo}?text=${encodedMessage}`;
|
| 169 |
+
|
| 170 |
+
// Tandai reminder terkirim
|
| 171 |
+
await prisma.bon.update({
|
| 172 |
+
where: { id: bonId },
|
| 173 |
+
data: { reminder_sent: 1 },
|
| 174 |
+
});
|
| 175 |
+
|
| 176 |
+
// Catat notifikasi
|
| 177 |
+
await prisma.notifikasi.create({
|
| 178 |
+
data: {
|
| 179 |
+
tipe: 'bon_jatuh_tempo',
|
| 180 |
+
judul: `WhatsApp Tagihan Dikirim: ${nama}`,
|
| 181 |
+
pesan: `Link WhatsApp pengingat tagihan Rp ${sisaRupiah} untuk ${nama} telah dibuat.`,
|
| 182 |
+
referensi_id: bon.id,
|
| 183 |
+
},
|
| 184 |
+
});
|
| 185 |
+
|
| 186 |
+
return waLink;
|
| 187 |
+
}
|
| 188 |
+
}
|
src/services/diskon.service.ts
ADDED
|
@@ -0,0 +1,214 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import prisma from '../utils/db';
|
| 2 |
+
import { APIError } from '../utils/errors';
|
| 3 |
+
|
| 4 |
+
export class DiskonService {
|
| 5 |
+
static async getActiveDiscounts(isMember: boolean) {
|
| 6 |
+
const now = new Date();
|
| 7 |
+
|
| 8 |
+
// Filter diskon yang aktif dan periode tanggalnya cocok
|
| 9 |
+
const discounts = await prisma.diskon.findMany({
|
| 10 |
+
where: {
|
| 11 |
+
is_aktif: 1,
|
| 12 |
+
tgl_mulai: { lte: now },
|
| 13 |
+
tgl_selesai: { gte: now },
|
| 14 |
+
...(isMember ? {} : { member_only: 0 }),
|
| 15 |
+
},
|
| 16 |
+
});
|
| 17 |
+
|
| 18 |
+
const currentTimeString = now.toTimeString().split(' ')[0]; // "HH:MM:SS"
|
| 19 |
+
|
| 20 |
+
const activeDiscounts = discounts.filter((d) => {
|
| 21 |
+
if (d.tipe === 'flash_sale') {
|
| 22 |
+
if (d.jam_mulai && d.jam_selesai) {
|
| 23 |
+
const jamMulai = new Date(d.jam_mulai).toTimeString().split(' ')[0];
|
| 24 |
+
const jamSelesai = new Date(d.jam_selesai).toTimeString().split(' ')[0];
|
| 25 |
+
return currentTimeString >= jamMulai && currentTimeString <= jamSelesai;
|
| 26 |
+
}
|
| 27 |
+
return true;
|
| 28 |
+
}
|
| 29 |
+
return true;
|
| 30 |
+
});
|
| 31 |
+
|
| 32 |
+
return activeDiscounts;
|
| 33 |
+
}
|
| 34 |
+
|
| 35 |
+
static async hitungDiskonDanHarga(items: Array<{ produk_id: number; qty: number }>, memberId?: number | null) {
|
| 36 |
+
const isMember = memberId !== undefined && memberId !== null;
|
| 37 |
+
const activePromos = await this.getActiveDiscounts(isMember);
|
| 38 |
+
|
| 39 |
+
// 1. Hitung harga dasar bertingkat per item (volume pricing)
|
| 40 |
+
const cartItems: any[] = [];
|
| 41 |
+
let subtotalDasar = 0;
|
| 42 |
+
|
| 43 |
+
for (const item of items) {
|
| 44 |
+
const produkId = item.produk_id;
|
| 45 |
+
const qty = item.qty;
|
| 46 |
+
|
| 47 |
+
const produk = await prisma.produk.findUnique({
|
| 48 |
+
where: { id: produkId },
|
| 49 |
+
include: { harga_tingkat: true },
|
| 50 |
+
});
|
| 51 |
+
|
| 52 |
+
if (!produk) {
|
| 53 |
+
throw new APIError(`Produk ID ${produkId} tidak ditemukan.`, 404);
|
| 54 |
+
}
|
| 55 |
+
|
| 56 |
+
// Tentukan harga dasar berdasarkan volume pricing (harga_tingkat)
|
| 57 |
+
let hargaSatuan = Number(produk.harga_jual); // Default ecer
|
| 58 |
+
|
| 59 |
+
if (produk.harga_tingkat && produk.harga_tingkat.length > 0) {
|
| 60 |
+
// Urutkan berdasarkan min_qty desc untuk mencari batas tertinggi yang terpenuhi
|
| 61 |
+
const sortedTingkat = [...produk.harga_tingkat].sort((a, b) => Number(b.min_qty) - Number(a.min_qty));
|
| 62 |
+
for (const ht of sortedTingkat) {
|
| 63 |
+
if (qty >= Number(ht.min_qty)) {
|
| 64 |
+
hargaSatuan = Number(ht.harga);
|
| 65 |
+
break;
|
| 66 |
+
}
|
| 67 |
+
}
|
| 68 |
+
}
|
| 69 |
+
|
| 70 |
+
const itemSubtotal = qty * hargaSatuan;
|
| 71 |
+
subtotalDasar += itemSubtotal;
|
| 72 |
+
|
| 73 |
+
cartItems.push({
|
| 74 |
+
produk,
|
| 75 |
+
qty,
|
| 76 |
+
harga_original: hargaSatuan,
|
| 77 |
+
diskon_item: 0,
|
| 78 |
+
diskon_id: null,
|
| 79 |
+
subtotal: itemSubtotal,
|
| 80 |
+
});
|
| 81 |
+
}
|
| 82 |
+
|
| 83 |
+
// 2. Terapkan diskon per item (per_item_persen, per_item_nominal, flash_sale)
|
| 84 |
+
for (const item of cartItems) {
|
| 85 |
+
const produk = item.produk;
|
| 86 |
+
const qty = item.qty;
|
| 87 |
+
const hargaOriginal = item.harga_original;
|
| 88 |
+
|
| 89 |
+
let bestDiscount = 0;
|
| 90 |
+
let bestDiskonId: number | null = null;
|
| 91 |
+
|
| 92 |
+
for (const promo of activePromos) {
|
| 93 |
+
// Cek apakah promo berlaku untuk produk ini
|
| 94 |
+
let isApplicable = false;
|
| 95 |
+
if (!promo.produk_ids) {
|
| 96 |
+
// Berlaku untuk semua produk
|
| 97 |
+
isApplicable = true;
|
| 98 |
+
} else {
|
| 99 |
+
try {
|
| 100 |
+
let pIds: number[] = [];
|
| 101 |
+
if (typeof promo.produk_ids === 'string') {
|
| 102 |
+
pIds = JSON.parse(promo.produk_ids);
|
| 103 |
+
} else if (Array.isArray(promo.produk_ids)) {
|
| 104 |
+
pIds = promo.produk_ids as number[];
|
| 105 |
+
}
|
| 106 |
+
if (pIds.includes(produk.id)) {
|
| 107 |
+
isApplicable = true;
|
| 108 |
+
}
|
| 109 |
+
} catch (e) {
|
| 110 |
+
isApplicable = false;
|
| 111 |
+
}
|
| 112 |
+
}
|
| 113 |
+
|
| 114 |
+
if (!isApplicable) continue;
|
| 115 |
+
|
| 116 |
+
// Hitung nilai diskon
|
| 117 |
+
let currentDiscount = 0;
|
| 118 |
+
if (promo.tipe === 'per_item_persen' || promo.tipe === 'flash_sale') {
|
| 119 |
+
currentDiscount = hargaOriginal * (Number(promo.nilai) / 100) * qty;
|
| 120 |
+
} else if (promo.tipe === 'per_item_nominal') {
|
| 121 |
+
currentDiscount = Number(promo.nilai) * qty;
|
| 122 |
+
}
|
| 123 |
+
|
| 124 |
+
if (currentDiscount > bestDiscount) {
|
| 125 |
+
bestDiscount = currentDiscount;
|
| 126 |
+
bestDiskonId = promo.id;
|
| 127 |
+
}
|
| 128 |
+
}
|
| 129 |
+
|
| 130 |
+
if (bestDiscount > 0) {
|
| 131 |
+
item.diskon_item = bestDiscount;
|
| 132 |
+
item.diskon_id = bestDiskonId;
|
| 133 |
+
item.subtotal = (hargaOriginal * qty) - bestDiscount;
|
| 134 |
+
}
|
| 135 |
+
}
|
| 136 |
+
|
| 137 |
+
// Kalkulasi subtotal setelah item-level discounts
|
| 138 |
+
let subtotalSetelahItem = cartItems.reduce((acc, item) => acc + item.subtotal, 0);
|
| 139 |
+
const totalDiskonItem = cartItems.reduce((acc, item) => acc + item.diskon_item, 0);
|
| 140 |
+
|
| 141 |
+
// 3. Terapkan diskon bundling (paket promo)
|
| 142 |
+
let totalDiskonBundling = 0;
|
| 143 |
+
const bundlingPromos = activePromos.filter((p) => p.tipe === 'bundling');
|
| 144 |
+
|
| 145 |
+
for (const promo of bundlingPromos) {
|
| 146 |
+
const bundleItems = await prisma.diskon_bundling.findMany({
|
| 147 |
+
where: { diskon_id: promo.id },
|
| 148 |
+
});
|
| 149 |
+
if (bundleItems.length === 0) continue;
|
| 150 |
+
|
| 151 |
+
// Hitung berapa banyak paket bundle yang bisa dibuat dari keranjang
|
| 152 |
+
let maxPossibleBundles = 999999;
|
| 153 |
+
for (const bItem of bundleItems) {
|
| 154 |
+
const matchingCartItem = cartItems.find((item) => item.produk.id === bItem.produk_id);
|
| 155 |
+
if (!matchingCartItem) {
|
| 156 |
+
maxPossibleBundles = 0;
|
| 157 |
+
break;
|
| 158 |
+
}
|
| 159 |
+
const availableMultiples = Math.floor(matchingCartItem.qty / Number(bItem.qty));
|
| 160 |
+
if (availableMultiples < maxPossibleBundles) {
|
| 161 |
+
maxPossibleBundles = availableMultiples;
|
| 162 |
+
}
|
| 163 |
+
}
|
| 164 |
+
|
| 165 |
+
if (maxPossibleBundles > 0) {
|
| 166 |
+
const diskonBundleTotal = Number(promo.nilai) * maxPossibleBundles;
|
| 167 |
+
totalDiskonBundling += diskonBundleTotal;
|
| 168 |
+
subtotalSetelahItem -= diskonBundleTotal;
|
| 169 |
+
}
|
| 170 |
+
}
|
| 171 |
+
|
| 172 |
+
// 4. Terapkan diskon tingkat transaksi (total_persen, total_nominal, member)
|
| 173 |
+
const transactionPromos = activePromos.filter((p) =>
|
| 174 |
+
['total_persen', 'total_nominal', 'member'].includes(p.tipe)
|
| 175 |
+
);
|
| 176 |
+
|
| 177 |
+
let bestTransDiscount = 0;
|
| 178 |
+
for (const promo of transactionPromos) {
|
| 179 |
+
// Cek minimal belanja
|
| 180 |
+
if (subtotalSetelahItem < Number(promo.min_belanja)) {
|
| 181 |
+
continue;
|
| 182 |
+
}
|
| 183 |
+
|
| 184 |
+
let currentTransDiscount = 0;
|
| 185 |
+
if (promo.tipe === 'total_persen' || promo.tipe === 'member') {
|
| 186 |
+
currentTransDiscount = subtotalSetelahItem * (Number(promo.nilai) / 100);
|
| 187 |
+
} else if (promo.tipe === 'total_nominal') {
|
| 188 |
+
currentTransDiscount = Number(promo.nilai);
|
| 189 |
+
}
|
| 190 |
+
|
| 191 |
+
if (currentTransDiscount > bestTransDiscount) {
|
| 192 |
+
bestTransDiscount = currentTransDiscount;
|
| 193 |
+
}
|
| 194 |
+
}
|
| 195 |
+
|
| 196 |
+
const totalDiskonTransaksi = bestTransDiscount;
|
| 197 |
+
|
| 198 |
+
// Hitung total akhir
|
| 199 |
+
let totalAkhir = subtotalSetelahItem - totalDiskonTransaksi;
|
| 200 |
+
if (totalAkhir < 0) {
|
| 201 |
+
totalAkhir = 0;
|
| 202 |
+
}
|
| 203 |
+
|
| 204 |
+
return {
|
| 205 |
+
cart_items: cartItems,
|
| 206 |
+
subtotal_dasar: subtotalDasar,
|
| 207 |
+
total_diskon_item: totalDiskonItem,
|
| 208 |
+
total_diskon_bundling: totalDiskonBundling,
|
| 209 |
+
total_diskon_transaksi: totalDiskonTransaksi,
|
| 210 |
+
total_diskon_keseluruhan: totalDiskonItem + totalDiskonBundling + totalDiskonTransaksi,
|
| 211 |
+
total_akhir: totalAkhir,
|
| 212 |
+
};
|
| 213 |
+
}
|
| 214 |
+
}
|
src/services/kategori.service.ts
ADDED
|
@@ -0,0 +1,149 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import prisma from '../utils/db';
|
| 2 |
+
import { catatAudit } from '../utils/audit';
|
| 3 |
+
import { moveToTrash } from '../utils/trash';
|
| 4 |
+
import { APIError } from '../utils/errors';
|
| 5 |
+
|
| 6 |
+
export class KategoriService {
|
| 7 |
+
static async getKategori(skip = 0, limit = 100, pencarian?: string) {
|
| 8 |
+
const where: any = {
|
| 9 |
+
deleted_at: null,
|
| 10 |
+
};
|
| 11 |
+
|
| 12 |
+
if (pencarian) {
|
| 13 |
+
where.nama = {
|
| 14 |
+
contains: pencarian,
|
| 15 |
+
};
|
| 16 |
+
}
|
| 17 |
+
|
| 18 |
+
return await prisma.kategori.findMany({
|
| 19 |
+
where,
|
| 20 |
+
skip,
|
| 21 |
+
take: limit,
|
| 22 |
+
});
|
| 23 |
+
}
|
| 24 |
+
|
| 25 |
+
static async getKategoriById(id: number) {
|
| 26 |
+
const kategori = await prisma.kategori.findFirst({
|
| 27 |
+
where: { id, deleted_at: null },
|
| 28 |
+
});
|
| 29 |
+
if (!kategori) {
|
| 30 |
+
throw new APIError('Kategori tidak ditemukan', 404);
|
| 31 |
+
}
|
| 32 |
+
return kategori;
|
| 33 |
+
}
|
| 34 |
+
|
| 35 |
+
static async create(data: any, actorId: number, ip: string, userAgent: string) {
|
| 36 |
+
// Check duplicate
|
| 37 |
+
const existing = await prisma.kategori.findFirst({
|
| 38 |
+
where: { nama: data.nama },
|
| 39 |
+
});
|
| 40 |
+
|
| 41 |
+
if (existing) {
|
| 42 |
+
if (existing.deleted_at !== null || existing.aktif === 0) {
|
| 43 |
+
// Reactivate
|
| 44 |
+
const reactivated = await prisma.kategori.update({
|
| 45 |
+
where: { id: existing.id },
|
| 46 |
+
data: {
|
| 47 |
+
deleted_at: null,
|
| 48 |
+
aktif: 1,
|
| 49 |
+
icon: data.icon ?? existing.icon,
|
| 50 |
+
},
|
| 51 |
+
});
|
| 52 |
+
return reactivated;
|
| 53 |
+
}
|
| 54 |
+
throw new APIError('Nama kategori sudah digunakan', 400);
|
| 55 |
+
}
|
| 56 |
+
|
| 57 |
+
const kategori = await prisma.kategori.create({
|
| 58 |
+
data: {
|
| 59 |
+
nama: data.nama,
|
| 60 |
+
icon: data.icon ?? null,
|
| 61 |
+
aktif: data.aktif ?? 1,
|
| 62 |
+
},
|
| 63 |
+
});
|
| 64 |
+
|
| 65 |
+
await catatAudit({
|
| 66 |
+
userId: actorId,
|
| 67 |
+
aksi: 'create',
|
| 68 |
+
entitas: 'kategori',
|
| 69 |
+
entitasId: kategori.id,
|
| 70 |
+
dataBaru: { nama: kategori.nama, icon: kategori.icon },
|
| 71 |
+
ipAddress: ip,
|
| 72 |
+
userAgent,
|
| 73 |
+
});
|
| 74 |
+
|
| 75 |
+
return kategori;
|
| 76 |
+
}
|
| 77 |
+
|
| 78 |
+
static async update(id: number, data: any, actorId: number, ip: string, userAgent: string) {
|
| 79 |
+
const kategori = await this.getKategoriById(id);
|
| 80 |
+
|
| 81 |
+
const dataLama = { nama: kategori.nama, icon: kategori.icon, aktif: kategori.aktif };
|
| 82 |
+
|
| 83 |
+
if (data.nama !== undefined) {
|
| 84 |
+
const dup = await prisma.kategori.findFirst({
|
| 85 |
+
where: { nama: data.nama, id: { not: id } },
|
| 86 |
+
});
|
| 87 |
+
if (dup) {
|
| 88 |
+
throw new APIError('Nama kategori sudah digunakan oleh kategori lain', 400);
|
| 89 |
+
}
|
| 90 |
+
}
|
| 91 |
+
|
| 92 |
+
const updateData: any = {};
|
| 93 |
+
if (data.nama !== undefined) updateData.nama = data.nama;
|
| 94 |
+
if (data.icon !== undefined) updateData.icon = data.icon;
|
| 95 |
+
if (data.aktif !== undefined) updateData.aktif = data.aktif;
|
| 96 |
+
|
| 97 |
+
const updated = await prisma.kategori.update({
|
| 98 |
+
where: { id },
|
| 99 |
+
data: updateData,
|
| 100 |
+
});
|
| 101 |
+
|
| 102 |
+
const dataBaru = { nama: updated.nama, icon: updated.icon, aktif: updated.aktif };
|
| 103 |
+
|
| 104 |
+
await catatAudit({
|
| 105 |
+
userId: actorId,
|
| 106 |
+
aksi: 'update',
|
| 107 |
+
entitas: 'kategori',
|
| 108 |
+
entitasId: updated.id,
|
| 109 |
+
dataLama,
|
| 110 |
+
dataBaru,
|
| 111 |
+
ipAddress: ip,
|
| 112 |
+
userAgent,
|
| 113 |
+
});
|
| 114 |
+
|
| 115 |
+
return updated;
|
| 116 |
+
}
|
| 117 |
+
|
| 118 |
+
static async delete(id: number, actorId: number, ip: string, userAgent: string) {
|
| 119 |
+
const kategori = await this.getKategoriById(id);
|
| 120 |
+
|
| 121 |
+
// Check if active products reference this category
|
| 122 |
+
const productCount = await prisma.produk.count({
|
| 123 |
+
where: { kategori_id: id, deleted_at: null },
|
| 124 |
+
});
|
| 125 |
+
|
| 126 |
+
if (productCount > 0) {
|
| 127 |
+
throw new APIError(`Tidak dapat menghapus kategori. Masih digunakan oleh ${productCount} produk.`, 400);
|
| 128 |
+
}
|
| 129 |
+
|
| 130 |
+
await moveToTrash({
|
| 131 |
+
instance: kategori,
|
| 132 |
+
entityType: 'kategori',
|
| 133 |
+
actorId,
|
| 134 |
+
});
|
| 135 |
+
|
| 136 |
+
await catatAudit({
|
| 137 |
+
userId: actorId,
|
| 138 |
+
aksi: 'delete',
|
| 139 |
+
entitas: 'kategori',
|
| 140 |
+
entitasId: kategori.id,
|
| 141 |
+
dataLama: { nama: kategori.nama, aktif: 1 },
|
| 142 |
+
dataBaru: { nama: kategori.nama, aktif: 0 },
|
| 143 |
+
ipAddress: ip,
|
| 144 |
+
userAgent,
|
| 145 |
+
});
|
| 146 |
+
|
| 147 |
+
return true;
|
| 148 |
+
}
|
| 149 |
+
}
|
src/services/member.service.ts
ADDED
|
@@ -0,0 +1,196 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import prisma from '../utils/db';
|
| 2 |
+
import { catatAudit } from '../utils/audit';
|
| 3 |
+
import { moveToTrash } from '../utils/trash';
|
| 4 |
+
import { APIError } from '../utils/errors';
|
| 5 |
+
|
| 6 |
+
export class MemberService {
|
| 7 |
+
static async getMembers(skip = 0, limit = 10, pencarian?: string) {
|
| 8 |
+
const where: any = {
|
| 9 |
+
deleted_at: null,
|
| 10 |
+
};
|
| 11 |
+
|
| 12 |
+
if (pencarian) {
|
| 13 |
+
where.OR = [
|
| 14 |
+
{ nama: { contains: pencarian } },
|
| 15 |
+
{ kode: { contains: pencarian } },
|
| 16 |
+
{ nomor_hp: { contains: pencarian } },
|
| 17 |
+
];
|
| 18 |
+
}
|
| 19 |
+
|
| 20 |
+
return await prisma.member.findMany({
|
| 21 |
+
where,
|
| 22 |
+
skip,
|
| 23 |
+
take: limit,
|
| 24 |
+
});
|
| 25 |
+
}
|
| 26 |
+
|
| 27 |
+
static async getMemberById(id: number) {
|
| 28 |
+
const member = await prisma.member.findFirst({
|
| 29 |
+
where: { id, deleted_at: null },
|
| 30 |
+
});
|
| 31 |
+
if (!member) {
|
| 32 |
+
throw new APIError('Member tidak ditemukan', 404);
|
| 33 |
+
}
|
| 34 |
+
return member;
|
| 35 |
+
}
|
| 36 |
+
|
| 37 |
+
static async create(data: any, actorId: number, ip: string, userAgent: string) {
|
| 38 |
+
const existingPhone = await prisma.member.findFirst({
|
| 39 |
+
where: { nomor_hp: data.nomor_hp },
|
| 40 |
+
});
|
| 41 |
+
const existingCode = await prisma.member.findUnique({
|
| 42 |
+
where: { kode: data.kode },
|
| 43 |
+
});
|
| 44 |
+
|
| 45 |
+
const existing = existingPhone || existingCode;
|
| 46 |
+
if (existing) {
|
| 47 |
+
if (existing.deleted_at === null) {
|
| 48 |
+
const fieldType = existing.id === existingPhone?.id ? 'Nomor HP' : 'Kode';
|
| 49 |
+
const fieldVal = existing.id === existingPhone?.id ? data.nomor_hp : data.kode;
|
| 50 |
+
throw new APIError(`${fieldType} member '${fieldVal}' sudah terdaftar/digunakan`, 400);
|
| 51 |
+
} else {
|
| 52 |
+
// Soft deleted duplicate. Reactivate and update!
|
| 53 |
+
const restored = await prisma.member.update({
|
| 54 |
+
where: { id: existing.id },
|
| 55 |
+
data: {
|
| 56 |
+
deleted_at: null,
|
| 57 |
+
aktif: 1,
|
| 58 |
+
kode: data.kode,
|
| 59 |
+
nama: data.nama,
|
| 60 |
+
nomor_hp: data.nomor_hp,
|
| 61 |
+
alamat: data.alamat ?? null,
|
| 62 |
+
limit_bon: data.limit_bon ?? 0,
|
| 63 |
+
foto_url: data.foto_url ?? null,
|
| 64 |
+
},
|
| 65 |
+
});
|
| 66 |
+
|
| 67 |
+
await catatAudit({
|
| 68 |
+
userId: actorId,
|
| 69 |
+
aksi: 'restore',
|
| 70 |
+
entitas: 'member',
|
| 71 |
+
entitasId: restored.id,
|
| 72 |
+
dataBaru: { kode: restored.kode, nama: restored.nama, nomor_hp: restored.nomor_hp, limit_bon: Number(restored.limit_bon) },
|
| 73 |
+
ipAddress: ip,
|
| 74 |
+
userAgent,
|
| 75 |
+
});
|
| 76 |
+
|
| 77 |
+
return restored;
|
| 78 |
+
}
|
| 79 |
+
}
|
| 80 |
+
|
| 81 |
+
const member = await prisma.member.create({
|
| 82 |
+
data: {
|
| 83 |
+
kode: data.kode,
|
| 84 |
+
nama: data.nama,
|
| 85 |
+
nomor_hp: data.nomor_hp,
|
| 86 |
+
alamat: data.alamat ?? null,
|
| 87 |
+
limit_bon: data.limit_bon ?? 0,
|
| 88 |
+
total_bon: 0,
|
| 89 |
+
foto_url: data.foto_url ?? null,
|
| 90 |
+
aktif: data.aktif ?? 1,
|
| 91 |
+
},
|
| 92 |
+
});
|
| 93 |
+
|
| 94 |
+
await catatAudit({
|
| 95 |
+
userId: actorId,
|
| 96 |
+
aksi: 'create',
|
| 97 |
+
entitas: 'member',
|
| 98 |
+
entitasId: member.id,
|
| 99 |
+
dataBaru: { kode: member.kode, nama: member.nama, nomor_hp: member.nomor_hp, limit_bon: Number(member.limit_bon) },
|
| 100 |
+
ipAddress: ip,
|
| 101 |
+
userAgent,
|
| 102 |
+
});
|
| 103 |
+
|
| 104 |
+
return member;
|
| 105 |
+
}
|
| 106 |
+
|
| 107 |
+
static async update(id: number, data: any, actorId: number, ip: string, userAgent: string) {
|
| 108 |
+
const member = await this.getMemberById(id);
|
| 109 |
+
|
| 110 |
+
const dataLama = {
|
| 111 |
+
nama: member.nama,
|
| 112 |
+
nomor_hp: member.nomor_hp,
|
| 113 |
+
alamat: member.alamat,
|
| 114 |
+
total_poin: member.total_poin,
|
| 115 |
+
limit_bon: Number(member.limit_bon),
|
| 116 |
+
total_bon: Number(member.total_bon),
|
| 117 |
+
foto_url: member.foto_url,
|
| 118 |
+
aktif: member.aktif,
|
| 119 |
+
};
|
| 120 |
+
|
| 121 |
+
if (data.nomor_hp !== undefined) {
|
| 122 |
+
const dup = await prisma.member.findFirst({
|
| 123 |
+
where: { nomor_hp: data.nomor_hp, id: { not: id } },
|
| 124 |
+
});
|
| 125 |
+
if (dup) {
|
| 126 |
+
throw new APIError('Nomor HP tersebut sudah digunakan oleh member lain', 400);
|
| 127 |
+
}
|
| 128 |
+
}
|
| 129 |
+
|
| 130 |
+
const updateData: any = {};
|
| 131 |
+
if (data.nama !== undefined) updateData.nama = data.nama;
|
| 132 |
+
if (data.nomor_hp !== undefined) updateData.nomor_hp = data.nomor_hp;
|
| 133 |
+
if (data.alamat !== undefined) updateData.alamat = data.alamat;
|
| 134 |
+
if (data.total_poin !== undefined) updateData.total_poin = data.total_poin;
|
| 135 |
+
if (data.limit_bon !== undefined) updateData.limit_bon = data.limit_bon;
|
| 136 |
+
if (data.total_bon !== undefined) updateData.total_bon = data.total_bon;
|
| 137 |
+
if (data.foto_url !== undefined) updateData.foto_url = data.foto_url;
|
| 138 |
+
if (data.aktif !== undefined) updateData.aktif = data.aktif;
|
| 139 |
+
|
| 140 |
+
const updated = await prisma.member.update({
|
| 141 |
+
where: { id },
|
| 142 |
+
data: updateData,
|
| 143 |
+
});
|
| 144 |
+
|
| 145 |
+
const dataBaru = {
|
| 146 |
+
nama: updated.nama,
|
| 147 |
+
nomor_hp: updated.nomor_hp,
|
| 148 |
+
alamat: updated.alamat,
|
| 149 |
+
total_poin: updated.total_poin,
|
| 150 |
+
limit_bon: Number(updated.limit_bon),
|
| 151 |
+
total_bon: Number(updated.total_bon),
|
| 152 |
+
foto_url: updated.foto_url,
|
| 153 |
+
aktif: updated.aktif,
|
| 154 |
+
};
|
| 155 |
+
|
| 156 |
+
await catatAudit({
|
| 157 |
+
userId: actorId,
|
| 158 |
+
aksi: 'update',
|
| 159 |
+
entitas: 'member',
|
| 160 |
+
entitasId: updated.id,
|
| 161 |
+
dataLama,
|
| 162 |
+
dataBaru,
|
| 163 |
+
ipAddress: ip,
|
| 164 |
+
userAgent,
|
| 165 |
+
});
|
| 166 |
+
|
| 167 |
+
return updated;
|
| 168 |
+
}
|
| 169 |
+
|
| 170 |
+
static async delete(id: number, actorId: number, ip: string, userAgent: string) {
|
| 171 |
+
const member = await this.getMemberById(id);
|
| 172 |
+
|
| 173 |
+
if (Number(member.total_bon) > 0) {
|
| 174 |
+
throw new APIError(`Tidak dapat menghapus member. Masih memiliki tanggungan kasbon sebesar Rp ${Number(member.total_bon).toLocaleString('id-ID')}`, 400);
|
| 175 |
+
}
|
| 176 |
+
|
| 177 |
+
await moveToTrash({
|
| 178 |
+
instance: member,
|
| 179 |
+
entityType: 'member',
|
| 180 |
+
actorId,
|
| 181 |
+
});
|
| 182 |
+
|
| 183 |
+
await catatAudit({
|
| 184 |
+
userId: actorId,
|
| 185 |
+
aksi: 'delete',
|
| 186 |
+
entitas: 'member',
|
| 187 |
+
entitasId: member.id,
|
| 188 |
+
dataLama: { kode: member.kode, nama: member.nama, aktif: 1 },
|
| 189 |
+
dataBaru: { kode: member.kode, nama: member.nama, aktif: 0 },
|
| 190 |
+
ipAddress: ip,
|
| 191 |
+
userAgent,
|
| 192 |
+
});
|
| 193 |
+
|
| 194 |
+
return true;
|
| 195 |
+
}
|
| 196 |
+
}
|