WhenRequiredFieldsFilled (#10937)

Signed-off-by: Denis Bykhov <bykhov.denis@gmail.com>
This commit is contained in:
Denis Bykhov
2026-06-30 14:46:40 +05:00
committed by GitHub
parent 4125453b04
commit 1145f17928
26 changed files with 141 additions and 3 deletions
+4
View File
@@ -0,0 +1,4 @@
# Project Instructions
- Do not run build commands automatically for verification.
- When finishing work, state that the task is complete and ask the user to verify it manually.
+15
View File
@@ -124,6 +124,21 @@ export function defineTriggers (builder: Builder): void {
process.trigger.WhenFieldChanges
)
builder.createDoc(
process.class.Trigger,
core.space.Model,
{
label: process.string.WhenRequiredFieldsFilled,
icon: process.icon.WhenCardMatches,
presenter: process.component.CardUpdatePresenter,
requiredParams: [],
checkFunction: process.triggerCheck.RequiredFieldsFilledCheck,
init: false,
auto: true
},
process.trigger.WhenRequiredFieldsFilled
)
// builder.createDoc(
// process.class.Trigger,
// core.space.Model,
+5
View File
@@ -70,6 +70,11 @@ export function createModel (builder: Builder): void {
serverCheckFunc: serverProcess.func.FieldChangedCheck
})
builder.mixin(process.trigger.WhenRequiredFieldsFilled, process.class.Trigger, serverProcess.mixin.TriggerImpl, {
preventRollback: true,
serverCheckFunc: serverProcess.func.RequiredFieldsFilledCheck
})
builder.mixin(process.trigger.OnApproveRequestApproved, process.class.Trigger, serverProcess.mixin.TriggerImpl, {
preventRollback: true,
serverCheckFunc: serverProcess.func.ApproveRequestApproved
+1
View File
@@ -96,6 +96,7 @@
"CreateCard": "Vytvořit kartu",
"WhenCardMatches": "Když karta odpovídá",
"WhenFieldChanges": "Když se pole změní",
"WhenRequiredFieldsFilled": "Když jsou vyplněna všechna povinná pole",
"Insert": "Vložit",
"Remove": "Odstranit",
"RemoveFirst": "Odstranit první",
+1
View File
@@ -96,6 +96,7 @@
"CreateCard": "Karte erstellen",
"WhenCardMatches": "Wenn Karte übereinstimmt",
"WhenFieldChanges": "Wenn sich das Feld ändert",
"WhenRequiredFieldsFilled": "Wenn erforderliche Felder ausgefüllt sind",
"Insert": "Einfügen",
"Remove": "Entfernen",
"RemoveFirst": "Erstes entfernen",
+1
View File
@@ -98,6 +98,7 @@
"CreateCard": "Create card",
"WhenCardMatches": "When card matches",
"WhenFieldChanges": "When field changes",
"WhenRequiredFieldsFilled": "When required fields are filled",
"ArraySizeEquals": "Size equals",
"ArraySizeGt": "Size greater than",
"ArraySizeGte": "Size greater than or equal",
+1
View File
@@ -102,6 +102,7 @@
"CreateCard": "Crear tarjeta",
"WhenCardMatches": "Cuando la tarjeta coincide",
"WhenFieldChanges": "Cuando el campo cambia",
"WhenRequiredFieldsFilled": "Cuando los campos obligatorios estén completos",
"ArraySizeEquals": "Tamaño igual a",
"ArraySizeGt": "Tamaño mayor que",
"ArraySizeGte": "Tamaño mayor o igual que",
+1
View File
@@ -102,6 +102,7 @@
"CreateCard": "Créer une carte",
"WhenCardMatches": "Lorsque la carte correspond",
"WhenFieldChanges": "Lorsque le champ change",
"WhenRequiredFieldsFilled": "Lorsque les champs requis sont remplis",
"ArraySizeEquals": "Taille égale à",
"ArraySizeGt": "Taille supérieure à",
"ArraySizeGte": "Taille supérieure ou égale à",
+1
View File
@@ -102,6 +102,7 @@
"CreateCard": "Crea scheda",
"WhenCardMatches": "Quando la scheda corrisponde",
"WhenFieldChanges": "Quando il campo cambia",
"WhenRequiredFieldsFilled": "Quando i campi richiesti sono compilati",
"ArraySizeEquals": "Dimensione uguale a",
"ArraySizeGt": "Dimensione maggiore di",
"ArraySizeGte": "Dimensione maggiore o uguale a",
+1
View File
@@ -101,6 +101,7 @@
"CreateCard": "カードを作成",
"WhenCardMatches": "カードが一致したとき",
"WhenFieldChanges": "フィールドが変更されたとき",
"WhenRequiredFieldsFilled": "必須フィールドが入力されたとき",
"ArraySizeEquals": "サイズが等しい",
"ArraySizeGt": "サイズがより大きい",
"ArraySizeGte": "サイズが以上",
+1
View File
@@ -98,6 +98,7 @@
"CreateCard": "카드 생성",
"WhenCardMatches": "카드 일치 시",
"WhenFieldChanges": "필드 변경 시",
"WhenRequiredFieldsFilled": "필수 필드가 채워졌을 때",
"ArraySizeEquals": "크기 같음",
"ArraySizeGt": "크기가 더 큼",
"ArraySizeGte": "크기가 이상",
+1
View File
@@ -98,6 +98,7 @@
"CreateCard": "Utwórz kartę",
"WhenCardMatches": "Kiedy karta spełnia warunek",
"WhenFieldChanges": "Kiedy pole jest zmieniane",
"WhenRequiredFieldsFilled": "Kiedy wymagane pola są wypełnione",
"ArraySizeEquals": "Rozmiar równy",
"ArraySizeGt": "Rozmiar większy niż",
"ArraySizeGte": "Rozmiar większy lub równy",
+1
View File
@@ -102,6 +102,7 @@
"CreateCard": "Criar Cartão",
"WhenCardMatches": "Quando o cartão corresponde",
"WhenFieldChanges": "Quando o campo muda",
"WhenRequiredFieldsFilled": "Quando os campos obrigatórios forem preenchidos",
"ArraySizeEquals": "Tamanho igual a",
"ArraySizeGt": "Tamanho maior que",
"ArraySizeGte": "Tamanho maior ou igual a",
+1
View File
@@ -102,6 +102,7 @@
"CreateCard": "Criar Cartão",
"WhenCardMatches": "Quando o cartão corresponde",
"WhenFieldChanges": "Quando o campo muda",
"WhenRequiredFieldsFilled": "Quando os campos obrigatórios forem preenchidos",
"ArraySizeEquals": "Tamanho igual a",
"ArraySizeGt": "Tamanho maior que",
"ArraySizeGte": "Tamanho maior ou igual a",
+1
View File
@@ -102,6 +102,7 @@
"CreateCard": "Создать карточку",
"WhenCardMatches": "Когда карточка соответствует",
"WhenFieldChanges": "Когда поле изменено",
"WhenRequiredFieldsFilled": "Когда обязательные поля заполнены",
"ArraySizeEquals": "Количество равно",
"ArraySizeGt": "Количество больше",
"ArraySizeGte": "Количество больше или равно",
+1
View File
@@ -98,6 +98,7 @@
"CreateCard": "Kart oluştur",
"WhenCardMatches": "Kart eşleştiğinde",
"WhenFieldChanges": "Alan değiştiğinde",
"WhenRequiredFieldsFilled": "Gerekli alanlar doldurulduğunda",
"ArraySizeEquals": "Boyut eşittir",
"ArraySizeGt": "Boyut büyüktür",
"ArraySizeGte": "Boyut büyük veya eşittir",
+1
View File
@@ -102,6 +102,7 @@
"CreateCard": "创建卡片",
"WhenCardMatches": "当卡片匹配时",
"WhenFieldChanges": "当字段更改时",
"WhenRequiredFieldsFilled": "当必需字段被填充时",
"ArraySizeEquals": "大小等于",
"ArraySizeGt": "大小大于",
"ArraySizeGte": "大小大于等于",
+2
View File
@@ -106,6 +106,7 @@ import {
eventCheck,
fieldChangesCheck,
matchCardCheck,
requiredFieldsFilledCheck,
showDoneQuery,
subProcessesDoneCheck,
subProcessMatchCheck,
@@ -212,6 +213,7 @@ export default async (): Promise<Resources> => ({
triggerCheck: {
MatchCheck: matchCardCheck,
FieldChangedCheck: fieldChangesCheck,
RequiredFieldsFilledCheck: requiredFieldsFilledCheck,
SubProcessesDoneCheck: subProcessesDoneCheck,
SubProcessMatchCheck: subProcessMatchCheck,
ToDo: todoTranstionCheck,
+1
View File
@@ -200,6 +200,7 @@ export default mergeIds(processId, process, {
WaitUntil: '' as IntlString,
WhenCardMatches: '' as IntlString,
WhenFieldChanges: '' as IntlString,
WhenRequiredFieldsFilled: '' as IntlString,
OnEvent: '' as IntlString,
Result: '' as IntlString,
RequestResult: '' as IntlString,
+31
View File
@@ -23,6 +23,7 @@ import core, {
type DocumentUpdate,
findProperty,
generateId,
getObjectValue,
matchQuery,
type Ref,
type RefTo,
@@ -962,6 +963,36 @@ export function fieldChangesCheck (
return res.length > 0
}
function isRequiredValueFilled (value: any, attr: AnyAttribute): boolean {
if (attr.type?._class === core.class.TypeMarkup) return !isEmptyMarkup(value)
if (Array.isArray(value)) return value.length > 0
return value !== undefined && value !== null && value !== ''
}
export function requiredFieldsFilledCheck (
client: Client,
execution: Execution,
params: Record<string, any>,
context: Record<string, any>
): boolean {
let doc = context.card
if (doc === undefined) return false
const _process = client.getModel().findObject(execution.process)
if (_process === undefined) return false
const hierarchy = client.getHierarchy()
const attributes = Array.from(
hierarchy.isMixin(_process.masterTag)
? hierarchy.getOwnAttributes(_process.masterTag).entries()
: hierarchy.getAllAttributes(_process.masterTag, core.class.Doc).entries()
).filter(([, attr]) => attr.required === true && attr.hidden !== true)
if (hierarchy.isMixin(_process.masterTag)) {
doc = hierarchy.as(doc, _process.masterTag)
}
return attributes.every(([key, attr]) => isRequiredValueFilled(getObjectValue(key, doc), attr))
}
export async function subProcessesDoneCheck (
client: Client,
execution: Execution,
+2
View File
@@ -279,6 +279,7 @@ export default plugin(processId, {
trigger: {
OnCardUpdate: '' as Ref<Trigger>, // in fact WhenCardMatches, should migrate in future
WhenFieldChanges: '' as Ref<Trigger>,
WhenRequiredFieldsFilled: '' as Ref<Trigger>,
OnSubProcessesDone: '' as Ref<Trigger>,
OnSubProcessMatch: '' as Ref<Trigger>,
OnToDoClose: '' as Ref<Trigger>,
@@ -294,6 +295,7 @@ export default plugin(processId, {
ToDo: '' as Resource<CheckFunc>,
MatchCheck: '' as Resource<CheckFunc>,
FieldChangedCheck: '' as Resource<CheckFunc>,
RequiredFieldsFilledCheck: '' as Resource<CheckFunc>,
SubProcessesDoneCheck: '' as Resource<CheckFunc>,
SubProcessMatchCheck: '' as Resource<CheckFunc>,
Time: '' as Resource<CheckFunc>,
@@ -18,6 +18,7 @@
import { getClient } from '@hcengineering/presentation'
import { AnySvelteComponent, Icon, IconMoreV2, IconOpenedArrow, Label, tooltip } from '@hcengineering/ui'
import view from '@hcengineering/view'
import setting from '../plugin'
export let attribute: AnyAttribute
export let attributeType: IntlString | undefined = undefined
@@ -64,6 +65,9 @@
{/if}
<div class="hulyTableAttr-content__row-label font-regular-14 grow" class:accent={!attribute.hidden}>
<Label label={attribute.label} />
{#if attribute.required === true}
<span class="required-marker font-medium-12" use:tooltip={{ label: setting.string.Required }}>*</span>
{/if}
</div>
{#if attributeMapper}
<svelte:component this={attributeMapper.component} {...attributeMapper.props} {attribute} />
@@ -91,3 +95,18 @@
<IconOpenedArrow size={'small'} />
</div>
</button>
<style lang="scss">
.hulyTableAttr-content__row-label {
display: flex;
align-items: center;
min-width: 0;
gap: var(--spacing-0_5);
}
.required-marker {
flex-shrink: 0;
color: var(--global-error-TextColor);
line-height: 1;
}
</style>
@@ -16,6 +16,7 @@
import cardPlugin, { Card, MasterTag, Tag } from '@hcengineering/card'
import core, {
Association,
AnyAttribute,
checkMixinKey,
Class,
Data,
@@ -220,6 +221,36 @@ export function FieldChangedCheck (
return res.length > 0
}
function isRequiredValueFilled (value: any, attr: AnyAttribute): boolean {
if (attr.type?._class === core.class.TypeMarkup) return !isEmptyMarkup(value)
if (Array.isArray(value)) return value.length > 0
return value !== undefined && value !== null && value !== ''
}
export function RequiredFieldsFilledCheck (
control: ProcessControl,
execution: Execution,
params: Record<string, any>,
context: Record<string, any>
): boolean {
let card = context.card
if (card === undefined) return false
const _process = control.client.getModel().findObject(execution.process)
if (_process === undefined) return false
const hierarchy = control.client.getHierarchy()
const attributes = Array.from(
hierarchy.isMixin(_process.masterTag)
? hierarchy.getOwnAttributes(_process.masterTag).entries()
: hierarchy.getAllAttributes(_process.masterTag, core.class.Doc).entries()
).filter(([, attr]) => attr.required === true && attr.hidden !== true)
if (hierarchy.isMixin(_process.masterTag)) {
card = hierarchy.as(card, _process.masterTag)
}
return attributes.every(([key, attr]) => isRequiredValueFilled(getObjectValue(key, card), attr))
}
export function CheckTime (control: ProcessControl, execution: Execution, params: Record<string, any>): boolean {
if (params.value === undefined) return false
return params.value <= Date.now()
@@ -72,6 +72,7 @@ import {
LockField,
LockSection,
MatchCardCheck,
RequiredFieldsFilledCheck,
RequestApproval,
RunSubProcess,
UnlockCard,
@@ -588,7 +589,11 @@ export async function OnCardUpdate (txes: Tx[], control: TriggerControl): Promis
const ops = isUpdateTx(cudTx) ? cudTx.operations : cudTx.attributes
await putEventToQueue(
{
event: [process.trigger.OnCardUpdate, process.trigger.WhenFieldChanges],
event: [
process.trigger.OnCardUpdate,
process.trigger.WhenFieldChanges,
process.trigger.WhenRequiredFieldsFilled
],
card: cudTx.objectId,
createdOn: tx.modifiedOn,
_id: tx._id,
@@ -731,6 +736,7 @@ export default async () => ({
CheckToDoCancelled,
FieldChangedCheck,
MatchCardCheck,
RequiredFieldsFilledCheck,
CheckSubProcessesDone,
CheckSubProcessMatch,
CheckTime,
+1
View File
@@ -67,6 +67,7 @@ export default plugin(serverProcessId, {
CheckToDoCancelled: '' as Resource<CheckFunc>,
MatchCardCheck: '' as Resource<CheckFunc>,
FieldChangedCheck: '' as Resource<CheckFunc>,
RequiredFieldsFilledCheck: '' as Resource<CheckFunc>,
CheckSubProcessesDone: '' as Resource<CheckFunc>,
CheckSubProcessMatch: '' as Resource<CheckFunc>,
CheckTime: '' as Resource<CheckFunc>,
+8 -1
View File
@@ -164,7 +164,14 @@ async function processCardExecutions (control: ProcessControl, record: ProcessMe
}
// we should update timers, probably context value changed
if (record.event.some((p) => p === process.trigger.OnCardUpdate || p === process.trigger.WhenFieldChanges)) {
if (
record.event.some(
(p) =>
p === process.trigger.OnCardUpdate ||
p === process.trigger.WhenFieldChanges ||
p === process.trigger.WhenRequiredFieldsFilled
)
) {
await updateTimers(control, record)
}
}