Files
minicrm/CODEBASE_SNAPSHOT.md
2026-05-01 04:33:00 +00:00

20 KiB

CODEBASE_SNAPSHOT.md — AfterSales CRM

Mục đích: File snapshot toàn bộ cấu trúc codebase hiện tại. Dành cho AI Agent đọc thay vì scan toàn bộ code. Cập nhật: Mỗi khi có thay đổi lớn về cấu trúc (thêm/xóa Model, Migration, Resource, Service, Policy...).


Thông tin chung

Mục Giá trị
Framework Laravel 13.6.0
Admin Panel Filament 5.6.1
Auth/RBAC Spatie laravel-permission (3 roles: admin, manager, staff)
Database SQLite (database/database.sqlite)
Testing Pest PHP
PHP 8.3
Composer /home/phuongtc/composer

Test Accounts (seeded):

Role Email Password
Admin admin@minicrm.local password
Manager manager@minicrm.local password
Staff staff@minicrm.local password

Cây thư mục app/

app/
├── Filament/
│   ├── Resources/
│   │   ├── Customers/
│   │   │   ├── CustomerResource.php
│   │   │   ├── Pages/
│   │   │   │   ├── CreateCustomer.php
│   │   │   │   ├── EditCustomer.php
│   │   │   │   └── ListCustomers.php
│   │   │   ├── RelationManagers/
│   │   │   │   └── FeedbacksRelationManager.php     # read-only table
│   │   │   ├── Schemas/
│   │   │   │   └── CustomerForm.php
│   │   │   └── Tables/
│   │   │       └── CustomersTable.php
│   │   ├── Feedback/
│   │   │   ├── FeedbackResource.php
│   │   │   ├── Pages/
│   │   │   │   ├── CreateFeedback.php
│   │   │   │   ├── EditFeedback.php                 # TransferDepartment + CloseTicket actions
│   │   │   │   └── ListFeedback.php
│   │   │   ├── RelationManagers/
│   │   │   │   └── InteractionsRelationManager.php  # status_change/note/assignment
│   │   │   ├── Schemas/
│   │   │   │   └── FeedbackForm.php
│   │   │   └── Tables/
│   │   │       └── FeedbackTable.php
│   │   ├── Contracts/                           # NEW
│   │   │   ├── ContractResource.php
│   │   │   ├── Pages/
│   │   │   │   ├── CreateContract.php
│   │   │   │   ├── EditContract.php
│   │   │   │   └── ListContracts.php
│   │   │   ├── Schemas/
│   │   │   │   └── ContractForm.php
│   │   │   └── Tables/
│   │   │       └── ContractsTable.php
│   │   ├── FeedbackChannels/
│   │   │   ├── FeedbackChannelResource.php
│   │   │   ├── Pages/
│   │   │   │   ├── CreateFeedbackChannel.php
│   │   │   │   ├── EditFeedbackChannel.php
│   │   │   │   └── ListFeedbackChannels.php
│   │   │   ├── Schemas/
│   │   │   │   └── FeedbackChannelForm.php
│   │   │   └── Tables/
│   │   │       └── FeedbackChannelsTable.php
│   │   ├── FeedbackTags/
│   │   │   ├── FeedbackTagResource.php
│   │   │   ├── Pages/
│   │   │   │   ├── CreateFeedbackTag.php
│   │   │   │   ├── EditFeedbackTag.php
│   │   │   │   └── ListFeedbackTags.php
│   │   │   ├── Schemas/
│   │   │   │   └── FeedbackTagForm.php
│   │   │   └── Tables/
│   │   │       └── FeedbackTagsTable.php
│   │   └── Products/
│   │       ├── ProductResource.php
│   │       ├── Pages/
│   │       │   ├── CreateProduct.php
│   │       │   ├── EditProduct.php
│   │       │   └── ListProducts.php
│   │       ├── RelationManagers/
│   │   │   ├── ContractsRelationManager.php   # NEW
│   │   │   ├── HandoversRelationManager.php   # NEW
│   │   │   └── ProductServicesRelationManager.php # NEW
│   │       ├── Schemas/
│   │       │   └── ProductForm.php
│   │       └── Tables/
│   │           └── ProductsTable.php
│   └── Widgets/
│       ├── AvgProcessingTimeWidget.php       # 4 stats: resolved count, avg time, pending, escalated
│       ├── HandlerPerformanceWidget.php      # bar chart: avg time per handler
│       └── ResolvedTicketsWidget.php         # table: resolved tickets awaiting closure
├── Console/                                   # NEW
│   └── Commands/
│       └── ImportCskh.php                     # app:import-cskh command
├── Enums/                                     # NEW
│   ├── 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
├── Models/
│   ├── Contract.php                           # NEW
│   ├── Customer.php
│   ├── CustomerProduct.php                   # pivot model (customer_product table)
│   ├── Department.php
│   ├── Feedback.php
│   ├── FeedbackAttachment.php
│   ├── FeedbackChannel.php
│   ├── FeedbackInteraction.php
│   ├── FeedbackTag.php
│   ├── Handover.php                           # NEW
│   ├── Product.php
│   ├── ProductService.php                     # NEW
│   ├── TicketTransferLog.php
│   └── User.php
├── Notifications/
│   ├── TicketClosed.php
│   └── TicketTransferred.php
├── Policies/
│   ├── ContractPolicy.php                    # NEW
│   ├── CustomerPolicy.php
│   ├── FeedbackChannelPolicy.php
│   ├── FeedbackPolicy.php
│   └── ProductPolicy.php
├── Providers/
│   ├── AppServiceProvider.php
│   └── Filament/
│       └── AdminPanelProvider.php
├── Rules/
│   └── RequireProofOfResolution.php
└── Services/
    ├── ClosingService.php
    ├── FileService.php
    └── TransferService.php

