Một sơ đồ dữ liệu cho website bán hàng cần thể hiện toàn bộ cách thông tin được tạo, liên kết và sử dụng trong quá trình khách tìm sản phẩm, đặt hàng, thanh toán và quay lại mua sắm. Nền tảng thường bắt đầu từ các nhóm người dùng – khách hàng – phân quyền, sau đó mở rộng sang sản phẩm, danh mục, thương hiệu, biến thể, hình ảnh và thuộc tính như màu sắc, kích thước hoặc chất liệu. Mỗi bảng cần có khóa chính để định danh bản ghi, khóa ngoại để liên kết dữ liệu và quy tắc quan hệ rõ ràng nhằm tránh trùng lặp hoặc sai lệch khi vận hành.

Ở luồng thương mại, giỏ hàng, dòng sản phẩm trong giỏ, đơn hàng, chi tiết đơn, thanh toán, vận chuyển và kho phải được tách thành các thực thể riêng. Cách tổ chức này giúp cố định giá, giảm giá, thuế, thông tin nhận hàng và trạng thái giao dịch tại thời điểm khách đặt mua; đồng thời theo dõi tồn kho theo SKU, kho hàng và từng lần nhập – xuất – hoàn trả.
Một cấu trúc hoàn chỉnh cũng cần bao quát khuyến mãi, mã giảm giá, chiến dịch, SEO, landing page, nội dung mạng xã hội, tracking và lịch sử thao tác. Nhờ đó, doanh nghiệp có thể phân tích doanh thu theo sản phẩm, kênh bán, chiến dịch hoặc nhóm khách hàng, thay vì chỉ lưu dữ liệu đơn lẻ. Thiết kế tốt cần cân bằng giữa chuẩn hóa dữ liệu, tốc độ truy vấn và khả năng mở rộng, để khi tăng số lượng sản phẩm, đơn hàng hay tích hợp CRM, quảng cáo và sàn thương mại điện tử, hệ thống vẫn ổn định và dễ quản trị.
ERD website bán hàng cần mô hình hóa dữ liệu để quản lý sản phẩm, đơn hàng và marketing bán hàng
ERD cho website bán hàng cần mô hình hóa dữ liệu xoay quanh các nhóm chính: sản phẩm – đơn hàng – khách hàng – marketing. Ở lớp dữ liệu cốt lõi, hệ thống phải phân tách rõ entity nghiệp vụ (Product, Order, Customer, Campaign, Payment, Inventory) với các bảng tham chiếu, log và giao dịch để vừa chuẩn hóa vừa dễ mở rộng. Mỗi entity cần được thiết kế thuộc tính định danh, mô tả, trạng thái, thời gian và nghiệp vụ, kết hợp PK, FK và quan hệ 1-1, 1-n, n-n để phản ánh đúng luồng vận hành. ERD là nền tảng quan trọng khi thiết kế website bán hàng có dữ liệu phức tạp. Sơ đồ được xây dựng đúng giúp đội ngũ phát triển xác định rõ các thực thể, thuộc tính và mối quan hệ, từ đó hạn chế trùng lặp dữ liệu, sai lệch nghiệp vụ và khó khăn khi mở rộng hệ thống.

ERD cũng phải bao quát các mảng SEO, tracking, chống click tặc, social, omnichannel, hỗ trợ báo cáo doanh thu đa chiều, tối ưu truy vấn và đảm bảo tính nhất quán khi hệ thống phát triển thêm kênh bán, chiến dịch và tính năng mới.
ERD giúp xác định entity, thuộc tính, khóa chính, khóa ngoại và quan hệ dữ liệu
Trong thiết kế hệ thống bán hàng chuyên nghiệp, ERD (Entity Relationship Diagram) không chỉ là sơ đồ minh họa mà là bản thiết kế dữ liệu cốt lõi, chi phối cách xây dựng database, API và logic nghiệp vụ. Mỗi entity tương ứng với một bảng trong cơ sở dữ liệu quan hệ, nhưng khi thiết kế ở mức chuyên sâu, cần phân biệt rõ:
- Core entity: các bảng trung tâm như Product, Order, Customer, Campaign, Payment, Inventory.
- Reference entity: các bảng danh mục, cấu hình như Category, Brand, Channel, PriceList, Country, Currency.
- Transaction entity: các bảng ghi nhận giao dịch như OrderItem, PaymentTransaction, StockMovement, AdCostLog.
- Log / Audit entity: các bảng lưu lịch sử thay đổi, tracking như ActivityLog, VisitSession, SeoAuditRun.
Nền tảng lý thuyết của cách phân chia này xuất phát từ mô hình Entity–Relationship do Peter P. Chen đề xuất, trong đó dữ liệu được biểu diễn thông qua tập thực thể, thuộc tính và các quan hệ có ý nghĩa ngữ nghĩa. Chen nhấn mạnh mô hình khái niệm phải mô tả được cách con người nhìn nhận thế giới nghiệp vụ trước khi chuyển thành bảng vật lý. Vì vậy, Product, Order hay Customer không nên được tạo chỉ từ nhu cầu hiển thị giao diện, mà phải đại diện cho những đối tượng có danh tính, thuộc tính và quy tắc tồn tại độc lập. Cách tiếp cận này giúp ERD trở thành cầu nối nhất quán giữa nghiệp vụ, cơ sở dữ liệu và chương trình ứng dụng (Chen, 1976).

Mỗi thuộc tính (attribute) không chỉ là một cột đơn giản mà cần được phân loại theo vai trò: thuộc tính định danh (code, SKU, slug), thuộc tính mô tả (name, description), thuộc tính trạng thái (status, isactive), thuộc tính thời gian (createdat, updatedat, publishedat) và thuộc tính nghiệp vụ (price, stockquantity, discountrate). Việc phân loại này giúp chuẩn hóa cách đặt tên, index và tối ưu truy vấn.
Khóa chính (Primary Key – PK) trong hệ thống bán hàng thường dùng kiểu số tăng tự động (auto-increment) hoặc UUID. Với các bảng có khả năng đồng bộ đa hệ thống (omnichannel, microservice), UUID hoặc các khóa tự nhiên như ordernumber, externalid thường được sử dụng song song với PK nội bộ để dễ tích hợp. Khóa ngoại (Foreign Key – FK) không chỉ đảm bảo toàn vẹn tham chiếu mà còn thể hiện rõ luồng nghiệp vụ, ví dụ:
- order.customerid → customer.id (ai là người đặt hàng)
- orderitem.productvariantid → productvariant.id (biến thể nào được bán)
- payment.orderid → order.id (thanh toán cho đơn hàng nào)
- campaignchannel.channelid → channel.id (chiến dịch chạy trên kênh nào)
Relationship trong ERD bán hàng thường bao gồm nhiều kiểu quan hệ phức tạp hơn mức 1-1, 1-n, n-n cơ bản. Ví dụ, quan hệ n-n giữa Product và Category được triển khai qua bảng trung gian ProductCategory, nhưng khi thêm yếu tố thứ tự hiển thị, category chính, thời gian gắn, bảng trung gian này trở thành một entity có ý nghĩa nghiệp vụ riêng. Tương tự, quan hệ giữa Campaign và Product có thể cần thêm các thuộc tính như campaignprice, priority, limitpercustomer, khiến bảng CampaignProduct trở thành nơi lưu logic giá khuyến mãi.
Trong bối cảnh website bán hàng hiện đại, ERD phải bao trùm cả các mảng marketing, SEO, tracking, chống click tặc, phân phối nội dung. Điều này dẫn đến việc bổ sung các entity chuyên biệt như:
- TrackingEvent: lưu từng sự kiện (view, addtocart, purchase, clickad) gắn với VisitSession và Customer.
- FraudClick: lưu các lần click nghi ngờ, kèm IP, useragent, referrer, campaignid.
- ContentBlock: các khối nội dung tái sử dụng trên nhiều landing page, gắn với SeoMeta và Campaign.
Một ERD chuẩn cho website bán hàng thường phân tách rõ các nhóm entity: người dùng – khách hàng – phân quyền, sản phẩm – danh mục – thuộc tính, giỏ hàng – đơn hàng – thanh toán, kho – vận chuyển – tồn kho, khuyến mãi – chiến dịch marketing, SEO – redirect – sitemap, tracking – chống click tặc và social – phân phối nội dung. Mỗi nhóm không chỉ có các bảng cốt lõi mà còn có:
- Junction table để thể hiện quan hệ n-n (ProductCategory, CampaignProduct, UserRole, ProductChannel).
- History / Snapshot table để lưu trạng thái tại thời điểm giao dịch (OrderItemSnapshot, PriceHistory, InventorySnapshot).
- Config table để điều khiển hành vi hệ thống (PromotionRule, ShippingRule, TaxRule).
Thiết kế ERD cần tuân thủ chuẩn hóa dữ liệu (thường đến 3NF) để giảm trùng lặp, nhưng với hệ thống bán hàng có lượng truy vấn đọc lớn, thường áp dụng thêm các kỹ thuật denormalization có kiểm soát, như lưu sẵn totalamount, totaldiscount, channelname, campaignname trong Order để tối ưu báo cáo, đồng thời vẫn giữ FK đến bảng gốc để đảm bảo tính nhất quán. Mô hình quan hệ của Codd đặt nền móng cho việc tổ chức dữ liệu thành các quan hệ độc lập với cách lưu trữ vật lý, qua đó hạn chế sự phụ thuộc không cần thiết giữa dữ liệu và chương trình. Trong website bán hàng, chuẩn hóa giúp Brand, Category, Customer hoặc ProductAttribute chỉ được định nghĩa tại một nguồn chính, thay vì lặp lại trong nhiều bảng. Mục tiêu không chỉ là tiết kiệm dung lượng mà còn ngăn bất thường khi thêm, sửa hoặc xóa dữ liệu. Denormalization chỉ nên được áp dụng cho trường tổng hợp hoặc snapshot có mục đích rõ ràng, kèm quy tắc cập nhật và kiểm thử nhất quán; nếu sao chép tùy tiện, dữ liệu báo cáo rất dễ lệch khỏi dữ liệu giao dịch gốc (Codd, 1970).
| Khái niệm | Mục đích trong ERD bán hàng |
| Entity | Biểu diễn đối tượng nghiệp vụ như Product, Order, Customer, Campaign |
| Attribute | Lưu thông tin chi tiết của entity như name, price, status, createdat |
| Primary Key (PK) | Định danh duy nhất mỗi bản ghi, thường là id dạng số hoặc UUID |
| Foreign Key (FK) | Liên kết giữa các bảng, ví dụ order.customerid tham chiếu customer.id |
| Relationship | Mô tả quan hệ 1-1, 1-n, n-n giữa các entity, ví dụ Customer – Order |
Website bán hàng cần ERD phục vụ vận hành, SEO, marketing, chăm sóc khách hàng và báo cáo doanh thu
ERD cho website bán hàng ở mức chuyên sâu phải hỗ trợ toàn bộ vòng đời khách hàng, từ lần truy cập đầu tiên, quá trình nuôi dưỡng qua email, remarketing, cho đến mua hàng, chăm sóc sau bán và tái mua. Về vận hành, các entity như Order, OrderItem, Shipment, Payment, StockMovement cần được thiết kế sao cho phản ánh chính xác trạng thái nghiệp vụ:
- Order có thể tách thành nhiều Shipment (giao nhiều lần, nhiều kho).
- Mỗi PaymentTransaction có thể gắn với một hoặc nhiều Order trong trường hợp thanh toán gộp.
- StockMovement ghi nhận từng lần nhập, xuất, điều chỉnh tồn kho, liên kết với OrderItem hoặc phiếu kiểm kho.

Về chăm sóc khách hàng, các bảng như Customer, CustomerAddress, CustomerSegment, ActivityLog, VisitSession cần được thiết kế để hỗ trợ:
- Phân biệt khách hàng đã đăng ký (registered) và khách vãng lai (guest), nhưng vẫn gom được lịch sử mua hàng khi họ đăng ký sau.
- Lưu nhiều địa chỉ giao hàng, địa chỉ hóa đơn, với cờ đánh dấu defaultshipping, defaultbilling.
- Gắn các sự kiện như mở email, click link, chat support vào ActivityLog, liên kết với Campaign hoặc Channel.
Ở khía cạnh SEO và marketing, các entity như SeoMeta, Redirect, SitemapUrl, Campaign, Promotion, Coupon, AdCostLog, SocialPost phải hỗ trợ đo lường đa chiều:
- SeoMeta gắn với Product, Category, Page, giúp lưu title, metadescription, canonicalurl, schematype.
- Redirect lưu mapping từ oldurl sang newurl, kèm type (301, 302) và source (migration, manual, auto).
- AdCostLog lưu chi phí quảng cáo theo ngày, theo kênh (Google Ads, Facebook Ads), theo campaignexternalid để đối soát với doanh thu.
Báo cáo doanh thu yêu cầu dữ liệu được thiết kế sao cho dễ tổng hợp theo ngày, kênh, chiến dịch, nhóm sản phẩm, phân khúc khách hàng. Điều này ảnh hưởng trực tiếp đến cách lưu Order, OrderItem, Payment, CampaignProduct, SocialMetric và các khóa ngoại liên quan. Ví dụ:
- Order cần lưu channelid, campaignid, source, medium tại thời điểm đặt hàng để không phụ thuộc vào thay đổi sau này.
- OrderItem nên lưu productname, sku, categorypath dạng snapshot để báo cáo lịch sử không bị ảnh hưởng khi đổi tên sản phẩm.
- SocialMetric gắn với SocialPost và Campaign, lưu các chỉ số như impression, click, engagement, conversion.
Cấu trúc dữ liệu phải hỗ trợ mở rộng sản phẩm, chiến dịch, nội dung và kênh bán hàng
Một ERD tốt cho website bán hàng cần được thiết kế với tư duy mở rộng trong tương lai và khả năng cấu hình cao. Về sản phẩm, hệ thống phải cho phép thêm mới các loại thuộc tính, biến thể, thương hiệu, bộ sưu tập mà không phải thay đổi cấu trúc bảng cốt lõi. Mô hình thường gặp:
- Attribute: định nghĩa loại thuộc tính (color, size, material, warrantyperiod), kiểu dữ liệu (text, number, boolean, select).
- AttributeValue: lưu các giá trị có thể chọn (red, blue, XL, 12 months).
- ProductAttribute: gắn thuộc tính với Product, có thể lưu cả giá trị tự do (free text) cho các thuộc tính không giới hạn.
- ProductVariant: đại diện cho từng biến thể cụ thể (một combination của nhiều AttributeValue), có SKU, barcode, price, weight, stock riêng.

Về chiến dịch, ERD phải hỗ trợ nhiều loại Promotion (giảm giá theo phần trăm, theo số tiền, theo combo, theo nhóm khách hàng) và nhiều loại Coupon (một lần, nhiều lần, theo user, theo kênh). Thường cần thêm các bảng cấu hình luật:
- PromotionCondition: điều kiện áp dụng (minordervalue, requiredproducts, customersegmentid).
- PromotionAction: hành động (discountpercent, discountamount, freeshipping, freegiftproductid).
- CouponUsage: log sử dụng coupon theo customerid, orderid, để giới hạn số lần dùng.
Về nội dung và kênh bán hàng, cấu trúc dữ liệu cần hỗ trợ đa kênh (omnichannel) như website, marketplace, social commerce, ứng dụng di động. Các bảng như Channel, ProductChannel, PriceList, Inventory (nếu mở rộng) có thể được thêm vào để quản lý giá và tồn kho theo từng kênh:
- Channel: định nghĩa từng kênh (web, app, Shopee, Lazada, Facebook Shop) với cấu hình riêng.
- ProductChannel: bật/tắt sản phẩm trên từng kênh, mapping với externalproductid của marketplace.
- PriceList và PriceListItem: cho phép định nghĩa bảng giá theo kênh, theo nhóm khách hàng, theo thời gian.
- Inventory: quản lý tồn kho theo kho (warehouseid) và theo kênh nếu cần tách tồn.
Hệ thống landing page kéo thả, social auto-post, tracking đa nguồn yêu cầu ERD có các bảng Page, PageBlock, Template, SocialAccount, SocialPost, PublishSchedule được thiết kế độc lập nhưng liên kết chặt chẽ với Product, Campaign, SeoMeta. Ví dụ:
- Page gắn với SeoMeta và có type (landing, blog, home, collection), status (draft, published, archived).
- PageBlock lưu cấu trúc từng block (banner, productslider, testimonial), với JSON config để linh hoạt layout.
- SocialPost liên kết với Product hoặc Campaign, có thể sinh tự động từ Template, và PublishSchedule điều khiển thời điểm đăng trên từng SocialAccount.
ERD tốt giúp giảm trùng dữ liệu, hạn chế lỗi đơn hàng và tối ưu trải nghiệm mua hàng
Một trong những lợi ích quan trọng của ERD được thiết kế chuẩn là giảm trùng lặp dữ liệu và đảm bảo tính nhất quán xuyên suốt. Thông tin khách hàng được lưu tập trung trong bảng Customer, còn các bảng như Order, Cart, VisitSession chỉ tham chiếu bằng khóa ngoại. Khi cần lưu thông tin tại thời điểm giao dịch (ví dụ địa chỉ giao hàng, số điện thoại), hệ thống sử dụng các cột snapshot trong Order (shippingname, shippingphone, shippingaddresstext) thay vì nhân bản toàn bộ Customer.

Tương tự, thông tin sản phẩm được chuẩn hóa trong Product, ProductVariant, AttributeValue, còn các bảng như OrderItem, CartItem, CampaignProduct chỉ lưu khóa ngoại và các trường snapshot quan trọng như unitprice, discountamount, taxamount tại thời điểm giao dịch. Cách làm này cân bằng giữa chuẩn hóa và yêu cầu báo cáo lịch sử chính xác.
ERD tốt cũng giúp hạn chế lỗi đơn hàng nhờ các ràng buộc dữ liệu và quy tắc toàn vẹn:
- Constraint không cho phép đặt hàng với ProductVariant đã hết tồn kho (stockquantity < 0), kết hợp với trigger hoặc logic ứng dụng để khóa tồn.
- Constraint và index đảm bảo không cho phép áp dụng Coupon hết hạn (expiredat < currenttime) hoặc vượt quá số lần sử dụng tối đa.
- Unique index trên SKU, slug theo từng kênh hoặc toàn hệ thống để tránh trùng, gây lỗi đồng bộ.
Về trải nghiệm mua hàng, cấu trúc dữ liệu hợp lý giúp truy vấn nhanh danh mục, bộ lọc, gợi ý sản phẩm, lịch sử đơn hàng. Các bảng được index theo các trường thường lọc như categoryid, pricerange, brandid, attributes (color, size), cho phép xây dựng bộ lọc faceted search hiệu quả. Các bảng log như ActivityLog, SeoIssue, SeoAuditRun hỗ trợ đội ngũ kỹ thuật và marketing nhanh chóng phát hiện:
- Trang có tốc độ tải chậm, tỷ lệ thoát cao, cần tối ưu nội dung hoặc caching.
- URL lỗi 404 nhiều, cần bổ sung Redirect hoặc sửa internal link.
- Chiến dịch quảng cáo có CTR cao nhưng conversion thấp, cần xem lại landing page hoặc quy trình checkout.
Khi ERD được thiết kế tốt, việc mở rộng thêm tính năng như loyalty point, membership tier, referral program, subscription cũng trở nên dễ dàng hơn, chỉ cần bổ sung các entity mới (LoyaltyPointTransaction, MembershipTier, ReferralCode, SubscriptionPlan) và liên kết với các bảng cốt lõi mà không phá vỡ cấu trúc hiện tại.
Nhóm entity người dùng, khách hàng và phân quyền quản trị
Nhóm entity này tạo nền tảng cho việc quản lý danh tính, khách hàng và kiểm soát truy cập trong toàn hệ thống. User đóng vai trò kho danh tính trung tâm, lưu thông tin đăng nhập, trạng thái, bảo mật (mật khẩu băm, 2FA, reset token) và là điểm gắn với mô hình phân quyền qua Role, Permission, UserRole. Customer tập trung dữ liệu hồ sơ mua hàng, hành vi, phân khúc và liên kết chặt với Order, Cart, VisitSession để phục vụ phân tích CLV và marketing. CustomerAddress cho phép mỗi khách có nhiều địa chỉ giao/thu, được tham chiếu bởi đơn hàng nhưng vẫn lưu bản sao lịch sử. ActivityLog ghi nhận chi tiết thao tác của user trên nhiều loại entity, hỗ trợ audit, bảo mật và truy vết lỗi.

User lưu tài khoản đăng nhập, trạng thái, vai trò và thông tin xác thực
Bảng User đóng vai trò là identity store trung tâm cho toàn bộ hệ thống, chịu trách nhiệm quản lý danh tính, xác thực (authentication) và là điểm gắn kết với cơ chế phân quyền (authorization). Mỗi bản ghi trong bảng này đại diện cho một thực thể đăng nhập có thể là admin, nhân viên bán hàng, marketing, kho, chăm sóc khách hàng hoặc khách hàng cuối nếu hệ thống thiết kế dùng chung tài khoản.

Về mặt cấu trúc, ngoài các trường cơ bản như id, username, email, bảng User nên có các trường chuyên sâu hơn:
- passwordhash: lưu mật khẩu đã băm bằng các thuật toán an toàn như bcrypt, Argon2, kèm theo salt và cấu hình cost. Tuyệt đối không lưu mật khẩu thô.
- passwordalgorithm, passwordversion: cho phép hệ thống nhận biết thuật toán băm đang dùng, hỗ trợ nâng cấp dần (password rehash) khi người dùng đăng nhập.
- status: thể hiện trạng thái tài khoản (active, inactive, pendingverification, banned, softdeleted). Có thể dùng enum hoặc bảng tham chiếu.
- emailverifiedat, phoneverifiedat: phục vụ quy trình xác minh danh tính đa kênh.
- twofactorsecret, twofactorrecoverycodes: lưu thông tin 2FA (TOTP, SMS, email OTP) ở dạng mã hóa (encrypted at rest), không lưu thô.
- passwordresettoken, passwordresetexpiresat: phục vụ luồng quên mật khẩu, đảm bảo token có thời hạn và được hash thay vì lưu token thô.
- lastloginat, lastloginip, lastloginuseragent: hỗ trợ phát hiện bất thường bảo mật và hiển thị lịch sử đăng nhập cho người dùng.
- failedloginattempts, lockeduntil: triển khai cơ chế khóa tạm thời (temporary lockout) sau nhiều lần đăng nhập sai, giảm nguy cơ brute-force.
- createdby, updatedby: tham chiếu đến User khác, phục vụ audit nội bộ khi tài khoản được tạo bởi admin.
Argon2 được thiết kế như một hàm băm mật khẩu có độ khó về bộ nhớ, nhằm làm tăng chi phí của các cuộc tấn công dò mật khẩu hàng loạt bằng GPU hoặc phần cứng chuyên dụng. Thuật toán cho phép cấu hình thời gian xử lý, dung lượng bộ nhớ và mức song song, nên các tham số phải được lưu cùng chuỗi băm để hệ thống có thể xác minh và nâng cấp sau này. Salt phải được tạo ngẫu nhiên cho từng mật khẩu nhằm ngăn các tài khoản có cùng mật khẩu tạo ra cùng kết quả băm. Trường passwordversion đặc biệt hữu ích vì cho phép tái băm bằng cấu hình mới sau khi đăng nhập thành công mà không buộc toàn bộ người dùng đổi mật khẩu cùng lúc (Biryukov et al., 2016).
Về quan hệ phân quyền, có hai mô hình phổ biến:
- roleid trực tiếp trong User: phù hợp hệ thống đơn giản, mỗi user chỉ có một vai trò chính.
- Quan hệ nhiều-nhiều với bảng Role thông qua bảng UserRole: cho phép một user có nhiều vai trò (ví dụ vừa là Sales vừa là Support), linh hoạt hơn cho hệ thống lớn.
Trong ERD, User thường là khóa ngoại của nhiều bảng nghiệp vụ:
- ActivityLog: xác định ai thực hiện thao tác, phục vụ audit.
- Order: lưu createdby, updatedby, approvedby cho các đơn offline hoặc đơn cần duyệt.
- Campaign: gắn người phụ trách chiến dịch marketing, người phê duyệt ngân sách.
- PublishSchedule: lưu người lên lịch đăng bài, người chỉnh sửa nội dung.
Thiết kế này cho phép truy vết trách nhiệm chi tiết, hỗ trợ xây dựng các báo cáo như: hiệu suất theo nhân viên, số đơn bị hủy bởi từng user, số lần chỉnh sửa giá nhạy cảm. Về mặt kỹ thuật, cần chú ý:
- Đánh chỉ mục (index) trên username, email, status để tối ưu đăng nhập và tra cứu.
- Áp dụng unique constraint cho username, email (tùy chính sách) để tránh trùng lặp.
- Sử dụng soft delete (trường deletedat) nếu cần giữ lịch sử liên kết với các bảng khác.
Customer lưu hồ sơ mua hàng, thông tin liên hệ và lịch sử tương tác
Bảng Customer tập trung toàn bộ thông tin về khách mua hàng, đóng vai trò là customer profile cho các hoạt động bán hàng, chăm sóc và phân tích. Các trường cơ bản gồm: fullname, email, phone, gender, dateofbirth, registrationsource, firstchannel. Có thể mở rộng thêm:
- customercode: mã khách hàng nội bộ, hỗ trợ tích hợp với ERP/CRM khác.
- tags (JSON hoặc bảng liên kết): gắn nhãn linh hoạt như VIP, highrisk, CODreturnedmanytimes.
- preferredlanguage, preferredcurrency: phục vụ cá nhân hóa trải nghiệm.
- consentmarketing, consentupdatedat: lưu trạng thái đồng ý nhận marketing theo yêu cầu tuân thủ pháp lý.
Việc dự đoán Customer Lifetime Value đòi hỏi dữ liệu khách hàng phải được thu thập, làm sạch, liên kết và kiểm định theo một quy trình nhất quán. Nghiên cứu về quy trình dự báo CLV cho thấy chất lượng của kết quả phụ thuộc mạnh vào việc xác định đúng khách hàng, khoảng thời gian quan sát, giá trị giao dịch và hành vi mua lặp lại. Vì vậy, Customer, Order và OrderItem cần có khóa liên kết ổn định, còn dữ liệu khách vãng lai phải có cơ chế hợp nhất thận trọng dựa trên email, số điện thoại hoặc danh tính đã xác minh. Các trường tổng hợp như totalspent hay averageordervalue không nên là nguồn dữ liệu gốc, mà phải có khả năng tính lại từ lịch sử giao dịch đã được kiểm soát (Nie et al., 2024).
Để phục vụ phân tích CLV (Customer Lifetime Value) và hành vi mua hàng, bảng Customer thường có các trường tổng hợp:
- totalorders: tổng số đơn đã hoàn tất (có thể tách theo trạng thái).
- totalspent: tổng doanh thu ròng từ khách (sau giảm giá, hoàn tiền).
- firstorderat, lastorderat: phục vụ phân tích vòng đời, tần suất mua.
- averageordervalue, averageorderinterval: có thể tính offline và lưu để truy vấn nhanh.
- segmentid: liên kết với bảng CustomerSegment (ví dụ: New, Loyal, Churn Risk, VIP).
Về quan hệ với User, có hai mô hình:
- 1-1 Customer – User: khi khách hàng có tài khoản đăng nhập, customer.userid hoặc user.customerid sẽ liên kết trực tiếp.
- Customer độc lập: cho phép đặt hàng không cần đăng ký; lúc này Customer có thể được tạo từ dữ liệu đơn hàng (Order) và dần dần hợp nhất (merge) nếu phát hiện trùng email/phone.
Về lịch sử tương tác, Customer liên kết với nhiều bảng:
- Order: lịch sử mua hàng, trạng thái thanh toán, kênh bán.
- Cart: giỏ hàng đang mở, giỏ hàng bị bỏ quên (abandoned cart).
- VisitSession: phiên truy cập website/app, nguồn traffic, hành vi duyệt trang.
- SocialMetric: tương tác trên mạng xã hội, phản hồi chiến dịch.
- CustomerSegment: phân khúc động hoặc tĩnh, phục vụ chiến dịch cá nhân hóa.
Trong ERD, cần đảm bảo:
- Khóa ngoại từ Order, Cart, VisitSession về Customer có chỉ mục để truy vấn nhanh các báo cáo như top khách hàng theo doanh thu, tần suất mua, tỷ lệ quay lại.
- Có cơ chế deduplication (hợp nhất khách hàng trùng) dựa trên email/phone, tránh phân mảnh dữ liệu.
- Thiết kế phù hợp cho các truy vấn phân tích lớn (có thể tách sang data warehouse, nhưng vẫn giữ ERD giao dịch rõ ràng).
Role và Permission quản lý quyền admin, nhân viên bán hàng, marketing, kho và chăm sóc khách hàng
Nhóm bảng Role và Permission hiện thực hóa mô hình RBAC (Role-Based Access Control), giúp hệ thống an toàn, dễ quản trị và mở rộng. Bảng Role lưu các vai trò như Super Admin, Sales, Marketing, Warehouse, Support với các thuộc tính:
- name: tên hiển thị cho quản trị viên.
- slug: định danh kỹ thuật, không dấu, dùng trong code (ví dụ: superadmin, sales, marketing).
- description: mô tả phạm vi trách nhiệm, giúp tránh nhầm lẫn khi phân quyền.
- issystem: đánh dấu role hệ thống không được xóa hoặc sửa slug.
RBAC được xây dựng trên nguyên tắc người dùng không nhận trực tiếp toàn bộ quyền thao tác mà được gán vào các vai trò phù hợp với chức trách. Mô hình chuẩn phân biệt người dùng, vai trò, quyền hạn, phiên làm việc và các quan hệ gán quyền, đồng thời hỗ trợ phân cấp vai trò và ràng buộc trách nhiệm. Trong ERD bán hàng, điều này cho phép quyền viewfinance, editorder hoặc manageinventory được quản trị tập trung qua RolePermission thay vì lưu rải rác trong User. Thiết kế RBAC còn hỗ trợ nguyên tắc đặc quyền tối thiểu và phân tách nhiệm vụ, chẳng hạn người tạo yêu cầu hoàn tiền không đồng thời là người phê duyệt giao dịch đó (Sandhu et al., 1996).

