[CLAUDE] PurchaseEvaluation · FE-User · FE-Admin · Tests: PE note "Duyệt là KẾT THÚC quy trình (không trình CEO)" — 3 surface
All checks were successful
Deploy SOLUTION_ERP / build-deploy (push) Successful in 4m58s
All checks were successful
Deploy SOLUTION_ERP / build-deploy (push) Successful in 4m58s
Phiếu mà quy trình duyệt có cấp bật AllowApproverFinalize (Mig 58) sẽ kết thúc tại cấp đó, KHÔNG trình CEO. Hiển thị note cho MỌI NGƯỜI NẮM ở 3 surface (anh Kiệt FDC). KHÔNG migration (cột AllowApproverFinalize đã có Mig 58, prod ở Mig 59). BE (3 file, computed DTO): - PurchaseEvaluationDtos.cs: +bool EndsBeforeCeo +string? FinalizeStepName +string? FinalizeLevelName trên DetailBundle + ListItem; flow-level +bool AllowApproverFinalize. - PurchaseEvaluationFeatures.cs: detail compute first-reachable (Step.Order↑→Level.Order↑, dept-name in-memory); list + inbox projection qua 3 subquery EF-safe (lv.Step!.Name). V1 (workflowId==null) → false. - CreateContractFromEvaluationFeatures.cs: ListApproved projection cùng DTO → +3 subquery y hệt. FE 2-app (fe-user + fe-admin SHA-mirror component): - PeFinalizeChip.tsx (MỚI, chip tím) ở 3 danh sách: PeListPanel + PurchaseEvaluationsListPage (cây) + InboxPage. - PeWorkflowPanel.tsx: note callout trong phiếu, branch DaDuyet (past-tense) / đang-chạy; copy boundary-aware "...là KẾT THÚC, không trình CEO. Các cấp trước vẫn duyệt như thường." (tránh cấp giữa hiểu nhầm). - UserDashboardPage.tsx (chỉ fe-user): dải "Phiếu Duyệt NCC gần đây" + chip (admin dashboard để nguyên). - types: PeListItem/PeDetailBundle/PeApprovalFlowLevel +field. Tests: +PeFinalizeProjectionTests (EF-translate smoke 3 subquery + first-reachable + V1-false). Full suite 421→422. em-main self-gate sau workflow (impl-frontend return rỗng #53 → mirror fe-admin + verify 5 site recover-disk). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@ -189,6 +189,19 @@ public class ListApprovedPurchaseEvaluationsQueryHandler(IApplicationDbContext d
|
||||
where sw.PurchaseEvaluationId == e.Id && sw.IsWinner
|
||||
join sup in db.Suppliers on sw.SupplierId equals sup.Id
|
||||
orderby sup.Name
|
||||
select sup.Name).ToList())).ToListAsync(ct); // [S69] cờ gấp + [Mig 58] winner names
|
||||
select sup.Name).ToList(), // [S69] cờ gấp + [Mig 58] winner names
|
||||
// [S89 Mig 58] "Duyệt là KẾT THÚC (không trình CEO)" — 3 subquery riêng mirror
|
||||
// winner-names (EF-safe). Cùng DTO PurchaseEvaluationListItemDto. V1 → false/null.
|
||||
(from lv in db.ApprovalWorkflowLevels
|
||||
where lv.AllowApproverFinalize && lv.Step!.ApprovalWorkflowId == e.ApprovalWorkflowId
|
||||
select 1).Any(),
|
||||
(from lv in db.ApprovalWorkflowLevels
|
||||
where lv.AllowApproverFinalize && lv.Step!.ApprovalWorkflowId == e.ApprovalWorkflowId
|
||||
orderby lv.Step!.Order, lv.Order
|
||||
select lv.Step!.Name).FirstOrDefault(),
|
||||
(from lv in db.ApprovalWorkflowLevels
|
||||
where lv.AllowApproverFinalize && lv.Step!.ApprovalWorkflowId == e.ApprovalWorkflowId
|
||||
orderby lv.Step!.Order, lv.Order
|
||||
select lv.Name).FirstOrDefault())).ToListAsync(ct);
|
||||
}
|
||||
}
|
||||
|
||||
@ -42,7 +42,13 @@ public record PurchaseEvaluationListItemDto(
|
||||
bool IsUrgentByCcm,
|
||||
// [Mig 58 multi-winner] Tên các đơn vị IsWinner (FE list card join ", "; ≥2 = liên-danh).
|
||||
// Thay nhãn từ SelectedSupplierName (null khi ≥2 cùng trúng → card trống). [] khi chưa chọn.
|
||||
List<string> WinnerSupplierNames);
|
||||
List<string> WinnerSupplierNames,
|
||||
// [S89 Mig 58 anh Kiệt FDC] Quy trình pin có Cấp "Duyệt là KẾT THÚC (không trình
|
||||
// CEO)" → FE render chip/note trên card list + sidebar. "first-reachable finalize"
|
||||
// theo Step.Order tăng → Level.Order tăng. V1 (ApprovalWorkflowId==null) → false/null.
|
||||
bool EndsBeforeCeo,
|
||||
string? FinalizeStepName,
|
||||
string? FinalizeLevelName);
|
||||
|
||||
public record PurchaseEvaluationSupplierDto(
|
||||
Guid Id,
|
||||
@ -155,7 +161,10 @@ public record PurchaseEvaluationApprovalFlowLevelDto(
|
||||
int Order, // 1/2/3 trong Step
|
||||
string? Name,
|
||||
List<PurchaseEvaluationApprovalLevelApproverDto> Approvers,
|
||||
string Status); // "Done" | "Current" | "Pending"
|
||||
string Status, // "Done" | "Current" | "Pending"
|
||||
// [S89 Mig 58 anh Kiệt FDC] Cấp này (OR-of-N rows cùng Order) có NV nào bật cờ
|
||||
// "Duyệt là KẾT THÚC quy trình" → FE render NOTE/chip trong flow Bước/Cấp.
|
||||
bool AllowApproverFinalize);
|
||||
|
||||
public record PurchaseEvaluationApprovalFlowStepDto(
|
||||
int Order, // 1-based
|
||||
@ -297,6 +306,13 @@ public record PurchaseEvaluationDetailBundleDto(
|
||||
// Mig 26 (Session 19) — Section 5 V2 dynamic. Empty list cho phiếu V1
|
||||
// legacy hoặc phiếu V2 chưa có cấp nào duyệt → FE fallback message.
|
||||
List<PurchaseEvaluationLevelOpinionDto> LevelOpinions,
|
||||
// [S89 Mig 58 anh Kiệt FDC] Quy trình pin có Cấp bật "Duyệt là KẾT THÚC (không
|
||||
// trình CEO)" → FE hiển thị NOTE cho MỌI người ở detail. "first-reachable
|
||||
// finalize" = Step.Order tăng → Level.Order tăng (đúng nơi flow THỰC kết thúc).
|
||||
// V1 (ApprovalWorkflowId==null) → EndsBeforeCeo=false, names null.
|
||||
bool EndsBeforeCeo,
|
||||
string? FinalizeStepName,
|
||||
string? FinalizeLevelName,
|
||||
PurchaseEvaluationWorkflowSummaryDto Workflow);
|
||||
|
||||
// [S61 Mig 50] Bảng "TỔNG HỢP NGÂN SÁCH TRÌNH KÝ" theo Excel anh Kiệt — BE compute
|
||||
|
||||
@ -633,7 +633,22 @@ public class ListPurchaseEvaluationsQueryHandler(
|
||||
where sw.PurchaseEvaluationId == x.e.Id && sw.IsWinner
|
||||
join sup in db.Suppliers on sw.SupplierId equals sup.Id
|
||||
orderby sup.Name
|
||||
select sup.Name).ToList()))
|
||||
select sup.Name).ToList(),
|
||||
// [S89 Mig 58] "Duyệt là KẾT THÚC (không trình CEO)" qua 3 subquery riêng
|
||||
// (mirror winner-names — EF-safe; record-ctor projection KHÔNG cho biến trung
|
||||
// gian nên không gộp tuple). lv.Step!.Name = simple navigation chắc translate.
|
||||
// ApprovalWorkflowId==null → subquery rỗng → false/null (V1 tự đúng).
|
||||
(from lv in db.ApprovalWorkflowLevels
|
||||
where lv.AllowApproverFinalize && lv.Step!.ApprovalWorkflowId == x.e.ApprovalWorkflowId
|
||||
select 1).Any(),
|
||||
(from lv in db.ApprovalWorkflowLevels
|
||||
where lv.AllowApproverFinalize && lv.Step!.ApprovalWorkflowId == x.e.ApprovalWorkflowId
|
||||
orderby lv.Step!.Order, lv.Order
|
||||
select lv.Step!.Name).FirstOrDefault(),
|
||||
(from lv in db.ApprovalWorkflowLevels
|
||||
where lv.AllowApproverFinalize && lv.Step!.ApprovalWorkflowId == x.e.ApprovalWorkflowId
|
||||
orderby lv.Step!.Order, lv.Order
|
||||
select lv.Name).FirstOrDefault()))
|
||||
.ToListAsync(ct);
|
||||
|
||||
return new PagedResult<PurchaseEvaluationListItemDto>(items, total, request.Page, request.PageSize);
|
||||
@ -735,7 +750,21 @@ public class GetMyPurchaseEvaluationInboxQueryHandler(
|
||||
where sw.PurchaseEvaluationId == x.e.Id && sw.IsWinner
|
||||
join sup in db.Suppliers on sw.SupplierId equals sup.Id
|
||||
orderby sup.Name
|
||||
select sup.Name).ToList()))
|
||||
select sup.Name).ToList(),
|
||||
// [S89 Mig 58] "Duyệt là KẾT THÚC (không trình CEO)" — 3 subquery riêng mirror
|
||||
// winner-names (EF-safe). lv.Step!.Name simple navigation. ApprovalWorkflowId
|
||||
// ==null → subquery rỗng → false/null (V1 tự đúng).
|
||||
(from lv in db.ApprovalWorkflowLevels
|
||||
where lv.AllowApproverFinalize && lv.Step!.ApprovalWorkflowId == x.e.ApprovalWorkflowId
|
||||
select 1).Any(),
|
||||
(from lv in db.ApprovalWorkflowLevels
|
||||
where lv.AllowApproverFinalize && lv.Step!.ApprovalWorkflowId == x.e.ApprovalWorkflowId
|
||||
orderby lv.Step!.Order, lv.Order
|
||||
select lv.Step!.Name).FirstOrDefault(),
|
||||
(from lv in db.ApprovalWorkflowLevels
|
||||
where lv.AllowApproverFinalize && lv.Step!.ApprovalWorkflowId == x.e.ApprovalWorkflowId
|
||||
orderby lv.Step!.Order, lv.Order
|
||||
select lv.Name).FirstOrDefault()))
|
||||
.Take(100)
|
||||
.ToListAsync(ct);
|
||||
}
|
||||
@ -951,6 +980,13 @@ public class GetPurchaseEvaluationQueryHandler(
|
||||
ApprovalWorkflowOptionsDto? currentLevelOptions = null;
|
||||
PurchaseEvaluationCurrentApprovalDto? currentApproval = null;
|
||||
PurchaseEvaluationApprovalFlowDto? approvalFlow = null;
|
||||
// [S89 Mig 58 anh Kiệt FDC] "Duyệt là KẾT THÚC quy trình (không trình CEO)" —
|
||||
// first-reachable Cấp bật AllowApproverFinalize theo Step.Order tăng → Level.Order
|
||||
// tăng. V1 (aw==null) giữ default false/null. Names dùng dept-name thân-thiện
|
||||
// (mirror step-label build flow ~:1084), fallback Step.Name.
|
||||
bool endsBeforeCeo = false;
|
||||
string? finalizeStepName = null;
|
||||
string? finalizeLevelName = null;
|
||||
if (e.ApprovalWorkflowId is Guid awId)
|
||||
{
|
||||
var aw = await db.ApprovalWorkflows.AsNoTracking()
|
||||
@ -1075,7 +1111,10 @@ public class GetPurchaseEvaluationQueryHandler(
|
||||
var levelName = g.FirstOrDefault()?.Name;
|
||||
return new PurchaseEvaluationApprovalFlowLevelDto(
|
||||
g.Key, levelName, approvers,
|
||||
ComputeLevelStatus(i, g.Key));
|
||||
ComputeLevelStatus(i, g.Key),
|
||||
// [S89 Mig 58] OR-of-N rows cùng Order: bất kỳ NV trong cấp bật cờ
|
||||
// → cấp này là điểm KẾT THÚC (FE render note trong flow).
|
||||
g.Any(l => l.AllowApproverFinalize));
|
||||
}).ToList();
|
||||
|
||||
flowSteps.Add(new PurchaseEvaluationApprovalFlowStepDto(
|
||||
@ -1088,6 +1127,20 @@ public class GetPurchaseEvaluationQueryHandler(
|
||||
|
||||
approvalFlow = new PurchaseEvaluationApprovalFlowDto(currentIdx, currentLevel, flowSteps);
|
||||
|
||||
// [S89 Mig 58] First-reachable finalize theo Step.Order tăng → Level.Order
|
||||
// tăng (steps đã OrderBy Order). Tên Bước ưu tiên dept-name thân-thiện
|
||||
// (mirror flow step-label ~:1084), fallback Step.Name.
|
||||
var finalizeStep = steps.FirstOrDefault(s => s.Levels.Any(l => l.AllowApproverFinalize));
|
||||
if (finalizeStep is not null)
|
||||
{
|
||||
var finalizeLevel = finalizeStep.Levels
|
||||
.Where(l => l.AllowApproverFinalize).OrderBy(l => l.Order).FirstOrDefault();
|
||||
endsBeforeCeo = true;
|
||||
finalizeStepName = finalizeStep.DepartmentId is Guid fsd
|
||||
&& stepDeptNames.TryGetValue(fsd, out var fsdn) ? fsdn : finalizeStep.Name;
|
||||
finalizeLevelName = finalizeLevel?.Name;
|
||||
}
|
||||
|
||||
// CurrentApproval (legacy banner) — chỉ populate nếu Phase=ChoDuyet
|
||||
if (phase == PurchaseEvaluationPhase.ChoDuyet
|
||||
&& currentIdx is int idxCur && currentLevel is int lvlCur
|
||||
@ -1185,6 +1238,7 @@ public class GetPurchaseEvaluationQueryHandler(
|
||||
o.Opinion, o.SignedAt, o.UserId, o.UserName))
|
||||
.ToList(),
|
||||
await BuildLevelOpinionsAsync(e, ct),
|
||||
endsBeforeCeo, finalizeStepName, finalizeLevelName, // [S89 Mig 58] Duyệt = KẾT THÚC (không trình CEO)
|
||||
new PurchaseEvaluationWorkflowSummaryDto(
|
||||
policy.Name, policy.Description,
|
||||
policy.ActivePhases.ToList(),
|
||||
|
||||
Reference in New Issue
Block a user