Models — Chi tiết

Product (products)

Cột Kiểu
id bigint PK
name string
description text nullable
status string (active/inactive/sold_out)

Relationships: customers() BelongsToMany (pivot: customer_product, withPivot: purchase_date), contracts() HasMany, handovers() HasMany, productServices() HasMany

ProductService (product_services) — NEW

Cột Kiểu
id bigint PK
product_id FK->products cascadeOnDelete
service_type string (management_fee/gratitude/self_business → ServiceType enum)
status string (active/pending/completed/cancelled)
actual_collection_date date nullable
remark text nullable

Relationships: product() BelongsTo

Handover (handovers) — NEW

Cột Kiểu
id bigint PK
product_id FK->products cascadeOnDelete
customer_id FK->customers cascadeOnDelete
handover_date date nullable
status string (chua_du_dieu_kien/du_dieu_kien/da_len_lich/da_ban_giao → HandoverStatus enum)
remark text nullable

Relationships: product() BelongsTo, customer() BelongsTo

Contract (contracts) — NEW

Cột Kiểu
id bigint PK
product_id FK->products cascadeOnDelete
customer_id FK->customers cascadeOnDelete
type string (sale/lease/bcc → ContractType enum)
start_date date nullable
end_date date nullable
status string (active/expired/liquidated → ContractStatus enum)
printed_at date nullable
signed_status string nullable

Relationships: product() BelongsTo, customer() BelongsTo, feedbacks() HasMany

Customer (customers)

Cột Kiểu
id bigint PK
name string
email string nullable unique
phone string nullable
address text nullable
status string (active)

Relationships: products() BelongsToMany, feedbacks() HasMany

CustomerProduct (customer_product — pivot model)

Cột Kiểu
id bigint PK
customer_id FK->customers cascadeOnDelete
product_id FK->products cascadeOnDelete
purchase_date date nullable
UNIQUE(customer_id, product_id)

Relationships: customer() BelongsTo, product() BelongsTo, feedbacks() HasMany

Department (departments)

Cột Kiểu
id bigint PK
name string
manager_id FK->users nullable nullOnDelete

Relationships: manager() BelongsTo User, feedbacks() HasMany, transferLogsFrom() HasMany, transferLogsTo() HasMany

User (users)

Cột Kiểu
id bigint PK
name string
email string unique
password string
role string (admin/manager/staff)

Uses Spatie HasRoles. Relationships: assignedFeedbacks() HasMany Feedback.

Feedback (feedback)

