Inactive employee (#2157)

Signed-off-by: Denis Bykhov <80476319+BykhovDenis@users.noreply.github.com>
This commit is contained in:
Denis Bykhov 2022-06-29 11:51:29 +06:00 committed by GitHub
parent 61216392e9
commit 168ae1cd54
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
57 changed files with 595 additions and 269 deletions

View File

@ -2,6 +2,11 @@
## 0.6.30 (upcoming)
Core:
- Allow to leave workspace
- Allow to kick employee
HR:
- Allow to change assignee in Kanban

View File

@ -37,6 +37,7 @@
"@anticrm/ui": "~0.6.0",
"@anticrm/platform": "~0.6.6",
"@anticrm/contact": "~0.6.5",
"@anticrm/contact-resources": "~0.6.0"
"@anticrm/contact-resources": "~0.6.0",
"@anticrm/view": "~0.6.0"
}
}

View File

@ -121,6 +121,8 @@ export class TStatus extends TAttachedDoc implements Status {
@Model(contact.class.Employee, contact.class.Person)
@UX(contact.string.Employee, contact.icon.Person, undefined, 'name')
export class TEmployee extends TPerson implements Employee {
active!: boolean
@Prop(Collection(contact.class.Status), contact.string.Status)
statuses?: number
}
@ -243,7 +245,7 @@ export function createModel (builder: Builder): void {
})
builder.mixin(contact.class.Employee, core.class.Class, view.mixin.AttributeEditor, {
inlineEditor: contact.component.PersonEditor
inlineEditor: contact.component.EmployeeEditor
})
builder.mixin(contact.class.Channel, core.class.Class, view.mixin.AttributePresenter, {
@ -393,6 +395,22 @@ export function createModel (builder: Builder): void {
group: 'create'
}
})
createAction(
builder,
{
action: contact.actionImpl.KickEmployee,
label: contact.string.KickEmployee,
category: contact.category.Contact,
target: contact.class.Employee,
input: 'focus',
context: {
mode: ['context'],
group: 'other'
}
},
contact.action.KickEmployee
)
}
export { contactOperation } from './migration'

View File

