Add auto-start functionality and related triggers for processes (#8478)

This commit is contained in:
Denis Bykhov 2025-04-07 09:06:58 +05:00 committed by GitHub
parent d2bdf88249
commit 507078d7ac
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
17 changed files with 116 additions and 6 deletions

View File

@ -11,7 +11,7 @@
// See the License for the specific language governing permissions and
// limitations under the License.
import card, { type Card, type MasterTag } from '@hcengineering/card'
import card, { type Tag, type Card, type MasterTag } from '@hcengineering/card'
import contact, { type Employee } from '@hcengineering/contact'
import core, {
AccountRole,
@ -68,11 +68,14 @@ export class TProcess extends TDoc implements Process {
@Prop(TypeString(), core.string.Description)
description!: string
@Prop(TypeRef(card.class.MasterTag), core.string.Name)
masterTag!: Ref<MasterTag>
@Prop(TypeRef(card.class.MasterTag), card.string.MasterTag)
masterTag!: Ref<MasterTag | Tag>
@Prop(ArrOf(TypeRef(process.class.State)), process.string.States)
states!: Ref<State>[]
@Prop(TypeBoolean(), process.string.StartAutomatically)
autoStart: boolean | undefined
}
@Model(process.class.Execution, core.class.Doc, DOMAIN_PROCESS)

View File

@ -33,6 +33,7 @@
"@hcengineering/model": "^0.6.11",
"@hcengineering/model-process": "^0.6.0",
"@hcengineering/process": "^0.6.0",
"@hcengineering/card": "^0.6.0",
"@hcengineering/server-process": "^0.6.0",
"@hcengineering/server-core": "^0.6.1"
}

View File

@ -11,6 +11,7 @@
// See the License for the specific language governing permissions and
// limitations under the License.
import card from '@hcengineering/card'
import core, { type Doc } from '@hcengineering/core'
import { Mixin, type Builder } from '@hcengineering/model'
import { TMethod, TProcessFunction } from '@hcengineering/model-process'
@ -19,8 +20,8 @@ import process from '@hcengineering/process'
import serverCore from '@hcengineering/server-core'
import serverProcess, {
type ExecuteFunc,
type MethodImpl,
type FuncImpl,
type MethodImpl,
type TransformFunc
} from '@hcengineering/server-process'
@ -110,6 +111,24 @@ export function createModel (builder: Builder): void {
isAsync: true
})
builder.createDoc(serverCore.class.Trigger, core.space.Model, {
trigger: serverProcess.trigger.OnCardCreate,
txMatch: {
_class: core.class.TxCreateDoc,
objectClass: card.class.Card
},
isAsync: true
})
builder.createDoc(serverCore.class.Trigger, core.space.Model, {
trigger: serverProcess.trigger.OnTagAdd,
txMatch: {
_class: core.class.TxMixin,
objectClass: card.class.Card
},
isAsync: true
})
builder.createDoc(serverCore.class.Trigger, core.space.Model, {
trigger: serverProcess.trigger.OnExecutionCreate,
txMatch: {

View File

@ -43,6 +43,7 @@
"FirstWorkingDayAfter": "První pracovní den po",
"FallbackValueError": "Zobrazit chybu, pokud není k dispozici",
"Required": "Povinné",
"StartAutomatically": "Spustit automaticky",
"Error": "Chyba",
"Continue": "Pokračovat"
},

View File

@ -43,6 +43,7 @@
"FirstWorkingDayAfter": "Erste Arbeitstag nach",
"FallbackValueError": "Fehler anzeigen, wenn leer",
"Required": "Erforderlich",
"StartAutomatically": "Automatisch starten",
"Error": "Fehler",
"Continue": "Fortsetzen"
},

View File

@ -43,6 +43,7 @@
"FirstWorkingDayAfter": "First working day after",
"FallbackValueError": "Show error if empty",
"Required": "Required",
"StartAutomatically": "Start automatically",
"Error": "Error",
"Continue": "Continue"
},

View File

@ -43,6 +43,7 @@
"FirstWorkingDayAfter": "Primer día de trabajo después",
"FallbackValueError": "Mostrar error si está vacío",
"Required": "Requerido",
"StartAutomatically": "Iniciar automáticamente",
"Error": "Error",
"Continue": "Continuar"
},

View File

@ -43,6 +43,7 @@
"FirstWorkingDayAfter": "Premier jour de travail après",
"FallbackValueError": "Afficher l'erreur si vide",
"Required": "Requis",
"StartAutomatically": "Démarrer automatiquement",
"Error": "Erreur",
"Continue": "Continuer"
},

View File

@ -43,6 +43,7 @@
"FirstWorkingDayAfter": "Primo giorno lavorativo dopo",
"FallbackValueError": "Mostra errore se vuoto",
"Required": "Obbligatorio",
"StartAutomatically": "Avvia automaticamente",
"Error": "Errore",
"Continue": "Continua"
},

View File

@ -43,6 +43,7 @@
"FirstWorkingDayAfter": "Primeiro Dia de Trabalho Após",
"FallbackValueError": "Mostrar erro se vazio",
"Required": "Obrigatório",
"StartAutomatically": "Iniciar automaticamente",
"Error": "Erro",
"Continue": "Continuar"
},

View File

@ -43,6 +43,7 @@
"FirstWorkingDayAfter": "Первый рабочий день после",
"FallbackValueError": "Показывать ошибку, если недоступно",
"Required": "Обязательно",
"StartAutomatically": "Запускать автоматически",
"Error": "Ошибка",
"Continue": "Продолжить"
},

