Kiến trúc dữ liệu - Data Architecture
1. Tổng quan kiến trúc
1.1. Sơ đồ quan hệ chính
Contract (1) ──── (N) Scope ──── (N) Campaign Plan
│
├──── (N) Payment Milestone
│
├──── (N) Vendor Assignment ──── (N) Vendor Payment
│
└──── (N) Project Deliverable
1.2. Nguyên tắc thiết kế
- Contract-driven: Mọi thứ phải xuất phát từ hợp đồng
- Scope-centric: Scope là đơn vị tính lời/lỗ, nghiệm thu, và thanh toán
- Flexible attributes: Sử dụng JSON attributes cho các trường động
- Audit trail: Mọi thay đổi quan trọng đều được log
2. Entity Relationship Diagram (ERD)
2.1. Các thực thể chính
3. Database Schema
3.1. Contract (Hợp đồng)
Mục đích
Quản lý hợp đồng tổng thể với khách hàng
Schema
{
"contract_id": "string (PK)",
"contract_code": "string (unique)",
"client_id": "string (FK to Client)",
"client_name": "string",
"start_date": "date",
"end_date": "date",
"total_value": "decimal",
"total_margin_target": "decimal (%)",
"currency": "string (VND, USD, etc.)",
"status": "enum (draft, active, completed, cancelled)",
"contract_file_url": "string",
"notes": "text",
"created_by": "string",
"created_at": "timestamp",
"updated_at": "timestamp"
}
Ví dụ
{
"contract_id": "CNT-2026-001",
"contract_code": "KWP2026",
"client_id": "CLI-001",
"client_name": "Kewpie Vietnam",
"start_date": "2026-01-01",
"end_date": "2026-12-31",
"total_value": 2400000000,
"total_margin_target": 20,
"currency": "VND",
"status": "active",
"notes": "Hợp đồng performance marketing tổng thể"
}
Validation Rules
end_datephải saustart_datetotal_value> 0total_margin_target>= 0 và <= 100contract_codephải unique
3.2. Scope (Channel / Service Package)
Mục đích
Chia nhỏ hợp đồng thành các gói dịch vụ độc lập về ngân sách, KPI và nghiệm thu
Schema
{
"scope_id": "string (PK)",
"contract_id": "string (FK)",
"contract_code": "string",
"scope_code": "string (unique trong contract)",
"service_type": "enum (ads, web, app, seo, hosting, kol, branding, outsource)",
"channel": "string (FB, TikTok, Google, Website, Mobile, etc.)",
"name": "string",
"description": "text",
"budget": "decimal",
"revenue": "decimal",
"cost_estimated": "decimal",
"margin_target": "decimal (%)",
"kpi_type": "string (leads, sales, traffic, views, etc.)",
"kpi_target": "decimal",
"unit_price": "decimal",
"pricing_model": "enum (CPA, CPL, CPC, CPM, fixed_price, milestone)",
"start_date": "date",
"end_date": "date",
"status": "enum (pending, active, paused, completed, cancelled)",
"attributes": "json",
"created_at": "timestamp",
"updated_at": "timestamp"
}
Ví dụ - Ads Scope
{
"scope_id": "SCP-2026-001",
"contract_id": "CNT-2026-001",
"contract_code": "KWP2026",
"scope_code": "FB01",
"service_type": "ads",
"channel": "FB",
"name": "Facebook Lead Generation - Office Workers",
"budget": 800000000,
"revenue": 1000000000,
"cost_estimated": 800000000,
"margin_target": 25,
"kpi_type": "leads",
"kpi_target": 50000,
"unit_price": 20000,
"pricing_model": "CPL",
"start_date": "2026-01-01",
"end_date": "2026-06-30",
"status": "active",
"attributes": {
"target_audience": "Office workers 25-45",
"region": "HCM, HN",
"product": "Kewpie Sesame Dressing",
"campaign_type": "Always-on",
"break_even_cpl": 16000
}
}
Ví dụ - Web Scope
{
"scope_id": "SCP-2026-002",
"contract_id": "CNT-2026-001",
"contract_code": "KWP2026",
"scope_code": "WEB01",
"service_type": "web",
"channel": "Website",
"name": "Landing Page - Kewpie Product",
"budget": 0,
"revenue": 50000000,
"cost_estimated": 30000000,
"margin_target": 40,
"kpi_type": "deliverable",
"kpi_target": 1,
"pricing_model": "fixed_price",
"start_date": "2026-01-01",
"end_date": "2026-02-28",
"status": "active",
"attributes": {
"platform": "Next.js",
"cms": "MongoREST",
"language": "VN",
"hosting": "Cloudflare",
"delivery": "Source code + Deployment"
}
}
Validation Rules
revenue>=cost_estimated- Tổng
budgetcủa tất cả scope <=contract.total_value end_datephải nằm trong khoảngcontract.start_datevàcontract.end_datescope_codephải unique trong mỗi contract
3.3. Campaign Plan (Kế hoạch Campaign)
Mục đích
PM phân bổ ngân sách chi tiết cho từng campaign trong scope ads
Schema
{
"campaign_plan_id": "string (PK)",
"scope_id": "string (FK)",
"contract_code": "string",
"scope_code": "string",
"campaign_code": "string (unique)",
"campaign_name": "string",
"platform": "enum (FB, TikTok, Google, etc.)",
"objective": "string (Lead, Traffic, Conversion, etc.)",
"segment": "string (Office, Housewife, etc.)",
"phase": "string (P1, P2, etc.)",
"budget_allocated": "decimal",
"kpi_target": "decimal",
"kpi_type": "string",
"pricing_model": "enum (CPA, CPL, CPC, CPM)",
"break_even_cost": "decimal",
"profit_margin_target": "decimal (%)",
"start_date": "date",
"end_date": "date",
"daily_budget_cap": "decimal",
"status": "enum (pending, active, paused, completed)",
"created_at": "timestamp",
"updated_at": "timestamp"
}
Ví dụ
{
"campaign_plan_id": "CMP-PLN-001",
"scope_id": "SCP-2026-001",
"contract_code": "KWP2026",
"scope_code": "FB01",
"campaign_code": "Kewpie-KWP2026-FB01-FB-Lead-Office-P1",
"campaign_name": "Kewpie Office Lead Gen - Phase 1",
"platform": "FB",
"objective": "Lead",
"segment": "Office",
"phase": "P1",
"budget_allocated": 200000000,
"kpi_target": 12500,
"kpi_type": "leads",
"pricing_model": "CPL",
"break_even_cost": 16000,
"profit_margin_target": 25,
"start_date": "2026-01-01",
"end_date": "2026-03-31",
"daily_budget_cap": 2222222,
"status": "active"
}
Validation Rules
- Tổng
budget_allocatedcủa tất cả campaign trong scope <=scope.budget campaign_codephải follow naming conventionend_datephải nằm trong khoảngscope.start_datevàscope.end_date
3.4. Campaign Execution (Thực thi Campaign - từ BigQuery)
Mục đích
Dữ liệu thực tế từ ads platform, được mapping vào campaign plan
Schema
{
"execution_id": "string (PK)",
"date": "date",
"platform": "string",
"campaign_name": "string",
"campaign_code": "string (parsed)",
"scope_code": "string (parsed)",
"contract_code": "string (parsed)",
"impressions": "integer",
"clicks": "integer",
"spend": "decimal",
"conversions": "decimal",
"kpi_achieved": "decimal",
"ctr": "decimal",
"cpc": "decimal",
"cpa": "decimal",
"data_source": "enum (Meta, TikTok, Google)",
"synced_at": "timestamp"
}
Ví dụ
{
"execution_id": "EXE-20260115-001",
"date": "2026-01-15",
"platform": "FB",
"campaign_name": "Kewpie-KWP2026-FB01-FB-Lead-Office-P1",
"campaign_code": "Kewpie-KWP2026-FB01-FB-Lead-Office-P1",
"scope_code": "FB01",
"contract_code": "KWP2026",
"impressions": 150000,
"clicks": 4500,
"spend": 2100000,
"conversions": 135,
"kpi_achieved": 135,
"ctr": 3.0,
"cpc": 467,
"cpa": 15556,
"data_source": "Meta",
"synced_at": "2026-01-16T02:00:00Z"
}
3.5. Payment Milestone (Mốc thanh toán)
Mục đích
Quản lý các đợt nghiệm thu và thanh toán cho mỗi scope
Schema
{
"milestone_id": "string (PK)",
"scope_id": "string (FK)",
"contract_code": "string",
"scope_code": "string",
"name": "string (Phase 1, Phase 2, etc.)",
"description": "text",
"due_date": "date",
"invoice_date": "date",
"payment_due_date": "date",
"amount": "decimal",
"kpi_required": "decimal",
"deliverable": "text",
"acceptance_criteria": "text",
"status": "enum (pending, ready_to_invoice, invoiced, paid)",
"invoice_number": "string",
"payment_received_date": "date",
"notes": "text",
"created_at": "timestamp",
"updated_at": "timestamp"
}
Ví dụ
{
"milestone_id": "MLS-001",
"scope_id": "SCP-2026-001",
"contract_code": "KWP2026",
"scope_code": "FB01",
"name": "Phase 1 - Q1 2026",
"due_date": "2026-03-31",
"invoice_date": "2026-04-05",
"payment_due_date": "2026-04-20",
"amount": 300000000,
"kpi_required": 15000,
"deliverable": "15,000 leads from Facebook Ads",
"acceptance_criteria": "Valid leads with complete contact information",
"status": "pending",
"notes": "Customer expects report by April 3rd"
}
Validation Rules
- Tổng
amountcủa tất cả milestone trong scope =scope.revenue due_date<=scope.end_date- Status transition: pending → ready_to_invoice → invoiced → paid
3.6. Vendor (Nhà thầu phụ)
Mục đích
Quản lý thông tin các vendor/freelancer/đối tác
Schema
{
"vendor_id": "string (PK)",
"vendor_code": "string (unique)",
"name": "string",
"type": "enum (agency, freelancer, studio, infrastructure)",
"services": "array[string]",
"contact_person": "string",
"email": "string",
"phone": "string",
"address": "text",
"tax_code": "string",
"payment_terms": "string",
"bank_account": "string",
"rating": "decimal (1-5)",
"status": "enum (active, inactive)",
"notes": "text",
"created_at": "timestamp",
"updated_at": "timestamp"
}
Ví dụ
{
"vendor_id": "VND-001",
"vendor_code": "ABC-DEV",
"name": "ABC Development Agency",
"type": "agency",
"services": ["web", "app", "ui-design"],
"contact_person": "Nguyen Van A",
"email": "[email protected]",
"phone": "+84901234567",
"tax_code": "0123456789",
"payment_terms": "Net 30",
"rating": 4.5,
"status": "active"
}
3.7. Vendor Assignment (Phân công Vendor)
Mục đích
Gán vendor vào scope cụ thể với chi phí và scope of work
Schema
{
"assignment_id": "string (PK)",
"scope_id": "string (FK)",
"vendor_id": "string (FK)",
"role": "string (UI Designer, Backend Dev, etc.)",
"description": "text",
"cost": "decimal",
"start_date": "date",
"end_date": "date",
"deliverable": "text",
"status": "enum (pending, active, completed, cancelled)",
"created_at": "timestamp",
"updated_at": "timestamp"
}
Ví dụ
{
"assignment_id": "ASG-001",
"scope_id": "SCP-2026-002",
"vendor_id": "VND-001",
"role": "Full-stack Development",
"description": "Develop landing page with Next.js",
"cost": 25000000,
"start_date": "2026-01-01",
"end_date": "2026-02-15",
"deliverable": "Responsive landing page with CMS integration",
"status": "active"
}
3.8. Vendor Payment (Thanh toán Vendor)
Mục đích
Quản lý các đợt thanh toán cho vendor
Schema
{
"payment_id": "string (PK)",
"assignment_id": "string (FK)",
"vendor_id": "string (FK)",
"amount": "decimal",
"due_date": "date",
"payment_date": "date",
"payment_method": "enum (bank_transfer, cash, etc.)",
"status": "enum (pending, paid, overdue)",
"invoice_number": "string",
"notes": "text",
"created_at": "timestamp",
"updated_at": "timestamp"
}
Ví dụ
{
"payment_id": "VPY-001",
"assignment_id": "ASG-001",
"vendor_id": "VND-001",
"amount": 12500000,
"due_date": "2026-02-01",
"payment_date": null,
"payment_method": "bank_transfer",
"status": "pending",
"notes": "50% deposit"
}
4. Computed Views & Reports
4.1. Project Finance View
Aggregate view cho từng scope
SELECT
scope_id,
contract_code,
scope_code,
service_type,
-- Budget
budget AS budget_allocated,
SUM(campaign_execution.spend) AS spend_to_date,
budget - SUM(campaign_execution.spend) AS budget_remaining,
(SUM(campaign_execution.spend) / budget * 100) AS budget_usage_pct,
-- KPI
kpi_target,
SUM(campaign_execution.kpi_achieved) AS kpi_achieved,
kpi_target - SUM(campaign_execution.kpi_achieved) AS kpi_remaining,
(SUM(campaign_execution.kpi_achieved) / kpi_target * 100) AS kpi_completion_pct,
-- Profit
revenue,
(cost_estimated + SUM(vendor_payment.amount)) AS actual_cost,
revenue - (cost_estimated + SUM(vendor_payment.amount)) AS profit,
((revenue - cost_estimated) / revenue * 100) AS margin_realized
FROM scope
LEFT JOIN campaign_execution ON scope.scope_code = campaign_execution.scope_code
LEFT JOIN vendor_assignment ON scope.scope_id = vendor_assignment.scope_id
LEFT JOIN vendor_payment ON vendor_assignment.assignment_id = vendor_payment.assignment_id
GROUP BY scope_id
4.2. Cashflow Forecast View
Dự báo dòng tiền vào/ra
-- Dòng tiền ra (outgoing)
SELECT
DATE_TRUNC('month', date) AS month,
SUM(budget_allocated) AS ads_spend_forecast,
SUM(vendor_payment.amount) AS vendor_cost,
SUM(budget_allocated) + SUM(vendor_payment.amount) AS total_outgoing
FROM campaign_plan
LEFT JOIN vendor_payment ON campaign_plan.scope_id = vendor_payment.scope_id
WHERE date \>= CURRENT_DATE
GROUP BY month
ORDER BY month
-- Dòng tiền vào (incoming)
SELECT
DATE_TRUNC('month', due_date) AS month,
SUM(amount) AS revenue_forecast,
SUM(CASE WHEN status = 'invoiced' THEN amount ELSE 0 END) AS invoiced,
SUM(CASE WHEN status = 'paid' THEN amount ELSE 0 END) AS paid
FROM payment_milestone
WHERE due_date \>= CURRENT_DATE
GROUP BY month
ORDER BY month
5. Indexes và Performance
5.1. Recommended Indexes
-- Contract
CREATE INDEX idx_contract_code ON contract(contract_code);
CREATE INDEX idx_contract_status ON contract(status);
-- Scope
CREATE INDEX idx_scope_contract ON scope(contract_id, contract_code);
CREATE INDEX idx_scope_code ON scope(contract_code, scope_code);
CREATE INDEX idx_scope_status ON scope(status);
-- Campaign Plan
CREATE INDEX idx_campaign_scope ON campaign_plan(scope_id);
CREATE INDEX idx_campaign_code ON campaign_plan(campaign_code);
-- Campaign Execution
CREATE INDEX idx_execution_date ON campaign_execution(date);
CREATE INDEX idx_execution_campaign ON campaign_execution(campaign_code);
CREATE INDEX idx_execution_scope ON campaign_execution(scope_code, contract_code);
-- Payment Milestone
CREATE INDEX idx_milestone_scope ON payment_milestone(scope_id);
CREATE INDEX idx_milestone_status ON payment_milestone(status, due_date);
-- Vendor Assignment
CREATE INDEX idx_assignment_scope ON vendor_assignment(scope_id);
CREATE INDEX idx_assignment_vendor ON vendor_assignment(vendor_id);
6. Data Integrity Rules
6.1. Referential Integrity
- Scope CANNOT be deleted if có Campaign Plan hoặc Payment Milestone
- Contract CANNOT be deleted if có Scope
- Vendor CANNOT be deleted if có active Vendor Assignment
6.2. Business Logic Constraints
- Tổng budget của Scope <= Contract total_value
- Tổng revenue của Milestone = Scope revenue
- Vendor Payment amount <= Vendor Assignment cost
- Campaign Plan budget <= Scope budget
6.3. Audit Trail
Các bảng quan trọng cần có audit log:
- Contract
- Scope
- Payment Milestone
- Vendor Payment
Audit log schema:
{
"audit_id": "string",
"table_name": "string",
"record_id": "string",
"action": "enum (create, update, delete)",
"old_value": "json",
"new_value": "json",
"changed_by": "string",
"changed_at": "timestamp"
}
7. Data Migration Strategy
7.1. Initial Data Load
- Import existing contracts
- Create scopes for each contract
- Import historical campaign data from BigQuery
- Map campaign data to scopes
- Create payment milestones based on contract terms
7.2. Ongoing Sync
- BigQuery sync: Every 4 hours
- Meta Ads API: Real-time via webhook (if available) or hourly
- Manual data entry: Real-time via UI
8. Backup và Recovery
8.1. Backup Strategy
- Full backup: Daily at 2 AM
- Incremental backup: Every 6 hours
- Transaction log: Continuous
- Retention: 30 days rolling
8.2. Recovery Point Objective (RPO)
- Maximum acceptable data loss: 1 hour
- Recovery Time Objective (RTO): 4 hours