22 KiB
22 KiB
HQLAND - WORKFLOW TOÀN HỆ THỐNG
Tài liệu này mô tả chi tiết luồng dữ liệu, nghiệp vụ và tương tác giữa các module trong HQLand. Cập nhật: 29/04/2026
I. TỔNG QUAN KIẾN TRÚC
┌─────────────────────────────────────────────────────────────────────────────┐
│ FILAMENT ADMIN PANEL v5.5 │
│ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ ┌──────────────────┐ │
│ │ Warehouse │ │ CRM │ │ Contracts │ │ Finance │ │
│ │ (Kho hàng) │ │ (Khách hàng)│ │ (Hợp đồng) │ │ (Thu tiền/Báo │ │
│ │ │ │ │ │ │ │ cáo) │ │
│ └──────┬───────┘ └──────┬───────┘ └──────┬───────┘ └────────┬─────────┘ │
│ │ │ │ │ │
│ ┌──────┴───────┐ ┌──────┴───────┐ ┌──────┴───────┐ ┌───────┴─────────┐ │
│ │ Project │ │ Customer │ │ Contract │ │ Payment │ │
│ │ Product │ │ (INDIVIDUAL│ │ Appendix │ │ PaymentFine │ │
│ │ │ │ /COMPANY) │ │ Settlement │ │ Settlement │ │
│ └──────────────┘ └──────────────┘ └──────────────┘ └─────────────────┘ │
│ │
│ ┌─────────────────────────────────────────────────────────────────────────┐│
│ │ SUPPORT MODULES ││
│ │ PaymentTemplate → PaymentSchedule → PaymentScheduleItem ││
│ │ SalesPhase → SalesPhaseProduct (Pivot) ││
│ │ FormTemplate → FormField → FormPrintLog ││
│ │ MailMergeService | DiscountEngine | PriceCalculationService ││
│ └─────────────────────────────────────────────────────────────────────────┘│
└─────────────────────────────────────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────────────────────┐
│ POSTGRESQL (UUID Primary Keys) │
│ 120 Customers | 45 Products | 139 Contracts | Notifications Table │
└─────────────────────────────────────────────────────────────────────────────┘
Tech Stack
- Framework: Laravel 13.x + PHP 8.3
- Admin Panel: Filament v5.5 (Schemas Architecture)
- Database: PostgreSQL + UUID (100% tables)
- Excel: PhpSpreadsheet 5.7
- Testing: Pest PHP 4.6
II. LUỒNG DỮ LIỆU TỔNG THỂ (END-TO-END)
1. Khởi tạo Dự án & Kho hàng
Project (Hà Quang 1)
├── PaymentTemplate (Mẫu lịch thanh toán mặc định)
│ └── PaymentScheduleItem x N đợt (% + ngày đến hạn)
└── Product x N (Lô đất/Căn hộ)
├── Giá gốc (qsdd_value, foundation_temp_value, total_price)
├── Hạ tầng (infrastructure_status - JSONB)
└── Custom data (block, building_density, legal_status)
2. Khởi tạo Khách hàng
Customer (INDIVIDUAL hoặc COMPANY)
├── INDIVIDUAL: full_name, cmnd_cccd, phone, addresses...
└── COMPANY: full_name (tên cty), tax_code, representative_id
└── representative → Customer (INDIVIDUAL)
3. Luồng Bán hàng chuẩn
Bước 1: Tạo Đợt mở bán (SalesPhase)
├── Chọn Project
├── Chọn PaymentTemplate (mẫu lịch thanh toán)
├── Định chính sách chiết khấu mặc định (discount_policy JSONB)
└── Thêm sản phẩm vào đợt (SalesPhaseProduct pivot)
├── Giá riêng cho đợt (sale_price/land_value/foundation_value)
├── Chiết khấu riêng (discount_details JSONB)
└── Status: Còn hàng | Đã giữ | Đã bán | Khóa
Bước 2: Tạo Hợp đồng (Contract)
├── Chọn Product (Lô đất)
├── [Optional] Chọn SalesPhase → Auto populate giá từ SalesPhaseProduct
├── Nhập giá trị tài chính:
│ land_value + foundation_value = total_value (auto)
├── Chiết khấu (discount_details JSONB)
├── Chọn Khách hàng (belongsToMany qua contract_customers)
├── Chọn PaymentTemplate (để tạo lịch thanh toán sau khi lưu)
└── [Auto] Khi save:
├── Booted: total_value = land_value + foundation_value
├── Saved: PriceCalculationService tạo calculation_log
└── AfterCreate: ContractScheduleService tạo PaymentSchedule
Bước 3: PaymentSchedule tự động tạo
├── Contract → PaymentSchedule (1-1)
└── PaymentSchedule → PaymentScheduleItem x N
├── Mỗi item = 1 đợt thanh toán
├── amount = total_value × (percentage / 100)
├── due_date tính từ signing_date + days_after_signing
└── Sắp xếp theo installment_no
Bước 4: Thu tiền (Payment)
├── Chọn Contract
├── [Optional] Chọn PaymentScheduleItem (đợt thanh toán)
│ Nếu không chọn = Tạm ứng / không đối soát đợt
├── Nhập amount, paid_date, method, receipt_number
├── [Validation] amount không vượt quá công nợ đợt / công nợ HĐ
└── [Auto - PaymentObserver] Khi Payment created/updated/deleted:
├── recalculateContract: SUM payments → paid_amount
│ remaining_amount = total_value - paid_amount
│ excess_amount = paid_amount - total_value (nếu > 0)
└── applySurplusToNextInstallment:
Nếu excess_amount > 0 → Auto tạo Payment mới cho đợt tiếp theo
4. In ấn (Form Templates)
FormTemplate (Admin tạo mẫu)
├── HTML content với placeholder {{ma_truong}}
└── FormField x N:
├── db_column: Lấy trực tiếp cột từ Model
├── db_relation: Lấy qua relation (customers.0.full_name)
├── formula: Tính toán (dùng Shunting Yard + bcmath)
├── input: Nhập tay khi in
└── static: Giá trị cố định
Khi in:
MailMergeService::render(template, record)
├── evaluateFields() → Tính giá trị tất cả fields
├── Thay placeholder → rendered HTML
└── savePrintLog() → Lưu snapshot + rendered HTML + user + timestamp
III. CHI TIẾT MODULE
3.1. WAREHOUSE (Kho hàng)
Models
| Model | Key Fields | Quan hệ |
|---|---|---|
| Project | code, name, type, address, payment_template_id | hasMany Products, hasMany SalesPhases, hasMany PaymentTemplates |
| Product | code, project_id, product_type (LAND/APARTMENT), area, price_per_unit, total_price, qsdd_value, foundation_temp_value, contract_temp_value, infrastructure_status (JSONB), custom_data (JSONB), status | belongsTo Project, hasMany Contracts, belongsToMany SalesPhases |
Nghiệp vụ
-
Import từ Excel (
import:products-excel):- File:
sanpham.xlsx - Parse hạ tầng từ chuỗi "Key: Value - Key2: Value2" →
infrastructure_statusJSONB - Parse custom_data: block, building_density, legal_status_raw
updateOrCreatetheocode + project_id
- File:
-
Tìm đợt mở bán đang active (
Product::activeSalesPhase()):- SalesPhase.status = "Đang mở bán"
- SalesPhaseProduct.status = "Còn hàng"
- Ngày hiện tại nằm trong [start_date, end_date]
3.2. CRM (Khách hàng)
Models
| Model | Key Fields | Quan hệ |
|---|---|---|
| Customer | type (INDIVIDUAL/COMPANY), full_name, cmnd_cccd, tax_code, title, phone, secondary_phones (JSONB), email, dob, permanent_address, contact_address, id_issue_date, id_issue_place, representative_id | belongsToMany Contracts (via contract_customers), representative → Customer, representedCompanies → Customer[] |
Nghiệp vụ
-
Import từ Excel (
import:customers-excel):- File:
khachhang.xlsx - Tách nhiều số điện thoại (dấu phẩy, gạch chéo, xuống dòng) →
phone+secondary_phones - Parse ngày Excel serial number → Carbon
- Tạo mẫu: Công ty TNHH BĐS Thịnh Vượng + Ngườ đại diện
- File:
-
Form tạo/sửa (CustomerForm):
- Chuyển đổi động INDIVIDUAL/COMPANY (live)
- INDIVIDUAL: Hiện dob, id_issue_date, id_issue_place
- COMPANY: Hiện tax_code, representative_id (chọn từ danh sách cá nhân)
- Copy địa chỉ thường trú → liên hệ (suffixAction)
3.3. CONTRACTS (Hợp đồng)
Models
| Model | Key Fields | Quan hệ |
|---|---|---|
| Contract | contract_number, contract_type (HĐMB/HĐGV/HĐDC), product_id, status, signing_date, sale_date, hql_confirmation_date, land_value, foundation_value, total_value, total_value_with_foundation, paid_amount, remaining_amount, excess_amount, discount_details (JSONB), calculation_log (JSONB), brokerage_name, stored_contract_count, filing_note, transfer_order, payment_template_id, sales_phase_id | belongsTo Product, belongsToMany Customers (via contract_customers + pivot role/transfer_order), belongsTo PaymentTemplate, belongsTo SalesPhase, hasOne PaymentSchedule, hasManyThrough PaymentScheduleItem, hasMany Payments, hasMany PaymentFines, hasMany Appendices |
Nghiệp vụ
A. Tạo hợp đồng mới (CreateContract)
1. Chọn Product → Auto điền:
- total_value = product.total_price
- land_value = product.qsdd_value
- foundation_value = product.foundation_temp_value
2. [Optional] Chọn SalesPhase → Auto điền từ SalesPhaseProduct:
- land_value, foundation_value, sale_price
- Nếu có sale_price → total_value = sale_price
- discount_details từ pivot
3. Nhập tay land_value, foundation_value → total_value auto cập nhật
4. Nhập discount_details (Key-Value) + xem tổng quan chiết khấu
5. Chọn Customers (multiple), nhập các thông tin quản lý
6. Chọn payment_template_id → [Auto] Sau khi lưu tạo PaymentSchedule
B. Booted Logic (Model Contract)
static::saving:
land_value + foundation_value → total_value
Nếu tạo mới và chưa có giá → fallback lấy product.total_price
remaining_amount = total_value - paid_amount
static::saved:
Nếu có land_value/foundation_value:
PriceCalculationService::calculateForContract() → calculation_log
Cập nhật calculation_log (steps, final_values, price_sheet)
C. Calculation Pipeline
Input: land_value, foundation_value, discount_details, vat_rate
Step 1: Giá trị QSDĐ → land_value
Step 2: Giá trị Móng → foundation_value
Step 3: Subtotal = land_value + foundation_value
Step 4: Chiết khấu = total_amount HOẶC total_percentage × subtotal
Step 5: Net value = subtotal - discount
Step 6: VAT = net_value × vat_rate%
Step 7: Total payment = net_value + VAT
Output: calculation_log lưu snapshot tất cả bước
D. Lịch thanh toán (ContractScheduleService)
generateFromTemplate(contract, template):
DB::transaction:
Xóa lịch cũ (nếu có)
Tạo PaymentSchedule
Duyệt template.items theo installment_no:
due_date = signing_date + days_after_signing
amount = contract.total_value × (percentage / 100)
Tạo PaymentScheduleItem
E. Các module phụ
- Appendix (Phụ lục): belongsTo Contract + Product, custom_data JSONB
- Settlement (Quyết toán): belongsTo Product, temp_value, final_value, difference
- PaymentFine (Tiền phạt): belongsTo Contract, amount, due_date, paid_date
3.4. FINANCE (Tài chính & Thu tiền)
Models
| Model | Key Fields | Quan hệ |
|---|---|---|
| Payment | contract_id, schedule_item_id, amount, paid_date, method, receipt_number, metadata (JSONB) | belongsTo Contract, belongsTo PaymentScheduleItem |
| PaymentSchedule | contract_id, template_id | belongsTo Contract, belongsTo PaymentTemplate, hasMany Items |
| PaymentScheduleItem | schedule_id, installment_no, type (PaymentType enum), percentage, amount, due_date, days_after_signing, days_after_previous | belongsTo PaymentSchedule, hasMany Payments |
PaymentType Enum
| Value | Label |
|---|---|
| QSDD | Tiền QSDĐ |
| MONG | Tiền Móng |
| THAN | Tiền Thân |
| CHI_PHI_TC | Chi phí thi công |
| CK | Chiết khấu |
| PHAT | Tiền phạt |
| OTHER | Khác |
Nghiệp vụ
A. Thu tiền (PaymentForm)
1. Chọn Contract → Cascade: Đợt thanh toán lọc theo contract
2. Chọn Đợt TT (optional)
- Để trống = Tạm ứng / Không đối soát đợt
3. Nhập amount
- Helper text hiển thị công nợ đợt / công nợ HĐ
- Validation: Không cho phép thu quá công nợ
4. Nhập paid_date, method, receipt_number
5. Lưu
B. PaymentObserver (Tự động)
created/updated/deleted:
├── recalculateContract(contract):
│ totalPaid = SUM(payments.amount)
│ paid_amount = totalPaid
│ Nếu totalPaid > total_value:
│ remaining_amount = 0
│ excess_amount = totalPaid - total_value
│ Ngược lại:
│ remaining_amount = total_value - totalPaid
│ excess_amount = 0
│ saveQuietly()
└── applySurplusToNextInstallment(contract):
Nếu excess_amount > 0:
Tìm đợt tiếp theo chưa thanh toán đủ (theo installment_no)
applyAmount = min(excess, remaining_for_item)
Tạo Payment auto:
method = "Tự động khấu trừ"
receipt_number = "AUTO-SURPLUS-..."
metadata = {auto_surplus: true}
C. Bảng thu tiền (PaymentsTable)
- Cột: Hợp đồng, Số tiền, Ngày thu, Phương thức, Số phiếu thu
- Cột mở rộng: Loại đợt, Đợt TT, Trạng thái đối soát (Đủ/Thiếu/Thừa/Tạm ứng), Còn thiếu
- Filter: Phương thức, Ngày thu (range)
- Eager load:
scheduleItem.payments
3.5. SALES PHASES (Đợt mở bán)
Models
| Model | Key Fields | Quan hệ |
|---|---|---|
| SalesPhase | project_id, name, code, status (Chuẩn bị/Đang mở bán/Tạm dừng/Đã đóng), start_date, end_date, description, payment_template_id, discount_policy (JSONB) | belongsTo Project, belongsTo PaymentTemplate, hasMany phaseProducts, belongsToMany Products |
| SalesPhaseProduct (Pivot) | sales_phase_id, product_id, sale_price, land_value, foundation_value, discount_details (JSONB), status (Còn hàng/Đã giữ/Đã bán/Khóa) | belongsTo SalesPhase, belongsTo Product |
Nghiệp vụ
-
Tạo đợt mở bán:
- Thông tin cơ bản (name, code, status, dates)
- Chính sách & Mẫu thanh toán
- Danh sách sản phẩm (Repeater) với giá riêng và chiết khấu riêng
-
Tích hợp vào ContractForm:
- Khi chọn
sales_phase_id+product_id→ Auto populate giá từ pivot - CreateContract: Nếu không chọn payment_template_id trực tiếp → fallback lấy từ
salesPhase->paymentTemplate
- Khi chọn
3.6. FORM TEMPLATES (Biểu mẫu in ấn)
Models
| Model | Key Fields | Quan hệ |
|---|---|---|
| FormTemplate | name, code, target_model (Contract/Product/Customer), html_template, paper_size (A4/A5/Letter), is_active | hasMany fields, hasMany printLogs |
| FormField | template_id, code, label, source_type (db_column/db_relation/formula/input/static), source_config (JSONB), format (text/number/currency/date/percent), decimal_places, display_order | belongsTo template |
| FormPrintLog | template_id, target_model, target_id, target_number, snapshot_data (JSONB), rendered_html, printed_by, printed_at | belongsTo template, belongsTo printedBy (User) |
MailMergeService Workflow
evaluateFields(template, record):
foreach field:
db_column → record->{column}
db_relation → record->{relation}[index]->{column}
formula → Shunting Yard evaluate (bcmath)
input → default value
static → fixed value
render(template, record):
evaluateFields() → raw_values
formatValue() theo format/currency/date/percent
str_replace placeholder {{code}} → value trong html_template
savePrintLog():
Lưu snapshot_data + rendered_html + user + timestamp
IV. DASHBOARD & BÁO CÁO
Widgets
| Widget | Dữ liệu |
|---|---|
| RecentNotifications | Thông báo chưa đọc của user hiện tại (database notifications) |
| ContractStatsOverview | Tổng doanh thu, Đã thu, Công nợ phải thu, HĐ hiệu lực, Đợt TT sắp đến hạn |
| UpcomingPaymentsTable | PaymentScheduleItem trong 30 ngày tới (có đủ công nợ) |
Pages
| Page | Chức năng |
|---|---|
| ProjectReport | Bảng thống kê theo dự án: Tổng SP, Đã bán, Số HĐ, Tổng giá trị, Đã thu, Công nợ |
Commands
| Command | Chức năng |
|---|---|
export:debt-report |
Xuất Excel 2 sheet: Tổng hợp công nợ + Chi tiết đợt TT |
notifications:send-due-payments |
Gửi cảnh báo đợt TT sắp đến hạn cho tất cả users (database notification) |
contracts:generate-schedules |
Tạo lịch thanh toán hàng loạt cho HĐ chưa có lịch |
V. CÁC COMMAND IMPORT DỮ LIỆU
import:products-excel {file=sanpham.xlsx}
- Input: File Excel sản phẩm
- Output: Product records trong Project "Hà Quang 1"
- Logic:
updateOrCreatetheo code, parse infrastructure_status, parse custom_data
import:customers-excel {file=khachhang.xlsx}
- Input: File Excel khách hàng
- Output: Customer records (INDIVIDUAL) + Mẫu Công ty
- Logic: Tách nhiều phone, parse Excel date serial
import:contracts-complex {hopdong=hopdong.xlsx} {hdkh=Hd_kh.xlsx}
- Input: 2 file (tài chính + liên kết KH)
- Logic "Bắc cầu":
- Đọc hopdong.xlsx → Map
contract_number → finance_data - Đọc Hd_kh.xlsx → Tìm product theo plotCode, customer theo cmnd_cccd
- Tìm mapping:
str_contains(contract_number, plotCode) updateOrCreateContract +syncWithoutDetachingcustomers qua pivot
- Đọc hopdong.xlsx → Map
- Transaction bọc toàn bộ
contracts:generate-schedules {--force}
- Input: HĐ chưa có PaymentSchedule (hoặc --force để tạo lại)
- Logic: Ưu tiên
contract.paymentTemplate, fallbackproduct.project.paymentTemplate
VI. MÔ HÌNH QUAN HỆ DATABASE (ER Tóm tắt)
Project 1───* Product 1───* Contract *───* Customer
│ │ │
│ │ 1───1 PaymentSchedule 1───* PaymentScheduleItem
│ │ │ │
│ │ *───* Payment *───────────┘
│ │ │
│ │ *───* Appendix
│ │ *───* PaymentFine
│ │ *───* Settlement (qua Product)
│ │
│ *───* SalesPhase *───* SalesPhaseProduct *───1 Product
│ │
│ 1───1 PaymentTemplate 1───* PaymentScheduleItem (template)
│
*───* PaymentTemplate
FormTemplate 1───* FormField
1───* FormPrintLog
User 1───* Notification (MorphMany)
VII. ĐIỂM CẦN XEM XÉT (TỪ ASSESSMENT)
| # | Vấn đề | Mức độ | Ghi chú |
|---|---|---|---|
| 1 | 🟢 Đã xong | Contract, Payment, Customer có SoftDeletes + Restore/ForceDelete UI | |
| 2 | Phân quyền | 🟡 Trung bình | Chỉ 1 loại user. Ai cũng xóa/sửa được mọi thứ |
| 3 | 🟢 Đã xong | Đã thêm collected_by (FK → users) + hiển thị Form/Table | |
| 4 | Sổ quỹ | 🟡 Trung bình | Thu tiền nhưng không ghi vào quỹ TM/NH. Không đối soát được |
| 5 | CRM Pipeline | 🟢 Thấp | Chưa quản lý Lead/Khách hàng tiềm năng |
| 6 | Báo cáo BCTC | 🟢 Thấp | Chưa có báo cáo theo chuẩn kế toán VN |
File này cần được cập nhật mỗi khi có thay đổi lớn trong kiến trúc hoặc nghiệp vụ.