Bảng Permission lưu các quyền chi tiết, thường được thiết kế theo dạng resource + action, ví dụ: vieworder, editorder, manageinventory, managecampaign, viewfinance. Có thể bổ sung:
- group: nhóm quyền theo module (Order, Product, Customer, Finance).
- scope: phạm vi dữ liệu (all, own, team, branch), hỗ trợ phân quyền theo tổ chức.
Quan hệ giữa Role và Permission là nhiều-nhiều thông qua bảng trung gian RolePermission:
- roleid, permissionid: cặp khóa ngoại, thường có unique constraint để tránh trùng.
- Có thể thêm grantedby, grantedat nếu cần audit chi tiết.
Việc gán vai trò cho user được thực hiện qua:
- UserRole (nếu một user có nhiều role): cho phép cấu hình linh hoạt, ví dụ một nhân viên vừa thuộc nhóm Sales vừa thuộc nhóm Support.
- Hoặc trường roleid trong User nếu hệ thống đơn giản, mỗi user chỉ có một vai trò.
Trong ERD, các ràng buộc FK đảm bảo chỉ những role và permission hợp lệ mới được gán, tránh lỗi cấu hình gây lộ dữ liệu nhạy cảm như doanh thu, chi phí quảng cáo, dữ liệu khách hàng. Ở tầng ứng dụng, có thể triển khai thêm:
- Policy/Guard kiểm tra quyền theo từng hành động cụ thể.
- Caching danh sách permission của user để giảm truy vấn, nhưng cần cơ chế làm mới khi thay đổi phân quyền.
CustomerAddress lưu nhiều địa chỉ giao hàng, thanh toán và thông tin nhận hàng
Bảng CustomerAddress giải quyết nhu cầu mỗi khách hàng có nhiều địa chỉ: nhà riêng, công ty, địa chỉ người thân. Đây là bảng quan trọng cho luồng đặt hàng, giao nhận và tính phí vận chuyển. Các thuộc tính chính gồm:
- customerid: khóa ngoại đến Customer, quan hệ 1-n.
- fullname: tên người nhận, có thể khác với tên chủ tài khoản.
- phone: số điện thoại người nhận, phục vụ shipper liên hệ.
- addressline1, addressline2: địa chỉ chi tiết (số nhà, tên đường, tòa nhà).
- ward, district, province, country: các cấp địa lý, nên chuẩn hóa theo danh mục địa lý nếu cần tính phí vận chuyển chính xác.
- postalcode (nếu áp dụng): hỗ trợ tích hợp với đơn vị vận chuyển quốc tế.
- type: phân loại địa chỉ (shipping, billing, both, office, home).
- isdefaultshipping, isdefaultbilling: đánh dấu địa chỉ mặc định, hỗ trợ chọn nhanh khi đặt hàng.
- note: ghi chú cho shipper (ví dụ: gọi trước khi giao, giao giờ hành chính).

Trong ERD, CustomerAddress liên kết 1-n với Customer và có thể được tham chiếu bởi Order thông qua các trường như shippingaddressid, billingaddressid. Tuy nhiên, để đảm bảo tính lịch sử, Order vẫn nên lưu bản sao địa chỉ tại thời điểm đặt hàng (denormalization có kiểm soát):
- Các trường như shippingfullname, shippingphone, shippingaddressline1, ... được copy từ CustomerAddress khi tạo đơn.
- Nếu khách hàng chỉnh sửa hoặc xóa địa chỉ sau này, thông tin trên đơn cũ vẫn giữ nguyên, đảm bảo tính toàn vẹn lịch sử và đối soát với đơn vị vận chuyển.
Về mặt kỹ thuật, cần chú ý:
- Đánh index trên customerid để truy vấn nhanh danh sách địa chỉ của khách.
- Có thể thêm geolat, geolng nếu cần tích hợp bản đồ, tối ưu tuyến giao hàng.
- Áp dụng ràng buộc logic để đảm bảo mỗi khách chỉ có tối đa một địa chỉ mặc định cho từng loại (shipping/billing).
ActivityLog ghi lại thao tác quan trọng để kiểm soát bảo mật và truy vết lỗi
Bảng ActivityLog là thành phần cốt lõi cho bảo mật, audit và phân tích hành vi nội bộ. Mỗi bản ghi lưu lại một sự kiện như đăng nhập, đổi mật khẩu, tạo đơn hàng, sửa giá, cập nhật tồn kho, thay đổi cấu hình SEO. Các thuộc tính thường gồm:
- userid: tham chiếu đến User thực hiện hành động (có thể null cho các sự kiện hệ thống).
- action: mã hành động (login, logout, createorder, updateprice, changesetting).
- entitytype, entityid: cặp định danh đối tượng bị tác động (Order, Product, Campaign, Customer...).
- ipaddress: địa chỉ IP nguồn, hỗ trợ phát hiện truy cập bất thường.
- useragent: thông tin trình duyệt/thiết bị, phục vụ điều tra sự cố.
- createdat: thời điểm xảy ra sự kiện.
- metadata (JSON): chứa dữ liệu chi tiết như giá trị trước/sau khi cập nhật, tham số request, kết quả xử lý.
Nhật ký audit chỉ có giá trị điều tra khi nội dung của nó không thể bị chỉnh sửa âm thầm sau sự cố. Nghiên cứu về secure audit log chỉ ra rằng log lưu trên một máy đã bị xâm nhập có thể bị kẻ tấn công xóa hoặc thay đổi để che giấu hoạt động. Vì vậy, ngoài userid, action, entitytype, entityid và thời gian, hệ thống có thể bổ sung chuỗi băm liên kết giữa các bản ghi, chữ ký hoặc cơ chế chuyển log sang vùng lưu trữ độc lập. ActivityLog cũng không nên chứa mật khẩu, token, CVV hoặc dữ liệu nhạy cảm nguyên dạng, vì bản thân kho log có thể trở thành nguồn rò rỉ dữ liệu nghiêm trọng (Schneier & Kelsey, 1999).

Thiết kế entitytype, entityid theo kiểu polymorphic giúp bảng ActivityLog linh hoạt, có thể log mọi loại đối tượng mà không cần thêm cột mới. Một số thực hành tốt:
- Chuẩn hóa giá trị action (danh sách cố định) để dễ lọc và báo cáo.
- Giới hạn kích thước metadata, tránh lưu dữ liệu quá lớn gây phình bảng.
- Ẩn hoặc mã hóa các trường nhạy cảm trong metadata (ví dụ: token, một phần số thẻ).
Trong ERD, ActivityLog liên kết với User qua khóa ngoại userid, và liên kết lỏng với các entity khác thông qua cặp entitytype, entityid. Do khối lượng log thường rất lớn, cần cân nhắc:
- Phân vùng (partition) theo thời gian (theo tháng/quý) để tối ưu truy vấn và dọn dẹp dữ liệu cũ.
- Có thể lưu ở database riêng hoặc cluster riêng để không ảnh hưởng hiệu năng giao dịch chính.
- Xây dựng chỉ mục phù hợp cho các truy vấn phổ biến: theo userid, action, entitytype, entityid, created_at.
Nhờ cấu trúc này, đội ngũ kỹ thuật và vận hành có thể nhanh chóng truy vết: ai đã làm gì, vào thời điểm nào, trên đối tượng nào, từ thiết bị và IP nào, hỗ trợ phát hiện gian lận, khôi phục cấu hình, cũng như phân tích hiệu quả quy trình nội bộ.
Nhóm entity sản phẩm, danh mục và thuộc tính bán hàng
Khối entity này mô hình hóa đầy đủ dữ liệu phục vụ bán hàng, điều hướng và tối ưu SEO. Product là trung tâm, lưu thông tin hiển thị, giá, trạng thái, SEO và kết nối tới Category, Brand, ProductVariant, Media, Inventory, Campaign, Review. Category xây dựng cây danh mục cha – con, hỗ trợ menu đa cấp, breadcrumb, landing page SEO và gán sản phẩm theo mô hình 1-n hoặc n-n. Brand chuẩn hóa thương hiệu, tách logo, mô tả, metadata và trang thương hiệu để filter và làm chiến dịch marketing. ProductVariant chi tiết hóa từng SKU với giá, tồn kho, barcode, thuộc tính biến thể. Cuối cùng, Attribute / AttributeValue định nghĩa bộ thuộc tính linh hoạt cho filter, so sánh, faceted navigation và sinh biến thể, đảm bảo dữ liệu vừa chuẩn hóa vừa tối ưu hiệu năng truy vấn.

Product lưu tên sản phẩm, slug, mô tả, trạng thái, giá cơ bản và thông tin SEO
Bảng Product là trung tâm của khối bán hàng, đóng vai trò như entity gốc để liên kết toàn bộ dữ liệu thương mại, nội dung và SEO. Ở mức tối thiểu, bảng nên có các cột:
- id (PK, auto increment hoặc UUID)
- name: tên hiển thị của sản phẩm, dùng cho UI, SEO title mặc định và các khối recommendation.
- slug: chuỗi duy nhất phục vụ routing URL thân thiện; nên có unique index và constraint để tránh trùng.
- skubase: mã SKU gốc cho toàn bộ sản phẩm (khác với SKU từng biến thể), dùng cho tích hợp ERP hoặc đồng bộ kênh bán.
- description: mô tả chi tiết, thường lưu dạng HTML hoặc rich text.
- shortdescription: mô tả ngắn, dùng cho listing, snippet SEO, block gợi ý.
- baseprice: giá cơ bản hoặc giá tham chiếu; có thể dùng làm giá mặc định khi không có biến thể.
- status: trạng thái workflow, ví dụ: draft, active, inactive, archived, hidden.
- brandid: FK tới bảng Brand, hỗ trợ filter theo thương hiệu.
- categoryid: FK tới danh mục chính (primary category) để xây dựng URL và breadcrumb.
- createdat, updatedat, publishedat: mốc thời gian phục vụ audit log, scheduling, cache invalidation.

Để tối ưu SEO và nội dung, có thể lưu trực tiếp các trường:
- seotitle: override cho thẻ <title> nếu khác name.
- seodescription: meta description tùy chỉnh.
- seoh1: heading chính trên trang chi tiết sản phẩm.
- canonicalurl: xử lý trùng lặp nội dung, đặc biệt khi sản phẩm xuất hiện ở nhiều danh mục.
Trong thiết kế chuyên sâu, các trường SEO có thể được tách sang bảng SeoMeta (1-1 với Product) để tái sử dụng cho nhiều loại entity (Product, Category, Brand, LandingPage). Khi đó, Product chỉ cần lưu seometaid.
Về quan hệ trong ERD, Product kết nối với nhiều bảng nghiệp vụ:
- Category: 1-n (một danh mục chính cho nhiều sản phẩm) hoặc n-n qua bảng ProductCategory.
- Brand: 1-n qua brandid.
- ProductVariant: 1-n, mỗi Product có nhiều biến thể giá, màu, size.
- ProductMedia: 1-n, quản lý ảnh, video, 3D model; có thể có trường isprimary, sortorder.
- AttributeValue (qua bảng trung gian ProductAttributeValue): gắn bộ thuộc tính filter, so sánh.
- OrderItem, CartItem: thường tham chiếu ProductVariant, nhưng có thể tham chiếu Product khi không có biến thể.
- CampaignProduct: gắn sản phẩm với các chiến dịch khuyến mãi, flash sale, bundle.
- Review: 1-n, lưu đánh giá, rating, UGC.
- Inventory: thường gắn với ProductVariant; trong một số mô hình đơn giản có thể gắn trực tiếp với Product.
Về tối ưu hiệu năng, nên tạo index kết hợp trên các cột:
- (slug): unique index cho routing.
- (status, categoryid): phục vụ listing theo danh mục.
- (name) hoặc FULLTEXT(name, shortdescription): hỗ trợ search nội bộ.
Trong các hệ thống lớn, có thể bổ sung các cột như isfeatured, sortorder, ratingaverage, ratingcount, salescount để phục vụ sắp xếp và recommendation mà không phải join nhiều bảng.
Category lưu cây danh mục cha con để hỗ trợ menu, breadcrumb và landing page SEO
Bảng Category mô hình hóa cấu trúc phân cấp sản phẩm, đóng vai trò quan trọng cho điều hướng, SEO và phân quyền merchandising. Các trường cốt lõi:
- id (PK)
- name: tên danh mục hiển thị trên menu, breadcrumb.
- slug: dùng trong URL, thường kết hợp với path đầy đủ.
- parentid: FK self-reference, cho phép tạo cây danh mục nhiều cấp.
- description: nội dung giới thiệu, có thể dùng cho landing page SEO.
- level: độ sâu trong cây (root = 0 hoặc 1), giúp query nhanh các tầng.
- sortorder: thứ tự hiển thị trong menu, filter.
- isactive: bật/tắt danh mục mà không xóa dữ liệu.
Để tối ưu SEO và nội dung, Category có thể có thêm:
- seotitle, seodescription, seoh1: tối ưu riêng cho từng landing page danh mục.
- bannerimageid: tham chiếu tới bảng media để hiển thị banner trên trang danh mục.
Trong ERD, Category có quan hệ 1-n với chính nó qua parentid, cho phép xây dựng:
- Menu đa cấp: top-level category, subcategory, micro category.
- Breadcrumb: truy vết từ root đến category hiện tại.
- Landing page SEO: mỗi node trong cây có thể là một trang SEO riêng.
Về quan hệ với Product:
- Mô hình đơn giản: 1-n, mỗi Product có một categoryid chính.
- Mô hình linh hoạt: n-n qua bảng ProductCategory(productid, categoryid, isprimary), cho phép một sản phẩm xuất hiện ở nhiều danh mục, nhưng vẫn xác định được danh mục chính.
Để tối ưu truy vấn cây danh mục, có thể áp dụng:
- Nested Set: thêm cột lft, rgt, cho phép query toàn bộ subtree bằng một câu lệnh SELECT duy nhất; phù hợp cho cây ít thay đổi.
- Materialized Path: thêm cột path (ví dụ:
/electronics/phone/smartphone) hoặc pathids (chuỗi id); dễ cập nhật, dễ build URL, phù hợp cho hệ thống thay đổi thường xuyên.
Index nên tập trung vào:
- (parentid, sortorder): render menu nhanh.
- (slug) hoặc (path): routing URL.
- (isactive): filter danh mục hiển thị.
Brand lưu thương hiệu, mô tả, logo và trang thương hiệu phục vụ tìm kiếm thương mại
Bảng Brand tách riêng thương hiệu khỏi Product để chuẩn hóa dữ liệu, hỗ trợ filter và xây dựng trang thương hiệu chuyên sâu. Các trường quan trọng:
- id (PK)
- name: tên thương hiệu hiển thị.
- slug: dùng cho URL trang thương hiệu.
- description: nội dung giới thiệu, lịch sử, điểm mạnh.
- logomediaid: FK tới bảng media, lưu logo chuẩn.
- websiteurl: link tới trang chính thức của thương hiệu.
- country: quốc gia xuất xứ, hỗ trợ filter hoặc hiển thị trust signal.
- isfeatured: đánh dấu thương hiệu nổi bật để ưu tiên hiển thị.
Trang thương hiệu thường là một landing page tập trung, hiển thị:
- Logo, mô tả, nội dung marketing.
- Danh sách sản phẩm theo Brand, có thể filter thêm theo Category, price range, attribute.
- Review tổng hợp, rating trung bình cho thương hiệu.
Trong ERD, Brand liên kết 1-n với Product qua brandid. Ngoài ra, Brand có thể liên kết với:
- SeoMeta: tối ưu SEO riêng cho trang thương hiệu.
- Page: nếu hệ thống CMS tách riêng nội dung trang.
- Campaign: dùng thương hiệu làm anchor cho các chiến dịch marketing, banner, voucher.
Việc tách Brand thành bảng riêng giúp:
- Chuẩn hóa dữ liệu: tránh lặp tên thương hiệu trong Product.
- Dễ dàng quản lý logo, nội dung, metadata.
- Tạo bộ lọc theo thương hiệu trong trang danh mục, hỗ trợ commercial search và tăng khả năng chuyển đổi.
ProductVariant lưu biến thể theo màu sắc, kích thước, dung lượng, chất liệu hoặc phiên bản
Bảng ProductVariant cho phép mô hình hóa các biến thể chi tiết của một sản phẩm, mỗi biến thể có thể có giá, tồn kho, barcode và thuộc tính riêng. Các trường cốt lõi:
- id (PK)
- productid: FK tới Product.
- sku: mã SKU duy nhất cho từng biến thể; bắt buộc unique để đồng bộ với WMS/ERP.
- variantname: tên hiển thị, ví dụ: “Đỏ / M”, “128GB / Black”.
- price: giá bán hiện tại của biến thể.
- compareatprice: giá gốc hoặc giá trước khuyến mãi, dùng hiển thị giảm giá.
- barcode: mã vạch (EAN, UPC) phục vụ quét tại kho, POS.
- weight: khối lượng, dùng cho tính phí vận chuyển.
- status: trạng thái riêng cho biến thể (có thể khác Product), ví dụ: active, outofstock, discontinued.
- variantattributes: có thể lưu dạng JSON (key-value) hoặc tách sang bảng liên kết.
Trong ERD, ProductVariant liên kết với:
- Inventory: 1-1 hoặc 1-n (nếu quản lý tồn kho theo kho, theo kênh bán).
- OrderItem, CartItem: tham chiếu trực tiếp tới biến thể để ghi nhận chính xác SKU, giá tại thời điểm mua.
- ProductMedia: có thể gắn ảnh riêng cho từng biến thể (ví dụ ảnh màu đỏ, màu xanh).
- StockMovement: log nhập – xuất – điều chỉnh tồn kho theo từng SKU.
Đối với hệ thống có nhiều thuộc tính biến thể (màu, size, dung lượng, chất liệu…), nên dùng bảng trung gian VariantAttributeValue:
- variantid
- attributeid
- attributevalueid
Cách này giúp:
- Xây dựng giao diện chọn biến thể (color swatch, size selector) dựa trên tập AttributeValue.
- Đảm bảo tính nhất quán giữa thuộc tính dùng cho filter và thuộc tính dùng cho biến thể.
- Hỗ trợ logic sinh tự động SKU từ tổ hợp thuộc tính.
Index quan trọng cho ProductVariant gồm:
- (sku): unique index.
- (productid, status): query nhanh các biến thể đang bán của một sản phẩm.
- (barcode): hỗ trợ tra cứu tại kho.
Attribute và AttributeValue lưu bộ thuộc tính lọc sản phẩm cho tìm kiếm, so sánh và faceted navigation
Bảng Attribute định nghĩa schema thuộc tính sản phẩm, tách biệt khỏi dữ liệu sản phẩm cụ thể để dễ mở rộng và quản trị. Các trường chính:
- id (PK)
- name: tên thuộc tính hiển thị, ví dụ: “Màu sắc”, “Kích thước”, “Chất liệu”.
- code: mã kỹ thuật (ví dụ: color, size, material, origin), dùng trong API, mapping với search engine.
- type: kiểu dữ liệu, ví dụ: select, multi-select, text, number, boolean, range.
- isfilterable: đánh dấu thuộc tính được dùng cho faceted navigation.
- isvariant: đánh dấu thuộc tính tham gia tạo biến thể (color, size…).
- sortorder: thứ tự hiển thị trong UI filter hoặc bảng thông số kỹ thuật.
Faceted navigation hiệu quả khi mỗi facet đại diện cho một chiều phân loại có ý nghĩa, còn giá trị facet phải nhất quán và hỗ trợ người dùng thu hẹp tập kết quả theo từng bước. Nghiên cứu hành vi trên giao diện tìm kiếm theo facet cho thấy người dùng kết hợp nhiều loại facet và thay đổi chiến lược tương tác trong quá trình khám phá thông tin. Vì vậy, Attribute, AttributeValue và ProductAttributeValue phải dùng mã chuẩn, tránh tạo các biến thể đồng nghĩa như “Đỏ”, “màu đỏ” và “Red” thành ba giá trị khác nhau. Các thuộc tính dùng cho lọc cũng cần tách khỏi nội dung mô tả tự do, bởi dữ liệu không chuẩn hóa sẽ làm bộ lọc phân mảnh, kết quả đếm sai và trải nghiệm tìm kiếm thiếu tin cậy (Niu & Hemminger, 2015).
Bảng AttributeValue lưu các giá trị cụ thể cho từng thuộc tính:
- id (PK)
- attributeid: FK tới Attribute.
- value: giá trị hiển thị, ví dụ: “Đỏ”, “XL”, “Inox 304”.
- slug: dùng trong URL filter hoặc SEO (ví dụ: mau-do).
- sortorder: thứ tự hiển thị trong danh sách filter.
- colorcode (nếu là màu): mã hex hoặc tên màu, dùng render swatch.
Để gắn thuộc tính với sản phẩm, ERD thường dùng bảng trung gian ProductAttributeValue:
- productid
- attributeid
- attributevalueid
Nếu thuộc tính gắn với biến thể thay vì sản phẩm chung, có thể mở rộng thêm cột variantid để phân biệt:
- Thuộc tính cấp Product: thông số kỹ thuật chung (ví dụ: “Loại máy giặt: Cửa trước”).
- Thuộc tính cấp Variant: màu, size, dung lượng… thay đổi theo từng biến thể.
Thiết kế này hỗ trợ mạnh cho faceted navigation:
- Lọc theo nhiều tiêu chí đồng thời (brand, price, color, size, material…).
- Tích hợp với search engine (Elasticsearch, OpenSearch, Solr) bằng cách map Attribute/AttributeValue sang field index.
- Tạo URL filter thân thiện SEO dựa trên slug của AttributeValue.
Các trường isfilterable và isvariant trong Attribute giúp:
- Phân biệt thuộc tính dùng cho filter (hiển thị ở sidebar, top filter) với thuộc tính chỉ để hiển thị thông tin.
- Phân biệt thuộc tính tham gia tạo biến thể (color, size) với thuộc tính mô tả (material, origin).
Về hiệu năng, nên index:
- (attributeid) trên AttributeValue.
- (productid, attributeid) trên ProductAttributeValue.
- Trong các hệ thống lớn, có thể denormalize một phần thuộc tính sang search index để query filter nhanh mà không cần join nhiều bảng.
Nhóm entity hình ảnh, nội dung và trình kéo thả landing page
Nhóm entity này tạo thành nền tảng cho hệ thống landing page kéo thả, kết nối chặt chẽ giữa tài nguyên media, sản phẩm và trang nội dung. Media tập trung toàn bộ metadata của ảnh, video, file tĩnh, hỗ trợ SEO, accessibility và tích hợp CDN, đồng thời liên kết linh hoạt qua quan hệ trực tiếp hoặc bảng đa hình để tái sử dụng file, tránh trùng lặp. ProductMedia gắn nhiều media cho sản phẩm và biến thể, kiểm soát ảnh chính, thứ tự hiển thị và tối ưu truy vấn gallery. Page mô hình hóa các trang tĩnh/landing, quản lý slug, type, trạng thái xuất bản và SEO. PageBlock cho phép lắp ghép trang bằng các block cấu hình JSON, còn Template định nghĩa khung layout, vùng block và constraint, giúp marketing tự tạo landing page mà vẫn đảm bảo chuẩn UI/UX.

