Compare commits
No commits in common. "dev" and "dev-test" have entirely different histories.
57 changed files with 5374 additions and 9273 deletions
134
CLAUDE.md
134
CLAUDE.md
|
|
@ -1,134 +0,0 @@
|
||||||
# CLAUDE.md
|
|
||||||
|
|
||||||
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
|
|
||||||
|
|
||||||
## Project Overview
|
|
||||||
|
|
||||||
TypeScript REST API for **Bangkok Metropolitan Administration (BMA)** — a Human Resource
|
|
||||||
Management System (HRMS) module managing organization structure (โครงสร้างอัตรากำลัง),
|
|
||||||
positions, personnel profiles, salary/tenure calculations, and related HR workflows.
|
|
||||||
|
|
||||||
**Stack:** Express.js · TypeORM (MySQL) · tsoa (controller-first OpenAPI generation) · Keycloak ·
|
|
||||||
RabbitMQ · Redis · Elasticsearch · WebSocket (socket.io) · node-cron
|
|
||||||
|
|
||||||
## Commands
|
|
||||||
|
|
||||||
```sh
|
|
||||||
npm install # install deps (npm is canonical — CI/Docker use npm install, not pnpm)
|
|
||||||
npm run dev # dev server with hot reload (nodemon)
|
|
||||||
npm run build # tsoa spec-and-routes (regenerates src/routes.ts, src/swagger.json) + tsc
|
|
||||||
npm run check # tsc --noEmit
|
|
||||||
npm start # run compiled dist/app.js
|
|
||||||
npm run format # prettier --write .
|
|
||||||
|
|
||||||
npm run migration:generate src/migration/<name> # generate a TypeORM migration
|
|
||||||
npm run migration:run # run pending migrations
|
|
||||||
node scripts/clean-migration-fk-idx.js # strip FK_/idx_ lines from generated migrations (run after every generate)
|
|
||||||
|
|
||||||
npm test # jest
|
|
||||||
npm run test:watch # jest --watch
|
|
||||||
npm run test:coverage # jest --coverage
|
|
||||||
npx jest src/__tests__/unit/OrganizationController.spec.ts # run a single test file
|
|
||||||
```
|
|
||||||
|
|
||||||
A repo note (README) documents building/testing the release pipeline locally with `act`
|
|
||||||
(`act workflow_dispatch -W .github/workflows/release.yaml ...`), but the actual CI is Forgejo
|
|
||||||
Actions (`.forgejo/workflows/`) and an OneDev buildspec (`.onedev-buildspec.yml`); both run
|
|
||||||
`npm install && npm run build` and build `docker/Dockerfile`.
|
|
||||||
|
|
||||||
**Note:** despite `pnpm-lock.yaml` being present in the repo, all CI/Docker paths use `npm`. Use
|
|
||||||
`npm` for scripts and dependency changes unless told otherwise.
|
|
||||||
|
|
||||||
## Architecture
|
|
||||||
|
|
||||||
### Request flow: tsoa controllers → (optional) services → TypeORM repositories
|
|
||||||
|
|
||||||
- Routes are **not** hand-written. `tsoa.json` globs `src/controllers/**/*Controller.ts` and
|
|
||||||
`npm run build` regenerates `src/routes.ts` (Express route registration, called via
|
|
||||||
`RegisterRoutes(app)` in `src/app.ts`) and `src/swagger.json` from tsoa decorators. **Any time
|
|
||||||
you add/change a controller method or its decorators, you must run `npm run build`** (or at
|
|
||||||
least `tsoa spec-and-routes`) or the route/swagger changes won't take effect.
|
|
||||||
- In practice most controllers get their repositories directly via
|
|
||||||
`AppDataSource.getRepository(Entity)` in the constructor/method body rather than going through a
|
|
||||||
service layer — the codebase is controller-heavy (`OrganizationController.ts`,
|
|
||||||
`ImportDataController.ts`, `PositionController.ts`, `ReportController.ts` are each hundreds of
|
|
||||||
KB). `src/services/` exists for genuinely cross-cutting or heavier business logic (org command
|
|
||||||
execution, salary/tenure batch jobs, RabbitMQ, WebSocket) but is not a strict mandatory layer for
|
|
||||||
every endpoint.
|
|
||||||
- All entities (`src/entities/`) extend `EntityBase` (`src/entities/base/Base.ts`), which supplies
|
|
||||||
`id` (UUID), `createdAt`/`lastUpdatedAt`, and creator/updater id+name audit columns — every table
|
|
||||||
is audited by default. `src/entities/mis/` holds legacy/external MIS-system table mappings
|
|
||||||
(read-mostly, prefixed `HR_*`); `src/entities/view/` holds TypeORM entities mapped onto SQL
|
|
||||||
views (`viewCurrentTenure*`, `viewDirector*`, etc.) used for computed/reporting reads.
|
|
||||||
- Responses are wrapped in `HttpSuccess` (`src/interfaces/http-success.ts`, `{status, message,
|
|
||||||
result}`, Thai success message by default) or thrown as `HttpError` (`src/interfaces/
|
|
||||||
http-error.ts`, carries an `HttpStatus` + Thai message) — the global error middleware
|
|
||||||
(`src/middlewares/error.ts`) catches thrown `HttpError`/exceptions and formats the response.
|
|
||||||
`src/interfaces/http-status.ts` is the status-code enum used everywhere instead of magic numbers.
|
|
||||||
|
|
||||||
### Auth (three schemes, selected per-route via `@Security(...)`)
|
|
||||||
|
|
||||||
All resolved in `expressAuthentication` (`src/middlewares/auth.ts`), which tsoa calls per-request
|
|
||||||
based on the controller's `@Security` decorator:
|
|
||||||
|
|
||||||
- `bearerAuth` — Keycloak JWT in `Authorization: Bearer <token>`. Verified either **offline**
|
|
||||||
(`AUTH_PUBLIC_KEY`, local RS256 verification via `fast-jwt`) or **online** (`AUTH_REALM_URL`
|
|
||||||
userinfo endpoint), selected by `AUTH_PREFERRED_MODE`. Populates `req.app.locals.logData` with
|
|
||||||
user/org-tree ids from the token for logging. Type: `RequestWithUser` (`src/middlewares/user.ts`).
|
|
||||||
- `webServiceAuth` — `X-API-Key` header, resolved against the `ApiKey` entity in
|
|
||||||
`src/middlewares/authWebService.ts` (looks up allowed `apiNames`/org scope). Type:
|
|
||||||
`RequestWithUserWebService`.
|
|
||||||
- `internalAuth` — `api-key`/`api_key`/`apikey` header checked against the `API_KEY` env var
|
|
||||||
(`src/middlewares/authInternal.ts`), for trusted internal services (e.g. the .NET HRMS system).
|
|
||||||
- If `NODE_ENV !== "production"` and `AUTH_BYPASS` is set, auth is skipped entirely
|
|
||||||
(`{preferred_username: "bypassed"}`) — dev/test convenience only.
|
|
||||||
- Role gating within `bearerAuth` routes uses `authRole()` (`src/middlewares/role.ts`) and the
|
|
||||||
`permission` helper (`src/interfaces/permission.ts`) which calls out to an external `/org/permission`
|
|
||||||
check via `CallAPI`.
|
|
||||||
|
|
||||||
### Background work
|
|
||||||
|
|
||||||
- **Cron jobs** are all registered inline in `src/app.ts` via `node-cron` (6-field: seconds
|
|
||||||
included), each wrapping a controller/service call in try/catch that only logs on failure —
|
|
||||||
daily org revision cache refresh, retirement status updates (Oct 1st), org DNA sync, tenure
|
|
||||||
recalculation, and posting retirement data to an external "Exprofile" system.
|
|
||||||
- **RabbitMQ** (`src/services/rabbitmq.ts`) publishes org-structure change events
|
|
||||||
(`sendToQueueOrg` / `sendToQueueOrgDraft`); connects with infinite retry
|
|
||||||
(`setTimeout(runMessageQueue, 1000)` on failure) — don't add redundant retry logic around it.
|
|
||||||
Fire-and-forget from callers; don't block HTTP responses on publish.
|
|
||||||
- **WebSocket** (`src/services/webSocket.ts`, `initWebSocket()`) for real-time push, initialized
|
|
||||||
before the HTTP server starts listening.
|
|
||||||
- **`OrgStructureCache`** (`src/utils/OrgStructureCache.ts`) is an in-memory TTL cache (30 min) for
|
|
||||||
org-tree reads, keyed by revision+root id, initialized/destroyed alongside the app lifecycle. Use
|
|
||||||
it instead of re-querying the same org structure repeatedly within a request.
|
|
||||||
- **`LogMemoryStore`** (`src/utils/LogMemoryStore.ts`) plus `src/middlewares/logs.ts` build a
|
|
||||||
per-request log sequence (including DB queries logged via the custom TypeORM `Logger` in
|
|
||||||
`src/database/data-source.ts`) — this is what's shipped to Elasticsearch for auditing.
|
|
||||||
|
|
||||||
### Database
|
|
||||||
|
|
||||||
- MySQL via TypeORM, connection pool configured in `src/database/data-source.ts`
|
|
||||||
(`connectionLimit`, `poolSize`, `maxQueryExecutionTime` all env-tunable). `synchronize` is
|
|
||||||
always `false` — schema changes go through migrations only.
|
|
||||||
- Timezone is pinned to Bangkok (`+07:00`) at the connection level; store/compare datetimes
|
|
||||||
accordingly rather than assuming UTC.
|
|
||||||
- After `migration:generate`, always run `node scripts/clean-migration-fk-idx.js` to strip
|
|
||||||
auto-generated `FK_*`/`idx_*` lines from the migration's `up`/`down` — this is a hard project
|
|
||||||
convention, not optional cleanup.
|
|
||||||
|
|
||||||
### Data dictionary
|
|
||||||
|
|
||||||
`docs/data-dictionary/` holds a generated schema data dictionary (`.docx`); the `data-dictionary`
|
|
||||||
skill / `scripts/generate-docx.py` regenerate it from `docs/data-dictionary/generate-prompt.md`.
|
|
||||||
|
|
||||||
## Conventions
|
|
||||||
|
|
||||||
- Files/classes: PascalCase (`OrganizationController.ts`). Variables/functions: camelCase.
|
|
||||||
- DB column comments and HTTP error/success messages are written in **Thai** — keep this
|
|
||||||
consistent when adding columns or throwing `HttpError`.
|
|
||||||
- Services must not import from `controllers/`; keep services HTTP-agnostic (no `HttpError`/
|
|
||||||
`HttpSuccess` imports in `src/services/`).
|
|
||||||
- Don't use `moment` for new code (native `Date`/`Intl` only — `moment` remains for legacy call
|
|
||||||
sites). Don't bypass tsoa validation with manual `req.body` casting.
|
|
||||||
- `tsconfig.json` excludes `src/__tests__/**` and `*.spec.ts`/`*.test.ts` from the production
|
|
||||||
build; tests only run under `ts-jest` via `jest.config.js` (path alias `@/` → `src/`).
|
|
||||||
18
src/app.ts
18
src/app.ts
|
|
@ -21,24 +21,6 @@ import { DateSerializer } from "./interfaces/date-serializer";
|
||||||
import { initWebSocket } from "./services/webSocket";
|
import { initWebSocket } from "./services/webSocket";
|
||||||
import { RetirementService } from "./services/RetirementService";
|
import { RetirementService } from "./services/RetirementService";
|
||||||
|
|
||||||
// ── Process-level safety nets ──────────────────────────────────────────────
|
|
||||||
// ตาข่ายกัน service ตายจาก promise ที่ reject แล้วไม่ถูกจับ / exception ที่หลุด
|
|
||||||
// ออกมา (เช่น bug เรียก async โดยไม่ await ใน PermissionController).
|
|
||||||
// ลงทะเบียนไว้ให้เร็วที่สุด ก่อนที่ main() จะเริ่มทำงาน
|
|
||||||
process.on("unhandledRejection", (reason) => {
|
|
||||||
console.error(
|
|
||||||
"[APP][unhandledRejection] A promise was rejected but never caught:",
|
|
||||||
reason,
|
|
||||||
);
|
|
||||||
});
|
|
||||||
process.on("uncaughtException", (err) => {
|
|
||||||
// คง service ไว้เพื่อ availability สูงสุด
|
|
||||||
// หมายเหตุ: process อาจอยู่ในสถานะไม่สม่ำเสมอ ควร monitor log นี้และ restart
|
|
||||||
// ในช่วงเวลาที่เหมาะสม ถ้าต้องการ crash-on-error ให้เปลี่ยนเป็น graceful
|
|
||||||
// shutdown + process.exit(1) ตรงนี้
|
|
||||||
console.error("[APP][uncaughtException] An exception escaped all handlers:", err);
|
|
||||||
});
|
|
||||||
|
|
||||||
async function main() {
|
async function main() {
|
||||||
await AppDataSource.initialize();
|
await AppDataSource.initialize();
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -161,47 +161,39 @@ export class ApiWebServiceController extends Controller {
|
||||||
);
|
);
|
||||||
} else if (dnaIds.dnaChild3Id) {
|
} else if (dnaIds.dnaChild3Id) {
|
||||||
conditions.push(
|
conditions.push(
|
||||||
accessType === "NORMAL"
|
`${tableAlias}.orgChild3Id IN (SELECT id FROM orgChild3 WHERE orgRevisionId = "${this.currentRevisionId}" AND ancestorDNA = "${dnaIds.dnaChild3Id}")`,
|
||||||
? `(${tableAlias}.orgChild3Id IN (SELECT id FROM orgChild3 WHERE orgRevisionId = "${this.currentRevisionId}" AND ancestorDNA = "${dnaIds.dnaChild3Id}") AND ${tableAlias}.orgChild4Id IS NULL)`
|
|
||||||
: `${tableAlias}.orgChild3Id IN (SELECT id FROM orgChild3 WHERE orgRevisionId = "${this.currentRevisionId}" AND ancestorDNA = "${dnaIds.dnaChild3Id}")`,
|
|
||||||
);
|
);
|
||||||
// For CHILD type, include all descendants
|
// For CHILD type, include all descendants
|
||||||
if (accessType === "CHILD") {
|
if (accessType === "CHILD") {
|
||||||
conditions.push(
|
conditions.push(
|
||||||
`(${tableAlias}.orgChild3Id IN (SELECT id FROM orgChild3 WHERE orgRevisionId = "${this.currentRevisionId}" AND ancestorDNA LIKE "${dnaIds.dnaChild3Id}%") OR ${tableAlias}.orgChild4Id IN (SELECT id FROM orgChild4 WHERE orgRevisionId = "${this.currentRevisionId}" AND ancestorDNA LIKE "${dnaIds.dnaChild3Id}%"))`,
|
`(${tableAlias}.orgChild3Id IN (SELECT id FROM orgChild3 WHERE orgRevisionId = "${this.currentRevisionId}" AND ancestorDNA LIKE "${dnaIds.dnaChild3Id}%") OR ${tableAlias}.orgChild4Id IS NOT NULL)`,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
} else if (dnaIds.dnaChild2Id) {
|
} else if (dnaIds.dnaChild2Id) {
|
||||||
conditions.push(
|
conditions.push(
|
||||||
accessType === "NORMAL"
|
`${tableAlias}.orgChild2Id IN (SELECT id FROM orgChild2 WHERE orgRevisionId = "${this.currentRevisionId}" AND ancestorDNA = "${dnaIds.dnaChild2Id}")`,
|
||||||
? `(${tableAlias}.orgChild2Id IN (SELECT id FROM orgChild2 WHERE orgRevisionId = "${this.currentRevisionId}" AND ancestorDNA = "${dnaIds.dnaChild2Id}") AND ${tableAlias}.orgChild3Id IS NULL AND ${tableAlias}.orgChild4Id IS NULL)`
|
|
||||||
: `${tableAlias}.orgChild2Id IN (SELECT id FROM orgChild2 WHERE orgRevisionId = "${this.currentRevisionId}" AND ancestorDNA = "${dnaIds.dnaChild2Id}")`,
|
|
||||||
);
|
);
|
||||||
if (accessType === "CHILD") {
|
if (accessType === "CHILD") {
|
||||||
conditions.push(
|
conditions.push(
|
||||||
`(${tableAlias}.orgChild2Id IN (SELECT id FROM orgChild2 WHERE orgRevisionId = "${this.currentRevisionId}" AND ancestorDNA LIKE "${dnaIds.dnaChild2Id}%") OR ${tableAlias}.orgChild3Id IN (SELECT id FROM orgChild3 WHERE orgRevisionId = "${this.currentRevisionId}" AND ancestorDNA LIKE "${dnaIds.dnaChild2Id}%") OR ${tableAlias}.orgChild4Id IN (SELECT id FROM orgChild4 WHERE orgRevisionId = "${this.currentRevisionId}" AND ancestorDNA LIKE "${dnaIds.dnaChild2Id}%"))`,
|
`(${tableAlias}.orgChild2Id IN (SELECT id FROM orgChild2 WHERE orgRevisionId = "${this.currentRevisionId}" AND ancestorDNA LIKE "${dnaIds.dnaChild2Id}%") OR ${tableAlias}.orgChild3Id IS NOT NULL)`,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
} else if (dnaIds.dnaChild1Id) {
|
} else if (dnaIds.dnaChild1Id) {
|
||||||
conditions.push(
|
conditions.push(
|
||||||
accessType === "NORMAL"
|
`${tableAlias}.orgChild1Id IN (SELECT id FROM orgChild1 WHERE orgRevisionId = "${this.currentRevisionId}" AND ancestorDNA = "${dnaIds.dnaChild1Id}")`,
|
||||||
? `(${tableAlias}.orgChild1Id IN (SELECT id FROM orgChild1 WHERE orgRevisionId = "${this.currentRevisionId}" AND ancestorDNA = "${dnaIds.dnaChild1Id}") AND ${tableAlias}.orgChild2Id IS NULL AND ${tableAlias}.orgChild3Id IS NULL AND ${tableAlias}.orgChild4Id IS NULL)`
|
|
||||||
: `${tableAlias}.orgChild1Id IN (SELECT id FROM orgChild1 WHERE orgRevisionId = "${this.currentRevisionId}" AND ancestorDNA = "${dnaIds.dnaChild1Id}")`,
|
|
||||||
);
|
);
|
||||||
if (accessType === "CHILD") {
|
if (accessType === "CHILD") {
|
||||||
conditions.push(
|
conditions.push(
|
||||||
`(${tableAlias}.orgChild1Id IN (SELECT id FROM orgChild1 WHERE orgRevisionId = "${this.currentRevisionId}" AND ancestorDNA LIKE "${dnaIds.dnaChild1Id}%") OR ${tableAlias}.orgChild2Id IN (SELECT id FROM orgChild2 WHERE orgRevisionId = "${this.currentRevisionId}" AND ancestorDNA LIKE "${dnaIds.dnaChild1Id}%") OR ${tableAlias}.orgChild3Id IN (SELECT id FROM orgChild3 WHERE orgRevisionId = "${this.currentRevisionId}" AND ancestorDNA LIKE "${dnaIds.dnaChild1Id}%") OR ${tableAlias}.orgChild4Id IN (SELECT id FROM orgChild4 WHERE orgRevisionId = "${this.currentRevisionId}" AND ancestorDNA LIKE "${dnaIds.dnaChild1Id}%"))`,
|
`(${tableAlias}.orgChild1Id IN (SELECT id FROM orgChild1 WHERE orgRevisionId = "${this.currentRevisionId}" AND ancestorDNA LIKE "${dnaIds.dnaChild1Id}%") OR ${tableAlias}.orgChild2Id IS NOT NULL)`,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
} else if (dnaIds.dnaRootId) {
|
} else if (dnaIds.dnaRootId) {
|
||||||
conditions.push(
|
conditions.push(
|
||||||
accessType === "NORMAL"
|
`${tableAlias}.orgRootId IN (SELECT id FROM orgRoot WHERE orgRevisionId = "${this.currentRevisionId}" AND ancestorDNA = "${dnaIds.dnaRootId}")`,
|
||||||
? `(${tableAlias}.orgRootId IN (SELECT id FROM orgRoot WHERE orgRevisionId = "${this.currentRevisionId}" AND ancestorDNA = "${dnaIds.dnaRootId}") AND ${tableAlias}.orgChild1Id IS NULL AND ${tableAlias}.orgChild2Id IS NULL AND ${tableAlias}.orgChild3Id IS NULL AND ${tableAlias}.orgChild4Id IS NULL)`
|
|
||||||
: `${tableAlias}.orgRootId IN (SELECT id FROM orgRoot WHERE orgRevisionId = "${this.currentRevisionId}" AND ancestorDNA = "${dnaIds.dnaRootId}")`,
|
|
||||||
);
|
);
|
||||||
if (accessType === "CHILD") {
|
if (accessType === "CHILD") {
|
||||||
conditions.push(
|
conditions.push(
|
||||||
`(${tableAlias}.orgRootId IN (SELECT id FROM orgRoot WHERE orgRevisionId = "${this.currentRevisionId}" AND ancestorDNA LIKE "${dnaIds.dnaRootId}%") OR ${tableAlias}.orgChild1Id IN (SELECT id FROM orgChild1 WHERE orgRevisionId = "${this.currentRevisionId}" AND ancestorDNA LIKE "${dnaIds.dnaRootId}%") OR ${tableAlias}.orgChild2Id IN (SELECT id FROM orgChild2 WHERE orgRevisionId = "${this.currentRevisionId}" AND ancestorDNA LIKE "${dnaIds.dnaRootId}%") OR ${tableAlias}.orgChild3Id IN (SELECT id FROM orgChild3 WHERE orgRevisionId = "${this.currentRevisionId}" AND ancestorDNA LIKE "${dnaIds.dnaRootId}%") OR ${tableAlias}.orgChild4Id IN (SELECT id FROM orgChild4 WHERE orgRevisionId = "${this.currentRevisionId}" AND ancestorDNA LIKE "${dnaIds.dnaRootId}%"))`,
|
`(${tableAlias}.orgRootId IN (SELECT id FROM orgRoot WHERE orgRevisionId = "${this.currentRevisionId}" AND ancestorDNA LIKE "${dnaIds.dnaRootId}%") OR ${tableAlias}.orgChild1Id IS NOT NULL)`,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
File diff suppressed because it is too large
Load diff
|
|
@ -39,7 +39,8 @@ import { RequestWithUser } from "../middlewares/user";
|
||||||
import permission from "../interfaces/permission";
|
import permission from "../interfaces/permission";
|
||||||
import { setLogDataDiff } from "../interfaces/utils";
|
import { setLogDataDiff } from "../interfaces/utils";
|
||||||
import {
|
import {
|
||||||
CreatePosMasterHistoryEmployee
|
CreatePosMasterHistoryEmployee,
|
||||||
|
CreatePosMasterHistoryOfficer,
|
||||||
} from "../services/PositionService";
|
} from "../services/PositionService";
|
||||||
import { PosMasterEmployeeHistory } from "../entities/PosMasterEmployeeHistory";
|
import { PosMasterEmployeeHistory } from "../entities/PosMasterEmployeeHistory";
|
||||||
import { KeycloakAttributeService } from "../services/KeycloakAttributeService";
|
import { KeycloakAttributeService } from "../services/KeycloakAttributeService";
|
||||||
|
|
@ -2376,47 +2377,26 @@ export class EmployeePositionController extends Controller {
|
||||||
dataMaster.positions.forEach(async (position) => {
|
dataMaster.positions.forEach(async (position) => {
|
||||||
if (position.id === requestBody.position) {
|
if (position.id === requestBody.position) {
|
||||||
position.positionIsSelected = true;
|
position.positionIsSelected = true;
|
||||||
|
const profile = await this.profileRepository.findOne({
|
||||||
|
where: { id: requestBody.profileId },
|
||||||
|
});
|
||||||
|
if (profile != null) {
|
||||||
|
const _null: any = null;
|
||||||
|
profile.posLevelId = position?.posLevelId ?? _null;
|
||||||
|
profile.posTypeId = position?.posTypeId ?? _null;
|
||||||
|
profile.position = position?.positionName ?? _null;
|
||||||
|
await this.profileRepository.save(profile);
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
position.positionIsSelected = false;
|
position.positionIsSelected = false;
|
||||||
}
|
}
|
||||||
await this.employeePositionRepository.save(position);
|
await this.employeePositionRepository.save(position);
|
||||||
});
|
});
|
||||||
const _null: any = null;
|
|
||||||
const before = null;
|
|
||||||
dataMaster.isSit = requestBody.isSit;
|
dataMaster.isSit = requestBody.isSit;
|
||||||
dataMaster.lastUpdatedAt = new Date();
|
|
||||||
|
|
||||||
//เช็คถ้า revision ปัจจุบันให้ปั๊มที่ profile
|
|
||||||
const chkRevision = await this.orgRevisionRepository.findOne({
|
|
||||||
where: { id: dataMaster.orgRevisionId },
|
|
||||||
});
|
|
||||||
if (chkRevision?.orgRevisionIsCurrent) {
|
|
||||||
const _profile = await this.profileRepository.findOne({
|
|
||||||
where: { id: requestBody.profileId },
|
|
||||||
});
|
|
||||||
if (_profile) {
|
|
||||||
let _position = await this.employeePositionRepository.findOne({
|
|
||||||
where: { id: requestBody.position, posMasterId: requestBody.posMaster },
|
|
||||||
});
|
|
||||||
if (_position) {
|
|
||||||
|
|
||||||
// ถ้าไม่ใช่ตำแหน่งนั่งทับ (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;
|
dataMaster.current_holderId = requestBody.profileId;
|
||||||
dataMaster.next_holderId = _null;
|
dataMaster.lastUpdatedAt = new Date();
|
||||||
} else {
|
// dataMaster.next_holderId = requestBody.profileId;
|
||||||
dataMaster.next_holderId = requestBody.profileId;
|
|
||||||
dataMaster.current_holderId = _null;
|
|
||||||
}
|
|
||||||
|
|
||||||
await this.employeePosMasterRepository.save(dataMaster);
|
await this.employeePosMasterRepository.save(dataMaster);
|
||||||
await CreatePosMasterHistoryEmployee(dataMaster.id, request);
|
await CreatePosMasterHistoryEmployee(dataMaster.id, request);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -23,7 +23,7 @@ interface CachedToken {
|
||||||
}
|
}
|
||||||
const API_URL_BANGKOK = "https://exprofile.bangkok.go.th/API";
|
const API_URL_BANGKOK = "https://exprofile.bangkok.go.th/API";
|
||||||
const clientId = "e5f6ad6ce374177eef023bf5d0c018b6";
|
const clientId = "e5f6ad6ce374177eef023bf5d0c018b6";
|
||||||
const clientSecret = "5EhOvN5DwHOKakupqT9FmCk7MOwpT3zLqLPkPh4ZhJpxBN2nMG";
|
const clientSecret = "5EhOvN5DwHOKakupqT9FmCk7MOwpT3zLqLPkPh4ZhJpxBN2nMG@2022";
|
||||||
|
|
||||||
class TokenCache {
|
class TokenCache {
|
||||||
private static cache: Map<string, CachedToken> = new Map();
|
private static cache: Map<string, CachedToken> = new Map();
|
||||||
|
|
@ -43,35 +43,6 @@ class TokenCache {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// network/DNS error codes ที่ถือว่าเป็น transient (ควร retry)
|
|
||||||
const TRANSIENT_NETWORK_CODES = [
|
|
||||||
"EAI_AGAIN", // DNS lookup ล้มเหลวชั่วคราว
|
|
||||||
"ENOTFOUND",
|
|
||||||
"ECONNRESET",
|
|
||||||
"ETIMEDOUT",
|
|
||||||
"ECONNREFUSED",
|
|
||||||
"EHOSTUNREACH",
|
|
||||||
"ENETUNREACH",
|
|
||||||
];
|
|
||||||
|
|
||||||
// ตรวจว่า error ควร retry: ครอบ network/DNS (ไม่มี response หรือ code ตรง TRANSIENT) และ HTTP 5xx
|
|
||||||
function isTransientError(error: any): boolean {
|
|
||||||
if (!error) return false;
|
|
||||||
if (!error.response || TRANSIENT_NETWORK_CODES.includes(error.code)) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
return error.response?.status >= 500;
|
|
||||||
}
|
|
||||||
|
|
||||||
// หน่วงเวลา (backoff) ระหว่าง retry — ใช้รูปแบบ setTimeout เดียวกับ keycloak/index.ts
|
|
||||||
function sleep(ms: number): Promise<void> {
|
|
||||||
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
||||||
}
|
|
||||||
|
|
||||||
// config retry สำหรับเรียก Exprofile
|
|
||||||
const EXPROFILE_MAX_RETRIES = 3; // จากเดิม 2
|
|
||||||
const EXPROFILE_BACKOFF_BASE_MS = 1000; // backoff = base * 2^retryCount → 1s, 2s
|
|
||||||
|
|
||||||
@Route("api/v1/org/ex/retirement")
|
@Route("api/v1/org/ex/retirement")
|
||||||
@Tags("ExRetirement")
|
@Tags("ExRetirement")
|
||||||
@Security("bearerAuth")
|
@Security("bearerAuth")
|
||||||
|
|
@ -89,7 +60,7 @@ export class ExRetirementController extends Controller {
|
||||||
},
|
},
|
||||||
) {
|
) {
|
||||||
let retryCount = 0;
|
let retryCount = 0;
|
||||||
const maxRetries = EXPROFILE_MAX_RETRIES;
|
const maxRetries = 2;
|
||||||
|
|
||||||
while (retryCount < maxRetries) {
|
while (retryCount < maxRetries) {
|
||||||
try {
|
try {
|
||||||
|
|
@ -121,14 +92,11 @@ export class ExRetirementController extends Controller {
|
||||||
// return res.data;
|
// return res.data;
|
||||||
return new HttpSuccess(res.data.data);
|
return new HttpSuccess(res.data.data);
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
if (isTransientError(error) && retryCount < maxRetries - 1) {
|
if (error.response?.status === 500 && retryCount < maxRetries - 1) {
|
||||||
TokenCache.delete(`${clientId}:${clientSecret}`);
|
TokenCache.delete(`${clientId}:${clientSecret}`);
|
||||||
await sleep(EXPROFILE_BACKOFF_BASE_MS * 2 ** retryCount);
|
|
||||||
retryCount++;
|
retryCount++;
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
// log error message
|
|
||||||
console.error('getData getOfficerRetireData error:', error);
|
|
||||||
throw new HttpError(HttpStatusCode.INTERNAL_SERVER_ERROR, "ไม่สามารถติดต่อ API ได้");
|
throw new HttpError(HttpStatusCode.INTERNAL_SERVER_ERROR, "ไม่สามารถติดต่อ API ได้");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -137,7 +105,7 @@ export class ExRetirementController extends Controller {
|
||||||
@Get("/document/{documentId}")
|
@Get("/document/{documentId}")
|
||||||
async getDocument(@Path("documentId") officerDocumentID: string, @Request() req: any) {
|
async getDocument(@Path("documentId") officerDocumentID: string, @Request() req: any) {
|
||||||
let retryCount = 0;
|
let retryCount = 0;
|
||||||
const maxRetries = EXPROFILE_MAX_RETRIES;
|
const maxRetries = 2;
|
||||||
while (retryCount < maxRetries) {
|
while (retryCount < maxRetries) {
|
||||||
try {
|
try {
|
||||||
const token = await getToken(clientId, clientSecret);
|
const token = await getToken(clientId, clientSecret);
|
||||||
|
|
@ -166,13 +134,11 @@ export class ExRetirementController extends Controller {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
if (isTransientError(error) && retryCount < maxRetries - 1) {
|
if (error.response?.status === 500 && retryCount < maxRetries - 1) {
|
||||||
TokenCache.delete(`${clientId}:${clientSecret}`);
|
TokenCache.delete(`${clientId}:${clientSecret}`);
|
||||||
await sleep(EXPROFILE_BACKOFF_BASE_MS * 2 ** retryCount);
|
|
||||||
retryCount++;
|
retryCount++;
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
console.error('getData document error:', error);
|
|
||||||
throw new HttpError(HttpStatusCode.INTERNAL_SERVER_ERROR, "ไม่สามารถติดต่อ API ได้");
|
throw new HttpError(HttpStatusCode.INTERNAL_SERVER_ERROR, "ไม่สามารถติดต่อ API ได้");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -202,13 +168,7 @@ async function getToken(ClientID: string, ClientSecret: string): Promise<string>
|
||||||
TokenCache.set(cacheKey, token);
|
TokenCache.set(cacheKey, token);
|
||||||
return token;
|
return token;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
// log แบบกระชับ (ลด log noise จากการ dump object ใหญ่ตามที่เห็นใน log จริง)
|
return Promise.reject({ message: "Error occurred", error });
|
||||||
console.error(
|
|
||||||
"getToken error:",
|
|
||||||
error instanceof Error ? `${error.name}: ${error.message}` : error,
|
|
||||||
);
|
|
||||||
// โยน AxiosError ตัวจริงออกไปให้ caller อ่าน code / response / status เพื่อตัดสินใจ retry
|
|
||||||
throw error;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -234,7 +194,7 @@ export async function PostRetireToExprofile(
|
||||||
}
|
}
|
||||||
|
|
||||||
let retryCount = 0;
|
let retryCount = 0;
|
||||||
const maxRetries = EXPROFILE_MAX_RETRIES;
|
const maxRetries = 2;
|
||||||
|
|
||||||
while (retryCount < maxRetries) {
|
while (retryCount < maxRetries) {
|
||||||
try {
|
try {
|
||||||
|
|
@ -271,9 +231,8 @@ export async function PostRetireToExprofile(
|
||||||
|
|
||||||
return res.data;
|
return res.data;
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
if (isTransientError(error) && retryCount < maxRetries - 1) {
|
if (error.response?.status === 500 && retryCount < maxRetries - 1) {
|
||||||
TokenCache.delete(`${clientId}:${clientSecret}`);
|
TokenCache.delete(`${clientId}:${clientSecret}`);
|
||||||
await sleep(EXPROFILE_BACKOFF_BASE_MS * 2 ** retryCount);
|
|
||||||
retryCount++;
|
retryCount++;
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
@ -292,7 +251,6 @@ export async function PostRetireToExprofile(
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
console.error('importOfficerRetireData error', error);
|
|
||||||
throw new HttpError(HttpStatusCode.INTERNAL_SERVER_ERROR, "ไม่สามารถติดต่อ API ได้");
|
throw new HttpError(HttpStatusCode.INTERNAL_SERVER_ERROR, "ไม่สามารถติดต่อ API ได้");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -74,11 +74,6 @@ export class OrgChild1Controller {
|
||||||
DIVISION_CODE: orgChild1.DIVISION_CODE,
|
DIVISION_CODE: orgChild1.DIVISION_CODE,
|
||||||
SECTION_CODE: orgChild1.SECTION_CODE,
|
SECTION_CODE: orgChild1.SECTION_CODE,
|
||||||
JOB_CODE: orgChild1.JOB_CODE,
|
JOB_CODE: orgChild1.JOB_CODE,
|
||||||
ROOT_CODE: orgChild1.ROOT_CODE,
|
|
||||||
CHILD1_CODE: orgChild1.CHILD1_CODE,
|
|
||||||
CHILD2_CODE: orgChild1.CHILD2_CODE,
|
|
||||||
CHILD3_CODE: orgChild1.CHILD3_CODE,
|
|
||||||
CHILD4_CODE: orgChild1.CHILD4_CODE,
|
|
||||||
orgCode: orgChild1.orgRoot.orgRootCode + orgChild1.orgChild1Code,
|
orgCode: orgChild1.orgRoot.orgRootCode + orgChild1.orgChild1Code,
|
||||||
};
|
};
|
||||||
return new HttpSuccess(getOrgChild1);
|
return new HttpSuccess(getOrgChild1);
|
||||||
|
|
@ -351,11 +346,6 @@ export class OrgChild1Controller {
|
||||||
DIVISION_CODE: requestBody.DIVISION_CODE != null ? requestBody.DIVISION_CODE : _null,
|
DIVISION_CODE: requestBody.DIVISION_CODE != null ? requestBody.DIVISION_CODE : _null,
|
||||||
SECTION_CODE: requestBody.SECTION_CODE != null ? requestBody.SECTION_CODE : _null,
|
SECTION_CODE: requestBody.SECTION_CODE != null ? requestBody.SECTION_CODE : _null,
|
||||||
JOB_CODE: requestBody.JOB_CODE != null ? requestBody.JOB_CODE : _null,
|
JOB_CODE: requestBody.JOB_CODE != null ? requestBody.JOB_CODE : _null,
|
||||||
ROOT_CODE: requestBody.ROOT_CODE != null ? requestBody.ROOT_CODE : _null,
|
|
||||||
CHILD1_CODE: requestBody.CHILD1_CODE != null ? requestBody.CHILD1_CODE : _null,
|
|
||||||
CHILD2_CODE: requestBody.CHILD2_CODE != null ? requestBody.CHILD2_CODE : _null,
|
|
||||||
CHILD3_CODE: requestBody.CHILD3_CODE != null ? requestBody.CHILD3_CODE : _null,
|
|
||||||
CHILD4_CODE: requestBody.CHILD4_CODE != null ? requestBody.CHILD4_CODE : _null,
|
|
||||||
isOfficer: requestBody.isOfficer,
|
isOfficer: requestBody.isOfficer,
|
||||||
isInformation: requestBody.isInformation,
|
isInformation: requestBody.isInformation,
|
||||||
orgChild1PhoneEx: requestBody.orgChild1PhoneEx,
|
orgChild1PhoneEx: requestBody.orgChild1PhoneEx,
|
||||||
|
|
|
||||||
|
|
@ -85,11 +85,6 @@ export class OrgChild2Controller extends Controller {
|
||||||
DIVISION_CODE: orgChild2.DIVISION_CODE,
|
DIVISION_CODE: orgChild2.DIVISION_CODE,
|
||||||
SECTION_CODE: orgChild2.SECTION_CODE,
|
SECTION_CODE: orgChild2.SECTION_CODE,
|
||||||
JOB_CODE: orgChild2.JOB_CODE,
|
JOB_CODE: orgChild2.JOB_CODE,
|
||||||
ROOT_CODE: orgChild2.ROOT_CODE,
|
|
||||||
CHILD1_CODE: orgChild2.CHILD1_CODE,
|
|
||||||
CHILD2_CODE: orgChild2.CHILD2_CODE,
|
|
||||||
CHILD3_CODE: orgChild2.CHILD3_CODE,
|
|
||||||
CHILD4_CODE: orgChild2.CHILD4_CODE,
|
|
||||||
orgCode: orgChild2.orgRoot.orgRootCode + orgChild2.orgChild2Code,
|
orgCode: orgChild2.orgRoot.orgRootCode + orgChild2.orgChild2Code,
|
||||||
};
|
};
|
||||||
return new HttpSuccess(getOrgChild2);
|
return new HttpSuccess(getOrgChild2);
|
||||||
|
|
@ -257,11 +252,6 @@ export class OrgChild2Controller extends Controller {
|
||||||
DIVISION_CODE: requestBody.DIVISION_CODE != null ? requestBody.DIVISION_CODE : _null,
|
DIVISION_CODE: requestBody.DIVISION_CODE != null ? requestBody.DIVISION_CODE : _null,
|
||||||
SECTION_CODE: requestBody.SECTION_CODE != null ? requestBody.SECTION_CODE : _null,
|
SECTION_CODE: requestBody.SECTION_CODE != null ? requestBody.SECTION_CODE : _null,
|
||||||
JOB_CODE: requestBody.JOB_CODE != null ? requestBody.JOB_CODE : _null,
|
JOB_CODE: requestBody.JOB_CODE != null ? requestBody.JOB_CODE : _null,
|
||||||
ROOT_CODE: requestBody.ROOT_CODE != null ? requestBody.ROOT_CODE : _null,
|
|
||||||
CHILD1_CODE: requestBody.CHILD1_CODE != null ? requestBody.CHILD1_CODE : _null,
|
|
||||||
CHILD2_CODE: requestBody.CHILD2_CODE != null ? requestBody.CHILD2_CODE : _null,
|
|
||||||
CHILD3_CODE: requestBody.CHILD3_CODE != null ? requestBody.CHILD3_CODE : _null,
|
|
||||||
CHILD4_CODE: requestBody.CHILD4_CODE != null ? requestBody.CHILD4_CODE : _null,
|
|
||||||
orgChild2PhoneEx: requestBody.orgChild2PhoneEx,
|
orgChild2PhoneEx: requestBody.orgChild2PhoneEx,
|
||||||
orgChild2PhoneIn: requestBody.orgChild2PhoneIn,
|
orgChild2PhoneIn: requestBody.orgChild2PhoneIn,
|
||||||
orgChild2Fax: requestBody.orgChild2Fax,
|
orgChild2Fax: requestBody.orgChild2Fax,
|
||||||
|
|
|
||||||
|
|
@ -69,11 +69,6 @@ export class OrgChild3Controller {
|
||||||
DIVISION_CODE: orgChild3.DIVISION_CODE,
|
DIVISION_CODE: orgChild3.DIVISION_CODE,
|
||||||
SECTION_CODE: orgChild3.SECTION_CODE,
|
SECTION_CODE: orgChild3.SECTION_CODE,
|
||||||
JOB_CODE: orgChild3.JOB_CODE,
|
JOB_CODE: orgChild3.JOB_CODE,
|
||||||
ROOT_CODE: orgChild3.ROOT_CODE,
|
|
||||||
CHILD1_CODE: orgChild3.CHILD1_CODE,
|
|
||||||
CHILD2_CODE: orgChild3.CHILD2_CODE,
|
|
||||||
CHILD3_CODE: orgChild3.CHILD3_CODE,
|
|
||||||
CHILD4_CODE: orgChild3.CHILD4_CODE,
|
|
||||||
orgCode: orgChild3.orgRoot.orgRootCode + orgChild3.orgChild3Code,
|
orgCode: orgChild3.orgRoot.orgRootCode + orgChild3.orgChild3Code,
|
||||||
};
|
};
|
||||||
return new HttpSuccess(getOrgChild3);
|
return new HttpSuccess(getOrgChild3);
|
||||||
|
|
@ -212,11 +207,6 @@ export class OrgChild3Controller {
|
||||||
DIVISION_CODE: requestBody.DIVISION_CODE != null ? requestBody.DIVISION_CODE : _null,
|
DIVISION_CODE: requestBody.DIVISION_CODE != null ? requestBody.DIVISION_CODE : _null,
|
||||||
SECTION_CODE: requestBody.SECTION_CODE != null ? requestBody.SECTION_CODE : _null,
|
SECTION_CODE: requestBody.SECTION_CODE != null ? requestBody.SECTION_CODE : _null,
|
||||||
JOB_CODE: requestBody.JOB_CODE != null ? requestBody.JOB_CODE : _null,
|
JOB_CODE: requestBody.JOB_CODE != null ? requestBody.JOB_CODE : _null,
|
||||||
ROOT_CODE: requestBody.ROOT_CODE != null ? requestBody.ROOT_CODE : _null,
|
|
||||||
CHILD1_CODE: requestBody.CHILD1_CODE != null ? requestBody.CHILD1_CODE : _null,
|
|
||||||
CHILD2_CODE: requestBody.CHILD2_CODE != null ? requestBody.CHILD2_CODE : _null,
|
|
||||||
CHILD3_CODE: requestBody.CHILD3_CODE != null ? requestBody.CHILD3_CODE : _null,
|
|
||||||
CHILD4_CODE: requestBody.CHILD4_CODE != null ? requestBody.CHILD4_CODE : _null,
|
|
||||||
orgChild3PhoneEx: requestBody.orgChild3PhoneEx,
|
orgChild3PhoneEx: requestBody.orgChild3PhoneEx,
|
||||||
orgChild3PhoneIn: requestBody.orgChild3PhoneIn,
|
orgChild3PhoneIn: requestBody.orgChild3PhoneIn,
|
||||||
orgChild3Fax: requestBody.orgChild3Fax,
|
orgChild3Fax: requestBody.orgChild3Fax,
|
||||||
|
|
|
||||||
|
|
@ -82,11 +82,6 @@ export class OrgChild4Controller extends Controller {
|
||||||
DIVISION_CODE: orgChild4.DIVISION_CODE,
|
DIVISION_CODE: orgChild4.DIVISION_CODE,
|
||||||
SECTION_CODE: orgChild4.SECTION_CODE,
|
SECTION_CODE: orgChild4.SECTION_CODE,
|
||||||
JOB_CODE: orgChild4.JOB_CODE,
|
JOB_CODE: orgChild4.JOB_CODE,
|
||||||
ROOT_CODE: orgChild4.ROOT_CODE,
|
|
||||||
CHILD1_CODE: orgChild4.CHILD1_CODE,
|
|
||||||
CHILD2_CODE: orgChild4.CHILD2_CODE,
|
|
||||||
CHILD3_CODE: orgChild4.CHILD3_CODE,
|
|
||||||
CHILD4_CODE: orgChild4.CHILD4_CODE,
|
|
||||||
orgCode: orgChild4.orgRoot.orgRootCode + orgChild4.orgChild4Code,
|
orgCode: orgChild4.orgRoot.orgRootCode + orgChild4.orgChild4Code,
|
||||||
};
|
};
|
||||||
return new HttpSuccess(getOrgChild4);
|
return new HttpSuccess(getOrgChild4);
|
||||||
|
|
@ -259,11 +254,6 @@ export class OrgChild4Controller extends Controller {
|
||||||
DIVISION_CODE: requestBody.DIVISION_CODE != null ? requestBody.DIVISION_CODE : _null,
|
DIVISION_CODE: requestBody.DIVISION_CODE != null ? requestBody.DIVISION_CODE : _null,
|
||||||
SECTION_CODE: requestBody.SECTION_CODE != null ? requestBody.SECTION_CODE : _null,
|
SECTION_CODE: requestBody.SECTION_CODE != null ? requestBody.SECTION_CODE : _null,
|
||||||
JOB_CODE: requestBody.JOB_CODE != null ? requestBody.JOB_CODE : _null,
|
JOB_CODE: requestBody.JOB_CODE != null ? requestBody.JOB_CODE : _null,
|
||||||
ROOT_CODE: requestBody.ROOT_CODE != null ? requestBody.ROOT_CODE : _null,
|
|
||||||
CHILD1_CODE: requestBody.CHILD1_CODE != null ? requestBody.CHILD1_CODE : _null,
|
|
||||||
CHILD2_CODE: requestBody.CHILD2_CODE != null ? requestBody.CHILD2_CODE : _null,
|
|
||||||
CHILD3_CODE: requestBody.CHILD3_CODE != null ? requestBody.CHILD3_CODE : _null,
|
|
||||||
CHILD4_CODE: requestBody.CHILD4_CODE != null ? requestBody.CHILD4_CODE : _null,
|
|
||||||
orgChild4PhoneEx: requestBody.orgChild4PhoneEx,
|
orgChild4PhoneEx: requestBody.orgChild4PhoneEx,
|
||||||
orgChild4PhoneIn: requestBody.orgChild4PhoneIn,
|
orgChild4PhoneIn: requestBody.orgChild4PhoneIn,
|
||||||
orgChild4Fax: requestBody.orgChild4Fax,
|
orgChild4Fax: requestBody.orgChild4Fax,
|
||||||
|
|
|
||||||
|
|
@ -83,11 +83,6 @@ export class OrgRootController extends Controller {
|
||||||
DIVISION_CODE: orgRoot.DIVISION_CODE,
|
DIVISION_CODE: orgRoot.DIVISION_CODE,
|
||||||
SECTION_CODE: orgRoot.SECTION_CODE,
|
SECTION_CODE: orgRoot.SECTION_CODE,
|
||||||
JOB_CODE: orgRoot.JOB_CODE,
|
JOB_CODE: orgRoot.JOB_CODE,
|
||||||
ROOT_CODE: orgRoot.ROOT_CODE,
|
|
||||||
CHILD1_CODE: orgRoot.CHILD1_CODE,
|
|
||||||
CHILD2_CODE: orgRoot.CHILD2_CODE,
|
|
||||||
CHILD3_CODE: orgRoot.CHILD3_CODE,
|
|
||||||
CHILD4_CODE: orgRoot.CHILD4_CODE,
|
|
||||||
orgCode: orgRoot.orgRootCode + "00",
|
orgCode: orgRoot.orgRootCode + "00",
|
||||||
};
|
};
|
||||||
return new HttpSuccess(getOrgRoot);
|
return new HttpSuccess(getOrgRoot);
|
||||||
|
|
@ -355,11 +350,6 @@ export class OrgRootController extends Controller {
|
||||||
DIVISION_CODE: requestBody.DIVISION_CODE != null ? requestBody.DIVISION_CODE : _null,
|
DIVISION_CODE: requestBody.DIVISION_CODE != null ? requestBody.DIVISION_CODE : _null,
|
||||||
SECTION_CODE: requestBody.SECTION_CODE != null ? requestBody.SECTION_CODE : _null,
|
SECTION_CODE: requestBody.SECTION_CODE != null ? requestBody.SECTION_CODE : _null,
|
||||||
JOB_CODE: requestBody.JOB_CODE != null ? requestBody.JOB_CODE : _null,
|
JOB_CODE: requestBody.JOB_CODE != null ? requestBody.JOB_CODE : _null,
|
||||||
ROOT_CODE: requestBody.ROOT_CODE != null ? requestBody.ROOT_CODE : _null,
|
|
||||||
CHILD1_CODE: requestBody.CHILD1_CODE != null ? requestBody.CHILD1_CODE : _null,
|
|
||||||
CHILD2_CODE: requestBody.CHILD2_CODE != null ? requestBody.CHILD2_CODE : _null,
|
|
||||||
CHILD3_CODE: requestBody.CHILD3_CODE != null ? requestBody.CHILD3_CODE : _null,
|
|
||||||
CHILD4_CODE: requestBody.CHILD4_CODE != null ? requestBody.CHILD4_CODE : _null,
|
|
||||||
});
|
});
|
||||||
await this.orgRootRepository.save(orgRoot, { data: request });
|
await this.orgRootRepository.save(orgRoot, { data: request });
|
||||||
setLogDataDiff(request, { before, after: orgRoot });
|
setLogDataDiff(request, { before, after: orgRoot });
|
||||||
|
|
|
||||||
|
|
@ -1441,14 +1441,7 @@ export class OrganizationController extends Controller {
|
||||||
orgRoot.orgRootCode +
|
orgRoot.orgRootCode +
|
||||||
orgChild1.orgChild1Code +
|
orgChild1.orgChild1Code +
|
||||||
" " +
|
" " +
|
||||||
orgChild1.orgChild1ShortName +
|
orgChild1.orgChild1ShortName,
|
||||||
"/" +
|
|
||||||
orgRoot.orgRootName +
|
|
||||||
" " +
|
|
||||||
orgRoot.orgRootCode +
|
|
||||||
"00" +
|
|
||||||
" " +
|
|
||||||
orgRoot.orgRootShortName,
|
|
||||||
// totalPosition: child1Counts.totalPosition,
|
// totalPosition: child1Counts.totalPosition,
|
||||||
// totalPositionCurrentUse: child1Counts.totalPositionCurrentUse,
|
// totalPositionCurrentUse: child1Counts.totalPositionCurrentUse,
|
||||||
// totalPositionCurrentVacant: child1Counts.totalPositionCurrentVacant,
|
// totalPositionCurrentVacant: child1Counts.totalPositionCurrentVacant,
|
||||||
|
|
@ -1498,21 +1491,7 @@ export class OrganizationController extends Controller {
|
||||||
orgRoot.orgRootCode +
|
orgRoot.orgRootCode +
|
||||||
orgChild2.orgChild2Code +
|
orgChild2.orgChild2Code +
|
||||||
" " +
|
" " +
|
||||||
orgChild2.orgChild2ShortName +
|
orgChild2.orgChild2ShortName,
|
||||||
"/" +
|
|
||||||
orgChild1.orgChild1Name +
|
|
||||||
" " +
|
|
||||||
orgRoot.orgRootCode +
|
|
||||||
orgChild1.orgChild1Code +
|
|
||||||
" " +
|
|
||||||
orgChild1.orgChild1ShortName +
|
|
||||||
"/" +
|
|
||||||
orgRoot.orgRootName +
|
|
||||||
" " +
|
|
||||||
orgRoot.orgRootCode +
|
|
||||||
"00" +
|
|
||||||
" " +
|
|
||||||
orgRoot.orgRootShortName,
|
|
||||||
// totalPosition: child2Counts.totalPosition,
|
// totalPosition: child2Counts.totalPosition,
|
||||||
// totalPositionCurrentUse: child2Counts.totalPositionCurrentUse,
|
// totalPositionCurrentUse: child2Counts.totalPositionCurrentUse,
|
||||||
// totalPositionCurrentVacant: child2Counts.totalPositionCurrentVacant,
|
// totalPositionCurrentVacant: child2Counts.totalPositionCurrentVacant,
|
||||||
|
|
@ -1563,28 +1542,7 @@ export class OrganizationController extends Controller {
|
||||||
orgRoot.orgRootCode +
|
orgRoot.orgRootCode +
|
||||||
orgChild3.orgChild3Code +
|
orgChild3.orgChild3Code +
|
||||||
" " +
|
" " +
|
||||||
orgChild3.orgChild3ShortName +
|
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,
|
|
||||||
// totalPosition: child3Counts.totalPosition,
|
// totalPosition: child3Counts.totalPosition,
|
||||||
// totalPositionCurrentUse: child3Counts.totalPositionCurrentUse,
|
// totalPositionCurrentUse: child3Counts.totalPositionCurrentUse,
|
||||||
// totalPositionCurrentVacant: child3Counts.totalPositionCurrentVacant,
|
// totalPositionCurrentVacant: child3Counts.totalPositionCurrentVacant,
|
||||||
|
|
@ -1637,35 +1595,7 @@ export class OrganizationController extends Controller {
|
||||||
orgRoot.orgRootCode +
|
orgRoot.orgRootCode +
|
||||||
orgChild4.orgChild4Code +
|
orgChild4.orgChild4Code +
|
||||||
" " +
|
" " +
|
||||||
orgChild4.orgChild4ShortName +
|
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,
|
|
||||||
// totalPosition: child4Counts.totalPosition,
|
// totalPosition: child4Counts.totalPosition,
|
||||||
// totalPositionCurrentUse: child4Counts.totalPositionCurrentUse,
|
// totalPositionCurrentUse: child4Counts.totalPositionCurrentUse,
|
||||||
// totalPositionCurrentVacant: child4Counts.totalPositionCurrentVacant,
|
// totalPositionCurrentVacant: child4Counts.totalPositionCurrentVacant,
|
||||||
|
|
@ -1817,11 +1747,6 @@ export class OrganizationController extends Controller {
|
||||||
"orgRoot.DIVISION_CODE",
|
"orgRoot.DIVISION_CODE",
|
||||||
"orgRoot.SECTION_CODE",
|
"orgRoot.SECTION_CODE",
|
||||||
"orgRoot.JOB_CODE",
|
"orgRoot.JOB_CODE",
|
||||||
"orgRoot.ROOT_CODE",
|
|
||||||
"orgRoot.CHILD1_CODE",
|
|
||||||
"orgRoot.CHILD2_CODE",
|
|
||||||
"orgRoot.CHILD3_CODE",
|
|
||||||
"orgRoot.CHILD4_CODE",
|
|
||||||
"orgRoot.responsibility",
|
"orgRoot.responsibility",
|
||||||
])
|
])
|
||||||
.orderBy("orgRoot.orgRootOrder", "ASC")
|
.orderBy("orgRoot.orgRootOrder", "ASC")
|
||||||
|
|
@ -1861,11 +1786,6 @@ export class OrganizationController extends Controller {
|
||||||
"orgChild1.DIVISION_CODE",
|
"orgChild1.DIVISION_CODE",
|
||||||
"orgChild1.SECTION_CODE",
|
"orgChild1.SECTION_CODE",
|
||||||
"orgChild1.JOB_CODE",
|
"orgChild1.JOB_CODE",
|
||||||
"orgChild1.ROOT_CODE",
|
|
||||||
"orgChild1.CHILD1_CODE",
|
|
||||||
"orgChild1.CHILD2_CODE",
|
|
||||||
"orgChild1.CHILD3_CODE",
|
|
||||||
"orgChild1.CHILD4_CODE",
|
|
||||||
"orgChild1.responsibility",
|
"orgChild1.responsibility",
|
||||||
])
|
])
|
||||||
.orderBy("orgChild1.orgChild1Order", "ASC")
|
.orderBy("orgChild1.orgChild1Order", "ASC")
|
||||||
|
|
@ -1905,11 +1825,6 @@ export class OrganizationController extends Controller {
|
||||||
"orgChild2.DIVISION_CODE",
|
"orgChild2.DIVISION_CODE",
|
||||||
"orgChild2.SECTION_CODE",
|
"orgChild2.SECTION_CODE",
|
||||||
"orgChild2.JOB_CODE",
|
"orgChild2.JOB_CODE",
|
||||||
"orgChild2.ROOT_CODE",
|
|
||||||
"orgChild2.CHILD1_CODE",
|
|
||||||
"orgChild2.CHILD2_CODE",
|
|
||||||
"orgChild2.CHILD3_CODE",
|
|
||||||
"orgChild2.CHILD4_CODE",
|
|
||||||
"orgChild2.orgChild1Id",
|
"orgChild2.orgChild1Id",
|
||||||
"orgChild2.responsibility",
|
"orgChild2.responsibility",
|
||||||
])
|
])
|
||||||
|
|
@ -1950,11 +1865,6 @@ export class OrganizationController extends Controller {
|
||||||
"orgChild3.DIVISION_CODE",
|
"orgChild3.DIVISION_CODE",
|
||||||
"orgChild3.SECTION_CODE",
|
"orgChild3.SECTION_CODE",
|
||||||
"orgChild3.JOB_CODE",
|
"orgChild3.JOB_CODE",
|
||||||
"orgChild3.ROOT_CODE",
|
|
||||||
"orgChild3.CHILD1_CODE",
|
|
||||||
"orgChild3.CHILD2_CODE",
|
|
||||||
"orgChild3.CHILD3_CODE",
|
|
||||||
"orgChild3.CHILD4_CODE",
|
|
||||||
"orgChild3.orgChild2Id",
|
"orgChild3.orgChild2Id",
|
||||||
"orgChild3.responsibility",
|
"orgChild3.responsibility",
|
||||||
])
|
])
|
||||||
|
|
@ -1995,11 +1905,6 @@ export class OrganizationController extends Controller {
|
||||||
"orgChild4.DIVISION_CODE",
|
"orgChild4.DIVISION_CODE",
|
||||||
"orgChild4.SECTION_CODE",
|
"orgChild4.SECTION_CODE",
|
||||||
"orgChild4.JOB_CODE",
|
"orgChild4.JOB_CODE",
|
||||||
"orgChild4.ROOT_CODE",
|
|
||||||
"orgChild4.CHILD1_CODE",
|
|
||||||
"orgChild4.CHILD2_CODE",
|
|
||||||
"orgChild4.CHILD3_CODE",
|
|
||||||
"orgChild4.CHILD4_CODE",
|
|
||||||
"orgChild4.orgChild3Id",
|
"orgChild4.orgChild3Id",
|
||||||
"orgChild4.responsibility",
|
"orgChild4.responsibility",
|
||||||
])
|
])
|
||||||
|
|
@ -2025,11 +1930,6 @@ export class OrganizationController extends Controller {
|
||||||
DIVISION_CODE: orgRoot.DIVISION_CODE,
|
DIVISION_CODE: orgRoot.DIVISION_CODE,
|
||||||
SECTION_CODE: orgRoot.SECTION_CODE,
|
SECTION_CODE: orgRoot.SECTION_CODE,
|
||||||
JOB_CODE: orgRoot.JOB_CODE,
|
JOB_CODE: orgRoot.JOB_CODE,
|
||||||
ROOT_CODE: orgRoot.ROOT_CODE,
|
|
||||||
CHILD1_CODE: orgRoot.CHILD1_CODE,
|
|
||||||
CHILD2_CODE: orgRoot.CHILD2_CODE,
|
|
||||||
CHILD3_CODE: orgRoot.CHILD3_CODE,
|
|
||||||
CHILD4_CODE: orgRoot.CHILD4_CODE,
|
|
||||||
orgTreeOrder: orgRoot.orgRootOrder,
|
orgTreeOrder: orgRoot.orgRootOrder,
|
||||||
orgTreePhoneEx: orgRoot.orgRootPhoneEx,
|
orgTreePhoneEx: orgRoot.orgRootPhoneEx,
|
||||||
orgTreePhoneIn: orgRoot.orgRootPhoneIn,
|
orgTreePhoneIn: orgRoot.orgRootPhoneIn,
|
||||||
|
|
@ -2061,11 +1961,6 @@ export class OrganizationController extends Controller {
|
||||||
DIVISION_CODE: orgChild1.DIVISION_CODE,
|
DIVISION_CODE: orgChild1.DIVISION_CODE,
|
||||||
SECTION_CODE: orgChild1.SECTION_CODE,
|
SECTION_CODE: orgChild1.SECTION_CODE,
|
||||||
JOB_CODE: orgChild1.JOB_CODE,
|
JOB_CODE: orgChild1.JOB_CODE,
|
||||||
ROOT_CODE: orgChild1.ROOT_CODE,
|
|
||||||
CHILD1_CODE: orgChild1.CHILD1_CODE,
|
|
||||||
CHILD2_CODE: orgChild1.CHILD2_CODE,
|
|
||||||
CHILD3_CODE: orgChild1.CHILD3_CODE,
|
|
||||||
CHILD4_CODE: orgChild1.CHILD4_CODE,
|
|
||||||
orgTreeOrder: orgChild1.orgChild1Order,
|
orgTreeOrder: orgChild1.orgChild1Order,
|
||||||
orgRootCode: orgRoot.orgRootCode,
|
orgRootCode: orgRoot.orgRootCode,
|
||||||
orgTreePhoneEx: orgChild1.orgChild1PhoneEx,
|
orgTreePhoneEx: orgChild1.orgChild1PhoneEx,
|
||||||
|
|
@ -2110,11 +2005,6 @@ export class OrganizationController extends Controller {
|
||||||
DIVISION_CODE: orgChild2.DIVISION_CODE,
|
DIVISION_CODE: orgChild2.DIVISION_CODE,
|
||||||
SECTION_CODE: orgChild2.SECTION_CODE,
|
SECTION_CODE: orgChild2.SECTION_CODE,
|
||||||
JOB_CODE: orgChild2.JOB_CODE,
|
JOB_CODE: orgChild2.JOB_CODE,
|
||||||
ROOT_CODE: orgChild2.ROOT_CODE,
|
|
||||||
CHILD1_CODE: orgChild2.CHILD1_CODE,
|
|
||||||
CHILD2_CODE: orgChild2.CHILD2_CODE,
|
|
||||||
CHILD3_CODE: orgChild2.CHILD3_CODE,
|
|
||||||
CHILD4_CODE: orgChild2.CHILD4_CODE,
|
|
||||||
orgTreeOrder: orgChild2.orgChild2Order,
|
orgTreeOrder: orgChild2.orgChild2Order,
|
||||||
orgRootCode: orgRoot.orgRootCode,
|
orgRootCode: orgRoot.orgRootCode,
|
||||||
orgTreePhoneEx: orgChild2.orgChild2PhoneEx,
|
orgTreePhoneEx: orgChild2.orgChild2PhoneEx,
|
||||||
|
|
@ -2164,11 +2054,6 @@ export class OrganizationController extends Controller {
|
||||||
DIVISION_CODE: orgChild3.DIVISION_CODE,
|
DIVISION_CODE: orgChild3.DIVISION_CODE,
|
||||||
SECTION_CODE: orgChild3.SECTION_CODE,
|
SECTION_CODE: orgChild3.SECTION_CODE,
|
||||||
JOB_CODE: orgChild3.JOB_CODE,
|
JOB_CODE: orgChild3.JOB_CODE,
|
||||||
ROOT_CODE: orgChild3.ROOT_CODE,
|
|
||||||
CHILD1_CODE: orgChild3.CHILD1_CODE,
|
|
||||||
CHILD2_CODE: orgChild3.CHILD2_CODE,
|
|
||||||
CHILD3_CODE: orgChild3.CHILD3_CODE,
|
|
||||||
CHILD4_CODE: orgChild3.CHILD4_CODE,
|
|
||||||
orgTreeOrder: orgChild3.orgChild3Order,
|
orgTreeOrder: orgChild3.orgChild3Order,
|
||||||
orgRootCode: orgRoot.orgRootCode,
|
orgRootCode: orgRoot.orgRootCode,
|
||||||
orgTreePhoneEx: orgChild3.orgChild3PhoneEx,
|
orgTreePhoneEx: orgChild3.orgChild3PhoneEx,
|
||||||
|
|
@ -2225,11 +2110,6 @@ export class OrganizationController extends Controller {
|
||||||
DIVISION_CODE: orgChild4.DIVISION_CODE,
|
DIVISION_CODE: orgChild4.DIVISION_CODE,
|
||||||
SECTION_CODE: orgChild4.SECTION_CODE,
|
SECTION_CODE: orgChild4.SECTION_CODE,
|
||||||
JOB_CODE: orgChild4.JOB_CODE,
|
JOB_CODE: orgChild4.JOB_CODE,
|
||||||
ROOT_CODE: orgChild4.ROOT_CODE,
|
|
||||||
CHILD1_CODE: orgChild4.CHILD1_CODE,
|
|
||||||
CHILD2_CODE: orgChild4.CHILD2_CODE,
|
|
||||||
CHILD3_CODE: orgChild4.CHILD3_CODE,
|
|
||||||
CHILD4_CODE: orgChild4.CHILD4_CODE,
|
|
||||||
orgTreeOrder: orgChild4.orgChild4Order,
|
orgTreeOrder: orgChild4.orgChild4Order,
|
||||||
orgRootCode: orgRoot.orgRootCode,
|
orgRootCode: orgRoot.orgRootCode,
|
||||||
orgTreePhoneEx: orgChild4.orgChild4PhoneEx,
|
orgTreePhoneEx: orgChild4.orgChild4PhoneEx,
|
||||||
|
|
@ -8682,9 +8562,8 @@ export class OrganizationController extends Controller {
|
||||||
orgChild2Id,
|
orgChild2Id,
|
||||||
orgChild3Id,
|
orgChild3Id,
|
||||||
orgChild4Id,
|
orgChild4Id,
|
||||||
authRoleId: draftPos.authRoleId,
|
|
||||||
current_holderId: draftPos.next_holderId,
|
current_holderId: draftPos.next_holderId,
|
||||||
next_holderId: null,
|
next_holderId: draftPos.next_holderId,
|
||||||
isSit: draftPos.isSit,
|
isSit: draftPos.isSit,
|
||||||
reason: draftPos.reason,
|
reason: draftPos.reason,
|
||||||
isDirector: draftPos.isDirector,
|
isDirector: draftPos.isDirector,
|
||||||
|
|
@ -8713,7 +8592,6 @@ export class OrganizationController extends Controller {
|
||||||
orgChild3Id,
|
orgChild3Id,
|
||||||
orgChild4Id,
|
orgChild4Id,
|
||||||
current_holderId: draftPos.next_holderId,
|
current_holderId: draftPos.next_holderId,
|
||||||
next_holderId: null,
|
|
||||||
statusReport: "DONE",
|
statusReport: "DONE",
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
@ -8823,45 +8701,6 @@ export class OrganizationController extends Controller {
|
||||||
};
|
};
|
||||||
|
|
||||||
await queryRunner.commitTransaction();
|
await queryRunner.commitTransaction();
|
||||||
|
|
||||||
// Clear Redis cache after successful publish (only menu and role for menu display)
|
|
||||||
const redis = require("redis");
|
|
||||||
const { promisify } = require("util");
|
|
||||||
const redisClient = redis.createClient({
|
|
||||||
host: process.env.REDIS_HOST || "localhost",
|
|
||||||
port: parseInt(process.env.REDIS_PORT || "6379"),
|
|
||||||
});
|
|
||||||
const keysAsync = promisify(redisClient.keys).bind(redisClient);
|
|
||||||
const delAsync = promisify(redisClient.del).bind(redisClient);
|
|
||||||
|
|
||||||
try {
|
|
||||||
// Clear only menu and role cache (affects menu display)
|
|
||||||
const menuRolePatterns = ["menu_*", "role_*"];
|
|
||||||
let totalCleared = 0;
|
|
||||||
|
|
||||||
for (const pattern of menuRolePatterns) {
|
|
||||||
const keys = await keysAsync(pattern);
|
|
||||||
if (keys.length > 0) {
|
|
||||||
// Delete in chunks of 1000 to avoid argument limit
|
|
||||||
const chunkSize = 1000;
|
|
||||||
for (let i = 0; i < keys.length; i += chunkSize) {
|
|
||||||
const chunk = keys.slice(i, i + chunkSize);
|
|
||||||
await delAsync(...chunk);
|
|
||||||
}
|
|
||||||
totalCleared += keys.length;
|
|
||||||
console.log(`[moveDraftToCurrent] Cleared ${keys.length} cache keys for pattern: ${pattern}`);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
console.log(`[moveDraftToCurrent] Total cache cleared: ${totalCleared} keys`);
|
|
||||||
} catch (err) {
|
|
||||||
console.error("[moveDraftToCurrent] Error clearing cache:", err);
|
|
||||||
} finally {
|
|
||||||
redisClient.quit();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Invalidate memory cache
|
|
||||||
orgStructureCache.invalidate(currentRevisionId);
|
|
||||||
|
|
||||||
return new HttpSuccess(summary);
|
return new HttpSuccess(summary);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("Error moving draft to current:", error);
|
console.error("Error moving draft to current:", error);
|
||||||
|
|
@ -9145,16 +8984,7 @@ export class OrganizationController extends Controller {
|
||||||
// Fetch draft PosMasters with relations for history tracking
|
// Fetch draft PosMasters with relations for history tracking
|
||||||
const draftPosMasters = await queryRunner.manager.find(PosMaster, {
|
const draftPosMasters = await queryRunner.manager.find(PosMaster, {
|
||||||
where: { id: In(draftPosMasterIds) },
|
where: { id: In(draftPosMasterIds) },
|
||||||
relations: [
|
relations: ["orgRoot", "orgChild1", "orgChild2", "orgChild3", "orgChild4", "next_holder"],
|
||||||
"orgRoot",
|
|
||||||
"orgChild1",
|
|
||||||
"orgChild2",
|
|
||||||
"orgChild3",
|
|
||||||
"orgChild4",
|
|
||||||
"next_holder",
|
|
||||||
"next_holder.posType",
|
|
||||||
"next_holder.posLevel",
|
|
||||||
],
|
|
||||||
});
|
});
|
||||||
|
|
||||||
// Fetch ALL positions for ALL posMasters in just 2 queries
|
// Fetch ALL positions for ALL posMasters in just 2 queries
|
||||||
|
|
@ -9292,10 +9122,8 @@ export class OrganizationController extends Controller {
|
||||||
org: draftPosMaster ? getOrgFullName(draftPosMaster as PosMaster) ?? _null : _null,
|
org: draftPosMaster ? getOrgFullName(draftPosMaster as PosMaster) ?? _null : _null,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
if (nextHolderId != null && draftPos.positionIsSelected) {
|
|
||||||
// ถ้าไม่ใช่ตำแหน่งนั่งทับ (isSit = false) ถึงจะอัพเดทตำแหน่งในทะเบียนประวัติ
|
// ถ้าไม่ใช่ตำแหน่งนั่งทับ (isSit = false) ถึงจะอัพเดทตำแหน่งในทะเบียนประวัติ
|
||||||
if (!draftPosMaster?.isSit) {
|
if (nextHolderId != null && draftPos.positionIsSelected && !draftPosMaster?.isSit) {
|
||||||
const existing = profileUpdates.get(nextHolderId) || {};
|
const existing = profileUpdates.get(nextHolderId) || {};
|
||||||
existing.position = draftPos.positionName;
|
existing.position = draftPos.positionName;
|
||||||
existing.posTypeId = draftPos.posTypeId;
|
existing.posTypeId = draftPos.posTypeId;
|
||||||
|
|
@ -9305,9 +9133,6 @@ export class OrganizationController extends Controller {
|
||||||
existing.positionArea = draftPos.positionArea ?? null;
|
existing.positionArea = draftPos.positionArea ?? null;
|
||||||
existing.positionExecutiveField = draftPos.positionExecutiveField ?? null;
|
existing.positionExecutiveField = draftPos.positionExecutiveField ?? null;
|
||||||
profileUpdates.set(nextHolderId, existing);
|
profileUpdates.set(nextHolderId, existing);
|
||||||
}
|
|
||||||
|
|
||||||
// ยังบันทึกประวัติคนครอง
|
|
||||||
if (draftPosMaster && draftPosMaster.ancestorDNA) {
|
if (draftPosMaster && draftPosMaster.ancestorDNA) {
|
||||||
// Find the selected position from draft positions
|
// Find the selected position from draft positions
|
||||||
const selectedPos =
|
const selectedPos =
|
||||||
|
|
@ -9319,11 +9144,10 @@ export class OrganizationController extends Controller {
|
||||||
prefix: draftPosMaster.next_holder?.prefix ?? null,
|
prefix: draftPosMaster.next_holder?.prefix ?? null,
|
||||||
firstName: draftPosMaster.next_holder?.firstName ?? null,
|
firstName: draftPosMaster.next_holder?.firstName ?? null,
|
||||||
lastName: draftPosMaster.next_holder?.lastName ?? null,
|
lastName: draftPosMaster.next_holder?.lastName ?? null,
|
||||||
// isSit = true ดึงค่าจาก profile
|
position: selectedPos.positionName ?? null,
|
||||||
position: draftPosMaster?.isSit ? draftPosMaster.next_holder?.position ?? null : selectedPos.positionName ?? null,
|
posType: (selectedPos as any).posType?.posTypeName ?? null,
|
||||||
posType: draftPosMaster?.isSit ? draftPosMaster.next_holder?.posType?.posTypeName ?? null : (selectedPos as any).posType?.posTypeName ?? null,
|
posLevel: (selectedPos as any).posLevel?.posLevelName ?? null,
|
||||||
posLevel: draftPosMaster?.isSit ? draftPosMaster.next_holder?.posLevel?.posLevelName ?? null : (selectedPos as any).posLevel?.posLevelName ?? null,
|
posExecutive: (selectedPos as any).posExecutive?.posExecutiveName ?? null,
|
||||||
posExecutive: draftPosMaster?.isSit ? draftPosMaster.next_holder?.posExecutive ?? null : (selectedPos as any).posExecutive?.posExecutiveName ?? null,
|
|
||||||
profileId: nextHolderId,
|
profileId: nextHolderId,
|
||||||
rootDnaId: draftPosMaster.orgRoot?.ancestorDNA ?? null,
|
rootDnaId: draftPosMaster.orgRoot?.ancestorDNA ?? null,
|
||||||
child1DnaId: draftPosMaster.orgChild1?.ancestorDNA ?? null,
|
child1DnaId: draftPosMaster.orgChild1?.ancestorDNA ?? null,
|
||||||
|
|
|
||||||
|
|
@ -2372,7 +2372,7 @@ export class OrganizationDotnetController extends Controller {
|
||||||
@Security("internalAuth")
|
@Security("internalAuth")
|
||||||
async GetProfileForProcessCheckInAsync(@Path() keycloakId: string) {
|
async GetProfileForProcessCheckInAsync(@Path() keycloakId: string) {
|
||||||
try {
|
try {
|
||||||
// console.log(`[check-keycloak] START - keycloakId=${keycloakId}`);
|
console.log(`[check-keycloak] START - keycloakId=${keycloakId}`);
|
||||||
|
|
||||||
/* =========================
|
/* =========================
|
||||||
* 1. Load profile (Officer)
|
* 1. Load profile (Officer)
|
||||||
|
|
@ -2447,14 +2447,14 @@ export class OrganizationDotnetController extends Controller {
|
||||||
child4DnaId: currentHolder?.orgChild4?.ancestorDNA ?? null,
|
child4DnaId: currentHolder?.orgChild4?.ancestorDNA ?? null,
|
||||||
};
|
};
|
||||||
|
|
||||||
// console.log(
|
console.log(
|
||||||
// `[check-keycloak] SUCCESS_EMPLOYEE - keycloakId=${keycloakId}, profileType=EMPLOYEE`,
|
`[check-keycloak] SUCCESS_EMPLOYEE - keycloakId=${keycloakId}, profileType=EMPLOYEE`,
|
||||||
// );
|
);
|
||||||
|
|
||||||
return new HttpSuccess(mapProfile);
|
return new HttpSuccess(mapProfile);
|
||||||
}
|
}
|
||||||
|
|
||||||
// console.log(`[check-keycloak] OFFICER_FOUND - keycloakId=${keycloakId}`);
|
console.log(`[check-keycloak] OFFICER_FOUND - keycloakId=${keycloakId}`);
|
||||||
|
|
||||||
/* =========================================
|
/* =========================================
|
||||||
* 2. current holder (Officer)
|
* 2. current holder (Officer)
|
||||||
|
|
@ -2494,9 +2494,9 @@ export class OrganizationDotnetController extends Controller {
|
||||||
child4DnaId: currentHolder?.orgChild4?.ancestorDNA ?? null,
|
child4DnaId: currentHolder?.orgChild4?.ancestorDNA ?? null,
|
||||||
};
|
};
|
||||||
|
|
||||||
// console.log(
|
console.log(
|
||||||
// `[check-keycloak] SUCCESS_OFFICER - keycloakId=${keycloakId}, profileType=OFFICER`,
|
`[check-keycloak] SUCCESS_OFFICER - keycloakId=${keycloakId}, profileType=OFFICER`,
|
||||||
// );
|
);
|
||||||
|
|
||||||
return new HttpSuccess(mapProfile);
|
return new HttpSuccess(mapProfile);
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
|
|
@ -4792,16 +4792,7 @@ export class OrganizationDotnetController extends Controller {
|
||||||
async getProfileByKeycloak(@Path() keycloakId: string) {
|
async getProfileByKeycloak(@Path() keycloakId: string) {
|
||||||
const profile = await this.profileRepo.findOne({
|
const profile = await this.profileRepo.findOne({
|
||||||
where: { keycloak: keycloakId },
|
where: { keycloak: keycloakId },
|
||||||
relations: [
|
relations: ["posLevel", "posType", "current_holders", "current_holders.orgRoot"],
|
||||||
"posLevel",
|
|
||||||
"posType",
|
|
||||||
"current_holders",
|
|
||||||
"current_holders.orgRoot",
|
|
||||||
"current_holders.orgChild1",
|
|
||||||
"current_holders.orgChild2",
|
|
||||||
"current_holders.orgChild3",
|
|
||||||
"current_holders.orgChild4",
|
|
||||||
],
|
|
||||||
});
|
});
|
||||||
if (!profile) {
|
if (!profile) {
|
||||||
throw new HttpError(HttpStatus.NOT_FOUND, "ไม่พบข้อมูลบุคคลนี้ในระบบ");
|
throw new HttpError(HttpStatus.NOT_FOUND, "ไม่พบข้อมูลบุคคลนี้ในระบบ");
|
||||||
|
|
@ -4816,16 +4807,11 @@ export class OrganizationDotnetController extends Controller {
|
||||||
throw new HttpError(HttpStatus.NOT_FOUND, "ไม่พบแบบร่างโครงสร้าง");
|
throw new HttpError(HttpStatus.NOT_FOUND, "ไม่พบแบบร่างโครงสร้าง");
|
||||||
}
|
}
|
||||||
|
|
||||||
const holder =
|
const root =
|
||||||
profile.current_holders == null
|
profile.current_holders == null ||
|
||||||
|
profile.current_holders.find((x) => x.orgRevisionId == orgRevisionPublish.id)?.orgRoot == null
|
||||||
? null
|
? null
|
||||||
: profile.current_holders.find((x) => x.orgRevisionId == orgRevisionPublish.id) ?? null;
|
: profile.current_holders.find((x) => x.orgRevisionId == orgRevisionPublish.id)?.orgRoot;
|
||||||
|
|
||||||
const root = holder?.orgRoot ?? null;
|
|
||||||
const child1 = holder?.orgChild1 ?? null;
|
|
||||||
const child2 = holder?.orgChild2 ?? null;
|
|
||||||
const child3 = holder?.orgChild3 ?? null;
|
|
||||||
const child4 = holder?.orgChild4 ?? null;
|
|
||||||
|
|
||||||
const _profile: any = {
|
const _profile: any = {
|
||||||
profileId: profile.id,
|
profileId: profile.id,
|
||||||
|
|
@ -4842,10 +4828,6 @@ export class OrganizationDotnetController extends Controller {
|
||||||
root: root == null ? null : root.orgRootName,
|
root: root == null ? null : root.orgRootName,
|
||||||
rootShortName: root == null ? null : root.orgRootShortName,
|
rootShortName: root == null ? null : root.orgRootShortName,
|
||||||
rootDnaId: root == null ? null : root.ancestorDNA,
|
rootDnaId: root == null ? null : root.ancestorDNA,
|
||||||
child1DnaId: child1 == null ? null : child1.ancestorDNA,
|
|
||||||
child2DnaId: child2 == null ? null : child2.ancestorDNA,
|
|
||||||
child3DnaId: child3 == null ? null : child3.ancestorDNA,
|
|
||||||
child4DnaId: child4 == null ? null : child4.ancestorDNA,
|
|
||||||
};
|
};
|
||||||
return new HttpSuccess(_profile);
|
return new HttpSuccess(_profile);
|
||||||
}
|
}
|
||||||
|
|
@ -7245,9 +7227,7 @@ export class OrganizationDotnetController extends Controller {
|
||||||
dateStart: profileEmp?.dateStart ?? null,
|
dateStart: profileEmp?.dateStart ?? null,
|
||||||
dateAppoint: profileEmp?.dateAppoint ?? null,
|
dateAppoint: profileEmp?.dateAppoint ?? null,
|
||||||
keycloak: profileEmp?.keycloak ?? null,
|
keycloak: profileEmp?.keycloak ?? null,
|
||||||
posNo: `${item.shortName} ${[item.posMasterNoPrefix, item.posMasterNo, item.posMasterNoSuffix]
|
posNo: `${item.shortName} ${item.posMasterNo}`,
|
||||||
.filter((p) => p !== null && p !== undefined && p !== "")
|
|
||||||
.join(" ")}`,
|
|
||||||
position: item.position,
|
position: item.position,
|
||||||
positionLevel: item.posLevel,
|
positionLevel: item.posLevel,
|
||||||
positionType: item.posType,
|
positionType: item.posType,
|
||||||
|
|
@ -8920,7 +8900,7 @@ export class OrganizationDotnetController extends Controller {
|
||||||
|
|
||||||
const profiles = await this.profileRepo.find({
|
const profiles = await this.profileRepo.find({
|
||||||
where: { id: In(profileIds) },
|
where: { id: In(profileIds) },
|
||||||
select: ["id", "citizenId", "dateStart", "dateAppoint", "keycloak", "posMasterNo"],
|
select: ["id", "citizenId", "dateStart", "dateAppoint", "keycloak"],
|
||||||
});
|
});
|
||||||
|
|
||||||
const profileMap = new Map(profiles.map((p) => [p.id, p]));
|
const profileMap = new Map(profiles.map((p) => [p.id, p]));
|
||||||
|
|
@ -8938,11 +8918,7 @@ export class OrganizationDotnetController extends Controller {
|
||||||
dateStart: profile?.dateStart ?? null,
|
dateStart: profile?.dateStart ?? null,
|
||||||
dateAppoint: profile?.dateAppoint ?? null,
|
dateAppoint: profile?.dateAppoint ?? null,
|
||||||
keycloak: profile?.keycloak ?? null,
|
keycloak: profile?.keycloak ?? null,
|
||||||
posNo:
|
posNo: `${item.shortName} ${item.posMasterNo}`,
|
||||||
profile?.posMasterNo ??
|
|
||||||
`${item.shortName} ${[item.posMasterNoPrefix, item.posMasterNo, item.posMasterNoSuffix]
|
|
||||||
.filter((p) => p !== null && p !== undefined && p !== "")
|
|
||||||
.join(" ")}`,
|
|
||||||
position: item.position,
|
position: item.position,
|
||||||
positionLevel: item.posLevel,
|
positionLevel: item.posLevel,
|
||||||
positionType: item.posType,
|
positionType: item.posType,
|
||||||
|
|
@ -9175,43 +9151,4 @@ export class OrganizationDotnetController extends Controller {
|
||||||
});
|
});
|
||||||
return new HttpSuccess(filteredPosMasters);
|
return new HttpSuccess(filteredPosMasters);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* API ตรวจสอบสถานะผู้สมัครสอบ
|
|
||||||
* @summary API ตรวจสอบสถานะผู้สมัครสอบ
|
|
||||||
*/
|
|
||||||
@Post("check-isLeave")
|
|
||||||
@Security("internalAuth")
|
|
||||||
async findProfileIsLeave(
|
|
||||||
@Body()
|
|
||||||
req: { citizenIds: string[] }
|
|
||||||
) {
|
|
||||||
|
|
||||||
const profiles = await this.profileRepo.find({
|
|
||||||
select: {
|
|
||||||
id: true,
|
|
||||||
citizenId: true,
|
|
||||||
isLeave: true,
|
|
||||||
isActive: true
|
|
||||||
},
|
|
||||||
where: {
|
|
||||||
citizenId: In(req.citizenIds)
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
if (profiles.length === 0) {
|
|
||||||
return new HttpSuccess([]);
|
|
||||||
}
|
|
||||||
|
|
||||||
return new HttpSuccess(
|
|
||||||
profiles.map(p => ({
|
|
||||||
citizenId: p.citizenId,
|
|
||||||
profileId: p.id,
|
|
||||||
isLeave: p.isLeave ?? false,
|
|
||||||
isActive: p.isActive ?? false
|
|
||||||
}))
|
|
||||||
);
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -810,7 +810,7 @@ export class PermissionController extends Controller {
|
||||||
});
|
});
|
||||||
const getAsync = promisify(redisClient.get).bind(redisClient);
|
const getAsync = promisify(redisClient.get).bind(redisClient);
|
||||||
|
|
||||||
let org = await this.PermissionOrg(request, system, action);
|
let org = this.PermissionOrg(request, system, action);
|
||||||
let reply = await getAsync("user_" + id);
|
let reply = await getAsync("user_" + id);
|
||||||
if (reply != null) {
|
if (reply != null) {
|
||||||
reply = JSON.parse(reply);
|
reply = JSON.parse(reply);
|
||||||
|
|
|
||||||
|
|
@ -65,7 +65,7 @@ export class PermissionProfileController extends Controller {
|
||||||
|
|
||||||
if (!request.user.role.includes("SUPER_ADMIN")) {
|
if (!request.user.role.includes("SUPER_ADMIN")) {
|
||||||
rootId =
|
rootId =
|
||||||
orgRevisionActive?.posMasters?.filter((x) => x.current_holderId == profile.id)[0]
|
orgRevisionActive?.posMasters?.filter((x) => x.next_holderId == profile.id)[0]
|
||||||
?.orgRootId || null;
|
?.orgRootId || null;
|
||||||
if (!rootId) return new HttpSuccess([]);
|
if (!rootId) return new HttpSuccess([]);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -325,33 +325,16 @@ export class PosMasterActController extends Controller {
|
||||||
// ลบ Redis cache ของคนที่เป็น acting
|
// ลบ Redis cache ของคนที่เป็น acting
|
||||||
if (posMasterAct != null && posMasterAct.posMasterChild?.current_holderId) {
|
if (posMasterAct != null && posMasterAct.posMasterChild?.current_holderId) {
|
||||||
const profileId = posMasterAct.posMasterChild.current_holderId;
|
const profileId = posMasterAct.posMasterChild.current_holderId;
|
||||||
const redisClient = this.redis.createClient({
|
const redisClient = await this.redis.createClient({
|
||||||
host: REDIS_HOST,
|
host: REDIS_HOST,
|
||||||
port: REDIS_PORT,
|
port: REDIS_PORT,
|
||||||
});
|
});
|
||||||
|
|
||||||
const delAsync = promisify(redisClient.del).bind(redisClient);
|
const delAsync = promisify(redisClient.del).bind(redisClient);
|
||||||
const quitAsync = promisify(redisClient.quit).bind(redisClient);
|
await delAsync("role_" + profileId);
|
||||||
try {
|
await delAsync("menu_" + profileId);
|
||||||
const [roleDeleted, menuDeleted] = await Promise.all([
|
|
||||||
delAsync("role_" + profileId),
|
|
||||||
delAsync("menu_" + profileId),
|
|
||||||
]);
|
|
||||||
|
|
||||||
if (roleDeleted === 0) {
|
redisClient.quit();
|
||||||
console.warn(`[PosMasterActController] Redis key not found: role_${profileId}`);
|
|
||||||
}
|
|
||||||
if (menuDeleted === 0) {
|
|
||||||
console.warn(`[PosMasterActController] Redis key not found: menu_${profileId}`);
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
console.error(
|
|
||||||
`[PosMasterActController] Redis delete error for profile ${profileId}:`,
|
|
||||||
error,
|
|
||||||
);
|
|
||||||
} finally {
|
|
||||||
await quitAsync();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return new HttpSuccess();
|
return new HttpSuccess();
|
||||||
|
|
@ -638,13 +621,13 @@ export class PosMasterActController extends Controller {
|
||||||
let conditionGroup = "";
|
let conditionGroup = "";
|
||||||
if (body.type.trim().toUpperCase() == "GROUP1.1") {
|
if (body.type.trim().toUpperCase() == "GROUP1.1") {
|
||||||
conditionGroup =
|
conditionGroup =
|
||||||
"(posType.posTypeName = 'ทั่วไป' AND posLevel.posLevelName = 'ชำนาญงาน') OR (posType.posTypeName = 'ทั่วไป' AND posLevel.posLevelName = 'ปฏิบัติงาน') OR (posType.posTypeName = 'วิชาการ' AND posLevel.posLevelName = 'ปฏิบัติการ') OR (posType.posTypeName = 'วิชาการ' AND posLevel.posLevelName = 'ชำนาญการ')";
|
"(posTypeAct.posTypeName = 'ทั่วไป' AND posLevelAct.posLevelName = 'ชำนาญงาน') OR (posTypeAct.posTypeName = 'ทั่วไป' AND posLevelAct.posLevelName = 'ปฏิบัติงาน') OR (posTypeAct.posTypeName = 'วิชาการ' AND posLevelAct.posLevelName = 'ปฏิบัติการ') OR (posTypeAct.posTypeName = 'วิชาการ' AND posLevelAct.posLevelName = 'ชำนาญการ')";
|
||||||
} else if (body.type.trim().toUpperCase() == "GROUP1.2") {
|
} else if (body.type.trim().toUpperCase() == "GROUP1.2") {
|
||||||
conditionGroup =
|
conditionGroup =
|
||||||
"(posType.posTypeName = 'ทั่วไป' AND posLevel.posLevelName = 'อาวุโส') OR (posType.posTypeName = 'วิชาการ' AND posLevel.posLevelName = 'ชำนาญการพิเศษ') OR (posType.posTypeName = 'อำนวยการ' AND posLevel.posLevelName = 'ต้น')";
|
"(posTypeAct.posTypeName = 'ทั่วไป' AND posLevelAct.posLevelName = 'อาวุโส') OR (posTypeAct.posTypeName = 'วิชาการ' AND posLevelAct.posLevelName = 'ชำนาญการพิเศษ') OR (posTypeAct.posTypeName = 'อำนวยการ' AND posLevelAct.posLevelName = 'ต้น')";
|
||||||
} else if (body.type.trim().toUpperCase() == "GROUP2") {
|
} else if (body.type.trim().toUpperCase() == "GROUP2") {
|
||||||
conditionGroup =
|
conditionGroup =
|
||||||
"(posType.posTypeName = 'ทั่วไป' AND posLevel.posLevelName = 'ทักษะพิเศษ') OR (posType.posTypeName = 'วิชาการ' AND posLevel.posLevelName = 'เชี่ยวชาญ') OR (posType.posTypeName = 'วิชาการ' AND posLevel.posLevelName = 'ทรงคุณวุฒิ') OR (posType.posTypeName = 'อำนวยการ' AND posLevel.posLevelName = 'สูง') OR (posType.posTypeName = 'บริหาร' AND posLevel.posLevelName = 'ต้น') OR (posType.posTypeName = 'บริหาร' AND posLevel.posLevelName = 'สูง')";
|
"(posTypeAct.posTypeName = 'ทั่วไป' AND posLevelAct.posLevelName = 'ทักษะพิเศษ') OR (posTypeAct.posTypeName = 'วิชาการ' AND posLevelAct.posLevelName = 'เชี่ยวชาญ') OR (posTypeAct.posTypeName = 'วิชาการ' AND posLevelAct.posLevelName = 'ทรงคุณวุฒิ') OR (posTypeAct.posTypeName = 'อำนวยการ' AND posLevelAct.posLevelName = 'สูง') OR (posTypeAct.posTypeName = 'บริหาร' AND posLevelAct.posLevelName = 'ต้น') OR (posTypeAct.posTypeName = 'บริหาร' AND posLevelAct.posLevelName = 'สูง')";
|
||||||
} else {
|
} else {
|
||||||
throw new HttpError(HttpStatusCode.NOT_FOUND, "กลุ่มเป้าหมายไม่ถูกต้อง");
|
throw new HttpError(HttpStatusCode.NOT_FOUND, "กลุ่มเป้าหมายไม่ถูกต้อง");
|
||||||
}
|
}
|
||||||
|
|
@ -885,28 +868,10 @@ export class PosMasterActController extends Controller {
|
||||||
});
|
});
|
||||||
|
|
||||||
const delAsync = promisify(redisClient.del).bind(redisClient);
|
const delAsync = promisify(redisClient.del).bind(redisClient);
|
||||||
const quitAsync = promisify(redisClient.quit).bind(redisClient);
|
await delAsync("role_" + profileId);
|
||||||
|
await delAsync("menu_" + profileId);
|
||||||
|
|
||||||
try {
|
redisClient.quit();
|
||||||
const [roleDeleted, menuDeleted] = await Promise.all([
|
|
||||||
delAsync("role_" + profileId),
|
|
||||||
delAsync("menu_" + profileId),
|
|
||||||
]);
|
|
||||||
|
|
||||||
if (roleDeleted === 0) {
|
|
||||||
console.warn(`[PosMasterActController] Redis key not found: role_${profileId}`);
|
|
||||||
}
|
|
||||||
if (menuDeleted === 0) {
|
|
||||||
console.warn(`[PosMasterActController] Redis key not found: menu_${profileId}`);
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
console.error(
|
|
||||||
`[PosMasterActController] Redis delete error for profile ${profileId}:`,
|
|
||||||
error,
|
|
||||||
);
|
|
||||||
} finally {
|
|
||||||
await quitAsync();
|
|
||||||
}
|
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -4242,7 +4242,6 @@ export class PositionController extends Controller {
|
||||||
|
|
||||||
const [posMaster, total] = await AppDataSource.getRepository(PosMaster)
|
const [posMaster, total] = await AppDataSource.getRepository(PosMaster)
|
||||||
.createQueryBuilder("posMaster")
|
.createQueryBuilder("posMaster")
|
||||||
.leftJoinAndSelect("posMaster.orgRevision", "orgRevision")
|
|
||||||
.leftJoinAndSelect("posMaster.orgRoot", "orgRoot")
|
.leftJoinAndSelect("posMaster.orgRoot", "orgRoot")
|
||||||
.leftJoinAndSelect("posMaster.orgChild1", "orgChild1")
|
.leftJoinAndSelect("posMaster.orgChild1", "orgChild1")
|
||||||
.leftJoinAndSelect("posMaster.orgChild2", "orgChild2")
|
.leftJoinAndSelect("posMaster.orgChild2", "orgChild2")
|
||||||
|
|
@ -4254,8 +4253,6 @@ export class PositionController extends Controller {
|
||||||
.leftJoinAndSelect("positions.posType", "posType")
|
.leftJoinAndSelect("positions.posType", "posType")
|
||||||
.leftJoinAndSelect("positions.posLevel", "posLevel")
|
.leftJoinAndSelect("positions.posLevel", "posLevel")
|
||||||
.leftJoinAndSelect("positions.posExecutive", "posExecutive")
|
.leftJoinAndSelect("positions.posExecutive", "posExecutive")
|
||||||
.andWhere("orgRevision.orgRevisionIsCurrent = true")
|
|
||||||
.andWhere("orgRevision.orgRevisionIsDraft = false")
|
|
||||||
.andWhere(
|
.andWhere(
|
||||||
new Brackets((qb) => {
|
new Brackets((qb) => {
|
||||||
qb.andWhere(typeCondition).andWhere(conditionA == null ? "1=1" : conditionA, {
|
qb.andWhere(typeCondition).andWhere(conditionA == null ? "1=1" : conditionA, {
|
||||||
|
|
@ -4434,7 +4431,6 @@ export class PositionController extends Controller {
|
||||||
typeCommand: string | null;
|
typeCommand: string | null;
|
||||||
posType?: string | null;
|
posType?: string | null;
|
||||||
posLevel?: string | null;
|
posLevel?: string | null;
|
||||||
profileId?: string | null;
|
|
||||||
isAll: boolean;
|
isAll: boolean;
|
||||||
isBlank: boolean;
|
isBlank: boolean;
|
||||||
},
|
},
|
||||||
|
|
@ -4476,13 +4472,9 @@ export class PositionController extends Controller {
|
||||||
posLevel: posLevel?.id,
|
posLevel: posLevel?.id,
|
||||||
};
|
};
|
||||||
} else if (body.typeCommand == "APPOINT") {
|
} else if (body.typeCommand == "APPOINT") {
|
||||||
// เดิม : กรองเฉพาะ posTypeRank ที่สูงกว่า
|
conditionA = "posType.posTypeRank > :posTypeRank";
|
||||||
// conditionA = "posType.posTypeRank > :posTypeRank";
|
|
||||||
// ใหม่ : กรองเฉพาะ posType ที่สูงหรือต่ำกว่าก็ได้
|
|
||||||
conditionA = "positions.posTypeId != :currentPosType";
|
|
||||||
params = {
|
params = {
|
||||||
// posTypeRank: posType?.posTypeRank ?? 0,
|
posTypeRank: posType?.posTypeRank ?? 0,
|
||||||
currentPosType: posType?.id,
|
|
||||||
};
|
};
|
||||||
} else if (body.typeCommand == "SLIP") {
|
} else if (body.typeCommand == "SLIP") {
|
||||||
conditionA = "positions.posTypeId LIKE :posType AND posLevel.posLevelRank > :posLevelRank";
|
conditionA = "positions.posTypeId LIKE :posType AND posLevel.posLevelRank > :posLevelRank";
|
||||||
|
|
@ -4546,13 +4538,8 @@ export class PositionController extends Controller {
|
||||||
typeCondition.current_holderId = IsNull();
|
typeCondition.current_holderId = IsNull();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (body.typeCommand === "MOVE" && body.profileId && !body.isBlank) {
|
|
||||||
typeCondition.current_holderId = Not(body.profileId);
|
|
||||||
}
|
|
||||||
|
|
||||||
const [posMaster, total] = await AppDataSource.getRepository(PosMaster)
|
const [posMaster, total] = await AppDataSource.getRepository(PosMaster)
|
||||||
.createQueryBuilder("posMaster")
|
.createQueryBuilder("posMaster")
|
||||||
.leftJoinAndSelect("posMaster.orgRevision", "orgRevision")
|
|
||||||
.leftJoinAndSelect("posMaster.orgRoot", "orgRoot")
|
.leftJoinAndSelect("posMaster.orgRoot", "orgRoot")
|
||||||
.leftJoinAndSelect("posMaster.orgChild1", "orgChild1")
|
.leftJoinAndSelect("posMaster.orgChild1", "orgChild1")
|
||||||
.leftJoinAndSelect("posMaster.orgChild2", "orgChild2")
|
.leftJoinAndSelect("posMaster.orgChild2", "orgChild2")
|
||||||
|
|
@ -4565,8 +4552,6 @@ export class PositionController extends Controller {
|
||||||
.leftJoinAndSelect("positions.posLevel", "posLevel")
|
.leftJoinAndSelect("positions.posLevel", "posLevel")
|
||||||
.leftJoinAndSelect("positions.posExecutive", "posExecutive")
|
.leftJoinAndSelect("positions.posExecutive", "posExecutive")
|
||||||
.andWhere("posMaster.next_holderId IS NULL")
|
.andWhere("posMaster.next_holderId IS NULL")
|
||||||
.andWhere("orgRevision.orgRevisionIsCurrent = true")
|
|
||||||
.andWhere("orgRevision.orgRevisionIsDraft = false")
|
|
||||||
.andWhere(
|
.andWhere(
|
||||||
new Brackets((qb) => {
|
new Brackets((qb) => {
|
||||||
qb.andWhere(typeCondition)
|
qb.andWhere(typeCondition)
|
||||||
|
|
|
||||||
|
|
@ -111,12 +111,8 @@ export class ProfileChangeNameController extends Controller {
|
||||||
setLogDataDiff(req, { before, after: history });
|
setLogDataDiff(req, { before, after: history });
|
||||||
profile.firstName = body.firstName ?? profile.firstName;
|
profile.firstName = body.firstName ?? profile.firstName;
|
||||||
profile.lastName = body.lastName ?? profile.lastName;
|
profile.lastName = body.lastName ?? profile.lastName;
|
||||||
// profile.prefix = body.prefix ?? profile.prefix; //old
|
profile.prefix = body.prefix ?? profile.prefix;
|
||||||
profile.rank = body.rank ?? profile.rank;
|
profile.prefixMain = profile.rank ?? profile.prefix;
|
||||||
// profile.prefixMain = profile.rank ?? profile.prefix; // old
|
|
||||||
profile.prefixMain = body.prefix ?? profile.prefix;
|
|
||||||
profile.prefix = body.rank && body.rank.length > 0 ? body.rank : body.prefix ?? profile.prefix;
|
|
||||||
|
|
||||||
await this.profileRepository.save(profile, { data: req });
|
await this.profileRepository.save(profile, { data: req });
|
||||||
setLogDataDiff(req, { before, after: profile });
|
setLogDataDiff(req, { before, after: profile });
|
||||||
|
|
||||||
|
|
@ -187,11 +183,8 @@ export class ProfileChangeNameController extends Controller {
|
||||||
if (profile && chkLastRecord.id === record.id) {
|
if (profile && chkLastRecord.id === record.id) {
|
||||||
profile.firstName = body.firstName ?? profile.firstName;
|
profile.firstName = body.firstName ?? profile.firstName;
|
||||||
profile.lastName = body.lastName ?? profile.lastName;
|
profile.lastName = body.lastName ?? profile.lastName;
|
||||||
// profile.prefix = body.prefix ?? profile.prefix; //old
|
profile.prefix = body.prefix ?? profile.prefix;
|
||||||
profile.rank = body.rank ?? profile.rank;
|
profile.prefixMain = profile.rank ?? profile.prefix;
|
||||||
// profile.prefixMain = profile.rank ?? profile.prefix; // old
|
|
||||||
profile.prefixMain = body.prefix ?? profile.prefix;
|
|
||||||
profile.prefix = body.rank && body.rank.length > 0 ? body.rank : body.prefix ?? profile.prefix;
|
|
||||||
await this.profileRepository.save(profile, { data: req });
|
await this.profileRepository.save(profile, { data: req });
|
||||||
setLogDataDiff(req, { before: before_profile, after: profile });
|
setLogDataDiff(req, { before: before_profile, after: profile });
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -117,11 +117,8 @@ export class ProfileChangeNameEmployeeController extends Controller {
|
||||||
|
|
||||||
profile.firstName = body.firstName ?? profile.firstName;
|
profile.firstName = body.firstName ?? profile.firstName;
|
||||||
profile.lastName = body.lastName ?? profile.lastName;
|
profile.lastName = body.lastName ?? profile.lastName;
|
||||||
// profile.prefix = body.prefix ?? profile.prefix; //old
|
profile.prefix = body.prefix ?? profile.prefix;
|
||||||
profile.rank = body.rank ?? profile.rank;
|
profile.prefixMain = profile.rank ?? profile.prefix;
|
||||||
// profile.prefixMain = profile.rank ?? profile.prefix; // old
|
|
||||||
profile.prefixMain = body.prefix ?? profile.prefix;
|
|
||||||
profile.prefix = body.rank && body.rank.length > 0 ? body.rank : body.prefix ?? profile.prefix;
|
|
||||||
await this.profileEmployeeRepo.save(profile, { data: req });
|
await this.profileEmployeeRepo.save(profile, { data: req });
|
||||||
setLogDataDiff(req, { before, after: profile });
|
setLogDataDiff(req, { before, after: profile });
|
||||||
|
|
||||||
|
|
@ -193,11 +190,8 @@ export class ProfileChangeNameEmployeeController extends Controller {
|
||||||
if (profile && chkLastRecord.id === record.id) {
|
if (profile && chkLastRecord.id === record.id) {
|
||||||
profile.firstName = body.firstName ?? profile.firstName;
|
profile.firstName = body.firstName ?? profile.firstName;
|
||||||
profile.lastName = body.lastName ?? profile.lastName;
|
profile.lastName = body.lastName ?? profile.lastName;
|
||||||
// profile.prefix = body.prefix ?? profile.prefix; //old
|
profile.prefix = body.prefix ?? profile.prefix;
|
||||||
profile.rank = body.rank ?? profile.rank;
|
profile.prefixMain = profile.rank ?? profile.prefix;
|
||||||
// profile.prefixMain = profile.rank ?? profile.prefix; // old
|
|
||||||
profile.prefixMain = body.prefix ?? profile.prefix;
|
|
||||||
profile.prefix = body.rank && body.rank.length > 0 ? body.rank : body.prefix ?? profile.prefix;
|
|
||||||
await this.profileEmployeeRepo.save(profile);
|
await this.profileEmployeeRepo.save(profile);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -108,11 +108,8 @@ export class ProfileChangeNameEmployeeTempController extends Controller {
|
||||||
|
|
||||||
profile.firstName = body.firstName ?? profile.firstName;
|
profile.firstName = body.firstName ?? profile.firstName;
|
||||||
profile.lastName = body.lastName ?? profile.lastName;
|
profile.lastName = body.lastName ?? profile.lastName;
|
||||||
// profile.prefix = body.prefix ?? profile.prefix; //old
|
profile.prefix = body.prefix ?? profile.prefix;
|
||||||
profile.rank = body.rank ?? profile.rank;
|
profile.prefixMain = profile.rank ?? profile.prefix;
|
||||||
// profile.prefixMain = profile.rank ?? profile.prefix; // old
|
|
||||||
profile.prefixMain = body.prefix ?? profile.prefix;
|
|
||||||
profile.prefix = body.rank && body.rank.length > 0 ? body.rank : body.prefix ?? profile.prefix;
|
|
||||||
await this.profileEmployeeRepo.save(profile, { data: req });
|
await this.profileEmployeeRepo.save(profile, { data: req });
|
||||||
setLogDataDiff(req, { before, after: profile });
|
setLogDataDiff(req, { before, after: profile });
|
||||||
|
|
||||||
|
|
@ -181,11 +178,8 @@ export class ProfileChangeNameEmployeeTempController extends Controller {
|
||||||
if (profile && chkLastRecord.id === record.id) {
|
if (profile && chkLastRecord.id === record.id) {
|
||||||
profile.firstName = body.firstName ?? profile.firstName;
|
profile.firstName = body.firstName ?? profile.firstName;
|
||||||
profile.lastName = body.lastName ?? profile.lastName;
|
profile.lastName = body.lastName ?? profile.lastName;
|
||||||
// profile.prefix = body.prefix ?? profile.prefix; //old
|
profile.prefix = body.prefix ?? profile.prefix;
|
||||||
profile.rank = body.rank ?? profile.rank;
|
profile.prefixMain = profile.rank ?? profile.prefix;
|
||||||
// profile.prefixMain = profile.rank ?? profile.prefix; // old
|
|
||||||
profile.prefixMain = body.prefix ?? profile.prefix;
|
|
||||||
profile.prefix = body.rank && body.rank.length > 0 ? body.rank : body.prefix ?? profile.prefix;
|
|
||||||
await this.profileEmployeeRepo.save(profile);
|
await this.profileEmployeeRepo.save(profile);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -5779,30 +5779,13 @@ export class ProfileController extends Controller {
|
||||||
}
|
}
|
||||||
const record = await this.profileRepo.findOneBy({ id });
|
const record = await this.profileRepo.findOneBy({ id });
|
||||||
const before = structuredClone(record);
|
const before = structuredClone(record);
|
||||||
// เช็คว่ามี profileHistory ของ profile นี้หรือไม่
|
|
||||||
const historyCount = await this.profileHistoryRepo.count({
|
|
||||||
where: { profileId: id },
|
|
||||||
});
|
|
||||||
|
|
||||||
// ถ้าไม่มีเลย ให้บันทึกข้อมูลเริ่มต้น (ก่อน update) ลงไปก่อน
|
|
||||||
if (historyCount === 0) {
|
|
||||||
await this.profileHistoryRepo.save(
|
|
||||||
Object.assign(new ProfileHistory(), {
|
|
||||||
...before,
|
|
||||||
birthDateOld: before?.birthDate,
|
|
||||||
profileId: id,
|
|
||||||
id: undefined,
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!record) throw new HttpError(HttpStatus.NOT_FOUND, "ไม่พบข้อมูลโปรไฟล์นี้");
|
if (!record) throw new HttpError(HttpStatus.NOT_FOUND, "ไม่พบข้อมูลโปรไฟล์นี้");
|
||||||
|
|
||||||
Object.assign(record, body);
|
Object.assign(record, body);
|
||||||
record.dateRetireLaw = calculateRetireLaw(record.birthDate);
|
record.dateRetireLaw = calculateRetireLaw(record.birthDate);
|
||||||
record.prefixMain = record.prefix;
|
record.prefixMain = record.prefix;
|
||||||
// record.prefix = record.rank && record.rank.length > 0 ? record.rank : record.prefixMain;
|
record.prefix = record.rank && record.rank.length > 0 ? record.rank : record.prefixMain;
|
||||||
record.prefix = record.rank && record.rank.length > 0 ? record.rank : record.prefix;
|
|
||||||
record.createdUserId = request.user.sub;
|
record.createdUserId = request.user.sub;
|
||||||
record.createdFullName = request.user.name;
|
record.createdFullName = request.user.name;
|
||||||
record.createdAt = new Date();
|
record.createdAt = new Date();
|
||||||
|
|
@ -7162,7 +7145,6 @@ export class ProfileController extends Controller {
|
||||||
: `profile.isLeave IS TRUE`
|
: `profile.isLeave IS TRUE`
|
||||||
: "1=1",
|
: "1=1",
|
||||||
)
|
)
|
||||||
.andWhere("profile.isActive IS TRUE AND profile.isDelete IS FALSE")
|
|
||||||
.andWhere(nodeCondition, { nodeId: nodeId })
|
.andWhere(nodeCondition, { nodeId: nodeId })
|
||||||
.andWhere(
|
.andWhere(
|
||||||
new Brackets((qb) => {
|
new Brackets((qb) => {
|
||||||
|
|
@ -7953,38 +7935,40 @@ export class ProfileController extends Controller {
|
||||||
privacyUser: profile.privacyUser,
|
privacyUser: profile.privacyUser,
|
||||||
privacyMgt: profile.privacyMgt,
|
privacyMgt: profile.privacyMgt,
|
||||||
isDeputy: root?.isDeputy ?? false,
|
isDeputy: root?.isDeputy ?? false,
|
||||||
|
// root?.orgRootShortName && posMaster?.posMasterNo
|
||||||
|
// ? `${root?.orgRootShortName} ${posMaster?.posMasterNo}`
|
||||||
|
// : "",
|
||||||
};
|
};
|
||||||
const _numPart = posMaster ? [posMaster.posMasterNoPrefix, posMaster.posMasterNo, posMaster.posMasterNoSuffix].filter((p) => p !== null && p !== undefined && p !== '').join(' ') : '';
|
|
||||||
if (_profile.child4Id != null) {
|
if (_profile.child4Id != null) {
|
||||||
_profile.node = 4;
|
_profile.node = 4;
|
||||||
_profile.nodeId = _profile.child4Id;
|
_profile.nodeId = _profile.child4Id;
|
||||||
_profile.nodeDnaId = _profile.child4DnaId;
|
_profile.nodeDnaId = _profile.child4DnaId;
|
||||||
_profile.nodeShortName = _profile.child4ShortName;
|
_profile.nodeShortName = _profile.child4ShortName;
|
||||||
_profile.posNo = `${_profile.child4ShortName} ${_numPart}`;
|
_profile.posNo = `${_profile.child4ShortName} ${_profile.posMasterNo}`;
|
||||||
} else if (_profile.child3Id != null) {
|
} else if (_profile.child3Id != null) {
|
||||||
_profile.node = 3;
|
_profile.node = 3;
|
||||||
_profile.nodeId = _profile.child3Id;
|
_profile.nodeId = _profile.child3Id;
|
||||||
_profile.nodeDnaId = _profile.child3DnaId;
|
_profile.nodeDnaId = _profile.child3DnaId;
|
||||||
_profile.nodeShortName = _profile.child3ShortName;
|
_profile.nodeShortName = _profile.child3ShortName;
|
||||||
_profile.posNo = `${_profile.child3ShortName} ${_numPart}`;
|
_profile.posNo = `${_profile.child3ShortName} ${_profile.posMasterNo}`;
|
||||||
} else if (_profile.child2Id != null) {
|
} else if (_profile.child2Id != null) {
|
||||||
_profile.node = 2;
|
_profile.node = 2;
|
||||||
_profile.nodeId = _profile.child2Id;
|
_profile.nodeId = _profile.child2Id;
|
||||||
_profile.nodeDnaId = _profile.child2DnaId;
|
_profile.nodeDnaId = _profile.child2DnaId;
|
||||||
_profile.nodeShortName = _profile.child2ShortName;
|
_profile.nodeShortName = _profile.child2ShortName;
|
||||||
_profile.posNo = `${_profile.child2ShortName} ${_numPart}`;
|
_profile.posNo = `${_profile.child2ShortName} ${_profile.posMasterNo}`;
|
||||||
} else if (_profile.child1Id != null) {
|
} else if (_profile.child1Id != null) {
|
||||||
_profile.node = 1;
|
_profile.node = 1;
|
||||||
_profile.nodeId = _profile.child1Id;
|
_profile.nodeId = _profile.child1Id;
|
||||||
_profile.nodeDnaId = _profile.child1DnaId;
|
_profile.nodeDnaId = _profile.child1DnaId;
|
||||||
_profile.nodeShortName = _profile.child1ShortName;
|
_profile.nodeShortName = _profile.child1ShortName;
|
||||||
_profile.posNo = `${_profile.child1ShortName} ${_numPart}`;
|
_profile.posNo = `${_profile.child1ShortName} ${_profile.posMasterNo}`;
|
||||||
} else if (_profile.rootId != null) {
|
} else if (_profile.rootId != null) {
|
||||||
_profile.node = 0;
|
_profile.node = 0;
|
||||||
_profile.nodeId = _profile.rootId;
|
_profile.nodeId = _profile.rootId;
|
||||||
_profile.nodeDnaId = _profile.rootDnaId;
|
_profile.nodeDnaId = _profile.rootDnaId;
|
||||||
_profile.nodeShortName = _profile.rootShortName;
|
_profile.nodeShortName = _profile.rootShortName;
|
||||||
_profile.posNo = `${_profile.rootShortName} ${_numPart}`;
|
_profile.posNo = `${_profile.rootShortName} ${_profile.posMasterNo}`;
|
||||||
}
|
}
|
||||||
return new HttpSuccess(_profile);
|
return new HttpSuccess(_profile);
|
||||||
}
|
}
|
||||||
|
|
@ -8124,39 +8108,41 @@ export class ProfileController extends Controller {
|
||||||
privacyUser: profile.privacyUser,
|
privacyUser: profile.privacyUser,
|
||||||
privacyMgt: profile.privacyMgt,
|
privacyMgt: profile.privacyMgt,
|
||||||
isDeputy: root?.isDeputy ?? false,
|
isDeputy: root?.isDeputy ?? false,
|
||||||
|
// root?.orgRootShortName && posMaster?.posMasterNo
|
||||||
|
// ? `${root?.orgRootShortName} ${posMaster?.posMasterNo}`
|
||||||
|
// : "",
|
||||||
};
|
};
|
||||||
|
|
||||||
const _numPart = posMaster ? [posMaster.posMasterNoPrefix, posMaster.posMasterNo, posMaster.posMasterNoSuffix].filter((p) => p !== null && p !== undefined && p !== '').join(' ') : '';
|
|
||||||
if (_profile.child4Id != null) {
|
if (_profile.child4Id != null) {
|
||||||
_profile.node = 4;
|
_profile.node = 4;
|
||||||
_profile.nodeId = _profile.child4Id;
|
_profile.nodeId = _profile.child4Id;
|
||||||
_profile.nodeDnaId = _profile.child4DnaId;
|
_profile.nodeDnaId = _profile.child4DnaId;
|
||||||
_profile.nodeShortName = _profile.child4ShortName;
|
_profile.nodeShortName = _profile.child4ShortName;
|
||||||
_profile.posNo = `${_profile.child4ShortName} ${_numPart}`;
|
_profile.posNo = `${_profile.child4ShortName} ${posMaster?.posMasterNo}`;
|
||||||
} else if (_profile.child3Id != null) {
|
} else if (_profile.child3Id != null) {
|
||||||
_profile.node = 3;
|
_profile.node = 3;
|
||||||
_profile.nodeId = _profile.child3Id;
|
_profile.nodeId = _profile.child3Id;
|
||||||
_profile.nodeDnaId = _profile.child3DnaId;
|
_profile.nodeDnaId = _profile.child3DnaId;
|
||||||
_profile.nodeShortName = _profile.child3ShortName;
|
_profile.nodeShortName = _profile.child3ShortName;
|
||||||
_profile.posNo = `${_profile.child3ShortName} ${_numPart}`;
|
_profile.posNo = `${_profile.child3ShortName} ${posMaster?.posMasterNo}`;
|
||||||
} else if (_profile.child2Id != null) {
|
} else if (_profile.child2Id != null) {
|
||||||
_profile.node = 2;
|
_profile.node = 2;
|
||||||
_profile.nodeId = _profile.child2Id;
|
_profile.nodeId = _profile.child2Id;
|
||||||
_profile.nodeDnaId = _profile.child2DnaId;
|
_profile.nodeDnaId = _profile.child2DnaId;
|
||||||
_profile.nodeShortName = _profile.child2ShortName;
|
_profile.nodeShortName = _profile.child2ShortName;
|
||||||
_profile.posNo = `${_profile.child2ShortName} ${_numPart}`;
|
_profile.posNo = `${_profile.child2ShortName} ${posMaster?.posMasterNo}`;
|
||||||
} else if (_profile.child1Id != null) {
|
} else if (_profile.child1Id != null) {
|
||||||
_profile.node = 1;
|
_profile.node = 1;
|
||||||
_profile.nodeId = _profile.child1Id;
|
_profile.nodeId = _profile.child1Id;
|
||||||
_profile.nodeDnaId = _profile.child1DnaId;
|
_profile.nodeDnaId = _profile.child1DnaId;
|
||||||
_profile.nodeShortName = _profile.child1ShortName;
|
_profile.nodeShortName = _profile.child1ShortName;
|
||||||
_profile.posNo = `${_profile.child1ShortName} ${_numPart}`;
|
_profile.posNo = `${_profile.child1ShortName} ${posMaster?.posMasterNo}`;
|
||||||
} else if (_profile.rootId != null) {
|
} else if (_profile.rootId != null) {
|
||||||
_profile.node = 0;
|
_profile.node = 0;
|
||||||
_profile.nodeId = _profile.rootId;
|
_profile.nodeId = _profile.rootId;
|
||||||
_profile.nodeDnaId = _profile.rootDnaId;
|
_profile.nodeDnaId = _profile.rootDnaId;
|
||||||
_profile.nodeShortName = _profile.rootShortName;
|
_profile.nodeShortName = _profile.rootShortName;
|
||||||
_profile.posNo = `${_profile.rootShortName} ${_numPart}`;
|
_profile.posNo = `${_profile.rootShortName} ${posMaster?.posMasterNo}`;
|
||||||
}
|
}
|
||||||
return new HttpSuccess(_profile);
|
return new HttpSuccess(_profile);
|
||||||
}
|
}
|
||||||
|
|
@ -8726,10 +8712,14 @@ export class ProfileController extends Controller {
|
||||||
"current_holders.orgChild2",
|
"current_holders.orgChild2",
|
||||||
"current_holders.orgChild3",
|
"current_holders.orgChild3",
|
||||||
"current_holders.orgChild4",
|
"current_holders.orgChild4",
|
||||||
|
// "profileSalary",
|
||||||
"profileEducations",
|
"profileEducations",
|
||||||
"profileActpositions",
|
"profileActpositions",
|
||||||
],
|
],
|
||||||
order: {
|
order: {
|
||||||
|
// profileSalary: {
|
||||||
|
// order: "DESC",
|
||||||
|
// },
|
||||||
profileEducations: {
|
profileEducations: {
|
||||||
level: "ASC",
|
level: "ASC",
|
||||||
},
|
},
|
||||||
|
|
@ -8794,6 +8784,72 @@ export class ProfileController extends Controller {
|
||||||
});
|
});
|
||||||
const holder = profile.current_holders.find((x) => x.orgRevisionId == orgRevisionPublish.id);
|
const holder = profile.current_holders.find((x) => x.orgRevisionId == orgRevisionPublish.id);
|
||||||
const numPart = holder ? [holder.posMasterNoPrefix, holder.posMasterNo, holder.posMasterNoSuffix].filter((p) => p !== null && p !== undefined && p !== '').join(' ') : '';
|
const numPart = holder ? [holder.posMasterNoPrefix, holder.posMasterNo, holder.posMasterNoSuffix].filter((p) => p !== null && p !== undefined && p !== '').join(' ') : '';
|
||||||
|
const shortName =
|
||||||
|
holder == null
|
||||||
|
? null
|
||||||
|
: holder.orgChild4 != null
|
||||||
|
? `${holder.orgChild4.orgChild4ShortName} ${numPart}`
|
||||||
|
: holder.orgChild3 != null
|
||||||
|
? `${holder.orgChild3.orgChild3ShortName} ${numPart}`
|
||||||
|
: holder.orgChild2 != null
|
||||||
|
? `${holder.orgChild2.orgChild2ShortName} ${numPart}`
|
||||||
|
: holder.orgChild1 != null
|
||||||
|
? `${holder.orgChild1.orgChild1ShortName} ${numPart}`
|
||||||
|
: holder.orgRoot != null
|
||||||
|
? `${holder.orgRoot.orgRootShortName} ${numPart}`
|
||||||
|
: null;
|
||||||
|
// const posMasterActs = await this.posMasterActRepository.find({
|
||||||
|
// relations: [
|
||||||
|
// "posMaster",
|
||||||
|
// "posMaster.orgRoot",
|
||||||
|
// "posMaster.orgChild1",
|
||||||
|
// "posMaster.orgChild2",
|
||||||
|
// "posMaster.orgChild3",
|
||||||
|
// "posMaster.orgChild4",
|
||||||
|
// "posMaster.current_holder",
|
||||||
|
// "posMaster.current_holder.posLevel",
|
||||||
|
// "posMaster.current_holder.posType",
|
||||||
|
// ],
|
||||||
|
// where: {
|
||||||
|
// posMaster: {
|
||||||
|
// orgRevisionId: orgRevisionPublish.id,
|
||||||
|
// },
|
||||||
|
// posMasterChild: {
|
||||||
|
// current_holderId: profile.id,
|
||||||
|
// },
|
||||||
|
// },
|
||||||
|
// });
|
||||||
|
// const data = await Promise.all(
|
||||||
|
// posMasterActs
|
||||||
|
// .sort((a, b) => a.posMaster.posMasterOrder - b.posMaster.posMasterOrder)
|
||||||
|
// .map((item) => {
|
||||||
|
// const shortName =
|
||||||
|
// item.posMaster != null && item.posMaster.orgChild4 != null
|
||||||
|
// ? `${item.posMaster.orgChild4.orgChild4ShortName} ${item.posMaster.posMasterNo}`
|
||||||
|
// : item.posMaster != null && item.posMaster?.orgChild3 != null
|
||||||
|
// ? `${item.posMaster.orgChild3.orgChild3ShortName} ${item.posMaster.posMasterNo}`
|
||||||
|
// : item.posMaster != null && item.posMaster?.orgChild2 != null
|
||||||
|
// ? `${item.posMaster.orgChild2.orgChild2ShortName} ${item.posMaster.posMasterNo}`
|
||||||
|
// : item.posMaster != null && item.posMaster?.orgChild1 != null
|
||||||
|
// ? `${item.posMaster.orgChild1.orgChild1ShortName} ${item.posMaster.posMasterNo}`
|
||||||
|
// : item.posMaster != null && item.posMaster?.orgRoot != null
|
||||||
|
// ? `${item.posMaster.orgRoot.orgRootShortName} ${item.posMaster.posMasterNo}`
|
||||||
|
// : null;
|
||||||
|
// return {
|
||||||
|
// id: item.id,
|
||||||
|
// posMasterOrder: item.posMasterOrder,
|
||||||
|
// profileId: item.posMaster?.current_holder?.id ?? null,
|
||||||
|
// citizenId: item.posMaster?.current_holder?.citizenId ?? null,
|
||||||
|
// prefix: item.posMaster?.current_holder?.prefix ?? null,
|
||||||
|
// firstName: item.posMaster?.current_holder?.firstName ?? null,
|
||||||
|
// lastName: item.posMaster?.current_holder?.lastName ?? null,
|
||||||
|
// posLevel: item.posMaster?.current_holder?.posLevel?.posLevelName ?? null,
|
||||||
|
// posType: item.posMaster?.current_holder?.posType?.posTypeName ?? null,
|
||||||
|
// position: item.posMaster?.current_holder?.position ?? null,
|
||||||
|
// posNo: shortName,
|
||||||
|
// };
|
||||||
|
// }),
|
||||||
|
// );
|
||||||
const data = await Promise.all(
|
const data = await Promise.all(
|
||||||
profile.profileActpositions
|
profile.profileActpositions
|
||||||
.filter((x) => x.status)
|
.filter((x) => x.status)
|
||||||
|
|
@ -8840,96 +8896,6 @@ export class ProfileController extends Controller {
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
let _Org = null;
|
|
||||||
let _PosNo = null;
|
|
||||||
if (!profile.org && !profile.posMasterNo) {
|
|
||||||
if (profile.isLeave) {
|
|
||||||
const profileWithSalary = await this.profileRepo.findOne({
|
|
||||||
where: {
|
|
||||||
id: id,
|
|
||||||
profileSalary: {
|
|
||||||
commandCode: In([
|
|
||||||
"0",
|
|
||||||
"9",
|
|
||||||
"1",
|
|
||||||
"2",
|
|
||||||
"3",
|
|
||||||
"4",
|
|
||||||
"8",
|
|
||||||
"10",
|
|
||||||
"11",
|
|
||||||
"12",
|
|
||||||
"13",
|
|
||||||
"14",
|
|
||||||
"15",
|
|
||||||
"16",
|
|
||||||
"20",
|
|
||||||
]),
|
|
||||||
},
|
|
||||||
},
|
|
||||||
relations: { profileSalary: true },
|
|
||||||
order: {
|
|
||||||
profileSalary: {
|
|
||||||
order: "DESC",
|
|
||||||
createdAt: "DESC",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
});
|
|
||||||
const profileSalaryList = profileWithSalary?.profileSalary || [];
|
|
||||||
|
|
||||||
if (profileSalaryList.length > 0) {
|
|
||||||
const _profileSalary =
|
|
||||||
profile.leaveType == "RETIRE"
|
|
||||||
? profileSalaryList.length > 1
|
|
||||||
? profileSalaryList[1]
|
|
||||||
: profileSalaryList[0]
|
|
||||||
: profileSalaryList[0];
|
|
||||||
|
|
||||||
if (_profileSalary) {
|
|
||||||
const orgLeaveParts = [
|
|
||||||
_profileSalary.orgChild4 ?? null,
|
|
||||||
_profileSalary.orgChild3 ?? null,
|
|
||||||
_profileSalary.orgChild2 ?? null,
|
|
||||||
_profileSalary.orgChild1 ?? null,
|
|
||||||
_profileSalary.orgRoot ?? null,
|
|
||||||
];
|
|
||||||
_Org = orgLeaveParts
|
|
||||||
.filter((x: any) => x !== undefined && x !== null)
|
|
||||||
.join("\n");
|
|
||||||
_PosNo = `${_profileSalary.posNoAbb} ${_profileSalary.posNo}`;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
_Org = [
|
|
||||||
child4?.orgChild4Name,
|
|
||||||
child3?.orgChild3Name,
|
|
||||||
child2?.orgChild2Name,
|
|
||||||
child1?.orgChild1Name,
|
|
||||||
root?.orgRootName,
|
|
||||||
]
|
|
||||||
.filter((x) => x != null && x !== "")
|
|
||||||
.join("\n");
|
|
||||||
|
|
||||||
_PosNo =
|
|
||||||
holder == null
|
|
||||||
? null
|
|
||||||
: holder.orgChild4 != null
|
|
||||||
? `${holder.orgChild4.orgChild4ShortName} ${numPart}`
|
|
||||||
: holder.orgChild3 != null
|
|
||||||
? `${holder.orgChild3.orgChild3ShortName} ${numPart}`
|
|
||||||
: holder.orgChild2 != null
|
|
||||||
? `${holder.orgChild2.orgChild2ShortName} ${numPart}`
|
|
||||||
: holder.orgChild1 != null
|
|
||||||
? `${holder.orgChild1.orgChild1ShortName} ${numPart}`
|
|
||||||
: holder.orgRoot != null
|
|
||||||
? `${holder.orgRoot.orgRootShortName} ${numPart}`
|
|
||||||
: null;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
_Org = profile.org;
|
|
||||||
_PosNo = profile.posMasterNo;
|
|
||||||
}
|
|
||||||
|
|
||||||
const _profile: any = {
|
const _profile: any = {
|
||||||
profileId: profile.id,
|
profileId: profile.id,
|
||||||
prefix: profile.prefix,
|
prefix: profile.prefix,
|
||||||
|
|
@ -8981,8 +8947,7 @@ export class ProfileController extends Controller {
|
||||||
node: null,
|
node: null,
|
||||||
nodeId: null,
|
nodeId: null,
|
||||||
nodeDnaId: null,
|
nodeDnaId: null,
|
||||||
posNo: _PosNo,
|
posNo: shortName,
|
||||||
org: _Org,
|
|
||||||
isPosmasterAct: data.length > 0,
|
isPosmasterAct: data.length > 0,
|
||||||
posmasterAct: data,
|
posmasterAct: data,
|
||||||
salary: profile ? profile.amount : null,
|
salary: profile ? profile.amount : null,
|
||||||
|
|
@ -9348,32 +9313,26 @@ export class ProfileController extends Controller {
|
||||||
: "-",
|
: "-",
|
||||||
};
|
};
|
||||||
|
|
||||||
const _numPart = posMaster ? [posMaster.posMasterNoPrefix, posMaster.posMasterNo, posMaster.posMasterNoSuffix].filter((p) => p !== null && p !== undefined && p !== '').join(' ') : '';
|
|
||||||
if (_profile.child4Id != null) {
|
if (_profile.child4Id != null) {
|
||||||
_profile.node = 4;
|
_profile.node = 4;
|
||||||
_profile.nodeId = _profile.child4Id;
|
_profile.nodeId = _profile.child4Id;
|
||||||
_profile.nodeShortName = _profile.child4ShortName;
|
_profile.nodeShortName = _profile.child4ShortName;
|
||||||
_profile.posNo = `${_profile.child4ShortName} ${_numPart}`;
|
|
||||||
} else if (_profile.child3Id != null) {
|
} else if (_profile.child3Id != null) {
|
||||||
_profile.node = 3;
|
_profile.node = 3;
|
||||||
_profile.nodeId = _profile.child3Id;
|
_profile.nodeId = _profile.child3Id;
|
||||||
_profile.nodeShortName = _profile.child3ShortName;
|
_profile.nodeShortName = _profile.child3ShortName;
|
||||||
_profile.posNo = `${_profile.child3ShortName} ${_numPart}`;
|
|
||||||
} else if (_profile.child2Id != null) {
|
} else if (_profile.child2Id != null) {
|
||||||
_profile.node = 2;
|
_profile.node = 2;
|
||||||
_profile.nodeId = _profile.child2Id;
|
_profile.nodeId = _profile.child2Id;
|
||||||
_profile.nodeShortName = _profile.child2ShortName;
|
_profile.nodeShortName = _profile.child2ShortName;
|
||||||
_profile.posNo = `${_profile.child2ShortName} ${_numPart}`;
|
|
||||||
} else if (_profile.child1Id != null) {
|
} else if (_profile.child1Id != null) {
|
||||||
_profile.node = 1;
|
_profile.node = 1;
|
||||||
_profile.nodeId = _profile.child1Id;
|
_profile.nodeId = _profile.child1Id;
|
||||||
_profile.nodeShortName = _profile.child1ShortName;
|
_profile.nodeShortName = _profile.child1ShortName;
|
||||||
_profile.posNo = `${_profile.child1ShortName} ${_numPart}`;
|
|
||||||
} else if (_profile.rootId != null) {
|
} else if (_profile.rootId != null) {
|
||||||
_profile.node = 0;
|
_profile.node = 0;
|
||||||
_profile.nodeId = _profile.rootId;
|
_profile.nodeId = _profile.rootId;
|
||||||
_profile.nodeShortName = _profile.rootShortName;
|
_profile.nodeShortName = _profile.rootShortName;
|
||||||
_profile.posNo = `${_profile.rootShortName} ${_numPart}`;
|
|
||||||
}
|
}
|
||||||
return new HttpSuccess(_profile);
|
return new HttpSuccess(_profile);
|
||||||
}
|
}
|
||||||
|
|
@ -9554,28 +9513,38 @@ export class ProfileController extends Controller {
|
||||||
const mapDataProfile = await Promise.all(
|
const mapDataProfile = await Promise.all(
|
||||||
findProfile.map(async (item: Profile) => {
|
findProfile.map(async (item: Profile) => {
|
||||||
const fullName = `${item.prefix}${item.firstName} ${item.lastName}`;
|
const fullName = `${item.prefix}${item.firstName} ${item.lastName}`;
|
||||||
const holder = item.current_holders?.find((x) => x.orgRevisionId == findRevision.id);
|
const shortName =
|
||||||
const _numPart = holder ? [holder.posMasterNoPrefix, holder.posMasterNo, holder.posMasterNoSuffix].filter((p) => p !== null && p !== undefined && p !== '').join(' ') : '';
|
item.current_holders.length == 0
|
||||||
const shortName = !holder
|
|
||||||
? null
|
? null
|
||||||
: holder.orgChild4 != null
|
: item.current_holders.find((x) => x.orgRevisionId == findRevision.id) != null &&
|
||||||
? `${holder.orgChild4.orgChild4ShortName} ${_numPart}`
|
item.current_holders.find((x) => x.orgRevisionId == findRevision.id)?.orgChild4 !=
|
||||||
: holder.orgChild3 != null
|
null
|
||||||
? `${holder.orgChild3.orgChild3ShortName} ${_numPart}`
|
? `${item.current_holders.find((x) => x.orgRevisionId == findRevision.id)?.orgChild4.orgChild4ShortName} ${item.current_holders.find((x) => x.orgRevisionId == findRevision.id)?.posMasterNo}`
|
||||||
: holder.orgChild2 != null
|
: item.current_holders.find((x) => x.orgRevisionId == findRevision.id) != null &&
|
||||||
? `${holder.orgChild2.orgChild2ShortName} ${_numPart}`
|
item.current_holders.find((x) => x.orgRevisionId == findRevision.id)?.orgChild3 !=
|
||||||
: holder.orgChild1 != null
|
null
|
||||||
? `${holder.orgChild1.orgChild1ShortName} ${_numPart}`
|
? `${item.current_holders.find((x) => x.orgRevisionId == findRevision.id)?.orgChild3.orgChild3ShortName} ${item.current_holders.find((x) => x.orgRevisionId == findRevision.id)?.posMasterNo}`
|
||||||
: holder.orgRoot != null
|
: item.current_holders.find((x) => x.orgRevisionId == findRevision.id) != null &&
|
||||||
? `${holder.orgRoot.orgRootShortName} ${_numPart}`
|
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;
|
: null;
|
||||||
|
|
||||||
const root =
|
const root =
|
||||||
item.current_holders.length == 0 ||
|
item.current_holders.length == 0 ||
|
||||||
(holder != null &&
|
(item.current_holders.find((x) => x.orgRevisionId == findRevision.id) != null &&
|
||||||
holder?.orgRoot == null)
|
item.current_holders.find((x) => x.orgRevisionId == findRevision.id)?.orgRoot == null)
|
||||||
? null
|
? null
|
||||||
: holder?.orgRoot;
|
: item.current_holders.find((x) => x.orgRevisionId == findRevision.id)?.orgRoot;
|
||||||
|
|
||||||
const rootHolder = item.current_holders?.find(
|
const rootHolder = item.current_holders?.find(
|
||||||
(x) => x.orgRevisionId == findRevision.id,
|
(x) => x.orgRevisionId == findRevision.id,
|
||||||
|
|
@ -11451,10 +11420,11 @@ export class ProfileController extends Controller {
|
||||||
system?: string;
|
system?: string;
|
||||||
},
|
},
|
||||||
) {
|
) {
|
||||||
// ค้นหารายชื่อถ้าไม่ส่ง system มาให้ default ตามทะเบียนประวัติ
|
// comment ออกก่อนเพราะยังไม่ได้ใช้
|
||||||
let _system: string = "SYS_REGISTRY_OFFICER";
|
// // ค้นหารายชื่อถ้าไม่ส่ง system มาให้ default ตามทะเบียนประวัติ
|
||||||
if (body.system) _system = body.system;
|
// let _system: string = "SYS_REGISTRY_OFFICER";
|
||||||
let _data = await new permission().PermissionOrgList(request, _system);
|
// if (body.system) _system = body.system;
|
||||||
|
// let _data = await new permission().PermissionOrgList(request, _system);
|
||||||
const findRevision = await this.orgRevisionRepo.findOne({
|
const findRevision = await this.orgRevisionRepo.findOne({
|
||||||
where: { orgRevisionIsCurrent: true },
|
where: { orgRevisionIsCurrent: true },
|
||||||
});
|
});
|
||||||
|
|
@ -11499,50 +11469,10 @@ export class ProfileController extends Controller {
|
||||||
.leftJoinAndSelect("current_holders.orgChild2", "orgChild2")
|
.leftJoinAndSelect("current_holders.orgChild2", "orgChild2")
|
||||||
.leftJoinAndSelect("current_holders.orgChild3", "orgChild3")
|
.leftJoinAndSelect("current_holders.orgChild3", "orgChild3")
|
||||||
.leftJoinAndSelect("current_holders.orgChild4", "orgChild4")
|
.leftJoinAndSelect("current_holders.orgChild4", "orgChild4")
|
||||||
.where("profile.isActive = :isActive AND profile.isDelete = :isDelete", {
|
.where(body.system ? "profile.isActive = :isActive" : "profile.isDelete = :isDelete", {
|
||||||
isActive: true,
|
isActive: false,
|
||||||
isDelete: false,
|
isDelete: true,
|
||||||
})
|
})
|
||||||
.andWhere(
|
|
||||||
_data.root != undefined && _data.root != null
|
|
||||||
? _data.root[0] != null
|
|
||||||
? `current_holders.orgRootId IN (:...root)`
|
|
||||||
: `current_holders.orgRootId is null`
|
|
||||||
: "1=1",
|
|
||||||
{ root: _data.root },
|
|
||||||
)
|
|
||||||
.andWhere(
|
|
||||||
_data.child1 != undefined && _data.child1 != null
|
|
||||||
? _data.child1[0] != null
|
|
||||||
? `current_holders.orgChild1Id IN (:...child1)`
|
|
||||||
: `current_holders.orgChild1Id is null`
|
|
||||||
: "1=1",
|
|
||||||
{ child1: _data.child1 },
|
|
||||||
)
|
|
||||||
.andWhere(
|
|
||||||
_data.child2 != undefined && _data.child2 != null
|
|
||||||
? _data.child2[0] != null
|
|
||||||
? `current_holders.orgChild2Id IN (:...child2)`
|
|
||||||
: `current_holders.orgChild2Id is null`
|
|
||||||
: "1=1",
|
|
||||||
{ child2: _data.child2 },
|
|
||||||
)
|
|
||||||
.andWhere(
|
|
||||||
_data.child3 != undefined && _data.child3 != null
|
|
||||||
? _data.child3[0] != null
|
|
||||||
? `current_holders.orgChild3Id IN (:...child3)`
|
|
||||||
: `current_holders.orgChild3Id is null`
|
|
||||||
: "1=1",
|
|
||||||
{ child3: _data.child3 },
|
|
||||||
)
|
|
||||||
.andWhere(
|
|
||||||
_data.child4 != undefined && _data.child4 != null
|
|
||||||
? _data.child4[0] != null
|
|
||||||
? `current_holders.orgChild4Id IN (:...child4)`
|
|
||||||
: `current_holders.orgChild4Id is null`
|
|
||||||
: "1=1",
|
|
||||||
{ child4: _data.child4 },
|
|
||||||
)
|
|
||||||
.andWhere(
|
.andWhere(
|
||||||
new Brackets((qb) => {
|
new Brackets((qb) => {
|
||||||
qb.orWhere(body.keyword ? queryLike : "1=1", { keyword: `%${body.keyword}%` });
|
qb.orWhere(body.keyword ? queryLike : "1=1", { keyword: `%${body.keyword}%` });
|
||||||
|
|
|
||||||
|
|
@ -2385,24 +2385,6 @@ export class ProfileEmployeeController extends Controller {
|
||||||
Extension.CheckCitizen(body.citizenId);
|
Extension.CheckCitizen(body.citizenId);
|
||||||
}
|
}
|
||||||
const record = await this.profileRepo.findOneBy({ id });
|
const record = await this.profileRepo.findOneBy({ id });
|
||||||
const before = structuredClone(record);
|
|
||||||
// เช็คว่ามี profileHistory ของ profile นี้หรือไม่
|
|
||||||
const historyCount = await this.profileHistoryRepo.count({
|
|
||||||
where: { profileEmployeeId: id },
|
|
||||||
});
|
|
||||||
|
|
||||||
// ถ้าไม่มีเลย ให้บันทึกข้อมูลเริ่มต้น (ก่อน update) ลงไปก่อน
|
|
||||||
if (historyCount === 0) {
|
|
||||||
await this.profileHistoryRepo.save(
|
|
||||||
Object.assign(new ProfileEmployeeHistory(), {
|
|
||||||
...before,
|
|
||||||
birthDateOld: before?.birthDate,
|
|
||||||
profileEmployeeId: id,
|
|
||||||
id: undefined,
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!record) throw new HttpError(HttpStatus.NOT_FOUND, "ไม่พบข้อมูลโปรไฟล์นี้");
|
if (!record) throw new HttpError(HttpStatus.NOT_FOUND, "ไม่พบข้อมูลโปรไฟล์นี้");
|
||||||
|
|
||||||
if (body.employeeClass == null || body.employeeClass == undefined || body.employeeClass == "") {
|
if (body.employeeClass == null || body.employeeClass == undefined || body.employeeClass == "") {
|
||||||
|
|
@ -2415,7 +2397,7 @@ export class ProfileEmployeeController extends Controller {
|
||||||
Object.assign(record, body);
|
Object.assign(record, body);
|
||||||
record.dateRetireLaw = calculateRetireLaw(record.birthDate);
|
record.dateRetireLaw = calculateRetireLaw(record.birthDate);
|
||||||
record.prefixMain = record.prefix;
|
record.prefixMain = record.prefix;
|
||||||
record.prefix = record.rank && record.rank.length > 0 ? record.rank : record.prefix;
|
record.prefix = record.rank && record.rank.length > 0 ? record.rank : record.prefixMain;
|
||||||
record.createdUserId = request.user.sub;
|
record.createdUserId = request.user.sub;
|
||||||
record.createdFullName = request.user.name;
|
record.createdFullName = request.user.name;
|
||||||
record.createdAt = new Date();
|
record.createdAt = new Date();
|
||||||
|
|
@ -3254,8 +3236,8 @@ export class ProfileEmployeeController extends Controller {
|
||||||
.leftJoinAndSelect("current_holders.orgChild2", "orgChild2")
|
.leftJoinAndSelect("current_holders.orgChild2", "orgChild2")
|
||||||
.leftJoinAndSelect("current_holders.orgChild3", "orgChild3")
|
.leftJoinAndSelect("current_holders.orgChild3", "orgChild3")
|
||||||
.leftJoinAndSelect("current_holders.orgChild4", "orgChild4")
|
.leftJoinAndSelect("current_holders.orgChild4", "orgChild4")
|
||||||
.where("current_holders.orgRevisionId = :orgRevisionId", {
|
.where(node && nodeId ? "current_holders.orgRevisionId = :orgRevisionId" : "1=1", {
|
||||||
orgRevisionId: findRevision.id,
|
orgRevisionId: node && nodeId ? findRevision.id : undefined,
|
||||||
})
|
})
|
||||||
.andWhere(
|
.andWhere(
|
||||||
_data.root != undefined && _data.root != null
|
_data.root != undefined && _data.root != null
|
||||||
|
|
@ -3338,7 +3320,6 @@ export class ProfileEmployeeController extends Controller {
|
||||||
: `profileEmployee.isLeave IS TRUE`
|
: `profileEmployee.isLeave IS TRUE`
|
||||||
: "1=1",
|
: "1=1",
|
||||||
)
|
)
|
||||||
.andWhere("profileEmployee.isActive IS TRUE AND profileEmployee.isDelete IS FALSE")
|
|
||||||
.andWhere("profileEmployee.employeeClass LIKE :type", {
|
.andWhere("profileEmployee.employeeClass LIKE :type", {
|
||||||
type: "PERM",
|
type: "PERM",
|
||||||
})
|
})
|
||||||
|
|
@ -4022,38 +4003,40 @@ export class ProfileEmployeeController extends Controller {
|
||||||
salary: profile ? profile.amount : null,
|
salary: profile ? profile.amount : null,
|
||||||
amountSpecial: profile ? profile.amountSpecial : null,
|
amountSpecial: profile ? profile.amountSpecial : null,
|
||||||
posNo: null,
|
posNo: null,
|
||||||
|
// root?.orgRootShortName && posMaster?.posMasterNo
|
||||||
|
// ? `${root?.orgRootShortName} ${posMaster?.posMasterNo}`
|
||||||
|
// : "",
|
||||||
};
|
};
|
||||||
const _numPart = posMaster ? [posMaster.posMasterNoPrefix, posMaster.posMasterNo, posMaster.posMasterNoSuffix].filter((p) => p !== null && p !== undefined && p !== '').join(' ') : '';
|
|
||||||
if (_profile.child4Id != null) {
|
if (_profile.child4Id != null) {
|
||||||
_profile.node = 4;
|
_profile.node = 4;
|
||||||
_profile.nodeId = _profile.child4Id;
|
_profile.nodeId = _profile.child4Id;
|
||||||
_profile.nodeDnaId = _profile.child4DnaId;
|
_profile.nodeDnaId = _profile.child4DnaId;
|
||||||
_profile.nodeShortName = _profile.child4ShortName;
|
_profile.nodeShortName = _profile.child4ShortName;
|
||||||
_profile.posNo = `${_profile.child4ShortName} ${_numPart}`;
|
_profile.posNo = `${_profile.child4ShortName} ${_profile.posMasterNo}`;
|
||||||
} else if (_profile.child3Id != null) {
|
} else if (_profile.child3Id != null) {
|
||||||
_profile.node = 3;
|
_profile.node = 3;
|
||||||
_profile.nodeId = _profile.child3Id;
|
_profile.nodeId = _profile.child3Id;
|
||||||
_profile.nodeDnaId = _profile.child3DnaId;
|
_profile.nodeDnaId = _profile.child3DnaId;
|
||||||
_profile.nodeShortName = _profile.child3ShortName;
|
_profile.nodeShortName = _profile.child3ShortName;
|
||||||
_profile.posNo = `${_profile.child3ShortName} ${_numPart}`;
|
_profile.posNo = `${_profile.child3ShortName} ${_profile.posMasterNo}`;
|
||||||
} else if (_profile.child2Id != null) {
|
} else if (_profile.child2Id != null) {
|
||||||
_profile.node = 2;
|
_profile.node = 2;
|
||||||
_profile.nodeId = _profile.child2Id;
|
_profile.nodeId = _profile.child2Id;
|
||||||
_profile.nodeDnaId = _profile.child2DnaId;
|
_profile.nodeDnaId = _profile.child2DnaId;
|
||||||
_profile.nodeShortName = _profile.child2ShortName;
|
_profile.nodeShortName = _profile.child2ShortName;
|
||||||
_profile.posNo = `${_profile.child2ShortName} ${_numPart}`;
|
_profile.posNo = `${_profile.child2ShortName} ${_profile.posMasterNo}`;
|
||||||
} else if (_profile.child1Id != null) {
|
} else if (_profile.child1Id != null) {
|
||||||
_profile.node = 1;
|
_profile.node = 1;
|
||||||
_profile.nodeId = _profile.child1Id;
|
_profile.nodeId = _profile.child1Id;
|
||||||
_profile.nodeDnaId = _profile.child1DnaId;
|
_profile.nodeDnaId = _profile.child1DnaId;
|
||||||
_profile.nodeShortName = _profile.child1ShortName;
|
_profile.nodeShortName = _profile.child1ShortName;
|
||||||
_profile.posNo = `${_profile.child1ShortName} ${_numPart}`;
|
_profile.posNo = `${_profile.child1ShortName} ${_profile.posMasterNo}`;
|
||||||
} else if (_profile.rootId != null) {
|
} else if (_profile.rootId != null) {
|
||||||
_profile.node = 0;
|
_profile.node = 0;
|
||||||
_profile.nodeId = _profile.rootId;
|
_profile.nodeId = _profile.rootId;
|
||||||
_profile.nodeDnaId = _profile.rootDnaId;
|
_profile.nodeDnaId = _profile.rootDnaId;
|
||||||
_profile.nodeShortName = _profile.rootShortName;
|
_profile.nodeShortName = _profile.rootShortName;
|
||||||
_profile.posNo = `${_profile.rootShortName} ${_numPart}`;
|
_profile.posNo = `${_profile.rootShortName} ${_profile.posMasterNo}`;
|
||||||
}
|
}
|
||||||
return new HttpSuccess(_profile);
|
return new HttpSuccess(_profile);
|
||||||
}
|
}
|
||||||
|
|
@ -6172,7 +6155,6 @@ export class ProfileEmployeeController extends Controller {
|
||||||
*/
|
*/
|
||||||
@Post("search-personal-no-keycloak")
|
@Post("search-personal-no-keycloak")
|
||||||
async getProfileBySearchKeywordNoKeyCloak(
|
async getProfileBySearchKeywordNoKeyCloak(
|
||||||
@Request() request: RequestWithUser,
|
|
||||||
@Query("page") page: number = 1,
|
@Query("page") page: number = 1,
|
||||||
@Query("pageSize") pageSize: number = 10,
|
@Query("pageSize") pageSize: number = 10,
|
||||||
@Body()
|
@Body()
|
||||||
|
|
@ -6182,10 +6164,6 @@ export class ProfileEmployeeController extends Controller {
|
||||||
system?: string;
|
system?: string;
|
||||||
},
|
},
|
||||||
) {
|
) {
|
||||||
// ค้นหารายชื่อถ้าไม่ส่ง system มาให้ default ตามทะเบียนประวัติ
|
|
||||||
let _system: string = "SYS_REGISTRY_EMP";
|
|
||||||
if (body.system) _system = body.system;
|
|
||||||
let _data = await new permission().PermissionOrgList(request, _system);
|
|
||||||
const findRevision = await this.orgRevisionRepo.findOne({
|
const findRevision = await this.orgRevisionRepo.findOne({
|
||||||
where: { orgRevisionIsCurrent: true },
|
where: { orgRevisionIsCurrent: true },
|
||||||
});
|
});
|
||||||
|
|
@ -6230,50 +6208,10 @@ export class ProfileEmployeeController extends Controller {
|
||||||
.leftJoinAndSelect("current_holders.orgChild2", "orgChild2")
|
.leftJoinAndSelect("current_holders.orgChild2", "orgChild2")
|
||||||
.leftJoinAndSelect("current_holders.orgChild3", "orgChild3")
|
.leftJoinAndSelect("current_holders.orgChild3", "orgChild3")
|
||||||
.leftJoinAndSelect("current_holders.orgChild4", "orgChild4")
|
.leftJoinAndSelect("current_holders.orgChild4", "orgChild4")
|
||||||
.where("profile.isActive = :isActive AND profile.isDelete = :isDelete", {
|
.where(body.system ? "profile.isActive = :isActive" : "profile.isDelete = :isDelete", {
|
||||||
isActive: true,
|
isActive: false,
|
||||||
isDelete: false,
|
isDelete: true,
|
||||||
})
|
})
|
||||||
.andWhere(
|
|
||||||
_data.root != undefined && _data.root != null
|
|
||||||
? _data.root[0] != null
|
|
||||||
? `current_holders.orgRootId IN (:...root)`
|
|
||||||
: `current_holders.orgRootId is null`
|
|
||||||
: "1=1",
|
|
||||||
{ root: _data.root },
|
|
||||||
)
|
|
||||||
.andWhere(
|
|
||||||
_data.child1 != undefined && _data.child1 != null
|
|
||||||
? _data.child1[0] != null
|
|
||||||
? `current_holders.orgChild1Id IN (:...child1)`
|
|
||||||
: `current_holders.orgChild1Id is null`
|
|
||||||
: "1=1",
|
|
||||||
{ child1: _data.child1 },
|
|
||||||
)
|
|
||||||
.andWhere(
|
|
||||||
_data.child2 != undefined && _data.child2 != null
|
|
||||||
? _data.child2[0] != null
|
|
||||||
? `current_holders.orgChild2Id IN (:...child2)`
|
|
||||||
: `current_holders.orgChild2Id is null`
|
|
||||||
: "1=1",
|
|
||||||
{ child2: _data.child2 },
|
|
||||||
)
|
|
||||||
.andWhere(
|
|
||||||
_data.child3 != undefined && _data.child3 != null
|
|
||||||
? _data.child3[0] != null
|
|
||||||
? `current_holders.orgChild3Id IN (:...child3)`
|
|
||||||
: `current_holders.orgChild3Id is null`
|
|
||||||
: "1=1",
|
|
||||||
{ child3: _data.child3 },
|
|
||||||
)
|
|
||||||
.andWhere(
|
|
||||||
_data.child4 != undefined && _data.child4 != null
|
|
||||||
? _data.child4[0] != null
|
|
||||||
? `current_holders.orgChild4Id IN (:...child4)`
|
|
||||||
: `current_holders.orgChild4Id is null`
|
|
||||||
: "1=1",
|
|
||||||
{ child4: _data.child4 },
|
|
||||||
)
|
|
||||||
.andWhere(
|
.andWhere(
|
||||||
new Brackets((qb) => {
|
new Brackets((qb) => {
|
||||||
qb.orWhere(body.keyword ? queryLike : "1=1", { keyword: `%${body.keyword}%` });
|
qb.orWhere(body.keyword ? queryLike : "1=1", { keyword: `%${body.keyword}%` });
|
||||||
|
|
@ -6506,93 +6444,33 @@ export class ProfileEmployeeController extends Controller {
|
||||||
null
|
null
|
||||||
? null
|
? null
|
||||||
: profile.current_holders.find((x) => x.orgRevisionId == orgRevisionPublish.id)?.orgChild4;
|
: profile.current_holders.find((x) => x.orgRevisionId == orgRevisionPublish.id)?.orgChild4;
|
||||||
const _numPart = posMaster ? [posMaster.posMasterNoPrefix, posMaster.posMasterNo, posMaster.posMasterNoSuffix].filter((p) => p !== null && p !== undefined && p !== '').join(' ') : '';
|
const shortName =
|
||||||
|
profile.current_holders.length == 0
|
||||||
// org / posNo — ล้อ fallback ของ officer (ProfileController.getProfileByProfileid)
|
|
||||||
// employee ไม่มี profile.org / profile.posMasterNo จึงแยกตาม isLeave โดยตรง
|
|
||||||
let _Org: string | null = null;
|
|
||||||
let _PosNo: string | null = null;
|
|
||||||
if (profile.isLeave) {
|
|
||||||
const profileWithSalary = await this.profileRepo.findOne({
|
|
||||||
where: {
|
|
||||||
id: id,
|
|
||||||
profileSalary: {
|
|
||||||
commandCode: In([
|
|
||||||
"0",
|
|
||||||
"9",
|
|
||||||
"1",
|
|
||||||
"2",
|
|
||||||
"3",
|
|
||||||
"4",
|
|
||||||
"8",
|
|
||||||
"10",
|
|
||||||
"11",
|
|
||||||
"12",
|
|
||||||
"13",
|
|
||||||
"14",
|
|
||||||
"15",
|
|
||||||
"16",
|
|
||||||
"20",
|
|
||||||
]),
|
|
||||||
},
|
|
||||||
},
|
|
||||||
relations: { profileSalary: true },
|
|
||||||
order: {
|
|
||||||
profileSalary: {
|
|
||||||
order: "DESC",
|
|
||||||
createdAt: "DESC",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
});
|
|
||||||
const profileSalaryList = profileWithSalary?.profileSalary || [];
|
|
||||||
|
|
||||||
if (profileSalaryList.length > 0) {
|
|
||||||
const _profileSalary =
|
|
||||||
profile.leaveType == "RETIRE"
|
|
||||||
? profileSalaryList.length > 1
|
|
||||||
? profileSalaryList[1]
|
|
||||||
: profileSalaryList[0]
|
|
||||||
: profileSalaryList[0];
|
|
||||||
|
|
||||||
if (_profileSalary) {
|
|
||||||
_Org = [
|
|
||||||
_profileSalary.orgChild4 ?? null,
|
|
||||||
_profileSalary.orgChild3 ?? null,
|
|
||||||
_profileSalary.orgChild2 ?? null,
|
|
||||||
_profileSalary.orgChild1 ?? null,
|
|
||||||
_profileSalary.orgRoot ?? null,
|
|
||||||
]
|
|
||||||
.filter((x: any) => x !== undefined && x !== null)
|
|
||||||
.join("\n");
|
|
||||||
_PosNo = `${_profileSalary.posNoAbb} ${_profileSalary.posNo}`;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
_Org = [
|
|
||||||
child4?.orgChild4Name,
|
|
||||||
child3?.orgChild3Name,
|
|
||||||
child2?.orgChild2Name,
|
|
||||||
child1?.orgChild1Name,
|
|
||||||
root?.orgRootName,
|
|
||||||
]
|
|
||||||
.filter((x) => x != null && x !== "")
|
|
||||||
.join("\n");
|
|
||||||
|
|
||||||
_PosNo =
|
|
||||||
posMaster == null
|
|
||||||
? null
|
? null
|
||||||
: child4 != null
|
: profile.current_holders.find((x) => x.orgRevisionId == orgRevisionPublish.id) != null &&
|
||||||
? `${child4.orgChild4ShortName} ${_numPart}`
|
profile.current_holders.find((x) => x.orgRevisionId == orgRevisionPublish.id)
|
||||||
: child3 != null
|
?.orgChild4 != null
|
||||||
? `${child3.orgChild3ShortName} ${_numPart}`
|
? `${profile.current_holders.find((x) => x.orgRevisionId == orgRevisionPublish.id)?.orgChild4.orgChild4ShortName} ${profile.current_holders.find((x) => x.orgRevisionId == orgRevisionPublish.id)?.posMasterNo}`
|
||||||
: child2 != null
|
: profile.current_holders.find((x) => x.orgRevisionId == orgRevisionPublish.id) != null &&
|
||||||
? `${child2.orgChild2ShortName} ${_numPart}`
|
profile.current_holders.find((x) => x.orgRevisionId == orgRevisionPublish.id)
|
||||||
: child1 != null
|
?.orgChild3 != null
|
||||||
? `${child1.orgChild1ShortName} ${_numPart}`
|
? `${profile.current_holders.find((x) => x.orgRevisionId == orgRevisionPublish.id)?.orgChild3.orgChild3ShortName} ${profile.current_holders.find((x) => x.orgRevisionId == orgRevisionPublish.id)?.posMasterNo}`
|
||||||
: root != null
|
: profile.current_holders.find((x) => x.orgRevisionId == orgRevisionPublish.id) !=
|
||||||
? `${root.orgRootShortName} ${_numPart}`
|
null &&
|
||||||
|
profile.current_holders.find((x) => x.orgRevisionId == orgRevisionPublish.id)
|
||||||
|
?.orgChild2 != null
|
||||||
|
? `${profile.current_holders.find((x) => x.orgRevisionId == orgRevisionPublish.id)?.orgChild2.orgChild2ShortName} ${profile.current_holders.find((x) => x.orgRevisionId == orgRevisionPublish.id)?.posMasterNo}`
|
||||||
|
: profile.current_holders.find((x) => x.orgRevisionId == orgRevisionPublish.id) !=
|
||||||
|
null &&
|
||||||
|
profile.current_holders.find((x) => x.orgRevisionId == orgRevisionPublish.id)
|
||||||
|
?.orgChild1 != null
|
||||||
|
? `${profile.current_holders.find((x) => x.orgRevisionId == orgRevisionPublish.id)?.orgChild1.orgChild1ShortName} ${profile.current_holders.find((x) => x.orgRevisionId == orgRevisionPublish.id)?.posMasterNo}`
|
||||||
|
: profile.current_holders.find((x) => x.orgRevisionId == orgRevisionPublish.id) !=
|
||||||
|
null &&
|
||||||
|
profile.current_holders.find((x) => x.orgRevisionId == orgRevisionPublish.id)
|
||||||
|
?.orgRoot != null
|
||||||
|
? `${profile.current_holders.find((x) => x.orgRevisionId == orgRevisionPublish.id)?.orgRoot.orgRootShortName} ${profile.current_holders.find((x) => x.orgRevisionId == orgRevisionPublish.id)?.posMasterNo}`
|
||||||
: null;
|
: null;
|
||||||
}
|
|
||||||
const _profile: any = {
|
const _profile: any = {
|
||||||
profileId: profile.id,
|
profileId: profile.id,
|
||||||
prefix: profile.prefix,
|
prefix: profile.prefix,
|
||||||
|
|
@ -6605,7 +6483,7 @@ export class ProfileEmployeeController extends Controller {
|
||||||
position: profile.position,
|
position: profile.position,
|
||||||
leaveDate: profile.dateLeave,
|
leaveDate: profile.dateLeave,
|
||||||
posMasterNo: posMaster == null ? null : posMaster.posMasterNo,
|
posMasterNo: posMaster == null ? null : posMaster.posMasterNo,
|
||||||
posLevelName: `${profile?.posType?.posTypeShortName ?? ""} ${profile?.posLevel?.posLevelName ?? ""}`.trim(),
|
posLevelName: `${profile?.posType?.posTypeShortName ?? ""} ${profile?.posLevel?.posLevelName ?? ""}`,
|
||||||
posLevelRank: profile.posLevel == null ? null : profile.posLevel.posLevelRank,
|
posLevelRank: profile.posLevel == null ? null : profile.posLevel.posLevelRank,
|
||||||
posLevelId: profile.posLevel == null ? null : profile.posLevel.id,
|
posLevelId: profile.posLevel == null ? null : profile.posLevel.id,
|
||||||
posTypeName: profile.posType == null ? null : profile.posType.posTypeName,
|
posTypeName: profile.posType == null ? null : profile.posType.posTypeName,
|
||||||
|
|
@ -6634,8 +6512,7 @@ export class ProfileEmployeeController extends Controller {
|
||||||
child4ShortName: child4 == null ? null : child4.orgChild4ShortName,
|
child4ShortName: child4 == null ? null : child4.orgChild4ShortName,
|
||||||
node: null,
|
node: null,
|
||||||
nodeId: null,
|
nodeId: null,
|
||||||
posNo: _PosNo,
|
posNo: shortName,
|
||||||
org: _Org,
|
|
||||||
salary: profile.amount,
|
salary: profile.amount,
|
||||||
education:
|
education:
|
||||||
profile && profile.profileEducations.length > 0
|
profile && profile.profileEducations.length > 0
|
||||||
|
|
|
||||||
|
|
@ -1001,24 +1001,6 @@ export class ProfileEmployeeTempController extends Controller {
|
||||||
}
|
}
|
||||||
|
|
||||||
const record = await this.profileRepo.findOneBy({ id });
|
const record = await this.profileRepo.findOneBy({ id });
|
||||||
const before = structuredClone(record);
|
|
||||||
// เช็คว่ามี profileHistory ของ profile นี้หรือไม่
|
|
||||||
const historyCount = await this.profileHistoryRepo.count({
|
|
||||||
where: { profileEmployeeId: id },
|
|
||||||
});
|
|
||||||
|
|
||||||
// ถ้าไม่มีเลย ให้บันทึกข้อมูลเริ่มต้น (ก่อน update) ลงไปก่อน
|
|
||||||
if (historyCount === 0) {
|
|
||||||
await this.profileHistoryRepo.save(
|
|
||||||
Object.assign(new ProfileEmployeeHistory(), {
|
|
||||||
...before,
|
|
||||||
birthDateOld: before?.birthDate,
|
|
||||||
profileEmployeeId: id,
|
|
||||||
id: undefined,
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!record) throw new HttpError(HttpStatus.NOT_FOUND, "ไม่พบข้อมูลโปรไฟล์นี้");
|
if (!record) throw new HttpError(HttpStatus.NOT_FOUND, "ไม่พบข้อมูลโปรไฟล์นี้");
|
||||||
|
|
||||||
if (body.employeeClass == null || body.employeeClass == undefined || body.employeeClass == "") {
|
if (body.employeeClass == null || body.employeeClass == undefined || body.employeeClass == "") {
|
||||||
|
|
|
||||||
|
|
@ -23,7 +23,6 @@ import { ProfileEmployee } from "../entities/ProfileEmployee";
|
||||||
import { In, IsNull, LessThan, MoreThan, Not } from "typeorm";
|
import { In, IsNull, LessThan, MoreThan, Not } from "typeorm";
|
||||||
import permission from "../interfaces/permission";
|
import permission from "../interfaces/permission";
|
||||||
import { setLogDataDiff } from "../interfaces/utils";
|
import { setLogDataDiff } from "../interfaces/utils";
|
||||||
import { ExecuteSalaryReportService } from "../services/ExecuteSalaryReportService";
|
|
||||||
import { normalizeDurationSumSimple } from "../utils/tenure";
|
import { normalizeDurationSumSimple } from "../utils/tenure";
|
||||||
import {
|
import {
|
||||||
TenurePositionOfficer,
|
TenurePositionOfficer,
|
||||||
|
|
@ -1381,10 +1380,91 @@ export class ProfileSalaryController extends Controller {
|
||||||
|
|
||||||
@Post("update")
|
@Post("update")
|
||||||
public async updateSalary(@Request() req: RequestWithUser, @Body() body: CreateProfileSalary) {
|
public async updateSalary(@Request() req: RequestWithUser, @Body() body: CreateProfileSalary) {
|
||||||
await new ExecuteSalaryReportService().executeOfficerSalaryUpdate([body], {
|
if (!body.profileId) {
|
||||||
user: { sub: req.user.sub, name: req.user.name },
|
throw new HttpError(HttpStatus.BAD_REQUEST, "กรุณากรอก profileId");
|
||||||
req,
|
}
|
||||||
|
|
||||||
|
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 dest_item = await this.salaryRepo.findOne({
|
||||||
|
where: { profileId: body.profileId },
|
||||||
|
order: { order: "DESC" },
|
||||||
});
|
});
|
||||||
|
const before = null;
|
||||||
|
let _posNumCodeSit: string = "";
|
||||||
|
let _posNumCodeSitAbb: string = "";
|
||||||
|
const _command = await this.commandRepository.findOne({
|
||||||
|
where: { id: body.commandId ?? "" },
|
||||||
|
});
|
||||||
|
if (_command) {
|
||||||
|
if (_command?.isBangkok?.toLocaleUpperCase() == "OFFICE") {
|
||||||
|
const orgRootDeputy = await this.orgRootRepository.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 this.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 ?? "";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const data = new ProfileSalary();
|
||||||
|
data.posNumCodeSit = _posNumCodeSit;
|
||||||
|
data.posNumCodeSitAbb = _posNumCodeSitAbb;
|
||||||
|
const meta = {
|
||||||
|
order: dest_item == null ? 1 : dest_item.order + 1,
|
||||||
|
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 });
|
||||||
|
const history = new ProfileSalaryHistory();
|
||||||
|
Object.assign(history, { ...data, id: undefined });
|
||||||
|
await this.salaryRepo.save(data, { data: req });
|
||||||
|
setLogDataDiff(req, { before, after: data });
|
||||||
|
history.profileSalaryId = data.id;
|
||||||
|
await this.salaryHistoryRepo.save(history, { data: req });
|
||||||
|
|
||||||
|
let _null: any = null;
|
||||||
|
profile.amount = body.amount ?? _null;
|
||||||
|
profile.amountSpecial = body.amountSpecial ?? _null;
|
||||||
|
profile.positionSalaryAmount = body.positionSalaryAmount ?? _null;
|
||||||
|
profile.mouthSalaryAmount = body.mouthSalaryAmount ?? _null;
|
||||||
|
await this.profileRepo.save(profile);
|
||||||
return new HttpSuccess();
|
return new HttpSuccess();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -27,7 +27,6 @@ import { Profile } from "../entities/Profile";
|
||||||
import { In, LessThan, IsNull, MoreThan } from "typeorm";
|
import { In, LessThan, IsNull, MoreThan } from "typeorm";
|
||||||
import permission from "../interfaces/permission";
|
import permission from "../interfaces/permission";
|
||||||
import { setLogDataDiff } from "../interfaces/utils";
|
import { setLogDataDiff } from "../interfaces/utils";
|
||||||
import { ExecuteSalaryReportService } from "../services/ExecuteSalaryReportService";
|
|
||||||
import { normalizeDurationSumSimple } from "../utils/tenure";
|
import { normalizeDurationSumSimple } from "../utils/tenure";
|
||||||
import { Command } from "../entities/Command";
|
import { Command } from "../entities/Command";
|
||||||
import { OrgRoot } from "../entities/OrgRoot";
|
import { OrgRoot } from "../entities/OrgRoot";
|
||||||
|
|
@ -508,10 +507,94 @@ export class ProfileSalaryEmployeeController extends Controller {
|
||||||
@Request() req: RequestWithUser,
|
@Request() req: RequestWithUser,
|
||||||
@Body() body: CreateProfileSalaryEmployee,
|
@Body() body: CreateProfileSalaryEmployee,
|
||||||
) {
|
) {
|
||||||
await new ExecuteSalaryReportService().executeEmployeeSalaryUpdate([body], {
|
if (!body.profileEmployeeId) {
|
||||||
user: { sub: req.user.sub, name: req.user.name },
|
throw new HttpError(HttpStatus.BAD_REQUEST, "กรุณากรอก profileEmployeeId");
|
||||||
req,
|
}
|
||||||
|
|
||||||
|
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 dest_item = await this.salaryRepo.findOne({
|
||||||
|
where: { profileEmployeeId: body.profileEmployeeId },
|
||||||
|
order: { order: "DESC" },
|
||||||
});
|
});
|
||||||
|
const before = null;
|
||||||
|
let _posNumCodeSit: string = "";
|
||||||
|
let _posNumCodeSitAbb: string = "";
|
||||||
|
const _command = await this.commandRepository.findOne({
|
||||||
|
where: { id: body.commandId ?? "" },
|
||||||
|
});
|
||||||
|
if (_command) {
|
||||||
|
if (_command?.isBangkok?.toLocaleUpperCase() == "OFFICE") {
|
||||||
|
const orgRootDeputy = await this.orgRootRepository.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 this.profileGovementRepo.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 ?? "";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const data = new ProfileSalary();
|
||||||
|
data.posNumCodeSit = _posNumCodeSit;
|
||||||
|
data.posNumCodeSitAbb = _posNumCodeSitAbb;
|
||||||
|
const meta = {
|
||||||
|
order: dest_item == null ? 1 : dest_item.order + 1,
|
||||||
|
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 });
|
||||||
|
const history = new ProfileSalaryHistory();
|
||||||
|
Object.assign(history, { ...data, id: undefined });
|
||||||
|
|
||||||
|
await this.salaryRepo.save(data, { data: req });
|
||||||
|
setLogDataDiff(req, { before, after: data });
|
||||||
|
history.profileSalaryId = data.id;
|
||||||
|
await this.salaryHistoryRepo.save(history, { data: req });
|
||||||
|
|
||||||
|
let _null: any = null;
|
||||||
|
profile.amount = body.amount ?? _null;
|
||||||
|
profile.amountSpecial = body.amountSpecial ?? _null;
|
||||||
|
profile.positionSalaryAmount = body.positionSalaryAmount ?? _null;
|
||||||
|
profile.mouthSalaryAmount = body.mouthSalaryAmount ?? _null;
|
||||||
|
profile.salaryLevel = body.salaryLevel ?? _null;
|
||||||
|
profile.group = body.group ?? _null;
|
||||||
|
await this.profileRepo.save(profile);
|
||||||
return new HttpSuccess();
|
return new HttpSuccess();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -38,8 +38,6 @@ import {
|
||||||
import { AppDataSource } from "../database/data-source";
|
import { AppDataSource } from "../database/data-source";
|
||||||
import { Profile } from "../entities/Profile";
|
import { Profile } from "../entities/Profile";
|
||||||
import { ProfileEmployee } from "../entities/ProfileEmployee";
|
import { ProfileEmployee } from "../entities/ProfileEmployee";
|
||||||
import { PosMaster } from "../entities/PosMaster";
|
|
||||||
import { EmployeePosMaster } from "../entities/EmployeePosMaster";
|
|
||||||
import { RequestWithUser } from "../middlewares/user";
|
import { RequestWithUser } from "../middlewares/user";
|
||||||
import HttpSuccess from "../interfaces/http-success";
|
import HttpSuccess from "../interfaces/http-success";
|
||||||
import { Brackets, In, IsNull, Not } from "typeorm";
|
import { Brackets, In, IsNull, Not } from "typeorm";
|
||||||
|
|
@ -49,11 +47,6 @@ import { RoleKeycloak } from "../entities/RoleKeycloak";
|
||||||
import { addLogSequence } from "../interfaces/utils";
|
import { addLogSequence } from "../interfaces/utils";
|
||||||
import { OrgRevision } from "../entities/OrgRevision";
|
import { OrgRevision } from "../entities/OrgRevision";
|
||||||
import { Uuid } from "@elastic/elasticsearch/lib/api/types";
|
import { Uuid } from "@elastic/elasticsearch/lib/api/types";
|
||||||
import { promisify } from "util";
|
|
||||||
|
|
||||||
const REDIS_HOST = process.env.REDIS_HOST;
|
|
||||||
const REDIS_PORT = process.env.REDIS_PORT;
|
|
||||||
const redis = require("redis");
|
|
||||||
// import * as io from "../lib/websocket";
|
// import * as io from "../lib/websocket";
|
||||||
// import elasticsearch from "../elasticsearch";
|
// import elasticsearch from "../elasticsearch";
|
||||||
// import { StorageFolder } from "../interfaces/storage-fs";
|
// import { StorageFolder } from "../interfaces/storage-fs";
|
||||||
|
|
@ -72,7 +65,6 @@ function stripLeadingSlash(str: string) {
|
||||||
export class KeycloakController extends Controller {
|
export class KeycloakController extends Controller {
|
||||||
private profileRepo = AppDataSource.getRepository(Profile);
|
private profileRepo = AppDataSource.getRepository(Profile);
|
||||||
private profileEmpRepo = AppDataSource.getRepository(ProfileEmployee);
|
private profileEmpRepo = AppDataSource.getRepository(ProfileEmployee);
|
||||||
private posMasterRepository = AppDataSource.getRepository(PosMaster);
|
|
||||||
private roleKeycloakRepo = AppDataSource.getRepository(RoleKeycloak);
|
private roleKeycloakRepo = AppDataSource.getRepository(RoleKeycloak);
|
||||||
|
|
||||||
@Get("user/{id}")
|
@Get("user/{id}")
|
||||||
|
|
@ -360,8 +352,6 @@ export class KeycloakController extends Controller {
|
||||||
where: { keycloak: userId },
|
where: { keycloak: userId },
|
||||||
relations: ["roleKeycloaks"],
|
relations: ["roleKeycloaks"],
|
||||||
});
|
});
|
||||||
let profileId: string | undefined;
|
|
||||||
|
|
||||||
if (!profile) {
|
if (!profile) {
|
||||||
const profileEmp = await this.profileEmpRepo.findOne({
|
const profileEmp = await this.profileEmpRepo.findOne({
|
||||||
where: { keycloak: userId, employeeClass: "PERM" },
|
where: { keycloak: userId, employeeClass: "PERM" },
|
||||||
|
|
@ -369,11 +359,10 @@ export class KeycloakController extends Controller {
|
||||||
});
|
});
|
||||||
if (!profileEmp) throw new HttpError(HttpStatus.NOT_FOUND, "ไม่พบข้อมูล");
|
if (!profileEmp) throw new HttpError(HttpStatus.NOT_FOUND, "ไม่พบข้อมูล");
|
||||||
profileEmp.roleKeycloaks = profileEmp.roleKeycloaks.filter((x) => x.id != roleId);
|
profileEmp.roleKeycloaks = profileEmp.roleKeycloaks.filter((x) => x.id != roleId);
|
||||||
await this.profileEmpRepo.save(profileEmp);
|
this.profileEmpRepo.save(profileEmp);
|
||||||
} else {
|
} else {
|
||||||
profile.roleKeycloaks = profile.roleKeycloaks.filter((x) => x.id != roleId);
|
profile.roleKeycloaks = profile.roleKeycloaks.filter((x) => x.id != roleId);
|
||||||
await this.profileRepo.save(profile);
|
this.profileRepo.save(profile);
|
||||||
profileId = profile.id;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const list = await getRoles();
|
const list = await getRoles();
|
||||||
|
|
@ -385,53 +374,6 @@ export class KeycloakController extends Controller {
|
||||||
list.filter((v) => roleId === v.id),
|
list.filter((v) => roleId === v.id),
|
||||||
);
|
);
|
||||||
if (!result) throw new Error("Failed. Cannot remove user's role.");
|
if (!result) throw new Error("Failed. Cannot remove user's role.");
|
||||||
|
|
||||||
// delete authRoleId in posMaster if roleId is "f1fff8db-0795-47c1-9952-f3c18d5b6172"
|
|
||||||
if (profileId && roleId === "f1fff8db-0795-47c1-9952-f3c18d5b6172") {
|
|
||||||
let _null: any = null;
|
|
||||||
const authRoleId = _null;
|
|
||||||
console.log(`Clearing authRoleId on position records for profile ${profileId}`);
|
|
||||||
try {
|
|
||||||
await this.posMasterRepository
|
|
||||||
.createQueryBuilder()
|
|
||||||
.update(PosMaster)
|
|
||||||
.set({ authRoleId: authRoleId })
|
|
||||||
.where("current_holderId = :profileId", { profileId })
|
|
||||||
.execute();
|
|
||||||
} catch (error) {
|
|
||||||
console.error(`Failed to clear authRoleId on position records for profile ${profileId}:`, error);
|
|
||||||
}
|
|
||||||
|
|
||||||
let redisClient: any;
|
|
||||||
let quitAsync: any;
|
|
||||||
try {
|
|
||||||
redisClient = redis.createClient({
|
|
||||||
host: REDIS_HOST,
|
|
||||||
port: REDIS_PORT,
|
|
||||||
});
|
|
||||||
const delAsync = promisify(redisClient.del).bind(redisClient);
|
|
||||||
quitAsync = promisify(redisClient.quit).bind(redisClient);
|
|
||||||
|
|
||||||
const [roleDeleted, menuDeleted] = await Promise.all([
|
|
||||||
delAsync("role_" + profileId),
|
|
||||||
delAsync("menu_" + profileId),
|
|
||||||
]);
|
|
||||||
|
|
||||||
if (roleDeleted === 0) {
|
|
||||||
console.warn(`Redis key not found: role_${profileId}`);
|
|
||||||
}
|
|
||||||
if (menuDeleted === 0) {
|
|
||||||
console.warn(`Redis key not found: menu_${profileId}`);
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
console.error(`Failed to delete Redis cache for profile ${profileId}:`, error);
|
|
||||||
} finally {
|
|
||||||
if (quitAsync) {
|
|
||||||
await quitAsync();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return new HttpSuccess();
|
return new HttpSuccess();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -865,7 +865,6 @@ export class WorkflowController extends Controller {
|
||||||
type?: string | null;
|
type?: string | null;
|
||||||
sortBy?: string | null;
|
sortBy?: string | null;
|
||||||
descending?: boolean;
|
descending?: boolean;
|
||||||
isAllDirector?: boolean;
|
|
||||||
},
|
},
|
||||||
) {
|
) {
|
||||||
const userKeycloak = body.keycloakId ?? request.user.sub;
|
const userKeycloak = body.keycloakId ?? request.user.sub;
|
||||||
|
|
@ -954,13 +953,6 @@ export class WorkflowController extends Controller {
|
||||||
let mainConditions: any[] = [];
|
let mainConditions: any[] = [];
|
||||||
|
|
||||||
if (type.trim().toUpperCase() === "OPERATE" || body.type === "employee") {
|
if (type.trim().toUpperCase() === "OPERATE" || body.type === "employee") {
|
||||||
|
|
||||||
if (body.isAllDirector === true) {
|
|
||||||
mainConditions = [
|
|
||||||
{ ...baseCondition, orgRootId: In(roodIds) }
|
|
||||||
];
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
mainConditions = [
|
mainConditions = [
|
||||||
{ ...baseCondition, orgRootId: In(roodIds), orgChild1Id: IsNull() },
|
{ ...baseCondition, orgRootId: In(roodIds), orgChild1Id: IsNull() },
|
||||||
{
|
{
|
||||||
|
|
@ -993,7 +985,6 @@ export class WorkflowController extends Controller {
|
||||||
orgChild4Id: posMasterUser.orgChild4Id,
|
orgChild4Id: posMasterUser.orgChild4Id,
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
}
|
|
||||||
} else if (isLowLevel) {
|
} else if (isLowLevel) {
|
||||||
mainConditions = [
|
mainConditions = [
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -174,46 +174,6 @@ export class OrgChild1 extends EntityBase {
|
||||||
})
|
})
|
||||||
JOB_CODE: string;
|
JOB_CODE: string;
|
||||||
|
|
||||||
@Column({
|
|
||||||
nullable: true,
|
|
||||||
length: 3,
|
|
||||||
comment: "ROOT_CODE",
|
|
||||||
default: null,
|
|
||||||
})
|
|
||||||
ROOT_CODE: string;
|
|
||||||
|
|
||||||
@Column({
|
|
||||||
nullable: true,
|
|
||||||
length: 3,
|
|
||||||
comment: "CHILD1_CODE",
|
|
||||||
default: null,
|
|
||||||
})
|
|
||||||
CHILD1_CODE: string;
|
|
||||||
|
|
||||||
@Column({
|
|
||||||
nullable: true,
|
|
||||||
length: 3,
|
|
||||||
comment: "CHILD2_CODE",
|
|
||||||
default: null,
|
|
||||||
})
|
|
||||||
CHILD2_CODE: string;
|
|
||||||
|
|
||||||
@Column({
|
|
||||||
nullable: true,
|
|
||||||
length: 3,
|
|
||||||
comment: "CHILD3_CODE",
|
|
||||||
default: null,
|
|
||||||
})
|
|
||||||
CHILD3_CODE: string;
|
|
||||||
|
|
||||||
@Column({
|
|
||||||
nullable: true,
|
|
||||||
length: 3,
|
|
||||||
comment: "CHILD4_CODE",
|
|
||||||
default: null,
|
|
||||||
})
|
|
||||||
CHILD4_CODE: string;
|
|
||||||
|
|
||||||
@ManyToOne(() => OrgRoot, (orgRoot) => orgRoot.orgChild1s)
|
@ManyToOne(() => OrgRoot, (orgRoot) => orgRoot.orgChild1s)
|
||||||
@JoinColumn({ name: "orgRootId" })
|
@JoinColumn({ name: "orgRootId" })
|
||||||
orgRoot: OrgRoot;
|
orgRoot: OrgRoot;
|
||||||
|
|
@ -268,21 +228,6 @@ export class CreateOrgChild1 {
|
||||||
@Column()
|
@Column()
|
||||||
JOB_CODE?: string | null;
|
JOB_CODE?: string | null;
|
||||||
|
|
||||||
@Column()
|
|
||||||
ROOT_CODE?: string | null;
|
|
||||||
|
|
||||||
@Column()
|
|
||||||
CHILD1_CODE?: string | null;
|
|
||||||
|
|
||||||
@Column()
|
|
||||||
CHILD2_CODE?: string | null;
|
|
||||||
|
|
||||||
@Column()
|
|
||||||
CHILD3_CODE?: string | null;
|
|
||||||
|
|
||||||
@Column()
|
|
||||||
CHILD4_CODE?: string | null;
|
|
||||||
|
|
||||||
@Column()
|
@Column()
|
||||||
orgChild1PhoneEx?: string;
|
orgChild1PhoneEx?: string;
|
||||||
|
|
||||||
|
|
@ -338,21 +283,6 @@ export class UpdateOrgChild1 {
|
||||||
@Column()
|
@Column()
|
||||||
JOB_CODE?: string | null;
|
JOB_CODE?: string | null;
|
||||||
|
|
||||||
@Column()
|
|
||||||
ROOT_CODE?: string | null;
|
|
||||||
|
|
||||||
@Column()
|
|
||||||
CHILD1_CODE?: string | null;
|
|
||||||
|
|
||||||
@Column()
|
|
||||||
CHILD2_CODE?: string | null;
|
|
||||||
|
|
||||||
@Column()
|
|
||||||
CHILD3_CODE?: string | null;
|
|
||||||
|
|
||||||
@Column()
|
|
||||||
CHILD4_CODE?: string | null;
|
|
||||||
|
|
||||||
@Column()
|
@Column()
|
||||||
orgChild1PhoneEx?: string;
|
orgChild1PhoneEx?: string;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -146,46 +146,6 @@ export class OrgChild2 extends EntityBase {
|
||||||
})
|
})
|
||||||
JOB_CODE: string;
|
JOB_CODE: string;
|
||||||
|
|
||||||
@Column({
|
|
||||||
nullable: true,
|
|
||||||
length: 3,
|
|
||||||
comment: "ROOT_CODE",
|
|
||||||
default: null,
|
|
||||||
})
|
|
||||||
ROOT_CODE: string;
|
|
||||||
|
|
||||||
@Column({
|
|
||||||
nullable: true,
|
|
||||||
length: 3,
|
|
||||||
comment: "CHILD1_CODE",
|
|
||||||
default: null,
|
|
||||||
})
|
|
||||||
CHILD1_CODE: string;
|
|
||||||
|
|
||||||
@Column({
|
|
||||||
nullable: true,
|
|
||||||
length: 3,
|
|
||||||
comment: "CHILD2_CODE",
|
|
||||||
default: null,
|
|
||||||
})
|
|
||||||
CHILD2_CODE: string;
|
|
||||||
|
|
||||||
@Column({
|
|
||||||
nullable: true,
|
|
||||||
length: 3,
|
|
||||||
comment: "CHILD3_CODE",
|
|
||||||
default: null,
|
|
||||||
})
|
|
||||||
CHILD3_CODE: string;
|
|
||||||
|
|
||||||
@Column({
|
|
||||||
nullable: true,
|
|
||||||
length: 3,
|
|
||||||
comment: "CHILD4_CODE",
|
|
||||||
default: null,
|
|
||||||
})
|
|
||||||
CHILD4_CODE: string;
|
|
||||||
|
|
||||||
@Column({
|
@Column({
|
||||||
nullable: true,
|
nullable: true,
|
||||||
length: 40,
|
length: 40,
|
||||||
|
|
@ -262,21 +222,6 @@ export class CreateOrgChild2 {
|
||||||
@Column()
|
@Column()
|
||||||
JOB_CODE?: string;
|
JOB_CODE?: string;
|
||||||
|
|
||||||
@Column()
|
|
||||||
ROOT_CODE?: string;
|
|
||||||
|
|
||||||
@Column()
|
|
||||||
CHILD1_CODE?: string;
|
|
||||||
|
|
||||||
@Column()
|
|
||||||
CHILD2_CODE?: string;
|
|
||||||
|
|
||||||
@Column()
|
|
||||||
CHILD3_CODE?: string;
|
|
||||||
|
|
||||||
@Column()
|
|
||||||
CHILD4_CODE?: string;
|
|
||||||
|
|
||||||
@Column()
|
@Column()
|
||||||
orgChild2PhoneEx?: string;
|
orgChild2PhoneEx?: string;
|
||||||
|
|
||||||
|
|
@ -324,21 +269,6 @@ export class UpdateOrgChild2 {
|
||||||
@Column()
|
@Column()
|
||||||
JOB_CODE?: string | null;
|
JOB_CODE?: string | null;
|
||||||
|
|
||||||
@Column()
|
|
||||||
ROOT_CODE?: string | null;
|
|
||||||
|
|
||||||
@Column()
|
|
||||||
CHILD1_CODE?: string | null;
|
|
||||||
|
|
||||||
@Column()
|
|
||||||
CHILD2_CODE?: string | null;
|
|
||||||
|
|
||||||
@Column()
|
|
||||||
CHILD3_CODE?: string | null;
|
|
||||||
|
|
||||||
@Column()
|
|
||||||
CHILD4_CODE?: string | null;
|
|
||||||
|
|
||||||
@Column()
|
@Column()
|
||||||
orgChild2PhoneEx?: string;
|
orgChild2PhoneEx?: string;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -153,46 +153,6 @@ export class OrgChild3 extends EntityBase {
|
||||||
})
|
})
|
||||||
JOB_CODE: string;
|
JOB_CODE: string;
|
||||||
|
|
||||||
@Column({
|
|
||||||
nullable: true,
|
|
||||||
length: 3,
|
|
||||||
comment: "ROOT_CODE",
|
|
||||||
default: null,
|
|
||||||
})
|
|
||||||
ROOT_CODE: string;
|
|
||||||
|
|
||||||
@Column({
|
|
||||||
nullable: true,
|
|
||||||
length: 3,
|
|
||||||
comment: "CHILD1_CODE",
|
|
||||||
default: null,
|
|
||||||
})
|
|
||||||
CHILD1_CODE: string;
|
|
||||||
|
|
||||||
@Column({
|
|
||||||
nullable: true,
|
|
||||||
length: 3,
|
|
||||||
comment: "CHILD2_CODE",
|
|
||||||
default: null,
|
|
||||||
})
|
|
||||||
CHILD2_CODE: string;
|
|
||||||
|
|
||||||
@Column({
|
|
||||||
nullable: true,
|
|
||||||
length: 3,
|
|
||||||
comment: "CHILD3_CODE",
|
|
||||||
default: null,
|
|
||||||
})
|
|
||||||
CHILD3_CODE: string;
|
|
||||||
|
|
||||||
@Column({
|
|
||||||
nullable: true,
|
|
||||||
length: 3,
|
|
||||||
comment: "CHILD4_CODE",
|
|
||||||
default: null,
|
|
||||||
})
|
|
||||||
CHILD4_CODE: string;
|
|
||||||
|
|
||||||
@Column({
|
@Column({
|
||||||
nullable: true,
|
nullable: true,
|
||||||
length: 40,
|
length: 40,
|
||||||
|
|
@ -270,21 +230,6 @@ export class CreateOrgChild3 {
|
||||||
@Column()
|
@Column()
|
||||||
JOB_CODE?: string;
|
JOB_CODE?: string;
|
||||||
|
|
||||||
@Column()
|
|
||||||
ROOT_CODE?: string;
|
|
||||||
|
|
||||||
@Column()
|
|
||||||
CHILD1_CODE?: string;
|
|
||||||
|
|
||||||
@Column()
|
|
||||||
CHILD2_CODE?: string;
|
|
||||||
|
|
||||||
@Column()
|
|
||||||
CHILD3_CODE?: string;
|
|
||||||
|
|
||||||
@Column()
|
|
||||||
CHILD4_CODE?: string;
|
|
||||||
|
|
||||||
@Column()
|
@Column()
|
||||||
orgChild3PhoneEx?: string;
|
orgChild3PhoneEx?: string;
|
||||||
|
|
||||||
|
|
@ -334,21 +279,6 @@ export class UpdateOrgChild3 {
|
||||||
@Column()
|
@Column()
|
||||||
JOB_CODE?: string | null;
|
JOB_CODE?: string | null;
|
||||||
|
|
||||||
@Column()
|
|
||||||
ROOT_CODE?: string | null;
|
|
||||||
|
|
||||||
@Column()
|
|
||||||
CHILD1_CODE?: string | null;
|
|
||||||
|
|
||||||
@Column()
|
|
||||||
CHILD2_CODE?: string | null;
|
|
||||||
|
|
||||||
@Column()
|
|
||||||
CHILD3_CODE?: string | null;
|
|
||||||
|
|
||||||
@Column()
|
|
||||||
CHILD4_CODE?: string | null;
|
|
||||||
|
|
||||||
@Column()
|
@Column()
|
||||||
orgChild3PhoneEx?: string;
|
orgChild3PhoneEx?: string;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -158,46 +158,6 @@ export class OrgChild4 extends EntityBase {
|
||||||
})
|
})
|
||||||
JOB_CODE: string;
|
JOB_CODE: string;
|
||||||
|
|
||||||
@Column({
|
|
||||||
nullable: true,
|
|
||||||
length: 3,
|
|
||||||
comment: "ROOT_CODE",
|
|
||||||
default: null,
|
|
||||||
})
|
|
||||||
ROOT_CODE: string;
|
|
||||||
|
|
||||||
@Column({
|
|
||||||
nullable: true,
|
|
||||||
length: 3,
|
|
||||||
comment: "CHILD1_CODE",
|
|
||||||
default: null,
|
|
||||||
})
|
|
||||||
CHILD1_CODE: string;
|
|
||||||
|
|
||||||
@Column({
|
|
||||||
nullable: true,
|
|
||||||
length: 3,
|
|
||||||
comment: "CHILD2_CODE",
|
|
||||||
default: null,
|
|
||||||
})
|
|
||||||
CHILD2_CODE: string;
|
|
||||||
|
|
||||||
@Column({
|
|
||||||
nullable: true,
|
|
||||||
length: 3,
|
|
||||||
comment: "CHILD3_CODE",
|
|
||||||
default: null,
|
|
||||||
})
|
|
||||||
CHILD3_CODE: string;
|
|
||||||
|
|
||||||
@Column({
|
|
||||||
nullable: true,
|
|
||||||
length: 3,
|
|
||||||
comment: "CHILD4_CODE",
|
|
||||||
default: null,
|
|
||||||
})
|
|
||||||
CHILD4_CODE: string;
|
|
||||||
|
|
||||||
@Column({
|
@Column({
|
||||||
nullable: true,
|
nullable: true,
|
||||||
length: 40,
|
length: 40,
|
||||||
|
|
@ -276,21 +236,6 @@ export class CreateOrgChild4 {
|
||||||
@Column()
|
@Column()
|
||||||
JOB_CODE?: string;
|
JOB_CODE?: string;
|
||||||
|
|
||||||
@Column()
|
|
||||||
ROOT_CODE?: string;
|
|
||||||
|
|
||||||
@Column()
|
|
||||||
CHILD1_CODE?: string;
|
|
||||||
|
|
||||||
@Column()
|
|
||||||
CHILD2_CODE?: string;
|
|
||||||
|
|
||||||
@Column()
|
|
||||||
CHILD3_CODE?: string;
|
|
||||||
|
|
||||||
@Column()
|
|
||||||
CHILD4_CODE?: string;
|
|
||||||
|
|
||||||
@Column()
|
@Column()
|
||||||
orgChild4PhoneEx?: string;
|
orgChild4PhoneEx?: string;
|
||||||
|
|
||||||
|
|
@ -338,21 +283,6 @@ export class UpdateOrgChild4 {
|
||||||
@Column()
|
@Column()
|
||||||
JOB_CODE?: string | null;
|
JOB_CODE?: string | null;
|
||||||
|
|
||||||
@Column()
|
|
||||||
ROOT_CODE?: string | null;
|
|
||||||
|
|
||||||
@Column()
|
|
||||||
CHILD1_CODE?: string | null;
|
|
||||||
|
|
||||||
@Column()
|
|
||||||
CHILD2_CODE?: string | null;
|
|
||||||
|
|
||||||
@Column()
|
|
||||||
CHILD3_CODE?: string | null;
|
|
||||||
|
|
||||||
@Column()
|
|
||||||
CHILD4_CODE?: string | null;
|
|
||||||
|
|
||||||
@Column()
|
@Column()
|
||||||
orgChild4PhoneEx?: string;
|
orgChild4PhoneEx?: string;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -166,46 +166,6 @@ export class OrgRoot extends EntityBase {
|
||||||
})
|
})
|
||||||
JOB_CODE: string;
|
JOB_CODE: string;
|
||||||
|
|
||||||
@Column({
|
|
||||||
nullable: true,
|
|
||||||
length: 3,
|
|
||||||
comment: "ROOT_CODE",
|
|
||||||
default: null,
|
|
||||||
})
|
|
||||||
ROOT_CODE: string;
|
|
||||||
|
|
||||||
@Column({
|
|
||||||
nullable: true,
|
|
||||||
length: 3,
|
|
||||||
comment: "CHILD1_CODE",
|
|
||||||
default: null,
|
|
||||||
})
|
|
||||||
CHILD1_CODE: string;
|
|
||||||
|
|
||||||
@Column({
|
|
||||||
nullable: true,
|
|
||||||
length: 3,
|
|
||||||
comment: "CHILD2_CODE",
|
|
||||||
default: null,
|
|
||||||
})
|
|
||||||
CHILD2_CODE: string;
|
|
||||||
|
|
||||||
@Column({
|
|
||||||
nullable: true,
|
|
||||||
length: 3,
|
|
||||||
comment: "CHILD3_CODE",
|
|
||||||
default: null,
|
|
||||||
})
|
|
||||||
CHILD3_CODE: string;
|
|
||||||
|
|
||||||
@Column({
|
|
||||||
nullable: true,
|
|
||||||
length: 3,
|
|
||||||
comment: "CHILD4_CODE",
|
|
||||||
default: null,
|
|
||||||
})
|
|
||||||
CHILD4_CODE: string;
|
|
||||||
|
|
||||||
@ManyToOne(() => OrgRevision, (orgRevision) => orgRevision.orgRoots)
|
@ManyToOne(() => OrgRevision, (orgRevision) => orgRevision.orgRoots)
|
||||||
@JoinColumn({ name: "orgRevisionId" })
|
@JoinColumn({ name: "orgRevisionId" })
|
||||||
orgRevision: OrgRevision;
|
orgRevision: OrgRevision;
|
||||||
|
|
@ -266,21 +226,6 @@ export class CreateOrgRoot {
|
||||||
@Column()
|
@Column()
|
||||||
JOB_CODE?: string;
|
JOB_CODE?: string;
|
||||||
|
|
||||||
@Column()
|
|
||||||
ROOT_CODE?: string;
|
|
||||||
|
|
||||||
@Column()
|
|
||||||
CHILD1_CODE?: string;
|
|
||||||
|
|
||||||
@Column()
|
|
||||||
CHILD2_CODE?: string;
|
|
||||||
|
|
||||||
@Column()
|
|
||||||
CHILD3_CODE?: string;
|
|
||||||
|
|
||||||
@Column()
|
|
||||||
CHILD4_CODE?: string;
|
|
||||||
|
|
||||||
@Column()
|
@Column()
|
||||||
orgRootPhoneEx?: string;
|
orgRootPhoneEx?: string;
|
||||||
|
|
||||||
|
|
@ -336,21 +281,6 @@ export class UpdateOrgRoot {
|
||||||
@Column()
|
@Column()
|
||||||
JOB_CODE?: string | null;
|
JOB_CODE?: string | null;
|
||||||
|
|
||||||
@Column()
|
|
||||||
ROOT_CODE?: string | null;
|
|
||||||
|
|
||||||
@Column()
|
|
||||||
CHILD1_CODE?: string | null;
|
|
||||||
|
|
||||||
@Column()
|
|
||||||
CHILD2_CODE?: string | null;
|
|
||||||
|
|
||||||
@Column()
|
|
||||||
CHILD3_CODE?: string | null;
|
|
||||||
|
|
||||||
@Column()
|
|
||||||
CHILD4_CODE?: string | null;
|
|
||||||
|
|
||||||
@Column()
|
@Column()
|
||||||
orgRootPhoneEx?: string;
|
orgRootPhoneEx?: string;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -31,14 +31,6 @@ export class ProfileChangeName extends EntityBase {
|
||||||
})
|
})
|
||||||
prefix: string;
|
prefix: string;
|
||||||
|
|
||||||
@Column({
|
|
||||||
nullable: true,
|
|
||||||
comment: "ยศ",
|
|
||||||
length: 40,
|
|
||||||
default: null,
|
|
||||||
})
|
|
||||||
rank: string;
|
|
||||||
|
|
||||||
@Column({
|
@Column({
|
||||||
nullable: true,
|
nullable: true,
|
||||||
length: 100,
|
length: 100,
|
||||||
|
|
@ -111,7 +103,6 @@ export class CreateProfileChangeName {
|
||||||
profileId: string | null;
|
profileId: string | null;
|
||||||
prefixId: string | null;
|
prefixId: string | null;
|
||||||
prefix: string | null;
|
prefix: string | null;
|
||||||
rank: string | null;
|
|
||||||
firstName: string | null;
|
firstName: string | null;
|
||||||
lastName: string | null;
|
lastName: string | null;
|
||||||
status: string | null;
|
status: string | null;
|
||||||
|
|
@ -122,7 +113,6 @@ export class CreateProfileChangeNameEmployee {
|
||||||
profileEmployeeId: string | null;
|
profileEmployeeId: string | null;
|
||||||
prefixId: string | null;
|
prefixId: string | null;
|
||||||
prefix: string | null;
|
prefix: string | null;
|
||||||
rank: string | null;
|
|
||||||
firstName: string | null;
|
firstName: string | null;
|
||||||
lastName: string | null;
|
lastName: string | null;
|
||||||
status: string | null;
|
status: string | null;
|
||||||
|
|
@ -132,7 +122,6 @@ export class CreateProfileChangeNameEmployee {
|
||||||
export type UpdateProfileChangeName = {
|
export type UpdateProfileChangeName = {
|
||||||
prefixId?: string | null;
|
prefixId?: string | null;
|
||||||
prefix?: string | null;
|
prefix?: string | null;
|
||||||
rank?: string | null;
|
|
||||||
firstName?: string | null;
|
firstName?: string | null;
|
||||||
lastName?: string | null;
|
lastName?: string | null;
|
||||||
status?: string | null;
|
status?: string | null;
|
||||||
|
|
|
||||||
|
|
@ -20,14 +20,6 @@ export class ProfileChangeNameHistory extends EntityBase {
|
||||||
})
|
})
|
||||||
prefix: string;
|
prefix: string;
|
||||||
|
|
||||||
@Column({
|
|
||||||
nullable: true,
|
|
||||||
comment: "ยศ",
|
|
||||||
length: 40,
|
|
||||||
default: null,
|
|
||||||
})
|
|
||||||
rank: string;
|
|
||||||
|
|
||||||
@Column({
|
@Column({
|
||||||
nullable: true,
|
nullable: true,
|
||||||
length: 100,
|
length: 100,
|
||||||
|
|
@ -87,7 +79,6 @@ export class CreateProfileChangeNameHistory {
|
||||||
profileChangeNameId: string | null;
|
profileChangeNameId: string | null;
|
||||||
prefixId: string | null;
|
prefixId: string | null;
|
||||||
prefix: string | null;
|
prefix: string | null;
|
||||||
rank: string | null;
|
|
||||||
firstName: string | null;
|
firstName: string | null;
|
||||||
lastName: string | null;
|
lastName: string | null;
|
||||||
status: string | null;
|
status: string | null;
|
||||||
|
|
@ -98,7 +89,6 @@ export type UpdateProfileChangeNameHistory = {
|
||||||
profileChangeNameId?: string | null;
|
profileChangeNameId?: string | null;
|
||||||
prefixId?: string | null;
|
prefixId?: string | null;
|
||||||
prefix?: string | null;
|
prefix?: string | null;
|
||||||
rank?: string | null;
|
|
||||||
firstName?: string | null;
|
firstName?: string | null;
|
||||||
lastName?: string | null;
|
lastName?: string | null;
|
||||||
status?: string | null;
|
status?: string | null;
|
||||||
|
|
|
||||||
|
|
@ -62,14 +62,6 @@ export class ProfileDiscipline extends EntityBase {
|
||||||
})
|
})
|
||||||
refCommandNo: string;
|
refCommandNo: string;
|
||||||
|
|
||||||
@Column({
|
|
||||||
nullable: true,
|
|
||||||
length: 40,
|
|
||||||
comment: "คีย์นอก(FK)ของตาราง command",
|
|
||||||
default: null,
|
|
||||||
})
|
|
||||||
refCommandId: string;
|
|
||||||
|
|
||||||
@Column({
|
@Column({
|
||||||
nullable: true,
|
nullable: true,
|
||||||
comment: "ล้างมลทิน",
|
comment: "ล้างมลทิน",
|
||||||
|
|
|
||||||
|
|
@ -51,14 +51,6 @@ export class ProfileDisciplineHistory extends EntityBase {
|
||||||
})
|
})
|
||||||
refCommandNo: string;
|
refCommandNo: string;
|
||||||
|
|
||||||
@Column({
|
|
||||||
nullable: true,
|
|
||||||
length: 40,
|
|
||||||
comment: "คีย์นอก(FK)ของตาราง command",
|
|
||||||
default: null,
|
|
||||||
})
|
|
||||||
refCommandId: string;
|
|
||||||
|
|
||||||
@Column({
|
@Column({
|
||||||
nullable: true,
|
nullable: true,
|
||||||
comment: "ล้างมลทิน",
|
comment: "ล้างมลทิน",
|
||||||
|
|
|
||||||
|
|
@ -1014,7 +1014,7 @@ export class UpdateInformationProfileEmployee {
|
||||||
}
|
}
|
||||||
|
|
||||||
export type UpdateProfileEmployee = {
|
export type UpdateProfileEmployee = {
|
||||||
prefix?: string | null;
|
prefix: string;
|
||||||
rank?: string | null;
|
rank?: string | null;
|
||||||
firstName: string;
|
firstName: string;
|
||||||
lastName: string;
|
lastName: string;
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,7 @@ import { PosMaster } from "../entities/PosMaster";
|
||||||
import { Position } from "../entities/Position";
|
import { Position } from "../entities/Position";
|
||||||
import { EmployeePosMaster } from "../entities/EmployeePosMaster";
|
import { EmployeePosMaster } from "../entities/EmployeePosMaster";
|
||||||
import { EmployeePosition } from "../entities/EmployeePosition";
|
import { EmployeePosition } from "../entities/EmployeePosition";
|
||||||
import { EntityManager, In, IsNull, MoreThan, Not } from "typeorm";
|
import { In, IsNull, MoreThan, Not } from "typeorm";
|
||||||
import { RequestWithUser } from "../middlewares/user";
|
import { RequestWithUser } from "../middlewares/user";
|
||||||
import { Command } from "../entities/Command";
|
import { Command } from "../entities/Command";
|
||||||
import { ProfileSalary } from "../entities/ProfileSalary";
|
import { ProfileSalary } from "../entities/ProfileSalary";
|
||||||
|
|
@ -254,23 +254,14 @@ export function calculateRetireYear(birthDate: Date) {
|
||||||
|
|
||||||
return yy + 61;
|
return yy + 61;
|
||||||
}
|
}
|
||||||
export async function removeProfileInOrganize(
|
export async function removeProfileInOrganize(profileId: string, type: string) {
|
||||||
profileId: string,
|
const currentRevision = await AppDataSource.getRepository(OrgRevision)
|
||||||
type: string,
|
|
||||||
manager?: EntityManager,
|
|
||||||
) {
|
|
||||||
// ถ้าส่ง manager เข้ามา → ทุก query/update อยู่ใน transaction ของ caller (all-or-nothing)
|
|
||||||
// ถ้าไม่ส่ง → ใช้ global DataSource เหมือนเดิม (backward compatible)
|
|
||||||
const ds = manager ?? AppDataSource;
|
|
||||||
const currentRevision = await ds
|
|
||||||
.getRepository(OrgRevision)
|
|
||||||
.createQueryBuilder("orgRevision")
|
.createQueryBuilder("orgRevision")
|
||||||
.where("orgRevision.orgRevisionIsDraft = false")
|
.where("orgRevision.orgRevisionIsDraft = false")
|
||||||
.andWhere("orgRevision.orgRevisionIsCurrent = true")
|
.andWhere("orgRevision.orgRevisionIsCurrent = true")
|
||||||
.getOne();
|
.getOne();
|
||||||
|
|
||||||
const draftRevision = await ds
|
const draftRevision = await AppDataSource.getRepository(OrgRevision)
|
||||||
.getRepository(OrgRevision)
|
|
||||||
.createQueryBuilder("orgRevision")
|
.createQueryBuilder("orgRevision")
|
||||||
.where("orgRevision.orgRevisionIsDraft = true")
|
.where("orgRevision.orgRevisionIsDraft = true")
|
||||||
.andWhere("orgRevision.orgRevisionIsCurrent = false")
|
.andWhere("orgRevision.orgRevisionIsCurrent = false")
|
||||||
|
|
@ -280,30 +271,26 @@ export async function removeProfileInOrganize(
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (type === "OFFICER") {
|
if (type === "OFFICER") {
|
||||||
const findProfileInposMaster = await ds
|
const findProfileInposMaster = await AppDataSource.getRepository(PosMaster)
|
||||||
.getRepository(PosMaster)
|
|
||||||
.createQueryBuilder("posMaster")
|
.createQueryBuilder("posMaster")
|
||||||
.where("posMaster.orgRevisionId = :orgRevisionId", { orgRevisionId: currentRevision?.id })
|
.where("posMaster.orgRevisionId = :orgRevisionId", { orgRevisionId: currentRevision?.id })
|
||||||
.andWhere("posMaster.current_holderId = :profileId", { profileId })
|
.andWhere("posMaster.current_holderId = :profileId", { profileId })
|
||||||
.getOne();
|
.getOne();
|
||||||
|
|
||||||
await ds
|
await AppDataSource.getRepository(PosMaster)
|
||||||
.getRepository(PosMaster)
|
|
||||||
.createQueryBuilder()
|
.createQueryBuilder()
|
||||||
.update(PosMaster)
|
.update(PosMaster)
|
||||||
.set({ current_holderId: null, isSit: false })
|
.set({ current_holderId: null, isSit: false })
|
||||||
.where("id = :id", { id: findProfileInposMaster?.id })
|
.where("id = :id", { id: findProfileInposMaster?.id })
|
||||||
.execute();
|
.execute();
|
||||||
|
|
||||||
const findProfileInposMasterDraft = await ds
|
const findProfileInposMasterDraft = await AppDataSource.getRepository(PosMaster)
|
||||||
.getRepository(PosMaster)
|
|
||||||
.createQueryBuilder("posMaster")
|
.createQueryBuilder("posMaster")
|
||||||
.where("posMaster.orgRevisionId = :orgRevisionId", { orgRevisionId: draftRevision?.id })
|
.where("posMaster.orgRevisionId = :orgRevisionId", { orgRevisionId: draftRevision?.id })
|
||||||
.andWhere("posMaster.next_holderId = :profileId", { profileId })
|
.andWhere("posMaster.next_holderId = :profileId", { profileId })
|
||||||
.getOne();
|
.getOne();
|
||||||
|
|
||||||
await ds
|
await AppDataSource.getRepository(PosMaster)
|
||||||
.getRepository(PosMaster)
|
|
||||||
.createQueryBuilder()
|
.createQueryBuilder()
|
||||||
.update(PosMaster)
|
.update(PosMaster)
|
||||||
.set({ next_holderId: null, isSit: false })
|
.set({ next_holderId: null, isSit: false })
|
||||||
|
|
@ -313,8 +300,7 @@ export async function removeProfileInOrganize(
|
||||||
if (!findProfileInposMaster && !findProfileInposMasterDraft) {
|
if (!findProfileInposMaster && !findProfileInposMasterDraft) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const findPosition = await ds
|
const findPosition = await AppDataSource.getRepository(Position)
|
||||||
.getRepository(Position)
|
|
||||||
.createQueryBuilder("position")
|
.createQueryBuilder("position")
|
||||||
.where("position.posMasterId = :posMasterId", { posMasterId: findProfileInposMaster?.id })
|
.where("position.posMasterId = :posMasterId", { posMasterId: findProfileInposMaster?.id })
|
||||||
.getMany();
|
.getMany();
|
||||||
|
|
@ -322,8 +308,7 @@ export async function removeProfileInOrganize(
|
||||||
if (!findPosition) {
|
if (!findPosition) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
await ds
|
await AppDataSource.getRepository(Position)
|
||||||
.getRepository(Position)
|
|
||||||
.createQueryBuilder()
|
.createQueryBuilder()
|
||||||
.update(Position)
|
.update(Position)
|
||||||
.set({ positionIsSelected: false })
|
.set({ positionIsSelected: false })
|
||||||
|
|
@ -331,27 +316,24 @@ export async function removeProfileInOrganize(
|
||||||
.execute();
|
.execute();
|
||||||
}
|
}
|
||||||
if (type === "EMPLOYEE") {
|
if (type === "EMPLOYEE") {
|
||||||
const findProfileInEmpPosMaster = await ds
|
const findProfileInEmpPosMaster = await AppDataSource.getRepository(EmployeePosMaster)
|
||||||
.getRepository(EmployeePosMaster)
|
|
||||||
.createQueryBuilder("employeePosMaster")
|
.createQueryBuilder("employeePosMaster")
|
||||||
.where("employeePosMaster.orgRevisionId = :orgRevisionId", {
|
.where("employeePosMaster.orgRevisionId = :orgRevisionId", {
|
||||||
orgRevisionId: currentRevision?.id,
|
orgRevisionId: currentRevision?.id,
|
||||||
})
|
})
|
||||||
.andWhere("employeePosMaster.current_holderId = :profileId", { profileId })
|
.andWhere("employeePosMaster.current_holderId = :profileId", { profileId })
|
||||||
.getOne();
|
.getOne();
|
||||||
await ds
|
await AppDataSource.getRepository(EmployeePosMaster)
|
||||||
.getRepository(EmployeePosMaster)
|
|
||||||
.createQueryBuilder()
|
.createQueryBuilder()
|
||||||
.update(EmployeePosMaster)
|
.update(EmployeePosMaster)
|
||||||
.set({ current_holderId: null, isSit: false })
|
.set({ current_holderId: null })
|
||||||
.where("id = :id", { id: findProfileInEmpPosMaster?.id })
|
.where("id = :id", { id: findProfileInEmpPosMaster?.id })
|
||||||
.execute();
|
.execute();
|
||||||
|
|
||||||
if (!findProfileInEmpPosMaster) {
|
if (!findProfileInEmpPosMaster) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const findEmpPosition = await ds
|
const findEmpPosition = await AppDataSource.getRepository(EmployeePosition)
|
||||||
.getRepository(EmployeePosition)
|
|
||||||
.createQueryBuilder("employeePosition")
|
.createQueryBuilder("employeePosition")
|
||||||
.where("employeePosition.posMasterId = :posMasterId", {
|
.where("employeePosition.posMasterId = :posMasterId", {
|
||||||
posMasterId: findProfileInEmpPosMaster?.id,
|
posMasterId: findProfileInEmpPosMaster?.id,
|
||||||
|
|
@ -362,8 +344,7 @@ export async function removeProfileInOrganize(
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
await ds
|
await AppDataSource.getRepository(EmployeePosition)
|
||||||
.getRepository(EmployeePosition)
|
|
||||||
.createQueryBuilder()
|
.createQueryBuilder()
|
||||||
.update(EmployeePosition)
|
.update(EmployeePosition)
|
||||||
.set({ positionIsSelected: false })
|
.set({ positionIsSelected: false })
|
||||||
|
|
@ -372,10 +353,8 @@ export async function removeProfileInOrganize(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function removePostMasterAct(profileId: string, manager?: EntityManager) {
|
export async function removePostMasterAct(profileId: string) {
|
||||||
const ds = manager ?? AppDataSource;
|
const currentRevision = await AppDataSource.getRepository(OrgRevision)
|
||||||
const currentRevision = await ds
|
|
||||||
.getRepository(OrgRevision)
|
|
||||||
.createQueryBuilder("orgRevision")
|
.createQueryBuilder("orgRevision")
|
||||||
.where("orgRevision.orgRevisionIsDraft = false")
|
.where("orgRevision.orgRevisionIsDraft = false")
|
||||||
.andWhere("orgRevision.orgRevisionIsCurrent = true")
|
.andWhere("orgRevision.orgRevisionIsCurrent = true")
|
||||||
|
|
@ -385,8 +364,7 @@ export async function removePostMasterAct(profileId: string, manager?: EntityMan
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const findProfileInposMaster = await ds
|
const findProfileInposMaster = await AppDataSource.getRepository(PosMaster)
|
||||||
.getRepository(PosMaster)
|
|
||||||
.createQueryBuilder("posMaster")
|
.createQueryBuilder("posMaster")
|
||||||
.where("posMaster.orgRevisionId = :orgRevisionId", { orgRevisionId: currentRevision?.id })
|
.where("posMaster.orgRevisionId = :orgRevisionId", { orgRevisionId: currentRevision?.id })
|
||||||
.andWhere("posMaster.current_holderId = :profileId", { profileId })
|
.andWhere("posMaster.current_holderId = :profileId", { profileId })
|
||||||
|
|
@ -396,12 +374,11 @@ export async function removePostMasterAct(profileId: string, manager?: EntityMan
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const posMasterAct = await ds
|
const posMasterAct = await AppDataSource.getRepository(PosMasterAct)
|
||||||
.getRepository(PosMasterAct)
|
|
||||||
.createQueryBuilder("posMasterAct")
|
.createQueryBuilder("posMasterAct")
|
||||||
.where("posMasterAct.posMasterChildId = :posMasterChildId", { posMasterChildId: findProfileInposMaster.id })
|
.where("posMasterAct.posMasterChildId = :posMasterChildId", { posMasterChildId: findProfileInposMaster.id })
|
||||||
.getMany();
|
.getMany();
|
||||||
await ds.getRepository(PosMasterAct).remove(posMasterAct);
|
await AppDataSource.getRepository(PosMasterAct).remove(posMasterAct);
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function checkReturnCommandType(commandId: string) {
|
export async function checkReturnCommandType(commandId: string) {
|
||||||
|
|
@ -418,6 +395,43 @@ export async function checkReturnCommandType(commandId: string) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export async function checkExceptCommandType(commandId: string) {
|
||||||
|
const commandRepository = AppDataSource.getRepository(Command);
|
||||||
|
const commandReciveRepository = AppDataSource.getRepository(CommandRecive);
|
||||||
|
const _type = await commandRepository.findOne({
|
||||||
|
where: {
|
||||||
|
id: commandId,
|
||||||
|
},
|
||||||
|
relations: ["commandType"],
|
||||||
|
});
|
||||||
|
if (!["C-PM-25", "C-PM-26"].includes(String(_type?.commandType.code))) {
|
||||||
|
return { status: false, LeaveType: null, leaveRemark: null };
|
||||||
|
}
|
||||||
|
const _commandRecive = await commandReciveRepository.findOne({
|
||||||
|
where: { commandId: commandId },
|
||||||
|
});
|
||||||
|
|
||||||
|
let _leaveType: string = "";
|
||||||
|
switch (String(_type?.commandType.code)) {
|
||||||
|
case "C-PM-25": {
|
||||||
|
_leaveType = "DISCIPLINE_SUSPEND"; //คำสั่งพักจากราชการ
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case "C-PM-26": {
|
||||||
|
_leaveType = "DISCIPLINE_TEMP_SUSPEND"; //คำสั่งให้ออกจากราชการไว้ก่อน
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
default: {
|
||||||
|
_leaveType = "";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
status: true,
|
||||||
|
LeaveType: _leaveType,
|
||||||
|
leaveRemark: _commandRecive ? _commandRecive.remarkVertical : null,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
export async function checkCommandType(commandId: string) {
|
export async function checkCommandType(commandId: string) {
|
||||||
const commandRepository = AppDataSource.getRepository(Command);
|
const commandRepository = AppDataSource.getRepository(Command);
|
||||||
const commandReciveRepository = AppDataSource.getRepository(CommandRecive);
|
const commandReciveRepository = AppDataSource.getRepository(CommandRecive);
|
||||||
|
|
@ -437,10 +451,7 @@ export async function checkCommandType(commandId: string) {
|
||||||
"C-PM-23",
|
"C-PM-23",
|
||||||
"C-PM-19",
|
"C-PM-19",
|
||||||
"C-PM-20",
|
"C-PM-20",
|
||||||
"C-PM-25",
|
|
||||||
"C-PM-26",
|
|
||||||
"C-PM-43",
|
"C-PM-43",
|
||||||
"C-PM-48"
|
|
||||||
].includes(String(_type?.commandType.code))
|
].includes(String(_type?.commandType.code))
|
||||||
) {
|
) {
|
||||||
// return false;
|
// return false;
|
||||||
|
|
@ -489,26 +500,11 @@ export async function checkCommandType(commandId: string) {
|
||||||
_retireTypeName = "ลาออกจากราชการ";
|
_retireTypeName = "ลาออกจากราชการ";
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case "C-PM-25": {
|
|
||||||
_leaveType = "DISCIPLINE_SUSPEND";
|
|
||||||
_retireTypeName = "พักจากราชการ";
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case "C-PM-26": {
|
|
||||||
_leaveType = "DISCIPLINE_TEMP_SUSPEND";
|
|
||||||
_retireTypeName = "ให้ออกจากราชการไว้ก่อน";
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case "C-PM-43": {
|
case "C-PM-43": {
|
||||||
_leaveType = "RETIRE_OUT_EMP";
|
_leaveType = "RETIRE_OUT_EMP";
|
||||||
_retireTypeName = "ให้ออกจากราชการ";
|
_retireTypeName = "ให้ออกจากราชการ";
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case "C-PM-48": {
|
|
||||||
_leaveType = "RETIRE_MILITARY";
|
|
||||||
_retireTypeName = "รับราชการทหาร";
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
default: {
|
default: {
|
||||||
_leaveType = "";
|
_leaveType = "";
|
||||||
_retireTypeName = "";
|
_retireTypeName = "";
|
||||||
|
|
@ -732,8 +728,6 @@ export function commandTypePath(commandCode: string): string | null {
|
||||||
return "/salary/report/command36/employee/report"; //SALARY
|
return "/salary/report/command36/employee/report"; //SALARY
|
||||||
case "C-PM-47":
|
case "C-PM-47":
|
||||||
return "/placement/appointment/gazette/report";
|
return "/placement/appointment/gazette/report";
|
||||||
case "C-PM-48":
|
|
||||||
return "/retirement/resign/command48/report";
|
|
||||||
default:
|
default:
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,204 +0,0 @@
|
||||||
/**
|
|
||||||
* Helper functions for Keycloak user operations
|
|
||||||
*
|
|
||||||
* สร้างเพื่อแยก Keycloak operations ออกจาก DB transaction
|
|
||||||
* และใช้ profile data ที่ save แล้วแทน input data
|
|
||||||
*/
|
|
||||||
|
|
||||||
import {
|
|
||||||
createUser,
|
|
||||||
addUserRoles,
|
|
||||||
removeUserRoles,
|
|
||||||
updateUserAttributes,
|
|
||||||
getUserByUsername,
|
|
||||||
getRoleMappings,
|
|
||||||
} from "./index";
|
|
||||||
import { Profile } from "../entities/Profile";
|
|
||||||
import HttpError from "../interfaces/http-error";
|
|
||||||
import HttpStatusCode from "../interfaces/http-status";
|
|
||||||
|
|
||||||
/**
|
|
||||||
* สร้าง password จาก profile.birthDate เหมือน reset-password endpoint
|
|
||||||
*
|
|
||||||
* @param profile - Profile object ที่มี birthDate
|
|
||||||
* @returns password string ในรูปแบบ ddmmyyyy (เช่น "31122563")
|
|
||||||
*
|
|
||||||
* Reference: UserController.ts reset-password (บรรทัด 867-876)
|
|
||||||
*/
|
|
||||||
export function generatePasswordFromProfile(profile: Profile): string {
|
|
||||||
if (!profile.birthDate) {
|
|
||||||
throw new Error("Profile birthDate is required for password generation");
|
|
||||||
}
|
|
||||||
|
|
||||||
const _date = new Date(profile.birthDate.toDateString())
|
|
||||||
.getDate()
|
|
||||||
.toString()
|
|
||||||
.padStart(2, "0");
|
|
||||||
const _month = (
|
|
||||||
new Date(profile.birthDate.toDateString()).getMonth() + 1
|
|
||||||
)
|
|
||||||
.toString()
|
|
||||||
.padStart(2, "0");
|
|
||||||
const _year = new Date(profile.birthDate.toDateString()).getFullYear() + 543;
|
|
||||||
|
|
||||||
return `${_date}${_month}${_year}`;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Sync Keycloak user สำหรับ profile ที่ save แล้ว
|
|
||||||
*
|
|
||||||
* รวมทุก Keycloak operations:
|
|
||||||
* - ตรวจสอบว่ามี user อยู่แล้วหรือไม่
|
|
||||||
* - สร้าง user ใหม่ (ถ้ายังไม่มี) ด้วย password จาก profile.birthDate
|
|
||||||
* - กำหนด role USER
|
|
||||||
* - อัปเดต attributes (profileId, prefix)
|
|
||||||
*
|
|
||||||
* @param profile - Profile object ที่ถูก save แล้ว (ต้องมี id และ birthDate)
|
|
||||||
* @param roleList - List of roles จาก Keycloak (ผลลัพธ์จาก getRoles())
|
|
||||||
* @returns userKeycloakId string หรือ throws HttpError
|
|
||||||
*
|
|
||||||
* @throws HttpError ถ้า createUser หรือ role operations ล้มเหลว
|
|
||||||
*/
|
|
||||||
export async function syncKeycloakForProfile(
|
|
||||||
profile: Profile,
|
|
||||||
roleList: any[],
|
|
||||||
): Promise<string> {
|
|
||||||
console.log(
|
|
||||||
`[syncKeycloakForProfile] Starting for citizenId: ${profile.citizenId}, profileId: ${profile.id}`,
|
|
||||||
);
|
|
||||||
|
|
||||||
if (!profile.id) {
|
|
||||||
throw new Error("Profile ID is required for Keycloak sync");
|
|
||||||
}
|
|
||||||
|
|
||||||
// ตรวจสอบว่ามี user อยู่แล้วหรือไม่
|
|
||||||
const checkUser = await getUserByUsername(profile.citizenId);
|
|
||||||
console.log(
|
|
||||||
`[syncKeycloakForProfile] Keycloak user exists: ${checkUser.length > 0}`,
|
|
||||||
);
|
|
||||||
|
|
||||||
let userKeycloakId: string;
|
|
||||||
|
|
||||||
if (checkUser.length === 0) {
|
|
||||||
// สร้าง user ใหม่
|
|
||||||
console.log(
|
|
||||||
`[syncKeycloakForProfile] Creating new Keycloak user for citizenId: ${profile.citizenId}`,
|
|
||||||
);
|
|
||||||
|
|
||||||
// สร้าง password จาก profile.birthDate (NOT input birthDate)
|
|
||||||
const password = generatePasswordFromProfile(profile);
|
|
||||||
|
|
||||||
console.log(
|
|
||||||
`[syncKeycloakForProfile] Generated password from profile.birthDate: ${profile.birthDate}`,
|
|
||||||
);
|
|
||||||
|
|
||||||
// กรอง "." ออกจาก firstName ก่อนส่งไป keycloak
|
|
||||||
const sanitizedFirstName = profile.firstName?.replace(/\./g, "") ?? "";
|
|
||||||
|
|
||||||
console.log(
|
|
||||||
`[syncKeycloakForProfile] Creating user - firstName: ${sanitizedFirstName}, lastName: ${profile.lastName}`,
|
|
||||||
);
|
|
||||||
|
|
||||||
const createUserResult: any = await createUser(profile.citizenId, password, {
|
|
||||||
firstName: sanitizedFirstName,
|
|
||||||
lastName: profile.lastName ?? "",
|
|
||||||
});
|
|
||||||
|
|
||||||
// ตรวจสอบ createUser error
|
|
||||||
if (
|
|
||||||
createUserResult &&
|
|
||||||
typeof createUserResult === "object" &&
|
|
||||||
createUserResult.errorMessage
|
|
||||||
) {
|
|
||||||
console.error(
|
|
||||||
`[syncKeycloakForProfile] createUser FAILED - field: ${createUserResult.field}, errorMessage: ${createUserResult.errorMessage}`,
|
|
||||||
);
|
|
||||||
throw new HttpError(
|
|
||||||
HttpStatusCode.BAD_REQUEST,
|
|
||||||
`Keycloak validation failed: ${createUserResult.field} - ${createUserResult.errorMessage}`,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
userKeycloakId = createUserResult;
|
|
||||||
|
|
||||||
console.log(
|
|
||||||
`[syncKeycloakForProfile] User created successfully, userKeycloakId: ${userKeycloakId}`,
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
// ใช้ user ที่มีอยู่แล้ว
|
|
||||||
console.log(
|
|
||||||
`[syncKeycloakForProfile] Using existing Keycloak user, userKeycloakId: ${checkUser[0].id}`,
|
|
||||||
);
|
|
||||||
userKeycloakId = checkUser[0].id;
|
|
||||||
|
|
||||||
// ลบ roles เดิมแล้วกำหนด role USER ใหม่
|
|
||||||
const rolesData = await getRoleMappings(userKeycloakId);
|
|
||||||
if (rolesData) {
|
|
||||||
const _delRole = rolesData.map((x: any) => ({
|
|
||||||
id: x.id,
|
|
||||||
name: x.name,
|
|
||||||
}));
|
|
||||||
console.log(
|
|
||||||
`[syncKeycloakForProfile] Removing old roles: ${_delRole.length}`,
|
|
||||||
);
|
|
||||||
await removeUserRoles(userKeycloakId, _delRole);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// กำหนด role USER
|
|
||||||
console.log(`[syncKeycloakForProfile] Assigning USER role`);
|
|
||||||
const result = await addUserRoles(
|
|
||||||
userKeycloakId,
|
|
||||||
roleList
|
|
||||||
.filter((v) => v.name === "USER")
|
|
||||||
.map((x) => ({
|
|
||||||
id: x.id,
|
|
||||||
name: x.name,
|
|
||||||
})),
|
|
||||||
);
|
|
||||||
|
|
||||||
console.log(`[syncKeycloakForProfile] USER role assigned, result: ${result}`);
|
|
||||||
|
|
||||||
// อัปเดต attributes
|
|
||||||
console.log(`[syncKeycloakForProfile] Updating user attributes`);
|
|
||||||
await updateUserAttributes(userKeycloakId, {
|
|
||||||
profileId: [profile.id],
|
|
||||||
prefix: [profile.prefix || ""],
|
|
||||||
});
|
|
||||||
|
|
||||||
console.log(
|
|
||||||
`[syncKeycloakForProfile] Completed successfully for citizenId: ${profile.citizenId}`,
|
|
||||||
);
|
|
||||||
|
|
||||||
return userKeycloakId;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* จัดการ error จาก Keycloak operations
|
|
||||||
*
|
|
||||||
* @param error - Error object จาก Keycloak operations
|
|
||||||
* @param context - Context object { citizenId, profileId, operation }
|
|
||||||
*
|
|
||||||
* @returns HttpError ที่ formatted สำหรับ throw กลับไป
|
|
||||||
*/
|
|
||||||
export function handleKeycloakError(
|
|
||||||
error: any,
|
|
||||||
context: {
|
|
||||||
citizenId?: string;
|
|
||||||
profileId?: string;
|
|
||||||
operation: string;
|
|
||||||
},
|
|
||||||
): HttpError {
|
|
||||||
console.error(
|
|
||||||
`[KeycloakError] ${context.operation} failed for citizenId: ${context.citizenId}, profileId: ${context.profileId}`,
|
|
||||||
error,
|
|
||||||
);
|
|
||||||
|
|
||||||
const errorMessage =
|
|
||||||
error instanceof Error ? error.message : "Unknown Keycloak error";
|
|
||||||
|
|
||||||
return new HttpError(
|
|
||||||
HttpStatusCode.INTERNAL_SERVER_ERROR,
|
|
||||||
`Keycloak ${context.operation} failed: ${errorMessage}. Profile created but Keycloak sync failed - manual fix required.`,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
@ -19,7 +19,7 @@ export async function handleInternalAuth(request: express.Request) {
|
||||||
throw new HttpError(HttpStatus.UNAUTHORIZED, "Invalid API Key");
|
throw new HttpError(HttpStatus.UNAUTHORIZED, "Invalid API Key");
|
||||||
}
|
}
|
||||||
|
|
||||||
// console.log(`[InternalAuth] Authentication successful`);
|
console.log(`[InternalAuth] Authentication successful`);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
sub: "internal_service",
|
sub: "internal_service",
|
||||||
|
|
|
||||||
|
|
@ -1,15 +0,0 @@
|
||||||
import { MigrationInterface, QueryRunner } from "typeorm";
|
|
||||||
|
|
||||||
export class UpdateProfileDisciplineAddRefCommandId1780634210221 implements MigrationInterface {
|
|
||||||
name = 'UpdateProfileDisciplineAddRefCommandId1780634210221'
|
|
||||||
|
|
||||||
public async up(queryRunner: QueryRunner): Promise<void> {
|
|
||||||
await queryRunner.query(`ALTER TABLE \`profileDisciplineHistory\` ADD \`refCommandId\` varchar(40) NULL COMMENT 'คีย์นอก(FK)ของตาราง command'`);
|
|
||||||
await queryRunner.query(`ALTER TABLE \`profileDiscipline\` ADD \`refCommandId\` varchar(40) NULL COMMENT 'คีย์นอก(FK)ของตาราง command'`);
|
|
||||||
}
|
|
||||||
|
|
||||||
public async down(queryRunner: QueryRunner): Promise<void> {
|
|
||||||
await queryRunner.query(`ALTER TABLE \`profileDiscipline\` DROP COLUMN \`refCommandId\``);
|
|
||||||
await queryRunner.query(`ALTER TABLE \`profileDisciplineHistory\` DROP COLUMN \`refCommandId\``);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,16 +0,0 @@
|
||||||
import { MigrationInterface, QueryRunner } from "typeorm";
|
|
||||||
|
|
||||||
export class UpdateProfileChangeNameAddFieldRank1781174051201 implements MigrationInterface {
|
|
||||||
name = 'UpdateProfileChangeNameAddFieldRank1781174051201'
|
|
||||||
|
|
||||||
public async up(queryRunner: QueryRunner): Promise<void> {
|
|
||||||
await queryRunner.query(`ALTER TABLE \`profileChangeNameHistory\` ADD \`rank\` varchar(40) NULL COMMENT 'ยศ'`);
|
|
||||||
await queryRunner.query(`ALTER TABLE \`profileChangeName\` ADD \`rank\` varchar(40) NULL COMMENT 'ยศ'`);
|
|
||||||
}
|
|
||||||
|
|
||||||
public async down(queryRunner: QueryRunner): Promise<void> {
|
|
||||||
await queryRunner.query(`ALTER TABLE \`profileChangeName\` DROP COLUMN \`rank\``);
|
|
||||||
await queryRunner.query(`ALTER TABLE \`profileChangeNameHistory\` DROP COLUMN \`rank\``);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
@ -1,22 +0,0 @@
|
||||||
import { MigrationInterface, QueryRunner } from "typeorm";
|
|
||||||
|
|
||||||
export class UpdateRootC1ToC4AddFieldCode1781517610929 implements MigrationInterface {
|
|
||||||
name = 'UpdateRootC1ToC4AddFieldCode1781517610929'
|
|
||||||
|
|
||||||
public async up(queryRunner: QueryRunner): Promise<void> {
|
|
||||||
await queryRunner.query(`ALTER TABLE \`orgChild4\` ADD \`CHILD4_CODE\` varchar(3) NULL COMMENT 'CHILD4_CODE'`);
|
|
||||||
await queryRunner.query(`ALTER TABLE \`orgChild3\` ADD \`CHILD3_CODE\` varchar(3) NULL COMMENT 'CHILD3_CODE'`);
|
|
||||||
await queryRunner.query(`ALTER TABLE \`orgChild2\` ADD \`CHILD2_CODE\` varchar(3) NULL COMMENT 'CHILD2_CODE'`);
|
|
||||||
await queryRunner.query(`ALTER TABLE \`orgChild1\` ADD \`CHILD1_CODE\` varchar(3) NULL COMMENT 'CHILD1_CODE'`);
|
|
||||||
await queryRunner.query(`ALTER TABLE \`orgRoot\` ADD \`ROOT_CODE\` varchar(3) NULL COMMENT 'ROOT_CODE'`);
|
|
||||||
}
|
|
||||||
|
|
||||||
public async down(queryRunner: QueryRunner): Promise<void> {
|
|
||||||
await queryRunner.query(`ALTER TABLE \`orgRoot\` DROP COLUMN \`ROOT_CODE\``);
|
|
||||||
await queryRunner.query(`ALTER TABLE \`orgChild1\` DROP COLUMN \`CHILD1_CODE\``);
|
|
||||||
await queryRunner.query(`ALTER TABLE \`orgChild2\` DROP COLUMN \`CHILD2_CODE\``);
|
|
||||||
await queryRunner.query(`ALTER TABLE \`orgChild3\` DROP COLUMN \`CHILD3_CODE\``);
|
|
||||||
await queryRunner.query(`ALTER TABLE \`orgChild4\` DROP COLUMN \`CHILD4_CODE\``);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
@ -1,51 +0,0 @@
|
||||||
import { MigrationInterface, QueryRunner } from "typeorm";
|
|
||||||
|
|
||||||
export class UpdateAddFieldCodeAllTables1781577597453 implements MigrationInterface {
|
|
||||||
name = 'UpdateAddFieldCodeAllTables1781577597453'
|
|
||||||
|
|
||||||
public async up(queryRunner: QueryRunner): Promise<void> {
|
|
||||||
await queryRunner.query(`ALTER TABLE \`orgChild4\` ADD \`ROOT_CODE\` varchar(3) NULL COMMENT 'ROOT_CODE'`);
|
|
||||||
await queryRunner.query(`ALTER TABLE \`orgChild4\` ADD \`CHILD1_CODE\` varchar(3) NULL COMMENT 'CHILD1_CODE'`);
|
|
||||||
await queryRunner.query(`ALTER TABLE \`orgChild4\` ADD \`CHILD2_CODE\` varchar(3) NULL COMMENT 'CHILD2_CODE'`);
|
|
||||||
await queryRunner.query(`ALTER TABLE \`orgChild4\` ADD \`CHILD3_CODE\` varchar(3) NULL COMMENT 'CHILD3_CODE'`);
|
|
||||||
await queryRunner.query(`ALTER TABLE \`orgChild3\` ADD \`ROOT_CODE\` varchar(3) NULL COMMENT 'ROOT_CODE'`);
|
|
||||||
await queryRunner.query(`ALTER TABLE \`orgChild3\` ADD \`CHILD1_CODE\` varchar(3) NULL COMMENT 'CHILD1_CODE'`);
|
|
||||||
await queryRunner.query(`ALTER TABLE \`orgChild3\` ADD \`CHILD2_CODE\` varchar(3) NULL COMMENT 'CHILD2_CODE'`);
|
|
||||||
await queryRunner.query(`ALTER TABLE \`orgChild3\` ADD \`CHILD4_CODE\` varchar(3) NULL COMMENT 'CHILD4_CODE'`);
|
|
||||||
await queryRunner.query(`ALTER TABLE \`orgChild2\` ADD \`ROOT_CODE\` varchar(3) NULL COMMENT 'ROOT_CODE'`);
|
|
||||||
await queryRunner.query(`ALTER TABLE \`orgChild2\` ADD \`CHILD1_CODE\` varchar(3) NULL COMMENT 'CHILD1_CODE'`);
|
|
||||||
await queryRunner.query(`ALTER TABLE \`orgChild2\` ADD \`CHILD3_CODE\` varchar(3) NULL COMMENT 'CHILD3_CODE'`);
|
|
||||||
await queryRunner.query(`ALTER TABLE \`orgChild2\` ADD \`CHILD4_CODE\` varchar(3) NULL COMMENT 'CHILD4_CODE'`);
|
|
||||||
await queryRunner.query(`ALTER TABLE \`orgChild1\` ADD \`ROOT_CODE\` varchar(3) NULL COMMENT 'ROOT_CODE'`);
|
|
||||||
await queryRunner.query(`ALTER TABLE \`orgChild1\` ADD \`CHILD2_CODE\` varchar(3) NULL COMMENT 'CHILD2_CODE'`);
|
|
||||||
await queryRunner.query(`ALTER TABLE \`orgChild1\` ADD \`CHILD3_CODE\` varchar(3) NULL COMMENT 'CHILD3_CODE'`);
|
|
||||||
await queryRunner.query(`ALTER TABLE \`orgChild1\` ADD \`CHILD4_CODE\` varchar(3) NULL COMMENT 'CHILD4_CODE'`);
|
|
||||||
await queryRunner.query(`ALTER TABLE \`orgRoot\` ADD \`CHILD1_CODE\` varchar(3) NULL COMMENT 'CHILD1_CODE'`);
|
|
||||||
await queryRunner.query(`ALTER TABLE \`orgRoot\` ADD \`CHILD2_CODE\` varchar(3) NULL COMMENT 'CHILD2_CODE'`);
|
|
||||||
await queryRunner.query(`ALTER TABLE \`orgRoot\` ADD \`CHILD3_CODE\` varchar(3) NULL COMMENT 'CHILD3_CODE'`);
|
|
||||||
await queryRunner.query(`ALTER TABLE \`orgRoot\` ADD \`CHILD4_CODE\` varchar(3) NULL COMMENT 'CHILD4_CODE'`);
|
|
||||||
}
|
|
||||||
|
|
||||||
public async down(queryRunner: QueryRunner): Promise<void> {
|
|
||||||
await queryRunner.query(`ALTER TABLE \`orgRoot\` DROP COLUMN \`CHILD4_CODE\``);
|
|
||||||
await queryRunner.query(`ALTER TABLE \`orgRoot\` DROP COLUMN \`CHILD3_CODE\``);
|
|
||||||
await queryRunner.query(`ALTER TABLE \`orgRoot\` DROP COLUMN \`CHILD2_CODE\``);
|
|
||||||
await queryRunner.query(`ALTER TABLE \`orgRoot\` DROP COLUMN \`CHILD1_CODE\``);
|
|
||||||
await queryRunner.query(`ALTER TABLE \`orgChild1\` DROP COLUMN \`CHILD4_CODE\``);
|
|
||||||
await queryRunner.query(`ALTER TABLE \`orgChild1\` DROP COLUMN \`CHILD3_CODE\``);
|
|
||||||
await queryRunner.query(`ALTER TABLE \`orgChild1\` DROP COLUMN \`CHILD2_CODE\``);
|
|
||||||
await queryRunner.query(`ALTER TABLE \`orgChild1\` DROP COLUMN \`ROOT_CODE\``);
|
|
||||||
await queryRunner.query(`ALTER TABLE \`orgChild2\` DROP COLUMN \`CHILD4_CODE\``);
|
|
||||||
await queryRunner.query(`ALTER TABLE \`orgChild2\` DROP COLUMN \`CHILD3_CODE\``);
|
|
||||||
await queryRunner.query(`ALTER TABLE \`orgChild2\` DROP COLUMN \`CHILD1_CODE\``);
|
|
||||||
await queryRunner.query(`ALTER TABLE \`orgChild2\` DROP COLUMN \`ROOT_CODE\``);
|
|
||||||
await queryRunner.query(`ALTER TABLE \`orgChild3\` DROP COLUMN \`CHILD4_CODE\``);
|
|
||||||
await queryRunner.query(`ALTER TABLE \`orgChild3\` DROP COLUMN \`CHILD2_CODE\``);
|
|
||||||
await queryRunner.query(`ALTER TABLE \`orgChild3\` DROP COLUMN \`CHILD1_CODE\``);
|
|
||||||
await queryRunner.query(`ALTER TABLE \`orgChild3\` DROP COLUMN \`ROOT_CODE\``);
|
|
||||||
await queryRunner.query(`ALTER TABLE \`orgChild4\` DROP COLUMN \`CHILD3_CODE\``);
|
|
||||||
await queryRunner.query(`ALTER TABLE \`orgChild4\` DROP COLUMN \`CHILD2_CODE\``);
|
|
||||||
await queryRunner.query(`ALTER TABLE \`orgChild4\` DROP COLUMN \`CHILD1_CODE\``);
|
|
||||||
await queryRunner.query(`ALTER TABLE \`orgChild4\` DROP COLUMN \`ROOT_CODE\``);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,12 +1,8 @@
|
||||||
import { AppDataSource } from "../database/data-source";
|
import { AppDataSource } from "../database/data-source";
|
||||||
import { CommandRecive } from "../entities/CommandRecive";
|
import { CommandRecive } from "../entities/CommandRecive";
|
||||||
import { Command } from "../entities/Command";
|
import { Command } from "../entities/Command";
|
||||||
import { CommandOperator } from "../entities/CommandOperator";
|
|
||||||
import { OrgRoot } from "../entities/OrgRoot";
|
import { OrgRoot } from "../entities/OrgRoot";
|
||||||
import { Position } from "../entities/Position";
|
|
||||||
import { Profile } from "../entities/Profile";
|
import { Profile } from "../entities/Profile";
|
||||||
import { RequestWithUser } from "../middlewares/user";
|
|
||||||
import { EntityManager } from "typeorm";
|
|
||||||
|
|
||||||
export interface PosNumCodeSitResult {
|
export interface PosNumCodeSitResult {
|
||||||
posNumCodeSit: string;
|
posNumCodeSit: string;
|
||||||
|
|
@ -21,23 +17,21 @@ export interface PosNumCodeSitResult {
|
||||||
* เรียงลำดับผู้ได้รับคำสั่งใหม่หลังจากลบรายการ และอัพเดทสถานะคำสั่งถ้าไม่มีผู้ได้รับคำสั่งเหลือ
|
* เรียงลำดับผู้ได้รับคำสั่งใหม่หลังจากลบรายการ และอัพเดทสถานะคำสั่งถ้าไม่มีผู้ได้รับคำสั่งเหลือ
|
||||||
* @param reciveId commandRecive.Id ของผู้ได้รับคำสั่ง
|
* @param reciveId commandRecive.Id ของผู้ได้รับคำสั่ง
|
||||||
* @param code ประเภทคำสั่ง
|
* @param code ประเภทคำสั่ง
|
||||||
* @param manager ถ้าส่งเข้ามา → ทุก operation อยู่ใน transaction ของ caller (all-or-nothing)
|
|
||||||
* @returns Promise<void>
|
* @returns Promise<void>
|
||||||
*/
|
*/
|
||||||
export async function reOrderCommandRecivesAndDelete(
|
export async function reOrderCommandRecivesAndDelete(
|
||||||
reciveId: string,
|
reciveId: string
|
||||||
manager?: EntityManager,
|
|
||||||
): Promise<void> {
|
): Promise<void> {
|
||||||
const ds = manager ?? AppDataSource;
|
const commandReciveRepo = AppDataSource.getRepository(CommandRecive);
|
||||||
const commandReciveRepo = ds.getRepository(CommandRecive);
|
const commandRepo = AppDataSource.getRepository(Command);
|
||||||
const commandRepo = ds.getRepository(Command);
|
|
||||||
|
|
||||||
// ค้นหาข้อมูลผู้ได้รับคำสั่งตาม reciveId
|
// ค้นหาข้อมูลผู้ได้รับคำสั่งตาม reciveId
|
||||||
const commandRecive = await commandReciveRepo.findOne({
|
const commandRecive = await commandReciveRepo.findOne({
|
||||||
where: { id: reciveId },
|
where: { id: reciveId }
|
||||||
});
|
});
|
||||||
|
|
||||||
if (commandRecive == null) return;
|
if (commandRecive == null)
|
||||||
|
return;
|
||||||
|
|
||||||
const commandId = commandRecive.commandId;
|
const commandId = commandRecive.commandId;
|
||||||
// ลบตาม refId
|
// ลบตาม refId
|
||||||
|
|
@ -49,14 +43,17 @@ export async function reOrderCommandRecivesAndDelete(
|
||||||
});
|
});
|
||||||
// ลำดับผู้ได้รับคำสั่งใหม่
|
// ลำดับผู้ได้รับคำสั่งใหม่
|
||||||
if (commandReciveList.length > 0) {
|
if (commandReciveList.length > 0) {
|
||||||
for (let i = 0; i < commandReciveList.length; i++) {
|
await Promise.all(
|
||||||
commandReciveList[i].order = i + 1;
|
commandReciveList.map(async (p, i) => {
|
||||||
await commandReciveRepo.save(commandReciveList[i]);
|
p.order = i + 1;
|
||||||
}
|
await commandReciveRepo.save(p);
|
||||||
|
})
|
||||||
|
);
|
||||||
} else {
|
} else {
|
||||||
// ถ้าไม่มีผู้ได้รับคำสั่งเหลือเลย ให้ยกเลิกคำสั่ง
|
// ถ้าไม่มีผู้ได้รับคำสั่งเหลือเลย ให้ยกเลิกคำสั่ง
|
||||||
await commandRepo.update({ id: commandId }, { status: "CANCEL" });
|
await commandRepo.update({ id: commandId }, { status: "CANCEL" });
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -147,101 +144,3 @@ export async function getPosNumCodeSit(
|
||||||
commandExcecuteDate,
|
commandExcecuteDate,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* สร้าง/insert CommandOperator "เจ้าหน้าที่ดำเนินการ" สำหรับ command
|
|
||||||
* ใช้ userProfile ที่ query ไปแล้วถ้ามี ถ้าไม่มีค่อย query ใหม่
|
|
||||||
* @param userProfile profile ที่ query ไปแล้ว (หรือ null ถ้ายังไม่ได้ query)
|
|
||||||
* @param commandId command id ที่จะผูกกับ operator
|
|
||||||
* @param request request context (สำหรับ user.sub / user.name)
|
|
||||||
* @param now timestamp สำหรับ audit fields
|
|
||||||
* @param manager ถ้าส่งเข้ามา → ทุก operation อยู่ใน transaction ของ caller (all-or-nothing)
|
|
||||||
* @returns Promise<void>
|
|
||||||
*/
|
|
||||||
export async function ensureCommandOperator(
|
|
||||||
userProfile: Profile | null,
|
|
||||||
commandId: string,
|
|
||||||
request: RequestWithUser,
|
|
||||||
now: Date,
|
|
||||||
manager?: EntityManager,
|
|
||||||
): Promise<void> {
|
|
||||||
const ds = manager ?? AppDataSource;
|
|
||||||
const profileRepo = ds.getRepository(Profile);
|
|
||||||
const positionRepo = ds.getRepository(Position);
|
|
||||||
const commandOperatorRepo = ds.getRepository(CommandOperator);
|
|
||||||
|
|
||||||
if (!request.user.sub) return;
|
|
||||||
// ใช้ userProfile ที่ query ไปแล้วถ้ามี ถ้าไม่มีค่อย query ใหม่
|
|
||||||
let profile = userProfile;
|
|
||||||
if (!profile) {
|
|
||||||
profile = await profileRepo.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) return;
|
|
||||||
|
|
||||||
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 positionRepo.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: commandId,
|
|
||||||
createdUserId: request.user.sub,
|
|
||||||
createdFullName: request.user.name,
|
|
||||||
createdAt: now,
|
|
||||||
lastUpdateUserId: request.user.sub,
|
|
||||||
lastUpdateFullName: request.user.name,
|
|
||||||
lastUpdatedAt: now,
|
|
||||||
});
|
|
||||||
await commandOperatorRepo.save(operator);
|
|
||||||
}
|
|
||||||
|
|
|
||||||
File diff suppressed because it is too large
Load diff
|
|
@ -1,860 +0,0 @@
|
||||||
import { Double, EntityManager, In, Like } from "typeorm";
|
|
||||||
import { AppDataSource } from "../database/data-source";
|
|
||||||
import HttpError from "../interfaces/http-error";
|
|
||||||
import HttpStatusCode from "../interfaces/http-status";
|
|
||||||
import Extension from "../interfaces/extension";
|
|
||||||
import CallAPI from "../interfaces/call-api";
|
|
||||||
import { setLogDataDiff } from "../interfaces/utils";
|
|
||||||
import {
|
|
||||||
CreatePosMasterHistoryEmployee,
|
|
||||||
CreatePosMasterHistoryEmployeeTemp,
|
|
||||||
} from "./PositionService";
|
|
||||||
import {
|
|
||||||
addUserRoles,
|
|
||||||
createUser,
|
|
||||||
getRoles,
|
|
||||||
getUserByUsername,
|
|
||||||
getRoleMappings,
|
|
||||||
} from "../keycloak";
|
|
||||||
import { Command } from "../entities/Command";
|
|
||||||
import { Profile } from "../entities/Profile";
|
|
||||||
import { ProfileEmployee } from "../entities/ProfileEmployee";
|
|
||||||
import { OrgRoot } from "../entities/OrgRoot";
|
|
||||||
import { OrgRevision } from "../entities/OrgRevision";
|
|
||||||
import { RoleKeycloak } from "../entities/RoleKeycloak";
|
|
||||||
import { EmployeePosMaster } from "../entities/EmployeePosMaster";
|
|
||||||
import { EmployeeTempPosMaster } from "../entities/EmployeeTempPosMaster";
|
|
||||||
import { EmployeePosition } from "../entities/EmployeePosition";
|
|
||||||
import { PosMaster } from "../entities/PosMaster";
|
|
||||||
import { Position } from "../entities/Position";
|
|
||||||
import { ProfileSalary } from "../entities/ProfileSalary";
|
|
||||||
import { ProfileSalaryHistory } from "../entities/ProfileSalaryHistory";
|
|
||||||
import { PosMasterAct } from "../entities/PosMasterAct";
|
|
||||||
import { ProfileActposition } from "../entities/ProfileActposition";
|
|
||||||
import { ProfileActpositionHistory } from "../entities/ProfileActpositionHistory";
|
|
||||||
import { promisify } from "util";
|
|
||||||
|
|
||||||
const redis = require("redis");
|
|
||||||
const REDIS_HOST = process.env.REDIS_HOST;
|
|
||||||
const REDIS_PORT = process.env.REDIS_PORT;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Input: refIds ที่ consumer ใน rabbitmq build ขึ้น (เดิมคือ body.refIds ของ endpoint /excecute)
|
|
||||||
* ใช้กับ C-PM-21, C-PM-38, C-PM-40
|
|
||||||
*/
|
|
||||||
export interface CommandRefItem {
|
|
||||||
refId: string;
|
|
||||||
commandId?: string | null;
|
|
||||||
amount: Double | null;
|
|
||||||
amountSpecial?: Double | null;
|
|
||||||
positionSalaryAmount: Double | null;
|
|
||||||
mouthSalaryAmount: Double | null;
|
|
||||||
commandNo: string | null;
|
|
||||||
commandYear: number;
|
|
||||||
commandDateAffect?: Date | string | null;
|
|
||||||
commandDateSign?: Date | string | null;
|
|
||||||
commandCode?: string | null;
|
|
||||||
commandName?: string | null;
|
|
||||||
remark: string | null;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Context สำหรับ audit/log (เหมือน ExecuteSalaryService)
|
|
||||||
*/
|
|
||||||
export interface OrgCommandExecutionContext {
|
|
||||||
user: { sub: string; name: string };
|
|
||||||
req?: any;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Service สำหรับคำสั่งที่เดิม "ยิงเข้าตัว" (HTTP loopback เข้า org เอง)
|
|
||||||
*
|
|
||||||
* ใช้กับ commandType:
|
|
||||||
* - C-PM-21 : command21/employee/report/excecute (ลูกจ้าง → พนักงานประจำ)
|
|
||||||
* - C-PM-38 : command38/officer/report/excecute (เงินเดือน next_holder ข้าราชการ)
|
|
||||||
* - C-PM-40 : command40/officer/report/excecute (รักษาการ)
|
|
||||||
*
|
|
||||||
* - endpoint commandXX/.../excecute ทั้ง 3 เรียกผ่าน service นี้ (thin wrapper)
|
|
||||||
* - consumer ใน rabbitmq handler เรียกผ่าน service นี้โดยตรง (Linear Flow / ทำต่อ)
|
|
||||||
* แทนการ PostData(path + "/excecute") ที่เป็น HTTP loopback เข้า org ตัวเอง
|
|
||||||
*
|
|
||||||
* Behavior ทั้งหมด preserve จาก CommandController ต้นฉบับ
|
|
||||||
*
|
|
||||||
* Batch semantics: all-or-nothing — ประมวลผลทุกคนภายใต้ transaction เดียว (sequential)
|
|
||||||
* ถ้าคนใด throw จะ rollback ทั้ง batch และ propagate error ออกไป (ล้มเหลวทั้งหมด)
|
|
||||||
*
|
|
||||||
* ⚠️ หมายเหตุ side-effect ที่อยู่นอก DB transaction:
|
|
||||||
* - Keycloak operations (createUser/addUserRoles/getRoleMappings) ใน C-PM-21 ทำภายใน transaction
|
|
||||||
* เพื่อ preserve behavior เดิม — Keycloak ไม่สามารถ rollback ได้ ถ้า DB rollback หลังจากนี้
|
|
||||||
* Keycloak จะถูกเปลี่ยนไปแล้ว
|
|
||||||
* - .NET call (C-PM-21) ทำหลัง transaction commit แล้ว เพราะ .NET ไม่สามารถ rollback ได้
|
|
||||||
* - Redis cache clear (C-PM-40) ทำหลัง transaction commit (เป็นการ del cache key — idempotent)
|
|
||||||
* - CreatePosMasterHistoryEmployeeTemp สร้าง nested transaction ของตัวเอง (ไม่รับ manager)
|
|
||||||
*/
|
|
||||||
export class ExecuteOrgCommandService {
|
|
||||||
private commandRepository = AppDataSource.getRepository(Command);
|
|
||||||
private profileRepository = AppDataSource.getRepository(Profile);
|
|
||||||
private profileEmployeeRepository = AppDataSource.getRepository(ProfileEmployee);
|
|
||||||
private orgRootRepository = AppDataSource.getRepository(OrgRoot);
|
|
||||||
private orgRevisionRepository = AppDataSource.getRepository(OrgRevision);
|
|
||||||
private roleKeycloakRepo = AppDataSource.getRepository(RoleKeycloak);
|
|
||||||
private posMasterRepository = AppDataSource.getRepository(PosMaster);
|
|
||||||
private posMasterActRepository = AppDataSource.getRepository(PosMasterAct);
|
|
||||||
|
|
||||||
// ─────────────────────────────────────────────────────────────
|
|
||||||
// แก้ปัญหา _posNumCodeSit/_command resolution ที่ซ้ำกันในทุก endpoint
|
|
||||||
// (เดิมอยู่ใน controller — ย้ายมานี่ ทำครั้งเดียวก่อนเข้า transaction)
|
|
||||||
// ─────────────────────────────────────────────────────────────
|
|
||||||
private async resolvePosNumCodeSit(
|
|
||||||
commandId: string | null | undefined,
|
|
||||||
): Promise<{ command: Command | null; posNumCodeSit: string; posNumCodeSitAbb: string }> {
|
|
||||||
let posNumCodeSit = "";
|
|
||||||
let posNumCodeSitAbb = "";
|
|
||||||
const command = commandId
|
|
||||||
? await this.commandRepository.findOne({ where: { id: commandId } })
|
|
||||||
: null;
|
|
||||||
if (command) {
|
|
||||||
if (command?.isBangkok?.toLocaleUpperCase() == "OFFICE") {
|
|
||||||
const orgRootDeputy = await this.orgRootRepository.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 this.profileRepository.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 ?? "";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return { command, posNumCodeSit, posNumCodeSitAbb };
|
|
||||||
}
|
|
||||||
|
|
||||||
// ═══════════════════════════════════════════════════════════════
|
|
||||||
// C-PM-21 : command21/employee/report/excecute
|
|
||||||
// ลูกจ้างชั่วคราว → พนักงานประจำ (บรรจุ)
|
|
||||||
// ═══════════════════════════════════════════════════════════════
|
|
||||||
/**
|
|
||||||
* @returns profileEmps ที่จะส่งต่อให้ .NET (สำหรับ consumer rabbitmq เรียก .NET เอง)
|
|
||||||
* ถ้าเรียกจาก thin-wrapper endpoint จะเรียก .NET ภายใน method นี้เอง
|
|
||||||
*/
|
|
||||||
async executeCommand21Employee(
|
|
||||||
data: CommandRefItem[],
|
|
||||||
ctx: OrgCommandExecutionContext,
|
|
||||||
options?: { callDotNet?: boolean },
|
|
||||||
): Promise<{ profileEmps: any[] }> {
|
|
||||||
const req = ctx.req;
|
|
||||||
const callDotNet = options?.callDotNet ?? true;
|
|
||||||
const commandId = data?.find((x) => x.commandId)?.commandId ?? "";
|
|
||||||
console.log(
|
|
||||||
`[ExecuteOrgCommandService] executeCommand21Employee — commandId: ${commandId}, count: ${data?.length ?? 0}`,
|
|
||||||
);
|
|
||||||
|
|
||||||
const roleKeycloak = await this.roleKeycloakRepo.findOne({
|
|
||||||
where: { name: Like("USER") },
|
|
||||||
});
|
|
||||||
const { command: _command, posNumCodeSit: _posNumCodeSit, posNumCodeSitAbb: _posNumCodeSitAbb } =
|
|
||||||
await this.resolvePosNumCodeSit(commandId);
|
|
||||||
|
|
||||||
const profileEmps: any[] = [];
|
|
||||||
|
|
||||||
await AppDataSource.transaction(async (manager) => {
|
|
||||||
for (const item of data ?? []) {
|
|
||||||
try {
|
|
||||||
await this.processOneCommand21(
|
|
||||||
item,
|
|
||||||
ctx,
|
|
||||||
manager,
|
|
||||||
roleKeycloak,
|
|
||||||
_command,
|
|
||||||
_posNumCodeSit,
|
|
||||||
_posNumCodeSitAbb,
|
|
||||||
profileEmps,
|
|
||||||
);
|
|
||||||
} catch (err) {
|
|
||||||
const reason =
|
|
||||||
err instanceof HttpError
|
|
||||||
? err.message
|
|
||||||
: err instanceof Error
|
|
||||||
? err.message
|
|
||||||
: "unexpected error";
|
|
||||||
console.error(
|
|
||||||
`[ExecuteOrgCommandService] Failed C-PM-21, commandId=${commandId}, refId=${item.refId}: ${reason}`,
|
|
||||||
err,
|
|
||||||
);
|
|
||||||
throw err; // → rollback ทั้ง transaction + propagate เป็น batch failure
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// .NET call ทำหลัง commit (เหมือน endpoint เดิมที่เรียกหลัง Promise.all) — .NET ไม่ rollback ได้
|
|
||||||
if (callDotNet && profileEmps.length > 0) {
|
|
||||||
await new CallAPI()
|
|
||||||
.PostData(req, "/placement/appointment/employee-appoint-21/report/excecute", {
|
|
||||||
profileEmps,
|
|
||||||
})
|
|
||||||
.catch((error) => {
|
|
||||||
throw new Error(`Failed. Cannot update status. ${error?.message ?? ""}`);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
console.log(
|
|
||||||
`[ExecuteOrgCommandService] Completed C-PM-21 — ${profileEmps.length} profiles sent to .NET`,
|
|
||||||
);
|
|
||||||
return { profileEmps };
|
|
||||||
}
|
|
||||||
|
|
||||||
private async processOneCommand21(
|
|
||||||
item: CommandRefItem,
|
|
||||||
ctx: OrgCommandExecutionContext,
|
|
||||||
manager: EntityManager,
|
|
||||||
roleKeycloak: RoleKeycloak | null,
|
|
||||||
_command: Command | null,
|
|
||||||
_posNumCodeSit: string,
|
|
||||||
_posNumCodeSitAbb: string,
|
|
||||||
profileEmps: any[],
|
|
||||||
): Promise<void> {
|
|
||||||
const req = ctx.req;
|
|
||||||
|
|
||||||
const profileEmployeeRepository = manager.getRepository(ProfileEmployee);
|
|
||||||
const employeePosMasterRepository = manager.getRepository(EmployeePosMaster);
|
|
||||||
const employeeTempPosMasterRepository = manager.getRepository(EmployeeTempPosMaster);
|
|
||||||
const employeePositionRepository = manager.getRepository(EmployeePosition);
|
|
||||||
const salaryRepo = manager.getRepository(ProfileSalary);
|
|
||||||
const salaryHistoryRepo = manager.getRepository(ProfileSalaryHistory);
|
|
||||||
|
|
||||||
const profile = await profileEmployeeRepository.findOne({
|
|
||||||
where: { id: item.refId },
|
|
||||||
relations: ["roleKeycloaks"],
|
|
||||||
});
|
|
||||||
if (!profile) {
|
|
||||||
throw new HttpError(HttpStatusCode.BAD_REQUEST, "ไม่พบ profile ดังกล่าว");
|
|
||||||
}
|
|
||||||
const orgRevision = await this.orgRevisionRepository.findOne({
|
|
||||||
where: {
|
|
||||||
orgRevisionIsCurrent: true,
|
|
||||||
orgRevisionIsDraft: false,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
const _posMaster = await employeePosMasterRepository.findOne({
|
|
||||||
where: {
|
|
||||||
orgRevisionId: orgRevision?.id,
|
|
||||||
id: profile.posmasterIdTemp,
|
|
||||||
},
|
|
||||||
relations: {
|
|
||||||
orgRoot: true,
|
|
||||||
orgChild1: true,
|
|
||||||
orgChild2: true,
|
|
||||||
orgChild3: true,
|
|
||||||
orgChild4: true,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
const orgRootRef = _posMaster?.orgRoot ?? null;
|
|
||||||
const orgChild1Ref = _posMaster?.orgChild1 ?? null;
|
|
||||||
const orgChild2Ref = _posMaster?.orgChild2 ?? null;
|
|
||||||
const orgChild3Ref = _posMaster?.orgChild3 ?? null;
|
|
||||||
const orgChild4Ref = _posMaster?.orgChild4 ?? null;
|
|
||||||
let orgShortName = "";
|
|
||||||
if (_posMaster != null) {
|
|
||||||
if (_posMaster.orgChild1Id === null) {
|
|
||||||
orgShortName = _posMaster.orgRoot?.orgRootShortName;
|
|
||||||
} else if (_posMaster.orgChild2Id === null) {
|
|
||||||
orgShortName = _posMaster.orgChild1?.orgChild1ShortName;
|
|
||||||
} else if (_posMaster.orgChild3Id === null) {
|
|
||||||
orgShortName = _posMaster.orgChild2?.orgChild2ShortName;
|
|
||||||
} else if (_posMaster.orgChild4Id === null) {
|
|
||||||
orgShortName = _posMaster.orgChild3?.orgChild3ShortName;
|
|
||||||
} else {
|
|
||||||
orgShortName = _posMaster.orgChild4?.orgChild4ShortName;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
const dest_item = await salaryRepo.findOne({
|
|
||||||
where: { profileEmployeeId: item.refId },
|
|
||||||
order: { order: "DESC" },
|
|
||||||
});
|
|
||||||
const before = null;
|
|
||||||
const dataSalary = new ProfileSalary();
|
|
||||||
dataSalary.posNumCodeSit = _posNumCodeSit;
|
|
||||||
dataSalary.posNumCodeSitAbb = _posNumCodeSitAbb;
|
|
||||||
const meta = {
|
|
||||||
profileEmployeeId: profile.id,
|
|
||||||
amount: item.amount,
|
|
||||||
amountSpecial: item.amountSpecial,
|
|
||||||
positionSalaryAmount: item.positionSalaryAmount,
|
|
||||||
mouthSalaryAmount: item.mouthSalaryAmount,
|
|
||||||
position: profile.positionTemp,
|
|
||||||
positionName: profile.positionTemp,
|
|
||||||
positionType: profile.posTypeNameTemp,
|
|
||||||
positionLevel: profile.posLevelNameTemp,
|
|
||||||
order: dest_item == null ? 1 : dest_item.order + 1,
|
|
||||||
orgRoot: orgRootRef?.orgRootName ?? null,
|
|
||||||
orgChild1: orgChild1Ref?.orgChild1Name ?? null,
|
|
||||||
orgChild2: orgChild2Ref?.orgChild2Name ?? null,
|
|
||||||
orgChild3: orgChild3Ref?.orgChild3Name ?? null,
|
|
||||||
orgChild4: orgChild4Ref?.orgChild4Name ?? null,
|
|
||||||
createdUserId: ctx.user.sub,
|
|
||||||
createdFullName: ctx.user.name,
|
|
||||||
lastUpdateUserId: ctx.user.sub,
|
|
||||||
lastUpdateFullName: ctx.user.name,
|
|
||||||
createdAt: new Date(),
|
|
||||||
lastUpdatedAt: new Date(),
|
|
||||||
commandNo: item.commandNo,
|
|
||||||
commandYear: item.commandYear,
|
|
||||||
posNo: profile.posMasterNoTemp ?? "",
|
|
||||||
posNoAbb: orgShortName,
|
|
||||||
commandDateAffect: item.commandDateAffect,
|
|
||||||
commandDateSign: item.commandDateSign,
|
|
||||||
commandCode: item.commandCode,
|
|
||||||
commandName: item.commandName,
|
|
||||||
remark: item.remark,
|
|
||||||
};
|
|
||||||
|
|
||||||
Object.assign(dataSalary, meta);
|
|
||||||
const history = new ProfileSalaryHistory();
|
|
||||||
Object.assign(history, { ...dataSalary, id: undefined });
|
|
||||||
|
|
||||||
await salaryRepo.save(dataSalary, { data: req });
|
|
||||||
setLogDataDiff(req, { before, after: dataSalary });
|
|
||||||
history.profileSalaryId = dataSalary.id;
|
|
||||||
await salaryHistoryRepo.save(history, { data: req });
|
|
||||||
|
|
||||||
const posMaster = await employeePosMasterRepository.findOne({
|
|
||||||
where: { id: profile.posmasterIdTemp },
|
|
||||||
relations: ["orgRoot", "orgChild1", "orgChild2", "orgChild3", "orgChild4"],
|
|
||||||
});
|
|
||||||
if (posMaster == null)
|
|
||||||
throw new HttpError(HttpStatusCode.NOT_FOUND, "ไม่พบข้อมูลตำแหน่งนี้");
|
|
||||||
|
|
||||||
const posMasterOld = await employeePosMasterRepository.findOne({
|
|
||||||
where: {
|
|
||||||
current_holderId: profile.id,
|
|
||||||
orgRevisionId: posMaster.orgRevisionId,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
if (posMasterOld != null) {
|
|
||||||
posMasterOld.current_holderId = null;
|
|
||||||
posMasterOld.lastUpdatedAt = new Date();
|
|
||||||
}
|
|
||||||
|
|
||||||
const positionOld = await employeePositionRepository.findOne({
|
|
||||||
where: {
|
|
||||||
posMasterId: posMasterOld?.id,
|
|
||||||
positionIsSelected: true,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
if (positionOld != null) {
|
|
||||||
positionOld.positionIsSelected = false;
|
|
||||||
await employeePositionRepository.save(positionOld);
|
|
||||||
}
|
|
||||||
|
|
||||||
const checkPosition = await employeePositionRepository.find({
|
|
||||||
where: {
|
|
||||||
posMasterId: profile.posmasterIdTemp,
|
|
||||||
positionIsSelected: true,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
if (checkPosition.length > 0) {
|
|
||||||
const clearPosition = checkPosition.map((positions) => ({
|
|
||||||
...positions,
|
|
||||||
positionIsSelected: false,
|
|
||||||
}));
|
|
||||||
await employeePositionRepository.save(clearPosition);
|
|
||||||
}
|
|
||||||
|
|
||||||
posMaster.current_holderId = profile.id;
|
|
||||||
posMaster.lastUpdatedAt = new Date();
|
|
||||||
posMaster.next_holderId = null;
|
|
||||||
if (posMasterOld != null) {
|
|
||||||
await employeePosMasterRepository.save(posMasterOld);
|
|
||||||
await CreatePosMasterHistoryEmployee(posMasterOld.id, req, undefined, manager);
|
|
||||||
}
|
|
||||||
await employeePosMasterRepository.save(posMaster);
|
|
||||||
await CreatePosMasterHistoryEmployee(posMaster.id, req, undefined, manager);
|
|
||||||
|
|
||||||
const clsTempPosmaster = await employeeTempPosMasterRepository.find({
|
|
||||||
where: {
|
|
||||||
current_holderId: profile.id,
|
|
||||||
orgRevisionId: posMaster.orgRevisionId,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
if (clsTempPosmaster.length > 0) {
|
|
||||||
const clearTempPosmaster = clsTempPosmaster.map((posMasterTemp) => ({
|
|
||||||
...posMasterTemp,
|
|
||||||
current_holderId: null,
|
|
||||||
next_holderId: null,
|
|
||||||
}));
|
|
||||||
await employeeTempPosMasterRepository.save(clearTempPosmaster);
|
|
||||||
|
|
||||||
const checkTempPosition = await employeePositionRepository.find({
|
|
||||||
where: {
|
|
||||||
posMasterTempId: In(clearTempPosmaster.map((x) => x.id)),
|
|
||||||
positionIsSelected: true,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
if (checkTempPosition.length > 0) {
|
|
||||||
const clearTempPosition = checkTempPosition.map((positions) => ({
|
|
||||||
...positions,
|
|
||||||
positionIsSelected: false,
|
|
||||||
}));
|
|
||||||
await employeePositionRepository.save(clearTempPosition);
|
|
||||||
}
|
|
||||||
await Promise.all(
|
|
||||||
clsTempPosmaster.map(
|
|
||||||
async (posMasterTemp) =>
|
|
||||||
await CreatePosMasterHistoryEmployeeTemp(posMasterTemp.id, req),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
const positionNew = await employeePositionRepository.findOne({
|
|
||||||
where: {
|
|
||||||
id: profile.positionIdTemp,
|
|
||||||
posMasterId: profile.posmasterIdTemp,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
if (positionNew != null) {
|
|
||||||
// Create Keycloak
|
|
||||||
const checkUser = await getUserByUsername(profile.citizenId);
|
|
||||||
if (checkUser.length == 0) {
|
|
||||||
let password = profile.citizenId;
|
|
||||||
if (profile.birthDate != null) {
|
|
||||||
const _date = new Date(profile.birthDate.toDateString())
|
|
||||||
.getDate()
|
|
||||||
.toString()
|
|
||||||
.padStart(2, "0");
|
|
||||||
const _month = (new Date(profile.birthDate.toDateString()).getMonth() + 1)
|
|
||||||
.toString()
|
|
||||||
.padStart(2, "0");
|
|
||||||
const _year = new Date(profile.birthDate.toDateString()).getFullYear() + 543;
|
|
||||||
password = `${_date}${_month}${_year}`;
|
|
||||||
}
|
|
||||||
// กรอง "." ออกจาก firstName ก่อนส่งไป keycloak
|
|
||||||
const sanitizedFirstName = profile.firstName?.replace(/\./g, "") ?? "";
|
|
||||||
const userKeycloakId = await createUser(profile.citizenId, password, {
|
|
||||||
firstName: sanitizedFirstName,
|
|
||||||
lastName: profile.lastName,
|
|
||||||
});
|
|
||||||
const list = await getRoles();
|
|
||||||
if (!Array.isArray(list))
|
|
||||||
throw new Error("Failed. Cannot get role(s) data from the server.");
|
|
||||||
const result = await addUserRoles(
|
|
||||||
userKeycloakId,
|
|
||||||
list
|
|
||||||
.filter((v) => v.name === "USER")
|
|
||||||
.map((x) => ({
|
|
||||||
id: x.id,
|
|
||||||
name: x.name,
|
|
||||||
})),
|
|
||||||
);
|
|
||||||
profile.keycloak =
|
|
||||||
userKeycloakId && typeof userKeycloakId == "string" ? userKeycloakId : "";
|
|
||||||
profile.roleKeycloaks = result && roleKeycloak ? [roleKeycloak] : [];
|
|
||||||
// End Create Keycloak
|
|
||||||
} else {
|
|
||||||
const rolesData = await getRoleMappings(checkUser[0].id);
|
|
||||||
if (rolesData) {
|
|
||||||
const _roleKeycloak = await this.roleKeycloakRepo.find({
|
|
||||||
where: { name: In(rolesData.map((x: any) => x.name)) },
|
|
||||||
});
|
|
||||||
profile.roleKeycloaks =
|
|
||||||
_roleKeycloak && _roleKeycloak.length > 0 ? _roleKeycloak : [];
|
|
||||||
}
|
|
||||||
profile.keycloak = checkUser[0].id;
|
|
||||||
}
|
|
||||||
positionNew.positionIsSelected = true;
|
|
||||||
profile.posLevelId = positionNew.posLevelId;
|
|
||||||
profile.posTypeId = positionNew.posTypeId;
|
|
||||||
profile.position = positionNew.positionName;
|
|
||||||
profile.employeeOc = posMaster?.orgRoot?.orgRootName ?? null;
|
|
||||||
profile.positionEmployeePositionId = positionNew.positionName;
|
|
||||||
profile.statusTemp = "DONE";
|
|
||||||
profile.employeeClass = "PERM";
|
|
||||||
const _null: any = null;
|
|
||||||
profile.employeeWage = item.amount == null ? _null : item.amount.toString();
|
|
||||||
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;
|
|
||||||
profileEmps.push({
|
|
||||||
profileId: profile.id,
|
|
||||||
prefix: profile.prefix,
|
|
||||||
firstName: profile.firstName,
|
|
||||||
lastName: profile.lastName,
|
|
||||||
citizenId: profile.citizenId,
|
|
||||||
root: posMaster.orgRoot.orgRootName,
|
|
||||||
rootId: posMaster.orgRootId,
|
|
||||||
rootShortName: posMaster.orgRoot.orgRootShortName,
|
|
||||||
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,
|
|
||||||
});
|
|
||||||
await profileEmployeeRepository.save(profile);
|
|
||||||
await employeePositionRepository.save(positionNew);
|
|
||||||
await CreatePosMasterHistoryEmployee(posMaster.id, req, undefined, manager);
|
|
||||||
//ลบออกคนออกจากโครงสร้างลูกจ้างชั่วคราว
|
|
||||||
const posMasterTemp = await employeeTempPosMasterRepository.findOne({
|
|
||||||
where: {
|
|
||||||
orgRevisionId: orgRevision?.id,
|
|
||||||
current_holderId: profile.id,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
if (posMasterTemp) {
|
|
||||||
await employeeTempPosMasterRepository.update(posMasterTemp.id, {
|
|
||||||
current_holderId: _null,
|
|
||||||
});
|
|
||||||
await CreatePosMasterHistoryEmployeeTemp(posMasterTemp.id, req);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// ═══════════════════════════════════════════════════════════════
|
|
||||||
// C-PM-38 : command38/officer/report/excecute
|
|
||||||
// เงินเดือน next_holder ของข้าราชการ
|
|
||||||
// ═══════════════════════════════════════════════════════════════
|
|
||||||
async executeCommand38Officer(
|
|
||||||
data: CommandRefItem[],
|
|
||||||
ctx: OrgCommandExecutionContext,
|
|
||||||
): Promise<void> {
|
|
||||||
const req = ctx.req;
|
|
||||||
const commandId = data?.find((x) => x.commandId)?.commandId ?? "";
|
|
||||||
console.log(
|
|
||||||
`[ExecuteOrgCommandService] executeCommand38Officer — commandId: ${commandId}, count: ${data?.length ?? 0}`,
|
|
||||||
);
|
|
||||||
|
|
||||||
const { posNumCodeSit: _posNumCodeSit, posNumCodeSitAbb: _posNumCodeSitAbb } =
|
|
||||||
await this.resolvePosNumCodeSit(commandId);
|
|
||||||
|
|
||||||
await AppDataSource.transaction(async (manager) => {
|
|
||||||
for (const item of data ?? []) {
|
|
||||||
try {
|
|
||||||
await this.processOneCommand38(
|
|
||||||
item,
|
|
||||||
ctx,
|
|
||||||
manager,
|
|
||||||
_posNumCodeSit,
|
|
||||||
_posNumCodeSitAbb,
|
|
||||||
);
|
|
||||||
} catch (err) {
|
|
||||||
const reason =
|
|
||||||
err instanceof HttpError
|
|
||||||
? err.message
|
|
||||||
: err instanceof Error
|
|
||||||
? err.message
|
|
||||||
: "unexpected error";
|
|
||||||
console.error(
|
|
||||||
`[ExecuteOrgCommandService] Failed C-PM-38, commandId=${commandId}, refId=${item.refId}: ${reason}`,
|
|
||||||
err,
|
|
||||||
);
|
|
||||||
throw err; // → rollback ทั้ง transaction + propagate เป็น batch failure
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
console.log(`[ExecuteOrgCommandService] Completed C-PM-38 — ${data?.length ?? 0} items`);
|
|
||||||
}
|
|
||||||
|
|
||||||
private async processOneCommand38(
|
|
||||||
item: CommandRefItem,
|
|
||||||
ctx: OrgCommandExecutionContext,
|
|
||||||
manager: EntityManager,
|
|
||||||
_posNumCodeSit: string,
|
|
||||||
_posNumCodeSitAbb: string,
|
|
||||||
): Promise<void> {
|
|
||||||
const req = ctx.req;
|
|
||||||
|
|
||||||
const posMasterRepository = manager.getRepository(PosMaster);
|
|
||||||
const profileRepository = manager.getRepository(Profile);
|
|
||||||
const positionRepository = manager.getRepository(Position);
|
|
||||||
const salaryRepo = manager.getRepository(ProfileSalary);
|
|
||||||
const salaryHistoryRepo = manager.getRepository(ProfileSalaryHistory);
|
|
||||||
|
|
||||||
const posMaster = await posMasterRepository.findOne({
|
|
||||||
where: { id: item.refId },
|
|
||||||
relations: [
|
|
||||||
"orgRoot",
|
|
||||||
"orgChild1",
|
|
||||||
"orgChild2",
|
|
||||||
"orgChild3",
|
|
||||||
"orgChild4",
|
|
||||||
"current_holder",
|
|
||||||
"current_holder.posLevel",
|
|
||||||
"current_holder.posType",
|
|
||||||
],
|
|
||||||
});
|
|
||||||
if (!posMaster) {
|
|
||||||
throw new HttpError(HttpStatusCode.BAD_REQUEST, "ไม่พบตำแหน่งดังกล่าว");
|
|
||||||
}
|
|
||||||
if (posMaster.next_holderId != null) {
|
|
||||||
const orgRootRef = posMaster?.orgRoot ?? null;
|
|
||||||
const orgChild1Ref = posMaster?.orgChild1 ?? null;
|
|
||||||
const orgChild2Ref = posMaster?.orgChild2 ?? null;
|
|
||||||
const orgChild3Ref = posMaster?.orgChild3 ?? null;
|
|
||||||
const orgChild4Ref = posMaster?.orgChild4 ?? null;
|
|
||||||
const shortName =
|
|
||||||
posMaster != null && posMaster.orgChild4 != null
|
|
||||||
? `${posMaster.orgChild4.orgChild4ShortName}`
|
|
||||||
: posMaster != null && posMaster.orgChild3 != null
|
|
||||||
? `${posMaster.orgChild3.orgChild3ShortName}`
|
|
||||||
: posMaster != null && posMaster.orgChild2 != null
|
|
||||||
? `${posMaster.orgChild2.orgChild2ShortName}`
|
|
||||||
: posMaster != null && posMaster.orgChild1 != null
|
|
||||||
? `${posMaster.orgChild1.orgChild1ShortName}`
|
|
||||||
: posMaster != null && posMaster?.orgRoot != null
|
|
||||||
? `${posMaster.orgRoot.orgRootShortName}`
|
|
||||||
: null;
|
|
||||||
const profile = await profileRepository.findOne({
|
|
||||||
where: { id: posMaster.next_holderId },
|
|
||||||
});
|
|
||||||
const position = await positionRepository.findOne({
|
|
||||||
where: {
|
|
||||||
posMasterId: posMaster.id,
|
|
||||||
positionIsSelected: true,
|
|
||||||
},
|
|
||||||
relations: ["posType", "posLevel"],
|
|
||||||
});
|
|
||||||
const dest_item = await salaryRepo.findOne({
|
|
||||||
where: { profileId: profile?.id },
|
|
||||||
order: { order: "DESC" },
|
|
||||||
});
|
|
||||||
const before = null;
|
|
||||||
const dataSalary = new ProfileSalary();
|
|
||||||
dataSalary.posNumCodeSit = _posNumCodeSit;
|
|
||||||
dataSalary.posNumCodeSitAbb = _posNumCodeSitAbb;
|
|
||||||
const meta = {
|
|
||||||
profileId: profile?.id,
|
|
||||||
date: new Date(),
|
|
||||||
amount: item.amount,
|
|
||||||
commandId: item.commandId,
|
|
||||||
positionSalaryAmount: item.positionSalaryAmount,
|
|
||||||
mouthSalaryAmount: item.mouthSalaryAmount,
|
|
||||||
position: position?.positionName ?? null,
|
|
||||||
positionType: position?.posType?.posTypeName ?? null,
|
|
||||||
positionLevel: position?.posLevel?.posLevelName ?? null,
|
|
||||||
order: dest_item == null ? 1 : dest_item.order + 1,
|
|
||||||
orgRoot: orgRootRef?.orgRootName ?? null,
|
|
||||||
orgChild1: orgChild1Ref?.orgChild1Name ?? null,
|
|
||||||
orgChild2: orgChild2Ref?.orgChild2Name ?? null,
|
|
||||||
orgChild3: orgChild3Ref?.orgChild3Name ?? null,
|
|
||||||
orgChild4: orgChild4Ref?.orgChild4Name ?? null,
|
|
||||||
createdUserId: ctx.user.sub,
|
|
||||||
createdFullName: ctx.user.name,
|
|
||||||
lastUpdateUserId: ctx.user.sub,
|
|
||||||
lastUpdateFullName: ctx.user.name,
|
|
||||||
createdAt: new Date(),
|
|
||||||
lastUpdatedAt: new Date(),
|
|
||||||
commandNo: item.commandNo,
|
|
||||||
commandYear: item.commandYear,
|
|
||||||
posNo: posMaster.posMasterNo,
|
|
||||||
posNoAbb: shortName,
|
|
||||||
commandDateAffect: item.commandDateAffect,
|
|
||||||
commandDateSign: item.commandDateSign,
|
|
||||||
commandCode: item.commandCode,
|
|
||||||
commandName: item.commandName,
|
|
||||||
remark: item.remark,
|
|
||||||
};
|
|
||||||
Object.assign(dataSalary, meta);
|
|
||||||
const history = new ProfileSalaryHistory();
|
|
||||||
Object.assign(history, { ...dataSalary, id: undefined });
|
|
||||||
|
|
||||||
await salaryRepo.save(dataSalary, { data: req });
|
|
||||||
setLogDataDiff(req, { before, after: dataSalary });
|
|
||||||
history.profileSalaryId = dataSalary.id;
|
|
||||||
await salaryHistoryRepo.save(history, { data: req });
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// ═══════════════════════════════════════════════════════════════
|
|
||||||
// C-PM-40 : command40/officer/report/excecute
|
|
||||||
// รักษาการ (ProfileActposition)
|
|
||||||
// ═══════════════════════════════════════════════════════════════
|
|
||||||
async executeCommand40Officer(
|
|
||||||
data: CommandRefItem[],
|
|
||||||
ctx: OrgCommandExecutionContext,
|
|
||||||
): Promise<void> {
|
|
||||||
const commandId = data?.find((x) => x.commandId)?.commandId ?? "";
|
|
||||||
console.log(
|
|
||||||
`[ExecuteOrgCommandService] executeCommand40Officer — commandId: ${commandId}, count: ${data?.length ?? 0}`,
|
|
||||||
);
|
|
||||||
|
|
||||||
// 3. ตรวจสอบว่ามี data[0] หรือไม่
|
|
||||||
const firstRef = data[0];
|
|
||||||
if (!firstRef) {
|
|
||||||
throw new HttpError(HttpStatusCode.BAD_REQUEST, "ไม่พบข้อมูล refIds");
|
|
||||||
}
|
|
||||||
|
|
||||||
const profileIdsToClearCache = new Set<string>();
|
|
||||||
|
|
||||||
await AppDataSource.transaction(async (manager) => {
|
|
||||||
// 1. Bulk update status
|
|
||||||
await manager.getRepository(PosMasterAct).update(
|
|
||||||
{ id: In(data.map((x) => x.refId)) },
|
|
||||||
{ statusReport: "DONE" },
|
|
||||||
);
|
|
||||||
|
|
||||||
// 2. ดึงข้อมูลครบทุก relation ที่จำเป็น
|
|
||||||
const posMasters = await manager.getRepository(PosMasterAct).find({
|
|
||||||
where: { id: In(data.map((x) => x.refId)) },
|
|
||||||
relations: [
|
|
||||||
"posMasterChild",
|
|
||||||
"posMasterChild.current_holder",
|
|
||||||
"posMaster",
|
|
||||||
"posMaster.current_holder",
|
|
||||||
"posMaster.positions",
|
|
||||||
"posMaster.orgRoot",
|
|
||||||
"posMaster.orgChild1",
|
|
||||||
"posMaster.orgChild2",
|
|
||||||
"posMaster.orgChild3",
|
|
||||||
"posMaster.orgChild4",
|
|
||||||
],
|
|
||||||
});
|
|
||||||
|
|
||||||
for (const item of posMasters) {
|
|
||||||
try {
|
|
||||||
// 4. ตรวจสอบข้อมูลที่จำเป็นทั้งหมด
|
|
||||||
if (!item.posMasterChild?.current_holderId || !item.posMaster) {
|
|
||||||
console.warn(`ข้ามรายการ ${item.id}: ข้อมูลไม่ครบ`);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (item.posMasterChild.current_holderId) {
|
|
||||||
profileIdsToClearCache.add(item.posMasterChild.current_holderId);
|
|
||||||
}
|
|
||||||
|
|
||||||
// 5. สร้าง orgShortName แบบปลอดภัย
|
|
||||||
const orgShortName =
|
|
||||||
[
|
|
||||||
item.posMaster?.orgChild4?.orgChild4ShortName,
|
|
||||||
item.posMaster?.orgChild3?.orgChild3ShortName,
|
|
||||||
item.posMaster?.orgChild2?.orgChild2ShortName,
|
|
||||||
item.posMaster?.orgChild1?.orgChild1ShortName,
|
|
||||||
item.posMaster?.orgRoot?.orgRootShortName,
|
|
||||||
].find(Boolean) ?? "";
|
|
||||||
|
|
||||||
// 6. หา position ที่ถูกเลือกแบบปลอดภัย
|
|
||||||
const selectedPosition = item.posMaster?.positions;
|
|
||||||
const positionName =
|
|
||||||
selectedPosition
|
|
||||||
?.map((pos) => pos.positionName)
|
|
||||||
.filter(Boolean)
|
|
||||||
.join(", ") ?? "-";
|
|
||||||
|
|
||||||
// 7. สร้าง metaAct แบบปลอดภัย
|
|
||||||
const metaAct = {
|
|
||||||
profileId: item.posMasterChild.current_holderId,
|
|
||||||
dateStart: firstRef.commandDateAffect ?? null,
|
|
||||||
dateEnd: null,
|
|
||||||
position: positionName,
|
|
||||||
status: true,
|
|
||||||
commandId: firstRef.commandId ?? null,
|
|
||||||
createdUserId: ctx.user.sub,
|
|
||||||
createdFullName: ctx.user.name,
|
|
||||||
lastUpdateUserId: ctx.user.sub,
|
|
||||||
lastUpdateFullName: ctx.user.name,
|
|
||||||
createdAt: new Date(),
|
|
||||||
lastUpdatedAt: new Date(),
|
|
||||||
commandNo: firstRef.commandNo ?? null,
|
|
||||||
refCommandNo: `${firstRef.commandNo ?? ""}/${firstRef.commandYear ? Extension.ToThaiYear(firstRef.commandYear) : ""}`,
|
|
||||||
commandYear: firstRef.commandYear ? Extension.ToThaiYear(firstRef.commandYear) : null,
|
|
||||||
posNo:
|
|
||||||
orgShortName && item.posMaster?.posMasterNo
|
|
||||||
? `${orgShortName} ${item.posMaster.posMasterNo}`
|
|
||||||
: item.posMaster?.posMasterNo ?? "-",
|
|
||||||
posNoAbb: orgShortName,
|
|
||||||
commandDateAffect: firstRef.commandDateAffect ?? null,
|
|
||||||
commandDateSign: firstRef.commandDateSign ?? null,
|
|
||||||
commandCode: firstRef.commandCode ?? null,
|
|
||||||
commandName: firstRef.commandName ?? null,
|
|
||||||
remark: firstRef.remark ?? null,
|
|
||||||
};
|
|
||||||
|
|
||||||
// 8. ปิดสถานะรักษาการ
|
|
||||||
const actpositionRepository = manager.getRepository(ProfileActposition);
|
|
||||||
const actpositionHistoryRepository = manager.getRepository(ProfileActpositionHistory);
|
|
||||||
|
|
||||||
const existingActPositions = await actpositionRepository.find({
|
|
||||||
where: {
|
|
||||||
profileId: item.posMasterChild.current_holderId,
|
|
||||||
status: true,
|
|
||||||
isDeleted: false,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
if (existingActPositions.length > 0) {
|
|
||||||
const updatedActPositions = existingActPositions.map((_data) => ({
|
|
||||||
..._data,
|
|
||||||
status: false,
|
|
||||||
dateEnd: new Date(),
|
|
||||||
}));
|
|
||||||
|
|
||||||
await actpositionRepository.save(updatedActPositions);
|
|
||||||
}
|
|
||||||
|
|
||||||
// 9. บันทึกข้อมูลใหม่
|
|
||||||
const dataAct = new ProfileActposition();
|
|
||||||
Object.assign(dataAct, metaAct);
|
|
||||||
|
|
||||||
const historyAct = new ProfileActpositionHistory();
|
|
||||||
Object.assign(historyAct, { ...dataAct, id: undefined });
|
|
||||||
|
|
||||||
await actpositionRepository.save(dataAct);
|
|
||||||
historyAct.profileActpositionId = dataAct.id;
|
|
||||||
await actpositionHistoryRepository.save(historyAct);
|
|
||||||
} catch (error) {
|
|
||||||
console.error(`Error processing item ${item.id}:`, error);
|
|
||||||
throw new HttpError(
|
|
||||||
HttpStatusCode.INTERNAL_SERVER_ERROR,
|
|
||||||
`เกิดข้อผิดพลาดในการประมวลผลรายการ ${item.id}`,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// Redis cache clear ทำหลัง commit (del cache key — idempotent)
|
|
||||||
if (profileIdsToClearCache.size > 0) {
|
|
||||||
await Promise.all(
|
|
||||||
Array.from(profileIdsToClearCache).map(async (profileId) => {
|
|
||||||
const redisClient = await redis.createClient({
|
|
||||||
host: REDIS_HOST,
|
|
||||||
port: REDIS_PORT,
|
|
||||||
});
|
|
||||||
|
|
||||||
const delAsync = promisify(redisClient.del).bind(redisClient);
|
|
||||||
await delAsync("role_" + profileId);
|
|
||||||
await delAsync("menu_" + profileId);
|
|
||||||
|
|
||||||
redisClient.quit();
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
console.log(`[ExecuteOrgCommandService] Completed C-PM-40 — ${data?.length ?? 0} items`);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,499 +0,0 @@
|
||||||
import { Double, EntityManager } from "typeorm";
|
|
||||||
import { AppDataSource } from "../database/data-source";
|
|
||||||
import HttpError from "../interfaces/http-error";
|
|
||||||
import HttpStatusCode from "../interfaces/http-status";
|
|
||||||
import { Profile } from "../entities/Profile";
|
|
||||||
import { ProfileSalary } from "../entities/ProfileSalary";
|
|
||||||
import { ProfileSalaryHistory } from "../entities/ProfileSalaryHistory";
|
|
||||||
import { OrgRoot } from "../entities/OrgRoot";
|
|
||||||
import { PosMaster } from "../entities/PosMaster";
|
|
||||||
import { Position } from "../entities/Position";
|
|
||||||
import { Command } from "../entities/Command";
|
|
||||||
import { getOrgFullName, getPosMasterNo } from "../utils/org-formatting";
|
|
||||||
import { logPositionIsSelectedChange, setLogDataDiff } from "../interfaces/utils";
|
|
||||||
import { CreatePosMasterHistoryOfficer } from "./PositionService";
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Input: ข้อมูล 1 คนสำหรับ endpoint excexute/salary-current
|
|
||||||
* (C-PM-03, 04, 05, 06, 07, 39, 47 — เปลี่ยนตำแหน่งปัจจุบันของข้าราชการ + salary ใหม่)
|
|
||||||
*/
|
|
||||||
export interface SalaryCurrentItem {
|
|
||||||
profileId: string;
|
|
||||||
amount?: Double | null;
|
|
||||||
amountSpecial?: Double | null;
|
|
||||||
positionSalaryAmount?: Double | null;
|
|
||||||
mouthSalaryAmount?: Double | null;
|
|
||||||
positionExecutive: string | null;
|
|
||||||
positionExecutiveField?: string | null;
|
|
||||||
positionArea?: string | null;
|
|
||||||
positionType: string | null;
|
|
||||||
positionLevel: string | null;
|
|
||||||
positionTypeId?: string | null;
|
|
||||||
positionLevelId?: string | null;
|
|
||||||
posmasterId: string;
|
|
||||||
positionId: string;
|
|
||||||
posExecutiveId?: string | null;
|
|
||||||
positionField?: string | null;
|
|
||||||
commandId?: string | null;
|
|
||||||
orgRoot?: string | null;
|
|
||||||
orgChild1?: string | null;
|
|
||||||
orgChild2?: string | null;
|
|
||||||
orgChild3?: string | null;
|
|
||||||
orgChild4?: string | null;
|
|
||||||
commandNo: string | null;
|
|
||||||
commandYear: number | null;
|
|
||||||
posNo: string | null;
|
|
||||||
posNoAbb: string | null;
|
|
||||||
commandDateAffect?: Date | string | null;
|
|
||||||
commandDateSign?: Date | string | null;
|
|
||||||
positionName: string | null;
|
|
||||||
commandCode?: string | null;
|
|
||||||
commandName?: string | null;
|
|
||||||
remark: string | null;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Context สำหรับ audit/log
|
|
||||||
*/
|
|
||||||
export interface SalaryCurrentExecutionContext {
|
|
||||||
user: { sub: string; name: string };
|
|
||||||
req?: any;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Service สำหรับสร้าง ProfileSalary ของข้าราชการ + อัปเดตตำแหน่งปัจจุบัน (เปลี่ยนตำแหน่ง)
|
|
||||||
*
|
|
||||||
* ใช้กับ commandType: C-PM-03, 04, 05, 06, 07, 39, 47
|
|
||||||
*
|
|
||||||
* - endpoint /org/command/excexute/salary-current เรียกผ่าน service นี้ (thin wrapper)
|
|
||||||
* - consumer ใน rabbitmq handler เรียกผ่าน service นี้โดยตรง (Linear Flow)
|
|
||||||
*
|
|
||||||
* Behavior ทั้งหมด preserve จาก CommandController.newSalaryAndUpdateCurrent ต้นฉบับ
|
|
||||||
*
|
|
||||||
* Batch semantics: all-or-nothing — ประมวลผลทุกคนภายใต้ transaction เดียว (sequential)
|
|
||||||
* ถ้าคนใด throw จะ rollback ทั้ง batch และ propagate error ออกไป (ล้มเหลวทั้งหมด)
|
|
||||||
* ถ้าทุกคนสำเร็จจะ return result รายงาน success count
|
|
||||||
*/
|
|
||||||
export class ExecuteSalaryCurrentService {
|
|
||||||
private commandRepository = AppDataSource.getRepository(Command);
|
|
||||||
private profileRepository = AppDataSource.getRepository(Profile);
|
|
||||||
private orgRootRepository = AppDataSource.getRepository(OrgRoot);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* ประมวลผลสร้าง ProfileSalary + อัปเดตตำแหน่งปัจจุบันของข้าราชการทั้ง batch
|
|
||||||
*
|
|
||||||
* @returns สรุปผล success/failure ต่อคน
|
|
||||||
*/
|
|
||||||
async executeSalaryCurrent(
|
|
||||||
data: SalaryCurrentItem[],
|
|
||||||
ctx: SalaryCurrentExecutionContext,
|
|
||||||
): Promise<void> {
|
|
||||||
const commandId = data?.find((x) => x.commandId)?.commandId ?? "unknown";
|
|
||||||
const commandCode = data?.find((x) => x.commandCode)?.commandCode ?? "unknown";
|
|
||||||
console.log(
|
|
||||||
`[ExecuteSalaryCurrentService] Starting executeSalaryCurrent — commandCode: ${commandCode}, commandId: ${commandId}`,
|
|
||||||
);
|
|
||||||
console.log(`[ExecuteSalaryCurrentService] Request body count: ${data?.length ?? 0}`);
|
|
||||||
|
|
||||||
// ─────────────────────────────────────────────────────────────
|
|
||||||
// Normalize date fields (ผ่าน handler จะได้ string → ต้องแปลงเป็น Date)
|
|
||||||
// ─────────────────────────────────────────────────────────────
|
|
||||||
const toDate = (v: any): Date | null => {
|
|
||||||
if (v == null || v === "") return null;
|
|
||||||
if (v instanceof Date) return isNaN(v.getTime()) ? null : v;
|
|
||||||
const d = new Date(v);
|
|
||||||
return isNaN(d.getTime()) ? null : d;
|
|
||||||
};
|
|
||||||
for (const item of data ?? []) {
|
|
||||||
const it = item as any;
|
|
||||||
it.commandDateAffect = toDate(it.commandDateAffect);
|
|
||||||
it.commandDateSign = toDate(it.commandDateSign);
|
|
||||||
}
|
|
||||||
|
|
||||||
let _posNumCodeSit: string = "";
|
|
||||||
let _posNumCodeSitAbb: string = "";
|
|
||||||
const _command = await this.commandRepository.findOne({
|
|
||||||
where: { id: data.find((x) => x.commandId)?.commandId ?? "" },
|
|
||||||
});
|
|
||||||
if (_command) {
|
|
||||||
if (_command?.isBangkok?.toLocaleUpperCase() == "OFFICE") {
|
|
||||||
const orgRootDeputy = await this.orgRootRepository.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 this.profileRepository.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 ?? "";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// ─────────────────────────────────────────────────────────────
|
|
||||||
// Single transaction ครอบทั้ง batch (all-or-nothing)
|
|
||||||
// ทุกคนใช้ manager ตัวเดียวกัน — คนใด throw จะ rollback ทั้ง batch
|
|
||||||
// และ propagate error ออกไป (ล้มเหลวทั้งหมด) โดย log error ของคนที่ทำให้ fail ก่อน rethrow
|
|
||||||
// ─────────────────────────────────────────────────────────────
|
|
||||||
await AppDataSource.transaction(async (manager) => {
|
|
||||||
for (const item of data ?? []) {
|
|
||||||
try {
|
|
||||||
await this.processOne(item, ctx, manager, _posNumCodeSit, _posNumCodeSitAbb);
|
|
||||||
} catch (err) {
|
|
||||||
const reason =
|
|
||||||
err instanceof HttpError
|
|
||||||
? err.message
|
|
||||||
: err instanceof Error
|
|
||||||
? err.message
|
|
||||||
: "unexpected error";
|
|
||||||
console.error(
|
|
||||||
`[ExecuteSalaryCurrentService] Failed — commandCode: ${commandCode}, commandId: ${commandId}, profileId: ${item.profileId}, reason: ${reason}`,
|
|
||||||
err,
|
|
||||||
);
|
|
||||||
throw err; // → rollback ทั้ง transaction + propagate เป็น batch failure
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* ประมวลผล 1 คน ภายใน transaction เดียว (manager)
|
|
||||||
* ทุก save ใช้ manager.getRepository(...) เพื่อให้อยู่ใน transaction เดียวกัน
|
|
||||||
* ถ้า throw ระหว่างทาง → rollback ทั้งหมดของคนนี้ (กัน partial commit)
|
|
||||||
*/
|
|
||||||
private async processOne(
|
|
||||||
item: SalaryCurrentItem,
|
|
||||||
ctx: SalaryCurrentExecutionContext,
|
|
||||||
manager: EntityManager,
|
|
||||||
_posNumCodeSit: string,
|
|
||||||
_posNumCodeSitAbb: string,
|
|
||||||
): Promise<void> {
|
|
||||||
const req = ctx.req;
|
|
||||||
|
|
||||||
const profileRepository = manager.getRepository(Profile);
|
|
||||||
const salaryRepo = manager.getRepository(ProfileSalary);
|
|
||||||
const salaryHistoryRepo = manager.getRepository(ProfileSalaryHistory);
|
|
||||||
const posMasterRepository = manager.getRepository(PosMaster);
|
|
||||||
const positionRepository = manager.getRepository(Position);
|
|
||||||
|
|
||||||
const profile: any = await profileRepository.findOneBy({ id: item.profileId });
|
|
||||||
if (!profile) {
|
|
||||||
throw new HttpError(HttpStatusCode.NOT_FOUND, `ไม่พบข้อมูลทะเบียนประวัตินี้ profileId: ${item.profileId}`);
|
|
||||||
}
|
|
||||||
let _null: any = null;
|
|
||||||
const dest_item = await salaryRepo.findOne({
|
|
||||||
where: { profileId: item.profileId },
|
|
||||||
order: { order: "DESC" },
|
|
||||||
});
|
|
||||||
const before = null;
|
|
||||||
const dataSalary = new ProfileSalary();
|
|
||||||
|
|
||||||
const meta = {
|
|
||||||
order: dest_item == null ? 1 : dest_item.order + 1,
|
|
||||||
createdUserId: ctx.user.sub,
|
|
||||||
createdFullName: ctx.user.name,
|
|
||||||
lastUpdateUserId: ctx.user.sub,
|
|
||||||
lastUpdateFullName: ctx.user.name,
|
|
||||||
createdAt: new Date(),
|
|
||||||
lastUpdatedAt: new Date(),
|
|
||||||
};
|
|
||||||
dataSalary.posNumCodeSit = _posNumCodeSit;
|
|
||||||
dataSalary.posNumCodeSitAbb = _posNumCodeSitAbb;
|
|
||||||
Object.assign(dataSalary, { ...item, ...meta });
|
|
||||||
const history = new ProfileSalaryHistory();
|
|
||||||
Object.assign(history, { ...dataSalary, id: undefined });
|
|
||||||
await salaryRepo.save(dataSalary, { data: req });
|
|
||||||
setLogDataDiff(req, { before, after: dataSalary });
|
|
||||||
history.commandId = item.commandId ?? _null;
|
|
||||||
history.profileSalaryId = dataSalary.id;
|
|
||||||
await salaryHistoryRepo.save(history, { data: req });
|
|
||||||
|
|
||||||
// STEP 1: หา posMaster ที่จะใช้งานตาม id ที่ส่งมา
|
|
||||||
console.log(
|
|
||||||
`[ExecuteSalaryCurrentService] STEP 1: Finding posMaster — posmasterId: ${item.posmasterId}, profileId: ${item.profileId}`,
|
|
||||||
);
|
|
||||||
let posMaster = await posMasterRepository.findOne({
|
|
||||||
where: { id: item.posmasterId },
|
|
||||||
relations: {
|
|
||||||
orgRevision: true,
|
|
||||||
orgRoot: true,
|
|
||||||
orgChild1: true,
|
|
||||||
orgChild2: true,
|
|
||||||
orgChild3: true,
|
|
||||||
orgChild4: true,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
console.log(
|
|
||||||
`[ExecuteSalaryCurrentService] STEP 1: posMaster found: ${!!posMaster}, ancestorDNA: ${posMaster?.ancestorDNA ?? "null"}, orgRevisionId: ${posMaster?.orgRevisionId ?? "null"}`,
|
|
||||||
);
|
|
||||||
|
|
||||||
// เช็คว่า posMaster ที่หามาอยู่ในโครงสร้างปัจจุบันหรือไม่
|
|
||||||
const isCurrent =
|
|
||||||
posMaster?.orgRevision?.orgRevisionIsCurrent === true &&
|
|
||||||
posMaster?.orgRevision?.orgRevisionIsDraft === false;
|
|
||||||
console.log(`[ExecuteSalaryCurrentService] STEP 1: isCurrent: ${isCurrent}`);
|
|
||||||
|
|
||||||
// ถ้าไม่อยู่ในโครงสร้างปัจจุบัน ให้หาตัวใหม่จาก ancestorDNA
|
|
||||||
if (!isCurrent && posMaster?.ancestorDNA) {
|
|
||||||
console.log(
|
|
||||||
`[ExecuteSalaryCurrentService] STEP 1: Not current — re-resolving via ancestorDNA: ${posMaster.ancestorDNA}`,
|
|
||||||
);
|
|
||||||
posMaster = await posMasterRepository.findOne({
|
|
||||||
where: {
|
|
||||||
ancestorDNA: posMaster.ancestorDNA,
|
|
||||||
orgRevision: {
|
|
||||||
orgRevisionIsCurrent: true,
|
|
||||||
orgRevisionIsDraft: false,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
relations: {
|
|
||||||
orgRevision: true,
|
|
||||||
orgRoot: true,
|
|
||||||
orgChild1: true,
|
|
||||||
orgChild2: true,
|
|
||||||
orgChild3: true,
|
|
||||||
orgChild4: true,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
console.log(
|
|
||||||
`[ExecuteSalaryCurrentService] STEP 1: ancestorDNA re-resolve — found: ${!!posMaster}`,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (posMaster == null) {
|
|
||||||
console.error(
|
|
||||||
`[ExecuteSalaryCurrentService] STEP 1: PosMaster not found — posmasterId: ${item.posmasterId}`,
|
|
||||||
);
|
|
||||||
throw new HttpError(HttpStatusCode.NOT_FOUND, `ไม่พบข้อมูลตำแหน่งนี้ posMasterId: ${item.posmasterId}`);
|
|
||||||
}
|
|
||||||
|
|
||||||
const posMasterOld = await posMasterRepository.findOne({
|
|
||||||
where: {
|
|
||||||
current_holderId: item.profileId,
|
|
||||||
orgRevisionId: posMaster.orgRevisionId,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
if (posMasterOld != null) {
|
|
||||||
posMasterOld.current_holderId = null;
|
|
||||||
posMasterOld.lastUpdatedAt = new Date();
|
|
||||||
}
|
|
||||||
|
|
||||||
const positionOld = await positionRepository.findOne({
|
|
||||||
where: {
|
|
||||||
posMasterId: posMasterOld?.id,
|
|
||||||
positionIsSelected: true,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
if (positionOld != null) {
|
|
||||||
logPositionIsSelectedChange(positionOld.id, positionOld.positionIsSelected, false, {
|
|
||||||
posMasterId: posMasterOld?.id,
|
|
||||||
userId: ctx.user.sub,
|
|
||||||
endpoint: "updateMaster",
|
|
||||||
action: "command_change_reset_old_position",
|
|
||||||
});
|
|
||||||
|
|
||||||
positionOld.positionIsSelected = false;
|
|
||||||
await positionRepository.save(positionOld);
|
|
||||||
}
|
|
||||||
|
|
||||||
const checkPosition = await positionRepository.find({
|
|
||||||
where: {
|
|
||||||
posMasterId: posMaster!.id, // ใช้ posMaster ตัวใหม่ (ที่อาจจะเปลี่ยนจาก ancestorDNA)
|
|
||||||
positionIsSelected: true,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
if (checkPosition.length > 0) {
|
|
||||||
console.log(
|
|
||||||
`[positionIsSelected-DEBUG] Command change: clearing ${checkPosition.length} positions (posMasterId: ${posMaster!.id}, userId: ${ctx.user.sub}, endpoint: updateMaster)`,
|
|
||||||
);
|
|
||||||
|
|
||||||
const clearPosition = checkPosition.map((positions) => {
|
|
||||||
logPositionIsSelectedChange(positions.id, positions.positionIsSelected, false, {
|
|
||||||
posMasterId: posMaster!.id,
|
|
||||||
userId: ctx.user.sub,
|
|
||||||
endpoint: "updateMaster",
|
|
||||||
action: "command_change_clear_positions",
|
|
||||||
});
|
|
||||||
|
|
||||||
return {
|
|
||||||
...positions,
|
|
||||||
positionIsSelected: false,
|
|
||||||
};
|
|
||||||
});
|
|
||||||
await positionRepository.save(clearPosition);
|
|
||||||
}
|
|
||||||
|
|
||||||
posMaster.current_holderId = item.profileId;
|
|
||||||
posMaster.lastUpdatedAt = new Date();
|
|
||||||
// posMaster.conditionReason = _null;
|
|
||||||
// posMaster.isCondition = false;
|
|
||||||
if (posMasterOld != null) {
|
|
||||||
await posMasterRepository.save(posMasterOld);
|
|
||||||
// ส่ง manager เข้าไปเพื่อให้อยู่ใน transaction เดียวกัน
|
|
||||||
console.log(
|
|
||||||
`[ExecuteSalaryCurrentService] Creating PosMasterHistory — posMasterId: ${posMasterOld.id}, profileId: ${item.profileId} (old)`,
|
|
||||||
);
|
|
||||||
await CreatePosMasterHistoryOfficer(posMasterOld.id, req, null, null, manager);
|
|
||||||
}
|
|
||||||
await posMasterRepository.save(posMaster);
|
|
||||||
|
|
||||||
// STEP 2: กำหนด position ใหม่
|
|
||||||
// Match position ตามลำดับ priority:
|
|
||||||
// Condition 1: match จาก positionId
|
|
||||||
// Condition 2: match 7 ฟิลด์ (positionName, posTypeId, posLevelId, positionField, positionArea, positionExecutiveField, posExecutiveId)
|
|
||||||
// Condition 3: match 3 ฟิลด์ (positionName, posTypeId, posLevelId)
|
|
||||||
// Fallback: เลือก position แรกใน posMaster
|
|
||||||
|
|
||||||
let positionNew: Position | null = null;
|
|
||||||
|
|
||||||
// Resolve ID: ใช้ positionTypeId/positionLevelId ก่อน ถ้าไม่มี fallback เป็น positionType/positionLevel
|
|
||||||
const posTypeId = item.positionTypeId || item.positionType;
|
|
||||||
const posLevelId = item.positionLevelId || item.positionLevel;
|
|
||||||
|
|
||||||
console.log(
|
|
||||||
`[ExecuteSalaryCurrentService] STEP 2: Resolving position — posMasterId: ${posMaster.id}, positionId: ${item.positionId ?? "null"}, positionName: ${item.positionName ?? "null"}, posTypeId: ${posTypeId ?? "null"}, posLevelId: ${posLevelId ?? "null"}`,
|
|
||||||
);
|
|
||||||
|
|
||||||
// ═══════════════════════════════════════════════════════════
|
|
||||||
// CONDITION 1: เช็คจาก positionId ตรง
|
|
||||||
// ═══════════════════════════════════════════════════════════
|
|
||||||
if (item.positionId) {
|
|
||||||
const positionById = await positionRepository.findOne({
|
|
||||||
where: {
|
|
||||||
id: item.positionId,
|
|
||||||
posMasterId: posMaster.id, // ต้องอยู่ใน posMaster ที่ถูกต้อง
|
|
||||||
},
|
|
||||||
relations: ["posExecutive"],
|
|
||||||
});
|
|
||||||
console.log(
|
|
||||||
`[ExecuteSalaryCurrentService] STEP 2 / Condition 1: match: ${!!positionById}`,
|
|
||||||
);
|
|
||||||
|
|
||||||
if (positionById) {
|
|
||||||
positionNew = positionById;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// ═══════════════════════════════════════════════════════════
|
|
||||||
// CONDITION 2: Match 7 ฟิลด์ (ถ้า Condition 1 ไม่ match)
|
|
||||||
// ═══════════════════════════════════════════════════════════
|
|
||||||
if (!positionNew && item.positionName && posTypeId && posLevelId) {
|
|
||||||
// สร้าง where clause แบบ dynamic - ใส่เฉพาะฟิลด์ที่มีค่า
|
|
||||||
const whereCondition: any = {
|
|
||||||
posMasterId: posMaster.id,
|
|
||||||
positionName: item.positionName,
|
|
||||||
posTypeId: posTypeId,
|
|
||||||
posLevelId: posLevelId,
|
|
||||||
};
|
|
||||||
|
|
||||||
// เพิ่มเฉพาะฟิลด์ที่มีค่า (ไม่ใช่ null, undefined, หรือ string ว่าง)
|
|
||||||
if (item.positionField) {
|
|
||||||
whereCondition.positionField = item.positionField;
|
|
||||||
}
|
|
||||||
if (item.posExecutiveId) {
|
|
||||||
whereCondition.posExecutiveId = item.posExecutiveId;
|
|
||||||
}
|
|
||||||
if (item.positionExecutiveField) {
|
|
||||||
whereCondition.positionExecutiveField = item.positionExecutiveField;
|
|
||||||
}
|
|
||||||
if (item.positionArea) {
|
|
||||||
whereCondition.positionArea = item.positionArea;
|
|
||||||
}
|
|
||||||
|
|
||||||
const positionBy7Fields = await positionRepository.findOne({
|
|
||||||
where: whereCondition,
|
|
||||||
relations: ["posExecutive"],
|
|
||||||
order: { orderNo: "ASC" },
|
|
||||||
});
|
|
||||||
console.log(
|
|
||||||
`[ExecuteSalaryCurrentService] STEP 2 / Condition 2: match: ${!!positionBy7Fields}`,
|
|
||||||
whereCondition,
|
|
||||||
);
|
|
||||||
|
|
||||||
if (positionBy7Fields) {
|
|
||||||
positionNew = positionBy7Fields;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// ═══════════════════════════════════════════════════════════
|
|
||||||
// CONDITION 3: Match 3 ฟิลด์ (ถ้า Condition 2 ไม่ match)
|
|
||||||
// ═══════════════════════════════════════════════════════════
|
|
||||||
if (!positionNew && item.positionName && posTypeId && posLevelId) {
|
|
||||||
const positionBy3Fields = await positionRepository.findOne({
|
|
||||||
where: {
|
|
||||||
posMasterId: posMaster.id,
|
|
||||||
positionName: item.positionName,
|
|
||||||
posTypeId: posTypeId,
|
|
||||||
posLevelId: posLevelId,
|
|
||||||
},
|
|
||||||
relations: ["posExecutive"],
|
|
||||||
order: { orderNo: "ASC" },
|
|
||||||
});
|
|
||||||
console.log(
|
|
||||||
`[ExecuteSalaryCurrentService] STEP 2 / Condition 3: match: ${!!positionBy3Fields}`,
|
|
||||||
);
|
|
||||||
|
|
||||||
if (positionBy3Fields) {
|
|
||||||
positionNew = positionBy3Fields;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
console.log(
|
|
||||||
`[ExecuteSalaryCurrentService] STEP 2: Resolved positionNew: ${positionNew ? positionNew.id : "null (no match — profile position not updated)"}`,
|
|
||||||
);
|
|
||||||
|
|
||||||
// ถ้าไม่ใช่ตำแหน่งนั่งทับ (isSit = false) ถึงจะอัพเดทตำแหน่งในทะเบียนประวัติ
|
|
||||||
if (positionNew != null) {
|
|
||||||
positionNew.positionIsSelected = true;
|
|
||||||
// อัพเดท org และ posMasterNo ตลอดไม่ต้องดัก isSit
|
|
||||||
profile.posMasterNo = getPosMasterNo(posMaster);
|
|
||||||
profile.org = getOrgFullName(posMaster);
|
|
||||||
if (!posMaster.isSit) {
|
|
||||||
profile.posLevelId = positionNew.posLevelId;
|
|
||||||
profile.posTypeId = positionNew.posTypeId;
|
|
||||||
profile.position = positionNew.positionName;
|
|
||||||
profile.positionField = positionNew.positionField ?? null;
|
|
||||||
profile.posExecutive = positionNew.posExecutive?.posExecutiveName ?? null;
|
|
||||||
profile.positionArea = positionNew.positionArea ?? null;
|
|
||||||
profile.positionExecutiveField = positionNew.positionExecutiveField ?? null;
|
|
||||||
}
|
|
||||||
profile.amount = item.amount ?? null;
|
|
||||||
profile.amountSpecial = item.amountSpecial ?? null;
|
|
||||||
await profileRepository.save(profile);
|
|
||||||
await positionRepository.save(positionNew);
|
|
||||||
}
|
|
||||||
// ส่ง manager เข้าไปเพื่อให้อยู่ใน transaction เดียวกัน
|
|
||||||
console.log(
|
|
||||||
`[ExecuteSalaryCurrentService] Creating PosMasterHistory — posMasterId: ${posMaster.id}, profileId: ${item.profileId}`,
|
|
||||||
);
|
|
||||||
await CreatePosMasterHistoryOfficer(posMaster.id, req, null, null, manager);
|
|
||||||
|
|
||||||
console.log(
|
|
||||||
`[ExecuteSalaryCurrentService] Completed processOne — profileId: ${item.profileId}, posMasterId: ${posMaster.id}`,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,316 +0,0 @@
|
||||||
import { Double, EntityManager } from "typeorm";
|
|
||||||
import { AppDataSource } from "../database/data-source";
|
|
||||||
import HttpError from "../interfaces/http-error";
|
|
||||||
import HttpStatusCode from "../interfaces/http-status";
|
|
||||||
import HttpStatus from "../interfaces/http-status";
|
|
||||||
import { Profile } from "../entities/Profile";
|
|
||||||
import { ProfileEmployee } from "../entities/ProfileEmployee";
|
|
||||||
import { ProfileSalary } from "../entities/ProfileSalary";
|
|
||||||
import { ProfileSalaryHistory } from "../entities/ProfileSalaryHistory";
|
|
||||||
import { OrgRoot } from "../entities/OrgRoot";
|
|
||||||
import { EmployeePosMaster } from "../entities/EmployeePosMaster";
|
|
||||||
import { EmployeePosition } from "../entities/EmployeePosition";
|
|
||||||
import { Command } from "../entities/Command";
|
|
||||||
import { setLogDataDiff } from "../interfaces/utils";
|
|
||||||
import { CreatePosMasterHistoryEmployee } from "./PositionService";
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Input: ข้อมูล 1 คนสำหรับ endpoint excexute/salary-employee-current
|
|
||||||
* (C-PM-22, 24 — เปลี่ยนตำแหน่งปัจจุบันของลูกจ้าง + salary ใหม่)
|
|
||||||
*/
|
|
||||||
export interface SalaryEmployeeCurrentItem {
|
|
||||||
profileId: string;
|
|
||||||
amount?: Double | null;
|
|
||||||
amountSpecial?: Double | null;
|
|
||||||
positionSalaryAmount?: Double | null;
|
|
||||||
mouthSalaryAmount?: Double | null;
|
|
||||||
positionType: string | null;
|
|
||||||
positionLevel: string | null;
|
|
||||||
posmasterId: string;
|
|
||||||
positionId: string;
|
|
||||||
commandId?: string | null;
|
|
||||||
orgRoot?: string | null;
|
|
||||||
orgChild1?: string | null;
|
|
||||||
orgChild2?: string | null;
|
|
||||||
orgChild3?: string | null;
|
|
||||||
orgChild4?: string | null;
|
|
||||||
commandNo: string | null;
|
|
||||||
commandYear: number | null;
|
|
||||||
posNo: string | null;
|
|
||||||
posNoAbb: string | null;
|
|
||||||
commandDateAffect?: Date | string | null;
|
|
||||||
commandDateSign?: Date | string | null;
|
|
||||||
positionName: string | null;
|
|
||||||
commandCode?: string | null;
|
|
||||||
commandName?: string | null;
|
|
||||||
remark: string | null;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Context สำหรับ audit/log
|
|
||||||
*/
|
|
||||||
export interface SalaryEmployeeCurrentExecutionContext {
|
|
||||||
user: { sub: string; name: string };
|
|
||||||
req?: any;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Service สำหรับสร้าง ProfileSalary ของลูกจ้าง + อัปเดตตำแหน่งปัจจุบัน (เปลี่ยนตำแหน่ง)
|
|
||||||
*
|
|
||||||
* ใช้กับ commandType: C-PM-22, 24
|
|
||||||
*
|
|
||||||
* - endpoint /org/command/excexute/salary-employee-current เรียกผ่าน service นี้ (thin wrapper)
|
|
||||||
* - consumer ใน rabbitmq handler เรียกผ่าน service นี้โดยตรง (Linear Flow)
|
|
||||||
*
|
|
||||||
* Behavior ทั้งหมด preserve จาก CommandController.newSalaryEmployeeAndUpdateCurrent ต้นฉบับ
|
|
||||||
*
|
|
||||||
* Batch semantics: all-or-nothing — ประมวลผลทุกคนภายใต้ transaction เดียว (sequential)
|
|
||||||
* ถ้าคนใด throw จะ rollback ทั้ง batch และ propagate error ออกไป (ล้มเหลวทั้งหมด)
|
|
||||||
* ถ้าทุกคนสำเร็จจะ return result รายงาน success count
|
|
||||||
*/
|
|
||||||
export class ExecuteSalaryEmployeeCurrentService {
|
|
||||||
private commandRepository = AppDataSource.getRepository(Command);
|
|
||||||
private profileRepository = AppDataSource.getRepository(Profile);
|
|
||||||
private orgRootRepository = AppDataSource.getRepository(OrgRoot);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* ประมวลผลสร้าง ProfileSalary + อัปเดตตำแหน่งปัจจุบันของลูกจ้างทั้ง batch
|
|
||||||
*/
|
|
||||||
async executeSalaryEmployeeCurrent(
|
|
||||||
data: SalaryEmployeeCurrentItem[],
|
|
||||||
ctx: SalaryEmployeeCurrentExecutionContext,
|
|
||||||
): Promise<void> {
|
|
||||||
const commandId = data?.find((x) => x.commandId)?.commandId ?? "unknown";
|
|
||||||
const commandCode = data?.find((x) => x.commandCode)?.commandCode ?? "unknown";
|
|
||||||
console.log(
|
|
||||||
`[ExecuteSalaryEmployeeCurrentService] Starting executeSalaryEmployeeCurrent — commandCode: ${commandCode}, commandId: ${commandId}`,
|
|
||||||
);
|
|
||||||
console.log(`[ExecuteSalaryEmployeeCurrentService] Request body count: ${data?.length ?? 0}`);
|
|
||||||
|
|
||||||
// ─────────────────────────────────────────────────────────────
|
|
||||||
// Normalize date fields (ผ่าน handler จะได้ string → ต้องแปลงเป็น Date)
|
|
||||||
// ─────────────────────────────────────────────────────────────
|
|
||||||
const toDate = (v: any): Date | null => {
|
|
||||||
if (v == null || v === "") return null;
|
|
||||||
if (v instanceof Date) return isNaN(v.getTime()) ? null : v;
|
|
||||||
const d = new Date(v);
|
|
||||||
return isNaN(d.getTime()) ? null : d;
|
|
||||||
};
|
|
||||||
for (const item of data ?? []) {
|
|
||||||
const it = item as any;
|
|
||||||
it.commandDateAffect = toDate(it.commandDateAffect);
|
|
||||||
it.commandDateSign = toDate(it.commandDateSign);
|
|
||||||
}
|
|
||||||
|
|
||||||
let _posNumCodeSit: string = "";
|
|
||||||
let _posNumCodeSitAbb: string = "";
|
|
||||||
const _command = await this.commandRepository.findOne({
|
|
||||||
where: { id: data.find((x) => x.commandId)?.commandId ?? "" },
|
|
||||||
});
|
|
||||||
if (_command) {
|
|
||||||
if (_command?.isBangkok?.toLocaleUpperCase() == "OFFICE") {
|
|
||||||
const orgRootDeputy = await this.orgRootRepository.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 this.profileRepository.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 ?? "";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// ─────────────────────────────────────────────────────────────
|
|
||||||
// Single transaction ครอบทั้ง batch (all-or-nothing)
|
|
||||||
// ทุกคนใช้ manager ตัวเดียวกัน — คนใด throw จะ rollback ทั้ง batch
|
|
||||||
// และ propagate error ออกไป (ล้มเหลวทั้งหมด) โดย log error ของคนที่ทำให้ fail ก่อน rethrow
|
|
||||||
// ─────────────────────────────────────────────────────────────
|
|
||||||
await AppDataSource.transaction(async (manager) => {
|
|
||||||
for (const item of data ?? []) {
|
|
||||||
try {
|
|
||||||
await this.processOne(item, ctx, manager, _posNumCodeSit, _posNumCodeSitAbb);
|
|
||||||
} catch (err) {
|
|
||||||
const reason =
|
|
||||||
err instanceof HttpError
|
|
||||||
? err.message
|
|
||||||
: err instanceof Error
|
|
||||||
? err.message
|
|
||||||
: "unexpected error";
|
|
||||||
console.error(
|
|
||||||
`[ExecuteSalaryEmployeeCurrentService] Failed commandCode=${commandCode}, commandId=${commandId}, profileId=${item.profileId}: ${reason}`,
|
|
||||||
err,
|
|
||||||
);
|
|
||||||
throw err; // → rollback ทั้ง transaction + propagate เป็น batch failure
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* ประมวลผล 1 คน ภายใน transaction เดียว (manager)
|
|
||||||
* ทุก save ใช้ manager.getRepository(...) เพื่อให้อยู่ใน transaction เดียวกัน
|
|
||||||
* ถ้า throw ระหว่างทาง → rollback ทั้งหมดของคนนี้ + ทั้ง batch (กัน partial commit)
|
|
||||||
*/
|
|
||||||
private async processOne(
|
|
||||||
item: SalaryEmployeeCurrentItem,
|
|
||||||
ctx: SalaryEmployeeCurrentExecutionContext,
|
|
||||||
manager: EntityManager,
|
|
||||||
_posNumCodeSit: string,
|
|
||||||
_posNumCodeSitAbb: string,
|
|
||||||
): Promise<void> {
|
|
||||||
const req = ctx.req;
|
|
||||||
|
|
||||||
const profileEmployeeRepository = manager.getRepository(ProfileEmployee);
|
|
||||||
const salaryRepo = manager.getRepository(ProfileSalary);
|
|
||||||
const salaryHistoryRepo = manager.getRepository(ProfileSalaryHistory);
|
|
||||||
const employeePosMasterRepository = manager.getRepository(EmployeePosMaster);
|
|
||||||
const employeePositionRepository = manager.getRepository(EmployeePosition);
|
|
||||||
|
|
||||||
const profile: any = await profileEmployeeRepository.findOneBy({ id: item.profileId });
|
|
||||||
if (!profile) {
|
|
||||||
throw new HttpError(HttpStatus.BAD_REQUEST, "ไม่พบ profile ดังกล่าว");
|
|
||||||
}
|
|
||||||
|
|
||||||
const dest_item = await salaryRepo.findOne({
|
|
||||||
where: { profileEmployeeId: item.profileId },
|
|
||||||
order: { order: "DESC" },
|
|
||||||
});
|
|
||||||
const before = null;
|
|
||||||
const dataSalary = new ProfileSalary();
|
|
||||||
dataSalary.posNumCodeSit = _posNumCodeSit;
|
|
||||||
dataSalary.posNumCodeSitAbb = _posNumCodeSitAbb;
|
|
||||||
const meta = {
|
|
||||||
order: dest_item == null ? 1 : dest_item.order + 1,
|
|
||||||
createdUserId: ctx.user.sub,
|
|
||||||
createdFullName: ctx.user.name,
|
|
||||||
lastUpdateUserId: ctx.user.sub,
|
|
||||||
lastUpdateFullName: ctx.user.name,
|
|
||||||
createdAt: new Date(),
|
|
||||||
lastUpdatedAt: new Date(),
|
|
||||||
};
|
|
||||||
|
|
||||||
Object.assign(dataSalary, {
|
|
||||||
...item,
|
|
||||||
...meta,
|
|
||||||
profileEmployeeId: item.profileId,
|
|
||||||
profileId: undefined,
|
|
||||||
});
|
|
||||||
const history = new ProfileSalaryHistory();
|
|
||||||
Object.assign(history, { ...dataSalary, id: undefined });
|
|
||||||
|
|
||||||
await salaryRepo.save(dataSalary, { data: req });
|
|
||||||
setLogDataDiff(req, { before, after: dataSalary });
|
|
||||||
history.profileSalaryId = dataSalary.id;
|
|
||||||
await salaryHistoryRepo.save(history, { data: req });
|
|
||||||
|
|
||||||
const posMaster = await employeePosMasterRepository.findOne({
|
|
||||||
where: { id: item.posmasterId },
|
|
||||||
relations: ["orgRoot"],
|
|
||||||
});
|
|
||||||
if (posMaster == null)
|
|
||||||
throw new HttpError(HttpStatusCode.NOT_FOUND, "ไม่พบข้อมูลตำแหน่งนี้");
|
|
||||||
|
|
||||||
const posMasterOld = await employeePosMasterRepository.findOne({
|
|
||||||
where: {
|
|
||||||
current_holderId: item.profileId,
|
|
||||||
orgRevisionId: posMaster.orgRevisionId,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
if (posMasterOld != null) {
|
|
||||||
posMasterOld.current_holderId = null;
|
|
||||||
posMasterOld.lastUpdatedAt = new Date();
|
|
||||||
}
|
|
||||||
// if (posMasterOld != null) posMasterOld.next_holderId = null;
|
|
||||||
|
|
||||||
const positionOld = await employeePositionRepository.findOne({
|
|
||||||
where: {
|
|
||||||
posMasterId: posMasterOld?.id,
|
|
||||||
positionIsSelected: true,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
if (positionOld != null) {
|
|
||||||
positionOld.positionIsSelected = false;
|
|
||||||
await employeePositionRepository.save(positionOld);
|
|
||||||
}
|
|
||||||
|
|
||||||
const checkPosition = await employeePositionRepository.find({
|
|
||||||
where: {
|
|
||||||
posMasterId: item.posmasterId,
|
|
||||||
positionIsSelected: true,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
if (checkPosition.length > 0) {
|
|
||||||
const clearPosition = checkPosition.map((positions) => ({
|
|
||||||
...positions,
|
|
||||||
positionIsSelected: false,
|
|
||||||
}));
|
|
||||||
await employeePositionRepository.save(clearPosition);
|
|
||||||
}
|
|
||||||
|
|
||||||
posMaster.current_holderId = item.profileId;
|
|
||||||
posMaster.lastUpdatedAt = new Date();
|
|
||||||
posMaster.next_holderId = null;
|
|
||||||
if (posMasterOld != null) {
|
|
||||||
await employeePosMasterRepository.save(posMasterOld);
|
|
||||||
// ส่ง manager เข้าไปเพื่อให้อยู่ใน transaction เดียวกัน
|
|
||||||
console.log(
|
|
||||||
`[ExecuteSalaryEmployeeCurrentService] Creating PosMasterHistory — posMasterId: ${posMasterOld.id}, profileId: ${item.profileId} (old)`,
|
|
||||||
);
|
|
||||||
await CreatePosMasterHistoryEmployee(posMasterOld.id, req, null, manager);
|
|
||||||
}
|
|
||||||
await employeePosMasterRepository.save(posMaster);
|
|
||||||
const positionNew = await employeePositionRepository.findOne({
|
|
||||||
where: {
|
|
||||||
id: item.positionId,
|
|
||||||
posMasterId: item.posmasterId,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
if (positionNew != null) {
|
|
||||||
positionNew.positionIsSelected = true;
|
|
||||||
profile.posLevelId = positionNew.posLevelId;
|
|
||||||
profile.posTypeId = positionNew.posTypeId;
|
|
||||||
profile.position = positionNew.positionName;
|
|
||||||
profile.employeeOc = posMaster?.orgRoot?.orgRootName ?? null;
|
|
||||||
profile.positionEmployeePositionId = positionNew.positionName;
|
|
||||||
profile.amount = item.amount ?? null;
|
|
||||||
profile.amountSpecial = item.amountSpecial ?? null;
|
|
||||||
await profileEmployeeRepository.save(profile);
|
|
||||||
await employeePositionRepository.save(positionNew);
|
|
||||||
}
|
|
||||||
// ส่ง manager เข้าไปเพื่อให้อยู่ใน transaction เดียวกัน
|
|
||||||
console.log(
|
|
||||||
`[ExecuteSalaryEmployeeCurrentService] Creating PosMasterHistory — posMasterId: ${posMaster.id}, profileId: ${item.profileId}`,
|
|
||||||
);
|
|
||||||
await CreatePosMasterHistoryEmployee(posMaster.id, req, null, manager);
|
|
||||||
|
|
||||||
console.log(
|
|
||||||
`[ExecuteSalaryEmployeeCurrentService] Completed processOne — profileId: ${item.profileId}, posMasterId: ${posMaster.id}`,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,382 +0,0 @@
|
||||||
import { Double, EntityManager } from "typeorm";
|
|
||||||
import { AppDataSource } from "../database/data-source";
|
|
||||||
import HttpError from "../interfaces/http-error";
|
|
||||||
import HttpStatus from "../interfaces/http-status";
|
|
||||||
import { Profile } from "../entities/Profile";
|
|
||||||
import { ProfileEmployee } from "../entities/ProfileEmployee";
|
|
||||||
import { ProfileSalary } from "../entities/ProfileSalary";
|
|
||||||
import { ProfileSalaryHistory } from "../entities/ProfileSalaryHistory";
|
|
||||||
import { OrgRoot } from "../entities/OrgRoot";
|
|
||||||
import { OrgRevision } from "../entities/OrgRevision";
|
|
||||||
import { EmployeePosMaster } from "../entities/EmployeePosMaster";
|
|
||||||
import { CommandRecive } from "../entities/CommandRecive";
|
|
||||||
import { Command } from "../entities/Command";
|
|
||||||
import { checkCommandType, removeProfileInOrganize, setLogDataDiff } from "../interfaces/utils";
|
|
||||||
import { reOrderCommandRecivesAndDelete } from "./CommandService";
|
|
||||||
import { CreatePosMasterHistoryEmployee } from "./PositionService";
|
|
||||||
import { deleteUser } from "../keycloak";
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Input: ข้อมูล 1 คนสำหรับ endpoint excexute/salary-employee-leave
|
|
||||||
* (C-PM-23, 42, 43 — ลาออก/ยกเลิกลาออก/กลับเข้าราชการ ของลูกจ้าง)
|
|
||||||
*/
|
|
||||||
export interface SalaryEmployeeLeaveItem {
|
|
||||||
profileId: string;
|
|
||||||
amount?: Double | null;
|
|
||||||
amountSpecial?: Double | null;
|
|
||||||
positionSalaryAmount?: Double | null;
|
|
||||||
mouthSalaryAmount?: Double | null;
|
|
||||||
positionType: string | null;
|
|
||||||
positionLevel: string | null;
|
|
||||||
isLeave: boolean;
|
|
||||||
leaveReason?: string | null;
|
|
||||||
dateLeave?: Date | string | null;
|
|
||||||
isGovernment?: boolean | null;
|
|
||||||
commandId?: string | null;
|
|
||||||
orgRoot?: string | null;
|
|
||||||
orgChild1?: string | null;
|
|
||||||
orgChild2?: string | null;
|
|
||||||
orgChild3?: string | null;
|
|
||||||
orgChild4?: string | null;
|
|
||||||
positionExecutive?: string | null;
|
|
||||||
positionExecutiveField?: string | null;
|
|
||||||
positionArea?: string | null;
|
|
||||||
commandNo: string | null;
|
|
||||||
commandYear: number | null;
|
|
||||||
posNo: string | null;
|
|
||||||
posNoAbb: string | null;
|
|
||||||
commandDateAffect?: Date | string | null;
|
|
||||||
commandDateSign?: Date | string | null;
|
|
||||||
positionName: string | null;
|
|
||||||
commandCode?: string | null;
|
|
||||||
commandName?: string | null;
|
|
||||||
remark: string | null;
|
|
||||||
resignId: string | null;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Context สำหรับ audit/log
|
|
||||||
*/
|
|
||||||
export interface SalaryEmployeeLeaveExecutionContext {
|
|
||||||
user: { sub: string; name: string };
|
|
||||||
req?: any;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Service สำหรับสร้าง ProfileSalary ลูกจ้าง + handle leave/กลับเข้าราชการ
|
|
||||||
*
|
|
||||||
* ใช้กับ commandType: C-PM-23, 42, 43
|
|
||||||
*
|
|
||||||
* - endpoint /org/command/excexute/salary-employee-leave เรียกผ่าน service นี้ (thin wrapper)
|
|
||||||
* - consumer ใน rabbitmq handler เรียกผ่าน service นี้โดยตรง (Linear Flow)
|
|
||||||
*
|
|
||||||
* Behavior ทั้งหมด preserve จาก CommandController.newSalaryEmployeeAndUpdateLeave ต้นฉบับ
|
|
||||||
*
|
|
||||||
* Batch semantics: all-or-nothing — ประมวลผลทุกคนภายใต้ transaction เดียว (sequential)
|
|
||||||
* ถ้าคนใด throw จะ rollback ทั้ง batch และ propagate error ออกไป (ล้มเหลวทั้งหมด)
|
|
||||||
* ถ้าทุกคนสำเร็จจะ return result รายงาน success count
|
|
||||||
*
|
|
||||||
* ⚠️ หมายเหตุ Keycloak: operation (deleteUser) ทำภายใน transaction เพื่อ preserve behavior
|
|
||||||
* เดิม — Keycloak ไม่สามารถ rollback ได้ ถ้า DB rollback หลังจาก Keycloak operation สำเร็จ
|
|
||||||
* → Keycloak จะถูกเปลี่ยนไปแล้ว
|
|
||||||
*/
|
|
||||||
export class ExecuteSalaryEmployeeLeaveService {
|
|
||||||
private commandRepository = AppDataSource.getRepository(Command);
|
|
||||||
private commandReciveRepository = AppDataSource.getRepository(CommandRecive);
|
|
||||||
private profileRepository = AppDataSource.getRepository(Profile);
|
|
||||||
private orgRootRepository = AppDataSource.getRepository(OrgRoot);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* ประมวลผลสร้าง ProfileSalary + handle leave ของลูกจ้างทั้ง batch
|
|
||||||
*/
|
|
||||||
async executeSalaryEmployeeLeave(
|
|
||||||
data: SalaryEmployeeLeaveItem[],
|
|
||||||
ctx: SalaryEmployeeLeaveExecutionContext,
|
|
||||||
): Promise<void> {
|
|
||||||
const commandId = data?.find((x) => x.commandId)?.commandId ?? "unknown";
|
|
||||||
const commandCode = data?.find((x) => x.commandCode)?.commandCode ?? "unknown";
|
|
||||||
console.log(
|
|
||||||
`[ExecuteSalaryEmployeeLeaveService] Starting executeSalaryEmployeeLeave — commandCode: ${commandCode}, commandId: ${commandId}`,
|
|
||||||
);
|
|
||||||
console.log(`[ExecuteSalaryEmployeeLeaveService] Request body count: ${data?.length ?? 0}`);
|
|
||||||
|
|
||||||
// ─────────────────────────────────────────────────────────────
|
|
||||||
// Normalize date fields (ผ่าน handler จะได้ string → ต้องแปลงเป็น Date)
|
|
||||||
// ─────────────────────────────────────────────────────────────
|
|
||||||
const toDate = (v: any): Date | null => {
|
|
||||||
if (v == null || v === "") return null;
|
|
||||||
if (v instanceof Date) return isNaN(v.getTime()) ? null : v;
|
|
||||||
const d = new Date(v);
|
|
||||||
return isNaN(d.getTime()) ? null : d;
|
|
||||||
};
|
|
||||||
for (const item of data ?? []) {
|
|
||||||
const it = item as any;
|
|
||||||
it.dateLeave = toDate(it.dateLeave);
|
|
||||||
it.commandDateAffect = toDate(it.commandDateAffect);
|
|
||||||
it.commandDateSign = toDate(it.commandDateSign);
|
|
||||||
}
|
|
||||||
|
|
||||||
let _posNumCodeSit: string = "";
|
|
||||||
let _posNumCodeSitAbb: string = "";
|
|
||||||
const _command = await this.commandRepository.findOne({
|
|
||||||
where: { id: data.find((x) => x.commandId)?.commandId ?? "" },
|
|
||||||
relations: { commandType: true },
|
|
||||||
});
|
|
||||||
if (_command) {
|
|
||||||
if (_command?.isBangkok?.toLocaleUpperCase() == "OFFICE") {
|
|
||||||
const orgRootDeputy = await this.orgRootRepository.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 this.profileRepository.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 ?? "";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
const today = new Date().setHours(0, 0, 0, 0);
|
|
||||||
|
|
||||||
// ─────────────────────────────────────────────────────────────
|
|
||||||
// Single transaction ครอบทั้ง batch (all-or-nothing)
|
|
||||||
// ทุกคนใช้ manager ตัวเดียวกัน — คนใด throw จะ rollback ทั้ง batch
|
|
||||||
// และ propagate error ออกไป (ล้มเหลวทั้งหมด) โดย log error ของคนที่ทำให้ fail ก่อน rethrow
|
|
||||||
// ─────────────────────────────────────────────────────────────
|
|
||||||
await AppDataSource.transaction(async (manager) => {
|
|
||||||
for (const item of data ?? []) {
|
|
||||||
try {
|
|
||||||
await this.processOne(item, ctx, manager, _command, _posNumCodeSit, _posNumCodeSitAbb, today);
|
|
||||||
} catch (err) {
|
|
||||||
const reason =
|
|
||||||
err instanceof HttpError
|
|
||||||
? err.message
|
|
||||||
: err instanceof Error
|
|
||||||
? err.message
|
|
||||||
: "unexpected error";
|
|
||||||
console.error(
|
|
||||||
`[ExecuteSalaryEmployeeLeaveService] Failed commandCode=${commandCode}, commandId=${commandId}, profileId=${item.profileId}: ${reason}`,
|
|
||||||
err,
|
|
||||||
);
|
|
||||||
throw err; // → rollback ทั้ง transaction + propagate เป็น batch failure
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* ประมวลผล 1 คน ภายใน transaction เดียว (manager)
|
|
||||||
* ทุก save ใช้ manager.getRepository(...) เพื่อให้อยู่ใน transaction เดียวกัน
|
|
||||||
* ถ้า throw ระหว่างทาง → rollback ทั้งหมดของคนนี้ + ทั้ง batch (กัน partial commit)
|
|
||||||
*/
|
|
||||||
private async processOne(
|
|
||||||
item: SalaryEmployeeLeaveItem,
|
|
||||||
ctx: SalaryEmployeeLeaveExecutionContext,
|
|
||||||
manager: EntityManager,
|
|
||||||
_command: Command | null,
|
|
||||||
_posNumCodeSit: string,
|
|
||||||
_posNumCodeSitAbb: string,
|
|
||||||
today: number,
|
|
||||||
): Promise<void> {
|
|
||||||
const req = ctx.req;
|
|
||||||
|
|
||||||
const commandReciveRepository = manager.getRepository(CommandRecive);
|
|
||||||
const profileEmployeeRepository = manager.getRepository(ProfileEmployee);
|
|
||||||
const salaryRepo = manager.getRepository(ProfileSalary);
|
|
||||||
const salaryHistoryRepo = manager.getRepository(ProfileSalaryHistory);
|
|
||||||
const employeePosMasterRepository = manager.getRepository(EmployeePosMaster);
|
|
||||||
const orgRevisionRepo = manager.getRepository(OrgRevision);
|
|
||||||
|
|
||||||
const profile = await profileEmployeeRepository.findOne({
|
|
||||||
where: { id: item.profileId },
|
|
||||||
relations: {
|
|
||||||
roleKeycloaks: true,
|
|
||||||
posType: true,
|
|
||||||
posLevel: true,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
if (!profile) {
|
|
||||||
throw new HttpError(HttpStatus.BAD_REQUEST, "ไม่พบ profile ดังกล่าว");
|
|
||||||
}
|
|
||||||
const code = _command?.commandType?.code;
|
|
||||||
//ออกคำสั่งยกเลิกลาออก ลบเฉพาะคนที่ขอยกเลิกลาออก
|
|
||||||
if (item.resignId && code && ["C-PM-42"].includes(code)) {
|
|
||||||
const commandResign = await commandReciveRepository.findOne({
|
|
||||||
where: { refId: item.resignId },
|
|
||||||
relations: { command: true },
|
|
||||||
});
|
|
||||||
const executeDate = commandResign
|
|
||||||
? new Date(commandResign.command.commandExcecuteDate).setHours(0, 0, 0, 0)
|
|
||||||
: today;
|
|
||||||
if (
|
|
||||||
commandResign &&
|
|
||||||
_command.status !== "REPORTED" &&
|
|
||||||
(_command.status !== "WAITING" || today < executeDate)
|
|
||||||
) {
|
|
||||||
// ส่ง manager เข้าไปเพื่อให้อยู่ใน transaction เดียวกัน
|
|
||||||
await reOrderCommandRecivesAndDelete(commandResign!.id, manager);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
let _commandYear = item.commandYear;
|
|
||||||
if (item.commandYear) {
|
|
||||||
_commandYear = item.commandYear > 2500 ? item.commandYear : item.commandYear + 543;
|
|
||||||
}
|
|
||||||
const dest_item = await salaryRepo.findOne({
|
|
||||||
where: { profileEmployeeId: item.profileId },
|
|
||||||
order: { order: "DESC" },
|
|
||||||
});
|
|
||||||
const before = null;
|
|
||||||
const dataSalary = new ProfileSalary();
|
|
||||||
dataSalary.posNumCodeSit = _posNumCodeSit;
|
|
||||||
dataSalary.posNumCodeSitAbb = _posNumCodeSitAbb;
|
|
||||||
const meta = {
|
|
||||||
order: dest_item == null ? 1 : dest_item.order + 1,
|
|
||||||
createdUserId: ctx.user.sub,
|
|
||||||
createdFullName: ctx.user.name,
|
|
||||||
lastUpdateUserId: ctx.user.sub,
|
|
||||||
lastUpdateFullName: ctx.user.name,
|
|
||||||
createdAt: new Date(),
|
|
||||||
lastUpdatedAt: new Date(),
|
|
||||||
};
|
|
||||||
|
|
||||||
Object.assign(dataSalary, {
|
|
||||||
...item,
|
|
||||||
...meta,
|
|
||||||
profileEmployeeId: item.profileId,
|
|
||||||
profileId: undefined,
|
|
||||||
});
|
|
||||||
const history = new ProfileSalaryHistory();
|
|
||||||
Object.assign(history, { ...dataSalary, id: undefined });
|
|
||||||
dataSalary.dateGovernment = (item.commandDateAffect as Date) ?? meta.createdAt;
|
|
||||||
await salaryRepo.save(dataSalary, { data: req });
|
|
||||||
setLogDataDiff(req, { before, after: dataSalary });
|
|
||||||
history.profileSalaryId = dataSalary.id;
|
|
||||||
await salaryHistoryRepo.save(history, { data: req });
|
|
||||||
|
|
||||||
const _null: any = null;
|
|
||||||
profile.isLeave = item.isLeave;
|
|
||||||
profile.leaveReason = item.leaveReason ?? _null;
|
|
||||||
profile.dateLeave = item.dateLeave ?? _null;
|
|
||||||
profile.lastUpdateUserId = ctx.user.sub;
|
|
||||||
profile.lastUpdateFullName = ctx.user.name;
|
|
||||||
profile.lastUpdatedAt = new Date();
|
|
||||||
// บันทึกประวัติก่อนลบตำแหน่ง
|
|
||||||
const clearProfile = await checkCommandType(String(item.commandId));
|
|
||||||
const curRevision = await orgRevisionRepo.findOne({
|
|
||||||
where: { orgRevisionIsCurrent: true, orgRevisionIsDraft: false },
|
|
||||||
});
|
|
||||||
let orgRootRef = null;
|
|
||||||
let orgChild1Ref = null;
|
|
||||||
let orgChild2Ref = null;
|
|
||||||
let orgChild3Ref = null;
|
|
||||||
let orgChild4Ref = null;
|
|
||||||
if (curRevision) {
|
|
||||||
const curPosMaster = await employeePosMasterRepository.findOne({
|
|
||||||
where: {
|
|
||||||
current_holderId: profile.id,
|
|
||||||
orgRevisionId: curRevision.id,
|
|
||||||
},
|
|
||||||
relations: {
|
|
||||||
orgRoot: true,
|
|
||||||
orgChild1: true,
|
|
||||||
orgChild2: true,
|
|
||||||
orgChild3: true,
|
|
||||||
orgChild4: true,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
orgRootRef = curPosMaster?.orgRoot ?? null;
|
|
||||||
orgChild1Ref = curPosMaster?.orgChild1 ?? null;
|
|
||||||
orgChild2Ref = curPosMaster?.orgChild2 ?? null;
|
|
||||||
orgChild3Ref = curPosMaster?.orgChild3 ?? null;
|
|
||||||
orgChild4Ref = curPosMaster?.orgChild4 ?? null;
|
|
||||||
if (curPosMaster) {
|
|
||||||
// ส่ง manager เข้าไปเพื่อให้อยู่ใน transaction เดียวกัน
|
|
||||||
console.log(
|
|
||||||
`[ExecuteSalaryEmployeeLeaveService] Creating PosMasterHistory — posMasterId: ${curPosMaster.id}, profileId: ${item.profileId}, type: DELETE`,
|
|
||||||
);
|
|
||||||
await CreatePosMasterHistoryEmployee(curPosMaster.id, req, "DELETE", manager);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// ลบตำแหน่ง
|
|
||||||
if (item.isLeave == true) {
|
|
||||||
// ส่ง manager เข้าไปเพื่อให้อยู่ใน transaction เดียวกัน
|
|
||||||
await removeProfileInOrganize(profile.id, "EMPLOYEE", manager);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (clearProfile.status) {
|
|
||||||
if (profile.keycloak != null && profile.keycloak != "" && profile.isDelete === false) {
|
|
||||||
// Keycloak deleteUser ทำภายใน transaction — ถ้า DB rollback หลังจากนี้ Keycloak จะถูกลบไปแล้ว
|
|
||||||
// (Keycloak ไม่สามารถ rollback ได้)
|
|
||||||
const delUserKeycloak = await deleteUser(profile.keycloak);
|
|
||||||
if (delUserKeycloak) {
|
|
||||||
// Task #228
|
|
||||||
// profile.keycloak = _null;
|
|
||||||
profile.roleKeycloaks = [];
|
|
||||||
profile.isActive = false;
|
|
||||||
profile.isDelete = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
profile.leaveCommandId = item.commandId ?? _null;
|
|
||||||
profile.leaveCommandNo = `${item.commandNo}/${_commandYear}`;
|
|
||||||
profile.leaveRemark = clearProfile.leaveRemark ?? _null;
|
|
||||||
profile.leaveDate = item.commandDateAffect ?? _null;
|
|
||||||
profile.leaveType = clearProfile.LeaveType ?? _null;
|
|
||||||
//ออกจากราชการ ไม่ต้องลบตำแหน่งในทะเบียน (issue #1516)
|
|
||||||
// profile.position = _null;
|
|
||||||
// profile.posTypeId = _null;
|
|
||||||
// profile.posLevelId = _null;
|
|
||||||
}
|
|
||||||
await profileEmployeeRepository.save(profile);
|
|
||||||
|
|
||||||
// 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) {
|
|
||||||
const names = [
|
|
||||||
orgChild4Ref?.orgChild4Name,
|
|
||||||
orgChild3Ref?.orgChild3Name,
|
|
||||||
orgChild2Ref?.orgChild2Name,
|
|
||||||
orgChild1Ref?.orgChild1Name,
|
|
||||||
orgRootRef?.orgRootName,
|
|
||||||
].filter(Boolean);
|
|
||||||
organizeName = names.join(" ");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
console.log(
|
|
||||||
`[ExecuteSalaryEmployeeLeaveService] Completed processOne — profileId: ${item.profileId}`,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,615 +0,0 @@
|
||||||
import { Double, EntityManager } from "typeorm";
|
|
||||||
import { AppDataSource } from "../database/data-source";
|
|
||||||
import HttpError from "../interfaces/http-error";
|
|
||||||
import HttpStatusCode from "../interfaces/http-status";
|
|
||||||
import { Profile } from "../entities/Profile";
|
|
||||||
import { ProfileEmployee } from "../entities/ProfileEmployee";
|
|
||||||
import { ProfileSalary } from "../entities/ProfileSalary";
|
|
||||||
import { ProfileSalaryHistory } from "../entities/ProfileSalaryHistory";
|
|
||||||
import { ProfileDiscipline } from "../entities/ProfileDiscipline";
|
|
||||||
import { ProfileDisciplineHistory } from "../entities/ProfileDisciplineHistory";
|
|
||||||
import { OrgRoot } from "../entities/OrgRoot";
|
|
||||||
import { OrgRevision } from "../entities/OrgRevision";
|
|
||||||
import { EmployeePosMaster } from "../entities/EmployeePosMaster";
|
|
||||||
import { Command } from "../entities/Command";
|
|
||||||
import {
|
|
||||||
checkCommandType,
|
|
||||||
removePostMasterAct,
|
|
||||||
removeProfileInOrganize,
|
|
||||||
setLogDataDiff,
|
|
||||||
} from "../interfaces/utils";
|
|
||||||
import {
|
|
||||||
CreatePosMasterHistoryEmployee,
|
|
||||||
CreatePosMasterHistoryOfficer,
|
|
||||||
} from "./PositionService";
|
|
||||||
import { deleteUser } from "../keycloak";
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Input: ข้อมูล 1 คนสำหรับ endpoint excexute/salary-leave-discipline
|
|
||||||
* (C-PM-19, 20, 25, 26, 27, 28, 29, 30, 31, 32 — คำสั่งวินัย ข้าราชการ/ลูกจ้าง)
|
|
||||||
*
|
|
||||||
* profileType "OFFICER" → ข้าราชการ, ค่าอื่น/null → ลูกจ้าง
|
|
||||||
*/
|
|
||||||
export interface SalaryLeaveDisciplineItem {
|
|
||||||
profileId: string;
|
|
||||||
profileType?: string | null;
|
|
||||||
isLeave: boolean | null;
|
|
||||||
leaveReason?: string | null;
|
|
||||||
dateLeave?: Date | string | null;
|
|
||||||
detail?: string | null;
|
|
||||||
level?: string | null;
|
|
||||||
unStigma?: string | null;
|
|
||||||
commandId?: string | null;
|
|
||||||
amount?: Double | null;
|
|
||||||
amountSpecial?: Double | null;
|
|
||||||
positionSalaryAmount?: Double | null;
|
|
||||||
mouthSalaryAmount?: Double | null;
|
|
||||||
isGovernment?: boolean | null;
|
|
||||||
commandNo: string | null;
|
|
||||||
commandYear: number | null;
|
|
||||||
commandDateAffect?: Date | string | null;
|
|
||||||
commandDateSign?: Date | string | null;
|
|
||||||
commandCode?: string | null;
|
|
||||||
commandName?: string | null;
|
|
||||||
remark: string | null;
|
|
||||||
orgRoot?: string | null;
|
|
||||||
orgChild1?: string | null;
|
|
||||||
orgChild2?: string | null;
|
|
||||||
orgChild3?: string | null;
|
|
||||||
orgChild4?: string | null;
|
|
||||||
posNo?: string | null;
|
|
||||||
posNoAbb?: string | null;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Context สำหรับ audit/log
|
|
||||||
*/
|
|
||||||
export interface SalaryLeaveDisciplineExecutionContext {
|
|
||||||
user: { sub: string; name: string };
|
|
||||||
req?: any;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Service สำหรับสร้าง ProfileSalary + ProfileDiscipline + handle leave ของคำสั่งวินัย
|
|
||||||
*
|
|
||||||
* ใช้กับ commandType: C-PM-19, 20, 25, 26, 27, 28, 29, 30, 31, 32
|
|
||||||
*
|
|
||||||
* - endpoint /org/command/excexute/salary-leave-discipline เรียกผ่าน service นี้ (thin wrapper)
|
|
||||||
* - consumer ใน rabbitmq handler เรียกผ่าน service นี้โดยตรง (Linear Flow)
|
|
||||||
*
|
|
||||||
* Behavior ทั้งหมด preserve จาก CommandController.newSalaryAndUpdateLeaveDiscipline ต้นฉบับ
|
|
||||||
* รวมถึงกรณี OFFICER ที่การ save ProfileSalary + ProfileDiscipline ถูก comment out ไว้
|
|
||||||
* (เก็บไว้เพื่อ preserve behavior เดิม — มีเพียง EMPLOYEE เท่านั้นที่ save จริง)
|
|
||||||
*
|
|
||||||
* Batch semantics: all-or-nothing — ประมวลผลทุกคนภายใต้ transaction เดียว (sequential)
|
|
||||||
* ถ้าคนใด throw จะ rollback ทั้ง batch และ propagate error ออกไป (ล้มเหลวทั้งหมด)
|
|
||||||
* ถ้าทุกคนสำเร็จจะ return result รายงาน success count
|
|
||||||
*
|
|
||||||
* ⚠️ หมายเหตุ Keycloak: operation (deleteUser) ทำภายใน transaction เพื่อ preserve behavior
|
|
||||||
* เดิม — Keycloak ไม่สามารถ rollback ได้ ถ้า DB rollback หลังจาก Keycloak operation สำเร็จ
|
|
||||||
* → Keycloak จะถูกเปลี่ยนไปแล้ว
|
|
||||||
*/
|
|
||||||
export class ExecuteSalaryLeaveDisciplineService {
|
|
||||||
private commandRepository = AppDataSource.getRepository(Command);
|
|
||||||
private profileRepository = AppDataSource.getRepository(Profile);
|
|
||||||
private orgRootRepository = AppDataSource.getRepository(OrgRoot);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* ประมวลผลคำสั่งวินัยทั้ง batch
|
|
||||||
*
|
|
||||||
* @returns สรุปผล success/failure ต่อคน
|
|
||||||
*/
|
|
||||||
async executeSalaryLeaveDiscipline(
|
|
||||||
data: SalaryLeaveDisciplineItem[],
|
|
||||||
ctx: SalaryLeaveDisciplineExecutionContext,
|
|
||||||
): Promise<void> {
|
|
||||||
const commandId = data?.find((x) => x.commandId)?.commandId ?? "unknown";
|
|
||||||
const commandCode = data?.find((x) => x.commandCode)?.commandCode ?? "unknown";
|
|
||||||
console.log(
|
|
||||||
`[ExecuteSalaryLeaveDisciplineService] Starting executeSalaryLeaveDiscipline — commandCode: ${commandCode}, commandId: ${commandId}`,
|
|
||||||
);
|
|
||||||
console.log(`[ExecuteSalaryLeaveDisciplineService] Request body count: ${data?.length ?? 0}`);
|
|
||||||
|
|
||||||
// ─────────────────────────────────────────────────────────────
|
|
||||||
// Normalize date fields (ผ่าน handler จะได้ string → ต้องแปลงเป็น Date)
|
|
||||||
// ─────────────────────────────────────────────────────────────
|
|
||||||
const toDate = (v: any): Date | null => {
|
|
||||||
if (v == null || v === "") return null;
|
|
||||||
if (v instanceof Date) return isNaN(v.getTime()) ? null : v;
|
|
||||||
const d = new Date(v);
|
|
||||||
return isNaN(d.getTime()) ? null : d;
|
|
||||||
};
|
|
||||||
for (const item of data ?? []) {
|
|
||||||
const it = item as any;
|
|
||||||
it.dateLeave = toDate(it.dateLeave);
|
|
||||||
it.commandDateAffect = toDate(it.commandDateAffect);
|
|
||||||
it.commandDateSign = toDate(it.commandDateSign);
|
|
||||||
}
|
|
||||||
|
|
||||||
let _posNumCodeSit: string = "";
|
|
||||||
let _posNumCodeSitAbb: string = "";
|
|
||||||
const _command = await this.commandRepository.findOne({
|
|
||||||
relations: ["commandType"],
|
|
||||||
where: { id: data.find((x) => x.commandId)?.commandId ?? "" },
|
|
||||||
});
|
|
||||||
if (_command) {
|
|
||||||
if (_command?.isBangkok?.toLocaleUpperCase() == "OFFICE") {
|
|
||||||
const orgRootDeputy = await this.orgRootRepository.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 this.profileRepository.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 ?? "";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// ─────────────────────────────────────────────────────────────
|
|
||||||
// Single transaction ครอบทั้ง batch (all-or-nothing)
|
|
||||||
// ทุกคนใช้ manager ตัวเดียวกัน — คนใด throw จะ rollback ทั้ง batch
|
|
||||||
// และ propagate error ออกไป (ล้มเหลวทั้งหมด) โดย log error ของคนที่ทำให้ fail ก่อน rethrow
|
|
||||||
// ─────────────────────────────────────────────────────────────
|
|
||||||
await AppDataSource.transaction(async (manager) => {
|
|
||||||
for (const item of data ?? []) {
|
|
||||||
try {
|
|
||||||
await this.processOne(item, ctx, manager, _command, _posNumCodeSit, _posNumCodeSitAbb);
|
|
||||||
} catch (err) {
|
|
||||||
const reason =
|
|
||||||
err instanceof HttpError
|
|
||||||
? err.message
|
|
||||||
: err instanceof Error
|
|
||||||
? err.message
|
|
||||||
: "unexpected error";
|
|
||||||
console.error(
|
|
||||||
`[ExecuteSalaryLeaveDisciplineService] Failed commandCode=${commandCode}, commandId=${commandId}, profileId=${item.profileId}: ${reason}`,
|
|
||||||
err,
|
|
||||||
);
|
|
||||||
throw err; // → rollback ทั้ง transaction + propagate เป็น batch failure
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* ประมวลผล 1 คน ภายใน transaction เดียว (manager)
|
|
||||||
* ทุก save ใช้ manager.getRepository(...) เพื่อให้อยู่ใน transaction เดียวกัน
|
|
||||||
* ถ้า throw ระหว่างทาง → rollback ทั้งหมดของคนนี้ + ทั้ง batch (กัน partial commit)
|
|
||||||
*/
|
|
||||||
private async processOne(
|
|
||||||
item: SalaryLeaveDisciplineItem,
|
|
||||||
ctx: SalaryLeaveDisciplineExecutionContext,
|
|
||||||
manager: EntityManager,
|
|
||||||
_command: Command | null,
|
|
||||||
_posNumCodeSit: string,
|
|
||||||
_posNumCodeSitAbb: string,
|
|
||||||
): Promise<void> {
|
|
||||||
const req = ctx.req;
|
|
||||||
|
|
||||||
const profileRepository = manager.getRepository(Profile);
|
|
||||||
const profileEmployeeRepository = manager.getRepository(ProfileEmployee);
|
|
||||||
const salaryRepo = manager.getRepository(ProfileSalary);
|
|
||||||
const salaryHistoryRepo = manager.getRepository(ProfileSalaryHistory);
|
|
||||||
const disciplineRepository = manager.getRepository(ProfileDiscipline);
|
|
||||||
const disciplineHistoryRepository = manager.getRepository(ProfileDisciplineHistory);
|
|
||||||
const orgRevisionRepo = manager.getRepository(OrgRevision);
|
|
||||||
const employeePosMasterRepository = manager.getRepository(EmployeePosMaster);
|
|
||||||
|
|
||||||
let _commandYear = item.commandYear;
|
|
||||||
if (item.commandYear) {
|
|
||||||
_commandYear = item.commandYear > 2500 ? item.commandYear : item.commandYear + 543;
|
|
||||||
}
|
|
||||||
|
|
||||||
const orgRevision = await orgRevisionRepo.findOne({
|
|
||||||
where: {
|
|
||||||
orgRevisionIsCurrent: true,
|
|
||||||
orgRevisionIsDraft: false,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
let orgRootRef: any = null;
|
|
||||||
let orgChild1Ref: any = null;
|
|
||||||
let orgChild2Ref: any = null;
|
|
||||||
let orgChild3Ref: any = null;
|
|
||||||
let orgChild4Ref: any = null;
|
|
||||||
|
|
||||||
const code = _command?.commandType?.code;
|
|
||||||
|
|
||||||
// ═══════════════════════════════════════════════════════════
|
|
||||||
// OFFICER (ข้าราชการ)
|
|
||||||
// ═══════════════════════════════════════════════════════════
|
|
||||||
if (item.profileType && item.profileType.trim().toUpperCase() == "OFFICER") {
|
|
||||||
const profile: any = await profileRepository.findOne({
|
|
||||||
relations: [
|
|
||||||
"posLevel",
|
|
||||||
"posType",
|
|
||||||
"current_holders",
|
|
||||||
"current_holders.orgRoot",
|
|
||||||
"current_holders.orgChild1",
|
|
||||||
"current_holders.orgChild2",
|
|
||||||
"current_holders.orgChild3",
|
|
||||||
"current_holders.orgChild4",
|
|
||||||
"current_holders.positions",
|
|
||||||
"current_holders.positions.posExecutive",
|
|
||||||
"roleKeycloaks",
|
|
||||||
],
|
|
||||||
where: { id: item.profileId },
|
|
||||||
});
|
|
||||||
if (!profile) {
|
|
||||||
throw new HttpError(HttpStatusCode.NOT_FOUND, "ไม่พบข้อมูลทะเบียนประวัตินี้");
|
|
||||||
}
|
|
||||||
const lastSalary = await salaryRepo.findOne({
|
|
||||||
where: { profileId: item.profileId },
|
|
||||||
select: ["order"],
|
|
||||||
order: { order: "DESC" },
|
|
||||||
});
|
|
||||||
const nextOrder = lastSalary ? lastSalary.order + 1 : 1;
|
|
||||||
|
|
||||||
//ลบตำแหน่งที่รักษาการแทน (await + ส่ง manager เข้าไปเพื่อให้อยู่ใน transaction เดียวกัน)
|
|
||||||
if (code && ["C-PM-19", "C-PM-20"].includes(code)) {
|
|
||||||
await removePostMasterAct(profile.id, manager);
|
|
||||||
}
|
|
||||||
|
|
||||||
const orgRevisionRef =
|
|
||||||
profile?.current_holders?.find((x: any) => 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;
|
|
||||||
|
|
||||||
const position =
|
|
||||||
profile.current_holders
|
|
||||||
.filter((x: any) => x.orgRevisionId == orgRevision?.id)[0]
|
|
||||||
?.positions?.filter((pos: any) => pos.positionIsSelected === true)[0] ?? null;
|
|
||||||
|
|
||||||
// ประวัติตำแหน่ง
|
|
||||||
const data = new ProfileSalary();
|
|
||||||
data.posNumCodeSit = _posNumCodeSit;
|
|
||||||
data.posNumCodeSitAbb = _posNumCodeSitAbb;
|
|
||||||
const meta = {
|
|
||||||
profileId: profile.id,
|
|
||||||
commandId: item.commandId,
|
|
||||||
position: profile.position,
|
|
||||||
positionName: profile.position,
|
|
||||||
positionType: profile?.posType?.posTypeName ?? null,
|
|
||||||
positionLevel: profile?.posLevel?.posLevelName ?? null,
|
|
||||||
positionExecutive: position?.posExecutive?.posExecutiveName ?? null,
|
|
||||||
amount: item.amount ? item.amount : null,
|
|
||||||
positionSalaryAmount: item.positionSalaryAmount ? item.positionSalaryAmount : null,
|
|
||||||
amountSpecial: item.amountSpecial ? item.amountSpecial : null,
|
|
||||||
mouthSalaryAmount: item.mouthSalaryAmount ? item.mouthSalaryAmount : null,
|
|
||||||
order: nextOrder,
|
|
||||||
orgRoot: item.orgRoot,
|
|
||||||
orgChild1: item.orgChild1,
|
|
||||||
orgChild2: item.orgChild2,
|
|
||||||
orgChild3: item.orgChild3,
|
|
||||||
orgChild4: item.orgChild4,
|
|
||||||
createdUserId: ctx.user.sub,
|
|
||||||
createdFullName: ctx.user.name,
|
|
||||||
lastUpdateUserId: ctx.user.sub,
|
|
||||||
lastUpdateFullName: ctx.user.name,
|
|
||||||
createdAt: new Date(),
|
|
||||||
lastUpdatedAt: new Date(),
|
|
||||||
dateGovernment: item.commandDateAffect ?? new Date(),
|
|
||||||
isGovernment: item.isGovernment,
|
|
||||||
commandNo: item.commandNo,
|
|
||||||
commandYear: item.commandYear,
|
|
||||||
posNo: item.posNo,
|
|
||||||
posNoAbb: item.posNoAbb,
|
|
||||||
commandDateAffect: item.commandDateAffect,
|
|
||||||
commandDateSign: item.commandDateSign,
|
|
||||||
commandCode: item.commandCode,
|
|
||||||
commandName: item.commandName,
|
|
||||||
remark: item.remark,
|
|
||||||
};
|
|
||||||
Object.assign(data, meta);
|
|
||||||
const history = new ProfileSalaryHistory();
|
|
||||||
Object.assign(history, { ...data, id: undefined });
|
|
||||||
// ── preserve: OFFICER branch ไม่ save ProfileSalary (comment ตามต้นฉบับ) ──
|
|
||||||
// await salaryRepo.save(data, { data: req });
|
|
||||||
// history.profileSalaryId = data.id;
|
|
||||||
// await salaryHistoryRepo.save(history, { data: req });
|
|
||||||
|
|
||||||
// ประวัติวินัย
|
|
||||||
const dataDis = new ProfileDiscipline();
|
|
||||||
const metaDis = {
|
|
||||||
date: item.commandDateAffect,
|
|
||||||
refCommandDate: item.commandDateSign,
|
|
||||||
refCommandNo: `${item.commandNo}/${item.commandYear}`,
|
|
||||||
refCommandId: item.commandId,
|
|
||||||
createdUserId: ctx.user.sub,
|
|
||||||
createdFullName: ctx.user.name,
|
|
||||||
lastUpdateUserId: ctx.user.sub,
|
|
||||||
lastUpdateFullName: ctx.user.name,
|
|
||||||
createdAt: new Date(),
|
|
||||||
lastUpdatedAt: new Date(),
|
|
||||||
};
|
|
||||||
Object.assign(dataDis, { ...item, ...metaDis });
|
|
||||||
const historyDis = new ProfileDisciplineHistory();
|
|
||||||
Object.assign(historyDis, { ...dataDis, id: undefined });
|
|
||||||
// ── preserve: OFFICER branch ไม่ save ProfileDiscipline (comment ตามต้นฉบับ) ──
|
|
||||||
// await disciplineRepository.save(dataDis, { data: req });
|
|
||||||
// historyDis.profileDisciplineId = dataDis.id;
|
|
||||||
// await disciplineHistoryRepository.save(historyDis, { data: req });
|
|
||||||
|
|
||||||
// ทะเบียนประวัติ
|
|
||||||
if (item.isLeave != null) {
|
|
||||||
const _profile: any = await profileRepository.findOne({
|
|
||||||
where: { id: item.profileId },
|
|
||||||
relations: ["roleKeycloaks"],
|
|
||||||
});
|
|
||||||
if (!_profile) {
|
|
||||||
throw new HttpError(HttpStatusCode.NOT_FOUND, "ไม่พบข้อมูลทะเบียนประวัตินี้");
|
|
||||||
}
|
|
||||||
const _null: any = null;
|
|
||||||
_profile.isLeave = item.isLeave;
|
|
||||||
_profile.leaveReason = item.leaveReason ?? _null;
|
|
||||||
_profile.dateLeave = item.dateLeave ?? _null;
|
|
||||||
_profile.lastUpdateUserId = ctx.user.sub;
|
|
||||||
_profile.lastUpdateFullName = ctx.user.name;
|
|
||||||
_profile.lastUpdatedAt = new Date();
|
|
||||||
if (item.isLeave == true) {
|
|
||||||
if (orgRevisionRef) {
|
|
||||||
// ส่ง manager เข้าไปเพื่อให้อยู่ใน transaction เดียวกัน
|
|
||||||
await CreatePosMasterHistoryOfficer(orgRevisionRef.id, req, "DELETE", null, manager);
|
|
||||||
}
|
|
||||||
// ส่ง manager เข้าไปเพื่อให้อยู่ใน transaction เดียวกัน
|
|
||||||
await removeProfileInOrganize(_profile.id, "OFFICER", manager);
|
|
||||||
}
|
|
||||||
const clearProfile = await checkCommandType(String(item.commandId));
|
|
||||||
if (clearProfile.status) {
|
|
||||||
if (
|
|
||||||
_profile.keycloak != null &&
|
|
||||||
_profile.keycloak != "" &&
|
|
||||||
_profile.isDelete === false
|
|
||||||
) {
|
|
||||||
// Keycloak ทำภายใน transaction — ไม่สามารถ rollback ได้ (ดู docstring ของ class)
|
|
||||||
const delUserKeycloak = await deleteUser(_profile.keycloak);
|
|
||||||
if (delUserKeycloak) {
|
|
||||||
// Task #228
|
|
||||||
// _profile.keycloak = _null;
|
|
||||||
_profile.roleKeycloaks = [];
|
|
||||||
_profile.isActive = false;
|
|
||||||
_profile.isDelete = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
_profile.leaveCommandId = item.commandId ?? _null;
|
|
||||||
_profile.leaveCommandNo = `${item.commandNo}/${_commandYear}`;
|
|
||||||
_profile.leaveRemark = clearProfile.leaveRemark ?? _null;
|
|
||||||
_profile.leaveDate = item.commandDateAffect ?? _null;
|
|
||||||
_profile.leaveType = clearProfile.LeaveType ?? _null;
|
|
||||||
//ออกจากราชการ ไม่ต้องลบตำแหน่งในทะเบียน (issue #1516)
|
|
||||||
// _profile.position = _null;
|
|
||||||
// _profile.posTypeId = _null;
|
|
||||||
// _profile.posLevelId = _null;
|
|
||||||
}
|
|
||||||
await profileRepository.save(_profile, { data: req });
|
|
||||||
setLogDataDiff(req, { before: null, after: _profile });
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// ═══════════════════════════════════════════════════════════
|
|
||||||
// EMPLOYEE (ลูกจ้าง)
|
|
||||||
// ═══════════════════════════════════════════════════════════
|
|
||||||
else {
|
|
||||||
const profile: any = await profileEmployeeRepository.findOne({
|
|
||||||
relations: [
|
|
||||||
"posLevel",
|
|
||||||
"posType",
|
|
||||||
"current_holders",
|
|
||||||
"current_holders.orgRoot",
|
|
||||||
"current_holders.orgChild1",
|
|
||||||
"current_holders.orgChild2",
|
|
||||||
"current_holders.orgChild3",
|
|
||||||
"current_holders.orgChild4",
|
|
||||||
"roleKeycloaks",
|
|
||||||
],
|
|
||||||
where: { id: item.profileId },
|
|
||||||
});
|
|
||||||
if (!profile) {
|
|
||||||
throw new HttpError(HttpStatusCode.NOT_FOUND, "ไม่พบข้อมูลทะเบียนประวัตินี้");
|
|
||||||
}
|
|
||||||
const lastSalary = await salaryRepo.findOne({
|
|
||||||
where: { profileEmployeeId: item.profileId },
|
|
||||||
select: ["order"],
|
|
||||||
order: { order: "DESC" },
|
|
||||||
});
|
|
||||||
const nextOrder = lastSalary ? lastSalary.order + 1 : 1;
|
|
||||||
const orgRevisionRef =
|
|
||||||
profile?.current_holders?.find((x: any) => 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;
|
|
||||||
|
|
||||||
// ประวัติตำแหน่ง
|
|
||||||
const data = new ProfileSalary();
|
|
||||||
data.posNumCodeSit = _posNumCodeSit;
|
|
||||||
data.posNumCodeSitAbb = _posNumCodeSitAbb;
|
|
||||||
const meta = {
|
|
||||||
profileEmployeeId: profile.id,
|
|
||||||
commandId: item.commandId,
|
|
||||||
position: profile.position,
|
|
||||||
positionName: profile.position,
|
|
||||||
positionType: profile?.posType?.posTypeName ?? null,
|
|
||||||
positionLevel:
|
|
||||||
profile?.posType && profile?.posLevel
|
|
||||||
? `${profile?.posType?.posTypeShortName} ${profile?.posLevel?.posLevelName}`
|
|
||||||
: null,
|
|
||||||
amount: item.amount ? item.amount : null,
|
|
||||||
positionSalaryAmount: item.positionSalaryAmount ? item.positionSalaryAmount : null,
|
|
||||||
mouthSalaryAmount: item.mouthSalaryAmount ? item.mouthSalaryAmount : null,
|
|
||||||
order: nextOrder,
|
|
||||||
orgRoot: item.orgRoot,
|
|
||||||
orgChild1: item.orgChild1,
|
|
||||||
orgChild2: item.orgChild2,
|
|
||||||
orgChild3: item.orgChild3,
|
|
||||||
orgChild4: item.orgChild4,
|
|
||||||
createdUserId: ctx.user.sub,
|
|
||||||
createdFullName: ctx.user.name,
|
|
||||||
lastUpdateUserId: ctx.user.sub,
|
|
||||||
lastUpdateFullName: ctx.user.name,
|
|
||||||
createdAt: new Date(),
|
|
||||||
lastUpdatedAt: new Date(),
|
|
||||||
dateGovernment: item.commandDateAffect ?? new Date(),
|
|
||||||
isGovernment: item.isGovernment,
|
|
||||||
commandNo: item.commandNo,
|
|
||||||
commandYear: item.commandYear,
|
|
||||||
posNo: item.posNo,
|
|
||||||
posNoAbb: item.posNoAbb,
|
|
||||||
commandDateAffect: item.commandDateAffect,
|
|
||||||
commandDateSign: item.commandDateSign,
|
|
||||||
commandCode: item.commandCode,
|
|
||||||
commandName: item.commandName,
|
|
||||||
remark: item.remark,
|
|
||||||
};
|
|
||||||
Object.assign(data, meta);
|
|
||||||
const history = new ProfileSalaryHistory();
|
|
||||||
Object.assign(history, { ...data, id: undefined });
|
|
||||||
await salaryRepo.save(data, { data: req });
|
|
||||||
setLogDataDiff(req, { before: null, after: data });
|
|
||||||
history.profileSalaryId = data.id;
|
|
||||||
await salaryHistoryRepo.save(history, { data: req });
|
|
||||||
|
|
||||||
// ประวัติวินัย
|
|
||||||
const dataDis = new ProfileDiscipline();
|
|
||||||
const metaDis = {
|
|
||||||
createdUserId: ctx.user.sub,
|
|
||||||
createdFullName: ctx.user.name,
|
|
||||||
lastUpdateUserId: ctx.user.sub,
|
|
||||||
lastUpdateFullName: ctx.user.name,
|
|
||||||
createdAt: new Date(),
|
|
||||||
lastUpdatedAt: new Date(),
|
|
||||||
};
|
|
||||||
Object.assign(dataDis, {
|
|
||||||
...item,
|
|
||||||
...metaDis,
|
|
||||||
date: item.commandDateAffect,
|
|
||||||
refCommandDate: item.commandDateSign,
|
|
||||||
refCommandNo: item.commandNo,
|
|
||||||
profileEmployeeId: item.profileId,
|
|
||||||
profileId: undefined,
|
|
||||||
});
|
|
||||||
const historyDis = new ProfileDisciplineHistory();
|
|
||||||
Object.assign(historyDis, { ...dataDis, id: undefined });
|
|
||||||
await disciplineRepository.save(dataDis, { data: req });
|
|
||||||
setLogDataDiff(req, { before: null, after: dataDis });
|
|
||||||
historyDis.profileDisciplineId = dataDis.id;
|
|
||||||
await disciplineHistoryRepository.save(historyDis, { data: req });
|
|
||||||
|
|
||||||
// ทะเบียนประวัติ
|
|
||||||
if (item.isLeave != null) {
|
|
||||||
const _profile: any = await profileEmployeeRepository.findOne({
|
|
||||||
where: { id: item.profileId },
|
|
||||||
relations: ["roleKeycloaks"],
|
|
||||||
});
|
|
||||||
if (!_profile) {
|
|
||||||
throw new HttpError(HttpStatusCode.NOT_FOUND, "ไม่พบข้อมูลทะเบียนประวัตินี้");
|
|
||||||
}
|
|
||||||
const _null: any = null;
|
|
||||||
_profile.isLeave = item.isLeave;
|
|
||||||
_profile.leaveReason = item.leaveReason ?? _null;
|
|
||||||
_profile.dateLeave = item.dateLeave ?? _null;
|
|
||||||
_profile.lastUpdateUserId = ctx.user.sub;
|
|
||||||
_profile.lastUpdateFullName = ctx.user.name;
|
|
||||||
_profile.lastUpdatedAt = new Date();
|
|
||||||
if (item.isLeave == true) {
|
|
||||||
// บันทึกประวัติก่อนลบตำแหน่ง
|
|
||||||
const curRevision = await orgRevisionRepo.findOne({
|
|
||||||
where: { orgRevisionIsCurrent: true, orgRevisionIsDraft: false },
|
|
||||||
});
|
|
||||||
if (curRevision) {
|
|
||||||
const curPosMaster = await employeePosMasterRepository.findOne({
|
|
||||||
where: {
|
|
||||||
current_holderId: _profile.id,
|
|
||||||
orgRevisionId: curRevision.id,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
if (curPosMaster) {
|
|
||||||
// ส่ง manager เข้าไปเพื่อให้อยู่ใน transaction เดียวกัน
|
|
||||||
await CreatePosMasterHistoryEmployee(curPosMaster.id, req, "DELETE", manager);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// ส่ง manager เข้าไปเพื่อให้อยู่ใน transaction เดียวกัน
|
|
||||||
await removeProfileInOrganize(_profile.id, "EMPLOYEE", manager);
|
|
||||||
}
|
|
||||||
const clearProfile = await checkCommandType(String(item.commandId));
|
|
||||||
if (clearProfile.status) {
|
|
||||||
if (
|
|
||||||
_profile.keycloak != null &&
|
|
||||||
_profile.keycloak != "" &&
|
|
||||||
_profile.isDelete === false
|
|
||||||
) {
|
|
||||||
// Keycloak deleteUser ทำภายใน transaction — ถ้า DB rollback หลังจากนี้ Keycloak จะถูกลบไปแล้ว
|
|
||||||
// (Keycloak ไม่สามารถ rollback ได้)
|
|
||||||
const delUserKeycloak = await deleteUser(_profile.keycloak);
|
|
||||||
if (delUserKeycloak) {
|
|
||||||
// Task #228
|
|
||||||
// _profile.keycloak = _null;
|
|
||||||
_profile.roleKeycloaks = [];
|
|
||||||
_profile.isActive = false;
|
|
||||||
_profile.isDelete = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
_profile.leaveCommandId = item.commandId ?? _null;
|
|
||||||
_profile.leaveCommandNo = `${item.commandNo}/${_commandYear}`;
|
|
||||||
_profile.leaveRemark = clearProfile.leaveRemark ?? _null;
|
|
||||||
_profile.leaveDate = item.commandDateAffect ?? _null;
|
|
||||||
_profile.leaveType = clearProfile.LeaveType ?? _null;
|
|
||||||
//ออกจากราชการ ไม่ต้องลบตำแหน่งในทะเบียน (issue #1516)
|
|
||||||
// _profile.position = _null;
|
|
||||||
// _profile.posTypeId = _null;
|
|
||||||
// _profile.posLevelId = _null;
|
|
||||||
}
|
|
||||||
await profileEmployeeRepository.save(_profile, { data: req });
|
|
||||||
setLogDataDiff(req, { before: null, after: _profile });
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Task #2190 (preserve: organizeName computed แต่ยังไม่ได้ใช้ในต้นฉบับ — เก็บไว้ตาม behavior เดิม)
|
|
||||||
if (_command && ["C-PM-19", "C-PM-20"].includes(_command.commandType.code)) {
|
|
||||||
let organizeName = "";
|
|
||||||
if (orgRootRef) {
|
|
||||||
const names = [
|
|
||||||
orgChild4Ref?.orgChild4Name,
|
|
||||||
orgChild3Ref?.orgChild3Name,
|
|
||||||
orgChild2Ref?.orgChild2Name,
|
|
||||||
orgChild1Ref?.orgChild1Name,
|
|
||||||
orgRootRef?.orgRootName,
|
|
||||||
].filter(Boolean);
|
|
||||||
organizeName = names.join(" ");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
console.log(
|
|
||||||
`[ExecuteSalaryLeaveDisciplineService] Completed processOne — profileId: ${item.profileId}`,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,708 +0,0 @@
|
||||||
import { Double, EntityManager, In, Like } from "typeorm";
|
|
||||||
import { AppDataSource } from "../database/data-source";
|
|
||||||
import HttpError from "../interfaces/http-error";
|
|
||||||
import HttpStatusCode from "../interfaces/http-status";
|
|
||||||
import { Profile } from "../entities/Profile";
|
|
||||||
import { ProfileSalary } from "../entities/ProfileSalary";
|
|
||||||
import { ProfileSalaryHistory } from "../entities/ProfileSalaryHistory";
|
|
||||||
import { OrgRoot } from "../entities/OrgRoot";
|
|
||||||
import { OrgRevision } from "../entities/OrgRevision";
|
|
||||||
import { PosMaster } from "../entities/PosMaster";
|
|
||||||
import { Position } from "../entities/Position";
|
|
||||||
import { RoleKeycloak } from "../entities/RoleKeycloak";
|
|
||||||
import { CommandRecive } from "../entities/CommandRecive";
|
|
||||||
import { Command } from "../entities/Command";
|
|
||||||
import {
|
|
||||||
checkCommandType,
|
|
||||||
checkReturnCommandType,
|
|
||||||
removePostMasterAct,
|
|
||||||
removeProfileInOrganize,
|
|
||||||
setLogDataDiff,
|
|
||||||
} from "../interfaces/utils";
|
|
||||||
import { reOrderCommandRecivesAndDelete } from "./CommandService";
|
|
||||||
import { CreatePosMasterHistoryOfficer } from "./PositionService";
|
|
||||||
import { getOrgFullName, getPosMasterNo } from "../utils/org-formatting";
|
|
||||||
import {
|
|
||||||
addUserRoles,
|
|
||||||
createUser,
|
|
||||||
deleteUser,
|
|
||||||
getRoleMappings,
|
|
||||||
getRoles,
|
|
||||||
getUserByUsername,
|
|
||||||
updateUserAttributes,
|
|
||||||
} from "../keycloak";
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Input: ข้อมูล 1 คนสำหรับ endpoint excexute/salary-leave
|
|
||||||
* (C-PM-08, 09, 17, 18, 41, 48 — ลาออก/พักราชการ/กลับเข้าราชการ ของข้าราชการ)
|
|
||||||
*/
|
|
||||||
export interface SalaryLeaveItem {
|
|
||||||
profileId: string;
|
|
||||||
amount?: Double | null;
|
|
||||||
amountSpecial?: Double | null;
|
|
||||||
positionSalaryAmount?: Double | null;
|
|
||||||
mouthSalaryAmount?: Double | null;
|
|
||||||
positionExecutive: string | null;
|
|
||||||
positionExecutiveField?: string | null;
|
|
||||||
positionArea?: string | null;
|
|
||||||
positionType: string | null;
|
|
||||||
positionLevel: string | null;
|
|
||||||
isLeave: boolean;
|
|
||||||
leaveReason?: string | null;
|
|
||||||
dateLeave?: Date | string | null;
|
|
||||||
posExecutiveId?: string | null;
|
|
||||||
positionField?: string | null;
|
|
||||||
commandId?: string | null;
|
|
||||||
isGovernment?: boolean | null;
|
|
||||||
orgRoot?: string | null;
|
|
||||||
orgChild1?: string | null;
|
|
||||||
orgChild2?: string | null;
|
|
||||||
orgChild3?: string | null;
|
|
||||||
orgChild4?: string | null;
|
|
||||||
commandNo: string | null;
|
|
||||||
commandYear: number | null;
|
|
||||||
posNo: string | null;
|
|
||||||
posNoAbb: string | null;
|
|
||||||
commandDateAffect?: Date | string | null;
|
|
||||||
commandDateSign?: Date | string | null;
|
|
||||||
positionName: string | null;
|
|
||||||
commandCode?: string | null;
|
|
||||||
commandName?: string | null;
|
|
||||||
remark: string | null;
|
|
||||||
positionId?: string | null;
|
|
||||||
positionTypeNew?: string | null;
|
|
||||||
positionLevelNew?: string | null;
|
|
||||||
positionNameNew?: string | null;
|
|
||||||
posmasterId?: string | null;
|
|
||||||
posTypeNameNew?: string | null;
|
|
||||||
posLevelNameNew?: string | null;
|
|
||||||
posNoNew?: string | null;
|
|
||||||
posNoAbbNew?: string | null;
|
|
||||||
orgRootNew?: string | null;
|
|
||||||
orgChild1New?: string | null;
|
|
||||||
orgChild2New?: string | null;
|
|
||||||
orgChild3New?: string | null;
|
|
||||||
orgChild4New?: string | null;
|
|
||||||
resignId?: string | null;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Context สำหรับ audit/log
|
|
||||||
*/
|
|
||||||
export interface SalaryLeaveExecutionContext {
|
|
||||||
user: { sub: string; name: string };
|
|
||||||
req?: any;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Service สำหรับสร้าง ProfileSalary ข้าราชการ + handle leave/กลับเข้าราชการ
|
|
||||||
*
|
|
||||||
* ใช้กับ commandType: C-PM-08, 09, 17, 18, 41, 48
|
|
||||||
*
|
|
||||||
* - endpoint /org/command/excexute/salary-leave เรียกผ่าน service นี้ (thin wrapper)
|
|
||||||
* - consumer ใน rabbitmq handler เรียกผ่าน service นี้โดยตรง (Linear Flow)
|
|
||||||
*
|
|
||||||
* Behavior ทั้งหมด preserve จาก CommandController.newSalaryAndUpdateLeave ต้นฉบับ
|
|
||||||
*
|
|
||||||
* Batch semantics: all-or-nothing — ประมวลผลทุกคนภายใต้ transaction เดียว (sequential)
|
|
||||||
* ถ้าคนใด throw จะ rollback ทั้ง batch และ propagate error ออกไป (ล้มเหลวทั้งหมด)
|
|
||||||
* ถ้าทุกคนสำเร็จจะ return result รายงาน success count
|
|
||||||
*
|
|
||||||
* ⚠️ หมายเหตุ Keycloak: operations (deleteUser/createUser/addUserRoles/updateUserAttributes)
|
|
||||||
* ทำภายใน transaction เพื่อ preserve behavior เดิม — Keycloak ไม่สามารถ rollback ได้
|
|
||||||
* ถ้า DB rollback หลังจาก Keycloak operation สำเร็จ → Keycloak จะถูกเปลี่ยนไปแล้ว
|
|
||||||
*/
|
|
||||||
export class ExecuteSalaryLeaveService {
|
|
||||||
private commandRepository = AppDataSource.getRepository(Command);
|
|
||||||
private commandReciveRepository = AppDataSource.getRepository(CommandRecive);
|
|
||||||
private profileRepository = AppDataSource.getRepository(Profile);
|
|
||||||
private orgRootRepository = AppDataSource.getRepository(OrgRoot);
|
|
||||||
private roleKeycloakRepo = AppDataSource.getRepository(RoleKeycloak);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* ประมวลผลสร้าง ProfileSalary + handle leave/กลับเข้าราชการ ของข้าราชการทั้ง batch
|
|
||||||
*
|
|
||||||
* @returns สรุปผล success/failure ต่อคน
|
|
||||||
*/
|
|
||||||
async executeSalaryLeave(data: SalaryLeaveItem[], ctx: SalaryLeaveExecutionContext): Promise<void> {
|
|
||||||
const commandId = data?.find((x) => x.commandId)?.commandId ?? "unknown";
|
|
||||||
const commandCode = data?.find((x) => x.commandCode)?.commandCode ?? "unknown";
|
|
||||||
console.log(
|
|
||||||
`[ExecuteSalaryLeaveService] Starting executeSalaryLeave — commandCode: ${commandCode}, commandId: ${commandId}`,
|
|
||||||
);
|
|
||||||
console.log(`[ExecuteSalaryLeaveService] Request body count: ${data?.length ?? 0}`);
|
|
||||||
|
|
||||||
// ─────────────────────────────────────────────────────────────
|
|
||||||
// Normalize date fields (ผ่าน handler จะได้ string → ต้องแปลงเป็น Date)
|
|
||||||
// ─────────────────────────────────────────────────────────────
|
|
||||||
const toDate = (v: any): Date | null => {
|
|
||||||
if (v == null || v === "") return null;
|
|
||||||
if (v instanceof Date) return isNaN(v.getTime()) ? null : v;
|
|
||||||
const d = new Date(v);
|
|
||||||
return isNaN(d.getTime()) ? null : d;
|
|
||||||
};
|
|
||||||
for (const item of data ?? []) {
|
|
||||||
const it = item as any;
|
|
||||||
it.dateLeave = toDate(it.dateLeave);
|
|
||||||
it.commandDateAffect = toDate(it.commandDateAffect);
|
|
||||||
it.commandDateSign = toDate(it.commandDateSign);
|
|
||||||
}
|
|
||||||
|
|
||||||
const roleKeycloak = await this.roleKeycloakRepo.findOne({
|
|
||||||
where: { name: Like("USER") },
|
|
||||||
});
|
|
||||||
let _posNumCodeSit: string = "";
|
|
||||||
let _posNumCodeSitAbb: string = "";
|
|
||||||
const _command = await this.commandRepository.findOne({
|
|
||||||
relations: ["commandType"],
|
|
||||||
where: { id: data.find((x) => x.commandId)?.commandId ?? "" },
|
|
||||||
});
|
|
||||||
if (_command) {
|
|
||||||
if (_command?.isBangkok?.toLocaleUpperCase() == "OFFICE") {
|
|
||||||
const orgRootDeputy = await this.orgRootRepository.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 this.profileRepository.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 ?? "";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
const today = new Date().setHours(0, 0, 0, 0);
|
|
||||||
|
|
||||||
// ─────────────────────────────────────────────────────────────
|
|
||||||
// Single transaction ครอบทั้ง batch (all-or-nothing)
|
|
||||||
// ทุกคนใช้ manager ตัวเดียวกัน — คนใด throw จะ rollback ทั้ง batch
|
|
||||||
// และ propagate error ออกไป (ล้มเหลวทั้งหมด) โดย log error ของคนที่ทำให้ fail ก่อน rethrow
|
|
||||||
// ─────────────────────────────────────────────────────────────
|
|
||||||
await AppDataSource.transaction(async (manager) => {
|
|
||||||
for (const item of data ?? []) {
|
|
||||||
try {
|
|
||||||
await this.processOne(
|
|
||||||
item,
|
|
||||||
ctx,
|
|
||||||
manager,
|
|
||||||
_command,
|
|
||||||
_posNumCodeSit,
|
|
||||||
_posNumCodeSitAbb,
|
|
||||||
today,
|
|
||||||
roleKeycloak,
|
|
||||||
);
|
|
||||||
} catch (err) {
|
|
||||||
const reason =
|
|
||||||
err instanceof HttpError
|
|
||||||
? err.message
|
|
||||||
: err instanceof Error
|
|
||||||
? err.message
|
|
||||||
: "unexpected error";
|
|
||||||
console.error(
|
|
||||||
`[ExecuteSalaryLeaveService] Failed commandCode=${commandCode}, commandId=${commandId}, profileId=${item.profileId}: ${reason}`,
|
|
||||||
err,
|
|
||||||
);
|
|
||||||
throw err; // → rollback ทั้ง transaction + propagate เป็น batch failure
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* ประมวลผล 1 คน ภายใน transaction เดียว (manager)
|
|
||||||
* ทุก save ใช้ manager.getRepository(...) เพื่อให้อยู่ใน transaction เดียวกัน
|
|
||||||
* ถ้า throw ระหว่างทาง → rollback ทั้งหมดของคนนี้ + ทั้ง batch (กัน partial commit)
|
|
||||||
*/
|
|
||||||
private async processOne(
|
|
||||||
item: SalaryLeaveItem,
|
|
||||||
ctx: SalaryLeaveExecutionContext,
|
|
||||||
manager: EntityManager,
|
|
||||||
_command: Command | null,
|
|
||||||
_posNumCodeSit: string,
|
|
||||||
_posNumCodeSitAbb: string,
|
|
||||||
today: number,
|
|
||||||
roleKeycloak: RoleKeycloak | null,
|
|
||||||
): Promise<void> {
|
|
||||||
const req = ctx.req;
|
|
||||||
|
|
||||||
const commandReciveRepository = manager.getRepository(CommandRecive);
|
|
||||||
const profileRepository = manager.getRepository(Profile);
|
|
||||||
const salaryRepo = manager.getRepository(ProfileSalary);
|
|
||||||
const salaryHistoryRepo = manager.getRepository(ProfileSalaryHistory);
|
|
||||||
const posMasterRepository = manager.getRepository(PosMaster);
|
|
||||||
const positionRepository = manager.getRepository(Position);
|
|
||||||
const orgRevisionRepo = manager.getRepository(OrgRevision);
|
|
||||||
const roleKeycloakRepo = manager.getRepository(RoleKeycloak);
|
|
||||||
|
|
||||||
const profile = await profileRepository.findOne({
|
|
||||||
where: { id: item.profileId },
|
|
||||||
relations: {
|
|
||||||
roleKeycloaks: true,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
if (!profile) {
|
|
||||||
throw new HttpError(HttpStatusCode.NOT_FOUND, "ไม่พบข้อมูลทะเบียนประวัตินี้");
|
|
||||||
}
|
|
||||||
//ลบตำแหน่งที่รักษาการแทน
|
|
||||||
const code = _command?.commandType?.code;
|
|
||||||
if (code && ["C-PM-08", "C-PM-17", "C-PM-18", "C-PM-48"].includes(code)) {
|
|
||||||
// await (เดิมไม่ await = fire-and-forget bug) + ส่ง manager เข้าไปเพื่อให้อยู่ใน transaction
|
|
||||||
await removePostMasterAct(profile.id, manager);
|
|
||||||
}
|
|
||||||
//ออกคำสั่งยกเลิกลาออก ลบเฉพาะคนที่ขอยกเลิกลาออก
|
|
||||||
else if (item.resignId && code && ["C-PM-41"].includes(code)) {
|
|
||||||
const commandResign = await commandReciveRepository.findOne({
|
|
||||||
where: { refId: item.resignId },
|
|
||||||
relations: { command: true },
|
|
||||||
});
|
|
||||||
const executeDate = commandResign
|
|
||||||
? new Date(commandResign.command.commandExcecuteDate).setHours(0, 0, 0, 0)
|
|
||||||
: today;
|
|
||||||
if (
|
|
||||||
commandResign &&
|
|
||||||
_command.status !== "REPORTED" &&
|
|
||||||
(_command.status !== "WAITING" || today < executeDate)
|
|
||||||
) {
|
|
||||||
// ส่ง manager เข้าไปเพื่อให้อยู่ใน transaction เดียวกัน
|
|
||||||
await reOrderCommandRecivesAndDelete(commandResign!.id, manager);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
let _commandYear = item.commandYear;
|
|
||||||
if (item.commandYear) {
|
|
||||||
_commandYear = item.commandYear > 2500 ? item.commandYear : item.commandYear + 543;
|
|
||||||
}
|
|
||||||
const returnWork = await checkReturnCommandType(String(item.commandId));
|
|
||||||
const dest_item = await salaryRepo.findOne({
|
|
||||||
where: { profileId: item.profileId },
|
|
||||||
order: { order: "DESC" },
|
|
||||||
});
|
|
||||||
const before = null;
|
|
||||||
const dataSalary = new ProfileSalary();
|
|
||||||
dataSalary.posNumCodeSit = _posNumCodeSit;
|
|
||||||
dataSalary.posNumCodeSitAbb = _posNumCodeSitAbb;
|
|
||||||
dataSalary.dateGovernment = (item.commandDateAffect as Date) ?? new Date();
|
|
||||||
dataSalary.order = dest_item == null ? 1 : dest_item.order + 1;
|
|
||||||
const meta = {
|
|
||||||
createdUserId: ctx.user.sub,
|
|
||||||
createdFullName: ctx.user.name,
|
|
||||||
lastUpdateUserId: ctx.user.sub,
|
|
||||||
lastUpdateFullName: ctx.user.name,
|
|
||||||
createdAt: new Date(),
|
|
||||||
lastUpdatedAt: new Date(),
|
|
||||||
};
|
|
||||||
if (!returnWork) {
|
|
||||||
Object.assign(dataSalary, { ...item, ...meta });
|
|
||||||
const history = new ProfileSalaryHistory();
|
|
||||||
Object.assign(history, { ...dataSalary, id: undefined });
|
|
||||||
await salaryRepo.save(dataSalary, { data: req });
|
|
||||||
setLogDataDiff(req, { before, after: dataSalary });
|
|
||||||
history.profileSalaryId = dataSalary.id;
|
|
||||||
await salaryHistoryRepo.save(history, { data: req });
|
|
||||||
}
|
|
||||||
const _null: any = null;
|
|
||||||
profile.isLeave = item.isLeave;
|
|
||||||
profile.leaveReason = item.leaveReason ?? _null;
|
|
||||||
profile.dateLeave = item.dateLeave ?? _null;
|
|
||||||
profile.lastUpdateUserId = ctx.user.sub;
|
|
||||||
profile.lastUpdateFullName = ctx.user.name;
|
|
||||||
profile.lastUpdatedAt = new Date();
|
|
||||||
const clearProfile = await checkCommandType(String(item.commandId));
|
|
||||||
|
|
||||||
//ปั๊มประวัติก่อนลบตำแหน่ง
|
|
||||||
const curRevision = await orgRevisionRepo.findOne({
|
|
||||||
where: { orgRevisionIsCurrent: true, orgRevisionIsDraft: false },
|
|
||||||
});
|
|
||||||
let orgRootRef = null;
|
|
||||||
let orgChild1Ref = null;
|
|
||||||
let orgChild2Ref = null;
|
|
||||||
let orgChild3Ref = null;
|
|
||||||
let orgChild4Ref = null;
|
|
||||||
if (curRevision) {
|
|
||||||
const curPosMaster = await posMasterRepository.findOne({
|
|
||||||
where: {
|
|
||||||
current_holderId: profile.id,
|
|
||||||
orgRevisionId: curRevision.id,
|
|
||||||
},
|
|
||||||
relations: {
|
|
||||||
orgRoot: true,
|
|
||||||
orgChild1: true,
|
|
||||||
orgChild2: true,
|
|
||||||
orgChild3: true,
|
|
||||||
orgChild4: true,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
orgRootRef = curPosMaster?.orgRoot ?? null;
|
|
||||||
orgChild1Ref = curPosMaster?.orgChild1 ?? null;
|
|
||||||
orgChild2Ref = curPosMaster?.orgChild2 ?? null;
|
|
||||||
orgChild3Ref = curPosMaster?.orgChild3 ?? null;
|
|
||||||
orgChild4Ref = curPosMaster?.orgChild4 ?? null;
|
|
||||||
if (curPosMaster && clearProfile.LeaveType != "RETIRE_OUT_EMP") {
|
|
||||||
// ส่ง manager เข้าไปเพื่อให้อยู่ใน transaction เดียวกัน
|
|
||||||
console.log(
|
|
||||||
`[ExecuteSalaryLeaveService] Creating PosMasterHistory — posMasterId: ${curPosMaster.id}, profileId: ${item.profileId}, type: DELETE`,
|
|
||||||
);
|
|
||||||
await CreatePosMasterHistoryOfficer(curPosMaster.id, req, "DELETE", null, manager);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
//ลบตำแหน่ง
|
|
||||||
if (item.isLeave == true) {
|
|
||||||
// ส่ง manager เข้าไปเพื่อให้อยู่ใน transaction เดียวกัน
|
|
||||||
await removeProfileInOrganize(profile.id, "OFFICER", manager);
|
|
||||||
}
|
|
||||||
if (clearProfile.status) {
|
|
||||||
if (profile.keycloak != null && profile.keycloak != "" && profile.isDelete === false) {
|
|
||||||
// Keycloak ทำภายใน transaction — ไม่สามารถ rollback ได้ (ดู docstring ของ class)
|
|
||||||
const delUserKeycloak = await deleteUser(profile.keycloak);
|
|
||||||
if (delUserKeycloak) {
|
|
||||||
// Task #228
|
|
||||||
// profile.keycloak = _null;
|
|
||||||
profile.roleKeycloaks = [];
|
|
||||||
profile.isActive = false;
|
|
||||||
profile.isDelete = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
profile.leaveCommandId = item.commandId ?? _null;
|
|
||||||
profile.leaveCommandNo = `${item.commandNo}/${_commandYear}`;
|
|
||||||
profile.leaveRemark = clearProfile.leaveRemark ?? _null;
|
|
||||||
profile.leaveDate = item.commandDateAffect ?? _null;
|
|
||||||
profile.leaveType = clearProfile.LeaveType ?? _null;
|
|
||||||
//ออกจากราชการ ไม่ต้องลบตำแหน่งในทะเบียน (issue #1516)
|
|
||||||
// profile.position = _null;
|
|
||||||
// profile.posTypeId = _null;
|
|
||||||
// profile.posLevelId = _null;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (item.isGovernment == true) {
|
|
||||||
if (returnWork) {
|
|
||||||
//ปลดตำแหน่งเดิมที่ไม่ถูกปลดออกจากกิ่งครั้งเมื่อออกคำสั่งพักราชการหรือออกราชการไว้
|
|
||||||
await removeProfileInOrganize(profile.id, "OFFICER", manager);
|
|
||||||
//ปั๊มตำแหน่งใหม่
|
|
||||||
// หา posMaster และเช็ค orgRevisionIsCurrent
|
|
||||||
let posMaster = await posMasterRepository.findOne({
|
|
||||||
where: { id: item.posmasterId?.toString() },
|
|
||||||
relations: {
|
|
||||||
orgRevision: true,
|
|
||||||
orgRoot: true,
|
|
||||||
orgChild1: true,
|
|
||||||
orgChild2: true,
|
|
||||||
orgChild3: true,
|
|
||||||
orgChild4: true,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
// เช็คว่า posMaster ที่หามาอยู่ในโครงสร้างปัจจุบันหรือไม่
|
|
||||||
const isCurrent =
|
|
||||||
posMaster?.orgRevision?.orgRevisionIsCurrent === true &&
|
|
||||||
posMaster?.orgRevision?.orgRevisionIsDraft === false;
|
|
||||||
|
|
||||||
// ถ้าไม่อยู่ในโครงสร้างปัจจุบัน ให้หาตัวใหม่จาก ancestorDNA
|
|
||||||
if (!isCurrent && posMaster?.ancestorDNA) {
|
|
||||||
posMaster = await posMasterRepository.findOne({
|
|
||||||
where: {
|
|
||||||
ancestorDNA: posMaster.ancestorDNA,
|
|
||||||
orgRevision: {
|
|
||||||
orgRevisionIsCurrent: true,
|
|
||||||
orgRevisionIsDraft: false,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
relations: {
|
|
||||||
orgRevision: true,
|
|
||||||
orgRoot: true,
|
|
||||||
orgChild1: true,
|
|
||||||
orgChild2: true,
|
|
||||||
orgChild3: true,
|
|
||||||
orgChild4: true,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
if (posMaster) {
|
|
||||||
const checkPosition = await positionRepository.find({
|
|
||||||
where: {
|
|
||||||
posMasterId: posMaster.id,
|
|
||||||
positionIsSelected: true,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
if (checkPosition.length > 0) {
|
|
||||||
const clearPosition = checkPosition.map((positions) => ({
|
|
||||||
...positions,
|
|
||||||
positionIsSelected: false,
|
|
||||||
}));
|
|
||||||
await positionRepository.save(clearPosition);
|
|
||||||
}
|
|
||||||
posMaster.current_holderId = profile.id;
|
|
||||||
posMaster.lastUpdatedAt = new Date();
|
|
||||||
// posMaster.conditionReason = _null;
|
|
||||||
// posMaster.isCondition = false;
|
|
||||||
await posMasterRepository.save(posMaster);
|
|
||||||
|
|
||||||
// Match position ตามลำดับ priority:
|
|
||||||
// Condition 1: match จาก positionId
|
|
||||||
// Condition 2: match 7 ฟิลด์ (positionName, posTypeId, posLevelId, positionField, positionArea, positionExecutiveField, posExecutiveId)
|
|
||||||
// Condition 3: match 3 ฟิลด์ (positionName, posTypeId, posLevelId)
|
|
||||||
// Fallback: เลือก position แรกใน posMaster
|
|
||||||
|
|
||||||
let positionNew: Position | null = null;
|
|
||||||
|
|
||||||
// ═══════════════════════════════════════════════════════════
|
|
||||||
// CONDITION 1: เช็คจาก positionId ตรง
|
|
||||||
// ═══════════════════════════════════════════════════════════
|
|
||||||
if (item.positionId) {
|
|
||||||
const positionById = await positionRepository.findOne({
|
|
||||||
where: {
|
|
||||||
id: item.positionId,
|
|
||||||
posMasterId: posMaster.id, // ต้องอยู่ใน posMaster ที่ถูกต้อง
|
|
||||||
},
|
|
||||||
relations: ["posExecutive"],
|
|
||||||
});
|
|
||||||
|
|
||||||
if (positionById) {
|
|
||||||
positionNew = positionById;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// ═══════════════════════════════════════════════════════════
|
|
||||||
// CONDITION 2: Match 7 ฟิลด์ (ถ้า Condition 1 ไม่ match)
|
|
||||||
// ═══════════════════════════════════════════════════════════
|
|
||||||
if (!positionNew && item.positionNameNew && item.positionTypeNew && item.positionLevelNew) {
|
|
||||||
// สร้าง where clause แบบ dynamic - ใส่เฉพาะฟิลด์ที่มีค่า
|
|
||||||
const whereCondition: any = {
|
|
||||||
posMasterId: posMaster.id,
|
|
||||||
positionName: item.positionNameNew,
|
|
||||||
posTypeId: item.positionTypeNew,
|
|
||||||
posLevelId: item.positionLevelNew,
|
|
||||||
};
|
|
||||||
|
|
||||||
if (item.positionField) {
|
|
||||||
whereCondition.positionField = item.positionField;
|
|
||||||
}
|
|
||||||
if (item.posExecutiveId) {
|
|
||||||
whereCondition.posExecutiveId = item.posExecutiveId;
|
|
||||||
}
|
|
||||||
if (item.positionExecutiveField) {
|
|
||||||
whereCondition.positionExecutiveField = item.positionExecutiveField;
|
|
||||||
}
|
|
||||||
if (item.positionArea) {
|
|
||||||
whereCondition.positionArea = item.positionArea;
|
|
||||||
}
|
|
||||||
|
|
||||||
const positionBy7Fields = await positionRepository.findOne({
|
|
||||||
where: whereCondition,
|
|
||||||
relations: ["posExecutive"],
|
|
||||||
order: { orderNo: "ASC" },
|
|
||||||
});
|
|
||||||
|
|
||||||
if (positionBy7Fields) {
|
|
||||||
positionNew = positionBy7Fields;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// ═══════════════════════════════════════════════════════════
|
|
||||||
// CONDITION 3: Match 3 ฟิลด์ (ถ้า Condition 2 ไม่ match)
|
|
||||||
// ═══════════════════════════════════════════════════════════
|
|
||||||
if (!positionNew && item.positionNameNew && item.positionTypeNew && item.positionLevelNew) {
|
|
||||||
const positionBy3Fields = await positionRepository.findOne({
|
|
||||||
where: {
|
|
||||||
posMasterId: posMaster.id,
|
|
||||||
positionName: item.positionNameNew,
|
|
||||||
posTypeId: item.positionTypeNew,
|
|
||||||
posLevelId: item.positionLevelNew,
|
|
||||||
},
|
|
||||||
relations: ["posExecutive"],
|
|
||||||
order: { orderNo: "ASC" },
|
|
||||||
});
|
|
||||||
|
|
||||||
if (positionBy3Fields) {
|
|
||||||
positionNew = positionBy3Fields;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// // FALLBACK: เลือก position แรก (ถ้าไม่เจอทั้ง 2 condition)
|
|
||||||
// if (!positionNew) {
|
|
||||||
// const fallbackPositions = await positionRepository.find({
|
|
||||||
// where: {
|
|
||||||
// posMasterId: posMaster.id,
|
|
||||||
// },
|
|
||||||
// relations: ["posExecutive"],
|
|
||||||
// order: {
|
|
||||||
// orderNo: "ASC",
|
|
||||||
// },
|
|
||||||
// take: 1,
|
|
||||||
// });
|
|
||||||
|
|
||||||
// if (fallbackPositions.length > 0) {
|
|
||||||
// positionNew = fallbackPositions[0];
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
|
|
||||||
if (positionNew) {
|
|
||||||
positionNew.positionIsSelected = true;
|
|
||||||
await positionRepository.save(positionNew, { data: req });
|
|
||||||
}
|
|
||||||
// ส่ง manager เข้าไปเพื่อให้อยู่ใน transaction เดียวกัน
|
|
||||||
console.log(
|
|
||||||
`[ExecuteSalaryLeaveService] Creating PosMasterHistory — posMasterId: ${posMaster.id}, profileId: ${item.profileId}`,
|
|
||||||
);
|
|
||||||
await CreatePosMasterHistoryOfficer(posMaster.id, req, null, null, manager);
|
|
||||||
profile.posMasterNo = getPosMasterNo(posMaster);
|
|
||||||
profile.org = getOrgFullName(posMaster);
|
|
||||||
}
|
|
||||||
const newMapProfileSalary = {
|
|
||||||
profileId: profile.id,
|
|
||||||
commandId: item.commandId,
|
|
||||||
positionName: item.positionNameNew ?? null,
|
|
||||||
positionType: item.posTypeNameNew ?? null,
|
|
||||||
positionLevel: item.posLevelNameNew ?? null,
|
|
||||||
amount: item.amount ? item.amount : null,
|
|
||||||
positionSalaryAmount: item.positionSalaryAmount ? item.positionSalaryAmount : null,
|
|
||||||
amountSpecial: item.amountSpecial ? item.amountSpecial : null,
|
|
||||||
mouthSalaryAmount: item.mouthSalaryAmount ? item.mouthSalaryAmount : null,
|
|
||||||
posNo: item.posNoNew,
|
|
||||||
posNoAbb: item.posNoAbbNew,
|
|
||||||
orgRoot: item.orgRootNew,
|
|
||||||
orgChild1: item.orgChild1New,
|
|
||||||
orgChild2: item.orgChild2New,
|
|
||||||
orgChild3: item.orgChild3New,
|
|
||||||
orgChild4: item.orgChild4New,
|
|
||||||
isGovernment: item.isGovernment,
|
|
||||||
commandNo: item.commandNo,
|
|
||||||
commandYear: item.commandYear,
|
|
||||||
commandDateAffect: item.commandDateAffect,
|
|
||||||
commandDateSign: item.commandDateSign,
|
|
||||||
commandCode: item.commandCode,
|
|
||||||
commandName: item.commandName,
|
|
||||||
remark: item.remark,
|
|
||||||
};
|
|
||||||
Object.assign(dataSalary, { ...newMapProfileSalary, ...meta });
|
|
||||||
const history = new ProfileSalaryHistory();
|
|
||||||
Object.assign(history, { ...dataSalary, id: undefined });
|
|
||||||
await salaryRepo.save(dataSalary);
|
|
||||||
history.profileSalaryId = dataSalary.id;
|
|
||||||
await salaryHistoryRepo.save(history);
|
|
||||||
profile.leaveReason = _null;
|
|
||||||
profile.leaveCommandId = _null;
|
|
||||||
profile.leaveCommandNo = _null;
|
|
||||||
profile.leaveRemark = _null;
|
|
||||||
profile.leaveDate = _null;
|
|
||||||
profile.leaveType = _null;
|
|
||||||
profile.position = item.positionNameNew ?? _null;
|
|
||||||
profile.posTypeId = item.positionTypeNew ?? _null;
|
|
||||||
profile.posLevelId = item.positionLevelNew ?? _null;
|
|
||||||
}
|
|
||||||
let userKeycloakId;
|
|
||||||
const checkUser = await getUserByUsername(profile.citizenId);
|
|
||||||
//ถ้ายังไม่มี user keycloak ให้สร้างใหม่
|
|
||||||
if (checkUser.length == 0) {
|
|
||||||
let password = profile.citizenId;
|
|
||||||
if (profile.birthDate != null) {
|
|
||||||
const _date = new Date(profile.birthDate.toDateString())
|
|
||||||
.getDate()
|
|
||||||
.toString()
|
|
||||||
.padStart(2, "0");
|
|
||||||
const _month = (new Date(profile.birthDate.toDateString()).getMonth() + 1)
|
|
||||||
.toString()
|
|
||||||
.padStart(2, "0");
|
|
||||||
const _year = new Date(profile.birthDate.toDateString()).getFullYear() + 543;
|
|
||||||
password = `${_date}${_month}${_year}`;
|
|
||||||
}
|
|
||||||
// กรอง "." ออกจาก firstName ก่อนส่งไป keycloak
|
|
||||||
const sanitizedFirstName = profile.firstName?.replace(/\./g, "") ?? "";
|
|
||||||
// Keycloak ทำภายใน transaction — ไม่สามารถ rollback ได้ (ดู docstring ของ class)
|
|
||||||
userKeycloakId = await createUser(profile.citizenId, password, {
|
|
||||||
firstName: sanitizedFirstName,
|
|
||||||
lastName: profile.lastName,
|
|
||||||
});
|
|
||||||
const list = await getRoles();
|
|
||||||
let result = false;
|
|
||||||
if (Array.isArray(list) && userKeycloakId) {
|
|
||||||
result = await addUserRoles(
|
|
||||||
userKeycloakId,
|
|
||||||
list
|
|
||||||
.filter((v) => v.name === "USER")
|
|
||||||
.map((x) => ({
|
|
||||||
id: x.id,
|
|
||||||
name: x.name,
|
|
||||||
})),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
profile.roleKeycloaks = result && roleKeycloak ? [roleKeycloak] : [];
|
|
||||||
profile.keycloak =
|
|
||||||
userKeycloakId && typeof userKeycloakId === "string" ? userKeycloakId : "";
|
|
||||||
}
|
|
||||||
//ถ้ามีอยู่แล้วให้ใช้อันเดิม
|
|
||||||
else {
|
|
||||||
const rolesData = await getRoleMappings(checkUser[0].id);
|
|
||||||
if (rolesData) {
|
|
||||||
const _roleKeycloak = await roleKeycloakRepo.find({
|
|
||||||
where: { name: In(rolesData.map((x: any) => x.name)) },
|
|
||||||
});
|
|
||||||
profile.roleKeycloaks =
|
|
||||||
_roleKeycloak && _roleKeycloak.length > 0 ? _roleKeycloak : [];
|
|
||||||
}
|
|
||||||
profile.keycloak = checkUser[0].id;
|
|
||||||
}
|
|
||||||
profile.amount = item.amount ?? _null;
|
|
||||||
profile.amountSpecial = item.amountSpecial ?? _null;
|
|
||||||
profile.isActive = true;
|
|
||||||
profile.isDelete = false;
|
|
||||||
}
|
|
||||||
await profileRepository.save(profile);
|
|
||||||
|
|
||||||
// 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", "C-PM-48"].includes(code)) {
|
|
||||||
let organizeName = "";
|
|
||||||
if (orgRootRef) {
|
|
||||||
const names = [
|
|
||||||
orgChild4Ref?.orgChild4Name,
|
|
||||||
orgChild3Ref?.orgChild3Name,
|
|
||||||
orgChild2Ref?.orgChild2Name,
|
|
||||||
orgChild1Ref?.orgChild1Name,
|
|
||||||
orgRootRef?.orgRootName,
|
|
||||||
].filter(Boolean);
|
|
||||||
organizeName = names.join(" ");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
console.log(
|
|
||||||
`[ExecuteSalaryLeaveService] Completed processOne — profileId: ${item.profileId}`,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,539 +0,0 @@
|
||||||
import { Double, EntityManager, In, Repository } from "typeorm";
|
|
||||||
import { AppDataSource } from "../database/data-source";
|
|
||||||
import HttpError from "../interfaces/http-error";
|
|
||||||
import HttpStatusCode from "../interfaces/http-status";
|
|
||||||
import { Profile } from "../entities/Profile";
|
|
||||||
import { ProfileSalary } from "../entities/ProfileSalary";
|
|
||||||
import { ProfileSalaryHistory } from "../entities/ProfileSalaryHistory";
|
|
||||||
import { Command } from "../entities/Command";
|
|
||||||
import { OrgRoot } from "../entities/OrgRoot";
|
|
||||||
import { OrgRevision } from "../entities/OrgRevision";
|
|
||||||
import { checkCommandType, removeProfileInOrganize } from "../interfaces/utils";
|
|
||||||
import { CreatePosMasterHistoryOfficer } from "./PositionService";
|
|
||||||
import { deleteUser } from "../keycloak";
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Input: ข้อมูล 1 คนที่ probation return กลับมา (หลัง Linear Flow refactor)
|
|
||||||
* - C-PM-11 : excexute/salary-probation (ผ่านทดลองงาน)
|
|
||||||
* - C-PM-12 : excexute/salary-probation-leave (ออกเพราะผลทดลองฯ ต่ำกว่ามาตรฐาน)
|
|
||||||
*
|
|
||||||
* shape เดียวกับ body.data ของ endpoint /org/command/excexute/salary-probation(-leave) เดิม
|
|
||||||
*/
|
|
||||||
export interface ProbationSalaryItem {
|
|
||||||
profileId: string;
|
|
||||||
commandId?: string | null;
|
|
||||||
amount?: Double | null;
|
|
||||||
amountSpecial?: Double | null;
|
|
||||||
positionSalaryAmount?: Double | null;
|
|
||||||
mouthSalaryAmount?: Double | null;
|
|
||||||
commandNo: string | null;
|
|
||||||
commandYear: number | null;
|
|
||||||
commandDateAffect?: Date | string | null;
|
|
||||||
commandDateSign?: Date | string | null;
|
|
||||||
positionName?: string | null;
|
|
||||||
commandCode?: string | null;
|
|
||||||
commandName?: string | null;
|
|
||||||
remark: string | null;
|
|
||||||
isGovernment?: boolean | null; // C-PM-12 เท่านั้น
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Context สำหรับ audit/log (เหมือน ExecuteSalaryService)
|
|
||||||
*/
|
|
||||||
export interface ProbationExecutionContext {
|
|
||||||
user: { sub: string; name: string };
|
|
||||||
req?: any;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Service สำหรับคำสั่งทดลองปฏิบัติหน้าที่ราชการ (probation) — C-PM-11, C-PM-12
|
|
||||||
*
|
|
||||||
* เดิมเป็น circular callback: org AMQ → probation → PostData("/org/command/excexute/salary-probation(-leave)")
|
|
||||||
* หลัง Linear Flow: org AMQ → probation (return salary data) → เรียก service นี้โดยตรง (no callback)
|
|
||||||
*
|
|
||||||
* - C-PM-11 : executeProbationPass — สร้าง ProfileSalary + history, isProbation=false
|
|
||||||
* - C-PM-12 : executeProbationLeave — leave logic + deleteUser(Keycloak) + สร้าง ProfileSalary + history
|
|
||||||
*
|
|
||||||
* - endpoint /org/command/excexute/salary-probation(-leave) เรียกผ่าน service นี้ (thin wrapper)
|
|
||||||
* - consumer ใน rabbitmq handler เรียกผ่าน service นี้โดยตรง (Linear Flow)
|
|
||||||
*
|
|
||||||
* Behavior ทั้งหมด preserve จาก CommandController
|
|
||||||
* (newSalaryAndUpdateLeaveDisciplinefgh / ExecuteCommand12Async ต้นฉบับ)
|
|
||||||
*
|
|
||||||
* Batch semantics: all-or-nothing — ประมวลผลทุกคนภายใต้ transaction เดียว (sequential)
|
|
||||||
* ถ้าคนใด throw จะ rollback ทั้ง batch และ propagate error ออกไป (ล้มเหลวทั้งหมด)
|
|
||||||
*
|
|
||||||
* ⚠️ Keycloak: deleteUser (C-PM-12) ทำภายใน transaction เพื่อ preserve behavior เดิม
|
|
||||||
* (consistent กับ ExecuteSalaryService C-PM-13/15/16) — Keycloak ไม่สามารถ rollback ได้
|
|
||||||
* ถ้า DB rollback หลังจาก deleteUser สำเร็จ → user จะถูกลบใน Keycloak ไปแล้ว
|
|
||||||
*/
|
|
||||||
export class ExecuteSalaryProbationService {
|
|
||||||
private commandRepository = AppDataSource.getRepository(Command);
|
|
||||||
private profileRepository = AppDataSource.getRepository(Profile);
|
|
||||||
private orgRootRepository = AppDataSource.getRepository(OrgRoot);
|
|
||||||
|
|
||||||
// ─────────────────────────────────────────────────────────────
|
|
||||||
// แก้ปัญหา _posNumCodeSit resolution ที่ซ้ำกันในทุก endpoint
|
|
||||||
// (เดิมอยู่ใน controller — ย้ายมานี่ ทำครั้งเดียวก่อนเข้า transaction)
|
|
||||||
// ─────────────────────────────────────────────────────────────
|
|
||||||
private async resolvePosNumCodeSit(
|
|
||||||
commandId: string | null | undefined,
|
|
||||||
): Promise<{ posNumCodeSit: string; posNumCodeSitAbb: string }> {
|
|
||||||
let posNumCodeSit = "";
|
|
||||||
let posNumCodeSitAbb = "";
|
|
||||||
const command = commandId
|
|
||||||
? await this.commandRepository.findOne({ where: { id: commandId } })
|
|
||||||
: null;
|
|
||||||
if (command) {
|
|
||||||
if (command?.isBangkok?.toLocaleUpperCase() == "OFFICE") {
|
|
||||||
const orgRootDeputy = await this.orgRootRepository.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 {
|
|
||||||
const profileAdmin = await this.profileRepository.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 ?? "";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return { posNumCodeSit, posNumCodeSitAbb };
|
|
||||||
}
|
|
||||||
|
|
||||||
// normalize date (AMQ path ส่ง string มา → แปลงเป็น Date / null ถ้า invalid)
|
|
||||||
private toDate(v: any): Date | null {
|
|
||||||
if (v == null || v === "") return null;
|
|
||||||
if (v instanceof Date) return isNaN(v.getTime()) ? null : v;
|
|
||||||
const d = new Date(v);
|
|
||||||
return isNaN(d.getTime()) ? null : d;
|
|
||||||
}
|
|
||||||
|
|
||||||
// ═══════════════════════════════════════════════════════════════
|
|
||||||
// C-PM-11 : ผ่านทดลองปฏิบัติหน้าที่ราชการ
|
|
||||||
// สร้าง ProfileSalary + history แล้ว set isProbation=false
|
|
||||||
// ═══════════════════════════════════════════════════════════════
|
|
||||||
async executeProbationPass(
|
|
||||||
data: ProbationSalaryItem[],
|
|
||||||
ctx: ProbationExecutionContext,
|
|
||||||
): Promise<void> {
|
|
||||||
const commandId = data?.find((x) => x.commandId)?.commandId ?? "";
|
|
||||||
console.log(
|
|
||||||
`[ExecuteSalaryProbationService] executeProbationPass (C-PM-11) — commandId: ${commandId}, count: ${data?.length ?? 0}`,
|
|
||||||
);
|
|
||||||
|
|
||||||
// ─────────────────────────────────────────────────────────────
|
|
||||||
// Normalize date fields (ผ่าน AMQ handler จะได้ string → ต้องแปลงเป็น Date)
|
|
||||||
// ─────────────────────────────────────────────────────────────
|
|
||||||
for (const item of data ?? []) {
|
|
||||||
const it = item as any;
|
|
||||||
it.commandDateAffect = this.toDate(it.commandDateAffect);
|
|
||||||
it.commandDateSign = this.toDate(it.commandDateSign);
|
|
||||||
}
|
|
||||||
|
|
||||||
const { posNumCodeSit: _posNumCodeSit, posNumCodeSitAbb: _posNumCodeSitAbb } =
|
|
||||||
await this.resolvePosNumCodeSit(commandId);
|
|
||||||
|
|
||||||
const profileIds = (data ?? []).map((x) => x.profileId).filter(Boolean);
|
|
||||||
|
|
||||||
await AppDataSource.transaction(async (manager) => {
|
|
||||||
const profileRepository = manager.getRepository(Profile);
|
|
||||||
const salaryRepo = manager.getRepository(ProfileSalary);
|
|
||||||
const salaryHistoryRepo = manager.getRepository(ProfileSalaryHistory);
|
|
||||||
const orgRevisionRepo = manager.getRepository(OrgRevision);
|
|
||||||
|
|
||||||
for (const item of data ?? []) {
|
|
||||||
try {
|
|
||||||
await this.processOneProbationPass(
|
|
||||||
item,
|
|
||||||
ctx,
|
|
||||||
_posNumCodeSit,
|
|
||||||
_posNumCodeSitAbb,
|
|
||||||
salaryRepo,
|
|
||||||
salaryHistoryRepo,
|
|
||||||
profileRepository,
|
|
||||||
orgRevisionRepo,
|
|
||||||
);
|
|
||||||
} catch (err) {
|
|
||||||
const reason =
|
|
||||||
err instanceof HttpError
|
|
||||||
? err.message
|
|
||||||
: err instanceof Error
|
|
||||||
? err.message
|
|
||||||
: "unexpected error";
|
|
||||||
console.error(
|
|
||||||
`[ExecuteSalaryProbationService] Failed C-PM-11, commandId=${commandId}, profileId=${item.profileId}: ${reason}`,
|
|
||||||
err,
|
|
||||||
);
|
|
||||||
throw err; // → rollback ทั้ง transaction + propagate เป็น batch failure
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// C-PM-11: ผ่านทดลองงาน → isProbation = false (bulk update ใน transaction เดียวกัน)
|
|
||||||
if (profileIds.length > 0) {
|
|
||||||
await profileRepository.update({ id: In(profileIds) }, { isProbation: false });
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
console.log(`[ExecuteSalaryProbationService] Completed C-PM-11 — ${data?.length ?? 0} items`);
|
|
||||||
}
|
|
||||||
|
|
||||||
private async processOneProbationPass(
|
|
||||||
item: ProbationSalaryItem,
|
|
||||||
ctx: ProbationExecutionContext,
|
|
||||||
_posNumCodeSit: string,
|
|
||||||
_posNumCodeSitAbb: string,
|
|
||||||
salaryRepo: Repository<ProfileSalary>,
|
|
||||||
salaryHistoryRepo: Repository<ProfileSalaryHistory>,
|
|
||||||
profileRepository: Repository<Profile>,
|
|
||||||
orgRevisionRepo: Repository<OrgRevision>,
|
|
||||||
): Promise<void> {
|
|
||||||
// current orgRevision (อ่านครั้งเดียวต่อคน — preserve query pattern ของ endpoint เดิม)
|
|
||||||
const orgRevision = await orgRevisionRepo.findOne({
|
|
||||||
where: { orgRevisionIsCurrent: true, orgRevisionIsDraft: false },
|
|
||||||
});
|
|
||||||
|
|
||||||
const profile: any = await profileRepository.findOne({
|
|
||||||
relations: [
|
|
||||||
"posType",
|
|
||||||
"posLevel",
|
|
||||||
"current_holders",
|
|
||||||
"current_holders.orgRoot",
|
|
||||||
"current_holders.orgChild1",
|
|
||||||
"current_holders.orgChild2",
|
|
||||||
"current_holders.orgChild3",
|
|
||||||
"current_holders.orgChild4",
|
|
||||||
],
|
|
||||||
where: { id: item.profileId },
|
|
||||||
});
|
|
||||||
if (!profile) {
|
|
||||||
throw new HttpError(HttpStatusCode.NOT_FOUND, "ไม่พบข้อมูลทะเบียนประวัตินี้");
|
|
||||||
}
|
|
||||||
|
|
||||||
const lastSalary = await salaryRepo.findOne({
|
|
||||||
where: { profileId: item.profileId },
|
|
||||||
select: ["order"],
|
|
||||||
order: { order: "DESC" },
|
|
||||||
});
|
|
||||||
const nextOrder = lastSalary ? lastSalary.order + 1 : 1;
|
|
||||||
|
|
||||||
const orgRevisionRef =
|
|
||||||
profile?.current_holders?.find((x: any) => x.orgRevisionId == orgRevision?.id) ?? null;
|
|
||||||
const shortName =
|
|
||||||
orgRevisionRef?.orgChild4?.orgChild4ShortName ??
|
|
||||||
orgRevisionRef?.orgChild3?.orgChild3ShortName ??
|
|
||||||
orgRevisionRef?.orgChild2?.orgChild2ShortName ??
|
|
||||||
orgRevisionRef?.orgChild1?.orgChild1ShortName ??
|
|
||||||
orgRevisionRef?.orgRoot?.orgRootShortName ??
|
|
||||||
null;
|
|
||||||
const posNo = orgRevisionRef?.posMasterNo?.toString() ?? null;
|
|
||||||
// NOTE: endpoint เดิมไม่ได้ load relation "current_holders.positions" → position เป็น null (preserve)
|
|
||||||
const position =
|
|
||||||
profile.current_holders
|
|
||||||
?.filter((x: any) => x.orgRevisionId == orgRevision?.id)[0]
|
|
||||||
?.positions?.filter((pos: any) => pos.positionIsSelected === true)[0] ?? null;
|
|
||||||
|
|
||||||
const dataSalary = new ProfileSalary();
|
|
||||||
dataSalary.posNumCodeSit = _posNumCodeSit;
|
|
||||||
dataSalary.posNumCodeSitAbb = _posNumCodeSitAbb;
|
|
||||||
const meta = {
|
|
||||||
profileId: item.profileId,
|
|
||||||
commandId: item.commandId,
|
|
||||||
positionName: profile.position,
|
|
||||||
positionType: profile?.posType?.posTypeName ?? null,
|
|
||||||
positionLevel: profile?.posLevel?.posLevelName ?? null,
|
|
||||||
positionExecutive: position?.posExecutive?.posExecutiveName ?? null,
|
|
||||||
amount: item.amount ? item.amount : null,
|
|
||||||
amountSpecial: item.amountSpecial ? item.amountSpecial : null,
|
|
||||||
positionSalaryAmount: item.positionSalaryAmount ? item.positionSalaryAmount : null,
|
|
||||||
mouthSalaryAmount: item.mouthSalaryAmount ? item.mouthSalaryAmount : 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: ctx.user.sub,
|
|
||||||
createdFullName: ctx.user.name,
|
|
||||||
lastUpdateUserId: ctx.user.sub,
|
|
||||||
lastUpdateFullName: ctx.user.name,
|
|
||||||
createdAt: new Date(),
|
|
||||||
lastUpdatedAt: new Date(),
|
|
||||||
commandNo: item.commandNo,
|
|
||||||
commandYear: item.commandYear,
|
|
||||||
posNo: posNo,
|
|
||||||
posNoAbb: shortName,
|
|
||||||
commandDateAffect: item.commandDateAffect,
|
|
||||||
commandDateSign: item.commandDateSign,
|
|
||||||
commandCode: item.commandCode,
|
|
||||||
commandName: item.commandName,
|
|
||||||
remark: item.remark,
|
|
||||||
};
|
|
||||||
Object.assign(dataSalary, meta);
|
|
||||||
const history = new ProfileSalaryHistory();
|
|
||||||
Object.assign(history, { ...dataSalary, id: undefined });
|
|
||||||
|
|
||||||
await salaryRepo.save(dataSalary);
|
|
||||||
history.profileSalaryId = dataSalary.id;
|
|
||||||
await salaryHistoryRepo.save(history);
|
|
||||||
}
|
|
||||||
|
|
||||||
// ═══════════════════════════════════════════════════════════════
|
|
||||||
// C-PM-12 : ออกจากราชการเพราะผลการทดลองปฏิบัติหน้าที่ราชการต่ำกว่ามาตรฐาน
|
|
||||||
// leave logic (removeProfileInOrganize + deleteUser Keycloak) + สร้าง ProfileSalary + history
|
|
||||||
// ═══════════════════════════════════════════════════════════════
|
|
||||||
async executeProbationLeave(
|
|
||||||
data: ProbationSalaryItem[],
|
|
||||||
ctx: ProbationExecutionContext,
|
|
||||||
): Promise<void> {
|
|
||||||
const commandId = data?.find((x) => x.commandId)?.commandId ?? "";
|
|
||||||
console.log(
|
|
||||||
`[ExecuteSalaryProbationService] executeProbationLeave (C-PM-12) — commandId: ${commandId}, count: ${data?.length ?? 0}`,
|
|
||||||
);
|
|
||||||
|
|
||||||
for (const item of data ?? []) {
|
|
||||||
const it = item as any;
|
|
||||||
it.commandDateAffect = this.toDate(it.commandDateAffect);
|
|
||||||
it.commandDateSign = this.toDate(it.commandDateSign);
|
|
||||||
}
|
|
||||||
|
|
||||||
const { posNumCodeSit: _posNumCodeSit, posNumCodeSitAbb: _posNumCodeSitAbb } =
|
|
||||||
await this.resolvePosNumCodeSit(commandId);
|
|
||||||
|
|
||||||
await AppDataSource.transaction(async (manager) => {
|
|
||||||
const profileRepository = manager.getRepository(Profile);
|
|
||||||
const salaryRepo = manager.getRepository(ProfileSalary);
|
|
||||||
const salaryHistoryRepo = manager.getRepository(ProfileSalaryHistory);
|
|
||||||
const orgRevisionRepo = manager.getRepository(OrgRevision);
|
|
||||||
|
|
||||||
for (const item of data ?? []) {
|
|
||||||
try {
|
|
||||||
await this.processOneProbationLeave(
|
|
||||||
item,
|
|
||||||
ctx,
|
|
||||||
manager,
|
|
||||||
_posNumCodeSit,
|
|
||||||
_posNumCodeSitAbb,
|
|
||||||
salaryRepo,
|
|
||||||
salaryHistoryRepo,
|
|
||||||
profileRepository,
|
|
||||||
orgRevisionRepo,
|
|
||||||
);
|
|
||||||
} catch (err) {
|
|
||||||
const reason =
|
|
||||||
err instanceof HttpError
|
|
||||||
? err.message
|
|
||||||
: err instanceof Error
|
|
||||||
? err.message
|
|
||||||
: "unexpected error";
|
|
||||||
console.error(
|
|
||||||
`[ExecuteSalaryProbationService] Failed C-PM-12, commandId=${commandId}, profileId=${item.profileId}: ${reason}`,
|
|
||||||
err,
|
|
||||||
);
|
|
||||||
throw err; // → rollback ทั้ง transaction + propagate เป็น batch failure
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
console.log(`[ExecuteSalaryProbationService] Completed C-PM-12 — ${data?.length ?? 0} items`);
|
|
||||||
}
|
|
||||||
|
|
||||||
private async processOneProbationLeave(
|
|
||||||
item: ProbationSalaryItem,
|
|
||||||
ctx: ProbationExecutionContext,
|
|
||||||
manager: EntityManager,
|
|
||||||
_posNumCodeSit: string,
|
|
||||||
_posNumCodeSitAbb: string,
|
|
||||||
salaryRepo: Repository<ProfileSalary>,
|
|
||||||
salaryHistoryRepo: Repository<ProfileSalaryHistory>,
|
|
||||||
profileRepository: Repository<Profile>,
|
|
||||||
orgRevisionRepo: Repository<OrgRevision>,
|
|
||||||
): Promise<void> {
|
|
||||||
const req = ctx.req;
|
|
||||||
|
|
||||||
const profile: any = await profileRepository.findOne({
|
|
||||||
relations: [
|
|
||||||
"posType",
|
|
||||||
"posLevel",
|
|
||||||
"current_holders",
|
|
||||||
"current_holders.orgRoot",
|
|
||||||
"current_holders.orgChild1",
|
|
||||||
"current_holders.orgChild2",
|
|
||||||
"current_holders.orgChild3",
|
|
||||||
"current_holders.orgChild4",
|
|
||||||
"current_holders.positions",
|
|
||||||
"current_holders.positions.posExecutive",
|
|
||||||
],
|
|
||||||
where: { id: item.profileId },
|
|
||||||
});
|
|
||||||
if (!profile) {
|
|
||||||
throw new HttpError(HttpStatusCode.NOT_FOUND, "ไม่พบข้อมูลโปรไฟล์");
|
|
||||||
}
|
|
||||||
|
|
||||||
const lastSalary = await salaryRepo.findOne({
|
|
||||||
where: { profileId: item.profileId },
|
|
||||||
select: ["order"],
|
|
||||||
order: { order: "DESC" },
|
|
||||||
});
|
|
||||||
const nextOrder = lastSalary ? lastSalary.order + 1 : 1;
|
|
||||||
let _commandYear = item.commandYear;
|
|
||||||
if (item.commandYear) {
|
|
||||||
_commandYear = item.commandYear > 2500 ? item.commandYear : item.commandYear + 543;
|
|
||||||
}
|
|
||||||
|
|
||||||
// _profile (load แยกสำหรับ mutation เกี่ยวกับ Keycloak/leave — preserve pattern เดิม)
|
|
||||||
const _profile: any = await profileRepository.findOne({
|
|
||||||
where: { id: item.profileId },
|
|
||||||
relations: ["roleKeycloaks"],
|
|
||||||
});
|
|
||||||
if (!_profile) {
|
|
||||||
throw new HttpError(HttpStatusCode.NOT_FOUND, "ไม่พบข้อมูลโปรไฟล์");
|
|
||||||
}
|
|
||||||
|
|
||||||
let dateLeave_: any = item.commandDateAffect;
|
|
||||||
_profile.isLeave = true;
|
|
||||||
_profile.leaveReason =
|
|
||||||
"คำสั่งให้ข้าราชการออกจากราชการเพราะผลการทดลองปฏิบัติหน้าที่ราชการต่ำกว่ามาตรฐานที่กำหนด";
|
|
||||||
_profile.dateLeave = dateLeave_;
|
|
||||||
_profile.lastUpdateUserId = ctx.user.sub;
|
|
||||||
_profile.lastUpdateFullName = ctx.user.name;
|
|
||||||
_profile.lastUpdatedAt = new Date();
|
|
||||||
|
|
||||||
const orgRevision = await orgRevisionRepo.findOne({
|
|
||||||
where: {
|
|
||||||
orgRevisionIsCurrent: true,
|
|
||||||
orgRevisionIsDraft: false,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
const orgRevisionRef =
|
|
||||||
profile?.current_holders?.find((x: any) => 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 matchHolder = profile.current_holders?.find((x: any) => x.orgRevisionId == orgRevision?.id);
|
|
||||||
const shortName =
|
|
||||||
!profile.current_holders || profile.current_holders.length == 0
|
|
||||||
? null
|
|
||||||
: matchHolder != null && matchHolder?.orgChild4 != null
|
|
||||||
? `${matchHolder.orgChild4.orgChild4ShortName}`
|
|
||||||
: matchHolder != null && matchHolder?.orgChild3 != null
|
|
||||||
? `${matchHolder.orgChild3.orgChild3ShortName}`
|
|
||||||
: matchHolder != null && matchHolder?.orgChild2 != null
|
|
||||||
? `${matchHolder.orgChild2.orgChild2ShortName}`
|
|
||||||
: matchHolder != null && matchHolder?.orgChild1 != null
|
|
||||||
? `${matchHolder.orgChild1.orgChild1ShortName}`
|
|
||||||
: matchHolder != null && matchHolder?.orgRoot != null
|
|
||||||
? `${matchHolder.orgRoot.orgRootShortName}`
|
|
||||||
: null;
|
|
||||||
const posNo = `${matchHolder?.posMasterNo}`;
|
|
||||||
const position =
|
|
||||||
matchHolder?.positions?.filter((pos: any) => pos.positionIsSelected === true)[0] ?? null;
|
|
||||||
|
|
||||||
const profileSalary: ProfileSalary = Object.assign(new ProfileSalary(), {
|
|
||||||
profileId: item.profileId,
|
|
||||||
commandId: item.commandId,
|
|
||||||
positionName: profile.position,
|
|
||||||
positionType: profile?.posType?.posTypeName ?? null,
|
|
||||||
positionLevel: profile?.posLevel?.posLevelName ?? null,
|
|
||||||
positionExecutive: position?.posExecutive?.posExecutiveName ?? null,
|
|
||||||
amount: item.amount ? item.amount : null,
|
|
||||||
amountSpecial: item.amountSpecial ? item.amountSpecial : null,
|
|
||||||
positionSalaryAmount: item.positionSalaryAmount ? item.positionSalaryAmount : null,
|
|
||||||
mouthSalaryAmount: item.mouthSalaryAmount ? item.mouthSalaryAmount : null,
|
|
||||||
order: nextOrder,
|
|
||||||
orgRoot: orgRootRef?.orgRootName ?? null,
|
|
||||||
orgChild1: orgChild1Ref?.orgChild1Name ?? null,
|
|
||||||
orgChild2: orgChild2Ref?.orgChild2Name ?? null,
|
|
||||||
orgChild3: orgChild3Ref?.orgChild3Name ?? null,
|
|
||||||
orgChild4: orgChild4Ref?.orgChild4Name ?? null,
|
|
||||||
createdUserId: ctx.user.sub,
|
|
||||||
createdFullName: ctx.user.name,
|
|
||||||
lastUpdateUserId: ctx.user.sub,
|
|
||||||
lastUpdateFullName: ctx.user.name,
|
|
||||||
createdAt: new Date(),
|
|
||||||
lastUpdatedAt: new Date(),
|
|
||||||
dateGovernment: item.commandDateAffect ?? new Date(),
|
|
||||||
isGovernment: item.isGovernment,
|
|
||||||
commandNo: item.commandNo,
|
|
||||||
commandYear: item.commandYear,
|
|
||||||
posNo: posNo,
|
|
||||||
posNoAbb: shortName,
|
|
||||||
commandDateAffect: item.commandDateAffect,
|
|
||||||
commandDateSign: item.commandDateSign,
|
|
||||||
commandCode: item.commandCode,
|
|
||||||
commandName: item.commandName,
|
|
||||||
remark: item.remark,
|
|
||||||
posNumCodeSit: _posNumCodeSit,
|
|
||||||
posNumCodeSitAbb: _posNumCodeSitAbb,
|
|
||||||
});
|
|
||||||
|
|
||||||
if (orgRevisionRef) {
|
|
||||||
await CreatePosMasterHistoryOfficer(orgRevisionRef.id, req, "DELETE", null, manager);
|
|
||||||
}
|
|
||||||
await removeProfileInOrganize(profile.id, "OFFICER", manager);
|
|
||||||
|
|
||||||
const clearProfile = await checkCommandType(String(item.commandId));
|
|
||||||
const _null: any = null;
|
|
||||||
if (clearProfile.status) {
|
|
||||||
// Keycloak deleteUser ทำภายใน transaction (preserve behavior เดิม — Keycloak ไม่ rollback ได้)
|
|
||||||
if (_profile.keycloak != null && _profile.keycloak != "" && _profile.isDelete === false) {
|
|
||||||
const delUserKeycloak = await deleteUser(_profile.keycloak);
|
|
||||||
if (delUserKeycloak) {
|
|
||||||
// Task #228
|
|
||||||
_profile.roleKeycloaks = [];
|
|
||||||
_profile.isActive = false;
|
|
||||||
_profile.isDelete = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
_profile.leaveCommandId = item.commandId ?? _null;
|
|
||||||
_profile.leaveCommandNo = `${item.commandNo}/${_commandYear}`;
|
|
||||||
_profile.leaveRemark = clearProfile.leaveRemark ?? _null;
|
|
||||||
_profile.leaveDate = item.commandDateAffect ?? _null;
|
|
||||||
_profile.leaveType = clearProfile.LeaveType ?? _null;
|
|
||||||
//ออกจากราชการ ไม่ต้องลบตำแหน่งในทะเบียน (issue #1516)
|
|
||||||
// _profile.position = _null;
|
|
||||||
// _profile.posTypeId = _null;
|
|
||||||
// _profile.posLevelId = _null;
|
|
||||||
}
|
|
||||||
await Promise.all([
|
|
||||||
profileRepository.save(_profile),
|
|
||||||
salaryRepo.save(profileSalary),
|
|
||||||
]);
|
|
||||||
|
|
||||||
const history = new ProfileSalaryHistory();
|
|
||||||
Object.assign(history, { ...profileSalary, id: undefined });
|
|
||||||
history.profileSalaryId = profileSalary.id;
|
|
||||||
await salaryHistoryRepo.save(history);
|
|
||||||
|
|
||||||
console.log(
|
|
||||||
`[ExecuteSalaryProbationService] processOneProbationLeave done — profileId: ${item.profileId}`,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,318 +0,0 @@
|
||||||
import { EntityManager, Repository } from "typeorm";
|
|
||||||
import { AppDataSource } from "../database/data-source";
|
|
||||||
import HttpError from "../interfaces/http-error";
|
|
||||||
import HttpStatusCode from "../interfaces/http-status";
|
|
||||||
import permission from "../interfaces/permission";
|
|
||||||
import { setLogDataDiff } from "../interfaces/utils";
|
|
||||||
import { Profile } from "../entities/Profile";
|
|
||||||
import { ProfileEmployee } from "../entities/ProfileEmployee";
|
|
||||||
import {
|
|
||||||
CreateProfileSalary,
|
|
||||||
CreateProfileSalaryEmployee,
|
|
||||||
ProfileSalary,
|
|
||||||
} from "../entities/ProfileSalary";
|
|
||||||
import { ProfileSalaryHistory } from "../entities/ProfileSalaryHistory";
|
|
||||||
import { Command } from "../entities/Command";
|
|
||||||
import { OrgRoot } from "../entities/OrgRoot";
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Context สำหรับ audit/log (เหมือน ExecuteSalaryService)
|
|
||||||
*/
|
|
||||||
export interface SalaryReportExecutionContext {
|
|
||||||
user: { sub: string; name: string };
|
|
||||||
req?: any;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Service สำหรับคำสั่งเงินเดือนที่ยิงมาจาก salary service
|
|
||||||
*
|
|
||||||
* - C-PM-33, C-PM-34, C-PM-35, C-PM-45 : officer → /org/profile/salary/update
|
|
||||||
* - C-PM-36, C-PM-37, C-PM-46 : employee → /org/profile-employee/salary/update
|
|
||||||
*
|
|
||||||
* เดิมเป็น circular callback: org AMQ → salary service → PostData("/org/profile/(employee/)salary/update")
|
|
||||||
* หลัง Linear Flow: org AMQ → salary service (return salary data) → เรียก service นี้โดยตรง (no callback)
|
|
||||||
*
|
|
||||||
* - executeOfficerSalaryUpdate : สร้าง ProfileSalary + history + อัปเดต Profile (amount*)
|
|
||||||
* - executeEmployeeSalaryUpdate : สร้าง ProfileSalary + history + อัปเดต ProfileEmployee (amount* + salaryLevel/group)
|
|
||||||
*
|
|
||||||
* Behavior ทั้งหมด preserve จาก
|
|
||||||
* ProfileSalaryController.updateSalary / ProfileSalaryEmployeeController.updateSalary ต้นฉบับ
|
|
||||||
* (รวม permission check + setLogDataDiff + save({data: req}))
|
|
||||||
*
|
|
||||||
* Batch semantics: all-or-nothing — transaction เดียวครอบทั้ง batch
|
|
||||||
* ถ้าคนใด throw (validation/permission) จะ rollback ทั้ง batch และ propagate error
|
|
||||||
*
|
|
||||||
* ⚠️ หมายเหตุ permission check: ทำ per-item ภายใน transaction (preserve behavior เดิมที่เช็คทุกคน)
|
|
||||||
* ทำ HTTP loopback ไป /org/permission/user/... ด้วย token ใน ctx.req — หาก batch ใหญ่อาจช้า
|
|
||||||
* (เหมือนเดิม เพราะ salary เดิมก็ยิงเข้า endpoint ทีละคนพร้อม permission check)
|
|
||||||
*/
|
|
||||||
export class ExecuteSalaryReportService {
|
|
||||||
private commandRepository = AppDataSource.getRepository(Command);
|
|
||||||
private profileRepository = AppDataSource.getRepository(Profile);
|
|
||||||
private profileEmployeeRepository = AppDataSource.getRepository(ProfileEmployee);
|
|
||||||
private orgRootRepository = AppDataSource.getRepository(OrgRoot);
|
|
||||||
|
|
||||||
// ─────────────────────────────────────────────────────────────
|
|
||||||
// resolve _posNumCodeSit/Abb จาก command (ทำครั้งเดียวก่อนเข้า transaction)
|
|
||||||
// admin-lookup ใช้ Profile (officer: profileRepo / employee: profileGovementRepo — ทั้งคู่คือ Profile)
|
|
||||||
// ─────────────────────────────────────────────────────────────
|
|
||||||
private async resolvePosNumCodeSit(
|
|
||||||
commandId: string | null | undefined,
|
|
||||||
): Promise<{ posNumCodeSit: string; posNumCodeSitAbb: string }> {
|
|
||||||
let posNumCodeSit = "";
|
|
||||||
let posNumCodeSitAbb = "";
|
|
||||||
const command = commandId
|
|
||||||
? await this.commandRepository.findOne({ where: { id: commandId } })
|
|
||||||
: null;
|
|
||||||
if (command) {
|
|
||||||
if (command?.isBangkok?.toLocaleUpperCase() == "OFFICE") {
|
|
||||||
const orgRootDeputy = await this.orgRootRepository.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 {
|
|
||||||
const profileAdmin = await this.profileRepository.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 ?? "";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return { posNumCodeSit, posNumCodeSitAbb };
|
|
||||||
}
|
|
||||||
|
|
||||||
// ═══════════════════════════════════════════════════════════════
|
|
||||||
// C-PM-33/34/35/45 : officer salary update
|
|
||||||
// ═══════════════════════════════════════════════════════════════
|
|
||||||
async executeOfficerSalaryUpdate(
|
|
||||||
data: CreateProfileSalary[],
|
|
||||||
ctx: SalaryReportExecutionContext,
|
|
||||||
): Promise<void> {
|
|
||||||
const commandId = data?.find((x) => x.commandId)?.commandId ?? "";
|
|
||||||
console.log(
|
|
||||||
`[ExecuteSalaryReportService] executeOfficerSalaryUpdate — commandId: ${commandId}, count: ${data?.length ?? 0}`,
|
|
||||||
);
|
|
||||||
|
|
||||||
const { posNumCodeSit: _posNumCodeSit, posNumCodeSitAbb: _posNumCodeSitAbb } =
|
|
||||||
await this.resolvePosNumCodeSit(commandId);
|
|
||||||
|
|
||||||
await AppDataSource.transaction(async (manager) => {
|
|
||||||
const profileRepository = manager.getRepository(Profile);
|
|
||||||
const salaryRepo = manager.getRepository(ProfileSalary);
|
|
||||||
const salaryHistoryRepo = manager.getRepository(ProfileSalaryHistory);
|
|
||||||
|
|
||||||
for (const item of data ?? []) {
|
|
||||||
try {
|
|
||||||
await this.processOneOfficer(
|
|
||||||
item,
|
|
||||||
ctx,
|
|
||||||
_posNumCodeSit,
|
|
||||||
_posNumCodeSitAbb,
|
|
||||||
profileRepository,
|
|
||||||
salaryRepo,
|
|
||||||
salaryHistoryRepo,
|
|
||||||
);
|
|
||||||
} catch (err) {
|
|
||||||
const reason =
|
|
||||||
err instanceof HttpError
|
|
||||||
? err.message
|
|
||||||
: err instanceof Error
|
|
||||||
? err.message
|
|
||||||
: "unexpected error";
|
|
||||||
console.error(
|
|
||||||
`[ExecuteSalaryReportService] Failed officer, commandId=${commandId}, profileId=${item.profileId}: ${reason}`,
|
|
||||||
err,
|
|
||||||
);
|
|
||||||
throw err; // → rollback ทั้ง transaction + propagate เป็น batch failure
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
console.log(`[ExecuteSalaryReportService] Completed officer — ${data?.length ?? 0} items`);
|
|
||||||
}
|
|
||||||
|
|
||||||
private async processOneOfficer(
|
|
||||||
item: CreateProfileSalary,
|
|
||||||
ctx: SalaryReportExecutionContext,
|
|
||||||
_posNumCodeSit: string,
|
|
||||||
_posNumCodeSitAbb: string,
|
|
||||||
profileRepository: Repository<Profile>,
|
|
||||||
salaryRepo: Repository<ProfileSalary>,
|
|
||||||
salaryHistoryRepo: Repository<ProfileSalaryHistory>,
|
|
||||||
): Promise<void> {
|
|
||||||
const req = ctx.req;
|
|
||||||
|
|
||||||
if (!item.profileId) {
|
|
||||||
throw new HttpError(HttpStatusCode.BAD_REQUEST, "กรุณากรอก profileId");
|
|
||||||
}
|
|
||||||
const profile = await profileRepository.findOneBy({ id: item.profileId });
|
|
||||||
if (!profile) {
|
|
||||||
throw new HttpError(HttpStatusCode.BAD_REQUEST, "ไม่พบ profile ดังกล่าว");
|
|
||||||
}
|
|
||||||
await new permission().PermissionOrgUserUpdate(req, "SYS_REGISTRY_OFFICER", profile.id);
|
|
||||||
|
|
||||||
const dest_item = await salaryRepo.findOne({
|
|
||||||
where: { profileId: item.profileId },
|
|
||||||
order: { order: "DESC" },
|
|
||||||
});
|
|
||||||
const before = null;
|
|
||||||
const data = new ProfileSalary();
|
|
||||||
data.posNumCodeSit = _posNumCodeSit;
|
|
||||||
data.posNumCodeSitAbb = _posNumCodeSitAbb;
|
|
||||||
const meta = {
|
|
||||||
order: dest_item == null ? 1 : dest_item.order + 1,
|
|
||||||
createdUserId: ctx.user.sub,
|
|
||||||
createdFullName: ctx.user.name,
|
|
||||||
lastUpdateUserId: ctx.user.sub,
|
|
||||||
lastUpdateFullName: ctx.user.name,
|
|
||||||
createdAt: new Date(),
|
|
||||||
lastUpdatedAt: new Date(),
|
|
||||||
};
|
|
||||||
|
|
||||||
Object.assign(data, { ...item, ...meta });
|
|
||||||
const history = new ProfileSalaryHistory();
|
|
||||||
Object.assign(history, { ...data, id: undefined });
|
|
||||||
await salaryRepo.save(data, { data: req });
|
|
||||||
setLogDataDiff(req, { before, after: data });
|
|
||||||
history.profileSalaryId = data.id;
|
|
||||||
await salaryHistoryRepo.save(history, { data: req });
|
|
||||||
|
|
||||||
const _null: any = null;
|
|
||||||
profile.amount = item.amount ?? _null;
|
|
||||||
profile.amountSpecial = item.amountSpecial ?? _null;
|
|
||||||
profile.positionSalaryAmount = item.positionSalaryAmount ?? _null;
|
|
||||||
profile.mouthSalaryAmount = item.mouthSalaryAmount ?? _null;
|
|
||||||
await profileRepository.save(profile);
|
|
||||||
}
|
|
||||||
|
|
||||||
// ═══════════════════════════════════════════════════════════════
|
|
||||||
// C-PM-36/37/46 : employee salary update
|
|
||||||
// ═══════════════════════════════════════════════════════════════
|
|
||||||
async executeEmployeeSalaryUpdate(
|
|
||||||
data: CreateProfileSalaryEmployee[],
|
|
||||||
ctx: SalaryReportExecutionContext,
|
|
||||||
): Promise<void> {
|
|
||||||
const commandId = data?.find((x) => x.commandId)?.commandId ?? "";
|
|
||||||
console.log(
|
|
||||||
`[ExecuteSalaryReportService] executeEmployeeSalaryUpdate — commandId: ${commandId}, count: ${data?.length ?? 0}`,
|
|
||||||
);
|
|
||||||
|
|
||||||
const { posNumCodeSit: _posNumCodeSit, posNumCodeSitAbb: _posNumCodeSitAbb } =
|
|
||||||
await this.resolvePosNumCodeSit(commandId);
|
|
||||||
|
|
||||||
await AppDataSource.transaction(async (manager) => {
|
|
||||||
const profileEmployeeRepository = manager.getRepository(ProfileEmployee);
|
|
||||||
const salaryRepo = manager.getRepository(ProfileSalary);
|
|
||||||
const salaryHistoryRepo = manager.getRepository(ProfileSalaryHistory);
|
|
||||||
|
|
||||||
for (const item of data ?? []) {
|
|
||||||
try {
|
|
||||||
await this.processOneEmployee(
|
|
||||||
item,
|
|
||||||
ctx,
|
|
||||||
_posNumCodeSit,
|
|
||||||
_posNumCodeSitAbb,
|
|
||||||
profileEmployeeRepository,
|
|
||||||
salaryRepo,
|
|
||||||
salaryHistoryRepo,
|
|
||||||
);
|
|
||||||
} catch (err) {
|
|
||||||
const reason =
|
|
||||||
err instanceof HttpError
|
|
||||||
? err.message
|
|
||||||
: err instanceof Error
|
|
||||||
? err.message
|
|
||||||
: "unexpected error";
|
|
||||||
console.error(
|
|
||||||
`[ExecuteSalaryReportService] Failed employee, commandId=${commandId}, profileEmployeeId=${item.profileEmployeeId}: ${reason}`,
|
|
||||||
err,
|
|
||||||
);
|
|
||||||
throw err; // → rollback ทั้ง transaction + propagate เป็น batch failure
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
console.log(`[ExecuteSalaryReportService] Completed employee — ${data?.length ?? 0} items`);
|
|
||||||
}
|
|
||||||
|
|
||||||
private async processOneEmployee(
|
|
||||||
item: CreateProfileSalaryEmployee,
|
|
||||||
ctx: SalaryReportExecutionContext,
|
|
||||||
_posNumCodeSit: string,
|
|
||||||
_posNumCodeSitAbb: string,
|
|
||||||
profileEmployeeRepository: Repository<ProfileEmployee>,
|
|
||||||
salaryRepo: Repository<ProfileSalary>,
|
|
||||||
salaryHistoryRepo: Repository<ProfileSalaryHistory>,
|
|
||||||
): Promise<void> {
|
|
||||||
const req = ctx.req;
|
|
||||||
|
|
||||||
if (!item.profileEmployeeId) {
|
|
||||||
throw new HttpError(HttpStatusCode.BAD_REQUEST, "กรุณากรอก profileEmployeeId");
|
|
||||||
}
|
|
||||||
const profile = await profileEmployeeRepository.findOneBy({ id: item.profileEmployeeId });
|
|
||||||
if (!profile) {
|
|
||||||
throw new HttpError(HttpStatusCode.BAD_REQUEST, "ไม่พบ profile ดังกล่าว");
|
|
||||||
}
|
|
||||||
await new permission().PermissionOrgUserUpdate(req, "SYS_REGISTRY_EMP", profile.id);
|
|
||||||
|
|
||||||
const dest_item = await salaryRepo.findOne({
|
|
||||||
where: { profileEmployeeId: item.profileEmployeeId },
|
|
||||||
order: { order: "DESC" },
|
|
||||||
});
|
|
||||||
const before = null;
|
|
||||||
const data = new ProfileSalary();
|
|
||||||
data.posNumCodeSit = _posNumCodeSit;
|
|
||||||
data.posNumCodeSitAbb = _posNumCodeSitAbb;
|
|
||||||
const meta = {
|
|
||||||
order: dest_item == null ? 1 : dest_item.order + 1,
|
|
||||||
createdUserId: ctx.user.sub,
|
|
||||||
createdFullName: ctx.user.name,
|
|
||||||
lastUpdateUserId: ctx.user.sub,
|
|
||||||
lastUpdateFullName: ctx.user.name,
|
|
||||||
createdAt: new Date(),
|
|
||||||
lastUpdatedAt: new Date(),
|
|
||||||
};
|
|
||||||
|
|
||||||
Object.assign(data, { ...item, ...meta });
|
|
||||||
const history = new ProfileSalaryHistory();
|
|
||||||
Object.assign(history, { ...data, id: undefined });
|
|
||||||
|
|
||||||
await salaryRepo.save(data, { data: req });
|
|
||||||
setLogDataDiff(req, { before, after: data });
|
|
||||||
history.profileSalaryId = data.id;
|
|
||||||
await salaryHistoryRepo.save(history, { data: req });
|
|
||||||
|
|
||||||
const _null: any = null;
|
|
||||||
profile.amount = item.amount ?? _null;
|
|
||||||
profile.amountSpecial = item.amountSpecial ?? _null;
|
|
||||||
profile.positionSalaryAmount = item.positionSalaryAmount ?? _null;
|
|
||||||
profile.mouthSalaryAmount = item.mouthSalaryAmount ?? _null;
|
|
||||||
profile.salaryLevel = item.salaryLevel ?? _null;
|
|
||||||
profile.group = item.group ?? _null;
|
|
||||||
await profileEmployeeRepository.save(profile);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,391 +0,0 @@
|
||||||
import { Double, EntityManager } from "typeorm";
|
|
||||||
import { AppDataSource } from "../database/data-source";
|
|
||||||
import HttpError from "../interfaces/http-error";
|
|
||||||
import HttpStatusCode from "../interfaces/http-status";
|
|
||||||
import { Profile } from "../entities/Profile";
|
|
||||||
import { ProfileSalary } from "../entities/ProfileSalary";
|
|
||||||
import { ProfileSalaryHistory } from "../entities/ProfileSalaryHistory";
|
|
||||||
import { ProfileAssistance } from "../entities/ProfileAssistance";
|
|
||||||
import { ProfileAssistanceHistory } from "../entities/ProfileAssistanceHistory";
|
|
||||||
import { OrgRoot } from "../entities/OrgRoot";
|
|
||||||
import { PosMaster } from "../entities/PosMaster";
|
|
||||||
import { Command } from "../entities/Command";
|
|
||||||
import {
|
|
||||||
checkCommandType,
|
|
||||||
removePostMasterAct,
|
|
||||||
removeProfileInOrganize,
|
|
||||||
setLogDataDiff,
|
|
||||||
} from "../interfaces/utils";
|
|
||||||
import { CreatePosMasterHistoryOfficer } from "./PositionService";
|
|
||||||
import { deleteUser } from "../keycloak";
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Input: ข้อมูล 1 คนสำหรับ endpoint excexute/salary
|
|
||||||
* (C-PM-13 โอน, C-PM-15 ช่วยราชการ, C-PM-16 เกษียณ/ปลดเกษียณ)
|
|
||||||
*/
|
|
||||||
export interface SalaryItem {
|
|
||||||
profileId: string;
|
|
||||||
amount?: Double | null;
|
|
||||||
amountSpecial?: Double | null;
|
|
||||||
positionSalaryAmount?: Double | null;
|
|
||||||
mouthSalaryAmount?: Double | null;
|
|
||||||
positionExecutive: string | null;
|
|
||||||
positionExecutiveField?: string | null;
|
|
||||||
positionArea?: string | null;
|
|
||||||
positionType: string | null;
|
|
||||||
positionLevel: string | null;
|
|
||||||
commandId?: string | null;
|
|
||||||
leaveReason?: string | null;
|
|
||||||
dateLeave?: Date | string | null;
|
|
||||||
isLeave?: boolean;
|
|
||||||
orgRoot?: string | null;
|
|
||||||
orgChild1?: string | null;
|
|
||||||
orgChild2?: string | null;
|
|
||||||
orgChild3?: string | null;
|
|
||||||
orgChild4?: string | null;
|
|
||||||
officerOrg?: string | null;
|
|
||||||
dateStart?: Date | string | null;
|
|
||||||
dateEnd?: Date | string | null;
|
|
||||||
commandNo: string | null;
|
|
||||||
commandYear: number | null;
|
|
||||||
posNo: string | null;
|
|
||||||
posNoAbb: string | null;
|
|
||||||
commandDateAffect?: Date | string | null;
|
|
||||||
commandDateSign?: Date | string | null;
|
|
||||||
positionName: string | null;
|
|
||||||
commandCode?: string | null;
|
|
||||||
commandName?: string | null;
|
|
||||||
remark: string | null;
|
|
||||||
refId?: string | null;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Context สำหรับ audit/log (เหมือน ExecuteOfficerProfileService)
|
|
||||||
*/
|
|
||||||
export interface SalaryExecutionContext {
|
|
||||||
user: { sub: string; name: string };
|
|
||||||
req?: any;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Service สำหรับสร้าง ProfileSalary ของข้าราชการ + handle leave/ออกจากราชการ/ช่วยราชการ
|
|
||||||
*
|
|
||||||
* ใช้กับ commandType: C-PM-13, 15, 16
|
|
||||||
*
|
|
||||||
* - endpoint /org/command/excexute/salary เรียกผ่าน service นี้ (thin wrapper)
|
|
||||||
* - consumer ใน rabbitmq handler เรียกผ่าน service นี้โดยตรง (Linear Flow)
|
|
||||||
*
|
|
||||||
* Behavior ทั้งหมด preserve จาก CommandController.newSalaryAndUpdate ต้นฉบับ
|
|
||||||
*
|
|
||||||
* Batch semantics: all-or-nothing — ประมวลผลทุกคนภายใต้ transaction เดียว (sequential)
|
|
||||||
* ถ้าคนใด throw จะ rollback ทั้ง batch และ propagate error ออกไป (ล้มเหลวทั้งหมด)
|
|
||||||
* ถ้าทุกคนสำเร็จจะ return result รายงาน success count
|
|
||||||
*
|
|
||||||
* ⚠️ หมายเหตุ Keycloak: operation (deleteUser) ทำภายใน transaction เพื่อ preserve behavior
|
|
||||||
* เดิม — Keycloak ไม่สามารถ rollback ได้ ถ้า DB rollback หลังจาก Keycloak operation สำเร็จ
|
|
||||||
* → Keycloak จะถูกเปลี่ยนไปแล้ว
|
|
||||||
*/
|
|
||||||
export class ExecuteSalaryService {
|
|
||||||
private commandRepository = AppDataSource.getRepository(Command);
|
|
||||||
private profileRepository = AppDataSource.getRepository(Profile);
|
|
||||||
private orgRootRepository = AppDataSource.getRepository(OrgRoot);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* ประมวลผลสร้าง ProfileSalary + handle leave/assistance ทั้ง batch
|
|
||||||
*
|
|
||||||
* @returns สรุปผล success/failure ต่อคน
|
|
||||||
*/
|
|
||||||
async executeSalary(data: SalaryItem[], ctx: SalaryExecutionContext): Promise<void> {
|
|
||||||
const commandId = data?.find((x) => x.commandId)?.commandId ?? "unknown";
|
|
||||||
const commandCode = data?.find((x) => x.commandCode)?.commandCode ?? "unknown";
|
|
||||||
console.log(
|
|
||||||
`[ExecuteSalaryService] Starting executeSalary — commandCode: ${commandCode}, commandId: ${commandId}`,
|
|
||||||
);
|
|
||||||
console.log(`[ExecuteSalaryService] Request body count: ${data?.length ?? 0}`);
|
|
||||||
|
|
||||||
// ─────────────────────────────────────────────────────────────
|
|
||||||
// Normalize date fields (ผ่าน handler จะได้ string → ต้องแปลงเป็น Date)
|
|
||||||
// ─────────────────────────────────────────────────────────────
|
|
||||||
const toDate = (v: any): Date | null => {
|
|
||||||
if (v == null || v === "") return null;
|
|
||||||
if (v instanceof Date) return isNaN(v.getTime()) ? null : v;
|
|
||||||
const d = new Date(v);
|
|
||||||
return isNaN(d.getTime()) ? null : d;
|
|
||||||
};
|
|
||||||
for (const item of data ?? []) {
|
|
||||||
const it = item as any;
|
|
||||||
it.dateLeave = toDate(it.dateLeave);
|
|
||||||
it.dateStart = toDate(it.dateStart);
|
|
||||||
it.dateEnd = toDate(it.dateEnd);
|
|
||||||
it.commandDateAffect = toDate(it.commandDateAffect);
|
|
||||||
it.commandDateSign = toDate(it.commandDateSign);
|
|
||||||
}
|
|
||||||
|
|
||||||
let _posNumCodeSit: string = "";
|
|
||||||
let _posNumCodeSitAbb: string = "";
|
|
||||||
const _command = await this.commandRepository.findOne({
|
|
||||||
relations: ["commandType"],
|
|
||||||
where: { id: data.find((x) => x.commandId)?.commandId ?? "" },
|
|
||||||
});
|
|
||||||
if (_command) {
|
|
||||||
if (_command?.isBangkok?.toLocaleUpperCase() == "OFFICE") {
|
|
||||||
const orgRootDeputy = await this.orgRootRepository.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 this.profileRepository.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 ?? "";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// ─────────────────────────────────────────────────────────────
|
|
||||||
// Single transaction ครอบทั้ง batch (all-or-nothing)
|
|
||||||
// ทุกคนใช้ manager ตัวเดียวกัน — คนใด throw จะ rollback ทั้ง batch
|
|
||||||
// และ propagate error ออกไป (ล้มเหลวทั้งหมด) โดย log error ของคนที่ทำให้ fail ก่อน rethrow
|
|
||||||
// ─────────────────────────────────────────────────────────────
|
|
||||||
let successCount = 0;
|
|
||||||
await AppDataSource.transaction(async (manager) => {
|
|
||||||
for (const item of data ?? []) {
|
|
||||||
try {
|
|
||||||
await this.processOne(item, ctx, manager, _command, _posNumCodeSit, _posNumCodeSitAbb);
|
|
||||||
} catch (err) {
|
|
||||||
const reason =
|
|
||||||
err instanceof HttpError
|
|
||||||
? err.message
|
|
||||||
: err instanceof Error
|
|
||||||
? err.message
|
|
||||||
: "unexpected error";
|
|
||||||
console.error(
|
|
||||||
`[ExecuteSalaryService] Failed commandCode=${commandCode}, commandId=${commandId}, profileId=${item.profileId}: ${reason}`,
|
|
||||||
err,
|
|
||||||
);
|
|
||||||
throw err; // → rollback ทั้ง transaction + propagate เป็น batch failure
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* ประมวลผล 1 คน ภายใน transaction เดียว (manager)
|
|
||||||
* ทุก save ใช้ manager.getRepository(...) เพื่อให้อยู่ใน transaction เดียวกัน
|
|
||||||
* ถ้า throw ระหว่างทาง → rollback ทั้งหมดของคนนี้ + ทั้ง batch (กัน partial commit)
|
|
||||||
*
|
|
||||||
* หมายเหตุ: Keycloak deleteUser ทำก่อนเข้า transaction เพราะไม่สามารถ rollback ได้
|
|
||||||
*/
|
|
||||||
private async processOne(
|
|
||||||
item: SalaryItem,
|
|
||||||
ctx: SalaryExecutionContext,
|
|
||||||
manager: EntityManager,
|
|
||||||
_command: Command | null,
|
|
||||||
_posNumCodeSit: string,
|
|
||||||
_posNumCodeSitAbb: string,
|
|
||||||
): Promise<void> {
|
|
||||||
const req = ctx.req;
|
|
||||||
|
|
||||||
const profileRepository = manager.getRepository(Profile);
|
|
||||||
const salaryRepo = manager.getRepository(ProfileSalary);
|
|
||||||
const salaryHistoryRepo = manager.getRepository(ProfileSalaryHistory);
|
|
||||||
const posMasterRepository = manager.getRepository(PosMaster);
|
|
||||||
const assistanceRepository = manager.getRepository(ProfileAssistance);
|
|
||||||
const assistanceHistoryRepository = manager.getRepository(ProfileAssistanceHistory);
|
|
||||||
|
|
||||||
const profile: any = await profileRepository.findOne({
|
|
||||||
where: { id: item.profileId },
|
|
||||||
relations: {
|
|
||||||
roleKeycloaks: true,
|
|
||||||
posType: true,
|
|
||||||
posLevel: true,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
if (!profile) {
|
|
||||||
throw new HttpError(HttpStatusCode.NOT_FOUND, "ไม่พบข้อมูลทะเบียนประวัตินี้");
|
|
||||||
}
|
|
||||||
const posMaster: any = await posMasterRepository.findOne({
|
|
||||||
where: {
|
|
||||||
current_holderId: item.profileId,
|
|
||||||
orgRevision: {
|
|
||||||
orgRevisionIsCurrent: true,
|
|
||||||
orgRevisionIsDraft: false,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
relations: {
|
|
||||||
orgRevision: true,
|
|
||||||
orgRoot: true,
|
|
||||||
orgChild1: true,
|
|
||||||
orgChild2: true,
|
|
||||||
orgChild3: true,
|
|
||||||
orgChild4: true,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
const orgRevisionRef = posMaster ? posMaster.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 code = _command?.commandType?.code;
|
|
||||||
if (code && ["C-PM-13"].includes(code)) {
|
|
||||||
// await (เดิมไม่ await = fire-and-forget bug) + ส่ง manager เข้าไปเพื่อให้อยู่ใน transaction
|
|
||||||
await removePostMasterAct(profile.id, manager);
|
|
||||||
}
|
|
||||||
|
|
||||||
let _commandYear = item.commandYear;
|
|
||||||
if (item.commandYear) {
|
|
||||||
_commandYear = item.commandYear > 2500 ? item.commandYear : item.commandYear + 543;
|
|
||||||
}
|
|
||||||
const dest_item = await salaryRepo.findOne({
|
|
||||||
where: { profileId: item.profileId },
|
|
||||||
order: { order: "DESC" },
|
|
||||||
});
|
|
||||||
const before = null;
|
|
||||||
const dataSalary = new ProfileSalary();
|
|
||||||
dataSalary.posNumCodeSit = _posNumCodeSit;
|
|
||||||
dataSalary.posNumCodeSitAbb = _posNumCodeSitAbb;
|
|
||||||
const meta = {
|
|
||||||
order: dest_item == null ? 1 : dest_item.order + 1,
|
|
||||||
createdUserId: ctx.user.sub,
|
|
||||||
createdFullName: ctx.user.name,
|
|
||||||
lastUpdateUserId: ctx.user.sub,
|
|
||||||
lastUpdateFullName: ctx.user.name,
|
|
||||||
createdAt: new Date(),
|
|
||||||
lastUpdatedAt: new Date(),
|
|
||||||
};
|
|
||||||
if (item.isLeave != undefined && item.isLeave == true) {
|
|
||||||
console.log(
|
|
||||||
`[ExecuteSalaryService] Creating PosMasterHistory — posMasterId: ${orgRevisionRef}, profileId: ${item.profileId}, type: DELETE`,
|
|
||||||
);
|
|
||||||
await CreatePosMasterHistoryOfficer(orgRevisionRef, req, "DELETE", null, manager);
|
|
||||||
await removeProfileInOrganize(profile.id, "OFFICER", manager);
|
|
||||||
}
|
|
||||||
const clearProfile = await checkCommandType(String(item.commandId));
|
|
||||||
const _null: any = null;
|
|
||||||
if (clearProfile.status) {
|
|
||||||
// Keycloak deleteUser ทำก่อนเข้า transaction-bound save ด้านล่าง
|
|
||||||
// (ทำภายใน transaction เดียวกัน เพราะถ้า fail ต้อง rollback DB ด้วย)
|
|
||||||
// หมายเหตุ: Keycloak ไม่สามารถ rollback ได้ → ถ้า DB rollback หลังจากนี้ Keycloak จะถูกลบไปแล้ว
|
|
||||||
if (profile.keycloak != null && profile.keycloak != "" && profile.isDelete === false) {
|
|
||||||
const delUserKeycloak = await deleteUser(profile.keycloak);
|
|
||||||
if (delUserKeycloak) {
|
|
||||||
// Task #228
|
|
||||||
// profile.keycloak = _null;
|
|
||||||
profile.roleKeycloaks = [];
|
|
||||||
profile.isActive = false;
|
|
||||||
profile.isDelete = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
profile.isLeave = item.isLeave;
|
|
||||||
profile.leaveCommandId = item.commandId ?? _null;
|
|
||||||
profile.leaveCommandNo = `${item.commandNo}/${_commandYear}`;
|
|
||||||
profile.leaveRemark = clearProfile.leaveRemark ?? _null;
|
|
||||||
profile.leaveDate = item.commandDateAffect ?? _null;
|
|
||||||
profile.leaveType = clearProfile.LeaveType ?? _null;
|
|
||||||
//ออกจากราชการ ไม่ต้องลบตำแหน่งในทะเบียน (issue #1516)
|
|
||||||
// profile.position = _null;
|
|
||||||
// profile.posTypeId = _null;
|
|
||||||
// profile.posLevelId = _null;
|
|
||||||
profile.leaveReason = item.leaveReason ?? _null;
|
|
||||||
profile.dateLeave = item.dateLeave ?? _null;
|
|
||||||
profile.amount = item.amount ?? _null;
|
|
||||||
profile.amountSpecial = item.amountSpecial ?? _null;
|
|
||||||
await profileRepository.save(profile, { data: req });
|
|
||||||
|
|
||||||
// if (profile.id) {
|
|
||||||
// await this.keycloakAttributeService.clearOrgDnaAttributes(
|
|
||||||
// [profile.id],
|
|
||||||
// "PROFILE",
|
|
||||||
// );
|
|
||||||
// }
|
|
||||||
}
|
|
||||||
Object.assign(dataSalary, { ...item, ...meta });
|
|
||||||
const history = new ProfileSalaryHistory();
|
|
||||||
Object.assign(history, { ...dataSalary, id: undefined });
|
|
||||||
|
|
||||||
await salaryRepo.save(dataSalary, { data: req });
|
|
||||||
setLogDataDiff(req, { before, after: dataSalary });
|
|
||||||
history.profileSalaryId = dataSalary.id;
|
|
||||||
await salaryHistoryRepo.save(history, { data: req });
|
|
||||||
|
|
||||||
if (_command) {
|
|
||||||
if (["C-PM-15", "C-PM-16"].includes(_command.commandType.code)) {
|
|
||||||
// ประวัติคำสั่งให้ช่วยราชการ
|
|
||||||
const dataAssis = new ProfileAssistance();
|
|
||||||
|
|
||||||
const metaAssis = {
|
|
||||||
profileId: item.profileId,
|
|
||||||
agency: item.officerOrg,
|
|
||||||
dateStart: item.dateStart,
|
|
||||||
dateEnd: item.dateEnd,
|
|
||||||
commandNo: `${item.commandNo}/${_commandYear}`,
|
|
||||||
commandName: item.commandName,
|
|
||||||
refId: item.refId,
|
|
||||||
refCommandDate: new Date(),
|
|
||||||
commandId: item.commandId,
|
|
||||||
createdUserId: ctx.user.sub,
|
|
||||||
createdFullName: ctx.user.name,
|
|
||||||
lastUpdateUserId: ctx.user.sub,
|
|
||||||
lastUpdateFullName: ctx.user.name,
|
|
||||||
createdAt: new Date(),
|
|
||||||
lastUpdatedAt: new Date(),
|
|
||||||
status: _command.commandType.code == "C-PM-15" ? "PENDING" : "DONE",
|
|
||||||
};
|
|
||||||
|
|
||||||
Object.assign(dataAssis, metaAssis);
|
|
||||||
const historyAssis = new ProfileAssistanceHistory();
|
|
||||||
Object.assign(historyAssis, { ...dataAssis, id: undefined });
|
|
||||||
|
|
||||||
await assistanceRepository.save(dataAssis);
|
|
||||||
historyAssis.profileAssistanceId = dataAssis.id;
|
|
||||||
await assistanceHistoryRepository.save(historyAssis);
|
|
||||||
}
|
|
||||||
// Task #2190
|
|
||||||
else if (_command.commandType.code == "C-PM-13") {
|
|
||||||
let organizeName = "";
|
|
||||||
if (orgRootRef) {
|
|
||||||
const names = [
|
|
||||||
orgChild4Ref?.orgChild4Name,
|
|
||||||
orgChild3Ref?.orgChild3Name,
|
|
||||||
orgChild2Ref?.orgChild2Name,
|
|
||||||
orgChild1Ref?.orgChild1Name,
|
|
||||||
orgRootRef?.orgRootName,
|
|
||||||
].filter(Boolean);
|
|
||||||
organizeName = names.join(" ");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
console.log(
|
|
||||||
`[ExecuteSalaryService] Completed processOne — profileId: ${item.profileId}`,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -11,8 +11,6 @@ import { PosMasterHistory } from "../entities/PosMasterHistory";
|
||||||
import { Position } from "../entities/Position";
|
import { Position } from "../entities/Position";
|
||||||
import { ProfileEducation } from "../entities/ProfileEducation";
|
import { ProfileEducation } from "../entities/ProfileEducation";
|
||||||
import { RequestWithUser } from "../middlewares/user";
|
import { RequestWithUser } from "../middlewares/user";
|
||||||
import { Profile } from "../entities/Profile";
|
|
||||||
import { ProfileEmployee } from "../entities/ProfileEmployee";
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* function สำหรับดึงตำแหน่งที่รักษาการแทน
|
* function สำหรับดึงตำแหน่งที่รักษาการแทน
|
||||||
|
|
@ -20,7 +18,9 @@ import { ProfileEmployee } from "../entities/ProfileEmployee";
|
||||||
* - ถ้า posType = "อำนวยการ" หรือ "บริหาร" ใช้ posExecutiveName
|
* - ถ้า posType = "อำนวยการ" หรือ "บริหาร" ใช้ posExecutiveName
|
||||||
* - ถ้า posType อื่นๆ ใช้ positionName + posLevel
|
* - ถ้า posType อื่นๆ ใช้ positionName + posLevel
|
||||||
*/
|
*/
|
||||||
export async function getPosMasterPositions(posMasterIds: string[]): Promise<Map<string, string>> {
|
export async function getPosMasterPositions(
|
||||||
|
posMasterIds: string[]
|
||||||
|
): Promise<Map<string, string>> {
|
||||||
if (posMasterIds.length === 0) {
|
if (posMasterIds.length === 0) {
|
||||||
return new Map();
|
return new Map();
|
||||||
}
|
}
|
||||||
|
|
@ -61,9 +61,7 @@ export async function getPosMasterPositions(posMasterIds: string[]): Promise<Map
|
||||||
let positionText = "";
|
let positionText = "";
|
||||||
|
|
||||||
if (posTypeName === "อำนวยการ" || posTypeName === "บริหาร") {
|
if (posTypeName === "อำนวยการ" || posTypeName === "บริหาร") {
|
||||||
positionText =
|
positionText = pos.posExecutive?.posExecutiveName || `${pos.positionName || ""}ระดับ${pos.posLevel?.posLevelName || ""}`.trim();
|
||||||
pos.posExecutive?.posExecutiveName ||
|
|
||||||
`${pos.positionName || ""}ระดับ${pos.posLevel?.posLevelName || ""}`.trim();
|
|
||||||
} else {
|
} else {
|
||||||
positionText = `${pos.positionName || ""}${pos.posLevel?.posLevelName || ""}`.trim();
|
positionText = `${pos.positionName || ""}${pos.posLevel?.posLevelName || ""}`.trim();
|
||||||
}
|
}
|
||||||
|
|
@ -74,6 +72,7 @@ export async function getPosMasterPositions(posMasterIds: string[]): Promise<Map
|
||||||
return positionMap;
|
return positionMap;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
export async function CreatePosMasterHistoryOfficer(
|
export async function CreatePosMasterHistoryOfficer(
|
||||||
posMasterId: string,
|
posMasterId: string,
|
||||||
request: RequestWithUser | null,
|
request: RequestWithUser | null,
|
||||||
|
|
@ -86,7 +85,6 @@ export async function CreatePosMasterHistoryOfficer(
|
||||||
const repoHistory = transactionManager.getRepository(PosMasterHistory);
|
const repoHistory = transactionManager.getRepository(PosMasterHistory);
|
||||||
const repoOrgRevision = transactionManager.getRepository(OrgRevision);
|
const repoOrgRevision = transactionManager.getRepository(OrgRevision);
|
||||||
const repoPosition = transactionManager.getRepository(Position);
|
const repoPosition = transactionManager.getRepository(Position);
|
||||||
const repoProfile = transactionManager.getRepository(Profile);
|
|
||||||
|
|
||||||
const pm = await repoPosmaster.findOne({
|
const pm = await repoPosmaster.findOne({
|
||||||
where: { id: posMasterId },
|
where: { id: posMasterId },
|
||||||
|
|
@ -134,21 +132,6 @@ export async function CreatePosMasterHistoryOfficer(
|
||||||
: null;
|
: null;
|
||||||
}
|
}
|
||||||
|
|
||||||
let position = selectedPosition?.positionName ?? _null;
|
|
||||||
let posTypeName = selectedPosition?.posType?.posTypeName ?? _null;
|
|
||||||
let posLevelName = selectedPosition?.posLevel?.posLevelName ?? _null;
|
|
||||||
let posExecutiveName = selectedPosition?.posExecutive?.posExecutiveName ?? _null;
|
|
||||||
if (pm.isSit && pm.current_holderId) {
|
|
||||||
const profile = await repoProfile.findOne({
|
|
||||||
where: { id: pm.current_holderId },
|
|
||||||
relations: ["posType", "posLevel"],
|
|
||||||
});
|
|
||||||
position = profile?.position ?? _null;
|
|
||||||
posTypeName = profile?.posType?.posTypeName ?? _null;
|
|
||||||
posLevelName = profile?.posLevel?.posLevelName ?? _null;
|
|
||||||
posExecutiveName = profile?.posExecutive ?? _null;
|
|
||||||
}
|
|
||||||
|
|
||||||
h.ancestorDNA = pm.ancestorDNA ? pm.ancestorDNA : _null;
|
h.ancestorDNA = pm.ancestorDNA ? pm.ancestorDNA : _null;
|
||||||
if (!type || type != "DELETE") {
|
if (!type || type != "DELETE") {
|
||||||
if (checkCurrentRevision) {
|
if (checkCurrentRevision) {
|
||||||
|
|
@ -161,9 +144,9 @@ export async function CreatePosMasterHistoryOfficer(
|
||||||
h.firstName = pm.next_holder?.firstName || _null;
|
h.firstName = pm.next_holder?.firstName || _null;
|
||||||
h.lastName = pm.next_holder?.lastName || _null;
|
h.lastName = pm.next_holder?.lastName || _null;
|
||||||
}
|
}
|
||||||
h.position = position;
|
h.position = selectedPosition?.positionName ?? _null;
|
||||||
h.posType = posTypeName;
|
h.posType = selectedPosition?.posType?.posTypeName ?? _null;
|
||||||
h.posLevel = posLevelName;
|
h.posLevel = selectedPosition?.posLevel?.posLevelName ?? _null;
|
||||||
}
|
}
|
||||||
h.rootDnaId = pm.orgRoot?.ancestorDNA || _null;
|
h.rootDnaId = pm.orgRoot?.ancestorDNA || _null;
|
||||||
h.child1DnaId = pm.orgChild1?.ancestorDNA || _null;
|
h.child1DnaId = pm.orgChild1?.ancestorDNA || _null;
|
||||||
|
|
@ -173,7 +156,7 @@ export async function CreatePosMasterHistoryOfficer(
|
||||||
h.posMasterNoPrefix = pm.posMasterNoPrefix ?? _null;
|
h.posMasterNoPrefix = pm.posMasterNoPrefix ?? _null;
|
||||||
h.posMasterNo = pm.posMasterNo ?? _null;
|
h.posMasterNo = pm.posMasterNo ?? _null;
|
||||||
h.posMasterNoSuffix = pm.posMasterNoSuffix ?? _null;
|
h.posMasterNoSuffix = pm.posMasterNoSuffix ?? _null;
|
||||||
h.posExecutive = posExecutiveName;
|
h.posExecutive = selectedPosition?.posExecutive?.posExecutiveName ?? _null;
|
||||||
h.shortName =
|
h.shortName =
|
||||||
[
|
[
|
||||||
pm.orgChild4?.orgChild4ShortName,
|
pm.orgChild4?.orgChild4ShortName,
|
||||||
|
|
@ -217,12 +200,11 @@ export async function CreatePosMasterHistoryEmployee(
|
||||||
posMasterId: string,
|
posMasterId: string,
|
||||||
request: RequestWithUser | null,
|
request: RequestWithUser | null,
|
||||||
type?: string | null,
|
type?: string | null,
|
||||||
manager?: EntityManager,
|
|
||||||
): Promise<boolean> {
|
): Promise<boolean> {
|
||||||
const execute = async (transactionManager: EntityManager) => {
|
try {
|
||||||
const repoPosmaster = transactionManager.getRepository(EmployeePosMaster);
|
await AppDataSource.transaction(async (manager) => {
|
||||||
const repoHistory = transactionManager.getRepository(PosMasterEmployeeHistory);
|
const repoPosmaster = manager.getRepository(EmployeePosMaster);
|
||||||
const repoProfileEmployee = transactionManager.getRepository(ProfileEmployee);
|
const repoHistory = manager.getRepository(PosMasterEmployeeHistory);
|
||||||
|
|
||||||
const pm = await repoPosmaster.findOne({
|
const pm = await repoPosmaster.findOne({
|
||||||
where: { id: posMasterId },
|
where: { id: posMasterId },
|
||||||
|
|
@ -239,40 +221,23 @@ export async function CreatePosMasterHistoryEmployee(
|
||||||
"current_holder",
|
"current_holder",
|
||||||
],
|
],
|
||||||
});
|
});
|
||||||
if (!pm) return;
|
if (!pm) return false;
|
||||||
if (!pm.ancestorDNA) return;
|
if (!pm.ancestorDNA) return false;
|
||||||
const _null: any = null;
|
const _null: any = null;
|
||||||
const h = new PosMasterEmployeeHistory();
|
const h = new PosMasterEmployeeHistory();
|
||||||
const selectedPosition =
|
const selectedPosition =
|
||||||
pm.positions.length > 0
|
pm.positions.length > 0
|
||||||
? pm.positions.find((p) => p.positionIsSelected === true) ?? null
|
? pm.positions.find((p) => p.positionIsSelected === true) ?? null
|
||||||
: null;
|
: null;
|
||||||
|
|
||||||
let position = selectedPosition?.positionName ?? _null;
|
|
||||||
let posTypeName = selectedPosition?.posType?.posTypeName ?? _null;
|
|
||||||
let posLevelName = selectedPosition?.posType && selectedPosition?.posLevel
|
|
||||||
? `${selectedPosition?.posType?.posTypeShortName ?? ""} ${selectedPosition?.posLevel?.posLevelName ?? ""}`.trim()
|
|
||||||
: _null;
|
|
||||||
if (pm.isSit && pm.current_holderId) {
|
|
||||||
const profile = await repoProfileEmployee.findOne({
|
|
||||||
where: { id: pm.current_holderId },
|
|
||||||
relations: ["posType", "posLevel"]
|
|
||||||
});
|
|
||||||
position = profile?.position ?? _null;
|
|
||||||
posTypeName = profile?.posType?.posTypeName ?? _null;
|
|
||||||
posLevelName = profile?.posType && profile?.posLevel
|
|
||||||
? `${profile?.posType?.posTypeShortName ?? ""} ${profile?.posLevel?.posLevelName ?? ""}`.trim()
|
|
||||||
: _null;
|
|
||||||
}
|
|
||||||
h.ancestorDNA = pm.ancestorDNA;
|
h.ancestorDNA = pm.ancestorDNA;
|
||||||
if (!type || type != "DELETE") {
|
if (!type || type != "DELETE") {
|
||||||
h.profileEmployeeId = pm.current_holder?.id || _null;
|
h.profileEmployeeId = pm.current_holder?.id || _null;
|
||||||
h.prefix = pm.current_holder?.prefix || _null;
|
h.prefix = pm.current_holder?.prefix || _null;
|
||||||
h.firstName = pm.current_holder?.firstName || _null;
|
h.firstName = pm.current_holder?.firstName || _null;
|
||||||
h.lastName = pm.current_holder?.lastName || _null;
|
h.lastName = pm.current_holder?.lastName || _null;
|
||||||
h.position = position;
|
h.position = selectedPosition?.positionName ?? _null;
|
||||||
h.posType = posTypeName;
|
h.posType = selectedPosition?.posType?.posTypeName ?? _null;
|
||||||
h.posLevel = posLevelName;
|
h.posLevel = selectedPosition?.posLevel?.posLevelName ?? _null;
|
||||||
}
|
}
|
||||||
h.rootDnaId = pm.orgRoot?.ancestorDNA || _null;
|
h.rootDnaId = pm.orgRoot?.ancestorDNA || _null;
|
||||||
h.child1DnaId = pm.orgChild1?.ancestorDNA || _null;
|
h.child1DnaId = pm.orgChild1?.ancestorDNA || _null;
|
||||||
|
|
@ -299,23 +264,10 @@ export async function CreatePosMasterHistoryEmployee(
|
||||||
h.createdAt = new Date();
|
h.createdAt = new Date();
|
||||||
h.lastUpdatedAt = new Date();
|
h.lastUpdatedAt = new Date();
|
||||||
await repoHistory.save(h);
|
await repoHistory.save(h);
|
||||||
};
|
|
||||||
|
|
||||||
try {
|
|
||||||
if (manager) {
|
|
||||||
await execute(manager);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
await AppDataSource.transaction(async (transactionManager) => {
|
|
||||||
await execute(transactionManager);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
if (manager) {
|
|
||||||
console.error("CreatePosMasterHistoryEmployee error (external transaction):", err);
|
|
||||||
throw err;
|
|
||||||
}
|
|
||||||
console.error("CreatePosMasterHistoryEmployee transaction error:", err);
|
console.error("CreatePosMasterHistoryEmployee transaction error:", err);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
@ -507,15 +459,8 @@ export async function BatchSavePosMasterHistoryOfficer(
|
||||||
const existing = historyByDna.get(op.posMasterDnaId)?.[0];
|
const existing = historyByDna.get(op.posMasterDnaId)?.[0];
|
||||||
const shouldInsert = !existing && op.profileId && op.pm;
|
const shouldInsert = !existing && op.profileId && op.pm;
|
||||||
const profileChanged = existing && existing.profileId !== op.profileId;
|
const profileChanged = existing && existing.profileId !== op.profileId;
|
||||||
const positionChanged =
|
|
||||||
existing &&
|
|
||||||
(existing.position !== op.pm?.position ||
|
|
||||||
existing.posType !== op.pm?.posType ||
|
|
||||||
existing.posLevel !== op.pm?.posLevel ||
|
|
||||||
existing.posExecutive !== op.pm?.posExecutive);
|
|
||||||
|
|
||||||
// ถ้าไม่มี record เดิม หรือ profile เปลี่ยน หรือ position เปลี่ยน ให้สร้าง record ใหม่
|
if (shouldInsert || profileChanged) {
|
||||||
if (shouldInsert || profileChanged || positionChanged) {
|
|
||||||
const newPmh = new PosMasterHistory();
|
const newPmh = new PosMasterHistory();
|
||||||
newPmh.ancestorDNA = op.posMasterDnaId;
|
newPmh.ancestorDNA = op.posMasterDnaId;
|
||||||
newPmh.prefix = op.pm?.prefix ?? _null;
|
newPmh.prefix = op.pm?.prefix ?? _null;
|
||||||
|
|
@ -580,11 +525,11 @@ export async function updateHolderProfileHistory(
|
||||||
orgRevision: {
|
orgRevision: {
|
||||||
orgRevisionIsCurrent: true,
|
orgRevisionIsCurrent: true,
|
||||||
orgRevisionIsDraft: false,
|
orgRevisionIsDraft: false,
|
||||||
},
|
}
|
||||||
},
|
},
|
||||||
relations: {
|
relations: {
|
||||||
orgRevision: true,
|
orgRevision : true
|
||||||
},
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
if (posMaster) {
|
if (posMaster) {
|
||||||
|
|
@ -598,11 +543,11 @@ export async function updateHolderProfileHistory(
|
||||||
orgRevision: {
|
orgRevision: {
|
||||||
orgRevisionIsCurrent: true,
|
orgRevisionIsCurrent: true,
|
||||||
orgRevisionIsDraft: false,
|
orgRevisionIsDraft: false,
|
||||||
},
|
}
|
||||||
},
|
},
|
||||||
relations: {
|
relations: {
|
||||||
orgRevision: true,
|
orgRevision : true
|
||||||
},
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
if (employeePosMaster) {
|
if (employeePosMaster) {
|
||||||
|
|
|
||||||
|
|
@ -29,16 +29,6 @@ import { sendWebSocket } from "./webSocket";
|
||||||
import { PayloadSendNoti } from "../interfaces/utils";
|
import { PayloadSendNoti } from "../interfaces/utils";
|
||||||
import { PermissionProfile } from "../entities/PermissionProfile";
|
import { PermissionProfile } from "../entities/PermissionProfile";
|
||||||
import { PosMasterHistory } from "../entities/PosMasterHistory";
|
import { PosMasterHistory } from "../entities/PosMasterHistory";
|
||||||
import { ExecuteOfficerProfileService } from "./ExecuteOfficerProfileService";
|
|
||||||
import { ExecuteSalaryService } from "./ExecuteSalaryService";
|
|
||||||
import { ExecuteSalaryCurrentService } from "./ExecuteSalaryCurrentService";
|
|
||||||
import { ExecuteSalaryEmployeeCurrentService } from "./ExecuteSalaryEmployeeCurrentService";
|
|
||||||
import { ExecuteSalaryLeaveService } from "./ExecuteSalaryLeaveService";
|
|
||||||
import { ExecuteSalaryEmployeeLeaveService } from "./ExecuteSalaryEmployeeLeaveService";
|
|
||||||
import { ExecuteSalaryLeaveDisciplineService } from "./ExecuteSalaryLeaveDisciplineService";
|
|
||||||
import { ExecuteOrgCommandService } from "./ExecuteOrgCommandService";
|
|
||||||
import { ExecuteSalaryProbationService } from "./ExecuteSalaryProbationService";
|
|
||||||
import { ExecuteSalaryReportService } from "./ExecuteSalaryReportService";
|
|
||||||
|
|
||||||
const redis = require("redis");
|
const redis = require("redis");
|
||||||
const REDIS_HOST = process.env.REDIS_HOST;
|
const REDIS_HOST = process.env.REDIS_HOST;
|
||||||
|
|
@ -176,23 +166,6 @@ function createConsumer( //----> consumer
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* สร้าง pseudo Express request สำหรับ service ที่ถูกเรียกจาก RabbitMQ (ไม่ใช่ HTTP)
|
|
||||||
* ต้องมี `.app.locals.logData.sequence` เพราะ addLogSequence (ใน CallAPI) อ่านค่านี้
|
|
||||||
* และ `.headers.authorization` + `.user` สำหรับ audit/auth
|
|
||||||
*/
|
|
||||||
function buildPseudoReq(token: string, user: any) {
|
|
||||||
return {
|
|
||||||
headers: { authorization: token },
|
|
||||||
user,
|
|
||||||
app: {
|
|
||||||
locals: {
|
|
||||||
logData: { sequence: [] as any[] },
|
|
||||||
},
|
|
||||||
},
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
async function handler(msg: amqp.ConsumeMessage): Promise<boolean> {
|
async function handler(msg: amqp.ConsumeMessage): Promise<boolean> {
|
||||||
//----> condition before process consumer
|
//----> condition before process consumer
|
||||||
// const repo = AppDataSource.getRepository(Command);
|
// const repo = AppDataSource.getRepository(Command);
|
||||||
|
|
@ -347,78 +320,6 @@ async function handler(msg: amqp.ConsumeMessage): Promise<boolean> {
|
||||||
20,
|
20,
|
||||||
);
|
);
|
||||||
|
|
||||||
// ─────────────────────────────────────────────────────────────
|
|
||||||
// Linear Flow
|
|
||||||
// รับ resultData จาก .NET แล้วเรียก Service ตรงๆ ตาม commandType (ไม่ผ่าน HTTP loopback)
|
|
||||||
// - ExecuteOfficerProfileService : C-PM-01, 02, 14 (บรรจุ/รับโอน)
|
|
||||||
// - ExecuteSalaryCurrentService : C-PM-03, 04, 05, 06, 07, 39, 47 (แต่งตั้ง-เลื่อน-ย้าย)
|
|
||||||
// - ExecuteSalaryEmployeeCurrentService : C-PM-22, 24 (ลูกจ้าง ปรับระดับชั้นงาน-ย้าย)
|
|
||||||
// - ExecuteSalaryService : C-PM-13, 15, 16 (ให้โอน/ให้ช่วยราชการ/ให้กลับเข้าราชการ)
|
|
||||||
// - ExecuteSalaryLeaveService : C-PM-08, 09, 17, 18, 41, 48 (ข้าราชการ leave/กลับเข้าราชการ)
|
|
||||||
// - ExecuteSalaryEmployeeLeaveService : C-PM-23, 42, 43 (ลูกจ้าง leave)
|
|
||||||
// - ExecuteSalaryLeaveDisciplineService : C-PM-19, 20, 25, 26, 27, 28, 29, 30, 31, 32 (คำสั่งวินัย)
|
|
||||||
// - ExecuteOrgCommandService : C-PM-21, 38, 40 (org-self — path ชี้กลับ org เอง
|
|
||||||
// เรียก Service ตรงๆ ไม่ผ่าน HTTP loopback เพราะ PostData(path+"/excecute") = ยิงเข้าตัว)
|
|
||||||
// - คำสั่งอื่น ยังใช้ Circular Flow เดิม
|
|
||||||
// ─────────────────────────────────────────────────────────────
|
|
||||||
const code = command.commandType?.code;
|
|
||||||
const isOfficerProfile = ["C-PM-01", "C-PM-02", "C-PM-14"].includes(code);
|
|
||||||
const isSalaryCurrent = ["C-PM-03", "C-PM-04", "C-PM-05", "C-PM-06", "C-PM-07", "C-PM-39", "C-PM-47"].includes(code);
|
|
||||||
const isSalaryEmployeeCurrent = ["C-PM-22", "C-PM-24"].includes(code);
|
|
||||||
const isSalary = ["C-PM-13", "C-PM-15", "C-PM-16"].includes(code);
|
|
||||||
const isSalaryLeave = ["C-PM-08", "C-PM-09", "C-PM-17", "C-PM-18", "C-PM-41", "C-PM-48"].includes(code);
|
|
||||||
const isSalaryEmployeeLeave = ["C-PM-23", "C-PM-42", "C-PM-43"].includes(code);
|
|
||||||
const isSalaryLeaveDiscipline = ["C-PM-19", "C-PM-20", "C-PM-25", "C-PM-26", "C-PM-27", "C-PM-28",
|
|
||||||
"C-PM-29", "C-PM-30", "C-PM-31", "C-PM-32",
|
|
||||||
].includes(code);
|
|
||||||
// C-PM-21/38/40: path ชี้กลับ org เอง (ไม่ใช่ .NET) → ต้องเรียก Service ตรงๆ ไม่ผ่าน loopback
|
|
||||||
const isCommand21 = code === "C-PM-21";
|
|
||||||
const isCommand38 = code === "C-PM-38";
|
|
||||||
const isCommand40 = code === "C-PM-40";
|
|
||||||
const isOrgSelfLinear = isCommand21 || isCommand38 || isCommand40;
|
|
||||||
// C-PM-10/11/12: ยิงไป probation service — เป็น branch แยก (ไม่ใช่ .NET linear flow)
|
|
||||||
// - C-PM-10: fire-only (probation update ในตัวเอง ไม่มี org-side action)
|
|
||||||
// - C-PM-11/12: probation return salary data → เรียก ExecuteSalaryProbationService ตรงๆ
|
|
||||||
const isProbation = ["C-PM-10", "C-PM-11", "C-PM-12"].includes(code);
|
|
||||||
// C-PM-33/34/35/45 (officer) + C-PM-36/37/46 (employee): ยิงไป salary service
|
|
||||||
// เป็น branch แยก — salary return salary data → เรียก ExecuteSalaryReportService ตรงๆ
|
|
||||||
const isSalaryServiceOfficer = ["C-PM-33", "C-PM-34", "C-PM-35", "C-PM-45"].includes(code);
|
|
||||||
const isSalaryServiceEmployee = ["C-PM-36", "C-PM-37", "C-PM-46"].includes(code);
|
|
||||||
const isSalaryService = isSalaryServiceOfficer || isSalaryServiceEmployee;
|
|
||||||
const isLinearFlow =
|
|
||||||
isOfficerProfile ||
|
|
||||||
isSalaryCurrent ||
|
|
||||||
isSalaryEmployeeCurrent ||
|
|
||||||
isSalary ||
|
|
||||||
isSalaryLeave ||
|
|
||||||
isSalaryEmployeeLeave ||
|
|
||||||
isSalaryLeaveDiscipline;
|
|
||||||
|
|
||||||
// Org-self (C-PM-21/38/40): เรียก Service ตรงๆ (Linear Flow / ทำต่อ) ไม่ผ่าน HTTP loopback
|
|
||||||
// เพราะ path ของ command เหล่านี้ชี้กลับ org เอง → PostData(path + "/excecute") = ยิงเข้าตัว
|
|
||||||
if (isOrgSelfLinear) {
|
|
||||||
console.log(`[AMQ] Linear Flow org-self (${code}) — เรียก Service ตรงๆ (no loopback)`);
|
|
||||||
const pseudoReq = buildPseudoReq(token, user);
|
|
||||||
const ctx = {
|
|
||||||
user: { sub: user?.sub ?? "system", name: user?.name ?? "System" },
|
|
||||||
req: pseudoReq,
|
|
||||||
};
|
|
||||||
const flatRefIds = chunks.flat();
|
|
||||||
if (isCommand21) {
|
|
||||||
await new ExecuteOrgCommandService().executeCommand21Employee(flatRefIds, ctx);
|
|
||||||
} else if (isCommand38) {
|
|
||||||
await new ExecuteOrgCommandService().executeCommand38Officer(flatRefIds, ctx);
|
|
||||||
} else if (isCommand40) {
|
|
||||||
await new ExecuteOrgCommandService().executeCommand40Officer(flatRefIds, ctx);
|
|
||||||
}
|
|
||||||
console.log(`[AMQ] Processed ${flatRefIds.length} items via ExecuteOrgCommandService (${code})`);
|
|
||||||
} else if (isProbation) {
|
|
||||||
// Probation Linear Flow (C-PM-10/11/12)
|
|
||||||
// - C-PM-10: fire-only — probation อัปเดต appoint ในตัวเอง ไม่มี org-side action
|
|
||||||
// - C-PM-11/12: fire → probation return salary data → route ไป ExecuteSalaryProbationService
|
|
||||||
// แทนการ callback เข้า org (Circular Flow เดิม)
|
|
||||||
console.log(`[AMQ] Probation Linear Flow (${code})`);
|
|
||||||
if (code === "C-PM-10") {
|
|
||||||
for (const chunk of chunks) {
|
for (const chunk of chunks) {
|
||||||
await new CallAPI().PostData(
|
await new CallAPI().PostData(
|
||||||
{ headers: { authorization: token } },
|
{ headers: { authorization: token } },
|
||||||
|
|
@ -427,167 +328,6 @@ async function handler(msg: amqp.ConsumeMessage): Promise<boolean> {
|
||||||
false,
|
false,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
console.log(`[AMQ] C-PM-10 fire-only — no org-side action`);
|
|
||||||
} else {
|
|
||||||
let resultData: any[] = [];
|
|
||||||
for (const chunk of chunks) {
|
|
||||||
const res = await new CallAPI().PostData(
|
|
||||||
{ headers: { authorization: token } },
|
|
||||||
path + "/excecute",
|
|
||||||
{ refIds: chunk },
|
|
||||||
false,
|
|
||||||
);
|
|
||||||
// รองรับทั้ง array และ { data: [...] } (contract ของ probation หลัง Linear Flow)
|
|
||||||
if (res && Array.isArray(res.data)) {
|
|
||||||
resultData.push(...res.data);
|
|
||||||
} else if (Array.isArray(res)) {
|
|
||||||
resultData.push(...res);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (resultData.length > 0) {
|
|
||||||
const pseudoReq = buildPseudoReq(token, user);
|
|
||||||
const ctx = {
|
|
||||||
user: { sub: user?.sub ?? "system", name: user?.name ?? "System" },
|
|
||||||
req: pseudoReq,
|
|
||||||
};
|
|
||||||
|
|
||||||
if (code === "C-PM-11") {
|
|
||||||
await new ExecuteSalaryProbationService().executeProbationPass(resultData, ctx);
|
|
||||||
console.log(
|
|
||||||
`[AMQ] Processed ${resultData.length} profiles via ExecuteSalaryProbationService (C-PM-11)`,
|
|
||||||
);
|
|
||||||
} else if (code === "C-PM-12") {
|
|
||||||
await new ExecuteSalaryProbationService().executeProbationLeave(resultData, ctx);
|
|
||||||
console.log(
|
|
||||||
`[AMQ] Processed ${resultData.length} profiles via ExecuteSalaryProbationService (C-PM-12)`,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else if (isSalaryService) {
|
|
||||||
// Salary Service Linear Flow (C-PM-33/34/35/45 officer, C-PM-36/37/46 employee)
|
|
||||||
// fire → salary service return salary data → route ไป ExecuteSalaryReportService
|
|
||||||
// แทนการ callback เข้า /org/profile(/-employee)/salary/update (Circular Flow เดิม)
|
|
||||||
console.log(`[AMQ] Salary Service Linear Flow (${code})`);
|
|
||||||
let resultData: any[] = [];
|
|
||||||
for (const chunk of chunks) {
|
|
||||||
const res = await new CallAPI().PostData(
|
|
||||||
{ headers: { authorization: token } },
|
|
||||||
path + "/excecute",
|
|
||||||
{ refIds: chunk },
|
|
||||||
false,
|
|
||||||
);
|
|
||||||
// รองรับทั้ง array และ { data: [...] } (contract ของ salary service หลัง Linear Flow)
|
|
||||||
if (res && Array.isArray(res.data)) {
|
|
||||||
resultData.push(...res.data);
|
|
||||||
} else if (Array.isArray(res)) {
|
|
||||||
resultData.push(...res);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (resultData.length > 0) {
|
|
||||||
const pseudoReq = buildPseudoReq(token, user);
|
|
||||||
const ctx = {
|
|
||||||
user: { sub: user?.sub ?? "system", name: user?.name ?? "System" },
|
|
||||||
req: pseudoReq,
|
|
||||||
};
|
|
||||||
|
|
||||||
if (isSalaryServiceOfficer) {
|
|
||||||
await new ExecuteSalaryReportService().executeOfficerSalaryUpdate(resultData, ctx);
|
|
||||||
console.log(
|
|
||||||
`[AMQ] Processed ${resultData.length} profiles via ExecuteSalaryReportService (officer)`,
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
await new ExecuteSalaryReportService().executeEmployeeSalaryUpdate(resultData, ctx);
|
|
||||||
console.log(
|
|
||||||
`[AMQ] Processed ${resultData.length} profiles via ExecuteSalaryReportService (employee)`,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else if (isLinearFlow) {
|
|
||||||
console.log(`[AMQ] Linear Flow (${code})`);
|
|
||||||
const isCpm32 = code === "C-PM-32";
|
|
||||||
let resultData: any[] = [];
|
|
||||||
let resultData1: any[] = []; //เฉพาะ C-PM-32 (ฝั่ง "การพิจารณาลงโทษ")
|
|
||||||
|
|
||||||
for (const chunk of chunks) {
|
|
||||||
const res = await new CallAPI().PostData(
|
|
||||||
{ headers: { authorization: token } },
|
|
||||||
path + "/excecute",
|
|
||||||
{ refIds: chunk },
|
|
||||||
false,
|
|
||||||
);
|
|
||||||
if (isCpm32 && res && !Array.isArray(res)) {
|
|
||||||
// C-PM-32: response เป็น object { data, data1 } → แยก 2 track
|
|
||||||
console.log(
|
|
||||||
`[AMQ] C-PM-32 split response — data: ${res.data?.length ?? 0}, data1: ${res.data1?.length ?? 0}`,
|
|
||||||
);
|
|
||||||
if (Array.isArray(res.data)) resultData.push(...res.data);
|
|
||||||
if (Array.isArray(res.data1)) resultData1.push(...res.data1);
|
|
||||||
} else if (Array.isArray(res)) {
|
|
||||||
console.log(`[AMQ] Push result data (${res.length})`);
|
|
||||||
resultData.push(...res);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
console.log(`[AMQ] Received ${resultData.length} profiles from .NET (${code})`);
|
|
||||||
|
|
||||||
// Route ไป service ที่ถูกต้องตาม commandType
|
|
||||||
if (resultData.length > 0 || resultData1.length > 0) {
|
|
||||||
// สร้าง pseudo-req สำหรับ setLogDataDiff/save({data: req})
|
|
||||||
const pseudoReq = buildPseudoReq(token, user);
|
|
||||||
const ctx = {
|
|
||||||
user: { sub: user?.sub ?? "system", name: user?.name ?? "System" },
|
|
||||||
req: pseudoReq,
|
|
||||||
};
|
|
||||||
|
|
||||||
if (isOfficerProfile) {
|
|
||||||
await new ExecuteOfficerProfileService().executeCreateOfficerProfile(resultData, ctx);
|
|
||||||
console.log(`[AMQ] Processed ${resultData.length} profiles via ExecuteOfficerProfileService`);
|
|
||||||
} else if (isSalaryCurrent) {
|
|
||||||
await new ExecuteSalaryCurrentService().executeSalaryCurrent(resultData, ctx);
|
|
||||||
console.log(`[AMQ] Processed ${resultData.length} profiles via ExecuteSalaryCurrentService`);
|
|
||||||
} else if (isSalaryEmployeeCurrent) {
|
|
||||||
await new ExecuteSalaryEmployeeCurrentService().executeSalaryEmployeeCurrent(resultData, ctx);
|
|
||||||
console.log(`[AMQ] Processed ${resultData.length} profiles via ExecuteSalaryEmployeeCurrentService`);
|
|
||||||
} else if (isSalary) {
|
|
||||||
await new ExecuteSalaryService().executeSalary(resultData, ctx);
|
|
||||||
console.log(`[AMQ] Processed ${resultData.length} profiles via ExecuteSalaryService`);
|
|
||||||
} else if (isSalaryLeave) {
|
|
||||||
await new ExecuteSalaryLeaveService().executeSalaryLeave(resultData, ctx);
|
|
||||||
console.log(`[AMQ] Processed ${resultData.length} profiles via ExecuteSalaryLeaveService`);
|
|
||||||
} else if (isSalaryEmployeeLeave) {
|
|
||||||
await new ExecuteSalaryEmployeeLeaveService().executeSalaryEmployeeLeave(resultData, ctx);
|
|
||||||
console.log(`[AMQ] Processed ${resultData.length} profiles via ExecuteSalaryEmployeeLeaveService`);
|
|
||||||
} else if (isSalaryLeaveDiscipline) {
|
|
||||||
// C-PM-32 (คำสั่งยุติเรื่อง): response เป็น object { data, data1 }
|
|
||||||
// profileId เดียวกันอาจอยู่ในทั้ง 2 track → ต้องส่งให้ org แยก 2 ครั้ง ห้าม merge
|
|
||||||
if (resultData.length > 0) {
|
|
||||||
await new ExecuteSalaryLeaveDisciplineService().executeSalaryLeaveDiscipline(resultData, ctx);
|
|
||||||
console.log(
|
|
||||||
`[AMQ] Processed ${resultData.length} profiles via ExecuteSalaryLeaveDisciplineService`,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
if (isCpm32 && resultData1.length > 0) {
|
|
||||||
await new ExecuteSalaryLeaveDisciplineService().executeSalaryLeaveDiscipline(resultData1, ctx);
|
|
||||||
console.log(
|
|
||||||
`[AMQ] Processed resultData1: ${resultData1.length} profiles via ExecuteSalaryLeaveDisciplineService`,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
console.log(`[AMQ] Circular Flow (${code})`);
|
|
||||||
for (const chunk of chunks) {
|
|
||||||
await new CallAPI().PostData(
|
|
||||||
{ headers: { authorization: token } },
|
|
||||||
path + "/excecute",
|
|
||||||
{ refIds: chunk },
|
|
||||||
false,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Object.assign(command, { status, lastUpdateUserId, lastUpdateFullName, lastUpdatedAt });
|
Object.assign(command, { status, lastUpdateUserId, lastUpdateFullName, lastUpdatedAt });
|
||||||
const result = await repo.save(command);
|
const result = await repo.save(command);
|
||||||
|
|
@ -955,19 +695,7 @@ async function handler_org(msg: amqp.ConsumeMessage): Promise<boolean> {
|
||||||
where: {
|
where: {
|
||||||
orgRevisionId: orgRevisionPublish.id,
|
orgRevisionId: orgRevisionPublish.id,
|
||||||
},
|
},
|
||||||
select: [
|
select: ["id", "current_holderId", "ancestorDNA"],
|
||||||
"id",
|
|
||||||
"current_holderId",
|
|
||||||
"ancestorDNA",
|
|
||||||
"posMasterNo",
|
|
||||||
"posMasterNoPrefix",
|
|
||||||
"posMasterNoSuffix",
|
|
||||||
"orgRootId",
|
|
||||||
"orgChild1Id",
|
|
||||||
"orgChild2Id",
|
|
||||||
"orgChild3Id",
|
|
||||||
"orgChild4Id",
|
|
||||||
],
|
|
||||||
});
|
});
|
||||||
|
|
||||||
// Task #2160 ดึง posMasterAssign ของ revision เดิม
|
// Task #2160 ดึง posMasterAssign ของ revision เดิม
|
||||||
|
|
@ -1054,9 +782,8 @@ async function handler_org(msg: amqp.ConsumeMessage): Promise<boolean> {
|
||||||
// 2. Batch load profiles ทั้งหมดในครั้งเดียว (แก้ปัญหา N+1 Query)
|
// 2. Batch load profiles ทั้งหมดในครั้งเดียว (แก้ปัญหา N+1 Query)
|
||||||
const profilesMap = new Map<string, Profile>();
|
const profilesMap = new Map<string, Profile>();
|
||||||
if (profileIds.length > 0) {
|
if (profileIds.length > 0) {
|
||||||
const profiles = await repoProfile.find({
|
const profiles = await repoProfile.findBy({
|
||||||
where: { id: In(profileIds) },
|
id: In(profileIds),
|
||||||
relations: ["posType", "posLevel"],
|
|
||||||
});
|
});
|
||||||
profiles.forEach((p) => profilesMap.set(p.id, p));
|
profiles.forEach((p) => profilesMap.set(p.id, p));
|
||||||
}
|
}
|
||||||
|
|
@ -1133,21 +860,7 @@ async function handler_org(msg: amqp.ConsumeMessage): Promise<boolean> {
|
||||||
const newHolderId = item?.next_holderId;
|
const newHolderId = item?.next_holderId;
|
||||||
const isHolderChanged = oldHolderId !== newHolderId;
|
const isHolderChanged = oldHolderId !== newHolderId;
|
||||||
|
|
||||||
// เช็คว่า holder เดิม แต่ตำแหน่งเปลี่ยน
|
if (isHolderChanged) {
|
||||||
const isSameHolder = oldHolderId === newHolderId && oldHolderId != null && newHolderId != null;
|
|
||||||
const isPositionChanged =
|
|
||||||
isSameHolder &&
|
|
||||||
oldPm &&
|
|
||||||
(oldPm.posMasterNo !== item.posMasterNo ||
|
|
||||||
oldPm.posMasterNoPrefix !== item.posMasterNoPrefix ||
|
|
||||||
oldPm.posMasterNoSuffix !== item.posMasterNoSuffix ||
|
|
||||||
oldPm.orgRootId !== item.orgRoot?.id ||
|
|
||||||
oldPm.orgChild1Id !== item.orgChild1?.id ||
|
|
||||||
oldPm.orgChild2Id !== item.orgChild2?.id ||
|
|
||||||
oldPm.orgChild3Id !== item.orgChild3?.id ||
|
|
||||||
oldPm.orgChild4Id !== item.orgChild4?.id);
|
|
||||||
|
|
||||||
if (isHolderChanged || isPositionChanged) {
|
|
||||||
const nextHolderProfile =
|
const nextHolderProfile =
|
||||||
item.next_holderId != null && item.next_holderId !== ""
|
item.next_holderId != null && item.next_holderId !== ""
|
||||||
? profilesMap.get(item.next_holderId)
|
? profilesMap.get(item.next_holderId)
|
||||||
|
|
@ -1165,18 +878,6 @@ async function handler_org(msg: amqp.ConsumeMessage): Promise<boolean> {
|
||||||
item.orgRoot?.orgRootShortName,
|
item.orgRoot?.orgRootShortName,
|
||||||
].find((name) => typeof name === "string" && name.trim().length > 0) ?? _null;
|
].find((name) => typeof name === "string" && name.trim().length > 0) ?? _null;
|
||||||
|
|
||||||
// ถ้าเป็นตำแหน่งนั่งทับ (isSit = true) และมีคนครอง ใช้ตำแหน่งจาก profile แทน
|
|
||||||
let positionName = selectedPosition?.positionName ?? _null;
|
|
||||||
let posTypeName = selectedPosition?.posType?.posTypeName ?? _null;
|
|
||||||
let posLevelName = selectedPosition?.posLevel?.posLevelName ?? _null;
|
|
||||||
let posExecutiveName = selectedPosition?.posExecutive?.posExecutiveName ?? _null;
|
|
||||||
if (item.isSit && nextHolderProfile) {
|
|
||||||
positionName = nextHolderProfile.position ?? _null;
|
|
||||||
posTypeName = nextHolderProfile.posType?.posTypeName ?? _null;
|
|
||||||
posLevelName = nextHolderProfile.posLevel?.posLevelName ?? _null;
|
|
||||||
posExecutiveName = nextHolderProfile.posExecutive ?? _null;
|
|
||||||
}
|
|
||||||
|
|
||||||
historyRowsToSave.push({
|
historyRowsToSave.push({
|
||||||
ancestorDNA: item.ancestorDNA ? item.ancestorDNA : _null,
|
ancestorDNA: item.ancestorDNA ? item.ancestorDNA : _null,
|
||||||
prefix: nextHolderProfile?.prefix || _null,
|
prefix: nextHolderProfile?.prefix || _null,
|
||||||
|
|
@ -1186,11 +887,11 @@ async function handler_org(msg: amqp.ConsumeMessage): Promise<boolean> {
|
||||||
posMasterNoPrefix: item.posMasterNoPrefix ?? _null,
|
posMasterNoPrefix: item.posMasterNoPrefix ?? _null,
|
||||||
posMasterNo: item.posMasterNo ?? _null,
|
posMasterNo: item.posMasterNo ?? _null,
|
||||||
posMasterNoSuffix: item.posMasterNoSuffix ?? _null,
|
posMasterNoSuffix: item.posMasterNoSuffix ?? _null,
|
||||||
position: positionName,
|
position: selectedPosition?.positionName ?? _null,
|
||||||
posType: posTypeName,
|
posType: selectedPosition?.posType?.posTypeName ?? _null,
|
||||||
posLevel: posLevelName,
|
posLevel: selectedPosition?.posLevel?.posLevelName ?? _null,
|
||||||
posExecutive: posExecutiveName,
|
posExecutive: selectedPosition?.posExecutive?.posExecutiveName ?? _null,
|
||||||
profileId: nextHolderProfile?.id || _null,
|
profileId: _null,
|
||||||
rootDnaId: item.orgRoot?.ancestorDNA || _null,
|
rootDnaId: item.orgRoot?.ancestorDNA || _null,
|
||||||
child1DnaId: item.orgChild1?.ancestorDNA || _null,
|
child1DnaId: item.orgChild1?.ancestorDNA || _null,
|
||||||
child2DnaId: item.orgChild2?.ancestorDNA || _null,
|
child2DnaId: item.orgChild2?.ancestorDNA || _null,
|
||||||
|
|
@ -1997,7 +1698,6 @@ async function handler_org(msg: amqp.ConsumeMessage): Promise<boolean> {
|
||||||
}
|
}
|
||||||
|
|
||||||
async function clearMenuAndRoleCache(): Promise<void> {
|
async function clearMenuAndRoleCache(): Promise<void> {
|
||||||
console.log("[AMQ] clearMenuAndRoleCache: Starting...");
|
|
||||||
const redisClient = redis.createClient({
|
const redisClient = redis.createClient({
|
||||||
host: REDIS_HOST,
|
host: REDIS_HOST,
|
||||||
port: REDIS_PORT,
|
port: REDIS_PORT,
|
||||||
|
|
@ -2007,28 +1707,17 @@ async function clearMenuAndRoleCache(): Promise<void> {
|
||||||
const delAsync = promisify(redisClient.del).bind(redisClient);
|
const delAsync = promisify(redisClient.del).bind(redisClient);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// Clear menu and role cache (patterns that affect menu display)
|
const menuKeys = await keysAsync("menu_*");
|
||||||
const menuRolePatterns = ["menu_*", "role_*"];
|
if (menuKeys.length > 0) {
|
||||||
|
await delAsync(...menuKeys);
|
||||||
|
console.log(`[AMQ] Cleared ${menuKeys.length} menu cache keys`);
|
||||||
|
}
|
||||||
|
|
||||||
for (const pattern of menuRolePatterns) {
|
const roleKeys = await keysAsync("role_*");
|
||||||
console.log(`[AMQ] Checking pattern: ${pattern}`);
|
if (roleKeys.length > 0) {
|
||||||
const keys = await keysAsync(pattern);
|
await delAsync(...roleKeys);
|
||||||
console.log(`[AMQ] Found ${keys.length} keys for pattern: ${pattern}`);
|
console.log(`[AMQ] Cleared ${roleKeys.length} role cache keys`);
|
||||||
if (keys.length > 0) {
|
|
||||||
// Delete in chunks of 1000 to avoid argument limit
|
|
||||||
const chunkSize = 1000;
|
|
||||||
for (let i = 0; i < keys.length; i += chunkSize) {
|
|
||||||
const chunk = keys.slice(i, i + chunkSize);
|
|
||||||
await delAsync(...chunk);
|
|
||||||
}
|
}
|
||||||
console.log(`[AMQ] Cleared ${keys.length} cache keys for pattern: ${pattern}`);
|
|
||||||
} else {
|
|
||||||
console.log(`[AMQ] No keys found for pattern: ${pattern}`);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
console.log("[AMQ] clearMenuAndRoleCache: Completed successfully");
|
|
||||||
} catch (error) {
|
|
||||||
console.error("[AMQ] clearMenuAndRoleCache ERROR:", error);
|
|
||||||
} finally {
|
} finally {
|
||||||
redisClient.quit();
|
redisClient.quit();
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue