diff --git a/src/controllers/OrganizationController.ts b/src/controllers/OrganizationController.ts index 3d9871a4..9cd6dd6a 100644 --- a/src/controllers/OrganizationController.ts +++ b/src/controllers/OrganizationController.ts @@ -33,7 +33,12 @@ import { PosMaster } from "../entities/PosMaster"; import { Profile } from "../entities/Profile"; import { RequestWithUser } from "../middlewares/user"; import permission from "../interfaces/permission"; -import { checkQueueInProgress, setLogDataDiff } from "../interfaces/utils"; +import { + checkQueueInProgress, + resolveNodeId, + resolveNodeLevel, + setLogDataDiff, +} from "../interfaces/utils"; import { sendToQueueOrg, sendToQueueOrgDraft } from "../services/rabbitmq"; import { PosType } from "../entities/PosType"; import { PosLevel } from "../entities/PosLevel"; @@ -5513,6 +5518,9 @@ export class OrganizationController extends Controller { } } + const orgDna = await new permission().checkDna(request, request.user.sub); + let level: any = resolveNodeLevel(orgDna); + const orgRootData = await AppDataSource.getRepository(OrgRoot) .createQueryBuilder("orgRoot") .where("orgRoot.orgRevisionId = :id", { id }) @@ -5619,6 +5627,35 @@ export class OrganizationController extends Controller { .getMany() : []; + const cannotViewRootPosMaster = + _privilege.privilege === "PARENT" || + (_privilege.privilege === "BROTHER" && level > 1) || + (_privilege.privilege === "CHILD" && level > 0) || + (_privilege.privilege === "NORMAL" && level != 0); + + const cannotViewChild1PosMaster = + (_privilege.privilege === "PARENT" && level > 1) || + (_privilege.privilege === "BROTHER" && level > 2) || + (_privilege.privilege === "CHILD" && level > 1) || + (_privilege.privilege === "NORMAL" && level !== 1); + + const cannotViewChild2PosMaster = + (_privilege.privilege === "PARENT" && level > 2) || + (_privilege.privilege === "BROTHER" && level > 3) || + (_privilege.privilege === "CHILD" && level > 2) || + (_privilege.privilege === "NORMAL" && level !== 2); + + const cannotViewChild3PosMaster = + (_privilege.privilege === "PARENT" && level > 3) || + (_privilege.privilege === "BROTHER" && level > 4) || + (_privilege.privilege === "CHILD" && level > 3) || + (_privilege.privilege === "NORMAL" && level !== 3); + + const cannotViewChild4PosMaster = + (_privilege.privilege === "PARENT" && level > 4) || + (_privilege.privilege === "CHILD" && level > 4) || + (_privilege.privilege === "NORMAL" && level !== 4); + // const formattedData = orgRootData.map((orgRoot) => { const formattedData = await Promise.all( orgRootData.map(async (orgRoot) => { @@ -5633,27 +5670,29 @@ export class OrganizationController extends Controller { orgRootName: orgRoot.orgRootName, labelName: orgRoot.orgRootName + " " + orgRoot.orgRootCode + "00" + " " + orgRoot.orgRootShortName, - posMaster: await Promise.all( - orgRoot.posMasters - .filter( - (x) => - x.orgChild1Id == null && - // x.current_holderId != null && - x.isDirector === true, - ) - // .sort((a, b) => a.posMasterOrder - b.posMasterOrder) // Sort by posMasterOrder ASC - // .slice(0, 3) // Select the first 3 rows - .map(async (x) => ({ - posmasterId: x.id, - posNo: `${orgRoot.orgRootShortName} ${x.posMasterNo}`, - orgTreeId: orgRoot.id, - orgLevel: 0, - fullNameCurrentHolder: - x.current_holder == null - ? null - : `${x.current_holder.prefix}${x.current_holder.firstName} ${x.current_holder.lastName}`, - })), - ), + posMaster: cannotViewRootPosMaster + ? [] + : await Promise.all( + orgRoot.posMasters + .filter( + (x) => + x.orgChild1Id == null && + // x.current_holderId != null && + x.isDirector === true, + ) + // .sort((a, b) => a.posMasterOrder - b.posMasterOrder) // Sort by posMasterOrder ASC + // .slice(0, 3) // Select the first 3 rows + .map(async (x) => ({ + posmasterId: x.id, + posNo: `${orgRoot.orgRootShortName} ${x.posMasterNo}`, + orgTreeId: orgRoot.id, + orgLevel: 0, + fullNameCurrentHolder: + x.current_holder == null + ? null + : `${x.current_holder.prefix}${x.current_holder.firstName} ${x.current_holder.lastName}`, + })), + ), children: await Promise.all( orgChild1Data .filter((orgChild1) => orgChild1.orgRootId === orgRoot.id) @@ -5681,27 +5720,29 @@ export class OrganizationController extends Controller { "00" + " " + orgRoot.orgRootShortName, - posMaster: await Promise.all( - orgChild1.posMasters - .filter( - (x) => - x.orgChild2Id == null && - // x.current_holderId != null && - x.isDirector === true, - ) - // .sort((a, b) => a.posMasterOrder - b.posMasterOrder) // Sort by posMasterOrder ASC - // .slice(0, 3) // Select the first 3 rows - .map(async (x) => ({ - posmasterId: x.id, - posNo: `${orgChild1.orgChild1ShortName} ${x.posMasterNo}`, - orgTreeId: orgChild1.id, - orgLevel: 1, - fullNameCurrentHolder: - x.current_holder == null - ? null - : `${x.current_holder.prefix}${x.current_holder.firstName} ${x.current_holder.lastName}`, - })), - ), + posMaster: cannotViewChild1PosMaster + ? [] + : await Promise.all( + orgChild1.posMasters + .filter( + (x) => + x.orgChild2Id == null && + // x.current_holderId != null && + x.isDirector === true, + ) + // .sort((a, b) => a.posMasterOrder - b.posMasterOrder) // Sort by posMasterOrder ASC + // .slice(0, 3) // Select the first 3 rows + .map(async (x) => ({ + posmasterId: x.id, + posNo: `${orgChild1.orgChild1ShortName} ${x.posMasterNo}`, + orgTreeId: orgChild1.id, + orgLevel: 1, + fullNameCurrentHolder: + x.current_holder == null + ? null + : `${x.current_holder.prefix}${x.current_holder.firstName} ${x.current_holder.lastName}`, + })), + ), children: await Promise.all( orgChild2Data @@ -5737,27 +5778,29 @@ export class OrganizationController extends Controller { "00" + " " + orgRoot.orgRootShortName, - posMaster: await Promise.all( - orgChild2.posMasters - .filter( - (x) => - x.orgChild3Id == null && - // x.current_holderId != null && - x.isDirector === true, - ) - // .sort((a, b) => a.posMasterOrder - b.posMasterOrder) // Sort by posMasterOrder ASC - // .slice(0, 3) // Select the first 3 rows - .map(async (x) => ({ - posmasterId: x.id, - posNo: `${orgChild2.orgChild2ShortName} ${x.posMasterNo}`, - orgTreeId: orgChild2.id, - orgLevel: 2, - fullNameCurrentHolder: - x.current_holder == null - ? null - : `${x.current_holder.prefix}${x.current_holder.firstName} ${x.current_holder.lastName}`, - })), - ), + posMaster: cannotViewChild2PosMaster + ? [] + : await Promise.all( + orgChild2.posMasters + .filter( + (x) => + x.orgChild3Id == null && + // x.current_holderId != null && + x.isDirector === true, + ) + // .sort((a, b) => a.posMasterOrder - b.posMasterOrder) // Sort by posMasterOrder ASC + // .slice(0, 3) // Select the first 3 rows + .map(async (x) => ({ + posmasterId: x.id, + posNo: `${orgChild2.orgChild2ShortName} ${x.posMasterNo}`, + orgTreeId: orgChild2.id, + orgLevel: 2, + fullNameCurrentHolder: + x.current_holder == null + ? null + : `${x.current_holder.prefix}${x.current_holder.firstName} ${x.current_holder.lastName}`, + })), + ), children: await Promise.all( orgChild3Data @@ -5800,27 +5843,29 @@ export class OrganizationController extends Controller { "00" + " " + orgRoot.orgRootShortName, - posMaster: await Promise.all( - orgChild3.posMasters - .filter( - (x) => - x.orgChild4Id == null && - // x.current_holderId != null && - x.isDirector === true, - ) - // .sort((a, b) => a.posMasterOrder - b.posMasterOrder) // Sort by posMasterOrder ASC - // .slice(0, 3) // Select the first 3 rows - .map(async (x) => ({ - posmasterId: x.id, - posNo: `${orgChild3.orgChild3ShortName} ${x.posMasterNo}`, - orgTreeId: orgChild3.id, - orgLevel: 3, - fullNameCurrentHolder: - x.current_holder == null - ? null - : `${x.current_holder.prefix}${x.current_holder.firstName} ${x.current_holder.lastName}`, - })), - ), + posMaster: cannotViewChild3PosMaster + ? [] + : await Promise.all( + orgChild3.posMasters + .filter( + (x) => + x.orgChild4Id == null && + // x.current_holderId != null && + x.isDirector === true, + ) + // .sort((a, b) => a.posMasterOrder - b.posMasterOrder) // Sort by posMasterOrder ASC + // .slice(0, 3) // Select the first 3 rows + .map(async (x) => ({ + posmasterId: x.id, + posNo: `${orgChild3.orgChild3ShortName} ${x.posMasterNo}`, + orgTreeId: orgChild3.id, + orgLevel: 3, + fullNameCurrentHolder: + x.current_holder == null + ? null + : `${x.current_holder.prefix}${x.current_holder.firstName} ${x.current_holder.lastName}`, + })), + ), children: await Promise.all( orgChild4Data @@ -5870,26 +5915,28 @@ export class OrganizationController extends Controller { "00" + " " + orgRoot.orgRootShortName, - posMaster: await Promise.all( - orgChild4.posMasters - .filter( - (x) => - // x.current_holderId != null && - x.isDirector === true, - ) - // .sort((a, b) => a.posMasterOrder - b.posMasterOrder) // Sort by posMasterOrder ASC - // .slice(0, 3) // Select the first 3 rows - .map(async (x) => ({ - posmasterId: x.id, - posNo: `${orgChild4.orgChild4ShortName} ${x.posMasterNo}`, - orgTreeId: orgChild4.id, - orgLevel: 4, - fullNameCurrentHolder: - x.current_holder == null - ? null - : `${x.current_holder.prefix}${x.current_holder.firstName} ${x.current_holder.lastName}`, - })), - ), + posMaster: cannotViewChild4PosMaster + ? [] + : await Promise.all( + orgChild4.posMasters + .filter( + (x) => + // x.current_holderId != null && + x.isDirector === true, + ) + // .sort((a, b) => a.posMasterOrder - b.posMasterOrder) // Sort by posMasterOrder ASC + // .slice(0, 3) // Select the first 3 rows + .map(async (x) => ({ + posmasterId: x.id, + posNo: `${orgChild4.orgChild4ShortName} ${x.posMasterNo}`, + orgTreeId: orgChild4.id, + orgLevel: 4, + fullNameCurrentHolder: + x.current_holder == null + ? null + : `${x.current_holder.prefix}${x.current_holder.firstName} ${x.current_holder.lastName}`, + })), + ), })), ), })), diff --git a/src/controllers/ProfileSalaryTempController.ts b/src/controllers/ProfileSalaryTempController.ts index b85bc85b..40c0003c 100644 --- a/src/controllers/ProfileSalaryTempController.ts +++ b/src/controllers/ProfileSalaryTempController.ts @@ -1339,9 +1339,7 @@ export class ProfileSalaryTempController extends Controller { // add insert to profile salary backup const profileSalaryBeforeDelete = await queryRunner.manager.find(ProfileSalary, { where: { - ...(isOfficer - ? { profileId: body.profileId } - : { profileEmployeeId: body.profileId }), + ...(isOfficer ? { profileId: body.profileId } : { profileEmployeeId: body.profileId }), }, }); @@ -1350,7 +1348,7 @@ export class ProfileSalaryTempController extends Controller { queryRunner.manager.create(ProfileSalaryBackup, { ...salary, profileSalaryId: id, - }) + }), ); await queryRunner.manager.insert(ProfileSalaryBackup, backupRows); } @@ -1389,17 +1387,11 @@ export class ProfileSalaryTempController extends Controller { lastUpdatedAt: dateNow, }; - const salaryRows = toInsert.filter( - x => !["17", "18"].includes(x.commandCode) - ); - // ส่งไปรักษาการ - const actPositionRows = toInsert.filter( - x => x.commandCode === "17" - ); + const salaryRows = toInsert.filter((x) => !["17", "18"].includes(x.commandCode)); + // ส่งไปรักษาการ + const actPositionRows = toInsert.filter((x) => x.commandCode === "17"); // ส่งไปช่วยราชการ - const assistanceRows = toInsert.filter( - x => x.commandCode === "18" - ); + const assistanceRows = toInsert.filter((x) => x.commandCode === "18"); if (salaryRows.length) { await queryRunner.manager.insert( @@ -1407,14 +1399,14 @@ export class ProfileSalaryTempController extends Controller { salaryRows.map(({ id, ...data }) => ({ ...data, ...metaCreated, - })) + })), ); } - if (actPositionRows.length) { + if (actPositionRows.length > 0) { await queryRunner.manager.insert( ProfileActposition, - actPositionRows.map(x => ({ + actPositionRows.map((x) => ({ profileId: x.profileId, profileEmployeeId: x.profileEmployeeId, dateStart: x.commandDateAffect, @@ -1427,14 +1419,14 @@ export class ProfileSalaryTempController extends Controller { status: false, isDeleted: false, ...metaCreated, - })) + })), ); } - - if (assistanceRows.length) { + + if (assistanceRows.length > 0) { await queryRunner.manager.insert( ProfileAssistance, - assistanceRows.map(x => ({ + assistanceRows.map((x) => ({ profileId: x.profileId, profileEmployeeId: x.profileEmployeeId, agency: x.orgRoot, @@ -1448,7 +1440,7 @@ export class ProfileSalaryTempController extends Controller { status: "DONE", isUpload: false, ...metaCreated, - })) + })), ); } } @@ -1457,6 +1449,7 @@ export class ProfileSalaryTempController extends Controller { if (backupTemp.length > 0) { const insertBackupTemp = toInsert.map(({ id, ...data }) => ({ ...data, + salaryId: null, })); await queryRunner.manager.insert(ProfileSalaryTemp, insertBackupTemp); diff --git a/src/entities/ProfileSalaryTemp.ts b/src/entities/ProfileSalaryTemp.ts index 9b13d071..97e74ec7 100644 --- a/src/entities/ProfileSalaryTemp.ts +++ b/src/entities/ProfileSalaryTemp.ts @@ -273,8 +273,9 @@ export class ProfileSalaryTemp extends EntityBase { length: 40, comment: "คีย์นอก(FK)ของตาราง profileSalary", default: null, + type: "varchar", }) - salaryId: string; + salaryId: string | null; @Column({ nullable: true, @@ -292,7 +293,7 @@ export class ProfileSalaryTemp extends EntityBase { }) posNumCodeSitAbb: string; - @Column({ + @Column({ nullable: true, length: 255, comment: "ด้านทางการบริหาร", diff --git a/src/interfaces/permission.ts b/src/interfaces/permission.ts index 1542ce45..fa61df3d 100644 --- a/src/interfaces/permission.ts +++ b/src/interfaces/permission.ts @@ -305,6 +305,22 @@ class CheckAuth { public async PermissionOrgUserUpdate(req: RequestWithUser, system: string, profileId: string) { return await this.PermissionOrgByUser(req, system, "UPDATE", profileId); } + + public async checkDna(request: RequestWithUser, keycloakId: any) { + try { + const result = await new CallAPI().GetData( + request, + `/org/finddna-by-keycloak/${keycloakId}`, + false + ); + + return result; + } catch (error) { + console.error("Error calling API:", error); + throw error; + } + } + } export default CheckAuth; diff --git a/src/interfaces/utils.ts b/src/interfaces/utils.ts index e572f038..d3409187 100644 --- a/src/interfaces/utils.ts +++ b/src/interfaces/utils.ts @@ -72,7 +72,7 @@ export async function calculateGovAge(profileId: string, type: string, bkk?: boo }); // console.log("endDateFristRec", endDateFristRec.dateGovernment); - const calculateDuration = (startDate: any, endDate: any , inclusive = false) => { + const calculateDuration = (startDate: any, endDate: any, inclusive = false) => { if (inclusive) { endDate = new Date(endDate.getTime() + 86400000); // +1 วัน } @@ -107,9 +107,9 @@ export async function calculateGovAge(profileId: string, type: string, bkk?: boo }); } // const firstStartDate = new Date(records[0].date); - const firstStartDate = !bkk - ? profile?.dateAppoint ? profile?.dateAppoint : new Date() - : profile?.dateStart ? profile?.dateStart : new Date() ; + const firstStartDate = !bkk + ? profile?.dateAppoint ? profile?.dateAppoint : new Date() + : profile?.dateStart ? profile?.dateStart : new Date(); const firstEndDate = endDateFristRec ? new Date(endDateFristRec.dateGovernment) : new Date(); // console.log("firstStartDate1", firstStartDate); @@ -143,37 +143,37 @@ export async function calculateGovAge(profileId: string, type: string, bkk?: boo for (let i = 0; i < records_middle.length; i++) { const current = records_middle[i]; const next = records_middle[i + 1]; - + // นับเฉพาะช่วงที่เป็นราชการ if (current.isGovernment === true) { const startDate = new Date(current.dateGovernment); const endDate = next ? new Date(next.dateGovernment) : new Date(); const { years, months, days } = calculateDuration(startDate, endDate); - + totalYears2 += years; totalMonths2 += months; totalDays2 += days; - + // console.log(`✔ นับช่วง ${startDate.toISOString()} → ${endDate.toISOString()} ได้ ${years} ปี ${months} เดือน ${days} วัน`); - } + } // else { // console.log(`❌ ไม่รวมช่วง ${current.dateGovernment} เพราะ isGovernment = false`); // } } - + //ตั้งแต่วันที่กลับมารับราขการไปจนถึงรวมถึงวันที่ปัจจุบัน - const adjustTotal = (years:number, months:number, days:number) => { + const adjustTotal = (years: number, months: number, days: number) => { const daysInThisMonth = new Date(new Date().getFullYear(), new Date().getMonth() + 1, 0).getDate(); if (days >= daysInThisMonth) { months += Math.floor(days / daysInThisMonth); days %= daysInThisMonth; } - + if (months >= 12) { years += Math.floor(months / 12); months %= 12; } - + return { years, months, days }; }; @@ -192,10 +192,10 @@ export async function calculateGovAge(profileId: string, type: string, bkk?: boo const finalAdjusted = adjustTotal(sumYears, sumMonths, sumDays); return { - year: finalAdjusted.years, - month: finalAdjusted.months, - day: finalAdjusted.days, -}; + year: finalAdjusted.years, + month: finalAdjusted.months, + day: finalAdjusted.days, + }; } export function calculateRetireDate(birthDate: Date) { @@ -262,11 +262,11 @@ export async function removeProfileInOrganize(profileId: string, type: string) { .getOne(); const draftRevision = await AppDataSource.getRepository(OrgRevision) - .createQueryBuilder("orgRevision") - .where("orgRevision.orgRevisionIsDraft = true") - .andWhere("orgRevision.orgRevisionIsCurrent = false") - .getOne(); - + .createQueryBuilder("orgRevision") + .where("orgRevision.orgRevisionIsDraft = true") + .andWhere("orgRevision.orgRevisionIsCurrent = false") + .getOne(); + if (!currentRevision && !draftRevision) { return; } @@ -354,31 +354,31 @@ export async function removeProfileInOrganize(profileId: string, type: string) { } export async function removePostMasterAct(profileId: string) { - const currentRevision = await AppDataSource.getRepository(OrgRevision) - .createQueryBuilder("orgRevision") - .where("orgRevision.orgRevisionIsDraft = false") - .andWhere("orgRevision.orgRevisionIsCurrent = true") - .getOne(); + const currentRevision = await AppDataSource.getRepository(OrgRevision) + .createQueryBuilder("orgRevision") + .where("orgRevision.orgRevisionIsDraft = false") + .andWhere("orgRevision.orgRevisionIsCurrent = true") + .getOne(); - if (!currentRevision) { - return; - } + if (!currentRevision) { + return; + } - const findProfileInposMaster = await AppDataSource.getRepository(PosMaster) - .createQueryBuilder("posMaster") - .where("posMaster.orgRevisionId = :orgRevisionId", { orgRevisionId: currentRevision?.id }) - .andWhere("posMaster.current_holderId = :profileId", { profileId }) - .getOne(); + const findProfileInposMaster = await AppDataSource.getRepository(PosMaster) + .createQueryBuilder("posMaster") + .where("posMaster.orgRevisionId = :orgRevisionId", { orgRevisionId: currentRevision?.id }) + .andWhere("posMaster.current_holderId = :profileId", { profileId }) + .getOne(); - if (!findProfileInposMaster) { - return; - } + if (!findProfileInposMaster) { + return; + } - const posMasterAct = await AppDataSource.getRepository(PosMasterAct) - .createQueryBuilder("posMasterAct") - .where("posMasterAct.posMasterChildId = :posMasterChildId", { posMasterChildId: findProfileInposMaster.id }) - .getMany(); - await AppDataSource.getRepository(PosMasterAct).remove(posMasterAct); + const posMasterAct = await AppDataSource.getRepository(PosMasterAct) + .createQueryBuilder("posMasterAct") + .where("posMasterAct.posMasterChildId = :posMasterChildId", { posMasterChildId: findProfileInposMaster.id }) + .getMany(); + await AppDataSource.getRepository(PosMasterAct).remove(posMasterAct); } export async function checkReturnCommandType(commandId: string) { @@ -567,22 +567,22 @@ export function editLogSequence(req: RequestWithUser, index: number, data: LogSe export async function checkQueueInProgress(queueName: string) { const axios = require('axios'); - // console.log("Checking queue in progress"); - const res = await axios.get(`${process.env.RABBIT_API_URL}/api/queues/%2F/${queueName}`, { - auth: { username: process.env.RABBIT_USER , password: process.env.RABBIT_PASS }, - }); + // console.log("Checking queue in progress"); + const res = await axios.get(`${process.env.RABBIT_API_URL}/api/queues/%2F/${queueName}`, { + auth: { username: process.env.RABBIT_USER, password: process.env.RABBIT_PASS }, + }); - const q = res.data; + const q = res.data; - // console.log(`Queue "${queueName}" has:`); - // console.log(` - ${q.messages_ready} messages ready`); - // console.log(` - ${q.messages_unacknowledged} messages in progress (unacked)`); + // console.log(`Queue "${queueName}" has:`); + // console.log(` - ${q.messages_ready} messages ready`); + // console.log(` - ${q.messages_unacknowledged} messages in progress (unacked)`); - if (q.messages_unacknowledged > 0) { - return true; - } + if (q.messages_unacknowledged > 0) { + return true; + } - return false; + return false; } export function chunkArray(array: any, size: number) { @@ -594,7 +594,7 @@ export function chunkArray(array: any, size: number) { } export async function PayloadSendNoti(commandId: string) { - if (!commandId) + if (!commandId) return ""; const commandRepository = AppDataSource.getRepository(Command); const _command = await commandRepository.findOne({ @@ -606,8 +606,8 @@ export async function PayloadSendNoti(commandId: string) { if (!_command || !_command.commandType) return ""; const _payload = { - name: _command && _command.commandType - ? `คำสั่ง${_command.commandType.name}` + name: _command && _command.commandType + ? `คำสั่ง${_command.commandType.name}` : "", url: `${process.env.API_URL}/salary/file/ระบบออกคำสั่ง/คำสั่ง/${commandId}/คำสั่ง`, isReport: true, @@ -616,8 +616,8 @@ export async function PayloadSendNoti(commandId: string) { let attachments = {} if (_command.commandType.isUploadAttachment === true) { const _payloadAtt = { - name: _command && _command.commandType - ? `เอกสารแนบท้ายคำสั่ง${_command.commandType.name}` + name: _command && _command.commandType + ? `เอกสารแนบท้ายคำสั่ง${_command.commandType.name}` : "", url: `${process.env.API_URL}/salary/file/ระบบออกคำสั่ง/แนบท้าย/${commandId}/แนบท้าย`, isReport: true, @@ -733,3 +733,23 @@ export function commandTypePath(commandCode: string): string | null { return null; } } + +export function resolveNodeLevel(data: any) { + if (data.child4DnaId) return 4; + if (data.child3DnaId) return 3; + if (data.child2DnaId) return 2; + if (data.child1DnaId) return 1; + if (data.rootDnaId) return 0; + return null; +} + +export function resolveNodeId(data: any) { + return ( + data.child4DnaId ?? + data.child3DnaId ?? + data.child2DnaId ?? + data.child1DnaId ?? + data.rootDnaId ?? + null + ); +} \ No newline at end of file