Media lưu ảnh sản phẩm, video, alt text, caption, dung lượng và định dạng file
Bảng Media đóng vai trò như một Digital Asset Repository, lưu trữ metadata cho toàn bộ tài nguyên tĩnh: ảnh sản phẩm, banner, icon, video, file hướng dẫn, file tải về. Thay vì nhúng trực tiếp đường dẫn file vào từng bảng nghiệp vụ, toàn bộ thông tin mô tả file được gom về một nơi, giúp quản lý tập trung, dễ kiểm soát vòng đời tài nguyên và tối ưu chi phí lưu trữ.

Các thuộc tính quan trọng thường có:
- filename: tên file gốc hoặc tên đã chuẩn hóa (slugified) để thân thiện URL.
- filepath: đường dẫn đầy đủ đến file (có thể là path trên object storage như S3, GCS, hoặc CDN URL tương đối).
- filetype: MIME type (image/jpeg, image/png, video/mp4, application/pdf…) phục vụ cho việc render đúng loại nội dung.
- filesize: dung lượng file (byte) để kiểm soát quota, hiển thị thông tin tải về, và tối ưu băng thông.
- width, height: kích thước pixel cho ảnh/video poster, hỗ trợ tính toán layout responsive và lazy-loading.
- alttext: mô tả ngắn gọn nội dung hình ảnh, cực kỳ quan trọng cho SEO hình ảnh và accessibility (screen reader).
- caption: chú thích hiển thị dưới ảnh/video, có thể chứa nội dung marketing hoặc credit.
- createdat, updatedat: phục vụ audit log, thống kê thời gian upload.
- uploadedby: tham chiếu đến user/admin đã upload, hỗ trợ phân quyền và truy vết.
Trường alttext thường được chuẩn hóa theo guideline SEO: mô tả ngắn, chứa từ khóa chính, nhưng không nhồi nhét keyword. Trong hệ thống lớn, có thể áp dụng quy tắc bắt buộc nhập alttext cho ảnh hiển thị trên landing page hoặc trang sản phẩm để đảm bảo chất lượng SEO tổng thể.
Trong ERD, Media liên kết với nhiều entity khác nhau như ProductMedia, PageBlock, Brand, SeoMeta, SocialPost. Có hai hướng thiết kế phổ biến:
- Liên kết trực tiếp: mỗi bảng nghiệp vụ có cột thumbnailmediaid, bannermediaid… trỏ thẳng đến Media. Cách này đơn giản, dễ hiểu nhưng tăng số lượng cột.
- Bảng trung gian đa hình (polymorphic) như MediaRelation với các cột:
- entitytype: loại entity (Product, Page, Brand, Category…)
- entityid: khóa chính của bản ghi tương ứng.
- mediaid: tham chiếu đến Media.
- Có thể bổ sung role (thumbnail, gallery, banner, icon) và sortorder.
Thiết kế dùng MediaRelation giúp:
- Tái sử dụng cùng một file cho nhiều entity mà không nhân bản file vật lý.
- Dễ dàng truy vấn ngược: tìm tất cả nơi đang dùng một media để tránh xóa nhầm.
- Hỗ trợ dọn dẹp file không dùng (orphan media) bằng cách so sánh Media với MediaRelation.
Đối với hệ thống có tích hợp CDN, bảng Media cũng có thể lưu thêm các trường như cdnurl, variantconfig (JSON mô tả các phiên bản resize, webp, thumbnail) để tối ưu hiệu năng render trên landing page và trang sản phẩm.
ProductMedia quản lý nhiều ảnh cho từng sản phẩm, biến thể và thứ tự hiển thị
Bảng ProductMedia giải quyết nhu cầu gắn nhiều media cho một sản phẩm, đồng thời hỗ trợ ảnh riêng cho từng biến thể (màu sắc, size…). Thay vì lưu một danh sách URL trong Product, việc tách thành bảng con giúp quản lý linh hoạt, dễ mở rộng và tối ưu truy vấn.

Các cột thường gặp:
- productid: khóa ngoại đến Product, bắt buộc.
- variantid (nullable): khóa ngoại đến ProductVariant, cho phép gắn media riêng cho từng biến thể; nếu null nghĩa là media áp dụng chung cho toàn bộ sản phẩm.
- mediaid: tham chiếu đến bảng Media, cho phép tái sử dụng file.
- sortorder: số nguyên dùng để sắp xếp thứ tự hiển thị trong gallery.
- ismain: cờ boolean đánh dấu ảnh đại diện chính (thumbnail) cho sản phẩm hoặc biến thể.
- Có thể bổ sung label (front, back, detail, lifestyle) để phân loại ảnh.
Logic nghiệp vụ thường áp dụng:
- Mỗi productid, variantid nên có tối đa một bản ghi ismain = true (ràng buộc unique partial index nếu DB hỗ trợ).
- Nếu không có ảnh riêng cho biến thể, hệ thống fallback sang ảnh chung của sản phẩm.
- sortorder được dùng để render gallery theo thứ tự ưu tiên: ảnh chính, ảnh chi tiết, ảnh lifestyle…
Trong ERD, ProductMedia liên kết 1-n với Product và Media, đồng thời có thể liên kết với ProductVariant. Các chỉ mục quan trọng:
- INDEX(productid, sortorder): phục vụ truy vấn danh sách ảnh cho trang chi tiết sản phẩm.
- INDEX(variantid, sortorder): tối ưu khi hiển thị ảnh theo từng biến thể (chọn màu khác đổi ảnh ngay).
- INDEX(mediaid): hỗ trợ kiểm tra media đang được dùng ở những sản phẩm nào.
Việc tách bảng ProductMedia khỏi Product còn giúp:
- Không làm phình to bảng Product khi số lượng ảnh tăng mạnh.
- Cho phép thao tác thêm/xóa/sắp xếp media độc lập với dữ liệu sản phẩm (ít lock, ít conflict khi nhiều người chỉnh sửa).
- Dễ dàng mở rộng hỗ trợ video, 3D model, AR asset mà không phải thay đổi cấu trúc Product.
Page lưu trang chủ, landing page, trang chính sách, trang chiến dịch và trạng thái xuất bản
Bảng Page mô hình hóa các trang nội dung tĩnh và landing page, đóng vai trò như một CMS nhẹ cho hệ thống thương mại điện tử. Thay vì hard-code từng trang trong code, mọi thông tin cấu hình trang được lưu trong DB, cho phép đội marketing tự quản lý mà không phụ thuộc vào developer.

Các thuộc tính quan trọng:
- title: tiêu đề trang, thường map với <title> trong HTML và heading chính.
- slug: phần cuối URL (ví dụ: /about-us, /chinh-sach-bao-mat), thường có ràng buộc unique.
- type: phân loại trang (home, landing, policy, blog, campaign…). Mỗi type có thể gắn với layout, template và logic tracking khác nhau.
- status: trạng thái (draft, published, scheduled, archived) để kiểm soát vòng đời nội dung.
- publishedat: thời điểm xuất bản, dùng cho việc schedule landing page hoặc chiến dịch.
- templateid: tham chiếu đến Template, quyết định bố cục và block mặc định.
- authorid: người tạo hoặc người chịu trách nhiệm nội dung.
- Có thể có updatedby, lastreviewedat để phục vụ quy trình kiểm duyệt.
Trong ERD, Page liên kết với PageBlock, Template, SeoMeta, Campaign, SitemapUrl:
- Page – PageBlock: 1-n, mỗi trang gồm nhiều block nội dung kéo thả.
- Page – Template: mỗi trang gắn với một template để xác định layout.
- Page – SeoMeta: lưu meta title, description, open graph, schema markup.
- Page – Campaign: mapping trang landing với chiến dịch marketing (UTM, tracking).
- Page – SitemapUrl: đảm bảo trang được index đúng trong sitemap.xml.
Các ràng buộc quan trọng:
- Unique(slug) hoặc Unique(fullpath) nếu có cấu trúc thư mục (ví dụ: /blog/slug-bai-viet).
- Ràng buộc logic: chỉ status = published và publishedat <= now() mới được render công khai.
- Đối với type = home, thường chỉ cho phép tối đa một bản ghi active.
Thiết kế này cho phép:
- Đội marketing tạo landing page cho chiến dịch mới bằng cách chọn template, cấu hình block, đặt slug và schedule thời gian xuất bản.
- Đảm bảo mỗi trang có metadata SEO đầy đủ, URL chuẩn, và được đưa vào sitemap đúng cách mà không cần sửa code.
- Hỗ trợ A/B testing bằng cách tạo nhiều Page cùng type/campaign nhưng khác slug hoặc tham số tracking.
PageBlock lưu các khối kéo thả như banner, sản phẩm nổi bật, CTA, FAQ, review và form
Bảng PageBlock là lõi của trình kéo thả landing page, cho phép xây dựng trang từ các khối nội dung tái sử dụng. Mỗi bản ghi đại diện cho một block với loại, vị trí và cấu hình riêng, giúp tách biệt rõ ràng giữa layout và content.

Các trường chính:
- pageid: khóa ngoại đến Page, xác định block thuộc trang nào.
- blocktype: loại block (banner, slider, productlist, CTA, testimonial, FAQ, review, form, video…). Mỗi loại sẽ có schema cấu hình riêng.
- position: có thể là tên vùng (header, body, footer, sidebar) hoặc index logic trong layout.
- sortorder: thứ tự hiển thị trong cùng một position, hỗ trợ kéo thả sắp xếp.
- settings (JSON): lưu cấu hình chi tiết, ví dụ:
- Tiêu đề, mô tả, màu sắc, font, alignment.
- Danh sách productid hoặc categoryid cho block sản phẩm nổi bật.
- campaignid để gắn tracking.
- mediaid cho banner, background, icon.
- Cấu hình form: field, validation, endpoint.
- isactive: cờ bật/tắt block mà không cần xóa.
Trong ERD, PageBlock liên kết 1-n với Page và có thể tham chiếu đến Product, Category, Campaign, Media thông qua:
- Dữ liệu trong settings (lưu ID trực tiếp trong JSON).
- Hoặc các bảng liên kết phụ như PageBlockProduct, PageBlockCategory để đảm bảo tính toàn vẹn khóa ngoại.
Thiết kế dạng block mang lại nhiều lợi ích:
- Tái sử dụng cấu trúc: cùng một blocktype có thể dùng cho nhiều trang, chỉ khác settings.
- Dễ A/B testing: tạo hai block cùng loại với cấu hình khác nhau, bật/tắt luân phiên hoặc hiển thị theo phân đoạn traffic.
- Tối ưu chuyển đổi: nhanh chóng thay đổi vị trí CTA, thêm/bớt testimonial, chỉnh sửa FAQ mà không ảnh hưởng logic backend.
- Hỗ trợ versioning: có thể lưu lịch sử thay đổi settings để rollback khi cần.
Về mặt kỹ thuật, settings (JSON) nên được thiết kế theo schema rõ ràng cho từng blocktype, có thể validate ở backend để tránh cấu hình sai làm vỡ layout. Một số hệ thống còn sinh ra form cấu hình động dựa trên schema này, giúp UX của trình kéo thả trực quan và an toàn hơn.
Template lưu bố cục trang bán hàng để marketing tự tạo landing page không cần sửa code
Bảng Template định nghĩa các mẫu bố cục trang, đóng vai trò “khung xương” cho Page. Mỗi template mô tả cấu trúc layout, vùng block, và các ràng buộc về loại block được phép sử dụng, giúp đảm bảo tính nhất quán UI/UX và tuân thủ guideline thương hiệu.

Các thuộc tính chính:
- name: tên hiển thị cho đội marketing (Home Default, Product Launch Landing, Blog Detail…).
- code: mã định danh duy nhất dùng trong code/frontend (HOMEDEFAULT, PRODUCTLANDINGV2…).
- description: mô tả ngắn về mục đích sử dụng, kênh traffic chính, hoặc guideline.
- thumbnailmediaid: tham chiếu đến Media để hiển thị preview hình ảnh của template trong UI chọn template.
- configschema (JSON): mô tả:
- Các vùng layout (header, hero, content, sidebar, footer…).
- Danh sách block mặc định cho từng vùng.
- Loại block được phép thêm vào mỗi vùng.
- Các constraint như: số lượng block tối đa, block bắt buộc (ví dụ: ít nhất một CTA).
- isdefault: đánh dấu template mặc định cho một type trang nhất định.
- type: phân loại template (home, landing, blog, campaign…), thường map với Page.type.
Trong ERD, Template liên kết với Page qua templateid và có thể liên kết với Campaign nếu mỗi chiến dịch dùng một template riêng. Quy trình thường gặp:
- Developer tạo template mới (code + configschema) và đăng ký trong bảng Template.
- Marketing chọn template khi tạo Page mới, sau đó cấu hình các PageBlock theo schema cho phép.
- Frontend render Page dựa trên Template + danh sách PageBlock + Media liên quan.
Thiết kế dựa trên Template giúp:
- Mở rộng số lượng layout mà không phải thay đổi logic cốt lõi của hệ thống.
- Đảm bảo mọi landing page tuân thủ chuẩn UI/UX, responsive, và SEO (vị trí heading, meta, breadcrumb…).
- Cho phép chạy nhiều chiến dịch song song với các template khác nhau, dễ đo lường hiệu quả và chuẩn hóa best practice.
Nhóm entity giỏ hàng, đơn hàng và thanh toán
Cụm entity giỏ hàng, đơn hàng và thanh toán tạo thành chuỗi dữ liệu xuyên suốt từ lúc khách bắt đầu thêm sản phẩm vào giỏ cho đến khi hoàn tất trả tiền. Cart và CartItem tập trung vào giai đoạn tiền mua hàng, lưu hành vi, nguồn traffic, khuyến mãi tạm tính và là cơ sở phân tích add-to-cart, cart abandonment. Khi chốt đơn, dữ liệu được “đóng băng” sang Order và OrderItem, cố định giá, thuế, địa chỉ, khuyến mãi để phục vụ báo cáo doanh thu, thuế và đối soát. Payment mô hình hóa các giao dịch thanh toán gắn với Order, tách bạch phương thức, nhà cung cấp, trạng thái và log kỹ thuật, giúp kiểm soát rủi ro, xử lý hoàn tiền và tối ưu tỷ lệ thanh toán thành công.

Cart lưu phiên giỏ hàng của khách đăng nhập hoặc khách vãng lai
Bảng Cart không chỉ lưu trạng thái giỏ hàng tạm thời trước khi chuyển thành đơn, mà còn là “trung tâm dữ liệu hành vi” của khách trong giai đoạn tiền mua hàng. Ngoài các trường chính: customerid (nullable), sessionid, status, createdat, updatedat, lastactivityat, source, campaignid, couponid, trong thiết kế thực tế thường bổ sung thêm một số cột kỹ thuật như: devicetype, useragent, ipaddress (tùy hệ thống) để phục vụ phân tích hành vi và chống gian lận.

sessionid là chìa khóa để theo dõi khách chưa đăng nhập. Thông thường, sessionid được sinh từ cookie hoặc token phía client, có thể gắn với bảng VisitSession để hợp nhất dữ liệu hành vi duyệt web (page view, event) với dữ liệu giỏ hàng. Khi khách đăng nhập, hệ thống có thể:
- Gắn customerid vào Cart hiện tại (nếu trước đó là khách vãng lai).
- Hợp nhất nhiều giỏ: giỏ gắn với session cũ và giỏ gắn với customerid (nếu tồn tại) thành một giỏ duy nhất, áp dụng quy tắc ưu tiên số lượng, khuyến mãi.
Trường status của Cart thường được chuẩn hóa, ví dụ: active, convertedtoorder, abandoned, expired. Việc chuẩn hóa giúp:
- Phân biệt giỏ đang hoạt động với giỏ đã được chuyển thành đơn.
- Đánh dấu giỏ bị “bỏ quên” sau một khoảng thời gian không có lastactivityat cập nhật.
- Thiết lập job định kỳ để “đóng” các giỏ hết hạn, tránh phình dữ liệu.
lastactivityat nên được cập nhật mỗi khi có thao tác liên quan đến giỏ (thêm/sửa/xóa CartItem, áp mã giảm giá, thay đổi địa chỉ giao hàng tạm thời…). Trường này là nền tảng để tính toán chỉ số cart abandonment theo thời gian, ví dụ: giỏ không có hoạt động trong 30 phút được coi là bỏ dở phiên, trong 7 ngày được coi là bỏ giỏ dài hạn.
source và campaignid thường được đồng bộ với dữ liệu tracking marketing (UTM source/medium/campaign). Một số hệ thống lưu thêm utmsource, utmmedium, utmcampaign trực tiếp trong Cart hoặc trong VisitSession rồi tham chiếu sang. Điều này cho phép:
- Đo lường tỷ lệ add-to-cart theo từng kênh (SEO, Ads, Social, Affiliate…).
- So sánh hiệu quả chiến dịch: số giỏ được tạo, số giỏ chuyển thành đơn, giá trị trung bình mỗi giỏ.
Trong ERD, Cart liên kết với CartItem, Customer, Coupon, Campaign, VisitSession. Một số ràng buộc và quy tắc nghiệp vụ thường gặp:
- Cart – Customer: quan hệ nhiều-nhiều theo thời gian (một khách có thể có nhiều Cart lịch sử), nhưng tại một thời điểm thường chỉ có 1 Cart “active” cho mỗi khách.
- Cart – Coupon: mỗi Cart thường chỉ gắn 1 coupon chính, nhưng có thể lưu thêm các khuyến mãi tự động ở CartItem.
- Cart – VisitSession: cho phép phân tích hành trình từ lần truy cập đầu tiên đến khi thêm vào giỏ, bao nhiêu page view trước khi add-to-cart.
Khi khách đặt hàng, dữ liệu từ Cart và CartItem được chuyển sang Order và OrderItem theo cơ chế “snapshot”: không chỉ copy id mà còn cố định giá, khuyến mãi, thuế, địa chỉ tại thời điểm chốt đơn. Thiết kế này giúp phân tích tỷ lệ add-to-cart, cart abandonment theo kênh, chiến dịch, phân khúc khách hàng mà không bị ảnh hưởng bởi thay đổi giá hoặc cấu trúc sản phẩm sau này.
CartItem lưu sản phẩm, biến thể, số lượng, giá tạm tính và khuyến mãi đang áp dụng
Bảng CartItem lưu từng dòng sản phẩm trong giỏ: cartid, productid, variantid, quantity, unitprice, discountamount, promotionid, addedat. Ở mức thiết kế chi tiết, có thể cân nhắc thêm các trường như maxquantityallowed (để phản ánh giới hạn khuyến mãi), isgift (quà tặng), hoặc metadata (JSON) cho các thuộc tính tùy biến.

unitprice trong CartItem thường là giá “niêm yết” hoặc giá đã áp dụng một số quy tắc định giá theo phân khúc (ví dụ: giá sỉ, giá VIP). discountamount phản ánh tổng mức giảm cho dòng sản phẩm đó tại thời điểm thêm vào giỏ, có thể đến từ:
- Khuyến mãi theo sản phẩm (Promotion gắn với Product/Variant).
- Khuyến mãi theo giỏ (áp dụng tạm thời, có thể thay đổi khi tổng giá trị giỏ thay đổi).
- Coupon nhập tay (nếu áp dụng trên từng dòng).
Vì giá và khuyến mãi có thể thay đổi liên tục, việc lưu unitprice và discountamount tại thời điểm thêm vào giỏ cho phép:
- Phân tích hành vi: khách thêm vào giỏ khi giá đang cao hay thấp, có khuyến mãi hay không.
- So sánh hiệu quả khuyến mãi: số lượng sản phẩm được thêm vào giỏ trong thời gian chạy promotion so với trước/sau.
Trường promotionid giúp liên kết trực tiếp với bảng Promotion, từ đó có thể:
- Tính doanh thu “gắn với” từng chương trình khuyến mãi ở giai đoạn giỏ hàng (chưa chắc đã chuyển thành đơn).
- Đánh giá mức độ hấp dẫn của từng promotion: tỷ lệ sản phẩm được thêm vào giỏ khi promotion đang active.
Trong ERD, CartItem liên kết với Cart, Product, ProductVariant, Promotion. Một số ràng buộc quan trọng:
- FK CartItem.cartid → Cart.id: đảm bảo mọi dòng giỏ đều thuộc về một Cart hợp lệ; thường dùng ON DELETE CASCADE để xóa CartItem khi Cart bị xóa.
- FK CartItem.productid → Product.id và variantid → ProductVariant.id: đảm bảo chỉ thêm vào giỏ những sản phẩm và biến thể hợp lệ, tránh tình trạng variant không thuộc product tương ứng.
- Ràng buộc unique logic (tùy hệ thống): không cho phép hai CartItem trùng cartid + variantid, thay vào đó cộng dồn quantity.
Khi chuyển sang đơn hàng, dữ liệu từ CartItem được sao chép sang OrderItem. Tại bước này, hệ thống thường:
- Tính lại giá và khuyến mãi dựa trên trạng thái mới nhất (nếu chính sách cho phép), sau đó cố định trong OrderItem.
- Kiểm tra tồn kho, giới hạn mua, điều kiện promotion (ví dụ: mua 2 tặng 1, combo).
Order lưu mã đơn, khách hàng, trạng thái, tổng tiền, phí vận chuyển, giảm giá và ghi chú
Bảng Order là trung tâm của quy trình bán hàng và là nguồn dữ liệu chính cho báo cáo doanh thu, lợi nhuận, thuế. Các thuộc tính quan trọng: ordernumber, customerid, status, totalamount, subtotalamount, shippingfee, discounttotal, taxtotal, paymentstatus, shippingstatus, shippingaddresssnapshot, billingaddresssnapshot, note, source, campaignid, createdat.

ordernumber thường được thiết kế có cấu trúc dễ đọc, dễ tra cứu (ví dụ: prefix theo kênh hoặc theo năm/tháng, kèm số tăng dần). Trong khi đó, id là khóa chính nội bộ, thường là số tự tăng hoặc UUID. Việc tách biệt hai khái niệm này cho phép thay đổi format ordernumber mà không ảnh hưởng đến logic hệ thống.
Các trường tiền tệ:
- subtotalamount: tổng tiền hàng trước giảm giá và thuế.
- discounttotal: tổng tất cả giảm giá áp dụng trên đơn (bao gồm coupon, promotion theo giỏ, giảm giá thủ công).
- taxtotal: tổng thuế áp dụng cho đơn (VAT, thuế môi trường…).
- shippingfee: phí vận chuyển sau khi áp dụng khuyến mãi vận chuyển (nếu có).
- totalamount: số tiền cuối cùng khách phải thanh toán, thường = subtotalamount + taxtotal + shippingfee − discounttotal.
paymentstatus và shippingstatus được tách riêng khỏi status tổng thể của đơn để hỗ trợ workflow phức tạp. Ví dụ:
- paymentstatus: unpaid, pending, paid, partiallyrefunded, refunded.
- shippingstatus: notrequired, pending, processing, shipped, delivered, returned.
- status tổng thể: new, confirmed, fulfilled, cancelled, closed.
Việc lưu shippingaddresssnapshot và billingaddresssnapshot dạng JSON hoặc text giúp “đóng băng” thông tin địa chỉ tại thời điểm mua, bao gồm cả tên người nhận, số điện thoại, ghi chú giao hàng, mã bưu chính, mã phường/xã/quận/huyện. Điều này cực kỳ quan trọng cho:
- Đối soát với đơn vị vận chuyển (tránh thay đổi địa chỉ sau khi đã giao).
- Tuân thủ quy định kế toán, thuế (địa chỉ xuất hóa đơn không được thay đổi sau khi phát hành).
- Chăm sóc khách hàng và xử lý khiếu nại.
Trong ERD, Order liên kết với Customer, OrderItem, Payment, Shipment, Coupon, Promotion, Campaign, Warehouse. Một số điểm chuyên môn:
- Order – Customer: cho phép phân tích LTV (lifetime value), tần suất mua, giá trị trung bình đơn hàng theo phân khúc.
- Order – Warehouse: xác định kho xuất hàng, phục vụ tối ưu tồn kho và thời gian giao.
- Order – Coupon/Promotion/Campaign: gắn doanh thu thực tế với từng chiến dịch, không chỉ dừng ở mức giỏ hàng.
OrderItem lưu từng sản phẩm trong đơn, biến thể, số lượng, giá bán và thuế nếu có
Bảng OrderItem lưu chi tiết từng dòng sản phẩm trong đơn: orderid, productid, variantid, productnamesnapshot, skusnapshot, quantity, unitprice, discountamount, taxamount, totallineamount, promotionid. Đây là nơi “đóng băng” toàn bộ thông tin thương mại của từng sản phẩm tại thời điểm mua.
productnamesnapshot và skusnapshot đảm bảo rằng khi sản phẩm đổi tên, đổi SKU, hoặc thậm chí bị xóa/ẩn khỏi catalog, dữ liệu lịch sử đơn hàng vẫn chính xác. Trong nhiều hệ thống, snapshot còn có thể bao gồm:
- Thuộc tính biến thể (màu, size) ở dạng text.
- Giá gốc (listprice) để so sánh với unitprice sau giảm.

