Fix hr departments (#5589)

Signed-off-by: Denis Bykhov <bykhov.denis@gmail.com>
This commit is contained in:
Denis Bykhov 2024-05-13 20:31:55 +05:00 committed by GitHub
parent cb82eb7a9f
commit 1d8de7681f
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
25 changed files with 238 additions and 161 deletions

View File

@ -15,29 +15,28 @@
import { type Contact, type Employee } from '@hcengineering/contact'
import {
AccountRole,
DOMAIN_MODEL,
IndexKind,
type Arr,
type Class,
DOMAIN_MODEL,
type Domain,
IndexKind,
type Markup,
type Ref,
type Type,
AccountRole
type Type
} from '@hcengineering/core'
import {
hrId,
type Department,
type DepartmentMember,
type PublicHoliday,
type Request,
type RequestType,
type Staff,
type TzDate,
hrId
type TzDate
} from '@hcengineering/hr'
import {
ArrOf,
type Builder,
Collection,
Hidden,
Index,
@ -48,13 +47,14 @@ import {
TypeMarkup,
TypeRef,
TypeString,
UX
UX,
type Builder
} from '@hcengineering/model'
import attachment from '@hcengineering/model-attachment'
import calendar from '@hcengineering/model-calendar'
import chunter from '@hcengineering/model-chunter'
import contact, { TEmployee, TPersonAccount } from '@hcengineering/model-contact'
import core, { TAttachedDoc, TDoc, TSpace, TType } from '@hcengineering/model-core'
import core, { TAttachedDoc, TDoc, TType } from '@hcengineering/model-core'
import view, { classPresenter, createAction } from '@hcengineering/model-view'
import workbench from '@hcengineering/model-workbench'
import notification from '@hcengineering/notification'
@ -68,16 +68,20 @@ export { default } from './plugin'
export const DOMAIN_HR = 'hr' as Domain
@Model(hr.class.Department, core.class.Space)
@Model(hr.class.Department, core.class.Doc, DOMAIN_HR)
@UX(hr.string.Department, hr.icon.Department)
export class TDepartment extends TSpace implements Department {
export class TDepartment extends TDoc implements Department {
@Prop(TypeRef(hr.class.Department), hr.string.ParentDepartmentLabel)
@Index(IndexKind.Indexed)
declare space: Ref<Department>
parent?: Ref<Department>
@Prop(TypeString(), core.string.Name)
@Index(IndexKind.FullText)
declare name: string
name!: string
@Prop(TypeString(), core.string.Description)
@Index(IndexKind.FullText)
description!: string
@Prop(Collection(contact.class.Channel), contact.string.ContactInfo)
channels?: number
@ -94,7 +98,7 @@ export class TDepartment extends TSpace implements Department {
teamLead!: Ref<Employee> | null
@Prop(ArrOf(TypeRef(hr.class.DepartmentMember)), contact.string.Members)
declare members: Arr<Ref<DepartmentMember>>
members!: Arr<Ref<DepartmentMember>>
@Prop(ArrOf(TypeRef(contact.class.Contact)), hr.string.Subscribers)
subscribers?: Arr<Ref<Contact>>
@ -154,7 +158,7 @@ export class TRequest extends TAttachedDoc implements Request {
@Prop(TypeRef(hr.class.Department), hr.string.Department)
@Index(IndexKind.Indexed)
declare space: Ref<Department>
department!: Ref<Department>
@Prop(TypeRef(hr.class.RequestType), hr.string.RequestType)
@Hidden()
@ -568,4 +572,8 @@ export function createModel (builder: Builder): void {
domain: DOMAIN_HR,
disabled: [{ modifiedOn: 1 }, { modifiedBy: 1 }, { createdBy: 1 }, { attachedToClass: 1 }, { createdOn: -1 }]
})
builder.mixin(hr.class.Department, core.class.Class, view.mixin.ObjectPresenter, {
presenter: hr.component.DepartmentPresenter
})
}

View File

@ -13,29 +13,30 @@
// limitations under the License.
//
import { TxOperations } from '@hcengineering/core'
import { type Ref, TxOperations } from '@hcengineering/core'
import {
createDefaultSpace,
tryMigrate,
tryUpgrade,
type MigrateOperation,
type MigrationClient,
type MigrationUpgradeClient
} from '@hcengineering/model'
import core from '@hcengineering/model-core'
import hr, { hrId } from './index'
import core, { DOMAIN_SPACE } from '@hcengineering/model-core'
import hr, { DOMAIN_HR, hrId } from './index'
import { type Department } from '@hcengineering/hr'
async function createSpace (tx: TxOperations): Promise<void> {
const current = await tx.findOne(core.class.Space, {
async function createDepartment (tx: TxOperations): Promise<void> {
const current = await tx.findOne(hr.class.Department, {
_id: hr.ids.Head
})
if (current === undefined) {
await tx.createDoc(
hr.class.Department,
core.space.Space,
hr.space.HR,
{
name: 'Organization',
description: '',
private: false,
archived: false,
members: [],
teamLead: null,
managers: []
@ -45,15 +46,54 @@ async function createSpace (tx: TxOperations): Promise<void> {
}
}
async function migrateDepartments (client: MigrationClient): Promise<void> {
await client.update(
DOMAIN_HR,
{ _class: hr.class.PublicHoliday, space: { $ne: hr.space.HR } },
{ space: hr.space.HR }
)
const objects = await client.find(DOMAIN_HR, { space: { $ne: hr.space.HR }, _class: hr.class.Request })
for (const obj of objects) {
await client.update(DOMAIN_HR, { _id: obj._id }, { space: hr.space.HR, department: obj.space })
}
await client.move(DOMAIN_SPACE, { _class: hr.class.Department }, DOMAIN_HR)
const departments = await client.find<Department>(DOMAIN_HR, {
_class: hr.class.Department,
space: { $ne: hr.space.HR }
})
for (const department of departments) {
const upd: Partial<Department> = {
space: hr.space.HR
}
if (department._id !== hr.ids.Head) {
upd.parent = department.space as unknown as Ref<Department>
}
await client.update(DOMAIN_HR, { _id: department._id }, upd)
}
await client.update(
DOMAIN_HR,
{ _class: hr.class.Department },
{ $unset: { archived: true, private: true, owners: true, autoJoin: true } }
)
}
export const hrOperation: MigrateOperation = {
async migrate (client: MigrationClient): Promise<void> {},
async migrate (client: MigrationClient): Promise<void> {
await tryMigrate(client, hrId, [
{
state: 'migrateDepartments',
func: migrateDepartments
}
])
},
async upgrade (client: MigrationUpgradeClient): Promise<void> {
await tryUpgrade(client, hrId, [
{
state: 'create-defaults',
state: 'create-defaults-v2',
func: async (client) => {
await createDefaultSpace(client, hr.space.HR, { name: 'HR', description: 'Human Resources' })
const tx = new TxOperations(client, core.account.System)
await createSpace(tx)
await createDepartment(tx)
}
}
])

View File

@ -49,7 +49,8 @@ export default mergeIds(hrId, hr, {
EditRequest: '' as AnyComponent,
TzDatePresenter: '' as AnyComponent,
TzDateEditor: '' as AnyComponent,
RequestPresenter: '' as AnyComponent
RequestPresenter: '' as AnyComponent,
DepartmentPresenter: '' as AnyComponent
},
category: {
HR: '' as Ref<ActionCategory>

View File

@ -41,7 +41,7 @@ export type QuerySelector<T> = {
/**
* @public
*/
export type ObjQueryType<T> = (T extends Array<infer U> ? U | U[] : T) | QuerySelector<T>
export type ObjQueryType<T> = (T extends Array<infer U> ? U | U[] | QuerySelector<U> : T) | QuerySelector<T>
/**
* @public

View File

@ -7,6 +7,7 @@
"scripts": {
"build": "compile ui",
"build:docs": "api-extractor run --local",
"svelte-check": "do-svelte-check",
"format": "format src",
"build:watch": "compile ui",
"_phase:build": "compile ui",

View File

@ -7,6 +7,7 @@
"scripts": {
"build": "compile ui",
"build:docs": "api-extractor run --local",
"svelte-check": "do-svelte-check",
"format": "format src",
"build:watch": "compile ui",
"_phase:build": "compile ui",

View File

@ -14,14 +14,15 @@
-->
<script lang="ts">
import { Employee } from '@hcengineering/contact'
import { Ref } from '@hcengineering/core'
import { Card, getClient, SpaceSelector } from '@hcengineering/presentation'
import { EmployeeBox } from '@hcengineering/contact-resources'
import { Button, createFocusManager, EditBox, FocusHandler } from '@hcengineering/ui'
import { Ref } from '@hcengineering/core'
import { Card, getClient } from '@hcengineering/presentation'
import { Button, EditBox, FocusHandler, createFocusManager } from '@hcengineering/ui'
import { createEventDispatcher } from 'svelte'
import hr from '../plugin'
import DepartmentEditor from './DepartmentEditor.svelte'
export let space = hr.ids.Head
export let parent = hr.ids.Head
const dispatch = createEventDispatcher()
@ -35,11 +36,10 @@
const client = getClient()
async function createDepartment () {
const id = await client.createDoc(hr.class.Department, space, {
const id = await client.createDoc(hr.class.Department, hr.space.HR, {
name,
description: '',
private: false,
archived: false,
parent,
members: [],
teamLead: lead,
managers: []
@ -75,13 +75,7 @@
</div>
</div>
<svelte:fragment slot="header">
<SpaceSelector
_class={hr.class.Department}
label={hr.string.ParentDepartmentLabel}
bind:space
kind={'regular'}
size={'large'}
/>
<DepartmentEditor label={hr.string.ParentDepartmentLabel} bind:value={parent} kind={'regular'} size={'large'} />
</svelte:fragment>
<svelte:fragment slot="pool">
<EmployeeBox

View File

@ -16,11 +16,11 @@
import { AttachmentStyledBox } from '@hcengineering/attachment-resources'
import calendar from '@hcengineering/calendar'
import { Employee } from '@hcengineering/contact'
import { EmployeeBox } from '@hcengineering/contact-resources'
import core, { DocumentQuery, generateId, Markup, Ref } from '@hcengineering/core'
import { Request, RequestType, Staff, toTzDate } from '@hcengineering/hr'
import { Request, RequestType, Staff, timeToTzDate } from '@hcengineering/hr'
import { translate } from '@hcengineering/platform'
import { Card, createQuery, getClient } from '@hcengineering/presentation'
import { EmployeeBox } from '@hcengineering/contact-resources'
import { EmptyMarkup } from '@hcengineering/text-editor'
import ui, {
Button,
@ -75,11 +75,12 @@
if (date === undefined) return
if (type === undefined) return
if (employee === null) return
await client.addCollection(hr.class.Request, staff.department, employee, staff._class, 'requests', {
await client.addCollection(hr.class.Request, hr.space.HR, employee, staff._class, 'requests', {
type: type._id,
tzDate: toTzDate(new Date(date)),
tzDueDate: toTzDate(new Date(dueDate)),
description
tzDate: timeToTzDate(date),
tzDueDate: timeToTzDate(dueDate),
description,
department: staff.department
})
await descriptionBox.createAttachments()
}
@ -145,7 +146,7 @@
bind:this={descriptionBox}
{objectId}
_class={hr.class.Request}
space={staff.department}
space={hr.space.HR}
alwaysEdit
showButtons={false}
maxHeight={'card'}

View File

@ -16,20 +16,20 @@
import { Ref } from '@hcengineering/core'
import { Department } from '@hcengineering/hr'
import { IntlString } from '@hcengineering/platform'
import { SpaceSelector } from '@hcengineering/presentation'
import { ButtonKind, ButtonSize } from '@hcengineering/ui'
import { ObjectBox } from '@hcengineering/view-resources'
import hr from '../plugin'
export let value: Ref<Department> | undefined
export let label: IntlString = hr.string.ParentDepartmentLabel
export let onChange: (value: any) => void
export let onChange: (value: any) => void = () => {}
export let kind: ButtonKind = 'no-border'
export let size: ButtonSize = 'small'
export let justify: 'left' | 'center' = 'center'
export let width: string | undefined = undefined
</script>
<SpaceSelector
<ObjectBox
_class={hr.class.Department}
{label}
{size}
@ -37,9 +37,9 @@
{justify}
allowDeselect
{width}
showNavigate={false}
autoSelect={false}
focus={false}
bind:space={value}
bind:value
on:change={(e) => {
onChange(e.detail)
}}

View File

@ -0,0 +1,23 @@
<!--
// Copyright © 2024 Hardcore Engineering Inc.
//
// Licensed under the Eclipse Public License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License. You may
// obtain a copy of the License at https://www.eclipse.org/legal/epl-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
//
// See the License for the specific language governing permissions and
// limitations under the License.
-->
<script lang="ts">
import { Department } from '@hcengineering/hr'
export let value: Department
</script>
<span>
{value.name}
</span>

View File

@ -37,7 +37,9 @@
{
_id: objectId
},
(res) => ([value] = res)
(res) => {
value = res[0]
}
)
function add (e: MouseEvent) {

View File

@ -1,30 +1,14 @@
<script lang="ts">
import presentation, { Card, createQuery, getClient } from '@hcengineering/presentation'
import hr from '../plugin'
import { createEventDispatcher } from 'svelte'
import { DropdownLabelsIntl, Label } from '@hcengineering/ui'
import { RequestType } from '@hcengineering/hr'
import { Ref } from '@hcengineering/core'
import { Request, RequestType } from '@hcengineering/hr'
import presentation, { Card, getClient } from '@hcengineering/presentation'
import { DropdownLabelsIntl, Label } from '@hcengineering/ui'
import { createEventDispatcher } from 'svelte'
import hr from '../plugin'
const dispatch = createEventDispatcher()
export let object: Request
let types: RequestType[] = []
let type: RequestType | undefined
let newType: RequestType | undefined
let typesToChange: (RequestType | undefined)[] | undefined
const typesQuery = createQuery()
const client = getClient()
$: typesQuery.query(hr.class.RequestType, {}, (res) => {
types = res
if (object !== undefined && object.type !== undefined) {
type = types.find((t) => t._id === object.type)
typesToChange = requestPairMap.get(type?._id)?.map((t) => types.find((x) => t === x._id))
if (typesToChange !== undefined) {
newType = typesToChange[0]
}
}
})
const requestPairMap = new Map<Ref<RequestType>, Array<Ref<RequestType>>>([
[hr.ids.PTO, [hr.ids.PTO2, hr.ids.Sick, hr.ids.Vacation]],
[hr.ids.PTO2, [hr.ids.PTO]],
@ -32,21 +16,28 @@
[hr.ids.Overtime2, [hr.ids.Overtime]]
])
const client = getClient()
const types: RequestType[] = client.getModel().findAllSync(hr.class.RequestType, {})
const type = types.find((t) => t._id === object.type)
const typesToChange: RequestType[] =
type !== undefined
? (requestPairMap
.get(type._id)
?.map((t) => types.find((x) => t === x._id))
.filter((p) => p !== undefined) as RequestType[]) ?? []
: []
let newType: RequestType | undefined = typesToChange[0]
function typeSelected (_id: Ref<RequestType>): void {
newType = types.find((p) => p._id === _id)
}
async function changeType () {
await client.updateCollection(
hr.class.Request,
object.space,
object._id,
object.attachedTo,
object.attachedToClass,
object.collecttion,
{
if (newType !== undefined) {
await client.update(object, {
type: newType._id
}
)
})
}
}
</script>

View File

@ -17,7 +17,7 @@
import calendar from '@hcengineering/calendar-resources/src/plugin'
import { Employee, PersonAccount } from '@hcengineering/contact'
import { employeeByIdStore } from '@hcengineering/contact-resources'
import { DocumentQuery, getCurrentAccount, Ref } from '@hcengineering/core'
import { DocumentQuery, Ref, getCurrentAccount } from '@hcengineering/core'
import { Department, Staff } from '@hcengineering/hr'
import { createQuery } from '@hcengineering/presentation'
import type { TabItem } from '@hcengineering/ui'
@ -27,10 +27,10 @@
IconForward,
Label,
SearchEdit,
Separator,
TabList,
workbenchSeparators,
defineSeparators,
Separator
workbenchSeparators
} from '@hcengineering/ui'
import view from '@hcengineering/view'
@ -77,9 +77,11 @@
departments.clear()
descendants.clear()
for (const doc of res) {
const current = descendants.get(doc.space) ?? []
current.push(doc)
descendants.set(doc.space, current)
if (doc.parent !== undefined) {
const current = descendants.get(doc.parent) ?? []
current.push(doc)
descendants.set(doc.parent, current)
}
departments.set(doc._id, doc)
}
departments = departments

View File

@ -16,8 +16,8 @@
import { CalendarMode } from '@hcengineering/calendar-resources'
import { Employee, PersonAccount } from '@hcengineering/contact'
import contact from '@hcengineering/contact-resources/src/plugin'
import { DocumentQuery, getCurrentAccount, Ref } from '@hcengineering/core'
import { Department, fromTzDate, Request, RequestType, Staff } from '@hcengineering/hr'
import { DocumentQuery, Ref, getCurrentAccount } from '@hcengineering/core'
import { Department, DepartmentMember, Request, RequestType, Staff, fromTzDate } from '@hcengineering/hr'
import { createQuery, getClient } from '@hcengineering/presentation'
import tracker, { Issue } from '@hcengineering/tracker'
import { Label } from '@hcengineering/ui'
@ -138,7 +138,7 @@
}
function isEditable (department: Department): boolean {
return department.teamLead === currentEmployee || department.managers.includes(currentEmployee)
return department.teamLead === currentEmployee || department.managers.includes(currentEmployee as Ref<Employee>)
}
function checkDepartmentEditable (
@ -170,7 +170,7 @@
departmentStaff: Staff[],
descendants: Map<Ref<Department>, Department[]>
) {
editableList = [currentEmployee]
editableList = [currentEmployee as Ref<Employee>]
checkDepartmentEditable(departmentById, hr.ids.Head, departmentStaff, descendants)
editableList = editableList
}
@ -235,7 +235,7 @@
holidays = new Map()
for (const groupKey in group) {
holidays.set(
groupKey,
groupKey as Ref<Department>,
group[groupKey].map((holiday) => new Date(fromTzDate(holiday.date)))
)
}
@ -266,7 +266,7 @@
const ids = departmentStaff.map((staff) => staff._id)
const staffs = await client.findAll(contact.class.PersonAccount, { person: { $in: ids } })
const departments = await client.findAll(hr.class.Department, {
members: { $in: staffs.map((staff) => staff._id) }
members: { $in: staffs.map((staff) => staff._id as Ref<DepartmentMember>) }
})
staffs.forEach((staff) => {
const filteredDepartments = departments.filter((department) => department.members.includes(staff._id))

View File

@ -17,7 +17,7 @@
import { DocumentQuery, Ref, WithLookup } from '@hcengineering/core'
import type { Department, Staff } from '@hcengineering/hr'
import { createQuery } from '@hcengineering/presentation'
import { Button, eventToHTMLElement, Label, Scroller, SearchEdit, showPopup, IconAdd } from '@hcengineering/ui'
import { Button, IconAdd, Label, Scroller, SearchEdit, eventToHTMLElement, showPopup } from '@hcengineering/ui'
import hr from '../plugin'
import CreateDepartment from './CreateDepartment.svelte'
import DepartmentCard from './DepartmentCard.svelte'
@ -50,9 +50,11 @@
head = res.find((p) => p._id === hr.ids.Head)
descendants.clear()
for (const doc of res) {
const current = descendants.get(doc.space) ?? []
current.push(doc)
descendants.set(doc.space, current)
if (doc.parent !== undefined) {
const current = descendants.get(doc.parent) ?? []
current.push(doc)
descendants.set(doc.parent, current)
}
}
descendants = descendants
},

View File

@ -13,23 +13,24 @@
// limitations under the License.
-->
<script lang="ts">
import presentation, { Card, getClient, SpaceSelector } from '@hcengineering/presentation'
import { Data, Ref, Timestamp } from '@hcengineering/core'
import { Department, PublicHoliday, timeToTzDate } from '@hcengineering/hr'
import presentation, { Card, getClient } from '@hcengineering/presentation'
import { Button, DateRangePresenter, EditBox, Label } from '@hcengineering/ui'
import hr from '../../plugin'
import core, { Data, Ref } from '@hcengineering/core'
import { toTzDate, PublicHoliday, Department } from '@hcengineering/hr'
import { createEventDispatcher } from 'svelte'
import hr from '../../plugin'
import DepartmentEditor from '../DepartmentEditor.svelte'
let description: string
let title: string
export let date: Date
export let date: Timestamp
export let department: Ref<Department>
const client = getClient()
let existingHoliday: PublicHoliday | undefined = undefined
const dispatch = createEventDispatcher()
async function findHoliday () {
existingHoliday = await client.findOne(hr.class.PublicHoliday, { date: toTzDate(date) })
existingHoliday = await client.findOne(hr.class.PublicHoliday, { date: timeToTzDate(date) })
if (existingHoliday !== undefined) {
title = existingHoliday.title
description = existingHoliday.description
@ -39,7 +40,7 @@
async function saveHoliday () {
if (existingHoliday !== undefined) {
await client.updateDoc(hr.class.PublicHoliday, core.space.Space, existingHoliday._id, {
await client.updateDoc(hr.class.PublicHoliday, hr.space.HR, existingHoliday._id, {
title,
description
})
@ -47,10 +48,10 @@
const holiday: Data<PublicHoliday> = {
title,
description,
date: toTzDate(date),
date: timeToTzDate(date),
department
}
await client.createDoc(hr.class.PublicHoliday, core.space.Space, holiday)
await client.createDoc(hr.class.PublicHoliday, hr.space.HR, holiday)
}
}
findHoliday()
@ -83,7 +84,7 @@
<svelte:fragment slot="pool">
<div class="flex-row-center flex-grow flex-gap-3">
<Label label={hr.string.Department} />
<SpaceSelector _class={hr.class.Department} label={hr.string.ParentDepartmentLabel} bind:space={department} />
<DepartmentEditor bind:value={department} />
</div>
</svelte:fragment>
<svelte:fragment slot="buttons">

View File

@ -14,23 +14,21 @@
-->
<script lang="ts">
import { Card } from '@hcengineering/presentation'
import { DropdownIntlItem, DropdownLabelsIntl, Label } from '@hcengineering/ui'
import { createEventDispatcher } from 'svelte'
import { DropdownLabelsIntl, Label } from '@hcengineering/ui'
import hr from '../../plugin'
const dispatch = createEventDispatcher()
export let items = []
export let items: DropdownIntlItem[] = []
let selectedSeparator = items[0].id
function close (): void {
dispatch('close', selectedSeparator)
}
</script>
<Card
label={hr.string.Export}
okLabel={hr.string.Export}
okAction={() => dispatch('close', selectedSeparator)}
canSave
on:close
>
<Card label={hr.string.Export} okLabel={hr.string.Export} okAction={close} canSave on:close>
<div class="flex-row-center">
<Label label={hr.string.ChooseSeparator} />
<DropdownLabelsIntl {items} label={hr.string.Separator} bind:selected={selectedSeparator} />

View File

@ -18,7 +18,7 @@
import type { Request, RequestType, Staff } from '@hcengineering/hr'
import { Department } from '@hcengineering/hr'
import { getEmbeddedLabel } from '@hcengineering/platform'
import { Button, Label, Loading, showPopup, tableToCSV } from '@hcengineering/ui'
import { Button, DropdownIntlItem, Label, Loading, showPopup, tableToCSV } from '@hcengineering/ui'
import { BuildModelKey, Viewlet, ViewletPreference } from '@hcengineering/view'
import { TableBrowser, ViewletSelector, ViewletSettingButton } from '@hcengineering/view-resources'
import hr from '../../plugin'
@ -320,7 +320,10 @@
return result
}
function exportTable (evt: Event) {
const items = [
interface ExportPopupItem extends DropdownIntlItem {
separator: ',' | ';'
}
const items: ExportPopupItem[] = [
{
id: '0',
label: getEmbeddedLabel(', (csv)'),

View File

@ -202,7 +202,7 @@
}
function setPublicHoliday (date: Date): void {
showPopup(CreatePublicHoliday, { date, department })
showPopup(CreatePublicHoliday, { date: date.getTime(), department })
}
function getRowHeight (row: TimelineRow): number {
@ -247,6 +247,12 @@
let rows: TimelineRow[]
$: rows = buildTimelineRows(departmentStaff, employeeRequests)
function getClickHandler (day: Date, staff: Staff): (e: MouseEvent, day: Date, staff: Staff) => void {
return (e) => {
createRequest(e, day, staff)
}
}
</script>
{#if rows.length}
@ -350,7 +356,7 @@
{/each}
</div>
{#each values as value, i}
{#each values as value}
{@const day = getDay(startDate, value)}
{@const today = areDatesEqual(todayDate, day)}
{@const weekend = isWeekend(day)}
@ -360,9 +366,11 @@
)}
{@const requests = getRequests(employeeRequests, day, day, employee._id)}
{@const tooltipValue = getTooltip(requests, day, employee)}
{@const clickHandler = getClickHandler(day, employee)}
{#key [tooltipValue, editable]}
<!-- svelte-ignore a11y-click-events-have-key-events -->
<!-- svelte-ignore a11y-no-static-element-interactions -->
<div
class="timeline-cell"
class:timeline-cell--today={today}
@ -370,9 +378,7 @@
class:timeline-cell--holiday={holiday}
style={getCellStyle()}
use:tooltip={tooltipValue}
on:click={(e) => {
createRequest(e, day, employee)
}}
on:click={clickHandler}
>
{#if today}
<div class="timeline-cell-today-marker" />

View File

@ -51,7 +51,6 @@
<EmployeePresenter value={employee} disabled />
</svelte:fragment>
<TableBrowser
showFilterBar={false}
_class={tracker.class.TimeSpendReport}
query={{ _id: { $in: reports.map((it) => it._id) } }}
config={['$lookup.attachedTo', '$lookup.attachedTo.title', '', 'employee', 'date']}

View File

@ -23,6 +23,7 @@ import Schedule from './components/Schedule.svelte'
import Structure from './components/Structure.svelte'
import TzDatePresenter from './components/TzDatePresenter.svelte'
import TzDateEditor from './components/TzDateEditor.svelte'
import DepartmentPresenter from './components/DepartmentPresenter.svelte'
import RequestPresenter from './components/RequestPresenter.svelte'
import { showPopup } from '@hcengineering/ui'
import { type Request } from '@hcengineering/hr'
@ -44,7 +45,8 @@ export default async (): Promise<Resources> => ({
TzDatePresenter,
TzDateEditor,
RequestPresenter,
EditRequestType
EditRequestType,
DepartmentPresenter
},
actionImpl: {
EditRequestType: editRequestType

View File

@ -23,14 +23,16 @@ import { Viewlet } from '@hcengineering/view'
/**
* @public
*/
export interface Department extends Space {
space: Ref<Department>
export interface Department extends Doc {
name: string
description: string
parent?: Ref<Department>
avatar?: string | null
teamLead: Ref<Employee> | null
attachments?: number
comments?: number
channels?: number
members: Arr<Ref<DepartmentMember>>
members: Ref<DepartmentMember>[]
subscribers?: Arr<Ref<Contact>>
managers: Arr<Ref<Employee>>
}
@ -85,7 +87,7 @@ export interface Request extends AttachedDoc {
attachedToClass: Ref<Class<Staff>>
space: Ref<Department>
department: Ref<Department>
type: Ref<RequestType>
@ -121,6 +123,9 @@ const hr = plugin(hrId, {
mixin: {
Staff: '' as Ref<Mixin<Staff>>
},
space: {
HR: '' as Ref<Space>
},
icon: {
HR: '' as Asset,
Department: '' as Asset,

View File

@ -14,6 +14,19 @@
//
import { TzDate } from '.'
/**
* @public
*/
export function timeToTzDate (val: number): TzDate {
const date = new Date(val)
return {
year: date.getFullYear(),
month: date.getMonth(),
day: date.getDate(),
offset: date.getTimezoneOffset()
}
}
/**
* @public
*/

View File

@ -68,8 +68,8 @@ async function buildHierarchy (_id: Ref<Department>, control: TriggerControl): P
const ancestors = new Map<Ref<Department>, Ref<Department>>()
const departments = await control.findAll(hr.class.Department, {})
for (const department of departments) {
if (department._id === hr.ids.Head) continue
ancestors.set(department._id, department.space)
if (department._id === hr.ids.Head || department.parent === undefined) continue
ancestors.set(department._id, department.parent)
}
const departmentsMap = toIdMap(departments)
while (true) {
@ -100,13 +100,13 @@ function getTxes (
removed?: Ref<Department>[]
): Tx[] {
const pushTxes = added.map((dep) =>
factory.createTxUpdateDoc(hr.class.Department, core.space.Space, dep, {
factory.createTxUpdateDoc(hr.class.Department, hr.space.HR, dep, {
$push: { members: account }
})
)
if (removed === undefined) return pushTxes
const pullTxes = removed.map((dep) =>
factory.createTxUpdateDoc(hr.class.Department, core.space.Space, dep, {
factory.createTxUpdateDoc(hr.class.Department, hr.space.HR, dep, {
$pull: { members: account }
})
)
@ -162,8 +162,8 @@ export async function OnDepartmentStaff (tx: Tx, control: TriggerControl): Promi
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 department = control.removedMap.get(ctx.objectId) as Department
if (department === undefined) return []
const targetAccounts = await control.modelDb.findAll(contact.class.PersonAccount, {
_id: { $in: department.members }
})
@ -298,13 +298,7 @@ export async function OnRequestCreate (tx: Tx, control: TriggerControl): Promise
const request = TxProcessor.createDoc2Doc(ctx)
await sendEmailNotifications(
control,
sender,
request,
ctx.objectSpace as Ref<Department>,
hr.ids.CreateRequestNotification
)
await sendEmailNotifications(control, sender, request, request.department, hr.ids.CreateRequestNotification)
return []
}
@ -320,13 +314,7 @@ export async function OnRequestUpdate (tx: Tx, control: TriggerControl): Promise
const request = (await control.findAll(hr.class.Request, { _id: ctx.objectId }))[0] as Request
if (request === undefined) return []
await sendEmailNotifications(
control,
sender,
request,
ctx.objectSpace as Ref<Department>,
hr.ids.UpdateRequestNotification
)
await sendEmailNotifications(control, sender, request, request.department, hr.ids.UpdateRequestNotification)
return []
}
@ -342,13 +330,7 @@ export async function OnRequestRemove (tx: Tx, control: TriggerControl): Promise
const request = control.removedMap.get(ctx.objectId) as Request
if (request === undefined) return []
await sendEmailNotifications(
control,
sender,
request,
ctx.objectSpace as Ref<Department>,
hr.ids.RemoveRequestNotification
)
await sendEmailNotifications(control, sender, request, request.department, hr.ids.RemoveRequestNotification)
return []
}

View File

@ -325,6 +325,8 @@ export class SpaceSecurityMiddleware extends BaseMiddleware implements Middlewar
const account = await getUser(this.storage, ctx)
if (tx.objectSpace === (account._id as string)) {
targets = [account.email, systemAccountEmail]
} else if ([...this.systemSpaces, ...this.mainSpaces].includes(tx.objectSpace)) {
return
} else {
const cudTx = tx as TxCUD<Doc>
const isSpace = h.isDerived(cudTx.objectClass, core.class.Space)