diff --git a/AGENTS.md b/AGENTS.md index b3a83dc4..36b16639 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -3,19 +3,25 @@ > **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) > - `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/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 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 ```bash cd /home/phuongtc/vibecode/minicrm php artisan migrate:fresh --seed php artisan serve -# Open http://localhost:8000/admin +# Admin panel: http://localhost:8000/admin +# Support panel: http://localhost:8000/support ``` ### Test Accounts (seeded) @@ -51,8 +57,30 @@ php artisan test # run pest tests php artisan app:export-backup php artisan app:export-backup --path=/custom/path --pretty 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 ``` @@ -60,6 +88,9 @@ minicrm/ ├── app/ │ ├── Filament/ │ │ ├── Resources/ +│ │ │ ├── ActivityLogs/ # NEW: Audit trail (read-only) +│ │ │ │ ├── ActivityLogResource.php +│ │ │ │ └── Pages/ListActivityLogs.php │ │ │ ├── Feedback/ │ │ │ │ ├── FeedbackResource.php │ │ │ │ ├── Schemas/FeedbackForm.php @@ -67,79 +98,101 @@ minicrm/ │ │ │ │ ├── Pages/{Create,Edit,List}Feedback.php │ │ │ │ └── RelationManagers/InteractionsRelationManager.php │ │ │ ├── Products/ +│ │ │ │ └── RelationManagers/{Contracts,Handovers,ProductServices}RelationManager.php │ │ │ ├── Customers/ │ │ │ │ └── RelationManagers/FeedbacksRelationManager.php +│ │ │ ├── Contracts/ │ │ │ ├── FeedbackChannels/ │ │ │ ├── FeedbackTags/ -│ │ │ ├── Contracts/ │ │ │ ├── Users/ │ │ │ └── Roles/ │ │ └── Widgets/ │ │ ├── ResolvedTicketsWidget.php # Dashboard: resolved tickets table │ │ ├── AvgProcessingTimeWidget.php # Dashboard: stats overview │ │ └── HandlerPerformanceWidget.php # Dashboard: bar chart per handler +│ ├── Console/Commands/ +│ │ ├── ImportCskh.php # app:import-cskh +│ │ └── ExportBackup.php # app:export-backup │ ├── Enums/ │ │ ├── ContractType.php │ │ ├── ContractStatus.php │ │ ├── HandoverStatus.php │ │ └── ServiceType.php +│ ├── Http/ +│ │ ├── Controllers/Controller.php +│ │ └── Middleware/HandleUploadErrors.php │ ├── Models/ -│ │ ├── User.php # HasRoles (Spatie), isAdmin(), isManager() -│ │ ├── Department.php # NEW: name, manager_id → User -│ │ ├── Product.php +│ │ ├── ActivityLog.php # NEW: Audit trail model +│ │ ├── Contract.php │ │ ├── Customer.php │ │ ├── CustomerProduct.php -│ │ ├── Feedback.php # added: current_department_id, is_escalated, transferLogs +│ │ ├── Department.php +│ │ ├── Feedback.php +│ │ ├── FeedbackAttachment.php │ │ ├── FeedbackChannel.php -│ │ ├── FeedbackTag.php │ │ ├── FeedbackInteraction.php -│ │ ├── FeedbackAttachment.php # added: uuid, disk, collection -│ │ └── TicketTransferLog.php # NEW: department-to-department transfer history +│ │ ├── FeedbackTag.php +│ │ ├── Handover.php +│ │ ├── Product.php +│ │ ├── ProductService.php +│ │ ├── TicketTransferLog.php +│ │ └── User.php # HasRoles (Spatie), isAdmin(), isManager() │ ├── Policies/ -│ │ ├── FeedbackPolicy.php # Spatie hasRole() checks -│ │ ├── ProductPolicy.php +│ │ ├── ActivityLogPolicy.php # NEW +│ │ ├── ContractPolicy.php │ │ ├── CustomerPolicy.php -│ │ └── FeedbackChannelPolicy.php -│ ├── Services/ -│ │ ├── FileService.php # Upload, validation, temp URLs, collections -│ │ ├── TransferService.php # Cross-dept transfer + notification -│ │ └── ClosingService.php # Role-gated closing + evidence check -│ ├── Rules/ -│ │ └── RequireProofOfResolution.php # Custom validation: proof_of_resolution required for close +│ │ ├── FeedbackChannelPolicy.php +│ │ ├── FeedbackPolicy.php +│ │ ├── ProductPolicy.php +│ │ ├── RolePolicy.php +│ │ └── UserPolicy.php │ ├── Notifications/ -│ │ ├── TicketTransferred.php # DB + Mail for department transfer -│ │ └── TicketClosed.php # DB + Mail for ticket closure -│ └── Providers/Filament/ -│ └── AdminPanelProvider.php -├── lang/ # i18n translation files -│ ├── en/ -│ │ ├── app.php # General UI labels, navigation, status, widgets -│ │ ├── feedback.php # Feedback domain: services, notifications, interactions -│ │ └── enums.php # Enum labels -│ └── vi/ -│ ├── app.php -│ ├── feedback.php -│ └── enums.php +│ │ ├── TicketTransferred.php +│ │ └── TicketClosed.php +│ ├── Providers/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 +├── lang/ # i18n translation files +│ ├── en/{app.php, feedback.php, enums.php} +│ └── vi/{app.php, feedback.php, enums.php} ├── resources/views/filament/resources/feedback/ │ ├── 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/ -│ ├── migrations/ -│ └── seeders/AfterSalesSeeder.php # includes departments, logs, attachments -├── tests/ -│ ├── Feature/ -│ │ ├── ClosingPermissionTest.php # 4 tests: staff forbid, no-evidence, with-evidence, cross-dept -│ │ └── TransferFlowTest.php # 2 tests: successful + missing reason -│ └── Pest.php -├── storage/app/private/feedback-attachments/ # Private disk upload directory -├── composer.json -├── PROGRESS.md -└── AGENTS.md +│ ├── migrations/ # 25 migrations +│ └── seeders/{AfterSalesSeeder,PermissionSeeder}.php +├── tests/Feature/ +│ ├── ClosingPermissionTest.php +│ └── TransferFlowTest.php +├── luutru/ # Archive các file đã lỗi thời +├── UI_DESIGN_BRIEF.md # Thiết kế UI chi tiết +└── TASKS_ROADMAP.md ``` ## 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 | |--------|------|-------| | id | bigint PK | | @@ -147,7 +200,7 @@ minicrm/ | manager_id | fk -> users nullable | Department head | | created_at, updated_at | timestamp | | -### ticket_transfer_logs (NEW) +### ticket_transfer_logs | Column | Type | Notes | |--------|------|-------| | id | bigint PK | | @@ -164,10 +217,11 @@ minicrm/ | id | bigint PK | | | customer_id | fk -> customers | cascadeOnDelete | | customer_product_id | fk -> customer_product nullable | null = general feedback | +| contract_id | fk -> contracts nullable | **(NEW)** Snapshot contract | | feedback_channel_id | fk -> feedback_channels | | | assigned_to | fk -> users nullable | Current handler | -| current_department_id | fk -> departments nullable | **(NEW)** Current department | -| is_escalated | boolean default false | **(NEW)** | +| current_department_id | fk -> departments nullable | Current department | +| is_escalated | boolean default false | | | title | string | | | content | text | | | status | string | pending, processing, resolved, closed | @@ -177,20 +231,20 @@ minicrm/ | Column | Type | Notes | |--------|------|-------| | id | bigint PK | | -| uuid | string nullable unique | **(NEW)** | +| uuid | string nullable unique | | | feedback_id | fk -> feedback | | | feedback_interaction_id | fk -> feedback_interactions nullable | | | user_id | fk -> users | | | name | string | | | path | string | | -| disk | string default 'local' | **(NEW)** | -| collection | string default 'general' | **(NEW)** proof_of_resolution, customer_evidence, general | +| disk | string default 'local' | | +| collection | string default 'general' | proof_of_resolution, customer_evidence, general | | mime_type | string nullable | | | size | bigint nullable | | | created_at, updated_at | timestamp | | ### 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 @@ -205,14 +259,24 @@ feedback_interactions, feedback_tags, feedback_feedback_tag, products, customers ## Key Features -### Transfer Department (NEW - SOP compliant) -- Action on EditFeedback page: TransferDepartment +### Dual Panel System (NEW) +- **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 - Calls TransferService: creates TicketTransferLog, updates current_department_id, resets handler to dept manager - Sends TicketTransferred notification to target department manager -### Close Ticket (NEW - SOP compliant) -- Action on EditFeedback page: CloseTicket +### Close Ticket (SOP compliant) +- Action trên EditFeedback: CloseTicket - Visible only to admin/manager, only when status != 'closed' - Requires confirmation - 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" - When "closed" selected: calls ClosingService which validates evidence -### File Management (Updated - SOP compliant) -- Files stored on private `local` disk (storage/app/private/feedback-attachments/) +### File Management (SOP compliant) +- Files stored on `public` disk (`storage/app/public/feedback-attachments/`) - Collection system: `proof_of_resolution`, `customer_evidence`, `general` - FileService validates: jpg, png, pdf, mp4, mov, max 20MB - Temporary signed URLs for private file access - FileService::hasCollection() checks existence of required evidence -### Dashboard Widgets (NEW) +### Dashboard Widgets - ResolvedTicketsWidget: table of resolved tickets awaiting closure (manager: filtered to own department) - AvgProcessingTimeWidget: 4 stats (resolved count, avg time, pending count, escalated count) - HandlerPerformanceWidget: bar chart of avg processing time per handler @@ -303,19 +367,35 @@ public function getHeading(): string | Htmlable | null ## Navigation Structure +### Admin Panel (`/admin`) ``` -Dashboard +Dashboard (AccountWidget + 3 custom widgets) ├── Customer Care / Chăm sóc khách hàng (group) │ └── Feedbacks / Phản ánh (chat-bubble-left-right, sort=1) └── Management / Quản lý (group) ├── Products / Sản phẩm (building-storefront, sort=1) ├── Customers / Khách hàng (user-group, sort=2) ├── Channels / Kênh phản ánh (inbox-stack, sort=3) + ├── Contracts / Hợp đồng (document-text, sort=4) ├── Tags / Thẻ (tag, sort=default) + ├── Activity Logs (clock, sort=7) — admin only ├── Users / Người dùng (users, sort=5) — 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) ## RBAC (Role-Based Access Control) - Spatie @@ -400,6 +480,9 @@ use Filament\Schemas\Schema; ```php use Illuminate\Support\Facades\App; $service = App::make(FileService::class); + +// ActivityLogger (static) +\App\Services\ActivityLogger::log('transfer', '...', $feedback, ['dept' => 'CSKH']); ``` ### 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(). 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., ``) 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 @@ -501,3 +585,4 @@ protected function afterSave(): void // For SVG icons, use inline attributes: ... // NOT: ... +``` diff --git a/CODEBASE_SNAPSHOT.md b/CODEBASE_SNAPSHOT.md index 94e6d349..b76a2581 100644 --- a/CODEBASE_SNAPSHOT.md +++ b/CODEBASE_SNAPSHOT.md @@ -10,8 +10,8 @@ | Mục | Giá trị | |------|--------| | Framework | Laravel 13.6.0 | -| Admin Panel | Filament 5.6.1 | -| Auth/RBAC | Spatie laravel-permission (3 roles: admin, manager, staff) | +| 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 | @@ -32,6 +32,9 @@ app/ ├── Filament/ │ ├── Resources/ +│ │ ├── ActivityLogs/ # NEW: Audit trail (read-only) +│ │ │ ├── ActivityLogResource.php +│ │ │ └── Pages/ListActivityLogs.php │ │ ├── Customers/ │ │ │ ├── CustomerResource.php │ │ │ ├── Pages/ @@ -56,7 +59,7 @@ app/ │ │ │ │ └── FeedbackForm.php │ │ │ └── Tables/ │ │ │ └── FeedbackTable.php -│ │ ├── Contracts/ # NEW +│ │ ├── Contracts/ │ │ │ ├── ContractResource.php │ │ │ ├── Pages/ │ │ │ │ ├── CreateContract.php @@ -86,32 +89,32 @@ app/ │ │ │ │ └── FeedbackTagForm.php │ │ │ └── Tables/ │ │ │ └── FeedbackTagsTable.php -│ │ └── Products/ -│ │ ├── ProductResource.php -│ │ ├── Pages/ -│ │ │ ├── CreateProduct.php -│ │ │ ├── EditProduct.php -│ │ │ └── ListProducts.php -│ │ ├── RelationManagers/ -│ │ │ ├── ContractsRelationManager.php # NEW -│ │ │ ├── HandoversRelationManager.php # NEW -│ │ │ └── ProductServicesRelationManager.php # NEW -│ │ ├── Schemas/ -│ │ │ └── ProductForm.php -│ │ └── Tables/ -│ │ └── ProductsTable.php -│ │ └── Users/ # NEW -│ │ ├── UserResource.php # Admin-only user management -│ │ ├── 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/ # NEW -│ │ ├── RoleResource.php # Admin-only role management +│ │ ├── 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 @@ -124,20 +127,23 @@ app/ │ ├── 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/ # NEW +├── Console/ │ └── Commands/ │ ├── ImportCskh.php # app:import-cskh command │ └── ExportBackup.php # app:export-backup command (JSON backup) -├── Enums/ # NEW +├── 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 +│ ├── Controllers/ +│ │ └── Controller.php +│ └── Middleware/ +│ └── HandleUploadErrors.php ├── Models/ -│ ├── Contract.php # NEW +│ ├── ActivityLog.php # NEW: Audit trail (user_id, action, description, metadata) +│ ├── Contract.php │ ├── Customer.php │ ├── CustomerProduct.php # pivot model (customer_product table) │ ├── Department.php @@ -146,27 +152,32 @@ app/ │ ├── FeedbackChannel.php │ ├── FeedbackInteraction.php │ ├── FeedbackTag.php -│ ├── Handover.php # NEW +│ ├── Handover.php │ ├── Product.php -│ ├── ProductService.php # NEW +│ ├── ProductService.php │ ├── TicketTransferLog.php │ └── User.php ├── Notifications/ │ ├── TicketClosed.php │ └── TicketTransferred.php ├── Policies/ -│ ├── ContractPolicy.php # NEW +│ ├── ActivityLogPolicy.php # NEW: Admin-only +│ ├── ContractPolicy.php │ ├── CustomerPolicy.php │ ├── FeedbackChannelPolicy.php │ ├── FeedbackPolicy.php -│ └── ProductPolicy.php +│ ├── ProductPolicy.php +│ ├── RolePolicy.php +│ └── UserPolicy.php ├── Providers/ │ ├── AppServiceProvider.php │ └── Filament/ -│ └── AdminPanelProvider.php +│ ├── 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 @@ -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 +### 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 | |-----|------| @@ -186,7 +226,7 @@ app/ 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 | |-----|------| | id | bigint PK | @@ -198,7 +238,7 @@ Relationships: `customers()` BelongsToMany (pivot: customer_product, withPivot: Relationships: `product()` BelongsTo -### Handover (`handovers`) — NEW +### Handover (`handovers`) | Cột | Kiểu | |-----|------| | id | bigint PK | @@ -210,7 +250,7 @@ Relationships: `product()` BelongsTo Relationships: `product()` BelongsTo, `customer()` BelongsTo -### Contract (`contracts`) — NEW +### Contract (`contracts`) | Cột | Kiểu | |-----|------| | id | bigint PK | @@ -266,7 +306,7 @@ Relationships: `manager()` BelongsTo User, `feedbacks()` HasMany, `transferLogsF | password | string | | role | string (admin/manager/staff) | -Uses Spatie `HasRoles`. Relationships: `assignedFeedbacks()` HasMany Feedback. +Uses Spatie `HasRoles`. Relationships: `assignedFeedbacks()` HasMany Feedback, `activityLogs()` HasMany. ### Feedback (`feedback`) | Cột | Kiểu | @@ -323,6 +363,7 @@ Relationships: `feedback()`, `interaction()`, `user()`. Scopes: `byCollection()` | name | string | | slug | string unique | | icon | string nullable | +| color | string nullable | | is_active | boolean default true | Relationships: `feedbacks()` HasMany @@ -353,27 +394,31 @@ 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). -### 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 -> **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 | |--------|-------------|--------|--------|--------|---------------------| +| 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 | @@ -386,6 +431,7 @@ Relationships: `feedback()`, `fromDepartment()`, `toDepartment()`, `sender()` ## Filament Navigation Structure +### Admin Panel (`/admin`) ``` Dashboard (AccountWidget + 3 custom widgets) ├── Customer Care (group) @@ -394,8 +440,24 @@ Dashboard (AccountWidget + 3 custom widgets) ├── 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) # NEW - └── Tags (heroicon-o-tag, default sort) + ├── 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 ``` --- @@ -407,7 +469,7 @@ Dashboard (AccountWidget + 3 custom widgets) --- -## Database Migration files (23 files) +## Database Migration files (25 files) | # | 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 | | 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` | **(NEW)** contracts | -| 21 | `2026_05_01_000001_add_contract_id_to_feedback_table` | **(NEW)** add contract_id FK to feedback | -| 22 | `2026_05_01_000002_create_handovers_table` | **(NEW)** handovers | -| 23 | `2026_05_01_000003_create_product_services_table` | **(NEW)** product_services | +| 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) — NEW +- **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 @@ -459,12 +523,9 @@ Dashboard (AccountWidget + 3 custom widgets) - 1 ticket_transfer_log - 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}` - 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) 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'])` 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 +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 --- @@ -506,9 +568,10 @@ Tổng: 8 tests, 21 assertions. | File | Nội dung | |------|----------| -| `AGENTS.md` | Hướng dẫn cho AI Agent: cách chạy, cấu trúc, workflow, RBAC, gotchas | -| `PROGRESS.md` | Lịch sử tiến độ hoàn thành (8 giai đoạn + bug fix) | -| `ai_instructions/AI_01_*.md` | Business Logic & Policies (hợp đồng snapshot, transfer policy, handover) | -| `ai_instructions/AI_02_*.md` | Database Schema Upgrade (contracts, handovers, product_services) | -| `ai_instructions/AI_03_*.md` | Data Import Pipeline (spatie/simple-excel, lazy collection) | -| `TASKS_ROADMAP.md` | Roadmap: việc đã làm, đang làm, sẽ làm | +| `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) | diff --git a/TASKS_ROADMAP.md b/TASKS_ROADMAP.md index cda77509..d84646e2 100644 --- a/TASKS_ROADMAP.md +++ b/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 - [x] Bảng `departments` (id, name, manager_id → users) - [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] Cài Spatie laravel-permission @@ -26,128 +36,210 @@ - [x] `TicketClosed` (Database + Mail) ### Giai đoạn 4: Filament UI -- [x] `TransferDepartment` Action trên EditFeedback -- [x] `CloseTicket` Action -- [x] InteractionsRelationManager: role-aware status options -- [x] FeedbackForm: current_department_id, is_escalated +- [x] `TransferDepartment` Action trên Edit Feedback (modal: department + reason + attachment) +- [x] `CloseTicket` Action (visible: admin/manager, status ≠ closed, requires confirmation) +- [x] InteractionsRelationManager: role-aware status options (staff: no "closed"), department field +- [x] FeedbackForm: current_department_id, is_escalated, attachment_collection ### Giai đoạn 5: Dashboard Widgets -- [x] `ResolvedTicketsWidget` -- [x] `AvgProcessingTimeWidget` -- [x] `HandlerPerformanceWidget` +- [x] `ResolvedTicketsWidget`: bảng ticket resolved chờ đóng (manager lọc theo phòng quản lý) +- [x] `AvgProcessingTimeWidget`: 4 stats (resolved count, avg time, pending, escalated) +- [x] `HandlerPerformanceWidget`: bar chart thời gian xử lý trung bình theo nhân viên ### 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 -- [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 - [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] 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] Manager chỉ đóng ticket phòng mình -- [x] View Attachments trong Interaction History -- [x] Docker deployment +- [x] View Attachments trong Interaction History (temporary signed URL) +- [x] Docker deployment (Dockerfile, docker-compose, entrypoint) - [x] Migration notifications table ### 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] ExportBackup command: `app:export-backup` (JSON, per-table, chunk 500) -- [x] UserResource: CRUD users + assign role (admin only) -- [x] RoleResource: CRUD roles + assign permissions via CheckboxList (admin only) +- [x] ExportBackup command: `app:export-backup` (JSON, per-table, chunk 500, manifest.json) +- [x] UserResource: admin-only CRUD users + assign role +- [x] RoleResource: admin-only CRUD roles + assign permissions via CheckboxList - [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` -- [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) +### F: Tự động gợi ý transfer (optional, nice-to-have) - [ ] Hiển thị badge gợi ý: "Ticket BCC → khuyến nghị chuyển Pháp lý" - [ ] 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 | Task | Phụ thuộc | |---------|------|-----------| -| 1 | ~~A: Module Hợp đồng (Migration + Model)~~ ✅ | Không | -| 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 | +| 1 | F: Tự động gợi ý transfer (nice-to-have) | A | --- ## 📊 Tiến độ tổng quan ``` -SOP Phase 1-8: ████████████████████████ 100% (Hoàn thành) -AI Instructions Core: ███████████████████████░ 85% (6/7 — Còn: auto-suggest transfer) -Data Import Tested: ████████████████████████ 100% (3658 rows real data imported successfully) +SOP Phase 1-9: ████████████████████████ 100% (Hoàn thành) +AI Instructions Core: ███████████████████████░ 85% (6/7 — Còn: auto-suggest transfer) +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ũ...) | diff --git a/app/Providers/Filament/SupportPanelProvider.php b/app/Providers/Filament/SupportPanelProvider.php new file mode 100644 index 00000000..709cacc4 --- /dev/null +++ b/app/Providers/Filament/SupportPanelProvider.php @@ -0,0 +1,89 @@ +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 => '', + ) + ->renderHook( + PanelsRenderHook::BODY_END, + fn (): string => '', + ); + } +} diff --git a/bootstrap/providers.php b/bootstrap/providers.php index 22744d11..f19cf8ac 100644 --- a/bootstrap/providers.php +++ b/bootstrap/providers.php @@ -3,4 +3,5 @@ return [ App\Providers\AppServiceProvider::class, App\Providers\Filament\AdminPanelProvider::class, + App\Providers\Filament\SupportPanelProvider::class, ]; diff --git a/ai_instructions/AI_01_BUSINESS_LOGIC_AND_POLICIES.md b/luutru/AI_01_BUSINESS_LOGIC_AND_POLICIES.md similarity index 100% rename from ai_instructions/AI_01_BUSINESS_LOGIC_AND_POLICIES.md rename to luutru/AI_01_BUSINESS_LOGIC_AND_POLICIES.md diff --git a/ai_instructions/AI_02_DATABASE_SCHEMA_UPGRADE.md b/luutru/AI_02_DATABASE_SCHEMA_UPGRADE.md similarity index 100% rename from ai_instructions/AI_02_DATABASE_SCHEMA_UPGRADE.md rename to luutru/AI_02_DATABASE_SCHEMA_UPGRADE.md diff --git a/ai_instructions/AI_03_DATA_IMPORT_PIPELINE.md b/luutru/AI_03_DATA_IMPORT_PIPELINE.md similarity index 100% rename from ai_instructions/AI_03_DATA_IMPORT_PIPELINE.md rename to luutru/AI_03_DATA_IMPORT_PIPELINE.md diff --git a/CHANGELOG.md b/luutru/CHANGELOG.md similarity index 100% rename from CHANGELOG.md rename to luutru/CHANGELOG.md diff --git a/PROGRESS.md b/luutru/PROGRESS.md similarity index 100% rename from PROGRESS.md rename to luutru/PROGRESS.md diff --git a/README.md b/luutru/README.md similarity index 100% rename from README.md rename to luutru/README.md diff --git a/SOP_des.md b/luutru/SOP_des.md similarity index 100% rename from SOP_des.md rename to luutru/SOP_des.md diff --git a/SYSTEM_ASSESSMENT_PROPOSALS.md b/luutru/SYSTEM_ASSESSMENT_PROPOSALS.md similarity index 100% rename from SYSTEM_ASSESSMENT_PROPOSALS.md rename to luutru/SYSTEM_ASSESSMENT_PROPOSALS.md diff --git a/mota.md b/luutru/mota.md similarity index 100% rename from mota.md rename to luutru/mota.md