unitprice trong OrderItem là giá bán cuối cùng trên mỗi đơn vị sau khi áp dụng các quy tắc định giá nhưng trước khi cộng thuế (tùy mô hình thuế). discountamount là tổng giảm giá áp dụng cho dòng đó (bao gồm cả phần phân bổ từ coupon theo giỏ nếu hệ thống phân bổ xuống từng dòng). taxamount là thuế tính riêng cho dòng, cho phép:
- Báo cáo thuế chi tiết theo sản phẩm, nhóm sản phẩm, mức thuế suất.
- Xử lý các trường hợp một số sản phẩm chịu thuế, một số không.
totallineamount thường được tính theo công thức: (unitprice − discountperunit) × quantity + taxamount hoặc tương đương, tùy cách phân bổ. Việc lưu sẵn totallineamount giúp:
- Giảm chi phí tính toán lại trong báo cáo.
- Tránh sai lệch do thay đổi logic tính toán trong tương lai.
Trong ERD, OrderItem liên kết với Order, Product, ProductVariant, Promotion. Một số ràng buộc nghiệp vụ:
- Không cho phép tồn tại OrderItem nếu không có Order tương ứng (FK bắt buộc, thường ON DELETE CASCADE).
- Cho phép productid hoặc variantid nullable trong trường hợp sản phẩm đã bị xóa khỏi catalog, nhưng snapshot vẫn giữ thông tin hiển thị.
- promotionid cho phép truy vết chính xác dòng nào được hưởng khuyến mãi nào, phục vụ phân tích hiệu quả khuyến mãi ở mức sản phẩm.
Payment lưu phương thức thanh toán, trạng thái giao dịch, mã giao dịch và thời điểm thanh toán
Bảng Payment mô hình hóa các giao dịch thanh toán: orderid, amount, method, status, transactionid, provider, paidat, failurereason, rawresponse (JSON). Đây là lớp trung gian giữa hệ thống bán hàng và các cổng thanh toán/bên thứ ba.

method có thể là COD, creditcard, ewallet, banktransfer hoặc các phương thức khác như installment, voucher. provider là tên cổng thanh toán hoặc đơn vị xử lý (ví dụ: tên ví điện tử, ngân hàng, PSP). Việc tách method và provider cho phép:
- Phân tích tỷ lệ thành công/thất bại theo từng cổng thanh toán.
- So sánh hiệu quả các phương thức (tỷ lệ hủy đơn, tỷ lệ chargeback…).
transactionid là mã giao dịch phía nhà cung cấp, dùng để đối soát và xử lý tranh chấp. Trong nhiều tích hợp, transactionid có thể thay đổi giữa các bước (pre-auth, capture, refund), nên rawresponse (JSON) là nơi lưu toàn bộ payload trả về, bao gồm:
- Mã giao dịch gốc, mã tham chiếu, mã phiên.
- Chi tiết lỗi, mã lỗi, thông điệp từ cổng thanh toán.
- Thông tin bổ sung như 3D Secure, token hóa thẻ.
status (pending, success, failed, refunded) là trạng thái của từng giao dịch Payment, không phải của đơn hàng. Một đơn có thể có nhiều Payment với các trạng thái khác nhau, ví dụ:
- Thanh toán lần 1: failed (thẻ bị từ chối).
- Thanh toán lần 2: success.
- Hoàn tiền một phần: tạo Payment mới với status = refunded và amount âm hoặc cờ đánh dấu refund.
Trong ERD, Payment liên kết 1-n với Order. Một số quy tắc nghiệp vụ thường gặp:
- Tổng amount của các Payment có status = success (trừ các bản ghi refund nếu tách riêng) phải khớp với totalamount của Order để coi là đã thanh toán đủ.
- Thay đổi paymentstatus của Order dựa trên tổng hợp trạng thái Payment (ví dụ: khi có ít nhất một Payment success đủ số tiền thì Order chuyển sang paid).
- Sử dụng failurereason để phân tích nguyên nhân thất bại (thẻ hết hạn, không đủ tiền, lỗi kỹ thuật…), từ đó tối ưu UX và tỷ lệ chuyển đổi.
Việc lưu rawresponse hỗ trợ debug khi có tranh chấp hoặc lỗi thanh toán, đồng thời là bằng chứng giao dịch khi làm việc với ngân hàng/cổng thanh toán. Tuy nhiên, cần chú ý tuân thủ các tiêu chuẩn bảo mật (như PCI DSS) và không lưu trữ dữ liệu nhạy cảm của thẻ (PAN đầy đủ, CVV) trong raw_response nếu không được phép.
Nhóm entity kho hàng, vận chuyển và trạng thái xử lý đơn
Nhóm entity này mô hình hóa toàn bộ vòng đời của hàng hóa từ lúc nằm trong kho đến khi được giao cho khách và phản hồi trạng thái về hệ thống. Inventory giữ ảnh chụp tồn kho tức thời theo từng SKU, kho và trạng thái khả dụng, kết hợp chặt với StockMovement để đảm bảo mọi biến động đều có “chứng từ” truy vết. Warehouse định nghĩa mạng lưới kho vật lý/logic, vùng phục vụ và người chịu trách nhiệm, là nền tảng cho chiến lược đa kho, ship-from-store và phân bổ đơn theo khu vực. Ở lớp vận chuyển, ShippingMethod cấu hình chính sách giao hàng, phí và điều kiện áp dụng, còn Shipment theo dõi từng vận đơn, trạng thái giao nhận và dữ liệu tracking chi tiết, từ đó đồng bộ với trạng thái xử lý đơn hàng tổng thể.

Inventory lưu tồn kho theo sản phẩm, biến thể, kho và trạng thái khả dụng
Bảng Inventory là lớp dữ liệu trung tâm của mô-đun quản lý kho, chịu trách nhiệm phản ánh trạng thái tồn kho tức thời cho từng tổ hợp productid, variantid, warehouseid. Các trường quan trọng:
- productid, variantid: khóa ngoại tới ProductVariant, đảm bảo tồn kho được quản lý chi tiết đến từng biến thể (màu, size, cấu hình...).
- warehouseid: liên kết tới Warehouse, cho phép phân tách tồn kho theo từng kho vật lý hoặc kho ảo (kho online, kho cửa hàng, kho ký gửi).
- quantityonhand: tổng số lượng hàng thực tế đang có trong kho, đã nhập kho và chưa xuất ra khỏi hệ thống.
- quantityreserved: số lượng đã được “giữ chỗ” cho các đơn hàng đang ở trạng thái xử lý (pending, confirmed, picking...) nhưng chưa xuất kho thực tế.
- quantityavailable: số lượng có thể bán được, thường được tính bằng quantityonhand - quantityreserved. Trong hệ thống có tải cao, có thể denormalize để lưu trực tiếp nhằm tối ưu truy vấn và giảm chi phí tính toán trong quá trình checkout.
- safetystock: ngưỡng tồn kho an toàn, dùng để cảnh báo hoặc kích hoạt quy trình mua hàng/nhập hàng khi tồn kho khả dụng xuống thấp hơn mức này.
Nghiên cứu thực địa về độ chính xác của hồ sơ tồn kho cho thấy chênh lệch giữa số lượng ghi nhận trong hệ thống và số lượng thực tế là hiện tượng đáng kể trong hoạt động bán lẻ. Sai lệch có thể hình thành từ thất thoát, quét sai mã, hàng đặt nhầm vị trí, hoàn trả chưa cập nhật hoặc quy trình nhập xuất thiếu kiểm soát. Vì vậy, Inventory không nên là bảng duy nhất giải thích mọi thay đổi tồn kho. Mỗi biến động cần được đối chiếu với StockMovement, chứng từ nguồn, kho, người thực hiện và thời điểm phát sinh. Hệ thống cũng cần hỗ trợ kiểm kê, điều chỉnh có phê duyệt và báo cáo mức chênh lệch, thay vì mặc định số lượng trong database luôn phản ánh chính xác hàng vật lý (DeHoratius & Raman, 2008).

Về mặt thiết kế, có thể bổ sung các chỉ số nâng cao (dù không nhất thiết lưu trong bảng này) như reorderpoint, reorderquantity để phục vụ bài toán tối ưu cung ứng. Tuy nhiên, safetystock vẫn là trường cốt lõi để cảnh báo rủi ro thiếu hàng.
Trong ERD, Inventory liên kết chặt chẽ với:
- ProductVariant: xác định chính xác SKU/biến thể được quản lý tồn kho.
- Warehouse: cho phép cùng một SKU có tồn kho phân tán ở nhiều kho khác nhau.
- StockMovement: mọi thay đổi về số lượng đều phải có bản ghi tương ứng, đảm bảo khả năng truy vết.
- OrderItem: khi tạo đơn, mỗi dòng sản phẩm sẽ tác động đến quantityreserved của bản ghi Inventory tương ứng.
Khi hồ sơ tồn kho không chắc chắn, quyết định nhận đơn chỉ dựa trên quantityonhand có thể dẫn đến bán vượt tồn hoặc từ chối nhầm đơn hàng vẫn còn khả năng đáp ứng. Nghiên cứu về quản trị tồn kho trong điều kiện dữ liệu không chính xác cho thấy hệ thống cần tính đến mức độ tin cậy của hồ sơ và cập nhật niềm tin dựa trên kiểm kê hoặc quan sát mới. Trong ERD thực tế, có thể bổ sung lastcountedat, countedquantity, variancequantity và lịch sử InventoryCount. quantityavailable phải được cập nhật nguyên tử với quantityreserved, đồng thời cần cơ chế giải phóng phần giữ chỗ khi thanh toán thất bại, đơn hết hạn hoặc bị hủy để tránh tồn kho ảo bị khóa kéo dài (DeHoratius et al., 2008).
Luồng xử lý điển hình:
- Khi khách hàng đặt đơn và đơn được xác nhận, hệ thống tăng quantityreserved cho từng productid, variantid, warehouseid được phân bổ.
- Khi đơn được đóng gói và xuất kho (hoặc khi shipment được đánh dấu shipped), hệ thống giảm quantityonhand và đồng thời giảm quantityreserved.
- Khi đơn bị hủy hoặc hết hạn trước khi xuất kho, hệ thống giảm quantityreserved và tăng lại quantityavailable tương ứng.
Cách tiếp cận này giúp tránh oversell trong bối cảnh đa kênh (website, sàn TMĐT, cửa hàng offline) và hỗ trợ chiến lược phân bổ tồn kho theo khu vực địa lý, theo kênh bán hoặc theo độ ưu tiên đơn hàng.
StockMovement ghi nhận nhập kho, xuất kho, hoàn hàng, hủy đơn và điều chỉnh tồn
Bảng StockMovement đóng vai trò như “sổ cái” ghi nhận toàn bộ lịch sử biến động tồn kho. Mỗi bản ghi thể hiện một sự kiện thay đổi số lượng, với các trường chính:
- productid, variantid: xác định SKU/biến thể bị ảnh hưởng.
- warehouseid: kho nơi phát sinh biến động.
- quantitychange: số lượng thay đổi, có thể là số dương (nhập, hoàn hàng, điều chỉnh tăng) hoặc số âm (xuất, hủy, điều chỉnh giảm).
- type: phân loại nghiệp vụ, thường bao gồm:
- import: nhập kho từ nhà cung cấp, chuyển kho đến, hoặc nhập ban đầu.
- export: xuất kho để giao hàng cho khách, chuyển kho đi, hoặc xuất hủy.
- return: hàng khách trả về, nhập lại kho hoặc đưa vào khu vực kiểm định.
- cancel: hoàn trả tồn kho do hủy đơn trước khi xuất kho.
- adjust: điều chỉnh tồn kho do kiểm kê, thất thoát, hư hỏng, hoặc sai lệch dữ liệu.
- referencetype, referenceid: liên kết đến entity nguồn gây ra biến động, ví dụ:
- Order cho các lần xuất kho giao hàng.
- PurchaseOrder cho các lần nhập hàng từ nhà cung cấp.
- ReturnRequest cho các lần hoàn hàng từ khách.
- createdat, createdby: thời điểm và người (hoặc hệ thống) tạo bản ghi, phục vụ kiểm soát nội bộ và truy vết trách nhiệm.
- note: ghi chú chi tiết, lý do điều chỉnh, số phiếu kho nội bộ, hoặc thông tin bổ sung khác.

Trong ERD, StockMovement liên kết với Inventory, ProductVariant, Warehouse, User. Mối quan hệ với Inventory thường được sử dụng trong các quy trình:
- Khi tạo StockMovement, hệ thống cập nhật quantityonhand (và trong một số trường hợp là quantityreserved) của bản ghi Inventory tương ứng.
- Các báo cáo tồn kho cuối kỳ, báo cáo vòng quay hàng tồn, phân tích thất thoát thường được xây dựng bằng cách:
- Lấy tồn đầu kỳ từ Inventory (snapshot hoặc tính từ lịch sử).
- Cộng/trừ các StockMovement phát sinh trong kỳ.
- Đối chiếu với tồn cuối kỳ trong Inventory để phát hiện sai lệch.
Việc lưu chi tiết từng lần biến động cho phép:
- Phân tích hiệu suất cung ứng: thời gian từ lúc tạo PurchaseOrder đến khi hàng thực sự nhập kho (type = import).
- Đo lường vòng quay tồn kho theo SKU, theo kho, theo nhóm sản phẩm.
- Phát hiện bất thường: số lượng điều chỉnh (type = adjust) quá lớn, tần suất xuất hủy cao, chênh lệch giữa lý thuyết và thực tế.
Warehouse lưu thông tin kho, địa chỉ, khu vực phục vụ và người phụ trách
Bảng Warehouse mô tả các thực thể kho hàng vật lý hoặc logic trong hệ thống:
- name: tên kho, có thể phản ánh chức năng (Kho trung tâm, Kho miền Bắc, Kho cửa hàng A...).
- code: mã kho duy nhất, dùng trong tích hợp với WMS, ERP hoặc các hệ thống bên ngoài.
- address, province, country: thông tin địa chỉ chi tiết, phục vụ tính toán khoảng cách, thời gian giao hàng và chi phí vận chuyển.
- servicearea: có thể là danh sách khu vực (mã tỉnh/thành, quận/huyện, zipcode) hoặc liên kết tới bảng phụ để mô hình hóa vùng phục vụ phức tạp hơn (đa vùng, ưu tiên, loại trừ).
- managerid: liên kết tới bảng User hoặc Staff, xác định người chịu trách nhiệm vận hành kho.
- isactive: trạng thái hoạt động của kho, cho phép tạm dừng phân bổ đơn về kho đang bảo trì hoặc ngừng hoạt động.
Trong ERD, Warehouse liên kết với:
- Inventory: mỗi bản ghi tồn kho luôn gắn với một kho cụ thể.
- StockMovement: mọi biến động tồn kho đều phải biết kho nguồn/kho đích.
- Shipment: kho xuất hàng cho shipment, đặc biệt quan trọng trong mô hình đa kho.
- Order (nếu mỗi đơn gắn với kho xử lý): cho phép xác định kho chịu trách nhiệm chuẩn bị đơn, từ đó tối ưu SLA giao hàng.
Thiết kế đa kho hỗ trợ các chiến lược:
- Phân bổ đơn hàng theo servicearea để chọn kho gần khách nhất, giảm chi phí vận chuyển và thời gian giao.
- Thiết lập kho ưu tiên cho từng kênh bán (online, marketplace, B2B) nhằm tách biệt tồn kho.
- Hỗ trợ mô hình ship-from-store, nơi các cửa hàng bán lẻ cũng được mô hình hóa như một Warehouse.
ShippingMethod lưu phương thức giao hàng, phí, thời gian dự kiến và điều kiện áp dụng
Bảng ShippingMethod định nghĩa các cấu hình vận chuyển mà khách hàng có thể lựa chọn trong quá trình checkout:
- name, code: tên hiển thị và mã nội bộ của phương thức giao hàng (ví dụ: Giao nhanh 2H, Tiết kiệm 3-5 ngày).
- carrier: tên hãng vận chuyển hoặc đối tác logistics (có thể liên kết tới bảng Carrier riêng).
- basefee: phí cố định cho mỗi đơn hoặc mỗi shipment.
- feeperweight: đơn giá theo trọng lượng (hoặc thể tích quy đổi), cho phép tính phí ship động dựa trên tổng khối lượng đơn hàng.
- estimateddaysmin, estimateddaysmax: khoảng thời gian giao hàng dự kiến, dùng để hiển thị SLA cho khách.
- conditions (JSON): cấu trúc linh hoạt mô tả điều kiện áp dụng, ví dụ:
- Giới hạn khu vực: chỉ áp dụng cho một số tỉnh/thành hoặc vùng.
- Giới hạn trọng lượng: min/max weight.
- Giá trị đơn hàng tối thiểu để được áp dụng hoặc để được miễn/giảm phí.
- Giới hạn theo loại sản phẩm (hàng cồng kềnh, hàng dễ vỡ, hàng nguy hiểm...).
- isactive: bật/tắt phương thức giao hàng mà không cần xóa cấu hình.

Trong ERD, ShippingMethod liên kết với:
- Order: phương thức giao hàng mà khách chọn cho đơn (hoặc cho từng shipment nếu thiết kế chi tiết hơn).
- Shipment: phương thức thực tế được sử dụng để tạo vận đơn với hãng vận chuyển.
- Campaign: nếu chiến dịch marketing có ưu đãi phí ship (miễn phí, giảm giá, đồng giá), có thể tham chiếu tới ShippingMethod để áp dụng logic khuyến mãi.
Thiết kế linh hoạt của ShippingMethod cho phép:
- Cấu hình nhiều chính sách vận chuyển song song: giao nhanh, giao tiết kiệm, giao trong ngày, giao hẹn giờ.
- Thiết lập miễn phí ship theo đơn hàng (ví dụ: đơn trên một ngưỡng giá trị) hoặc giảm phí ship theo khu vực.
- Tính toán phí ship động trong quá trình checkout bằng cách kết hợp trọng lượng, địa chỉ giao hàng, kho xuất hàng và điều kiện trong conditions.
Shipment lưu mã vận đơn, đơn vị vận chuyển, trạng thái giao hàng và lịch sử cập nhật
Bảng Shipment mô hình hóa vòng đời của quá trình giao hàng cho một đơn hoặc một phần đơn:
- orderid: đơn hàng gốc mà shipment này phục vụ.
- trackingnumber: mã vận đơn do hãng vận chuyển cung cấp, dùng để tra cứu trạng thái.
- carrier: tên hoặc mã hãng vận chuyển, đồng bộ với cấu hình trong ShippingMethod.
- shippingmethodid: phương thức giao hàng được áp dụng cho shipment này.
- status: trạng thái hiện tại của shipment, thường bao gồm:
- pending: đã tạo shipment trong hệ thống nhưng chưa gửi yêu cầu sang hãng vận chuyển hoặc chưa được xác nhận.
- picking: đang được lấy hàng/đóng gói tại kho.
- intransit: đang trên đường giao cho khách.
- delivered: đã giao thành công.
- failed: giao không thành công (khách không nhận, sai địa chỉ, sự cố vận chuyển...).
- returned: hàng đã quay lại kho sau khi giao thất bại hoặc khách trả hàng.
- shippedat: thời điểm hàng được bàn giao cho hãng vận chuyển.
- deliveredat: thời điểm giao thành công cho khách.
- failedat: thời điểm xác nhận giao thất bại.
- laststatusupdateat: lần cuối cùng trạng thái shipment được cập nhật, thường đồng bộ từ webhook hoặc API của hãng vận chuyển.
- rawtrackingdata (JSON): dữ liệu thô trả về từ hệ thống của hãng vận chuyển, bao gồm lịch sử hành trình, điểm trung chuyển, ghi chú giao hàng...

Trong ERD, Shipment liên kết với Order, ShippingMethod, Warehouse. Một đơn có thể có nhiều shipment trong các trường hợp:
- Đơn được tách giao từ nhiều kho khác nhau (mỗi kho tạo một shipment riêng).
- Đơn được giao nhiều lần (backorder, giao trước một phần, phần còn lại giao sau).
Việc lưu rawtracking_data cho phép:
- Đồng bộ chi tiết lịch sử vận chuyển mà không cần ánh xạ toàn bộ các trạng thái nội bộ.
- Hiển thị hành trình cho khách hàng (timeline giao hàng) dựa trên dữ liệu gốc.
- Phân tích hiệu suất hãng vận chuyển: thời gian trung bình ở từng chặng, tỷ lệ giao đúng hẹn, tỷ lệ thất bại.
Kết hợp Shipment.status với trạng thái xử lý đơn hàng giúp xây dựng luồng nghiệp vụ nhất quán: khi tất cả shipment của một đơn ở trạng thái delivered, đơn có thể được tự động chuyển sang trạng thái hoàn tất; khi shipment ở trạng thái failed hoặc returned, hệ thống có thể kích hoạt quy trình chăm sóc khách hàng, hoàn tiền hoặc giao lại.
Nhóm entity khuyến mãi, coupon và chiến dịch marketing bán hàng
Nhóm entity này tạo thành nền tảng cho toàn bộ cơ chế giá động và hoạt động marketing trong hệ thống thương mại điện tử. Promotion chịu trách nhiệm định nghĩa logic khuyến mãi theo thời gian, phạm vi, điều kiện áp dụng và liên kết chặt với đơn hàng, sản phẩm, phân khúc khách. Coupon tách phần “mã sử dụng” khỏi logic giảm giá, giúp kiểm soát quota, ngữ cảnh dùng và đo lường theo kênh phân phối. Ở tầng chiến lược, Campaign gom nhóm promotion, coupon, sản phẩm trọng tâm và chi phí quảng cáo để tối ưu ngân sách, doanh thu và ROI. CampaignProduct xử lý mặt trình bày, merchandising trên landing page, trong khi CustomerSegment cung cấp lớp phân khúc động để cá nhân hóa ưu đãi và nhắm mục tiêu chính xác hơn.

Promotion lưu chương trình giảm giá theo thời gian, danh mục, sản phẩm hoặc nhóm khách hàng
Bảng Promotion mô tả chi tiết các chương trình khuyến mãi và là trung tâm của logic pricing động trong hệ thống thương mại điện tử. Một số trường quan trọng:
- name: tên chương trình, dùng cho hiển thị nội bộ và báo cáo.
- code: mã định danh duy nhất, thường dùng cho tích hợp với hệ thống khác hoặc tracking.
- type: phân loại cấp áp dụng, ví dụ:
- orderdiscount: giảm trên tổng giá trị đơn hàng.
- productdiscount: giảm trên từng sản phẩm hoặc nhóm sản phẩm.
- shippingdiscount: giảm phí vận chuyển.
- discountvalue: giá trị giảm, kết hợp với discounttype để tính toán.
- discounttype: cách tính giảm:
- percent: giảm theo % trên giá gốc.
- fixedamount: giảm số tiền cố định.
- startat, endat: khung thời gian hiệu lực, dùng cho scheduler và kiểm tra runtime.
- conditions (JSON): cấu trúc linh hoạt mô tả điều kiện áp dụng.
- applyscope: phạm vi áp dụng (toàn shop, theo kênh, theo khu vực, theo store…).
- status: trạng thái (draft, active, paused, expired, archived…).
- campaignid: liên kết đến chiến dịch marketing tổng thể.
Trường conditions thường được thiết kế dạng JSON có cấu trúc, ví dụ:
- categoryids: danh sách category được áp dụng.
- productids hoặc excludedproductids: whitelist/blacklist sản phẩm.
- customersegmentids: giới hạn theo phân khúc khách hàng.
- minordervalue, maxordervalue: ngưỡng giá trị đơn.
- channels: kênh bán (web, app, POS, marketplace…).
- geo: khu vực địa lý hoặc kho giao hàng.
Trong ERD, Promotion liên kết với Campaign, Product, Category, CustomerSegment, Order, OrderItem. Các quan hệ phổ biến:
- Promotion – Campaign: nhiều promotion thuộc một campaign, giúp gom nhóm theo mục tiêu marketing.
- Promotion – Product / Category: có thể qua bảng trung gian (PromotionProduct, PromotionCategory) hoặc lưu trong conditions.
- Promotion – CustomerSegment: giới hạn đối tượng hưởng khuyến mãi.
- Promotion – Order / OrderItem: lưu dấu vết promotion đã áp dụng để phục vụ tính toán và báo cáo.
Thiết kế linh hoạt cho phép áp dụng nhiều promotion trên cùng đơn, nhưng cần cơ chế promotion engine với các rule như:
- Ưu tiên theo priority hoặc stackingrule (cho phép cộng dồn hay không).
- Giới hạn số promotion tối đa trên một đơn hoặc một item.
- Rule loại trừ lẫn nhau (mutually exclusive) giữa các nhóm promotion.
- Chiến lược chọn tối ưu: tối đa giảm giá cho khách, tối đa lợi nhuận, hoặc theo cấu hình business.
Các báo cáo hiệu quả khuyến mãi dựa trên dữ liệu từ Order, OrderItem và Promotion, thường bao gồm:
- Doanh thu incremental do promotion mang lại (so với baseline).
- Tỷ lệ sử dụng promotion theo kênh, theo phân khúc khách hàng.
- Biên lợi nhuận sau khuyến mãi, chi phí “discount cost” theo campaign.
- Hiệu quả từng loại type (order vs product vs shipping).
Coupon lưu mã giảm giá, giới hạn sử dụng, điều kiện đơn hàng và thời gian hiệu lực
Bảng Coupon lưu các mã giảm giá mang tính “token” mà khách nhập vào hoặc được gắn tự động. Các trường chính:
- code: chuỗi mã mà khách sử dụng, cần unique và có thể sinh theo pattern.
- promotionid: liên kết đến Promotion để tái sử dụng logic giảm giá.
- usagelimit: tổng số lần coupon được phép dùng trên toàn hệ thống.
- usagepercustomer: số lần tối đa mỗi khách hàng được dùng.
- usedcount: số lần đã sử dụng thực tế, phục vụ kiểm soát quota.
- startat, endat: thời gian hiệu lực của mã, có thể khác với promotion gốc.
- status: active, expired, disabled, test…
- conditions (JSON): điều kiện bổ sung ngoài promotion.
conditions của coupon thường tập trung vào ngữ cảnh sử dụng, ví dụ:
- Áp dụng cho khách mới (firstorderonly = true).
- Áp dụng cho kênh cụ thể (webonly, apponly, affiliatechannel…).
- Yêu cầu đăng nhập hoặc ràng buộc theo customerid cụ thể.
- Giới hạn theo region, store hoặc partner.

