From 8d20a510d44f0e9caeda2c3704191d5093e8a333 Mon Sep 17 00:00:00 2001 From: Vyacheslav Tumanov Date: Thu, 4 May 2023 17:35:07 +0500 Subject: [PATCH] TSK-1236: trigger to remove members when deleting department. Fix for already broken departments (#3120) Signed-off-by: Vyacheslav Tumanov --- models/hr/src/migration.ts | 57 +++++++++++++++++++++++- models/server-hr/src/index.ts | 9 ++++ server-plugins/hr-resources/src/index.ts | 36 +++++++++++++++ server-plugins/hr/src/index.ts | 1 + 4 files changed, 101 insertions(+), 2 deletions(-) diff --git a/models/hr/src/migration.ts b/models/hr/src/migration.ts index e0345d2957..b0b5b3c71a 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, 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' @@ -42,6 +42,57 @@ 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 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) { + 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 = [] + 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[] = parentsWithDepartmentMap.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 +232,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..b87444276a 100644 --- a/models/server-hr/src/index.ts +++ b/models/server-hr/src/index.ts @@ -33,6 +33,15 @@ export function createModel (builder: Builder): void { } }) + builder.createDoc(serverCore.class.Trigger, core.space.Model, { + 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, { 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..618b3fc510 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,40 @@ export async function OnDepartmentStaff (tx: Tx, control: TriggerControl): Promi return [] } +/** + * @public + */ +export async function OnDepartmentRemove (tx: Tx, control: TriggerControl): Promise { + const ctx = TxProcessor.extractTx(tx) as TxRemoveDoc + + 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 +434,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,