[CLAUDE] PurchaseEvaluation: YC-028 sự cố prod A/059 — FE tách "404 thật ⟂ lỗi hệ thống" 5 site ×2 app + guard disk/orphan vào runbook+cicd-monitor
Sự cố 2 tầng (đã giải trọn trong phiên, chi tiết runs/2026-08-10-S189-bookstart-open/run.md + WAL): - T1: ổ C VPS 0 byte (gitea repo-archive 16,49GB + npm-cache SYSTEM 12,76GB) → SQL Error -2 → "ko duyệt được" rồi FE vẽ mọi lỗi thành "Không tìm thấy phiếu" (#44). Dọn ~30GB cache + AUTO_CLOSE OFF ×3 DB + gỡ nén NTFS 4 file template (#88 tái phát). - T2: 3 sshd mồ côi (lệnh đo ssh bị cắt output) bão hòa 3/3 core → mọi GET detail 5-47s, A/059 (5 NCC) vượt trần hủy ~30s của FE. Kill orphan → A/059 47s→0,38s · phiếu bé 0,17s. Code (5 site cùng lớp, SHA-mirror 4 cặp MATCH, build ×2 xanh): - lib/apiError.ts +isNotFound(err) — chỉ 404 mới được nói "không tìm thấy" - PurchaseEvaluationsListPage (inline + overlay + fullpage) + KhkkListPage + KhkkCreatePage: detail.isError && !404 → "Hệ thống đang chậm hoặc lỗi khi tải phiếu — thử lại (refetch)" Ops-guard (YC-028 (4)): cicd-monitor persona +bước 1b server-health (fsutil free <5GB = FAIL, CPU-delta tìm orphan) · iis-deploy-runbook +§"Disk-free + orphan guard" + sửa path log Serilog sai (Logs\log-*.txt → logs\solution-erp-*.log, đo thật). (6) đóng: log 10/08 mất do đợt "giải phóng dung lượng" hôm qua (retention 30 file config đúng). (3B) cron archive_cleanup Gitea + decompress Vietreport_Master = chờ ngoài giờ (restart service dùng chung VIETREPORT). Sổ: +YC-028 (nguyên văn + 2 tầng đã xử). Squash wal: trailing theo §5.0. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@ -11,3 +11,11 @@ export function getErrorMessage(err: unknown, fallback = 'Lỗi hệ thống'):
|
||||
}
|
||||
return fallback
|
||||
}
|
||||
|
||||
// [S189 · YC-028(5)] 404 thật ⟂ lỗi hệ thống/chậm — nền sự cố A/059: server nghẹt làm GET detail
|
||||
// bị hủy sau ~30s, FE gom mọi nhánh lỗi vào "Không tìm thấy phiếu" ⇒ người dùng lẫn người chẩn
|
||||
// đoán nghi oan DATA trong khi gốc là HẠ TẦNG (lớp gotcha #44 silent-error). Chỉ 404 mới được
|
||||
// nói "không tìm thấy"; mọi lỗi khác phải nói thật là lỗi hệ thống + cho thử lại.
|
||||
export function isNotFound(err: unknown): boolean {
|
||||
return axios.isAxiosError(err) && err.response?.status === 404
|
||||
}
|
||||
|
||||
@ -38,7 +38,7 @@ import { Textarea } from '@/components/ui/Textarea'
|
||||
import { KhkkDetailContent } from '@/components/khkk/KhkkDetailContent'
|
||||
import { KhkkWorkListPanel } from '@/components/khkk/KhkkWorkListPanel'
|
||||
import { api } from '@/lib/api'
|
||||
import { getErrorMessage } from '@/lib/apiError'
|
||||
import { getErrorMessage, isNotFound } from '@/lib/apiError'
|
||||
import { useAuth } from '@/contexts/AuthContext'
|
||||
import {
|
||||
KHKK_APPROVAL_GROUP_DEFAULT,
|
||||
@ -245,8 +245,16 @@ export function KhkkCreatePage() {
|
||||
tại TRƯỚC câu cảnh-báo đó. Thứ duy nhất mất đi là đường quay lại màn rỗng
|
||||
"Chọn phiếu Duyệt NCC…" (`:256`), mà đó chỉ là chỗ hướng-dẫn, không phải đích. */}
|
||||
{viewPlan.isLoading && <div className="text-sm text-slate-500">Đang tải…</div>}
|
||||
{/* [S189 · YC-028(5)] 404 thật ⟂ lỗi hệ thống — mirror PurchaseEvaluationsListPage. */}
|
||||
{!viewPlan.isLoading && !viewPlan.data && (
|
||||
<div className="text-sm text-red-600">Không tìm thấy phiếu.</div>
|
||||
viewPlan.isError && !isNotFound(viewPlan.error) ? (
|
||||
<div className="text-sm text-red-600">
|
||||
Hệ thống đang chậm hoặc lỗi khi tải phiếu —{' '}
|
||||
<button type="button" className="font-medium underline" onClick={() => viewPlan.refetch()}>thử lại</button>.
|
||||
</div>
|
||||
) : (
|
||||
<div className="text-sm text-red-600">Không tìm thấy phiếu.</div>
|
||||
)
|
||||
)}
|
||||
{viewPlan.data && (
|
||||
<KhkkDetailContent plan={viewPlan.data} readOnly={readOnly} onChanged={() => viewPlan.refetch()} onDeleted={() => { setViewPlanId(null); setEditPlan(false) }} />
|
||||
|
||||
@ -38,6 +38,7 @@ import { PeDetailTabs } from '@/components/pe/PeDetailTabs'
|
||||
import { type PeDetailBundle } from '@/types/purchaseEvaluation'
|
||||
import { useAuth } from '@/contexts/AuthContext'
|
||||
import { api } from '@/lib/api'
|
||||
import { isNotFound } from '@/lib/apiError'
|
||||
import {
|
||||
KhkkPhase,
|
||||
type KhkkDetailDto,
|
||||
@ -232,8 +233,16 @@ export function KhkkListPage() {
|
||||
)}
|
||||
|
||||
{selectedId && detail.isLoading && <div className="text-sm text-slate-500">Đang tải…</div>}
|
||||
{/* [S189 · YC-028(5)] 404 thật ⟂ lỗi hệ thống — mirror PurchaseEvaluationsListPage. */}
|
||||
{selectedId && !detail.isLoading && !plan && (
|
||||
<div className="text-sm text-red-600">Không tìm thấy phiếu.</div>
|
||||
detail.isError && !isNotFound(detail.error) ? (
|
||||
<div className="text-sm text-red-600">
|
||||
Hệ thống đang chậm hoặc lỗi khi tải phiếu —{' '}
|
||||
<button type="button" className="font-medium underline" onClick={() => detail.refetch()}>thử lại</button>.
|
||||
</div>
|
||||
) : (
|
||||
<div className="text-sm text-red-600">Không tìm thấy phiếu.</div>
|
||||
)
|
||||
)}
|
||||
{selectedId && plan && (
|
||||
<KhkkDetailContent plan={plan} readOnly={readOnly} onChanged={() => detail.refetch()} onDeleted={() => setId(null)} />
|
||||
|
||||
@ -12,7 +12,7 @@ import { Input } from '@/components/ui/Input'
|
||||
import { Select } from '@/components/ui/Select'
|
||||
import { EmptyState } from '@/components/EmptyState'
|
||||
import { api } from '@/lib/api'
|
||||
import { getErrorMessage } from '@/lib/apiError'
|
||||
import { getErrorMessage, isNotFound } from '@/lib/apiError'
|
||||
import { cn } from '@/lib/cn'
|
||||
import type { Paged } from '@/types/master'
|
||||
import {
|
||||
@ -651,8 +651,17 @@ export function PurchaseEvaluationsListPage() {
|
||||
/>
|
||||
)}
|
||||
{selectedId && detail.isLoading && <div className="text-sm text-slate-500">Đang tải…</div>}
|
||||
{/* [S189 · YC-028(5)] 404 thật ⟂ lỗi hệ thống — đừng để lỗi 500/timeout đội lốt
|
||||
"không tìm thấy" (sự cố A/059: phiếu CÒN NGUYÊN, server nghẹt). */}
|
||||
{selectedId && !detail.isLoading && !detail.data && (
|
||||
<div className="text-sm text-red-600">Không tìm thấy phiếu.</div>
|
||||
detail.isError && !isNotFound(detail.error) ? (
|
||||
<div className="text-sm text-red-600">
|
||||
Hệ thống đang chậm hoặc lỗi khi tải phiếu —{' '}
|
||||
<button type="button" className="font-medium underline" onClick={() => detail.refetch()}>thử lại</button>.
|
||||
</div>
|
||||
) : (
|
||||
<div className="text-sm text-red-600">Không tìm thấy phiếu.</div>
|
||||
)
|
||||
)}
|
||||
{selectedId && !isExpand && detail.data && (
|
||||
<PeDetailTabs
|
||||
@ -747,7 +756,14 @@ export function PurchaseEvaluationsListPage() {
|
||||
<div className="flex flex-1 items-center justify-center text-sm text-slate-500">Đang tải phiếu…</div>
|
||||
)}
|
||||
{!detail.isLoading && !detail.data && (
|
||||
<div className="flex flex-1 items-center justify-center p-6 text-sm text-red-600">Không tìm thấy phiếu.</div>
|
||||
detail.isError && !isNotFound(detail.error) ? (
|
||||
<div className="flex flex-1 items-center justify-center p-6 text-sm text-red-600">
|
||||
Hệ thống đang chậm hoặc lỗi khi tải phiếu —{' '}
|
||||
<button type="button" className="font-medium underline" onClick={() => detail.refetch()}>thử lại</button>.
|
||||
</div>
|
||||
) : (
|
||||
<div className="flex flex-1 items-center justify-center p-6 text-sm text-red-600">Không tìm thấy phiếu.</div>
|
||||
)
|
||||
)}
|
||||
{detail.data && (
|
||||
<div className="flex min-h-0 flex-1 flex-col overflow-y-auto lg:flex-row lg:overflow-hidden">
|
||||
@ -794,7 +810,16 @@ export function PurchaseEvaluationDetailPage() {
|
||||
},
|
||||
})
|
||||
if (detail.isLoading) return <div className="p-6 text-sm text-slate-500">Đang tải…</div>
|
||||
if (!detail.data) return <div className="p-6 text-sm text-red-600">Không tìm thấy phiếu.</div>
|
||||
if (!detail.data) {
|
||||
// [S189 · YC-028(5)] mirror 2 site trên — 404 thật mới được nói "không tìm thấy".
|
||||
if (detail.isError && !isNotFound(detail.error)) return (
|
||||
<div className="p-6 text-sm text-red-600">
|
||||
Hệ thống đang chậm hoặc lỗi khi tải phiếu —{' '}
|
||||
<button type="button" className="font-medium underline" onClick={() => detail.refetch()}>thử lại</button>.
|
||||
</div>
|
||||
)
|
||||
return <div className="p-6 text-sm text-red-600">Không tìm thấy phiếu.</div>
|
||||
}
|
||||
return (
|
||||
<div className="space-y-4 p-6">
|
||||
<PeDetailTabs evaluation={detail.data} onBack={() => navigate('/purchase-evaluations')} onDelete={() => del.mutate()} />
|
||||
|
||||
Reference in New Issue
Block a user