Trong ERD, Coupon liên kết với Promotion, Order, Cart, Campaign, Customer (qua bảng CouponUsage nếu cần). Một số pattern triển khai:
- Coupon – Promotion: nhiều coupon trỏ về một promotion, cho phép:
- Tạo mã khác nhau cho từng kênh (Facebook, Email, KOL…).
- Phân biệt mã public và mã dành riêng cho đối tác.
- Coupon – Order / Cart: lưu mã đã áp dụng để tính giảm giá và phục vụ CSKH.
- Coupon – Customer qua CouponUsage:
- Theo dõi lịch sử sử dụng theo từng khách.
- Thực thi usagepercustomer một cách chính xác.
- Coupon – Campaign: mapping coupon với chiến dịch marketing để đo lường hiệu quả từng mã.
Việc tách Coupon khỏi Promotion mang lại các lợi ích chuyên môn:
- Một logic giảm giá (promotion) có thể được “đóng gói” thành nhiều mã cho nhiều kịch bản phân phối.
- Dễ A/B test hiệu quả từng mã theo kênh, thông điệp, hoặc nhóm khách hàng.
- Cho phép quản lý vòng đời mã (phát hành, revoke, reissue) mà không ảnh hưởng logic giảm giá gốc.
Campaign lưu chiến dịch marketing theo mục tiêu doanh thu, kênh, ngân sách và thời gian chạy
Bảng Campaign mô hình hóa chiến dịch marketing ở mức chiến lược. Các trường chính:
- name: tên chiến dịch, thường gắn với concept marketing.
- code: mã nội bộ, dùng cho tracking đa hệ thống.
- objective: mục tiêu chính, ví dụ:
- doanh thu: tối đa hóa GMV, AOV.
- lead: thu thập thông tin khách hàng tiềm năng.
- traffic: tăng lượt truy cập site/app.
- brand awareness: tăng độ nhận diện thương hiệu.
- budget: ngân sách dự kiến, có thể chia nhỏ theo kênh.
- startat, endat: thời gian chạy chiến dịch.
- status: planned, running, paused, completed, cancelled…
- channels (JSON): danh sách kênh và cấu hình, ví dụ:
- Facebook Ads, Google Ads, Email, Social Organic, Affiliate…
- Thông tin chi tiết: adaccountid, campaignid phía ad platform, UTM…
- targetsegmentid: phân khúc khách hàng mục tiêu.
- landingpageid: trang đích chính để đo lường traffic và conversion.

Trong ERD, Campaign liên kết với Promotion, Coupon, CampaignProduct, Page, SocialPost, AdCostLog, CustomerSegment. Một số mối liên hệ chuyên sâu:
- Campaign – Promotion / Coupon: gom toàn bộ ưu đãi thuộc cùng chiến dịch, giúp:
- Đo lường doanh thu, số đơn, AOV theo campaign.
- Quản lý lịch trình khuyến mãi đồng bộ với lịch truyền thông.
- Campaign – CampaignProduct: xác định tập sản phẩm trọng tâm để tối ưu creative, landing page.
- Campaign – Page / SocialPost: mapping nội dung truyền thông với chiến dịch.
- Campaign – AdCostLog: ghi nhận chi phí quảng cáo theo ngày, theo kênh, theo ad set.
- Campaign – CustomerSegment: đảm bảo chiến dịch được nhắm đúng đối tượng.
Thiết kế này cho phép đo lường hiệu quả chiến dịch theo doanh thu, đơn hàng, chi phí, ROI bằng cách kết nối dữ liệu từ Order, AdCostLog, SocialMetric với Campaign, ví dụ:
- Tính ROAS (Return On Ad Spend) và ROI theo từng kênh trong campaign.
- Phân tích funnel: impression → click → visit → add-to-cart → order.
- So sánh hiệu quả giữa các campaign có cùng objective nhưng khác kênh hoặc segment.
CampaignProduct liên kết sản phẩm với chiến dịch để tạo landing page, banner và ưu đãi
Bảng CampaignProduct là bảng trung gian: campaignid, productid, position, specialprice, label, isfeatured. Vai trò chi tiết:
- campaignid: chiến dịch chứa sản phẩm.
- productid: sản phẩm được đẩy trong chiến dịch.
- position: thứ tự hiển thị trên landing page, banner block, email block…
- specialprice: giá ưu đãi riêng trong chiến dịch, có thể khác với promotion chung:
- Cho phép tạo “deal độc quyền” trong một campaign.
- Có thể override giá niêm yết trong thời gian campaign.
- label: tag hiển thị như Hot, Deal sốc, Flash sale, giúp tăng CTR.
- isfeatured: đánh dấu sản phẩm nổi bật để ưu tiên hiển thị.

Trong ERD, CampaignProduct liên kết với Campaign, Product và gián tiếp với Page, PageBlock nếu landing page lấy dữ liệu từ bảng này. Một số pattern triển khai:
- Landing page đọc danh sách CampaignProduct theo campaignid, sắp xếp theo position, filter theo isfeatured.
- Cho phép đội marketing cấu hình sản phẩm, giá, label qua CMS mà không cần deploy code.
- Hỗ trợ A/B test layout bằng cách thay đổi position và isfeatured giữa các variant.
Cách thiết kế này tách biệt rõ ràng giữa:
- Logic giá và khuyến mãi (Promotion, Coupon).
- Logic trình bày và merchandising (CampaignProduct, Page, PageBlock).
CustomerSegment lưu nhóm khách hàng theo hành vi mua, giá trị đơn hàng hoặc nguồn truy cập
Bảng CustomerSegment mô tả các phân khúc khách hàng phục vụ cá nhân hóa và nhắm mục tiêu. Các trường chính:
- name: tên phân khúc (VIP, New Users, Churn Risk…).
- code: mã kỹ thuật, dùng trong rule engine và tích hợp.
- description: mô tả business cho team marketing và CRM.
- rules (JSON): tập luật phân nhóm.
- isdynamic: cho biết phân khúc được cập nhật tự động hay gán thủ công.
rules thường được thiết kế như một mini rule-engine, ví dụ:
- totalspent: tổng chi tiêu trong khoảng thời gian (lifetime, 12 tháng, 90 ngày…).
- ordercount, purchasefrequency: số đơn, tần suất mua.
- lastorderat: thời gian mua gần nhất để xác định khách ngủ quên.
- trafficsource: nguồn truy cập (organic, paid, referral…).
- purchasedproducts hoặc categories: ngành hàng, brand đã mua.

Với isdynamic = true, hệ thống cần có job định kỳ hoặc event-driven để:
- Đánh giá lại rules và cập nhật membership của khách trong segment.
- Hỗ trợ khách có thể thuộc nhiều segment cùng lúc.
- Lưu lịch sử chuyển dịch segment để phân tích hành vi dài hạn.
Trong ERD, CustomerSegment liên kết với Customer, Campaign, Promotion, Coupon, SocialMetric:
- CustomerSegment – Customer: mapping nhiều-nhiều, lưu membership.
- CustomerSegment – Campaign / Promotion / Coupon:
- Giới hạn ưu đãi cho khách VIP, khách mới, khách có nguy cơ rời bỏ…
- Tạo chiến dịch win-back, cross-sell, up-sell theo hành vi.
- CustomerSegment – SocialMetric: phân tích mức độ tương tác social theo từng phân khúc.
Thiết kế này cho phép triển khai các chiến dịch cá nhân hóa như ưu đãi cho khách VIP, khuyến mãi quay lại cho khách ngủ quên, ưu đãi theo ngành hàng quan tâm, đồng thời tạo nền tảng cho các mô hình nâng cao như RFM, CLV, hoặc recommendation engine dựa trên segment.
Nhóm entity SEO toàn trang và sửa lỗi SEO hệ thống
Hệ thống entity SEO toàn trang hoạt động như một lớp hạ tầng tối ưu hóa tập trung, giúp tách biệt rõ ràng giữa nội dung và metadata. SeoMeta giữ vai trò “sổ cái SEO” cho từng URL, liên kết với các entity như Page, Product, Category, Brand để quản lý title, meta description, canonical, robots và social preview một cách linh hoạt, hỗ trợ đồng bộ khi URL thay đổi và kiểm soát lỗi trùng lặp, thiếu sót. Redirect quản lý quy tắc 301/302 ở tầng ứng dụng, tránh vòng lặp và chuỗi redirect dài, bảo toàn tín hiệu SEO khi thay đổi cấu trúc URL. SitemapUrl điều phối tập URL indexable, lastmod, priority để tối ưu crawl budget. SeoIssue và SeoAuditRun biến toàn bộ lỗi SEO thành backlog có thể đo lường, ưu tiên và theo dõi theo thời gian.

SeoMeta lưu title, meta description, canonical, robots meta và social preview theo từng URL
Bảng SeoMeta đóng vai trò là lớp cấu hình SEO trung tâm cho toàn bộ hệ thống, lưu metadata SEO chi tiết cho từng URL: url, title, metadescription, canonicalurl, robotsmeta, ogtitle, ogdescription, ogimagemediaid, twittercard, entitytype, entityid. Thiết kế này cho phép tách biệt hoàn toàn phần nội dung (content) và phần tối ưu hóa SEO (metadata), giúp đội SEO có thể tinh chỉnh mà không phụ thuộc quá nhiều vào đội phát triển.

Trường url được xem là khóa chính logic ở tầng nghiệp vụ, vì mọi thao tác crawl, index, ranking của công cụ tìm kiếm đều xoay quanh URL. Tuy nhiên, việc bổ sung cặp entitytype, entityid cho phép ánh xạ metadata về đúng entity nguồn như Product, Category, Page, Brand. Cách làm này hỗ trợ các kịch bản:
- Đồng bộ metadata khi URL của entity thay đổi (ví dụ đổi slug sản phẩm nhưng vẫn giữ nguyên entityid).
- Cho phép một entity có nhiều URL trong các trường hợp đa ngôn ngữ, đa domain, hoặc A/B testing URL.
- Ưu tiên xử lý theo url khi render trang, nhưng vẫn có thể truy vấn ngược từ entity để audit hoặc bulk update.
Trong ERD, SeoMeta liên kết với Page, Product, Category, Brand, SitemapUrl. Ràng buộc duy nhất trên url hoặc trên cặp (entitytype, entityid) giúp:
- Ngăn chặn việc tạo nhiều bản ghi metadata cho cùng một URL, tránh xung đột title, meta description, canonical.
- Đảm bảo mỗi entity chỉ có một bộ metadata “chính thức” trong trường hợp hệ thống không cho phép nhiều URL cho một entity.
- Hỗ trợ các job kiểm tra dữ liệu (data quality check) phát hiện nhanh các URL bị trùng hoặc thiếu metadata.
Các trường title và metadescription là nền tảng cho SEO on-page. Việc lưu riêng trong SeoMeta cho phép:
- Phân tích hàng loạt các vấn đề như thiếu title, title quá dài, meta description trùng lặp.
- Triển khai các công cụ gợi ý tối ưu (ví dụ gợi ý thêm từ khóa, độ dài chuẩn) dựa trên dữ liệu tập trung.
Trường canonicalurl cực kỳ quan trọng để xử lý vấn đề nội dung trùng lặp (duplicate content). Khi canonical được lưu tập trung:
- Có thể chạy các rule kiểm tra canonical sai (trỏ sang URL 404, trỏ vòng, trỏ sang domain khác không mong muốn).
- Dễ dàng cập nhật hàng loạt canonical khi thay đổi cấu trúc URL hoặc hợp nhất nội dung.
Trường robotsmeta (ví dụ: index, noindex, follow, nofollow, noarchive) cho phép điều khiển chi tiết cách bot xử lý từng trang. Lưu robots meta trong bảng giúp:
- Audit các trang đang bị noindex nhầm, gây mất traffic.
- Thiết lập rule tự động: ví dụ tất cả trang filter có hơn X tham số sẽ gán noindex, nofollow.
Các trường ogtitle, ogdescription, ogimagemediaid, twittercard phục vụ hiển thị trên mạng xã hội (Open Graph, Twitter Card). Việc lưu riêng các trường này cho phép:
- Tối ưu riêng nội dung social preview khác với title/meta dành cho SEO.
- Kiểm soát chất lượng hình ảnh chia sẻ (tỷ lệ, dung lượng) thông qua ogimagemediaid liên kết với hệ thống media.
Nhờ thiết kế này, SeoMeta trở thành nguồn dữ liệu trung tâm để sửa lỗi SEO hệ thống như thiếu title, trùng meta description, canonical sai, robots meta không nhất quán, đồng thời hỗ trợ các dashboard phân tích chất lượng SEO toàn site.
Redirect lưu quy tắc 301, 302, URL cũ, URL mới và trạng thái áp dụng
Bảng Redirect quản lý toàn bộ quy tắc chuyển hướng URL ở tầng ứng dụng: sourceurl, targeturl, statuscode, isactive, createdat, createdby, notes. Thay vì cấu hình rải rác ở web server (nginx, Apache) hoặc file cấu hình, việc lưu redirect trong database giúp:
- Quản lý tập trung, có lịch sử thay đổi và người chịu trách nhiệm (createdby).
- Dễ dàng tích hợp với giao diện quản trị để đội SEO tự thêm/sửa redirect mà không cần deploy.
- Đồng bộ với hệ thống cache hoặc CDN, cho phép purge cache chính xác khi rule redirect thay đổi.

Trường statuscode thường là 301 (vĩnh viễn) hoặc 302 (tạm thời). Việc phân biệt rõ hai loại này có ý nghĩa SEO sâu sắc:
- 301 chuyển hầu hết tín hiệu xếp hạng (link equity) sang URL mới, dùng khi thay đổi cấu trúc URL, đổi domain, hợp nhất nội dung.
- 302 chỉ nên dùng khi chuyển hướng tạm thời (A/B test, chiến dịch ngắn hạn), tránh làm mất tín hiệu của URL gốc.
Trường isactive cho phép bật/tắt redirect mà không cần xóa bản ghi, hữu ích khi rollback hoặc kiểm thử. Trường notes giúp ghi chú lý do redirect (ví dụ: “merge category A vào category B”, “fix trailing slash issue”).
Trong ERD, Redirect có thể liên kết lỏng với Page, Product, Category thông qua targeturl. Điều này cho phép:
- Kiểm tra tính hợp lệ của target (không trỏ tới URL 404, không trỏ tới trang bị noindex).
- Tự động cập nhật redirect khi URL của entity đích thay đổi (ví dụ đổi slug product, hệ thống cập nhật lại targeturl tương ứng).
Ràng buộc dữ liệu và logic nghiệp vụ cần đảm bảo:
- Không tạo vòng lặp redirect (A → B → A) hoặc chuỗi redirect quá dài (redirect chain), vì sẽ làm giảm hiệu quả crawl và trải nghiệm người dùng.
- Ưu tiên redirect cụ thể hơn khi có nhiều quy tắc trùng khớp, ví dụ:
- Rule 1: /category → /danh-muc
- Rule 2: /category/shoes → /giay
Khi người dùng truy cập /category/shoes, hệ thống phải áp dụng Rule 2 thay vì Rule 1.
Bảng Redirect là công cụ quan trọng khi:
- Thay đổi cấu trúc URL toàn site (ví dụ thêm slug brand vào URL product).
- Di chuyển nội dung sang domain mới hoặc subfolder mới.
- Hợp nhất danh mục, xóa bớt landing page nhưng vẫn bảo toàn traffic và thứ hạng SEO.
SitemapUrl lưu URL indexable, lastmod, priority và loại trang trong XML sitemap
Bảng SitemapUrl lưu danh sách URL được đưa vào sitemap: url, lastmod, changefreq, priority, entitytype, entityid, isindexable. Thay vì generate sitemap hoàn toàn động từ code mỗi lần request, việc lưu bảng này cho phép:
- Xây dựng sitemap động nhưng có kiểm soát, có thể cache và phân trang (sitemap index) hiệu quả.
- Cập nhật lastmod chính xác mỗi khi nội dung thay đổi, dựa trên event từ Page, Product, Category, Brand.
- Thực hiện audit các URL đang được gửi cho Google, đối chiếu với log crawl và index coverage.

Trường isindexable cho biết URL có nên được index hay không. Điều này rất quan trọng để:
- Loại trừ các trang như giỏ hàng, trang nội bộ, trang test, trang filter có quá nhiều tham số.
- Đảm bảo sitemap chỉ chứa URL có khả năng index, tránh gửi tín hiệu nhiễu cho Google.
Trường changefreq (ví dụ: daily, weekly, monthly) và priority giúp tạo sitemap XML chuẩn cho công cụ tìm kiếm. Dù các công cụ tìm kiếm hiện đại không phụ thuộc tuyệt đối vào hai trường này, chúng vẫn hữu ích để:
- Thể hiện mức độ quan trọng tương đối giữa các nhóm trang (homepage, category, product, blog).
- Hỗ trợ các hệ thống crawl nội bộ hoặc công cụ bên thứ ba ưu tiên quét các URL quan trọng.
Trong ERD, SitemapUrl liên kết với Page, Product, Category, Brand, SeoMeta. Cách liên kết này cho phép:
- Tự động thêm URL mới vào sitemap khi tạo entity mới (product, category, page).
- Cập nhật lastmod khi nội dung hoặc metadata (title, description, price, stock) thay đổi.
- Đồng bộ trạng thái indexable với SeoMeta.robotsmeta (ví dụ nếu robots meta là noindex thì isindexable nên là false).
Việc lưu bảng SitemapUrl giúp tối ưu crawl budget bằng cách:
- Giảm số lượng URL rác hoặc ít giá trị được gửi cho Google.
- Ưu tiên các URL có khả năng mang lại traffic và chuyển đổi.
SeoIssue lưu lỗi SEO toàn trang như thiếu title, trùng meta, 404, canonical sai và ảnh thiếu alt
Bảng SeoIssue lưu các lỗi SEO phát hiện qua quá trình audit: auditrunid, url, issuetype, severity, message, entitytype, entityid, status, detectedat, resolvedat. Đây là nơi tập trung toàn bộ “backlog lỗi SEO” của hệ thống, cho phép quản lý giống như quản lý bug trong phát triển phần mềm.

Các loại issuetype phổ biến: missingtitle, duplicatemetadescription, brokenlink, wrongcanonical, missingalt, redirectchain. Mỗi loại lỗi có đặc thù riêng:
- missingtitle: trang không có thẻ title, hoặc title rỗng, ảnh hưởng trực tiếp đến khả năng hiển thị trên SERP.
- duplicatemetadescription: nhiều URL dùng chung meta description, gây khó cho công cụ tìm kiếm trong việc phân biệt nội dung.
- brokenlink: liên kết nội bộ hoặc external trỏ tới URL 404, làm giảm trải nghiệm người dùng và lãng phí crawl budget.
- wrongcanonical: canonical trỏ sai URL, trỏ vòng, hoặc trỏ sang trang kém liên quan, có thể làm mất thứ hạng của trang chính.
- missingalt: ảnh không có thuộc tính alt, ảnh hưởng đến SEO hình ảnh và khả năng truy cập (accessibility).
- redirectchain: chuỗi redirect nhiều bước, làm chậm tải trang và giảm hiệu quả truyền tín hiệu SEO.
Trường severity (ví dụ: low, medium, high, critical) giúp ưu tiên xử lý. Các lỗi như broken link trên trang có nhiều traffic hoặc wrong canonical trên trang chủ thường được gán mức độ nghiêm trọng cao hơn.
Trường status (ví dụ: open, in-progress, resolved, ignored) cùng với detectedat, resolvedat cho phép:
- Theo dõi vòng đời của từng lỗi SEO từ lúc phát hiện đến khi được xử lý.
- Đo thời gian phản hồi (time-to-fix) cho các nhóm lỗi quan trọng.
- Phân tích xu hướng: số lỗi mới phát sinh, số lỗi được giải quyết theo từng giai đoạn.
Trong ERD, SeoIssue liên kết với SeoAuditRun, SeoMeta, Page, Product. Thiết kế này cho phép:
- Gắn mỗi lỗi với một lần audit cụ thể thông qua auditrunid, giúp truy vết nguồn gốc.
- Liên kết lỗi với entity gốc (entitytype, entityid) để giao việc cho đúng team (content, dev, merchandiser).
- Đối chiếu lỗi với metadata hiện tại trong SeoMeta để xác định lỗi đã được fix hay chưa.
Nhờ đó, hệ thống có thể đánh giá chất lượng SEO on-site theo thời gian, ưu tiên các lỗi ảnh hưởng lớn đến traffic và trải nghiệm người dùng, thay vì sửa lẻ tẻ từng trang một cách thủ công.
SeoAuditRun lưu lịch sử quét lỗi SEO, mức độ ưu tiên và trạng thái xử lý
Bảng SeoAuditRun mô tả mỗi lần quét SEO: startedat, finishedat, status, totalurlsscanned, totalissuesfound, createdby, notes. Đây là lớp “phiên bản hóa” cho toàn bộ hoạt động audit SEO, tương tự như các lần chạy test trong CI/CD.

Trường status cho biết quá trình audit đang running, completed, failed. Việc lưu trạng thái chi tiết giúp:
- Phát hiện các lần audit bị lỗi (ví dụ do timeout, do thay đổi cấu trúc site) để chạy lại.
- Phân biệt dữ liệu lỗi SEO thuộc lần audit hoàn chỉnh với dữ liệu từ lần audit bị gián đoạn.
Trường totalurlsscanned và totalissuesfound cho phép xây dựng các chỉ số:
- Tỷ lệ lỗi trên mỗi 100 URL.
- Xu hướng cải thiện: số lỗi giảm dần qua các lần audit sau khi tối ưu.
- Đánh giá tác động của các đợt refactor hoặc migration URL đến chất lượng SEO.
Trường createdby và notes giúp phân biệt các loại audit:
- Audit định kỳ (hàng tuần, hàng tháng) do hệ thống tự chạy.
- Audit đặc biệt trước/ sau khi deploy tính năng lớn hoặc thay đổi cấu trúc URL.
Trong ERD, SeoAuditRun liên kết 1-n với SeoIssue. Mối quan hệ này cho phép:
- So sánh số lượng và mức độ nghiêm trọng của lỗi giữa các lần audit.
- Xây dựng báo cáo SEO tổng quan cho từng giai đoạn tối ưu (trước tối ưu, sau tối ưu).
- Giúp đội SEO và kỹ thuật phối hợp xử lý lỗi một cách có hệ thống, ưu tiên theo severity và theo nhóm URL quan trọng.
Khi kết hợp SeoAuditRun với SeoIssue, SeoMeta, Redirect, SitemapUrl, hệ thống có thể vận hành như một nền tảng SEO nội bộ hoàn chỉnh: phát hiện lỗi, ghi nhận, phân tích, ưu tiên, giao việc và theo dõi kết quả tối ưu trên toàn site.
Nhóm entity chống click tặc, tracking và bảo vệ ngân sách quảng cáo
Nhóm entity này tạo thành một lớp bảo vệ toàn diện cho ngân sách quảng cáo bằng cách theo dõi chi tiết hành vi truy cập, click và chi phí. VisitSession đóng vai trò “xương sống” ghi nhận phiên truy cập, nguồn traffic, thiết bị, IP và liên kết với Customer, Cart, Order để đánh giá chất lượng traffic. ClickEvent đi sâu đến từng lượt click quảng cáo, CTA, số điện thoại, link ngoài, cho phép phân tích tần suất, vị trí và hành trình click để phát hiện bất thường. FraudRule là engine luật linh hoạt (conditions JSON) kết hợp nhiều tín hiệu từ session, click, chi phí để quyết định block, bỏ qua hoặc cảnh báo. BlockedIp hiện thực hóa việc chặn ở mức IP, còn AdCostLog kết nối dữ liệu chi phí với hành vi onsite để tính CPC, CPA, ROAS và nhận diện dấu hiệu click tặc.

VisitSession lưu phiên truy cập, nguồn traffic, thiết bị, trình duyệt và landing page
Bảng VisitSession mô hình hóa phiên truy cập chi tiết ở cấp độ session, là “xương sống” cho toàn bộ hệ thống tracking và chống click tặc. Các trường chính: sessionid, customerid (nullable), ipaddress, useragent, devicetype, browser, os, source, medium, campaign, landingpageurl, referrer, startedat, endedat.

Trong đó:
- sessionid: khóa chính logic cho mỗi phiên, có thể là UUID hoặc hash từ cookie / local storage, dùng để gom nhóm toàn bộ hành vi trong một lần truy cập liên tục.
- customerid (nullable): liên kết với khách hàng đã đăng nhập; khi chưa đăng nhập, session vẫn tồn tại ở dạng ẩn danh, sau đó có thể “gắn” lại với customer khi user login (session stitching).
- ipaddress: dữ liệu cốt lõi cho phát hiện click tặc, dùng để:
- Đếm số phiên, số click, số đơn hàng theo IP.
- Đối chiếu với bảng BlockedIp để chặn hiển thị quảng cáo hoặc bỏ qua ghi nhận click.
- useragent: chuỗi thô từ trình duyệt, là nguồn để parse ra devicetype, browser, os. Việc lưu cả chuỗi gốc giúp tái phân tích khi thuật toán nhận diện bot / thiết bị thay đổi.
- devicetype, browser, os: các trường đã chuẩn hóa (normalized) từ useragent, phục vụ:
- Phân tích hành vi theo loại thiết bị (mobile/desktop/tablet).
- Phát hiện mẫu gian lận như nhiều session bất thường từ cùng một loại thiết bị cũ, trình duyệt hiếm gặp, hoặc OS không phổ biến.
- source, medium, campaign: thường lấy từ UTM hoặc cấu hình tracking nội bộ:
- source: nguồn traffic (google, facebook, tiktok, email, direct...).
- medium: loại kênh (cpc, organic, referral, social, display...).
- campaign: mã chiến dịch marketing, đồng bộ với entity Campaign để phân tích hiệu quả.
- landingpageurl: URL đầu tiên user truy cập trong session, dùng để:
- Đánh giá hiệu quả từng landing page.
- Phát hiện pattern bất thường như nhiều session lặp lại cùng một landing page từ cùng IP trong thời gian ngắn.
- referrer: URL giới thiệu, hỗ trợ phân biệt traffic từ organic search, referral site, hoặc từ các mạng quảng cáo không gắn UTM đầy đủ.
- startedat, endedat: thời điểm bắt đầu và kết thúc session, cho phép tính:
- Thời lượng phiên (session duration).
- Khoảng thời gian giữa các phiên từ cùng IP / cùng user để phát hiện tần suất bất thường.
Trong ERD, VisitSession liên kết với Customer, ClickEvent, Cart, Order, Campaign. Mối liên kết này cho phép:
- Phân tích hành vi người dùng theo phiên: chuỗi pageview, click, add-to-cart, checkout trong một session.
- Đo lường hiệu quả kênh và chiến dịch: mapping từ source/medium/campaign đến Order và doanh thu.
- Phát hiện mẫu truy cập bất thường:
- Nhiều session ngắn, bounce cao từ cùng IP hoặc cùng dải IP.
- Session không tạo Cart hoặc Order dù số click CTA rất lớn.
Về mặt kỹ thuật, nên tạo index trên ipaddress, startedat, campaign để tối ưu các truy vấn phân tích tần suất truy cập, phân bố theo thời gian và theo chiến dịch. Có thể bổ sung cột geocountry, geocity (được resolve từ IP) trong cùng bảng hoặc bảng phụ để phục vụ FraudRule theo vị trí địa lý.
ClickEvent lưu lượt click quảng cáo, CTA, số điện thoại, nút mua hàng và link ngoài
Bảng ClickEvent lưu từng sự kiện click chi tiết: visitsessionid, eventtype, target, url, position, createdat, ipaddress, useragent, campaignid, adgroup, keyword. Đây là lớp dữ liệu granular cho phân tích hành vi và phát hiện click bất thường.

