AfterSales CRM - AI Agent Instructions
Overview
AfterSales CRM is a real-estate after-sales customer care system built with Laravel 13.6 + Filament 5.6.
How to Run
Test Accounts (seeded)
Tech Stack Versions
- PHP 8.3
- Laravel 13.6.0 (
vendor/laravel/framework)
- Filament 5.6.1 (
vendor/filament/filament)
- Database: SQLite (
database/database.sqlite)
- Composer:
/home/phuongtc/composer
- Spatie: laravel-permission (role-based auth)
- Testing: Pest PHP
Running Commands
Directory Structure
Database Schema
departments (NEW)
| Column |
Type |
Notes |
| id |
bigint PK |
|
| name |
string |
|
| manager_id |
fk -> users nullable |
Department head |
| created_at, updated_at |
timestamp |
|
ticket_transfer_logs (NEW)
| Column |
Type |
Notes |
| id |
bigint PK |
|
| feedback_id |
fk -> feedback |
cascadeOnDelete |
| from_department_id |
fk -> departments nullable |
|
| to_department_id |
fk -> departments nullable |
|
| sender_id |
fk -> users |
Who initiated transfer |
| reason |
text |
Mandatory |
| created_at, updated_at |
timestamp |
|
feedback (expanded)
| Column |
Type |
Notes |
| id |
bigint PK |
|
| customer_id |
fk -> customers |
cascadeOnDelete |
| customer_product_id |
fk -> customer_product nullable |
null = general feedback |
| 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) |
| title |
string |
|
| content |
text |
|
| status |
string |
pending, processing, resolved, closed |
| created_at, updated_at |
timestamp |
|
feedback_attachments (expanded)
| Column |
Type |
Notes |
| id |
bigint PK |
|
| uuid |
string nullable unique |
(NEW) |
| 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 |
| 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.)
Workflow
Key Features
Transfer Department (NEW - SOP compliant)
- Action on EditFeedback page: 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
- Visible only to admin/manager, only when status != 'closed'
- Requires confirmation
- Staff (403) if tries to close
- 422 if no proof_of_resolution attachment exists
- Sends TicketClosed notification to handler and department manager
Status Change in Interactions (Updated)
- Staff: only pending, processing, resolved (no "closed" option)
- 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/)
- 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)
- ResolvedTicketsWidget: table of resolved tickets awaiting closure (manager: filtered to own department)
- AvgProcessingTimeWidget: 4 stats (resolved count, avg time, pending count, escalated count)
Navigation Structure
Dashboard widgets: ResolvedTicketsWidget, AvgProcessingTimeWidget (admin/manager only)
RBAC (Role-Based Access Control) - Spatie
Roles: admin, manager, staff
| Permission |
admin |
manager |
staff |
| View products/customers/channels/tags |
✓ |
✓ |
✓ |
| Create/Update products/customers/channels |
✓ |
✓ |
✗ |
| Delete anything |
✓ |
✗ |
✗ |
| View feedbacks |
✓ |
✓ |
✓ |
| Create/Update feedbacks |
✓ |
✓ |
✓ |
| Delete feedbacks |
✓ |
✓ |
✗ |
| Add interactions |
✓ |
✓ |
✓ |
| Close ticket (status→closed) |
✓ |
✓ |
✗ (403) |
| Transfer department |
✓ |
✓ |
✗ |
| Dashboard widgets |
✓ |
✓ |
✗ |
Filament Key Patterns (v5.6)
Form components use Filament\Forms\Components
Schema/layout components use Filament\Schemas\Components
Services access pattern
Spatie role check
Known Gotchas
- Navigation group icons: Filament 5 raises exception if both a navigation group AND its child items have icons.
- Form component namespaces: Form input components under
Filament\Forms\Components\*, layout under Filament\Schemas\Components\*.
- Dynamic selects: Use
$get function injection, NOT $component->getLivewire()->data[...].
- Table name: Model
Feedback maps to table feedback (singular).
- FileUpload with dehydrated(false): Attachments stored by Filament first; create FeedbackAttachment record directly in after()/afterSave()/afterCreate() — do NOT re-upload via FileService.
- Spatie roles in tests: Create roles explicitly in each test (RefreshDatabase rolls back).
- Private disk: Files use
local disk (storage/app/private/); access via signed URLs from FileService.
- ChartWidget properties:
$heading and $columnSpan are non-static in Filament's ChartWidget.
- Manager closing permission: Manager must be department head (
Department.manager_id) to close tickets in that department.
- Docker: Entrypoint auto-generates APP_KEY and runs migrations. SQLite persists via volume mount.