mirror of
https://github.com/hcengineering/platform.git
synced 2026-08-17 18:05:42 +02:00
Merge branch 'develop' of https://github.com/hcengineering/platform into staging-new
This commit is contained in:
@@ -31,7 +31,7 @@ jobs:
|
||||
"containerd-snapshotter": true
|
||||
}
|
||||
}
|
||||
- uses: actions/checkout@v6
|
||||
- uses: actions/checkout@v7
|
||||
with:
|
||||
fetch-depth: 1
|
||||
filter: tree:0
|
||||
|
||||
+26
-26
@@ -68,7 +68,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 60
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
- uses: actions/checkout@v7
|
||||
with:
|
||||
ref: ${{ github.event.inputs.ref || github.ref }}
|
||||
fetch-depth: 0
|
||||
@@ -78,7 +78,7 @@ jobs:
|
||||
with:
|
||||
node-version-file: '.nvmrc'
|
||||
- name: Cache node modules
|
||||
uses: actions/cache@v5
|
||||
uses: actions/cache@v6
|
||||
env:
|
||||
cache-name: cache-node-platform
|
||||
with:
|
||||
@@ -127,7 +127,7 @@ jobs:
|
||||
run: node common/scripts/install-run-rush.js validate
|
||||
|
||||
- name: Cache build results
|
||||
uses: actions/cache@v5
|
||||
uses: actions/cache@v6
|
||||
env:
|
||||
cache-name: cache-build-results
|
||||
with:
|
||||
@@ -139,7 +139,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 60
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
- uses: actions/checkout@v7
|
||||
with:
|
||||
fetch-depth: 0
|
||||
filter: tree:0
|
||||
@@ -150,7 +150,7 @@ jobs:
|
||||
node-version-file: '.nvmrc'
|
||||
|
||||
- name: Cache build results
|
||||
uses: actions/cache@v5
|
||||
uses: actions/cache@v6
|
||||
env:
|
||||
cache-name: cache-build-results
|
||||
with:
|
||||
@@ -165,7 +165,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 60
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
- uses: actions/checkout@v7
|
||||
with:
|
||||
fetch-depth: 0
|
||||
submodules: recursive
|
||||
@@ -173,7 +173,7 @@ jobs:
|
||||
with:
|
||||
node-version-file: '.nvmrc'
|
||||
- name: Cache build results
|
||||
uses: actions/cache@v5
|
||||
uses: actions/cache@v6
|
||||
env:
|
||||
cache-name: cache-build-results
|
||||
with:
|
||||
@@ -196,7 +196,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 60
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
- uses: actions/checkout@v7
|
||||
with:
|
||||
fetch-depth: 0
|
||||
filter: tree:0
|
||||
@@ -205,7 +205,7 @@ jobs:
|
||||
with:
|
||||
node-version-file: '.nvmrc'
|
||||
- name: Cache build results
|
||||
uses: actions/cache@v5
|
||||
uses: actions/cache@v6
|
||||
env:
|
||||
cache-name: cache-build-results
|
||||
with:
|
||||
@@ -234,7 +234,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 60
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
- uses: actions/checkout@v7
|
||||
with:
|
||||
fetch-depth: 0
|
||||
filter: tree:0
|
||||
@@ -243,7 +243,7 @@ jobs:
|
||||
with:
|
||||
node-version-file: '.nvmrc'
|
||||
- name: Cache node modules
|
||||
uses: actions/cache@v5
|
||||
uses: actions/cache@v6
|
||||
env:
|
||||
cache-name: cache-node-platform
|
||||
with:
|
||||
@@ -322,7 +322,7 @@ jobs:
|
||||
name: playwright-results
|
||||
path: ./tests/sanity/playwright-report/
|
||||
# - name: Get Allure history
|
||||
# uses: actions/checkout@v6
|
||||
# uses: actions/checkout@v7
|
||||
# if: ${{ github.ref == 'refs/heads/main' || startsWith(github.ref, 'refs/tags/v') }}
|
||||
# continue-on-error: true
|
||||
# with:
|
||||
@@ -366,7 +366,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 60
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
- uses: actions/checkout@v7
|
||||
with:
|
||||
fetch-depth: 0
|
||||
filter: tree:0
|
||||
@@ -375,7 +375,7 @@ jobs:
|
||||
with:
|
||||
node-version-file: '.nvmrc'
|
||||
- name: Cache node modules
|
||||
uses: actions/cache@v5
|
||||
uses: actions/cache@v6
|
||||
env:
|
||||
cache-name: cache-node-platform
|
||||
with:
|
||||
@@ -463,7 +463,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 60
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
- uses: actions/checkout@v7
|
||||
with:
|
||||
fetch-depth: 0
|
||||
filter: tree:0
|
||||
@@ -472,7 +472,7 @@ jobs:
|
||||
with:
|
||||
node-version-file: '.nvmrc'
|
||||
- name: Cache node modules
|
||||
uses: actions/cache@v5
|
||||
uses: actions/cache@v6
|
||||
env:
|
||||
cache-name: cache-node-platform
|
||||
with:
|
||||
@@ -544,7 +544,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 60
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
- uses: actions/checkout@v7
|
||||
with:
|
||||
fetch-depth: 0
|
||||
filter: tree:0
|
||||
@@ -553,7 +553,7 @@ jobs:
|
||||
with:
|
||||
node-version-file: '.nvmrc'
|
||||
- name: Cache node modules
|
||||
uses: actions/cache@v5
|
||||
uses: actions/cache@v6
|
||||
env:
|
||||
cache-name: cache-node-platform
|
||||
with:
|
||||
@@ -641,7 +641,7 @@ jobs:
|
||||
timeout-minutes: 60
|
||||
steps:
|
||||
# https://github.com/actions/runner-images/issues/2840#issuecomment-790492173
|
||||
- uses: actions/checkout@v6
|
||||
- uses: actions/checkout@v7
|
||||
with:
|
||||
fetch-depth: 0
|
||||
filter: tree:0
|
||||
@@ -674,7 +674,7 @@ jobs:
|
||||
with:
|
||||
version: 10
|
||||
- name: Cache node modules
|
||||
uses: actions/cache@v5
|
||||
uses: actions/cache@v6
|
||||
env:
|
||||
cache-name: cache-node-platform
|
||||
with:
|
||||
@@ -744,7 +744,7 @@ jobs:
|
||||
runs-on: macos-latest
|
||||
timeout-minutes: 60
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
- uses: actions/checkout@v7
|
||||
with:
|
||||
fetch-depth: 0
|
||||
filter: tree:0
|
||||
@@ -755,7 +755,7 @@ jobs:
|
||||
node-version-file: '.nvmrc'
|
||||
|
||||
- name: Cache node modules
|
||||
uses: actions/cache@v5
|
||||
uses: actions/cache@v6
|
||||
env:
|
||||
cache-name: node
|
||||
with:
|
||||
@@ -847,7 +847,7 @@ jobs:
|
||||
runs-on: macos-latest
|
||||
timeout-minutes: 60
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
- uses: actions/checkout@v7
|
||||
with:
|
||||
fetch-depth: 0
|
||||
filter: tree:0
|
||||
@@ -858,7 +858,7 @@ jobs:
|
||||
node-version-file: '.nvmrc'
|
||||
|
||||
- name: Cache node modules
|
||||
uses: actions/cache@v5
|
||||
uses: actions/cache@v6
|
||||
env:
|
||||
cache-name: node
|
||||
with:
|
||||
@@ -949,7 +949,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 30
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
- uses: actions/checkout@v7
|
||||
with:
|
||||
ref: ${{ github.event.inputs.ref || github.ref }}
|
||||
fetch-depth: 0
|
||||
@@ -957,7 +957,7 @@ jobs:
|
||||
with:
|
||||
node-version-file: '.nvmrc'
|
||||
- name: Cache node modules
|
||||
uses: actions/cache@v5
|
||||
uses: actions/cache@v6
|
||||
env:
|
||||
cache-name: cache-node-platform
|
||||
with:
|
||||
|
||||
@@ -15,7 +15,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 30
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
- uses: actions/checkout@v7
|
||||
with:
|
||||
ref: ${{ github.event.inputs.ref }}
|
||||
fetch-depth: 0
|
||||
@@ -23,7 +23,7 @@ jobs:
|
||||
with:
|
||||
node-version-file: '.nvmrc'
|
||||
- name: Cache node modules
|
||||
uses: actions/cache@v5
|
||||
uses: actions/cache@v6
|
||||
env:
|
||||
cache-name: cache-node-platform
|
||||
with:
|
||||
|
||||
+27
@@ -0,0 +1,27 @@
|
||||
# Security Policy
|
||||
|
||||
## Reporting a Vulnerability
|
||||
|
||||
We take security seriously. If you discover a security vulnerability in the
|
||||
Huly Platform, please report it privately — **do not open a public issue.**
|
||||
|
||||
Use GitHub's private vulnerability reporting:
|
||||
|
||||
1. Go to the [Security tab](https://github.com/hcengineering/platform/security) of this repository.
|
||||
2. Click **"Report a vulnerability"**.
|
||||
3. Fill in the advisory with as much detail as possible (affected component,
|
||||
reproduction steps, impact, suggested fix if any).
|
||||
|
||||
We'll acknowledge your report, investigate, and coordinate a fix and
|
||||
disclosure timeline with you before any public disclosure.
|
||||
|
||||
## Supported Versions
|
||||
|
||||
Security fixes are applied to the latest release on the `main`/`develop`
|
||||
branch. Older versions are not guaranteed to receive patches.
|
||||
|
||||
## Scope
|
||||
|
||||
This policy covers the code in this repository. Vulnerabilities in
|
||||
third-party dependencies should be reported upstream, though we're happy to
|
||||
hear about them too if they affect the Platform directly.
|
||||
@@ -22,7 +22,8 @@ import {
|
||||
DOMAIN_MODEL_TX,
|
||||
type TxCUD
|
||||
} from '@hcengineering/core'
|
||||
import { type Department } from '@hcengineering/hr'
|
||||
import contact, { type Employee, type Person } from '@hcengineering/contact'
|
||||
import { type Department, type Staff } from '@hcengineering/hr'
|
||||
import {
|
||||
migrateSpace,
|
||||
tryMigrate,
|
||||
@@ -32,6 +33,7 @@ import {
|
||||
type MigrationUpgradeClient
|
||||
} from '@hcengineering/model'
|
||||
import core, { DOMAIN_SPACE, getAccountsFromTxes } from '@hcengineering/model-core'
|
||||
import { DOMAIN_CONTACT } from '@hcengineering/model-contact'
|
||||
|
||||
import hr, { DOMAIN_HR, hrId } from './index'
|
||||
|
||||
@@ -103,6 +105,52 @@ async function migrateDepartmentMembersToEmployee (client: MigrationClient): Pro
|
||||
}
|
||||
}
|
||||
|
||||
async function rebuildDepartmentMembersFromStaff (client: MigrationClient): Promise<void> {
|
||||
const departments = await client.find<Department>(DOMAIN_HR, { _class: hr.class.Department })
|
||||
const departmentById = new Map(departments.map((department) => [department._id, department]))
|
||||
const membersByDepartment = new Map<Ref<Department>, Set<Ref<Employee>>>(
|
||||
departments.map((department) => [department._id, new Set<Ref<Employee>>()])
|
||||
)
|
||||
|
||||
const getHierarchy = (department: Ref<Department>): Ref<Department>[] => {
|
||||
const result: Ref<Department>[] = []
|
||||
let current: Ref<Department> | undefined = department
|
||||
|
||||
while (current !== undefined) {
|
||||
if (result.includes(current)) {
|
||||
break
|
||||
}
|
||||
|
||||
const currentDepartment = departmentById.get(current)
|
||||
if (currentDepartment === undefined) {
|
||||
break
|
||||
}
|
||||
|
||||
result.push(currentDepartment._id)
|
||||
current = currentDepartment.parent ?? (currentDepartment._id !== hr.ids.Head ? hr.ids.Head : undefined)
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
const persons = await client.find<Person>(DOMAIN_CONTACT, { _class: contact.class.Person })
|
||||
for (const person of persons) {
|
||||
const staff = client.hierarchy.asIf<Person, Staff>(person, hr.mixin.Staff)
|
||||
if (staff?.department === undefined || !staff.active) {
|
||||
continue
|
||||
}
|
||||
|
||||
for (const department of getHierarchy(staff.department)) {
|
||||
membersByDepartment.get(department)?.add(person._id as Ref<Employee>)
|
||||
}
|
||||
}
|
||||
|
||||
for (const department of departments) {
|
||||
const members = Array.from(membersByDepartment.get(department._id) ?? [])
|
||||
await client.update(DOMAIN_HR, { _id: department._id }, { members })
|
||||
}
|
||||
}
|
||||
|
||||
export const hrOperation: MigrateOperation = {
|
||||
async migrate (client: MigrationClient, mode): Promise<void> {
|
||||
await tryMigrate(mode, client, hrId, [
|
||||
@@ -120,6 +168,10 @@ export const hrOperation: MigrateOperation = {
|
||||
{
|
||||
state: 'migrateDepartmentMembersToEmployee',
|
||||
func: migrateDepartmentMembersToEmployee
|
||||
},
|
||||
{
|
||||
state: 'rebuildDepartmentMembersFromStaff',
|
||||
func: rebuildDepartmentMembersFromStaff
|
||||
}
|
||||
])
|
||||
},
|
||||
|
||||
@@ -41,6 +41,14 @@ export function createModel (builder: Builder): void {
|
||||
}
|
||||
})
|
||||
|
||||
builder.createDoc(serverCore.class.Trigger, core.space.Model, {
|
||||
trigger: serverHr.trigger.OnDepartmentUpdate,
|
||||
txMatch: {
|
||||
objectClass: hr.class.Department,
|
||||
_class: core.class.TxUpdateDoc
|
||||
}
|
||||
})
|
||||
|
||||
builder.createDoc(serverCore.class.Trigger, core.space.Model, {
|
||||
trigger: serverHr.trigger.OnRequestCreate,
|
||||
txMatch: {
|
||||
|
||||
@@ -77,7 +77,8 @@
|
||||
|
||||
$: dragPersonId = dragPerson?._id
|
||||
|
||||
$: values = allEmployees.filter((it) => it.department === value._id && it._id !== dragPersonId)
|
||||
$: members = new Set<Ref<Staff>>(value.members as Ref<Staff>[])
|
||||
$: values = allEmployees.filter((it) => members.has(it._id) && it._id !== dragPersonId)
|
||||
|
||||
$: dragging = value._id === dragOver?._id && dragPersonId !== undefined
|
||||
|
||||
|
||||
@@ -13,10 +13,10 @@
|
||||
// limitations under the License.
|
||||
-->
|
||||
<script lang="ts">
|
||||
import contact from '@hcengineering/contact'
|
||||
import contact, { Employee } from '@hcengineering/contact'
|
||||
import { UsersPopup } from '@hcengineering/contact-resources'
|
||||
import { Ref, WithLookup } from '@hcengineering/core'
|
||||
import { Department, Staff } from '@hcengineering/hr'
|
||||
import { Department } from '@hcengineering/hr'
|
||||
import { createQuery, getClient } from '@hcengineering/presentation'
|
||||
import { Button, IconAdd, Label, Scroller, Section, eventToHTMLElement, showPopup } from '@hcengineering/ui'
|
||||
import { Viewlet, ViewletPreference } from '@hcengineering/view'
|
||||
@@ -42,7 +42,7 @@
|
||||
}
|
||||
)
|
||||
|
||||
function add (e: MouseEvent) {
|
||||
function add (e: MouseEvent): void {
|
||||
showPopup(
|
||||
UsersPopup,
|
||||
{
|
||||
@@ -50,19 +50,15 @@
|
||||
docQuery: {
|
||||
active: true
|
||||
},
|
||||
ignoreUsers: memberItems.map((it) => it._id)
|
||||
ignoreUsers: members
|
||||
},
|
||||
eventToHTMLElement(e),
|
||||
(res) => addMember(client, res, value)
|
||||
)
|
||||
}
|
||||
|
||||
let memberItems: Staff[] = []
|
||||
|
||||
const membersQuery = createQuery()
|
||||
$: membersQuery.query(hr.mixin.Staff, { department: objectId }, (result) => {
|
||||
memberItems = result
|
||||
})
|
||||
let members: Array<Ref<Employee>> = []
|
||||
$: members = value?.members ?? []
|
||||
|
||||
let preference: ViewletPreference | undefined
|
||||
let loading = false
|
||||
@@ -85,14 +81,14 @@
|
||||
</svelte:fragment>
|
||||
|
||||
<svelte:fragment slot="content">
|
||||
{#if (value?.members.length ?? 0) > 0}
|
||||
{#if members.length > 0}
|
||||
<Scroller>
|
||||
<Table
|
||||
_class={hr.mixin.Staff}
|
||||
config={preference?.config ?? viewlet?.config ?? []}
|
||||
options={viewlet?.options}
|
||||
query={{ department: objectId }}
|
||||
loadingProps={{ length: value?.members.length ?? 0 }}
|
||||
query={{ _id: { $in: members } }}
|
||||
loadingProps={{ length: members.length }}
|
||||
/>
|
||||
</Scroller>
|
||||
{:else}
|
||||
|
||||
@@ -50,8 +50,8 @@
|
||||
? getEndDate(currentDate.getFullYear(), 11)
|
||||
: getEndDate(currentDate.getFullYear(), currentDate.getMonth())
|
||||
|
||||
$: departments = [department, ...getDescendants(department, descendants, new Set())]
|
||||
$: staffIdsForOpenedDepartments = staff.filter((p) => departments.includes(p.department)).map((p) => p._id)
|
||||
$: departmentMembers = new Set<Ref<Staff>>((departmentById.get(department)?.members ?? []) as Ref<Staff>[])
|
||||
$: staffIdsForOpenedDepartments = staff.filter((p) => departmentMembers.has(p._id)).map((p) => p._id)
|
||||
|
||||
const lq = createQuery()
|
||||
const typeQuery = createQuery()
|
||||
@@ -81,23 +81,7 @@
|
||||
|
||||
let employeeRequests = new Map<Ref<Staff>, Request[]>()
|
||||
|
||||
function getDescendants (
|
||||
department: Ref<Department>,
|
||||
descendants: Map<Ref<Department>, Department[]>,
|
||||
visited: Set<string>
|
||||
): Ref<Department>[] {
|
||||
const res = (descendants.get(department) ?? []).map((p) => p._id)
|
||||
for (const department of res) {
|
||||
const has = visited.has(department)
|
||||
if (!has) {
|
||||
visited.add(department)
|
||||
res.push(...getDescendants(department, descendants, visited))
|
||||
}
|
||||
}
|
||||
return res
|
||||
}
|
||||
|
||||
let departmentStaff: Staff[]
|
||||
let departmentStaff: Staff[] = []
|
||||
let editableList: Ref<Employee>[] = []
|
||||
|
||||
function update (staffIdsForOpenedDepartments: Ref<Staff>[], startDate: Date, endDate: Date) {
|
||||
@@ -136,14 +120,11 @@
|
||||
function pushChilds (
|
||||
department: Ref<Department>,
|
||||
departmentStaff: Staff[],
|
||||
descendants: Map<Ref<Department>, Department[]>
|
||||
departmentById: Map<Ref<Department>, Department>
|
||||
): void {
|
||||
const staff = departmentStaff.filter((p) => p.department === department)
|
||||
const members = new Set<Ref<Staff>>((departmentById.get(department)?.members ?? []) as Ref<Staff>[])
|
||||
const staff = departmentStaff.filter((p) => members.has(p._id))
|
||||
editableList.push(...staff.filter((p) => !editableList.includes(p._id)).map((p) => p._id))
|
||||
const desc = descendants.get(department) ?? []
|
||||
for (const des of desc) {
|
||||
pushChilds(des._id, departmentStaff, descendants)
|
||||
}
|
||||
}
|
||||
|
||||
function isEditable (department: Department): boolean {
|
||||
@@ -155,17 +136,17 @@
|
||||
department: Ref<Department>,
|
||||
departmentStaff: Staff[],
|
||||
descendants: Map<Ref<Department>, Department[]>
|
||||
) {
|
||||
): void {
|
||||
const dep = departmentById.get(department)
|
||||
if (dep === undefined) return
|
||||
if (isEditable(dep)) {
|
||||
pushChilds(dep._id, departmentStaff, descendants)
|
||||
pushChilds(dep._id, departmentStaff, departmentById)
|
||||
} else {
|
||||
const descendantDepartments = descendants.get(dep._id)
|
||||
if (descendantDepartments !== undefined) {
|
||||
for (const department of descendantDepartments) {
|
||||
if (isEditable(department)) {
|
||||
pushChilds(department._id, departmentStaff, descendants)
|
||||
pushChilds(department._id, departmentStaff, departmentById)
|
||||
} else {
|
||||
checkDepartmentEditable(departmentById, department._id, departmentStaff, descendants)
|
||||
}
|
||||
@@ -178,7 +159,7 @@
|
||||
departmentById: Map<Ref<Department>, Department>,
|
||||
departmentStaff: Staff[],
|
||||
descendants: Map<Ref<Department>, Department[]>
|
||||
) {
|
||||
): void {
|
||||
editableList = [currentEmployee]
|
||||
checkDepartmentEditable(departmentById, hr.ids.Head, departmentStaff, descendants)
|
||||
editableList = editableList
|
||||
@@ -186,15 +167,16 @@
|
||||
|
||||
function updateStaff (
|
||||
staff: Staff[],
|
||||
departments: Ref<Department>[],
|
||||
staffIdsForOpenedDepartments: Ref<Staff>[],
|
||||
descendants: Map<Ref<Department>, Department[]>,
|
||||
departmentById: Map<Ref<Department>, Department>
|
||||
) {
|
||||
departmentStaff = staff.filter((p) => departments.includes(p.department))
|
||||
): void {
|
||||
const departmentMembers = new Set(staffIdsForOpenedDepartments)
|
||||
departmentStaff = staff.filter((p) => departmentMembers.has(p._id))
|
||||
updateEditableList(departmentById, departmentStaff, descendants)
|
||||
}
|
||||
|
||||
$: updateStaff(staff, departments, descendants, departmentById)
|
||||
$: updateStaff(staff, staffIdsForOpenedDepartments, descendants, departmentById)
|
||||
|
||||
const reportQuery = createQuery()
|
||||
|
||||
@@ -287,7 +269,7 @@
|
||||
}
|
||||
return map
|
||||
}
|
||||
let staffDepartmentMap = new Map()
|
||||
let staffDepartmentMap = new Map<Ref<Staff>, Department[]>()
|
||||
$: void getDepartmentsForEmployee(departmentStaff).then((res) => {
|
||||
staffDepartmentMap = res
|
||||
})
|
||||
@@ -295,18 +277,21 @@
|
||||
function getDepartmentHolidays (department: Ref<Department>): Date[] {
|
||||
const parents = ancestors.get(department) ?? []
|
||||
|
||||
const result = []
|
||||
const result = new Map<string, Date>()
|
||||
const addHoliday = (holiday: Date): void => {
|
||||
result.set(`${holiday.getFullYear()}-${holiday.getMonth()}-${holiday.getDate()}`, holiday)
|
||||
}
|
||||
|
||||
// get own holidays
|
||||
const holidays = holidaysMap.get(department) ?? []
|
||||
result.push(...holidays)
|
||||
holidays.forEach(addHoliday)
|
||||
|
||||
// get ancestor holidays
|
||||
for (const parent of parents) {
|
||||
const parentHolidays = holidaysMap.get(parent) ?? []
|
||||
result.push(...parentHolidays)
|
||||
parentHolidays.forEach(addHoliday)
|
||||
}
|
||||
return result
|
||||
return [...result.values()]
|
||||
}
|
||||
</script>
|
||||
|
||||
|
||||
@@ -14,7 +14,8 @@
|
||||
-->
|
||||
<script lang="ts">
|
||||
import { getName } from '@hcengineering/contact'
|
||||
import contact, { Avatar } from '@hcengineering/contact-resources'
|
||||
import { Avatar } from '@hcengineering/contact-resources'
|
||||
import contact from '@hcengineering/contact-resources/src/plugin'
|
||||
import hr, { Department, Staff } from '@hcengineering/hr'
|
||||
import { getClient } from '@hcengineering/presentation'
|
||||
import { Label } from '@hcengineering/ui'
|
||||
|
||||
@@ -63,6 +63,44 @@ async function getOldDepartment (
|
||||
return lastDepartment
|
||||
}
|
||||
|
||||
async function getOldDepartmentParent (
|
||||
currentTx: TxUpdateDoc<Department>,
|
||||
control: TriggerControl
|
||||
): Promise<Ref<Department> | undefined> {
|
||||
const updateTxes = await control.findAll<TxUpdateDoc<Department>>(
|
||||
control.ctx,
|
||||
core.class.TxUpdateDoc,
|
||||
{
|
||||
objectId: currentTx.objectId,
|
||||
objectClass: currentTx.objectClass
|
||||
},
|
||||
{ sort: { modifiedOn: SortingOrder.Ascending } }
|
||||
)
|
||||
|
||||
let parent: Ref<Department> | undefined
|
||||
|
||||
for (const tx of updateTxes) {
|
||||
if (tx._id === currentTx._id) break
|
||||
if (tx.operations.parent !== undefined) {
|
||||
parent = tx.operations.parent
|
||||
}
|
||||
}
|
||||
|
||||
if (parent !== undefined) return parent
|
||||
|
||||
const createTxes = await control.findAll<TxCreateDoc<Department>>(
|
||||
control.ctx,
|
||||
core.class.TxCreateDoc,
|
||||
{
|
||||
objectId: currentTx.objectId,
|
||||
objectClass: currentTx.objectClass
|
||||
},
|
||||
{ sort: { modifiedOn: SortingOrder.Ascending } }
|
||||
)
|
||||
|
||||
return createTxes[0]?.attributes.parent
|
||||
}
|
||||
|
||||
async function buildHierarchy (_id: Ref<Department>, control: TriggerControl): Promise<Department[]> {
|
||||
const res: Department[] = []
|
||||
const ancestors = new Map<Ref<Department>, Ref<Department>>()
|
||||
@@ -82,7 +120,7 @@ async function buildHierarchy (_id: Ref<Department>, control: TriggerControl): P
|
||||
}
|
||||
}
|
||||
|
||||
function exlude (first: Ref<Department>[], second: Ref<Department>[]): Ref<Department>[] {
|
||||
function exclude (first: Ref<Department>[], second: Ref<Department>[]): Ref<Department>[] {
|
||||
const set = new Set(first)
|
||||
const res: Ref<Department>[] = []
|
||||
for (const department of second) {
|
||||
@@ -96,16 +134,18 @@ function exlude (first: Ref<Department>[], second: Ref<Department>[]): Ref<Depar
|
||||
function getTxes (
|
||||
factory: TxFactory,
|
||||
employees: Ref<Employee>[],
|
||||
added: Ref<Department>[],
|
||||
added: Department[],
|
||||
removed?: Ref<Department>[]
|
||||
): Tx[] {
|
||||
const pushTxes = added
|
||||
.map((dep) =>
|
||||
employees.map((emp) =>
|
||||
factory.createTxUpdateDoc(hr.class.Department, core.space.Workspace, dep, {
|
||||
$push: { members: emp }
|
||||
})
|
||||
)
|
||||
employees
|
||||
.filter((emp) => !dep.members.includes(emp))
|
||||
.map((emp) =>
|
||||
factory.createTxUpdateDoc(hr.class.Department, core.space.Workspace, dep._id, {
|
||||
$push: { members: emp }
|
||||
})
|
||||
)
|
||||
)
|
||||
.flat()
|
||||
if (removed === undefined) return pushTxes
|
||||
@@ -146,22 +186,58 @@ export async function OnDepartmentStaff (txes: Tx[], control: TriggerControl): P
|
||||
)
|
||||
)
|
||||
}
|
||||
continue
|
||||
}
|
||||
const push = (await buildHierarchy(departmentId, control)).map((p) => p._id)
|
||||
const push = await buildHierarchy(departmentId, control)
|
||||
|
||||
if (lastDepartment === undefined) {
|
||||
result.push(...getTxes(control.txFactory, [employee], push))
|
||||
} else {
|
||||
let removed = (await buildHierarchy(lastDepartment, control)).map((p) => p._id)
|
||||
const added = exlude(removed, push)
|
||||
removed = exlude(push, removed)
|
||||
result.push(...getTxes(control.txFactory, [employee], added, removed))
|
||||
const removedDepartments = await buildHierarchy(lastDepartment, control)
|
||||
const removed = removedDepartments.map((p) => p._id)
|
||||
const pushIds = push.map((p) => p._id)
|
||||
const added = push.filter((p) => !removed.includes(p._id))
|
||||
const removedIds = exclude(pushIds, removed)
|
||||
result.push(...getTxes(control.txFactory, [employee], added, removedIds))
|
||||
}
|
||||
}
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
/**
|
||||
* @public
|
||||
*/
|
||||
export async function OnDepartmentUpdate (txes: Tx[], control: TriggerControl): Promise<Tx[]> {
|
||||
const result: Tx[] = []
|
||||
for (const tx of txes) {
|
||||
const ctx = tx as TxUpdateDoc<Department>
|
||||
if (ctx.operations.parent === undefined) continue
|
||||
|
||||
const department = await control.findAll(control.ctx, hr.class.Department, { _id: ctx.objectId })
|
||||
const currentDepartment = department[0]
|
||||
if (currentDepartment === undefined) continue
|
||||
|
||||
const members = currentDepartment.members
|
||||
if (members.length === 0) continue
|
||||
|
||||
const oldParent = await getOldDepartmentParent(ctx, control)
|
||||
if (oldParent === ctx.operations.parent) continue
|
||||
|
||||
const newParent = currentDepartment.parent
|
||||
const oldHierarchy = oldParent !== undefined ? await buildHierarchy(oldParent, control) : []
|
||||
const newHierarchy = newParent !== undefined ? await buildHierarchy(newParent, control) : []
|
||||
|
||||
const oldHierarchyIds = oldHierarchy.map((p) => p._id)
|
||||
const newHierarchyIds = newHierarchy.map((p) => p._id)
|
||||
const added = newHierarchy.filter((p) => !oldHierarchyIds.includes(p._id))
|
||||
const removed = oldHierarchy.filter((p) => !newHierarchyIds.includes(p._id)).map((p) => p._id)
|
||||
|
||||
result.push(...getTxes(control.txFactory, members, added, removed))
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
/**
|
||||
* @public
|
||||
*/
|
||||
@@ -438,6 +514,7 @@ export default async () => ({
|
||||
OnRequestUpdate,
|
||||
OnRequestRemove,
|
||||
OnDepartmentStaff,
|
||||
OnDepartmentUpdate,
|
||||
OnDepartmentRemove,
|
||||
OnEmployeeDeactivate,
|
||||
OnPublicHolidayCreate
|
||||
|
||||
@@ -31,6 +31,7 @@ export default plugin(serverHrId, {
|
||||
OnEmployee: '' as Resource<TriggerFunc>,
|
||||
OnEmployeeDeactivate: '' as Resource<TriggerFunc>,
|
||||
OnDepartmentStaff: '' as Resource<TriggerFunc>,
|
||||
OnDepartmentUpdate: '' as Resource<TriggerFunc>,
|
||||
OnDepartmentRemove: '' as Resource<TriggerFunc>,
|
||||
OnRequestCreate: '' as Resource<TriggerFunc>,
|
||||
OnRequestUpdate: '' as Resource<TriggerFunc>,
|
||||
|
||||
Reference in New Issue
Block a user