mirror of
https://github.com/hcengineering/platform.git
synced 2024-11-25 19:58:30 +03:00
Fix hr departments (#5589)
Signed-off-by: Denis Bykhov <bykhov.denis@gmail.com>
This commit is contained in:
parent
cb82eb7a9f
commit
1d8de7681f
@ -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
|
||||
})
|
||||
}
|
||||
|
@ -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)
|
||||
}
|
||||
}
|
||||
])
|
||||
|
@ -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>
|
||||
|
@ -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
|
||||
|
@ -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",
|
||||
|
@ -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",
|
||||
|
@ -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
|
||||
|
@ -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'}
|
||||
|
@ -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)
|
||||
}}
|
||||
|
@ -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>
|
@ -37,7 +37,9 @@
|
||||
{
|
||||
_id: objectId
|
||||
},
|
||||
(res) => ([value] = res)
|
||||
(res) => {
|
||||
value = res[0]
|
||||
}
|
||||
)
|
||||
|
||||
function add (e: MouseEvent) {
|
||||
|
@ -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>
|
||||
|
||||
|
@ -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
|
||||
|
@ -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))
|
||||
|
@ -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
|
||||
},
|
||||
|
@ -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">
|
||||
|
@ -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} />
|
||||
|
@ -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)'),
|
||||
|
@ -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" />
|
||||
|
@ -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']}
|
||||
|
@ -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
|
||||
|
@ -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,
|
||||
|
@ -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
|
||||
*/
|
||||
|
@ -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 []
|
||||
}
|
||||
|
||||
|
@ -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)
|
||||
|
Loading…
Reference in New Issue
Block a user