[CLAUDE] Contract: KHKK thung rac theo khuon man Duyet NCC — banner + dau xoa (FE-only, 0-mig)
All checks were successful
Deploy SOLUTION_ERP / build-deploy (push) Successful in 6m41s
All checks were successful
Deploy SOLUTION_ERP / build-deploy (push) Successful in 6m41s
Owner gui anh man "Da xoa" cua module Duyet NCC lam MAU ("anh chi la mau —
em soi lai KHKK"). Do KHKK thieu 2 thu so voi mau:
1. Banner do "🗑 Phieu da xoa — chi xem. Khong khoi phuc, khong thao tac."
PE co o PurchaseEvaluationsListPage:429; KHKK truoc do chi co tooltip hover
o panel 2, khong co banner nhin thay duoc.
2. Dau "🗑 xoa ~ gio ngay" tren tung the. PE co o :584; KhkkPlanCard = 0 hit.
Banner THAY dropdown trang thai khi xem thung rac (mirror PE): phieu giu
NGUYEN phase luc bi xoa, `IsDeleted` moi la thu phan loai => loc trang thai
o day la nut chet. Chinh `plansForPe` cung co y KHONG loc phase khi xem
thung rac, nen de dropdown lai thi bam ma khong thay gi doi.
🔸 CO Y BO ve "khong con tinh vao luy ke ngan sach" cua ban PE:
`grep 'lũy kế'` trong toan bo components/khkk + pages/khkk = 0 hit => be
nguyen cau do la khai mot dieu chua chung minh cho module nay. Giu dung
phan verify duoc tu chinh ma: khong khoi phuc, khong thao tac.
🔸 Dau xoa dung `updatedAt` chu KHONG phai `deletedAt`: API /deleted tra DTO
DUNG CHUNG voi list song nen khong co `deletedAt`/`deletedBy` (KhkkListItemDto
chi co createdAt + updatedAt). `AuditingInterceptor` ghi updatedAt ngay luot
SaveChanges cua xoa mem => XAP XI. Nhan "~" de khong hua chinh xac; ten nguoi
xoa API khong tra nen co y bo trong thay vi doan. Y het ly do ban PE.
Co truyen o CA HAI cho dung the (than cay + danh sach phang) — thieu 1 cho la
cung mot phieu hien khac nhau o 2 che-do.
mirror sha256 khop; build xanh x2 app; tsc sach x2.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@ -105,12 +105,15 @@ export function KhkkPlanCard({
|
|||||||
selected,
|
selected,
|
||||||
onSelect,
|
onSelect,
|
||||||
finalize,
|
finalize,
|
||||||
|
deleted,
|
||||||
}: {
|
}: {
|
||||||
plan: KhkkListItemDto
|
plan: KhkkListItemDto
|
||||||
selected: boolean
|
selected: boolean
|
||||||
onSelect: (id: string) => void
|
onSelect: (id: string) => void
|
||||||
/** Chỉ có khi phiếu ĐANG được chọn (detail đã tải) — list payload không mang cờ này. */
|
/** Chỉ có khi phiếu ĐANG được chọn (detail đã tải) — list payload không mang cờ này. */
|
||||||
finalize?: { endedByLevelFinalize: boolean; finalizeStepName: string | null }
|
finalize?: { endedByLevelFinalize: boolean; finalizeStepName: string | null }
|
||||||
|
/** [S176] Đang xem thùng rác ⇒ hiện dấu thời-điểm xóa. Mirror khuôn màn Duyệt NCC. */
|
||||||
|
deleted?: boolean
|
||||||
}) {
|
}) {
|
||||||
const frozen = plan.phase === KhkkPhase.DaDuyet
|
const frozen = plan.phase === KhkkPhase.DaDuyet
|
||||||
const amount = frozen ? (plan.totalApprovedAmount ?? plan.totalProposedAmount) : plan.totalProposedAmount
|
const amount = frozen ? (plan.totalApprovedAmount ?? plan.totalProposedAmount) : plan.totalProposedAmount
|
||||||
@ -160,6 +163,22 @@ export function KhkkPlanCard({
|
|||||||
)}
|
)}
|
||||||
<span>·</span>
|
<span>·</span>
|
||||||
<span className="truncate">{formatDateTime(plan.createdAt)}</span>
|
<span className="truncate">{formatDateTime(plan.createdAt)}</span>
|
||||||
|
{/* [S176] "Xóa lúc" — mirror khuôn màn Duyệt NCC (`PurchaseEvaluationsListPage:573`).
|
||||||
|
🔴 API `/contract-signing-plans/deleted` trả DTO DÙNG CHUNG với list sống ⇒ KHÔNG
|
||||||
|
có `deletedAt`/`deletedBy`. Mốc dưới là `updatedAt`, mà `AuditingInterceptor` ghi
|
||||||
|
ngay lượt SaveChanges của thao tác xóa mềm ⇒ XẤP XỈ thời điểm xóa. Nhãn "≈" để
|
||||||
|
KHÔNG hứa chính xác. Tên người xóa API không trả — cố ý bỏ trống thay vì đoán. */}
|
||||||
|
{deleted && plan.updatedAt && (
|
||||||
|
<>
|
||||||
|
<span>·</span>
|
||||||
|
<span
|
||||||
|
className="shrink-0 rounded bg-red-50 px-1 py-0.5 text-[10px] font-medium text-red-700"
|
||||||
|
title="Thời điểm xóa (xấp xỉ — lấy từ mốc cập nhật cuối, ghi cùng lượt xóa mềm). Xem lịch sử phiếu để biết ai xóa và lý do."
|
||||||
|
>
|
||||||
|
🗑 xóa ≈ {formatDateTime(plan.updatedAt)}
|
||||||
|
</span>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
<div className="mt-0.5 flex items-center justify-between gap-2 text-[11px] text-slate-500">
|
<div className="mt-0.5 flex items-center justify-between gap-2 text-[11px] text-slate-500">
|
||||||
<span className="min-w-0 truncate">👤 {plan.drafterFullName ?? '—'}</span>
|
<span className="min-w-0 truncate">👤 {plan.drafterFullName ?? '—'}</span>
|
||||||
@ -293,6 +312,7 @@ export function KhkkListPanel({
|
|||||||
selected={selectedId === k.id}
|
selected={selectedId === k.id}
|
||||||
onSelect={deletedView ? () => { /* phiếu đã xóa: chỉ liệt kê, không mở */ } : onSelect}
|
onSelect={deletedView ? () => { /* phiếu đã xóa: chỉ liệt kê, không mở */ } : onSelect}
|
||||||
finalize={selectedId === k.id ? selectedFinalize : undefined}
|
finalize={selectedId === k.id ? selectedFinalize : undefined}
|
||||||
|
deleted={deletedView}
|
||||||
/>
|
/>
|
||||||
</li>
|
</li>
|
||||||
))}
|
))}
|
||||||
@ -316,6 +336,19 @@ export function KhkkListPanel({
|
|||||||
className="pl-8"
|
className="pl-8"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
{/* [S176] Thùng rác ⇒ THAY dropdown bằng banner, mirror khuôn màn Duyệt NCC
|
||||||
|
(`PurchaseEvaluationsListPage:427-431`).
|
||||||
|
🔴 Ẩn dropdown là CÓ LÝ DO, không phải cho gọn: phiếu giữ NGUYÊN phase lúc bị xóa,
|
||||||
|
`IsDeleted` mới là thứ phân loại ⇒ lọc trạng thái ở đây là nút chết. Chính `plansForPe`
|
||||||
|
bên dưới cũng cố ý KHÔNG lọc phase khi xem thùng rác — để dropdown lại thì người dùng
|
||||||
|
bấm mà không thấy gì đổi.
|
||||||
|
🔸 CỐ Ý BỎ vế "không còn tính vào lũy kế ngân sách" của bản PE: `grep 'lũy kế'` trong
|
||||||
|
toàn bộ KHKK = 0 hit ⇒ bê nguyên là khai một điều chưa chứng minh cho module này. */}
|
||||||
|
{deletedView ? (
|
||||||
|
<div className="rounded border border-red-200 bg-red-50 px-2 py-1.5 text-[11px] text-red-700">
|
||||||
|
🗑 <strong>Phiếu đã xóa</strong> — chỉ xem. Không khôi phục, không thao tác.
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
<Select
|
<Select
|
||||||
value={phase ?? ''}
|
value={phase ?? ''}
|
||||||
aria-label="Lọc trạng thái"
|
aria-label="Lọc trạng thái"
|
||||||
@ -326,6 +359,7 @@ export function KhkkListPanel({
|
|||||||
<option key={p} value={p}>{KHKK_PHASE_LABELS[Number(p) as KhkkPhaseValue]}</option>
|
<option key={p} value={p}>{KHKK_PHASE_LABELS[Number(p) as KhkkPhaseValue]}</option>
|
||||||
))}
|
))}
|
||||||
</Select>
|
</Select>
|
||||||
|
)}
|
||||||
{/* 🧊 [S175] Dòng "Đang lọc — danh sách lấy thẳng từ máy chủ…" ĐÃ GỠ (anh: "Bỏ mấy dòng
|
{/* 🧊 [S175] Dòng "Đang lọc — danh sách lấy thẳng từ máy chủ…" ĐÃ GỠ (anh: "Bỏ mấy dòng
|
||||||
này đi"). Đó là chú-thích về CƠ-CHẾ bên trong, người dùng không cần biết cây tải 200
|
này đi"). Đó là chú-thích về CƠ-CHẾ bên trong, người dùng không cần biết cây tải 200
|
||||||
phiếu hay lấy thẳng máy chủ — họ chỉ cần thấy đúng phiếu. */}
|
phiếu hay lấy thẳng máy chủ — họ chỉ cần thấy đúng phiếu. */}
|
||||||
@ -412,6 +446,7 @@ export function KhkkListPanel({
|
|||||||
selected={selectedId === k.id}
|
selected={selectedId === k.id}
|
||||||
onSelect={deletedView ? () => { /* phiếu đã xóa: chỉ liệt kê, không mở */ } : onSelect}
|
onSelect={deletedView ? () => { /* phiếu đã xóa: chỉ liệt kê, không mở */ } : onSelect}
|
||||||
finalize={selectedId === k.id ? selectedFinalize : undefined}
|
finalize={selectedId === k.id ? selectedFinalize : undefined}
|
||||||
|
deleted={deletedView}
|
||||||
/>
|
/>
|
||||||
</li>
|
</li>
|
||||||
))}
|
))}
|
||||||
|
|||||||
@ -105,12 +105,15 @@ export function KhkkPlanCard({
|
|||||||
selected,
|
selected,
|
||||||
onSelect,
|
onSelect,
|
||||||
finalize,
|
finalize,
|
||||||
|
deleted,
|
||||||
}: {
|
}: {
|
||||||
plan: KhkkListItemDto
|
plan: KhkkListItemDto
|
||||||
selected: boolean
|
selected: boolean
|
||||||
onSelect: (id: string) => void
|
onSelect: (id: string) => void
|
||||||
/** Chỉ có khi phiếu ĐANG được chọn (detail đã tải) — list payload không mang cờ này. */
|
/** Chỉ có khi phiếu ĐANG được chọn (detail đã tải) — list payload không mang cờ này. */
|
||||||
finalize?: { endedByLevelFinalize: boolean; finalizeStepName: string | null }
|
finalize?: { endedByLevelFinalize: boolean; finalizeStepName: string | null }
|
||||||
|
/** [S176] Đang xem thùng rác ⇒ hiện dấu thời-điểm xóa. Mirror khuôn màn Duyệt NCC. */
|
||||||
|
deleted?: boolean
|
||||||
}) {
|
}) {
|
||||||
const frozen = plan.phase === KhkkPhase.DaDuyet
|
const frozen = plan.phase === KhkkPhase.DaDuyet
|
||||||
const amount = frozen ? (plan.totalApprovedAmount ?? plan.totalProposedAmount) : plan.totalProposedAmount
|
const amount = frozen ? (plan.totalApprovedAmount ?? plan.totalProposedAmount) : plan.totalProposedAmount
|
||||||
@ -160,6 +163,22 @@ export function KhkkPlanCard({
|
|||||||
)}
|
)}
|
||||||
<span>·</span>
|
<span>·</span>
|
||||||
<span className="truncate">{formatDateTime(plan.createdAt)}</span>
|
<span className="truncate">{formatDateTime(plan.createdAt)}</span>
|
||||||
|
{/* [S176] "Xóa lúc" — mirror khuôn màn Duyệt NCC (`PurchaseEvaluationsListPage:573`).
|
||||||
|
🔴 API `/contract-signing-plans/deleted` trả DTO DÙNG CHUNG với list sống ⇒ KHÔNG
|
||||||
|
có `deletedAt`/`deletedBy`. Mốc dưới là `updatedAt`, mà `AuditingInterceptor` ghi
|
||||||
|
ngay lượt SaveChanges của thao tác xóa mềm ⇒ XẤP XỈ thời điểm xóa. Nhãn "≈" để
|
||||||
|
KHÔNG hứa chính xác. Tên người xóa API không trả — cố ý bỏ trống thay vì đoán. */}
|
||||||
|
{deleted && plan.updatedAt && (
|
||||||
|
<>
|
||||||
|
<span>·</span>
|
||||||
|
<span
|
||||||
|
className="shrink-0 rounded bg-red-50 px-1 py-0.5 text-[10px] font-medium text-red-700"
|
||||||
|
title="Thời điểm xóa (xấp xỉ — lấy từ mốc cập nhật cuối, ghi cùng lượt xóa mềm). Xem lịch sử phiếu để biết ai xóa và lý do."
|
||||||
|
>
|
||||||
|
🗑 xóa ≈ {formatDateTime(plan.updatedAt)}
|
||||||
|
</span>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
<div className="mt-0.5 flex items-center justify-between gap-2 text-[11px] text-slate-500">
|
<div className="mt-0.5 flex items-center justify-between gap-2 text-[11px] text-slate-500">
|
||||||
<span className="min-w-0 truncate">👤 {plan.drafterFullName ?? '—'}</span>
|
<span className="min-w-0 truncate">👤 {plan.drafterFullName ?? '—'}</span>
|
||||||
@ -293,6 +312,7 @@ export function KhkkListPanel({
|
|||||||
selected={selectedId === k.id}
|
selected={selectedId === k.id}
|
||||||
onSelect={deletedView ? () => { /* phiếu đã xóa: chỉ liệt kê, không mở */ } : onSelect}
|
onSelect={deletedView ? () => { /* phiếu đã xóa: chỉ liệt kê, không mở */ } : onSelect}
|
||||||
finalize={selectedId === k.id ? selectedFinalize : undefined}
|
finalize={selectedId === k.id ? selectedFinalize : undefined}
|
||||||
|
deleted={deletedView}
|
||||||
/>
|
/>
|
||||||
</li>
|
</li>
|
||||||
))}
|
))}
|
||||||
@ -316,6 +336,19 @@ export function KhkkListPanel({
|
|||||||
className="pl-8"
|
className="pl-8"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
{/* [S176] Thùng rác ⇒ THAY dropdown bằng banner, mirror khuôn màn Duyệt NCC
|
||||||
|
(`PurchaseEvaluationsListPage:427-431`).
|
||||||
|
🔴 Ẩn dropdown là CÓ LÝ DO, không phải cho gọn: phiếu giữ NGUYÊN phase lúc bị xóa,
|
||||||
|
`IsDeleted` mới là thứ phân loại ⇒ lọc trạng thái ở đây là nút chết. Chính `plansForPe`
|
||||||
|
bên dưới cũng cố ý KHÔNG lọc phase khi xem thùng rác — để dropdown lại thì người dùng
|
||||||
|
bấm mà không thấy gì đổi.
|
||||||
|
🔸 CỐ Ý BỎ vế "không còn tính vào lũy kế ngân sách" của bản PE: `grep 'lũy kế'` trong
|
||||||
|
toàn bộ KHKK = 0 hit ⇒ bê nguyên là khai một điều chưa chứng minh cho module này. */}
|
||||||
|
{deletedView ? (
|
||||||
|
<div className="rounded border border-red-200 bg-red-50 px-2 py-1.5 text-[11px] text-red-700">
|
||||||
|
🗑 <strong>Phiếu đã xóa</strong> — chỉ xem. Không khôi phục, không thao tác.
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
<Select
|
<Select
|
||||||
value={phase ?? ''}
|
value={phase ?? ''}
|
||||||
aria-label="Lọc trạng thái"
|
aria-label="Lọc trạng thái"
|
||||||
@ -326,6 +359,7 @@ export function KhkkListPanel({
|
|||||||
<option key={p} value={p}>{KHKK_PHASE_LABELS[Number(p) as KhkkPhaseValue]}</option>
|
<option key={p} value={p}>{KHKK_PHASE_LABELS[Number(p) as KhkkPhaseValue]}</option>
|
||||||
))}
|
))}
|
||||||
</Select>
|
</Select>
|
||||||
|
)}
|
||||||
{/* 🧊 [S175] Dòng "Đang lọc — danh sách lấy thẳng từ máy chủ…" ĐÃ GỠ (anh: "Bỏ mấy dòng
|
{/* 🧊 [S175] Dòng "Đang lọc — danh sách lấy thẳng từ máy chủ…" ĐÃ GỠ (anh: "Bỏ mấy dòng
|
||||||
này đi"). Đó là chú-thích về CƠ-CHẾ bên trong, người dùng không cần biết cây tải 200
|
này đi"). Đó là chú-thích về CƠ-CHẾ bên trong, người dùng không cần biết cây tải 200
|
||||||
phiếu hay lấy thẳng máy chủ — họ chỉ cần thấy đúng phiếu. */}
|
phiếu hay lấy thẳng máy chủ — họ chỉ cần thấy đúng phiếu. */}
|
||||||
@ -412,6 +446,7 @@ export function KhkkListPanel({
|
|||||||
selected={selectedId === k.id}
|
selected={selectedId === k.id}
|
||||||
onSelect={deletedView ? () => { /* phiếu đã xóa: chỉ liệt kê, không mở */ } : onSelect}
|
onSelect={deletedView ? () => { /* phiếu đã xóa: chỉ liệt kê, không mở */ } : onSelect}
|
||||||
finalize={selectedId === k.id ? selectedFinalize : undefined}
|
finalize={selectedId === k.id ? selectedFinalize : undefined}
|
||||||
|
deleted={deletedView}
|
||||||
/>
|
/>
|
||||||
</li>
|
</li>
|
||||||
))}
|
))}
|
||||||
|
|||||||
Reference in New Issue
Block a user