View File

@ -43,6 +43,7 @@
"FirstWorkingDayAfter": "在之后的第一个工作日",
"FallbackValueError": "显示错误,如果为空",
"Required": "必需",
"StartAutomatically": "自动启动",
"Error": "错误",
"Continue": "继续"
},

View File

@ -29,6 +29,7 @@
IconDescription,
navigate,
NavItem,
ToggleWithLabel,
Scroller,
secondNavSeparators,
Separator,
@ -77,6 +78,12 @@
}
}
async function saveAutoStart (e: CustomEvent<boolean>): Promise<void> {
if (value !== undefined) {
await client.update(value, { autoStart: e.detail })
}
}
async function addState (): Promise<void> {
if (value === undefined) return
const prevState = states[states.length - 1]
@ -160,6 +167,13 @@
<EditBox bind:value={value.name} on:change={saveName} placeholder={process.string.Untitled} />
<ButtonIcon icon={IconDelete} size="small" kind="secondary" on:click={handleDelete} />
</div>
<div>
<ToggleWithLabel
on={value.autoStart ?? false}
on:change={saveAutoStart}
label={process.string.StartAutomatically}
/>
</div>
<div class="hulyComponent-content flex-col-center">
<div class="flex-col-center">
{#each sortedStates as state (state._id)}

View File

@ -89,6 +89,7 @@ export default mergeIds(processId, process, {
FirstWorkingDayAfter: '' as IntlString,
FallbackValueError: '' as IntlString,
Required: '' as IntlString,
StartAutomatically: '' as IntlString,
Continue: '' as IntlString
}
})

View File

@ -11,7 +11,7 @@
// See the License for the specific language governing permissions and
// limitations under the License.
import { Card, MasterTag } from '@hcengineering/card'
import { Card, MasterTag, Tag } from '@hcengineering/card'
import { Employee } from '@hcengineering/contact'
import { Class, Doc, DocumentUpdate, ObjQueryType, Ref, Tx } from '@hcengineering/core'
import { Asset, IntlString, Plugin, plugin } from '@hcengineering/platform'
@ -25,10 +25,11 @@ import { AttributeCategory } from '@hcengineering/view'
export const processId = 'process' as Plugin
export interface Process extends Doc {
masterTag: Ref<MasterTag>
masterTag: Ref<MasterTag | Tag>
name: string
description: string
states: Ref<State>[]
autoStart?: boolean
}
export interface Execution extends Doc {

View File

@ -25,6 +25,7 @@ import core, {
Timestamp,
Tx,
TxCreateDoc,
TxMixin,
TxProcessor,
TxRemoveDoc,
TxUpdateDoc
@ -635,6 +636,63 @@ export function FirstWorkingDayAfter (val: Timestamp): Timestamp {
return val
}
export async function OnCardCreate (txes: Tx[], control: TriggerControl): Promise<Tx[]> {
const res: Tx[] = []
for (const tx of txes) {
if (tx._class !== core.class.TxCreateDoc) continue
const createTx = tx as TxCreateDoc<Card>
if (!control.hierarchy.isDerived(createTx.objectClass, card.class.Card)) continue
const ancestors = control.hierarchy
.getAncestors(createTx.objectClass)
.filter((p) => control.hierarchy.isDerived(p, card.class.Card))
const processes = control.modelDb.findAllSync(process.class.Process, {
masterTag: { $in: ancestors },
autoStart: true
})
for (const proc of processes) {
res.push(
control.txFactory.createTxCreateDoc(process.class.Execution, core.space.Workspace, {
process: proc._id,
currentState: null,
card: createTx.objectId,
done: false,
rollback: {},
currentToDo: null,
assignee: null
})
)
}
}
return res
}
export async function OnTagAdd (txes: Tx[], control: TriggerControl): Promise<Tx[]> {
const res: Tx[] = []
for (const tx of txes) {
if (tx._class !== core.class.TxMixin) continue
const mixinTx = tx as TxMixin<Card, Card>
if (!control.hierarchy.isDerived(mixinTx.objectClass, card.class.Card)) continue
if (Object.keys(mixinTx.attributes).length !== 0) continue
const processes = control.modelDb.findAllSync(process.class.Process, { masterTag: mixinTx.mixin, autoStart: true })
for (const proc of processes) {
res.push(
control.txFactory.createTxCreateDoc(process.class.Execution, core.space.Workspace, {
process: proc._id,
currentState: null,
card: mixinTx.objectId,
done: false,
rollback: {},
currentToDo: null,
assignee: null
})
)
}
}
return res
}
export async function OnExecutionContinue (txes: Tx[], control: TriggerControl): Promise<Tx[]> {
const res: Tx[] = []
for (const tx of txes) {
@ -679,6 +737,8 @@ export default async () => ({
FirstWorkingDayAfter
},
trigger: {
OnCardCreate,
OnTagAdd,
OnExecutionCreate,
OnStateRemove,
OnProcessRemove,

View File

@ -46,6 +46,8 @@ export default plugin(serverProcessId, {
FirstWorkingDayAfter: '' as Resource<TransformFunc>
},
trigger: {
OnCardCreate: '' as Resource<TriggerFunc>,
OnTagAdd: '' as Resource<TriggerFunc>,
OnExecutionCreate: '' as Resource<TriggerFunc>,
OnStateRemove: '' as Resource<TriggerFunc>,
OnProcessRemove: '' as Resource<TriggerFunc>,