- visitsessionid: khóa ngoại tới VisitSession, đảm bảo mọi click đều gắn với một session cụ thể, cho phép tái dựng toàn bộ hành trình người dùng.
- eventtype: phân loại hành động, ví dụ:
- adclick: click vào quảng cáo (banner, search ad...).
- ctaclick: click vào nút kêu gọi hành động (mua ngay, đăng ký, nhận tư vấn...).
- phoneclick: click vào số điện thoại (tel: link), quan trọng cho business lead-based.
- addtocartclick: click thêm sản phẩm vào giỏ.
- externallinkclick: click ra ngoài domain chính (outbound link).
- target: mô tả đối tượng bị click, có thể là:
- productid hoặc SKU khi click vào sản phẩm.
- pageid khi click vào một block nội dung cụ thể.
- phonenumber khi click gọi điện.
Có thể chuẩn hóa target thành cấu trúc JSON (type, id, extra) để linh hoạt mở rộng loại đối tượng.
- url: URL tại thời điểm click, dùng để:
- Phân tích hiệu quả từng page, từng vị trí trên page.
- Đối chiếu với landingpageurl để xem user di chuyển như thế nào trước khi click.
- position: vị trí của element được click (ví dụ: header, footer, sidebar, section A/B test), có thể là:
- Toạ độ (x, y) tương đối.
- Hoặc một key logic (heroctatop, productlistitem3...).
- createdat: timestamp của click, là cơ sở để:
- Tính tần suất click theo IP / session / campaign trong khoảng thời gian.
- Phát hiện burst click (bùng nổ click trong vài phút) – dấu hiệu click tặc.
- ipaddress, useragent: được lưu lại tại thời điểm click để:
- Đối chiếu với VisitSession và BlockedIp.
- Phát hiện trường hợp spoofing hoặc mismatch giữa session và click (ví dụ session IP khác IP click).
- campaignid, adgroup, keyword: liên kết với hệ thống quảng cáo:
- campaignid: mapping với entity Campaign và AdCostLog.
- adgroup, keyword: chi tiết cấp thấp hơn, dùng để tối ưu bidding, negative keyword, và phát hiện keyword bị tấn công.
Trong ERD, ClickEvent liên kết với VisitSession, Campaign, Product, Page. Dữ liệu này là nền tảng để:
- Phát hiện click bất thường:
- Nhiều adclick từ cùng IP trong thời gian rất ngắn, không dẫn đến Cart hoặc Order.
- Tỷ lệ externallinkclick cao bất thường từ một nguồn traffic cụ thể.
- Tính CTR, conversion rate theo:
- Chiến dịch, nhóm quảng cáo, keyword.
- Vị trí CTA trên trang (dựa trên position và url).
- Tối ưu UI/UX: xác định khu vực nóng (hotspot) và khu vực chết (dead zone) trên trang.
Các chỉ mục trên ipaddress, createdat, campaignid hỗ trợ phân tích nhanh theo thời gian và chiến dịch. Ngoài ra, có thể cân nhắc index kết hợp (ipaddress, createdat) để phục vụ các FraudRule dựa trên tần suất click trong khoảng thời gian ngắn.
FraudRule lưu quy tắc phát hiện click tặc theo IP, tần suất, thiết bị, vị trí và hành vi bất thường
Bảng FraudRule mô tả các quy tắc phát hiện gian lận có thể cấu hình động: name, code, description, conditions (JSON), action, threshold, isactive, createdat, createdby. Đây là lớp “rule engine” ở mức dữ liệu. Click gian lận không thể được nhận diện chính xác chỉ bằng một ngưỡng IP cố định, bởi bot có thể phân tán truy cập qua nhiều thiết bị, VPN hoặc khu vực địa lý. Nghiên cứu sử dụng machine learning và deep learning cho thấy các tín hiệu hữu ích còn bao gồm số trang đã xem, thời lượng ở lại, hành vi cuộn, cách kết thúc phiên và chuỗi tương tác sau click. Do đó, FraudRule nên tạo điểm rủi ro tổng hợp từ nhiều đặc trưng, đồng thời lưu phiên bản rule, dữ liệu đầu vào và kết quả đánh giá để kiểm tra false positive. Việc chặn tự động cần có thời hạn và khả năng rà soát, tránh loại nhầm khách hàng hợp lệ chỉ vì họ truy cập hoặc nhấp nhiều lần (Aljabri et al., 2025).

- name: tên dễ hiểu cho người vận hành (ví dụ: “Giới hạn 20 adclick / IP / 1h”).
- code: mã duy nhất, dùng trong hệ thống để tham chiếu, logging và mapping với BlockedIp hoặc các job xử lý.
- description: mô tả chi tiết logic, phạm vi áp dụng, kênh bị ảnh hưởng.
- conditions (JSON): phần quan trọng nhất, chứa cấu hình điều kiện, ví dụ:
- Giới hạn số click tối đa trên mỗi IP trong khoảng thời gian:
- {"metric": "clicksperip", "window": "1h", "max": 20}
- Số phiên không tạo đơn:
- {"metric": "sessionswithoutorder", "window": "24h", "ipthreshold": 10}
- Tỉ lệ bounce cao bất thường theo campaign hoặc source.
- Thiết bị hoặc vị trí địa lý đáng ngờ (ví dụ: nhiều click từ một quốc gia không nằm trong thị trường mục tiêu).
- action: mô tả hành động khi vi phạm, có thể là:
- blockip: ghi vào BlockedIp và chặn hiển thị quảng cáo / ghi nhận click.
- ignoreclick: vẫn cho hiển thị nhưng không tính vào báo cáo / không gửi về nền tảng quảng cáo.
- sendalert: gửi cảnh báo cho đội marketing / vận hành để kiểm tra thủ công.
- threshold: ngưỡng tổng hợp, có thể dùng chung cho nhiều điều kiện hoặc làm hệ số trọng số (score) để đánh giá mức độ nghi ngờ.
- isactive: bật/tắt rule mà không cần xóa, hỗ trợ A/B test các chiến lược chống click tặc.
- createdat, createdby: phục vụ audit trail, biết ai tạo / chỉnh sửa rule, từ đó kiểm soát rủi ro vận hành.
Trong ERD, FraudRule liên kết với BlockedIp, AdCostLog, VisitSession, ClickEvent ở mức logic (thông qua quá trình xử lý, không nhất thiết bằng foreign key trực tiếp). Thiết kế linh hoạt với conditions dạng JSON cho phép:
- Cập nhật và thử nghiệm nhiều chiến lược chống click tặc mà không cần thay đổi cấu trúc bảng.
- Kết hợp nhiều tín hiệu:
- Tần suất click (từ ClickEvent).
- Chất lượng session (từ VisitSession – bounce rate, duration, page depth).
- Tương quan chi phí – click – đơn hàng (từ AdCostLog và Order).
Có thể triển khai engine đánh giá FraudRule theo hai chế độ: realtime (khi có click mới) và batch (chạy định kỳ để rà soát pattern dài hạn). Mỗi lần rule được kích hoạt, nên log lại kết quả đánh giá để phục vụ tuning rule sau này.
BlockedIp lưu IP, lý do chặn, thời gian chặn và nguồn phát hiện gian lận
Bảng BlockedIp lưu danh sách IP bị chặn: ipaddress, reason, source, blockedat, blockeduntil, fraudruleid, createdby. Đây là lớp “enforcement” của hệ thống chống click tặc.

- ipaddress: có thể lưu dạng text hoặc dạng số (IPv4 integer, IPv6 binary) để tối ưu tìm kiếm và range query.
- reason: mô tả ngắn gọn lý do chặn, có thể tự động sinh từ FraudRule hoặc do admin nhập tay.
- source: cho biết IP bị chặn do:
- Hệ thống tự động (fraud engine).
- Quản trị viên (manual review).
- Bên thứ ba (blacklist provider, anti-fraud service).
- blockedat: thời điểm bắt đầu chặn.
- blockeduntil: thời điểm kết thúc chặn, cho phép:
- Chặn tạm thời (cooldown) để giảm thiểu false positive.
- Chặn vĩnh viễn bằng cách đặt giá trị rất xa trong tương lai hoặc null với logic “vô thời hạn”.
- fraudruleid: tham chiếu tới FraudRule đã kích hoạt việc chặn, giúp phân tích hiệu quả từng rule.
- createdby: phân biệt IP do hệ thống hay con người thêm vào, phục vụ audit và rollback.
Trong ERD, BlockedIp liên kết với FraudRule và gián tiếp với VisitSession, ClickEvent. Khi có phiên truy cập mới, hệ thống:
- Kiểm tra ipaddress của request trong bảng BlockedIp với điều kiện blockeduntil >= now().
- Quyết định:
- Có ghi nhận VisitSession và ClickEvent hay không.
- Có hiển thị quảng cáo, pixel tracking, hoặc redirect sang landing page chính hay trang thông báo hạn chế.
Để hiệu năng tốt, nên tạo index trên ipaddress và có thể thêm cột isactive (derivative từ blockeduntil) để filter nhanh các IP đang bị chặn. Ngoài ra, có thể triển khai cơ chế sync BlockedIp với layer cache (Redis) hoặc WAF/CDN để chặn ngay từ edge.
AdCostLog lưu chi phí quảng cáo theo chiến dịch để so sánh click, đơn hàng và doanh thu
Bảng AdCostLog lưu dữ liệu chi phí quảng cáo: campaignid, date, channel, adplatform, impressions, clicks, cost, currency, rawdata (JSON). Đây là lớp dữ liệu tài chính – marketing, dùng để đối chiếu với hành vi onsite và doanh thu.

- campaignid: liên kết với entity Campaign, đảm bảo mapping 1-1 hoặc 1-n giữa chiến dịch trên nền tảng quảng cáo và chiến dịch trong hệ thống nội bộ.
- date: ngày ghi nhận chi phí, thường là ngày theo timezone của nền tảng quảng cáo; dùng để:
- Tổng hợp chi phí theo ngày, tuần, tháng.
- Đồng bộ với dữ liệu VisitSession, ClickEvent, Order theo trục thời gian.
- channel: kênh marketing logic (paidsearch, paidsocial, display, affiliate...).
- adplatform: nền tảng quảng cáo, ví dụ: Google Ads, Facebook Ads, TikTok Ads, giúp phân tích hiệu quả từng nền tảng.
- impressions: số lần hiển thị quảng cáo, dùng để tính CTR (clicks / impressions).
- clicks: số click ghi nhận từ nền tảng quảng cáo, có thể khác với số ClickEvent onsite do chênh lệch tracking hoặc do click tặc bị hệ thống bỏ qua.
- cost: tổng chi phí cho ngày và chiến dịch đó.
- currency: loại tiền tệ (USD, VND...), cần chuẩn hóa để tính ROAS đa quốc gia.
- raw_data (JSON): lưu payload gốc từ API nền tảng quảng cáo (chi tiết theo ad group, creative, placement...), phục vụ debug và phân tích sâu khi cần.
Trong ERD, AdCostLog liên kết với Campaign, SocialMetric, VisitSession, ClickEvent. Khi kết hợp với Order và doanh thu, có thể tính:
- CPC (Cost Per Click): cost / clicks (từ nền tảng) hoặc cost / số ClickEvent hợp lệ (sau khi loại bỏ click tặc).
- CPA (Cost Per Acquisition): cost / số đơn hàng gắn với campaign đó.
- ROAS (Return On Ad Spend): revenue / cost.
Khi so sánh dữ liệu AdCostLog với VisitSession và ClickEvent, có thể phát hiện bất thường như:
- Click tăng mạnh nhưng đơn không tăng, hoặc thậm chí giảm.
- CTR trên nền tảng cao nhưng onsite engagement thấp (session duration ngắn, bounce rate cao).
- Chênh lệch lớn giữa số click nền tảng báo cáo và số click hợp lệ sau khi áp dụng FraudRule.
Các bất thường này là tín hiệu để kích hoạt hoặc điều chỉnh FraudRule, tối ưu ngân sách quảng cáo, và thương lượng với nền tảng quảng cáo khi có bằng chứng click tặc rõ ràng.
Nhóm entity tự động đăng bài mạng xã hội và phân phối nội dung
Nhóm entity này tạo thành một social hub giúp quản lý tập trung tài khoản, nội dung và hiệu suất trên nhiều nền tảng. SocialAccount đóng vai trò lớp tích hợp, lưu thông tin tài khoản, token ủy quyền và chủ sở hữu, đồng thời liên kết chặt với SocialPost, PublishSchedule, SocialMetric, Campaign để điều phối toàn bộ luồng nghiệp vụ. SocialPost là đơn vị nội dung chuẩn hóa, chứa text, media, link, CTA, trạng thái duyệt và gắn với chiến dịch, hỗ trợ quy trình kiểm duyệt, đa phiên bản và tái sử dụng nội dung. PublishSchedule tách riêng lịch đăng, cho phép một post được phân phối nhiều lần, nhiều kênh, tối ưu khung giờ. SocialPostProduct ánh xạ chi tiết nội dung với sản phẩm, danh mục, landing page để đo lường hiệu quả kinh doanh. SocialMetric lưu snapshot số liệu, phục vụ phân tích ROI, tối ưu nội dung và kênh.

SocialAccount lưu tài khoản Facebook, TikTok, Instagram, Zalo, YouTube hoặc nền tảng khác
Bảng SocialAccount đóng vai trò là lớp tích hợp với các nền tảng mạng xã hội, lưu thông tin tài khoản và thông tin ủy quyền để hệ thống có thể thao tác tự động. Các trường quan trọng gồm: platform, accountname, accountid, accesstoken, refreshtoken, tokenexpiresat, status, ownerid. Trong đó:
- platform: định danh loại kênh như Facebook, Instagram, TikTok, Zalo, YouTube hoặc các nền tảng khác (LinkedIn, Twitter/X, v.v.). Nên chuẩn hóa bằng enum hoặc bảng tham chiếu để tránh sai lệch chính tả.
- accountname: tên hiển thị của tài khoản hoặc page/channel, phục vụ UI và báo cáo.
- accountid: ID nội bộ của nền tảng (page id, business id, channel id…), dùng để gọi API chính xác.
- accesstoken: token truy cập ngắn hạn/ trung hạn dùng để gọi API đăng bài, lấy số liệu.
- refreshtoken: token làm mới, dùng để tự động gia hạn accesstoken mà không cần người dùng đăng nhập lại.
- tokenexpiresat: thời điểm hết hạn token, giúp job nền chủ động làm mới trước khi token invalid.
- status: trạng thái tài khoản, ví dụ active, disconnected, expired, error, hỗ trợ giám sát sức khỏe kết nối.
- ownerid: tham chiếu tới User, Team hoặc Merchant, xác định ai là chủ sở hữu tài khoản, phục vụ phân quyền.

Về bảo mật, accesstoken và refreshtoken không nên lưu dạng plain text. Thực tế triển khai thường:
- Mã hóa token ở tầng ứng dụng trước khi ghi vào DB (AES, KMS, v.v.).
- Hoặc chỉ lưu metadata (loại token, scope, thời gian hết hạn) trong bảng SocialAccount, còn giá trị token thực lưu trong secret store (Vault, Secret Manager) và tham chiếu bằng một khóa nội bộ.
- Giới hạn quyền truy cập bảng này bằng cơ chế RBAC/ABAC, audit log mọi thao tác đọc/ghi token.
Trong ERD, SocialAccount liên kết với SocialPost, PublishSchedule, SocialMetric, Campaign. Các mối quan hệ chính:
- SocialAccount – SocialPost: một tài khoản có nhiều bài đăng; có thể thiết kế 1-n hoặc cho phép một SocialPost template được nhân bản sang nhiều tài khoản.
- SocialAccount – PublishSchedule: mỗi lịch đăng gắn với một tài khoản cụ thể, phục vụ multi-account publishing.
- SocialAccount – SocialMetric: số liệu hiệu suất được gom theo tài khoản để phân tích hiệu quả từng kênh.
- SocialAccount – Campaign: cho phép giới hạn chiến dịch theo kênh (ví dụ chiến dịch chỉ chạy trên TikTok và Instagram).
Thiết kế này giúp hệ thống đóng vai trò như một social hub, quản lý tập trung nhiều tài khoản, tự động đăng bài, thu thập số liệu, đồng thời vẫn đảm bảo an toàn cho thông tin ủy quyền.
SocialPost lưu nội dung bài đăng, ảnh, link sản phẩm, CTA và trạng thái duyệt
Bảng SocialPost mô tả từng đơn vị nội dung được chuẩn bị cho việc đăng lên mạng xã hội. Các trường chính: socialaccountid, content, mediaids (JSON hoặc bảng phụ), linkurl, ctatext, status, scheduledat, publishedat, campaignid, createdby, approvedby. Ý nghĩa chi tiết:
- socialaccountid: xác định bài viết này gắn với tài khoản nào. Trong mô hình nâng cao, có thể tách khái niệm “post template” và “post instance” để reuse nội dung cho nhiều tài khoản.
- content: nội dung text của bài đăng, cần lưu ý giới hạn ký tự theo từng nền tảng (ví dụ Twitter/X, TikTok caption, v.v.). Có thể bổ sung trường contentrich nếu cần lưu cấu trúc (hashtag, mention, line-break).
- mediaids: có thể là JSON chứa danh sách ID media hoặc tách thành bảng phụ SocialPostMedia để quản lý nhiều ảnh/video, thứ tự, loại media (image, video, reel, story).
- linkurl: URL trỏ đến Product, Category, Page, Landing page hoặc link rút gọn. Nên chuẩn hóa bằng cách mapping sang entity nội bộ để tracking tốt hơn.
- ctatext: nội dung kêu gọi hành động (mua ngay, đăng ký, xem thêm…), có thể dùng cho A/B testing.
- status: phản ánh workflow: draft, pendingapproval, scheduled, published, failed. Có thể mở rộng thêm rejected, canceled, archived để quản lý vòng đời nội dung.
- scheduledat: thời điểm dự kiến đăng (theo timezone chuẩn, thường là UTC), dùng để sinh PublishSchedule hoặc đồng bộ với nó.
- publishedat: thời điểm thực tế được nền tảng xác nhận đã đăng, dùng để tính toán số liệu và SLA.
- campaignid: gắn bài với chiến dịch marketing, hỗ trợ báo cáo theo chiến dịch.
- createdby: người tạo nội dung (content creator, marketer).
- approvedby: người duyệt nội dung (team lead, brand manager), phục vụ quy trình kiểm duyệt 2 bước.

Trong ERD, SocialPost liên kết với SocialAccount, Campaign, SocialPostProduct, SocialMetric. Một số luồng nghiệp vụ chuyên sâu:
- Quy trình duyệt bài: khi status = draft, chỉ người tạo chỉnh sửa; khi chuyển sang pendingapproval, hệ thống ghi log và gửi thông báo cho người duyệt; khi được duyệt, status chuyển sang scheduled hoặc tạo bản ghi PublishSchedule.
- Quản lý đa phiên bản: cùng một nội dung có thể có nhiều biến thể theo nền tảng (caption khác nhau, tỉ lệ ảnh khác nhau). Có thể dùng parentpostid (không nêu trong schema nhưng là hướng mở rộng) để nhóm các biến thể.
- Gắn với chiến dịch: campaignid cho phép tổng hợp hiệu quả nội dung theo chiến dịch, so sánh giữa các kênh.
PublishSchedule lưu lịch đăng tự động theo kênh, thời gian, chiến dịch và người phụ trách
Bảng PublishSchedule chịu trách nhiệm điều phối thời điểm và kênh đăng bài. Các trường: socialpostid, socialaccountid, scheduledtime, timezone, status, resultmessage, createdby. Cụ thể:
- socialpostid: tham chiếu tới nội dung sẽ được đăng.
- socialaccountid: tài khoản đích; cho phép cùng một SocialPost được lên lịch trên nhiều tài khoản khác nhau bằng nhiều bản ghi PublishSchedule.
- scheduledtime: thời điểm đăng theo timezone chuẩn (thường lưu UTC). Job scheduler sẽ dựa vào trường này để trigger.
- timezone: timezone người dùng chọn (ví dụ Asia/HoChiMinh), phục vụ hiển thị UI và tính toán chuyển đổi thời gian.
- status: trạng thái lịch đăng: pending, running, completed, failed. Có thể bổ sung canceled để hỗ trợ hủy lịch.
- resultmessage: log ngắn gọn kết quả gọi API (ID bài đăng trên nền tảng, lỗi trả về, lý do thất bại).
- createdby: người thiết lập lịch đăng, giúp audit và phân quyền chỉnh sửa/hủy.

Trong ERD, PublishSchedule liên kết với SocialPost, SocialAccount, Campaign. Một số điểm thiết kế chuyên sâu:
- Tách lịch đăng khỏi SocialPost cho phép:
- Một bài đăng được đăng nhiều lần (repost, remarketing) với các thời điểm khác nhau.
- Cùng một nội dung được phân phối trên nhiều tài khoản/kênh mà không cần nhân bản nội dung.
- Có thể dùng PublishSchedule như nguồn dữ liệu để tối ưu khung giờ đăng: lưu thêm các trường phân tích (ví dụ slot, dayofweek) ở tầng view/report.
- Job nền (worker) sẽ quét các bản ghi status = pending và scheduledtime <= now(), chuyển sang running, gọi API nền tảng, cập nhật status và resultmessage tương ứng.
SocialPostProduct liên kết bài đăng mạng xã hội với sản phẩm, danh mục hoặc landing page
Bảng SocialPostProduct là bảng trung gian: socialpostid, productid (nullable), categoryid (nullable), pageid (nullable), position. Đây là lớp mapping chi tiết giữa nội dung và đối tượng kinh doanh (business object):
- socialpostid: bài đăng chứa thông tin về sản phẩm/danh mục/trang đích.
- productid (nullable): tham chiếu tới sản phẩm cụ thể; nullable để hỗ trợ trường hợp bài chỉ gắn với category hoặc page.
- categoryid (nullable): dùng khi bài đăng mang tính giới thiệu nhóm sản phẩm hoặc bộ sưu tập.
- pageid (nullable): liên kết với landing page, blog post, hoặc trang nội dung marketing.
- position: thứ tự hiển thị trong bài đăng dạng carousel, album hoặc danh sách sản phẩm; hỗ trợ UI sắp xếp kéo-thả.

Trong ERD, SocialPostProduct liên kết với SocialPost, Product, Category, Page. Khi kết hợp với SocialMetric, Order, hệ thống có thể:
- Phân tích hiệu quả từng bài đăng đối với từng sản phẩm: số click, số đơn hàng, doanh thu gắn với productid.
- Xác định sản phẩm nào phù hợp với kênh nào: so sánh hiệu suất cùng một sản phẩm trên Facebook vs TikTok vs Instagram.
- Đo lường hiệu quả của landing page: mapping pageid với conversion rate, time-on-page (qua VisitSession).
Một số lưu ý thiết kế:
- Cần đảm bảo ràng buộc chỉ một trong các trường productid, categoryid, pageid được set trong một bản ghi, hoặc định nghĩa rõ rule cho trường hợp nhiều trường cùng có giá trị.
- position nên được index theo (socialpostid, position) để tối ưu truy vấn hiển thị.
- Có thể dùng bảng này làm cơ sở cho việc sinh dynamic link (deep link) tới từng sản phẩm trong bài đăng.
SocialMetric lưu lượt hiển thị, click, tương tác, đơn hàng và doanh thu từ từng bài đăng
Bảng SocialMetric lưu số liệu hiệu suất: socialpostid, date, impressions, clicks, likes, comments, shares, saves, orders, revenue, campaignid. Đây là lớp dữ liệu phân tích, phục vụ đo lường ROI và tối ưu nội dung:
- socialpostid: gắn số liệu với bài đăng cụ thể, cho phép drill-down từ chiến dịch đến từng post.
- date: ngày thống kê (thường là theo ngày, có thể mở rộng theo giờ nếu cần granular hơn). Hỗ trợ phân tích xu hướng theo thời gian.
- impressions: số lượt hiển thị, dùng để tính reach và frequency.
- clicks: số lượt click vào link hoặc vào bài đăng (tùy định nghĩa theo nền tảng).
- likes, comments, shares, saves: các chỉ số tương tác (engagement metrics) giúp đánh giá mức độ quan tâm.
- orders: số đơn hàng được quy gán cho bài đăng (thông qua tracking link, UTM, hoặc mapping với VisitSession và Order).
- revenue: doanh thu phát sinh từ các đơn hàng đó, có thể là gross hoặc net tùy quy ước.
- campaignid: gắn số liệu với chiến dịch tổng thể, hỗ trợ báo cáo đa chiều.

