mirror of
https://github.com/hcengineering/platform.git
synced 2025-06-11 12:57:59 +00:00
HR requests notifications (#2710)
Signed-off-by: Denis Bykhov <bykhov.denis@gmail.com>
This commit is contained in:
parent
ec2235e26b
commit
5696f216ca
@ -127,6 +127,7 @@ export function createModel (builder: Builder): void {
|
||||
notification.class.NotificationType,
|
||||
core.space.Model,
|
||||
{
|
||||
hidden: false,
|
||||
label: calendar.string.Reminder,
|
||||
textTemplate: 'Reminder: {doc}',
|
||||
htmlTemplate: 'Reminder: {doc}',
|
||||
|
@ -305,6 +305,10 @@ export function createModel (builder: Builder): void {
|
||||
inlineEditor: contact.component.EmployeeArrayEditor
|
||||
})
|
||||
|
||||
builder.mixin(contact.class.Contact, core.class.Class, view.mixin.ArrayEditor, {
|
||||
inlineEditor: contact.component.ContactArrayEditor
|
||||
})
|
||||
|
||||
builder.mixin(contact.class.Member, core.class.Class, view.mixin.ObjectPresenter, {
|
||||
presenter: contact.component.MemberPresenter
|
||||
})
|
||||
|
@ -43,6 +43,7 @@ export default mergeIds(contactId, contact, {
|
||||
MemberPresenter: '' as AnyComponent,
|
||||
EditMember: '' as AnyComponent,
|
||||
EmployeeArrayEditor: '' as AnyComponent,
|
||||
ContactArrayEditor: '' as AnyComponent,
|
||||
EmployeeEditor: '' as AnyComponent,
|
||||
CreateEmployee: '' as AnyComponent,
|
||||
AccountArrayEditor: '' as AnyComponent,
|
||||
|
@ -31,6 +31,7 @@
|
||||
"@hcengineering/contact": "^0.6.11",
|
||||
"@hcengineering/platform": "^0.6.8",
|
||||
"@hcengineering/model-core": "^0.6.0",
|
||||
"@hcengineering/notification": "^0.6.5",
|
||||
"@hcengineering/model-view": "^0.6.0",
|
||||
"@hcengineering/model-workbench": "^0.6.1",
|
||||
"@hcengineering/model-contact": "^0.6.1",
|
||||
|
@ -13,7 +13,7 @@
|
||||
// limitations under the License.
|
||||
//
|
||||
|
||||
import { Employee } from '@hcengineering/contact'
|
||||
import { Contact, Employee } from '@hcengineering/contact'
|
||||
import { Arr, Class, Domain, DOMAIN_MODEL, IndexKind, Markup, Ref, Type } from '@hcengineering/core'
|
||||
import { Department, DepartmentMember, hrId, Request, RequestType, Staff, TzDate } from '@hcengineering/hr'
|
||||
import {
|
||||
@ -40,6 +40,7 @@ import view, { classPresenter, createAction } from '@hcengineering/model-view'
|
||||
import workbench from '@hcengineering/model-workbench'
|
||||
import { Asset, IntlString } from '@hcengineering/platform'
|
||||
import hr from './plugin'
|
||||
import notification from '@hcengineering/notification'
|
||||
|
||||
export const DOMAIN_HR = 'hr' as Domain
|
||||
|
||||
@ -70,6 +71,9 @@ export class TDepartment extends TSpace implements Department {
|
||||
|
||||
@Prop(ArrOf(TypeRef(hr.class.DepartmentMember)), contact.string.Members)
|
||||
declare members: Arr<Ref<DepartmentMember>>
|
||||
|
||||
@Prop(ArrOf(TypeRef(contact.class.Contact)), hr.string.Subscribers)
|
||||
subscribers?: Arr<Ref<Contact>>
|
||||
}
|
||||
|
||||
@Model(hr.class.DepartmentMember, contact.class.EmployeeAccount)
|
||||
@ -405,6 +409,45 @@ export function createModel (builder: Builder): void {
|
||||
builder.mixin(hr.class.Request, core.class.Class, view.mixin.ObjectPresenter, {
|
||||
presenter: hr.component.RequestPresenter
|
||||
})
|
||||
|
||||
builder.createDoc(
|
||||
notification.class.NotificationType,
|
||||
core.space.Model,
|
||||
{
|
||||
hidden: true,
|
||||
label: hr.string.Request,
|
||||
textTemplate: 'New request: {doc}',
|
||||
htmlTemplate: 'New request: {doc}',
|
||||
subjectTemplate: 'New request'
|
||||
},
|
||||
hr.ids.CreateRequestNotifcation
|
||||
)
|
||||
|
||||
builder.createDoc(
|
||||
notification.class.NotificationType,
|
||||
core.space.Model,
|
||||
{
|
||||
hidden: true,
|
||||
label: hr.string.Request,
|
||||
textTemplate: 'Request updated: {doc}',
|
||||
htmlTemplate: 'Request updated: {doc}',
|
||||
subjectTemplate: 'Request updated'
|
||||
},
|
||||
hr.ids.UpdateRequestNotifcation
|
||||
)
|
||||
|
||||
builder.createDoc(
|
||||
notification.class.NotificationType,
|
||||
core.space.Model,
|
||||
{
|
||||
hidden: true,
|
||||
label: hr.string.Request,
|
||||
textTemplate: 'Request removed: {doc}',
|
||||
htmlTemplate: 'Request removed: {doc}',
|
||||
subjectTemplate: 'Request removed'
|
||||
},
|
||||
hr.ids.RemoveRequestNotifcation
|
||||
)
|
||||
}
|
||||
|
||||
export { hrOperation } from './migration'
|
||||
|
@ -31,7 +31,8 @@ export default mergeIds(hrId, hr, {
|
||||
PTO2: '' as IntlString,
|
||||
Remote: '' as IntlString,
|
||||
Overtime: '' as IntlString,
|
||||
Overtime2: '' as IntlString
|
||||
Overtime2: '' as IntlString,
|
||||
Subscribers: '' as IntlString
|
||||
},
|
||||
component: {
|
||||
Structure: '' as AnyComponent,
|
||||
|
@ -84,6 +84,7 @@ export class TNotificationType extends TDoc implements NotificationType {
|
||||
textTemplate!: string
|
||||
htmlTemplate!: string
|
||||
subjectTemplate!: string
|
||||
hidden!: boolean
|
||||
}
|
||||
|
||||
@Model(notification.class.NotificationProvider, core.class.Doc, DOMAIN_MODEL)
|
||||
@ -130,6 +131,7 @@ export function createModel (builder: Builder): void {
|
||||
core.space.Model,
|
||||
{
|
||||
label: notification.string.MentionNotification,
|
||||
hidden: false,
|
||||
textTemplate: '{sender} mentioned you in {doc} {data}',
|
||||
htmlTemplate: '<p><b>{sender}</b> mentioned you in {doc}</p> {data}',
|
||||
subjectTemplate: 'You was mentioned in {doc}'
|
||||
@ -141,7 +143,8 @@ export function createModel (builder: Builder): void {
|
||||
notification.class.NotificationType,
|
||||
core.space.Model,
|
||||
{
|
||||
label: notification.string.DMNotification,
|
||||
label: notification.string.DM,
|
||||
hidden: false,
|
||||
textTemplate: '{sender} has send you a message: {doc} {data}',
|
||||
htmlTemplate: '<p><b>{sender}</b> has send you a message {doc}</p> {data}',
|
||||
subjectTemplate: 'You have new DM message in {doc}'
|
||||
|
@ -21,6 +21,7 @@ import { AnyComponent } from '@hcengineering/ui'
|
||||
export default mergeIds(notificationId, notification, {
|
||||
string: {
|
||||
LastView: '' as IntlString,
|
||||
DM: '' as IntlString,
|
||||
DMNotification: '' as IntlString,
|
||||
MentionNotification: '' as IntlString,
|
||||
PlatformNotification: '' as IntlString,
|
||||
|
@ -28,6 +28,8 @@
|
||||
"@hcengineering/core": "^0.6.21",
|
||||
"@hcengineering/model": "^0.6.0",
|
||||
"@hcengineering/platform": "^0.6.8",
|
||||
"@hcengineering/hr": "^0.6.0",
|
||||
"@hcengineering/server-notification": "^0.6.0",
|
||||
"@hcengineering/server-hr": "^0.6.0",
|
||||
"@hcengineering/server-core": "^0.6.1"
|
||||
}
|
||||
|
@ -18,9 +18,31 @@ import { Builder } from '@hcengineering/model'
|
||||
import serverCore from '@hcengineering/server-core'
|
||||
import core from '@hcengineering/core'
|
||||
import serverHr from '@hcengineering/server-hr'
|
||||
import serverNotification from '@hcengineering/server-notification'
|
||||
import hr from '@hcengineering/hr'
|
||||
|
||||
export function createModel (builder: Builder): void {
|
||||
builder.createDoc(serverCore.class.Trigger, core.space.Model, {
|
||||
trigger: serverHr.trigger.OnDepartmentStaff
|
||||
})
|
||||
|
||||
builder.createDoc(serverCore.class.Trigger, core.space.Model, {
|
||||
trigger: serverHr.trigger.OnRequestCreate
|
||||
})
|
||||
|
||||
builder.createDoc(serverCore.class.Trigger, core.space.Model, {
|
||||
trigger: serverHr.trigger.OnRequestUpdate
|
||||
})
|
||||
|
||||
builder.createDoc(serverCore.class.Trigger, core.space.Model, {
|
||||
trigger: serverHr.trigger.OnRequestRemove
|
||||
})
|
||||
|
||||
builder.mixin(hr.class.Request, core.class.Class, serverNotification.mixin.HTMLPresenter, {
|
||||
presenter: serverHr.function.RequestHTMLPresenter
|
||||
})
|
||||
|
||||
builder.mixin(hr.class.Request, core.class.Class, serverNotification.mixin.TextPresenter, {
|
||||
presenter: serverHr.function.RequestTextPresenter
|
||||
})
|
||||
}
|
||||
|
@ -572,6 +572,7 @@ export function createModel (builder: Builder): void {
|
||||
core.space.Model,
|
||||
{
|
||||
label: task.string.Assigned,
|
||||
hidden: false,
|
||||
textTemplate: '{doc} was assigned to you by {sender}',
|
||||
htmlTemplate: '<p>{doc} was assigned to you by {sender}</p>',
|
||||
subjectTemplate: '{doc} was assigned to you'
|
||||
|
@ -15,18 +15,18 @@
|
||||
<script lang="ts">
|
||||
import type { Class, Doc, Ref } from '@hcengineering/core'
|
||||
import { createQuery } from '../utils'
|
||||
import { Person } from '@hcengineering/contact'
|
||||
import { Person as Contact } from '@hcengineering/contact'
|
||||
import Avatar from './Avatar.svelte'
|
||||
import { IconSize } from '@hcengineering/ui'
|
||||
|
||||
export let _class: Ref<Class<Doc>>
|
||||
export let items: Ref<Person>[] = []
|
||||
export let items: Ref<Contact>[] = []
|
||||
export let size: IconSize
|
||||
export let limit: number = 3
|
||||
|
||||
let persons: Person[] = []
|
||||
let persons: Contact[] = []
|
||||
const query = createQuery()
|
||||
$: query.query<Person>(
|
||||
$: query.query<Contact>(
|
||||
_class,
|
||||
{ _id: { $in: items } },
|
||||
(result) => {
|
||||
|
@ -368,11 +368,11 @@ export async function getAttributeEditor (
|
||||
}
|
||||
|
||||
if (editorMixin.inlineEditor === undefined) {
|
||||
if (presenterClass.category === 'array') {
|
||||
// NOTE: Don't show error for array attributes for compatibility with previous implementation
|
||||
} else {
|
||||
console.error(getAttributeEditorNotFoundError(_class, key))
|
||||
}
|
||||
// if (presenterClass.category === 'array') {
|
||||
// // NOTE: Don't show error for array attributes for compatibility with previous implementation
|
||||
// } else {
|
||||
console.error(getAttributeEditorNotFoundError(_class, key))
|
||||
// }
|
||||
return
|
||||
}
|
||||
|
||||
|
@ -0,0 +1,31 @@
|
||||
<script lang="ts">
|
||||
import { Contact } from '@hcengineering/contact'
|
||||
import { Ref } from '@hcengineering/core'
|
||||
import { IntlString } from '@hcengineering/platform'
|
||||
import ContactList from './ContactList.svelte'
|
||||
|
||||
export let label: IntlString
|
||||
export let value: Ref<Contact>[]
|
||||
export let onChange: (refs: Ref<Contact>[]) => void
|
||||
export let readonly = false
|
||||
|
||||
let timer: any
|
||||
|
||||
function onUpdate (evt: CustomEvent<Ref<Contact>[]>): void {
|
||||
clearTimeout(timer)
|
||||
timer = setTimeout(() => {
|
||||
onChange(evt.detail)
|
||||
}, 500)
|
||||
}
|
||||
</script>
|
||||
|
||||
<ContactList
|
||||
items={value}
|
||||
{label}
|
||||
on:update={onUpdate}
|
||||
kind={'link'}
|
||||
size={'medium'}
|
||||
justify={'left'}
|
||||
width={'100%'}
|
||||
{readonly}
|
||||
/>
|
99
plugins/contact-resources/src/components/ContactList.svelte
Normal file
99
plugins/contact-resources/src/components/ContactList.svelte
Normal file
@ -0,0 +1,99 @@
|
||||
<!--
|
||||
// Copyright © 2023 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 { Contact } from '@hcengineering/contact'
|
||||
import type { Class, DocumentQuery, Ref } from '@hcengineering/core'
|
||||
import type { IntlString } from '@hcengineering/platform'
|
||||
import presentation, { CombineAvatars, createQuery, IconMembers, UsersPopup } from '@hcengineering/presentation'
|
||||
import { Button, ButtonKind, ButtonSize, Label, showPopup, TooltipAlignment } from '@hcengineering/ui'
|
||||
import { createEventDispatcher } from 'svelte'
|
||||
import { ContactPresenter } from '..'
|
||||
import contact from '../plugin'
|
||||
|
||||
export let items: Ref<Contact>[] = []
|
||||
export let _class: Ref<Class<Contact>> = contact.class.Contact
|
||||
export let label: IntlString
|
||||
export let docQuery: DocumentQuery<Contact> | undefined = {
|
||||
active: true
|
||||
}
|
||||
|
||||
export let kind: ButtonKind = 'no-border'
|
||||
export let size: ButtonSize = 'small'
|
||||
export let justify: 'left' | 'center' = 'center'
|
||||
export let width: string | undefined = undefined
|
||||
export let labelDirection: TooltipAlignment | undefined = undefined
|
||||
export let emptyLabel = contact.string.Contacts
|
||||
export let readonly: boolean = false
|
||||
|
||||
let contacts: Contact[] = []
|
||||
|
||||
const query = createQuery()
|
||||
|
||||
$: query.query<Contact>(_class, { _id: { $in: items } }, (result) => {
|
||||
contacts = result
|
||||
})
|
||||
|
||||
const dispatch = createEventDispatcher()
|
||||
|
||||
async function addPerson (evt: Event): Promise<void> {
|
||||
showPopup(
|
||||
UsersPopup,
|
||||
{
|
||||
_class,
|
||||
label,
|
||||
docQuery,
|
||||
multiSelect: true,
|
||||
allowDeselect: false,
|
||||
selectedUsers: items,
|
||||
readonly
|
||||
},
|
||||
evt.target as HTMLElement,
|
||||
undefined,
|
||||
(result) => {
|
||||
if (result != null) {
|
||||
items = result
|
||||
dispatch('update', items)
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
</script>
|
||||
|
||||
<Button
|
||||
icon={contacts.length === 0 ? IconMembers : undefined}
|
||||
label={contacts.length === 0 ? emptyLabel : undefined}
|
||||
notSelected={contacts.length === 0}
|
||||
width={width ?? 'min-content'}
|
||||
{kind}
|
||||
{size}
|
||||
{justify}
|
||||
showTooltip={{ label, direction: labelDirection }}
|
||||
on:click={addPerson}
|
||||
>
|
||||
<svelte:fragment slot="content">
|
||||
{#if contacts.length > 0}
|
||||
<div class="flex-row-center flex-nowrap pointer-events-none">
|
||||
{#if contacts.length === 1}
|
||||
<ContactPresenter value={contacts[0]} isInteractive={false} />
|
||||
{:else}
|
||||
<CombineAvatars {_class} bind:items size={'inline'} />
|
||||
<span class="overflow-label ml-1-5">
|
||||
<Label label={presentation.string.NumberMembers} params={{ count: contacts.length }} />
|
||||
</span>
|
||||
{/if}
|
||||
</div>
|
||||
{/if}
|
||||
</svelte:fragment>
|
||||
</Button>
|
@ -55,6 +55,7 @@ import EmployeeRefPresenter from './components/EmployeeRefPresenter.svelte'
|
||||
import ChannelFilter from './components/ChannelFilter.svelte'
|
||||
import AccountBox from './components/AccountBox.svelte'
|
||||
import MergeEmployee from './components/MergeEmployee.svelte'
|
||||
import ContactArrayEditor from './components/ContactArrayEditor.svelte'
|
||||
import contact from './plugin'
|
||||
import {
|
||||
employeeSort,
|
||||
@ -154,6 +155,7 @@ export default async (): Promise<Resources> => ({
|
||||
OpenChannel: openChannelURL
|
||||
},
|
||||
component: {
|
||||
ContactArrayEditor,
|
||||
PersonEditor,
|
||||
OrganizationEditor,
|
||||
ContactPresenter,
|
||||
|
@ -40,6 +40,7 @@
|
||||
"Member": "Member",
|
||||
"Members": "Members",
|
||||
"NoMembers": "No members added",
|
||||
"AddMember": "Add member"
|
||||
"AddMember": "Add member",
|
||||
"Subscribers": "Subscribers"
|
||||
}
|
||||
}
|
||||
|
@ -40,6 +40,7 @@
|
||||
"Member": "Сотрудник",
|
||||
"Members": "Сотрудники",
|
||||
"NoMembers": "Нет добавленных сотрудников",
|
||||
"AddMember": "Добавить сотрудника"
|
||||
"AddMember": "Добавить сотрудника",
|
||||
"Subscribers": "Подписчики"
|
||||
}
|
||||
}
|
||||
|
@ -17,13 +17,12 @@
|
||||
import calendar from '@hcengineering/calendar'
|
||||
import { Employee } from '@hcengineering/contact'
|
||||
import core, { DocumentQuery, generateId, Ref } from '@hcengineering/core'
|
||||
import { Request, RequestType, Staff } from '@hcengineering/hr'
|
||||
import { Request, RequestType, Staff, toTzDate } from '@hcengineering/hr'
|
||||
import { translate } from '@hcengineering/platform'
|
||||
import { Card, createQuery, EmployeeBox, getClient } from '@hcengineering/presentation'
|
||||
import ui, { Button, DateRangePresenter, DropdownLabelsIntl, IconAttachment } from '@hcengineering/ui'
|
||||
import { createEventDispatcher } from 'svelte'
|
||||
import hr from '../plugin'
|
||||
import { toTzDate } from '../utils'
|
||||
|
||||
export let staff: Staff
|
||||
export let date: Date
|
||||
|
@ -14,10 +14,9 @@
|
||||
// limitations under the License.
|
||||
-->
|
||||
<script lang="ts">
|
||||
import { Request } from '@hcengineering/hr'
|
||||
import { fromTzDate, Request, tzDateEqual } from '@hcengineering/hr'
|
||||
import { getClient } from '@hcengineering/presentation'
|
||||
import { DateRangePresenter, Label } from '@hcengineering/ui'
|
||||
import { fromTzDate, tzDateEqual } from '../utils'
|
||||
|
||||
export let value: Request | null | undefined
|
||||
export let noShift: boolean = false
|
||||
|
@ -16,10 +16,12 @@
|
||||
import { CalendarMode } from '@hcengineering/calendar-resources'
|
||||
import { Employee, EmployeeAccount } from '@hcengineering/contact'
|
||||
import { DocumentQuery, getCurrentAccount, Ref } from '@hcengineering/core'
|
||||
import type { Department, Request, RequestType, Staff } from '@hcengineering/hr'
|
||||
import { Department, fromTzDate, Request, RequestType, Staff } from '@hcengineering/hr'
|
||||
import { createQuery } from '@hcengineering/presentation'
|
||||
import tracker, { Issue } from '@hcengineering/tracker'
|
||||
import { Label } from '@hcengineering/ui'
|
||||
import hr from '../plugin'
|
||||
import { EmployeeReports, getEndDate, getStartDate } from '../utils'
|
||||
import MonthTableView from './schedule/MonthTableView.svelte'
|
||||
import MonthView from './schedule/MonthView.svelte'
|
||||
import YearView from './schedule/YearView.svelte'
|
||||
@ -149,9 +151,6 @@
|
||||
|
||||
const reportQuery = createQuery()
|
||||
|
||||
import tracker, { Issue } from '@hcengineering/tracker'
|
||||
import { EmployeeReports, fromTzDate, getEndDate, getStartDate } from '../utils'
|
||||
|
||||
let timeReports: Map<Ref<Employee>, EmployeeReports> = new Map()
|
||||
|
||||
$: reportQuery.query(
|
||||
|
@ -13,11 +13,10 @@
|
||||
// limitations under the License.
|
||||
-->
|
||||
<script lang="ts">
|
||||
import { TzDate } from '@hcengineering/hr'
|
||||
import { fromTzDate, toTzDate, TzDate } from '@hcengineering/hr'
|
||||
|
||||
// import { IntlString } from '@hcengineering/platform'
|
||||
import { DateRangePresenter } from '@hcengineering/ui'
|
||||
import { fromTzDate, toTzDate } from '../utils'
|
||||
|
||||
export let value: TzDate | null | undefined
|
||||
export let onChange: (value: TzDate | null | undefined) => void
|
||||
|
@ -1,6 +1,6 @@
|
||||
import { Employee, formatName } from '@hcengineering/contact'
|
||||
import { Ref, TxOperations } from '@hcengineering/core'
|
||||
import { Department, Request, RequestType, Staff, TzDate } from '@hcengineering/hr'
|
||||
import { Department, fromTzDate, Request, RequestType, Staff } from '@hcengineering/hr'
|
||||
import { MessageBox } from '@hcengineering/presentation'
|
||||
import { Issue, TimeSpendReport } from '@hcengineering/tracker'
|
||||
import { isWeekend, MILLISECONDS_IN_DAY, showPopup } from '@hcengineering/ui'
|
||||
@ -56,32 +56,6 @@ export async function addMember (client: TxOperations, employee?: Employee, valu
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @public
|
||||
*/
|
||||
export function toTzDate (date: Date): TzDate {
|
||||
return {
|
||||
year: date.getFullYear(),
|
||||
month: date.getMonth(),
|
||||
day: date.getDate(),
|
||||
offset: date.getTimezoneOffset()
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @public
|
||||
*/
|
||||
export function fromTzDate (tzDate: TzDate): number {
|
||||
return new Date().setFullYear(tzDate?.year ?? 0, tzDate.month, tzDate.day)
|
||||
}
|
||||
|
||||
/**
|
||||
* @public
|
||||
*/
|
||||
export function tzDateEqual (tzDate: TzDate, tzDate2: TzDate): boolean {
|
||||
return tzDate.year === tzDate2.year && tzDate.month === tzDate2.month && tzDate.day === tzDate2.day
|
||||
}
|
||||
|
||||
/**
|
||||
* @public
|
||||
*/
|
||||
|
@ -29,7 +29,8 @@
|
||||
"@hcengineering/contact": "^0.6.11",
|
||||
"@hcengineering/core": "^0.6.21",
|
||||
"@hcengineering/platform": "^0.6.8",
|
||||
"@hcengineering/view": "^0.6.2"
|
||||
"@hcengineering/view": "^0.6.2",
|
||||
"@hcengineering/notification": "^0.6.5"
|
||||
},
|
||||
"repository": "https://github.com/hcenginneing/anticrm",
|
||||
"publishConfig": {
|
||||
|
@ -13,11 +13,12 @@
|
||||
// limitations under the License.
|
||||
//
|
||||
|
||||
import type { Employee, EmployeeAccount } from '@hcengineering/contact'
|
||||
import type { Contact, Employee, EmployeeAccount } from '@hcengineering/contact'
|
||||
import type { Arr, AttachedDoc, Class, Doc, Markup, Mixin, Ref, Space, Type } from '@hcengineering/core'
|
||||
import type { Asset, IntlString, Plugin } from '@hcengineering/platform'
|
||||
import { plugin } from '@hcengineering/platform'
|
||||
import { Viewlet } from '@hcengineering/view'
|
||||
import { NotificationType } from '@hcengineering/notification'
|
||||
|
||||
/**
|
||||
* @public
|
||||
@ -30,6 +31,7 @@ export interface Department extends Space {
|
||||
comments?: number
|
||||
channels?: number
|
||||
members: Arr<Ref<DepartmentMember>>
|
||||
subscribers?: Arr<Ref<Contact>>
|
||||
}
|
||||
|
||||
/**
|
||||
@ -126,7 +128,10 @@ const hr = plugin(hrId, {
|
||||
PTO2: '' as Ref<RequestType>,
|
||||
Remote: '' as Ref<RequestType>,
|
||||
Overtime: '' as Ref<RequestType>,
|
||||
Overtime2: '' as Ref<RequestType>
|
||||
Overtime2: '' as Ref<RequestType>,
|
||||
CreateRequestNotifcation: '' as Ref<NotificationType>,
|
||||
UpdateRequestNotifcation: '' as Ref<NotificationType>,
|
||||
RemoveRequestNotifcation: '' as Ref<NotificationType>
|
||||
},
|
||||
viewlet: {
|
||||
TableMember: '' as Ref<Viewlet>,
|
||||
@ -134,6 +139,8 @@ const hr = plugin(hrId, {
|
||||
}
|
||||
})
|
||||
|
||||
export * from './utils'
|
||||
|
||||
/**
|
||||
* @public
|
||||
*/
|
||||
|
41
plugins/hr/src/utils.ts
Normal file
41
plugins/hr/src/utils.ts
Normal file
@ -0,0 +1,41 @@
|
||||
//
|
||||
// Copyright © 2023 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.
|
||||
//
|
||||
import { TzDate } from '.'
|
||||
|
||||
/**
|
||||
* @public
|
||||
*/
|
||||
export function toTzDate (date: Date): TzDate {
|
||||
return {
|
||||
year: date.getFullYear(),
|
||||
month: date.getMonth(),
|
||||
day: date.getDate(),
|
||||
offset: date.getTimezoneOffset()
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @public
|
||||
*/
|
||||
export function fromTzDate (tzDate: TzDate): number {
|
||||
return new Date().setFullYear(tzDate?.year ?? 0, tzDate.month, tzDate.day)
|
||||
}
|
||||
|
||||
/**
|
||||
* @public
|
||||
*/
|
||||
export function tzDateEqual (tzDate: TzDate, tzDate2: TzDate): boolean {
|
||||
return tzDate.year === tzDate2.year && tzDate.month === tzDate2.month && tzDate.day === tzDate2.day
|
||||
}
|
@ -5,7 +5,8 @@
|
||||
"Notifications": "Notifications",
|
||||
"NoNotifications": "No notifications",
|
||||
"MentionNotification": "Mentioned",
|
||||
"DMNotification": "Sent you a message",
|
||||
"DM": "Direct message",
|
||||
"DMNotification": "Sent you as message",
|
||||
"EmailNotification": "by email",
|
||||
"PlatformNotification": "in platform",
|
||||
"Track": "Track",
|
||||
|
@ -5,6 +5,7 @@
|
||||
"Notifications": "Уведомления",
|
||||
"NoNotifications": "Нет уведомлений",
|
||||
"MentionNotification": "Упомянул",
|
||||
"DM": "Личное сообщение",
|
||||
"DMNotification": "Отправил сообщение",
|
||||
"EmailNotification": "по email",
|
||||
"PlatformNotification": "в системе",
|
||||
|
@ -39,7 +39,7 @@
|
||||
Map<Ref<NotificationProvider>, NotificationSetting>
|
||||
>()
|
||||
|
||||
typeQuery.query(notification.class.NotificationType, {}, (res) => (types = res))
|
||||
typeQuery.query(notification.class.NotificationType, { hidden: false }, (res) => (types = res))
|
||||
providersQuery.query(notification.class.NotificationProvider, {}, (res) => (providers = res))
|
||||
settingsQuery.query(notification.class.NotificationSetting, { space }, (res) => {
|
||||
settings = convertToMap(res)
|
||||
|
@ -76,6 +76,7 @@ export enum NotificationStatus {
|
||||
* @public
|
||||
*/
|
||||
export interface NotificationType extends Doc {
|
||||
hidden: boolean
|
||||
label: IntlString
|
||||
textTemplate: string
|
||||
htmlTemplate: string
|
||||
|
@ -30,6 +30,9 @@
|
||||
"@hcengineering/platform": "^0.6.8",
|
||||
"@hcengineering/server-core": "^0.6.1",
|
||||
"@hcengineering/contact": "^0.6.11",
|
||||
"@hcengineering/server-notification": "^0.6.0",
|
||||
"@hcengineering/server-notification-resources": "^0.6.0",
|
||||
"@hcengineering/notification": "^0.6.5",
|
||||
"@hcengineering/hr": "^0.6.0"
|
||||
}
|
||||
}
|
||||
|
@ -13,10 +13,25 @@
|
||||
// limitations under the License.
|
||||
//
|
||||
|
||||
import contact, { Employee } from '@hcengineering/contact'
|
||||
import core, { Ref, SortingOrder, Tx, TxFactory, TxMixin, TxProcessor, TxUpdateDoc } from '@hcengineering/core'
|
||||
import hr, { Department, DepartmentMember, Staff } from '@hcengineering/hr'
|
||||
import contact, { Contact, Employee, EmployeeAccount, formatName } from '@hcengineering/contact'
|
||||
import core, {
|
||||
Doc,
|
||||
Ref,
|
||||
SortingOrder,
|
||||
toIdMap,
|
||||
Tx,
|
||||
TxCreateDoc,
|
||||
TxFactory,
|
||||
TxMixin,
|
||||
TxProcessor,
|
||||
TxUpdateDoc
|
||||
} from '@hcengineering/core'
|
||||
import hr, { Department, DepartmentMember, fromTzDate, Request, Staff, tzDateEqual } from '@hcengineering/hr'
|
||||
import notification, { NotificationType } from '@hcengineering/notification'
|
||||
import { translate } from '@hcengineering/platform'
|
||||
import { TriggerControl } from '@hcengineering/server-core'
|
||||
import { getEmployeeAccountById } from '@hcengineering/server-notification'
|
||||
import { getContent } from '@hcengineering/server-notification-resources'
|
||||
|
||||
async function getOldDepartment (
|
||||
currentTx: TxMixin<Employee, Staff> | TxUpdateDoc<Employee>,
|
||||
@ -39,19 +54,23 @@ async function getOldDepartment (
|
||||
return lastDepartment
|
||||
}
|
||||
|
||||
async function buildHierarchy (_id: Ref<Department>, control: TriggerControl): Promise<Ref<Department>[]> {
|
||||
const res: Ref<Department>[] = []
|
||||
if (_id === hr.ids.Head) return [hr.ids.Head]
|
||||
const department = (
|
||||
await control.findAll(hr.class.Department, {
|
||||
_id
|
||||
})
|
||||
)[0]
|
||||
if (department !== undefined) {
|
||||
const ancestors = await buildHierarchy(department.space, control)
|
||||
return [department._id, ...ancestors]
|
||||
async function buildHierarchy (_id: Ref<Department>, control: TriggerControl): Promise<Department[]> {
|
||||
const res: Department[] = []
|
||||
const ancestors: Map<Ref<Department>, Ref<Department>> = new Map()
|
||||
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)
|
||||
}
|
||||
const departmentsMap = toIdMap(departments)
|
||||
while (true) {
|
||||
const department = departmentsMap.get(_id)
|
||||
if (department === undefined) return res
|
||||
res.push(department)
|
||||
const next = ancestors.get(department._id)
|
||||
if (next === undefined) return res
|
||||
_id = next
|
||||
}
|
||||
return res
|
||||
}
|
||||
|
||||
function exlude (first: Ref<Department>[], second: Ref<Department>[]): Ref<Department>[] {
|
||||
@ -112,16 +131,21 @@ export async function OnDepartmentStaff (tx: Tx, control: TriggerControl): Promi
|
||||
if (departmentId === null) {
|
||||
if (lastDepartment !== undefined) {
|
||||
const removed = await buildHierarchy(lastDepartment, control)
|
||||
return getTxes(control.txFactory, targetAccount._id, [], removed)
|
||||
return getTxes(
|
||||
control.txFactory,
|
||||
targetAccount._id,
|
||||
[],
|
||||
removed.map((p) => p._id)
|
||||
)
|
||||
}
|
||||
}
|
||||
const push = await buildHierarchy(departmentId, control)
|
||||
const push = (await buildHierarchy(departmentId, control)).map((p) => p._id)
|
||||
|
||||
if (lastDepartment === undefined) {
|
||||
return getTxes(control.txFactory, targetAccount._id, push)
|
||||
}
|
||||
|
||||
let removed = await buildHierarchy(lastDepartment, control)
|
||||
let removed = (await buildHierarchy(lastDepartment, control)).map((p) => p._id)
|
||||
const added = exlude(removed, push)
|
||||
removed = exlude(push, removed)
|
||||
return getTxes(control.txFactory, targetAccount._id, added, removed)
|
||||
@ -153,13 +177,188 @@ export async function OnEmployeeDeactivate (tx: Tx, control: TriggerControl): Pr
|
||||
if (lastDepartment === undefined) return []
|
||||
|
||||
const removed = await buildHierarchy(lastDepartment, control)
|
||||
return getTxes(control.txFactory, targetAccount._id, [], removed)
|
||||
return getTxes(
|
||||
control.txFactory,
|
||||
targetAccount._id,
|
||||
[],
|
||||
removed.map((p) => p._id)
|
||||
)
|
||||
}
|
||||
|
||||
async function getEmailNotification (
|
||||
control: TriggerControl,
|
||||
sender: EmployeeAccount,
|
||||
doc: Request,
|
||||
space: Ref<Department>,
|
||||
type: Ref<NotificationType>
|
||||
): Promise<Tx[]> {
|
||||
const contacts: Set<Ref<Contact>> = new Set()
|
||||
const departments = await buildHierarchy(space, control)
|
||||
for (const department of departments) {
|
||||
if (department.subscribers === undefined) continue
|
||||
for (const subscriber of department.subscribers) {
|
||||
contacts.add(subscriber)
|
||||
}
|
||||
}
|
||||
|
||||
const channels = await control.findAll(contact.class.Channel, {
|
||||
provider: contact.channelProvider.Email,
|
||||
attachedTo: { $in: Array.from(contacts) }
|
||||
})
|
||||
|
||||
const senderName = formatName(sender.name)
|
||||
const content = await getContent(doc, senderName, type, control, '')
|
||||
if (content === undefined) return []
|
||||
|
||||
const res: Tx[] = []
|
||||
for (const channel of channels) {
|
||||
const tx = control.txFactory.createTxCreateDoc(
|
||||
notification.class.EmailNotification,
|
||||
notification.space.Notifications,
|
||||
{
|
||||
status: 'new',
|
||||
sender: senderName,
|
||||
receivers: [channel.value],
|
||||
subject: content.subject,
|
||||
text: content.text,
|
||||
html: content.html
|
||||
}
|
||||
)
|
||||
res.push(tx)
|
||||
}
|
||||
return res
|
||||
}
|
||||
|
||||
/**
|
||||
* @public
|
||||
*/
|
||||
export async function OnRequestCreate (tx: Tx, control: TriggerControl): Promise<Tx[]> {
|
||||
const actualTx = TxProcessor.extractTx(tx)
|
||||
if (core.class.TxCreateDoc !== actualTx._class) {
|
||||
return []
|
||||
}
|
||||
const ctx = actualTx as TxCreateDoc<Request>
|
||||
if (ctx.objectClass !== hr.class.Request) {
|
||||
return []
|
||||
}
|
||||
|
||||
const sender = await getEmployeeAccountById(ctx.modifiedBy, control)
|
||||
if (sender === undefined) return []
|
||||
|
||||
const request = TxProcessor.createDoc2Doc(ctx)
|
||||
|
||||
return await getEmailNotification(
|
||||
control,
|
||||
sender,
|
||||
request,
|
||||
ctx.objectSpace as Ref<Department>,
|
||||
hr.ids.CreateRequestNotifcation
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* @public
|
||||
*/
|
||||
export async function OnRequestUpdate (tx: Tx, control: TriggerControl): Promise<Tx[]> {
|
||||
const actualTx = TxProcessor.extractTx(tx)
|
||||
if (core.class.TxUpdateDoc !== actualTx._class) {
|
||||
return []
|
||||
}
|
||||
const ctx = actualTx as TxUpdateDoc<Request>
|
||||
if (ctx.objectClass !== hr.class.Request) {
|
||||
return []
|
||||
}
|
||||
|
||||
const sender = await getEmployeeAccountById(ctx.modifiedBy, control)
|
||||
if (sender === undefined) return []
|
||||
|
||||
const request = (await control.findAll(hr.class.Request, { _id: ctx.objectId }))[0] as Request
|
||||
if (request === undefined) return []
|
||||
|
||||
return await getEmailNotification(
|
||||
control,
|
||||
sender,
|
||||
request,
|
||||
ctx.objectSpace as Ref<Department>,
|
||||
hr.ids.UpdateRequestNotifcation
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* @public
|
||||
*/
|
||||
export async function OnRequestRemove (tx: Tx, control: TriggerControl): Promise<Tx[]> {
|
||||
const actualTx = TxProcessor.extractTx(tx)
|
||||
if (core.class.TxCreateDoc !== actualTx._class) {
|
||||
return []
|
||||
}
|
||||
const ctx = actualTx as TxCreateDoc<Request>
|
||||
if (ctx.objectClass !== hr.class.Request) {
|
||||
return []
|
||||
}
|
||||
|
||||
const sender = await getEmployeeAccountById(ctx.modifiedBy, control)
|
||||
if (sender === undefined) return []
|
||||
|
||||
const request = control.removedMap.get(ctx.objectId) as Request
|
||||
if (request === undefined) return []
|
||||
|
||||
return await getEmailNotification(
|
||||
control,
|
||||
sender,
|
||||
request,
|
||||
ctx.objectSpace as Ref<Department>,
|
||||
hr.ids.RemoveRequestNotifcation
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* @public
|
||||
*/
|
||||
export async function RequestHTMLPresenter (doc: Doc, control: TriggerControl): Promise<string> {
|
||||
const request = doc as Request
|
||||
const employee = (await control.findAll(contact.class.Employee, { _id: request.attachedTo }))[0]
|
||||
const who = formatName(employee.name)
|
||||
const type = await translate(control.modelDb.getObject(request.type).label, {})
|
||||
|
||||
const date = tzDateEqual(request.tzDate, request.tzDueDate)
|
||||
? `on ${new Date(fromTzDate(request.tzDate)).toLocaleDateString()}`
|
||||
: `from ${new Date(fromTzDate(request.tzDate)).toLocaleDateString()} to ${new Date(
|
||||
fromTzDate(request.tzDueDate)
|
||||
).toLocaleDateString()}`
|
||||
|
||||
return `${who} - ${type.toLowerCase()} ${date}`
|
||||
}
|
||||
|
||||
/**
|
||||
* @public
|
||||
*/
|
||||
export async function RequestTextPresenter (doc: Doc, control: TriggerControl): Promise<string> {
|
||||
const request = doc as Request
|
||||
const employee = (await control.findAll(contact.class.Employee, { _id: request.attachedTo }))[0]
|
||||
const who = formatName(employee.name)
|
||||
const type = await translate(control.modelDb.getObject(request.type).label, {})
|
||||
|
||||
const date = tzDateEqual(request.tzDate, request.tzDueDate)
|
||||
? `on ${new Date(fromTzDate(request.tzDate)).toLocaleDateString()}`
|
||||
: `from ${new Date(fromTzDate(request.tzDate)).toLocaleDateString()} to ${new Date(
|
||||
fromTzDate(request.tzDueDate)
|
||||
).toLocaleDateString()}`
|
||||
|
||||
return `${who} - ${type.toLowerCase()} ${date}`
|
||||
}
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/explicit-function-return-type
|
||||
export default async () => ({
|
||||
trigger: {
|
||||
OnRequestCreate,
|
||||
OnRequestUpdate,
|
||||
OnRequestRemove,
|
||||
OnDepartmentStaff,
|
||||
OnEmployeeDeactivate
|
||||
},
|
||||
function: {
|
||||
RequestHTMLPresenter,
|
||||
RequestTextPresenter
|
||||
}
|
||||
})
|
||||
|
@ -29,6 +29,7 @@
|
||||
"dependencies": {
|
||||
"@hcengineering/core": "^0.6.21",
|
||||
"@hcengineering/platform": "^0.6.8",
|
||||
"@hcengineering/server-core": "^0.6.1"
|
||||
"@hcengineering/server-core": "^0.6.1",
|
||||
"@hcengineering/server-notification": "^0.6.0"
|
||||
}
|
||||
}
|
||||
|
@ -16,6 +16,7 @@
|
||||
import type { Plugin, Resource } from '@hcengineering/platform'
|
||||
import { plugin } from '@hcengineering/platform'
|
||||
import type { TriggerFunc } from '@hcengineering/server-core'
|
||||
import { Presenter } from '@hcengineering/server-notification'
|
||||
|
||||
/**
|
||||
* @public
|
||||
@ -27,6 +28,13 @@ export const serverHrId = 'server-hr' as Plugin
|
||||
*/
|
||||
export default plugin(serverHrId, {
|
||||
trigger: {
|
||||
OnDepartmentStaff: '' as Resource<TriggerFunc>
|
||||
OnDepartmentStaff: '' as Resource<TriggerFunc>,
|
||||
OnRequestCreate: '' as Resource<TriggerFunc>,
|
||||
OnRequestUpdate: '' as Resource<TriggerFunc>,
|
||||
OnRequestRemove: '' as Resource<TriggerFunc>
|
||||
},
|
||||
function: {
|
||||
RequestHTMLPresenter: '' as Resource<Presenter>,
|
||||
RequestTextPresenter: '' as Resource<Presenter>
|
||||
}
|
||||
})
|
||||
|
@ -168,7 +168,10 @@ function fillTemplate (template: string, sender: string, doc: string, data: stri
|
||||
return res
|
||||
}
|
||||
|
||||
async function getContent (
|
||||
/**
|
||||
* @public
|
||||
*/
|
||||
export async function getContent (
|
||||
doc: Doc | undefined,
|
||||
sender: string,
|
||||
type: Ref<NotificationType>,
|
||||
@ -414,6 +417,8 @@ async function getBacklinkDoc (backlink: Backlink, control: TriggerControl): Pro
|
||||
)[0]
|
||||
}
|
||||
|
||||
export * from './types'
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/explicit-function-return-type
|
||||
export default async () => ({
|
||||
trigger: {
|
||||
|
@ -1,3 +1,6 @@
|
||||
/**
|
||||
* @public
|
||||
*/
|
||||
export interface Content {
|
||||
text: string
|
||||
html: string
|
||||
|
Loading…
Reference in New Issue
Block a user