Files
minicrm/CODEBASE_SNAPSHOT.md
2026-05-12 07:18:09 +00:00

578 lines
24 KiB
Markdown

# CODEBASE_SNAPSHOT.md — AfterSales CRM
> **Mục đích:** File snapshot toàn bộ cấu trúc codebase hiện tại. Dành cho AI Agent đọc thay vì scan toàn bộ code.
> **Cập nhật:** Mỗi khi có thay đổi lớn về cấu trúc (thêm/xóa Model, Migration, Resource, Service, Policy...).
---
## Thông tin chung
| Mục | Giá trị |
|------|--------|
| Framework | Laravel 13.6.0 |
| Panels | Filament 5.6.1 — 2 panels: `/admin` (đầy đủ) + `/support` (rút gọn) |
| Auth/RBAC | Spatie laravel-permission (3 roles: admin, manager, staff, 29 permissions) |
| Database | SQLite (`database/database.sqlite`) |
| Testing | Pest PHP |
| PHP | 8.3 |
| Composer | `/home/phuongtc/composer` |
**Test Accounts (seeded):**
| Role | Email | Password |
|------|-------|----------|
| Admin | admin@minicrm.local | password |
| Manager | manager@minicrm.local | password |
| Staff | staff@minicrm.local | password |
---
## Cây thư mục `app/`
```
app/
├── Filament/
│ ├── Resources/
│ │ ├── ActivityLogs/ # NEW: Audit trail (read-only)
│ │ │ ├── ActivityLogResource.php
│ │ │ └── Pages/ListActivityLogs.php
│ │ ├── Customers/
│ │ │ ├── CustomerResource.php
│ │ │ ├── Pages/
│ │ │ │ ├── CreateCustomer.php
│ │ │ │ ├── EditCustomer.php
│ │ │ │ └── ListCustomers.php
│ │ │ ├── RelationManagers/
│ │ │ │ └── FeedbacksRelationManager.php # read-only table
│ │ │ ├── Schemas/
│ │ │ │ └── CustomerForm.php
│ │ │ └── Tables/
│ │ │ └── CustomersTable.php
│ │ ├── Feedback/
│ │ │ ├── FeedbackResource.php
│ │ │ ├── Pages/
│ │ │ │ ├── CreateFeedback.php
│ │ │ │ ├── EditFeedback.php # TransferDepartment + CloseTicket actions
│ │ │ │ └── ListFeedback.php
│ │ │ ├── RelationManagers/
│ │ │ │ └── InteractionsRelationManager.php # status_change/note/assignment
│ │ │ ├── Schemas/
│ │ │ │ └── FeedbackForm.php
│ │ │ └── Tables/
│ │ │ └── FeedbackTable.php
│ │ ├── Contracts/
│ │ │ ├── ContractResource.php
│ │ │ ├── Pages/
│ │ │ │ ├── CreateContract.php
│ │ │ │ ├── EditContract.php
│ │ │ │ └── ListContracts.php
│ │ │ ├── Schemas/
│ │ │ │ └── ContractForm.php
│ │ │ └── Tables/
│ │ │ └── ContractsTable.php
│ │ ├── FeedbackChannels/
│ │ │ ├── FeedbackChannelResource.php
│ │ │ ├── Pages/
│ │ │ │ ├── CreateFeedbackChannel.php
│ │ │ │ ├── EditFeedbackChannel.php
│ │ │ │ └── ListFeedbackChannels.php
│ │ │ ├── Schemas/
│ │ │ │ └── FeedbackChannelForm.php
│ │ │ └── Tables/
│ │ │ └── FeedbackChannelsTable.php
│ │ ├── FeedbackTags/
│ │ │ ├── FeedbackTagResource.php
│ │ │ ├── Pages/
│ │ │ │ ├── CreateFeedbackTag.php
│ │ │ │ ├── EditFeedbackTag.php
│ │ │ │ └── ListFeedbackTags.php
│ │ │ ├── Schemas/
│ │ │ │ └── FeedbackTagForm.php
│ │ │ └── Tables/
│ │ │ └── FeedbackTagsTable.php
│ │ ├── Products/
│ │ │ ├── ProductResource.php
│ │ │ ├── Pages/
│ │ │ │ ├── CreateProduct.php
│ │ │ │ ├── EditProduct.php
│ │ │ │ └── ListProducts.php
│ │ │ ├── RelationManagers/
│ │ │ │ ├── ContractsRelationManager.php
│ │ │ │ ├── HandoversRelationManager.php
│ │ │ │ └── ProductServicesRelationManager.php
│ │ │ ├── Schemas/
│ │ │ │ └── ProductForm.php
│ │ │ └── Tables/
│ │ │ └── ProductsTable.php
│ │ ├── Users/ # Admin-only user management
│ │ │ ├── UserResource.php
│ │ │ ├── Pages/
│ │ │ │ ├── CreateUser.php
│ │ │ │ ├── EditUser.php
│ │ │ │ └── ListUsers.php # Tabs: All/Admin/Manager/Staff
│ │ │ ├── Schemas/
│ │ │ │ └── UserForm.php # name, email, password, role (dynamic)
│ │ │ └── Tables/
│ │ │ └── UsersTable.php # name, email, role badge
│ │ └── Roles/ # Admin-only role management
│ │ ├── RoleResource.php
│ │ ├── Pages/
│ │ │ ├── CreateRole.php
│ │ │ ├── EditRole.php
│ │ │ └── ListRoles.php
│ │ ├── Schemas/
│ │ │ └── RoleForm.php # name + permissions checkbox list
│ │ └── Tables/
│ │ └── RolesTable.php # name, permissions count, users count
│ └── Widgets/
│ ├── AvgProcessingTimeWidget.php # 4 stats: resolved count, avg time, pending, escalated
│ ├── HandlerPerformanceWidget.php # bar chart: avg time per handler
│ └── ResolvedTicketsWidget.php # table: resolved tickets awaiting closure
├── Console/
│ └── Commands/
│ ├── ImportCskh.php # app:import-cskh command
│ └── ExportBackup.php # app:export-backup command (JSON backup)
├── Enums/
│ ├── ContractType.php # SALE/LEASE/BCC
│ ├── ContractStatus.php # ACTIVE/EXPIRED/LIQUIDATED
│ ├── HandoverStatus.php # NOT_READY/READY/SCHEDULED/HANDED_OVER
│ └── ServiceType.php # MANAGEMENT_FEE/GRATITUDE/SELF_BUSINESS
├── Http/
│ ├── Controllers/
│ │ └── Controller.php
│ └── Middleware/
│ └── HandleUploadErrors.php
├── Models/
│ ├── ActivityLog.php # NEW: Audit trail (user_id, action, description, metadata)
│ ├── Contract.php
│ ├── Customer.php
│ ├── CustomerProduct.php # pivot model (customer_product table)
│ ├── Department.php
│ ├── Feedback.php
│ ├── FeedbackAttachment.php
│ ├── FeedbackChannel.php
│ ├── FeedbackInteraction.php
│ ├── FeedbackTag.php
│ ├── Handover.php
│ ├── Product.php
│ ├── ProductService.php
│ ├── TicketTransferLog.php
│ └── User.php
├── Notifications/
│ ├── TicketClosed.php
│ └── TicketTransferred.php
├── Policies/
│ ├── ActivityLogPolicy.php # NEW: Admin-only
│ ├── ContractPolicy.php
│ ├── CustomerPolicy.php
│ ├── FeedbackChannelPolicy.php
│ ├── FeedbackPolicy.php
│ ├── ProductPolicy.php
│ ├── RolePolicy.php
│ └── UserPolicy.php
├── Providers/
│ ├── AppServiceProvider.php
│ └── Filament/
│ ├── AdminPanelProvider.php # Panel đầy đủ (/admin)
│ └── SupportPanelProvider.php # Panel rút gọn (/support)
├── Rules/
│ └── RequireProofOfResolution.php
└── Services/
├── ActivityLogger.php # NEW: static log() cho audit trail
├── ClosingService.php
├── FileService.php
└── TransferService.php
```
---
## Dual Panel Architecture
### Admin Panel (`/admin`) — `AdminPanelProvider.php`
Đầy đủ tất cả modules:
- Resources: Feedback, Customer, Product, Contract, FeedbackChannel, FeedbackTag, User, Role, ActivityLog
- Widgets: ResolvedTicketsWidget, AvgProcessingTimeWidget, HandlerPerformanceWidget
- User/Role management (admin-only)
### Support Panel (`/support`) — `SupportPanelProvider.php`
Rút gọn, tập trung feedback:
- Resources: Feedback, Customer, Product, FeedbackChannel, FeedbackTag, ActivityLog
- Widgets: ResolvedTicketsWidget, AvgProcessingTimeWidget, HandlerPerformanceWidget
---
## Models — Chi tiết
### ActivityLog (`activity_logs`) — NEW
| Cột | Kiểu |
|-----|------|
| id | bigint PK |
| user_id | FK->users nullable |
| action | string (import/export/transfer/close/create/update/delete) |
| description | text |
| subject_type | string nullable |
| subject_id | bigint nullable |
| metadata | json nullable |
| created_at | timestamp |
Relationships: `user()` BelongsTo
### Product (`products`)
| Cột | Kiểu |
|-----|------|
| id | bigint PK |
| name | string |
| description | text nullable |
| status | string (active/inactive/sold_out) |
Relationships: `customers()` BelongsToMany (pivot: customer_product, withPivot: purchase_date), `contracts()` HasMany, `handovers()` HasMany, `productServices()` HasMany
### ProductService (`product_services`)
| Cột | Kiểu |
|-----|------|
| id | bigint PK |
| product_id | FK->products cascadeOnDelete |
| service_type | string (management_fee/gratitude/self_business → ServiceType enum) |
| status | string (active/pending/completed/cancelled) |
| actual_collection_date | date nullable |
| remark | text nullable |
Relationships: `product()` BelongsTo
### Handover (`handovers`)
| Cột | Kiểu |
|-----|------|
| id | bigint PK |
| product_id | FK->products cascadeOnDelete |
| customer_id | FK->customers cascadeOnDelete |
| handover_date | date nullable |
| status | string (chua_du_dieu_kien/du_dieu_kien/da_len_lich/da_ban_giao → HandoverStatus enum) |
| remark | text nullable |
Relationships: `product()` BelongsTo, `customer()` BelongsTo
### Contract (`contracts`)
| Cột | Kiểu |
|-----|------|
| id | bigint PK |
| product_id | FK->products cascadeOnDelete |
| customer_id | FK->customers cascadeOnDelete |
| type | string (sale/lease/bcc → ContractType enum) |
| start_date | date nullable |
| end_date | date nullable |
| status | string (active/expired/liquidated → ContractStatus enum) |
| printed_at | date nullable |
| signed_status | string nullable |
Relationships: `product()` BelongsTo, `customer()` BelongsTo, `feedbacks()` HasMany
### Customer (`customers`)
| Cột | Kiểu |
|-----|------|
| id | bigint PK |
| name | string |
| email | string nullable unique |
| phone | string nullable |
| address | text nullable |
| status | string (active) |
Relationships: `products()` BelongsToMany, `feedbacks()` HasMany
### CustomerProduct (`customer_product` — pivot model)
| Cột | Kiểu |
|-----|------|
| id | bigint PK |
| customer_id | FK->customers cascadeOnDelete |
| product_id | FK->products cascadeOnDelete |
| purchase_date | date nullable |
UNIQUE(customer_id, product_id)
Relationships: `customer()` BelongsTo, `product()` BelongsTo, `feedbacks()` HasMany
### Department (`departments`)
| Cột | Kiểu |
|-----|------|
| id | bigint PK |
| name | string |
| manager_id | FK->users nullable nullOnDelete |
Relationships: `manager()` BelongsTo User, `feedbacks()` HasMany, `transferLogsFrom()` HasMany, `transferLogsTo()` HasMany
### User (`users`)
| Cột | Kiểu |
|-----|------|
| id | bigint PK |
| name | string |
| email | string unique |
| password | string |
| role | string (admin/manager/staff) |
Uses Spatie `HasRoles`. Relationships: `assignedFeedbacks()` HasMany Feedback, `activityLogs()` HasMany.
### Feedback (`feedback`)
| Cột | Kiểu |
|-----|------|
| id | bigint PK |
| customer_id | FK->customers cascadeOnDelete |
| customer_product_id | FK->customer_product nullable nullOnDelete |
| contract_id | FK->contracts nullable nullOnDelete |
| feedback_channel_id | FK->feedback_channels |
| assigned_to | FK->users nullable nullOnDelete |
| current_department_id | FK->departments nullable nullOnDelete |
| is_escalated | boolean default false |
| title | string |
| content | text |
| status | string (pending/processing/resolved/closed) |
Fillable: `['customer_id', 'customer_product_id', 'contract_id', 'feedback_channel_id', 'assigned_to', 'title', 'content', 'status', 'current_department_id', 'is_escalated']`
Relationships (10): `customer()`, `customerProduct()`, `contract()`, `feedbackChannel()`, `assignedTo()`, `currentDepartment()`, `transferLogs()`, `interactions()` ordered latest, `attachments()`, `tags()` BelongsToMany
### FeedbackInteraction (`feedback_interactions`)
| Cột | Kiểu |
|-----|------|
| id | bigint PK |
| feedback_id | FK->feedback cascadeOnDelete |
| user_id | FK->users |
| type | string (note/status_change/assignment) |
| content | text nullable |
| changes | json nullable |
Relationships: `feedback()` BelongsTo, `user()` BelongsTo, `attachments()` HasMany
### FeedbackAttachment (`feedback_attachments`)
| Cột | Kiểu |
|-----|------|
| id | bigint PK |
| uuid | uuid nullable unique |
| feedback_id | FK->feedback cascadeOnDelete |
| feedback_interaction_id | FK->feedback_interactions nullable nullOnDelete |
| user_id | FK->users |
| name | string |
| path | string |
| disk | string default 'local' |
| collection | string default 'general' (proof_of_resolution/customer_evidence/general) |
| mime_type | string nullable |
| size | bigint nullable |
Relationships: `feedback()`, `interaction()`, `user()`. Scopes: `byCollection()`, `byDisk()`.
### FeedbackChannel (`feedback_channels`)
| Cột | Kiểu |
|-----|------|
| id | bigint PK |
| name | string |
| slug | string unique |
| icon | string nullable |
| color | string nullable |
| is_active | boolean default true |
Relationships: `feedbacks()` HasMany
### FeedbackTag (`feedback_tags`)
| Cột | Kiểu |
|-----|------|
| id | bigint PK |
| name | string |
| slug | string unique |
| color | string nullable |
Relationships: `feedbacks()` BelongsToMany (pivot: feedback_feedback_tag)
### TicketTransferLog (`ticket_transfer_logs`)
| Cột | Kiểu |
|-----|------|
| id | bigint PK |
| feedback_id | FK->feedback cascadeOnDelete |
| from_department_id | FK->departments nullable nullOnDelete |
| to_department_id | FK->departments nullable nullOnDelete |
| sender_id | FK->users |
| reason | text |
Relationships: `feedback()`, `fromDepartment()`, `toDepartment()`, `sender()`
---
## Services — Tóm tắt
### ActivityLogger (`app/Services/ActivityLogger.php`) — NEW
- `ActivityLogger::log(action, description, subject?, metadata?, userId?)`: Static method. Tạo ActivityLog record cho audit trail. Dùng trong Import/Export/Transfer/Close/Create/Update/Delete.
### TransferService (`app/Services/TransferService.php`)
- `transfer(Feedback, Department, reason, User, ?int $newHandlerId)`: Tạo TicketTransferLog, cập nhật current_department_id + assigned_to, gửi TicketTransferred notification cho manager đích.
- Validate reason không được rỗng (ValidationException).
### FileService (`app/Services/FileService.php`)
- `upload()`: Validate (jpg/png/pdf/mp4/mov, ≤20MB), store vào public disk, tạo FeedbackAttachment record.
- `getTemporaryUrl()` / `getPreviewUrl()`: Signed URL cho private file.
- `getByCollection()` / `hasCollection()`: Query collection-based attachments.
### ClosingService (`app/Services/ClosingService.php`)
- `close(Feedback, User)`: 4 guard: status ≠ closed, staff → 403, manager chỉ đóng ticket phòng mình, phải có proof_of_resolution attachment (FileService::hasCollection). Gửi TicketClosed notification.
- `resolve(Feedback, User)`: Đánh dấu resolved (không role gate).
---
## Policies — Tóm tắt
> **Note:** Policies use Spatie `hasPermissionTo()` instead of `hasRole()` for granular permission control.
| Policy | viewAny/view | create | update | delete | restore/forceDelete |
|--------|-------------|--------|--------|--------|---------------------|
| ActivityLogPolicy | admin only | — | — | — | — |
| ContractPolicy | view-contract | create-contract | update-contract | delete-contract | admin only |
| FeedbackPolicy | view-feedback | create-feedback | update-feedback | delete-feedback | admin only |
| ProductPolicy | view-product | create-product | update-product | delete-product | admin only |
| CustomerPolicy | view-customer | create-customer | update-customer | delete-customer | admin only |
| FeedbackChannelPolicy | view-channel | create-channel | update-channel | delete-channel | admin only |
| UserPolicy | admin only | admin only | admin only | admin only (not self) | admin only (not self) |
| RolePolicy | admin only | admin only | admin only | admin only (not built-in) | admin only (not built-in) |
---
## Filament Navigation Structure
### Admin Panel (`/admin`)
```
Dashboard (AccountWidget + 3 custom widgets)
├── Customer Care (group)
│ └── Feedbacks (heroicon-o-chat-bubble-left-right, sort=1)
└── Management (group)
├── Products (heroicon-o-building-storefront, sort=1)
├── Customers (heroicon-o-user-group, sort=2)
├── Channels (heroicon-o-inbox-stack, sort=3)
├── Contracts (heroicon-o-document-text, sort=4)
├── Tags (heroicon-o-tag, default sort)
├── Activity Logs (heroicon-o-clock, sort=7) — admin only
├── Users (heroicon-o-users, sort=5) — admin only
└── Roles (heroicon-o-shield-check, sort=6) — admin only
```
### Support Panel (`/support`)
```
Dashboard (3 custom widgets)
├── Customer Care (group)
│ └── Feedbacks
└── Management (group)
├── Products
├── Customers
├── Channels
├── Tags
└── Activity Logs
```
---
## Key Filament Actions trên EditFeedback
1. **TransferDepartment** — Modal: Chọn phòng ban đích + reason (required) + attachments (optional). Visibility: admin/manager.
2. **CloseTicket** — Confirm modal. Visibility: admin/manager, status ≠ closed.
---
## Database Migration files (25 files)
| # | File | Tables |
|---|------|--------|
| 1 | `0001_01_01_000000` | users, password_reset_tokens, sessions |
| 2 | `0001_01_01_000001` | cache, cache_locks |
| 3 | `0001_01_01_000002` | jobs, job_batches, failed_jobs |
| 4 | `2026_04_26_052423_create_products_table` | products |
| 5 | `2026_04_26_052423_create_customers_table` | customers |
| 6 | `2026_04_26_052423_create_customer_product_table` | customer_product (pivot) |
| 7 | `2026_04_26_052424_create_feedback_channels_table` | feedback_channels |
| 8 | `2026_04_26_052424_create_feedbacks_table` | feedback (base) |
| 9 | `2026_04_26_052528_add_role_to_users_table` | (modify users) add role |
| 10 | `2026_04_26_063709_create_feedback_interactions_table` | feedback_interactions |
| 11 | `2026_04_26_063710_create_feedback_attachments_table` | feedback_attachments (base) |
| 12 | `2026_04_26_070225_create_feedback_tags_table` | feedback_tags, feedback_feedback_tag |
| 13 | `2026_04_27_030125_create_permission_tables` | Spatie: permissions, roles, model_has_roles, etc. |
| 14 | `2026_04_27_080000_create_departments_table` | departments |
| 15 | `2026_04_27_081000_add_department_to_feedback_table` | (modify feedback) add current_department_id, is_escalated |
| 16 | `2026_04_27_082000_create_ticket_transfer_logs_table` | ticket_transfer_logs |
| 17 | `2026_04_27_083000_add_collection_to_feedback_attachments_table` | (modify feedback_attachments) add uuid, disk, collection |
| 18 | `2026_04_27_140000_fix_attachment_disk_default` | (modify feedback_attachments) fix disk default private→local |
| 19 | `2026_04_27_150000_create_notifications_table` | notifications |
| 20 | `2026_05_01_000000_create_contracts_table` | contracts |
| 21 | `2026_05_01_000001_add_contract_id_to_feedback_table` | (modify feedback) add contract_id FK |
| 22 | `2026_05_01_000002_create_handovers_table` | handovers |
| 23 | `2026_05_01_000003_create_product_services_table` | product_services |
| 24 | `2026_05_01_000004_add_color_to_feedback_channels_table` | (modify feedback_channels) add color |
| 25 | `2026_05_09_104519_create_activity_logs_table` | activity_logs |
---
## Seeder (AfterSalesSeeder + PermissionSeeder) — Data mẫu
- 3 users (admin, manager, staff) + Spatie roles
- **29 granular permissions** (PermissionSeeder)
- Feedback: view/create/update/delete, add-interaction, close-ticket, transfer-department
- Products/Customers/Contracts/Channels/Tags: view/create/update/delete each
- Dashboard: view-dashboard-widgets, export-data
- Role → Permission mapping: admin (all), manager (no delete/export), staff (view + limited write)
- 4 departments (CSKH: manager quản lý, Kỹ thuật: admin quản lý, Kế toán, Pháp lý)
- 5 feedback channels (Email, Zalo, Phone, Document, In Person)
- 5 products (Sunrise A1, A2, Green Valley Villa, Ocean Tower, Park Hill)
- 4 customers
- 5 customer-product assignments
- **5 contracts** (2 Sale, 2 Lease, 1 BCC)
- **5 handovers** (2 Đã BG, 1 Đủ ĐK, 1 Đã lên lịch, 1 Chưa đủ ĐK)
- **5 product_services** (2 Management Fee, 2 Gratitude, 1 Self Business)
- 5 feedbacks (pending: 2, processing: 1, resolved: 1, closed: 1)
- 5 feedback tags
- 5 feedback interactions
- 1 ticket_transfer_log
- 3 feedback_attachments (2 proof_of_resolution, 1 customer_evidence)
---
## Data Import (`app:import-cskh`)
Command: `php artisan app:import-cskh {file_path}`
- Sử dụng `spatie/simple-excel` với Lazy Collection (chống OOM)
- ProgressBar + chunk 100 rows/transaction
- Pipeline mỗi dòng:
1. `firstOrCreate` Product (Mã căn hộ) + Customer (Họ tên KH)
2. Insert Contract, Handover, ProductService nếu có dữ liệu
3. Tách `Lịch Sử khách hàng` → Feedback + FeedbackInteraction
4. Auto-gắn active contract vào Feedback
- Hỗ trợ cột: Mã căn hộ, Họ tên KH, BÀN GIÀO CĂN HỘ, TÌNH TRẠNG, HỢP ĐỒNG THUÊ, HĐ HỢP TÁC KINH DOANH, TỰ DOANH, Phí QL, Lịch Sử khách hàng
---
**Test files:** `tests/Feature/ClosingPermissionTest.php`, `tests/Feature/TransferFlowTest.php`
| Test | Coverage |
|------|----------|
| ClosingPermissionTest (4 tests) | staff close → 403, close without evidence → 422, close with evidence → OK, cross-department manager → 403 |
| TransferFlowTest (2 tests) | successful transfer, missing reason |
Tổng: 8 tests, 21 assertions.
---
## Các Pattern cần biết khi code
1. **Form components:** `Filament\Forms\Components\*` (Select, TextInput, Textarea, Toggle, FileUpload, RichEditor)
2. **Schema/layout:** `Filament\Schemas\Components\*` (Section, Schema)
3. **Service access:** `App::make(FileService::class)` hoặc inject constructor. `ActivityLogger::log()` là static.
4. **Role check:** `$user->hasRole('admin')` / `$user->hasRole(['admin', 'manager'])`
5. **Dynamic form field:** dùng `$get` function injection, KHÔNG dùng `$component->getLivewire()->data`
6. **FileUpload với dehydrated(false):** File được Filament lưu trước, chỉ cần tạo FeedbackAttachment record trong after()/afterSave() — KHÔNG re-upload qua FileService
7. **Private disk:** files lưu ở `storage/app/private/feedback-attachments/`, truy cập qua signed URL từ FileService. Luôn dùng `disk('public')` cho FileUpload.
8. **Navigation group icons:** Filament 5 báo lỗi nếu cả navigation group VÀ child items đều có icons
9. **Dual Panel:** Cả 2 panel share models, services, policies, database, translations
---
## File hướng dẫn liên quan
| File | Nội dung |
|------|----------|
| `AGENTS.md` | Hướng dẫn cho AI Agent: cách chạy, cấu trúc, workflow, RBAC, gotchas, patterns |
| `TASKS_ROADMAP.md` | Roadmap + lịch sử tiến độ hoàn thành |
| `docs/I18N_GUIDE.md` | Hướng dẫn đa ngôn ngữ chi tiết |
| `docs/ENUM_GUIDE.md` | Hướng dẫn thêm Enum mới |
| `UI_DESIGN_BRIEF.md` | Thiết kế UI chi tiết (horizontal top bar) |
| `webappUI/aftersales_crm_core/DESIGN.md` | Design tokens (colors, typography, spacing) |
| `luutru/` | Archive các file đã lỗi thời (tham khảo nếu cần) |