docs: document InventoryScan feature + note deviation from agent pipeline
Co-Authored-By: Claude Sonnet 5 <[email protected]>
This commit is contained in:
co-authored by
Claude Sonnet 5
parent
84e6db7406
commit
9773dd8a39
@@ -140,6 +140,26 @@ Xuất phát từ yêu cầu stakeholder: "muốn thêm 1 menu maintenance — k
|
||||
- **Đóng** `MaintenanceRecord` (set `endDate` + `resolutionNotes` qua `PATCH /api/maintenance-records/[id]`, cùng REST-shape tiền lệ đã chốt cho `Assignment`/`PurchaseOrder` — "chốt 2026-08-02 (b)" ở mục Assignment): body nhận thêm `targetStatus` bắt buộc (vd `in_stock` nếu sửa xong, `retired` nếu sửa không thành công) — **không** tự động mặc định về `in_stock`, vì sửa không phải lúc nào cũng thành công.
|
||||
- Các field khác ngoài hành động "đóng" record không có update endpoint riêng — set 1 lần lúc tạo, không sửa lại sau, theo đúng pattern hiện có (vd `PurchaseOrder.orderDate` không có update endpoint).
|
||||
|
||||
## InventoryScan — kiểm kê văn phòng move (chốt 2026-08-20)
|
||||
|
||||
Bối cảnh: dự án move văn phòng cần kiểm kê toàn bộ thiết bị, càng nhỏ càng phải ghi — nhưng thao tác chụp ảnh rồi gõ Excel trên laptop quá chậm khi đi vòng quanh văn phòng. Giải pháp: bot Telegram (`Inventory_capture_bot`, chạy trên service riêng `inventory-capture` ở `/opt/homelab/inventory-capture/` trên máy `appsrv01`, KHÔNG phải trong repo này) cho phép nhập liệu bằng bấm nút trên điện thoại, gọi ngược vào app này qua các route `/api/bot/*` để ghi thẳng vào Postgres.
|
||||
|
||||
**Vì sao KHÔNG dùng `AssetItem` cho việc này:** `AssetItem` bắt buộc `serialNumber` (unique) + `purchaseDate` — cả hai đều không có sẵn ngay lúc đi bấm nút kiểm kê nhanh. Bắt buộc 1 trong 2 sẽ hoặc chặn luồng kiểm kê, hoặc bịa dữ liệu giả (đi ngược nguyên tắc "không dữ liệu giả" đã áp dụng xuyên suốt codebase này — xem comment `AssetItem`/`License`/`PurchaseOrder`). Quyết định: **entity mới `InventoryScan`** — bản ghi "quét lúc kiểm kê", nhẹ, không unique constraint nào chặn luồng nhập nhanh. Sau này review, cái nào cần lên sổ chính thức thì "promote" sang `AssetItem` thật (đọc serial thật lúc đó) — **bước promote này chưa build**, xem "Việc còn dở".
|
||||
|
||||
**Scope ban đầu (3 category, tự seed qua script, không phải migration seed):** Controller (PS5/Sony, Xbox/Microsoft), Màn hình (Dell/Monitor, HP/Monitor), Workstation (HP/Z4 G4, Lenovo/P620 — category duy nhất có `AssetCategoryAttributeDef`: `cpu`/`ram_gb`/`gpu`, cả 3 `required: false` ở tầng DB dù bot luôn hỏi đủ cả 3 trong hội thoại — lý do: field `required` này chỉ chi phối validate `AssetModel.specs`, không phải `InventoryScan.specs`, và giá trị CPU/RAM/GPU thực tế khác nhau **theo từng máy vật lý** chứ không cố định theo model, nên không nên ép cứng ở tầng `AssetModel`). Category/model KHÔNG dừng ở scope này — bot có nút "➕ Model mới" (gọi `POST /api/bot/asset-models`) cho phép thêm model mới ngay trong lúc kiểm kê, category mới thì vẫn cần seed tay (business call lớn hơn, có thể cần thêm `AssetCategoryAttributeDef`).
|
||||
|
||||
**`Area` (không dùng `Seat`):** Cân nhắc ban đầu là "Seat" gắn cứng với `team`, nhưng lúc build sơ đồ ghế ngồi văn phòng mới **chưa có** — đồng thời cần cả vị trí kiểu phòng (phòng họp, kho), không chỉ ghế cá nhân. Quyết định: 1 bảng `Area` chung (tên unique + `team` nullable) cho cả 2 kiểu, bot tự học dần (list nút bấm lớn theo thời gian, có nút "➕ Area mới") thay vì cần seed sơ đồ trước. Khi có sơ đồ move chính thức, chỉ cần update cột `team` cho từng `Area` đã có — không đổi cấu trúc.
|
||||
|
||||
**`assetTag` + `serialNumber`:** cả hai đều `String?`, **không unique/required** ở tầng `InventoryScan` (khác hẳn `AssetItem.serialNumber`) — cùng lý do "không chặn luồng kiểm kê" ở trên. Đọc từ tem dán trên thiết bị. Hiện tại nhập tay qua bot hoặc **đọc thủ công từ ảnh** (stakeholder tự gửi ảnh cho Claude, không có OCR tự động trong app) — xem "Việc còn dở" cho hướng OCR tự động nếu cần sau.
|
||||
|
||||
**Ảnh:** `InventoryScan.photoFileId` lưu Telegram `file_id`, KHÔNG lưu file vật lý trên disk của app này (tránh phải share volume giữa 2 service khác container/khác concern). `GET /api/inventory-scans/[id]/photo` proxy on-demand qua Telegram Bot API (`getFile` + download) mỗi lần xem — cần `BOT_TOKEN` trong `.env` của **chính app này** (không chỉ bot service) để làm việc đó. Gotcha đã gặp: Telegram trả `Content-Type: application/octet-stream` cho file download bất kể loại thật, browser hiểu nhầm thành tải file — phải tự suy `Content-Type` từ đuôi file trong `file_path` (`.jpg`/`.png`/`.webp`), không tin header gốc.
|
||||
|
||||
**Auth cho `/api/bot/*`:** route riêng (`src/lib/bot-auth.ts`), check header `Authorization: Bearer <BOT_INGEST_SECRET>` — **không** qua Auth.js session như mọi route khác trong app, vì caller là bot service (machine), không phải browser người dùng đã login. `BOT_INGEST_SECRET` là secret riêng, khác `AUTH_SECRET` (session JWT) và khác password của User `[email protected]` (tài khoản review dùng trang `/inventory-scans`, tạo tay qua `scripts/create-user.ts` giống mọi `User` khác — không phải service account đặc biệt).
|
||||
|
||||
**Trang review** (`/inventory-scans`, session-authed như mọi trang khác): list + inline edit (đổi model trong cùng category, area, người gán, specs, asset tag, serial number) + delete. Không có trang "create" riêng vì tạo mới chỉ qua bot.
|
||||
|
||||
Xem thêm: `src/lib/inventory-scans.ts`, `src/lib/areas.ts`, `prisma/schema.prisma` (model `Area`/`InventoryScan`), `tests/areas.test.ts`, `tests/inventory-scans.test.ts`, `tests/bot-api.test.ts`.
|
||||
|
||||
## Module tương lai: Rack/Slot management + IoT (chưa build, chỉ note kiến trúc)
|
||||
|
||||
Mục tiêu xa hơn: biết chính xác asset nằm ở ô/slot nào trên tủ rack, và hướng dẫn người lấy đúng ô bằng đèn (pick-to-light).
|
||||
@@ -198,11 +218,12 @@ QR gán asset vào slot (bước 1, thủ công hàng ngày) **không phải IoT
|
||||
- Migrate 2 request đang có (`Firefly_License_Request.docx`, `Eric Pelatan.docx`, ở workspace `ubisoft/` ngoài repo này) vào hệ thống mới sau khi có DB
|
||||
- **`MaintenanceRecord` (spec BA + stakeholder, chốt 2026-08-04): đã có spec đầy đủ, sẵn sàng cho `pm` chia task** — xem mục "MaintenanceRecord" phía trên. Field cơ bản + `type` (`repair`/`warranty`/`preventive`) + `vendorId` (tái dùng `Vendor`, nullable) đã chốt; đồng bộ `AssetItem.status` đã chốt rõ (auto → `repair` lúc mở, chặn nếu có `Assignment` active hoặc đã `retired`, `targetStatus` bắt buộc lúc đóng); không track `cost`.
|
||||
- (Xa hơn, chưa ưu tiên) Rack/Slot + IoT pick-to-light, cron email cảnh báo license hết hạn, Power BI report views
|
||||
- Setup Gitea tự host ở nhà (2026-07-31) — dự định làm remote chính sau này, giữ GitHub làm backup off-site (đây là "sản phẩm dùng cả đời" của user, không nên chỉ có 1 bản duy nhất). Tạm thời vẫn push GitHub như hiện tại, chưa làm gấp.
|
||||
- Gitea tự host (`git.toanhoang.work`) giờ đã là `origin` chính thức (đổi từ GitHub — cập nhật note cũ 2026-07-31 vốn nói "tạm thời vẫn push GitHub"; chưa xác nhận có còn remote GitHub backup off-site nào không, cần check lại). CI (build+test+tsc+lint qua Gitea Actions) đã setup 2026-08-20, xem `.gitea/workflows/ci.yml` — chỉ chạy trên PR/push, **chưa có auto-deploy**, deploy vẫn làm tay qua `docker compose up -d --build` + `npm run db:migrate` trên `appsrv01` (xem `CLAUDE.md`).
|
||||
- **`InventoryScan` (kiểm kê move, chốt 2026-08-20): xem mục "InventoryScan — kiểm kê văn phòng move" phía trên.** Việc còn dở riêng của feature này: (1) chưa build bước "promote" 1 `InventoryScan` thành `AssetItem` thật (đọc serial thật, tạo `AssetItem` + optionally `Assignment`) — hiện review xong vẫn chỉ nằm ở `InventoryScan`; (2) chưa có OCR tự động đọc `assetTag`/`serialNumber` từ ảnh — stakeholder tự nhờ Claude đọc ảnh thủ công, nếu cần tự động hoá sau thì cần thêm `ANTHROPIC_API_KEY` riêng cho server + route gọi Claude Vision (đã bàn, chưa làm vì stakeholder chọn hướng thủ công trước); (3) `Area.team` đang trống cho toàn bộ `Area` đã tạo — cần điền khi có sơ đồ move chính thức.
|
||||
|
||||
## Ghi chú & bài học
|
||||
|
||||
- Migrate entity `Request` từ SQLite sang Postgres (2026-07-30) và setup ESLint đều đã **CONFIRMED xong** qua lượt verify độc lập (test-runner riêng, không phải agent vừa code tự chấm) — 6 test pass trên Postgres thật, lint sạch, type-check sạch. Chi tiết trạng thái nằm ở `../CLAUDE.md`, không duplicate ở đây.
|
||||
|
||||
---
|
||||
Cập nhật lần cuối: 2026-08-04 (spec `MaintenanceRecord` chốt đủ với stakeholder — sẵn sàng cho `pm`, xem "Việc còn dở")
|
||||
Cập nhật lần cuối: 2026-08-20 (`InventoryScan` — kiểm kê move — chốt đủ + build xong qua Claude Code từ xa (SSH, không qua pipeline agent chuẩn của repo), CI Gitea Actions setup — xem "Việc còn dở")
|
||||
|
||||
Reference in New Issue
Block a user