Domain Model
Cả hai entity được định nghĩa trong
@nx/coredưới schema PostgreSQLoutreachvà re-export vào package này. Không có khóa ngoại giữa chúng — mỗi cái là một bảng thu thập độc lập.
1. ERD đầy đủ
2. Entities
Inquiry
| Thuộc tính | Giá trị |
|---|---|
| Bảng | outreach.Inquiry |
| Nguồn | packages/core/src/models/schemas/outreach/inquiry/schema.ts |
| Soft-delete | có (deletedAt) |
| Cột Owner ID | — (global, không scope theo merchant) |
| Thuộc tính ẩn | deletedAt |
Trường:
| Trường | Kiểu | Bắt buộc | Mặc định | Mô tả |
|---|---|---|---|---|
id | text | ✓ | Snowflake | Khóa chính |
type | text | ✓ | 000_CONSULT | Channel nguồn — xem enum |
status | text | ✓ | NEW | Trạng thái pipeline — xem enum |
firstName | text | ✓ | — | Tên liên hệ |
lastName | text | — | Họ liên hệ | |
email | text | ✓ | — | Email liên hệ (indexed) |
phone | text | ✓ | — | SĐT liên hệ |
businessName | text | — | Doanh nghiệp của lead | |
businessType | text | — | Danh mục doanh nghiệp | |
locationCount | text | — | Đánh giá lead | |
estimatedRevenue | text | — | Đánh giá lead | |
subject | text | — | Chủ đề tin nhắn | |
message | text | — | Nội dung free-text | |
assignedTo | text | — | id admin/user sở hữu | |
repliedAt | timestamptz | — | Timestamp reply đầu tiên | |
repliedBy | text | — | id user reply | |
convertedAt | timestamptz | — | Timestamp conversion | |
lostReason | text | — | Lý do mất deal | |
note | text | — | Ghi chú nội bộ | |
createdAt | timestamptz | ✓ | now() | — |
Enum type (InquiryTypes):
| Giá trị | Mô tả |
|---|---|
000_CONSULT | Tư vấn chung / nguồn không rõ (mặc định) |
100_CONTACT | Form liên hệ (/contact) |
200_SALES | Yêu cầu sales (/contact-sales) |
300_DEMO | Yêu cầu demo (/demo) |
400_PARTNER | Đăng ký partner (/partners) |
Enum status (InquiryStatuses, IGNIS Statuses):
| Giá trị | Mô tả |
|---|---|
NEW | Vừa submit |
PROCESSING | Admin đã tiếp nhận |
COMPLETED | Converted |
CLOSED | Giải quyết không convert |
CANCELLED | Spam / invalid / abandon |
Index & constraint:
| Tên | Cột | Loại |
|---|---|---|
PK_Inquiry | id | Khóa chính |
IDX_Inquiry_type | type | Btree |
IDX_Inquiry_status | status | Btree |
IDX_Inquiry_email | email | Btree |
Quan hệ: không có.
Subscriber
| Thuộc tính | Giá trị |
|---|---|
| Bảng | outreach.Subscriber |
| Nguồn | packages/core/src/models/schemas/outreach/subscriber/schema.ts |
| Soft-delete | có (deletedAt) |
| Cột Owner ID | — (global) |
| Thuộc tính ẩn | deletedAt, unsubscribeToken (không bao giờ trả về bởi model API) |
Trường:
| Trường | Kiểu | Bắt buộc | Mặc định | Mô tả |
|---|---|---|---|---|
id | text | ✓ | Snowflake | Khóa chính |
email | text | ✓ | — | Unique (partial, where not deleted) |
locale | text | ✓ | vi | Ngôn ngữ newsletter (vi / en) |
topics | jsonb (string[]) | ✓ | ['all'] | Tùy chọn chủ đề |
status | text | ✓ | ACTIVATED | Xem enum |
unsubscribeToken | text | ✓ | Snowflake ($defaultFn) | Token cho link unsubscribe; ẩn khỏi API |
subscribedAt | timestamptz | ✓ | now() | Thời điểm subscribe đầu/cuối |
unsubscribedAt | timestamptz | — | Set khi unsubscribe, clear khi re-subscribe | |
createdAt | timestamptz | ✓ | now() | — |
Enum status (SubscriberStatuses, IGNIS Statuses):
| Giá trị | Mô tả |
|---|---|
ACTIVATED | Subscription đang active |
DEACTIVATED | Đã unsubscribe |
ARCHIVED | Admin lưu trữ (status hợp lệ, không tạo ra bởi luồng service) |
Index & constraint:
| Tên | Cột | Loại |
|---|---|---|
PK_Subscriber | id | Khóa chính |
UPI_Subscriber_email | email (where deletedAt IS NULL) | Unique partial index |
IDX_Subscriber_status | status | Btree |
IDX_Subscriber_unsubscribeToken | unsubscribeToken | Btree |
Quan hệ: không có.
3. Bất biến xuyên entity
| Bất biến | Cách thực thi |
|---|---|
Một Subscriber live cho mỗi email | Unique partial index UPI_Subscriber_email (where deletedAt IS NULL) + SubscriberService.subscribe() find-then-reactivate |
unsubscribeToken đủ unique để không đoán được | Snowflake $defaultFn (64-bit, worker 10) — xem ADR-0001 |
Re-subscribe clear unsubscribedAt | SubscriberService.subscribe() set unsubscribedAt: null khi reactivate |
4. Hành vi Soft-delete
| Hành vi | Chi tiết |
|---|---|
| Đọc mặc định | deletedAt IS NULL (model defaultFilter) |
| Hard-delete | Không bao giờ theo mặc định; CRUD admin deleteById thực hiện soft-delete |
| Restore | Không expose qua API |
| Email uniqueness | Chỉ thực thi trên các dòng không-xóa (partial unique index) |