From b876d3eaff0aa808f683c9c4fe135abd0f8107dd Mon Sep 17 00:00:00 2001 From: Vyacheslav Tumanov Date: Tue, 2 May 2023 19:59:40 +0500 Subject: [PATCH 1/3] TSK-1236: trigger to remove members when deleting department. Fix for already broken departments Signed-off-by: Vyacheslav Tumanov --- models/hr/src/migration.ts | 43 ++++++++++++++++++++++-- models/server-hr/src/index.ts | 4 +++ server-plugins/hr-resources/src/index.ts | 43 ++++++++++++++++++++++++ server-plugins/hr/src/index.ts | 1 + 4 files changed, 89 insertions(+), 2 deletions(-) diff --git a/models/hr/src/migration.ts b/models/hr/src/migration.ts index e0345d2957..561130958f 100644 --- a/models/hr/src/migration.ts +++ b/models/hr/src/migration.ts @@ -13,8 +13,8 @@ // limitations under the License. // -import { Employee } from '@hcengineering/contact' -import { DOMAIN_TX, TxCollectionCUD, TxCreateDoc, TxOperations, TxUpdateDoc } from '@hcengineering/core' +import contact, { Employee } from '@hcengineering/contact' +import { DOMAIN_TX, Ref, TxCollectionCUD, TxCreateDoc, TxOperations, TxUpdateDoc } from '@hcengineering/core' import { Department, Request, TzDate } from '@hcengineering/hr' import { MigrateOperation, MigrationClient, MigrationUpgradeClient } from '@hcengineering/model' import core, { DOMAIN_SPACE } from '@hcengineering/model-core' @@ -42,6 +42,43 @@ async function createSpace (tx: TxOperations): Promise { } } +async function fixDuplicatesInDepartments (tx: TxOperations): Promise { + const departments = await tx.findAll(hr.class.Department, {}) + const departmentUpdate = departments.map((department) => { + const uniqueMembers = [...new Set(department.members)] + return tx.update(department, { members: uniqueMembers }) + }) + await Promise.all(departmentUpdate) +} + +async function fixDepartmentsFromStaff (tx: TxOperations): Promise { + const ancestors: Map, Department[]> = new Map() + const departments = await tx.findAll(hr.class.Department, {}) + for (const department of departments) { + const current = ancestors.get(department.space) ?? [] + current.push(department) + ancestors.set(department._id, current) + } + const staff = await tx.findAll(hr.mixin.Staff, {}) + const promises = [] + const employeeAccountByEmployeeMap = new Map( + (await tx.findAll(contact.class.EmployeeAccount, {})).map((ea) => [ea.employee, ea]) + ) + for (const st of staff) { + if (st.department == null) continue + const correctDepartments: Department[] = ancestors.get(st.department) ?? [] + promises.push( + ...departments + .filter((department) => !correctDepartments.includes(department)) + .map((dep) => { + const employeeAccount = employeeAccountByEmployeeMap.get(st._id) + if (employeeAccount == null) return [] + return tx.update(dep, { $pull: { members: employeeAccount._id } }) + }) + ) + } + await Promise.all(promises) +} function toTzDate (date: number): TzDate { const res = new Date(date) return { @@ -181,5 +218,7 @@ export const hrOperation: MigrateOperation = { async upgrade (client: MigrationUpgradeClient): Promise { const tx = new TxOperations(client, core.account.System) await createSpace(tx) + await fixDuplicatesInDepartments(tx) + await fixDepartmentsFromStaff(tx) } } diff --git a/models/server-hr/src/index.ts b/models/server-hr/src/index.ts index 7721310161..e6058e0d09 100644 --- a/models/server-hr/src/index.ts +++ b/models/server-hr/src/index.ts @@ -33,6 +33,10 @@ export function createModel (builder: Builder): void { } }) + builder.createDoc(serverCore.class.Trigger, core.space.Model, { + trigger: serverHr.trigger.OnDepartmentRemove + }) + builder.createDoc(serverCore.class.Trigger, core.space.Model, { trigger: serverHr.trigger.OnRequestCreate, txMatch: { diff --git a/server-plugins/hr-resources/src/index.ts b/server-plugins/hr-resources/src/index.ts index 3a8c5aef60..dc9d166e1d 100644 --- a/server-plugins/hr-resources/src/index.ts +++ b/server-plugins/hr-resources/src/index.ts @@ -24,6 +24,7 @@ import core, { TxFactory, TxMixin, TxProcessor, + TxRemoveDoc, TxUpdateDoc } from '@hcengineering/core' import hr, { @@ -155,6 +156,47 @@ export async function OnDepartmentStaff (tx: Tx, control: TriggerControl): Promi return [] } +/** + * @public + */ +export async function OnDepartmentRemove (tx: Tx, control: TriggerControl): Promise { + const actualTx = TxProcessor.extractTx(tx) + if (core.class.TxRemoveDoc !== actualTx._class) { + return [] + } + const ctx = actualTx as TxRemoveDoc + if (ctx.objectClass !== hr.class.Department) { + return [] + } + + const department = (await control.findAll(hr.class.Department, { _id: ctx.objectSpace as Ref }))[0] + + const targetAccounts = await control.modelDb.findAll(contact.class.EmployeeAccount, { + _id: { $in: department.members } + }) + const employeeIds = targetAccounts.map((acc) => acc.employee as Ref) + + const employee = await control.findAll(contact.class.Employee, { + _id: { $in: employeeIds } + }) + const removed = await buildHierarchy(department._id, control) + const res: Tx[] = [] + employee.forEach((em) => { + res.push(control.txFactory.createTxMixin(em._id, em._class, em.space, hr.mixin.Staff, { department: undefined })) + }) + targetAccounts.forEach((acc) => { + res.push( + ...getTxes( + control.txFactory, + acc._id, + [], + removed.map((p) => p._id) + ) + ) + }) + return res +} + /** * @public */ @@ -399,6 +441,7 @@ export default async () => ({ OnRequestUpdate, OnRequestRemove, OnDepartmentStaff, + OnDepartmentRemove, OnEmployeeDeactivate, OnPublicHolidayCreate }, diff --git a/server-plugins/hr/src/index.ts b/server-plugins/hr/src/index.ts index aa3cf44612..a9f4fd9270 100644 --- a/server-plugins/hr/src/index.ts +++ b/server-plugins/hr/src/index.ts @@ -29,6 +29,7 @@ export const serverHrId = 'server-hr' as Plugin export default plugin(serverHrId, { trigger: { OnDepartmentStaff: '' as Resource, + OnDepartmentRemove: '' as Resource, OnRequestCreate: '' as Resource, OnRequestUpdate: '' as Resource, OnRequestRemove: '' as Resource, From e17819039021a9e12df27e98acfd42301e3175db Mon Sep 17 00:00:00 2001 From: Vyacheslav Tumanov Date: Wed, 3 May 2023 16:54:12 +0500 Subject: [PATCH 2/3] TSK-1236: correctly make map of all parents Signed-off-by: Vyacheslav Tumanov --- models/hr/src/migration.ts | 26 ++++++++++++++++++++------ 1 file changed, 20 insertions(+), 6 deletions(-) diff --git a/models/hr/src/migration.ts b/models/hr/src/migration.ts index 561130958f..b0b5b3c71a 100644 --- a/models/hr/src/migration.ts +++ b/models/hr/src/migration.ts @@ -14,7 +14,7 @@ // import contact, { Employee } from '@hcengineering/contact' -import { DOMAIN_TX, Ref, TxCollectionCUD, TxCreateDoc, TxOperations, TxUpdateDoc } from '@hcengineering/core' +import { DOMAIN_TX, Ref, toIdMap, TxCollectionCUD, TxCreateDoc, TxOperations, TxUpdateDoc } from '@hcengineering/core' import { Department, Request, TzDate } from '@hcengineering/hr' import { MigrateOperation, MigrationClient, MigrationUpgradeClient } from '@hcengineering/model' import core, { DOMAIN_SPACE } from '@hcengineering/model-core' @@ -52,12 +52,26 @@ async function fixDuplicatesInDepartments (tx: TxOperations): Promise { } async function fixDepartmentsFromStaff (tx: TxOperations): Promise { - const ancestors: Map, Department[]> = new Map() const departments = await tx.findAll(hr.class.Department, {}) + const parentsWithDepartmentMap: Map, Department[]> = new Map() + const departmentsMap = toIdMap(departments) + const ancestors: Map, Ref> = new Map() for (const department of departments) { - const current = ancestors.get(department.space) ?? [] - current.push(department) - ancestors.set(department._id, current) + if (department._id === hr.ids.Head) continue + ancestors.set(department._id, department.space) + } + for (const departmentTest of departments) { + const parents: Department[] = parentsWithDepartmentMap.get(departmentTest._id) ?? [] + let _id = departmentTest._id + while (true) { + const department = departmentsMap.get(_id) + if (department === undefined) break + if (!parents.includes(department)) parents.push(department) + const next = ancestors.get(department._id) + if (next === undefined) break + _id = next + } + parentsWithDepartmentMap.set(departmentTest._id, parents) } const staff = await tx.findAll(hr.mixin.Staff, {}) const promises = [] @@ -66,7 +80,7 @@ async function fixDepartmentsFromStaff (tx: TxOperations): Promise { ) for (const st of staff) { if (st.department == null) continue - const correctDepartments: Department[] = ancestors.get(st.department) ?? [] + const correctDepartments: Department[] = parentsWithDepartmentMap.get(st.department) ?? [] promises.push( ...departments .filter((department) => !correctDepartments.includes(department)) From 2bf682630bafc34f78118c7f1fc26e301aa16147 Mon Sep 17 00:00:00 2001 From: Vyacheslav Tumanov Date: Thu, 4 May 2023 15:06:33 +0500 Subject: [PATCH 3/3] TSK-1236: use txMatch Signed-off-by: Vyacheslav Tumanov --- models/server-hr/src/index.ts | 7 ++++++- server-plugins/hr-resources/src/index.ts | 9 +-------- 2 files changed, 7 insertions(+), 9 deletions(-) diff --git a/models/server-hr/src/index.ts b/models/server-hr/src/index.ts index e6058e0d09..b87444276a 100644 --- a/models/server-hr/src/index.ts +++ b/models/server-hr/src/index.ts @@ -34,7 +34,12 @@ export function createModel (builder: Builder): void { }) builder.createDoc(serverCore.class.Trigger, core.space.Model, { - trigger: serverHr.trigger.OnDepartmentRemove + trigger: serverHr.trigger.OnDepartmentRemove, + txMatch: { + _class: core.class.TxCollectionCUD, + 'tx.objectClass': hr.class.Department, + 'tx._class': core.class.TxRemoveDoc + } }) builder.createDoc(serverCore.class.Trigger, core.space.Model, { diff --git a/server-plugins/hr-resources/src/index.ts b/server-plugins/hr-resources/src/index.ts index dc9d166e1d..618b3fc510 100644 --- a/server-plugins/hr-resources/src/index.ts +++ b/server-plugins/hr-resources/src/index.ts @@ -160,14 +160,7 @@ export async function OnDepartmentStaff (tx: Tx, control: TriggerControl): Promi * @public */ export async function OnDepartmentRemove (tx: Tx, control: TriggerControl): Promise { - const actualTx = TxProcessor.extractTx(tx) - if (core.class.TxRemoveDoc !== actualTx._class) { - return [] - } - const ctx = actualTx as TxRemoveDoc - if (ctx.objectClass !== hr.class.Department) { - return [] - } + const ctx = TxProcessor.extractTx(tx) as TxRemoveDoc const department = (await control.findAll(hr.class.Department, { _id: ctx.objectSpace as Ref }))[0]