Merge branch 'develop' into staging-new

This commit is contained in:
Denis Bykhov
2026-02-08 19:02:11 +05:00
33 changed files with 230 additions and 42 deletions
+28 -3
View File
@@ -38,10 +38,9 @@ export function defineMethods (builder: Builder): void {
label: process.string.RequestApproval,
objectClass: process.class.ApproveRequest,
editor: process.component.ApproveRequestEditor,
presenter: process.component.ApproveRequestPresenter,
presenter: process.component.ToDoPresenter,
createdContext: {
_class: process.class.ApproveRequest,
nameField: 'title'
_class: process.class.ApproveRequest
},
requiredParams: ['user']
},
@@ -146,4 +145,30 @@ export function defineMethods (builder: Builder): void {
},
process.method.LockSection
)
builder.createDoc(
process.class.Method,
core.space.Model,
{
label: process.string.UnlockCard,
objectClass: card.class.Card,
requiredParams: [],
createdContext: null
},
process.method.UnlockCard
)
builder.createDoc(
process.class.Method,
core.space.Model,
{
label: process.string.UnlockSection,
objectClass: card.class.Card,
editor: process.component.LockSectionEditor,
presenter: process.component.UnLockSectionPresenter,
requiredParams: ['_id'],
createdContext: null
},
process.method.UnlockSection
)
}
+12 -6
View File
@@ -199,13 +199,15 @@ export class TExecution extends TDoc implements Execution {
@Model(process.class.ProcessToDo, time.class.ToDo)
@UX(process.string.ToDo)
export class TProcessToDo extends TToDo implements ProcessToDo {
execution!: Ref<Execution>
@Prop(TypeRef(process.class.Execution), process.string.Execution)
execution!: Ref<Execution>
@Prop(TypeBoolean(), process.string.Rollback)
withRollback!: boolean
}
@Model(process.class.ApproveRequest, process.class.ProcessToDo)
@UX(process.string.ApproveRequest)
export class TApproveRequest extends TProcessToDo implements ApproveRequest {
@Prop(TypeBoolean(), process.string.IsApproved)
approved?: boolean
@@ -461,7 +463,7 @@ export function createModel (builder: Builder): void {
baseMenuClass: process.class.ApproveRequest
},
viewOptions: {
groupBy: ['user', 'approved'],
groupBy: ['user', 'approved', 'execution'],
orderBy: [
['approved', SortingOrder.Descending],
['modifiedOn', SortingOrder.Descending],
@@ -473,14 +475,18 @@ export function createModel (builder: Builder): void {
strict: true
},
config: [
'user',
{
key: '',
presenter: view.component.GrowPresenter,
displayProps: { grow: true }
},
'reason',
{
key: '',
label: process.string.ApproveRequest,
presenter: process.component.ApproveRequestPresenter
},
'user',
'approved',
'reason'
}
]
},
process.viewlet.CardRequests
+8
View File
@@ -139,6 +139,14 @@ export function createModel (builder: Builder): void {
func: serverProcess.func.LockSection
})
builder.mixin(process.method.UnlockCard, process.class.Method, serverProcess.mixin.MethodImpl, {
func: serverProcess.func.UnlockCard
})
builder.mixin(process.method.UnlockSection, process.class.Method, serverProcess.mixin.MethodImpl, {
func: serverProcess.func.UnlockSection
})
builder.mixin(process.function.FirstValue, process.class.ProcessFunction, serverProcess.mixin.FuncImpl, {
func: serverProcess.transform.FirstValue
})
+4 -1
View File
@@ -137,7 +137,10 @@
"FieldIsEmpty": "Pole je prázdné",
"Reviewers": "Recenzenti",
"LockCard": "Zamknout kartu",
"LockSection": "Zamknout sekci"
"LockSection": "Zamknout sekci",
"Approvers": "Schvalovatelé",
"UnlockCard": "Odemknout kartu",
"UnlockSection": "Odemknout sekci"
},
"error": {
"MethodNotFound": "Metoda nenalezena: {methodId}",
+4 -1
View File
@@ -137,7 +137,10 @@
"FieldIsEmpty": "Feld ist leer",
"Reviewers": "Prüfer",
"LockCard": "Karte sperren",
"LockSection": "Abschnitt sperren"
"LockSection": "Abschnitt sperren",
"Approvers": "Genehmiger",
"UnlockCard": "Karte entsperren",
"UnlockSection": "Abschnitt entsperren"
},
"error": {
"MethodNotFound": "Methode nicht gefunden: {methodId}",
+4 -1
View File
@@ -142,7 +142,10 @@
"FieldIsEmpty": "Field is empty",
"Reviewers": "Reviewers",
"LockCard": "Lock card",
"LockSection": "Lock section"
"LockSection": "Lock section",
"Approvers": "Approvers",
"UnlockCard": "Unlock card",
"UnlockSection": "Unlock section"
},
"error": {
"MethodNotFound": "Method not found: {methodId}",
+4 -1
View File
@@ -142,7 +142,10 @@
"FieldIsEmpty": "Campo vacío",
"Reviewers": "Revisores",
"LockCard": "Bloquear tarjeta",
"LockSection": "Bloquear sección"
"LockSection": "Bloquear sección",
"Approvers": "Aprobadores",
"UnlockCard": "Desbloquear tarjeta",
"UnlockSection": "Desbloquear sección"
},
"error": {
"MethodNotFound": "Método no encontrado: {methodId}",
+4 -1
View File
@@ -142,7 +142,10 @@
"FieldIsEmpty": "Le champ est vide",
"Reviewers": "Relecteurs",
"LockCard": "Verrouiller la carte",
"LockSection": "Verrouiller la section"
"LockSection": "Verrouiller la section",
"Approvers": "Approbateurs",
"UnlockCard": "Déverrouiller la carte",
"UnlockSection": "Déverrouiller la section"
},
"error": {
"MethodNotFound": "Méthode introuvable : {methodId}",
+4 -1
View File
@@ -142,7 +142,10 @@
"FieldIsEmpty": "Campo vuoto",
"Reviewers": "Revisori",
"LockCard": "Blocca scheda",
"LockSection": "Blocca sezione"
"LockSection": "Blocca sezione",
"Approvers": "Approvatori",
"UnlockCard": "Sblocca scheda",
"UnlockSection": "Sblocca sezione"
},
"error": {
"MethodNotFound": "Metodo non trovato: {methodId}",
+4 -1
View File
@@ -141,7 +141,10 @@
"FieldIsEmpty": "フィールドが空です",
"Reviewers": "レビュー担当者",
"LockCard": "カードをロック",
"LockSection": "セクションをロック"
"LockSection": "セクションをロック",
"Approvers": "承認者",
"UnlockCard": "カードのロックを解除",
"UnlockSection": "セクションのロックを解除"
},
"error": {
"MethodNotFound": "メソッドが見つかりません: {methodId}",
+8 -1
View File
@@ -126,7 +126,14 @@
"RunProcessPermission": "Executar processo",
"CancelProcessPermission": "Cancelar processo",
"ForbidRunProcessPermission": "Proibir execução do processo",
"ForbidCancelProcessPermission": "Proibir cancelamento do processo"
"ForbidCancelProcessPermission": "Proibir cancelamento do processo",
"Approvers": "Aprovadores",
"LockCard": "Bloquear cartão",
"LockSection": "Bloquear seção",
"Reviewers": "Revisores",
"FieldIsEmpty": "Campo vazio",
"UnlockCard": "Desbloquear cartão",
"UnlockSection": "Desbloquear seção"
},
"error": {
"MethodNotFound": "Método não encontrado: {methodId}",
+4 -1
View File
@@ -142,7 +142,10 @@
"FieldIsEmpty": "Campo está vazio",
"Reviewers": "Revisores",
"LockCard": "Bloquear cartão",
"LockSection": "Bloquear seção"
"LockSection": "Bloquear seção",
"Approvers": "Aprovadores",
"UnlockCard": "Desbloquear cartão",
"UnlockSection": "Desbloquear seção"
},
"error": {
"MethodNotFound": "Método não encontrado: {methodId}",
+4 -1
View File
@@ -142,7 +142,10 @@
"FieldIsEmpty": "Поле пустое",
"Reviewers": "Рецензенты",
"LockCard": "Заблокировать карточку",
"LockSection": "Заблокировать секцию"
"LockSection": "Заблокировать секцию",
"Approvers": "Утверждающие",
"UnlockCard": "Разблокировать карточку",
"UnlockSection": "Разблокировать секцию"
},
"error": {
"MethodNotFound": "Метод не найден: {methodId}",
+4 -1
View File
@@ -139,7 +139,10 @@
"FieldIsEmpty": "Alan boş",
"Reviewers": "Gözden geçirenler",
"LockCard": "Kartı kilitle",
"LockSection": "Bölümü kilitle"
"LockSection": "Bölümü kilitle",
"Approvers": "Onaylayanlar",
"UnlockCard": "Kartın kilidini aç",
"UnlockSection": "Bölümün kilidini aç"
},
"error": {
"MethodNotFound": "Metod bulunamadı: {methodId}",
+4 -1
View File
@@ -142,7 +142,10 @@
"FieldIsEmpty": "字段为空",
"Reviewers": "审查员",
"LockCard": "锁定卡片",
"LockSection": "锁定部分"
"LockSection": "锁定部分",
"Approvers": "批准人",
"UnlockCard": "解锁卡片",
"UnlockSection": "解锁部分"
},
"error": {
"MethodNotFound": "找不到方法:{methodId}",
@@ -38,7 +38,8 @@
await client.update(todo, {
doneOn: new Date().getTime(),
approved: !isRejection
approved: !isRejection,
reason: isRejection ? rejectionNote : undefined
})
})
}
@@ -14,9 +14,18 @@
-->
<script lang="ts">
import { getEmbeddedLabel } from '@hcengineering/platform'
import { ApproveRequest } from '@hcengineering/process'
import { tooltip } from '@hcengineering/ui'
import { BooleanPresenter } from '@hcengineering/view-resources'
export let value: ApproveRequest
</script>
{value.title}
<div
use:tooltip={value.approved !== undefined
? { label: getEmbeddedLabel(value.user + ' ' + new Date(value.modifiedOn).toLocaleString()) }
: undefined}
>
<BooleanPresenter value={value.approved} />
</div>
@@ -56,6 +56,7 @@
{#each todos as todo (todo._id)}
{#if isRequest(todo)}
<ApproveRequestButtons {todo} card={value.card} />
{:else}
<Button label={getEmbeddedLabel(todo.title)} on:click={() => checkTodo(todo)} />
{/if}
<Button label={getEmbeddedLabel(todo.title)} on:click={() => checkTodo(todo)} />
{/each}
@@ -113,6 +113,7 @@
{#if viewOptions && viewlet}
<List
bind:this={list}
readonly={true}
_class={process.class.ApproveRequest}
{viewOptions}
baseMenuClass={process.class.ApproveRequest}
@@ -41,7 +41,7 @@
}
}
const keys = ['title', 'dueDate']
const keys = ['dueDate']
$: value = params.user
@@ -55,7 +55,7 @@
}
}
const attribute = getMockAttribute(plugin.class.ApproveRequest, plugin.string.Reviewers, type)
const attribute = getMockAttribute(plugin.class.ApproveRequest, plugin.string.Approvers, type)
const presenterClass = getAttributePresenterClass(hierarchy, attribute.type)
$: context = getContext(client, process, presenterClass.attrClass, presenterClass.category)
@@ -105,7 +105,7 @@
align-items: center;
row-gap: 0.5rem;
column-gap: 1rem;
margin: 0.25rem 2rem 0;
margin: 0.25rem 2rem 0.5rem;
width: calc(100% - 4rem);
height: min-content;
}
@@ -15,19 +15,18 @@
<script lang="ts">
import { getClient } from '@hcengineering/presentation'
import { Process } from '@hcengineering/process'
import { Label } from '@hcengineering/ui'
import plugin from '../../plugin'
export let process: Process
export let params: Record<string, any>
export let unlock: boolean = false
const client = getClient()
$: _class = client.getHierarchy().findClass(params._id)
</script>
<Label label={plugin.string.LockSection} />:
<Label label={unlock ? plugin.string.UnlockSection : plugin.string.LockSection} />:
{#if _class !== undefined}
<Label label={_class.label} />
{/if}
@@ -0,0 +1,22 @@
<!--
// Copyright © 2026 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.
-->
<script lang="ts">
import LockSectionPresenter from './LockSectionPresenter.svelte'
export let params: Record<string, any>
</script>
<LockSectionPresenter {params} unlock />
+3 -1
View File
@@ -79,6 +79,7 @@ import ApproveRequestTriggerEditor from './components/settings/ApproveRequestTri
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'
import ReplaceEditor from './components/transformEditors/ReplaceEditor.svelte'
@@ -167,7 +168,8 @@ export default async (): Promise<Resources> => ({
ApproveRequestTriggerEditor,
ApproveRequestTriggerPresenter,
LockSectionPresenter,
LockSectionEditor
LockSectionEditor,
UnLockSectionPresenter
},
criteriaEditor: {
BaseCriteria,
+6 -2
View File
@@ -86,7 +86,8 @@ export default mergeIds(processId, process, {
SubProcessMatchPresenter: '' as AnyComponent,
FunctionSubmenu: '' as AnyComponent,
LockSectionEditor: '' as AnyComponent,
LockSectionPresenter: '' as AnyComponent
LockSectionPresenter: '' as AnyComponent,
UnLockSectionPresenter: '' as AnyComponent
},
criteriaEditor: {
BaseCriteria: '' as AnyComponent,
@@ -241,8 +242,11 @@ export default mergeIds(processId, process, {
ProvideRejectionReason: '' as IntlString,
FieldIsEmpty: '' as IntlString,
Reviewers: '' as IntlString,
Approvers: '' as IntlString,
LockCard: '' as IntlString,
LockSection: '' as IntlString
LockSection: '' as IntlString,
UnlockCard: '' as IntlString,
UnlockSection: '' as IntlString
},
permission: {
RunProcess: '' as Ref<Permission>,
+3 -1
View File
@@ -247,7 +247,9 @@ export default plugin(processId, {
AddTag: '' as Ref<Method<Tag>>,
RequestApproval: '' as Ref<Method<ApproveRequest>>,
LockCard: '' as Ref<Method<Card>>,
LockSection: '' as Ref<Method<Card>>
LockSection: '' as Ref<Method<Card>>,
UnlockCard: '' as Ref<Method<Card>>,
UnlockSection: '' as Ref<Method<Card>>
},
trigger: {
OnCardUpdate: '' as Ref<Trigger>, // in fact WhenCardMatches, should migrate in future
@@ -54,6 +54,7 @@
export let compactMode: boolean = false
export let listProvider: SelectionFocusProvider
export let singleCategoryLimit: number | undefined = undefined
export let readonly: boolean = false
const limiter = new RateLimiter(10)
@@ -240,6 +241,7 @@
on:collapsed
{resultQuery}
{resultOptions}
{readonly}
/>
</div>
@@ -87,6 +87,7 @@
export let limiter: RateLimiter
export let listProvider: SelectionFocusProvider
export let singleCategoryLimit: number | undefined = undefined
export let readonly: boolean = false
$: groupByKey = viewOptions.groupBy[level] ?? noCategory
let categories: CategoryType[] = []
@@ -420,6 +421,7 @@
{resultQuery}
{resultOptions}
{limiter}
{readonly}
{listProvider}
on:check
on:uncheckAll
@@ -481,6 +483,7 @@
{resultQuery}
{resultOptions}
{limiter}
{readonly}
{listProvider}
bind:dragItem
on:dragItem
@@ -90,6 +90,7 @@
export let limiter: RateLimiter
export let listProvider: SelectionFocusProvider
export let singleCategoryLimit = 50
export let readonly: boolean = false
$: lastLevel = level + 1 >= viewOptions.groupBy.length
@@ -515,6 +516,7 @@
{groupPersistKey}
{viewOptionsConfig}
{listDiv}
{readonly}
dragItem
dragstart={dragStartHandler}
/>
@@ -529,6 +531,7 @@
model={getDocItemModel(Hierarchy.mixinOrClass(docObject))}
{groupByKey}
selected={isSelected(docObject, $focusStore)}
{readonly}
checked={selectedObjectIdsSet.has(docObject._id)}
last={i === limited.length - 1 && HLimited >= itemProj.length}
lastCat={i === limited.length - 1 && (oneCat || lastCat) && HLimited >= itemProj.length}
@@ -32,6 +32,7 @@
export let lastCat: boolean = false
export let props: Record<string, any> = {}
export let compactMode: boolean = false
export let readonly: boolean = false
export function scroll () {
elem?.scrollIntoView({ behavior: 'auto', block: 'nearest' })
@@ -133,6 +134,7 @@
{docObject}
attributeModel={attrModel}
{props}
{readonly}
{compactMode}
value={getObjectValue(attrModel.key, docObject)}
onChange={getOnChange(docObject, attrModel)}
@@ -149,6 +151,7 @@
{props}
value={getObjectValue(attrModel.key, docObject)}
onChange={getOnChange(docObject, attrModel)}
{readonly}
on:resize={(e) => {
if (e.detail == null) return
sizes.set(index, e.detail)
@@ -161,6 +164,7 @@
<ListPresenter
{docObject}
attributeModel={attrModel}
{readonly}
{props}
value={getObjectValue(attrModel.key, docObject)}
onChange={getOnChange(docObject, attrModel)}
@@ -172,6 +176,7 @@
{docObject}
{attributeModel}
{props}
{readonly}
value={getObjectValue(attributeModel.key, docObject)}
onChange={getOnChange(docObject, attributeModel)}
hideDivider={i === 0}
@@ -201,6 +206,7 @@
{docObject}
attributeModel={attrModel}
{props}
{readonly}
{compactMode}
value={getObjectValue(attrModel.key, docObject)}
onChange={getOnChange(docObject, attrModel)}
@@ -216,6 +222,7 @@
{docObject}
{attributeModel}
{props}
{readonly}
value={getObjectValue(attributeModel.key, docObject)}
onChange={getOnChange(docObject, attributeModel)}
hideDivider={j === 0}
@@ -234,6 +241,7 @@
{docObject}
{attributeModel}
{props}
{readonly}
value={getObjectValue(attributeModel.key, docObject)}
onChange={getOnChange(docObject, attributeModel)}
/>
@@ -29,6 +29,7 @@
export let props: Record<string, any>
export let hideDivider: boolean = false
export let compactMode: boolean = false
export let readonly: boolean = false
const dispatch = createEventDispatcher()
@@ -89,7 +90,7 @@
attributeModel,
docObject,
props,
$restrictionStore.readonly || !canChangeAttr(docObject, attributeModel.attribute, $permissionsStore)
readonly || $restrictionStore.readonly || !canChangeAttr(docObject, attributeModel.attribute, $permissionsStore)
)}
on:resize={translateSize}
/>
@@ -107,7 +108,7 @@
attributeModel,
docObject,
props,
$restrictionStore.readonly || !canChangeAttr(docObject, attributeModel.attribute, $permissionsStore)
readonly || $restrictionStore.readonly || !canChangeAttr(docObject, attributeModel.attribute, $permissionsStore)
)}
on:resize={translateSize}
/>
@@ -400,7 +400,6 @@ export async function RequestApproval (
control: ProcessControl,
results: UserResult[] | undefined
): Promise<ExecuteResult> {
if (params.title === undefined) throw processError(process.error.RequiredParamsNotProvided, { params: 'title' })
if (params.user === undefined) throw processError(process.error.RequiredParamsNotProvided, { params: 'user' })
const group = generateId()
const res: TxCreateDoc<ApproveRequest>[] = []
@@ -416,7 +415,7 @@ export async function RequestApproval (
collection: 'todos',
workslots: 0,
execution: execution._id,
title: params.title,
title: 'Approve request',
user,
description: params.description ?? '',
dueDate: params.dueDate,
@@ -512,6 +511,48 @@ export async function LockSection (
return { txes: res, rollback, context: [] }
}
export async function UnlockCard (
params: MethodParams<Card>,
execution: Execution,
control: ProcessControl
): Promise<ExecuteResult> {
const res: Tx[] = []
const rollback: Tx[] = []
const tx = control.client.txFactory.createTxUpdateDoc(cardPlugin.class.Card, execution.space, execution.card, {
readonly: false
})
res.push(tx)
rollback.push(
control.client.txFactory.createTxUpdateDoc(cardPlugin.class.Card, execution.space, execution.card, {
readonly: true
})
)
return { txes: res, rollback, context: [] }
}
export async function UnlockSection (
params: MethodParams<Card>,
execution: Execution,
control: ProcessControl
): Promise<ExecuteResult> {
if (params._id === undefined) throw processError(process.error.RequiredParamsNotProvided, { params: '_id' })
const res: Tx[] = []
const rollback: Tx[] = []
const card = await control.client.findOne(cardPlugin.class.Card, { _id: execution.card })
if (card === undefined) throw processError(process.error.ObjectNotFound, { _id: execution.card })
const target = params._id as Ref<MasterTag>
const tx = control.client.txFactory.createTxUpdateDoc(cardPlugin.class.Card, execution.space, execution.card, {
$pull: { readonlySections: target }
})
res.push(tx)
rollback.push(
control.client.txFactory.createTxUpdateDoc(cardPlugin.class.Card, execution.space, execution.card, {
$push: { readonlySections: target }
})
)
return { txes: res, rollback, context: [] }
}
export async function CreateToDo (
params: MethodParams<ProcessToDo>,
execution: Execution,
+12 -4
View File
@@ -40,7 +40,8 @@ import process, {
Transition,
isUpdateTx,
ProcessCustomEvent,
ApproveRequest
ApproveRequest,
ExecutionStatus
} from '@hcengineering/process'
import { QueueTopic, TriggerControl } from '@hcengineering/server-core'
import { ProcessMessage } from '@hcengineering/server-process'
@@ -103,7 +104,9 @@ import {
ApproveRequestApproved,
ApproveRequestRejected,
LockCard,
LockSection
LockSection,
UnlockCard,
UnlockSection
} from './functions'
import { ToDoCancellRollback, ToDoCloseRollback } from './rollback'
@@ -339,7 +342,10 @@ async function getExecutionReassignTxes (card: Card, control: TriggerControl): P
const res: Tx[] = []
const cards = await control.findAll(control.ctx, cardPlugin.class.Card, { baseId: card.baseId })
const ids = cards.map((p) => p._id).filter((p) => p !== card._id)
const executions = await control.findAll(control.ctx, process.class.Execution, { card: { $in: ids } })
const executions = await control.findAll(control.ctx, process.class.Execution, {
card: { $in: ids },
status: ExecutionStatus.Active
})
for (const execution of executions) {
res.push(
control.txFactory.createTxUpdateDoc(execution._class, execution.space, execution._id, {
@@ -523,7 +529,9 @@ export default async () => ({
ApproveRequestApproved,
ApproveRequestRejected,
LockCard,
LockSection
LockSection,
UnlockCard,
UnlockSection
},
transform: {
CurrentDate,
+2
View File
@@ -57,6 +57,8 @@ export default plugin(serverProcessId, {
RequestApproval: '' as Resource<ExecuteFunc>,
LockCard: '' as Resource<ExecuteFunc>,
LockSection: '' as Resource<ExecuteFunc>,
UnlockCard: '' as Resource<ExecuteFunc>,
UnlockSection: '' as Resource<ExecuteFunc>,
CheckToDoDone: '' as Resource<CheckFunc>,
CheckToDoCancelled: '' as Resource<CheckFunc>,
MatchCardCheck: '' as Resource<CheckFunc>,