mirror of
https://github.com/hcengineering/platform.git
synced 2025-05-12 02:11:57 +00:00
Process roles (#8654)
Signed-off-by: Denis Bykhov <bykhov.denis@gmail.com>
This commit is contained in:
parent
c07db83d1b
commit
470c629f9f
@ -19,7 +19,8 @@ import {
|
|||||||
type Card,
|
type Card,
|
||||||
type MasterTag,
|
type MasterTag,
|
||||||
type ParentInfo,
|
type ParentInfo,
|
||||||
type Tag
|
type Tag,
|
||||||
|
type Role
|
||||||
} from '@hcengineering/card'
|
} from '@hcengineering/card'
|
||||||
import chunter from '@hcengineering/chunter'
|
import chunter from '@hcengineering/chunter'
|
||||||
import core, {
|
import core, {
|
||||||
@ -47,7 +48,7 @@ import {
|
|||||||
type Builder
|
type Builder
|
||||||
} from '@hcengineering/model'
|
} from '@hcengineering/model'
|
||||||
import attachment from '@hcengineering/model-attachment'
|
import attachment from '@hcengineering/model-attachment'
|
||||||
import { TClass, TDoc, TMixin, TSpace } from '@hcengineering/model-core'
|
import { TAttachedDoc, TClass, TDoc, TMixin, TSpace } from '@hcengineering/model-core'
|
||||||
import presentation from '@hcengineering/model-presentation'
|
import presentation from '@hcengineering/model-presentation'
|
||||||
import setting from '@hcengineering/model-setting'
|
import setting from '@hcengineering/model-setting'
|
||||||
import view, { createAction } from '@hcengineering/model-view'
|
import view, { createAction } from '@hcengineering/model-view'
|
||||||
@ -115,6 +116,13 @@ export class MasterTagEditorSection extends TDoc implements MasterTagEditorSecti
|
|||||||
component!: AnyComponent
|
component!: AnyComponent
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Model(card.class.Role, core.class.AttachedDoc, DOMAIN_MODEL)
|
||||||
|
export class TRole extends TAttachedDoc implements Role {
|
||||||
|
name!: string
|
||||||
|
declare attachedTo: Ref<MasterTag | Tag>
|
||||||
|
declare collection: 'roles'
|
||||||
|
}
|
||||||
|
|
||||||
export * from './migration'
|
export * from './migration'
|
||||||
|
|
||||||
const listConfig: (BuildModelKey | string)[] = [
|
const listConfig: (BuildModelKey | string)[] = [
|
||||||
@ -204,7 +212,7 @@ export function createSystemType (
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function createModel (builder: Builder): void {
|
export function createModel (builder: Builder): void {
|
||||||
builder.createModel(TMasterTag, TTag, TCard, MasterTagEditorSection, TCardSpace)
|
builder.createModel(TMasterTag, TTag, TCard, MasterTagEditorSection, TCardSpace, TRole)
|
||||||
|
|
||||||
createSystemType(builder, card.types.File, card.icon.File, attachment.string.File, attachment.string.Files)
|
createSystemType(builder, card.types.File, card.icon.File, attachment.string.File, attachment.string.Files)
|
||||||
createSystemType(builder, card.types.Document, card.icon.Document, card.string.Document, card.string.Documents)
|
createSystemType(builder, card.types.Document, card.icon.Document, card.string.Document, card.string.Documents)
|
||||||
@ -681,6 +689,12 @@ export function createModel (builder: Builder): void {
|
|||||||
component: card.component.RelationsSection
|
component: card.component.RelationsSection
|
||||||
})
|
})
|
||||||
|
|
||||||
|
builder.createDoc(card.class.MasterTagEditorSection, core.space.Model, {
|
||||||
|
id: 'roles',
|
||||||
|
label: core.string.Roles,
|
||||||
|
component: card.component.RolesSection
|
||||||
|
})
|
||||||
|
|
||||||
builder.createDoc(card.class.MasterTagEditorSection, core.space.Model, {
|
builder.createDoc(card.class.MasterTagEditorSection, core.space.Model, {
|
||||||
id: 'views',
|
id: 'views',
|
||||||
label: card.string.Views,
|
label: card.string.Views,
|
||||||
|
@ -15,11 +15,12 @@
|
|||||||
//
|
//
|
||||||
|
|
||||||
import activity from '@hcengineering/activity'
|
import activity from '@hcengineering/activity'
|
||||||
|
import card, { type Role, type Card } from '@hcengineering/card'
|
||||||
import {
|
import {
|
||||||
AvatarType,
|
AvatarType,
|
||||||
|
type UserRole,
|
||||||
contactId,
|
contactId,
|
||||||
type AvatarProvider,
|
type AvatarProvider,
|
||||||
type SocialIdentity,
|
|
||||||
type Channel,
|
type Channel,
|
||||||
type ChannelProvider,
|
type ChannelProvider,
|
||||||
type Contact,
|
type Contact,
|
||||||
@ -29,26 +30,27 @@ import {
|
|||||||
type Member,
|
type Member,
|
||||||
type Organization,
|
type Organization,
|
||||||
type Person,
|
type Person,
|
||||||
type Status,
|
type PersonSpace,
|
||||||
type PersonSpace
|
type SocialIdentity,
|
||||||
|
type Status
|
||||||
} from '@hcengineering/contact'
|
} from '@hcengineering/contact'
|
||||||
import {
|
import {
|
||||||
AccountRole,
|
AccountRole,
|
||||||
|
ClassifierKind,
|
||||||
DOMAIN_MODEL,
|
DOMAIN_MODEL,
|
||||||
DateRangeMode,
|
DateRangeMode,
|
||||||
IndexKind,
|
IndexKind,
|
||||||
type Collection,
|
type AccountUuid,
|
||||||
type Blob,
|
type Blob,
|
||||||
type Class,
|
type Class,
|
||||||
type MarkupBlobRef,
|
type Collection,
|
||||||
type Domain,
|
type Domain,
|
||||||
type Ref,
|
type MarkupBlobRef,
|
||||||
type Timestamp,
|
|
||||||
type SocialIdType,
|
|
||||||
type PersonUuid,
|
|
||||||
type PersonId,
|
type PersonId,
|
||||||
type AccountUuid,
|
type PersonUuid,
|
||||||
ClassifierKind
|
type Ref,
|
||||||
|
type SocialIdType,
|
||||||
|
type Timestamp
|
||||||
} from '@hcengineering/core'
|
} from '@hcengineering/core'
|
||||||
import {
|
import {
|
||||||
Collection as CollectionType,
|
Collection as CollectionType,
|
||||||
@ -84,7 +86,6 @@ import setting from '@hcengineering/setting'
|
|||||||
import templates from '@hcengineering/templates'
|
import templates from '@hcengineering/templates'
|
||||||
import { type AnyComponent } from '@hcengineering/ui/src/types'
|
import { type AnyComponent } from '@hcengineering/ui/src/types'
|
||||||
import { type Action } from '@hcengineering/view'
|
import { type Action } from '@hcengineering/view'
|
||||||
import card, { type Card } from '@hcengineering/card'
|
|
||||||
import contact from './plugin'
|
import contact from './plugin'
|
||||||
|
|
||||||
export { contactId } from '@hcengineering/contact'
|
export { contactId } from '@hcengineering/contact'
|
||||||
@ -92,6 +93,7 @@ export { contactOperation } from './migration'
|
|||||||
export { contact as default }
|
export { contact as default }
|
||||||
|
|
||||||
export const DOMAIN_CONTACT = 'contact' as Domain
|
export const DOMAIN_CONTACT = 'contact' as Domain
|
||||||
|
export const DOMAIN_ROLE = 'role' as Domain
|
||||||
export const DOMAIN_CHANNEL = 'channel' as Domain
|
export const DOMAIN_CHANNEL = 'channel' as Domain
|
||||||
|
|
||||||
@Model(contact.class.AvatarProvider, core.class.Doc, DOMAIN_MODEL)
|
@Model(contact.class.AvatarProvider, core.class.Doc, DOMAIN_MODEL)
|
||||||
@ -272,6 +274,12 @@ export class TPersonSpace extends TSpace implements PersonSpace {
|
|||||||
person!: Ref<Person>
|
person!: Ref<Person>
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Model(contact.class.UserRole, core.class.Doc, DOMAIN_ROLE)
|
||||||
|
export class TUserRole extends TDoc implements UserRole {
|
||||||
|
user!: Ref<Employee>
|
||||||
|
role!: Ref<Role>
|
||||||
|
}
|
||||||
|
|
||||||
function createUserProfileTag (builder: Builder): void {
|
function createUserProfileTag (builder: Builder): void {
|
||||||
builder.createDoc(
|
builder.createDoc(
|
||||||
card.class.MasterTag,
|
card.class.MasterTag,
|
||||||
@ -334,7 +342,8 @@ export function createModel (builder: Builder): void {
|
|||||||
TStatus,
|
TStatus,
|
||||||
TMember,
|
TMember,
|
||||||
TContactsTab,
|
TContactsTab,
|
||||||
TPersonSpace
|
TPersonSpace,
|
||||||
|
TUserRole
|
||||||
)
|
)
|
||||||
|
|
||||||
builder.mixin(contact.class.Contact, core.class.Class, activity.mixin.ActivityDoc, {})
|
builder.mixin(contact.class.Contact, core.class.Class, activity.mixin.ActivityDoc, {})
|
||||||
|
@ -164,6 +164,7 @@ export class TProcessFunction extends TDoc implements ProcessFunction {
|
|||||||
label!: IntlString
|
label!: IntlString
|
||||||
editor?: AnyComponent
|
editor?: AnyComponent
|
||||||
allowMany?: boolean
|
allowMany?: boolean
|
||||||
|
type!: 'transform' | 'reduce' | 'context'
|
||||||
}
|
}
|
||||||
|
|
||||||
export * from './migration'
|
export * from './migration'
|
||||||
@ -239,7 +240,8 @@ export function createModel (builder: Builder): void {
|
|||||||
{
|
{
|
||||||
of: core.class.TypeString,
|
of: core.class.TypeString,
|
||||||
category: 'attribute',
|
category: 'attribute',
|
||||||
label: process.string.UpperCase
|
label: process.string.UpperCase,
|
||||||
|
type: 'transform'
|
||||||
},
|
},
|
||||||
process.function.UpperCase
|
process.function.UpperCase
|
||||||
)
|
)
|
||||||
@ -250,7 +252,8 @@ export function createModel (builder: Builder): void {
|
|||||||
{
|
{
|
||||||
of: core.class.TypeString,
|
of: core.class.TypeString,
|
||||||
category: 'attribute',
|
category: 'attribute',
|
||||||
label: process.string.LowerCase
|
label: process.string.LowerCase,
|
||||||
|
type: 'transform'
|
||||||
},
|
},
|
||||||
process.function.LowerCase
|
process.function.LowerCase
|
||||||
)
|
)
|
||||||
@ -261,7 +264,8 @@ export function createModel (builder: Builder): void {
|
|||||||
{
|
{
|
||||||
of: core.class.TypeString,
|
of: core.class.TypeString,
|
||||||
category: 'attribute',
|
category: 'attribute',
|
||||||
label: process.string.Trim
|
label: process.string.Trim,
|
||||||
|
type: 'transform'
|
||||||
},
|
},
|
||||||
process.function.Trim
|
process.function.Trim
|
||||||
)
|
)
|
||||||
@ -272,7 +276,8 @@ export function createModel (builder: Builder): void {
|
|||||||
{
|
{
|
||||||
of: core.class.ArrOf,
|
of: core.class.ArrOf,
|
||||||
category: undefined,
|
category: undefined,
|
||||||
label: process.string.FirstValue
|
label: process.string.FirstValue,
|
||||||
|
type: 'reduce'
|
||||||
},
|
},
|
||||||
process.function.FirstValue
|
process.function.FirstValue
|
||||||
)
|
)
|
||||||
@ -282,6 +287,7 @@ export function createModel (builder: Builder): void {
|
|||||||
core.space.Model,
|
core.space.Model,
|
||||||
{
|
{
|
||||||
of: core.class.ArrOf,
|
of: core.class.ArrOf,
|
||||||
|
type: 'reduce',
|
||||||
category: undefined,
|
category: undefined,
|
||||||
label: process.string.LastValue
|
label: process.string.LastValue
|
||||||
},
|
},
|
||||||
@ -293,6 +299,7 @@ export function createModel (builder: Builder): void {
|
|||||||
core.space.Model,
|
core.space.Model,
|
||||||
{
|
{
|
||||||
of: core.class.ArrOf,
|
of: core.class.ArrOf,
|
||||||
|
type: 'reduce',
|
||||||
category: undefined,
|
category: undefined,
|
||||||
label: process.string.Random
|
label: process.string.Random
|
||||||
},
|
},
|
||||||
@ -304,6 +311,7 @@ export function createModel (builder: Builder): void {
|
|||||||
core.space.Model,
|
core.space.Model,
|
||||||
{
|
{
|
||||||
of: core.class.TypeNumber,
|
of: core.class.TypeNumber,
|
||||||
|
type: 'transform',
|
||||||
category: 'attribute',
|
category: 'attribute',
|
||||||
label: process.string.Add,
|
label: process.string.Add,
|
||||||
allowMany: true,
|
allowMany: true,
|
||||||
@ -320,7 +328,8 @@ export function createModel (builder: Builder): void {
|
|||||||
category: 'attribute',
|
category: 'attribute',
|
||||||
label: process.string.Subtract,
|
label: process.string.Subtract,
|
||||||
allowMany: true,
|
allowMany: true,
|
||||||
editor: process.component.NumberOffsetEditor
|
editor: process.component.NumberOffsetEditor,
|
||||||
|
type: 'transform'
|
||||||
},
|
},
|
||||||
process.function.Subtract
|
process.function.Subtract
|
||||||
)
|
)
|
||||||
@ -332,7 +341,8 @@ export function createModel (builder: Builder): void {
|
|||||||
of: core.class.TypeDate,
|
of: core.class.TypeDate,
|
||||||
category: 'attribute',
|
category: 'attribute',
|
||||||
label: process.string.Offset,
|
label: process.string.Offset,
|
||||||
editor: process.component.DateOffsetEditor
|
editor: process.component.DateOffsetEditor,
|
||||||
|
type: 'transform'
|
||||||
},
|
},
|
||||||
process.function.Offset
|
process.function.Offset
|
||||||
)
|
)
|
||||||
@ -343,11 +353,25 @@ export function createModel (builder: Builder): void {
|
|||||||
{
|
{
|
||||||
of: core.class.TypeDate,
|
of: core.class.TypeDate,
|
||||||
category: 'attribute',
|
category: 'attribute',
|
||||||
label: process.string.FirstWorkingDayAfter
|
label: process.string.FirstWorkingDayAfter,
|
||||||
|
type: 'transform'
|
||||||
},
|
},
|
||||||
process.function.FirstWorkingDayAfter
|
process.function.FirstWorkingDayAfter
|
||||||
)
|
)
|
||||||
|
|
||||||
|
builder.createDoc(
|
||||||
|
process.class.ProcessFunction,
|
||||||
|
core.space.Model,
|
||||||
|
{
|
||||||
|
of: contact.mixin.Employee,
|
||||||
|
editor: process.component.RoleEditor,
|
||||||
|
category: 'array',
|
||||||
|
label: core.string.Role,
|
||||||
|
type: 'context'
|
||||||
|
},
|
||||||
|
process.function.RoleContext
|
||||||
|
)
|
||||||
|
|
||||||
builder.mixin(process.class.Process, core.class.Class, view.mixin.AttributePresenter, {
|
builder.mixin(process.class.Process, core.class.Class, view.mixin.AttributePresenter, {
|
||||||
presenter: process.component.ProcessPresenter
|
presenter: process.component.ProcessPresenter
|
||||||
})
|
})
|
||||||
|
@ -95,6 +95,10 @@ export function createModel (builder: Builder): void {
|
|||||||
func: serverProcess.transform.FirstWorkingDayAfter
|
func: serverProcess.transform.FirstWorkingDayAfter
|
||||||
})
|
})
|
||||||
|
|
||||||
|
builder.mixin(process.function.RoleContext, process.class.ProcessFunction, serverProcess.mixin.FuncImpl, {
|
||||||
|
func: serverProcess.transform.RoleContext
|
||||||
|
})
|
||||||
|
|
||||||
builder.createDoc(serverCore.class.Trigger, core.space.Model, {
|
builder.createDoc(serverCore.class.Trigger, core.space.Model, {
|
||||||
trigger: serverProcess.trigger.OnExecutionContinue,
|
trigger: serverProcess.trigger.OnExecutionContinue,
|
||||||
txMatch: {
|
txMatch: {
|
||||||
|
@ -0,0 +1,47 @@
|
|||||||
|
<!--
|
||||||
|
// Copyright © 2025 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 { MasterTag, Tag } from '@hcengineering/card'
|
||||||
|
import core from '@hcengineering/core'
|
||||||
|
import { Card, getClient } from '@hcengineering/presentation'
|
||||||
|
import { EditBox } from '@hcengineering/ui'
|
||||||
|
import { createEventDispatcher } from 'svelte'
|
||||||
|
import card from '../../plugin'
|
||||||
|
|
||||||
|
export let masterTag: MasterTag | Tag
|
||||||
|
|
||||||
|
let value: string = ''
|
||||||
|
|
||||||
|
const dispatch = createEventDispatcher()
|
||||||
|
const client = getClient()
|
||||||
|
|
||||||
|
async function handleSave (): Promise<void> {
|
||||||
|
const _id = await client.addCollection(
|
||||||
|
card.class.Role,
|
||||||
|
core.space.Model,
|
||||||
|
masterTag._id,
|
||||||
|
masterTag._class,
|
||||||
|
'roles',
|
||||||
|
{
|
||||||
|
name: value
|
||||||
|
}
|
||||||
|
)
|
||||||
|
dispatch('close', _id)
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<Card okAction={handleSave} label={core.string.Role} on:close width={'menu'} canSave={value.trim().length > 0}>
|
||||||
|
<EditBox bind:value placeholder={core.string.Role} />
|
||||||
|
</Card>
|
186
plugins/card-resources/src/components/settings/EditRole.svelte
Normal file
186
plugins/card-resources/src/components/settings/EditRole.svelte
Normal file
@ -0,0 +1,186 @@
|
|||||||
|
<!--
|
||||||
|
// Copyright © 2025 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 card, { Role } from '@hcengineering/card'
|
||||||
|
import contact, { Employee, UserRole } from '@hcengineering/contact'
|
||||||
|
import { SelectUsersPopup, UserDetails } from '@hcengineering/contact-resources'
|
||||||
|
import core, { Ref, WithLookup } from '@hcengineering/core'
|
||||||
|
import presentation, { createQuery, getClient } from '@hcengineering/presentation'
|
||||||
|
import { clearSettingsStore } from '@hcengineering/setting-resources'
|
||||||
|
import { ButtonIcon, Icon, IconAdd, IconDelete, Modal, ModernButton, Scroller, showPopup } from '@hcengineering/ui'
|
||||||
|
|
||||||
|
export let _id: Ref<Role>
|
||||||
|
|
||||||
|
const client = getClient()
|
||||||
|
|
||||||
|
const query = createQuery()
|
||||||
|
let role: Role | undefined
|
||||||
|
query.query(card.class.Role, { _id }, (res) => {
|
||||||
|
role = res[0]
|
||||||
|
})
|
||||||
|
$: name = role?.name
|
||||||
|
|
||||||
|
const usersQ = createQuery()
|
||||||
|
|
||||||
|
let roles: WithLookup<UserRole>[] = []
|
||||||
|
|
||||||
|
usersQ.query(
|
||||||
|
contact.class.UserRole,
|
||||||
|
{ role: _id },
|
||||||
|
(res) => {
|
||||||
|
roles = res
|
||||||
|
},
|
||||||
|
{
|
||||||
|
lookup: {
|
||||||
|
user: contact.class.Person
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
let users: Employee[] = []
|
||||||
|
$: users = roles.map((role) => role.$lookup?.user).filter((p) => p !== undefined)
|
||||||
|
|
||||||
|
async function remove (): Promise<void> {
|
||||||
|
if (role === undefined) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
await client.remove(role)
|
||||||
|
clearSettingsStore()
|
||||||
|
}
|
||||||
|
|
||||||
|
async function removeAssignment (val: Ref<Employee>): Promise<void> {
|
||||||
|
const toRemove = roles.filter((role) => role.user === val)
|
||||||
|
for (const obj of toRemove) {
|
||||||
|
await client.remove(obj)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function openSelectUsersPopup (): void {
|
||||||
|
const selected = users.map((user) => user._id)
|
||||||
|
showPopup(
|
||||||
|
SelectUsersPopup,
|
||||||
|
{
|
||||||
|
okLabel: presentation.string.Add,
|
||||||
|
disableDeselectFor: selected,
|
||||||
|
skipInactive: true,
|
||||||
|
selected,
|
||||||
|
showStatus: true
|
||||||
|
},
|
||||||
|
'top',
|
||||||
|
(result?: Ref<Employee>[]) => {
|
||||||
|
if (result != null) {
|
||||||
|
void changeUsers(result)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
async function changeUsers (persons: Ref<Employee>[]): Promise<void> {
|
||||||
|
const current = new Set(users.map((user) => user._id))
|
||||||
|
const newPersons = persons.filter((person) => !current.has(person))
|
||||||
|
if (newPersons.length === 0) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
const apply = client.apply()
|
||||||
|
for (const person of newPersons) {
|
||||||
|
await apply.createDoc(contact.class.UserRole, contact.space.Contacts, { role: _id, user: person })
|
||||||
|
}
|
||||||
|
await apply.commit()
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<Modal
|
||||||
|
label={core.string.Role}
|
||||||
|
type={'type-aside'}
|
||||||
|
showCancelButton={false}
|
||||||
|
canSave={true}
|
||||||
|
okLabel={presentation.string.Close}
|
||||||
|
okAction={clearSettingsStore}
|
||||||
|
>
|
||||||
|
<svelte:fragment slot="actions">
|
||||||
|
<ButtonIcon icon={IconDelete} size={'small'} kind={'tertiary'} on:click={remove} />
|
||||||
|
</svelte:fragment>
|
||||||
|
<div class="hulyModal-content__titleGroup">
|
||||||
|
<div class="flex fs-title items-center flex-gap-2">
|
||||||
|
<Icon icon={contact.icon.Person} size={'medium'} />
|
||||||
|
<div>
|
||||||
|
{name}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="hulyModal-content__settingsSet">
|
||||||
|
<div class="item" style:padding="var(--spacing-1_5)" class:withoutBorder={users.length === 0}>
|
||||||
|
<ModernButton
|
||||||
|
label={presentation.string.Add}
|
||||||
|
icon={IconAdd}
|
||||||
|
iconSize="small"
|
||||||
|
kind="secondary"
|
||||||
|
size="small"
|
||||||
|
on:click={openSelectUsersPopup}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
{#each users as user, index (user._id)}
|
||||||
|
<div class="item" class:withoutBorder={index === users.length - 1}>
|
||||||
|
<div class="item__content">
|
||||||
|
<UserDetails person={user} showStatus />
|
||||||
|
<div class="item__action">
|
||||||
|
<ButtonIcon
|
||||||
|
icon={IconDelete}
|
||||||
|
size="small"
|
||||||
|
on:click={async () => {
|
||||||
|
await removeAssignment(user._id)
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{/each}
|
||||||
|
</div>
|
||||||
|
</Modal>
|
||||||
|
|
||||||
|
<style lang="scss">
|
||||||
|
.item {
|
||||||
|
padding: var(--spacing-0_75);
|
||||||
|
|
||||||
|
&.withoutBorder {
|
||||||
|
border: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.item__content {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
padding: var(--spacing-0_75);
|
||||||
|
border-radius: var(--small-BorderRadius);
|
||||||
|
cursor: pointer;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
background: var(--global-ui-highlight-BackgroundColor);
|
||||||
|
|
||||||
|
.item__action {
|
||||||
|
visibility: visible;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.item__action {
|
||||||
|
visibility: hidden;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
visibility: visible;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
@ -0,0 +1,72 @@
|
|||||||
|
<!--
|
||||||
|
// Copyright © 2025 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 { MasterTag, Role, Tag } from '@hcengineering/card'
|
||||||
|
import contact from '@hcengineering/contact'
|
||||||
|
import core from '@hcengineering/core'
|
||||||
|
import { createQuery, getClient } from '@hcengineering/presentation'
|
||||||
|
import { clearSettingsStore, settingsStore } from '@hcengineering/setting-resources'
|
||||||
|
import { ButtonIcon, Icon, IconAdd, Label, showPopup } from '@hcengineering/ui'
|
||||||
|
import { onDestroy } from 'svelte'
|
||||||
|
import card from '../../plugin'
|
||||||
|
import CreateRolePopup from './CreateRolePopup.svelte'
|
||||||
|
|
||||||
|
export let masterTag: MasterTag | Tag
|
||||||
|
|
||||||
|
const client = getClient()
|
||||||
|
|
||||||
|
const ancestors = client.getHierarchy().getAncestors(masterTag._id)
|
||||||
|
|
||||||
|
let roles = client.getModel().findAllSync(card.class.Role, { attachedTo: { $in: ancestors } })
|
||||||
|
const query = createQuery()
|
||||||
|
query.query(card.class.Role, { attachedTo: { $in: ancestors } }, (res) => {
|
||||||
|
roles = res
|
||||||
|
})
|
||||||
|
|
||||||
|
function addRole (): void {
|
||||||
|
showPopup(CreateRolePopup, { masterTag }, undefined, (res) => {
|
||||||
|
if (res != null) {
|
||||||
|
$settingsStore = { id: res, component: card.component.EditRole, props: { _id: res } }
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleSelect = (role: Role): void => {
|
||||||
|
$settingsStore = { id: role._id, component: card.component.EditRole, props: { _id: role._id } }
|
||||||
|
}
|
||||||
|
onDestroy(() => {
|
||||||
|
clearSettingsStore()
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<div class="hulyTableAttr-header font-medium-12">
|
||||||
|
<Icon icon={contact.icon.User} size="small" />
|
||||||
|
<span><Label label={core.string.Roles} /></span>
|
||||||
|
<ButtonIcon kind="primary" icon={IconAdd} size="small" dataId={'btnAdd'} on:click={addRole} />
|
||||||
|
</div>
|
||||||
|
<div class="hulyTableAttr-content task">
|
||||||
|
{#each roles as role}
|
||||||
|
<button
|
||||||
|
class="hulyTableAttr-content__row justify-start"
|
||||||
|
on:click|stopPropagation={() => {
|
||||||
|
handleSelect(role)
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<div class="hulyTableAttr-content__row-label font-medium-14 cursor-pointer">
|
||||||
|
{role.name}
|
||||||
|
</div>
|
||||||
|
</button>
|
||||||
|
{/each}
|
||||||
|
</div>
|
@ -47,6 +47,8 @@ import CardArrayEditor from './components/CardArrayEditor.svelte'
|
|||||||
import NewCardHeader from './components/navigator/NewCardHeader.svelte'
|
import NewCardHeader from './components/navigator/NewCardHeader.svelte'
|
||||||
import SpacePresenter from './components/navigator/SpacePresenter.svelte'
|
import SpacePresenter from './components/navigator/SpacePresenter.svelte'
|
||||||
import LabelsPresenter from './components/LabelsPresenter.svelte'
|
import LabelsPresenter from './components/LabelsPresenter.svelte'
|
||||||
|
import RolesSection from './components/settings/RolesSection.svelte'
|
||||||
|
import EditRole from './components/settings/EditRole.svelte'
|
||||||
|
|
||||||
export { default as CardSelector } from './components/CardSelector.svelte'
|
export { default as CardSelector } from './components/CardSelector.svelte'
|
||||||
|
|
||||||
@ -76,7 +78,9 @@ export default async (): Promise<Resources> => ({
|
|||||||
CardArrayEditor,
|
CardArrayEditor,
|
||||||
NewCardHeader,
|
NewCardHeader,
|
||||||
SpacePresenter,
|
SpacePresenter,
|
||||||
LabelsPresenter
|
LabelsPresenter,
|
||||||
|
RolesSection,
|
||||||
|
EditRole
|
||||||
},
|
},
|
||||||
completion: {
|
completion: {
|
||||||
CardQuery: queryCard
|
CardQuery: queryCard
|
||||||
|
@ -46,7 +46,9 @@ export default mergeIds(cardId, card, {
|
|||||||
CardArrayEditor: '' as AnyComponent,
|
CardArrayEditor: '' as AnyComponent,
|
||||||
NewCardHeader: '' as AnyComponent,
|
NewCardHeader: '' as AnyComponent,
|
||||||
SpacePresenter: '' as AnyComponent,
|
SpacePresenter: '' as AnyComponent,
|
||||||
LabelsPresenter: '' as AnyComponent
|
LabelsPresenter: '' as AnyComponent,
|
||||||
|
RolesSection: '' as AnyComponent,
|
||||||
|
EditRole: '' as AnyComponent
|
||||||
},
|
},
|
||||||
completion: {
|
completion: {
|
||||||
CardQuery: '' as Resource<ObjectSearchFactory>,
|
CardQuery: '' as Resource<ObjectSearchFactory>,
|
||||||
|
@ -11,7 +11,19 @@
|
|||||||
// See the License for the specific language governing permissions and
|
// See the License for the specific language governing permissions and
|
||||||
// limitations under the License.
|
// limitations under the License.
|
||||||
|
|
||||||
import { Blobs, Class, Doc, Domain, MarkupBlobRef, Mixin, Rank, Ref, Space } from '@hcengineering/core'
|
import {
|
||||||
|
AttachedDoc,
|
||||||
|
Blobs,
|
||||||
|
Class,
|
||||||
|
CollectionSize,
|
||||||
|
Doc,
|
||||||
|
Domain,
|
||||||
|
MarkupBlobRef,
|
||||||
|
Mixin,
|
||||||
|
Rank,
|
||||||
|
Ref,
|
||||||
|
Space
|
||||||
|
} from '@hcengineering/core'
|
||||||
import { Asset, IntlString, plugin, Plugin } from '@hcengineering/platform'
|
import { Asset, IntlString, plugin, Plugin } from '@hcengineering/platform'
|
||||||
import type { AnyComponent, ComponentExtensionId } from '@hcengineering/ui'
|
import type { AnyComponent, ComponentExtensionId } from '@hcengineering/ui'
|
||||||
|
|
||||||
@ -20,10 +32,16 @@ export * from './analytics'
|
|||||||
export interface MasterTag extends Class<Card> {
|
export interface MasterTag extends Class<Card> {
|
||||||
color?: number
|
color?: number
|
||||||
removed?: boolean
|
removed?: boolean
|
||||||
|
roles?: CollectionSize<Role>
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface Tag extends MasterTag, Mixin<Card> {}
|
export interface Tag extends MasterTag, Mixin<Card> {}
|
||||||
|
|
||||||
|
export interface Role extends AttachedDoc<MasterTag | Tag, 'roles'> {
|
||||||
|
name: string
|
||||||
|
attachedTo: Ref<MasterTag | Tag>
|
||||||
|
}
|
||||||
|
|
||||||
export interface Card extends Doc {
|
export interface Card extends Doc {
|
||||||
_class: Ref<MasterTag>
|
_class: Ref<MasterTag>
|
||||||
title: string
|
title: string
|
||||||
@ -69,7 +87,8 @@ const cardPlugin = plugin(cardId, {
|
|||||||
MasterTag: '' as Ref<Class<MasterTag>>,
|
MasterTag: '' as Ref<Class<MasterTag>>,
|
||||||
Tag: '' as Ref<Class<Tag>>,
|
Tag: '' as Ref<Class<Tag>>,
|
||||||
MasterTagEditorSection: '' as Ref<Class<MasterTagEditorSection>>,
|
MasterTagEditorSection: '' as Ref<Class<MasterTagEditorSection>>,
|
||||||
CardSpace: '' as Ref<Class<CardSpace>>
|
CardSpace: '' as Ref<Class<CardSpace>>,
|
||||||
|
Role: '' as Ref<Class<Role>>
|
||||||
},
|
},
|
||||||
space: {
|
space: {
|
||||||
Default: '' as Ref<CardSpace>
|
Default: '' as Ref<CardSpace>
|
||||||
|
@ -38,7 +38,7 @@ import { TemplateField, TemplateFieldCategory } from '@hcengineering/templates'
|
|||||||
import type { AnyComponent, ColorDefinition, ResolvedLocation, Location, ComponentExtensionId } from '@hcengineering/ui'
|
import type { AnyComponent, ColorDefinition, ResolvedLocation, Location, ComponentExtensionId } from '@hcengineering/ui'
|
||||||
import { Action, FilterMode, Viewlet } from '@hcengineering/view'
|
import { Action, FilterMode, Viewlet } from '@hcengineering/view'
|
||||||
import type { Readable } from 'svelte/store'
|
import type { Readable } from 'svelte/store'
|
||||||
import { Card, MasterTag } from '@hcengineering/card'
|
import { Card, MasterTag, Role } from '@hcengineering/card'
|
||||||
import { PermissionsStore } from './types'
|
import { PermissionsStore } from './types'
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -144,6 +144,11 @@ export interface Person extends Contact, BasePerson {
|
|||||||
profile?: Ref<Card>
|
profile?: Ref<Card>
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface UserRole extends Doc {
|
||||||
|
user: Ref<Employee>
|
||||||
|
role: Ref<Role>
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @public
|
* @public
|
||||||
*/
|
*/
|
||||||
@ -213,7 +218,8 @@ export const contactPlugin = plugin(contactId, {
|
|||||||
ContactsTab: '' as Ref<Class<ContactsTab>>,
|
ContactsTab: '' as Ref<Class<ContactsTab>>,
|
||||||
PersonSpace: '' as Ref<Class<PersonSpace>>,
|
PersonSpace: '' as Ref<Class<PersonSpace>>,
|
||||||
SocialIdentity: '' as Ref<Class<SocialIdentity>>,
|
SocialIdentity: '' as Ref<Class<SocialIdentity>>,
|
||||||
UserProfile: '' as Ref<MasterTag>
|
UserProfile: '' as Ref<MasterTag>,
|
||||||
|
UserRole: '' as Ref<Class<UserRole>>
|
||||||
},
|
},
|
||||||
mixin: {
|
mixin: {
|
||||||
Employee: '' as Ref<Class<Employee>>
|
Employee: '' as Ref<Class<Employee>>
|
||||||
|
@ -65,6 +65,7 @@
|
|||||||
"ObjectNotFound": "Objekt nenalezen: {_id}",
|
"ObjectNotFound": "Objekt nenalezen: {_id}",
|
||||||
"EmptyRelatedObjectValue": "Prázdná hodnota {attr} souvisejícího objektu: {parent}",
|
"EmptyRelatedObjectValue": "Prázdná hodnota {attr} souvisejícího objektu: {parent}",
|
||||||
"InternalServerError": "Vnitřní chyba serveru, kontaktujte podporu, chybové id: {errorId}",
|
"InternalServerError": "Vnitřní chyba serveru, kontaktujte podporu, chybové id: {errorId}",
|
||||||
"ResultNotProvided": "Výsledek nebyl poskytnut"
|
"ResultNotProvided": "Výsledek nebyl poskytnut",
|
||||||
|
"EmptyFunctionResult": "Prázdný výsledek funkce {func}"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -65,6 +65,7 @@
|
|||||||
"ObjectNotFound": "Objekt nicht gefunden: {_id}",
|
"ObjectNotFound": "Objekt nicht gefunden: {_id}",
|
||||||
"EmptyRelatedObjectValue": "Leerer verwandtes Objekt {parent} Wert: {attr}",
|
"EmptyRelatedObjectValue": "Leerer verwandtes Objekt {parent} Wert: {attr}",
|
||||||
"InternalServerError": "Interner Serverfehler, bitte kontaktieren Sie den Support, Fehler-ID: {errorId}",
|
"InternalServerError": "Interner Serverfehler, bitte kontaktieren Sie den Support, Fehler-ID: {errorId}",
|
||||||
"ResultNotProvided": "Ergebnis nicht bereitgestellt"
|
"ResultNotProvided": "Ergebnis nicht bereitgestellt",
|
||||||
|
"EmptyFunctionResult": "Leerer Funktionsresultat {func}"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -65,6 +65,7 @@
|
|||||||
"ObjectNotFound": "Object not found: {_id}",
|
"ObjectNotFound": "Object not found: {_id}",
|
||||||
"EmptyRelatedObjectValue": "Empty related object {parent} value: {attr}",
|
"EmptyRelatedObjectValue": "Empty related object {parent} value: {attr}",
|
||||||
"InternalServerError": "Internal server error, contact support, error id: {errorId}",
|
"InternalServerError": "Internal server error, contact support, error id: {errorId}",
|
||||||
"ResultNotProvided": "Result not provided"
|
"ResultNotProvided": "Result not provided",
|
||||||
|
"EmptyFunctionResult": "Empty function result {func}"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -65,6 +65,7 @@
|
|||||||
"ObjectNotFound": "Objeto no encontrado: {_id}",
|
"ObjectNotFound": "Objeto no encontrado: {_id}",
|
||||||
"EmptyRelatedObjectValue": "Valor de objeto relacionado vacío: {parent} {attr}",
|
"EmptyRelatedObjectValue": "Valor de objeto relacionado vacío: {parent} {attr}",
|
||||||
"InternalServerError": "Error interno del servidor, contacte con el soporte, id de error: {errorId}",
|
"InternalServerError": "Error interno del servidor, contacte con el soporte, id de error: {errorId}",
|
||||||
"ResultNotProvided": "Resultado no proporcionado"
|
"ResultNotProvided": "Resultado no proporcionado",
|
||||||
|
"EmptyFunctionResult": "Resultado de función vacío {func}"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -65,6 +65,7 @@
|
|||||||
"ObjectNotFound": "Objet introuvable : {_id}",
|
"ObjectNotFound": "Objet introuvable : {_id}",
|
||||||
"EmptyRelatedObjectValue": "Valeur d'objet lié vide : {parent} {attr}",
|
"EmptyRelatedObjectValue": "Valeur d'objet lié vide : {parent} {attr}",
|
||||||
"InternalServerError": "Erreur interne du serveur, contactez le support, id d'erreur : {errorId}",
|
"InternalServerError": "Erreur interne du serveur, contactez le support, id d'erreur : {errorId}",
|
||||||
"ResultNotProvided": "Résultat non fourni"
|
"ResultNotProvided": "Résultat non fourni",
|
||||||
|
"EmptyFunctionResult": "Résultat de fonction vide {func}"
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -65,6 +65,7 @@
|
|||||||
"ObjectNotFound": "Oggetto non trovato: {_id}",
|
"ObjectNotFound": "Oggetto non trovato: {_id}",
|
||||||
"EmptyRelatedObjectValue": "Valore oggetto correlato vuoto: {parent} {attr}",
|
"EmptyRelatedObjectValue": "Valore oggetto correlato vuoto: {parent} {attr}",
|
||||||
"InternalServerError": "Errore interno del server, contatta il supporto, id errore: {errorId}",
|
"InternalServerError": "Errore interno del server, contatta il supporto, id errore: {errorId}",
|
||||||
"ResultNotProvided": "Risultato non fornito"
|
"ResultNotProvided": "Risultato non fornito",
|
||||||
|
"EmptyFunctionResult": "Risultato funzione vuoto {func}"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -61,6 +61,7 @@
|
|||||||
"UserRequestedValueNotProvided": "ユーザーが要求した値が提供されていません: {attr}",
|
"UserRequestedValueNotProvided": "ユーザーが要求した値が提供されていません: {attr}",
|
||||||
"ObjectNotFound": "オブジェクトが見つかりません: {_id}",
|
"ObjectNotFound": "オブジェクトが見つかりません: {_id}",
|
||||||
"EmptyRelatedObjectValue": "関連オブジェクト {parent} の値が空です: {attr}",
|
"EmptyRelatedObjectValue": "関連オブジェクト {parent} の値が空です: {attr}",
|
||||||
"InternalServerError": "内部サーバーエラー。サポートにお問い合わせください。エラーID: {errorId}"
|
"InternalServerError": "内部サーバーエラー。サポートにお問い合わせください。エラーID: {errorId}",
|
||||||
|
"EmptyFunctionResult": "関数結果が空です {func}"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -65,6 +65,7 @@
|
|||||||
"ObjectNotFound": "Objeto não encontrado: {_id}",
|
"ObjectNotFound": "Objeto não encontrado: {_id}",
|
||||||
"EmptyRelatedObjectValue": "Valor de objeto relacionado vazio: {parent} {attr}",
|
"EmptyRelatedObjectValue": "Valor de objeto relacionado vazio: {parent} {attr}",
|
||||||
"InternalServerError": "Erro interno do servidor, contate o suporte, id de erro: {errorId}",
|
"InternalServerError": "Erro interno do servidor, contate o suporte, id de erro: {errorId}",
|
||||||
"ResultNotProvided": "Resultado não fornecido"
|
"ResultNotProvided": "Resultado não fornecido",
|
||||||
|
"EmptyFunctionResult": "Resultado da função vazio {func}"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -65,6 +65,7 @@
|
|||||||
"UserRequestedValueNotProvided": "Знвчение не предоставлено пользователем: {attr}",
|
"UserRequestedValueNotProvided": "Знвчение не предоставлено пользователем: {attr}",
|
||||||
"ObjectNotFound": "Объект не найден: {_id}",
|
"ObjectNotFound": "Объект не найден: {_id}",
|
||||||
"AttributeNotExists": "Атрибут не существует: {key}",
|
"AttributeNotExists": "Атрибут не существует: {key}",
|
||||||
"ResultNotProvided": "Результат не предоставлен"
|
"ResultNotProvided": "Результат не предоставлен",
|
||||||
|
"EmptyFunctionResult": "Отсутствуют результат функции {func}"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -65,6 +65,7 @@
|
|||||||
"ObjectNotFound": "找不到对象:{_id}",
|
"ObjectNotFound": "找不到对象:{_id}",
|
||||||
"EmptyRelatedObjectValue": "空相关对象值:{parent} {attr}",
|
"EmptyRelatedObjectValue": "空相关对象值:{parent} {attr}",
|
||||||
"InternalServerError": "内部服务器错误,请联系支持,错误 ID:{errorId}",
|
"InternalServerError": "内部服务器错误,请联系支持,错误 ID:{errorId}",
|
||||||
"ResultNotProvided": "未提供结果"
|
"ResultNotProvided": "未提供结果",
|
||||||
|
"EmptyFunctionResult": "空函数结果 {func}"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -29,7 +29,9 @@
|
|||||||
import { createEventDispatcher } from 'svelte'
|
import { createEventDispatcher } from 'svelte'
|
||||||
import ContextSelectorPopup from './attributeEditors/ContextSelectorPopup.svelte'
|
import ContextSelectorPopup from './attributeEditors/ContextSelectorPopup.svelte'
|
||||||
import ContextValue from './attributeEditors/ContextValue.svelte'
|
import ContextValue from './attributeEditors/ContextValue.svelte'
|
||||||
|
import { MasterTag, Tag } from '@hcengineering/card'
|
||||||
|
|
||||||
|
export let masterTag: Ref<MasterTag | Tag>
|
||||||
export let value: any
|
export let value: any
|
||||||
export let context: Context
|
export let context: Context
|
||||||
export let presenterClass: {
|
export let presenterClass: {
|
||||||
@ -52,6 +54,7 @@
|
|||||||
showPopup(
|
showPopup(
|
||||||
ContextSelectorPopup,
|
ContextSelectorPopup,
|
||||||
{
|
{
|
||||||
|
masterTag,
|
||||||
context,
|
context,
|
||||||
attribute,
|
attribute,
|
||||||
onSelect
|
onSelect
|
||||||
@ -78,6 +81,7 @@
|
|||||||
<div class="text-input" class:context={contextValue}>
|
<div class="text-input" class:context={contextValue}>
|
||||||
{#if contextValue}
|
{#if contextValue}
|
||||||
<ContextValue
|
<ContextValue
|
||||||
|
{masterTag}
|
||||||
{contextValue}
|
{contextValue}
|
||||||
{context}
|
{context}
|
||||||
{attribute}
|
{attribute}
|
||||||
|
@ -62,6 +62,7 @@
|
|||||||
{attribute}
|
{attribute}
|
||||||
{presenterClass}
|
{presenterClass}
|
||||||
{value}
|
{value}
|
||||||
|
masterTag={process.masterTag}
|
||||||
{allowRemove}
|
{allowRemove}
|
||||||
on:remove
|
on:remove
|
||||||
on:change={(e) => {
|
on:change={(e) => {
|
||||||
|
@ -13,10 +13,10 @@
|
|||||||
// limitations under the License.
|
// limitations under the License.
|
||||||
-->
|
-->
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import core, { generateId, Ref } from '@hcengineering/core'
|
import core, { Ref } from '@hcengineering/core'
|
||||||
import { translate } from '@hcengineering/platform'
|
import { translate } from '@hcengineering/platform'
|
||||||
import { createQuery, getClient, MessageBox } from '@hcengineering/presentation'
|
import { createQuery, getClient, MessageBox } from '@hcengineering/presentation'
|
||||||
import { Process, SelectedUserRequest, State } from '@hcengineering/process'
|
import { Process, State } from '@hcengineering/process'
|
||||||
import { settingsStore } from '@hcengineering/setting-resources'
|
import { settingsStore } from '@hcengineering/setting-resources'
|
||||||
import {
|
import {
|
||||||
Button,
|
Button,
|
||||||
@ -29,21 +29,20 @@
|
|||||||
IconDescription,
|
IconDescription,
|
||||||
navigate,
|
navigate,
|
||||||
NavItem,
|
NavItem,
|
||||||
ToggleWithLabel,
|
|
||||||
Scroller,
|
Scroller,
|
||||||
secondNavSeparators,
|
secondNavSeparators,
|
||||||
Separator,
|
Separator,
|
||||||
showPopup
|
showPopup,
|
||||||
|
ToggleWithLabel
|
||||||
} from '@hcengineering/ui'
|
} from '@hcengineering/ui'
|
||||||
import { createEventDispatcher } from 'svelte'
|
import { createEventDispatcher } from 'svelte'
|
||||||
import process from '../plugin'
|
import process from '../plugin'
|
||||||
|
import { getToDoEndAction } from '../utils'
|
||||||
import Aside from './Aside.svelte'
|
import Aside from './Aside.svelte'
|
||||||
import ArrowEnd from './icons/ArrowEnd.svelte'
|
import ArrowEnd from './icons/ArrowEnd.svelte'
|
||||||
import ArrowStart from './icons/ArrowStart.svelte'
|
import ArrowStart from './icons/ArrowStart.svelte'
|
||||||
import StateEditor from './StateEditor.svelte'
|
import StateEditor from './StateEditor.svelte'
|
||||||
import TransitionEditor from './TransitionEditor.svelte'
|
import TransitionEditor from './TransitionEditor.svelte'
|
||||||
import { getToDoEndAction } from '../utils'
|
|
||||||
import ExecutionResultEditor from './ExecutionResultEditor.svelte'
|
|
||||||
|
|
||||||
export let _id: Ref<Process>
|
export let _id: Ref<Process>
|
||||||
export let visibleSecondNav: boolean = true
|
export let visibleSecondNav: boolean = true
|
||||||
|
@ -13,6 +13,9 @@
|
|||||||
// limitations under the License.
|
// limitations under the License.
|
||||||
-->
|
-->
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
|
import { AnyAttribute, Class, Doc, Ref } from '@hcengineering/core'
|
||||||
|
import { getClient } from '@hcengineering/presentation'
|
||||||
|
import { Context, Func, ProcessFunction, SelectedContext } from '@hcengineering/process'
|
||||||
import {
|
import {
|
||||||
ButtonIcon,
|
ButtonIcon,
|
||||||
CheckBox,
|
CheckBox,
|
||||||
@ -26,14 +29,13 @@
|
|||||||
showPopup,
|
showPopup,
|
||||||
Submenu
|
Submenu
|
||||||
} from '@hcengineering/ui'
|
} from '@hcengineering/ui'
|
||||||
|
import { AttributeCategory } from '@hcengineering/view'
|
||||||
import { createEventDispatcher } from 'svelte'
|
import { createEventDispatcher } from 'svelte'
|
||||||
import plugin from '../../plugin'
|
import plugin from '../../plugin'
|
||||||
import core, { AnyAttribute, Class, Doc, Ref } from '@hcengineering/core'
|
|
||||||
import { Context, Func, ProcessFunction, SelectedContext } from '@hcengineering/process'
|
|
||||||
import { getClient } from '@hcengineering/presentation'
|
|
||||||
import { AttributeCategory } from '@hcengineering/view'
|
|
||||||
import FallbackEditor from '../contextEditors/FallbackEditor.svelte'
|
import FallbackEditor from '../contextEditors/FallbackEditor.svelte'
|
||||||
|
import { MasterTag, Tag } from '@hcengineering/card'
|
||||||
|
|
||||||
|
export let masterTag: Ref<MasterTag | Tag>
|
||||||
export let contextValue: SelectedContext
|
export let contextValue: SelectedContext
|
||||||
export let context: Context
|
export let context: Context
|
||||||
export let attribute: AnyAttribute
|
export let attribute: AnyAttribute
|
||||||
@ -63,7 +65,7 @@
|
|||||||
|
|
||||||
const reduceFuncs = client
|
const reduceFuncs = client
|
||||||
.getModel()
|
.getModel()
|
||||||
.findAllSync(plugin.class.ProcessFunction, { of: core.class.ArrOf })
|
.findAllSync(plugin.class.ProcessFunction, { type: 'reduce' })
|
||||||
.map((it) => it._id)
|
.map((it) => it._id)
|
||||||
|
|
||||||
$: availableFunctions = getAvailableFunctions(context, contextValue.functions, attrClass, category)
|
$: availableFunctions = getAvailableFunctions(context, contextValue.functions, attrClass, category)
|
||||||
@ -77,7 +79,9 @@
|
|||||||
category: AttributeCategory
|
category: AttributeCategory
|
||||||
): Ref<ProcessFunction>[] {
|
): Ref<ProcessFunction>[] {
|
||||||
const result: Ref<ProcessFunction>[] = []
|
const result: Ref<ProcessFunction>[] = []
|
||||||
const allFunctions = client.getModel().findAllSync(plugin.class.ProcessFunction, { of: attrClass, category })
|
const allFunctions = client
|
||||||
|
.getModel()
|
||||||
|
.findAllSync(plugin.class.ProcessFunction, { of: attrClass, category, type: 'transform' })
|
||||||
for (const f of allFunctions) {
|
for (const f of allFunctions) {
|
||||||
if (functions === undefined || f.allowMany === true || functions.findIndex((p) => p.func === f._id) === -1) {
|
if (functions === undefined || f.allowMany === true || functions.findIndex((p) => p.func === f._id) === -1) {
|
||||||
result.push(f._id)
|
result.push(f._id)
|
||||||
@ -179,6 +183,7 @@
|
|||||||
func.editor,
|
func.editor,
|
||||||
{
|
{
|
||||||
func,
|
func,
|
||||||
|
masterTag,
|
||||||
context,
|
context,
|
||||||
attribute,
|
attribute,
|
||||||
props: val?.props ?? {}
|
props: val?.props ?? {}
|
||||||
@ -192,7 +197,6 @@
|
|||||||
func.props = res
|
func.props = res
|
||||||
contextValue.functions[pos] = func
|
contextValue.functions[pos] = func
|
||||||
contextValue.functions = contextValue.functions
|
contextValue.functions = contextValue.functions
|
||||||
console.log(contextValue.functions)
|
|
||||||
onChange(contextValue)
|
onChange(contextValue)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -309,8 +313,8 @@
|
|||||||
/>
|
/>
|
||||||
<!-- <div class="menu-separator" /> -->
|
<!-- <div class="menu-separator" /> -->
|
||||||
{/if}
|
{/if}
|
||||||
|
<div class="menu-separator" />
|
||||||
{/if}
|
{/if}
|
||||||
<div class="menu-separator" />
|
|
||||||
<!-- svelte-ignore a11y-mouse-events-have-key-events -->
|
<!-- svelte-ignore a11y-mouse-events-have-key-events -->
|
||||||
<button
|
<button
|
||||||
bind:this={elements[functionButtonIndex + 1]}
|
bind:this={elements[functionButtonIndex + 1]}
|
||||||
|
@ -13,13 +13,16 @@
|
|||||||
// limitations under the License.
|
// limitations under the License.
|
||||||
-->
|
-->
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { AnyAttribute, generateId } from '@hcengineering/core'
|
import { AnyAttribute, generateId, Ref } from '@hcengineering/core'
|
||||||
import { Context, SelectedContext } from '@hcengineering/process'
|
import { getClient } from '@hcengineering/presentation'
|
||||||
|
import { Context, ProcessFunction, SelectedContext } from '@hcengineering/process'
|
||||||
import { Label, resizeObserver, Scroller, Submenu } from '@hcengineering/ui'
|
import { Label, resizeObserver, Scroller, Submenu } from '@hcengineering/ui'
|
||||||
import { createEventDispatcher } from 'svelte'
|
import { createEventDispatcher } from 'svelte'
|
||||||
import plugin from '../../plugin'
|
import plugin from '../../plugin'
|
||||||
import { getValueReduceFunc } from '../../utils'
|
import { getValueReduceFunc } from '../../utils'
|
||||||
|
import { MasterTag, Tag } from '@hcengineering/card'
|
||||||
|
|
||||||
|
export let masterTag: Ref<MasterTag | Tag>
|
||||||
export let context: Context
|
export let context: Context
|
||||||
export let attribute: AnyAttribute
|
export let attribute: AnyAttribute
|
||||||
export let onSelect: (val: SelectedContext | null) => void
|
export let onSelect: (val: SelectedContext | null) => void
|
||||||
@ -58,20 +61,20 @@
|
|||||||
dispatch('close')
|
dispatch('close')
|
||||||
}
|
}
|
||||||
|
|
||||||
function getIndex (ownIndex: number, kind: 'attribute' | 'relation' | 'nested' | 'total'): number {
|
function onFunc (func: Ref<ProcessFunction>): void {
|
||||||
if (kind === 'attribute') {
|
onSelect({
|
||||||
return ownIndex + 1
|
type: 'function',
|
||||||
}
|
key: attribute.name,
|
||||||
if (kind === 'nested') {
|
func,
|
||||||
return ownIndex + context.attributes.length + 1
|
props: {}
|
||||||
}
|
})
|
||||||
if (kind === 'relation') {
|
dispatch('close')
|
||||||
return ownIndex + context.attributes.length + nested.length + 1
|
}
|
||||||
}
|
|
||||||
if (kind === 'total') {
|
function getFunc (func: Ref<ProcessFunction>): ProcessFunction {
|
||||||
return ownIndex + context.attributes.length + relations.length + nested.length + 1
|
const client = getClient()
|
||||||
}
|
const f = client.getModel().getObject(func)
|
||||||
return ownIndex
|
return f
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
@ -89,8 +92,38 @@
|
|||||||
</span>
|
</span>
|
||||||
</button>
|
</button>
|
||||||
<div class="menu-separator" />
|
<div class="menu-separator" />
|
||||||
|
{#if context.functions.length > 0}
|
||||||
|
{#each context.functions as f}
|
||||||
|
{@const func = getFunc(f)}
|
||||||
|
{#if func.editor !== undefined}
|
||||||
|
<Submenu
|
||||||
|
label={func.label}
|
||||||
|
props={{
|
||||||
|
masterTag,
|
||||||
|
context: func,
|
||||||
|
target: attribute,
|
||||||
|
onSelect: onClick
|
||||||
|
}}
|
||||||
|
options={{ component: func.editor }}
|
||||||
|
withHover
|
||||||
|
/>
|
||||||
|
{:else}
|
||||||
|
<button
|
||||||
|
on:click={() => {
|
||||||
|
onFunc(func._id)
|
||||||
|
}}
|
||||||
|
class="menu-item"
|
||||||
|
>
|
||||||
|
<span class="overflow-label pr-1">
|
||||||
|
<Label label={func.label} />
|
||||||
|
</span>
|
||||||
|
</button>
|
||||||
|
{/if}
|
||||||
|
{/each}
|
||||||
|
<div class="menu-separator" />
|
||||||
|
{/if}
|
||||||
{#if context.attributes.length > 0}
|
{#if context.attributes.length > 0}
|
||||||
{#each context.attributes as attr, i}
|
{#each context.attributes as attr}
|
||||||
<button
|
<button
|
||||||
on:click={() => {
|
on:click={() => {
|
||||||
onAttribute(attr)
|
onAttribute(attr)
|
||||||
@ -105,8 +138,7 @@
|
|||||||
<div class="menu-separator" />
|
<div class="menu-separator" />
|
||||||
{/if}
|
{/if}
|
||||||
{#if nested.length > 0}
|
{#if nested.length > 0}
|
||||||
{#each nested as object, i}
|
{#each nested as object}
|
||||||
{@const index = getIndex(i, 'nested')}
|
|
||||||
<Submenu
|
<Submenu
|
||||||
label={object.attribute.label}
|
label={object.attribute.label}
|
||||||
props={{
|
props={{
|
||||||
@ -121,8 +153,7 @@
|
|||||||
<div class="menu-separator" />
|
<div class="menu-separator" />
|
||||||
{/if}
|
{/if}
|
||||||
{#if relations.length > 0}
|
{#if relations.length > 0}
|
||||||
{#each relations as object, i}
|
{#each relations as object}
|
||||||
{@const index = getIndex(i, 'relation')}
|
|
||||||
<Submenu
|
<Submenu
|
||||||
text={object[0]}
|
text={object[0]}
|
||||||
props={{
|
props={{
|
||||||
|
@ -20,7 +20,9 @@
|
|||||||
import ContextValuePresenter from './ContextValuePresenter.svelte'
|
import ContextValuePresenter from './ContextValuePresenter.svelte'
|
||||||
import { AttributeCategory } from '@hcengineering/view'
|
import { AttributeCategory } from '@hcengineering/view'
|
||||||
import { createEventDispatcher } from 'svelte'
|
import { createEventDispatcher } from 'svelte'
|
||||||
|
import { MasterTag, Tag } from '@hcengineering/card'
|
||||||
|
|
||||||
|
export let masterTag: Ref<MasterTag | Tag>
|
||||||
export let contextValue: SelectedContext
|
export let contextValue: SelectedContext
|
||||||
export let context: Context
|
export let context: Context
|
||||||
export let attribute: AnyAttribute
|
export let attribute: AnyAttribute
|
||||||
@ -46,7 +48,7 @@
|
|||||||
}
|
}
|
||||||
showPopup(
|
showPopup(
|
||||||
ConfigurePopup,
|
ConfigurePopup,
|
||||||
{ contextValue, attrClass, category, attribute, context, onChange },
|
{ contextValue, attrClass, masterTag, category, attribute, context, onChange },
|
||||||
eventToHTMLElement(e)
|
eventToHTMLElement(e)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -19,6 +19,7 @@
|
|||||||
import AttrContextPresenter from './AttrContextPresenter.svelte'
|
import AttrContextPresenter from './AttrContextPresenter.svelte'
|
||||||
import NestedContextPresenter from './NestedContextPresenter.svelte'
|
import NestedContextPresenter from './NestedContextPresenter.svelte'
|
||||||
import RelContextPresenter from './RelContextPresenter.svelte'
|
import RelContextPresenter from './RelContextPresenter.svelte'
|
||||||
|
import FunctionContextPresenter from './FunctionContextPresenter.svelte'
|
||||||
|
|
||||||
export let contextValue: SelectedContext
|
export let contextValue: SelectedContext
|
||||||
export let context: Context
|
export let context: Context
|
||||||
@ -33,6 +34,8 @@
|
|||||||
<NestedContextPresenter {contextValue} {context} />
|
<NestedContextPresenter {contextValue} {context} />
|
||||||
{:else if contextValue.type === 'userRequest'}
|
{:else if contextValue.type === 'userRequest'}
|
||||||
<Label label={plugin.string.RequestFromUser} />
|
<Label label={plugin.string.RequestFromUser} />
|
||||||
|
{:else if contextValue.type === 'function'}
|
||||||
|
<FunctionContextPresenter {contextValue} {context} />
|
||||||
{/if}
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
@ -0,0 +1,29 @@
|
|||||||
|
<!--
|
||||||
|
// Copyright © 2025 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 { getClient } from '@hcengineering/presentation'
|
||||||
|
import { Context, SelectedContextFunc } from '@hcengineering/process'
|
||||||
|
import { Label } from '@hcengineering/ui'
|
||||||
|
|
||||||
|
export let contextValue: SelectedContextFunc
|
||||||
|
export let context: Context
|
||||||
|
|
||||||
|
const client = getClient()
|
||||||
|
const func = client.getModel().findObject(contextValue.func)
|
||||||
|
</script>
|
||||||
|
|
||||||
|
{#if func !== undefined}
|
||||||
|
<Label label={func.label} />
|
||||||
|
{/if}
|
@ -13,7 +13,8 @@
|
|||||||
// limitations under the License.
|
// limitations under the License.
|
||||||
-->
|
-->
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import core, { AnyAttribute } from '@hcengineering/core'
|
import { MasterTag, Tag } from '@hcengineering/card'
|
||||||
|
import core, { AnyAttribute, Ref } from '@hcengineering/core'
|
||||||
import { getResource } from '@hcengineering/platform'
|
import { getResource } from '@hcengineering/platform'
|
||||||
import presentation, { Card, getClient } from '@hcengineering/presentation'
|
import presentation, { Card, getClient } from '@hcengineering/presentation'
|
||||||
import { Context, ProcessFunction } from '@hcengineering/process'
|
import { Context, ProcessFunction } from '@hcengineering/process'
|
||||||
@ -23,6 +24,7 @@
|
|||||||
import ProcessAttribute from '../ProcessAttribute.svelte'
|
import ProcessAttribute from '../ProcessAttribute.svelte'
|
||||||
|
|
||||||
export let func: ProcessFunction
|
export let func: ProcessFunction
|
||||||
|
export let masterTag: Ref<MasterTag | Tag>
|
||||||
export let context: Context
|
export let context: Context
|
||||||
export let attribute: AnyAttribute
|
export let attribute: AnyAttribute
|
||||||
export let props: Record<string, any> = {}
|
export let props: Record<string, any> = {}
|
||||||
@ -59,6 +61,7 @@
|
|||||||
|
|
||||||
<Card on:close width={'menu'} label={func.label} canSave okAction={save} okLabel={presentation.string.Save}>
|
<Card on:close width={'menu'} label={func.label} canSave okAction={save} okLabel={presentation.string.Save}>
|
||||||
<ProcessAttribute
|
<ProcessAttribute
|
||||||
|
{masterTag}
|
||||||
{context}
|
{context}
|
||||||
{attribute}
|
{attribute}
|
||||||
presenterClass={{
|
presenterClass={{
|
||||||
|
@ -0,0 +1,91 @@
|
|||||||
|
<!--
|
||||||
|
// Copyright © 2025 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 card, { MasterTag, Role, Tag } from '@hcengineering/card'
|
||||||
|
import { AnyAttribute, Ref } from '@hcengineering/core'
|
||||||
|
import { getClient } from '@hcengineering/presentation'
|
||||||
|
import { ProcessFunction, SelectedContext } from '@hcengineering/process'
|
||||||
|
import { resizeObserver, Scroller } from '@hcengineering/ui'
|
||||||
|
import { createEventDispatcher } from 'svelte'
|
||||||
|
import { getContextFunctionReduce } from '../../utils'
|
||||||
|
|
||||||
|
export let context: ProcessFunction
|
||||||
|
export let masterTag: Ref<MasterTag | Tag>
|
||||||
|
export let target: AnyAttribute
|
||||||
|
export let onSelect: (val: SelectedContext) => void
|
||||||
|
|
||||||
|
const dispatch = createEventDispatcher()
|
||||||
|
const client = getClient()
|
||||||
|
|
||||||
|
const elements: HTMLButtonElement[] = []
|
||||||
|
|
||||||
|
const keyDown = (event: KeyboardEvent, index: number): void => {
|
||||||
|
if (event.key === 'ArrowDown') {
|
||||||
|
elements[(index + 1) % elements.length].focus()
|
||||||
|
}
|
||||||
|
|
||||||
|
if (event.key === 'ArrowUp') {
|
||||||
|
elements[(elements.length + index - 1) % elements.length].focus()
|
||||||
|
}
|
||||||
|
|
||||||
|
if (event.key === 'ArrowLeft') {
|
||||||
|
dispatch('close')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function onValue (role: Ref<Role>): void {
|
||||||
|
const pathReduce = getContextFunctionReduce(context, target)
|
||||||
|
onSelect({
|
||||||
|
type: 'function',
|
||||||
|
func: context._id,
|
||||||
|
key: target.name,
|
||||||
|
functions: [],
|
||||||
|
sourceFunction: pathReduce,
|
||||||
|
props: {
|
||||||
|
target: role
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
const ancestors = client.getHierarchy().getAncestors(masterTag)
|
||||||
|
const roles = client.getModel().findAllSync(card.class.Role, { attachedTo: { $in: ancestors } })
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<div class="selectPopup" use:resizeObserver={() => dispatch('changeContent')}>
|
||||||
|
<div class="menu-space" />
|
||||||
|
<Scroller>
|
||||||
|
{#each roles as role, i}
|
||||||
|
<!-- svelte-ignore a11y-mouse-events-have-key-events -->
|
||||||
|
<button
|
||||||
|
bind:this={elements[i]}
|
||||||
|
on:keydown={(event) => {
|
||||||
|
keyDown(event, i)
|
||||||
|
}}
|
||||||
|
on:mouseover={() => {
|
||||||
|
elements[i]?.focus()
|
||||||
|
}}
|
||||||
|
on:click={() => {
|
||||||
|
onValue(role._id)
|
||||||
|
}}
|
||||||
|
class="menu-item"
|
||||||
|
>
|
||||||
|
<span class="overflow-label pr-1">
|
||||||
|
{role.name}
|
||||||
|
</span>
|
||||||
|
</button>
|
||||||
|
{/each}
|
||||||
|
</Scroller>
|
||||||
|
<div class="menu-space" />
|
||||||
|
</div>
|
@ -35,6 +35,7 @@ import SubProcessEditor from './components/SubProcessEditor.svelte'
|
|||||||
import ToDoEditor from './components/ToDoEditor.svelte'
|
import ToDoEditor from './components/ToDoEditor.svelte'
|
||||||
import UpdateCardEditor from './components/UpdateCardEditor.svelte'
|
import UpdateCardEditor from './components/UpdateCardEditor.svelte'
|
||||||
import ResultInput from './components/contextEditors/ResultInput.svelte'
|
import ResultInput from './components/contextEditors/ResultInput.svelte'
|
||||||
|
import RoleEditor from './components/contextEditors/RoleEditor.svelte'
|
||||||
|
|
||||||
import { continueExecution, showDoneQuery } from './utils'
|
import { continueExecution, showDoneQuery } from './utils'
|
||||||
import { ProcessMiddleware } from './middleware'
|
import { ProcessMiddleware } from './middleware'
|
||||||
@ -66,7 +67,8 @@ export default async (): Promise<Resources> => ({
|
|||||||
NumberOffsetEditor,
|
NumberOffsetEditor,
|
||||||
ErrorPresenter,
|
ErrorPresenter,
|
||||||
RequestUserInput,
|
RequestUserInput,
|
||||||
ResultInput
|
ResultInput,
|
||||||
|
RoleEditor
|
||||||
},
|
},
|
||||||
function: {
|
function: {
|
||||||
ShowDoneQuery: showDoneQuery,
|
ShowDoneQuery: showDoneQuery,
|
||||||
|
@ -46,7 +46,8 @@ export default mergeIds(processId, process, {
|
|||||||
NumberOffsetEditor: '' as AnyComponent,
|
NumberOffsetEditor: '' as AnyComponent,
|
||||||
ErrorPresenter: '' as AnyComponent,
|
ErrorPresenter: '' as AnyComponent,
|
||||||
RequestUserInput: '' as AnyComponent,
|
RequestUserInput: '' as AnyComponent,
|
||||||
ResultInput: '' as AnyComponent
|
ResultInput: '' as AnyComponent,
|
||||||
|
RoleEditor: '' as AnyComponent
|
||||||
},
|
},
|
||||||
function: {
|
function: {
|
||||||
ShowDoneQuery: '' as ViewQueryAction,
|
ShowDoneQuery: '' as ViewQueryAction,
|
||||||
|
@ -66,6 +66,8 @@ export function getContext (
|
|||||||
if (attr !== undefined && category === 'object') {
|
if (attr !== undefined && category === 'object') {
|
||||||
attributes = attributes.filter((it) => it._id !== attr)
|
attributes = attributes.filter((it) => it._id !== attr)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const functions = getContextFunctions(client, process.masterTag, target, category)
|
||||||
const nested: Record<string, NestedContext> = {}
|
const nested: Record<string, NestedContext> = {}
|
||||||
const relations: Record<string, RelatedContext> = {}
|
const relations: Record<string, RelatedContext> = {}
|
||||||
|
|
||||||
@ -119,12 +121,55 @@ export function getContext (
|
|||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
functions,
|
||||||
attributes,
|
attributes,
|
||||||
nested,
|
nested,
|
||||||
relations
|
relations
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function getContextFunctions (
|
||||||
|
client: Client,
|
||||||
|
_class: Ref<Class<Doc>>,
|
||||||
|
target: Ref<Class<Type<any>>>,
|
||||||
|
category: AttributeCategory
|
||||||
|
): Array<Ref<ProcessFunction>> {
|
||||||
|
const matched: Array<Ref<ProcessFunction>> = []
|
||||||
|
const hierarchy = client.getHierarchy()
|
||||||
|
const funcs = client.getModel().findAllSync(process.class.ProcessFunction, { type: 'context' })
|
||||||
|
for (const func of funcs) {
|
||||||
|
switch (category) {
|
||||||
|
case 'object': {
|
||||||
|
if (func.category === 'array') {
|
||||||
|
if (hierarchy.isDerived(func.of, target)) {
|
||||||
|
matched.push(func._id)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (func.category === 'object') {
|
||||||
|
if (hierarchy.isDerived(func.of, target)) {
|
||||||
|
matched.push(func._id)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break
|
||||||
|
}
|
||||||
|
case 'array': {
|
||||||
|
if (func.category === 'array') {
|
||||||
|
if (hierarchy.isDerived(func.of, target)) {
|
||||||
|
matched.push(func._id)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break
|
||||||
|
}
|
||||||
|
default: {
|
||||||
|
if (func.of === target) {
|
||||||
|
matched.push(func._id)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return matched
|
||||||
|
}
|
||||||
|
|
||||||
function getClassAttributes (
|
function getClassAttributes (
|
||||||
client: Client,
|
client: Client,
|
||||||
_class: Ref<Class<Doc>>,
|
_class: Ref<Class<Doc>>,
|
||||||
@ -192,6 +237,15 @@ export function getValueReduceFunc (source: AnyAttribute, target: AnyAttribute):
|
|||||||
return process.function.FirstValue
|
return process.function.FirstValue
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function getContextFunctionReduce (
|
||||||
|
func: ProcessFunction,
|
||||||
|
target: AnyAttribute
|
||||||
|
): Ref<ProcessFunction> | undefined {
|
||||||
|
if (func.category !== 'array') return undefined
|
||||||
|
if (target.type._class === core.class.ArrOf) return undefined
|
||||||
|
return process.function.FirstValue
|
||||||
|
}
|
||||||
|
|
||||||
export function showDoneQuery (value: any, query: DocumentQuery<Doc>): DocumentQuery<Doc> {
|
export function showDoneQuery (value: any, query: DocumentQuery<Doc>): DocumentQuery<Doc> {
|
||||||
if (value === false) {
|
if (value === false) {
|
||||||
return { ...query, done: false }
|
return { ...query, done: false }
|
||||||
|
@ -91,6 +91,7 @@ export interface Method<T extends Doc> extends Doc {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export interface ProcessFunction extends Doc {
|
export interface ProcessFunction extends Doc {
|
||||||
|
type: 'transform' | 'reduce' | 'context'
|
||||||
of: Ref<Class<Doc>>
|
of: Ref<Class<Doc>>
|
||||||
editor?: AnyComponent
|
editor?: AnyComponent
|
||||||
category: AttributeCategory | undefined
|
category: AttributeCategory | undefined
|
||||||
@ -134,7 +135,8 @@ export default plugin(processId, {
|
|||||||
ObjectNotFound: '' as IntlString,
|
ObjectNotFound: '' as IntlString,
|
||||||
AttributeNotExists: '' as IntlString,
|
AttributeNotExists: '' as IntlString,
|
||||||
UserRequestedValueNotProvided: '' as IntlString,
|
UserRequestedValueNotProvided: '' as IntlString,
|
||||||
ResultNotProvided: '' as IntlString
|
ResultNotProvided: '' as IntlString,
|
||||||
|
EmptyFunctionResult: '' as IntlString
|
||||||
},
|
},
|
||||||
icon: {
|
icon: {
|
||||||
Process: '' as Asset,
|
Process: '' as Asset,
|
||||||
@ -153,6 +155,7 @@ export default plugin(processId, {
|
|||||||
Add: '' as Ref<ProcessFunction>,
|
Add: '' as Ref<ProcessFunction>,
|
||||||
Subtract: '' as Ref<ProcessFunction>,
|
Subtract: '' as Ref<ProcessFunction>,
|
||||||
Offset: '' as Ref<ProcessFunction>,
|
Offset: '' as Ref<ProcessFunction>,
|
||||||
FirstWorkingDayAfter: '' as Ref<ProcessFunction>
|
FirstWorkingDayAfter: '' as Ref<ProcessFunction>,
|
||||||
|
RoleContext: '' as Ref<ProcessFunction>
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
@ -2,6 +2,7 @@ import { Class, Doc, type AnyAttribute, type Association, type Ref } from '@hcen
|
|||||||
import { ProcessFunction } from '.'
|
import { ProcessFunction } from '.'
|
||||||
|
|
||||||
export interface Context {
|
export interface Context {
|
||||||
|
functions: Ref<ProcessFunction>[]
|
||||||
attributes: AnyAttribute[]
|
attributes: AnyAttribute[]
|
||||||
nested: Record<string, NestedContext>
|
nested: Record<string, NestedContext>
|
||||||
relations: Record<string, RelatedContext>
|
relations: Record<string, RelatedContext>
|
||||||
@ -25,7 +26,7 @@ export interface Func {
|
|||||||
}
|
}
|
||||||
|
|
||||||
interface BaseSelectedContext {
|
interface BaseSelectedContext {
|
||||||
type: 'attribute' | 'relation' | 'nested' | 'userRequest'
|
type: 'attribute' | 'relation' | 'nested' | 'userRequest' | 'function'
|
||||||
// attribute key
|
// attribute key
|
||||||
key: string
|
key: string
|
||||||
|
|
||||||
@ -60,4 +61,15 @@ export interface SelectedUserRequest extends BaseSelectedContext {
|
|||||||
id: string
|
id: string
|
||||||
}
|
}
|
||||||
|
|
||||||
export type SelectedContext = SelectedAttribute | SelectedRelation | SelectedNested | SelectedUserRequest
|
export interface SelectedContextFunc extends BaseSelectedContext {
|
||||||
|
type: 'function'
|
||||||
|
func: Ref<ProcessFunction>
|
||||||
|
props: Record<string, any>
|
||||||
|
}
|
||||||
|
|
||||||
|
export type SelectedContext =
|
||||||
|
| SelectedAttribute
|
||||||
|
| SelectedRelation
|
||||||
|
| SelectedNested
|
||||||
|
| SelectedUserRequest
|
||||||
|
| SelectedContextFunc
|
||||||
|
@ -39,6 +39,7 @@
|
|||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@hcengineering/time": "^0.6.0",
|
"@hcengineering/time": "^0.6.0",
|
||||||
"@hcengineering/core": "^0.6.32",
|
"@hcengineering/core": "^0.6.32",
|
||||||
|
"@hcengineering/contact": "^0.6.24",
|
||||||
"@hcengineering/card": "^0.6.0",
|
"@hcengineering/card": "^0.6.0",
|
||||||
"@hcengineering/server-process": "^0.6.0",
|
"@hcengineering/server-process": "^0.6.0",
|
||||||
"@hcengineering/server-core": "^0.6.1",
|
"@hcengineering/server-core": "^0.6.1",
|
||||||
|
@ -14,6 +14,7 @@
|
|||||||
//
|
//
|
||||||
|
|
||||||
import card, { Card } from '@hcengineering/card'
|
import card, { Card } from '@hcengineering/card'
|
||||||
|
import contact, { Employee } from '@hcengineering/contact'
|
||||||
import core, {
|
import core, {
|
||||||
ArrOf,
|
ArrOf,
|
||||||
Doc,
|
Doc,
|
||||||
@ -40,6 +41,7 @@ import process, {
|
|||||||
ProcessError,
|
ProcessError,
|
||||||
ProcessToDo,
|
ProcessToDo,
|
||||||
SelectedContext,
|
SelectedContext,
|
||||||
|
SelectedContextFunc,
|
||||||
SelectedNested,
|
SelectedNested,
|
||||||
SelectedRelation,
|
SelectedRelation,
|
||||||
SelectedUserRequest,
|
SelectedUserRequest,
|
||||||
@ -342,6 +344,8 @@ async function getContextValue (value: any, control: TriggerControl, execution:
|
|||||||
value = await getNestedValue(control, execution, context)
|
value = await getNestedValue(control, execution, context)
|
||||||
} else if (context.type === 'userRequest') {
|
} else if (context.type === 'userRequest') {
|
||||||
value = getUserRequestValue(control, execution, context)
|
value = getUserRequestValue(control, execution, context)
|
||||||
|
} else if (context.type === 'function') {
|
||||||
|
value = await getFunctionValue(control, execution, context)
|
||||||
}
|
}
|
||||||
return await fillValue(value, context, control, execution)
|
return await fillValue(value, context, control, execution)
|
||||||
} catch (err: any) {
|
} catch (err: any) {
|
||||||
@ -355,6 +359,39 @@ async function getContextValue (value: any, control: TriggerControl, execution:
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function getFunctionValue (
|
||||||
|
control: TriggerControl,
|
||||||
|
execution: Execution,
|
||||||
|
context: SelectedContextFunc
|
||||||
|
): Promise<any> {
|
||||||
|
const func = control.modelDb.findObject(context.func)
|
||||||
|
if (func === undefined) throw processError(process.error.MethodNotFound, { methodId: context.func }, {}, true)
|
||||||
|
const impl = control.hierarchy.as(func, serverProcess.mixin.FuncImpl)
|
||||||
|
if (impl === undefined) throw processError(process.error.MethodNotFound, { methodId: context.func }, {}, true)
|
||||||
|
const f = await getResource(impl.func)
|
||||||
|
const res = await f(null, context.props, control, execution)
|
||||||
|
if (context.sourceFunction !== undefined) {
|
||||||
|
const transform = control.modelDb.findObject(context.sourceFunction)
|
||||||
|
if (transform === undefined) {
|
||||||
|
throw processError(process.error.MethodNotFound, { methodId: context.sourceFunction }, {}, true)
|
||||||
|
}
|
||||||
|
if (!control.hierarchy.hasMixin(transform, serverProcess.mixin.FuncImpl)) {
|
||||||
|
throw processError(process.error.MethodNotFound, { methodId: context.sourceFunction }, {}, true)
|
||||||
|
}
|
||||||
|
const funcImpl = control.hierarchy.as(transform, serverProcess.mixin.FuncImpl)
|
||||||
|
const f = await getResource(funcImpl.func)
|
||||||
|
const val = await f(res, {}, control, execution)
|
||||||
|
if (val == null) {
|
||||||
|
throw processError(process.error.EmptyFunctionResult, {}, { func: func.label })
|
||||||
|
}
|
||||||
|
return val
|
||||||
|
}
|
||||||
|
if (res == null) {
|
||||||
|
throw processError(process.error.EmptyFunctionResult, {}, { func: func.label })
|
||||||
|
}
|
||||||
|
return res
|
||||||
|
}
|
||||||
|
|
||||||
function getUserRequestValue (control: TriggerControl, execution: Execution, context: SelectedUserRequest): any {
|
function getUserRequestValue (control: TriggerControl, execution: Execution, context: SelectedUserRequest): any {
|
||||||
const userContext = execution.context?.[context.id]
|
const userContext = execution.context?.[context.id]
|
||||||
if (userContext !== undefined) return userContext
|
if (userContext !== undefined) return userContext
|
||||||
@ -682,6 +719,18 @@ export function Trim (value: string): string {
|
|||||||
return value.trim()
|
return value.trim()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export async function RoleContext (
|
||||||
|
value: null,
|
||||||
|
props: Record<string, any>,
|
||||||
|
control: TriggerControl,
|
||||||
|
execution: Execution
|
||||||
|
): Promise<Ref<Employee>[]> {
|
||||||
|
const targetRole = props.target
|
||||||
|
if (targetRole === undefined) return []
|
||||||
|
const users = await control.findAll(control.ctx, contact.class.UserRole, { role: targetRole })
|
||||||
|
return users.map((it) => it.user)
|
||||||
|
}
|
||||||
|
|
||||||
export async function Add (
|
export async function Add (
|
||||||
value: number,
|
value: number,
|
||||||
props: Record<string, any>,
|
props: Record<string, any>,
|
||||||
@ -787,7 +836,8 @@ export default async () => ({
|
|||||||
Add,
|
Add,
|
||||||
Subtract,
|
Subtract,
|
||||||
Offset,
|
Offset,
|
||||||
FirstWorkingDayAfter
|
FirstWorkingDayAfter,
|
||||||
|
RoleContext
|
||||||
},
|
},
|
||||||
trigger: {
|
trigger: {
|
||||||
OnExecutionCreate,
|
OnExecutionCreate,
|
||||||
|
@ -44,7 +44,8 @@ export default plugin(serverProcessId, {
|
|||||||
Add: '' as Resource<TransformFunc>,
|
Add: '' as Resource<TransformFunc>,
|
||||||
Subtract: '' as Resource<TransformFunc>,
|
Subtract: '' as Resource<TransformFunc>,
|
||||||
Offset: '' as Resource<TransformFunc>,
|
Offset: '' as Resource<TransformFunc>,
|
||||||
FirstWorkingDayAfter: '' as Resource<TransformFunc>
|
FirstWorkingDayAfter: '' as Resource<TransformFunc>,
|
||||||
|
RoleContext: '' as Resource<TransformFunc>
|
||||||
},
|
},
|
||||||
trigger: {
|
trigger: {
|
||||||
OnExecutionCreate: '' as Resource<TriggerFunc>,
|
OnExecutionCreate: '' as Resource<TriggerFunc>,
|
||||||
|
Loading…
Reference in New Issue
Block a user