Trả lời câu hỏi: action cần join dữ liệu giữa bảng của nhiều module thì thực hiện thế nào? Phương án rút từ ràng buộc thực của OpenGate + đối chiếu Odoo · ERPNext · Lark. Bản chi tiết: markdown.
| Đặc điểm OpenGate | Hệ quả |
|---|---|
| Mỗi workspace = 1 DB Postgres chung cho mọi module | JOIN chéo module khả thi về kỹ thuật — vấn đề là kỷ luật, không phải công nghệ |
| Module cài/gỡ độc lập, không có ORM registry chung kiểu Odoo | Không join "tự do" — bảng module khác có thể vắng mặt hoặc đổi schema |
| Manifest v2 có depends + scopes ('module:feature:readonly') | Đã có chỗ khai báo hợp thức quyền đọc chéo — chỉ thiếu quy ước thực thi |
| Đã chốt: không FK cứng xuyên module — khóa nghiệp vụ (ma_kh, ma_gv, ma_xe, ma_dk…) | Join theo khóa nghiệp vụ, không theo id nội bộ |
| # | Loại quan hệ | Ví dụ | Tính chất |
|---|---|---|---|
| Q1 | Tham chiếu danh mục | mọi module → catalog (hạng, checklist) | đọc, rất ổn định |
| Q2 | Tham chiếu khóa nghiệp vụ | student.ma_kh → course · vehicle.seri_dat → dat | đọc, enrich khi hiển thị |
| Q3 | Consumer đọc rộng | report, bi, payroll đọc nhiều module | đọc, khối lượng lớn, theo kỳ |
| Q4 | Callback trạng thái | tuition → student (da_thu_dot1) · report → course (da_bao_so) | ghi — phải qua rule nghiệp vụ |
| Q5 | Sự kiện / thông báo | dat.alert → notify | ghi một chiều, fire-and-forget |
| Hệ | Cách join chéo module | Đáng học | Rủi ro nếu bê nguyên |
|---|---|---|---|
| Odoo 1 DB + 1 ORM registry |
Module load vào registry chung → ORM join tự do; depends xếp thứ tự; tích hợp 2 module optional bằng bridge module (vd sale_stock) | ① Bridge module ② đồ thị depends tường minh | Join tự do nhờ registry + loader — OpenGate không có → module vỡ ngầm khi module khác nâng version |
| ERPNext Frappe · DocType · 1 DB |
Link field (tham chiếu theo tên ≈ khóa nghiệp vụ) + fetch_from (copy giá trị lúc lưu); phản ứng chéo qua hooks/doc_events; báo cáo = Query Report read-only | ① fetch_from = denorm có chủ đích ② hooks = callback chuẩn hóa ③ báo cáo tách kênh read-only | Denorm tràn lan không đánh dấu → không biết cột nào là snapshot |
| Lark multi-app, không chung DB |
Mọi truy cập chéo qua Open API + scopes cấp cho từng app + event subscription; eventual consistency | ① scopes khai báo — khớp field OpenGate đã có ② event một chiều ③ app vắng không làm app khác chết | Ép mọi thứ qua API trong khi chung 1 DB → N+1, chậm không cần thiết |
| # | Pattern | Dùng cho | Cảm hứng |
|---|---|---|---|
| P1 | Contract view — module chủ publish view read-only <key>_v_*; consumer JOIN vào view, không vào bảng gốc; khai scopes trong manifest | Q1, Q2 — enrich list, lookup danh mục | Odoo + Lark |
| P2 | Copy tại thời điểm phát sinh — ghi kèm giá trị tham chiếu vào bản ghi sự kiện, không sync ngược | dat_phien_hoc giữ ma_gv/ma_xe lúc phiên chạy; phiếu thu giữ số tiền lúc thu | ERPNext fetch_from |
| P3 | Service API + callback — ghi chéo chỉ qua API module chủ; transition chạy rule + ghi lịch sử tại module chủ | Q4: tuition→student · report→course | ERPNext hooks / Lark API |
| P4 | Event một chiều (outbox nhẹ) — bắn sự kiện, không chờ; retry hàng đợi | Q5: cảnh báo/nhắc hạn → notify | Lark event |
| P5 | Bridge module — tính năng cần 2 module optional đặt ở module thứ 3 depends cả hai | hrm.timesheet-dat = bridge feature (dat + teacher + lms); chip×booking | Odoo bridge |
| P6 | Snapshot / materialized view — đọc rộng theo kỳ: chụp lúc lập (report) hoặc MV refresh định kỳ (bi) | Q3: báo cáo Sở, BI | ERPNext Query Report |
| Action | Pattern | Thực hiện |
|---|---|---|
| List học viên kèm tên khóa, GV | P1 | JOIN course_v_khoa_hoc + teacher_v_giao_vien; manifest khai scopes: ['course:course:readonly','teacher:teacher:readonly'] |
| Đối soát km/giờ DAT | P1+P2 | Chuẩn đọc catalog_v_hang_gplx; phân công đọc course_v_enrollment; phiên giữ nguyên ma_gv/ma_xe lúc chạy — lệch → hop_le=false, không "sửa quá khứ" |
| Thu đủ đợt 1 → da_thu_dot1 | P3 | tuition ghi phiếu (bảng mình) → gọi API student transition; student ghi lịch sử. Lỗi → outbox retry + job đối soát "đã thu mà chưa chuyển trạng thái" |
| Sinh báo cáo Sở XD | P6+P3 | generate = query view các module, snapshot vào file immutable; sau da_gui → callback course "đã báo Sở" |
| Lương GV theo giờ DAT | P5+P1 | hrm.timesheet-dat (built-in, bridge feature depends dat+teacher+lms); đọc dat_v_phien_hop_le group theo ma_gv → hrm.payroll |
| App học viên xem tiến độ | P1 | portal đọc dat_v_doi_soat qua service + record-scope (ma_dk ∈ current_ma_dk_list) |
| Cảnh báo thiếu km/giờ → Zalo | P4 | dat.alert bắn event vào notify (outbox); notify chọn kênh + log gửi — dat không biết Zalo là gì |
| BI dashboard | P6 | Materialized view refresh theo lịch; drill-down deep-link sang module chủ |
Sau khảo sát 7 built-in module của WS (review core §1b), bản đồ quan hệ đổi như sau:
| Trước | Nay |
|---|---|
| Module mới hr, payroll (v1.5) | Bỏ → hrm built-in + feature timesheet-dat (bridge: dat+teacher+lms) |
| Module mới crm (v1.5) | Bỏ → crm built-in + feature convert (lead→hồ sơ, P3 callback) + commission (mốc da_thu_dot1) |
| Danh mục phòng ban/chức danh dự kiến tự tạo | org built-in — nguồn duy nhất; student.phong_phu_trach + data-scope current_dept_id đọc từ đây |
| GV riêng lẻ trong teacher | teacher ↔ hrm.Employee 1-1 (mã NV) — teacher giữ hồ sơ nghiệp vụ dạy |
| Adapter DAT/HĐĐT/payment/Zalo nằm trong từng module | workflow.connector — két adapter + credential một cửa (áp dụng P4 outbox khi gọi) |
| v1.5 = 7 module mới | v1.5 = 4 module mới (lms, finance, fleet, portal) + 3 mở rộng built-in · workplace disabled ở Vĩnh An |
Các pattern P1–P6 và quy tắc mục 4 không đổi — built-in tuân cùng quy ước (contract view hrm_v_*, org_v_*; ghi chéo qua service API).