Trong ERD, SocialMetric liên kết với SocialPost, Campaign, VisitSession, Order (qua logic mapping). Một số phân tích chuyên sâu có thể thực hiện:
- Tính CTR (Click-Through Rate) = clicks / impressions, đánh giá mức độ hấp dẫn của nội dung và CTA.
- Tính conversion rate = orders / clicks hoặc orders / impressions, đo hiệu quả chuyển đổi.
- Tính revenue per post và revenue per impression, phục vụ so sánh hiệu quả giữa các bài đăng và kênh.
- Kết hợp với VisitSession để phân tích hành trình người dùng: từ click trên mạng xã hội đến session trên site/app, rồi đến Order.
Về kiến trúc dữ liệu, SocialMetric thường được nạp từ job ETL hoặc worker đồng bộ với API của nền tảng (Facebook Insights, TikTok Analytics, v.v.). Có thể:
- Lưu dữ liệu dạng snapshot theo ngày để tránh phụ thuộc vào API lịch sử của nền tảng.
- Dùng partition theo date để tối ưu truy vấn báo cáo.
- Kết hợp với bảng Campaign và SocialPostProduct để xây dựng dashboard marketing đa kênh, đa sản phẩm.
Quan hệ chính trong ERD website bán hàng
Khối quan hệ chính trong ERD website bán hàng xoay quanh các entity trung tâm như Customer, Category, Product, Order, Campaign và Page. Customer kết nối chuỗi hành vi mua sắm từ truy cập, giỏ hàng, đơn hàng đến đánh giá, địa chỉ và phiên truy cập, tạo nền tảng phân tích LTV, phân khúc và cá nhân hóa. Category tổ chức sản phẩm theo cây danh mục, hỗ trợ điều hướng, lọc và SEO. Product liên kết biến thể, media, thuộc tính, giỏ hàng, đơn hàng và chiến dịch khuyến mãi để quản lý thương mại và tồn kho. Order gắn với dòng sản phẩm, thanh toán, vận chuyển và lịch sử trạng thái, phục vụ vận hành và đối soát. Campaign và Page kết nối marketing, nội dung, chi phí quảng cáo với hành vi và doanh thu, giúp đo lường và tối ưu hiệu quả tổng thể.

Customer có nhiều Order, Cart, Address, Review và VisitSession
Trong ERD của một website bán hàng, cụm quan hệ xoay quanh Customer thường được xem là “vùng dữ liệu vàng” vì liên quan trực tiếp đến hành vi và giá trị vòng đời khách hàng (Customer Lifetime Value). Về mặt mô hình dữ liệu, Customer đóng vai trò entity gốc trong nhiều quan hệ 1-n:
- Customer – Order: 1 Customer có nhiều Order, thể hiện qua khóa ngoại order.customerid. Đây là quan hệ cốt lõi để phân tích doanh thu, tần suất mua, giá trị trung bình đơn hàng (AOV).
- Customer – Cart: 1 Customer có thể có nhiều Cart (giỏ hàng), đặc biệt trong các hệ thống cho phép lưu nhiều giỏ (ví dụ giỏ “cá nhân”, giỏ “công ty”). Khóa ngoại cart.customerid giúp truy vết các giỏ bị bỏ quên (abandoned cart) và hành vi thêm/xóa sản phẩm.
- Customer – CustomerAddress: 1 Customer có nhiều địa chỉ giao hàng/lập hóa đơn, lưu qua customeraddress.customerid. Có thể bổ sung cờ isdefaultshipping, isdefaultbilling để tối ưu trải nghiệm checkout.
- Customer – Review: 1 Customer có nhiều Review, thường được ràng buộc thêm với Product qua review.productid. Quan hệ này hỗ trợ kiểm soát review “đã mua hàng” (verified purchase) bằng cách đối chiếu với OrderItem.
- Customer – VisitSession: 1 Customer có nhiều VisitSession, thể hiện qua visitsession.customerid. Trong trường hợp khách chưa đăng nhập, có thể gắn session với cookie hoặc deviceid, sau đó “hợp nhất” về Customer khi khách đăng nhập.
Giá trị vòng đời khách hàng không thể được suy ra chỉ từ tổng doanh thu đã mua, mà cần xem xét thời điểm giao dịch, tần suất mua, khả năng quay lại, chi phí phục vụ và quá trình rời bỏ. Các mô hình CLV hiện đại sử dụng chuỗi giao dịch theo thời gian để dự báo giá trị tương lai, vì vậy ERD phải giữ được lịch sử Order đầy đủ và định danh khách hàng ổn định.
Không nên ghi đè hoặc xóa cứng giao dịch cũ khi khách thay đổi thông tin hồ sơ; các điều chỉnh như hoàn tiền, hủy hoặc trả hàng cần được thể hiện bằng trạng thái và giao dịch đối ứng. Cấu trúc dữ liệu như vậy giúp các mô hình phân tích phân biệt khách mua thường xuyên với khách có doanh thu cao nhưng chỉ phát sinh trong một giai đoạn ngắn (Yan & Resnick, 2024).

Nhờ cấu trúc này, có thể xây dựng các truy vấn và báo cáo chuyên sâu:
- Phân tích hành vi trước – trong – sau mua: từ VisitSession → Cart → Order → Review.
- Tính toán các chỉ số như: tổng chi tiêu theo Customer, LTV, tần suất mua, thời gian giữa các lần mua (purchase interval).
- Nhận diện phân khúc khách hàng: khách mới, khách trung thành, khách có nguy cơ rời bỏ (churn) dựa trên lịch sử Order và VisitSession.
Trên nền tảng đó, các tính năng như gợi ý sản phẩm cá nhân hóa, email remarketing, kịch bản chăm sóc sau bán có thể được triển khai bằng cách kết hợp dữ liệu từ nhiều bảng liên quan đến Customer, thay vì chỉ dựa trên Order đơn lẻ.
Category có nhiều Product và có thể có Category cha con
Trong khối dữ liệu sản phẩm, Category là entity quan trọng để tổ chức và điều hướng nội dung. Quan hệ cơ bản:
- Category – Product (1-n): mỗi Category chứa nhiều Product, với khóa ngoại product.categoryid trong thiết kế đơn giản.
- Category – Product (n-n): khi một Product có thể thuộc nhiều Category (ví dụ “Điện thoại” và “Flash sale”), sử dụng bảng trung gian ProductCategory với cặp khóa ngoại productcategory.productid và productcategory.categoryid.
- Category – Category (self-referencing): quan hệ cha – con thông qua category.parentid, cho phép xây dựng cây phân cấp nhiều tầng.

Cấu trúc cây Category hỗ trợ nhiều nghiệp vụ:
- Xây dựng menu đa cấp và breadcrumb dựa trên đường đi từ Category gốc đến Category lá.
- Tạo landing page theo danh mục, kết hợp với các bộ lọc (filter) dựa trên AttributeValue hoặc khoảng giá.
- Quản lý hiển thị: sắp xếp thứ tự Category, ẩn/hiện Category, giới hạn độ sâu hiển thị trên menu.
Về SEO, quan hệ Category – Product và cấu trúc cha – con cho phép định nghĩa URL thân thiện theo cây danh mục, ví dụ: /dien-thoai/smartphone/iphone-15. Khi thiết kế ERD, có thể bổ sung các trường như slug, fullpath hoặc lft/rgt (mô hình nested set) để tối ưu truy vấn cây Category, giảm số lượng join khi render menu hoặc breadcrumb.
Product có nhiều Variant, Media, AttributeValue, Review, OrderItem và CampaignProduct
Product là trung tâm của khối thương mại, liên kết với nhiều entity để mô tả đầy đủ vòng đời và ngữ cảnh kinh doanh của sản phẩm. Các quan hệ chính:
- Product – ProductVariant (1-n): 1 Product có nhiều Variant (màu sắc, kích cỡ, dung lượng…). Mỗi Variant có thể có SKU, giá, tồn kho, mã vạch riêng. Khóa ngoại: productvariant.productid.
- Product – ProductMedia (1-n): 1 Product có nhiều hình ảnh/video, lưu trong productmedia với các trường như url, type, sortorder. Có thể gắn media theo Variant nếu cần (thêm productvariantid).
- Product – AttributeValue (n-n): thông qua bảng ProductAttributeValue, cho phép gán nhiều thuộc tính (RAM, chất liệu, thương hiệu…) cho 1 Product. Cấu trúc này hỗ trợ mạnh cho bộ lọc sản phẩm và tìm kiếm nâng cao.
- Product – Review (1-n): 1 Product có nhiều Review, với các trường như rating, comment, status (pending/approved/rejected). Có thể ràng buộc thêm với Customer để kiểm soát spam.
- Product – OrderItem (1-n): mỗi dòng OrderItem tham chiếu đến Product (và thường là ProductVariant). Đây là nguồn dữ liệu chính để tính doanh thu, số lượng bán, tỉ lệ hoàn trả.
- Product – CartItem (1-n): tương tự OrderItem nhưng ở trạng thái “chưa chốt đơn”, giúp phân tích sản phẩm thường bị bỏ giỏ.
- Product – Campaign (n-n): thông qua CampaignProduct, cho phép 1 Product tham gia nhiều Campaign (flash sale, combo, khuyến mãi theo mùa…).

Nhờ các quan hệ này, hệ thống có thể:
- Hiển thị chi tiết sản phẩm: biến thể, hình ảnh, thuộc tính, đánh giá, khuyến mãi đang áp dụng trên cùng một trang Product.
- Phân tích hiệu quả kinh doanh: doanh thu theo Product/Variant, tỉ lệ chuyển đổi từ lượt xem sang mua, hiệu quả từng Campaign trên từng Product.
- Quản lý tồn kho chi tiết theo Variant, đồng bộ với hệ thống kho hoặc POS nếu có tích hợp.
Ở mức thiết kế chuyên sâu, có thể cân nhắc tách Product (thông tin logic, nội dung) khỏi InventoryItem hoặc StockKeepingUnit (thông tin tồn kho, SKU) để linh hoạt hơn trong các mô hình đa kho, đa kênh bán hàng.
Order có nhiều OrderItem, Payment, Shipment và trạng thái xử lý
Order là entity trung tâm của luồng giao dịch, kết nối giữa Customer, Product, Payment và Shipment. Các quan hệ chính:
- Order – OrderItem (1-n): 1 Order có nhiều dòng sản phẩm, mỗi dòng chứa thông tin Product/Variant, số lượng, đơn giá, chiết khấu. Khóa ngoại: orderitem.orderid.
- Order – Payment (1-n): 1 Order có thể có nhiều lần thanh toán (thanh toán lại, thanh toán bổ sung, hoàn tiền một phần). Khóa ngoại: payment.orderid. Mỗi Payment thường có trạng thái riêng (pending, success, failed, refunded).
- Order – Shipment (1-n): 1 Order có thể được chia thành nhiều Shipment (giao từ nhiều kho, giao tách nhiều lần). Khóa ngoại: shipment.orderid. Shipment có thể liên kết thêm với đơn vị vận chuyển, mã vận đơn, trạng thái giao hàng.
- Order – OrderStatusHistory (1-n, nếu dùng): lưu lịch sử thay đổi trạng thái đơn hàng (created, confirmed, packed, shipped, completed, canceled) với timestamp và user thực hiện.

Cấu trúc này cho phép:
- Theo dõi chi tiết vòng đời đơn hàng từ lúc tạo đến khi hoàn tất hoặc hủy, bao gồm cả các lần thanh toán thất bại, giao lại, hoàn tiền.
- Đối soát tài chính: so sánh tổng Payment thành công với tổng giá trị Order (sau chiết khấu, phí vận chuyển, thuế).
- Phân tích vận hành: thời gian xử lý từng bước (SLAs), tỉ lệ đơn bị hủy, tỉ lệ giao thất bại, hiệu suất từng đơn vị vận chuyển.
Về mặt thiết kế, việc tách Payment và Shipment khỏi Order giúp hệ thống linh hoạt hơn khi tích hợp nhiều cổng thanh toán, nhiều nhà vận chuyển, đồng thời giảm rủi ro khi thay đổi logic nghiệp vụ ở một phần mà không ảnh hưởng toàn bộ Order.
Campaign liên kết Promotion, Coupon, Product, Landing Page, SocialPost và AdCostLog
Trong khối marketing, Campaign là entity gom nhóm các hoạt động và tài nguyên liên quan đến một chiến dịch cụ thể. Các quan hệ thường gặp:
- Campaign – Promotion (1-n): 1 Campaign có nhiều Promotion (giảm giá theo phần trăm, theo số tiền, mua X tặng Y…). Mỗi Promotion có thể có điều kiện áp dụng riêng (theo Category, Product, giá trị đơn hàng).
- Campaign – Coupon (1-n): 1 Campaign phát hành nhiều Coupon (mã giảm giá), có thể là mã chung hoặc mã cá nhân hóa. Khóa ngoại: coupon.campaignid.
- Campaign – CampaignProduct (1-n từ Campaign, n-1 từ Product): bảng trung gian xác định danh sách Product tham gia Campaign, có thể kèm theo mức giảm giá, hạn mức số lượng.
- Campaign – Page: 1 Campaign có thể gắn với một hoặc nhiều Page đóng vai trò landing page, phục vụ cho từng kênh hoặc nhóm đối tượng khác nhau.
- Campaign – SocialPost (1-n): lưu các bài đăng mạng xã hội (Facebook, Instagram, TikTok…) thuộc Campaign, kèm theo chỉ số lượt hiển thị, click, tương tác nếu có.
- Campaign – AdCostLog (1-n): ghi nhận chi phí quảng cáo theo ngày, theo kênh, theo nhóm quảng cáo. Đây là nguồn dữ liệu để tính ROI, ROAS.

Khi kết hợp dữ liệu Campaign với Order, VisitSession và Page, có thể xây dựng chuỗi đo lường đầy đủ:
- Chi phí → lượt hiển thị → click → phiên truy cập → giỏ hàng → đơn hàng → doanh thu.
- So sánh hiệu quả giữa các kênh (social, search, email), giữa các nhóm quảng cáo, giữa các landing page trong cùng một Campaign.
- Tối ưu phân bổ ngân sách dựa trên ROAS theo Product, Category hoặc phân khúc khách hàng.
Thiết kế ERD rõ ràng cho Campaign giúp đội marketing có thể thử nghiệm A/B, chạy nhiều biến thể nội dung và ưu đãi mà vẫn theo dõi được hiệu quả chi tiết đến từng Product hoặc nhóm khách hàng.
Page liên kết SeoMeta, PageBlock, Form, Campaign và SitemapUrl
Page là entity trung tâm của khối nội dung (content), đặc biệt trong các hệ thống có CMS tích hợp. Các quan hệ điển hình:
- Page – PageBlock (1-n): 1 Page có nhiều block nội dung (banner, text, slider, product list…). Mỗi PageBlock có loại (type), vị trí (position), cấu hình riêng, cho phép xây dựng layout linh hoạt mà không cần chỉnh sửa code.
- Page – SeoMeta (1-1 hoặc 1-n): lưu meta title, meta description, canonical URL, schema markup… Có thể thiết kế 1-1 (mỗi Page một SeoMeta) hoặc 1-n nếu cần nhiều phiên bản theo ngôn ngữ hoặc theo A/B test.
- Page – FormSubmission (1-n): nếu Page chứa form (đăng ký, liên hệ, nhận báo giá…), mỗi lần gửi form sẽ tạo một FormSubmission gắn với Page, phục vụ cho lead tracking.
- Page – Campaign (n-1): khi Page là landing page của một Campaign, page.campaign_id giúp liên kết dữ liệu traffic và chuyển đổi với chiến dịch tương ứng.
- Page – SitemapUrl (1-1 hoặc 1-n): ánh xạ Page vào sitemap, quản lý tần suất cập nhật, độ ưu tiên, trạng thái index. Thiết kế 1-n cho phép một Page có nhiều URL trong các ngôn ngữ hoặc domain khác nhau.

Cấu trúc này mang lại nhiều lợi ích vận hành:
- Đội marketing có thể tạo mới hoặc chỉnh sửa landing page, thay đổi block nội dung, cập nhật SeoMeta mà không ảnh hưởng đến logic Product hoặc Order.
- Dễ dàng gắn Page với Campaign để đo lường tỉ lệ chuyển đổi theo từng landing page, từng biến thể nội dung.
- Quản lý SEO tập trung: tối ưu title, description, internal link, sitemap mà không cần can thiệp vào code ứng dụng cốt lõi.
Khi thiết kế ERD, việc tách biệt rõ Page, SeoMeta, PageBlock và SitemapUrl giúp hệ thống mở rộng tốt hơn khi số lượng trang tăng lớn, đồng thời hỗ trợ các mô hình đa ngôn ngữ, đa domain hoặc headless CMS.
Ràng buộc dữ liệu giúp ERD bán hàng vận hành ổn định và dễ SEO
Ràng buộc dữ liệu trong ERD bán hàng giữ vai trò nền tảng để hệ thống vận hành ổn định, dễ mở rộng và thân thiện với SEO. Các khóa nghiệp vụ như slug, SKU, URL, canonical hay thông tin tracking marketing đều cần được chuẩn hóa và ràng buộc chặt chẽ ở mức cơ sở dữ liệu lẫn tầng ứng dụng. Việc thiết kế đúng giúp tránh trùng URL, đảm bảo mỗi biến thể sản phẩm được nhận diện duy nhất, giá trị đơn hàng không bị sai lệch khi cập nhật giá, canonical không mâu thuẫn và dữ liệu marketing không bị phân mảnh. Nhờ đó, doanh nghiệp có thể xây dựng báo cáo tin cậy, tối ưu SEO, đo lường hiệu quả chiến dịch chính xác và giảm rủi ro vận hành, tài chính, pháp lý trong dài hạn.

Slug sản phẩm, danh mục, bài viết và landing page cần duy nhất để tránh trùng URL
Trong hệ thống bán hàng, slug là thành phần quan trọng của URL thân thiện, gắn trực tiếp với cấu trúc SEO và trải nghiệm người dùng. Về mặt thiết kế ERD, cần xác định rõ slug là một dạng business key và phải được ràng buộc ở mức cơ sở dữ liệu để tránh xung đột.

Có hai mô hình phổ biến:
- UNIQUE trực tiếp trên slug của từng bảng Product, Category, Page, Brand khi mỗi bảng có không gian URL riêng, ví dụ:
/product/{slug} /category/{slug} /brand/{slug} /page/{slug}
Khi đó, mỗi bảng có một UNIQUE(slug) độc lập. - UNIQUE trên cặp (slug, type) khi dùng chung một bảng cho nhiều loại entity (ví dụ bảng
Content hoặc Node), trong đó: type ∈ {product, category, brand, page, ...} - Ràng buộc: UNIQUE(slug, type) đảm bảo cùng một slug có thể dùng cho nhiều loại khác nhau nhưng không trùng trong cùng một loại.
Một số hệ thống tách phần URL ra bảng UrlAlias để quản lý mapping giữa entity và URL. Cấu trúc thường gặp:
UrlAlias(id, entitytype, entityid, path, isactive, createdat, ...) - path là đường dẫn đầy đủ, ví dụ
/dien-thoai/iphone-15 - Ràng buộc:
- UNIQUE(path) để không có hai entity dùng chung một URL
- UNIQUE(entitytype, entityid, isactive) nếu muốn đảm bảo mỗi entity chỉ có một URL đang hoạt động.
Ràng buộc này đảm bảo mỗi trang có một URL chuẩn, hỗ trợ thiết lập canonical chính xác, tránh nội dung trùng lặp (duplicate content) và giúp công cụ tìm kiếm hiểu rõ cấu trúc website. Khi thay đổi slug, luồng xử lý chuẩn thường gồm:
- Ghi nhận slug mới, tạo bản ghi
UrlAlias mới với path mới. - Đánh dấu bản ghi cũ là
isactive = false (nếu dùng soft history). - Tạo bản ghi trong bảng Redirect:
Redirect(id, frompath, topath, statuscode, createdat, ...) statuscode thường là 301 để giữ sức mạnh SEO và tránh mất traffic.
Ở mức ứng dụng, cần thêm các kiểm tra:
- Validate slug không chứa ký tự đặc biệt, không trùng với các reserved path (ví dụ
/admin, /api). - Chuẩn hóa slug (lowercase, thay khoảng trắng bằng dấu gạch ngang, loại bỏ dấu tiếng Việt) trước khi lưu để ràng buộc UNIQUE hoạt động ổn định.
SKU biến thể phải duy nhất để quản lý tồn kho, đơn hàng và đồng bộ kênh bán
Trong thiết kế ERD bán hàng, SKU (Stock Keeping Unit) là mã định danh nghiệp vụ cho từng biến thể sản phẩm. Khác với id (primary key) mang tính kỹ thuật, SKU là mã được sử dụng xuyên suốt trong:
- Quản lý kho nội bộ (Inventory, StockMovement).
- Đơn hàng (OrderItem, InvoiceItem).
- Đồng bộ với các hệ thống bên ngoài: Marketplace, POS, ERP, WMS.

Vì vậy, trường ProductVariant.sku cần được ràng buộc UNIQUE trên toàn hệ thống. Một số điểm chuyên sâu cần lưu ý:
- Nếu hệ thống có nhiều kho (multi-warehouse), SKU vẫn nên duy nhất trên toàn hệ thống, còn số lượng tồn được tách sang bảng
Inventory(warehouseid, sku, quantity) với: - FK(sku) →
ProductVariant.sku - UNIQUE(warehouseid, sku) để mỗi SKU chỉ xuất hiện một lần trong mỗi kho.
- Trong bảng OrderItem, nên lưu cả:
productvariantid (FK kỹ thuật) sku (mã nghiệp vụ tại thời điểm đặt hàng)
để khi SKU thay đổi trong tương lai vẫn có thể đối soát với chứng từ cũ. - Bảng StockMovement (nhập, xuất, điều chỉnh) nên ràng buộc:
sku NOT NULL productvariantid NOT NULL - FK tới
ProductVariant để đảm bảo mọi biến động tồn kho đều gắn với một biến thể hợp lệ.
Ràng buộc UNIQUE trên SKU kết hợp với các ràng buộc FK giữa ProductVariant, Inventory, OrderItem, StockMovement giúp đảm bảo:
- Mỗi dòng tồn kho (Inventory) chỉ thuộc về một biến thể duy nhất.
- Mỗi dòng đơn hàng (OrderItem) tham chiếu chính xác tới biến thể được bán.
- Các báo cáo tồn kho, doanh thu, lợi nhuận theo SKU không bị trùng lặp hoặc thiếu dữ liệu.
Ngoài ra, cần quy định rõ quy tắc sinh SKU (pattern, độ dài, prefix theo brand hoặc category) và đảm bảo ứng dụng không cho phép chỉnh sửa SKU tùy tiện sau khi đã phát sinh giao dịch, hoặc nếu cho phép thì phải có cơ chế lịch sử mapping SKU cũ – SKU mới để phục vụ đối soát.
OrderItem cần lưu giá tại thời điểm mua để không bị thay đổi khi giá sản phẩm cập nhật
Trong mô hình dữ liệu bán hàng, OrderItem là nơi ghi nhận chi tiết giao dịch tại thời điểm mua. Một sai lầm phổ biến là để giá của OrderItem phụ thuộc động vào bảng ProductVariant. Hệ thống xử lý giao dịch phải bảo đảm tính nguyên tử, nhất quán, cô lập và bền vững để đơn hàng không rơi vào trạng thái cập nhật dở dang. Khi tạo Order, các thao tác ghi OrderItem, áp khuyến mãi, giữ tồn kho và tạo trạng thái thanh toán cần được kiểm soát trong một ranh giới giao dịch phù hợp. Nếu một bước thất bại, hệ thống phải rollback hoặc có cơ chế bù trừ rõ ràng. Giá, thuế, SKU và mô tả tại thời điểm mua phải được ghi thành snapshot bất biến, vì việc truy vấn lại ProductVariant hiện tại sẽ làm sai báo cáo lịch sử, hoàn tiền và đối soát. Nhật ký giao dịch còn là nền tảng phục hồi dữ liệu sau lỗi máy chủ hoặc mất kết nối (Gray & Reuter, 1993). Thiết kế đúng cần đảm bảo:
- OrderItem.unitprice: đơn giá gốc tại thời điểm đặt hàng (chưa giảm giá, có thể chưa bao gồm thuế tùy mô hình).
- OrderItem.discountamount hoặc
discountrate: giá trị hoặc tỷ lệ chiết khấu áp dụng cho dòng hàng. - OrderItem.taxamount hoặc
taxrate: phần thuế tính riêng cho dòng hàng. - OrderItem.finalprice: có thể lưu thêm để tối ưu truy vấn, bằng:
finalprice = unitprice - discountamount + taxamount

Các trường này phải được lưu cố định tại thời điểm tạo đơn và không được cập nhật theo giá hiện tại của ProductVariant. Đây là ràng buộc logic ở tầng nghiệp vụ, không thể biểu diễn hoàn toàn bằng constraint SQL, nhưng có thể hỗ trợ bằng:
- Không join trực tiếp OrderItem với ProductVariant để hiển thị giá trong báo cáo lịch sử, mà chỉ dùng giá đã snapshot trong OrderItem.
- Thiết lập quyền hạn: không cho phép cập nhật giá trên OrderItem sau khi đơn đã chuyển sang trạng thái khóa (ví dụ:
paid, completed). - Sử dụng trigger hoặc event trong ứng dụng để:
- Snapshot giá từ ProductVariant và các bảng khuyến mãi (Promotion, Coupon) vào OrderItem khi tạo đơn.
- Ghi log thay đổi nếu có điều chỉnh thủ công (manual override) để phục vụ kiểm toán.
Nhờ đó, khi cập nhật giá sản phẩm (thay đổi ProductVariant.price, cấu trúc thuế, chương trình khuyến mãi), các đơn hàng cũ vẫn giữ nguyên giá trị, phục vụ:
- Đối soát doanh thu theo ngày, tháng, chiến dịch.
- Tính thuế và lập báo cáo tài chính.
- Xử lý khiếu nại, hoàn tiền, đổi trả dựa trên giá gốc đã bán.
Nếu không tuân thủ nguyên tắc snapshot giá, việc thay đổi giá sản phẩm sẽ làm sai lệch toàn bộ lịch sử đơn hàng, gây rủi ro lớn về tài chính, pháp lý và làm mất độ tin cậy của hệ thống báo cáo.
SeoMeta cần ràng buộc mỗi URL chỉ có một canonical chính
Trong chiến lược SEO, canonical là tín hiệu quan trọng giúp công cụ tìm kiếm hiểu đâu là phiên bản chuẩn của một nội dung khi tồn tại nhiều URL khác nhau (do tham số, phân trang, lọc, sắp xếp,...). Bảng SeoMeta thường được thiết kế để quản lý các thuộc tính SEO cho từng URL:
SeoMeta(id, url, title, description, canonicalurl, iscanonical, metarobots, ...)

