1423 lines
65 KiB
TypeScript
1423 lines
65 KiB
TypeScript
import { EntityManager, In, Like } from "typeorm";
|
|
import { AppDataSource } from "../database/data-source";
|
|
import HttpError from "../interfaces/http-error";
|
|
import HttpStatusCode from "../interfaces/http-status";
|
|
import { CreateProfileAllFields, Profile } from "../entities/Profile";
|
|
import { ProfileEmployee } from "../entities/ProfileEmployee";
|
|
import { CreateProfileSalary, ProfileSalary } from "../entities/ProfileSalary";
|
|
import { ProfileSalaryHistory } from "../entities/ProfileSalaryHistory";
|
|
import { CreateProfileEducation, ProfileEducation } from "../entities/ProfileEducation";
|
|
import { ProfileEducationHistory } from "../entities/ProfileEducationHistory";
|
|
import {
|
|
CreateProfileCertificate,
|
|
ProfileCertificate,
|
|
} from "../entities/ProfileCertificate";
|
|
import { ProfileCertificateHistory } from "../entities/ProfileCertificateHistory";
|
|
import { ProfileFamilyCouple } from "../entities/ProfileFamilyCouple";
|
|
import { ProfileFamilyCoupleHistory } from "../entities/ProfileFamilyCoupleHistory";
|
|
import { ProfileFamilyFather } from "../entities/ProfileFamilyFather";
|
|
import { ProfileFamilyFatherHistory } from "../entities/ProfileFamilyFatherHistory";
|
|
import { ProfileFamilyMother } from "../entities/ProfileFamilyMother";
|
|
import { ProfileFamilyMotherHistory } from "../entities/ProfileFamilyMotherHistory";
|
|
import { CreateProfileInsignia, ProfileInsignia } from "../entities/ProfileInsignia";
|
|
import { ProfileInsigniaHistory } from "../entities/ProfileInsigniaHistory";
|
|
import { ProfileAvatar } from "../entities/ProfileAvatar";
|
|
import { PosLevel } from "../entities/PosLevel";
|
|
import { PosType } from "../entities/PosType";
|
|
import { Province } from "../entities/Province";
|
|
import { District } from "../entities/District";
|
|
import { SubDistrict } from "../entities/SubDistrict";
|
|
import { OrgRoot } from "../entities/OrgRoot";
|
|
import { RoleKeycloak } from "../entities/RoleKeycloak";
|
|
import { PosMaster } from "../entities/PosMaster";
|
|
import { Position } from "../entities/Position";
|
|
import { Command } from "../entities/Command";
|
|
import {
|
|
calculateRetireDate,
|
|
calculateRetireLaw,
|
|
removeProfileInOrganize,
|
|
setLogDataDiff,
|
|
} from "../interfaces/utils";
|
|
import {
|
|
addUserRoles,
|
|
createUser,
|
|
getRoleMappings,
|
|
getRoles,
|
|
getUserByUsername,
|
|
removeUserRoles,
|
|
updateUserAttributes,
|
|
} from "../keycloak";
|
|
import { CreatePosMasterHistoryOfficer } from "./PositionService";
|
|
import { getOrgFullName, getPosMasterNo } from "../utils/org-formatting";
|
|
import CallAPI from "../interfaces/call-api";
|
|
|
|
/**
|
|
* Input: ตำแหน่งที่จะกำหนดให้กับ profile ใหม่ (ส่งมาจากฝั่งบรรจุ)
|
|
*/
|
|
export interface OfficerPositionInput {
|
|
posmasterId: string;
|
|
positionId: string;
|
|
positionName: string;
|
|
posTypeId: string;
|
|
posLevelId: string;
|
|
posExecutiveId: string | null;
|
|
positionField: string | null;
|
|
positionExecutiveField: string | null;
|
|
positionArea: string | null;
|
|
}
|
|
|
|
/**
|
|
* Input: ข้อมูลคู่สมรส
|
|
*/
|
|
export interface OfficerMarryInput {
|
|
marry?: boolean | null;
|
|
marryPrefix?: string | null;
|
|
marryFirstName?: string | null;
|
|
marryLastName?: string | null;
|
|
marryOccupation?: string | null;
|
|
marryNationality?: string | null;
|
|
}
|
|
|
|
/**
|
|
* Input: ข้อมูลบิดา
|
|
*/
|
|
export interface OfficerFatherInput {
|
|
fatherPrefix?: string | null;
|
|
fatherFirstName?: string | null;
|
|
fatherLastName?: string | null;
|
|
fatherOccupation?: string | null;
|
|
fatherNationality?: string | null;
|
|
}
|
|
|
|
/**
|
|
* Input: ข้อมูลมารดา
|
|
*/
|
|
export interface OfficerMotherInput {
|
|
motherPrefix?: string | null;
|
|
motherFirstName?: string | null;
|
|
motherLastName?: string | null;
|
|
motherOccupation?: string | null;
|
|
motherNationality?: string | null;
|
|
}
|
|
|
|
/**
|
|
* Input: ข้อมูล 1 คนที่จะบรรจุ/แต่งตั้ง (ตรงกับ body.data[i] ของ endpoint เดิม)
|
|
*/
|
|
export interface OfficerProfileItem {
|
|
bodyProfile: CreateProfileAllFields;
|
|
bodyEducations?: CreateProfileEducation[];
|
|
bodyCertificates?: CreateProfileCertificate[];
|
|
bodySalarys?: CreateProfileSalary | null;
|
|
bodyPosition?: OfficerPositionInput | null;
|
|
bodyMarry?: OfficerMarryInput | null;
|
|
bodyFather?: OfficerFatherInput | null;
|
|
bodyMother?: OfficerMotherInput | null;
|
|
}
|
|
|
|
/**
|
|
* Context สำหรับ audit/log — แยกขาดจาก HTTP request เพื่อให้ service
|
|
* สามารถถูกเรียกได้ทั้งจาก endpoint (ผ่าน Express req) และจาก message consumer
|
|
* (ผ่าน pseudo-req ที่สร้างขึ้น) โดยไม่ผูกติดกับ HTTP layer
|
|
*/
|
|
export interface ExecutionContext {
|
|
/** user ที่ทริกเกอร์งานนี้ (sub = keycloak id, name = ชื่อเต็ม) */
|
|
user: { sub: string; name: string };
|
|
/** Express request (สำหรับ subscriber pattern: setLogDataDiff, save({data: req})) */
|
|
req?: any;
|
|
}
|
|
|
|
/**
|
|
* Service สำหรับสร้าง/อัปเดตทะเบียนประวัติข้าราชการ (Profile) หลังออกคำสั่งบรรจุ
|
|
*
|
|
* ใช้กับ commandType: C-PM-01, 02, 14
|
|
*
|
|
* - endpoint /org/command/excexute/create-officer-profile เรียกผ่าน service นี้ (thin wrapper)
|
|
* - consumer ใน rabbitmq handler เรียกผ่าน service นี้โดยตรง (Linear Flow)
|
|
*
|
|
* Behavior ทั้งหมด preserve จาก CommandController.CreateOfficeProfileExcecute ต้นฉบับ
|
|
*
|
|
* Batch semantics: all-or-nothing — ประมวลผลทุกคนภายใต้ transaction เดียว (sequential)
|
|
* ถ้าคนใด throw จะ rollback ทั้ง batch และ propagate error ออกไป (ล้มเหลวทั้งหมด)
|
|
* ถ้าทุกคนสำเร็จจะ return result รายงาน success count
|
|
*
|
|
* ⚠️ หมายเหตุ Keycloak: operations (createUser/addUserRoles/removeUserRoles/updateUserAttributes)
|
|
* ทำภายใน transaction เพื่อ preserve behavior เดิม — Keycloak ไม่สามารถ rollback ได้
|
|
* ถ้า DB rollback หลังจาก Keycloak operation สำเร็จ → Keycloak จะถูกเปลี่ยนไปแล้ว
|
|
*
|
|
* Design note: แก้ปัญหา "Circular Dependency" ระหว่าง API Org กับ API บรรจุ โดยให้ฝั่งบรรจุ
|
|
* ส่ง resultData กลับมา แล้วฝั่ง Org ประมวลผลสร้าง profile เองที่ต้นทาง แทนการเรียกซ้อนกัน
|
|
*/
|
|
export class ExecuteOfficerProfileService {
|
|
private commandRepository = AppDataSource.getRepository(Command);
|
|
private profileRepository = AppDataSource.getRepository(Profile);
|
|
private profileEmployeeRepository = AppDataSource.getRepository(ProfileEmployee);
|
|
private salaryRepo = AppDataSource.getRepository(ProfileSalary);
|
|
private salaryHistoryRepo = AppDataSource.getRepository(ProfileSalaryHistory);
|
|
private posLevelRepo = AppDataSource.getRepository(PosLevel);
|
|
private posTypeRepo = AppDataSource.getRepository(PosType);
|
|
private provinceRepo = AppDataSource.getRepository(Province);
|
|
private districtRepo = AppDataSource.getRepository(District);
|
|
private subDistrictRepo = AppDataSource.getRepository(SubDistrict);
|
|
private orgRootRepository = AppDataSource.getRepository(OrgRoot);
|
|
private roleKeycloakRepo = AppDataSource.getRepository(RoleKeycloak);
|
|
private posMasterRepository = AppDataSource.getRepository(PosMaster);
|
|
private positionRepository = AppDataSource.getRepository(Position);
|
|
private profileEducationRepo = AppDataSource.getRepository(ProfileEducation);
|
|
private profileEducationHistoryRepo = AppDataSource.getRepository(ProfileEducationHistory);
|
|
private certificateRepo = AppDataSource.getRepository(ProfileCertificate);
|
|
private certificateHistoryRepo = AppDataSource.getRepository(ProfileCertificateHistory);
|
|
private profileFamilyCoupleRepo = AppDataSource.getRepository(ProfileFamilyCouple);
|
|
private profileFamilyCoupleHistoryRepo = AppDataSource.getRepository(ProfileFamilyCoupleHistory);
|
|
private profileFamilyFatherRepo = AppDataSource.getRepository(ProfileFamilyFather);
|
|
private profileFamilyFatherHistoryRepo = AppDataSource.getRepository(ProfileFamilyFatherHistory);
|
|
private profileFamilyMotherRepo = AppDataSource.getRepository(ProfileFamilyMother);
|
|
private profileFamilyMotherHistoryRepo = AppDataSource.getRepository(ProfileFamilyMotherHistory);
|
|
private insigniaRepo = AppDataSource.getRepository(ProfileInsignia);
|
|
private insigniaHistoryRepo = AppDataSource.getRepository(ProfileInsigniaHistory);
|
|
private avatarRepository = AppDataSource.getRepository(ProfileAvatar);
|
|
|
|
/**
|
|
* ประมวลผลสร้าง/อัปเดท profile ข้าราชการ ตามข้อมูลที่ฝั่งบรรจุส่งมา
|
|
*
|
|
* @param data รายการข้อมูลที่จะประมวลผล (1 คำสั่งอาจมีหลายคน)
|
|
* @param ctx context สำหรับ audit/log
|
|
*/
|
|
async executeCreateOfficerProfile(
|
|
data: OfficerProfileItem[],
|
|
ctx: ExecutionContext,
|
|
): Promise<void> {
|
|
const commandId =
|
|
data?.find((x: any) => x.bodySalarys?.commandId)?.bodySalarys?.commandId ?? "unknown";
|
|
console.log(
|
|
`[ExecuteOfficerProfileService] Starting executeCreateOfficerProfile — commandId: ${commandId}`,
|
|
);
|
|
console.log(`[ExecuteOfficerProfileService] Request body count: ${data?.length ?? 0}`);
|
|
|
|
// ─────────────────────────────────────────────────────────────
|
|
// Normalize date fields
|
|
// ผ่าน HTTP endpoint → tsoa แปลง ISO string → Date ให้อัตโนมัติ
|
|
// แต่ผ่าน RabbitMQ handler (axios) → จะได้ string → ต้องแปลงเอง
|
|
// ไม่งั้น calculateRetireDate/getFullYear ฯลฯ จะพัง
|
|
// ─────────────────────────────────────────────────────────────
|
|
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 bp = item.bodyProfile as any;
|
|
if (bp) {
|
|
bp.birthDate = toDate(bp.birthDate);
|
|
bp.dateStart = toDate(bp.dateStart);
|
|
bp.dateAppoint = toDate(bp.dateAppoint);
|
|
bp.dateRetire = toDate(bp.dateRetire);
|
|
}
|
|
const bs = item.bodySalarys as any;
|
|
if (bs) {
|
|
bs.commandDateAffect = toDate(bs.commandDateAffect);
|
|
bs.commandDateSign = toDate(bs.commandDateSign);
|
|
}
|
|
if (item.bodyEducations) {
|
|
for (const edu of item.bodyEducations as any[]) {
|
|
edu.startDate = toDate(edu.startDate);
|
|
edu.endDate = toDate(edu.endDate);
|
|
edu.finishDate = toDate(edu.finishDate);
|
|
}
|
|
}
|
|
if (item.bodyCertificates) {
|
|
for (const cer of item.bodyCertificates as any[]) {
|
|
cer.expireDate = toDate(cer.expireDate);
|
|
cer.issueDate = toDate(cer.issueDate);
|
|
}
|
|
}
|
|
}
|
|
|
|
const req = ctx.req;
|
|
const roleKeycloak = await this.roleKeycloakRepo.findOne({
|
|
where: { name: Like("USER") },
|
|
});
|
|
console.log("[ExecuteOfficerProfileService] roleKeycloak found:", !!roleKeycloak);
|
|
const list = await getRoles();
|
|
console.log(
|
|
"[ExecuteOfficerProfileService] Roles list retrieved, length:",
|
|
Array.isArray(list) ? list.length : "not array",
|
|
);
|
|
if (!Array.isArray(list)) {
|
|
console.error(
|
|
"[ExecuteOfficerProfileService] Failed - Cannot get role(s) data from the server",
|
|
);
|
|
throw new Error("Failed. Cannot get role(s) data from the server.");
|
|
}
|
|
let _posNumCodeSit: string = "";
|
|
let _posNumCodeSitAbb: string = "";
|
|
console.log("[ExecuteOfficerProfileService] Getting command data");
|
|
const _command = await this.commandRepository.findOne({
|
|
where: {
|
|
id: data.find((x) => x.bodySalarys?.commandId)?.bodySalarys?.commandId ?? "",
|
|
},
|
|
});
|
|
console.log(
|
|
"[ExecuteOfficerProfileService] Command found:",
|
|
!!_command,
|
|
"isBangkok:",
|
|
_command?.isBangkok,
|
|
);
|
|
if (_command) {
|
|
if (_command?.isBangkok?.toLocaleUpperCase() == "OFFICE") {
|
|
console.log("[ExecuteOfficerProfileService] Setting position codes for 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 : "สนป.";
|
|
console.log(
|
|
"[ExecuteOfficerProfileService] OFFICE position codes set:",
|
|
_posNumCodeSit,
|
|
_posNumCodeSitAbb,
|
|
);
|
|
} else if (_command?.isBangkok?.toLocaleUpperCase() == "BANGKOK") {
|
|
console.log("[ExecuteOfficerProfileService] Setting position codes for BANGKOK");
|
|
_posNumCodeSit = "กรุงเทพมหานคร";
|
|
_posNumCodeSitAbb = "กทม.";
|
|
console.log(
|
|
"[ExecuteOfficerProfileService] BANGKOK position codes set:",
|
|
_posNumCodeSit,
|
|
_posNumCodeSitAbb,
|
|
);
|
|
} else {
|
|
console.log("[ExecuteOfficerProfileService] Setting position codes from admin profile");
|
|
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 ?? "";
|
|
console.log(
|
|
"[ExecuteOfficerProfileService] Admin profile position codes set:",
|
|
_posNumCodeSit,
|
|
_posNumCodeSitAbb,
|
|
);
|
|
}
|
|
}
|
|
const before = null;
|
|
const meta = {
|
|
createdUserId: ctx.user.sub,
|
|
createdFullName: ctx.user.name,
|
|
lastUpdateUserId: ctx.user.sub,
|
|
lastUpdateFullName: ctx.user.name,
|
|
createdAt: new Date(),
|
|
lastUpdatedAt: new Date(),
|
|
};
|
|
console.log(
|
|
"[ExecuteOfficerProfileService] Starting to process",
|
|
data.length,
|
|
"profile(s)",
|
|
);
|
|
|
|
// ─────────────────────────────────────────────────────────────
|
|
// 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,
|
|
roleKeycloak,
|
|
list,
|
|
_posNumCodeSit,
|
|
_posNumCodeSitAbb,
|
|
meta,
|
|
before,
|
|
commandId,
|
|
);
|
|
} catch (err) {
|
|
const reason =
|
|
err instanceof HttpError
|
|
? err.message
|
|
: err instanceof Error
|
|
? err.message
|
|
: "unexpected error";
|
|
console.error(
|
|
`[ExecuteOfficerProfileService] Failed commandId=${commandId}, citizenId=${item.bodyProfile?.citizenId}: ${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: OfficerProfileItem,
|
|
ctx: ExecutionContext,
|
|
manager: EntityManager,
|
|
roleKeycloak: RoleKeycloak | null,
|
|
list: any[],
|
|
_posNumCodeSit: string,
|
|
_posNumCodeSitAbb: string,
|
|
meta: any,
|
|
before: any,
|
|
commandId: string,
|
|
): Promise<void> {
|
|
const req = ctx.req;
|
|
|
|
// ─────────────────────────────────────────────────────────────
|
|
// repo ทั้งหมดสร้างจาก manager เพื่อให้อยู่ใน transaction เดียวกัน
|
|
// ─────────────────────────────────────────────────────────────
|
|
const profileRepository = manager.getRepository(Profile);
|
|
const profileEmployeeRepository = manager.getRepository(ProfileEmployee);
|
|
const salaryRepo = manager.getRepository(ProfileSalary);
|
|
const salaryHistoryRepo = manager.getRepository(ProfileSalaryHistory);
|
|
const posLevelRepo = manager.getRepository(PosLevel);
|
|
const posTypeRepo = manager.getRepository(PosType);
|
|
const provinceRepo = manager.getRepository(Province);
|
|
const districtRepo = manager.getRepository(District);
|
|
const subDistrictRepo = manager.getRepository(SubDistrict);
|
|
const posMasterRepository = manager.getRepository(PosMaster);
|
|
const positionRepository = manager.getRepository(Position);
|
|
const profileEducationRepo = manager.getRepository(ProfileEducation);
|
|
const profileEducationHistoryRepo = manager.getRepository(ProfileEducationHistory);
|
|
const certificateRepo = manager.getRepository(ProfileCertificate);
|
|
const certificateHistoryRepo = manager.getRepository(ProfileCertificateHistory);
|
|
const profileFamilyCoupleRepo = manager.getRepository(ProfileFamilyCouple);
|
|
const profileFamilyCoupleHistoryRepo = manager.getRepository(ProfileFamilyCoupleHistory);
|
|
const profileFamilyFatherRepo = manager.getRepository(ProfileFamilyFather);
|
|
const profileFamilyFatherHistoryRepo = manager.getRepository(ProfileFamilyFatherHistory);
|
|
const profileFamilyMotherRepo = manager.getRepository(ProfileFamilyMother);
|
|
const profileFamilyMotherHistoryRepo = manager.getRepository(ProfileFamilyMotherHistory);
|
|
const insigniaRepo = manager.getRepository(ProfileInsignia);
|
|
const insigniaHistoryRepo = manager.getRepository(ProfileInsigniaHistory);
|
|
const avatarRepository = manager.getRepository(ProfileAvatar);
|
|
|
|
console.log(
|
|
"[ExecuteOfficerProfileService] Processing citizenId:",
|
|
item.bodyProfile.citizenId,
|
|
);
|
|
const _null: any = null;
|
|
if (item.bodyProfile.posLevelId === "") item.bodyProfile.posLevelId = null;
|
|
if (item.bodyProfile.posTypeId === "") item.bodyProfile.posTypeId = null;
|
|
if (
|
|
item.bodyProfile.posLevelId &&
|
|
!(await posLevelRepo.findOneBy({ id: item.bodyProfile.posLevelId }))
|
|
) {
|
|
console.error(
|
|
"[ExecuteOfficerProfileService] ไม่พบข้อมูลระดับตำแหน่งนี้ posLevelId:",
|
|
item.bodyProfile.posLevelId,
|
|
);
|
|
throw new HttpError(HttpStatusCode.NOT_FOUND, "ไม่พบข้อมูลระดับตำแหน่งนี้");
|
|
}
|
|
if (
|
|
item.bodyProfile.posTypeId &&
|
|
!(await posTypeRepo.findOneBy({ id: item.bodyProfile.posTypeId }))
|
|
) {
|
|
console.error(
|
|
"[ExecuteOfficerProfileService] ไม่พบข้อมูลประเภทตำแหน่งนี้ posTypeId:",
|
|
item.bodyProfile.posTypeId,
|
|
);
|
|
throw new HttpError(HttpStatusCode.NOT_FOUND, "ไม่พบข้อมูลประเภทตำแหน่งนี้");
|
|
}
|
|
|
|
console.log(
|
|
"[ExecuteOfficerProfileService] Processing citizenId:",
|
|
item.bodyProfile.citizenId,
|
|
);
|
|
let registrationProvinceId = await provinceRepo.findOneBy({
|
|
id: item.bodyProfile.registrationProvinceId ?? "",
|
|
});
|
|
let registrationDistrictId = await districtRepo.findOneBy({
|
|
id: item.bodyProfile.registrationDistrictId ?? "",
|
|
});
|
|
let registrationSubDistrictId = await subDistrictRepo.findOneBy({
|
|
id: item.bodyProfile.registrationSubDistrictId ?? "",
|
|
});
|
|
let currentProvinceId = await provinceRepo.findOneBy({
|
|
id: item.bodyProfile.currentProvinceId ?? "",
|
|
});
|
|
let currentDistrictId = await districtRepo.findOneBy({
|
|
id: item.bodyProfile.currentDistrictId ?? "",
|
|
});
|
|
let currentSubDistrictId = await subDistrictRepo.findOneBy({
|
|
id: item.bodyProfile.currentSubDistrictId ?? "",
|
|
});
|
|
console.log("[ExecuteOfficerProfileService] Address validation completed");
|
|
|
|
let _dateRetire =
|
|
item.bodyProfile.birthDate == null
|
|
? _null
|
|
: calculateRetireDate(item.bodyProfile.birthDate);
|
|
let _dateRetireLaw =
|
|
item.bodyProfile.birthDate == null
|
|
? _null
|
|
: calculateRetireLaw(item.bodyProfile.birthDate);
|
|
|
|
let userKeycloakId: any;
|
|
let result: any;
|
|
console.log(
|
|
"[ExecuteOfficerProfileService] Checking Keycloak user for citizenId:",
|
|
item.bodyProfile.citizenId,
|
|
);
|
|
const checkUser = await getUserByUsername(item.bodyProfile.citizenId);
|
|
console.log(
|
|
"[ExecuteOfficerProfileService] Keycloak user exists:",
|
|
checkUser.length > 0,
|
|
);
|
|
if (checkUser.length == 0) {
|
|
console.log("[ExecuteOfficerProfileService] Creating new Keycloak user");
|
|
let password = item.bodyProfile.citizenId;
|
|
if (item.bodyProfile.birthDate != null) {
|
|
const _date = new Date(item.bodyProfile.birthDate.toDateString())
|
|
.getDate()
|
|
.toString()
|
|
.padStart(2, "0");
|
|
const _month = (
|
|
new Date(item.bodyProfile.birthDate.toDateString()).getMonth() + 1
|
|
)
|
|
.toString()
|
|
.padStart(2, "0");
|
|
const _year = new Date(item.bodyProfile.birthDate.toDateString()).getFullYear() + 543;
|
|
password = `${_date}${_month}${_year}`;
|
|
}
|
|
console.log(
|
|
"[ExecuteOfficerProfileService] Calling createUser for:",
|
|
item.bodyProfile.citizenId,
|
|
);
|
|
console.log(
|
|
"[ExecuteOfficerProfileService] createUser data - firstName:",
|
|
item.bodyProfile.firstName,
|
|
"lastName:",
|
|
item.bodyProfile.lastName,
|
|
);
|
|
// กรอง "." ออกจาก firstName ก่อนส่งไป keycloak (ป้องกัน . หรืออักขระอื่นๆ)
|
|
const sanitizedFirstName = item.bodyProfile.firstName?.replace(/\./g, "") ?? "";
|
|
userKeycloakId = await createUser(item.bodyProfile.citizenId, password, {
|
|
firstName: sanitizedFirstName,
|
|
lastName: item.bodyProfile.lastName,
|
|
});
|
|
if (
|
|
userKeycloakId &&
|
|
typeof userKeycloakId === "object" &&
|
|
userKeycloakId.errorMessage
|
|
) {
|
|
console.error(
|
|
"[ExecuteOfficerProfileService] createUser FAILED - field:",
|
|
userKeycloakId.field,
|
|
"errorMessage:",
|
|
userKeycloakId.errorMessage,
|
|
"params:",
|
|
userKeycloakId.params,
|
|
);
|
|
throw new HttpError(
|
|
HttpStatusCode.BAD_REQUEST,
|
|
`Keycloak validation failed: ${userKeycloakId.field} - ${userKeycloakId.errorMessage}`,
|
|
);
|
|
}
|
|
console.log(
|
|
"[ExecuteOfficerProfileService] User created in Keycloak, userKeycloakId:",
|
|
userKeycloakId,
|
|
);
|
|
result = await addUserRoles(
|
|
userKeycloakId,
|
|
list
|
|
.filter((v) => v.name === "USER")
|
|
.map((x) => ({
|
|
id: x.id,
|
|
name: x.name,
|
|
})),
|
|
);
|
|
console.log(
|
|
"[ExecuteOfficerProfileService] USER role assigned to new user, result:",
|
|
result,
|
|
);
|
|
} else {
|
|
console.log("[ExecuteOfficerProfileService] Updating existing Keycloak user");
|
|
userKeycloakId = checkUser[0].id;
|
|
console.log(
|
|
"[ExecuteOfficerProfileService] Existing userKeycloakId:",
|
|
userKeycloakId,
|
|
);
|
|
const rolesData = await getRoleMappings(userKeycloakId);
|
|
if (rolesData) {
|
|
const _delRole = rolesData.map((x: any) => ({
|
|
id: x.id,
|
|
name: x.name,
|
|
}));
|
|
console.log(
|
|
"[ExecuteOfficerProfileService] Removing old roles:",
|
|
_delRole.length,
|
|
);
|
|
await removeUserRoles(userKeycloakId, _delRole);
|
|
}
|
|
result = await addUserRoles(
|
|
userKeycloakId,
|
|
list
|
|
.filter((v) => v.name === "USER")
|
|
.map((x) => ({
|
|
id: x.id,
|
|
name: x.name,
|
|
})),
|
|
);
|
|
console.log(
|
|
"[ExecuteOfficerProfileService] USER role assigned to existing user",
|
|
);
|
|
}
|
|
|
|
let profile: any = await profileRepository.findOne({
|
|
where: { citizenId: item.bodyProfile.citizenId /*, isActive: true */ },
|
|
relations: ["roleKeycloaks", "profileInsignias", "profileAvatars"],
|
|
});
|
|
console.log(
|
|
"[ExecuteOfficerProfileService] Profile found:",
|
|
!!profile,
|
|
"for citizenId:",
|
|
item.bodyProfile.citizenId,
|
|
);
|
|
let _oldInsigniaIds: string[] = [];
|
|
let _oldSalaries: any[] = [];
|
|
//ลูกจ้างประจำ หรือ บุคคลภายนอก
|
|
if (!profile) {
|
|
console.log(
|
|
"[ExecuteOfficerProfileService] No existing profile found, creating new profile",
|
|
);
|
|
//กรณีลูกจ้างประจำมาสอบเป็นข้าราชการ ต้อง update สถานะโปรไฟล์เดิม
|
|
let profileEmployee: any = await profileEmployeeRepository.findOne({
|
|
where: { citizenId: item.bodyProfile.citizenId },
|
|
relations: ["profileInsignias", "roleKeycloaks"],
|
|
});
|
|
console.log(
|
|
"[ExecuteOfficerProfileService] Employee profile found:",
|
|
!!profileEmployee,
|
|
);
|
|
if (profileEmployee) {
|
|
console.log(
|
|
"[ExecuteOfficerProfileService] Converting employee profile to officer profile",
|
|
);
|
|
const _order = await salaryRepo.findOne({
|
|
where: { profileEmployeeId: profileEmployee.id },
|
|
order: { order: "DESC" },
|
|
});
|
|
const profileEmpSalary = new ProfileSalary();
|
|
profileEmpSalary.posNumCodeSit = _posNumCodeSit;
|
|
profileEmpSalary.posNumCodeSitAbb = _posNumCodeSitAbb;
|
|
profileEmpSalary.order = _order == null ? 1 : _order.order + 1;
|
|
Object.assign(profileEmpSalary, {
|
|
...item.bodySalarys,
|
|
...meta,
|
|
profileEmployeeId: profileEmployee.id,
|
|
profileId: undefined,
|
|
});
|
|
const history = new ProfileSalaryHistory();
|
|
Object.assign(history, { ...profileEmpSalary, id: undefined });
|
|
profileEmpSalary.dateGovernment = item.bodySalarys?.commandDateAffect ?? meta.createdAt;
|
|
(profileEmpSalary.profileId = _null),
|
|
await salaryRepo.save(profileEmpSalary, { data: req });
|
|
setLogDataDiff(req, { before, after: profileEmpSalary });
|
|
history.profileSalaryId = profileEmpSalary.id;
|
|
await salaryHistoryRepo.save(history, { data: req });
|
|
|
|
if (profileEmployee.profileInsignias.length > 0) {
|
|
_oldInsigniaIds = profileEmployee.profileInsignias?.map((x: any) => x.id) ?? [];
|
|
}
|
|
await removeProfileInOrganize(profileEmployee.id, "EMPLOYEE", manager);
|
|
if (profileEmployee.keycloak != null) {
|
|
// const delUserKeycloak = await deleteUser(profileEmployee.keycloak);
|
|
// if (delUserKeycloak) {
|
|
// Task #228
|
|
// profileEmployee.keycloak = _null;
|
|
profileEmployee.roleKeycloaks = [];
|
|
profileEmployee.isActive = false;
|
|
// }
|
|
}
|
|
profileEmployee.isLeave = true;
|
|
profileEmployee.leaveReason = "บรรจุข้าราชการ";
|
|
profileEmployee.lastUpdateUserId = ctx.user.sub;
|
|
profileEmployee.lastUpdateFullName = ctx.user.name;
|
|
profileEmployee.lastUpdatedAt = new Date();
|
|
await profileEmployeeRepository.save(profileEmployee);
|
|
setLogDataDiff(req, { before, after: profileEmployee });
|
|
}
|
|
profile = Object.assign({ ...item.bodyProfile, ...meta });
|
|
profile.dateRetire = _dateRetire;
|
|
profile.dateRetireLaw = _dateRetireLaw;
|
|
profile.roleKeycloaks = result && roleKeycloak ? [roleKeycloak] : [];
|
|
profile.keycloak =
|
|
userKeycloakId && typeof userKeycloakId === "string" ? userKeycloakId : "";
|
|
profile.registrationAddress = item.bodyProfile.registrationAddress;
|
|
profile.registrationProvinceId = registrationProvinceId
|
|
? registrationProvinceId.id
|
|
: _null;
|
|
profile.registrationDistrictId = registrationDistrictId
|
|
? registrationDistrictId.id
|
|
: _null;
|
|
profile.registrationSubDistrictId = registrationSubDistrictId
|
|
? registrationSubDistrictId.id
|
|
: _null;
|
|
profile.registrationZipCode = item.bodyProfile.registrationZipCode;
|
|
profile.currentAddress = item.bodyProfile.currentAddress;
|
|
profile.currentProvinceId = currentProvinceId ? currentProvinceId.id : _null;
|
|
profile.currentDistrictId = currentDistrictId ? currentDistrictId.id : _null;
|
|
profile.currentSubDistrictId = currentSubDistrictId ? currentSubDistrictId.id : _null;
|
|
profile.currentZipCode = item.bodyProfile.currentZipCode;
|
|
profile.email = item.bodyProfile.email;
|
|
profile.dateStart = item.bodyProfile.dateStart;
|
|
profile.amount = item.bodyProfile.amount ?? null;
|
|
profile.amountSpecial = item.bodyProfile.amountSpecial ?? null;
|
|
profile.isProbation = item.bodyProfile.isProbation;
|
|
//เพิ่มใหม่จากรับโอน
|
|
profile.rank = item?.bodyProfile?.rank || null;
|
|
profile.prefix = item?.bodyProfile?.rank || item?.bodyProfile?.prefix || null;
|
|
profile.prefixMain = item?.bodyProfile?.prefix ?? null;
|
|
profile.firstName = item.bodyProfile.firstName ?? null;
|
|
profile.lastName = item.bodyProfile.lastName ?? null;
|
|
profile.birthDate = item.bodyProfile.birthDate ?? null;
|
|
profile.gender = item.bodyProfile.gender ?? null;
|
|
profile.relationship = item.bodyProfile.relationship ?? null;
|
|
profile.religion = item.bodyProfile.religion ?? null;
|
|
profile.ethnicity = item.bodyProfile.ethnicity;
|
|
profile.nationality = item.bodyProfile.nationality ?? null;
|
|
profile.bloodGroup = item.bodyProfile.bloodGroup ?? null;
|
|
profile.phone = item.bodyProfile.phone ?? null;
|
|
|
|
console.log("[ExecuteOfficerProfileService] Saving new profile");
|
|
await profileRepository.save(profile);
|
|
console.log(
|
|
"[ExecuteOfficerProfileService] New profile saved, profileId:",
|
|
profile.id,
|
|
);
|
|
// update user attribute in keycloak
|
|
await updateUserAttributes(profile.keycloak ?? "", {
|
|
profileId: [profile.id],
|
|
prefix: [profile.prefix || ""],
|
|
});
|
|
console.log("[ExecuteOfficerProfileService] Keycloak attributes updated");
|
|
setLogDataDiff(req, { before, after: profile });
|
|
}
|
|
//ขรก.ในระบบ หรือ ขรก.ในระบบที่สถานะพ้นจากราชการ
|
|
else {
|
|
console.log(
|
|
"[ExecuteOfficerProfileService] Existing profile found, isLeave:",
|
|
profile.isLeave,
|
|
"leaveType:",
|
|
profile.leaveType,
|
|
);
|
|
//สร้างโปรไฟล์ใหม่ ถ้าสถานะพ้นราชการ คำสั่งโอนออกหรือคำสั่งขอลาออก
|
|
if (
|
|
profile.isLeave &&
|
|
["PLACEMENT_TRANSFER", "RETIRE_RESIGN"].includes(profile.leaveType)
|
|
) {
|
|
console.log(
|
|
"[ExecuteOfficerProfileService] Profile is leaving with eligible leave type, creating new profile record",
|
|
);
|
|
//ดึง profileSalary เดิม
|
|
_oldSalaries = await salaryRepo.find({
|
|
where: { profileId: profile.id },
|
|
order: { order: "ASC" },
|
|
});
|
|
if (profile.profileInsignias.length > 0) {
|
|
_oldInsigniaIds = profile.profileInsignias?.map((x: any) => x.id) ?? [];
|
|
}
|
|
profile = Object.assign({ ...item.bodyProfile, ...meta });
|
|
profile.dateRetire = _dateRetire;
|
|
profile.dateRetireLaw = _dateRetireLaw;
|
|
profile.roleKeycloaks = result && roleKeycloak ? [roleKeycloak] : [];
|
|
profile.keycloak =
|
|
userKeycloakId && typeof userKeycloakId === "string" ? userKeycloakId : "";
|
|
profile.registrationAddress = item.bodyProfile.registrationAddress;
|
|
profile.registrationProvinceId = registrationProvinceId
|
|
? registrationProvinceId.id
|
|
: _null;
|
|
profile.registrationDistrictId = registrationDistrictId
|
|
? registrationDistrictId.id
|
|
: _null;
|
|
profile.registrationSubDistrictId = registrationSubDistrictId
|
|
? registrationSubDistrictId.id
|
|
: _null;
|
|
profile.registrationZipCode = item.bodyProfile.registrationZipCode;
|
|
profile.currentAddress = item.bodyProfile.currentAddress;
|
|
profile.currentProvinceId = currentProvinceId ? currentProvinceId.id : _null;
|
|
profile.currentDistrictId = currentDistrictId ? currentDistrictId.id : _null;
|
|
profile.currentSubDistrictId = currentSubDistrictId ? currentSubDistrictId.id : _null;
|
|
profile.currentZipCode = item.bodyProfile.currentZipCode;
|
|
profile.email = item.bodyProfile.email;
|
|
profile.dateStart = item.bodyProfile.dateStart;
|
|
profile.amount = item.bodyProfile.amount ?? null;
|
|
profile.amountSpecial = item.bodyProfile.amountSpecial ?? null;
|
|
profile.isProbation = item.bodyProfile.isProbation;
|
|
profile.rank = item?.bodyProfile?.rank || null;
|
|
profile.prefix = item?.bodyProfile?.rank || item?.bodyProfile?.prefix || null;
|
|
profile.prefixMain = item?.bodyProfile?.prefix ?? null;
|
|
profile.firstName = item.bodyProfile.firstName ?? null;
|
|
profile.lastName = item.bodyProfile.lastName ?? null;
|
|
profile.birthDate = item.bodyProfile.birthDate ?? null;
|
|
profile.gender = item.bodyProfile.gender ?? null;
|
|
profile.relationship = item.bodyProfile.relationship ?? null;
|
|
profile.religion = item.bodyProfile.religion ?? null;
|
|
profile.ethnicity = item.bodyProfile.ethnicity;
|
|
profile.nationality = item.bodyProfile.nationality ?? null;
|
|
profile.bloodGroup = item.bodyProfile.bloodGroup ?? null;
|
|
profile.phone = item.bodyProfile.phone ?? null;
|
|
await profileRepository.save(profile);
|
|
console.log(
|
|
"[ExecuteOfficerProfileService] New profile record saved for leaving officer, profileId:",
|
|
profile.id,
|
|
);
|
|
setLogDataDiff(req, { before, after: profile });
|
|
} else {
|
|
console.log(
|
|
"[ExecuteOfficerProfileService] Updating existing active profile",
|
|
);
|
|
profile.roleKeycloaks = result && roleKeycloak ? [roleKeycloak] : [];
|
|
profile.keycloak =
|
|
userKeycloakId && typeof userKeycloakId === "string" ? userKeycloakId : "";
|
|
profile.isProbation = item.bodyProfile.isProbation;
|
|
profile.isLeave = item.bodyProfile.isLeave;
|
|
profile.isRetirement = false;
|
|
profile.isActive = true;
|
|
profile.isDelete = false;
|
|
profile.dateLeave = _null;
|
|
profile.dateRetire = _dateRetire;
|
|
profile.dateRetireLaw = _dateRetireLaw;
|
|
profile.registrationAddress = item.bodyProfile.registrationAddress;
|
|
profile.registrationProvinceId = registrationProvinceId
|
|
? registrationProvinceId.id
|
|
: _null;
|
|
profile.registrationDistrictId = registrationDistrictId
|
|
? registrationDistrictId.id
|
|
: _null;
|
|
profile.registrationSubDistrictId = registrationSubDistrictId
|
|
? registrationSubDistrictId.id
|
|
: _null;
|
|
profile.registrationZipCode = item.bodyProfile.registrationZipCode;
|
|
profile.currentAddress = item.bodyProfile.currentAddress;
|
|
profile.currentProvinceId = currentProvinceId ? currentProvinceId.id : _null;
|
|
profile.currentDistrictId = currentDistrictId ? currentDistrictId.id : _null;
|
|
profile.currentSubDistrictId = currentSubDistrictId ? currentSubDistrictId.id : _null;
|
|
profile.currentZipCode = item.bodyProfile.currentZipCode;
|
|
profile.email = item.bodyProfile.email;
|
|
profile.telephoneNumber = item.bodyProfile.telephoneNumber;
|
|
profile.phone = item.bodyProfile.phone;
|
|
profile.dateStart = item.bodyProfile.dateStart;
|
|
profile.amount = item.bodyProfile.amount ?? null;
|
|
profile.amountSpecial = item.bodyProfile.amountSpecial ?? null;
|
|
profile.leaveCommandId = _null;
|
|
profile.leaveCommandNo = _null;
|
|
profile.leaveRemark = _null;
|
|
profile.leaveDate = _null;
|
|
profile.leaveType = _null;
|
|
profile.leaveReason = _null;
|
|
profile.lastUpdateUserId = ctx.user.sub;
|
|
profile.lastUpdateFullName = ctx.user.name;
|
|
profile.lastUpdatedAt = new Date();
|
|
//เพิ่มใหม่จากรับโอน
|
|
profile.rank = item?.bodyProfile?.rank || null;
|
|
profile.prefix = item?.bodyProfile?.rank || item?.bodyProfile?.prefix || null;
|
|
profile.prefixMain = item?.bodyProfile?.prefix ?? null;
|
|
profile.firstName =
|
|
item.bodyProfile.firstName && item.bodyProfile.firstName != ""
|
|
? item.bodyProfile.firstName
|
|
: profile.firstName;
|
|
profile.lastName =
|
|
item.bodyProfile.lastName && item.bodyProfile.lastName != ""
|
|
? item.bodyProfile.lastName
|
|
: profile.lastName;
|
|
profile.birthDate = item.bodyProfile.birthDate
|
|
? item.bodyProfile.birthDate
|
|
: profile.birthDate;
|
|
profile.gender =
|
|
item.bodyProfile.gender && item.bodyProfile.gender != ""
|
|
? item.bodyProfile.gender
|
|
: profile.gender;
|
|
profile.relationship =
|
|
item.bodyProfile.relationship && item.bodyProfile.relationship != ""
|
|
? item.bodyProfile.relationship
|
|
: profile.relationship;
|
|
profile.religion =
|
|
item.bodyProfile.religion && item.bodyProfile.religion != ""
|
|
? item.bodyProfile.religion
|
|
: profile.religion;
|
|
profile.ethnicity =
|
|
item.bodyProfile.ethnicity && item.bodyProfile.ethnicity != ""
|
|
? item.bodyProfile.ethnicity
|
|
: profile.ethnicity;
|
|
profile.nationality =
|
|
item.bodyProfile.nationality && item.bodyProfile.nationality != ""
|
|
? item.bodyProfile.nationality
|
|
: profile.nationality;
|
|
profile.bloodGroup =
|
|
item.bodyProfile.bloodGroup && item.bodyProfile.bloodGroup != ""
|
|
? item.bodyProfile.bloodGroup
|
|
: profile.bloodGroup;
|
|
profile.phone =
|
|
item.bodyProfile.phone && item.bodyProfile.phone != ""
|
|
? item.bodyProfile.phone
|
|
: profile.phone;
|
|
await profileRepository.save(profile);
|
|
console.log(
|
|
"[ExecuteOfficerProfileService] Existing active profile updated, profileId:",
|
|
profile.id,
|
|
);
|
|
setLogDataDiff(req, { before, after: profile });
|
|
}
|
|
}
|
|
|
|
if (profile && profile.id) {
|
|
console.log(
|
|
"[ExecuteOfficerProfileService] Processing additional data for profileId:",
|
|
profile.id,
|
|
);
|
|
//Educations
|
|
if (item.bodyEducations && item.bodyEducations.length > 0) {
|
|
console.log(
|
|
"[ExecuteOfficerProfileService] Processing educations, count:",
|
|
item.bodyEducations.length,
|
|
);
|
|
for (const education of item.bodyEducations) {
|
|
const profileEdu = new ProfileEducation();
|
|
Object.assign(profileEdu, { ...education, ...meta });
|
|
const eduHistory = new ProfileEducationHistory();
|
|
Object.assign(eduHistory, { ...profileEdu, id: undefined });
|
|
profileEdu.profileId = profile.id;
|
|
const educationLevel = await profileEducationRepo.findOne({
|
|
select: ["id", "level", "profileId"],
|
|
where: { profileId: profile.id, isDeleted: false },
|
|
order: { level: "DESC" },
|
|
});
|
|
profileEdu.level = educationLevel == null ? 1 : educationLevel.level + 1;
|
|
await profileEducationRepo.save(profileEdu, { data: req });
|
|
setLogDataDiff(req, { before, after: profileEdu });
|
|
eduHistory.profileEducationId = profileEdu.id;
|
|
await profileEducationHistoryRepo.save(eduHistory, { data: req });
|
|
}
|
|
}
|
|
//Certificates
|
|
if (item.bodyCertificates && item.bodyCertificates.length > 0) {
|
|
console.log(
|
|
"[ExecuteOfficerProfileService] Processing certificates, count:",
|
|
item.bodyCertificates.length,
|
|
);
|
|
for (const cer of item.bodyCertificates) {
|
|
const profileCer = new ProfileCertificate();
|
|
Object.assign(profileCer, { ...cer, ...meta });
|
|
const cerHistory = new ProfileCertificateHistory();
|
|
Object.assign(cerHistory, { ...profileCer, id: undefined });
|
|
profileCer.profileId = profile.id;
|
|
await certificateRepo.save(profileCer, { data: req });
|
|
setLogDataDiff(req, { before, after: profileCer });
|
|
cerHistory.profileCertificateId = profileCer.id;
|
|
await certificateHistoryRepo.save(cerHistory, { data: req });
|
|
}
|
|
}
|
|
//FamilyCouple
|
|
if (item.bodyMarry != null) {
|
|
console.log("[ExecuteOfficerProfileService] Processing couple/marry data");
|
|
const profileCouple = new ProfileFamilyCouple();
|
|
const data = {
|
|
profileId: profile.id,
|
|
couple: item.bodyMarry.marry,
|
|
couplePrefix: item.bodyMarry.marryPrefix,
|
|
coupleFirstName: item.bodyMarry.marryFirstName,
|
|
coupleLastName: item.bodyMarry.marryLastName,
|
|
coupleCareer: item.bodyMarry.marryOccupation,
|
|
coupleLive: true,
|
|
};
|
|
Object.assign(profileCouple, { ...data, ...meta });
|
|
const coupleHistory = new ProfileFamilyCoupleHistory();
|
|
Object.assign(coupleHistory, { ...profileCouple, id: undefined });
|
|
profileCouple.profileId = profile.id;
|
|
await profileFamilyCoupleRepo.save(profileCouple, { data: req });
|
|
setLogDataDiff(req, { before, after: profileCouple });
|
|
coupleHistory.profileFamilyCoupleId = profileCouple.id;
|
|
await profileFamilyCoupleHistoryRepo.save(coupleHistory, { data: req });
|
|
}
|
|
//FamilyFather
|
|
if (item.bodyFather != null) {
|
|
console.log("[ExecuteOfficerProfileService] Processing father data");
|
|
const profileFather = new ProfileFamilyFather();
|
|
const data = {
|
|
profileId: profile.id,
|
|
fatherPrefix: item.bodyFather.fatherPrefix,
|
|
fatherFirstName: item.bodyFather.fatherFirstName,
|
|
fatherLastName: item.bodyFather.fatherLastName,
|
|
fatherCareer: item.bodyFather.fatherOccupation,
|
|
fatherLive: true,
|
|
};
|
|
Object.assign(profileFather, { ...data, ...meta });
|
|
const fatherHistory = new ProfileFamilyFatherHistory();
|
|
Object.assign(fatherHistory, { ...profileFather, id: undefined });
|
|
profileFather.profileId = profile.id;
|
|
await profileFamilyFatherRepo.save(profileFather, { data: req });
|
|
setLogDataDiff(req, { before, after: profileFather });
|
|
fatherHistory.profileFamilyFatherId = profileFather.id;
|
|
await profileFamilyFatherHistoryRepo.save(fatherHistory, { data: req });
|
|
}
|
|
//FamilyMother
|
|
if (item.bodyMother != null) {
|
|
console.log("[ExecuteOfficerProfileService] Processing mother data");
|
|
const profileMother = new ProfileFamilyMother();
|
|
const data = {
|
|
profileId: profile.id,
|
|
motherPrefix: item.bodyMother.motherPrefix,
|
|
motherFirstName: item.bodyMother.motherFirstName,
|
|
motherLastName: item.bodyMother.motherLastName,
|
|
motherCareer: item.bodyMother.motherOccupation,
|
|
motherLive: true,
|
|
};
|
|
Object.assign(profileMother, { ...data, ...meta });
|
|
const motherHistory = new ProfileFamilyMotherHistory();
|
|
Object.assign(motherHistory, { ...profileMother, id: undefined });
|
|
profileMother.profileId = profile.id;
|
|
await profileFamilyMotherRepo.save(profileMother, { data: req });
|
|
setLogDataDiff(req, { before, after: profileMother });
|
|
motherHistory.profileFamilyMotherId = profileMother.id;
|
|
await profileFamilyMotherHistoryRepo.save(motherHistory, { data: req });
|
|
}
|
|
//Salary
|
|
//insert profileSalary อันเก่า กรณีพ้นราชการแล้วกลับมาบรรจุ
|
|
if (_oldSalaries.length > 0) {
|
|
console.log(
|
|
"[ExecuteOfficerProfileService] Restoring old salaries, count:",
|
|
_oldSalaries.length,
|
|
);
|
|
for (const oldSal of _oldSalaries) {
|
|
const profileSal: any = new ProfileSalary();
|
|
Object.assign(profileSal, { ...oldSal, ...meta });
|
|
const salaryHistory = new ProfileSalaryHistory();
|
|
Object.assign(salaryHistory, { ...profileSal, id: undefined });
|
|
profileSal.profileId = profile.id;
|
|
await salaryRepo.save(profileSal, { data: req });
|
|
setLogDataDiff(req, { before, after: profileSal });
|
|
salaryHistory.profileSalaryId = profileSal.id;
|
|
await salaryHistoryRepo.save(salaryHistory, { data: req });
|
|
}
|
|
}
|
|
//insert item.bodySalarys ต่อจากที่ insert เดิมไปแล้ว
|
|
if (item.bodySalarys && item.bodySalarys != null) {
|
|
console.log("[ExecuteOfficerProfileService] Processing new salary data");
|
|
const dest_item = await salaryRepo.findOne({
|
|
where: { profileId: profile.id },
|
|
order: { order: "DESC" },
|
|
});
|
|
const profileSal: any = new ProfileSalary();
|
|
profileSal.posNumCodeSit = _posNumCodeSit;
|
|
profileSal.posNumCodeSitAbb = _posNumCodeSitAbb;
|
|
Object.assign(profileSal, { ...item.bodySalarys, ...meta });
|
|
const salaryHistory = new ProfileSalaryHistory();
|
|
Object.assign(salaryHistory, { ...profileSal, id: undefined });
|
|
profileSal.order = dest_item == null ? 1 : dest_item.order + 1;
|
|
profileSal.profileId = profile.id;
|
|
profileSal.dateGovernment = item.bodySalarys.commandDateAffect ?? meta.createdAt;
|
|
profileSal.amount = item.bodySalarys.amount ?? null;
|
|
profileSal.amountSpecial = item.bodySalarys.amountSpecial ?? null;
|
|
profileSal.positionSalaryAmount = item.bodySalarys.positionSalaryAmount ?? null;
|
|
profileSal.mouthSalaryAmount = item.bodySalarys.mouthSalaryAmount ?? null;
|
|
await salaryRepo.save(profileSal, { data: req });
|
|
setLogDataDiff(req, { before, after: profileSal });
|
|
salaryHistory.profileSalaryId = profileSal.id;
|
|
await salaryHistoryRepo.save(salaryHistory, { data: req });
|
|
}
|
|
//Position
|
|
if (item.bodyPosition && item.bodyPosition != null) {
|
|
console.log("[ExecuteOfficerProfileService] Processing position assignment");
|
|
// STEP 1: หา posMaster ที่จะใช้งานตาม id ที่ส่งมา (อาจเป็นตำแหน่งเก่าหรือใหม่ก็ได้)
|
|
console.log(
|
|
"[ExecuteOfficerProfileService] STEP 1: Finding posMaster, posmasterId:",
|
|
item.bodyPosition.posmasterId,
|
|
);
|
|
let posMaster = await posMasterRepository.findOne({
|
|
where: {
|
|
id: item.bodyPosition.posmasterId,
|
|
},
|
|
relations: {
|
|
orgRevision: true,
|
|
orgRoot: true,
|
|
orgChild1: true,
|
|
orgChild2: true,
|
|
orgChild3: true,
|
|
orgChild4: true,
|
|
},
|
|
});
|
|
console.log("[ExecuteOfficerProfileService] posMaster found:", !!posMaster);
|
|
|
|
// เช็คว่า posMaster ที่หามาอยู่ในโครงสร้างปัจจุบันหรือไม่
|
|
const isCurrent =
|
|
posMaster?.orgRevision?.orgRevisionIsCurrent === true &&
|
|
posMaster?.orgRevision?.orgRevisionIsDraft === false;
|
|
console.log("[ExecuteOfficerProfileService] posMaster isCurrent:", isCurrent);
|
|
|
|
// ถ้าไม่อยู่ในโครงสร้างปัจจุบัน ให้หาตัวใหม่จาก ancestorDNA
|
|
if (!isCurrent && posMaster?.ancestorDNA) {
|
|
console.log(
|
|
"[ExecuteOfficerProfileService] Finding current posMaster from 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(
|
|
"[ExecuteOfficerProfileService] Current posMaster from ancestorDNA found:",
|
|
!!posMaster,
|
|
);
|
|
}
|
|
|
|
if (posMaster == null) {
|
|
console.error(
|
|
`[ExecuteOfficerProfileService] not found posMasterId: ${item.bodyPosition.posmasterId}`,
|
|
);
|
|
throw new HttpError(HttpStatusCode.NOT_FOUND, "ไม่พบข้อมูลตำแหน่งนี้");
|
|
}
|
|
|
|
// STEP 2: เคลียร์ข้อมูลตำแหน่งเก่าที่ครองอยู่ ในโครงสร้างปัจจุบัน
|
|
console.log("[ExecuteOfficerProfileService] STEP 2: Clearing old position data");
|
|
const posMasterOld = await posMasterRepository.findOne({
|
|
where: {
|
|
current_holderId: profile.id,
|
|
orgRevisionId: posMaster.orgRevisionId,
|
|
},
|
|
});
|
|
if (posMasterOld != null) {
|
|
// เคลียร์คนครองเก่าออกจากตำแหน่งเดิม
|
|
posMasterOld.current_holderId = null;
|
|
posMasterOld.lastUpdatedAt = new Date();
|
|
}
|
|
|
|
// หา position เก่าที่เลือกไว้ แล้วเคลียร์การเลือก
|
|
const positionOld = await positionRepository.findOne({
|
|
where: {
|
|
posMasterId: posMasterOld?.id,
|
|
positionIsSelected: true,
|
|
},
|
|
});
|
|
if (positionOld != null) {
|
|
positionOld.positionIsSelected = false;
|
|
await positionRepository.save(positionOld);
|
|
}
|
|
|
|
// STEP 3: เคลียร์ position ที่เลือกไว้อื่นๆ ใน posMaster ตัวใหม่
|
|
console.log(
|
|
"[ExecuteOfficerProfileService] STEP 3: Clearing other selected positions in new 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);
|
|
}
|
|
|
|
// STEP 4: กำหนดคนครองใหม่ให้กับ posMaster
|
|
console.log(
|
|
"[ExecuteOfficerProfileService] STEP 4: Assigning new holder to posMaster",
|
|
);
|
|
posMaster.current_holderId = profile.id;
|
|
posMaster.lastUpdatedAt = new Date();
|
|
// posMaster.conditionReason = _null;
|
|
// posMaster.isCondition = false;
|
|
if (posMasterOld != null) {
|
|
await posMasterRepository.save(posMasterOld);
|
|
console.log(
|
|
`[ExecuteOfficerProfileService] Creating PosMasterHistory — posMasterId: ${posMasterOld.id}, citizenId: ${item.bodyProfile?.citizenId} (old)`,
|
|
);
|
|
await CreatePosMasterHistoryOfficer(posMasterOld.id, req, null, null, manager);
|
|
}
|
|
await posMasterRepository.save(posMaster);
|
|
console.log("[ExecuteOfficerProfileService] posMaster saved with new holder");
|
|
|
|
// STEP 5: กำหนด position ใหม่
|
|
console.log(
|
|
"[ExecuteOfficerProfileService] STEP 5: Determining position to assign",
|
|
);
|
|
// 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 ตรง
|
|
// ═══════════════════════════════════════════════════════════
|
|
console.log(
|
|
"[ExecuteOfficerProfileService] CONDITION 1: Checking by positionId:",
|
|
item.bodyPosition?.positionId,
|
|
);
|
|
if (item.bodyPosition?.positionId) {
|
|
const positionById = await positionRepository.findOne({
|
|
where: {
|
|
id: item.bodyPosition.positionId,
|
|
posMasterId: posMaster.id, // ต้องอยู่ใน posMaster ที่ถูกต้อง
|
|
},
|
|
relations: ["posExecutive"],
|
|
});
|
|
|
|
if (positionById) {
|
|
positionNew = positionById;
|
|
console.log(
|
|
"[ExecuteOfficerProfileService] CONDITION 1 matched, positionId:",
|
|
positionById.id,
|
|
);
|
|
}
|
|
}
|
|
|
|
// ═══════════════════════════════════════════════════════════
|
|
// CONDITION 2: Match 7 ฟิลด์ (ถ้า Condition 1 ไม่ match)
|
|
// ═══════════════════════════════════════════════════════════
|
|
if (!positionNew && item.bodyPosition) {
|
|
console.log(
|
|
"[ExecuteOfficerProfileService] CONDITION 1 not matched, trying CONDITION 2: Match 7 fields",
|
|
);
|
|
// สร้าง where clause แบบ dynamic - ใส่เฉพาะฟิลด์ที่ไม่ใช่ null
|
|
const whereCondition: any = {
|
|
posMasterId: posMaster.id,
|
|
positionName: item.bodyPosition.positionName,
|
|
posTypeId: item.bodyPosition.posTypeId,
|
|
posLevelId: item.bodyPosition.posLevelId,
|
|
};
|
|
|
|
if (item.bodyPosition.positionField) {
|
|
whereCondition.positionField = item.bodyPosition.positionField;
|
|
}
|
|
if (item.bodyPosition.posExecutiveId) {
|
|
whereCondition.posExecutiveId = item.bodyPosition.posExecutiveId;
|
|
}
|
|
if (item.bodyPosition.positionExecutiveField) {
|
|
whereCondition.positionExecutiveField = item.bodyPosition.positionExecutiveField;
|
|
}
|
|
if (item.bodyPosition.positionArea) {
|
|
whereCondition.positionArea = item.bodyPosition.positionArea;
|
|
}
|
|
|
|
const positionBy7Fields = await positionRepository.findOne({
|
|
where: whereCondition,
|
|
relations: ["posExecutive"],
|
|
order: { orderNo: "ASC" },
|
|
});
|
|
|
|
if (positionBy7Fields) {
|
|
positionNew = positionBy7Fields;
|
|
console.log(
|
|
"[ExecuteOfficerProfileService] CONDITION 2 matched with 7 fields, positionId:",
|
|
positionBy7Fields.id,
|
|
);
|
|
}
|
|
}
|
|
|
|
// ═══════════════════════════════════════════════════════════
|
|
// CONDITION 3: Match 3 ฟิลด์ (ถ้า Condition 2 ไม่ match)
|
|
// ═══════════════════════════════════════════════════════════
|
|
if (!positionNew && item.bodyPosition) {
|
|
console.log(
|
|
"[ExecuteOfficerProfileService] CONDITION 2 not matched, trying CONDITION 3: Match 3 fields",
|
|
);
|
|
const positionBy3Fields = await positionRepository.findOne({
|
|
where: {
|
|
posMasterId: posMaster.id,
|
|
positionName: item.bodyPosition.positionName,
|
|
posTypeId: item.bodyPosition.posTypeId,
|
|
posLevelId: item.bodyPosition.posLevelId,
|
|
},
|
|
relations: ["posExecutive"],
|
|
order: { orderNo: "ASC" },
|
|
});
|
|
|
|
if (positionBy3Fields) {
|
|
positionNew = positionBy3Fields;
|
|
console.log(
|
|
"[ExecuteOfficerProfileService] CONDITION 3 matched with 3 fields, positionId:",
|
|
positionBy3Fields.id,
|
|
);
|
|
} else {
|
|
console.log(
|
|
"[ExecuteOfficerProfileService] No position matched for profileId:",
|
|
profile.id,
|
|
);
|
|
}
|
|
}
|
|
|
|
// อัพเดท org และ posMasterNo ตลอดไม่ต้องดัก isSit
|
|
profile.posMasterNo = getPosMasterNo(posMaster);
|
|
profile.org = getOrgFullName(posMaster);
|
|
// ถ้าไม่ใช่ตำแหน่งนั่งทับ (isSit = false) ถึงจะอัพเดทตำแหน่งในทะเบียนประวัติ
|
|
if (positionNew != null) {
|
|
console.log(
|
|
"[ExecuteOfficerProfileService] Final position assignment, isSit:",
|
|
posMaster.isSit,
|
|
"positionId:",
|
|
positionNew.id,
|
|
);
|
|
positionNew.positionIsSelected = true;
|
|
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.dateStart = new Date();
|
|
}
|
|
await positionRepository.save(positionNew, { data: req });
|
|
} else if (!posMaster.isSit) {
|
|
// fallback: ตำแหน่งในโครงสร้างถูกแก้ไข ใช้ข้อมูลตำแหน่งที่สมัครสอบมา
|
|
console.log(
|
|
"[ExecuteOfficerProfileService] positionNew is null, using bodyPosition data as fallback",
|
|
);
|
|
profile.position = item.bodyPosition.positionName ?? null;
|
|
profile.posTypeId = item.bodyPosition.posTypeId ?? null;
|
|
profile.posLevelId = item.bodyPosition.posLevelId ?? null;
|
|
profile.positionField = item.bodyPosition.positionField ?? null;
|
|
profile.positionArea = item.bodyPosition.positionArea ?? null;
|
|
profile.positionExecutiveField = item.bodyPosition.positionExecutiveField ?? null;
|
|
}
|
|
await profileRepository.save(profile, { data: req });
|
|
setLogDataDiff(req, { before, after: profile });
|
|
// await CreatePosMasterHistoryOfficer(posMaster.id, req);
|
|
console.log(
|
|
`[ExecuteOfficerProfileService] Creating PosMasterHistory — posMasterId: ${posMaster.id}, citizenId: ${item.bodyProfile?.citizenId}`,
|
|
);
|
|
await CreatePosMasterHistoryOfficer(posMaster.id, req, null, {
|
|
positionId: positionNew?.id,
|
|
}, manager);
|
|
}
|
|
// Insignia
|
|
if (_oldInsigniaIds.length > 0) {
|
|
console.log(
|
|
"[ExecuteOfficerProfileService] Processing old insignias, count:",
|
|
_oldInsigniaIds.length,
|
|
);
|
|
const _insignias = await insigniaRepo.find({
|
|
where: { id: In(_oldInsigniaIds), isDeleted: false },
|
|
order: { createdAt: "ASC" },
|
|
});
|
|
for (const oldInsignia of _insignias) {
|
|
const newInsigniaData: CreateProfileInsignia = {
|
|
profileId: profile.id,
|
|
year: oldInsignia.year,
|
|
no: oldInsignia.no,
|
|
volume: oldInsignia.volume,
|
|
section: oldInsignia.section,
|
|
page: oldInsignia.page,
|
|
receiveDate: oldInsignia.receiveDate,
|
|
insigniaId: oldInsignia.insigniaId,
|
|
dateAnnounce: oldInsignia.dateAnnounce,
|
|
issue: oldInsignia.issue,
|
|
volumeNo: oldInsignia.volumeNo,
|
|
refCommandDate: oldInsignia.refCommandDate,
|
|
refCommandNo: oldInsignia.refCommandNo,
|
|
note: oldInsignia.note,
|
|
isUpload: oldInsignia.isUpload,
|
|
};
|
|
const insignia = new ProfileInsignia();
|
|
Object.assign(insignia, { ...newInsigniaData, ...meta });
|
|
const history = new ProfileInsigniaHistory();
|
|
Object.assign(history, { ...insignia, id: undefined });
|
|
await insigniaRepo.save(insignia, { data: req });
|
|
setLogDataDiff(req, { before, after: insignia });
|
|
history.profileInsigniaId = insignia.id;
|
|
await insigniaHistoryRepo.save(history, { data: req });
|
|
}
|
|
}
|
|
// เพิ่มรูปภาพโปรไฟล์
|
|
if (item.bodyProfile.objectRefId) {
|
|
console.log(
|
|
"[ExecuteOfficerProfileService] Processing profile avatar image, objectRefId:",
|
|
item.bodyProfile.objectRefId,
|
|
);
|
|
const _profileAvatar = new ProfileAvatar();
|
|
Object.assign(_profileAvatar, {
|
|
...meta,
|
|
profileId: profile.id,
|
|
profileEmployeeId: undefined,
|
|
});
|
|
if (profile.profileAvatars && profile.profileAvatars.length > 0) {
|
|
for (const avatarItem of profile.profileAvatars) {
|
|
avatarItem.isActive = false;
|
|
await avatarRepository.save(avatarItem);
|
|
}
|
|
}
|
|
await avatarRepository.save(_profileAvatar);
|
|
let avatar = `ทะเบียนประวัติ/โปรไฟล์/${profile.id}`;
|
|
let fileName = `profile-${_profileAvatar.id}`;
|
|
_profileAvatar.isActive = true;
|
|
_profileAvatar.avatar = avatar;
|
|
_profileAvatar.avatarName = fileName;
|
|
await avatarRepository.save(_profileAvatar, { data: req });
|
|
profile.avatar = avatar;
|
|
profile.avatarName = fileName;
|
|
await profileRepository.save(profile, { data: req });
|
|
const checkAvatar = await avatarRepository.findOne({
|
|
where: { avatar: avatar, avatarName: fileName },
|
|
});
|
|
if (checkAvatar && checkAvatar.profileId == null) {
|
|
checkAvatar.profileId = profile.id;
|
|
await avatarRepository.save(checkAvatar);
|
|
}
|
|
//duplicate รูปภาพโปรไฟล์โดยอิงจากรูปภาพเดิม
|
|
await new CallAPI()
|
|
.PostData(req, `/salary/file/avatar/${item.bodyProfile.objectRefId}`, {
|
|
prefix: avatar,
|
|
fileName: fileName,
|
|
})
|
|
.then(() => {})
|
|
.catch(() => {});
|
|
}
|
|
}
|
|
|
|
console.log(
|
|
`[ExecuteOfficerProfileService] Completed processOne — citizenId: ${item.bodyProfile?.citizenId}`,
|
|
);
|
|
}
|
|
}
|