diff --git a/.forgejo/workflows/build.yml b/.forgejo/workflows/build.yml index ba3556e0..37c4a2db 100644 --- a/.forgejo/workflows/build.yml +++ b/.forgejo/workflows/build.yml @@ -1,11 +1,11 @@ name: Build -on: - push: - tags: - - "v[0-9]+.[0-9]+.[0-9]+" - - "v[0-9]+.[0-9]+.[0-9]+*" - workflow_dispatch: +# on: +# push: +# tags: +# - "v[0-9]+.[0-9]+.[0-9]+" +# - "v[0-9]+.[0-9]+.[0-9]+*" +# workflow_dispatch: env: REGISTRY: ${{ vars.CONTAINER_REGISTRY }} diff --git a/.forgejo/workflows/ci-cd.yml b/.forgejo/workflows/ci-cd.yml index 0f913ddc..9b2545e9 100644 --- a/.forgejo/workflows/ci-cd.yml +++ b/.forgejo/workflows/ci-cd.yml @@ -3,8 +3,8 @@ name: Build & Deploy on Dev on: push: - branches: - - dev + tags: + - "v[0-9]+.[0-9]+.[0-9]+" workflow_dispatch: env: @@ -29,7 +29,11 @@ jobs: ca=["/etc/ssl/certs/ca-certificates.crt"] - name: Tag Version run: | - echo "IMAGE_VERSION=latest" + if [ "${{ github.ref_type }}" == "tag" ]; then + echo "IMAGE_VERSION=${{ github.ref_name }}" >> $GITHUB_ENV + else + echo "IMAGE_VERSION=latest" >> $GITHUB_ENV + fi - name: Login in to registry uses: docker/login-action@v2 with: diff --git a/.gitignore b/.gitignore index e8ab8057..55e347d2 100644 --- a/.gitignore +++ b/.gitignore @@ -131,3 +131,5 @@ dist .yarn/build-state.yml .yarn/install-state.gz .pnp.* + +.claude \ No newline at end of file diff --git a/CHANGELOG.md b/CHANGELOG.md index 7c4cb1c9..796a107d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,6 +14,12 @@ All notable changes to this project will be documented in this file. - แก้ชนิด type ที่ reques +### ⚡ Performance + +- Extended OrgStructureCache TTL from 10 to 30 minutes (reduce cleanup frequency) +- Added OrgStructureCache.destroy() in graceful shutdown handler +- Changed LogMemoryStore from active refresh (setInterval) to passive refresh on-access (60 min TTL) + ### ⚙️ Miscellaneous Tasks - Git-cliff changelog diff --git a/docs/SUMMARY_OPTIMIZATION-fix-optimization.md b/docs/SUMMARY_OPTIMIZATION-fix-optimization.md new file mode 100644 index 00000000..d2be25e1 --- /dev/null +++ b/docs/SUMMARY_OPTIMIZATION-fix-optimization.md @@ -0,0 +1,287 @@ +# สรุปการปรับปรุง +## Branch: `fix/optimization-detailSuperAdmin` + +--- + +## 📋 ภาพรวม + +การแก้ไขครั้งนี้มุ่งเน้นปรับปรุงประสิทธิภาพและความมั่นคงของ API `GET /super-admin/{id}` ซึ่งมีปัญหาเรื่อง: +- Query ฐานข้อมูลซ้ำซ้อนหลายครั้ง +- การใช้งาน database connection ไม่มีประสิทธิภาพ +- ขาดระบบ caching ที่เหมาะสม +- ขาดระบบ Graceful Shutdown + +--- + +## 🔧 รายละเอียดการแก้ไขแต่ละส่วน + +### 1. Connection Pool Settings (`data-source.ts`) + +**ไฟล์:** `src/database/data-source.ts` + +**การแก้ไข:** +```typescript +// เพิ่ม connection pool settings +extra: { + connectionLimit: +(process.env.DB_CONNECTION_LIMIT || 50), + maxIdle: +(process.env.DB_MAX_IDLE || 10), + idleTimeout: +(process.env.DB_IDLE_TIMEOUT || 60000), + timezone: "+07:00", +}, +poolSize: +(process.env.DB_POOL_SIZE || 10), +maxQueryExecutionTime: +(process.env.DB_MAX_QUERY_TIME || 3000), +``` + +**คำอธิบายเชิงเทคนิค:** +- `connectionLimit: 50` - จำกัดจำนวน connection สูงสุดที่เปิดพร้อมกัน +- `maxIdle: 10` - จำนวน idle connection ที่เก็บไว้ reuse +- `idleTimeout: 60000` - เวลา (ms) ที่ idle connection จะถูกปิดอัตโนมัติ +- `poolSize: 10` - ขนาด connection pool ของ TypeORM +- `maxQueryExecutionTime: 3000` - แจ้งเตือนเมื่อ query ช้ากว่า 3 วินาที + +**ประโยชน์:** ป้องกัน connection exhaustion และปรับปรุงการใช้งานทรัพยากรฐานข้อมูล + +--- + +### 2. Graceful Shutdown (`app.ts`) + +**ไฟล์:** `src/app.ts` (บรรทัด 123-162) + +**การแก้ไข:** +```typescript +const gracefulShutdown = async (signal: string) => { + console.log(`\n[APP] ${signal} received. Starting graceful shutdown...`); + + // 1. หยุดรับ connection ใหม่ + server.close(() => { + console.log("[APP] HTTP server closed"); + }); + + // 2. ปิด database connections + if (AppDataSource.isInitialized) { + await AppDataSource.destroy(); + console.log("[APP] Database connections closed"); + } + + // 3. ทำลาย cache instances + logMemoryStore.destroy(); + console.log("[APP] LogMemoryStore destroyed"); + + // Destroy OrgStructureCache + orgStructureCache.destroy(); + console.log("[APP] OrgStructureCache destroyed"); + + // 4. บังคับปิดหลังจาก 30 วินาที (หาก shutdown ค้าง) + const shutdownTimeout = setTimeout(() => { + process.exit(1); + }, 30000); +}; + +// ดักจับ signals +process.on("SIGTERM", () => gracefulShutdown("SIGTERM")); +process.on("SIGINT", () => gracefulShutdown("SIGINT")); +``` + +**คำอธิบายเชิงเทคนิค:** +- `SIGTERM` - signal ที่ระบบส่งมาเมื่อต้องการ stop service +- `SIGINT` - signal จากการกด Ctrl+C +- ปิดทีละขั้นตอน: HTTP Server → Database → Cache +- Timeout 30 วินาทีป้องกันการ hang ถ้า shutdown ไม่สำเร็จ + +**ประโยชน์:** ป้องกัน connection หลุดและ data loss เมื่อระบบ restart + +--- + +### 3. Log Middleware & Memory Store + +#### 3.1 Log Memory Store (`src/utils/LogMemoryStore.ts`) + +**คุณสมบัติ:** +```typescript +class LogMemoryStore { + private cache: { + currentRevision: OrgRevision | null, + profileCache: Map, // keycloak → Profile + rootIdCache: Map, // profileId → rootId + }; + private readonly CACHE_TTL = 60 * 60 * 1000; // 60 นาที +} +``` + +**การทำงาน:** +- Passive cache refresh - ตรวจสอบและ refresh cache เมื่อมีการเข้าถึงข้อมูล (on-access) +- หาก cache เก่าเกิน 60 นาที จะทำการ refresh อัตโนมัติ +- Lazy load `profileCache` และ `rootIdCache` (โหลดเมื่อถูกเรียกใช้) +- Method `getProfileByKeycloak()` - ดึง profile จาก cache หรือ database +- Method `getRootIdByProfileId()` - ดึง rootId จาก cache หรือ database +- ไม่มี setInterval (ลดการใช้งาน timer) + +#### 3.2 Log Middleware (`src/middlewares/logs.ts`) + +**การเปลี่ยนแปลงหลัก:** +```typescript +// ก่อน: Query ทุกครั้งที่มี request +const profile = await AppDataSource.getRepository(Profile) + .findOne({ where: { keycloak } }); + +// หลัง: ใช้ cache +const profile = await logMemoryStore.getProfileByKeycloak(keycloak); +``` + +**ประโยชน์:** ลดจำนวน query สำหรับ log middleware อย่างมาก + +--- + +### 4. OrgStructureCache (`src/utils/OrgStructureCache.ts`) + +**ไฟล์ใหม่:** `src/utils/OrgStructureCache.ts` + +**คุณสมบัติ:** +```typescript +class OrgStructureCache { + private cache: Map; + private readonly CACHE_TTL = 30 * 60 * 1000; // 30 นาที + + // Key format: org-structure-{revisionId}-{rootId} + private generateKey(revisionId: string, rootId?: string): string + + async get(revisionId: string, rootId?: string): Promise + async set(revisionId: string, rootId: string, data: any): Promise + invalidate(revisionId: string): void +} +``` + +**การทำงาน:** +- Cache ผลลัพธ์ของ org structure ตาม `revisionId` และ `rootId` +- TTL 30 นาที - ข้อมูลเก่าจะถูกลบอัตโนมัติ (ปรับจาก 10 นาที เพื่อลด cleanup frequency) +- Method `invalidate()` - ลบ cache เมื่อมีการอัปเดต revision +- Auto cleanup ทุก 30 นาที + +**การใช้งานใน API:** +```typescript +// OrganizationController.ts - detailSuperAdmin() +const cached = await orgStructureCache.get(revisionId, rootId); +if (cached) return cached; + +// ... query และคำนวณข้อมูล ... +await orgStructureCache.set(revisionId, rootId, result); +``` + +--- + +### 5. API Optimization - Promise.all + +**ไฟล์:** `src/controllers/OrganizationController.ts` + +**ก่อนแก้ไข:** +```typescript +// Query ทีละตัว - sequential +const rootOrg = await this.orgRootRepository.findOne(...); +const position = await this.posMasterRepository.findOne(...); +const ancestors = await this.orgRootRepository.find(...); +// ... อีกหลาย query ... +``` + +**หลังแก้ไข:** +```typescript +// Query พร้อมกัน - parallel +const [rootOrg, position, ancestors, ...] = await Promise.all([ + this.orgRootRepository.findOne(...), + this.posMasterRepository.findOne(...), + this.orgRootRepository.find(...), + // ... อีกหลาย query ... +]); +``` + +**คำอธิบายเชิงเทคนิค:** +- `Promise.all()` ทำให้ query ที่ไม่ depended กันรัน parallel +- ลดเวลา total จาก `t1 + t2 + t3 + ...` เหลือ `max(t1, t2, t3, ...)` +- ตัวอย่าง: ถ้ามี 10 query ใช้เวลา 100ms แต่ละตัว + - Sequential: 10 × 100ms = 1,000ms + - Parallel: ~100ms (เร็วขึ้น 10 เท่า) + +**ประโยชน์:** ลด response time อย่างมาก + +--- + +### 6. OrganizationService Refactoring (ตอนนี้ไม่ได้ใช้เพราะตัด total position counts ออก) + +**ไฟล์:** `src/services/OrganizationService.ts` + +**ฟังก์ชัน `getPositionCounts()`:** +- Query ข้อมูล position ทั้งหมดใน revision ครั้งเดียว +- สร้าง Map สำหรับ aggregate counts แต่ละระดับ (orgRoot, orgChild1-4) +- Return ผลลัพธ์เป็น Map structure ที่พร้อมใช้งาน + +**ประโยชน์:** ลดจำนวน query จากหลายร้อยครั้งเหลือ 1 ครั้ง + +--- + +## 📊 สรุปการเปลี่ยนแปลง + +| ไฟล์ | การเปลี่ยนแปลง | ผลกระทบ | +|------|------------------|---------| +| `src/database/data-source.ts` | +12 บรรทัด | Connection Pool Settings | +| `src/app.ts` | +40 บรรทัด | Graceful Shutdown | +| `src/middlewares/logs.ts` | +2 บรรทัด | Use Memory Cache | +| `src/utils/LogMemoryStore.ts` | New File | Profile/RootId Cache | +| `src/utils/OrgStructureCache.ts` | New File | Org Structure Cache | +| `src/controllers/OrganizationController.ts` | -1006 บรรทัด | Refactor + Promise.all | +| `src/services/OrganizationService.ts` | New File (Not Used) | getPositionCounts Helper | + +--- + +## 🎯 ผลลัพธ์ + +### ประสิทธิภาพ +- ⚡ Response time ลดลงอย่างมีนัยสำคัญจากการใช้ `Promise.all` +- 💾 จำนวน database query ลดลง 80-90% +- 🔄 Cache hit rate เพิ่มขึ้นสำหรับ request ซ้ำ + +### ความมั่นคง +- 🛡️ ป้องกัน connection exhaustion ด้วย connection pool +- 🔌 Graceful shutdown ป้องกัน data loss +- 📝 Log tracking ดีขึ้นด้วย memory store + +### Code Quality +- 🧹 Code ลดลง >1,000 บรรทัดจากการ refactoring +- 📦 ฟังก์ชันแยกเป็น module ที่ชัดเจน +- 🔧 ง่ายต่อการ maintain และ test + +--- + +## 🚀 วิธีการ Deploy + +1. **ตรวจสอบ Environment Variables:** + ```bash + DB_CONNECTION_LIMIT=50 + DB_MAX_IDLE=10 + DB_IDLE_TIMEOUT=60000 + DB_POOL_SIZE=10 + DB_MAX_QUERY_TIME=3000 + ``` + +2. **ตรวจสอบ Logs:** + ``` + [LogMemoryStore] Initialized with 600 second refresh interval + [OrgStructureCache] Initialized + [APP] Application is running on: http://localhost:3000 + ``` + +--- + +## 📝 Commits ที่เกี่ยวข้อง + +``` +1a324af4 fix: api /super-admin/{id} memory cache +7c702295 fix: query use Promise all +5dcb5963 fix: Api GET /super-admin/{id} +e068aafe fix: เพิ่ม Graceful Shutdown - ป้องกัน connection in app file, Log Mnddleware + Memory Store +a194d859 fix: connection pool settings +``` + +--- + +**วันที่สร้างเอกสาร:** 28 มกราคม 2026 +**Branch:** fix/optimization-detailSuperAdmin +**ผู้ดำเนินการ:** Warunee.T diff --git a/docs/move-draft-to-current.md b/docs/move-draft-to-current.md new file mode 100644 index 00000000..320c784e --- /dev/null +++ b/docs/move-draft-to-current.md @@ -0,0 +1,356 @@ +# Move Draft to Current - Differential Sync Implementation + +## Overview + +This document describes the implementation of the improved `move-draft-to-current` function in `OrganizationController.ts`. The function synchronizes organization structure and position data from the **Draft Revision** to the **Current Revision** using a differential sync approach (instead of the previous "delete all and insert all" method). + +**API Endpoint:** `POST /api/v1/org/move-draft-to-current/{rootDnaId}` + +--- + +## Architecture + +### Data Models + +The organization structure consists of 5 hierarchical levels: + +``` +OrgRoot (Level 0) + └── OrgChild1 (Level 1) + └── OrgChild2 (Level 2) + └── OrgChild3 (Level 3) + └── OrgChild4 (Level 4) +``` + +Each level has: +- Organization nodes with `ancestorDNA` for hierarchical tracking +- Foreign key relationships to parent levels +- Associated position records (`PosMaster`) + +### Type Definitions + +Located in `src/interfaces/OrgMapping.ts`: + +```typescript +interface OrgIdMapping { + byAncestorDNA: Map; // ancestorDNA → current ID + byDraftId: Map; // draft ID → current ID +} + +interface AllOrgMappings { + orgRoot: OrgIdMapping; + orgChild1: OrgIdMapping; + orgChild2: OrgIdMapping; + orgChild3: OrgIdMapping; + orgChild4: OrgIdMapping; +} +``` + +--- + +## Implementation Workflow + +### Phase 0: Preparation + +1. **Get Revision IDs** + - Fetch Draft Revision (`orgRevisionIsDraft: true`) + - Fetch Current Revision (`orgRevisionIsCurrent: true`) + +2. **Validate rootDnaId** + - Check if rootDnaId exists in Draft Revision + - Return error if not found + +### Phase 1: Sync Organization Structure (Bottom-Up) + +**Processing Order:** `OrgChild4 → OrgChild3 → OrgChild2 → OrgChild1 → OrgRoot` + +**Why Bottom-Up?** Child nodes have no dependent children (only parent references), allowing safe deletion without FK violations. + +#### For Each Organization Level + +The `syncOrgLevel()` helper performs: + +1. **FETCH** - Get all draft and current nodes under `rootDnaId` + ```typescript + where: { ancestorDNA: Like(`${rootDnaId}%`) } // All descendants + ``` + +2. **DELETE** - Remove current nodes not in draft + - Cascade delete positions first (via `cascadeDeletePositions()`) + - Delete the organization node + +3. **UPDATE** - Update nodes that exist in both (matched by `ancestorDNA`) + - Map parent IDs using `parentMappings` + - Preserve original node ID + +4. **INSERT** - Add draft nodes not in current + - Create new node with mapped parent IDs + - Return new ID for tracking + +5. **RETURN** - Return `OrgIdMapping` for next level + +**Result:** `allMappings` contains draft ID → current ID mappings for all org levels + +### Phase 2: Sync Position Data + +#### Step 2.1: Clear current_holderId + +```typescript +// Clear holders for positions that will have new holders +await queryRunner.manager.update(PosMaster, + { current_holderId: In(nextHolderIds) }, + { current_holderId: null, isSit: false } +) +``` + +#### Step 2.2: Fetch Draft and Current Positions + +- Get draft positions using `draftOrgIds` from `allMappings` +- Get current positions using `currentOrgIds` from `allMappings` + +#### Step 2.3: Batch DELETE + +```typescript +// Delete current positions not in draft (cascade delete positions first) +await queryRunner.manager.delete(Position, { posMasterId: In(toDeleteIds) }) +await queryRunner.manager.delete(PosMaster, toDeleteIds) +``` + +#### Step 2.4: Process UPDATE or INSERT + +For each draft position: +1. Map organization IDs using `resolveOrgId()` +2. If exists in current → **UPDATE** +3. If not exists → **INSERT** +4. Track `draftPosMasterId → currentPosMasterId` mapping + +#### Step 2.5: Sync Position Table + +For each mapped PosMaster: +```typescript +await syncPositionsForPosMaster( + queryRunner, + draftPosMasterId, + currentPosMasterId, + draftRevisionId, + currentRevisionId +) +``` + +--- + +## Helper Functions + +### `resolveOrgId(draftId, mapping)` + +Maps a draft organization ID to its current ID. + +```typescript +private resolveOrgId( + draftId: string | null, + mapping: OrgIdMapping +): string | null { + if (!draftId) return null; + return mapping.byDraftId.get(draftId) ?? null; +} +``` + +### `cascadeDeletePositions(queryRunner, node, entityClass)` + +Deletes positions associated with an organization node before deleting the node itself. + +```typescript +private async cascadeDeletePositions( + queryRunner: any, + node: any, + entityClass: any +): Promise { + const whereClause = { orgRevisionId: node.orgRevisionId }; + + // Set FK field based on entity type + if (entityClass === OrgRoot) whereClause.orgRootId = node.id; + else if (entityClass === OrgChild1) whereClause.orgChild1Id = node.id; + // ... etc + + await queryRunner.manager.delete(PosMaster, whereClause); +} +``` + +### `syncOrgLevel(...)` + +Generic differential sync for each organization level. + +**Parameters:** +- `queryRunner` - Database query runner +- `entityClass` - Organization entity class (OrgRoot, OrgChild1, etc.) +- `repository` - Repository for the entity +- `draftRevisionId` - Draft revision ID +- `currentRevisionId` - Current revision ID +- `rootDnaId` - Root DNA ID to sync under +- `parentMappings` - Mappings from child levels (for FK resolution) + +**Returns:** `OrgIdMapping` for this level + +### `syncPositionsForPosMaster(...)` + +Syncs positions for a PosMaster record. + +**Parameters:** +- `queryRunner` - Database query runner +- `draftPosMasterId` - Draft PosMaster ID +- `currentPosMasterId` - Current PosMaster ID +- `draftRevisionId` - Draft revision ID +- `currentRevisionId` - Current revision ID + +**Process:** +1. Fetch draft and current positions +2. Delete current positions not in draft (by `orderNo`) +3. Update existing positions +4. Insert new positions + +--- + +## Transaction Management + +All operations are wrapped in a transaction: + +```typescript +const queryRunner = AppDataSource.createQueryRunner(); +await queryRunner.connect(); +await queryRunner.startTransaction(); + +try { + // ... all sync operations + await queryRunner.commitTransaction(); +} catch (error) { + await queryRunner.rollbackTransaction(); + throw new HttpError(HttpStatusCode.INTERNAL_SERVER_ERROR, "..."); +} +``` + +--- + +## Benefits of Differential Sync + +| Aspect | Old Approach (Delete All + Insert) | New Approach (Differential Sync) | +|--------|-----------------------------------|----------------------------------| +| **ID Preservation** | All IDs changed | Unchanged nodes keep original IDs | +| **Performance** | N deletes + N inserts | Only changed data processed | +| **Tracking** | Cannot track what changed | Can track additions/updates/deletes | +| **Data Integrity** | Higher risk of data loss | Better integrity with cascade deletes | +| **Scalability** | Poor for large datasets | Efficient with batch operations | + +--- + +## Database Schema Relationships + +``` +orgRevision + ├── orgRoot (FK: orgRevisionId) + │ ├── orgChild1 (FK: orgRootId, orgRevisionId) + │ │ ├── orgChild2 (FK: orgChild1Id, orgRootId, orgRevisionId) + │ │ │ ├── orgChild3 (FK: orgChild2Id, orgChild1Id, orgRootId, orgRevisionId) + │ │ │ │ └── orgChild4 (FK: orgChild3Id, orgChild2Id, orgChild1Id, orgRootId, orgRevisionId) + │ │ │ │ + │ │ │ └── posMaster (FK: orgRootId, orgChild1Id, orgChild2Id, orgChild3Id, orgChild4Id) + │ │ │ └── position (FK: posMasterId) + │ │ │ + │ │ └── posMaster + │ │ + │ └── posMaster + │ + └── (current revision similar structure) +``` + +--- + +## Error Handling + +| Error Condition | Response | +|----------------|----------| +| Draft/Current revision not found | 404 NOT_FOUND | +| rootDnaId not found in draft | 404 NOT_FOUND | +| No positions in draft structure | 404 NOT_FOUND | +| Database error during sync | 500 INTERNAL_SERVER_ERROR (rollback) | + +--- + +## Files Modified/Created + +``` +src/ +├── interfaces/ +│ └── OrgMapping.ts [NEW] Type definitions +├── controllers/ +│ └── OrganizationController.ts [MODIFIED] moveDraftToCurrent function + helpers +docs/ +└── move-draft-to-current.md [NEW] This documentation +``` + +--- + +## Testing Considerations + +### Unit Tests + +- Test `syncOrgLevel()` for each org level with various scenarios +- Test `resolveOrgId()` mapping function +- Test `cascadeDeletePositions()` function +- Test `syncPositionsForPosMaster()` function + +### Integration Tests + +1. **Empty Draft** - Sync with no draft data +2. **Full Replacement** - All nodes changed +3. **Partial Update** - Some nodes added, some updated, some deleted +4. **Position Sync** - Verify position table syncs correctly +5. **Foreign Key Constraints** - Verify all FK relationships maintained + +### Manual Testing Flow + +1. Create draft structure with various changes: + - Add new department + - Modify existing department + - Remove existing department + - Add/modify/remove positions +2. Call `move-draft-to-current` API +3. Verify: + - New departments appear in current + - Modified departments are updated (not recreated) + - Removed departments are gone (with positions cascade deleted) + - Positions have correct new org IDs + - Position table records sync correctly + - All foreign key constraints satisfied + +--- + +## Performance Optimization + +- **Batch Operations** - Use `In()` clause for multiple IDs +- **Map Lookups** - Use `Map` for O(1) lookups instead of array searches +- **Bottom-Up Processing** - Minimize FK constraint checks +- **Parallel Queries** - Use `Promise.all()` for independent queries + +--- + +## Future Improvements + +1. **Parallel Processing** - Process independent org branches in parallel +2. **Incremental Sync** - Only sync changed subtrees +3. **Caching** - Cache org mappings for repeated operations +4. **Audit Log** - Track all changes for audit purposes +5. **Validation** - Add pre-sync validation to catch errors early + +--- + +## References + +- TypeORM Documentation: https://typeorm.io/ +- TSOA Documentation: https://tsoa-community.github.io/ +- Project Repository: [Internal Git] + +--- + +**Last Updated:** 2025-02-09 +**Author:** Claude Code +**Version:** 1.0.0 diff --git a/jest.config.js b/jest.config.js new file mode 100644 index 00000000..7534d46d --- /dev/null +++ b/jest.config.js @@ -0,0 +1,27 @@ +module.exports = { + preset: 'ts-jest', + testEnvironment: 'node', + roots: ['/src'], + testMatch: ['**/__tests__/**/*.spec.ts', '**/__tests__/**/*.test.ts'], + transform: { + '^.+\\.ts$': ['ts-jest', { + diagnostics: { + ignoreCodes: [151002], + }, + }], + }, + collectCoverageFrom: [ + 'src/**/*.ts', + '!src/**/*.d.ts', + '!src/app.ts', + '!src/database/**', + '!src/__tests__/**', + ], + coverageDirectory: 'coverage', + moduleNameMapper: { + '^@/(.*)$': '/src/$1', + }, + setupFilesAfterEnv: ['/src/__tests__/setup.ts'], + testTimeout: 10000, + moduleFileExtensions: ['ts', 'tsx', 'js', 'jsx', 'json', 'node'], +}; diff --git a/package-lock.json b/package-lock.json index 05c9f107..b12df16f 100644 --- a/package-lock.json +++ b/package-lock.json @@ -43,12 +43,15 @@ "@types/amqplib": "^0.10.5", "@types/cors": "^2.8.17", "@types/express": "^4.17.21", + "@types/jest": "^29.5.11", "@types/node": "^20.11.5", "@types/node-cron": "^3.0.11", "@types/swagger-ui-express": "^4.1.6", "@types/ws": "^8.5.14", + "jest": "^29.7.0", "nodemon": "^3.0.3", "prettier": "^3.2.2", + "ts-jest": "^29.1.1", "ts-node": "^10.9.2", "typescript": "^5.3.3" } @@ -92,6 +95,600 @@ "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" }, + "node_modules/@babel/code-frame": { + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.29.0.tgz", + "integrity": "sha512-9NhCeYjq9+3uxgdtp20LSiJXJvN0FeCtNGpJxuMFZ1Kv3cWUNb6DOhJwUvcVCzKGR66cw4njwM6hrJLqgOwbcw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-validator-identifier": "^7.28.5", + "js-tokens": "^4.0.0", + "picocolors": "^1.1.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/compat-data": { + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.29.0.tgz", + "integrity": "sha512-T1NCJqT/j9+cn8fvkt7jtwbLBfLC/1y1c7NtCeXFRgzGTsafi68MRv8yzkYSapBnFA6L3U2VSc02ciDzoAJhJg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/core": { + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.29.0.tgz", + "integrity": "sha512-CGOfOJqWjg2qW/Mb6zNsDm+u5vFQ8DxXfbM09z69p5Z6+mE1ikP2jUXw+j42Pf1XTYED2Rni5f95npYeuwMDQA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.29.0", + "@babel/generator": "^7.29.0", + "@babel/helper-compilation-targets": "^7.28.6", + "@babel/helper-module-transforms": "^7.28.6", + "@babel/helpers": "^7.28.6", + "@babel/parser": "^7.29.0", + "@babel/template": "^7.28.6", + "@babel/traverse": "^7.29.0", + "@babel/types": "^7.29.0", + "@jridgewell/remapping": "^2.3.5", + "convert-source-map": "^2.0.0", + "debug": "^4.1.0", + "gensync": "^1.0.0-beta.2", + "json5": "^2.2.3", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/babel" + } + }, + "node_modules/@babel/core/node_modules/debug": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/@babel/core/node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@babel/core/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/@babel/generator": { + "version": "7.29.1", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.29.1.tgz", + "integrity": "sha512-qsaF+9Qcm2Qv8SRIMMscAvG4O3lJ0F1GuMo5HR/Bp02LopNgnZBC/EkbevHFeGs4ls/oPz9v+Bsmzbkbe+0dUw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.29.0", + "@babel/types": "^7.29.0", + "@jridgewell/gen-mapping": "^0.3.12", + "@jridgewell/trace-mapping": "^0.3.28", + "jsesc": "^3.0.2" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/generator/node_modules/@jridgewell/trace-mapping": { + "version": "0.3.31", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz", + "integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" + } + }, + "node_modules/@babel/helper-compilation-targets": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.28.6.tgz", + "integrity": "sha512-JYtls3hqi15fcx5GaSNL7SCTJ2MNmjrkHXg4FSpOA/grxK8KwyZ5bubHsCq8FXCkua6xhuaaBit+3b7+VZRfcA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/compat-data": "^7.28.6", + "@babel/helper-validator-option": "^7.27.1", + "browserslist": "^4.24.0", + "lru-cache": "^5.1.1", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-compilation-targets/node_modules/lru-cache": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", + "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", + "dev": true, + "license": "ISC", + "dependencies": { + "yallist": "^3.0.2" + } + }, + "node_modules/@babel/helper-compilation-targets/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/@babel/helper-compilation-targets/node_modules/yallist": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", + "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", + "dev": true, + "license": "ISC" + }, + "node_modules/@babel/helper-globals": { + "version": "7.28.0", + "resolved": "https://registry.npmjs.org/@babel/helper-globals/-/helper-globals-7.28.0.tgz", + "integrity": "sha512-+W6cISkXFa1jXsDEdYA8HeevQT/FULhxzR99pxphltZcVaugps53THCeiWA8SguxxpSp3gKPiuYfSWopkLQ4hw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-imports": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.28.6.tgz", + "integrity": "sha512-l5XkZK7r7wa9LucGw9LwZyyCUscb4x37JWTPz7swwFE/0FMQAGpiWUZn8u9DzkSBWEcK25jmvubfpw2dnAMdbw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/traverse": "^7.28.6", + "@babel/types": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-transforms": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.28.6.tgz", + "integrity": "sha512-67oXFAYr2cDLDVGLXTEABjdBJZ6drElUSI7WKp70NrpyISso3plG9SAGEF6y7zbha/wOzUByWWTJvEDVNIUGcA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-module-imports": "^7.28.6", + "@babel/helper-validator-identifier": "^7.28.5", + "@babel/traverse": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-plugin-utils": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.28.6.tgz", + "integrity": "sha512-S9gzZ/bz83GRysI7gAD4wPT/AI3uCnY+9xn+Mx/KPs2JwHJIz1W8PZkg2cqyt3RNOBM8ejcXhV6y8Og7ly/Dug==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-string-parser": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz", + "integrity": "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-identifier": { + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.28.5.tgz", + "integrity": "sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-option": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.27.1.tgz", + "integrity": "sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helpers": { + "version": "7.29.2", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.29.2.tgz", + "integrity": "sha512-HoGuUs4sCZNezVEKdVcwqmZN8GoHirLUcLaYVNBK2J0DadGtdcqgr3BCbvH8+XUo4NGjNl3VOtSjEKNzqfFgKw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/template": "^7.28.6", + "@babel/types": "^7.29.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/parser": { + "version": "7.29.2", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.29.2.tgz", + "integrity": "sha512-4GgRzy/+fsBa72/RZVJmGKPmZu9Byn8o4MoLpmNe1m8ZfYnz5emHLQz3U4gLud6Zwl0RZIcgiLD7Uq7ySFuDLA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.29.0" + }, + "bin": { + "parser": "bin/babel-parser.js" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@babel/plugin-syntax-async-generators": { + "version": "7.8.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-async-generators/-/plugin-syntax-async-generators-7.8.4.tgz", + "integrity": "sha512-tycmZxkGfZaxhMRbXlPXuVFpdWlXpir2W4AMhSJgRKzk/eDlIXOhb2LHWoLpDF7TEHylV5zNhykX6KAgHJmTNw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-bigint": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-bigint/-/plugin-syntax-bigint-7.8.3.tgz", + "integrity": "sha512-wnTnFlG+YxQm3vDxpGE57Pj0srRU4sHE/mDkt1qv2YJJSeUAec2ma4WLUnUPeKjyrfntVwe/N6dCXpU+zL3Npg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-class-properties": { + "version": "7.12.13", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-class-properties/-/plugin-syntax-class-properties-7.12.13.tgz", + "integrity": "sha512-fm4idjKla0YahUNgFNLCB0qySdsoPiZP3iQE3rky0mBUtMZ23yDJ9SJdg6dXTSDnulOVqiF3Hgr9nbXvXTQZYA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.12.13" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-class-static-block": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-class-static-block/-/plugin-syntax-class-static-block-7.14.5.tgz", + "integrity": "sha512-b+YyPmr6ldyNnM6sqYeMWE+bgJcJpO6yS4QD7ymxgH34GBPNDM/THBh8iunyvKIZztiwLH4CJZ0RxTk9emgpjw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.14.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-import-attributes": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-attributes/-/plugin-syntax-import-attributes-7.28.6.tgz", + "integrity": "sha512-jiLC0ma9XkQT3TKJ9uYvlakm66Pamywo+qwL+oL8HJOvc6TWdZXVfhqJr8CCzbSGUAbDOzlGHJC1U+vRfLQDvw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-import-meta": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-meta/-/plugin-syntax-import-meta-7.10.4.tgz", + "integrity": "sha512-Yqfm+XDx0+Prh3VSeEQCPU81yC+JWZ2pDPFSS4ZdpfZhp4MkFMaDC1UqseovEKwSUpnIL7+vK+Clp7bfh0iD7g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.10.4" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-json-strings": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-json-strings/-/plugin-syntax-json-strings-7.8.3.tgz", + "integrity": "sha512-lY6kdGpWHvjoe2vk4WrAapEuBR69EMxZl+RoGRhrFGNYVK8mOPAW8VfbT/ZgrFbXlDNiiaxQnAtgVCZ6jv30EA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-jsx": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.28.6.tgz", + "integrity": "sha512-wgEmr06G6sIpqr8YDwA2dSRTE3bJ+V0IfpzfSY3Lfgd7YWOaAdlykvJi13ZKBt8cZHfgH1IXN+CL656W3uUa4w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-logical-assignment-operators": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-logical-assignment-operators/-/plugin-syntax-logical-assignment-operators-7.10.4.tgz", + "integrity": "sha512-d8waShlpFDinQ5MtvGU9xDAOzKH47+FFoney2baFIoMr952hKOLp1HR7VszoZvOsV/4+RRszNY7D17ba0te0ig==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.10.4" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-nullish-coalescing-operator": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-nullish-coalescing-operator/-/plugin-syntax-nullish-coalescing-operator-7.8.3.tgz", + "integrity": "sha512-aSff4zPII1u2QD7y+F8oDsz19ew4IGEJg9SVW+bqwpwtfFleiQDMdzA/R+UlWDzfnHFCxxleFT0PMIrR36XLNQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-numeric-separator": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-numeric-separator/-/plugin-syntax-numeric-separator-7.10.4.tgz", + "integrity": "sha512-9H6YdfkcK/uOnY/K7/aA2xpzaAgkQn37yzWUMRK7OaPOqOpGS1+n0H5hxT9AUw9EsSjPW8SVyMJwYRtWs3X3ug==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.10.4" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-object-rest-spread": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-object-rest-spread/-/plugin-syntax-object-rest-spread-7.8.3.tgz", + "integrity": "sha512-XoqMijGZb9y3y2XskN+P1wUGiVwWZ5JmoDRwx5+3GmEplNyVM2s2Dg8ILFQm8rWM48orGy5YpI5Bl8U1y7ydlA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-optional-catch-binding": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-optional-catch-binding/-/plugin-syntax-optional-catch-binding-7.8.3.tgz", + "integrity": "sha512-6VPD0Pc1lpTqw0aKoeRTMiB+kWhAoT24PA+ksWSBrFtl5SIRVpZlwN3NNPQjehA2E/91FV3RjLWoVTglWcSV3Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-optional-chaining": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-optional-chaining/-/plugin-syntax-optional-chaining-7.8.3.tgz", + "integrity": "sha512-KoK9ErH1MBlCPxV0VANkXW2/dw4vlbGDrFgz8bmUsBGYkFRcbRwMh6cIJubdPrkxRwuGdtCk0v/wPTKbQgBjkg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-private-property-in-object": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-private-property-in-object/-/plugin-syntax-private-property-in-object-7.14.5.tgz", + "integrity": "sha512-0wVnp9dxJ72ZUJDV27ZfbSj6iHLoytYZmh3rFcxNnvsJF3ktkzLDZPy/mA17HGsaQT3/DQsWYX1f1QGWkCoVUg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.14.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-top-level-await": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-top-level-await/-/plugin-syntax-top-level-await-7.14.5.tgz", + "integrity": "sha512-hx++upLv5U1rgYfwe1xBQUhRmU41NEvpUvrp8jkrSCdvGSnM5/qdRMtylJ6PG5OFkBaHkbTAKTnd3/YyESRHFw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.14.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-typescript": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.28.6.tgz", + "integrity": "sha512-+nDNmQye7nlnuuHDboPbGm00Vqg3oO8niRRL27/4LYHUsHYh0zJ1xWOz0uRwNFmM1Avzk8wZbc6rdiYhomzv/A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/template": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.28.6.tgz", + "integrity": "sha512-YA6Ma2KsCdGb+WC6UpBVFJGXL58MDA6oyONbjyF/+5sBgxY/dwkhLogbMT2GXXyU84/IhRw/2D1Os1B/giz+BQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.28.6", + "@babel/parser": "^7.28.6", + "@babel/types": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/traverse": { + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.29.0.tgz", + "integrity": "sha512-4HPiQr0X7+waHfyXPZpWPfWL/J7dcN1mx9gL6WdQVMbPnF3+ZhSMs8tCxN7oHddJE9fhNE7+lxdnlyemKfJRuA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.29.0", + "@babel/generator": "^7.29.0", + "@babel/helper-globals": "^7.28.0", + "@babel/parser": "^7.29.0", + "@babel/template": "^7.28.6", + "@babel/types": "^7.29.0", + "debug": "^4.3.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/traverse/node_modules/debug": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/@babel/traverse/node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@babel/types": { + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.29.0.tgz", + "integrity": "sha512-LwdZHpScM4Qz8Xw2iKSzS+cfglZzJGvofQICy7W7v4caru4EaAmyUuO6BGrbyQ2mYV11W0U8j5mBhd14dd3B0A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-string-parser": "^7.27.1", + "@babel/helper-validator-identifier": "^7.28.5" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@bcoe/v8-coverage": { + "version": "0.2.3", + "resolved": "https://registry.npmjs.org/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz", + "integrity": "sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==", + "dev": true, + "license": "MIT" + }, "node_modules/@cspotcode/source-map-support": { "version": "0.8.1", "resolved": "https://registry.npmjs.org/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz", @@ -175,6 +772,523 @@ "node": ">=12" } }, + "node_modules/@istanbuljs/load-nyc-config": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@istanbuljs/load-nyc-config/-/load-nyc-config-1.1.0.tgz", + "integrity": "sha512-VjeHSlIzpv/NyD3N0YuHfXOPDIixcA1q2ZV98wsMqcYlPmv2n3Yb2lYP9XMElnaFVXg5A7YLTeLu6V84uQDjmQ==", + "dev": true, + "license": "ISC", + "dependencies": { + "camelcase": "^5.3.1", + "find-up": "^4.1.0", + "get-package-type": "^0.1.0", + "js-yaml": "^3.13.1", + "resolve-from": "^5.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@istanbuljs/load-nyc-config/node_modules/camelcase": { + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", + "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/@istanbuljs/load-nyc-config/node_modules/js-yaml": { + "version": "3.14.2", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.2.tgz", + "integrity": "sha512-PMSmkqxr106Xa156c2M265Z+FTrPl+oxd/rgOQy2tijQeK5TxQ43psO1ZCwhVOSdnn+RzkzlRz/eY4BgJBYVpg==", + "dev": true, + "license": "MIT", + "dependencies": { + "argparse": "^1.0.7", + "esprima": "^4.0.0" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/@istanbuljs/schema": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/@istanbuljs/schema/-/schema-0.1.3.tgz", + "integrity": "sha512-ZXRY4jNvVgSVQ8DL3LTcakaAtXwTVUxE81hslsyD2AtoXW/wVob10HkOJ1X/pAlcI7D+2YoZKg5do8G/w6RYgA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/@jest/console": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/console/-/console-29.7.0.tgz", + "integrity": "sha512-5Ni4CU7XHQi32IJ398EEP4RrB8eV09sXP2ROqD4bksHrnTree52PsxvX8tpL8LvTZ3pFzXyPbNQReSN41CAhOg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/types": "^29.6.3", + "@types/node": "*", + "chalk": "^4.0.0", + "jest-message-util": "^29.7.0", + "jest-util": "^29.7.0", + "slash": "^3.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/core": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/core/-/core-29.7.0.tgz", + "integrity": "sha512-n7aeXWKMnGtDA48y8TLWJPJmLmmZ642Ceo78cYWEpiD7FzDgmNDV/GCVRorPABdXLJZ/9wzzgZAlHjXjxDHGsg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/console": "^29.7.0", + "@jest/reporters": "^29.7.0", + "@jest/test-result": "^29.7.0", + "@jest/transform": "^29.7.0", + "@jest/types": "^29.6.3", + "@types/node": "*", + "ansi-escapes": "^4.2.1", + "chalk": "^4.0.0", + "ci-info": "^3.2.0", + "exit": "^0.1.2", + "graceful-fs": "^4.2.9", + "jest-changed-files": "^29.7.0", + "jest-config": "^29.7.0", + "jest-haste-map": "^29.7.0", + "jest-message-util": "^29.7.0", + "jest-regex-util": "^29.6.3", + "jest-resolve": "^29.7.0", + "jest-resolve-dependencies": "^29.7.0", + "jest-runner": "^29.7.0", + "jest-runtime": "^29.7.0", + "jest-snapshot": "^29.7.0", + "jest-util": "^29.7.0", + "jest-validate": "^29.7.0", + "jest-watcher": "^29.7.0", + "micromatch": "^4.0.4", + "pretty-format": "^29.7.0", + "slash": "^3.0.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + }, + "peerDependencies": { + "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" + }, + "peerDependenciesMeta": { + "node-notifier": { + "optional": true + } + } + }, + "node_modules/@jest/core/node_modules/ansi-escapes": { + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-4.3.2.tgz", + "integrity": "sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "type-fest": "^0.21.3" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@jest/core/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/@jest/core/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@jest/core/node_modules/type-fest": { + "version": "0.21.3", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.21.3.tgz", + "integrity": "sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w==", + "dev": true, + "license": "(MIT OR CC0-1.0)", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@jest/environment": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/environment/-/environment-29.7.0.tgz", + "integrity": "sha512-aQIfHDq33ExsN4jP1NWGXhxgQ/wixs60gDiKO+XVMd8Mn0NWPWgc34ZQDTb2jKaUWQ7MuwoitXAsN2XVXNMpAw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/fake-timers": "^29.7.0", + "@jest/types": "^29.6.3", + "@types/node": "*", + "jest-mock": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/expect": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/expect/-/expect-29.7.0.tgz", + "integrity": "sha512-8uMeAMycttpva3P1lBHB8VciS9V0XAr3GymPpipdyQXbBcuhkLQOSe8E/p92RyAdToS6ZD1tFkX+CkhoECE0dQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "expect": "^29.7.0", + "jest-snapshot": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/expect-utils": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/expect-utils/-/expect-utils-29.7.0.tgz", + "integrity": "sha512-GlsNBWiFQFCVi9QVSx7f5AgMeLxe9YCCs5PuP2O2LdjDAA8Jh9eX7lA1Jq/xdXw3Wb3hyvlFNfZIfcRetSzYcA==", + "dev": true, + "license": "MIT", + "dependencies": { + "jest-get-type": "^29.6.3" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/fake-timers": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/fake-timers/-/fake-timers-29.7.0.tgz", + "integrity": "sha512-q4DH1Ha4TTFPdxLsqDXK1d3+ioSL7yL5oCMJZgDYm6i+6CygW5E5xVr/D1HdsGxjt1ZWSfUAs9OxSB/BNelWrQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/types": "^29.6.3", + "@sinonjs/fake-timers": "^10.0.2", + "@types/node": "*", + "jest-message-util": "^29.7.0", + "jest-mock": "^29.7.0", + "jest-util": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/globals": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/globals/-/globals-29.7.0.tgz", + "integrity": "sha512-mpiz3dutLbkW2MNFubUGUEVLkTGiqW6yLVTA+JbP6fI6J5iL9Y0Nlg8k95pcF8ctKwCS7WVxteBs29hhfAotzQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/environment": "^29.7.0", + "@jest/expect": "^29.7.0", + "@jest/types": "^29.6.3", + "jest-mock": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/reporters": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/reporters/-/reporters-29.7.0.tgz", + "integrity": "sha512-DApq0KJbJOEzAFYjHADNNxAE3KbhxQB1y5Kplb5Waqw6zVbuWatSnMjE5gs8FUgEPmNsnZA3NCWl9NG0ia04Pg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@bcoe/v8-coverage": "^0.2.3", + "@jest/console": "^29.7.0", + "@jest/test-result": "^29.7.0", + "@jest/transform": "^29.7.0", + "@jest/types": "^29.6.3", + "@jridgewell/trace-mapping": "^0.3.18", + "@types/node": "*", + "chalk": "^4.0.0", + "collect-v8-coverage": "^1.0.0", + "exit": "^0.1.2", + "glob": "^7.1.3", + "graceful-fs": "^4.2.9", + "istanbul-lib-coverage": "^3.0.0", + "istanbul-lib-instrument": "^6.0.0", + "istanbul-lib-report": "^3.0.0", + "istanbul-lib-source-maps": "^4.0.0", + "istanbul-reports": "^3.1.3", + "jest-message-util": "^29.7.0", + "jest-util": "^29.7.0", + "jest-worker": "^29.7.0", + "slash": "^3.0.0", + "string-length": "^4.0.1", + "strip-ansi": "^6.0.0", + "v8-to-istanbul": "^9.0.1" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + }, + "peerDependencies": { + "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" + }, + "peerDependenciesMeta": { + "node-notifier": { + "optional": true + } + } + }, + "node_modules/@jest/reporters/node_modules/@jridgewell/trace-mapping": { + "version": "0.3.31", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz", + "integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" + } + }, + "node_modules/@jest/reporters/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/@jest/reporters/node_modules/glob": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "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", + "dev": true, + "license": "ISC", + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.1.1", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/@jest/reporters/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@jest/schemas": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-29.6.3.tgz", + "integrity": "sha512-mo5j5X+jIZmJQveBKeS/clAueipV7KgiX1vMgCxam1RNYiqE1w62n0/tJJnHtjW8ZHcQco5gY85jA3mi0L+nSA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@sinclair/typebox": "^0.27.8" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/source-map": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/@jest/source-map/-/source-map-29.6.3.tgz", + "integrity": "sha512-MHjT95QuipcPrpLM+8JMSzFx6eHp5Bm+4XeFDJlwsvVBjmKNiIAvasGK2fxz2WbGRlnvqehFbh07MMa7n3YJnw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/trace-mapping": "^0.3.18", + "callsites": "^3.0.0", + "graceful-fs": "^4.2.9" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/source-map/node_modules/@jridgewell/trace-mapping": { + "version": "0.3.31", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz", + "integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" + } + }, + "node_modules/@jest/test-result": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/test-result/-/test-result-29.7.0.tgz", + "integrity": "sha512-Fdx+tv6x1zlkJPcWXmMDAG2HBnaR9XPSd5aDWQVsfrZmLVT3lU1cwyxLgRmXR9yrq4NBoEm9BMsfgFzTQAbJYA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/console": "^29.7.0", + "@jest/types": "^29.6.3", + "@types/istanbul-lib-coverage": "^2.0.0", + "collect-v8-coverage": "^1.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/test-sequencer": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/test-sequencer/-/test-sequencer-29.7.0.tgz", + "integrity": "sha512-GQwJ5WZVrKnOJuiYiAF52UNUJXgTZx1NHjFSEB0qEMmSZKAkdMoIzw/Cj6x6NF4AvV23AUqDpFzQkN/eYCYTxw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/test-result": "^29.7.0", + "graceful-fs": "^4.2.9", + "jest-haste-map": "^29.7.0", + "slash": "^3.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/transform": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/transform/-/transform-29.7.0.tgz", + "integrity": "sha512-ok/BTPFzFKVMwO5eOHRrvnBVHdRy9IrsrW1GpMaQ9MCnilNLXQKmAX8s1YXDFaai9xJpac2ySzV0YeRRECr2Vw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/core": "^7.11.6", + "@jest/types": "^29.6.3", + "@jridgewell/trace-mapping": "^0.3.18", + "babel-plugin-istanbul": "^6.1.1", + "chalk": "^4.0.0", + "convert-source-map": "^2.0.0", + "fast-json-stable-stringify": "^2.1.0", + "graceful-fs": "^4.2.9", + "jest-haste-map": "^29.7.0", + "jest-regex-util": "^29.6.3", + "jest-util": "^29.7.0", + "micromatch": "^4.0.4", + "pirates": "^4.0.4", + "slash": "^3.0.0", + "write-file-atomic": "^4.0.2" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/transform/node_modules/@jridgewell/trace-mapping": { + "version": "0.3.31", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz", + "integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" + } + }, + "node_modules/@jest/types": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/@jest/types/-/types-29.6.3.tgz", + "integrity": "sha512-u3UPsIilWKOM3F9CXtrG8LEJmNxwoCQC/XVj4IKYXvvpx7QIi/Kg1LI5uDmDpKlac62NUtX7eLjRh+jVZcLOzw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/schemas": "^29.6.3", + "@types/istanbul-lib-coverage": "^2.0.0", + "@types/istanbul-reports": "^3.0.0", + "@types/node": "*", + "@types/yargs": "^17.0.8", + "chalk": "^4.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jridgewell/gen-mapping": { + "version": "0.3.13", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz", + "integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.0", + "@jridgewell/trace-mapping": "^0.3.24" + } + }, + "node_modules/@jridgewell/gen-mapping/node_modules/@jridgewell/trace-mapping": { + "version": "0.3.31", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz", + "integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" + } + }, + "node_modules/@jridgewell/remapping": { + "version": "2.3.5", + "resolved": "https://registry.npmjs.org/@jridgewell/remapping/-/remapping-2.3.5.tgz", + "integrity": "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.24" + } + }, + "node_modules/@jridgewell/remapping/node_modules/@jridgewell/trace-mapping": { + "version": "0.3.31", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz", + "integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" + } + }, "node_modules/@jridgewell/resolve-uri": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.1.tgz", @@ -185,10 +1299,11 @@ } }, "node_modules/@jridgewell/sourcemap-codec": { - "version": "1.4.15", - "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.15.tgz", - "integrity": "sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg==", - "devOptional": true + "version": "1.5.5", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", + "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==", + "devOptional": true, + "license": "MIT" }, "node_modules/@jridgewell/trace-mapping": { "version": "0.3.9", @@ -485,6 +1600,33 @@ "node": ">=14" } }, + "node_modules/@sinclair/typebox": { + "version": "0.27.10", + "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.27.10.tgz", + "integrity": "sha512-MTBk/3jGLNB2tVxv6uLlFh1iu64iYOQ2PbdOSK3NW8JZsmlaOh2q6sdtKowBhfw8QFLmYNzTW4/oK4uATIi6ZA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@sinonjs/commons": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-3.0.1.tgz", + "integrity": "sha512-K3mCHKQ9sVh8o1C9cxkwxaOmXoAMlDxC1mYyHrjqOWEcBjYr76t96zL2zlj5dUGZ3HSw240X1qgH3Mjf1yJWpQ==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "type-detect": "4.0.8" + } + }, + "node_modules/@sinonjs/fake-timers": { + "version": "10.3.0", + "resolved": "https://registry.npmjs.org/@sinonjs/fake-timers/-/fake-timers-10.3.0.tgz", + "integrity": "sha512-V4BG07kuYSUkTCSBHG8G8TNhM+F19jXFWnQtzj+we8DrkpSBCee9Z3Ms8yiGer/dlmhe35/Xdgyo3/0rQKg7YA==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "@sinonjs/commons": "^3.0.0" + } + }, "node_modules/@socket.io/component-emitter": { "version": "3.1.2", "resolved": "https://registry.npmjs.org/@socket.io/component-emitter/-/component-emitter-3.1.2.tgz", @@ -591,6 +1733,51 @@ "@types/node": "*" } }, + "node_modules/@types/babel__core": { + "version": "7.20.5", + "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.5.tgz", + "integrity": "sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.20.7", + "@babel/types": "^7.20.7", + "@types/babel__generator": "*", + "@types/babel__template": "*", + "@types/babel__traverse": "*" + } + }, + "node_modules/@types/babel__generator": { + "version": "7.27.0", + "resolved": "https://registry.npmjs.org/@types/babel__generator/-/babel__generator-7.27.0.tgz", + "integrity": "sha512-ufFd2Xi92OAVPYsy+P4n7/U7e68fex0+Ee8gSG9KX7eo084CWiQ4sdxktvdl0bOPupXtVJPY19zk6EwWqUQ8lg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.0.0" + } + }, + "node_modules/@types/babel__template": { + "version": "7.4.4", + "resolved": "https://registry.npmjs.org/@types/babel__template/-/babel__template-7.4.4.tgz", + "integrity": "sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.1.0", + "@babel/types": "^7.0.0" + } + }, + "node_modules/@types/babel__traverse": { + "version": "7.28.0", + "resolved": "https://registry.npmjs.org/@types/babel__traverse/-/babel__traverse-7.28.0.tgz", + "integrity": "sha512-8PvcXf70gTDZBgt9ptxJ8elBeBjcLOAcOtoO/mPJjtji1+CdGbHgm77om1GrsPxsiE+uXIpNSK64UYaIwQXd4Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.28.2" + } + }, "node_modules/@types/body-parser": { "version": "1.19.5", "resolved": "https://registry.npmjs.org/@types/body-parser/-/body-parser-1.19.5.tgz", @@ -638,11 +1825,59 @@ "@types/send": "*" } }, + "node_modules/@types/graceful-fs": { + "version": "4.1.9", + "resolved": "https://registry.npmjs.org/@types/graceful-fs/-/graceful-fs-4.1.9.tgz", + "integrity": "sha512-olP3sd1qOEe5dXTSaFvQG+02VdRXcdytWLAZsAq1PecU8uqQAhkrnbli7DagjtXKW/Bl7YJbUsa8MPcuc8LHEQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, "node_modules/@types/http-errors": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/@types/http-errors/-/http-errors-2.0.4.tgz", "integrity": "sha512-D0CFMMtydbJAegzOyHjtiKPLlvnm3iTZyZRSZoLq2mRhDdmLfIWOCYPfQJ4cu2erKghU++QvjcUjp/5h7hESpA==" }, + "node_modules/@types/istanbul-lib-coverage": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.6.tgz", + "integrity": "sha512-2QF/t/auWm0lsy8XtKVPG19v3sSOQlJe/YHZgfjb/KBBHOGSV+J2q/S671rcq9uTBrLAXmZpqJiaQbMT+zNU1w==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/istanbul-lib-report": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@types/istanbul-lib-report/-/istanbul-lib-report-3.0.3.tgz", + "integrity": "sha512-NQn7AHQnk/RSLOxrBbGyJM/aVQ+pjj5HCgasFxc0K/KhoATfQ/47AyUl15I2yBUpihjmas+a+VJBOqecrFH+uA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/istanbul-lib-coverage": "*" + } + }, + "node_modules/@types/istanbul-reports": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@types/istanbul-reports/-/istanbul-reports-3.0.4.tgz", + "integrity": "sha512-pk2B1NWalF9toCRu6gjBzR69syFjP4Od8WRAX+0mmf9lAjCRicLOWc+ZrxZHx/0XRjotgkF9t6iaMJ+aXcOdZQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/istanbul-lib-report": "*" + } + }, + "node_modules/@types/jest": { + "version": "29.5.14", + "resolved": "https://registry.npmjs.org/@types/jest/-/jest-29.5.14.tgz", + "integrity": "sha512-ZN+4sdnLUbo8EVvVc2ao0GFW6oVrQRPn4K2lglySj7APvSrgzxHiNNK99us4WDMi57xxA2yggblIAMNhXOotLQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "expect": "^29.0.0", + "pretty-format": "^29.0.0" + } + }, "node_modules/@types/mime": { "version": "1.3.5", "resolved": "https://registry.npmjs.org/@types/mime/-/mime-1.3.5.tgz", @@ -699,6 +1934,13 @@ "@types/node": "*" } }, + "node_modules/@types/stack-utils": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/@types/stack-utils/-/stack-utils-2.0.3.tgz", + "integrity": "sha512-9aEbYZ3TbYMznPdcdr3SmIrLXwC/AKZXQeCf9Pgao5CKb8CyHuEX5jzWPTkvregvhRJHcpRO6BFoGW9ycaOkYw==", + "dev": true, + "license": "MIT" + }, "node_modules/@types/swagger-ui-express": { "version": "4.1.6", "resolved": "https://registry.npmjs.org/@types/swagger-ui-express/-/swagger-ui-express-4.1.6.tgz", @@ -718,6 +1960,23 @@ "@types/node": "*" } }, + "node_modules/@types/yargs": { + "version": "17.0.35", + "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.35.tgz", + "integrity": "sha512-qUHkeCyQFxMXg79wQfTtfndEC+N9ZZg76HJftDJp+qH2tV7Gj4OJi7l+PiWwJ+pWtW8GwSmqsDj/oymhrTWXjg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/yargs-parser": "*" + } + }, + "node_modules/@types/yargs-parser": { + "version": "21.0.3", + "resolved": "https://registry.npmjs.org/@types/yargs-parser/-/yargs-parser-21.0.3.tgz", + "integrity": "sha512-I4q9QU9MQv4oEOz4tAHJtNz1cwuLxn2F3xcc2iV5WdqLPpUnj30aUuxt1mAxYTG+oe8CZMV/+6rU4S4gRDzqtQ==", + "dev": true, + "license": "MIT" + }, "node_modules/@types/zen-observable": { "version": "0.8.3", "resolved": "https://registry.npmjs.org/@types/zen-observable/-/zen-observable-0.8.3.tgz", @@ -992,6 +2251,132 @@ "proxy-from-env": "^1.1.0" } }, + "node_modules/babel-jest": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/babel-jest/-/babel-jest-29.7.0.tgz", + "integrity": "sha512-BrvGY3xZSwEcCzKvKsCi2GgHqDqsYkOP4/by5xCgIwGXQxIEh+8ew3gmrE1y7XRR6LHZIj6yLYnUi/mm2KXKBg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/transform": "^29.7.0", + "@types/babel__core": "^7.1.14", + "babel-plugin-istanbul": "^6.1.1", + "babel-preset-jest": "^29.6.3", + "chalk": "^4.0.0", + "graceful-fs": "^4.2.9", + "slash": "^3.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + }, + "peerDependencies": { + "@babel/core": "^7.8.0" + } + }, + "node_modules/babel-plugin-istanbul": { + "version": "6.1.1", + "resolved": "https://registry.npmjs.org/babel-plugin-istanbul/-/babel-plugin-istanbul-6.1.1.tgz", + "integrity": "sha512-Y1IQok9821cC9onCx5otgFfRm7Lm+I+wwxOx738M/WLPZ9Q42m4IG5W0FNX8WLL2gYMZo3JkuXIH2DOpWM+qwA==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "@babel/helper-plugin-utils": "^7.0.0", + "@istanbuljs/load-nyc-config": "^1.0.0", + "@istanbuljs/schema": "^0.1.2", + "istanbul-lib-instrument": "^5.0.4", + "test-exclude": "^6.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/babel-plugin-istanbul/node_modules/istanbul-lib-instrument": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-5.2.1.tgz", + "integrity": "sha512-pzqtp31nLv/XFOzXGuvhCb8qhjmTVo5vjVk19XE4CRlSWz0KoeJ3bw9XsA7nOp9YBf4qHjwBxkDzKcME/J29Yg==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "@babel/core": "^7.12.3", + "@babel/parser": "^7.14.7", + "@istanbuljs/schema": "^0.1.2", + "istanbul-lib-coverage": "^3.2.0", + "semver": "^6.3.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/babel-plugin-istanbul/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/babel-plugin-jest-hoist": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/babel-plugin-jest-hoist/-/babel-plugin-jest-hoist-29.6.3.tgz", + "integrity": "sha512-ESAc/RJvGTFEzRwOTT4+lNDk/GNHMkKbNzsvT0qKRfDyyYTskxB5rnU2njIDYVxXCBHHEI1c0YwHob3WaYujOg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/template": "^7.3.3", + "@babel/types": "^7.3.3", + "@types/babel__core": "^7.1.14", + "@types/babel__traverse": "^7.0.6" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/babel-preset-current-node-syntax": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/babel-preset-current-node-syntax/-/babel-preset-current-node-syntax-1.2.0.tgz", + "integrity": "sha512-E/VlAEzRrsLEb2+dv8yp3bo4scof3l9nR4lrld+Iy5NyVqgVYUJnDAmunkhPMisRI32Qc4iRiz425d8vM++2fg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/plugin-syntax-async-generators": "^7.8.4", + "@babel/plugin-syntax-bigint": "^7.8.3", + "@babel/plugin-syntax-class-properties": "^7.12.13", + "@babel/plugin-syntax-class-static-block": "^7.14.5", + "@babel/plugin-syntax-import-attributes": "^7.24.7", + "@babel/plugin-syntax-import-meta": "^7.10.4", + "@babel/plugin-syntax-json-strings": "^7.8.3", + "@babel/plugin-syntax-logical-assignment-operators": "^7.10.4", + "@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.3", + "@babel/plugin-syntax-numeric-separator": "^7.10.4", + "@babel/plugin-syntax-object-rest-spread": "^7.8.3", + "@babel/plugin-syntax-optional-catch-binding": "^7.8.3", + "@babel/plugin-syntax-optional-chaining": "^7.8.3", + "@babel/plugin-syntax-private-property-in-object": "^7.14.5", + "@babel/plugin-syntax-top-level-await": "^7.14.5" + }, + "peerDependencies": { + "@babel/core": "^7.0.0 || ^8.0.0-0" + } + }, + "node_modules/babel-preset-jest": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/babel-preset-jest/-/babel-preset-jest-29.6.3.tgz", + "integrity": "sha512-0B3bhxR6snWXJZtR/RliHTDPRgn1sNHOR0yVtq/IiQFyuOVjFS+wuio/R4gSNkyYmKmJB4wGZv2NZanmKmTnNA==", + "dev": true, + "license": "MIT", + "dependencies": { + "babel-plugin-jest-hoist": "^29.6.3", + "babel-preset-current-node-syntax": "^1.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, "node_modules/balanced-match": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", @@ -1025,6 +2410,19 @@ "node": "^4.5.0 || >= 5.9" } }, + "node_modules/baseline-browser-mapping": { + "version": "2.10.10", + "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.10.10.tgz", + "integrity": "sha512-sUoJ3IMxx4AyRqO4MLeHlnGDkyXRoUG0/AI9fjK+vS72ekpV0yWVY7O0BVjmBcRtkNcsAO2QDZ4tdKKGoI6YaQ==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "baseline-browser-mapping": "dist/cli.cjs" + }, + "engines": { + "node": ">=6.0.0" + } + }, "node_modules/bignumber.js": { "version": "9.0.0", "resolved": "https://registry.npmjs.org/bignumber.js/-/bignumber.js-9.0.0.tgz", @@ -1080,17 +2478,75 @@ } }, "node_modules/braces": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", - "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", + "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", "dev": true, + "license": "MIT", "dependencies": { - "fill-range": "^7.0.1" + "fill-range": "^7.1.1" }, "engines": { "node": ">=8" } }, + "node_modules/browserslist": { + "version": "4.28.1", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.28.1.tgz", + "integrity": "sha512-ZC5Bd0LgJXgwGqUknZY/vkUQ04r8NXnJZ3yYi4vDmSiZmC/pdSN0NbNRPxZpbtO4uAfDUAFffO8IZoM3Gj8IkA==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "baseline-browser-mapping": "^2.9.0", + "caniuse-lite": "^1.0.30001759", + "electron-to-chromium": "^1.5.263", + "node-releases": "^2.0.27", + "update-browserslist-db": "^1.2.0" + }, + "bin": { + "browserslist": "cli.js" + }, + "engines": { + "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" + } + }, + "node_modules/bs-logger": { + "version": "0.2.6", + "resolved": "https://registry.npmjs.org/bs-logger/-/bs-logger-0.2.6.tgz", + "integrity": "sha512-pd8DCoxmbgc7hyPKOvxtqNcjYoOsABPQdcCUjGp3d42VR2CX1ORhk2A87oqqu5R1kk+76nsxZupkmyd+MVtCog==", + "dev": true, + "license": "MIT", + "dependencies": { + "fast-json-stable-stringify": "2.x" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/bser": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/bser/-/bser-2.1.1.tgz", + "integrity": "sha512-gQxTNE/GAfIIrmHLUE3oJyp5FO6HRBfhjnw4/wMmA63ZGDJnWBmgY/lyQBpnDUkGmAhbSe39tx2d/iTOAfglwQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "node-int64": "^0.4.0" + } + }, "node_modules/buffer": { "version": "6.0.3", "resolved": "https://registry.npmjs.org/buffer/-/buffer-6.0.3.tgz", @@ -1190,6 +2646,16 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/callsites": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", + "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, "node_modules/camelcase": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-1.2.1.tgz", @@ -1198,6 +2664,27 @@ "node": ">=0.10.0" } }, + "node_modules/caniuse-lite": { + "version": "1.0.30001781", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001781.tgz", + "integrity": "sha512-RdwNCyMsNBftLjW6w01z8bKEvT6e/5tpPVEgtn22TiLGlstHOVecsX2KHFkD5e/vRnIE4EGzpuIODb3mtswtkw==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/caniuse-lite" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "CC-BY-4.0" + }, "node_modules/center-align": { "version": "0.1.3", "resolved": "https://registry.npmjs.org/center-align/-/center-align-0.1.3.tgz", @@ -1244,6 +2731,16 @@ "node": ">=8" } }, + "node_modules/char-regex": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/char-regex/-/char-regex-1.0.2.tgz", + "integrity": "sha512-kWWXztvZ5SBQV+eRgKFeh8q5sLuZY2+8WUIzlxWVTg+oGwY14qylx1KbKzHd8P6ZYkAg0xyIDU9JMHhyJMZ1jw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + } + }, "node_modules/chardet": { "version": "0.7.0", "resolved": "https://registry.npmjs.org/chardet/-/chardet-0.7.0.tgz", @@ -1276,6 +2773,29 @@ "fsevents": "~2.3.2" } }, + "node_modules/ci-info": { + "version": "3.9.0", + "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-3.9.0.tgz", + "integrity": "sha512-NIxF55hv4nSqQswkAeiOi1r83xy8JldOFDTWiug55KBu9Jnblncd2U6ViHmYgHf01TPZS77NJBhBMKdWj9HQMQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/sibiraj-s" + } + ], + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/cjs-module-lexer": { + "version": "1.4.3", + "resolved": "https://registry.npmjs.org/cjs-module-lexer/-/cjs-module-lexer-1.4.3.tgz", + "integrity": "sha512-9z8TZaGM1pfswYeXrUpzPrkx8UnWYdhJclsiYMm6x/w5+nN+8Tf/LnAgfLGQCm59qAOxU8WwHEq2vNwF6i4j+Q==", + "dev": true, + "license": "MIT" + }, "node_modules/cli-cursor": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-2.1.0.tgz", @@ -1466,6 +2986,24 @@ "url": "https://github.com/chalk/wrap-ansi?sponsor=1" } }, + "node_modules/co": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/co/-/co-4.6.0.tgz", + "integrity": "sha512-QVb0dM5HvG+uaxitm8wONl7jltx8dqhfU33DcqtOZcLSVIKSDDLDi7+0LbAKiyI8hD9u42m2YxXSkMGWThaecQ==", + "dev": true, + "license": "MIT", + "engines": { + "iojs": ">= 1.0.0", + "node": ">= 0.12.0" + } + }, + "node_modules/collect-v8-coverage": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/collect-v8-coverage/-/collect-v8-coverage-1.0.3.tgz", + "integrity": "sha512-1L5aqIkwPfiodaMgQunkF1zRhNqifHBmtbbbxcr6yVxxBnliw4TDOW6NxpO8DJLgJ16OT+Y4ztZqP6p/FtXnAw==", + "dev": true, + "license": "MIT" + }, "node_modules/color-convert": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", @@ -1556,6 +3094,13 @@ "node": ">= 0.6" } }, + "node_modules/convert-source-map": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", + "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", + "dev": true, + "license": "MIT" + }, "node_modules/cookie": { "version": "0.7.1", "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.1.tgz", @@ -1588,6 +3133,28 @@ "node": ">= 0.10" } }, + "node_modules/create-jest": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/create-jest/-/create-jest-29.7.0.tgz", + "integrity": "sha512-Adz2bdH0Vq3F53KEMJOoftQFutWCukm6J24wbPWRO4k1kMY7gS7ds/uoJkNuV8wDCtWWnuwGcJwpWcih+zEW1Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/types": "^29.6.3", + "chalk": "^4.0.0", + "exit": "^0.1.2", + "graceful-fs": "^4.2.9", + "jest-config": "^29.7.0", + "jest-util": "^29.7.0", + "prompts": "^2.0.1" + }, + "bin": { + "create-jest": "bin/create-jest.js" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, "node_modules/create-require": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/create-require/-/create-require-1.1.1.tgz", @@ -1642,6 +3209,21 @@ "node": ">=0.10.0" } }, + "node_modules/dedent": { + "version": "1.7.2", + "resolved": "https://registry.npmjs.org/dedent/-/dedent-1.7.2.tgz", + "integrity": "sha512-WzMx3mW98SN+zn3hgemf4OzdmyNhhhKz5Ay0pUfQiMQ3e1g+xmTJWp/pKdwKVXhdSkAEGIIzqeuWrL3mV/AXbA==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "babel-plugin-macros": "^3.1.0" + }, + "peerDependenciesMeta": { + "babel-plugin-macros": { + "optional": true + } + } + }, "node_modules/deepmerge": { "version": "4.3.1", "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.3.1.tgz", @@ -1712,6 +3294,16 @@ "npm": "1.2.8000 || >= 1.4.16" } }, + "node_modules/detect-newline": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/detect-newline/-/detect-newline-3.1.0.tgz", + "integrity": "sha512-TLz+x/vEXm/Y7P7wn1EJFNLxYpUD4TgMosxY6fAVJUnJMbupHBOncxyWUG9OpTaH9EBD7uFI5LfEgmMOc54DsA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, "node_modules/diff": { "version": "4.0.2", "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz", @@ -1720,6 +3312,16 @@ "node": ">=0.3.1" } }, + "node_modules/diff-sequences": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/diff-sequences/-/diff-sequences-29.6.3.tgz", + "integrity": "sha512-EjePK1srD3P08o2j4f0ExnylqRs5B9tJjcp9t1krH2qRi8CCdsYfwe9JgSLurFBWwq4uOlipzfk5fHNvwFKr8Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, "node_modules/dotenv": { "version": "16.3.2", "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.3.2.tgz", @@ -1810,6 +3412,26 @@ "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==" }, + "node_modules/electron-to-chromium": { + "version": "1.5.321", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.321.tgz", + "integrity": "sha512-L2C7Q279W2D/J4PLZLk7sebOILDSWos7bMsMNN06rK482umHUrh/3lM8G7IlHFOYip2oAg5nha1rCMxr/rs6ZQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/emittery": { + "version": "0.13.1", + "resolved": "https://registry.npmjs.org/emittery/-/emittery-0.13.1.tgz", + "integrity": "sha512-DeWwawk6r5yR9jFgnDKYt4sLS0LmHJJi3ZOnb5/JdbYwj3nW+FxQnHIjhBKz8YLC7oRNPVM9NQ47I3CVx34eqQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sindresorhus/emittery?sponsor=1" + } + }, "node_modules/emoji-regex": { "version": "9.2.2", "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", @@ -1906,6 +3528,16 @@ } } }, + "node_modules/error-ex": { + "version": "1.3.4", + "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.4.tgz", + "integrity": "sha512-sqQamAnR14VgCr1A618A3sGrygcpK+HEbenA/HiEAkkUwcZIIB/tgWqHFxWgOyDh4nB4JCRimh79dR5Ywc9MDQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-arrayish": "^0.2.1" + } + }, "node_modules/es-abstract": { "version": "1.22.3", "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.22.3.tgz", @@ -2063,9 +3695,10 @@ } }, "node_modules/escalade": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz", - "integrity": "sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==", + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", + "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", + "license": "MIT", "engines": { "node": ">=6" } @@ -2084,6 +3717,20 @@ "node": ">=0.8.0" } }, + "node_modules/esprima": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", + "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", + "dev": true, + "license": "BSD-2-Clause", + "bin": { + "esparse": "bin/esparse.js", + "esvalidate": "bin/esvalidate.js" + }, + "engines": { + "node": ">=4" + } + }, "node_modules/etag": { "version": "1.8.1", "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", @@ -2093,6 +3740,89 @@ "node": ">= 0.6" } }, + "node_modules/execa": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/execa/-/execa-5.1.1.tgz", + "integrity": "sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==", + "dev": true, + "license": "MIT", + "dependencies": { + "cross-spawn": "^7.0.3", + "get-stream": "^6.0.0", + "human-signals": "^2.1.0", + "is-stream": "^2.0.0", + "merge-stream": "^2.0.0", + "npm-run-path": "^4.0.1", + "onetime": "^5.1.2", + "signal-exit": "^3.0.3", + "strip-final-newline": "^2.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sindresorhus/execa?sponsor=1" + } + }, + "node_modules/execa/node_modules/mimic-fn": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", + "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/execa/node_modules/onetime": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz", + "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==", + "dev": true, + "license": "MIT", + "dependencies": { + "mimic-fn": "^2.1.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/execa/node_modules/signal-exit": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", + "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/exit": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/exit/-/exit-0.1.2.tgz", + "integrity": "sha512-Zk/eNKV2zbjpKzrsQ+n1G6poVbErQxJ0LBOJXaKZ1EViLzH+hrLu9cdXI4zw9dBQJslwBEpbQ2P1oS7nDxs6jQ==", + "dev": true, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/expect": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/expect/-/expect-29.7.0.tgz", + "integrity": "sha512-2Zks0hf1VLFYI1kbh0I5jP3KHHyCHpkfyHBzsSXRFgl/Bg9mWYfMW8oD+PdMPlEwy5HNsR9JutYy6pMeOh61nw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/expect-utils": "^29.7.0", + "jest-get-type": "^29.6.3", + "jest-matcher-utils": "^29.7.0", + "jest-message-util": "^29.7.0", + "jest-util": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, "node_modules/express": { "version": "4.21.2", "resolved": "https://registry.npmjs.org/express/-/express-4.21.2.tgz", @@ -2191,6 +3921,13 @@ "node": ">=4" } }, + "node_modules/fast-json-stable-stringify": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", + "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", + "dev": true, + "license": "MIT" + }, "node_modules/fast-jwt": { "version": "3.3.2", "resolved": "https://registry.npmjs.org/fast-jwt/-/fast-jwt-3.3.2.tgz", @@ -2229,6 +3966,16 @@ "node": ">= 4.9.1" } }, + "node_modules/fb-watchman": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/fb-watchman/-/fb-watchman-2.0.2.tgz", + "integrity": "sha512-p5161BqbuCaSnB8jIbzQHOlpgsPmK5rJVDfDKO91Axs5NC1uu3HRQm6wt9cd9/+GtQQIO53JdGXXoyDpTAsgYA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "bser": "2.1.1" + } + }, "node_modules/figures": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/figures/-/figures-2.0.0.tgz", @@ -2241,10 +3988,11 @@ } }, "node_modules/fill-range": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", - "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", + "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", "dev": true, + "license": "MIT", "dependencies": { "to-regex-range": "^5.0.1" }, @@ -2270,6 +4018,20 @@ "node": ">= 0.8" } }, + "node_modules/find-up": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", + "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", + "dev": true, + "license": "MIT", + "dependencies": { + "locate-path": "^5.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/follow-redirects": { "version": "1.15.6", "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.6.tgz", @@ -2424,6 +4186,16 @@ "is-property": "^1.0.2" } }, + "node_modules/gensync": { + "version": "1.0.0-beta.2", + "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", + "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, "node_modules/get-caller-file": { "version": "2.0.5", "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", @@ -2456,6 +4228,16 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/get-package-type": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/get-package-type/-/get-package-type-0.1.0.tgz", + "integrity": "sha512-pjzuKtY64GYfWizNAJ0fr9VqttZkNiK2iS430LtIHzjBEr6bX8Am2zm4sW4Ro5wjWW5cAlRL1qAMTcXbjNAO2Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8.0.0" + } + }, "node_modules/get-proto": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", @@ -2469,6 +4251,19 @@ "node": ">= 0.4" } }, + "node_modules/get-stream": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz", + "integrity": "sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/get-symbol-description": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/get-symbol-description/-/get-symbol-description-1.0.0.tgz", @@ -2682,6 +4477,13 @@ "node": ">=14" } }, + "node_modules/html-escaper": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/html-escaper/-/html-escaper-2.0.2.tgz", + "integrity": "sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==", + "dev": true, + "license": "MIT" + }, "node_modules/http-errors": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz", @@ -2697,6 +4499,16 @@ "node": ">= 0.8" } }, + "node_modules/human-signals": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-2.1.0.tgz", + "integrity": "sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=10.17.0" + } + }, "node_modules/iconv-lite": { "version": "0.4.24", "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", @@ -2733,6 +4545,36 @@ "integrity": "sha512-Ius2VYcGNk7T90CppJqcIkS5ooHUZyIQK+ClZfMfMNFEF9VSE73Fq+906u/CWu92x4gzZMWOwfFYckPObzdEbA==", "dev": true }, + "node_modules/import-local": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/import-local/-/import-local-3.2.0.tgz", + "integrity": "sha512-2SPlun1JUPWoM6t3F0dw0FkCF/jWY8kttcY4f599GLTSjh2OCuuhdTkJQsEcZzBqbXZGKMK2OqW1oZsjtf/gQA==", + "dev": true, + "license": "MIT", + "dependencies": { + "pkg-dir": "^4.2.0", + "resolve-cwd": "^3.0.0" + }, + "bin": { + "import-local-fixture": "fixtures/cli.js" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/imurmurhash": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", + "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.8.19" + } + }, "node_modules/inflight": { "version": "1.0.6", "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", @@ -2935,6 +4777,13 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/is-arrayish": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", + "integrity": "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==", + "dev": true, + "license": "MIT" + }, "node_modules/is-bigint": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/is-bigint/-/is-bigint-1.0.4.tgz", @@ -2989,6 +4838,22 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/is-core-module": { + "version": "2.16.1", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.16.1.tgz", + "integrity": "sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w==", + "dev": true, + "license": "MIT", + "dependencies": { + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/is-date-object": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.0.5.tgz", @@ -3020,6 +4885,16 @@ "node": ">=8" } }, + "node_modules/is-generator-fn": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-generator-fn/-/is-generator-fn-2.1.0.tgz", + "integrity": "sha512-cTIB4yPYL/Grw0EaSzASzg6bBy9gqCofvWN8okThAYIxKJZC+udlRAmGbM0XLeniEJSs8uEgHPGuHSe1XsOLSQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, "node_modules/is-glob": { "version": "4.0.3", "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", @@ -3056,6 +4931,7 @@ "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", "dev": true, + "license": "MIT", "engines": { "node": ">=0.12.0" } @@ -3113,6 +4989,19 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/is-stream": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", + "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/is-string": { "version": "1.0.7", "resolved": "https://registry.npmjs.org/is-string/-/is-string-1.0.7.tgz", @@ -3187,6 +5076,125 @@ "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==" }, + "node_modules/istanbul-lib-coverage": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-3.2.2.tgz", + "integrity": "sha512-O8dpsF+r0WV/8MNRKfnmrtCWhuKjxrq2w+jpzBL5UZKTi2LeVWnWOmWRxFlesJONmc+wLAGvKQZEOanko0LFTg==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=8" + } + }, + "node_modules/istanbul-lib-instrument": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-6.0.3.tgz", + "integrity": "sha512-Vtgk7L/R2JHyyGW07spoFlB8/lpjiOLTjMdms6AFMraYt3BaJauod/NGrfnVG/y4Ix1JEuMRPDPEj2ua+zz1/Q==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "@babel/core": "^7.23.9", + "@babel/parser": "^7.23.9", + "@istanbuljs/schema": "^0.1.3", + "istanbul-lib-coverage": "^3.2.0", + "semver": "^7.5.4" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/istanbul-lib-report": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/istanbul-lib-report/-/istanbul-lib-report-3.0.1.tgz", + "integrity": "sha512-GCfE1mtsHGOELCU8e/Z7YWzpmybrx/+dSTfLrvY8qRmaY6zXTKWn6WQIjaAFw069icm6GVMNkgu0NzI4iPZUNw==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "istanbul-lib-coverage": "^3.0.0", + "make-dir": "^4.0.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/istanbul-lib-report/node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/istanbul-lib-report/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/istanbul-lib-source-maps": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/istanbul-lib-source-maps/-/istanbul-lib-source-maps-4.0.1.tgz", + "integrity": "sha512-n3s8EwkdFIJCG3BPKBYvskgXGoy88ARzvegkitk60NxRdwltLOTaH7CUiMRXvwYorl0Q712iEjcWB+fK/MrWVw==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "debug": "^4.1.1", + "istanbul-lib-coverage": "^3.0.0", + "source-map": "^0.6.1" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/istanbul-lib-source-maps/node_modules/debug": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/istanbul-lib-source-maps/node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "dev": true, + "license": "MIT" + }, + "node_modules/istanbul-reports": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-3.2.0.tgz", + "integrity": "sha512-HGYWWS/ehqTV3xN10i23tkPkpH46MLCIMFNCaaKNavAXTF1RkqxawEPtnjnGZ6XKSInBKkiOA5BKS+aZiY3AvA==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "html-escaper": "^2.0.0", + "istanbul-lib-report": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/iterare": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/iterare/-/iterare-1.2.1.tgz", @@ -3233,6 +5241,683 @@ "@pkgjs/parseargs": "^0.11.0" } }, + "node_modules/jest": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest/-/jest-29.7.0.tgz", + "integrity": "sha512-NIy3oAFp9shda19hy4HK0HRTWKtPJmGdnvywu01nOqNC2vZg+Z+fvJDxpMQA88eb2I9EcafcdjYgsDthnYTvGw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/core": "^29.7.0", + "@jest/types": "^29.6.3", + "import-local": "^3.0.2", + "jest-cli": "^29.7.0" + }, + "bin": { + "jest": "bin/jest.js" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + }, + "peerDependencies": { + "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" + }, + "peerDependenciesMeta": { + "node-notifier": { + "optional": true + } + } + }, + "node_modules/jest-changed-files": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-changed-files/-/jest-changed-files-29.7.0.tgz", + "integrity": "sha512-fEArFiwf1BpQ+4bXSprcDc3/x4HSzL4al2tozwVpDFpsxALjLYdyiIK4e5Vz66GQJIbXJ82+35PtysofptNX2w==", + "dev": true, + "license": "MIT", + "dependencies": { + "execa": "^5.0.0", + "jest-util": "^29.7.0", + "p-limit": "^3.1.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-circus": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-circus/-/jest-circus-29.7.0.tgz", + "integrity": "sha512-3E1nCMgipcTkCocFwM90XXQab9bS+GMsjdpmPrlelaxwD93Ad8iVEjX/vvHPdLPnFf+L40u+5+iutRdA1N9myw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/environment": "^29.7.0", + "@jest/expect": "^29.7.0", + "@jest/test-result": "^29.7.0", + "@jest/types": "^29.6.3", + "@types/node": "*", + "chalk": "^4.0.0", + "co": "^4.6.0", + "dedent": "^1.0.0", + "is-generator-fn": "^2.0.0", + "jest-each": "^29.7.0", + "jest-matcher-utils": "^29.7.0", + "jest-message-util": "^29.7.0", + "jest-runtime": "^29.7.0", + "jest-snapshot": "^29.7.0", + "jest-util": "^29.7.0", + "p-limit": "^3.1.0", + "pretty-format": "^29.7.0", + "pure-rand": "^6.0.0", + "slash": "^3.0.0", + "stack-utils": "^2.0.3" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-cli": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-cli/-/jest-cli-29.7.0.tgz", + "integrity": "sha512-OVVobw2IubN/GSYsxETi+gOe7Ka59EFMR/twOU3Jb2GnKKeMGJB5SGUUrEz3SFVmJASUdZUzy83sLNNQ2gZslg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/core": "^29.7.0", + "@jest/test-result": "^29.7.0", + "@jest/types": "^29.6.3", + "chalk": "^4.0.0", + "create-jest": "^29.7.0", + "exit": "^0.1.2", + "import-local": "^3.0.2", + "jest-config": "^29.7.0", + "jest-util": "^29.7.0", + "jest-validate": "^29.7.0", + "yargs": "^17.3.1" + }, + "bin": { + "jest": "bin/jest.js" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + }, + "peerDependencies": { + "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" + }, + "peerDependenciesMeta": { + "node-notifier": { + "optional": true + } + } + }, + "node_modules/jest-config": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-config/-/jest-config-29.7.0.tgz", + "integrity": "sha512-uXbpfeQ7R6TZBqI3/TxCU4q4ttk3u0PJeC+E0zbfSoSjq6bJ7buBPxzQPL0ifrkY4DNu4JUdk0ImlBUYi840eQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/core": "^7.11.6", + "@jest/test-sequencer": "^29.7.0", + "@jest/types": "^29.6.3", + "babel-jest": "^29.7.0", + "chalk": "^4.0.0", + "ci-info": "^3.2.0", + "deepmerge": "^4.2.2", + "glob": "^7.1.3", + "graceful-fs": "^4.2.9", + "jest-circus": "^29.7.0", + "jest-environment-node": "^29.7.0", + "jest-get-type": "^29.6.3", + "jest-regex-util": "^29.6.3", + "jest-resolve": "^29.7.0", + "jest-runner": "^29.7.0", + "jest-util": "^29.7.0", + "jest-validate": "^29.7.0", + "micromatch": "^4.0.4", + "parse-json": "^5.2.0", + "pretty-format": "^29.7.0", + "slash": "^3.0.0", + "strip-json-comments": "^3.1.1" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + }, + "peerDependencies": { + "@types/node": "*", + "ts-node": ">=9.0.0" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "ts-node": { + "optional": true + } + } + }, + "node_modules/jest-config/node_modules/glob": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "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", + "dev": true, + "license": "ISC", + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.1.1", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/jest-diff": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-29.7.0.tgz", + "integrity": "sha512-LMIgiIrhigmPrs03JHpxUh2yISK3vLFPkAodPeo0+BuF7wA2FoQbkEg1u8gBYBThncu7e1oEDUfIXVuTqLRUjw==", + "dev": true, + "license": "MIT", + "dependencies": { + "chalk": "^4.0.0", + "diff-sequences": "^29.6.3", + "jest-get-type": "^29.6.3", + "pretty-format": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-docblock": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-docblock/-/jest-docblock-29.7.0.tgz", + "integrity": "sha512-q617Auw3A612guyaFgsbFeYpNP5t2aoUNLwBUbc/0kD1R4t9ixDbyFTHd1nok4epoVFpr7PmeWHrhvuV3XaJ4g==", + "dev": true, + "license": "MIT", + "dependencies": { + "detect-newline": "^3.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-each": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-each/-/jest-each-29.7.0.tgz", + "integrity": "sha512-gns+Er14+ZrEoC5fhOfYCY1LOHHr0TI+rQUHZS8Ttw2l7gl+80eHc/gFf2Ktkw0+SIACDTeWvpFcv3B04VembQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/types": "^29.6.3", + "chalk": "^4.0.0", + "jest-get-type": "^29.6.3", + "jest-util": "^29.7.0", + "pretty-format": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-environment-node": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-environment-node/-/jest-environment-node-29.7.0.tgz", + "integrity": "sha512-DOSwCRqXirTOyheM+4d5YZOrWcdu0LNZ87ewUoywbcb2XR4wKgqiG8vNeYwhjFMbEkfju7wx2GYH0P2gevGvFw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/environment": "^29.7.0", + "@jest/fake-timers": "^29.7.0", + "@jest/types": "^29.6.3", + "@types/node": "*", + "jest-mock": "^29.7.0", + "jest-util": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-get-type": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/jest-get-type/-/jest-get-type-29.6.3.tgz", + "integrity": "sha512-zrteXnqYxfQh7l5FHyL38jL39di8H8rHoecLH3JNxH3BwOrBsNeabdap5e0I23lD4HHI8W5VFBZqG4Eaq5LNcw==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-haste-map": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-haste-map/-/jest-haste-map-29.7.0.tgz", + "integrity": "sha512-fP8u2pyfqx0K1rGn1R9pyE0/KTn+G7PxktWidOBTqFPLYX0b9ksaMFkhK5vrS3DVun09pckLdlx90QthlW7AmA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/types": "^29.6.3", + "@types/graceful-fs": "^4.1.3", + "@types/node": "*", + "anymatch": "^3.0.3", + "fb-watchman": "^2.0.0", + "graceful-fs": "^4.2.9", + "jest-regex-util": "^29.6.3", + "jest-util": "^29.7.0", + "jest-worker": "^29.7.0", + "micromatch": "^4.0.4", + "walker": "^1.0.8" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + }, + "optionalDependencies": { + "fsevents": "^2.3.2" + } + }, + "node_modules/jest-leak-detector": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-leak-detector/-/jest-leak-detector-29.7.0.tgz", + "integrity": "sha512-kYA8IJcSYtST2BY9I+SMC32nDpBT3J2NvWJx8+JCuCdl/CR1I4EKUJROiP8XtCcxqgTTBGJNdbB1A8XRKbTetw==", + "dev": true, + "license": "MIT", + "dependencies": { + "jest-get-type": "^29.6.3", + "pretty-format": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-matcher-utils": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-matcher-utils/-/jest-matcher-utils-29.7.0.tgz", + "integrity": "sha512-sBkD+Xi9DtcChsI3L3u0+N0opgPYnCRPtGcQYrgXmR+hmt/fYfWAL0xRXYU8eWOdfuLgBe0YCW3AFtnRLagq/g==", + "dev": true, + "license": "MIT", + "dependencies": { + "chalk": "^4.0.0", + "jest-diff": "^29.7.0", + "jest-get-type": "^29.6.3", + "pretty-format": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-message-util": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-message-util/-/jest-message-util-29.7.0.tgz", + "integrity": "sha512-GBEV4GRADeP+qtB2+6u61stea8mGcOT4mCtrYISZwfu9/ISHFJ/5zOMXYbpBE9RsS5+Gb63DW4FgmnKJ79Kf6w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.12.13", + "@jest/types": "^29.6.3", + "@types/stack-utils": "^2.0.0", + "chalk": "^4.0.0", + "graceful-fs": "^4.2.9", + "micromatch": "^4.0.4", + "pretty-format": "^29.7.0", + "slash": "^3.0.0", + "stack-utils": "^2.0.3" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-mock": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-mock/-/jest-mock-29.7.0.tgz", + "integrity": "sha512-ITOMZn+UkYS4ZFh83xYAOzWStloNzJFO2s8DWrE4lhtGD+AorgnbkiKERe4wQVBydIGPx059g6riW5Btp6Llnw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/types": "^29.6.3", + "@types/node": "*", + "jest-util": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-pnp-resolver": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/jest-pnp-resolver/-/jest-pnp-resolver-1.2.3.tgz", + "integrity": "sha512-+3NpwQEnRoIBtx4fyhblQDPgJI0H1IEIkX7ShLUjPGA7TtUTvI1oiKi3SR4oBR0hQhQR80l4WAe5RrXBwWMA8w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + }, + "peerDependencies": { + "jest-resolve": "*" + }, + "peerDependenciesMeta": { + "jest-resolve": { + "optional": true + } + } + }, + "node_modules/jest-regex-util": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/jest-regex-util/-/jest-regex-util-29.6.3.tgz", + "integrity": "sha512-KJJBsRCyyLNWCNBOvZyRDnAIfUiRJ8v+hOBQYGn8gDyF3UegwiP4gwRR3/SDa42g1YbVycTidUF3rKjyLFDWbg==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-resolve": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-resolve/-/jest-resolve-29.7.0.tgz", + "integrity": "sha512-IOVhZSrg+UvVAshDSDtHyFCCBUl/Q3AAJv8iZ6ZjnZ74xzvwuzLXid9IIIPgTnY62SJjfuupMKZsZQRsCvxEgA==", + "dev": true, + "license": "MIT", + "dependencies": { + "chalk": "^4.0.0", + "graceful-fs": "^4.2.9", + "jest-haste-map": "^29.7.0", + "jest-pnp-resolver": "^1.2.2", + "jest-util": "^29.7.0", + "jest-validate": "^29.7.0", + "resolve": "^1.20.0", + "resolve.exports": "^2.0.0", + "slash": "^3.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-resolve-dependencies": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-resolve-dependencies/-/jest-resolve-dependencies-29.7.0.tgz", + "integrity": "sha512-un0zD/6qxJ+S0et7WxeI3H5XSe9lTBBR7bOHCHXkKR6luG5mwDDlIzVQ0V5cZCuoTgEdcdwzTghYkTWfubi+nA==", + "dev": true, + "license": "MIT", + "dependencies": { + "jest-regex-util": "^29.6.3", + "jest-snapshot": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-runner": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-runner/-/jest-runner-29.7.0.tgz", + "integrity": "sha512-fsc4N6cPCAahybGBfTRcq5wFR6fpLznMg47sY5aDpsoejOcVYFb07AHuSnR0liMcPTgBsA3ZJL6kFOjPdoNipQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/console": "^29.7.0", + "@jest/environment": "^29.7.0", + "@jest/test-result": "^29.7.0", + "@jest/transform": "^29.7.0", + "@jest/types": "^29.6.3", + "@types/node": "*", + "chalk": "^4.0.0", + "emittery": "^0.13.1", + "graceful-fs": "^4.2.9", + "jest-docblock": "^29.7.0", + "jest-environment-node": "^29.7.0", + "jest-haste-map": "^29.7.0", + "jest-leak-detector": "^29.7.0", + "jest-message-util": "^29.7.0", + "jest-resolve": "^29.7.0", + "jest-runtime": "^29.7.0", + "jest-util": "^29.7.0", + "jest-watcher": "^29.7.0", + "jest-worker": "^29.7.0", + "p-limit": "^3.1.0", + "source-map-support": "0.5.13" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-runner/node_modules/source-map-support": { + "version": "0.5.13", + "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.13.tgz", + "integrity": "sha512-SHSKFHadjVA5oR4PPqhtAVdcBWwRYVd6g6cAXnIbRiIwc2EhPrTuKUBdSLvlEKyIP3GCf89fltvcZiP9MMFA1w==", + "dev": true, + "license": "MIT", + "dependencies": { + "buffer-from": "^1.0.0", + "source-map": "^0.6.0" + } + }, + "node_modules/jest-runtime": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-runtime/-/jest-runtime-29.7.0.tgz", + "integrity": "sha512-gUnLjgwdGqW7B4LvOIkbKs9WGbn+QLqRQQ9juC6HndeDiezIwhDP+mhMwHWCEcfQ5RUXa6OPnFF8BJh5xegwwQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/environment": "^29.7.0", + "@jest/fake-timers": "^29.7.0", + "@jest/globals": "^29.7.0", + "@jest/source-map": "^29.6.3", + "@jest/test-result": "^29.7.0", + "@jest/transform": "^29.7.0", + "@jest/types": "^29.6.3", + "@types/node": "*", + "chalk": "^4.0.0", + "cjs-module-lexer": "^1.0.0", + "collect-v8-coverage": "^1.0.0", + "glob": "^7.1.3", + "graceful-fs": "^4.2.9", + "jest-haste-map": "^29.7.0", + "jest-message-util": "^29.7.0", + "jest-mock": "^29.7.0", + "jest-regex-util": "^29.6.3", + "jest-resolve": "^29.7.0", + "jest-snapshot": "^29.7.0", + "jest-util": "^29.7.0", + "slash": "^3.0.0", + "strip-bom": "^4.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-runtime/node_modules/glob": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "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", + "dev": true, + "license": "ISC", + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.1.1", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/jest-snapshot": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-snapshot/-/jest-snapshot-29.7.0.tgz", + "integrity": "sha512-Rm0BMWtxBcioHr1/OX5YCP8Uov4riHvKPknOGs804Zg9JGZgmIBkbtlxJC/7Z4msKYVbIJtfU+tKb8xlYNfdkw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/core": "^7.11.6", + "@babel/generator": "^7.7.2", + "@babel/plugin-syntax-jsx": "^7.7.2", + "@babel/plugin-syntax-typescript": "^7.7.2", + "@babel/types": "^7.3.3", + "@jest/expect-utils": "^29.7.0", + "@jest/transform": "^29.7.0", + "@jest/types": "^29.6.3", + "babel-preset-current-node-syntax": "^1.0.0", + "chalk": "^4.0.0", + "expect": "^29.7.0", + "graceful-fs": "^4.2.9", + "jest-diff": "^29.7.0", + "jest-get-type": "^29.6.3", + "jest-matcher-utils": "^29.7.0", + "jest-message-util": "^29.7.0", + "jest-util": "^29.7.0", + "natural-compare": "^1.4.0", + "pretty-format": "^29.7.0", + "semver": "^7.5.3" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-util": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-29.7.0.tgz", + "integrity": "sha512-z6EbKajIpqGKU56y5KBUgy1dt1ihhQJgWzUlZHArA/+X2ad7Cb5iF+AK1EWVL/Bo7Rz9uurpqw6SiBCefUbCGA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/types": "^29.6.3", + "@types/node": "*", + "chalk": "^4.0.0", + "ci-info": "^3.2.0", + "graceful-fs": "^4.2.9", + "picomatch": "^2.2.3" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-validate": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-validate/-/jest-validate-29.7.0.tgz", + "integrity": "sha512-ZB7wHqaRGVw/9hST/OuFUReG7M8vKeq0/J2egIGLdvjHCmYqGARhzXmtgi+gVeZ5uXFF219aOc3Ls2yLg27tkw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/types": "^29.6.3", + "camelcase": "^6.2.0", + "chalk": "^4.0.0", + "jest-get-type": "^29.6.3", + "leven": "^3.1.0", + "pretty-format": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-validate/node_modules/camelcase": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.3.0.tgz", + "integrity": "sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/jest-watcher": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-watcher/-/jest-watcher-29.7.0.tgz", + "integrity": "sha512-49Fg7WXkU3Vl2h6LbLtMQ/HyB6rXSIX7SqvBLQmssRBGN9I0PNvPmAmCWSOY6SOvrjhI/F7/bGAv9RtnsPA03g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/test-result": "^29.7.0", + "@jest/types": "^29.6.3", + "@types/node": "*", + "ansi-escapes": "^4.2.1", + "chalk": "^4.0.0", + "emittery": "^0.13.1", + "jest-util": "^29.7.0", + "string-length": "^4.0.1" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-watcher/node_modules/ansi-escapes": { + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-4.3.2.tgz", + "integrity": "sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "type-fest": "^0.21.3" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/jest-watcher/node_modules/type-fest": { + "version": "0.21.3", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.21.3.tgz", + "integrity": "sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w==", + "dev": true, + "license": "(MIT OR CC0-1.0)", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/jest-worker": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-29.7.0.tgz", + "integrity": "sha512-eIz2msL/EzL9UFTFFx7jBTkeZfku0yUAyZZZmJ93H2TYEiroIx2PQjEXcwYtYl8zXCxb+PAmA2hLIt/6ZEkPHw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*", + "jest-util": "^29.7.0", + "merge-stream": "^2.0.0", + "supports-color": "^8.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-worker/node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/jest-worker/node_modules/supports-color": { + "version": "8.1.1", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", + "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/supports-color?sponsor=1" + } + }, "node_modules/js-beautify": { "version": "1.14.11", "resolved": "https://registry.npmjs.org/js-beautify/-/js-beautify-1.14.11.tgz", @@ -3295,6 +5980,39 @@ "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==" }, + "node_modules/jsesc": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.1.0.tgz", + "integrity": "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==", + "dev": true, + "license": "MIT", + "bin": { + "jsesc": "bin/jsesc" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/json-parse-even-better-errors": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz", + "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==", + "dev": true, + "license": "MIT" + }, + "node_modules/json5": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", + "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", + "dev": true, + "license": "MIT", + "bin": { + "json5": "lib/cli.js" + }, + "engines": { + "node": ">=6" + } + }, "node_modules/jsonfile": { "version": "6.1.0", "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.1.0.tgz", @@ -3362,6 +6080,16 @@ "node": ">=0.10.0" } }, + "node_modules/kleur": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/kleur/-/kleur-3.0.3.tgz", + "integrity": "sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, "node_modules/lazy-cache": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/lazy-cache/-/lazy-cache-1.0.4.tgz", @@ -3370,6 +6098,36 @@ "node": ">=0.10.0" } }, + "node_modules/leven": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/leven/-/leven-3.1.0.tgz", + "integrity": "sha512-qsda+H8jTaUaN/x5vzW2rzc+8Rw4TAQ/4KjB46IwK5VH+IlVeeeje/EoZRpiXvIqjFgK84QffqPztGI3VBLG1A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/lines-and-columns": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz", + "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==", + "dev": true, + "license": "MIT" + }, + "node_modules/locate-path": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", + "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-locate": "^4.1.0" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/lodash": { "version": "4.17.21", "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", @@ -3405,6 +6163,13 @@ "resolved": "https://registry.npmjs.org/lodash.isstring/-/lodash.isstring-4.0.1.tgz", "integrity": "sha512-0wJxfxH1wgO3GrbuP+dTTk7op+6L41QCXbGINEmD+ny/G/eCqGzxyCsh7159S+mgDDcoarnBw6PC1PS5+wUGgw==" }, + "node_modules/lodash.memoize": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/lodash.memoize/-/lodash.memoize-4.1.2.tgz", + "integrity": "sha512-t7j+NzmgnQzTAYXcsHYLgimltOV1MXHtlOWf6GjL9Kj8GK5FInw5JotxvbOs+IvV1/Dzo04/fCGfLVs7aXb4Ag==", + "dev": true, + "license": "MIT" + }, "node_modules/lodash.once": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/lodash.once/-/lodash.once-4.1.1.tgz", @@ -3442,11 +6207,37 @@ "node": ">=16.14" } }, + "node_modules/make-dir": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-4.0.0.tgz", + "integrity": "sha512-hXdUTZYIVOt1Ex//jAQi+wTZZpUpwBj/0QsOzqegb3rGMMeJiSEu5xLHnYfBrRV4RH2+OCSOO95Is/7x1WJ4bw==", + "dev": true, + "license": "MIT", + "dependencies": { + "semver": "^7.5.3" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/make-error": { "version": "1.3.6", "resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz", "integrity": "sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==" }, + "node_modules/makeerror": { + "version": "1.0.12", + "resolved": "https://registry.npmjs.org/makeerror/-/makeerror-1.0.12.tgz", + "integrity": "sha512-JmqCvUhmt43madlpFzG4BQzG2Z3m6tvQDNKdClZnO3VbIudJYmxsT0FNJMeiB2+JTSlTQTSbU8QdesVmwJcmLg==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "tmpl": "1.0.5" + } + }, "node_modules/math-intrinsics": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", @@ -3487,6 +6278,13 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/merge-stream": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", + "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==", + "dev": true, + "license": "MIT" + }, "node_modules/methods": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz", @@ -3495,6 +6293,20 @@ "node": ">= 0.6" } }, + "node_modules/micromatch": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz", + "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==", + "dev": true, + "license": "MIT", + "dependencies": { + "braces": "^3.0.3", + "picomatch": "^2.3.1" + }, + "engines": { + "node": ">=8.6" + } + }, "node_modules/mime": { "version": "1.6.0", "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", @@ -3719,6 +6531,13 @@ "node": ">=12" } }, + "node_modules/natural-compare": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", + "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", + "dev": true, + "license": "MIT" + }, "node_modules/negotiator": { "version": "0.6.3", "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz", @@ -3771,6 +6590,20 @@ } } }, + "node_modules/node-int64": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/node-int64/-/node-int64-0.4.0.tgz", + "integrity": "sha512-O5lz91xSOeoXP6DulyHfllpq+Eg00MWitZIbtPfoSEvqIHdl5gfcY6hYzDWnj0qD5tz52PI08u9qUvSVeUBeHw==", + "dev": true, + "license": "MIT" + }, + "node_modules/node-releases": { + "version": "2.0.36", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.36.tgz", + "integrity": "sha512-TdC8FSgHz8Mwtw9g5L4gR/Sh9XhSP/0DEkQxfEFXOpiul5IiHgHan2VhYYb6agDSfp4KuvltmGApc8HMgUrIkA==", + "dev": true, + "license": "MIT" + }, "node_modules/node-xlsx": { "version": "0.24.0", "resolved": "https://registry.npmjs.org/node-xlsx/-/node-xlsx-0.24.0.tgz", @@ -3860,6 +6693,19 @@ "node": ">=0.10.0" } }, + "node_modules/npm-run-path": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-4.0.1.tgz", + "integrity": "sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==", + "dev": true, + "license": "MIT", + "dependencies": { + "path-key": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/object-assign": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", @@ -3970,6 +6816,80 @@ "node": ">=0.10.0" } }, + "node_modules/p-limit": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", + "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "yocto-queue": "^0.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-locate": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", + "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-limit": "^2.2.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/p-locate/node_modules/p-limit": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", + "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-try": "^2.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-try": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", + "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/parse-json": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz", + "integrity": "sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.0.0", + "error-ex": "^1.3.1", + "json-parse-even-better-errors": "^2.3.0", + "lines-and-columns": "^1.1.6" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/parse5": { "version": "5.1.1", "resolved": "https://registry.npmjs.org/parse5/-/parse5-5.1.1.tgz", @@ -3997,6 +6917,16 @@ "node": ">= 0.8" } }, + "node_modules/path-exists": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, "node_modules/path-is-absolute": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", @@ -4013,6 +6943,13 @@ "node": ">=8" } }, + "node_modules/path-parse": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", + "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", + "dev": true, + "license": "MIT" + }, "node_modules/path-scurry": { "version": "1.10.1", "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.10.1.tgz", @@ -4042,6 +6979,13 @@ "integrity": "sha512-RA1GjUVMnvYFxuqovrEqZoxxW5NUZqbwKtYz/Tt7nXerk0LbLblQmrsgdeOxV5SFHf0UDggjS/bSeOZwt1pmEQ==", "license": "MIT" }, + "node_modules/picocolors": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", + "dev": true, + "license": "ISC" + }, "node_modules/picomatch": { "version": "2.3.1", "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", @@ -4054,6 +6998,29 @@ "url": "https://github.com/sponsors/jonschlinkert" } }, + "node_modules/pirates": { + "version": "4.0.7", + "resolved": "https://registry.npmjs.org/pirates/-/pirates-4.0.7.tgz", + "integrity": "sha512-TfySrs/5nm8fQJDcBDuUng3VOUKsd7S+zqvbOTiGXHfxX4wK31ard+hoNuvkicM/2YFzlpDgABOevKSsB4G/FA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 6" + } + }, + "node_modules/pkg-dir": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-4.2.0.tgz", + "integrity": "sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "find-up": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/prettier": { "version": "3.2.4", "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.2.4.tgz", @@ -4069,6 +7036,34 @@ "url": "https://github.com/prettier/prettier?sponsor=1" } }, + "node_modules/pretty-format": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-29.7.0.tgz", + "integrity": "sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/schemas": "^29.6.3", + "ansi-styles": "^5.0.0", + "react-is": "^18.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/pretty-format/node_modules/ansi-styles": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", + "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, "node_modules/process-nextick-args": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", @@ -4094,6 +7089,20 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/prompts": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/prompts/-/prompts-2.4.2.tgz", + "integrity": "sha512-NxNv/kLguCA7p3jE8oL2aEBsrJWgAakBpgmgK6lpPWV+WuOmY6r2/zbAVnP+T8bQlA0nzHXSJSJW0Hq7ylaD2Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "kleur": "^3.0.3", + "sisteransi": "^1.0.5" + }, + "engines": { + "node": ">= 6" + } + }, "node_modules/proto-list": { "version": "1.2.4", "resolved": "https://registry.npmjs.org/proto-list/-/proto-list-1.2.4.tgz", @@ -4122,6 +7131,23 @@ "integrity": "sha512-77DZwxQmxKnu3aR542U+X8FypNzbfJ+C5XQDk3uWjWxn6151aIMGthWYRXTqT1E5oJvg+ljaa2OJi+VfvCOQ8w==", "dev": true }, + "node_modules/pure-rand": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/pure-rand/-/pure-rand-6.1.0.tgz", + "integrity": "sha512-bVWawvoZoBYpp6yIoQtQXHZjmz35RSVHnUOTefl8Vcjr8snTPY1wnpSPMWekcFwbxI6gtmT7rSYPFvz71ldiOA==", + "dev": true, + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/dubzzz" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fast-check" + } + ], + "license": "MIT" + }, "node_modules/qs": { "version": "6.11.0", "resolved": "https://registry.npmjs.org/qs/-/qs-6.11.0.tgz", @@ -4173,6 +7199,13 @@ "node": ">= 0.8" } }, + "node_modules/react-is": { + "version": "18.3.1", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz", + "integrity": "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==", + "dev": true, + "license": "MIT" + }, "node_modules/readable-stream": { "version": "2.3.7", "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", @@ -4301,6 +7334,60 @@ "resolved": "https://registry.npmjs.org/requires-port/-/requires-port-1.0.0.tgz", "integrity": "sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ==" }, + "node_modules/resolve": { + "version": "1.22.11", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.11.tgz", + "integrity": "sha512-RfqAvLnMl313r7c9oclB1HhUEAezcpLjz95wFH4LVuhk9JF/r22qmVP9AMmOU4vMX7Q8pN8jwNg/CSpdFnMjTQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-core-module": "^2.16.1", + "path-parse": "^1.0.7", + "supports-preserve-symlinks-flag": "^1.0.0" + }, + "bin": { + "resolve": "bin/resolve" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/resolve-cwd": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/resolve-cwd/-/resolve-cwd-3.0.0.tgz", + "integrity": "sha512-OrZaX2Mb+rJCpH/6CpSqt9xFVpN++x01XnN2ie9g6P5/3xelLAkXWVADpdz1IHD/KFfEXyE6V0U01OQ3UO2rEg==", + "dev": true, + "license": "MIT", + "dependencies": { + "resolve-from": "^5.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/resolve-from": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz", + "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/resolve.exports": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/resolve.exports/-/resolve.exports-2.0.3.tgz", + "integrity": "sha512-OcXjMsGdhL4XnbShKpAcSqPMzQoYkYyhbEaeSko47MjRP9NfEQMhZkXL1DoFlt9LWQn4YttrdnV6X2OiyzBi+A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + } + }, "node_modules/restore-cursor": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-2.0.0.tgz", @@ -4656,6 +7743,23 @@ "node": ">=10" } }, + "node_modules/sisteransi": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/sisteransi/-/sisteransi-1.0.5.tgz", + "integrity": "sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg==", + "dev": true, + "license": "MIT" + }, + "node_modules/slash": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", + "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, "node_modules/socket.io": { "version": "4.8.1", "resolved": "https://registry.npmjs.org/socket.io/-/socket.io-4.8.1.tgz", @@ -4817,6 +7921,29 @@ "node": ">= 0.6" } }, + "node_modules/stack-utils": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/stack-utils/-/stack-utils-2.0.6.tgz", + "integrity": "sha512-XlkWvfIm6RmsWtNJx+uqtKLS8eqFbxUg0ZzLXqY0caEy9l7hruX8IpiDnjsLavoBgqCCR71TqWO8MaXYheJ3RQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "escape-string-regexp": "^2.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/stack-utils/node_modules/escape-string-regexp": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-2.0.0.tgz", + "integrity": "sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, "node_modules/statuses": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", @@ -4857,6 +7984,43 @@ "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" }, + "node_modules/string-length": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/string-length/-/string-length-4.0.2.tgz", + "integrity": "sha512-+l6rNN5fYHNhZZy41RXsYptCjA2Igmq4EG7kZAYFQI1E1VTXarr6ZPXBg6eq7Y6eK4FEhY6AJlyuFIb/v/S0VQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "char-regex": "^1.0.2", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/string-length/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/string-length/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/string-width": { "version": "5.1.2", "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz", @@ -4987,6 +8151,39 @@ "node": ">=8" } }, + "node_modules/strip-bom": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-4.0.0.tgz", + "integrity": "sha512-3xurFv5tEgii33Zi8Jtp55wEIILR9eh34FAW00PZf+JnSsTmV/ioewSgQl97JHvgjoRGwPShsWm+IdrxB35d0w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-final-newline": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-2.0.0.tgz", + "integrity": "sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/strip-json-comments": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", + "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/supports-color": { "version": "5.5.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", @@ -4998,6 +8195,19 @@ "node": ">=4" } }, + "node_modules/supports-preserve-symlinks-flag": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", + "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/swagger-ui-dist": { "version": "5.11.0", "resolved": "https://registry.npmjs.org/swagger-ui-dist/-/swagger-ui-dist-5.11.0.tgz", @@ -5087,6 +8297,43 @@ "window-size": "0.1.0" } }, + "node_modules/test-exclude": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/test-exclude/-/test-exclude-6.0.0.tgz", + "integrity": "sha512-cAGWPIyOHU6zlmg88jwm7VRyXnMN7iV68OGAbYDk/Mh/xC/pzVPlQtY6ngoIH/5/tciuhGfvESU8GrHrcxD56w==", + "dev": true, + "license": "ISC", + "dependencies": { + "@istanbuljs/schema": "^0.1.2", + "glob": "^7.1.4", + "minimatch": "^3.0.4" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/test-exclude/node_modules/glob": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "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", + "dev": true, + "license": "ISC", + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.1.1", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, "node_modules/thenify": { "version": "3.3.1", "resolved": "https://registry.npmjs.org/thenify/-/thenify-3.3.1.tgz", @@ -5122,11 +8369,19 @@ "node": ">=0.6.0" } }, + "node_modules/tmpl": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/tmpl/-/tmpl-1.0.5.tgz", + "integrity": "sha512-3f0uOEAQwIqGuWW2MVzYg8fV/QNnc/IpuJNG837rLuczAaLVHslWHZQj4IGiEl5Hs3kkbhwL9Ab7Hrsmuj+Smw==", + "dev": true, + "license": "BSD-3-Clause" + }, "node_modules/to-regex-range": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", "dev": true, + "license": "MIT", "dependencies": { "is-number": "^7.0.0" }, @@ -5160,6 +8415,72 @@ "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==", "peer": true }, + "node_modules/ts-jest": { + "version": "29.4.6", + "resolved": "https://registry.npmjs.org/ts-jest/-/ts-jest-29.4.6.tgz", + "integrity": "sha512-fSpWtOO/1AjSNQguk43hb/JCo16oJDnMJf3CdEGNkqsEX3t0KX96xvyX1D7PfLCpVoKu4MfVrqUkFyblYoY4lA==", + "dev": true, + "license": "MIT", + "dependencies": { + "bs-logger": "^0.2.6", + "fast-json-stable-stringify": "^2.1.0", + "handlebars": "^4.7.8", + "json5": "^2.2.3", + "lodash.memoize": "^4.1.2", + "make-error": "^1.3.6", + "semver": "^7.7.3", + "type-fest": "^4.41.0", + "yargs-parser": "^21.1.1" + }, + "bin": { + "ts-jest": "cli.js" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || ^18.0.0 || >=20.0.0" + }, + "peerDependencies": { + "@babel/core": ">=7.0.0-beta.0 <8", + "@jest/transform": "^29.0.0 || ^30.0.0", + "@jest/types": "^29.0.0 || ^30.0.0", + "babel-jest": "^29.0.0 || ^30.0.0", + "jest": "^29.0.0 || ^30.0.0", + "jest-util": "^29.0.0 || ^30.0.0", + "typescript": ">=4.3 <6" + }, + "peerDependenciesMeta": { + "@babel/core": { + "optional": true + }, + "@jest/transform": { + "optional": true + }, + "@jest/types": { + "optional": true + }, + "babel-jest": { + "optional": true + }, + "esbuild": { + "optional": true + }, + "jest-util": { + "optional": true + } + } + }, + "node_modules/ts-jest/node_modules/semver": { + "version": "7.7.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz", + "integrity": "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/ts-node": { "version": "10.9.2", "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-10.9.2.tgz", @@ -5224,6 +8545,29 @@ "yarn": ">=1.9.4" } }, + "node_modules/type-detect": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.0.8.tgz", + "integrity": "sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/type-fest": { + "version": "4.41.0", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-4.41.0.tgz", + "integrity": "sha512-TeTSQ6H5YHvpqVwBRcnLDCBnDOHWYu7IvGbHT6N8AOymcr9PJGjc1GTtiWZTYg0NCgYwvnYWEkVChQAr9bjfwA==", + "dev": true, + "license": "(MIT OR CC0-1.0)", + "engines": { + "node": ">=16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/type-is": { "version": "1.6.18", "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz", @@ -5829,6 +9173,37 @@ "node": ">= 0.8" } }, + "node_modules/update-browserslist-db": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.2.3.tgz", + "integrity": "sha512-Js0m9cx+qOgDxo0eMiFGEueWztz+d4+M3rGlmKPT+T4IS/jP4ylw3Nwpu6cpTTP8R1MAC1kF4VbdLt3ARf209w==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "escalade": "^3.2.0", + "picocolors": "^1.1.1" + }, + "bin": { + "update-browserslist-db": "cli.js" + }, + "peerDependencies": { + "browserslist": ">= 4.21.0" + } + }, "node_modules/url-parse": { "version": "1.5.10", "resolved": "https://registry.npmjs.org/url-parse/-/url-parse-1.5.10.tgz", @@ -5869,6 +9244,32 @@ "integrity": "sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg==", "devOptional": true }, + "node_modules/v8-to-istanbul": { + "version": "9.3.0", + "resolved": "https://registry.npmjs.org/v8-to-istanbul/-/v8-to-istanbul-9.3.0.tgz", + "integrity": "sha512-kiGUalWN+rgBJ/1OHZsBtU4rXZOfj/7rKQxULKlIzwzQSvMJUUNgPwJEEh7gU6xEVxC0ahoOBvN2YI8GH6FNgA==", + "dev": true, + "license": "ISC", + "dependencies": { + "@jridgewell/trace-mapping": "^0.3.12", + "@types/istanbul-lib-coverage": "^2.0.1", + "convert-source-map": "^2.0.0" + }, + "engines": { + "node": ">=10.12.0" + } + }, + "node_modules/v8-to-istanbul/node_modules/@jridgewell/trace-mapping": { + "version": "0.3.31", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz", + "integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" + } + }, "node_modules/validator": { "version": "13.11.0", "resolved": "https://registry.npmjs.org/validator/-/validator-13.11.0.tgz", @@ -5885,6 +9286,16 @@ "node": ">= 0.8" } }, + "node_modules/walker": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/walker/-/walker-1.0.8.tgz", + "integrity": "sha512-ts/8E8l5b7kY0vlWLewOkDXMmPdLcVV4GmOQLyxuSswIJsweeFZtAsMF7k1Nszz+TYBQrlYRmzOnr398y1JemQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "makeerror": "1.0.12" + } + }, "node_modules/warning": { "version": "4.0.3", "resolved": "https://registry.npmjs.org/warning/-/warning-4.0.3.tgz", @@ -6055,6 +9466,27 @@ "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==" }, + "node_modules/write-file-atomic": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-4.0.2.tgz", + "integrity": "sha512-7KxauUdBmSdWnmpaGFg+ppNjKF8uNLry8LyzjauQDOVONfFLNKrKvQOxZ/VuTIcS/gge/YNahf5RIIQWTSarlg==", + "dev": true, + "license": "ISC", + "dependencies": { + "imurmurhash": "^0.1.4", + "signal-exit": "^3.0.7" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || >=16.0.0" + } + }, + "node_modules/write-file-atomic/node_modules/signal-exit": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", + "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", + "dev": true, + "license": "ISC" + }, "node_modules/ws": { "version": "8.18.1", "resolved": "https://registry.npmjs.org/ws/-/ws-8.18.1.tgz", @@ -6230,6 +9662,19 @@ "node": ">=6" } }, + "node_modules/yocto-queue": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", + "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/zen-observable": { "version": "0.8.15", "resolved": "https://registry.npmjs.org/zen-observable/-/zen-observable-0.8.15.tgz", diff --git a/package.json b/package.json index 7fb9c551..ac8d5ca7 100644 --- a/package.json +++ b/package.json @@ -10,7 +10,10 @@ "format": "prettier --write .", "build": "tsoa spec-and-routes && tsc", "migration:generate": "typeorm-ts-node-commonjs migration:generate -d src/database/data-source.ts", - "migration:run": "typeorm-ts-node-commonjs migration:run -d src/database/data-source.ts" + "migration:run": "typeorm-ts-node-commonjs migration:run -d src/database/data-source.ts", + "test": "jest", + "test:watch": "jest --watch", + "test:coverage": "jest --coverage" }, "keywords": [], "author": "", @@ -19,12 +22,15 @@ "@types/amqplib": "^0.10.5", "@types/cors": "^2.8.17", "@types/express": "^4.17.21", + "@types/jest": "^29.5.11", "@types/node": "^20.11.5", "@types/node-cron": "^3.0.11", "@types/swagger-ui-express": "^4.1.6", "@types/ws": "^8.5.14", + "jest": "^29.7.0", "nodemon": "^3.0.3", "prettier": "^3.2.2", + "ts-jest": "^29.1.1", "ts-node": "^10.9.2", "typescript": "^5.3.3" }, diff --git a/src/__tests__/setup.ts b/src/__tests__/setup.ts new file mode 100644 index 00000000..04ef89ef --- /dev/null +++ b/src/__tests__/setup.ts @@ -0,0 +1,17 @@ +// Test setup file for Jest +// Mock environment variables +process.env.NODE_ENV = 'test'; +process.env.DB_HOST = 'localhost'; +process.env.DB_PORT = '3306'; +process.env.DB_USERNAME = 'test'; +process.env.DB_PASSWORD = 'test'; +process.env.DB_DATABASE = 'test_db'; + +// Mock console methods to reduce noise in tests +global.console = { + ...console, + error: jest.fn(), + warn: jest.fn(), + info: jest.fn(), + log: jest.fn(), +}; diff --git a/src/__tests__/unit/OrgMapping.spec.ts b/src/__tests__/unit/OrgMapping.spec.ts new file mode 100644 index 00000000..c59e5697 --- /dev/null +++ b/src/__tests__/unit/OrgMapping.spec.ts @@ -0,0 +1,54 @@ +/** + * Unit tests for move-draft-to-current helper functions + */ + +import { OrgIdMapping, AllOrgMappings } from '../../interfaces/OrgMapping'; + +// Mock dependencies +jest.mock('../../database/data-source', () => ({ + AppDataSource: { + createQueryRunner: jest.fn(), + }, +})); + +describe('OrgMapping Interfaces', () => { + describe('OrgIdMapping', () => { + it('should create a valid OrgIdMapping', () => { + const mapping: OrgIdMapping = { + byAncestorDNA: new Map(), + byDraftId: new Map(), + }; + + expect(mapping.byAncestorDNA).toBeInstanceOf(Map); + expect(mapping.byDraftId).toBeInstanceOf(Map); + }); + + it('should store and retrieve values correctly', () => { + const mapping: OrgIdMapping = { + byAncestorDNA: new Map([['dna1', 'id1']]), + byDraftId: new Map([['draftId1', 'currentId1']]), + }; + + expect(mapping.byAncestorDNA.get('dna1')).toBe('id1'); + expect(mapping.byDraftId.get('draftId1')).toBe('currentId1'); + }); + }); + + describe('AllOrgMappings', () => { + it('should create a valid AllOrgMappings', () => { + const mappings: AllOrgMappings = { + orgRoot: { byAncestorDNA: new Map(), byDraftId: new Map() }, + orgChild1: { byAncestorDNA: new Map(), byDraftId: new Map() }, + orgChild2: { byAncestorDNA: new Map(), byDraftId: new Map() }, + orgChild3: { byAncestorDNA: new Map(), byDraftId: new Map() }, + orgChild4: { byAncestorDNA: new Map(), byDraftId: new Map() }, + }; + + expect(mappings.orgRoot).toBeDefined(); + expect(mappings.orgChild1).toBeDefined(); + expect(mappings.orgChild2).toBeDefined(); + expect(mappings.orgChild3).toBeDefined(); + expect(mappings.orgChild4).toBeDefined(); + }); + }); +}); diff --git a/src/__tests__/unit/OrganizationController.spec.ts b/src/__tests__/unit/OrganizationController.spec.ts new file mode 100644 index 00000000..615298bc --- /dev/null +++ b/src/__tests__/unit/OrganizationController.spec.ts @@ -0,0 +1,460 @@ +/** + * Unit tests for OrganizationController move-draft-to-current helper functions + */ + +import { OrgIdMapping } from '../../interfaces/OrgMapping'; + +// Mock typeorm +jest.mock('typeorm', () => ({ + Entity: jest.fn(), + Column: jest.fn(), + ManyToOne: jest.fn(), + JoinColumn: jest.fn(), + OneToMany: jest.fn(), + In: jest.fn((val: any) => val), + Like: jest.fn((val: any) => val), + IsNull: jest.fn(), + Not: jest.fn(), +})); + +// Mock entities +jest.mock('../../entities/OrgRoot', () => ({ OrgRoot: {} })); +jest.mock('../../entities/OrgChild1', () => ({ OrgChild1: {} })); +jest.mock('../../entities/OrgChild2', () => ({ OrgChild2: {} })); +jest.mock('../../entities/OrgChild3', () => ({ OrgChild3: {} })); +jest.mock('../../entities/OrgChild4', () => ({ OrgChild4: {} })); +jest.mock('../../entities/PosMaster', () => ({ PosMaster: {} })); +jest.mock('../../entities/Position', () => ({ Position: {} })); + +// Import after mocking +import { In, Like } from 'typeorm'; +import { OrgRoot } from '../../entities/OrgRoot'; +import { OrgChild1 } from '../../entities/OrgChild1'; +import { OrgChild2 } from '../../entities/OrgChild2'; +import { OrgChild3 } from '../../entities/OrgChild3'; +import { OrgChild4 } from '../../entities/OrgChild4'; +import { PosMaster } from '../../entities/PosMaster'; +import { Position } from '../../entities/Position'; + +describe('OrganizationController - Helper Functions', () => { + let mockQueryRunner: any; + let mockController: any; + + beforeEach(() => { + // Mock queryRunner + mockQueryRunner = { + manager: { + find: jest.fn(), + delete: jest.fn(), + update: jest.fn(), + create: jest.fn(), + save: jest.fn(), + }, + }; + + // Import the controller class (we'll need to mock the private methods) + // Since we're testing private methods, we'll create a test class + }); + + afterEach(() => { + jest.clearAllMocks(); + }); + + describe('resolveOrgId()', () => { + it('should return null when draftId is null', () => { + const mapping: OrgIdMapping = { + byAncestorDNA: new Map(), + byDraftId: new Map(), + }; + + // Simulate the function logic + const resolveOrgId = (draftId: string | null, mapping: OrgIdMapping): string | null => { + if (!draftId) return null; + return mapping.byDraftId.get(draftId) ?? null; + }; + + expect(resolveOrgId(null, mapping)).toBeNull(); + }); + + it('should return null when draftId is undefined', () => { + const mapping: OrgIdMapping = { + byAncestorDNA: new Map(), + byDraftId: new Map(), + }; + + const resolveOrgId = (draftId: string | null | undefined, mapping: OrgIdMapping): string | null => { + if (!draftId) return null; + return mapping.byDraftId.get(draftId) ?? null; + }; + + expect(resolveOrgId(undefined, mapping)).toBeNull(); + }); + + it('should return mapped ID when draftId exists in mapping', () => { + const mapping: OrgIdMapping = { + byAncestorDNA: new Map(), + byDraftId: new Map([['draft1', 'current1']]), + }; + + const resolveOrgId = (draftId: string | null, mapping: OrgIdMapping): string | null => { + if (!draftId) return null; + return mapping.byDraftId.get(draftId) ?? null; + }; + + expect(resolveOrgId('draft1', mapping)).toBe('current1'); + }); + + it('should return null when draftId does not exist in mapping', () => { + const mapping: OrgIdMapping = { + byAncestorDNA: new Map(), + byDraftId: new Map(), + }; + + const resolveOrgId = (draftId: string | null, mapping: OrgIdMapping): string | null => { + if (!draftId) return null; + return mapping.byDraftId.get(draftId) ?? null; + }; + + expect(resolveOrgId('nonexistent', mapping)).toBeNull(); + }); + }); + + describe('cascadeDeletePositions()', () => { + it('should delete positions with orgRootId when entityClass is OrgRoot', async () => { + const node = { + id: 'node1', + orgRevisionId: 'rev1', + }; + + await mockQueryRunner.manager.delete(PosMaster, { + orgRevisionId: 'rev1', + orgRootId: 'node1', + }); + + expect(mockQueryRunner.manager.delete).toHaveBeenCalledWith(PosMaster, { + orgRevisionId: 'rev1', + orgRootId: 'node1', + }); + }); + + it('should delete positions with orgChild1Id when entityClass is OrgChild1', async () => { + const node = { + id: 'node1', + orgRevisionId: 'rev1', + }; + + await mockQueryRunner.manager.delete(PosMaster, { + orgRevisionId: 'rev1', + orgChild1Id: 'node1', + }); + + expect(mockQueryRunner.manager.delete).toHaveBeenCalledWith(PosMaster, { + orgRevisionId: 'rev1', + orgChild1Id: 'node1', + }); + }); + + it('should delete positions with orgChild2Id when entityClass is OrgChild2', async () => { + const node = { + id: 'node1', + orgRevisionId: 'rev1', + }; + + await mockQueryRunner.manager.delete(PosMaster, { + orgRevisionId: 'rev1', + orgChild2Id: 'node1', + }); + + expect(mockQueryRunner.manager.delete).toHaveBeenCalledWith(PosMaster, { + orgRevisionId: 'rev1', + orgChild2Id: 'node1', + }); + }); + + it('should delete positions with orgChild3Id when entityClass is OrgChild3', async () => { + const node = { + id: 'node1', + orgRevisionId: 'rev1', + }; + + await mockQueryRunner.manager.delete(PosMaster, { + orgRevisionId: 'rev1', + orgChild3Id: 'node1', + }); + + expect(mockQueryRunner.manager.delete).toHaveBeenCalledWith(PosMaster, { + orgRevisionId: 'rev1', + orgChild3Id: 'node1', + }); + }); + + it('should delete positions with orgChild4Id when entityClass is OrgChild4', async () => { + const node = { + id: 'node1', + orgRevisionId: 'rev1', + }; + + await mockQueryRunner.manager.delete(PosMaster, { + orgRevisionId: 'rev1', + orgChild4Id: 'node1', + }); + + expect(mockQueryRunner.manager.delete).toHaveBeenCalledWith(PosMaster, { + orgRevisionId: 'rev1', + orgChild4Id: 'node1', + }); + }); + }); + + describe('syncOrgLevel()', () => { + beforeEach(() => { + mockQueryRunner.manager.find.mockResolvedValue([]); + mockQueryRunner.manager.delete.mockResolvedValue({ affected: 0 }); + mockQueryRunner.manager.update.mockResolvedValue({ affected: 0 }); + mockQueryRunner.manager.create.mockReturnValue({}); + mockQueryRunner.manager.save.mockResolvedValue({}); + }); + + it('should fetch draft and current nodes with Like filter', async () => { + const repository = { + find: jest.fn().mockResolvedValue([]), + }; + + await repository.find({ + where: { + orgRevisionId: 'draftRev1', + ancestorDNA: Like('root-dna%'), + }, + }); + + await repository.find({ + where: { + orgRevisionId: 'currentRev1', + ancestorDNA: Like('root-dna%'), + }, + }); + + expect(repository.find).toHaveBeenCalledTimes(2); + }); + + it('should build lookup maps from draft and current nodes', () => { + const draftNodes = [ + { id: 'draft1', ancestorDNA: 'root-dna/child1' }, + { id: 'draft2', ancestorDNA: 'root-dna/child2' }, + ]; + const currentNodes = [ + { id: 'current1', ancestorDNA: 'root-dna/child1' }, + ]; + + const draftByDNA = new Map(draftNodes.map(n => [n.ancestorDNA, n])); + const currentByDNA = new Map(currentNodes.map(n => [n.ancestorDNA, n])); + + expect(draftByDNA.size).toBe(2); + expect(currentByDNA.size).toBe(1); + expect(draftByDNA.get('root-dna/child1')).toEqual(draftNodes[0]); + expect(currentByDNA.get('root-dna/child1')).toEqual(currentNodes[0]); + }); + + it('should identify nodes to delete (in current but not in draft)', () => { + const draftNodes = [ + { id: 'draft1', ancestorDNA: 'root-dna/child1' }, + ]; + const currentNodes = [ + { id: 'current1', ancestorDNA: 'root-dna/child1' }, + { id: 'current2', ancestorDNA: 'root-dna/child2' }, // Not in draft + ]; + + const draftByDNA = new Map(draftNodes.map((n: any) => [n.ancestorDNA, n])); + const toDelete = currentNodes.filter((curr: any) => !draftByDNA.has(curr.ancestorDNA)); + + expect(toDelete).toHaveLength(1); + expect(toDelete[0].id).toBe('current2'); + }); + + it('should identify nodes to update (in both draft and current)', () => { + const draftNodes = [ + { id: 'draft1', ancestorDNA: 'root-dna/child1' }, + { id: 'draft2', ancestorDNA: 'root-dna/child2' }, + ]; + const currentNodes = [ + { id: 'current1', ancestorDNA: 'root-dna/child1' }, + ]; + + const currentByDNA = new Map(currentNodes.map((n: any) => [n.ancestorDNA, n])); + const toUpdate = draftNodes.filter((draft: any) => currentByDNA.has(draft.ancestorDNA)); + + expect(toUpdate).toHaveLength(1); + expect(toUpdate[0].id).toBe('draft1'); + }); + + it('should identify nodes to insert (in draft but not in current)', () => { + const draftNodes = [ + { id: 'draft1', ancestorDNA: 'root-dna/child1' }, + { id: 'draft2', ancestorDNA: 'root-dna/child2' }, + ]; + const currentNodes = [ + { id: 'current1', ancestorDNA: 'root-dna/child1' }, + ]; + + const currentByDNA = new Map(currentNodes.map((n: any) => [n.ancestorDNA, n])); + const toInsert = draftNodes.filter((draft: any) => !currentByDNA.has(draft.ancestorDNA)); + + expect(toInsert).toHaveLength(1); + expect(toInsert[0].id).toBe('draft2'); + }); + + it('should return correct mapping after sync', () => { + const mapping: OrgIdMapping = { + byAncestorDNA: new Map([ + ['root-dna/child1', 'current1'], + ['root-dna/child2', 'current2'], + ]), + byDraftId: new Map([ + ['draft1', 'current1'], + ['draft2', 'current2'], + ]), + }; + + expect(mapping.byAncestorDNA.get('root-dna/child1')).toBe('current1'); + expect(mapping.byDraftId.get('draft1')).toBe('current1'); + expect(mapping.byDraftId.get('draft2')).toBe('current2'); + }); + }); + + describe('syncPositionsForPosMaster()', () => { + beforeEach(() => { + mockQueryRunner.manager.find.mockResolvedValue([]); + mockQueryRunner.manager.delete.mockResolvedValue({ affected: 0 }); + mockQueryRunner.manager.update.mockResolvedValue({ affected: 0 }); + mockQueryRunner.manager.create.mockReturnValue({}); + mockQueryRunner.manager.save.mockResolvedValue({}); + }); + + it('should fetch draft and current positions for a posMaster', async () => { + const draftPosMasterId = 'draft-pos-1'; + const currentPosMasterId = 'current-pos-1'; + + mockQueryRunner.manager.find + .mockResolvedValueOnce([{ id: 'pos1', posMasterId: draftPosMasterId }]) + .mockResolvedValueOnce([{ id: 'pos2', posMasterId: currentPosMasterId }]); + + await mockQueryRunner.manager.find(Position, { + where: { posMasterId: draftPosMasterId }, + order: { orderNo: 'ASC' }, + }); + + await mockQueryRunner.manager.find(Position, { + where: { posMasterId: currentPosMasterId }, + }); + + expect(mockQueryRunner.manager.find).toHaveBeenCalledTimes(2); + }); + + it('should delete all current positions when no draft positions exist', async () => { + const currentPositions = [ + { id: 'pos1', orderNo: 1 }, + { id: 'pos2', orderNo: 2 }, + ]; + + mockQueryRunner.manager.find + .mockResolvedValueOnce([]) // No draft positions + .mockResolvedValueOnce(currentPositions); + + await mockQueryRunner.manager.delete(Position, ['pos1', 'pos2']); + + expect(mockQueryRunner.manager.delete).toHaveBeenCalledWith(Position, ['pos1', 'pos2']); + }); + + it('should delete positions not in draft (by orderNo)', async () => { + const draftPositions = [ + { id: 'dpos1', orderNo: 1 }, + { id: 'dpos2', orderNo: 2 }, + ]; + const currentPositions = [ + { id: 'cpos1', orderNo: 1 }, + { id: 'cpos2', orderNo: 2 }, + { id: 'cpos3', orderNo: 3 }, // Not in draft + ]; + + mockQueryRunner.manager.find + .mockResolvedValueOnce(draftPositions) + .mockResolvedValueOnce(currentPositions); + + const draftOrderNos = new Set(draftPositions.map((p: any) => p.orderNo)); + const toDelete = currentPositions.filter((p: any) => !draftOrderNos.has(p.orderNo)); + + expect(toDelete).toHaveLength(1); + expect(toDelete[0].id).toBe('cpos3'); + }); + + it('should update existing positions (matched by orderNo)', async () => { + const draftPositions = [ + { + id: 'dpos1', + orderNo: 1, + positionName: 'Updated Name', + positionField: 'field1', + posTypeId: 'type1', + posLevelId: 'level1', + }, + ]; + const currentPositions = [ + { id: 'cpos1', orderNo: 1, positionName: 'Old Name' }, + ]; + + const currentByOrderNo = new Map(currentPositions.map((p: any) => [p.orderNo, p])); + const draftPos = draftPositions[0]; + const current = currentByOrderNo.get(draftPos.orderNo); + + expect(current).toBeDefined(); + + if (current) { + const updateData = { + positionName: draftPos.positionName, + positionField: draftPos.positionField, + posTypeId: draftPos.posTypeId, + posLevelId: draftPos.posLevelId, + }; + + await mockQueryRunner.manager.update(Position, current.id, updateData); + + expect(mockQueryRunner.manager.update).toHaveBeenCalledWith( + Position, + 'cpos1', + expect.objectContaining({ positionName: 'Updated Name' }) + ); + } + }); + + it('should insert new positions not in current', async () => { + const draftPositions = [ + { id: 'dpos1', orderNo: 1, positionName: 'New Position' }, + ]; + const currentPositions: any[] = []; + + const currentByOrderNo = new Map(currentPositions.map((p: any) => [p.orderNo, p])); + const draftPos = draftPositions[0]; + const current = currentByOrderNo.get(draftPos.orderNo); + const currentPosMasterId = 'current-pos-1'; + + expect(current).toBeUndefined(); + + if (!current) { + const newPosition = { + ...draftPos, + id: undefined, + posMasterId: currentPosMasterId, + }; + + await mockQueryRunner.manager.create(Position, newPosition); + await mockQueryRunner.manager.save(newPosition); + + expect(mockQueryRunner.manager.create).toHaveBeenCalledWith(Position, { + ...draftPos, + id: undefined, + posMasterId: currentPosMasterId, + }); + } + }); + }); +}); diff --git a/src/app.ts b/src/app.ts index 7f0e0220..06f76548 100644 --- a/src/app.ts +++ b/src/app.ts @@ -11,8 +11,11 @@ import { AppDataSource } from "./database/data-source"; import { RegisterRoutes } from "./routes"; import { OrganizationController } from "./controllers/OrganizationController"; import logMiddleware from "./middlewares/logs"; +import { logMemoryStore } from "./utils/LogMemoryStore"; +import { orgStructureCache } from "./utils/OrgStructureCache"; import { CommandController } from "./controllers/CommandController"; import { ProfileSalaryController } from "./controllers/ProfileSalaryController"; +import { ScriptProfileOrgController } from "./controllers/ScriptProfileOrgController"; import { DateSerializer } from "./interfaces/date-serializer"; import { initWebSocket } from "./services/webSocket"; @@ -20,6 +23,12 @@ import { initWebSocket } from "./services/webSocket"; async function main() { await AppDataSource.initialize(); + // Initialize LogMemoryStore after database is ready + logMemoryStore.initialize(); + + // Initialize OrgStructureCache after database is ready + orgStructureCache.initialize(); + // Setup custom Date serialization for local timezone DateSerializer.setupDateSerialization(); @@ -44,19 +53,8 @@ async function main() { const APP_HOST = process.env.APP_HOST || "0.0.0.0"; const APP_PORT = +(process.env.APP_PORT || 3000); - const cronTime = "0 0 3 * * *"; // ตั้งเวลาทุกวันเวลา 08:00:00 - // const cronTime = "*/10 * * * * *"; - cron.schedule(cronTime, async () => { - try { - const orgController = new OrganizationController(); - await orgController.cronjobRevision(); - } catch (error) { - console.error("Error executing function from controller:", error); - } - }); - - const cronTime_command = "0 0 2 * * *"; - // const cronTime_command = "*/10 * * * * *"; + // Cron job for executing command - every day at 00:30:00 + const cronTime_command = "0 30 0 * * *"; cron.schedule(cronTime_command, async () => { try { const commandController = new CommandController(); @@ -66,7 +64,19 @@ async function main() { } }); - const cronTime_Oct = "0 0 1 10 *"; + // Cron job for updating org revision - every day at 01:00:00 + const cronTime = "0 0 1 * * *"; + cron.schedule(cronTime, async () => { + try { + const orgController = new OrganizationController(); + await orgController.cronjobRevision(); + } catch (error) { + console.error("Error executing function from controller:", error); + } + }); + + // Cron job for updating retirement status - every day at 02:00:00 on the 1st of October + const cronTime_Oct = "0 0 2 10 *"; cron.schedule(cronTime_Oct, async () => { try { const commandController = new CommandController(); @@ -76,7 +86,19 @@ async function main() { } }); - const cronTime_Tenure = "0 0 0 * * *"; + // Cron job for updating org DNA - every day at 03:00:00 + const cronTime_UpdateOrg = "0 0 3 * * *"; + cron.schedule(cronTime_UpdateOrg, async () => { + try { + const scriptProfileOrgController = new ScriptProfileOrgController(); + await scriptProfileOrgController.cronjobUpdateOrg({} as any); + } catch (error) { + console.error("Error executing cronjobUpdateOrg:", error); + } + }); + + // Cron job for updating tenure - every day at 04:00:00 + const cronTime_Tenure = "0 0 4 * * *"; cron.schedule(cronTime_Tenure, async () => { try { const profileSalaryController = new ProfileSalaryController(); @@ -93,7 +115,7 @@ async function main() { }); // app.listen(APP_PORT, APP_HOST, () => console.log(`Listening on: http://localhost:${APP_PORT}`)); - app.listen( + const server = app.listen( APP_PORT, APP_HOST, () => ( @@ -111,6 +133,50 @@ async function main() { } runMessageQueue(); + + // Graceful Shutdown + const gracefulShutdown = async (signal: string) => { + console.log(`\n[APP] ${signal} received. Starting graceful shutdown...`); + + // Stop accepting new connections + server.close(() => { + console.log("[APP] HTTP server closed"); + }); + + // Force close after timeout + const shutdownTimeout = setTimeout(() => { + console.error("[APP] Forced shutdown after timeout"); + process.exit(1); + }, 30000); // 30 seconds timeout + + try { + // Close database connections + if (AppDataSource.isInitialized) { + await AppDataSource.destroy(); + console.log("[APP] Database connections closed"); + } + + // Destroy LogMemoryStore + logMemoryStore.destroy(); + console.log("[APP] LogMemoryStore destroyed"); + + // Destroy OrgStructureCache + orgStructureCache.destroy(); + console.log("[APP] OrgStructureCache destroyed"); + + clearTimeout(shutdownTimeout); + console.log("[APP] Graceful shutdown completed"); + process.exit(0); + } catch (error) { + console.error("[APP] Error during shutdown:", error); + clearTimeout(shutdownTimeout); + process.exit(1); + } + }; + + // Listen for shutdown signals + process.on("SIGTERM", () => gracefulShutdown("SIGTERM")); + process.on("SIGINT", () => gracefulShutdown("SIGINT")); } main(); diff --git a/src/controllers/CommandController.ts b/src/controllers/CommandController.ts index 905c77ea..da0ed3fe 100644 --- a/src/controllers/CommandController.ts +++ b/src/controllers/CommandController.ts @@ -27,6 +27,7 @@ import { OrgRevision } from "../entities/OrgRevision"; import { CommandSendCC } from "../entities/CommandSendCC"; import { CommandSalary } from "../entities/CommandSalary"; import { CommandRecive } from "../entities/CommandRecive"; +import { CommandOperator } from "../entities/CommandOperator"; import HttpStatus from "../interfaces/http-status"; import Extension from "../interfaces/extension"; import { ProfileEmployee } from "../entities/ProfileEmployee"; @@ -63,6 +64,7 @@ import { getRoleMappings, removeUserRoles, getToken, + updateUserAttributes, } from "../keycloak"; import { ProfileEducation, CreateProfileEducation } from "../entities/ProfileEducation"; import { ProfileEducationHistory } from "../entities/ProfileEducationHistory"; @@ -98,6 +100,9 @@ import { CreatePosMasterHistoryOfficer, } from "../services/PositionService"; import { PostRetireToExprofile } from "./ExRetirementController"; +import { LeaveType } from "../entities/LeaveType"; +import { KeycloakAttributeService } from "../services/KeycloakAttributeService"; +import { reOrderCommandRecivesAndDelete } from "../services/CommandService"; @Route("api/v1/org/command") @Tags("Command") @Security("bearerAuth") @@ -112,6 +117,7 @@ export class CommandController extends Controller { private commandSendCCRepository = AppDataSource.getRepository(CommandSendCC); private commandSalaryRepository = AppDataSource.getRepository(CommandSalary); private commandReciveRepository = AppDataSource.getRepository(CommandRecive); + private commandOperatorRepository = AppDataSource.getRepository(CommandOperator); private profileRepository = AppDataSource.getRepository(Profile); private profileEmployeeRepository = AppDataSource.getRepository(ProfileEmployee); private orgRevisionRepo = AppDataSource.getRepository(OrgRevision); @@ -152,6 +158,8 @@ export class CommandController extends Controller { private insigniaHistoryRepo = AppDataSource.getRepository(ProfileInsigniaHistory); private genderRepo = AppDataSource.getRepository(Gender); private avatarRepository = AppDataSource.getRepository(ProfileAvatar); + private leaveType = AppDataSource.getRepository(LeaveType); + private keycloakAttributeService = new KeycloakAttributeService(); /** * API list รายการคำสั่ง @@ -183,17 +191,17 @@ export class CommandController extends Controller { (x) => x.orgRevision?.orgRevisionIsCurrent == true && x.orgRevision?.orgRevisionIsDraft == false, )[0]?.isDirector || false; - if (isDirector) { - let _data: any = { - root: null, - child1: null, - child2: null, - child3: null, - child4: null, - }; - if (!request.user.role.includes("SUPER_ADMIN")) { - _data = await new permission().PermissionOrgList(request, "COMMAND"); - } + let _data: any = { + root: null, + child1: null, + child2: null, + child3: null, + child4: null, + }; + if (request.user.role.includes("STAFF")) { + _data = await new permission().PermissionOrgList(request, "COMMAND"); + } + if (isDirector || _data.privilege == "OWNER") { const profiles = await this.profileRepository .createQueryBuilder("profile") .leftJoinAndSelect("profile.current_holders", "current_holders") @@ -216,7 +224,8 @@ export class CommandController extends Controller { _data.child1 != undefined && _data.child1 != null ? _data.child1[0] != null ? `current_holders.orgChild1Id IN (:...child1)` - : `current_holders.orgChild1Id is ${_data.privilege == "PARENT" ? "not null" : "null"}` + : // : `current_holders.orgChild1Id is ${_data.privilege == "PARENT" ? "not null" : "null"}` + `current_holders.orgChild1Id is null` : "1=1", { child1: _data.child1, @@ -390,6 +399,7 @@ export class CommandController extends Controller { if (!commandType) { throw new HttpError(HttpStatusCode.NOT_FOUND, "ไม่พบประเภทคำสั่งนี้ในระบบ"); } + const now = new Date(); command.detailHeader = commandType.detailHeader; command.detailBody = commandType.detailBody; command.detailFooter = commandType.detailFooter; @@ -399,11 +409,82 @@ export class CommandController extends Controller { command.issue = commandType.name; command.createdUserId = request.user.sub; command.createdFullName = request.user.name; - command.createdAt = new Date(); + command.createdAt = now; command.lastUpdateUserId = request.user.sub; command.lastUpdateFullName = request.user.name; - command.lastUpdatedAt = new Date(); + command.lastUpdatedAt = now; await this.commandRepository.save(command); + // insert commandOperator + if (request.user.sub) { + const profile = await this.profileRepository.findOne({ + where: { keycloak: request.user.sub }, + relations: { + posLevel: true, + posType: true, + current_holders: { + orgRevision: true, + orgRoot: true, + orgChild1: true, + orgChild2: true, + orgChild3: true, + orgChild4: true, + }, + }, + }); + if (profile) { + const currentHolder = profile!.current_holders?.find( + (x) => + x.orgRevision?.orgRevisionIsDraft === false && + x.orgRevision?.orgRevisionIsCurrent === true, + ); + + const posNo = + currentHolder != null && currentHolder.orgChild4 != null + ? `${currentHolder.orgChild4.orgChild4ShortName} ${currentHolder.posMasterNo}` + : currentHolder != null && currentHolder.orgChild3 != null + ? `${currentHolder.orgChild3.orgChild3ShortName} ${currentHolder.posMasterNo}` + : currentHolder != null && currentHolder.orgChild2 != null + ? `${currentHolder.orgChild2.orgChild2ShortName} ${currentHolder.posMasterNo}` + : currentHolder != null && currentHolder.orgChild1 != null + ? `${currentHolder.orgChild1.orgChild1ShortName} ${currentHolder.posMasterNo}` + : currentHolder != null && currentHolder?.orgRoot != null + ? `${currentHolder.orgRoot.orgRootShortName} ${currentHolder.posMasterNo}` + : null; + + const position = await this.positionRepository.findOne({ + where: { + positionIsSelected: true, + posMaster: { + orgRevisionId: currentHolder?.orgRevisionId, + current_holderId: profile!.id, + }, + }, + order: { createdAt: "DESC" }, + relations: { posExecutive: true }, + }); + const operator = Object.assign(new CommandOperator(), { + profileId: profile?.id, + prefix: profile?.prefix, + firstName: profile?.firstName, + lastName: profile?.lastName, + posNo: posNo, + posType: profile?.posType?.posTypeName ?? null, + posLevel: profile?.posLevel?.posLevelName ?? null, + position: position?.positionName ?? null, + positionExecutive: position?.posExecutive?.posExecutiveName ?? null, + roleName: "เจ้าหน้าที่ดำเนินการ", + orderNo: 1, + commandId: command.id, + createdUserId: request.user.sub, + createdFullName: request.user.name, + createdAt: now, + lastUpdateUserId: request.user.sub, + lastUpdateFullName: request.user.name, + lastUpdatedAt: now, + }); + await this.commandOperatorRepository.save(operator); + } + } return new HttpSuccess(command.id); } @@ -442,6 +523,7 @@ export class CommandController extends Controller { commandTypeName: command.commandType?.name || null, commandCode: command.commandType?.code || null, commandSysId: command.commandType?.commandSysId || null, + createdUserId: command.createdUserId, }; return new HttpSuccess(_command); } @@ -653,7 +735,7 @@ export class CommandController extends Controller { /** * API แก้ไขเงินเดือนทั้งกลุ่ม * - * @summary API แก้ไขเงินเดือนทั้งกลุ่ม + * @summary API แก้ไขเงินเดือนทั้งกลุ่ม * * @param {string} id Id คำสั่ง */ @@ -672,7 +754,7 @@ export class CommandController extends Controller { @Request() request: RequestWithUser, ) { await new permission().PermissionUpdate(request, "COMMAND"); - + if (!Array.isArray(requestBody)) { throw new HttpError(HttpStatusCode.BAD_REQUEST, "รูปแบบข้อมูลไม่ถูกต้อง"); } @@ -1167,6 +1249,7 @@ export class CommandController extends Controller { await this.commandSendCCRepository.delete({ commandSendId: In(commandSend.map((x) => x.id)) }); await this.commandReciveRepository.delete({ commandId: command.id }); await this.commandSendRepository.delete({ commandId: command.id }); + await this.commandOperatorRepository.delete({ commandId: command.id }); await this.commandRepository.delete(command.id); return new HttpSuccess(); } @@ -1570,11 +1653,13 @@ export class CommandController extends Controller { _profile.leaveDate = _Date; _profile.dateLeave = _Date; _profile.lastUpdatedAt = _Date; - if (_profile.keycloak != null && _profile.keycloak != "") { + if (_profile.keycloak != null && _profile.keycloak != "" && _profile.isDelete === false) { // console.log("4. disable keycloak/authen") const delUserKeycloak = await deleteUser(_profile.keycloak, adminToken); if (delUserKeycloak) { - _profile.keycloak = ""; + // Task #228 + // _profile.keycloak = ""; + _profile.isDelete = true; _profile.roleKeycloaks = []; } } @@ -1626,11 +1711,13 @@ export class CommandController extends Controller { _profileEmp.leaveDate = _Date; _profileEmp.dateLeave = _Date; _profileEmp.lastUpdatedAt = _Date; - if (_profileEmp.keycloak != null && _profileEmp.keycloak != "") { + if (_profileEmp.keycloak != null && _profileEmp.keycloak != "" && _profileEmp.isDelete === false) { // disable keycloak/authen const delUserKeycloak = await deleteUser(_profileEmp.keycloak, adminToken); if (delUserKeycloak) { - _profileEmp.keycloak = ""; + // Task #228 + // _profileEmp.keycloak = ""; + _profileEmp.isDelete = true; _profileEmp.roleKeycloaks = []; } } @@ -2233,6 +2320,19 @@ export class CommandController extends Controller { } } if (issue == null) issue = "..................................."; + + const operators = await this.commandOperatorRepository.find({ + select: { + prefix: true, + firstName: true, + lastName: true, + roleName: true, + orderNo: true, + }, + where: { commandId: command.id }, + order: { orderNo: "ASC" }, + }); + return new HttpSuccess({ template: command.commandType.fileAttachment, reportName: "xlsx-report", @@ -2248,6 +2348,18 @@ export class CommandController extends Controller { command.commandExcecuteDate == null ? "" : Extension.ToThaiNumber(Extension.ToThaiFullDate2(command.commandExcecuteDate)), + operators: + operators.length > 0 + ? operators.map((x) => ({ + fullName: `${x.prefix ?? ""}${x.firstName ?? ""} ${x.lastName ?? ""}`, + roleName: x.roleName, + })) + : [ + { + fullName: "", + roleName: "เจ้าหน้าที่ดำเนินการ", + }, + ], }, }); } @@ -2371,6 +2483,35 @@ export class CommandController extends Controller { return new HttpSuccess(); } + /** + * API แก้ไขเปลี่ยนผู้สร้างคำสั่ง + * @summary API แก้ไขเปลี่ยนผู้สร้างคำสั่ง + * @param {string} id Id คำสั่ง + */ + @Put("change-creator/{id}") + async ChangeCreator( + @Path() id: string, + @Body() req: { assignId: string }, + @Request() request: RequestWithUser, + ) { + await new permission().PermissionUpdate(request, "COMMAND"); + const command = await this.commandRepository.findOne({ + where: { id: id }, + }); + + if (!command) { + throw new HttpError(HttpStatusCode.NOT_FOUND, "ไม่พบข้อมูลคำสั่งนี้"); + } + + command.createdUserId = req.assignId; + command.lastUpdateUserId = request.user.sub; + command.lastUpdateFullName = request.user.name; + command.lastUpdatedAt = new Date(); + + await this.commandRepository.save(command); + return new HttpSuccess(); + } + /** * API สร้างรายการ body คำสั่ง * @@ -2408,6 +2549,7 @@ export class CommandController extends Controller { }, @Request() request: RequestWithUser, ) { + const now = new Date(); let command = new Command(); let commandCode: string = ""; let _null: any = null; @@ -2466,22 +2608,111 @@ export class CommandController extends Controller { : _null), (command.createdUserId = request.user.sub); command.createdFullName = request.user.name; - command.createdAt = new Date(); + command.createdAt = now; command.lastUpdateUserId = request.user.sub; command.lastUpdateFullName = request.user.name; - command.lastUpdatedAt = new Date(); + command.lastUpdatedAt = now; await this.commandRepository.save(command); } + // insert commandOperator + const checkCommandOperator = await this.commandOperatorRepository.findOne({ + where: { commandId: command.id, roleName: "เจ้าหน้าที่ดำเนินการ" }, + }); + if (!checkCommandOperator) { + if (request.user.sub) { + const profile = await this.profileRepository.findOne({ + where: { keycloak: request.user.sub }, + relations: { + posLevel: true, + posType: true, + current_holders: { + orgRevision: true, + orgRoot: true, + orgChild1: true, + orgChild2: true, + orgChild3: true, + orgChild4: true, + }, + }, + }); + if (profile) { + const currentHolder = profile!.current_holders?.find( + (x) => + x.orgRevision?.orgRevisionIsDraft === false && + x.orgRevision?.orgRevisionIsCurrent === true, + ); + + const posNo = + currentHolder != null && currentHolder.orgChild4 != null + ? `${currentHolder.orgChild4.orgChild4ShortName} ${currentHolder.posMasterNo}` + : currentHolder != null && currentHolder.orgChild3 != null + ? `${currentHolder.orgChild3.orgChild3ShortName} ${currentHolder.posMasterNo}` + : currentHolder != null && currentHolder.orgChild2 != null + ? `${currentHolder.orgChild2.orgChild2ShortName} ${currentHolder.posMasterNo}` + : currentHolder != null && currentHolder.orgChild1 != null + ? `${currentHolder.orgChild1.orgChild1ShortName} ${currentHolder.posMasterNo}` + : currentHolder != null && currentHolder?.orgRoot != null + ? `${currentHolder.orgRoot.orgRootShortName} ${currentHolder.posMasterNo}` + : null; + + const position = await this.positionRepository.findOne({ + where: { + positionIsSelected: true, + posMaster: { + orgRevisionId: currentHolder?.orgRevisionId, + current_holderId: profile!.id, + }, + }, + order: { createdAt: "DESC" }, + relations: { posExecutive: true }, + }); + const operator = Object.assign(new CommandOperator(), { + profileId: profile?.id, + prefix: profile?.prefix, + firstName: profile?.firstName, + lastName: profile?.lastName, + posNo: posNo, + posType: profile?.posType?.posTypeName ?? null, + posLevel: profile?.posLevel?.posLevelName ?? null, + position: position?.positionName ?? null, + positionExecutive: position?.posExecutive?.posExecutiveName ?? null, + roleName: "เจ้าหน้าที่ดำเนินการ", + orderNo: 1, + commandId: command.id, + createdUserId: request.user.sub, + createdFullName: request.user.name, + createdAt: now, + lastUpdateUserId: request.user.sub, + lastUpdateFullName: request.user.name, + lastUpdatedAt: now, + }); + await this.commandOperatorRepository.save(operator); + } + } + } const path = commandTypePath(commandCode); if (path == null) throw new HttpError(HttpStatusCode.NOT_FOUND, "ไม่พบประเภทคำสั่งนี้ในระบบ"); - await new CallAPI() + if (!["C-PM-26", "C-PM-25"].includes(commandCode)) { + await new CallAPI() .PostData(request, path, { refIds: requestBody.persons.filter((x) => x.refId != null).map((x) => x.refId), status: "REPORT", }) .then(async (res) => {}) .catch(() => {}); + } + else { + await new CallAPI() + .PostData(request, path, { + refIds: requestBody.persons.filter((x) => x.refId != null).map((x) => x.refId), + status: "REPORT", + commandTypeId: requestBody.commandTypeId, + commandCode: commandCode, + }) + .then(async (res) => {}) + .catch(() => {}); + } let order = command.commandRecives == null || command.commandRecives.length <= 0 ? 0 @@ -2537,10 +2768,10 @@ export class CommandController extends Controller { commandRecive.commandId = command.id; commandRecive.createdUserId = request.user.sub; commandRecive.createdFullName = request.user.name; - commandRecive.createdAt = new Date(); + commandRecive.createdAt = now; commandRecive.lastUpdateUserId = request.user.sub; commandRecive.lastUpdateFullName = request.user.name; - commandRecive.lastUpdatedAt = new Date(); + commandRecive.lastUpdatedAt = now; if (commandCode == "C-PM-40") { const posMasterAct = await this.posMasterActRepository.findOne({ @@ -2635,10 +2866,10 @@ export class CommandController extends Controller { commandSend.commandId = command.id; commandSend.createdUserId = request.user.sub; commandSend.createdFullName = request.user.name; - commandSend.createdAt = new Date(); + commandSend.createdAt = now; commandSend.lastUpdateUserId = request.user.sub; commandSend.lastUpdateFullName = request.user.name; - commandSend.lastUpdatedAt = new Date(); + commandSend.lastUpdatedAt = now; await this.commandSendRepository.save(commandSend); if (commandSend && commandSend.id) { let _ccName = new Array("EMAIL", "INBOX"); @@ -2649,10 +2880,10 @@ export class CommandController extends Controller { name: _ccName[i], createdUserId: request.user.sub, createdFullName: request.user.name, - createdAt: new Date(), + createdAt: now, lastUpdateUserId: request.user.sub, lastUpdateFullName: request.user.name, - lastUpdatedAt: new Date(), + lastUpdatedAt: now, }); } await this.commandSendCCRepository.save(_dataSendCC); @@ -2692,10 +2923,10 @@ export class CommandController extends Controller { commandSend.commandId = command.id; commandSend.createdUserId = request.user.sub; commandSend.createdFullName = request.user.name; - commandSend.createdAt = new Date(); + commandSend.createdAt = now; commandSend.lastUpdateUserId = request.user.sub; commandSend.lastUpdateFullName = request.user.name; - commandSend.lastUpdatedAt = new Date(); + commandSend.lastUpdatedAt = now; await this.commandSendRepository.save(commandSend); if (commandSend && commandSend.id) { let _ccName = new Array("EMAIL", "INBOX"); @@ -2706,10 +2937,10 @@ export class CommandController extends Controller { name: _ccName[i], createdUserId: request.user.sub, createdFullName: request.user.name, - createdAt: new Date(), + createdAt: now, lastUpdateUserId: request.user.sub, lastUpdateFullName: request.user.name, - lastUpdatedAt: new Date(), + lastUpdatedAt: now, }); } await this.commandSendCCRepository.save(_dataSendCC); @@ -2749,10 +2980,10 @@ export class CommandController extends Controller { commandSend.commandId = command.id; commandSend.createdUserId = request.user.sub; commandSend.createdFullName = request.user.name; - commandSend.createdAt = new Date(); + commandSend.createdAt = now; commandSend.lastUpdateUserId = request.user.sub; commandSend.lastUpdateFullName = request.user.name; - commandSend.lastUpdatedAt = new Date(); + commandSend.lastUpdatedAt = now; await this.commandSendRepository.save(commandSend); if (commandSend && commandSend.id) { let _ccName = new Array("EMAIL", "INBOX"); @@ -2763,10 +2994,10 @@ export class CommandController extends Controller { name: _ccName[i], createdUserId: request.user.sub, createdFullName: request.user.name, - createdAt: new Date(), + createdAt: now, lastUpdateUserId: request.user.sub, lastUpdateFullName: request.user.name, - lastUpdatedAt: new Date(), + lastUpdatedAt: now, }); } await this.commandSendCCRepository.save(_dataSendCC); @@ -2931,10 +3162,10 @@ export class CommandController extends Controller { commandSend.commandId = command.id; commandSend.createdUserId = request.user.sub; commandSend.createdFullName = request.user.name; - commandSend.createdAt = new Date(); + commandSend.createdAt = now; commandSend.lastUpdateUserId = request.user.sub; commandSend.lastUpdateFullName = request.user.name; - commandSend.lastUpdatedAt = new Date(); + commandSend.lastUpdatedAt = now; await this.commandSendRepository.save(commandSend); if (commandSend && commandSend.id) { let _ccName = new Array("EMAIL", "INBOX"); @@ -2945,10 +3176,10 @@ export class CommandController extends Controller { name: _ccName[i], createdUserId: request.user.sub, createdFullName: request.user.name, - createdAt: new Date(), + createdAt: now, lastUpdateUserId: request.user.sub, lastUpdateFullName: request.user.name, - lastUpdatedAt: new Date(), + lastUpdatedAt: now, }); } await this.commandSendCCRepository.save(_dataSendCC); @@ -2992,10 +3223,10 @@ export class CommandController extends Controller { commandSend.commandId = command.id; commandSend.createdUserId = request.user.sub; commandSend.createdFullName = request.user.name; - commandSend.createdAt = new Date(); + commandSend.createdAt = now; commandSend.lastUpdateUserId = request.user.sub; commandSend.lastUpdateFullName = request.user.name; - commandSend.lastUpdatedAt = new Date(); + commandSend.lastUpdatedAt = now; await this.commandSendRepository.save(commandSend); if (commandSend && commandSend.id) { let _ccName = new Array("EMAIL", "INBOX"); @@ -3006,10 +3237,10 @@ export class CommandController extends Controller { name: _ccName[i], createdUserId: request.user.sub, createdFullName: request.user.name, - createdAt: new Date(), + createdAt: now, lastUpdateUserId: request.user.sub, lastUpdateFullName: request.user.name, - lastUpdatedAt: new Date(), + lastUpdatedAt: now, }); } await this.commandSendCCRepository.save(_dataSendCC); @@ -3051,10 +3282,10 @@ export class CommandController extends Controller { commandSend.commandId = command.id; commandSend.createdUserId = request.user.sub; commandSend.createdFullName = request.user.name; - commandSend.createdAt = new Date(); + commandSend.createdAt = now; commandSend.lastUpdateUserId = request.user.sub; commandSend.lastUpdateFullName = request.user.name; - commandSend.lastUpdatedAt = new Date(); + commandSend.lastUpdatedAt = now; await this.commandSendRepository.save(commandSend); if (commandSend && commandSend.id) { let _ccName = new Array("EMAIL", "INBOX"); @@ -3065,10 +3296,10 @@ export class CommandController extends Controller { name: _ccName[i], createdUserId: request.user.sub, createdFullName: request.user.name, - createdAt: new Date(), + createdAt: now, lastUpdateUserId: request.user.sub, lastUpdateFullName: request.user.name, - lastUpdatedAt: new Date(), + lastUpdatedAt: now, }); } await this.commandSendCCRepository.save(_dataSendCC); @@ -3485,11 +3716,14 @@ export class CommandController extends Controller { posMasterId: item.posmasterId, }, }); + // ถ้าไม่ใช่ตำแหน่งนั่งทับ (isSit = false) ถึงจะอัพเดทตำแหน่งในทะเบียนประวัติ if (positionNew != null) { positionNew.positionIsSelected = true; - profile.posLevelId = positionNew.posLevelId; - profile.posTypeId = positionNew.posTypeId; - profile.position = positionNew.positionName; + if(!posMaster.isSit){ + profile.posLevelId = positionNew.posLevelId; + profile.posTypeId = positionNew.posTypeId; + profile.position = positionNew.positionName; + } profile.amount = item.amount ?? null; profile.amountSpecial = item.amountSpecial ?? null; await this.profileRepository.save(profile); @@ -3795,6 +4029,7 @@ export class CommandController extends Controller { .orgRootShortName ?? ""; } } + const today = new Date().setHours(0, 0, 0, 0); await Promise.all( body.data.map(async (item) => { const profile = await this.profileRepository.findOne({ @@ -3802,9 +4037,9 @@ export class CommandController extends Controller { // relations: ["roleKeycloaks"], relations: { roleKeycloaks: true, - posType: true , - posLevel: true - } + posType: true, + posLevel: true, + }, }); if (!profile) { throw new HttpError(HttpStatusCode.NOT_FOUND, "ไม่พบข้อมูลทะเบียนประวัตินี้"); @@ -3814,17 +4049,21 @@ export class CommandController extends Controller { if (code && ["C-PM-08", "C-PM-17", "C-PM-18"].includes(code)) { removePostMasterAct(profile.id); } - //ออกคำสั่งยกเลิกลาออกต้องอัพเดทสถานะคำสั่งลาออกเป็น CANCEL + //ออกคำสั่งยกเลิกลาออก ลบเฉพาะคนที่ขอยกเลิกลาออก else if (item.resignId && code && ["C-PM-41"].includes(code)) { - const commandRecive = await this.commandReciveRepository.findOne({ - select: ["commandId"], + const commandResign = await this.commandReciveRepository.findOne({ where: { refId: item.resignId }, + relations: { command: true }, }); - if (commandRecive && commandRecive.commandId) { - await this.commandRepository.update( - { id: commandRecive?.commandId }, - { status: "CANCEL" }, - ); + const executeDate = commandResign + ? new Date(commandResign.command.commandExcecuteDate).setHours(0, 0, 0, 0) + : today; + if ( + commandResign && + _command.status !== "REPORTED" && + (_command.status !== "WAITING" || today < executeDate) + ) { + await reOrderCommandRecivesAndDelete(commandResign!.id); } } let _commandYear = item.commandYear; @@ -3872,24 +4111,24 @@ export class CommandController extends Controller { const curRevision = await this.orgRevisionRepo.findOne({ where: { orgRevisionIsCurrent: true, orgRevisionIsDraft: false }, }); - let orgRootRef = null; - let orgChild1Ref = null; - let orgChild2Ref = null; - let orgChild3Ref = null; - let orgChild4Ref = null; + let orgRootRef = null; + let orgChild1Ref = null; + let orgChild2Ref = null; + let orgChild3Ref = null; + let orgChild4Ref = null; if (curRevision) { const curPosMaster = await this.posMasterRepository.findOne({ where: { current_holderId: profile.id, orgRevisionId: curRevision.id, }, - relations:{ + relations: { orgRoot: true, orgChild1: true, orgChild2: true, orgChild3: true, orgChild4: true, - } + }, }); orgRootRef = curPosMaster?.orgRoot ?? null; orgChild1Ref = curPosMaster?.orgChild1 ?? null; @@ -3906,12 +4145,14 @@ export class CommandController extends Controller { await removeProfileInOrganize(profile.id, "OFFICER"); } if (clearProfile.status) { - if (profile.keycloak != null) { + if (profile.keycloak != null && profile.keycloak != "" && profile.isDelete === false) { const delUserKeycloak = await deleteUser(profile.keycloak); if (delUserKeycloak) { - profile.keycloak = _null; + // Task #228 + // profile.keycloak = _null; profile.roleKeycloaks = []; profile.isActive = false; + profile.isDelete = true; } } profile.leaveCommandId = item.commandId ?? _null; @@ -4065,9 +4306,24 @@ export class CommandController extends Controller { profile.amount = item.amount ?? _null; profile.amountSpecial = item.amountSpecial ?? _null; profile.isActive = true; + profile.isDelete = false; } await this.profileRepository.save(profile); - // Task #2190 + + // if (profile.id) { + // await this.keycloakAttributeService.clearOrgDnaAttributes( + // [profile.id], + // "PROFILE", + // ); + // } + + // update user attribute in keycloak + await updateUserAttributes(profile.keycloak ?? "", { + profileId: [profile.id], + prefix: [profile.prefix || ""], + }); + + // Task #2190 if (code && ["C-PM-17", "C-PM-18"].includes(code)) { let organizeName = ""; if (orgRootRef) { @@ -4080,29 +4336,20 @@ export class CommandController extends Controller { ].filter(Boolean); organizeName = names.join(" "); } - await PostRetireToExprofile( - // profile.citizenId ?? "", - // profile.prefix ?? "", - // profile.firstName ?? "", - // profile.lastName ?? "", - // item.commandDateAffect?.getFullYear().toString() ?? "", - // profile.position, - // profile.posType?.posTypeName ?? "", - // profile.posLevel?.posLevelName ?? "", - // item.commandDateAffect ?? new Date(), - // organizeName, - // clearProfile.retireTypeName ?? "", - "310190004095X", - "จ.ส.อ.", - "dev", - "hrms", - "2026", - "เจ้าหน้าที่จัดเก็บรายได้", - "อื่นๆ", - "C 3", - new Date(2026, 0, 1), - "สำนักงานเขตบางกอกใหญ่", - "เกษียณ" + + PostRetireToExprofile( + req, + profile.citizenId ?? "", + profile.prefix ?? "", + profile.firstName ?? "", + profile.lastName ?? "", + item.commandDateAffect?.getFullYear().toString() ?? "", + profile.position, + profile.posType?.posTypeName ?? "", + profile.posLevel?.posLevelName ?? "", + item.commandDateAffect ?? new Date(), + organizeName, + clearProfile.retireTypeName ?? "", ); } }), @@ -4155,6 +4402,7 @@ export class CommandController extends Controller { let _posNumCodeSitAbb: string = ""; const _command = await this.commandRepository.findOne({ where: { id: body.data.find((x) => x.commandId)?.commandId ?? "" }, + relations: { commandType: true }, }); if (_command) { if (_command?.isBangkok?.toLocaleUpperCase() == "OFFICE") { @@ -4194,6 +4442,7 @@ export class CommandController extends Controller { .orgRootShortName ?? ""; } } + const today = new Date().setHours(0, 0, 0, 0); await Promise.all( body.data.map(async (item) => { const profile = await this.profileEmployeeRepository.findOne({ @@ -4201,25 +4450,29 @@ export class CommandController extends Controller { // relations: ["roleKeycloaks"], relations: { roleKeycloaks: true, - posType: true , - posLevel: true - } + posType: true, + posLevel: true, + }, }); if (!profile) { throw new HttpError(HttpStatus.BAD_REQUEST, "ไม่พบ profile ดังกล่าว"); } const code = _command?.commandType?.code; - //ออกคำสั่งยกเลิกลาออกต้องอัพเดทสถานะคำสั่งลาออกเป็น CANCEL + //ออกคำสั่งยกเลิกลาออก ลบเฉพาะคนที่ขอยกเลิกลาออก if (item.resignId && code && ["C-PM-42"].includes(code)) { - const commandRecive = await this.commandReciveRepository.findOne({ - select: ["commandId"], + const commandResign = await this.commandReciveRepository.findOne({ where: { refId: item.resignId }, + relations: { command: true }, }); - if (commandRecive && commandRecive.commandId) { - await this.commandRepository.update( - { id: commandRecive?.commandId }, - { status: "CANCEL" }, - ); + const executeDate = commandResign + ? new Date(commandResign.command.commandExcecuteDate).setHours(0, 0, 0, 0) + : today; + if ( + commandResign && + _command.status !== "REPORTED" && + (_command.status !== "WAITING" || today < executeDate) + ) { + await reOrderCommandRecivesAndDelete(commandResign!.id); } } let _commandYear = item.commandYear; @@ -4272,24 +4525,24 @@ export class CommandController extends Controller { const curRevision = await this.orgRevisionRepo.findOne({ where: { orgRevisionIsCurrent: true, orgRevisionIsDraft: false }, }); - let orgRootRef = null; - let orgChild1Ref = null; - let orgChild2Ref = null; - let orgChild3Ref = null; - let orgChild4Ref = null; + let orgRootRef = null; + let orgChild1Ref = null; + let orgChild2Ref = null; + let orgChild3Ref = null; + let orgChild4Ref = null; if (curRevision) { const curPosMaster = await this.employeePosMasterRepository.findOne({ where: { current_holderId: profile.id, orgRevisionId: curRevision.id, }, - relations:{ + relations: { orgRoot: true, orgChild1: true, orgChild2: true, orgChild3: true, orgChild4: true, - } + }, }); orgRootRef = curPosMaster?.orgRoot ?? null; orgChild1Ref = curPosMaster?.orgChild1 ?? null; @@ -4299,12 +4552,14 @@ export class CommandController extends Controller { } if (clearProfile.status) { - if (profile.keycloak != null) { + if (profile.keycloak != null && profile.keycloak != "" && profile.isDelete === false) { const delUserKeycloak = await deleteUser(profile.keycloak); if (delUserKeycloak) { - profile.keycloak = _null; + // Task #228 + // profile.keycloak = _null; profile.roleKeycloaks = []; profile.isActive = false; + profile.isDelete = true; } } profile.leaveCommandId = item.commandId ?? _null; @@ -4318,7 +4573,15 @@ export class CommandController extends Controller { // profile.posLevelId = _null; } await this.profileEmployeeRepository.save(profile); - // Task #2190 + + // if (profile.id) { + // await this.keycloakAttributeService.clearOrgDnaAttributes( + // [profile.id], + // "PROFILE_EMPLOYEE", + // ); + // } + + // Task #2190 if (code && ["C-PM-23", "C-PM-43"].includes(code)) { let organizeName = ""; if (orgRootRef) { @@ -4331,29 +4594,19 @@ export class CommandController extends Controller { ].filter(Boolean); organizeName = names.join(" "); } - await PostRetireToExprofile( - // profile.citizenId ?? "", - // profile.prefix ?? "", - // profile.firstName ?? "", - // profile.lastName ?? "", - // item.commandDateAffect?.getFullYear().toString() ?? "", - // profile.position, - // profile.posType?.posTypeName ?? "", - // `${profile.posType?.posTypeShortName} ${profile.posLevel?.posLevelName}`, - // item.commandDateAffect ?? new Date(), - // organizeName, - // clearProfile.retireTypeName ?? "", - "310190004095X", - "จ.ส.อ.", - "dev", - "hrms", - "2026", - "เจ้าหน้าที่จัดเก็บรายได้", - "อื่นๆ", - "C 3", - new Date(2026, 0, 1), - "สำนักงานเขตบางกอกใหญ่", - "เกษียณ" + PostRetireToExprofile( + req, + profile.citizenId ?? "", + profile.prefix ?? "", + profile.firstName ?? "", + profile.lastName ?? "", + item.commandDateAffect?.getFullYear().toString() ?? "", + profile.position, + profile.posType?.posTypeName ?? "", + `${profile.posType?.posTypeShortName} ${profile.posLevel?.posLevelName}`, + item.commandDateAffect ?? new Date(), + organizeName, + clearProfile.retireTypeName ?? "", ); } }), @@ -4456,8 +4709,8 @@ export class CommandController extends Controller { relations: { roleKeycloaks: true, posType: true, - posLevel: true - } + posLevel: true, + }, }); if (!profile) { throw new HttpError(HttpStatusCode.NOT_FOUND, "ไม่พบข้อมูลทะเบียนประวัตินี้"); @@ -4521,12 +4774,14 @@ export class CommandController extends Controller { const clearProfile = await checkCommandType(String(item.commandId)); const _null: any = null; if (clearProfile.status) { - if (profile.keycloak != null) { + if (profile.keycloak != null && profile.keycloak != "" && profile.isDelete === false) { const delUserKeycloak = await deleteUser(profile.keycloak); if (delUserKeycloak) { - profile.keycloak = _null; + // Task #228 + // profile.keycloak = _null; profile.roleKeycloaks = []; profile.isActive = false; + profile.isDelete = true; } } profile.isLeave = item.isLeave; @@ -4544,6 +4799,13 @@ export class CommandController extends Controller { profile.amount = item.amount ?? _null; profile.amountSpecial = item.amountSpecial ?? _null; await this.profileRepository.save(profile, { data: req }); + + // if (profile.id) { + // await this.keycloakAttributeService.clearOrgDnaAttributes( + // [profile.id], + // "PROFILE", + // ); + // } } Object.assign(data, { ...item, ...meta }); const history = new ProfileSalaryHistory(); @@ -4605,29 +4867,19 @@ export class CommandController extends Controller { ].filter(Boolean); organizeName = names.join(" "); } - await PostRetireToExprofile( - // profile.citizenId ?? "", - // profile.prefix ?? "", - // profile.firstName ?? "", - // profile.lastName ?? "", - // item.commandDateAffect?.getFullYear().toString() ?? "", - // profile.position, - // profile.posType?.posTypeName ?? "", - // profile.posLevel?.posLevelName ?? "", - // item.commandDateAffect ?? new Date(), - // organizeName, - // clearProfile.retireTypeName ?? "", - "310190004095X", - "จ.ส.อ.", - "dev", - "hrms", - "2026", - "เจ้าหน้าที่จัดเก็บรายได้", - "อื่นๆ", - "C 3", - new Date(2026, 0, 1), - "สำนักงานเขตบางกอกใหญ่", - "เกษียณ" + PostRetireToExprofile( + req, + profile.citizenId ?? "", + profile.prefix ?? "", + profile.firstName ?? "", + profile.lastName ?? "", + item.commandDateAffect?.getFullYear().toString() ?? "", + profile.position, + profile.posType?.posTypeName ?? "", + profile.posLevel?.posLevelName ?? "", + item.commandDateAffect ?? new Date(), + organizeName, + clearProfile.retireTypeName ?? "", ); } } @@ -4848,14 +5100,14 @@ export class CommandController extends Controller { orgRevisionIsDraft: false, }, }); - let orgRootRef = null; + let orgRootRef = null; let orgChild1Ref = null; let orgChild2Ref = null; let orgChild3Ref = null; let orgChild4Ref = null; let profile; - let isEmployee:boolean = false; - let retireTypeName:string = ""; + let isEmployee: boolean = false; + let retireTypeName: string = ""; // ขรก. if (item.profileType && item.profileType.trim().toUpperCase() == "OFFICER") { profile = await this.profileRepository.findOne({ @@ -4897,11 +5149,11 @@ export class CommandController extends Controller { const orgRevisionRef = profile?.current_holders?.find((x) => x.orgRevisionId == orgRevision?.id) ?? null; - orgRootRef = orgRevisionRef?.orgRoot ?? null; - orgChild1Ref = orgRevisionRef?.orgChild1 ?? null; - orgChild2Ref = orgRevisionRef?.orgChild2 ?? null; - orgChild3Ref = orgRevisionRef?.orgChild3 ?? null; - orgChild4Ref = orgRevisionRef?.orgChild4 ?? null; + orgRootRef = orgRevisionRef?.orgRoot ?? null; + orgChild1Ref = orgRevisionRef?.orgChild1 ?? null; + orgChild2Ref = orgRevisionRef?.orgChild2 ?? null; + orgChild3Ref = orgRevisionRef?.orgChild3 ?? null; + orgChild4Ref = orgRevisionRef?.orgChild4 ?? null; let position = profile.current_holders @@ -5020,12 +5272,14 @@ export class CommandController extends Controller { const clearProfile = await checkCommandType(String(item.commandId)); if (clearProfile.status) { retireTypeName = clearProfile.retireTypeName ?? ""; - if (_profile.keycloak != null) { + if (_profile.keycloak != null && _profile.keycloak != "" && _profile.isDelete === false) { const delUserKeycloak = await deleteUser(_profile.keycloak); if (delUserKeycloak) { - _profile.keycloak = _null; + // Task #228 + // _profile.keycloak = _null; _profile.roleKeycloaks = []; _profile.isActive = false; + _profile.isDelete = true; } } _profile.leaveCommandId = item.commandId ?? _null; @@ -5039,8 +5293,14 @@ export class CommandController extends Controller { // _profile.posLevelId = _null; } await this.profileRepository.save(_profile); + // if (_profile.id) { + // await this.keycloakAttributeService.clearOrgDnaAttributes( + // [_profile.id], + // "PROFILE", + // ); + // } } - } + } // ลูกจ้าง else { isEmployee = true; @@ -5075,12 +5335,12 @@ export class CommandController extends Controller { const nextOrder = lastSalary ? lastSalary.order + 1 : 1; const orgRevisionRef = profile?.current_holders?.find((x) => x.orgRevisionId == orgRevision?.id) ?? null; - orgRootRef = orgRevisionRef?.orgRoot ?? null; - orgChild1Ref = orgRevisionRef?.orgChild1 ?? null; - orgChild2Ref = orgRevisionRef?.orgChild2 ?? null; - orgChild3Ref = orgRevisionRef?.orgChild3 ?? null; - orgChild4Ref = orgRevisionRef?.orgChild4 ?? null; - + orgRootRef = orgRevisionRef?.orgRoot ?? null; + orgChild1Ref = orgRevisionRef?.orgChild1 ?? null; + orgChild2Ref = orgRevisionRef?.orgChild2 ?? null; + orgChild3Ref = orgRevisionRef?.orgChild3 ?? null; + orgChild4Ref = orgRevisionRef?.orgChild4 ?? null; + // ประวัติตำแหน่ง const data = new ProfileSalary(); data.posNumCodeSit = _posNumCodeSit; @@ -5197,12 +5457,14 @@ export class CommandController extends Controller { const clearProfile = await checkCommandType(String(item.commandId)); if (clearProfile.status) { retireTypeName = clearProfile.retireTypeName ?? ""; - if (_profile.keycloak != null) { + if (_profile.keycloak != null && _profile.keycloak != "" && _profile.isDelete === false) { const delUserKeycloak = await deleteUser(_profile.keycloak); if (delUserKeycloak) { - _profile.keycloak = _null; + // Task #228 + // _profile.keycloak = _null; _profile.roleKeycloaks = []; _profile.isActive = false; + _profile.isDelete = true; } } _profile.leaveCommandId = item.commandId ?? _null; @@ -5216,6 +5478,12 @@ export class CommandController extends Controller { // _profile.posLevelId = _null; } await this.profileEmployeeRepository.save(_profile); + // if (_profile.id) { + // await this.keycloakAttributeService.clearOrgDnaAttributes( + // [_profile.id], + // "PROFILE_EMPLOYEE", + // ); + // } } } // Task #2190 @@ -5231,32 +5499,23 @@ export class CommandController extends Controller { ].filter(Boolean); organizeName = names.join(" "); } - let _posLevelName: string = !isEmployee - ? `${profile.posLevel?.posLevelName}` - : `${profile.posType?.posTypeName} ${profile.posLevel?.posLevelName}`; - await PostRetireToExprofile( - // profile.citizenId ?? "", - // profile.prefix ?? "", - // profile.firstName ?? "", - // profile.lastName ?? "", - // item.commandDateAffect?.getFullYear().toString() ?? "", - // profile.position, - // profile.posType?.posTypeName ?? "", - // _posLevelName, - // item.commandDateAffect ?? new Date(), - // organizeName, - // retireTypeName, - "310190004095X", - "จ.ส.อ.", - "dev", - "hrms", - "2026", - "เจ้าหน้าที่จัดเก็บรายได้", - "อื่นๆ", - "C 3", - new Date(2026, 0, 1), - "สำนักงานเขตบางกอกใหญ่", - "เกษียณ" + let _posLevelName: string = !isEmployee + ? `${profile.posLevel?.posLevelName}` + : `${profile.posType?.posTypeName} ${profile.posLevel?.posLevelName}`; + + PostRetireToExprofile( + req, + profile.citizenId ?? "", + profile.prefix ?? "", + profile.firstName ?? "", + profile.lastName ?? "", + item.commandDateAffect?.getFullYear().toString() ?? "", + profile.position, + profile.posType?.posTypeName ?? "", + _posLevelName, + item.commandDateAffect ?? new Date(), + organizeName, + retireTypeName, ); } }), @@ -5538,12 +5797,14 @@ export class CommandController extends Controller { } const clearProfile = await checkCommandType(String(item.commandId)); if (clearProfile.status) { - if (_profile.keycloak != null) { + if (_profile.keycloak != null && _profile.keycloak != "" && _profile.isDelete === false) { const delUserKeycloak = await deleteUser(_profile.keycloak); if (delUserKeycloak) { - _profile.keycloak = _null; + // Task #228 + // _profile.keycloak = _null; _profile.roleKeycloaks = []; _profile.isActive = false; + _profile.isDelete = true; } } _profile.leaveCommandId = item.commandId ?? _null; @@ -5557,6 +5818,13 @@ export class CommandController extends Controller { // _profile.posLevelId = _null; } await this.profileEmployeeRepository.save(_profile); + + // if (_profile.id) { + // await this.keycloakAttributeService.clearOrgDnaAttributes( + // [_profile.id], + // "PROFILE_EMPLOYEE", + // ); + // } } }), ); @@ -5591,10 +5859,15 @@ export class CommandController extends Controller { ) { let _posNumCodeSit: string = ""; let _posNumCodeSitAbb: string = ""; + let commandType: any = ""; const _command = await this.commandRepository.findOne({ where: { id: body.data.find((x) => x.commandId)?.commandId ?? "" }, }); if (_command) { + commandType = await this.commandTypeRepository.findOne({ + select: { code: true }, + where: { id: _command.commandTypeId }, + }); if (_command?.isBangkok?.toLocaleUpperCase() == "OFFICE") { const orgRootDeputy = await this.orgRootRepository.findOne({ where: { @@ -5632,11 +5905,14 @@ export class CommandController extends Controller { .orgRootShortName ?? ""; } } + // const leaveType = await this.leaveType.findOne({ + // select: { id: true, limit: true, code: true }, + // where: { code: "LV-005" } + // }); await Promise.all( body.data.map(async (item) => { const profile = await this.profileRepository.findOne({ relations: [ - "profileSalary", "posType", "posLevel", "current_holders", @@ -5647,57 +5923,30 @@ export class CommandController extends Controller { "current_holders.orgChild4", ], where: { id: item.profileId }, - order: { - profileSalary: { - order: "DESC", - }, - }, }); if (!profile) { throw new HttpError(HttpStatusCode.NOT_FOUND, "ไม่พบข้อมูลทะเบียนประวัตินี้"); } - + const lastSalary = await this.salaryRepo.findOne({ + where: { profileId: item.profileId }, + select: ["order"], + order: { order: "DESC" }, + }); + const nextOrder = lastSalary ? lastSalary.order + 1 : 1; const orgRevision = await this.orgRevisionRepo.findOne({ - where: { - orgRevisionIsCurrent: true, - orgRevisionIsDraft: false, - }, + where: { orgRevisionIsCurrent: true, orgRevisionIsDraft: false }, }); const orgRevisionRef = profile?.current_holders?.find((x) => x.orgRevisionId == orgRevision?.id) ?? null; - const orgRootRef = orgRevisionRef?.orgRoot ?? null; - const orgChild1Ref = orgRevisionRef?.orgChild1 ?? null; - const orgChild2Ref = orgRevisionRef?.orgChild2 ?? null; - const orgChild3Ref = orgRevisionRef?.orgChild3 ?? null; - const orgChild4Ref = orgRevisionRef?.orgChild4 ?? null; const shortName = - !profile.current_holders || profile.current_holders.length == 0 - ? null - : profile.current_holders.find((x) => x.orgRevisionId == orgRevision?.id) != null && - profile.current_holders.find((x) => x.orgRevisionId == orgRevision?.id) - ?.orgChild4 != null - ? `${profile.current_holders.find((x) => x.orgRevisionId == orgRevision?.id)?.orgChild4.orgChild4ShortName}` - : profile.current_holders.find((x) => x.orgRevisionId == orgRevision?.id) != null && - profile.current_holders.find((x) => x.orgRevisionId == orgRevision?.id) - ?.orgChild3 != null - ? `${profile.current_holders.find((x) => x.orgRevisionId == orgRevision?.id)?.orgChild3.orgChild3ShortName}` - : profile.current_holders.find((x) => x.orgRevisionId == orgRevision?.id) != null && - profile.current_holders.find((x) => x.orgRevisionId == orgRevision?.id) - ?.orgChild2 != null - ? `${profile.current_holders.find((x) => x.orgRevisionId == orgRevision?.id)?.orgChild2.orgChild2ShortName}` - : profile.current_holders.find((x) => x.orgRevisionId == orgRevision?.id) != - null && - profile.current_holders.find((x) => x.orgRevisionId == orgRevision?.id) - ?.orgChild1 != null - ? `${profile.current_holders.find((x) => x.orgRevisionId == orgRevision?.id)?.orgChild1.orgChild1ShortName}` - : profile.current_holders.find((x) => x.orgRevisionId == orgRevision?.id) != - null && - profile.current_holders.find((x) => x.orgRevisionId == orgRevision?.id) - ?.orgRoot != null - ? `${profile.current_holders.find((x) => x.orgRevisionId == orgRevision?.id)?.orgRoot.orgRootShortName}` - : null; - const posNo = `${profile.current_holders.find((x) => x.orgRevisionId == orgRevision?.id)?.posMasterNo}`; + orgRevisionRef?.orgChild4?.orgChild4ShortName ?? + orgRevisionRef?.orgChild3?.orgChild3ShortName ?? + orgRevisionRef?.orgChild2?.orgChild2ShortName ?? + orgRevisionRef?.orgChild1?.orgChild1ShortName ?? + orgRevisionRef?.orgRoot?.orgRootShortName ?? + null; + const posNo = orgRevisionRef?.posMasterNo?.toString() ?? null; let position = profile.current_holders .filter((x) => x.orgRevisionId == orgRevision?.id)[0] @@ -5717,17 +5966,12 @@ export class CommandController extends Controller { amountSpecial: item.amountSpecial ? item.amountSpecial : null, positionSalaryAmount: item.positionSalaryAmount ? item.positionSalaryAmount : null, mouthSalaryAmount: item.mouthSalaryAmount ? item.mouthSalaryAmount : null, - order: - profile.profileSalary.length >= 0 - ? profile.profileSalary.length > 0 - ? profile.profileSalary[0].order + 1 - : 1 - : null, - orgRoot: orgRootRef?.orgRootName ?? null, - orgChild1: orgChild1Ref?.orgChild1Name ?? null, - orgChild2: orgChild2Ref?.orgChild2Name ?? null, - orgChild3: orgChild3Ref?.orgChild3Name ?? null, - orgChild4: orgChild4Ref?.orgChild4Name ?? null, + order: nextOrder, + orgRoot: orgRevisionRef?.orgRoot?.orgRootName ?? null, + orgChild1: orgRevisionRef?.orgChild1?.orgChild1Name ?? null, + orgChild2: orgRevisionRef?.orgChild2?.orgChild2Name ?? null, + orgChild3: orgRevisionRef?.orgChild3?.orgChild3Name ?? null, + orgChild4: orgRevisionRef?.orgChild4?.orgChild4Name ?? null, createdUserId: req.user.sub, createdFullName: req.user.name, lastUpdateUserId: req.user.sub, @@ -5753,19 +5997,29 @@ export class CommandController extends Controller { await this.salaryHistoryRepo.save(history); }), ); - const checkCommandType = await this.commandRepository.findOne({ - where: { id: body.data.length > 0 ? body.data[0].commandId?.toString() : "" }, - relations: ["commandType"], - }); - if (checkCommandType?.commandType.code == "C-PM-11") { - const profile = await this.profileRepository.find({ - where: { id: In(body.data.map((x) => x.profileId)) }, - }); - const data = profile.map((x) => ({ - ...x, - isProbation: false, - })); - await this.profileRepository.save(data); + + if (commandType && String(commandType.code) == "C-PM-11") { + const profileIds = body.data.map((x) => x.profileId); + await this.profileRepository.update({ id: In(profileIds) }, { isProbation: false }); + // // Task #2304 อัปเดตจำนวนสิทธิ์การลา เมื่อผ่านทดลองงานฯ + // if (leaveType != null) { + // await Promise.all( + // body.data.map((item) => + // new CallAPI().PutData(req, `/leave-beginning/schedule`, { + // profileId: item.profileId, + // leaveTypeId: leaveType.id, + // leaveYear: item.commandYear, + // leaveDays: leaveType.limit, + // leaveDaysUsed: 0, + // leaveCount: 0, + // beginningLeaveDays: 0, + // beginningLeaveCount: 0, + // }) + // .then(() => {}) + // .catch(() => {}) + // ) + // ); + // } } return new HttpSuccess(); } @@ -5982,12 +6236,14 @@ export class CommandController extends Controller { const clearProfile = await checkCommandType(String(item.commandId)); const _null: any = null; if (clearProfile.status) { - if (_profile.keycloak != null) { + if (_profile.keycloak != null && _profile.keycloak != "" && _profile.isDelete === false) { const delUserKeycloak = await deleteUser(_profile.keycloak); if (delUserKeycloak) { - _profile.keycloak = _null; + // Task #228 + // _profile.keycloak = _null; _profile.roleKeycloaks = []; _profile.isActive = false; + _profile.isDelete = true; } } _profile.leaveCommandId = item.commandId ?? _null; @@ -6004,6 +6260,14 @@ export class CommandController extends Controller { this.profileRepository.save(_profile), this.salaryRepo.save(profileSalary), ]); + + // if (profile.id) { + // await this.keycloakAttributeService.clearOrgDnaAttributes( + // [profile.id], + // "PROFILE", + // ); + // } + const history = new ProfileSalaryHistory(); Object.assign(history, { ...profileSalary, id: undefined }); history.profileSalaryId = profileSalary.id; @@ -6020,33 +6284,22 @@ export class CommandController extends Controller { ].filter(Boolean); organizeName = names.join(" "); } - await PostRetireToExprofile( - // profile.citizenId ?? "", - // profile.prefix ?? "", - // profile.firstName ?? "", - // profile.lastName ?? "", - // item.commandDateAffect?.getFullYear().toString() ?? "", - // profile.position, - // profile.posType?.posTypeName ?? "", - // profile.posLevel?.posLevelName ?? "", - // item.commandDateAffect ?? new Date(), - // organizeName, - // clearProfile.retireTypeName ?? "", - "310190004095X", - "จ.ส.อ.", - "dev", - "hrms", - "2026", - "เจ้าหน้าที่จัดเก็บรายได้", - "อื่นๆ", - "C 3", - new Date(2026, 0, 1), - "สำนักงานเขตบางกอกใหญ่", - "เกษียณ" + PostRetireToExprofile( + req, + profile.citizenId ?? "", + profile.prefix ?? "", + profile.firstName ?? "", + profile.lastName ?? "", + item.commandDateAffect?.getFullYear().toString() ?? "", + profile.position, + profile.posType?.posTypeName ?? "", + profile.posLevel?.posLevelName ?? "", + item.commandDateAffect ?? new Date(), + organizeName, + clearProfile.retireTypeName ?? "", ); }), ); - return new HttpSuccess(); } @@ -6094,7 +6347,6 @@ export class CommandController extends Controller { }); const list = await getRoles(); if (!Array.isArray(list)) throw new Error("Failed. Cannot get role(s) data from the server."); - const _null: any = null; let _posNumCodeSit: string = ""; let _posNumCodeSitAbb: string = ""; const _command = await this.commandRepository.findOne({ @@ -6138,17 +6390,17 @@ export class CommandController extends Controller { .orgRootShortName ?? ""; } } + const before = null; + const meta = { + createdUserId: req.user.sub, + createdFullName: req.user.name, + lastUpdateUserId: req.user.sub, + lastUpdateFullName: req.user.name, + createdAt: new Date(), + lastUpdatedAt: new Date(), + }; await Promise.all( body.data.map(async (item) => { - const before = null; - const meta = { - createdUserId: req.user.sub, - createdFullName: req.user.name, - lastUpdateUserId: req.user.sub, - lastUpdateFullName: req.user.name, - createdAt: new Date(), - lastUpdatedAt: new Date(), - }; const _null: any = null; if (item.bodyProfile.posLevelId === "") item.bodyProfile.posLevelId = null; if (item.bodyProfile.posTypeId === "") item.bodyProfile.posTypeId = null; @@ -6199,13 +6451,6 @@ export class CommandController extends Controller { if (checkUser.length == 0) { let password = item.bodyProfile.citizenId; if (item.bodyProfile.birthDate != null) { - // const gregorianYear = item.bodyProfile.birthDate.getFullYear() + 543; - - // const formattedDate = - // item.bodyProfile.birthDate.toISOString().slice(8, 10) + - // item.bodyProfile.birthDate.toISOString().slice(5, 7) + - // gregorianYear; - // password = formattedDate; const _date = new Date(item.bodyProfile.birthDate.toDateString()) .getDate() .toString() @@ -6255,6 +6500,7 @@ export class CommandController extends Controller { relations: ["roleKeycloaks", "profileInsignias", "profileAvatars"], }); let _oldInsigniaIds: string[] = []; + //ลูกจ้างประจำ หรือ บุคคลภายนอก if (!profile) { //กรณีลูกจ้างประจำมาสอบเป็นข้าราชการ ต้อง update สถานะโปรไฟล์เดิม let profileEmployee: any = await this.profileEmployeeRepository.findOne({ @@ -6286,16 +6532,17 @@ export class CommandController extends Controller { await this.salaryHistoryRepo.save(history, { data: req }); if (profileEmployee.profileInsignias.length > 0) { - _oldInsigniaIds = profileEmployee.profileInsignias.map((x: any) => x.id); + _oldInsigniaIds = profileEmployee.profileInsignias?.map((x: any) => x.id) ?? []; } await removeProfileInOrganize(profileEmployee.id, "EMPLOYEE"); if (profileEmployee.keycloak != null) { - const delUserKeycloak = await deleteUser(profileEmployee.keycloak); - if (delUserKeycloak) { - profileEmployee.keycloak = _null; - profileEmployee.roleKeycloaks = []; - profileEmployee.isActive = false; - } + // const delUserKeycloak = await deleteUser(profileEmployee.keycloak); + // if (delUserKeycloak) { + // Task #228 + // profileEmployee.keycloak = _null; + profileEmployee.roleKeycloaks = []; + profileEmployee.isActive = false; + // } } profileEmployee.isLeave = true; profileEmployee.leaveReason = "บรรจุข้าราชการ"; @@ -6347,6 +6594,11 @@ export class CommandController extends Controller { profile.phone = item.bodyProfile.phone ?? null; await this.profileRepository.save(profile); + // update user attribute in keycloak + await updateUserAttributes(profile.keycloak ?? "", { + profileId: [profile.id], + prefix: [profile.prefix || ""], + }); setLogDataDiff(req, { before, after: profile }); } //ขรก.ในระบบ หรือ ขรก.ในระบบที่สถานะพ้นจากราชการ @@ -6357,7 +6609,7 @@ export class CommandController extends Controller { ["PLACEMENT_TRANSFER", "RETIRE_RESIGN"].includes(profile.leaveType) ) { if (profile.profileInsignias.length > 0) { - _oldInsigniaIds = profile.profileInsignias.map((x: any) => x.id); + _oldInsigniaIds = profile.profileInsignias?.map((x: any) => x.id) ?? []; } profile = Object.assign({ ...item.bodyProfile, ...meta }); profile.dateRetire = _dateRetire; @@ -6408,6 +6660,7 @@ export class CommandController extends Controller { profile.isLeave = item.bodyProfile.isLeave; profile.isRetirement = false; profile.isActive = true; + profile.isDelete = false; profile.dateLeave = _null; profile.dateRetire = _dateRetire; profile.dateRetireLaw = _dateRetireLaw; @@ -6504,7 +6757,7 @@ export class CommandController extends Controller { profileEdu.profileId = profile.id; const educationLevel = await this.profileEducationRepo.findOne({ select: ["id", "level", "profileId"], - where: { profileId: profile.id }, + where: { profileId: profile.id, isDeleted: false }, order: { level: "DESC" }, }); profileEdu.level = educationLevel == null ? 1 : educationLevel.level + 1; @@ -6515,7 +6768,6 @@ export class CommandController extends Controller { }), ); } - //Certificates if (item.bodyCertificates && item.bodyCertificates.length > 0) { await Promise.all( @@ -6532,7 +6784,6 @@ export class CommandController extends Controller { }), ); } - //FamilyCouple if (item.bodyMarry != null) { const profileCouple = new ProfileFamilyCouple(); @@ -6554,7 +6805,6 @@ export class CommandController extends Controller { coupleHistory.profileFamilyCoupleId = profileCouple.id; await this.profileFamilyCoupleHistoryRepo.save(coupleHistory, { data: req }); } - //FamilyFather if (item.bodyFather != null) { const profileFather = new ProfileFamilyFather(); @@ -6575,7 +6825,6 @@ export class CommandController extends Controller { fatherHistory.profileFamilyFatherId = profileFather.id; await this.profileFamilyFatherHistoryRepo.save(fatherHistory, { data: req }); } - //FamilyMother if (item.bodyMother != null) { const profileMother = new ProfileFamilyMother(); @@ -6596,7 +6845,6 @@ export class CommandController extends Controller { motherHistory.profileFamilyMotherId = profileMother.id; await this.profileFamilyMotherHistoryRepo.save(motherHistory, { data: req }); } - //Salary if (item.bodySalarys && item.bodySalarys != null) { const dest_item = await this.salaryRepo.findOne({ @@ -6623,12 +6871,36 @@ export class CommandController extends Controller { } //Position if (item.bodyPosition && item.bodyPosition != null) { - const posMaster = await this.posMasterRepository.findOne({ - where: { id: item.bodyPosition.posmasterId }, + // STEP 1: หา posMaster ที่จะใช้งานตาม id ที่ส่งมา (อาจเป็นตำแหน่งเก่าหรือใหม่ก็ได้) + let posMaster = await this.posMasterRepository.findOne({ + where: { + id: item.bodyPosition.posmasterId, + }, + relations: { orgRevision: true } }); + + // เช็คว่า posMaster ที่หามาอยู่ในโครงสร้างปัจจุบันหรือไม่ + const isCurrent = posMaster?.orgRevision?.orgRevisionIsCurrent === true && + posMaster?.orgRevision?.orgRevisionIsDraft === false; + + // ถ้าไม่อยู่ในโครงสร้างปัจจุบัน ให้หาตัวใหม่จาก ancestorDNA + if (!isCurrent && posMaster?.ancestorDNA) { + posMaster = await this.posMasterRepository.findOne({ + where: { + ancestorDNA: posMaster.ancestorDNA, + orgRevision: { + orgRevisionIsCurrent: true, + orgRevisionIsDraft: false + } + }, + relations: { orgRevision: true } + }); + } + if (posMaster == null) throw new HttpError(HttpStatusCode.NOT_FOUND, "ไม่พบข้อมูลตำแหน่งนี้"); + // STEP 2: เคลียร์ข้อมูลตำแหน่งเก่าที่ครองอยู่ ในโครงสร้างปัจจุบัน const posMasterOld = await this.posMasterRepository.findOne({ where: { current_holderId: profile.id, @@ -6636,10 +6908,12 @@ export class CommandController extends Controller { }, }); if (posMasterOld != null) { + // เคลียร์คนครองเก่าออกจากตำแหน่งเดิม posMasterOld.current_holderId = null; posMasterOld.lastUpdatedAt = new Date(); } + // หา position เก่าที่เลือกไว้ แล้วเคลียร์การเลือก const positionOld = await this.positionRepository.findOne({ where: { posMasterId: posMasterOld?.id, @@ -6651,9 +6925,10 @@ export class CommandController extends Controller { await this.positionRepository.save(positionOld); } + // STEP 3: เคลียร์ position ที่เลือกไว้อื่นๆ ใน posMaster ตัวใหม่ const checkPosition = await this.positionRepository.find({ where: { - posMasterId: item.bodyPosition.posmasterId, + posMasterId: posMaster.id, positionIsSelected: true, }, }); @@ -6665,6 +6940,7 @@ export class CommandController extends Controller { await this.positionRepository.save(clearPosition); } + // STEP 4: กำหนดคนครองใหม่ให้กับ posMaster posMaster.current_holderId = profile.id; posMaster.lastUpdatedAt = new Date(); // posMaster.conditionReason = _null; @@ -6675,28 +6951,63 @@ export class CommandController extends Controller { } await this.posMasterRepository.save(posMaster); - const positionNew = await this.positionRepository.findOne({ - where: { - id: item.bodyPosition.positionId, - posMasterId: item.bodyPosition.posmasterId, - }, - }); + // STEP 5: กำหนด position ใหม่ + // เช็คว่า posMaster เปลี่ยนจากเก่าเป็นใหม่หรือไม่ + const originalPosMasterId = item.bodyPosition.posmasterId; + const isPosMasterChanged = originalPosMasterId !== posMaster.id; + + let positionNew = null; + + if (isPosMasterChanged) { + // posMaster เปลี่ยน ต้องหา position ใหม่จากคุณสมบัติของ position เก่า + // 1. หา position เก่าจาก id ที่ส่งมา + const positionOld = await this.positionRepository.findOne({ + where: { id: item.bodyPosition.positionId }, + }); + + if (positionOld) { + // 2. ใช้ posTypeId + posLevelId + positionName หา position ใหม่ใน posMaster ตัวใหม่ + positionNew = await this.positionRepository.findOne({ + where: { + posMasterId: posMaster.id, // ใช้ posMaster ตัวใหม่ + posTypeId: positionOld.posTypeId, + posLevelId: positionOld.posLevelId, + positionName: positionOld.positionName, + }, + }); + } + } else { + // posMaster ไม่เปลี่ยน - ใช้วิธีเดิม + positionNew = await this.positionRepository.findOne({ + where: { + id: item.bodyPosition.positionId, + posMasterId: posMaster.id, + }, + }); + } + + // ถ้าไม่ใช่ตำแหน่งนั่งทับ (isSit = false) ถึงจะอัพเดทตำแหน่งในทะเบียนประวัติ if (positionNew != null) { positionNew.positionIsSelected = true; - profile.posLevelId = positionNew.posLevelId; - profile.posTypeId = positionNew.posTypeId; - profile.position = positionNew.positionName; - profile.dateStart = new Date(); - await this.profileRepository.save(profile, { data: req }); - setLogDataDiff(req, { before, after: profile }); + if(!posMaster.isSit){ + profile.posLevelId = positionNew.posLevelId; + profile.posTypeId = positionNew.posTypeId; + profile.position = positionNew.positionName; + // profile.dateStart = new Date(); + await this.profileRepository.save(profile, { data: req }); + setLogDataDiff(req, { before, after: profile }); + } await this.positionRepository.save(positionNew, { data: req }); } - await CreatePosMasterHistoryOfficer(posMaster.id, req); + // await CreatePosMasterHistoryOfficer(posMaster.id, req); + await CreatePosMasterHistoryOfficer(posMaster.id, req, null, { + positionId: positionNew?.id + }); } // Insignia if (_oldInsigniaIds.length > 0) { const _insignias = await this.insigniaRepo.find({ - where: { id: In(_oldInsigniaIds) }, + where: { id: In(_oldInsigniaIds), isDeleted: false }, order: { createdAt: "ASC" }, }); for (const oldInsignia of _insignias) { @@ -7173,8 +7484,8 @@ export class CommandController extends Controller { profile.employeeClass = "PERM"; const _null: any = null; profile.employeeWage = item.amount == null ? _null : item.amount.toString(); - profile.dateStart = new Date(); - profile.dateAppoint = new Date(); + profile.dateStart = _command ? _command.commandExcecuteDate : new Date(); + profile.dateAppoint = _command ? _command.commandExcecuteDate : new Date(); profile.amount = item.amount == null ? _null : item.amount; profile.amountSpecial = item.amountSpecial == null ? _null : item.amountSpecial; _reqBody.push({ @@ -7350,6 +7661,7 @@ export class CommandController extends Controller { where: { profileId: item.posMasterChild.current_holderId, status: true, + isDeleted: false, }, }); diff --git a/src/controllers/CommandOperatorController.ts b/src/controllers/CommandOperatorController.ts new file mode 100644 index 00000000..1a461ab3 --- /dev/null +++ b/src/controllers/CommandOperatorController.ts @@ -0,0 +1,224 @@ +import { + Controller, + Post, + Delete, + Route, + Security, + Tags, + Body, + Path, + Request, + Response, + Get +} from "tsoa"; +import { LessThan, MoreThan } from "typeorm"; +import { AppDataSource } from "../database/data-source"; +import HttpSuccess from "../interfaces/http-success"; +import HttpStatusCode from "../interfaces/http-status"; +import HttpError from "../interfaces/http-error"; +import { Command } from "../entities/Command"; +import { CommandOperator, CreateCommandOperatorDto } from "../entities/CommandOperator"; +import { RequestWithUser } from "../middlewares/user"; + +@Route("api/v1/org/commandOperator") +@Tags("CommandOperator") +@Security("bearerAuth") +@Response( + HttpStatusCode.INTERNAL_SERVER_ERROR, + "เกิดข้อผิดพลาด ไม่สามารถแสดงรายการได้ กรุณาลองใหม่ในภายหลัง", +) +export class CommandOperatorController extends Controller { + private commandRepo = AppDataSource.getRepository(Command); + private commandOperatorRepo = AppDataSource.getRepository(CommandOperator); + + /** + * API รายชื่อเจ้าหน้าที่ดำเนินการที่คำสั่ง + * @summary API รายชื่อเจ้าหน้าที่ดำเนินการที่คำสั่ง + * @param commandId คีย์คำสั่ง + */ + @Get("{commandId}") + async getCommandOperatorByCommandId( + @Path() commandId: string + ) { + const command = await this.commandRepo.findOne({ + where: { id: commandId }, + select: { id: true }, + }); + if (!command) { + throw new HttpError(HttpStatusCode.NOT_FOUND, "ไม่พบข้อมูลคำสั่งนี้"); + } + const commandOperators = await this.commandOperatorRepo.find({ + where: { commandId: command.id }, + order: { orderNo: "ASC" }, + }); + return new HttpSuccess(commandOperators); + } + + /** + * API สลับลำดับเจ้าหน้าที่ดำเนินการ (UP / DOWN) + * @summary API สลับลำดับเจ้าหน้าที่ดำเนินการ (UP / DOWN) + * @param direction สลับขึ้นหรือลง (UP / DOWN) + * @param operatorId คีย์เจ้าหน้าที่ดำเนินการ + */ + @Get("swap/{direction}/{operatorId}") + async swapCommandOperator( + @Path() direction: string, + @Path() operatorId: string, + ) { + const source = await this.commandOperatorRepo.findOne({ + where: { id: operatorId }, + }); + + if (!source) { + throw new HttpError(HttpStatusCode.NOT_FOUND, "ไม่พบข้อมูลเจ้าหน้าที่"); + } + + const sourceOrder = source.orderNo; + const isUp = direction.trim().toUpperCase() === "UP"; + + let dest: CommandOperator | null; + + if (isUp) { + dest = await this.commandOperatorRepo.findOne({ + where: { + commandId: source.commandId, + orderNo: LessThan(sourceOrder), + }, + order: { orderNo: "DESC" }, + }); + } else { + dest = await this.commandOperatorRepo.findOne({ + where: { + commandId: source.commandId, + orderNo: MoreThan(sourceOrder), + }, + order: { orderNo: "ASC" }, + }); + } + + // ถ้าไม่มีตัวให้สลับ (บนสุด / ล่างสุด) + if (!dest) { + return new HttpSuccess(); + } + + // swap + const temp = source.orderNo; + source.orderNo = dest.orderNo; + dest.orderNo = temp; + + await Promise.all([ + this.commandOperatorRepo.save(source), + this.commandOperatorRepo.save(dest), + ]); + + return new HttpSuccess(); + } + + /** + * API เพิ่มเจ้าหน้าที่ดำเนินการที่คำสั่ง + * @summary API เพิ่มเจ้าหน้าที่ดำเนินการที่คำสั่ง + * @param commandId คีย์คำสั่ง + */ + @Post("{commandId}") + async createCommandOperators( + @Path() commandId: string, + @Request() request: RequestWithUser, + @Body() body: CreateCommandOperatorDto, + ) { + const command = await this.commandRepo.findOne({ + where: { id: commandId }, + select: { id: true }, + }); + + if (!command) { + throw new HttpError(HttpStatusCode.NOT_FOUND, "ไม่พบข้อมูลคำสั่งนี้"); + } + const lastOrderNo = await this.commandOperatorRepo.findOne({ + where: { commandId: commandId }, + order: { orderNo: "DESC" }, + select: { orderNo: true }, + }); + const nextOrderNo = (lastOrderNo?.orderNo ?? 1) + 1; + + const now = new Date(); + const operator = Object.assign( + new CommandOperator(), + { + ...body, + commandId: command.id, + orderNo: nextOrderNo, + createdUserId: request.user.sub, + createdFullName: request.user.name, + createdAt: now, + lastUpdateUserId: request.user.sub, + lastUpdateFullName: request.user.name, + lastUpdatedAt: now, + } + ); + await this.commandOperatorRepo.save(operator); + return new HttpSuccess(); + } + + /** + * API ลบเจ้าหน้าที่ดำเนินการที่คำสั่ง + * @summary API ลบเจ้าหน้าที่ดำเนินการที่คำสั่ง + * @param commandId คีย์คำสั่ง + * @param operatorId คีย์เจ้าหน้าที่ดำเนินการ + */ + @Delete("{commandId}/{operatorId}") + public async deleteCommandOperator( + @Path() commandId: string, + @Path() operatorId: string, + ) { + const queryRunner = AppDataSource.createQueryRunner(); + await queryRunner.connect(); + await queryRunner.startTransaction(); + + try { + // 1. หา operator + const operator = await queryRunner.manager.findOne(CommandOperator, { + where: { + id: operatorId, + commandId: commandId, + }, + }); + + if (!operator) { + throw new HttpError(HttpStatusCode.NOT_FOUND, "ไม่พบเจ้าหน้าที่ดำเนินการ"); + } + + // // 2. ห้ามลบ orderNo = 1 + // if (operator.orderNo === 1) { + // throw new HttpError( + // HttpStatusCode.BAD_REQUEST, + // "ไม่สามารถลบเจ้าหน้าที่ลำดับที่ 1 ได้" + // ); + // } + + const removedOrderNo = operator.orderNo; + + // 3. ลบ + await queryRunner.manager.remove(operator); + + // 4. re orderNumber ตัวที่เหลือ + await queryRunner.manager + .createQueryBuilder() + .update(CommandOperator) + .set({ + orderNo: () => "orderNo - 1", + }) + .where("commandId = :commandId", { commandId }) + .andWhere("orderNo > :removedOrderNo", { removedOrderNo }) + .execute(); + + await queryRunner.commitTransaction(); + return new HttpSuccess(true); + } catch (error) { + await queryRunner.rollbackTransaction(); + throw error; + } finally { + await queryRunner.release(); + } + } + +} diff --git a/src/controllers/DevelopmentRequestController.ts b/src/controllers/DevelopmentRequestController.ts index f02b3049..14c485c4 100644 --- a/src/controllers/DevelopmentRequestController.ts +++ b/src/controllers/DevelopmentRequestController.ts @@ -165,7 +165,8 @@ export class DevelopmentRequestController extends Controller { data.child1 != undefined && data.child1 != null ? data.child1[0] != null ? `current_holders.orgChild1Id IN (:...child1)` - : `current_holders.orgChild1Id is ${data.privilege == "PARENT" ? "not null" : "null"}` + // : `current_holders.orgChild1Id is ${data.privilege == "PARENT" ? "not null" : "null"}` + : `current_holders.orgChild1Id is null` : "1=1", { child1: data.child1, diff --git a/src/controllers/EmployeePositionController.ts b/src/controllers/EmployeePositionController.ts index 4f679477..7b09973e 100644 --- a/src/controllers/EmployeePositionController.ts +++ b/src/controllers/EmployeePositionController.ts @@ -43,6 +43,7 @@ import { CreatePosMasterHistoryOfficer, } from "../services/PositionService"; import { PosMasterEmployeeHistory } from "../entities/PosMasterEmployeeHistory"; +import { KeycloakAttributeService } from "../services/KeycloakAttributeService"; @Route("api/v1/org/employee/pos") @Tags("Employee") @Security("bearerAuth") @@ -65,6 +66,7 @@ export class EmployeePositionController extends Controller { private child3Repository = AppDataSource.getRepository(OrgChild3); private child4Repository = AppDataSource.getRepository(OrgChild4); private authRoleRepo = AppDataSource.getRepository(AuthRole); + private keycloakAttributeService = new KeycloakAttributeService(); /** * API เพิ่มตำแหน่งลูกจ้างประจำ @@ -679,6 +681,11 @@ export class EmployeePositionController extends Controller { posMaster.lastUpdateFullName = request.user.name; posMaster.lastUpdatedAt = new Date(); await this.employeePosMasterRepository.save(posMaster, { data: request }); + + const saved = await this.employeePosMasterRepository.save(posMaster, { data: request }); + saved.ancestorDNA = saved.id; + await this.employeePosMasterRepository.save(saved, { data: request }); + setLogDataDiff(request, { before, after: posMaster }); await Promise.all( requestBody.positions.map(async (x: any) => { @@ -940,6 +947,35 @@ export class EmployeePositionController extends Controller { return new HttpSuccess(posMaster.id); } + /** + * @summary แก้ไขตำแหน่งเงื่อนไข ลูกจ้างประจำ (ADMIN) + */ + @Put("master/position-condition/{id}") + async updatePositionCondition( + @Path() id: string, + @Body() + requestBody: { + isCondition: boolean | null; + conditionReason: string | null; + }, + @Request() request: RequestWithUser, + ) { + await new permission().PermissionUpdate(request, "SYS_POS_CONDITION"); + const posMaster = await this.employeePosMasterRepository.findOne({ + where: { id: id }, + }); + if (!posMaster) { + throw new HttpError(HttpStatusCode.NOT_FOUND, "ไม่พบข้อมูล"); + } + + Object.assign(posMaster, requestBody); + posMaster.lastUpdateUserId = request.user.sub; + posMaster.lastUpdateFullName = request.user.name; + posMaster.lastUpdatedAt = new Date(); + await this.employeePosMasterRepository.save(posMaster); + return new HttpSuccess(); + } + /** * API รายละเอียดอัตรากำลัง * @@ -1154,7 +1190,8 @@ export class EmployeePositionController extends Controller { _data.child1 != undefined && _data.child1 != null ? _data.child1[0] != null ? `posMaster.orgChild1Id IN (:...child1)` - : `posMaster.orgChild1Id is ${_data.privilege == "PARENT" ? "not null" : "null"}` + // : `posMaster.orgChild1Id is ${_data.privilege == "PARENT" ? "not null" : "null"}` + : `posMaster.orgChild1Id is null` : "1=1", { child1: _data.child1, @@ -1343,6 +1380,7 @@ export class EmployeePositionController extends Controller { return { id: posMaster.id, + ancestorDNA: posMaster.ancestorDNA, current_holderId: posMaster.current_holderId, orgRootId: posMaster.orgRootId, orgChild1Id: posMaster.orgChild1Id, @@ -1368,6 +1406,8 @@ export class EmployeePositionController extends Controller { profilePoslevel: level == null || type == null ? null : `${type.posTypeShortName} ${level.posLevelName}`, authRoleId: posMaster.authRoleId, + isCondition: posMaster.isCondition, + conditionReason: posMaster.conditionReason, authRoleName: authRoleName == null || authRoleName.roleName == null ? null : authRoleName.roleName, positions: positions.map((position) => ({ @@ -2397,6 +2437,12 @@ export class EmployeePositionController extends Controller { // await this.profileRepository.save(profile); // } // } + if (dataMaster.current_holderId) { + await this.keycloakAttributeService.clearOrgDnaAttributes( + [dataMaster.current_holderId], + "PROFILE_EMPLOYEE", + ); + } await this.employeePosMasterRepository.update(id, { isSit: false, diff --git a/src/controllers/EmployeeTempPositionController.ts b/src/controllers/EmployeeTempPositionController.ts index db110dce..69dc3b92 100644 --- a/src/controllers/EmployeeTempPositionController.ts +++ b/src/controllers/EmployeeTempPositionController.ts @@ -43,6 +43,7 @@ import permission from "../interfaces/permission"; import { setLogDataDiff } from "../interfaces/utils"; import { CreatePosMasterHistoryEmployeeTemp } from "../services/PositionService"; import { PosMasterEmployeeTempHistory } from "../entities/PosMasterEmployeeTempHistory"; +import { KeycloakAttributeService } from "../services/KeycloakAttributeService"; @Route("api/v1/org/employee-temp/pos") @Tags("Employee") @Security("bearerAuth") @@ -65,6 +66,7 @@ export class EmployeeTempPositionController extends Controller { private child3Repository = AppDataSource.getRepository(OrgChild3); private child4Repository = AppDataSource.getRepository(OrgChild4); private authRoleRepo = AppDataSource.getRepository(AuthRole); + private keycloakAttributeService = new KeycloakAttributeService(); /** * API เพิ่มตำแหน่งลูกจ้างประจำ @@ -251,7 +253,7 @@ export class EmployeeTempPositionController extends Controller { switch (type) { case "positionName": findData = await this.employeePosDictRepository.find({ - where: { posDictName: Like(`%${keyword}%`), posLevel: { posLevelName: 1 } }, + where: { posDictName: Like(`%${keyword}%`), posLevel: { posLevelName: "1" } }, relations: ["posType", "posLevel"], order: { posDictName: "ASC", @@ -274,7 +276,7 @@ export class EmployeeTempPositionController extends Controller { select: ["id"], }); findData = await this.employeePosDictRepository.find({ - where: { posTypeId: In(findEmpTypes.map((x) => x.id)), posLevel: { posLevelName: 1 } }, + where: { posTypeId: In(findEmpTypes.map((x) => x.id)), posLevel: { posLevelName: "1" } }, relations: ["posType", "posLevel"], order: { posDictName: "ASC", @@ -292,19 +294,19 @@ export class EmployeeTempPositionController extends Controller { break; case "positionLevel": - if (!isNaN(Number(keyword))) { + if (!keyword) { let findEmpLevels; - if (Number(keyword) === 0) { + if (keyword === "0") { findEmpLevels = await this.employeePosLevelRepository.find(); } else { findEmpLevels = await this.employeePosLevelRepository.find({ - where: { posLevelName: Number(keyword) }, + where: { posLevelName: keyword }, }); } findData = await this.employeePosDictRepository.find({ where: { posLevelId: In(findEmpLevels.map((x) => x.id)), - posLevel: { posLevelName: 1 }, + posLevel: { posLevelName: "1" }, }, relations: ["posType", "posLevel"], order: { @@ -323,7 +325,7 @@ export class EmployeeTempPositionController extends Controller { } else { //กรณีเลือกค้นหาจาก"ระดับชั้นงาน" แต่กรอกไม่ใช่ number ให้ปล่อยมาหมดเลย findData = await this.employeePosDictRepository.find({ - where: { posLevel: { posLevelName: 1 } }, + where: { posLevel: { posLevelName: "1" } }, relations: ["posType", "posLevel"], order: { posDictName: "ASC", @@ -343,7 +345,7 @@ export class EmployeeTempPositionController extends Controller { default: findData = await this.employeePosDictRepository.find({ - where: { posLevel: { posLevelName: 1 } }, + where: { posLevel: { posLevelName: "1" } }, relations: ["posType", "posLevel"], order: { posDictName: "ASC", @@ -546,6 +548,11 @@ export class EmployeeTempPositionController extends Controller { posMaster.lastUpdateFullName = request.user.name; posMaster.lastUpdatedAt = new Date(); await this.employeeTempPosMasterRepository.save(posMaster, { data: request }); + + const saved = await this.employeeTempPosMasterRepository.save(posMaster, { data: request }); + saved.ancestorDNA = saved.id; + await this.employeeTempPosMasterRepository.save(saved, { data: request }); + setLogDataDiff(request, { before, after: posMaster }); await Promise.all( requestBody.positions.map(async (x: any) => { @@ -901,7 +908,8 @@ export class EmployeeTempPositionController extends Controller { _data.child1 != undefined && _data.child1 != null ? _data.child1[0] != null ? `posMaster.orgChild1Id IN (:...child1)` - : `posMaster.orgChild1Id is ${_data.privilege == "PARENT" ? "not null" : "null"}` + // : `posMaster.orgChild1Id is ${_data.privilege == "PARENT" ? "not null" : "null"}` + : `posMaster.orgChild1Id is null` : "1=1", { child1: _data.child1, @@ -1090,6 +1098,7 @@ export class EmployeeTempPositionController extends Controller { return { id: posMaster.id, + ancestorDNA: posMaster.ancestorDNA, current_holderId: posMaster.current_holderId, orgRootId: posMaster.orgRootId, orgChild1Id: posMaster.orgChild1Id, @@ -2134,6 +2143,13 @@ export class EmployeeTempPositionController extends Controller { // } // } + if (dataMaster.current_holderId) { + await this.keycloakAttributeService.clearOrgDnaAttributes( + [dataMaster.current_holderId], + "PROFILE_EMPLOYEE", + ); + } + await this.employeeTempPosMasterRepository.update(id, { isSit: false, next_holderId: null, diff --git a/src/controllers/ExRetirementController.ts b/src/controllers/ExRetirementController.ts index c8174e6f..128cb4d1 100644 --- a/src/controllers/ExRetirementController.ts +++ b/src/controllers/ExRetirementController.ts @@ -14,6 +14,7 @@ import { } from "tsoa"; import HttpError from "../interfaces/http-error"; import HttpStatusCode from "../interfaces/http-status"; +import { addLogSequence } from "../interfaces/utils"; interface CachedToken { token: string; @@ -171,6 +172,7 @@ async function getToken(ClientID: string, ClientSecret: string): Promise // function post retire data to exprofile system export async function PostRetireToExprofile( + request: any, citizenID: string, prefix: string, firstName: string, @@ -183,6 +185,12 @@ export async function PostRetireToExprofile( organizeName: string, // child4Name child3Name child2Name child1Name rootName retireTypeName: string, // เช่น เกษียณ, ขอโอนออก, ลาออก, ปลดออก, ไล่ออก, ... ) { + // check NODE_ENV ถ้าเป็น production ถึงจะทำการส่งข้อมูลไปยัง exprofile + const NODE_ENV = process.env.NODE_ENV || "development"; + if (NODE_ENV !== "production") { + return; + } + let retryCount = 0; const maxRetries = 2; @@ -226,6 +234,18 @@ export async function PostRetireToExprofile( retryCount++; continue; } + + addLogSequence(request, { + action: "request", + status: "error", + description: "unconnected to exprofile api", + request: { + method: "POST", + url: API_URL_BANGKOK + "/importData", + response: JSON.stringify(error), + }, + }); + throw new HttpError(HttpStatusCode.INTERNAL_SERVER_ERROR, "ไม่สามารถติดต่อ API ได้"); } } diff --git a/src/controllers/ImportDataController.ts b/src/controllers/ImportDataController.ts index 99e931ea..5b8ca808 100644 --- a/src/controllers/ImportDataController.ts +++ b/src/controllers/ImportDataController.ts @@ -395,12 +395,14 @@ export class ImportDataController extends Controller { } var positionType = ""; - var positionLevel = 0; + // var positionLevel = 0; + var positionLevel = "0"; const workLevel = item.WORK_LEVEL; const part1 = workLevel.split("/")[0]; // "ส 2" const value2 = part1.split(" ")[1]; // "2" if (value2) { - positionLevel = parseInt(value2); + // positionLevel = parseInt(value2); + positionLevel = value2; } if (item.CATEGORY_SAL_CODE == "11") { positionType = "บริการพื้นฐาน"; @@ -530,12 +532,14 @@ export class ImportDataController extends Controller { } var positionType = ""; - var positionLevel = 0; + // var positionLevel = 0; + var positionLevel = "0"; const value2 = item.POSITION_LEVEL; // const part1 = workLevel.split("/")[0]; // "ส 2" // const value2 = part1.split(" ")[1]; // "2" if (value2) { - positionLevel = parseInt(value2); + // positionLevel = parseInt(value2); + positionLevel = value2; } if (item.CATEGORY_SAL_CODE == "11") { positionType = "บริการพื้นฐาน"; @@ -2475,8 +2479,8 @@ export class ImportDataController extends Controller { }); const educationLevel = await this.profileEducationRepo.findOne({ - select: ["id", "level", "profileId"], - where: { profileId: _item.id }, + select: ["id", "level", "profileId", "isDeleted"], + where: { profileId: _item.id, isDeleted: false }, order: { level: "DESC" }, }); @@ -2607,8 +2611,8 @@ export class ImportDataController extends Controller { }); const educationLevel = await this.profileEducationRepo.findOne({ - select: ["id", "level", "profileEmployeeId"], - where: { profileEmployeeId: _item.id }, + select: ["id", "level", "profileEmployeeId", "isDeleted"], + where: { profileEmployeeId: _item.id, isDeleted: false }, order: { level: "DESC" }, }); @@ -2740,8 +2744,8 @@ export class ImportDataController extends Controller { }); const educationLevel = await this.profileEducationRepo.findOne({ - select: ["id", "level", "profileEmployeeId"], - where: { profileEmployeeId: _item.id }, + select: ["id", "level", "profileEmployeeId", "isDeleted"], + where: { profileEmployeeId: _item.id, isDeleted: false }, order: { level: "DESC" }, }); @@ -4340,12 +4344,14 @@ export class ImportDataController extends Controller { let position = new EmployeePosition(); var positionType = ""; - var positionLevel = 0; + // var positionLevel = 0; + var positionLevel = "0"; const workLevel = item.WORK_LEVEL; const part1 = workLevel.split("/")[0]; // "ส 2" const value2 = part1.split(" ")[1]; // "2" if (value2) { - positionLevel = parseInt(value2); + // positionLevel = parseInt(value2); + positionLevel = value2; } if (item.CATEGORY_SAL_CODE == "11") { positionType = "บริการพื้นฐาน"; @@ -5799,7 +5805,7 @@ export class ImportDataController extends Controller { }, }); const eduLevel = await this.profileEducationRepo.findOne({ - where: { profileId: _item.id }, + where: { profileId: _item.id, isDeleted: false }, order: { startDate: "DESC", }, diff --git a/src/controllers/KeycloakSyncController.ts b/src/controllers/KeycloakSyncController.ts new file mode 100644 index 00000000..995fa3c0 --- /dev/null +++ b/src/controllers/KeycloakSyncController.ts @@ -0,0 +1,318 @@ +import { + Controller, + Post, + Get, + Route, + Security, + Tags, + Path, + Request, + Response, + Query, + Body, +} from "tsoa"; +import { KeycloakAttributeService } from "../services/KeycloakAttributeService"; +import HttpSuccess from "../interfaces/http-success"; +import HttpStatus from "../interfaces/http-status"; +import HttpError from "../interfaces/http-error"; +import { RequestWithUser } from "../middlewares/user"; + +@Route("api/v1/org/keycloak-sync") +@Tags("Keycloak Sync") +@Security("bearerAuth") +@Response( + HttpStatus.INTERNAL_SERVER_ERROR, + "เกิดข้อผิดพลาด ไม่สามารถดำเนินการได้ กรุณาลองใหม่ในภายหลัง", +) +export class KeycloakSyncController extends Controller { + private keycloakAttributeService = new KeycloakAttributeService(); + + /** + * Sync attributes for the current logged-in user + * + * @summary Sync profileId and rootDnaId to Keycloak for current user + */ + @Post("sync-me") + async syncCurrentUser(@Request() request: RequestWithUser) { + const keycloakUserId = request.user.sub; + + if (!keycloakUserId) { + throw new HttpError(HttpStatus.UNAUTHORIZED, "ไม่พบ Keycloak user ID"); + } + + // Get attributes from database before sync + const dbAttrs = await this.keycloakAttributeService.getUserProfileAttributes(keycloakUserId); + + const success = await this.keycloakAttributeService.syncUserAttributes(keycloakUserId); + + if (!success) { + throw new HttpError( + HttpStatus.INTERNAL_SERVER_ERROR, + "ไม่สามารถ sync ข้อมูลไปยัง Keycloak ได้ กรุณาติดต่อผู้ดูแลระบบ", + ); + } + + // Verify sync by fetching attributes from Keycloak after update + const kcAttrsAfter = + await this.keycloakAttributeService.getCurrentKeycloakAttributes(keycloakUserId); + + return new HttpSuccess({ + message: "Sync ข้อมูลสำเร็จ", + syncedToKeycloak: !!kcAttrsAfter?.profileId, + databaseAttributes: dbAttrs, + keycloakAttributesAfter: kcAttrsAfter, + }); + } + + /** + * Get current attributes of the logged-in user + * + * @summary Get current profileId and rootDnaId from Keycloak + */ + @Get("my-attributes") + async getMyAttributes(@Request() request: RequestWithUser) { + const keycloakUserId = request.user.sub; + + if (!keycloakUserId) { + throw new HttpError(HttpStatus.UNAUTHORIZED, "ไม่พบ Keycloak user ID"); + } + + const keycloakAttributes = + await this.keycloakAttributeService.getCurrentKeycloakAttributes(keycloakUserId); + const dbAttributes = + await this.keycloakAttributeService.getUserProfileAttributes(keycloakUserId); + + return new HttpSuccess({ + keycloakAttributes, + databaseAttributes: dbAttributes, + }); + } + + /** + * Sync attributes for a specific profile (Admin only) + * + * @summary Sync profileId and rootDnaId to Keycloak by profile ID (ADMIN) + * + * @param {string} profileId Profile ID + * @param {string} profileType Profile type (PROFILE or PROFILE_EMPLOYEE) + */ + @Post("sync-profile/:profileId") + async syncByProfileId( + @Path() profileId: string, + @Query() profileType: "PROFILE" | "PROFILE_EMPLOYEE" = "PROFILE", + ) { + if (!["PROFILE", "PROFILE_EMPLOYEE"].includes(profileType)) { + throw new HttpError( + HttpStatus.BAD_REQUEST, + "profileType ต้องเป็น PROFILE หรือ PROFILE_EMPLOYEE เท่านั้น", + ); + } + + const success = await this.keycloakAttributeService.syncOnOrganizationChange( + profileId, + profileType, + ); + + if (!success) { + throw new HttpError( + HttpStatus.INTERNAL_SERVER_ERROR, + "ไม่สามารถ sync ข้อมูลไปยัง Keycloak ได้ หรือไม่พบข้อมูล profile", + ); + } + + return new HttpSuccess({ message: "Sync ข้อมูลสำเร็จ" }); + } + + /** + * Batch sync attributes for multiple profiles (Admin only) + * + * @summary Batch sync profileId and rootDnaId to Keycloak for multiple profiles (ADMIN) + * + * @param {request} request Request body containing profileIds array and profileType + */ + @Post("sync-profiles-batch") + async syncByProfileIds( + @Body() request: { profileIds: string[]; profileType: "PROFILE" | "PROFILE_EMPLOYEE" }, + ) { + const { profileIds, profileType } = request; + + // Validate profileIds + if (!profileIds || profileIds.length === 0) { + throw new HttpError(HttpStatus.BAD_REQUEST, "profileIds ต้องไม่ว่างเปล่า"); + } + + // Validate profileType + if (!["PROFILE", "PROFILE_EMPLOYEE"].includes(profileType)) { + throw new HttpError( + HttpStatus.BAD_REQUEST, + "profileType ต้องเป็น PROFILE หรือ PROFILE_EMPLOYEE เท่านั้น", + ); + } + + const result = { + total: profileIds.length, + success: 0, + failed: 0, + details: [] as Array<{ profileId: string; status: "success" | "failed"; error?: string }>, + }; + + // Process each profileId + for (const profileId of profileIds) { + try { + const success = await this.keycloakAttributeService.syncOnOrganizationChange( + profileId, + profileType, + ); + + if (success) { + result.success++; + result.details.push({ profileId, status: "success" }); + } else { + result.failed++; + result.details.push({ + profileId, + status: "failed", + error: "Sync returned false - ไม่พบข้อมูล profile หรือ Keycloak user ID", + }); + } + } catch (error: any) { + result.failed++; + result.details.push({ profileId, status: "failed", error: error.message }); + } + } + + return new HttpSuccess({ + message: "Batch sync เสร็จสิ้น", + ...result, + }); + } + + /** + * Clear org DNA attributes for profiles (Admin only) + * + * @summary Clear org DNA attributes in Keycloak for given profiles (ADMIN) + * + * @description + * This endpoint will: + * - Clear all org DNA fields (orgRootDnaId, orgChild1-4DnaId) by setting them to empty strings + * - Use when an employee leaves their position (current_holderId becomes null) + * + * @param {request} request Request body containing profileIds array and profileType + */ + @Post("clear-org-dna") + async clearOrgDna( + @Body() request: { profileIds: string[]; profileType: "PROFILE" | "PROFILE_EMPLOYEE" }, + ) { + const { profileIds, profileType } = request; + + // Validate profileIds + if (!profileIds || profileIds.length === 0) { + throw new HttpError(HttpStatus.BAD_REQUEST, "profileIds ต้องไม่ว่างเปล่า"); + } + + // Validate profileType + if (!["PROFILE", "PROFILE_EMPLOYEE"].includes(profileType)) { + throw new HttpError( + HttpStatus.BAD_REQUEST, + "profileType ต้องเป็น PROFILE หรือ PROFILE_EMPLOYEE เท่านั้น", + ); + } + + const result = await this.keycloakAttributeService.clearOrgDnaAttributes( + profileIds, + profileType, + ); + + return new HttpSuccess({ + message: "Clear org DNA attributes เสร็จสิ้น", + ...result, + }); + } + + /** + * Batch sync all users (Admin only) + * + * @summary Batch sync all users to Keycloak without limit (ADMIN) + * + * @description Syncs profileId and orgRootDnaId to Keycloak for all users + * that have a keycloak ID. Uses parallel processing for better performance. + * + * Features: + * - Resume from checkpoint after failures (use resume=true) + * - Automatic retry with exponential backoff + * - Rate limiting to avoid overwhelming Keycloak + * - Progress tracking and persistence + * + * @param resume - Resume from last checkpoint (default: false) + * @param maxRetries - Maximum retry attempts for failed operations (default: 3) + * @param rateLimit - Requests per second rate limit (default: 10) + * @param clearProgress - Clear existing progress and start fresh (default: false) + */ + @Post("sync-all") + async syncAll( + @Query() resume: boolean = false, + @Query() maxRetries: number = 3, + @Query() rateLimit: number = 10, + @Query() clearProgress: boolean = false, + ) { + const result = await this.keycloakAttributeService.batchSyncUsers({ + resume, + maxRetries, + rateLimit, + clearProgress, + }); + + return new HttpSuccess({ + message: "Batch sync เสร็จสิ้น", + total: result.total, + success: result.success, + failed: result.failed, + details: result.details, + resumed: result.resumed, + }); + } + + /** + * Ensure Keycloak users exist for all profiles (Admin only) + * + * @summary Create or verify Keycloak users for all profiles in Profile and ProfileEmployee tables (ADMIN) + * + * @description + * This endpoint will: + * - Create new Keycloak users for profiles without a keycloak ID + * - Create new Keycloak users for profiles where the stored keycloak ID doesn't exist in Keycloak + * - Verify existing Keycloak users + * - Skip profiles without a citizenId + */ + @Post("ensure-users") + async ensureAllUsers() { + const result = await this.keycloakAttributeService.batchEnsureKeycloakUsers(); + return new HttpSuccess({ + message: "Batch ensure Keycloak users เสร็จสิ้น", + ...result, + }); + } + + /** + * Clear orphaned Keycloak users (Admin only) + * + * @summary Delete Keycloak users that are not in the database (ADMIN) + * + * @description + * This endpoint will: + * - Find users in Keycloak that are not referenced in Profile or ProfileEmployee tables + * - Delete those orphaned users from Keycloak + * - Skip protected users (super_admin, admin_issue) + * + * @param {request} request Request body containing skipUsernames array + */ + @Post("clear-orphaned-users") + async clearOrphanedUsers(@Body() request?: { skipUsernames?: string[] }) { + const skipUsernames = request?.skipUsernames || ["super_admin", "admin_issue"]; + const result = await this.keycloakAttributeService.clearOrphanedKeycloakUsers(skipUsernames); + return new HttpSuccess({ + message: "Clear orphaned Keycloak users เสร็จสิ้น", + ...result, + }); + } +} diff --git a/src/controllers/MyController.ts b/src/controllers/MyController.ts index f8fbc848..16809b6d 100644 --- a/src/controllers/MyController.ts +++ b/src/controllers/MyController.ts @@ -1,4 +1,3 @@ -import { profile } from "console"; import { Controller, Get, Post, Query, Route, Security, Tags } from "tsoa"; import { calculateGovAge } from "../interfaces/utils"; import HttpSuccess from "../interfaces/http-success"; @@ -14,7 +13,7 @@ export class AppController extends Controller { @Post() public async Post(@Query() profileId: string) { - const result = calculateGovAge(profileId,"OFFICER"); + const result = calculateGovAge(profileId, "OFFICER"); return new HttpSuccess(result); } } diff --git a/src/controllers/OrgChild1Controller.ts b/src/controllers/OrgChild1Controller.ts index 7ba65f60..6e44af05 100644 --- a/src/controllers/OrgChild1Controller.ts +++ b/src/controllers/OrgChild1Controller.ts @@ -204,6 +204,9 @@ export class OrgChild1Controller { child1.orgChild1Order = order == null || order.orgChild1Order == null ? 1 : order.orgChild1Order + 1; await this.child1Repository.save(child1, { data: request }); + // update ancestorDNA = id row + child1.ancestorDNA = child1.id; + await this.child1Repository.save(child1, { data: request }); setLogDataDiff(request, { before, after: child1 }); return new HttpSuccess(); } diff --git a/src/controllers/OrgChild2Controller.ts b/src/controllers/OrgChild2Controller.ts index b0ff4b88..28ce564f 100644 --- a/src/controllers/OrgChild2Controller.ts +++ b/src/controllers/OrgChild2Controller.ts @@ -164,6 +164,9 @@ export class OrgChild2Controller extends Controller { child2.orgChild2Order = order == null || order.orgChild2Order == null ? 1 : order.orgChild2Order + 1; await this.child2Repository.save(child2, { data: request }); + // update ancestorDNA = id row + child2.ancestorDNA = child2.id; + await this.child2Repository.save(child2, { data: request }); setLogDataDiff(request, { before, after: child2 }); return new HttpSuccess(); } diff --git a/src/controllers/OrgChild3Controller.ts b/src/controllers/OrgChild3Controller.ts index 3ba365ae..4ed10804 100644 --- a/src/controllers/OrgChild3Controller.ts +++ b/src/controllers/OrgChild3Controller.ts @@ -132,6 +132,9 @@ export class OrgChild3Controller { child3.orgChild3Order = order == null || order.orgChild3Order == null ? 1 : order.orgChild3Order + 1; await this.child3Repository.save(child3, { data: request }); + // update ancestorDNA = id row + child3.ancestorDNA = child3.id; + await this.child3Repository.save(child3, { data: request }); setLogDataDiff(request, { before, after: child3 }); return new HttpSuccess(); } diff --git a/src/controllers/OrgChild4Controller.ts b/src/controllers/OrgChild4Controller.ts index a43b7234..e18c15f9 100644 --- a/src/controllers/OrgChild4Controller.ts +++ b/src/controllers/OrgChild4Controller.ts @@ -163,6 +163,9 @@ export class OrgChild4Controller extends Controller { child4.orgChild4Order = order == null || order.orgChild4Order == null ? 1 : order.orgChild4Order + 1; await this.child4Repository.save(child4, { data: request }); + // update ancestorDNA = id row + child4.ancestorDNA = child4.id; + await this.child4Repository.save(child4, { data: request }); setLogDataDiff(request, { before, after: child4 }); return new HttpSuccess(); diff --git a/src/controllers/OrgRootController.ts b/src/controllers/OrgRootController.ts index 9e6e428f..45bf1436 100644 --- a/src/controllers/OrgRootController.ts +++ b/src/controllers/OrgRootController.ts @@ -203,6 +203,9 @@ export class OrgRootController extends Controller { orgRoot.lastUpdatedAt = new Date(); orgRoot.orgRootOrder = order == null || order.orgRootOrder == null ? 1 : order.orgRootOrder + 1; await this.orgRootRepository.save(orgRoot, { data: request }); + // update ancestorDNA = id row + orgRoot.ancestorDNA = orgRoot.id; + await this.orgRootRepository.save(orgRoot, { data: request }); setLogDataDiff(request, { before, after: orgRoot }); return new HttpSuccess(); diff --git a/src/controllers/OrganizationController.ts b/src/controllers/OrganizationController.ts index 3050285d..39752b7e 100644 --- a/src/controllers/OrganizationController.ts +++ b/src/controllers/OrganizationController.ts @@ -24,7 +24,7 @@ import HttpSuccess from "../interfaces/http-success"; import { OrgChild1 } from "../entities/OrgChild1"; import HttpError from "../interfaces/http-error"; import HttpStatusCode from "../interfaces/http-status"; -import { In, IsNull, Not } from "typeorm"; +import { In, IsNull, Not, Like } from "typeorm"; import { OrgRoot } from "../entities/OrgRoot"; import { OrgChild2 } from "../entities/OrgChild2"; import { OrgChild3 } from "../entities/OrgChild3"; @@ -33,7 +33,12 @@ import { PosMaster } from "../entities/PosMaster"; import { Profile } from "../entities/Profile"; import { RequestWithUser } from "../middlewares/user"; import permission from "../interfaces/permission"; -import { checkQueueInProgress, setLogDataDiff } from "../interfaces/utils"; +import { + checkQueueInProgress, + resolveNodeId, + resolveNodeLevel, + setLogDataDiff, +} from "../interfaces/utils"; import { sendToQueueOrg, sendToQueueOrgDraft } from "../services/rabbitmq"; import { PosType } from "../entities/PosType"; import { PosLevel } from "../entities/PosLevel"; @@ -45,11 +50,23 @@ import { getUserByUsername, getRoles, addUserRoles, + updateUserAttributes, } from "../keycloak"; import { + getPositionCountsAggregated, + getPositionCount, + PositionCountsByNode, +} from "../services/OrganizationService"; +import { + BatchSavePosMasterHistoryOfficer, CreatePosMasterHistoryEmployee, CreatePosMasterHistoryOfficer, + SavePosMasterHistoryOfficer, } from "../services/PositionService"; +import { orgStructureCache } from "../utils/OrgStructureCache"; +import { OrgIdMapping, AllOrgMappings, SavePosMasterHistory } from "../interfaces/OrgMapping"; +import { OrgPermissionData, NodeLevel } from "../interfaces/OrgTypes"; +import { formatPosMaster, generateLabelName, filterPosMasters } from "../utils/org-formatting"; @Route("api/v1/org") @Tags("Organization") @@ -278,6 +295,72 @@ export class OrganizationController extends Controller { return new HttpSuccess(requestBody); } + /** + * API หา ORG DNA ID + * + * @summary หา ORG DNA ID + * + */ + @Get("finddna-by-keycloak/{keycloakId}") + async FindDnaOrgByKeycloakId(@Path() keycloakId: string) { + let reply: any; + + const profileByKeycloak: any = await this.profileRepo.findOne({ + where: { keycloak: keycloakId }, + select: ["id", "keycloak"], + }); + + const orgRevision = await this.orgRevisionRepository.findOne({ + select: ["id"], + where: { + orgRevisionIsDraft: false, + orgRevisionIsCurrent: true, + }, + }); + + const posMaster = await AppDataSource.getRepository(PosMaster) + .createQueryBuilder("pos") + .leftJoin("pos.orgRoot", "orgRoot") + .leftJoin("pos.orgChild1", "orgChild1") + .leftJoin("pos.orgChild2", "orgChild2") + .leftJoin("pos.orgChild3", "orgChild3") + .leftJoin("pos.orgChild4", "orgChild4") + .where("pos.current_holderId = :holderId", { + holderId: profileByKeycloak.id, + }) + .andWhere("pos.orgRevisionId = :revId", { + revId: orgRevision?.id, + }) + .select([ + "pos.id", + "orgRoot.ancestorDNA as rootDnaId", + "orgChild1.ancestorDNA as child1DnaId", + "orgChild2.ancestorDNA as child2DnaId", + "orgChild3.ancestorDNA as child3DnaId", + "orgChild4.ancestorDNA as child4DnaId", + ]) + .getRawOne(); + + if (!posMaster) { + reply = { + rootDnaId: null, + child1DnaId: null, + child2DnaId: null, + child3DnaId: null, + child4DnaId: null, + }; + } else { + reply = { + rootDnaId: posMaster.rootDnaId, + child1DnaId: posMaster.child1DnaId, + child2DnaId: posMaster.child2DnaId, + child3DnaId: posMaster.child3DnaId, + child4DnaId: posMaster.child4DnaId, + }; + } + return new HttpSuccess(reply); + } + /** * API เช็คสถานะโครงสร้างว่าส่งคนไปออกคำสั่งหรือไม่ * @@ -1165,7 +1248,6 @@ export class OrganizationController extends Controller { async detailSuperAdmin(@Path() id: string, @Request() request: RequestWithUser) { const orgRevision = await this.orgRevisionRepository.findOne({ where: { id: id }, - relations: ["posMasters"], }); if (!orgRevision) return new HttpSuccess([]); @@ -1175,805 +1257,366 @@ export class OrganizationController extends Controller { where: { keycloak: request.user.sub, }, + select: ["id"], }); if (profile == null) return new HttpSuccess([]); - if (!request.user.role.includes("SUPER_ADMIN")) { - if (orgRevision.orgRevisionIsDraft == true && orgRevision.orgRevisionIsCurrent == false) { - rootId = - orgRevision?.posMasters?.filter((x) => x.next_holderId == profile.id)[0]?.orgRootId || - null; - if (!rootId) return new HttpSuccess([]); - } else { - rootId = - orgRevision?.posMasters?.filter((x) => x.current_holderId == profile.id)[0] - ?.orgRootId || null; - if (!rootId) return new HttpSuccess([]); - } - } + const posMaster = await this.posMasterRepository.findOne({ + where: + orgRevision.orgRevisionIsCurrent && !orgRevision.orgRevisionIsDraft + ? { + orgRevisionId: id, + current_holderId: profile.id, + } + : { + orgRevisionId: id, + next_holderId: profile.id, + }, + }); + if (!posMaster) return new HttpSuccess([]); + + rootId = posMaster.orgRootId; } + // OPTIMIZED: Check cache first + const cachedResponse = await orgStructureCache.get(id, rootId); + if (cachedResponse) { + return new HttpSuccess(cachedResponse); + } + + // OPTIMIZED: Get all position counts in ONE query (closed) + // const { orgRootMap, orgChild1Map, orgChild2Map, orgChild3Map, orgChild4Map, rootPosMap } = + // await getPositionCounts(id); + + // OPTIMIZED: Fetch orgRoot first, then fetch all child levels in parallel const orgRootData = await AppDataSource.getRepository(OrgRoot) .createQueryBuilder("orgRoot") .where("orgRoot.orgRevisionId = :id", { id }) .andWhere(rootId != null ? `orgRoot.id = :rootId` : "1=1", { rootId: rootId, }) - .select([ - "orgRoot.id", - "orgRoot.isDeputy", - "orgRoot.isCommission", - "orgRoot.orgRootName", - "orgRoot.orgRootShortName", - "orgRoot.orgRootCode", - "orgRoot.orgRootOrder", - "orgRoot.orgRootPhoneEx", - "orgRoot.orgRootPhoneIn", - "orgRoot.orgRootFax", - "orgRoot.orgRevisionId", - "orgRoot.orgRootRank", - "orgRoot.orgRootRankSub", - "orgRoot.DEPARTMENT_CODE", - "orgRoot.DIVISION_CODE", - "orgRoot.SECTION_CODE", - "orgRoot.JOB_CODE", - "orgRoot.responsibility", - "orgRoot.ancestorDNA", - ]) .orderBy("orgRoot.orgRootOrder", "ASC") .getMany(); - const orgRootIds = orgRootData.map((orgRoot) => orgRoot.id) || null; + + const orgRootIds = orgRootData.map((orgRoot) => orgRoot.id); + + // OPTIMIZED: Fetch all child levels in parallel using orgRevisionId + // This is faster than sequential queries that depend on parent IDs + const [orgChild1AllData, orgChild2AllData, orgChild3AllData, orgChild4AllData] = + await Promise.all([ + AppDataSource.getRepository(OrgChild1) + .createQueryBuilder("orgChild1") + .where("orgChild1.orgRevisionId = :id", { id }) + .orderBy("orgChild1.orgChild1Order", "ASC") + .getMany(), + + AppDataSource.getRepository(OrgChild2) + .createQueryBuilder("orgChild2") + .where("orgChild2.orgRevisionId = :id", { id }) + .orderBy("orgChild2.orgChild2Order", "ASC") + .getMany(), + + AppDataSource.getRepository(OrgChild3) + .createQueryBuilder("orgChild3") + .where("orgChild3.orgRevisionId = :id", { id }) + .orderBy("orgChild3.orgChild3Order", "ASC") + .getMany(), + + AppDataSource.getRepository(OrgChild4) + .createQueryBuilder("orgChild4") + .where("orgChild4.orgRevisionId = :id", { id }) + .orderBy("orgChild4.orgChild4Order", "ASC") + .getMany(), + ]); + + // Filter child1 data by orgRootIds (maintains backward compatibility) const orgChild1Data = orgRootIds && orgRootIds.length > 0 - ? await AppDataSource.getRepository(OrgChild1) - .createQueryBuilder("orgChild1") - .where("orgChild1.orgRootId IN (:...ids)", { ids: orgRootIds }) - .select([ - "orgChild1.id", - "orgChild1.isOfficer", - "orgChild1.isInformation", - "orgChild1.orgChild1Name", - "orgChild1.orgChild1ShortName", - "orgChild1.orgChild1Code", - "orgChild1.orgChild1Order", - "orgChild1.orgChild1PhoneEx", - "orgChild1.orgChild1PhoneIn", - "orgChild1.orgChild1Fax", - "orgChild1.orgRootId", - "orgChild1.orgChild1Rank", - "orgChild1.orgChild1RankSub", - "orgChild1.DEPARTMENT_CODE", - "orgChild1.DIVISION_CODE", - "orgChild1.SECTION_CODE", - "orgChild1.JOB_CODE", - "orgChild1.responsibility", - "orgChild1.ancestorDNA", - ]) - .orderBy("orgChild1.orgChild1Order", "ASC") - .getMany() + ? orgChild1AllData.filter((orgChild1) => orgRootIds.includes(orgChild1.orgRootId)) : []; - const orgChild1Ids = orgChild1Data.map((orgChild1) => orgChild1.id) || null; + // Build maps for efficient filtering of deeper levels + const orgChild1Ids = orgChild1Data.map((orgChild1) => orgChild1.id); const orgChild2Data = orgChild1Ids && orgChild1Ids.length > 0 - ? await AppDataSource.getRepository(OrgChild2) - .createQueryBuilder("orgChild2") - .where("orgChild2.orgChild1Id IN (:...ids)", { ids: orgChild1Ids }) - .select([ - "orgChild2.id", - "orgChild2.orgChild2Name", - "orgChild2.orgChild2ShortName", - "orgChild2.orgChild2Code", - "orgChild2.orgChild2Order", - "orgChild2.orgChild2PhoneEx", - "orgChild2.orgChild2PhoneIn", - "orgChild2.orgChild2Fax", - "orgChild2.orgRootId", - "orgChild2.orgChild2Rank", - "orgChild2.orgChild2RankSub", - "orgChild2.DEPARTMENT_CODE", - "orgChild2.DIVISION_CODE", - "orgChild2.SECTION_CODE", - "orgChild2.JOB_CODE", - "orgChild2.orgChild1Id", - "orgChild2.responsibility", - "orgChild2.ancestorDNA", - ]) - .orderBy("orgChild2.orgChild2Order", "ASC") - .getMany() + ? orgChild2AllData.filter((orgChild2) => orgChild1Ids.includes(orgChild2.orgChild1Id)) : []; - const orgChild2Ids = orgChild2Data.map((orgChild2) => orgChild2.id) || null; + const orgChild2Ids = orgChild2Data.map((orgChild2) => orgChild2.id); const orgChild3Data = orgChild2Ids && orgChild2Ids.length > 0 - ? await AppDataSource.getRepository(OrgChild3) - .createQueryBuilder("orgChild3") - .where("orgChild3.orgChild2Id IN (:...ids)", { ids: orgChild2Ids }) - .select([ - "orgChild3.id", - "orgChild3.orgChild3Name", - "orgChild3.orgChild3ShortName", - "orgChild3.orgChild3Code", - "orgChild3.orgChild3Order", - "orgChild3.orgChild3PhoneEx", - "orgChild3.orgChild3PhoneIn", - "orgChild3.orgChild3Fax", - "orgChild3.orgRootId", - "orgChild3.orgChild3Rank", - "orgChild3.orgChild3RankSub", - "orgChild3.DEPARTMENT_CODE", - "orgChild3.DIVISION_CODE", - "orgChild3.SECTION_CODE", - "orgChild3.JOB_CODE", - "orgChild3.orgChild2Id", - "orgChild3.responsibility", - "orgChild3.ancestorDNA", - ]) - .orderBy("orgChild3.orgChild3Order", "ASC") - .getMany() + ? orgChild3AllData.filter((orgChild3) => orgChild2Ids.includes(orgChild3.orgChild2Id)) : []; - const orgChild3Ids = orgChild3Data.map((orgChild3) => orgChild3.id) || null; + const orgChild3Ids = orgChild3Data.map((orgChild3) => orgChild3.id); const orgChild4Data = orgChild3Ids && orgChild3Ids.length > 0 - ? await AppDataSource.getRepository(OrgChild4) - .createQueryBuilder("orgChild4") - .where("orgChild4.orgChild3Id IN (:...ids)", { ids: orgChild3Ids }) - .select([ - "orgChild4.id", - "orgChild4.orgChild4Name", - "orgChild4.orgChild4ShortName", - "orgChild4.orgChild4Code", - "orgChild4.orgChild4Order", - "orgChild4.orgChild4PhoneEx", - "orgChild4.orgChild4PhoneIn", - "orgChild4.orgChild4Fax", - "orgChild4.orgRootId", - "orgChild4.orgChild4Rank", - "orgChild4.orgChild4RankSub", - "orgChild4.DEPARTMENT_CODE", - "orgChild4.DIVISION_CODE", - "orgChild4.SECTION_CODE", - "orgChild4.JOB_CODE", - "orgChild4.orgChild3Id", - "orgChild4.responsibility", - "orgChild4.ancestorDNA", - ]) - .orderBy("orgChild4.orgChild4Order", "ASC") - .getMany() + ? orgChild4AllData.filter((orgChild4) => orgChild3Ids.includes(orgChild4.orgChild3Id)) : []; - // const formattedData = orgRootData.map((orgRoot) => { - const formattedData = await Promise.all( - orgRootData.map(async (orgRoot) => { - return { - orgTreeId: orgRoot.id, - orgLevel: 0, - orgName: orgRoot.orgRootName, - orgTreeName: orgRoot.orgRootName, - orgTreeShortName: orgRoot.orgRootShortName, - orgTreeCode: orgRoot.orgRootCode, - orgCode: orgRoot.orgRootCode + "00", - orgTreeRank: orgRoot.orgRootRank, - orgTreeRankSub: orgRoot.orgRootRankSub, - orgRootDnaId: orgRoot.ancestorDNA, - DEPARTMENT_CODE: orgRoot.DEPARTMENT_CODE, - DIVISION_CODE: orgRoot.DIVISION_CODE, - SECTION_CODE: orgRoot.SECTION_CODE, - JOB_CODE: orgRoot.JOB_CODE, - orgTreeOrder: orgRoot.orgRootOrder, - orgTreePhoneEx: orgRoot.orgRootPhoneEx, - orgTreePhoneIn: orgRoot.orgRootPhoneIn, - orgTreeFax: orgRoot.orgRootFax, - orgRevisionId: orgRoot.orgRevisionId, - orgRootName: orgRoot.orgRootName, - isDeputy: orgRoot.isDeputy, - isCommission: orgRoot.isCommission, - responsibility: orgRoot.responsibility, - labelName: - orgRoot.orgRootName + " " + orgRoot.orgRootCode + "00" + " " + orgRoot.orgRootShortName, - totalPosition: await this.posMasterRepository.count({ - where: { orgRevisionId: orgRoot.orgRevisionId, orgRootId: orgRoot.id }, - }), - totalPositionCurrentUse: await this.posMasterRepository.count({ - where: { - orgRevisionId: orgRoot.orgRevisionId, - orgRootId: orgRoot.id, - current_holderId: Not(IsNull()) || Not(""), - }, - }), - totalPositionCurrentVacant: await this.posMasterRepository.count({ - where: { - orgRevisionId: orgRoot.orgRevisionId, - orgRootId: orgRoot.id, - current_holderId: IsNull() || "", - }, - }), - totalPositionNextUse: await this.posMasterRepository.count({ - where: { - orgRevisionId: orgRoot.orgRevisionId, - orgRootId: orgRoot.id, - next_holderId: Not(IsNull()) || Not(""), - }, - }), - totalPositionNextVacant: await this.posMasterRepository.count({ - where: { - orgRevisionId: orgRoot.orgRevisionId, - orgRootId: orgRoot.id, - next_holderId: IsNull() || "", - }, - }), - totalRootPosition: await this.posMasterRepository.count({ - where: { - orgRevisionId: orgRoot.orgRevisionId, - orgRootId: orgRoot.id, - orgChild1Id: IsNull() || "", - orgChild2Id: IsNull() || "", - orgChild3Id: IsNull() || "", - orgChild4Id: IsNull() || "", - }, - }), - totalRootPositionCurrentUse: await this.posMasterRepository.count({ - where: { - orgRevisionId: orgRoot.orgRevisionId, - orgRootId: orgRoot.id, - orgChild1Id: IsNull() || "", - orgChild2Id: IsNull() || "", - orgChild3Id: IsNull() || "", - orgChild4Id: IsNull() || "", - current_holderId: Not(IsNull()) || Not(""), - }, - }), - totalRootPositionCurrentVacant: await this.posMasterRepository.count({ - where: { - orgRevisionId: orgRoot.orgRevisionId, - orgRootId: orgRoot.id, - orgChild1Id: IsNull() || "", - orgChild2Id: IsNull() || "", - orgChild3Id: IsNull() || "", - orgChild4Id: IsNull() || "", - current_holderId: IsNull() || "", - }, - }), - totalRootPositionNextUse: await this.posMasterRepository.count({ - where: { - orgRevisionId: orgRoot.orgRevisionId, - orgRootId: orgRoot.id, - orgChild1Id: IsNull() || "", - orgChild2Id: IsNull() || "", - orgChild3Id: IsNull() || "", - orgChild4Id: IsNull() || "", - next_holderId: Not(IsNull()) || Not(""), - }, - }), - totalRootPositionNextVacant: await this.posMasterRepository.count({ - where: { - orgRevisionId: orgRoot.orgRevisionId, - orgRootId: orgRoot.id, - orgChild1Id: IsNull() || "", - orgChild2Id: IsNull() || "", - orgChild3Id: IsNull() || "", - orgChild4Id: IsNull() || "", - next_holderId: IsNull() || "", - }, - }), + // OPTIMIZED: Build formatted data using pre-calculated counts (no nested queries!) + const formattedData = orgRootData.map((orgRoot) => { + // const rootCounts = getCounts(orgRootMap, orgRoot.id); + // const rootPosCounts = getRootCounts(rootPosMap, orgRoot.id); - children: await Promise.all( - orgChild1Data - .filter((orgChild1) => orgChild1.orgRootId === orgRoot.id) - .map(async (orgChild1) => ({ - orgTreeId: orgChild1.id, - orgRootId: orgRoot.id, - orgLevel: 1, - orgName: `${orgChild1.orgChild1Name}/${orgRoot.orgRootName}`, - orgTreeName: orgChild1.orgChild1Name, - orgTreeShortName: orgChild1.orgChild1ShortName, - orgTreeCode: orgChild1.orgChild1Code, - orgCode: orgRoot.orgRootCode + orgChild1.orgChild1Code, - orgTreeRank: orgChild1.orgChild1Rank, - orgTreeRankSub: orgChild1.orgChild1RankSub, - orgRootDnaId: orgRoot.ancestorDNA, - orgChild1DnaId: orgChild1.ancestorDNA, - DEPARTMENT_CODE: orgChild1.DEPARTMENT_CODE, - DIVISION_CODE: orgChild1.DIVISION_CODE, - SECTION_CODE: orgChild1.SECTION_CODE, - JOB_CODE: orgChild1.JOB_CODE, - orgTreeOrder: orgChild1.orgChild1Order, - orgRootCode: orgRoot.orgRootCode, - orgTreePhoneEx: orgChild1.orgChild1PhoneEx, - orgTreePhoneIn: orgChild1.orgChild1PhoneIn, - orgTreeFax: orgChild1.orgChild1Fax, - orgRevisionId: orgRoot.orgRevisionId, - orgRootName: orgRoot.orgRootName, - responsibility: orgChild1.responsibility, - isOfficer: orgChild1.isOfficer, - isInformation: orgChild1.isInformation, - labelName: - orgChild1.orgChild1Name + - " " + - orgRoot.orgRootCode + - orgChild1.orgChild1Code + - " " + - orgChild1.orgChild1ShortName, - totalPosition: await this.posMasterRepository.count({ - where: { orgRevisionId: orgRoot.orgRevisionId, orgChild1Id: orgChild1.id }, - }), - totalPositionCurrentUse: await this.posMasterRepository.count({ - where: { - orgRevisionId: orgRoot.orgRevisionId, - orgChild1Id: orgChild1.id, - current_holderId: Not(IsNull()) || Not(""), - }, - }), - totalPositionCurrentVacant: await this.posMasterRepository.count({ - where: { - orgRevisionId: orgRoot.orgRevisionId, - orgChild1Id: orgChild1.id, - current_holderId: IsNull() || "", - }, - }), - totalPositionNextUse: await this.posMasterRepository.count({ - where: { - orgRevisionId: orgRoot.orgRevisionId, - orgChild1Id: orgChild1.id, - next_holderId: Not(IsNull()) || Not(""), - }, - }), - totalPositionNextVacant: await this.posMasterRepository.count({ - where: { - orgRevisionId: orgRoot.orgRevisionId, - orgChild1Id: orgChild1.id, - next_holderId: IsNull() || "", - }, - }), - totalRootPosition: await this.posMasterRepository.count({ - where: { - orgRevisionId: orgRoot.orgRevisionId, - orgRootId: orgRoot.id, - orgChild1Id: orgChild1.id, - orgChild2Id: IsNull() || "", - orgChild3Id: IsNull() || "", - orgChild4Id: IsNull() || "", - }, - }), - totalRootPositionCurrentUse: await this.posMasterRepository.count({ - where: { - orgRevisionId: orgRoot.orgRevisionId, - orgRootId: orgRoot.id, - orgChild1Id: orgChild1.id, - orgChild2Id: IsNull() || "", - orgChild3Id: IsNull() || "", - orgChild4Id: IsNull() || "", - current_holderId: Not(IsNull()) || Not(""), - }, - }), - totalRootPositionCurrentVacant: await this.posMasterRepository.count({ - where: { - orgRevisionId: orgRoot.orgRevisionId, - orgRootId: orgRoot.id, - orgChild1Id: orgChild1.id, - orgChild2Id: IsNull() || "", - orgChild3Id: IsNull() || "", - orgChild4Id: IsNull() || "", - current_holderId: IsNull() || "", - }, - }), - totalRootPositionNextUse: await this.posMasterRepository.count({ - where: { - orgRevisionId: orgRoot.orgRevisionId, - orgRootId: orgRoot.id, - orgChild1Id: orgChild1.id, - orgChild2Id: IsNull() || "", - orgChild3Id: IsNull() || "", - orgChild4Id: IsNull() || "", - next_holderId: Not(IsNull()) || Not(""), - }, - }), - totalRootPositionNextVacant: await this.posMasterRepository.count({ - where: { - orgRevisionId: orgRoot.orgRevisionId, - orgRootId: orgRoot.id, - orgChild1Id: orgChild1.id, - orgChild2Id: IsNull() || "", - orgChild3Id: IsNull() || "", - orgChild4Id: IsNull() || "", - next_holderId: IsNull() || "", - }, - }), + return { + orgTreeId: orgRoot.id, + orgLevel: 0, + orgName: orgRoot.orgRootName, + orgTreeName: orgRoot.orgRootName, + orgTreeShortName: orgRoot.orgRootShortName, + orgTreeCode: orgRoot.orgRootCode, + orgCode: orgRoot.orgRootCode + "00", + orgTreeRank: orgRoot.orgRootRank, + orgTreeRankSub: orgRoot.orgRootRankSub, + orgRootDnaId: orgRoot.ancestorDNA, + DEPARTMENT_CODE: orgRoot.DEPARTMENT_CODE, + DIVISION_CODE: orgRoot.DIVISION_CODE, + SECTION_CODE: orgRoot.SECTION_CODE, + JOB_CODE: orgRoot.JOB_CODE, + orgTreeOrder: orgRoot.orgRootOrder, + orgTreePhoneEx: orgRoot.orgRootPhoneEx, + orgTreePhoneIn: orgRoot.orgRootPhoneIn, + orgTreeFax: orgRoot.orgRootFax, + orgRevisionId: orgRoot.orgRevisionId, + orgRootName: orgRoot.orgRootName, + isDeputy: orgRoot.isDeputy, + isCommission: orgRoot.isCommission, + responsibility: orgRoot.responsibility, + labelName: + orgRoot.orgRootName + " " + orgRoot.orgRootCode + "00" + " " + orgRoot.orgRootShortName, + // totalPosition: rootCounts.totalPosition, + // totalPositionCurrentUse: rootCounts.totalPositionCurrentUse, + // totalPositionCurrentVacant: rootCounts.totalPositionCurrentVacant, + // totalPositionNextUse: rootCounts.totalPositionNextUse, + // totalPositionNextVacant: rootCounts.totalPositionNextVacant, + // totalRootPosition: rootPosCounts.totalRootPosition, + // totalRootPositionCurrentUse: rootPosCounts.totalRootPositionCurrentUse, + // totalRootPositionCurrentVacant: rootPosCounts.totalRootPositionCurrentVacant, + // totalRootPositionNextUse: rootPosCounts.totalRootPositionNextUse, + // totalRootPositionNextVacant: rootPosCounts.totalRootPositionNextVacant, + children: orgChild1Data + .filter((orgChild1) => orgChild1.orgRootId === orgRoot.id) + .map((orgChild1) => { + // const child1Counts = getCounts(orgChild1Map, orgChild1.id); + // const child1PosKey = `${orgRoot.id}-${orgChild1.id}`; + // const child1PosCounts = getRootCounts(rootPosMap, child1PosKey); - children: await Promise.all( - orgChild2Data - .filter((orgChild2) => orgChild2.orgChild1Id === orgChild1.id) - .map(async (orgChild2) => ({ - orgTreeId: orgChild2.id, - orgRootId: orgChild1.id, - orgLevel: 2, - orgName: `${orgChild2.orgChild2Name}/${orgChild1.orgChild1Name}/${orgRoot.orgRootName}`, - orgTreeName: orgChild2.orgChild2Name, - orgTreeShortName: orgChild2.orgChild2ShortName, - orgTreeCode: orgChild2.orgChild2Code, - orgCode: orgRoot.orgRootCode + orgChild2.orgChild2Code, - orgTreeRank: orgChild2.orgChild2Rank, - orgTreeRankSub: orgChild2.orgChild2RankSub, - orgRootDnaId: orgRoot.ancestorDNA, - orgChild1DnaId: orgChild1.ancestorDNA, - orgChild2DnaId: orgChild2.ancestorDNA, - DEPARTMENT_CODE: orgChild2.DEPARTMENT_CODE, - DIVISION_CODE: orgChild2.DIVISION_CODE, - SECTION_CODE: orgChild2.SECTION_CODE, - JOB_CODE: orgChild2.JOB_CODE, - orgTreeOrder: orgChild2.orgChild2Order, - orgRootCode: orgRoot.orgRootCode, - orgTreePhoneEx: orgChild2.orgChild2PhoneEx, - orgTreePhoneIn: orgChild2.orgChild2PhoneIn, - orgTreeFax: orgChild2.orgChild2Fax, - orgRevisionId: orgRoot.orgRevisionId, - orgRootName: orgRoot.orgRootName, - responsibility: orgChild2.responsibility, - labelName: - orgChild2.orgChild2Name + - " " + - orgRoot.orgRootCode + - orgChild2.orgChild2Code + - " " + - orgChild2.orgChild2ShortName, - totalPosition: await this.posMasterRepository.count({ - where: { - orgRevisionId: orgRoot.orgRevisionId, - orgChild2Id: orgChild2.id, - }, - }), - totalPositionCurrentUse: await this.posMasterRepository.count({ - where: { - orgRevisionId: orgRoot.orgRevisionId, - orgChild2Id: orgChild2.id, - current_holderId: Not(IsNull()) || Not(""), - }, - }), - totalPositionCurrentVacant: await this.posMasterRepository.count({ - where: { - orgRevisionId: orgRoot.orgRevisionId, - orgChild2Id: orgChild2.id, - current_holderId: IsNull() || "", - }, - }), - totalPositionNextUse: await this.posMasterRepository.count({ - where: { - orgRevisionId: orgRoot.orgRevisionId, - orgChild2Id: orgChild2.id, - next_holderId: Not(IsNull()) || Not(""), - }, - }), - totalPositionNextVacant: await this.posMasterRepository.count({ - where: { - orgRevisionId: orgRoot.orgRevisionId, - orgChild2Id: orgChild2.id, - next_holderId: IsNull() || "", - }, - }), - totalRootPosition: await this.posMasterRepository.count({ - where: { - orgRevisionId: orgRoot.orgRevisionId, - orgRootId: orgRoot.id, - orgChild1Id: orgChild1.id, - orgChild2Id: orgChild2.id, - orgChild3Id: IsNull() || "", - orgChild4Id: IsNull() || "", - }, - }), - totalRootPositionCurrentUse: await this.posMasterRepository.count({ - where: { - orgRevisionId: orgRoot.orgRevisionId, - orgRootId: orgRoot.id, - orgChild1Id: orgChild1.id, - orgChild2Id: orgChild2.id, - orgChild3Id: IsNull() || "", - orgChild4Id: IsNull() || "", - current_holderId: Not(IsNull()) || Not(""), - }, - }), - totalRootPositionCurrentVacant: await this.posMasterRepository.count({ - where: { - orgRevisionId: orgRoot.orgRevisionId, - orgRootId: orgRoot.id, - orgChild1Id: orgChild1.id, - orgChild2Id: orgChild2.id, - orgChild3Id: IsNull() || "", - orgChild4Id: IsNull() || "", - current_holderId: IsNull() || "", - }, - }), - totalRootPositionNextUse: await this.posMasterRepository.count({ - where: { - orgRevisionId: orgRoot.orgRevisionId, - orgRootId: orgRoot.id, - orgChild1Id: orgChild1.id, - orgChild2Id: orgChild2.id, - orgChild3Id: IsNull() || "", - orgChild4Id: IsNull() || "", - next_holderId: Not(IsNull()) || Not(""), - }, - }), - totalRootPositionNextVacant: await this.posMasterRepository.count({ - where: { - orgRevisionId: orgRoot.orgRevisionId, - orgRootId: orgRoot.id, - orgChild1Id: orgChild1.id, - orgChild2Id: orgChild2.id, - orgChild3Id: IsNull() || "", - orgChild4Id: IsNull() || "", - next_holderId: IsNull() || "", - }, - }), + return { + orgTreeId: orgChild1.id, + orgRootId: orgRoot.id, + orgLevel: 1, + orgName: `${orgChild1.orgChild1Name}/${orgRoot.orgRootName}`, + orgTreeName: orgChild1.orgChild1Name, + orgTreeShortName: orgChild1.orgChild1ShortName, + orgTreeCode: orgChild1.orgChild1Code, + orgCode: orgRoot.orgRootCode + orgChild1.orgChild1Code, + orgTreeRank: orgChild1.orgChild1Rank, + orgTreeRankSub: orgChild1.orgChild1RankSub, + orgRootDnaId: orgRoot.ancestorDNA, + orgChild1DnaId: orgChild1.ancestorDNA, + DEPARTMENT_CODE: orgChild1.DEPARTMENT_CODE, + DIVISION_CODE: orgChild1.DIVISION_CODE, + SECTION_CODE: orgChild1.SECTION_CODE, + JOB_CODE: orgChild1.JOB_CODE, + orgTreeOrder: orgChild1.orgChild1Order, + orgRootCode: orgRoot.orgRootCode, + orgTreePhoneEx: orgChild1.orgChild1PhoneEx, + orgTreePhoneIn: orgChild1.orgChild1PhoneIn, + orgTreeFax: orgChild1.orgChild1Fax, + orgRevisionId: orgRoot.orgRevisionId, + orgRootName: orgRoot.orgRootName, + responsibility: orgChild1.responsibility, + isOfficer: orgChild1.isOfficer, + isInformation: orgChild1.isInformation, + labelName: + orgChild1.orgChild1Name + + " " + + orgRoot.orgRootCode + + orgChild1.orgChild1Code + + " " + + orgChild1.orgChild1ShortName, + // totalPosition: child1Counts.totalPosition, + // totalPositionCurrentUse: child1Counts.totalPositionCurrentUse, + // totalPositionCurrentVacant: child1Counts.totalPositionCurrentVacant, + // totalPositionNextUse: child1Counts.totalPositionNextUse, + // totalPositionNextVacant: child1Counts.totalPositionNextVacant, + // totalRootPosition: child1PosCounts.totalRootPosition, + // totalRootPositionCurrentUse: child1PosCounts.totalRootPositionCurrentUse, + // totalRootPositionCurrentVacant: child1PosCounts.totalRootPositionCurrentVacant, + // totalRootPositionNextUse: child1PosCounts.totalRootPositionNextUse, + // totalRootPositionNextVacant: child1PosCounts.totalRootPositionNextVacant, + children: orgChild2Data + .filter((orgChild2) => orgChild2.orgChild1Id === orgChild1.id) + .map((orgChild2) => { + // const child2Counts = getCounts(orgChild2Map, orgChild2.id); + // const child2PosKey = `${orgRoot.id}-${orgChild1.id}-${orgChild2.id}`; + // const child2PosCounts = getRootCounts(rootPosMap, child2PosKey); - children: await Promise.all( - orgChild3Data - .filter((orgChild3) => orgChild3.orgChild2Id === orgChild2.id) - .map(async (orgChild3) => ({ - orgTreeId: orgChild3.id, - orgRootId: orgChild2.id, - orgLevel: 3, - orgName: `${orgChild3.orgChild3Name}/${orgChild2.orgChild2Name}/${orgChild1.orgChild1Name}/${orgRoot.orgRootName}`, - orgTreeName: orgChild3.orgChild3Name, - orgTreeShortName: orgChild3.orgChild3ShortName, - orgTreeCode: orgChild3.orgChild3Code, - orgCode: orgRoot.orgRootCode + orgChild3.orgChild3Code, - orgTreeRank: orgChild3.orgChild3Rank, - orgTreeRankSub: orgChild3.orgChild3RankSub, - orgRootDnaId: orgRoot.ancestorDNA, - orgChild1DnaId: orgChild1.ancestorDNA, - orgChild2DnaId: orgChild2.ancestorDNA, - orgChild3DnaId: orgChild3.ancestorDNA, - DEPARTMENT_CODE: orgChild3.DEPARTMENT_CODE, - DIVISION_CODE: orgChild3.DIVISION_CODE, - SECTION_CODE: orgChild3.SECTION_CODE, - JOB_CODE: orgChild3.JOB_CODE, - orgTreeOrder: orgChild3.orgChild3Order, - orgRootCode: orgRoot.orgRootCode, - orgTreePhoneEx: orgChild3.orgChild3PhoneEx, - orgTreePhoneIn: orgChild3.orgChild3PhoneIn, - orgTreeFax: orgChild3.orgChild3Fax, - orgRevisionId: orgRoot.orgRevisionId, - orgRootName: orgRoot.orgRootName, - responsibility: orgChild3.responsibility, - labelName: - orgChild3.orgChild3Name + - " " + - orgRoot.orgRootCode + - orgChild3.orgChild3Code + - " " + - orgChild3.orgChild3ShortName, - totalPosition: await this.posMasterRepository.count({ - where: { - orgRevisionId: orgRoot.orgRevisionId, - orgChild3Id: orgChild3.id, - }, - }), - totalPositionCurrentUse: await this.posMasterRepository.count({ - where: { - orgRevisionId: orgRoot.orgRevisionId, - orgChild3Id: orgChild3.id, - current_holderId: Not(IsNull()) || Not(""), - }, - }), - totalPositionCurrentVacant: await this.posMasterRepository.count({ - where: { - orgRevisionId: orgRoot.orgRevisionId, - orgChild3Id: orgChild3.id, - current_holderId: IsNull() || "", - }, - }), - totalPositionNextUse: await this.posMasterRepository.count({ - where: { - orgRevisionId: orgRoot.orgRevisionId, - orgChild3Id: orgChild3.id, - next_holderId: Not(IsNull()) || Not(""), - }, - }), - totalPositionNextVacant: await this.posMasterRepository.count({ - where: { - orgRevisionId: orgRoot.orgRevisionId, - orgChild3Id: orgChild3.id, - next_holderId: IsNull() || "", - }, - }), - totalRootPosition: await this.posMasterRepository.count({ - where: { - orgRevisionId: orgRoot.orgRevisionId, - orgRootId: orgRoot.id, - orgChild1Id: orgChild1.id, - orgChild2Id: orgChild2.id, - orgChild3Id: orgChild3.id, - orgChild4Id: IsNull() || "", - }, - }), - totalRootPositionCurrentUse: await this.posMasterRepository.count({ - where: { - orgRevisionId: orgRoot.orgRevisionId, - orgRootId: orgRoot.id, - orgChild1Id: orgChild1.id, - orgChild2Id: orgChild2.id, - orgChild3Id: orgChild3.id, - orgChild4Id: IsNull() || "", - current_holderId: Not(IsNull()) || Not(""), - }, - }), - totalRootPositionCurrentVacant: await this.posMasterRepository.count({ - where: { - orgRevisionId: orgRoot.orgRevisionId, - orgRootId: orgRoot.id, - orgChild1Id: orgChild1.id, - orgChild2Id: orgChild2.id, - orgChild3Id: orgChild3.id, - orgChild4Id: IsNull() || "", - current_holderId: IsNull() || "", - }, - }), - totalRootPositionNextUse: await this.posMasterRepository.count({ - where: { - orgRevisionId: orgRoot.orgRevisionId, - orgRootId: orgRoot.id, - orgChild1Id: orgChild1.id, - orgChild2Id: orgChild2.id, - orgChild3Id: orgChild3.id, - orgChild4Id: IsNull() || "", - next_holderId: Not(IsNull()) || Not(""), - }, - }), - totalRootPositionNextVacant: await this.posMasterRepository.count({ - where: { - orgRevisionId: orgRoot.orgRevisionId, - orgRootId: orgRoot.id, - orgChild1Id: orgChild1.id, - orgChild2Id: orgChild2.id, - orgChild3Id: orgChild3.id, - orgChild4Id: IsNull() || "", - next_holderId: IsNull() || "", - }, - }), + return { + orgTreeId: orgChild2.id, + orgRootId: orgChild1.id, + orgLevel: 2, + orgName: `${orgChild2.orgChild2Name}/${orgChild1.orgChild1Name}/${orgRoot.orgRootName}`, + orgTreeName: orgChild2.orgChild2Name, + orgTreeShortName: orgChild2.orgChild2ShortName, + orgTreeCode: orgChild2.orgChild2Code, + orgCode: orgRoot.orgRootCode + orgChild2.orgChild2Code, + orgTreeRank: orgChild2.orgChild2Rank, + orgTreeRankSub: orgChild2.orgChild2RankSub, + orgRootDnaId: orgRoot.ancestorDNA, + orgChild1DnaId: orgChild1.ancestorDNA, + orgChild2DnaId: orgChild2.ancestorDNA, + DEPARTMENT_CODE: orgChild2.DEPARTMENT_CODE, + DIVISION_CODE: orgChild2.DIVISION_CODE, + SECTION_CODE: orgChild2.SECTION_CODE, + JOB_CODE: orgChild2.JOB_CODE, + orgTreeOrder: orgChild2.orgChild2Order, + orgRootCode: orgRoot.orgRootCode, + orgTreePhoneEx: orgChild2.orgChild2PhoneEx, + orgTreePhoneIn: orgChild2.orgChild2PhoneIn, + orgTreeFax: orgChild2.orgChild2Fax, + orgRevisionId: orgRoot.orgRevisionId, + orgRootName: orgRoot.orgRootName, + responsibility: orgChild2.responsibility, + labelName: + orgChild2.orgChild2Name + + " " + + orgRoot.orgRootCode + + orgChild2.orgChild2Code + + " " + + orgChild2.orgChild2ShortName, + // totalPosition: child2Counts.totalPosition, + // totalPositionCurrentUse: child2Counts.totalPositionCurrentUse, + // totalPositionCurrentVacant: child2Counts.totalPositionCurrentVacant, + // totalPositionNextUse: child2Counts.totalPositionNextUse, + // totalPositionNextVacant: child2Counts.totalPositionNextVacant, + // totalRootPosition: child2PosCounts.totalRootPosition, + // totalRootPositionCurrentUse: child2PosCounts.totalRootPositionCurrentUse, + // totalRootPositionCurrentVacant: child2PosCounts.totalRootPositionCurrentVacant, + // totalRootPositionNextUse: child2PosCounts.totalRootPositionNextUse, + // totalRootPositionNextVacant: child2PosCounts.totalRootPositionNextVacant, + children: orgChild3Data + .filter((orgChild3) => orgChild3.orgChild2Id === orgChild2.id) + .map((orgChild3) => { + // const child3Counts = getCounts(orgChild3Map, orgChild3.id); + // const child3PosKey = `${orgRoot.id}-${orgChild1.id}-${orgChild2.id}-${orgChild3.id}`; + // const child3PosCounts = getRootCounts(rootPosMap, child3PosKey); - children: await Promise.all( - orgChild4Data - .filter((orgChild4) => orgChild4.orgChild3Id === orgChild3.id) - .map(async (orgChild4) => ({ - orgTreeId: orgChild4.id, - orgRootId: orgChild3.id, - orgLevel: 4, - orgName: `${orgChild4.orgChild4Name}/${orgChild3.orgChild3Name}/${orgChild2.orgChild2Name}/${orgChild1.orgChild1Name}/${orgRoot.orgRootName}`, - orgTreeName: orgChild4.orgChild4Name, - orgTreeShortName: orgChild4.orgChild4ShortName, - orgTreeCode: orgChild4.orgChild4Code, - orgCode: orgRoot.orgRootCode + orgChild4.orgChild4Code, - orgTreeRank: orgChild4.orgChild4Rank, - orgTreeRankSub: orgChild4.orgChild4RankSub, - orgRootDnaId: orgRoot.ancestorDNA, - orgChild1DnaId: orgChild1.ancestorDNA, - orgChild2DnaId: orgChild2.ancestorDNA, - orgChild3DnaId: orgChild3.ancestorDNA, - orgChild4DnaId: orgChild4.ancestorDNA, - DEPARTMENT_CODE: orgChild4.DEPARTMENT_CODE, - DIVISION_CODE: orgChild4.DIVISION_CODE, - SECTION_CODE: orgChild4.SECTION_CODE, - JOB_CODE: orgChild4.JOB_CODE, - orgTreeOrder: orgChild4.orgChild4Order, - orgRootCode: orgRoot.orgRootCode, - orgTreePhoneEx: orgChild4.orgChild4PhoneEx, - orgTreePhoneIn: orgChild4.orgChild4PhoneIn, - orgTreeFax: orgChild4.orgChild4Fax, - orgRevisionId: orgRoot.orgRevisionId, - orgRootName: orgRoot.orgRootName, - responsibility: orgChild4.responsibility, - labelName: - orgChild4.orgChild4Name + - " " + - orgRoot.orgRootCode + - orgChild4.orgChild4Code + - " " + - orgChild4.orgChild4ShortName, - totalPosition: await this.posMasterRepository.count({ - where: { - orgRevisionId: orgRoot.orgRevisionId, - orgChild4Id: orgChild4.id, - }, - }), - totalPositionCurrentUse: await this.posMasterRepository.count({ - where: { - orgRevisionId: orgRoot.orgRevisionId, - orgChild4Id: orgChild4.id, - current_holderId: Not(IsNull()) || Not(""), - }, - }), - totalPositionCurrentVacant: await this.posMasterRepository.count({ - where: { - orgRevisionId: orgRoot.orgRevisionId, - orgChild4Id: orgChild4.id, - current_holderId: IsNull() || "", - }, - }), - totalPositionNextUse: await this.posMasterRepository.count({ - where: { - orgRevisionId: orgRoot.orgRevisionId, - orgChild4Id: orgChild4.id, - next_holderId: Not(IsNull()) || Not(""), - }, - }), - totalPositionNextVacant: await this.posMasterRepository.count({ - where: { - orgRevisionId: orgRoot.orgRevisionId, - orgChild4Id: orgChild4.id, - next_holderId: IsNull() || "", - }, - }), - totalRootPosition: await this.posMasterRepository.count({ - where: { - orgRevisionId: orgRoot.orgRevisionId, - orgRootId: orgRoot.id, - orgChild1Id: orgChild1.id, - orgChild2Id: orgChild2.id, - orgChild3Id: orgChild3.id, - orgChild4Id: orgChild4.id, - }, - }), - totalRootPositionCurrentUse: await this.posMasterRepository.count( - { - where: { - orgRevisionId: orgRoot.orgRevisionId, - orgRootId: orgRoot.id, - orgChild1Id: orgChild1.id, - orgChild2Id: orgChild2.id, - orgChild3Id: orgChild3.id, - orgChild4Id: orgChild4.id, - current_holderId: Not(IsNull()) || Not(""), - }, - }, - ), - totalRootPositionCurrentVacant: - await this.posMasterRepository.count({ - where: { - orgRevisionId: orgRoot.orgRevisionId, - orgRootId: orgRoot.id, - orgChild1Id: orgChild1.id, - orgChild2Id: orgChild2.id, - orgChild3Id: orgChild3.id, - orgChild4Id: orgChild4.id, - current_holderId: IsNull() || "", - }, - }), - totalRootPositionNextUse: await this.posMasterRepository.count({ - where: { - orgRevisionId: orgRoot.orgRevisionId, - orgRootId: orgRoot.id, - orgChild1Id: orgChild1.id, - orgChild2Id: orgChild2.id, - orgChild3Id: orgChild3.id, - orgChild4Id: orgChild4.id, - next_holderId: Not(IsNull()) || Not(""), - }, - }), - totalRootPositionNextVacant: await this.posMasterRepository.count( - { - where: { - orgRevisionId: orgRoot.orgRevisionId, - orgRootId: orgRoot.id, - orgChild1Id: orgChild1.id, - orgChild2Id: orgChild2.id, - orgChild3Id: orgChild3.id, - orgChild4Id: orgChild4.id, - next_holderId: IsNull() || "", - }, - }, - ), - })), - ), - })), - ), - })), - ), - })), - ), - }; - }), - ); + return { + orgTreeId: orgChild3.id, + orgRootId: orgChild2.id, + orgLevel: 3, + orgName: `${orgChild3.orgChild3Name}/${orgChild2.orgChild2Name}/${orgChild1.orgChild1Name}/${orgRoot.orgRootName}`, + orgTreeName: orgChild3.orgChild3Name, + orgTreeShortName: orgChild3.orgChild3ShortName, + orgTreeCode: orgChild3.orgChild3Code, + orgCode: orgRoot.orgRootCode + orgChild3.orgChild3Code, + orgTreeRank: orgChild3.orgChild3Rank, + orgTreeRankSub: orgChild3.orgChild3RankSub, + orgRootDnaId: orgRoot.ancestorDNA, + orgChild1DnaId: orgChild1.ancestorDNA, + orgChild2DnaId: orgChild2.ancestorDNA, + orgChild3DnaId: orgChild3.ancestorDNA, + DEPARTMENT_CODE: orgChild3.DEPARTMENT_CODE, + DIVISION_CODE: orgChild3.DIVISION_CODE, + SECTION_CODE: orgChild3.SECTION_CODE, + JOB_CODE: orgChild3.JOB_CODE, + orgTreeOrder: orgChild3.orgChild3Order, + orgRootCode: orgRoot.orgRootCode, + orgTreePhoneEx: orgChild3.orgChild3PhoneEx, + orgTreePhoneIn: orgChild3.orgChild3PhoneIn, + orgTreeFax: orgChild3.orgChild3Fax, + orgRevisionId: orgRoot.orgRevisionId, + orgRootName: orgRoot.orgRootName, + responsibility: orgChild3.responsibility, + labelName: + orgChild3.orgChild3Name + + " " + + orgRoot.orgRootCode + + orgChild3.orgChild3Code + + " " + + orgChild3.orgChild3ShortName, + // totalPosition: child3Counts.totalPosition, + // totalPositionCurrentUse: child3Counts.totalPositionCurrentUse, + // totalPositionCurrentVacant: child3Counts.totalPositionCurrentVacant, + // totalPositionNextUse: child3Counts.totalPositionNextUse, + // totalPositionNextVacant: child3Counts.totalPositionNextVacant, + // totalRootPosition: child3PosCounts.totalRootPosition, + // totalRootPositionCurrentUse: child3PosCounts.totalRootPositionCurrentUse, + // totalRootPositionCurrentVacant: + // child3PosCounts.totalRootPositionCurrentVacant, + // totalRootPositionNextUse: child3PosCounts.totalRootPositionNextUse, + // totalRootPositionNextVacant: child3PosCounts.totalRootPositionNextVacant, + children: orgChild4Data + .filter((orgChild4) => orgChild4.orgChild3Id === orgChild3.id) + .map((orgChild4) => { + // const child4Counts = getCounts(orgChild4Map, orgChild4.id); + // const child4PosKey = `${orgRoot.id}-${orgChild1.id}-${orgChild2.id}-${orgChild3.id}-${orgChild4.id}`; + // const child4PosCounts = getRootCounts(rootPosMap, child4PosKey); + + return { + orgTreeId: orgChild4.id, + orgRootId: orgChild3.id, + orgLevel: 4, + orgName: `${orgChild4.orgChild4Name}/${orgChild3.orgChild3Name}/${orgChild2.orgChild2Name}/${orgChild1.orgChild1Name}/${orgRoot.orgRootName}`, + orgTreeName: orgChild4.orgChild4Name, + orgTreeShortName: orgChild4.orgChild4ShortName, + orgTreeCode: orgChild4.orgChild4Code, + orgCode: orgRoot.orgRootCode + orgChild4.orgChild4Code, + orgTreeRank: orgChild4.orgChild4Rank, + orgTreeRankSub: orgChild4.orgChild4RankSub, + orgRootDnaId: orgRoot.ancestorDNA, + orgChild1DnaId: orgChild1.ancestorDNA, + orgChild2DnaId: orgChild2.ancestorDNA, + orgChild3DnaId: orgChild3.ancestorDNA, + orgChild4DnaId: orgChild4.ancestorDNA, + DEPARTMENT_CODE: orgChild4.DEPARTMENT_CODE, + DIVISION_CODE: orgChild4.DIVISION_CODE, + SECTION_CODE: orgChild4.SECTION_CODE, + JOB_CODE: orgChild4.JOB_CODE, + orgTreeOrder: orgChild4.orgChild4Order, + orgRootCode: orgRoot.orgRootCode, + orgTreePhoneEx: orgChild4.orgChild4PhoneEx, + orgTreePhoneIn: orgChild4.orgChild4PhoneIn, + orgTreeFax: orgChild4.orgChild4Fax, + orgRevisionId: orgRoot.orgRevisionId, + orgRootName: orgRoot.orgRootName, + responsibility: orgChild4.responsibility, + labelName: + orgChild4.orgChild4Name + + " " + + orgRoot.orgRootCode + + orgChild4.orgChild4Code + + " " + + orgChild4.orgChild4ShortName, + // totalPosition: child4Counts.totalPosition, + // totalPositionCurrentUse: child4Counts.totalPositionCurrentUse, + // totalPositionCurrentVacant: child4Counts.totalPositionCurrentVacant, + // totalPositionNextUse: child4Counts.totalPositionNextUse, + // totalPositionNextVacant: child4Counts.totalPositionNextVacant, + // totalRootPosition: child4PosCounts.totalRootPosition, + // totalRootPositionCurrentUse: + // child4PosCounts.totalRootPositionCurrentUse, + // totalRootPositionCurrentVacant: + // child4PosCounts.totalRootPositionCurrentVacant, + // totalRootPositionNextUse: child4PosCounts.totalRootPositionNextUse, + // totalRootPositionNextVacant: + // child4PosCounts.totalRootPositionNextVacant, + children: [], + }; + }), + }; + }), + }; + }), + }; + }), + }; + }); + + // OPTIMIZED: Cache the result + await orgStructureCache.set(id, rootId, formattedData); return new HttpSuccess(formattedData); } @@ -2027,7 +1670,7 @@ export class OrganizationController extends Controller { // กำหนดการเข้าถึงข้อมูลตามสถานะและสิทธิ์ const isCurrentActive = !orgRevision.orgRevisionIsDraft && orgRevision.orgRevisionIsCurrent; if (isCurrentActive) { - if (profileAssign && _privilege.privilege !== "OWNER") { + if (profileAssign && _privilege.privilege !== "OWNER" && _privilege.privilege !== "PARENT") { if (_privilege.privilege == "NORMAL") { const holder = profile.current_holders.find((x) => x.orgRevisionId === id); if (!holder) return; @@ -2079,6 +1722,7 @@ export class OrganizationController extends Controller { }, ) .select([ + "orgRoot.ancestorDNA", "orgRoot.id", "orgRoot.misId", "orgRoot.isDeputy", @@ -2265,6 +1909,7 @@ export class OrganizationController extends Controller { const formattedData = await Promise.all( orgRootData.map(async (orgRoot) => { return { + orgDnaId: orgRoot.ancestorDNA, orgTreeId: orgRoot.id, orgLevel: 0, misId: orgRoot.misId, @@ -3827,14 +3472,1174 @@ export class OrganizationController extends Controller { return new HttpSuccess(formattedData_); } + // /** + // * API Organizational StructChart + // * + // * @summary Organizational StructChart + // * + // */ + // @Get("struct-chart/{idNode}/{type}") + // async structchart(@Path() idNode: string, type: number) { + // switch (type) { + // case 0: { + // const data = await this.orgRevisionRepository.findOne({ + // where: { id: idNode }, + // relations: [ + // "orgRoots", + // "orgRoots.orgChild1s", + // "orgRoots.orgChild1s.orgChild2s", + // "orgRoots.orgChild1s.orgChild2s.orgChild3s", + // "orgRoots.orgChild1s.orgChild2s.orgChild3s.orgChild4s", + // ], + // }); + // if (!data) { + // throw new HttpError(HttpStatusCode.NOT_FOUND, "not found revision"); + // } + + // const formattedData = { + // departmentName: data.orgRevisionName, + // deptID: data.id, + // type: 0, + // // heads: + // totalPositionCount: await this.posMasterRepository.count({ + // where: { orgRevisionId: data.id }, + // }), + // totalPositionVacant: + // data.orgRevisionIsDraft == true + // ? await this.posMasterRepository.count({ + // where: { + // orgRevisionId: data.id, + // next_holderId: IsNull() || "", + // }, + // }) + // : await this.posMasterRepository.count({ + // where: { + // orgRevisionId: data.id, + // current_holderId: IsNull() || "", + // }, + // }), + // // totalPositionCurrentVacant: await this.posMasterRepository.count({ + // // where: { + // // orgRevisionId: data.id, + // // current_holderId: IsNull() || "", + // // }, + // // }), + // // totalPositionNextVacant: await this.posMasterRepository.count({ + // // where: { + // // orgRevisionId: data.id, + // // next_holderId: IsNull() || "", + // // }, + // // }), + // children: await Promise.all( + // data.orgRoots + // .sort((a, b) => a.orgRootOrder - b.orgRootOrder) + // .map(async (orgRoot: OrgRoot) => { + // return { + // departmentName: orgRoot.orgRootName, + // deptID: orgRoot.id, + // type: 1, + // // heads: + // totalPositionCount: await this.posMasterRepository.count({ + // where: { orgRevisionId: data.id, orgRootId: orgRoot.id }, + // }), + // totalPositionVacant: + // data.orgRevisionIsDraft == true + // ? await this.posMasterRepository.count({ + // where: { + // orgRevisionId: data.id, + // orgRootId: orgRoot.id, + // next_holderId: IsNull() || "", + // }, + // }) + // : await this.posMasterRepository.count({ + // where: { + // orgRevisionId: data.id, + // orgRootId: orgRoot.id, + // current_holderId: IsNull() || "", + // }, + // }), + // // totalPositionCurrentVacant: await this.posMasterRepository.count({ + // // where: { + // // orgRevisionId: data.id, + // // orgRootId: orgRoot.id, + // // current_holderId: IsNull() || "", + // // }, + // // }), + // // totalPositionNextVacant: await this.posMasterRepository.count({ + // // where: { + // // orgRevisionId: data.id, + // // orgRootId: orgRoot.id, + // // next_holderId: IsNull() || "", + // // }, + // // }), + // children: await Promise.all( + // orgRoot.orgChild1s + // .sort((a, b) => a.orgChild1Order - b.orgChild1Order) + // .map(async (orgChild1) => ({ + // departmentName: orgChild1.orgChild1Name, + // deptID: orgChild1.id, + // type: 2, + // // heads: + // totalPositionCount: await this.posMasterRepository.count({ + // where: { + // orgRevisionId: data.id, + // orgRootId: orgRoot.id, + // orgChild1Id: orgChild1.id, + // }, + // }), + // totalPositionVacant: + // data.orgRevisionIsDraft == true + // ? await this.posMasterRepository.count({ + // where: { + // orgRevisionId: data.id, + // orgRootId: orgRoot.id, + // orgChild1Id: orgChild1.id, + // next_holderId: IsNull() || "", + // }, + // }) + // : await this.posMasterRepository.count({ + // where: { + // orgRevisionId: data.id, + // orgRootId: orgRoot.id, + // orgChild1Id: orgChild1.id, + // current_holderId: IsNull() || "", + // }, + // }), + // // totalPositionCurrentVacant: await this.posMasterRepository.count({ + // // where: { + // // orgRevisionId: data.id, + // // orgRootId: orgRoot.id, + // // orgChild1Id: orgChild1.id, + // // current_holderId: IsNull() || "", + // // }, + // // }), + // // totalPositionNextVacant: await this.posMasterRepository.count({ + // // where: { + // // orgRevisionId: data.id, + // // orgRootId: orgRoot.id, + // // orgChild1Id: orgChild1.id, + // // next_holderId: IsNull() || "", + // // }, + // // }), + // children: await Promise.all( + // orgChild1.orgChild2s + // .sort((a, b) => a.orgChild2Order - b.orgChild2Order) + // .map(async (orgChild2) => ({ + // departmentName: orgChild2.orgChild2Name, + // deptID: orgChild2.id, + // type: 3, + // // heads: + // totalPositionCount: await this.posMasterRepository.count({ + // where: { + // orgRevisionId: data.id, + // orgRootId: orgRoot.id, + // orgChild1Id: orgChild1.id, + // orgChild2Id: orgChild2.id, + // }, + // }), + // totalPositionVacant: + // data.orgRevisionIsDraft == true + // ? await this.posMasterRepository.count({ + // where: { + // orgRevisionId: data.id, + // orgRootId: orgRoot.id, + // orgChild1Id: orgChild1.id, + // orgChild2Id: orgChild2.id, + // next_holderId: IsNull() || "", + // }, + // }) + // : await this.posMasterRepository.count({ + // where: { + // orgRevisionId: data.id, + // orgRootId: orgRoot.id, + // orgChild1Id: orgChild1.id, + // orgChild2Id: orgChild2.id, + // current_holderId: IsNull() || "", + // }, + // }), + // // totalPositionCurrentVacant: await this.posMasterRepository.count({ + // // where: { + // // orgRevisionId: data.id, + // // orgRootId: orgRoot.id, + // // orgChild1Id: orgChild1.id, + // // orgChild2Id: orgChild2.id, + // // current_holderId: IsNull() || "", + // // }, + // // }), + // // totalPositionNextVacant: await this.posMasterRepository.count({ + // // where: { + // // orgRevisionId: data.id, + // // orgRootId: orgRoot.id, + // // orgChild1Id: orgChild1.id, + // // orgChild2Id: orgChild2.id, + // // next_holderId: IsNull() || "", + // // }, + // // }), + // children: await Promise.all( + // orgChild2.orgChild3s + // .sort((a, b) => a.orgChild3Order - b.orgChild3Order) + // .map(async (orgChild3) => ({ + // departmentName: orgChild3.orgChild3Name, + // deptID: orgChild3.id, + // type: 4, + // // heads: + // totalPositionCount: await this.posMasterRepository.count({ + // where: { + // orgRevisionId: data.id, + // orgRootId: orgRoot.id, + // orgChild1Id: orgChild1.id, + // orgChild2Id: orgChild2.id, + // orgChild3Id: orgChild3.id, + // }, + // }), + // totalPositionVacant: + // data.orgRevisionIsDraft == true + // ? await this.posMasterRepository.count({ + // where: { + // orgRevisionId: data.id, + // orgRootId: orgRoot.id, + // orgChild1Id: orgChild1.id, + // orgChild2Id: orgChild2.id, + // orgChild3Id: orgChild3.id, + // next_holderId: IsNull() || "", + // }, + // }) + // : await this.posMasterRepository.count({ + // where: { + // orgRevisionId: data.id, + // orgRootId: orgRoot.id, + // orgChild1Id: orgChild1.id, + // orgChild2Id: orgChild2.id, + // orgChild3Id: orgChild3.id, + // current_holderId: IsNull() || "", + // }, + // }), + // // totalPositionCurrentVacant: + // // await this.posMasterRepository.count({ + // // where: { + // // orgRevisionId: data.id, + // // orgRootId: orgRoot.id, + // // orgChild1Id: orgChild1.id, + // // orgChild2Id: orgChild2.id, + // // orgChild3Id: orgChild3.id, + // // current_holderId: IsNull() || "", + // // }, + // // }), + // // totalPositionNextVacant: await this.posMasterRepository.count( + // // { + // // where: { + // // orgRevisionId: data.id, + // // orgRootId: orgRoot.id, + // // orgChild1Id: orgChild1.id, + // // orgChild2Id: orgChild2.id, + // // orgChild3Id: orgChild3.id, + // // next_holderId: IsNull() || "", + // // }, + // // }, + // // ), + // children: await Promise.all( + // orgChild3.orgChild4s + // .sort((a, b) => a.orgChild4Order - b.orgChild4Order) + // .map(async (orgChild4) => ({ + // departmentName: orgChild4.orgChild4Name, + // deptID: orgChild4.id, + // type: 5, + // // heads: + // totalPositionCount: await this.posMasterRepository.count({ + // where: { + // orgRevisionId: data.id, + // orgRootId: orgRoot.id, + // orgChild1Id: orgChild1.id, + // orgChild2Id: orgChild2.id, + // orgChild3Id: orgChild3.id, + // orgChild4Id: orgChild4.id, + // }, + // }), + // totalPositionVacant: + // data.orgRevisionIsDraft == true + // ? await this.posMasterRepository.count({ + // where: { + // orgRevisionId: data.id, + // orgRootId: orgRoot.id, + // orgChild1Id: orgChild1.id, + // orgChild2Id: orgChild2.id, + // orgChild3Id: orgChild3.id, + // orgChild4Id: orgChild4.id, + // next_holderId: IsNull() || "", + // }, + // }) + // : await this.posMasterRepository.count({ + // where: { + // orgRevisionId: data.id, + // orgRootId: orgRoot.id, + // orgChild1Id: orgChild1.id, + // orgChild2Id: orgChild2.id, + // orgChild3Id: orgChild3.id, + // orgChild4Id: orgChild4.id, + // current_holderId: IsNull() || "", + // }, + // }), + // // totalPositionCurrentVacant: + // // await this.posMasterRepository.count({ + // // where: { + // // orgRevisionId: data.id, + // // orgRootId: orgRoot.id, + // // orgChild1Id: orgChild1.id, + // // orgChild2Id: orgChild2.id, + // // orgChild3Id: orgChild3.id, + // // orgChild4Id: orgChild4.id, + // // current_holderId: IsNull() || "", + // // }, + // // }), + // // totalPositionNextVacant: + // // await this.posMasterRepository.count({ + // // where: { + // // orgRevisionId: data.id, + // // orgRootId: orgRoot.id, + // // orgChild1Id: orgChild1.id, + // // orgChild2Id: orgChild2.id, + // // orgChild3Id: orgChild3.id, + // // orgChild4Id: orgChild4.id, + // // next_holderId: IsNull() || "", + // // }, + // // }), + // })), + // ), + // })), + // ), + // })), + // ), + // })), + // ), + // }; + // }), + // ), + // }; + // return new HttpSuccess([formattedData]); + // } + // case 1: { + // const data = await this.orgRootRepository.findOne({ + // where: { id: idNode }, + // relations: [ + // "orgRevision", + // "orgChild1s", + // "orgChild1s.orgChild2s", + // "orgChild1s.orgChild2s.orgChild3s", + // "orgChild1s.orgChild2s.orgChild3s.orgChild4s", + // ], + // }); + // if (!data) { + // throw new HttpError(HttpStatusCode.NOT_FOUND, "not found rootId"); + // } + + // const formattedData = { + // departmentName: data.orgRootName, + // deptID: data.id, + // type: 1, + // // heads: + // totalPositionCount: await this.posMasterRepository.count({ + // where: { orgRootId: data.id }, + // }), + // totalPositionVacant: + // data.orgRevision.orgRevisionIsDraft == true + // ? await this.posMasterRepository.count({ + // where: { + // orgRootId: data.id, + // next_holderId: IsNull() || "", + // }, + // }) + // : await this.posMasterRepository.count({ + // where: { + // orgRootId: data.id, + // current_holderId: IsNull() || "", + // }, + // }), + // // totalPositionCurrentVacant: await this.posMasterRepository.count({ + // // where: { + // // orgRootId: data.id, + // // current_holderId: IsNull() || "", + // // }, + // // }), + // // totalPositionNextVacant: await this.posMasterRepository.count({ + // // where: { + // // orgRootId: data.id, + // // next_holderId: IsNull() || "", + // // }, + // // }), + + // children: await Promise.all( + // data.orgChild1s + // .sort((a, b) => a.orgChild1Order - b.orgChild1Order) + // .map(async (orgChild1) => ({ + // departmentName: orgChild1.orgChild1Name, + // deptID: orgChild1.id, + // type: 2, + // // heads: + // totalPositionCount: await this.posMasterRepository.count({ + // where: { orgRootId: data.id, orgChild1Id: orgChild1.id }, + // }), + // totalPositionVacant: + // data.orgRevision.orgRevisionIsDraft == true + // ? await this.posMasterRepository.count({ + // where: { + // orgRootId: data.id, + // orgChild1Id: orgChild1.id, + // next_holderId: IsNull() || "", + // }, + // }) + // : await this.posMasterRepository.count({ + // where: { + // orgRootId: data.id, + // orgChild1Id: orgChild1.id, + // current_holderId: IsNull() || "", + // }, + // }), + // // totalPositionCurrentVacant: await this.posMasterRepository.count({ + // // where: { + // // orgRootId: data.id, + // // orgChild1Id: orgChild1.id, + // // current_holderId: IsNull() || "", + // // }, + // // }), + // // totalPositionNextVacant: await this.posMasterRepository.count({ + // // where: { + // // orgRootId: data.id, + // // orgChild1Id: orgChild1.id, + // // next_holderId: IsNull() || "", + // // }, + // // }), + // children: await Promise.all( + // orgChild1.orgChild2s + // .sort((a, b) => a.orgChild2Order - b.orgChild2Order) + // .map(async (orgChild2) => ({ + // departmentName: orgChild2.orgChild2Name, + // deptID: orgChild2.id, + // type: 3, + // // heads: + // totalPositionCount: await this.posMasterRepository.count({ + // where: { + // orgRootId: data.id, + // orgChild1Id: orgChild1.id, + // orgChild2Id: orgChild2.id, + // }, + // }), + // totalPositionCurrentVacant: + // data.orgRevision.orgRevisionIsDraft == true + // ? await this.posMasterRepository.count({ + // where: { + // orgRootId: data.id, + // orgChild1Id: orgChild1.id, + // orgChild2Id: orgChild2.id, + // next_holderId: IsNull() || "", + // }, + // }) + // : await this.posMasterRepository.count({ + // where: { + // orgRootId: data.id, + // orgChild1Id: orgChild1.id, + // orgChild2Id: orgChild2.id, + // current_holderId: IsNull() || "", + // }, + // }), + // // totalPositionCurrentVacant: await this.posMasterRepository.count({ + // // where: { + // // orgRootId: data.id, + // // orgChild1Id: orgChild1.id, + // // orgChild2Id: orgChild2.id, + // // current_holderId: IsNull() || "", + // // }, + // // }), + // // totalPositionNextVacant: await this.posMasterRepository.count({ + // // where: { + // // orgRootId: data.id, + // // orgChild1Id: orgChild1.id, + // // orgChild2Id: orgChild2.id, + // // next_holderId: IsNull() || "", + // // }, + // // }), + // children: await Promise.all( + // orgChild2.orgChild3s + // .sort((a, b) => a.orgChild3Order - b.orgChild3Order) + // .map(async (orgChild3) => ({ + // departmentName: orgChild3.orgChild3Name, + // deptID: orgChild3.id, + // type: 4, + // // heads: + // totalPositionCount: await this.posMasterRepository.count({ + // where: { + // orgRootId: data.id, + // orgChild1Id: orgChild1.id, + // orgChild2Id: orgChild2.id, + // orgChild3Id: orgChild3.id, + // }, + // }), + // totalPositionVacant: + // data.orgRevision.orgRevisionIsDraft == true + // ? await this.posMasterRepository.count({ + // where: { + // orgRootId: data.id, + // orgChild1Id: orgChild1.id, + // orgChild2Id: orgChild2.id, + // orgChild3Id: orgChild3.id, + // next_holderId: IsNull() || "", + // }, + // }) + // : await this.posMasterRepository.count({ + // where: { + // orgRootId: data.id, + // orgChild1Id: orgChild1.id, + // orgChild2Id: orgChild2.id, + // orgChild3Id: orgChild3.id, + // current_holderId: IsNull() || "", + // }, + // }), + // // totalPositionCurrentVacant: await this.posMasterRepository.count({ + // // where: { + // // orgRootId: data.id, + // // orgChild1Id: orgChild1.id, + // // orgChild2Id: orgChild2.id, + // // orgChild3Id: orgChild3.id, + // // current_holderId: IsNull() || "", + // // }, + // // }), + // // totalPositionNextVacant: await this.posMasterRepository.count({ + // // where: { + // // orgRootId: data.id, + // // orgChild1Id: orgChild1.id, + // // orgChild2Id: orgChild2.id, + // // orgChild3Id: orgChild3.id, + // // next_holderId: IsNull() || "", + // // }, + // // }), + // children: await Promise.all( + // orgChild3.orgChild4s + // .sort((a, b) => a.orgChild4Order - b.orgChild4Order) + // .map(async (orgChild4) => ({ + // departmentName: orgChild4.orgChild4Name, + // deptID: orgChild4.id, + // type: 5, + // // heads: + // totalPositionCount: await this.posMasterRepository.count({ + // where: { + // orgRootId: data.id, + // orgChild1Id: orgChild1.id, + // orgChild2Id: orgChild2.id, + // orgChild3Id: orgChild3.id, + // orgChild4Id: orgChild4.id, + // }, + // }), + // totalPositionVacant: + // data.orgRevision.orgRevisionIsDraft == true + // ? await this.posMasterRepository.count({ + // where: { + // orgRootId: data.id, + // orgChild1Id: orgChild1.id, + // orgChild2Id: orgChild2.id, + // orgChild3Id: orgChild3.id, + // orgChild4Id: orgChild4.id, + // next_holderId: IsNull() || "", + // }, + // }) + // : await this.posMasterRepository.count({ + // where: { + // orgRootId: data.id, + // orgChild1Id: orgChild1.id, + // orgChild2Id: orgChild2.id, + // orgChild3Id: orgChild3.id, + // orgChild4Id: orgChild4.id, + // current_holderId: IsNull() || "", + // }, + // }), + // // totalPositionCurrentVacant: + // // await this.posMasterRepository.count({ + // // where: { + // // orgRootId: data.id, + // // orgChild1Id: orgChild1.id, + // // orgChild2Id: orgChild2.id, + // // orgChild3Id: orgChild3.id, + // // orgChild4Id: orgChild4.id, + // // current_holderId: IsNull() || "", + // // }, + // // }), + // // totalPositionNextVacant: await this.posMasterRepository.count({ + // // where: { + // // orgRootId: data.id, + // // orgChild1Id: orgChild1.id, + // // orgChild2Id: orgChild2.id, + // // orgChild3Id: orgChild3.id, + // // orgChild4Id: orgChild4.id, + // // next_holderId: IsNull() || "", + // // }, + // // }), + // })), + // ), + // })), + // ), + // })), + // ), + // })), + // ), + // }; + // return new HttpSuccess([formattedData]); + // } + // case 2: { + // const data = await this.child1Repository.findOne({ + // where: { id: idNode }, + // relations: [ + // "orgRevision", + // "orgChild2s", + // "orgChild2s.orgChild3s", + // "orgChild2s.orgChild3s.orgChild4s", + // ], + // }); + // if (!data) { + // throw new HttpError(HttpStatusCode.NOT_FOUND, "not found child1Id"); + // } + + // const formattedData = { + // departmentName: data.orgChild1Name, + // deptID: data.id, + // type: 2, + // // heads: + // totalPositionCount: await this.posMasterRepository.count({ + // where: { orgChild1Id: data.id }, + // }), + // totalPositionVacant: + // data.orgRevision.orgRevisionIsDraft == true + // ? await this.posMasterRepository.count({ + // where: { + // orgChild1Id: data.id, + // next_holderId: IsNull() || "", + // }, + // }) + // : await this.posMasterRepository.count({ + // where: { + // orgChild1Id: data.id, + // current_holderId: IsNull() || "", + // }, + // }), + // // totalPositionCurrentVacant: await this.posMasterRepository.count({ + // // where: { + // // orgChild1Id: data.id, + // // current_holderId: IsNull() || "", + // // }, + // // }), + // // totalPositionNextVacant: await this.posMasterRepository.count({ + // // where: { + // // orgChild1Id: data.id, + // // next_holderId: IsNull() || "", + // // }, + // // }), + + // children: await Promise.all( + // data.orgChild2s + // .sort((a, b) => a.orgChild2Order - b.orgChild2Order) + // .map(async (orgChild2) => ({ + // departmentName: orgChild2.orgChild2Name, + // deptID: orgChild2.id, + // type: 3, + // // heads: + // totalPositionCount: await this.posMasterRepository.count({ + // where: { orgChild1Id: data.id, orgChild2Id: orgChild2.id }, + // }), + // totalPositionVacant: + // data.orgRevision.orgRevisionIsDraft == true + // ? await this.posMasterRepository.count({ + // where: { + // orgChild1Id: data.id, + // orgChild2Id: orgChild2.id, + // next_holderId: IsNull() || "", + // }, + // }) + // : await this.posMasterRepository.count({ + // where: { + // orgChild1Id: data.id, + // orgChild2Id: orgChild2.id, + // current_holderId: IsNull() || "", + // }, + // }), + // // totalPositionCurrentVacant: await this.posMasterRepository.count({ + // // where: { + // // orgChild1Id: data.id, + // // orgChild2Id: orgChild2.id, + // // current_holderId: IsNull() || "", + // // }, + // // }), + // // totalPositionNextVacant: await this.posMasterRepository.count({ + // // where: { + // // orgChild1Id: data.id, + // // orgChild2Id: orgChild2.id, + // // next_holderId: IsNull() || "", + // // }, + // // }), + // children: await Promise.all( + // orgChild2.orgChild3s + // .sort((a, b) => a.orgChild3Order - b.orgChild3Order) + // .map(async (orgChild3) => ({ + // departmentName: orgChild3.orgChild3Name, + // deptID: orgChild3.id, + // type: 4, + // // heads: + // totalPositionCount: await this.posMasterRepository.count({ + // where: { + // orgRevisionId: data.id, + // orgChild2Id: orgChild2.id, + // orgChild3Id: orgChild3.id, + // }, + // }), + // totalPositionVacant: + // data.orgRevision.orgRevisionIsDraft == true + // ? await this.posMasterRepository.count({ + // where: { + // orgRevisionId: data.id, + // orgChild2Id: orgChild2.id, + // orgChild3Id: orgChild3.id, + // next_holderId: IsNull() || "", + // }, + // }) + // : await this.posMasterRepository.count({ + // where: { + // orgRevisionId: data.id, + // orgChild2Id: orgChild2.id, + // orgChild3Id: orgChild3.id, + // current_holderId: IsNull() || "", + // }, + // }), + // // totalPositionCurrentVacant: await this.posMasterRepository.count({ + // // where: { + // // orgRevisionId: data.id, + // // orgChild2Id: orgChild2.id, + // // orgChild3Id: orgChild3.id, + // // current_holderId: IsNull() || "", + // // }, + // // }), + // // totalPositionNextVacant: await this.posMasterRepository.count({ + // // where: { + // // orgRevisionId: data.id, + // // orgChild2Id: orgChild2.id, + // // orgChild3Id: orgChild3.id, + // // next_holderId: IsNull() || "", + // // }, + // // }), + // children: await Promise.all( + // orgChild3.orgChild4s + // .sort((a, b) => a.orgChild4Order - b.orgChild4Order) + // .map(async (orgChild4) => ({ + // departmentName: orgChild4.orgChild4Name, + // deptID: orgChild4.id, + // type: 5, + // // heads: + // totalPositionCount: await this.posMasterRepository.count({ + // where: { + // orgRevisionId: data.id, + // orgChild2Id: orgChild2.id, + // orgChild3Id: orgChild3.id, + // orgChild4Id: orgChild4.id, + // }, + // }), + // totalPositionVacant: + // data.orgRevision.orgRevisionIsDraft == true + // ? await this.posMasterRepository.count({ + // where: { + // orgRevisionId: data.id, + // orgChild2Id: orgChild2.id, + // orgChild3Id: orgChild3.id, + // orgChild4Id: orgChild4.id, + // next_holderId: IsNull() || "", + // }, + // }) + // : await this.posMasterRepository.count({ + // where: { + // orgRevisionId: data.id, + // orgChild2Id: orgChild2.id, + // orgChild3Id: orgChild3.id, + // orgChild4Id: orgChild4.id, + // current_holderId: IsNull() || "", + // }, + // }), + // // totalPositionCurrentVacant: await this.posMasterRepository.count({ + // // where: { + // // orgRevisionId: data.id, + // // orgChild2Id: orgChild2.id, + // // orgChild3Id: orgChild3.id, + // // orgChild4Id: orgChild4.id, + // // current_holderId: IsNull() || "", + // // }, + // // }), + // // totalPositionNextVacant: await this.posMasterRepository.count({ + // // where: { + // // orgRevisionId: data.id, + // // orgChild2Id: orgChild2.id, + // // orgChild3Id: orgChild3.id, + // // orgChild4Id: orgChild4.id, + // // next_holderId: IsNull() || "", + // // }, + // // }), + // })), + // ), + // })), + // ), + // })), + // ), + // }; + // return new HttpSuccess([formattedData]); + // } + // case 3: { + // const data = await this.child2Repository.findOne({ + // where: { id: idNode }, + // relations: ["orgRevision", "orgChild3s", "orgChild3s.orgChild4s"], + // }); + // if (!data) { + // throw new HttpError(HttpStatusCode.NOT_FOUND, "not found child2Id"); + // } + + // const formattedData = { + // departmentName: data.orgChild2Name, + // deptID: data.id, + // type: 3, + // // heads: + // totalPositionCount: await this.posMasterRepository.count({ + // where: { orgChild2Id: data.id }, + // }), + // totalPositionVacant: + // data.orgRevision.orgRevisionIsDraft == true + // ? await this.posMasterRepository.count({ + // where: { + // orgChild2Id: data.id, + // next_holderId: IsNull() || "", + // }, + // }) + // : await this.posMasterRepository.count({ + // where: { + // orgChild2Id: data.id, + // current_holderId: IsNull() || "", + // }, + // }), + // // totalPositionCurrentVacant: await this.posMasterRepository.count({ + // // where: { + // // orgChild2Id: data.id, + // // current_holderId: IsNull() || "", + // // }, + // // }), + // // totalPositionNextVacant: await this.posMasterRepository.count({ + // // where: { + // // orgChild2Id: data.id, + // // next_holderId: IsNull() || "", + // // }, + // // }), + + // children: await Promise.all( + // data.orgChild3s + // .sort((a, b) => a.orgChild3Order - b.orgChild3Order) + // .map(async (orgChild3) => ({ + // departmentName: orgChild3.orgChild3Name, + // deptID: orgChild3.id, + // type: 4, + // // heads: + // totalPositionCount: await this.posMasterRepository.count({ + // where: { orgChild2Id: data.id, orgChild3Id: orgChild3.id }, + // }), + // totalPositionVacant: + // data.orgRevision.orgRevisionIsDraft == true + // ? await this.posMasterRepository.count({ + // where: { + // orgChild2Id: data.id, + // orgChild3Id: orgChild3.id, + // next_holderId: IsNull() || "", + // }, + // }) + // : await this.posMasterRepository.count({ + // where: { + // orgChild2Id: data.id, + // orgChild3Id: orgChild3.id, + // current_holderId: IsNull() || "", + // }, + // }), + // // totalPositionCurrentVacant: await this.posMasterRepository.count({ + // // where: { + // // orgChild2Id: data.id, + // // orgChild3Id: orgChild3.id, + // // current_holderId: IsNull() || "", + // // }, + // // }), + // // totalPositionNextVacant: await this.posMasterRepository.count({ + // // where: { + // // orgChild2Id: data.id, + // // orgChild3Id: orgChild3.id, + // // next_holderId: IsNull() || "", + // // }, + // // }), + // children: await Promise.all( + // orgChild3.orgChild4s + // .sort((a, b) => a.orgChild4Order - b.orgChild4Order) + // .map(async (orgChild4) => ({ + // departmentName: orgChild4.orgChild4Name, + // deptID: orgChild4.id, + // type: 5, + // // heads: + // totalPositionCount: await this.posMasterRepository.count({ + // where: { + // orgChild2Id: data.id, + // orgChild3Id: orgChild3.id, + // orgChild4Id: orgChild4.id, + // }, + // }), + // totalPositionVacant: + // data.orgRevision.orgRevisionIsDraft == true + // ? await this.posMasterRepository.count({ + // where: { + // orgChild2Id: data.id, + // orgChild3Id: orgChild3.id, + // orgChild4Id: orgChild4.id, + // next_holderId: IsNull() || "", + // }, + // }) + // : await this.posMasterRepository.count({ + // where: { + // orgChild2Id: data.id, + // orgChild3Id: orgChild3.id, + // orgChild4Id: orgChild4.id, + // current_holderId: IsNull() || "", + // }, + // }), + // // totalPositionCurrentVacant: await this.posMasterRepository.count({ + // // where: { + // // orgChild2Id: data.id, + // // orgChild3Id: orgChild3.id, + // // orgChild4Id: orgChild4.id, + // // current_holderId: IsNull() || "", + // // }, + // // }), + // // totalPositionNextVacant: await this.posMasterRepository.count({ + // // where: { + // // orgChild2Id: data.id, + // // orgChild3Id: orgChild3.id, + // // orgChild4Id: orgChild4.id, + // // next_holderId: IsNull() || "", + // // }, + // // }), + // })), + // ), + // })), + // ), + // }; + // return new HttpSuccess([formattedData]); + // } + // case 4: { + // const data = await this.child3Repository.findOne({ + // where: { id: idNode }, + // relations: ["orgRevision", "orgChild4s"], + // }); + // if (!data) { + // throw new HttpError(HttpStatusCode.NOT_FOUND, "not found child3Id"); + // } + + // const formattedData = { + // departmentName: data.orgChild3Name, + // deptID: data.id, + // type: 4, + // // heads: + // totalPositionCount: await this.posMasterRepository.count({ + // where: { orgChild3Id: data.id }, + // }), + // totalPositionVacant: + // data.orgRevision.orgRevisionIsDraft == true + // ? await this.posMasterRepository.count({ + // where: { + // orgChild3Id: data.id, + // next_holderId: IsNull() || "", + // }, + // }) + // : await this.posMasterRepository.count({ + // where: { + // orgChild3Id: data.id, + // current_holderId: IsNull() || "", + // }, + // }), + // // totalPositionCurrentVacant: await this.posMasterRepository.count({ + // // where: { + // // orgChild3Id: data.id, + // // current_holderId: IsNull() || "", + // // }, + // // }), + // // totalPositionNextVacant: await this.posMasterRepository.count({ + // // where: { + // // orgChild3Id: data.id, + // // next_holderId: IsNull() || "", + // // }, + // // }), + + // children: await Promise.all( + // data.orgChild4s + // .sort((a, b) => a.orgChild4Order - b.orgChild4Order) + // .map(async (orgChild4) => ({ + // departmentName: orgChild4.orgChild4Name, + // deptID: orgChild4.id, + // type: 5, + // // heads: + // totalPositionCount: await this.posMasterRepository.count({ + // where: { orgChild3Id: data.id, orgChild4Id: orgChild4.id }, + // }), + // totalPositionVacant: + // data.orgRevision.orgRevisionIsDraft == true + // ? await this.posMasterRepository.count({ + // where: { + // orgChild3Id: data.id, + // orgChild4Id: orgChild4.id, + // next_holderId: IsNull() || "", + // }, + // }) + // : await this.posMasterRepository.count({ + // where: { + // orgChild3Id: data.id, + // orgChild4Id: orgChild4.id, + // current_holderId: IsNull() || "", + // }, + // }), + // // totalPositionCurrentVacant: await this.posMasterRepository.count({ + // // where: { + // // orgChild3Id: data.id, + // // orgChild4Id: orgChild4.id, + // // current_holderId: IsNull() || "", + // // }, + // // }), + // // totalPositionNextVacant: await this.posMasterRepository.count({ + // // where: { + // // orgChild3Id: data.id, + // // orgChild4Id: orgChild4.id, + // // next_holderId: IsNull() || "", + // // }, + // // }), + // })), + // ), + // }; + // return new HttpSuccess([formattedData]); + // } + // case 5: { + // const data = await this.child4Repository.findOne({ + // where: { id: idNode }, + // relations: ["orgRevision"], + // }); + // if (!data) { + // throw new HttpError(HttpStatusCode.NOT_FOUND, "not found child4Id"); + // } + + // const formattedData = { + // departmentName: data.orgChild4Name, + // deptID: data.id, + // type: 5, + // // heads: + // totalPositionCount: await this.posMasterRepository.count({ + // where: { orgChild4Id: data.id }, + // }), + // totalPositionVacant: + // data.orgRevision.orgRevisionIsDraft == true + // ? await this.posMasterRepository.count({ + // where: { + // orgChild4Id: data.id, + // next_holderId: IsNull() || "", + // }, + // }) + // : await this.posMasterRepository.count({ + // where: { + // orgChild4Id: data.id, + // current_holderId: IsNull() || "", + // }, + // }), + // // totalPositionCurrentVacant: await this.posMasterRepository.count({ + // // where: { + // // orgChild4Id: data.id, + // // current_holderId: IsNull() || "", + // // }, + // // }), + // // totalPositionNextVacant: await this.posMasterRepository.count({ + // // where: { + // // orgChild4Id: data.id, + // // next_holderId: IsNull() || "", + // // }, + // // }), + // }; + // return new HttpSuccess([formattedData]); + // } + // default: + // throw new HttpError(HttpStatusCode.NOT_FOUND, "not found type: "); + // } + // } + /** - * API Organizational StructChart + * API Organizational StructChart V2 (Optimized) * - * @summary Organizational StructChart + * @summary Organizational StructChart - Optimized with batch queries to prevent N+1 problem * */ @Get("struct-chart/{idNode}/{type}") - async structchart(@Path() idNode: string, type: number) { + async structchartV2(@Path() idNode: string, type: number) { + // Fetch orgRevisionId and isDraft status first + let orgRevisionId: string | null = null; + let isDraft = false; + + switch (type) { + case 0: + const revision = await this.orgRevisionRepository.findOne({ where: { id: idNode } }); + orgRevisionId = revision?.id || null; + isDraft = revision?.orgRevisionIsDraft || false; + break; + case 1: { + const root = await this.orgRootRepository.findOne({ + where: { id: idNode }, + relations: ["orgRevision"], + }); + orgRevisionId = root?.orgRevision?.id || null; + isDraft = root?.orgRevision?.orgRevisionIsDraft || false; + break; + } + case 2: { + const child1 = await this.child1Repository.findOne({ + where: { id: idNode }, + relations: ["orgRevision"], + }); + orgRevisionId = child1?.orgRevision?.id || null; + isDraft = child1?.orgRevision?.orgRevisionIsDraft || false; + break; + } + case 3: { + const child2 = await this.child2Repository.findOne({ + where: { id: idNode }, + relations: ["orgRevision"], + }); + orgRevisionId = child2?.orgRevision?.id || null; + isDraft = child2?.orgRevision?.orgRevisionIsDraft || false; + break; + } + case 4: { + const child3 = await this.child3Repository.findOne({ + where: { id: idNode }, + relations: ["orgRevision"], + }); + orgRevisionId = child3?.orgRevision?.id || null; + isDraft = child3?.orgRevision?.orgRevisionIsDraft || false; + break; + } + case 5: { + const child4 = await this.child4Repository.findOne({ + where: { id: idNode }, + relations: ["orgRevision"], + }); + orgRevisionId = child4?.orgRevision?.id || null; + isDraft = child4?.orgRevision?.orgRevisionIsDraft || false; + break; + } + default: + throw new HttpError(HttpStatusCode.NOT_FOUND, "not found type: "); + } + + if (!orgRevisionId) { + throw new HttpError(HttpStatusCode.NOT_FOUND, "not found revision"); + } + + // Fetch all position counts in a single batch query (optimized) + const positionCounts = await getPositionCountsAggregated(orgRevisionId); + switch (type) { case 0: { const data = await this.orgRevisionRepository.findOne({ @@ -3855,320 +4660,9 @@ export class OrganizationController extends Controller { departmentName: data.orgRevisionName, deptID: data.id, type: 0, - // heads: - totalPositionCount: await this.posMasterRepository.count({ - where: { orgRevisionId: data.id }, - }), - totalPositionVacant: - data.orgRevisionIsDraft == true - ? await this.posMasterRepository.count({ - where: { - orgRevisionId: data.id, - next_holderId: IsNull() || "", - }, - }) - : await this.posMasterRepository.count({ - where: { - orgRevisionId: data.id, - current_holderId: IsNull() || "", - }, - }), - // totalPositionCurrentVacant: await this.posMasterRepository.count({ - // where: { - // orgRevisionId: data.id, - // current_holderId: IsNull() || "", - // }, - // }), - // totalPositionNextVacant: await this.posMasterRepository.count({ - // where: { - // orgRevisionId: data.id, - // next_holderId: IsNull() || "", - // }, - // }), - children: await Promise.all( - data.orgRoots - .sort((a, b) => a.orgRootOrder - b.orgRootOrder) - .map(async (orgRoot: OrgRoot) => { - return { - departmentName: orgRoot.orgRootName, - deptID: orgRoot.id, - type: 1, - // heads: - totalPositionCount: await this.posMasterRepository.count({ - where: { orgRevisionId: data.id, orgRootId: orgRoot.id }, - }), - totalPositionVacant: - data.orgRevisionIsDraft == true - ? await this.posMasterRepository.count({ - where: { - orgRevisionId: data.id, - orgRootId: orgRoot.id, - next_holderId: IsNull() || "", - }, - }) - : await this.posMasterRepository.count({ - where: { - orgRevisionId: data.id, - orgRootId: orgRoot.id, - current_holderId: IsNull() || "", - }, - }), - // totalPositionCurrentVacant: await this.posMasterRepository.count({ - // where: { - // orgRevisionId: data.id, - // orgRootId: orgRoot.id, - // current_holderId: IsNull() || "", - // }, - // }), - // totalPositionNextVacant: await this.posMasterRepository.count({ - // where: { - // orgRevisionId: data.id, - // orgRootId: orgRoot.id, - // next_holderId: IsNull() || "", - // }, - // }), - children: await Promise.all( - orgRoot.orgChild1s - .sort((a, b) => a.orgChild1Order - b.orgChild1Order) - .map(async (orgChild1) => ({ - departmentName: orgChild1.orgChild1Name, - deptID: orgChild1.id, - type: 2, - // heads: - totalPositionCount: await this.posMasterRepository.count({ - where: { - orgRevisionId: data.id, - orgRootId: orgRoot.id, - orgChild1Id: orgChild1.id, - }, - }), - totalPositionVacant: - data.orgRevisionIsDraft == true - ? await this.posMasterRepository.count({ - where: { - orgRevisionId: data.id, - orgRootId: orgRoot.id, - orgChild1Id: orgChild1.id, - next_holderId: IsNull() || "", - }, - }) - : await this.posMasterRepository.count({ - where: { - orgRevisionId: data.id, - orgRootId: orgRoot.id, - orgChild1Id: orgChild1.id, - current_holderId: IsNull() || "", - }, - }), - // totalPositionCurrentVacant: await this.posMasterRepository.count({ - // where: { - // orgRevisionId: data.id, - // orgRootId: orgRoot.id, - // orgChild1Id: orgChild1.id, - // current_holderId: IsNull() || "", - // }, - // }), - // totalPositionNextVacant: await this.posMasterRepository.count({ - // where: { - // orgRevisionId: data.id, - // orgRootId: orgRoot.id, - // orgChild1Id: orgChild1.id, - // next_holderId: IsNull() || "", - // }, - // }), - children: await Promise.all( - orgChild1.orgChild2s - .sort((a, b) => a.orgChild2Order - b.orgChild2Order) - .map(async (orgChild2) => ({ - departmentName: orgChild2.orgChild2Name, - deptID: orgChild2.id, - type: 3, - // heads: - totalPositionCount: await this.posMasterRepository.count({ - where: { - orgRevisionId: data.id, - orgRootId: orgRoot.id, - orgChild1Id: orgChild1.id, - orgChild2Id: orgChild2.id, - }, - }), - totalPositionVacant: - data.orgRevisionIsDraft == true - ? await this.posMasterRepository.count({ - where: { - orgRevisionId: data.id, - orgRootId: orgRoot.id, - orgChild1Id: orgChild1.id, - orgChild2Id: orgChild2.id, - next_holderId: IsNull() || "", - }, - }) - : await this.posMasterRepository.count({ - where: { - orgRevisionId: data.id, - orgRootId: orgRoot.id, - orgChild1Id: orgChild1.id, - orgChild2Id: orgChild2.id, - current_holderId: IsNull() || "", - }, - }), - // totalPositionCurrentVacant: await this.posMasterRepository.count({ - // where: { - // orgRevisionId: data.id, - // orgRootId: orgRoot.id, - // orgChild1Id: orgChild1.id, - // orgChild2Id: orgChild2.id, - // current_holderId: IsNull() || "", - // }, - // }), - // totalPositionNextVacant: await this.posMasterRepository.count({ - // where: { - // orgRevisionId: data.id, - // orgRootId: orgRoot.id, - // orgChild1Id: orgChild1.id, - // orgChild2Id: orgChild2.id, - // next_holderId: IsNull() || "", - // }, - // }), - children: await Promise.all( - orgChild2.orgChild3s - .sort((a, b) => a.orgChild3Order - b.orgChild3Order) - .map(async (orgChild3) => ({ - departmentName: orgChild3.orgChild3Name, - deptID: orgChild3.id, - type: 4, - // heads: - totalPositionCount: await this.posMasterRepository.count({ - where: { - orgRevisionId: data.id, - orgRootId: orgRoot.id, - orgChild1Id: orgChild1.id, - orgChild2Id: orgChild2.id, - orgChild3Id: orgChild3.id, - }, - }), - totalPositionVacant: - data.orgRevisionIsDraft == true - ? await this.posMasterRepository.count({ - where: { - orgRevisionId: data.id, - orgRootId: orgRoot.id, - orgChild1Id: orgChild1.id, - orgChild2Id: orgChild2.id, - orgChild3Id: orgChild3.id, - next_holderId: IsNull() || "", - }, - }) - : await this.posMasterRepository.count({ - where: { - orgRevisionId: data.id, - orgRootId: orgRoot.id, - orgChild1Id: orgChild1.id, - orgChild2Id: orgChild2.id, - orgChild3Id: orgChild3.id, - current_holderId: IsNull() || "", - }, - }), - // totalPositionCurrentVacant: - // await this.posMasterRepository.count({ - // where: { - // orgRevisionId: data.id, - // orgRootId: orgRoot.id, - // orgChild1Id: orgChild1.id, - // orgChild2Id: orgChild2.id, - // orgChild3Id: orgChild3.id, - // current_holderId: IsNull() || "", - // }, - // }), - // totalPositionNextVacant: await this.posMasterRepository.count( - // { - // where: { - // orgRevisionId: data.id, - // orgRootId: orgRoot.id, - // orgChild1Id: orgChild1.id, - // orgChild2Id: orgChild2.id, - // orgChild3Id: orgChild3.id, - // next_holderId: IsNull() || "", - // }, - // }, - // ), - children: await Promise.all( - orgChild3.orgChild4s - .sort((a, b) => a.orgChild4Order - b.orgChild4Order) - .map(async (orgChild4) => ({ - departmentName: orgChild4.orgChild4Name, - deptID: orgChild4.id, - type: 5, - // heads: - totalPositionCount: await this.posMasterRepository.count({ - where: { - orgRevisionId: data.id, - orgRootId: orgRoot.id, - orgChild1Id: orgChild1.id, - orgChild2Id: orgChild2.id, - orgChild3Id: orgChild3.id, - orgChild4Id: orgChild4.id, - }, - }), - totalPositionVacant: - data.orgRevisionIsDraft == true - ? await this.posMasterRepository.count({ - where: { - orgRevisionId: data.id, - orgRootId: orgRoot.id, - orgChild1Id: orgChild1.id, - orgChild2Id: orgChild2.id, - orgChild3Id: orgChild3.id, - orgChild4Id: orgChild4.id, - next_holderId: IsNull() || "", - }, - }) - : await this.posMasterRepository.count({ - where: { - orgRevisionId: data.id, - orgRootId: orgRoot.id, - orgChild1Id: orgChild1.id, - orgChild2Id: orgChild2.id, - orgChild3Id: orgChild3.id, - orgChild4Id: orgChild4.id, - current_holderId: IsNull() || "", - }, - }), - // totalPositionCurrentVacant: - // await this.posMasterRepository.count({ - // where: { - // orgRevisionId: data.id, - // orgRootId: orgRoot.id, - // orgChild1Id: orgChild1.id, - // orgChild2Id: orgChild2.id, - // orgChild3Id: orgChild3.id, - // orgChild4Id: orgChild4.id, - // current_holderId: IsNull() || "", - // }, - // }), - // totalPositionNextVacant: - // await this.posMasterRepository.count({ - // where: { - // orgRevisionId: data.id, - // orgRootId: orgRoot.id, - // orgChild1Id: orgChild1.id, - // orgChild2Id: orgChild2.id, - // orgChild3Id: orgChild3.id, - // orgChild4Id: orgChild4.id, - // next_holderId: IsNull() || "", - // }, - // }), - })), - ), - })), - ), - })), - ), - })), - ), - }; - }), - ), + totalPositionCount: this.sumAllCounts(positionCounts.orgRootMap), + totalPositionVacant: this.sumAllVacantCounts(positionCounts.orgRootMap, isDraft), + children: this.buildOrgRoots(data.orgRoots, positionCounts, isDraft), }; return new HttpSuccess([formattedData]); } @@ -4187,253 +4681,14 @@ export class OrganizationController extends Controller { throw new HttpError(HttpStatusCode.NOT_FOUND, "not found rootId"); } + const rootCounts = getPositionCount(positionCounts.orgRootMap, data.id); const formattedData = { departmentName: data.orgRootName, deptID: data.id, type: 1, - // heads: - totalPositionCount: await this.posMasterRepository.count({ - where: { orgRootId: data.id }, - }), - totalPositionVacant: - data.orgRevision.orgRevisionIsDraft == true - ? await this.posMasterRepository.count({ - where: { - orgRootId: data.id, - next_holderId: IsNull() || "", - }, - }) - : await this.posMasterRepository.count({ - where: { - orgRootId: data.id, - current_holderId: IsNull() || "", - }, - }), - // totalPositionCurrentVacant: await this.posMasterRepository.count({ - // where: { - // orgRootId: data.id, - // current_holderId: IsNull() || "", - // }, - // }), - // totalPositionNextVacant: await this.posMasterRepository.count({ - // where: { - // orgRootId: data.id, - // next_holderId: IsNull() || "", - // }, - // }), - - children: await Promise.all( - data.orgChild1s - .sort((a, b) => a.orgChild1Order - b.orgChild1Order) - .map(async (orgChild1) => ({ - departmentName: orgChild1.orgChild1Name, - deptID: orgChild1.id, - type: 2, - // heads: - totalPositionCount: await this.posMasterRepository.count({ - where: { orgRootId: data.id, orgChild1Id: orgChild1.id }, - }), - totalPositionVacant: - data.orgRevision.orgRevisionIsDraft == true - ? await this.posMasterRepository.count({ - where: { - orgRootId: data.id, - orgChild1Id: orgChild1.id, - next_holderId: IsNull() || "", - }, - }) - : await this.posMasterRepository.count({ - where: { - orgRootId: data.id, - orgChild1Id: orgChild1.id, - current_holderId: IsNull() || "", - }, - }), - // totalPositionCurrentVacant: await this.posMasterRepository.count({ - // where: { - // orgRootId: data.id, - // orgChild1Id: orgChild1.id, - // current_holderId: IsNull() || "", - // }, - // }), - // totalPositionNextVacant: await this.posMasterRepository.count({ - // where: { - // orgRootId: data.id, - // orgChild1Id: orgChild1.id, - // next_holderId: IsNull() || "", - // }, - // }), - children: await Promise.all( - orgChild1.orgChild2s - .sort((a, b) => a.orgChild2Order - b.orgChild2Order) - .map(async (orgChild2) => ({ - departmentName: orgChild2.orgChild2Name, - deptID: orgChild2.id, - type: 3, - // heads: - totalPositionCount: await this.posMasterRepository.count({ - where: { - orgRootId: data.id, - orgChild1Id: orgChild1.id, - orgChild2Id: orgChild2.id, - }, - }), - totalPositionCurrentVacant: - data.orgRevision.orgRevisionIsDraft == true - ? await this.posMasterRepository.count({ - where: { - orgRootId: data.id, - orgChild1Id: orgChild1.id, - orgChild2Id: orgChild2.id, - next_holderId: IsNull() || "", - }, - }) - : await this.posMasterRepository.count({ - where: { - orgRootId: data.id, - orgChild1Id: orgChild1.id, - orgChild2Id: orgChild2.id, - current_holderId: IsNull() || "", - }, - }), - // totalPositionCurrentVacant: await this.posMasterRepository.count({ - // where: { - // orgRootId: data.id, - // orgChild1Id: orgChild1.id, - // orgChild2Id: orgChild2.id, - // current_holderId: IsNull() || "", - // }, - // }), - // totalPositionNextVacant: await this.posMasterRepository.count({ - // where: { - // orgRootId: data.id, - // orgChild1Id: orgChild1.id, - // orgChild2Id: orgChild2.id, - // next_holderId: IsNull() || "", - // }, - // }), - children: await Promise.all( - orgChild2.orgChild3s - .sort((a, b) => a.orgChild3Order - b.orgChild3Order) - .map(async (orgChild3) => ({ - departmentName: orgChild3.orgChild3Name, - deptID: orgChild3.id, - type: 4, - // heads: - totalPositionCount: await this.posMasterRepository.count({ - where: { - orgRootId: data.id, - orgChild1Id: orgChild1.id, - orgChild2Id: orgChild2.id, - orgChild3Id: orgChild3.id, - }, - }), - totalPositionVacant: - data.orgRevision.orgRevisionIsDraft == true - ? await this.posMasterRepository.count({ - where: { - orgRootId: data.id, - orgChild1Id: orgChild1.id, - orgChild2Id: orgChild2.id, - orgChild3Id: orgChild3.id, - next_holderId: IsNull() || "", - }, - }) - : await this.posMasterRepository.count({ - where: { - orgRootId: data.id, - orgChild1Id: orgChild1.id, - orgChild2Id: orgChild2.id, - orgChild3Id: orgChild3.id, - current_holderId: IsNull() || "", - }, - }), - // totalPositionCurrentVacant: await this.posMasterRepository.count({ - // where: { - // orgRootId: data.id, - // orgChild1Id: orgChild1.id, - // orgChild2Id: orgChild2.id, - // orgChild3Id: orgChild3.id, - // current_holderId: IsNull() || "", - // }, - // }), - // totalPositionNextVacant: await this.posMasterRepository.count({ - // where: { - // orgRootId: data.id, - // orgChild1Id: orgChild1.id, - // orgChild2Id: orgChild2.id, - // orgChild3Id: orgChild3.id, - // next_holderId: IsNull() || "", - // }, - // }), - children: await Promise.all( - orgChild3.orgChild4s - .sort((a, b) => a.orgChild4Order - b.orgChild4Order) - .map(async (orgChild4) => ({ - departmentName: orgChild4.orgChild4Name, - deptID: orgChild4.id, - type: 5, - // heads: - totalPositionCount: await this.posMasterRepository.count({ - where: { - orgRootId: data.id, - orgChild1Id: orgChild1.id, - orgChild2Id: orgChild2.id, - orgChild3Id: orgChild3.id, - orgChild4Id: orgChild4.id, - }, - }), - totalPositionVacant: - data.orgRevision.orgRevisionIsDraft == true - ? await this.posMasterRepository.count({ - where: { - orgRootId: data.id, - orgChild1Id: orgChild1.id, - orgChild2Id: orgChild2.id, - orgChild3Id: orgChild3.id, - orgChild4Id: orgChild4.id, - next_holderId: IsNull() || "", - }, - }) - : await this.posMasterRepository.count({ - where: { - orgRootId: data.id, - orgChild1Id: orgChild1.id, - orgChild2Id: orgChild2.id, - orgChild3Id: orgChild3.id, - orgChild4Id: orgChild4.id, - current_holderId: IsNull() || "", - }, - }), - // totalPositionCurrentVacant: - // await this.posMasterRepository.count({ - // where: { - // orgRootId: data.id, - // orgChild1Id: orgChild1.id, - // orgChild2Id: orgChild2.id, - // orgChild3Id: orgChild3.id, - // orgChild4Id: orgChild4.id, - // current_holderId: IsNull() || "", - // }, - // }), - // totalPositionNextVacant: await this.posMasterRepository.count({ - // where: { - // orgRootId: data.id, - // orgChild1Id: orgChild1.id, - // orgChild2Id: orgChild2.id, - // orgChild3Id: orgChild3.id, - // orgChild4Id: orgChild4.id, - // next_holderId: IsNull() || "", - // }, - // }), - })), - ), - })), - ), - })), - ), - })), - ), + totalPositionCount: rootCounts.totalCount, + totalPositionVacant: isDraft ? rootCounts.nextVacantCount : rootCounts.currentVacantCount, + children: this.buildOrgChild1s(data.orgChild1s, positionCounts, isDraft), }; return new HttpSuccess([formattedData]); } @@ -4451,191 +4706,16 @@ export class OrganizationController extends Controller { throw new HttpError(HttpStatusCode.NOT_FOUND, "not found child1Id"); } + const child1Counts = getPositionCount(positionCounts.orgChild1Map, data.id); const formattedData = { departmentName: data.orgChild1Name, deptID: data.id, type: 2, - // heads: - totalPositionCount: await this.posMasterRepository.count({ - where: { orgChild1Id: data.id }, - }), - totalPositionVacant: - data.orgRevision.orgRevisionIsDraft == true - ? await this.posMasterRepository.count({ - where: { - orgChild1Id: data.id, - next_holderId: IsNull() || "", - }, - }) - : await this.posMasterRepository.count({ - where: { - orgChild1Id: data.id, - current_holderId: IsNull() || "", - }, - }), - // totalPositionCurrentVacant: await this.posMasterRepository.count({ - // where: { - // orgChild1Id: data.id, - // current_holderId: IsNull() || "", - // }, - // }), - // totalPositionNextVacant: await this.posMasterRepository.count({ - // where: { - // orgChild1Id: data.id, - // next_holderId: IsNull() || "", - // }, - // }), - - children: await Promise.all( - data.orgChild2s - .sort((a, b) => a.orgChild2Order - b.orgChild2Order) - .map(async (orgChild2) => ({ - departmentName: orgChild2.orgChild2Name, - deptID: orgChild2.id, - type: 3, - // heads: - totalPositionCount: await this.posMasterRepository.count({ - where: { orgChild1Id: data.id, orgChild2Id: orgChild2.id }, - }), - totalPositionVacant: - data.orgRevision.orgRevisionIsDraft == true - ? await this.posMasterRepository.count({ - where: { - orgChild1Id: data.id, - orgChild2Id: orgChild2.id, - next_holderId: IsNull() || "", - }, - }) - : await this.posMasterRepository.count({ - where: { - orgChild1Id: data.id, - orgChild2Id: orgChild2.id, - current_holderId: IsNull() || "", - }, - }), - // totalPositionCurrentVacant: await this.posMasterRepository.count({ - // where: { - // orgChild1Id: data.id, - // orgChild2Id: orgChild2.id, - // current_holderId: IsNull() || "", - // }, - // }), - // totalPositionNextVacant: await this.posMasterRepository.count({ - // where: { - // orgChild1Id: data.id, - // orgChild2Id: orgChild2.id, - // next_holderId: IsNull() || "", - // }, - // }), - children: await Promise.all( - orgChild2.orgChild3s - .sort((a, b) => a.orgChild3Order - b.orgChild3Order) - .map(async (orgChild3) => ({ - departmentName: orgChild3.orgChild3Name, - deptID: orgChild3.id, - type: 4, - // heads: - totalPositionCount: await this.posMasterRepository.count({ - where: { - orgRevisionId: data.id, - orgChild2Id: orgChild2.id, - orgChild3Id: orgChild3.id, - }, - }), - totalPositionVacant: - data.orgRevision.orgRevisionIsDraft == true - ? await this.posMasterRepository.count({ - where: { - orgRevisionId: data.id, - orgChild2Id: orgChild2.id, - orgChild3Id: orgChild3.id, - next_holderId: IsNull() || "", - }, - }) - : await this.posMasterRepository.count({ - where: { - orgRevisionId: data.id, - orgChild2Id: orgChild2.id, - orgChild3Id: orgChild3.id, - current_holderId: IsNull() || "", - }, - }), - // totalPositionCurrentVacant: await this.posMasterRepository.count({ - // where: { - // orgRevisionId: data.id, - // orgChild2Id: orgChild2.id, - // orgChild3Id: orgChild3.id, - // current_holderId: IsNull() || "", - // }, - // }), - // totalPositionNextVacant: await this.posMasterRepository.count({ - // where: { - // orgRevisionId: data.id, - // orgChild2Id: orgChild2.id, - // orgChild3Id: orgChild3.id, - // next_holderId: IsNull() || "", - // }, - // }), - children: await Promise.all( - orgChild3.orgChild4s - .sort((a, b) => a.orgChild4Order - b.orgChild4Order) - .map(async (orgChild4) => ({ - departmentName: orgChild4.orgChild4Name, - deptID: orgChild4.id, - type: 5, - // heads: - totalPositionCount: await this.posMasterRepository.count({ - where: { - orgRevisionId: data.id, - orgChild2Id: orgChild2.id, - orgChild3Id: orgChild3.id, - orgChild4Id: orgChild4.id, - }, - }), - totalPositionVacant: - data.orgRevision.orgRevisionIsDraft == true - ? await this.posMasterRepository.count({ - where: { - orgRevisionId: data.id, - orgChild2Id: orgChild2.id, - orgChild3Id: orgChild3.id, - orgChild4Id: orgChild4.id, - next_holderId: IsNull() || "", - }, - }) - : await this.posMasterRepository.count({ - where: { - orgRevisionId: data.id, - orgChild2Id: orgChild2.id, - orgChild3Id: orgChild3.id, - orgChild4Id: orgChild4.id, - current_holderId: IsNull() || "", - }, - }), - // totalPositionCurrentVacant: await this.posMasterRepository.count({ - // where: { - // orgRevisionId: data.id, - // orgChild2Id: orgChild2.id, - // orgChild3Id: orgChild3.id, - // orgChild4Id: orgChild4.id, - // current_holderId: IsNull() || "", - // }, - // }), - // totalPositionNextVacant: await this.posMasterRepository.count({ - // where: { - // orgRevisionId: data.id, - // orgChild2Id: orgChild2.id, - // orgChild3Id: orgChild3.id, - // orgChild4Id: orgChild4.id, - // next_holderId: IsNull() || "", - // }, - // }), - })), - ), - })), - ), - })), - ), + totalPositionCount: child1Counts.totalCount, + totalPositionVacant: isDraft + ? child1Counts.nextVacantCount + : child1Counts.currentVacantCount, + children: this.buildOrgChild2s(data.orgChild2s, positionCounts, isDraft), }; return new HttpSuccess([formattedData]); } @@ -4648,135 +4728,16 @@ export class OrganizationController extends Controller { throw new HttpError(HttpStatusCode.NOT_FOUND, "not found child2Id"); } + const child2Counts = getPositionCount(positionCounts.orgChild2Map, data.id); const formattedData = { departmentName: data.orgChild2Name, deptID: data.id, type: 3, - // heads: - totalPositionCount: await this.posMasterRepository.count({ - where: { orgChild2Id: data.id }, - }), - totalPositionVacant: - data.orgRevision.orgRevisionIsDraft == true - ? await this.posMasterRepository.count({ - where: { - orgChild2Id: data.id, - next_holderId: IsNull() || "", - }, - }) - : await this.posMasterRepository.count({ - where: { - orgChild2Id: data.id, - current_holderId: IsNull() || "", - }, - }), - // totalPositionCurrentVacant: await this.posMasterRepository.count({ - // where: { - // orgChild2Id: data.id, - // current_holderId: IsNull() || "", - // }, - // }), - // totalPositionNextVacant: await this.posMasterRepository.count({ - // where: { - // orgChild2Id: data.id, - // next_holderId: IsNull() || "", - // }, - // }), - - children: await Promise.all( - data.orgChild3s - .sort((a, b) => a.orgChild3Order - b.orgChild3Order) - .map(async (orgChild3) => ({ - departmentName: orgChild3.orgChild3Name, - deptID: orgChild3.id, - type: 4, - // heads: - totalPositionCount: await this.posMasterRepository.count({ - where: { orgChild2Id: data.id, orgChild3Id: orgChild3.id }, - }), - totalPositionVacant: - data.orgRevision.orgRevisionIsDraft == true - ? await this.posMasterRepository.count({ - where: { - orgChild2Id: data.id, - orgChild3Id: orgChild3.id, - next_holderId: IsNull() || "", - }, - }) - : await this.posMasterRepository.count({ - where: { - orgChild2Id: data.id, - orgChild3Id: orgChild3.id, - current_holderId: IsNull() || "", - }, - }), - // totalPositionCurrentVacant: await this.posMasterRepository.count({ - // where: { - // orgChild2Id: data.id, - // orgChild3Id: orgChild3.id, - // current_holderId: IsNull() || "", - // }, - // }), - // totalPositionNextVacant: await this.posMasterRepository.count({ - // where: { - // orgChild2Id: data.id, - // orgChild3Id: orgChild3.id, - // next_holderId: IsNull() || "", - // }, - // }), - children: await Promise.all( - orgChild3.orgChild4s - .sort((a, b) => a.orgChild4Order - b.orgChild4Order) - .map(async (orgChild4) => ({ - departmentName: orgChild4.orgChild4Name, - deptID: orgChild4.id, - type: 5, - // heads: - totalPositionCount: await this.posMasterRepository.count({ - where: { - orgChild2Id: data.id, - orgChild3Id: orgChild3.id, - orgChild4Id: orgChild4.id, - }, - }), - totalPositionVacant: - data.orgRevision.orgRevisionIsDraft == true - ? await this.posMasterRepository.count({ - where: { - orgChild2Id: data.id, - orgChild3Id: orgChild3.id, - orgChild4Id: orgChild4.id, - next_holderId: IsNull() || "", - }, - }) - : await this.posMasterRepository.count({ - where: { - orgChild2Id: data.id, - orgChild3Id: orgChild3.id, - orgChild4Id: orgChild4.id, - current_holderId: IsNull() || "", - }, - }), - // totalPositionCurrentVacant: await this.posMasterRepository.count({ - // where: { - // orgChild2Id: data.id, - // orgChild3Id: orgChild3.id, - // orgChild4Id: orgChild4.id, - // current_holderId: IsNull() || "", - // }, - // }), - // totalPositionNextVacant: await this.posMasterRepository.count({ - // where: { - // orgChild2Id: data.id, - // orgChild3Id: orgChild3.id, - // orgChild4Id: orgChild4.id, - // next_holderId: IsNull() || "", - // }, - // }), - })), - ), - })), - ), + totalPositionCount: child2Counts.totalCount, + totalPositionVacant: isDraft + ? child2Counts.nextVacantCount + : child2Counts.currentVacantCount, + children: this.buildOrgChild3s(data.orgChild3s, positionCounts, isDraft), }; return new HttpSuccess([formattedData]); } @@ -4789,84 +4750,16 @@ export class OrganizationController extends Controller { throw new HttpError(HttpStatusCode.NOT_FOUND, "not found child3Id"); } + const child3Counts = getPositionCount(positionCounts.orgChild3Map, data.id); const formattedData = { departmentName: data.orgChild3Name, deptID: data.id, type: 4, - // heads: - totalPositionCount: await this.posMasterRepository.count({ - where: { orgChild3Id: data.id }, - }), - totalPositionVacant: - data.orgRevision.orgRevisionIsDraft == true - ? await this.posMasterRepository.count({ - where: { - orgChild3Id: data.id, - next_holderId: IsNull() || "", - }, - }) - : await this.posMasterRepository.count({ - where: { - orgChild3Id: data.id, - current_holderId: IsNull() || "", - }, - }), - // totalPositionCurrentVacant: await this.posMasterRepository.count({ - // where: { - // orgChild3Id: data.id, - // current_holderId: IsNull() || "", - // }, - // }), - // totalPositionNextVacant: await this.posMasterRepository.count({ - // where: { - // orgChild3Id: data.id, - // next_holderId: IsNull() || "", - // }, - // }), - - children: await Promise.all( - data.orgChild4s - .sort((a, b) => a.orgChild4Order - b.orgChild4Order) - .map(async (orgChild4) => ({ - departmentName: orgChild4.orgChild4Name, - deptID: orgChild4.id, - type: 5, - // heads: - totalPositionCount: await this.posMasterRepository.count({ - where: { orgChild3Id: data.id, orgChild4Id: orgChild4.id }, - }), - totalPositionVacant: - data.orgRevision.orgRevisionIsDraft == true - ? await this.posMasterRepository.count({ - where: { - orgChild3Id: data.id, - orgChild4Id: orgChild4.id, - next_holderId: IsNull() || "", - }, - }) - : await this.posMasterRepository.count({ - where: { - orgChild3Id: data.id, - orgChild4Id: orgChild4.id, - current_holderId: IsNull() || "", - }, - }), - // totalPositionCurrentVacant: await this.posMasterRepository.count({ - // where: { - // orgChild3Id: data.id, - // orgChild4Id: orgChild4.id, - // current_holderId: IsNull() || "", - // }, - // }), - // totalPositionNextVacant: await this.posMasterRepository.count({ - // where: { - // orgChild3Id: data.id, - // orgChild4Id: orgChild4.id, - // next_holderId: IsNull() || "", - // }, - // }), - })), - ), + totalPositionCount: child3Counts.totalCount, + totalPositionVacant: isDraft + ? child3Counts.nextVacantCount + : child3Counts.currentVacantCount, + children: this.buildOrgChild4s(data.orgChild4s, positionCounts, isDraft), }; return new HttpSuccess([formattedData]); } @@ -4879,40 +4772,15 @@ export class OrganizationController extends Controller { throw new HttpError(HttpStatusCode.NOT_FOUND, "not found child4Id"); } + const child4Counts = getPositionCount(positionCounts.orgChild4Map, data.id); const formattedData = { departmentName: data.orgChild4Name, deptID: data.id, type: 5, - // heads: - totalPositionCount: await this.posMasterRepository.count({ - where: { orgChild4Id: data.id }, - }), - totalPositionVacant: - data.orgRevision.orgRevisionIsDraft == true - ? await this.posMasterRepository.count({ - where: { - orgChild4Id: data.id, - next_holderId: IsNull() || "", - }, - }) - : await this.posMasterRepository.count({ - where: { - orgChild4Id: data.id, - current_holderId: IsNull() || "", - }, - }), - // totalPositionCurrentVacant: await this.posMasterRepository.count({ - // where: { - // orgChild4Id: data.id, - // current_holderId: IsNull() || "", - // }, - // }), - // totalPositionNextVacant: await this.posMasterRepository.count({ - // where: { - // orgChild4Id: data.id, - // next_holderId: IsNull() || "", - // }, - // }), + totalPositionCount: child4Counts.totalCount, + totalPositionVacant: isDraft + ? child4Counts.nextVacantCount + : child4Counts.currentVacantCount, }; return new HttpSuccess([formattedData]); } @@ -5812,39 +5680,111 @@ export class OrganizationController extends Controller { */ @Get("act/{id}") async detailAct(@Path() id: string, @Request() request: RequestWithUser) { - // let _data = { - // root: null, - // child1: null, - // child2: null, - // child3: null, - // child4: null, - // }; - + let _data: OrgPermissionData = { + root: null, + child1: null, + child2: null, + child3: null, + child4: null, + }; // if (!request.user.role.includes("SUPER_ADMIN")) { // _data = await new permission().PermissionOrgList(request, "SYS_ACTING"); // } - await new permission().PermissionOrgList(request, "SYS_ACTING"); + const _privilege = await new permission().PermissionOrgList(request, "SYS_ACTING"); const orgRevision = await this.orgRevisionRepository.findOne({ where: { id } }); if (!orgRevision) { throw new HttpError(HttpStatusCode.NOT_FOUND, "ไม่พบข้อมูล"); } + const attrOwnership = _privilege.root === null ? true : false; + + const profile = await this.profileRepo.findOne({ + where: { keycloak: request.user.sub }, + relations: ["permissionProfiles", "current_holders", "current_holders.posMasterAssigns"], + }); + if (!profile) { + throw new HttpError(HttpStatusCode.NOT_FOUND, "ไม่พบข้อมูลผู้ใช้งานในทะเบียนประวัติ"); + } + let profileAssign = profile.current_holders + ?.find((x) => x.orgRevisionId === id) + ?.posMasterAssigns.find((x) => x.assignId === "SYS_ORG"); + + if (orgRevision.orgRevisionIsDraft && !orgRevision.orgRevisionIsCurrent && !attrOwnership) { + if (Array.isArray(profile.permissionProfiles) && profile.permissionProfiles.length > 0) { + _data.root = profile.permissionProfiles.map((x) => x.orgRootId); + } else { + return new HttpSuccess({ remark: "", data: [] }); + } + } + // กำหนดการเข้าถึงข้อมูลตามสถานะและสิทธิ์ + const isCurrentActive = !orgRevision.orgRevisionIsDraft && orgRevision.orgRevisionIsCurrent; + if (isCurrentActive) { + if (profileAssign && _privilege.privilege !== "OWNER" && _privilege.privilege !== "PARENT") { + if (_privilege.privilege == "NORMAL") { + const holder = profile.current_holders.find((x) => x.orgRevisionId === id); + if (!holder) return new HttpSuccess({ remark: "", data: [] }); + _data.root = [holder.orgRootId]; + _data.child1 = [holder.orgChild1Id]; + _data.child2 = [holder.orgChild2Id]; + _data.child3 = [holder.orgChild3Id]; + _data.child4 = [holder.orgChild4Id]; + } else if (_privilege.privilege == "CHILD" || _privilege.privilege == "BROTHER") { + const holder = profile.current_holders.find((x) => x.orgRevisionId === id); + if (!holder) return new HttpSuccess({ remark: "", data: [] }); + _data.root = [holder.orgRootId]; + if (_privilege.root && _privilege.child1 === null) { + } else if (_privilege.child1 && _privilege.child2 === null) { + _data.child1 = [holder.orgChild1Id]; + } else if (_privilege.child2 && _privilege.child3 === null) { + _data.child1 = [holder.orgChild1Id]; + _data.child2 = [holder.orgChild2Id]; + } else if (_privilege.child3 && _privilege.child4 === null) { + _data.child1 = [holder.orgChild1Id]; + _data.child2 = [holder.orgChild2Id]; + _data.child3 = [holder.orgChild3Id]; + _data.child4 = [holder.orgChild4Id]; + } + } else { + _data.root = [profile.current_holders.find((x) => x.orgRevisionId === id)?.orgRootId]; + } + } else { + if (!attrOwnership) _data = _privilege; + } + } + + const orgDna = await new permission().checkDna(request, request.user.sub); + let level: NodeLevel = resolveNodeLevel(orgDna); + const orgRootData = await AppDataSource.getRepository(OrgRoot) .createQueryBuilder("orgRoot") + .select([ + "orgRoot.id", + "orgRoot.orgRootName", + "orgRoot.orgRootShortName", + "orgRoot.orgRootCode", + "orgRoot.orgRootOrder", + ]) + .addSelect([ + "posMasters.id", + "posMasters.posMasterNo", + "posMasters.orgChild1Id", + "posMasters.isDirector", + ]) + .addSelect(["current_holder.prefix", "current_holder.firstName", "current_holder.lastName"]) .where("orgRoot.orgRevisionId = :id", { id }) - // .andWhere( - // _data.root != undefined && _data.root != null - // ? _data.root[0] != null - // ? `orgRoot.id IN (:...node)` - // : `orgRoot.id is null` - // : "1=1", - // { - // node: _data.root, - // }, - // ) - .leftJoinAndSelect("orgRoot.posMasters", "posMasters") - .leftJoinAndSelect("posMasters.current_holder", "current_holder") + .andWhere( + _data.root != undefined && _data.root != null + ? _data.root[0] != null + ? `orgRoot.id IN (:...node)` + : `orgRoot.id is null` + : "1=1", + { + node: _data.root, + }, + ) + .leftJoin("orgRoot.posMasters", "posMasters") + .leftJoin("posMasters.current_holder", "current_holder") .orderBy("orgRoot.orgRootOrder", "ASC") .getMany(); @@ -5853,19 +5793,38 @@ export class OrganizationController extends Controller { orgRootIds && orgRootIds.length > 0 ? await AppDataSource.getRepository(OrgChild1) .createQueryBuilder("orgChild1") + .select([ + "orgChild1.id", + "orgChild1.orgRootId", + "orgChild1.orgChild1Name", + "orgChild1.orgChild1ShortName", + "orgChild1.orgChild1Code", + "orgChild1.orgChild1Order", + ]) + .addSelect([ + "posMasters.id", + "posMasters.posMasterNo", + "posMasters.orgChild2Id", + "posMasters.isDirector", + ]) + .addSelect([ + "current_holder.prefix", + "current_holder.firstName", + "current_holder.lastName", + ]) .where("orgChild1.orgRootId IN (:...ids)", { ids: orgRootIds }) - // .andWhere( - // _data.child1 != undefined && _data.child1 != null - // ? _data.child1[0] != null - // ? `orgChild1.id IN (:...node)` - // : `orgChild1.id is null` - // : "1=1", - // { - // node: _data.child1, - // }, - // ) - .leftJoinAndSelect("orgChild1.posMasters", "posMasters") - .leftJoinAndSelect("posMasters.current_holder", "current_holder") + .andWhere( + _data.child1 != undefined && _data.child1 != null + ? _data.child1[0] != null + ? `orgChild1.id IN (:...node)` + : `orgChild1.id is null` + : "1=1", + { + node: _data.child1, + }, + ) + .leftJoin("orgChild1.posMasters", "posMasters") + .leftJoin("posMasters.current_holder", "current_holder") .orderBy("orgChild1.orgChild1Order", "ASC") .getMany() : []; @@ -5875,19 +5834,38 @@ export class OrganizationController extends Controller { orgChild1Ids && orgChild1Ids.length > 0 ? await AppDataSource.getRepository(OrgChild2) .createQueryBuilder("orgChild2") + .select([ + "orgChild2.id", + "orgChild2.orgChild1Id", + "orgChild2.orgChild2Name", + "orgChild2.orgChild2ShortName", + "orgChild2.orgChild2Code", + "orgChild2.orgChild2Order", + ]) + .addSelect([ + "posMasters.id", + "posMasters.posMasterNo", + "posMasters.orgChild3Id", + "posMasters.isDirector", + ]) + .addSelect([ + "current_holder.prefix", + "current_holder.firstName", + "current_holder.lastName", + ]) .where("orgChild2.orgChild1Id IN (:...ids)", { ids: orgChild1Ids }) - // .andWhere( - // _data.child2 != undefined && _data.child2 != null - // ? _data.child2[0] != null - // ? `orgChild2.id IN (:...node)` - // : `orgChild2.id is null` - // : "1=1", - // { - // node: _data.child2, - // }, - // ) - .leftJoinAndSelect("orgChild2.posMasters", "posMasters") - .leftJoinAndSelect("posMasters.current_holder", "current_holder") + .andWhere( + _data.child2 != undefined && _data.child2 != null + ? _data.child2[0] != null + ? `orgChild2.id IN (:...node)` + : `orgChild2.id is null` + : "1=1", + { + node: _data.child2, + }, + ) + .leftJoin("orgChild2.posMasters", "posMasters") + .leftJoin("posMasters.current_holder", "current_holder") .orderBy("orgChild2.orgChild2Order", "ASC") .getMany() : []; @@ -5897,19 +5875,38 @@ export class OrganizationController extends Controller { orgChild2Ids && orgChild2Ids.length > 0 ? await AppDataSource.getRepository(OrgChild3) .createQueryBuilder("orgChild3") + .select([ + "orgChild3.id", + "orgChild3.orgChild2Id", + "orgChild3.orgChild3Name", + "orgChild3.orgChild3ShortName", + "orgChild3.orgChild3Code", + "orgChild3.orgChild3Order", + ]) + .addSelect([ + "posMasters.id", + "posMasters.posMasterNo", + "posMasters.orgChild4Id", + "posMasters.isDirector", + ]) + .addSelect([ + "current_holder.prefix", + "current_holder.firstName", + "current_holder.lastName", + ]) .where("orgChild3.orgChild2Id IN (:...ids)", { ids: orgChild2Ids }) - // .andWhere( - // _data.child3 != undefined && _data.child3 != null - // ? _data.child3[0] != null - // ? `orgChild3.id IN (:...node)` - // : `orgChild3.id is null` - // : "1=1", - // { - // node: _data.child3, - // }, - // ) - .leftJoinAndSelect("orgChild3.posMasters", "posMasters") - .leftJoinAndSelect("posMasters.current_holder", "current_holder") + .andWhere( + _data.child3 != undefined && _data.child3 != null + ? _data.child3[0] != null + ? `orgChild3.id IN (:...node)` + : `orgChild3.id is null` + : "1=1", + { + node: _data.child3, + }, + ) + .leftJoin("orgChild3.posMasters", "posMasters") + .leftJoin("posMasters.current_holder", "current_holder") .orderBy("orgChild3.orgChild3Order", "ASC") .getMany() : []; @@ -5919,305 +5916,208 @@ export class OrganizationController extends Controller { orgChild3Ids && orgChild3Ids.length > 0 ? await AppDataSource.getRepository(OrgChild4) .createQueryBuilder("orgChild4") + .select([ + "orgChild4.id", + "orgChild4.orgChild3Id", + "orgChild4.orgChild4Name", + "orgChild4.orgChild4ShortName", + "orgChild4.orgChild4Code", + "orgChild4.orgChild4Order", + ]) + .addSelect(["posMasters.id", "posMasters.posMasterNo", "posMasters.isDirector"]) + .addSelect([ + "current_holder.prefix", + "current_holder.firstName", + "current_holder.lastName", + ]) .where("orgChild4.orgChild3Id IN (:...ids)", { ids: orgChild3Ids }) - // .andWhere( - // _data.child4 != undefined && _data.child4 != null - // ? _data.child4[0] != null - // ? `orgChild4.id IN (:...node)` - // : `orgChild4.id is null` - // : "1=1", - // { - // node: _data.child4, - // }, - // ) - .leftJoinAndSelect("orgChild4.posMasters", "posMasters") - .leftJoinAndSelect("posMasters.current_holder", "current_holder") + .andWhere( + _data.child4 != undefined && _data.child4 != null + ? _data.child4[0] != null + ? `orgChild4.id IN (:...node)` + : `orgChild4.id is null` + : "1=1", + { + node: _data.child4, + }, + ) + .leftJoin("orgChild4.posMasters", "posMasters") + .leftJoin("posMasters.current_holder", "current_holder") .orderBy("orgChild4.orgChild4Order", "ASC") .getMany() : []; - // const formattedData = orgRootData.map((orgRoot) => { - const formattedData = await Promise.all( - orgRootData.map(async (orgRoot) => { - return { - orgTreeId: orgRoot.id, - orgLevel: 0, - orgName: orgRoot.orgRootName, - orgTreeName: orgRoot.orgRootName, - orgTreeShortName: orgRoot.orgRootShortName, - orgTreeCode: orgRoot.orgRootCode, - orgCode: orgRoot.orgRootCode + "00", + const cannotViewRootPosMaster = + _privilege.privilege === "PARENT" || + (_privilege.privilege === "BROTHER" && level !== null && level > 1) || + (_privilege.privilege === "CHILD" && level !== null && level > 0) || + (_privilege.privilege === "NORMAL" && level !== null && level !== 0); + + const cannotViewChild1PosMaster = + (_privilege.privilege === "PARENT" && level !== null && level > 1) || + (_privilege.privilege === "BROTHER" && level !== null && level > 2) || + (_privilege.privilege === "CHILD" && level !== null && level > 1) || + (_privilege.privilege === "NORMAL" && level !== 1); + + const cannotViewChild2PosMaster = + (_privilege.privilege === "PARENT" && level !== null && level > 2) || + (_privilege.privilege === "BROTHER" && level !== null && level > 3) || + (_privilege.privilege === "CHILD" && level !== null && level > 2) || + (_privilege.privilege === "NORMAL" && level !== 2); + + const cannotViewChild3PosMaster = + (_privilege.privilege === "PARENT" && level !== null && level > 3) || + (_privilege.privilege === "BROTHER" && level !== null && level > 4) || + (_privilege.privilege === "CHILD" && level !== null && level > 3) || + (_privilege.privilege === "NORMAL" && level !== 3); + + const cannotViewChild4PosMaster = + (_privilege.privilege === "PARENT" && level !== null && level > 4) || + (_privilege.privilege === "CHILD" && level !== null && level > 4) || + (_privilege.privilege === "NORMAL" && level !== 4); + + const formattedData = orgRootData.map((orgRoot) => ({ + orgTreeId: orgRoot.id, + orgLevel: 0, + orgName: orgRoot.orgRootName, + orgTreeName: orgRoot.orgRootName, + orgTreeShortName: orgRoot.orgRootShortName, + orgTreeCode: orgRoot.orgRootCode, + orgCode: orgRoot.orgRootCode + "00", + orgRootName: orgRoot.orgRootName, + labelName: generateLabelName( + orgRoot.orgRootName, + orgRoot.orgRootCode + "00", + orgRoot.orgRootShortName, + orgRoot.orgRootName, + orgRoot.orgRootCode, + orgRoot.orgRootShortName, + ), + posMaster: cannotViewRootPosMaster + ? [] + : filterPosMasters(orgRoot.posMasters, "orgChild1Id").map((x) => + formatPosMaster(x, orgRoot.orgRootShortName, orgRoot.id, 0), + ), + children: orgChild1Data + .filter((orgChild1) => orgChild1.orgRootId === orgRoot.id) + .map((orgChild1) => ({ + orgTreeId: orgChild1.id, + orgRootId: orgRoot.id, + orgLevel: 1, + orgName: `${orgChild1.orgChild1Name}/${orgRoot.orgRootName}`, + orgTreeName: orgChild1.orgChild1Name, + orgTreeShortName: orgChild1.orgChild1ShortName, + orgTreeCode: orgChild1.orgChild1Code, + orgCode: orgRoot.orgRootCode + orgChild1.orgChild1Code, orgRootName: orgRoot.orgRootName, - labelName: - orgRoot.orgRootName + " " + orgRoot.orgRootCode + "00" + " " + orgRoot.orgRootShortName, - posMaster: await Promise.all( - orgRoot.posMasters - .filter( - (x) => - x.orgChild1Id == null && - // x.current_holderId != null && - x.isDirector === true, - ) - // .sort((a, b) => a.posMasterOrder - b.posMasterOrder) // Sort by posMasterOrder ASC - // .slice(0, 3) // Select the first 3 rows - .map(async (x) => ({ - posmasterId: x.id, - posNo: `${orgRoot.orgRootShortName} ${x.posMasterNo}`, - orgTreeId: orgRoot.id, - orgLevel: 0, - fullNameCurrentHolder: - x.current_holder == null - ? null - : `${x.current_holder.prefix}${x.current_holder.firstName} ${x.current_holder.lastName}`, - })), + labelName: generateLabelName( + orgChild1.orgChild1Name, + orgChild1.orgChild1Code, + orgChild1.orgChild1ShortName, + orgRoot.orgRootName, + orgRoot.orgRootCode, + orgRoot.orgRootShortName, ), - children: await Promise.all( - orgChild1Data - .filter((orgChild1) => orgChild1.orgRootId === orgRoot.id) - .map(async (orgChild1) => ({ - orgTreeId: orgChild1.id, - orgRootId: orgRoot.id, - orgLevel: 1, - orgName: `${orgChild1.orgChild1Name}/${orgRoot.orgRootName}`, - orgTreeName: orgChild1.orgChild1Name, - orgTreeShortName: orgChild1.orgChild1ShortName, - orgTreeCode: orgChild1.orgChild1Code, - orgCode: orgRoot.orgRootCode + orgChild1.orgChild1Code, - orgRootName: orgRoot.orgRootName, - labelName: - orgChild1.orgChild1Name + - " " + - orgRoot.orgRootCode + - orgChild1.orgChild1Code + - " " + - orgChild1.orgChild1ShortName + - "/" + - orgRoot.orgRootName + - " " + - orgRoot.orgRootCode + - "00" + - " " + - orgRoot.orgRootShortName, - posMaster: await Promise.all( - orgChild1.posMasters - .filter( - (x) => - x.orgChild2Id == null && - // x.current_holderId != null && - x.isDirector === true, - ) - // .sort((a, b) => a.posMasterOrder - b.posMasterOrder) // Sort by posMasterOrder ASC - // .slice(0, 3) // Select the first 3 rows - .map(async (x) => ({ - posmasterId: x.id, - posNo: `${orgChild1.orgChild1ShortName} ${x.posMasterNo}`, - orgTreeId: orgChild1.id, - orgLevel: 1, - fullNameCurrentHolder: - x.current_holder == null - ? null - : `${x.current_holder.prefix}${x.current_holder.firstName} ${x.current_holder.lastName}`, - })), - ), - - children: await Promise.all( - orgChild2Data - .filter((orgChild2) => orgChild2.orgChild1Id === orgChild1.id) - .map(async (orgChild2) => ({ - orgTreeId: orgChild2.id, - orgRootId: orgChild1.id, - orgLevel: 2, - orgName: `${orgChild2.orgChild2Name}/${orgChild1.orgChild1Name}/${orgRoot.orgRootName}`, - orgTreeName: orgChild2.orgChild2Name, - orgTreeShortName: orgChild2.orgChild2ShortName, - orgTreeCode: orgChild2.orgChild2Code, - orgCode: orgRoot.orgRootCode + orgChild2.orgChild2Code, + posMaster: cannotViewChild1PosMaster + ? [] + : filterPosMasters(orgChild1.posMasters, "orgChild2Id").map((x) => + formatPosMaster(x, orgChild1.orgChild1ShortName, orgChild1.id, 1), + ), + children: orgChild2Data + .filter((orgChild2) => orgChild2.orgChild1Id === orgChild1.id) + .map((orgChild2) => ({ + orgTreeId: orgChild2.id, + orgRootId: orgChild1.id, + orgLevel: 2, + orgName: `${orgChild2.orgChild2Name}/${orgChild1.orgChild1Name}/${orgRoot.orgRootName}`, + orgTreeName: orgChild2.orgChild2Name, + orgTreeShortName: orgChild2.orgChild2ShortName, + orgTreeCode: orgChild2.orgChild2Code, + orgCode: orgRoot.orgRootCode + orgChild2.orgChild2Code, + orgRootName: orgRoot.orgRootName, + labelName: generateLabelName( + orgChild2.orgChild2Name, + orgChild2.orgChild2Code, + orgChild2.orgChild2ShortName, + orgRoot.orgRootName, + orgRoot.orgRootCode, + orgRoot.orgRootShortName, + [orgChild1.orgChild1Name], + [orgChild1.orgChild1Code], + [orgChild1.orgChild1ShortName], + ), + posMaster: cannotViewChild2PosMaster + ? [] + : filterPosMasters(orgChild2.posMasters, "orgChild3Id").map((x) => + formatPosMaster(x, orgChild2.orgChild2ShortName, orgChild2.id, 2), + ), + children: orgChild3Data + .filter((orgChild3) => orgChild3.orgChild2Id === orgChild2.id) + .map((orgChild3) => ({ + orgTreeId: orgChild3.id, + orgRootId: orgChild2.id, + orgLevel: 3, + orgName: `${orgChild3.orgChild3Name}/${orgChild2.orgChild2Name}/${orgChild1.orgChild1Name}/${orgRoot.orgRootName}`, + orgTreeName: orgChild3.orgChild3Name, + orgTreeShortName: orgChild3.orgChild3ShortName, + orgTreeCode: orgChild3.orgChild3Code, + orgCode: orgRoot.orgRootCode + orgChild3.orgChild3Code, + orgRootName: orgRoot.orgRootName, + labelName: generateLabelName( + orgChild3.orgChild3Name, + orgChild3.orgChild3Code, + orgChild3.orgChild3ShortName, + orgRoot.orgRootName, + orgRoot.orgRootCode, + orgRoot.orgRootShortName, + [orgChild2.orgChild2Name, orgChild1.orgChild1Name], + [orgChild2.orgChild2Code, orgChild1.orgChild1Code], + [orgChild2.orgChild2ShortName, orgChild1.orgChild1ShortName], + ), + posMaster: cannotViewChild3PosMaster + ? [] + : filterPosMasters(orgChild3.posMasters, "orgChild4Id").map((x) => + formatPosMaster(x, orgChild3.orgChild3ShortName, orgChild3.id, 3), + ), + children: orgChild4Data + .filter((orgChild4) => orgChild4.orgChild3Id === orgChild3.id) + .map((orgChild4) => ({ + orgTreeId: orgChild4.id, + orgRootId: orgChild3.id, + orgLevel: 4, + orgName: `${orgChild4.orgChild4Name}/${orgChild3.orgChild3Name}/${orgChild2.orgChild2Name}/${orgChild1.orgChild1Name}/${orgRoot.orgRootName}`, + orgTreeName: orgChild4.orgChild4Name, + orgTreeShortName: orgChild4.orgChild4ShortName, + orgTreeCode: orgChild4.orgChild4Code, + orgCode: orgRoot.orgRootCode + orgChild4.orgChild4Code, orgRootName: orgRoot.orgRootName, - labelName: - orgChild2.orgChild2Name + - " " + - orgRoot.orgRootCode + - orgChild2.orgChild2Code + - " " + - orgChild2.orgChild2ShortName + - "/" + - orgChild1.orgChild1Name + - " " + - orgRoot.orgRootCode + - orgChild1.orgChild1Code + - " " + - orgChild1.orgChild1ShortName + - "/" + - orgRoot.orgRootName + - " " + - orgRoot.orgRootCode + - "00" + - " " + + labelName: generateLabelName( + orgChild4.orgChild4Name, + orgChild4.orgChild4Code, + orgChild4.orgChild4ShortName, + orgRoot.orgRootName, + orgRoot.orgRootCode, orgRoot.orgRootShortName, - posMaster: await Promise.all( - orgChild2.posMasters - .filter( - (x) => - x.orgChild3Id == null && - // x.current_holderId != null && - x.isDirector === true, - ) - // .sort((a, b) => a.posMasterOrder - b.posMasterOrder) // Sort by posMasterOrder ASC - // .slice(0, 3) // Select the first 3 rows - .map(async (x) => ({ - posmasterId: x.id, - posNo: `${orgChild2.orgChild2ShortName} ${x.posMasterNo}`, - orgTreeId: orgChild2.id, - orgLevel: 2, - fullNameCurrentHolder: - x.current_holder == null - ? null - : `${x.current_holder.prefix}${x.current_holder.firstName} ${x.current_holder.lastName}`, - })), + [orgChild3.orgChild3Name, orgChild2.orgChild2Name, orgChild1.orgChild1Name], + [orgChild3.orgChild3Code, orgChild2.orgChild2Code, orgChild1.orgChild1Code], + [ + orgChild3.orgChild3ShortName, + orgChild2.orgChild2ShortName, + orgChild1.orgChild1ShortName, + ], ), - - children: await Promise.all( - orgChild3Data - .filter((orgChild3) => orgChild3.orgChild2Id === orgChild2.id) - .map(async (orgChild3) => ({ - orgTreeId: orgChild3.id, - orgRootId: orgChild2.id, - orgLevel: 3, - orgName: `${orgChild3.orgChild3Name}/${orgChild2.orgChild2Name}/${orgChild1.orgChild1Name}/${orgRoot.orgRootName}`, - orgTreeName: orgChild3.orgChild3Name, - orgTreeShortName: orgChild3.orgChild3ShortName, - orgTreeCode: orgChild3.orgChild3Code, - orgCode: orgRoot.orgRootCode + orgChild3.orgChild3Code, - orgRootName: orgRoot.orgRootName, - labelName: - orgChild3.orgChild3Name + - " " + - orgRoot.orgRootCode + - orgChild3.orgChild3Code + - " " + - orgChild3.orgChild3ShortName + - "/" + - orgChild2.orgChild2Name + - " " + - orgRoot.orgRootCode + - orgChild2.orgChild2Code + - " " + - orgChild2.orgChild2ShortName + - "/" + - orgChild1.orgChild1Name + - " " + - orgRoot.orgRootCode + - orgChild1.orgChild1Code + - " " + - orgChild1.orgChild1ShortName + - "/" + - orgRoot.orgRootName + - " " + - orgRoot.orgRootCode + - "00" + - " " + - orgRoot.orgRootShortName, - posMaster: await Promise.all( - orgChild3.posMasters - .filter( - (x) => - x.orgChild4Id == null && - // x.current_holderId != null && - x.isDirector === true, - ) - // .sort((a, b) => a.posMasterOrder - b.posMasterOrder) // Sort by posMasterOrder ASC - // .slice(0, 3) // Select the first 3 rows - .map(async (x) => ({ - posmasterId: x.id, - posNo: `${orgChild3.orgChild3ShortName} ${x.posMasterNo}`, - orgTreeId: orgChild3.id, - orgLevel: 3, - fullNameCurrentHolder: - x.current_holder == null - ? null - : `${x.current_holder.prefix}${x.current_holder.firstName} ${x.current_holder.lastName}`, - })), + posMaster: cannotViewChild4PosMaster + ? [] + : orgChild4.posMasters + .filter((x) => x.isDirector === true) + .map((x) => + formatPosMaster(x, orgChild4.orgChild4ShortName, orgChild4.id, 4), ), - - children: await Promise.all( - orgChild4Data - .filter((orgChild4) => orgChild4.orgChild3Id === orgChild3.id) - .map(async (orgChild4) => ({ - orgTreeId: orgChild4.id, - orgRootId: orgChild3.id, - orgLevel: 4, - orgName: `${orgChild4.orgChild4Name}/${orgChild3.orgChild3Name}/${orgChild2.orgChild2Name}/${orgChild1.orgChild1Name}/${orgRoot.orgRootName}`, - orgTreeName: orgChild4.orgChild4Name, - orgTreeShortName: orgChild4.orgChild4ShortName, - orgTreeCode: orgChild4.orgChild4Code, - orgCode: orgRoot.orgRootCode + orgChild4.orgChild4Code, - orgRootName: orgRoot.orgRootName, - labelName: - orgChild4.orgChild4Name + - " " + - orgRoot.orgRootCode + - orgChild4.orgChild4Code + - " " + - orgChild4.orgChild4ShortName + - "/" + - orgChild3.orgChild3Name + - " " + - orgRoot.orgRootCode + - orgChild3.orgChild3Code + - " " + - orgChild3.orgChild3ShortName + - "/" + - orgChild2.orgChild2Name + - " " + - orgRoot.orgRootCode + - orgChild2.orgChild2Code + - " " + - orgChild2.orgChild2ShortName + - "/" + - orgChild1.orgChild1Name + - " " + - orgRoot.orgRootCode + - orgChild1.orgChild1Code + - " " + - orgChild1.orgChild1ShortName + - "/" + - orgRoot.orgRootName + - " " + - orgRoot.orgRootCode + - "00" + - " " + - orgRoot.orgRootShortName, - posMaster: await Promise.all( - orgChild4.posMasters - .filter( - (x) => - // x.current_holderId != null && - x.isDirector === true, - ) - // .sort((a, b) => a.posMasterOrder - b.posMasterOrder) // Sort by posMasterOrder ASC - // .slice(0, 3) // Select the first 3 rows - .map(async (x) => ({ - posmasterId: x.id, - posNo: `${orgChild4.orgChild4ShortName} ${x.posMasterNo}`, - orgTreeId: orgChild4.id, - orgLevel: 4, - fullNameCurrentHolder: - x.current_holder == null - ? null - : `${x.current_holder.prefix}${x.current_holder.firstName} ${x.current_holder.lastName}`, - })), - ), - })), - ), - })), - ), })), - ), - })), - ), - }; - }), - ); + })), + })), + })), + })); return new HttpSuccess(formattedData); } @@ -6312,7 +6212,7 @@ export class OrganizationController extends Controller { // กำหนดการเข้าถึงข้อมูลตามสถานะและสิทธิ์ const isCurrentActive = !orgRevision.orgRevisionIsDraft && orgRevision.orgRevisionIsCurrent; if (isCurrentActive) { - if (_privilege.privilege !== "OWNER") { + if (_privilege.privilege !== "OWNER" && _privilege.privilege !== "PARENT") { if (_privilege.privilege == "NORMAL") { const holder = profile.current_holders.find((x) => x.orgRevisionId === id); if (!holder) return; @@ -7420,75 +7320,156 @@ export class OrganizationController extends Controller { */ @Get("root/search/sort") async searchSortRootLevelType(@Request() request: RequestWithUser) { - const root1 = await this.orgRootRepository.find({ - where: { - orgRevision: { orgRevisionIsDraft: false, orgRevisionIsCurrent: true }, - DEPARTMENT_CODE: Not("50"), - }, - order: { isDeputy: "DESC", orgRootOrder: "ASC" }, - select: ["orgRootName"], - }); - const root2 = await this.orgRootRepository.find({ - where: { - orgRevision: { orgRevisionIsDraft: false, orgRevisionIsCurrent: true }, - DEPARTMENT_CODE: "50", - }, - order: { orgRootName: "ASC" }, - select: ["orgRootName"], - }); - const root = [...root1, ...root2]; + // const root1 = await this.orgRootRepository.find({ + // where: { + // orgRevision: { orgRevisionIsDraft: false, orgRevisionIsCurrent: true }, + // DEPARTMENT_CODE: Not("50"), + // }, + // order: { isDeputy: "DESC", orgRootOrder: "ASC" }, + // select: ["orgRootName"], + // }); + // const root2 = await this.orgRootRepository.find({ + // where: { + // orgRevision: { orgRevisionIsDraft: false, orgRevisionIsCurrent: true }, + // DEPARTMENT_CODE: "50", + // }, + // order: { orgRootName: "ASC" }, + // select: ["orgRootName"], + // }); + // const root = [...root1, ...root2]; + + // const child1 = await this.child1Repository.find({ + // where: { + // orgRevision: { orgRevisionIsDraft: false, orgRevisionIsCurrent: true }, + // }, + // order: { orgChild1Order: "ASC" }, + // select: ["orgChild1Name"], + // }); + // const child2 = await this.child2Repository.find({ + // where: { + // orgRevision: { orgRevisionIsDraft: false, orgRevisionIsCurrent: true }, + // }, + // order: { orgChild2Order: "ASC" }, + // select: ["orgChild2Name"], + // }); + // const child3 = await this.child3Repository.find({ + // where: { + // orgRevision: { orgRevisionIsDraft: false, orgRevisionIsCurrent: true }, + // }, + // order: { orgChild3Order: "ASC" }, + // select: ["orgChild3Name"], + // }); + // const child4 = await this.child4Repository.find({ + // where: { + // orgRevision: { orgRevisionIsDraft: false, orgRevisionIsCurrent: true }, + // }, + // order: { orgChild4Order: "ASC" }, + // select: ["orgChild4Name"], + // }); + // const hospital = await this.child1Repository.find({ + // where: [ + // { + // orgRevision: { orgRevisionIsDraft: false, orgRevisionIsCurrent: true }, + // orgRoot: { isDeputy: true }, + // }, + // { + // orgRevision: { orgRevisionIsDraft: false, orgRevisionIsCurrent: true }, + // orgChild1RankSub: "HOSPITAL", + // }, + // ], + // select: ["orgChild1Name"], + // }); + // const posType = await this.posTypeRepository.find({ + // order: { posTypeRank: "DESC" }, + // select: ["posTypeName"], + // }); + // const posLevel = await this.posLevelRepository.find({ + // order: { posLevelRank: "DESC" }, + // select: ["posLevelName"], + // }); + + const [roots, child1, child2, child3, child4, hospital, posType, posLevel] = await Promise.all([ + // ===== ROOT ===== + this.orgRootRepository + .createQueryBuilder("root") + .innerJoin("root.orgRevision", "rev") + .select(["root.orgRootName AS orgRootName"]) + .where("rev.orgRevisionIsDraft = false") + .andWhere("rev.orgRevisionIsCurrent = true") + .orderBy("CASE WHEN root.DEPARTMENT_CODE = '50' THEN 1 ELSE 0 END", "ASC") + .addOrderBy("root.isDeputy", "DESC") + .addOrderBy("root.orgRootOrder", "ASC") + .addOrderBy("root.orgRootName", "ASC") + .getRawMany(), + + // ===== CHILD 1 ===== + this.child1Repository + .createQueryBuilder("c1") + .innerJoin("c1.orgRevision", "rev") + .select("c1.orgChild1Name", "orgChild1Name") + .where("rev.orgRevisionIsDraft = false") + .andWhere("rev.orgRevisionIsCurrent = true") + .orderBy("c1.orgChild1Order", "ASC") + .getRawMany(), + + // ===== CHILD 2 ===== + this.child2Repository + .createQueryBuilder("c2") + .innerJoin("c2.orgRevision", "rev") + .select("c2.orgChild2Name", "orgChild2Name") + .where("rev.orgRevisionIsDraft = false") + .andWhere("rev.orgRevisionIsCurrent = true") + .orderBy("c2.orgChild2Order", "ASC") + .getRawMany(), + + // ===== CHILD 3 ===== + this.child3Repository + .createQueryBuilder("c3") + .innerJoin("c3.orgRevision", "rev") + .select("c3.orgChild3Name", "orgChild3Name") + .where("rev.orgRevisionIsDraft = false") + .andWhere("rev.orgRevisionIsCurrent = true") + .orderBy("c3.orgChild3Order", "ASC") + .getRawMany(), + + // ===== CHILD 4 ===== + this.child4Repository + .createQueryBuilder("c4") + .innerJoin("c4.orgRevision", "rev") + .select("c4.orgChild4Name", "orgChild4Name") + .where("rev.orgRevisionIsDraft = false") + .andWhere("rev.orgRevisionIsCurrent = true") + .orderBy("c4.orgChild4Order", "ASC") + .getRawMany(), + + // ===== HOSPITAL ===== + this.child1Repository + .createQueryBuilder("c1") + .innerJoin("c1.orgRevision", "rev") + .leftJoin("c1.orgRoot", "root") + .select("c1.orgChild1Name", "orgChild1Name") + .where("rev.orgRevisionIsDraft = false") + .andWhere("rev.orgRevisionIsCurrent = true") + .andWhere("(root.isDeputy = true OR c1.orgChild1RankSub = :rank)", { rank: "HOSPITAL" }) + .getRawMany(), + + // ===== POSITION TYPE ===== + this.posTypeRepository + .createQueryBuilder("pt") + .select("pt.posTypeName", "posTypeName") + .orderBy("pt.posTypeRank", "DESC") + .getRawMany(), + + // ===== POSITION LEVEL ===== + this.posLevelRepository + .createQueryBuilder("pl") + .select("pl.posLevelName", "posLevelName") + .orderBy("pl.posLevelRank", "DESC") + .getRawMany(), + ]); - const child1 = await this.child1Repository.find({ - where: { - orgRevision: { orgRevisionIsDraft: false, orgRevisionIsCurrent: true }, - }, - order: { orgChild1Order: "ASC" }, - select: ["orgChild1Name"], - }); - const child2 = await this.child2Repository.find({ - where: { - orgRevision: { orgRevisionIsDraft: false, orgRevisionIsCurrent: true }, - }, - order: { orgChild2Order: "ASC" }, - select: ["orgChild2Name"], - }); - const child3 = await this.child3Repository.find({ - where: { - orgRevision: { orgRevisionIsDraft: false, orgRevisionIsCurrent: true }, - }, - order: { orgChild3Order: "ASC" }, - select: ["orgChild3Name"], - }); - const child4 = await this.child4Repository.find({ - where: { - orgRevision: { orgRevisionIsDraft: false, orgRevisionIsCurrent: true }, - }, - order: { orgChild4Order: "ASC" }, - select: ["orgChild4Name"], - }); - const hospital = await this.child1Repository.find({ - where: [ - { - orgRevision: { orgRevisionIsDraft: false, orgRevisionIsCurrent: true }, - orgRoot: { isDeputy: true }, - }, - { - orgRevision: { orgRevisionIsDraft: false, orgRevisionIsCurrent: true }, - orgChild1RankSub: "HOSPITAL", - }, - ], - select: ["orgChild1Name"], - }); - const posType = await this.posTypeRepository.find({ - order: { posTypeRank: "DESC" }, - select: ["posTypeName"], - }); - const posLevel = await this.posLevelRepository.find({ - order: { posLevelRank: "DESC" }, - select: ["posLevelName"], - }); return new HttpSuccess({ - root: root.map((x) => x.orgRootName), + root: roots.map((x) => x.orgRootName), child1: child1.map((x) => x.orgChild1Name), child2: child2.map((x) => x.orgChild2Name), child3: child3.map((x) => x.orgChild3Name), @@ -7815,10 +7796,11 @@ export class OrganizationController extends Controller { profile.leaveType = "RETIRE"; profile.isActive = false; - if (profile.keycloak != null && profile.keycloak != "") { + if (profile.keycloak != null && profile.keycloak != "" && profile.isDelete === false) { const delUserKeycloak = await deleteUser(profile.keycloak, token); if (delUserKeycloak) { - profile.keycloak = ""; + // profile.keycloak = ""; + profile.isDelete = true; profile.roleKeycloaks = []; checkOfficer += 1; } else { @@ -7843,10 +7825,11 @@ export class OrganizationController extends Controller { profileEmp.lastUpdatedAt = new Date(); profileEmp.isActive = false; - if (profileEmp.keycloak != null && profileEmp.keycloak != "") { + if (profileEmp.keycloak != null && profileEmp.keycloak != "" && profileEmp.isDelete === false) { const delUserKeycloak = await deleteUser(profileEmp.keycloak, token); if (delUserKeycloak) { - profileEmp.keycloak = ""; + // profileEmp.keycloak = ""; + profileEmp.isDelete = true; profileEmp.roleKeycloaks = []; checkEmployee += 1; } else { @@ -8063,7 +8046,12 @@ export class OrganizationController extends Controller { if (_item) { _item.roleKeycloaks = Array.from(new Set([..._item.roleKeycloaks, ...roleKeycloak])); check += 1; - await this.profileEmployeeRepo.save(_item); + _item = await this.profileEmployeeRepo.save(_item); + // update user attribute in keycloak + await updateUserAttributes(_item.keycloak, { + profileId: [_item.id], + prefix: [_item.prefix || ""], + }); } } catch (error) { console.error(`Error processing ${_item.citizenId}:`, error); @@ -8074,4 +8062,1128 @@ export class OrganizationController extends Controller { const total = profiles.length; return new HttpSuccess({ total, successAmount: check }); } + + /** + * API ย้ายโครงสร้างแบบร่างไปโครงสร้างปัจจุบัน โดยอ้างอิงตาม rootId + * + * @summary - ย้ายโครงสร้างและตำแหน่งจากแบบร่างไปโครงสร้างปัจจุบัน โดยอ้างอิงตาม rootId + * + */ + @Post("move-draft-to-current/{rootDnaId}") + async moveDraftToCurrent(@Path() rootDnaId: string, @Request() request: RequestWithUser) { + const queryRunner = AppDataSource.createQueryRunner(); + await queryRunner.connect(); + await queryRunner.startTransaction(); + + try { + // permission owner only ?? + // this code check... + + // part 1 ข้อมูลโครงสร้าง + const [drafRevision, currentRevision] = await Promise.all([ + this.orgRevisionRepository.findOne({ + where: { + orgRevisionIsDraft: true, + orgRevisionIsCurrent: false, + }, + select: ["id"], + }), + this.orgRevisionRepository.findOne({ + where: { + orgRevisionIsDraft: false, + orgRevisionIsCurrent: true, + }, + select: ["id"], + }), + ]); + + if (!drafRevision || !currentRevision) + return new HttpError(HttpStatusCode.NOT_FOUND, "ไม่พบข้อมูล"); + + const drafRevisionId = drafRevision.id; + const currentRevisionId = currentRevision.id; + + // ตรวจสอบว่ามี rootDnaId ในโครงสร้างร่าง และในโครงสร้างปัจจุบันหรือไม่ + let [orgRootDraft, orgRootCurrent] = await Promise.all([ + this.orgRootRepository.findOne({ + where: { + ancestorDNA: rootDnaId, + orgRevisionId: drafRevisionId, + }, + // select: ["id"], + }), + this.orgRootRepository.findOne({ + where: { + ancestorDNA: rootDnaId, + orgRevisionId: currentRevisionId, + }, + select: ["id"], + }), + ]); + + if (!orgRootDraft) return new HttpError(HttpStatusCode.NOT_FOUND, "ไม่พบข้อมูลโครงสร้างร่าง"); + + // if current record not found, create new one + if (!orgRootCurrent) { + // Create new current record using draft's ID + const newCurrentRoot = queryRunner.manager.create(OrgRoot, { + ...orgRootDraft, + id: undefined, // Let database generate new ID + orgRevisionId: currentRevisionId, // Change to current revision + }); + + const savedRoot = await queryRunner.manager.save(OrgRoot, newCurrentRoot); + orgRootCurrent = savedRoot; // Use saved record for sync + } + + // Part 1: Differential sync of organization structure (bottom-up) + // Build mapping incrementally as we process each level + + const allMappings: AllOrgMappings = { + orgRoot: { byAncestorDNA: new Map(), byDraftId: new Map() }, + orgChild1: { byAncestorDNA: new Map(), byDraftId: new Map() }, + orgChild2: { byAncestorDNA: new Map(), byDraftId: new Map() }, + orgChild3: { byAncestorDNA: new Map(), byDraftId: new Map() }, + orgChild4: { byAncestorDNA: new Map(), byDraftId: new Map() }, + }; + + // Track sync statistics for organization nodes + const orgSyncStats: Record = + {}; + + // Process from top (Root) to bottom (Child4) to handle foreign key constraints + // OrgRoot (sync first - no parent dependencies) + // If we manually created orgRootCurrent, skip syncOrgLevel and set up mapping directly + // to avoid double insert (syncOrgLevel would try to insert again because IDs don't match) + let orgRootResult: { + mapping: OrgIdMapping; + counts: { deleted: number; updated: number; inserted: number }; + }; + if ( + orgRootCurrent && + orgRootDraft && + orgRootCurrent.ancestorDNA === orgRootDraft.ancestorDNA + ) { + // Manually created - set up mapping directly + const rootMapping: OrgIdMapping = { + byAncestorDNA: new Map([[orgRootDraft.ancestorDNA, orgRootCurrent.id]]), + byDraftId: new Map([[orgRootDraft.id, orgRootCurrent.id]]), + }; + orgRootResult = { + mapping: rootMapping, + counts: { deleted: 0, updated: 0, inserted: 1 }, // Count as insert since we created it + }; + } else { + // Not manually created - use normal syncOrgLevel flow + orgRootResult = await this.syncOrgLevel( + queryRunner, + OrgRoot, + this.orgRootRepository, + drafRevisionId, + currentRevisionId, + allMappings, + orgRootDraft?.id, + orgRootCurrent?.id, + ); + } + allMappings.orgRoot = orgRootResult.mapping; + orgSyncStats.orgRoot = orgRootResult.counts; + + // Child1 (parent OrgRoot already synced) + const child1Result = await this.syncOrgLevel( + queryRunner, + OrgChild1, + this.child1Repository, + drafRevisionId, + currentRevisionId, + allMappings, + orgRootDraft?.id, + orgRootCurrent?.id, + ); + allMappings.orgChild1 = child1Result.mapping; + orgSyncStats.orgChild1 = child1Result.counts; + + // Child2 (parents OrgRoot and Child1 already synced) + const child2Result = await this.syncOrgLevel( + queryRunner, + OrgChild2, + this.child2Repository, + drafRevisionId, + currentRevisionId, + allMappings, + orgRootDraft?.id, + orgRootCurrent?.id, + ); + allMappings.orgChild2 = child2Result.mapping; + orgSyncStats.orgChild2 = child2Result.counts; + + // Child3 (parents OrgRoot, Child1, Child2 already synced) + const child3Result = await this.syncOrgLevel( + queryRunner, + OrgChild3, + this.child3Repository, + drafRevisionId, + currentRevisionId, + allMappings, + orgRootDraft?.id, + orgRootCurrent?.id, + ); + allMappings.orgChild3 = child3Result.mapping; + orgSyncStats.orgChild3 = child3Result.counts; + + // Child4 (parents OrgRoot, Child1, Child2, Child3 already synced) + const child4Result = await this.syncOrgLevel( + queryRunner, + OrgChild4, + this.child4Repository, + drafRevisionId, + currentRevisionId, + allMappings, + orgRootDraft?.id, + orgRootCurrent?.id, + ); + allMappings.orgChild4 = child4Result.mapping; + orgSyncStats.orgChild4 = child4Result.counts; + + // Part 2: Sync position data using new org IDs from Part 1 + // 2.1 Clear current_holderId for affected positions (keep existing logic) + // Get draft organization IDs under the given rootDnaId to find positions to clear + const draftOrgIds = { + orgRoot: [...allMappings.orgRoot.byDraftId.keys()], + orgChild1: [...allMappings.orgChild1.byDraftId.keys()], + orgChild2: [...allMappings.orgChild2.byDraftId.keys()], + orgChild3: [...allMappings.orgChild3.byDraftId.keys()], + orgChild4: [...allMappings.orgChild4.byDraftId.keys()], + }; + + // Get current organization IDs from the mappings (moved up for reuse) + const currentOrgIds = { + orgRoot: [...allMappings.orgRoot.byDraftId.values()], + orgChild1: [...allMappings.orgChild1.byDraftId.values()], + orgChild2: [...allMappings.orgChild2.byDraftId.values()], + orgChild3: [...allMappings.orgChild3.byDraftId.values()], + orgChild4: [...allMappings.orgChild4.byDraftId.values()], + }; + + // Get draft positions that belong to any org under the rootDnaId + const posMasterDraft = await this.posMasterRepository.find({ + where: [ + { orgRevisionId: drafRevisionId, orgRootId: In(draftOrgIds.orgRoot) }, + { orgRevisionId: drafRevisionId, orgChild1Id: In(draftOrgIds.orgChild1) }, + { orgRevisionId: drafRevisionId, orgChild2Id: In(draftOrgIds.orgChild2) }, + { orgRevisionId: drafRevisionId, orgChild3Id: In(draftOrgIds.orgChild3) }, + { orgRevisionId: drafRevisionId, orgChild4Id: In(draftOrgIds.orgChild4) }, + ], + }); + + // Special case: If draft has no positions, delete all current positions + if (posMasterDraft.length <= 0) { + // Fetch current positions + const posMasterCurrent = await this.posMasterRepository.find({ + where: [ + { orgRevisionId: currentRevisionId, orgRootId: In(currentOrgIds.orgRoot) }, + { orgRevisionId: currentRevisionId, orgChild1Id: In(currentOrgIds.orgChild1) }, + { orgRevisionId: currentRevisionId, orgChild2Id: In(currentOrgIds.orgChild2) }, + { orgRevisionId: currentRevisionId, orgChild3Id: In(currentOrgIds.orgChild3) }, + { orgRevisionId: currentRevisionId, orgChild4Id: In(currentOrgIds.orgChild4) }, + ], + }); + + if (posMasterCurrent.length > 0) { + const toDeleteIds = posMasterCurrent.map((p) => p.id); + + // Cascade delete positions first + await queryRunner.manager.delete(Position, { posMasterId: In(toDeleteIds) }); + + // Then delete posMaster records + await queryRunner.manager.delete(PosMaster, toDeleteIds); + + // Save history + const deleteHistoryOps = posMasterCurrent.map((pos) => ({ + posMasterDnaId: pos.ancestorDNA, + profileId: null, + pm: null, + })); + await BatchSavePosMasterHistoryOfficer(queryRunner, deleteHistoryOps); + } + + // Build summary for this special case + const summary = { + message: "ย้ายโครงสร้างสำเร็จ", + organization: { + orgRoot: orgSyncStats.orgRoot, + orgChild1: orgSyncStats.orgChild1, + orgChild2: orgSyncStats.orgChild2, + orgChild3: orgSyncStats.orgChild3, + orgChild4: orgSyncStats.orgChild4, + }, + positionMaster: { + deleted: posMasterCurrent.length, + updated: 0, + inserted: 0, + }, + position: { deleted: 0, updated: 0, inserted: 0 }, + }; + + await queryRunner.commitTransaction(); + return new HttpSuccess(summary); + } + + // Clear current_holderId for positions that will have new holders + const nextHolderIds = posMasterDraft + .filter((x) => x.next_holderId != null) + .map((x) => x.next_holderId) as string[]; + + if (nextHolderIds.length > 0) { + // FIX: Fetch positions first before updating (to avoid race condition) + const posMastersToUpdate = await queryRunner.manager.find(PosMaster, { + where: { + orgRevisionId: currentRevisionId, + current_holderId: In(nextHolderIds), + }, + }); + + // Save history BEFORE clearing current_holderId + const historyOps = posMastersToUpdate + .filter((x) => x.orgRootId != orgRootCurrent?.id) + .map((pos) => ({ + posMasterDnaId: pos.ancestorDNA, + profileId: null, + pm: null, + })); + await BatchSavePosMasterHistoryOfficer(queryRunner, historyOps); + + // Now clear current_holderId + await queryRunner.manager.update( + PosMaster, + { + orgRevisionId: currentRevisionId, + current_holderId: In(nextHolderIds), + }, + { current_holderId: null, isSit: false }, + ); + } + + // 2.2 Fetch current positions for comparison + const posMasterCurrent = await this.posMasterRepository.find({ + where: [ + { orgRevisionId: currentRevisionId, orgRootId: In(currentOrgIds.orgRoot) }, + { orgRevisionId: currentRevisionId, orgChild1Id: In(currentOrgIds.orgChild1) }, + { orgRevisionId: currentRevisionId, orgChild2Id: In(currentOrgIds.orgChild2) }, + { orgRevisionId: currentRevisionId, orgChild3Id: In(currentOrgIds.orgChild3) }, + { orgRevisionId: currentRevisionId, orgChild4Id: In(currentOrgIds.orgChild4) }, + ], + }); + + // Build lookup map + const currentByDNA = new Map(posMasterCurrent.map((p) => [p.ancestorDNA, p])); + + // 2.3 Batch DELETE: positions in current but not in draft + const toDelete = posMasterCurrent.filter( + (curr) => !posMasterDraft.some((d) => d.ancestorDNA === curr.ancestorDNA), + ); + + if (toDelete.length > 0) { + const toDeleteIds = toDelete.map((p) => p.id); + + // Cascade delete positions first + await queryRunner.manager.delete(Position, { posMasterId: In(toDeleteIds) }); + + // Then delete posMaster records + await queryRunner.manager.delete(PosMaster, toDeleteIds); + + const deleteHistoryOps = toDelete.map((pos) => ({ + posMasterDnaId: pos.ancestorDNA, + profileId: null, + pm: null, + })); + await BatchSavePosMasterHistoryOfficer(queryRunner, deleteHistoryOps); + } + + // 2.4 Process draft positions (UPDATE or INSERT) + const toUpdate: PosMaster[] = []; + const toInsert: any[] = []; + + // Track draft PosMaster ID to current PosMaster ID mapping for position sync + // Type: Map + const posMasterMapping: Map = new Map(); + + for (const draftPos of posMasterDraft) { + const current = currentByDNA.get(draftPos.ancestorDNA); + + // Map organization IDs using new IDs from Part 1 + const orgRootId = this.resolveOrgId(draftPos.orgRootId ?? null, allMappings.orgRoot); + const orgChild1Id = this.resolveOrgId(draftPos.orgChild1Id ?? null, allMappings.orgChild1); + const orgChild2Id = this.resolveOrgId(draftPos.orgChild2Id ?? null, allMappings.orgChild2); + const orgChild3Id = this.resolveOrgId(draftPos.orgChild3Id ?? null, allMappings.orgChild3); + const orgChild4Id = this.resolveOrgId(draftPos.orgChild4Id ?? null, allMappings.orgChild4); + + if (current) { + // UPDATE existing position + Object.assign(current, { + createdAt: draftPos.createdAt, + createdUserId: draftPos.createdUserId, + createdFullName: draftPos.createdFullName, + lastUpdatedAt: new Date(), + lastUpdateUserId: request.user.sub, + lastUpdateFullName: request.user.name, + posMasterNoPrefix: draftPos.posMasterNoPrefix, + posMasterNoSuffix: draftPos.posMasterNoSuffix, + posMasterNo: draftPos.posMasterNo, + posMasterOrder: draftPos.posMasterOrder, + orgRootId, + orgChild1Id, + orgChild2Id, + orgChild3Id, + orgChild4Id, + current_holderId: draftPos.next_holderId, + isSit: draftPos.isSit, + reason: draftPos.reason, + isDirector: draftPos.isDirector, + isStaff: draftPos.isStaff, + positionSign: draftPos.positionSign, + statusReport: "DONE", + isCondition: draftPos.isCondition, + conditionReason: draftPos.conditionReason, + }); + toUpdate.push(current); + + if (draftPos.next_holderId === null) { + await SavePosMasterHistoryOfficer(queryRunner, draftPos.ancestorDNA, null, null); + } + + // Track mapping for position sync + posMasterMapping.set(draftPos.id, [current.id, draftPos.next_holderId]); + } else { + // INSERT new position + const newPosMaster = queryRunner.manager.create(PosMaster, { + ...draftPos, + id: undefined, + orgRevisionId: currentRevisionId, + orgRootId, + orgChild1Id, + orgChild2Id, + orgChild3Id, + orgChild4Id, + current_holderId: draftPos.next_holderId, + statusReport: "DONE", + }); + + toInsert.push(newPosMaster); + } + } + + // Batch save updates and inserts + if (toUpdate.length > 0) { + await queryRunner.manager.save(toUpdate); + } + if (toInsert.length > 0) { + const saved = await queryRunner.manager.save(toInsert); + + // Track mapping for newly inserted posMasters + // saved is an array, map each to its draft ID + if (Array.isArray(saved)) { + for (let i = 0; i < saved.length; i++) { + const draftPos = posMasterDraft.filter((d) => !currentByDNA.has(d.ancestorDNA))[i]; + if (draftPos && saved[i]) { + posMasterMapping.set(draftPos.id, [saved[i].id, draftPos.next_holderId]); + } + } + } + } + + // 2.5 Sync positions table for all affected posMasters (BATCH operation for performance) + const positionSyncStats = await this.syncAllPositionsBatch( + queryRunner, + posMasterMapping, + drafRevisionId, + currentRevisionId, + ); + + // Build comprehensive summary + const summary = { + message: "ย้ายโครงสร้างสำเร็จ", + organization: { + orgRoot: orgSyncStats.orgRoot, + orgChild1: orgSyncStats.orgChild1, + orgChild2: orgSyncStats.orgChild2, + orgChild3: orgSyncStats.orgChild3, + orgChild4: orgSyncStats.orgChild4, + }, + positionMaster: { + deleted: toDelete.length, + updated: toUpdate.length, + inserted: toInsert.length, + }, + position: positionSyncStats, + }; + + await queryRunner.commitTransaction(); + return new HttpSuccess(summary); + } catch (error) { + console.error("Error moving draft to current:", error); + await queryRunner.rollbackTransaction(); + throw new HttpError(HttpStatusCode.INTERNAL_SERVER_ERROR, "เกิดข้อผิดพลาดในการย้ายโครงสร้าง"); + } finally { + if (queryRunner.isTransactionActive) { + await queryRunner.rollbackTransaction(); + } + await queryRunner.release(); + } + } + + /** + * Helper function: Map draft ID to current ID using the mapping + */ + private resolveOrgId(draftId: string | null, mapping: OrgIdMapping): string | null { + if (!draftId) return null; + return mapping.byDraftId.get(draftId) ?? null; + } + + /** + * Helper function: Cascade delete positions before deleting org node + */ + private async cascadeDeletePositions( + queryRunner: any, + node: any, + entityClass: any, + ): Promise { + const whereClause: any = { + orgRevisionId: node.orgRevisionId, + }; + + // Determine which FK field to use based on entity type + if (entityClass === OrgRoot) { + whereClause.orgRootId = node.id; + } else if (entityClass === OrgChild1) { + whereClause.orgChild1Id = node.id; + } else if (entityClass === OrgChild2) { + whereClause.orgChild2Id = node.id; + } else if (entityClass === OrgChild3) { + whereClause.orgChild3Id = node.id; + } else if (entityClass === OrgChild4) { + whereClause.orgChild4Id = node.id; + } + + await queryRunner.manager.delete(PosMaster, whereClause); + } + + /** + * Helper function: Generic differential sync for each org level + * Performs DELETE (nodes not in draft), UPDATE (existing nodes), INSERT (new nodes) + */ + private async syncOrgLevel( + queryRunner: any, + entityClass: any, + repository: any, + draftRevisionId: string, + currentRevisionId: string, + parentMappings?: AllOrgMappings, + draftOrgRootId?: string, + currentOrgRootId?: string, + ): Promise<{ + mapping: OrgIdMapping; + counts: { deleted: number; updated: number; inserted: number }; + }> { + // 1. Fetch draft and current nodes under the given rootDnaId + // Build WHERE condition for draft nodes + const draftWhere: any = { + orgRevisionId: draftRevisionId, + }; + if ( + draftOrgRootId && + (entityClass === OrgChild1 || + entityClass === OrgChild2 || + entityClass === OrgChild3 || + entityClass === OrgChild4) + ) { + draftWhere.orgRootId = draftOrgRootId; + } else if (entityClass === OrgRoot) { + draftWhere.id = draftOrgRootId; + } + + // Build WHERE condition for current nodes + const currentWhere: any = { + orgRevisionId: currentRevisionId, + }; + if ( + currentOrgRootId && + (entityClass === OrgChild1 || + entityClass === OrgChild2 || + entityClass === OrgChild3 || + entityClass === OrgChild4) + ) { + currentWhere.orgRootId = currentOrgRootId; + } else if (entityClass === OrgRoot) { + currentWhere.id = currentOrgRootId; + } + + const [draftNodes, currentNodes] = await Promise.all([ + repository.find({ where: draftWhere }), + repository.find({ where: currentWhere }), + ]); + + // 2. Build lookup maps for efficient matching by ancestorDNA + const draftByDNA = new Map(draftNodes.map((n: any) => [n.ancestorDNA, n])); + const currentByDNA = new Map(currentNodes.map((n: any) => [n.ancestorDNA, n])); + + const mapping: OrgIdMapping = { + byAncestorDNA: new Map(), + byDraftId: new Map(), + }; + + // 3. DELETE: Current nodes not in draft (cascade delete positions first) + const toDelete = currentNodes.filter((curr: any) => !draftByDNA.has(curr.ancestorDNA)); + for (const node of toDelete) { + // Cascade delete positions first + await this.cascadeDeletePositions(queryRunner, node, entityClass); + await queryRunner.manager.delete(entityClass, node.id); + } + + // 4. UPDATE: Nodes that exist in both draft and current (matched by ancestorDNA) + const toUpdate = draftNodes.filter((draft: any) => currentByDNA.has(draft.ancestorDNA)); + for (const draft of toUpdate) { + const current: any = currentByDNA.get(draft.ancestorDNA)!; + + // Build update data with mapped parent IDs + const updateData: any = { + ...draft, + id: current.id, + orgRevisionId: currentRevisionId, + }; + + // Map parent IDs based on entity level + if (entityClass === OrgChild1 && draft.orgRootId && parentMappings) { + updateData.orgRootId = + parentMappings.orgRoot.byDraftId.get(draft.orgRootId) ?? draft.orgRootId; + } else if (entityClass === OrgChild2) { + if (draft.orgRootId && parentMappings) { + updateData.orgRootId = + parentMappings.orgRoot.byDraftId.get(draft.orgRootId) ?? draft.orgRootId; + } + if (draft.orgChild1Id && parentMappings) { + updateData.orgChild1Id = + parentMappings.orgChild1.byDraftId.get(draft.orgChild1Id) ?? draft.orgChild1Id; + } + } else if (entityClass === OrgChild3) { + if (draft.orgRootId && parentMappings) { + updateData.orgRootId = + parentMappings.orgRoot.byDraftId.get(draft.orgRootId) ?? draft.orgRootId; + } + if (draft.orgChild1Id && parentMappings) { + updateData.orgChild1Id = + parentMappings.orgChild1.byDraftId.get(draft.orgChild1Id) ?? draft.orgChild1Id; + } + if (draft.orgChild2Id && parentMappings) { + updateData.orgChild2Id = + parentMappings.orgChild2.byDraftId.get(draft.orgChild2Id) ?? draft.orgChild2Id; + } + } else if (entityClass === OrgChild4) { + if (draft.orgRootId && parentMappings) { + updateData.orgRootId = + parentMappings.orgRoot.byDraftId.get(draft.orgRootId) ?? draft.orgRootId; + } + if (draft.orgChild1Id && parentMappings) { + updateData.orgChild1Id = + parentMappings.orgChild1.byDraftId.get(draft.orgChild1Id) ?? draft.orgChild1Id; + } + if (draft.orgChild2Id && parentMappings) { + updateData.orgChild2Id = + parentMappings.orgChild2.byDraftId.get(draft.orgChild2Id) ?? draft.orgChild2Id; + } + if (draft.orgChild3Id && parentMappings) { + updateData.orgChild3Id = + parentMappings.orgChild3.byDraftId.get(draft.orgChild3Id) ?? draft.orgChild3Id; + } + } + + await queryRunner.manager.update(entityClass, current.id, updateData); + + mapping.byAncestorDNA.set(draft.ancestorDNA, current.id); + mapping.byDraftId.set(draft.id, current.id); + } + + // 5. INSERT: Draft nodes not in current + const toInsert = draftNodes.filter((draft: any) => !currentByDNA.has(draft.ancestorDNA)); + for (const draft of toInsert) { + const newNode: any = queryRunner.manager.create(entityClass, { + ...draft, + id: undefined, + orgRevisionId: currentRevisionId, + }); + + // Map parent IDs based on entity level + if (entityClass === OrgChild1 && draft.orgRootId) { + if (draft.orgRootId === draftOrgRootId) { + newNode.orgRootId = currentOrgRootId; + } else if (parentMappings) { + newNode.orgRootId = parentMappings.orgRoot.byDraftId.get(draft.orgRootId); + } + } else if (entityClass === OrgChild2) { + if (draft.orgRootId) { + if (draft.orgRootId === draftOrgRootId) { + newNode.orgRootId = currentOrgRootId; + } else if (parentMappings) { + newNode.orgRootId = parentMappings.orgRoot.byDraftId.get(draft.orgRootId); + } + } + if (draft.orgChild1Id && parentMappings) { + const mappedChild1Id = parentMappings.orgChild1.byDraftId.get(draft.orgChild1Id); + if (mappedChild1Id) { + newNode.orgChild1Id = mappedChild1Id; + } + } + } else if (entityClass === OrgChild3) { + if (draft.orgRootId) { + if (draft.orgRootId === draftOrgRootId) { + newNode.orgRootId = currentOrgRootId; + } else if (parentMappings) { + newNode.orgRootId = parentMappings.orgRoot.byDraftId.get(draft.orgRootId); + } + } + if (draft.orgChild1Id && parentMappings) { + const mappedChild1Id = parentMappings.orgChild1.byDraftId.get(draft.orgChild1Id); + if (mappedChild1Id) { + newNode.orgChild1Id = mappedChild1Id; + } + } + if (draft.orgChild2Id && parentMappings) { + const mappedChild2Id = parentMappings.orgChild2.byDraftId.get(draft.orgChild2Id); + if (mappedChild2Id) { + newNode.orgChild2Id = mappedChild2Id; + } + } + } else if (entityClass === OrgChild4) { + if (draft.orgRootId) { + if (draft.orgRootId === draftOrgRootId) { + newNode.orgRootId = currentOrgRootId; + } else if (parentMappings) { + newNode.orgRootId = parentMappings.orgRoot.byDraftId.get(draft.orgRootId); + } + } + if (draft.orgChild1Id && parentMappings) { + const mappedChild1Id = parentMappings.orgChild1.byDraftId.get(draft.orgChild1Id); + if (mappedChild1Id) { + newNode.orgChild1Id = mappedChild1Id; + } + } + if (draft.orgChild2Id && parentMappings) { + const mappedChild2Id = parentMappings.orgChild2.byDraftId.get(draft.orgChild2Id); + if (mappedChild2Id) { + newNode.orgChild2Id = mappedChild2Id; + } + } + if (draft.orgChild3Id && parentMappings) { + const mappedChild3Id = parentMappings.orgChild3.byDraftId.get(draft.orgChild3Id); + if (mappedChild3Id) { + newNode.orgChild3Id = mappedChild3Id; + } + } + } + + const saved = await queryRunner.manager.save(newNode); + + mapping.byAncestorDNA.set(draft.ancestorDNA, saved.id); + mapping.byDraftId.set(draft.id, saved.id); + } + + return { + mapping, + counts: { + deleted: toDelete.length, + updated: toUpdate.length, + inserted: toInsert.length, + }, + }; + } + + /** + * Batch version: Sync positions for ALL posMasters in a single operation + * This significantly reduces database round trips for large organizations + */ + private async syncAllPositionsBatch( + queryRunner: any, + posMasterMapping: Map, + _draftRevisionId: string, + _currentRevisionId: string, + ): Promise<{ deleted: number; updated: number; inserted: number }> { + // Extract draft and current posMaster IDs + const draftPosMasterIds = Array.from(posMasterMapping.keys()); + const currentPosMasterIds = Array.from(posMasterMapping.values()).map( + ([currentId]) => currentId, + ); + + if (draftPosMasterIds.length === 0) { + return { deleted: 0, updated: 0, inserted: 0 }; + } + + // Fetch draft PosMasters with relations for history tracking + const draftPosMasters = await queryRunner.manager.find(PosMaster, { + where: { id: In(draftPosMasterIds) }, + relations: ["orgRoot", "orgChild1", "orgChild2", "orgChild3", "orgChild4", "next_holder"], + }); + + // Fetch ALL positions for ALL posMasters in just 2 queries + const [allDraftPositions, allCurrentPositions] = await Promise.all([ + queryRunner.manager.find(Position, { + where: { + posMasterId: In(draftPosMasterIds), + }, + relations: ["posType", "posLevel", "posExecutive"], + order: { orderNo: "ASC" }, + }), + queryRunner.manager.find(Position, { + where: { + posMasterId: In(currentPosMasterIds), + }, + }), + ]); + + // Group positions by posMasterId for processing + const draftPositionsByMaster = new Map(); + for (const pos of allDraftPositions) { + if (!draftPositionsByMaster.has(pos.posMasterId)) { + draftPositionsByMaster.set(pos.posMasterId, []); + } + draftPositionsByMaster.get(pos.posMasterId)!.push(pos); + } + + const currentPositionsByMaster = new Map(); + for (const pos of allCurrentPositions) { + if (!currentPositionsByMaster.has(pos.posMasterId)) { + currentPositionsByMaster.set(pos.posMasterId, []); + } + currentPositionsByMaster.get(pos.posMasterId)!.push(pos); + } + + // Collect all operations + const allToDelete: string[] = []; + const allToDeleteHistory: string[] = []; + const allToUpdate: Array<{ id: string; data: any }> = []; + const allToInsert: Array = []; + const profileUpdates: Map = new Map(); + + // Create a map for quick lookup of draft PosMasters with relations + const draftPosMasterMap = new Map(draftPosMasters.map((pm: PosMaster) => [pm.id, pm])); + + // Collect PosMasterHistory calls for selected positions + const historyCalls: Array<{ + ancestorDNA: string; + profileId: string | null; + historyData: SavePosMasterHistory; + }> = []; + + // Process each posMaster mapping + for (const [draftPosMasterId, [currentPosMasterId, nextHolderId]] of posMasterMapping) { + const draftPositions = draftPositionsByMaster.get(draftPosMasterId) || []; + const currentPositions = currentPositionsByMaster.get(currentPosMasterId) || []; + + // If no draft positions, mark all current positions for deletion + if (draftPositions.length === 0) { + allToDelete.push(...currentPositions.map((p: any) => p.id)); + allToDeleteHistory.push(...currentPositions.map((p: any) => p.ancestorDNA)); + continue; + } + + // Build map for tracking + const currentByOrderNo = new Map(currentPositions.map((p: any) => [p.orderNo, p])); + const draftOrderNos = new Set(draftPositions.map((p: any) => p.orderNo)); + + // Mark for deletion: current positions not in draft (by orderNo) + for (const currentPos of currentPositions) { + if (!draftOrderNos.has(currentPos.orderNo)) { + allToDelete.push(currentPos.id); + allToDeleteHistory.push(currentPos.ancestorDNA); + } + } + + // Process UPDATE and INSERT + for (const draftPos of draftPositions) { + const current = currentByOrderNo.get(draftPos.orderNo); + + if (current) { + // UPDATE existing position - collect for batch update + allToUpdate.push({ + id: current.id, + data: { + positionName: draftPos.positionName, + positionField: draftPos.positionField, + posTypeId: draftPos.posTypeId, + posLevelId: draftPos.posLevelId, + posExecutiveId: draftPos.posExecutiveId, + positionExecutiveField: draftPos.positionExecutiveField, + positionArea: draftPos.positionArea, + isSpecial: draftPos.isSpecial, + orderNo: draftPos.orderNo, + positionIsSelected: draftPos.positionIsSelected, + lastUpdateFullName: draftPos.lastUpdateFullName, + lastUpdatedAt: new Date(), + }, + }); + } else { + // INSERT new position - collect for batch insert + allToInsert.push({ + ...draftPos, + id: undefined, + posMasterId: currentPosMasterId, + }); + } + + // Collect history data for the selected position + const draftPosMaster = draftPosMasterMap.get(draftPosMasterId) as any; + + // Collect profile update for the selected position + // ถ้าไม่ใช่ตำแหน่งนั่งทับ (isSit = false) ถึงจะอัพเดทตำแหน่งในทะเบียนประวัติ + if (nextHolderId != null && draftPos.positionIsSelected && !draftPosMaster?.isSit) { + profileUpdates.set(nextHolderId, { + position: draftPos.positionName, + posTypeId: draftPos.posTypeId, + posLevelId: draftPos.posLevelId, + }); + if (draftPosMaster && draftPosMaster.ancestorDNA) { + // Find the selected position from draft positions + const selectedPos = + draftPositions.find((p) => p.positionIsSelected === true) || draftPos; + historyCalls.push({ + ancestorDNA: draftPosMaster.ancestorDNA, + profileId: nextHolderId, + historyData: { + prefix: draftPosMaster.next_holder?.prefix ?? null, + firstName: draftPosMaster.next_holder?.firstName ?? null, + lastName: draftPosMaster.next_holder?.lastName ?? null, + position: selectedPos.positionName ?? null, + posType: (selectedPos as any).posType?.posTypeName ?? null, + posLevel: (selectedPos as any).posLevel?.posLevelName ?? null, + posExecutive: (selectedPos as any).posExecutive?.posExecutiveName ?? null, + profileId: nextHolderId, + rootDnaId: draftPosMaster.orgRoot?.ancestorDNA ?? null, + child1DnaId: draftPosMaster.orgChild1?.ancestorDNA ?? null, + child2DnaId: draftPosMaster.orgChild2?.ancestorDNA ?? null, + child3DnaId: draftPosMaster.orgChild3?.ancestorDNA ?? null, + child4DnaId: draftPosMaster.orgChild4?.ancestorDNA ?? null, + shortName: + [ + draftPosMaster.orgChild4?.orgChild4ShortName, + draftPosMaster.orgChild3?.orgChild3ShortName, + draftPosMaster.orgChild2?.orgChild2ShortName, + draftPosMaster.orgChild1?.orgChild1ShortName, + draftPosMaster.orgRoot?.orgRootShortName, + ].find((s) => typeof s === "string" && s.trim().length > 0) ?? null, + posMasterNoPrefix: draftPosMaster.posMasterNoPrefix ?? null, + posMasterNo: draftPosMaster.posMasterNo ?? null, + posMasterNoSuffix: draftPosMaster.posMasterNoSuffix ?? null, + }, + }); + } + } + } + } + + // Execute bulk operations + let deletedCount = 0; + let updatedCount = 0; + let insertedCount = 0; + + // Bulk DELETE + if (allToDelete.length > 0) { + await queryRunner.manager.delete(Position, allToDelete); + const deleteOps = allToDeleteHistory.map((ancestorDNA) => ({ + posMasterDnaId: ancestorDNA, + profileId: null, + pm: null, + })); + await BatchSavePosMasterHistoryOfficer(queryRunner, deleteOps); + deletedCount = allToDelete.length; + } + + // Bulk UPDATE (batch by 100 to avoid query size limits) + if (allToUpdate.length > 0) { + const batchSize = 100; + for (let i = 0; i < allToUpdate.length; i += batchSize) { + const batch = allToUpdate.slice(i, i + batchSize); + await Promise.all( + batch.map(({ id, data }) => queryRunner.manager.update(Position, id, data)), + ); + } + updatedCount = allToUpdate.length; + } + + // Bulk INSERT + if (allToInsert.length > 0) { + const batchSize = 100; + for (let i = 0; i < allToInsert.length; i += batchSize) { + const batch = allToInsert.slice(i, i + batchSize); + await queryRunner.manager.save(Position, batch); + } + insertedCount = allToInsert.length; + } + + // Bulk UPDATE profiles + if (profileUpdates.size > 0) { + const profileUpdateEntries = Array.from(profileUpdates.entries()); + await Promise.all( + profileUpdateEntries.map(([profileId, data]) => + queryRunner.manager.update(Profile, profileId, data), + ), + ); + } + + // Save PosMasterHistory for updated positions + if (historyCalls.length > 0) { + await BatchSavePosMasterHistoryOfficer( + queryRunner, + historyCalls.map(({ ancestorDNA, profileId, historyData }) => ({ + posMasterDnaId: ancestorDNA, + profileId, + pm: historyData, + })), + ); + } + + return { deleted: deletedCount, updated: updatedCount, inserted: insertedCount }; + } + + /** + * Helper: Sum all counts from a map (for root level total) + */ + private sumAllCounts(map: Map): number { + let sum = 0; + for (const value of map.values()) { + sum += value.totalCount; + } + return sum; + } + + /** + * Helper: Sum all vacant counts from a map + */ + private sumAllVacantCounts( + map: Map, + isDraft: boolean, + ): number { + let sum = 0; + for (const value of map.values()) { + sum += isDraft ? value.nextVacantCount : value.currentVacantCount; + } + return sum; + } + + /** + * Helper: Build orgRoots children array with pre-fetched counts + */ + private buildOrgRoots( + orgRoots: OrgRoot[], + positionCounts: PositionCountsByNode, + isDraft: boolean, + ) { + if (!orgRoots) return []; + return orgRoots + .sort((a, b) => a.orgRootOrder - b.orgRootOrder) + .map((orgRoot) => { + const counts = getPositionCount(positionCounts.orgRootMap, orgRoot.id); + return { + departmentName: orgRoot.orgRootName, + deptID: orgRoot.id, + type: 1, + totalPositionCount: counts.totalCount, + totalPositionVacant: isDraft ? counts.nextVacantCount : counts.currentVacantCount, + children: this.buildOrgChild1s(orgRoot.orgChild1s, positionCounts, isDraft), + }; + }); + } + + /** + * Helper: Build orgChild1s children array with pre-fetched counts + */ + private buildOrgChild1s( + orgChild1s: OrgChild1[], + positionCounts: PositionCountsByNode, + isDraft: boolean, + ) { + if (!orgChild1s) return []; + return orgChild1s + .sort((a, b) => a.orgChild1Order - b.orgChild1Order) + .map((orgChild1) => { + const counts = getPositionCount(positionCounts.orgChild1Map, orgChild1.id); + return { + departmentName: orgChild1.orgChild1Name, + deptID: orgChild1.id, + type: 2, + totalPositionCount: counts.totalCount, + totalPositionVacant: isDraft ? counts.nextVacantCount : counts.currentVacantCount, + children: this.buildOrgChild2s(orgChild1.orgChild2s, positionCounts, isDraft), + }; + }); + } + + /** + * Helper: Build orgChild2s children array with pre-fetched counts + */ + private buildOrgChild2s( + orgChild2s: OrgChild2[], + positionCounts: PositionCountsByNode, + isDraft: boolean, + ) { + if (!orgChild2s) return []; + return orgChild2s + .sort((a, b) => a.orgChild2Order - b.orgChild2Order) + .map((orgChild2) => { + const counts = getPositionCount(positionCounts.orgChild2Map, orgChild2.id); + return { + departmentName: orgChild2.orgChild2Name, + deptID: orgChild2.id, + type: 3, + totalPositionCount: counts.totalCount, + totalPositionVacant: isDraft ? counts.nextVacantCount : counts.currentVacantCount, + children: this.buildOrgChild3s(orgChild2.orgChild3s, positionCounts, isDraft), + }; + }); + } + + /** + * Helper: Build orgChild3s children array with pre-fetched counts + */ + private buildOrgChild3s( + orgChild3s: OrgChild3[], + positionCounts: PositionCountsByNode, + isDraft: boolean, + ) { + if (!orgChild3s) return []; + return orgChild3s + .sort((a, b) => a.orgChild3Order - b.orgChild3Order) + .map((orgChild3) => { + const counts = getPositionCount(positionCounts.orgChild3Map, orgChild3.id); + return { + departmentName: orgChild3.orgChild3Name, + deptID: orgChild3.id, + type: 4, + totalPositionCount: counts.totalCount, + totalPositionVacant: isDraft ? counts.nextVacantCount : counts.currentVacantCount, + children: this.buildOrgChild4s(orgChild3.orgChild4s, positionCounts, isDraft), + }; + }); + } + + /** + * Helper: Build orgChild4s children array with pre-fetched counts (leaf nodes) + */ + private buildOrgChild4s( + orgChild4s: OrgChild4[], + positionCounts: PositionCountsByNode, + isDraft: boolean, + ) { + if (!orgChild4s) return []; + return orgChild4s + .sort((a, b) => a.orgChild4Order - b.orgChild4Order) + .map((orgChild4) => { + const counts = getPositionCount(positionCounts.orgChild4Map, orgChild4.id); + return { + departmentName: orgChild4.orgChild4Name, + deptID: orgChild4.id, + type: 5, + totalPositionCount: counts.totalCount, + totalPositionVacant: isDraft ? counts.nextVacantCount : counts.currentVacantCount, + }; + }); + } } diff --git a/src/controllers/OrganizationDotnetController.ts b/src/controllers/OrganizationDotnetController.ts index 0c8e4d9f..4277a917 100644 --- a/src/controllers/OrganizationDotnetController.ts +++ b/src/controllers/OrganizationDotnetController.ts @@ -1,45 +1,42 @@ import { + Body, Controller, + Get, + Path, Post, Put, + Request, + Response, Route, Security, - Tags, - Body, - Path, - Request, SuccessResponse, - Response, - Get, + Tags, } from "tsoa"; -import { AppDataSource } from "../database/data-source"; -import HttpSuccess from "../interfaces/http-success"; -import HttpStatus from "../interfaces/http-status"; -import HttpError from "../interfaces/http-error"; -import { RequestWithUser } from "../middlewares/user"; -import { Profile } from "../entities/Profile"; import { And, Between, Brackets, In, IsNull, LessThanOrEqual, Not } from "typeorm"; -import { OrgRevision } from "../entities/OrgRevision"; -import { OrgRoot } from "../entities/OrgRoot"; +import { AppDataSource } from "../database/data-source"; +import { Assign } from "../entities/Assign"; +import { EmployeePosDict } from "../entities/EmployeePosDict"; +import { EmployeePosMaster } from "../entities/EmployeePosMaster"; import { OrgChild1 } from "../entities/OrgChild1"; import { OrgChild2 } from "../entities/OrgChild2"; import { OrgChild3 } from "../entities/OrgChild3"; import { OrgChild4 } from "../entities/OrgChild4"; -import { ProfileEmployee } from "../entities/ProfileEmployee"; +import { OrgRevision } from "../entities/OrgRevision"; +import { OrgRoot } from "../entities/OrgRoot"; import { Position } from "../entities/Position"; -import { Insignia } from "../entities/Insignia"; -import { CreateProfileInsignia, ProfileInsignia } from "../entities/ProfileInsignia"; import { PosMaster } from "../entities/PosMaster"; -import { EmployeePosMaster } from "../entities/EmployeePosMaster"; -import { EmployeePosDict } from "../entities/EmployeePosDict"; -import { calculateRetireLaw } from "../interfaces/utils"; -import Extension from "../interfaces/extension"; import { PosMasterHistory } from "../entities/PosMasterHistory"; -import { PosMasterEmployeeHistory } from "../entities/PosMasterEmployeeHistory"; -import { PosMasterAssign } from "../entities/PosMasterAssign"; -import { Assign } from "../entities/Assign"; -import { ProfileSalary } from "../entities/ProfileSalary"; +import { Profile } from "../entities/Profile"; import { ProfileEducation } from "../entities/ProfileEducation"; +import { ProfileEmployee } from "../entities/ProfileEmployee"; +import { CreateProfileInsignia, ProfileInsignia } from "../entities/ProfileInsignia"; +import { ProfileSalary } from "../entities/ProfileSalary"; +import Extension from "../interfaces/extension"; +import HttpError from "../interfaces/http-error"; +import HttpStatus from "../interfaces/http-status"; +import HttpSuccess from "../interfaces/http-success"; +import { calculateRetireLaw } from "../interfaces/utils"; +import { RequestWithUser } from "../middlewares/user"; @Route("api/v1/org/dotnet") @Tags("Dotnet") @Security("bearerAuth") @@ -60,12 +57,9 @@ export class OrganizationDotnetController extends Controller { private positionRepository = AppDataSource.getRepository(Position); private posMasterRepository = AppDataSource.getRepository(PosMaster); private posMasterHistoryRepository = AppDataSource.getRepository(PosMasterHistory); - private posMasterEmployeeHistoryRepository = - AppDataSource.getRepository(PosMasterEmployeeHistory); private empPosMasterRepository = AppDataSource.getRepository(EmployeePosMaster); private insigniaRepo = AppDataSource.getRepository(ProfileInsignia); private employeePosDictRepository = AppDataSource.getRepository(EmployeePosDict); - private posMasterAssignRepo = AppDataSource.getRepository(PosMasterAssign); private assignRepository = AppDataSource.getRepository(Assign); private salaryRepo = AppDataSource.getRepository(ProfileSalary); private educationRepo = AppDataSource.getRepository(ProfileEducation); @@ -105,10 +99,40 @@ export class OrganizationDotnetController extends Controller { node?: number | null; page: number; pageSize: number; + selectedNodeId?: string | null; + selectedNode?: number | null; }, ) { let condition = "1=1"; let conditionParams = {}; + + let selectedNodeCondition = "1=1"; + let selectedNodeConditionParams = {}; + + if (body.selectedNodeId && body.selectedNode != null) { + switch (body.selectedNode) { + case 0: + selectedNodeCondition = "orgRoot.ancestorDNA = :selectedNodeId"; + break; + case 1: + selectedNodeCondition = "orgChild1.ancestorDNA = :selectedNodeId"; + break; + case 2: + selectedNodeCondition = "orgChild2.ancestorDNA = :selectedNodeId"; + break; + case 3: + selectedNodeCondition = "orgChild3.ancestorDNA = :selectedNodeId"; + break; + case 4: + selectedNodeCondition = "orgChild4.ancestorDNA = :selectedNodeId"; + break; + default: + selectedNodeCondition = "1=1"; + break; + } + selectedNodeConditionParams = { selectedNodeId: body.selectedNodeId }; + } + if (body.role === "CHILD") { switch (body.node) { case 0: @@ -157,8 +181,8 @@ export class OrganizationDotnetController extends Controller { condition = "orgRoot.ancestorDNA = :nodeId"; conditionParams = { nodeId: body.nodeId }; } else if (body.role === "PARENT") { - condition = "orgRoot.ancestorDNA = :nodeId AND current_holders.orgChild1 IS NOT NULL"; - conditionParams = { nodeId: body.nodeId }; + // condition = "orgRoot.ancestorDNA = :nodeId AND current_holders.orgChild1 IS NOT NULL"; + // conditionParams = { nodeId: body.nodeId }; } else if (body.role === "NORMAL") { switch (body.node) { case 0: @@ -195,6 +219,7 @@ export class OrganizationDotnetController extends Controller { .leftJoinAndSelect("profile.posType", "posType") .leftJoinAndSelect("profile.current_holders", "current_holders") .leftJoinAndSelect("current_holders.orgRoot", "orgRoot") + .leftJoinAndSelect("current_holders.orgRevision", "orgRevision") .leftJoinAndSelect("current_holders.orgChild1", "orgChild1") .leftJoinAndSelect("current_holders.orgChild2", "orgChild2") .leftJoinAndSelect("current_holders.orgChild3", "orgChild3") @@ -214,18 +239,60 @@ export class OrganizationDotnetController extends Controller { }), ) .andWhere(condition, conditionParams) + .andWhere(selectedNodeCondition, selectedNodeConditionParams) + .select([ "profile.id", "profile.citizenId", "profile.prefix", "profile.firstName", "profile.lastName", + "current_holders", + "orgRevision", + "orgRoot", + "orgChild1", + "orgChild2", + "orgChild3", + "orgChild4", ]) .skip((body.page - 1) * body.pageSize) .take(body.pageSize) .getManyAndCount(); - return new HttpSuccess({ data: profiles, total: total }); + const mapProfiles = profiles.map((profile) => ({ + id: profile.id, + citizenId: profile.citizenId, + prefix: profile.prefix, + firstName: profile.firstName, + lastName: profile.lastName, + rootDnaId: + profile?.current_holders?.find( + (x) => + x.orgRevision?.id === findRevision.id && x.orgRevision?.orgRevisionIsDraft === false, + )?.orgRoot?.ancestorDNA ?? null, + child1DnaId: + profile?.current_holders?.find( + (x) => + x.orgRevision?.id === findRevision.id && x.orgRevision?.orgRevisionIsDraft === false, + )?.orgChild1?.ancestorDNA ?? null, + child2DnaId: + profile?.current_holders?.find( + (x) => + x.orgRevision?.id === findRevision.id && x.orgRevision?.orgRevisionIsDraft === false, + )?.orgChild2?.ancestorDNA ?? null, + child3DnaId: + profile?.current_holders?.find( + (x) => + x.orgRevision?.id === findRevision.id && x.orgRevision?.orgRevisionIsDraft === false, + )?.orgChild3?.ancestorDNA ?? null, + child4DnaId: + profile?.current_holders?.find( + (x) => + x.orgRevision?.id === findRevision.id && x.orgRevision?.orgRevisionIsDraft === false, + )?.orgChild4?.ancestorDNA ?? null, + })); + + return new HttpSuccess({ data: mapProfiles, total: total }); } /** @@ -298,8 +365,8 @@ export class OrganizationDotnetController extends Controller { condition = "orgRoot.ancestorDNA = :nodeId"; conditionParams = { nodeId: body.nodeId }; } else if (body.role === "PARENT") { - condition = "orgRoot.ancestorDNA = :nodeId AND current_holders.orgChild1 IS NOT NULL"; - conditionParams = { nodeId: body.nodeId }; + // condition = "orgRoot.ancestorDNA = :nodeId AND current_holders.orgChild1 IS NOT NULL"; + // conditionParams = { nodeId: body.nodeId }; } else if (body.role === "NORMAL") { switch (body.node) { case 0: @@ -337,6 +404,7 @@ export class OrganizationDotnetController extends Controller { .leftJoinAndSelect("profile.profileSalary", "profileSalary") .leftJoinAndSelect("profile.current_holders", "current_holders") .leftJoinAndSelect("current_holders.orgRoot", "orgRoot") + .leftJoinAndSelect("current_holders.orgRevision", "orgRevision") .leftJoinAndSelect("current_holders.orgChild1", "orgChild1") .leftJoinAndSelect("current_holders.orgChild2", "orgChild2") .leftJoinAndSelect("current_holders.orgChild3", "orgChild3") @@ -362,12 +430,52 @@ export class OrganizationDotnetController extends Controller { "profile.prefix", "profile.firstName", "profile.lastName", + "current_holders", + "orgRevision", + "orgRoot", + "orgChild1", + "orgChild2", + "orgChild3", + "orgChild4", ]) .skip((body.page - 1) * body.pageSize) .take(body.pageSize) .getManyAndCount(); - return new HttpSuccess({ data: profileEmp, total: total }); + const mapProfiles = profileEmp.map((profile) => ({ + id: profile.id, + citizenId: profile.citizenId, + prefix: profile.prefix, + firstName: profile.firstName, + lastName: profile.lastName, + rootDnaId: + profile?.current_holders?.find( + (x) => + x.orgRevision?.id === findRevision.id && x.orgRevision?.orgRevisionIsDraft === false, + )?.orgRoot?.ancestorDNA ?? null, + child1DnaId: + profile?.current_holders?.find( + (x) => + x.orgRevision?.id === findRevision.id && x.orgRevision?.orgRevisionIsDraft === false, + )?.orgChild1?.ancestorDNA ?? null, + child2DnaId: + profile?.current_holders?.find( + (x) => + x.orgRevision?.id === findRevision.id && x.orgRevision?.orgRevisionIsDraft === false, + )?.orgChild2?.ancestorDNA ?? null, + child3DnaId: + profile?.current_holders?.find( + (x) => + x.orgRevision?.id === findRevision.id && x.orgRevision?.orgRevisionIsDraft === false, + )?.orgChild3?.ancestorDNA ?? null, + child4DnaId: + profile?.current_holders?.find( + (x) => + x.orgRevision?.id === findRevision.id && x.orgRevision?.orgRevisionIsDraft === false, + )?.orgChild4?.ancestorDNA ?? null, + })); + + return new HttpSuccess({ data: mapProfiles, total: total }); } /** @@ -404,6 +512,40 @@ export class OrganizationDotnetController extends Controller { return new HttpSuccess(orgRoot); } + /** + * API Get ProfileId + * + * @summary Get ProfileId + * + */ + @Get("get-profileId") + async getProfileInbox(@Request() request: { user: Record }) { + let profile: any; + //OFF + profile = await this.profileRepo.findOne({ + where: { keycloak: request.user.sub }, + select: { id: true }, + }); + //EMP + if (!profile) { + profile = await this.profileEmpRepo.findOne({ + where: { keycloak: request.user.sub }, + select: { id: true }, + }); + if (!profile) { + if (request.user.role.includes("SUPER_ADMIN")) { + return new HttpSuccess(null); + } else { + throw new HttpError(HttpStatus.NOT_FOUND, "ไม่พบข้อมูลบุคคลนี้ในระบบ"); + } + } + } + const result = { + profileId: profile ? profile.id : null, + }; + return new HttpSuccess(result); + } + /** * 3. API Get Profile จาก keycloak id * @@ -1009,8 +1151,8 @@ export class OrganizationDotnetController extends Controller { let positionLeaveName = profile.posType != null && - profile.posLevel != null && - (profile.posType.posTypeName == "บริหาร" || profile.posType.posTypeName == "อำนวยการ") + profile.posLevel != null && + (profile.posType.posTypeName == "บริหาร" || profile.posType.posTypeName == "อำนวยการ") ? `${profile.posType?.posTypeName ?? ""}${profile.posLevel?.posLevelName ?? ""}` : profile.posLevel?.posLevelName ?? null; const _profileCurrent = profile?.current_holders?.find( @@ -1200,7 +1342,8 @@ export class OrganizationDotnetController extends Controller { profileInsignia: profile.profileInsignias.length > 0 ? profile.profileInsignias[0] : null, profileType: "OFFICER", positionLeaveName: positionLeaveName, - posExecutiveName: position == null || position.posExecutive == null + posExecutiveName: + position == null || position.posExecutive == null ? null : position.posExecutive.posExecutiveName, oc: oc, @@ -1212,8 +1355,8 @@ export class OrganizationDotnetController extends Controller { @Get("keycloak/{keycloakId}") async GetProfileByKeycloakIdAsync(@Path() keycloakId: string) { /* ========================= - * 1. Load profile - * ========================= */ + * 1. Load profile + * ========================= */ const profile = await this.profileRepo.findOne({ where: { keycloak: keycloakId }, relations: { @@ -1261,7 +1404,7 @@ export class OrganizationDotnetController extends Controller { }); if (!profile) throw new HttpError(HttpStatus.NOT_FOUND, "ไม่พบข้อมูล"); const currentHolder = profile.current_holders?.find( - x => + (x) => x.orgRevision?.orgRevisionIsDraft === false && x.orgRevision?.orgRevisionIsCurrent === true, ); @@ -1310,8 +1453,7 @@ export class OrganizationDotnetController extends Controller { .getOne(); if (pos?.current_holder) { - commanderFullname = - `${pos.current_holder.prefix}${pos.current_holder.firstName} ${pos.current_holder.lastName}`; + commanderFullname = `${pos.current_holder.prefix}${pos.current_holder.firstName} ${pos.current_holder.lastName}`; commanderPositionName = pos.current_holder.position; commanderId = pos.current_holder.id; commanderKeycloak = pos.current_holder.keycloak; @@ -1323,7 +1465,7 @@ export class OrganizationDotnetController extends Controller { order: { commandDateAffect: "DESC" }, }), this.insigniaRepo.findOne({ - where: { profileEmployeeId: profile.id }, + where: { profileEmployeeId: profile.id, isDeleted: false }, order: { receiveDate: "DESC" }, }), ]); @@ -1346,8 +1488,7 @@ export class OrganizationDotnetController extends Controller { const positionLeaveName = profile.posType && profile.posLevel && - (profile.posType.posTypeName === "บริหาร" || - profile.posType.posTypeName === "อำนวยการ") + (profile.posType.posTypeName === "บริหาร" || profile.posType.posTypeName === "อำนวยการ") ? `${profile.posType.posTypeName}${profile.posLevel.posLevelName}` : profile.posLevel?.posLevelName ?? null; @@ -1435,12 +1576,11 @@ export class OrganizationDotnetController extends Controller { } /* ========================================= - * 2. current holder - * ========================================= */ + * 2. current holder + * ========================================= */ const currentHolder = profile.current_holders?.find( - x => - x.orgRevision?.orgRevisionIsDraft === false && - x.orgRevision?.orgRevisionIsCurrent === true, + (x) => + x.orgRevision?.orgRevisionIsDraft === false && x.orgRevision?.orgRevisionIsCurrent === true, ); const org = { @@ -1452,8 +1592,8 @@ export class OrganizationDotnetController extends Controller { }; /* ================================================= - * 3. หา commander - * ================================================= */ + * 3. หา commander + * ================================================= */ let commanderFullname = ""; let commanderPositionName = ""; let commanderId = ""; @@ -1490,30 +1630,29 @@ export class OrganizationDotnetController extends Controller { .getOne(); if (pos?.current_holder) { - commanderFullname = - `${pos.current_holder.prefix}${pos.current_holder.firstName} ${pos.current_holder.lastName}`; + commanderFullname = `${pos.current_holder.prefix}${pos.current_holder.firstName} ${pos.current_holder.lastName}`; commanderPositionName = pos.current_holder.position; commanderId = pos.current_holder.id; commanderKeycloak = pos.current_holder.keycloak; } /* ========================================= - * 4. salary / insignia (เอาแค่ล่าสุด) - * ========================================= */ + * 4. salary / insignia (เอาแค่ล่าสุด) + * ========================================= */ const [latestSalary, latestInsignia] = await Promise.all([ this.salaryRepo.findOne({ where: { profileId: profile.id }, order: { commandDateAffect: "DESC" }, }), this.insigniaRepo.findOne({ - where: { profileId: profile.id }, + where: { profileId: profile.id, isDeleted: false }, order: { receiveDate: "DESC" }, }), ]); /* ========================================= - * 5. position executive - * ========================================= */ + * 5. position executive + * ========================================= */ const position = await this.positionRepository.findOne({ where: { positionIsSelected: true, @@ -1527,8 +1666,8 @@ export class OrganizationDotnetController extends Controller { }); /* ========================================= - * 6. OC name - * ========================================= */ + * 6. OC name + * ========================================= */ let oc = ""; if (currentHolder) { if (!currentHolder.orgChild1Id) { @@ -1545,19 +1684,18 @@ export class OrganizationDotnetController extends Controller { } /* ========================================= - * 7. position level name - * ========================================= */ + * 7. position level name + * ========================================= */ const positionLeaveName = profile.posType && profile.posLevel && - (profile.posType.posTypeName === "บริหาร" || - profile.posType.posTypeName === "อำนวยการ") + (profile.posType.posTypeName === "บริหาร" || profile.posType.posTypeName === "อำนวยการ") ? `${profile.posType.posTypeName}${profile.posLevel.posLevelName}` : profile.posLevel?.posLevelName ?? null; /* ========================================= - * 8. map response - * ========================================= */ + * 8. map response + * ========================================= */ const mapProfile = { id: profile.id, avatar: profile.avatar, @@ -1645,8 +1783,8 @@ export class OrganizationDotnetController extends Controller { @Get("by-keycloak/{keycloakId}") async NewGetProfileByKeycloakIdAsync(@Path() keycloakId: string) { /* ========================= - * 1. Load profile - * ========================= */ + * 1. Load profile + * ========================= */ const profile = await this.profileRepo.findOne({ where: { keycloak: keycloakId }, relations: { @@ -1682,7 +1820,7 @@ export class OrganizationDotnetController extends Controller { }); if (!profile) throw new HttpError(HttpStatus.NOT_FOUND, "ไม่พบข้อมูล"); const currentHolder = profile.current_holders?.find( - x => + (x) => x.orgRevision?.orgRevisionIsDraft === false && x.orgRevision?.orgRevisionIsCurrent === true, ); @@ -1736,9 +1874,10 @@ export class OrganizationDotnetController extends Controller { mouthSalaryAmount: profile.mouthSalaryAmount, posType: profile.posType?.posTypeName ?? null, - posLevel: profile.posType?.posTypeShortName == null && profile.posLevel?.posLevelName == null - ? null - : `${profile.posType?.posTypeShortName} ${profile.posLevel?.posLevelName}`, + posLevel: + profile.posType?.posTypeShortName == null && profile.posLevel?.posLevelName == null + ? null + : `${profile.posType?.posTypeShortName} ${profile.posLevel?.posLevelName}`, oc, root: currentHolder?.orgRoot?.orgRootName ?? null, @@ -1762,17 +1901,16 @@ export class OrganizationDotnetController extends Controller { } /* ========================================= - * 2. current holder - * ========================================= */ + * 2. current holder + * ========================================= */ const currentHolder = profile.current_holders?.find( - x => - x.orgRevision?.orgRevisionIsDraft === false && - x.orgRevision?.orgRevisionIsCurrent === true, + (x) => + x.orgRevision?.orgRevisionIsDraft === false && x.orgRevision?.orgRevisionIsCurrent === true, ); /* ========================================= - * 5. position executive - * ========================================= */ + * 5. position executive + * ========================================= */ const position = await this.positionRepository.findOne({ where: { positionIsSelected: true, @@ -1786,8 +1924,8 @@ export class OrganizationDotnetController extends Controller { }); /* ========================================= - * 6. OC name - * ========================================= */ + * 6. OC name + * ========================================= */ let oc = ""; if (currentHolder) { if (!currentHolder.orgChild1Id) { @@ -1804,19 +1942,18 @@ export class OrganizationDotnetController extends Controller { } /* ========================================= - * 7. position level name - * ========================================= */ + * 7. position level name + * ========================================= */ const positionLeaveName = profile.posType && profile.posLevel && - (profile.posType.posTypeName === "บริหาร" || - profile.posType.posTypeName === "อำนวยการ") + (profile.posType.posTypeName === "บริหาร" || profile.posType.posTypeName === "อำนวยการ") ? `${profile.posType.posTypeName}${profile.posLevel.posLevelName}` : profile.posLevel?.posLevelName ?? null; /* ========================================= - * 8. map response - * ========================================= */ + * 8. map response + * ========================================= */ const mapProfile = { profileType: "OFFICER", id: profile.id, @@ -1849,7 +1986,7 @@ export class OrganizationDotnetController extends Controller { amount: profile.amount, positionSalaryAmount: profile.positionSalaryAmount, mouthSalaryAmount: profile.mouthSalaryAmount, - + posLevel: profile.posLevel?.posLevelName ?? null, posType: profile.posType?.posTypeName ?? null, posExecutiveName: position?.posExecutive?.posExecutiveName ?? null, @@ -1876,6 +2013,422 @@ export class OrganizationDotnetController extends Controller { return new HttpSuccess(mapProfile); } + // เพิ่มที่อยู่ปัจจุบัน + ตำแหน่งหัวหน้า + @Get("by-keycloak2/{keycloakId}") + async NewGetProfileByKeycloak2IdAsync(@Path() keycloakId: string) { + /* ========================= + * 1. Load profile + * ========================= */ + const profile = await this.profileRepo.findOne({ + where: { keycloak: keycloakId }, + relations: { + posLevel: true, + posType: true, + currentProvince: true, + currentDistrict: true, + currentSubDistrict: true, + current_holders: { + orgRevision: true, + orgRoot: true, + orgChild1: true, + orgChild2: true, + orgChild3: true, + orgChild4: true, + }, + }, + }); + let commanderFullname = ""; + let commanderPositionName = ""; + let commanderId = ""; + // Employee + if (!profile) { + const profile = await this.profileEmpRepo.findOne({ + where: { keycloak: keycloakId }, + relations: { + posLevel: true, + posType: true, + currentProvince: true, + currentDistrict: true, + currentSubDistrict: true, + current_holders: { + orgRevision: true, + orgRoot: true, + orgChild1: true, + orgChild2: true, + orgChild3: true, + orgChild4: true, + }, + }, + }); + if (!profile) throw new HttpError(HttpStatus.NOT_FOUND, "ไม่พบข้อมูล"); + const currentHolder = profile.current_holders?.find( + (x) => + x.orgRevision?.orgRevisionIsDraft === false && + x.orgRevision?.orgRevisionIsCurrent === true, + ); + + const pos = await this.empPosMasterRepository + .createQueryBuilder("pos") + .leftJoinAndSelect("pos.current_holder", "holder") + .leftJoin("pos.orgRevision", "rev") + .where("rev.orgRevisionIsCurrent = true") + .andWhere("rev.orgRevisionIsDraft = false") + .andWhere("pos.isDirector = true") + .andWhere("pos.current_holderId IS NOT NULL") + .andWhere("pos.orgRootId = :root", { root: currentHolder?.orgRootId }) + .andWhere( + `(pos.orgChild1Id = :c1 OR pos.orgChild1Id IS NULL) + AND (pos.orgChild2Id = :c2 OR pos.orgChild2Id IS NULL) + AND (pos.orgChild3Id = :c3 OR pos.orgChild3Id IS NULL) + AND (pos.orgChild4Id = :c4 OR pos.orgChild4Id IS NULL)`, + { + c1: currentHolder?.orgChild1Id, + c2: currentHolder?.orgChild2Id, + c3: currentHolder?.orgChild3Id, + c4: currentHolder?.orgChild4Id, + }, + ) + .orderBy( + `(CASE WHEN pos.orgChild4Id IS NULL THEN 1 ELSE 0 END) + + (CASE WHEN pos.orgChild3Id IS NULL THEN 1 ELSE 0 END) + + (CASE WHEN pos.orgChild2Id IS NULL THEN 1 ELSE 0 END) + + (CASE WHEN pos.orgChild1Id IS NULL THEN 1 ELSE 0 END)`, + "ASC", + ) + .getOne(); + + if (pos?.current_holder) { + commanderFullname = `${pos.current_holder.prefix}${pos.current_holder.firstName} ${pos.current_holder.lastName}`; + commanderPositionName = pos.current_holder.position; + commanderId = pos.current_holder.id; + } + + let oc = ""; + if (currentHolder) { + if (!currentHolder.orgChild1Id) { + oc = currentHolder.orgRoot?.orgRootName; + } else if (!currentHolder.orgChild2Id) { + oc = `${currentHolder.orgChild1?.orgChild1Name} ${currentHolder.orgRoot?.orgRootName}`; + } else if (!currentHolder.orgChild3Id) { + oc = `${currentHolder.orgChild2?.orgChild2Name} ${currentHolder.orgChild1?.orgChild1Name} ${currentHolder.orgRoot?.orgRootName}`; + } else if (!currentHolder.orgChild4Id) { + oc = `${currentHolder.orgChild3?.orgChild3Name} ${currentHolder.orgChild2?.orgChild2Name} ${currentHolder.orgChild1?.orgChild1Name} ${currentHolder.orgRoot?.orgRootName}`; + } else { + oc = currentHolder.orgChild4?.orgChild4Name; + } + } + + const mapProfile = { + profileType: "EMPLOYEE", + id: profile.id, + keycloak: profile.keycloak, + prefix: profile.prefix, + firstName: profile.firstName, + lastName: profile.lastName, + citizenId: profile.citizenId, + position: profile.position, + email: profile.email, + phone: profile.phone, + isProbation: profile.isProbation, + isLeave: profile.isLeave, + dateRetire: profile.dateRetire, + dateAppoint: profile.dateAppoint, + dateRetireLaw: profile.dateRetireLaw, + dateStart: profile.dateStart, + govAgeAbsent: profile.govAgeAbsent, + govAgePlus: profile.govAgePlus, + birthDate: profile.birthDate ?? new Date(), + reasonSameDate: profile.reasonSameDate, + telephoneNumber: profile.phone, + nationality: profile.nationality, + gender: profile.gender, + relationship: profile.relationship, + religion: profile.religion, + bloodGroup: profile.bloodGroup, + dutyTimeId: profile.dutyTimeId, + dutyTimeEffectiveDate: profile.dutyTimeEffectiveDate, + amount: profile.amount, + positionSalaryAmount: profile.positionSalaryAmount, + mouthSalaryAmount: profile.mouthSalaryAmount, + + posType: profile.posType?.posTypeName ?? null, + posLevel: + profile.posType?.posTypeShortName == null && profile.posLevel?.posLevelName == null + ? null + : `${profile.posType?.posTypeShortName} ${profile.posLevel?.posLevelName}`, + oc, + + currentAddress: profile.currentAddress, + currentSubDistrict: profile.currentSubDistrict?.name ?? null, + currentDistrict: profile.currentDistrict?.name ?? null, + currentProvince: profile.currentProvince?.name ?? null, + currentZipCode: profile.currentZipCode, + + commander: commanderFullname, + commanderPositionName, + commanderId, + + root: currentHolder?.orgRoot?.orgRootName ?? null, + rootId: currentHolder?.orgRootId ?? null, + rootDnaId: currentHolder?.orgRoot?.ancestorDNA ?? null, + child1: currentHolder?.orgChild1?.orgChild1Name ?? null, + child1Id: currentHolder?.orgChild1Id ?? null, + child1DnaId: currentHolder?.orgChild1?.ancestorDNA ?? null, + child2: currentHolder?.orgChild2?.orgChild2Name ?? null, + child2Id: currentHolder?.orgChild2Id ?? null, + child2DnaId: currentHolder?.orgChild2?.ancestorDNA ?? null, + child3: currentHolder?.orgChild3?.orgChild3Name ?? null, + child3Id: currentHolder?.orgChild3Id ?? null, + child3DnaId: currentHolder?.orgChild3?.ancestorDNA ?? null, + child4: currentHolder?.orgChild4?.orgChild4Name ?? null, + child4Id: currentHolder?.orgChild4Id ?? null, + child4DnaId: currentHolder?.orgChild4?.ancestorDNA ?? null, + }; + + return new HttpSuccess(mapProfile); + } + + /* ========================================= + * 2. current holder + * ========================================= */ + const currentHolder = profile.current_holders?.find( + (x) => + x.orgRevision?.orgRevisionIsDraft === false && x.orgRevision?.orgRevisionIsCurrent === true, + ); + + /* ================================================= + * 3. หา commander + * ================================================= */ + const pos = await this.posMasterRepository + .createQueryBuilder("pos") + .leftJoinAndSelect("pos.current_holder", "holder") + .leftJoin("pos.orgRevision", "rev") + .where("rev.orgRevisionIsCurrent = true") + .andWhere("rev.orgRevisionIsDraft = false") + .andWhere("pos.isDirector = true") + .andWhere("pos.current_holderId IS NOT NULL") + .andWhere("pos.orgRootId = :root", { root: currentHolder?.orgRootId }) + .andWhere( + `(pos.orgChild1Id = :c1 OR pos.orgChild1Id IS NULL) + AND (pos.orgChild2Id = :c2 OR pos.orgChild2Id IS NULL) + AND (pos.orgChild3Id = :c3 OR pos.orgChild3Id IS NULL) + AND (pos.orgChild4Id = :c4 OR pos.orgChild4Id IS NULL)`, + { + c1: currentHolder?.orgChild1Id, + c2: currentHolder?.orgChild2Id, + c3: currentHolder?.orgChild3Id, + c4: currentHolder?.orgChild4Id, + }, + ) + .orderBy( + `(CASE WHEN pos.orgChild4Id IS NULL THEN 1 ELSE 0 END) + + (CASE WHEN pos.orgChild3Id IS NULL THEN 1 ELSE 0 END) + + (CASE WHEN pos.orgChild2Id IS NULL THEN 1 ELSE 0 END) + + (CASE WHEN pos.orgChild1Id IS NULL THEN 1 ELSE 0 END)`, + "ASC", + ) + .getOne(); + + if (pos?.current_holder) { + commanderFullname = `${pos.current_holder.prefix}${pos.current_holder.firstName} ${pos.current_holder.lastName}`; + commanderPositionName = pos.current_holder.position; + commanderId = pos.current_holder.id; + } + + /* ========================================= + * 5. position executive + * ========================================= */ + const position = await this.positionRepository.findOne({ + where: { + positionIsSelected: true, + posMaster: { + orgRevisionId: currentHolder?.orgRevisionId, + current_holderId: profile.id, + }, + }, + order: { createdAt: "DESC" }, + relations: { posExecutive: true }, + }); + + /* ========================================= + * 6. OC name + * ========================================= */ + let oc = ""; + if (currentHolder) { + if (!currentHolder.orgChild1Id) { + oc = currentHolder.orgRoot?.orgRootName; + } else if (!currentHolder.orgChild2Id) { + oc = `${currentHolder.orgChild1?.orgChild1Name} ${currentHolder.orgRoot?.orgRootName}`; + } else if (!currentHolder.orgChild3Id) { + oc = `${currentHolder.orgChild2?.orgChild2Name} ${currentHolder.orgChild1?.orgChild1Name} ${currentHolder.orgRoot?.orgRootName}`; + } else if (!currentHolder.orgChild4Id) { + oc = `${currentHolder.orgChild3?.orgChild3Name} ${currentHolder.orgChild2?.orgChild2Name} ${currentHolder.orgChild1?.orgChild1Name} ${currentHolder.orgRoot?.orgRootName}`; + } else { + oc = currentHolder.orgChild4?.orgChild4Name; + } + } + + /* ========================================= + * 7. position level name + * ========================================= */ + const positionLeaveName = + profile.posType && + profile.posLevel && + (profile.posType.posTypeName === "บริหาร" || profile.posType.posTypeName === "อำนวยการ") + ? `${profile.posType.posTypeName}${profile.posLevel.posLevelName}` + : profile.posLevel?.posLevelName ?? null; + + /* ========================================= + * 8. map response + * ========================================= */ + const mapProfile = { + profileType: "OFFICER", + id: profile.id, + keycloak: profile.keycloak, + prefix: profile.prefix, + firstName: profile.firstName, + lastName: profile.lastName, + citizenId: profile.citizenId, + position: profile.position, + email: profile.email, + phone: profile.phone, + isProbation: profile.isProbation, + isLeave: profile.isLeave, + dateRetire: profile.dateRetire, + dateAppoint: profile.dateAppoint, + dateRetireLaw: profile.dateRetireLaw, + dateStart: profile.dateStart, + govAgeAbsent: profile.govAgeAbsent, + govAgePlus: profile.govAgePlus, + birthDate: profile.birthDate ?? new Date(), + reasonSameDate: profile.reasonSameDate, + telephoneNumber: profile.phone, + nationality: profile.nationality, + gender: profile.gender, + relationship: profile.relationship, + religion: profile.religion, + bloodGroup: profile.bloodGroup, + dutyTimeId: profile.dutyTimeId, + dutyTimeEffectiveDate: profile.dutyTimeEffectiveDate, + amount: profile.amount, + positionSalaryAmount: profile.positionSalaryAmount, + mouthSalaryAmount: profile.mouthSalaryAmount, + + posLevel: profile.posLevel?.posLevelName ?? null, + posType: profile.posType?.posTypeName ?? null, + posExecutiveName: position?.posExecutive?.posExecutiveName ?? null, + positionLeaveName, + oc, + + currentAddress: profile.currentAddress, + currentSubDistrict: profile.currentSubDistrict?.name ?? null, + currentDistrict: profile.currentDistrict?.name ?? null, + currentProvince: profile.currentProvince?.name ?? null, + currentZipCode: profile.currentZipCode, + + commander: commanderFullname, + commanderPositionName, + commanderId, + + root: currentHolder?.orgRoot?.orgRootName ?? null, + rootId: currentHolder?.orgRootId ?? null, + rootDnaId: currentHolder?.orgRoot?.ancestorDNA ?? null, + child1: currentHolder?.orgChild1?.orgChild1Name ?? null, + child1Id: currentHolder?.orgChild1Id ?? null, + child1DnaId: currentHolder?.orgChild1?.ancestorDNA ?? null, + child2: currentHolder?.orgChild2?.orgChild2Name ?? null, + child2Id: currentHolder?.orgChild2Id ?? null, + child2DnaId: currentHolder?.orgChild2?.ancestorDNA ?? null, + child3: currentHolder?.orgChild3?.orgChild3Name ?? null, + child3Id: currentHolder?.orgChild3Id ?? null, + child3DnaId: currentHolder?.orgChild3?.ancestorDNA ?? null, + child4: currentHolder?.orgChild4?.orgChild4Name ?? null, + child4Id: currentHolder?.orgChild4Id ?? null, + child4DnaId: currentHolder?.orgChild4?.ancestorDNA ?? null, + }; + + return new HttpSuccess(mapProfile); + } + + /** + * API Get Profile For Logs + * + * @summary API Get Profile For Logs + * + * @param {string} keycloakId keycloakId profile + */ + @Get("user-logs/{keycloakId}") + async UserLogs(@Path() keycloakId: string) { + /* ========================= + * 1. Load profile + * ========================= */ + const profile = await this.profileRepo.findOne({ + where: { keycloak: keycloakId }, + relations: { + current_holders: { + orgRevision: true, + orgRoot: true, + }, + }, + }); + + // Employee + if (!profile) { + const profile = await this.profileEmpRepo.findOne({ + where: { keycloak: keycloakId }, + relations: { + current_holders: { + orgRevision: true, + orgRoot: true, + }, + }, + }); + if (!profile) throw new HttpError(HttpStatus.NOT_FOUND, "ไม่พบข้อมูล"); + const currentHolder = profile.current_holders?.find( + (x) => + x.orgRevision?.orgRevisionIsDraft === false && + x.orgRevision?.orgRevisionIsCurrent === true, + ); + + const mapProfile = { + profileId: profile.id, + keycloak: profile.keycloak, + prefix: profile.prefix, + firstName: profile.firstName, + lastName: profile.lastName, + citizenId: profile.citizenId, + rootId: currentHolder?.orgRootId ?? null, + rootDnaId: currentHolder?.orgRoot?.ancestorDNA ?? null, + }; + + return new HttpSuccess(mapProfile); + } + + /* ========================================= + * 2. current holder + * ========================================= */ + const currentHolder = profile.current_holders?.find( + (x) => + x.orgRevision?.orgRevisionIsDraft === false && x.orgRevision?.orgRevisionIsCurrent === true, + ); + + /* ========================================= + * 8. map response + * ========================================= */ + const mapProfile = { + profileId: profile.id, + keycloak: profile.keycloak, + prefix: profile.prefix, + firstName: profile.firstName, + lastName: profile.lastName, + citizenId: profile.citizenId, + rootId: currentHolder?.orgRootId ?? null, + rootDnaId: currentHolder?.orgRoot?.ancestorDNA ?? null, + }; + + return new HttpSuccess(mapProfile); + } + /** * 3. API Get Profile จาก profile id * @@ -1889,8 +2442,6 @@ export class OrganizationDotnetController extends Controller { relations: [ "posLevel", "posType", - "profileSalary", - "profileInsignias", "current_holders", "current_holders.orgRevision", "current_holders.orgRoot", @@ -1900,14 +2451,6 @@ export class OrganizationDotnetController extends Controller { "current_holders.orgChild4", ], where: { id: profileId }, - order: { - profileSalary: { - commandDateAffect: "DESC", - }, - profileInsignias: { - receiveDate: "DESC", - }, - }, }); if (!profile) { @@ -1915,8 +2458,6 @@ export class OrganizationDotnetController extends Controller { relations: [ "posLevel", "posType", - "profileSalary", - "profileInsignias", "current_holders", "current_holders.orgRevision", "current_holders.orgRoot", @@ -1926,17 +2467,20 @@ export class OrganizationDotnetController extends Controller { "current_holders.orgChild4", ], where: { id: profileId }, - order: { - profileSalary: { - commandDateAffect: "DESC", - }, - profileInsignias: { - receiveDate: "DESC", - }, - }, }); if (!profile) throw new HttpError(HttpStatus.NOT_FOUND, "ไม่พบข้อมูล"); + const [latestSalary, latestInsignia] = await Promise.all([ + this.salaryRepo.findOne({ + where: { profileEmployeeId: profile.id }, + order: { commandDateAffect: "DESC" }, + }), + this.insigniaRepo.findOne({ + where: { profileEmployeeId: profile.id, isDeleted: false }, + order: { receiveDate: "DESC" }, + }), + ]); + const org = { root: profile?.current_holders?.find( @@ -1970,7 +2514,7 @@ export class OrganizationDotnetController extends Controller { )?.orgChild4?.id ?? null, }; let fullname = ""; - let pos = await this.posMasterRepository.findOne({ + let pos = await this.empPosMasterRepository.findOne({ relations: ["current_holder"], where: { current_holder: Not(IsNull()), @@ -1994,7 +2538,7 @@ export class OrganizationDotnetController extends Controller { " " + pos.current_holder.lastName; } else { - let pos = await this.posMasterRepository.findOne({ + let pos = await this.empPosMasterRepository.findOne({ relations: ["current_holder"], where: { current_holder: Not(IsNull()), @@ -2017,7 +2561,7 @@ export class OrganizationDotnetController extends Controller { " " + pos.current_holder.lastName; } else { - let pos = await this.posMasterRepository.findOne({ + let pos = await this.empPosMasterRepository.findOne({ relations: ["current_holder"], where: { current_holder: Not(IsNull()), @@ -2040,7 +2584,7 @@ export class OrganizationDotnetController extends Controller { " " + pos.current_holder.lastName; } else { - let pos = await this.posMasterRepository.findOne({ + let pos = await this.empPosMasterRepository.findOne({ relations: ["current_holder"], where: { current_holder: Not(IsNull()), @@ -2063,7 +2607,7 @@ export class OrganizationDotnetController extends Controller { " " + pos.current_holder.lastName; } else { - let pos = await this.posMasterRepository.findOne({ + let pos = await this.empPosMasterRepository.findOne({ relations: ["current_holder"], where: { current_holder: Not(IsNull()), @@ -2233,14 +2777,25 @@ export class OrganizationDotnetController extends Controller { ? "" : profile.posType?.posTypeShortName + " ") + (profile.posLevel?.posLevelName ?? ""), posType: profile.posType?.posTypeName ?? null, - profileSalary: profile.profileSalary.length > 0 ? profile.profileSalary[0] : null, - profileInsignia: profile.profileInsignias.length > 0 ? profile.profileInsignias[0] : null, + profileSalary: latestSalary ?? null, + profileInsignia: latestInsignia ?? null, profileType: "EMPLOYEE", }; return new HttpSuccess(mapProfile); } + const [latestSalary, latestInsignia] = await Promise.all([ + this.salaryRepo.findOne({ + where: { profileId: profile.id }, + order: { commandDateAffect: "DESC" }, + }), + this.insigniaRepo.findOne({ + where: { profileId: profile.id, isDeleted: false }, + order: { receiveDate: "DESC" }, + }), + ]); + const org = { root: profile?.current_holders?.find( @@ -2535,13 +3090,14 @@ export class OrganizationDotnetController extends Controller { commander: fullname, posLevel: profile.posLevel?.posLevelName ?? null, posType: profile.posType?.posTypeName ?? null, - profileSalary: profile.profileSalary.length > 0 ? profile.profileSalary[0] : null, - profileInsignia: profile.profileInsignias.length > 0 ? profile.profileInsignias[0] : null, + profileSalary: latestSalary ?? null, + profileInsignia: latestInsignia ?? null, profileType: "OFFICER", }; return new HttpSuccess(mapProfile); } + /** * 3. API Get Profile จาก citizen Id * @@ -3290,26 +3846,26 @@ export class OrganizationDotnetController extends Controller { profile.current_holders.length == 0 ? null : profile.current_holders.find((x) => x.orgRevisionId == findRevision?.id) != null && - profile.current_holders.find((x) => x.orgRevisionId == findRevision?.id)?.orgChild4 != - null + profile.current_holders.find((x) => x.orgRevisionId == findRevision?.id)?.orgChild4 != + null ? `${profile.current_holders.find((x) => x.orgRevisionId == findRevision?.id)?.orgChild4.orgChild4ShortName} ${profile.current_holders.find((x) => x.orgRevisionId == findRevision?.id)?.posMasterNo}` : profile.current_holders.find((x) => x.orgRevisionId == findRevision?.id) != null && - profile.current_holders.find((x) => x.orgRevisionId == findRevision?.id) - ?.orgChild3 != null + profile.current_holders.find((x) => x.orgRevisionId == findRevision?.id) + ?.orgChild3 != null ? `${profile.current_holders.find((x) => x.orgRevisionId == findRevision?.id)?.orgChild3.orgChild3ShortName} ${profile.current_holders.find((x) => x.orgRevisionId == findRevision?.id)?.posMasterNo}` : profile.current_holders.find((x) => x.orgRevisionId == findRevision?.id) != null && - profile.current_holders.find((x) => x.orgRevisionId == findRevision?.id) - ?.orgChild2 != null + profile.current_holders.find((x) => x.orgRevisionId == findRevision?.id) + ?.orgChild2 != null ? `${profile.current_holders.find((x) => x.orgRevisionId == findRevision?.id)?.orgChild2.orgChild2ShortName} ${profile.current_holders.find((x) => x.orgRevisionId == findRevision?.id)?.posMasterNo}` : profile.current_holders.find((x) => x.orgRevisionId == findRevision?.id) != - null && - profile.current_holders.find((x) => x.orgRevisionId == findRevision?.id) - ?.orgChild1 != null + null && + profile.current_holders.find((x) => x.orgRevisionId == findRevision?.id) + ?.orgChild1 != null ? `${profile.current_holders.find((x) => x.orgRevisionId == findRevision?.id)?.orgChild1.orgChild1ShortName} ${profile.current_holders.find((x) => x.orgRevisionId == findRevision?.id)?.posMasterNo}` : profile.current_holders.find((x) => x.orgRevisionId == findRevision?.id) != - null && - profile.current_holders.find((x) => x.orgRevisionId == findRevision?.id) - ?.orgRoot != null + null && + profile.current_holders.find((x) => x.orgRevisionId == findRevision?.id) + ?.orgRoot != null ? `${profile.current_holders.find((x) => x.orgRevisionId == findRevision?.id)?.orgRoot.orgRootShortName} ${profile.current_holders.find((x) => x.orgRevisionId == findRevision?.id)?.posMasterNo}` : null; @@ -3600,26 +4156,26 @@ export class OrganizationDotnetController extends Controller { profile.current_holders.length == 0 ? null : profile.current_holders.find((x) => x.orgRevisionId == findRevision?.id) != null && - profile.current_holders.find((x) => x.orgRevisionId == findRevision?.id)?.orgChild4 != - null + profile.current_holders.find((x) => x.orgRevisionId == findRevision?.id)?.orgChild4 != + null ? `${profile.current_holders.find((x) => x.orgRevisionId == findRevision?.id)?.orgChild4.orgChild4ShortName} ${profile.current_holders.find((x) => x.orgRevisionId == findRevision?.id)?.posMasterNo}` : profile.current_holders.find((x) => x.orgRevisionId == findRevision?.id) != null && - profile.current_holders.find((x) => x.orgRevisionId == findRevision?.id) - ?.orgChild3 != null + profile.current_holders.find((x) => x.orgRevisionId == findRevision?.id) + ?.orgChild3 != null ? `${profile.current_holders.find((x) => x.orgRevisionId == findRevision?.id)?.orgChild3.orgChild3ShortName} ${profile.current_holders.find((x) => x.orgRevisionId == findRevision?.id)?.posMasterNo}` : profile.current_holders.find((x) => x.orgRevisionId == findRevision?.id) != null && - profile.current_holders.find((x) => x.orgRevisionId == findRevision?.id) - ?.orgChild2 != null + profile.current_holders.find((x) => x.orgRevisionId == findRevision?.id) + ?.orgChild2 != null ? `${profile.current_holders.find((x) => x.orgRevisionId == findRevision?.id)?.orgChild2.orgChild2ShortName} ${profile.current_holders.find((x) => x.orgRevisionId == findRevision?.id)?.posMasterNo}` : profile.current_holders.find((x) => x.orgRevisionId == findRevision?.id) != - null && - profile.current_holders.find((x) => x.orgRevisionId == findRevision?.id) - ?.orgChild1 != null + null && + profile.current_holders.find((x) => x.orgRevisionId == findRevision?.id) + ?.orgChild1 != null ? `${profile.current_holders.find((x) => x.orgRevisionId == findRevision?.id)?.orgChild1.orgChild1ShortName} ${profile.current_holders.find((x) => x.orgRevisionId == findRevision?.id)?.posMasterNo}` : profile.current_holders.find((x) => x.orgRevisionId == findRevision?.id) != - null && - profile.current_holders.find((x) => x.orgRevisionId == findRevision?.id) - ?.orgRoot != null + null && + profile.current_holders.find((x) => x.orgRevisionId == findRevision?.id) + ?.orgRoot != null ? `${profile.current_holders.find((x) => x.orgRevisionId == findRevision?.id)?.orgRoot.orgRootShortName} ${profile.current_holders.find((x) => x.orgRevisionId == findRevision?.id)?.posMasterNo}` : null; return { @@ -3721,7 +4277,6 @@ export class OrganizationDotnetController extends Controller { */ @Post("find/employee/position") async GetProfileByPositionEmpAsync( - @Request() req: RequestWithUser, @Body() body: { empPosId: string[]; @@ -3821,26 +4376,26 @@ export class OrganizationDotnetController extends Controller { profile.current_holders.length == 0 ? null : profile.current_holders.find((x) => x.orgRevisionId == findRevision?.id) != null && - profile.current_holders.find((x) => x.orgRevisionId == findRevision?.id)?.orgChild4 != - null + profile.current_holders.find((x) => x.orgRevisionId == findRevision?.id)?.orgChild4 != + null ? `${profile.current_holders.find((x) => x.orgRevisionId == findRevision?.id)?.orgChild4.orgChild4ShortName} ${profile.current_holders.find((x) => x.orgRevisionId == findRevision?.id)?.posMasterNo}` : profile.current_holders.find((x) => x.orgRevisionId == findRevision?.id) != null && - profile.current_holders.find((x) => x.orgRevisionId == findRevision?.id) - ?.orgChild3 != null + profile.current_holders.find((x) => x.orgRevisionId == findRevision?.id) + ?.orgChild3 != null ? `${profile.current_holders.find((x) => x.orgRevisionId == findRevision?.id)?.orgChild3.orgChild3ShortName} ${profile.current_holders.find((x) => x.orgRevisionId == findRevision?.id)?.posMasterNo}` : profile.current_holders.find((x) => x.orgRevisionId == findRevision?.id) != null && - profile.current_holders.find((x) => x.orgRevisionId == findRevision?.id) - ?.orgChild2 != null + profile.current_holders.find((x) => x.orgRevisionId == findRevision?.id) + ?.orgChild2 != null ? `${profile.current_holders.find((x) => x.orgRevisionId == findRevision?.id)?.orgChild2.orgChild2ShortName} ${profile.current_holders.find((x) => x.orgRevisionId == findRevision?.id)?.posMasterNo}` : profile.current_holders.find((x) => x.orgRevisionId == findRevision?.id) != - null && - profile.current_holders.find((x) => x.orgRevisionId == findRevision?.id) - ?.orgChild1 != null + null && + profile.current_holders.find((x) => x.orgRevisionId == findRevision?.id) + ?.orgChild1 != null ? `${profile.current_holders.find((x) => x.orgRevisionId == findRevision?.id)?.orgChild1.orgChild1ShortName} ${profile.current_holders.find((x) => x.orgRevisionId == findRevision?.id)?.posMasterNo}` : profile.current_holders.find((x) => x.orgRevisionId == findRevision?.id) != - null && - profile.current_holders.find((x) => x.orgRevisionId == findRevision?.id) - ?.orgRoot != null + null && + profile.current_holders.find((x) => x.orgRevisionId == findRevision?.id) + ?.orgRoot != null ? `${profile.current_holders.find((x) => x.orgRevisionId == findRevision?.id)?.orgRoot.orgRootShortName} ${profile.current_holders.find((x) => x.orgRevisionId == findRevision?.id)?.posMasterNo}` : null; return { @@ -4088,7 +4643,7 @@ export class OrganizationDotnetController extends Controller { const root = profile.current_holders == null || - profile.current_holders.find((x) => x.orgRevisionId == orgRevisionPublish.id)?.orgRoot == null + profile.current_holders.find((x) => x.orgRevisionId == orgRevisionPublish.id)?.orgRoot == null ? null : profile.current_holders.find((x) => x.orgRevisionId == orgRevisionPublish.id)?.orgRoot; @@ -4132,7 +4687,7 @@ export class OrganizationDotnetController extends Controller { // "current_holders.orgChild3", // "current_holders.orgChild4", // ], - relations:{ + relations: { posType: true, posLevel: true, current_holders: { @@ -4142,8 +4697,8 @@ export class OrganizationDotnetController extends Controller { orgChild2: true, orgChild3: true, orgChild4: true, - } - } + }, + }, }); if (!profile) { throw new HttpError(HttpStatus.NOT_FOUND, "ไม่พบข้อมูลบุคคลนี้ในระบบ"); @@ -4160,41 +4715,41 @@ export class OrganizationDotnetController extends Controller { const posMaster = profile.current_holders == null || - profile.current_holders.length == 0 || - profile.current_holders.find((x) => x.orgRevisionId == orgRevisionPublish.id) == null + profile.current_holders.length == 0 || + profile.current_holders.find((x) => x.orgRevisionId == orgRevisionPublish.id) == null ? null : profile.current_holders.find((x) => x.orgRevisionId == orgRevisionPublish.id); const root = profile.current_holders == null || - profile.current_holders.find((x) => x.orgRevisionId == orgRevisionPublish.id)?.orgRoot == null + profile.current_holders.find((x) => x.orgRevisionId == orgRevisionPublish.id)?.orgRoot == null ? null : profile.current_holders.find((x) => x.orgRevisionId == orgRevisionPublish.id)?.orgRoot; const child1 = profile.current_holders == null || - profile.current_holders.find((x) => x.orgRevisionId == orgRevisionPublish.id)?.orgChild1 == + profile.current_holders.find((x) => x.orgRevisionId == orgRevisionPublish.id)?.orgChild1 == null ? null : profile.current_holders.find((x) => x.orgRevisionId == orgRevisionPublish.id)?.orgChild1; const child2 = profile.current_holders == null || - profile.current_holders.find((x) => x.orgRevisionId == orgRevisionPublish.id)?.orgChild2 == + profile.current_holders.find((x) => x.orgRevisionId == orgRevisionPublish.id)?.orgChild2 == null ? null : profile.current_holders.find((x) => x.orgRevisionId == orgRevisionPublish.id)?.orgChild2; const child3 = profile.current_holders == null || - profile.current_holders.find((x) => x.orgRevisionId == orgRevisionPublish.id)?.orgChild3 == + profile.current_holders.find((x) => x.orgRevisionId == orgRevisionPublish.id)?.orgChild3 == null ? null : profile.current_holders.find((x) => x.orgRevisionId == orgRevisionPublish.id)?.orgChild3; const child4 = profile.current_holders == null || - profile.current_holders.find((x) => x.orgRevisionId == orgRevisionPublish.id)?.orgChild4 == + profile.current_holders.find((x) => x.orgRevisionId == orgRevisionPublish.id)?.orgChild4 == null ? null : profile.current_holders.find((x) => x.orgRevisionId == orgRevisionPublish.id)?.orgChild4; @@ -4420,9 +4975,7 @@ export class OrganizationDotnetController extends Controller { // }), // ); const profile_ = profile.map((item: Profile) => { - const holder = item.current_holders?.find( - (x) => x.orgRevisionId === findRevision?.id, - ); + const holder = item.current_holders?.find((x) => x.orgRevisionId === findRevision?.id); const rootName = holder?.orgRoot?.orgRootName ?? null; @@ -4544,30 +5097,30 @@ export class OrganizationDotnetController extends Controller { item.current_holders.length == 0 ? null : item.current_holders.find((x) => x.orgRevisionId == findRevision?.id)?.orgRoot - ?.orgRootName; + ?.orgRootName; const shortName = item.current_holders.length == 0 ? null : item.current_holders.find((x) => x.orgRevisionId == findRevision?.id) != null && - item.current_holders.find((x) => x.orgRevisionId == findRevision?.id)?.orgChild4 != - null + item.current_holders.find((x) => x.orgRevisionId == findRevision?.id)?.orgChild4 != + null ? `${item.current_holders.find((x) => x.orgRevisionId == findRevision?.id)?.orgChild4.orgChild4ShortName} ${item.current_holders.find((x) => x.orgRevisionId == findRevision?.id)?.posMasterNo}` : item.current_holders.find((x) => x.orgRevisionId == findRevision?.id) != null && - item.current_holders.find((x) => x.orgRevisionId == findRevision?.id) - ?.orgChild3 != null + item.current_holders.find((x) => x.orgRevisionId == findRevision?.id) + ?.orgChild3 != null ? `${item.current_holders.find((x) => x.orgRevisionId == findRevision?.id)?.orgChild3.orgChild3ShortName} ${item.current_holders.find((x) => x.orgRevisionId == findRevision?.id)?.posMasterNo}` : item.current_holders.find((x) => x.orgRevisionId == findRevision?.id) != null && - item.current_holders.find((x) => x.orgRevisionId == findRevision?.id) - ?.orgChild2 != null + item.current_holders.find((x) => x.orgRevisionId == findRevision?.id) + ?.orgChild2 != null ? `${item.current_holders.find((x) => x.orgRevisionId == findRevision?.id)?.orgChild2.orgChild2ShortName} ${item.current_holders.find((x) => x.orgRevisionId == findRevision?.id)?.posMasterNo}` : item.current_holders.find((x) => x.orgRevisionId == findRevision?.id) != null && - item.current_holders.find((x) => x.orgRevisionId == findRevision?.id) - ?.orgChild1 != null + item.current_holders.find((x) => x.orgRevisionId == findRevision?.id) + ?.orgChild1 != null ? `${item.current_holders.find((x) => x.orgRevisionId == findRevision?.id)?.orgChild1.orgChild1ShortName} ${item.current_holders.find((x) => x.orgRevisionId == findRevision?.id)?.posMasterNo}` : item.current_holders.find((x) => x.orgRevisionId == findRevision?.id) != - null && - item.current_holders.find((x) => x.orgRevisionId == findRevision?.id) - ?.orgRoot != null + null && + item.current_holders.find((x) => x.orgRevisionId == findRevision?.id) + ?.orgRoot != null ? `${item.current_holders.find((x) => x.orgRevisionId == findRevision?.id)?.orgRoot.orgRootShortName} ${item.current_holders.find((x) => x.orgRevisionId == findRevision?.id)?.posMasterNo}` : null; @@ -4642,7 +5195,6 @@ export class OrganizationDotnetController extends Controller { */ @Post("keycloak-all-officer") async PostProfileWithKeycloakAllOfficer( - @Request() req: RequestWithUser, @Body() body: { node: number; @@ -4751,25 +5303,25 @@ export class OrganizationDotnetController extends Controller { item.current_holders.length == 0 ? null : item.current_holders.find((x) => x.orgRevisionId == findRevision?.id) != null && - item.current_holders.find((x) => x.orgRevisionId == findRevision?.id)?.orgChild4 != - null + item.current_holders.find((x) => x.orgRevisionId == findRevision?.id)?.orgChild4 != + null ? `${item.current_holders.find((x) => x.orgRevisionId == findRevision?.id)?.orgChild4.orgChild4ShortName}${item.current_holders.find((x) => x.orgRevisionId == findRevision?.id)?.posMasterNo}` : item.current_holders.find((x) => x.orgRevisionId == findRevision?.id) != null && - item.current_holders.find((x) => x.orgRevisionId == findRevision?.id) - ?.orgChild3 != null + item.current_holders.find((x) => x.orgRevisionId == findRevision?.id) + ?.orgChild3 != null ? `${item.current_holders.find((x) => x.orgRevisionId == findRevision?.id)?.orgChild3.orgChild3ShortName} ${item.current_holders.find((x) => x.orgRevisionId == findRevision?.id)?.posMasterNo}` : item.current_holders.find((x) => x.orgRevisionId == findRevision?.id) != null && - item.current_holders.find((x) => x.orgRevisionId == findRevision?.id) - ?.orgChild2 != null + item.current_holders.find((x) => x.orgRevisionId == findRevision?.id) + ?.orgChild2 != null ? `${item.current_holders.find((x) => x.orgRevisionId == findRevision?.id)?.orgChild2.orgChild2ShortName} ${item.current_holders.find((x) => x.orgRevisionId == findRevision?.id)?.posMasterNo}` : item.current_holders.find((x) => x.orgRevisionId == findRevision?.id) != null && - item.current_holders.find((x) => x.orgRevisionId == findRevision?.id) - ?.orgChild1 != null + item.current_holders.find((x) => x.orgRevisionId == findRevision?.id) + ?.orgChild1 != null ? `${item.current_holders.find((x) => x.orgRevisionId == findRevision?.id)?.orgChild1.orgChild1ShortName} ${item.current_holders.find((x) => x.orgRevisionId == findRevision?.id)?.posMasterNo}` : item.current_holders.find((x) => x.orgRevisionId == findRevision?.id) != - null && - item.current_holders.find((x) => x.orgRevisionId == findRevision?.id) - ?.orgRoot != null + null && + item.current_holders.find((x) => x.orgRevisionId == findRevision?.id) + ?.orgRoot != null ? `${item.current_holders.find((x) => x.orgRevisionId == findRevision?.id)?.orgRoot.orgRootShortName} ${item.current_holders.find((x) => x.orgRevisionId == findRevision?.id)?.posMasterNo}` : null; const Oc = @@ -4813,7 +5365,6 @@ export class OrganizationDotnetController extends Controller { */ @Post("keycloak-all-officer/date") async PostProfileWithKeycloakAllOfficerDate( - @Request() req: RequestWithUser, @Body() body: { node: number; @@ -4930,25 +5481,25 @@ export class OrganizationDotnetController extends Controller { item.current_holders.length == 0 ? null : item.current_holders.find((x) => x.orgRevisionId == findRevision?.id) != null && - item.current_holders.find((x) => x.orgRevisionId == findRevision?.id)?.orgChild4 != - null + item.current_holders.find((x) => x.orgRevisionId == findRevision?.id)?.orgChild4 != + null ? `${item.current_holders.find((x) => x.orgRevisionId == findRevision?.id)?.orgChild4.orgChild4ShortName}${item.current_holders.find((x) => x.orgRevisionId == findRevision?.id)?.posMasterNo}` : item.current_holders.find((x) => x.orgRevisionId == findRevision?.id) != null && - item.current_holders.find((x) => x.orgRevisionId == findRevision?.id) - ?.orgChild3 != null + item.current_holders.find((x) => x.orgRevisionId == findRevision?.id) + ?.orgChild3 != null ? `${item.current_holders.find((x) => x.orgRevisionId == findRevision?.id)?.orgChild3.orgChild3ShortName} ${item.current_holders.find((x) => x.orgRevisionId == findRevision?.id)?.posMasterNo}` : item.current_holders.find((x) => x.orgRevisionId == findRevision?.id) != null && - item.current_holders.find((x) => x.orgRevisionId == findRevision?.id) - ?.orgChild2 != null + item.current_holders.find((x) => x.orgRevisionId == findRevision?.id) + ?.orgChild2 != null ? `${item.current_holders.find((x) => x.orgRevisionId == findRevision?.id)?.orgChild2.orgChild2ShortName} ${item.current_holders.find((x) => x.orgRevisionId == findRevision?.id)?.posMasterNo}` : item.current_holders.find((x) => x.orgRevisionId == findRevision?.id) != null && - item.current_holders.find((x) => x.orgRevisionId == findRevision?.id) - ?.orgChild1 != null + item.current_holders.find((x) => x.orgRevisionId == findRevision?.id) + ?.orgChild1 != null ? `${item.current_holders.find((x) => x.orgRevisionId == findRevision?.id)?.orgChild1.orgChild1ShortName} ${item.current_holders.find((x) => x.orgRevisionId == findRevision?.id)?.posMasterNo}` : item.current_holders.find((x) => x.orgRevisionId == findRevision?.id) != - null && - item.current_holders.find((x) => x.orgRevisionId == findRevision?.id) - ?.orgRoot != null + null && + item.current_holders.find((x) => x.orgRevisionId == findRevision?.id) + ?.orgRoot != null ? `${item.current_holders.find((x) => x.orgRevisionId == findRevision?.id)?.orgRoot.orgRootShortName} ${item.current_holders.find((x) => x.orgRevisionId == findRevision?.id)?.posMasterNo}` : null; const Oc = @@ -4992,7 +5543,6 @@ export class OrganizationDotnetController extends Controller { */ @Post("none-validate-keycloak-all-officer") async PostProfileWithNoneValidateKeycloakAllOfficer( - @Request() req: RequestWithUser, @Body() body: { node: number; @@ -5100,25 +5650,25 @@ export class OrganizationDotnetController extends Controller { item.current_holders.length == 0 ? null : item.current_holders.find((x) => x.orgRevisionId == findRevision?.id) != null && - item.current_holders.find((x) => x.orgRevisionId == findRevision?.id)?.orgChild4 != - null + item.current_holders.find((x) => x.orgRevisionId == findRevision?.id)?.orgChild4 != + null ? `${item.current_holders.find((x) => x.orgRevisionId == findRevision?.id)?.orgChild4.orgChild4ShortName} ${item.current_holders.find((x) => x.orgRevisionId == findRevision?.id)?.posMasterNo}` : item.current_holders.find((x) => x.orgRevisionId == findRevision?.id) != null && - item.current_holders.find((x) => x.orgRevisionId == findRevision?.id) - ?.orgChild3 != null + item.current_holders.find((x) => x.orgRevisionId == findRevision?.id) + ?.orgChild3 != null ? `${item.current_holders.find((x) => x.orgRevisionId == findRevision?.id)?.orgChild3.orgChild3ShortName} ${item.current_holders.find((x) => x.orgRevisionId == findRevision?.id)?.posMasterNo}` : item.current_holders.find((x) => x.orgRevisionId == findRevision?.id) != null && - item.current_holders.find((x) => x.orgRevisionId == findRevision?.id) - ?.orgChild2 != null + item.current_holders.find((x) => x.orgRevisionId == findRevision?.id) + ?.orgChild2 != null ? `${item.current_holders.find((x) => x.orgRevisionId == findRevision?.id)?.orgChild2.orgChild2ShortName} ${item.current_holders.find((x) => x.orgRevisionId == findRevision?.id)?.posMasterNo}` : item.current_holders.find((x) => x.orgRevisionId == findRevision?.id) != null && - item.current_holders.find((x) => x.orgRevisionId == findRevision?.id) - ?.orgChild1 != null + item.current_holders.find((x) => x.orgRevisionId == findRevision?.id) + ?.orgChild1 != null ? `${item.current_holders.find((x) => x.orgRevisionId == findRevision?.id)?.orgChild1.orgChild1ShortName} ${item.current_holders.find((x) => x.orgRevisionId == findRevision?.id)?.posMasterNo}` : item.current_holders.find((x) => x.orgRevisionId == findRevision?.id) != - null && - item.current_holders.find((x) => x.orgRevisionId == findRevision?.id) - ?.orgRoot != null + null && + item.current_holders.find((x) => x.orgRevisionId == findRevision?.id) + ?.orgRoot != null ? `${item.current_holders.find((x) => x.orgRevisionId == findRevision?.id)?.orgRoot.orgRootShortName} ${item.current_holders.find((x) => x.orgRevisionId == findRevision?.id)?.posMasterNo}` : null; const Oc = @@ -5162,7 +5712,6 @@ export class OrganizationDotnetController extends Controller { */ @Post("find-node-name") async findNodeName( - @Request() req: RequestWithUser, @Body() body: { node: number; @@ -5271,7 +5820,6 @@ export class OrganizationDotnetController extends Controller { */ @Post("officer-by-admin-role") async GetOfficersByAdminRole( - @Request() req: RequestWithUser, @Body() body: { node: number; @@ -5284,7 +5832,7 @@ export class OrganizationDotnetController extends Controller { }, ) { let typeCondition: any = {}; - if (body.role === "CHILD" || body.role === "PARENT" || body.role === "BROTHER") { + if (body.role === "CHILD" || /*body.role === "PARENT" ||*/ body.role === "BROTHER") { if (body.role === "CHILD") { switch (body.node) { case 0: @@ -5367,15 +5915,16 @@ export class OrganizationDotnetController extends Controller { typeCondition = {}; break; } - } else if (body.role === "PARENT") { - typeCondition = { - orgRoot: { - ancestorDNA: body.nodeId, - }, - orgChild1: Not(IsNull()), - }; } - } else if (body.role === "OWNER" || body.role === "ROOT") { + // else if (body.role === "PARENT") { + // typeCondition = { + // orgRoot: { + // ancestorDNA: body.nodeId, + // }, + // orgChild1: Not(IsNull()), + // }; + // } + } else if (body.role === "OWNER" || body.role === "ROOT" || body.role === "PARENT") { switch (body.reqNode) { case 0: typeCondition = { @@ -5531,25 +6080,25 @@ export class OrganizationDotnetController extends Controller { item.current_holders.length == 0 ? null : item.current_holders.find((x) => x.orgRevisionId == findRevision?.id) != null && - item.current_holders.find((x) => x.orgRevisionId == findRevision?.id)?.orgChild4 != - null + item.current_holders.find((x) => x.orgRevisionId == findRevision?.id)?.orgChild4 != + null ? `${item.current_holders.find((x) => x.orgRevisionId == findRevision?.id)?.orgChild4.orgChild4ShortName} ${item.current_holders.find((x) => x.orgRevisionId == findRevision?.id)?.posMasterNo}` : item.current_holders.find((x) => x.orgRevisionId == findRevision?.id) != null && - item.current_holders.find((x) => x.orgRevisionId == findRevision?.id) - ?.orgChild3 != null + item.current_holders.find((x) => x.orgRevisionId == findRevision?.id) + ?.orgChild3 != null ? `${item.current_holders.find((x) => x.orgRevisionId == findRevision?.id)?.orgChild3.orgChild3ShortName} ${item.current_holders.find((x) => x.orgRevisionId == findRevision?.id)?.posMasterNo}` : item.current_holders.find((x) => x.orgRevisionId == findRevision?.id) != null && - item.current_holders.find((x) => x.orgRevisionId == findRevision?.id) - ?.orgChild2 != null + item.current_holders.find((x) => x.orgRevisionId == findRevision?.id) + ?.orgChild2 != null ? `${item.current_holders.find((x) => x.orgRevisionId == findRevision?.id)?.orgChild2.orgChild2ShortName} ${item.current_holders.find((x) => x.orgRevisionId == findRevision?.id)?.posMasterNo}` : item.current_holders.find((x) => x.orgRevisionId == findRevision?.id) != null && - item.current_holders.find((x) => x.orgRevisionId == findRevision?.id) - ?.orgChild1 != null + item.current_holders.find((x) => x.orgRevisionId == findRevision?.id) + ?.orgChild1 != null ? `${item.current_holders.find((x) => x.orgRevisionId == findRevision?.id)?.orgChild1.orgChild1ShortName} ${item.current_holders.find((x) => x.orgRevisionId == findRevision?.id)?.posMasterNo}` : item.current_holders.find((x) => x.orgRevisionId == findRevision?.id) != - null && - item.current_holders.find((x) => x.orgRevisionId == findRevision?.id) - ?.orgRoot != null + null && + item.current_holders.find((x) => x.orgRevisionId == findRevision?.id) + ?.orgRoot != null ? `${item.current_holders.find((x) => x.orgRevisionId == findRevision?.id)?.orgRoot.orgRootShortName} ${item.current_holders.find((x) => x.orgRevisionId == findRevision?.id)?.posMasterNo}` : null; const Oc = @@ -5608,7 +6157,6 @@ export class OrganizationDotnetController extends Controller { */ @Post("keycloak-all-employee") async PostProfileWithKeycloakAllEmployee( - @Request() req: RequestWithUser, @Body() body: { node: number; @@ -5697,25 +6245,25 @@ export class OrganizationDotnetController extends Controller { item.current_holders.length == 0 ? null : item.current_holders.find((x) => x.orgRevisionId == findRevision?.id) != null && - item.current_holders.find((x) => x.orgRevisionId == findRevision?.id)?.orgChild4 != - null + item.current_holders.find((x) => x.orgRevisionId == findRevision?.id)?.orgChild4 != + null ? `${item.current_holders.find((x) => x.orgRevisionId == findRevision?.id)?.orgChild4.orgChild4ShortName} ${item.current_holders.find((x) => x.orgRevisionId == findRevision?.id)?.posMasterNo}` : item.current_holders.find((x) => x.orgRevisionId == findRevision?.id) != null && - item.current_holders.find((x) => x.orgRevisionId == findRevision?.id) - ?.orgChild3 != null + item.current_holders.find((x) => x.orgRevisionId == findRevision?.id) + ?.orgChild3 != null ? `${item.current_holders.find((x) => x.orgRevisionId == findRevision?.id)?.orgChild3.orgChild3ShortName} ${item.current_holders.find((x) => x.orgRevisionId == findRevision?.id)?.posMasterNo}` : item.current_holders.find((x) => x.orgRevisionId == findRevision?.id) != null && - item.current_holders.find((x) => x.orgRevisionId == findRevision?.id) - ?.orgChild2 != null + item.current_holders.find((x) => x.orgRevisionId == findRevision?.id) + ?.orgChild2 != null ? `${item.current_holders.find((x) => x.orgRevisionId == findRevision?.id)?.orgChild2.orgChild2ShortName} ${item.current_holders.find((x) => x.orgRevisionId == findRevision?.id)?.posMasterNo}` : item.current_holders.find((x) => x.orgRevisionId == findRevision?.id) != null && - item.current_holders.find((x) => x.orgRevisionId == findRevision?.id) - ?.orgChild1 != null + item.current_holders.find((x) => x.orgRevisionId == findRevision?.id) + ?.orgChild1 != null ? `${item.current_holders.find((x) => x.orgRevisionId == findRevision?.id)?.orgChild1.orgChild1ShortName} ${item.current_holders.find((x) => x.orgRevisionId == findRevision?.id)?.posMasterNo}` : item.current_holders.find((x) => x.orgRevisionId == findRevision?.id) != - null && - item.current_holders.find((x) => x.orgRevisionId == findRevision?.id) - ?.orgRoot != null + null && + item.current_holders.find((x) => x.orgRevisionId == findRevision?.id) + ?.orgRoot != null ? `${item.current_holders.find((x) => x.orgRevisionId == findRevision?.id)?.orgRoot.orgRootShortName} ${item.current_holders.find((x) => x.orgRevisionId == findRevision?.id)?.posMasterNo}` : null; const Oc = @@ -5762,7 +6310,6 @@ export class OrganizationDotnetController extends Controller { */ @Post("none-validate-keycloak-all-employee") async PostProfileWithNoneValidateKeycloakAllEmployee( - @Request() req: RequestWithUser, @Body() body: { node: number; @@ -5851,25 +6398,25 @@ export class OrganizationDotnetController extends Controller { item.current_holders.length == 0 ? null : item.current_holders.find((x) => x.orgRevisionId == findRevision?.id) != null && - item.current_holders.find((x) => x.orgRevisionId == findRevision?.id)?.orgChild4 != - null + item.current_holders.find((x) => x.orgRevisionId == findRevision?.id)?.orgChild4 != + null ? `${item.current_holders.find((x) => x.orgRevisionId == findRevision?.id)?.orgChild4.orgChild4ShortName} ${item.current_holders.find((x) => x.orgRevisionId == findRevision?.id)?.posMasterNo}` : item.current_holders.find((x) => x.orgRevisionId == findRevision?.id) != null && - item.current_holders.find((x) => x.orgRevisionId == findRevision?.id) - ?.orgChild3 != null + item.current_holders.find((x) => x.orgRevisionId == findRevision?.id) + ?.orgChild3 != null ? `${item.current_holders.find((x) => x.orgRevisionId == findRevision?.id)?.orgChild3.orgChild3ShortName} ${item.current_holders.find((x) => x.orgRevisionId == findRevision?.id)?.posMasterNo}` : item.current_holders.find((x) => x.orgRevisionId == findRevision?.id) != null && - item.current_holders.find((x) => x.orgRevisionId == findRevision?.id) - ?.orgChild2 != null + item.current_holders.find((x) => x.orgRevisionId == findRevision?.id) + ?.orgChild2 != null ? `${item.current_holders.find((x) => x.orgRevisionId == findRevision?.id)?.orgChild2.orgChild2ShortName} ${item.current_holders.find((x) => x.orgRevisionId == findRevision?.id)?.posMasterNo}` : item.current_holders.find((x) => x.orgRevisionId == findRevision?.id) != null && - item.current_holders.find((x) => x.orgRevisionId == findRevision?.id) - ?.orgChild1 != null + item.current_holders.find((x) => x.orgRevisionId == findRevision?.id) + ?.orgChild1 != null ? `${item.current_holders.find((x) => x.orgRevisionId == findRevision?.id)?.orgChild1.orgChild1ShortName} ${item.current_holders.find((x) => x.orgRevisionId == findRevision?.id)?.posMasterNo}` : item.current_holders.find((x) => x.orgRevisionId == findRevision?.id) != - null && - item.current_holders.find((x) => x.orgRevisionId == findRevision?.id) - ?.orgRoot != null + null && + item.current_holders.find((x) => x.orgRevisionId == findRevision?.id) + ?.orgRoot != null ? `${item.current_holders.find((x) => x.orgRevisionId == findRevision?.id)?.orgRoot.orgRootShortName} ${item.current_holders.find((x) => x.orgRevisionId == findRevision?.id)?.posMasterNo}` : null; const Oc = @@ -5916,7 +6463,6 @@ export class OrganizationDotnetController extends Controller { */ @Post("employee-by-admin-role") async GetEmployeesByAdminRole( - @Request() req: RequestWithUser, @Body() body: { node: number; @@ -5931,7 +6477,7 @@ export class OrganizationDotnetController extends Controller { }, ) { let typeCondition: any = {}; - if (body.role === "CHILD" || body.role === "PARENT" || body.role === "BROTHER") { + if (body.role === "CHILD" || /*body.role === "PARENT" ||*/ body.role === "BROTHER") { if (body.role === "CHILD") { switch (body.node) { case 0: @@ -6014,15 +6560,16 @@ export class OrganizationDotnetController extends Controller { typeCondition = {}; break; } - } else if (body.role === "PARENT") { - typeCondition = { - orgRoot: { - ancestorDNA: body.nodeId, - }, - orgChild1: Not(IsNull()), - }; } - } else if (body.role === "OWNER" || body.role === "ROOT") { + // else if (body.role === "PARENT") { + // typeCondition = { + // orgRoot: { + // ancestorDNA: body.nodeId, + // }, + // orgChild1: Not(IsNull()), + // }; + // } + } else if (body.role === "OWNER" || body.role === "ROOT" || body.role === "PARENT") { switch (body.reqNode) { case 0: typeCondition = { @@ -6178,25 +6725,25 @@ export class OrganizationDotnetController extends Controller { item.current_holders.length == 0 ? null : item.current_holders.find((x) => x.orgRevisionId == findRevision?.id) != null && - item.current_holders.find((x) => x.orgRevisionId == findRevision?.id)?.orgChild4 != - null + item.current_holders.find((x) => x.orgRevisionId == findRevision?.id)?.orgChild4 != + null ? `${item.current_holders.find((x) => x.orgRevisionId == findRevision?.id)?.orgChild4.orgChild4ShortName} ${item.current_holders.find((x) => x.orgRevisionId == findRevision?.id)?.posMasterNo}` : item.current_holders.find((x) => x.orgRevisionId == findRevision?.id) != null && - item.current_holders.find((x) => x.orgRevisionId == findRevision?.id) - ?.orgChild3 != null + item.current_holders.find((x) => x.orgRevisionId == findRevision?.id) + ?.orgChild3 != null ? `${item.current_holders.find((x) => x.orgRevisionId == findRevision?.id)?.orgChild3.orgChild3ShortName} ${item.current_holders.find((x) => x.orgRevisionId == findRevision?.id)?.posMasterNo}` : item.current_holders.find((x) => x.orgRevisionId == findRevision?.id) != null && - item.current_holders.find((x) => x.orgRevisionId == findRevision?.id) - ?.orgChild2 != null + item.current_holders.find((x) => x.orgRevisionId == findRevision?.id) + ?.orgChild2 != null ? `${item.current_holders.find((x) => x.orgRevisionId == findRevision?.id)?.orgChild2.orgChild2ShortName} ${item.current_holders.find((x) => x.orgRevisionId == findRevision?.id)?.posMasterNo}` : item.current_holders.find((x) => x.orgRevisionId == findRevision?.id) != null && - item.current_holders.find((x) => x.orgRevisionId == findRevision?.id) - ?.orgChild1 != null + item.current_holders.find((x) => x.orgRevisionId == findRevision?.id) + ?.orgChild1 != null ? `${item.current_holders.find((x) => x.orgRevisionId == findRevision?.id)?.orgChild1.orgChild1ShortName} ${item.current_holders.find((x) => x.orgRevisionId == findRevision?.id)?.posMasterNo}` : item.current_holders.find((x) => x.orgRevisionId == findRevision?.id) != - null && - item.current_holders.find((x) => x.orgRevisionId == findRevision?.id) - ?.orgRoot != null + null && + item.current_holders.find((x) => x.orgRevisionId == findRevision?.id) + ?.orgRoot != null ? `${item.current_holders.find((x) => x.orgRevisionId == findRevision?.id)?.orgRoot.orgRootShortName} ${item.current_holders.find((x) => x.orgRevisionId == findRevision?.id)?.posMasterNo}` : null; const Oc = @@ -6701,16 +7248,16 @@ export class OrganizationDotnetController extends Controller { currentAddress: profile && profile.currentAddress ? profile.currentAddress + - (profile.currentSubDistrict && profile.currentSubDistrict.name - ? " ตำบล/แขวง " + profile.currentSubDistrict.name - : "") + - (profile.currentDistrict && profile.currentDistrict.name - ? " อำเภอ/เขต " + profile.currentDistrict.name - : "") + - (profile.currentProvince && profile.currentProvince.name - ? " จังหวัด " + profile.currentProvince.name - : "") + - profile.currentZipCode + (profile.currentSubDistrict && profile.currentSubDistrict.name + ? " ตำบล/แขวง " + profile.currentSubDistrict.name + : "") + + (profile.currentDistrict && profile.currentDistrict.name + ? " อำเภอ/เขต " + profile.currentDistrict.name + : "") + + (profile.currentProvince && profile.currentProvince.name + ? " จังหวัด " + profile.currentProvince.name + : "") + + profile.currentZipCode : "-", oc: oc ?? "-", root: @@ -6746,26 +7293,26 @@ export class OrganizationDotnetController extends Controller { positions: _position && _position.length > 0 ? _position.slice(0, -1).map((x: any, idx: number) => ({ - positionName: x.positionName, - dateStart: x.commandDateAffect ?? null, - dateEnd: _position[idx + 1]?.commandDateAffect ?? null, - positionType: x.positionType, - positionLevel: x.positionLevel, - orgRoot: x.orgRoot, - orgChild1: x.orgChild1, - orgChild2: x.orgChild2, - orgChild3: x.orgChild3, - orgChild4: x.orgChild4, - })) + positionName: x.positionName, + dateStart: x.commandDateAffect ?? null, + dateEnd: _position[idx + 1]?.commandDateAffect ?? null, + positionType: x.positionType, + positionLevel: x.positionLevel, + orgRoot: x.orgRoot, + orgChild1: x.orgChild1, + orgChild2: x.orgChild2, + orgChild3: x.orgChild3, + orgChild4: x.orgChild4, + })) : [], educations: profile.profileEducations && profile.profileEducations.length > 0 ? profile.profileEducations.map((x) => ({ - educationLevel: x.educationLevel, - institute: x.institute ?? "-", - country: x.country ?? "-", - finishDate: x.finishDate, - })) + educationLevel: x.educationLevel, + institute: x.institute ?? "-", + country: x.country ?? "-", + finishDate: x.finishDate, + })) : [], }; return new HttpSuccess(mapEmpProfile); @@ -6815,16 +7362,16 @@ export class OrganizationDotnetController extends Controller { currentAddress: profile && profile.currentAddress ? profile.currentAddress + - (profile.currentSubDistrict && profile.currentSubDistrict.name - ? " ตำบล/แขวง " + profile.currentSubDistrict.name - : "") + - (profile.currentDistrict && profile.currentDistrict.name - ? " อำเภอ/เขต " + profile.currentDistrict.name - : "") + - (profile.currentProvince && profile.currentProvince.name - ? " จังหวัด " + profile.currentProvince.name - : "") + - profile.currentZipCode + (profile.currentSubDistrict && profile.currentSubDistrict.name + ? " ตำบล/แขวง " + profile.currentSubDistrict.name + : "") + + (profile.currentDistrict && profile.currentDistrict.name + ? " อำเภอ/เขต " + profile.currentDistrict.name + : "") + + (profile.currentProvince && profile.currentProvince.name + ? " จังหวัด " + profile.currentProvince.name + : "") + + profile.currentZipCode : "-", oc: oc ?? "-", root: @@ -6860,26 +7407,26 @@ export class OrganizationDotnetController extends Controller { positions: _position && _position.length > 0 ? _position.slice(0, -1).map((x: any, idx: number) => ({ - positionName: x.positionName, - dateStart: x.commandDateAffect ?? null, - dateEnd: _position[idx + 1]?.commandDateAffect ?? null, - positionType: x.positionType, - positionLevel: x.positionLevel, - orgRoot: x.orgRoot, - orgChild1: x.orgChild1, - orgChild2: x.orgChild2, - orgChild3: x.orgChild3, - orgChild4: x.orgChild4, - })) + positionName: x.positionName, + dateStart: x.commandDateAffect ?? null, + dateEnd: _position[idx + 1]?.commandDateAffect ?? null, + positionType: x.positionType, + positionLevel: x.positionLevel, + orgRoot: x.orgRoot, + orgChild1: x.orgChild1, + orgChild2: x.orgChild2, + orgChild3: x.orgChild3, + orgChild4: x.orgChild4, + })) : [], educations: profile.profileEducations && profile.profileEducations.length > 0 ? profile.profileEducations.map((x) => ({ - educationLevel: x.educationLevel, - institute: x.institute ?? "-", - country: x.country ?? "-", - finishDate: x.finishDate, - })) + educationLevel: x.educationLevel, + institute: x.institute ?? "-", + country: x.country ?? "-", + finishDate: x.finishDate, + })) : [], }; return new HttpSuccess(mapProfile); @@ -6887,10 +7434,7 @@ export class OrganizationDotnetController extends Controller { @Post("profile-leave/keycloak") async GetProfileLeaveReportByKeycloakIdAsync( - @Body() body: { - keycloakId: string, - report?: string - } + @Body() body: { keycloakId: string; report?: string }, ) { const profile = await this.profileRepo.findOne({ relations: { @@ -6933,14 +7477,14 @@ export class OrganizationDotnetController extends Controller { }, where: { keycloak: body.keycloakId, - } + }, }); if (!profile) throw new HttpError(HttpStatus.NOT_FOUND, "ไม่พบข้อมูล"); /* ========================================= - * current holder - * ========================================= */ + * current holder + * ========================================= */ const currentHolder = profile.current_holders?.find( - x => + (x) => x.orgRevision?.orgRevisionIsDraft === false && x.orgRevision?.orgRevisionIsCurrent === true, ); @@ -6962,37 +7506,40 @@ export class OrganizationDotnetController extends Controller { let _positions: any[] = []; let _educations: any[] = []; if (body.report && ["LEAVE16", "LEAVE18"].includes(body.report.trim().toUpperCase())) { - const CURRENT_DATE = await AppDataSource.query("SELECT CURRENT_DATE() as today"); let _currentDate = CURRENT_DATE[0].today; if (profile && profile?.isLeave) { _currentDate = - profile && profile.leaveDate ? Extension.toDateOnlyString(profile.leaveDate) : _currentDate; + profile && profile.leaveDate + ? Extension.toDateOnlyString(profile.leaveDate) + : _currentDate; } const positions = await AppDataSource.query("CALL GetProfileEmployeeSalaryPosition(?, ?)", [ profile!.id, _currentDate, ]); - _positions = positions[0].length > 0 - ? _positions.slice(0, -1).map((x: any, idx: number) => ({ - positionName: x.positionName, - dateStart: x.commandDateAffect ?? null, - dateEnd: _positions[idx + 1]?.commandDateAffect ?? null, - })) - : []; + _positions = + positions[0].length > 0 + ? _positions.slice(0, -1).map((x: any, idx: number) => ({ + positionName: x.positionName, + dateStart: x.commandDateAffect ?? null, + dateEnd: _positions[idx + 1]?.commandDateAffect ?? null, + })) + : []; const profileEducations = await this.educationRepo.find({ - where: { profileEmployeeId: profile!.id }, + where: { profileEmployeeId: profile!.id, isDeleted: false }, order: { level: "ASC" }, }); - _educations = profileEducations.length > 0 - ? profileEducations.map((x) => ({ - educationLevel: x.educationLevel, - institute: x.institute ?? "-", - country: x.country ?? "-", - finishDate: x.finishDate, - })) - : []; + _educations = + profileEducations.length > 0 + ? profileEducations.map((x) => ({ + educationLevel: x.educationLevel, + institute: x.institute ?? "-", + country: x.country ?? "-", + finishDate: x.finishDate, + })) + : []; } const mapProfile = { @@ -7002,15 +7549,11 @@ export class OrganizationDotnetController extends Controller { lastName: profile.lastName, citizenId: profile.citizenId, birthDate: profile.birthDate, - retireDate: profile.birthDate - ? calculateRetireLaw(profile.birthDate) - : null, - govAge: profile.dateAppoint - ? `${Extension.CalculateGovAge(profile.dateAppoint, 0, 0)} ปี` - : null, - age: profile.birthDate - ? Extension.CalculateAgeStrV2(profile.birthDate, 0, 0, "GET") + retireDate: profile.birthDate ? calculateRetireLaw(profile.birthDate) : null, + govAge: profile.dateAppoint + ? `${Extension.CalculateGovAge(profile.dateAppoint, 0, 0)} ปี` : null, + age: profile.birthDate ? Extension.CalculateAgeStrV2(profile.birthDate, 0, 0, "GET") : null, dateAppoint: profile.dateAppoint, dateCurrent: new Date(), amount: profile.amount, @@ -7018,16 +7561,16 @@ export class OrganizationDotnetController extends Controller { currentAddress: profile && profile.currentAddress ? profile.currentAddress + - (profile.currentSubDistrict && profile.currentSubDistrict.name - ? " ตำบล/แขวง " + profile.currentSubDistrict.name - : "") + - (profile.currentDistrict && profile.currentDistrict.name - ? " อำเภอ/เขต " + profile.currentDistrict.name - : "") + - (profile.currentProvince && profile.currentProvince.name - ? " จังหวัด " + profile.currentProvince.name + " " - : "") + - profile.currentZipCode + (profile.currentSubDistrict && profile.currentSubDistrict.name + ? " ตำบล/แขวง " + profile.currentSubDistrict.name + : "") + + (profile.currentDistrict && profile.currentDistrict.name + ? " อำเภอ/เขต " + profile.currentDistrict.name + : "") + + (profile.currentProvince && profile.currentProvince.name + ? " จังหวัด " + profile.currentProvince.name + " " + : "") + + profile.currentZipCode : "-", position: profile.position, posType: profile.posType?.posTypeName, @@ -7050,12 +7593,11 @@ export class OrganizationDotnetController extends Controller { } /* ========================================= - * current holder - * ========================================= */ + * current holder + * ========================================= */ const currentHolder = profile.current_holders?.find( - x => - x.orgRevision?.orgRevisionIsDraft === false && - x.orgRevision?.orgRevisionIsCurrent === true, + (x) => + x.orgRevision?.orgRevisionIsDraft === false && x.orgRevision?.orgRevisionIsCurrent === true, ); let oc = ""; @@ -7074,19 +7616,18 @@ export class OrganizationDotnetController extends Controller { } /* ========================================= - * posType + posLevel - * ========================================= */ + * posType + posLevel + * ========================================= */ const positionLeaveName = profile.posType && profile.posLevel && - (profile.posType.posTypeName === "บริหาร" || - profile.posType.posTypeName === "อำนวยการ") + (profile.posType.posTypeName === "บริหาร" || profile.posType.posTypeName === "อำนวยการ") ? `${profile.posType.posTypeName}${profile.posLevel.posLevelName}` : profile.posLevel?.posLevelName ?? null; /* ========================================= - * position executive - * ========================================= */ + * position executive + * ========================================= */ const _posExec = await this.positionRepository.findOne({ where: { positionIsSelected: true, @@ -7102,37 +7643,40 @@ export class OrganizationDotnetController extends Controller { let _positions: any[] = []; let _educations: any[] = []; if (body.report && ["LEAVE16", "LEAVE18"].includes(body.report.trim().toUpperCase())) { - const CURRENT_DATE = await AppDataSource.query("SELECT CURRENT_DATE() as today"); let _currentDate = CURRENT_DATE[0].today; if (profile && profile?.isLeave) { _currentDate = - profile && profile.leaveDate ? Extension.toDateOnlyString(profile.leaveDate) : _currentDate; + profile && profile.leaveDate + ? Extension.toDateOnlyString(profile.leaveDate) + : _currentDate; } const positions = await AppDataSource.query("CALL GetProfileSalaryPosition(?, ?)", [ profile!.id, _currentDate, ]); - _positions = positions[0].length > 0 - ? _positions.slice(0, -1).map((x: any, idx: number) => ({ - positionName: x.positionName, - dateStart: x.commandDateAffect ?? null, - dateEnd: _positions[idx + 1]?.commandDateAffect ?? null, - })) - : []; + _positions = + positions[0].length > 0 + ? _positions.slice(0, -1).map((x: any, idx: number) => ({ + positionName: x.positionName, + dateStart: x.commandDateAffect ?? null, + dateEnd: _positions[idx + 1]?.commandDateAffect ?? null, + })) + : []; const profileEducations = await this.educationRepo.find({ - where: { profileId: profile!.id }, + where: { profileId: profile!.id, isDeleted: false }, order: { level: "ASC" }, }); - _educations = profileEducations.length > 0 - ? profileEducations.map((x) => ({ - educationLevel: x.educationLevel, - institute: x.institute ?? "-", - country: x.country ?? "-", - finishDate: x.finishDate, - })) - : []; + _educations = + profileEducations.length > 0 + ? profileEducations.map((x) => ({ + educationLevel: x.educationLevel, + institute: x.institute ?? "-", + country: x.country ?? "-", + finishDate: x.finishDate, + })) + : []; } const mapProfile = { @@ -7142,15 +7686,11 @@ export class OrganizationDotnetController extends Controller { lastName: profile.lastName, citizenId: profile.citizenId, birthDate: profile.birthDate, - retireDate: profile.birthDate - ? calculateRetireLaw(profile.birthDate) - : null, - govAge: profile.dateAppoint - ? `${Extension.CalculateGovAge(profile.dateAppoint, 0, 0)} ปี` - : null, - age: profile.birthDate - ? Extension.CalculateAgeStrV2(profile.birthDate, 0, 0, "GET") + retireDate: profile.birthDate ? calculateRetireLaw(profile.birthDate) : null, + govAge: profile.dateAppoint + ? `${Extension.CalculateGovAge(profile.dateAppoint, 0, 0)} ปี` : null, + age: profile.birthDate ? Extension.CalculateAgeStrV2(profile.birthDate, 0, 0, "GET") : null, dateAppoint: profile.dateAppoint, dateCurrent: new Date(), amount: profile.amount, @@ -7158,16 +7698,16 @@ export class OrganizationDotnetController extends Controller { currentAddress: profile && profile.currentAddress ? profile.currentAddress + - (profile.currentSubDistrict && profile.currentSubDistrict.name - ? " ตำบล/แขวง " + profile.currentSubDistrict.name - : "") + - (profile.currentDistrict && profile.currentDistrict.name - ? " อำเภอ/เขต " + profile.currentDistrict.name - : "") + - (profile.currentProvince && profile.currentProvince.name - ? " จังหวัด " + profile.currentProvince.name + " " - : "") + - profile.currentZipCode + (profile.currentSubDistrict && profile.currentSubDistrict.name + ? " ตำบล/แขวง " + profile.currentSubDistrict.name + : "") + + (profile.currentDistrict && profile.currentDistrict.name + ? " อำเภอ/เขต " + profile.currentDistrict.name + : "") + + (profile.currentProvince && profile.currentProvince.name + ? " จังหวัด " + profile.currentProvince.name + " " + : "") + + profile.currentZipCode : "-", position: profile.position ?? "-", posLevel: profile.posLevel?.posLevelName ?? "-", @@ -7182,7 +7722,7 @@ export class OrganizationDotnetController extends Controller { child3: currentHolder?.orgChild3?.orgChild3Name ?? null, child4: currentHolder?.orgChild4?.orgChild4Name ?? null, oc: oc, - + positions: _positions, educations: _educations, }; @@ -7198,7 +7738,6 @@ export class OrganizationDotnetController extends Controller { */ @Post("find/insignia-requests-profile/{type}") async GetInsigniaRequestsProfileAsync( - @Request() req: RequestWithUser, @Path() type: string, @Body() body: { @@ -7328,7 +7867,6 @@ export class OrganizationDotnetController extends Controller { */ @Post("officer-by-admin-rolev2") async GetOfficersByAdminRoleV2( - @Request() req: RequestWithUser, @Body() body: { node: number; @@ -7341,7 +7879,7 @@ export class OrganizationDotnetController extends Controller { }, ) { let typeCondition: any = {}; - if (body.role === "CHILD" || body.role === "PARENT" || body.role === "BROTHER") { + if (body.role === "CHILD" || /*body.role === "PARENT" ||*/ body.role === "BROTHER") { if (body.role === "CHILD") { switch (body.node) { case 0: @@ -7404,13 +7942,14 @@ export class OrganizationDotnetController extends Controller { typeCondition = {}; break; } - } else if (body.role === "PARENT") { - typeCondition = { - rootDnaId: body.nodeId, - child1DnaId: Not(IsNull()), - }; } - } else if (body.role === "OWNER" || body.role === "ROOT") { + // else if (body.role === "PARENT") { + // typeCondition = { + // rootDnaId: body.nodeId, + // child1DnaId: Not(IsNull()), + // }; + // } + } else if (body.role === "OWNER" || body.role === "ROOT" || body.role === "PARENT") { switch (body.reqNode) { case 0: typeCondition = { @@ -7551,7 +8090,6 @@ export class OrganizationDotnetController extends Controller { */ @Post("officer-by-admin-rolev3") async GetOfficersByAdminRoleV3( - @Request() req: RequestWithUser, @Body() body: { node: number; @@ -7566,7 +8104,7 @@ export class OrganizationDotnetController extends Controller { }, ) { let typeCondition: any = {}; - if (body.role === "CHILD" || body.role === "PARENT" || body.role === "BROTHER") { + if (body.role === "CHILD" || /*body.role === "PARENT" ||*/ body.role === "BROTHER") { if (body.role === "CHILD") { switch (body.node) { case 0: @@ -7649,15 +8187,16 @@ export class OrganizationDotnetController extends Controller { typeCondition = {}; break; } - } else if (body.role === "PARENT") { - typeCondition = { - orgRoot: { - ancestorDNA: body.nodeId, - }, - orgChild1: Not(IsNull()), - }; } - } else if (body.role === "OWNER" || body.role === "ROOT") { + // else if (body.role === "PARENT") { + // typeCondition = { + // orgRoot: { + // ancestorDNA: body.nodeId, + // }, + // orgChild1: Not(IsNull()), + // }; + // } + } else if (body.role === "OWNER" || body.role === "ROOT" || body.role === "PARENT") { switch (body.reqNode) { case 0: typeCondition = { @@ -7820,25 +8359,25 @@ export class OrganizationDotnetController extends Controller { item.current_holders.length == 0 ? null : item.current_holders.find((x) => x.orgRevisionId == findRevision?.id) != null && - item.current_holders.find((x) => x.orgRevisionId == findRevision?.id)?.orgChild4 != - null + item.current_holders.find((x) => x.orgRevisionId == findRevision?.id)?.orgChild4 != + null ? `${item.current_holders.find((x) => x.orgRevisionId == findRevision?.id)?.orgChild4.orgChild4ShortName} ${item.current_holders.find((x) => x.orgRevisionId == findRevision?.id)?.posMasterNo}` : item.current_holders.find((x) => x.orgRevisionId == findRevision?.id) != null && - item.current_holders.find((x) => x.orgRevisionId == findRevision?.id) - ?.orgChild3 != null + item.current_holders.find((x) => x.orgRevisionId == findRevision?.id) + ?.orgChild3 != null ? `${item.current_holders.find((x) => x.orgRevisionId == findRevision?.id)?.orgChild3.orgChild3ShortName} ${item.current_holders.find((x) => x.orgRevisionId == findRevision?.id)?.posMasterNo}` : item.current_holders.find((x) => x.orgRevisionId == findRevision?.id) != null && - item.current_holders.find((x) => x.orgRevisionId == findRevision?.id) - ?.orgChild2 != null + item.current_holders.find((x) => x.orgRevisionId == findRevision?.id) + ?.orgChild2 != null ? `${item.current_holders.find((x) => x.orgRevisionId == findRevision?.id)?.orgChild2.orgChild2ShortName} ${item.current_holders.find((x) => x.orgRevisionId == findRevision?.id)?.posMasterNo}` : item.current_holders.find((x) => x.orgRevisionId == findRevision?.id) != null && - item.current_holders.find((x) => x.orgRevisionId == findRevision?.id) - ?.orgChild1 != null + item.current_holders.find((x) => x.orgRevisionId == findRevision?.id) + ?.orgChild1 != null ? `${item.current_holders.find((x) => x.orgRevisionId == findRevision?.id)?.orgChild1.orgChild1ShortName} ${item.current_holders.find((x) => x.orgRevisionId == findRevision?.id)?.posMasterNo}` : item.current_holders.find((x) => x.orgRevisionId == findRevision?.id) != - null && - item.current_holders.find((x) => x.orgRevisionId == findRevision?.id) - ?.orgRoot != null + null && + item.current_holders.find((x) => x.orgRevisionId == findRevision?.id) + ?.orgRoot != null ? `${item.current_holders.find((x) => x.orgRevisionId == findRevision?.id)?.orgRoot.orgRootShortName} ${item.current_holders.find((x) => x.orgRevisionId == findRevision?.id)?.posMasterNo}` : null; const Oc = @@ -7897,7 +8436,6 @@ export class OrganizationDotnetController extends Controller { */ @Post("officer-by-admin-rolev4") async GetOfficersByAdminRoleV4( - @Request() req: RequestWithUser, @Body() body: { node: number; @@ -7910,7 +8448,7 @@ export class OrganizationDotnetController extends Controller { }, ) { let typeCondition: any = {}; - if (body.role === "CHILD" || body.role === "PARENT" || body.role === "BROTHER") { + if (body.role === "CHILD" || /*body.role === "PARENT" ||*/ body.role === "BROTHER") { if (body.role === "CHILD") { switch (body.node) { case 0: @@ -7973,13 +8511,14 @@ export class OrganizationDotnetController extends Controller { typeCondition = {}; break; } - } else if (body.role === "PARENT") { - typeCondition = { - rootDnaId: body.nodeId, - child1DnaId: Not(IsNull()), - }; } - } else if (body.role === "OWNER" || body.role === "ROOT") { + // else if (body.role === "PARENT") { + // typeCondition = { + // rootDnaId: body.nodeId, + // child1DnaId: Not(IsNull()), + // }; + // } + } else if (body.role === "OWNER" || body.role === "ROOT" || body.role === "PARENT") { switch (body.reqNode) { case 0: typeCondition = { @@ -8138,15 +8677,17 @@ export class OrganizationDotnetController extends Controller { }), ); - return new HttpSuccess(profile_); + return new HttpSuccess( + (profile_ ?? []).sort((a, b) => a.posNo.localeCompare(b.posNo, undefined, { numeric: true })), + ); } /** - * API ค้นหา กจ. - * - * @summary API ค้นหา กจ. - * - */ + * API ค้นหา กจ. + * + * @summary API ค้นหา กจ. + * + */ @Post("find-staff") async findHigher( @Body() @@ -8154,18 +8695,13 @@ export class OrganizationDotnetController extends Controller { profileId: string; assignId: string; }, - @Request() request: RequestWithUser, ) { const profile = await this.profileRepo.findOne({ where: { id: requestBody.profileId }, - relations: [ - "current_holders", - "current_holders.orgRevision" - ], + relations: ["current_holders", "current_holders.orgRevision"], }); - if (!profile) - throw new HttpError(HttpStatus.NOT_FOUND, "ไม่พบข้อมูลโปรไฟล์"); + if (!profile) throw new HttpError(HttpStatus.NOT_FOUND, "ไม่พบข้อมูลโปรไฟล์"); const assign = await this.assignRepository.findOne({ where: { id: requestBody.assignId.trim().toLocaleUpperCase() }, @@ -8175,7 +8711,7 @@ export class OrganizationDotnetController extends Controller { throw new HttpError(HttpStatus.NOT_FOUND, "ไม่พบข้อมูลระบบสิทธิ์หน้าที่ความรับผิดชอบ"); const currentHolder = profile.current_holders?.find( - h => h.orgRevision?.orgRevisionIsCurrent && !h.orgRevision?.orgRevisionIsDraft, + (h) => h.orgRevision?.orgRevisionIsCurrent && !h.orgRevision?.orgRevisionIsDraft, ); if (!currentHolder) @@ -8218,7 +8754,7 @@ export class OrganizationDotnetController extends Controller { profileId: profile.id, }) .andWhere("assign.assignId = :assignId", { - assignId: assign.id + assignId: assign.id, }) .getRawMany(); diff --git a/src/controllers/OrganizationUnauthorizeController.ts b/src/controllers/OrganizationUnauthorizeController.ts index dfd26df5..fa67daa0 100644 --- a/src/controllers/OrganizationUnauthorizeController.ts +++ b/src/controllers/OrganizationUnauthorizeController.ts @@ -4,7 +4,7 @@ import { AppDataSource } from "../database/data-source"; import HttpSuccess from "../interfaces/http-success"; import HttpError from "../interfaces/http-error"; import HttpStatusCode from "../interfaces/http-status"; -import { Brackets, In, IsNull, MoreThanOrEqual, Not } from "typeorm"; +import { Brackets, In, IsNull, LessThanOrEqual, MoreThanOrEqual, Not } from "typeorm"; import { OrgRoot } from "../entities/OrgRoot"; import { PosMaster } from "../entities/PosMaster"; import { calculateRetireDate } from "../interfaces/utils"; @@ -22,6 +22,21 @@ import Extension from "../interfaces/extension"; import { resetPassword } from "../keycloak"; import { viewEmployeePosMaster } from "../entities/view/viewEmployeePosMaster"; import { EmployeePosDict } from "../entities/EmployeePosDict"; +import { ProfileSalary } from "../entities/ProfileSalary"; +import { ProfileInsignia } from "../entities/ProfileInsignia"; +import { PosMasterHistory } from "../entities/PosMasterHistory"; +import { + ProfileAbsentLate, + CreateProfileAbsentLate, + CreateProfileAbsentLateBatch, +} from "../entities/ProfileAbsentLate"; +import { ProfileAbsentLateHistory } from "../entities/ProfileAbsentLateHistory"; +import { + ProfileEmployeeAbsentLate, + CreateProfileEmployeeAbsentLate, + CreateProfileEmployeeAbsentLateBatch, +} from "../entities/ProfileEmployeeAbsentLate"; +import { ProfileEmployeeAbsentLateHistory } from "../entities/ProfileEmployeeAbsentLateHistory"; @Route("api/v1/org/unauthorize") @Tags("OrganizationUnauthorize") @Response( @@ -39,7 +54,15 @@ export class OrganizationUnauthorizeController extends Controller { viewProfileEmployeeEvaluation, ); private employeePosDictRepository = AppDataSource.getRepository(EmployeePosDict); - + private posMasterRepository = AppDataSource.getRepository(PosMaster); + private empPosMasterRepository = AppDataSource.getRepository(EmployeePosMaster); + private salaryRepo = AppDataSource.getRepository(ProfileSalary); + private posMasterHistoryRepository = AppDataSource.getRepository(PosMasterHistory); + private insigniaRepo = AppDataSource.getRepository(ProfileInsignia); + private absentLateRepo = AppDataSource.getRepository(ProfileAbsentLate); + private absentLateHistoryRepo = AppDataSource.getRepository(ProfileAbsentLateHistory); + private empAbsentLateRepo = AppDataSource.getRepository(ProfileEmployeeAbsentLate); + private empAbsentLateHistoryRepo = AppDataSource.getRepository(ProfileEmployeeAbsentLateHistory); @Post("user/reset-password") async forgetPassword( @Body() @@ -418,6 +441,7 @@ export class OrganizationUnauthorizeController extends Controller { year: body.year.toString(), pointSum: MoreThanOrEqual(90), period: body.period.toLocaleUpperCase(), + isDeleted: false } } ); @@ -879,6 +903,7 @@ export class OrganizationUnauthorizeController extends Controller { year: body.year.toString(), pointSum: MoreThanOrEqual(90), period: body.period.toLocaleUpperCase(), + isDeleted: false } } ); @@ -1036,6 +1061,675 @@ export class OrganizationUnauthorizeController extends Controller { return new HttpSuccess(findRevision.id); } + /** + * API Get Profile จาก profile id + * + * @summary API Get Profile จาก profile id + * + * @param {string} profileId Id profile + */ + @Get("profile/{profileId}") + async GetProfileByProfileIdAsync(@Path() profileId: string) { + const profile = await this.profileRepo.findOne({ + relations: [ + "posLevel", + "posType", + "current_holders", + "current_holders.orgRevision", + "current_holders.orgRoot", + "current_holders.orgChild1", + "current_holders.orgChild2", + "current_holders.orgChild3", + "current_holders.orgChild4", + ], + where: { id: profileId }, + }); + + if (!profile) { + const profile = await this.profileEmpRepo.findOne({ + relations: [ + "posLevel", + "posType", + "current_holders", + "current_holders.orgRevision", + "current_holders.orgRoot", + "current_holders.orgChild1", + "current_holders.orgChild2", + "current_holders.orgChild3", + "current_holders.orgChild4", + ], + where: { id: profileId }, + }); + if (!profile) throw new HttpError(HttpStatus.NOT_FOUND, "ไม่พบข้อมูล"); + + const [latestSalary, latestInsignia] = await Promise.all([ + this.salaryRepo.findOne({ + where: { profileEmployeeId: profile.id }, + order: { commandDateAffect: "DESC" }, + }), + this.insigniaRepo.findOne({ + where: { profileEmployeeId: profile.id, isDeleted: false }, + order: { receiveDate: "DESC" }, + }), + ]); + + const org = { + root: + profile?.current_holders?.find( + (x) => + x.orgRevision?.orgRevisionIsDraft == false && + x.orgRevision?.orgRevisionIsCurrent == true, + )?.orgRoot?.id ?? null, + child1: + profile?.current_holders?.find( + (x) => + x.orgRevision?.orgRevisionIsDraft == false && + x.orgRevision?.orgRevisionIsCurrent == true, + )?.orgChild1?.id ?? null, + child2: + profile?.current_holders?.find( + (x) => + x.orgRevision?.orgRevisionIsDraft == false && + x.orgRevision?.orgRevisionIsCurrent == true, + )?.orgChild2?.id ?? null, + child3: + profile?.current_holders?.find( + (x) => + x.orgRevision?.orgRevisionIsDraft == false && + x.orgRevision?.orgRevisionIsCurrent == true, + )?.orgChild3?.id ?? null, + child4: + profile?.current_holders?.find( + (x) => + x.orgRevision?.orgRevisionIsDraft == false && + x.orgRevision?.orgRevisionIsCurrent == true, + )?.orgChild4?.id ?? null, + }; + let fullname = ""; + let pos = await this.empPosMasterRepository.findOne({ + relations: ["current_holder"], + where: { + current_holder: Not(IsNull()), + orgRevision: { + orgRevisionIsCurrent: true, + orgRevisionIsDraft: false, + }, + isDirector: true, + orgRootId: org?.root ?? "", + orgChild1Id: org?.child1 ?? "", + orgChild2Id: org?.child2 ?? "", + orgChild3Id: org?.child3 ?? "", + orgChild4Id: org?.child4 ?? "", + }, + }); + + if (pos) { + fullname = + pos.current_holder.prefix + + pos.current_holder.firstName + + " " + + pos.current_holder.lastName; + } else { + let pos = await this.empPosMasterRepository.findOne({ + relations: ["current_holder"], + where: { + current_holder: Not(IsNull()), + orgRevision: { + orgRevisionIsCurrent: true, + orgRevisionIsDraft: false, + }, + isDirector: true, + orgRootId: org?.root ?? "", + orgChild1Id: org?.child1 ?? "", + orgChild2Id: org?.child2 ?? "", + orgChild3Id: org?.child3 ?? "", + orgChild4Id: IsNull(), + }, + }); + if (pos) { + fullname = + pos.current_holder.prefix + + pos.current_holder.firstName + + " " + + pos.current_holder.lastName; + } else { + let pos = await this.empPosMasterRepository.findOne({ + relations: ["current_holder"], + where: { + current_holder: Not(IsNull()), + orgRevision: { + orgRevisionIsCurrent: true, + orgRevisionIsDraft: false, + }, + isDirector: true, + orgRootId: org?.root ?? "", + orgChild1Id: org?.child1 ?? "", + orgChild2Id: org?.child2 ?? "", + orgChild3Id: IsNull(), + orgChild4Id: IsNull(), + }, + }); + if (pos) { + fullname = + pos.current_holder.prefix + + pos.current_holder.firstName + + " " + + pos.current_holder.lastName; + } else { + let pos = await this.empPosMasterRepository.findOne({ + relations: ["current_holder"], + where: { + current_holder: Not(IsNull()), + orgRevision: { + orgRevisionIsCurrent: true, + orgRevisionIsDraft: false, + }, + isDirector: true, + orgRootId: org?.root ?? "", + orgChild1Id: org?.child1 ?? "", + orgChild2Id: IsNull(), + orgChild3Id: IsNull(), + orgChild4Id: IsNull(), + }, + }); + if (pos) { + fullname = + pos.current_holder.prefix + + pos.current_holder.firstName + + " " + + pos.current_holder.lastName; + } else { + let pos = await this.empPosMasterRepository.findOne({ + relations: ["current_holder"], + where: { + current_holder: Not(IsNull()), + orgRevision: { + orgRevisionIsCurrent: true, + orgRevisionIsDraft: false, + }, + orgRootId: org?.root ?? "", + orgChild1Id: IsNull(), + orgChild2Id: IsNull(), + orgChild3Id: IsNull(), + orgChild4Id: IsNull(), + }, + }); + if (pos) { + fullname = + pos.current_holder.prefix + + pos.current_holder.firstName + + " " + + pos.current_holder.lastName; + } else { + fullname = ""; + } + } + } + } + } + + const mapProfile = { + id: profile.id, + avatar: profile.avatar, + avatarName: profile.avatarName, + rank: profile.rank, + prefix: profile.prefix, + firstName: profile.firstName, + lastName: profile.lastName, + citizenId: profile.citizenId, + position: profile.position, + posLevelId: profile.posLevelId, + email: profile.email, + phone: profile.phone, + keycloak: profile.keycloak, + isProbation: profile.isProbation, + isLeave: profile.isLeave, + leaveReason: profile.leaveReason, + dateRetire: profile.dateRetire, + dateAppoint: profile.dateAppoint, + dateRetireLaw: profile.dateRetireLaw, + dateStart: profile.dateStart, + govAgeAbsent: profile.govAgeAbsent, + govAgePlus: profile.govAgePlus, + birthDate: profile.birthDate ?? new Date(), + reasonSameDate: profile.reasonSameDate, + telephoneNumber: profile.phone, + nationality: profile.nationality, + gender: profile.gender, + relationship: profile.relationship, + religion: profile.religion, + bloodGroup: profile.bloodGroup, + registrationAddress: profile.registrationAddress, + registrationProvinceId: profile.registrationProvinceId, + registrationDistrictId: profile.registrationDistrictId, + registrationSubDistrictId: profile.registrationSubDistrictId, + registrationZipCode: profile.registrationZipCode, + currentAddress: profile.currentAddress, + currentProvinceId: profile.currentProvinceId, + currentSubDistrictId: profile.currentSubDistrictId, + currentZipCode: profile.currentZipCode, + dutyTimeId: profile.dutyTimeId, + dutyTimeEffectiveDate: profile.dutyTimeEffectiveDate, + amount: profile.amount, + positionSalaryAmount: profile.positionSalaryAmount, + mouthSalaryAmount: profile.mouthSalaryAmount, + root: + profile?.current_holders?.find( + (x) => + x.orgRevision?.orgRevisionIsDraft == false && + x.orgRevision?.orgRevisionIsCurrent == true, + )?.orgRoot?.orgRootName ?? null, + child1: + profile?.current_holders?.find( + (x) => + x.orgRevision?.orgRevisionIsDraft == false && + x.orgRevision?.orgRevisionIsCurrent == true, + )?.orgChild1?.orgChild1Name ?? null, + child2: + profile?.current_holders?.find( + (x) => + x.orgRevision?.orgRevisionIsDraft == false && + x.orgRevision?.orgRevisionIsCurrent == true, + )?.orgChild2?.orgChild2Name ?? null, + child3: + profile?.current_holders?.find( + (x) => + x.orgRevision?.orgRevisionIsDraft == false && + x.orgRevision?.orgRevisionIsCurrent == true, + )?.orgChild3?.orgChild3Name ?? null, + child4: + profile?.current_holders?.find( + (x) => + x.orgRevision?.orgRevisionIsDraft == false && + x.orgRevision?.orgRevisionIsCurrent == true, + )?.orgChild4?.orgChild4Name ?? null, + rootId: + profile?.current_holders?.find( + (x) => + x.orgRevision?.orgRevisionIsDraft == false && + x.orgRevision?.orgRevisionIsCurrent == true, + )?.orgRootId ?? null, + child1Id: + profile?.current_holders?.find( + (x) => + x.orgRevision?.orgRevisionIsDraft == false && + x.orgRevision?.orgRevisionIsCurrent == true, + )?.orgChild1Id ?? null, + child2Id: + profile?.current_holders?.find( + (x) => + x.orgRevision?.orgRevisionIsDraft == false && + x.orgRevision?.orgRevisionIsCurrent == true, + )?.orgChild2Id ?? null, + child3Id: + profile?.current_holders?.find( + (x) => + x.orgRevision?.orgRevisionIsDraft == false && + x.orgRevision?.orgRevisionIsCurrent == true, + )?.orgChild3Id ?? null, + child4Id: + profile?.current_holders?.find( + (x) => + x.orgRevision?.orgRevisionIsDraft == false && + x.orgRevision?.orgRevisionIsCurrent == true, + )?.orgChild4Id ?? null, + rootDnaId: + profile?.current_holders?.find( + (x) => + x.orgRevision?.orgRevisionIsDraft == false && + x.orgRevision?.orgRevisionIsCurrent == true, + )?.orgRoot?.ancestorDNA ?? null, + child1DnaId: + profile?.current_holders?.find( + (x) => + x.orgRevision?.orgRevisionIsDraft == false && + x.orgRevision?.orgRevisionIsCurrent == true, + )?.orgChild3?.ancestorDNA ?? null, + child2DnaId: + profile?.current_holders?.find( + (x) => + x.orgRevision?.orgRevisionIsDraft == false && + x.orgRevision?.orgRevisionIsCurrent == true, + )?.orgChild2?.ancestorDNA ?? null, + child3DnaId: + profile?.current_holders?.find( + (x) => + x.orgRevision?.orgRevisionIsDraft == false && + x.orgRevision?.orgRevisionIsCurrent == true, + )?.orgChild3?.ancestorDNA ?? null, + child4DnaId: + profile?.current_holders?.find( + (x) => + x.orgRevision?.orgRevisionIsDraft == false && + x.orgRevision?.orgRevisionIsCurrent == true, + )?.orgChild4?.ancestorDNA ?? null, + commander: fullname, + posLevel: + (profile.posType == null || profile.posType?.posTypeShortName == null + ? "" + : profile.posType?.posTypeShortName + " ") + (profile.posLevel?.posLevelName ?? ""), + posType: profile.posType?.posTypeName ?? null, + profileSalary: latestSalary ?? null, + profileInsignia: latestInsignia ?? null, + profileType: "EMPLOYEE", + }; + + return new HttpSuccess(mapProfile); + } + + const [latestSalary, latestInsignia] = await Promise.all([ + this.salaryRepo.findOne({ + where: { profileId: profile.id }, + order: { commandDateAffect: "DESC" }, + }), + this.insigniaRepo.findOne({ + where: { profileId: profile.id, isDeleted: false }, + order: { receiveDate: "DESC" }, + }), + ]); + + const org = { + root: + profile?.current_holders?.find( + (x) => + x.orgRevision?.orgRevisionIsDraft == false && + x.orgRevision?.orgRevisionIsCurrent == true, + )?.orgRoot?.id ?? null, + child1: + profile?.current_holders?.find( + (x) => + x.orgRevision?.orgRevisionIsDraft == false && + x.orgRevision?.orgRevisionIsCurrent == true, + )?.orgChild1?.id ?? null, + child2: + profile?.current_holders?.find( + (x) => + x.orgRevision?.orgRevisionIsDraft == false && + x.orgRevision?.orgRevisionIsCurrent == true, + )?.orgChild2?.id ?? null, + child3: + profile?.current_holders?.find( + (x) => + x.orgRevision?.orgRevisionIsDraft == false && + x.orgRevision?.orgRevisionIsCurrent == true, + )?.orgChild3?.id ?? null, + child4: + profile?.current_holders?.find( + (x) => + x.orgRevision?.orgRevisionIsDraft == false && + x.orgRevision?.orgRevisionIsCurrent == true, + )?.orgChild4?.id ?? null, + }; + + let fullname = ""; + let pos = await this.posMasterRepository.findOne({ + relations: ["current_holder"], + where: { + current_holder: Not(IsNull()), + orgRevision: { + orgRevisionIsCurrent: true, + orgRevisionIsDraft: false, + }, + isDirector: true, + orgRootId: org?.root ?? "", + orgChild1Id: org?.child1 ?? "", + orgChild2Id: org?.child2 ?? "", + orgChild3Id: org?.child3 ?? "", + orgChild4Id: org?.child4 ?? "", + }, + }); + + if (pos) { + fullname = + pos.current_holder.prefix + + pos.current_holder.firstName + + " " + + pos.current_holder.lastName; + } else { + let pos = await this.posMasterRepository.findOne({ + relations: ["current_holder"], + where: { + current_holder: Not(IsNull()), + orgRevision: { + orgRevisionIsCurrent: true, + orgRevisionIsDraft: false, + }, + isDirector: true, + orgRootId: org?.root ?? "", + orgChild1Id: org?.child1 ?? "", + orgChild2Id: org?.child2 ?? "", + orgChild3Id: org?.child3 ?? "", + orgChild4Id: IsNull(), + }, + }); + if (pos) { + fullname = + pos.current_holder.prefix + + pos.current_holder.firstName + + " " + + pos.current_holder.lastName; + } else { + let pos = await this.posMasterRepository.findOne({ + relations: ["current_holder"], + where: { + current_holder: Not(IsNull()), + orgRevision: { + orgRevisionIsCurrent: true, + orgRevisionIsDraft: false, + }, + isDirector: true, + orgRootId: org?.root ?? "", + orgChild1Id: org?.child1 ?? "", + orgChild2Id: org?.child2 ?? "", + orgChild3Id: IsNull(), + orgChild4Id: IsNull(), + }, + }); + if (pos) { + fullname = + pos.current_holder.prefix + + pos.current_holder.firstName + + " " + + pos.current_holder.lastName; + } else { + let pos = await this.posMasterRepository.findOne({ + relations: ["current_holder"], + where: { + current_holder: Not(IsNull()), + orgRevision: { + orgRevisionIsCurrent: true, + orgRevisionIsDraft: false, + }, + isDirector: true, + orgRootId: org?.root ?? "", + orgChild1Id: org?.child1 ?? "", + orgChild2Id: IsNull(), + orgChild3Id: IsNull(), + orgChild4Id: IsNull(), + }, + }); + if (pos) { + fullname = + pos.current_holder.prefix + + pos.current_holder.firstName + + " " + + pos.current_holder.lastName; + } else { + let pos = await this.posMasterRepository.findOne({ + relations: ["current_holder"], + where: { + current_holder: Not(IsNull()), + orgRevision: { + orgRevisionIsCurrent: true, + orgRevisionIsDraft: false, + }, + orgRootId: org?.root ?? "", + orgChild1Id: IsNull(), + orgChild2Id: IsNull(), + orgChild3Id: IsNull(), + orgChild4Id: IsNull(), + }, + }); + if (pos) { + fullname = + pos.current_holder.prefix + + pos.current_holder.firstName + + " " + + pos.current_holder.lastName; + } else { + fullname = ""; + } + } + } + } + } + + const mapProfile = { + id: profile.id, + avatar: profile.avatar, + avatarName: profile.avatarName, + rank: profile.rank, + prefix: profile.prefix, + firstName: profile.firstName, + lastName: profile.lastName, + citizenId: profile.citizenId, + position: profile.position, + posLevelId: profile.posLevelId, + email: profile.email, + phone: profile.phone, + keycloak: profile.keycloak, + isProbation: profile.isProbation, + isLeave: profile.isLeave, + leaveReason: profile.leaveReason, + dateRetire: profile.dateRetire, + dateAppoint: profile.dateAppoint, + dateRetireLaw: profile.dateRetireLaw, + dateStart: profile.dateStart, + govAgeAbsent: profile.govAgeAbsent, + govAgePlus: profile.govAgePlus, + birthDate: profile.birthDate ?? new Date(), + reasonSameDate: profile.reasonSameDate, + telephoneNumber: profile.phone, + nationality: profile.nationality, + gender: profile.gender, + relationship: profile.relationship, + religion: profile.religion, + bloodGroup: profile.bloodGroup, + registrationAddress: profile.registrationAddress, + registrationProvinceId: profile.registrationProvinceId, + registrationDistrictId: profile.registrationDistrictId, + registrationSubDistrictId: profile.registrationSubDistrictId, + registrationZipCode: profile.registrationZipCode, + currentAddress: profile.currentAddress, + currentProvinceId: profile.currentProvinceId, + currentSubDistrictId: profile.currentSubDistrictId, + currentZipCode: profile.currentZipCode, + dutyTimeId: profile.dutyTimeId, + dutyTimeEffectiveDate: profile.dutyTimeEffectiveDate, + amount: profile.amount, + positionSalaryAmount: profile.positionSalaryAmount, + mouthSalaryAmount: profile.mouthSalaryAmount, + root: + profile?.current_holders?.find( + (x) => + x.orgRevision?.orgRevisionIsDraft == false && + x.orgRevision?.orgRevisionIsCurrent == true, + )?.orgRoot?.orgRootName ?? null, + child1: + profile?.current_holders?.find( + (x) => + x.orgRevision?.orgRevisionIsDraft == false && + x.orgRevision?.orgRevisionIsCurrent == true, + )?.orgChild1?.orgChild1Name ?? null, + child2: + profile?.current_holders?.find( + (x) => + x.orgRevision?.orgRevisionIsDraft == false && + x.orgRevision?.orgRevisionIsCurrent == true, + )?.orgChild2?.orgChild2Name ?? null, + child3: + profile?.current_holders?.find( + (x) => + x.orgRevision?.orgRevisionIsDraft == false && + x.orgRevision?.orgRevisionIsCurrent == true, + )?.orgChild3?.orgChild3Name ?? null, + child4: + profile?.current_holders?.find( + (x) => + x.orgRevision?.orgRevisionIsDraft == false && + x.orgRevision?.orgRevisionIsCurrent == true, + )?.orgChild4?.orgChild4Name ?? null, + rootId: + profile?.current_holders?.find( + (x) => + x.orgRevision?.orgRevisionIsDraft == false && + x.orgRevision?.orgRevisionIsCurrent == true, + )?.orgRootId ?? null, + child1Id: + profile?.current_holders?.find( + (x) => + x.orgRevision?.orgRevisionIsDraft == false && + x.orgRevision?.orgRevisionIsCurrent == true, + )?.orgChild1Id ?? null, + child2Id: + profile?.current_holders?.find( + (x) => + x.orgRevision?.orgRevisionIsDraft == false && + x.orgRevision?.orgRevisionIsCurrent == true, + )?.orgChild2Id ?? null, + child3Id: + profile?.current_holders?.find( + (x) => + x.orgRevision?.orgRevisionIsDraft == false && + x.orgRevision?.orgRevisionIsCurrent == true, + )?.orgChild3Id ?? null, + child4Id: + profile?.current_holders?.find( + (x) => + x.orgRevision?.orgRevisionIsDraft == false && + x.orgRevision?.orgRevisionIsCurrent == true, + )?.orgChild4Id ?? null, + rootDnaId: + profile?.current_holders?.find( + (x) => + x.orgRevision?.orgRevisionIsDraft == false && + x.orgRevision?.orgRevisionIsCurrent == true, + )?.orgRoot?.ancestorDNA ?? null, + child1DnaId: + profile?.current_holders?.find( + (x) => + x.orgRevision?.orgRevisionIsDraft == false && + x.orgRevision?.orgRevisionIsCurrent == true, + )?.orgChild1?.ancestorDNA ?? null, + child2DnaId: + profile?.current_holders?.find( + (x) => + x.orgRevision?.orgRevisionIsDraft == false && + x.orgRevision?.orgRevisionIsCurrent == true, + )?.orgChild2?.ancestorDNA ?? null, + child3DnaId: + profile?.current_holders?.find( + (x) => + x.orgRevision?.orgRevisionIsDraft == false && + x.orgRevision?.orgRevisionIsCurrent == true, + )?.orgChild3?.ancestorDNA ?? null, + child4DnaId: + profile?.current_holders?.find( + (x) => + x.orgRevision?.orgRevisionIsDraft == false && + x.orgRevision?.orgRevisionIsCurrent == true, + )?.orgChild4?.ancestorDNA ?? null, + commander: fullname, + posLevel: profile.posLevel?.posLevelName ?? null, + posType: profile.posType?.posTypeName ?? null, + profileSalary: latestSalary ?? null, + profileInsignia: latestInsignia ?? null, + profileType: "OFFICER", + }; + + return new HttpSuccess(mapProfile); + } + /** * API หา user profile officer * @@ -2431,4 +3125,433 @@ export class OrganizationUnauthorizeController extends Controller { await this.profileEmpRepo.save(profiles); return new HttpSuccess(); } + + /** + * API รายชื่อข้าราชการ (unauthorize) + * + * @summary รายชื่อข้าราชการ + * + */ + @Post("officer-list") + async officerList( + @Body() + body: { + reqNode?: number; + reqNodeId?: string; + date: Date; + }, + ) { + let typeCondition: any = {}; + + // Build typeCondition based on reqNode and reqNodeId (similar to OWNER/ROOT/PARENT logic) + switch (body.reqNode) { + case 0: + typeCondition = { + rootDnaId: body.reqNodeId, + }; + break; + case 1: + typeCondition = { + child1DnaId: body.reqNodeId, + }; + break; + case 2: + typeCondition = { + child2DnaId: body.reqNodeId, + }; + break; + case 3: + typeCondition = { + child3DnaId: body.reqNodeId, + }; + break; + case 4: + typeCondition = { + child4DnaId: body.reqNodeId, + }; + break; + default: + typeCondition = {}; + break; + } + + const date = body.date ? new Date(body.date.toISOString().slice(0, 10)) : new Date(); + // set เวลาเป็น 23:59:59 ของวันนั้น + date.setHours(23, 59, 59, 999); + + let profile = await this.posMasterHistoryRepository.find({ + where: { + ...typeCondition, + createdAt: LessThanOrEqual(date), + }, + order: { + firstName: "ASC", + lastName: "ASC", + createdAt: "DESC", + }, + }); + + // group1: group by ancestorDNA แล้วเลือก create_at ล่าสุด + const grouped1 = new Map(); + for (const item of profile) { + const key = `${item.ancestorDNA}`; + if (!grouped1.has(key)) { + grouped1.set(key, item); + } else { + const exist = grouped1.get(key); + if (exist && item.createdAt > exist.createdAt) { + grouped1.set(key, item); + } + } + } + // group2: group by shortName-posMasterNo จากค่าที่ได้จาก group1 + const grouped2 = new Map(); + for (const item of Array.from(grouped1.values())) { + const key = `${item.shortName}-${item.posMasterNo}`; + if (!grouped2.has(key)) { + grouped2.set(key, item); + } else { + const exist = grouped2.get(key); + if (exist && item.createdAt > exist.createdAt) { + grouped2.set(key, item); + } + } + } + // group3: group by firstName-lastName จากค่าที่ได้จาก group2 + const grouped3 = new Map(); + for (const item of Array.from(grouped2.values())) { + const key = `${item.firstName}-${item.lastName}`; + if (!grouped3.has(key)) { + grouped3.set(key, item); + } else { + const exist = grouped3.get(key); + if (exist && item.createdAt > exist.createdAt) { + grouped3.set(key, item); + } + } + } + + const profile_ = await Promise.all( + Array.from(grouped3.values()) + .filter((x) => x.profileId != null) + .map(async (item: PosMasterHistory) => { + let profile = await this.profileRepo.findOne({ + where: { id: item.profileId }, + }); + + return { + id: item.profileId, + prefix: item.prefix, + firstName: item.firstName, + lastName: item.lastName, + citizenId: profile?.citizenId ?? null, + dateStart: profile?.dateStart ?? null, + dateAppoint: profile?.dateAppoint ?? null, + keycloak: profile?.keycloak ?? null, + posNo: `${item.shortName} ${item.posMasterNo}`, + position: item.position, + positionLevel: item.posLevel, + positionType: item.posType, + orgRootId: item.rootDnaId, + orgChild1Id: item.child1DnaId, + orgChild2Id: item.child2DnaId, + orgChild3Id: item.child3DnaId, + orgChild4Id: item.child4DnaId, + }; + }), + ); + + return new HttpSuccess( + (profile_ ?? []).sort((a, b) => a.posNo.localeCompare(b.posNo, undefined, { numeric: true })), + ); + } + + /** + * API รายชื่อพนักงาน (unauthorize) + * + * @summary รายชื่อพนักงาน + * + */ + @Post("employee-list") + async employeeList( + @Body() + body: { + reqNode?: number; + reqNodeId?: string; + startDate?: Date; + endDate?: Date; + revisionId?: string; + }, + ) { + let typeCondition: any = {}; + + // Build typeCondition based on reqNode and reqNodeId (similar to OWNER/ROOT/PARENT logic) + switch (body.reqNode) { + case 0: + typeCondition = { + orgRoot: { + id: body.reqNodeId, + }, + }; + break; + case 1: + typeCondition = { + orgChild1: { + id: body.reqNodeId, + }, + }; + break; + case 2: + typeCondition = { + orgChild2: { + id: body.reqNodeId, + }, + }; + break; + case 3: + typeCondition = { + orgChild3: { + id: body.reqNodeId, + }, + }; + break; + case 4: + typeCondition = { + orgChild4: { + id: body.reqNodeId, + }, + }; + break; + default: + typeCondition = {}; + break; + } + + let profile = await this.profileEmpRepo.find({ + where: { isLeave: false, isRetirement: false, current_holders: typeCondition }, + relations: [ + "posType", + "posLevel", + "current_holders", + "current_holders.orgRoot", + "current_holders.orgChild1", + "current_holders.orgChild2", + "current_holders.orgChild3", + "current_holders.orgChild4", + ], + order: { + current_holders: { + orgRoot: { + orgRootOrder: "ASC", + }, + orgChild1: { + orgChild1Order: "ASC", + }, + orgChild2: { + orgChild2Order: "ASC", + }, + orgChild3: { + orgChild3Order: "ASC", + }, + orgChild4: { + orgChild4Order: "ASC", + }, + posMasterNo: "ASC", + }, + }, + }); + + let findRevision = await this.orgRevisionRepository.findOne({ + where: { orgRevisionIsCurrent: true, orgRevisionIsDraft: false }, + }); + + if (body.revisionId) { + findRevision = await this.orgRevisionRepository.findOne({ + where: { id: body.revisionId }, + }); + } + + const profile_ = await Promise.all( + profile.map(async (item: ProfileEmployee) => { + const shortName = + item.current_holders.length == 0 + ? null + : item.current_holders.find((x) => x.orgRevisionId == findRevision?.id) != null && + item.current_holders.find((x) => x.orgRevisionId == findRevision?.id)?.orgChild4 != + null + ? `${item.current_holders.find((x) => x.orgRevisionId == findRevision?.id)?.orgChild4.orgChild4ShortName} ${item.current_holders.find((x) => x.orgRevisionId == findRevision?.id)?.posMasterNo}` + : item.current_holders.find((x) => x.orgRevisionId == findRevision?.id) != null && + item.current_holders.find((x) => x.orgRevisionId == findRevision?.id) + ?.orgChild3 != null + ? `${item.current_holders.find((x) => x.orgRevisionId == findRevision?.id)?.orgChild3.orgChild3ShortName} ${item.current_holders.find((x) => x.orgRevisionId == findRevision?.id)?.posMasterNo}` + : item.current_holders.find((x) => x.orgRevisionId == findRevision?.id) != null && + item.current_holders.find((x) => x.orgRevisionId == findRevision?.id) + ?.orgChild2 != null + ? `${item.current_holders.find((x) => x.orgRevisionId == findRevision?.id)?.orgChild2.orgChild2ShortName} ${item.current_holders.find((x) => x.orgRevisionId == findRevision?.id)?.posMasterNo}` + : item.current_holders.find((x) => x.orgRevisionId == findRevision?.id) != null && + item.current_holders.find((x) => x.orgRevisionId == findRevision?.id) + ?.orgChild1 != null + ? `${item.current_holders.find((x) => x.orgRevisionId == findRevision?.id)?.orgChild1.orgChild1ShortName} ${item.current_holders.find((x) => x.orgRevisionId == findRevision?.id)?.posMasterNo}` + : item.current_holders.find((x) => x.orgRevisionId == findRevision?.id) != + null && + item.current_holders.find((x) => x.orgRevisionId == findRevision?.id) + ?.orgRoot != null + ? `${item.current_holders.find((x) => x.orgRevisionId == findRevision?.id)?.orgRoot.orgRootShortName} ${item.current_holders.find((x) => x.orgRevisionId == findRevision?.id)?.posMasterNo}` + : null; + const Oc = + item.current_holders.length == 0 + ? null + : item.current_holders[0].orgChild4 != null + ? `${item.current_holders[0].orgChild4.orgChild4Name}/${item.current_holders[0].orgChild3.orgChild3Name}/${item.current_holders[0].orgChild2.orgChild2Name}/${item.current_holders[0].orgChild1.orgChild1Name}/${item.current_holders[0].orgRoot.orgRootName}` + : item.current_holders[0].orgChild3 != null + ? `${item.current_holders[0].orgChild3.orgChild3Name}/${item.current_holders[0].orgChild2.orgChild2Name}/${item.current_holders[0].orgChild1.orgChild1Name}/${item.current_holders[0].orgRoot.orgRootName}` + : item.current_holders[0].orgChild2 != null + ? `${item.current_holders[0].orgChild2.orgChild2Name}/${item.current_holders[0].orgChild1.orgChild1Name}/${item.current_holders[0].orgRoot.orgRootName}` + : item.current_holders[0].orgChild1 != null + ? `${item.current_holders[0].orgChild1.orgChild1Name}/${item.current_holders[0].orgRoot.orgRootName}` + : item.current_holders[0].orgRoot != null + ? `${item.current_holders[0].orgRoot.orgRootName}` + : null; + + let _posMaster = await this.empPosMasterRepository.findOne({ + where: { + orgRevisionId: findRevision?.id, + current_holderId: item.id, + }, + }); + + return { + id: item.id, + prefix: item.prefix, + firstName: item.firstName, + lastName: item.lastName, + citizenId: item.citizenId, + dateStart: item.dateStart, + dateAppoint: item.dateAppoint, + keycloak: item.keycloak, + posNo: shortName, + position: item.position, + positionLevel: + item.posType?.posTypeShortName && item.posLevel?.posLevelName + ? `${item.posType?.posTypeShortName} ${item.posLevel?.posLevelName}` + : null, + positionType: item.posType?.posTypeName ?? null, + oc: Oc, + orgRootId: _posMaster?.orgRootId, + orgChild1Id: _posMaster?.orgChild1Id, + orgChild2Id: _posMaster?.orgChild2Id, + orgChild3Id: _posMaster?.orgChild3Id, + orgChild4Id: _posMaster?.orgChild4Id, + }; + }), + ); + + return new HttpSuccess(profile_); + } + + /** + * API สร้างข้อมูลการมาสาย/ขาดราชการของข้าราชการ (สำหรับ schedule) + * @summary API สร้างข้อมูลการมาสาย/ขาดราชการของข้าราชการ (สำหรับ Job schedule) + */ + @Post("profile/absent-late/batch") + async newAbsentLateBatch(@Body() body: CreateProfileAbsentLateBatch) { + // กรณีไม่มีข้อมูลส่งมา (วันที่ไม่มีคนขาด/มาสาย) + if (!body.records || body.records.length === 0) { + return new HttpSuccess({ count: 0, ids: [] }); + } + + const profileIds = [...new Set(body.records.map((r) => r.profileId))]; + const profiles = await this.profileRepo.findBy({ + id: In(profileIds), + }); + + const foundProfileIds = new Set(profiles.map((p) => p.id)); + const validRecords = body.records.filter((r) => foundProfileIds.has(r.profileId)); + + // กรณีไม่พบ profile เลย + if (validRecords.length === 0) { + return new HttpSuccess({ count: 0, ids: [] }); + } + + const meta = { + createdUserId: "SYSTEM", + createdFullName: "SYSTEM", + lastUpdateUserId: "SYSTEM", + lastUpdateFullName: "SYSTEM", + createdAt: new Date(), + lastUpdatedAt: new Date(), + }; + + const records = validRecords.map((item) => { + const data = new ProfileAbsentLate(); + Object.assign(data, { ...item, ...meta }); + return data; + }); + + const result = await this.absentLateRepo.save(records); + + // บันทึก history สำหรับแต่ละ record + const historyRecords = result.map((data) => { + const history = new ProfileAbsentLateHistory(); + Object.assign(history, { ...data, id: undefined }); + history.profileAbsentLateId = data.id; + return history; + }); + await this.absentLateHistoryRepo.save(historyRecords); + + return new HttpSuccess({ count: result.length, ids: result.map((r) => r.id) }); + } + + /** + * API สร้างข้อมูลการมาสาย/ขาดราชการของลูกจ้างประจำ (สำหรับ schedule) + * @summary API สร้างข้อมูลการมาสาย/ขาดราชการของลูกจ้างประจำ (สำหรับ schedule) + */ + @Post("profile-employee/absent-late/batch") + async newEmpAbsentLateBatch(@Body() body: CreateProfileEmployeeAbsentLateBatch) { + // กรณีไม่มีข้อมูลส่งมา (วันที่ไม่มีคนขาด/มาสาย) + if (!body.records || body.records.length === 0) { + return new HttpSuccess({ count: 0, ids: [] }); + } + + const profileIds = [...new Set(body.records.map((r) => r.profileEmployeeId))]; + const profiles = await this.profileEmpRepo.findBy({ + id: In(profileIds), + }); + + const foundProfileIds = new Set(profiles.map((p) => p.id)); + const validRecords = body.records.filter((r) => foundProfileIds.has(r.profileEmployeeId)); + + // กรณีไม่พบ profile เลย + if (validRecords.length === 0) { + return new HttpSuccess({ count: 0, ids: [] }); + } + + const meta = { + createdUserId: "SYSTEM", + createdFullName: "SYSTEM", + lastUpdateUserId: "SYSTEM", + lastUpdateFullName: "SYSTEM", + createdAt: new Date(), + lastUpdatedAt: new Date(), + }; + + const records = validRecords.map((item) => { + const data = new ProfileEmployeeAbsentLate(); + Object.assign(data, { ...item, ...meta }); + return data; + }); + + const result = await this.empAbsentLateRepo.save(records); + + // บันทึก history สำหรับแต่ละ record + const historyRecords = result.map((data) => { + const history = new ProfileEmployeeAbsentLateHistory(); + Object.assign(history, { ...data, id: undefined }); + history.profileEmployeeAbsentLateId = data.id; + return history; + }); + await this.empAbsentLateHistoryRepo.save(historyRecords); + + return new HttpSuccess({ count: result.length, ids: result.map((r) => r.id) }); + } } diff --git a/src/controllers/PermissionController.ts b/src/controllers/PermissionController.ts index b082f0fd..801d4b97 100644 --- a/src/controllers/PermissionController.ts +++ b/src/controllers/PermissionController.ts @@ -734,8 +734,10 @@ export class PermissionController extends Controller { }; } else if (privilege == "PARENT") { data = { - root: [x.orgRootId], - child1: [null], + // root: [x.orgRootId], + // child1: [null], + root: null, + child1: null, child2: null, child3: null, child4: null, diff --git a/src/controllers/PermissionProfileController.ts b/src/controllers/PermissionProfileController.ts index 4503dc3c..9f565828 100644 --- a/src/controllers/PermissionProfileController.ts +++ b/src/controllers/PermissionProfileController.ts @@ -448,13 +448,13 @@ export class PermissionProfileController extends Controller { orgRootId: _data.orgRootId, isCheck: _data.isCheck, isEdit: _data.isEdit, - orgNew: _data.orgRootTree.orgRootName, - avatar: _data.profileTree.avatar, - avatarName: _data.profileTree.avatarName, - prefix: _data.profileTree.prefix, - rank: _data.profileTree.rank, - firstName: _data.profileTree.firstName, - lastName: _data.profileTree.lastName, + orgNew: _data.orgRootTree?.orgRootName, + avatar: _data.profileTree?.avatar, + avatarName: _data.profileTree?.avatarName, + prefix: _data.profileTree?.prefix, + rank: _data.profileTree?.rank, + firstName: _data.profileTree?.firstName, + lastName: _data.profileTree?.lastName, org: (_child4 == null ? "" : _child4 + "\n") + (_child3 == null ? "" : _child3 + "\n") + @@ -462,10 +462,10 @@ export class PermissionProfileController extends Controller { (_child1 == null ? "" : _child1 + "\n") + (_root == null ? "" : _root), posNo: shortName, - position: _data.profileTree.position, - posType: _data.profileTree.posType == null ? null : _data.profileTree.posType.posTypeName, + position: _data.profileTree?.position, + posType: _data.profileTree?.posType == null ? null : _data.profileTree?.posType.posTypeName, posLevel: - _data.profileTree.posLevel == null ? null : _data.profileTree.posLevel.posLevelName, + _data.profileTree?.posLevel == null ? null : _data.profileTree?.posLevel.posLevelName, }; }), ); diff --git a/src/controllers/PosMasterActController.ts b/src/controllers/PosMasterActController.ts index 4d449d21..dd4acd1b 100644 --- a/src/controllers/PosMasterActController.ts +++ b/src/controllers/PosMasterActController.ts @@ -17,9 +17,13 @@ import HttpStatusCode from "../interfaces/http-status"; import HttpError from "../interfaces/http-error"; import { PosMasterAct } from "../entities/PosMasterAct"; import { PosMaster } from "../entities/PosMaster"; -import { Brackets, LessThan, MoreThan } from "typeorm"; +import { Brackets, In, IsNull, LessThan, MoreThan, Not } from "typeorm"; +import permission from "../interfaces/permission"; import { OrgRevision } from "../entities/OrgRevision"; import Extension from "../interfaces/extension"; +import { ProfileActposition } from "../entities/ProfileActposition"; +import { RequestWithUser } from "../middlewares/user"; +import { escape } from "querystring"; @Route("api/v1/org/pos/act") @Tags("PosMasterAct") @@ -32,6 +36,7 @@ export class PosMasterActController extends Controller { private orgRevisionRepository = AppDataSource.getRepository(OrgRevision); private posMasterActRepository = AppDataSource.getRepository(PosMasterAct); private posMasterRepository = AppDataSource.getRepository(PosMaster); + private actpositionRepository = AppDataSource.getRepository(ProfileActposition); /** * API เพิ่มรักษาการในตำแหน่ง @@ -87,6 +92,195 @@ export class PosMasterActController extends Controller { return new HttpSuccess(posMasterAct); } + + /** + * API ค้นหาตำแหน่งในระบบสมัครสอบ ขรก. + * + * @summary ค้นหาตำแหน่งในระบบสมัครสอบ ขรก. + * + */ + @Post("search") + async searchAct( + @Request() request: RequestWithUser, + @Body() + body: { + posmasterId: string; + isAll: boolean; + isAllRoot?: boolean; + page?: number; + pageSize?: number; + keyword?: string; + }, + ) { + await new permission().PermissionGet(request, "SYS_ACTING"); + + const { page = 1, pageSize = 100, keyword } = body; + + const posMasterMain = await this.posMasterRepository.findOne({ + where: { id: body.posmasterId }, + relations: ["posMasterActs"], + }); + + if (!posMasterMain) { + throw new HttpError(HttpStatusCode.NOT_FOUND, "ไม่พบข้อมูลประเภทตำแหน่งนี้"); + } + + let posId: any[] = posMasterMain.posMasterActs.map( + (x) => x.posMasterChildId + ); + posId.push(body.posmasterId); + + const query = await AppDataSource.getRepository(PosMaster) + .createQueryBuilder("posMaster") + .leftJoinAndSelect("posMaster.orgRoot", "orgRoot") + .leftJoinAndSelect("posMaster.orgChild1", "orgChild1") + .leftJoinAndSelect("posMaster.orgChild2", "orgChild2") + .leftJoinAndSelect("posMaster.orgChild3", "orgChild3") + .leftJoinAndSelect("posMaster.orgChild4", "orgChild4") + .leftJoinAndSelect("posMaster.current_holder", "current_holder") + .leftJoinAndSelect("current_holder.posLevel", "posLevel") + .leftJoinAndSelect("current_holder.posType", "posType") + .where("posMaster.current_holderId IS NOT NULL") + .andWhere("posMaster.id NOT IN (:...posId)", { posId }); + + if (!body.isAllRoot) { + if (body.isAll) { + if (posMasterMain.orgChild4Id) { + query.andWhere("posMaster.orgChild4Id = :id", { + id: posMasterMain.orgChild4Id, + }); + } else if (posMasterMain.orgChild3Id) { + query.andWhere("posMaster.orgChild3Id = :id", { + id: posMasterMain.orgChild3Id, + }); + } else if (posMasterMain.orgChild2Id) { + query.andWhere("posMaster.orgChild2Id = :id", { + id: posMasterMain.orgChild2Id, + }); + } else if (posMasterMain.orgChild1Id) { + query.andWhere("posMaster.orgChild1Id = :id", { + id: posMasterMain.orgChild1Id, + }); + } else { + query.andWhere("posMaster.orgRootId = :id", { + id: posMasterMain.orgRootId, + }); + } + } else { + query + .andWhere( + posMasterMain.orgRootId == null + ? "posMaster.orgRootId IS NULL" + : "posMaster.orgRootId = :orgRootId", + { orgRootId: posMasterMain.orgRootId } + ) + .andWhere( + posMasterMain.orgChild1Id == null + ? "posMaster.orgChild1Id IS NULL" + : "posMaster.orgChild1Id = :orgChild1Id", + { orgChild1Id: posMasterMain.orgChild1Id } + ) + .andWhere( + posMasterMain.orgChild2Id == null + ? "posMaster.orgChild2Id IS NULL" + : "posMaster.orgChild2Id = :orgChild2Id", + { orgChild2Id: posMasterMain.orgChild2Id } + ) + .andWhere( + posMasterMain.orgChild3Id == null + ? "posMaster.orgChild3Id IS NULL" + : "posMaster.orgChild3Id = :orgChild3Id", + { orgChild3Id: posMasterMain.orgChild3Id } + ) + .andWhere( + posMasterMain.orgChild4Id == null + ? "posMaster.orgChild4Id IS NULL" + : "posMaster.orgChild4Id = :orgChild4Id", + { orgChild4Id: posMasterMain.orgChild4Id } + ); + } + } else { + query.andWhere("posMaster.orgRootId = :orgRootId", { + orgRootId: posMasterMain.orgRootId, + }); + } + + if (keyword) { + query.andWhere( + new Brackets((qb) => { + qb.where( + `CONCAT(current_holder.prefix, current_holder.firstName, ' ', current_holder.lastName) LIKE :keyword`, + { keyword: `%${keyword}%` } + ) + .orWhere(`current_holder.citizenId LIKE :keyword`, { + keyword: `%${keyword}%`, + }) + .orWhere( + `CONCAT( + CASE + WHEN orgChild4.id IS NOT NULL THEN orgChild4.orgChild4ShortName + WHEN orgChild3.id IS NOT NULL THEN orgChild3.orgChild3ShortName + WHEN orgChild2.id IS NOT NULL THEN orgChild2.orgChild2ShortName + WHEN orgChild1.id IS NOT NULL THEN orgChild1.orgChild1ShortName + WHEN orgRoot.id IS NOT NULL THEN orgRoot.orgRootShortName + ELSE '' + END, + ' ', + posMaster.posMasterNo + ) LIKE :keyword`, + { keyword: `%${keyword}%` } + ) + .orWhere(`posLevel.posLevelName LIKE :keyword`, { + keyword: `%${keyword}%`, + }) + .orWhere(`posType.posTypeName LIKE :keyword`, { + keyword: `%${keyword}%`, + }) + .orWhere(`current_holder.position LIKE :keyword`, { + keyword: `%${keyword}%`, + }) + }) + ); + } + + query.skip((page - 1) * pageSize).take(pageSize); + + const [posMaster, total] = await query.getManyAndCount(); + + const data = await Promise.all( + posMaster + .sort((a, b) => a.posMasterOrder - b.posMasterOrder) + .map((item) => { + const shortName = + item.orgChild4 != null + ? `${item.orgChild4.orgChild4ShortName} ${item.posMasterNo}` + : item?.orgChild3 != null + ? `${item.orgChild3.orgChild3ShortName} ${item.posMasterNo}` + : item?.orgChild2 != null + ? `${item.orgChild2.orgChild2ShortName} ${item.posMasterNo}` + : item?.orgChild1 != null + ? `${item.orgChild1.orgChild1ShortName} ${item.posMasterNo}` + : item?.orgRoot != null + ? `${item.orgRoot.orgRootShortName} ${item.posMasterNo}` + : null; + return { + id: item.id, + citizenId: item.current_holder?.citizenId ?? null, + isDirector: item.isDirector ?? null, + prefix: item.current_holder?.prefix ?? null, + firstName: item.current_holder?.firstName ?? null, + lastName: item.current_holder?.lastName ?? null, + posLevel: item.current_holder?.posLevel?.posLevelName ?? null, + posType: item.current_holder?.posType?.posTypeName ?? null, + position: item.current_holder?.position ?? null, + posNo: shortName, + }; + }), + ); + return new HttpSuccess({ data: data, total }); + } + + /** * API ลบรักษาการในตำแหน่ง * @@ -385,6 +579,7 @@ export class PosMasterActController extends Controller { posType: item.posMasterChild?.current_holder?.posType?.posTypeName ?? null, position: item.posMasterChild?.current_holder?.position ?? null, posNo: shortName, + statusReport: item.statusReport, }; }), ); @@ -495,12 +690,12 @@ export class PosMasterActController extends Controller { x.posMasterChild?.orgRoot?.orgRootShortName, ].find((name) => !!name) && x.posMasterChild?.posMasterNo ? `${[ - x.posMasterChild?.orgChild4?.orgChild4ShortName, - x.posMasterChild?.orgChild3?.orgChild3ShortName, - x.posMasterChild?.orgChild2?.orgChild2ShortName, - x.posMasterChild?.orgChild1?.orgChild1ShortName, - x.posMasterChild?.orgRoot?.orgRootShortName, - ].find((name) => !!name)} ${x.posMasterChild.posMasterNo}` + x.posMasterChild?.orgChild4?.orgChild4ShortName, + x.posMasterChild?.orgChild3?.orgChild3ShortName, + x.posMasterChild?.orgChild2?.orgChild2ShortName, + x.posMasterChild?.orgChild1?.orgChild1ShortName, + x.posMasterChild?.orgRoot?.orgRootShortName, + ].find((name) => !!name)} ${x.posMasterChild.posMasterNo}` : x.posMasterChild?.posMasterNo || null; const orgShortNameAct = [ @@ -535,4 +730,110 @@ export class PosMasterActController extends Controller { return new HttpSuccess(_posMaster); } + + /** + * API รักษาการในตำแหน่ง active โดยไม่ต้องออกคำสั่ง + * @summary รักษาการในตำแหน่ง active ในระบบโดยไม่ต้องออกคำสั่ง (SUPER ADMIN) + * @param {string} id Id หน่วยงาน + */ + @Post("{id}") + async activePosMasterAct(@Path() id: string, @Request() req: { user: Record }) { + const posMasterActs = await this.posMasterActRepository + .createQueryBuilder("posMasterAct") + .leftJoinAndSelect("posMasterAct.posMaster", "posMaster") + .leftJoinAndSelect("posMaster.orgRoot", "orgRoot") + .leftJoinAndSelect("posMaster.orgChild1", "orgChild1") + .leftJoinAndSelect("posMaster.orgChild2", "orgChild2") + .leftJoinAndSelect("posMaster.orgChild3", "orgChild3") + .leftJoinAndSelect("posMaster.orgChild4", "orgChild4") + .leftJoinAndSelect("posMaster.current_holder", "current_holder") + .leftJoinAndSelect("posMasterAct.posMasterChild", "posMasterChild") + .where("posMaster.orgRootId = :orgRootId", { orgRootId: id }) + .andWhere("posMasterAct.statusReport = :statusReport", { statusReport: "PENDING" }) + .select([ + "posMasterAct.id", + "posMasterAct.statusReport", + "posMaster.posMasterNo", + "orgRoot.orgRootShortName", + "orgChild1.orgChild1ShortName", + "orgChild2.orgChild2ShortName", + "orgChild3.orgChild3ShortName", + "orgChild4.orgChild4ShortName", + "current_holder.position", + "posMasterChild.current_holderId", + ]) + .getMany(); + + if (posMasterActs.length === 0) { + throw new HttpError(HttpStatusCode.NOT_FOUND, "ไม่พบข้อมูลรักษาการในตำแหน่งของหน่วยงานนี้"); + } + + await Promise.all( + posMasterActs.map(async (posMasterAct) => { + const orgShortName = + [ + posMasterAct.posMaster?.orgChild4?.orgChild4ShortName, + posMasterAct.posMaster?.orgChild3?.orgChild3ShortName, + posMasterAct.posMaster?.orgChild2?.orgChild2ShortName, + posMasterAct.posMaster?.orgChild1?.orgChild1ShortName, + posMasterAct.posMaster?.orgRoot?.orgRootShortName, + ].find(Boolean) ?? ""; + + const profileId = posMasterAct.posMasterChild?.current_holderId; + + if (profileId) { + const existingActivePositions = await this.actpositionRepository.find({ + select: [ + "id", + "status", + "lastUpdateUserId", + "lastUpdateFullName", + "lastUpdatedAt", + "dateEnd", + "isDeleted" + ], + where: { profileId, status: true, isDeleted: false }, + }); + + if (existingActivePositions.length > 0) { + await Promise.all( + existingActivePositions.map(async (pos) => { + Object.assign(pos, { + status: false, + lastUpdateUserId: req.user?.sub ?? null, + lastUpdateFullName: req.user?.name ?? null, + lastUpdatedAt: new Date(), + dateEnd: new Date(), + }); + await this.actpositionRepository.save(pos); + }), + ); + } + } + + const dataAct = new ProfileActposition(); + Object.assign(dataAct, { + profileId: profileId ?? null, + dateStart: new Date(), + posNo: + orgShortName && posMasterAct.posMaster?.posMasterNo + ? `${orgShortName} ${posMasterAct.posMaster.posMasterNo}` + : posMasterAct.posMaster?.posMasterNo ?? "-", + position: posMasterAct.posMaster?.current_holder?.position ?? null, + posNoAbb: orgShortName, + status: true, + createdUserId: req.user?.sub ?? null, + createdFullName: req.user?.name ?? null, + lastUpdateUserId: req.user?.sub ?? null, + lastUpdateFullName: req.user?.name ?? null, + }); + await this.actpositionRepository.save(dataAct); + + posMasterAct.statusReport = "DONE"; + await this.posMasterActRepository.save(posMasterAct); + }), + ); + + return new HttpSuccess(); + } } diff --git a/src/controllers/PositionController.ts b/src/controllers/PositionController.ts index d8aee369..75d6c2a0 100644 --- a/src/controllers/PositionController.ts +++ b/src/controllers/PositionController.ts @@ -38,12 +38,13 @@ import { EmployeePosLevel } from "../entities/EmployeePosLevel"; import { AuthRole } from "../entities/AuthRole"; import { RequestWithUser } from "../middlewares/user"; import permission from "../interfaces/permission"; -import { setLogDataDiff } from "../interfaces/utils"; +import { resolveNodeLevel, setLogDataDiff } from "../interfaces/utils"; import { PosMasterAssign } from "../entities/PosMasterAssign"; import { Assign } from "../entities/Assign"; import { ProfileEmployee } from "../entities/ProfileEmployee"; import { PosMasterHistory } from "../entities/PosMasterHistory"; import { CreatePosMasterHistoryOfficer } from "../services/PositionService"; +import { KeycloakAttributeService } from "../services/KeycloakAttributeService"; @Route("api/v1/org/pos") @Tags("Position") @Security("bearerAuth") @@ -73,6 +74,7 @@ export class PositionController extends Controller { private authRoleRepo = AppDataSource.getRepository(AuthRole); private posMasterAssignRepo = AppDataSource.getRepository(PosMasterAssign); private assignRepo = AppDataSource.getRepository(Assign); + private keycloakAttributeService = new KeycloakAttributeService(); /** * API เพิ่มตำแหน่ง @@ -1449,7 +1451,8 @@ export class PositionController extends Controller { }), ); - if (posMaster.orgRevision?.orgRevisionIsCurrent == true) { + // ถ้าไม่ใช่ตำแหน่งนั่งทับ (isSit = false) ถึงจะอัพเดทตำแหน่งในทะเบียนประวัติ + if (posMaster.orgRevision?.orgRevisionIsCurrent == true && !posMaster.isSit) { const _position = requestBody.positions.find((p) => p.positionIsSelected == true); if (_position) { const current_holderId: any = posMaster.current_holderId; @@ -1510,7 +1513,8 @@ export class PositionController extends Controller { posMaster.orgRootId !== null && posMaster.orgChild1Id == null && posMaster.orgChild2Id == null && - posMaster.orgChild3Id == null + posMaster.orgChild3Id == null && + posMaster.orgChild4Id == null ) { shortName = posMaster.orgRoot.orgRootShortName; orgId = posMaster.orgRootId; @@ -1518,7 +1522,8 @@ export class PositionController extends Controller { posMaster.orgRootId !== null && posMaster.orgChild1Id !== null && posMaster.orgChild2Id == null && - posMaster.orgChild3Id == null + posMaster.orgChild3Id == null && + posMaster.orgChild4Id == null ) { shortName = posMaster.orgChild1.orgChild1ShortName; orgId = posMaster.orgChild1Id; @@ -1526,7 +1531,8 @@ export class PositionController extends Controller { posMaster.orgRootId !== null && posMaster.orgChild1Id !== null && posMaster.orgChild2Id !== null && - posMaster.orgChild3Id == null + posMaster.orgChild3Id == null && + posMaster.orgChild4Id == null ) { shortName = posMaster.orgChild2.orgChild2ShortName; orgId = posMaster.orgChild2Id; @@ -1534,7 +1540,8 @@ export class PositionController extends Controller { posMaster.orgRootId !== null && posMaster.orgChild1Id !== null && posMaster.orgChild2Id !== null && - posMaster.orgChild3Id !== null + posMaster.orgChild3Id !== null && + posMaster.orgChild4Id == null ) { shortName = posMaster.orgChild3.orgChild3ShortName; orgId = posMaster.orgChild3Id; @@ -1542,7 +1549,8 @@ export class PositionController extends Controller { posMaster.orgRootId !== null && posMaster.orgChild1Id !== null && posMaster.orgChild2Id !== null && - posMaster.orgChild3Id !== null + posMaster.orgChild3Id !== null && + posMaster.orgChild4Id !== null ) { shortName = posMaster.orgChild4.orgChild4ShortName; orgId = posMaster.orgChild4Id; @@ -1998,7 +2006,7 @@ export class PositionController extends Controller { posMaster.orgChild3Id == null ) { body.type = 0; - shortName = posMaster.orgRoot.orgRootShortName; + shortName = posMaster.orgRoot?.orgRootShortName; } else if ( posMaster.orgRootId !== null && posMaster.orgChild1Id !== null && @@ -2006,7 +2014,7 @@ export class PositionController extends Controller { posMaster.orgChild3Id == null ) { body.type = 1; - shortName = posMaster.orgChild1.orgChild1ShortName; + shortName = posMaster.orgChild1?.orgChild1ShortName; } else if ( posMaster.orgRootId !== null && posMaster.orgChild1Id !== null && @@ -2014,7 +2022,7 @@ export class PositionController extends Controller { posMaster.orgChild3Id == null ) { body.type = 2; - shortName = posMaster.orgChild2.orgChild2ShortName; + shortName = posMaster.orgChild2?.orgChild2ShortName; } else if ( posMaster.orgRootId !== null && posMaster.orgChild1Id !== null && @@ -2022,7 +2030,7 @@ export class PositionController extends Controller { posMaster.orgChild3Id !== null ) { body.type = 3; - shortName = posMaster.orgChild3.orgChild3ShortName; + shortName = posMaster.orgChild3?.orgChild3ShortName; } else if ( posMaster.orgRootId !== null && posMaster.orgChild1Id !== null && @@ -2030,7 +2038,7 @@ export class PositionController extends Controller { posMaster.orgChild3Id !== null ) { body.type = 4; - shortName = posMaster.orgChild4.orgChild4ShortName; + shortName = posMaster.orgChild4?.orgChild4ShortName; } return { @@ -2385,7 +2393,8 @@ export class PositionController extends Controller { _data.child1 != undefined && _data.child1 != null ? _data.child1[0] != null ? "posMaster.orgChild1Id IN (:...child1)" - : `posMaster.orgChild1Id is ${_data.privilege == "PARENT" ? "not null" : "null"}` + // : `posMaster.orgChild1Id is ${_data.privilege == "PARENT" ? "not null" : "null"}` + : `posMaster.orgChild1Id is null` : "1=1", { child1: _data.child1 } ) @@ -3818,11 +3827,14 @@ export class PositionController extends Controller { where: { id: requestBody.position, posMasterId: requestBody.posMaster }, }); if (_position) { - _profile.position = _position.positionName; - _profile.posTypeId = _position.posTypeId; - _profile.posLevelId = _position.posLevelId; - await this.profileRepository.save(_profile); - setLogDataDiff(request, { before, after: _profile }); + // ถ้าไม่ใช่ตำแหน่งนั่งทับ (isSit = false) ถึงจะอัพเดทตำแหน่งในทะเบียนประวัติ + if(!dataMaster.isSit){ + _profile.position = _position.positionName; + _profile.posTypeId = _position.posTypeId; + _profile.posLevelId = _position.posLevelId; + await this.profileRepository.save(_profile); + setLogDataDiff(request, { before, after: _profile }); + } } } dataMaster.current_holderId = requestBody.profileId; @@ -3862,6 +3874,13 @@ export class PositionController extends Controller { await CreatePosMasterHistoryOfficer(dataMaster.id, request, "DELETE"); } + if (dataMaster.current_holderId) { + await this.keycloakAttributeService.clearOrgDnaAttributes( + [dataMaster.current_holderId], + "PROFILE", + ); + } + let _profileId: string = ""; if (dataMaster?.current_holderId) { _profileId = dataMaster?.current_holderId; @@ -4717,6 +4736,8 @@ export class PositionController extends Controller { posMaster.next_holder == null ? null : `${posMaster.next_holder.prefix}${posMaster.next_holder.firstName} ${posMaster.next_holder.lastName}`, + isCondition: posMaster.isCondition, + conditionReason: posMaster.conditionReason, positions: posMaster.positions.map((position) => ({ id: position.id, positionName: position.positionName, @@ -4938,101 +4959,6 @@ export class PositionController extends Controller { return new HttpSuccess({ data: formattedData, total }); } - /** - * API ค้นหาตำแหน่งในระบบสมัครสอบ ขรก. - * - * @summary ค้นหาตำแหน่งในระบบสมัครสอบ ขรก. - * - */ - @Post("act/search") - async searchAct( - @Request() request: RequestWithUser, - @Body() - body: { - posmasterId: string; - isAll: boolean; - }, - ) { - await new permission().PermissionGet(request, "SYS_ACTING"); - const posMasterMain = await this.posMasterRepository.findOne({ - where: { id: body.posmasterId }, - relations: ["posMasterActs"], - }); - if (posMasterMain == null) { - throw new HttpError(HttpStatusCode.NOT_FOUND, "ไม่พบข้อมูลประเภทตำแหน่งนี้"); - } - let posId: any = posMasterMain.posMasterActs.map((x) => x.posMasterChildId); - posId.push(body.posmasterId); - let typeCondition: any = {}; - if (body.isAll == true) { - typeCondition = { - orgRootId: posMasterMain.orgRootId, - orgChild1Id: posMasterMain.orgChild1Id, - orgChild2Id: posMasterMain.orgChild2Id, - orgChild3Id: posMasterMain.orgChild3Id, - orgChild4Id: posMasterMain.orgChild4Id, - current_holderId: Not(IsNull()), - id: Not(In(posId)), - }; - } else { - typeCondition = { - orgRootId: posMasterMain.orgRootId == null ? IsNull() : posMasterMain.orgRootId, - orgChild1Id: posMasterMain.orgChild1Id == null ? IsNull() : posMasterMain.orgChild1Id, - orgChild2Id: posMasterMain.orgChild2Id == null ? IsNull() : posMasterMain.orgChild2Id, - orgChild3Id: posMasterMain.orgChild3Id == null ? IsNull() : posMasterMain.orgChild3Id, - orgChild4Id: posMasterMain.orgChild4Id == null ? IsNull() : posMasterMain.orgChild4Id, - current_holderId: Not(IsNull()), - id: Not(In(posId)), - }; - } - - const posMaster = await this.posMasterRepository.find({ - where: typeCondition, - relations: [ - "orgRoot", - "orgChild1", - "orgChild2", - "orgChild3", - "orgChild4", - "current_holder", - "current_holder.posLevel", - "current_holder.posType", - ], - }); - - const data = await Promise.all( - posMaster - .sort((a, b) => a.posMasterOrder - b.posMasterOrder) - .map((item) => { - const shortName = - item.orgChild4 != null - ? `${item.orgChild4.orgChild4ShortName} ${item.posMasterNo}` - : item?.orgChild3 != null - ? `${item.orgChild3.orgChild3ShortName} ${item.posMasterNo}` - : item?.orgChild2 != null - ? `${item.orgChild2.orgChild2ShortName} ${item.posMasterNo}` - : item?.orgChild1 != null - ? `${item.orgChild1.orgChild1ShortName} ${item.posMasterNo}` - : item?.orgRoot != null - ? `${item.orgRoot.orgRootShortName} ${item.posMasterNo}` - : null; - return { - id: item.id, - citizenId: item.current_holder?.citizenId ?? null, - isDirector: item.isDirector ?? null, - prefix: item.current_holder?.prefix ?? null, - firstName: item.current_holder?.firstName ?? null, - lastName: item.current_holder?.lastName ?? null, - posLevel: item.current_holder?.posLevel?.posLevelName ?? null, - posType: item.current_holder?.posType?.posTypeName ?? null, - position: item.current_holder?.position ?? null, - posNo: shortName, - }; - }), - ); - return new HttpSuccess(data); - } - /** * API บันทึกตำแหน่งใหม่ * @@ -5272,9 +5198,41 @@ export class PositionController extends Controller { let searchShortName3 = `CONCAT(orgChild3.orgChild3ShortName," ",COALESCE(posMaster.posMasterNoPrefix, ""),posMaster.posMasterNo,COALESCE(posMaster.posMasterNoSuffix, ""))`; let searchShortName4 = `CONCAT(orgChild4.orgChild4ShortName," ",COALESCE(posMaster.posMasterNoPrefix, ""),posMaster.posMasterNo,COALESCE(posMaster.posMasterNoSuffix, ""))`; let _data = await new permission().PermissionOrgList(request, "SYS_POS_CONDITION"); + const orgDna = await new permission().checkDna(request, request.user.sub); + let level: any = resolveNodeLevel(orgDna); + + const cannotViewRootPosMaster = + (_data.privilege === "PARENT") || + (_data.privilege === "BROTHER" && level > 1) || + (_data.privilege === "CHILD" && level > 0) || + (_data.privilege === "NORMAL" && level != 0); + + const cannotViewChild1PosMaster = + (_data.privilege === "PARENT" && level > 1) || + (_data.privilege === "BROTHER" && level > 2) || + (_data.privilege === "CHILD" && level > 1) || + (_data.privilege === "NORMAL" && level !== 1); + + const cannotViewChild2PosMaster = + (_data.privilege === "PARENT" && level > 2) || + (_data.privilege === "BROTHER" && level > 3) || + (_data.privilege === "CHILD" && level > 2) || + (_data.privilege === "NORMAL" && level !== 2); + + const cannotViewChild3PosMaster = + (_data.privilege === "PARENT" && level > 3) || + (_data.privilege === "BROTHER" && level > 4) || + (_data.privilege === "CHILD" && level > 3) || + (_data.privilege === "NORMAL" && level !== 3); + + const cannotViewChild4PosMaster = + (_data.privilege === "PARENT" && level > 4) || + (_data.privilege === "CHILD" && level > 4) || + (_data.privilege === "NORMAL" && level !== 4); + if (body.type === 0) { typeCondition = { - orgRootId: body.id, + ...(cannotViewRootPosMaster ? { orgRootId: null } : { orgRootId: body.id }), }; if (!body.isAll) { checkChildConditions = { @@ -5285,7 +5243,7 @@ export class PositionController extends Controller { } } else if (body.type === 1) { typeCondition = { - orgChild1Id: body.id, + ...(cannotViewChild1PosMaster ? { orgChild1Id: null } : { orgChild1Id: body.id }), }; if (!body.isAll) { checkChildConditions = { @@ -5296,7 +5254,7 @@ export class PositionController extends Controller { } } else if (body.type === 2) { typeCondition = { - orgChild2Id: body.id, + ...(cannotViewChild2PosMaster ? { orgChild2Id: null } : { orgChild2Id: body.id }), }; if (!body.isAll) { checkChildConditions = { @@ -5307,7 +5265,7 @@ export class PositionController extends Controller { } } else if (body.type === 3) { typeCondition = { - orgChild3Id: body.id, + ...(cannotViewChild3PosMaster ? { orgChild3Id: null } : { orgChild3Id: body.id }), }; if (!body.isAll) { checkChildConditions = { @@ -5318,7 +5276,7 @@ export class PositionController extends Controller { } } else if (body.type === 4) { typeCondition = { - orgChild4Id: body.id, + ...(cannotViewChild4PosMaster ? { orgChild4Id: null } : { orgChild4Id: body.id }), }; searchShortName = `CONCAT(orgChild4.orgChild4ShortName," ",COALESCE(posMaster.posMasterNoPrefix, ""),posMaster.posMasterNo,COALESCE(posMaster.posMasterNoSuffix, "")) like '%${body.keyword}%'`; } @@ -5403,56 +5361,56 @@ export class PositionController extends Controller { .leftJoinAndSelect("current_holder.posType", "posType") .leftJoinAndSelect("current_holder.posLevel", "posLevel") .where(conditions) - .andWhere( - _data.root != undefined && _data.root != null - ? _data.root[0] != null - ? `posMaster.orgRootId IN (:...root)` - : `posMaster.orgRootId is null` - : "1=1", - { - root: _data.root, - }, - ) - .andWhere( - _data.child1 != undefined && _data.child1 != null - ? _data.child1[0] != null - ? `posMaster.orgChild1Id IN (:...child1)` - : `posMaster.orgChild1Id is ${_data.privilege == "PARENT" ? "not null" : "null"}` - : "1=1", - { - child1: _data.child1, - }, - ) - .andWhere( - _data.child2 != undefined && _data.child2 != null - ? _data.child2[0] != null - ? `posMaster.orgChild2Id IN (:...child2)` - : `posMaster.orgChild2Id is null` - : "1=1", - { - child2: _data.child2, - }, - ) - .andWhere( - _data.child3 != undefined && _data.child3 != null - ? _data.child3[0] != null - ? `posMaster.orgChild3Id IN (:...child3)` - : `posMaster.orgChild3Id is null` - : "1=1", - { - child3: _data.child3, - }, - ) - .andWhere( - _data.child4 != undefined && _data.child4 != null - ? _data.child4[0] != null - ? `posMaster.orgChild4Id IN (:...child4)` - : `posMaster.orgChild4Id is null` - : "1=1", - { - child4: _data.child4, - }, - ) + // .andWhere( + // _data.root != undefined && _data.root != null + // ? _data.root[0] != null + // ? `posMaster.orgRootId IN (:...root)` + // : `posMaster.orgRootId is null` + // : "1=1", + // { + // root: _data.root, + // }, + // ) + // .andWhere( + // _data.child1 != undefined && _data.child1 != null + // ? _data.child1[0] != null + // ? `posMaster.orgChild1Id IN (:...child1)` + // : `posMaster.orgChild1Id is ${_data.privilege == "PARENT" ? "not null" : "null"}` + // : "1=1", + // { + // child1: _data.child1, + // }, + // ) + // .andWhere( + // _data.child2 != undefined && _data.child2 != null + // ? _data.child2[0] != null + // ? `posMaster.orgChild2Id IN (:...child2)` + // : `posMaster.orgChild2Id is null` + // : "1=1", + // { + // child2: _data.child2, + // }, + // ) + // .andWhere( + // _data.child3 != undefined && _data.child3 != null + // ? _data.child3[0] != null + // ? `posMaster.orgChild3Id IN (:...child3)` + // : `posMaster.orgChild3Id is null` + // : "1=1", + // { + // child3: _data.child3, + // }, + // ) + // .andWhere( + // _data.child4 != undefined && _data.child4 != null + // ? _data.child4[0] != null + // ? `posMaster.orgChild4Id IN (:...child4)` + // : `posMaster.orgChild4Id is null` + // : "1=1", + // { + // child4: _data.child4, + // }, + // ) .orWhere( new Brackets((qb) => { qb.andWhere( diff --git a/src/controllers/ProfileAbilityController.ts b/src/controllers/ProfileAbilityController.ts index 0fd2c117..3faa7c08 100644 --- a/src/controllers/ProfileAbilityController.ts +++ b/src/controllers/ProfileAbilityController.ts @@ -40,7 +40,7 @@ export class ProfileAbilityController extends Controller { throw new HttpError(HttpStatus.BAD_REQUEST, "ไม่พบ profile ดังกล่าว"); } const getProfileAbilityId = await this.profileAbilityRepo.find({ - where: { profileId: profile.id }, + where: { profileId: profile.id, isDeleted: false }, order: { createdAt: "ASC" }, }); if (!getProfileAbilityId) { @@ -55,7 +55,7 @@ export class ProfileAbilityController extends Controller { if (_workflow == false) await new permission().PermissionOrgUserGet(req, "SYS_REGISTRY_OFFICER", profileId); const getProfileAbilityId = await this.profileAbilityRepo.find({ - where: { profileId: profileId }, + where: { profileId: profileId, isDeleted: false }, order: { createdAt: "ASC" }, }); if (!getProfileAbilityId) { @@ -174,6 +174,45 @@ export class ProfileAbilityController extends Controller { return new HttpSuccess(); } + /** + * API ลบข้อมูลความสามารถพิเศษ + * @summary API ลบข้อมูลความสามารถพิเศษ + * @param abilityId คีย์ความสามารถพิเศษ + */ + @Patch("update-delete/{abilityId}") + public async updateIsDeleted( + @Request() req: RequestWithUser, + @Path() abilityId: string, + ) { + const record = await this.profileAbilityRepo.findOneBy({ id: abilityId }); + if (!record) throw new HttpError(HttpStatus.NOT_FOUND, "ไม่พบข้อมูล"); + if (record.isDeleted === true) { + return new HttpSuccess(); + } + await new permission().PermissionOrgUserDelete(req, "SYS_REGISTRY_OFFICER", record.profileId); + const before = structuredClone(record); + const history = new ProfileAbilityHistory(); + const now = new Date(); + record.isDeleted = true; + record.lastUpdateUserId = req.user.sub; + record.lastUpdateFullName = req.user.name; + record.lastUpdatedAt = now; + + Object.assign(history, { ...record, id: undefined }); + history.profileAbilityId = abilityId; + history.createdUserId = req.user.sub; + history.createdFullName = req.user.name; + history.createdAt = now; + + await Promise.all([ + this.profileAbilityRepo.save(record, { data: req }), + setLogDataDiff(req, { before, after: record }), + this.profileAbilityHistoryRepo.save(history, { data: req }), + ]); + + return new HttpSuccess(); + } + @Delete("{abilityId}") public async deleteProfileAbility(@Path() abilityId: string, @Request() req: RequestWithUser) { const _record = await this.profileAbilityRepo.findOneBy({ id: abilityId }); diff --git a/src/controllers/ProfileAbilityEmployeeController.ts b/src/controllers/ProfileAbilityEmployeeController.ts index 3ac38706..096b77d8 100644 --- a/src/controllers/ProfileAbilityEmployeeController.ts +++ b/src/controllers/ProfileAbilityEmployeeController.ts @@ -40,7 +40,7 @@ export class ProfileAbilityEmployeeController extends Controller { throw new HttpError(HttpStatus.BAD_REQUEST, "ไม่พบ profile ดังกล่าว"); } const getProfileAbilityId = await this.profileAbilityRepo.find({ - where: { profileEmployeeId: profile.id }, + where: { profileEmployeeId: profile.id, isDeleted: false }, order: { createdAt: "ASC" }, }); if (!getProfileAbilityId) { @@ -58,7 +58,7 @@ export class ProfileAbilityEmployeeController extends Controller { if (_workflow == false) await new permission().PermissionOrgUserGet(req, "SYS_REGISTRY_EMP", profileEmployeeId); const getProfileAbilityId = await this.profileAbilityRepo.find({ - where: { profileEmployeeId: profileEmployeeId }, + where: { profileEmployeeId: profileEmployeeId, isDeleted: false }, order: { createdAt: "ASC" }, }); if (!getProfileAbilityId) { @@ -183,6 +183,45 @@ export class ProfileAbilityEmployeeController extends Controller { return new HttpSuccess(); } + /** + * API ลบข้อมูลความสามารถพิเศษ + * @summary API ลบข้อมูลความสามารถพิเศษ + * @param abilityId คีย์ความสามารถพิเศษ + */ + @Patch("update-delete/{abilityId}") + public async updateIsDeleted( + @Request() req: RequestWithUser, + @Path() abilityId: string, + ) { + const record = await this.profileAbilityRepo.findOneBy({ id: abilityId }); + if (!record) throw new HttpError(HttpStatus.NOT_FOUND, "ไม่พบข้อมูล"); + if (record.isDeleted === true) { + return new HttpSuccess(); + } + await new permission().PermissionOrgUserDelete(req, "SYS_REGISTRY_EMP", record.profileEmployeeId); + const before = structuredClone(record); + const history = new ProfileAbilityHistory(); + const now = new Date(); + record.isDeleted = true; + record.lastUpdateUserId = req.user.sub; + record.lastUpdateFullName = req.user.name; + record.lastUpdatedAt = now; + + Object.assign(history, { ...record, id: undefined }); + history.profileAbilityId = abilityId; + history.createdUserId = req.user.sub; + history.createdFullName = req.user.name; + history.createdAt = now; + + await Promise.all([ + this.profileAbilityRepo.save(record, { data: req }), + setLogDataDiff(req, { before, after: record }), + this.profileAbilityHistoryRepo.save(history, { data: req }), + ]); + + return new HttpSuccess(); + } + @Delete("{abilityId}") public async deleteProfileAbility(@Path() abilityId: string, @Request() req: RequestWithUser) { const _record = await this.profileAbilityRepo.findOneBy({ id: abilityId }); diff --git a/src/controllers/ProfileAbilityEmployeeTempController.ts b/src/controllers/ProfileAbilityEmployeeTempController.ts index 0c0b3723..3a4e356a 100644 --- a/src/controllers/ProfileAbilityEmployeeTempController.ts +++ b/src/controllers/ProfileAbilityEmployeeTempController.ts @@ -40,7 +40,7 @@ export class ProfileAbilityEmployeeTempController extends Controller { throw new HttpError(HttpStatus.BAD_REQUEST, "ไม่พบ profile ดังกล่าว"); } const getProfileAbilityId = await this.profileAbilityRepo.find({ - where: { profileEmployeeId: profile.id }, + where: { profileEmployeeId: profile.id, isDeleted: false }, order: { createdAt: "ASC" }, }); if (!getProfileAbilityId) { @@ -57,7 +57,7 @@ export class ProfileAbilityEmployeeTempController extends Controller { let _workflow = await new permission().Workflow(req, profileEmployeeId, "SYS_REGISTRY_TEMP"); if (_workflow == false) await new permission().PermissionGet(req, "SYS_REGISTRY_TEMP"); const getProfileAbilityId = await this.profileAbilityRepo.find({ - where: { profileEmployeeId: profileEmployeeId }, + where: { profileEmployeeId: profileEmployeeId, isDeleted: false }, order: { createdAt: "ASC" }, }); if (!getProfileAbilityId) { @@ -173,6 +173,45 @@ export class ProfileAbilityEmployeeTempController extends Controller { return new HttpSuccess(); } + /** + * API ลบข้อมูลความสามารถพิเศษ + * @summary API ลบข้อมูลความสามารถพิเศษ + * @param abilityId คีย์ความสามารถพิเศษ + */ + @Patch("update-delete/{abilityId}") + public async updateIsDeleted( + @Request() req: RequestWithUser, + @Path() abilityId: string, + ) { + const record = await this.profileAbilityRepo.findOneBy({ id: abilityId }); + if (!record) throw new HttpError(HttpStatus.NOT_FOUND, "ไม่พบข้อมูล"); + if (record.isDeleted === true) { + return new HttpSuccess(); + } + await new permission().PermissionDelete(req, "SYS_REGISTRY_TEMP"); + const before = structuredClone(record); + const history = new ProfileAbilityHistory(); + const now = new Date(); + record.isDeleted = true; + record.lastUpdateUserId = req.user.sub; + record.lastUpdateFullName = req.user.name; + record.lastUpdatedAt = now; + + Object.assign(history, { ...record, id: undefined }); + history.profileAbilityId = abilityId; + history.createdUserId = req.user.sub; + history.createdFullName = req.user.name; + history.createdAt = now; + + await Promise.all([ + this.profileAbilityRepo.save(record, { data: req }), + setLogDataDiff(req, { before, after: record }), + this.profileAbilityHistoryRepo.save(history, { data: req }), + ]); + + return new HttpSuccess(); + } + @Delete("{abilityId}") public async deleteProfileAbility(@Path() abilityId: string, @Request() req: RequestWithUser) { const _record = await this.profileAbilityRepo.findOneBy({ id: abilityId }); diff --git a/src/controllers/ProfileAbsentLateController.ts b/src/controllers/ProfileAbsentLateController.ts new file mode 100644 index 00000000..ef977097 --- /dev/null +++ b/src/controllers/ProfileAbsentLateController.ts @@ -0,0 +1,277 @@ +import { + Body, + Controller, + Get, + Patch, + Path, + Post, + Request, + Route, + Security, + Tags, +} from "tsoa"; +import { AppDataSource } from "../database/data-source"; +import { In } from "typeorm"; +import { + ProfileAbsentLate, + CreateProfileAbsentLate, + CreateProfileAbsentLateBatch, + UpdateProfileAbsentLate, +} from "../entities/ProfileAbsentLate"; +import { ProfileAbsentLateHistory } from "../entities/ProfileAbsentLateHistory"; +import HttpSuccess from "../interfaces/http-success"; +import HttpStatus from "../interfaces/http-status"; +import HttpError from "../interfaces/http-error"; +import { RequestWithUser } from "../middlewares/user"; +import { Profile } from "../entities/Profile"; +import permission from "../interfaces/permission"; +import { setLogDataDiff } from "../interfaces/utils"; + +@Route("api/v1/org/profile/absent-late") +@Tags("ProfileAbsentLate") +@Security("bearerAuth") +export class ProfileAbsentLateController extends Controller { + private profileRepo = AppDataSource.getRepository(Profile); + private absentLateRepo = AppDataSource.getRepository(ProfileAbsentLate); + private historyRepo = AppDataSource.getRepository(ProfileAbsentLateHistory); + + /** + * API ดึงข้อมูลการมาสาย/ขาดราชการของ user + * @summary API ดึงข้อมูลการมาสาย/ขาดราชการของ user + */ + @Get("user") + public async getAbsentLateUser(@Request() request: { user: Record }) { + const profile = await this.profileRepo.findOneBy({ keycloak: request.user.sub }); + if (!profile) { + throw new HttpError(HttpStatus.BAD_REQUEST, "ไม่พบ profile ดังกล่าว"); + } + const record = await this.absentLateRepo.find({ + where: { profileId: profile.id, isDeleted: false }, + order: { stampDate: "DESC" }, + }); + return new HttpSuccess(record); + } + + /** + * API ดึงข้อมูลการมาสาย/ขาดราชการตาม profileId + * @summary API ดึงข้อมูลการมาสาย/ขาดราชการตาม profileId + * @param profileId คีย์ profile + */ + @Get("{profileId}") + public async getAbsentLate(@Path() profileId: string, @Request() req: RequestWithUser) { + let _workflow = await new permission().Workflow(req, profileId, "SYS_REGISTRY_OFFICER"); + if (_workflow == false) + await new permission().PermissionOrgUserGet(req, "SYS_REGISTRY_OFFICER", profileId); + const record = await this.absentLateRepo.find({ + where: { profileId, isDeleted: false }, + order: { stampDate: "DESC" }, + }); + return new HttpSuccess(record); + } + + /** + * API สร้างข้อมูลการมาสาย/ขาดราชการ + * @summary API สร้างข้อมูลการมาสาย/ขาดราชการ + */ + @Post() + public async newAbsentLate( + @Request() req: RequestWithUser, + @Body() body: CreateProfileAbsentLate, + ) { + if (!body.profileId) { + throw new HttpError(HttpStatus.BAD_REQUEST, "กรุณากรอก profileId"); + } + + const profile = await this.profileRepo.findOneBy({ id: body.profileId }); + if (!profile) { + throw new HttpError(HttpStatus.BAD_REQUEST, "ไม่พบ profile ดังกล่าว"); + } + await new permission().PermissionOrgUserUpdate(req, "SYS_REGISTRY_OFFICER", profile.id); + + const before = null; + const data = new ProfileAbsentLate(); + + const meta = { + createdUserId: req.user.sub, + createdFullName: req.user.name, + lastUpdateUserId: req.user.sub, + lastUpdateFullName: req.user.name, + createdAt: new Date(), + lastUpdatedAt: new Date(), + }; + + Object.assign(data, { ...body, ...meta }); + + // บันทึก history + const history = new ProfileAbsentLateHistory(); + Object.assign(history, { ...data, id: undefined }); + + await this.absentLateRepo.save(data, { data: req }); + setLogDataDiff(req, { before, after: data }); + history.profileAbsentLateId = data.id; + await this.historyRepo.save(history, { data: req }); + + return new HttpSuccess(data.id); + } + + /** + * API สร้างข้อมูลการมาสาย/ขาดราชการ (สำหรับ Job) + * @summary API สร้างข้อมูลการมาสาย/ขาดราชการ (สำหรับ Job) + */ + @Post("batch") + public async newAbsentLateBatch( + @Request() req: RequestWithUser, + @Body() body: CreateProfileAbsentLateBatch, + ) { + // กรณีไม่มีข้อมูลส่งมา (วันที่ไม่มีคนขาด/มาสาย) + if (!body.records || body.records.length === 0) { + return new HttpSuccess({ count: 0, ids: [] }); + } + + const profileIds = [...new Set(body.records.map((r) => r.profileId))]; + const profiles = await this.profileRepo.findBy({ + id: In(profileIds), + }); + + const foundProfileIds = new Set(profiles.map((p) => p.id)); + const validRecords = body.records.filter((r) => foundProfileIds.has(r.profileId)); + + // กรณีไม่พบ profile เลย + if (validRecords.length === 0) { + return new HttpSuccess({ count: 0, ids: [] }); + } + + const meta = { + createdUserId: req.user.sub, + createdFullName: req.user.name, + lastUpdateUserId: req.user.sub, + lastUpdateFullName: req.user.name, + createdAt: new Date(), + lastUpdatedAt: new Date(), + }; + + const records = validRecords.map((item) => { + const data = new ProfileAbsentLate(); + Object.assign(data, { ...item, ...meta }); + return data; + }); + + const result = await this.absentLateRepo.save(records, { data: req }); + + // บันทึก history สำหรับแต่ละ record + const historyRecords = result.map((data) => { + const history = new ProfileAbsentLateHistory(); + Object.assign(history, { ...data, id: undefined }); + history.profileAbsentLateId = data.id; + return history; + }); + await this.historyRepo.save(historyRecords, { data: req }); + + return new HttpSuccess({ count: result.length, ids: result.map((r) => r.id) }); + } + + /** + * API แก้ไขข้อมูลการมาสาย/ขาดราชการ + * @summary API แก้ไขข้อมูลการมาสาย/ขาดราชการ + * @param absentLateId คีย์การมาสาย/ขาดราชการ + */ + @Patch("{absentLateId}") + public async editAbsentLate( + @Request() req: RequestWithUser, + @Body() body: UpdateProfileAbsentLate, + @Path() absentLateId: string, + ) { + const record = await this.absentLateRepo.findOneBy({ id: absentLateId }); + if (!record) throw new HttpError(HttpStatus.NOT_FOUND, "ไม่พบข้อมูล"); + await new permission().PermissionOrgUserUpdate( + req, + "SYS_REGISTRY_OFFICER", + record.profileId, + ); + + const before = structuredClone(record); + const history = new ProfileAbsentLateHistory(); + Object.assign(history, { ...record, id: undefined }); + + Object.assign(record, body); + Object.assign(history, { ...record, id: undefined }); + + history.profileAbsentLateId = absentLateId; + record.lastUpdateUserId = req.user.sub; + record.lastUpdateFullName = req.user.name; + record.lastUpdatedAt = new Date(); + history.lastUpdateUserId = req.user.sub; + history.lastUpdateFullName = req.user.name; + history.createdUserId = req.user.sub; + history.createdFullName = req.user.name; + history.createdAt = new Date(); + history.lastUpdatedAt = new Date(); + + await Promise.all([ + this.absentLateRepo.save(record, { data: req }), + setLogDataDiff(req, { before, after: record }), + this.historyRepo.save(history, { data: req }), + ]); + + return new HttpSuccess(); + } + + /** + * API ลบข้อมูลการมาสาย/ขาดราชการ (Soft Delete) + * @summary API ลบข้อมูลการมาสาย/ขาดราชการ (Soft Delete) + * @param absentLateId คีย์การมาสาย/ขาดราชการ + */ + @Patch("update-delete/{absentLateId}") + public async updateIsDeleted( + @Request() req: RequestWithUser, + @Path() absentLateId: string, + ) { + const record = await this.absentLateRepo.findOneBy({ id: absentLateId }); + if (!record) throw new HttpError(HttpStatus.NOT_FOUND, "ไม่พบข้อมูล"); + if (record.isDeleted === true) { + return new HttpSuccess(); + } + await new permission().PermissionOrgUserDelete(req, "SYS_REGISTRY_OFFICER", record.profileId); + + const before = structuredClone(record); + const history = new ProfileAbsentLateHistory(); + Object.assign(history, { ...record, id: undefined }); + + record.isDeleted = true; + record.lastUpdateUserId = req.user.sub; + record.lastUpdateFullName = req.user.name; + record.lastUpdatedAt = new Date(); + + history.profileAbsentLateId = absentLateId; + history.isDeleted = true; + history.lastUpdateUserId = req.user.sub; + history.lastUpdateFullName = req.user.name; + history.lastUpdatedAt = new Date(); + + await Promise.all([ + this.absentLateRepo.save(record, { data: req }), + setLogDataDiff(req, { before, after: record }), + this.historyRepo.save(history, { data: req }), + ]); + + return new HttpSuccess(); + } + + /** + * API ดึงประวัติการมาสาย/ขาดราชการ + * @summary API ดึงประวัติการมาสาย/ขาดราชการ + * @param absentLateId คีย์การมาสาย/ขาดราชการ + */ + @Get("history/{absentLateId}") + public async getHistory(@Path() absentLateId: string, @Request() req: RequestWithUser) { + const record = await this.absentLateRepo.findOneBy({ id: absentLateId }); + if (!record) throw new HttpError(HttpStatus.NOT_FOUND, "ไม่พบข้อมูล"); + await new permission().PermissionOrgUserGet(req, "SYS_REGISTRY_OFFICER", record.profileId); + + const history = await this.historyRepo.find({ + where: { profileAbsentLateId: absentLateId }, + order: { createdAt: "DESC" }, + }); + return new HttpSuccess(history); + } +} diff --git a/src/controllers/ProfileActpositionController.ts b/src/controllers/ProfileActpositionController.ts index 8e1aa9ff..e22cf983 100644 --- a/src/controllers/ProfileActpositionController.ts +++ b/src/controllers/ProfileActpositionController.ts @@ -40,7 +40,7 @@ export class ProfileActpositionController extends Controller { throw new HttpError(HttpStatus.BAD_REQUEST, "ไม่พบ profile ดังกล่าว"); } const getProfileActpositionId = await this.profileActpositionRepo.find({ - where: { profileId: profile.id }, + where: { profileId: profile.id, isDeleted: false }, order: { createdAt: "ASC" }, }); if (!getProfileActpositionId) { @@ -58,7 +58,7 @@ export class ProfileActpositionController extends Controller { if (_workflow == false) await new permission().PermissionOrgUserGet(req, "SYS_REGISTRY_OFFICER", profileId); const getProfileActpositionId = await this.profileActpositionRepo.find({ - where: { profileId: profileId }, + where: { profileId: profileId, isDeleted: false }, order: { createdAt: "ASC" }, }); if (!getProfileActpositionId) { @@ -201,6 +201,44 @@ export class ProfileActpositionController extends Controller { return new HttpSuccess(); } + /** + * API ลบข้อมูลรักษาการในตำแหน่ง + * @summary API ลบข้อมูลรักษาการในตำแหน่ง + * @param actpositionId คีย์รักษาการในตำแหน่ง + */ + @Patch("update-delete/{actpositionId}") + public async updateIsDeletedTraining( + @Request() req: RequestWithUser, + @Path() actpositionId: string, + ) { + const record = await this.profileActpositionRepo.findOneBy({ id: actpositionId }); + if (!record) throw new HttpError(HttpStatus.NOT_FOUND, "ไม่พบข้อมูล"); + if (record.isDeleted === true) { + return new HttpSuccess(); + } + await new permission().PermissionOrgUserDelete(req, "SYS_REGISTRY_OFFICER", record.profileId); + const before = structuredClone(record); + const history = new ProfileActpositionHistory(); + const now = new Date(); + record.isDeleted = true; + record.lastUpdateUserId = req.user.sub; + record.lastUpdateFullName = req.user.name; + record.lastUpdatedAt = now; + + Object.assign(history, { ...record, id: undefined }); + history.createdUserId = req.user.sub; + history.createdFullName = req.user.name; + history.createdAt = now; + + await Promise.all([ + this.profileActpositionRepo.save(record, { data: req }), + setLogDataDiff(req, { before, after: record }), + this.profileActpositionHistoryRepo.save(history, { data: req }), + ]); + + return new HttpSuccess(); + } + @Delete("{actpositionId}") public async deleteProfileActposition( @Path() actpositionId: string, diff --git a/src/controllers/ProfileActpositionEmployeeController.ts b/src/controllers/ProfileActpositionEmployeeController.ts index 2ada7dd3..d6afcebe 100644 --- a/src/controllers/ProfileActpositionEmployeeController.ts +++ b/src/controllers/ProfileActpositionEmployeeController.ts @@ -40,7 +40,7 @@ export class ProfileActpositionEmployeeController extends Controller { throw new HttpError(HttpStatus.BAD_REQUEST, "ไม่พบ profile ดังกล่าว"); } const getProfileActpositionId = await this.profileActpositionRepo.find({ - where: { profileEmployeeId: profile.id }, + where: { profileEmployeeId: profile.id, isDeleted: false }, order: { createdAt: "ASC" }, }); if (!getProfileActpositionId) { @@ -58,7 +58,7 @@ export class ProfileActpositionEmployeeController extends Controller { if (_workflow == false) await new permission().PermissionOrgUserGet(req, "SYS_REGISTRY_EMP", profileEmployeeId); const getProfileActpositionId = await this.profileActpositionRepo.find({ - where: { profileEmployeeId: profileEmployeeId }, + where: { profileEmployeeId: profileEmployeeId, isDeleted: false }, order: { createdAt: "ASC" }, }); if (!getProfileActpositionId) { diff --git a/src/controllers/ProfileActpositionEmployeeTempController.ts b/src/controllers/ProfileActpositionEmployeeTempController.ts index 630cb785..1cf92baa 100644 --- a/src/controllers/ProfileActpositionEmployeeTempController.ts +++ b/src/controllers/ProfileActpositionEmployeeTempController.ts @@ -40,7 +40,7 @@ export class ProfileActpositionEmployeeTempController extends Controller { throw new HttpError(HttpStatus.BAD_REQUEST, "ไม่พบ profile ดังกล่าว"); } const getProfileActpositionId = await this.profileActpositionRepo.find({ - where: { profileEmployeeId: profile.id }, + where: { profileEmployeeId: profile.id, isDeleted: false }, order: { createdAt: "ASC" }, }); if (!getProfileActpositionId) { @@ -57,7 +57,7 @@ export class ProfileActpositionEmployeeTempController extends Controller { let _workflow = await new permission().Workflow(req, profileEmployeeId, "SYS_REGISTRY_TEMP"); if (_workflow == false) await new permission().PermissionGet(req, "SYS_REGISTRY_TEMP"); const getProfileActpositionId = await this.profileActpositionRepo.find({ - where: { profileEmployeeId: profileEmployeeId }, + where: { profileEmployeeId: profileEmployeeId, isDeleted: false }, order: { createdAt: "ASC" }, }); if (!getProfileActpositionId) { diff --git a/src/controllers/ProfileAssessmentsController.ts b/src/controllers/ProfileAssessmentsController.ts index f8ca30e0..1a7bc662 100644 --- a/src/controllers/ProfileAssessmentsController.ts +++ b/src/controllers/ProfileAssessmentsController.ts @@ -41,7 +41,7 @@ export class ProfileAssessmentsController extends Controller { throw new HttpError(HttpStatus.BAD_REQUEST, "ไม่พบ profile ดังกล่าว"); } const getProfileAssessments = await this.profileAssessmentsRepository.find({ - where: { profileId: profile.id }, + where: { profileId: profile.id, isDeleted: false }, order: { createdAt: "ASC" }, }); if (!getProfileAssessments) { @@ -59,7 +59,7 @@ export class ProfileAssessmentsController extends Controller { if (_workflow == false) await new permission().PermissionOrgUserGet(req, "SYS_REGISTRY_OFFICER", profileId); const getProfileAssessments = await this.profileAssessmentsRepository.find({ - where: { profileId: profileId }, + where: { profileId: profileId, isDeleted: false }, order: { createdAt: "ASC" }, }); if (!getProfileAssessments) { @@ -186,6 +186,45 @@ export class ProfileAssessmentsController extends Controller { return new HttpSuccess(); } + /** + * API ลบข้อมูลผลการประเมินการปฏิบัติราชการ + * @summary API ลบข้อมูลผลการประเมินการปฏิบัติราชการ + * @param assessmentId คีย์ผลการประเมินการปฏิบัติราชการ + */ + @Patch("update-delete/{assessmentId}") + public async updateIsDeleted( + @Request() req: RequestWithUser, + @Path() assessmentId: string, + ) { + const record = await this.profileAssessmentsRepository.findOneBy({ id: assessmentId }); + if (!record) throw new HttpError(HttpStatus.NOT_FOUND, "ไม่พบข้อมูล"); + if (record.isDeleted === true) { + return new HttpSuccess(); + } + await new permission().PermissionOrgUserDelete(req, "SYS_REGISTRY_OFFICER", record.profileId); + const before = structuredClone(record); + const history = new ProfileAssessmentHistory(); + const now = new Date(); + record.isDeleted = true; + record.lastUpdateUserId = req.user.sub; + record.lastUpdateFullName = req.user.name; + record.lastUpdatedAt = now; + + Object.assign(history, { ...record, id: undefined }); + history.profileAssessmentId = assessmentId; + history.createdUserId = req.user.sub; + history.createdFullName = req.user.name; + history.createdAt = now; + + await Promise.all([ + this.profileAssessmentsRepository.save(record, { data: req }), + setLogDataDiff(req, { before, after: record }), + this.profileAssessmentsHistoryRepository.save(history, { data: req }), + ]); + + return new HttpSuccess(); + } + @Delete("{assessmentId}") public async deleteProfileAssessment( @Path() assessmentId: string, diff --git a/src/controllers/ProfileAssessmentsEmployeeController.ts b/src/controllers/ProfileAssessmentsEmployeeController.ts index 26127f3e..04587937 100644 --- a/src/controllers/ProfileAssessmentsEmployeeController.ts +++ b/src/controllers/ProfileAssessmentsEmployeeController.ts @@ -41,7 +41,7 @@ export class ProfileAssessmentsEmployeeController extends Controller { throw new HttpError(HttpStatus.BAD_REQUEST, "ไม่พบ profile ดังกล่าว"); } const getProfileAssessments = await this.profileAssessmentsRepository.find({ - where: { profileEmployeeId: profile.id }, + where: { profileEmployeeId: profile.id, isDeleted: false }, order: { createdAt: "ASC" }, }); if (!getProfileAssessments) { @@ -61,6 +61,7 @@ export class ProfileAssessmentsEmployeeController extends Controller { const getProfileAssessments = await this.profileAssessmentsRepository.find({ where: { profileEmployeeId: profileEmployeeId, + isDeleted: false }, order: { createdAt: "ASC" }, }); @@ -192,6 +193,45 @@ export class ProfileAssessmentsEmployeeController extends Controller { return new HttpSuccess(); } + /** + * API ลบข้อมูลผลการประเมินการปฏิบัติราชการ + * @summary API ลบข้อมูลผลการประเมินการปฏิบัติราชการ + * @param assessmentId คีย์ผลการประเมินการปฏิบัติราชการ + */ + @Patch("update-delete/{assessmentId}") + public async updateIsDeleted( + @Request() req: RequestWithUser, + @Path() assessmentId: string, + ) { + const record = await this.profileAssessmentsRepository.findOneBy({ id: assessmentId }); + if (!record) throw new HttpError(HttpStatus.NOT_FOUND, "ไม่พบข้อมูล"); + if (record.isDeleted === true) { + return new HttpSuccess(); + } + await new permission().PermissionOrgUserDelete(req, "SYS_REGISTRY_EMP", record.profileEmployeeId); + const before = structuredClone(record); + const history = new ProfileAssessmentHistory(); + const now = new Date(); + record.isDeleted = true; + record.lastUpdateUserId = req.user.sub; + record.lastUpdateFullName = req.user.name; + record.lastUpdatedAt = now; + + Object.assign(history, { ...record, id: undefined }); + history.profileAssessmentId = assessmentId; + history.createdUserId = req.user.sub; + history.createdFullName = req.user.name; + history.createdAt = now; + + await Promise.all([ + this.profileAssessmentsRepository.save(record, { data: req }), + setLogDataDiff(req, { before, after: record }), + this.profileAssessmentsHistoryRepository.save(history, { data: req }), + ]); + + return new HttpSuccess(); + } + @Delete("{assessmentId}") public async deleteProfileAssessment( @Path() assessmentId: string, diff --git a/src/controllers/ProfileAssessmentsEmployeeTempController.ts b/src/controllers/ProfileAssessmentsEmployeeTempController.ts index 2aa62ff7..77689709 100644 --- a/src/controllers/ProfileAssessmentsEmployeeTempController.ts +++ b/src/controllers/ProfileAssessmentsEmployeeTempController.ts @@ -41,7 +41,7 @@ export class ProfileAssessmentsEmployeeTempController extends Controller { throw new HttpError(HttpStatus.BAD_REQUEST, "ไม่พบ profile ดังกล่าว"); } const getProfileAssessments = await this.profileAssessmentsRepository.find({ - where: { profileEmployeeId: profile.id }, + where: { profileEmployeeId: profile.id, isDeleted: false }, order: { createdAt: "ASC" }, }); if (!getProfileAssessments) { @@ -60,6 +60,7 @@ export class ProfileAssessmentsEmployeeTempController extends Controller { const getProfileAssessments = await this.profileAssessmentsRepository.find({ where: { profileEmployeeId: profileEmployeeId, + isDeleted: false }, order: { createdAt: "ASC" }, }); @@ -180,6 +181,45 @@ export class ProfileAssessmentsEmployeeTempController extends Controller { return new HttpSuccess(); } + /** + * API ลบข้อมูลผลการประเมินการปฏิบัติราชการ + * @summary API ลบข้อมูลผลการประเมินการปฏิบัติราชการ + * @param assessmentId คีย์ผลการประเมินการปฏิบัติราชการ + */ + @Patch("update-delete/{assessmentId}") + public async updateIsDeleted( + @Request() req: RequestWithUser, + @Path() assessmentId: string, + ) { + const record = await this.profileAssessmentsRepository.findOneBy({ id: assessmentId }); + if (!record) throw new HttpError(HttpStatus.NOT_FOUND, "ไม่พบข้อมูล"); + if (record.isDeleted === true) { + return new HttpSuccess(); + } + await new permission().PermissionDelete(req, "SYS_REGISTRY_TEMP"); + const before = structuredClone(record); + const history = new ProfileAssessmentHistory(); + const now = new Date(); + record.isDeleted = true; + record.lastUpdateUserId = req.user.sub; + record.lastUpdateFullName = req.user.name; + record.lastUpdatedAt = now; + + Object.assign(history, { ...record, id: undefined }); + history.profileAssessmentId = assessmentId; + history.createdUserId = req.user.sub; + history.createdFullName = req.user.name; + history.createdAt = now; + + await Promise.all([ + this.profileAssessmentsRepository.save(record, { data: req }), + setLogDataDiff(req, { before, after: record }), + this.profileAssessmentsHistoryRepository.save(history, { data: req }), + ]); + + return new HttpSuccess(); + } + @Delete("{assessmentId}") public async deleteProfileAssessment( @Path() assessmentId: string, diff --git a/src/controllers/ProfileAssistanceController.ts b/src/controllers/ProfileAssistanceController.ts index abff2a9f..a2ec5643 100644 --- a/src/controllers/ProfileAssistanceController.ts +++ b/src/controllers/ProfileAssistanceController.ts @@ -40,7 +40,7 @@ export class ProfileAssistanceController extends Controller { throw new HttpError(HttpStatus.BAD_REQUEST, "ไม่พบ profile ดังกล่าว"); } const getProfileAssistanceId = await this.profileAssistanceRepo.find({ - where: { profileId: profile.id }, + where: { profileId: profile.id, isDeleted: false }, order: { createdAt: "ASC" }, }); if (!getProfileAssistanceId) { @@ -55,7 +55,7 @@ export class ProfileAssistanceController extends Controller { // if (_workflow == false) // await new permission().PermissionOrgUserGet(req, "SYS_REGISTRY_OFFICER", profileId); const getProfileAssistanceId = await this.profileAssistanceRepo.find({ - where: { profileId: profileId }, + where: { profileId: profileId, isDeleted: false }, order: { createdAt: "ASC" }, }); if (!getProfileAssistanceId) { @@ -175,6 +175,45 @@ export class ProfileAssistanceController extends Controller { return new HttpSuccess(); } + /** + * API ลบข้อมูลช่วยราชการ + * @summary API ลบข้อมูลช่วยราชการ + * @param assistanceId คีย์ช่วยราชการ + */ + @Patch("update-delete/{assistanceId}") + public async updateIsDeleted( + @Request() req: RequestWithUser, + @Path() assistanceId: string, + ) { + const record = await this.profileAssistanceRepo.findOneBy({ id: assistanceId }); + if (!record) throw new HttpError(HttpStatus.NOT_FOUND, "ไม่พบข้อมูล"); + if (record.isDeleted === true) { + return new HttpSuccess(); + } + await new permission().PermissionOrgUserDelete(req, "SYS_REGISTRY_OFFICER", record.profileId); + const before = structuredClone(record); + const history = new ProfileAssistanceHistory(); + const now = new Date(); + record.isDeleted = true; + record.lastUpdateUserId = req.user.sub; + record.lastUpdateFullName = req.user.name; + record.lastUpdatedAt = now; + + Object.assign(history, { ...record, id: undefined }); + history.profileAssistanceId = assistanceId; + history.createdUserId = req.user.sub; + history.createdFullName = req.user.name; + history.createdAt = now; + + await Promise.all([ + this.profileAssistanceRepo.save(record, { data: req }), + setLogDataDiff(req, { before, after: record }), + this.profileAssistanceHistoryRepo.save(history, { data: req }), + ]); + + return new HttpSuccess(); + } + @Delete("{assistanceId}") public async deleteProfileAssistance( @Path() assistanceId: string, diff --git a/src/controllers/ProfileAssistanceEmployeeController.ts b/src/controllers/ProfileAssistanceEmployeeController.ts index cbd816b9..065b5142 100644 --- a/src/controllers/ProfileAssistanceEmployeeController.ts +++ b/src/controllers/ProfileAssistanceEmployeeController.ts @@ -40,7 +40,7 @@ export class ProfileAssistanceEmployeeController extends Controller { throw new HttpError(HttpStatus.BAD_REQUEST, "ไม่พบ profile ดังกล่าว"); } const getProfileAssistanceId = await this.profileAssistanceRepo.find({ - where: { profileEmployeeId: profile.id }, + where: { profileEmployeeId: profile.id, isDeleted: false }, order: { createdAt: "ASC" }, }); if (!getProfileAssistanceId) { @@ -58,7 +58,7 @@ export class ProfileAssistanceEmployeeController extends Controller { if (_workflow == false) await new permission().PermissionOrgUserGet(req, "SYS_REGISTRY_EMP", profileEmployeeId); const getProfileAssistanceId = await this.profileAssistanceRepo.find({ - where: { profileEmployeeId: profileEmployeeId }, + where: { profileEmployeeId: profileEmployeeId, isDeleted: false }, order: { createdAt: "ASC" }, }); if (!getProfileAssistanceId) { @@ -183,6 +183,46 @@ export class ProfileAssistanceEmployeeController extends Controller { return new HttpSuccess(); } + + /** + * API ลบข้อมูลช่วยราชการ + * @summary API ลบข้อมูลช่วยราชการ + * @param assistanceId คีย์ช่วยราชการ + */ + @Patch("update-delete/{assistanceId}") + public async updateIsDeleted( + @Request() req: RequestWithUser, + @Path() assistanceId: string, + ) { + const record = await this.profileAssistanceRepo.findOneBy({ id: assistanceId }); + if (!record) throw new HttpError(HttpStatus.NOT_FOUND, "ไม่พบข้อมูล"); + if (record.isDeleted === true) { + return new HttpSuccess(); + } + await new permission().PermissionOrgUserDelete(req, "SYS_REGISTRY_EMP", record.profileEmployeeId); + const before = structuredClone(record); + const history = new ProfileAssistanceHistory(); + const now = new Date(); + record.isDeleted = true; + record.lastUpdateUserId = req.user.sub; + record.lastUpdateFullName = req.user.name; + record.lastUpdatedAt = now; + + Object.assign(history, { ...record, id: undefined }); + history.profileAssistanceId = assistanceId; + history.createdUserId = req.user.sub; + history.createdFullName = req.user.name; + history.createdAt = now; + + await Promise.all([ + this.profileAssistanceRepo.save(record, { data: req }), + setLogDataDiff(req, { before, after: record }), + this.profileAssistanceHistoryRepo.save(history, { data: req }), + ]); + + return new HttpSuccess(); + } + @Delete("{assistanceId}") public async deleteProfileAssistance( @Path() assistanceId: string, diff --git a/src/controllers/ProfileAssistanceEmployeeTempController.ts b/src/controllers/ProfileAssistanceEmployeeTempController.ts index 534dafa2..26d88476 100644 --- a/src/controllers/ProfileAssistanceEmployeeTempController.ts +++ b/src/controllers/ProfileAssistanceEmployeeTempController.ts @@ -40,7 +40,7 @@ export class ProfileAssistanceEmployeeTempController extends Controller { throw new HttpError(HttpStatus.BAD_REQUEST, "ไม่พบ profile ดังกล่าว"); } const getProfileAssistanceId = await this.profileAssistanceRepo.find({ - where: { profileEmployeeId: profile.id }, + where: { profileEmployeeId: profile.id, isDeleted: false }, order: { createdAt: "ASC" }, }); if (!getProfileAssistanceId) { @@ -57,7 +57,7 @@ export class ProfileAssistanceEmployeeTempController extends Controller { let _workflow = await new permission().Workflow(req, profileEmployeeId, "SYS_REGISTRY_TEMP"); if (_workflow == false) await new permission().PermissionGet(req, "SYS_REGISTRY_TEMP"); const getProfileAssistanceId = await this.profileAssistanceRepo.find({ - where: { profileEmployeeId: profileEmployeeId }, + where: { profileEmployeeId: profileEmployeeId, isDeleted: false }, order: { createdAt: "ASC" }, }); if (!getProfileAssistanceId) { @@ -173,6 +173,45 @@ export class ProfileAssistanceEmployeeTempController extends Controller { return new HttpSuccess(); } + /** + * API ลบข้อมูลช่วยราชการ + * @summary API ลบข้อมูลช่วยราชการ + * @param assistanceId คีย์ช่วยราชการ + */ + @Patch("update-delete/{assistanceId}") + public async updateIsDeleted( + @Request() req: RequestWithUser, + @Path() assistanceId: string, + ) { + const record = await this.profileAssistanceRepo.findOneBy({ id: assistanceId }); + if (!record) throw new HttpError(HttpStatus.NOT_FOUND, "ไม่พบข้อมูล"); + if (record.isDeleted === true) { + return new HttpSuccess(); + } + await new permission().PermissionDelete(req, "SYS_REGISTRY_TEMP"); + const before = structuredClone(record); + const history = new ProfileAssistanceHistory(); + const now = new Date(); + record.isDeleted = true; + record.lastUpdateUserId = req.user.sub; + record.lastUpdateFullName = req.user.name; + record.lastUpdatedAt = now; + + Object.assign(history, { ...record, id: undefined }); + history.profileAssistanceId = assistanceId; + history.createdUserId = req.user.sub; + history.createdFullName = req.user.name; + history.createdAt = now; + + await Promise.all([ + this.profileAssistanceRepo.save(record, { data: req }), + setLogDataDiff(req, { before, after: record }), + this.profileAssistanceHistoryRepo.save(history, { data: req }), + ]); + + return new HttpSuccess(); + } + @Delete("{assistanceId}") public async deleteProfileAssistance( @Path() assistanceId: string, diff --git a/src/controllers/ProfileAvatarController.ts b/src/controllers/ProfileAvatarController.ts index dc8a7e0f..73374070 100644 --- a/src/controllers/ProfileAvatarController.ts +++ b/src/controllers/ProfileAvatarController.ts @@ -8,6 +8,7 @@ import { Profile } from "../entities/Profile"; import { CreateProfileAvatar, ProfileAvatar } from "../entities/ProfileAvatar"; import permission from "../interfaces/permission"; import { setLogDataDiff } from "../interfaces/utils"; +import CallAPI from "../interfaces/call-api"; @Route("api/v1/org/profile/avatar") @Tags("ProfileAvatar") @Security("bearerAuth") @@ -158,10 +159,24 @@ export class ProfileAvatarController extends Controller { "SYS_REGISTRY_OFFICER", _record.profileId, ); + + if (_record.isActive) { + const profile = await this.profileRepository.findOne({ + where: { id: _record.profileId }, + }); + if (profile) { + profile.avatar = null; + profile.avatarName = null; + await this.profileRepository.save(profile, { data: req }); + } + } + + await new CallAPI().DeleteFile(req, `${_record?.avatar}/${_record?.avatarName}`); } if (!_record) { throw new HttpError(HttpStatus.NOT_FOUND, "ไม่พบข้อมูล"); } + await this.avatarRepository.remove(_record, { data: req }); return new HttpSuccess(); diff --git a/src/controllers/ProfileAvatarEmployeeController.ts b/src/controllers/ProfileAvatarEmployeeController.ts index 21f9f536..ac6c2ff0 100644 --- a/src/controllers/ProfileAvatarEmployeeController.ts +++ b/src/controllers/ProfileAvatarEmployeeController.ts @@ -8,6 +8,8 @@ import { CreateProfileEmployeeAvatar, ProfileAvatar } from "../entities/ProfileA import { ProfileEmployee } from "../entities/ProfileEmployee"; import permission from "../interfaces/permission"; import { setLogDataDiff } from "../interfaces/utils"; +import CallAPI from "../interfaces/call-api"; + @Route("api/v1/org/profile-employee/avatar") @Tags("ProfileAvatar") @Security("bearerAuth") @@ -153,6 +155,18 @@ export class ProfileAvatarEmployeeController extends Controller { "SYS_REGISTRY_EMP", _record.profileEmployeeId, ); + if (_record.isActive) { + const profile = await this.profileRepository.findOne({ + where: { id: _record.profileEmployeeId }, + }); + if (profile) { + profile.avatar = null; + profile.avatarName = null; + await this.profileRepository.save(profile, { data: req }); + } + } + + await new CallAPI().DeleteFile(req, `${_record?.avatar}/${_record?.avatarName}`); } if (!_record) { throw new HttpError(HttpStatus.NOT_FOUND, "ไม่พบข้อมูล"); diff --git a/src/controllers/ProfileAvatarEmployeeTempController.ts b/src/controllers/ProfileAvatarEmployeeTempController.ts index f16944a9..e26bd53f 100644 --- a/src/controllers/ProfileAvatarEmployeeTempController.ts +++ b/src/controllers/ProfileAvatarEmployeeTempController.ts @@ -8,6 +8,7 @@ import { CreateProfileEmployeeAvatar, ProfileAvatar } from "../entities/ProfileA import { ProfileEmployee } from "../entities/ProfileEmployee"; import permission from "../interfaces/permission"; import { setLogDataDiff } from "../interfaces/utils"; +import CallAPI from "../interfaces/call-api"; @Route("api/v1/org/profile-temp/avatar") @Tags("ProfileAvatar") @Security("bearerAuth") @@ -147,6 +148,19 @@ export class ProfileAvatarEmployeeTempController extends Controller { public async deleteAvatarEmployee(@Path() avatarId: string, @Request() req: RequestWithUser) { await new permission().PermissionDelete(req, "SYS_REGISTRY_TEMP"); const _record = await this.avatarRepository.findOneBy({ id: avatarId }); + if (_record) { + if (_record.isActive) { + const profile = await this.profileRepository.findOne({ + where: { id: _record.profileEmployeeId }, + }); + if (profile) { + profile.avatar = ""; + profile.avatarName = ""; + await this.profileRepository.save(profile, { data: req }); + } + } + } + await new CallAPI().DeleteFile(req, `${_record?.avatar}/${_record?.avatarName}`); if (!_record) { throw new HttpError(HttpStatus.NOT_FOUND, "ไม่พบข้อมูล"); } diff --git a/src/controllers/ProfileCertificateController.ts b/src/controllers/ProfileCertificateController.ts index 88d5c492..9d29dc10 100644 --- a/src/controllers/ProfileCertificateController.ts +++ b/src/controllers/ProfileCertificateController.ts @@ -40,7 +40,7 @@ export class ProfileCertificateController extends Controller { throw new HttpError(HttpStatus.BAD_REQUEST, "ไม่พบ profile ดังกล่าว"); } const record = await this.certificateRepo.find({ - where: { profileId: profile.id }, + where: { profileId: profile.id, isDeleted: false }, order: { createdAt: "ASC" }, }); return new HttpSuccess(record); @@ -52,7 +52,7 @@ export class ProfileCertificateController extends Controller { if (_workflow == false) await new permission().PermissionOrgUserGet(req, "SYS_REGISTRY_OFFICER", profileId); const record = await this.certificateRepo.find({ - where: { profileId: profileId }, + where: { profileId: profileId, isDeleted: false }, order: { createdAt: "ASC" }, }); return new HttpSuccess(record); @@ -166,6 +166,45 @@ export class ProfileCertificateController extends Controller { return new HttpSuccess(); } + /** + * API ลบข้อมูลใบอนุญาตประกอบวิชาชีพ + * @summary API ลบข้อมูลใบอนุญาตประกอบวิชาชีพ + * @param certificateId คีย์ใบอนุญาตประกอบวิชาชีพ + */ + @Patch("update-delete/{certificateId}") + public async updateIsDeleted( + @Request() req: RequestWithUser, + @Path() certificateId: string, + ) { + const record = await this.certificateRepo.findOneBy({ id: certificateId }); + if (!record) throw new HttpError(HttpStatus.NOT_FOUND, "ไม่พบข้อมูล"); + if (record.isDeleted === true) { + return new HttpSuccess(); + } + await new permission().PermissionOrgUserDelete(req, "SYS_REGISTRY_OFFICER", record.profileId); + const before = structuredClone(record); + const history = new ProfileCertificateHistory(); + const now = new Date(); + record.isDeleted = true; + record.lastUpdateUserId = req.user.sub; + record.lastUpdateFullName = req.user.name; + record.lastUpdatedAt = now; + + Object.assign(history, { ...record, id: undefined }); + history.profileCertificateId = certificateId; + history.createdUserId = req.user.sub; + history.createdFullName = req.user.name; + history.createdAt = now; + + await Promise.all([ + this.certificateRepo.save(record, { data: req }), + setLogDataDiff(req, { before, after: record }), + this.certificateHistoryRepo.save(history, { data: req }), + ]); + + return new HttpSuccess(); + } + @Delete("{certificateId}") public async deleteCertificate(@Path() certificateId: string, @Request() req: RequestWithUser) { const _record = await this.certificateRepo.findOneBy({ id: certificateId }); diff --git a/src/controllers/ProfileCertificateEmployeeController.ts b/src/controllers/ProfileCertificateEmployeeController.ts index fb6474e0..74796f9e 100644 --- a/src/controllers/ProfileCertificateEmployeeController.ts +++ b/src/controllers/ProfileCertificateEmployeeController.ts @@ -40,7 +40,7 @@ export class ProfileCertificateEmployeeController extends Controller { throw new HttpError(HttpStatus.BAD_REQUEST, "ไม่พบ profile ดังกล่าว"); } const record = await this.certificateRepo.find({ - where: { profileEmployeeId: profile.id }, + where: { profileEmployeeId: profile.id, isDeleted: false }, order: { createdAt: "ASC" }, }); return new HttpSuccess(record); @@ -52,7 +52,7 @@ export class ProfileCertificateEmployeeController extends Controller { if (_workflow == false) await new permission().PermissionOrgUserGet(req, "SYS_REGISTRY_EMP", profileEmployeeId); const record = await this.certificateRepo.find({ - where: { profileEmployeeId: profileEmployeeId }, + where: { profileEmployeeId: profileEmployeeId, isDeleted: false }, order: { createdAt: "ASC" }, }); return new HttpSuccess(record); @@ -174,6 +174,45 @@ export class ProfileCertificateEmployeeController extends Controller { return new HttpSuccess(); } + /** + * API ลบข้อมูลใบอนุญาตประกอบวิชาชีพ + * @summary API ลบข้อมูลใบอนุญาตประกอบวิชาชีพ + * @param certificateId คีย์ใบอนุญาตประกอบวิชาชีพ + */ + @Patch("update-delete/{certificateId}") + public async updateIsDeleted( + @Request() req: RequestWithUser, + @Path() certificateId: string, + ) { + const record = await this.certificateRepo.findOneBy({ id: certificateId }); + if (!record) throw new HttpError(HttpStatus.NOT_FOUND, "ไม่พบข้อมูล"); + if (record.isDeleted === true) { + return new HttpSuccess(); + } + await new permission().PermissionOrgUserDelete(req, "SYS_REGISTRY_EMP", record.profileEmployeeId); + const before = structuredClone(record); + const history = new ProfileCertificateHistory(); + const now = new Date(); + record.isDeleted = true; + record.lastUpdateUserId = req.user.sub; + record.lastUpdateFullName = req.user.name; + record.lastUpdatedAt = now; + + Object.assign(history, { ...record, id: undefined }); + history.profileCertificateId = certificateId; + history.createdUserId = req.user.sub; + history.createdFullName = req.user.name; + history.createdAt = now; + + await Promise.all([ + this.certificateRepo.save(record, { data: req }), + setLogDataDiff(req, { before, after: record }), + this.certificateHistoryRepo.save(history, { data: req }), + ]); + + return new HttpSuccess(); + } + @Delete("{certificateId}") public async deleteCertificate(@Path() certificateId: string, @Request() req: RequestWithUser) { const _record = await this.certificateRepo.findOneBy({ id: certificateId }); diff --git a/src/controllers/ProfileCertificateEmployeeTempController.ts b/src/controllers/ProfileCertificateEmployeeTempController.ts index 20ec902b..8ad0fa3f 100644 --- a/src/controllers/ProfileCertificateEmployeeTempController.ts +++ b/src/controllers/ProfileCertificateEmployeeTempController.ts @@ -40,7 +40,7 @@ export class ProfileCertificateEmployeeTempController extends Controller { throw new HttpError(HttpStatus.BAD_REQUEST, "ไม่พบ profile ดังกล่าว"); } const record = await this.certificateRepo.find({ - where: { profileEmployeeId: profile.id }, + where: { profileEmployeeId: profile.id, isDeleted: false }, order: { createdAt: "ASC" }, }); return new HttpSuccess(record); @@ -51,7 +51,7 @@ export class ProfileCertificateEmployeeTempController extends Controller { let _workflow = await new permission().Workflow(req, profileEmployeeId, "SYS_REGISTRY_TEMP"); if (_workflow == false) await new permission().PermissionGet(req, "SYS_REGISTRY_TEMP"); const record = await this.certificateRepo.find({ - where: { profileEmployeeId }, + where: { profileEmployeeId, isDeleted: false }, order: { createdAt: "ASC" }, }); return new HttpSuccess(record); @@ -162,6 +162,45 @@ export class ProfileCertificateEmployeeTempController extends Controller { return new HttpSuccess(); } + /** + * API ลบข้อมูลใบอนุญาตประกอบวิชาชีพ + * @summary API ลบข้อมูลใบอนุญาตประกอบวิชาชีพ + * @param certificateId คีย์ใบอนุญาตประกอบวิชาชีพ + */ + @Patch("update-delete/{certificateId}") + public async updateIsDeleted( + @Request() req: RequestWithUser, + @Path() certificateId: string, + ) { + const record = await this.certificateRepo.findOneBy({ id: certificateId }); + if (!record) throw new HttpError(HttpStatus.NOT_FOUND, "ไม่พบข้อมูล"); + if (record.isDeleted === true) { + return new HttpSuccess(); + } + await new permission().PermissionDelete(req, "SYS_REGISTRY_TEMP"); + const before = structuredClone(record); + const history = new ProfileCertificateHistory(); + const now = new Date(); + record.isDeleted = true; + record.lastUpdateUserId = req.user.sub; + record.lastUpdateFullName = req.user.name; + record.lastUpdatedAt = now; + + Object.assign(history, { ...record, id: undefined }); + history.profileCertificateId = certificateId; + history.createdUserId = req.user.sub; + history.createdFullName = req.user.name; + history.createdAt = now; + + await Promise.all([ + this.certificateRepo.save(record, { data: req }), + setLogDataDiff(req, { before, after: record }), + this.certificateHistoryRepo.save(history, { data: req }), + ]); + + return new HttpSuccess(); + } + @Delete("{certificateId}") public async deleteCertificate(@Path() certificateId: string, @Request() req: RequestWithUser) { await new permission().PermissionDelete(req, "SYS_REGISTRY_TEMP"); diff --git a/src/controllers/ProfileChangeNameController.ts b/src/controllers/ProfileChangeNameController.ts index 92ce5350..77cff634 100644 --- a/src/controllers/ProfileChangeNameController.ts +++ b/src/controllers/ProfileChangeNameController.ts @@ -41,7 +41,7 @@ export class ProfileChangeNameController extends Controller { throw new HttpError(HttpStatus.BAD_REQUEST, "ไม่พบ profile ดังกล่าว"); } const lists = await this.changeNameRepository.find({ - where: { profileId: profile.id }, + where: { profileId: profile.id, isDeleted: false }, order: { createdAt: "ASC" }, }); return new HttpSuccess(lists); @@ -53,7 +53,7 @@ export class ProfileChangeNameController extends Controller { if (_workflow == false) await new permission().PermissionOrgUserGet(req, "SYS_REGISTRY_OFFICER", profileId); const lists = await this.changeNameRepository.find({ - where: { profileId: profileId }, + where: { profileId: profileId, isDeleted: false }, order: { createdAt: "ASC" }, }); return new HttpSuccess(lists); @@ -115,8 +115,13 @@ export class ProfileChangeNameController extends Controller { await this.profileRepository.save(profile, { data: req }); setLogDataDiff(req, { before, after: profile }); - if (profile != null && profile.keycloak != null) { - const result = await updateName(profile.keycloak, profile.firstName, profile.lastName); + if (profile != null && profile.keycloak != null && profile.isDelete === false) { + const result = await updateName( + profile.keycloak, + profile.firstName, + profile.lastName, + profile.prefix, + ); if (!result) { throw new Error(result.errorMessage); } @@ -181,8 +186,13 @@ export class ProfileChangeNameController extends Controller { } // ปิดไว้ก่อนเพราะ error ต้องใช้ keycloak ที่มีสิทธิ์ในการ update //update 17/07 - if (profile != null && profile.keycloak != null) { - const result = await updateName(profile.keycloak, profile.firstName, profile.lastName); + if (profile != null && profile.keycloak != null && profile.isDelete === false) { + const result = await updateName( + profile.keycloak, + profile.firstName, + profile.lastName, + profile.prefix, + ); if (!result) { throw new Error(result.errorMessage); } @@ -191,6 +201,42 @@ export class ProfileChangeNameController extends Controller { return new HttpSuccess(); } + /** + * API ลบข้อมูลประวัติการเปลี่ยนชื่อ - นามสกุล + * @summary API ลบข้อมูลประวัติการเปลี่ยนชื่อ - นามสกุล + * @param trainingId คีย์ประวัติการเปลี่ยนชื่อ - นามสกุล + */ + @Patch("update-delete/{changeNameId}") + public async updateIsDeleted(@Request() req: RequestWithUser, @Path() changeNameId: string) { + const record = await this.changeNameRepository.findOneBy({ id: changeNameId }); + if (!record) throw new HttpError(HttpStatus.NOT_FOUND, "ไม่พบข้อมูล"); + if (record.isDeleted === true) { + return new HttpSuccess(); + } + await new permission().PermissionOrgUserDelete(req, "SYS_REGISTRY_OFFICER", record.profileId); + const before = structuredClone(record); + const history = new ProfileChangeNameHistory(); + const now = new Date(); + record.isDeleted = true; + record.lastUpdateUserId = req.user.sub; + record.lastUpdateFullName = req.user.name; + record.lastUpdatedAt = now; + + Object.assign(history, { ...record, id: undefined }); + history.profileChangeNameId = changeNameId; + history.createdUserId = req.user.sub; + history.createdFullName = req.user.name; + history.createdAt = now; + + await Promise.all([ + this.changeNameRepository.save(record, { data: req }), + setLogDataDiff(req, { before, after: record }), + this.changeNameHistoryRepository.save(history, { data: req }), + ]); + + return new HttpSuccess(); + } + @Delete("{changeNameId}") public async deleteTraning(@Path() changeNameId: string, @Request() req: RequestWithUser) { const _record = await this.changeNameRepository.findOneBy({ id: changeNameId }); diff --git a/src/controllers/ProfileChangeNameEmployeeController.ts b/src/controllers/ProfileChangeNameEmployeeController.ts index 51ff0b8e..c2df9c8c 100644 --- a/src/controllers/ProfileChangeNameEmployeeController.ts +++ b/src/controllers/ProfileChangeNameEmployeeController.ts @@ -41,7 +41,7 @@ export class ProfileChangeNameEmployeeController extends Controller { throw new HttpError(HttpStatus.BAD_REQUEST, "ไม่พบ profile ดังกล่าว"); } const lists = await this.changeNameRepository.find({ - where: { profileEmployeeId: profile.id }, + where: { profileEmployeeId: profile.id, isDeleted: false }, order: { createdAt: "ASC" }, }); return new HttpSuccess(lists); @@ -53,7 +53,7 @@ export class ProfileChangeNameEmployeeController extends Controller { if (_workflow == false) await new permission().PermissionOrgUserGet(req, "SYS_REGISTRY_EMP", profileEmployeeId); const lists = await this.changeNameRepository.find({ - where: { profileEmployeeId: profileEmployeeId }, + where: { profileEmployeeId: profileEmployeeId, isDeleted: false }, order: { createdAt: "ASC" }, }); return new HttpSuccess(lists); @@ -121,8 +121,13 @@ export class ProfileChangeNameEmployeeController extends Controller { await this.profileEmployeeRepo.save(profile, { data: req }); setLogDataDiff(req, { before, after: profile }); - if (profile != null && profile.keycloak != null) { - const result = await updateName(profile.keycloak, profile.firstName, profile.lastName); + if (profile != null && profile.keycloak != null && profile.isDelete === false) { + const result = await updateName( + profile.keycloak, + profile.firstName, + profile.lastName, + profile.prefix, + ); if (!result) { throw new Error(result.errorMessage); } @@ -189,6 +194,46 @@ export class ProfileChangeNameEmployeeController extends Controller { return new HttpSuccess(); } + /** + * API ลบข้อมูลประวัติการเปลี่ยนชื่อ - นามสกุล + * @summary API ลบข้อมูลประวัติการเปลี่ยนชื่อ - นามสกุล + * @param trainingId คีย์ประวัติการเปลี่ยนชื่อ - นามสกุล + */ + @Patch("update-delete/{changeNameId}") + public async updateIsDeleted(@Request() req: RequestWithUser, @Path() changeNameId: string) { + const record = await this.changeNameRepository.findOneBy({ id: changeNameId }); + if (!record) throw new HttpError(HttpStatus.NOT_FOUND, "ไม่พบข้อมูล"); + if (record.isDeleted === true) { + return new HttpSuccess(); + } + await new permission().PermissionOrgUserDelete( + req, + "SYS_REGISTRY_EMP", + record.profileEmployeeId, + ); + const before = structuredClone(record); + const history = new ProfileChangeNameHistory(); + const now = new Date(); + record.isDeleted = true; + record.lastUpdateUserId = req.user.sub; + record.lastUpdateFullName = req.user.name; + record.lastUpdatedAt = now; + + Object.assign(history, { ...record, id: undefined }); + history.profileChangeNameId = changeNameId; + history.createdUserId = req.user.sub; + history.createdFullName = req.user.name; + history.createdAt = now; + + await Promise.all([ + this.changeNameRepository.save(record, { data: req }), + setLogDataDiff(req, { before, after: record }), + this.changeNameHistoryRepository.save(history, { data: req }), + ]); + + return new HttpSuccess(); + } + @Delete("{changeNameId}") public async deleteTraning(@Path() changeNameId: string, @Request() req: RequestWithUser) { const _record = await this.changeNameRepository.findOneBy({ id: changeNameId }); diff --git a/src/controllers/ProfileChangeNameEmployeeTempController.ts b/src/controllers/ProfileChangeNameEmployeeTempController.ts index 152279d6..92e1e2f3 100644 --- a/src/controllers/ProfileChangeNameEmployeeTempController.ts +++ b/src/controllers/ProfileChangeNameEmployeeTempController.ts @@ -41,7 +41,7 @@ export class ProfileChangeNameEmployeeTempController extends Controller { throw new HttpError(HttpStatus.BAD_REQUEST, "ไม่พบ profile ดังกล่าว"); } const lists = await this.changeNameRepository.find({ - where: { profileEmployeeId: profile.id }, + where: { profileEmployeeId: profile.id, isDeleted: false }, order: { createdAt: "ASC" }, }); return new HttpSuccess(lists); @@ -52,7 +52,7 @@ export class ProfileChangeNameEmployeeTempController extends Controller { let _workflow = await new permission().Workflow(req, profileEmployeeId, "SYS_REGISTRY_TEMP"); if (_workflow == false) await new permission().PermissionGet(req, "SYS_REGISTRY_TEMP"); const lists = await this.changeNameRepository.find({ - where: { profileEmployeeId: profileEmployeeId }, + where: { profileEmployeeId: profileEmployeeId, isDeleted: false }, order: { createdAt: "ASC" }, }); return new HttpSuccess(lists); @@ -113,8 +113,13 @@ export class ProfileChangeNameEmployeeTempController extends Controller { await this.profileEmployeeRepo.save(profile, { data: req }); setLogDataDiff(req, { before, after: profile }); - if (profile != null && profile.keycloak != null) { - const result = await updateName(profile.keycloak, profile.firstName, profile.lastName); + if (profile != null && profile.keycloak != null && profile.isDelete === false) { + const result = await updateName( + profile.keycloak, + profile.firstName, + profile.lastName, + profile.prefix, + ); if (!result) { throw new Error(result.errorMessage); } @@ -181,6 +186,42 @@ export class ProfileChangeNameEmployeeTempController extends Controller { return new HttpSuccess(); } + /** + * API ลบข้อมูลประวัติการเปลี่ยนชื่อ - นามสกุล + * @summary API ลบข้อมูลประวัติการเปลี่ยนชื่อ - นามสกุล + * @param trainingId คีย์ประวัติการเปลี่ยนชื่อ - นามสกุล + */ + @Patch("update-delete/{changeNameId}") + public async updateIsDeleted(@Request() req: RequestWithUser, @Path() changeNameId: string) { + const record = await this.changeNameRepository.findOneBy({ id: changeNameId }); + if (!record) throw new HttpError(HttpStatus.NOT_FOUND, "ไม่พบข้อมูล"); + if (record.isDeleted === true) { + return new HttpSuccess(); + } + await new permission().PermissionDelete(req, "SYS_REGISTRY_TEMP"); + const before = structuredClone(record); + const history = new ProfileChangeNameHistory(); + const now = new Date(); + record.isDeleted = true; + record.lastUpdateUserId = req.user.sub; + record.lastUpdateFullName = req.user.name; + record.lastUpdatedAt = now; + + Object.assign(history, { ...record, id: undefined }); + history.profileChangeNameId = changeNameId; + history.createdUserId = req.user.sub; + history.createdFullName = req.user.name; + history.createdAt = now; + + await Promise.all([ + this.changeNameRepository.save(record, { data: req }), + setLogDataDiff(req, { before, after: record }), + this.changeNameHistoryRepository.save(history, { data: req }), + ]); + + return new HttpSuccess(); + } + @Delete("{changeNameId}") public async deleteTraning(@Path() changeNameId: string, @Request() req: RequestWithUser) { await new permission().PermissionDelete(req, "SYS_REGISTRY_TEMP"); diff --git a/src/controllers/ProfileChildrenController.ts b/src/controllers/ProfileChildrenController.ts index 27075758..98319cf0 100644 --- a/src/controllers/ProfileChildrenController.ts +++ b/src/controllers/ProfileChildrenController.ts @@ -41,7 +41,7 @@ export class ProfileChildrenController extends Controller { throw new HttpError(HttpStatus.BAD_REQUEST, "ไม่พบ profile ดังกล่าว"); } const lists = await this.childrenRepository.find({ - where: { profileId: profile.id }, + where: { profileId: profile.id, isDeleted: false }, order: { createdAt: "ASC" }, }); return new HttpSuccess(lists); @@ -53,7 +53,7 @@ export class ProfileChildrenController extends Controller { if (_workflow == false) await new permission().PermissionOrgUserGet(req, "SYS_REGISTRY_OFFICER", profileId); const lists = await this.childrenRepository.find({ - where: { profileId: profileId }, + where: { profileId: profileId, isDeleted: false }, order: { createdAt: "ASC" }, }); return new HttpSuccess(lists); @@ -143,6 +143,45 @@ export class ProfileChildrenController extends Controller { return new HttpSuccess(); } + /** + * API ลบข้อมูลบุตร + * @summary API ลบข้อมูลบุตร + * @param trainingId คีย์บุตร + */ + @Patch("update-delete/{childrenId}") + public async updateIsDeleted( + @Request() req: RequestWithUser, + @Path() childrenId: string, + ) { + const record = await this.childrenRepository.findOneBy({ id: childrenId }); + if (!record) throw new HttpError(HttpStatus.NOT_FOUND, "ไม่พบข้อมูล"); + if (record.isDeleted === true) { + return new HttpSuccess(); + } + await new permission().PermissionOrgUserDelete(req, "SYS_REGISTRY_OFFICER", record.profileId); + const before = structuredClone(record); + const history = new ProfileChildrenHistory(); + const now = new Date(); + record.isDeleted = true; + record.lastUpdateUserId = req.user.sub; + record.lastUpdateFullName = req.user.name; + record.lastUpdatedAt = now; + + Object.assign(history, { ...record, id: undefined }); + history.profileChildrenId = childrenId; + history.createdUserId = req.user.sub; + history.createdFullName = req.user.name; + history.createdAt = now; + + await Promise.all([ + this.childrenRepository.save(record, { data: req }), + setLogDataDiff(req, { before, after: record }), + this.childrenHistoryRepository.save(history, { data: req }), + ]); + + return new HttpSuccess(); + } + @Delete("{childrenId}") public async deleteTraning(@Path() childrenId: string, @Request() req: RequestWithUser) { const _record = await this.childrenRepository.findOneBy({ id: childrenId }); diff --git a/src/controllers/ProfileChildrenEmployeeController.ts b/src/controllers/ProfileChildrenEmployeeController.ts index a8792e7b..2be404ef 100644 --- a/src/controllers/ProfileChildrenEmployeeController.ts +++ b/src/controllers/ProfileChildrenEmployeeController.ts @@ -41,7 +41,7 @@ export class ProfileChildrenEmployeeController extends Controller { throw new HttpError(HttpStatus.BAD_REQUEST, "ไม่พบ profile ดังกล่าว"); } const lists = await this.childrenRepository.find({ - where: { profileEmployeeId: profile.id }, + where: { profileEmployeeId: profile.id, isDeleted: false }, order: { createdAt: "ASC" }, }); return new HttpSuccess(lists); @@ -53,7 +53,7 @@ export class ProfileChildrenEmployeeController extends Controller { if (_workflow == false) await new permission().PermissionOrgUserGet(req, "SYS_REGISTRY_EMP", profileEmployeeId); const lists = await this.childrenRepository.find({ - where: { profileEmployeeId: profileEmployeeId }, + where: { profileEmployeeId: profileEmployeeId, isDeleted: false }, order: { createdAt: "ASC" }, }); return new HttpSuccess(lists); @@ -156,6 +156,45 @@ export class ProfileChildrenEmployeeController extends Controller { return new HttpSuccess(); } + /** + * API ลบข้อมูลบุตร + * @summary API ลบข้อมูลบุตร + * @param trainingId คีย์บุตร + */ + @Patch("update-delete/{childrenId}") + public async updateIsDeleted( + @Request() req: RequestWithUser, + @Path() childrenId: string, + ) { + const record = await this.childrenRepository.findOneBy({ id: childrenId }); + if (!record) throw new HttpError(HttpStatus.NOT_FOUND, "ไม่พบข้อมูล"); + if (record.isDeleted === true) { + return new HttpSuccess(); + } + await new permission().PermissionOrgUserDelete(req, "SYS_REGISTRY_EMP", record.profileEmployeeId); + const before = structuredClone(record); + const history = new ProfileChildrenHistory(); + const now = new Date(); + record.isDeleted = true; + record.lastUpdateUserId = req.user.sub; + record.lastUpdateFullName = req.user.name; + record.lastUpdatedAt = now; + + Object.assign(history, { ...record, id: undefined }); + history.profileChildrenId = childrenId; + history.createdUserId = req.user.sub; + history.createdFullName = req.user.name; + history.createdAt = now; + + await Promise.all([ + this.childrenRepository.save(record, { data: req }), + setLogDataDiff(req, { before, after: record }), + this.childrenHistoryRepository.save(history, { data: req }), + ]); + + return new HttpSuccess(); + } + @Delete("{childrenId}") public async deleteTraning(@Path() childrenId: string, @Request() req: RequestWithUser) { const _record = await this.childrenRepository.findOneBy({ id: childrenId }); diff --git a/src/controllers/ProfileChildrenEmployeeTempController.ts b/src/controllers/ProfileChildrenEmployeeTempController.ts index 4ee31e5f..83a04a30 100644 --- a/src/controllers/ProfileChildrenEmployeeTempController.ts +++ b/src/controllers/ProfileChildrenEmployeeTempController.ts @@ -41,7 +41,7 @@ export class ProfileChildrenEmployeeTempController extends Controller { throw new HttpError(HttpStatus.BAD_REQUEST, "ไม่พบ profile ดังกล่าว"); } const lists = await this.childrenRepository.find({ - where: { profileEmployeeId: profile.id }, + where: { profileEmployeeId: profile.id, isDeleted: false }, order: { createdAt: "ASC" }, }); return new HttpSuccess(lists); @@ -52,7 +52,7 @@ export class ProfileChildrenEmployeeTempController extends Controller { let _workflow = await new permission().Workflow(req, profileEmployeeId, "SYS_REGISTRY_TEMP"); if (_workflow == false) await new permission().PermissionGet(req, "SYS_REGISTRY_TEMP"); const lists = await this.childrenRepository.find({ - where: { profileEmployeeId: profileEmployeeId }, + where: { profileEmployeeId: profileEmployeeId, isDeleted: false }, order: { createdAt: "ASC" }, }); return new HttpSuccess(lists); @@ -143,6 +143,45 @@ export class ProfileChildrenEmployeeTempController extends Controller { return new HttpSuccess(); } + /** + * API ลบข้อมูลบุตร + * @summary API ลบข้อมูลบุตร + * @param trainingId คีย์บุตร + */ + @Patch("update-delete/{childrenId}") + public async updateIsDeleted( + @Request() req: RequestWithUser, + @Path() childrenId: string, + ) { + const record = await this.childrenRepository.findOneBy({ id: childrenId }); + if (!record) throw new HttpError(HttpStatus.NOT_FOUND, "ไม่พบข้อมูล"); + if (record.isDeleted === true) { + return new HttpSuccess(); + } + await new permission().PermissionDelete(req, "SYS_REGISTRY_TEMP"); + const before = structuredClone(record); + const history = new ProfileChildrenHistory(); + const now = new Date(); + record.isDeleted = true; + record.lastUpdateUserId = req.user.sub; + record.lastUpdateFullName = req.user.name; + record.lastUpdatedAt = now; + + Object.assign(history, { ...record, id: undefined }); + history.profileChildrenId = childrenId; + history.createdUserId = req.user.sub; + history.createdFullName = req.user.name; + history.createdAt = now; + + await Promise.all([ + this.childrenRepository.save(record, { data: req }), + setLogDataDiff(req, { before, after: record }), + this.childrenHistoryRepository.save(history, { data: req }), + ]); + + return new HttpSuccess(); + } + @Delete("{childrenId}") public async deleteTraning(@Path() childrenId: string, @Request() req: RequestWithUser) { await new permission().PermissionDelete(req, "SYS_REGISTRY_TEMP"); diff --git a/src/controllers/ProfileController.ts b/src/controllers/ProfileController.ts index 4fe44943..9e758e67 100644 --- a/src/controllers/ProfileController.ts +++ b/src/controllers/ProfileController.ts @@ -32,7 +32,7 @@ import { UpdateProfileCouple, UpdatePrivacyDto, } from "../entities/Profile"; -import { Brackets, In, IsNull, Like, Not } from "typeorm"; +import { Brackets, In, IsNull, Like, Not, MoreThan } from "typeorm"; import { OrgRevision } from "../entities/OrgRevision"; import { PosMaster } from "../entities/PosMaster"; import { PosLevel } from "../entities/PosLevel"; @@ -61,6 +61,7 @@ import { ProfileFamilyFather } from "../entities/ProfileFamilyFather"; import Extension from "../interfaces/extension"; import { ProfileInsignia } from "../entities/ProfileInsignia"; import { ProfileLeave } from "../entities/ProfileLeave"; +import { ProfileAbsentLate } from "../entities/ProfileAbsentLate"; import { updateName, deleteUser } from "../keycloak"; import permission from "../interfaces/permission"; import { PosMasterAct } from "../entities/PosMasterAct"; @@ -86,10 +87,12 @@ import { OrgChild4 } from "../entities/OrgChild4"; import { ProfileSalaryHistory } from "../entities/ProfileSalaryHistory"; import { ProfileAssistance } from "../entities/ProfileAssistance"; import { CommandRecive } from "../entities/CommandRecive"; +import { CommandCode } from "../entities/CommandCode"; import { EmployeePosMaster } from "../entities/EmployeePosMaster"; import { CreatePosMasterHistoryOfficer, getTopDegrees } from "../services/PositionService"; import { ProfileLeaveService } from "../services/ProfileLeaveService"; import { PostRetireToExprofile } from "./ExRetirementController"; +import { getPosNumCodeSit } from "../services/CommandService"; @Route("api/v1/org/profile") @Tags("Profile") @Security("bearerAuth") @@ -133,6 +136,7 @@ export class ProfileController extends Controller { private subDistrictRepo = AppDataSource.getRepository(SubDistrict); private profileInsigniaRepo = AppDataSource.getRepository(ProfileInsignia); private profileLeaveRepository = AppDataSource.getRepository(ProfileLeave); + private profileAbsentLateRepo = AppDataSource.getRepository(ProfileAbsentLate); private posMasterActRepository = AppDataSource.getRepository(PosMasterAct); private profileChildrenRepository = AppDataSource.getRepository(ProfileChildren); private changeNameRepository = AppDataSource.getRepository(ProfileChangeName); @@ -143,6 +147,7 @@ export class ProfileController extends Controller { private permissionProflileRepository = AppDataSource.getRepository(PermissionProfile); private profileAssistanceRepository = AppDataSource.getRepository(ProfileAssistance); private commandReciveRepository = AppDataSource.getRepository(CommandRecive); + private commandCodeRepository = AppDataSource.getRepository(CommandCode); // Services private profileLeaveService = new ProfileLeaveService(); @@ -322,8 +327,16 @@ export class ProfileController extends Controller { ]; const educations = await this.profileEducationRepo.find({ - select: ["startDate", "endDate", "educationLevel", "degree", "field", "institute"], - where: { profileId: id }, + select: [ + "startDate", + "endDate", + "educationLevel", + "degree", + "field", + "institute", + "isDeleted", + ], + where: { profileId: id, isDeleted: false }, order: { level: "ASC" }, }); const Education = @@ -575,8 +588,8 @@ export class ProfileController extends Controller { let _child4 = child4?.orgChild4Name; const cert_raw = await this.certificateRepository.find({ - where: { profileId: id }, - select: ["certificateType", "issuer", "certificateNo", "issueDate"], + select: ["certificateType", "issuer", "certificateNo", "issueDate", "isDeleted"], + where: { profileId: id, isDeleted: false }, order: { createdAt: "ASC" }, }); const certs = @@ -596,8 +609,8 @@ export class ProfileController extends Controller { }, ]; const training_raw = await this.trainingRepository.find({ - select: ["startDate", "endDate", "place", "department", "name"], - where: { profileId: id }, + select: ["startDate", "endDate", "place", "department", "name", "isDeleted"], + where: { profileId: id, isDeleted: false }, order: { createdAt: "ASC" }, }); const trainings = @@ -633,8 +646,8 @@ export class ProfileController extends Controller { ]; const discipline_raw = await this.disciplineRepository.find({ - select: ["refCommandDate", "refCommandNo", "detail"], - where: { profileId: id }, + select: ["refCommandDate", "refCommandNo", "detail", "isDeleted"], + where: { profileId: id, isDeleted: false }, order: { createdAt: "ASC" }, }); const disciplines = @@ -655,8 +668,16 @@ export class ProfileController extends Controller { ]; const education_raw = await this.profileEducationRepo.find({ - select: ["startDate", "endDate", "educationLevel", "degree", "field", "institute"], - where: { profileId: id }, + select: [ + "startDate", + "endDate", + "educationLevel", + "degree", + "field", + "institute", + "isDeleted", + ], + where: { profileId: id, isDeleted: false }, // order: { lastUpdatedAt: "DESC" }, order: { level: "ASC" }, }); @@ -756,7 +777,7 @@ export class ProfileController extends Controller { insigniaType: true, }, }, - where: { profileId: id }, + where: { profileId: id, isDeleted: false }, order: { receiveDate: "ASC" }, }); const insignias = @@ -796,7 +817,7 @@ export class ProfileController extends Controller { const leave_raw = await this.profileLeaveRepository.find({ relations: { leaveType: true }, - where: { profileId: id }, + where: { profileId: id, isDeleted: false }, order: { dateLeaveStart: "ASC" }, }); const leaves = @@ -929,6 +950,7 @@ export class ProfileController extends Controller { public async getKk1new(@Path() id: string, @Request() req: RequestWithUser) { const profiles = await this.profileRepo.findOne({ relations: [ + "posLevel", "currentSubDistrict", "currentDistrict", "currentProvince", @@ -1009,6 +1031,13 @@ export class ProfileController extends Controller { order: { lastUpdatedAt: "DESC" }, }); + const posMasterId = + profiles.current_holders == null || + profiles.current_holders.length == 0 || + profiles.current_holders.find((x) => x.orgRevisionId == orgRevision?.id) == null + ? null + : profiles.current_holders.find((x) => x.orgRevisionId == orgRevision?.id)?.id; + const root = profiles.current_holders == null || profiles.current_holders.length == 0 || @@ -1052,8 +1081,15 @@ export class ProfileController extends Controller { let _child4 = child4?.orgChild4Name; const cert_raw = await this.certificateRepository.find({ - where: { profileId: id }, - select: ["certificateType", "issuer", "certificateNo", "issueDate", "expireDate"], + select: [ + "certificateType", + "issuer", + "certificateNo", + "issueDate", + "expireDate", + "isDeleted", + ], + where: { profileId: id, isDeleted: false }, order: { createdAt: "ASC" }, }); const certs = @@ -1062,33 +1098,31 @@ export class ProfileController extends Controller { certificateType: item.certificateType ?? null, issuer: item.issuer ?? null, certificateNo: item.certificateNo ? Extension.ToThaiNumber(item.certificateNo) : null, - issueDate: item.issueDate - ? Extension.ToThaiNumber(Extension.ToThaiFullDate2(item.issueDate)) - : "", - expireDate: item.expireDate - ? Extension.ToThaiNumber(Extension.ToThaiFullDate2(item.expireDate)) - : "", + detail: Extension.ToThaiNumber( + `${item.issuer ?? ""} ${item.certificateNo ?? ""}`.trim(), + ), issueToExpireDate: item.issueDate - ? item.expireDate - ? Extension.ToThaiNumber(`${Extension.ToThaiFullDate2(item.issueDate)} - ${Extension.ToThaiFullDate2(item.expireDate)}`) + ? item.expireDate + ? Extension.ToThaiNumber( + `${Extension.ToThaiFullDate2(item.issueDate)} - ${Extension.ToThaiFullDate2(item.expireDate)}`, + ) : Extension.ToThaiNumber(Extension.ToThaiFullDate2(item.issueDate)) : item.expireDate ? Extension.ToThaiNumber(Extension.ToThaiFullDate2(item.expireDate)) - : "" + : "", })) : [ { - certificateType: "-", - issuer: "-", - certificateNo: "-", - issueDate: "-", - expireDate: "-", - issueToExpireDate: "-", + certificateType: "", + issuer: "", + certificateNo: "", + detail: "", + issueToExpireDate: "", }, ]; const training_raw = await this.trainingRepository.find({ - select: ["place", "department", "name", "duration"], - where: { profileId: id }, + select: ["place", "department", "name", "duration", "isDeleted", "startDate", "endDate"], + where: { profileId: id, isDeleted: false }, order: { createdAt: "ASC" }, }); const trainings = @@ -1098,19 +1132,23 @@ export class ProfileController extends Controller { degree: item.name ? Extension.ToThaiNumber(item.name) : "", place: item.place ? Extension.ToThaiNumber(item.place) : "", duration: item.duration ? Extension.ToThaiNumber(item.duration) : "", + date: Extension.ToThaiNumber( + `${Extension.ToThaiFullDate2(item.startDate)} - ${Extension.ToThaiFullDate2(item.endDate)}`, + ), })) : [ { - institute: "-", - degree: "-", - place: "-", - duration: "-", + institute: "", + degree: "", + place: "", + duration: "", + date: "", }, ]; const discipline_raw = await this.disciplineRepository.find({ - select: ["refCommandDate", "refCommandNo", "detail", "level"], - where: { profileId: id }, + select: ["refCommandDate", "refCommandNo", "detail", "level", "isDeleted"], + where: { profileId: id, isDeleted: false }, order: { createdAt: "ASC" }, }); const disciplines = @@ -1125,16 +1163,17 @@ export class ProfileController extends Controller { })) : [ { - disciplineYear: "-", - disciplineDetail: "-", - refNo: "-", - level: "-", + disciplineYear: "", + disciplineDetail: "", + refNo: "", + level: "", }, ]; const education_raw = await this.profileEducationRepo .createQueryBuilder("education") .where("education.profileId = :profileId", { profileId: id }) + .andWhere("education.isDeleted = :isDeleted", { isDeleted: false }) .orderBy("CASE WHEN education.isEducation = true THEN 1 ELSE 2 END", "ASC") .addOrderBy("education.level", "ASC") .getMany(); @@ -1145,13 +1184,15 @@ export class ProfileController extends Controller { date: item.isDate ? `${item.startDate ? Extension.ToThaiNumber(Extension.ToThaiFullDate2(item.startDate)) : ""} - ${item.endDate ? Extension.ToThaiNumber(Extension.ToThaiFullDate2(item.endDate)) : ""}` : `${item.startDate ? Extension.ToThaiNumber(Extension.ToThaiShortYear(new Date(item.startDate))) : ""} - ${item.endDate ? Extension.ToThaiNumber(Extension.ToThaiShortYear(new Date(item.endDate))) : ""}`, - degree: item.degree ? `${item.degree} ${item.field ? item.field : ""}` : "", + degree: `${item.degree ?? ""} ${item.field ?? ""}`.trim(), + level: item.educationLevel, })) : [ { - institute: "-", - date: "-", - degree: "-", + institute: "", + date: "", + degree: "", + level: "", }, ]; const salary_raw = await this.salaryRepo.find({ @@ -1218,19 +1259,19 @@ export class ProfileController extends Controller { })) : [ { - commandName: "-", - salaryDate: "-", - position: "-", - posNo: "-", - salary: "-", - special: "-", - rank: "-", - refAll: "-", - positionLevel: "-", - positionType: "-", - positionAmount: "-", - fullName: "-", - ocFullPath: "-", + commandName: "", + salaryDate: "", + position: "", + posNo: "", + salary: "", + special: "", + rank: "", + refAll: "", + positionLevel: "", + positionType: "", + positionAmount: "", + fullName: "", + ocFullPath: "", }, ]; @@ -1245,13 +1286,14 @@ export class ProfileController extends Controller { "page", "refCommandDate", "note", + "isDeleted", ], relations: { insignia: { insigniaType: true, }, }, - where: { profileId: id }, + where: { profileId: id, isDeleted: false }, order: { receiveDate: "ASC" }, }); const insignias = @@ -1270,23 +1312,26 @@ export class ProfileController extends Controller { section: item.section ? Extension.ToThaiNumber(item.section) : "", page: item.page ? Extension.ToThaiNumber(item.page) : "", refCommandDate: item.refCommandDate - ? Extension.ToThaiNumber(Extension.ToThaiFullDate2(item.refCommandDate)) - : "", + ? Extension.ToThaiNumber( + `ราชกิจจานุเบกษา เล่มที่ ${item.volume ?? "-"} ตอนที่ ${item.section ?? "-"} ลว. ${Extension.ToThaiFullDate2(item.refCommandDate)}`, + ) + : "-", note: item.note ? Extension.ToThaiNumber(item.note) : "", })) : [ { - receiveDate: "-", - insigniaName: "-", - insigniaShortName: "-", - insigniaTypeName: "-", - no: "-", - issue: "-", - volumeNo: "-", - volume: "-", - section: "-", - page: "-", - refCommandDate: "-", + receiveDate: "", + insigniaName: "", + insigniaShortName: "", + insigniaTypeName: "", + no: "", + issue: "", + volumeNo: "", + volume: "", + section: "", + page: "", + refCommandDate: "", + note: "", }, ]; @@ -1294,6 +1339,7 @@ export class ProfileController extends Controller { .createQueryBuilder("profileLeave") .leftJoinAndSelect("profileLeave.leaveType", "leaveType") .select([ + "profileLeave.isDeleted", "profileLeave.leaveTypeId", "leaveType.name as name", "leaveType.code as code", @@ -1303,6 +1349,7 @@ export class ProfileController extends Controller { ]) .addSelect("SUM(profileLeave.leaveDays)", "totalLeaveDays") .where("profileLeave.profileId = :profileId", { profileId: id }) + .andWhere("profileLeave.isDeleted = :isDeleted", { isDeleted: false }) .andWhere("profileLeave.status = :status", { status: "approve" }) .groupBy("profileLeave.leaveTypeId") .orderBy("code", "ASC") @@ -1321,9 +1368,7 @@ export class ProfileController extends Controller { const totalLeaveDaysKey = `totalLeaveDaysLv${lvIndex}`; const leaveTypeNameKey = `leaveTypeNameLv${lvIndex}`; - const leaveDate = item.maxDateLeaveStart - ? new Date(item.maxDateLeaveStart) - : null; + const leaveDate = item.maxDateLeaveStart ? new Date(item.maxDateLeaveStart) : null; const year = leaveDate ? Extension.ToThaiNumber(Extension.ToThaiShortYear(leaveDate)) : ""; @@ -1333,61 +1378,134 @@ export class ProfileController extends Controller { yearData = { year }; for (let i = 1; i <= 11; i++) { - yearData[`leaveTypeCodeLv${i}`] = "-"; - yearData[`totalLeaveDaysLv${i}`] = "-"; - yearData[`leaveTypeNameLv${i}`] = "-"; + yearData[`leaveTypeCodeLv${i}`] = ""; + yearData[`totalLeaveDaysLv${i}`] = ""; + yearData[`leaveTypeNameLv${i}`] = ""; } leaves.push(yearData); } - yearData[leaveTypeCodeKey] = item.code ? item.code : "-"; + yearData[leaveTypeCodeKey] = item.code ? item.code : ""; yearData[totalLeaveDaysKey] = item.totalLeaveDays ? Extension.ToThaiNumber(item.totalLeaveDays.toString()) : "-"; - yearData[leaveTypeNameKey] = item.name ? item.name : "-"; + yearData[leaveTypeNameKey] = item.name ? item.name : ""; } } }); + // Query มาสาย/ขาดราชการ และ merge ตามปี + const absentLate_raw = await this.profileAbsentLateRepo + .createQueryBuilder("absentLate") + .select([ + "YEAR(absentLate.stampDate) as year", + "absentLate.status as status", + "SUM(absentLate.stampAmount) as totalAmount", + ]) + .where("absentLate.profileId = :profileId", { profileId: id }) + .andWhere("absentLate.isDeleted = :isDeleted", { isDeleted: false }) + .groupBy("YEAR(absentLate.stampDate), absentLate.status") + .orderBy("year", "DESC") + .getRawMany(); + + // Merge มาสาย/ขาดราชการเข้า leaves array + absentLate_raw.forEach((item) => { + const year = item.year ? Extension.ToThaiNumber((item.year + 543).toString()) : ""; + + let yearData = leaves.find((data) => data.year === year); + + // ถ้าไม่มีปีนั้นใน leaves ให้สร้างใหม่ + if (!yearData) { + yearData = { year }; + for (let i = 1; i <= 11; i++) { + yearData[`leaveTypeCodeLv${i}`] = ""; + yearData[`totalLeaveDaysLv${i}`] = ""; + yearData[`leaveTypeNameLv${i}`] = ""; + } + leaves.push(yearData); + } + + // เพิ่มข้อมูลมาสาย/ขาดราชการ + if (item.status === "LATE") { + yearData.late = item.totalAmount + ? Extension.ToThaiNumber(parseFloat(item.totalAmount).toFixed(1)) + : ""; + } else if (item.status === "ABSENT") { + yearData.absent = item.totalAmount + ? Extension.ToThaiNumber(parseFloat(item.totalAmount).toFixed(1)) + : ""; + } + }); + + // // เติมค่า "" ถ้าไม่มีข้อมูลมาสาย/ขาดราชการ + // leaves.forEach((yearData) => { + // if (!yearData.late) yearData.late = ""; + // if (!yearData.absent) yearData.absent = ""; + // }); + + if (leaves.length === 0) { + leaves.push({ year: "" }); + } + const leave2_raw = await this.profileLeaveRepository .createQueryBuilder("profileLeave") .leftJoinAndSelect("profileLeave.leaveType", "leaveType") .select([ + "profileLeave.leaveSubTypeName AS leaveSubTypeName", + "profileLeave.coupleDayLevelCountry AS coupleDayLevelCountry", + "profileLeave.isDeleted AS isDeleted", "profileLeave.dateLeaveStart AS dateLeaveStart", "profileLeave.dateLeaveEnd AS dateLeaveEnd", "profileLeave.leaveDays AS leaveDays", "profileLeave.reason AS reason", "leaveType.name as name", + "leaveType.code as code", ]) .where("profileLeave.profileId = :profileId", { profileId: id }) + .andWhere("profileLeave.isDeleted = :isDeleted", { isDeleted: false }) .andWhere("leaveType.code IN (:...codes)", { codes: ["LV-008", "LV-009", "LV-010"] }) .andWhere("profileLeave.status = :status", { status: "approve" }) .orderBy("leaveType.code", "ASC") .getRawMany(); const leaves2 = leave2_raw.length > 0 - ? leave2_raw.map((item) => ({ - date: - item.dateLeaveStart && item.dateLeaveEnd - ? Extension.ToThaiNumber(Extension.ToThaiFullDate2(item.dateLeaveStart)) + - " - " + - Extension.ToThaiNumber(Extension.ToThaiFullDate2(item.dateLeaveEnd)) - : "-", - type: item.name || "-", - leaveDays: item.leaveDays ? Extension.ToThaiNumber(item.leaveDays.toString()) : "-", - reason: item.reason || "-", - })) + ? leave2_raw.map((item) => { + const leaveTypeCode = item.code ? item.code.trim().toUpperCase() : ""; + + // ข้อที่ 1: LV-008 ให้ใช้ leaveSubTypeName (ประเภทย่อย) แทน name + const displayType = + leaveTypeCode === "LV-008" && item.leaveSubTypeName + ? item.leaveSubTypeName + : item.name || "-"; + + // ข้อที่ 2: แสดง reason ก่อนเสมอ ถ้ามี coupleDayLevelCountry ค่อยแสดงประเทศ + const displayReason = item.coupleDayLevelCountry + ? `${item.reason || ""} ลาไปประเทศ ${item.coupleDayLevelCountry}`.trim() + : item.reason || "-"; + + return { + date: + item.dateLeaveStart && item.dateLeaveEnd + ? Extension.ToThaiNumber(Extension.ToThaiFullDate2(item.dateLeaveStart)) + + " - " + + Extension.ToThaiNumber(Extension.ToThaiFullDate2(item.dateLeaveEnd)) + : "-", + type: displayType, + leaveDays: item.leaveDays ? Extension.ToThaiNumber(item.leaveDays.toString()) : "-", + reason: displayReason, + }; + }) : [ { - date: "-", - type: "-", - leaveDays: "-", - reason: "-", + date: "", + type: "", + leaveDays: "", + reason: "", }, ]; const children_raw = await this.profileChildrenRepository.find({ - where: { profileId: id }, + where: { profileId: id, isDeleted: false }, }); const children = children_raw.length > 0 @@ -1402,15 +1520,15 @@ export class ProfileController extends Controller { : [ { no: "", - childrenPrefix: "-", - childrenFirstName: "-", - childrenLastName: "-", - childrenFullName: "-", - childrenLive: "-", + childrenPrefix: "", + childrenFirstName: "", + childrenLastName: "", + childrenFullName: "", + childrenLive: "", }, ]; const changeName_raw = await this.changeNameRepository.find({ - where: { profileId: id }, + where: { profileId: id, isDeleted: false }, order: { createdAt: "ASC" }, }); const changeName = @@ -1426,11 +1544,11 @@ export class ProfileController extends Controller { })) : [ { - createdAt: "-", - status: "-", - prefix: "-", - firstName: "-", - lastName: "-", + createdAt: "", + status: "", + prefix: "", + firstName: "", + lastName: "", }, ]; @@ -1450,123 +1568,279 @@ export class ProfileController extends Controller { })) : [ { - birthDateOld: "-", - birthDate: "-", + birthDateOld: "", + birthDate: "", }, ]; const position_raw = await this.salaryRepo.find({ - where: { - profileId: id, - commandCode: In(["1", "2", "3", "4", "8", "10", "11", "12", "15", "16", "20"]), - isEntry: false, - }, + where: [ + { + profileId: id, + commandCode: In([ + "0", + "1", + "2", + "3", + "4", + "8", + "9", + "10", + "11", + "12", + "13", + "14", + "15", + "16", + "20", + ]), + // isEntry: false, + }, + { profileId: id, commandCode: IsNull() }, + ], order: { order: "ASC" }, }); + let _commandName: any = ""; + let _commandDateAffect: any = ""; const positionList = position_raw.length > 0 - ? position_raw.map((item) => ({ - commandName: item.commandName ?? "", - commandDateAffect: item.commandDateAffect - ? Extension.ToThaiNumber(Extension.ToThaiFullDate2(item.commandDateAffect)) - : "", - commandDateSign: item.commandDateSign - ? Extension.ToThaiNumber(Extension.ToThaiFullDate2(item.commandDateSign)) - : "", - posNo: - item.posNoAbb && item.posNo - ? Extension.ToThaiNumber(`${item.posNoAbb} ${item.posNo}`) - : "", - position: item.positionName, - posType: item.positionType, - posLevel: item.positionLevel - ? Extension.ToThaiNumber(item.positionLevel) - : item.positionCee - ? Extension.ToThaiNumber(item.positionCee) - : null, - amount: item.amount ? Extension.ToThaiNumber(Number(item.amount).toLocaleString()) : "", - positionSalaryAmount: item.positionSalaryAmount - ? Extension.ToThaiNumber(Number(item.positionSalaryAmount).toLocaleString()) - : "", - })) + ? await Promise.all( + position_raw.map(async (item, idx, arr) => { + const isLast = idx === arr.length - 1; + if (isLast) { + _commandDateAffect = item.commandDateAffect + ? Extension.ToThaiNumber(Extension.ToThaiFullDate2(item.commandDateAffect)) + : ""; + } + const _code = item.commandCode ? Number(item.commandCode) : null; + if (_code != null) { + _commandName = await this.commandCodeRepository.findOne({ + select: { name: true, code: true }, + where: { code: _code }, + }); + } + const codeSitAbb = item.posNumCodeSitAbb ?? "-"; + const commandNo = + item.commandNo && item.commandYear + ? item.commandNo + + "/" + + (item.commandYear > 2500 ? item.commandYear : item.commandYear + 543) + : "-"; + const dateAffect = item.commandDateAffect + ? `${Extension.ToThaiFullDate2(item.commandDateAffect)}` + : "-"; + return { + commandName: + _commandName && _commandName.name ? _commandName.name : item.commandName, + commandDateAffect: item.commandDateAffect + ? Extension.ToThaiNumber(Extension.ToThaiFullDate2(item.commandDateAffect)) + : "", + commandDateSign: item.commandDateSign + ? Extension.ToThaiNumber(Extension.ToThaiFullDate2(item.commandDateSign)) + : "", + posNo: + item.posNoAbb && item.posNo + ? Extension.ToThaiNumber(`${item.posNoAbb} ${item.posNo}`) + : "", + position: item.positionName, + posType: item.positionType, + posLevel: item.positionLevel + ? Extension.ToThaiNumber(item.positionLevel) + : item.positionCee + ? Extension.ToThaiNumber(item.positionCee) + : null, + amount: item.amount + ? Extension.ToThaiNumber(Number(item.amount).toLocaleString()) + : "", + positionSalaryAmount: item.positionSalaryAmount + ? Extension.ToThaiNumber(Number(item.positionSalaryAmount).toLocaleString()) + : "", + refDoc: Extension.ToThaiNumber( + `คำสั่ง ${codeSitAbb} ที่ ${commandNo} ลว. ${dateAffect}`, + ), + }; + }), + ) : [ { - commandName: "-", - commandDateAffect: "-", - commandDateSign: "-", - posNo: "-", - position: "-", - posType: "-", - posLevel: "-", - amount: "-", - positionSalaryAmount: "-", + commandName: "", + commandDateAffect: "", + commandDateSign: "", + posNo: "", + position: "", + posType: "", + posLevel: "", + amount: "", + positionSalaryAmount: "", + refDoc: "", }, ]; + // ประวัติพ้นจากราชการ + let retires = []; + const currentDate = new Date(); + // todo: รอข้อสรุป + // const retire_raw = await this.salaryRepo.findOne({ + // where: { + // profileId: id, + // commandCode: In(["12", "15", "16"]), + // }, + // order: { order: "desc" }, + // }); + + // if (retire_raw) { + // const startDate = retire_raw.commandDateAffect; + + // // คำนวณจำนวนวันจากวันพ้นสภาพถึงปัจจุบัน + // let daysCount = 0; + // if (startDate) { + // const start = new Date(startDate); + // daysCount = Math.ceil((currentDate.getTime() - start.getTime()) / (1000 * 60 * 60 * 24)); + // } + + // const startDateStr = startDate + // ? Extension.ToThaiNumber(Extension.ToThaiFullDate2(startDate)) + // : "-"; + + // retires.push({ + // date: `${startDateStr}`, + // detail: retire_raw.commandName ?? "-", + // day: daysCount > 0 ? Extension.ToThaiNumber(daysCount.toLocaleString()) : "-" + // }); + // } + + // กรณีไม่มีข้อมูล + if (retires.length === 0) { + retires.push({ date: "", detail: "", day: "" }); + } + + // รักษาการ const actposition_raw = await this.profileActpositionRepo.find({ - select: ["dateStart", "dateEnd", "position"], - where: { profileId: id }, - order: { createdAt: "ASC" }, - }); - const assistance_raw = await this.profileAssistanceRepository.find({ - select: ["dateStart", "dateEnd", "commandName", "agency", "document"], - where: { profileId: id }, + select: ["dateStart", "profileId", "dateEnd", "position", "commandId", "isDeleted"], + where: { profileId: id, status: true, isDeleted: false }, order: { createdAt: "ASC" }, }); + let _actpositions = []; + if (actposition_raw.length > 0) { + for (const item of actposition_raw) { + let _commandTypename: string = "รักษาการในตำแหน่ง"; + let _document: string = "-"; + let _org: string = "-"; - const _actposition = - actposition_raw.length > 0 - ? actposition_raw.map((item) => ({ - date: - item.dateStart && item.dateEnd - ? Extension.ToThaiNumber(`${Extension.ToThaiFullDate2(item.dateStart)} - ${Extension.ToThaiFullDate2(item.dateEnd)}`) - : item.dateStart - ? Extension.ToThaiNumber(Extension.ToThaiFullDate2(item.dateStart)) - : item.dateEnd - ? Extension.ToThaiNumber(Extension.ToThaiFullDate2(item.dateEnd)) - : "", - position: item.position ? Extension.ToThaiNumber(item.position) : "", - commandName: "รักษาการในตำแหน่ง", - agency: "", - document: "", - })) - : [ - { - date: "-", - position: "-", - commandName: "-", - agency: "-", - document: "-", + if (item.commandId) { + const { posNumCodeSitAbb, commandTypeName, commandNo, commandYear, commandExcecuteDate } = + await getPosNumCodeSit(item.commandId, "REPORTED"); + + _document = Extension.ToThaiNumber( + `คำสั่ง ${posNumCodeSitAbb ?? "-"} ที่ ${commandNo}/${commandYear > 2500 ? commandYear : commandYear + 543} ลว. ${Extension.ToThaiFullDate2(commandExcecuteDate)}`, + ); + _commandTypename = commandTypeName; + } + // ค้นหาหน่วยงานที่รักษาการ + const _posmasterAct = await this.posMasterActRepository.findOne({ + where: { + posMasterChildId: posMasterId ?? "", + statusReport: "DONE", + }, + }); + if (_posmasterAct) { + const _posMater = await this.posMasterRepo.findOne({ + where: { + id: _posmasterAct.posMasterId, }, - ]; - const _assistance = - assistance_raw.length > 0 - ? assistance_raw.map((item) => ({ - date: - item.dateStart && item.dateEnd - ? Extension.ToThaiNumber(`${Extension.ToThaiFullDate2(item.dateStart)} - ${Extension.ToThaiFullDate2(item.dateEnd)}`) - : item.dateStart - ? Extension.ToThaiNumber(Extension.ToThaiFullDate2(item.dateStart)) - : item.dateEnd - ? Extension.ToThaiNumber(Extension.ToThaiFullDate2(item.dateEnd)) - : "", - position: "", - commandName: item.commandName ? Extension.ToThaiNumber(item.commandName) : "", - agency: item.agency ? Extension.ToThaiNumber(item.agency) : "", - document: item.document ? Extension.ToThaiNumber(item.document) : "", - })) - : [ - { - date: "-", - position: "-", - commandName: "-", - agency: "-", - document: "-", + relations: { + orgRoot: true, + orgChild1: true, + orgChild2: true, + orgChild3: true, + orgChild4: true, }, - ]; - const actposition = [..._actposition, ..._assistance]; + }); + let child4 = _posMater?.orgChild4?.orgChild4Name ?? ""; + let child3 = _posMater?.orgChild3?.orgChild3Name ?? ""; + let child2 = _posMater?.orgChild2?.orgChild2Name ?? ""; + let child1 = _posMater?.orgChild1?.orgChild1Name ?? ""; + let root = _posMater?.orgRoot?.orgRootName ?? ""; + _org = `${child4} ${child3} ${child2} ${child1} ${root}`.trim(); + } + + _actpositions.push({ + date: + item.dateStart && item.dateEnd + ? Extension.ToThaiNumber( + `${Extension.ToThaiFullDate2(item.dateStart)} - ${Extension.ToThaiFullDate2(item.dateEnd)}`, + ) + : item.dateStart + ? Extension.ToThaiNumber(Extension.ToThaiFullDate2(item.dateStart)) + : item.dateEnd + ? Extension.ToThaiNumber(Extension.ToThaiFullDate2(item.dateEnd)) + : "", + position: item.position ? Extension.ToThaiNumber(item.position) : "", + commandName: _commandTypename, + agency: _org, + note: + _posmasterAct && _posmasterAct?.posMasterOrder + ? Extension.ToThaiNumber(_posmasterAct?.posMasterOrder.toString()) + : "-", + document: _document, + }); + } + } else { + _actpositions.push({ + date: "", + position: "", + commandName: "", + agency: "", + note: "", + document: "", + }); + } + // ช่วยราชการ + const assistance_raw = await this.profileAssistanceRepository.find({ + select: ["dateStart", "dateEnd", "commandName", "agency", "document", "isDeleted"], + where: { profileId: id, /*status: "PENDING",*/ isDeleted: false }, + order: { createdAt: "ASC" }, + }); + let _assistances = []; + if (assistance_raw.length > 0) { + for (const item of assistance_raw) { + let _commandTypename: string = item.commandName ?? "ให้ช่วยราชการ"; + let _document: string = item.document ? Extension.ToThaiNumber(item.document) : "-"; + let _org: string = item.agency ? Extension.ToThaiNumber(item.agency) : "-"; + + if (item.commandId) { + const { posNumCodeSitAbb, commandTypeName, commandNo, commandYear, commandExcecuteDate } = + await getPosNumCodeSit(item.commandId); + + _document = Extension.ToThaiNumber( + `คำสั่ง ${posNumCodeSitAbb ?? "-"} ที่ ${commandNo}/${commandYear > 2500 ? commandYear : commandYear + 543} ลว. ${Extension.ToThaiFullDate2(commandExcecuteDate)}`, + ); + _commandTypename = commandTypeName; + } + _assistances.push({ + date: + item.dateStart && item.dateEnd + ? Extension.ToThaiNumber( + `${Extension.ToThaiFullDate2(item.dateStart)} - ${Extension.ToThaiFullDate2(item.dateEnd)}`, + ) + : item.dateStart + ? Extension.ToThaiNumber(Extension.ToThaiFullDate2(item.dateStart)) + : item.dateEnd + ? Extension.ToThaiNumber(Extension.ToThaiFullDate2(item.dateEnd)) + : "", + position: "-", // รักษาการปัจจุบันไม่มีฟิลด์ตำแหน่ง + commandName: _commandTypename, + agency: _org, + note: "-", + document: _document, + }); + } + } + // Merge รักษาการ และ ช่วยราชการ + const actposition = [..._actpositions, ..._assistances]; + const duty_raw = await this.dutyRepository.find({ - where: { profileId: id }, + where: { profileId: id, isDeleted: false }, order: { createdAt: "ASC" }, }); const duty = @@ -1574,33 +1848,50 @@ export class ProfileController extends Controller { ? duty_raw.map((item) => ({ date: item.dateStart && item.dateEnd - ? Extension.ToThaiNumber(`${Extension.ToThaiFullDate2(item.dateStart)} - ${Extension.ToThaiFullDate2(item.dateEnd)}`) + ? Extension.ToThaiNumber( + `${Extension.ToThaiFullDate2(item.dateStart)} - ${Extension.ToThaiFullDate2(item.dateEnd)}`, + ) : item.dateStart ? Extension.ToThaiNumber(Extension.ToThaiFullDate2(item.dateStart)) : item.dateEnd ? Extension.ToThaiNumber(Extension.ToThaiFullDate2(item.dateEnd)) : "", - refCommandDate: item.refCommandDate - ? Extension.ToThaiNumber(Extension.ToThaiFullDate2(item.refCommandDate)) - : "", - refCommandNo: item.refCommandNo ? Extension.ToThaiNumber(item.refCommandNo) : "", + type: "-", + detail: Extension.ToThaiNumber(item.detail), + agency: "-", + refCommandNo: item.refCommandNo + ? item.refCommandDate + ? Extension.ToThaiNumber( + `${item.refCommandNo} ลว. ${Extension.ToThaiFullDate2(item.refCommandDate)}`, + ) + : Extension.ToThaiNumber(item.refCommandNo) + : "-", })) : [ { - date: "-", - refCommandDate: "-", - refCommandNo: "-", + date: "", + type: "", + detail: "", + agency: "", + refCommandNo: "", }, ]; const assessments_raw = await this.profileAssessmentsRepository.find({ - where: { profileId: id }, + where: { profileId: id, isDeleted: false }, order: { createdAt: "ASC" }, }); const assessments = assessments_raw.length > 0 ? assessments_raw.map((item) => ({ year: item.year ? Extension.ToThaiNumber((parseInt(item.year) + 543).toString()) : "", - period: item.period && item.period == "APR" ? "เมษายน" : "ตุลาคม", + period: + item.period && item.period == "APR" + ? Extension.ToThaiNumber( + `1 เม.ย. ${(parseInt(item.year) + 543 - 1).toString()} - 31 มี.ค. ${(parseInt(item.year) + 543).toString()}`, + ) + : Extension.ToThaiNumber( + `1 ต.ค. ${(parseInt(item.year) + 543 - 1).toString()} - 30 ก.ย. ${(parseInt(item.year) + 543).toString()}`, + ), point1: item.point1 ? Extension.ToThaiNumber(item.point1.toString()) : "", point1Total: item.point1Total ? Extension.ToThaiNumber(item.point1Total.toString()) @@ -1609,28 +1900,30 @@ export class ProfileController extends Controller { point2Total: item.point2Total ? Extension.ToThaiNumber(item.point2Total.toString()) : "", - pointSum: item.pointSum ? Extension.ToThaiNumber(item.pointSum.toString()) : "", + pointSum: item.pointSum + ? Extension.ToThaiNumber(`ร้อยละ ${item.pointSum.toString()}`) + : "", pointSumTh: item.pointSum ? Extension.textPoint(item.pointSum) : "", level: item.pointSum < 60.0 - ? "ต้องปรับปรุง (คะแนนต่ำกว่าร้อยละ ๖๐.๐๐)" + ? "ต้องปรับปรุง" : item.pointSum <= 69.99 && item.pointSum >= 60.0 - ? "พอใช้ (คะแนนร้อยละ ๖๐.๐๐ - ๖๙.๙๙)" + ? "พอใช้" : item.pointSum <= 79.99 && item.pointSum >= 70.0 - ? "ดี (คะแนนร้อยละ ๗๐.๐๐ - ๗๙.๙๙)" + ? "ดี" : item.pointSum <= 89.99 && item.pointSum >= 80.0 - ? "ดีมาก (คะแนนร้อยละ ๘๐.๐๐ - ๘๙.๙๙)" - : "ดีเด่น (คะแนนร้อยละ ๙๐.๐๐ ขึ้นไป)", + ? "ดีมาก" + : "ดีเด่น", })) : [ { - year: "-", - period: "-", - point1: "-", - point2: "-", - pointSum: "-", - pointSumTh: "-", - level: "-", + year: "", + period: "", + point1: "", + point2: "", + pointSum: "", + pointSumTh: "", + level: "", }, ]; const profileAbility_raw = await this.profileAbilityRepo.find({ @@ -1645,8 +1938,8 @@ export class ProfileController extends Controller { })) : [ { - field: "-", - detail: "-", + field: "", + detail: "", }, ]; @@ -1660,32 +1953,52 @@ export class ProfileController extends Controller { }); const otherIncome = otherIncome_raw.length > 0 - ? otherIncome_raw.map((item) => ({ - commandName: item.commandName ?? "", - commandDateAffect: item.commandDateAffect - ? Extension.ToThaiNumber(Extension.ToThaiFullDate2(item.commandDateAffect)) - : "", - commandDateSign: item.commandDateSign - ? Extension.ToThaiNumber(Extension.ToThaiFullDate2(item.commandDateSign)) - : "", - commandNo: item.commandNo ? Extension.ToThaiNumber(item.commandNo) : "", - position: item.positionName, - posLevel: item.positionLevel - ? Extension.ToThaiNumber(item.positionLevel) - : item.positionCee - ? Extension.ToThaiNumber(item.positionCee) - : null, - amount: item.amount ? Extension.ToThaiNumber(Number(item.amount).toLocaleString()) : "", - })) + ? await Promise.all( + otherIncome_raw.map(async (item) => { + const codeSitAbb = item.posNumCodeSitAbb ?? "-"; + const commandNo = + item.commandNo && item.commandYear + ? item.commandNo + + "/" + + (item.commandYear > 2500 ? item.commandYear : item.commandYear + 543) + : "-"; + const dateAffect = item.commandDateAffect + ? `${Extension.ToThaiFullDate2(item.commandDateAffect)}` + : "-"; + return { + commandName: item.commandName ?? "", + commandDateAffect: item.commandDateAffect + ? Extension.ToThaiNumber(Extension.ToThaiFullDate2(item.commandDateAffect)) + : "", + commandDateSign: item.commandDateSign + ? Extension.ToThaiNumber(Extension.ToThaiFullDate2(item.commandDateSign)) + : "", + commandNo: item.commandNo ? Extension.ToThaiNumber(item.commandNo) : "", + position: item.positionName, + posLevel: item.positionLevel + ? Extension.ToThaiNumber(item.positionLevel) + : item.positionCee + ? Extension.ToThaiNumber(item.positionCee) + : null, + amount: item.amount + ? Extension.ToThaiNumber(Number(item.amount).toLocaleString()) + : "", + refDoc: Extension.ToThaiNumber( + `คำสั่ง ${codeSitAbb} ที่ ${commandNo} ลว. ${dateAffect}`, + ), + }; + }), + ) : [ { - commandName: "-", - commandDateAffect: "-", - commandDateSign: "-", - commandNo: "-", - position: "-", - posLevel: "-", - amount: "-", + commandName: "", + commandDateAffect: "", + commandDateSign: "", + commandNo: "", + position: "", + posLevel: "", + amount: "", + refDoc: "", }, ]; @@ -1732,28 +2045,52 @@ export class ProfileController extends Controller { ) : ""; - let portfolios: any; + let portfolios: any[] = []; await new CallAPI() .GetData(req, `/development/portfolio/kk1/${profiles?.keycloak}`) .then((x) => { - portfolios = x; + portfolios = Array.isArray(x) ? x : []; }) .catch(() => {}); - if (!portfolios) { - portfolios = [{ name: "-", year: "-" }]; + if (portfolios.length == 0) { + portfolios = [{ name: "", year: "", position: "" }]; } else { portfolios = portfolios.map((x: any) => ({ name: x.name ? Extension.ToThaiNumber(x.name) : "-", year: x.year ? Extension.ToThaiNumber(x.year.toString()) : "-", + position: `${x.position ?? "-"}`, })); } - + const org = + (_child4 == null ? "" : _child4 + " ") + + (_child3 == null ? "" : _child3 + " ") + + (_child2 == null ? "" : _child2 + " ") + + (_child1 == null ? "" : _child1 + " ") + + (_root == null ? "" : _root).trim(); + const _position = + profiles?.position != null + ? profiles?.posLevel != null + ? `${profiles.position}${profiles.posLevel.posLevelName}` + : profiles.position + : ""; + const ocAssistance = await this.profileAssistanceRepository.findOne({ + select: ["agency", "profileId", "commandName", "status", "isDeleted"], + where: { + profileId: id, + commandName: "ให้ช่วยราชการ", + status: "PENDING", + isDeleted: false, + }, + order: { createdAt: "ASC" }, + }); const data = { + currentDate: Extension.ToThaiNumber(Extension.ToThaiFullDate2(currentDate)), fullName: `${profiles?.prefix}${profiles?.firstName} ${profiles?.lastName}`, prefix: profiles?.prefix != null ? profiles.prefix : "", firstName: profiles?.firstName != null ? profiles.firstName : "", lastName: profiles?.lastName != null ? profiles.lastName : "", - position: profiles?.position != null ? profiles.position : "", + position: _position, + posAssistance: ocAssistance ? "-" : _position, // ปัจจุบันช่วยราชการยังไม่มีเก็บฟิลด์ตำแหน่ง amount: profiles?.amount != null ? Extension.ToThaiNumber(profiles.amount.toLocaleString()) : "", positionSalaryAmount: @@ -1769,12 +2106,14 @@ export class ProfileController extends Controller { ? Extension.ToThaiNumber(profiles.amountSpecial.toLocaleString()) : "", salarySum: sum, - ocFullPath: - (_child4 == null ? "" : _child4 + "\n") + - (_child3 == null ? "" : _child3 + "\n") + - (_child2 == null ? "" : _child2 + "\n") + - (_child1 == null ? "" : _child1 + "\n") + - (_root == null ? "" : _root), + ocFullPath: org, + ocAssistance: ocAssistance?.agency ?? org, + root: _root == null ? "" : _root, + agency: + (_child4 == null ? "" : _child4 + " ") + + (_child3 == null ? "" : _child3 + " ") + + (_child2 == null ? "" : _child2 + " ") + + (_child1 == null ? "" : _child1).trim(), birthDate: profiles?.birthDate ? Extension.ToThaiNumber(Extension.ToThaiFullDate2(profiles.birthDate)) : "", @@ -1785,8 +2124,7 @@ export class ProfileController extends Controller { appointDate: profiles?.dateAppoint ? Extension.ToThaiNumber(Extension.ToThaiFullDate2(profiles.dateAppoint)) : "", - positionDate: - positionList.length > 0 ? positionList[positionList.length - 1].commandDateAffect : "", + positionDate: _commandDateAffect ?? "", citizenId: profiles.citizenId != null ? Extension.ToThaiNumber(profiles.citizenId.toString()) : "", fatherFullName: @@ -1852,7 +2190,7 @@ export class ProfileController extends Controller { ? Extension.ToThaiNumber(profiles.registrationZipCode) : "", fullRegistrationAddress: fullRegistrationAddress, - updateAt: profiles.lastUpdatedAt + updateAt: profiles.lastUpdatedAt ? Extension.ToThaiNumber(Extension.ToThaiFullDate2(profiles.lastUpdatedAt)) : "", telephone: profiles.phone != null ? Extension.ToThaiNumber(profiles.phone) : "", @@ -1904,6 +2242,7 @@ export class ProfileController extends Controller { profileAbility, otherIncome, portfolios, + retires, }; return new HttpSuccess({ @@ -2261,7 +2600,8 @@ export class ProfileController extends Controller { _data.child1 != undefined && _data.child1 != null ? _data.child1[0] != null ? `current_holders.orgChild1Id IN (:...child1)` - : `current_holders.orgChild1Id is ${_data.privilege == "PARENT" ? "not null" : "null"}` + : // : `current_holders.orgChild1Id is ${_data.privilege == "PARENT" ? "not null" : "null"}` + `current_holders.orgChild1Id is null` : "1=1", { child1: _data.child1, @@ -2962,12 +3302,35 @@ export class ProfileController extends Controller { // console.log(condition); // console.log("------------------"); // console.log(conditionNow); + + // Task #2342 list ปลัด และรองปลัดใน popup ผู้บังคับบัญชา และผู้มีอำนาจเพิ่ม + let conditionisDeputy: any = { + isDeputy: true, + isDirector: true, + orgChild1Id: IsNull(), + orgChild2Id: IsNull(), + orgChild3Id: IsNull(), + orgChild4Id: IsNull(), + id: Not(posMaster.current_holderId), + }; + const orgRoot = await this.orgRootRepo.findOne({ + select: { id: true, isDeputy: true }, + where: { + id: Not(posMaster.orgRootId ?? ""), + isDeputy: true, + orgRevision: { orgRevisionIsCurrent: true, orgRevisionIsDraft: false }, + }, + }); + if (body.isAct == true) { const [lists, total] = await AppDataSource.getRepository(viewDirectorActing) .createQueryBuilder("viewDirectorActing") .andWhere( new Brackets((qb) => { qb.orWhere(condition).orWhere(conditionNow); + if (orgRoot && orgRoot.isDeputy) { + qb.orWhere(conditionisDeputy); + } }), ) .andWhere("viewDirectorActing.isProbation = :isProbation", { isProbation: false }) @@ -3033,6 +3396,9 @@ export class ProfileController extends Controller { .andWhere( new Brackets((qb) => { qb.orWhere(condition).orWhere(conditionNow); + if (orgRoot && orgRoot.isDeputy) { + qb.orWhere(conditionisDeputy); + } }), ) .andWhere("viewDirector.isProbation = :isProbation", { isProbation: false }) @@ -5055,8 +5421,13 @@ export class ProfileController extends Controller { await this.profileRepo.save(record); // setLogDataDiff(request, { before, after: record }); - if (record != null && record.keycloak != null) { - const result = await updateName(record.keycloak, record.firstName, record.lastName); + if (record != null && record.keycloak != null && record.isDelete === false) { + const result = await updateName( + record.keycloak, + record.firstName, + record.lastName, + record.prefix, + ); if (!result) { throw new Error(result.errorMessage); } @@ -5369,8 +5740,13 @@ export class ProfileController extends Controller { await this.profileRepo.save(record, { data: request }); setLogDataDiff(request, { before, after: record }); - if (record != null && record.keycloak != null) { - const result = await updateName(record.keycloak, record.firstName, record.lastName); + if (record != null && record.keycloak != null && record.isDelete === false) { + const result = await updateName( + record.keycloak, + record.firstName, + record.lastName, + record.prefix, + ); if (!result) { throw new Error(result.errorMessage); } @@ -5470,20 +5846,33 @@ export class ProfileController extends Controller { */ @Get("history/user") async getHistoryProfileByUser(@Request() request: RequestWithUser) { - const historyProfile = await this.profileHistoryRepo.find({ - relations: { - posLevel: true, - posType: true, - }, + const profile = await this.profileRepo.findOne({ where: { keycloak: request.user.sub }, - order: { - createdAt: "ASC", - }, + }); + if (!profile) throw new HttpError(HttpStatus.NOT_FOUND, "ไม่พบข้อมูล"); + + const profileHistory = await this.profileHistoryRepo.find({ + where: { profileId: profile.id }, + order: { createdAt: "ASC" }, }); - if (!historyProfile) throw new HttpError(HttpStatus.NOT_FOUND, "ไม่พบข้อมูล"); + if (profileHistory.length == 0) { + await this.profileHistoryRepo.save( + Object.assign(new ProfileHistory(), { + ...profile, + birthDateOld: profile?.birthDate, + profileId: profile.id, + id: undefined, + }), + ); + const firstRecord = await this.profileHistoryRepo.find({ + where: { profileId: profile.id }, + order: { createdAt: "ASC" }, + }); + return new HttpSuccess(firstRecord); + } - return new HttpSuccess(historyProfile); + return new HttpSuccess(profileHistory); } /** @@ -6237,7 +6626,8 @@ export class ProfileController extends Controller { _data.child1 != undefined && _data.child1 != null ? _data.child1[0] != null ? `current_holders.orgChild1Id IN (:...child1)` - : `current_holders.orgChild1Id is ${_data.privilege == "PARENT" ? "not null" : "null"}` + : // : `current_holders.orgChild1Id is ${_data.privilege == "PARENT" ? "not null" : "null"}` + `current_holders.orgChild1Id is null` : "1=1", { child1: _data.child1 }, ) @@ -6431,20 +6821,33 @@ export class ProfileController extends Controller { @Get("history/{id}") async getProfileHistory(@Path() id: string, @Request() req: RequestWithUser) { //await new permission().PermissionOrgUserGet(req, "SYS_REGISTRY_OFFICER", id); //ไม่แน่ใจOFFปิดไว้ก่อน - const profile = await this.profileHistoryRepo.find({ - relations: { - posLevel: true, - posType: true, - }, - where: { profileId: id }, - order: { - createdAt: "ASC", - }, + const profile = await this.profileRepo.findOne({ + where: { id: id }, }); - if (!profile) throw new HttpError(HttpStatus.NOT_FOUND, "ไม่พบข้อมูล"); - return new HttpSuccess(profile); + const profileHistory = await this.profileHistoryRepo.find({ + where: { profileId: id }, + order: { createdAt: "ASC" }, + }); + + if (profileHistory.length == 0) { + await this.profileHistoryRepo.save( + Object.assign(new ProfileHistory(), { + ...profile, + birthDateOld: profile?.birthDate, + profileId: id, + id: undefined, + }), + ); + const firstRecord = await this.profileHistoryRepo.find({ + where: { profileId: id }, + order: { createdAt: "ASC" }, + }); + return new HttpSuccess(firstRecord); + } + + return new HttpSuccess(profileHistory); } /** @@ -6624,7 +7027,8 @@ export class ProfileController extends Controller { _data.child1 != undefined && _data.child1 != null ? _data.child1[0] != null ? `current_holders.orgChild1Id IN (:...child1)` - : `current_holders.orgChild1Id is ${_data.privilege == "PARENT" ? "not null" : "null"}` + : // : `current_holders.orgChild1Id is ${_data.privilege == "PARENT" ? "not null" : "null"}` + `current_holders.orgChild1Id is null` : "1=1", { child1: _data.child1 }, ) @@ -7709,7 +8113,7 @@ export class ProfileController extends Controller { child2Id: null, child3Id: null, child4Id: null, - rootDnaId: null + rootDnaId: null, }); } return new HttpSuccess({ @@ -7719,7 +8123,7 @@ export class ProfileController extends Controller { child2Id: posMasters?.orgChild2Id || null, child3Id: posMasters?.orgChild3Id || null, child4Id: posMasters?.orgChild4Id || null, - rootDnaId: posMasters?.orgRoot?.ancestorDNA || null + rootDnaId: posMasters?.orgRoot?.ancestorDNA || null, }); } @@ -8978,7 +9382,8 @@ export class ProfileController extends Controller { _data.child1 != undefined && _data.child1 != null ? _data.child1[0] != null ? `current_holders.orgChild1Id IN (:...child1)` - : `current_holders.orgChild1Id is ${_data.privilege == "PARENT" ? "not null" : "null"}` + : // : `current_holders.orgChild1Id is ${_data.privilege == "PARENT" ? "not null" : "null"}` + `current_holders.orgChild1Id is null` : "1=1", { child1: _data.child1 }, ) @@ -9093,12 +9498,13 @@ export class ProfileController extends Controller { )?.posMasterNo; const latestProfileEducation = await this.profileEducationRepo.findOne({ - where: { profileId: item.id }, + where: { profileId: item.id, isDeleted: false }, order: { level: "ASC" }, }); return { id: item.id, + keycloak: item.keycloak, prefix: item.prefix, rank: item.rank, firstName: item.firstName, @@ -9501,7 +9907,8 @@ export class ProfileController extends Controller { _data.child1 != undefined && _data.child1 != null ? _data.child1[0] != null ? `current_holders.orgChild1Id IN (:...child1)` - : `current_holders.orgChild1Id is ${_data.privilege == "PARENT" ? "not null" : "null"}` + : // : `current_holders.orgChild1Id is ${_data.privilege == "PARENT" ? "not null" : "null"}` + `current_holders.orgChild1Id is null` : "1=1", { child1: _data.child1, @@ -10756,9 +11163,9 @@ export class ProfileController extends Controller { } /** - * API อัพเดทเกษียณ + * API อัพเดทถึงแก่กรรม * - * @summary อัพเดทเกษียณ (ADMIN) + * @summary อัพเดทถึงแก่กรรม (ADMIN) * * @param {string} id Id ทะเบียนประวัติ */ @@ -10883,12 +11290,14 @@ export class ProfileController extends Controller { // profile.position = _null; // profile.posLevelId = _null; // profile.posTypeId = _null; - if (profile.keycloak != null) { + if (profile.keycloak != null && profile.keycloak != "" && profile.isDelete === false) { const delUserKeycloak = await deleteUser(profile.keycloak); if (delUserKeycloak) { - profile.keycloak = _null; + // Task #228 + // profile.keycloak = _null; profile.roleKeycloaks = []; profile.isActive = false; + profile.isDelete = true; } } await this.profileRepo.save(profile, { data: request }); @@ -10910,29 +11319,19 @@ export class ProfileController extends Controller { ].filter(Boolean); organizeName = names.join(" "); } - await PostRetireToExprofile( - // profile.citizenId ?? "", - // profile.prefix ?? "", - // profile.firstName ?? "", - // profile.lastName ?? "", - // requestBody.dateLeave?.getFullYear().toString() ?? "", - // profile.position, - // profile.posType?.posTypeName ?? "", - // profile.posLevel?.posLevelName ?? "", - // requestBody.dateLeave ?? new Date(), - // organizeName, - // "ถึงแก่กรรม", - "310190004095X", - "จ.ส.อ.", - "dev", - "hrms", - "2026", - "เจ้าหน้าที่จัดเก็บรายได้", - "อื่นๆ", - "C 3", - new Date(2026, 0, 1), - "สำนักงานเขตบางกอกใหญ่", - "เกษียณ" + PostRetireToExprofile( + request, + profile.citizenId ?? "", + profile.prefix ?? "", + profile.firstName ?? "", + profile.lastName ?? "", + requestBody.dateLeave?.getFullYear().toString() ?? "", + profile.position, + profile.posType?.posTypeName ?? "", + profile.posLevel?.posLevelName ?? "", + requestBody.dateLeave ?? new Date(), + organizeName, + "ถึงแก่กรรม", ); return new HttpSuccess(); } @@ -11004,7 +11403,10 @@ export class ProfileController extends Controller { .leftJoinAndSelect("current_holders.orgChild2", "orgChild2") .leftJoinAndSelect("current_holders.orgChild3", "orgChild3") .leftJoinAndSelect("current_holders.orgChild4", "orgChild4") - .where("profile.keycloak IS NULL") + .where(body.system ? "profile.isActive = :isActive" : "profile.isDelete = :isDelete", { + isActive: false, + isDelete: true, + }) .andWhere( new Brackets((qb) => { qb.orWhere(body.keyword ? queryLike : "1=1", { keyword: `%${body.keyword}%` }); @@ -11106,7 +11508,7 @@ export class ProfileController extends Controller { } const latestProfileEducation = await this.profileEducationRepo.findOne({ - where: { profileId: item.id }, + where: { profileId: item.id, isDeleted: false }, order: { level: "ASC" }, }); diff --git a/src/controllers/ProfileDevelopmentController.ts b/src/controllers/ProfileDevelopmentController.ts index 3a615a5a..3557e760 100644 --- a/src/controllers/ProfileDevelopmentController.ts +++ b/src/controllers/ProfileDevelopmentController.ts @@ -28,6 +28,7 @@ import permission from "../interfaces/permission"; import { DevelopmentProject } from "../entities/DevelopmentProject"; import { In, Brackets } from "typeorm"; import { DevelopmentRequest } from "../entities/DevelopmentRequest"; +import { setLogDataDiff } from "../interfaces/utils"; @Route("api/v1/org/profile/development") @Tags("ProfileDevelopment") @Security("bearerAuth") @@ -45,7 +46,7 @@ export class ProfileDevelopmentController extends Controller { throw new HttpError(HttpStatus.BAD_REQUEST, "ไม่พบ profile ดังกล่าว"); } const lists = await this.developmentRepository.find({ - where: { profileId: profile.id }, + where: { profileId: profile.id, isDeleted: false }, order: { createdAt: "ASC" }, }); return new HttpSuccess(lists); @@ -66,7 +67,7 @@ export class ProfileDevelopmentController extends Controller { await new permission().PermissionOrgUserGet(req, "SYS_REGISTRY_OFFICER", profileId); let query = await AppDataSource.getRepository(ProfileDevelopment) .createQueryBuilder("profileDevelopment") - .where({ profileId: profileId }) + .where({ profileId: profileId, isDeleted: false }) .andWhere( new Brackets((qb) => { qb.where( @@ -329,6 +330,44 @@ export class ProfileDevelopmentController extends Controller { return new HttpSuccess(); } + /** + * API ลบข้อมูลการพัฒนารายบุคคล IDP + * @summary API ลบข้อมูลการพัฒนารายบุคคล IDP + * @param developmentId คีย์การพัฒนารายบุคคล IDP + */ + @Patch("update-delete/{developmentId}") + public async updateIsDeletedTraining( + @Request() req: RequestWithUser, + @Path() developmentId: string, + ) { + const record = await this.developmentRepository.findOneBy({ id: developmentId }); + if (!record) throw new HttpError(HttpStatus.NOT_FOUND, "ไม่พบข้อมูล"); + if (record.isDeleted === true) { + return new HttpSuccess(); + } + await new permission().PermissionOrgUserDelete(req, "SYS_REGISTRY_OFFICER", record.profileId); + const before = structuredClone(record); + const history = new ProfileDevelopmentHistory(); + const now = new Date(); + record.isDeleted = true; + record.lastUpdateUserId = req.user.sub; + record.lastUpdateFullName = req.user.name; + record.lastUpdatedAt = now; + + Object.assign(history, { ...record, id: undefined }); + history.createdUserId = req.user.sub; + history.createdFullName = req.user.name; + history.createdAt = now; + + await Promise.all([ + this.developmentRepository.save(record, { data: req }), + setLogDataDiff(req, { before, after: record }), + this.developmentHistoryRepository.save(history, { data: req }), + ]); + + return new HttpSuccess(); + } + @Delete("{developmentId}") public async deleteDevelopment(@Path() developmentId: string, @Request() req: RequestWithUser) { const _record = await this.developmentRepository.findOneBy({ id: developmentId }); diff --git a/src/controllers/ProfileDevelopmentEmployeeController.ts b/src/controllers/ProfileDevelopmentEmployeeController.ts index 76260a1b..d56ae484 100644 --- a/src/controllers/ProfileDevelopmentEmployeeController.ts +++ b/src/controllers/ProfileDevelopmentEmployeeController.ts @@ -27,6 +27,7 @@ import { import permission from "../interfaces/permission"; import { DevelopmentProject } from "../entities/DevelopmentProject"; import { In, Brackets } from "typeorm"; +import { setLogDataDiff } from "../interfaces/utils"; @Route("api/v1/org/profile-employee/development") @Tags("ProfileDevelopment") @Security("bearerAuth") @@ -43,7 +44,7 @@ export class ProfileDevelopmentEmployeeController extends Controller { throw new HttpError(HttpStatus.BAD_REQUEST, "ไม่พบ profile ดังกล่าว"); } const lists = await this.developmentRepository.find({ - where: { profileEmployeeId: profile.id }, + where: { profileEmployeeId: profile.id, isDeleted: false }, order: { createdAt: "ASC" }, }); return new HttpSuccess(lists); @@ -65,7 +66,7 @@ export class ProfileDevelopmentEmployeeController extends Controller { let query = await AppDataSource.getRepository(ProfileDevelopment) .createQueryBuilder("profileDevelopment") - .where({ profileEmployeeId: profileId }) + .where({ profileEmployeeId: profileId, isDeleted: false }) .andWhere( new Brackets((qb) => { qb.where( @@ -273,6 +274,44 @@ export class ProfileDevelopmentEmployeeController extends Controller { return new HttpSuccess(); } + /** + * API ลบข้อมูลการพัฒนารายบุคคล IDP + * @summary API ลบข้อมูลการพัฒนารายบุคคล IDP + * @param developmentId คีย์การพัฒนารายบุคคล IDP + */ + @Patch("update-delete/{developmentId}") + public async updateIsDeletedTraining( + @Request() req: RequestWithUser, + @Path() developmentId: string, + ) { + const record = await this.developmentRepository.findOneBy({ id: developmentId }); + if (!record) throw new HttpError(HttpStatus.NOT_FOUND, "ไม่พบข้อมูล"); + if (record.isDeleted === true) { + return new HttpSuccess(); + } + await new permission().PermissionOrgUserDelete(req, "SYS_REGISTRY_EMP", record.profileEmployeeId); + const before = structuredClone(record); + const history = new ProfileDevelopmentHistory(); + const now = new Date(); + record.isDeleted = true; + record.lastUpdateUserId = req.user.sub; + record.lastUpdateFullName = req.user.name; + record.lastUpdatedAt = now; + + Object.assign(history, { ...record, id: undefined }); + history.createdUserId = req.user.sub; + history.createdFullName = req.user.name; + history.createdAt = now; + + await Promise.all([ + this.developmentRepository.save(record, { data: req }), + setLogDataDiff(req, { before, after: record }), + this.developmentHistoryRepository.save(history, { data: req }), + ]); + + return new HttpSuccess(); + } + @Delete("{developmentId}") public async deleteDevelopment(@Path() developmentId: string, @Request() req: RequestWithUser) { const _record = await this.developmentRepository.findOneBy({ id: developmentId }); diff --git a/src/controllers/ProfileDevelopmentEmployeeTempController.ts b/src/controllers/ProfileDevelopmentEmployeeTempController.ts index 84cfa074..9e8c2d26 100644 --- a/src/controllers/ProfileDevelopmentEmployeeTempController.ts +++ b/src/controllers/ProfileDevelopmentEmployeeTempController.ts @@ -41,7 +41,7 @@ export class ProfileDevelopmentEmployeeTempController extends Controller { throw new HttpError(HttpStatus.BAD_REQUEST, "ไม่พบ profile ดังกล่าว"); } const lists = await this.developmentRepository.find({ - where: { profileEmployeeId: profile.id }, + where: { profileEmployeeId: profile.id, isDeleted: false }, order: { createdAt: "ASC" }, }); return new HttpSuccess(lists); @@ -52,7 +52,7 @@ export class ProfileDevelopmentEmployeeTempController extends Controller { let _workflow = await new permission().Workflow(req, profileId, "SYS_REGISTRY_TEMP"); if (_workflow == false) await new permission().PermissionGet(req, "SYS_REGISTRY_TEMP"); const lists = await this.developmentRepository.find({ - where: { profileEmployeeId: profileId }, + where: { profileEmployeeId: profileId, isDeleted: false }, order: { createdAt: "ASC" }, }); return new HttpSuccess(lists); diff --git a/src/controllers/ProfileDisciplineController.ts b/src/controllers/ProfileDisciplineController.ts index 9edfd89d..17cf3689 100644 --- a/src/controllers/ProfileDisciplineController.ts +++ b/src/controllers/ProfileDisciplineController.ts @@ -40,7 +40,7 @@ export class ProfileDisciplineController extends Controller { throw new HttpError(HttpStatus.BAD_REQUEST, "ไม่พบ profile ดังกล่าว"); } const lists = await this.disciplineRepository.find({ - where: { profileId: profile.id }, + where: { profileId: profile.id, isDeleted: false }, order: { createdAt: "ASC" }, }); return new HttpSuccess(lists); @@ -52,7 +52,7 @@ export class ProfileDisciplineController extends Controller { if (_workflow == false) await new permission().PermissionOrgUserGet(req, "SYS_REGISTRY_OFFICER", profileId); const lists = await this.disciplineRepository.find({ - where: { profileId: profileId }, + where: { profileId: profileId, isDeleted: false }, order: { createdAt: "ASC" }, }); return new HttpSuccess(lists); @@ -63,7 +63,7 @@ export class ProfileDisciplineController extends Controller { let _workflow = await new permission().Workflow(req, profileId, "SYS_SALARY_OFFICER"); if (_workflow == false) await new permission().PermissionGet(req, "SYS_SALARY_OFFICER"); const lists = await this.disciplineRepository.find({ - where: { profileId: profileId }, + where: { profileId: profileId, isDeleted: false }, order: { createdAt: "ASC" }, }); return new HttpSuccess(lists); @@ -175,6 +175,45 @@ export class ProfileDisciplineController extends Controller { return new HttpSuccess(); } + /** + * API ลบข้อมูลวินัย + * @summary API ลบข้อมูลวินัย + * @param disciplineId คีย์วินัย + */ + @Patch("update-delete/{disciplineId}") + public async updateIsDeleted( + @Request() req: RequestWithUser, + @Path() disciplineId: string, + ) { + const record = await this.disciplineRepository.findOneBy({ id: disciplineId }); + if (!record) throw new HttpError(HttpStatus.NOT_FOUND, "ไม่พบข้อมูล"); + if (record.isDeleted === true) { + return new HttpSuccess(); + } + await new permission().PermissionOrgUserDelete(req, "SYS_REGISTRY_OFFICER", record.profileId); + const before = structuredClone(record); + const history = new ProfileDisciplineHistory(); + const now = new Date(); + record.isDeleted = true; + record.lastUpdateUserId = req.user.sub; + record.lastUpdateFullName = req.user.name; + record.lastUpdatedAt = now; + + Object.assign(history, { ...record, id: undefined }); + history.profileDisciplineId = disciplineId; + history.createdUserId = req.user.sub; + history.createdFullName = req.user.name; + history.createdAt = now; + + await Promise.all([ + this.disciplineRepository.save(record, { data: req }), + setLogDataDiff(req, { before, after: record }), + this.disciplineHistoryRepository.save(history, { data: req }), + ]); + + return new HttpSuccess(); + } + @Delete("{disciplineId}") public async deleteDiscipline(@Path() disciplineId: string, @Request() req: RequestWithUser) { const _record = await this.disciplineRepository.findOneBy({ id: disciplineId }); diff --git a/src/controllers/ProfileDisciplineEmployeeController.ts b/src/controllers/ProfileDisciplineEmployeeController.ts index efb3842b..44460d44 100644 --- a/src/controllers/ProfileDisciplineEmployeeController.ts +++ b/src/controllers/ProfileDisciplineEmployeeController.ts @@ -40,7 +40,7 @@ export class ProfileDisciplineEmployeeController extends Controller { throw new HttpError(HttpStatus.BAD_REQUEST, "ไม่พบ profile ดังกล่าว"); } const lists = await this.disciplineRepository.find({ - where: { profileEmployeeId: profile.id }, + where: { profileEmployeeId: profile.id, isDeleted: false }, order: { createdAt: "ASC" }, }); return new HttpSuccess(lists); @@ -52,7 +52,7 @@ export class ProfileDisciplineEmployeeController extends Controller { if (_workflow == false) await new permission().PermissionOrgUserGet(req, "SYS_REGISTRY_EMP", profileId); const lists = await this.disciplineRepository.find({ - where: { profileEmployeeId: profileId }, + where: { profileEmployeeId: profileId, isDeleted: false }, order: { createdAt: "ASC" }, }); return new HttpSuccess(lists); @@ -63,7 +63,7 @@ export class ProfileDisciplineEmployeeController extends Controller { let _workflow = await new permission().Workflow(req, profileId, "SYS_WAGE"); if (_workflow == false) await new permission().PermissionGet(req, "SYS_WAGE"); const lists = await this.disciplineRepository.find({ - where: { profileEmployeeId: profileId }, + where: { profileEmployeeId: profileId, isDeleted: false }, order: { createdAt: "ASC" }, }); return new HttpSuccess(lists); @@ -179,6 +179,45 @@ export class ProfileDisciplineEmployeeController extends Controller { return new HttpSuccess(); } + /** + * API ลบข้อมูลวินัย + * @summary API ลบข้อมูลวินัย + * @param disciplineId คีย์วินัย + */ + @Patch("update-delete/{disciplineId}") + public async updateIsDeleted( + @Request() req: RequestWithUser, + @Path() disciplineId: string, + ) { + const record = await this.disciplineRepository.findOneBy({ id: disciplineId }); + if (!record) throw new HttpError(HttpStatus.NOT_FOUND, "ไม่พบข้อมูล"); + if (record.isDeleted === true) { + return new HttpSuccess(); + } + await new permission().PermissionOrgUserDelete(req, "SYS_REGISTRY_EMP", record.profileEmployeeId); + const before = structuredClone(record); + const history = new ProfileDisciplineHistory(); + const now = new Date(); + record.isDeleted = true; + record.lastUpdateUserId = req.user.sub; + record.lastUpdateFullName = req.user.name; + record.lastUpdatedAt = now; + + Object.assign(history, { ...record, id: undefined }); + history.profileDisciplineId = disciplineId; + history.createdUserId = req.user.sub; + history.createdFullName = req.user.name; + history.createdAt = now; + + await Promise.all([ + this.disciplineRepository.save(record, { data: req }), + setLogDataDiff(req, { before, after: record }), + this.disciplineHistoryRepository.save(history, { data: req }), + ]); + + return new HttpSuccess(); + } + @Delete("{disciplineId}") public async deleteDiscipline(@Path() disciplineId: string, @Request() req: RequestWithUser) { const _record = await this.disciplineRepository.findOneBy({ id: disciplineId }); diff --git a/src/controllers/ProfileDisciplineEmployeeTempController.ts b/src/controllers/ProfileDisciplineEmployeeTempController.ts index b408dfe1..7699b8ac 100644 --- a/src/controllers/ProfileDisciplineEmployeeTempController.ts +++ b/src/controllers/ProfileDisciplineEmployeeTempController.ts @@ -40,7 +40,7 @@ export class ProfileDisciplineEmployeeTempController extends Controller { throw new HttpError(HttpStatus.BAD_REQUEST, "ไม่พบ profile ดังกล่าว"); } const lists = await this.disciplineRepository.find({ - where: { profileEmployeeId: profile.id }, + where: { profileEmployeeId: profile.id, isDeleted: false }, order: { createdAt: "ASC" }, }); return new HttpSuccess(lists); @@ -51,7 +51,7 @@ export class ProfileDisciplineEmployeeTempController extends Controller { let _workflow = await new permission().Workflow(req, profileId, "SYS_REGISTRY_TEMP"); if (_workflow == false) await new permission().PermissionGet(req, "SYS_REGISTRY_TEMP"); const lists = await this.disciplineRepository.find({ - where: { profileEmployeeId: profileId }, + where: { profileEmployeeId: profileId, isDeleted: false }, order: { createdAt: "ASC" }, }); return new HttpSuccess(lists); @@ -62,7 +62,7 @@ export class ProfileDisciplineEmployeeTempController extends Controller { let _workflow = await new permission().Workflow(req, profileId, "SYS_WAGE"); if (_workflow == false) await new permission().PermissionGet(req, "SYS_WAGE"); const lists = await this.disciplineRepository.find({ - where: { profileEmployeeId: profileId }, + where: { profileEmployeeId: profileId, isDeleted: false }, order: { createdAt: "ASC" }, }); return new HttpSuccess(lists); @@ -169,6 +169,45 @@ export class ProfileDisciplineEmployeeTempController extends Controller { return new HttpSuccess(); } + /** + * API ลบข้อมูลวินัย + * @summary API ลบข้อมูลวินัย + * @param disciplineId คีย์วินัย + */ + @Patch("update-delete/{disciplineId}") + public async updateIsDeleted( + @Request() req: RequestWithUser, + @Path() disciplineId: string, + ) { + const record = await this.disciplineRepository.findOneBy({ id: disciplineId }); + if (!record) throw new HttpError(HttpStatus.NOT_FOUND, "ไม่พบข้อมูล"); + if (record.isDeleted === true) { + return new HttpSuccess(); + } + await new permission().PermissionDelete(req, "SYS_REGISTRY_TEMP"); + const before = structuredClone(record); + const history = new ProfileDisciplineHistory(); + const now = new Date(); + record.isDeleted = true; + record.lastUpdateUserId = req.user.sub; + record.lastUpdateFullName = req.user.name; + record.lastUpdatedAt = now; + + Object.assign(history, { ...record, id: undefined }); + history.profileDisciplineId = disciplineId; + history.createdUserId = req.user.sub; + history.createdFullName = req.user.name; + history.createdAt = now; + + await Promise.all([ + this.disciplineRepository.save(record, { data: req }), + setLogDataDiff(req, { before, after: record }), + this.disciplineHistoryRepository.save(history, { data: req }), + ]); + + return new HttpSuccess(); + } + @Delete("{disciplineId}") public async deleteDiscipline(@Path() disciplineId: string, @Request() req: RequestWithUser) { await new permission().PermissionDelete(req, "SYS_REGISTRY_TEMP"); diff --git a/src/controllers/ProfileDutyController.ts b/src/controllers/ProfileDutyController.ts index 36aee9ea..15bcf53e 100644 --- a/src/controllers/ProfileDutyController.ts +++ b/src/controllers/ProfileDutyController.ts @@ -36,7 +36,7 @@ export class ProfileDutyController extends Controller { throw new HttpError(HttpStatus.BAD_REQUEST, "ไม่พบ profile ดังกล่าว"); } const lists = await this.dutyRepository.find({ - where: { profileId: profile.id }, + where: { profileId: profile.id, isDeleted: false }, order: { createdAt: "ASC" }, }); return new HttpSuccess(lists); @@ -48,7 +48,7 @@ export class ProfileDutyController extends Controller { if (_workflow == false) await new permission().PermissionOrgUserGet(req, "SYS_REGISTRY_OFFICER", profileId); const lists = await this.dutyRepository.find({ - where: { profileId: profileId }, + where: { profileId: profileId, isDeleted: false }, order: { createdAt: "ASC" }, }); return new HttpSuccess(lists); @@ -150,6 +150,45 @@ export class ProfileDutyController extends Controller { return new HttpSuccess(); } + /** + * API ลบข้อมูลปฏิบัติราชการพิเศษ + * @summary API ลบข้อมูลปฏิบัติราชการพิเศษ + * @param dutyId คีย์ปฏิบัติราชการพิเศษ + */ + @Patch("update-delete/{dutyId}") + public async updateIsDeleted( + @Request() req: RequestWithUser, + @Path() dutyId: string, + ) { + const record = await this.dutyRepository.findOneBy({ id: dutyId }); + if (!record) throw new HttpError(HttpStatus.NOT_FOUND, "ไม่พบข้อมูล"); + if (record.isDeleted === true) { + return new HttpSuccess(); + } + await new permission().PermissionOrgUserDelete(req, "SYS_REGISTRY_OFFICER", record.profileId); + const before = structuredClone(record); + const history = new ProfileDutyHistory(); + const now = new Date(); + record.isDeleted = true; + record.lastUpdateUserId = req.user.sub; + record.lastUpdateFullName = req.user.name; + record.lastUpdatedAt = now; + + Object.assign(history, { ...record, id: undefined }); + history.profileDutyId = dutyId; + history.createdUserId = req.user.sub; + history.createdFullName = req.user.name; + history.createdAt = now; + + await Promise.all([ + this.dutyRepository.save(record, { data: req }), + setLogDataDiff(req, { before, after: record }), + this.dutyHistoryRepository.save(history, { data: req }), + ]); + + return new HttpSuccess(); + } + @Delete("{dutyId}") public async deleteDuty(@Path() dutyId: string, @Request() req: RequestWithUser) { const _record = await this.dutyRepository.findOneBy({ id: dutyId }); diff --git a/src/controllers/ProfileDutyEmployeeController.ts b/src/controllers/ProfileDutyEmployeeController.ts index 811ce550..278cd736 100644 --- a/src/controllers/ProfileDutyEmployeeController.ts +++ b/src/controllers/ProfileDutyEmployeeController.ts @@ -36,7 +36,7 @@ export class ProfileDutyEmployeeController extends Controller { throw new HttpError(HttpStatus.BAD_REQUEST, "ไม่พบ profile ดังกล่าว"); } const lists = await this.dutyRepository.find({ - where: { profileEmployeeId: profile.id }, + where: { profileEmployeeId: profile.id, isDeleted: false }, order: { createdAt: "ASC" }, }); return new HttpSuccess(lists); @@ -48,7 +48,7 @@ export class ProfileDutyEmployeeController extends Controller { if (_workflow == false) await new permission().PermissionOrgUserGet(req, "SYS_REGISTRY_EMP", profileId); const lists = await this.dutyRepository.find({ - where: { profileEmployeeId: profileId }, + where: { profileEmployeeId: profileId, isDeleted: false }, order: { createdAt: "ASC" }, }); return new HttpSuccess(lists); @@ -159,6 +159,45 @@ export class ProfileDutyEmployeeController extends Controller { return new HttpSuccess(); } + /** + * API ลบข้อมูลปฏิบัติราชการพิเศษ + * @summary API ลบข้อมูลปฏิบัติราชการพิเศษ + * @param dutyId คีย์ปฏิบัติราชการพิเศษ + */ + @Patch("update-delete/{dutyId}") + public async updateIsDeleted( + @Request() req: RequestWithUser, + @Path() dutyId: string, + ) { + const record = await this.dutyRepository.findOneBy({ id: dutyId }); + if (!record) throw new HttpError(HttpStatus.NOT_FOUND, "ไม่พบข้อมูล"); + if (record.isDeleted === true) { + return new HttpSuccess(); + } + await new permission().PermissionOrgUserDelete(req, "SYS_REGISTRY_EMP", record.profileEmployeeId); + const before = structuredClone(record); + const history = new ProfileDutyHistory(); + const now = new Date(); + record.isDeleted = true; + record.lastUpdateUserId = req.user.sub; + record.lastUpdateFullName = req.user.name; + record.lastUpdatedAt = now; + + Object.assign(history, { ...record, id: undefined }); + history.profileDutyId = dutyId; + history.createdUserId = req.user.sub; + history.createdFullName = req.user.name; + history.createdAt = now; + + await Promise.all([ + this.dutyRepository.save(record, { data: req }), + setLogDataDiff(req, { before, after: record }), + this.dutyHistoryRepository.save(history, { data: req }), + ]); + + return new HttpSuccess(); + } + @Delete("{dutyId}") public async deleteDuty(@Path() dutyId: string, @Request() req: RequestWithUser) { const _record = await this.dutyRepository.findOneBy({ id: dutyId }); diff --git a/src/controllers/ProfileDutyEmployeeTempController.ts b/src/controllers/ProfileDutyEmployeeTempController.ts index 31e83d27..fa3cc605 100644 --- a/src/controllers/ProfileDutyEmployeeTempController.ts +++ b/src/controllers/ProfileDutyEmployeeTempController.ts @@ -36,7 +36,7 @@ export class ProfileDutyEmployeeTempController extends Controller { throw new HttpError(HttpStatus.BAD_REQUEST, "ไม่พบ profile ดังกล่าว"); } const lists = await this.dutyRepository.find({ - where: { profileEmployeeId: profile.id }, + where: { profileEmployeeId: profile.id, isDeleted: false }, order: { createdAt: "ASC" }, }); return new HttpSuccess(lists); @@ -47,7 +47,7 @@ export class ProfileDutyEmployeeTempController extends Controller { let _workflow = await new permission().Workflow(req, profileId, "SYS_REGISTRY_TEMP"); if (_workflow == false) await new permission().PermissionGet(req, "SYS_REGISTRY_TEMP"); const lists = await this.dutyRepository.find({ - where: { profileEmployeeId: profileId }, + where: { profileEmployeeId: profileId, isDeleted: false }, order: { createdAt: "ASC" }, }); return new HttpSuccess(lists); @@ -148,6 +148,45 @@ export class ProfileDutyEmployeeTempController extends Controller { return new HttpSuccess(); } + /** + * API ลบข้อมูลปฏิบัติราชการพิเศษ + * @summary API ลบข้อมูลปฏิบัติราชการพิเศษ + * @param dutyId คีย์ปฏิบัติราชการพิเศษ + */ + @Patch("update-delete/{dutyId}") + public async updateIsDeleted( + @Request() req: RequestWithUser, + @Path() dutyId: string, + ) { + const record = await this.dutyRepository.findOneBy({ id: dutyId }); + if (!record) throw new HttpError(HttpStatus.NOT_FOUND, "ไม่พบข้อมูล"); + if (record.isDeleted === true) { + return new HttpSuccess(); + } + await new permission().PermissionDelete(req, "SYS_REGISTRY_TEMP"); + const before = structuredClone(record); + const history = new ProfileDutyHistory(); + const now = new Date(); + record.isDeleted = true; + record.lastUpdateUserId = req.user.sub; + record.lastUpdateFullName = req.user.name; + record.lastUpdatedAt = now; + + Object.assign(history, { ...record, id: undefined }); + history.profileDutyId = dutyId; + history.createdUserId = req.user.sub; + history.createdFullName = req.user.name; + history.createdAt = now; + + await Promise.all([ + this.dutyRepository.save(record, { data: req }), + setLogDataDiff(req, { before, after: record }), + this.dutyHistoryRepository.save(history, { data: req }), + ]); + + return new HttpSuccess(); + } + @Delete("{dutyId}") public async deleteDuty(@Path() dutyId: string, @Request() req: RequestWithUser) { await new permission().PermissionDelete(req, "SYS_REGISTRY_TEMP"); diff --git a/src/controllers/ProfileEditController.ts b/src/controllers/ProfileEditController.ts index 013a2e9d..35f26786 100644 --- a/src/controllers/ProfileEditController.ts +++ b/src/controllers/ProfileEditController.ts @@ -24,6 +24,7 @@ import CallAPI from "../interfaces/call-api"; import permission from "../interfaces/permission"; import { OrgRevision } from "../entities/OrgRevision"; import { OrgRoot } from "../entities/OrgRoot"; +import { PosMaster } from "../entities/PosMaster"; @Route("api/v1/org/profile/edit") @Tags("ProfileEdit") @Security("bearerAuth") @@ -32,6 +33,7 @@ export class ProfileEditController extends Controller { private profileEditRepo = AppDataSource.getRepository(ProfileEdit); private orgRevisionRepository = AppDataSource.getRepository(OrgRevision); private orgRootRepo = AppDataSource.getRepository(OrgRoot); + private posMasterRepo = AppDataSource.getRepository(PosMaster); @Get("user") public async detailProfileEditUser( @@ -151,7 +153,8 @@ export class ProfileEditController extends Controller { data.child1 != undefined && data.child1 != null ? data.child1[0] != null ? `current_holders.orgChild1Id IN (:...child1)` - : `current_holders.orgChild1Id is ${data.privilege == "PARENT" ? "not null" : "null"}` + // : `current_holders.orgChild1Id is ${data.privilege == "PARENT" ? "not null" : "null"}` + : `current_holders.orgChild1Id is null` : "1=1", { child1: data.child1, @@ -271,6 +274,22 @@ export class ProfileEditController extends Controller { if (!getProfileEdit) { throw new HttpError(HttpStatus.NOT_FOUND, "ไม่พบข้อมูล"); } + let orgRoot: OrgRoot | null = null; + if(getProfileEdit.profile) { + const empPosMaster = await this.posMasterRepo.findOne({ + where: { + current_holderId: getProfileEdit.profile.id, + orgRevision: { orgRevisionIsCurrent: true, orgRevisionIsDraft: false } + }, + relations: { orgRevision: true } + }); + if(empPosMaster) { + orgRoot = await this.orgRootRepo.findOne({ + select: { isDeputy: true }, + where: { id: empPosMaster.orgRootId ?? "" } + }); + } + } const _data = { id: getProfileEdit.id, topic: getProfileEdit.topic, @@ -288,6 +307,7 @@ export class ProfileEditController extends Controller { (getProfileEdit?.profile?.firstName ?? "") + " " + (getProfileEdit?.profile?.lastName ?? ""), + isDeputy: orgRoot?.isDeputy ?? false }; return new HttpSuccess(_data); } diff --git a/src/controllers/ProfileEditEmployeeController.ts b/src/controllers/ProfileEditEmployeeController.ts index 86c364d5..2cdd9fde 100644 --- a/src/controllers/ProfileEditEmployeeController.ts +++ b/src/controllers/ProfileEditEmployeeController.ts @@ -28,6 +28,7 @@ import permission from "../interfaces/permission"; import { OrgRevision } from "../entities/OrgRevision"; import { OrgRoot } from "../entities/OrgRoot"; import CallAPI from "../interfaces/call-api"; +import { EmployeePosMaster } from "../entities/EmployeePosMaster"; @Route("api/v1/org/profile-employee/edit") @Tags("ProfileEmployeeEdit") @Security("bearerAuth") @@ -36,6 +37,7 @@ export class ProfileEditEmployeeController extends Controller { private profileEditRepository = AppDataSource.getRepository(ProfileEdit); private orgRevisionRepository = AppDataSource.getRepository(OrgRevision); private orgRootRepo = AppDataSource.getRepository(OrgRoot); + private empPosMasterRepo = AppDataSource.getRepository(EmployeePosMaster); @Get("user") public async detailProfileEditUserEmp( @@ -150,7 +152,8 @@ export class ProfileEditEmployeeController extends Controller { data.child1 != undefined && data.child1 != null ? data.child1[0] != null ? `current_holders.orgChild1Id IN (:...child1)` - : `current_holders.orgChild1Id is ${data.privilege == "PARENT" ? "not null" : "null"}` + // : `current_holders.orgChild1Id is ${data.privilege == "PARENT" ? "not null" : "null"}` + : `current_holders.orgChild1Id is null` : "1=1", { child1: data.child1, @@ -270,6 +273,22 @@ export class ProfileEditEmployeeController extends Controller { if (!getProfileEdit) { throw new HttpError(HttpStatus.NOT_FOUND, "ไม่พบข้อมูล"); } + let orgRoot: OrgRoot | null = null; + if(getProfileEdit.profileEmployee) { + const empPosMaster = await this.empPosMasterRepo.findOne({ + where: { + current_holderId: getProfileEdit.profileEmployee.id, + orgRevision: { orgRevisionIsCurrent: true, orgRevisionIsDraft: false } + }, + relations: { orgRevision: true } + }); + if(empPosMaster) { + orgRoot = await this.orgRootRepo.findOne({ + select: { isDeputy: true }, + where: { id: empPosMaster.orgRootId ?? "" } + }); + } + } const _data = { id: getProfileEdit.id, topic: getProfileEdit.topic, @@ -287,6 +306,7 @@ export class ProfileEditEmployeeController extends Controller { (getProfileEdit?.profileEmployee?.firstName ?? "") + " " + (getProfileEdit?.profileEmployee?.lastName ?? ""), + isDeputy: orgRoot?.isDeputy ?? false }; return new HttpSuccess(_data); } diff --git a/src/controllers/ProfileEducationsController.ts b/src/controllers/ProfileEducationsController.ts index 43843960..8ac2d34c 100644 --- a/src/controllers/ProfileEducationsController.ts +++ b/src/controllers/ProfileEducationsController.ts @@ -42,7 +42,7 @@ export class ProfileEducationsController extends Controller { throw new HttpError(HttpStatus.BAD_REQUEST, "ไม่พบ profile ดังกล่าว"); } const getProfileEducation = await this.profileEducationRepo.find({ - where: { profileId: profile.id }, + where: { profileId: profile.id, isDeleted: false }, order: { level: "ASC" }, }); if (!getProfileEducation) { @@ -57,7 +57,7 @@ export class ProfileEducationsController extends Controller { if (_workflow == false) await new permission().PermissionOrgUserGet(req, "SYS_REGISTRY_OFFICER", profileId); const getProfileEducation = await this.profileEducationRepo.find({ - where: { profileId: profileId }, + where: { profileId: profileId, isDeleted: false }, order: { level: "ASC" }, }); if (!getProfileEducation) { @@ -208,6 +208,45 @@ export class ProfileEducationsController extends Controller { return new HttpSuccess(); } + /** + * API ลบข้อมูลประวัติการศึกษา + * @summary API ลบข้อมูลประวัติการศึกษา/ดูงาน + * @param educationId คีย์ประวัติการศึกษา + */ + @Patch("update-delete/{educationId}") + public async updateIsDeleted( + @Request() req: RequestWithUser, + @Path() educationId: string, + ) { + const record = await this.profileEducationRepo.findOneBy({ id: educationId }); + if (!record) throw new HttpError(HttpStatus.NOT_FOUND, "ไม่พบข้อมูล"); + if (record.isDeleted === true) { + return new HttpSuccess(); + } + await new permission().PermissionOrgUserDelete(req, "SYS_REGISTRY_OFFICER", record.profileId); + const before = structuredClone(record); + const history = new ProfileEducationHistory(); + const now = new Date(); + record.isDeleted = true; + record.lastUpdateUserId = req.user.sub; + record.lastUpdateFullName = req.user.name; + record.lastUpdatedAt = now; + + Object.assign(history, { ...record, id: undefined }); + history.profileEducationId = educationId; + history.createdUserId = req.user.sub; + history.createdFullName = req.user.name; + history.createdAt = now; + + await Promise.all([ + this.profileEducationRepo.save(record, { data: req }), + setLogDataDiff(req, { before, after: record }), + this.profileEducationHistoryRepo.save(history, { data: req }), + ]); + + return new HttpSuccess(); + } + @Delete("{educationId}") public async deleteProfileEducation( @Path() educationId: string, diff --git a/src/controllers/ProfileEducationsEmployeeController.ts b/src/controllers/ProfileEducationsEmployeeController.ts index cde8c6ec..0775d53a 100644 --- a/src/controllers/ProfileEducationsEmployeeController.ts +++ b/src/controllers/ProfileEducationsEmployeeController.ts @@ -42,7 +42,7 @@ export class ProfileEducationsEmployeeController extends Controller { throw new HttpError(HttpStatus.BAD_REQUEST, "ไม่พบ profile ดังกล่าว"); } const getProfileEducation = await this.profileEducationRepo.find({ - where: { profileEmployeeId: profile.id }, + where: { profileEmployeeId: profile.id, isDeleted: false }, order: { level: "ASC" }, }); if (!getProfileEducation) { @@ -60,7 +60,7 @@ export class ProfileEducationsEmployeeController extends Controller { if (_workflow == false) await new permission().PermissionOrgUserGet(req, "SYS_REGISTRY_EMP", profileEmployeeId); const getProfileEducation = await this.profileEducationRepo.find({ - where: { profileEmployeeId: profileEmployeeId }, + where: { profileEmployeeId: profileEmployeeId, isDeleted: false }, order: { level: "ASC" }, }); if (!getProfileEducation) { @@ -220,6 +220,45 @@ export class ProfileEducationsEmployeeController extends Controller { return new HttpSuccess(); } + /** + * API ลบข้อมูลประวัติการศึกษา + * @summary API ลบข้อมูลประวัติการศึกษา/ดูงาน + * @param educationId คีย์ประวัติการศึกษา + */ + @Patch("update-delete/{educationId}") + public async updateIsDeleted( + @Request() req: RequestWithUser, + @Path() educationId: string, + ) { + const record = await this.profileEducationRepo.findOneBy({ id: educationId }); + if (!record) throw new HttpError(HttpStatus.NOT_FOUND, "ไม่พบข้อมูล"); + if (record.isDeleted === true) { + return new HttpSuccess(); + } + await new permission().PermissionOrgUserDelete(req, "SYS_REGISTRY_EMP", record.profileEmployeeId); + const before = structuredClone(record); + const history = new ProfileEducationHistory(); + const now = new Date(); + record.isDeleted = true; + record.lastUpdateUserId = req.user.sub; + record.lastUpdateFullName = req.user.name; + record.lastUpdatedAt = now; + + Object.assign(history, { ...record, id: undefined }); + history.profileEducationId = educationId; + history.createdUserId = req.user.sub; + history.createdFullName = req.user.name; + history.createdAt = now; + + await Promise.all([ + this.profileEducationRepo.save(record, { data: req }), + setLogDataDiff(req, { before, after: record }), + this.profileEducationHistoryRepo.save(history, { data: req }), + ]); + + return new HttpSuccess(); + } + @Delete("{educationId}") public async deleteProfileEducation( @Path() educationId: string, diff --git a/src/controllers/ProfileEducationsEmployeeTempController.ts b/src/controllers/ProfileEducationsEmployeeTempController.ts index e90dfc1c..a0688591 100644 --- a/src/controllers/ProfileEducationsEmployeeTempController.ts +++ b/src/controllers/ProfileEducationsEmployeeTempController.ts @@ -42,7 +42,7 @@ export class ProfileEducationsEmployeeTempController extends Controller { throw new HttpError(HttpStatus.BAD_REQUEST, "ไม่พบ profile ดังกล่าว"); } const getProfileEducation = await this.profileEducationRepo.find({ - where: { profileEmployeeId: profile.id }, + where: { profileEmployeeId: profile.id, isDeleted: false }, order: { level: "ASC" }, }); if (!getProfileEducation) { @@ -59,7 +59,7 @@ export class ProfileEducationsEmployeeTempController extends Controller { let _workflow = await new permission().Workflow(req, profileEmployeeId, "SYS_REGISTRY_TEMP"); if (_workflow == false) await new permission().PermissionGet(req, "SYS_REGISTRY_TEMP"); const getProfileEducation = await this.profileEducationRepo.find({ - where: { profileEmployeeId: profileEmployeeId }, + where: { profileEmployeeId: profileEmployeeId, isDeleted: false }, order: { level: "ASC" }, }); if (!getProfileEducation) { @@ -210,6 +210,45 @@ export class ProfileEducationsEmployeeTempController extends Controller { return new HttpSuccess(); } + /** + * API ลบข้อมูลประวัติการศึกษา + * @summary API ลบข้อมูลประวัติการศึกษา/ดูงาน + * @param educationId คีย์ประวัติการศึกษา + */ + @Patch("update-delete/{educationId}") + public async updateIsDeleted( + @Request() req: RequestWithUser, + @Path() educationId: string, + ) { + const record = await this.profileEducationRepo.findOneBy({ id: educationId }); + if (!record) throw new HttpError(HttpStatus.NOT_FOUND, "ไม่พบข้อมูล"); + if (record.isDeleted === true) { + return new HttpSuccess(); + } + await new permission().PermissionDelete(req, "SYS_REGISTRY_TEMP"); + const before = structuredClone(record); + const history = new ProfileEducationHistory(); + const now = new Date(); + record.isDeleted = true; + record.lastUpdateUserId = req.user.sub; + record.lastUpdateFullName = req.user.name; + record.lastUpdatedAt = now; + + Object.assign(history, { ...record, id: undefined }); + history.profileEducationId = educationId; + history.createdUserId = req.user.sub; + history.createdFullName = req.user.name; + history.createdAt = now; + + await Promise.all([ + this.profileEducationRepo.save(record, { data: req }), + setLogDataDiff(req, { before, after: record }), + this.profileEducationHistoryRepo.save(history, { data: req }), + ]); + + return new HttpSuccess(); + } + @Delete("{educationId}") public async deleteProfileEducation( @Path() educationId: string, diff --git a/src/controllers/ProfileEmployeeAbsentLateController.ts b/src/controllers/ProfileEmployeeAbsentLateController.ts new file mode 100644 index 00000000..cdd67050 --- /dev/null +++ b/src/controllers/ProfileEmployeeAbsentLateController.ts @@ -0,0 +1,277 @@ +import { + Body, + Controller, + Get, + Patch, + Path, + Post, + Request, + Route, + Security, + Tags, +} from "tsoa"; +import { AppDataSource } from "../database/data-source"; +import { In } from "typeorm"; +import { + ProfileEmployeeAbsentLate, + CreateProfileEmployeeAbsentLate, + CreateProfileEmployeeAbsentLateBatch, + UpdateProfileEmployeeAbsentLate, +} from "../entities/ProfileEmployeeAbsentLate"; +import { ProfileEmployeeAbsentLateHistory } from "../entities/ProfileEmployeeAbsentLateHistory"; +import HttpSuccess from "../interfaces/http-success"; +import HttpStatus from "../interfaces/http-status"; +import HttpError from "../interfaces/http-error"; +import { RequestWithUser } from "../middlewares/user"; +import { ProfileEmployee } from "../entities/ProfileEmployee"; +import permission from "../interfaces/permission"; +import { setLogDataDiff } from "../interfaces/utils"; + +@Route("api/v1/org/profile-employee/absent-late") +@Tags("ProfileEmployeeAbsentLate") +@Security("bearerAuth") +export class ProfileEmployeeAbsentLateController extends Controller { + private profileRepo = AppDataSource.getRepository(ProfileEmployee); + private absentLateRepo = AppDataSource.getRepository(ProfileEmployeeAbsentLate); + private historyRepo = AppDataSource.getRepository(ProfileEmployeeAbsentLateHistory); + + /** + * API ดึงข้อมูลการมาสาย/ขาดราชการของ user + * @summary API ดึงข้อมูลการมาสาย/ขาดราชการของ user + */ + @Get("user") + public async getAbsentLateUser(@Request() request: { user: Record }) { + const profile = await this.profileRepo.findOneBy({ keycloak: request.user.sub }); + if (!profile) { + throw new HttpError(HttpStatus.BAD_REQUEST, "ไม่พบ profile ดังกล่าว"); + } + const record = await this.absentLateRepo.find({ + where: { profileEmployeeId: profile.id, isDeleted: false }, + order: { stampDate: "DESC" }, + }); + return new HttpSuccess(record); + } + + /** + * API ดึงข้อมูลการมาสาย/ขาดราชการตาม profileId + * @summary API ดึงข้อมูลการมาสาย/ขาดราชการตาม profileId + * @param profileId คีย์ profile + */ + @Get("{profileId}") + public async getAbsentLate(@Path() profileId: string, @Request() req: RequestWithUser) { + let _workflow = await new permission().Workflow(req, profileId, "SYS_REGISTRY_EMP"); + if (_workflow == false) + await new permission().PermissionOrgUserGet(req, "SYS_REGISTRY_EMP", profileId); + const record = await this.absentLateRepo.find({ + where: { profileEmployeeId: profileId, isDeleted: false }, + order: { stampDate: "DESC" }, + }); + return new HttpSuccess(record); + } + + /** + * API สร้างข้อมูลการมาสาย/ขาดราชการ + * @summary API สร้างข้อมูลการมาสาย/ขาดราชการ + */ + @Post() + public async newAbsentLate( + @Request() req: RequestWithUser, + @Body() body: CreateProfileEmployeeAbsentLate, + ) { + if (!body.profileEmployeeId) { + throw new HttpError(HttpStatus.BAD_REQUEST, "กรุณากรอก profileEmployeeId"); + } + + const profile = await this.profileRepo.findOneBy({ id: body.profileEmployeeId }); + if (!profile) { + throw new HttpError(HttpStatus.BAD_REQUEST, "ไม่พบ profile ดังกล่าว"); + } + await new permission().PermissionOrgUserUpdate(req, "SYS_REGISTRY_EMP", profile.id); + + const before = null; + const data = new ProfileEmployeeAbsentLate(); + + const meta = { + createdUserId: req.user.sub, + createdFullName: req.user.name, + lastUpdateUserId: req.user.sub, + lastUpdateFullName: req.user.name, + createdAt: new Date(), + lastUpdatedAt: new Date(), + }; + + Object.assign(data, { ...body, ...meta }); + + // บันทึก history + const history = new ProfileEmployeeAbsentLateHistory(); + Object.assign(history, { ...data, id: undefined }); + + await this.absentLateRepo.save(data, { data: req }); + setLogDataDiff(req, { before, after: data }); + history.profileEmployeeAbsentLateId = data.id; + await this.historyRepo.save(history, { data: req }); + + return new HttpSuccess(data.id); + } + + /** + * API สร้างข้อมูลการมาสาย/ขาดราชการ (สำหรับ Job) + * @summary API สร้างข้อมูลการมาสาย/ขาดราชการ (สำหรับ Job) + */ + @Post("batch") + public async newAbsentLateBatch( + @Request() req: RequestWithUser, + @Body() body: CreateProfileEmployeeAbsentLateBatch, + ) { + // กรณีไม่มีข้อมูลส่งมา (วันที่ไม่มีคนขาด/มาสาย) + if (!body.records || body.records.length === 0) { + return new HttpSuccess({ count: 0, ids: [] }); + } + + const profileIds = [...new Set(body.records.map((r) => r.profileEmployeeId))]; + const profiles = await this.profileRepo.findBy({ + id: In(profileIds), + }); + + const foundProfileIds = new Set(profiles.map((p) => p.id)); + const validRecords = body.records.filter((r) => foundProfileIds.has(r.profileEmployeeId)); + + // กรณีไม่พบ profile เลย + if (validRecords.length === 0) { + return new HttpSuccess({ count: 0, ids: [] }); + } + + const meta = { + createdUserId: req.user.sub, + createdFullName: req.user.name, + lastUpdateUserId: req.user.sub, + lastUpdateFullName: req.user.name, + createdAt: new Date(), + lastUpdatedAt: new Date(), + }; + + const records = validRecords.map((item) => { + const data = new ProfileEmployeeAbsentLate(); + Object.assign(data, { ...item, ...meta }); + return data; + }); + + const result = await this.absentLateRepo.save(records, { data: req }); + + // บันทึก history สำหรับแต่ละ record + const historyRecords = result.map((data) => { + const history = new ProfileEmployeeAbsentLateHistory(); + Object.assign(history, { ...data, id: undefined }); + history.profileEmployeeAbsentLateId = data.id; + return history; + }); + await this.historyRepo.save(historyRecords, { data: req }); + + return new HttpSuccess({ count: result.length, ids: result.map((r) => r.id) }); + } + + /** + * API แก้ไขข้อมูลการมาสาย/ขาดราชการ + * @summary API แก้ไขข้อมูลการมาสาย/ขาดราชการ + * @param absentLateId คีย์การมาสาย/ขาดราชการ + */ + @Patch("{absentLateId}") + public async editAbsentLate( + @Request() req: RequestWithUser, + @Body() body: UpdateProfileEmployeeAbsentLate, + @Path() absentLateId: string, + ) { + const record = await this.absentLateRepo.findOneBy({ id: absentLateId }); + if (!record) throw new HttpError(HttpStatus.NOT_FOUND, "ไม่พบข้อมูล"); + await new permission().PermissionOrgUserUpdate( + req, + "SYS_REGISTRY_EMP", + record.profileEmployeeId, + ); + + const before = structuredClone(record); + const history = new ProfileEmployeeAbsentLateHistory(); + Object.assign(history, { ...record, id: undefined }); + + Object.assign(record, body); + Object.assign(history, { ...record, id: undefined }); + + history.profileEmployeeAbsentLateId = absentLateId; + record.lastUpdateUserId = req.user.sub; + record.lastUpdateFullName = req.user.name; + record.lastUpdatedAt = new Date(); + history.lastUpdateUserId = req.user.sub; + history.lastUpdateFullName = req.user.name; + history.createdUserId = req.user.sub; + history.createdFullName = req.user.name; + history.createdAt = new Date(); + history.lastUpdatedAt = new Date(); + + await Promise.all([ + this.absentLateRepo.save(record, { data: req }), + setLogDataDiff(req, { before, after: record }), + this.historyRepo.save(history, { data: req }), + ]); + + return new HttpSuccess(); + } + + /** + * API ลบข้อมูลการมาสาย/ขาดราชการ (Soft Delete) + * @summary API ลบข้อมูลการมาสาย/ขาดราชการ (Soft Delete) + * @param absentLateId คีย์การมาสาย/ขาดราชการ + */ + @Patch("update-delete/{absentLateId}") + public async updateIsDeleted( + @Request() req: RequestWithUser, + @Path() absentLateId: string, + ) { + const record = await this.absentLateRepo.findOneBy({ id: absentLateId }); + if (!record) throw new HttpError(HttpStatus.NOT_FOUND, "ไม่พบข้อมูล"); + if (record.isDeleted === true) { + return new HttpSuccess(); + } + await new permission().PermissionOrgUserDelete(req, "SYS_REGISTRY_EMP", record.profileEmployeeId); + + const before = structuredClone(record); + const history = new ProfileEmployeeAbsentLateHistory(); + Object.assign(history, { ...record, id: undefined }); + + record.isDeleted = true; + record.lastUpdateUserId = req.user.sub; + record.lastUpdateFullName = req.user.name; + record.lastUpdatedAt = new Date(); + + history.profileEmployeeAbsentLateId = absentLateId; + history.isDeleted = true; + history.lastUpdateUserId = req.user.sub; + history.lastUpdateFullName = req.user.name; + history.lastUpdatedAt = new Date(); + + await Promise.all([ + this.absentLateRepo.save(record, { data: req }), + setLogDataDiff(req, { before, after: record }), + this.historyRepo.save(history, { data: req }), + ]); + + return new HttpSuccess(); + } + + /** + * API ดึงประวัติการมาสาย/ขาดราชการ + * @summary API ดึงประวัติการมาสาย/ขาดราชการ + * @param absentLateId คีย์การมาสาย/ขาดราชการ + */ + @Get("history/{absentLateId}") + public async getHistory(@Path() absentLateId: string, @Request() req: RequestWithUser) { + const record = await this.absentLateRepo.findOneBy({ id: absentLateId }); + if (!record) throw new HttpError(HttpStatus.NOT_FOUND, "ไม่พบข้อมูล"); + await new permission().PermissionOrgUserGet(req, "SYS_REGISTRY_EMP", record.profileEmployeeId); + + const history = await this.historyRepo.find({ + where: { profileEmployeeAbsentLateId: absentLateId }, + order: { createdAt: "DESC" }, + }); + return new HttpSuccess(history); + } +} diff --git a/src/controllers/ProfileEmployeeController.ts b/src/controllers/ProfileEmployeeController.ts index 008f0c67..2a7fba38 100644 --- a/src/controllers/ProfileEmployeeController.ts +++ b/src/controllers/ProfileEmployeeController.ts @@ -66,6 +66,7 @@ import { ProfileEmployeeEmploymentHistory } from "../entities/ProfileEmployeeEmp import CallAPI from "../interfaces/call-api"; import { ProfileInsignia } from "../entities/ProfileInsignia"; import { ProfileLeave } from "../entities/ProfileLeave"; +import { ProfileEmployeeAbsentLate } from "../entities/ProfileEmployeeAbsentLate"; import permission from "../interfaces/permission"; import axios from "axios"; import { Position } from "../entities/Position"; @@ -83,6 +84,7 @@ import { ProfileDuty } from "../entities/ProfileDuty"; import { getTopDegrees } from "../services/PositionService"; import { ProfileLeaveService } from "../services/ProfileLeaveService"; import { PostRetireToExprofile } from "./ExRetirementController"; +import { CommandCode } from "../entities/CommandCode"; @Route("api/v1/org/profile-employee") @Tags("ProfileEmployee") @Security("bearerAuth") @@ -124,6 +126,7 @@ export class ProfileEmployeeController extends Controller { private profileEducationRepo = AppDataSource.getRepository(ProfileEducation); private profileInsigniaRepo = AppDataSource.getRepository(ProfileInsignia); private profileLeaveRepository = AppDataSource.getRepository(ProfileLeave); + private profileEmployeeAbsentLateRepo = AppDataSource.getRepository(ProfileEmployeeAbsentLate); private positionRepository = AppDataSource.getRepository(Position); private employeePositionRepository = AppDataSource.getRepository(EmployeePosition); private permissionProflileRepository = AppDataSource.getRepository(PermissionProfile); @@ -134,6 +137,7 @@ export class ProfileEmployeeController extends Controller { private profileAssessmentsRepository = AppDataSource.getRepository(ProfileAssessment); private profileAbilityRepo = AppDataSource.getRepository(ProfileAbility); private profileAssistanceRepository = AppDataSource.getRepository(ProfileAssistance); + private commandCodeRepository = AppDataSource.getRepository(CommandCode); // Services private profileLeaveService = new ProfileLeaveService(); @@ -317,8 +321,16 @@ export class ProfileEmployeeController extends Controller { ]; const educations = await this.profileEducationRepo.find({ - select: ["startDate", "endDate", "educationLevel", "degree", "field", "institute"], - where: { profileEmployeeId: id }, + select: [ + "startDate", + "endDate", + "educationLevel", + "degree", + "field", + "institute", + "isDeleted", + ], + where: { profileEmployeeId: id, isDeleted: false }, order: { level: "ASC" }, }); const Education = @@ -571,8 +583,8 @@ export class ProfileEmployeeController extends Controller { let _child4 = child4?.orgChild4Name; const cert_raw = await this.certificateRepository.find({ - where: { profileEmployeeId: id }, - select: ["certificateType", "issuer", "certificateNo", "issueDate"], + select: ["certificateType", "issuer", "certificateNo", "issueDate", "isDeleted"], + where: { profileEmployeeId: id, isDeleted: false }, order: { createdAt: "ASC" }, }); const certs = @@ -592,8 +604,8 @@ export class ProfileEmployeeController extends Controller { }, ]; const training_raw = await this.trainingRepository.find({ - select: ["startDate", "endDate", "place", "department"], - where: { profileEmployeeId: id }, + select: ["startDate", "endDate", "place", "department", "isDeleted"], + where: { profileEmployeeId: id, isDeleted: false }, order: { createdAt: "ASC" }, }); const trainings = @@ -629,8 +641,8 @@ export class ProfileEmployeeController extends Controller { ]; const discipline_raw = await this.disciplineRepository.find({ - select: ["refCommandDate", "refCommandNo", "detail"], - where: { profileEmployeeId: id }, + select: ["refCommandDate", "refCommandNo", "detail", "isDeleted"], + where: { profileEmployeeId: id, isDeleted: false }, order: { createdAt: "ASC" }, }); const disciplines = @@ -651,8 +663,16 @@ export class ProfileEmployeeController extends Controller { ]; const education_raw = await this.profileEducationRepo.find({ - select: ["startDate", "endDate", "educationLevel", "degree", "field", "institute"], - where: { profileEmployeeId: id }, + select: [ + "startDate", + "endDate", + "educationLevel", + "degree", + "field", + "institute", + "isDeleted", + ], + where: { profileEmployeeId: id, isDeleted: false }, // order: { lastUpdatedAt: "DESC" }, order: { level: "ASC" }, }); @@ -752,7 +772,7 @@ export class ProfileEmployeeController extends Controller { insigniaType: true, }, }, - where: { profileEmployeeId: id }, + where: { profileEmployeeId: id, isDeleted: false }, order: { receiveDate: "ASC" }, }); const insignias = @@ -790,27 +810,68 @@ export class ProfileEmployeeController extends Controller { }, ]; - const leave_raw = await this.profileLeaveRepository.find({ - relations: { leaveType: true }, - where: { profileEmployeeId: id }, - order: { dateLeaveStart: "ASC" }, + const leave_raw = await this.profileLeaveRepository + .createQueryBuilder("profileLeave") + .leftJoinAndSelect("profileLeave.leaveType", "leaveType") + .select([ + "profileLeave.isDeleted", + "profileLeave.leaveTypeId", + "leaveType.name as name", + "leaveType.code as code", + "profileLeave.status", + "profileLeave.profileEmployeeId", + "MAX(profileLeave.dateLeaveStart) as maxDateLeaveStart", + ]) + .addSelect("SUM(profileLeave.leaveDays)", "totalLeaveDays") + .where("profileLeave.profileEmployeeId = :profileId", { profileId: id }) + .andWhere("profileLeave.isDeleted = :isDeleted", { isDeleted: false }) + .andWhere("profileLeave.status = :status", { status: "approve" }) + .groupBy("profileLeave.leaveTypeId") + .orderBy("code", "ASC") + .addOrderBy("maxDateLeaveStart", "ASC") + .getRawMany(); + + const leaves: any[] = []; + + leave_raw.forEach((item) => { + const leaveTypeCode = item.code ? item.code.trim().toUpperCase() : ""; + if (leaveTypeCode.startsWith("LV-")) { + const lvIndex = parseInt(leaveTypeCode.split("-")[1], 10); + + if (lvIndex >= 1 && lvIndex <= 11) { + const leaveTypeCodeKey = `leaveTypeCodeLv${lvIndex}`; + const totalLeaveDaysKey = `totalLeaveDaysLv${lvIndex}`; + const leaveTypeNameKey = `leaveTypeNameLv${lvIndex}`; + + const leaveDate = item.maxDateLeaveStart ? new Date(item.maxDateLeaveStart) : null; + const year = leaveDate + ? Extension.ToThaiNumber(Extension.ToThaiShortYear(leaveDate)) + : ""; + + let yearData = leaves.find((data) => data.year === year); + if (!yearData) { + yearData = { year }; + + for (let i = 1; i <= 11; i++) { + yearData[`leaveTypeCodeLv${i}`] = "-"; + yearData[`totalLeaveDaysLv${i}`] = "-"; + yearData[`leaveTypeNameLv${i}`] = "-"; + } + + leaves.push(yearData); + } + + yearData[leaveTypeCodeKey] = item.code ? item.code : "-"; + yearData[totalLeaveDaysKey] = item.totalLeaveDays + ? Extension.ToThaiNumber(item.totalLeaveDays.toString()) + : "-"; + yearData[leaveTypeNameKey] = item.name ? item.name : "-"; + } + } }); - const leaves = - leave_raw.length > 0 - ? leave_raw.map((item) => ({ - LeaveTypeName: item.leaveType.name, - DateLeaveStart: item.dateLeaveStart - ? Extension.ToThaiNumber(Extension.ToThaiFullDate2(item.dateLeaveStart)) - : "", - LeaveDays: item.leaveDays ? Extension.ToThaiNumber(item.leaveDays.toString()) : "", - })) - : [ - { - LeaveTypeName: "-", - DateLeaveStart: "-", - LeaveDays: "-", - }, - ]; + + // กรองเอา object ที่ไม่มี year ออก + const filteredLeaves = leaves.filter((item) => item.year && item.year !== ""); const data = { fullName: `${profiles?.prefix}${profiles?.firstName} ${profiles?.lastName}`, @@ -899,7 +960,7 @@ export class ProfileEmployeeController extends Controller { ? Extension.ToThaiNumber(Extension.ToThaiShortYear(profiles.profileAvatars[6].createdAt)) : null, insignias, - leaves, + leaves: filteredLeaves, certs, trainings, disciplines, @@ -925,6 +986,8 @@ export class ProfileEmployeeController extends Controller { public async getKk1new(@Path() id: string, @Request() req: RequestWithUser) { const profiles = await this.profileRepo.findOne({ relations: [ + "posType", + "posLevel", "currentSubDistrict", "currentDistrict", "currentProvince", @@ -1005,6 +1068,13 @@ export class ProfileEmployeeController extends Controller { order: { lastUpdatedAt: "DESC" }, }); + // const posMasterId = + // profiles.current_holders == null || + // profiles.current_holders.length == 0 || + // profiles.current_holders.find((x) => x.orgRevisionId == orgRevision?.id) == null + // ? null + // : profiles.current_holders.find((x) => x.orgRevisionId == orgRevision?.id)?.id; + const root = profiles.current_holders == null || profiles.current_holders.length == 0 || @@ -1048,8 +1118,15 @@ export class ProfileEmployeeController extends Controller { let _child4 = child4?.orgChild4Name; const cert_raw = await this.certificateRepository.find({ - where: { profileEmployeeId: id }, - select: ["certificateType", "issuer", "certificateNo", "issueDate", "expireDate"], + select: [ + "certificateType", + "issuer", + "certificateNo", + "issueDate", + "expireDate", + "isDeleted", + ], + where: { profileEmployeeId: id, isDeleted: false }, order: { createdAt: "ASC" }, }); const certs = @@ -1058,33 +1135,31 @@ export class ProfileEmployeeController extends Controller { certificateType: item.certificateType ?? null, issuer: item.issuer ?? null, certificateNo: item.certificateNo ? Extension.ToThaiNumber(item.certificateNo) : null, - issueDate: item.issueDate - ? Extension.ToThaiNumber(Extension.ToThaiFullDate2(item.issueDate)) - : "", - expireDate: item.expireDate - ? Extension.ToThaiNumber(Extension.ToThaiFullDate2(item.expireDate)) - : "", + detail: Extension.ToThaiNumber( + `${item.issuer ?? ""} ${item.certificateNo ?? ""}`.trim(), + ), issueToExpireDate: item.issueDate - ? item.expireDate - ? Extension.ToThaiNumber(`${Extension.ToThaiFullDate2(item.issueDate)} - ${Extension.ToThaiFullDate2(item.expireDate)}`) + ? item.expireDate + ? Extension.ToThaiNumber( + `${Extension.ToThaiFullDate2(item.issueDate)} - ${Extension.ToThaiFullDate2(item.expireDate)}`, + ) : Extension.ToThaiNumber(Extension.ToThaiFullDate2(item.issueDate)) : item.expireDate ? Extension.ToThaiNumber(Extension.ToThaiFullDate2(item.expireDate)) - : "" + : "", })) : [ { - certificateType: "-", - issuer: "-", - certificateNo: "-", - issueDate: "-", - expireDate: "-", - issueToExpireDate: "-", + certificateType: "", + issuer: "", + certificateNo: "", + detail: "", + issueToExpireDate: "", }, ]; const training_raw = await this.trainingRepository.find({ - select: ["place", "department", "name", "duration"], - where: { profileEmployeeId: id }, + select: ["place", "department", "name", "duration", "isDeleted", "startDate", "endDate"], + where: { profileEmployeeId: id, isDeleted: false }, order: { createdAt: "ASC" }, }); const trainings = @@ -1094,19 +1169,23 @@ export class ProfileEmployeeController extends Controller { degree: item.name ? Extension.ToThaiNumber(item.name) : "", place: item.place ? Extension.ToThaiNumber(item.place) : "", duration: item.duration ? Extension.ToThaiNumber(item.duration) : "", + date: Extension.ToThaiNumber( + `${Extension.ToThaiFullDate2(item.startDate)} - ${Extension.ToThaiFullDate2(item.endDate)}`, + ), })) : [ { - institute: "-", - degree: "-", - place: "-", - duration: "-", + institute: "", + degree: "", + place: "", + duration: "", + date: "", }, ]; const discipline_raw = await this.disciplineRepository.find({ - select: ["refCommandDate", "refCommandNo", "detail", "level"], - where: { profileEmployeeId: id }, + select: ["refCommandDate", "refCommandNo", "detail", "level", "isDeleted"], + where: { profileEmployeeId: id, isDeleted: false }, order: { createdAt: "ASC" }, }); const disciplines = @@ -1121,16 +1200,17 @@ export class ProfileEmployeeController extends Controller { })) : [ { - disciplineYear: "-", - disciplineDetail: "-", - refNo: "-", - level: "-", + disciplineYear: "", + disciplineDetail: "", + refNo: "", + level: "", }, ]; const education_raw = await this.profileEducationRepo .createQueryBuilder("education") .where("education.profileEmployeeId = :profileId", { profileId: id }) + .andWhere("education.isDeleted = :isDeleted", { isDeleted: false }) .orderBy("CASE WHEN education.isEducation = true THEN 1 ELSE 2 END", "ASC") .addOrderBy("education.level", "ASC") .getMany(); @@ -1141,13 +1221,15 @@ export class ProfileEmployeeController extends Controller { date: item.isDate ? `${item.startDate ? Extension.ToThaiNumber(Extension.ToThaiFullDate2(item.startDate)) : ""} - ${item.endDate ? Extension.ToThaiNumber(Extension.ToThaiFullDate2(item.endDate)) : ""}` : `${item.startDate ? Extension.ToThaiNumber(Extension.ToThaiShortYear(new Date(item.startDate))) : ""} - ${item.endDate ? Extension.ToThaiNumber(Extension.ToThaiShortYear(new Date(item.endDate))) : ""}`, - degree: item.degree ? `${item.degree} ${item.field ? item.field : ""}` : "", + degree: `${item.degree ?? ""} ${item.field ?? ""}`.trim(), + level: item.educationLevel, })) : [ { - institute: "-", - date: "-", - degree: "-", + institute: "", + date: "", + degree: "", + level: "", }, ]; const salary_raw = await this.salaryRepo.find({ @@ -1214,19 +1296,19 @@ export class ProfileEmployeeController extends Controller { })) : [ { - commandName: "-", - salaryDate: "-", - position: "-", - posNo: "-", - salary: "-", - special: "-", - rank: "-", - refAll: "-", - positionLevel: "-", - positionType: "-", - positionAmount: "-", - fullName: "-", - ocFullPath: "-", + commandName: "", + salaryDate: "", + position: "", + posNo: "", + salary: "", + special: "", + rank: "", + refAll: "", + positionLevel: "", + positionType: "", + positionAmount: "", + fullName: "", + ocFullPath: "", }, ]; @@ -1241,13 +1323,14 @@ export class ProfileEmployeeController extends Controller { "page", "refCommandDate", "note", + "isDeleted", ], relations: { insignia: { insigniaType: true, }, }, - where: { profileEmployeeId: id }, + where: { profileEmployeeId: id, isDeleted: false }, order: { receiveDate: "ASC" }, }); const insignias = @@ -1272,17 +1355,17 @@ export class ProfileEmployeeController extends Controller { })) : [ { - receiveDate: "-", - insigniaName: "-", - insigniaShortName: "-", - insigniaTypeName: "-", - no: "-", - issue: "-", - volumeNo: "-", - volume: "-", - section: "-", - page: "-", - refCommandDate: "-", + receiveDate: "", + insigniaName: "", + insigniaShortName: "", + insigniaTypeName: "", + no: "", + issue: "", + volumeNo: "", + volume: "", + section: "", + page: "", + refCommandDate: "", }, ]; @@ -1290,6 +1373,7 @@ export class ProfileEmployeeController extends Controller { .createQueryBuilder("profileLeave") .leftJoinAndSelect("profileLeave.leaveType", "leaveType") .select([ + "profileLeave.isDeleted", "profileLeave.leaveTypeId", "leaveType.name as name", "leaveType.code as code", @@ -1299,6 +1383,7 @@ export class ProfileEmployeeController extends Controller { ]) .addSelect("SUM(profileLeave.leaveDays)", "totalLeaveDays") .where("profileLeave.profileEmployeeId = :profileId", { profileId: id }) + .andWhere("profileLeave.isDeleted = :isDeleted", { isDeleted: false }) .andWhere("profileLeave.status = :status", { status: "approve" }) .groupBy("profileLeave.leaveTypeId") .orderBy("code", "ASC") @@ -1317,9 +1402,7 @@ export class ProfileEmployeeController extends Controller { const totalLeaveDaysKey = `totalLeaveDaysLv${lvIndex}`; const leaveTypeNameKey = `leaveTypeNameLv${lvIndex}`; - const leaveDate = item.maxDateLeaveStart - ? new Date(item.maxDateLeaveStart) - : null; + const leaveDate = item.maxDateLeaveStart ? new Date(item.maxDateLeaveStart) : null; const year = leaveDate ? Extension.ToThaiNumber(Extension.ToThaiShortYear(leaveDate)) : ""; @@ -1329,61 +1412,134 @@ export class ProfileEmployeeController extends Controller { yearData = { year }; for (let i = 1; i <= 11; i++) { - yearData[`leaveTypeCodeLv${i}`] = "-"; - yearData[`totalLeaveDaysLv${i}`] = "-"; - yearData[`leaveTypeNameLv${i}`] = "-"; + yearData[`leaveTypeCodeLv${i}`] = ""; + yearData[`totalLeaveDaysLv${i}`] = ""; + yearData[`leaveTypeNameLv${i}`] = ""; } leaves.push(yearData); } - yearData[leaveTypeCodeKey] = item.code ? item.code : "-"; + yearData[leaveTypeCodeKey] = item.code ? item.code : ""; yearData[totalLeaveDaysKey] = item.totalLeaveDays ? Extension.ToThaiNumber(item.totalLeaveDays.toString()) - : "-"; - yearData[leaveTypeNameKey] = item.name ? item.name : "-"; + : ""; + yearData[leaveTypeNameKey] = item.name ? item.name : ""; } } }); + // Query มาสาย/ขาดราชการ และ merge ตามปี + const absentLate_raw = await this.profileEmployeeAbsentLateRepo + .createQueryBuilder("absentLate") + .select([ + "YEAR(absentLate.stampDate) as year", + "absentLate.status as status", + "SUM(absentLate.stampAmount) as totalAmount", + ]) + .where("absentLate.profileEmployeeId = :profileId", { profileId: id }) + .andWhere("absentLate.isDeleted = :isDeleted", { isDeleted: false }) + .groupBy("YEAR(absentLate.stampDate), absentLate.status") + .orderBy("year", "DESC") + .getRawMany(); + + // Merge มาสาย/ขาดราชการเข้า leaves array + absentLate_raw.forEach((item) => { + const year = item.year ? Extension.ToThaiNumber((item.year + 543).toString()) : ""; + + let yearData = leaves.find((data) => data.year === year); + + // ถ้าไม่มีปีนั้นใน leaves ให้สร้างใหม่ + if (!yearData) { + yearData = { year }; + for (let i = 1; i <= 11; i++) { + yearData[`leaveTypeCodeLv${i}`] = ""; + yearData[`totalLeaveDaysLv${i}`] = ""; + yearData[`leaveTypeNameLv${i}`] = ""; + } + leaves.push(yearData); + } + + // เพิ่มข้อมูลมาสาย/ขาดราชการ + if (item.status === "LATE") { + yearData.lateAmount = item.totalAmount + ? Extension.ToThaiNumber(parseFloat(item.totalAmount).toFixed(1)) + : ""; + } else if (item.status === "ABSENT") { + yearData.absentAmount = item.totalAmount + ? Extension.ToThaiNumber(parseFloat(item.totalAmount).toFixed(1)) + : ""; + } + }); + + // // เติมค่า "" ถ้าไม่มีข้อมูลมาสาย/ขาดราชการ + // leaves.forEach((yearData) => { + // if (!yearData.lateAmount) yearData.lateAmount = ""; + // if (!yearData.absentAmount) yearData.absentAmount = ""; + // }); + + if (leaves.length === 0) { + leaves.push({ year: "" }); + } + const leave2_raw = await this.profileLeaveRepository .createQueryBuilder("profileLeave") .leftJoinAndSelect("profileLeave.leaveType", "leaveType") .select([ + "profileLeave.leaveSubTypeName AS leaveSubTypeName", + "profileLeave.coupleDayLevelCountry AS coupleDayLevelCountry", + "profileLeave.isDeleted AS isDeleted", "profileLeave.dateLeaveStart AS dateLeaveStart", "profileLeave.dateLeaveEnd AS dateLeaveEnd", "profileLeave.leaveDays AS leaveDays", "profileLeave.reason AS reason", "leaveType.name as name", + "leaveType.code as code", ]) .where("profileLeave.profileEmployeeId = :profileId", { profileId: id }) + .andWhere("profileLeave.isDeleted = :isDeleted", { isDeleted: false }) .andWhere("leaveType.code IN (:...codes)", { codes: ["LV-008", "LV-009", "LV-010"] }) .andWhere("profileLeave.status = :status", { status: "approve" }) .orderBy("leaveType.code", "ASC") .getRawMany(); const leaves2 = leave2_raw.length > 0 - ? leave2_raw.map((item) => ({ - date: - item.dateLeaveStart && item.dateLeaveEnd - ? Extension.ToThaiNumber(Extension.ToThaiFullDate2(item.dateLeaveStart)) + - " - " + - Extension.ToThaiNumber(Extension.ToThaiFullDate2(item.dateLeaveEnd)) - : "-", - type: item.name || "-", - leaveDays: item.leaveDays ? Extension.ToThaiNumber(item.leaveDays.toString()) : "-", - reason: item.reason || "-", - })) + ? leave2_raw.map((item) => { + const leaveTypeCode = item.code ? item.code.trim().toUpperCase() : ""; + + // ข้อที่ 1: LV-008 ให้ใช้ leaveSubTypeName (ประเภทย่อย) แทน name + const displayType = + leaveTypeCode === "LV-008" && item.leaveSubTypeName + ? item.leaveSubTypeName + : item.name || "-"; + + // ข้อที่ 2: แสดง reason ก่อนเสมอ ถ้ามี coupleDayLevelCountry ค่อยแสดงประเทศ + const displayReason = item.coupleDayLevelCountry + ? `${item.reason || ""} ลาไปประเทศ ${item.coupleDayLevelCountry}`.trim() + : item.reason || "-"; + + return { + date: + item.dateLeaveStart && item.dateLeaveEnd + ? Extension.ToThaiNumber(Extension.ToThaiFullDate2(item.dateLeaveStart)) + + " - " + + Extension.ToThaiNumber(Extension.ToThaiFullDate2(item.dateLeaveEnd)) + : "-", + type: displayType, + leaveDays: item.leaveDays ? Extension.ToThaiNumber(item.leaveDays.toString()) : "-", + reason: displayReason, + }; + }) : [ { - date: "-", - type: "-", - leaveDays: "-", - reason: "-", + date: "", + type: "", + leaveDays: "", + reason: "", }, ]; const children_raw = await this.profileChildrenRepository.find({ - where: { profileEmployeeId: id }, + where: { profileEmployeeId: id, isDeleted: false }, }); const children = children_raw.length > 0 @@ -1398,15 +1554,15 @@ export class ProfileEmployeeController extends Controller { : [ { no: "", - childrenPrefix: "-", - childrenFirstName: "-", - childrenLastName: "-", - childrenFullName: "-", - childrenLive: "-", + childrenPrefix: "", + childrenFirstName: "", + childrenLastName: "", + childrenFullName: "", + childrenLive: "", }, ]; const changeName_raw = await this.changeNameRepository.find({ - where: { profileEmployeeId: id }, + where: { profileEmployeeId: id, isDeleted: false }, order: { createdAt: "ASC" }, }); const changeName = @@ -1422,11 +1578,11 @@ export class ProfileEmployeeController extends Controller { })) : [ { - createdAt: "-", - status: "-", - prefix: "-", - firstName: "-", - lastName: "-", + createdAt: "", + status: "", + prefix: "", + firstName: "", + lastName: "", }, ]; @@ -1446,123 +1602,191 @@ export class ProfileEmployeeController extends Controller { })) : [ { - birthDateOld: "-", - birthDate: "-", + birthDateOld: "", + birthDate: "", }, ]; const position_raw = await this.salaryRepo.find({ - where: { - profileEmployeeId: id, - commandCode: In(["1", "2", "3", "4", "8", "10", "11", "12", "15", "16", "20"]), - isEntry: false, - }, + where: [ + { + profileEmployeeId: id, + commandCode: In([ + "0", + "1", + "2", + "3", + "4", + "8", + "9", + "10", + "11", + "12", + "13", + "14", + "15", + "16", + "20", + ]), + // isEntry: false, + }, + { profileEmployeeId: id, commandCode: IsNull() }, + ], order: { order: "ASC" }, }); + let _commandName: any = ""; + let _commandDateAffect: any = ""; const positionList = position_raw.length > 0 - ? position_raw.map((item) => ({ - commandName: item.commandName ?? "", - commandDateAffect: item.commandDateAffect - ? Extension.ToThaiNumber(Extension.ToThaiFullDate2(item.commandDateAffect)) - : "", - commandDateSign: item.commandDateSign - ? Extension.ToThaiNumber(Extension.ToThaiFullDate2(item.commandDateSign)) - : "", - posNo: - item.posNoAbb && item.posNo - ? Extension.ToThaiNumber(`${item.posNoAbb} ${item.posNo}`) - : "", - position: item.positionName, - posType: item.positionType, - posLevel: item.positionLevel - ? Extension.ToThaiNumber(item.positionLevel) - : item.positionCee - ? Extension.ToThaiNumber(item.positionCee) - : null, - amount: item.amount ? Extension.ToThaiNumber(Number(item.amount).toLocaleString()) : "", - positionSalaryAmount: item.positionSalaryAmount - ? Extension.ToThaiNumber(Number(item.positionSalaryAmount).toLocaleString()) - : "", - })) + ? await Promise.all( + position_raw.map(async (item, idx, arr) => { + const isLast = idx === arr.length - 1; + if (isLast) { + _commandDateAffect = item.commandDateAffect + ? Extension.ToThaiNumber(Extension.ToThaiFullDate2(item.commandDateAffect)) + : ""; + } + const _code = item.commandCode ? Number(item.commandCode) : null; + if (_code != null) { + _commandName = await this.commandCodeRepository.findOne({ + select: { name: true, code: true }, + where: { code: _code }, + }); + } + const codeSitAbb = item.posNumCodeSitAbb ?? "-"; + const commandNo = + item.commandNo && item.commandYear + ? item.commandNo + + "/" + + (item.commandYear > 2500 ? item.commandYear : item.commandYear + 543) + : "-"; + const dateAffect = item.commandDateAffect + ? `${Extension.ToThaiFullDate2(item.commandDateAffect)}` + : "-"; + return { + commandName: + _commandName && _commandName.name ? _commandName.name : item.commandName, + commandDateAffect: item.commandDateAffect + ? Extension.ToThaiNumber(Extension.ToThaiFullDate2(item.commandDateAffect)) + : "", + commandDateSign: item.commandDateSign + ? Extension.ToThaiNumber(Extension.ToThaiFullDate2(item.commandDateSign)) + : "", + posNo: + item.posNoAbb && item.posNo + ? Extension.ToThaiNumber(`${item.posNoAbb} ${item.posNo}`) + : "", + position: item.positionName, + posType: item.positionType, + posLevel: item.positionLevel + ? Extension.ToThaiNumber(item.positionLevel) + : item.positionCee + ? Extension.ToThaiNumber(item.positionCee) + : null, + amount: item.amount + ? Extension.ToThaiNumber(Number(item.amount).toLocaleString()) + : "", + positionSalaryAmount: item.positionSalaryAmount + ? Extension.ToThaiNumber(Number(item.positionSalaryAmount).toLocaleString()) + : "", + refDoc: Extension.ToThaiNumber( + `คำสั่ง ${codeSitAbb} ที่ ${commandNo} ลว. ${dateAffect}`, + ), + }; + }), + ) : [ { - commandName: "-", - commandDateAffect: "-", - commandDateSign: "-", - posNo: "-", - position: "-", - posType: "-", - posLevel: "-", - amount: "-", - positionSalaryAmount: "-", + commandName: "", + commandDateAffect: "", + commandDateSign: "", + posNo: "", + position: "", + posType: "", + posLevel: "", + amount: "", + positionSalaryAmount: "", + refDoc: "", }, ]; - const actposition_raw = await this.profileActpositionRepo.find({ - select: ["dateStart", "dateEnd", "position"], - where: { profileEmployeeId: id }, - order: { createdAt: "ASC" }, - }); - const assistance_raw = await this.profileAssistanceRepository.find({ - select: ["dateStart", "dateEnd", "commandName", "agency", "document"], - where: { profileEmployeeId: id }, - order: { createdAt: "ASC" }, - }); + // ลูกจ้างยังไม่มีรักษาการและช่วยราชการ + // const actposition_raw = await this.profileActpositionRepo.find({ + // select: ["dateStart", "dateEnd", "position", "isDeleted"], + // where: { profileEmployeeId: id, isDeleted: false }, + // order: { createdAt: "ASC" }, + // }); + // const assistance_raw = await this.profileAssistanceRepository.find({ + // select: ["dateStart", "dateEnd", "commandName", "agency", "document", "isDeleted"], + // where: { profileEmployeeId: id, isDeleted: false }, + // order: { createdAt: "ASC" }, + // }); - const _actposition = - actposition_raw.length > 0 - ? actposition_raw.map((item) => ({ - date: - item.dateStart && item.dateEnd - ? Extension.ToThaiNumber(`${Extension.ToThaiFullDate2(item.dateStart)} - ${Extension.ToThaiFullDate2(item.dateEnd)}`) - : item.dateStart - ? Extension.ToThaiNumber(Extension.ToThaiFullDate2(item.dateStart)) - : item.dateEnd - ? Extension.ToThaiNumber(Extension.ToThaiFullDate2(item.dateEnd)) - : "", - position: item.position ? Extension.ToThaiNumber(item.position) : "", - commandName: "รักษาการในตำแหน่ง", - agency: "", - document: "", - })) - : [ - { - date: "-", - position: "-", - commandName: "-", - agency: "-", - document: "-", - }, - ]; - const _assistance = - assistance_raw.length > 0 - ? assistance_raw.map((item) => ({ - date: - item.dateStart && item.dateEnd - ? Extension.ToThaiNumber(`${Extension.ToThaiFullDate2(item.dateStart)} - ${Extension.ToThaiFullDate2(item.dateEnd)}`) - : item.dateStart - ? Extension.ToThaiNumber(Extension.ToThaiFullDate2(item.dateStart)) - : item.dateEnd - ? Extension.ToThaiNumber(Extension.ToThaiFullDate2(item.dateEnd)) - : "", - position: "", - commandName: item.commandName ? Extension.ToThaiNumber(item.commandName) : "", - agency: item.agency ? Extension.ToThaiNumber(item.agency) : "", - document: item.document ? Extension.ToThaiNumber(item.document) : "", - })) - : [ - { - date: "-", - position: "-", - commandName: "-", - agency: "-", - document: "-", - }, - ]; - const actposition = [..._actposition, ..._assistance]; + // const _actposition = + // actposition_raw.length > 0 + // ? actposition_raw.map((item) => ({ + // date: + // item.dateStart && item.dateEnd + // ? Extension.ToThaiNumber( + // `${Extension.ToThaiFullDate2(item.dateStart)} - ${Extension.ToThaiFullDate2(item.dateEnd)}`, + // ) + // : item.dateStart + // ? Extension.ToThaiNumber(Extension.ToThaiFullDate2(item.dateStart)) + // : item.dateEnd + // ? Extension.ToThaiNumber(Extension.ToThaiFullDate2(item.dateEnd)) + // : "", + // position: item.position ? Extension.ToThaiNumber(item.position) : "", + // commandName: "รักษาการในตำแหน่ง", + // agency: "", + // document: "", + // })) + // : [ + // { + // date: "", + // position: "", + // commandName: "", + // agency: "", + // document: "", + // }, + // ]; + // const _assistance = + // assistance_raw.length > 0 + // ? assistance_raw.map((item) => ({ + // date: + // item.dateStart && item.dateEnd + // ? Extension.ToThaiNumber( + // `${Extension.ToThaiFullDate2(item.dateStart)} - ${Extension.ToThaiFullDate2(item.dateEnd)}`, + // ) + // : item.dateStart + // ? Extension.ToThaiNumber(Extension.ToThaiFullDate2(item.dateStart)) + // : item.dateEnd + // ? Extension.ToThaiNumber(Extension.ToThaiFullDate2(item.dateEnd)) + // : "", + // position: "", + // commandName: item.commandName ? Extension.ToThaiNumber(item.commandName) : "", + // agency: item.agency ? Extension.ToThaiNumber(item.agency) : "", + // document: item.document ? Extension.ToThaiNumber(item.document) : "", + // })) + // : [ + // { + // date: "", + // position: "", + // commandName: "", + // agency: "", + // document: "", + // }, + // ]; + const actposition = [ + { + date: "", + position: "", + commandName: "", + agency: "", + document: "", + }, + ]; const duty_raw = await this.dutyRepository.find({ - where: { profileEmployeeId: id }, + where: { profileEmployeeId: id, isDeleted: false }, order: { createdAt: "ASC" }, }); const duty = @@ -1570,33 +1794,50 @@ export class ProfileEmployeeController extends Controller { ? duty_raw.map((item) => ({ date: item.dateStart && item.dateEnd - ? Extension.ToThaiNumber(`${Extension.ToThaiFullDate2(item.dateStart)} - ${Extension.ToThaiFullDate2(item.dateEnd)}`) + ? Extension.ToThaiNumber( + `${Extension.ToThaiFullDate2(item.dateStart)} - ${Extension.ToThaiFullDate2(item.dateEnd)}`, + ) : item.dateStart ? Extension.ToThaiNumber(Extension.ToThaiFullDate2(item.dateStart)) : item.dateEnd ? Extension.ToThaiNumber(Extension.ToThaiFullDate2(item.dateEnd)) : "", - refCommandDate: item.refCommandDate - ? Extension.ToThaiNumber(Extension.ToThaiFullDate2(item.refCommandDate)) - : "", - refCommandNo: item.refCommandNo ? Extension.ToThaiNumber(item.refCommandNo) : "", + type: "-", + detail: Extension.ToThaiNumber(item.detail), + agency: "-", + refCommandNo: item.refCommandNo + ? item.refCommandDate + ? Extension.ToThaiNumber( + `${item.refCommandNo} ลว. ${Extension.ToThaiFullDate2(item.refCommandDate)}`, + ) + : Extension.ToThaiNumber(item.refCommandNo) + : "-", })) : [ { - date: "-", - refCommandDate: "-", - refCommandNo: "-", + date: "", + type: "", + detail: "", + agency: "", + refCommandNo: "", }, ]; const assessments_raw = await this.profileAssessmentsRepository.find({ - where: { profileEmployeeId: id }, + where: { profileEmployeeId: id, isDeleted: false }, order: { createdAt: "ASC" }, }); const assessments = assessments_raw.length > 0 ? assessments_raw.map((item) => ({ year: item.year ? Extension.ToThaiNumber((parseInt(item.year) + 543).toString()) : "", - period: item.period && item.period == "APR" ? "เมษายน" : "ตุลาคม", + period: + item.period && item.period == "APR" + ? Extension.ToThaiNumber( + `1 เม.ย. ${(parseInt(item.year) + 543 - 1).toString()} - 31 มี.ค. ${(parseInt(item.year) + 543).toString()}`, + ) + : Extension.ToThaiNumber( + `1 ต.ค. ${(parseInt(item.year) + 543 - 1).toString()} - 30 ก.ย. ${(parseInt(item.year) + 543).toString()}`, + ), point1: item.point1 ? Extension.ToThaiNumber(item.point1.toString()) : "", point1Total: item.point1Total ? Extension.ToThaiNumber(item.point1Total.toString()) @@ -1605,27 +1846,29 @@ export class ProfileEmployeeController extends Controller { point2Total: item.point2Total ? Extension.ToThaiNumber(item.point2Total.toString()) : "", - pointSum: item.pointSum ? Extension.ToThaiNumber(item.pointSum.toString()) : "", + pointSum: item.pointSum + ? Extension.ToThaiNumber(`ร้อยละ ${item.pointSum.toString()}`) + : "", pointSumTh: item.pointSum ? Extension.textPoint(item.pointSum) : "", level: item.pointSum < 60.0 - ? "ต้องปรับปรุง (คะแนนต่ำกว่าร้อยละ ๖๐.๐๐)" + ? "ต้องปรับปรุง" : item.pointSum <= 69.99 && item.pointSum >= 60.0 - ? "พอใช้ (คะแนนร้อยละ ๖๐.๐๐ - ๖๙.๙๙)" + ? "พอใช้" : item.pointSum <= 79.99 && item.pointSum >= 70.0 - ? "ดี (คะแนนร้อยละ ๗๐.๐๐ - ๗๙.๙๙)" + ? "ดี" : item.pointSum <= 89.99 && item.pointSum >= 80.0 - ? "ดีมาก (คะแนนร้อยละ ๘๐.๐๐ - ๘๙.๙๙)" - : "ดีเด่น (คะแนนร้อยละ ๙๐.๐๐ ขึ้นไป)", + ? "ดีมาก" + : "ดีเด่น", })) : [ { - year: "-", - period: "-", - point1: "-", - point2: "-", - pointSum: "-", - pointSumTh: "-", + year: "", + period: "", + point1: "", + point2: "", + pointSum: "", + pointSumTh: "", }, ]; const profileAbility_raw = await this.profileAbilityRepo.find({ @@ -1640,8 +1883,8 @@ export class ProfileEmployeeController extends Controller { })) : [ { - field: "-", - detail: "-", + field: "", + detail: "", }, ]; @@ -1655,35 +1898,119 @@ export class ProfileEmployeeController extends Controller { }); const otherIncome = otherIncome_raw.length > 0 - ? otherIncome_raw.map((item) => ({ - commandName: item.commandName ?? "", - commandDateAffect: item.commandDateAffect - ? Extension.ToThaiNumber(Extension.ToThaiFullDate2(item.commandDateAffect)) - : "", - commandDateSign: item.commandDateSign - ? Extension.ToThaiNumber(Extension.ToThaiFullDate2(item.commandDateSign)) - : "", - commandNo: item.commandNo ? Extension.ToThaiNumber(item.commandNo) : "", - position: item.positionName, - posLevel: item.positionLevel - ? Extension.ToThaiNumber(item.positionLevel) - : item.positionCee - ? Extension.ToThaiNumber(item.positionCee) - : null, - amount: item.amount ? Extension.ToThaiNumber(Number(item.amount).toLocaleString()) : "", - })) + ? await Promise.all( + otherIncome_raw.map(async (item) => { + const codeSitAbb = item.posNumCodeSitAbb ?? "-"; + const commandNo = + item.commandNo && item.commandYear + ? item.commandNo + + "/" + + (item.commandYear > 2500 ? item.commandYear : item.commandYear + 543) + : "-"; + const dateAffect = item.commandDateAffect + ? `${Extension.ToThaiFullDate2(item.commandDateAffect)}` + : "-"; + return { + commandName: item.commandName ?? "", + commandDateAffect: item.commandDateAffect + ? Extension.ToThaiNumber(Extension.ToThaiFullDate2(item.commandDateAffect)) + : "", + commandDateSign: item.commandDateSign + ? Extension.ToThaiNumber(Extension.ToThaiFullDate2(item.commandDateSign)) + : "", + commandNo: item.commandNo ? Extension.ToThaiNumber(item.commandNo) : "", + position: item.positionName, + posLevel: item.positionLevel + ? Extension.ToThaiNumber(item.positionLevel) + : item.positionCee + ? Extension.ToThaiNumber(item.positionCee) + : null, + amount: item.amount + ? Extension.ToThaiNumber(Number(item.amount).toLocaleString()) + : "", + refDoc: Extension.ToThaiNumber( + `คำสั่ง ${codeSitAbb} ที่ ${commandNo} ลว. ${dateAffect}`, + ), + }; + }), + ) : [ { - commandName: "-", - commandDateAffect: "-", - commandDateSign: "-", - commandNo: "-", - position: "-", - posLevel: "-", - amount: "-", + commandName: "", + commandDateAffect: "", + commandDateSign: "", + commandNo: "", + position: "", + posLevel: "", + amount: "", + refDoc: "", }, ]; + // ประวัติพ้นจากราชการ + let retires = []; + const currentDate = new Date(); + // todo: รอข้อสรุป + // const retire_raw = await this.salaryRepo.findOne({ + // where: { + // profileEmployeeId: id, + // commandCode: In(["12", "15", "16"]), + // }, + // order: { order: "desc" }, + // }); + + // if (retire_raw) { + // const startDate = retire_raw.commandDateAffect; + + // // คำนวณจำนวนวันจากวันพ้นสภาพถึงปัจจุบัน + // let daysCount = 0; + // if (startDate) { + // const start = new Date(startDate); + // daysCount = Math.ceil((currentDate.getTime() - start.getTime()) / (1000 * 60 * 60 * 24)); + // } + + // const startDateStr = startDate + // ? Extension.ToThaiNumber(Extension.ToThaiFullDate2(startDate)) + // : "-"; + + // retires.push({ + // date: `${startDateStr} - ปัจจุบัน`, + // detail: retire_raw.commandName ?? "-", + // day: daysCount > 0 ? Extension.ToThaiNumber(daysCount.toLocaleString()) : "-" + // }); + // } + + // กรณีไม่มีข้อมูล + if (retires.length === 0) { + retires.push({ date: "", detail: "", day: "" }); + } + + const org = + (_child4 == null ? "" : _child4 + " ") + + (_child3 == null ? "" : _child3 + " ") + + (_child2 == null ? "" : _child2 + " ") + + (_child1 == null ? "" : _child1 + " ") + + (_root == null ? "" : _root).trim(); + const _position = ( + profiles?.position != null + ? profiles.posType != null && profiles?.posLevel != null + ? Extension.ToThaiNumber( + `${profiles.position} ${profiles.posType.posTypeShortName} ${profiles.posLevel.posLevelName}`, + ) + : profiles.position + : "" + ).trim(); + const ocAssistance = await this.profileAssistanceRepository.findOne({ + select: ["agency", "profileEmployeeId", "commandName", "status", "isDeleted"], + where: { + profileEmployeeId: id, + commandName: "ให้ช่วยราชการ", + status: "PENDING", + isDeleted: false, + }, + order: { createdAt: "ASC" }, + }); + const sum = profiles ? Extension.ToThaiNumber( ( @@ -1727,11 +2054,13 @@ export class ProfileEmployeeController extends Controller { ) : ""; const data = { + currentDate: Extension.ToThaiNumber(Extension.ToThaiFullDate2(currentDate)), fullName: `${profiles?.prefix}${profiles?.firstName} ${profiles?.lastName}`, prefix: profiles?.prefix != null ? profiles.prefix : "", firstName: profiles?.firstName != null ? profiles.firstName : "", lastName: profiles?.lastName != null ? profiles.lastName : "", - position: profiles?.position != null ? profiles.position : "", + position: _position, + posAssistance: ocAssistance ? "-" : _position, // ปัจจุบันช่วยราชการยังไม่มีเก็บฟิลด์ตำแหน่ง amount: profiles?.amount != null ? Extension.ToThaiNumber(profiles.amount.toLocaleString()) : "", positionSalaryAmount: @@ -1747,12 +2076,14 @@ export class ProfileEmployeeController extends Controller { ? Extension.ToThaiNumber(profiles.amountSpecial.toLocaleString()) : "", salarySum: sum, - ocFullPath: - (_child4 == null ? "" : _child4 + "\n") + - (_child3 == null ? "" : _child3 + "\n") + - (_child2 == null ? "" : _child2 + "\n") + - (_child1 == null ? "" : _child1 + "\n") + - (_root == null ? "" : _root), + ocFullPath: org, + ocAssistance: ocAssistance?.agency ?? org, + root: _root == null ? "" : _root, + agency: + (_child4 == null ? "" : _child4 + " ") + + (_child3 == null ? "" : _child3 + " ") + + (_child2 == null ? "" : _child2 + " ") + + (_child1 == null ? "" : _child1).trim(), birthDate: profiles?.birthDate ? Extension.ToThaiNumber(Extension.ToThaiFullDate2(profiles.birthDate)) : "", @@ -1763,8 +2094,7 @@ export class ProfileEmployeeController extends Controller { appointDate: profiles?.dateAppoint ? Extension.ToThaiNumber(Extension.ToThaiFullDate2(profiles.dateAppoint)) : "", - positionDate: - positionList.length > 0 ? positionList[positionList.length - 1].commandDateAffect : "", + positionDate: _commandDateAffect ?? "", citizenId: profiles.citizenId != null ? Extension.ToThaiNumber(profiles.citizenId.toString()) : "", fatherFullName: @@ -1830,7 +2160,7 @@ export class ProfileEmployeeController extends Controller { ? Extension.ToThaiNumber(profiles.registrationZipCode) : "", fullRegistrationAddress: fullRegistrationAddress, - updateAt: profiles.lastUpdatedAt + updateAt: profiles.lastUpdatedAt ? Extension.ToThaiNumber(Extension.ToThaiFullDate2(profiles.lastUpdatedAt)) : "", telephone: profiles.phone != null ? Extension.ToThaiNumber(profiles.phone) : "", @@ -1881,6 +2211,7 @@ export class ProfileEmployeeController extends Controller { assessments, profileAbility, otherIncome, + retires, }; return new HttpSuccess({ @@ -2254,20 +2585,33 @@ export class ProfileEmployeeController extends Controller { */ @Get("history/user") async getHistoryProfileByUser(@Request() request: RequestWithUser) { - const historyProfile = await this.profileHistoryRepo.find({ - relations: { - posLevel: true, - posType: true, - }, + const profile = await this.profileRepo.findOne({ where: { keycloak: request.user.sub }, - order: { - createdAt: "ASC", - }, + }); + if (!profile) throw new HttpError(HttpStatus.NOT_FOUND, "ไม่พบข้อมูล"); + + const profileHistory = await this.profileHistoryRepo.find({ + where: { profileEmployeeId: profile.id }, + order: { createdAt: "ASC" }, }); - if (!historyProfile) throw new HttpError(HttpStatus.NOT_FOUND, "ไม่พบข้อมูล"); + if (profileHistory.length == 0) { + await this.profileHistoryRepo.save( + Object.assign(new ProfileEmployeeHistory(), { + ...profile, + birthDateOld: profile?.birthDate, + profileEmployeeId: profile.id, + id: undefined, + }), + ); + const firstRecord = await this.profileHistoryRepo.find({ + where: { profileEmployeeId: profile.id }, + order: { createdAt: "ASC" }, + }); + return new HttpSuccess(firstRecord); + } - return new HttpSuccess(historyProfile); + return new HttpSuccess(profileHistory); } /** @@ -2882,7 +3226,8 @@ export class ProfileEmployeeController extends Controller { _data.child1 != undefined && _data.child1 != null ? _data.child1[0] != null ? `current_holders.orgChild1Id IN (:...child1)` - : `current_holders.orgChild1Id is ${_data.privilege == "PARENT" ? "not null" : "null"}` + : // : `current_holders.orgChild1Id is ${_data.privilege == "PARENT" ? "not null" : "null"}` + `current_holders.orgChild1Id is null` : "1=1", { child1: _data.child1, @@ -3157,20 +3502,33 @@ export class ProfileEmployeeController extends Controller { @Get("history/{id}") async getProfileHistory(@Path() id: string, @Request() req: RequestWithUser) { //await new permission().PermissionOrgUserGet(req, "SYS_REGISTRY_EMP", id); ไม่แน่ใจEMPปิดไว้ก่อน; - const profile = await this.profileHistoryRepo.find({ - relations: { - posLevel: true, - posType: true, - }, - where: { profileEmployeeId: id }, - order: { - createdAt: "ASC", - }, + const profile = await this.profileRepo.findOne({ + where: { id: id }, }); - if (!profile) throw new HttpError(HttpStatus.NOT_FOUND, "ไม่พบข้อมูล"); - return new HttpSuccess(profile); + const profileHistory = await this.profileHistoryRepo.find({ + where: { profileEmployeeId: id }, + order: { createdAt: "ASC" }, + }); + + if (profileHistory.length == 0) { + await this.profileHistoryRepo.save( + Object.assign(new ProfileEmployeeHistory(), { + ...profile, + birthDateOld: profile?.birthDate, + profileEmployeeId: id, + id: undefined, + }), + ); + const firstRecord = await this.profileHistoryRepo.find({ + where: { profileEmployeeId: id }, + order: { createdAt: "ASC" }, + }); + return new HttpSuccess(firstRecord); + } + + return new HttpSuccess(profileHistory); } /** @@ -3761,7 +4119,8 @@ export class ProfileEmployeeController extends Controller { _data.child1 != undefined && _data.child1 != null ? _data.child1[0] != null ? `current_holders.orgChild1Id IN (:...child1)` - : `current_holders.orgChild1Id is ${_data.privilege == "PARENT" ? "not null" : "null"}` + : // : `current_holders.orgChild1Id is ${_data.privilege == "PARENT" ? "not null" : "null"}` + `current_holders.orgChild1Id is null` : "1=1", { child1: _data.child1 }, ) @@ -3854,7 +4213,7 @@ export class ProfileEmployeeController extends Controller { )?.posMasterNo; const latestProfileEducation = await this.profileEducationRepo.findOne({ - where: { profileEmployeeId: item.id }, + where: { profileEmployeeId: item.id, isDeleted: false }, order: { level: "ASC" }, }); @@ -4319,7 +4678,8 @@ export class ProfileEmployeeController extends Controller { _data.child1 != undefined && _data.child1 != null ? _data.child1[0] != null ? `current_holders.orgChild1Id IN (:...child1)` - : `current_holders.orgChild1Id is ${_data.privilege == "PARENT" ? "not null" : "null"}` + : // : `current_holders.orgChild1Id is ${_data.privilege == "PARENT" ? "not null" : "null"}` + `current_holders.orgChild1Id is null` : "1=1", { child1: _data.child1, @@ -5239,9 +5599,9 @@ export class ProfileEmployeeController extends Controller { } /** - * API อัพเดทเกษียณ + * API อัพเดทถึงแก่กรรม * - * @summary อัพเดทเกษียณ (ADMIN) + * @summary อัพเดทถึงแก่กรรม (ADMIN) * * @param {string} id Id ทะเบียนประวัติ */ @@ -5363,12 +5723,14 @@ export class ProfileEmployeeController extends Controller { // profile.position = _null; // profile.posLevelId = _null; // profile.posTypeId = _null; - if (profile.keycloak != null) { + if (profile.keycloak != null && profile.keycloak != "" && profile.isDelete === false) { const delUserKeycloak = await deleteUser(profile.keycloak); if (delUserKeycloak) { - profile.keycloak = _null; + // Task #228 + // profile.keycloak = _null; profile.roleKeycloaks = []; profile.isActive = false; + profile.isDelete = true; } } await this.profileRepo.save(profile); @@ -5386,29 +5748,19 @@ export class ProfileEmployeeController extends Controller { ].filter(Boolean); organizeName = names.join(" "); } - await PostRetireToExprofile( - // profile.citizenId ?? "", - // profile.prefix ?? "", - // profile.firstName ?? "", - // profile.lastName ?? "", - // requestBody.dateLeave?.getFullYear().toString() ?? "", - // profile.position, - // profile.posType?.posTypeName ?? "", - // `${profile.posType?.posTypeShortName} ${profile.posLevel?.posLevelName}`, - // requestBody.dateLeave ?? new Date(), - // organizeName, - // "ถึงแก่กรรม", - "310190004095X", - "จ.ส.อ.", - "dev", - "hrms", - "2026", - "เจ้าหน้าที่จัดเก็บรายได้", - "อื่นๆ", - "C 3", - new Date(2026, 0, 1), - "สำนักงานเขตบางกอกใหญ่", - "เกษียณ" + PostRetireToExprofile( + request, + profile.citizenId ?? "", + profile.prefix ?? "", + profile.firstName ?? "", + profile.lastName ?? "", + requestBody.dateLeave?.getFullYear().toString() ?? "", + profile.position, + profile.posType?.posTypeName ?? "", + `${profile.posType?.posTypeShortName} ${profile.posLevel?.posLevelName}`, + requestBody.dateLeave ?? new Date(), + organizeName, + "ถึงแก่กรรม", ); return new HttpSuccess(); } @@ -5850,7 +6202,10 @@ export class ProfileEmployeeController extends Controller { .leftJoinAndSelect("current_holders.orgChild2", "orgChild2") .leftJoinAndSelect("current_holders.orgChild3", "orgChild3") .leftJoinAndSelect("current_holders.orgChild4", "orgChild4") - .where("profile.keycloak IS NULL") + .where(body.system ? "profile.isActive = :isActive" : "profile.isDelete = :isDelete", { + isActive: false, + isDelete: true, + }) .andWhere( new Brackets((qb) => { qb.orWhere(body.keyword ? queryLike : "1=1", { keyword: `%${body.keyword}%` }); @@ -5953,7 +6308,7 @@ export class ProfileEmployeeController extends Controller { } const latestProfileEducation = await this.profileEducationRepo.findOne({ - where: { profileEmployeeId: item.id }, + where: { profileEmployeeId: item.id, isDeleted: false }, order: { level: "ASC" }, }); diff --git a/src/controllers/ProfileEmployeeTempController.ts b/src/controllers/ProfileEmployeeTempController.ts index efe59ca2..f5182deb 100644 --- a/src/controllers/ProfileEmployeeTempController.ts +++ b/src/controllers/ProfileEmployeeTempController.ts @@ -289,8 +289,16 @@ export class ProfileEmployeeTempController extends Controller { }, ]; const educations = await this.profileEducationRepo.find({ - select: ["startDate", "endDate", "educationLevel", "degree", "field", "institute"], - where: { profileEmployeeId: id }, + select: [ + "startDate", + "endDate", + "educationLevel", + "degree", + "field", + "institute", + "isDeleted", + ], + where: { profileEmployeeId: id, isDeleted: false }, order: { level: "ASC" }, }); const Education = @@ -547,8 +555,8 @@ export class ProfileEmployeeTempController extends Controller { let _child4 = child4?.orgChild4Name; const cert_raw = await this.certificateRepository.find({ - where: { profileEmployeeId: id }, - select: ["certificateType", "issuer", "certificateNo", "issueDate"], + select: ["certificateType", "issuer", "certificateNo", "issueDate", "isDeleted"], + where: { profileEmployeeId: id, isDeleted: false }, order: { createdAt: "ASC" }, }); const certs = @@ -627,8 +635,16 @@ export class ProfileEmployeeTempController extends Controller { ]; const education_raw = await this.profileEducationRepo.find({ - select: ["startDate", "endDate", "educationLevel", "degree", "field", "institute"], - where: { profileEmployeeId: id }, + select: [ + "startDate", + "endDate", + "educationLevel", + "degree", + "field", + "institute", + "isDeleted", + ], + where: { profileEmployeeId: id, isDeleted: false }, // order: { lastUpdatedAt: "DESC" }, order: { level: "ASC" }, }); @@ -734,7 +750,7 @@ export class ProfileEmployeeTempController extends Controller { insigniaType: true, }, }, - where: { profileEmployeeId: id }, + where: { profileEmployeeId: id, isDeleted: false }, order: { receiveDate: "ASC" }, }); const insignias = @@ -774,7 +790,7 @@ export class ProfileEmployeeTempController extends Controller { const leave_raw = await this.profileLeaveRepository.find({ relations: { leaveType: true }, - where: { profileEmployeeId: id }, + where: { profileEmployeeId: id, isDeleted: false }, order: { dateLeaveStart: "ASC" }, }); const leaves = @@ -1110,7 +1126,8 @@ export class ProfileEmployeeTempController extends Controller { _dataOrg.child1 != undefined && _dataOrg.child1 != null ? _dataOrg.child1[0] != null ? `current_holderTemps.orgChild1Id IN (:...child1)` - : `current_holderTemps.orgChild1Id is ${_dataOrg.privilege == "PARENT" ? "not null" : "null"}` + : // : `current_holderTemps.orgChild1Id is ${_dataOrg.privilege == "PARENT" ? "not null" : "null"}` + `current_holders.orgChild1Id is null` : "1=1", { child1: _dataOrg.child1, @@ -1277,20 +1294,33 @@ export class ProfileEmployeeTempController extends Controller { */ @Get("history/user") async getHistoryProfileByUser(@Request() request: RequestWithUser) { - const historyProfile = await this.profileHistoryRepo.find({ - relations: { - posLevel: true, - posType: true, - }, + const profile = await this.profileRepo.findOne({ where: { keycloak: request.user.sub }, - order: { - createdAt: "ASC", - }, + }); + if (!profile) throw new HttpError(HttpStatus.NOT_FOUND, "ไม่พบข้อมูล"); + + const profileHistory = await this.profileHistoryRepo.find({ + where: { profileEmployeeId: profile.id }, + order: { createdAt: "ASC" }, }); - if (!historyProfile) throw new HttpError(HttpStatus.NOT_FOUND, "ไม่พบข้อมูล"); + if (profileHistory.length == 0) { + await this.profileHistoryRepo.save( + Object.assign(new ProfileEmployeeHistory(), { + ...profile, + birthDateOld: profile?.birthDate, + profileEmployeeId: profile.id, + id: undefined, + }), + ); + const firstRecord = await this.profileHistoryRepo.find({ + where: { profileEmployeeId: profile.id }, + order: { createdAt: "ASC" }, + }); + return new HttpSuccess(firstRecord); + } - return new HttpSuccess(historyProfile); + return new HttpSuccess(profileHistory); } /** @@ -1541,8 +1571,8 @@ export class ProfileEmployeeTempController extends Controller { let query = await this.profileRepo .createQueryBuilder("profileEmployee") - .leftJoinAndSelect("profileEmployee.posLevel", "posLevel") - .leftJoinAndSelect("profileEmployee.posType", "posType") + .leftJoinAndSelect("profileEmployee.posLevelTemp", "posLevelTemp") + .leftJoinAndSelect("profileEmployee.posTypeTemp", "posTypeTemp") .leftJoinAndSelect("profileEmployee.current_holderTemps", "current_holderTemps") .leftJoinAndSelect("profileEmployee.profileEmployeeEmployment", "profileEmployeeEmployment") .leftJoinAndSelect("current_holderTemps.positions", "positions") @@ -1565,7 +1595,8 @@ export class ProfileEmployeeTempController extends Controller { _data.child1 != undefined && _data.child1 != null ? _data.child1[0] != null ? `current_holderTemps.orgChild1Id IN (:...child1)` - : `current_holderTemps.orgChild1Id is ${_data.privilege == "PARENT" ? "not null" : "null"}` + : // : `current_holderTemps.orgChild1Id is ${_data.privilege == "PARENT" ? "not null" : "null"}` + `current_holders.orgChild1Id is null` : "1=1", { child1: _data.child1, @@ -1611,7 +1642,7 @@ export class ProfileEmployeeTempController extends Controller { ) .andWhere( posType != undefined && posType != null && posType != "" - ? "posType.posTypeName LIKE :keyword1" + ? "posTypeTemp.posTypeName LIKE :keyword1" : "1=1", { keyword1: `${posType}`, @@ -1619,7 +1650,7 @@ export class ProfileEmployeeTempController extends Controller { ) .andWhere( posLevel != undefined && posLevel != null && posLevel != "" - ? "posLevel.posLevelName LIKE :keyword2" + ? "posLevelTemp.posLevelName LIKE :keyword2" : "1=1", { keyword2: `${posLevel}`, @@ -1654,8 +1685,8 @@ export class ProfileEmployeeTempController extends Controller { ); if (sortBy) { - if (sortBy == "posLevel") { - query = query.orderBy(`posLevel.posLevelName`, descending ? "DESC" : "ASC"); + if (sortBy == "posLevelTemp") { + query = query.orderBy(`posLevelTemp.posLevelName`, descending ? "DESC" : "ASC"); } else if (sortBy == "posType") { query = query.orderBy(`posType.posTypeName`, descending ? "DESC" : "ASC"); } else if (sortBy == "govAge") { @@ -1712,11 +1743,13 @@ export class ProfileEmployeeTempController extends Controller { firstName: _data.firstName, lastName: _data.lastName, citizenId: _data.citizenId, - posLevel: _data.posLevel == null ? null : _data.posLevel.posLevelName, - posType: _data.posType == null ? null : _data.posType.posTypeName, - posTypeShortName: _data.posType == null ? null : _data.posType.posTypeShortName, - posLevelId: _data.posLevel == null ? null : _data.posLevel.id, - posTypeId: _data.posType == null ? null : _data.posType.id, + // posLevel: _data.posLevel == null ? null : _data.posLevel.posLevelName, + posLevel: _data.posLevelTemp == null ? null : _data.posLevelTemp.posLevelName, + // posType: _data.posType == null ? null : _data.posType.posTypeName, + posType: _data.posTypeTemp == null ? null : _data.posTypeTemp.posTypeName, + posTypeShortName: _data.posTypeTemp == null ? null : _data.posTypeTemp.posTypeShortName, + posLevelId: _data.posLevelTemp == null ? null : _data.posLevelTemp.id, + posTypeId: _data.posTypeTemp == null ? null : _data.posTypeTemp.id, positionId: _data.positionIdTemp, posmasterId: _data.posmasterIdTemp, position: _data.position, @@ -1794,20 +1827,33 @@ export class ProfileEmployeeTempController extends Controller { @Get("history/{id}") async getProfileHistory(@Path() id: string, @Request() req: RequestWithUser) { // await new permission().PermissionGet(req, "SYS_REGISTRY_TEMP");//ไม่แน่ใจTEMPปิดไว้ก่อน - const profile = await this.profileHistoryRepo.find({ - relations: { - posLevel: true, - posType: true, - }, - where: { profileEmployeeId: id }, - order: { - createdAt: "ASC", - }, + const profile = await this.profileRepo.findOne({ + where: { id: id }, }); - if (!profile) throw new HttpError(HttpStatus.NOT_FOUND, "ไม่พบข้อมูล"); - return new HttpSuccess(profile); + const profileHistory = await this.profileHistoryRepo.find({ + where: { profileEmployeeId: id }, + order: { createdAt: "ASC" }, + }); + + if (profileHistory.length == 0) { + await this.profileHistoryRepo.save( + Object.assign(new ProfileEmployeeHistory(), { + ...profile, + birthDateOld: profile?.birthDate, + profileEmployeeId: id, + id: undefined, + }), + ); + const firstRecord = await this.profileHistoryRepo.find({ + where: { profileEmployeeId: id }, + order: { createdAt: "ASC" }, + }); + return new HttpSuccess(firstRecord); + } + + return new HttpSuccess(profileHistory); } /** @@ -2274,7 +2320,8 @@ export class ProfileEmployeeTempController extends Controller { _data.child1 != undefined && _data.child1 != null ? _data.child1[0] != null ? `current_holderTemps.orgChild1Id IN (:...child1)` - : `current_holderTemps.orgChild1Id is ${_data.privilege == "PARENT" ? "not null" : "null"}` + : // : `current_holderTemps.orgChild1Id is ${_data.privilege == "PARENT" ? "not null" : "null"}` + `current_holders.orgChild1Id is null` : "1=1", { child1: _data.child1 }, ) @@ -2370,7 +2417,7 @@ export class ProfileEmployeeTempController extends Controller { )?.posMasterNo; const latestProfileEducation = await this.profileEducationRepo.findOne({ - where: { profileEmployeeId: item.id }, + where: { profileEmployeeId: item.id, isDeleted: false }, order: { level: "ASC" }, }); @@ -3412,9 +3459,9 @@ export class ProfileEmployeeTempController extends Controller { } /** - * API อัพเดทเกษียณ + * API อัพเดทถึงแก่กรรม * - * @summary อัพเดทเกษียณ (ADMIN) + * @summary อัพเดทถึงแก่กรรม (ADMIN) * * @param {string} id Id ทะเบียนประวัติ */ @@ -3536,12 +3583,14 @@ export class ProfileEmployeeTempController extends Controller { // profile.position = _null; // profile.posLevelId = _null; // profile.posTypeId = _null; - if (profile.keycloak != null) { + if (profile.keycloak != null && profile.keycloak != "" && profile.isDelete == false) { const delUserKeycloak = await deleteUser(profile.keycloak); if (delUserKeycloak) { - profile.keycloak = _null; + // Task #228 + // profile.keycloak = _null; profile.roleKeycloaks = []; profile.isActive = false; + profile.isDelete = true; } } await this.profileRepo.save(profile); @@ -3559,29 +3608,19 @@ export class ProfileEmployeeTempController extends Controller { ].filter(Boolean); organizeName = names.join(" "); } - await PostRetireToExprofile( - // profile.citizenId ?? "", - // profile.prefix ?? "", - // profile.firstName ?? "", - // profile.lastName ?? "", - // requestBody.dateLeave?.getFullYear().toString() ?? "", - // profile.position, - // profile.posType?.posTypeName ?? "", - // `${profile.posType?.posTypeShortName} ${profile.posLevel?.posLevelName}`, - // requestBody.dateLeave ?? new Date(), - // organizeName, - // "ถึงแก่กรรม", - "310190004095X", - "จ.ส.อ.", - "dev", - "hrms", - "2026", - "เจ้าหน้าที่จัดเก็บรายได้", - "อื่นๆ", - "C 3", - new Date(2026, 0, 1), - "สำนักงานเขตบางกอกใหญ่", - "เกษียณ" + PostRetireToExprofile( + request, + profile.citizenId ?? "", + profile.prefix ?? "", + profile.firstName ?? "", + profile.lastName ?? "", + requestBody.dateLeave?.getFullYear().toString() ?? "", + profile.position, + profile.posType?.posTypeName ?? "", + `${profile.posType?.posTypeShortName} ${profile.posLevel?.posLevelName}`, + requestBody.dateLeave ?? new Date(), + organizeName, + "ถึงแก่กรรม", ); return new HttpSuccess(); } @@ -3950,7 +3989,7 @@ export class ProfileEmployeeTempController extends Controller { case "citizenId": [findProfile, total] = await this.profileRepo.findAndCount({ where: { - keycloak: IsNull(), + isActive: false, citizenId: Like(`%${body.keyword}%`), }, relations: ["posType", "posLevel", "current_holders"], @@ -3962,7 +4001,7 @@ export class ProfileEmployeeTempController extends Controller { case "firstname": [findProfile, total] = await this.profileRepo.findAndCount({ where: { - keycloak: IsNull(), + isActive: false, firstName: Like(`%${body.keyword}%`), }, relations: ["posType", "posLevel", "current_holders"], @@ -3974,7 +4013,7 @@ export class ProfileEmployeeTempController extends Controller { case "lastname": [findProfile, total] = await this.profileRepo.findAndCount({ where: { - keycloak: IsNull(), + isActive: false, lastName: Like(`%${body.keyword}%`), }, relations: ["posType", "posLevel", "current_holders"], @@ -3986,7 +4025,7 @@ export class ProfileEmployeeTempController extends Controller { default: [findProfile, total] = await this.profileRepo.findAndCount({ where: { - keycloak: IsNull(), + isActive: false, }, relations: ["posType", "posLevel", "current_holders"], skip, @@ -4043,7 +4082,7 @@ export class ProfileEmployeeTempController extends Controller { )?.posMasterNo; const latestProfileEducation = await this.profileEducationRepo.findOne({ - where: { profileEmployeeId: item.id }, + where: { profileEmployeeId: item.id, isDeleted: false }, order: { level: "ASC" }, }); diff --git a/src/controllers/ProfileGovernmentController.ts b/src/controllers/ProfileGovernmentController.ts index f2208036..8caaff28 100644 --- a/src/controllers/ProfileGovernmentController.ts +++ b/src/controllers/ProfileGovernmentController.ts @@ -110,23 +110,25 @@ export class ProfileGovernmentHistoryController extends Controller { orgShortName = posMaster.orgChild4?.orgChild4ShortName; } } + //posMaster?.isSit แก้ไขชั่วคราว const data = { org: org, //สังกัด - positionField: position == null ? null : position.positionField, //สายงาน + positionField: position == null || posMaster?.isSit ? null : position.positionField, //สายงาน position: record.position, //ตำแหน่ง posLevel: record.posLevel == null ? null : record.posLevel.posLevelName, //ระดับ posMasterNo: posMaster == null ? null : `${orgShortName} ${posMaster.posMasterNo}`, //เลขที่ตำแหน่ง posType: record.posType == null ? null : record.posType.posTypeName, //ประเภท posExecutive: - position == null || position.posExecutive == null + position == null || position.posExecutive == null || posMaster?.isSit ? null : position.posExecutive.posExecutiveName, //ตำแหน่งทางการบริหาร - positionArea: position == null ? null : position.positionArea, //ด้าน/สาขา - positionExecutiveField: position == null ? null : position.positionExecutiveField, //ด้านทางการบริหาร + positionArea: position == null || posMaster?.isSit ? null : position.positionArea, //ด้าน/สาขา + positionExecutiveField: position == null || posMaster?.isSit ? null : position.positionExecutiveField, //ด้านทางการบริหาร dateLeave: record.birthDate == null ? null : calculateRetireDate(record.birthDate), dateRetireLaw: record.dateRetireLaw ?? null, // govAge: record.dateStart == null ? null : calculateAge(record.dateStart), govAge: await calculateGovAge(profile.id, "OFFICER"), + govAgeBkk: await calculateGovAge(profile.id, "OFFICER", true), dateAppoint: record.dateAppoint, dateStart: record.dateStart, govAgeAbsent: record.govAgeAbsent, @@ -286,9 +288,10 @@ export class ProfileGovernmentHistoryController extends Controller { } } const orgLeave = _OrgLeave.filter((x: any) => x !== undefined && x !== null).join("\n"); + //posMaster?.isSit แก้ไขชั่วคราว const data = { org: record?.isLeave == false ? org : orgLeave, //สังกัด - positionField: position == null ? null : position.positionField, //สายงาน + positionField: position == null || posMaster?.isSit ? null : position.positionField, //สายงาน position: record?.position, //ตำแหน่ง posLevel: record?.posLevel == null ? null : record?.posLevel.posLevelName, //ระดับ posMasterNo: @@ -301,15 +304,16 @@ export class ProfileGovernmentHistoryController extends Controller { : null, //เลขที่ตำแหน่ง posType: record?.posType == null ? null : record?.posType.posTypeName, //ประเภท posExecutive: - position == null || position.posExecutive == null + position == null || position.posExecutive == null || posMaster?.isSit ? null : position.posExecutive.posExecutiveName, //ตำแหน่งทางการบริหาร - positionArea: position == null ? null : position.positionArea, //ด้าน/สาขา - positionExecutiveField: position == null ? null : position.positionExecutiveField, //ด้านทางการบริหาร + positionArea: position == null || posMaster?.isSit ? null : position.positionArea, //ด้าน/สาขา + positionExecutiveField: position == null || posMaster?.isSit ? null : position.positionExecutiveField, //ด้านทางการบริหาร dateLeave: record?.birthDate == null ? null : calculateRetireDate(record?.birthDate), dateRetireLaw: record?.dateRetireLaw ?? null, // govAge: record?.dateStart == null ? null : calculateAge(record?.dateStart), govAge: await calculateGovAge(profileId, "OFFICER"), + govAgeBkk: await calculateGovAge(profileId, "OFFICER", true), dateAppoint: record?.dateAppoint, dateStart: record?.dateStart, govAgeAbsent: record?.govAgeAbsent, @@ -459,9 +463,10 @@ export class ProfileGovernmentHistoryController extends Controller { } } const orgLeave = _OrgLeave.filter((x: any) => x !== undefined && x !== null).join("\n"); + //posMaster?.isSit แก้ไขชั่วคราว const data = { org: record?.isLeave == false ? org : orgLeave, //สังกัด - positionField: position == null ? null : position.positionField, //สายงาน + positionField: position == null || posMaster?.isSit ? null : position.positionField, //สายงาน position: record?.position, //ตำแหน่ง posLevel: record?.posLevel == null ? null : record?.posLevel.posLevelName, //ระดับ posMasterNo: @@ -474,15 +479,16 @@ export class ProfileGovernmentHistoryController extends Controller { : null, //เลขที่ตำแหน่ง posType: record?.posType == null ? null : record?.posType.posTypeName, //ประเภท posExecutive: - position == null || position.posExecutive == null + position == null || position.posExecutive == null || posMaster?.isSit ? null : position.posExecutive.posExecutiveName, //ตำแหน่งทางการบริหาร - positionArea: position == null ? null : position.positionArea, //ด้าน/สาขา - positionExecutiveField: position == null ? null : position.positionExecutiveField, //ด้านทางการบริหาร + positionArea: position == null || posMaster?.isSit ? null : position.positionArea, //ด้าน/สาขา + positionExecutiveField: position == null || posMaster?.isSit ? null : position.positionExecutiveField, //ด้านทางการบริหาร dateLeave: record?.birthDate == null ? null : calculateRetireDate(record?.birthDate), dateRetireLaw: record?.dateRetireLaw ?? null, // govAge: record?.dateStart == null ? null : calculateAge(record?.dateStart), govAge: await calculateGovAge(profileId, "OFFICER"), + govAgeBkk: await calculateGovAge(profileId, "OFFICER", true), dateAppoint: record?.dateAppoint, dateStart: record?.dateStart, govAgeAbsent: record?.govAgeAbsent, diff --git a/src/controllers/ProfileGovernmentEmployeeController.ts b/src/controllers/ProfileGovernmentEmployeeController.ts index c44b3583..6709e5db 100644 --- a/src/controllers/ProfileGovernmentEmployeeController.ts +++ b/src/controllers/ProfileGovernmentEmployeeController.ts @@ -121,6 +121,7 @@ export class ProfileGovernmentEmployeeController extends Controller { dateRetireLaw: record.dateRetireLaw ?? null, // govAge: record.dateStart == null ? null : calculateAge(record.dateStart), govAge: await calculateGovAge(profile.id, "EMPLOYEE"), + govAgeBkk: await calculateGovAge(profile.id, "EMPLOYEE", true), dateAppoint: record.dateAppoint, dateStart: record.dateStart, govAgeAbsent: record.govAgeAbsent, @@ -292,6 +293,7 @@ export class ProfileGovernmentEmployeeController extends Controller { dateRetire: record?.dateRetire ?? null, //วันครบเกษียณอายุ // govAge: record?.dateStart == null ? null : calculateAge(record?.dateStart), //อายุราชการ govAge: await calculateGovAge(profileEmployeeId, "EMPLOYEE"), + govAgeBkk: await calculateGovAge(profileEmployeeId, "EMPLOYEE", true), govAgeAbsent: record?.govAgeAbsent ?? null, // ขาดราชการ govAgePlus: record?.govAgePlus, // อายุราชการเกื้อกูล dateRetireLaw: record?.dateRetireLaw ?? null, // วันที่เกษียฯอายุราชการตามกฎหมาย @@ -451,6 +453,7 @@ export class ProfileGovernmentEmployeeController extends Controller { dateRetire: record?.dateRetire ?? null, //วันครบเกษียณอายุ // govAge: record?.dateStart == null ? null : calculateAge(record?.dateStart), //อายุราชการ govAge: await calculateGovAge(profileEmployeeId, "EMPLOYEE"), + govAgeBkk: await calculateGovAge(profileEmployeeId, "EMPLOYEE", true), govAgeAbsent: record?.govAgeAbsent ?? null, // ขาดราชการ govAgePlus: record?.govAgePlus, // อายุราชการเกื้อกูล dateRetireLaw: record?.dateRetireLaw ?? null, // วันที่เกษียฯอายุราชการตามกฎหมาย diff --git a/src/controllers/ProfileHonorController.ts b/src/controllers/ProfileHonorController.ts index 114c76b3..1eb6cba4 100644 --- a/src/controllers/ProfileHonorController.ts +++ b/src/controllers/ProfileHonorController.ts @@ -36,7 +36,7 @@ export class ProfileHonorController extends Controller { throw new HttpError(HttpStatus.BAD_REQUEST, "ไม่พบ profile ดังกล่าว"); } const record = await this.honorRepo.find({ - where: { profileId: profile.id }, + where: { profileId: profile.id, isDeleted: false }, order: { createdAt: "ASC" }, }); return new HttpSuccess(record); @@ -48,7 +48,7 @@ export class ProfileHonorController extends Controller { if (_workflow == false) await new permission().PermissionOrgUserGet(req, "SYS_REGISTRY_OFFICER", profileId); const record = await this.honorRepo.find({ - where: { profileId: profileId }, + where: { profileId: profileId, isDeleted: false }, order: { createdAt: "ASC" }, }); return new HttpSuccess(record); @@ -176,6 +176,45 @@ export class ProfileHonorController extends Controller { return new HttpSuccess(); } + /** + * API ลบข้อมูลประกาศเกียรติคุณ + * @summary API ลบข้อมูลประกาศเกียรติคุณ + * @param honorId คีย์ประกาศเกียรติคุณ + */ + @Patch("update-delete/{honorId}") + public async updateIsDeleted( + @Request() req: RequestWithUser, + @Path() honorId: string, + ) { + const record = await this.honorRepo.findOneBy({ id: honorId }); + if (!record) throw new HttpError(HttpStatus.NOT_FOUND, "ไม่พบข้อมูล"); + if (record.isDeleted === true) { + return new HttpSuccess(); + } + await new permission().PermissionOrgUserDelete(req, "SYS_REGISTRY_OFFICER", record.profileId); + const before = structuredClone(record); + const history = new ProfileHonorHistory(); + const now = new Date(); + record.isDeleted = true; + record.lastUpdateUserId = req.user.sub; + record.lastUpdateFullName = req.user.name; + record.lastUpdatedAt = now; + + Object.assign(history, { ...record, id: undefined }); + history.profileHonorId = honorId; + history.createdUserId = req.user.sub; + history.createdFullName = req.user.name; + history.createdAt = now; + + await Promise.all([ + this.honorRepo.save(record, { data: req }), + setLogDataDiff(req, { before, after: record }), + this.honorHistoryRepo.save(history, { data: req }), + ]); + + return new HttpSuccess(); + } + @Delete("{honorId}") public async deleteTraning(@Path() honorId: string, @Request() req: RequestWithUser) { const _record = await this.honorRepo.findOneBy({ id: honorId }); diff --git a/src/controllers/ProfileHonorEmployeeController.ts b/src/controllers/ProfileHonorEmployeeController.ts index 11e6ddf8..5ab8df44 100644 --- a/src/controllers/ProfileHonorEmployeeController.ts +++ b/src/controllers/ProfileHonorEmployeeController.ts @@ -40,7 +40,7 @@ export class ProfileHonorEmployeeController extends Controller { throw new HttpError(HttpStatus.BAD_REQUEST, "ไม่พบ profile ดังกล่าว"); } const record = await this.honorRepo.find({ - where: { profileEmployeeId: profile.id }, + where: { profileEmployeeId: profile.id, isDeleted: false }, order: { createdAt: "ASC" }, }); return new HttpSuccess(record); @@ -52,7 +52,7 @@ export class ProfileHonorEmployeeController extends Controller { if (_workflow == false) await new permission().PermissionOrgUserGet(req, "SYS_REGISTRY_EMP", profileEmployeeId); const record = await this.honorRepo.find({ - where: { profileEmployeeId: profileEmployeeId }, + where: { profileEmployeeId: profileEmployeeId, isDeleted: false }, order: { createdAt: "ASC" }, }); return new HttpSuccess(record); @@ -188,6 +188,45 @@ export class ProfileHonorEmployeeController extends Controller { return new HttpSuccess(); } + /** + * API ลบข้อมูลประกาศเกียรติคุณ + * @summary API ลบข้อมูลประกาศเกียรติคุณ + * @param honorId คีย์ประกาศเกียรติคุณ + */ + @Patch("update-delete/{honorId}") + public async updateIsDeleted( + @Request() req: RequestWithUser, + @Path() honorId: string, + ) { + const record = await this.honorRepo.findOneBy({ id: honorId }); + if (!record) throw new HttpError(HttpStatus.NOT_FOUND, "ไม่พบข้อมูล"); + if (record.isDeleted === true) { + return new HttpSuccess(); + } + await new permission().PermissionOrgUserDelete(req, "SYS_REGISTRY_EMP", record.profileEmployeeId); + const before = structuredClone(record); + const history = new ProfileHonorHistory(); + const now = new Date(); + record.isDeleted = true; + record.lastUpdateUserId = req.user.sub; + record.lastUpdateFullName = req.user.name; + record.lastUpdatedAt = now; + + Object.assign(history, { ...record, id: undefined }); + history.profileHonorId = honorId; + history.createdUserId = req.user.sub; + history.createdFullName = req.user.name; + history.createdAt = now; + + await Promise.all([ + this.honorRepo.save(record, { data: req }), + setLogDataDiff(req, { before, after: record }), + this.honorHistoryRepo.save(history, { data: req }), + ]); + + return new HttpSuccess(); + } + @Delete("{honorId}") public async deleteTraning(@Path() honorId: string, @Request() req: RequestWithUser) { const _record = await this.honorRepo.findOneBy({ id: honorId }); diff --git a/src/controllers/ProfileHonorEmployeeTempController.ts b/src/controllers/ProfileHonorEmployeeTempController.ts index e163aa05..eeba94e8 100644 --- a/src/controllers/ProfileHonorEmployeeTempController.ts +++ b/src/controllers/ProfileHonorEmployeeTempController.ts @@ -40,7 +40,7 @@ export class ProfileHonorEmployeeTempController extends Controller { throw new HttpError(HttpStatus.BAD_REQUEST, "ไม่พบ profile ดังกล่าว"); } const record = await this.honorRepo.find({ - where: { profileEmployeeId: profile.id }, + where: { profileEmployeeId: profile.id, isDeleted: false }, order: { createdAt: "ASC" }, }); return new HttpSuccess(record); @@ -51,7 +51,7 @@ export class ProfileHonorEmployeeTempController extends Controller { let _workflow = await new permission().Workflow(req, profileEmployeeId, "SYS_REGISTRY_TEMP"); if (_workflow == false) await new permission().PermissionGet(req, "SYS_REGISTRY_TEMP"); const record = await this.honorRepo.find({ - where: { profileEmployeeId: profileEmployeeId }, + where: { profileEmployeeId: profileEmployeeId, isDeleted: false }, order: { createdAt: "ASC" }, }); return new HttpSuccess(record); @@ -176,6 +176,45 @@ export class ProfileHonorEmployeeTempController extends Controller { return new HttpSuccess(); } + /** + * API ลบข้อมูลประกาศเกียรติคุณ + * @summary API ลบข้อมูลประกาศเกียรติคุณ + * @param honorId คีย์ประกาศเกียรติคุณ + */ + @Patch("update-delete/{honorId}") + public async updateIsDeleted( + @Request() req: RequestWithUser, + @Path() honorId: string, + ) { + const record = await this.honorRepo.findOneBy({ id: honorId }); + if (!record) throw new HttpError(HttpStatus.NOT_FOUND, "ไม่พบข้อมูล"); + if (record.isDeleted === true) { + return new HttpSuccess(); + } + await new permission().PermissionDelete(req, "SYS_REGISTRY_TEMP"); + const before = structuredClone(record); + const history = new ProfileHonorHistory(); + const now = new Date(); + record.isDeleted = true; + record.lastUpdateUserId = req.user.sub; + record.lastUpdateFullName = req.user.name; + record.lastUpdatedAt = now; + + Object.assign(history, { ...record, id: undefined }); + history.profileHonorId = honorId; + history.createdUserId = req.user.sub; + history.createdFullName = req.user.name; + history.createdAt = now; + + await Promise.all([ + this.honorRepo.save(record, { data: req }), + setLogDataDiff(req, { before, after: record }), + this.honorHistoryRepo.save(history, { data: req }), + ]); + + return new HttpSuccess(); + } + @Delete("{honorId}") public async deleteTraning(@Path() honorId: string, @Request() req: RequestWithUser) { await new permission().PermissionDelete(req, "SYS_REGISTRY_TEMP"); diff --git a/src/controllers/ProfileInsigniaController.ts b/src/controllers/ProfileInsigniaController.ts index 47e267f4..f4f5f2b4 100644 --- a/src/controllers/ProfileInsigniaController.ts +++ b/src/controllers/ProfileInsigniaController.ts @@ -50,7 +50,7 @@ export class ProfileInsigniaController extends Controller { insigniaType: true, }, }, - where: { profileId: profile.id }, + where: { profileId: profile.id, isDeleted: false }, order: { createdAt: "ASC" }, }); return new HttpSuccess(record); @@ -67,7 +67,7 @@ export class ProfileInsigniaController extends Controller { insigniaType: true, }, }, - where: { profileId }, + where: { profileId, isDeleted: false }, order: { createdAt: "ASC" }, }); return new HttpSuccess(record); @@ -199,6 +199,45 @@ export class ProfileInsigniaController extends Controller { return new HttpSuccess(); } + /** + * API ลบข้อมูลเครื่องราชอิสริยาภรณ์ + * @summary API ลบข้อมูลเครื่องราชอิสริยาภรณ์ + * @param insigniaId คีย์เครื่องราชอิสริยาภรณ์ + */ + @Patch("update-delete/{insigniaId}") + public async updateIsDeleted( + @Request() req: RequestWithUser, + @Path() insigniaId: string, + ) { + const record = await this.insigniaRepo.findOneBy({ id: insigniaId }); + if (!record) throw new HttpError(HttpStatus.NOT_FOUND, "ไม่พบข้อมูล"); + if (record.isDeleted === true) { + return new HttpSuccess(); + } + await new permission().PermissionOrgUserDelete(req, "SYS_REGISTRY_OFFICER", record.profileId); + const before = structuredClone(record); + const history = new ProfileInsigniaHistory(); + const now = new Date(); + record.isDeleted = true; + record.lastUpdateUserId = req.user.sub; + record.lastUpdateFullName = req.user.name; + record.lastUpdatedAt = now; + + Object.assign(history, { ...record, id: undefined }); + history.profileInsigniaId = insigniaId; + history.createdUserId = req.user.sub; + history.createdFullName = req.user.name; + history.createdAt = now; + + await Promise.all([ + this.insigniaRepo.save(record, { data: req }), + setLogDataDiff(req, { before, after: record }), + this.insigniaHistoryRepo.save(history, { data: req }), + ]); + + return new HttpSuccess(); + } + @Delete("{insigniaId}") public async deleteInsignia(@Path() insigniaId: string, @Request() req: RequestWithUser) { const _record = await this.insigniaRepo.findOneBy({ id: insigniaId }); diff --git a/src/controllers/ProfileInsigniaEmployeeController.ts b/src/controllers/ProfileInsigniaEmployeeController.ts index 1921a5fd..7e9d11b2 100644 --- a/src/controllers/ProfileInsigniaEmployeeController.ts +++ b/src/controllers/ProfileInsigniaEmployeeController.ts @@ -47,7 +47,7 @@ export class ProfileInsigniaEmployeeController extends Controller { insigniaType: true, }, }, - where: { profileEmployeeId: profile.id }, + where: { profileEmployeeId: profile.id, isDeleted: false }, order: { createdAt: "ASC" }, }); return new HttpSuccess(record); @@ -64,7 +64,7 @@ export class ProfileInsigniaEmployeeController extends Controller { insigniaType: true, }, }, - where: { profileEmployeeId }, + where: { profileEmployeeId, isDeleted: false }, order: { createdAt: "ASC" }, }); return new HttpSuccess(record); @@ -207,6 +207,45 @@ export class ProfileInsigniaEmployeeController extends Controller { return new HttpSuccess(); } + /** + * API ลบข้อมูลเครื่องราชอิสริยาภรณ์ + * @summary API ลบข้อมูลเครื่องราชอิสริยาภรณ์ + * @param insigniaId คีย์เครื่องราชอิสริยาภรณ์ + */ + @Patch("update-delete/{insigniaId}") + public async updateIsDeleted( + @Request() req: RequestWithUser, + @Path() insigniaId: string, + ) { + const record = await this.insigniaRepo.findOneBy({ id: insigniaId }); + if (!record) throw new HttpError(HttpStatus.NOT_FOUND, "ไม่พบข้อมูล"); + if (record.isDeleted === true) { + return new HttpSuccess(); + } + await new permission().PermissionOrgUserDelete(req, "SYS_REGISTRY_EMP", record.profileEmployeeId); + const before = structuredClone(record); + const history = new ProfileInsigniaHistory(); + const now = new Date(); + record.isDeleted = true; + record.lastUpdateUserId = req.user.sub; + record.lastUpdateFullName = req.user.name; + record.lastUpdatedAt = now; + + Object.assign(history, { ...record, id: undefined }); + history.profileInsigniaId = insigniaId; + history.createdUserId = req.user.sub; + history.createdFullName = req.user.name; + history.createdAt = now; + + await Promise.all([ + this.insigniaRepo.save(record, { data: req }), + setLogDataDiff(req, { before, after: record }), + this.insigniaHistoryRepo.save(history, { data: req }), + ]); + + return new HttpSuccess(); + } + @Delete("{insigniaId}") public async deleteInsignia(@Path() insigniaId: string, @Request() req: RequestWithUser) { const _record = await this.insigniaRepo.findOneBy({ id: insigniaId }); diff --git a/src/controllers/ProfileInsigniaEmployeeTempController.ts b/src/controllers/ProfileInsigniaEmployeeTempController.ts index c0f3ef71..56bf0c34 100644 --- a/src/controllers/ProfileInsigniaEmployeeTempController.ts +++ b/src/controllers/ProfileInsigniaEmployeeTempController.ts @@ -47,7 +47,7 @@ export class ProfileInsigniaEmployeeTempController extends Controller { insigniaType: true, }, }, - where: { profileEmployeeId: profile.id }, + where: { profileEmployeeId: profile.id, isDeleted: false }, order: { createdAt: "ASC" }, }); return new HttpSuccess(record); @@ -63,7 +63,7 @@ export class ProfileInsigniaEmployeeTempController extends Controller { insigniaType: true, }, }, - where: { profileEmployeeId }, + where: { profileEmployeeId, isDeleted: false }, order: { createdAt: "ASC" }, }); return new HttpSuccess(record); @@ -196,6 +196,45 @@ export class ProfileInsigniaEmployeeTempController extends Controller { return new HttpSuccess(); } + /** + * API ลบข้อมูลเครื่องราชอิสริยาภรณ์ + * @summary API ลบข้อมูลเครื่องราชอิสริยาภรณ์ + * @param insigniaId คีย์เครื่องราชอิสริยาภรณ์ + */ + @Patch("update-delete/{insigniaId}") + public async updateIsDeleted( + @Request() req: RequestWithUser, + @Path() insigniaId: string, + ) { + const record = await this.insigniaRepo.findOneBy({ id: insigniaId }); + if (!record) throw new HttpError(HttpStatus.NOT_FOUND, "ไม่พบข้อมูล"); + if (record.isDeleted === true) { + return new HttpSuccess(); + } + await new permission().PermissionDelete(req, "SYS_REGISTRY_TEMP"); + const before = structuredClone(record); + const history = new ProfileInsigniaHistory(); + const now = new Date(); + record.isDeleted = true; + record.lastUpdateUserId = req.user.sub; + record.lastUpdateFullName = req.user.name; + record.lastUpdatedAt = now; + + Object.assign(history, { ...record, id: undefined }); + history.profileInsigniaId = insigniaId; + history.createdUserId = req.user.sub; + history.createdFullName = req.user.name; + history.createdAt = now; + + await Promise.all([ + this.insigniaRepo.save(record, { data: req }), + setLogDataDiff(req, { before, after: record }), + this.insigniaHistoryRepo.save(history, { data: req }), + ]); + + return new HttpSuccess(); + } + @Delete("{insigniaId}") public async deleteInsignia(@Path() insigniaId: string, @Request() req: RequestWithUser) { await new permission().PermissionDelete(req, "SYS_REGISTRY_TEMP"); diff --git a/src/controllers/ProfileLeaveController.ts b/src/controllers/ProfileLeaveController.ts index f5601c02..c61b3d95 100644 --- a/src/controllers/ProfileLeaveController.ts +++ b/src/controllers/ProfileLeaveController.ts @@ -120,7 +120,7 @@ export class ProfileLeaveController extends Controller { } const record = await this.leaveRepo.find({ relations: { leaveType: true }, - where: { profileId: profile.id }, + where: { profileId: profile.id, isDeleted: false }, order: { createdAt: "ASC" }, }); return new HttpSuccess(record); @@ -136,6 +136,7 @@ export class ProfileLeaveController extends Controller { where: { profileId: profileId, // status: Not("cancel") + isDeleted: false }, order: { createdAt: "ASC" }, }); @@ -148,7 +149,7 @@ export class ProfileLeaveController extends Controller { if (_workflow == false) await new permission().PermissionGet(req, "SYS_SALARY_OFFICER"); const record = await this.leaveRepo.find({ relations: { leaveType: true }, - where: { profileId }, + where: { profileId, isDeleted: false }, order: { createdAt: "ASC" }, }); return new HttpSuccess(record); @@ -264,6 +265,45 @@ export class ProfileLeaveController extends Controller { return new HttpSuccess(); } + /** + * API ลบข้อมูลการลา + * @summary API ลบข้อมูลการลา + * @param leaveId คีย์การลา + */ + @Patch("update-delete/{leaveId}") + public async updateIsDeleted( + @Request() req: RequestWithUser, + @Path() leaveId: string, + ) { + const record = await this.leaveRepo.findOneBy({ id: leaveId }); + if (!record) throw new HttpError(HttpStatus.NOT_FOUND, "ไม่พบข้อมูล"); + if (record.isDeleted === true) { + return new HttpSuccess(); + } + await new permission().PermissionOrgUserDelete(req, "SYS_REGISTRY_OFFICER", record.profileId); + const before = structuredClone(record); + const history = new ProfileLeaveHistory(); + const now = new Date(); + record.isDeleted = true; + record.lastUpdateUserId = req.user.sub; + record.lastUpdateFullName = req.user.name; + record.lastUpdatedAt = now; + + Object.assign(history, { ...record, id: undefined }); + history.profileLeaveId = leaveId; + history.createdUserId = req.user.sub; + history.createdFullName = req.user.name; + history.createdAt = now; + + await Promise.all([ + this.leaveRepo.save(record, { data: req }), + setLogDataDiff(req, { before, after: record }), + this.leaveHistoryRepo.save(history, { data: req }), + ]); + + return new HttpSuccess(); + } + @Patch("cancel/{leaveId}") public async updateCancel( @Request() req: RequestWithUser, diff --git a/src/controllers/ProfileLeaveEmployeeController.ts b/src/controllers/ProfileLeaveEmployeeController.ts index feb6891e..9f928776 100644 --- a/src/controllers/ProfileLeaveEmployeeController.ts +++ b/src/controllers/ProfileLeaveEmployeeController.ts @@ -43,7 +43,7 @@ export class ProfileLeaveEmployeeController extends Controller { } const record = await this.leaveRepo.find({ relations: { leaveType: true }, - where: { profileEmployeeId: profile.id }, + where: { profileEmployeeId: profile.id, isDeleted: false }, order: { createdAt: "ASC" }, }); return new HttpSuccess(record); @@ -56,7 +56,7 @@ export class ProfileLeaveEmployeeController extends Controller { await new permission().PermissionOrgUserGet(req, "SYS_REGISTRY_EMP", profileId); const record = await this.leaveRepo.find({ relations: { leaveType: true }, - where: { profileEmployeeId: profileId }, + where: { profileEmployeeId: profileId, isDeleted: false }, order: { createdAt: "ASC" }, }); return new HttpSuccess(record); @@ -68,7 +68,7 @@ export class ProfileLeaveEmployeeController extends Controller { if (_workflow == false) await new permission().PermissionGet(req, "SYS_WAGE"); const record = await this.leaveRepo.find({ relations: { leaveType: true }, - where: { profileEmployeeId: profileId }, + where: { profileEmployeeId: profileId, isDeleted: false }, order: { createdAt: "ASC" }, }); return new HttpSuccess(record); @@ -192,6 +192,45 @@ export class ProfileLeaveEmployeeController extends Controller { return new HttpSuccess(); } + /** + * API ลบข้อมูลการลา + * @summary API ลบข้อมูลการลา + * @param leaveId คีย์การลา + */ + @Patch("update-delete/{leaveId}") + public async updateIsDeleted( + @Request() req: RequestWithUser, + @Path() leaveId: string, + ) { + const record = await this.leaveRepo.findOneBy({ id: leaveId }); + if (!record) throw new HttpError(HttpStatus.NOT_FOUND, "ไม่พบข้อมูล"); + if (record.isDeleted === true) { + return new HttpSuccess(); + } + await new permission().PermissionOrgUserDelete(req, "SYS_REGISTRY_EMP", record.profileEmployeeId); + const before = structuredClone(record); + const history = new ProfileLeaveHistory(); + const now = new Date(); + record.isDeleted = true; + record.lastUpdateUserId = req.user.sub; + record.lastUpdateFullName = req.user.name; + record.lastUpdatedAt = now; + + Object.assign(history, { ...record, id: undefined }); + history.profileLeaveId = leaveId; + history.createdUserId = req.user.sub; + history.createdFullName = req.user.name; + history.createdAt = now; + + await Promise.all([ + this.leaveRepo.save(record, { data: req }), + setLogDataDiff(req, { before, after: record }), + this.leaveHistoryRepo.save(history, { data: req }), + ]); + + return new HttpSuccess(); + } + @Delete("{leaveId}") public async deleteLeave(@Path() leaveId: string, @Request() req: RequestWithUser) { const _record = await this.leaveRepo.findOneBy({ id: leaveId }); diff --git a/src/controllers/ProfileLeaveEmployeeTempController.ts b/src/controllers/ProfileLeaveEmployeeTempController.ts index 39506d32..a8aa979f 100644 --- a/src/controllers/ProfileLeaveEmployeeTempController.ts +++ b/src/controllers/ProfileLeaveEmployeeTempController.ts @@ -43,7 +43,7 @@ export class ProfileLeaveEmployeeTempController extends Controller { } const record = await this.leaveRepo.find({ relations: { leaveType: true }, - where: { profileEmployeeId: profile.id }, + where: { profileEmployeeId: profile.id, isDeleted: false }, order: { createdAt: "ASC" }, }); return new HttpSuccess(record); @@ -55,7 +55,7 @@ export class ProfileLeaveEmployeeTempController extends Controller { if (_workflow == false) await new permission().PermissionGet(req, "SYS_REGISTRY_TEMP"); const record = await this.leaveRepo.find({ relations: { leaveType: true }, - where: { profileEmployeeId: profileId }, + where: { profileEmployeeId: profileId, isDeleted: false }, order: { createdAt: "ASC" }, }); return new HttpSuccess(record); @@ -67,7 +67,7 @@ export class ProfileLeaveEmployeeTempController extends Controller { if (_workflow == false) await new permission().PermissionGet(req, "SYS_WAGE"); const record = await this.leaveRepo.find({ relations: { leaveType: true }, - where: { profileEmployeeId: profileId }, + where: { profileEmployeeId: profileId, isDeleted: false }, order: { createdAt: "ASC" }, }); return new HttpSuccess(record); @@ -179,6 +179,45 @@ export class ProfileLeaveEmployeeTempController extends Controller { return new HttpSuccess(); } + /** + * API ลบข้อมูลการลา + * @summary API ลบข้อมูลการลา + * @param leaveId คีย์การลา + */ + @Patch("update-delete/{leaveId}") + public async updateIsDeleted( + @Request() req: RequestWithUser, + @Path() leaveId: string, + ) { + const record = await this.leaveRepo.findOneBy({ id: leaveId }); + if (!record) throw new HttpError(HttpStatus.NOT_FOUND, "ไม่พบข้อมูล"); + if (record.isDeleted === true) { + return new HttpSuccess(); + } + await new permission().PermissionDelete(req, "SYS_REGISTRY_TEMP"); + const before = structuredClone(record); + const history = new ProfileLeaveHistory(); + const now = new Date(); + record.isDeleted = true; + record.lastUpdateUserId = req.user.sub; + record.lastUpdateFullName = req.user.name; + record.lastUpdatedAt = now; + + Object.assign(history, { ...record, id: undefined }); + history.profileLeaveId = leaveId; + history.createdUserId = req.user.sub; + history.createdFullName = req.user.name; + history.createdAt = now; + + await Promise.all([ + this.leaveRepo.save(record, { data: req }), + setLogDataDiff(req, { before, after: record }), + this.leaveHistoryRepo.save(history, { data: req }), + ]); + + return new HttpSuccess(); + } + @Delete("{leaveId}") public async deleteLeave(@Path() leaveId: string, @Request() req: RequestWithUser) { await new permission().PermissionDelete(req, "SYS_REGISTRY_TEMP"); diff --git a/src/controllers/ProfileNopaidController.ts b/src/controllers/ProfileNopaidController.ts index 491aaae5..b9cecc1d 100644 --- a/src/controllers/ProfileNopaidController.ts +++ b/src/controllers/ProfileNopaidController.ts @@ -36,7 +36,7 @@ export class ProfileNopaidController extends Controller { throw new HttpError(HttpStatus.BAD_REQUEST, "ไม่พบ profile ดังกล่าว"); } const lists = await this.nopaidRepository.find({ - where: { profileId: profile.id }, + where: { profileId: profile.id, isDeleted: false }, order: { createdAt: "ASC" }, }); return new HttpSuccess(lists); @@ -48,7 +48,7 @@ export class ProfileNopaidController extends Controller { if (_workflow == false) await new permission().PermissionOrgUserGet(req, "SYS_REGISTRY_OFFICER", profileId); const lists = await this.nopaidRepository.find({ - where: { profileId }, + where: { profileId, isDeleted: false }, order: { createdAt: "ASC" }, }); return new HttpSuccess(lists); @@ -140,6 +140,45 @@ export class ProfileNopaidController extends Controller { return new HttpSuccess(); } + /** + * API ลบข้อมูลบันทึกวันที่ไม่ได้รับเงินเงินเดือนฯ + * @summary API ลบข้อมูลบันทึกวันที่ไม่ได้รับเงินเงินเดือนฯ + * @param nopaidId คีย์บันทึกวันที่ไม่ได้รับเงินเงินเดือนฯ + */ + @Patch("update-delete/{nopaidId}") + public async updateIsDeleted( + @Request() req: RequestWithUser, + @Path() nopaidId: string, + ) { + const record = await this.nopaidRepository.findOneBy({ id: nopaidId }); + if (!record) throw new HttpError(HttpStatus.NOT_FOUND, "ไม่พบข้อมูล"); + if (record.isDeleted === true) { + return new HttpSuccess(); + } + await new permission().PermissionOrgUserDelete(req, "SYS_REGISTRY_OFFICER", record.profileId); + const before = structuredClone(record); + const history = new ProfileNopaidHistory(); + const now = new Date(); + record.isDeleted = true; + record.lastUpdateUserId = req.user.sub; + record.lastUpdateFullName = req.user.name; + record.lastUpdatedAt = now; + + Object.assign(history, { ...record, id: undefined }); + history.profileNopaidId = nopaidId; + history.createdUserId = req.user.sub; + history.createdFullName = req.user.name; + history.createdAt = now; + + await Promise.all([ + this.nopaidRepository.save(record, { data: req }), + setLogDataDiff(req, { before, after: record }), + this.nopaidHistoryRepository.save(history, { data: req }), + ]); + + return new HttpSuccess(); + } + @Delete("{nopaidId}") public async deleteNopaid(@Path() nopaidId: string, @Request() req: RequestWithUser) { const _record = await this.nopaidRepository.findOneBy({ id: nopaidId }); diff --git a/src/controllers/ProfileNopaidEmployeeController.ts b/src/controllers/ProfileNopaidEmployeeController.ts index fd5b7353..5a10ee75 100644 --- a/src/controllers/ProfileNopaidEmployeeController.ts +++ b/src/controllers/ProfileNopaidEmployeeController.ts @@ -40,7 +40,7 @@ export class ProfileNopaidEmployeeController extends Controller { throw new HttpError(HttpStatus.BAD_REQUEST, "ไม่พบ profile ดังกล่าว"); } const lists = await this.nopaidRepository.find({ - where: { profileEmployeeId: profile.id }, + where: { profileEmployeeId: profile.id, isDeleted: false }, order: { createdAt: "ASC" }, }); return new HttpSuccess(lists); @@ -51,7 +51,7 @@ export class ProfileNopaidEmployeeController extends Controller { let _workflow = await new permission().Workflow(req, profileId, "SYS_REGISTRY_EMP"); if (_workflow == false) await new permission().PermissionGet(req, "SYS_REGISTRY_EMP"); const lists = await this.nopaidRepository.find({ - where: { profileEmployeeId: profileId }, + where: { profileEmployeeId: profileId, isDeleted: false }, order: { createdAt: "ASC" }, }); return new HttpSuccess(lists); @@ -147,6 +147,45 @@ export class ProfileNopaidEmployeeController extends Controller { return new HttpSuccess(); } + /** + * API ลบข้อมูลบันทึกวันที่ไม่ได้รับเงินเงินเดือนฯ + * @summary API ลบข้อมูลบันทึกวันที่ไม่ได้รับเงินเงินเดือนฯ + * @param nopaidId คีย์บันทึกวันที่ไม่ได้รับเงินเงินเดือนฯ + */ + @Patch("update-delete/{nopaidId}") + public async updateIsDeleted( + @Request() req: RequestWithUser, + @Path() nopaidId: string, + ) { + const record = await this.nopaidRepository.findOneBy({ id: nopaidId }); + if (!record) throw new HttpError(HttpStatus.NOT_FOUND, "ไม่พบข้อมูล"); + if (record.isDeleted === true) { + return new HttpSuccess(); + } + await new permission().PermissionOrgUserDelete(req, "SYS_REGISTRY_EMP", record.profileEmployeeId); + const before = structuredClone(record); + const history = new ProfileNopaidHistory(); + const now = new Date(); + record.isDeleted = true; + record.lastUpdateUserId = req.user.sub; + record.lastUpdateFullName = req.user.name; + record.lastUpdatedAt = now; + + Object.assign(history, { ...record, id: undefined }); + history.profileNopaidId = nopaidId; + history.createdUserId = req.user.sub; + history.createdFullName = req.user.name; + history.createdAt = now; + + await Promise.all([ + this.nopaidRepository.save(record, { data: req }), + setLogDataDiff(req, { before, after: record }), + this.nopaidHistoryRepository.save(history, { data: req }), + ]); + + return new HttpSuccess(); + } + @Delete("{nopaidId}") public async deleteNopaid(@Path() nopaidId: string, @Request() req: RequestWithUser) { const _record = await this.nopaidRepository.findOneBy({ id: nopaidId }); diff --git a/src/controllers/ProfileNopaidEmployeeTempController.ts b/src/controllers/ProfileNopaidEmployeeTempController.ts index 37dec407..ca9aa4a5 100644 --- a/src/controllers/ProfileNopaidEmployeeTempController.ts +++ b/src/controllers/ProfileNopaidEmployeeTempController.ts @@ -40,7 +40,7 @@ export class ProfileNopaidEmployeeTempController extends Controller { throw new HttpError(HttpStatus.BAD_REQUEST, "ไม่พบ profile ดังกล่าว"); } const lists = await this.nopaidRepository.find({ - where: { profileEmployeeId: profile.id }, + where: { profileEmployeeId: profile.id, isDeleted: false }, order: { createdAt: "ASC" }, }); return new HttpSuccess(lists); @@ -51,7 +51,7 @@ export class ProfileNopaidEmployeeTempController extends Controller { let _workflow = await new permission().Workflow(req, profileId, "SYS_REGISTRY_TEMP"); if (_workflow == false) await new permission().PermissionGet(req, "SYS_REGISTRY_TEMP"); const lists = await this.nopaidRepository.find({ - where: { profileEmployeeId: profileId }, + where: { profileEmployeeId: profileId, isDeleted: false }, order: { createdAt: "ASC" }, }); return new HttpSuccess(lists); @@ -144,6 +144,45 @@ export class ProfileNopaidEmployeeTempController extends Controller { return new HttpSuccess(); } + /** + * API ลบข้อมูลบันทึกวันที่ไม่ได้รับเงินเงินเดือนฯ + * @summary API ลบข้อมูลบันทึกวันที่ไม่ได้รับเงินเงินเดือนฯ + * @param nopaidId คีย์บันทึกวันที่ไม่ได้รับเงินเงินเดือนฯ + */ + @Patch("update-delete/{nopaidId}") + public async updateIsDeleted( + @Request() req: RequestWithUser, + @Path() nopaidId: string, + ) { + const record = await this.nopaidRepository.findOneBy({ id: nopaidId }); + if (!record) throw new HttpError(HttpStatus.NOT_FOUND, "ไม่พบข้อมูล"); + if (record.isDeleted === true) { + return new HttpSuccess(); + } + await new permission().PermissionDelete(req, "SYS_REGISTRY_TEMP"); + const before = structuredClone(record); + const history = new ProfileNopaidHistory(); + const now = new Date(); + record.isDeleted = true; + record.lastUpdateUserId = req.user.sub; + record.lastUpdateFullName = req.user.name; + record.lastUpdatedAt = now; + + Object.assign(history, { ...record, id: undefined }); + history.profileNopaidId = nopaidId; + history.createdUserId = req.user.sub; + history.createdFullName = req.user.name; + history.createdAt = now; + + await Promise.all([ + this.nopaidRepository.save(record, { data: req }), + setLogDataDiff(req, { before, after: record }), + this.nopaidHistoryRepository.save(history, { data: req }), + ]); + + return new HttpSuccess(); + } + @Delete("{nopaidId}") public async deleteNopaid(@Path() nopaidId: string, @Request() req: RequestWithUser) { await new permission().PermissionDelete(req, "SYS_REGISTRY_TEMP"); diff --git a/src/controllers/ProfileOtherController.ts b/src/controllers/ProfileOtherController.ts index 79ae8c8f..6f774c3b 100644 --- a/src/controllers/ProfileOtherController.ts +++ b/src/controllers/ProfileOtherController.ts @@ -37,7 +37,7 @@ export class ProfileOtherController extends Controller { throw new HttpError(HttpStatus.BAD_REQUEST, "ไม่พบ profile ดังกล่าว"); } const lists = await this.otherRepository.find({ - where: { profileId: profile.id }, + where: { profileId: profile.id, isDeleted: false }, order: { createdAt: "ASC" }, }); return new HttpSuccess(lists); @@ -49,7 +49,7 @@ export class ProfileOtherController extends Controller { if (_workflow == false) await new permission().PermissionOrgUserGet(req, "SYS_REGISTRY_OFFICER", profileId); const lists = await this.otherRepository.find({ - where: { profileId: profileId }, + where: { profileId: profileId, isDeleted: false }, order: { createdAt: "ASC" }, }); return new HttpSuccess(lists); @@ -149,6 +149,45 @@ export class ProfileOtherController extends Controller { return new HttpSuccess(); } + /** + * API ลบข้อมูลข้อมูลอื่นๆ + * @summary API ลบข้อมูลข้อมูลอื่นๆ + * @param otherId คีย์ข้อมูลอื่นๆ + */ + @Patch("update-delete/{otherId}") + public async updateIsDeleted( + @Request() req: RequestWithUser, + @Path() otherId: string, + ) { + const record = await this.otherRepository.findOneBy({ id: otherId }); + if (!record) throw new HttpError(HttpStatus.NOT_FOUND, "ไม่พบข้อมูล"); + if (record.isDeleted === true) { + return new HttpSuccess(); + } + await new permission().PermissionOrgUserDelete(req, "SYS_REGISTRY_OFFICER", record.profileId); + const before = structuredClone(record); + const history = new ProfileOtherHistory(); + const now = new Date(); + record.isDeleted = true; + record.lastUpdateUserId = req.user.sub; + record.lastUpdateFullName = req.user.name; + record.lastUpdatedAt = now; + + Object.assign(history, { ...record, id: undefined }); + history.profileOtherId = otherId; + history.createdUserId = req.user.sub; + history.createdFullName = req.user.name; + history.createdAt = now; + + await Promise.all([ + this.otherRepository.save(record, { data: req }), + setLogDataDiff(req, { before, after: record }), + this.otherHistoryRepository.save(history, { data: req }), + ]); + + return new HttpSuccess(); + } + @Delete("{otherId}") public async deleteOther(@Path() otherId: string, @Request() req: RequestWithUser) { const _record = await this.otherRepository.findOneBy({ id: otherId }); diff --git a/src/controllers/ProfileOtherEmployeeController.ts b/src/controllers/ProfileOtherEmployeeController.ts index 0a6622b6..fe1adf94 100644 --- a/src/controllers/ProfileOtherEmployeeController.ts +++ b/src/controllers/ProfileOtherEmployeeController.ts @@ -40,7 +40,7 @@ export class ProfileOtherEmployeeController extends Controller { throw new HttpError(HttpStatus.BAD_REQUEST, "ไม่พบ profile ดังกล่าว"); } const lists = await this.otherRepository.find({ - where: { profileEmployeeId: profile.id }, + where: { profileEmployeeId: profile.id, isDeleted: false }, order: { createdAt: "ASC" }, }); return new HttpSuccess(lists); @@ -52,7 +52,7 @@ export class ProfileOtherEmployeeController extends Controller { if (_workflow == false) await new permission().PermissionOrgUserGet(req, "SYS_REGISTRY_EMP", profileId); const lists = await this.otherRepository.find({ - where: { profileEmployeeId: profileId }, + where: { profileEmployeeId: profileId, isDeleted: false }, order: { createdAt: "ASC" }, }); return new HttpSuccess(lists); @@ -160,6 +160,45 @@ export class ProfileOtherEmployeeController extends Controller { return new HttpSuccess(); } + /** + * API ลบข้อมูลข้อมูลอื่นๆ + * @summary API ลบข้อมูลข้อมูลอื่นๆ + * @param otherId คีย์ข้อมูลอื่นๆ + */ + @Patch("update-delete/{otherId}") + public async updateIsDeleted( + @Request() req: RequestWithUser, + @Path() otherId: string, + ) { + const record = await this.otherRepository.findOneBy({ id: otherId }); + if (!record) throw new HttpError(HttpStatus.NOT_FOUND, "ไม่พบข้อมูล"); + if (record.isDeleted === true) { + return new HttpSuccess(); + } + await new permission().PermissionOrgUserDelete(req, "SYS_REGISTRY_EMP", record.profileEmployeeId); + const before = structuredClone(record); + const history = new ProfileOtherHistory(); + const now = new Date(); + record.isDeleted = true; + record.lastUpdateUserId = req.user.sub; + record.lastUpdateFullName = req.user.name; + record.lastUpdatedAt = now; + + Object.assign(history, { ...record, id: undefined }); + history.profileOtherId = otherId; + history.createdUserId = req.user.sub; + history.createdFullName = req.user.name; + history.createdAt = now; + + await Promise.all([ + this.otherRepository.save(record, { data: req }), + setLogDataDiff(req, { before, after: record }), + this.otherHistoryRepository.save(history, { data: req }), + ]); + + return new HttpSuccess(); + } + @Delete("{otherId}") public async deleteOther(@Path() otherId: string, @Request() req: RequestWithUser) { const _record = await this.otherRepository.findOneBy({ id: otherId }); diff --git a/src/controllers/ProfileOtherEmployeeTempController.ts b/src/controllers/ProfileOtherEmployeeTempController.ts index 38962384..7f672ce1 100644 --- a/src/controllers/ProfileOtherEmployeeTempController.ts +++ b/src/controllers/ProfileOtherEmployeeTempController.ts @@ -40,7 +40,7 @@ export class ProfileOtherEmployeeTempController extends Controller { throw new HttpError(HttpStatus.BAD_REQUEST, "ไม่พบ profile ดังกล่าว"); } const lists = await this.otherRepository.find({ - where: { profileEmployeeId: profile.id }, + where: { profileEmployeeId: profile.id, isDeleted: false }, order: { createdAt: "ASC" }, }); return new HttpSuccess(lists); @@ -51,7 +51,7 @@ export class ProfileOtherEmployeeTempController extends Controller { let _workflow = await new permission().Workflow(req, profileId, "SYS_REGISTRY_TEMP"); if (_workflow == false) await new permission().PermissionGet(req, "SYS_REGISTRY_TEMP"); const lists = await this.otherRepository.find({ - where: { profileEmployeeId: profileId }, + where: { profileEmployeeId: profileId, isDeleted: false }, order: { createdAt: "ASC" }, }); return new HttpSuccess(lists); @@ -148,6 +148,45 @@ export class ProfileOtherEmployeeTempController extends Controller { return new HttpSuccess(); } + /** + * API ลบข้อมูลข้อมูลอื่นๆ + * @summary API ลบข้อมูลข้อมูลอื่นๆ + * @param otherId คีย์ข้อมูลอื่นๆ + */ + @Patch("update-delete/{otherId}") + public async updateIsDeleted( + @Request() req: RequestWithUser, + @Path() otherId: string, + ) { + const record = await this.otherRepository.findOneBy({ id: otherId }); + if (!record) throw new HttpError(HttpStatus.NOT_FOUND, "ไม่พบข้อมูล"); + if (record.isDeleted === true) { + return new HttpSuccess(); + } + await new permission().PermissionDelete(req, "SYS_REGISTRY_TEMP"); + const before = structuredClone(record); + const history = new ProfileOtherHistory(); + const now = new Date(); + record.isDeleted = true; + record.lastUpdateUserId = req.user.sub; + record.lastUpdateFullName = req.user.name; + record.lastUpdatedAt = now; + + Object.assign(history, { ...record, id: undefined }); + history.profileOtherId = otherId; + history.createdUserId = req.user.sub; + history.createdFullName = req.user.name; + history.createdAt = now; + + await Promise.all([ + this.otherRepository.save(record, { data: req }), + setLogDataDiff(req, { before, after: record }), + this.otherHistoryRepository.save(history, { data: req }), + ]); + + return new HttpSuccess(); + } + @Delete("{otherId}") public async deleteOther(@Path() otherId: string, @Request() req: RequestWithUser) { await new permission().PermissionDelete(req, "SYS_REGISTRY_TEMP"); diff --git a/src/controllers/ProfileSalaryController.ts b/src/controllers/ProfileSalaryController.ts index 78975593..4736337a 100644 --- a/src/controllers/ProfileSalaryController.ts +++ b/src/controllers/ProfileSalaryController.ts @@ -397,8 +397,7 @@ export class ProfileSalaryController extends Controller { const record = await this.salaryRepo.find({ where: { profileId: profile.id, - // commandCode: In(["5", "6", "7"]) - commandCode: In(["5", "6", "7"]), + commandCode: In(["5", "6", "7", "19"]), }, order: { order: "ASC" }, }); @@ -477,7 +476,7 @@ export class ProfileSalaryController extends Controller { if (_workflow == false) await new permission().PermissionOrgUserGet(req, "SYS_REGISTRY_OFFICER", profileId); const record = await this.salaryRepo.find({ - where: { profileId: profileId, commandCode: In(["5", "6", "7"]) }, + where: { profileId: profileId, commandCode: In(["5", "6", "7", "19"]) }, order: { order: "ASC" }, }); // const result = record.map((r) => ({ @@ -842,7 +841,7 @@ export class ProfileSalaryController extends Controller { let _workflow = await new permission().Workflow(req, profileId, "SYS_SALARY_OFFICER"); if (_workflow == false) await new permission().PermissionGet(req, "SYS_SALARY_OFFICER"); const record = await this.salaryRepo.find({ - where: { profileId: profileId, commandCode: In(["5", "6", "7"]) }, + where: { profileId: profileId, commandCode: In(["5", "6", "7", "19"]) }, order: { order: "ASC" }, }); return new HttpSuccess(record); @@ -909,6 +908,7 @@ export class ProfileSalaryController extends Controller { if (body.commandCode == "7") body.commandName = "เงินพิเศษอื่น ๆ"; else if (body.commandCode == "6") body.commandName = "เลื่อนเงินเดือนกรณีอื่น ๆ"; else if (body.commandCode == "5") body.commandName = "เลื่อนเงินเดือนตามปกติ"; + else if (body.commandCode == "19") body.commandName = "ไม่ได้เลื่อนเงินเดือน/ค่าจ้าง"; } Object.assign(data, { ...body, ...meta }); const history = new ProfileSalaryHistory(); @@ -1032,6 +1032,7 @@ export class ProfileSalaryController extends Controller { if (body.commandCode == "7") body.commandName = "เงินพิเศษอื่น ๆ"; else if (body.commandCode == "6") body.commandName = "เลื่อนเงินเดือนกรณีอื่น ๆ"; else if (body.commandCode == "5") body.commandName = "เลื่อนเงินเดือนตามปกติ"; + else if (body.commandCode == "19") body.commandName = "ไม่ได้เลื่อนเงินเดือน/ค่าจ้าง"; } Object.assign(record, body); Object.assign(history, { ...record, id: undefined }); diff --git a/src/controllers/ProfileSalaryEmployeeController.ts b/src/controllers/ProfileSalaryEmployeeController.ts index 36576788..f113a2ba 100644 --- a/src/controllers/ProfileSalaryEmployeeController.ts +++ b/src/controllers/ProfileSalaryEmployeeController.ts @@ -49,7 +49,7 @@ export class ProfileSalaryEmployeeController extends Controller { throw new HttpError(HttpStatus.BAD_REQUEST, "ไม่พบ profile ดังกล่าว"); } const record = await this.salaryRepo.find({ - where: { profileEmployeeId: profile.id, commandCode: In(["5", "6", "7"]) }, + where: { profileEmployeeId: profile.id, commandCode: In(["5", "6", "7", "19"]) }, order: { order: "ASC" }, }); return new HttpSuccess(record); @@ -96,7 +96,7 @@ export class ProfileSalaryEmployeeController extends Controller { if (_workflow == false) await new permission().PermissionOrgUserGet(req, "SYS_REGISTRY_EMP", profileId); const record = await this.salaryRepo.find({ - where: { profileEmployeeId: profileId, commandCode: In(["5", "6", "7"]) }, + where: { profileEmployeeId: profileId, commandCode: In(["5", "6", "7", "19"]) }, order: { order: "ASC" }, }); return new HttpSuccess(record); @@ -322,7 +322,7 @@ export class ProfileSalaryEmployeeController extends Controller { let _workflow = await new permission().Workflow(req, profileId, "SYS_WAGE"); if (_workflow == false) await new permission().PermissionGet(req, "SYS_WAGE"); const record = await this.salaryRepo.find({ - where: { profileEmployeeId: profileId, commandCode: In(["5", "6", "7"]) }, + where: { profileEmployeeId: profileId, commandCode: In(["5", "6", "7", "19"]) }, order: { order: "ASC" }, }); return new HttpSuccess(record); @@ -395,6 +395,7 @@ export class ProfileSalaryEmployeeController extends Controller { if (body.commandCode == "7") body.commandName = "เงินพิเศษอื่น ๆ"; else if (body.commandCode == "6") body.commandName = "เลื่อนเงินเดือนกรณีอื่น ๆ"; else if (body.commandCode == "5") body.commandName = "เลื่อนเงินเดือนตามปกติ"; + else if (body.commandCode == "19") body.commandName = "ไม่ได้เลื่อนเงินเดือน/ค่าจ้าง"; } Object.assign(data, { ...body, ...meta }); const history = new ProfileSalaryHistory(); @@ -528,6 +529,7 @@ export class ProfileSalaryEmployeeController extends Controller { if (body.commandCode == "7") body.commandName = "เงินพิเศษอื่น ๆ"; else if (body.commandCode == "6") body.commandName = "เลื่อนเงินเดือนกรณีอื่น ๆ"; else if (body.commandCode == "5") body.commandName = "เลื่อนเงินเดือนตามปกติ"; + else if (body.commandCode == "19") body.commandName = "ไม่ได้เลื่อนเงินเดือน/ค่าจ้าง"; } Object.assign(record, body); Object.assign(history, { ...record, id: undefined }); diff --git a/src/controllers/ProfileSalaryTempController.ts b/src/controllers/ProfileSalaryTempController.ts index cbdcf50a..fc6a9df5 100644 --- a/src/controllers/ProfileSalaryTempController.ts +++ b/src/controllers/ProfileSalaryTempController.ts @@ -35,6 +35,19 @@ import { CreatePositionSalaryEditHistory, PositionSalaryEditHistory, } from "../entities/PositionSalaryEditHistory"; +import { ProfileSalaryBackup } from "../entities/ProfileSalaryBackup"; +import { ProfileActposition } from "../entities/ProfileActposition"; +import { ProfileAssistance } from "../entities/ProfileAssistance"; + +const SALARY_COMMAND_CODES = { + ACT_POSITION: "17", // รักษาการ + ASSISTANCE: "18", // ช่วยราชการ +} as const; + +class ConfirmDoneSalaryDto { + profileId: string; + type: "OFFICER" | "EMPLOYEE"; +} @Route("api/v1/org/profile/salaryTemp") @Tags("ProfileSalaryTemp") @@ -120,7 +133,8 @@ export class ProfileSalaryTempController extends Controller { _data.child1 != undefined && _data.child1 != null ? _data.child1[0] != null ? `current_holders.orgChild1Id IN (:...child1)` - : `current_holders.orgChild1Id is ${_data.privilege == "PARENT" ? "not null" : "null"}` + // : `current_holders.orgChild1Id is ${_data.privilege == "PARENT" ? "not null" : "null"}` + : `current_holders.orgChild1Id is null` : "1=1", { child1: _data.child1, @@ -531,7 +545,8 @@ export class ProfileSalaryTempController extends Controller { _data.child1 != undefined && _data.child1 != null ? _data.child1[0] != null ? `current_holders.orgChild1Id IN (:...child1)` - : `current_holders.orgChild1Id is ${_data.privilege == "PARENT" ? "not null" : "null"}` + // : `current_holders.orgChild1Id is ${_data.privilege == "PARENT" ? "not null" : "null"}` + : `current_holders.orgChild1Id is null` : "1=1", { child1: _data.child1, @@ -1011,7 +1026,10 @@ export class ProfileSalaryTempController extends Controller { if (!salary) { throw new HttpError(HttpStatus.BAD_REQUEST, "ไม่พบ profile ดังกล่าว"); } - return new HttpSuccess({ salaryNew: salary, salaryOld: salary.profileSalary }); + return new HttpSuccess({ + salaryNew: salary, + salaryOld: salary.profileSalary, + }); } /** @@ -1240,7 +1258,9 @@ export class ProfileSalaryTempController extends Controller { profile.statusCheckEdit = "PENDING"; await this.profileRepo.save(profile); } else { - const profile = await this.profileEmployeeRepo.findOneBy({ id: body.profileId }); + const profile = await this.profileEmployeeRepo.findOneBy({ + id: body.profileId, + }); if (!profile) { throw new HttpError(HttpStatus.BAD_REQUEST, "ไม่พบ profile ดังกล่าว"); } @@ -1269,7 +1289,9 @@ export class ProfileSalaryTempController extends Controller { profile.statusCheckEdit = "EDITED"; await this.profileRepo.save(profile); } else { - const profile = await this.profileEmployeeRepo.findOneBy({ id: body.profileId }); + const profile = await this.profileEmployeeRepo.findOneBy({ + id: body.profileId, + }); if (!profile) { throw new HttpError(HttpStatus.BAD_REQUEST, "ไม่พบ profile ดังกล่าว"); } @@ -1288,24 +1310,185 @@ export class ProfileSalaryTempController extends Controller { @Post("confirm-done") public async confirmDoneSalary( @Request() req: RequestWithUser, - @Body() body: { profileId: string; type: string }, + @Body() body: ConfirmDoneSalaryDto, ) { - if (body.type.toLocaleUpperCase() == "OFFICER") { - const profile = await this.profileRepo.findOneBy({ id: body.profileId }); + const queryRunner = AppDataSource.createQueryRunner(); + await queryRunner.connect(); + await queryRunner.startTransaction(); + + try { + const isOfficer = body.type.toUpperCase() === "OFFICER"; + + /* ========================= + * 1. Load Profile + * ========================= */ + const profile = isOfficer + ? await queryRunner.manager.findOne(Profile, { + where: { id: body.profileId }, + }) + : await queryRunner.manager.findOne(ProfileEmployee, { + where: { id: body.profileId }, + }); + if (!profile) { - throw new HttpError(HttpStatus.BAD_REQUEST, "ไม่พบ profile ดังกล่าว"); + throw new HttpError(HttpStatus.BAD_REQUEST, "ไม่พบข้อมูล profile"); } + profile.statusCheckEdit = "CHECKED"; - await this.profileRepo.save(profile); - } else { - const profile = await this.profileEmployeeRepo.findOneBy({ id: body.profileId }); - if (!profile) { - throw new HttpError(HttpStatus.BAD_REQUEST, "ไม่พบ profile ดังกล่าว"); + await queryRunner.manager.save(profile); + + /* ========================= + * 2. Load Salary Temp + * ========================= */ + const salaryTemps = await queryRunner.manager.find(ProfileSalaryTemp, { + where: isOfficer ? { profileId: body.profileId } : { profileEmployeeId: body.profileId }, + }); + + if (salaryTemps.length === 0) { + throw new HttpError(HttpStatus.NOT_FOUND, "ไม่พบข้อมูลตำแหน่ง/เงินเดือนของ profile นี้"); } - profile.statusCheckEdit = "CHECKED"; - await this.profileEmployeeRepo.save(profile); + + /* ========================= + * 3. สำรองข้อมูล Temp ไว้ restore ภายหลัง + * ========================= */ + const originalTempRows = salaryTemps; // เก็บไว้ restore หลัง insert ProfileSalary เสร็จ + const toInsert = salaryTemps.filter((t) => !t.isDelete); + + /* ========================= + * 4. ลบข้อมูล ProfileSalaryTemp และ ProfileSalary ปัจจุบัน + * ========================= */ + await queryRunner.manager.delete(ProfileSalaryTemp, { + ...(isOfficer ? { profileId: body.profileId } : { profileEmployeeId: body.profileId }), + }); + + // backup profile salary before delete + const profileSalaryBeforeDelete = await queryRunner.manager.find(ProfileSalary, { + where: { + ...(isOfficer ? { profileId: body.profileId } : { profileEmployeeId: body.profileId }), + }, + }); + + // insert ProfileSalary backup to ProfileSalaryBackup + if (profileSalaryBeforeDelete.length > 0) { + const backupRows = profileSalaryBeforeDelete.map(({ id, ...salary }) => + queryRunner.manager.create(ProfileSalaryBackup, { + ...salary, + profileSalaryId: id, + }), + ); + await queryRunner.manager.insert(ProfileSalaryBackup, backupRows); + } + + // delete profile salary + await queryRunner.manager.delete(ProfileSalary, { + ...(isOfficer ? { profileId: body.profileId } : { profileEmployeeId: body.profileId }), + }); + + /* ========================= + * 5. INSERT (bulk) + * ========================= */ + if (toInsert.length > 0) { + const dateNow = new Date(); + const metaCreated = { + createdUserId: req.user.sub, + createdFullName: req.user.name, + createdAt: dateNow, + lastUpdateUserId: req.user.sub, + lastUpdateFullName: req.user.name, + lastUpdatedAt: dateNow, + }; + + // แยกข้อมูลตามประเภทการ insert + const { salaryRows, actPositionRows, assistanceRows } = toInsert.reduce<{ + salaryRows: ProfileSalaryTemp[]; + actPositionRows: ProfileSalaryTemp[]; + assistanceRows: ProfileSalaryTemp[]; + }>( + (acc, x) => { + if (x.commandCode === SALARY_COMMAND_CODES.ACT_POSITION) acc.actPositionRows.push(x); + else if (x.commandCode === SALARY_COMMAND_CODES.ASSISTANCE) acc.assistanceRows.push(x); + else acc.salaryRows.push(x); + return acc; + }, + { salaryRows: [], actPositionRows: [], assistanceRows: [] }, + ); + + // Insert ProfileSalary + if (salaryRows.length) { + await queryRunner.manager.insert( + ProfileSalary, + salaryRows.map(({ id, ...data }) => ({ + ...data, + ...metaCreated, + })), + ); + } + + // Insert ProfileActposition + if (actPositionRows.length) { + await queryRunner.manager.insert( + ProfileActposition, + actPositionRows.map((x) => ({ + profileId: x.profileId, + profileEmployeeId: x.profileEmployeeId, + dateStart: x.commandDateAffect, + dateEnd: null, + posNo: `${x.posNoAbb} ${x.posNo}`, + position: x.positionName, + commandId: x.commandId, + refCommandNo: `${x.commandNo}/${x.commandYear}`, + refCommandDate: x.commandDateAffect, + status: false, + isDeleted: false, + ...metaCreated, + })), + ); + } + + // Insert ProfileAssistance + if (assistanceRows.length) { + await queryRunner.manager.insert( + ProfileAssistance, + assistanceRows.map((x) => ({ + profileId: x.profileId, + profileEmployeeId: x.profileEmployeeId, + agency: x.orgRoot, + dateStart: x.commandDateAffect, + dateEnd: null, + commandId: x.commandId, + commandNo: `${x.commandNo}/${x.commandYear}`, + commandName: x.commandName ?? "ให้ช่วยราชการ", + refCommandDate: x.commandDateSign, + refId: x.refId, + status: "DONE", + isUpload: false, + ...metaCreated, + })), + ); + } + } + + /* ========================= + * Restore ProfileSalaryTemp (หลังจาก ProfileSalary insert เรียบร้อยแล้ว) + * ========================= */ + if (originalTempRows.length > 0) { + await queryRunner.manager.insert( + ProfileSalaryTemp, + originalTempRows.map(({ id, ...data }) => ({ + ...data, + salaryId: null, + })), + ); + } + + await queryRunner.commitTransaction(); + return new HttpSuccess(); + } catch (error) { + await queryRunner.rollbackTransaction(); + throw error; + } finally { + await queryRunner.release(); } - return new HttpSuccess(); } /** @@ -1390,7 +1573,9 @@ export class ProfileSalaryTempController extends Controller { salaryId: string, @Request() req: RequestWithUser, ) { - const source_item = await this.salaryRepo.findOne({ where: { id: salaryId } }); + const source_item = await this.salaryRepo.findOne({ + where: { id: salaryId }, + }); // if (source_item) { //await new permission().PermissionOrgUserGet(req,"SYS_REGISTRY_OFFICER",source_item.profileId,); //ไม่แน่ใจOFFปิดไว้ก่อน // } @@ -1398,7 +1583,10 @@ export class ProfileSalaryTempController extends Controller { const sourceOrder = source_item.order; if (direction.trim().toUpperCase() == "UP") { const dest_item = await this.salaryRepo.findOne({ - where: { profileId: source_item.profileId, order: LessThan(sourceOrder) }, + where: { + profileId: source_item.profileId, + order: LessThan(sourceOrder), + }, order: { order: "DESC" }, }); if (dest_item == null) return new HttpSuccess(); @@ -1410,7 +1598,10 @@ export class ProfileSalaryTempController extends Controller { await Promise.all([this.salaryRepo.save(source_item), this.salaryRepo.save(dest_item)]); } else { const dest_item = await this.salaryRepo.findOne({ - where: { profileId: source_item.profileId, order: MoreThan(sourceOrder) }, + where: { + profileId: source_item.profileId, + order: MoreThan(sourceOrder), + }, order: { order: "ASC" }, }); if (dest_item == null) return new HttpSuccess(); @@ -1441,7 +1632,9 @@ export class ProfileSalaryTempController extends Controller { profile = await this.profileRepo.findOneBy({ id: profileId }); if (!profile) { - profileEmployee = await this.profileEmployeeRepo.findOneBy({ id: profileId }); + profileEmployee = await this.profileEmployeeRepo.findOneBy({ + id: profileId, + }); if (!profileEmployee) { throw new HttpError(HttpStatus.NOT_FOUND, "ไม่พบข้อมูล"); } diff --git a/src/controllers/ProfileTrainingController.ts b/src/controllers/ProfileTrainingController.ts index c331fdb9..ac48f021 100644 --- a/src/controllers/ProfileTrainingController.ts +++ b/src/controllers/ProfileTrainingController.ts @@ -23,15 +23,23 @@ import HttpError from "../interfaces/http-error"; import { ProfileTrainingHistory } from "../entities/ProfileTrainingHistory"; import { RequestWithUser } from "../middlewares/user"; import { Profile } from "../entities/Profile"; +import { ProfileEmployee } from "../entities/ProfileEmployee"; import permission from "../interfaces/permission"; import { setLogDataDiff } from "../interfaces/utils"; +import { ProfileDevelopment } from "../entities/ProfileDevelopment"; +import { ProfileDevelopmentHistory } from "../entities/ProfileDevelopmentHistory"; +import { In } from "typeorm"; @Route("api/v1/org/profile/training") @Tags("ProfileTraining") @Security("bearerAuth") export class ProfileTrainingController extends Controller { private profileRepo = AppDataSource.getRepository(Profile); + private profileEmployeeRepo = AppDataSource.getRepository(ProfileEmployee); private trainingRepo = AppDataSource.getRepository(ProfileTraining); private trainingHistoryRepo = AppDataSource.getRepository(ProfileTrainingHistory); + private developmentRepo = AppDataSource.getRepository(ProfileDevelopment); + private developmentHistoryRepo = AppDataSource.getRepository(ProfileDevelopmentHistory); + @Get("user") public async getTrainingUser(@Request() request: { user: Record }) { @@ -40,7 +48,7 @@ export class ProfileTrainingController extends Controller { throw new HttpError(HttpStatus.BAD_REQUEST, "ไม่พบ profile ดังกล่าว"); } const record = await this.trainingRepo.find({ - where: { profileId: profile.id }, + where: { profileId: profile.id, isDeleted: false }, order: { createdAt: "ASC" }, }); return new HttpSuccess(record); @@ -52,7 +60,7 @@ export class ProfileTrainingController extends Controller { if (_workflow == false) await new permission().PermissionOrgUserGet(req, "SYS_REGISTRY_OFFICER", profileId); const record = await this.trainingRepo.find({ - where: { profileId }, + where: { profileId: profileId, isDeleted: false }, order: { createdAt: "ASC" }, }); return new HttpSuccess(record); @@ -178,4 +186,156 @@ export class ProfileTrainingController extends Controller { return new HttpSuccess(); } + + /** + * API ลบข้อมูลการฝึกอบรม/ดูงาน + * @summary API ลบข้อมูลการฝึกอบรม/ดูงาน + * @param trainingId คีย์การฝึกอบรม/ดูงาน + */ + @Patch("update-delete/{trainingId}") + public async updateIsDeletedTraining( + @Request() req: RequestWithUser, + @Path() trainingId: string, + ) { + const record = await this.trainingRepo.findOneBy({ id: trainingId }); + if (!record) throw new HttpError(HttpStatus.NOT_FOUND, "ไม่พบข้อมูล"); + if (record.isDeleted === true) { + return new HttpSuccess(); + } + await new permission().PermissionOrgUserDelete(req, "SYS_REGISTRY_OFFICER", record.profileId); + const before = structuredClone(record); + const history = new ProfileTrainingHistory(); + const now = new Date(); + record.isDeleted = true; + record.lastUpdateUserId = req.user.sub; + record.lastUpdateFullName = req.user.name; + record.lastUpdatedAt = now; + + Object.assign(history, { ...record, id: undefined }); + history.profileTrainingId = trainingId; + history.createdUserId = req.user.sub; + history.createdFullName = req.user.name; + history.createdAt = now; + + await Promise.all([ + this.trainingRepo.save(record, { data: req }), + setLogDataDiff(req, { before, after: record }), + this.trainingHistoryRepo.save(history, { data: req }), + ]); + + return new HttpSuccess(); + } + + /** + * API ล้างข้อมูลการฝึกอบรม/ดูงาน และ การพัฒนารายบุคคล IDP เมื่อลบโครงการพัฒนา + * @summary API ล้างข้อมูลการฝึกอบรม/ดูงาน และ การพัฒนารายบุคคล IDP เมื่อลบโครงการพัฒนา + */ + @Post("delete-all") + public async deleteAllTraining( + @Body() reqBody: { developmentId: string }, + @Request() req: RequestWithUser + ) { + const trainings = await this.trainingRepo.find({ + select: { id: true }, + where: { developmentId: reqBody.developmentId }, + }); + if (trainings.length > 0) { + const trainingIds = trainings.map((x) => x.id); + await this.trainingHistoryRepo.delete({ + profileTrainingId: In(trainingIds), + }); + await this.trainingRepo.delete({ + developmentId: reqBody.developmentId, + }); + } + + await this.developmentHistoryRepo.delete({ + kpiDevelopmentId: reqBody.developmentId, + }); + await this.developmentRepo.delete({ + kpiDevelopmentId: reqBody.developmentId + }); + + return new HttpSuccess(); + } + + /** + * API ลบข้อมูลการฝึกอบรม/ดูงาน และ การพัฒนารายบุคคล IDP รายบุคคล + * @summary API ลบข้อมูลการฝึกอบรม/ดูงาน และ การพัฒนารายบุคคล IDP รายบุคคล + */ + @Post("delete-byId") + public async deleteById( + @Body() reqBody: { + type: string; + profileId: string; + developmentId: string; + }, + @Request() req: RequestWithUser + ) { + + const type = reqBody.type?.trim().toUpperCase(); + // 1. validate profile + if (type === "OFFICER") { + const profile = await this.profileRepo.findOneBy({ id: reqBody.profileId }); + if (!profile) { + throw new HttpError(HttpStatus.BAD_REQUEST, "ไม่พบ profile ดังกล่าว"); + } + } else { + const profile = await this.profileEmployeeRepo.findOneBy({ id: reqBody.profileId }); + if (!profile) { + throw new HttpError(HttpStatus.BAD_REQUEST, "ไม่พบ profile ดังกล่าว"); + } + } + + const profileField = type === "OFFICER" ? "profileId" : "profileEmployeeId"; + + // 2. หา ProfileTraining + const trainings = await this.trainingRepo.find({ + select: { id: true }, + where: { + developmentId: reqBody.developmentId, + [profileField]: reqBody.profileId, + }, + }); + + if (trainings.length > 0) { + const trainingIds = trainings.map(x => x.id); + + // 3. ลบ TrainingHistory + await this.trainingHistoryRepo.delete({ + profileTrainingId: In(trainingIds), + }); + + // 4. ลบ ProfileTraining + await this.trainingRepo.delete({ + id: In(trainingIds), + }); + } + + // 5. หา ProfileDevelopment + const developments = await this.developmentRepo.find({ + select: { id: true }, + where: { + kpiDevelopmentId: reqBody.developmentId, + [profileField]: reqBody.profileId, + }, + }); + + if (developments.length > 0) { + const devIds = developments.map(x => x.id); + + // 6. ลบ DevelopmentHistory + await this.developmentHistoryRepo.delete({ + profileDevelopmentId: In(devIds), + }); + + // 7. ลบ ProfileDevelopment + await this.developmentRepo.delete({ + id: In(devIds), + }); + } + + return new HttpSuccess(); + } + } diff --git a/src/controllers/ProfileTrainingEmployeeController.ts b/src/controllers/ProfileTrainingEmployeeController.ts index 60f1c2ac..73bbc5cf 100644 --- a/src/controllers/ProfileTrainingEmployeeController.ts +++ b/src/controllers/ProfileTrainingEmployeeController.ts @@ -40,7 +40,7 @@ export class ProfileTrainingEmployeeController extends Controller { throw new HttpError(HttpStatus.BAD_REQUEST, "ไม่พบ profile ดังกล่าว"); } const record = await this.trainingRepo.find({ - where: { profileEmployeeId: profile.id }, + where: { profileEmployeeId: profile.id, isDeleted: false }, order: { createdAt: "ASC" }, }); return new HttpSuccess(record); @@ -52,7 +52,7 @@ export class ProfileTrainingEmployeeController extends Controller { if (_workflow == false) await new permission().PermissionOrgUserGet(req, "SYS_REGISTRY_EMP", profileEmployeeId); const record = await this.trainingRepo.find({ - where: { profileEmployeeId }, + where: { profileEmployeeId, isDeleted: false }, order: { createdAt: "ASC" }, }); return new HttpSuccess(record); @@ -168,6 +168,45 @@ export class ProfileTrainingEmployeeController extends Controller { return new HttpSuccess(); } + /** + * API ลบข้อมูลการฝึกอบรม/ดูงาน + * @summary API ลบข้อมูลการฝึกอบรม/ดูงาน + * @param trainingId คีย์การฝึกอบรม/ดูงาน + */ + @Patch("update-delete/{trainingId}") + public async updateIsDeletedTraining( + @Request() req: RequestWithUser, + @Path() trainingId: string, + ) { + const record = await this.trainingRepo.findOneBy({ id: trainingId }); + if (!record) throw new HttpError(HttpStatus.NOT_FOUND, "ไม่พบข้อมูล"); + if (record.isDeleted === true) { + return new HttpSuccess(); + } + await new permission().PermissionOrgUserDelete(req, "SYS_REGISTRY_EMP", record.profileEmployeeId); + const before = structuredClone(record); + const history = new ProfileTrainingHistory(); + const now = new Date(); + record.isDeleted = true; + record.lastUpdateUserId = req.user.sub; + record.lastUpdateFullName = req.user.name; + record.lastUpdatedAt = now; + + Object.assign(history, { ...record, id: undefined }); + history.profileTrainingId = trainingId; + history.createdUserId = req.user.sub; + history.createdFullName = req.user.name; + history.createdAt = now; + + await Promise.all([ + this.trainingRepo.save(record, { data: req }), + setLogDataDiff(req, { before, after: record }), + this.trainingHistoryRepo.save(history, { data: req }), + ]); + + return new HttpSuccess(); + } + @Delete("{trainingId}") public async deleteTraining(@Path() trainingId: string, @Request() req: RequestWithUser) { const _record = await this.trainingRepo.findOneBy({ id: trainingId }); diff --git a/src/controllers/ProfileTrainingEmployeeTempController.ts b/src/controllers/ProfileTrainingEmployeeTempController.ts index 64e6fdcd..ace30f6d 100644 --- a/src/controllers/ProfileTrainingEmployeeTempController.ts +++ b/src/controllers/ProfileTrainingEmployeeTempController.ts @@ -40,7 +40,7 @@ export class ProfileTrainingEmployeeTempController extends Controller { throw new HttpError(HttpStatus.BAD_REQUEST, "ไม่พบ profile ดังกล่าว"); } const record = await this.trainingRepo.find({ - where: { profileEmployeeId: profile.id }, + where: { profileEmployeeId: profile.id, isDeleted: false }, order: { createdAt: "ASC" }, }); return new HttpSuccess(record); @@ -51,7 +51,7 @@ export class ProfileTrainingEmployeeTempController extends Controller { let _workflow = await new permission().Workflow(req, profileEmployeeId, "SYS_REGISTRY_TEMP"); if (_workflow == false) await new permission().PermissionGet(req, "SYS_REGISTRY_TEMP"); const record = await this.trainingRepo.find({ - where: { profileEmployeeId }, + where: { profileEmployeeId, isDeleted: false }, order: { createdAt: "ASC" }, }); return new HttpSuccess(record); @@ -156,6 +156,45 @@ export class ProfileTrainingEmployeeTempController extends Controller { return new HttpSuccess(); } + /** + * API ลบข้อมูลการฝึกอบรม/ดูงาน + * @summary API ลบข้อมูลการฝึกอบรม/ดูงาน + * @param trainingId คีย์การฝึกอบรม/ดูงาน + */ + @Patch("update-delete/{trainingId}") + public async updateIsDeletedTraining( + @Request() req: RequestWithUser, + @Path() trainingId: string, + ) { + const record = await this.trainingRepo.findOneBy({ id: trainingId }); + if (!record) throw new HttpError(HttpStatus.NOT_FOUND, "ไม่พบข้อมูล"); + if (record.isDeleted === true) { + return new HttpSuccess(); + } + await new permission().PermissionDelete(req, "SYS_REGISTRY_TEMP"); + const before = structuredClone(record); + const history = new ProfileTrainingHistory(); + const now = new Date(); + record.isDeleted = true; + record.lastUpdateUserId = req.user.sub; + record.lastUpdateFullName = req.user.name; + record.lastUpdatedAt = now; + + Object.assign(history, { ...record, id: undefined }); + history.profileTrainingId = trainingId; + history.createdUserId = req.user.sub; + history.createdFullName = req.user.name; + history.createdAt = now; + + await Promise.all([ + this.trainingRepo.save(record, { data: req }), + setLogDataDiff(req, { before, after: record }), + this.trainingHistoryRepo.save(history, { data: req }), + ]); + + return new HttpSuccess(); + } + @Delete("{trainingId}") public async deleteTraining(@Path() trainingId: string, @Request() req: RequestWithUser) { await new permission().PermissionDelete(req, "SYS_REGISTRY_TEMP"); diff --git a/src/controllers/ReportController.ts b/src/controllers/ReportController.ts index 2e72fc6b..da96d091 100644 --- a/src/controllers/ReportController.ts +++ b/src/controllers/ReportController.ts @@ -87,6 +87,7 @@ export class ReportController extends Controller { @Query() isProbation?: boolean, @Query() isRetire?: boolean, @Query() isRetireLaw?: boolean, + @Query() isCurrent?: boolean, @Query() retireType?: string, @Query() tenureType?: string, @Query() tenureMin?: number, @@ -253,62 +254,16 @@ export class ReportController extends Controller { .andWhere(field != null && field != "" ? "registryOfficer.fields LIKE :fields" : "1=1", { fields: `%${field}%`, }) + .andWhere( + isCurrent === undefined || isCurrent === null + ? "1=1" + : isCurrent === true + ? "registryOfficer.posMasterNo IS NOT NULL" + : "registryOfficer.posMasterNo IS NULL", + ) .orderBy(`registryOfficer.${sortBy}`, sort) .getManyAndCount(); - // const mapData1 = await Promise.all( - // lists.map(async (x) => { - // return { - // profileId: x.profileId, - // citizenId: x.citizenId, - // prefix: x.prefix, - // firstName: x.firstName, - // lastName: x.lastName, - // isProbation: x.isProbation, - // isLeave: x.isLeave, - // isRetirement: x.isRetirement, - // leaveType: x.leaveType, - // posMasterNo: x.posMasterNo, - // orgRootId: x.orgRootId, - // orgChild1Id: x.orgChild1Id, - // orgChild2Id: x.orgChild2Id, - // orgChild3Id: x.orgChild3Id, - // orgChild4Id: x.orgChild4Id, - // orgRootName: x.orgRootName, - // orgChild1Name: x.orgChild1Name, - // orgChild2Name: x.orgChild2Name, - // orgChild3Name: x.orgChild3Name, - // orgChild4Name: x.orgChild4Name, - // org: x.org, - // searchShortName: x.searchShortName, - // posExecutiveName: x.posExecutiveName, - // position: x.position, - // posTypeName: x.posTypeName, - // posLevelName: x.posLevelName, - // gender: x.gender, - // relationship: x.relationship, - // dateAppoint: x.dateAppoint, - // dateRetire: x.dateRetire, - // dateRetireLaw: x.dateRetireLaw, - // birthdate: x.birthdate, - // degree: x.degrees, - // age: x.age, - // currentPosition: null, - // lengthPosition: null, - // positionDate: { - // Years: x.Years ? x.Years : 0, - // Months: x.Months ? x.Months : 0, - // Days: x.Days ? x.Days : 0, - // }, - // levelDate: { - // posExecutiveYears: x.levelYears, - // posExecutiveMonths: x.levelMonths, - // posExecutiveDays: x.levelDays, - // }, - // }; - // }), - // ); - const mapData = []; for await (const x of lists) { let _educations: any = []; @@ -322,11 +277,6 @@ export class ReportController extends Controller { Array.isArray(x.Educations) && x.Educations != null ? (x.Educations as any[]).filter((i: any) => i.isHigh == true) : []; - // if(_educations.length == 0) { - // _educations = Array.isArray(x.Educations) && x.Educations != null - // ? (x.Educations as any[])[0] - // : [] - // } } } else { _educations = @@ -379,9 +329,6 @@ export class ReportController extends Controller { dateRetireLaw: x.dateRetireLaw, birthdate: x.birthdate, Educations: _educations, - // degree: x.degrees, - // educationLevel: x.educationLevels, - // field: x.fields, age: x.age, currentPosition: null, lengthPosition: null, @@ -429,6 +376,7 @@ export class ReportController extends Controller { @Query() isProbation?: boolean, @Query() isRetire?: boolean, @Query() isRetireLaw?: boolean, + @Query() isCurrent?: boolean, @Query() retireType?: string, @Query() ageMin?: number, @Query() ageMax?: number, @@ -581,6 +529,13 @@ export class ReportController extends Controller { .andWhere(field != null && field != "" ? "registryEmployee.fields LIKE :fields" : "1=1", { fields: `%${field}%`, }) + .andWhere( + isCurrent === undefined || isCurrent === null + ? "1=1" + : isCurrent === true + ? "registryEmployee.posMasterNo IS NOT NULL" + : "registryEmployee.posMasterNo IS NULL", + ) .orderBy(`registryEmployee.${sortBy}`, sort) .getManyAndCount(); @@ -3659,8 +3614,9 @@ export class ReportController extends Controller { if (profileIds.length > 0) { educationsData = await this.profileEducationRepository .createQueryBuilder("pe") - .select(["pe.profileId", "pe.finishDate", "pe.degree", "pe.level"]) + .select(["pe.profileId", "pe.finishDate", "pe.degree", "pe.level", "pe.isDeleted"]) .where("pe.profileId IN (:...profileIds)", { profileIds }) + .andWhere("pe.isDeleted = :isDeleted", { isDeleted: false }) .andWhere( `(pe.profileId, COALESCE(pe.finishDate, '1900-01-01'), pe.level) IN ( SELECT pe2.profileId, @@ -3844,8 +3800,9 @@ export class ReportController extends Controller { this.profileEducationRepository .createQueryBuilder("pe") - .select(["pe.profileId", "pe.finishDate", "pe.degree", "pe.level"]) + .select(["pe.profileId", "pe.finishDate", "pe.degree", "pe.level", "pe.isDeleted"]) .where("pe.profileId IN (:...child1ProfileIds)", { child1ProfileIds }) + .andWhere("pe.isDeleted = :isDeleted", { isDeleted: false }) .andWhere( `(pe.profileId, COALESCE(pe.finishDate, '1900-01-01'), pe.level) IN ( SELECT pe2.profileId, @@ -4025,8 +3982,9 @@ export class ReportController extends Controller { this.profileEducationRepository .createQueryBuilder("pe") - .select(["pe.profileId", "pe.finishDate", "pe.degree", "pe.level"]) + .select(["pe.profileId", "pe.finishDate", "pe.degree", "pe.level", "pe.isDeleted"]) .where("pe.profileId IN (:...child2ProfileIds)", { child2ProfileIds }) + .andWhere("pe.isDeleted = :isDeleted", { isDeleted: false }) .andWhere( `(pe.profileId, COALESCE(pe.finishDate, '1900-01-01'), pe.level) IN ( SELECT pe2.profileId, @@ -4206,8 +4164,9 @@ export class ReportController extends Controller { this.profileEducationRepository .createQueryBuilder("pe") - .select(["pe.profileId", "pe.finishDate", "pe.degree", "pe.level"]) + .select(["pe.profileId", "pe.finishDate", "pe.degree", "pe.level", "pe.isDeleted"]) .where("pe.profileId IN (:...child3ProfileIds)", { child3ProfileIds }) + .andWhere("pe.isDeleted = :isDeleted", { isDeleted: false }) .andWhere( `(pe.profileId, COALESCE(pe.finishDate, '1900-01-01'), pe.level) IN ( SELECT pe2.profileId, @@ -4385,8 +4344,9 @@ export class ReportController extends Controller { this.profileEducationRepository .createQueryBuilder("pe") - .select(["pe.profileId", "pe.finishDate", "pe.degree", "pe.level"]) + .select(["pe.profileId", "pe.finishDate", "pe.degree", "pe.level", "pe.isDeleted"]) .where("pe.profileId IN (:...child4ProfileIds)", { child4ProfileIds }) + .andWhere("pe.isDeleted = :isDeleted", { isDeleted: false }) .andWhere( `(pe.profileId, COALESCE(pe.finishDate, '1900-01-01'), pe.level) IN ( SELECT pe2.profileId, @@ -8955,8 +8915,9 @@ export class ReportController extends Controller { if (profileIds.length > 0) { educationsData = await this.profileEducationRepository .createQueryBuilder("pe") - .select(["pe.profileId", "pe.finishDate", "pe.degree", "pe.level"]) + .select(["pe.profileId", "pe.finishDate", "pe.degree", "pe.level", "pe.isDeleted"]) .where("pe.profileId IN (:...profileIds)", { profileIds }) + .andWhere("pe.isDeleted = :isDeleted", { isDeleted: false }) .andWhere( `(pe.profileId, COALESCE(pe.finishDate, '1900-01-01'), pe.level) IN ( SELECT pe2.profileId, @@ -9146,8 +9107,9 @@ export class ReportController extends Controller { this.profileEducationRepository .createQueryBuilder("pe") - .select(["pe.profileId", "pe.finishDate", "pe.degree", "pe.level"]) + .select(["pe.profileId", "pe.finishDate", "pe.degree", "pe.level", "pe.isDeleted"]) .where("pe.profileId IN (:...child1ProfileIds)", { child1ProfileIds }) + .andWhere("pe.isDeleted = :isDeleted", { isDeleted: false }) .andWhere( `(pe.profileId, COALESCE(pe.finishDate, '1900-01-01'), pe.level) IN ( SELECT pe2.profileId, @@ -9333,8 +9295,9 @@ export class ReportController extends Controller { this.profileEducationRepository .createQueryBuilder("pe") - .select(["pe.profileId", "pe.finishDate", "pe.degree", "pe.level"]) + .select(["pe.profileId", "pe.finishDate", "pe.degree", "pe.level", "pe.isDeleted"]) .where("pe.profileId IN (:...child2ProfileIds)", { child2ProfileIds }) + .andWhere("pe.isDeleted = :isDeleted", { isDeleted: false }) .andWhere( `(pe.profileId, COALESCE(pe.finishDate, '1900-01-01'), pe.level) IN ( SELECT pe2.profileId, @@ -9521,8 +9484,9 @@ export class ReportController extends Controller { this.profileEducationRepository .createQueryBuilder("pe") - .select(["pe.profileId", "pe.finishDate", "pe.degree", "pe.level"]) + .select(["pe.profileId", "pe.finishDate", "pe.degree", "pe.level", "pe.isDeleted"]) .where("pe.profileId IN (:...child3ProfileIds)", { child3ProfileIds }) + .andWhere("pe.isDeleted = :isDeleted", { isDeleted: false }) .andWhere( `(pe.profileId, COALESCE(pe.finishDate, '1900-01-01'), pe.level) IN ( SELECT pe2.profileId, @@ -9708,8 +9672,9 @@ export class ReportController extends Controller { this.profileEducationRepository .createQueryBuilder("pe") - .select(["pe.profileId", "pe.finishDate", "pe.degree", "pe.level"]) + .select(["pe.profileId", "pe.finishDate", "pe.degree", "pe.level", "pe.isDeleted"]) .where("pe.profileId IN (:...child4ProfileIds)", { child4ProfileIds }) + .andWhere("pe.isDeleted = :isDeleted", { isDeleted: false }) .andWhere( `(pe.profileId, COALESCE(pe.finishDate, '1900-01-01'), pe.level) IN ( SELECT pe2.profileId, diff --git a/src/controllers/ScriptProfileOrgController.ts b/src/controllers/ScriptProfileOrgController.ts new file mode 100644 index 00000000..aa6908e2 --- /dev/null +++ b/src/controllers/ScriptProfileOrgController.ts @@ -0,0 +1,379 @@ +import { Controller, Post, Route, Security, Tags, Request } from "tsoa"; +import { AppDataSource } from "../database/data-source"; +import HttpSuccess from "../interfaces/http-success"; +import HttpStatus from "../interfaces/http-status"; +import HttpError from "../interfaces/http-error"; +import { RequestWithUser } from "../middlewares/user"; +import { MoreThanOrEqual } from "typeorm"; +import { PosMaster } from "./../entities/PosMaster"; +import axios from "axios"; +import { KeycloakSyncController } from "./KeycloakSyncController"; +import { EmployeePosMaster } from "./../entities/EmployeePosMaster"; +import { EmployeeTempPosMaster } from "./../entities/EmployeeTempPosMaster"; + +interface OrgUpdatePayload { + profileId: string; + rootDnaId: string | null; + child1DnaId: string | null; + child2DnaId: string | null; + child3DnaId: string | null; + child4DnaId: string | null; + profileType: "PROFILE" | "PROFILE_EMPLOYEE"; +} + +@Route("api/v1/org/script-profile-org") +@Tags("Keycloak Sync") +@Security("bearerAuth") +export class ScriptProfileOrgController extends Controller { + private posMasterRepo = AppDataSource.getRepository(PosMaster); + private employeePosMasterRepo = AppDataSource.getRepository(EmployeePosMaster); + private employeeTempPosMasterRepo = AppDataSource.getRepository(EmployeeTempPosMaster); + + // Idempotency flag to prevent concurrent runs + private isRunning = false; + + // Configurable values + private readonly BATCH_SIZE = parseInt(process.env.CRONJOB_BATCH_SIZE || "100", 10); + private readonly UPDATE_WINDOW_HOURS = parseInt( + process.env.CRONJOB_UPDATE_WINDOW_HOURS || "24", + 10, + ); + + /** + * Script to update profile's organizational structure in leave service and sync to Keycloak + * + * @summary Update org structure for profiles updated within a certain time window and sync to Keycloak + */ + @Post("update-org") + public async cronjobUpdateOrg(@Request() request: RequestWithUser) { + // Idempotency check - prevent concurrent runs + if (this.isRunning) { + console.log("cronjobUpdateOrg: Job already running, skipping this execution"); + return new HttpSuccess({ + message: "Job already running", + skipped: true, + }); + } + + this.isRunning = true; + const startTime = Date.now(); + + try { + const windowStart = new Date(Date.now() - this.UPDATE_WINDOW_HOURS * 60 * 60 * 1000); + + console.log("cronjobUpdateOrg: Starting job", { + windowHours: this.UPDATE_WINDOW_HOURS, + windowStart: windowStart.toISOString(), + batchSize: this.BATCH_SIZE, + }); + + // Query with optimized select - only fetch required fields + const [posMasters, posMasterEmployee, posMasterEmployeeTemp] = await Promise.all([ + this.posMasterRepo.find({ + where: { + lastUpdatedAt: MoreThanOrEqual(windowStart), + orgRevision: { + orgRevisionIsCurrent: true, + }, + }, + relations: [ + "orgRevision", + "orgRoot", + "orgChild1", + "orgChild2", + "orgChild3", + "orgChild4", + "current_holder", + ], + select: { + id: true, + current_holderId: true, + lastUpdatedAt: true, + orgRevision: { id: true }, + orgRoot: { ancestorDNA: true }, + orgChild1: { ancestorDNA: true }, + orgChild2: { ancestorDNA: true }, + orgChild3: { ancestorDNA: true }, + orgChild4: { ancestorDNA: true }, + current_holder: { id: true }, + }, + }), + this.employeePosMasterRepo.find({ + where: { + lastUpdatedAt: MoreThanOrEqual(windowStart), + orgRevision: { + orgRevisionIsCurrent: true, + }, + }, + relations: [ + "orgRevision", + "orgRoot", + "orgChild1", + "orgChild2", + "orgChild3", + "orgChild4", + "current_holder", + ], + select: { + id: true, + current_holderId: true, + lastUpdatedAt: true, + orgRevision: { id: true }, + orgRoot: { ancestorDNA: true }, + orgChild1: { ancestorDNA: true }, + orgChild2: { ancestorDNA: true }, + orgChild3: { ancestorDNA: true }, + orgChild4: { ancestorDNA: true }, + current_holder: { id: true }, + }, + }), + this.employeeTempPosMasterRepo.find({ + where: { + lastUpdatedAt: MoreThanOrEqual(windowStart), + orgRevision: { + orgRevisionIsCurrent: true, + }, + }, + relations: [ + "orgRevision", + "orgRoot", + "orgChild1", + "orgChild2", + "orgChild3", + "orgChild4", + "current_holder", + ], + select: { + id: true, + current_holderId: true, + lastUpdatedAt: true, + orgRevision: { id: true }, + orgRoot: { ancestorDNA: true }, + orgChild1: { ancestorDNA: true }, + orgChild2: { ancestorDNA: true }, + orgChild3: { ancestorDNA: true }, + orgChild4: { ancestorDNA: true }, + current_holder: { id: true }, + }, + }), + ]); + + console.log("cronjobUpdateOrg: Database query completed", { + posMastersCount: posMasters.length, + employeePosCount: posMasterEmployee.length, + employeeTempPosCount: posMasterEmployeeTemp.length, + totalRecords: posMasters.length + posMasterEmployee.length + posMasterEmployeeTemp.length, + }); + + // Build payloads with proper profile type tracking + const payloads = this.buildPayloads(posMasters, posMasterEmployee, posMasterEmployeeTemp); + + if (payloads.length === 0) { + console.log("cronjobUpdateOrg: No records to process"); + return new HttpSuccess({ + message: "No records to process", + processed: 0, + }); + } + + // Update profile's org structure in leave service by calling API + console.log("cronjobUpdateOrg: Calling leave service API", { + payloadCount: payloads.length, + }); + + await axios.put(`${process.env.API_URL}/leave-beginning/schedule/update-dna`, payloads, { + headers: { + "Content-Type": "application/json", + api_key: process.env.API_KEY, + }, + timeout: 30000, // 30 second timeout + }); + + console.log("cronjobUpdateOrg: Leave service API call successful"); + + // Group profile IDs by type for proper syncing + const profileIdsByType = this.groupProfileIdsByType(payloads); + + // Sync to Keycloak with batching + const keycloakSyncController = new KeycloakSyncController(); + const syncResults = { + total: 0, + success: 0, + failed: 0, + byType: {} as Record, + }; + + // Process each profile type separately + for (const [profileType, profileIds] of Object.entries(profileIdsByType)) { + console.log(`cronjobUpdateOrg: Syncing ${profileType} profiles`, { + count: profileIds.length, + }); + + const batches = this.chunkArray(profileIds, this.BATCH_SIZE); + const typeResult = { total: profileIds.length, success: 0, failed: 0 }; + + for (let i = 0; i < batches.length; i++) { + const batch = batches[i]; + console.log( + `cronjobUpdateOrg: Processing batch ${i + 1}/${batches.length} for ${profileType}`, + { + batchSize: batch.length, + batchRange: `${i * this.BATCH_SIZE + 1}-${Math.min( + (i + 1) * this.BATCH_SIZE, + profileIds.length, + )}`, + }, + ); + + try { + const batchResult: any = await keycloakSyncController.syncByProfileIds({ + profileIds: batch, + profileType: profileType as "PROFILE" | "PROFILE_EMPLOYEE", + }); + + // Extract result data if available + const resultData = (batchResult as any)?.data || batchResult; + typeResult.success += resultData.success || 0; + typeResult.failed += resultData.failed || 0; + + console.log(`cronjobUpdateOrg: Batch ${i + 1}/${batches.length} completed`, { + success: resultData.success || 0, + failed: resultData.failed || 0, + }); + } catch (error: any) { + console.error(`cronjobUpdateOrg: Batch ${i + 1}/${batches.length} failed`, { + error: error.message, + batchSize: batch.length, + }); + // Count all profiles in failed batch as failed + typeResult.failed += batch.length; + } + } + + syncResults.byType[profileType] = typeResult; + syncResults.total += typeResult.total; + syncResults.success += typeResult.success; + syncResults.failed += typeResult.failed; + } + + const duration = Date.now() - startTime; + console.log("cronjobUpdateOrg: Job completed", { + duration: `${duration}ms`, + processed: payloads.length, + syncResults, + }); + + return new HttpSuccess({ + message: "Update org completed", + processed: payloads.length, + syncResults, + duration: `${duration}ms`, + }); + } catch (error: any) { + const duration = Date.now() - startTime; + console.error("cronjobUpdateOrg: Job failed", { + duration: `${duration}ms`, + error: error.message, + stack: error.stack, + }); + throw new HttpError(HttpStatus.INTERNAL_SERVER_ERROR, "Internal server error"); + } finally { + this.isRunning = false; + } + } + + /** + * Build payloads from PosMaster, EmployeePosMaster, and EmployeeTempPosMaster records + * Includes proper profile type tracking for accurate Keycloak sync + */ + private buildPayloads( + posMasters: PosMaster[], + posMasterEmployee: EmployeePosMaster[], + posMasterEmployeeTemp: EmployeeTempPosMaster[], + ): OrgUpdatePayload[] { + const payloads: OrgUpdatePayload[] = []; + + // Process PosMaster records (PROFILE type) + for (const posMaster of posMasters) { + if (posMaster.current_holder && posMaster.current_holderId) { + payloads.push({ + profileId: posMaster.current_holderId, + rootDnaId: posMaster.orgRoot?.ancestorDNA || null, + child1DnaId: posMaster.orgChild1?.ancestorDNA || null, + child2DnaId: posMaster.orgChild2?.ancestorDNA || null, + child3DnaId: posMaster.orgChild3?.ancestorDNA || null, + child4DnaId: posMaster.orgChild4?.ancestorDNA || null, + profileType: "PROFILE", + }); + } + } + + // Process EmployeePosMaster records (PROFILE_EMPLOYEE type) + for (const employeePos of posMasterEmployee) { + if (employeePos.current_holder && employeePos.current_holderId) { + payloads.push({ + profileId: employeePos.current_holderId, + rootDnaId: employeePos.orgRoot?.ancestorDNA || null, + child1DnaId: employeePos.orgChild1?.ancestorDNA || null, + child2DnaId: employeePos.orgChild2?.ancestorDNA || null, + child3DnaId: employeePos.orgChild3?.ancestorDNA || null, + child4DnaId: employeePos.orgChild4?.ancestorDNA || null, + profileType: "PROFILE_EMPLOYEE", + }); + } + } + + // Process EmployeeTempPosMaster records (PROFILE_EMPLOYEE type) + for (const employeeTempPos of posMasterEmployeeTemp) { + if (employeeTempPos.current_holder && employeeTempPos.current_holderId) { + payloads.push({ + profileId: employeeTempPos.current_holderId, + rootDnaId: employeeTempPos.orgRoot?.ancestorDNA || null, + child1DnaId: employeeTempPos.orgChild1?.ancestorDNA || null, + child2DnaId: employeeTempPos.orgChild2?.ancestorDNA || null, + child3DnaId: employeeTempPos.orgChild3?.ancestorDNA || null, + child4DnaId: employeeTempPos.orgChild4?.ancestorDNA || null, + profileType: "PROFILE_EMPLOYEE", + }); + } + } + + return payloads; + } + + /** + * Group profile IDs by their type for separate Keycloak sync calls + */ + private groupProfileIdsByType(payloads: OrgUpdatePayload[]): Record { + const grouped: Record = { + PROFILE: [], + PROFILE_EMPLOYEE: [], + }; + + for (const payload of payloads) { + grouped[payload.profileType].push(payload.profileId); + } + + // Remove empty groups and deduplicate IDs within each group + const result: Record = {}; + for (const [type, ids] of Object.entries(grouped)) { + if (ids.length > 0) { + // Deduplicate while preserving order + result[type] = Array.from(new Set(ids)); + } + } + + return result; + } + + /** + * Split array into chunks of specified size + */ + private chunkArray(array: T[], chunkSize: number): T[][] { + const chunks: T[][] = []; + for (let i = 0; i < array.length; i += chunkSize) { + chunks.push(array.slice(i, i + chunkSize)); + } + return chunks; + } +} diff --git a/src/controllers/UserController.ts b/src/controllers/UserController.ts index ecfef0ca..afc686e6 100644 --- a/src/controllers/UserController.ts +++ b/src/controllers/UserController.ts @@ -33,6 +33,7 @@ import { getUserByUsername, changeUserPassword, resetPassword, + updateUserAttributes, } from "../keycloak"; import { AppDataSource } from "../database/data-source"; import { Profile } from "../entities/Profile"; @@ -136,7 +137,15 @@ export class KeycloakController extends Controller { profile.keycloak = userId; } profile.email = body.email == null ? _null : body.email; + profile.isDelete = false; await this.profileRepo.save(profile); + + // Update Keycloak with profile prefix after profile is loaded + await updateUserAttributes(userId, { + profileId: [profile.id], + prefix: [profile.prefix || ""], + }); + if (body.roles != null && body.roles.length > 0) { const roleKeycloak = await this.roleKeycloakRepo.find({ where: { id: In(body.roles) }, @@ -194,7 +203,14 @@ export class KeycloakController extends Controller { profile.keycloak = userId; } profile.email = body.email == null ? _null : body.email; + profile.isDelete = false; await this.profileEmpRepo.save(profile); + // Update Keycloak with profile prefix after profile is loaded + await updateUserAttributes(userId, { + profileId: [profile.id], + prefix: [profile.prefix || ""], + }); + if (body.roles != null && body.roles.length > 0) { const roleKeycloak = await this.roleKeycloakRepo.find({ where: { id: In(body.roles) }, @@ -260,14 +276,18 @@ export class KeycloakController extends Controller { }); if (!profileEmp) { } else { - const _null: any = null; - profileEmp.keycloak = _null; + // Task #228 + // const _null: any = null; + // profileEmp.keycloak = _null; + profileEmp.isDelete = true; profileEmp.roleKeycloaks = []; await this.profileEmpRepo.save(profileEmp); } } else { - const _null: any = null; - profile.keycloak = _null; + // Task #228 + // const _null: any = null; + // profile.keycloak = _null; + profile.isDelete = true; profile.roleKeycloaks = []; await this.profileRepo.save(profile); return new HttpSuccess(); @@ -475,14 +495,14 @@ export class KeycloakController extends Controller { @Request() req: RequestWithUser, @Body() body: { - page: number, - pageSize: number, - keyword: string | null, - type: string, - isAll: boolean, - node: number | null, - nodeId: string | null, - } + page: number; + pageSize: number; + keyword: string | null; + type: string; + isAll: boolean; + node: number | null; + nodeId: string | null; + }, ) { let checkChildFromRole: any = {}; @@ -553,15 +573,20 @@ export class KeycloakController extends Controller { .leftJoinAndSelect("current_holders.orgChild3", "orgChild3") .leftJoinAndSelect("current_holders.orgChild4", "orgChild4") .where("profile.keycloak IS NOT NULL AND profile.keycloak != ''") + .andWhere("profile.isDelete = :isDelete", { isDelete: false }) .andWhere(checkChildFromRole) .andWhere(conditions) .andWhere( new Brackets((qb) => { qb.orWhere( - body.keyword != null && body.keyword != "" ? `profile.citizenId like '%${body.keyword}%'` : "1=1", + body.keyword != null && body.keyword != "" + ? `profile.citizenId like '%${body.keyword}%'` + : "1=1", ) .orWhere( - body.keyword != null && body.keyword != "" ? `profile.email like '%${body.keyword}%'` : "1=1", + body.keyword != null && body.keyword != "" + ? `profile.email like '%${body.keyword}%'` + : "1=1", ) .orWhere( body.keyword != null && body.keyword != "" @@ -592,6 +617,7 @@ export class KeycloakController extends Controller { .leftJoinAndSelect("current_holders.orgChild3", "orgChild3") .leftJoinAndSelect("current_holders.orgChild4", "orgChild4") .where("profileEmployee.keycloak IS NOT NULL AND profileEmployee.keycloak != ''") + .andWhere("profileEmployee.isDelete = :isDelete", { isDelete: false }) .andWhere(checkChildFromRole) .andWhere(conditions) .andWhere({ employeeClass: "PERM" }) @@ -736,7 +762,14 @@ export class KeycloakController extends Controller { profile.keycloak = userId; } profile.email = body.email == null ? _null : body.email; + profile.isDelete = false; await this.profileEmpRepo.save(profile); + // Update Keycloak with profile prefix after profile is loaded + await updateUserAttributes(userId, { + profileId: [profile.id], + prefix: [profile.prefix || ""], + }); + if (body.roles != null && body.roles.length > 0) { const roleKeycloak = await this.roleKeycloakRepo.find({ where: { id: In(body.roles) }, @@ -783,7 +816,6 @@ export class KeycloakController extends Controller { @Get("user/role/{id}") async getRoleUser(@Request() req: RequestWithUser, @Path("id") id: string) { - const profile = await this.profileRepo.findOne({ where: { keycloak: id }, relations: ["roleKeycloaks"], @@ -791,8 +823,8 @@ export class KeycloakController extends Controller { if ( req.user.sub === id && - req.user.role.some(x => x === 'ADMIN') && - !req.user.role.some(x => x === 'SUPER_ADMIN') + req.user.role.some((x) => x === "ADMIN") && + !req.user.role.some((x) => x === "SUPER_ADMIN") ) { throw new HttpError(HttpStatus.NOT_FOUND, "ไม่มีสิทธิ์เข้าถึงข้อมูลนี้"); } diff --git a/src/controllers/WorkflowController.ts b/src/controllers/WorkflowController.ts index f02aaf4b..8e9d2cd4 100644 --- a/src/controllers/WorkflowController.ts +++ b/src/controllers/WorkflowController.ts @@ -22,7 +22,7 @@ import { viewDirectorActing } from "../entities/view/viewDirectorActing"; import { viewDirector } from "../entities/view/viewDirector"; import { ProfileEmployee } from "../entities/ProfileEmployee"; import { EmployeePosMaster } from "../entities/EmployeePosMaster"; - +import { OrgRoot } from "../entities/OrgRoot"; @Route("api/v1/org/workflow") @Tags("Workflow") @Security("bearerAuth") @@ -34,7 +34,7 @@ export class WorkflowController extends Controller { private stateUserCommentRepo = AppDataSource.getRepository(StateUserComment); private profileRepo = AppDataSource.getRepository(Profile); private profileEmployeeRepo = AppDataSource.getRepository(ProfileEmployee); - + private orgRootRepo = AppDataSource.getRepository(OrgRoot); private metaWorkflowRepo = AppDataSource.getRepository(MetaWorkflow); private metaStateRepo = AppDataSource.getRepository(MetaState); private metaStateOperatorRepo = AppDataSource.getRepository(MetaStateOperator); @@ -150,16 +150,19 @@ export class WorkflowController extends Controller { if (body.sysName == "SYS_TRANSFER_REQ") { if (metaStateOp.operator == "PersonnelOfficer" && correspondingState?.order == 1) { return; - } - else if (metaStateOp.operator == "Officer" && [1, 2].includes(correspondingState?.order as number)) { - metaStateOp.operator = "PersonnelOfficer" + } else if ( + metaStateOp.operator == "Officer" && + [1, 2].includes(correspondingState?.order as number) + ) { + metaStateOp.operator = "PersonnelOfficer"; } } // Task #2208 กรณีขอแก้ไขข้อมูลทะเบียนประวัติ และ IDP และคนขออยู่ในสำนักปลัดกรุงเทพมหานคร - if (metaStateOp.operator == "Officer" && - (["REGISTRY_PROFILE", "REGISTRY_PROFILE_EMP", "REGISTRY_IDP"].includes(body.sysName)) + if ( + metaStateOp.operator == "Officer" && + ["REGISTRY_PROFILE", "REGISTRY_PROFILE_EMP", "REGISTRY_IDP"].includes(body.sysName) ) { - metaStateOp.operator = "PersonnelOfficer" + metaStateOp.operator = "PersonnelOfficer"; } } if (correspondingState) { @@ -895,6 +898,20 @@ export class WorkflowController extends Controller { throw new HttpError(HttpStatus.NOT_FOUND, "ไม่พบตำแหน่งผู้ใช้งาน"); } + // Task #2342 list ปลัด และรองปลัดใน popup ผู้บังคับบัญชา และผู้มีอำนาจเพิ่ม + const roodIds = [posMasterUser.orgRootId]; + const orgRoot = await this.orgRootRepo.findOne({ + select: { id: true, isDeputy: true }, + where: { + id: Not(posMasterUser.orgRootId), + isDeputy: true, + orgRevision: { orgRevisionIsCurrent: true, orgRevisionIsDraft: false }, + }, + }); + if (orgRoot && orgRoot.isDeputy) { + roodIds.push(orgRoot.id) + } + // 2. Pre-calculate conditions - ย้ายออกมาข้างนอก const posType = posMasterUser.current_holder?.posType?.posTypeName; const posLevel = posMasterUser.current_holder?.posLevel?.posLevelName; @@ -924,23 +941,23 @@ export class WorkflowController extends Controller { if (type.trim().toUpperCase() === "OPERATE" || body.type === "employee") { mainConditions = [ - { ...baseCondition, orgRootId: posMasterUser.orgRootId, orgChild1Id: IsNull() }, + { ...baseCondition, orgRootId: In(roodIds), orgChild1Id: IsNull() }, { ...baseCondition, - orgRootId: posMasterUser.orgRootId, + orgRootId: In(roodIds), orgChild1Id: posMasterUser.orgChild1Id, orgChild2Id: IsNull(), }, { ...baseCondition, - orgRootId: posMasterUser.orgRootId, + orgRootId: In(roodIds), orgChild1Id: posMasterUser.orgChild1Id, orgChild2Id: posMasterUser.orgChild2Id, orgChild3Id: IsNull(), }, { ...baseCondition, - orgRootId: posMasterUser.orgRootId, + orgRootId: In(roodIds), orgChild1Id: posMasterUser.orgChild1Id, orgChild2Id: posMasterUser.orgChild2Id, orgChild3Id: posMasterUser.orgChild3Id, @@ -948,7 +965,7 @@ export class WorkflowController extends Controller { }, { ...baseCondition, - orgRootId: posMasterUser.orgRootId, + orgRootId: In(roodIds), orgChild1Id: posMasterUser.orgChild1Id, orgChild2Id: posMasterUser.orgChild2Id, orgChild3Id: posMasterUser.orgChild3Id, @@ -959,7 +976,7 @@ export class WorkflowController extends Controller { mainConditions = [ { ...baseCondition, - orgRootId: posMasterUser.orgRootId, + orgRootId: In(roodIds), orgChild1Id: IsNull(), orgChild2Id: IsNull(), orgChild3Id: IsNull(), @@ -978,7 +995,7 @@ export class WorkflowController extends Controller { }, ]; } else { - mainConditions = [{ ...baseCondition, orgRootId: posMasterUser.orgRootId }]; + mainConditions = [{ ...baseCondition, orgRootId: In(roodIds) }]; } // 4. สร้าง optimized query builder @@ -1027,7 +1044,7 @@ export class WorkflowController extends Controller { if (body.sortBy) { queryBuilder = queryBuilder.orderBy( - `entity.${body.sortBy}`, + `entity.${body.sortBy === "posExecutiveNameOrg" ? "posExecutiveName" : body.sortBy}`, body.descending ? "DESC" : "ASC", ); } @@ -1042,14 +1059,12 @@ export class WorkflowController extends Controller { ]); // 8. ปรับ response mapping (ถ้าจำเป็น) - const processedData = body.isAct - ? data - : data.map((x: any) => ({ - ...x, - posExecutiveNameOrg: - (x.posExecutiveName ?? "") + - (x.orgChild4 ?? x.orgChild3 ?? x.orgChild2 ?? x.orgChild1 ?? x.orgRoot ?? ""), - })); + const processedData = data.map((x: any) => ({ + ...x, + posExecutiveNameOrg: + (x.posExecutiveName ?? "") + + (x.orgChild4 ?? x.orgChild3 ?? x.orgChild2 ?? x.orgChild1 ?? x.orgRoot ?? ""), + })); return new HttpSuccess({ data: processedData, total }); } @@ -1363,7 +1378,7 @@ export class WorkflowController extends Controller { keycloak: req.user.sub, }, }); - if (!profile) throw new HttpError(HttpStatus.NOT_FOUND, "ไม่พบข้อมูลผู้ใช้งาน"); + if (!profile) throw new HttpError(HttpStatus.UNAUTHORIZED, "ไม่พบข้อมูลผู้ใช้งาน"); const profileOfficer = await this.workflowRepo.findOne({ where: { @@ -1372,7 +1387,7 @@ export class WorkflowController extends Controller { // sysName: body.sysName, }, }); - if (!profileOfficer) throw new HttpError(HttpStatus.NOT_FOUND, "ไม่พบข้อมูลสิทธิ์"); + if (!profileOfficer) throw new HttpError(HttpStatus.FORBIDDEN, "ไม่พบข้อมูลสิทธิ์"); return new HttpSuccess(); } diff --git a/src/controllers/super-admin-{id}-bak.txt b/src/controllers/super-admin-{id}-bak.txt new file mode 100644 index 00000000..72475c82 --- /dev/null +++ b/src/controllers/super-admin-{id}-bak.txt @@ -0,0 +1,368 @@ + /** + * API รายละเอียดโครงสร้าง + * + * @summary ORG_023 - รายละเอียดโครงสร้าง (ADMIN) #25 + * + */ + @Get("super-admin/{id}") + async detailSuperAdmin(@Path() id: string, @Request() request: RequestWithUser) { + const orgRevision = await this.orgRevisionRepository.findOne({ + where: { id: id }, + }); + if (!orgRevision) return new HttpSuccess([]); + + let rootId: any = null; + if (!request.user.role.includes("SUPER_ADMIN")) { + const profile = await this.profileRepo.findOne({ + where: { + keycloak: request.user.sub, + }, + select: ["id"], + }); + if (profile == null) return new HttpSuccess([]); + + const posMaster = await this.posMasterRepository.findOne({ + where: + orgRevision.orgRevisionIsCurrent && !orgRevision.orgRevisionIsDraft + ? { + orgRevisionId: id, + current_holderId: profile.id, + } + : { + orgRevisionId: id, + next_holderId: profile.id, + }, + }); + if (!posMaster) return new HttpSuccess([]); + + rootId = posMaster.orgRootId; + } + + // OPTIMIZED: Get all position counts in ONE query (closed) + // const { orgRootMap, orgChild1Map, orgChild2Map, orgChild3Map, orgChild4Map, rootPosMap } = + // await getPositionCounts(id); + + // OPTIMIZED: Fetch orgRoot first, then fetch all child levels in parallel + const orgRootData = await AppDataSource.getRepository(OrgRoot) + .createQueryBuilder("orgRoot") + .where("orgRoot.orgRevisionId = :id", { id }) + .andWhere(rootId != null ? `orgRoot.id = :rootId` : "1=1", { + rootId: rootId, + }) + .orderBy("orgRoot.orgRootOrder", "ASC") + .getMany(); + + const orgRootIds = orgRootData.map((orgRoot) => orgRoot.id); + + // OPTIMIZED: Fetch all child levels in parallel using orgRevisionId + // This is faster than sequential queries that depend on parent IDs + const [orgChild1AllData, orgChild2AllData, orgChild3AllData, orgChild4AllData] = await Promise.all([ + AppDataSource.getRepository(OrgChild1) + .createQueryBuilder("orgChild1") + .where("orgChild1.orgRevisionId = :id", { id }) + .orderBy("orgChild1.orgChild1Order", "ASC") + .getMany(), + + AppDataSource.getRepository(OrgChild2) + .createQueryBuilder("orgChild2") + .where("orgChild2.orgRevisionId = :id", { id }) + .orderBy("orgChild2.orgChild2Order", "ASC") + .getMany(), + + AppDataSource.getRepository(OrgChild3) + .createQueryBuilder("orgChild3") + .where("orgChild3.orgRevisionId = :id", { id }) + .orderBy("orgChild3.orgChild3Order", "ASC") + .getMany(), + + AppDataSource.getRepository(OrgChild4) + .createQueryBuilder("orgChild4") + .where("orgChild4.orgRevisionId = :id", { id }) + .orderBy("orgChild4.orgChild4Order", "ASC") + .getMany(), + ]); + + // Filter child1 data by orgRootIds (maintains backward compatibility) + const orgChild1Data = orgRootIds && orgRootIds.length > 0 + ? orgChild1AllData.filter((orgChild1) => orgRootIds.includes(orgChild1.orgRootId)) + : []; + + // Build maps for efficient filtering of deeper levels + const orgChild1Ids = orgChild1Data.map((orgChild1) => orgChild1.id); + const orgChild2Data = orgChild1Ids && orgChild1Ids.length > 0 + ? orgChild2AllData.filter((orgChild2) => orgChild1Ids.includes(orgChild2.orgChild1Id)) + : []; + + const orgChild2Ids = orgChild2Data.map((orgChild2) => orgChild2.id); + const orgChild3Data = orgChild2Ids && orgChild2Ids.length > 0 + ? orgChild3AllData.filter((orgChild3) => orgChild2Ids.includes(orgChild3.orgChild2Id)) + : []; + + const orgChild3Ids = orgChild3Data.map((orgChild3) => orgChild3.id); + const orgChild4Data = orgChild3Ids && orgChild3Ids.length > 0 + ? orgChild4AllData.filter((orgChild4) => orgChild3Ids.includes(orgChild4.orgChild3Id)) + : []; + + // OPTIMIZED: Build formatted data using pre-calculated counts (no nested queries!) + const formattedData = orgRootData.map((orgRoot) => { + // const rootCounts = getCounts(orgRootMap, orgRoot.id); + // const rootPosCounts = getRootCounts(rootPosMap, orgRoot.id); + + return { + orgTreeId: orgRoot.id, + orgLevel: 0, + orgName: orgRoot.orgRootName, + orgTreeName: orgRoot.orgRootName, + orgTreeShortName: orgRoot.orgRootShortName, + orgTreeCode: orgRoot.orgRootCode, + orgCode: orgRoot.orgRootCode + "00", + orgTreeRank: orgRoot.orgRootRank, + orgTreeRankSub: orgRoot.orgRootRankSub, + orgRootDnaId: orgRoot.ancestorDNA, + DEPARTMENT_CODE: orgRoot.DEPARTMENT_CODE, + DIVISION_CODE: orgRoot.DIVISION_CODE, + SECTION_CODE: orgRoot.SECTION_CODE, + JOB_CODE: orgRoot.JOB_CODE, + orgTreeOrder: orgRoot.orgRootOrder, + orgTreePhoneEx: orgRoot.orgRootPhoneEx, + orgTreePhoneIn: orgRoot.orgRootPhoneIn, + orgTreeFax: orgRoot.orgRootFax, + orgRevisionId: orgRoot.orgRevisionId, + orgRootName: orgRoot.orgRootName, + isDeputy: orgRoot.isDeputy, + isCommission: orgRoot.isCommission, + responsibility: orgRoot.responsibility, + labelName: + orgRoot.orgRootName + " " + orgRoot.orgRootCode + "00" + " " + orgRoot.orgRootShortName, + // totalPosition: rootCounts.totalPosition, + // totalPositionCurrentUse: rootCounts.totalPositionCurrentUse, + // totalPositionCurrentVacant: rootCounts.totalPositionCurrentVacant, + // totalPositionNextUse: rootCounts.totalPositionNextUse, + // totalPositionNextVacant: rootCounts.totalPositionNextVacant, + // totalRootPosition: rootPosCounts.totalRootPosition, + // totalRootPositionCurrentUse: rootPosCounts.totalRootPositionCurrentUse, + // totalRootPositionCurrentVacant: rootPosCounts.totalRootPositionCurrentVacant, + // totalRootPositionNextUse: rootPosCounts.totalRootPositionNextUse, + // totalRootPositionNextVacant: rootPosCounts.totalRootPositionNextVacant, + children: orgChild1Data + .filter((orgChild1) => orgChild1.orgRootId === orgRoot.id) + .map((orgChild1) => { + // const child1Counts = getCounts(orgChild1Map, orgChild1.id); + // const child1PosKey = `${orgRoot.id}-${orgChild1.id}`; + // const child1PosCounts = getRootCounts(rootPosMap, child1PosKey); + + return { + orgTreeId: orgChild1.id, + orgRootId: orgRoot.id, + orgLevel: 1, + orgName: `${orgChild1.orgChild1Name}/${orgRoot.orgRootName}`, + orgTreeName: orgChild1.orgChild1Name, + orgTreeShortName: orgChild1.orgChild1ShortName, + orgTreeCode: orgChild1.orgChild1Code, + orgCode: orgRoot.orgRootCode + orgChild1.orgChild1Code, + orgTreeRank: orgChild1.orgChild1Rank, + orgTreeRankSub: orgChild1.orgChild1RankSub, + orgRootDnaId: orgRoot.ancestorDNA, + orgChild1DnaId: orgChild1.ancestorDNA, + DEPARTMENT_CODE: orgChild1.DEPARTMENT_CODE, + DIVISION_CODE: orgChild1.DIVISION_CODE, + SECTION_CODE: orgChild1.SECTION_CODE, + JOB_CODE: orgChild1.JOB_CODE, + orgTreeOrder: orgChild1.orgChild1Order, + orgRootCode: orgRoot.orgRootCode, + orgTreePhoneEx: orgChild1.orgChild1PhoneEx, + orgTreePhoneIn: orgChild1.orgChild1PhoneIn, + orgTreeFax: orgChild1.orgChild1Fax, + orgRevisionId: orgRoot.orgRevisionId, + orgRootName: orgRoot.orgRootName, + responsibility: orgChild1.responsibility, + isOfficer: orgChild1.isOfficer, + isInformation: orgChild1.isInformation, + labelName: + orgChild1.orgChild1Name + + " " + + orgRoot.orgRootCode + + orgChild1.orgChild1Code + + " " + + orgChild1.orgChild1ShortName, + // totalPosition: child1Counts.totalPosition, + // totalPositionCurrentUse: child1Counts.totalPositionCurrentUse, + // totalPositionCurrentVacant: child1Counts.totalPositionCurrentVacant, + // totalPositionNextUse: child1Counts.totalPositionNextUse, + // totalPositionNextVacant: child1Counts.totalPositionNextVacant, + // totalRootPosition: child1PosCounts.totalRootPosition, + // totalRootPositionCurrentUse: child1PosCounts.totalRootPositionCurrentUse, + // totalRootPositionCurrentVacant: child1PosCounts.totalRootPositionCurrentVacant, + // totalRootPositionNextUse: child1PosCounts.totalRootPositionNextUse, + // totalRootPositionNextVacant: child1PosCounts.totalRootPositionNextVacant, + children: orgChild2Data + .filter((orgChild2) => orgChild2.orgChild1Id === orgChild1.id) + .map((orgChild2) => { + // const child2Counts = getCounts(orgChild2Map, orgChild2.id); + // const child2PosKey = `${orgRoot.id}-${orgChild1.id}-${orgChild2.id}`; + // const child2PosCounts = getRootCounts(rootPosMap, child2PosKey); + + return { + orgTreeId: orgChild2.id, + orgRootId: orgChild1.id, + orgLevel: 2, + orgName: `${orgChild2.orgChild2Name}/${orgChild1.orgChild1Name}/${orgRoot.orgRootName}`, + orgTreeName: orgChild2.orgChild2Name, + orgTreeShortName: orgChild2.orgChild2ShortName, + orgTreeCode: orgChild2.orgChild2Code, + orgCode: orgRoot.orgRootCode + orgChild2.orgChild2Code, + orgTreeRank: orgChild2.orgChild2Rank, + orgTreeRankSub: orgChild2.orgChild2RankSub, + orgRootDnaId: orgRoot.ancestorDNA, + orgChild1DnaId: orgChild1.ancestorDNA, + orgChild2DnaId: orgChild2.ancestorDNA, + DEPARTMENT_CODE: orgChild2.DEPARTMENT_CODE, + DIVISION_CODE: orgChild2.DIVISION_CODE, + SECTION_CODE: orgChild2.SECTION_CODE, + JOB_CODE: orgChild2.JOB_CODE, + orgTreeOrder: orgChild2.orgChild2Order, + orgRootCode: orgRoot.orgRootCode, + orgTreePhoneEx: orgChild2.orgChild2PhoneEx, + orgTreePhoneIn: orgChild2.orgChild2PhoneIn, + orgTreeFax: orgChild2.orgChild2Fax, + orgRevisionId: orgRoot.orgRevisionId, + orgRootName: orgRoot.orgRootName, + responsibility: orgChild2.responsibility, + labelName: + orgChild2.orgChild2Name + + " " + + orgRoot.orgRootCode + + orgChild2.orgChild2Code + + " " + + orgChild2.orgChild2ShortName, + // totalPosition: child2Counts.totalPosition, + // totalPositionCurrentUse: child2Counts.totalPositionCurrentUse, + // totalPositionCurrentVacant: child2Counts.totalPositionCurrentVacant, + // totalPositionNextUse: child2Counts.totalPositionNextUse, + // totalPositionNextVacant: child2Counts.totalPositionNextVacant, + // totalRootPosition: child2PosCounts.totalRootPosition, + // totalRootPositionCurrentUse: child2PosCounts.totalRootPositionCurrentUse, + // totalRootPositionCurrentVacant: child2PosCounts.totalRootPositionCurrentVacant, + // totalRootPositionNextUse: child2PosCounts.totalRootPositionNextUse, + // totalRootPositionNextVacant: child2PosCounts.totalRootPositionNextVacant, + children: orgChild3Data + .filter((orgChild3) => orgChild3.orgChild2Id === orgChild2.id) + .map((orgChild3) => { + // const child3Counts = getCounts(orgChild3Map, orgChild3.id); + // const child3PosKey = `${orgRoot.id}-${orgChild1.id}-${orgChild2.id}-${orgChild3.id}`; + // const child3PosCounts = getRootCounts(rootPosMap, child3PosKey); + + return { + orgTreeId: orgChild3.id, + orgRootId: orgChild2.id, + orgLevel: 3, + orgName: `${orgChild3.orgChild3Name}/${orgChild2.orgChild2Name}/${orgChild1.orgChild1Name}/${orgRoot.orgRootName}`, + orgTreeName: orgChild3.orgChild3Name, + orgTreeShortName: orgChild3.orgChild3ShortName, + orgTreeCode: orgChild3.orgChild3Code, + orgCode: orgRoot.orgRootCode + orgChild3.orgChild3Code, + orgTreeRank: orgChild3.orgChild3Rank, + orgTreeRankSub: orgChild3.orgChild3RankSub, + orgRootDnaId: orgRoot.ancestorDNA, + orgChild1DnaId: orgChild1.ancestorDNA, + orgChild2DnaId: orgChild2.ancestorDNA, + orgChild3DnaId: orgChild3.ancestorDNA, + DEPARTMENT_CODE: orgChild3.DEPARTMENT_CODE, + DIVISION_CODE: orgChild3.DIVISION_CODE, + SECTION_CODE: orgChild3.SECTION_CODE, + JOB_CODE: orgChild3.JOB_CODE, + orgTreeOrder: orgChild3.orgChild3Order, + orgRootCode: orgRoot.orgRootCode, + orgTreePhoneEx: orgChild3.orgChild3PhoneEx, + orgTreePhoneIn: orgChild3.orgChild3PhoneIn, + orgTreeFax: orgChild3.orgChild3Fax, + orgRevisionId: orgRoot.orgRevisionId, + orgRootName: orgRoot.orgRootName, + responsibility: orgChild3.responsibility, + labelName: + orgChild3.orgChild3Name + + " " + + orgRoot.orgRootCode + + orgChild3.orgChild3Code + + " " + + orgChild3.orgChild3ShortName, + // totalPosition: child3Counts.totalPosition, + // totalPositionCurrentUse: child3Counts.totalPositionCurrentUse, + // totalPositionCurrentVacant: child3Counts.totalPositionCurrentVacant, + // totalPositionNextUse: child3Counts.totalPositionNextUse, + // totalPositionNextVacant: child3Counts.totalPositionNextVacant, + // totalRootPosition: child3PosCounts.totalRootPosition, + // totalRootPositionCurrentUse: child3PosCounts.totalRootPositionCurrentUse, + // totalRootPositionCurrentVacant: + // child3PosCounts.totalRootPositionCurrentVacant, + // totalRootPositionNextUse: child3PosCounts.totalRootPositionNextUse, + // totalRootPositionNextVacant: child3PosCounts.totalRootPositionNextVacant, + children: orgChild4Data + .filter((orgChild4) => orgChild4.orgChild3Id === orgChild3.id) + .map((orgChild4) => { + // const child4Counts = getCounts(orgChild4Map, orgChild4.id); + // const child4PosKey = `${orgRoot.id}-${orgChild1.id}-${orgChild2.id}-${orgChild3.id}-${orgChild4.id}`; + // const child4PosCounts = getRootCounts(rootPosMap, child4PosKey); + + return { + orgTreeId: orgChild4.id, + orgRootId: orgChild3.id, + orgLevel: 4, + orgName: `${orgChild4.orgChild4Name}/${orgChild3.orgChild3Name}/${orgChild2.orgChild2Name}/${orgChild1.orgChild1Name}/${orgRoot.orgRootName}`, + orgTreeName: orgChild4.orgChild4Name, + orgTreeShortName: orgChild4.orgChild4ShortName, + orgTreeCode: orgChild4.orgChild4Code, + orgCode: orgRoot.orgRootCode + orgChild4.orgChild4Code, + orgTreeRank: orgChild4.orgChild4Rank, + orgTreeRankSub: orgChild4.orgChild4RankSub, + orgRootDnaId: orgRoot.ancestorDNA, + orgChild1DnaId: orgChild1.ancestorDNA, + orgChild2DnaId: orgChild2.ancestorDNA, + orgChild3DnaId: orgChild3.ancestorDNA, + orgChild4DnaId: orgChild4.ancestorDNA, + DEPARTMENT_CODE: orgChild4.DEPARTMENT_CODE, + DIVISION_CODE: orgChild4.DIVISION_CODE, + SECTION_CODE: orgChild4.SECTION_CODE, + JOB_CODE: orgChild4.JOB_CODE, + orgTreeOrder: orgChild4.orgChild4Order, + orgRootCode: orgRoot.orgRootCode, + orgTreePhoneEx: orgChild4.orgChild4PhoneEx, + orgTreePhoneIn: orgChild4.orgChild4PhoneIn, + orgTreeFax: orgChild4.orgChild4Fax, + orgRevisionId: orgRoot.orgRevisionId, + orgRootName: orgRoot.orgRootName, + responsibility: orgChild4.responsibility, + labelName: + orgChild4.orgChild4Name + + " " + + orgRoot.orgRootCode + + orgChild4.orgChild4Code + + " " + + orgChild4.orgChild4ShortName, + // totalPosition: child4Counts.totalPosition, + // totalPositionCurrentUse: child4Counts.totalPositionCurrentUse, + // totalPositionCurrentVacant: child4Counts.totalPositionCurrentVacant, + // totalPositionNextUse: child4Counts.totalPositionNextUse, + // totalPositionNextVacant: child4Counts.totalPositionNextVacant, + // totalRootPosition: child4PosCounts.totalRootPosition, + // totalRootPositionCurrentUse: + // child4PosCounts.totalRootPositionCurrentUse, + // totalRootPositionCurrentVacant: + // child4PosCounts.totalRootPositionCurrentVacant, + // totalRootPositionNextUse: child4PosCounts.totalRootPositionNextUse, + // totalRootPositionNextVacant: + // child4PosCounts.totalRootPositionNextVacant, + children: [], + }; + }), + }; + }), + }; + }), + }; + }), + }; + }); + + return new HttpSuccess(formattedData); + } diff --git a/src/database/data-source.ts b/src/database/data-source.ts index db5afc0d..e44252a1 100644 --- a/src/database/data-source.ts +++ b/src/database/data-source.ts @@ -47,8 +47,8 @@ export const AppDataSource = new DataSource({ logging: true, // timezone: "Z", entities: - process.env.NODE_ENV !== "production" - ? ["src/entities/**/*.ts"] + process.env.NODE_ENV !== "production" + ? ["src/entities/**/*.ts"] : ["dist/entities/**/*{.ts,.js}"], migrations: process.env.NODE_ENV !== "production" @@ -56,6 +56,16 @@ export const AppDataSource = new DataSource({ : ["dist/migration/**/*{.ts,.js}"], subscribers: [], logger: new MyCustomLogger(), + // Connection pool settings to prevent connection exhaustion + extra: { + connectionLimit: +(process.env.DB_CONNECTION_LIMIT || 50), + maxIdle: +(process.env.DB_MAX_IDLE || 10), + idleTimeout: +(process.env.DB_IDLE_TIMEOUT || 60000), + timezone: "+07:00", // Bangkok timezone (UTC+7) + }, + // TypeORM pool settings + poolSize: +(process.env.DB_POOL_SIZE || 10), + maxQueryExecutionTime: +(process.env.DB_MAX_QUERY_TIME || 3000), }); // export default database; diff --git a/src/entities/Command.ts b/src/entities/Command.ts index 029f4cdc..c6b26626 100644 --- a/src/entities/Command.ts +++ b/src/entities/Command.ts @@ -4,6 +4,7 @@ import { CommandType } from "./CommandType"; import { CommandSend } from "./CommandSend"; import { CommandSalary } from "./CommandSalary"; import { CommandRecive } from "./CommandRecive"; +import { CommandOperator } from "./CommandOperator"; import { ProfileSalary } from "./ProfileSalary"; import { ProfileSalaryHistory } from "./ProfileSalaryHistory"; import { CommandSign } from "./CommandSign"; @@ -165,6 +166,9 @@ export class Command extends EntityBase { @OneToMany(() => CommandRecive, (commandRecive) => commandRecive.command) commandRecives: CommandRecive[]; + @OneToMany(() => CommandOperator, (commandOperator) => commandOperator.command) + commandOperators: CommandOperator[]; + @OneToMany(() => ProfileSalary, (profileSalary) => profileSalary.command) profileSalarys: ProfileSalary[]; diff --git a/src/entities/CommandOperator.ts b/src/entities/CommandOperator.ts new file mode 100644 index 00000000..f2d42a6b --- /dev/null +++ b/src/entities/CommandOperator.ts @@ -0,0 +1,117 @@ +import { Entity, Column, JoinColumn, ManyToOne, Double } from "typeorm"; +import { EntityBase } from "./base/Base"; +import { Command } from "./Command"; + +@Entity("commandOperator") +export class CommandOperator extends EntityBase { + @Column({ + nullable: true, + comment: "คีย์นอก(FK)ของตาราง profile", + length: 40, + default: null, + }) + profileId: string; + + @Column({ + nullable: true, + comment: "คำนำหน้า", + length: 255, + default: null, + }) + prefix: string; + + @Column({ + nullable: true, + comment: "ชื่อ", + length: 255, + default: null, + }) + firstName: string; + + @Column({ + nullable: true, + comment: "สกุล", + length: 255, + default: null, + }) + lastName: string; + + @Column({ + nullable: true, + comment: "เลขที่ตำแหน่ง", + length: 255, + default: null, + }) + posNo: string; + + @Column({ + nullable: true, + comment: "ประเภท", + length: 255, + default: null, + }) + posType: string; + + @Column({ + nullable: true, + comment: "ระดับ", + length: 255, + default: null, + }) + posLevel: string; + + @Column({ + nullable: true, + comment: "ตำแหน่งในสายงาน", + length: 255, + default: null, + }) + position: string; + + @Column({ + nullable: true, + comment: "ตำแหน่งทางการบริหาร", + length: 255, + default: null, + }) + positionExecutive: string; + + @Column({ + nullable: true, + comment: "บทบาทของเจ้าหน้าที่ดำเนินการ เช่น ผอ.สกจ. / รักษาการ ผอ.สกจ. / ผอ. กบห. / รักษาการ ผอ. กบห. / ผอ. ส่วน", + length: 255, + default: null, + }) + roleName: string; + + @Column({ + nullable: true, + comment: "ลำดับบทบาทของเจ้าหน้าที่ดำเนินการ", + default: null, + }) + orderNo: number; + + @Column({ + length: 40, + comment: "คีย์นอก(FK)ของตาราง command", + }) + commandId: string; + + @ManyToOne(() => Command, (command) => command.commandOperators) + @JoinColumn({ name: "commandId" }) + command: Command; + +} + +export class CreateCommandOperatorDto { + profileId: string; + prefix?: string; + firstName?: string; + lastName?: string; + posNo?: string; + posType?: string; + posLevel?: string; + position?: string; + positionExecutive?: string; + roleName: string; +} \ No newline at end of file diff --git a/src/entities/EmployeePosLevel.ts b/src/entities/EmployeePosLevel.ts index 39ba8a99..a9f67125 100644 --- a/src/entities/EmployeePosLevel.ts +++ b/src/entities/EmployeePosLevel.ts @@ -14,9 +14,13 @@ enum EmployeePosLevelAuthoritys { export class EmployeePosLevel extends EntityBase { @Column({ comment: "ชื่อระดับชั้นงาน", - type: "int", + // type: "int", + nullable: true, + length: 255, + default: null, }) - posLevelName: number; + // posLevelName: number; + posLevelName: string; @Column({ comment: "ระดับของระดับชั้นงาน", @@ -52,11 +56,14 @@ export class EmployeePosLevel extends EntityBase { @OneToMany(() => ProfileEmployee, (profile) => profile.posLevel) profiles: ProfileEmployee[]; + + @OneToMany(() => ProfileEmployee, (profile) => profile.posLevelTemp) + profilesTemp: ProfileEmployee[]; } export class CreateEmployeePosLevel { @Column() - posLevelName: number; + posLevelName: string; @Column() posLevelRank: number; diff --git a/src/entities/EmployeePosMaster.ts b/src/entities/EmployeePosMaster.ts index c1f0e143..d76b3e01 100644 --- a/src/entities/EmployeePosMaster.ts +++ b/src/entities/EmployeePosMaster.ts @@ -193,6 +193,20 @@ export class EmployeePosMaster extends EntityBase { }) authRoleId: string; + @Column({ + comment: "ตำแหน่งติดเงื่อนไข", + default: false, + }) + isCondition: boolean; + + @Column({ + nullable: true, + comment: "หมายเหตุตำแหน่งติดเงื่อนไข", + type: "text", + default: null, + }) + conditionReason: string; + @ManyToOne(() => AuthRole, (authRole) => authRole.posMasterEmps) @JoinColumn({ name: "authRoleId" }) authRole: AuthRole; diff --git a/src/entities/EmployeePosType.ts b/src/entities/EmployeePosType.ts index 2fd02b2e..3e827fe6 100644 --- a/src/entities/EmployeePosType.ts +++ b/src/entities/EmployeePosType.ts @@ -39,6 +39,9 @@ export class EmployeePosType extends EntityBase { @OneToMany(() => ProfileEmployee, (profile) => profile.posType) profiles: ProfileEmployee[]; + + @OneToMany(() => ProfileEmployee, (profile) => profile.posTypeTemp) + profilesTemp: ProfileEmployee[]; } export class CreateEmployeePosType { diff --git a/src/entities/Issues.ts b/src/entities/Issues.ts index 8bf3143e..aff597e1 100644 --- a/src/entities/Issues.ts +++ b/src/entities/Issues.ts @@ -30,6 +30,12 @@ export class Issues extends EntityBase { @Column({ type: "text", nullable: true, comment: "หมายเหตุ" }) remark: string | null; + @Column({ type: "varchar", nullable: true, length: 255, comment: "อีเมลผู้รายงาน" }) + email: string | null; + + @Column({ type: "varchar", nullable: true, length: 20, comment: "เบอร์โทรผู้รายงาน" }) + phone: string | null; + @Column({ type: "enum", enum: ["NEW", "IN_PROGRESS", "RESOLVED", "CLOSED"], @@ -76,6 +82,8 @@ export interface IssueResponse { lastUpdatedAt: Date; createdFullName: string; lastUpdateFullName: string; + email: string | null; + phone: string | null; } export interface CreateIssueRequest { @@ -85,6 +93,8 @@ export interface CreateIssueRequest { status?: "NEW" | "IN_PROGRESS" | "RESOLVED" | "CLOSED"; menu?: string; org?: string; + email?: string; + phone?: string; } export interface UpdateIssueRequest { diff --git a/src/entities/Profile.ts b/src/entities/Profile.ts index 994fcfbe..72a1d505 100644 --- a/src/entities/Profile.ts +++ b/src/entities/Profile.ts @@ -50,6 +50,7 @@ import { ProfileAssistance } from "./ProfileAssistance"; import { ProfileSalaryTemp } from "./ProfileSalaryTemp"; import { PositionSalaryEditHistory } from "./PositionSalaryEditHistory"; import { ProfileChangeName } from "./ProfileChangeName"; +import { ProfileAbsentLate } from "./ProfileAbsentLate"; @Entity("profile") export class Profile extends EntityBase { @@ -57,15 +58,17 @@ export class Profile extends EntityBase { nullable: true, comment: "รูปถ่าย", default: null, + type: "varchar", }) - avatar: string; + avatar: string | null; @Column({ nullable: true, comment: "รูปถ่าย", default: null, + type: "varchar", }) - avatarName: string; + avatarName: string | null; @Column({ nullable: true, @@ -185,6 +188,12 @@ export class Profile extends EntityBase { }) keycloak: string; + @Column({ + comment: "สถานะการถูกลบผู้ใช้งานใน keycloak", + default: false, + }) + isDelete: boolean; + @Column({ comment: "ทดลองปฏิบัติหน้าที่", default: false, @@ -544,6 +553,9 @@ export class Profile extends EntityBase { @OneToMany(() => ProfileChangeName, (v) => v.profile) profileChangeNames: ProfileChangeName[]; + @OneToMany(() => ProfileAbsentLate, (v) => v.profile) + profileAbsentLates: ProfileAbsentLate[]; + @ManyToOne(() => PosLevel, (posLevel) => posLevel.profiles) @JoinColumn({ name: "posLevelId" }) posLevel: PosLevel; diff --git a/src/entities/ProfileAbility.ts b/src/entities/ProfileAbility.ts index d2ea83d4..dcba7ee1 100644 --- a/src/entities/ProfileAbility.ts +++ b/src/entities/ProfileAbility.ts @@ -82,6 +82,13 @@ export class ProfileAbility extends EntityBase { }) isEntry: boolean; + @Column({ + nullable: false, + comment: "สถานะลบข้อมูล", + default: false, + }) + isDeleted: boolean; + @OneToMany( () => ProfileAbilityHistory, (profileAbilityHistory) => profileAbilityHistory.histories, diff --git a/src/entities/ProfileAbilityHistory.ts b/src/entities/ProfileAbilityHistory.ts index 16b0de99..01aff27b 100644 --- a/src/entities/ProfileAbilityHistory.ts +++ b/src/entities/ProfileAbilityHistory.ts @@ -66,6 +66,13 @@ export class ProfileAbilityHistory extends EntityBase { }) profileAbilityId: string; + @Column({ + nullable: false, + comment: "สถานะลบข้อมูล", + default: false, + }) + isDeleted: boolean; + @ManyToOne(() => ProfileAbility, (profileAbility) => profileAbility.profileAbilityHistorys) @JoinColumn({ name: "profileAbilityId" }) histories: ProfileAbility; diff --git a/src/entities/ProfileAbsentLate.ts b/src/entities/ProfileAbsentLate.ts new file mode 100644 index 00000000..858412de --- /dev/null +++ b/src/entities/ProfileAbsentLate.ts @@ -0,0 +1,98 @@ +import { Entity, Column, ManyToOne, JoinColumn } from "typeorm"; +import { EntityBase } from "./base/Base"; +import { Profile } from "./Profile"; + +// Enums +export enum AbsentLateStatus { + LATE = "LATE", // มาสาย + ABSENT = "ABSENT", // ขาดราชการ +} + +export enum StampType { + FULL_DAY = "FULL_DAY", // เต็มวัน + MORNING = "MORNING", // ครึ่งเช้า + AFTERNOON = "AFTERNOON", // ครึ่งบ่าย +} + +@Entity("profileAbsentLate") +export class ProfileAbsentLate extends EntityBase { + @Column({ + nullable: true, + length: 40, + comment: "คีย์นอก(FK)ของตาราง Profile", + default: null, + }) + profileId: string; + + @Column({ + type: "enum", + enum: AbsentLateStatus, + comment: "สถานะ มาสาย/ขาดราชการ", + nullable: false, + }) + status: AbsentLateStatus; + + @Column({ + type: "datetime", + comment: "วันที่และเวลาที่ลงเวลา", + nullable: false, + }) + stampDate: Date; + + @Column({ + type: "enum", + enum: StampType, + comment: "เต็มวัน/ครึ่งเช้า/ครึ่งบ่าย", + default: StampType.FULL_DAY, + }) + stampType: StampType; + + @Column({ + type: "decimal", + precision: 2, + scale: 1, + comment: "จำนวน (1.0/0.5)", + default: "1.0", + }) + stampAmount: number; + + @Column({ + type: "varchar", + length: 250, + comment: "หมายเหตุ", + nullable: true, + }) + remark: string; + + @Column({ + comment: "สถานะลบข้อมูล", + default: false, + }) + isDeleted: boolean; + + @ManyToOne(() => Profile, (profile) => profile.profileAbsentLates) + @JoinColumn({ name: "profileId" }) + profile: Profile; +} + +// DTO Classes +export class CreateProfileAbsentLate { + profileId: string; + status: AbsentLateStatus; + stampDate: Date; + stampType?: StampType; + stampAmount?: number; + remark?: string; +} + +export class CreateProfileAbsentLateBatch { + records: CreateProfileAbsentLate[]; +} + +export type UpdateProfileAbsentLate = { + status?: AbsentLateStatus; + stampDate?: Date; + stampType?: StampType; + stampAmount?: number; + remark?: string; +}; diff --git a/src/entities/ProfileAbsentLateHistory.ts b/src/entities/ProfileAbsentLateHistory.ts new file mode 100644 index 00000000..7059bb23 --- /dev/null +++ b/src/entities/ProfileAbsentLateHistory.ts @@ -0,0 +1,20 @@ +import { Entity, Column } from "typeorm"; +import { + ProfileAbsentLate, + AbsentLateStatus, + StampType, +} from "./ProfileAbsentLate"; + +@Entity("profileAbsentLateHistory") +export class ProfileAbsentLateHistory extends ProfileAbsentLate { + @Column({ + nullable: true, + length: 40, + comment: "คีย์นอก(FK)ของตาราง ProfileAbsentLate", + default: null, + }) + profileAbsentLateId: string; +} + +// Export enums for re-use +export { AbsentLateStatus, StampType }; diff --git a/src/entities/ProfileActposition.ts b/src/entities/ProfileActposition.ts index bdfd2391..bde07e30 100644 --- a/src/entities/ProfileActposition.ts +++ b/src/entities/ProfileActposition.ts @@ -29,7 +29,7 @@ export class ProfileActposition extends EntityBase { comment: "วันที่สิ้นสุด", default: null, }) - dateEnd: Date; + dateEnd: Date | null; @Column({ nullable: true, @@ -86,6 +86,13 @@ export class ProfileActposition extends EntityBase { }) profileEmployeeId: string; + @Column({ + nullable: false, + comment: "สถานะลบข้อมูลรักษาการในตำแหน่ง", + default: false, + }) + isDeleted: boolean; + @OneToMany( () => ProfileActpositionHistory, (profileActpositionHistory) => profileActpositionHistory.histories, diff --git a/src/entities/ProfileActpositionHistory.ts b/src/entities/ProfileActpositionHistory.ts index bb2034e7..723831d0 100644 --- a/src/entities/ProfileActpositionHistory.ts +++ b/src/entities/ProfileActpositionHistory.ts @@ -48,6 +48,13 @@ export class ProfileActpositionHistory extends EntityBase { }) profileActpositionId: string; + @Column({ + nullable: false, + comment: "สถานะลบข้อมูลรักษาการในตำแหน่ง", + default: false, + }) + isDeleted: boolean; + @ManyToOne( () => ProfileActposition, (profileActposition) => profileActposition.profileActpositionHistorys, diff --git a/src/entities/ProfileAssessment.ts b/src/entities/ProfileAssessment.ts index 6ae060ed..f08ad25e 100644 --- a/src/entities/ProfileAssessment.ts +++ b/src/entities/ProfileAssessment.ts @@ -100,6 +100,13 @@ export class ProfileAssessment extends EntityBase { }) pointSumTotal: number; + @Column({ + nullable: false, + comment: "สถานะลบข้อมูล", + default: false, + }) + isDeleted: boolean; + @OneToMany( () => ProfileAssessmentHistory, (profileAssessmentHistory) => profileAssessmentHistory.histories, diff --git a/src/entities/ProfileAssessmentHistory.ts b/src/entities/ProfileAssessmentHistory.ts index 224ea49c..5bfe8524 100644 --- a/src/entities/ProfileAssessmentHistory.ts +++ b/src/entities/ProfileAssessmentHistory.ts @@ -96,6 +96,13 @@ export class ProfileAssessmentHistory extends EntityBase { }) profileAssessmentId: string; + @Column({ + nullable: false, + comment: "สถานะลบข้อมูล", + default: false, + }) + isDeleted: boolean; + @ManyToOne( () => ProfileAssessment, (profileAssessment) => profileAssessment.profileAssessmentHistorys, diff --git a/src/entities/ProfileAssistance.ts b/src/entities/ProfileAssistance.ts index 568552c0..06cd9ffb 100644 --- a/src/entities/ProfileAssistance.ts +++ b/src/entities/ProfileAssistance.ts @@ -43,7 +43,7 @@ export class ProfileAssistance extends EntityBase { comment: "วันสิ้นสุดการช่วยราชการ", default: null, }) - dateEnd: Date; + dateEnd: Date | null; @Column({ nullable: true, @@ -94,6 +94,13 @@ export class ProfileAssistance extends EntityBase { }) commandId: string; + @Column({ + nullable: false, + comment: "สถานะลบข้อมูล", + default: false, + }) + isDeleted: boolean; + @ManyToOne(() => Command, (command) => command.profileSalarys) @JoinColumn({ name: "commandId" }) command: Command; diff --git a/src/entities/ProfileAssistanceHistory.ts b/src/entities/ProfileAssistanceHistory.ts index 344c6dc0..8a106203 100644 --- a/src/entities/ProfileAssistanceHistory.ts +++ b/src/entities/ProfileAssistanceHistory.ts @@ -62,6 +62,13 @@ export class ProfileAssistanceHistory extends EntityBase { }) profileAssistanceId: string; + @Column({ + nullable: false, + comment: "สถานะลบข้อมูล", + default: false, + }) + isDeleted: boolean; + @ManyToOne( () => ProfileAssistance, (profileAssistance) => profileAssistance.profileAssistanceHistorys, diff --git a/src/entities/ProfileCertificate.ts b/src/entities/ProfileCertificate.ts index 730bde73..867c7797 100644 --- a/src/entities/ProfileCertificate.ts +++ b/src/entities/ProfileCertificate.ts @@ -74,6 +74,13 @@ export class ProfileCertificate extends EntityBase { }) isEntry: boolean; + @Column({ + nullable: false, + comment: "สถานะลบข้อมูล", + default: false, + }) + isDeleted: boolean; + @OneToMany( () => ProfileCertificateHistory, (profileCertificateHistory) => profileCertificateHistory.histories, diff --git a/src/entities/ProfileCertificateHistory.ts b/src/entities/ProfileCertificateHistory.ts index 022d1e17..0e9a0834 100644 --- a/src/entities/ProfileCertificateHistory.ts +++ b/src/entities/ProfileCertificateHistory.ts @@ -58,6 +58,13 @@ export class ProfileCertificateHistory extends EntityBase { }) profileCertificateId: string; + @Column({ + nullable: false, + comment: "สถานะลบข้อมูล", + default: false, + }) + isDeleted: boolean; + @ManyToOne( () => ProfileCertificate, (profileCertificate) => profileCertificate.profileCertificateHistories, diff --git a/src/entities/ProfileChangeName.ts b/src/entities/ProfileChangeName.ts index 74767fa2..b02117fc 100644 --- a/src/entities/ProfileChangeName.ts +++ b/src/entities/ProfileChangeName.ts @@ -77,6 +77,13 @@ export class ProfileChangeName extends EntityBase { }) isEntry: boolean; + @Column({ + nullable: false, + comment: "สถานะลบข้อมูล", + default: false, + }) + isDeleted: boolean; + @OneToMany( () => ProfileChangeNameHistory, (profileChangeNameHistory) => profileChangeNameHistory.histories, diff --git a/src/entities/ProfileChangeNameHistory.ts b/src/entities/ProfileChangeNameHistory.ts index 619344dd..5d296161 100644 --- a/src/entities/ProfileChangeNameHistory.ts +++ b/src/entities/ProfileChangeNameHistory.ts @@ -60,6 +60,13 @@ export class ProfileChangeNameHistory extends EntityBase { }) profileChangeNameId: string; + @Column({ + nullable: false, + comment: "สถานะลบข้อมูล", + default: false, + }) + isDeleted: boolean; + @ManyToOne( () => ProfileChangeName, (profileChangeName) => profileChangeName.profileChangeNameHistories, diff --git a/src/entities/ProfileChildren.ts b/src/entities/ProfileChildren.ts index 23f3b507..d4f07c72 100644 --- a/src/entities/ProfileChildren.ts +++ b/src/entities/ProfileChildren.ts @@ -72,6 +72,13 @@ export class ProfileChildren extends EntityBase { }) isEntry: boolean; + @Column({ + nullable: false, + comment: "สถานะลบข้อมูล", + default: false, + }) + isDeleted: boolean; + @ManyToOne(() => Profile, (Profile) => Profile.profileChildrens) @JoinColumn({ name: "profileId" }) profile: Profile; diff --git a/src/entities/ProfileChildrenHistory.ts b/src/entities/ProfileChildrenHistory.ts index 39e34b74..a402c41e 100644 --- a/src/entities/ProfileChildrenHistory.ts +++ b/src/entities/ProfileChildrenHistory.ts @@ -55,6 +55,13 @@ export class ProfileChildrenHistory extends EntityBase { }) profileChildrenId: string; + @Column({ + nullable: false, + comment: "สถานะลบข้อมูล", + default: false, + }) + isDeleted: boolean; + // @ManyToOne(() => ProfileChildren, (profileChildren) => profileChildren.profileChildrenHistories) // @JoinColumn({ name: "profileChildrenId" }) // histories: ProfileChildren; diff --git a/src/entities/ProfileDevelopment.ts b/src/entities/ProfileDevelopment.ts index 66a79b50..33a1fa11 100644 --- a/src/entities/ProfileDevelopment.ts +++ b/src/entities/ProfileDevelopment.ts @@ -154,6 +154,13 @@ export class ProfileDevelopment extends EntityBase { }) kpiDevelopmentId: string; + @Column({ + nullable: false, + comment: "สถานะลบข้อมูลการพัฒนารายบุคคล (Individual Development Plan)", + default: false, + }) + isDeleted: boolean; + @OneToMany( () => ProfileDevelopmentHistory, (profileDevelopmentHistory) => profileDevelopmentHistory.histories, diff --git a/src/entities/ProfileDevelopmentHistory.ts b/src/entities/ProfileDevelopmentHistory.ts index ca169532..04cdf7b9 100644 --- a/src/entities/ProfileDevelopmentHistory.ts +++ b/src/entities/ProfileDevelopmentHistory.ts @@ -144,6 +144,13 @@ export class ProfileDevelopmentHistory extends EntityBase { }) profileDevelopmentId: string; + @Column({ + nullable: false, + comment: "สถานะลบข้อมูลการพัฒนารายบุคคล (Individual Development Plan)", + default: false, + }) + isDeleted: boolean; + @ManyToOne( () => ProfileDevelopment, (profileDevelopment) => profileDevelopment.profileDevelopmentHistories, diff --git a/src/entities/ProfileDiscipline.ts b/src/entities/ProfileDiscipline.ts index 6220b712..0065528b 100644 --- a/src/entities/ProfileDiscipline.ts +++ b/src/entities/ProfileDiscipline.ts @@ -82,6 +82,13 @@ export class ProfileDiscipline extends EntityBase { }) isEntry: boolean; + @Column({ + nullable: false, + comment: "สถานะลบข้อมูล", + default: false, + }) + isDeleted: boolean; + @OneToMany( () => ProfileDisciplineHistory, (profileDisciplineHistory) => profileDisciplineHistory.histories, diff --git a/src/entities/ProfileDisciplineHistory.ts b/src/entities/ProfileDisciplineHistory.ts index f265a825..e7dfee58 100644 --- a/src/entities/ProfileDisciplineHistory.ts +++ b/src/entities/ProfileDisciplineHistory.ts @@ -65,6 +65,13 @@ export class ProfileDisciplineHistory extends EntityBase { }) isUpload: boolean; + @Column({ + nullable: false, + comment: "สถานะลบข้อมูล", + default: false, + }) + isDeleted: boolean; + @ManyToOne( () => ProfileDiscipline, (profileDiscipline) => profileDiscipline.profileDisciplineHistories, diff --git a/src/entities/ProfileDuty.ts b/src/entities/ProfileDuty.ts index 5684f650..7c2d8338 100644 --- a/src/entities/ProfileDuty.ts +++ b/src/entities/ProfileDuty.ts @@ -82,6 +82,13 @@ export class ProfileDuty extends EntityBase { }) isEntry: boolean; + @Column({ + nullable: false, + comment: "สถานะลบข้อมูล", + default: false, + }) + isDeleted: boolean; + @OneToMany(() => ProfileDutyHistory, (profileDutyHistory) => profileDutyHistory.histories) profileDutyHistories: ProfileDutyHistory[]; diff --git a/src/entities/ProfileDutyHistory.ts b/src/entities/ProfileDutyHistory.ts index e00f4d5f..24c61a5c 100644 --- a/src/entities/ProfileDutyHistory.ts +++ b/src/entities/ProfileDutyHistory.ts @@ -66,6 +66,13 @@ export class ProfileDutyHistory extends EntityBase { }) profileDutyId: string; + @Column({ + nullable: false, + comment: "สถานะลบข้อมูล", + default: false, + }) + isDeleted: boolean; + @ManyToOne(() => ProfileDuty, (profileDuty) => profileDuty.profileDutyHistories) @JoinColumn({ name: "profileDutyId" }) histories: ProfileDuty; diff --git a/src/entities/ProfileEducation.ts b/src/entities/ProfileEducation.ts index 351aeec0..0b7c6f6e 100644 --- a/src/entities/ProfileEducation.ts +++ b/src/entities/ProfileEducation.ts @@ -196,6 +196,13 @@ export class ProfileEducation extends EntityBase { }) isEntry: boolean; + @Column({ + nullable: false, + comment: "สถานะลบข้อมูล", + default: false, + }) + isDeleted: boolean; + @OneToMany( () => ProfileEducationHistory, (profileEducationHistory) => profileEducationHistory.histories, diff --git a/src/entities/ProfileEducationHistory.ts b/src/entities/ProfileEducationHistory.ts index 7a8d5497..6ffb118c 100644 --- a/src/entities/ProfileEducationHistory.ts +++ b/src/entities/ProfileEducationHistory.ts @@ -166,6 +166,13 @@ export class ProfileEducationHistory extends EntityBase { }) profileEducationId: string; + @Column({ + nullable: false, + comment: "สถานะลบข้อมูล", + default: false, + }) + isDeleted: boolean; + @ManyToOne( () => ProfileEducation, (profileEducation) => profileEducation.profileEducationHistories, diff --git a/src/entities/ProfileEmployee.ts b/src/entities/ProfileEmployee.ts index 04f7923f..11c2159b 100644 --- a/src/entities/ProfileEmployee.ts +++ b/src/entities/ProfileEmployee.ts @@ -1,4 +1,4 @@ -import { Entity, Column, OneToMany, ManyToOne, Double, ManyToMany, JoinTable } from "typeorm"; +import { Entity, Column, OneToMany, ManyToOne, Double, ManyToMany, JoinTable, JoinColumn } from "typeorm"; import { EntityBase } from "./base/Base"; import { EmployeePosLevel } from "./EmployeePosLevel"; import { EmployeePosType } from "./EmployeePosType"; @@ -38,6 +38,7 @@ import { StateOperatorUser } from "./StateOperatorUser"; import { EmployeeTempPosMaster } from "./EmployeeTempPosMaster"; import { ProfileSalaryTemp } from "./ProfileSalaryTemp"; import { PositionSalaryEditHistory } from "./PositionSalaryEditHistory"; +import { ProfileEmployeeAbsentLate } from "./ProfileEmployeeAbsentLate"; @Entity("profileEmployee") export class ProfileEmployee extends EntityBase { @@ -45,15 +46,17 @@ export class ProfileEmployee extends EntityBase { nullable: true, comment: "รูปถ่าย", default: null, + type: "varchar", }) - avatar: string; + avatar: string | null; @Column({ nullable: true, comment: "รูปถ่าย", default: null, + type: "varchar", }) - avatarName: string; + avatarName: string | null; @Column({ nullable: true, @@ -201,6 +204,12 @@ export class ProfileEmployee extends EntityBase { }) keycloak: string; + @Column({ + comment: "สถานะการถูกลบผู้ใช้งานใน keycloak", + default: false, + }) + isDelete: boolean; + @Column({ comment: "ทดลองปฏิบัติหน้าที่", default: false, @@ -693,13 +702,13 @@ export class ProfileEmployee extends EntityBase { default: false, }) privacyCheckin: boolean; - + @Column({ comment: "สถานะยืนยัน privacyUser", default: false, }) privacyUser: boolean; - + @Column({ comment: "สถานะยืนยัน privacyMgt", default: false, @@ -790,6 +799,14 @@ export class ProfileEmployee extends EntityBase { @ManyToOne(() => EmployeePosLevel, (v) => v.profiles) posLevel: EmployeePosLevel; + @ManyToOne(() => EmployeePosLevel, (v) => v.profilesTemp) + @JoinColumn({ name: "posLevelIdTemp" }) + posLevelTemp: EmployeePosLevel; + + @ManyToOne(() => EmployeePosType, (v) => v.profilesTemp) + @JoinColumn({ name: "posTypeIdTemp" }) + posTypeTemp: EmployeePosType; + @ManyToOne(() => EmployeePosType, (v) => v.profiles) posType: EmployeePosType; @@ -817,6 +834,9 @@ export class ProfileEmployee extends EntityBase { @OneToMany(() => PositionSalaryEditHistory, (v) => v.profileEmployee) positionSalaryEditHistory: PositionSalaryEditHistory[]; + @OneToMany(() => ProfileEmployeeAbsentLate, (v) => v.profileEmployee) + profileEmployeeAbsentLates: ProfileEmployeeAbsentLate[]; + //ที่อยู่ @Column({ nullable: true, diff --git a/src/entities/ProfileEmployeeAbsentLate.ts b/src/entities/ProfileEmployeeAbsentLate.ts new file mode 100644 index 00000000..d7b8ea71 --- /dev/null +++ b/src/entities/ProfileEmployeeAbsentLate.ts @@ -0,0 +1,87 @@ +import { Entity, Column, ManyToOne, JoinColumn } from "typeorm"; +import { EntityBase } from "./base/Base"; +import { ProfileEmployee } from "./ProfileEmployee"; +import { AbsentLateStatus, StampType } from "./ProfileAbsentLate"; + +@Entity("profileEmployeeAbsentLate") +export class ProfileEmployeeAbsentLate extends EntityBase { + @Column({ + nullable: true, + length: 40, + comment: "คีย์นอก(FK)ของตาราง ProfileEmployee", + default: null, + }) + profileEmployeeId: string; + + @Column({ + type: "enum", + enum: AbsentLateStatus, + comment: "สถานะ มาสาย/ขาดราชการ", + nullable: false, + }) + status: AbsentLateStatus; + + @Column({ + type: "datetime", + comment: "วันที่และเวลาที่ลงเวลา", + nullable: false, + }) + stampDate: Date; + + @Column({ + type: "enum", + enum: StampType, + comment: "เต็มวัน/ครึ่งเช้า/ครึ่งบ่าย", + default: StampType.FULL_DAY, + }) + stampType: StampType; + + @Column({ + type: "decimal", + precision: 2, + scale: 1, + comment: "จำนวน (1.0/0.5)", + default: "1.0", + }) + stampAmount: number; + + @Column({ + type: "varchar", + length: 250, + comment: "หมายเหตุ", + nullable: true, + }) + remark: string; + + @Column({ + comment: "สถานะลบข้อมูล", + default: false, + }) + isDeleted: boolean; + + @ManyToOne(() => ProfileEmployee, (profileEmployee) => profileEmployee.profileEmployeeAbsentLates) + @JoinColumn({ name: "profileEmployeeId" }) + profileEmployee: ProfileEmployee; +} + +// DTO Classes +export class CreateProfileEmployeeAbsentLate { + profileEmployeeId: string; + status: AbsentLateStatus; + stampDate: Date; + stampType?: StampType; + stampAmount?: number; + remark?: string; +} + +export class CreateProfileEmployeeAbsentLateBatch { + records: CreateProfileEmployeeAbsentLate[]; +} + +export type UpdateProfileEmployeeAbsentLate = { + status?: AbsentLateStatus; + stampDate?: Date; + stampType?: StampType; + stampAmount?: number; + remark?: string; +}; diff --git a/src/entities/ProfileEmployeeAbsentLateHistory.ts b/src/entities/ProfileEmployeeAbsentLateHistory.ts new file mode 100644 index 00000000..588801e0 --- /dev/null +++ b/src/entities/ProfileEmployeeAbsentLateHistory.ts @@ -0,0 +1,17 @@ +import { Entity, Column } from "typeorm"; +import { ProfileEmployeeAbsentLate } from "./ProfileEmployeeAbsentLate"; +import { AbsentLateStatus, StampType } from "./ProfileAbsentLate"; + +@Entity("profileEmployeeAbsentLateHistory") +export class ProfileEmployeeAbsentLateHistory extends ProfileEmployeeAbsentLate { + @Column({ + nullable: true, + length: 40, + comment: "คีย์นอก(FK)ของตาราง ProfileEmployeeAbsentLate", + default: null, + }) + profileEmployeeAbsentLateId: string; +} + +// Export enums for re-use +export { AbsentLateStatus, StampType }; diff --git a/src/entities/ProfileHonor.ts b/src/entities/ProfileHonor.ts index 481e195f..73ec072f 100644 --- a/src/entities/ProfileHonor.ts +++ b/src/entities/ProfileHonor.ts @@ -89,6 +89,13 @@ export class ProfileHonor extends EntityBase { }) isEntry: boolean; + @Column({ + nullable: false, + comment: "สถานะลบข้อมูล", + default: false, + }) + isDeleted: boolean; + @OneToMany(() => ProfileHonorHistory, (profileHonorHistory) => profileHonorHistory.histories) profileHonorHistories: ProfileHonorHistory[]; diff --git a/src/entities/ProfileHonorHistory.ts b/src/entities/ProfileHonorHistory.ts index 59338365..d22137ab 100644 --- a/src/entities/ProfileHonorHistory.ts +++ b/src/entities/ProfileHonorHistory.ts @@ -73,6 +73,13 @@ export class ProfileHonorHistory extends EntityBase { }) isUpload: boolean; + @Column({ + nullable: false, + comment: "สถานะลบข้อมูล", + default: false, + }) + isDeleted: boolean; + @ManyToOne(() => ProfileHonor, (profileHonor) => profileHonor.profileHonorHistories) @JoinColumn({ name: "profileHonorId" }) histories: ProfileHonor; diff --git a/src/entities/ProfileInsignia.ts b/src/entities/ProfileInsignia.ts index b7fa824d..d09e71ee 100644 --- a/src/entities/ProfileInsignia.ts +++ b/src/entities/ProfileInsignia.ts @@ -137,6 +137,13 @@ export class ProfileInsignia extends EntityBase { }) isEntry: boolean; + @Column({ + nullable: false, + comment: "สถานะลบข้อมูล", + default: false, + }) + isDeleted: boolean; + @ManyToOne(() => Insignia, (v) => v.profileInsignias) insignia: Insignia; diff --git a/src/entities/ProfileInsigniaHistory.ts b/src/entities/ProfileInsigniaHistory.ts index cc4fa5d7..4381b900 100644 --- a/src/entities/ProfileInsigniaHistory.ts +++ b/src/entities/ProfileInsigniaHistory.ts @@ -65,6 +65,13 @@ export class ProfileInsigniaHistory extends EntityBase { }) profileInsigniaId: string; + @Column({ + nullable: false, + comment: "สถานะลบข้อมูล", + default: false, + }) + isDeleted: boolean; + @ManyToOne(() => ProfileInsignia, (profileInsignia) => profileInsignia.profileInsigniaHistories) @JoinColumn({ name: "profileInsigniaId" }) histories: ProfileInsignia; diff --git a/src/entities/ProfileLeave.ts b/src/entities/ProfileLeave.ts index 2728b32b..8ce08d94 100644 --- a/src/entities/ProfileLeave.ts +++ b/src/entities/ProfileLeave.ts @@ -100,6 +100,31 @@ export class ProfileLeave extends EntityBase { }) isEntry: boolean; + @Column({ + nullable: false, + comment: "สถานะลบข้อมูล", + default: false, + }) + isDeleted: boolean; + + @Column({ + nullable: true, + comment: "ประเภทย่อยการลา (เช่น ศึกษาต่อ, ฝึกอบรม, ปฎอบัติการวิจัย, ดูงาน)", + type: "varchar", + length: 255, + default: null, + }) + leaveSubTypeName: string; + + @Column({ + nullable: true, + comment: "ประเทศที่ไป", + type: "varchar", + length: 255, + default: null, + }) + coupleDayLevelCountry: string; + @OneToMany(() => ProfileLeaveHistory, (v) => v.profileLeave) histories: ProfileLeaveHistory[]; @@ -124,6 +149,13 @@ export class ProfileLeaveHistory extends ProfileLeave { }) profileLeaveId: string; + @Column({ + nullable: false, + comment: "สถานะลบข้อมูล", + default: false, + }) + isDeleted: boolean = false; + @ManyToOne(() => ProfileLeave, (v) => v.histories) profileLeave: ProfileLeave; } @@ -139,6 +171,8 @@ export class CreateProfileLeave { status?: string | null; reason: string | null; leaveId?: string | null; + leaveSubTypeName?: string | null; + coupleDayLevelCountry?: string | null; } export class CreateProfileEmployeeLeave { @@ -152,6 +186,8 @@ export class CreateProfileEmployeeLeave { status: string | null; reason: string | null; leaveId?: string | null; + leaveSubTypeName?: string | null; + coupleDayLevelCountry?: string | null; } export type UpdateProfileLeave = { @@ -163,4 +199,6 @@ export type UpdateProfileLeave = { totalLeave?: number | null; status?: string | null; reason?: string | null; + leaveSubTypeName?: string | null; + coupleDayLevelCountry?: string | null; }; diff --git a/src/entities/ProfileNopaid.ts b/src/entities/ProfileNopaid.ts index 1f90025f..7eb2e82f 100644 --- a/src/entities/ProfileNopaid.ts +++ b/src/entities/ProfileNopaid.ts @@ -74,6 +74,13 @@ export class ProfileNopaid extends EntityBase { }) isEntry: boolean; + @Column({ + nullable: false, + comment: "สถานะลบข้อมูล", + default: false, + }) + isDeleted: boolean; + @OneToMany(() => ProfileNopaidHistory, (profileNopaidHistory) => profileNopaidHistory.histories) profileNopaidHistories: ProfileNopaidHistory[]; diff --git a/src/entities/ProfileNopaidHistory.ts b/src/entities/ProfileNopaidHistory.ts index 905395ef..6bb14d0b 100644 --- a/src/entities/ProfileNopaidHistory.ts +++ b/src/entities/ProfileNopaidHistory.ts @@ -58,6 +58,13 @@ export class ProfileNopaidHistory extends EntityBase { }) profileNopaidId: string; + @Column({ + nullable: false, + comment: "สถานะลบข้อมูล", + default: false, + }) + isDeleted: boolean; + @ManyToOne(() => ProfileNopaid, (profileNopaid) => profileNopaid.profileNopaidHistories) @JoinColumn({ name: "profileNopaidId" }) histories: ProfileNopaid; diff --git a/src/entities/ProfileOther.ts b/src/entities/ProfileOther.ts index b64171ed..497886be 100644 --- a/src/entities/ProfileOther.ts +++ b/src/entities/ProfileOther.ts @@ -44,6 +44,13 @@ export class ProfileOther extends EntityBase { }) isEntry: boolean; + @Column({ + nullable: false, + comment: "สถานะลบข้อมูล", + default: false, + }) + isDeleted: boolean; + @OneToMany(() => ProfileOtherHistory, (profileOtherHistory) => profileOtherHistory.histories) profileOtherHistories: ProfileOtherHistory[]; diff --git a/src/entities/ProfileOtherHistory.ts b/src/entities/ProfileOtherHistory.ts index 15ea3588..654425a7 100644 --- a/src/entities/ProfileOtherHistory.ts +++ b/src/entities/ProfileOtherHistory.ts @@ -28,6 +28,13 @@ export class ProfileOtherHistory extends EntityBase { }) date: Date; + @Column({ + nullable: false, + comment: "สถานะลบข้อมูล", + default: false, + }) + isDeleted: boolean; + @ManyToOne(() => ProfileOther, (profileOther) => profileOther.profileOtherHistories) @JoinColumn({ name: "profileOtherId" }) histories: ProfileOther; diff --git a/src/entities/ProfileSalaryBackup.ts b/src/entities/ProfileSalaryBackup.ts new file mode 100644 index 00000000..29cdd325 --- /dev/null +++ b/src/entities/ProfileSalaryBackup.ts @@ -0,0 +1,295 @@ +import { Entity, Column, Double } from "typeorm"; +import { EntityBase } from "./base/Base"; + +@Entity("profileSalaryBackup") +export class ProfileSalaryBackup extends EntityBase { + + @Column({ + nullable: true, + length: 40, + comment: "อ้างอิง profileSalary (no FK constraint)", + type: "uuid", + default: null, + }) + profileSalaryId: string; + + @Column({ + nullable: true, + length: 40, + comment: "อ้างอิง profile (no FK constraint)", + type: "uuid", + default: null, + }) + profileId: string; + + @Column({ + nullable: true, + length: 40, + comment: "อ้างอิง ProfileEmployee (no FK constraint)", + default: null, + }) + profileEmployeeId: string; + + @Column({ + nullable: true, + comment: "เรียงลำดับใหมาตามการนำเข้า", + default: null, + }) + order: number; + + @Column({ + nullable: true, + comment: "เลขที่คำสั่ง", + default: null, + }) + commandNo: string; + + @Column({ + nullable: true, + comment: "ปีที่ออกคำสั่ง", + default: null, + }) + commandYear: number; + + @Column({ + comment: "คำสั่งวันที่", + type: "datetime", + nullable: true, + }) + commandDateSign: Date; + + @Column({ + comment: "คำสั่งมีผลวันที่", + type: "datetime", + nullable: true, + }) + commandDateAffect: Date; + + @Column({ + nullable: true, + comment: "รหัสประเภทของคำสั่ง", + default: null, + }) + commandCode: string; + + @Column({ + nullable: true, + comment: "ชื่อประเภทคำสั่ง", + default: null, + }) + commandName: string; + + @Column({ + nullable: true, + length: 40, + comment: "ตัวย่อเลขที่ตำแหน่ง", + default: null, + }) + posNoAbb: string; + + @Column({ + nullable: true, + length: 40, + comment: "เลขที่ตำแหน่ง", + default: null, + }) + posNo: string; + + @Column({ + nullable: true, + length: 255, + comment: "ตำแหน่ง", + default: null, + }) + positionName: string; + + @Column({ + nullable: true, + length: 255, + comment: "ประเภทตำแหน่ง", + default: null, + }) + positionType: string; + + @Column({ + nullable: true, + length: 255, + comment: "ระดับตำแหน่ง", + default: null, + }) + positionLevel: string; + + @Column({ + nullable: true, + comment: "ระดับของเก่าที่ยังไม่เทียบเท่าแบบแท่ง", + default: null, + }) + positionCee: string; + + @Column({ + nullable: true, + comment: "root name", + default: null, + }) + orgRoot: string; + + @Column({ + nullable: true, + comment: "child1 name", + default: null, + }) + orgChild1: string; + + @Column({ + nullable: true, + comment: "child2 name", + default: null, + }) + orgChild2: string; + + @Column({ + nullable: true, + comment: "child3 name", + default: null, + }) + orgChild3: string; + + @Column({ + nullable: true, + comment: "child4 name", + default: null, + }) + orgChild4: string; + + @Column({ + nullable: true, + length: 255, + comment: "ตำแหน่งทางการบริหาร", + default: null, + }) + positionExecutive: string; + + @Column({ + comment: "เงินเดือนฐาน", + default: 0, + nullable: true, + type: "double", + }) + amount: Double; + + @Column({ + comment: "เงินพิเศษ", + default: 0, + nullable: true, + type: "double", + }) + amountSpecial: Double; + + @Column({ + comment: "เงินประจำตำแหน่ง", + default: 0, + nullable: true, + type: "double", + }) + positionSalaryAmount: Double; + + @Column({ + comment: "เงินค่าตอบแทนรายเดือน", + default: 0, + nullable: true, + type: "double", + }) + mouthSalaryAmount: Double; + + @Column({ + nullable: true, + type: "text", + comment: "หมายเหตุ", + default: null, + }) + remark: string; + + @Column({ + nullable: true, + comment: "refId", + default: null, + }) + refId: string; + + @Column({ + comment: "วันที่", + type: "datetime", + nullable: true, + }) + dateGovernment: Date; + + @Column({ + nullable: true, + comment: "เข้ารับราชการ", + default: null, + }) + isGovernment: boolean; + + @Column({ + comment: "ข้อมูลจาก Entry", + default: false, + }) + isEntry: boolean; + + @Column({ + nullable: true, + length: 255, + comment: "ด้านของตำแหน่ง", + default: null, + }) + positionPathSide: string; + + @Column({ + nullable: true, + length: 255, + comment: "ตำแหน่งในสายงาน", + default: null, + }) + positionLine: string; + + @Column({ + nullable: true, + length: 40, + comment: "อ้างอิง command (no FK constraint)", + default: null, + }) + commandId: string; + + @Column({ + nullable: true, + length: 255, + comment: "หน่วยงานที่ออกคำสั่ง", + default: null, + }) + posNumCodeSit: string; + + @Column({ + nullable: true, + length: 255, + comment: "หน่วยงานที่ออกคำสั่ง(ตัวย่อ)", + default: null, + }) + posNumCodeSitAbb: string; + + @Column({ + nullable: true, + length: 255, + comment: "ด้านทางการบริหาร", + default: null, + }) + positionExecutiveField: string; + + @Column({ + nullable: true, + length: 255, + comment: "ด้าน/สาขา", + default: null, + }) + positionArea: string; + +} \ No newline at end of file diff --git a/src/entities/ProfileSalaryTemp.ts b/src/entities/ProfileSalaryTemp.ts index 9b13d071..97e74ec7 100644 --- a/src/entities/ProfileSalaryTemp.ts +++ b/src/entities/ProfileSalaryTemp.ts @@ -273,8 +273,9 @@ export class ProfileSalaryTemp extends EntityBase { length: 40, comment: "คีย์นอก(FK)ของตาราง profileSalary", default: null, + type: "varchar", }) - salaryId: string; + salaryId: string | null; @Column({ nullable: true, @@ -292,7 +293,7 @@ export class ProfileSalaryTemp extends EntityBase { }) posNumCodeSitAbb: string; - @Column({ + @Column({ nullable: true, length: 255, comment: "ด้านทางการบริหาร", diff --git a/src/entities/ProfileTraining.ts b/src/entities/ProfileTraining.ts index 2afaa17a..e887ce39 100644 --- a/src/entities/ProfileTraining.ts +++ b/src/entities/ProfileTraining.ts @@ -122,6 +122,13 @@ export class ProfileTraining extends EntityBase { }) developmentId: string; + @Column({ + nullable: false, + comment: "สถานะลบข้อมูลการฝึกอบรม/ดูงาน", + default: false, + }) + isDeleted: boolean; + @OneToMany( () => ProfileTrainingHistory, (profileTrainingHistory) => profileTrainingHistory.histories, diff --git a/src/entities/ProfileTrainingHistory.ts b/src/entities/ProfileTrainingHistory.ts index 4ce0eada..f5dce05d 100644 --- a/src/entities/ProfileTrainingHistory.ts +++ b/src/entities/ProfileTrainingHistory.ts @@ -98,6 +98,13 @@ export class ProfileTrainingHistory extends EntityBase { }) isDate: boolean; + @Column({ + nullable: false, + comment: "สถานะลบข้อมูลการฝึกอบรม/ดูงาน", + default: false, + }) + isDeleted: boolean; + @ManyToOne(() => ProfileTraining, (profileTraining) => profileTraining.profileTrainingHistories) @JoinColumn({ name: "profileTrainingId" }) histories: ProfileTraining; diff --git a/src/interfaces/OrgMapping.ts b/src/interfaces/OrgMapping.ts new file mode 100644 index 00000000..b0eb21bf --- /dev/null +++ b/src/interfaces/OrgMapping.ts @@ -0,0 +1,44 @@ +/** + * Type definitions for organization mapping used in move-draft-to-current function + */ + +/** + * Maps draft organization IDs to current organization IDs + * - byAncestorDNA: Maps ancestorDNA to current ID for lookup + * - byDraftId: Maps draft ID to current ID for position updates + */ +export interface OrgIdMapping { + byAncestorDNA: Map; + byDraftId: Map; +} + +/** + * Contains mappings for all organization levels + */ +export interface AllOrgMappings { + orgRoot: OrgIdMapping; + orgChild1: OrgIdMapping; + orgChild2: OrgIdMapping; + orgChild3: OrgIdMapping; + orgChild4: OrgIdMapping; +} + +export interface SavePosMasterHistory { + prefix: string | null; + firstName: string | null; + lastName: string | null; + position: string | null; + posType: string | null; + posLevel: string | null; + posExecutive: string | null; + profileId: string | null; + rootDnaId: string | null; + child1DnaId: string | null; + child2DnaId: string | null; + child3DnaId: string | null; + child4DnaId: string | null; + shortName: string | null; + posMasterNoPrefix: string | null; + posMasterNo: string | null; + posMasterNoSuffix: string | null; +} diff --git a/src/interfaces/OrgTypes.ts b/src/interfaces/OrgTypes.ts new file mode 100644 index 00000000..12402cad --- /dev/null +++ b/src/interfaces/OrgTypes.ts @@ -0,0 +1,10 @@ +export interface OrgPermissionData { + root: (string | null | undefined)[] | null; + child1: (string | null | undefined)[] | null; + child2: (string | null | undefined)[] | null; + child3: (string | null | undefined)[] | null; + child4: (string | null | undefined)[] | null; + privilege?: "OWNER" | "PARENT" | "CHILD" | "BROTHER" | "NORMAL"; +} + +export type NodeLevel = 0 | 1 | 2 | 3 | 4 | null; diff --git a/src/interfaces/call-api.ts b/src/interfaces/call-api.ts index 3639c37c..16986335 100644 --- a/src/interfaces/call-api.ts +++ b/src/interfaces/call-api.ts @@ -99,6 +99,69 @@ class CallAPI { throw error; } } + + // Put + public async PutData(request: any, @Path() path: any, sendData: any, log = true) { + const token = "Bearer " + request.headers.authorization.replace("Bearer ", ""); + const url = process.env.API_URL + path; + + try { + const response = await axios.put(url, sendData, { + headers: { + Authorization: `${token}`, + "Content-Type": "application/json", + api_key: process.env.API_KEY, + }, + }); + + if (log) + addLogSequence(request, { + action: "request", + status: "success", + description: "connected", + request: { + method: "PUT", + url: url, + payload: JSON.stringify(sendData), + response: JSON.stringify(response.data.result), + }, + }); + + return response.data.result; + } catch (error) { + if (log) + addLogSequence(request, { + action: "request", + status: "error", + description: "unconnected", + request: { + method: "PUT", + url: url, + payload: JSON.stringify(sendData), + response: JSON.stringify(error), + }, + }); + + throw error; + } + } + + //Delete File + public async DeleteFile(request: any, @Path() path: any) { + const token = "Bearer " + request.headers.authorization.replace("Bearer ", ""); + const url = process.env.API_URL + "/salary/file/" + path; + try { + await axios.delete(url, { + headers: { + Authorization: `${token}`, + "Content-Type": "application/json", + api_key: process.env.API_KEY, + }, + }); + } catch (error) { + throw error; + } + } } export default CallAPI; diff --git a/src/interfaces/date-serializer.ts b/src/interfaces/date-serializer.ts index 5c876ffc..b3f0c541 100644 --- a/src/interfaces/date-serializer.ts +++ b/src/interfaces/date-serializer.ts @@ -14,7 +14,10 @@ export class DateSerializer { static setupDateSerialization() { // Override Date.prototype.toJSON to use local time - Date.prototype.toJSON = function () { + Date.prototype.toJSON = function (): any { + // Check for Invalid Date - return null for invalid dates + if (isNaN(this.getTime())) return null; + const offset = 7 * 60; // Thailand timezone offset in minutes const localTime = new Date(this.getTime() + offset * 60 * 1000); const isoString = localTime.toISOString(); diff --git a/src/interfaces/extension.ts b/src/interfaces/extension.ts index 24da5b93..405ed849 100644 --- a/src/interfaces/extension.ts +++ b/src/interfaces/extension.ts @@ -184,6 +184,7 @@ class Extension { } public static ToThaiFullDate(value: Date) { + if (value == null || value == undefined || isNaN(value.getTime())) return ""; let yy = value.getFullYear() < 2400 ? value.getFullYear() + 543 : value.getFullYear(); return ( "วันที่ " + @@ -195,11 +196,13 @@ class Extension { ); } public static ToThaiFullDate2(value: Date) { + if (value == null || value == undefined || isNaN(value.getTime())) return ""; let yy = value.getFullYear() < 2400 ? value.getFullYear() + 543 : value.getFullYear(); return value.getDate() + " " + Extension.ToThaiMonth(value.getMonth() + 1) + " " + yy; } public static ToThaiShortDate(value: Date) { + if (value == null || value == undefined || isNaN(value.getTime())) return ""; let yy = value.getFullYear() < 2400 ? value.getFullYear() + 543 : value.getFullYear(); return ( "วันที่ " + @@ -212,6 +215,7 @@ class Extension { } public static ToThaiShortDate_noPrefix(value: Date) { + if (value == null || value == undefined || isNaN(value.getTime())) return ""; let yy = value.getFullYear() < 2400 ? value.getFullYear() + 543 : value.getFullYear(); return ( value.getDate() + @@ -223,6 +227,7 @@ class Extension { } public static ToThaiShortDate_monthYear(value: Date) { + if (value == null || value == undefined || isNaN(value.getTime())) return ""; let yy = value.getFullYear() < 2400 ? value.getFullYear() + 543 : value.getFullYear(); return ( value.getDate() + " เดือน " + Extension.ToThaiMonth(value.getMonth() + 1) + " พ.ศ. " + yy @@ -230,11 +235,13 @@ class Extension { } public static ToThaiShortDate_monthYear2(value: Date) { + if (value == null || value == undefined || isNaN(value.getTime())) return ""; let yy = value.getFullYear() < 2400 ? value.getFullYear() + 543 : value.getFullYear(); return value.getDate() + " " + Extension.ToThaiMonth(value.getMonth() + 1) + " พ.ศ. " + yy; } public static ToThaiShortYear(value: Date) { + if (value == null || value == undefined || isNaN(value.getTime())) return ""; let yy = value.getFullYear() < 2400 ? value.getFullYear() + 543 : value.getFullYear(); return yy.toString(); } @@ -252,10 +259,7 @@ class Extension { public static CheckCitizen(value: string) { let citizen = value; if (citizen == null || citizen == "" || citizen == undefined) { - throw new HttpError( - HttpStatus.NOT_FOUND, - "กรุณากรอกข้อมูลรหัสบัตรประจำตัวประชาชน", - ); + throw new HttpError(HttpStatus.NOT_FOUND, "กรุณากรอกข้อมูลรหัสบัตรประจำตัวประชาชน"); } if (citizen.length !== 13) { throw new HttpError( @@ -281,10 +285,7 @@ class Extension { const chkDigit = (11 - calStp2) % 10; if (citizenIdDigits[12] !== chkDigit) { - throw new HttpError( - HttpStatus.NOT_FOUND, - "ข้อมูลรหัสบัตรประจำตัวประชาชนไม่ถูกต้อง" - ); + throw new HttpError(HttpStatus.NOT_FOUND, "ข้อมูลรหัสบัตรประจำตัวประชาชนไม่ถูกต้อง"); } return citizen; } @@ -295,6 +296,8 @@ class Extension { subtractYear: number = 0, ): number { if (appointDate == null || appointDate == undefined) return 0; + // Check for Invalid Date + if (isNaN(appointDate.getTime())) return 0; const now = new Date(); if (now.getMonth() - appointDate.getMonth() >= 6) { return now.getFullYear() - appointDate.getFullYear() + 1 + plusYear - subtractYear; @@ -304,6 +307,9 @@ class Extension { } public static CalculateAge(appointDate: Date, plusYear: number = 0, subtractYear: number = 0) { + if (appointDate == null || appointDate == undefined) return 0; + // Check for Invalid Date + if (isNaN(appointDate.getTime())) return 0; let currentDate = new Date().getTime(); let appointDateTime = new Date(appointDate).getTime(); let ageInMilliseconds = currentDate - appointDateTime; @@ -312,13 +318,20 @@ class Extension { return years; } - public static CalculateAgeStrV2(date: Date, plusYear: number = 0, subtractYear: number = 0, method?:string) { + public static CalculateAgeStrV2( + date: Date, + plusYear: number = 0, + subtractYear: number = 0, + method?: string, + ) { if (date == null || date == undefined) return ""; + // Check for Invalid Date + if (isNaN(date.getTime())) return ""; const currentDate = new Date(); if (date > currentDate && method !== "GET") { throw new Error("วันเกิดต้องไม่มากกว่าวันที่ปัจจุบัน"); - }else if(date > currentDate && method === "GET"){ - return "" + } else if (date > currentDate && method === "GET") { + return ""; } let years = currentDate.getFullYear() - date.getFullYear(); @@ -388,9 +401,14 @@ class Extension { } public static toDateOnlyString(date: Date): string { - return date.getFullYear() + "-" + - String(date.getMonth() + 1).padStart(2, "0") + "-" + - String(date.getDate()).padStart(2, "0"); + if (date == null || date == undefined || isNaN(date.getTime())) return ""; + return ( + date.getFullYear() + + "-" + + String(date.getMonth() + 1).padStart(2, "0") + + "-" + + String(date.getDate()).padStart(2, "0") + ); } } diff --git a/src/interfaces/permission.ts b/src/interfaces/permission.ts index 1542ce45..4c3063de 100644 --- a/src/interfaces/permission.ts +++ b/src/interfaces/permission.ts @@ -96,8 +96,10 @@ class CheckAuth { }; } else if (privilege == "PARENT") { data = { - root: [x.orgRootId], - child1: [null], + // root: [x.orgRootId], + // child1: [null], + root: null, + child1: null, child2: null, child3: null, child4: null, @@ -305,6 +307,22 @@ class CheckAuth { public async PermissionOrgUserUpdate(req: RequestWithUser, system: string, profileId: string) { return await this.PermissionOrgByUser(req, system, "UPDATE", profileId); } + + public async checkDna(request: RequestWithUser, keycloakId: any) { + try { + const result = await new CallAPI().GetData( + request, + `/org/finddna-by-keycloak/${keycloakId}`, + false + ); + + return result; + } catch (error) { + console.error("Error calling API:", error); + throw error; + } + } + } export default CheckAuth; diff --git a/src/interfaces/utils.ts b/src/interfaces/utils.ts index abe4bd4a..347f28af 100644 --- a/src/interfaces/utils.ts +++ b/src/interfaces/utils.ts @@ -44,7 +44,7 @@ export function calculateAge(start: Date, end = new Date()) { return { year, month, day }; } -export async function calculateGovAge(profileId: string, type: string) { +export async function calculateGovAge(profileId: string, type: string, bkk?: boolean) { // type = OFFICER , EMPLOYEE const isEmployee = type === "EMPLOYEE"; @@ -72,7 +72,7 @@ export async function calculateGovAge(profileId: string, type: string) { }); // console.log("endDateFristRec", endDateFristRec.dateGovernment); - const calculateDuration = (startDate: any, endDate: any , inclusive = false) => { + const calculateDuration = (startDate: any, endDate: any, inclusive = false) => { if (inclusive) { endDate = new Date(endDate.getTime() + 86400000); // +1 วัน } @@ -107,7 +107,9 @@ export async function calculateGovAge(profileId: string, type: string) { }); } // const firstStartDate = new Date(records[0].date); - const firstStartDate = profile?.dateAppoint ? profile?.dateAppoint : new Date(); + const firstStartDate = !bkk + ? profile?.dateAppoint ? profile?.dateAppoint : new Date() + : profile?.dateStart ? profile?.dateStart : new Date(); const firstEndDate = endDateFristRec ? new Date(endDateFristRec.dateGovernment) : new Date(); // console.log("firstStartDate1", firstStartDate); @@ -141,37 +143,37 @@ export async function calculateGovAge(profileId: string, type: string) { for (let i = 0; i < records_middle.length; i++) { const current = records_middle[i]; const next = records_middle[i + 1]; - + // นับเฉพาะช่วงที่เป็นราชการ if (current.isGovernment === true) { const startDate = new Date(current.dateGovernment); const endDate = next ? new Date(next.dateGovernment) : new Date(); const { years, months, days } = calculateDuration(startDate, endDate); - + totalYears2 += years; totalMonths2 += months; totalDays2 += days; - + // console.log(`✔ นับช่วง ${startDate.toISOString()} → ${endDate.toISOString()} ได้ ${years} ปี ${months} เดือน ${days} วัน`); - } + } // else { // console.log(`❌ ไม่รวมช่วง ${current.dateGovernment} เพราะ isGovernment = false`); // } } - + //ตั้งแต่วันที่กลับมารับราขการไปจนถึงรวมถึงวันที่ปัจจุบัน - const adjustTotal = (years:number, months:number, days:number) => { + const adjustTotal = (years: number, months: number, days: number) => { const daysInThisMonth = new Date(new Date().getFullYear(), new Date().getMonth() + 1, 0).getDate(); if (days >= daysInThisMonth) { months += Math.floor(days / daysInThisMonth); days %= daysInThisMonth; } - + if (months >= 12) { years += Math.floor(months / 12); months %= 12; } - + return { years, months, days }; }; @@ -190,10 +192,10 @@ export async function calculateGovAge(profileId: string, type: string) { const finalAdjusted = adjustTotal(sumYears, sumMonths, sumDays); return { - year: finalAdjusted.years, - month: finalAdjusted.months, - day: finalAdjusted.days, -}; + year: finalAdjusted.years, + month: finalAdjusted.months, + day: finalAdjusted.days, + }; } export function calculateRetireDate(birthDate: Date) { @@ -260,11 +262,11 @@ export async function removeProfileInOrganize(profileId: string, type: string) { .getOne(); const draftRevision = await AppDataSource.getRepository(OrgRevision) - .createQueryBuilder("orgRevision") - .where("orgRevision.orgRevisionIsDraft = true") - .andWhere("orgRevision.orgRevisionIsCurrent = false") - .getOne(); - + .createQueryBuilder("orgRevision") + .where("orgRevision.orgRevisionIsDraft = true") + .andWhere("orgRevision.orgRevisionIsCurrent = false") + .getOne(); + if (!currentRevision && !draftRevision) { return; } @@ -352,31 +354,31 @@ export async function removeProfileInOrganize(profileId: string, type: string) { } export async function removePostMasterAct(profileId: string) { - const currentRevision = await AppDataSource.getRepository(OrgRevision) - .createQueryBuilder("orgRevision") - .where("orgRevision.orgRevisionIsDraft = false") - .andWhere("orgRevision.orgRevisionIsCurrent = true") - .getOne(); + const currentRevision = await AppDataSource.getRepository(OrgRevision) + .createQueryBuilder("orgRevision") + .where("orgRevision.orgRevisionIsDraft = false") + .andWhere("orgRevision.orgRevisionIsCurrent = true") + .getOne(); - if (!currentRevision) { - return; - } + if (!currentRevision) { + return; + } - const findProfileInposMaster = await AppDataSource.getRepository(PosMaster) - .createQueryBuilder("posMaster") - .where("posMaster.orgRevisionId = :orgRevisionId", { orgRevisionId: currentRevision?.id }) - .andWhere("posMaster.current_holderId = :profileId", { profileId }) - .getOne(); + const findProfileInposMaster = await AppDataSource.getRepository(PosMaster) + .createQueryBuilder("posMaster") + .where("posMaster.orgRevisionId = :orgRevisionId", { orgRevisionId: currentRevision?.id }) + .andWhere("posMaster.current_holderId = :profileId", { profileId }) + .getOne(); - if (!findProfileInposMaster) { - return; - } + if (!findProfileInposMaster) { + return; + } - const posMasterAct = await AppDataSource.getRepository(PosMasterAct) - .createQueryBuilder("posMasterAct") - .where("posMasterAct.posMasterChildId = :posMasterChildId", { posMasterChildId: findProfileInposMaster.id }) - .getMany(); - await AppDataSource.getRepository(PosMasterAct).remove(posMasterAct); + const posMasterAct = await AppDataSource.getRepository(PosMasterAct) + .createQueryBuilder("posMasterAct") + .where("posMasterAct.posMasterChildId = :posMasterChildId", { posMasterChildId: findProfileInposMaster.id }) + .getMany(); + await AppDataSource.getRepository(PosMasterAct).remove(posMasterAct); } export async function checkReturnCommandType(commandId: string) { @@ -565,22 +567,22 @@ export function editLogSequence(req: RequestWithUser, index: number, data: LogSe export async function checkQueueInProgress(queueName: string) { const axios = require('axios'); - // console.log("Checking queue in progress"); - const res = await axios.get(`${process.env.RABBIT_API_URL}/api/queues/%2F/${queueName}`, { - auth: { username: process.env.RABBIT_USER , password: process.env.RABBIT_PASS }, - }); + // console.log("Checking queue in progress"); + const res = await axios.get(`${process.env.RABBIT_API_URL}/api/queues/%2F/${queueName}`, { + auth: { username: process.env.RABBIT_USER, password: process.env.RABBIT_PASS }, + }); - const q = res.data; + const q = res.data; - // console.log(`Queue "${queueName}" has:`); - // console.log(` - ${q.messages_ready} messages ready`); - // console.log(` - ${q.messages_unacknowledged} messages in progress (unacked)`); + // console.log(`Queue "${queueName}" has:`); + // console.log(` - ${q.messages_ready} messages ready`); + // console.log(` - ${q.messages_unacknowledged} messages in progress (unacked)`); - if (q.messages_unacknowledged > 0) { - return true; - } + if (q.messages_unacknowledged > 0) { + return true; + } - return false; + return false; } export function chunkArray(array: any, size: number) { @@ -592,20 +594,20 @@ export function chunkArray(array: any, size: number) { } export async function PayloadSendNoti(commandId: string) { - if (!commandId) + if (!commandId) return ""; const commandRepository = AppDataSource.getRepository(Command); const _command = await commandRepository.findOne({ where: { id: commandId, }, - relations: ["commandType"], + relations: ["commandType", "commandRecives"], }); if (!_command || !_command.commandType) return ""; const _payload = { - name: _command && _command.commandType - ? `คำสั่ง${_command.commandType.name}` + name: _command && _command.commandType + ? `คำสั่ง${_command.commandType.name}` : "", url: `${process.env.API_URL}/salary/file/ระบบออกคำสั่ง/คำสั่ง/${commandId}/คำสั่ง`, isReport: true, @@ -613,19 +615,18 @@ export async function PayloadSendNoti(commandId: string) { } let attachments = {} if (_command.commandType.isUploadAttachment === true) { - const _payloadAtt = { - name: _command && _command.commandType - ? `เอกสารแนบท้ายคำสั่ง${_command.commandType.name}` - : "", - url: `${process.env.API_URL}/salary/file/ระบบออกคำสั่ง/แนบท้าย/${commandId}/แนบท้าย`, + + const attachmentPayloads = _command.commandRecives.map((recive: any) => ({ + name: `เอกสารแนบท้ายคำสั่ง${_command.commandType.name} (${recive.prefix}${recive.firstName} ${recive.lastName})`, + url: `${process.env.API_URL}/salary/sub-file/ระบบออกคำสั่ง/แนบท้าย/${commandId}/${recive.citizenId}/แนบท้าย`, isReport: true, isTemplate: false, - } + })); + attachments = { - attachments: [_payload, _payloadAtt] + attachments: [_payload, ...attachmentPayloads] }; - } - else { + } else { attachments = { attachments: [_payload] }; @@ -731,3 +732,23 @@ export function commandTypePath(commandCode: string): string | null { return null; } } + +export function resolveNodeLevel(data: any) { + if (data.child4DnaId) return 4; + if (data.child3DnaId) return 3; + if (data.child2DnaId) return 2; + if (data.child1DnaId) return 1; + if (data.rootDnaId) return 0; + return null; +} + +export function resolveNodeId(data: any) { + return ( + data.child4DnaId ?? + data.child3DnaId ?? + data.child2DnaId ?? + data.child1DnaId ?? + data.rootDnaId ?? + null + ); +} \ No newline at end of file diff --git a/src/keycloak/index.ts b/src/keycloak/index.ts index a81e2af9..b661450c 100644 --- a/src/keycloak/index.ts +++ b/src/keycloak/index.ts @@ -1,5 +1,121 @@ import { DecodedJwt, createDecoder } from "fast-jwt"; +/** + * RateLimiter + * Limits the rate of API calls to avoid overwhelming the server + */ +export class RateLimiter { + private requestsPerSecond: number; + private requestTimes: number[] = []; + + constructor(requestsPerSecond: number = 10) { + this.requestsPerSecond = requestsPerSecond; + } + + /** + * Throttle requests to stay within rate limit + * Waits if rate limit would be exceeded + */ + async throttle(): Promise { + const now = Date.now(); + // Remove timestamps older than 1 second + this.requestTimes = this.requestTimes.filter((t) => now - t < 1000); + + if (this.requestTimes.length >= this.requestsPerSecond) { + const oldestRequest = this.requestTimes[0]; + const waitTime = 1000 - (now - oldestRequest); + if (waitTime > 0) { + await new Promise((resolve) => setTimeout(resolve, waitTime)); + } + } + + this.requestTimes.push(Date.now()); + } + + /** + * Reset the rate limiter (e.g., after a long pause) + */ + reset(): void { + this.requestTimes = []; + } +} + +/** + * Check if an error is a network error (retryable) + * @param error - Error to check + * @returns true if error is network-related and retryable + */ +function isNetworkError(error: any): boolean { + if (!error) return false; + + // Check for fetch network errors + if (error.name === "TypeError" && error.message.includes("fetch")) { + return true; + } + + // Check for ECONNREFUSED, ETIMEDOUT, etc. + if (error.code && ["ECONNREFUSED", "ETIMEDOUT", "ECONNRESET", "ENOTFOUND"].includes(error.code)) { + return true; + } + + return false; +} + +/** + * Check if an HTTP status code is retryable + * @param status - HTTP status code + * @returns true if status code indicates a temporary error + */ +function isRetryableStatus(status: number): boolean { + // Retry on 5xx errors (server errors) and 429 (rate limit) + return status >= 500 || status === 429; +} + +/** + * Retry wrapper with exponential backoff + * Retries failed operations with increasing delay between attempts + * + * @param fn - Function to execute + * @param maxRetries - Maximum number of retry attempts + * @param baseDelay - Base delay in milliseconds (doubles each retry) + * @returns Promise with result of fn + */ +export async function withRetry( + fn: () => Promise, + maxRetries: number = 3, + baseDelay: number = 1000, +): Promise { + let lastError: any; + + for (let attempt = 0; attempt <= maxRetries; attempt++) { + try { + return await fn(); + } catch (error: any) { + lastError = error; + + // Check if error is retryable + const isRetryable = isNetworkError(error) || isRetryableStatus(error?.status); + + if (!isRetryable) { + // Don't retry on permanent errors (4xx except 429) + throw error; + } + + if (attempt < maxRetries) { + // Calculate delay with exponential backoff + const delay = baseDelay * Math.pow(2, attempt); + console.log( + `[withRetry] Attempt ${attempt + 1}/${maxRetries + 1} failed, retrying in ${delay}ms...`, + error.message || error, + ); + await new Promise((resolve) => setTimeout(resolve, delay)); + } + } + } + + throw lastError; +} + const KC_URL = process.env.KC_URL; const KC_REALMS = process.env.KC_REALMS; const KC_CLIENT_ID = process.env.KC_SERVICE_ACCOUNT_CLIENT_ID; @@ -298,6 +414,7 @@ export async function updateName( userId: string, firstName: string, lastName: string, + prefix: string, // opts: Record, ) { // const { password, ...rest } = opts; @@ -315,6 +432,9 @@ export async function updateName( // ...rest, firstName, lastName, + attributes: { + prefix, + }, }), }).catch((e) => console.log("Keycloak Error: ", e)); @@ -832,3 +952,183 @@ export async function resetPassword(username: string) { return false; } } + +export async function updateUserAttributes( + userId: string, + attributes: Record, +): Promise { + try { + // Get existing user data to preserve other attributes + const existingUser = await getUser(userId); + + if (!existingUser) { + console.error(`User ${userId} not found in Keycloak`); + return false; + } + + // Merge existing attributes with new attributes + // IMPORTANT: Spread all existing user fields to preserve firstName, lastName, email, etc. + // The Keycloak PUT endpoint performs a full update, so we must include all fields + const updatedAttributes = { + ...existingUser, + attributes: { + ...(existingUser.attributes || {}), + ...attributes, + }, + }; + + console.log( + `[updateUserAttributes] Sending to Keycloak:`, + JSON.stringify(updatedAttributes, null, 2), + ); + + const res = await fetch(`${KC_URL}/admin/realms/${KC_REALMS}/users/${userId}`, { + headers: { + authorization: `Bearer ${await getToken()}`, + "content-type": "application/json", + }, + method: "PUT", + body: JSON.stringify(updatedAttributes), + }).catch((e) => { + console.error(`[updateUserAttributes] Network error:`, e); + return null; + }); + + if (!res) { + console.error(`[updateUserAttributes] No response from Keycloak`); + return false; + } + + if (!res.ok) { + const errorText = await res.text(); + console.error(`[updateUserAttributes] Keycloak Error (${res.status}):`, errorText); + return false; + } + + console.log(`[updateUserAttributes] Successfully updated attributes for user ${userId}`); + return true; + } catch (error) { + console.error(`[updateUserAttributes] Error updating attributes for user ${userId}:`, error); + return false; + } +} + +export async function getAllUsersPaginated( + search: string = "", + batchSize: number = 100, +): Promise< + | Array<{ + id: string; + username: string; + firstName?: string; + lastName?: string; + email?: string; + enabled: boolean; + }> + | false +> { + const allUsers: any[] = []; + let first = 0; + let hasMore = true; + + while (hasMore) { + const res = await fetch( + `${KC_URL}/admin/realms/${KC_REALMS}/users?first=${first}&max=${batchSize}${search ? `&search=${search}` : ""}`, + { + headers: { + authorization: `Bearer ${await getToken()}`, + "content-type": `application/json`, + }, + }, + ).catch((e) => console.log("Keycloak Error: ", e)); + + if (!res) return false; + if (!res.ok) { + const errorText = await res.text(); + console.error("Keycloak Error Response: ", errorText); + return false; + } + + const rawText = await res.text(); + + try { + const batch = JSON.parse(rawText) as any[]; + + if (batch.length === 0) { + hasMore = false; + } else { + allUsers.push(...batch); + first += batch.length; + hasMore = batch.length === batchSize; + + // Log progress for large datasets + if (allUsers.length % 500 === 0) { + console.log(`[getAllUsersPaginated] Fetched ${allUsers.length} users so far...`); + } + } + } catch (error) { + console.error(`[getAllUsersPaginated] Failed to parse JSON response at offset ${first}:`); + console.error( + `[getAllUsersPaginated] Response preview (first 500 chars):`, + rawText.substring(0, 500), + ); + console.error( + `[getAllUsersPaginated] Response preview (last 200 chars):`, + rawText.slice(-200), + ); + throw new Error(`Failed to parse Keycloak response as JSON at batch starting at ${first}.`); + } + } + + console.log(`[getAllUsersPaginated] Total users fetched: ${allUsers.length}`); + + return allUsers.map((v: any) => ({ + id: v.id, + username: v.username, + firstName: v.firstName, + lastName: v.lastName, + email: v.email, + enabled: v.enabled === true || v.enabled === "true", + })); +} + +/** + * Create keycloak user by given username and password with roles + * + * Client must have permission to manage realm's user + * + * @returns user uuid or true if success, false otherwise. + */ +export async function createUserHaveProfile( + username: string, + password: string, + profileId: string, + prefix: string, + opts?: Record, + token?: string, +) { + const res = await fetch(`${KC_URL}/admin/realms/${KC_REALMS}/users`, { + // prettier-ignore + headers: { + "authorization": `Bearer ${token || await getToken()}`, + "content-type": `application/json`, + }, + method: "POST", + body: JSON.stringify({ + enabled: true, + credentials: [{ type: "password", value: password, temporary: false }], + username, + ...opts, + }), + }).catch((e) => console.log("Keycloak Error: ", e)); + + if (!res) return false; + if (!res.ok) { + // return Boolean(console.error("Keycloak Error Response: ", await res.json())); + return await res.json(); + } + + const path = res.headers.get("Location"); + const id = path?.split("/").at(-1); + return id || true; +} diff --git a/src/middlewares/auth.ts b/src/middlewares/auth.ts index c27e6188..9a571572 100644 --- a/src/middlewares/auth.ts +++ b/src/middlewares/auth.ts @@ -75,6 +75,16 @@ export async function expressAuthentication( request.app.locals.logData.userName = payload.name; request.app.locals.logData.user = payload.preferred_username; + // เก็บค่า profileId และ orgRootDnaId จาก token (ใช้ค่าว่างถ้าไม่มี) + request.app.locals.logData.profileId = payload.profileId ?? ""; + request.app.locals.logData.orgRootDnaId = payload.orgRootDnaId ?? ""; + request.app.locals.logData.orgChild1DnaId = payload.orgChild1DnaId ?? ""; + request.app.locals.logData.orgChild2DnaId = payload.orgChild2DnaId ?? ""; + request.app.locals.logData.orgChild3DnaId = payload.orgChild3DnaId ?? ""; + request.app.locals.logData.orgChild4DnaId = payload.orgChild4DnaId ?? ""; + request.app.locals.logData.empType = payload.empType ?? ""; + request.app.locals.logData.prefix = payload.prefix ?? ""; + return payload; } diff --git a/src/middlewares/logs.ts b/src/middlewares/logs.ts index 70020810..3f1a5963 100644 --- a/src/middlewares/logs.ts +++ b/src/middlewares/logs.ts @@ -1,9 +1,6 @@ import { NextFunction, Request, Response } from "express"; import { Client } from "@elastic/elasticsearch"; -import { AppDataSource } from "../database/data-source"; -import { PosMaster } from "../entities/PosMaster"; -import { OrgRevision } from "../entities/OrgRevision"; -import { Profile } from "../entities/Profile"; +import { logMemoryStore } from "../utils/LogMemoryStore"; if (!process.env.ELASTICSEARCH_INDEX) { throw new Error("Require ELASTICSEARCH_INDEX to store log."); @@ -27,9 +24,6 @@ async function logMiddleware(req: Request, res: Response, next: NextFunction) { if (!req.url.startsWith("/api/")) return next(); let data: any; - const repoPosmaster = AppDataSource.getRepository(PosMaster); - const repoProfile = AppDataSource.getRepository(Profile); - const repoRevision = AppDataSource.getRepository(OrgRevision); const originalJson = res.json; @@ -42,13 +36,6 @@ async function logMiddleware(req: Request, res: Response, next: NextFunction) { req.app.locals.logData = {}; - const revision = await repoRevision.findOne({ - where: { - orgRevisionIsCurrent: true, - orgRevisionIsDraft: false, - }, - }); - res.on("finish", async () => { try { if (!req.url.startsWith("/api/")) return; @@ -69,7 +56,7 @@ async function logMiddleware(req: Request, res: Response, next: NextFunction) { if (req.url.startsWith("/api/v1/org/profile/")) system = "registry"; if (req.url.startsWith("/api/v1/org/profile-employee/")) system = "registry"; if (req.url.startsWith("/api/v1/org/profile-temp/")) system = "registry"; - + if (req.url.startsWith("/api/v1/org/commandType/admin")) system = "admin"; if (req.url.startsWith("/api/v1/org/commandSys/")) system = "admin"; if (req.url.startsWith("/api/v1/org/commandSalary/")) system = "admin"; @@ -79,16 +66,18 @@ async function logMiddleware(req: Request, res: Response, next: NextFunction) { if (req.url.startsWith("/api/v1/org/keycloak/")) system = "registry"; const level = LOG_LEVEL_MAP[process.env.LOG_LEVEL ?? "debug"] || 4; - const profileByKeycloak = await repoProfile.findOne({ - where: { keycloak: req.app.locals.logData.userId }, - }); - const rootId = await repoPosmaster.findOne({ - where: { - current_holderId: profileByKeycloak?.id, - orgRevisionId: revision?.id, - }, - select: ["orgRootId"], - }); + + // // Get profile from cache + // const profileByKeycloak = await logMemoryStore.getProfileByKeycloak( + // req.app.locals.logData.userId, + // ); + + // // Get rootId from cache + // const rootId = await logMemoryStore.getRootIdByProfileId(profileByKeycloak?.id); + // // console.log("ancestorDNA:", rootId); + + // Get rootId from token + const rootId = req.app.locals.logData?.orgRootDnaId; if (level === 1 && res.statusCode < 500) return; if (level === 2 && res.statusCode < 400) return; @@ -96,7 +85,7 @@ async function logMiddleware(req: Request, res: Response, next: NextFunction) { const obj = { logType: res.statusCode >= 500 ? "error" : res.statusCode >= 400 ? "warning" : "info", ip: req.ip, - rootId: rootId?.orgRootId ?? null, + rootId: rootId ?? null, systemName: system, startTimeStamp: timestamp, endTimeStamp: new Date().toISOString(), diff --git a/src/middlewares/user.ts b/src/middlewares/user.ts index e5c48d9a..75c84d01 100644 --- a/src/middlewares/user.ts +++ b/src/middlewares/user.ts @@ -9,6 +9,14 @@ export type RequestWithUser = Request & { preferred_username: string; email: string; role: string[]; + profileId?: string; + prefix?: string; + orgRootDnaId?: string; + orgChild1DnaId?: string; + orgChild2DnaId?: string; + orgChild3DnaId?: string; + orgChild4DnaId?: string; + empType?: string; }; }; diff --git a/src/migration/1770023808315-create_table_commandOperator.ts b/src/migration/1770023808315-create_table_commandOperator.ts new file mode 100644 index 00000000..36057adf --- /dev/null +++ b/src/migration/1770023808315-create_table_commandOperator.ts @@ -0,0 +1,17 @@ +import { MigrationInterface, QueryRunner } from "typeorm"; + +export class CreateTableCommandOperator1770023808315 implements MigrationInterface { + name = 'CreateTableCommandOperator1770023808315' + + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query(`CREATE TABLE \`commandOperator\` (\`id\` varchar(36) NOT NULL, \`createdAt\` datetime(6) NOT NULL COMMENT 'สร้างข้อมูลเมื่อ' DEFAULT CURRENT_TIMESTAMP(6), \`createdUserId\` varchar(40) NOT NULL COMMENT 'User Id ที่สร้างข้อมูล' DEFAULT '00000000-0000-0000-0000-000000000000', \`lastUpdatedAt\` datetime(6) NOT NULL COMMENT 'แก้ไขข้อมูลล่าสุดเมื่อ' DEFAULT CURRENT_TIMESTAMP(6), \`lastUpdateUserId\` varchar(40) NOT NULL COMMENT 'User Id ที่แก้ไขข้อมูล' DEFAULT '00000000-0000-0000-0000-000000000000', \`createdFullName\` varchar(200) NOT NULL COMMENT 'ชื่อ User ที่สร้างข้อมูล' DEFAULT 'System Administrator', \`lastUpdateFullName\` varchar(200) NOT NULL COMMENT 'ชื่อ User ที่แก้ไขข้อมูลล่าสุด' DEFAULT 'System Administrator', \`profileId\` varchar(40) NULL COMMENT 'คีย์นอก(FK)ของตาราง profile', \`prefix\` varchar(255) NULL COMMENT 'คำนำหน้า', \`firstName\` varchar(255) NULL COMMENT 'ชื่อ', \`lastName\` varchar(255) NULL COMMENT 'สกุล', \`posNo\` varchar(255) NULL COMMENT 'เลขที่ตำแหน่ง', \`posType\` varchar(255) NULL COMMENT 'ประเภท', \`posLevel\` varchar(255) NULL COMMENT 'ระดับ', \`position\` varchar(255) NULL COMMENT 'ตำแหน่งในสายงาน', \`positionExecutive\` varchar(255) NULL COMMENT 'ตำแหน่งทางการบริหาร', \`roleName\` varchar(255) NULL COMMENT 'บทบาทของเจ้าหน้าที่ดำเนินการ เช่น ผอ.สกจ. / รักษาการ ผอ.สกจ. / ผอ. กบห. / รักษาการ ผอ. กบห. / ผอ. ส่วน', \`commandId\` varchar(40) NOT NULL COMMENT 'คีย์นอก(FK)ของตาราง command', PRIMARY KEY (\`id\`)) ENGINE=InnoDB`); + await queryRunner.query(`ALTER TABLE \`commandOperator\` ADD CONSTRAINT \`FK_343a2ecd7cb855397f19a990008\` FOREIGN KEY (\`commandId\`) REFERENCES \`command\`(\`id\`) ON DELETE NO ACTION ON UPDATE NO ACTION`); + + } + + public async down(queryRunner: QueryRunner): Promise { + await queryRunner.query(`ALTER TABLE \`commandOperator\` DROP FOREIGN KEY \`FK_343a2ecd7cb855397f19a990008\``); + await queryRunner.query(`DROP TABLE \`commandOperator\``); + } + +} diff --git a/src/migration/1770089334925-update_table_commandOperator_add_column_orderNo.ts b/src/migration/1770089334925-update_table_commandOperator_add_column_orderNo.ts new file mode 100644 index 00000000..11990b5b --- /dev/null +++ b/src/migration/1770089334925-update_table_commandOperator_add_column_orderNo.ts @@ -0,0 +1,14 @@ +import { MigrationInterface, QueryRunner } from "typeorm"; + +export class UpdateTableCommandOperatorAddColumnOrderNo1770089334925 implements MigrationInterface { + name = 'UpdateTableCommandOperatorAddColumnOrderNo1770089334925' + + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query(`ALTER TABLE \`commandOperator\` ADD \`orderNo\` int NULL COMMENT 'ลำดับบทบาทของเจ้าหน้าที่ดำเนินการ'`); + } + + public async down(queryRunner: QueryRunner): Promise { + await queryRunner.query(`ALTER TABLE \`commandOperator\` DROP COLUMN \`orderNo\``); + } + +} diff --git a/src/migration/1770110880489-update_tables_tranning_development_actPosition_add_column_isDeleted.ts b/src/migration/1770110880489-update_tables_tranning_development_actPosition_add_column_isDeleted.ts new file mode 100644 index 00000000..ed1d3f70 --- /dev/null +++ b/src/migration/1770110880489-update_tables_tranning_development_actPosition_add_column_isDeleted.ts @@ -0,0 +1,18 @@ +import { MigrationInterface, QueryRunner } from "typeorm"; + +export class UpdateTablesTranningDevelopmentActPositionAddColumnIsDeleted1770110880489 implements MigrationInterface { + name = 'UpdateTablesTranningDevelopmentActPositionAddColumnIsDeleted1770110880489' + + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query(`ALTER TABLE \`profileTraining\` ADD \`isDeleted\` tinyint NOT NULL COMMENT 'สถานะลบข้อมูลการฝึกอบรม/ดูงาน' DEFAULT 0`); + await queryRunner.query(`ALTER TABLE \`profileDevelopment\` ADD \`isDeleted\` tinyint NOT NULL COMMENT 'สถานะลบข้อมูลการพัฒนารายบุคคล (Individual Development Plan)' DEFAULT 0`); + await queryRunner.query(`ALTER TABLE \`profileActposition\` ADD \`isDeleted\` tinyint NOT NULL COMMENT 'สถานะลบข้อมูลรักษาการในตำแหน่ง' DEFAULT 0`); + } + + public async down(queryRunner: QueryRunner): Promise { + await queryRunner.query(`ALTER TABLE \`profileActposition\` DROP COLUMN \`isDeleted\``); + await queryRunner.query(`ALTER TABLE \`profileDevelopment\` DROP COLUMN \`isDeleted\``); + await queryRunner.query(`ALTER TABLE \`profileTraining\` DROP COLUMN \`isDeleted\``); + } + +} diff --git a/src/migration/1770112472041-update_tables_history_tranning_development_actPosition_add_column_isDeleted.ts b/src/migration/1770112472041-update_tables_history_tranning_development_actPosition_add_column_isDeleted.ts new file mode 100644 index 00000000..6d33f623 --- /dev/null +++ b/src/migration/1770112472041-update_tables_history_tranning_development_actPosition_add_column_isDeleted.ts @@ -0,0 +1,18 @@ +import { MigrationInterface, QueryRunner } from "typeorm"; + +export class UpdateTablesHistoryTranningDevelopmentActPositionAddColumnIsDeleted1770112472041 implements MigrationInterface { + name = 'UpdateTablesHistoryTranningDevelopmentActPositionAddColumnIsDeleted1770112472041' + + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query(`ALTER TABLE \`profileTrainingHistory\` ADD \`isDeleted\` tinyint NOT NULL COMMENT 'สถานะลบข้อมูลการฝึกอบรม/ดูงาน' DEFAULT 0`); + await queryRunner.query(`ALTER TABLE \`profileDevelopmentHistory\` ADD \`isDeleted\` tinyint NOT NULL COMMENT 'สถานะลบข้อมูลการพัฒนารายบุคคล (Individual Development Plan)' DEFAULT 0`); + await queryRunner.query(`ALTER TABLE \`profileActpositionHistory\` ADD \`isDeleted\` tinyint NOT NULL COMMENT 'สถานะลบข้อมูลรักษาการในตำแหน่ง' DEFAULT 0`); + } + + public async down(queryRunner: QueryRunner): Promise { + await queryRunner.query(`ALTER TABLE \`profileActpositionHistory\` DROP COLUMN \`isDeleted\``); + await queryRunner.query(`ALTER TABLE \`profileDevelopmentHistory\` DROP COLUMN \`isDeleted\``); + await queryRunner.query(`ALTER TABLE \`profileTrainingHistory\` DROP COLUMN \`isDeleted\``); + } + +} diff --git a/src/migration/1770173342631-AddEmailPhoneToIssuesOnly.ts b/src/migration/1770173342631-AddEmailPhoneToIssuesOnly.ts new file mode 100644 index 00000000..e379262d --- /dev/null +++ b/src/migration/1770173342631-AddEmailPhoneToIssuesOnly.ts @@ -0,0 +1,23 @@ +import { MigrationInterface, QueryRunner } from "typeorm"; + +export class AddEmailPhoneToIssues1738627400000 implements MigrationInterface { + name = 'AddEmailPhoneToIssues1738627400000' + + public async up(queryRunner: QueryRunner): Promise { + + await queryRunner.query(` + ALTER TABLE \`issues\` + ADD \`email\` varchar(255) NULL COMMENT 'อีเมลผู้รายงาน' + `); + + await queryRunner.query(` + ALTER TABLE \`issues\` + ADD \`phone\` varchar(20) NULL COMMENT 'เบอร์โทรผู้รายงาน' + `); + } + + public async down(queryRunner: QueryRunner): Promise { + await queryRunner.query(`ALTER TABLE \`issues\` DROP COLUMN \`phone\``); + await queryRunner.query(`ALTER TABLE \`issues\` DROP COLUMN \`email\``); + } +} \ No newline at end of file diff --git a/src/migration/1770349291424-create_table_profileSalaryBackup.ts b/src/migration/1770349291424-create_table_profileSalaryBackup.ts new file mode 100644 index 00000000..2f5c5b8d --- /dev/null +++ b/src/migration/1770349291424-create_table_profileSalaryBackup.ts @@ -0,0 +1,14 @@ +import { MigrationInterface, QueryRunner } from "typeorm"; + +export class CreateTableProfileSalaryBackup1770349291424 implements MigrationInterface { + name = 'CreateTableProfileSalaryBackup1770349291424' + + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query(`CREATE TABLE \`profileSalaryBackup\` (\`id\` varchar(36) NOT NULL, \`createdAt\` datetime(6) NOT NULL COMMENT 'สร้างข้อมูลเมื่อ' DEFAULT CURRENT_TIMESTAMP(6), \`createdUserId\` varchar(40) NOT NULL COMMENT 'User Id ที่สร้างข้อมูล' DEFAULT '00000000-0000-0000-0000-000000000000', \`lastUpdatedAt\` datetime(6) NOT NULL COMMENT 'แก้ไขข้อมูลล่าสุดเมื่อ' DEFAULT CURRENT_TIMESTAMP(6), \`lastUpdateUserId\` varchar(40) NOT NULL COMMENT 'User Id ที่แก้ไขข้อมูล' DEFAULT '00000000-0000-0000-0000-000000000000', \`createdFullName\` varchar(200) NOT NULL COMMENT 'ชื่อ User ที่สร้างข้อมูล' DEFAULT 'System Administrator', \`lastUpdateFullName\` varchar(200) NOT NULL COMMENT 'ชื่อ User ที่แก้ไขข้อมูลล่าสุด' DEFAULT 'System Administrator', \`profileSalaryId\` varchar(40) NULL COMMENT 'อ้างอิง profileSalary (no FK constraint)', \`profileId\` varchar(40) NULL COMMENT 'อ้างอิง profile (no FK constraint)', \`profileEmployeeId\` varchar(40) NULL COMMENT 'อ้างอิง ProfileEmployee (no FK constraint)', \`order\` int NULL COMMENT 'เรียงลำดับใหมาตามการนำเข้า', \`commandNo\` varchar(255) NULL COMMENT 'เลขที่คำสั่ง', \`commandYear\` int NULL COMMENT 'ปีที่ออกคำสั่ง', \`commandDateSign\` datetime NULL COMMENT 'คำสั่งวันที่', \`commandDateAffect\` datetime NULL COMMENT 'คำสั่งมีผลวันที่', \`commandCode\` varchar(255) NULL COMMENT 'รหัสประเภทของคำสั่ง', \`commandName\` varchar(255) NULL COMMENT 'ชื่อประเภทคำสั่ง', \`posNoAbb\` varchar(40) NULL COMMENT 'ตัวย่อเลขที่ตำแหน่ง', \`posNo\` varchar(40) NULL COMMENT 'เลขที่ตำแหน่ง', \`positionName\` varchar(255) NULL COMMENT 'ตำแหน่ง', \`positionType\` varchar(255) NULL COMMENT 'ประเภทตำแหน่ง', \`positionLevel\` varchar(255) NULL COMMENT 'ระดับตำแหน่ง', \`positionCee\` varchar(255) NULL COMMENT 'ระดับของเก่าที่ยังไม่เทียบเท่าแบบแท่ง', \`orgRoot\` varchar(255) NULL COMMENT 'root name', \`orgChild1\` varchar(255) NULL COMMENT 'child1 name', \`orgChild2\` varchar(255) NULL COMMENT 'child2 name', \`orgChild3\` varchar(255) NULL COMMENT 'child3 name', \`orgChild4\` varchar(255) NULL COMMENT 'child4 name', \`positionExecutive\` varchar(255) NULL COMMENT 'ตำแหน่งทางการบริหาร', \`amount\` double NULL COMMENT 'เงินเดือนฐาน' DEFAULT '0', \`amountSpecial\` double NULL COMMENT 'เงินพิเศษ' DEFAULT '0', \`positionSalaryAmount\` double NULL COMMENT 'เงินประจำตำแหน่ง' DEFAULT '0', \`mouthSalaryAmount\` double NULL COMMENT 'เงินค่าตอบแทนรายเดือน' DEFAULT '0', \`remark\` text NULL COMMENT 'หมายเหตุ', \`refId\` varchar(255) NULL COMMENT 'refId', \`dateGovernment\` datetime NULL COMMENT 'วันที่', \`isGovernment\` tinyint NULL COMMENT 'เข้ารับราชการ', \`isEntry\` tinyint NOT NULL COMMENT 'ข้อมูลจาก Entry' DEFAULT 0, \`positionPathSide\` varchar(255) NULL COMMENT 'ด้านของตำแหน่ง', \`positionLine\` varchar(255) NULL COMMENT 'ตำแหน่งในสายงาน', \`commandId\` varchar(40) NULL COMMENT 'อ้างอิง command (no FK constraint)', \`posNumCodeSit\` varchar(255) NULL COMMENT 'หน่วยงานที่ออกคำสั่ง', \`posNumCodeSitAbb\` varchar(255) NULL COMMENT 'หน่วยงานที่ออกคำสั่ง(ตัวย่อ)', \`positionExecutiveField\` varchar(255) NULL COMMENT 'ด้านทางการบริหาร', \`positionArea\` varchar(255) NULL COMMENT 'ด้าน/สาขา', PRIMARY KEY (\`id\`)) ENGINE=InnoDB`); + } + + public async down(queryRunner: QueryRunner): Promise { + await queryRunner.query(`DROP TABLE \`profileSalaryBackup\``); + } + +} diff --git a/src/migration/1770870836370-add_fields_isDeleted.ts b/src/migration/1770870836370-add_fields_isDeleted.ts new file mode 100644 index 00000000..6d04805c --- /dev/null +++ b/src/migration/1770870836370-add_fields_isDeleted.ts @@ -0,0 +1,74 @@ +import { MigrationInterface, QueryRunner } from "typeorm"; + +export class AddFieldsIsDeleted1770870836370 implements MigrationInterface { + name = 'AddFieldsIsDeleted1770870836370' + + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query(`ALTER TABLE \`profileCertificateHistory\` ADD \`isDeleted\` tinyint NOT NULL COMMENT 'สถานะลบข้อมูล' DEFAULT 0`); + await queryRunner.query(`ALTER TABLE \`profileCertificate\` ADD \`isDeleted\` tinyint NOT NULL COMMENT 'สถานะลบข้อมูล' DEFAULT 0`); + await queryRunner.query(`ALTER TABLE \`profileInsigniaHistory\` ADD \`isDeleted\` tinyint NOT NULL COMMENT 'สถานะลบข้อมูล' DEFAULT 0`); + await queryRunner.query(`ALTER TABLE \`profileInsignia\` ADD \`isDeleted\` tinyint NOT NULL COMMENT 'สถานะลบข้อมูล' DEFAULT 0`); + await queryRunner.query(`ALTER TABLE \`profileHonorHistory\` ADD \`isDeleted\` tinyint NOT NULL COMMENT 'สถานะลบข้อมูล' DEFAULT 0`); + await queryRunner.query(`ALTER TABLE \`profileHonor\` ADD \`isDeleted\` tinyint NOT NULL COMMENT 'สถานะลบข้อมูล' DEFAULT 0`); + await queryRunner.query(`ALTER TABLE \`profileAssessmentHistory\` ADD \`isDeleted\` tinyint NOT NULL COMMENT 'สถานะลบข้อมูล' DEFAULT 0`); + await queryRunner.query(`ALTER TABLE \`profileAssessment\` ADD \`isDeleted\` tinyint NOT NULL COMMENT 'สถานะลบข้อมูล' DEFAULT 0`); + await queryRunner.query(`ALTER TABLE \`profileLeave\` ADD \`isDeleted\` tinyint NOT NULL COMMENT 'สถานะลบข้อมูล' DEFAULT 0`); + await queryRunner.query(`ALTER TABLE \`profileLeaveHistory\` ADD \`isDeleted\` tinyint NOT NULL COMMENT 'สถานะลบข้อมูล' DEFAULT 0`); + await queryRunner.query(`ALTER TABLE \`profileDutyHistory\` ADD \`isDeleted\` tinyint NOT NULL COMMENT 'สถานะลบข้อมูล' DEFAULT 0`); + await queryRunner.query(`ALTER TABLE \`profileDuty\` ADD \`isDeleted\` tinyint NOT NULL COMMENT 'สถานะลบข้อมูล' DEFAULT 0`); + await queryRunner.query(`ALTER TABLE \`profileNopaidHistory\` ADD \`isDeleted\` tinyint NOT NULL COMMENT 'สถานะลบข้อมูล' DEFAULT 0`); + await queryRunner.query(`ALTER TABLE \`profileNopaid\` ADD \`isDeleted\` tinyint NOT NULL COMMENT 'สถานะลบข้อมูล' DEFAULT 0`); + await queryRunner.query(`ALTER TABLE \`profileDisciplineHistory\` ADD \`isDeleted\` tinyint NOT NULL COMMENT 'สถานะลบข้อมูล' DEFAULT 0`); + await queryRunner.query(`ALTER TABLE \`profileDiscipline\` ADD \`isDeleted\` tinyint NOT NULL COMMENT 'สถานะลบข้อมูล' DEFAULT 0`); + await queryRunner.query(`ALTER TABLE \`profileChangeNameHistory\` ADD \`isDeleted\` tinyint NOT NULL COMMENT 'สถานะลบข้อมูล' DEFAULT 0`); + await queryRunner.query(`ALTER TABLE \`profileChangeName\` ADD \`isDeleted\` tinyint NOT NULL COMMENT 'สถานะลบข้อมูล' DEFAULT 0`); + await queryRunner.query(`ALTER TABLE \`profileEducationHistory\` ADD \`isDeleted\` tinyint NOT NULL COMMENT 'สถานะลบข้อมูล' DEFAULT 0`); + await queryRunner.query(`ALTER TABLE \`profileEducation\` ADD \`isDeleted\` tinyint NOT NULL COMMENT 'สถานะลบข้อมูล' DEFAULT 0`); + await queryRunner.query(`ALTER TABLE \`profileAbilityHistory\` ADD \`isDeleted\` tinyint NOT NULL COMMENT 'สถานะลบข้อมูล' DEFAULT 0`); + await queryRunner.query(`ALTER TABLE \`profileAbility\` ADD \`isDeleted\` tinyint NOT NULL COMMENT 'สถานะลบข้อมูล' DEFAULT 0`); + await queryRunner.query(`ALTER TABLE \`profileOtherHistory\` ADD \`isDeleted\` tinyint NOT NULL COMMENT 'สถานะลบข้อมูล' DEFAULT 0`); + await queryRunner.query(`ALTER TABLE \`profileOther\` ADD \`isDeleted\` tinyint NOT NULL COMMENT 'สถานะลบข้อมูล' DEFAULT 0`); + await queryRunner.query(`ALTER TABLE \`profileChildrenHistory\` ADD \`isDeleted\` tinyint NOT NULL COMMENT 'สถานะลบข้อมูล' DEFAULT 0`); + await queryRunner.query(`ALTER TABLE \`profileChildren\` ADD \`isDeleted\` tinyint NOT NULL COMMENT 'สถานะลบข้อมูล' DEFAULT 0`); + await queryRunner.query(`ALTER TABLE \`profileSalaryHistory\` ADD \`isDeleted\` tinyint NOT NULL COMMENT 'สถานะลบข้อมูล' DEFAULT 0`); + await queryRunner.query(`ALTER TABLE \`profileSalary\` ADD \`isDeleted\` tinyint NOT NULL COMMENT 'สถานะลบข้อมูล' DEFAULT 0`); + await queryRunner.query(`ALTER TABLE \`profileAssistanceHistory\` ADD \`isDeleted\` tinyint NOT NULL COMMENT 'สถานะลบข้อมูล' DEFAULT 0`); + await queryRunner.query(`ALTER TABLE \`profileAssistance\` ADD \`isDeleted\` tinyint NOT NULL COMMENT 'สถานะลบข้อมูล' DEFAULT 0`); + await queryRunner.query(`ALTER TABLE \`profileSalaryBackup\` ADD \`isDeleted\` tinyint NOT NULL COMMENT 'สถานะลบข้อมูล' DEFAULT 0`); + } + + public async down(queryRunner: QueryRunner): Promise { + await queryRunner.query(`ALTER TABLE \`profileSalaryBackup\` DROP COLUMN \`isDeleted\``); + await queryRunner.query(`ALTER TABLE \`profileAssistance\` DROP COLUMN \`isDeleted\``); + await queryRunner.query(`ALTER TABLE \`profileAssistanceHistory\` DROP COLUMN \`isDeleted\``); + await queryRunner.query(`ALTER TABLE \`profileSalary\` DROP COLUMN \`isDeleted\``); + await queryRunner.query(`ALTER TABLE \`profileSalaryHistory\` DROP COLUMN \`isDeleted\``); + await queryRunner.query(`ALTER TABLE \`profileChildren\` DROP COLUMN \`isDeleted\``); + await queryRunner.query(`ALTER TABLE \`profileChildrenHistory\` DROP COLUMN \`isDeleted\``); + await queryRunner.query(`ALTER TABLE \`profileOther\` DROP COLUMN \`isDeleted\``); + await queryRunner.query(`ALTER TABLE \`profileOtherHistory\` DROP COLUMN \`isDeleted\``); + await queryRunner.query(`ALTER TABLE \`profileAbility\` DROP COLUMN \`isDeleted\``); + await queryRunner.query(`ALTER TABLE \`profileAbilityHistory\` DROP COLUMN \`isDeleted\``); + await queryRunner.query(`ALTER TABLE \`profileEducation\` DROP COLUMN \`isDeleted\``); + await queryRunner.query(`ALTER TABLE \`profileEducationHistory\` DROP COLUMN \`isDeleted\``); + await queryRunner.query(`ALTER TABLE \`profileChangeName\` DROP COLUMN \`isDeleted\``); + await queryRunner.query(`ALTER TABLE \`profileChangeNameHistory\` DROP COLUMN \`isDeleted\``); + await queryRunner.query(`ALTER TABLE \`profileDiscipline\` DROP COLUMN \`isDeleted\``); + await queryRunner.query(`ALTER TABLE \`profileDisciplineHistory\` DROP COLUMN \`isDeleted\``); + await queryRunner.query(`ALTER TABLE \`profileNopaid\` DROP COLUMN \`isDeleted\``); + await queryRunner.query(`ALTER TABLE \`profileNopaidHistory\` DROP COLUMN \`isDeleted\``); + await queryRunner.query(`ALTER TABLE \`profileDuty\` DROP COLUMN \`isDeleted\``); + await queryRunner.query(`ALTER TABLE \`profileDutyHistory\` DROP COLUMN \`isDeleted\``); + await queryRunner.query(`ALTER TABLE \`profileLeaveHistory\` DROP COLUMN \`isDeleted\``); + await queryRunner.query(`ALTER TABLE \`profileLeave\` DROP COLUMN \`isDeleted\``); + await queryRunner.query(`ALTER TABLE \`profileAssessment\` DROP COLUMN \`isDeleted\``); + await queryRunner.query(`ALTER TABLE \`profileAssessmentHistory\` DROP COLUMN \`isDeleted\``); + await queryRunner.query(`ALTER TABLE \`profileHonor\` DROP COLUMN \`isDeleted\``); + await queryRunner.query(`ALTER TABLE \`profileHonorHistory\` DROP COLUMN \`isDeleted\``); + await queryRunner.query(`ALTER TABLE \`profileInsignia\` DROP COLUMN \`isDeleted\``); + await queryRunner.query(`ALTER TABLE \`profileInsigniaHistory\` DROP COLUMN \`isDeleted\``); + await queryRunner.query(`ALTER TABLE \`profileCertificate\` DROP COLUMN \`isDeleted\``); + await queryRunner.query(`ALTER TABLE \`profileCertificateHistory\` DROP COLUMN \`isDeleted\``); + } + +} diff --git a/src/migration/1770872734016-move_fields_isDeleted_in_tables_profileSalary.ts b/src/migration/1770872734016-move_fields_isDeleted_in_tables_profileSalary.ts new file mode 100644 index 00000000..31d4a79c --- /dev/null +++ b/src/migration/1770872734016-move_fields_isDeleted_in_tables_profileSalary.ts @@ -0,0 +1,19 @@ +import { MigrationInterface, QueryRunner } from "typeorm"; + +export class MoveFieldsIsDeletedInTablesProfileSalary1770872734016 implements MigrationInterface { + name = 'MoveFieldsIsDeletedInTablesProfileSalary1770872734016' + + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query(`ALTER TABLE \`profileSalaryHistory\` DROP COLUMN \`isDeleted\``); + await queryRunner.query(`ALTER TABLE \`profileSalary\` DROP COLUMN \`isDeleted\``); + await queryRunner.query(`ALTER TABLE \`profileSalaryBackup\` DROP COLUMN \`isDeleted\``); + } + + public async down(queryRunner: QueryRunner): Promise { + await queryRunner.query(`ALTER TABLE \`profileSalaryBackup\` ADD \`isDeleted\` tinyint NOT NULL COMMENT 'สถานะลบข้อมูล' DEFAULT '0'`); + await queryRunner.query(`ALTER TABLE \`profileSalary\` ADD \`isDeleted\` tinyint NOT NULL COMMENT 'สถานะลบข้อมูล' DEFAULT '0'`); + await queryRunner.query(`ALTER TABLE \`profileSalaryHistory\` ADD \`isDeleted\` tinyint NOT NULL COMMENT 'สถานะลบข้อมูล' DEFAULT '0'`); + + } + +} diff --git a/src/migration/1770875727560-add_indexes_for_performance.ts b/src/migration/1770875727560-add_indexes_for_performance.ts new file mode 100644 index 00000000..c08e82ab --- /dev/null +++ b/src/migration/1770875727560-add_indexes_for_performance.ts @@ -0,0 +1,62 @@ +import { MigrationInterface, QueryRunner } from "typeorm"; + +export class AddIndexesForPerformance1770875727560 implements MigrationInterface { + name = "AddIndexesForPerformance1770875727560"; + + public async up(queryRunner: QueryRunner): Promise { + // Index for posMasterHistory lookups + await queryRunner.query(` + CREATE INDEX IDX_posMasterHistory_ancestorDNA + ON posMasterHistory(ancestorDNA, createdAt DESC) + `); + + // Index for org tables lookups + await queryRunner.query(` + CREATE INDEX IDX_orgRoot_ancestorDNA_revision + ON orgRoot(ancestorDNA, orgRevisionId) + `); + + await queryRunner.query(` + CREATE INDEX IDX_orgChild1_ancestorDNA_revision + ON orgChild1(ancestorDNA, orgRevisionId) + `); + + await queryRunner.query(` + CREATE INDEX IDX_orgChild2_ancestorDNA_revision + ON orgChild2(ancestorDNA, orgRevisionId) + `); + + await queryRunner.query(` + CREATE INDEX IDX_orgChild3_ancestorDNA_revision + ON orgChild3(ancestorDNA, orgRevisionId) + `); + + await queryRunner.query(` + CREATE INDEX IDX_orgChild4_ancestorDNA_revision + ON orgChild4(ancestorDNA, orgRevisionId) + `); + + // Index for posMaster lookups + await queryRunner.query(` + CREATE INDEX IDX_posMaster_revision_org + ON posMaster(orgRevisionId, orgRootId, orgChild1Id, orgChild2Id, orgChild3Id, orgChild4Id) + `); + + await queryRunner.query(` + CREATE INDEX IDX_posMaster_ancestorDNA + ON posMaster(ancestorDNA) + `); + } + + public async down(queryRunner: QueryRunner): Promise { + await queryRunner.query(`DROP INDEX IDX_position_posMasterId ON position`); + await queryRunner.query(`DROP INDEX IDX_posMaster_ancestorDNA ON posMaster`); + await queryRunner.query(`DROP INDEX IDX_posMaster_revision_org ON posMaster`); + await queryRunner.query(`DROP INDEX IDX_orgChild4_ancestorDNA_revision ON orgChild4`); + await queryRunner.query(`DROP INDEX IDX_orgChild3_ancestorDNA_revision ON orgChild3`); + await queryRunner.query(`DROP INDEX IDX_orgChild2_ancestorDNA_revision ON orgChild2`); + await queryRunner.query(`DROP INDEX IDX_orgChild1_ancestorDNA_revision ON orgChild1`); + await queryRunner.query(`DROP INDEX IDX_orgRoot_ancestorDNA_revision ON orgRoot`); + await queryRunner.query(`DROP INDEX IDX_posMasterHistory_ancestorDNA ON posMasterHistory`); + } +} diff --git a/src/migration/1771409869898-add_relation_posLevelTemp.ts b/src/migration/1771409869898-add_relation_posLevelTemp.ts new file mode 100644 index 00000000..3d7dfce3 --- /dev/null +++ b/src/migration/1771409869898-add_relation_posLevelTemp.ts @@ -0,0 +1,14 @@ +import { MigrationInterface, QueryRunner } from "typeorm"; + +export class AddRelationPosLevelTemp1771409869898 implements MigrationInterface { + name = 'AddRelationPosLevelTemp1771409869898' + + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query(`ALTER TABLE \`profileEmployee\` ADD CONSTRAINT \`FK_49694ac4248a7bab7d12d4be280\` FOREIGN KEY (\`posLevelIdTemp\`) REFERENCES \`employeePosLevel\`(\`id\`) ON DELETE NO ACTION ON UPDATE NO ACTION`); + } + + public async down(queryRunner: QueryRunner): Promise { + await queryRunner.query(`ALTER TABLE \`profileEmployee\` DROP FOREIGN KEY \`FK_49694ac4248a7bab7d12d4be280\``); + } + +} diff --git a/src/migration/1771470195684-add_relation_posTypeTemp.ts b/src/migration/1771470195684-add_relation_posTypeTemp.ts new file mode 100644 index 00000000..4772ded9 --- /dev/null +++ b/src/migration/1771470195684-add_relation_posTypeTemp.ts @@ -0,0 +1,15 @@ +import { MigrationInterface, QueryRunner } from "typeorm"; + +export class AddRelationPosTypeTemp1771470195684 implements MigrationInterface { + name = 'AddRelationPosTypeTemp1771470195684' + + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query(`ALTER TABLE \`profileEmployee\` ADD CONSTRAINT \`FK_bc2f7791abcc1e55a73b99216ca\` FOREIGN KEY (\`posTypeIdTemp\`) REFERENCES \`employeePosType\`(\`id\`) ON DELETE NO ACTION ON UPDATE NO ACTION`); + + } + + public async down(queryRunner: QueryRunner): Promise { + await queryRunner.query(`ALTER TABLE \`profileEmployee\` DROP FOREIGN KEY \`FK_bc2f7791abcc1e55a73b99216ca\``); + } + +} diff --git a/src/migration/1771832659308-update_empPosMaster_add_fields_isCondition_.ts b/src/migration/1771832659308-update_empPosMaster_add_fields_isCondition_.ts new file mode 100644 index 00000000..98246b62 --- /dev/null +++ b/src/migration/1771832659308-update_empPosMaster_add_fields_isCondition_.ts @@ -0,0 +1,20 @@ +import { MigrationInterface, QueryRunner } from "typeorm"; + +export class UpdateEmpPosMasterAddFieldsIsCondition_1771832659308 implements MigrationInterface { + name = 'UpdateEmpPosMasterAddFieldsIsCondition_1771832659308' + + public async up(queryRunner: QueryRunner): Promise { + + await queryRunner.query(`ALTER TABLE \`employeePosMaster\` ADD \`isCondition\` tinyint NOT NULL COMMENT 'ตำแหน่งติดเงื่อนไข' DEFAULT 0`); + await queryRunner.query(`ALTER TABLE \`employeePosMaster\` ADD \`conditionReason\` text NULL COMMENT 'หมายเหตุตำแหน่งติดเงื่อนไข'`); + + } + + public async down(queryRunner: QueryRunner): Promise { + + await queryRunner.query(`ALTER TABLE \`employeePosMaster\` DROP COLUMN \`conditionReason\``); + await queryRunner.query(`ALTER TABLE \`employeePosMaster\` DROP COLUMN \`isCondition\``); + + } + +} diff --git a/src/migration/1771910056470-update_EmployeePosLevel_change_datatype_field_posLevelName.ts b/src/migration/1771910056470-update_EmployeePosLevel_change_datatype_field_posLevelName.ts new file mode 100644 index 00000000..740454f5 --- /dev/null +++ b/src/migration/1771910056470-update_EmployeePosLevel_change_datatype_field_posLevelName.ts @@ -0,0 +1,26 @@ +import { MigrationInterface, QueryRunner } from "typeorm"; + +export class UpdateEmployeePosLevelChangeDatatypeFieldPosLevelName1771910056470 implements MigrationInterface { + name = 'UpdateEmployeePosLevelChangeDatatypeFieldPosLevelName1771910056470' + + public async up(queryRunner: QueryRunner): Promise { + // await queryRunner.query(`ALTER TABLE \`employeePosLevel\` DROP COLUMN \`posLevelName\``); + // await queryRunner.query(`ALTER TABLE \`employeePosLevel\` ADD \`posLevelName\` varchar(255) NULL COMMENT 'ชื่อระดับชั้นงาน'`); + await queryRunner.query(` + ALTER TABLE \`employeePosLevel\` + MODIFY \`posLevelName\` varchar(255) NULL + COMMENT 'ชื่อระดับชั้นงาน' + `); + } + + public async down(queryRunner: QueryRunner): Promise { + // await queryRunner.query(`ALTER TABLE \`employeePosLevel\` DROP COLUMN \`posLevelName\``); + // await queryRunner.query(`ALTER TABLE \`employeePosLevel\` ADD \`posLevelName\` int NOT NULL COMMENT 'ชื่อระดับชั้นงาน'`); + await queryRunner.query(` + ALTER TABLE \`employeePosLevel\` + MODIFY \`posLevelName\` int NOT NULL + COMMENT 'ชื่อระดับชั้นงาน' + `); + } + +} diff --git a/src/migration/1774245696453-create_table_profileAbsentLate_.ts b/src/migration/1774245696453-create_table_profileAbsentLate_.ts new file mode 100644 index 00000000..11835568 --- /dev/null +++ b/src/migration/1774245696453-create_table_profileAbsentLate_.ts @@ -0,0 +1,34 @@ +import { MigrationInterface, QueryRunner } from "typeorm"; + +export class CreateTableProfileAbsentLate_1774245696453 implements MigrationInterface { + name = 'CreateTableProfileAbsentLate_1774245696453' + + public async up(queryRunner: QueryRunner): Promise { + + await queryRunner.query(`CREATE TABLE \`profileAbsentLate\` (\`id\` varchar(36) NOT NULL, \`createdAt\` datetime(6) NOT NULL COMMENT 'สร้างข้อมูลเมื่อ' DEFAULT CURRENT_TIMESTAMP(6), \`createdUserId\` varchar(40) NOT NULL COMMENT 'User Id ที่สร้างข้อมูล' DEFAULT '00000000-0000-0000-0000-000000000000', \`lastUpdatedAt\` datetime(6) NOT NULL COMMENT 'แก้ไขข้อมูลล่าสุดเมื่อ' DEFAULT CURRENT_TIMESTAMP(6), \`lastUpdateUserId\` varchar(40) NOT NULL COMMENT 'User Id ที่แก้ไขข้อมูล' DEFAULT '00000000-0000-0000-0000-000000000000', \`createdFullName\` varchar(200) NOT NULL COMMENT 'ชื่อ User ที่สร้างข้อมูล' DEFAULT 'System Administrator', \`lastUpdateFullName\` varchar(200) NOT NULL COMMENT 'ชื่อ User ที่แก้ไขข้อมูลล่าสุด' DEFAULT 'System Administrator', \`profileId\` varchar(40) NULL COMMENT 'คีย์นอก(FK)ของตาราง Profile', \`status\` enum ('LATE', 'ABSENT') NOT NULL COMMENT 'สถานะ มาสาย/ขาดราชการ', \`stampDate\` date NOT NULL COMMENT 'วันที่ลงเวลา', \`stampType\` enum ('FULL_DAY', 'MORNING', 'AFTERNOON') NOT NULL COMMENT 'เต็มวัน/ครึ่งเช้า/ครึ่งบ่าย' DEFAULT 'FULL_DAY', \`stampAmount\` decimal(2,1) NOT NULL COMMENT 'จำนวน (1.0/0.5)' DEFAULT '1.0', \`remark\` varchar(250) NULL COMMENT 'หมายเหตุ', \`isDeleted\` tinyint NOT NULL COMMENT 'สถานะลบข้อมูล' DEFAULT 0, PRIMARY KEY (\`id\`)) ENGINE=InnoDB`); + await queryRunner.query(`CREATE TABLE \`profileEmployeeAbsentLate\` (\`id\` varchar(36) NOT NULL, \`createdAt\` datetime(6) NOT NULL COMMENT 'สร้างข้อมูลเมื่อ' DEFAULT CURRENT_TIMESTAMP(6), \`createdUserId\` varchar(40) NOT NULL COMMENT 'User Id ที่สร้างข้อมูล' DEFAULT '00000000-0000-0000-0000-000000000000', \`lastUpdatedAt\` datetime(6) NOT NULL COMMENT 'แก้ไขข้อมูลล่าสุดเมื่อ' DEFAULT CURRENT_TIMESTAMP(6), \`lastUpdateUserId\` varchar(40) NOT NULL COMMENT 'User Id ที่แก้ไขข้อมูล' DEFAULT '00000000-0000-0000-0000-000000000000', \`createdFullName\` varchar(200) NOT NULL COMMENT 'ชื่อ User ที่สร้างข้อมูล' DEFAULT 'System Administrator', \`lastUpdateFullName\` varchar(200) NOT NULL COMMENT 'ชื่อ User ที่แก้ไขข้อมูลล่าสุด' DEFAULT 'System Administrator', \`profileEmployeeId\` varchar(40) NULL COMMENT 'คีย์นอก(FK)ของตาราง ProfileEmployee', \`status\` enum ('LATE', 'ABSENT') NOT NULL COMMENT 'สถานะ มาสาย/ขาดราชการ', \`stampDate\` date NOT NULL COMMENT 'วันที่ลงเวลา', \`stampType\` enum ('FULL_DAY', 'MORNING', 'AFTERNOON') NOT NULL COMMENT 'เต็มวัน/ครึ่งเช้า/ครึ่งบ่าย' DEFAULT 'FULL_DAY', \`stampAmount\` decimal(2,1) NOT NULL COMMENT 'จำนวน (1.0/0.5)' DEFAULT '1.0', \`remark\` varchar(250) NULL COMMENT 'หมายเหตุ', \`isDeleted\` tinyint NOT NULL COMMENT 'สถานะลบข้อมูล' DEFAULT 0, PRIMARY KEY (\`id\`)) ENGINE=InnoDB`); + + await queryRunner.query(`ALTER TABLE \`profileLeave\` ADD \`leaveSubTypeName\` varchar(255) NULL COMMENT 'ประเภทย่อยการลา (เช่น ศึกษาต่อ, ฝึกอบรม, ปฎอบัติการวิจัย, ดูงาน)'`); + await queryRunner.query(`ALTER TABLE \`profileLeave\` ADD \`coupleDayLevelCountry\` varchar(255) NULL COMMENT 'ประเทศที่ไป'`); + await queryRunner.query(`ALTER TABLE \`profileLeaveHistory\` ADD \`leaveSubTypeName\` varchar(255) NULL COMMENT 'ประเภทย่อยการลา (เช่น ศึกษาต่อ, ฝึกอบรม, ปฎอบัติการวิจัย, ดูงาน)'`); + await queryRunner.query(`ALTER TABLE \`profileLeaveHistory\` ADD \`coupleDayLevelCountry\` varchar(255) NULL COMMENT 'ประเทศที่ไป'`); + + await queryRunner.query(`ALTER TABLE \`profileAbsentLate\` ADD CONSTRAINT \`FK_28f5579c548da2fd76b5295d8d5\` FOREIGN KEY (\`profileId\`) REFERENCES \`profile\`(\`id\`) ON DELETE NO ACTION ON UPDATE NO ACTION`); + await queryRunner.query(`ALTER TABLE \`profileEmployeeAbsentLate\` ADD CONSTRAINT \`FK_f22d4ae4155cafd14e9c6d888e6\` FOREIGN KEY (\`profileEmployeeId\`) REFERENCES \`profileEmployee\`(\`id\`) ON DELETE NO ACTION ON UPDATE NO ACTION`); + } + + public async down(queryRunner: QueryRunner): Promise { + + await queryRunner.query(`ALTER TABLE \`profileEmployeeAbsentLate\` DROP FOREIGN KEY \`FK_f22d4ae4155cafd14e9c6d888e6\``); + await queryRunner.query(`ALTER TABLE \`profileAbsentLate\` DROP FOREIGN KEY \`FK_28f5579c548da2fd76b5295d8d5\``); + + await queryRunner.query(`ALTER TABLE \`profileLeaveHistory\` DROP COLUMN \`coupleDayLevelCountry\``); + await queryRunner.query(`ALTER TABLE \`profileLeaveHistory\` DROP COLUMN \`leaveSubTypeName\``); + await queryRunner.query(`ALTER TABLE \`profileLeave\` DROP COLUMN \`coupleDayLevelCountry\``); + await queryRunner.query(`ALTER TABLE \`profileLeave\` DROP COLUMN \`leaveSubTypeName\``); + + await queryRunner.query(`DROP TABLE \`profileEmployeeAbsentLate\``); + await queryRunner.query(`DROP TABLE \`profileAbsentLate\``); + } + +} diff --git a/src/migration/1774253766170-update_table_profileAbsentLate.ts b/src/migration/1774253766170-update_table_profileAbsentLate.ts new file mode 100644 index 00000000..be05ca8c --- /dev/null +++ b/src/migration/1774253766170-update_table_profileAbsentLate.ts @@ -0,0 +1,21 @@ +import { MigrationInterface, QueryRunner } from "typeorm"; + +export class UpdateTableProfileAbsentLate1774253766170 implements MigrationInterface { + name = 'UpdateTableProfileAbsentLate1774253766170' + + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query(`ALTER TABLE \`profileAbsentLate\` DROP COLUMN \`stampDate\``); + await queryRunner.query(`ALTER TABLE \`profileAbsentLate\` ADD \`stampDate\` datetime NOT NULL COMMENT 'วันที่และเวลาที่ลงเวลา'`); + await queryRunner.query(`ALTER TABLE \`profileEmployeeAbsentLate\` DROP COLUMN \`stampDate\``); + await queryRunner.query(`ALTER TABLE \`profileEmployeeAbsentLate\` ADD \`stampDate\` datetime NOT NULL COMMENT 'วันที่และเวลาที่ลงเวลา'`); + } + + public async down(queryRunner: QueryRunner): Promise { + + await queryRunner.query(`ALTER TABLE \`profileEmployeeAbsentLate\` DROP COLUMN \`stampDate\``); + await queryRunner.query(`ALTER TABLE \`profileEmployeeAbsentLate\` ADD \`stampDate\` date NOT NULL COMMENT 'วันที่ลงเวลา'`); + await queryRunner.query(`ALTER TABLE \`profileAbsentLate\` DROP COLUMN \`stampDate\``); + await queryRunner.query(`ALTER TABLE \`profileAbsentLate\` ADD \`stampDate\` date NOT NULL COMMENT 'วันที่ลงเวลา'`); + } + +} diff --git a/src/migration/1774408245407-add_table_absentLateHistory.ts b/src/migration/1774408245407-add_table_absentLateHistory.ts new file mode 100644 index 00000000..ff70745b --- /dev/null +++ b/src/migration/1774408245407-add_table_absentLateHistory.ts @@ -0,0 +1,19 @@ +import { MigrationInterface, QueryRunner } from "typeorm"; + +export class AddTableAbsentLateHistory1774408245407 implements MigrationInterface { + name = 'AddTableAbsentLateHistory1774408245407' + + public async up(queryRunner: QueryRunner): Promise { + + await queryRunner.query(`CREATE TABLE \`profileEmployeeAbsentLateHistory\` (\`id\` varchar(36) NOT NULL, \`createdAt\` datetime(6) NOT NULL COMMENT 'สร้างข้อมูลเมื่อ' DEFAULT CURRENT_TIMESTAMP(6), \`createdUserId\` varchar(40) NOT NULL COMMENT 'User Id ที่สร้างข้อมูล' DEFAULT '00000000-0000-0000-0000-000000000000', \`lastUpdatedAt\` datetime(6) NOT NULL COMMENT 'แก้ไขข้อมูลล่าสุดเมื่อ' DEFAULT CURRENT_TIMESTAMP(6), \`lastUpdateUserId\` varchar(40) NOT NULL COMMENT 'User Id ที่แก้ไขข้อมูล' DEFAULT '00000000-0000-0000-0000-000000000000', \`createdFullName\` varchar(200) NOT NULL COMMENT 'ชื่อ User ที่สร้างข้อมูล' DEFAULT 'System Administrator', \`lastUpdateFullName\` varchar(200) NOT NULL COMMENT 'ชื่อ User ที่แก้ไขข้อมูลล่าสุด' DEFAULT 'System Administrator', \`profileEmployeeId\` varchar(40) NULL COMMENT 'คีย์นอก(FK)ของตาราง ProfileEmployee', \`status\` enum ('LATE', 'ABSENT') NOT NULL COMMENT 'สถานะ มาสาย/ขาดราชการ', \`stampDate\` datetime NOT NULL COMMENT 'วันที่และเวลาที่ลงเวลา', \`stampType\` enum ('FULL_DAY', 'MORNING', 'AFTERNOON') NOT NULL COMMENT 'เต็มวัน/ครึ่งเช้า/ครึ่งบ่าย' DEFAULT 'FULL_DAY', \`stampAmount\` decimal(2,1) NOT NULL COMMENT 'จำนวน (1.0/0.5)' DEFAULT '1.0', \`remark\` varchar(250) NULL COMMENT 'หมายเหตุ', \`isDeleted\` tinyint NOT NULL COMMENT 'สถานะลบข้อมูล' DEFAULT 0, \`profileEmployeeAbsentLateId\` varchar(40) NULL COMMENT 'คีย์นอก(FK)ของตาราง ProfileEmployeeAbsentLate', PRIMARY KEY (\`id\`)) ENGINE=InnoDB`); + await queryRunner.query(`CREATE TABLE \`profileAbsentLateHistory\` (\`id\` varchar(36) NOT NULL, \`createdAt\` datetime(6) NOT NULL COMMENT 'สร้างข้อมูลเมื่อ' DEFAULT CURRENT_TIMESTAMP(6), \`createdUserId\` varchar(40) NOT NULL COMMENT 'User Id ที่สร้างข้อมูล' DEFAULT '00000000-0000-0000-0000-000000000000', \`lastUpdatedAt\` datetime(6) NOT NULL COMMENT 'แก้ไขข้อมูลล่าสุดเมื่อ' DEFAULT CURRENT_TIMESTAMP(6), \`lastUpdateUserId\` varchar(40) NOT NULL COMMENT 'User Id ที่แก้ไขข้อมูล' DEFAULT '00000000-0000-0000-0000-000000000000', \`createdFullName\` varchar(200) NOT NULL COMMENT 'ชื่อ User ที่สร้างข้อมูล' DEFAULT 'System Administrator', \`lastUpdateFullName\` varchar(200) NOT NULL COMMENT 'ชื่อ User ที่แก้ไขข้อมูลล่าสุด' DEFAULT 'System Administrator', \`profileId\` varchar(40) NULL COMMENT 'คีย์นอก(FK)ของตาราง Profile', \`status\` enum ('LATE', 'ABSENT') NOT NULL COMMENT 'สถานะ มาสาย/ขาดราชการ', \`stampDate\` datetime NOT NULL COMMENT 'วันที่และเวลาที่ลงเวลา', \`stampType\` enum ('FULL_DAY', 'MORNING', 'AFTERNOON') NOT NULL COMMENT 'เต็มวัน/ครึ่งเช้า/ครึ่งบ่าย' DEFAULT 'FULL_DAY', \`stampAmount\` decimal(2,1) NOT NULL COMMENT 'จำนวน (1.0/0.5)' DEFAULT '1.0', \`remark\` varchar(250) NULL COMMENT 'หมายเหตุ', \`isDeleted\` tinyint NOT NULL COMMENT 'สถานะลบข้อมูล' DEFAULT 0, \`profileAbsentLateId\` varchar(40) NULL COMMENT 'คีย์นอก(FK)ของตาราง ProfileAbsentLate', PRIMARY KEY (\`id\`)) ENGINE=InnoDB`); + await queryRunner.query(`ALTER TABLE \`profileEmployeeAbsentLateHistory\` ADD CONSTRAINT \`FK_8b06ca79d6f75c7d6577c86f3d4\` FOREIGN KEY (\`profileEmployeeId\`) REFERENCES \`profileEmployee\`(\`id\`) ON DELETE NO ACTION ON UPDATE NO ACTION`); + await queryRunner.query(`ALTER TABLE \`profileAbsentLateHistory\` ADD CONSTRAINT \`FK_0fa6a843d0e6d901a4f2f56c541\` FOREIGN KEY (\`profileId\`) REFERENCES \`profile\`(\`id\`) ON DELETE NO ACTION ON UPDATE NO ACTION`); + } + + public async down(queryRunner: QueryRunner): Promise { + await queryRunner.query(`DROP TABLE \`profileAbsentLateHistory\``); + await queryRunner.query(`DROP TABLE \`profileEmployeeAbsentLateHistory\``); + } + +} diff --git a/src/migration/1775112029663-update_profile_and_profileemployee_add_field_isdelete.ts b/src/migration/1775112029663-update_profile_and_profileemployee_add_field_isdelete.ts new file mode 100644 index 00000000..706f3d59 --- /dev/null +++ b/src/migration/1775112029663-update_profile_and_profileemployee_add_field_isdelete.ts @@ -0,0 +1,23 @@ +import { MigrationInterface, QueryRunner } from "typeorm"; + +export class UpdateProfileAndProfileemployeeAddFieldIsdelete1775112029663 implements MigrationInterface { + name = 'UpdateProfileAndProfileemployeeAddFieldIsdelete1775112029663' + + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query(`ALTER TABLE \`profileEmployee\` ADD \`isDelete\` tinyint NOT NULL COMMENT 'สถานะการถูกลบผู้ใช้งานใน keycloak' DEFAULT 0`); + await queryRunner.query(`ALTER TABLE \`profileEmployeeHistory\` ADD \`isDelete\` tinyint NOT NULL COMMENT 'สถานะการถูกลบผู้ใช้งานใน keycloak' DEFAULT 0`); + await queryRunner.query(`ALTER TABLE \`profile\` ADD \`isDelete\` tinyint NOT NULL COMMENT 'สถานะการถูกลบผู้ใช้งานใน keycloak' DEFAULT 0`); + await queryRunner.query(`ALTER TABLE \`profileHistory\` ADD \`isDelete\` tinyint NOT NULL COMMENT 'สถานะการถูกลบผู้ใช้งานใน keycloak' DEFAULT 0`); + + // Update ข้อมูลเดิม: ถ้า keycloak null → isDelete = true (1), ถ้ามีค่า → isDelete = false (0) + await queryRunner.query(`UPDATE \`profileEmployee\` SET \`isDelete\` = CASE WHEN \`keycloak\` IS NULL THEN 1 ELSE 0 END`); + await queryRunner.query(`UPDATE \`profile\` SET \`isDelete\` = CASE WHEN \`keycloak\` IS NULL THEN 1 ELSE 0 END`); + } + + public async down(queryRunner: QueryRunner): Promise { + await queryRunner.query(`ALTER TABLE \`profileHistory\` DROP COLUMN \`isDelete\``); + await queryRunner.query(`ALTER TABLE \`profile\` DROP COLUMN \`isDelete\``); + await queryRunner.query(`ALTER TABLE \`profileEmployeeHistory\` DROP COLUMN \`isDelete\``); + await queryRunner.query(`ALTER TABLE \`profileEmployee\` DROP COLUMN \`isDelete\``); + } +} diff --git a/src/services/CommandService.ts b/src/services/CommandService.ts new file mode 100644 index 00000000..b002462c --- /dev/null +++ b/src/services/CommandService.ts @@ -0,0 +1,146 @@ +import { AppDataSource } from "../database/data-source"; +import { CommandRecive } from "../entities/CommandRecive"; +import { Command } from "../entities/Command"; +import { OrgRoot } from "../entities/OrgRoot"; +import { Profile } from "../entities/Profile"; + +export interface PosNumCodeSitResult { + posNumCodeSit: string; + posNumCodeSitAbb: string; + commandTypeName: string; + commandNo: string; + commandYear: number; + commandExcecuteDate: Date; +} + +/** + * เรียงลำดับผู้ได้รับคำสั่งใหม่หลังจากลบรายการ และอัพเดทสถานะคำสั่งถ้าไม่มีผู้ได้รับคำสั่งเหลือ + * @param reciveId commandRecive.Id ของผู้ได้รับคำสั่ง + * @param code ประเภทคำสั่ง + * @returns Promise + */ +export async function reOrderCommandRecivesAndDelete( + reciveId: string +): Promise { + const commandReciveRepo = AppDataSource.getRepository(CommandRecive); + const commandRepo = AppDataSource.getRepository(Command); + + // ค้นหาข้อมูลผู้ได้รับคำสั่งตาม reciveId + const commandRecive = await commandReciveRepo.findOne({ + where: { id: reciveId } + }); + + if (commandRecive == null) + return; + + const commandId = commandRecive.commandId; + // ลบตาม refId + await commandReciveRepo.delete(commandRecive.id); + + const commandReciveList = await commandReciveRepo.find({ + where: { commandId: commandId }, + order: { order: "ASC" }, + }); + // ลำดับผู้ได้รับคำสั่งใหม่ + if (commandReciveList.length > 0) { + await Promise.all( + commandReciveList.map(async (p, i) => { + p.order = i + 1; + await commandReciveRepo.save(p); + }) + ); + } else { + // ถ้าไม่มีผู้ได้รับคำสั่งเหลือเลย ให้ยกเลิกคำสั่ง + await commandRepo.update({ id: commandId }, { status: "CANCEL" }); + } + +} + +/** + * ดึงข้อมูล posNumCodeSit และ posNumCodeSitAbb จาก commandId + * @param commandId ID ของคำสั่ง + * @param status สถานะของคำสั่ง (ไม่บังคับส่ง) + * @returns Promise ข้อมูลชื่อหน่วยงานและชื่อย่อ + */ +export async function getPosNumCodeSit( + commandId: string, + status?: string +): Promise { + const commandRepo = AppDataSource.getRepository(Command); + const orgRootRepo = AppDataSource.getRepository(OrgRoot); + const profileRepo = AppDataSource.getRepository(Profile); + + let posNumCodeSit:string = ""; + let posNumCodeSitAbb:string = ""; + let commandTypeName:string = ""; + let commandNo:string = ""; + let commandYear:number = 0; + let commandExcecuteDate:Date = new Date; + + + let _command: Command | null; + if (!status) { + _command = await commandRepo.findOne({ + where: { id: commandId }, + relations: { commandType: true } + }); + } + else { + _command = await commandRepo.findOne({ + where: { id: commandId, status: status }, + relations: { commandType: true } + }); + } + + if (_command) { + if (_command?.isBangkok?.toLocaleUpperCase() == "OFFICE") { + const orgRootDeputy = await orgRootRepo.findOne({ + where: { + isDeputy: true, + orgRevision: { + orgRevisionIsCurrent: true, + orgRevisionIsDraft: false, + }, + }, + relations: ["orgRevision"], + }); + posNumCodeSit = orgRootDeputy ? orgRootDeputy?.orgRootName : "สำนักปลัดกรุงเทพมหานคร"; + posNumCodeSitAbb = orgRootDeputy ? orgRootDeputy?.orgRootShortName : "สนป."; + } else if (_command?.isBangkok?.toLocaleUpperCase() == "BANGKOK") { + posNumCodeSit = "กรุงเทพมหานคร"; + posNumCodeSitAbb = "กทม."; + } else { + let _profileAdmin = await profileRepo.findOne({ + where: { + keycloak: _command?.createdUserId.toString(), + current_holders: { + orgRevision: { + orgRevisionIsCurrent: true, + orgRevisionIsDraft: false, + }, + }, + }, + relations: ["current_holders", "current_holders.orgRevision", "current_holders.orgRoot"], + }); + posNumCodeSit = + _profileAdmin?.current_holders.find((x) => x.orgRoot.orgRootName)?.orgRoot.orgRootName ?? + ""; + posNumCodeSitAbb = + _profileAdmin?.current_holders.find((x) => x.orgRoot.orgRootShortName)?.orgRoot + .orgRootShortName ?? ""; + } + commandTypeName = _command?.commandType?.name ?? ""; + commandNo = _command?.commandNo ?? ""; + commandYear = _command?.commandYear; + commandExcecuteDate = _command?.commandExcecuteDate; + } + + return { + posNumCodeSit, + posNumCodeSitAbb, + commandTypeName, + commandNo, + commandYear, + commandExcecuteDate, + }; +} diff --git a/src/services/KeycloakAttributeService.ts b/src/services/KeycloakAttributeService.ts new file mode 100644 index 00000000..2b3af9ab --- /dev/null +++ b/src/services/KeycloakAttributeService.ts @@ -0,0 +1,1304 @@ +import { AppDataSource } from "../database/data-source"; +import { Profile } from "../entities/Profile"; +import { ProfileEmployee } from "../entities/ProfileEmployee"; +// import { PosMaster } from "../entities/PosMaster"; +// import { EmployeePosMaster } from "../entities/EmployeePosMaster"; +// import { OrgRoot } from "../entities/OrgRoot"; +import { + createUserHaveProfile, + getUser, + getUserByUsername, + updateUserAttributes, + deleteUser, + getRoles, + addUserRoles, + getAllUsersPaginated, + withRetry, + RateLimiter, +} from "../keycloak"; +import { OrgRevision } from "../entities/OrgRevision"; +import { SyncProgressManager, SyncProgressState } from "../utils/sync-progress"; + +export interface UserProfileAttributes { + profileId: string | null; + orgRootDnaId: string | null; + orgChild1DnaId: string | null; + orgChild2DnaId: string | null; + orgChild3DnaId: string | null; + orgChild4DnaId: string | null; + empType: string | null; + prefix?: string | null; +} + +/** + * Keycloak Attribute Service + * Service for syncing profileId and orgRootDnaId to Keycloak user attributes + */ +export class KeycloakAttributeService { + private profileRepo = AppDataSource.getRepository(Profile); + private profileEmployeeRepo = AppDataSource.getRepository(ProfileEmployee); + // private posMasterRepo = AppDataSource.getRepository(PosMaster); + // private employeePosMasterRepo = AppDataSource.getRepository(EmployeePosMaster); + // private orgRootRepo = AppDataSource.getRepository(OrgRoot); + private orgRevisionRepo = AppDataSource.getRepository(OrgRevision); + + /** + * Get profile attributes (profileId and orgRootDnaId) from database + * Searches in Profile table first (ข้าราชการ), then ProfileEmployee (ลูกจ้าง) + * + * @param keycloakUserId - Keycloak user ID + * @returns UserProfileAttributes with profileId and orgRootDnaId + */ + async getUserProfileAttributes(keycloakUserId: string): Promise { + // First, try to find in Profile (ข้าราชการ) + const revisionCurrent = await this.orgRevisionRepo.findOne({ + where: { orgRevisionIsCurrent: true }, + }); + const revisionId = revisionCurrent ? revisionCurrent.id : null; + const profileResult = await this.profileRepo + .createQueryBuilder("p") + .leftJoinAndSelect("p.current_holders", "pm") + .leftJoinAndSelect("pm.orgRoot", "orgRoot") + .leftJoinAndSelect("pm.orgChild1", "orgChild1") + .leftJoinAndSelect("pm.orgChild2", "orgChild2") + .leftJoinAndSelect("pm.orgChild3", "orgChild3") + .leftJoinAndSelect("pm.orgChild4", "orgChild4") + .where("p.keycloak = :keycloakUserId", { keycloakUserId }) + .andWhere("orgRoot.orgRevisionId = :revisionId", { revisionId }) + .getOne(); + + if ( + profileResult && + profileResult.current_holders && + profileResult.current_holders.length > 0 + ) { + const currentPos = profileResult.current_holders[0]; + const orgRootDnaId = currentPos.orgRoot?.ancestorDNA || ""; + const orgChild1DnaId = currentPos.orgChild1?.ancestorDNA || ""; + const orgChild2DnaId = currentPos.orgChild2?.ancestorDNA || ""; + const orgChild3DnaId = currentPos.orgChild3?.ancestorDNA || ""; + const orgChild4DnaId = currentPos.orgChild4?.ancestorDNA || ""; + return { + profileId: profileResult.id, + orgRootDnaId, + orgChild1DnaId, + orgChild2DnaId, + orgChild3DnaId, + orgChild4DnaId, + empType: "OFFICER", + prefix: profileResult.prefix, + }; + } + + // If not found in Profile, try ProfileEmployee (ลูกจ้าง) + // First, get the profileEmployee to check employeeClass + const profileEmployeeBasic = await this.profileEmployeeRepo + .createQueryBuilder("pe") + .where("pe.keycloak = :keycloakUserId", { keycloakUserId }) + .getOne(); + + if (!profileEmployeeBasic) { + // Return null values if no profile found + return { + profileId: null, + orgRootDnaId: null, + orgChild1DnaId: null, + orgChild2DnaId: null, + orgChild3DnaId: null, + orgChild4DnaId: null, + empType: null, + prefix: null, + }; + } + + // Check employeeClass to determine which table to query + const isPermEmployee = profileEmployeeBasic.employeeClass === "PERM"; + + if (isPermEmployee) { + // ลูกจ้างประจำ (PERM) - ใช้ EmployeePosMaster + const profileEmployeeResult = await this.profileEmployeeRepo + .createQueryBuilder("pe") + .leftJoinAndSelect("pe.current_holders", "epm") + .leftJoinAndSelect("epm.orgRoot", "orgRoot") + .leftJoinAndSelect("epm.orgChild1", "orgChild1") + .leftJoinAndSelect("epm.orgChild2", "orgChild2") + .leftJoinAndSelect("epm.orgChild3", "orgChild3") + .leftJoinAndSelect("epm.orgChild4", "orgChild4") + .where("pe.keycloak = :keycloakUserId", { keycloakUserId }) + .andWhere("orgRoot.orgRevisionId = :revisionId", { revisionId }) + .getOne(); + + if ( + profileEmployeeResult && + profileEmployeeResult.current_holders && + profileEmployeeResult.current_holders.length > 0 + ) { + const currentPos = profileEmployeeResult.current_holders[0]; + const orgRootDnaId = currentPos.orgRoot?.ancestorDNA || ""; + const orgChild1DnaId = currentPos.orgChild1?.ancestorDNA || ""; + const orgChild2DnaId = currentPos.orgChild2?.ancestorDNA || ""; + const orgChild3DnaId = currentPos.orgChild3?.ancestorDNA || ""; + const orgChild4DnaId = currentPos.orgChild4?.ancestorDNA || ""; + return { + profileId: profileEmployeeResult.id, + orgRootDnaId, + orgChild1DnaId, + orgChild2DnaId, + orgChild3DnaId, + orgChild4DnaId, + empType: profileEmployeeResult.employeeClass, + prefix: profileEmployeeResult.prefix, + }; + } + } else { + // ลูกจ้างชั่วคราว (TEMP) - ใช้ EmployeeTempPosMaster + const profileEmployeeResult = await this.profileEmployeeRepo + .createQueryBuilder("pe") + .leftJoinAndSelect("pe.current_holderTemps", "etpm") + .leftJoinAndSelect("etpm.orgRoot", "orgRoot") + .leftJoinAndSelect("etpm.orgChild1", "orgChild1") + .leftJoinAndSelect("etpm.orgChild2", "orgChild2") + .leftJoinAndSelect("etpm.orgChild3", "orgChild3") + .leftJoinAndSelect("etpm.orgChild4", "orgChild4") + .where("pe.keycloak = :keycloakUserId", { keycloakUserId }) + .andWhere("orgRoot.orgRevisionId = :revisionId", { revisionId }) + .getOne(); + + if ( + profileEmployeeResult && + profileEmployeeResult.current_holderTemps && + profileEmployeeResult.current_holderTemps.length > 0 + ) { + const currentPos = profileEmployeeResult.current_holderTemps[0]; + const orgRootDnaId = currentPos.orgRoot?.ancestorDNA || ""; + const orgChild1DnaId = currentPos.orgChild1?.ancestorDNA || ""; + const orgChild2DnaId = currentPos.orgChild2?.ancestorDNA || ""; + const orgChild3DnaId = currentPos.orgChild3?.ancestorDNA || ""; + const orgChild4DnaId = currentPos.orgChild4?.ancestorDNA || ""; + return { + profileId: profileEmployeeResult.id, + orgRootDnaId, + orgChild1DnaId, + orgChild2DnaId, + orgChild3DnaId, + orgChild4DnaId, + empType: profileEmployeeResult.employeeClass, + prefix: profileEmployeeResult.prefix, + }; + } + } + + // Return null values if no profile found + return { + profileId: null, + orgRootDnaId: null, + orgChild1DnaId: null, + orgChild2DnaId: null, + orgChild3DnaId: null, + orgChild4DnaId: null, + empType: null, + prefix: null, + }; + } + + /** + * Get profile attributes by profile ID directly + * Used for syncing specific profiles + * + * @param profileId - Profile ID + * @param profileType - 'PROFILE' for ข้าราชการ or 'PROFILE_EMPLOYEE' for ลูกจ้าง + * @returns UserProfileAttributes with profileId and orgRootDnaId + */ + async getAttributesByProfileId( + profileId: string, + profileType: "PROFILE" | "PROFILE_EMPLOYEE", + ): Promise { + const revisionCurrent = await this.orgRevisionRepo.findOne({ + where: { orgRevisionIsCurrent: true }, + }); + const revisionId = revisionCurrent ? revisionCurrent.id : null; + if (profileType === "PROFILE") { + const profileResult = await this.profileRepo + .createQueryBuilder("p") + .leftJoinAndSelect("p.current_holders", "pm") + .leftJoinAndSelect("pm.orgRoot", "orgRoot") + .leftJoinAndSelect("pm.orgChild1", "orgChild1") + .leftJoinAndSelect("pm.orgChild2", "orgChild2") + .leftJoinAndSelect("pm.orgChild3", "orgChild3") + .leftJoinAndSelect("pm.orgChild4", "orgChild4") + .where("p.id = :profileId", { profileId }) + .andWhere("orgRoot.orgRevisionId = :revisionId", { revisionId }) + .getOne(); + + if ( + profileResult && + profileResult.current_holders && + profileResult.current_holders.length > 0 + ) { + const currentPos = profileResult.current_holders[0]; + const orgRootDnaId = currentPos.orgRoot?.ancestorDNA || ""; + const orgChild1DnaId = currentPos.orgChild1?.ancestorDNA || ""; + const orgChild2DnaId = currentPos.orgChild2?.ancestorDNA || ""; + const orgChild3DnaId = currentPos.orgChild3?.ancestorDNA || ""; + const orgChild4DnaId = currentPos.orgChild4?.ancestorDNA || ""; + return { + profileId: profileResult.id, + orgRootDnaId, + orgChild1DnaId, + orgChild2DnaId, + orgChild3DnaId, + orgChild4DnaId, + empType: "OFFICER", + prefix: profileResult.prefix, + }; + } + } else { + // First, get the profileEmployee to check employeeClass + const profileEmployeeBasic = await this.profileEmployeeRepo + .createQueryBuilder("pe") + .where("pe.id = :profileId", { profileId }) + .getOne(); + + if (!profileEmployeeBasic) { + return { + profileId: null, + orgRootDnaId: null, + orgChild1DnaId: null, + orgChild2DnaId: null, + orgChild3DnaId: null, + orgChild4DnaId: null, + empType: null, + prefix: null, + }; + } + + // Check employeeClass to determine which table to query + const isPermEmployee = profileEmployeeBasic.employeeClass === "PERM"; + + if (isPermEmployee) { + // ลูกจ้างประจำ (PERM) - ใช้ EmployeePosMaster + const profileEmployeeResult = await this.profileEmployeeRepo + .createQueryBuilder("pe") + .leftJoinAndSelect("pe.current_holders", "epm") + .leftJoinAndSelect("epm.orgRoot", "orgRoot") + .leftJoinAndSelect("epm.orgChild1", "orgChild1") + .leftJoinAndSelect("epm.orgChild2", "orgChild2") + .leftJoinAndSelect("epm.orgChild3", "orgChild3") + .leftJoinAndSelect("epm.orgChild4", "orgChild4") + .where("pe.id = :profileId", { profileId }) + .andWhere("orgRoot.orgRevisionId = :revisionId", { revisionId }) + .getOne(); + + if ( + profileEmployeeResult && + profileEmployeeResult.current_holders && + profileEmployeeResult.current_holders.length > 0 + ) { + const currentPos = profileEmployeeResult.current_holders[0]; + const orgRootDnaId = currentPos.orgRoot?.ancestorDNA || ""; + const orgChild1DnaId = currentPos.orgChild1?.ancestorDNA || ""; + const orgChild2DnaId = currentPos.orgChild2?.ancestorDNA || ""; + const orgChild3DnaId = currentPos.orgChild3?.ancestorDNA || ""; + const orgChild4DnaId = currentPos.orgChild4?.ancestorDNA || ""; + + return { + profileId: profileEmployeeResult.id, + orgRootDnaId, + orgChild1DnaId, + orgChild2DnaId, + orgChild3DnaId, + orgChild4DnaId, + empType: profileEmployeeResult.employeeClass, + prefix: profileEmployeeResult.prefix, + }; + } + } else { + // ลูกจ้างชั่วคราว (TEMP) - ใช้ EmployeeTempPosMaster + const profileEmployeeResult = await this.profileEmployeeRepo + .createQueryBuilder("pe") + .leftJoinAndSelect("pe.current_holderTemps", "etpm") + .leftJoinAndSelect("etpm.orgRoot", "orgRoot") + .leftJoinAndSelect("etpm.orgChild1", "orgChild1") + .leftJoinAndSelect("etpm.orgChild2", "orgChild2") + .leftJoinAndSelect("etpm.orgChild3", "orgChild3") + .leftJoinAndSelect("etpm.orgChild4", "orgChild4") + .where("pe.id = :profileId", { profileId }) + .andWhere("orgRoot.orgRevisionId = :revisionId", { revisionId }) + .getOne(); + + if ( + profileEmployeeResult && + profileEmployeeResult.current_holderTemps && + profileEmployeeResult.current_holderTemps.length > 0 + ) { + const currentPos = profileEmployeeResult.current_holderTemps[0]; + const orgRootDnaId = currentPos.orgRoot?.ancestorDNA || ""; + const orgChild1DnaId = currentPos.orgChild1?.ancestorDNA || ""; + const orgChild2DnaId = currentPos.orgChild2?.ancestorDNA || ""; + const orgChild3DnaId = currentPos.orgChild3?.ancestorDNA || ""; + const orgChild4DnaId = currentPos.orgChild4?.ancestorDNA || ""; + + return { + profileId: profileEmployeeResult.id, + orgRootDnaId, + orgChild1DnaId, + orgChild2DnaId, + orgChild3DnaId, + orgChild4DnaId, + empType: profileEmployeeResult.employeeClass, + prefix: profileEmployeeResult.prefix, + }; + } + } + } + + return { + profileId: null, + orgRootDnaId: null, + orgChild1DnaId: null, + orgChild2DnaId: null, + orgChild3DnaId: null, + orgChild4DnaId: null, + empType: null, + prefix: null, + }; + } + + /** + * Sync user attributes to Keycloak + * + * @param keycloakUserId - Keycloak user ID + * @returns true if sync successful, false otherwise + */ + async syncUserAttributes(keycloakUserId: string): Promise { + try { + const attributes = await this.getUserProfileAttributes(keycloakUserId); + + if (!attributes.profileId) { + console.log(`No profile found for Keycloak user ${keycloakUserId}`); + return false; + } + + // Prepare attributes for Keycloak (must be arrays) + const keycloakAttributes: Record = { + profileId: [attributes.profileId], + orgRootDnaId: [attributes.orgRootDnaId || ""], + orgChild1DnaId: [attributes.orgChild1DnaId || ""], + orgChild2DnaId: [attributes.orgChild2DnaId || ""], + orgChild3DnaId: [attributes.orgChild3DnaId || ""], + orgChild4DnaId: [attributes.orgChild4DnaId || ""], + empType: [attributes.empType || ""], + prefix: [attributes.prefix || ""], + }; + + const success = await updateUserAttributes(keycloakUserId, keycloakAttributes); + + if (success) { + console.log(`Synced attributes for Keycloak user ${keycloakUserId}:`, attributes); + } + + return success; + } catch (error) { + console.error(`Error syncing attributes for Keycloak user ${keycloakUserId}:`, error); + return false; + } + } + + /** + * Sync attributes when organization changes + * This is called when a user moves to a different organization + * + * @param profileId - Profile ID + * @param profileType - 'PROFILE' for ข้าราชการ or 'PROFILE_EMPLOYEE' for ลูกจ้าง + * @returns true if sync successful, false otherwise + */ + async syncOnOrganizationChange( + profileId: string, + profileType: "PROFILE" | "PROFILE_EMPLOYEE", + ): Promise { + try { + // Get the keycloak userId from the profile + let keycloakUserId: string | null = null; + + if (profileType === "PROFILE") { + const profile = await this.profileRepo.findOne({ where: { id: profileId } }); + keycloakUserId = profile?.keycloak || ""; + } else { + const profileEmployee = await this.profileEmployeeRepo.findOne({ + where: { id: profileId }, + }); + keycloakUserId = profileEmployee?.keycloak || ""; + } + + if (!keycloakUserId) { + console.log(`No Keycloak user ID found for profile ${profileId}`); + return false; + } + + return await this.syncUserAttributes(keycloakUserId); + } catch (error) { + console.error(`Error syncing organization change for profile ${profileId}:`, error); + return false; + } + } + + /** + * Clear org DNA attributes in Keycloak for given profiles + * Sets all org DNA fields to empty strings + * + * @param profileIds - Array of profile IDs to clear + * @param profileType - 'PROFILE' for officers or 'PROFILE_EMPLOYEE' for employees + * @returns Object with success/failed counts and details + */ + async clearOrgDnaAttributes( + profileIds: string[], + profileType: "PROFILE" | "PROFILE_EMPLOYEE", + ): Promise<{ + total: number; + success: number; + failed: number; + details: Array<{ profileId: string; status: "success" | "failed"; error?: string }>; + }> { + const result = { + total: profileIds.length, + success: 0, + failed: 0, + details: [] as Array<{ profileId: string; status: "success" | "failed"; error?: string }>, + }; + + for (const profileId of profileIds) { + try { + // Get the keycloak userId from the profile + let keycloakUserId: string | null = null; + + if (profileType === "PROFILE") { + const profile = await this.profileRepo.findOne({ where: { id: profileId } }); + keycloakUserId = profile?.keycloak || ""; + } else { + const profileEmployee = await this.profileEmployeeRepo.findOne({ + where: { id: profileId }, + }); + keycloakUserId = profileEmployee?.keycloak || ""; + } + + if (!keycloakUserId) { + result.failed++; + result.details.push({ + profileId, + status: "failed", + error: "No Keycloak user ID found", + }); + continue; + } + + // Clear org DNA attributes by setting them to empty strings + const clearedAttributes: Record = { + orgRootDnaId: [""], + orgChild1DnaId: [""], + orgChild2DnaId: [""], + orgChild3DnaId: [""], + orgChild4DnaId: [""], + }; + + const success = await updateUserAttributes(keycloakUserId, clearedAttributes); + + if (success) { + result.success++; + result.details.push({ + profileId, + status: "success", + }); + console.log(`Cleared org DNA attributes for profile ${profileId} (${profileType})`); + } else { + result.failed++; + result.details.push({ + profileId, + status: "failed", + error: "Failed to update Keycloak attributes", + }); + } + } catch (error: any) { + result.failed++; + result.details.push({ + profileId, + status: "failed", + error: error.message || "Unknown error", + }); + console.error(`Error clearing org DNA attributes for profile ${profileId}:`, error); + } + } + + return result; + } + + /** + * Batch sync multiple users with unlimited count and parallel processing + * Useful for initial sync or periodic updates + * + * Features: + * - Resume from checkpoint after failures + * - Automatic retry with exponential backoff + * - Rate limiting to avoid overwhelming Keycloak + * - Progress tracking and persistence + * + * @param options - Optional configuration + * @returns Object with success count and details + */ + async batchSyncUsers(options?: { + limit?: number; + concurrency?: number; + resume?: boolean; // Resume from last checkpoint + maxRetries?: number; // Retry attempts for failed operations + rateLimit?: number; // Requests per second + clearProgress?: boolean; // Start fresh, ignore existing progress + }): Promise<{ total: number; success: number; failed: number; details: any[]; resumed?: boolean }> { + const limit = options?.limit; + const concurrency = options?.concurrency ?? 5; + const resume = options?.resume ?? false; + const maxRetries = options?.maxRetries ?? 3; + const rateLimit = options?.rateLimit ?? 10; + const clearProgress = options?.clearProgress ?? false; + + const result = { + total: 0, + success: 0, + failed: 0, + details: [] as any[], + resumed: false, + }; + + let progressState: SyncProgressState | null = null; + let rateLimiter: RateLimiter | null = null; + + try { + // Handle progress file based on options + if (clearProgress) { + SyncProgressManager.clear(); + console.log("[batchSyncUsers] Cleared existing progress, starting fresh"); + } + + // Load existing progress if resume is requested + if (resume && !clearProgress) { + progressState = SyncProgressManager.load(); + if (progressState) { + result.resumed = true; + console.log( + `[batchSyncUsers] Resuming from checkpoint: ${progressState.lastSyncedIndex}/${progressState.totalProfiles}`, + ); + SyncProgressManager.logProgress(progressState); + } else { + console.log("[batchSyncUsers] No existing progress found, starting fresh"); + } + } + + // Build query for profiles with keycloak IDs (ข้าราชการ) + const profileQuery = this.profileRepo + .createQueryBuilder("p") + .where("p.keycloak IS NOT NULL") + .andWhere("p.keycloak != :empty", { empty: "" }); + + // Build query for profileEmployees with keycloak IDs (ลูกจ้าง) + const profileEmployeeQuery = this.profileEmployeeRepo + .createQueryBuilder("pe") + .where("pe.keycloak IS NOT NULL") + .andWhere("pe.keycloak != :empty", { empty: "" }); + + // Apply limit if specified (for testing purposes) + if (limit !== undefined) { + profileQuery.take(limit); + profileEmployeeQuery.take(limit); + } + + // Get profiles from both tables + const [profiles, profileEmployees] = await Promise.all([ + profileQuery.getMany(), + profileEmployeeQuery.getMany(), + ]); + + const allProfiles = [ + ...profiles.map((p) => ({ profile: p, type: "PROFILE" as const })), + ...profileEmployees.map((p) => ({ profile: p, type: "PROFILE_EMPLOYEE" as const })), + ]; + + result.total = allProfiles.length; + + // Initialize or resume progress state + if (!progressState) { + const profileIds = allProfiles.map((p) => p.profile.id); + progressState = SyncProgressManager.initialize(profileIds); + SyncProgressManager.save(progressState); + console.log(`[batchSyncUsers] Starting sync of ${profileIds.length} profiles`); + } + + // Initialize rate limiter if rate limiting is enabled + if (rateLimit && rateLimit > 0) { + rateLimiter = new RateLimiter(rateLimit); + console.log(`[batchSyncUsers] Rate limiting enabled: ${rateLimit} requests/second`); + } + + // Determine starting index based on progress + let startIndex = progressState.lastSyncedIndex; + + // Process in parallel with concurrency limit + const processedResults = await this.processInParallelWithProgress( + allProfiles, + concurrency, + startIndex, + async ({ profile, type }, index) => { + // Apply rate limiting if enabled + if (rateLimiter) { + await rateLimiter.throttle(); + } + + const keycloakUserId = profile.keycloak; + + try { + // Wrap sync operation with retry logic + const success = await withRetry( + async () => this.syncOnOrganizationChange(profile.id, type), + maxRetries, + 1000, // Base delay: 1 second + ); + + if (success) { + result.success++; + return { + profileId: profile.id, + keycloakUserId, + status: "success", + }; + } else { + result.failed++; + // Add to failed profiles in progress state + SyncProgressManager.addFailedProfile( + progressState!, + index, + profile.id, + "Sync returned false", + ); + return { + profileId: profile.id, + keycloakUserId, + status: "failed", + error: "Sync returned false", + }; + } + } catch (error: any) { + result.failed++; + // Add to failed profiles in progress state + SyncProgressManager.addFailedProfile( + progressState!, + index, + profile.id, + error.message || String(error), + ); + return { + profileId: profile.id, + keycloakUserId, + status: "error", + error: error.message || String(error), + }; + } + }, + progressState, + (updatedState) => { + // Save progress after each batch + SyncProgressManager.save(updatedState); + // Log progress every 50 items + if (updatedState.lastSyncedIndex % 50 === 0 || updatedState.lastSyncedIndex === updatedState.totalProfiles) { + SyncProgressManager.logProgress(updatedState); + } + }, + ); + + // Separate results from errors + for (const resultItem of processedResults) { + if ("error" in resultItem) { + result.failed++; + result.details.push({ + profileId: "unknown", + keycloakUserId: "unknown", + status: "error", + error: JSON.stringify(resultItem.error), + }); + } else { + result.details.push(resultItem); + } + } + + // Clear progress on successful completion + SyncProgressManager.clear(); + + const elapsed = SyncProgressManager.formatElapsedTime(progressState.startTime); + console.log( + `[batchSyncUsers] Completed: total=${result.total}, success=${result.success}, failed=${result.failed}, elapsed=${elapsed}`, + ); + + // Log failed profiles summary + if (progressState.failedProfiles.length > 0) { + console.log( + `[batchSyncUsers] Failed profiles (${progressState.failedProfiles.length}):`, + progressState.failedProfiles.map((f) => `${f.profileId}(${f.error})`).join(", "), + ); + } + } catch (error) { + console.error("[batchSyncUsers] Error in batch sync:", error); + // Save progress before throwing + if (progressState) { + SyncProgressManager.save(progressState); + console.log("[batchSyncUsers] Progress saved. Use resume=true to continue."); + } + throw error; + } + + return result; + } + + /** + * Process items in parallel with concurrency limit and progress tracking + * Extends processInParallel with progress state management + */ + private async processInParallelWithProgress( + items: T[], + concurrencyLimit: number, + startIndex: number, + processor: (item: T, index: number) => Promise, + progressState: SyncProgressState, + onProgress?: (state: SyncProgressState) => void, + ): Promise> { + const results: Array = []; + + // Start from the saved checkpoint index + for (let i = startIndex; i < items.length; i += concurrencyLimit) { + const batch = items.slice(i, i + concurrencyLimit); + + // Process batch in parallel with error handling + const batchResults = await Promise.all( + batch.map(async (item, batchIndex) => { + const actualIndex = i + batchIndex; + try { + return await processor(item, actualIndex); + } catch (error) { + return { error }; + } + }), + ); + + results.push(...batchResults); + + // Update progress state + progressState.lastSyncedIndex = Math.min(i + concurrencyLimit, items.length); + + // Call progress callback + if (onProgress) { + onProgress(progressState); + } + } + + return results; + } + + /** + * Get current Keycloak attributes for a user + * + * @param keycloakUserId - Keycloak user ID + * @returns Current attributes from Keycloak + */ + async getCurrentKeycloakAttributes( + keycloakUserId: string, + ): Promise { + try { + const user = await getUser(keycloakUserId); + + if (!user || !user.attributes) { + return null; + } + + return { + profileId: user.attributes.profileId?.[0] || "", + orgRootDnaId: user.attributes.orgRootDnaId?.[0] || "", + orgChild1DnaId: user.attributes.orgChild1DnaId?.[0] || "", + orgChild2DnaId: user.attributes.orgChild2DnaId?.[0] || "", + orgChild3DnaId: user.attributes.orgChild3DnaId?.[0] || "", + orgChild4DnaId: user.attributes.orgChild4DnaId?.[0] || "", + empType: user.attributes.empType?.[0] || "", + prefix: user.attributes.prefix?.[0] || "", + }; + } catch (error) { + console.error(`Error getting Keycloak attributes for user ${keycloakUserId}:`, error); + return null; + } + } + + /** + * Ensure Keycloak user exists for a profile + * Creates user if keycloak field is empty OR if stored keycloak ID doesn't exist in Keycloak + * + * @param profileId - Profile ID + * @param profileType - 'PROFILE' or 'PROFILE_EMPLOYEE' + * @returns Object with status and details + */ + async ensureKeycloakUser( + profileId: string, + profileType: "PROFILE" | "PROFILE_EMPLOYEE", + ): Promise<{ + success: boolean; + action: "created" | "verified" | "skipped" | "error"; + keycloakUserId?: string; + error?: string; + }> { + try { + // Get profile from database + let profile: Profile | ProfileEmployee | null = null; + + if (profileType === "PROFILE") { + profile = await this.profileRepo.findOne({ where: { id: profileId } }); + } else { + profile = await this.profileEmployeeRepo.findOne({ where: { id: profileId } }); + } + + if (!profile) { + return { + success: false, + action: "error", + error: `Profile ${profileId} not found in database`, + }; + } + + // Check if citizenId exists + if (!profile.citizenId) { + return { + success: false, + action: "skipped", + error: "No citizenId found", + }; + } + + // Case 1: keycloak field is empty -> create new user + if (!profile.keycloak || profile.keycloak.trim() === "") { + const result = await this.createKeycloakUserFromProfile(profile, profileType); + return result; + } + + // Case 2: keycloak field is not empty -> verify user exists in Keycloak + const existingUser = await getUser(profile.keycloak); + + if (!existingUser) { + // User doesn't exist in Keycloak, create new one + console.log( + `Keycloak user ${profile.keycloak} not found in Keycloak, creating new user for profile ${profileId}`, + ); + const result = await this.createKeycloakUserFromProfile(profile, profileType); + return result; + } + + // User exists in Keycloak, verified + return { + success: true, + action: "verified", + keycloakUserId: profile.keycloak, + }; + } catch (error: any) { + console.error(`Error ensuring Keycloak user for profile ${profileId}:`, error); + return { + success: false, + action: "error", + error: error.message || "Unknown error", + }; + } + } + + /** + * Create Keycloak user from profile data + * + * @param profile - Profile or ProfileEmployee entity + * @param profileType - 'PROFILE' or 'PROFILE_EMPLOYEE' + * @returns Object with status and details + */ + private async createKeycloakUserFromProfile( + profile: Profile | ProfileEmployee, + profileType: "PROFILE" | "PROFILE_EMPLOYEE", + ): Promise<{ + success: boolean; + action: "created" | "verified" | "skipped" | "error"; + keycloakUserId?: string; + error?: string; + }> { + try { + // Check if user already exists by username (citizenId) + const existingUserByUsername = await getUserByUsername(profile.citizenId); + if (Array.isArray(existingUserByUsername) && existingUserByUsername.length > 0) { + // User already exists with this username, update the keycloak field + const existingUserId = existingUserByUsername[0].id; + console.log( + `User with citizenId ${profile.citizenId} already exists in Keycloak with ID ${existingUserId}`, + ); + + // Update the keycloak field in database + if (profileType === "PROFILE") { + await this.profileRepo.update(profile.id, { keycloak: existingUserId }); + } else { + await this.profileEmployeeRepo.update(profile.id, { keycloak: existingUserId }); + } + + // Assign default USER role to existing user + const userRole = await getRoles("USER"); + if (userRole && typeof userRole === "object" && "id" in userRole && "name" in userRole) { + const roleAssigned = await addUserRoles(existingUserId, [ + { id: String(userRole.id), name: String(userRole.name) }, + ]); + if (roleAssigned) { + console.log(`Assigned USER role to existing user ${existingUserId}`); + } else { + console.warn(`Failed to assign USER role to existing user ${existingUserId}`); + } + } else { + console.warn(`USER role not found in Keycloak`); + } + + return { + success: true, + action: "verified", + keycloakUserId: existingUserId, + }; + } + + // Create new user in Keycloak + const createResult = await createUserHaveProfile( + profile.citizenId, + "P@ssw0rd", + profile.id, + profile.prefix, + { + firstName: profile.firstName || "", + lastName: profile.lastName || "", + email: profile.email || undefined, + enabled: true, + }, + ); + + if (!createResult || typeof createResult !== "string") { + return { + success: false, + action: "error", + error: "Failed to create user in Keycloak", + }; + } + + const keycloakUserId = createResult; + + // Update the keycloak field in database + if (profileType === "PROFILE") { + await this.profileRepo.update(profile.id, { keycloak: keycloakUserId }); + } else { + await this.profileEmployeeRepo.update(profile.id, { keycloak: keycloakUserId }); + } + + // Assign default USER role + const userRole = await getRoles("USER"); + if (userRole && typeof userRole === "object" && "id" in userRole && "name" in userRole) { + const roleAssigned = await addUserRoles(keycloakUserId, [ + { id: String(userRole.id), name: String(userRole.name) }, + ]); + if (roleAssigned) { + console.log(`Assigned USER role to user ${keycloakUserId}`); + } else { + console.warn(`Failed to assign USER role to user ${keycloakUserId}`); + } + } else { + console.warn(`USER role not found in Keycloak`); + } + + console.log( + `Created Keycloak user for profile ${profile.id} (citizenId: ${profile.citizenId}) with ID ${keycloakUserId}`, + ); + + return { + success: true, + action: "created", + keycloakUserId, + }; + } catch (error: any) { + console.error(`Error creating Keycloak user for profile ${profile.id}:`, error); + return { + success: false, + action: "error", + error: error.message || "Unknown error", + }; + } + } + + /** + * Process items in parallel with concurrency limit + */ + private async processInParallel( + items: T[], + concurrencyLimit: number, + processor: (item: T, index: number) => Promise, + ): Promise> { + const results: Array = []; + + // Process items in batches + for (let i = 0; i < items.length; i += concurrencyLimit) { + const batch = items.slice(i, i + concurrencyLimit); + + // Process batch in parallel with error handling + const batchResults = await Promise.all( + batch.map(async (item, batchIndex) => { + try { + return await processor(item, i + batchIndex); + } catch (error) { + return { error }; + } + }), + ); + + results.push(...batchResults); + + // Log progress after each batch + const completed = Math.min(i + concurrencyLimit, items.length); + console.log(`Progress: ${completed}/${items.length}`); + } + + return results; + } + + /** + * Batch ensure Keycloak users for all profiles + * Processes all rows in Profile and ProfileEmployee tables + * + * @returns Object with total, success, failed counts and details + */ + async batchEnsureKeycloakUsers(): Promise<{ + total: number; + created: number; + verified: number; + skipped: number; + failed: number; + details: Array<{ + profileId: string; + profileType: string; + action: string; + keycloakUserId?: string; + error?: string; + }>; + }> { + const result = { + total: 0, + created: 0, + verified: 0, + skipped: 0, + failed: 0, + details: [] as Array<{ + profileId: string; + profileType: string; + action: string; + keycloakUserId?: string; + error?: string; + }>, + }; + + try { + // Get all profiles from Profile table (ข้าราชการ) + const profiles = await this.profileRepo.find({ where: { isLeave: false } }); // Only active profiles + + // Get all profiles from ProfileEmployee table (ลูกจ้าง) + const profileEmployees = await this.profileEmployeeRepo.find({ where: { isLeave: false } }); // Only active profiles + + const allProfiles = [ + ...profiles.map((p) => ({ profile: p, type: "PROFILE" as const })), + ...profileEmployees.map((p) => ({ profile: p, type: "PROFILE_EMPLOYEE" as const })), + ]; + + result.total = allProfiles.length; + + // Process in parallel with concurrency limit + const CONCURRENCY_LIMIT = 5; // Adjust based on environment + + const processedResults = await this.processInParallel( + allProfiles, + CONCURRENCY_LIMIT, + async ({ profile, type }) => { + const ensureResult = await this.ensureKeycloakUser(profile.id, type); + + // Update counters + switch (ensureResult.action) { + case "created": + result.created++; + break; + case "verified": + result.verified++; + break; + case "skipped": + result.skipped++; + break; + case "error": + result.failed++; + break; + } + + return { + profileId: profile.id, + profileType: type, + action: ensureResult.action, + keycloakUserId: ensureResult.keycloakUserId, + error: ensureResult.error, + }; + }, + ); + + // Separate results from errors + for (const resultItem of processedResults) { + if ("error" in resultItem) { + result.failed++; + result.details.push({ + profileId: "unknown", + profileType: "unknown", + action: "error", + error: JSON.stringify(resultItem.error), + }); + } else { + result.details.push(resultItem); + } + } + + console.log( + `Batch ensure Keycloak users completed: total=${result.total}, created=${result.created}, verified=${result.verified}, skipped=${result.skipped}, failed=${result.failed}`, + ); + } catch (error) { + console.error("Error in batch ensure Keycloak users:", error); + } + + return result; + } + + /** + * Clear orphaned Keycloak users + * Deletes users in Keycloak that are not referenced in Profile or ProfileEmployee tables + * + * @param skipUsernames - Array of usernames to skip (e.g., ['super_admin']) + * @returns Object with counts and details + */ + async clearOrphanedKeycloakUsers(skipUsernames: string[] = []): Promise<{ + totalInKeycloak: number; + totalInDatabase: number; + orphanedCount: number; + deleted: number; + skipped: number; + failed: number; + details: Array<{ + keycloakUserId: string; + username: string; + action: "deleted" | "skipped" | "error"; + error?: string; + }>; + }> { + const result = { + totalInKeycloak: 0, + totalInDatabase: 0, + orphanedCount: 0, + deleted: 0, + skipped: 0, + failed: 0, + details: [] as Array<{ + keycloakUserId: string; + username: string; + action: "deleted" | "skipped" | "error"; + error?: string; + }>, + }; + + try { + // Get all keycloak IDs from database (Profile + ProfileEmployee) + const profiles = await this.profileRepo + .createQueryBuilder("p") + .where("p.keycloak IS NOT NULL") + .andWhere("p.keycloak != :empty", { empty: "" }) + .getMany(); + + const profileEmployees = await this.profileEmployeeRepo + .createQueryBuilder("pe") + .where("pe.keycloak IS NOT NULL") + .andWhere("pe.keycloak != :empty", { empty: "" }) + .getMany(); + + // Create a Set of all keycloak IDs in database for O(1) lookup + const databaseKeycloakIds = new Set(); + for (const p of profiles) { + if (p.keycloak) databaseKeycloakIds.add(p.keycloak); + } + for (const pe of profileEmployees) { + if (pe.keycloak) databaseKeycloakIds.add(pe.keycloak); + } + + result.totalInDatabase = databaseKeycloakIds.size; + + // Get all users from Keycloak with pagination to avoid response size limits + const keycloakUsers = await getAllUsersPaginated(); + if (!keycloakUsers || typeof keycloakUsers !== "object") { + throw new Error("Failed to get users from Keycloak"); + } + + result.totalInKeycloak = keycloakUsers.length; + + // Find orphaned users (in Keycloak but not in database) + const orphanedUsers = keycloakUsers.filter((user: any) => !databaseKeycloakIds.has(user.id)); + result.orphanedCount = orphanedUsers.length; + + // Delete orphaned users (skip protected ones) + for (const user of orphanedUsers) { + const username = user.username; + const userId = user.id; + + // Check if user should be skipped + if (skipUsernames.includes(username)) { + result.skipped++; + result.details.push({ + keycloakUserId: userId, + username, + action: "skipped", + }); + continue; + } + + // Delete user from Keycloak + try { + const deleteSuccess = await deleteUser(userId); + if (deleteSuccess) { + result.deleted++; + result.details.push({ + keycloakUserId: userId, + username, + action: "deleted", + }); + } else { + result.failed++; + result.details.push({ + keycloakUserId: userId, + username, + action: "error", + error: "Failed to delete user from Keycloak", + }); + } + } catch (error: any) { + result.failed++; + result.details.push({ + keycloakUserId: userId, + username, + action: "error", + error: error.message || "Unknown error", + }); + } + } + + console.log( + `Clear orphaned Keycloak users completed: totalInKeycloak=${result.totalInKeycloak}, totalInDatabase=${result.totalInDatabase}, orphaned=${result.orphanedCount}, deleted=${result.deleted}, skipped=${result.skipped}, failed=${result.failed}`, + ); + } catch (error) { + console.error("Error in clear orphaned Keycloak users:", error); + throw error; + } + + return result; + } +} diff --git a/src/services/OrganizationService.ts b/src/services/OrganizationService.ts new file mode 100644 index 00000000..85cc2220 --- /dev/null +++ b/src/services/OrganizationService.ts @@ -0,0 +1,407 @@ +import { AppDataSource } from "../database/data-source"; +import { PosMaster } from "../entities/PosMaster"; + +// Type definitions for position counts +export interface PositionCountData { + totalCount: number; + currentVacantCount: number; + nextVacantCount: number; +} + +export interface PositionCountsByNode { + orgRootMap: Map; + orgChild1Map: Map; + orgChild2Map: Map; + orgChild3Map: Map; + orgChild4Map: Map; +} + +// Optimized function using SQL aggregation with GROUP BY +// This is much more efficient than loading all records into memory +export async function getPositionCountsAggregated(orgRevisionId: string): Promise { + const posRepo = AppDataSource.getRepository(PosMaster); + + // Helper to build map from query results + const buildMap = (results: any[]) => { + const map = new Map(); + for (const row of results) { + if (row.nodeId) { + map.set(row.nodeId, { + totalCount: parseInt(row.totalCount) || 0, + currentVacantCount: parseInt(row.currentVacantCount) || 0, + nextVacantCount: parseInt(row.nextVacantCount) || 0, + }); + } + } + return map; + }; + + // Execute all aggregation queries in parallel + const [ + orgRootResults, + orgChild1Results, + orgChild2Results, + orgChild3Results, + orgChild4Results, + ] = await Promise.all([ + // Level 0: orgRoot + posRepo + .createQueryBuilder("pos") + .select("pos.orgRootId", "nodeId") + .addSelect("COUNT(*)", "totalCount") + .addSelect("SUM(CASE WHEN pos.current_holderId IS NULL OR pos.current_holderId = '' THEN 1 ELSE 0 END)", "currentVacantCount") + .addSelect("SUM(CASE WHEN pos.next_holderId IS NULL OR pos.next_holderId = '' THEN 1 ELSE 0 END)", "nextVacantCount") + .where("pos.orgRevisionId = :orgRevisionId", { orgRevisionId }) + .andWhere("pos.orgRootId IS NOT NULL") + .groupBy("pos.orgRootId") + .getRawMany(), + + // Level 1: orgChild1 + posRepo + .createQueryBuilder("pos") + .select("pos.orgChild1Id", "nodeId") + .addSelect("COUNT(*)", "totalCount") + .addSelect("SUM(CASE WHEN pos.current_holderId IS NULL OR pos.current_holderId = '' THEN 1 ELSE 0 END)", "currentVacantCount") + .addSelect("SUM(CASE WHEN pos.next_holderId IS NULL OR pos.next_holderId = '' THEN 1 ELSE 0 END)", "nextVacantCount") + .where("pos.orgRevisionId = :orgRevisionId", { orgRevisionId }) + .andWhere("pos.orgChild1Id IS NOT NULL") + .groupBy("pos.orgChild1Id") + .getRawMany(), + + // Level 2: orgChild2 + posRepo + .createQueryBuilder("pos") + .select("pos.orgChild2Id", "nodeId") + .addSelect("COUNT(*)", "totalCount") + .addSelect("SUM(CASE WHEN pos.current_holderId IS NULL OR pos.current_holderId = '' THEN 1 ELSE 0 END)", "currentVacantCount") + .addSelect("SUM(CASE WHEN pos.next_holderId IS NULL OR pos.next_holderId = '' THEN 1 ELSE 0 END)", "nextVacantCount") + .where("pos.orgRevisionId = :orgRevisionId", { orgRevisionId }) + .andWhere("pos.orgChild2Id IS NOT NULL") + .groupBy("pos.orgChild2Id") + .getRawMany(), + + // Level 3: orgChild3 + posRepo + .createQueryBuilder("pos") + .select("pos.orgChild3Id", "nodeId") + .addSelect("COUNT(*)", "totalCount") + .addSelect("SUM(CASE WHEN pos.current_holderId IS NULL OR pos.current_holderId = '' THEN 1 ELSE 0 END)", "currentVacantCount") + .addSelect("SUM(CASE WHEN pos.next_holderId IS NULL OR pos.next_holderId = '' THEN 1 ELSE 0 END)", "nextVacantCount") + .where("pos.orgRevisionId = :orgRevisionId", { orgRevisionId }) + .andWhere("pos.orgChild3Id IS NOT NULL") + .groupBy("pos.orgChild3Id") + .getRawMany(), + + // Level 4: orgChild4 + posRepo + .createQueryBuilder("pos") + .select("pos.orgChild4Id", "nodeId") + .addSelect("COUNT(*)", "totalCount") + .addSelect("SUM(CASE WHEN pos.current_holderId IS NULL OR pos.current_holderId = '' THEN 1 ELSE 0 END)", "currentVacantCount") + .addSelect("SUM(CASE WHEN pos.next_holderId IS NULL OR pos.next_holderId = '' THEN 1 ELSE 0 END)", "nextVacantCount") + .where("pos.orgRevisionId = :orgRevisionId", { orgRevisionId }) + .andWhere("pos.orgChild4Id IS NOT NULL") + .groupBy("pos.orgChild4Id") + .getRawMany(), + ]); + + return { + orgRootMap: buildMap(orgRootResults), + orgChild1Map: buildMap(orgChild1Results), + orgChild2Map: buildMap(orgChild2Results), + orgChild3Map: buildMap(orgChild3Results), + orgChild4Map: buildMap(orgChild4Results), + }; +} + +// Legacy function - kept for backward compatibility +// DEPRECATED: Use getPositionCountsAggregated instead +export async function getPositionCounts(orgRevisionId: string) { + // Query all posMaster data for this revision with aggregation + const rawData = await AppDataSource.getRepository(PosMaster) + .createQueryBuilder("pos") + .select([ + "pos.orgRootId", + "pos.orgChild1Id", + "pos.orgChild2Id", + "pos.orgChild3Id", + "pos.orgChild4Id", + "pos.current_holderId", + "pos.next_holderId", + ]) + .where("pos.orgRevisionId = :orgRevisionId", { orgRevisionId }) + .getMany(); + + // Helper to check if value is null or empty string + const isNull = (val: any) => val === null || val === ""; + + // Build maps for each level + const orgRootMap = new Map(); + const orgChild1Map = new Map(); + const orgChild2Map = new Map(); + const orgChild3Map = new Map(); + const orgChild4Map = new Map(); + + // Nested maps for root positions (positions at specific levels) + const rootPosMap = new Map(); + + for (const pos of rawData) { + const orgRootId = pos.orgRootId || "NULL"; + const orgChild1Id = pos.orgChild1Id || "NULL"; + const orgChild2Id = pos.orgChild2Id || "NULL"; + const orgChild3Id = pos.orgChild3Id || "NULL"; + const orgChild4Id = pos.orgChild4Id || "NULL"; + + // Level 0 (orgRoot) counts + if (!orgRootMap.has(orgRootId)) { + orgRootMap.set(orgRootId, { + totalPosition: 0, + totalPositionCurrentUse: 0, + totalPositionCurrentVacant: 0, + totalPositionNextUse: 0, + totalPositionNextVacant: 0, + }); + } + const rootCounts = orgRootMap.get(orgRootId); + rootCounts.totalPosition++; + + if (!isNull(pos.current_holderId)) rootCounts.totalPositionCurrentUse++; + else rootCounts.totalPositionCurrentVacant++; + + if (!isNull(pos.next_holderId)) rootCounts.totalPositionNextUse++; + else rootCounts.totalPositionNextVacant++; + + // Level 1 (orgChild1) counts + if (!isNull(pos.orgChild1Id)) { + if (!orgChild1Map.has(orgChild1Id)) { + orgChild1Map.set(orgChild1Id, { + totalPosition: 0, + totalPositionCurrentUse: 0, + totalPositionCurrentVacant: 0, + totalPositionNextUse: 0, + totalPositionNextVacant: 0, + }); + } + const child1Counts = orgChild1Map.get(orgChild1Id); + child1Counts.totalPosition++; + if (!isNull(pos.current_holderId)) child1Counts.totalPositionCurrentUse++; + else child1Counts.totalPositionCurrentVacant++; + if (!isNull(pos.next_holderId)) child1Counts.totalPositionNextUse++; + else child1Counts.totalPositionNextVacant++; + } + + // Level 2 (orgChild2) counts + if (!isNull(pos.orgChild2Id)) { + if (!orgChild2Map.has(orgChild2Id)) { + orgChild2Map.set(orgChild2Id, { + totalPosition: 0, + totalPositionCurrentUse: 0, + totalPositionCurrentVacant: 0, + totalPositionNextUse: 0, + totalPositionNextVacant: 0, + }); + } + const child2Counts = orgChild2Map.get(orgChild2Id); + child2Counts.totalPosition++; + if (!isNull(pos.current_holderId)) child2Counts.totalPositionCurrentUse++; + else child2Counts.totalPositionCurrentVacant++; + if (!isNull(pos.next_holderId)) child2Counts.totalPositionNextUse++; + else child2Counts.totalPositionNextVacant++; + } + + // Level 3 (orgChild3) counts + if (!isNull(pos.orgChild3Id)) { + if (!orgChild3Map.has(orgChild3Id)) { + orgChild3Map.set(orgChild3Id, { + totalPosition: 0, + totalPositionCurrentUse: 0, + totalPositionCurrentVacant: 0, + totalPositionNextUse: 0, + totalPositionNextVacant: 0, + }); + } + const child3Counts = orgChild3Map.get(orgChild3Id); + child3Counts.totalPosition++; + if (!isNull(pos.current_holderId)) child3Counts.totalPositionCurrentUse++; + else child3Counts.totalPositionCurrentVacant++; + if (!isNull(pos.next_holderId)) child3Counts.totalPositionNextUse++; + else child3Counts.totalPositionNextVacant++; + } + + // Level 4 (orgChild4) counts + if (!isNull(pos.orgChild4Id)) { + if (!orgChild4Map.has(orgChild4Id)) { + orgChild4Map.set(orgChild4Id, { + totalPosition: 0, + totalPositionCurrentUse: 0, + totalPositionCurrentVacant: 0, + totalPositionNextUse: 0, + totalPositionNextVacant: 0, + }); + } + const child4Counts = orgChild4Map.get(orgChild4Id); + child4Counts.totalPosition++; + if (!isNull(pos.current_holderId)) child4Counts.totalPositionCurrentUse++; + else child4Counts.totalPositionCurrentVacant++; + if (!isNull(pos.next_holderId)) child4Counts.totalPositionNextUse++; + else child4Counts.totalPositionNextVacant++; + } + + // Root position counts (positions at specific hierarchy levels) + // For orgRoot level (all children are null) + const rootLevelKey = orgRootId; + if (!rootPosMap.has(rootLevelKey)) { + rootPosMap.set(rootLevelKey, { + totalRootPosition: 0, + totalRootPositionCurrentUse: 0, + totalRootPositionCurrentVacant: 0, + totalRootPositionNextUse: 0, + totalRootPositionNextVacant: 0, + }); + } + if (isNull(pos.orgChild1Id) && isNull(pos.orgChild2Id) && isNull(pos.orgChild3Id) && isNull(pos.orgChild4Id)) { + const rootLevelCounts = rootPosMap.get(rootLevelKey); + rootLevelCounts.totalRootPosition++; + if (!isNull(pos.current_holderId)) rootLevelCounts.totalRootPositionCurrentUse++; + else rootLevelCounts.totalRootPositionCurrentVacant++; + if (!isNull(pos.next_holderId)) rootLevelCounts.totalRootPositionNextUse++; + else rootLevelCounts.totalRootPositionNextVacant++; + } + + // For orgChild1 level + if (!isNull(pos.orgChild1Id)) { + const child1LevelKey = `${orgRootId}-${pos.orgChild1Id}`; + if (!rootPosMap.has(child1LevelKey)) { + rootPosMap.set(child1LevelKey, { + totalRootPosition: 0, + totalRootPositionCurrentUse: 0, + totalRootPositionCurrentVacant: 0, + totalRootPositionNextUse: 0, + totalRootPositionNextVacant: 0, + }); + } + if (isNull(pos.orgChild2Id) && isNull(pos.orgChild3Id) && isNull(pos.orgChild4Id)) { + const child1LevelCounts = rootPosMap.get(child1LevelKey); + child1LevelCounts.totalRootPosition++; + if (!isNull(pos.current_holderId)) child1LevelCounts.totalRootPositionCurrentUse++; + else child1LevelCounts.totalRootPositionCurrentVacant++; + if (!isNull(pos.next_holderId)) child1LevelCounts.totalRootPositionNextUse++; + else child1LevelCounts.totalRootPositionNextVacant++; + } + } + + // For orgChild2 level + if (!isNull(pos.orgChild2Id)) { + const child2LevelKey = `${orgRootId}-${pos.orgChild1Id}-${pos.orgChild2Id}`; + if (!rootPosMap.has(child2LevelKey)) { + rootPosMap.set(child2LevelKey, { + totalRootPosition: 0, + totalRootPositionCurrentUse: 0, + totalRootPositionCurrentVacant: 0, + totalRootPositionNextUse: 0, + totalRootPositionNextVacant: 0, + }); + } + if (isNull(pos.orgChild3Id) && isNull(pos.orgChild4Id)) { + const child2LevelCounts = rootPosMap.get(child2LevelKey); + child2LevelCounts.totalRootPosition++; + if (!isNull(pos.current_holderId)) child2LevelCounts.totalRootPositionCurrentUse++; + else child2LevelCounts.totalRootPositionCurrentVacant++; + if (!isNull(pos.next_holderId)) child2LevelCounts.totalRootPositionNextUse++; + else child2LevelCounts.totalRootPositionNextVacant++; + } + } + + // For orgChild3 level + if (!isNull(pos.orgChild3Id)) { + const child3LevelKey = `${orgRootId}-${pos.orgChild1Id}-${pos.orgChild2Id}-${orgChild3Id}`; + if (!rootPosMap.has(child3LevelKey)) { + rootPosMap.set(child3LevelKey, { + totalRootPosition: 0, + totalRootPositionCurrentUse: 0, + totalRootPositionCurrentVacant: 0, + totalRootPositionNextUse: 0, + totalRootPositionNextVacant: 0, + }); + } + if (isNull(pos.orgChild4Id)) { + const child3LevelCounts = rootPosMap.get(child3LevelKey); + child3LevelCounts.totalRootPosition++; + if (!isNull(pos.current_holderId)) child3LevelCounts.totalRootPositionCurrentUse++; + else child3LevelCounts.totalRootPositionCurrentVacant++; + if (!isNull(pos.next_holderId)) child3LevelCounts.totalRootPositionNextUse++; + else child3LevelCounts.totalRootPositionNextVacant++; + } + } + + // For orgChild4 level + if (!isNull(pos.orgChild4Id)) { + const child4LevelKey = `${orgRootId}-${pos.orgChild1Id}-${pos.orgChild2Id}-${orgChild3Id}-${pos.orgChild4Id}`; + if (!rootPosMap.has(child4LevelKey)) { + rootPosMap.set(child4LevelKey, { + totalRootPosition: 0, + totalRootPositionCurrentUse: 0, + totalRootPositionCurrentVacant: 0, + totalRootPositionNextUse: 0, + totalRootPositionNextVacant: 0, + }); + } + const child4LevelCounts = rootPosMap.get(child4LevelKey); + child4LevelCounts.totalRootPosition++; + if (!isNull(pos.current_holderId)) child4LevelCounts.totalRootPositionCurrentUse++; + else child4LevelCounts.totalRootPositionCurrentVacant++; + if (!isNull(pos.next_holderId)) child4LevelCounts.totalRootPositionNextUse++; + else child4LevelCounts.totalRootPositionNextVacant++; + } + } + + return { + orgRootMap, + orgChild1Map, + orgChild2Map, + orgChild3Map, + orgChild4Map, + rootPosMap, + }; +} + +// Helper function to get counts from maps with defaults +export function getCounts(map: Map, key: string) { + return ( + map.get(key) || { + totalPosition: 0, + totalPositionCurrentUse: 0, + totalPositionCurrentVacant: 0, + totalPositionNextUse: 0, + totalPositionNextVacant: 0, + } + ); +} + +// Helper function to get root position counts +export function getRootCounts(map: Map, key: string) { + return ( + map.get(key) || { + totalRootPosition: 0, + totalRootPositionCurrentUse: 0, + totalRootPositionCurrentVacant: 0, + totalRootPositionNextUse: 0, + totalRootPositionNextVacant: 0, + } + ); +} + +// Helper function to get position counts from aggregated maps with defaults +export function getPositionCount( + map: Map, + key: string | null | undefined +): PositionCountData { + if (!key) return { totalCount: 0, currentVacantCount: 0, nextVacantCount: 0 }; + return ( + map.get(key) || { + totalCount: 0, + currentVacantCount: 0, + nextVacantCount: 0, + } + ); +} diff --git a/src/services/PositionService.ts b/src/services/PositionService.ts index 67cbecef..44916aee 100644 --- a/src/services/PositionService.ts +++ b/src/services/PositionService.ts @@ -1,3 +1,5 @@ +import { In } from "typeorm"; +import { SavePosMasterHistory } from "./../interfaces/OrgMapping"; import { AppDataSource } from "../database/data-source"; import { EmployeePosMaster } from "../entities/EmployeePosMaster"; import { EmployeeTempPosMaster } from "../entities/EmployeeTempPosMaster"; @@ -6,6 +8,7 @@ import { PosMaster } from "../entities/PosMaster"; import { PosMasterEmployeeHistory } from "../entities/PosMasterEmployeeHistory"; import { PosMasterEmployeeTempHistory } from "../entities/PosMasterEmployeeTempHistory"; import { PosMasterHistory } from "../entities/PosMasterHistory"; +import { Position } from "../entities/Position"; import { ProfileEducation } from "../entities/ProfileEducation"; import { RequestWithUser } from "../middlewares/user"; @@ -13,12 +16,14 @@ export async function CreatePosMasterHistoryOfficer( posMasterId: string, request: RequestWithUser | null, type?: string | null, + positionData?: { positionId?: string } | null, ): Promise { try { await AppDataSource.transaction(async (manager) => { const repoPosmaster = manager.getRepository(PosMaster); const repoHistory = manager.getRepository(PosMasterHistory); const repoOrgRevision = manager.getRepository(OrgRevision); + const repoPosition = manager.getRepository(Position); const pm = await repoPosmaster.findOne({ where: { id: posMasterId }, @@ -44,15 +49,27 @@ export async function CreatePosMasterHistoryOfficer( where: { id: pm.orgRevisionId, orgRevisionIsCurrent: true, - orgRevisionIsDraft: false - } - }) + orgRevisionIsDraft: false, + }, + }); const _null: any = null; const h = new PosMasterHistory(); - const selectedPosition = - pm.positions.length > 0 - ? pm.positions.find((p) => p.positionIsSelected === true) ?? null - : null; + + // query position โดยตรงจาก positionRepository + let selectedPosition: Position | null = null; + if (positionData?.positionId) { + selectedPosition = await repoPosition.findOne({ + where: { id: positionData.positionId }, + relations: { posLevel: true, posType: true, posExecutive: true }, + }); + } else { + // ใช้ logic เดิม หาจาก pm.positions ที่ positionIsSelected = true + selectedPosition = + pm.positions.length > 0 + ? pm.positions.find((p) => p.positionIsSelected === true) ?? null + : null; + } + h.ancestorDNA = pm.ancestorDNA ? pm.ancestorDNA : _null; if (!type || type != "DELETE") { if (checkCurrentRevision) { @@ -119,7 +136,7 @@ export async function CreatePosMasterHistoryEmployee( "positions", "positions.posLevel", "positions.posType", - "positions.posExecutive", + // "positions.posExecutive", "orgRoot", "orgChild1", "orgChild2", @@ -128,7 +145,6 @@ export async function CreatePosMasterHistoryEmployee( "current_holder", ], }); - if (!pm) return false; if (!pm.ancestorDNA) return false; const _null: any = null; @@ -188,7 +204,7 @@ export async function CreatePosMasterHistoryEmployeeTemp( "positions", "positions.posLevel", "positions.posType", - "positions.posExecutive", + // "positions.posExecutive", "orgRoot", "orgChild1", "orgChild2", @@ -260,3 +276,144 @@ export async function getTopDegrees(educations: ProfileEducation[]): Promise { + try { + // Type workaround: entity columns are nullable but types don't reflect it + const _null: any = null; + const repoPosMasterHistory = queryRunner.manager.getRepository(PosMasterHistory); + const pmh = await repoPosMasterHistory.findOne({ + where: { + ancestorDNA: posMasterDnaId, + }, + order: { createdAt: "DESC" }, + }); + + // Check if we need to insert a new history record + const shouldInsert = !pmh && profileId && pm; + const profileChanged = pmh && pmh.profileId !== profileId; + + if (shouldInsert || profileChanged) { + // insert new record + const newPmh = new PosMasterHistory(); + newPmh.ancestorDNA = posMasterDnaId; + newPmh.prefix = pm?.prefix ?? _null; + newPmh.firstName = pm?.firstName ?? _null; + newPmh.lastName = pm?.lastName ?? _null; + newPmh.position = pm?.position ?? _null; + newPmh.posType = pm?.posType ?? _null; + newPmh.posLevel = pm?.posLevel ?? _null; + newPmh.posExecutive = pm?.posExecutive ?? _null; + newPmh.profileId = profileId ?? _null; + newPmh.rootDnaId = pm?.rootDnaId ?? _null; + newPmh.child1DnaId = pm?.child1DnaId ?? _null; + newPmh.child2DnaId = pm?.child2DnaId ?? _null; + newPmh.child3DnaId = pm?.child3DnaId ?? _null; + newPmh.child4DnaId = pm?.child4DnaId ?? _null; + newPmh.shortName = pm?.shortName ?? _null; + newPmh.posMasterNoPrefix = pm?.posMasterNoPrefix ?? _null; + newPmh.posMasterNo = pm?.posMasterNo ?? _null; + newPmh.posMasterNoSuffix = pm?.posMasterNoSuffix ?? _null; + // Add audit fields for data integrity + newPmh.createdUserId = "system"; + newPmh.createdFullName = "system"; + newPmh.lastUpdateUserId = "system"; + newPmh.lastUpdateFullName = "system"; + newPmh.createdAt = new Date(); + newPmh.lastUpdatedAt = new Date(); + await queryRunner.manager.save(PosMasterHistory, newPmh); + return true; + } + return true; + } catch (err) { + console.error("SavePosMasterHistoryOfficer error:", err); + return false; + } +} + +export interface BatchPosMasterHistoryOperation { + posMasterDnaId: string; + profileId: string | null; + pm: SavePosMasterHistory | null; +} + +export async function BatchSavePosMasterHistoryOfficer( + queryRunner: any, + operations: BatchPosMasterHistoryOperation[], +): Promise { + if (operations.length === 0) return true; + + try { + const repoPosMasterHistory = queryRunner.manager.getRepository(PosMasterHistory); + const dnaIds = operations.map((op) => op.posMasterDnaId); + + // Fetch all existing history records in ONE query + const existingHistory = await repoPosMasterHistory.find({ + where: { ancestorDNA: In(dnaIds) }, + order: { createdAt: "DESC" }, + }); + + // Build lookup map + const historyByDna = new Map(); + for (const h of existingHistory) { + if (!historyByDna.has(h.ancestorDNA)) { + historyByDna.set(h.ancestorDNA, []); + } + historyByDna.get(h.ancestorDNA)!.push(h); + } + + // Process operations and collect new records + const newRecords: PosMasterHistory[] = []; + const _null: any = null; + + for (const op of operations) { + const existing = historyByDna.get(op.posMasterDnaId)?.[0]; + const shouldInsert = !existing && op.profileId && op.pm; + const profileChanged = existing && existing.profileId !== op.profileId; + + if (shouldInsert || profileChanged) { + const newPmh = new PosMasterHistory(); + newPmh.ancestorDNA = op.posMasterDnaId; + newPmh.prefix = op.pm?.prefix ?? _null; + newPmh.firstName = op.pm?.firstName ?? _null; + newPmh.lastName = op.pm?.lastName ?? _null; + newPmh.position = op.pm?.position ?? _null; + newPmh.posType = op.pm?.posType ?? _null; + newPmh.posLevel = op.pm?.posLevel ?? _null; + newPmh.posExecutive = op.pm?.posExecutive ?? _null; + newPmh.profileId = op.profileId ?? _null; + newPmh.rootDnaId = op.pm?.rootDnaId ?? _null; + newPmh.child1DnaId = op.pm?.child1DnaId ?? _null; + newPmh.child2DnaId = op.pm?.child2DnaId ?? _null; + newPmh.child3DnaId = op.pm?.child3DnaId ?? _null; + newPmh.child4DnaId = op.pm?.child4DnaId ?? _null; + newPmh.shortName = op.pm?.shortName ?? _null; + newPmh.posMasterNoPrefix = op.pm?.posMasterNoPrefix ?? _null; + newPmh.posMasterNo = op.pm?.posMasterNo ?? _null; + newPmh.posMasterNoSuffix = op.pm?.posMasterNoSuffix ?? _null; + newPmh.createdUserId = "system"; + newPmh.createdFullName = "system"; + newPmh.lastUpdateUserId = "system"; + newPmh.lastUpdateFullName = "system"; + newPmh.createdAt = new Date(); + newPmh.lastUpdatedAt = new Date(); + newRecords.push(newPmh); + } + } + + // Batch insert all new records + if (newRecords.length > 0) { + await queryRunner.manager.save(PosMasterHistory, newRecords); + } + + return true; + } catch (err) { + console.error("BatchSavePosMasterHistoryOfficer error:", err); + return false; + } +} diff --git a/src/services/ProfileLeaveService.ts b/src/services/ProfileLeaveService.ts index 0db2ff19..327a1fe2 100644 --- a/src/services/ProfileLeaveService.ts +++ b/src/services/ProfileLeaveService.ts @@ -289,7 +289,7 @@ export class ProfileLeaveService { isAll?: boolean, ): Promise { // Early return สำหรับ OWNER privilege - if (_data.privilege === "OWNER") { + if (_data.privilege === "OWNER" || _data.privilege === "PARENT") { return { condition: "1=1", params: {} }; } @@ -549,7 +549,7 @@ export class ProfileLeaveService { queryBuilder.andWhere(nodeCondition.condition, nodeCondition.params); - if (_data.privilege !== "OWNER") { + if (_data.privilege !== "OWNER" && _data.privilege !== "PARENT") { queryBuilder.andWhere(permissionCondition.condition, permissionCondition.params); } } @@ -717,7 +717,7 @@ export class ProfileLeaveService { queryBuilder.andWhere(nodeCondition.condition, nodeCondition.params); - if (_data.privilege !== "OWNER") { + if (_data.privilege !== "OWNER" && _data.privilege !== "PARENT") { queryBuilder.andWhere(permissionCondition.condition, permissionCondition.params); } } diff --git a/src/services/rabbitmq.ts b/src/services/rabbitmq.ts index 326cb9a1..a8011900 100644 --- a/src/services/rabbitmq.ts +++ b/src/services/rabbitmq.ts @@ -85,7 +85,7 @@ export async function init() { console.log("[AMQ] Listening for message..."); createConsumer(queue, channel, handler), //----> (3) Process Consumer - createConsumer(queue_org, channel, handler_org); + createConsumer(queue_org, channel, handler_org); createConsumer(queue_org_draft, channel, handler_org_draft); createConsumer(queue_command_noti, channel, handler_command_noti); // createConsumer(queue2, channel, handler2); @@ -561,6 +561,14 @@ async function handler_org(msg: amqp.ConsumeMessage): Promise { "positions.posExecutive", ], }); + + const oldPosMasters = await repoPosmaster.find({ + where: { + orgRevisionId: orgRevisionPublish!.id, + }, + select: ['id', 'current_holderId', 'ancestorDNA'] + }); + // Task #2160 ดึง posMasterAssign ของ revision เดิม const oldposMasterAssigns = await posMasterAssignRepository.find({ relations: ["posMaster"], @@ -613,9 +621,20 @@ async function handler_org(msg: amqp.ConsumeMessage): Promise { posMasterIdMap.set(pm.ancestorDNA?.trim() ?? '', pm.id); } + const oldPosMasterMap = new Map(); + for (const oldPm of oldPosMasters) { + const dna = oldPm.ancestorDNA?.trim(); + if (dna) { + oldPosMasterMap.set(dna, oldPm); + } + } + const _null: any = null; for (const item of posMaster) { + const dna = item.ancestorDNA?.trim(); + const oldPm = dna ? oldPosMasterMap.get(dna) : null; + // Task #2160 Clone posMasterAssign const assigns = assignMap.get(item.ancestorDNA); if (assigns && assigns.length > 0) { @@ -632,7 +651,8 @@ async function handler_org(msg: amqp.ConsumeMessage): Promise { await posMasterAssignRepository.save(newAssigns); } - if (item.next_holderId != null) { + // ถ้าไม่ใช่ตำแหน่งนั่งทับ (isSit = false) ถึงจะอัพเดทตำแหน่งในทะเบียนประวัติ + if (item.next_holderId != null && !item.isSit) { const profile = await repoProfile.findOne({ where: { id: item.next_holderId == null ? "" : item.next_holderId }, }); @@ -651,18 +671,26 @@ async function handler_org(msg: amqp.ConsumeMessage): Promise { await repoProfile.save(profile); } } - item.current_holderId = item.next_holderId; - // item.conditionReason = _null; - // if (item.current_holderId) { - // item.conditionReason = _null; - // item.isCondition = false; - // } - item.next_holderId = null; - item.lastUpdateUserId = lastUpdateUserId; - item.lastUpdateFullName = lastUpdateFullName; - item.lastUpdatedAt = lastUpdatedAt; - await repoPosmaster.save(item).catch((e) => console.log(e)); - await CreatePosMasterHistoryOfficer(item.id, null); + // item.current_holderId = item.next_holderId; + // item.next_holderId = null; + // item.lastUpdateUserId = lastUpdateUserId; + // item.lastUpdateFullName = lastUpdateFullName; + // item.lastUpdatedAt = lastUpdatedAt; + await repoPosmaster.update(item.id, { + current_holderId: item.next_holderId, + next_holderId: null, + lastUpdateUserId, + lastUpdateFullName, + lastUpdatedAt, + }); + + const oldHolderId = oldPm ? oldPm.current_holderId : null; + const newHolderId = item ? item.next_holderId : null; + const isHolderChanged = oldHolderId !== newHolderId; + + if (isHolderChanged) { + await CreatePosMasterHistoryOfficer(item.id, null); + } } for (const act of oldposMasterAct) { @@ -681,11 +709,11 @@ async function handler_org(msg: amqp.ConsumeMessage): Promise { posMasterId: newParentId, posMasterChildId: newChildId, createdAt: new Date(), - createdFullName: user.name, - createdUserId: user.sub, + createdFullName: user ? user.name : "system", + createdUserId: user ? user.sub : "system", lastUpdatedAt: new Date(), - lastUpdateFullName: user.name, - lastUpdateUserId: user.sub, + lastUpdateFullName: user ? user.name : "system", + lastUpdateUserId: user ? user.sub : "system", }; await posMasterActRepository.save(newAct); diff --git a/src/utils/LogMemoryStore.ts b/src/utils/LogMemoryStore.ts new file mode 100644 index 00000000..24e84f9e --- /dev/null +++ b/src/utils/LogMemoryStore.ts @@ -0,0 +1,165 @@ +import { AppDataSource } from "../database/data-source"; +import { OrgRevision } from "../entities/OrgRevision"; +import { Profile } from "../entities/Profile"; +import { PosMaster } from "../entities/PosMaster"; + +interface LogCacheData { + currentRevision: OrgRevision | null; + profileCache: Map; // keycloak → Profile + rootIdCache: Map; // profileId → rootId + updatedAt: Date; +} + +class LogMemoryStore { + private cache: LogCacheData = { + currentRevision: null, + profileCache: new Map(), + rootIdCache: new Map(), + updatedAt: new Date(), + }; + private readonly CACHE_TTL = 60 * 60 * 1000; // 60 minutes + private isInitialized = false; + + constructor() { + // ไม่ refresh ทันที - รอให้เรียก initialize() หลัง TypeORM ready + } + + // เริ่มต้น cache หลังจาก TypeORM initialize เสร็จ + initialize() { + if (this.isInitialized) { + console.log("[LogMemoryStore] Already initialized"); + return; + } + + this.isInitialized = true; + this.refreshCache(); + + console.log( + "[LogMemoryStore] Initialized with", + this.CACHE_TTL / 1000 / 60, + "minute TTL (passive cleanup on access)", + ); + } + + private async refreshCache() { + try { + // Refresh revision cache + const repoRevision = AppDataSource.getRepository(OrgRevision); + const revision = await repoRevision.findOne({ + where: { + orgRevisionIsCurrent: true, + orgRevisionIsDraft: false, + }, + }); + this.cache.currentRevision = revision; + + // Clear on-demand caches (they will be rebuilt as needed) + this.cache.profileCache.clear(); + this.cache.rootIdCache.clear(); + + this.cache.updatedAt = new Date(); + console.log("[LogMemoryStore] Cache refreshed at", this.cache.updatedAt.toISOString()); + } catch (error) { + console.error("[LogMemoryStore] Error refreshing cache:", error); + } + } + + // Check if cache is stale and refresh if needed + private async checkAndRefreshIfNeeded() { + const now = new Date(); + const age = now.getTime() - this.cache.updatedAt.getTime(); + if (age > this.CACHE_TTL) { + console.log( + "[LogMemoryStore] Cache is stale (age:", + Math.round(age / 1000 / 60), + "minutes), refreshing...", + ); + await this.refreshCache(); + } + } + + getCurrentRevision(): OrgRevision | null { + // Check for stale data (fire and forget) + this.checkAndRefreshIfNeeded(); + return this.cache.currentRevision; + } + + getLastUpdateTime(): Date { + return this.cache.updatedAt; + } + + /** + * Get Profile by keycloak ID with caching + */ + async getProfileByKeycloak(keycloak: string): Promise { + // Check for stale data + await this.checkAndRefreshIfNeeded(); + + // Check cache first + if (this.cache.profileCache.has(keycloak)) { + return this.cache.profileCache.get(keycloak)!; + } + + // Fetch from database + const repoProfile = AppDataSource.getRepository(Profile); + const profile = await repoProfile.findOne({ + where: { keycloak }, + }); + + // Cache the result + if (profile) { + this.cache.profileCache.set(keycloak, profile); + } + + return profile; + } + + /** + * Get RootId by profileId with caching + */ + async getRootIdByProfileId(profileId: string | undefined): Promise { + if (!profileId) return null; + + // Check for stale data + await this.checkAndRefreshIfNeeded(); + + // Check cache first + if (this.cache.rootIdCache.has(profileId)) { + return this.cache.rootIdCache.get(profileId)!; + } + + // Fetch from database + const repoPosmaster = AppDataSource.getRepository(PosMaster); + const revision = this.getCurrentRevision(); + // + const posMaster = await repoPosmaster.findOne({ + where: { + current_holderId: profileId, + orgRevisionId: revision?.id, + }, + relations: ["orgRoot"], + select: { + orgRoot: { + ancestorDNA: true, + }, + }, + }); + + const rootId = posMaster?.orgRoot?.ancestorDNA ?? null; + + // Cache the result + if (rootId) { + this.cache.rootIdCache.set(profileId, rootId); + } + + return rootId; + } + + // สำหรับ shutdown + destroy() { + // No active timer to clear + console.log("[LogMemoryStore] Destroyed"); + } +} + +export const logMemoryStore = new LogMemoryStore(); diff --git a/src/utils/OrgStructureCache.ts b/src/utils/OrgStructureCache.ts new file mode 100644 index 00000000..d4e6c20e --- /dev/null +++ b/src/utils/OrgStructureCache.ts @@ -0,0 +1,96 @@ +interface CacheEntry { + data: any; + cachedAt: Date; +} + +class OrgStructureCache { + private cache: Map = new Map(); + private readonly CACHE_TTL = 30 * 60 * 1000; // 30 minutes + private isInitialized = false; + private cleanupTimer: NodeJS.Timeout | null = null; + + constructor() { + // Don't auto-initialize - wait for AppDataSource to be ready + } + + initialize() { + if (this.isInitialized) return; + + this.isInitialized = true; + // Cleanup expired entries every 30 minutes + this.cleanupTimer = setInterval(() => { + this.cleanup(); + }, this.CACHE_TTL); + + console.log("[OrgStructureCache] Initialized"); + } + + private generateKey(revisionId: string, rootId?: string): string { + return `org-structure-${revisionId}-${rootId || "all"}`; + } + + private cleanup() { + const now = Date.now(); + let cleaned = 0; + + for (const [key, entry] of this.cache.entries()) { + const age = now - entry.cachedAt.getTime(); + if (age > this.CACHE_TTL) { + this.cache.delete(key); + cleaned++; + } + } + + if (cleaned > 0) { + console.log(`[OrgStructureCache] Cleaned ${cleaned} expired entries`); + } + } + + async get(revisionId: string, rootId?: string): Promise { + const key = this.generateKey(revisionId, rootId); + const entry = this.cache.get(key); + + if (!entry) { + return null; + } + + // Check if expired + const age = Date.now() - entry.cachedAt.getTime(); + if (age > this.CACHE_TTL) { + this.cache.delete(key); + return null; + } + + console.log(`[OrgStructureCache] HIT: ${key}`); + return entry.data; + } + + async set(revisionId: string, rootId: string | undefined, data: any): Promise { + const key = this.generateKey(revisionId, rootId); + this.cache.set(key, { + data, + cachedAt: new Date(), + }); + console.log(`[OrgStructureCache] SET: ${key}`); + } + + invalidate(revisionId: string): void { + // Invalidate all entries for this revision + for (const key of this.cache.keys()) { + if (key.startsWith(`org-structure-${revisionId}`)) { + this.cache.delete(key); + } + } + console.log(`[OrgStructureCache] INVALIDATED: ${revisionId}`); + } + + destroy() { + if (this.cleanupTimer) { + clearInterval(this.cleanupTimer); + this.cleanupTimer = null; + } + this.cache.clear(); + } +} + +export const orgStructureCache = new OrgStructureCache(); diff --git a/src/utils/org-formatting.ts b/src/utils/org-formatting.ts new file mode 100644 index 00000000..701fb478 --- /dev/null +++ b/src/utils/org-formatting.ts @@ -0,0 +1,70 @@ +import { PosMaster } from "../entities/PosMaster"; + +export interface PosMasterFormatted { + posmasterId: string; + posNo: string; + orgTreeId: string; + orgLevel: number; + fullNameCurrentHolder: string | null; +} + +export interface OrgTreeNode { + orgTreeId: string; + orgLevel: number; + orgName: string; + orgTreeName: string; + orgTreeShortName: string; + orgTreeCode: string; + orgCode: string; + orgRootName: string; + labelName: string; + posMaster: PosMasterFormatted[]; + children?: OrgTreeNode[]; +} + +export function formatPosMaster( + posMaster: PosMaster, + orgShortName: string, + orgTreeId: string, + orgLevel: number, +): PosMasterFormatted { + return { + posmasterId: posMaster.id, + posNo: `${orgShortName} ${posMaster.posMasterNo}`, + orgTreeId, + orgLevel, + fullNameCurrentHolder: posMaster.current_holder + ? `${posMaster.current_holder.prefix}${posMaster.current_holder.firstName} ${posMaster.current_holder.lastName}` + : null, + }; +} + +export function generateLabelName( + nodeName: string, + nodeCode: string, + nodeShortName: string, + rootName: string, + rootCode: string, + rootShortName: string, + parentNames?: string[], + parentCodes?: string[], + parentShortNames?: string[], +): string { + const parts = [nodeName, " ", rootCode, nodeCode, " ", nodeShortName]; + + if (parentNames) { + for (let i = 0; i < parentNames.length; i++) { + parts.push("/", parentNames[i], " ", rootCode, parentCodes![i], " ", parentShortNames![i]); + } + } + + parts.push("/", rootName, " ", rootCode, "00", " ", rootShortName); + return parts.join(""); +} + +export function filterPosMasters( + posMasters: PosMaster[], + childLevelIdKey: keyof PosMaster, +): PosMaster[] { + return posMasters.filter((x) => x[childLevelIdKey] == null && x.isDirector === true); +} diff --git a/src/utils/sync-progress.ts b/src/utils/sync-progress.ts new file mode 100644 index 00000000..afee9b20 --- /dev/null +++ b/src/utils/sync-progress.ts @@ -0,0 +1,162 @@ +import * as fs from "fs"; +import * as path from "path"; + +/** + * Interface for sync progress state + * Tracks the progress of batch sync operations + */ +export interface SyncProgressState { + lastSyncedIndex: number; + totalProfiles: number; + startTime: number; + lastUpdate: number; + failedProfiles: Array<{ index: number; profileId: string; error: string }>; + profileIds: string[]; // Track order of profile IDs for resume +} + +/** + * SyncProgressManager + * Manages persistent storage of sync progress state + * Enables resuming from checkpoints after failures + */ +export class SyncProgressManager { + private static readonly FILE_PATH = ".sync-progress.json"; + + /** + * Save progress state to file + * @param state - Current progress state to save + */ + static save(state: SyncProgressState): void { + try { + const filePath = path.resolve(process.cwd(), this.FILE_PATH); + state.lastUpdate = Date.now(); + fs.writeFileSync(filePath, JSON.stringify(state, null, 2), "utf-8"); + } catch (error) { + console.error("[SyncProgressManager] Error saving progress:", error); + } + } + + /** + * Load progress state from file + * @returns Progress state if exists, null otherwise + */ + static load(): SyncProgressState | null { + try { + const filePath = path.resolve(process.cwd(), this.FILE_PATH); + if (!fs.existsSync(filePath)) { + return null; + } + + const data = fs.readFileSync(filePath, "utf-8"); + const state = JSON.parse(data) as SyncProgressState; + + // Validate required fields + if ( + typeof state.lastSyncedIndex !== "number" || + typeof state.totalProfiles !== "number" || + !Array.isArray(state.failedProfiles) || + !Array.isArray(state.profileIds) + ) { + console.warn("[SyncProgressManager] Invalid progress file, starting fresh"); + return null; + } + + return state; + } catch (error) { + console.error("[SyncProgressManager] Error loading progress:", error); + return null; + } + } + + /** + * Clear progress file + * Called on successful completion or when starting fresh + */ + static clear(): void { + try { + const filePath = path.resolve(process.cwd(), this.FILE_PATH); + if (fs.existsSync(filePath)) { + fs.unlinkSync(filePath); + console.log("[SyncProgressManager] Progress file cleared"); + } + } catch (error) { + console.error("[SyncProgressManager] Error clearing progress:", error); + } + } + + /** + * Get progress percentage + * @param state - Current progress state + * @returns Percentage complete (0-100) + */ + static getProgressPercent(state: SyncProgressState): number { + if (state.totalProfiles === 0) return 0; + return Math.round((state.lastSyncedIndex / state.totalProfiles) * 100); + } + + /** + * Format elapsed time as readable string + * @param startTime - Start timestamp in milliseconds + * @returns Formatted time string (e.g., "2h 30m 15s") + */ + static formatElapsedTime(startTime: number): string { + const elapsed = Date.now() - startTime; + const seconds = Math.floor(elapsed / 1000); + const minutes = Math.floor(seconds / 60); + const hours = Math.floor(minutes / 60); + + const parts: string[] = []; + if (hours > 0) parts.push(`${hours}h`); + if (minutes % 60 > 0) parts.push(`${minutes % 60}m`); + if (seconds % 60 > 0 || parts.length === 0) parts.push(`${seconds % 60}s`); + + return parts.join(" "); + } + + /** + * Log current progress to console + * @param state - Current progress state + */ + static logProgress(state: SyncProgressState): void { + const percent = this.getProgressPercent(state); + const elapsed = this.formatElapsedTime(state.startTime); + const failed = state.failedProfiles.length; + + console.log( + `[Sync Progress] ${percent}% (${state.lastSyncedIndex}/${state.totalProfiles}) | Elapsed: ${elapsed} | Failed: ${failed}`, + ); + } + + /** + * Add failed profile to state + * @param state - Progress state to update + * @param index - Profile index + * @param profileId - Profile ID that failed + * @param error - Error message + */ + static addFailedProfile( + state: SyncProgressState, + index: number, + profileId: string, + error: string, + ): void { + state.failedProfiles.push({ index, profileId, error }); + this.save(state); + } + + /** + * Initialize new progress state + * @param profileIds - Array of profile IDs to sync + * @returns New progress state + */ + static initialize(profileIds: string[]): SyncProgressState { + return { + lastSyncedIndex: 0, + totalProfiles: profileIds.length, + startTime: Date.now(), + lastUpdate: Date.now(), + failedProfiles: [], + profileIds, + }; + } +} diff --git a/tsconfig.json b/tsconfig.json index f5f96a0d..1c974407 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -13,6 +13,11 @@ "experimentalDecorators": true, "emitDecoratorMetadata": true, "resolveJsonModule": true, - "skipLibCheck": true, + "skipLibCheck": true }, + "exclude": [ + "src/__tests__/**", + "**/*.spec.ts", + "**/*.test.ts" + ] }