[CLAUDE] AwV2: Mig 25 +IsUserSelectable + Designer pin toggle + Workspace filter, bỏ "(clone)"
Some checks failed
Deploy SOLUTION_ERP / build-deploy (push) Has been cancelled
Some checks failed
Deploy SOLUTION_ERP / build-deploy (push) Has been cancelled
Hai yêu cầu UAT 2026-05-08:
1. Bỏ "(clone)" auto-append khi clone version mới — version đã đủ phân biệt.
2. Thêm pin toggle để admin chọn workflows nào cho user pick lúc tạo phiếu.
Migration 25 AddIsUserSelectableToApprovalWorkflows:
- ALTER ApprovalWorkflows ADD IsUserSelectable bit NOT NULL DEFAULT 0
- UPDATE backfill SET IsUserSelectable=1 WHERE IsActive=1 (giữ behavior cũ
cho active versions, archived = false default — admin tự pin nếu cần)
BE:
- Domain ApprovalWorkflow +property IsUserSelectable
- DTO AwDefinitionDto +field
- CreateAwDefinitionCommandHandler set default true cho version mới
- New SetAwUserSelectableCommand + Handler
- API PATCH /api/approval-workflows-v2/{id}/user-selectable (Workflows.Create policy)
- DbInitializer SeedSampleApprovalWorkflowsV2Async set IsUserSelectable=true
FE Designer (fe-admin):
- DefinitionDto +isUserSelectable
- Badge amber "Pin Cho user chọn" khi true (cạnh Đang áp dụng/Archived)
- Button "Pin/PinOff Ghim cho user / Bỏ ghim" trong action group + mutation toggle
- Auto-fill name khi clone: bỏ "(clone)" suffix → giữ nguyên name
FE Workspace (fe-admin + fe-user):
- approvalWorkflows query filter w.isUserSelectable === true
- User dropdown chỉ thấy workflows admin đã pin
Verify: dotnet build pass · 81 test pass · npm build × 2 pass · Mig 25 apply LocalDB OK.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@ -32,6 +32,17 @@ public class ApprovalWorkflowsV2Controller(IMediator mediator) : ControllerBase
|
||||
return Ok(new { id });
|
||||
}
|
||||
|
||||
public record SetUserSelectableBody(bool IsUserSelectable);
|
||||
|
||||
// Mig 25 — admin toggle stick/unstick "cho user pick lúc create phiếu".
|
||||
[HttpPatch("{id:guid}/user-selectable")]
|
||||
[Authorize(Policy = "Workflows.Create")]
|
||||
public async Task<IActionResult> SetUserSelectable(Guid id, [FromBody] SetUserSelectableBody body, CancellationToken ct)
|
||||
{
|
||||
await mediator.Send(new SetAwUserSelectableCommand(id, body.IsUserSelectable), ct);
|
||||
return NoContent();
|
||||
}
|
||||
|
||||
[HttpDelete("{id:guid}")]
|
||||
[Authorize(Policy = "Workflows.Create")]
|
||||
public async Task<IActionResult> Delete(Guid id, CancellationToken ct)
|
||||
|
||||
@ -45,6 +45,7 @@ public record AwDefinitionDto(
|
||||
string Name,
|
||||
string? Description,
|
||||
bool IsActive,
|
||||
bool IsUserSelectable,
|
||||
DateTime? ActivatedAt,
|
||||
DateTime CreatedAt,
|
||||
List<AwStepDto> Steps);
|
||||
@ -126,6 +127,7 @@ public class GetAwAdminOverviewQueryHandler(
|
||||
d.Name,
|
||||
d.Description,
|
||||
d.IsActive,
|
||||
d.IsUserSelectable,
|
||||
d.ActivatedAt,
|
||||
d.CreatedAt,
|
||||
d.Steps.OrderBy(s => s.Order).Select(s => new AwStepDto(
|
||||
@ -268,6 +270,7 @@ public class CreateAwDefinitionCommandHandler(IApplicationDbContext db)
|
||||
Name = request.Name,
|
||||
Description = request.Description,
|
||||
IsActive = true,
|
||||
IsUserSelectable = true, // Mig 25 — version mới mặc định cho user pick
|
||||
ActivatedAt = DateTime.UtcNow,
|
||||
Steps = request.Steps.OrderBy(s => s.Order)
|
||||
.Select(s => new ApprovalWorkflowStep
|
||||
@ -291,6 +294,27 @@ public class CreateAwDefinitionCommandHandler(IApplicationDbContext db)
|
||||
}
|
||||
}
|
||||
|
||||
// ========== PATCH user-selectable toggle (Mig 25 — admin pin/unpin cho user pick) ==========
|
||||
// Independent với IsActive: cho phép multiple version cùng selectable. Default
|
||||
// version mới IsUserSelectable=true (mirror IsActive default), admin có thể
|
||||
// unstick để dấu khỏi Workspace dropdown, hoặc stick lại version cũ archived.
|
||||
|
||||
public record SetAwUserSelectableCommand(Guid Id, bool IsUserSelectable) : IRequest;
|
||||
|
||||
public class SetAwUserSelectableCommandHandler(IApplicationDbContext db)
|
||||
: IRequestHandler<SetAwUserSelectableCommand>
|
||||
{
|
||||
public async Task Handle(SetAwUserSelectableCommand request, CancellationToken ct)
|
||||
{
|
||||
var def = await db.ApprovalWorkflows
|
||||
.FirstOrDefaultAsync(d => d.Id == request.Id, ct)
|
||||
?? throw new KeyNotFoundException($"ApprovalWorkflow {request.Id} không tồn tại.");
|
||||
|
||||
def.IsUserSelectable = request.IsUserSelectable;
|
||||
await db.SaveChangesAsync(ct);
|
||||
}
|
||||
}
|
||||
|
||||
// ========== DELETE version (chỉ khi chưa có phiếu pin) ==========
|
||||
// Hiện chưa có phiếu nào pin schema mới → unconditional delete OK cho UAT.
|
||||
// Sau UAT khi link với PE/Contract thật cần check usage trước khi delete.
|
||||
|
||||
@ -28,6 +28,12 @@ public class ApprovalWorkflow : BaseEntity
|
||||
public bool IsActive { get; set; }
|
||||
public DateTime? ActivatedAt { get; set; }
|
||||
|
||||
// Mig 25 — admin toggle "cho user pick lúc create phiếu". Workspace dropdown
|
||||
// chỉ hiện workflow IsUserSelectable=true. Independent với IsActive: cho phép
|
||||
// multiple version cùng selectable (vd v02+v03 cùng pickable). Default true
|
||||
// khi tạo version mới (mirror IsActive default), admin có thể unstick.
|
||||
public bool IsUserSelectable { get; set; }
|
||||
|
||||
public List<ApprovalWorkflowStep> Steps { get; set; } = new();
|
||||
}
|
||||
|
||||
|
||||
@ -115,6 +115,7 @@ public static class DbInitializer
|
||||
Name = "Quy trình duyệt NCC và Giải pháp (mẫu UAT)",
|
||||
Description = "Sample seed cho UAT B — 1 Bước Phòng CCM × 1 Cấp NV test. Admin có thể clone tạo version mới qua Designer.",
|
||||
IsActive = true,
|
||||
IsUserSelectable = true, // Mig 25 — sample mặc định cho user pick
|
||||
ActivatedAt = DateTime.UtcNow,
|
||||
};
|
||||
var step = new ApprovalWorkflowStep
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,34 @@
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace SolutionErp.Infrastructure.Persistence.Migrations
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public partial class AddIsUserSelectableToApprovalWorkflows : Migration
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.AddColumn<bool>(
|
||||
name: "IsUserSelectable",
|
||||
table: "ApprovalWorkflows",
|
||||
type: "bit",
|
||||
nullable: false,
|
||||
defaultValue: false);
|
||||
|
||||
// Backfill: workflow đang active = auto user-selectable (giữ behavior
|
||||
// hiện tại — Workspace dropdown vẫn hiện active workflow). Archived
|
||||
// versions default false, admin tự toggle khi muốn user pick.
|
||||
migrationBuilder.Sql("UPDATE ApprovalWorkflows SET IsUserSelectable = 1 WHERE IsActive = 1;");
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.DropColumn(
|
||||
name: "IsUserSelectable",
|
||||
table: "ApprovalWorkflows");
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -155,6 +155,9 @@ namespace SolutionErp.Infrastructure.Persistence.Migrations
|
||||
b.Property<bool>("IsActive")
|
||||
.HasColumnType("bit");
|
||||
|
||||
b.Property<bool>("IsUserSelectable")
|
||||
.HasColumnType("bit");
|
||||
|
||||
b.Property<string>("Name")
|
||||
.IsRequired()
|
||||
.HasMaxLength(200)
|
||||
|
||||
Reference in New Issue
Block a user