Về ràng buộc dữ liệu, có hai cách tiếp cận:
- Nếu mỗi URL chỉ có một bản ghi SeoMeta:
- Ràng buộc UNIQUE(url).
- Trường
iscanonical có thể suy ra (ví dụ: luôn true, hoặc chỉ dùng khi cần override).
- Nếu cho phép nhiều bản ghi cho cùng một URL (ví dụ: theo ngôn ngữ, theo phiên bản A/B test):
- Dùng trường
iscanonical kiểu boolean. - Ràng buộc UNIQUE(url, iscanonical) với điều kiện chỉ áp dụng khi
iscanonical = true (có thể dùng partial index tùy hệ quản trị CSDL).
Ràng buộc này đảm bảo không có nhiều canonical mâu thuẫn cho cùng một trang, tránh gây nhầm lẫn cho công cụ tìm kiếm và giảm nguy cơ phân tán sức mạnh SEO. Ở tầng ứng dụng, cần bổ sung logic:
- Chuẩn hóa
url trước khi lưu (loại bỏ slash dư, chuẩn hóa chữ hoa/thường, xử lý trailing slash). - Đảm bảo canonical luôn trỏ đến URL chuẩn đã được định nghĩa trong hệ thống routing (thường là URL ngắn, không tham số).
- Trong các trường hợp có tham số, phân trang, lọc sản phẩm:
- SeoMeta của URL có tham số nên có
canonicalurl trỏ về URL gốc không tham số. - Tránh tạo nhiều bản ghi SeoMeta dư thừa cho các biến thể URL không cần index.
Thiết kế ERD rõ ràng cho SeoMeta, với ràng buộc UNIQUE hợp lý, giúp đội SEO kiểm soát tốt hơn cấu trúc index của website, giảm lỗi cấu hình meta tag và đảm bảo tính nhất quán giữa các môi trường (staging, production).
Event tracking cần gắn source, medium, campaign và landing page để đo hiệu quả marketing
Hệ thống tracking sự kiện là nền tảng cho phân tích marketing và tối ưu hiệu quả chi tiêu quảng cáo. Các bảng như VisitSession, ClickEvent, SocialMetric, AdCostLog cần được thiết kế nhất quán về schema để dễ join và tổng hợp dữ liệu. Các trường quan trọng thường bao gồm:
- source: nguồn traffic (google, facebook, email,...).
- medium: kênh (cpc, organic, referral, social, email,...).
- campaign: tên chiến dịch (blackfriday2025, launchnewproduct,...).
- landingpageurl: URL trang đích đầu tiên người dùng truy cập.
- Các trường bổ sung:
content, term, device, country, sessionid, userid,...

Về ràng buộc dữ liệu, có thể áp dụng:
- NOT NULL cho
source, medium trong các bảng mà dữ liệu này luôn xác định được (ví dụ AdCostLog). - Cho phép NULL nhưng có logic fill default (ví dụ
source = 'direct', medium = 'none') khi không đọc được UTM. - Đảm bảo
landingpageurl được chuẩn hóa (loại bỏ fragment, chuẩn hóa query string nếu cần) để tránh phân mảnh dữ liệu khi báo cáo.
Trong pipeline tracking, cần các quy tắc nghiệp vụ:
- Khi tạo VisitSession:
- Parse UTM từ URL (utmsource, utmmedium, utmcampaign, utmcontent, utmterm).
- Gán giá trị vào các trường
source, medium, campaign nếu có. - Nếu không có UTM, suy luận từ referrer (ví dụ: referrer chứa
google. → source = 'google', medium = 'organic').
- Khi ghi ClickEvent hoặc SocialMetric:
- Luôn gắn
sessionid hoặc userid để có thể quy chiếu về VisitSession. - Copy hoặc join lại các trường
source, medium, campaign để tránh phải join nhiều bảng khi báo cáo.
- Khi ghi AdCostLog:
- Đảm bảo mapping chính xác giữa
campaign trong hệ thống quảng cáo (Google Ads, Facebook Ads) và campaign nội bộ. - Chuẩn hóa tên campaign để join được với dữ liệu doanh thu (ví dụ: mapping bảng
CampaignMapping nếu tên khác nhau giữa các nền tảng).
Nhờ gắn đầy đủ và nhất quán các trường này, hệ thống có thể xây dựng báo cáo marketing chính xác:
- Doanh thu, số đơn hàng, số khách hàng mới theo source/medium.
- Hiệu quả từng campaign (ROAS, CPA, CPL,...).
- Hiệu suất từng landing page (conversion rate, bounce rate, revenue per visit).
Nếu thiếu hoặc không chuẩn hóa, dữ liệu sẽ bị phân mảnh, khó phân tích và khó tối ưu ngân sách. Thiết kế ERD cần đảm bảo các trường tracking được đặt tên thống nhất, kiểu dữ liệu tương thích và có index phù hợp (ví dụ index trên campaign, source, medium, landingpage_url) để hỗ trợ các truy vấn phân tích khối lượng lớn.
FAQ về sơ đồ ERD website bán hàng
Phần FAQ tập trung giải đáp các câu hỏi thực tế khi thiết kế ERD cho website bán hàng, từ mức tối thiểu đến mở rộng nâng cao. Trọng tâm là nhóm bảng cốt lõi cho bán hàng (User, Customer, Product, Order, Payment, Inventory...), sau đó mở rộng sang tracking, marketing, SEO, social, landing page builder, đa kênh và chống click tặc. Nội dung nhấn mạnh việc tách Product / ProductVariant, tách riêng SeoMeta, Redirect, cũng như thiết kế linh hoạt bằng JSON (settings, params) để hạn chế phải migrate schema. Bên cạnh đó là các gợi ý thực tiễn cho website nhỏ: có thể lược bớt một số bảng nâng cao nhưng vẫn nên chuẩn bị sẵn cấu trúc VisitSession, ClickEvent, Channel để dễ mở rộng sau này.

ERD website bán hàng tối thiểu cần những bảng nào?
Một ERD tối thiểu cho website bán hàng thường cần các bảng cốt lõi: User, Customer, Product, Category, Cart, CartItem, Order, OrderItem, Payment, Inventory, Warehouse. Nếu có khuyến mãi cơ bản, thêm Promotion, Coupon. Nếu chú trọng SEO, thêm SeoMeta, Redirect, SitemapUrl. Tùy quy mô và mục tiêu, có thể mở rộng thêm các nhóm entity về marketing, tracking, social, chống click tặc như đã mô tả.
Ở mức thiết kế chuyên sâu hơn, có thể phân tách vai trò và trách nhiệm của từng bảng:
- User: đại diện tài khoản đăng nhập hệ thống (admin, nhân viên CSKH, nhân viên kho, đối tác). Thường chứa thông tin xác thực (email, password hash, role, permission). Không nên trộn lẫn với thông tin khách mua hàng.
- Customer: đại diện khách hàng mua sắm (có thể có hoặc không có tài khoản). Thường có các trường: fullname, phone, email, defaultshippingaddressid, defaultbillingaddressid, loyaltypoint, customergroupid. Nên tách thêm bảng CustomerAddress nếu cần nhiều địa chỉ, nhưng với ERD tối thiểu có thể lưu trực tiếp trong Customer.
- Product: lớp khái niệm, tập trung vào nội dung và phân loại: name, slug, description, brandid, categoryid, status, seotitle, seodescription (nếu chưa tách SeoMeta), attributes chung (material, origin...).
- Category: phân cấp danh mục sản phẩm, thường là cấu trúc cây: parentid, name, slug, level, sortorder, isactive. Có thể thêm trường path hoặc lft/rgt nếu dùng mô hình nested set.
- Cart: đại diện giỏ hàng của một khách (đăng nhập hoặc guest). Chứa customerid (nullable), sessionid, status (active, converted, abandoned), currency, tổng tạm tính, tổng giảm giá, tổng thuế.
- CartItem: từng dòng sản phẩm trong giỏ: cartid, productvariantid, quantity, unitprice, discountamount, note. Thường gắn với ProductVariant để quản lý tồn kho chính xác.
- Order: đơn hàng đã chốt, là snapshot tại thời điểm đặt: ordernumber, customerid, status (pending, paid, shipped, completed, cancelled...), totalamount, shippingfee, taxamount, discountamount, paymentstatus, shippingaddress, billingaddress, channel, source.
- OrderItem: chi tiết từng dòng trong đơn: orderid, productvariantid, productnamesnapshot, skusnapshot, unitprice, quantity, linetotal, appliedpromotionid.
- Payment: giao dịch thanh toán: orderid, provider (VNPay, Momo, COD...), amount, currency, status, transactionref, paidat, failurereason. Có thể có nhiều bản ghi Payment cho một Order (retry, partial refund).
- Inventory: tồn kho theo từng biến thể và kho: productvariantid, warehouseid, onhand, reserved, available, safetystock. Cho phép tính toán chính xác số lượng có thể bán.
- Warehouse: thông tin kho vật lý hoặc kho ảo (store, dropship, fulfillment partner): name, code, address, type, isactive.
- Promotion: định nghĩa chương trình khuyến mãi: name, type (percentage, fixedamount, buyxgety...), scope (product, category, cart), startat, endat, conditions (JSON), maxuses, maxusespercustomer.
- Coupon: mã giảm giá cụ thể: code, promotionid, usagelimit, usedcount, customergroupid, minordervalue, issingleuse.
- SeoMeta: metadata cho nhiều loại entity: entitytype (Product, Category, Page, Brand...), entityid, metatitle, metadescription, metakeywords, canonicalurl, ogtags (JSON), indexable (boolean).
- Redirect: quy tắc chuyển hướng: sourceurl, targeturl, httpcode (301, 302), isactive, createdby, note. Hữu ích khi thay đổi cấu trúc URL hoặc xóa sản phẩm.
- SitemapUrl: lưu các URL đưa vào sitemap: loc, lastmod, changefreq, priority, entitytype, entityid, isindexed. Cho phép rebuild sitemap có kiểm soát.
Nhóm entity nâng cao về marketing/tracking/social/chống click tặc có thể được thiết kế sao cho tách biệt với lõi bán hàng, tránh làm phức tạp các bảng cốt lõi nhưng vẫn liên kết được qua khóa ngoại (customerid, visitsessionid, campaignid...).
Website bán hàng nhỏ có cần bảng campaign và tracking không?
Với website nhỏ, có thể tạm thời bỏ qua một số bảng nâng cao như Campaign, AdCostLog, SocialMetric, nhưng vẫn nên có tối thiểu VisitSession, ClickEvent để lưu dữ liệu hành vi cơ bản. Khi quy mô marketing tăng, việc đã có sẵn cấu trúc tracking sẽ giúp mở rộng nhanh hơn. Bảng Campaign đặc biệt hữu ích khi bắt đầu chạy quảng cáo đa kênh và cần đo lường ROI chi tiết.
Thiết kế chi tiết hơn cho nhóm tracking:
- VisitSession: đại diện một phiên truy cập:
- Trường nên có: sessionid, customerid (nullable), ip, useragent, devicetype, source, medium, campaign, landingpage, referrer, startedat, endedat.
- Có thể thêm trường utmsource, utmmedium, utmcampaign, utmcontent, utmterm để lưu nguyên gốc tham số UTM.
- Liên kết với Order qua order.visitsessionid để phân tích hành trình từ click đến mua.
- ClickEvent: từng sự kiện click hoặc tương tác:
- Trường: visitsessionid, eventtype (clickad, clickbanner, viewproduct, addtocart, begincheckout, purchase...), targetid, targettype, url, createdat.
- Cho phép xây dựng funnel và phân tích drop-off theo bước.
- Campaign (khi cần): định nghĩa chiến dịch marketing:
- name, channel (Facebook, Google, TikTok, Email...), objective (traffic, conversion...), startat, endat, budget, targeturl, trackingparams (JSON).
- Liên kết với VisitSession qua campaigncode hoặc campaignid để tính CPA, ROAS.
- AdCostLog: chi phí quảng cáo theo ngày hoặc theo nhóm quảng cáo:
- campaignid, date, channel, cost, impressions, clicks, dataraw (JSON từ API nền tảng).
- Cho phép so sánh chi phí với doanh thu/đơn hàng sinh ra từ cùng campaign.
- SocialMetric (nếu có social): lưu hiệu suất bài đăng: socialpostid, platform, date, impressions, reach, clicks, likes, comments, shares.
Ngay cả với website nhỏ, việc có sẵn VisitSession và ClickEvent giúp:
- Phân biệt traffic chất lượng và traffic rác.
- Phân tích hiệu quả từng nguồn (organic, paid, referral).
- Chuẩn bị dữ liệu cho các thuật toán gợi ý sản phẩm, remarketing, lookalike trong tương lai.
Product và ProductVariant khác nhau thế nào trong ERD?
Product đại diện cho sản phẩm ở mức khái niệm chung: tên, mô tả, thương hiệu, danh mục, nội dung SEO. ProductVariant đại diện cho từng biến thể cụ thể có SKU riêng: màu, size, dung lượng, giá, trọng lượng. Một Product có thể có nhiều ProductVariant. Trong ERD, tồn kho, đơn hàng, giỏ hàng thường gắn với ProductVariant chứ không gắn trực tiếp với Product, để quản lý chính xác từng biến thể.
Thiết kế chi tiết:
- Product:
- Trường nội dung: name, slug, description, shortdescription, brandid, categoryid, status (draft, active, archived).
- Trường SEO: seotitle, seodescription, seoslug (hoặc liên kết SeoMeta).
- Trường quản trị: createdby, approvedby, publishedat, sortorder, tags (JSON hoặc bảng phụ).
- Không nên chứa giá, tồn kho, vì các thông tin này phụ thuộc biến thể.
- ProductVariant:
- Khóa ngoại: productid.
- Trường thương mại: sku (unique), barcode, price, compareatprice, costprice, currency, isdefault.
- Thuộc tính biến thể: color, size, capacity, material... Có thể lưu dạng cột cố định hoặc JSON, hoặc dùng bảng ProductAttributeValue nếu cần linh hoạt cao.
- Trường logistics: weight, length, width, height, hscode, isshippable.
- Liên kết với Inventory, CartItem, OrderItem, Warehouse.
Một số lưu ý chuyên môn:
- Tất cả các thực thể liên quan đến giao dịch (Inventory, CartItem, OrderItem, ShipmentItem) nên tham chiếu ProductVariant để đảm bảo tính chính xác theo SKU.
- Nếu sản phẩm không có biến thể, vẫn nên tạo một ProductVariant mặc định để giữ mô hình nhất quán.
- Có thể thêm bảng ProductImage gắn với cả Product và ProductVariant (ví dụ ảnh riêng cho từng màu).
Làm sao thiết kế ERD để hỗ trợ kéo thả landing page?
Để hỗ trợ kéo thả landing page, ERD cần tối thiểu các bảng: Page, PageBlock, Template, Media. Page lưu thông tin trang, Template định nghĩa bố cục, PageBlock lưu từng khối nội dung với blocktype và settings (JSON), Media lưu ảnh và video. Quan hệ 1-n giữa Page và PageBlock cho phép sắp xếp block theo sortorder. Thiết kế này giúp đội marketing kéo thả, cấu hình nội dung mà không cần thay đổi schema mỗi khi thêm loại block mới.
Mô tả chi tiết hơn:
- Page:
- Trường: title, slug, type (landing, blog, homepage, collection...), status (draft, published, archived), templateid, seometaid (nullable), publishedat, createdby.
- Có thể thêm trường layoutsettings (JSON) để lưu cấu hình chung của trang (màu nền, font, header/footer variant).
- Template:
- Định nghĩa khung bố cục: name, code, description, previewimageid, allowedblocktypes (JSON), isdefault.
- Không chứa nội dung cụ thể, chỉ mô tả cấu trúc và ràng buộc.
- PageBlock:
- Khóa ngoại: pageid.
- Trường chính: blocktype (hero, productgrid, banner, testimonial, countdown, form...), sortorder, settings (JSON), isactive.
- settings (JSON) có thể chứa: text, html, productids, categoryids, layoutoptions, animation, backgroundimageid.
- Cho phép thêm/sửa/xóa block mà không cần thay đổi schema, chỉ cần frontend hiểu blocktype và cấu trúc settings.
- Media:
- Trường: url, type (image, video, svg, document), mimetype, size, width, height, alttext, title, folder, uploadedby.
- Có thể liên kết với nhiều entity qua bảng trung gian (MediaUsage) hoặc lưu id trực tiếp trong settings JSON.
Với thiết kế này, công cụ kéo thả chỉ cần thao tác trên Page và PageBlock, cập nhật sortorder và settings. Backend không phải migrate database mỗi khi thêm một loại block mới, chỉ cần định nghĩa blocktype mới và logic render tương ứng.
ERD có cần bảng riêng cho SEO meta và redirect không?
Việc tách SeoMeta và Redirect thành bảng riêng mang lại nhiều lợi ích: quản lý tập trung metadata và quy tắc chuyển hướng, dễ audit và sửa lỗi SEO, hỗ trợ nhiều loại entity (Product, Category, Page, Brand) mà không phải thêm cột SEO vào từng bảng. Với website chú trọng SEO, đây gần như là bắt buộc. Nếu gộp metadata vào từng bảng, về lâu dài sẽ khó mở rộng và khó kiểm soát toàn cục.
Phân tích chuyên sâu:
- SeoMeta:
- entitytype, entityid cho phép áp dụng metadata cho bất kỳ bảng nào (Product, Category, Page, Brand, BlogPost...).
- Các trường nên có: metatitle, metadescription, metakeywords, canonicalurl, robots (index, noindex, follow, nofollow), ogtitle, ogdescription, ogimage, twittercard, structureddata (JSON).
- Có thể thêm trường locale để hỗ trợ đa ngôn ngữ, mỗi entity có nhiều bản ghi SeoMeta theo ngôn ngữ.
- Quản lý tập trung giúp dễ tìm các trang bị noindex, trùng canonical, thiếu metatitle.
- Redirect:
- sourceurl nên được chuẩn hóa (không query string hoặc có quy tắc rõ ràng), targeturl có thể là URL tuyệt đối hoặc tương đối.
- Trường: httpcode (301, 302), isregex (nếu hỗ trợ pattern), isactive, priority, hitcount, lasthitat.
- Cho phép xây dựng hệ thống redirect rule engine, tránh vòng lặp redirect và xung đột.
Lợi ích kỹ thuật khi tách bảng:
- Giảm độ phình to của các bảng nghiệp vụ (Product, Category) khi phải chứa quá nhiều cột SEO.
- Cho phép team SEO thao tác, export/import, audit trên một vài bảng chuyên biệt.
- Dễ tích hợp với các công cụ crawl nội bộ, sinh sitemap, kiểm tra broken link.
Chống click tặc nên lưu dữ liệu nào trong database?
Để chống click tặc hiệu quả, ERD nên lưu tối thiểu: VisitSession (IP, useragent, source, campaign, landingpage), ClickEvent (loại click, mục tiêu, thời gian), BlockedIp (IP bị chặn, lý do, thời gian), FraudRule (quy tắc phát hiện gian lận), AdCostLog (chi phí quảng cáo theo chiến dịch). Kết hợp các bảng này cho phép phát hiện mẫu hành vi bất thường, tự động chặn IP, và đánh giá thiệt hại do click tặc gây ra.
Mô hình chi tiết:
- VisitSession:
- ip, useragent, devicefingerprint (nếu có), source, medium, campaign, landingpage, referrer, startedat, endedat, totalclicks.
- Có thể thêm trường issuspectedfraud để đánh dấu phiên nghi ngờ.
- ClickEvent:
- visitsessionid, eventtype (clickad, clicklink, clickcall, clickzalo...), adid, campaignid, createdat.
- Cho phép phân tích tần suất click theo IP, theo session, theo thời gian.
- BlockedIp:
- ip, reason, blockedby (system, manual), blockedat, expiredat, note.
- Có thể liên kết với FraudRule để biết quy tắc nào đã kích hoạt chặn.
- FraudRule:
- name, description, ruletype (threshold, pattern, machinelearning...), params (JSON), isactive, severity.
- Ví dụ rule: “>30 clicks từ cùng IP trong 5 phút mà không có pageview sâu hoặc không có addtocart”.
- AdCostLog:
- campaignid, date, channel, cost, clicksreportedbyplatform, clickstrackedonsite, discrepancyrate.
- Giúp so sánh số click nền tảng báo cáo với số click thực tế, phát hiện bất thường.
Khi kết hợp các bảng này, có thể xây dựng pipeline:
- Thu thập VisitSession và ClickEvent theo thời gian thực.
- Áp dụng FraudRule để gắn cờ session hoặc IP nghi ngờ.
- Tự động ghi vào BlockedIp và đồng bộ với firewall, WAF, hoặc rule trên nền tảng quảng cáo.
- Đối chiếu AdCostLog để ước lượng thiệt hại và tối ưu ngân sách.
Tự động đăng bài mạng xã hội cần những entity nào?
Để tự động đăng bài mạng xã hội, ERD cần các bảng: SocialAccount (tài khoản từng nền tảng), SocialPost (nội dung bài đăng), PublishSchedule (lịch đăng), SocialPostProduct (liên kết bài với sản phẩm hoặc trang), SocialMetric (số liệu hiệu suất). Các quan hệ này cho phép lên lịch, đăng bài, gắn bài với sản phẩm và chiến dịch, sau đó đo lường hiệu quả từng nội dung trên từng kênh.
Thiết kế chi tiết:
- SocialAccount:
- platform (Facebook, Instagram, TikTok, YouTube...), accountname, pageid/profileid, accesstoken (mã hóa), refreshtoken, tokenexpiredat, isactive.
- Có thể thêm trường settings (JSON) để lưu cấu hình riêng cho từng nền tảng (mặc định hashtag, template caption...).
- SocialPost:
- title, content, mediaids (list hoặc bảng phụ), status (draft, scheduled, published, failed), createdby, campaignid (nullable).
- Không gắn cứng với một nền tảng; nội dung có thể được tái sử dụng cho nhiều SocialAccount.
- PublishSchedule:
- socialpostid, socialaccountid, scheduledat, publishedat, publishstatus, platformpostid, errormessage.
- Cho phép một SocialPost được lên lịch trên nhiều tài khoản/nền tảng khác nhau.
- SocialPostProduct:
- socialpostid, productid hoặc pageid, relationtype (featured, mentioned, tagged).
- Giúp truy vết: sản phẩm nào được quảng bá trong bài nào, trên kênh nào, hiệu quả ra sao.
- SocialMetric:
- publishscheduleid, date, impressions, reach, clicks, likes, comments, shares, saves, videoviews.
- Cho phép phân tích hiệu suất theo bài, theo nền tảng, theo chiến dịch.
Cấu trúc này hỗ trợ workflow: tạo nội dung > chọn sản phẩm liên quan > chọn tài khoản > lên lịch > thu thập metric > gắn với doanh thu (qua campaignid hoặc link tracking).
Làm sao thiết kế ERD để sau này mở rộng nhiều kênh bán hàng?
Để dễ mở rộng đa kênh, ERD nên tách biệt các khối nghiệp vụ và tránh phụ thuộc cứng vào một kênh duy nhất. Một số hướng thiết kế: dùng ProductVariant với SKU duy nhất để đồng bộ với marketplace và POS; thêm bảng Channel, ProductChannel, PriceList nếu cần giá theo kênh; thiết kế Inventory theo kho và kênh; dùng VisitSession, Campaign, AdCostLog với trường channel, source, medium linh hoạt; tách Page, SeoMeta, SocialPost để nội dung có thể phân phối trên nhiều nền tảng. Với cấu trúc như vậy, khi thêm kênh mới, chỉ cần bổ sung một số bảng và quan hệ phụ, không phải thay đổi các bảng cốt lõi như Product, Order, Customer.
Phân tích chuyên sâu cho đa kênh:
- Channel:
- code (web, pos, shopee, lazada, tiktokshop, facebookshop...), name, type (online, offline, marketplace), isactive.
- Được tham chiếu bởi Order, VisitSession, Campaign để biết đơn hàng/traffic đến từ kênh nào.
- ProductChannel:
- productvariantid, channelid, externalproductid, externalsku, statusonchannel, listingurl.
- Cho phép map một SKU nội bộ với nhiều mã sản phẩm khác nhau trên từng marketplace.
- PriceList (nếu cần giá theo kênh/nhóm khách):
- name, currency, channelid (nullable), customergroupid (nullable), validfrom, validto.
- Bảng phụ PriceListItem: pricelistid, productvariantid, price, compareatprice.
- Inventory mở rộng:
- Thêm trường channelid (nullable) để phân biệt tồn kho dành riêng cho một kênh (ví dụ kho online vs kho cửa hàng).
- Hoặc giữ Inventory theo kho vật lý, còn phân bổ theo kênh được xử lý ở tầng logic.
- VisitSession, Campaign, AdCostLog:
- Thêm trường channel, source, medium dạng text hoặc khóa ngoại tới Channel để linh hoạt mapping.
- Giúp phân tích hiệu quả marketing theo từng kênh bán hàng.
- Page, SeoMeta, SocialPost:
- Không gắn cứng với website; nội dung có thể được tái sử dụng trên app, landing riêng, hoặc embedded trong marketplace store.
- Có thể thêm trường targetchannelid hoặc visibility_settings (JSON) để kiểm soát hiển thị theo kênh.
Với cách thiết kế này, khi thêm một kênh mới (ví dụ marketplace mới), chỉ cần:
- Thêm bản ghi mới trong Channel.
- Thêm mapping trong ProductChannel.
- Cấu hình PriceList hoặc rule giá cho kênh đó.
- Tích hợp API đồng bộ tồn kho dựa trên Inventory và ProductVariant.