mirror of
https://github.com/hcengineering/platform.git
synced 2025-04-19 23:00:13 +00:00
Move with state and validate (#680)
Signed-off-by: Denis Bykhov <80476319+BykhovDenis@users.noreply.github.com>
This commit is contained in:
parent
9f2670964c
commit
ec738daf75
@ -224,6 +224,10 @@ export function createModel (builder: Builder): void {
|
|||||||
presenter: recruit.component.ApplicationPresenter
|
presenter: recruit.component.ApplicationPresenter
|
||||||
})
|
})
|
||||||
|
|
||||||
|
builder.mixin(recruit.class.Applicant, core.class.Class, view.mixin.ObjectValidator, {
|
||||||
|
validator: recruit.validator.ApplicantValidator
|
||||||
|
})
|
||||||
|
|
||||||
builder.createDoc(
|
builder.createDoc(
|
||||||
view.class.Action,
|
view.class.Action,
|
||||||
core.space.Model,
|
core.space.Model,
|
||||||
|
@ -25,22 +25,35 @@ function logInfo (msg: string, result: MigrationResult): void {
|
|||||||
}
|
}
|
||||||
export const recruitOperation: MigrateOperation = {
|
export const recruitOperation: MigrateOperation = {
|
||||||
async migrate (client: MigrationClient): Promise<void> {
|
async migrate (client: MigrationClient): Promise<void> {
|
||||||
logInfo('done for Applicants', await client.update(DOMAIN_TASK, { _class: recruit.class.Applicant, doneState: { $exists: false } }, { doneState: null }))
|
logInfo(
|
||||||
|
'done for Applicants',
|
||||||
|
await client.update(
|
||||||
|
DOMAIN_TASK,
|
||||||
|
{ _class: recruit.class.Applicant, doneState: { $exists: false } },
|
||||||
|
{ doneState: null }
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
logInfo('$move employee => assignee', await client.update(
|
logInfo(
|
||||||
|
'$move employee => assignee',
|
||||||
|
await client.update(
|
||||||
DOMAIN_TASK,
|
DOMAIN_TASK,
|
||||||
{ _class: recruit.class.Applicant, employee: { $exists: true } },
|
{ _class: recruit.class.Applicant, employee: { $exists: true } },
|
||||||
{ $rename: { employee: 'assignee' } }
|
{ $rename: { employee: 'assignee' } }
|
||||||
))
|
)
|
||||||
|
)
|
||||||
|
|
||||||
const employees = (await client.find(DOMAIN_CONTACT, { _class: contact.class.Employee })).map(emp => emp._id)
|
const employees = (await client.find(DOMAIN_CONTACT, { _class: contact.class.Employee })).map((emp) => emp._id)
|
||||||
|
|
||||||
// update assignee to unassigned if there is no employee exists.
|
// update assignee to unassigned if there is no employee exists.
|
||||||
logInfo('applicants wrong assignee', await client.update(
|
logInfo(
|
||||||
|
'applicants wrong assignee',
|
||||||
|
await client.update(
|
||||||
DOMAIN_TASK,
|
DOMAIN_TASK,
|
||||||
{ _class: recruit.class.Applicant, assignee: { $not: { $in: employees } } },
|
{ _class: recruit.class.Applicant, assignee: { $not: { $in: employees } } },
|
||||||
{ assignee: null }
|
{ assignee: null }
|
||||||
))
|
)
|
||||||
|
)
|
||||||
},
|
},
|
||||||
async upgrade (client: MigrationUpgradeClient): Promise<void> {
|
async upgrade (client: MigrationUpgradeClient): Promise<void> {
|
||||||
console.log('Recruit: Performing model upgrades')
|
console.log('Recruit: Performing model upgrades')
|
||||||
|
@ -13,8 +13,8 @@
|
|||||||
// limitations under the License.
|
// limitations under the License.
|
||||||
//
|
//
|
||||||
|
|
||||||
import type { Doc, Ref, Space } from '@anticrm/core'
|
import type { Client, Doc, Ref, Space } from '@anticrm/core'
|
||||||
import type { IntlString, Resource } from '@anticrm/platform'
|
import type { IntlString, Resource, Status } from '@anticrm/platform'
|
||||||
import { mergeIds } from '@anticrm/platform'
|
import { mergeIds } from '@anticrm/platform'
|
||||||
import { recruitId } from '@anticrm/recruit'
|
import { recruitId } from '@anticrm/recruit'
|
||||||
import recruit from '@anticrm/recruit-resources/src/plugin'
|
import recruit from '@anticrm/recruit-resources/src/plugin'
|
||||||
@ -40,6 +40,9 @@ export default mergeIds(recruitId, recruit, {
|
|||||||
Candidates: '' as IntlString,
|
Candidates: '' as IntlString,
|
||||||
Vacancy: '' as IntlString
|
Vacancy: '' as IntlString
|
||||||
},
|
},
|
||||||
|
validator: {
|
||||||
|
ApplicantValidator: '' as Resource<<T extends Doc>(doc: T, client: Client) => Promise<Status>>
|
||||||
|
},
|
||||||
component: {
|
component: {
|
||||||
CreateVacancy: '' as AnyComponent,
|
CreateVacancy: '' as AnyComponent,
|
||||||
CreateCandidates: '' as AnyComponent,
|
CreateCandidates: '' as AnyComponent,
|
||||||
|
@ -13,13 +13,13 @@
|
|||||||
// limitations under the License.
|
// limitations under the License.
|
||||||
//
|
//
|
||||||
|
|
||||||
import type { Class, Doc, Ref, Space } from '@anticrm/core'
|
import type { Class, Client, Doc, Ref, Space } from '@anticrm/core'
|
||||||
import { DOMAIN_MODEL } from '@anticrm/core'
|
import { DOMAIN_MODEL } from '@anticrm/core'
|
||||||
import { Builder, Mixin, Model } from '@anticrm/model'
|
import { Builder, Mixin, Model } from '@anticrm/model'
|
||||||
import core, { TClass, TDoc } from '@anticrm/model-core'
|
import core, { TClass, TDoc } from '@anticrm/model-core'
|
||||||
import type { Asset, IntlString, Resource } from '@anticrm/platform'
|
import type { Asset, IntlString, Resource, Status } from '@anticrm/platform'
|
||||||
import type { AnyComponent } from '@anticrm/ui'
|
import type { AnyComponent } from '@anticrm/ui'
|
||||||
import type { Action, ActionTarget, AttributeEditor, AttributePresenter, ObjectEditor, Viewlet, ViewletDescriptor } from '@anticrm/view'
|
import type { Action, ActionTarget, AttributeEditor, AttributePresenter, ObjectEditor, ObjectValidator, Viewlet, ViewletDescriptor } from '@anticrm/view'
|
||||||
import view from './plugin'
|
import view from './plugin'
|
||||||
|
|
||||||
@Mixin(view.mixin.AttributeEditor, core.class.Class)
|
@Mixin(view.mixin.AttributeEditor, core.class.Class)
|
||||||
@ -37,6 +37,11 @@ export class TObjectEditor extends TClass implements ObjectEditor {
|
|||||||
editor!: AnyComponent
|
editor!: AnyComponent
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Mixin(view.mixin.ObjectValidator, core.class.Class)
|
||||||
|
export class TObjectValidator extends TClass implements ObjectValidator {
|
||||||
|
validator!: Resource<(<T extends Doc>(doc: T, client: Client) => Promise<Status<{}>>)>
|
||||||
|
}
|
||||||
|
|
||||||
@Model(view.class.ViewletDescriptor, core.class.Doc, DOMAIN_MODEL)
|
@Model(view.class.ViewletDescriptor, core.class.Doc, DOMAIN_MODEL)
|
||||||
export class TViewletDescriptor extends TDoc implements ViewletDescriptor {
|
export class TViewletDescriptor extends TDoc implements ViewletDescriptor {
|
||||||
component!: AnyComponent
|
component!: AnyComponent
|
||||||
@ -65,7 +70,7 @@ export class TActionTarget extends TDoc implements ActionTarget {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function createModel (builder: Builder): void {
|
export function createModel (builder: Builder): void {
|
||||||
builder.createModel(TAttributeEditor, TAttributePresenter, TObjectEditor, TViewletDescriptor, TViewlet, TAction, TActionTarget)
|
builder.createModel(TAttributeEditor, TAttributePresenter, TObjectEditor, TViewletDescriptor, TViewlet, TAction, TActionTarget, TObjectValidator)
|
||||||
|
|
||||||
builder.mixin(core.class.TypeString, core.class.Class, view.mixin.AttributeEditor, {
|
builder.mixin(core.class.TypeString, core.class.Class, view.mixin.AttributeEditor, {
|
||||||
editor: view.component.StringEditor
|
editor: view.component.StringEditor
|
||||||
|
@ -15,16 +15,17 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import type { Employee } from '@anticrm/contact'
|
import type { Employee } from '@anticrm/contact'
|
||||||
import contact from '@anticrm/contact'
|
import contact from '@anticrm/contact'
|
||||||
import { Ref, SortingOrder, Space } from '@anticrm/core'
|
import { Account, Class, Client, Doc, generateId, Ref, SortingOrder } from '@anticrm/core'
|
||||||
import { calcRank } from '@anticrm/core'
|
import { calcRank } from '@anticrm/core'
|
||||||
import { OK, Severity, Status } from '@anticrm/platform'
|
import { getResource, OK, Resource, Severity, Status } from '@anticrm/platform'
|
||||||
import { Card, getClient, UserBox } from '@anticrm/presentation'
|
import { Card, getClient, UserBox } from '@anticrm/presentation'
|
||||||
import type { Candidate } from '@anticrm/recruit'
|
import type { Applicant, Candidate } from '@anticrm/recruit'
|
||||||
import type { SpaceWithStates } from '@anticrm/task'
|
import type { SpaceWithStates, State } from '@anticrm/task'
|
||||||
import task from '@anticrm/task'
|
import task from '@anticrm/task'
|
||||||
import { Grid, Status as StatusControl } from '@anticrm/ui'
|
import { Grid, Status as StatusControl } from '@anticrm/ui'
|
||||||
import { createEventDispatcher } from 'svelte'
|
import { createEventDispatcher } from 'svelte'
|
||||||
import recruit from '../plugin'
|
import recruit from '../plugin'
|
||||||
|
import view from '@anticrm/view'
|
||||||
|
|
||||||
export let space: Ref<SpaceWithStates>
|
export let space: Ref<SpaceWithStates>
|
||||||
export let candidate: Ref<Candidate>
|
export let candidate: Ref<Candidate>
|
||||||
@ -33,17 +34,33 @@
|
|||||||
export let preserveCandidate = false
|
export let preserveCandidate = false
|
||||||
|
|
||||||
let status: Status = OK
|
let status: Status = OK
|
||||||
let _space = space
|
|
||||||
|
const doc: Applicant = {
|
||||||
|
state: '' as Ref<State>,
|
||||||
|
doneState: null,
|
||||||
|
number: 0,
|
||||||
|
assignee: assignee,
|
||||||
|
rank: '',
|
||||||
|
attachedTo: candidate,
|
||||||
|
attachedToClass: recruit.class.Candidate,
|
||||||
|
_class: recruit.class.Applicant,
|
||||||
|
space: space,
|
||||||
|
_id: generateId(),
|
||||||
|
collection: 'applications',
|
||||||
|
modifiedOn: Date.now(),
|
||||||
|
modifiedBy: '' as Ref<Account>
|
||||||
|
}
|
||||||
|
|
||||||
const dispatch = createEventDispatcher()
|
const dispatch = createEventDispatcher()
|
||||||
const client = getClient()
|
const client = getClient()
|
||||||
|
const hierarchy = client.getHierarchy()
|
||||||
|
|
||||||
export function canClose (): boolean {
|
export function canClose (): boolean {
|
||||||
return candidate === undefined && assignee === undefined
|
return candidate === undefined && assignee === undefined
|
||||||
}
|
}
|
||||||
|
|
||||||
async function createApplication () {
|
async function createApplication () {
|
||||||
const state = await client.findOne(task.class.State, { space: _space })
|
const state = await client.findOne(task.class.State, { space: doc.space })
|
||||||
if (state === undefined) {
|
if (state === undefined) {
|
||||||
throw new Error('create application: state not found')
|
throw new Error('create application: state not found')
|
||||||
}
|
}
|
||||||
@ -68,38 +85,40 @@
|
|||||||
)
|
)
|
||||||
await client.addCollection(
|
await client.addCollection(
|
||||||
recruit.class.Applicant,
|
recruit.class.Applicant,
|
||||||
_space,
|
doc.space,
|
||||||
candidate,
|
doc.attachedTo,
|
||||||
recruit.class.Candidate,
|
recruit.class.Candidate,
|
||||||
'applications',
|
'applications',
|
||||||
{
|
{
|
||||||
state: state._id,
|
state: state._id,
|
||||||
doneState: null,
|
doneState: null,
|
||||||
number: incResult.object.sequence,
|
number: (incResult as any).object.sequence,
|
||||||
assignee: assignee,
|
assignee: doc.assignee,
|
||||||
rank: calcRank(lastOne, undefined)
|
rank: calcRank(lastOne, undefined)
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
async function validate (candidate: Ref<Candidate>, space: Ref<Space>) {
|
async function invokeValidate (
|
||||||
if (candidate === undefined) {
|
action: Resource<<T extends Doc>(doc: T, client: Client) => Promise<Status>>
|
||||||
status = new Status(Severity.INFO, recruit.status.CandidateRequired, {})
|
): Promise<Status> {
|
||||||
} else {
|
const impl = await getResource(action)
|
||||||
if (space === undefined) {
|
return await impl(doc, client)
|
||||||
status = new Status(Severity.INFO, recruit.status.VacancyRequired, {})
|
}
|
||||||
} else {
|
|
||||||
const applicants = await client.findAll(recruit.class.Applicant, { space, attachedTo: candidate })
|
async function validate (doc: Applicant, _class: Ref<Class<Doc>>): Promise<void> {
|
||||||
if (applicants.length > 0) {
|
const clazz = hierarchy.getClass(_class)
|
||||||
status = new Status(Severity.ERROR, recruit.status.ApplicationExists, {})
|
const validatorMixin = hierarchy.as(clazz, view.mixin.ObjectValidator)
|
||||||
|
if (validatorMixin?.validator != null) {
|
||||||
|
status = await invokeValidate(validatorMixin.validator)
|
||||||
|
} else if (clazz.extends != null) {
|
||||||
|
await validate(doc, clazz.extends)
|
||||||
} else {
|
} else {
|
||||||
status = OK
|
status = OK
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
$: validate(candidate, _space)
|
$: validate(doc, doc._class)
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<Card
|
<Card
|
||||||
@ -109,7 +128,7 @@
|
|||||||
spaceClass={recruit.class.Vacancy}
|
spaceClass={recruit.class.Vacancy}
|
||||||
spaceLabel={'Vacancy'}
|
spaceLabel={'Vacancy'}
|
||||||
spacePlaceholder={'Select vacancy'}
|
spacePlaceholder={'Select vacancy'}
|
||||||
bind:space={_space}
|
bind:space={doc.space}
|
||||||
on:close={() => {
|
on:close={() => {
|
||||||
dispatch('close')
|
dispatch('close')
|
||||||
}}
|
}}
|
||||||
@ -117,13 +136,13 @@
|
|||||||
<StatusControl slot="error" {status} />
|
<StatusControl slot="error" {status} />
|
||||||
<Grid column={1} rowGap={1.75}>
|
<Grid column={1} rowGap={1.75}>
|
||||||
{#if !preserveCandidate}
|
{#if !preserveCandidate}
|
||||||
<UserBox _class={recruit.class.Candidate} title="Candidate" caption="Candidates" bind:value={candidate} />
|
<UserBox _class={recruit.class.Candidate} title="Candidate" caption="Candidates" bind:value={doc.attachedTo} />
|
||||||
{/if}
|
{/if}
|
||||||
<UserBox
|
<UserBox
|
||||||
_class={contact.class.Employee}
|
_class={contact.class.Employee}
|
||||||
title="Assigned recruiter"
|
title="Assigned recruiter"
|
||||||
caption="Recruiters"
|
caption="Recruiters"
|
||||||
bind:value={assignee}
|
bind:value={doc.assignee}
|
||||||
allowDeselect
|
allowDeselect
|
||||||
titleDeselect={'Unassign recruiter'}
|
titleDeselect={'Unassign recruiter'}
|
||||||
/>
|
/>
|
||||||
|
@ -13,7 +13,7 @@
|
|||||||
// limitations under the License.
|
// limitations under the License.
|
||||||
//
|
//
|
||||||
|
|
||||||
import type { Doc } from '@anticrm/core'
|
import type { Client, Doc } from '@anticrm/core'
|
||||||
|
|
||||||
import CreateVacancy from './components/CreateVacancy.svelte'
|
import CreateVacancy from './components/CreateVacancy.svelte'
|
||||||
import CreateCandidates from './components/CreateCandidates.svelte'
|
import CreateCandidates from './components/CreateCandidates.svelte'
|
||||||
@ -29,16 +29,38 @@ import Applications from './components/Applications.svelte'
|
|||||||
import EditApplication from './components/EditApplication.svelte'
|
import EditApplication from './components/EditApplication.svelte'
|
||||||
|
|
||||||
import { showPopup } from '@anticrm/ui'
|
import { showPopup } from '@anticrm/ui'
|
||||||
import { Resources } from '@anticrm/platform'
|
import { OK, Resources, Severity, Status } from '@anticrm/platform'
|
||||||
|
import { Applicant } from '@anticrm/recruit'
|
||||||
|
import recruit from './plugin'
|
||||||
|
|
||||||
async function createApplication (object: Doc): Promise<void> {
|
async function createApplication (object: Doc): Promise<void> {
|
||||||
showPopup(CreateApplication, { candidate: object._id, preserveCandidate: true })
|
showPopup(CreateApplication, { candidate: object._id, preserveCandidate: true })
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export async function applicantValidator (applicant: Applicant, client: Client): Promise<Status> {
|
||||||
|
if (applicant.attachedTo === undefined) {
|
||||||
|
return new Status(Severity.INFO, recruit.status.CandidateRequired, {})
|
||||||
|
}
|
||||||
|
if (applicant.space === undefined) {
|
||||||
|
return new Status(Severity.INFO, recruit.status.VacancyRequired, {})
|
||||||
|
}
|
||||||
|
const applicants = await client.findAll(recruit.class.Applicant, {
|
||||||
|
space: applicant.space,
|
||||||
|
attachedTo: applicant.attachedTo
|
||||||
|
})
|
||||||
|
if (applicants.filter((p) => p._id !== applicant._id).length > 0) {
|
||||||
|
return new Status(Severity.ERROR, recruit.status.ApplicationExists, {})
|
||||||
|
}
|
||||||
|
return OK
|
||||||
|
}
|
||||||
|
|
||||||
export default async (): Promise<Resources> => ({
|
export default async (): Promise<Resources> => ({
|
||||||
actionImpl: {
|
actionImpl: {
|
||||||
CreateApplication: createApplication
|
CreateApplication: createApplication
|
||||||
},
|
},
|
||||||
|
validator: {
|
||||||
|
ApplicantValidator: applicantValidator
|
||||||
|
},
|
||||||
component: {
|
component: {
|
||||||
CreateVacancy,
|
CreateVacancy,
|
||||||
CreateCandidates,
|
CreateCandidates,
|
||||||
|
@ -35,6 +35,7 @@
|
|||||||
"@anticrm/core": "~0.6.11",
|
"@anticrm/core": "~0.6.11",
|
||||||
"@anticrm/view": "~0.6.0",
|
"@anticrm/view": "~0.6.0",
|
||||||
"@anticrm/ui": "~0.6.0",
|
"@anticrm/ui": "~0.6.0",
|
||||||
|
"@anticrm/task": "~0.6.0",
|
||||||
"@anticrm/presentation": "~0.6.2"
|
"@anticrm/presentation": "~0.6.2"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -14,19 +14,20 @@
|
|||||||
// limitations under the License.
|
// limitations under the License.
|
||||||
-->
|
-->
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { Label, Button } from '@anticrm/ui'
|
import { Label, Button, Status as StatusControl } from '@anticrm/ui'
|
||||||
import { getClient } from '@anticrm/presentation'
|
import { getClient } from '@anticrm/presentation'
|
||||||
|
|
||||||
import core, { AttachedDoc, Collection, Doc, Ref, Space } from '@anticrm/core'
|
import core, { AttachedDoc, Collection, Doc, Ref, Space, SortingOrder, calcRank, Client, Class } from '@anticrm/core'
|
||||||
import { SpaceSelect } from '@anticrm/presentation'
|
import { SpaceSelect } from '@anticrm/presentation'
|
||||||
import { createEventDispatcher } from 'svelte'
|
import { createEventDispatcher } from 'svelte'
|
||||||
import ui from '@anticrm/ui'
|
|
||||||
import view from '../plugin'
|
import view from '../plugin'
|
||||||
import { translate } from '@anticrm/platform'
|
import task, { Task } from '@anticrm/task'
|
||||||
|
import { getResource, OK, Resource, Status, translate } from '@anticrm/platform'
|
||||||
|
|
||||||
export let object: Doc
|
export let object: Doc
|
||||||
|
|
||||||
|
let status: Status = OK
|
||||||
let currentSpace: Space | undefined
|
let currentSpace: Space | undefined
|
||||||
let space: Ref<Space> = object.space
|
|
||||||
const client = getClient()
|
const client = getClient()
|
||||||
const dispatch = createEventDispatcher()
|
const dispatch = createEventDispatcher()
|
||||||
const hierarchy = client.getHierarchy()
|
const hierarchy = client.getHierarchy()
|
||||||
@ -37,52 +38,83 @@
|
|||||||
$: _class && translate(_class, {}).then((res) => (classLabel = res.toLocaleLowerCase()))
|
$: _class && translate(_class, {}).then((res) => (classLabel = res.toLocaleLowerCase()))
|
||||||
|
|
||||||
async function move (doc: Doc): Promise<void> {
|
async function move (doc: Doc): Promise<void> {
|
||||||
console.log('start move')
|
|
||||||
console.log(doc)
|
|
||||||
const attributes = hierarchy.getAllAttributes(doc._class)
|
const attributes = hierarchy.getAllAttributes(doc._class)
|
||||||
for (const [name, attribute] of attributes) {
|
for (const [name, attribute] of attributes) {
|
||||||
if (hierarchy.isDerived(attribute.type._class, core.class.Collection)) {
|
if (hierarchy.isDerived(attribute.type._class, core.class.Collection)) {
|
||||||
const collection = attribute.type as Collection<AttachedDoc>
|
const collection = attribute.type as Collection<AttachedDoc>
|
||||||
console.log('find collection')
|
|
||||||
console.log(collection)
|
|
||||||
const allAttached = await client.findAll(collection.of, { attachedTo: doc._id })
|
const allAttached = await client.findAll(collection.of, { attachedTo: doc._id })
|
||||||
console.log(allAttached)
|
|
||||||
for (const attached of allAttached) {
|
for (const attached of allAttached) {
|
||||||
move(attached).catch((err) => console.log('failed to move', name, err))
|
move(attached).catch((err) => console.log('failed to move', name, err))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (doc.space === object.space) {
|
const update: any = {
|
||||||
console.log('move doc')
|
space: doc.space
|
||||||
console.log(doc)
|
|
||||||
client.updateDoc(doc._class, doc.space, doc._id, {
|
|
||||||
space: space
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
console.log('close')
|
const needStates = currentSpace ? hierarchy.isDerived(currentSpace._class, task.class.SpaceWithStates) : false
|
||||||
|
if (needStates) {
|
||||||
|
const state = await client.findOne(task.class.State, { space: doc.space })
|
||||||
|
if (state === undefined) {
|
||||||
|
throw new Error('Move: state not found')
|
||||||
|
}
|
||||||
|
const lastOne = await client.findOne(
|
||||||
|
(doc as Task)._class,
|
||||||
|
{ state: state._id },
|
||||||
|
{ sort: { rank: SortingOrder.Descending } }
|
||||||
|
)
|
||||||
|
update.state = state._id
|
||||||
|
update.rank = calcRank(lastOne, undefined)
|
||||||
|
}
|
||||||
|
client.updateDoc(doc._class, doc.space, doc._id, update)
|
||||||
dispatch('close')
|
dispatch('close')
|
||||||
}
|
}
|
||||||
|
|
||||||
$: client.findOne(core.class.Space, { _id: object.space }).then((res) => (currentSpace = res))
|
async function getSpace (): Promise<void> {
|
||||||
|
client.findOne(core.class.Space, { _id: object.space }).then((res) => (currentSpace = res))
|
||||||
|
}
|
||||||
|
|
||||||
|
async function invokeValidate (
|
||||||
|
action: Resource<<T extends Doc>(doc: T, client: Client) => Promise<Status>>
|
||||||
|
): Promise<Status> {
|
||||||
|
const impl = await getResource(action)
|
||||||
|
return await impl(object, client)
|
||||||
|
}
|
||||||
|
|
||||||
|
async function validate (doc: Doc, _class: Ref<Class<Doc>>): Promise<void> {
|
||||||
|
const clazz = hierarchy.getClass(_class)
|
||||||
|
const validatorMixin = hierarchy.as(clazz, view.mixin.ObjectValidator)
|
||||||
|
if (validatorMixin?.validator != null) {
|
||||||
|
status = await invokeValidate(validatorMixin.validator)
|
||||||
|
} else if (clazz.extends != null) {
|
||||||
|
await validate(doc, clazz.extends)
|
||||||
|
} else {
|
||||||
|
status = OK
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$: validate(object, object._class)
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="container">
|
<div class="container">
|
||||||
<div class="overflow-label fs-title mb-4">
|
<div class="overflow-label fs-title">
|
||||||
<Label label={view.string.MoveClass} params={{ class: label }} />
|
<Label label={view.string.MoveClass} params={{ class: label }} />
|
||||||
</div>
|
</div>
|
||||||
<div class="content-accent-color mb-4">
|
<StatusControl {status} />
|
||||||
|
<div class="content-accent-color mt-4 mb-4">
|
||||||
<Label label={view.string.SelectToMove} params={{ class: label, classLabel: classLabel }} />
|
<Label label={view.string.SelectToMove} params={{ class: label, classLabel: classLabel }} />
|
||||||
</div>
|
</div>
|
||||||
<div class="spaceSelect">
|
<div class="spaceSelect">
|
||||||
|
{#await getSpace() then}
|
||||||
{#if currentSpace}
|
{#if currentSpace}
|
||||||
<SpaceSelect _class={currentSpace._class} label={_class ?? ''} bind:value={space} />
|
<SpaceSelect _class={currentSpace._class} label={_class ?? ''} bind:value={object.space} />
|
||||||
{/if}
|
{/if}
|
||||||
|
{/await}
|
||||||
</div>
|
</div>
|
||||||
<div class="footer">
|
<div class="footer">
|
||||||
<Button
|
<Button
|
||||||
label={view.string.Move}
|
label={view.string.Move}
|
||||||
size={'small'}
|
size={'small'}
|
||||||
disabled={space === object?.space}
|
disabled={object.space === currentSpace?._id || status !== OK}
|
||||||
transparent
|
transparent
|
||||||
primary
|
primary
|
||||||
on:click={() => {
|
on:click={() => {
|
||||||
@ -116,7 +148,7 @@
|
|||||||
padding: 1rem 1.25rem;
|
padding: 1rem 1.25rem;
|
||||||
background-color: var(--theme-button-bg-enabled);
|
background-color: var(--theme-button-bg-enabled);
|
||||||
border: 1px solid var(--theme-bg-accent-color);
|
border: 1px solid var(--theme-bg-accent-color);
|
||||||
border-radius: .75rem;
|
border-radius: 0.75rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.footer {
|
.footer {
|
||||||
@ -127,7 +159,7 @@
|
|||||||
justify-content: start;
|
justify-content: start;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
margin-top: 1rem;
|
margin-top: 1rem;
|
||||||
column-gap: .5rem;
|
column-gap: 0.5rem;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
@ -15,7 +15,7 @@
|
|||||||
//
|
//
|
||||||
|
|
||||||
import type { Class, Client, Doc, FindOptions, Mixin, Obj, Ref, Space, UXObject } from '@anticrm/core'
|
import type { Class, Client, Doc, FindOptions, Mixin, Obj, Ref, Space, UXObject } from '@anticrm/core'
|
||||||
import type { Asset, IntlString, Plugin, Resource } from '@anticrm/platform'
|
import type { Asset, IntlString, Plugin, Resource, Status } from '@anticrm/platform'
|
||||||
import { plugin } from '@anticrm/platform'
|
import { plugin } from '@anticrm/platform'
|
||||||
import type { AnyComponent, AnySvelteComponent } from '@anticrm/ui'
|
import type { AnyComponent, AnySvelteComponent } from '@anticrm/ui'
|
||||||
|
|
||||||
@ -40,6 +40,13 @@ export interface ObjectEditor extends Class<Doc> {
|
|||||||
editor: AnyComponent
|
editor: AnyComponent
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @public
|
||||||
|
*/
|
||||||
|
export interface ObjectValidator extends Class<Doc> {
|
||||||
|
validator: Resource<<T extends Doc>(doc: T, client: Client) => Promise<Status>>
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @public
|
* @public
|
||||||
*/
|
*/
|
||||||
@ -118,7 +125,8 @@ const view = plugin(viewId, {
|
|||||||
mixin: {
|
mixin: {
|
||||||
AttributeEditor: '' as Ref<Mixin<AttributeEditor>>,
|
AttributeEditor: '' as Ref<Mixin<AttributeEditor>>,
|
||||||
AttributePresenter: '' as Ref<Mixin<AttributePresenter>>,
|
AttributePresenter: '' as Ref<Mixin<AttributePresenter>>,
|
||||||
ObjectEditor: '' as Ref<Mixin<ObjectEditor>>
|
ObjectEditor: '' as Ref<Mixin<ObjectEditor>>,
|
||||||
|
ObjectValidator: '' as Ref<Mixin<ObjectValidator>>
|
||||||
},
|
},
|
||||||
class: {
|
class: {
|
||||||
ViewletDescriptor: '' as Ref<Class<ViewletDescriptor>>,
|
ViewletDescriptor: '' as Ref<Class<ViewletDescriptor>>,
|
||||||
|
Loading…
Reference in New Issue
Block a user