Добавлен выбор режима условия для table-полей в переходах: по значению или пользователь из колонки

This commit is contained in:
2026-08-20 15:11:27 +03:00
parent 281f8da995
commit e24df563df
2 changed files with 86 additions and 18 deletions

View File

@@ -3761,7 +3761,20 @@ return function Layout({ ctx }) {
// 3. Простой: assigneeUserId установлен, assigneeConditions = null
const raw = transition.assigneeConditions as any;
let assigneeBlocks: { type: 'user' | 'role' | 'field_user' | 'table_column_user'; id: number; fieldCode?: string; columnIndex?: number; rules: { fieldCode: string; value: string; operator?: string }[] }[] = [];
let assigneeBlocks: { type: 'user' | 'role' | 'field_user' | 'table_column_user'; id: number; fieldCode?: string; columnIndex?: number; rules: { fieldCode: string; value: string; operator?: string; tableMode?: 'value' | 'column_user' }[] }[] = [];
const resolveTableMode = (r: any, fieldCode?: string): 'value' | 'column_user' | undefined => {
if (r.tableMode === 'value' || r.tableMode === 'column_user') return r.tableMode;
// Обратная совместимость: раньше для table-поля в value хранился индекс колонки
const field = fields.find((f: FormField) => f.code === (fieldCode || r.fieldCode));
if (field?.type === 'table') {
const tableId = (field.options as any)?.tableId as number | undefined;
const cols = tablesData?.tables?.find((t: any) => t.id === tableId)?.columns || [];
const idx = Number(r.value);
if (!isNaN(idx) && idx >= 0 && idx < cols.length) return 'column_user';
}
return 'value';
};
if (raw?.assignees && Array.isArray(raw.assignees)) {
assigneeBlocks = raw.assignees.map((a: any) => ({
@@ -3769,15 +3782,20 @@ return function Layout({ ctx }) {
id: a.id ?? 0,
fieldCode: a.fieldCode,
columnIndex: a.columnIndex,
rules: (a.rules || []).map((r: any) => ({ fieldCode: r.fieldCode || '', value: r.value || '', operator: r.operator || 'contains' })),
rules: (a.rules || []).map((r: any) => ({
fieldCode: r.fieldCode || '',
value: r.value || '',
operator: r.operator || 'contains',
tableMode: resolveTableMode(r, a.fieldCode),
})),
}));
} else if (raw?.rules && Array.isArray(raw.rules)) {
const merged = new Map<string, { type: 'user' | 'role' | 'field_user' | 'table_column_user'; id: number; fieldCode?: string; columnIndex?: number; rules: { fieldCode: string; value: string; operator?: string }[] }>();
const merged = new Map<string, { type: 'user' | 'role' | 'field_user' | 'table_column_user'; id: number; fieldCode?: string; columnIndex?: number; rules: { fieldCode: string; value: string; operator?: string; tableMode?: 'value' | 'column_user' }[] }>();
for (const r of raw.rules) {
if (r.tableUserColumnIndex !== undefined && !r.tableId && r.fieldCode) {
const key = `table_column_user:${r.fieldCode}:${r.tableUserColumnIndex}`;
if (!merged.has(key)) merged.set(key, { type: 'table_column_user', id: 0, fieldCode: r.fieldCode, columnIndex: r.tableUserColumnIndex, rules: [] });
if (r.fieldCode) merged.get(key)!.rules.push({ fieldCode: r.fieldCode, value: r.value || '', operator: r.operator || 'contains' });
if (r.fieldCode) merged.get(key)!.rules.push({ fieldCode: r.fieldCode, value: r.value || '', operator: r.operator || 'contains', tableMode: 'column_user' });
continue;
}
if (r.tableId !== undefined && r.tableUserColumnIndex !== undefined) {
@@ -3788,7 +3806,7 @@ return function Layout({ ctx }) {
if (!id) continue;
const key = `${type}:${id}`;
if (!merged.has(key)) merged.set(key, { type, id, rules: [] });
if (r.fieldCode) merged.get(key)!.rules.push({ fieldCode: r.fieldCode, value: r.value || '', operator: r.operator || 'contains' });
if (r.fieldCode) merged.get(key)!.rules.push({ fieldCode: r.fieldCode, value: r.value || '', operator: r.operator || 'contains', tableMode: resolveTableMode(r) });
}
assigneeBlocks = Array.from(merged.values());
}
@@ -3810,7 +3828,7 @@ return function Layout({ ctx }) {
id: (b.type === 'user' || b.type === 'role') ? b.id : undefined,
fieldCode: (b.type === 'field_user' || b.type === 'table_column_user') ? b.fieldCode : undefined,
columnIndex: b.type === 'table_column_user' ? b.columnIndex : undefined,
rules: b.rules.map(r => ({ fieldCode: r.fieldCode, value: r.value, operator: r.operator || 'contains' })),
rules: b.rules.map(r => ({ fieldCode: r.fieldCode, value: r.value, operator: r.operator || 'contains', tableMode: r.tableMode })),
})) }
: null;
@@ -3822,7 +3840,7 @@ return function Layout({ ctx }) {
updateTransition(index, { assigneeConditions: payload, assigneeUserId });
};
const emptyRule = { fieldCode: '', value: '', operator: 'contains' };
const emptyRule = { fieldCode: '', value: '', operator: 'contains', tableMode: undefined as 'value' | 'column_user' | undefined };
const addRule = (bi: number) => {
const blocks = [...assigneeBlocks];
blocks[bi] = { ...blocks[bi], rules: [...blocks[bi].rules, { ...emptyRule }] };
@@ -3833,7 +3851,7 @@ return function Layout({ ctx }) {
blocks[bi] = { ...blocks[bi], rules: blocks[bi].rules.filter((_, i) => i !== ri) };
saveAssigneeBlocks(blocks);
};
const updateRule = (bi: number, ri: number, patch: Partial<{ fieldCode: string; value: string; operator?: string }>) => {
const updateRule = (bi: number, ri: number, patch: Partial<{ fieldCode: string; value: string; operator?: string; tableMode?: 'value' | 'column_user' }>) => {
const blocks = [...assigneeBlocks];
blocks[bi] = { ...blocks[bi], rules: blocks[bi].rules.map((r, i) => i === ri ? { ...r, ...patch } : r) };
saveAssigneeBlocks(blocks);
@@ -3855,7 +3873,7 @@ return function Layout({ ctx }) {
id: b.id,
fieldCode: b.fieldCode,
columnIndex: b.columnIndex,
rules: b.rules.map(r => ({ fieldCode: r.fieldCode, value: r.value })),
rules: b.rules.map(r => ({ fieldCode: r.fieldCode, value: r.value, operator: r.operator, tableMode: r.tableMode })),
})),
},
assigneeUserId: undefined,
@@ -3965,21 +3983,66 @@ return function Layout({ ctx }) {
{isTableField ? (
<>
<span className="text-muted-foreground shrink-0">→ пользователь из колонки</span>
<Select
value={rule.value || 'none'}
onValueChange={(v) => updateRule(bi, ri, { value: v === 'none' ? '' : v })}
value={rule.tableMode || 'value'}
onValueChange={(v) => {
const nextMode = v as 'value' | 'column_user';
updateRule(bi, ri, {
tableMode: nextMode,
value: nextMode === 'column_user' ? '' : '',
operator: 'contains',
});
}}
>
<SelectTrigger className="h-6 text-xs w-36">
<SelectValue placeholder="колонка..." />
<SelectValue />
</SelectTrigger>
<SelectContent>
<SelectItem value="none">— выбрать —</SelectItem>
{fieldTableCols.map((col: any, ci: number) => (
<SelectItem key={ci} value={ci.toString()}>{col.name} ({col.type})</SelectItem>
))}
<SelectItem value="value">содержит значение</SelectItem>
<SelectItem value="column_user">пользователь из колонки</SelectItem>
</SelectContent>
</Select>
{rule.tableMode === 'column_user' ? (
<Select
value={rule.value || 'none'}
onValueChange={(v) => updateRule(bi, ri, { value: v === 'none' ? '' : v })}
>
<SelectTrigger className="h-6 text-xs w-36">
<SelectValue placeholder="колонка..." />
</SelectTrigger>
<SelectContent>
<SelectItem value="none">— выбрать —</SelectItem>
{fieldTableCols.map((col: any, ci: number) => (
<SelectItem key={ci} value={ci.toString()}>{col.name} ({col.type})</SelectItem>
))}
</SelectContent>
</Select>
) : (
<>
<Select
value={rule.operator || 'contains'}
onValueChange={(v) => updateRule(bi, ri, { operator: v })}
>
<SelectTrigger className="h-6 text-xs w-28">
<SelectValue />
</SelectTrigger>
<SelectContent>
<SelectItem value="contains">содержит</SelectItem>
<SelectItem value="empty">не заполнено</SelectItem>
<SelectItem value="filled">заполнено</SelectItem>
</SelectContent>
</Select>
{rule.operator !== 'empty' && (
<Input
className="h-6 text-xs w-28 px-1.5"
placeholder="значение"
value={rule.value || ''}
onChange={(e) => updateRule(bi, ri, { value: e.target.value })}
/>
)}
</>
)}
</>
) : (
<>

View File

@@ -292,7 +292,7 @@ export function buildSystemFieldValuesForConditions(
* Проверяет, соответствуют ли значения полей заданным правилам.
*/
export function fieldConditionsMatch(
rules: { fieldCode?: string; value?: string; operator?: "contains" | "empty" | "filled" }[],
rules: { fieldCode?: string; value?: string; operator?: "contains" | "empty" | "filled"; tableMode?: "value" | "column_user" }[],
fieldValues: Array<{ fieldId: number; value: unknown }>,
fields: Array<{
id: number;
@@ -311,6 +311,10 @@ export function fieldConditionsMatch(
const field = fields.find((f) => f.code === rule.fieldCode);
if (!field) return false;
// Режим "пользователь из колонки" — это конфигурация выборки согласующего,
// а не условие на значение поля. Пропускаем, чтобы не ломать проверку.
if (rule.tableMode === "column_user") continue;
const fv = fieldValues.find((fv) => fv.fieldId === field.id);
const operator = rule.operator || "contains";
@@ -364,6 +368,7 @@ export interface FieldRule {
fieldCode: string;
operator?: "contains" | "empty" | "filled";
value?: string;
tableMode?: "value" | "column_user";
}>;
}