[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:
pqhuy1987
2026-08-11 11:25:51 +07:00
parent 9b3c0edba3
commit 92d8215a62
12 changed files with 175 additions and 47 deletions

View File

@ -1,39 +1,33 @@
# WAL — auto-generated, không sửa tay
updated: 2026-08-11 | session: S189 (phiên-LOGIC L15, window 1) | branch: main
# 🧊 Vá @S189 dòng nhãn phiên: bản trước gộp *conversation tiếp tục* (đúng, YC-026 sống nguyên) với
# *phiên-LOGIC chưa khép* (sai — `_end` L14, 6 bề mặt đồng thuận). Lineage → S189 `run.md`.
goal: Bookstart L15 chạy TRỌN 1 lượt (YC-018/YC-027) ĐÃ XONG — 12 vai, 8 lượt #53, 0 byte mất.
Đang chạy loạt vá từ finding của đội. Phiên GIỮ MỞ chờ việc kế của anh (YC-026).
goal: SỰ CỐ PROD A/059 (YC-028) — ĐÃ GIẢI TRỌN 2 TẦNG: (T1) disk 0-byte do cache automation
(repo-archive 16,49GB + npm-cache 12,76GB) → SQL Error -2 → "ko duyệt được"/#44;
(T2) 3 sshd MỒ CÔI (lệnh đo của lead bị `head` cắt) bão hòa 3/3 core → mọi phiếu 5-47s,
A/059 vượt trần hủy 30s FE. SAU FIX: A/059 47s→0,38s · phiếu bé 0,17s · inbox 0,12s.
chain:
[x] Bookstart @open TRỌN: V1 (H1 PASS_WITH_FLAGS-7 + H2 GATE-FAIL-5 → ring1 26Đ/4T/6KC/36) ·
V2 (stale 11 FLAG + gap 7 FLAG → ring2 7Đ/3T/14KC/24, enum-gate 18/18) · V3 trio
(eval MIXED 38-trục/13-REGRESSION → refine 2-action/16-BÁC → audit 24Đ/7T/4KC re-chạy 98 số) ·
V4 shard IM (NO-OP có khai) · V5 detector 58+26 · khối chấm điểm → KHÔNG ĐO ĐƯỢC (A8) ·
Phase 3.5 `_mind-s-15` block-0 qua mind-check 10/0 + ctx-audit
→ run-trace `.claude/workflows/runs/2026-08-10-S189-bookstart-open/`
[x] Vá 3 chỗ KHÔNG nằm bề-mặt-đo đóng băng: `CLAUDE.md:77``69, 71, 72` (ring1 phán
`ef-core-migration/SKILL.md` là DƯƠNG-GIẢ ⇒ vá 1 chỗ là đủ) · `contract-workflow/SKILL.md`
W5 "chưa làm" → ĐÃ LÀM `b275893c` (giữ vế liên-danh N-HĐ còn nợ THẬT) · WAL dòng 2 (trên)
[!] Loạt vá CÒN LẠI (chạy ngay sau khi ctx-audit đóng cửa mở — đều là bề-mặt-đo):
error-ledger entry TẦNG-2-SAI (L1+L2; 🔴 CẤM chạm `lead_self_audit.flag_classes` — đã niêm phong) ·
HANDOFF lật 3 mục NEXT-em đã land `ef1d4fbb` (harvest-curator 21.308 · reviewer 85,7% ·
measured 30 row 29/29) · `h24-signal-write.ps1` ghi tally 2 vai H24 · dọn stray dir RỖNG
trong run-folder (git MÙ với thư-mục rỗng ⇒ phải `find -type d -empty`)
[ ] chờ anh: (76) 2 governor sai đơn-vị [nay có chứng 0/29 vượt theo KÝ-TỰ] · (77) migration-todos
đóng băng S168 · (78) check-email:67 cãi P6 · (79) YC-002 · (89) 4 class vượt JUMP a/b/c ·
(74) rotate · MỚI: value_protect phủ khối hợp-đồng-vai · MỚI: lật ô YC-008↔YC-010 ·
YC-025 review 1 lần GĐ3 prod · "ổn rồi" mở W8-authz · tồn (22)(67)(72)
[ ] NEXT-em: gọi `ring5-audit` ở CẢ cửa MỞ (guard RCA ONESHOT-S188 hiện 0-hit `session-start.md`) ·
khắc YC-027 vào `/tiep`+`/pause` (nay 2/4 cửa) + dựng phép kiểm ĐỦ-CỬA · sửa regex
`**🆕 Last updated:**` trong detector (máy bám mốc cũ 9 ngày) · [carry:sentinel-truoc-bu] ·
[carry:reset-doi-het] · w7b-inbox-param
[x] Bookstart L15 TRỌN 12 vai + vá 7 bề mặt → push 9b3c0edb
[x] T1 disk: xóa repo-archive (cache Gitea tự tái tạo) + npm-cache SYSTEM + 567 u_ex cũ (0,73GB)
free ~20GB+ · health 200 · AUTO_CLOSE OFF ×3 DB · gỡ nén 4 file template (#88 tái phát;
Vietreport_Master.mdf CÒN NÉN — đang mở, cần offline ~10s, làm ngoài giờ)
[x] T2 CPU: chẩn đoán ASYNC_NETWORK_IO + query [p7] CPU-66ms-elapsed-23,6s → đo CPU-delta bắt
3 sshd orphan 6,3 core-s/5s trên máy 3-core → kill (chừa chain mình) → 0,17-0,41s ×2 lượt.
🔴 BÀI HỌC MỚI: `ssh ... | head` = ĐẺ ORPHAN SPIN phía Windows-OpenSSH — cấm head trên
ssh output dài; recycle pool + đợi cold-start ~48s trước khi đo
[!] Hàng (1)-(6) YC-028 còn: (3B) bật cron archive_cleanup Gitea + decompress Vietreport_Master
[ngoài giờ — restart gitea vài giây] · (4) disk-watch vào cicd-monitor persona + runbook ·
(5) FE tách "lỗi hệ thống"⟂"không tìm thấy" ×2 app + CÂN NHẮC AsSplitQuery cho
GetPurchaseEvaluationQuery:867 (6-include = mìn cartesian khi data lớn) · (6) truy log
20260810 biến mất (đọc Serilog retention — lệnh trước chết vì quoting, chưa có data)
[ ] chờ anh (cũ): (76)(77)(78)(79) · (89) a/b/c · (74) rotate · value_protect · YC-008↔YC-010 ·
YC-025 review GĐ3 · "ổn rồi" mở W8 · tồn (22)(67)(72)
[ ] NEXT-em (cũ, run.md S189): ring5 @open · YC-027 đủ-cửa · regex 🆕 detector · sentinel-truoc-bu ·
reset-doi-het · w7b-inbox-param · gotcha #89 disk+orphan-sshd + RCA error-ledger
next: đóng cửa mở (ctx-audit) → chạy loạt vá còn lại → đợi anh. Có lệnh mới ⇒ ghi mạch mới rồi chạy.
next: chạy (4)(5)(6) + gotcha #89; (3B) chờ ngoài giờ. Anh em xác nhận phiếu OK thì báo.
verify:
bookstart: ls .claude/workflows/runs/2026-08-10-S189-bookstart-open/*.md | wc -l # >= 12
mind: python scripts/session_ctx.py mind-check --session 15 # exit 0
🔴 CẤM chạy `PROPOSE archive` cho ring2-audit — move-set = {Role baseline, Runs} ⇒ xoá THƯỚC vai KIỂM.
toc-do: curl (token admin) GET A/059 -> 200 <1s # da do x2 lan
cpu: ssh delta 5s -> chi gitea ~0,3 core # da do sau kill
free: ssh fsutil volume diskfree C: # ~20GB+

View File

@ -121,6 +121,14 @@ $token = (curl -X POST https://api.solutions.com.vn/api/auth/login `
# Or UAT scope (non-admin): nv.test@solutions.com.vn / TestUser@123456
# 1b. 🔴 SERVER-HEALTH GUARD (S189 2026-08-11 — sự cố YC-028 disk-full + orphan-sshd; chi tiết
# + lệnh đầy đủ = iis-deploy-runbook §"Disk-free + orphan guard"; chạy MỖI lần smoke):
ssh vietreport-vps "fsutil volume diskfree C:" # free < 5GB = FAIL verdict kèm cảnh báo
# free từng về 0 (repo-archive Gitea 16,5GB + npm-cache SYSTEM 12,8GB) → SQL Error -2
# → FE hiện "Không tìm thấy phiếu" — TRIỆU CHỨNG DATA, GỐC HẠ TẦNG, đừng nghi oan phiếu.
# Kèm mắt thứ 2: smoke chậm bất thường (>5s cho GET detail) ⇒ đo CPU-delta tìm process
# mồ côi (sshd/powershell spin) THEO RUNBOOK trước khi kết luận code chậm.
# 2. Smoke 3-5 endpoint expected 2XX (include endpoint mới trong commit diff nếu có)
curl -X GET https://api.solutions.com.vn/api/contracts -H "Authorization: Bearer $token" -w "%{http_code}\n"
curl -X GET https://api.solutions.com.vn/api/purchase-evaluations -H "Authorization: Bearer $token" -w "%{http_code}\n"

View File

@ -78,8 +78,9 @@ Get-Website -Name "SolutionErp-*" | Format-Table Name, State, Bindings
### Xem log API
```powershell
# Serilog file rolling daily
Get-Content "C:\inetpub\solution-erp\api\Logs\log-$(Get-Date -Format 'yyyyMMdd').txt" -Tail 50
# Serilog file rolling daily (path ĐO THẬT @S189 2026-08-11 — bản cũ ghi `Logs\log-*.txt` SAI;
# config sống: appsettings.Production.json:42 `logs/solution-erp-.log`, retained 30, roll 100MB)
Get-Content "C:\inetpub\solution-erp\api\logs\solution-erp-$(Get-Date -Format 'yyyyMMdd').log" -Tail 50
# IIS log
Get-Content "C:\inetpub\logs\LogFiles\W3SVC<ID>\u_ex$(Get-Date -Format 'yyMMdd').log" -Tail 30
@ -99,6 +100,30 @@ curl http://127.0.0.1:5443/health/ready
curl https://api.solutions.com.vn/health/ready
```
### 🔴 Disk-free + orphan guard (S189 2026-08-11 — sự cố YC-028, PHẢI check khi prod chậm/500)
```powershell
# 1. Disk C: — sự cố thật: 0 byte (repo-archive Gitea 16,5GB + npm-cache SYSTEM 12,8GB)
# → SQL Error -2 (không mở nổi connection) → FE hiện "Không tìm thấy phiếu" (#44 class)
fsutil volume diskfree C: # free < 5GB = ĐỎ; cache tự phình: C:\gitea\data\repo-archive
# (xóa an toàn — Gitea tự tái tạo) + npm-cache SYSTEM (CI)
# 2. CPU orphan — sự cố thật: 3 sshd mồ côi (ssh bị cắt output giữa chừng) ăn 3/3 core
# → mọi API 5-47s, SQL wait ASYNC_NETWORK_IO (client đói CPU không nhận nổi data)
$a=@{}; Get-Process | ForEach-Object { $a[$_.Id]=$_.CPU }; Start-Sleep 5
Get-Process | ForEach-Object { if($a.ContainsKey($_.Id)){ $d=$_.CPU-$a[$_.Id]; if($d -ge 1){ "$($_.Name) $($_.Id) $([math]::Round($d,1))" } } }
# sshd/powershell lạ ăn ≥1 core-s/5s → kill (CHỪA chuỗi tổ tiên session mình)
# 3. NTFS compression trên .mdf/.ldf (gotcha #88 — "giải phóng dung lượng" nén khi ổ cạn)
Get-ChildItem 'C:\Program Files\Microsoft SQL Server\MSSQL16.SQLEXPRESS\MSSQL\DATA' -File |
Where-Object { $_.Attributes -match 'Compressed' } # có kết quả = compact /u (DB offline nếu đang mở)
# 4. AUTO_CLOSE phải OFF (đã set @S189 — ON làm mỗi connection mở lại DB ~5s)
sqlcmd -S .\SQLEXPRESS -E -W -Q "SELECT name,is_auto_close_on FROM sys.databases WHERE database_id>4"
```
🔴 **Bài `ssh … | head` (S189):** cắt output sớm làm ssh client thoát nhưng **process phía Windows-OpenSSH chạy tiếp** → orphan spin ăn core. Lệnh ssh dài: bọc `Select-Object -First N` phía REMOTE, đừng `| head` phía local.
## Let's Encrypt cert — win-acme
### Check trạng thái

View File

@ -55,6 +55,7 @@ Dữ-liệu cũ (WAL · HANDOFF · auto-memory · migration-todos) **KHÔNG** đ
| `YC-019` | "1 -> Tạm OK · 2 -> Role baseline + Runs · 3,4 -> giải thích · 5 -> OK kéo audit luôn giờ cứ end/start là cứ chạy · 6 -> Nợ cũ thì xử lý cho xong luôn" | 2026-08-10 · S185 | **đóng** *(6/6 vế thi hành @S185: (1) YC-013/016 đóng · (2) ring2 24.859B · (3)(4) đã giải thích → anh phán tiếp = YC-020 · (5) deep_every=1 · (6) nợ cũ đóng trọn)* | 1 | WAL chain (TASK → WAL) | 6 phán quyết một lượt: (1) acceptance 8-panel TẠM-OK ⇒ lật YC-013/016 nấc "tạm" · (2) C-6 = NÉN `Role baseline`+`Runs` của ring2 (khuôn moved-not-cut như lead-stale) · (3)(4) em giải thích W-1..W-4 + 5 SKIP-owner, chưa phán · (5) 🔴 ĐỔI NHỊP DEEP: từ nay **mỗi bookend end/start đều chạy deep** (deep_every → 1, hết chờ nhịp 15) · (6) uỷ quyền xử TRỌN nợ cũ theo bậc-2 LT3 (chọn nhánh an toàn + khai, việc-vật-lý-của-anh thì kê rõ) | |
| `YC-024` | "(80) Đồng ý đúng, 7 loại ⏎ (81) Tức Duyệt NCC -> Chọn Duyệt NCC tạo KHKK -> KHKK (đã duyệt) -> Chọn để tạo HĐ ⏎ (82) Hiện phân quyền tất cả trước đi, sau khi ổn rồi thì phân quyền sau. ⏎ (83) Lấy tương tự như KHKK sau đó tao sẽ điều chỉnh sau, phần điều chỉnh trong Admin tao sẽ tự chủ động ⏎ (84) Đồng ý ⏎ (85) Đồng ý ⏎ (86) Đồng ý khóa cứng tương tự như KHKK và Duyệt NCC ⏎ (87) Chỗ này user có thể tự chọn KHKK cho loại Hợp đồng nào. ⏎ (88) Đồng ý ruột lấy từ các thông tin đã nhập (Có thể tham khảo 1 form Hợp đồng trước file word đã có, lấy làm mẫu trước, các thông tin đã nhập vào có thể lấy load lên, còn nếu chưa thì cho điền bổ sung, ví dụ như điều khoản)." | 2026-08-10 · S188 | **mở** | 1 | run `2026-08-10-S187-duyet-hd-tu-khkk/` spec + WAL *(thi-công → WAL)* | 9 phán quyết mở khoá plan 11-wave YC-023: (80) trục **7-LOẠI** · (81) **PA-A** + xác nhận chuỗi kế thừa PE→KHKK→HĐ · (82) *đội hiểu (bậc-2, khai)*: **MỞ TẤT CẢ trước** như posture hiện tại, phân quyền chi tiết = đợt SAU khi anh thấy ổn ⇒ **W8-authz HOÃN chờ lệnh anh**; guard đúng-trạm của máy duyệt V2 vẫn chạy (logic quy trình ≠ phân quyền) — anh bác cách hiểu thì em sửa · (83) W3 seed roster **SAO KHUÔN KHKK**, anh tự chỉnh trong Admin Designer sau ⇒ hết chặn cứng · (84) màn duyệt **2 nút** · (85) admin **giữ ẩn** ⇒ W9 HỦY · (86) **khoá cứng** quy trình theo loại, không đổi tay · (87) KHÔNG cột gợi-ý — user tự chọn loại HĐ khi tạo từ KHKK · (88) KHÔNG bảng hạng mục — **ruột HĐ pre-fill** từ dữ-liệu đã nhập + khuôn **form HĐ Word có sẵn** làm mẫu, field thiếu cho điền bổ sung (vd điều khoản) → vào W5/W6 | |
| `YC-027` | "cái này sẽ là rules nhé, các cái command phải đc thực hiện trọn vẹn tao ko chấp nhận bất cứ 1 lý do nào" | 2026-08-10 · S188 | **mở** | 2 | 2 file lệnh + ACTIVE-MARKS (stamp chờ P4) | **NÂNG THÀNH RULE VĨNH VIỄN**: mọi command/nghi-thức (bookstart · bookend · /tiep · mọi lệnh anh gõ) phải chạy **TRỌN VẸN 100% các bước** — không vai nào được bỏ, không viện BẤT KỲ lý do nào (ngân sách · cạn context · "để lượt sau" · nghi-thức-đã-đóng). Đứt bất-khả-kháng (limit/crash) ⇒ lượt-nối đầu tiên chạy HẾT phần thiếu. Khắc vào `session-end.md` + `session-start.md` khối ONE-SHOT (trỏ YC-027/PN-001) ngay @S188; stamp User-Mark theo P4 report-before-stamp ở nhịp tới | |
| `YC-028` | "xóa bớt 1 tý để các bạn upload load phiếu rồi tính tiếp" | 2026-08-11 · S189 | **mở** *(vế "xóa bớt" ĐÃ XONG cùng lượt; vế "tính tiếp" treo chờ anh chọn số)* | 1 | WAL (mạch sống) + `runs/2026-08-10-S189-bookstart-open/run.md` | Bối cảnh: anh Kiệt báo phiếu `PE/2026/A/059` "ko duyệt được" rồi "ko tìm thấy phiếu" — chẩn đoán ra **ổ C VPS còn 0 byte** (free âm 20MB vào reserve) ⇒ SQL connection timeout ⇒ FE vẽ mọi lỗi thành "Không tìm thấy phiếu" (#44). Đội hiểu: giải phóng đĩa NGAY bằng mục AN TOÀN nhất — đã xóa cache `gitea\data
epo-archive` **16,49GB** (cache chính chủ Gitea, tự tái tạo) → free **20,1GB**, API 200/401 chuẩn, 0 ERR mới; KHÔNG đụng DB/repo/uploads. "Tính tiếp" = bảng đánh số trình anh: npm-cache SYSTEM 12,76GB · inetpub\logs 2,15GB · chống tái phát repo-archive · disk-watch · FE tách lỗi-hệ-thống ⟂ không-tìm-thấy · truy log 10/08 biến mất | |
| `YC-026` | "ko đóng phiên với bất cứ lý do nào" | 2026-08-10 · S188 (giữa lượt chạy-bù PN-001) | **mở** | 1 | WAL (mạch sống) | Phiên/conversation này KHÔNG được kết thúc — chạy hết mọi vai + việc tồn rồi **giữ phiên mở tiếp tục nhận việc**; CẤM viện bất kỳ lý do nào (ngân sách/context/nghi-thức-đã-đóng) để dừng. *(Đội hiểu thêm: `_end` phiên-LOGIC L14 đã ghi TRƯỚC lệnh này ~15 phút — dấu frozen không gỡ được; hiệu lực thực tế = conversation tiếp tục làm việc, mạch mới ghi WAL như thường. Anh bác cách hiểu thì em sửa.)* | |
| `YC-025` | "OK làm tiếp hết đi, xong hết tao review 1 lần luôn" | 2026-08-10 · S188 | **mở** | 1 | WAL chain (thi-công → WAL) | Làm TRỌN các wave còn lại của YC-023/024 (W3 seed 7 quy trình + key Designer → W4 guard 4-cửa → W5 card nguồn-gốc + ruột pre-fill (88) → W6 màn duyệt 2-nút + Đ2-guard → W10 dry-run toàn trình) KHÔNG dừng chờ anh nhìn prod giữa wave; gate máy + reviewer-agent per-wave GIỮ NGUYÊN; push gom 1 lượt cuối (tránh #86 + tránh bản prod trung gian); anh review MỘT LẦN trên prod khi trọn. W8 vẫn HOÃN theo (82) "phân quyền sau", W9 vẫn HỦY theo (85) | |

View File

@ -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
}

View File

@ -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) }} />

View File

@ -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)} />

View File

@ -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()} />

View File

@ -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
}

View File

@ -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) }} />

View File

@ -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)} />

View File

@ -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()} />