[CLAUDE] Workflow: fix workflow picker 2 bug (P11-A Max re-review) + SetWorkflow endpoint
All checks were successful
Deploy SOLUTION_ERP / build-deploy (push) Successful in 4m5s
All checks were successful
Deploy SOLUTION_ERP / build-deploy (push) Successful in 4m5s
Double-check chất lượng P11-A ở Max (agents trước chạy High + truncate 3×) → phát hiện 2 bug THẬT trong workflow-picker FE của WorkflowAppDetailPage (core approve/reject/return ĐÚNG, chỉ sub-flow chọn quy trình hỏng): Bug #1 (HIGH) — pinWorkflow PUT /{id} chỉ gửi {approvalWorkflowId} → UpdateDraft validator (Reason NotEmpty, NumDays>0...) fail → 400. Nút "Lưu quy trình" vỡ. Bug #2 (HIGH) — fetch workflow expect flat array nhưng endpoint trả AwAdminOverviewDto {types:[...]} → picker rỗng/crash. FE copy nhầm pattern hỏng của ProposalCreatePage thay vì PE/Contract proven. Fix: - BE: thêm endpoint chuyên dụng PUT /{id}/workflow + Set{Module}WorkflowCommand/Handler cho 4 module — chỉ set ApprovalWorkflowId trên draft Nhap/TraLai (verify ApplicableType per module), KHÔNG validate field khác. Single-responsibility, bulletproof. - FE: sửa fetch mirror PE/Contract (data.types.find(t=>t.applicableType===X)?.history .filter(isUserSelectable)) + pin gọi endpoint mới. fe-admin+fe-user SHA256 identical. - Test: +3 SetWorkflow (happy no-status-change / wrong ApplicableType Conflict / submitted guard) → 141→144 PASS. Verify: BE build 0 error · 144 test PASS · FE build ×2 · SHA256 identical. Bonus phát hiện: ProposalCreatePage (S37) có bug #2 có sẵn (latent, chưa exercise UAT) → flag spawn task riêng, KHÔNG fix trong commit này. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@ -440,4 +440,83 @@ public class WorkflowAppApproveV2Tests
|
||||
opinions[0].Comment.Should().Be("(duyệt — không ý kiến)");
|
||||
}
|
||||
}
|
||||
|
||||
// ============ SetWorkflow (P11-A fix S42): pin quy trình cho draft ============
|
||||
// Endpoint riêng /workflow — KHÔNG validate field khác (fix FE bug PUT /{id} partial → 400).
|
||||
|
||||
[Fact]
|
||||
public async Task SetWorkflow_OnDraft_SetsApprovalWorkflowId_NoStatusChange()
|
||||
{
|
||||
var (fix, db, clock) = NewCtx();
|
||||
using (fix)
|
||||
{
|
||||
var requester = await fix.CreateUserAsync("req-sw1@test.local", "Requester", null, Array.Empty<string>());
|
||||
var approver = await fix.CreateUserAsync("ap-sw1@test.local", "Approver", null, Array.Empty<string>());
|
||||
var (wf, _) = await SeedLeaveWorkflowAsync(db, approver.Id);
|
||||
|
||||
var leave = BuildLeave(requester.Id, workflowId: null, WorkflowAppStatus.Nhap, currentLevel: null);
|
||||
db.LeaveRequests.Add(leave);
|
||||
await db.SaveChangesAsync(CancellationToken.None);
|
||||
|
||||
await new SetLeaveRequestWorkflowHandler(db, AsUser(requester), clock)
|
||||
.Handle(new SetLeaveRequestWorkflowCommand(leave.Id, wf.Id), CancellationToken.None);
|
||||
|
||||
leave.ApprovalWorkflowId.Should().Be(wf.Id);
|
||||
leave.Status.Should().Be(WorkflowAppStatus.Nhap, "set workflow KHÔNG đổi trạng thái");
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task SetWorkflow_WrongApplicableType_ThrowsConflict_DoesNotPin()
|
||||
{
|
||||
var (fix, db, clock) = NewCtx();
|
||||
using (fix)
|
||||
{
|
||||
var requester = await fix.CreateUserAsync("req-sw2@test.local", "Requester", null, Array.Empty<string>());
|
||||
|
||||
// Workflow loại OtRequest (=6) — KHÔNG khớp LeaveRequest (=5)
|
||||
var otWf = new ApprovalWorkflow
|
||||
{
|
||||
Id = Guid.NewGuid(),
|
||||
Code = "QT-OT-X",
|
||||
Version = 1,
|
||||
Name = "OT workflow",
|
||||
ApplicableType = ApprovalWorkflowApplicableType.OtRequest,
|
||||
IsActive = true,
|
||||
IsUserSelectable = true,
|
||||
};
|
||||
db.ApprovalWorkflows.Add(otWf);
|
||||
|
||||
var leave = BuildLeave(requester.Id, workflowId: null, WorkflowAppStatus.Nhap, currentLevel: null);
|
||||
db.LeaveRequests.Add(leave);
|
||||
await db.SaveChangesAsync(CancellationToken.None);
|
||||
|
||||
var act = async () => await new SetLeaveRequestWorkflowHandler(db, AsUser(requester), clock)
|
||||
.Handle(new SetLeaveRequestWorkflowCommand(leave.Id, otWf.Id), CancellationToken.None);
|
||||
|
||||
await act.Should().ThrowAsync<ConflictException>().WithMessage("*Đơn nghỉ phép*");
|
||||
leave.ApprovalWorkflowId.Should().BeNull("guard chặn trước khi pin");
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task SetWorkflow_WhenAlreadySubmitted_ThrowsConflict()
|
||||
{
|
||||
var (fix, db, clock) = NewCtx();
|
||||
using (fix)
|
||||
{
|
||||
var requester = await fix.CreateUserAsync("req-sw3@test.local", "Requester", null, Array.Empty<string>());
|
||||
var approver = await fix.CreateUserAsync("ap-sw3@test.local", "Approver", null, Array.Empty<string>());
|
||||
var (wf, _) = await SeedLeaveWorkflowAsync(db, approver.Id);
|
||||
|
||||
var leave = BuildLeave(requester.Id, wf.Id, WorkflowAppStatus.DaGuiDuyet, currentLevel: 1);
|
||||
db.LeaveRequests.Add(leave);
|
||||
await db.SaveChangesAsync(CancellationToken.None);
|
||||
|
||||
var act = async () => await new SetLeaveRequestWorkflowHandler(db, AsUser(requester), clock)
|
||||
.Handle(new SetLeaveRequestWorkflowCommand(leave.Id, wf.Id), CancellationToken.None);
|
||||
|
||||
await act.Should().ThrowAsync<ConflictException>().WithMessage("*Nháp hoặc Trả lại*");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user