Cột Kiểu
id bigint PK
customer_id FK->customers cascadeOnDelete
customer_product_id FK->customer_product nullable nullOnDelete
contract_id FK->contracts nullable nullOnDelete
feedback_channel_id FK->feedback_channels
assigned_to FK->users nullable nullOnDelete
current_department_id FK->departments nullable nullOnDelete
is_escalated boolean default false
title string
content text
status string (pending/processing/resolved/closed)

Fillable: ['customer_id', 'customer_product_id', 'contract_id', 'feedback_channel_id', 'assigned_to', 'title', 'content', 'status', 'current_department_id', 'is_escalated']

Relationships (10): customer(), customerProduct(), contract(), feedbackChannel(), assignedTo(), currentDepartment(), transferLogs(), interactions() ordered latest, attachments(), tags() BelongsToMany

FeedbackInteraction (feedback_interactions)

Cột Kiểu
id bigint PK
feedback_id FK->feedback cascadeOnDelete
user_id FK->users
type string (note/status_change/assignment)
content text nullable
changes json nullable

Relationships: feedback() BelongsTo, user() BelongsTo, attachments() HasMany

FeedbackAttachment (feedback_attachments)

Cột Kiểu
id bigint PK
uuid uuid nullable unique
feedback_id FK->feedback cascadeOnDelete
feedback_interaction_id FK->feedback_interactions nullable nullOnDelete
user_id FK->users
name string
path string
disk string default 'local'
collection string default 'general' (proof_of_resolution/customer_evidence/general)
mime_type string nullable
size bigint nullable

Relationships: feedback(), interaction(), user(). Scopes: byCollection(), byDisk().

FeedbackChannel (feedback_channels)

Cột Kiểu
id bigint PK
name string
slug string unique
icon string nullable
is_active boolean default true

Relationships: feedbacks() HasMany

FeedbackTag (feedback_tags)

Cột Kiểu
id bigint PK
name string
slug string unique
color string nullable

Relationships: feedbacks() BelongsToMany (pivot: feedback_feedback_tag)

TicketTransferLog (ticket_transfer_logs)

Cột Kiểu
id bigint PK
feedback_id FK->feedback cascadeOnDelete
from_department_id FK->departments nullable nullOnDelete
to_department_id FK->departments nullable nullOnDelete
sender_id FK->users
reason text

Relationships: feedback(), fromDepartment(), toDepartment(), sender()


Services — Tóm tắt

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).

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

Policy viewAny/view create update delete restore/forceDelete
ContractPolicy all roles admin+manager admin+manager admin admin only
FeedbackPolicy all roles all roles all roles admin+manager admin only
ProductPolicy all roles admin+manager admin+manager admin admin only
CustomerPolicy all roles admin+manager admin+manager admin admin only
FeedbackChannelPolicy all roles admin+manager admin+manager admin admin only

Filament Navigation Structure

Dashboard (AccountWidget + 3 custom widgets)
├── Customer Care (group)
│   └── Feedbacks (heroicon-o-chat-bubble-left-right, sort=1)
└── Management (group)
    ├── Products (heroicon-o-building-storefront, sort=1)
    ├── Customers (heroicon-o-user-group, sort=2)
    ├── Channels (heroicon-o-inbox-stack, sort=3)
    ├── Contracts (heroicon-o-document-text, sort=4)     # NEW
    └── Tags (heroicon-o-tag, default sort)

Key Filament Actions trên EditFeedback

  1. TransferDepartment — Modal: Chọn phòng ban đích + reason (required) + attachments (optional). Visibility: admin/manager.
  2. CloseTicket — Confirm modal. Visibility: admin/manager, status ≠ closed.

Database Migration files (23 files)

