[CLAUDE] PurchaseEvaluation: Chunk A — reorder section Hạng mục lên #2 + auto-tạo 1 row mặc định
All checks were successful
Deploy SOLUTION_ERP / build-deploy (push) Successful in 3m20s

Session 20 UI restructure (3 yêu cầu user). Chunk A xử lý:

BE — CreatePurchaseEvaluationCommandHandler thêm 1 PurchaseEvaluationDetail
mặc định khi tạo phiếu mới:
  - GroupCode="01", GroupName="Hạng mục chính"
  - NoiDung = TenGoiThau (tên gói thầu)
  - DonGiaNganSach = ThanhTienNganSach = Budget.TongNganSach (nếu link)
    fallback BudgetManualAmount fallback 0
  - DonViTinh="gói", KL=1, Order=1
  - Changelog entry kèm theo (audit Insert Detail)

FE — Đổi thứ tự 5 section trong PeDetailTabs.tsx (mirror 2 app):
  1. Thông tin gói thầu (giữ)
  2. Hạng mục + Báo giá (chuyển từ #4 lên #2)
  3. Chọn NCC / TP (từ #2 xuống #3)
  4. NCC / TP tham gia (từ #3 xuống #4 — Chunk B sẽ gộp vào #2 nested)
  5. Ý kiến cấp duyệt (giữ)

Q1=a: Giữ Section "Chọn NCC TP thắng thầu" riêng (rõ UX).
Q2=a "1 hạng mục trước tiên": auto-seed đủ, multi-hạng-mục defer.

Verify:
- dotnet build SolutionErp.slnx — 0 warning / 0 error
- Test pass mặc định skip (Phase 9 UAT iteration, Q4 user public luôn)

Pending Chunk B: Nested grid Hạng mục → NCC expand inline edit
Pending Chunk C: Section 5 gộp đồng cấp cùng Phòng (1 box / Step)
Pending Chunk D: Docs S20 changelog + STATUS + HANDOFF

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
pqhuy1987
2026-05-11 09:54:12 +07:00
parent 7710d4345c
commit 9dee00da01
3 changed files with 50 additions and 12 deletions

View File

@ -161,19 +161,22 @@ export function PeDetailTabs({
</div> </div>
<div className="divide-y divide-slate-200"> <div className="divide-y divide-slate-200">
{/* Section 1 — đúng spec form FO-PHIẾU TRÌNH KÝ CHỌN TP/NCC */} {/* Section order (Session 20): Hạng mục lên đầu sau Thông tin gói thầu.
BE auto-tạo 1 hạng mục mặc định (tên = TenGoiThau, giá trị = ngân sách)
khi Create. NCC tham gia tạm giữ riêng — Chunk B sẽ gộp NCC nested
expand dưới mỗi hạng mục. */}
<Section title="1. Thông tin gói thầu"> <Section title="1. Thông tin gói thầu">
<InfoTab ev={evaluation} readOnly={readOnly} autoEdit={autoEditHeader} /> <InfoTab ev={evaluation} readOnly={readOnly} autoEdit={autoEditHeader} />
</Section> </Section>
<Section title="2. Chọn NCC / TP"> <Section title={`2. Hạng mục + Báo giá (${evaluation.details.length})`}>
<ItemsTab ev={evaluation} readOnly={readOnly} />
</Section>
<Section title="3. Chọn NCC / TP">
<ChonNccSection ev={evaluation} readOnly={readOnly} /> <ChonNccSection ev={evaluation} readOnly={readOnly} />
</Section> </Section>
<Section title={`3. NCC / TP tham gia (${evaluation.suppliers.length})`}> <Section title={`4. NCC / TP tham gia (${evaluation.suppliers.length})`}>
<SuppliersTab ev={evaluation} readOnly={readOnly} /> <SuppliersTab ev={evaluation} readOnly={readOnly} />
</Section> </Section>
<Section title={`4. Hạng mục + Báo giá (${evaluation.details.length})`}>
<ItemsTab ev={evaluation} readOnly={readOnly} />
</Section>
<Section title="5. Ý kiến cấp duyệt (sign-off theo workflow)"> <Section title="5. Ý kiến cấp duyệt (sign-off theo workflow)">
{mode === 'workspace' && ( {mode === 'workspace' && (
<div className="mb-3 rounded border border-amber-200 bg-amber-50 px-3 py-2 text-[12px] text-amber-800"> <div className="mb-3 rounded border border-amber-200 bg-amber-50 px-3 py-2 text-[12px] text-amber-800">

View File

@ -161,19 +161,22 @@ export function PeDetailTabs({
</div> </div>
<div className="divide-y divide-slate-200"> <div className="divide-y divide-slate-200">
{/* Section 1 — đúng spec form FO-PHIẾU TRÌNH KÝ CHỌN TP/NCC */} {/* Section order (Session 20): Hạng mục lên đầu sau Thông tin gói thầu.
BE auto-tạo 1 hạng mục mặc định (tên = TenGoiThau, giá trị = ngân sách)
khi Create. NCC tham gia tạm giữ riêng — Chunk B sẽ gộp NCC nested
expand dưới mỗi hạng mục. */}
<Section title="1. Thông tin gói thầu"> <Section title="1. Thông tin gói thầu">
<InfoTab ev={evaluation} readOnly={readOnly} autoEdit={autoEditHeader} /> <InfoTab ev={evaluation} readOnly={readOnly} autoEdit={autoEditHeader} />
</Section> </Section>
<Section title="2. Chọn NCC / TP"> <Section title={`2. Hạng mục + Báo giá (${evaluation.details.length})`}>
<ItemsTab ev={evaluation} readOnly={readOnly} />
</Section>
<Section title="3. Chọn NCC / TP">
<ChonNccSection ev={evaluation} readOnly={readOnly} /> <ChonNccSection ev={evaluation} readOnly={readOnly} />
</Section> </Section>
<Section title={`3. NCC / TP tham gia (${evaluation.suppliers.length})`}> <Section title={`4. NCC / TP tham gia (${evaluation.suppliers.length})`}>
<SuppliersTab ev={evaluation} readOnly={readOnly} /> <SuppliersTab ev={evaluation} readOnly={readOnly} />
</Section> </Section>
<Section title={`4. Hạng mục + Báo giá (${evaluation.details.length})`}>
<ItemsTab ev={evaluation} readOnly={readOnly} />
</Section>
<Section title="5. Ý kiến cấp duyệt (sign-off theo workflow)"> <Section title="5. Ý kiến cấp duyệt (sign-off theo workflow)">
{mode === 'workspace' && ( {mode === 'workspace' && (
<div className="mb-3 rounded border border-amber-200 bg-amber-50 px-3 py-2 text-[12px] text-amber-800"> <div className="mb-3 rounded border border-amber-200 bg-amber-50 px-3 py-2 text-[12px] text-amber-800">

View File

@ -77,6 +77,7 @@ public class CreatePurchaseEvaluationCommandHandler(
// Validate Budget link (nếu có): cùng Project + Phase=DaDuyet (chỉ cho // Validate Budget link (nếu có): cùng Project + Phase=DaDuyet (chỉ cho
// pick ngân sách đã duyệt mới được dùng làm reference đối chiếu). // pick ngân sách đã duyệt mới được dùng làm reference đối chiếu).
decimal? linkedBudgetTotal = null;
if (request.BudgetId is Guid bid) if (request.BudgetId is Guid bid)
{ {
var bg = await db.Budgets.AsNoTracking() var bg = await db.Budgets.AsNoTracking()
@ -86,6 +87,7 @@ public class CreatePurchaseEvaluationCommandHandler(
throw new ConflictException("Ngân sách phải cùng dự án với phiếu."); throw new ConflictException("Ngân sách phải cùng dự án với phiếu.");
if (bg.Phase != Domain.Budgets.BudgetPhase.DaDuyet) if (bg.Phase != Domain.Budgets.BudgetPhase.DaDuyet)
throw new ConflictException("Chỉ link được ngân sách đã duyệt."); throw new ConflictException("Chỉ link được ngân sách đã duyệt.");
linkedBudgetTotal = bg.TongNganSach;
} }
var entity = new PurchaseEvaluation var entity = new PurchaseEvaluation
@ -124,6 +126,36 @@ public class CreatePurchaseEvaluationCommandHandler(
Summary = $"Tạo phiếu {entity.MaPhieu} — {entity.TenGoiThau}", Summary = $"Tạo phiếu {entity.MaPhieu} — {entity.TenGoiThau}",
}); });
// Auto-seed 1 Hạng mục mặc định lấy tên + giá trị từ gói thầu / ngân sách
// — user yêu cầu Session 20: hạng mục là đơn vị làm việc chính, NCC expand
// dưới hạng mục → cần có sẵn 1 row khi vào Detail. Có thể edit lại sau.
var defaultBudgetValue = linkedBudgetTotal ?? request.BudgetManualAmount ?? 0m;
var defaultDetail = new PurchaseEvaluationDetail
{
PurchaseEvaluationId = entity.Id,
GroupCode = "01",
GroupName = "Hạng mục chính",
NoiDung = request.TenGoiThau,
DonViTinh = "gói",
KhoiLuongNganSach = 1m,
KhoiLuongThiCong = 1m,
DonGiaNganSach = defaultBudgetValue,
ThanhTienNganSach = defaultBudgetValue,
Order = 1,
};
db.PurchaseEvaluationDetails.Add(defaultDetail);
db.PurchaseEvaluationChangelogs.Add(new PurchaseEvaluationChangelog
{
PurchaseEvaluationId = entity.Id,
EntityType = PurchaseEvaluationEntityType.Detail,
EntityId = defaultDetail.Id,
Action = ChangelogAction.Insert,
PhaseAtChange = entity.Phase,
UserId = currentUser.UserId,
Summary = $"Hạng mục mặc định — {defaultDetail.NoiDung}",
});
await db.SaveChangesAsync(ct); await db.SaveChangesAsync(ct);
return entity.Id; return entity.Id;
} }