update DOC
This commit is contained in:
205
AGENTS.md
205
AGENTS.md
@@ -3,19 +3,25 @@
|
|||||||
> **Trước khi làm việc, đọc các file quan trọng:**
|
> **Trước khi làm việc, đọc các file quan trọng:**
|
||||||
> - `CODEBASE_SNAPSHOT.md` — Toàn bộ cấu trúc codebase (thay vì scan lại code)
|
> - `CODEBASE_SNAPSHOT.md` — Toàn bộ cấu trúc codebase (thay vì scan lại code)
|
||||||
> - `TASKS_ROADMAP.md` — Roadmap: đã làm, đang làm, sẽ làm
|
> - `TASKS_ROADMAP.md` — Roadmap: đã làm, đang làm, sẽ làm
|
||||||
> - `PROGRESS.md` — Lịch sử tiến độ hoàn thành
|
|
||||||
> - `docs/I18N_GUIDE.md` — Hướng dẫn đa ngôn ngữ
|
> - `docs/I18N_GUIDE.md` — Hướng dẫn đa ngôn ngữ
|
||||||
> - `docs/ENUM_GUIDE.md` — Hướng dẫn thêm Enum mới
|
> - `docs/ENUM_GUIDE.md` — Hướng dẫn thêm Enum mới
|
||||||
|
> - `luutru/` — Archive các file đã lỗi thời (tham khảo nếu cần)
|
||||||
|
>
|
||||||
|
> **File đã lỗi thời trong luutru/:** PROGRESS.md, SOP_des.md, AI_01/02/03, CHANGELOG.md, README.md gốc, SYSTEM_ASSESSMENT_PROPOSALS.md
|
||||||
|
|
||||||
## Overview
|
## Overview
|
||||||
AfterSales CRM is a real-estate after-sales customer care system built with **Laravel 13.6** + **Filament 5.6**.
|
AfterSales CRM is a real-estate after-sales customer care system built with **Laravel 13.6** + **Filament 5.6**.
|
||||||
|
Hệ thống có **2 Filament Panel**:
|
||||||
|
- `/admin` — Admin Panel: đầy đủ tất cả modules (Contract, Handover, Service, User/Role...)
|
||||||
|
- `/support` — Support Panel: rút gọn, tập trung Feedback (Feedback, Customer, Product, Channel, Tag, ActivityLog)
|
||||||
|
|
||||||
## How to Run
|
## How to Run
|
||||||
```bash
|
```bash
|
||||||
cd /home/phuongtc/vibecode/minicrm
|
cd /home/phuongtc/vibecode/minicrm
|
||||||
php artisan migrate:fresh --seed
|
php artisan migrate:fresh --seed
|
||||||
php artisan serve
|
php artisan serve
|
||||||
# Open http://localhost:8000/admin
|
# Admin panel: http://localhost:8000/admin
|
||||||
|
# Support panel: http://localhost:8000/support
|
||||||
```
|
```
|
||||||
|
|
||||||
### Test Accounts (seeded)
|
### Test Accounts (seeded)
|
||||||
@@ -51,8 +57,30 @@ php artisan test # run pest tests
|
|||||||
php artisan app:export-backup
|
php artisan app:export-backup
|
||||||
php artisan app:export-backup --path=/custom/path --pretty
|
php artisan app:export-backup --path=/custom/path --pretty
|
||||||
php artisan app:export-backup --tables=feedback,customers,products
|
php artisan app:export-backup --tables=feedback,customers,products
|
||||||
|
|
||||||
|
# Import data from Excel
|
||||||
|
php artisan app:import-cskh ai_instructions/CSKH.xlsx --dry-run
|
||||||
|
php artisan app:import-cskh ai_instructions/CSKH.xlsx
|
||||||
```
|
```
|
||||||
|
|
||||||
|
## Dual Panel Architecture
|
||||||
|
|
||||||
|
Hệ thống có 2 Filament Panel độc lập:
|
||||||
|
|
||||||
|
### Admin Panel (`/admin`) — `AdminPanelProvider.php`
|
||||||
|
Đầy đủ tất cả modules, bao gồm:
|
||||||
|
- Feedback, Customer, Product, Contract, FeedbackChannel, FeedbackTag
|
||||||
|
- User, Role (admin-only)
|
||||||
|
- 3 Dashboard widgets: ResolvedTicketsWidget, AvgProcessingTimeWidget, HandlerPerformanceWidget
|
||||||
|
|
||||||
|
### Support Panel (`/support`) — `SupportPanelProvider.php`
|
||||||
|
Rút gọn, chỉ tập trung vào quy trình xử lý feedback:
|
||||||
|
- Feedback, Customer, Product, FeedbackChannel, FeedbackTag, ActivityLog
|
||||||
|
- 3 Dashboard widgets giống Admin panel
|
||||||
|
- Không có: Contracts, Users, Roles, Handovers, ProductServices
|
||||||
|
|
||||||
|
Cả 2 panel dùng chung: Models, Services, Policies, Database, Translations.
|
||||||
|
|
||||||
## Directory Structure
|
## Directory Structure
|
||||||
|
|
||||||
```
|
```
|
||||||
@@ -60,6 +88,9 @@ minicrm/
|
|||||||
├── app/
|
├── app/
|
||||||
│ ├── Filament/
|
│ ├── Filament/
|
||||||
│ │ ├── Resources/
|
│ │ ├── Resources/
|
||||||
|
│ │ │ ├── ActivityLogs/ # NEW: Audit trail (read-only)
|
||||||
|
│ │ │ │ ├── ActivityLogResource.php
|
||||||
|
│ │ │ │ └── Pages/ListActivityLogs.php
|
||||||
│ │ │ ├── Feedback/
|
│ │ │ ├── Feedback/
|
||||||
│ │ │ │ ├── FeedbackResource.php
|
│ │ │ │ ├── FeedbackResource.php
|
||||||
│ │ │ │ ├── Schemas/FeedbackForm.php
|
│ │ │ │ ├── Schemas/FeedbackForm.php
|
||||||
@@ -67,79 +98,101 @@ minicrm/
|
|||||||
│ │ │ │ ├── Pages/{Create,Edit,List}Feedback.php
|
│ │ │ │ ├── Pages/{Create,Edit,List}Feedback.php
|
||||||
│ │ │ │ └── RelationManagers/InteractionsRelationManager.php
|
│ │ │ │ └── RelationManagers/InteractionsRelationManager.php
|
||||||
│ │ │ ├── Products/
|
│ │ │ ├── Products/
|
||||||
|
│ │ │ │ └── RelationManagers/{Contracts,Handovers,ProductServices}RelationManager.php
|
||||||
│ │ │ ├── Customers/
|
│ │ │ ├── Customers/
|
||||||
│ │ │ │ └── RelationManagers/FeedbacksRelationManager.php
|
│ │ │ │ └── RelationManagers/FeedbacksRelationManager.php
|
||||||
|
│ │ │ ├── Contracts/
|
||||||
│ │ │ ├── FeedbackChannels/
|
│ │ │ ├── FeedbackChannels/
|
||||||
│ │ │ ├── FeedbackTags/
|
│ │ │ ├── FeedbackTags/
|
||||||
│ │ │ ├── Contracts/
|
|
||||||
│ │ │ ├── Users/
|
│ │ │ ├── Users/
|
||||||
│ │ │ └── Roles/
|
│ │ │ └── Roles/
|
||||||
│ │ └── Widgets/
|
│ │ └── Widgets/
|
||||||
│ │ ├── ResolvedTicketsWidget.php # Dashboard: resolved tickets table
|
│ │ ├── ResolvedTicketsWidget.php # Dashboard: resolved tickets table
|
||||||
│ │ ├── AvgProcessingTimeWidget.php # Dashboard: stats overview
|
│ │ ├── AvgProcessingTimeWidget.php # Dashboard: stats overview
|
||||||
│ │ └── HandlerPerformanceWidget.php # Dashboard: bar chart per handler
|
│ │ └── HandlerPerformanceWidget.php # Dashboard: bar chart per handler
|
||||||
|
│ ├── Console/Commands/
|
||||||
|
│ │ ├── ImportCskh.php # app:import-cskh
|
||||||
|
│ │ └── ExportBackup.php # app:export-backup
|
||||||
│ ├── Enums/
|
│ ├── Enums/
|
||||||
│ │ ├── ContractType.php
|
│ │ ├── ContractType.php
|
||||||
│ │ ├── ContractStatus.php
|
│ │ ├── ContractStatus.php
|
||||||
│ │ ├── HandoverStatus.php
|
│ │ ├── HandoverStatus.php
|
||||||
│ │ └── ServiceType.php
|
│ │ └── ServiceType.php
|
||||||
|
│ ├── Http/
|
||||||
|
│ │ ├── Controllers/Controller.php
|
||||||
|
│ │ └── Middleware/HandleUploadErrors.php
|
||||||
│ ├── Models/
|
│ ├── Models/
|
||||||
│ │ ├── User.php # HasRoles (Spatie), isAdmin(), isManager()
|
│ │ ├── ActivityLog.php # NEW: Audit trail model
|
||||||
│ │ ├── Department.php # NEW: name, manager_id → User
|
│ │ ├── Contract.php
|
||||||
│ │ ├── Product.php
|
|
||||||
│ │ ├── Customer.php
|
│ │ ├── Customer.php
|
||||||
│ │ ├── CustomerProduct.php
|
│ │ ├── CustomerProduct.php
|
||||||
│ │ ├── Feedback.php # added: current_department_id, is_escalated, transferLogs
|
│ │ ├── Department.php
|
||||||
|
│ │ ├── Feedback.php
|
||||||
|
│ │ ├── FeedbackAttachment.php
|
||||||
│ │ ├── FeedbackChannel.php
|
│ │ ├── FeedbackChannel.php
|
||||||
│ │ ├── FeedbackTag.php
|
|
||||||
│ │ ├── FeedbackInteraction.php
|
│ │ ├── FeedbackInteraction.php
|
||||||
│ │ ├── FeedbackAttachment.php # added: uuid, disk, collection
|
│ │ ├── FeedbackTag.php
|
||||||
│ │ └── TicketTransferLog.php # NEW: department-to-department transfer history
|
│ │ ├── Handover.php
|
||||||
|
│ │ ├── Product.php
|
||||||
|
│ │ ├── ProductService.php
|
||||||
|
│ │ ├── TicketTransferLog.php
|
||||||
|
│ │ └── User.php # HasRoles (Spatie), isAdmin(), isManager()
|
||||||
│ ├── Policies/
|
│ ├── Policies/
|
||||||
│ │ ├── FeedbackPolicy.php # Spatie hasRole() checks
|
│ │ ├── ActivityLogPolicy.php # NEW
|
||||||
│ │ ├── ProductPolicy.php
|
│ │ ├── ContractPolicy.php
|
||||||
│ │ ├── CustomerPolicy.php
|
│ │ ├── CustomerPolicy.php
|
||||||
│ │ └── FeedbackChannelPolicy.php
|
│ │ ├── FeedbackChannelPolicy.php
|
||||||
│ ├── Services/
|
│ │ ├── FeedbackPolicy.php
|
||||||
│ │ ├── FileService.php # Upload, validation, temp URLs, collections
|
│ │ ├── ProductPolicy.php
|
||||||
│ │ ├── TransferService.php # Cross-dept transfer + notification
|
│ │ ├── RolePolicy.php
|
||||||
│ │ └── ClosingService.php # Role-gated closing + evidence check
|
│ │ └── UserPolicy.php
|
||||||
│ ├── Rules/
|
|
||||||
│ │ └── RequireProofOfResolution.php # Custom validation: proof_of_resolution required for close
|
|
||||||
│ ├── Notifications/
|
│ ├── Notifications/
|
||||||
│ │ ├── TicketTransferred.php # DB + Mail for department transfer
|
│ │ ├── TicketTransferred.php
|
||||||
│ │ └── TicketClosed.php # DB + Mail for ticket closure
|
│ │ └── TicketClosed.php
|
||||||
│ └── Providers/Filament/
|
│ ├── Providers/Filament/
|
||||||
│ └── AdminPanelProvider.php
|
│ │ ├── AdminPanelProvider.php # Panel đầy đủ (/admin)
|
||||||
├── lang/ # i18n translation files
|
│ │ └── SupportPanelProvider.php # Panel rút gọn (/support)
|
||||||
│ ├── en/
|
│ ├── Rules/
|
||||||
│ │ ├── app.php # General UI labels, navigation, status, widgets
|
│ │ └── RequireProofOfResolution.php
|
||||||
│ │ ├── feedback.php # Feedback domain: services, notifications, interactions
|
│ └── Services/
|
||||||
│ │ └── enums.php # Enum labels
|
│ ├── ActivityLogger.php # NEW: static log() cho audit trail
|
||||||
│ └── vi/
|
│ ├── ClosingService.php
|
||||||
│ ├── app.php
|
│ ├── FileService.php
|
||||||
│ ├── feedback.php
|
│ └── TransferService.php
|
||||||
│ └── enums.php
|
├── lang/ # i18n translation files
|
||||||
|
│ ├── en/{app.php, feedback.php, enums.php}
|
||||||
|
│ └── vi/{app.php, feedback.php, enums.php}
|
||||||
├── resources/views/filament/resources/feedback/
|
├── resources/views/filament/resources/feedback/
|
||||||
│ ├── similar-cases.blade.php
|
│ ├── similar-cases.blade.php
|
||||||
│ └── attachment-links.blade.php # Modal listing attachments with signed download URLs
|
│ └── attachment-links.blade.php
|
||||||
|
├── webappUI/aftersales_crm_core/ # Design tokens & system
|
||||||
|
│ └── DESIGN.md
|
||||||
├── database/
|
├── database/
|
||||||
│ ├── migrations/
|
│ ├── migrations/ # 25 migrations
|
||||||
│ └── seeders/AfterSalesSeeder.php # includes departments, logs, attachments
|
│ └── seeders/{AfterSalesSeeder,PermissionSeeder}.php
|
||||||
├── tests/
|
├── tests/Feature/
|
||||||
│ ├── Feature/
|
│ ├── ClosingPermissionTest.php
|
||||||
│ │ ├── ClosingPermissionTest.php # 4 tests: staff forbid, no-evidence, with-evidence, cross-dept
|
│ └── TransferFlowTest.php
|
||||||
│ │ └── TransferFlowTest.php # 2 tests: successful + missing reason
|
├── luutru/ # Archive các file đã lỗi thời
|
||||||
│ └── Pest.php
|
├── UI_DESIGN_BRIEF.md # Thiết kế UI chi tiết
|
||||||
├── storage/app/private/feedback-attachments/ # Private disk upload directory
|
└── TASKS_ROADMAP.md
|
||||||
├── composer.json
|
|
||||||
├── PROGRESS.md
|
|
||||||
└── AGENTS.md
|
|
||||||
```
|
```
|
||||||
|
|
||||||
## Database Schema
|
## Database Schema
|
||||||
|
|
||||||
### departments (NEW)
|
### activity_logs (NEW)
|
||||||
|
| Column | Type | Notes |
|
||||||
|
|--------|------|-------|
|
||||||
|
| id | bigint PK | |
|
||||||
|
| user_id | fk -> users nullable | Who performed action |
|
||||||
|
| action | string | import, export, transfer, close, create, update, delete |
|
||||||
|
| description | text | Human-readable description |
|
||||||
|
| subject_type | string nullable | Related model class |
|
||||||
|
| subject_id | bigint nullable | Related model ID |
|
||||||
|
| metadata | json nullable | Extra data |
|
||||||
|
| created_at | timestamp | |
|
||||||
|
|
||||||
|
### departments
|
||||||
| Column | Type | Notes |
|
| Column | Type | Notes |
|
||||||
|--------|------|-------|
|
|--------|------|-------|
|
||||||
| id | bigint PK | |
|
| id | bigint PK | |
|
||||||
@@ -147,7 +200,7 @@ minicrm/
|
|||||||
| manager_id | fk -> users nullable | Department head |
|
| manager_id | fk -> users nullable | Department head |
|
||||||
| created_at, updated_at | timestamp | |
|
| created_at, updated_at | timestamp | |
|
||||||
|
|
||||||
### ticket_transfer_logs (NEW)
|
### ticket_transfer_logs
|
||||||
| Column | Type | Notes |
|
| Column | Type | Notes |
|
||||||
|--------|------|-------|
|
|--------|------|-------|
|
||||||
| id | bigint PK | |
|
| id | bigint PK | |
|
||||||
@@ -164,10 +217,11 @@ minicrm/
|
|||||||
| id | bigint PK | |
|
| id | bigint PK | |
|
||||||
| customer_id | fk -> customers | cascadeOnDelete |
|
| customer_id | fk -> customers | cascadeOnDelete |
|
||||||
| customer_product_id | fk -> customer_product nullable | null = general feedback |
|
| customer_product_id | fk -> customer_product nullable | null = general feedback |
|
||||||
|
| contract_id | fk -> contracts nullable | **(NEW)** Snapshot contract |
|
||||||
| feedback_channel_id | fk -> feedback_channels | |
|
| feedback_channel_id | fk -> feedback_channels | |
|
||||||
| assigned_to | fk -> users nullable | Current handler |
|
| assigned_to | fk -> users nullable | Current handler |
|
||||||
| current_department_id | fk -> departments nullable | **(NEW)** Current department |
|
| current_department_id | fk -> departments nullable | Current department |
|
||||||
| is_escalated | boolean default false | **(NEW)** |
|
| is_escalated | boolean default false | |
|
||||||
| title | string | |
|
| title | string | |
|
||||||
| content | text | |
|
| content | text | |
|
||||||
| status | string | pending, processing, resolved, closed |
|
| status | string | pending, processing, resolved, closed |
|
||||||
@@ -177,20 +231,20 @@ minicrm/
|
|||||||
| Column | Type | Notes |
|
| Column | Type | Notes |
|
||||||
|--------|------|-------|
|
|--------|------|-------|
|
||||||
| id | bigint PK | |
|
| id | bigint PK | |
|
||||||
| uuid | string nullable unique | **(NEW)** |
|
| uuid | string nullable unique | |
|
||||||
| feedback_id | fk -> feedback | |
|
| feedback_id | fk -> feedback | |
|
||||||
| feedback_interaction_id | fk -> feedback_interactions nullable | |
|
| feedback_interaction_id | fk -> feedback_interactions nullable | |
|
||||||
| user_id | fk -> users | |
|
| user_id | fk -> users | |
|
||||||
| name | string | |
|
| name | string | |
|
||||||
| path | string | |
|
| path | string | |
|
||||||
| disk | string default 'local' | **(NEW)** |
|
| disk | string default 'local' | |
|
||||||
| collection | string default 'general' | **(NEW)** proof_of_resolution, customer_evidence, general |
|
| collection | string default 'general' | proof_of_resolution, customer_evidence, general |
|
||||||
| mime_type | string nullable | |
|
| mime_type | string nullable | |
|
||||||
| size | bigint nullable | |
|
| size | bigint nullable | |
|
||||||
| created_at, updated_at | timestamp | |
|
| created_at, updated_at | timestamp | |
|
||||||
|
|
||||||
### Other tables
|
### Other tables
|
||||||
feedback_interactions, feedback_tags, feedback_feedback_tag, products, customers, customer_product, feedback_channels, users (Spatie permission tables: roles, permissions, model_has_roles, etc.)
|
feedback_interactions, feedback_tags, feedback_feedback_tag, products, customers, customer_product, feedback_channels, users, contracts, handovers, product_services, roles, permissions, model_has_roles (Spatie), notifications, sessions, cache
|
||||||
|
|
||||||
## Workflow
|
## Workflow
|
||||||
|
|
||||||
@@ -205,14 +259,24 @@ feedback_interactions, feedback_tags, feedback_feedback_tag, products, customers
|
|||||||
|
|
||||||
## Key Features
|
## Key Features
|
||||||
|
|
||||||
### Transfer Department (NEW - SOP compliant)
|
### Dual Panel System (NEW)
|
||||||
- Action on EditFeedback page: TransferDepartment
|
- **Admin Panel** (`/admin`): Đầy đủ modules, User/Role management
|
||||||
|
- **Support Panel** (`/support`): Rút gọn, tập trung Feedback. Dùng cho đội support không cần quản lý hệ thống
|
||||||
|
|
||||||
|
### ActivityLog — Audit Trail (NEW)
|
||||||
|
- Tự động ghi log các hành động: import, export, transfer, close, create, update, delete
|
||||||
|
- `ActivityLogger::log(action, description, subject, metadata, userId)`
|
||||||
|
- Xem tại `/admin/activity-logs` hoặc `/support/activity-logs`
|
||||||
|
- Admin-only access (ActivityLogPolicy)
|
||||||
|
|
||||||
|
### Transfer Department (SOP compliant)
|
||||||
|
- Action trên EditFeedback: TransferDepartment
|
||||||
- Modal: select target department + reason (required) + optional attachments
|
- Modal: select target department + reason (required) + optional attachments
|
||||||
- Calls TransferService: creates TicketTransferLog, updates current_department_id, resets handler to dept manager
|
- Calls TransferService: creates TicketTransferLog, updates current_department_id, resets handler to dept manager
|
||||||
- Sends TicketTransferred notification to target department manager
|
- Sends TicketTransferred notification to target department manager
|
||||||
|
|
||||||
### Close Ticket (NEW - SOP compliant)
|
### Close Ticket (SOP compliant)
|
||||||
- Action on EditFeedback page: CloseTicket
|
- Action trên EditFeedback: CloseTicket
|
||||||
- Visible only to admin/manager, only when status != 'closed'
|
- Visible only to admin/manager, only when status != 'closed'
|
||||||
- Requires confirmation
|
- Requires confirmation
|
||||||
- Staff (403) if tries to close
|
- Staff (403) if tries to close
|
||||||
@@ -224,14 +288,14 @@ feedback_interactions, feedback_tags, feedback_feedback_tag, products, customers
|
|||||||
- Manager/Admin: all options including "closed"
|
- Manager/Admin: all options including "closed"
|
||||||
- When "closed" selected: calls ClosingService which validates evidence
|
- When "closed" selected: calls ClosingService which validates evidence
|
||||||
|
|
||||||
### File Management (Updated - SOP compliant)
|
### File Management (SOP compliant)
|
||||||
- Files stored on private `local` disk (storage/app/private/feedback-attachments/)
|
- Files stored on `public` disk (`storage/app/public/feedback-attachments/`)
|
||||||
- Collection system: `proof_of_resolution`, `customer_evidence`, `general`
|
- Collection system: `proof_of_resolution`, `customer_evidence`, `general`
|
||||||
- FileService validates: jpg, png, pdf, mp4, mov, max 20MB
|
- FileService validates: jpg, png, pdf, mp4, mov, max 20MB
|
||||||
- Temporary signed URLs for private file access
|
- Temporary signed URLs for private file access
|
||||||
- FileService::hasCollection() checks existence of required evidence
|
- FileService::hasCollection() checks existence of required evidence
|
||||||
|
|
||||||
### Dashboard Widgets (NEW)
|
### Dashboard Widgets
|
||||||
- ResolvedTicketsWidget: table of resolved tickets awaiting closure (manager: filtered to own department)
|
- ResolvedTicketsWidget: table of resolved tickets awaiting closure (manager: filtered to own department)
|
||||||
- AvgProcessingTimeWidget: 4 stats (resolved count, avg time, pending count, escalated count)
|
- AvgProcessingTimeWidget: 4 stats (resolved count, avg time, pending count, escalated count)
|
||||||
- HandlerPerformanceWidget: bar chart of avg processing time per handler
|
- HandlerPerformanceWidget: bar chart of avg processing time per handler
|
||||||
@@ -303,19 +367,35 @@ public function getHeading(): string | Htmlable | null
|
|||||||
|
|
||||||
## Navigation Structure
|
## Navigation Structure
|
||||||
|
|
||||||
|
### Admin Panel (`/admin`)
|
||||||
```
|
```
|
||||||
Dashboard
|
Dashboard (AccountWidget + 3 custom widgets)
|
||||||
├── Customer Care / Chăm sóc khách hàng (group)
|
├── Customer Care / Chăm sóc khách hàng (group)
|
||||||
│ └── Feedbacks / Phản ánh (chat-bubble-left-right, sort=1)
|
│ └── Feedbacks / Phản ánh (chat-bubble-left-right, sort=1)
|
||||||
└── Management / Quản lý (group)
|
└── Management / Quản lý (group)
|
||||||
├── Products / Sản phẩm (building-storefront, sort=1)
|
├── Products / Sản phẩm (building-storefront, sort=1)
|
||||||
├── Customers / Khách hàng (user-group, sort=2)
|
├── Customers / Khách hàng (user-group, sort=2)
|
||||||
├── Channels / Kênh phản ánh (inbox-stack, sort=3)
|
├── Channels / Kênh phản ánh (inbox-stack, sort=3)
|
||||||
|
├── Contracts / Hợp đồng (document-text, sort=4)
|
||||||
├── Tags / Thẻ (tag, sort=default)
|
├── Tags / Thẻ (tag, sort=default)
|
||||||
|
├── Activity Logs (clock, sort=7) — admin only
|
||||||
├── Users / Người dùng (users, sort=5) — admin only
|
├── Users / Người dùng (users, sort=5) — admin only
|
||||||
└── Roles / Vai trò (shield-check, sort=6) — admin only
|
└── Roles / Vai trò (shield-check, sort=6) — admin only
|
||||||
```
|
```
|
||||||
|
|
||||||
|
### Support Panel (`/support`)
|
||||||
|
```
|
||||||
|
Dashboard (3 custom widgets)
|
||||||
|
├── Customer Care / Chăm sóc khách hàng (group)
|
||||||
|
│ └── Feedbacks
|
||||||
|
└── Management / Quản lý (group)
|
||||||
|
├── Products
|
||||||
|
├── Customers
|
||||||
|
├── Channels
|
||||||
|
├── Tags
|
||||||
|
└── Activity Logs
|
||||||
|
```
|
||||||
|
|
||||||
Dashboard widgets: ResolvedTicketsWidget, AvgProcessingTimeWidget, HandlerPerformanceWidget (admin/manager only)
|
Dashboard widgets: ResolvedTicketsWidget, AvgProcessingTimeWidget, HandlerPerformanceWidget (admin/manager only)
|
||||||
|
|
||||||
## RBAC (Role-Based Access Control) - Spatie
|
## RBAC (Role-Based Access Control) - Spatie
|
||||||
@@ -400,6 +480,9 @@ use Filament\Schemas\Schema;
|
|||||||
```php
|
```php
|
||||||
use Illuminate\Support\Facades\App;
|
use Illuminate\Support\Facades\App;
|
||||||
$service = App::make(FileService::class);
|
$service = App::make(FileService::class);
|
||||||
|
|
||||||
|
// ActivityLogger (static)
|
||||||
|
\App\Services\ActivityLogger::log('transfer', '...', $feedback, ['dept' => 'CSKH']);
|
||||||
```
|
```
|
||||||
|
|
||||||
### Spatie role check
|
### Spatie role check
|
||||||
@@ -432,6 +515,7 @@ $user->assignRole('manager');
|
|||||||
19. **FileUpload in Actions**: When handling file uploads in Action callbacks (e.g., transfer attachments), file paths are strings (not UploadedFile). Create records directly instead of using FileService::upload().
|
19. **FileUpload in Actions**: When handling file uploads in Action callbacks (e.g., transfer attachments), file paths are strings (not UploadedFile). Create records directly instead of using FileService::upload().
|
||||||
20. **Tailwind CSS in Filament**: Custom blade views render inside Filament layout which does NOT include full Tailwind CSS build. Must add needed utilities to `public/css/filament-custom.css` with `!important`.
|
20. **Tailwind CSS in Filament**: Custom blade views render inside Filament layout which does NOT include full Tailwind CSS build. Must add needed utilities to `public/css/filament-custom.css` with `!important`.
|
||||||
21. **SVG icon sizing**: Use inline `width`/`height` attributes on SVG icons (e.g., `<svg width="14" height="14">`) instead of Tailwind classes (`w-3.5 h-3.5`) — prevents Filament CSS from overriding sizes.
|
21. **SVG icon sizing**: Use inline `width`/`height` attributes on SVG icons (e.g., `<svg width="14" height="14">`) instead of Tailwind classes (`w-3.5 h-3.5`) — prevents Filament CSS from overriding sizes.
|
||||||
|
22. **Dual Panel**: Support panel at `/support` is separate from Admin panel at `/admin`. Both share the same models/services/policies/database.
|
||||||
|
|
||||||
## File Upload Architecture
|
## File Upload Architecture
|
||||||
|
|
||||||
@@ -501,3 +585,4 @@ protected function afterSave(): void
|
|||||||
// For SVG icons, use inline attributes:
|
// For SVG icons, use inline attributes:
|
||||||
<svg width="14" height="14" class="text-blue-600">...</svg>
|
<svg width="14" height="14" class="text-blue-600">...</svg>
|
||||||
// NOT: <svg class="w-3.5 h-3.5 text-blue-600">...</svg>
|
// NOT: <svg class="w-3.5 h-3.5 text-blue-600">...</svg>
|
||||||
|
```
|
||||||
|
|||||||
@@ -10,8 +10,8 @@
|
|||||||
| Mục | Giá trị |
|
| Mục | Giá trị |
|
||||||
|------|--------|
|
|------|--------|
|
||||||
| Framework | Laravel 13.6.0 |
|
| Framework | Laravel 13.6.0 |
|
||||||
| Admin Panel | Filament 5.6.1 |
|
| Panels | Filament 5.6.1 — 2 panels: `/admin` (đầy đủ) + `/support` (rút gọn) |
|
||||||
| Auth/RBAC | Spatie laravel-permission (3 roles: admin, manager, staff) |
|
| Auth/RBAC | Spatie laravel-permission (3 roles: admin, manager, staff, 29 permissions) |
|
||||||
| Database | SQLite (`database/database.sqlite`) |
|
| Database | SQLite (`database/database.sqlite`) |
|
||||||
| Testing | Pest PHP |
|
| Testing | Pest PHP |
|
||||||
| PHP | 8.3 |
|
| PHP | 8.3 |
|
||||||
@@ -32,6 +32,9 @@
|
|||||||
app/
|
app/
|
||||||
├── Filament/
|
├── Filament/
|
||||||
│ ├── Resources/
|
│ ├── Resources/
|
||||||
|
│ │ ├── ActivityLogs/ # NEW: Audit trail (read-only)
|
||||||
|
│ │ │ ├── ActivityLogResource.php
|
||||||
|
│ │ │ └── Pages/ListActivityLogs.php
|
||||||
│ │ ├── Customers/
|
│ │ ├── Customers/
|
||||||
│ │ │ ├── CustomerResource.php
|
│ │ │ ├── CustomerResource.php
|
||||||
│ │ │ ├── Pages/
|
│ │ │ ├── Pages/
|
||||||
@@ -56,7 +59,7 @@ app/
|
|||||||
│ │ │ │ └── FeedbackForm.php
|
│ │ │ │ └── FeedbackForm.php
|
||||||
│ │ │ └── Tables/
|
│ │ │ └── Tables/
|
||||||
│ │ │ └── FeedbackTable.php
|
│ │ │ └── FeedbackTable.php
|
||||||
│ │ ├── Contracts/ # NEW
|
│ │ ├── Contracts/
|
||||||
│ │ │ ├── ContractResource.php
|
│ │ │ ├── ContractResource.php
|
||||||
│ │ │ ├── Pages/
|
│ │ │ ├── Pages/
|
||||||
│ │ │ │ ├── CreateContract.php
|
│ │ │ │ ├── CreateContract.php
|
||||||
@@ -86,32 +89,32 @@ app/
|
|||||||
│ │ │ │ └── FeedbackTagForm.php
|
│ │ │ │ └── FeedbackTagForm.php
|
||||||
│ │ │ └── Tables/
|
│ │ │ └── Tables/
|
||||||
│ │ │ └── FeedbackTagsTable.php
|
│ │ │ └── FeedbackTagsTable.php
|
||||||
│ │ └── Products/
|
│ │ ├── Products/
|
||||||
│ │ ├── ProductResource.php
|
│ │ │ ├── ProductResource.php
|
||||||
│ │ ├── Pages/
|
│ │ │ ├── Pages/
|
||||||
│ │ │ ├── CreateProduct.php
|
│ │ │ │ ├── CreateProduct.php
|
||||||
│ │ │ ├── EditProduct.php
|
│ │ │ │ ├── EditProduct.php
|
||||||
│ │ │ └── ListProducts.php
|
│ │ │ │ └── ListProducts.php
|
||||||
│ │ ├── RelationManagers/
|
│ │ │ ├── RelationManagers/
|
||||||
│ │ │ ├── ContractsRelationManager.php # NEW
|
│ │ │ │ ├── ContractsRelationManager.php
|
||||||
│ │ │ ├── HandoversRelationManager.php # NEW
|
│ │ │ │ ├── HandoversRelationManager.php
|
||||||
│ │ │ └── ProductServicesRelationManager.php # NEW
|
│ │ │ │ └── ProductServicesRelationManager.php
|
||||||
│ │ ├── Schemas/
|
│ │ │ ├── Schemas/
|
||||||
│ │ │ └── ProductForm.php
|
│ │ │ │ └── ProductForm.php
|
||||||
│ │ └── Tables/
|
│ │ │ └── Tables/
|
||||||
│ │ └── ProductsTable.php
|
│ │ │ └── ProductsTable.php
|
||||||
│ │ └── Users/ # NEW
|
│ │ ├── Users/ # Admin-only user management
|
||||||
│ │ ├── UserResource.php # Admin-only user management
|
│ │ │ ├── UserResource.php
|
||||||
│ │ ├── Pages/
|
│ │ │ ├── Pages/
|
||||||
│ │ │ ├── CreateUser.php
|
│ │ │ │ ├── CreateUser.php
|
||||||
│ │ │ ├── EditUser.php
|
│ │ │ │ ├── EditUser.php
|
||||||
│ │ │ └── ListUsers.php # Tabs: All/Admin/Manager/Staff
|
│ │ │ │ └── ListUsers.php # Tabs: All/Admin/Manager/Staff
|
||||||
│ │ ├── Schemas/
|
│ │ │ ├── Schemas/
|
||||||
│ │ │ └── UserForm.php # name, email, password, role (dynamic)
|
│ │ │ │ └── UserForm.php # name, email, password, role (dynamic)
|
||||||
│ │ └── Tables/
|
│ │ │ └── Tables/
|
||||||
│ │ └── UsersTable.php # name, email, role badge
|
│ │ │ └── UsersTable.php # name, email, role badge
|
||||||
│ │ └── Roles/ # NEW
|
│ │ └── Roles/ # Admin-only role management
|
||||||
│ │ ├── RoleResource.php # Admin-only role management
|
│ │ ├── RoleResource.php
|
||||||
│ │ ├── Pages/
|
│ │ ├── Pages/
|
||||||
│ │ │ ├── CreateRole.php
|
│ │ │ ├── CreateRole.php
|
||||||
│ │ │ ├── EditRole.php
|
│ │ │ ├── EditRole.php
|
||||||
@@ -124,20 +127,23 @@ app/
|
|||||||
│ ├── AvgProcessingTimeWidget.php # 4 stats: resolved count, avg time, pending, escalated
|
│ ├── AvgProcessingTimeWidget.php # 4 stats: resolved count, avg time, pending, escalated
|
||||||
│ ├── HandlerPerformanceWidget.php # bar chart: avg time per handler
|
│ ├── HandlerPerformanceWidget.php # bar chart: avg time per handler
|
||||||
│ └── ResolvedTicketsWidget.php # table: resolved tickets awaiting closure
|
│ └── ResolvedTicketsWidget.php # table: resolved tickets awaiting closure
|
||||||
├── Console/ # NEW
|
├── Console/
|
||||||
│ └── Commands/
|
│ └── Commands/
|
||||||
│ ├── ImportCskh.php # app:import-cskh command
|
│ ├── ImportCskh.php # app:import-cskh command
|
||||||
│ └── ExportBackup.php # app:export-backup command (JSON backup)
|
│ └── ExportBackup.php # app:export-backup command (JSON backup)
|
||||||
├── Enums/ # NEW
|
├── Enums/
|
||||||
│ ├── ContractType.php # SALE/LEASE/BCC
|
│ ├── ContractType.php # SALE/LEASE/BCC
|
||||||
│ ├── ContractStatus.php # ACTIVE/EXPIRED/LIQUIDATED
|
│ ├── ContractStatus.php # ACTIVE/EXPIRED/LIQUIDATED
|
||||||
│ ├── HandoverStatus.php # NOT_READY/READY/SCHEDULED/HANDED_OVER
|
│ ├── HandoverStatus.php # NOT_READY/READY/SCHEDULED/HANDED_OVER
|
||||||
│ └── ServiceType.php # MANAGEMENT_FEE/GRATITUDE/SELF_BUSINESS
|
│ └── ServiceType.php # MANAGEMENT_FEE/GRATITUDE/SELF_BUSINESS
|
||||||
├── Http/
|
├── Http/
|
||||||
│ └── Controllers/
|
│ ├── Controllers/
|
||||||
│ └── Controller.php
|
│ │ └── Controller.php
|
||||||
|
│ └── Middleware/
|
||||||
|
│ └── HandleUploadErrors.php
|
||||||
├── Models/
|
├── Models/
|
||||||
│ ├── Contract.php # NEW
|
│ ├── ActivityLog.php # NEW: Audit trail (user_id, action, description, metadata)
|
||||||
|
│ ├── Contract.php
|
||||||
│ ├── Customer.php
|
│ ├── Customer.php
|
||||||
│ ├── CustomerProduct.php # pivot model (customer_product table)
|
│ ├── CustomerProduct.php # pivot model (customer_product table)
|
||||||
│ ├── Department.php
|
│ ├── Department.php
|
||||||
@@ -146,27 +152,32 @@ app/
|
|||||||
│ ├── FeedbackChannel.php
|
│ ├── FeedbackChannel.php
|
||||||
│ ├── FeedbackInteraction.php
|
│ ├── FeedbackInteraction.php
|
||||||
│ ├── FeedbackTag.php
|
│ ├── FeedbackTag.php
|
||||||
│ ├── Handover.php # NEW
|
│ ├── Handover.php
|
||||||
│ ├── Product.php
|
│ ├── Product.php
|
||||||
│ ├── ProductService.php # NEW
|
│ ├── ProductService.php
|
||||||
│ ├── TicketTransferLog.php
|
│ ├── TicketTransferLog.php
|
||||||
│ └── User.php
|
│ └── User.php
|
||||||
├── Notifications/
|
├── Notifications/
|
||||||
│ ├── TicketClosed.php
|
│ ├── TicketClosed.php
|
||||||
│ └── TicketTransferred.php
|
│ └── TicketTransferred.php
|
||||||
├── Policies/
|
├── Policies/
|
||||||
│ ├── ContractPolicy.php # NEW
|
│ ├── ActivityLogPolicy.php # NEW: Admin-only
|
||||||
|
│ ├── ContractPolicy.php
|
||||||
│ ├── CustomerPolicy.php
|
│ ├── CustomerPolicy.php
|
||||||
│ ├── FeedbackChannelPolicy.php
|
│ ├── FeedbackChannelPolicy.php
|
||||||
│ ├── FeedbackPolicy.php
|
│ ├── FeedbackPolicy.php
|
||||||
│ └── ProductPolicy.php
|
│ ├── ProductPolicy.php
|
||||||
|
│ ├── RolePolicy.php
|
||||||
|
│ └── UserPolicy.php
|
||||||
├── Providers/
|
├── Providers/
|
||||||
│ ├── AppServiceProvider.php
|
│ ├── AppServiceProvider.php
|
||||||
│ └── Filament/
|
│ └── Filament/
|
||||||
│ └── AdminPanelProvider.php
|
│ ├── AdminPanelProvider.php # Panel đầy đủ (/admin)
|
||||||
|
│ └── SupportPanelProvider.php # Panel rút gọn (/support)
|
||||||
├── Rules/
|
├── Rules/
|
||||||
│ └── RequireProofOfResolution.php
|
│ └── RequireProofOfResolution.php
|
||||||
└── Services/
|
└── Services/
|
||||||
|
├── ActivityLogger.php # NEW: static log() cho audit trail
|
||||||
├── ClosingService.php
|
├── ClosingService.php
|
||||||
├── FileService.php
|
├── FileService.php
|
||||||
└── TransferService.php
|
└── TransferService.php
|
||||||
@@ -174,8 +185,37 @@ app/
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
## 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
|
## 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`)
|
### Product (`products`)
|
||||||
| Cột | Kiểu |
|
| Cột | Kiểu |
|
||||||
|-----|------|
|
|-----|------|
|
||||||
@@ -186,7 +226,7 @@ app/
|
|||||||
|
|
||||||
Relationships: `customers()` BelongsToMany (pivot: customer_product, withPivot: purchase_date), `contracts()` HasMany, `handovers()` HasMany, `productServices()` HasMany
|
Relationships: `customers()` BelongsToMany (pivot: customer_product, withPivot: purchase_date), `contracts()` HasMany, `handovers()` HasMany, `productServices()` HasMany
|
||||||
|
|
||||||
### ProductService (`product_services`) — NEW
|
### ProductService (`product_services`)
|
||||||
| Cột | Kiểu |
|
| Cột | Kiểu |
|
||||||
|-----|------|
|
|-----|------|
|
||||||
| id | bigint PK |
|
| id | bigint PK |
|
||||||
@@ -198,7 +238,7 @@ Relationships: `customers()` BelongsToMany (pivot: customer_product, withPivot:
|
|||||||
|
|
||||||
Relationships: `product()` BelongsTo
|
Relationships: `product()` BelongsTo
|
||||||
|
|
||||||
### Handover (`handovers`) — NEW
|
### Handover (`handovers`)
|
||||||
| Cột | Kiểu |
|
| Cột | Kiểu |
|
||||||
|-----|------|
|
|-----|------|
|
||||||
| id | bigint PK |
|
| id | bigint PK |
|
||||||
@@ -210,7 +250,7 @@ Relationships: `product()` BelongsTo
|
|||||||
|
|
||||||
Relationships: `product()` BelongsTo, `customer()` BelongsTo
|
Relationships: `product()` BelongsTo, `customer()` BelongsTo
|
||||||
|
|
||||||
### Contract (`contracts`) — NEW
|
### Contract (`contracts`)
|
||||||
| Cột | Kiểu |
|
| Cột | Kiểu |
|
||||||
|-----|------|
|
|-----|------|
|
||||||
| id | bigint PK |
|
| id | bigint PK |
|
||||||
@@ -266,7 +306,7 @@ Relationships: `manager()` BelongsTo User, `feedbacks()` HasMany, `transferLogsF
|
|||||||
| password | string |
|
| password | string |
|
||||||
| role | string (admin/manager/staff) |
|
| role | string (admin/manager/staff) |
|
||||||
|
|
||||||
Uses Spatie `HasRoles`. Relationships: `assignedFeedbacks()` HasMany Feedback.
|
Uses Spatie `HasRoles`. Relationships: `assignedFeedbacks()` HasMany Feedback, `activityLogs()` HasMany.
|
||||||
|
|
||||||
### Feedback (`feedback`)
|
### Feedback (`feedback`)
|
||||||
| Cột | Kiểu |
|
| Cột | Kiểu |
|
||||||
@@ -323,6 +363,7 @@ Relationships: `feedback()`, `interaction()`, `user()`. Scopes: `byCollection()`
|
|||||||
| name | string |
|
| name | string |
|
||||||
| slug | string unique |
|
| slug | string unique |
|
||||||
| icon | string nullable |
|
| icon | string nullable |
|
||||||
|
| color | string nullable |
|
||||||
| is_active | boolean default true |
|
| is_active | boolean default true |
|
||||||
|
|
||||||
Relationships: `feedbacks()` HasMany
|
Relationships: `feedbacks()` HasMany
|
||||||
@@ -353,27 +394,31 @@ Relationships: `feedback()`, `fromDepartment()`, `toDepartment()`, `sender()`
|
|||||||
|
|
||||||
## Services — Tóm tắt
|
## 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`)
|
### 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.
|
- `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).
|
- 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`)
|
### 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.
|
- `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).
|
- `resolve(Feedback, User)`: Đánh dấu resolved (không role gate).
|
||||||
|
|
||||||
### FileService (`app/Services/FileService.php`)
|
|
||||||
- `upload()`: Validate (jpg/png/pdf/mp4/mov, ≤20MB), store vào private local disk (`feedback-attachments/`), tạo FeedbackAttachment record.
|
|
||||||
- `getTemporaryUrl()` / `getPreviewUrl()`: Signed URL cho private file.
|
|
||||||
- `getByCollection()` / `hasCollection()`: Query collection-based attachments.
|
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## Policies — Tóm tắt
|
## Policies — Tóm tắt
|
||||||
|
|
||||||
> **Note:** Policies now use Spatie `hasPermissionTo()` instead of `hasRole()` for granular permission control.
|
> **Note:** Policies use Spatie `hasPermissionTo()` instead of `hasRole()` for granular permission control.
|
||||||
|
|
||||||
| Policy | viewAny/view | create | update | delete | restore/forceDelete |
|
| Policy | viewAny/view | create | update | delete | restore/forceDelete |
|
||||||
|--------|-------------|--------|--------|--------|---------------------|
|
|--------|-------------|--------|--------|--------|---------------------|
|
||||||
|
| ActivityLogPolicy | admin only | — | — | — | — |
|
||||||
| ContractPolicy | view-contract | create-contract | update-contract | delete-contract | admin only |
|
| ContractPolicy | view-contract | create-contract | update-contract | delete-contract | admin only |
|
||||||
| FeedbackPolicy | view-feedback | create-feedback | update-feedback | delete-feedback | admin only |
|
| FeedbackPolicy | view-feedback | create-feedback | update-feedback | delete-feedback | admin only |
|
||||||
| ProductPolicy | view-product | create-product | update-product | delete-product | admin only |
|
| ProductPolicy | view-product | create-product | update-product | delete-product | admin only |
|
||||||
@@ -386,6 +431,7 @@ Relationships: `feedback()`, `fromDepartment()`, `toDepartment()`, `sender()`
|
|||||||
|
|
||||||
## Filament Navigation Structure
|
## Filament Navigation Structure
|
||||||
|
|
||||||
|
### Admin Panel (`/admin`)
|
||||||
```
|
```
|
||||||
Dashboard (AccountWidget + 3 custom widgets)
|
Dashboard (AccountWidget + 3 custom widgets)
|
||||||
├── Customer Care (group)
|
├── Customer Care (group)
|
||||||
@@ -394,8 +440,24 @@ Dashboard (AccountWidget + 3 custom widgets)
|
|||||||
├── Products (heroicon-o-building-storefront, sort=1)
|
├── Products (heroicon-o-building-storefront, sort=1)
|
||||||
├── Customers (heroicon-o-user-group, sort=2)
|
├── Customers (heroicon-o-user-group, sort=2)
|
||||||
├── Channels (heroicon-o-inbox-stack, sort=3)
|
├── Channels (heroicon-o-inbox-stack, sort=3)
|
||||||
├── Contracts (heroicon-o-document-text, sort=4) # NEW
|
├── Contracts (heroicon-o-document-text, sort=4)
|
||||||
└── Tags (heroicon-o-tag, default sort)
|
├── 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
|
||||||
```
|
```
|
||||||
|
|
||||||
---
|
---
|
||||||
@@ -407,7 +469,7 @@ Dashboard (AccountWidget + 3 custom widgets)
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## Database Migration files (23 files)
|
## Database Migration files (25 files)
|
||||||
|
|
||||||
| # | File | Tables |
|
| # | File | Tables |
|
||||||
|---|------|--------|
|
|---|------|--------|
|
||||||
@@ -430,17 +492,19 @@ Dashboard (AccountWidget + 3 custom widgets)
|
|||||||
| 17 | `2026_04_27_083000_add_collection_to_feedback_attachments_table` | (modify feedback_attachments) add uuid, disk, collection |
|
| 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 |
|
| 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 |
|
| 19 | `2026_04_27_150000_create_notifications_table` | notifications |
|
||||||
| 20 | `2026_05_01_000000_create_contracts_table` | **(NEW)** contracts |
|
| 20 | `2026_05_01_000000_create_contracts_table` | contracts |
|
||||||
| 21 | `2026_05_01_000001_add_contract_id_to_feedback_table` | **(NEW)** add contract_id FK to feedback |
|
| 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` | **(NEW)** handovers |
|
| 22 | `2026_05_01_000002_create_handovers_table` | handovers |
|
||||||
| 23 | `2026_05_01_000003_create_product_services_table` | **(NEW)** product_services |
|
| 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
|
## Seeder (AfterSalesSeeder + PermissionSeeder) — Data mẫu
|
||||||
|
|
||||||
- 3 users (admin, manager, staff) + Spatie roles
|
- 3 users (admin, manager, staff) + Spatie roles
|
||||||
- **29 granular permissions** (PermissionSeeder) — NEW
|
- **29 granular permissions** (PermissionSeeder)
|
||||||
- Feedback: view/create/update/delete, add-interaction, close-ticket, transfer-department
|
- Feedback: view/create/update/delete, add-interaction, close-ticket, transfer-department
|
||||||
- Products/Customers/Contracts/Channels/Tags: view/create/update/delete each
|
- Products/Customers/Contracts/Channels/Tags: view/create/update/delete each
|
||||||
- Dashboard: view-dashboard-widgets, export-data
|
- Dashboard: view-dashboard-widgets, export-data
|
||||||
@@ -459,12 +523,9 @@ Dashboard (AccountWidget + 3 custom widgets)
|
|||||||
- 1 ticket_transfer_log
|
- 1 ticket_transfer_log
|
||||||
- 3 feedback_attachments (2 proof_of_resolution, 1 customer_evidence)
|
- 3 feedback_attachments (2 proof_of_resolution, 1 customer_evidence)
|
||||||
|
|
||||||
---|---
|
|
||||||
| 22 | `2026_05_01_000003_create_product_services_table` | **(NEW)** product_services |
|
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## Data Import (`app:import-cskh`) — NEW
|
## Data Import (`app:import-cskh`)
|
||||||
|
|
||||||
Command: `php artisan app:import-cskh {file_path}`
|
Command: `php artisan app:import-cskh {file_path}`
|
||||||
- Sử dụng `spatie/simple-excel` với Lazy Collection (chống OOM)
|
- Sử dụng `spatie/simple-excel` với Lazy Collection (chống OOM)
|
||||||
@@ -493,12 +554,13 @@ Tổng: 8 tests, 21 assertions.
|
|||||||
|
|
||||||
1. **Form components:** `Filament\Forms\Components\*` (Select, TextInput, Textarea, Toggle, FileUpload, RichEditor)
|
1. **Form components:** `Filament\Forms\Components\*` (Select, TextInput, Textarea, Toggle, FileUpload, RichEditor)
|
||||||
2. **Schema/layout:** `Filament\Schemas\Components\*` (Section, Schema)
|
2. **Schema/layout:** `Filament\Schemas\Components\*` (Section, Schema)
|
||||||
3. **Service access:** `App::make(FileService::class)` hoặc inject constructor
|
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'])`
|
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`
|
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
|
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
|
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
|
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
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -506,9 +568,10 @@ Tổng: 8 tests, 21 assertions.
|
|||||||
|
|
||||||
| File | Nội dung |
|
| File | Nội dung |
|
||||||
|------|----------|
|
|------|----------|
|
||||||
| `AGENTS.md` | Hướng dẫn cho AI Agent: cách chạy, cấu trúc, workflow, RBAC, gotchas |
|
| `AGENTS.md` | Hướng dẫn cho AI Agent: cách chạy, cấu trúc, workflow, RBAC, gotchas, patterns |
|
||||||
| `PROGRESS.md` | Lịch sử tiến độ hoàn thành (8 giai đoạn + bug fix) |
|
| `TASKS_ROADMAP.md` | Roadmap + lịch sử tiến độ hoàn thành |
|
||||||
| `ai_instructions/AI_01_*.md` | Business Logic & Policies (hợp đồng snapshot, transfer policy, handover) |
|
| `docs/I18N_GUIDE.md` | Hướng dẫn đa ngôn ngữ chi tiết |
|
||||||
| `ai_instructions/AI_02_*.md` | Database Schema Upgrade (contracts, handovers, product_services) |
|
| `docs/ENUM_GUIDE.md` | Hướng dẫn thêm Enum mới |
|
||||||
| `ai_instructions/AI_03_*.md` | Data Import Pipeline (spatie/simple-excel, lazy collection) |
|
| `UI_DESIGN_BRIEF.md` | Thiết kế UI chi tiết (horizontal top bar) |
|
||||||
| `TASKS_ROADMAP.md` | Roadmap: việc đã làm, đang làm, sẽ làm |
|
| `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) |
|
||||||
|
|||||||
272
TASKS_ROADMAP.md
272
TASKS_ROADMAP.md
@@ -5,12 +5,22 @@
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## ✅ ĐÃ HOÀN THÀNH (SOP Phase 1–8)
|
## Trạng thái hiện tại
|
||||||
|
|
||||||
|
- **SOP Phase 1-8:** 100% hoàn thành
|
||||||
|
- **AI Instructions Core:** 85% (6/7, còn auto-suggest transfer)
|
||||||
|
- **Test:** Pest 8/8 tests (21 assertions) pass
|
||||||
|
- **Data import:** 3658 rows từ `ai_instructions/CSKH.xlsx` imported successfully
|
||||||
|
- **Dual Panel:** Admin (đầy đủ) + Support (rút gọn, tập trung feedback)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## ✅ ĐÃ HOÀN THÀNH
|
||||||
|
|
||||||
### Giai đoạn 1: Mở rộng CSDL
|
### Giai đoạn 1: Mở rộng CSDL
|
||||||
- [x] Bảng `departments` (id, name, manager_id → users)
|
- [x] Bảng `departments` (id, name, manager_id → users)
|
||||||
- [x] Bổ sung `feedback`: current_department_id, is_escalated
|
- [x] Bổ sung `feedback`: current_department_id, is_escalated
|
||||||
- [x] Bảng `ticket_transfer_logs`
|
- [x] Bảng `ticket_transfer_logs` (feedback_id, from/to department, sender, reason)
|
||||||
- [x] Nâng cấp `feedback_attachments`: uuid, disk, collection
|
- [x] Nâng cấp `feedback_attachments`: uuid, disk, collection
|
||||||
- [x] Cài Spatie laravel-permission
|
- [x] Cài Spatie laravel-permission
|
||||||
|
|
||||||
@@ -26,128 +36,210 @@
|
|||||||
- [x] `TicketClosed` (Database + Mail)
|
- [x] `TicketClosed` (Database + Mail)
|
||||||
|
|
||||||
### Giai đoạn 4: Filament UI
|
### Giai đoạn 4: Filament UI
|
||||||
- [x] `TransferDepartment` Action trên EditFeedback
|
- [x] `TransferDepartment` Action trên Edit Feedback (modal: department + reason + attachment)
|
||||||
- [x] `CloseTicket` Action
|
- [x] `CloseTicket` Action (visible: admin/manager, status ≠ closed, requires confirmation)
|
||||||
- [x] InteractionsRelationManager: role-aware status options
|
- [x] InteractionsRelationManager: role-aware status options (staff: no "closed"), department field
|
||||||
- [x] FeedbackForm: current_department_id, is_escalated
|
- [x] FeedbackForm: current_department_id, is_escalated, attachment_collection
|
||||||
|
|
||||||
### Giai đoạn 5: Dashboard Widgets
|
### Giai đoạn 5: Dashboard Widgets
|
||||||
- [x] `ResolvedTicketsWidget`
|
- [x] `ResolvedTicketsWidget`: bảng ticket resolved chờ đóng (manager lọc theo phòng quản lý)
|
||||||
- [x] `AvgProcessingTimeWidget`
|
- [x] `AvgProcessingTimeWidget`: 4 stats (resolved count, avg time, pending, escalated)
|
||||||
- [x] `HandlerPerformanceWidget`
|
- [x] `HandlerPerformanceWidget`: bar chart thời gian xử lý trung bình theo nhân viên
|
||||||
|
|
||||||
### Giai đoạn 6: Testing
|
### Giai đoạn 6: Testing
|
||||||
- [x] Pest: 8 tests (21 assertions)
|
- [x] Pest cài đặt + 8 test scenarios
|
||||||
|
- [x] `ClosingPermissionTest`: 4 tests (staff 403, no evidence 422, with evidence OK, cross-department 403)
|
||||||
|
- [x] `TransferFlowTest`: 2 tests (success + missing reason)
|
||||||
|
|
||||||
### Giai đoạn 7: Seeder
|
### Giai đoạn 7: Seeder
|
||||||
- [x] 4 departments, 5 channels, 5 products, 4 customers, 5 feedbacks, 5 tags, 5 interactions, 1 transfer log, 3 attachments
|
- [x] 4 departments (CSKH, Kỹ thuật, Kế toán, Pháp lý)
|
||||||
|
- [x] 1 ticket_transfer_log mẫu
|
||||||
|
- [x] 3 feedback_attachments (2 proof_of_resolution, 1 customer_evidence)
|
||||||
|
|
||||||
### Giai đoạn 8: Polish & Bug Fix
|
### Giai đoạn 8: Polish & Bug Fix
|
||||||
- [x] Dashboard widget filter Manager theo department
|
- [x] Dashboard widget filter Manager theo department
|
||||||
- [x] Migration disk default fix
|
- [x] Migration disk default fix (private → local)
|
||||||
- [x] File upload pattern fix
|
- [x] File upload pattern fix
|
||||||
- [x] Department + Escalated columns trên FeedbackTable
|
- [x] Department + Escalated columns trên FeedbackTable + Department filter
|
||||||
- [x] Validation proof_of_resolution khi close từ form
|
- [x] Validation proof_of_resolution khi close từ form
|
||||||
- [x] Manager chỉ đóng ticket phòng mình
|
- [x] Manager chỉ đóng ticket phòng mình
|
||||||
- [x] View Attachments trong Interaction History
|
- [x] View Attachments trong Interaction History (temporary signed URL)
|
||||||
- [x] Docker deployment
|
- [x] Docker deployment (Dockerfile, docker-compose, entrypoint)
|
||||||
- [x] Migration notifications table
|
- [x] Migration notifications table
|
||||||
|
|
||||||
### Giai đoạn 9: Granular Permissions + Export Backup + User/Role Management
|
### Giai đoạn 9: Granular Permissions + Export Backup + User/Role Management
|
||||||
- [x] PermissionSeeder: 29 granular permissions (admin/manager/staff mapping)
|
- [x] PermissionSeeder: 29 granular permissions (view/create/update/delete per module + close-ticket, transfer-department, export-data)
|
||||||
|
- [x] Role → Permission mapping: admin (all), manager (no delete/export), staff (view + limited write)
|
||||||
- [x] Policies updated to `hasPermissionTo()` (5 policies)
|
- [x] Policies updated to `hasPermissionTo()` (5 policies)
|
||||||
- [x] ExportBackup command: `app:export-backup` (JSON, per-table, chunk 500)
|
- [x] ExportBackup command: `app:export-backup` (JSON, per-table, chunk 500, manifest.json)
|
||||||
- [x] UserResource: CRUD users + assign role (admin only)
|
- [x] UserResource: admin-only CRUD users + assign role
|
||||||
- [x] RoleResource: CRUD roles + assign permissions via CheckboxList (admin only)
|
- [x] RoleResource: admin-only CRUD roles + assign permissions via CheckboxList
|
||||||
- [x] UserPolicy + RolePolicy (admin-only access)
|
- [x] UserPolicy + RolePolicy (admin-only access)
|
||||||
|
|
||||||
|
### A: Module Hợp đồng (Contract)
|
||||||
|
- [x] Migration `contracts`: product_id, customer_id, type (sale/lease/bcc), dates, status, signed_status
|
||||||
|
- [x] Enum `ContractType` (SALE/LEASE/BCC) + `ContractStatus` (ACTIVE/EXPIRED/LIQUIDATED)
|
||||||
|
- [x] Model `Contract`: relationships product(), customer(), feedbacks()
|
||||||
|
- [x] Filament Resource `Contracts` (CRUD) + `ContractPolicy`
|
||||||
|
- [x] RelationManager `ContractsRelationManager` trên ProductResource
|
||||||
|
- [x] Model `Product` thêm `contracts()` HasMany
|
||||||
|
- [x] Seeder: 5 contracts mẫu
|
||||||
|
|
||||||
|
### B: Cơ chế Snapshot Hợp đồng
|
||||||
|
- [x] Migration thêm `contract_id` (FK->contracts, nullable) vào bảng `feedback`
|
||||||
|
- [x] Model `Feedback`: thêm `contract_id` fillable + `contract()` BelongsTo
|
||||||
|
- [x] FeedbackForm: auto-select active contract khi chọn sản phẩm, cho phép chọn lại thủ công
|
||||||
|
- [x] FeedbackTable: cột "Contract" hiển thị loại HĐ dạng badge
|
||||||
|
- [x] Seeder: gán contract_id cho 5 feedback mẫu
|
||||||
|
|
||||||
|
### C: Module Bàn giao (Handover)
|
||||||
|
- [x] Migration `handovers`: product_id, customer_id, handover_date, status, remark
|
||||||
|
- [x] Enum `HandoverStatus` (NOT_READY/READY/SCHEDULED/HANDED_OVER)
|
||||||
|
- [x] Model `Handover`: relationships product(), customer()
|
||||||
|
- [x] RelationManager `HandoversRelationManager` trên ProductResource
|
||||||
|
- [x] Model `Product` thêm `handovers()` HasMany
|
||||||
|
- [x] Seeder: 5 handovers mẫu
|
||||||
|
|
||||||
|
### D: Module Dịch vụ phụ trợ (ProductService)
|
||||||
|
- [x] Migration `product_services`: product_id, service_type, status, actual_collection_date, remark
|
||||||
|
- [x] Enum `ServiceType` (MANAGEMENT_FEE/GRATITUDE/SELF_BUSINESS)
|
||||||
|
- [x] Model `ProductService`: relationship product()
|
||||||
|
- [x] RelationManager `ProductServicesRelationManager` trên ProductResource
|
||||||
|
- [x] Model `Product` thêm `productServices()` HasMany
|
||||||
|
- [x] Seeder: 5 services mẫu
|
||||||
|
|
||||||
|
### E: Enhancement ProductResource
|
||||||
|
- [x] ProductResource có 3 tabs: Contracts + Handovers + Services (via getRelations())
|
||||||
|
|
||||||
|
### G: Data Import Pipeline
|
||||||
|
- [x] Cài `spatie/simple-excel` (Lazy Collection, chống OOM)
|
||||||
|
- [x] Command `app:import-cskh {file_path} --dry-run` với ProgressBar, chunk 100 rows
|
||||||
|
- [x] Pipeline per row: Product/Customer firstOrCreate → customer_product pivot → Contract → Handover → Service → Feedback + Interactions
|
||||||
|
- [x] Xử lý DateTimeImmutable object từ Excel, skip header/meta rows
|
||||||
|
- [x] Test thành công: 3658 rows → 3299 products, 2089 customers, 2637 contracts, 2077 handovers, 159 services, 71 feedbacks, 226 interactions
|
||||||
|
|
||||||
|
### Internationalization (i18n) — Vietnamese + English
|
||||||
|
- [x] Config: `APP_LOCALE=vi`, `APP_FALLBACK_LOCALE=en`
|
||||||
|
- [x] Created `lang/en/` and `lang/vi/` with 3 files each: `app.php`, `feedback.php`, `enums.php`
|
||||||
|
- [x] ~120 translation keys: navigation, labels, status, widgets, services, notifications, enums
|
||||||
|
- [x] All Resources: override `getPluralLabel()` + `getNavigationGroup()` with `__()` calls
|
||||||
|
- [x] All Form fields: explicit `->label(__('key'))`
|
||||||
|
- [x] All Table columns: explicit `->label(__('key'))`
|
||||||
|
- [x] All RelationManagers: `$title` uses translation key
|
||||||
|
- [x] All Widgets: `getHeading()`/`getDescription()` return translated strings
|
||||||
|
- [x] Enums: `ContractType`, `ContractStatus`, `HandoverStatus`, `ServiceType` use `__()` in `label()`
|
||||||
|
- [x] Filament vendor: 57 Vietnamese translation files auto-activated
|
||||||
|
|
||||||
|
### UI Polish & Bug Fixes
|
||||||
|
- [x] File upload: Fixed `disk('local')` → `disk('public')` across all components
|
||||||
|
- [x] File upload: Added `exists()` check before getting metadata
|
||||||
|
- [x] File upload: Fixed `dehydrated(false)` issue — use property to store paths
|
||||||
|
- [x] Tailwind CSS: Added ~200 utility classes to `public/css/filament-custom.css`
|
||||||
|
- [x] SVG icons: Use inline `width`/`height` attributes instead of Tailwind classes
|
||||||
|
- [x] GlobalSearch: Customized for all Resources with multi-field search
|
||||||
|
- [x] RelationManager titles: Override `getTitle()` for proper translation
|
||||||
|
- [x] Enum refactoring: Added `color()` method, static `options()`, developer comments
|
||||||
|
- [x] Documentation: Created `docs/I18N_GUIDE.md` and `docs/ENUM_GUIDE.md`
|
||||||
|
|
||||||
|
### Support Panel (Dual Panel)
|
||||||
|
- [x] `SupportPanelProvider`: panel thứ 2 tại path `/support`, rút gọn chỉ tập trung feedback
|
||||||
|
- [x] Resources trong Support: Feedback, Customer, Product, FeedbackChannel, FeedbackTag, ActivityLog
|
||||||
|
- [x] Widgets: ResolvedTicketsWidget, AvgProcessingTimeWidget, HandlerPerformanceWidget
|
||||||
|
|
||||||
|
### ActivityLog — Audit Trail
|
||||||
|
- [x] Migration `activity_logs`: user_id, action, description, subject_type/id, metadata
|
||||||
|
- [x] Model `ActivityLog` + Service `ActivityLogger` (static log method)
|
||||||
|
- [x] Filament Resource `ActivityLogResource` (read-only, list view with filters)
|
||||||
|
- [x] Policy `ActivityLogPolicy` (admin-only)
|
||||||
|
- [x] Translation keys: `resource_activity_logs`, `action_*`, `details`, `system`
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 🔴 CHƯA LÀM — Theo AI Instructions
|
## 🔴 CHƯA LÀM
|
||||||
|
|
||||||
### A: Module Hợp đồng (Contract) — từ `AI_01`, `AI_02`
|
### F: Tự động gợi ý transfer (optional, nice-to-have)
|
||||||
- [x] Tạo migration bảng `contracts`:
|
|
||||||
- `product_id` FK, `customer_id` FK
|
|
||||||
- `type` (LEASE/BCC/SALE)
|
|
||||||
- `start_date`, `end_date`
|
|
||||||
- `status` (Active/Expired/Liquidated)
|
|
||||||
- `printed_at`, `signed_status`
|
|
||||||
- [x] Tạo Model `Contract` + Enum `ContractType`
|
|
||||||
- [x] Relationships: `product()`, `customer()`, `feedbacks()`
|
|
||||||
- [x] Tạo ContractResource Filament + ContractPolicy
|
|
||||||
- [x] Tạo ContractsRelationManager trên ProductResource
|
|
||||||
|
|
||||||
### B: Cơ chế Snapshot Hợp đồng — từ `AI_01`
|
|
||||||
- [x] Tạo migration thêm `contract_id` (FK->contracts, nullable) vào bảng `feedback`
|
|
||||||
- [x] Cập nhật Model `Feedback`: fillable + relationship `contract()`
|
|
||||||
- [x] Logic auto-select active contract khi tạo Feedback mới (FeedbackForm)
|
|
||||||
- [x] Hiển thị contract info trên Feedback list (FeedbackTable column)
|
|
||||||
- [x] Gán contract_id cho feedback mẫu trong Seeder
|
|
||||||
|
|
||||||
### C: Module Bàn giao (Handover) — từ `AI_01`, `AI_02`
|
|
||||||
- [x] Tạo migration bảng `handovers`:
|
|
||||||
- `product_id` FK, `customer_id` FK
|
|
||||||
- `handover_date` (date)
|
|
||||||
- `status` (Chưa đủ điều kiện/Đủ ĐK/Đã lên lịch/Đã BG)
|
|
||||||
- `remark` (text nullable)
|
|
||||||
- [x] Tạo Model `Handover` + Enum `HandoverStatus`
|
|
||||||
- [x] Relationships: `product()`, `customer()`
|
|
||||||
- [x] Tạo `HandoversRelationManager` gắn vào `ProductResource`
|
|
||||||
- [x] Cập nhật Product model: thêm `handovers()` HasMany
|
|
||||||
|
|
||||||
### D: Module Dịch vụ phụ trợ (ProductService) — từ `AI_02`
|
|
||||||
- [x] Tạo migration bảng `product_services`:
|
|
||||||
- `product_id` FK
|
|
||||||
- `service_type` (MANAGEMENT_FEE/GRATITUDE/SELF_BUSINESS)
|
|
||||||
- `status`
|
|
||||||
- `actual_collection_date` (date nullable)
|
|
||||||
- `remark` (text nullable)
|
|
||||||
- [x] Tạo Model `ProductService` + Enum `ServiceType`
|
|
||||||
- [x] Relationship: `product()`
|
|
||||||
- [x] Tạo `ProductServicesRelationManager` gắn vào `ProductResource`
|
|
||||||
- [x] Cập nhật Product model: thêm `productServices()` HasMany
|
|
||||||
|
|
||||||
### E: Enhancement ProductResource — từ `AI_02`
|
|
||||||
- [x] Thêm RelationManagers vào `ProductResource::getRelations()`
|
|
||||||
- ContractsRelationManager ✅
|
|
||||||
- HandoversRelationManager ✅
|
|
||||||
- ProductServicesRelationManager ✅
|
|
||||||
- [x] Product detail page có 3 tabs: Contracts + Handovers + Services
|
|
||||||
|
|
||||||
### F: Tự động gợi ý transfer — từ `AI_01` (optional)
|
|
||||||
- [ ] Hiển thị badge gợi ý: "Ticket BCC → khuyến nghị chuyển Pháp lý"
|
- [ ] Hiển thị badge gợi ý: "Ticket BCC → khuyến nghị chuyển Pháp lý"
|
||||||
- [ ] Logic detect contract type → suggest department
|
- [ ] Logic detect contract type → suggest department
|
||||||
|
|
||||||
### G: Data Import Pipeline — từ `AI_03`
|
|
||||||
- [x] Cài package `spatie/simple-excel`
|
|
||||||
- [x] Tạo Command `app:import-cskh {file_path} --dry-run`
|
|
||||||
- [x] Logic: Lazy Collection, chunk 100 rows, DB::transaction, ProgressBar
|
|
||||||
- [x] Pipeline per row: Product/Customer firstOrCreate → Contract/Handover/Service → Feedback + Interactions
|
|
||||||
- [x] Tạo customer_product pivot record
|
|
||||||
- [x] Xử lý DateTimeImmutable từ Excel
|
|
||||||
- [x] Skip header/meta rows
|
|
||||||
- [x] Đã test thành công với CSKH.xlsx (3658 rows → 3299 products, 2089 customers, 2637 contracts, 2077 handovers, 159 services, 71 feedbacks, 226 interactions)
|
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 📋 Ưu tiên phát triển
|
## 📋 Ưu tiên phát triển
|
||||||
|
|
||||||
| Ưu tiên | Task | Phụ thuộc |
|
| Ưu tiên | Task | Phụ thuộc |
|
||||||
|---------|------|-----------|
|
|---------|------|-----------|
|
||||||
| 1 | ~~A: Module Hợp đồng (Migration + Model)~~ ✅ | Không |
|
| 1 | F: Tự động gợi ý transfer (nice-to-have) | A |
|
||||||
| 2 | ~~B: contract_id trên feedback (Snapshot)~~ ✅ | A |
|
|
||||||
| 3 | ~~C: Module Bàn giao (Migration + Model + RelationManager)~~ ✅ | Không |
|
|
||||||
| 4 | ~~D: Module Dịch vụ (Migration + Model + RelationManager)~~ ✅ | Không |
|
|
||||||
| 5 | ~~E: Enhancement ProductResource~~ ✅ | A, C, D |
|
|
||||||
| 6 | ~~G: Data Import Pipeline~~ ✅ | A, B, C, D |
|
|
||||||
| 7 | F: Tự động gợi ý transfer (nice-to-have) | A |
|
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 📊 Tiến độ tổng quan
|
## 📊 Tiến độ tổng quan
|
||||||
|
|
||||||
```
|
```
|
||||||
SOP Phase 1-8: ████████████████████████ 100% (Hoàn thành)
|
SOP Phase 1-9: ████████████████████████ 100% (Hoàn thành)
|
||||||
AI Instructions Core: ███████████████████████░ 85% (6/7 — Còn: auto-suggest transfer)
|
AI Instructions Core: ███████████████████████░ 85% (6/7 — Còn: auto-suggest transfer)
|
||||||
Data Import Tested: ████████████████████████ 100% (3658 rows real data imported successfully)
|
Support Panel: ████████████████████████ 100% (Hoàn thành)
|
||||||
|
ActivityLog: ████████████████████████ 100% (Hoàn thành)
|
||||||
|
i18n (vi/en): ████████████████████████ 100% (Hoàn thành)
|
||||||
|
Data Import: ████████████████████████ 100% (3658 rows imported)
|
||||||
|
Tests: ████████████████████████ 100% (8 tests, 21 assertions)
|
||||||
```
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Cách chạy Local Dev
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cd /home/phuongtc/vibecode/minicrm
|
||||||
|
php artisan migrate:fresh --seed
|
||||||
|
php artisan serve
|
||||||
|
# Admin panel: http://localhost:8000/admin
|
||||||
|
# Support panel: http://localhost:8000/support
|
||||||
|
php artisan test # 8 tests (21 assertions)
|
||||||
|
```
|
||||||
|
|
||||||
|
### Data Import
|
||||||
|
```bash
|
||||||
|
php artisan app:import-cskh ai_instructions/CSKH.xlsx --dry-run # phân tích
|
||||||
|
php artisan app:import-cskh ai_instructions/CSKH.xlsx # import thật
|
||||||
|
```
|
||||||
|
|
||||||
|
### Export Backup
|
||||||
|
```bash
|
||||||
|
php artisan app:export-backup
|
||||||
|
php artisan app:export-backup --path=/custom/path --pretty
|
||||||
|
php artisan app:export-backup --tables=feedback,customers,products
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Tài khoản test (seeded)
|
||||||
|
|
||||||
|
| Role | Email | Password |
|
||||||
|
|------|-------|----------|
|
||||||
|
| Admin | admin@minicrm.local | password |
|
||||||
|
| Manager | manager@minicrm.local | password |
|
||||||
|
| Staff | staff@minicrm.local | password |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Tech Stack
|
||||||
|
|
||||||
|
- PHP 8.3, Laravel 13.6.0, Filament 5.6.1
|
||||||
|
- Database: SQLite (`database/database.sqlite`)
|
||||||
|
- Composer: `/home/phuongtc/composer`
|
||||||
|
- Spatie: laravel-permission (RBAC)
|
||||||
|
- Testing: Pest PHP
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## File hướng dẫn cho AI Agent
|
||||||
|
|
||||||
|
| File | Nội dung |
|
||||||
|
|------|----------|
|
||||||
|
| `AGENTS.md` | Hướng dẫn toàn diện: chạy, cấu trúc, workflow, RBAC, gotchas, patterns |
|
||||||
|
| `CODEBASE_SNAPSHOT.md` | Snapshot cấu trúc codebase (đọc thay vì scan code) |
|
||||||
|
| `TASKS_ROADMAP.md` | File này — roadmap + tiến độ |
|
||||||
|
| `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 (AI_01-03 specs, SOP_des, PROGRESS cũ...) |
|
||||||
|
|||||||
89
app/Providers/Filament/SupportPanelProvider.php
Normal file
89
app/Providers/Filament/SupportPanelProvider.php
Normal file
@@ -0,0 +1,89 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Providers\Filament;
|
||||||
|
|
||||||
|
use App\Filament\Resources\ActivityLogs\ActivityLogResource;
|
||||||
|
use App\Filament\Resources\Customers\CustomerResource;
|
||||||
|
use App\Filament\Resources\Feedback\FeedbackResource;
|
||||||
|
use App\Filament\Resources\FeedbackChannels\FeedbackChannelResource;
|
||||||
|
use App\Filament\Resources\FeedbackTags\FeedbackTagResource;
|
||||||
|
use App\Filament\Resources\Products\ProductResource;
|
||||||
|
use App\Filament\Widgets\AvgProcessingTimeWidget;
|
||||||
|
use App\Filament\Widgets\HandlerPerformanceWidget;
|
||||||
|
use App\Filament\Widgets\ResolvedTicketsWidget;
|
||||||
|
use Filament\Http\Middleware\Authenticate;
|
||||||
|
use Filament\Http\Middleware\AuthenticateSession;
|
||||||
|
use Filament\Http\Middleware\DisableBladeIconComponents;
|
||||||
|
use Filament\Http\Middleware\DispatchServingFilamentEvent;
|
||||||
|
use Filament\Pages\Dashboard;
|
||||||
|
use Filament\Panel;
|
||||||
|
use Filament\PanelProvider;
|
||||||
|
use Filament\View\PanelsRenderHook;
|
||||||
|
use Illuminate\Cookie\Middleware\AddQueuedCookiesToResponse;
|
||||||
|
use Illuminate\Cookie\Middleware\EncryptCookies;
|
||||||
|
use Illuminate\Foundation\Http\Middleware\PreventRequestForgery;
|
||||||
|
use Illuminate\Routing\Middleware\SubstituteBindings;
|
||||||
|
use Illuminate\Session\Middleware\StartSession;
|
||||||
|
use Illuminate\View\Middleware\ShareErrorsFromSession;
|
||||||
|
|
||||||
|
class SupportPanelProvider extends PanelProvider
|
||||||
|
{
|
||||||
|
public function panel(Panel $panel): Panel
|
||||||
|
{
|
||||||
|
return $panel
|
||||||
|
->id('support')
|
||||||
|
->path('support')
|
||||||
|
->login()
|
||||||
|
->brandName('AfterSales CRM — Support')
|
||||||
|
->sidebarCollapsibleOnDesktop()
|
||||||
|
->colors([
|
||||||
|
'primary' => '#1a56db',
|
||||||
|
'secondary' => '#64748b',
|
||||||
|
'gray' => '#64748b',
|
||||||
|
'success' => '#059669',
|
||||||
|
'warning' => '#d97706',
|
||||||
|
'danger' => '#dc2626',
|
||||||
|
'info' => '#2563eb',
|
||||||
|
])
|
||||||
|
->font('Inter')
|
||||||
|
->favicon(asset('favicon.svg'))
|
||||||
|
->resources([
|
||||||
|
FeedbackResource::class,
|
||||||
|
CustomerResource::class,
|
||||||
|
ProductResource::class,
|
||||||
|
FeedbackChannelResource::class,
|
||||||
|
FeedbackTagResource::class,
|
||||||
|
ActivityLogResource::class,
|
||||||
|
])
|
||||||
|
->pages([
|
||||||
|
Dashboard::class,
|
||||||
|
])
|
||||||
|
->widgets([
|
||||||
|
ResolvedTicketsWidget::class,
|
||||||
|
AvgProcessingTimeWidget::class,
|
||||||
|
HandlerPerformanceWidget::class,
|
||||||
|
])
|
||||||
|
->middleware([
|
||||||
|
EncryptCookies::class,
|
||||||
|
AddQueuedCookiesToResponse::class,
|
||||||
|
StartSession::class,
|
||||||
|
AuthenticateSession::class,
|
||||||
|
ShareErrorsFromSession::class,
|
||||||
|
PreventRequestForgery::class,
|
||||||
|
SubstituteBindings::class,
|
||||||
|
DisableBladeIconComponents::class,
|
||||||
|
DispatchServingFilamentEvent::class,
|
||||||
|
])
|
||||||
|
->authMiddleware([
|
||||||
|
Authenticate::class,
|
||||||
|
])
|
||||||
|
->renderHook(
|
||||||
|
PanelsRenderHook::HEAD_END,
|
||||||
|
fn (): string => '<link rel="stylesheet" href="' . asset('css/filament-custom.css') . '">',
|
||||||
|
)
|
||||||
|
->renderHook(
|
||||||
|
PanelsRenderHook::BODY_END,
|
||||||
|
fn (): string => '<link href="https://fonts.googleapis.com/css2?family=Manrope:wght@500;600;700;800&family=Material+Symbols+Outlined:wght,FILL@100..700,0..1&display=swap" rel="stylesheet">',
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -3,4 +3,5 @@
|
|||||||
return [
|
return [
|
||||||
App\Providers\AppServiceProvider::class,
|
App\Providers\AppServiceProvider::class,
|
||||||
App\Providers\Filament\AdminPanelProvider::class,
|
App\Providers\Filament\AdminPanelProvider::class,
|
||||||
|
App\Providers\Filament\SupportPanelProvider::class,
|
||||||
];
|
];
|
||||||
|
|||||||
Reference in New Issue
Block a user