From feb76fcafa4593a2dc24125f5d81c1bc4ef197a0 Mon Sep 17 00:00:00 2001 From: Denis Bykhov Date: Thu, 19 Feb 2026 14:41:51 +0500 Subject: [PATCH] =?UTF-8?q?feat:=20Implement=20process=20import/export=20f?= =?UTF-8?q?unctionality,=20enhance=20process=20=E2=80=A6=20(#10528)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Denis Bykhov --- foundations/core/packages/core/src/memdb.ts | 1 + models/process/src/index.ts | 21 ++++++- plugins/process-assets/lang/cs.json | 4 +- plugins/process-assets/lang/de.json | 4 +- plugins/process-assets/lang/en.json | 4 +- plugins/process-assets/lang/es.json | 4 +- plugins/process-assets/lang/fr.json | 4 +- plugins/process-assets/lang/it.json | 4 +- plugins/process-assets/lang/ja.json | 4 +- plugins/process-assets/lang/pt-br.json | 4 +- plugins/process-assets/lang/pt.json | 4 +- plugins/process-assets/lang/ru.json | 4 +- plugins/process-assets/lang/tr.json | 4 +- plugins/process-assets/lang/zh.json | 4 +- .../src/components/ProcessesSection.svelte | 28 ++++++++- .../components/settings/ProcessEditor.svelte | 28 ++++++++- .../settings/SubProcessMatchEditor.svelte | 6 +- plugins/process-resources/src/exporter.ts | 51 ++++++++++++--- plugins/process-resources/src/index.ts | 25 ++++---- plugins/process-resources/src/plugin.ts | 4 +- server-plugins/process-resources/src/index.ts | 62 ++++++------------- server-plugins/process/src/types.ts | 2 +- server-plugins/time-resources/src/index.ts | 3 +- services/process/src/main.ts | 50 ++++++++++----- services/worker/src/activities.ts | 2 +- 25 files changed, 228 insertions(+), 103 deletions(-) diff --git a/foundations/core/packages/core/src/memdb.ts b/foundations/core/packages/core/src/memdb.ts index 29cdb08891..8e07c32266 100644 --- a/foundations/core/packages/core/src/memdb.ts +++ b/foundations/core/packages/core/src/memdb.ts @@ -249,6 +249,7 @@ export abstract class MemDb extends TxProcessor implements Storage { // We need to filter instances without mixin was set result = result.filter((r) => (r as any)[_class] !== undefined) } + if (options?.sort !== undefined) resultSort(result, options?.sort, _class, this.hierarchy, this) const total = result.length result = result.slice(0, options?.limit) diff --git a/models/process/src/index.ts b/models/process/src/index.ts index 6dc13c8b99..15928063f2 100644 --- a/models/process/src/index.ts +++ b/models/process/src/index.ts @@ -43,7 +43,7 @@ import presentation from '@hcengineering/model-presentation' import { TToDo } from '@hcengineering/model-time' import view, { createAction } from '@hcengineering/model-view' import workbench from '@hcengineering/model-workbench' -import notification from '@hcengineering/notification' +import notification, { type NotificationGroup } from '@hcengineering/notification' import { type Asset, type IntlString, type Resource } from '@hcengineering/platform' import { type ApproveRequest, @@ -727,6 +727,25 @@ export function createModel (builder: Builder): void { component: process.component.ProcessesSettingSection }) + builder.createDoc(notification.class.NotificationType, core.space.Model, { + hidden: false, + generated: false, + allowedForAuthor: true, + label: process.string.NewProcessToDo, + group: time.ids.TimeNotificationGroup as Ref, + txClasses: [core.class.TxCreateDoc], + objectClass: process.class.ProcessToDo, + txMatch: { + objectClass: process.class.ProcessToDo + }, + defaultEnabled: true, + templates: { + textTemplate: '{body}', + htmlTemplate: '

{body}

', + subjectTemplate: '{title}' + } + }) + // builder.createDoc(presentation.class.ComponentPointExtension, core.space.Model, { // extension: workbench.extensions.WorkbenchExtensions, // component: process.component.NotifierExtension diff --git a/plugins/process-assets/lang/cs.json b/plugins/process-assets/lang/cs.json index 39c6238d46..15eec12828 100644 --- a/plugins/process-assets/lang/cs.json +++ b/plugins/process-assets/lang/cs.json @@ -141,7 +141,9 @@ "Approvers": "Schvalovatelé", "UnlockCard": "Odemknout kartu", "UnlockSection": "Odemknout sekci", - "CancelToDo": "Zrušit akční položku" + "CancelToDo": "Zrušit akční položku", + "Export": "Exportovat", + "Import": "Importovat" }, "error": { "MethodNotFound": "Metoda nenalezena: {methodId}", diff --git a/plugins/process-assets/lang/de.json b/plugins/process-assets/lang/de.json index c3ed7445f8..6812871aa4 100644 --- a/plugins/process-assets/lang/de.json +++ b/plugins/process-assets/lang/de.json @@ -141,7 +141,9 @@ "Approvers": "Genehmiger", "UnlockCard": "Karte entsperren", "UnlockSection": "Abschnitt entsperren", - "CancelToDo": "Action Item abbrechen" + "CancelToDo": "Action Item abbrechen", + "Export": "Exportieren", + "Import": "Importieren" }, "error": { "MethodNotFound": "Methode nicht gefunden: {methodId}", diff --git a/plugins/process-assets/lang/en.json b/plugins/process-assets/lang/en.json index c2f0350933..9327ab32ea 100644 --- a/plugins/process-assets/lang/en.json +++ b/plugins/process-assets/lang/en.json @@ -146,7 +146,9 @@ "Approvers": "Approvers", "UnlockCard": "Unlock card", "UnlockSection": "Unlock section", - "CancelToDo": "Cancel Action item" + "CancelToDo": "Cancel Action item", + "Export": "Export", + "Import": "Import" }, "error": { "MethodNotFound": "Method not found: {methodId}", diff --git a/plugins/process-assets/lang/es.json b/plugins/process-assets/lang/es.json index 269b9989c9..8d7e784622 100644 --- a/plugins/process-assets/lang/es.json +++ b/plugins/process-assets/lang/es.json @@ -146,7 +146,9 @@ "Approvers": "Aprobadores", "UnlockCard": "Desbloquear tarjeta", "UnlockSection": "Desbloquear sección", - "CancelToDo": "Cancelar acción" + "CancelToDo": "Cancelar acción", + "Export": "Exportar", + "Import": "Importar" }, "error": { "MethodNotFound": "Método no encontrado: {methodId}", diff --git a/plugins/process-assets/lang/fr.json b/plugins/process-assets/lang/fr.json index 888d8efb65..71abb5ff37 100644 --- a/plugins/process-assets/lang/fr.json +++ b/plugins/process-assets/lang/fr.json @@ -146,7 +146,9 @@ "Approvers": "Approbateurs", "UnlockCard": "Déverrouiller la carte", "UnlockSection": "Déverrouiller la section", - "CancelToDo": "Annuler l'action" + "CancelToDo": "Annuler l'action", + "Export": "Exporter", + "Import": "Importer" }, "error": { "MethodNotFound": "Méthode introuvable : {methodId}", diff --git a/plugins/process-assets/lang/it.json b/plugins/process-assets/lang/it.json index 9aea7e4f80..f8ab40ce42 100644 --- a/plugins/process-assets/lang/it.json +++ b/plugins/process-assets/lang/it.json @@ -146,7 +146,9 @@ "Approvers": "Approvatori", "UnlockCard": "Sblocca scheda", "UnlockSection": "Sblocca sezione", - "CancelToDo": "Annulla azione" + "CancelToDo": "Annulla azione", + "Export": "Esporta", + "Import": "Importa" }, "error": { "MethodNotFound": "Metodo non trovato: {methodId}", diff --git a/plugins/process-assets/lang/ja.json b/plugins/process-assets/lang/ja.json index 8f8ffd4a8d..258fdb2fb7 100644 --- a/plugins/process-assets/lang/ja.json +++ b/plugins/process-assets/lang/ja.json @@ -145,7 +145,9 @@ "Approvers": "承認者", "UnlockCard": "カードのロックを解除", "UnlockSection": "セクションのロックを解除", - "CancelToDo": "アクションをキャンセル" + "CancelToDo": "アクションをキャンセル", + "Export": "エクスポート", + "Import": "インポート" }, "error": { "MethodNotFound": "メソッドが見つかりません: {methodId}", diff --git a/plugins/process-assets/lang/pt-br.json b/plugins/process-assets/lang/pt-br.json index 8c6e3daa54..2cb6b65eb8 100644 --- a/plugins/process-assets/lang/pt-br.json +++ b/plugins/process-assets/lang/pt-br.json @@ -134,7 +134,9 @@ "FieldIsEmpty": "Campo vazio", "UnlockCard": "Desbloquear cartão", "UnlockSection": "Desbloquear seção", - "CancelToDo": "Cancelar ação" + "CancelToDo": "Cancelar ação", + "Export": "Exportar", + "Import": "Importar" }, "error": { "MethodNotFound": "Método não encontrado: {methodId}", diff --git a/plugins/process-assets/lang/pt.json b/plugins/process-assets/lang/pt.json index 483ddbdd1f..9ea9da61fc 100644 --- a/plugins/process-assets/lang/pt.json +++ b/plugins/process-assets/lang/pt.json @@ -146,7 +146,9 @@ "Approvers": "Aprovadores", "UnlockCard": "Desbloquear cartão", "UnlockSection": "Desbloquear seção", - "CancelToDo": "Cancelar ação" + "CancelToDo": "Cancelar ação", + "Export": "Exportar", + "Import": "Importar" }, "error": { "MethodNotFound": "Método não encontrado: {methodId}", diff --git a/plugins/process-assets/lang/ru.json b/plugins/process-assets/lang/ru.json index f439c75f6f..ddf0c2892e 100644 --- a/plugins/process-assets/lang/ru.json +++ b/plugins/process-assets/lang/ru.json @@ -146,7 +146,9 @@ "Approvers": "Утверждающие", "UnlockCard": "Разблокировать карточку", "UnlockSection": "Разблокировать секцию", - "CancelToDo": "Отменить задачу" + "CancelToDo": "Отменить задачу", + "Export": "Экспорт", + "Import": "Импорт" }, "error": { "MethodNotFound": "Метод не найден: {methodId}", diff --git a/plugins/process-assets/lang/tr.json b/plugins/process-assets/lang/tr.json index ce64b07ae3..04efd6cd65 100644 --- a/plugins/process-assets/lang/tr.json +++ b/plugins/process-assets/lang/tr.json @@ -143,7 +143,9 @@ "Approvers": "Onaylayanlar", "UnlockCard": "Kartın kilidini aç", "UnlockSection": "Bölümün kilidini aç", - "CancelToDo": "Eylem öğesini iptal et" + "CancelToDo": "Eylem öğesini iptal et", + "Export": "Dışa aktar", + "Import": "İçe aktar" }, "error": { "MethodNotFound": "Metod bulunamadı: {methodId}", diff --git a/plugins/process-assets/lang/zh.json b/plugins/process-assets/lang/zh.json index 118be0d39b..b909465ca9 100644 --- a/plugins/process-assets/lang/zh.json +++ b/plugins/process-assets/lang/zh.json @@ -146,7 +146,9 @@ "Approvers": "批准人", "UnlockCard": "解锁卡片", "UnlockSection": "解锁部分", - "CancelToDo": "取消任务" + "CancelToDo": "取消任务", + "Export": "导出", + "Import": "导入" }, "error": { "MethodNotFound": "找不到方法:{methodId}", diff --git a/plugins/process-resources/src/components/ProcessesSection.svelte b/plugins/process-resources/src/components/ProcessesSection.svelte index dbd593f796..d83bb92064 100644 --- a/plugins/process-resources/src/components/ProcessesSection.svelte +++ b/plugins/process-resources/src/components/ProcessesSection.svelte @@ -18,9 +18,10 @@ import { translate } from '@hcengineering/platform' import { createQuery, getClient } from '@hcengineering/presentation' import { Process, State } from '@hcengineering/process' - import { ButtonIcon, getCurrentLocation, Icon, IconAdd, Label, navigate } from '@hcengineering/ui' + import { ButtonIcon, getCurrentLocation, Icon, IconAdd, IconFile, IconOpen, Label, navigate } from '@hcengineering/ui' import process from '../plugin' import { makeRank } from '@hcengineering/rank' + import { importProcess } from '../exporter' export let masterTag: MasterTag @@ -76,12 +77,35 @@ loc.path[6] = id navigate(loc, true) } + + function handleImport (): void { + const input = document.createElement('input') + input.type = 'file' + input.accept = '.json' + input.onchange = async (evt) => { + const file = (evt.target as HTMLInputElement).files?.[0] + if (file != null) { + const text = await file.text() + await importProcess(masterTag._id, text) + } + } + input.click() + }
- +
+ + +
{#if processes.length}
diff --git a/plugins/process-resources/src/components/settings/ProcessEditor.svelte b/plugins/process-resources/src/components/settings/ProcessEditor.svelte index e6d5b7e0a3..735ec3928c 100644 --- a/plugins/process-resources/src/components/settings/ProcessEditor.svelte +++ b/plugins/process-resources/src/components/settings/ProcessEditor.svelte @@ -14,7 +14,7 @@ -->
@@ -153,6 +172,13 @@ />
+ import { Ref } from '@hcengineering/core' import { getClient } from '@hcengineering/presentation' - import { Process, State } from '@hcengineering/process' + import { Process } from '@hcengineering/process' import { DropdownLabels, Label } from '@hcengineering/ui' import { createEventDispatcher } from 'svelte' import plugin from '../../plugin' - import { ModeId, Modes } from '../../query' - import { getContext } from '../../utils' - import BaseCriteria from '../criterias/BaseCriteria.svelte' + import { ModeId } from '../../query' import SubProcessStateCriteria from '../criterias/SubProcessStateCriteria.svelte' export let readonly: boolean diff --git a/plugins/process-resources/src/exporter.ts b/plugins/process-resources/src/exporter.ts index 033a84bbcf..30d206d5a5 100644 --- a/plugins/process-resources/src/exporter.ts +++ b/plugins/process-resources/src/exporter.ts @@ -1,9 +1,9 @@ import { type MasterTag } from '@hcengineering/card' -import { type Class, type Doc, type Ref } from '@hcengineering/core' +import core, { type Doc, type Ref, type Class, generateId } from '@hcengineering/core' import { getClient } from '@hcengineering/presentation' import process from './plugin' -export function exportProcess (_id: Ref): { +export function exportProcesses (_id: Ref): { docs: Doc[] required: Array>> } { @@ -12,11 +12,48 @@ export function exportProcess (_id: Ref): { const m = client.getModel() const processes = m.findAllSync(process.class.Process, { masterTag: _id }) for (const proc of processes) { - docs.push(proc) - - docs.push(...m.findAllSync(process.class.State, { process: proc._id })) - - docs.push(...m.findAllSync(process.class.Transition, { process: proc._id })) + const res = exportProcess(proc) + docs.push(...res.docs) } return { docs, required: [] } } + +export function exportProcess (proc: Doc): { + docs: Doc[] + required: Array>> +} { + const docs: Doc[] = [proc] + const client = getClient() + const m = client.getModel() + + docs.push(...m.findAllSync(process.class.State, { process: proc._id as any })) + docs.push(...m.findAllSync(process.class.Transition, { process: proc._id as any })) + + return { docs, required: [] } +} + +export async function importProcess (masterTag: Ref, json: string): Promise { + try { + const data = JSON.parse(json) as Doc[] + if (data[0] === undefined) return + const client = getClient() + const m = client.getModel() + const apply = client.apply('Import process') + ;(data[0] as any).masterTag = masterTag + ;(data[0] as any)._id = generateId() + for (const elem of data) { + if (m.findObject(elem._id) !== undefined) continue + await apply.createDoc(elem._class, core.space.Model, stripData(elem), elem._id) + } + await apply.commit(true) + } catch (e) { + console.error('Failed to import process:', e) + } +} + +function stripData ( + doc: T +): Omit { + const { _id, _class, space, modifiedBy, modifiedOn, createdBy, createdOn, ...rest } = doc + return rest +} diff --git a/plugins/process-resources/src/index.ts b/plugins/process-resources/src/index.ts index 1b9578357d..3c1383f4d2 100644 --- a/plugins/process-resources/src/index.ts +++ b/plugins/process-resources/src/index.ts @@ -12,6 +12,7 @@ // limitations under the License. import { type Resources } from '@hcengineering/platform' +import ApproveRequestPresenter from './components/ApproveRequestPresenter.svelte' import ExecutionContextSelector from './components/attributeEditors/ExecutionContextSelector.svelte' import FunctionSelector from './components/attributeEditors/FunctionSelector.svelte' import NestedContextSelector from './components/attributeEditors/NestedContextSelector.svelte' @@ -24,20 +25,19 @@ import ExecutionMyToDos from './components/ExecutionMyToDos.svelte' import ExecutonPresenter from './components/ExecutonPresenter.svelte' import ExecutonProgressPresenter from './components/ExecutonProgressPresenter.svelte' import Main from './components/Main.svelte' -import CancelToDoEditor from './components/settings/CancelToDoEditor.svelte' -import ApproveRequestPresenter from './components/ApproveRequestPresenter.svelte' import SubProcessPresenter from './components/presenters/SubProcessPresenter.svelte' import ToDoPresenter from './components/presenters/ToDoPresenter.svelte' import UpdateCardPresenter from './components/presenters/UpdateCardPresenter.svelte' import ProcessesCardSection from './components/ProcessesCardSection.svelte' import ProcessesExtension from './components/ProcessesExtension.svelte' -import RequestsCardSection from './components/RequestsCardSection.svelte' -import RequestsExtension from './components/RequestsExtension.svelte' import ProcessesSettingSection from './components/ProcessesSection.svelte' import ProcessPresenter from './components/ProcessPresenter.svelte' +import RequestsCardSection from './components/RequestsCardSection.svelte' +import RequestsExtension from './components/RequestsExtension.svelte' import RunProcessCardPopup from './components/RunProcessCardPopup.svelte' import RunProcessPopup from './components/RunProcessPopup.svelte' import ActionsPresenter from './components/settings/ActionsPresenter.svelte' +import CancelToDoEditor from './components/settings/CancelToDoEditor.svelte' import FunctionSubmenu from './components/settings/FunctionSubmenu.svelte' import ProcessEditor from './components/settings/ProcessEditor.svelte' import StatePresenter from './components/settings/StatePresenter.svelte' @@ -65,21 +65,22 @@ import CreateCardPresenter from './components/presenters/CreateCardPresenter.sve import ProcessesHeaderExtension from './components/ProcessesHeaderExtension.svelte' import AddRelationEditor from './components/settings/AddRelationEditor.svelte' import AddTagEditor from './components/settings/AddTagEditor.svelte' +import ApproveRequestEditor from './components/settings/ApproveRequestEditor.svelte' +import ApproveRequestTriggerEditor from './components/settings/ApproveRequestTriggerEditor.svelte' +import ApproveRequestTriggerPresenter from './components/settings/ApproveRequestTriggerPresenter.svelte' import CardUpdateEditor from './components/settings/CardUpdateEditor.svelte' import CardUpdatePresenter from './components/settings/CardUpdatePresenter.svelte' import CreateCardEditor from './components/settings/CreateCardEditor.svelte' import FieldChangesEditor from './components/settings/FieldChangesEditor.svelte' +import LockSectionEditor from './components/settings/LockSectionEditor.svelte' +import LockSectionPresenter from './components/settings/LockSectionPresenter.svelte' import SubProcessMatchEditor from './components/settings/SubProcessMatchEditor.svelte' import SubProcessMatchPresenter from './components/settings/SubProcessMatchPresenter.svelte' import TimeEditor from './components/settings/TimeEditor.svelte' import TimePresenter from './components/settings/TimePresenter.svelte' import ToDoSettingPresenter from './components/settings/ToDoPresenter.svelte' +import ToDoValuePresenter from './components/settings/ToDoValuePresenter.svelte' import TransitionRefPresenter from './components/settings/TransitionRefPresenter.svelte' -import ApproveRequestEditor from './components/settings/ApproveRequestEditor.svelte' -import ApproveRequestTriggerEditor from './components/settings/ApproveRequestTriggerEditor.svelte' -import ApproveRequestTriggerPresenter from './components/settings/ApproveRequestTriggerPresenter.svelte' -import LockSectionEditor from './components/settings/LockSectionEditor.svelte' -import LockSectionPresenter from './components/settings/LockSectionPresenter.svelte' import UnLockSectionPresenter from './components/settings/UnLockSectionPresenter.svelte' import AppendEditor from './components/transformEditors/AppendEditor.svelte' import CutEditor from './components/transformEditors/CutEditor.svelte' @@ -87,8 +88,7 @@ import ReplaceEditor from './components/transformEditors/ReplaceEditor.svelte' import SplitEditor from './components/transformEditors/SplitEditor.svelte' import NumberPresenter from './components/transformPresenters/NumberPresenter.svelte' import RolePresenter from './components/transformPresenters/RolePresenter.svelte' -import ToDoValuePresenter from './components/settings/ToDoValuePresenter.svelte' -import { exportProcess } from './exporter' +import { exportProcesses, importProcess } from './exporter' import { ProcessMiddleware } from './middleware' import { approveRequestApproved, @@ -207,7 +207,8 @@ export default async (): Promise => ({ ApproveRequestRejected: approveRequestRejected }, function: { - ExportProcess: exportProcess, + ExportProcess: exportProcesses, + ImportProcess: importProcess, ShowDoneQuery: showDoneQuery, CheckProcessSectionVisibility: checkProcessSectionVisibility, CheckRequestsSectionVisibility: checkRequestsSectionVisibility, diff --git a/plugins/process-resources/src/plugin.ts b/plugins/process-resources/src/plugin.ts index 2e0b3429df..7a34bfed55 100644 --- a/plugins/process-resources/src/plugin.ts +++ b/plugins/process-resources/src/plugin.ts @@ -249,7 +249,9 @@ export default mergeIds(processId, process, { LockCard: '' as IntlString, LockSection: '' as IntlString, UnlockCard: '' as IntlString, - UnlockSection: '' as IntlString + UnlockSection: '' as IntlString, + Export: '' as IntlString, + Import: '' as IntlString }, permission: { RunProcess: '' as Ref, diff --git a/server-plugins/process-resources/src/index.ts b/server-plugins/process-resources/src/index.ts index 35c5968778..d18b8498da 100644 --- a/server-plugins/process-resources/src/index.ts +++ b/server-plugins/process-resources/src/index.ts @@ -17,6 +17,7 @@ import cardPlugin, { Card } from '@hcengineering/card' import core, { ArrOf, Doc, + Ref, RefTo, Tx, TxCreateDoc, @@ -41,7 +42,8 @@ import process, { isUpdateTx, ProcessCustomEvent, ApproveRequest, - ExecutionStatus + ExecutionStatus, + Trigger } from '@hcengineering/process' import { QueueTopic, TriggerControl } from '@hcengineering/server-core' import { ProcessMessage } from '@hcengineering/server-process' @@ -138,9 +140,18 @@ export async function OnProcessToDoClose (txes: Tx[], control: TriggerControl): await control.findAll(control.ctx, process.class.ProcessToDo, { _id: updateTx.objectId }, { limit: 1 }) )[0] if (todo === undefined) continue + const events: Ref[] = [process.trigger.OnToDoClose] + if (todo._class === process.class.ApproveRequest) { + const request = todo as ApproveRequest + if (request.approved === true) { + events.push(process.trigger.OnApproveRequestApproved) + } else if (request.approved === false) { + events.push(process.trigger.OnApproveRequestRejected) + } + } await putEventToQueue( { - event: process.trigger.OnToDoClose, + event: events, execution: todo.execution, createdOn: tx.modifiedOn, context: { @@ -151,19 +162,7 @@ export async function OnProcessToDoClose (txes: Tx[], control: TriggerControl): ) if (todo._class === process.class.ApproveRequest) { const request = todo as ApproveRequest - if (request.approved === true) { - await putEventToQueue( - { - event: process.trigger.OnApproveRequestApproved, - execution: todo.execution, - createdOn: tx.modifiedOn, - context: { - todo - } - }, - control - ) - } else if (request.approved === false) { + if (request.approved === false) { // remove all other approve requests for this execution const toRemove = await control.findAll(control.ctx, process.class.ApproveRequest, { group: request.group, @@ -172,17 +171,6 @@ export async function OnProcessToDoClose (txes: Tx[], control: TriggerControl): for (const req of toRemove) { res.push(control.txFactory.createTxRemoveDoc(req._class, req.space, req._id)) } - await putEventToQueue( - { - event: process.trigger.OnApproveRequestRejected, - execution: todo.execution, - createdOn: tx.modifiedOn, - context: { - todo - } - }, - control - ) } } } @@ -199,7 +187,7 @@ export async function OnCustomEvent (txes: Tx[], control: TriggerControl): Promi if (card.length === 0) continue await putEventToQueue( { - event: process.trigger.OnEvent, + event: [process.trigger.OnEvent], execution: customEvent.execution, createdOn: tx.modifiedOn, card: customEvent.card, @@ -222,7 +210,7 @@ export async function OnExecutionCreate (txes: Tx[], control: TriggerControl): P const execution = TxProcessor.createDoc2Doc(createTx) await putEventToQueue( { - event: process.trigger.OnExecutionStart, + event: [process.trigger.OnExecutionStart], execution: execution._id, createdOn: tx.modifiedOn, context: {} @@ -242,7 +230,7 @@ export async function OnProcessToDoRemove (txes: Tx[], control: TriggerControl): if (removedTodo === undefined) continue await putEventToQueue( { - event: process.trigger.OnToDoRemove, + event: [process.trigger.OnToDoRemove], execution: removedTodo.execution, createdOn: tx.modifiedOn, context: { @@ -271,7 +259,7 @@ export async function OnExecutionContinue (txes: Tx[], control: TriggerControl): if (transition === undefined) continue await putEventToQueue( { - event: process.trigger.OnExecutionContinue, + event: [process.trigger.OnExecutionContinue], execution: execution._id, createdOn: tx.modifiedOn, context: {} @@ -404,19 +392,7 @@ export async function OnCardUpdate (txes: Tx[], control: TriggerControl): Promis const ops = isUpdateTx(cudTx) ? cudTx.operations : cudTx.attributes await putEventToQueue( { - event: process.trigger.OnCardUpdate, - card: cudTx.objectId, - createdOn: tx.modifiedOn, - context: { - card: card[0], - operations: ops ?? {} - } - }, - control - ) - await putEventToQueue( - { - event: process.trigger.WhenFieldChanges, + event: [process.trigger.OnCardUpdate, process.trigger.WhenFieldChanges], card: cudTx.objectId, createdOn: tx.modifiedOn, context: { diff --git a/server-plugins/process/src/types.ts b/server-plugins/process/src/types.ts index ab707b9287..16426d218e 100644 --- a/server-plugins/process/src/types.ts +++ b/server-plugins/process/src/types.ts @@ -33,7 +33,7 @@ export type TransformFunc = ( export interface ProcessMessage { account: PersonId createdOn: Timestamp - event: Ref + event: Ref[] context: Record execution?: Ref card?: Ref diff --git a/server-plugins/time-resources/src/index.ts b/server-plugins/time-resources/src/index.ts index 9224cdbe7d..494a84a4b8 100644 --- a/server-plugins/time-resources/src/index.ts +++ b/server-plugins/time-resources/src/index.ts @@ -263,7 +263,8 @@ export async function OnToDoCreate (txes: TxCUD[], control: TriggerControl) if ( !hierarchy.isDerived(objectSpace._class, core.class.SystemSpace) && - !objectSpace.members.includes(currentAcc.uuid) + !objectSpace.members.includes(currentAcc.uuid) && + currentAcc.primarySocialId !== core.account.System ) { continue } diff --git a/services/process/src/main.ts b/services/process/src/main.ts index f1df7cbdf8..83f05eee11 100644 --- a/services/process/src/main.ts +++ b/services/process/src/main.ts @@ -102,15 +102,15 @@ export async function messageHandler (record: ProcessMessage, ws: WorkspaceUuid, } } -function isActiveExecution (execution: Execution, trigger: Ref): boolean { +function isActiveExecution (execution: Execution, trigger: Ref[]): boolean { if (execution.status !== ExecutionStatus.Active) return false - if (execution.error != null && trigger !== process.trigger.OnExecutionContinue) return false + if (execution.error != null && !trigger.includes(process.trigger.OnExecutionContinue)) return false return true } async function processBroadcast (control: ProcessControl, record: ProcessMessage): Promise { const transitions = control.client.getModel().findAllSync(process.class.Transition, { - trigger: record.event + trigger: { $in: record.event } }) for (const transition of transitions) { if (transition.from == null) continue @@ -128,7 +128,7 @@ async function processBroadcast (control: ProcessControl, record: ProcessMessage async function processCardExecutions (control: ProcessControl, record: ProcessMessage): Promise { const transitions = control.client.getModel().findAllSync(process.class.Transition, { - trigger: record.event + trigger: { $in: record.event } }) const states = new Set>() for (const transition of transitions) { @@ -148,7 +148,7 @@ async function processCardExecutions (control: ProcessControl, record: ProcessMe } // we should update timers, probably context value changed - if ([process.trigger.OnCardUpdate, process.trigger.WhenFieldChanges].includes(record.event)) { + if (record.event.some((p) => p === process.trigger.OnCardUpdate || p === process.trigger.WhenFieldChanges)) { await updateTimers(control, record) } } @@ -158,18 +158,18 @@ async function findTransitions ( record: ProcessMessage, execution: Execution ): Promise { - if (record.event === process.trigger.OnExecutionStart) { + if (record.event.includes(process.trigger.OnExecutionStart)) { const transitions = control.client.getModel().findAllSync( process.class.Transition, { process: execution.process, - trigger: record.event + trigger: { $in: record.event } }, { sort: { rank: SortingOrder.Ascending } } ) return await pickTransition(control, execution, transitions, record.context) } - if (record.event === process.trigger.OnExecutionContinue) { + if (record.event.includes(process.trigger.OnExecutionContinue)) { const transition = execution.error?.[0].transition if (transition === undefined) return const res = control.client.getModel().findAllSync(process.class.Transition, { @@ -180,16 +180,20 @@ async function findTransitions ( return res } } - const transitions = control.client.getModel().findAllSync(process.class.Transition, { - from: execution.currentState, - process: execution.process, - trigger: record.event - }) + const transitions = control.client.getModel().findAllSync( + process.class.Transition, + { + from: execution.currentState, + process: execution.process, + trigger: { $in: record.event } + }, + { sort: { rank: SortingOrder.Ascending } } + ) return await pickTransition(control, execution, transitions, record.context) } async function checkToDoResult (control: ProcessControl, record: ProcessMessage, execution: Execution): Promise { - if (record.event !== process.trigger.OnToDoClose) return + if (!record.event.includes(process.trigger.OnToDoClose)) return const todo = record.context.todo as ProcessToDo if (todo === undefined) return if (todo.results == null) return @@ -284,10 +288,10 @@ async function processExecution (control: ProcessControl, record: ProcessMessage } function isRollback (record: ProcessMessage): boolean { - if (record.event === process.trigger.OnEvent && record.context.eventType === 'rollback') { + if (record.event.includes(process.trigger.OnEvent) && record.context.eventType === 'rollback') { return true } - if (record.event === process.trigger.OnToDoRemove && record.context.todo?.withRollback === true) { + if (record.event.includes(process.trigger.OnToDoRemove) && record.context.todo?.withRollback === true) { return true } return false @@ -393,11 +397,23 @@ async function executeTransition ( } else { rollback.push(client.txFactory.createTxRemoveDoc(execution._class, execution.space, execution._id)) } - const card: Card = + let card: Card | undefined = control.cache.get(execution.card) ?? (await control.client.findOne(cardPlugin.class.Card, { _id: execution.card })) if (card !== undefined) { control.cache.set(execution.card, card) + } else if (transition.trigger === process.trigger.OnExecutionStart) { + for (let attempt = 1; attempt < 5; attempt++) { + await new Promise((resolve) => setTimeout(resolve, 1000 * attempt)) + card = await control.client.findOne(cardPlugin.class.Card, { _id: execution.card }) + if (card !== undefined) { + control.cache.set(execution.card, card) + break + } + } + } + if (card === undefined) { + return } const context: Record = {} for (const action of transition.actions) { diff --git a/services/worker/src/activities.ts b/services/worker/src/activities.ts index 96a68b7bd7..e8016f0801 100644 --- a/services/worker/src/activities.ts +++ b/services/worker/src/activities.ts @@ -14,7 +14,7 @@ export async function SendTimeEvent (ws: WorkspaceUuid, _execution: Ref