- docs/asset-license-management.md (moved from ../processes/, fixed relative links to stay accurate from the new location) - CLAUDE.md now points at docs/asset-license-management.md - removed stale no-git-repo-yet note, added git init/push status
11 KiB
Quản lý Tài sản IT & License
Trạng thái hiện tại
- Chưa có hệ thống tập trung — request xử lý rời rạc qua email/docx (vd
Firefly_License_Request.docx,Eric Pelatan.docxở gốc workspaceubisoft/, ngoài repo này). Không tracking được ai đang giữ gì, license nào sắp hết hạn/hết seat, tài sản nào sắp hết bảo hành. - Đã chốt: kiến trúc monolith Next.js, không tách backend/frontend ở giai đoạn này (xem trigger để tách ở dưới).
- Đã chốt: Postgres (2026-07-29) — lý do: đảm bảo scale sau này + Power BI connect native, và bạn đã có kinh nghiệm dùng Postgres. Migrate task 1 (entity Request, state Draft) từ SQLite sang Postgres đã xong, CONFIRMED pass.
- Đã chốt: Prisma (ORM) + Zod (validation) (2026-07-30) — lý do Prisma over Drizzle cho solo dev: Prisma Studio (xem/sửa data qua GUI, khỏi viết psql query khi không có teammate hỏi), migration workflow tự động (
prisma migrate dev), docs/community lớn hơn hẳn khi cần tự tra cứu một mình. Đánh đổi: nặng hơn Drizzle (query engine binary riêng, cần bước generate client), "magic" hơn raw SQL. Zod đi kèm để tận dụng type inference thay vì viết tay interface + hàm validate riêng. - Retrofit
Requestsang Prisma+Zod xong (2026-07-30) — CONFIRMED qua test-runner độc lập: 6 test pass trên Postgres thật (quaprisma migrate deploy), type-check sạch, lint sạch.typechuyển từTEXT + CHECKsang Postgres enum (RequestType) — tương đương semantics, không phải divergence.id/createdAtgiữ nguyên hành vi cũ. Chi tiết đầy đủ ở../CLAUDE.md. - Project scaffold (multi-agent dev workflow) đặt ngay tại repo này — xem
../CLAUDE.md. - File này trước đây nằm ở
ubisoft/processes/asset-license-management.md(workspace chung, không phải git repo) — đã move vào đây (2026-07-30) vì nó gắn chặt với app này và cần update song song với code.ubisoft/processes/README.mdvẫn giữ 1 dòng trỏ tới đây cho ai tìm từ đó.
Mục tiêu
Theo dõi toàn bộ vòng đời tài sản/license: Request → Approval → Procurement → Receiving → Delivery → Tracking → Thu hồi/Retire, và trả lời nhanh được:
- Ai đang giữ máy/license nào?
- License nào sắp hết hạn hoặc hết seat?
- Tài sản nào sắp hết bảo hành?
- Chi phí/license theo phòng ban là bao nhiêu?
Data model (sơ bộ)
| Entity | Ý nghĩa |
|---|---|
Employee |
id, tên, phòng ban, manager |
Vendor |
nhà cung cấp |
AssetItem |
hardware — serial number, model, ngày mua, bảo hành, trạng thái (in stock / assigned / repair / retired) |
License |
phần mềm — tên, vendor, tổng seat, seat đã dùng, ngày hết hạn, loại (perpetual/subscription) |
Request |
người yêu cầu, loại (asset/license), lý do, trạng thái |
PurchaseOrder |
gộp nhiều Request đã duyệt, vendor, ngày đặt, ngày dự kiến nhận |
Receipt |
nhận hàng — đối chiếu PO, ngày nhận, người nhận |
Assignment |
gán AssetItem/License cho Employee — ngày giao, ngày trả, biên bản ký nhận |
Request.servicenow_ticket |
field text, nhập tay số ticket ServiceNow liên quan (không tích hợp API ServiceNow ngay — volume thấp, nhập tay đủ dùng; để ngỏ cho auto-sync sau nếu cần) |
Luồng trạng thái Request
Draft → Pending Approval → Approved → Ordered → Received → Delivered → Closed
↓
Rejected / Cancelled
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).
Data model thêm:
Rack(id, tên, vị trí — map vớirack-1/2/3đã có ảnh ở../../server-racks/, thư mục ngoài repo này trong workspaceubisoft/)RackSlot(rack_id, vị trí U,sensor_status[empty/occupied — ghi bởi cảm biến vật lý],assigned_asset_id[nullable — ghi khi người dùng quét QR gán asset vào slot])- Mismatch giữa
sensor_statusvàassigned_asset_id(vd sensor báo occupied nhưng không có asset nào được assign) = tín hiệu cần kiểm tra thủ công (phát hiện thất thoát/quên cập nhật)
Kiến trúc: tách stack IoT riêng (broker MQTT hoặc tận dụng Home Assistant đã định dùng cho KNX ở văn phòng mới, xem ../../processes/new-office-fitout.md) khỏi app chính — lý do là giao thức (MQTT/CoAP), data shape (time-series), và network/bảo mật (nên tách VLAN như camera/access control) đều khác hẳn CRUD web app. Tích hợp qua 1 bridge job đọc/ghi trạng thái vào Postgres của app chính (RackSlot.sensor_status) — app chính không cần biết MQTT/HASS, Postgres vẫn là nguồn sự thật duy nhất.
Luồng pick-to-light (tương lai):
- Request duyệt → nhận hàng → asset gán vào 1
RackSlotcụ thể → trạng thái Request thêm state mới "Ready for Pickup" - Người tới quét số ticket (ServiceNow, nhập tay ở trên) tại trạm quét QR gắn cạnh tủ rack → gọi API tra
RackSlottương ứng - Trạm quét = consumer độc lập thứ 2 của app (ngoài web frontend) → endpoint này cần thiết kế như API thật (auth riêng cho thiết bị, không dựa session web) — đây là trigger hợp lệ đầu tiên để có 1 API boundary rõ ràng, dù chưa cần tách hẳn backend
- App → bridge → publish lệnh bật đèn đúng slot qua IoT stack
- Người lấy xong → xác nhận (quét lại hoặc sensor tự phát hiện chuyển empty) → tắt đèn, đóng Assignment/Request
QR gán asset vào slot (bước 1, thủ công hàng ngày) không phải IoT — chỉ là quét mã bằng camera trình duyệt/máy quét cầm tay, ghi thẳng qua API hiện có của app chính, không cần MQTT.
Case đặc biệt cần xử lý
- Nhân viên nghỉ việc → checklist thu hồi asset + license (offboarding)
- License seat-based dùng chung (vd Adobe CC theo pool chung của phòng ban)
- Tài sản luân chuyển giữa phòng ban / chi nhánh (không phải "delivery" mới, mà là re-assign)
- Asset hỏng → gửi RMA/sửa chữa → cần state "in repair" tách khỏi "assigned"
Quyết định kiến trúc
- Quy mô hiện tại <100 người, IT team nhỏ → web app tự build gọn nhẹ, monolith (Next.js full-stack, frontend + API routes chung 1 app) — self-hosted, tương tự pattern app "IT Knowledge Base + Ticket Analyzer" đã build cho
it-support.md(workspace ngoài repo này), theo nguyên tắc chung của workspace là ưu tiên self-host hơn cloud (xem../../processes/new-office-fitout.md). Không tách backend/frontend riêng ở giai đoạn này — xem lý do & trigger để tách ở dưới. - DB: Postgres, đã chốt (2026-07-29). Lý do: đảm bảo scale sau này, Power BI connect native/ổn định tới Postgres (còn SQLite cần ODBC driver gượng ép + không hợp để BI query đồng thời lúc app đang ghi), và đã có kinh nghiệm sẵn với Postgres. Cách lộ dữ liệu cho Power BI: tạo Postgres views riêng cho report (vd
v_requests_report,v_license_utilization) + 1 read-only DB user trỏ Power BI vào, không cần expose API riêng cho việc này. - Cron job (vd gửi email cảnh báo license sắp hết hạn hàng ngày) không cần tách backend riêng — làm 1 script độc lập (
scripts/send-license-expiry-emails.ts) query thẳng Postgres, lịch chạy qua Windows Task Scheduler (đã có tiền lệ ởxibo-signage.md, workspace ngoài repo này) hoặc container cron riêng trong Docker. Cần chốt sau: SMTP nào, ngưỡng cảnh báo bao nhiêu ngày, gửi cho ai. - Cân nhắc thay thế cho MVP: Airtable/Google Form nếu muốn test quy trình trước khi build — nhưng approval workflow nhiều bước + audit trail sẽ gượng ép nếu để lâu dài trên low-code.
- Reverse proxy khi deploy: Nginx Proxy Manager (đã chốt 2026-07-30) — tự host, có UI quản lý + tự động Let's Encrypt. Chưa dựng ngay vì app còn ở local dev, chưa tới bước deploy production. Khi deploy, app này khả năng sẽ dùng chung 1 NPM instance với các service self-host khác trong workspace (vd Xibo) thay vì mỗi app 1 reverse proxy riêng — cần xác nhận lại lúc thực sự deploy.
Khi nào nên tách backend riêng (chưa áp dụng, chỉ ghi lại trigger)
- Có consumer thứ 2 thật sự cần gọi API độc lập ngoài web frontend (vd trạm quét QR pick-to-light — xem module Rack/Slot ở dưới; hoặc tích hợp ServiceNow sau này)
- Có job cần chạy tách biệt khỏi request-response của web (cron email đã quyết là chưa cần tách, chỉ cần script + scheduler)
- Cần hạ tầng/bảo mật khác biệt (vd module IoT rack — xem dưới, lý do khác hẳn: giao thức, data shape, network)
- Team lớn tới mức có người riêng phụ trách backend
"Muốn tích hợp với hệ thống khác" tự nó không phải lý do để tách — ngược lại, tích hợp chặt (vd Rack/Slot dùng chung AssetItem) là lý do để gộp chung app/DB thay vì làm hệ thống riêng rồi nối lại qua API.
Process / SOP
(Chưa có — sẽ bổ sung khi bắt đầu implement: quy trình tạo request mới, checklist nhận hàng, checklist bàn giao có ký nhận, checklist offboarding thu hồi thiết bị)
Việc còn dở
- Thiết kế ERD chi tiết + wireframe màn hình
- Entity còn lại:
Employee,AssetItem,License,PurchaseOrder,Receipt,Assignment - Migrate 2 request đang có (
Firefly_License_Request.docx,Eric Pelatan.docx, ở workspaceubisoft/ngoài repo này) vào hệ thống mới sau khi có DB - (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
Ghi chú & bài học
- Migrate entity
Requesttừ 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-07-30