10c3057eff
Раньше шаг маршрута мог нести ровно одно условие (field_name/operation/ value). Теперь условий может быть сколько угодно, а их объединение задаётся явно — ConditionsLogic.And (все условия) или .Or (любое из условий). Бэкенд: - Новая сущность TemplateRouteStepCondition (дочерняя таблица template_route_step_conditions, FK на шаг, cascade delete) вместо трёх колонок на TemplateRouteStep. Миграция AddMultipleStepConditions переносит существующие условия шагов в новую таблицу перед удалением старых колонок, чтобы не потерять уже настроенные в деве/проде условия. - TemplateRouteService.MatchesConditions: несколько условий шага объединяются по ConditionsLogic (Any/All); HasUnevaluatableManagerCondition проверяет условие на IsManager в списке условий, а не единственное поле. - TemplateRouteStepService: дубликаты шагов теперь сравниваются по всему набору условий (без учёта порядка) и, если условий два и более, по логике их объединения — сравнение в памяти, а не в SQL, кандидатов на дубликат в пределах маршрута всегда мало. - Валидация условия: раньше «все три поля или ни одного», теперь — в каждом элементе списка все три поля обязательны. Фронтенд: - RouteStepFormModal: условие — динамический список строк (добавить/удалить), у каждой строки свой набор допустимых операторов по типу колонки; селектор И/ИЛИ появляется, когда условий два и больше. - describeStepCondition в таблице шагов склеивает все условия через « И »/« ИЛИ ». Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
121 lines
5.2 KiB
C#
121 lines
5.2 KiB
C#
using Microsoft.EntityFrameworkCore.Migrations;
|
|
using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata;
|
|
|
|
#nullable disable
|
|
|
|
namespace Tnsu.ApprovalRoute.Infrastructure.Persistence.Migrations
|
|
{
|
|
/// <inheritdoc />
|
|
public partial class AddMultipleStepConditions : Migration
|
|
{
|
|
/// <inheritdoc />
|
|
protected override void Up(MigrationBuilder migrationBuilder)
|
|
{
|
|
migrationBuilder.AddColumn<string>(
|
|
name: "conditions_logic",
|
|
table: "template_route_steps",
|
|
type: "character varying(16)",
|
|
maxLength: 16,
|
|
nullable: false,
|
|
defaultValue: "And");
|
|
|
|
migrationBuilder.CreateTable(
|
|
name: "template_route_step_conditions",
|
|
columns: table => new
|
|
{
|
|
id = table.Column<int>(type: "integer", nullable: false)
|
|
.Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn),
|
|
step_id = table.Column<int>(type: "integer", nullable: false),
|
|
order_no = table.Column<int>(type: "integer", nullable: false),
|
|
field_name = table.Column<string>(type: "character varying(128)", maxLength: 128, nullable: false),
|
|
operation = table.Column<string>(type: "character varying(32)", maxLength: 32, nullable: false),
|
|
value = table.Column<string>(type: "character varying(256)", maxLength: 256, nullable: false)
|
|
},
|
|
constraints: table =>
|
|
{
|
|
table.PrimaryKey("pk_template_route_step_conditions", x => x.id);
|
|
table.ForeignKey(
|
|
name: "fk_template_route_step_conditions_template_route_steps_step_id",
|
|
column: x => x.step_id,
|
|
principalTable: "template_route_steps",
|
|
principalColumn: "id",
|
|
onDelete: ReferentialAction.Cascade);
|
|
});
|
|
|
|
migrationBuilder.CreateIndex(
|
|
name: "ix_template_route_step_conditions_step_id_order_no",
|
|
table: "template_route_step_conditions",
|
|
columns: new[] { "step_id", "order_no" });
|
|
|
|
// Существующее одиночное условие шага (field_name/operation/value) переносим
|
|
// как первое (и единственное) условие в новую таблицу — до удаления старых
|
|
// колонок, иначе уже настроенные в деве/проде условия шагов молча пропадут.
|
|
migrationBuilder.Sql(
|
|
"""
|
|
INSERT INTO template_route_step_conditions (step_id, order_no, field_name, operation, value)
|
|
SELECT id, 0, field_name, operation, value
|
|
FROM template_route_steps
|
|
WHERE field_name IS NOT NULL AND operation IS NOT NULL AND value IS NOT NULL;
|
|
""");
|
|
|
|
migrationBuilder.DropColumn(
|
|
name: "field_name",
|
|
table: "template_route_steps");
|
|
|
|
migrationBuilder.DropColumn(
|
|
name: "operation",
|
|
table: "template_route_steps");
|
|
|
|
migrationBuilder.DropColumn(
|
|
name: "value",
|
|
table: "template_route_steps");
|
|
}
|
|
|
|
/// <inheritdoc />
|
|
protected override void Down(MigrationBuilder migrationBuilder)
|
|
{
|
|
migrationBuilder.AddColumn<string>(
|
|
name: "field_name",
|
|
table: "template_route_steps",
|
|
type: "character varying(128)",
|
|
maxLength: 128,
|
|
nullable: true);
|
|
|
|
migrationBuilder.AddColumn<string>(
|
|
name: "operation",
|
|
table: "template_route_steps",
|
|
type: "character varying(32)",
|
|
maxLength: 32,
|
|
nullable: true);
|
|
|
|
migrationBuilder.AddColumn<string>(
|
|
name: "value",
|
|
table: "template_route_steps",
|
|
type: "character varying(256)",
|
|
maxLength: 256,
|
|
nullable: true);
|
|
|
|
// Откат теряет условия сверх первого (по order_no) — старая схема хранила
|
|
// только одно условие на шаг, второе и далее девать некуда.
|
|
migrationBuilder.Sql(
|
|
"""
|
|
UPDATE template_route_steps s
|
|
SET field_name = c.field_name, operation = c.operation, value = c.value
|
|
FROM (
|
|
SELECT DISTINCT ON (step_id) step_id, field_name, operation, value
|
|
FROM template_route_step_conditions
|
|
ORDER BY step_id, order_no
|
|
) c
|
|
WHERE c.step_id = s.id;
|
|
""");
|
|
|
|
migrationBuilder.DropTable(
|
|
name: "template_route_step_conditions");
|
|
|
|
migrationBuilder.DropColumn(
|
|
name: "conditions_logic",
|
|
table: "template_route_steps");
|
|
}
|
|
}
|
|
}
|