From 4612b2c7db8b37dc44e5af4dd4dc067abb5a4392 Mon Sep 17 00:00:00 2001 From: Denis Bykhov Date: Thu, 26 Feb 2026 18:05:22 +0500 Subject: [PATCH] Fix process concurrency Signed-off-by: Denis Bykhov --- .../src/components/RunProcessCardPopup.svelte | 3 +- .../src/components/RunProcessPopup.svelte | 3 +- plugins/process-resources/src/middleware.ts | 122 +++++++++++++----- plugins/process-resources/src/utils.ts | 39 +++--- 4 files changed, 118 insertions(+), 49 deletions(-) diff --git a/plugins/process-resources/src/components/RunProcessCardPopup.svelte b/plugins/process-resources/src/components/RunProcessCardPopup.svelte index 4e49c4e3aa..0559c6800c 100644 --- a/plugins/process-resources/src/components/RunProcessCardPopup.svelte +++ b/plugins/process-resources/src/components/RunProcessCardPopup.svelte @@ -41,7 +41,8 @@ async function runProcess (): Promise { if (process === undefined || card === undefined) return - await createExecution(card._id, process, card.space) + const tx = await createExecution(card._id, process, card.space) + if (tx) await client.tx(tx) dispatch('close') } diff --git a/plugins/process-resources/src/components/RunProcessPopup.svelte b/plugins/process-resources/src/components/RunProcessPopup.svelte index b526f7cc8c..026609023a 100644 --- a/plugins/process-resources/src/components/RunProcessPopup.svelte +++ b/plugins/process-resources/src/components/RunProcessPopup.svelte @@ -94,7 +94,8 @@ async function runProcess (_id: Ref): Promise { if (!value) return for (const element of values) { - await createExecution(element._id, _id, element.space) + const tx = await createExecution(element._id, _id, element.space) + if (tx) await client.tx(tx) } dispatch('close') } diff --git a/plugins/process-resources/src/middleware.ts b/plugins/process-resources/src/middleware.ts index dc7d8a0c27..ccad232938 100644 --- a/plugins/process-resources/src/middleware.ts +++ b/plugins/process-resources/src/middleware.ts @@ -13,14 +13,17 @@ import cardPlugin, { type Card } from '@hcengineering/card' import core, { + generateId, getCurrentAccount, SortingOrder, TxOperations, TxProcessor, type Client, + type Doc, type Tx, type TxApplyIf, type TxCreateDoc, + type TxCUD, type TxMixin, type TxResult, type TxUpdateDoc @@ -51,26 +54,43 @@ export class ProcessMiddleware extends BasePresentationMiddleware implements Pre } async tx (tx: Tx): Promise { - await this.handleTx(tx) + const extraTx: Array> = [] + await this.handleTx(extraTx, tx) + + if (extraTx.length > 0) { + if (TxProcessor.isExtendsCUD(tx._class)) { + const applyIf = new TxOperations(this.client, getCurrentAccount().primarySocialId).txFactory.createTxApplyIf( + core.space.Tx, + generateId(), + [], + [], + [tx as TxCUD, ...extraTx], + 'process', + true + ) + return await this.provideTx(applyIf) + } + } + return await this.provideTx(tx) } - private async handleTx (...txes: Tx[]): Promise { + private async handleTx (extraTx: Array>, ...txes: Tx[]): Promise { for (const etx of txes) { if (etx._class === core.class.TxApplyIf) { const applyIf = etx as TxApplyIf - await this.handleTx(...applyIf.txes) + await this.handleTx(extraTx, ...applyIf.txes) } - await this.handleCardCreate(etx) - await this.handleCardUpdate(etx) - await this.handleTagAdd(etx) - await this.handleToDoDone(etx) - await this.handleApproveRequest(etx) + await this.handleCardCreate(extraTx, etx) + await this.handleCardUpdate(extraTx, etx) + await this.handleTagAdd(extraTx, etx) + await this.handleToDoDone(extraTx, etx) + await this.handleApproveRequest(extraTx, etx) } } - private async handleCardUpdate (etx: Tx): Promise { + private async handleCardUpdate (extraTx: Array>, etx: Tx): Promise { if (etx._class === core.class.TxUpdateDoc || etx._class === core.class.TxMixin) { const updateTx = etx as TxUpdateDoc | TxMixin const hierarchy = this.client.getHierarchy() @@ -103,15 +123,23 @@ export class ProcessMiddleware extends BasePresentationMiddleware implements Pre }) if (transition === undefined) return const context = await getNextStateUserInput(execution, transition, execution.context) - const txop = new TxOperations(this.client, getCurrentAccount().primarySocialId) - await txop.update(execution, { - context - }) + if (context !== undefined) { + extraTx.push( + new TxOperations(this.client, getCurrentAccount().primarySocialId).txFactory.createTxUpdateDoc( + execution._class, + execution.space, + execution._id, + { + context + } + ) + ) + } } } } - private async handleCardCreate (etx: Tx): Promise { + private async handleCardCreate (extraTx: Array>, etx: Tx): Promise { if (etx._class === core.class.TxCreateDoc) { const createTx = etx as TxCreateDoc const doc = TxProcessor.createDoc2Doc(createTx) @@ -130,12 +158,13 @@ export class ProcessMiddleware extends BasePresentationMiddleware implements Pre autoStart: true }) for (const proc of processes) { - await createExecution(createTx.objectId, proc._id, createTx.objectSpace) + const res = await createExecution(createTx.objectId, proc._id, createTx.objectSpace) + if (res !== undefined) extraTx.push(res) } } } - private async handleTagAdd (tx: Tx): Promise { + private async handleTagAdd (extraTx: Array>, tx: Tx): Promise { if (tx._class !== core.class.TxMixin) return const mixinTx = tx as TxMixin const hierarchy = this.client.getHierarchy() @@ -146,11 +175,12 @@ export class ProcessMiddleware extends BasePresentationMiddleware implements Pre .getModel() .findAllSync(process.class.Process, { masterTag: mixinTx.mixin, autoStart: true }) for (const proc of processes) { - await createExecution(mixinTx.objectId, proc._id, mixinTx.objectSpace) + const res = await createExecution(mixinTx.objectId, proc._id, mixinTx.objectSpace) + if (res !== undefined) extraTx.push(res) } } - private async handleApproveRequest (etx: Tx): Promise { + private async handleApproveRequest (extraTx: Array>, etx: Tx): Promise { if (etx._class === core.class.TxUpdateDoc) { const cud = etx as TxUpdateDoc if (cud.objectClass !== process.class.ApproveRequest) return @@ -163,7 +193,6 @@ export class ProcessMiddleware extends BasePresentationMiddleware implements Pre _id: approveRequest.execution }) if (execution === undefined) return - const txop = new TxOperations(this.client, getCurrentAccount().primarySocialId) const transitions = this.client.getModel().findAllSync( process.class.Transition, { @@ -181,13 +210,22 @@ export class ProcessMiddleware extends BasePresentationMiddleware implements Pre }) if (transition === undefined) return const context = await getNextStateUserInput(execution, transition, execution.context) - await txop.update(execution, { - context - }) + if (context !== undefined) { + extraTx.push( + new TxOperations(this.client, getCurrentAccount().primarySocialId).txFactory.createTxUpdateDoc( + execution._class, + execution.space, + execution._id, + { + context + } + ) + ) + } } } - private async handleToDoDone (etx: Tx): Promise { + private async handleToDoDone (extraTx: Array>, etx: Tx): Promise { if (etx._class === core.class.TxUpdateDoc) { const cud = etx as TxUpdateDoc if (cud.objectClass !== process.class.ProcessToDo) return @@ -200,8 +238,9 @@ export class ProcessMiddleware extends BasePresentationMiddleware implements Pre _id: todo.execution }) if (execution === undefined) return - const txop = new TxOperations(this.client, getCurrentAccount().primarySocialId) - await requestResult(txop, execution, todo.results, execution.context) + + const context = await requestResult(execution, todo.results, execution.context) + const transitions = this.client.getModel().findAllSync(process.class.Transition, { process: execution.process, from: execution.currentState, @@ -210,12 +249,33 @@ export class ProcessMiddleware extends BasePresentationMiddleware implements Pre const transition = await pickTransition(this.client, execution, transitions, { todo }) - if (transition === undefined) return - const context = await getNextStateUserInput(execution, transition, execution.context) - if (context !== undefined) { - await txop.update(execution, { - context - }) + if (transition === undefined) { + if (context !== undefined) { + extraTx.push( + new TxOperations(this.client, getCurrentAccount().primarySocialId).txFactory.createTxUpdateDoc( + execution._class, + execution.space, + execution._id, + { + context + } + ) + ) + } + return + } + const finalContext = (await getNextStateUserInput(execution, transition, context ?? execution.context)) ?? context + if (finalContext !== undefined) { + extraTx.push( + new TxOperations(this.client, getCurrentAccount().primarySocialId).txFactory.createTxUpdateDoc( + execution._class, + execution.space, + execution._id, + { + context: finalContext + } + ) + ) } } } diff --git a/plugins/process-resources/src/utils.ts b/plugins/process-resources/src/utils.ts index 6437ebce6f..c66493076c 100644 --- a/plugins/process-resources/src/utils.ts +++ b/plugins/process-resources/src/utils.ts @@ -22,13 +22,15 @@ import core, { type DocumentQuery, type DocumentUpdate, generateId, + getCurrentAccount, matchQuery, type Ref, type RefTo, type Space, - type TxOperations, + TxOperations, TxProcessor, - type Type + type Type, + type TxCUD } from '@hcengineering/core' import { getResource, type IntlString, PlatformError, Severity, Status } from '@hcengineering/platform' import { getClient } from '@hcengineering/presentation' @@ -545,7 +547,11 @@ export async function getNextStateUserInput ( return await requestUserInput(execution.process, execution.space, transition, userContext) } -export async function createExecution (card: Ref, _id: Ref, space: Ref): Promise { +export async function createExecution ( + card: Ref, + _id: Ref, + space: Ref +): Promise | undefined> { const client = getClient() // eslint-disable-next-line @typescript-eslint/consistent-type-assertions const context = await newExecutionUserInput(_id, space) @@ -556,14 +562,18 @@ export async function createExecution (card: Ref, _id: Ref, space from: null })[0] if (initTransition === undefined) return - await client.createDoc(process.class.Execution, space, { - process: _id, - currentState: initTransition.to, - card, - rollback: [], - context: context ?? getEmptyContext(), - status: ExecutionStatus.Active - }) + return new TxOperations(client, getCurrentAccount().primarySocialId).txFactory.createTxCreateDoc( + process.class.Execution, + space, + { + process: _id, + currentState: initTransition.to, + card, + rollback: [], + context: context ?? getEmptyContext(), + status: ExecutionStatus.Active + } + ) } export function getToDoEndAction (prevState: State): Step { @@ -590,11 +600,10 @@ export function getToDoEndAction (prevState: State): Step { } export async function requestResult ( - txop: TxOperations, execution: Execution, results: UserResult[] | undefined, context: ExecutionContext -): Promise { +): Promise { if (results == null || results.length === 0) return const promise = new Promise((resolve, reject) => { showPopup(process.component.ResultInput, { results, context }, undefined, (res) => { @@ -610,9 +619,7 @@ export async function requestResult ( }) }) await promise - await txop.update(execution, { - context - }) + return context } export function todoTranstionCheck (