TSK-1236: trigger to remove members when deleting department. Fix for already broken departments (#3120)

Signed-off-by: Vyacheslav Tumanov <me@slavatumanov.me>
This commit is contained in:
Vyacheslav Tumanov 2023-05-04 17:35:07 +05:00 committed by GitHub
parent 239d4da6ce
commit 8d20a510d4
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 101 additions and 2 deletions

View File

@ -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<void> {
}
}
async function fixDuplicatesInDepartments (tx: TxOperations): Promise<void> {
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<void> {
const departments = await tx.findAll(hr.class.Department, {})
const parentsWithDepartmentMap: Map<Ref<Department>, Department[]> = new Map()
const departmentsMap = toIdMap(departments)
const ancestors: Map<Ref<Department>, Ref<Department>> = 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<void> {
const tx = new TxOperations(client, core.account.System)
await createSpace(tx)
await fixDuplicatesInDepartments(tx)
await fixDepartmentsFromStaff(tx)
}
}

View File

@ -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: {

View File

@ -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<Tx[]> {
const ctx = TxProcessor.extractTx(tx) as TxRemoveDoc<Department>
const department = (await control.findAll(hr.class.Department, { _id: ctx.objectSpace as Ref<Department> }))[0]
const targetAccounts = await control.modelDb.findAll(contact.class.EmployeeAccount, {
_id: { $in: department.members }
})
const employeeIds = targetAccounts.map((acc) => acc.employee as Ref<Staff>)
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
},

View File

@ -29,6 +29,7 @@ export const serverHrId = 'server-hr' as Plugin
export default plugin(serverHrId, {
trigger: {
OnDepartmentStaff: '' as Resource<TriggerFunc>,
OnDepartmentRemove: '' as Resource<TriggerFunc>,
OnRequestCreate: '' as Resource<TriggerFunc>,
OnRequestUpdate: '' as Resource<TriggerFunc>,
OnRequestRemove: '' as Resource<TriggerFunc>,