01 · Kiến trúc
Các thành phần và vì sao chọn
Mọi thứ chạy trên một máy chủ ảo Google Cloud ở Singapore (asia-southeast1). Không có thành phần nào lưu dữ liệu ngoài vùng này, trừ trợ lý AI đọc theo yêu cầu (pháp chế đã cho phép).
| Lớp | Chọn | Ghi chú |
|---|---|---|
| Ứng dụng | Odoo Community 20 + module abb_deal | Mã nguồn mở; module riêng kế thừa, không sửa lõi Odoo; 121–123 bài test tự động |
| Cơ sở dữ liệu | PostgreSQL 16 | Image ghim theo digest, không dùng tag trôi |
| Cửa vào | nginx + chứng chỉ Let's Encrypt | Chỉ mở giao diện web; API JSON-2 và trang tài liệu API bị chặn từ internet |
| Bí mật | Google Secret Manager | Nạp vào bộ nhớ tạm (tmpfs) khi khởi động, không nằm trên đĩa |
| Sao lưu | Backup 02:00 mỗi đêm ra Cloud Storage + snapshot đĩa | Khôi phục thử trên DB tên khác; cảnh báo email khi backup lỗi hoặc quá hạn |
| Giám sát | Cloud Logging + Monitoring | Uptime web, RAM, backup, DWH — mỗi loại một chính sách cảnh báo email |
| Kho dữ liệu | Google BigQuery, dataset abbmb_dwh | Ảnh chụp đầy đủ mỗi đêm 03:30 — phần 05–08 |
| Trợ lý AI | Claude qua MCP, chỉ đọc | Mỗi người một tài khoản chỉ xem + khoá API riêng, hạn 90 ngày — phần 04 |
Hạ tầng dựng từ bộ khung dùng chung odoo-gcp-base (v2.1.0). Mọi giá trị riêng của ABBMB nằm trong một file cấu hình tenant; bộ khung không chứa tên tenant nào — đó là điều kiện để portco khác dùng lại được.
02 · Người dùng
Dùng CRM hằng ngày
Giao diện bằng tiếng Anh; tên nút và menu dưới đây giữ nguyên như trên màn hình. Ứng dụng nằm ở menu trên cùng bên trái: ABB CRM.
Đăng nhập lần đầu
- Tài khoản là của riêng bạn — không dùng chung. Chủ CRM gửi riêng cho bạn tên đăng nhập và mật khẩu tạm; đăng nhập rồi đổi mật khẩu ngay.
- Bật xác thực hai lớp: avatar → My Preferences → Account Security.
- Thông báo hiện trong Odoo (chuông, Discuss), không gửi qua email.
Vai trò
| Vai trò | Làm được |
|---|---|
| Manager (Quản lý) | Mọi việc của Nhân viên, thêm: đổi Company Group của công ty, gộp bản ghi trùng, xử lý hàng Pending Review, xuất dữ liệu |
| Staff (Nhân viên) | Đọc toàn bộ; tạo và sửa biên bản, công ty, liên hệ; giao việc theo dõi. Không xuất được dữ liệu. |
| Read-only (Chỉ xem) | Đọc toàn bộ; chỉ sửa hồ sơ của chính mình |
Ghi một cuộc gặp
ABB CRM → Meeting Notes → New. Điền 7 phần theo mẫu ABB — phần in đậm là bắt buộc:
- ABB Attendees (mặc định là bạn) cũng bắt buộc. Phần Contact details ghi thông tin liên hệ mới nhận.
- Người mới (New person) hoặc liên hệ đổi thông tin (Contact update) đều vào Pending Review cho Quản lý duyệt — không có liên hệ nào tự sinh sau lưng bạn.
Việc theo dõi
Trên biên bản bấm Schedule follow-up, chọn người, việc, hạn. Việc hiện ở My Activities (biểu tượng đồng hồ) của người được giao; đánh dấu xong ở đó.
Tìm lại
- Meeting Notes: bộ lọc My notes, This week, nhóm theo Year; tìm theo công ty, người hoặc chữ.
- Companies / Contacts: nút Meeting Notes trên một công ty liệt kê mọi cuộc gặp với công ty đó. Salesperson trên công ty = người phụ trách phía ABB.
- Legacy Deals: deal từ CRM cũ, chỉ đọc.
Thói quen tốt: mỗi cuộc gặp một biên bản, viết trong ngày; dùng đúng các phần của mẫu, không dán nguyên chuỗi email. Odoo không gửi thư nào tới liên hệ — không có gì bạn gõ tự đi tới khách hàng.
03 · Dữ liệu cũ
Lịch sử PipelineDeals
Dữ liệu cũ được nạp ngày 12/10/2026 sau 22 cổng kiểm chất lượng và một vòng duyệt 100 bản ghi mẫu (0 lỗi nghiêm trọng).
Ghi chú gắn nhãn Legacy (PD)
Các trường gốc chỉ đọc. Legacy Author là người viết trong PipelineDeals; Logged by chỉ đặt cho nhân sự hiện tại, ghi chú của người đã nghỉ và của tài khoản chung "ABB Team" giữ tên. Original Text (PD) giữ nguyên văn bản cũ; 7 ghi chú rất dài đã bị bản xuất cũ cắt (Truncated in Export).
Số điện thoại cần xem lại
Số không đọc được mang thẻ Phone to fix và số gốc ở Original Phone (PD). Số được thêm dấu "+" theo suy luận mang thẻ Phone: + added (inferred). Sửa dần trong lúc dùng.
04 · Trợ lý AI
Claude đọc CRM — không ghi được gì
Claude (ứng dụng Desktop hoặc Claude Code trên máy tính) kết nối CRM qua một máy chủ MCP nhỏ chạy trên chính máy của bạn, đi qua đường hầm bảo mật của Google tới máy chủ CRM. Giao diện web của CRM không mở thêm cổng nào cho việc này.
Quyền
Mỗi người một tài khoản Odoo nhóm Chỉ xem và một khoá API riêng hạn 90 ngày — thu hồi được từng người, nhật ký truy cập theo từng người. Chủ CRM tạo và cài một lần cho bạn.
8 công cụ chỉ đọc
search_companies, get_company, search_contacts, get_contact, search_meeting_notes, get_meeting_note, list_legacy_deals, list_company_groups. Không có công cụ tạo, sửa hay xoá.
Hỏi thế nào
Claude chỉ thấy những gì tài khoản Chỉ xem của bạn thấy, và chỉ khi đường hầm đang mở trên máy bạn. Câu trả lời của AI có thể sai — luôn mở biên bản gốc theo id để kiểm trước khi dùng.
05 · Kho dữ liệu
Mỗi đêm một ảnh chụp đầy đủ của CRM
Kho dữ liệu không đồng bộ từng thay đổi. Mỗi đêm nó chụp toàn bộ 5 bảng nghiệp vụ, gắn ngày chụp (snapshot_date) cho từng dòng và giữ lại mọi ngày. Nhờ vậy bạn hỏi được cả "hôm nay có gì" lẫn "hôm qua khác hôm nay ở đâu", và một đêm lỗi không làm hỏng các đêm trước.
abbmb_dwh ở Singapore, cùng vùng với CRM05 · Luồng chạy
Từ Odoo tới BigQuery trong năm bước
Toàn bộ chạy trên máy chủ CRM, do một bộ hẹn giờ của hệ điều hành (systemd timer) kích hoạt. Không có máy nào khác tham gia.
_snapshot_check 5/5, dựng lại view; ghi DWH OK hoặc DWH FAIL vào log → email khi lỗiKhông có file khoá nào nằm lại
Khoá Odoo chỉ tồn tại trong thư mục tmpfs của lượt chạy, hệ điều hành tự xoá khi lượt kết thúc. BigQuery xác thực bằng tài khoản dịch vụ của máy chủ qua metadata server — không có file khoá Google nào.
User bot chỉ đọc
Odoo cấp cho kho một user riêng nhóm Chỉ xem, không mật khẩu, một khoá API phạm vi rpc hạn 90 ngày. Đã đo: user này không có quyền ghi, tạo hay xoá trên các model trong hợp đồng dữ liệu; khoá sai trả 401.
Gói Python khoá bằng hash
Môi trường Python dựng một lần từ file lock có hash (74 gói), chỉ nhận bản đóng gói sẵn, không tự biên dịch. Đã thử sửa một hash → cài đặt phải thất bại, và nó thất bại.
Không giữ bản thừa
Dataset trung gian abbmb_dwh_staging được làm rỗng sau mỗi lượt; thư mục làm việc trên máy chủ được dọn sau mỗi lượt OK — vì cả hai đều chứa dữ liệu cá nhân.
06 · Mô hình dữ liệu
Bảng, view và cột
Mọi bảng dữ liệu đều có cột snapshot_date (DATE). Quan hệ nhiều-một (ví dụ công ty của một biên bản) được lưu bằng id số của bản ghi đích. Số dòng là của ảnh chụp 18/10/2026.
| Bảng | Model Odoo | Dòng / ngày | Cột chính |
|---|---|---|---|
res_partner | Công ty + liên hệ | 5.277 | id, name, is_company, parent_id, commercial_partner_id, function, email, phone, website, street, city, country_id, industry_id, user_id (người phụ trách), abb_prospect_status, comment, active |
abb_meeting_note | Biên bản gặp mặt | 3.374 | id, name, date, partner_company_id, raw_company_name, description, follow_up, key_data, user_id, legacy_deal_id, review_pending, is_legacy, legacy_category, legacy_author, legacy_truncated, legacy_raw, active |
abb_meeting_attendee | Người gặp trong biên bản | 1.033 | id, note_id, sequence, partner_id, name, function, phone, email, company_name, state, abb_reviewed |
abb_legacy_deal | Deal cũ PipelineDeals | 128 | id, name, partner_company_id, last_stage, currency_id, amount, first_date, last_date, active |
abb_company_group | Company Group | 4 | id, name, code, sequence, description, active |
Mọi bảng còn có create_date, write_date (TIMESTAMP) và hai cột kỹ thuật của dlt (_dlt_load_id, _dlt_id).
View dựng sẵn — nên dùng thay cho bảng con
| View | Cột | Dùng để |
|---|---|---|
bridge__res_partner__abb_company_group_ids | left_id = partner, right_id = company group, snapshot_date | Công ty thuộc nhóm nào (một công ty có thể thuộc nhiều nhóm) |
bridge__res_partner__category_id | left_id = partner, right_id = thẻ | Thẻ gắn trên liên hệ (ví dụ Phone to fix) |
bridge__abb_meeting_note__abb_staff_ids | left_id = biên bản, right_id = user ABB | Nhân sự ABB dự cuộc gặp |
deleted_since_prev__<bảng> | id, prev_snapshot_date, snapshot_date | Id có ở ảnh chụp hợp lệ trước nhưng biến mất ở ảnh chụp hợp lệ mới nhất (đã xoá hẳn trong Odoo) |
View chỉ lấy những ngày mà _snapshot_check ghi ok = true. Bảng con gốc (res_partner__category_id…) nối qua _dlt_parent_id — dùng view thì không cần biết chi tiết đó. Các bảng _dlt_loads, _dlt_pipeline_state, _dlt_version là siêu dữ liệu của dlt, không chứa dữ liệu CRM.
07 · Truy vấn
Ba quy tắc để không đếm sai
Thay PROJECT_ID bằng mã project Google Cloud của CRM (hỏi chủ CRM). Mọi ví dụ chạy được trong BigQuery Studio với vùng asia-southeast1.
snapshot_date là đếm một công ty nhiều lần.ok = true trong _snapshot_check.active khi cần "hiện tại". Ảnh chụp lấy cả bản ghi đã lưu trữ (archive) để không mất lịch sử.Ngày chụp hợp lệ mới nhất
-- ngày có đủ 5 bảng và cả 5 đều ok
select snapshot_date
from `PROJECT_ID.abbmb_dwh._snapshot_check`
group by snapshot_date
having count(*) = 5 and logical_and(ok)
order by snapshot_date desc
limit 1;
Số cuộc gặp theo công ty và năm
declare d date default (
select snapshot_date from `PROJECT_ID.abbmb_dwh._snapshot_check`
group by 1 having count(*) = 5 and logical_and(ok)
order by 1 desc limit 1);
select c.name as company,
extract(year from n.date) as year,
count(*) as meetings
from `PROJECT_ID.abbmb_dwh.abb_meeting_note` n
join `PROJECT_ID.abbmb_dwh.res_partner` c
on c.id = n.partner_company_id and c.snapshot_date = d
where n.snapshot_date = d and n.active
group by company, year
order by year desc, meetings desc;
Số công ty trong từng Company Group
declare d date default ( select snapshot_date from `PROJECT_ID.abbmb_dwh._snapshot_check` group by 1 having count(*) = 5 and logical_and(ok) order by 1 desc limit 1); select g.name as company_group, count(distinct b.left_id) as companies from `PROJECT_ID.abbmb_dwh.bridge__res_partner__abb_company_group_ids` b join `PROJECT_ID.abbmb_dwh.abb_company_group` g on g.id = b.right_id and g.snapshot_date = b.snapshot_date join `PROJECT_ID.abbmb_dwh.res_partner` p on p.id = b.left_id and p.snapshot_date = b.snapshot_date where b.snapshot_date = d and p.is_company and p.active group by company_group order by companies desc;
Biên bản mới hoặc sửa giữa hai ngày chụp
select n.id, n.date, n.name, n.write_date
from `PROJECT_ID.abbmb_dwh.abb_meeting_note` n
where n.snapshot_date = '2026-10-18'
and n.write_date >= timestamp('2026-10-17 03:30:00', 'Asia/Ho_Chi_Minh')
order by n.write_date desc;
Kết quả truy vấn có thể chứa tên, email, số điện thoại của đối tác. Không xuất ra file cá nhân, không dán vào công cụ AI ngoài, không chia sẻ màn hình kết quả thô. Báo cáo gửi đi nên chỉ chứa số đếm và tên công ty.
08 · Kiểm tra & bảo mật
Kho tự kiểm mỗi đêm và báo khi sai
Một ảnh chụp chỉ được coi là đúng khi, với từng bảng: số dòng = số id khác nhau = số bản ghi Odoo tự đếm được lúc chụp. Kết quả ghi ở _snapshot_check (cột snapshot_date, table_name, model, rows, distinct_ids, expected, ok, checked_at).
| Tình huống | Hệ thống làm gì | Bạn thấy gì |
|---|---|---|
| Lượt chạy lỗi (mạng, quyền, số đếm lệch) | Ghi DWH FAIL, giữ nguyên các ngày cũ | Email cảnh báo trong vài phút; ngày hôm đó không có trong view |
| Quá 26 giờ không có lượt OK | Kiểm hằng giờ ghi DWH OVERDUE | Email cảnh báo |
| Odoo mất một trường có trong hợp đồng (sau nâng cấp) | Dừng trước khi nạp, mã lỗi riêng | Email; người vận hành sửa hợp đồng |
| Odoo có trường mới chưa có trong hợp đồng | Chỉ ghi log DWH NGOAI_HOP_DONG n=…, không chặn | Không có gì — trường mới chỉ vào kho khi hợp đồng được nâng version |
| Chạy lại cùng ngày | Thay đúng phân vùng của ngày đó | Không có dòng trùng |
Đã đo trước khi bật trên production
Đúng dữ liệu
Tập id trong BigQuery = tập id đọc thẳng từ PostgreSQL cho từng bảng; tập cặp quan hệ khớp 3/3; xoá 7 bản ghi thử → view báo đúng 7 id.
Ca sai phải đỏ
Bỏ bản ghi lưu trữ, cắt phân trang, nạp chồng thay vì thay thế — cả ba cấu hình sai đều bị tự kiểm bắt và báo lỗi.
Hai đêm liên tiếp
17/10 và 18/10/2026 đều DWH OK, 5/5; diễn tập cảnh báo: log tới trong ~1 giây, email trong vòng 10 phút.
Ai đọc được
- Hiện chỉ chủ dự án. Thêm một người hay một công cụ BI = một quyết định ghi lại, cấp quyền
BigQuery Data Viewerở mức dataset, không bao giờ ở mức project. - Tài khoản dịch vụ của máy chủ chỉ ghi được vào hai dataset của kho, không có vai trò rộng nào ở mức project.
- Dữ liệu cá nhân được giữ nguyên (không băm, không cắt) theo ý kiến pháp chế; vì thế quyền đọc là lớp bảo vệ chính.
09 · Vận hành
Cho người vận hành CRM
Mọi thay đổi hạ tầng là file trong bộ khung + biến trong file cấu hình tenant, đưa lên bằng script. Không sửa tay trên máy chủ.
| Việc | Cách làm | Lịch |
|---|---|---|
| Đưa code module lên prod | Kiểm tra tự động trên máy dev → in kế hoạch → chủ dự án duyệt bằng mã commit → áp dụng; lỗi đường truyền thì dừng, không tự lùi | Khi có thay đổi |
| Đổi code hay hợp đồng DWH | Kiểm tra tự động → đẩy repo lên máy chủ (cổng duyệt) → dựng lại môi trường Python nếu lock đổi → chạy một lượt → kiểm | Khi có thay đổi |
| Kiểm sức khoẻ DWH | Lệnh kiểm chỉ đọc: timer, lần OK cuối, _snapshot_check ngày gần nhất 5/5, 0 file khoá ngoài tmpfs, 0 email trong log | Bất kỳ lúc nào |
| Xoay khoá API của DWH | Backup → sinh khoá mới đi thẳng vào Secret Manager qua ống (không hiện màn hình) → trỏ version mới → kiểm | Trước ngày 85 của khoá; lần kế 09/01/2027 |
| Tắt DWH | Tắt timer, giữ dữ liệu; bật lại bằng một lệnh | Khi cần |
| Lùi hẳn DWH | Tắt timer → thu hồi khoá trong Odoo → vô hiệu version secret → xoá dataset (chỉ khi chủ dự án duyệt riêng; dữ liệu nguồn vẫn ở Odoo) | Khi cần |
| Billing Google Cloud | Kiểm theo quý; nâng cấp gói trước khi credit dùng thử hết | Trước 15/12/2026 |
10 · Giới hạn
Những gì kho chưa làm
Biết trước để không hỏi kho một câu nó không trả lời được.
user_id), quốc gia (country_id), ngành (industry_id), tiền tệ — chỉ có id. Cần tên thì đối chiếu trong CRM, hoặc mở rộng hợp đồng dữ liệu (một version mới).Một phép đo chỉ có giá trị khi nó cho kết quả khác nhau giữa cấu hình đúng và cấu hình sai — kho dữ liệu này được bật trên production chỉ sau khi cả ba cấu hình sai đều bị bắt.