hrms-api-org/src/services/ExecuteOfficerProfileService.ts
harid 3d2fc5128a
All checks were successful
Build & Deploy on Dev / build (push) Successful in 1m11s
Linear Flow discipline + organization #224
2026-06-26 18:09:45 +07:00

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}`,
);
}
}