// // 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. // import cardPlugin, { Card, MasterTag, Tag } from '@hcengineering/card' import core, { Association, Data, Doc, generateId, matchQuery, Ref, Relation, Tx } from '@hcengineering/core' import process, { Execution, ExecutionContext, ExecutionStatus, MethodParams, Process, processError, ProcessToDo } from '@hcengineering/process' import { ExecuteResult, ProcessControl } from '@hcengineering/server-process' import time, { ToDoPriority } from '@hcengineering/time' export function CheckToDo ( control: ProcessControl, execution: Execution, params: Record, context: Record ): boolean { if (params._id === undefined) return false if (context.todo === undefined) return false return context.todo._id === params._id } export async function CheckSubProcessesDone (control: ProcessControl, execution: Execution): Promise { const res = control.client.findOne(process.class.Execution, { parentId: execution._id, status: ExecutionStatus.Active }) return res === undefined } export function OnCardUpdateCheck ( control: ProcessControl, execution: Execution, params: Record, context: Record ): boolean { if (context.card === undefined) return false const res = matchQuery([context.card], params, context.card._class, control.client.getHierarchy(), true) return res.length > 0 } export function CheckTime (control: ProcessControl, execution: Execution, params: Record): boolean { if (params.value === undefined) return false return params.value <= Date.now() } export async function AddRelation ( params: MethodParams, execution: Execution, control: ProcessControl ): Promise { const _id = generateId() const association = params.association as Ref if (isEmpty(association)) { throw processError(process.error.RequiredParamsNotProvided, { params: 'association' }) } if (isEmpty(params._id)) { throw processError(process.error.RequiredParamsNotProvided, { params: '_id' }) } if (isEmpty(params.direction)) { throw processError(process.error.RequiredParamsNotProvided, { params: 'direction' }) } const targetId = params._id as Ref const direction = params.direction as 'A' | 'B' const docA = direction === 'A' ? targetId : execution.card const docB = direction === 'A' ? execution.card : targetId const data: Data = { association, docA, docB } const res: Tx[] = [control.client.txFactory.createTxCreateDoc(core.class.Relation, core.space.Workspace, data, _id)] const rollback: Tx[] = [control.client.txFactory.createTxRemoveDoc(core.class.Relation, core.space.Workspace, _id)] return { txes: res, rollback, context: _id } } export async function UpdateCard ( params: MethodParams, execution: Execution, control: ProcessControl ): Promise { if (Object.keys(params).length === 0) throw processError(process.error.RequiredParamsNotProvided, { params: 'ANY' }) const target = control.cache.get(execution.card) if (target === undefined) throw processError(process.error.ObjectNotFound, { _id: execution.card }) const update: Record = {} const prevValue: Record = {} for (const key in params) { prevValue[key] = target[key] update[key] = (params as any)[key] } const res: Tx[] = [control.client.txFactory.createTxUpdateDoc(target._class, target.space, target._id, update)] const rollback: Tx[] = [ control.client.txFactory.createTxUpdateDoc(target._class, target.space, target._id, prevValue) ] return { txes: res, rollback, context: null } } export async function AddTag ( params: MethodParams, execution: Execution, control: ProcessControl ): Promise { const { _id, props } = params if (_id === undefined) throw processError(process.error.RequiredParamsNotProvided, { params: '_id' }) const res: Tx[] = [] const context: Ref[] = [execution.card] const _process = control.client.getModel().findObject(execution.process) if (_process === undefined) throw processError(process.error.ObjectNotFound, { _id: execution.process }) res.push( control.client.txFactory.createTxMixin( execution.card, _process.masterTag, core.space.Workspace, _id as Ref, props ) ) return { txes: res, rollback: undefined, context } } export async function RunSubProcess ( params: MethodParams, execution: Execution, control: ProcessControl ): Promise { if (params._id === undefined) throw processError(process.error.RequiredParamsNotProvided, { params: '_id' }) const card = params.card ?? execution.card const processId = params._id as Ref const target = control.client.getModel().findObject(processId) if (target === undefined) throw processError(process.error.ObjectNotFound, { _id: processId }) const res: Tx[] = [] const resultContext: Ref[] = [] for (const _card of Array.isArray(card) ? card : [card]) { if (target.parallelExecutionForbidden === true) { const currentExecution = await control.client.findAll(process.class.Execution, { process: target._id, card: _card, done: false }) if (currentExecution.length > 0) { // todo, show erro after merge another pr continue } } const initTransition = control.client .getModel() .findAllSync(process.class.Transition, { process: target._id, from: null })[0] // eslint-disable-next-line @typescript-eslint/consistent-type-assertions const context = params.context ?? ({} as ExecutionContext) const id = generateId() res.push( control.client.txFactory.createTxCreateDoc( process.class.Execution, core.space.Workspace, { process: processId, currentState: initTransition.to, card: _card, context, status: ExecutionStatus.Active, rollback: [], parentId: execution._id }, id ) ) resultContext.push(id) } return { txes: res, rollback: undefined, context: resultContext } } export async function CreateToDo ( params: MethodParams, execution: Execution, control: ProcessControl ): Promise { for (const key in { user: params.user, title: params.title }) { const val = (params as any)[key] if (isEmpty(val)) { throw processError(process.error.RequiredParamsNotProvided, { params: key }) } } if (params.user === undefined || params.title === undefined) return { txes: [], rollback: [], context: null } const res: Tx[] = [] const rollback: Tx[] = [] const id = generateId() res.push( control.client.txFactory.createTxCreateDoc( process.class.ProcessToDo, time.space.ToDos, { attachedTo: execution.card, attachedToClass: cardPlugin.class.Card, collection: 'todos', workslots: 0, execution: execution._id, title: params.title, user: params.user, description: params.description ?? '', dueDate: params.dueDate, priority: params.priority ?? ToDoPriority.NoPriority, visibility: 'public', doneOn: null, rank: '', withRollback: params.withRollback ?? false }, id ) ) return { txes: res, rollback, context: id } } export async function CreateCard ( params: MethodParams, execution: Execution, control: ProcessControl ): Promise { const { _class, title, ...attrs } = params for (const key in { _class, title }) { const val = (params as any)[key] if (isEmpty(val)) { throw processError(process.error.RequiredParamsNotProvided, { params: key }) } } const _id = generateId() const data = { title, ...attrs } as any const res: Tx[] = [control.client.txFactory.createTxCreateDoc(_class as Ref, execution.space, data, _id)] const rollback: Tx[] = [control.client.txFactory.createTxRemoveDoc(_class as Ref, execution.space, _id)] return { txes: res, rollback, context: _id } } function isEmpty (value: any): boolean { return value === undefined || value === null || (typeof value === 'string' && value.trim() === '') }