Dashboard Role Architecture Plan

Cập nhật triển khai (2026-04-25)

Các phần của kiến trúc trong tài liệu này đã được dựng foundation:

  • Đã có backend/src/dashboard/* với module/controller/services/dto riêng, tách khỏi reports.
  • Đã có 5 endpoint role-specific:
    • GET /api/v1/dashboard/super-admin
    • GET /api/v1/dashboard/admin
    • GET /api/v1/dashboard/owner
    • GET /api/v1/dashboard/staff
    • GET /api/v1/dashboard/technical
  • JWT role đã chuẩn hóa enum active role; frontend decode role fail-closed.
  • Frontend đã có DashboardRoute, lazy route theo role, và redirect / -> /dashboard.
  • Navigation đã tách cấu hình theo role trong frontend/src/navigation/*, SidebarMenu chỉ còn resolver/render.
  • Service/hook/type boundary cho dashboard role đã tách riêng trong frontend/src/services/dashboard, frontend/src/hooks/dashboard, frontend/src/types/dashboard.

Phần chưa hoàn tất:

  • Aggregate query/presenter và UI section business thật cho từng role (các phase tiếp theo).

1. Mục tiêu tài liệu

Tài liệu này chuyển hóa docs/dashboard_design.md thành plan kiến trúc code để triển khai dashboard theo từng vai trò trong HMP IoT Platform.

Mục tiêu chính:

  • Tách dashboard theo mục tiêu công việc của từng role, không dùng một dashboard chung rồi ẩn/hiện UI.
  • Tổ chức file structure để dễ mở rộng, dễ test, dễ lazy-load và tránh bundle phình không cần thiết.
  • Đảm bảo dữ liệu được scope từ backend theo multi-tenant/RBAC, không dựa vào frontend để che dữ liệu nhạy cảm.
  • Tạo nền tảng cho các phase: Owner/Staff, Admin, Super Admin, Technical, Ticket & Maintenance.

Phạm vi tài liệu:

  • Chỉ plan kiến trúc và tổ chức code.
  • Không mô tả visual design chi tiết từng component.
  • Không thay thế docs/dashboard_design.md; file này là implementation architecture companion cho tài liệu design đó.

Tài liệu triển khai theo task:

  • docs/dashboard-role-implementation-tasks.md: task breakdown theo phase, dependencies và Acceptance Criteria để giao dev.

2. Bối cảnh codebase hiện tại

Frontend hiện có:

  • frontend/src/App.tsx: khai báo route tập trung.
  • frontend/src/components/layouts/DashboardLayout.tsx: layout dashboard dùng chung.
  • frontend/src/components/layouts/SidebarMenu.tsx: menu sidebar tập trung theo permission/role.
  • frontend/src/pages/dashboard/DashboardOverview.tsx: dashboard tổng quan hiện tại.
  • frontend/src/pages/farm/FarmDashboard.tsx: dashboard farm đơn giản hiện tại.
  • frontend/src/hooks/useReports.ts: hooks dashboard/report hiện tại.
  • frontend/src/services/report.service.ts: service gọi /dashboard/*/reports/*.
  • frontend/src/stores/auth.store.ts: lưu role, permissions, user, accessToken.
  • frontend/src/lib/permissions.ts: decode JWT role/permission và check permission.

Backend hiện có:

  • backend/src/reports/dashboard.controller.ts: endpoint dashboard hiện tại.
  • backend/src/reports/dashboard.service.ts: service tổng hợp dashboard/report hiện tại.
  • Các module domain: farm, aquaculture, devices, financial, inventory, organizations, users, notifications, telemetry.
  • RBAC và multi-tenancy qua permission guard và OrganizationFilterInterceptor.

Các quyết định kiến trúc đã chốt cho dashboard:

  • Backend role chuẩn hóa bằng enum: SUPER_ADMIN, ADMIN, OWNER, STAFF, TECHNICAL.
  • Mỗi user chỉ có một role active tại một thời điểm; không hỗ trợ multi-role active trong dashboard.
  • Owner quản lý đúng một farm.
  • Technical được tạo ticket nội bộ, nhưng customer ticket vẫn đi qua luồng Owner/Admin/Staff/Super Admin.
  • Dashboard endpoint chưa dùng Redis ở phase đầu; chỉ thêm Redis sau khi có số đo hiệu năng.
  • Route / redirect về /dashboard.

Rủi ro nếu tiếp tục mở rộng theo cấu trúc hiện tại:

  • App.tsx sẽ thành route registry quá lớn.
  • SidebarMenu.tsx sẽ chứa quá nhiều logic role/menu/permission.
  • DashboardOverview.tsx dễ biến thành file conditional theo 5 role.
  • report.service.ts sẽ trộn report analytics với role dashboard.
  • Frontend có nguy cơ nhận dư dữ liệu rồi chỉ ẩn bằng UI, không an toàn cho Technical/Staff.

3. Nguyên tắc kiến trúc

3.1. Role-specific dashboard entry

Mỗi role có dashboard entry riêng:

  • Super Admin: SaaS Control Center.
  • Admin: Organization Command Center.
  • Owner: Farm Operation Dashboard.
  • Staff: Field Work Dashboard.
  • Technical: Maintenance Dashboard.

Không tạo một dashboard chung kiểu:

if (role === 'Admin') return <AdminBlocks />;
if (role === 'Owner') return <OwnerBlocks />;

Cách này làm file lớn, khó lazy-load, khó test, và dễ lộ dependency không liên quan.

3.2. Backend aggregate theo role

Dashboard là màn hình tổng hợp nhiều domain. Frontend không nên gọi 8-12 API nhỏ cho một dashboard nếu các dữ liệu đó luôn được dùng cùng nhau.

Nên có aggregate endpoint theo role:

GET /api/v1/dashboard/super-admin
GET /api/v1/dashboard/admin
GET /api/v1/dashboard/owner
GET /api/v1/dashboard/staff
GET /api/v1/dashboard/technical

Mỗi endpoint:

  • Enforce role/permission ở backend.
  • Query đúng scope dữ liệu.
  • Trả DTO đã tối ưu cho UI.
  • Không trả field nhạy cảm ngoài phạm vi role.
  • Có thể cache từng phần theo tính chất dữ liệu.

3.3. Shared UI, khác composition

Dùng chung primitive component:

  • KPI card.
  • Section card.
  • Table card.
  • Status badge.
  • Empty state.
  • Loading skeleton.
  • Filter toolbar.

Không dùng chung toàn bộ dashboard layout giữa các role. Mỗi role khác:

  • KPI.
  • Section order.
  • Table columns.
  • Quick actions.
  • Refresh interval.
  • Mobile behavior.
  • Data visibility.

3.4. Permission là guard, role là UX composition

Permission quyết định user có được truy cập resource/action không. Role quyết định trải nghiệm dashboard mặc định.

Ví dụ:

  • financial:read:all quyết định có vào financial report không.
  • Role Owner quyết định dashboard ưu tiên pond/task/feed/staff.
  • Role Technical quyết định chỉ thấy ticket/device liên quan bảo trì.

Không dùng role string thay thế permission guard cho action nhạy cảm.

3.5. Data security nằm ở backend

Frontend chỉ là lớp hiển thị. Các rule sau phải được backend đảm bảo:

  • Staff chỉ nhận pond/task/log/incident của chính mình hoặc pond được giao.
  • Owner chỉ nhận farm/staff/device/ticket thuộc farm của mình.
  • Admin chỉ nhận dữ liệu trong organization.
  • Super Admin nhận dữ liệu cấp SaaS, nhưng không cần nhận sâu dữ liệu kinh doanh farm nếu không phục vụ support/debug.
  • Technical chỉ nhận ticket được giao, device liên quan ticket, vị trí và contact đủ để xử lý, không nhận chi phí/FCR/sản lượng/nhật ký nuôi chi tiết.

4. Frontend file structure đề xuất

4.1. Dashboard pages

frontend/src/pages/dashboard/
  DashboardRoute.tsx
  dashboard-role.ts
  dashboard.routes.tsx
  index.ts

  shared/
    components/
      DashboardPageShell.tsx
      DashboardSection.tsx
      DashboardTableCard.tsx
      EmptyDashboardState.tsx
      KpiCard.tsx
      KpiGrid.tsx
      RealtimeStatusBadge.tsx
    hooks/
      useDashboardFilters.ts
      useDashboardRefresh.ts
    utils/
      dashboard-formatters.ts
      dashboard-query-keys.ts
    types.ts

  super-admin/
    SuperAdminDashboard.tsx
    components/
      AuditSecurityFeed.tsx
      DeviceInventorySummary.tsx
      DeviceIssueTable.tsx
      OrganizationHealthTable.tsx
      SaaSOverview.tsx
      SystemHealthPanel.tsx
      TechnicalWorkloadTable.tsx
      TicketMaintenanceTable.tsx
    hooks/
      useSuperAdminDashboard.ts
    types.ts

  admin/
    AdminDashboard.tsx
    components/
      AdminAlertCenter.tsx
      DataInterventionQueue.tsx
      FarmPerformanceTable.tsx
      FarmRanking.tsx
      OrganizationDeviceSummary.tsx
      OrganizationOverview.tsx
      OwnerStaffActivity.tsx
      ReportsSnapshot.tsx
    hooks/
      useAdminDashboard.ts
    types.ts

  owner/
    OwnerDashboard.tsx
    components/
      EnvironmentSummary.tsx
      FarmDeviceStatus.tsx
      FarmOverview.tsx
      FeedGrowthCharts.tsx
      IncidentReports.tsx
      PondAttentionList.tsx
      PondStatusMap.tsx
      StaffPerformance.tsx
      TodayTasks.tsx
    hooks/
      useOwnerDashboard.ts
    types.ts

  staff/
    StaffDashboard.tsx
    components/
      IncidentReportPanel.tsx
      MyPonds.tsx
      QuickLogEntry.tsx
      TodayChecklist.tsx
      UrgentAlerts.tsx
      WorkHistory.tsx
    hooks/
      useStaffDashboard.ts
    types.ts

  technical/
    TechnicalDashboard.tsx
    components/
      DeviceDiagnostics.tsx
      MaintenanceHistory.tsx
      MyTicketQueue.tsx
      PartsNeeded.tsx
      ServiceReportPanel.tsx
      TodayRoute.tsx
    hooks/
      useTechnicalDashboard.ts
    types.ts

4.2. Trách nhiệm từng file lõi

DashboardRoute.tsx

  • Đọc role từ useAuthStore.
  • Normalize role qua dashboard-role.ts.
  • Chọn đúng lazy dashboard từ dashboard.routes.tsx.
  • Render fallback nếu role không hợp lệ hoặc thiếu permission.
  • Không chứa UI block nghiệp vụ.

dashboard-role.ts

  • Định nghĩa enum frontend đồng bộ với backend: SUPER_ADMIN, ADMIN, OWNER, STAFF, TECHNICAL.
  • Validate role nhận từ JWT/backend; role không thuộc enum phải bị coi là unsupported.
  • Không xử lý multi-role priority vì user chỉ có một role active.

dashboard.routes.tsx

  • Khai báo React.lazy cho từng dashboard.
  • Là nơi duy nhất import entry dashboard theo role.
  • Giúp code splitting rõ ràng.

shared/components/*

  • Chỉ chứa UI primitive có thể dùng cho nhiều dashboard.
  • Không import service/hook domain cụ thể.
  • Không biết role hiện tại là gì.

role/hooks/use<Role>Dashboard.ts

  • Dùng TanStack Query để gọi service tương ứng.
  • Định nghĩa query key, stale time, refetch interval.
  • Mapping error/loading state cho dashboard entry.

role/types.ts

  • Type frontend cho payload đã được backend DTO trả về.
  • Chỉ chứa type dùng trong role dashboard đó.
  • Shared type dùng nhiều role thì đưa vào shared/types.ts.

4.3. Services, hooks, types tách khỏi report

Không nên tiếp tục mở rộng frontend/src/services/report.service.ts cho dashboard theo role. Report analytics và dashboard role là hai use case khác nhau.

Đề xuất:

frontend/src/services/dashboard/
  admin-dashboard.service.ts
  dashboard.service.ts
  owner-dashboard.service.ts
  staff-dashboard.service.ts
  super-admin-dashboard.service.ts
  technical-dashboard.service.ts

frontend/src/hooks/dashboard/
  useAdminDashboard.ts
  useDashboardRole.ts
  useOwnerDashboard.ts
  useStaffDashboard.ts
  useSuperAdminDashboard.ts
  useTechnicalDashboard.ts

frontend/src/types/dashboard/
  admin-dashboard.types.ts
  owner-dashboard.types.ts
  shared.types.ts
  staff-dashboard.types.ts
  super-admin-dashboard.types.ts
  technical-dashboard.types.ts

Quy ước:

  • Service chỉ gọi API và return typed response.
  • Hook quản lý query options, polling, enabled condition.
  • Page/component chỉ consume hook và render UI.
  • Không đặt API response type trong component file.

5. Routing plan

5.1. Route chính

Route /dashboard nên đổi từ render trực tiếp DashboardOverview sang render DashboardRoute.

/dashboard -> DashboardRoute -> lazy dashboard theo role

Route / hiện đang render FarmDashboard. Quyết định đã chốt: / phải redirect về /dashboard.

/           -> Navigate /dashboard
/dashboard  -> DashboardRoute

Lý do:

  • Mọi user có entry nhất quán.
  • Không mặc định mọi role vào FarmDashboard.
  • Technical và Super Admin không bị rơi vào màn farm không phù hợp.

5.2. Lazy loading

Mỗi dashboard role phải lazy-load:

SuperAdminDashboard chunk
AdminDashboard chunk
OwnerDashboard chunk
StaffDashboard chunk
TechnicalDashboard chunk

Lợi ích:

  • Staff mobile không tải bảng/biểu đồ Super Admin.
  • Technical không tải financial/farm analytics không liên quan.
  • Giảm initial bundle cho dashboard.
  • Dễ phát triển độc lập từng role.

5.3. Fallback behavior

Fallback cần rõ:

  • Chưa auth: ProtectedRoute xử lý.
  • Đã auth nhưng chưa có role: hiển thị empty state “Không xác định vai trò”.
  • Role không được hỗ trợ: redirect /403 hoặc ForbiddenPage.
  • Nếu JWT/backend trả nhiều role, đây là dữ liệu không hợp lệ cho dashboard; frontend nên chọn fail-closed thay vì tự quyết định priority.
  • Role hợp lệ phải là một trong SUPER_ADMIN, ADMIN, OWNER, STAFF, TECHNICAL.

6. Navigation/menu plan

6.1. Tách menu khỏi SidebarMenu

SidebarMenu.tsx hiện vừa chứa config menu vừa render menu. Khi thêm 5 role, file này sẽ phình lớn.

Đề xuất:

frontend/src/navigation/
  nav-resolver.ts
  nav.types.ts
  permission-visibility.ts
  menus/
    admin.menu.tsx
    owner.menu.tsx
    staff.menu.tsx
    super-admin.menu.tsx
    technical.menu.tsx

SidebarMenu.tsx chỉ nên:

  • Lấy role/permissions từ auth store.
  • Gọi resolveMenuForRole(role, permissions).
  • Render Ant Design Menu.
  • Xử lý selected/open keys.

6.2. Menu theo role

Super Admin:

  • Dashboard.
  • Organizations.
  • Admin Accounts.
  • Device Inventory.
  • Device Assignment.
  • Ticket & Maintenance.
  • Technical Team.
  • Subscription & Billing.
  • System Health.
  • Audit Logs.
  • System Settings.

Admin:

  • Dashboard.
  • Farms.
  • Owners & Staff.
  • Ponds.
  • Crops / Vụ nuôi.
  • Device Management.
  • Device Assignment.
  • Alerts.
  • Logs & Data Correction.
  • Reports & Analytics.
  • Cost & Profit.
  • Organization Settings.

Owner:

  • Dashboard.
  • Ao nuôi.
  • Staff.
  • Phân công công việc.
  • Nhật ký vận hành.
  • Cảnh báo.
  • Thiết bị Farm.
  • Vụ nuôi.
  • Kho thức ăn.
  • Chi phí & lợi nhuận.
  • Báo cáo.

Staff:

  • Dashboard.
  • Ao của tôi.
  • Việc hôm nay.
  • Nhập nhật ký.
  • Cảnh báo.
  • Báo cáo sự cố.
  • Lịch sử công việc.

Technical:

  • Dashboard.
  • My Tickets.
  • Devices.
  • Maintenance Schedule.
  • Map.
  • Parts / Inventory.
  • Service Reports.
  • Profile.

6.3. Visibility rule

Menu visibility cần qua hai lớp:

  • Role menu: role quyết định nhóm menu nền.
  • Permission filter: permission quyết định item cụ thể có hiển thị không.

Không hardcode logic phức tạp trực tiếp trong JSX.

7. Backend architecture plan

7.1. Module dashboard riêng

Dashboard theo role nên có module riêng, thay vì mở rộng vô hạn reports.

Đề xuất:

backend/src/dashboard/
  dashboard.module.ts
  dashboard.controller.ts
  dashboard-query.service.ts
  dashboard-cache.service.ts

  dto/
    admin-dashboard-response.dto.ts
    owner-dashboard-response.dto.ts
    shared-dashboard.dto.ts
    staff-dashboard-response.dto.ts
    super-admin-dashboard-response.dto.ts
    technical-dashboard-response.dto.ts

  services/
    admin-dashboard.service.ts
    owner-dashboard.service.ts
    staff-dashboard.service.ts
    super-admin-dashboard.service.ts
    technical-dashboard.service.ts

  presenters/
    admin-dashboard.presenter.ts
    owner-dashboard.presenter.ts
    staff-dashboard.presenter.ts
    super-admin-dashboard.presenter.ts
    technical-dashboard.presenter.ts

7.2. Controller contract

Controller nên expose endpoint theo role:

GET /dashboard/super-admin
GET /dashboard/admin
GET /dashboard/owner
GET /dashboard/staff
GET /dashboard/technical

Mỗi endpoint cần:

  • @UseGuards(JwtAuthGuard, PermissionsGuard).
  • @RequirePermission(...) phù hợp.
  • Lấy user hiện tại từ @CurrentUser.
  • Không nhận organizationId tùy tiện từ query nếu user không có quyền cross-tenant.
  • Gọi service đúng role.

7.3. Service responsibility

super-admin-dashboard.service.ts

  • Tổng hợp SaaS KPI.
  • Organization health.
  • Device inventory toàn hệ thống.
  • Device issues.
  • Ticket & maintenance toàn hệ thống.
  • Technical workload.
  • System health.
  • Audit/security feed.

admin-dashboard.service.ts

  • Tổng hợp organization KPI.
  • Farm performance.
  • Farm ranking.
  • Alert center.
  • Device trong organization.
  • Data intervention queue.
  • Owner/staff activity.
  • Reports snapshot.

owner-dashboard.service.ts

  • Farm overview.
  • Pond status.
  • Today tasks.
  • Staff performance trong farm.
  • Pond attention list.
  • Environment summary.
  • Feed/growth/FCR summary.
  • Device trong farm.
  • Incident reports.

staff-dashboard.service.ts

  • Today checklist của staff.
  • My ponds.
  • Quick log options.
  • Urgent alerts liên quan pond được giao.
  • Incident report state.
  • Work history.

technical-dashboard.service.ts

  • My ticket queue.
  • Today route.
  • Device diagnostics cho ticket được giao.
  • Parts needed.
  • Maintenance history của device liên quan.
  • Service report draft/status.

7.4. Query service và presenter

dashboard-query.service.ts nên chứa query helper dùng lại:

  • Count active ponds.
  • Count online/offline devices.
  • Load active alerts.
  • Load task completion.
  • Load ticket SLA.
  • Load organization/farm summary.

Presenter chịu trách nhiệm map entity/raw query thành DTO:

  • Format status.
  • Tính severity.
  • Tính SLA label.
  • Giới hạn field trả về theo role.
  • Không để component frontend phải suy luận quá nhiều.

7.5. Cache strategy

Không phải mọi block dashboard cần refresh cùng tần suất.

Gợi ý cache:

Dữ liệu TTL gợi ý Ghi chú
SaaS KPI tổng 60-120 giây Không cần realtime tuyệt đối
Organization health 60 giây Phụ thuộc alert/device/ticket
Device online/offline 15-30 giây Có thể polling hoặc WebSocket
Ticket queue 15-30 giây Cần gần realtime
Staff today tasks 30-60 giây Có mutation invalidate
System health 10-30 giây Có thể dùng health endpoint riêng
Reports snapshot 5-15 phút Có thể cache lâu hơn
Audit feed 30-60 giây Hoặc cursor pagination

Nếu số đo hiệu năng cho thấy cần Redis, key cần bao gồm role/scope:

dashboard:super-admin
dashboard:admin:{organizationId}
dashboard:owner:{farmId}
dashboard:staff:{userId}:{date}
dashboard:technical:{userId}:{date}

Redis không được đưa vào phase đầu theo mặc định. Phase đầu chỉ dùng TanStack Query staleTime, backend aggregate query, và đo thời gian response. Chỉ thêm dashboard-cache.service.ts/Redis khi số đo cho thấy endpoint chậm, request volume cao, hoặc block dữ liệu có chi phí query lớn.

8. Data contract đề xuất

8.1. Shared DTO pattern

Các dashboard nên dùng structure nhất quán:

type DashboardResponse = {
  generatedAt: string;
  scope: DashboardScope;
  kpis: DashboardKpi[];
  sections: RoleSpecificSections;
};

Tuy nhiên không nên ép mọi role dùng chung một mega type. Nên có shared primitive và role-specific sections.

Shared primitive:

type DashboardKpi = {
  key: string;
  label: string;
  value: number | string;
  unit?: string;
  status?: 'good' | 'warning' | 'critical' | 'neutral';
  trend?: {
    direction: 'up' | 'down' | 'flat';
    value: number;
    label: string;
  };
};

Role-specific response:

type OwnerDashboardResponse = {
  generatedAt: string;
  farmId: string;
  farmName: string;
  kpis: DashboardKpi[];
  pondStatus: PondStatusSummary[];
  todayTasks: TodayTask[];
  staffPerformance: StaffPerformanceRow[];
  pondAttention: PondAttentionRow[];
  environmentSummary: EnvironmentMetric[];
  feedGrowthCharts: FeedGrowthChartData;
  farmDevices: FarmDeviceRow[];
  incidents: IncidentRow[];
};

8.2. Không trả dữ liệu dư

Ví dụ Technical dashboard không được nhận:

  • farmProfit.
  • fcr.
  • survivalRate.
  • feedCost.
  • staffPerformance.
  • dailyLogDetails.

Technical chỉ cần:

  • Ticket.
  • Device.
  • Issue.
  • Location.
  • Contact.
  • Device logs kỹ thuật.
  • Maintenance history.
  • Parts.
  • Service report state.

9. Backend implementation plan chi tiết

Phần này là plan để sửa backend cho phù hợp với kiến trúc dashboard theo role.

9.1. Hiện trạng backend dashboard

Backend hiện có backend/src/reports/dashboard.controller.ts với các endpoint:

GET /dashboard/overview
GET /dashboard/farm-stats
GET /dashboard/device-status

Service hiện tại nằm ở backend/src/reports/dashboard.service.ts và đang aggregate dữ liệu farm/report chung theo report:read. Cách này phù hợp cho dashboard/report tổng quan ban đầu, nhưng chưa đủ cho dashboard theo role vì:

  • Chưa có endpoint riêng cho Super Admin/Admin/Owner/Staff/Technical.
  • Chưa có DTO response tách theo data visibility từng role.
  • Chưa tách service theo boundary nghiệp vụ.
  • getDeviceStatus() đang load toàn bộ devices rồi count ở memory; khi device lớn nên chuyển sang aggregate query.
  • Technical dashboard cần scope theo ticket được giao, không theo organizationId.
  • Staff dashboard cần scope theo pond/task được giao, không theo toàn organization.

Không nên xóa ngay controller/service cũ. Giai đoạn chuyển đổi nên giữ backward compatibility cho các màn report hiện tại, sau đó migrate frontend dashboard mới sang module mới.

9.2. Backend target structure

Tạo module mới:

backend/src/dashboard/
  dashboard.module.ts
  dashboard.controller.ts
  dashboard-permission.service.ts
  dashboard-query.service.ts
  dashboard-cache.service.ts

  dto/
    dashboard-kpi.dto.ts
    dashboard-scope.dto.ts
    super-admin-dashboard-response.dto.ts
    admin-dashboard-response.dto.ts
    owner-dashboard-response.dto.ts
    staff-dashboard-response.dto.ts
    technical-dashboard-response.dto.ts

  services/
    super-admin-dashboard.service.ts
    admin-dashboard.service.ts
    owner-dashboard.service.ts
    staff-dashboard.service.ts
    technical-dashboard.service.ts

  presenters/
    super-admin-dashboard.presenter.ts
    admin-dashboard.presenter.ts
    owner-dashboard.presenter.ts
    staff-dashboard.presenter.ts
    technical-dashboard.presenter.ts

  dashboard.controller.spec.ts
  services/
    super-admin-dashboard.service.spec.ts
    admin-dashboard.service.spec.ts
    owner-dashboard.service.spec.ts
    staff-dashboard.service.spec.ts
    technical-dashboard.service.spec.ts

Lý do tách khỏi reports:

  • Dashboard là operational command center, không chỉ là report analytics.
  • Ticket, technical workload, device lifecycle, staff checklist không nên sống trong reports module.
  • Có thể giữ reports cho report xuất file, chart phân tích dài hạn, comparison, FCR, biomass.

9.3. App module integration

Khi implement, cần:

  1. Import DashboardModule vào backend/src/app.module.ts.
  2. DashboardModule import các module hoặc repository cần thiết:
    • TypeOrmModule.forFeature([...]) cho entities cần query.
    • RbacModule nếu cần AuthorizationPolicyService.
    • HealthModule nếu Super Admin dashboard dùng system health.
  3. Không import RedisModule ở phase đầu; chỉ thêm sau khi có số đo hiệu năng chứng minh cần cache backend.
  4. Không tạo circular dependency với reports.

Entities dự kiến cần đăng ký trong DashboardModule:

Organization
User
Area
Pond
FarmingCycle
DailyLog
GrowthRecord
Device
DeviceAlert
Notification
InventoryItem hoặc InventoryTransaction nếu dashboard cần feed/parts

Sau khi có Ticket & Maintenance module, bổ sung:

MaintenanceTicket
TicketComment hoặc TicketEvent
ServiceReport
MaintenancePartUsage

9.4. Resource/permission plan

backend/src/common/enums/resource.enum.ts hiện chưa có dashboard, ticket, audit, system-health.

Khuyến nghị thêm resource:

export enum Resource {
  // existing resources...
  DASHBOARD = 'dashboard',
  TICKET = 'ticket',
  MAINTENANCE = 'maintenance',
  AUDIT = 'audit',
  SYSTEM_HEALTH = 'system-health',
}

Permission gợi ý:

dashboard:read:system       # Super Admin dashboard
dashboard:read:organization # Admin dashboard
dashboard:read:own          # Owner/Staff/Technical dashboard tùy scope
ticket:read:system
ticket:read:organization
ticket:read:own
ticket:update:own
maintenance:read:system
maintenance:update:own
audit:read:system
audit:read:organization
system-health:read:system

Mapping role gợi ý:

Role Dashboard permission
SUPER_ADMIN dashboard:read:system
ADMIN dashboard:read:organization
OWNER dashboard:read:own
STAFF dashboard:read:own
TECHNICAL dashboard:read:own

Vì Owner, Staff, Technical cùng có dashboard:read:own, controller không được chỉ dựa vào permission này. Cần kiểm tra role/scope cụ thể cho endpoint tương ứng.

Backend role claim phải chuẩn hóa enum. JWT không nên trả role dạng display label như Super Admin; display label chỉ nên xử lý ở frontend i18n/menu.

9.5. Controller endpoint contract

dashboard.controller.ts nên expose:

GET /dashboard/super-admin
GET /dashboard/admin
GET /dashboard/owner
GET /dashboard/staff
GET /dashboard/technical

Contract chi tiết:

Endpoint Guard Permission Role check Scope
/dashboard/super-admin JWT + permission dashboard:read:system SUPER_ADMIN System
/dashboard/admin JWT + permission dashboard:read:organization ADMIN currentUser.organizationId
/dashboard/owner JWT + permission dashboard:read:own OWNER một farm owner quản lý
/dashboard/staff JWT + permission dashboard:read:own STAFF pond/task được giao
/dashboard/technical JWT + permission dashboard:read:own TECHNICAL ticket được giao hoặc internal ticket do technical tạo

Controller không nên nhận organizationId, farmId, staffId, technicalId tự do từ query trong dashboard mặc định. Nếu cần filter:

  • Admin có thể filter farmId, nhưng backend phải verify farm thuộc organization.
  • Owner có thể filter pondId, nhưng backend phải verify pond thuộc farm của owner.
  • Staff có thể filter pondId, nhưng backend phải verify pond được assign.
  • Technical có thể filter ticketId, nhưng backend phải verify ticket assigned to current technical.

9.6. Dashboard permission service

Tạo dashboard-permission.service.ts để gom các kiểm tra role/scope, tránh lặp trong 5 service.

Trách nhiệm:

  • assertRole(currentUser, allowedRoles).
  • assertOrganizationScope(currentUser).
  • getOrganizationIdOrThrow(currentUser).
  • getOwnerFarmId(currentUser).
  • getStaffAssignedPondIds(currentUser).
  • getTechnicalAssignedTicketIds(currentUser).
  • assertFarmInOrganization(farmId, organizationId).
  • assertPondAssignedToStaff(pondId, userId).
  • assertTicketAssignedToTechnical(ticketId, userId).
  • assertInternalTicketCreatedByTechnical(ticketId, userId).

Không nên để frontend truyền scope quyết định. Scope phải được derive từ currentUser và database.

9.7. Query service plan

dashboard-query.service.ts chứa các query helper dùng lại, không chứa role orchestration.

Nhóm query nên có:

Organization/system:

  • countActiveOrganizations().
  • countTotalFarmsSystem().
  • countTotalPondsSystem().
  • countTotalUsersSystem().
  • countUsersByRole(role).
  • getOrganizationHealthRows(limit, offset).

Farm/pond:

  • countFarmsByOrganization(organizationId).
  • countPondsByOrganization(organizationId).
  • countActivePondsByFarmId(farmId).
  • getFarmPerformanceRows(organizationId, filters).
  • getPondAttentionRows(scope).

Aquaculture/log:

  • countActiveCycles(scope).
  • sumTodayFeed(scope, date).
  • getDataCompletion(scope, date).
  • getFcrSummary(scope).
  • getGrowthTrend(scope, range).

Device:

  • countDevicesByStatus(scope).
  • countDevicesByLifecycle(scope).
  • getOfflineDeviceRows(scope, limit).
  • getDeviceIssueRows(scope, limit).
  • getDeviceDiagnosticsForTickets(ticketIds).

Alert:

  • countActiveAlerts(scope).
  • getAlertCenterRows(scope, limit).
  • getCriticalAlerts(scope, limit).

Task/staff:

  • getTodayTasksForOwner(farmId, date).
  • getTodayTasksForStaff(userId, date).
  • getStaffPerformanceRows(farmId, date).
  • getWorkHistory(userId, range).

Ticket/maintenance:

  • countTicketsByStatus(scope).
  • countOverdueTickets(scope).
  • getTicketQueue(scope, limit).
  • getInternalTicketQueueForTechnical(userId, limit).
  • getTechnicalWorkloadRows().
  • getTodayRouteForTechnical(userId, date).
  • getMaintenanceHistory(deviceIds, limit).
  • getPartsNeeded(ticketIds).

9.8. Query efficiency requirements

Các query count/status không nên load entity list rồi filter bằng JavaScript nếu dữ liệu có thể lớn.

Nên dùng:

  • repository.count({ where }) cho count đơn giản.
  • QueryBuilder COUNT(*) FILTER (...) hoặc SUM(CASE WHEN ...) cho nhiều status.
  • GROUP BY cho status/lifecycle/type.
  • Pagination cho table rows.
  • Select field cụ thể thay vì find() full entity nếu chỉ cần row summary.

Ví dụ device status nên đi theo hướng:

SELECT
  COUNT(*) AS total,
  SUM(CASE WHEN status = 'online' THEN 1 ELSE 0 END) AS online,
  SUM(CASE WHEN status = 'offline' THEN 1 ELSE 0 END) AS offline,
  SUM(CASE WHEN status = 'error' THEN 1 ELSE 0 END) AS error
FROM devices
WHERE organization_id = $1;

Trong TypeORM có thể dùng QueryBuilder, không cần raw SQL nếu query vẫn rõ.

9.9. Service orchestration theo role

Super Admin service:

validate Super Admin
load SaaS overview
load organization health
load device lifecycle counts
load device issue rows
load ticket SLA summary
load technical workload
load system health snapshot
load audit/security feed
present response

Admin service:

validate Admin
derive organizationId
load organization overview
load farm performance rows
load farm ranking
load alert center
load organization device summary
load data intervention queue
load owner/staff activity
load reports snapshot
present response

Owner service:

validate Owner
derive single farmId owned by current user
load farm overview
load pond status
load today tasks
load staff performance
load pond attention list
load environment summary
load feed/growth charts
load farm device status
load incident reports
present response

Staff service:

validate Staff
derive assigned pondIds
load today checklist
load my ponds
load quick log entry options
load urgent alerts
load incident report state
load work history
present response

Technical service:

validate Technical
derive assigned ticketIds and internal ticketIds created by current technical
load assigned ticket queue
load internal ticket queue
load today route
load device diagnostics for ticket devices
load parts needed
load maintenance history
load service report state
present response

9.10. DTO response plan

DTO class phải dùng explicit type, không dùng any.

Shared DTO:

DashboardKpiDto
DashboardTrendDto
DashboardScopeDto
DashboardStatusDto
DashboardTablePaginationDto

Role DTO:

SuperAdminDashboardResponseDto
AdminDashboardResponseDto
OwnerDashboardResponseDto
StaffDashboardResponseDto
TechnicalDashboardResponseDto

Mỗi DTO nên có:

  • generatedAt.
  • scope.
  • kpis.
  • Section data.
  • Optional warnings nếu có partial data.

Không nên tạo mega DTO chứa toàn bộ field của 5 role.

9.11. Ticket & Maintenance dependency plan

Technical dashboard cần Ticket & Maintenance module. Nếu module này chưa có, triển khai theo 2 bước:

Step 1: Dashboard foundation không phụ thuộc ticket entity thật.

  • Tạo Technical dashboard endpoint trả empty state có typed response.
  • DTO đã định nghĩa đúng shape.
  • Frontend Technical dashboard render empty state.

Step 2: Khi Ticket module có entity/service.

  • Thêm MaintenanceTicket entity.
  • Thêm ticket repository vào DashboardModule.
  • Implement technical-dashboard.service.ts.
  • Implement Super Admin ticket/technical workload section.
  • Implement Admin/Owner ticket status section nếu cần.
  • Implement luồng Technical tạo internal ticket để ghi nhận sự cố kỹ thuật nội bộ, bảo trì chủ động, kiểm tra lại, hoặc nhu cầu linh kiện không xuất phát từ khách hàng.

Không nên fake ticket bằng device alert lâu dài vì lifecycle ticket có SLA, assignee, verification, parts, service report khác alert.

Technical được tạo internal ticket, nhưng không được tự tạo customer ticket thay mặt Organization/Farm nếu không có luồng triage rõ. Internal ticket cần có loại riêng, ví dụ INTERNAL_MAINTENANCE, và không được mặc định hiển thị cho Owner/Admin như ticket khách hàng.

9.12. Migration và seed plan

Foundation dashboard không nhất thiết cần migration nếu chỉ aggregate dữ liệu hiện có.

Cần migration khi thêm:

  • Ticket & Maintenance tables.
  • Audit log tables nếu chưa có.
  • Task assignment tables nếu chưa có.
  • Device lifecycle fields nếu entity hiện tại chưa đủ.
  • Staff/Pond assignment nếu chưa đủ quan hệ.

Seed nên bổ sung:

  • Demo Super Admin có dashboard:read:system.
  • Demo Admin có dashboard:read:organization.
  • Demo Owner/Staff/Technical có dashboard:read:own.
  • Demo device online/offline.
  • Demo alerts.
  • Demo ticket nếu Ticket module đã có.

9.13. Backend rollout tasks

Task 1: Thêm resource và permission seed.

  • Add DASHBOARD, TICKET, MAINTENANCE, AUDIT, SYSTEM_HEALTH vào Resource.
  • Update role permission seed/migration.
  • Test decode JWT vẫn trả permissions đúng.

Task 2: Tạo DashboardModule skeleton.

  • Create module/controller/service placeholders.
  • Import vào AppModule.
  • Add endpoint typed empty response cho 5 role.
  • Add controller tests cho auth/role rejection cơ bản.

Task 3: Implement permission/scope service.

  • Derive organization scope.
  • Derive owner single-farm scope.
  • Derive staff pond scope.
  • Derive technical ticket scope sau khi có ticket module.
  • Derive technical internal ticket scope theo createdByTechnicalId.
  • Test không cho cross-scope.

Task 4: Implement Admin/Owner/Staff trước.

  • Reuse existing farm/aquaculture/device/alert data.
  • Chưa phụ thuộc Ticket module.
  • Optimize count/group queries.
  • Add DTO tests/service tests.

Task 5: Implement Super Admin.

  • Organization health.
  • Device inventory lifecycle.
  • System-level counts.
  • Audit/system health section có thể trả partial nếu module chưa đủ.

Task 6: Implement Ticket & Maintenance dependency.

  • Add Ticket module nếu chưa có.
  • Add Technical dashboard.
  • Add ticket summary vào Super Admin/Admin/Owner.

Task 7: Deprecate old dashboard endpoints.

  • Frontend mới chuyển sang /dashboard/{role}.
  • Giữ /dashboard/overview, /dashboard/farm-stats, /dashboard/device-status cho report legacy nếu còn dùng.
  • Sau khi không còn consumer, đánh dấu deprecated hoặc migrate sang reports.

9.14. Backend test checklist

Bắt buộc test:

  • Super Admin gọi /dashboard/super-admin thành công.
  • Admin gọi /dashboard/super-admin bị forbidden.
  • Admin gọi /dashboard/admin chỉ thấy organization của mình.
  • Owner gọi /dashboard/owner chỉ thấy farm mình quản lý.
  • Staff gọi /dashboard/staff chỉ thấy pond/task được giao.
  • Technical gọi /dashboard/technical chỉ thấy ticket/device được giao và internal ticket do chính technical tạo.
  • Technical response không chứa FCR, cost, revenue, staff performance, daily log detail.
  • Staff response không chứa cost/revenue/admin report.
  • Query service count device status không load full list khi chỉ cần count.
  • Empty scope trả empty response hợp lệ, không throw 500.

9.15. Backend acceptance criteria

Backend phần dashboard đạt yêu cầu khi:

  • Có 5 endpoint role-specific.
  • Có DTO riêng cho từng role.
  • Có service riêng cho từng role.
  • Có permission/scope guard rõ.
  • Không endpoint nào trả dữ liệu ngoài phạm vi role.
  • Query dashboard chính dùng aggregate query hợp lý.
  • Tests cover role access và data visibility.
  • bun run check pass từ root.

10. Performance plan

9.1. Frontend performance

Áp dụng:

  • Lazy-load từng dashboard role.
  • Lazy-load chart nặng nếu chart nằm dưới fold.
  • Dùng TanStack Query query key ổn định theo role/scope/date/filter.
  • Dùng staleTime theo loại dữ liệu.
  • Dùng pagination/cursor cho bảng lớn như audit/ticket/device.
  • Dùng virtual table nếu bảng Super Admin có nhiều organization/device.
  • Staff dashboard mobile-first, ít chart, ưu tiên checklist và quick actions.

Không nên:

  • Fetch toàn bộ device rồi count ở frontend.
  • Fetch toàn bộ farm/pond rồi tự tính ranking nếu backend có thể aggregate.
  • Dùng một query lớn cho mọi role.
  • Để mọi section polling cùng interval.

9.2. Backend performance

Áp dụng:

  • Aggregate bằng SQL count/group by ở backend.
  • Index các field dùng nhiều trong dashboard: organization_id, farm_id, pond_id, status, created_at, updated_at, last_signal_at, assigned_technical_id.
  • Cache block ít thay đổi.
  • Không join sâu không cần thiết.
  • Với telemetry/time-series, query theo window ngắn và aggregate sẵn.
  • Với reports snapshot, dùng precomputed summary nếu cần.

9.3. Realtime strategy

Không cần realtime toàn bộ dashboard ngay phase đầu.

Ưu tiên realtime/polling ngắn cho:

  • Device online/offline.
  • Critical alerts.
  • Ticket SLA/status.
  • Staff task status hôm nay.

Các block khác có thể refresh thủ công hoặc stale time dài hơn.

11. Security và access plan

11.1. Role access matrix cho dashboard

Endpoint Role chính Scope dữ liệu
/dashboard/super-admin SUPER_ADMIN Toàn hệ thống SaaS
/dashboard/admin ADMIN Organization hiện tại
/dashboard/owner OWNER Một farm được quản lý
/dashboard/staff STAFF Pond/task được giao
/dashboard/technical TECHNICAL Ticket/device được giao và internal ticket do technical tạo

11.2. Audit bắt buộc

Dashboard không chỉ hiển thị dữ liệu; nhiều quick action có thể kích hoạt thay đổi. Những action sau phải có audit khi triển khai mutation:

  • Assign device cho Organization/Farm/Pond.
  • Correct data hoặc mark data reviewed.
  • Tạo ticket từ alert/device issue.
  • Technical tạo internal ticket.
  • Assign technical.
  • Technical cập nhật ticket.
  • Owner/Admin verify ticket.
  • Thay đổi role/scope user.

11.3. Technical isolation

Technical là role đặc biệt:

  • Không thuộc tenant.
  • Không xem business dashboard.
  • Không xem chi phí/lợi nhuận/FCR/sản lượng.
  • Không xem staff performance.
  • Không xem daily log chi tiết.
  • Chỉ xem Organization/Farm/Location ở mức đủ để xử lý ticket.

Do đó Technical dashboard phải là module riêng cả frontend và backend, không tái dùng Admin/Owner data response.

12. Phase triển khai đề xuất

Phase 0: Foundation

Mục tiêu: chuẩn bị route/menu/service boundary trước khi build UI thật.

Tasks:

  1. Tạo DashboardRoute.
  2. Tạo lazy dashboard placeholders cho 5 role.
  3. Tách dashboard services/hooks/types khỏi report service.
  4. Tách navigation config khỏi SidebarMenu.
  5. Redirect / về /dashboard.
  6. Thêm fallback cho unknown role.

Kết quả:

  • Role nào cũng vào đúng dashboard shell.
  • Chưa cần đủ KPI/table thật.
  • Bundle đã có boundary rõ.

Phase 1: Core Farm Operation

Mục tiêu: Owner và Staff vận hành farm thật.

Ưu tiên:

  1. Owner dashboard.
  2. Staff dashboard.
  3. Pond/task/log/alert cơ bản.
  4. Feed/FCR/growth summary.
  5. Staff performance cơ bản.

Lý do:

  • Đây là nhóm người dùng vận hành hằng ngày.
  • Staff cần mobile-first và thao tác nhanh.
  • Owner cần nắm farm, pond, staff, task, device, incident.

Phase 2: Organization Management

Mục tiêu: Admin quản lý nhiều farm trong một organization.

Ưu tiên:

  1. Admin dashboard.
  2. Farm performance table.
  3. Farm ranking.
  4. Alert center cấp organization.
  5. Data completion tracking.
  6. Data correction queue.
  7. Organization device summary.
  8. Owner/staff activity.

Lý do:

  • Admin không phải Owner mở rộng; Admin là cấp organization.
  • Dashboard phải giúp phát hiện farm yếu, dữ liệu thiếu, device lỗi, staff/owner chưa hoàn thành việc.

Phase 3: SaaS Platform Management

Mục tiêu: Super Admin vận hành SaaS multi-tenant.

Ưu tiên:

  1. Super Admin dashboard.
  2. Organization health.
  3. Device inventory lifecycle.
  4. Device issues.
  5. Ticket & maintenance overview.
  6. Technical workload.
  7. System health.
  8. Audit/security feed.

Lý do:

  • Super Admin cần control center toàn hệ thống.
  • Không nên dùng Farm dashboard hoặc Admin dashboard cho Super Admin.

Phase 4: IoT & Maintenance

Mục tiêu: hoàn thiện luồng thiết bị thật và đội Technical.

Ưu tiên:

  1. Ticket & Maintenance module.
  2. Technical dashboard.
  3. Device diagnostics.
  4. SLA.
  5. Maintenance history.
  6. Parts/inventory.
  7. Service report.

Lý do:

  • Technical dashboard phụ thuộc ticket lifecycle.
  • Nếu chưa có ticket module, Technical dashboard chỉ là UI rỗng.

13. Testing plan

13.1. Frontend tests

Nên có test cho:

  • DashboardRoute map role đúng dashboard.
  • Unknown role hiển thị forbidden/empty state.
  • JWT/backend trả nhiều role thì dashboard fail-closed hoặc hiển thị invalid role state.
  • Role enum SUPER_ADMIN, ADMIN, OWNER, STAFF, TECHNICAL map đúng dashboard.
  • Navigation resolver trả đúng menu theo role/permission.
  • Staff dashboard không render section nhạy cảm.
  • Technical dashboard không render business metric.

Test dạng unit/component là đủ cho foundation.

13.2. Backend tests

Nên có test cho:

  • Mỗi endpoint reject role không đúng.
  • Role claim dùng enum chuẩn, không dùng display label.
  • Admin endpoint chỉ trả organization hiện tại.
  • Owner endpoint chỉ trả đúng một farm được quản lý.
  • Staff endpoint chỉ trả pond/task được giao.
  • Technical endpoint chỉ trả ticket/device được giao và internal ticket do chính technical tạo.
  • Super Admin endpoint không bị filter sai bởi organization scope.
  • DTO không chứa field nhạy cảm ở Technical/Staff response.

13.3. Performance verification

Nên đo:

  • Dashboard initial load theo role.
  • Số request khi vào /dashboard.
  • Bundle chunk cho từng role.
  • Query duration cho endpoint aggregate.
  • Polling interval không gây request storm.

14. Coding convention áp dụng

Theo AGENTS.md của repo:

  • Dùng Bun, không dùng npm/yarn.
  • Dùng Biome, không chạy ESLint/Prettier trực tiếp.
  • Không dùng any; dùng unknown hoặc type rõ.
  • Frontend import bằng alias @/, không dùng ../.
  • Sau mỗi edit chạy bun run check từ root.

Quy ước bổ sung cho dashboard:

  • Component role dashboard chỉ import từ cùng role folder hoặc shared.
  • Không import chéo admin sang owner, owner sang staff, hoặc role khác.
  • Shared component không biết role hiện tại.
  • Hook/service role không nằm trong component file.
  • Type API response không đặt inline trong JSX.
  • Không để frontend tự filter dữ liệu nhạy cảm từ response dư.

15. Quyết định kiến trúc đã chốt

Các quyết định sau là baseline khi implement:

  1. Backend role chuẩn hóa enum: SUPER_ADMIN, ADMIN, OWNER, STAFF, TECHNICAL.
  2. User không thể có nhiều role active trong dashboard.
  3. Owner quản lý đúng một farm.
  4. Technical được tạo ticket nội bộ.
  5. Dashboard endpoint chỉ thêm Redis sau khi có số đo hiệu năng.
  6. Route / redirect về /dashboard.

Hệ quả triển khai:

  • JWT/backend phải trả một role enum duy nhất cho dashboard. Nếu payload trả nhiều role, frontend/backend nên fail-closed thay vì tự chọn priority.
  • dashboard-role.ts chỉ validate/normalize enum chuẩn, không cần logic priority hoặc activeRole.
  • Owner dashboard/service dùng farmId, không dùng farmIds.
  • Technical dashboard/service có hai nhóm ticket: ticket được giao và internal ticket do technical tạo.
  • Customer ticket vẫn cần luồng triage/assignment riêng; internal ticket của Technical không mặc định thay mặt khách hàng.
  • Redis không nằm trong foundation phase; phase đầu cần đo query duration, request count và load thực tế trước.
  • / không còn render legacy FarmDashboard; mọi role vào /dashboard rồi được route tới dashboard tương ứng.

16. Definition of done

Một phase dashboard được xem là hoàn thành khi:

  • Route /dashboard đưa đúng role vào đúng dashboard.
  • Menu sidebar hiển thị đúng theo role và permission.
  • Endpoint backend enforce scope đúng role.
  • Response không chứa dữ liệu ngoài phạm vi role.
  • Loading/error/empty states có xử lý.
  • Các bảng lớn có pagination hoặc giới hạn rõ.
  • Query key và stale/refetch interval được định nghĩa rõ.
  • Unit/backend tests cover role routing và data visibility chính.
  • bun run check pass từ root.

17. Tóm tắt quyết định

Triển khai dashboard nên đi theo hướng:

  • Frontend: role-specific page modules, lazy-load, shared primitive components.
  • Backend: dashboard module riêng, aggregate endpoint theo role, DTO theo scope.
  • Navigation: menu config tách theo role, SidebarMenu chỉ render.
  • Security: backend quyết định data visibility, frontend không nhận dữ liệu dư.
  • Performance: aggregate API, cache/polling theo block, không fetch toàn bộ rồi tính ở frontend.
  • Rollout: Foundation -> Owner/Staff -> Admin -> Super Admin -> Technical/Ticket.

Đây là hướng phù hợp nhất với yêu cầu SaaS multi-tenant, RBAC theo resource:action:scope, và đặc thù Technical không thuộc tenant nhưng cần xử lý thiết bị thật.