@ -17,7 +17,7 @@
import { TxOperations } from '@anticrm/core'
import { MigrateOperation, MigrationClient, MigrationUpgradeClient } from '@anticrm/model'
import core from '@anticrm/model-core'
import contact from './index'
import contact, { DOMAIN_CONTACT } from './index'
async function createSpace (tx: TxOperations): Promise<void> {
const current = await tx.findOne(core.class.Space, {
@ -56,8 +56,22 @@ async function createSpace (tx: TxOperations): Promise<void> {
}
}
async function setActiveEmployee (client: MigrationClient): Promise<void> {
await client.update(
DOMAIN_CONTACT,
{
_class: contact.class.Employee
},
{
active: true
}
)
}
export const contactOperation: MigrateOperation = {
async migrate (client: MigrationClient): Promise<void> {},
async migrate (client: MigrationClient): Promise<void> {
await setActiveEmployee(client)
},
async upgrade (client: MigrationUpgradeClient): Promise<void> {
const tx = new TxOperations(client, core.account.System)
await createSpace(tx)

View File

@ -20,6 +20,7 @@ import {} from '@anticrm/core'
import { ObjectSearchCategory, ObjectSearchFactory } from '@anticrm/model-presentation'
import { IntlString, mergeIds, Resource } from '@anticrm/platform'
import type { AnyComponent } from '@anticrm/ui'
import { Action, ActionCategory, ViewAction } from '@anticrm/view'
export default mergeIds(contactId, contact, {
component: {
@ -38,7 +39,8 @@ export default mergeIds(contactId, contact, {
Members: '' as AnyComponent,
MemberPresenter: '' as AnyComponent,
EditMember: '' as AnyComponent,
EmployeeArrayEditor: '' as AnyComponent
EmployeeArrayEditor: '' as AnyComponent,
EmployeeEditor: '' as AnyComponent
},
string: {
Persons: '' as IntlString,
@ -71,5 +73,14 @@ export default mergeIds(contactId, contact, {
EmployeeCategory: '' as Ref<ObjectSearchCategory>,
PersonCategory: '' as Ref<ObjectSearchCategory>,
OrganizationCategory: '' as Ref<ObjectSearchCategory>
},
category: {
Contact: '' as Ref<ActionCategory>
},
action: {
KickEmployee: '' as Ref<Action>
},
actionImpl: {
KickEmployee: '' as ViewAction
}
})

View File

@ -54,7 +54,8 @@ export const demoOperation: MigrateOperation = {
if (current === undefined) {
const employee = await tx.createDoc(contact.class.Employee, contact.space.Employee, {
name: 'Chen,Rosamund',
city: 'Mountain View'
city: 'Mountain View',
active: true
})
await tx.createDoc<EmployeeAccount>(contact.class.EmployeeAccount, core.space.Model, {

View File

@ -0,0 +1,60 @@
<!--
// Copyright © 2020, 2021 Anticrm Platform Contributors.
// Copyright © 2021 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, { Employee } from '@anticrm/contact'
import type { Class, DocumentQuery, FindOptions, Ref } from '@anticrm/core'
import type { IntlString } from '@anticrm/platform'
import { ButtonKind, ButtonSize, LabelAndProps } from '@anticrm/ui'
import presentation from '..'
import IconPerson from './icons/Person.svelte'
import UserBox from './UserBox.svelte'
export let _class: Ref<Class<Employee>> = contact.class.Employee
export let options: FindOptions<Employee> | undefined = undefined
export let docQuery: DocumentQuery<Employee> | undefined = {
active: true
}
export let label: IntlString
export let placeholder: IntlString = presentation.string.Search
export let value: Ref<Employee> | null | undefined
export let allowDeselect = false
export let titleDeselect: IntlString | undefined = undefined
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 focusIndex = -1
export let showTooltip: LabelAndProps | undefined = undefined
</script>
<UserBox
{_class}
{options}
{docQuery}
{label}
icon={IconPerson}
{placeholder}
bind:value
{allowDeselect}
{titleDeselect}
{kind}
{size}
{justify}
{width}
{focusIndex}
{showTooltip}
on:change
/>

View File

@ -16,7 +16,6 @@
import type { Class, Doc, DocumentQuery, FindOptions, Ref } from '@anticrm/core'
import type { IntlString } from '@anticrm/platform'
import {
AnyComponent,
Button,
CheckBox,
createFocusManager,
@ -32,6 +31,7 @@
import { createEventDispatcher, afterUpdate } from 'svelte'
import presentation from '..'
import { createQuery, getClient } from '../utils'
import { ObjectCreate } from '../types'
export let _class: Ref<Class<Doc>>
export let options: FindOptions<Doc> | undefined = undefined
@ -53,13 +53,7 @@
export let groupBy = '_class'
export let create:
| {
component: AnyComponent
label: IntlString
update: (doc: Doc) => string
}
| undefined = undefined
export let create: ObjectCreate | undefined = undefined
let search: string = ''
let objects: Doc[] = []
@ -151,7 +145,7 @@
// We expect reference to new object.
const newPerson = await client.findOne(_class, { _id: res })
if (newPerson !== undefined) {
search = c.update(newPerson)
search = c.update?.(newPerson) ?? ''
}
}
})

View File

@ -23,7 +23,6 @@
Button,
eventToHTMLElement,
getFocusManager,
AnyComponent,
Tooltip,
TooltipAlignment,
ButtonKind,
@ -33,6 +32,7 @@
import type { Ref, Class, Space, DocumentQuery } from '@anticrm/core'
import { createEventDispatcher } from 'svelte'
import { ObjectCreate } from '../types'
export let _class: Ref<Class<Space>>
export let spaceQuery: DocumentQuery<Space> | undefined = { archived: false }
@ -40,17 +40,13 @@
export let value: Ref<Space> | undefined
export let focusIndex = -1
export let focus = false
export let create:
| {
component: AnyComponent
label: IntlString
}
| undefined = undefined
export let create: ObjectCreate | undefined = undefined
export let labelDirection: TooltipAlignment | undefined = undefined
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 allowDeselect = false
let selected: Space | undefined
@ -70,6 +66,7 @@
{
_class,
label,
allowDeselect,
options: { sort: { modifiedOn: -1 } },
selected,
spaceQuery,

View File

@ -15,7 +15,8 @@
<script lang="ts">
import type { Class, DocumentQuery, Ref, Space } from '@anticrm/core'
import type { IntlString } from '@anticrm/platform'
import { AnyComponent, ButtonKind, ButtonSize } from '@anticrm/ui'
import { ButtonKind, ButtonSize } from '@anticrm/ui'
import { ObjectCreate } from '../types'
import SpaceSelect from './SpaceSelect.svelte'
export let space: Ref<Space> | undefined = undefined
@ -26,13 +27,9 @@
export let size: ButtonSize = 'small'
export let justify: 'left' | 'center' = 'center'
export let width: string | undefined = undefined
export let allowDeselect = false
export let create:
| {
component: AnyComponent
label: IntlString
}
| undefined = undefined
export let create: ObjectCreate | undefined = undefined
</script>
<SpaceSelect
@ -41,6 +38,7 @@
focusIndex={-10}
{_class}
spaceQuery={query}
{allowDeselect}
{label}
{size}
{kind}

View File

@ -14,20 +14,15 @@
-->
<script lang="ts">
import type { Class, Doc, DocumentQuery, Ref, Space } from '@anticrm/core'
import { IntlString } from '@anticrm/platform'
import { AnyComponent } from '@anticrm/ui'
import { ObjectCreate } from '../types'
import ObjectPopup from './ObjectPopup.svelte'
import SpaceInfo from './SpaceInfo.svelte'
export let _class: Ref<Class<Space>>
export let selected: Ref<Space> | undefined
export let spaceQuery: DocumentQuery<Space> | undefined
export let create:
| {
component: AnyComponent
label: IntlString
}
| undefined = undefined
export let create: ObjectCreate | undefined = undefined
export let allowDeselect = false
$: _create =
create !== undefined
@ -43,7 +38,7 @@
{selected}
bind:docQuery={spaceQuery}
multiSelect={false}
allowDeselect={false}
{allowDeselect}
shadows={true}
create={_create}
on:update

View File

@ -15,10 +15,9 @@
-->
<script lang="ts">
import contact, { Contact, formatName } from '@anticrm/contact'
import type { Class, FindOptions, Ref } from '@anticrm/core'
import type { Class, DocumentQuery, FindOptions, Ref } from '@anticrm/core'
import type { Asset, IntlString } from '@anticrm/platform'
import {
AnyComponent,
AnySvelteComponent,
Button,
ButtonKind,
@ -30,6 +29,7 @@
} from '@anticrm/ui'
import { createEventDispatcher } from 'svelte'
import presentation from '..'
import { ObjectCreate } from '../types'
import { getClient } from '../utils'
import IconPerson from './icons/Person.svelte'
import UserInfo from './UserInfo.svelte'
@ -38,6 +38,7 @@
export let _class: Ref<Class<Contact>>
export let excluded: Ref<Contact>[] | undefined = undefined
export let options: FindOptions<Contact> | undefined = undefined
export let docQuery: DocumentQuery<Contact> | undefined = undefined
export let label: IntlString
export let icon: Asset | AnySvelteComponent | undefined = IconPerson
export let placeholder: IntlString = presentation.string.Search
@ -52,12 +53,7 @@
export let focusIndex = -1
export let showTooltip: LabelAndProps | undefined = undefined
export let create:
| {
component: AnyComponent
label: IntlString
}
| undefined = undefined
export let create: ObjectCreate | undefined = undefined
const dispatch = createEventDispatcher()
@ -85,6 +81,7 @@
{
_class,
options,
docQuery,
ignoreUsers: excluded ?? [],
icon,
allowDeselect,
@ -108,54 +105,31 @@
)
}
}
$: hideIcon = size === 'x-large' || (size === 'large' && kind !== 'link')
</script>
<div bind:this={container} class="min-w-0" class:w-full={width === '100%'}>
{#if kind !== 'link'}
<Button
{focusIndex}
icon={size === 'x-large' && selected ? undefined : icon}
width={width ?? 'min-content'}
{size}
{kind}
{justify}
{showTooltip}
on:click={_click}
>
<span slot="content" class="overflow-label disabled">
{#if selected}
{#if size === 'x-large'}
<UserInfo value={selected} size={'medium'} {icon} />
{:else}
{getName(selected)}
{/if}
<Button
{focusIndex}
icon={hideIcon && selected ? undefined : icon}
width={width ?? 'min-content'}
{size}
{kind}
{justify}
{showTooltip}
on:click={_click}
>
<span slot="content" class="overflow-label disabled">
{#if selected}
{#if hideIcon}
<UserInfo value={selected} size={kind === 'link' ? 'x-small' : 'medium'} {icon} />
{:else}
<Label {label} />
{getName(selected)}
{/if}
</span>
</Button>
{:else}
<Button
{focusIndex}
icon={(size === 'x-large' || size === 'large') && selected ? undefined : icon}
width={width ?? 'min-content'}
{size}
{kind}
{justify}
{showTooltip}
on:click={_click}
>
<span slot="content" class="overflow-label disabled">
{#if selected}
{#if size === 'x-large' || size === 'large'}
<UserInfo value={selected} size={'x-small'} {icon} />
{:else}
{getName(selected)}
{/if}
{:else}
<Label {label} />
{/if}
</span>
</Button>
{/if}
{:else}
<Label {label} />
{/if}
</span>
</Button>
</div>

View File

@ -13,20 +13,22 @@
// limitations under the License.
-->
<script lang="ts">
import { Person } from '@anticrm/contact'
import type { Class, Doc, Ref } from '@anticrm/core'
import contact, { Employee } from '@anticrm/contact'
import type { Class, DocumentQuery, Ref } from '@anticrm/core'
import type { IntlString } from '@anticrm/platform'
import { translate } from '@anticrm/platform'
import type { ButtonKind, ButtonSize, TooltipAlignment } from '@anticrm/ui'
import { ButtonKind, ButtonSize, Label, TooltipAlignment } from '@anticrm/ui'
import { Tooltip, showPopup, Button } from '@anticrm/ui'
import { createEventDispatcher } from 'svelte'
import presentation, { CombineAvatars, UsersPopup } from '..'
import { createQuery } from '../utils'
import Members from './icons/Members.svelte'
export let items: Ref<Person>[] = []
export let _class: Ref<Class<Doc>>
export let items: Ref<Employee>[] = []
export let _class: Ref<Class<Employee>> = contact.class.Employee
export let label: IntlString
export let docQuery: DocumentQuery<Employee> | undefined = {
active: true
}
export let kind: ButtonKind = 'no-border'
export let size: ButtonSize = 'small'
@ -34,11 +36,11 @@
export let width: string | undefined = undefined
export let labelDirection: TooltipAlignment | undefined = undefined
let persons: Person[] = []
let persons: Employee[] = []
const query = createQuery()
$: query.query<Person>(_class, { _id: { $in: items } }, (result) => {
$: query.query<Employee>(_class, { _id: { $in: items } }, (result) => {
persons = result
})
@ -50,6 +52,7 @@
{
_class,
label,
docQuery,
multiSelect: true,
allowDeselect: false,
selectedUsers: items
@ -80,9 +83,9 @@
{#if persons.length > 0}
<div class="flex-row-center flex-nowrap pointer-events-none">
<CombineAvatars {_class} bind:items size={'inline'} />
{#await translate(presentation.string.NumberMembers, { count: persons.length }) then text}
<span class="overflow-label ml-1-5">{text}</span>
{/await}
<span class="overflow-label ml-1-5">
<Label label={presentation.string.NumberMembers} params={{ count: persons.length }} />
</span>
</div>
{/if}
</svelte:fragment>

View File

@ -15,17 +15,19 @@
<script lang="ts">
import { createEventDispatcher, afterUpdate } from 'svelte'
import { Contact, getFirstName, Person } from '@anticrm/contact'
import type { Class, Doc, FindOptions, Ref } from '@anticrm/core'
import type { Class, Doc, DocumentQuery, FindOptions, Ref } from '@anticrm/core'
import type { Asset, IntlString } from '@anticrm/platform'
import { AnyComponent, AnySvelteComponent, Icon, Label } from '@anticrm/ui'
import { AnySvelteComponent, Icon, Label } from '@anticrm/ui'
import presentation from '..'
import { getClient } from '../utils'
import ObjectPopup from './ObjectPopup.svelte'
import UserInfo from './UserInfo.svelte'
import { ObjectCreate } from '../types'
export let _class: Ref<Class<Contact>>
export let options: FindOptions<Contact> | undefined = undefined
export let selected: Ref<Person> | undefined
export let docQuery: DocumentQuery<Contact> | undefined = undefined
export let multiSelect: boolean = false
export let allowDeselect: boolean = false
@ -35,14 +37,9 @@
export let ignoreUsers: Ref<Person>[] = []
export let shadows: boolean = true
export let icon: Asset | AnySvelteComponent | undefined = undefined
export let create: ObjectCreate | undefined = undefined
const hierarchy = getClient().getHierarchy()
export let create:
| {
component: AnyComponent
label: IntlString
}
| undefined = undefined
$: _create =
create !== undefined
@ -68,6 +65,7 @@
{allowDeselect}
{titleDeselect}
{placeholder}
{docQuery}
groupBy={'_class'}
bind:selectedObjects={selectedUsers}
bind:ignoreObjects={ignoreUsers}

View File

@ -37,6 +37,7 @@ export { default as SpacesMultiPopup } from './components/SpacesMultiPopup.svelt
export { default as UserBox } from './components/UserBox.svelte'
export { default as UserBoxList } from './components/UserBoxList.svelte'
export { default as UserInfo } from './components/UserInfo.svelte'
export { default as EmployeeBox } from './components/EmployeeBox.svelte'
export { default as UsersPopup } from './components/UsersPopup.svelte'
export { default as MembersBox } from './components/MembersBox.svelte'
export { default as IconMembers } from './components/icons/Members.svelte'

View File

@ -1,6 +1,6 @@
import { Client, Doc } from '@anticrm/core'
import { Asset, IntlString, Resource } from '@anticrm/platform'
import { AnySvelteComponent } from '@anticrm/ui'
import { AnyComponent, AnySvelteComponent } from '@anticrm/ui'
/**
* @public
@ -14,6 +14,15 @@ export interface ObjectSearchResult {
iconProps?: any
}
/**
* @public
*/
export interface ObjectCreate {
component: AnyComponent
label: IntlString
update?: (doc: Doc) => string
}
/**
* @public
*/

View File

@ -13,7 +13,7 @@
// limitations under the License.
-->
<script lang="ts">
import contact, { Employee } from '@anticrm/contact'
import { Employee } from '@anticrm/contact'
import { Class, Ref, Space } from '@anticrm/core'
import { SpaceMultiBoxList, UserBoxList } from '@anticrm/presentation'
import { DropdownLabelsIntl } from '@anticrm/ui'
@ -31,7 +31,6 @@
<div class="filterBlockContainer">
<div class="simpleFilterButton">
<UserBoxList
_class={contact.class.Employee}
items={selectedParticipants}
label={attachment.string.FileBrowserFilterFrom}
on:update={(evt) => {

View File

@ -17,7 +17,7 @@
import { AttachmentDroppable, AttachmentsPresenter } from '@anticrm/attachment-resources'
import type { Card } from '@anticrm/board'
import { CommentsPresenter } from '@anticrm/chunter-resources'
import contact, { Employee } from '@anticrm/contact'
import { Employee } from '@anticrm/contact'
import type { Ref, WithLookup } from '@anticrm/core'
import notification from '@anticrm/notification'
import view from '@anticrm/view'
@ -217,12 +217,7 @@
</div>
{#if (object.members?.length ?? 0) > 0}
<div class="flex justify-end mt-1 mb-2" style:pointer-events={dragoverAttachment ? 'none' : 'all'}>
<UserBoxList
_class={contact.class.Employee}
items={object.members}
label={board.string.Members}
on:update={updateMembers}
/>
<UserBoxList items={object.members} label={board.string.Members} on:update={updateMembers} />
</div>
{/if}
</div>

View File

@ -1,5 +1,5 @@
<script lang="ts">
import contact, { Employee } from '@anticrm/contact'
import { Employee } from '@anticrm/contact'
import { Ref } from '@anticrm/core'
import { UserBoxList } from '@anticrm/presentation'
@ -8,4 +8,4 @@
export let value: Ref<Employee>[]
</script>
<UserBoxList items={value} _class={contact.class.Employee} label={board.string.Members} on:update />
<UserBoxList items={value} label={board.string.Members} on:update />

View File

@ -14,7 +14,7 @@
-->
<script lang="ts">
import { Ref } from '@anticrm/core'
import { createQuery, getClient, UserBox, MessageBox } from '@anticrm/presentation'
import { createQuery, getClient, EmployeeBox, MessageBox } from '@anticrm/presentation'
import type { TodoItem } from '@anticrm/task'
import task, { calcRank } from '@anticrm/task'
import {
@ -30,7 +30,7 @@
DateRangePresenter
} from '@anticrm/ui'
import { ContextMenu, HTMLPresenter } from '@anticrm/view-resources'
import contact, { Employee } from '@anticrm/contact'
import { Employee } from '@anticrm/contact'
import board from '../../plugin'
import { getDateIcon } from '../../utils/BoardUtils'
@ -273,8 +273,7 @@
on:change={(e) => updateDueDate(item, e.detail)}
noShift
/>
<UserBox
_class={contact.class.Employee}
<EmployeeBox
label={board.string.Assignee}
bind:value={item.assignee}
allowDeselect={true}

View File

@ -13,7 +13,7 @@
// limitations under the License.
-->
<script lang="ts">
import contact, { Employee, EmployeeAccount } from '@anticrm/contact'
import { Employee, EmployeeAccount } from '@anticrm/contact'
import { Class, Doc, getCurrentAccount, Ref } from '@anticrm/core'
import { Card, getClient, UserBoxList } from '@anticrm/presentation'
import ui, { EditBox, DateRangePresenter } from '@anticrm/ui'
@ -71,6 +71,6 @@
<svelte:fragment slot="pool">
<DateRangePresenter bind:value withTime editable labelNull={ui.string.SelectDate} />
<DateRangePresenter bind:value={dueDate} labelNull={calendar.string.DueTo} withTime editable />
<UserBoxList _class={contact.class.Employee} bind:items={participants} label={calendar.string.Participants} />
<UserBoxList bind:items={participants} label={calendar.string.Participants} />
</svelte:fragment>
</Card>

View File

@ -13,7 +13,7 @@
// limitations under the License.
-->
<script lang="ts">
import contact, { Employee, EmployeeAccount } from '@anticrm/contact'
import { Employee, EmployeeAccount } from '@anticrm/contact'
import { Class, Doc, getCurrentAccount, Ref } from '@anticrm/core'
import { Card, getClient, UserBoxList } from '@anticrm/presentation'
import ui, { EditBox, DateRangePresenter } from '@anticrm/ui'
@ -74,6 +74,6 @@
<svelte:fragment slot="pool">
<!-- <TimeShiftPicker title={calendar.string.Date} bind:value direction="after" /> -->
<DateRangePresenter bind:value withTime={true} editable={true} labelNull={ui.string.SelectDate} />
<UserBoxList _class={contact.class.Employee} bind:items={participants} label={calendar.string.Participants} />
<UserBoxList bind:items={participants} label={calendar.string.Participants} />
</svelte:fragment>
</Card>

View File

@ -73,6 +73,9 @@
<UsersPopup
selected={undefined}
_class={contact.class.Employee}
docQuery={{
active: true
}}
multiSelect={true}
allowDeselect={true}
selectedUsers={selectedEmployees}

View File

@ -70,9 +70,5 @@
dispatch('close')
}}
>
<UserBoxList
_class={contact.class.Employee}
label={chunter.string.Members}
on:update={(evt) => (employeeIds = evt.detail)}
/>
<UserBoxList label={chunter.string.Members} on:update={(evt) => (employeeIds = evt.detail)} />
</SpaceCreateCard>

View File

@ -63,6 +63,8 @@
"Member": "Member",
"Members": "Members",
"NoMembers": "No members added",
"AddMember": "Add member"
"AddMember": "Add member",
"KickEmployee": "Kick an employee",
"KickEmployeeDescr": "Are you sure you want to kick the employee out of the workspace? This action cannot be undone"
}
}

View File

@ -63,6 +63,8 @@
"Member": "Сотрудник",
"Members": "Сотрудники",
"NoMembers": "Нет добавленных сотрудников",
"AddMember": "Добавить сотрудника"
"AddMember": "Добавить сотрудника",
"KickEmployee": "Исключить сотрудника",
"KickEmployeeDescr": "Вы действительно хотите выгнать сотрудника из рабочего пространства? Это действие нельзя отменить"
}
}

View File

@ -43,6 +43,7 @@
"@anticrm/notification-resources": "~0.6.0",
"@anticrm/panel": "~0.6.0",
"@anticrm/view-resources": "~0.6.0",
"@anticrm/attachment": "~0.6.1"
"@anticrm/attachment": "~0.6.1",
"@anticrm/login-resources": "~0.6.2"
}
}

View File

@ -1,17 +1,16 @@
<script lang="ts">
import { Person } from '@anticrm/contact'
import { Employee } from '@anticrm/contact'
import { Ref } from '@anticrm/core'
import { IntlString } from '@anticrm/platform'
import { UserBoxList } from '@anticrm/presentation'
import contact from '../plugin'
export let label: IntlString
export let value: Ref<Person>[]
export let onChange: (refs: Ref<Person>[]) => void
export let value: Ref<Employee>[]
export let onChange: (refs: Ref<Employee>[]) => void
let timer: any
function onUpdate (evt: CustomEvent<Ref<Person>[]>): void {
function onUpdate (evt: CustomEvent<Ref<Employee>[]>): void {
clearTimeout(timer)
timer = setTimeout(() => {
onChange(evt.detail)
@ -19,13 +18,4 @@
}
</script>
<UserBoxList
_class={contact.class.Employee}
items={value}
{label}
on:update={onUpdate}
kind={'link'}
size={'medium'}
justify={'left'}
width={'100%'}
/>
<UserBoxList items={value} {label} on:update={onUpdate} kind={'link'} size={'medium'} justify={'left'} width={'100%'} />

View File

@ -0,0 +1,49 @@
<!--
// Copyright © 2022 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 { Employee } from '@anticrm/contact'
import { DocumentQuery, Ref, RefTo } from '@anticrm/core'
import { IntlString } from '@anticrm/platform'
import { EmployeeBox } from '@anticrm/presentation'
import contact from '../plugin'
import { ButtonKind, ButtonSize } from '@anticrm/ui'
export let value: Ref<Employee> | undefined
export let label: IntlString = contact.string.Employee
export let onChange: (value: any) => void
export let type: RefTo<Employee> | undefined
export let kind: ButtonKind = 'no-border'
export let size: ButtonSize = 'small'
export let justify: 'left' | 'center' = 'center'
export let width: string | undefined = undefined
$: _class = type?.to ?? contact.class.Employee
const query: DocumentQuery<Employee> = {
active: true
}
</script>
<EmployeeBox
{_class}
docQuery={query}
{label}
{kind}
{size}
{justify}
{width}
bind:value
on:change={(e) => onChange(e.detail)}
/>

View File

@ -14,10 +14,11 @@
// limitations under the License.
//
import { Contact, formatName } from '@anticrm/contact'
import { Contact, Employee, formatName } from '@anticrm/contact'
import { Class, Client, Ref } from '@anticrm/core'
import { Resources } from '@anticrm/platform'
import { Avatar, ObjectSearchResult, UserInfo } from '@anticrm/presentation'
import { Avatar, getClient, MessageBox, ObjectSearchResult, UserInfo } from '@anticrm/presentation'
import { showPopup } from '@anticrm/ui'
import Channels from './components/Channels.svelte'
import ChannelsEditor from './components/ChannelsEditor.svelte'
import ChannelsPresenter from './components/ChannelsPresenter.svelte'
@ -45,6 +46,8 @@ import Members from './components/Members.svelte'
import MemberPresenter from './components/MemberPresenter.svelte'
import EditMember from './components/EditMember.svelte'
import EmployeeArrayEditor from './components/EmployeeArrayEditor.svelte'
import EmployeeEditor from './components/EmployeeEditor.svelte'
import { leaveWorkspace } from '@anticrm/login-resources'
export {
Channels,
@ -55,7 +58,8 @@ export {
ChannelsDropdown,
EmployeePresenter,
EmployeeBrowser,
MemberPresenter
MemberPresenter,
EmployeeEditor
}
async function queryContact (
@ -73,7 +77,30 @@ async function queryContact (
}))
}
async function kickEmployee (doc: Employee): Promise<void> {
const client = getClient()
const email = await client.findOne(contact.class.EmployeeAccount, { employee: doc._id })
if (email === undefined) return
showPopup(
MessageBox,
{
label: contact.string.KickEmployee,
message: contact.string.KickEmployeeDescr
},
undefined,
(res?: boolean) => {
if (res === true) {
// eslint-disable-next-line @typescript-eslint/no-floating-promises
leaveWorkspace(email.email)
}
}
)
}
export default async (): Promise<Resources> => ({
actionImpl: {
KickEmployee: kickEmployee
},
component: {
PersonEditor,
OrganizationEditor,
@ -94,7 +121,8 @@ export default async (): Promise<Resources> => ({
Members,
MemberPresenter,
EditMember,
EmployeeArrayEditor
EmployeeArrayEditor,
EmployeeEditor
},
completion: {
EmployeeQuery: async (client: Client, query: string) => await queryContact(contact.class.Employee, client, query),

View File

@ -56,6 +56,8 @@ export default mergeIds(contactId, contact, {
Member: '' as IntlString,
Members: '' as IntlString,
NoMembers: '' as IntlString,
AddMember: '' as IntlString
AddMember: '' as IntlString,
KickEmployee: '' as IntlString,
KickEmployeeDescr: '' as IntlString
}
})

View File

@ -105,6 +105,7 @@ export interface Status extends AttachedDoc {
* @public
*/
export interface Employee extends Person {
active: boolean
statuses?: number
}

View File

@ -13,9 +13,9 @@
// limitations under the License.
-->
<script lang="ts">
import contact, { Employee } from '@anticrm/contact'
import { Employee } from '@anticrm/contact'
import { Ref } from '@anticrm/core'
import { Card, getClient, SpaceSelector, UserBox } from '@anticrm/presentation'
import { Card, getClient, SpaceSelector, EmployeeBox } from '@anticrm/presentation'
import { Button, createFocusManager, EditBox, FocusHandler } from '@anticrm/ui'
import { createEventDispatcher } from 'svelte'
import hr from '../plugin'
@ -74,16 +74,13 @@
<SpaceSelector _class={hr.class.Department} label={hr.string.ParentDepartmentLabel} bind:space />
</svelte:fragment>
<svelte:fragment slot="pool">
<UserBox
<EmployeeBox
focusIndex={3}
_class={contact.class.Employee}
label={hr.string.TeamLead}
placeholder={hr.string.TeamLead}
bind:value={lead}
allowDeselect
titleDeselect={hr.string.UnAssignLead}
kind={'no-border'}
size={'small'}
/>
</svelte:fragment>
</Card>

View File

@ -52,7 +52,10 @@
_class: contact.class.Employee,
selected: value.$lookup?.teamLead,
allowDeselect: true,
placeholder: hr.string.TeamLead
placeholder: hr.string.TeamLead,
docQuery: {
active: true
}
},
eventToHTMLElement(event),
changeLead

View File

@ -35,6 +35,7 @@
{size}
{kind}
{justify}
allowDeselect
{width}
bind:space={value}
on:change={(e) => onChange(e.detail)}

View File

@ -77,6 +77,9 @@
UsersPopup,
{
_class: contact.class.Employee,
docQuery: {
active: true
},
ignoreUsers: employees.filter((p) => p.department === objectId).map((p) => p._id)
},
eventToHTMLElement(e),

View File

@ -434,3 +434,34 @@ export async function changePassword (oldPassword: string, password: string): Pr
body: serialize(request)
})
}
export async function leaveWorkspace (email: string): Promise<void> {
const accountsUrl = getMetadata(login.metadata.AccountsUrl)
if (accountsUrl === undefined) {
throw new Error('accounts url not specified')
}
const overrideToken = getMetadata(login.metadata.OverrideLoginToken)
if (overrideToken !== undefined) {
const endpoint = getMetadata(login.metadata.OverrideEndpoint)
if (endpoint !== undefined) {
return
}
}
const token = fetchMetadataLocalStorage(login.metadata.LoginToken) as string
const request: Request<[string]> = {
method: 'leaveWorkspace',
params: [email]
}
await fetch(accountsUrl, {
method: 'POST',
headers: {
Authorization: 'Bearer ' + token,
'Content-Type': 'application/json'
},
body: serialize(request)
})
}

View File

@ -17,7 +17,7 @@
import contact from '@anticrm/contact'
import { Account, Class, Client, Doc, generateId, Ref, SortingOrder, Space } from '@anticrm/core'
import { getResource, OK, Resource, Severity, Status } from '@anticrm/platform'
import { Card, createQuery, getClient, SpaceSelector, UserBox } from '@anticrm/presentation'
import { Card, createQuery, EmployeeBox, getClient, SpaceSelector, UserBox } from '@anticrm/presentation'
import type { Applicant, Candidate } from '@anticrm/recruit'
import task, { calcRank, SpaceWithStates, State } from '@anticrm/task'
import ui, {
@ -269,16 +269,13 @@
create={{ component: recruit.component.CreateCandidate, label: recruit.string.CreateTalent }}
/>
{/if}
<UserBox
<EmployeeBox
focusIndex={2}
_class={contact.class.Employee}
label={recruit.string.AssignRecruiter}
placeholder={recruit.string.Recruiters}
bind:value={doc.assignee}
allowDeselect
titleDeselect={recruit.string.UnAssignRecruiter}
kind={'no-border'}
size={'small'}
/>
{#if states.length > 0}
<Button

View File

@ -180,6 +180,6 @@
on:change={updateStart}
/>
<DateRangePresenter bind:value={dueDate} labelNull={recruit.string.DueDate} withTime editable />
<UserBoxList _class={contact.class.Employee} bind:items={doc.participants} label={calendar.string.Participants} />
<UserBoxList bind:items={doc.participants} label={calendar.string.Participants} />
</svelte:fragment>
</Card>

View File

@ -43,6 +43,8 @@
"EditAttribute": "Edit attribute",
"CreateEnum": "Create enum",
"Enums": "Enums",
"NewValue": "New value"
"NewValue": "New value",
"Leave": "Leave workspace",
"LeaveDescr": "Are you sure you want to leave the workspace? This action cannot be undone."
}
}

View File

@ -43,6 +43,8 @@
"EditAttribute": "Редактирование атрибута",
"CreateEnum": "Создать справочник",
"Enums": "Справочники",
"NewValue": "Новое значение"
"NewValue": "Новое значение",
"Leave": "Покинуть рабочее пространство",
"LeaveDescr": "Вы действительно хотите покинуть рабочее пространство? Отменить это действие невозможно"
}
}

View File

@ -15,15 +15,16 @@
<script lang="ts">
import { AttributeEditor, createQuery, EditableAvatar, getClient } from '@anticrm/presentation'
import setting from '@anticrm/setting'
import { EditBox, Icon, Label, createFocusManager, FocusHandler } from '@anticrm/ui'
import setting from '../plugin'
import { EditBox, Icon, Label, createFocusManager, FocusHandler, Button, showPopup } from '@anticrm/ui'
import contact, { Employee, EmployeeAccount, getFirstName, getLastName } from '@anticrm/contact'
import contactRes from '@anticrm/contact-resources/src/plugin'
import { getCurrentAccount } from '@anticrm/core'
import { getResource } from '@anticrm/platform'
import attachment from '@anticrm/attachment'
import { changeName } from '@anticrm/login-resources'
import { changeName, leaveWorkspace } from '@anticrm/login-resources'
import { ChannelsEditor } from '@anticrm/contact-resources'
import MessageBox from '@anticrm/presentation/src/components/MessageBox.svelte'
const client = getClient()
let employee: Employee | undefined
@ -71,6 +72,22 @@
}
const manager = createFocusManager()
async function leave (): Promise<void> {
showPopup(
MessageBox,
{
label: setting.string.Leave,
message: setting.string.LeaveDescr
},
undefined,
async (res?: boolean) => {
if (res === true) {
await leaveWorkspace(getCurrentAccount().email)
}
}
)
}
</script>
<FocusHandler {manager} />
@ -80,51 +97,62 @@
<div class="ac-header__icon"><Icon icon={setting.icon.EditProfile} size={'medium'} /></div>
<div class="ac-header__title"><Label label={setting.string.EditProfile} /></div>
</div>
{#if employee}
<div class="ac-body columns p-10">
<div class="mr-8">
<EditableAvatar avatar={employee.avatar} size={'x-large'} on:done={onAvatarDone} on:remove={removeAvatar} />
</div>
<div class="flex-grow flex-col">
<div class="flex-col">
<div class="name">
<EditBox
placeholder={contactRes.string.PersonFirstNamePlaceholder}
maxWidth="20rem"
bind:value={firstName}
focus
focusIndex={1}
on:change={() => {
changeName(firstName, lastName)
}}
/>
</div>
<div class="name">
<EditBox
placeholder={contactRes.string.PersonLastNamePlaceholder}
maxWidth="20rem"
bind:value={lastName}
focusIndex={2}
on:change={() => {
changeName(firstName, lastName)
}}
/>
</div>
<div class="location">
<AttributeEditor
maxWidth="20rem"
_class={contact.class.Person}
object={employee}
focusIndex={3}
key="city"
/>
</div>
<div class="ac-body p-10">
{#if employee}
<div class="flex flex-grow">
<div class="mr-8">
<EditableAvatar avatar={employee.avatar} size={'x-large'} on:done={onAvatarDone} on:remove={removeAvatar} />
</div>
<div class="flex-grow flex-col">
<div class="flex-col">
<div class="name">
<EditBox
placeholder={contactRes.string.PersonFirstNamePlaceholder}
maxWidth="20rem"
bind:value={firstName}
focus
focusIndex={1}
on:change={() => {
changeName(firstName, lastName)
}}
/>
</div>
<div class="name">
<EditBox
placeholder={contactRes.string.PersonLastNamePlaceholder}
maxWidth="20rem"
bind:value={lastName}
focusIndex={2}
on:change={() => {
changeName(firstName, lastName)
}}
/>
</div>
<div class="location">
<AttributeEditor
maxWidth="20rem"
_class={contact.class.Person}
object={employee}
focusIndex={3}
key="city"
/>
</div>
</div>
<div class="separator" />
<ChannelsEditor attachedTo={employee._id} attachedClass={employee._class} focusIndex={10} allowOpen={false} />
</div>
<div class="separator" />
<ChannelsEditor attachedTo={employee._id} attachedClass={employee._class} focusIndex={10} allowOpen={false} />
</div>
{/if}
<div class="footer">
<Button
icon={setting.icon.Signout}
label={setting.string.Leave}
on:click={() => {
leave()
}}
/>
</div>
{/if}
</div>
</div>
<style lang="scss">
@ -144,4 +172,8 @@
height: 1px;
background-color: var(--theme-card-divider);
}
.footer {
align-self: flex-end;
}
</style>

View File

@ -14,19 +14,12 @@
-->
<script lang="ts">
import type { Class, Doc, DocumentQuery, Enum, Ref } from '@anticrm/core'
import { IntlString } from '@anticrm/platform'
import { ObjectPopup } from '@anticrm/presentation'
import { AnyComponent } from '@anticrm/ui'
import { ObjectCreate, ObjectPopup } from '@anticrm/presentation'
export let _class: Ref<Class<Enum>>
export let selected: Ref<Enum> | undefined
export let query: DocumentQuery<Enum> | undefined
export let create:
| {
component: AnyComponent
label: IntlString
}
| undefined = undefined
export let create: ObjectCreate | undefined = undefined
$: _create =
create !== undefined

View File

@ -21,24 +21,19 @@
Button,
eventToHTMLElement,
getFocusManager,
AnyComponent,
Tooltip,
TooltipAlignment
} from '@anticrm/ui'
import EnumPopup from './EnumPopup.svelte'
import core, { Ref, Class, DocumentQuery, Enum } from '@anticrm/core'
import { ObjectCreate } from '@anticrm/presentation'
export let label: IntlString
export let value: Enum | undefined
export let focusIndex = -1
export let focus = false
export let create:
| {
component: AnyComponent
label: IntlString
}
| undefined = undefined
export let create: ObjectCreate | undefined = undefined
export let labelDirection: TooltipAlignment | undefined = undefined
const _class: Ref<Class<Enum>> = core.class.Enum

View File

@ -39,6 +39,8 @@ export default mergeIds(settingId, setting, {
EditAttribute: '' as IntlString,
CreateEnum: '' as IntlString,
Enums: '' as IntlString,
NewValue: '' as IntlString
NewValue: '' as IntlString,
Leave: '' as IntlString,
LeaveDescr: '' as IntlString
}
})

View File

@ -83,6 +83,9 @@
{
_class: contact.class.Employee,
selected: value?._id,
docQuery: {
active: true
},
allowDeselect: true,
placeholder: task.string.AssignThisTask
},

View File

@ -16,10 +16,9 @@
import { createEventDispatcher } from 'svelte'
import { Employee } from '@anticrm/contact'
import { AttachedData, Ref } from '@anticrm/core'
import { getClient, UserBox } from '@anticrm/presentation'
import { getClient, EmployeeBox } from '@anticrm/presentation'
import { Issue } from '@anticrm/tracker'
import { ButtonKind, ButtonSize, TooltipAlignment } from '@anticrm/ui'
import contact from '@anticrm/contact'
import tracker from '../../plugin'
export let value: Issue | AttachedData<Issue>
@ -45,8 +44,7 @@
</script>
{#if value}
<UserBox
_class={contact.class.Employee}
<EmployeeBox
label={tracker.string.Assignee}
placeholder={tracker.string.Assignee}
value={value.assignee}

View File

@ -83,6 +83,9 @@
{
_class: contact.class.Employee,
selected: value?._id,
docQuery: {
active: true
},
allowDeselect: true,
placeholder: tracker.string.AssignTo
},

View File

@ -72,6 +72,9 @@
{
_class: contact.class.Employee,
selected: value?._id,
docQuery: {
active: true
},
allowDeselect: true,
placeholder: tracker.string.ProjectLeadSearchPlaceholder
},

View File

@ -13,10 +13,9 @@
// limitations under the License.
-->
<script lang="ts">
import contact from '@anticrm/contact'
import { Data, Ref } from '@anticrm/core'
import { IntlString } from '@anticrm/platform'
import { Card, getClient, SpaceSelector, UserBox, UserBoxList } from '@anticrm/presentation'
import { Card, getClient, SpaceSelector, EmployeeBox, UserBoxList } from '@anticrm/presentation'
import { Project, ProjectStatus, Team } from '@anticrm/tracker'
import { DatePresenter, EditBox } from '@anticrm/ui'
import { createEventDispatcher } from 'svelte'
@ -83,19 +82,14 @@
</div>
<div slot="pool" class="flex-row-center text-sm gap-1-5">
<ProjectStatusSelector selectedProjectStatus={object.status} onProjectStatusChange={handleProjectStatusChanged} />
<UserBox
_class={contact.class.Employee}
<EmployeeBox
label={tracker.string.ProjectLead}
placeholder={tracker.string.AssignTo}
bind:value={object.lead}
allowDeselect
titleDeselect={tracker.string.Unassigned}
/>
<UserBoxList
_class={contact.class.Employee}
bind:items={object.members}
label={tracker.string.ProjectStatusPlaceholder}
/>
<UserBoxList bind:items={object.members} label={tracker.string.ProjectStatusPlaceholder} />
<!-- TODO: add labels after customize IssueNeedsToBeCompletedByThisDate -->
<DatePresenter bind:value={object.startDate} labelNull={tracker.string.StartDate} editable />
<DatePresenter bind:value={object.targetDate} labelNull={tracker.string.TargetDate} editable />

View File

@ -61,6 +61,9 @@
selectedUsers: value.members,
allowDeselect: true,
multiSelect: true,
docQuery: {
active: true
},
placeholder: tracker.string.ProjectMembersSearchPlaceholder
},
eventToHTMLElement(event),

View File

@ -14,6 +14,8 @@
"Leave": "Leave",
"Joined": "Joined",
"Join": "Join",
"BrowseSpaces": "Browse spaces"
"BrowseSpaces": "Browse spaces",
"AccountDisabled": "Account is disabled",
"AccountDisabledDescr": "Please contact the workspace administrator"
}
}

View File

@ -14,6 +14,8 @@
"Leave": "Покинуть",
"Joined": "Вы присоеденились",
"Join": "Присоедениться",
"BrowseSpaces": "Обзор пространств"
"BrowseSpaces": "Обзор пространств",
"AccountDisabled": "Аккаунт отключен",
"AccountDisabledDescr": "Пожалуйста свяжитесь с администратором"
}
}

View File

@ -28,6 +28,7 @@
DatePickerPopup,
fetchMetadataLocalStorage,
getCurrentLocation,
Label,
location,
Location,
navigate,
@ -358,7 +359,7 @@
</script>
<svelte:window on:resize={windowResize} />
{#if client}
{#if employee?.active === true}
<ActionHandler />
<svg class="svg-mask">
<clipPath id="notify-normal">
@ -426,9 +427,7 @@
showPopup(AccountPopup, {}, 'account')
}}
>
{#if employee}
<Avatar avatar={employee.avatar} size={'medium'} />
{/if}
<Avatar avatar={employee.avatar} size={'medium'} />
</div>
</div>
</div>
@ -507,7 +506,10 @@
</Popup>
<DatePickerPopup />
{:else}
No client
<div class="flex-col-center justify-center h-full flex-grow">
<h1><Label label={workbench.string.AccountDisabled} /></h1>
<Label label={workbench.string.AccountDisabledDescr} />
</div>
{/if}
<style lang="scss">

View File

@ -34,7 +34,9 @@ export default mergeIds(workbenchId, workbench, {
Leave: '' as IntlString,
Joined: '' as IntlString,
Join: '' as IntlString,
BrowseSpaces: '' as IntlString
BrowseSpaces: '' as IntlString,
AccountDisabled: '' as IntlString,
AccountDisabledDescr: '' as IntlString
},
component: {
SpacePanel: '' as AnyComponent

View File

@ -14,12 +14,12 @@
//
import contact, { Employee } from '@anticrm/contact'
import core, { Ref, SortingOrder, Tx, TxFactory, TxMixin } from '@anticrm/core'
import core, { Ref, SortingOrder, Tx, TxFactory, TxMixin, TxUpdateDoc } from '@anticrm/core'
import hr, { Department, DepartmentMember, Staff } from '@anticrm/hr'
import { extractTx, TriggerControl } from '@anticrm/server-core'
async function getOldDepartment (
currentTx: TxMixin<Employee, Staff>,
currentTx: TxMixin<Employee, Staff> | TxUpdateDoc<Employee>,
control: TriggerControl
): Promise<Ref<Department> | undefined> {
const txes = await control.findAll<TxMixin<Employee, Staff>>(
@ -109,6 +109,12 @@ export async function OnDepartmentStaff (tx: Tx, control: TriggerControl): Promi
const lastDepartment = await getOldDepartment(ctx, control)
const departmentId = ctx.attributes.department
if (departmentId === null) {
if (lastDepartment !== undefined) {
const removed = await buildHierarchy(lastDepartment, control)
return getTxes(control.txFactory, targetAccount._id, [], removed)
}
}
const push = await buildHierarchy(departmentId, control)
if (lastDepartment === undefined) {
@ -124,9 +130,36 @@ export async function OnDepartmentStaff (tx: Tx, control: TriggerControl): Promi
return []
}
/**
* @public
*/
export async function OnEmployeeDeactivate (tx: Tx, control: TriggerControl): Promise<Tx[]> {
const actualTx = extractTx(tx)
if (core.class.TxUpdateDoc !== actualTx._class) {
return []
}
const ctx = actualTx as TxUpdateDoc<Employee>
if (ctx.objectClass !== contact.class.Employee || ctx.operations.active !== false) {
return []
}
const targetAccount = (
await control.modelDb.findAll(contact.class.EmployeeAccount, {
employee: ctx.objectId
})
)[0]
if (targetAccount === undefined) return []
const lastDepartment = await getOldDepartment(ctx, control)
if (lastDepartment === undefined) return []
const removed = await buildHierarchy(lastDepartment, control)
return getTxes(control.txFactory, targetAccount._id, [], removed)
}
// eslint-disable-next-line @typescript-eslint/explicit-function-return-type
export default async () => ({
trigger: {
OnDepartmentStaff
OnDepartmentStaff,
OnEmployeeDeactivate
}
})

View File

@ -13,8 +13,8 @@
// limitations under the f.
//
import contact, { combineName } from '@anticrm/contact'
import core, { TxOperations } from '@anticrm/core'
import contact, { combineName, Employee } from '@anticrm/contact'
import core, { Ref, TxOperations } from '@anticrm/core'
import platform, {
getMetadata,
PlatformError,
@ -461,6 +461,14 @@ export async function assignWorkspace (db: Db, email: string, workspace: string)
if (account !== null) await createEmployeeAccount(account, workspace)
}
async function createEmployee (ops: TxOperations, name: string): Promise<Ref<Employee>> {
return await ops.createDoc(contact.class.Employee, contact.space.Employee, {
name,
city: '',
active: true
})
}
async function createEmployeeAccount (account: Account, workspace: string): Promise<void> {
const connection = await connect(getTransactor(), workspace, account.email)
try {
@ -472,10 +480,7 @@ async function createEmployeeAccount (account: Account, workspace: string): Prom
const existingAccount = await ops.findOne(contact.class.EmployeeAccount, { email: account.email })
if (existingAccount === undefined) {
const employee = await ops.createDoc(contact.class.Employee, contact.space.Employee, {
name,
city: ''
})
const employee = await createEmployee(ops, name)
await ops.createDoc(contact.class.EmployeeAccount, core.space.Model, {
email: account.email,
@ -486,13 +491,15 @@ async function createEmployeeAccount (account: Account, workspace: string): Prom
const employee = await ops.findOne(contact.class.Employee, { _id: existingAccount.employee })
if (employee === undefined) {
// Employee was deleted, let's restore it.
const employeeId = await ops.createDoc(contact.class.Employee, contact.space.Employee, {
name,
city: ''
})
const employeeId = await createEmployee(ops, name)
await ops.updateDoc(contact.class.EmployeeAccount, existingAccount.space, existingAccount._id, {
employee: employeeId
})
} else if (!employee.active) {
await ops.update(employee, {
active: true
})
}
}
} finally {
@ -599,12 +606,81 @@ export async function dropAccount (db: Db, email: string): Promise<void> {
if (account === null) {
throw new PlatformError(new Status(Severity.ERROR, accountPlugin.status.AccountNotFound, { account: email }))
}
const workspaces = await db
.collection<Workspace>(WORKSPACE_COLLECTION)
.find({ _id: { $in: account.workspaces } })
.toArray()
await Promise.all(
workspaces.map(async (ws) => {
return await deactivateEmployeeAccount(account, ws.workspace)
})
)
await db.collection(ACCOUNT_COLLECTION).deleteOne({ _id: account._id })
await db
.collection<Workspace>(WORKSPACE_COLLECTION)
.updateMany({ _id: { $in: account.workspaces } }, { $pull: { accounts: account._id } })
}
/**
* @public
*/
export async function leaveWorkspace (db: Db, token: string, email: string): Promise<void> {
const tokenData = decodeToken(token)
const account = await getAccount(db, email)
if (account === null) {
throw new PlatformError(new Status(Severity.ERROR, accountPlugin.status.AccountNotFound, { account: email }))
}
if (tokenData.email !== email) {
const currentAccount = await getAccount(db, tokenData.email)
if (currentAccount === null) {
throw new PlatformError(
new Status(Severity.ERROR, accountPlugin.status.AccountNotFound, { account: tokenData.email })
)
}
}
const workspace = await getWorkspace(db, tokenData.workspace)
if (workspace === null) {
throw new PlatformError(
new Status(Severity.ERROR, accountPlugin.status.WorkspaceNotFound, { workspace: tokenData.workspace })
)
}
await deactivateEmployeeAccount(account, workspace.workspace)
await db
.collection<Workspace>(WORKSPACE_COLLECTION)
.updateOne({ _id: workspace._id }, { $pull: { accounts: account._id } })
await db
.collection<Account>(ACCOUNT_COLLECTION)
.updateOne({ _id: account._id }, { $pull: { workspaces: workspace._id } })
}
async function deactivateEmployeeAccount (account: Account, workspace: string): Promise<void> {
const connection = await connect(getTransactor(), workspace, account.email)
try {
const ops = new TxOperations(connection, core.account.System)
const existingAccount = await ops.findOne(contact.class.EmployeeAccount, { email: account.email })
if (existingAccount !== undefined) {
const employee = await ops.findOne(contact.class.Employee, { _id: existingAccount.employee })
if (employee !== undefined) {
await ops.update(employee, {
active: false
})
}
}
} finally {
await connection.close()
}
}
function wrap (f: (db: Db, ...args: any[]) => Promise<any>) {
return async function (db: Db, request: Request<any[]>, token?: string): Promise<Response<any>> {
if (token !== undefined) request.params.unshift(token)
@ -634,6 +710,7 @@ export const methods = {
createWorkspace: wrap(createUserWorkspace),
assignWorkspace: wrap(assignWorkspace),
removeWorkspace: wrap(removeWorkspace),
leaveWorkspace: wrap(leaveWorkspace),
listWorkspaces: wrap(listWorkspaces),
changeName: wrap(changeName),
changePassword: wrap(changePassword)