feat(automations): parallel branch canvas, simple/advanced level per org, rules list UX
This commit is contained in:
@@ -6,6 +6,7 @@ import {
|
||||
} from '@nestjs/common';
|
||||
import type {
|
||||
AutomationAction,
|
||||
AutomationChainMode,
|
||||
AutomationConditionField,
|
||||
AutomationConditionOp,
|
||||
AutomationNotifyChannel,
|
||||
@@ -19,6 +20,7 @@ import type {
|
||||
import {
|
||||
MAX_AUTOMATION_STEPS,
|
||||
automationStepToConfig,
|
||||
automationUsesAdvanced,
|
||||
firstAutomationAction,
|
||||
matchFormFieldCondition,
|
||||
normalizeAutomationSteps,
|
||||
@@ -95,6 +97,7 @@ export class AutomationsService {
|
||||
const action = firstAutomationAction(steps);
|
||||
if (!action) throw new BadRequestException('حداقل یک عمل لازم است');
|
||||
await this.assertStepRefs(organizationId, steps);
|
||||
await this.assertLevelAllows(organizationId, steps, chainExtra(dto.config).chainMode);
|
||||
const row = await this.prisma.automationRule.create({
|
||||
data: {
|
||||
organizationId,
|
||||
@@ -130,7 +133,14 @@ export class AutomationsService {
|
||||
if (steps && !nextAction) {
|
||||
throw new BadRequestException('حداقل یک عمل لازم است');
|
||||
}
|
||||
if (steps) await this.assertStepRefs(organizationId, steps);
|
||||
if (steps) {
|
||||
await this.assertStepRefs(organizationId, steps);
|
||||
await this.assertLevelAllows(
|
||||
organizationId,
|
||||
steps,
|
||||
chainExtra(dto.config ?? asConfig(existing.config)).chainMode,
|
||||
);
|
||||
}
|
||||
|
||||
const row = await this.prisma.automationRule.update({
|
||||
where: { id },
|
||||
@@ -487,6 +497,23 @@ export class AutomationsService {
|
||||
}
|
||||
}
|
||||
|
||||
private async assertLevelAllows(
|
||||
organizationId: string,
|
||||
steps: AutomationStep[],
|
||||
chainMode: AutomationChainMode | undefined,
|
||||
) {
|
||||
if (!automationUsesAdvanced(steps, chainMode)) return;
|
||||
const org = await this.prisma.organization.findUnique({
|
||||
where: { id: organizationId },
|
||||
select: { enabledModules: true },
|
||||
});
|
||||
if (!org?.enabledModules.includes('workflow_automations_advanced')) {
|
||||
throw new BadRequestException(
|
||||
'شاخهها، شرط روی پاسخ فرم و تقسیم نوبتی فقط در اتوماسیون حرفهای فعال است',
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
private async executeNotify(
|
||||
title: string,
|
||||
config: AutomationRuleConfig,
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
import { describe, it } from 'node:test';
|
||||
import assert from 'node:assert/strict';
|
||||
import {
|
||||
automationUsesAdvanced,
|
||||
matchFormFieldCondition,
|
||||
splitAutomationBranches,
|
||||
planAutomationActions,
|
||||
splitFormAnswer,
|
||||
type AutomationStep,
|
||||
@@ -91,6 +93,33 @@ describe('اتوماسیون شاخهای فرم', () => {
|
||||
);
|
||||
});
|
||||
|
||||
it('تفکیک شاخهها برای نمایش موازی', () => {
|
||||
assert.deepEqual(splitAutomationBranches(BSJ), {
|
||||
prefixEnd: 0,
|
||||
branches: [
|
||||
{ start: 0, end: 2 },
|
||||
{ start: 2, end: 4 },
|
||||
{ start: 4, end: 6 },
|
||||
],
|
||||
});
|
||||
const mixed = [act('a0'), cond('c1', 'x'), cond('c2', 'y'), act('a1')];
|
||||
assert.deepEqual(splitAutomationBranches(mixed), {
|
||||
prefixEnd: 1,
|
||||
branches: [{ start: 1, end: 4 }],
|
||||
});
|
||||
assert.deepEqual(splitAutomationBranches([act('a0')]), { prefixEnd: 1, branches: [] });
|
||||
});
|
||||
|
||||
it('تشخیص نیاز به اتوماسیون حرفهای', () => {
|
||||
assert.equal(automationUsesAdvanced([act('a0')], 'stop'), false);
|
||||
assert.equal(automationUsesAdvanced([act('a0')], 'branches'), true);
|
||||
assert.equal(automationUsesAdvanced(BSJ, 'stop'), true);
|
||||
assert.equal(
|
||||
automationUsesAdvanced([{ ...act('a0'), assignMode: 'team_round_robin' }], undefined),
|
||||
true,
|
||||
);
|
||||
});
|
||||
|
||||
it('تقسیم نوبتی مساوی و پایدار', () => {
|
||||
const team = ['u3', 'u1', 'u2'];
|
||||
const picks = [1, 2, 3, 4, 5, 6].map((n) => pickRoundRobin(team, n));
|
||||
|
||||
Reference in New Issue
Block a user