# File Tables
1 0001_01_01_000000 users, password_reset_tokens, sessions
2 0001_01_01_000001 cache, cache_locks
3 0001_01_01_000002 jobs, job_batches, failed_jobs
4 2026_04_26_052423_create_products_table products
5 2026_04_26_052423_create_customers_table customers
6 2026_04_26_052423_create_customer_product_table customer_product (pivot)
7 2026_04_26_052424_create_feedback_channels_table feedback_channels
8 2026_04_26_052424_create_feedbacks_table feedback (base)
9 2026_04_26_052528_add_role_to_users_table (modify users) add role
10 2026_04_26_063709_create_feedback_interactions_table feedback_interactions
11 2026_04_26_063710_create_feedback_attachments_table feedback_attachments (base)
12 2026_04_26_070225_create_feedback_tags_table feedback_tags, feedback_feedback_tag
13 2026_04_27_030125_create_permission_tables Spatie: permissions, roles, model_has_roles, etc.
14 2026_04_27_080000_create_departments_table departments
15 2026_04_27_081000_add_department_to_feedback_table (modify feedback) add current_department_id, is_escalated
16 2026_04_27_082000_create_ticket_transfer_logs_table ticket_transfer_logs
17 2026_04_27_083000_add_collection_to_feedback_attachments_table (modify feedback_attachments) add uuid, disk, collection
18 2026_04_27_140000_fix_attachment_disk_default (modify feedback_attachments) fix disk default private→local
19 2026_04_27_150000_create_notifications_table notifications
20 2026_05_01_000000_create_contracts_table (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

Seeder (AfterSalesSeeder) — Data mẫu

  • 3 users (admin, manager, staff) + Spatie roles
  • 4 departments (CSKH: manager quản lý, Kỹ thuật: admin quản lý, Kế toán, Pháp lý)
  • 5 feedback channels (Email, Zalo, Phone, Document, In Person)
  • 5 products (Sunrise A1, A2, Green Valley Villa, Ocean Tower, Park Hill)
  • 4 customers
  • 5 customer-product assignments
  • 5 contracts (2 Sale, 2 Lease, 1 BCC) — NEW
  • 5 handovers (2 Đã BG, 1 Đủ ĐK, 1 Đã lên lịch, 1 Chưa đủ ĐK) — NEW
  • 5 product_services (2 Management Fee, 2 Gratitude, 1 Self Business) — NEW
  • 5 feedbacks (pending: 2, processing: 1, resolved: 1, closed: 1)
  • 5 feedback tags
  • 5 feedback interactions
  • 1 ticket_transfer_log
  • 3 feedback_attachments (2 proof_of_resolution, 1 customer_evidence)

---|--- | 22 | 2026_05_01_000003_create_product_services_table | (NEW) product_services |


Data Import (app:import-cskh) — NEW

Command: php artisan app:import-cskh {file_path}

  • Sử dụng spatie/simple-excel với Lazy Collection (chống OOM)
  • ProgressBar + chunk 100 rows/transaction
  • Pipeline mỗi dòng:
    1. firstOrCreate Product (Mã căn hộ) + Customer (Họ tên KH)
    2. Insert Contract, Handover, ProductService nếu có dữ liệu
    3. Tách Lịch Sử khách hàng → Feedback + FeedbackInteraction
    4. Auto-gắn active contract vào Feedback
  • Hỗ trợ cột: Mã căn hộ, Họ tên KH, BÀN GIÀO CĂN HỘ, TÌNH TRẠNG, HỢP ĐỒNG THUÊ, HĐ HỢP TÁC KINH DOANH, TỰ DOANH, Phí QL, Lịch Sử khách hàng

Test files: tests/Feature/ClosingPermissionTest.php, tests/Feature/TransferFlowTest.php

Test Coverage
ClosingPermissionTest (4 tests) staff close → 403, close without evidence → 422, close with evidence → OK, cross-department manager → 403
TransferFlowTest (2 tests) successful transfer, missing reason

Tổng: 8 tests, 21 assertions.


Các Pattern cần biết khi code

  1. Form components: Filament\Forms\Components\* (Select, TextInput, Textarea, Toggle, FileUpload, RichEditor)
  2. Schema/layout: Filament\Schemas\Components\* (Section, Schema)
  3. Service access: App::make(FileService::class) hoặc inject constructor
  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
  8. Navigation group icons: Filament 5 báo lỗi nếu cả navigation group VÀ child items đều có icons

File hướng dẫn liên quan

File Nội dung
AGENTS.md Hướng dẫn cho AI Agent: cách chạy, cấu trúc, workflow, RBAC, gotchas
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