[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

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:
pqhuy1987
2026-05-30 10:14:34 +07:00
parent e7b66cd52b
commit 75df04ec82
11 changed files with 302 additions and 31 deletions

View File

@ -64,6 +64,15 @@ public class LeaveRequestsController(IMediator mediator) : ControllerBase
return NoContent();
}
[HttpPut("{id:guid}/workflow")]
public async Task<IActionResult> SetWorkflow(Guid id, [FromBody] SetWorkflowBody body)
{
await mediator.Send(new SetLeaveRequestWorkflowCommand(id, body.ApprovalWorkflowId));
return NoContent();
}
public record SetWorkflowBody(Guid ApprovalWorkflowId);
public record UpdateLeaveRequestDraftBody(
Guid LeaveTypeId,
DateTime StartDate,

View File

@ -64,6 +64,15 @@ public class OtRequestsController(IMediator mediator) : ControllerBase
return NoContent();
}
[HttpPut("{id:guid}/workflow")]
public async Task<IActionResult> SetWorkflow(Guid id, [FromBody] SetWorkflowBody body)
{
await mediator.Send(new SetOtRequestWorkflowCommand(id, body.ApprovalWorkflowId));
return NoContent();
}
public record SetWorkflowBody(Guid ApprovalWorkflowId);
public record UpdateOtRequestDraftBody(
DateTime OtDate,
TimeSpan StartTime,

View File

@ -64,6 +64,15 @@ public class TravelRequestsController(IMediator mediator) : ControllerBase
return NoContent();
}
[HttpPut("{id:guid}/workflow")]
public async Task<IActionResult> SetWorkflow(Guid id, [FromBody] SetWorkflowBody body)
{
await mediator.Send(new SetTravelRequestWorkflowCommand(id, body.ApprovalWorkflowId));
return NoContent();
}
public record SetWorkflowBody(Guid ApprovalWorkflowId);
public record UpdateTravelRequestBody(
string Destination,
DateTime StartDate,

View File

@ -64,6 +64,15 @@ public class VehicleBookingsController(IMediator mediator) : ControllerBase
return NoContent();
}
[HttpPut("{id:guid}/workflow")]
public async Task<IActionResult> SetWorkflow(Guid id, [FromBody] SetWorkflowBody body)
{
await mediator.Send(new SetVehicleBookingWorkflowCommand(id, body.ApprovalWorkflowId));
return NoContent();
}
public record SetWorkflowBody(Guid ApprovalWorkflowId);
public record UpdateVehicleBookingBody(
string VehicleLicense,
string? VehicleName,