NoWayHome — Database Design

MySQL Schema · InnoDB · Normalized to 3NF

MySQL 8.0 InnoDB Engine 48 Bảng Dữ Liệu

Phân tích hệ thống NoWayHome

48
Bảng dữ liệu
3
Actor chính
8
Module chức năng
70+
Foreign Keys

Actor và Phân hệ

K
Khách hàng
Tìm kiếm, đặt phòng, thanh toán, đánh giá, wishlist, loyalty program.
Đ
Đối tác (Host)
Quản lý chỗ nghỉ, phòng, giá, khuyến mãi, rút tiền, phản hồi đánh giá.
A
Admin
Kiểm duyệt, CSKH, tài chính, phân quyền RBAC, cài đặt phí nền tảng.

8 Module chức năng

M1 Người dùng & Xác thực
users, social_accounts, otp_tokens, user_sessions, profiles... (6 bảng)
M2 Chỗ nghỉ & Phòng
properties, room_types, rooms, property_media, amenities... (8 bảng)
M3 Giá & Khuyến mãi
rate_plans, daily_rates, promotions, vouchers, voucher_usages (5 bảng)
M4 Đặt phòng & Thanh toán
bookings, booking_rooms, booking_guests, payments, refunds... (7 bảng)
M5 Đánh giá
reviews, review_media, review_responses (3 bảng)
M6 Tài chính & Giải ngân
wallets, transactions, payout_requests, fee configs, loyalty... (6 bảng)
M7 CSKH & Tranh chấp
support_tickets, ticket_messages, disputes (3 bảng)
M8 Quản trị & Cấu hình
roles, permissions, audit_logs, risk_rules, risk_assessments... (10 bảng)

Thực thể và Mối quan hệ

Module M1 – Người dùng & Xác thực
u
users
Bảng trung tâm cho mọi loại tài khoản (customer, partner, staff). Phân biệt qua user_type.
s
social_accounts
Lưu provider (Google, Facebook) và external_id cho Social Login.
o
otp_tokens
Mã OTP / xác thực 2 bước, có TTL.
s
user_sessions
JWT/Session token tracking, dùng cho revocation.
p
partner_profiles
Hồ sơ pháp lý, hợp đồng, trạng thái KYC của Đối tác.
c
customer_profiles
Thông tin mở rộng của Khách hàng, sở thích, loyalty tier.
Module M2 – Chỗ nghỉ & Phòng
p
properties
Chỗ nghỉ (khách sạn, homestay). Thuộc về partner_profiles. Có trạng thái KYC review.
r
room_types
Loại phòng (Superior, Deluxe...) kèm cấu hình diện tích, hướng view.
r
rooms
Phòng vật lý cụ thể, tracking trạng thái (available/occupied/blocked).
m
property_media
Ảnh/video Cloudinary của chỗ nghỉ và phòng.
a
amenities
Danh mục tiện ích chuẩn. Gán qua bảng N-N.
p
property_amenities
Bảng nối N-N giữa properties và amenities.
r
room_type_amenities
Bảng nối N-N giữa room_types và amenities.
p
property_policies
Chính sách hủy phòng, giờ check-in/out theo từng chỗ nghỉ.
Module M3 – Giá & Khuyến mãi
r
rate_plans
Gói giá (Base, Non-refundable, Breakfast included...) theo room_type.
d
daily_rates
Giá từng ngày cụ thể, override rate_plan. Hỗ trợ dynamic pricing.
p
promotions
Chương trình khuyến mãi (early bird, last-minute, partner-specific).
v
vouchers
Mã voucher cụ thể sinh từ promotion, tracking usage.
v
voucher_usages NEW
Tracking chi tiết từng lần dùng voucher. Enforce max_uses_per_user ở DB level.
Module M4 – Đặt phòng & Thanh toán
b
bookings
Đơn đặt phòng tổng. Gắn với khách hàng, chỗ nghỉ, trạng thái lifecycle.
b
booking_rooms
Chi tiết từng phòng trong booking (hỗ trợ multi-room booking).
g
booking_guests NEW
Thông tin khách đi cùng (họ tên, CCCD, quốc tịch). is_primary đánh dấu khách chính.
p
payments
Giao dịch thanh toán (credit card, ví, QR). Lưu gateway response.
r
refunds
Hoàn tiền, trạng thái xử lý, reason, gắn với payment.
w
wishlists
Danh sách yêu thích của khách hàng.
b
booking_fees NEW
Phí thực tế từng booking (platform fee, VAT, service charge). Snapshot rate tại thời điểm đặt.
Module M5 – Đánh giá
r
reviews
Đánh giá sau lưu trú, gắn với booking. Moderation bởi Admin.
m
review_media NEW
Ảnh/video thực tế từ khách hàng đính kèm đánh giá. Lưu URL Cloudinary, sort_order.
r
review_responses
Phản hồi của Đối tác đối với đánh giá khách.
Module M6 – Tài chính & Giải ngân
w
payout_wallets
Ví tiền của Đối tác, lưu available và pending balance.
t
wallet_transactions
Lịch sử giao dịch ví (credit/debit), audit trail.
r
payout_requests
Yêu cầu giải ngân của Đối tác, trạng thái xét duyệt Admin.
c
commission_configs
Cấu hình hoa hồng theo tier/đối tác/khu vực.
l
loyalty_point_ledger
Sổ cái lịch sử giao dịch điểm (earn/redeem/expire). balance được denorm tại customer_profiles.
p
platform_fee_configs
Cấu hình % phí nền tảng theo loại hình/tier/quốc gia. Admin thiết lập, có hiệu lực theo ngày.
Module M7 – CSKH & Tranh chấp
t
support_tickets
Ticket hỗ trợ khách hàng, phân công Agent.
m
ticket_messages
Lịch sử chat/email trong ticket.
d
disputes NEW
Tranh chấp hủy phòng/hoàn tiền. Lưu dispute_type, mô tả, kết quả phán quyết của Admin.
Module M8 – Admin & Hệ thống
r
roles
Vai trò RBAC (Super Admin, Finance, CS Agent, Regional Manager…).
p
permissions
Quyền hạt nhân (action:resource) ánh xạ tới roles.
s
role_permissions
Bảng nối N-N roles ↔ permissions.
s
staff_profiles
Thông tin nhân viên nội bộ, khu vực phụ trách (Data Scope).
u
user_roles
Gán role cho user, hỗ trợ scope theo khu vực.
a
audit_logs
Ghi nhận mọi thao tác quan trọng, immutable, polymorphic.
s
system_configs
Key-value cấu hình động hệ thống (env vars, feature flags).
n
notifications
Thông báo gửi đến user (booking confirm, review reminder…).
r
risk_rules NEW
Danh mục luật đánh giá rủi ro. Cấu hình rule_code, trọng số, bật/tắt linh hoạt.
r
risk_assessments NEW
Lịch sử đánh giá rủi ro từng giao dịch. Lưu risk_score, decision, triggered_rules (JSON snapshot).

M1: Người dùng & Xác thực

Bảng A (Gốc)KiểuBảng B (Đích)Ghi chú
users1 → 1customer_profilesMỗi user (loại customer) có 1 profile khách hàng
users1 → 1partner_profilesMỗi user (loại partner) có 1 profile đối tác kinh doanh
users1 → 1staff_profilesMỗi user (loại staff) có 1 profile nhân viên/admin
users1 → Nsocial_accounts1 user có thể liên kết nhiều tài khoản (Google, Facebook...)
users1 → Notp_tokens1 user có thể phát sinh nhiều mã OTP theo thời gian
users1 → Nuser_sessions1 user có thể đăng nhập trên nhiều thiết bị/trình duyệt
users1 → Nuser_roles1 user có thể được gán nhiều Roles khác nhau
roles1 → Nuser_roles1 Role được gán cho nhiều users
roles1 → Nrole_permissions1 Role bao gồm nhiều Permissions
permissions1 → Nrole_permissions1 Permission có thể thuộc về nhiều Roles
staff_profiles1 → Nstaff_profilesNhân viên cấp quản lý trực tiếp (Self-referencing via manager_id)

M2: Chỗ nghỉ & Phòng

Bảng A (Gốc)KiểuBảng B (Đích)Ghi chú
partner_profiles1 → Nproperties1 đối tác (chủ nhà) có thể sở hữu/quản lý nhiều cơ sở lưu trú
properties1 → 1property_policies1 chỗ nghỉ có 1 bộ quy định (hủy phòng, check-in, thú cưng...)
properties1 → Nroom_types1 chỗ nghỉ có nhiều loại phòng (Deluxe, Standard, Suite...)
room_types1 → Nrooms1 loại phòng có nhiều phòng vật lý thực tế (Phòng 101, 102...)
properties1 → Nrooms(Shortcut) Chỗ nghỉ chứa nhiều phòng vật lý
properties1 → Nproperty_media1 chỗ nghỉ có nhiều hình ảnh/video tổng quan
room_types1 → Nproperty_media1 loại phòng cũng có bộ hình ảnh/video riêng
propertiesN ↔ NamenitiesThông qua bảng trung gian property_amenities (Tiện ích chung)
room_typesN ↔ NamenitiesThông qua bảng trung gian room_type_amenities (Tiện ích trong phòng)
users1 → Nwishlists1 user có thể lưu nhiều chỗ nghỉ vào danh sách yêu thích
properties1 → Nwishlists1 chỗ nghỉ có thể nằm trong wishlists của nhiều users

M3: Giá & Khuyến mãi

Bảng A (Gốc)KiểuBảng B (Đích)Ghi chú
room_types1 → Nrate_plans1 loại phòng bán theo nhiều gói giá (chỉ phòng, có ăn sáng...)
rate_plans1 → Ndaily_rates1 gói giá được setup mức giá và tồn kho riêng cho từng ngày
partner_profiles1 → Npromotions1 đối tác có thể tạo nhiều chương trình khuyến mãi
promotions1 → Nvouchers1 chương trình KM sinh ra nhiều mã voucher cụ thể

M4: Đặt phòng & Thanh toán

Bảng A (Gốc)KiểuBảng B (Đích)Ghi chú
users (Khách)1 → Nbookings1 khách hàng có thể đặt nhiều đơn phòng
properties1 → Nbookings1 chỗ nghỉ có nhiều booking đổ về
vouchers1 → Nbookings1 mã voucher có thể áp dụng cho nhiều booking
bookings1 → Nbooking_rooms1 booking có thể đặt nhiều phòng (khách đi theo đoàn)
rooms1 → Nbooking_rooms1 phòng vật lý có lịch sử được đặt nhiều lần qua các booking
rate_plans1 → Nbooking_roomsLưu lại thông tin gói giá đã áp dụng cho từng phòng lúc đặt
bookings1 → Nbooking_guests1 booking có danh sách nhiều khách lưu trú (khai báo e-KYC)
bookings1 → Npayments1 booking có thể chia làm nhiều đợt thanh toán (cọc, tất toán)
payments1 → Nrefunds1 giao dịch thanh toán có thể có nhiều lệnh hoàn tiền
payments1 → 1risk_assessments1 giao dịch thanh toán được hệ thống chấm điểm rủi ro 1 lần
bookings1 → Nbooking_fees1 booking chịu nhiều loại phí (VAT, phí nền tảng, phí dịch vụ)
vouchers1 → Nvoucher_usages1 mã voucher có lịch sử sử dụng chi tiết (ghi nhận ai dùng)
bookings1 → 1voucher_usages(Unique) Mỗi booking chỉ dùng 1 voucher, lưu 1 bản ghi lịch sử

M5: Đánh giá & Nội dung

Bảng A (Gốc)KiểuBảng B (Đích)Ghi chú
bookings1 → 1reviewsMỗi chuyến đi khách chỉ được đánh giá 1 lần (Unique per booking)
users (Khách)1 → Nreviews1 khách hàng có thể viết nhiều đánh giá (cho các chuyến đi khác nhau)
properties1 → Nreviews1 chỗ nghỉ nhận được nhiều lượt đánh giá
reviews1 → 1review_responsesChủ nhà (Responder) chỉ được phản hồi đánh giá 1 lần duy nhất
reviews1 → Nreview_mediaKhách có thể up nhiều ảnh/video kèm theo review
users1 → Nnotifications1 người dùng nhận được nhiều thông báo hệ thống

M6: Tài chính & Giải ngân

Bảng A (Gốc)KiểuBảng B (Đích)Ghi chú
partner_profiles1 → 1payout_walletsMỗi đối tác có duy nhất 1 ví doanh thu
payout_wallets1 → Nwallet_transactions1 ví phát sinh nhiều biến động số dư (Cộng/Trừ tiền)
payout_wallets1 → Npayout_requestsĐối tác tạo nhiều lệnh yêu cầu rút tiền từ ví ra ngân hàng
users (Khách)1 → Nloyalty_point_ledger1 khách hàng có sổ cái ghi nhận nhiều lần cộng/trừ điểm thưởng

M7: CSKH & Tranh chấp

Bảng A (Gốc)KiểuBảng B (Đích)Ghi chú
users (Khách)1 → Nsupport_tickets1 người có thể gửi nhiều yêu cầu hỗ trợ (Ticket)
users (Agent)1 → Nsupport_tickets1 nhân viên CSKH được assign xử lý nhiều Tickets
bookings1 → Nsupport_tickets1 mã đặt phòng có thể gặp nhiều sự cố phải mở Ticket
support_tickets1 → Nticket_messages1 Ticket chứa lịch sử chat qua lại nhiều tin nhắn
bookings1 → Ndisputes1 chuyến đi có thể phát sinh khiếu nại (đòi đền bù/tranh chấp)
users1 → NdisputesKhách/Đối tác có thể là người tạo (Raised_by) nhiều tranh chấp

M8: Quản trị & Cấu hình

Bảng A (Gốc)KiểuBảng B (Đích)Ghi chú
users (Admin)1 → Naudit_logsGhi vết (Trace) 1 admin/system đã thay đổi dữ liệu nào
users (Admin)1 → Nsystem_configs1 admin cập nhật cấu hình hệ thống (updated_by)
users (Admin)1 → Ncommission_configs1 admin setup nhiều quy định thu hoa hồng
users (Admin)1 → Nplatform_fee_configs1 admin setup nhiều quy tắc thu phí nền tảng/phí dịch vụ

Mối Quan Hệ Mở Rộng (Polymorphic/Loose Coupling)

Bảng A (Gốc)KiểuBảng B (Đích)Ghi chú
notifications1 → N(Bất kỳ bảng nào)Kết nối qua entity_typeentity_id (VD: bookings, payments)
audit_logs1 → N(Bất kỳ bảng nào)Theo dõi biến động dữ liệu toàn hệ thống qua entity_typeentity_id

Data Dictionary – Đầy đủ 48 bảng

Ghi chú: Tất cả các bảng hệ thống đều có cột created_atupdated_at để audit. id luôn là BIGINT UNSIGNED AUTO_INCREMENT.

M1 Người dùng & Xác thực (6 bảng)

users — Tài khoản đăng nhập (Customer, Partner, Staff)

Tên cộtKiểu dữ liệuRàng buộcMô tả
idBIGINT UNSIGNEDPKKhóa chính
uuid / email / phoneVARCHARUKCác trường định danh duy nhất
password_hashVARCHAR(255)NULLNULL nếu chỉ đăng nhập qua Social
user_typeENUMNOT NULL'customer', 'partner', 'staff'
statusENUMNOT NULL'active', 'suspended', 'pending'

social_accounts — Liên kết đăng nhập MXH

Tên cộtKiểu dữ liệuRàng buộcMô tả
user_idBIGINT UNSIGNEDFK→ users.id
providerENUMNOT NULL'google', 'facebook', 'apple', 'zalo'
provider_idVARCHAR(255)NOT NULLID trả về từ MXH. UNIQUE(provider, provider_id)

otp_tokens — Mã xác thực OTP (Email / SMS)

Tên cộtKiểu dữ liệuRàng buộcMô tả
identifierVARCHAR(255)NOT NULLEmail/SDT nhận mã
purposeENUMNOT NULL'register', 'login', 'reset_password'...
token_hashVARCHAR(255)NOT NULLMã hash SHA-256 của mã OTP 6 số
expires_atDATETIMENOT NULLHạn sử dụng (thường +5 phút)

user_sessions — Refresh Tokens & Phiên đăng nhập

Tên cộtKiểu dữ liệuRàng buộcMô tả
user_idBIGINT UNSIGNEDFK→ users.id
token_hashVARCHAR(255)UKHash của refresh token
device_typeENUMNOT NULL'web', 'ios', 'android'
revoked_atDATETIMENULLThu hồi session (Đăng xuất)

partner_profiles — Hồ sơ Đối tác & Doanh nghiệp

Tên cộtKiểu dữ liệuRàng buộcMô tả
user_idBIGINT UNSIGNEDFK UK→ users.id (1-1)
business_typeENUMNOT NULL'individual', 'company'
kyc_statusENUMNOT NULL'pending', 'approved', 'rejected'
commission_tierVARCHAR(50)NOT NULLHạng chia sẻ hoa hồng

customer_profiles — Hồ sơ Khách hàng & Loyalty

Tên cộtKiểu dữ liệuRàng buộcMô tả
user_idBIGINT UNSIGNEDFK UK→ users.id (1-1)
loyalty_tierENUMNOT NULL'member', 'silver', 'gold', 'platinum'
loyalty_points_balanceINT UNSIGNEDNOT NULLSố điểm hiện có
total_bookingsINT UNSIGNEDNOT NULLTổng số booking thành công
M2 Chỗ nghỉ & Phòng (8 bảng)

properties — Chỗ nghỉ (Khách sạn, Homestay...)

Tên cộtKiểu dữ liệuRàng buộcMô tả
partner_idBIGINT UNSIGNEDFK→ partner_profiles.id
property_typeENUMNOT NULL'hotel', 'homestay', 'resort', 'apartment'...
latitude / longitudeDECIMALNOT NULLTọa độ GPS
statusENUMNOT NULL'draft', 'pending_review', 'active', 'suspended'

room_types — Loại phòng (Deluxe, Superior...)

Tên cộtKiểu dữ liệuRàng buộcMô tả
property_idBIGINT UNSIGNEDFK→ properties.id
max_occupancyTINYINTNOT NULLSức chứa tối đa (Khách)
total_roomsSMALLINTNOT NULLTổng số lượng phòng thuộc loại này

rooms — Phòng vật lý cụ thể

Tên cộtKiểu dữ liệuRàng buộcMô tả
room_type_idBIGINT UNSIGNEDFK→ room_types.id
room_numberVARCHAR(20)NOT NULLSố phòng (P.101, A2...)
statusENUMNOT NULL'available', 'occupied', 'blocked', 'maintenance'

amenities — Danh mục tiện ích chuẩn

Tên cộtKiểu dữ liệuRàng buộcMô tả
nameVARCHAR(100)UKTên tiện ích (Wifi, Hồ bơi...)
categoryVARCHAR(50)NOT NULLPhân loại (Dịch vụ, Giải trí...)

property_amenities & room_type_amenities — Pivot Tables N-N

BảngKhóa 1Khóa 2Mô tả
property_amenitiesproperty_id (FK)amenity_id (FK)Tiện ích chung của chỗ nghỉ (Bãi đỗ xe...)
room_type_amenitiesroom_type_id (FK)amenity_id (FK)Tiện ích riêng trong phòng (Bồn tắm, Ban công...)

property_media — Media (Ảnh/Video)

Tên cộtKiểu dữ liệuRàng buộcMô tả
property_idBIGINT UNSIGNEDFK→ properties.id
room_type_idBIGINT UNSIGNEDFK NULLNếu NULL: ảnh chung. Nếu có: ảnh loại phòng.
urlVARCHAR(500)NOT NULLĐường dẫn Cloudinary/S3
is_coverTINYINT(1)NOT NULLĐánh dấu ảnh bìa

property_policies EXTENDED — Cấu hình chính sách & Quy định vận hành Chỗ nghỉ

Nhóm chính sách Tên cột & Kiểu dữ liệu Ràng buộc Mô tả chi tiết & Logic nghiệp vụ
Check-in / Out check_in_from, until
TIME
NOT NULL Khung giờ nhận phòng tiêu chuẩn. Dùng để gửi thông báo nhắc nhở khách và hiển thị trên bộ lọc "Nhận phòng 24h".
early_check_in_fee, late_check_out_fee
DECIMAL(12,2)
NULL Phụ phí nhận phòng sớm hoặc trả phòng muộn. Nếu NULL nghĩa là chỗ nghỉ không hỗ trợ dịch vụ này.
Chính sách Trẻ em infant_0_4_fee, child_5_11_fee
DECIMAL(12,2)
NOT NULL Mức phí áp dụng cho từng độ tuổi khi ngủ chung giường. Dùng để tính toán giá cuối cùng (Final Price) khi khách nhập số lượng trẻ em lúc tìm kiếm.
child_5_11_must_use_extra_bed
TINYINT(1)
NOT NULL Quy định bắt buộc kê giường phụ cho trẻ từ 5-11 tuổi. Nếu bật, hệ thống tự động cộng thêm phí extra_bed_charge.
Thanh toán & Cọc deposit_required, deposit_value
MIXED
NULL Quy định tiền cọc giữ chỗ. Support cả dạng % tổng đơn hoặc số tiền cố định. deposit_days_before xác định hạn chót phải cọc.
accepted_payment_methods
JSON
NULL Danh sách các phương thức thanh toán tại chỗ (Offline). VD: ["cash", "visa", "transfer"].
Dịch vụ (Fees) breakfast_fee, wifi_fee, parking_fee
DECIMAL(12,2)
NULL Đơn giá các dịch vụ gia tăng. Nếu cờ _included là 1 thì hiển thị "Miễn phí" cho khách.
House Rules smoking_penalty, pet_fee, marriage_cert
MIXED
NULL Các chế tài xử phạt và quy định pháp lý (VD: Giấy ĐKKH). Giúp giảm thiểu tranh chấp giữa Host và Khách.
M3 Giá & Khuyến mãi (5 bảng)

rate_plans — Gói giá (Room Only, B&B...)

Tên cộtKiểu dữ liệuRàng buộcMô tả
room_type_idBIGINT UNSIGNEDFK→ room_types.id
meal_planENUMNOT NULL'room_only', 'breakfast', 'all_inclusive'...
base_priceDECIMAL(12,2)NOT NULLGiá niêm yết cơ sở

daily_rates — Quản lý giá theo ngày (Dynamic Pricing)

Tên cộtKiểu dữ liệuRàng buộcMô tả
rate_plan_idBIGINT UNSIGNEDFK→ rate_plans.id
dateDATENOT NULLNgày cụ thể (UNIQUE cùng với rate_plan_id)
priceDECIMAL(12,2)NOT NULLGiá ghi đè trong ngày
available_qtySMALLINTNOT NULLSố phòng mở bán trong ngày

promotions — Chương trình khuyến mãi

Tên cộtKiểu dữ liệuRàng buộcMô tả
discount_typeENUMNOT NULL'percent', 'fixed'
discount_valueDECIMAL(10,2)NOT NULLMức giảm
start_date / end_dateDATENOT NULLHiệu lực chương trình

vouchers — Mã Voucher sinh ra từ Promotion

Tên cộtKiểu dữ liệuRàng buộcMô tả
promotion_idBIGINT UNSIGNEDFK→ promotions.id
codeVARCHAR(50)UKMã nhập (SUMMER25, FREESHIP...)
max_uses_per_userTINYINTNOT NULLGiới hạn số lần dùng mỗi KH

voucher_usages — Lịch sử dùng mã giảm giá

Tên cộtKiểu dữ liệuRàng buộcMô tả
voucher_idBIGINT UNSIGNEDFK→ vouchers.id
user_idBIGINT UNSIGNEDFK→ users.id
booking_idBIGINT UNSIGNEDFK UKUNIQUE(voucher_id, booking_id)
M4 Đặt phòng & Thanh toán (7 bảng)

bookings — Đơn đặt phòng tổng thể

Tên cộtKiểu dữ liệuRàng buộcMô tả
booking_codeVARCHAR(30)UKMã booking công khai (NWH-12345)
customer_id / property_idBIGINT UNSIGNEDFKLiên kết Khách hàng & Chỗ nghỉ
check_in_date / check_out_dateDATENOT NULLNgày lưu trú
total_amountDECIMAL(12,2)NOT NULLKhách hàng cần thanh toán
platform_fee_amountDECIMAL(12,2)NOT NULLTiền Nền tảng thu (Denormalized)
partner_payout_amountDECIMAL(12,2)NOT NULLTiền Đối tác nhận về (Denormalized)
statusENUMNOT NULL'pending', 'confirmed', 'checked_in', 'cancelled'...

booking_rooms — Chi tiết phòng đã đặt (Multi-room)

Tên cộtKiểu dữ liệuRàng buộcMô tả
booking_idBIGINT UNSIGNEDFK→ bookings.id
room_idBIGINT UNSIGNEDFK UK→ rooms.id. Đảm bảo 1 phòng không book 2 lần.
room_priceDECIMAL(12,2)NOT NULLGiá chốt tại thời điểm đặt

booking_guests — Khách lưu trú (Check-in)

Tên cộtKiểu dữ liệuRàng buộcMô tả
booking_idBIGINT UNSIGNEDFK→ bookings.id
full_nameVARCHAR(255)NOT NULLHọ tên người ở
is_primaryTINYINT(1)NOT NULL1 = Người đại diện đặt

booking_fees — Snapshot các loại phí

Tên cộtKiểu dữ liệuRàng buộcMô tả
fee_typeENUMNOT NULL'platform', 'vat', 'service', 'other'
fee_amountDECIMAL(12,2)NOT NULLSố tiền phí
charged_toENUMNOT NULL'customer' hay 'partner' chịu phí này?

payments — Lịch sử thanh toán

Tên cộtKiểu dữ liệuRàng buộcMô tả
booking_idBIGINT UNSIGNEDFK→ bookings.id
transaction_refVARCHAR(100)UKMã tham chiếu từ Cổng thanh toán (VNPay, Momo)
payment_methodENUMNOT NULL'credit_card', 'ewallet', 'bank_transfer'...
statusENUMNOT NULL'pending', 'success', 'failed'

refunds — Giao dịch hoàn tiền

Tên cộtKiểu dữ liệuRàng buộcMô tả
payment_idBIGINT UNSIGNEDFK→ payments.id
amountDECIMAL(12,2)NOT NULLSố tiền hoàn trả
statusENUMNOT NULL'pending', 'processing', 'completed'

wishlists — Yêu thích của khách hàng

Tên cộtKiểu dữ liệuRàng buộcMô tả
user_idBIGINT UNSIGNEDFK→ users.id
property_idBIGINT UNSIGNEDFK UK→ properties.id
M5 Đánh giá & Nội dung (3 bảng)

reviews — Nhận xét của khách

Tên cộtKiểu dữ liệuRàng buộcMô tả
booking_idBIGINT UNSIGNEDFK UK1 booking chỉ đánh giá 1 lần
overall_ratingDECIMAL(3,1)NOT NULLThang điểm 1-10
moderation_statusENUMNOT NULL'pending', 'approved', 'rejected'

review_media — Ảnh/Video review đính kèm

Tên cộtKiểu dữ liệuRàng buộcMô tả
review_idBIGINT UNSIGNEDFK→ reviews.id
urlVARCHAR(500)NOT NULLẢnh do khách upload

review_responses — Phản hồi của Host

Tên cộtKiểu dữ liệuRàng buộcMô tả
review_idBIGINT UNSIGNEDFK UK→ reviews.id (1 review có 1 response)
contentTEXTNOT NULLNội dung trả lời
M6 Tài chính & Giải ngân (6 bảng)

payout_wallets — Ví Đối tác

Tên cộtKiểu dữ liệuRàng buộcMô tả
partner_idBIGINT UNSIGNEDFK UK→ partner_profiles.id
available_balanceDECIMAL(15,2)NOT NULLSố dư có thể rút
pending_balanceDECIMAL(15,2)NOT NULLTiền chưa đối soát / Tạm giữ

wallet_transactions — Biến động số dư

Tên cộtKiểu dữ liệuRàng buộcMô tả
wallet_idBIGINT UNSIGNEDFK→ payout_wallets.id
typeENUMNOT NULL'credit' (+ tiền) hoặc 'debit' (- tiền)

payout_requests — Yêu cầu rút tiền

Tên cộtKiểu dữ liệuRàng buộcMô tả
wallet_idBIGINT UNSIGNEDFK→ payout_wallets.id
amountDECIMAL(12,2)NOT NULLSố tiền muốn rút
statusENUMNOT NULL'pending', 'approved', 'processing', 'completed'

commission_configs — Cấu hình hoa hồng hệ thống

Tên cộtKiểu dữ liệuRàng buộcMô tả
scopeENUMNOT NULL'global', 'partner', 'property_type'...
commission_rateDECIMAL(5,2)NOT NULL% tỷ lệ chiết khấu Nền tảng lấy từ Host

platform_fee_configs — Cấu hình phí dịch vụ

Tên cộtKiểu dữ liệuRàng buộcMô tả
fee_typeENUMNOT NULL'percent', 'fixed'
fee_valueDECIMAL(8,4)NOT NULLMức phí thu trên mỗi đơn

loyalty_point_ledger — Sổ cái Loyalty

Tên cộtKiểu dữ liệuRàng buộcMô tả
user_idBIGINT UNSIGNEDFK→ users.id (Customer)
typeENUMNOT NULL'earn', 'redeem', 'expire', 'adjust'
pointsINTNOT NULLSố điểm (+ / -)
M7 CSKH & Tranh chấp (3 bảng)

support_tickets — Yêu cầu hỗ trợ

Tên cộtKiểu dữ liệuRàng buộcMô tả
ticket_codeVARCHAR(30)UKMã ticket
statusENUMNOT NULL'open', 'in_progress', 'resolved'...
assigned_toBIGINT UNSIGNEDFK→ users.id (Staff CS)

ticket_messages — Lịch sử trao đổi trong Ticket

Tên cộtKiểu dữ liệuRàng buộcMô tả
ticket_idBIGINT UNSIGNEDFK→ support_tickets.id
is_internalTINYINT(1)NOT NULLTin nhắn nội bộ giữa Admin/Staff

disputes — Tranh chấp giao dịch / booking

Tên cộtKiểu dữ liệuRàng buộcMô tả
booking_idBIGINT UNSIGNEDFK→ bookings.id
dispute_typeENUMNOT NULL'cancellation', 'refund', 'property_issue'
resolutionTEXTNULLPhán quyết cuối cùng của Admin
M8 Admin, RBAC & Hệ thống (10 bảng)

roles & permissions & role_permissions — Hệ thống Phân quyền

BảngMô tả vai trò
rolesVai trò hệ thống (Admin, Finance, CS Agent...) - slug là Unique.
permissionsQuyền thao tác cụ thể. Ví dụ: `action: booking:cancel`, `resource: booking`
role_permissionsBảng nối N-N giữa Vai trò và Quyền hạn

user_roles — Gán quyền cho User (Data Scope)

Tên cộtKiểu dữ liệuRàng buộcMô tả
user_idBIGINT UNSIGNEDFK→ users.id
role_idBIGINT UNSIGNEDFK→ roles.id
scope_typeVARCHAR(50)NULLPhạm vi: 'city', 'region'
scope_valueVARCHAR(100)NULLVD: 'Hanoi'. Cấp quyền admin nhưng chỉ thao tác ở HN.

staff_profiles — Hồ sơ Nhân viên Nội bộ

Tên cộtKiểu dữ liệuRàng buộcMô tả
user_idBIGINT UNSIGNEDFK UK→ users.id
employee_codeVARCHAR(50)UKMã nhân sự (NWH-001)
departmentENUMNOT NULLPhòng ban làm việc
manager_idBIGINT UNSIGNEDFK NULLQuản lý trực tiếp (Self-referencing)

audit_logs — Nhật ký thao tác (Audit Trail)

Tên cộtKiểu dữ liệuRàng buộcMô tả
actor_idBIGINT UNSIGNEDNOT NULLID người thực hiện (Không để FK tránh lỗi Cascade Delete)
entity_type / entity_idVARCHAR / BIGINTNOT NULLBản ghi bị tác động (polymorphic)
old_values / new_valuesJSONNULLSnapshot data trước và sau thay đổi

system_configs — Cấu hình Key-Value

Tên cộtKiểu dữ liệuRàng buộcMô tả
config_keyVARCHAR(200)UKKey biến môi trường linh hoạt
config_valueTEXTNOT NULLValue. Cùng với `value_type` để ép kiểu

notifications — Thông báo gửi tới User

Tên cộtKiểu dữ liệuRàng buộcMô tả
user_idBIGINT UNSIGNEDFK→ users.id
channelENUMNOT NULL'in_app', 'email', 'push'
dataJSONNULLPayload xử lý deep link app

risk_rules & risk_assessments — Anti-Fraud System

BảngMô tả nhiệm vụ
risk_rulesCác luật cảnh báo. VD: IP Đen (Điểm 50), Thẻ thanh toán xịt nhiều (Điểm 30)...
risk_assessmentsĐánh giá rủi ro cho mỗi `payment_id`. Chứa mảng JSON các `triggered_rules` và điểm tổng. Nếu điểm cao -> Trạng thái 'flagged' cần Review tay.

DDL Script – MySQL InnoDB (Module Optimized)

MODULE 1: AUTH & USERS (6 Bảng)M1
-- Bật tắt FK Check để an toàn khi Init DB
SET FOREIGN_KEY_CHECKS = 0;

CREATE TABLE users (
  id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
  uuid CHAR(36) NOT NULL,
  email VARCHAR(255) NOT NULL,
  phone VARCHAR(20) DEFAULT NULL,
  password_hash VARCHAR(255) DEFAULT NULL,
  full_name VARCHAR(255) NOT NULL,
  avatar_url VARCHAR(500) DEFAULT NULL,
  user_type ENUM('customer','partner','staff') NOT NULL,
  status ENUM('active','suspended','pending') NOT NULL DEFAULT 'pending',
  email_verified_at DATETIME DEFAULT NULL,
  last_login_at DATETIME DEFAULT NULL,
  preferred_language CHAR(5) NOT NULL DEFAULT 'vi',
  created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
  updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
  PRIMARY KEY (id), UNIQUE KEY uq_users_uuid (uuid), UNIQUE KEY uq_users_email (email),
  UNIQUE KEY uq_users_phone (phone), KEY idx_users_type_status (user_type, status)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;

CREATE TABLE social_accounts (
  id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
  user_id BIGINT UNSIGNED NOT NULL,
  provider ENUM('google','facebook','apple','zalo') NOT NULL,
  provider_id VARCHAR(255) NOT NULL,
  access_token TEXT DEFAULT NULL,
  refresh_token TEXT DEFAULT NULL,
  token_expires_at DATETIME DEFAULT NULL,
  created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
  updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
  PRIMARY KEY (id), UNIQUE KEY uq_social_provider (provider, provider_id),
  CONSTRAINT fk_social_user FOREIGN KEY (user_id) REFERENCES users (id) ON DELETE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

CREATE TABLE partner_profiles (
  id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
  user_id BIGINT UNSIGNED NOT NULL,
  business_name VARCHAR(255) NOT NULL,
  business_type ENUM('individual','company') NOT NULL,
  tax_code VARCHAR(50) DEFAULT NULL,
  id_card_number VARCHAR(50) DEFAULT NULL,
  contract_url VARCHAR(500) DEFAULT NULL,
  kyc_status ENUM('pending','approved','rejected') NOT NULL DEFAULT 'pending',
  kyc_reviewed_by BIGINT UNSIGNED DEFAULT NULL,
  kyc_reviewed_at DATETIME DEFAULT NULL,
  bank_account_name VARCHAR(255) DEFAULT NULL,
  bank_account_number VARCHAR(100) DEFAULT NULL,
  bank_name VARCHAR(100) DEFAULT NULL,
  commission_tier VARCHAR(50) NOT NULL DEFAULT 'standard',
  created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
  updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
  PRIMARY KEY (id), UNIQUE KEY uq_partner_user (user_id),
  CONSTRAINT fk_partner_user FOREIGN KEY (user_id) REFERENCES users (id) ON DELETE CASCADE,
  CONSTRAINT fk_partner_reviewer FOREIGN KEY (kyc_reviewed_by) REFERENCES users (id) ON DELETE SET NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

CREATE TABLE customer_profiles (
  id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
  user_id BIGINT UNSIGNED NOT NULL,
  date_of_birth DATE DEFAULT NULL,
  gender ENUM('male','female','other') DEFAULT NULL,
  nationality CHAR(2) DEFAULT NULL,
  id_card_number VARCHAR(50) DEFAULT NULL,
  loyalty_tier ENUM('member','silver','gold','platinum') NOT NULL DEFAULT 'member',
  loyalty_points_balance INT UNSIGNED NOT NULL DEFAULT 0,
  total_bookings INT UNSIGNED NOT NULL DEFAULT 0,
  created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
  updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
  PRIMARY KEY (id), UNIQUE KEY uq_customer_user (user_id),
  CONSTRAINT fk_customer_user FOREIGN KEY (user_id) REFERENCES users (id) ON DELETE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

CREATE TABLE otp_tokens (
  id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
  user_id BIGINT UNSIGNED DEFAULT NULL,
  identifier VARCHAR(255) NOT NULL,
  identifier_type ENUM('email','phone') NOT NULL,
  purpose ENUM('register','login','reset_password','verify_phone','verify_email') NOT NULL,
  token_hash VARCHAR(255) NOT NULL,
  expires_at DATETIME NOT NULL,
  used_at DATETIME DEFAULT NULL,
  attempts TINYINT UNSIGNED NOT NULL DEFAULT 0,
  ip_address VARCHAR(45) DEFAULT NULL,
  created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
  PRIMARY KEY (id), KEY idx_otp_lookup (identifier, purpose, expires_at),
  CONSTRAINT fk_otp_user FOREIGN KEY (user_id) REFERENCES users (id) ON DELETE SET NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

CREATE TABLE user_sessions (
  id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
  user_id BIGINT UNSIGNED NOT NULL,
  token_hash VARCHAR(255) NOT NULL,
  device_name VARCHAR(255) DEFAULT NULL,
  device_type ENUM('web','ios','android','other') NOT NULL DEFAULT 'web',
  ip_address VARCHAR(45) DEFAULT NULL,
  user_agent TEXT DEFAULT NULL,
  last_active_at DATETIME DEFAULT NULL,
  expires_at DATETIME NOT NULL,
  revoked_at DATETIME DEFAULT NULL,
  created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
  PRIMARY KEY (id), UNIQUE KEY uq_session_token (token_hash),
  KEY idx_session_active (user_id, revoked_at, expires_at),
  CONSTRAINT fk_session_user FOREIGN KEY (user_id) REFERENCES users (id) ON DELETE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
MODULE 2: PROPERTIES & ROOMS (8 Bảng)M2
CREATE TABLE properties (
  id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
  partner_id BIGINT UNSIGNED NOT NULL,
  slug VARCHAR(300) NOT NULL,
  name VARCHAR(500) NOT NULL,
  property_type ENUM('hotel','homestay','resort','apartment','villa','hostel') NOT NULL,
  description TEXT DEFAULT NULL,
  address TEXT NOT NULL,
  city VARCHAR(100) NOT NULL,
  district VARCHAR(100) DEFAULT NULL,
  country_code CHAR(2) NOT NULL DEFAULT 'VN',
  latitude DECIMAL(10,8) NOT NULL,
  longitude DECIMAL(11,8) NOT NULL,
  star_rating TINYINT UNSIGNED DEFAULT NULL,
  avg_rating DECIMAL(3,2) NOT NULL DEFAULT 0.00,
  total_reviews INT UNSIGNED NOT NULL DEFAULT 0,
  check_in_time TIME NOT NULL DEFAULT '14:00:00',
  check_out_time TIME NOT NULL DEFAULT '12:00:00',
  status ENUM('draft','pending_review','active','suspended') NOT NULL DEFAULT 'draft',
  reviewed_by BIGINT UNSIGNED DEFAULT NULL,
  reviewed_at DATETIME DEFAULT NULL,
  created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
  updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
  PRIMARY KEY (id), UNIQUE KEY uq_property_slug (slug),
  KEY idx_prop_partner (partner_id), KEY idx_prop_search (city, status),
  CONSTRAINT fk_property_partner FOREIGN KEY (partner_id) REFERENCES partner_profiles (id),
  CONSTRAINT fk_property_reviewer FOREIGN KEY (reviewed_by) REFERENCES users (id) ON DELETE SET NULL,
  CONSTRAINT chk_star_rating CHECK (star_rating BETWEEN 1 AND 5)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

CREATE TABLE room_types (
  id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
  property_id BIGINT UNSIGNED NOT NULL,
  name VARCHAR(255) NOT NULL,
  description TEXT DEFAULT NULL,
  area_sqm DECIMAL(6,2) DEFAULT NULL,
  bed_configuration VARCHAR(200) DEFAULT NULL,
  max_occupancy TINYINT UNSIGNED NOT NULL DEFAULT 2,
  view_type VARCHAR(100) DEFAULT NULL,
  total_rooms SMALLINT UNSIGNED NOT NULL DEFAULT 1,
  base_price DECIMAL(12,2) NOT NULL,
  is_active TINYINT(1) NOT NULL DEFAULT 1,
  created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
  updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
  PRIMARY KEY (id),
  CONSTRAINT fk_rt_property FOREIGN KEY (property_id) REFERENCES properties (id) ON DELETE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

CREATE TABLE rooms (
  id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
  room_type_id BIGINT UNSIGNED NOT NULL,
  property_id BIGINT UNSIGNED NOT NULL,
  room_number VARCHAR(20) NOT NULL,
  floor SMALLINT DEFAULT NULL,
  status ENUM('available','occupied','blocked','maintenance') NOT NULL DEFAULT 'available',
  notes TEXT DEFAULT NULL,
  created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
  updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
  PRIMARY KEY (id), UNIQUE KEY uq_room_property (property_id, room_number),
  CONSTRAINT fk_room_type FOREIGN KEY (room_type_id) REFERENCES room_types (id) ON DELETE CASCADE,
  CONSTRAINT fk_room_property FOREIGN KEY (property_id) REFERENCES properties (id) ON DELETE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

CREATE TABLE amenities (
  id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
  name VARCHAR(100) NOT NULL,
  category VARCHAR(50) NOT NULL,
  icon_code VARCHAR(50) DEFAULT NULL,
  is_active TINYINT(1) NOT NULL DEFAULT 1,
  created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
  updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
  PRIMARY KEY (id), UNIQUE KEY uq_amenity_name (name)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

CREATE TABLE property_amenities (
  property_id BIGINT UNSIGNED NOT NULL,
  amenity_id BIGINT UNSIGNED NOT NULL,
  PRIMARY KEY (property_id, amenity_id),
  CONSTRAINT fk_pa_prop FOREIGN KEY (property_id) REFERENCES properties (id) ON DELETE CASCADE,
  CONSTRAINT fk_pa_amenity FOREIGN KEY (amenity_id) REFERENCES amenities (id) ON DELETE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

CREATE TABLE room_type_amenities (
  room_type_id BIGINT UNSIGNED NOT NULL,
  amenity_id BIGINT UNSIGNED NOT NULL,
  PRIMARY KEY (room_type_id, amenity_id),
  CONSTRAINT fk_rta_rt FOREIGN KEY (room_type_id) REFERENCES room_types (id) ON DELETE CASCADE,
  CONSTRAINT fk_rta_amenity FOREIGN KEY (amenity_id) REFERENCES amenities (id) ON DELETE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

CREATE TABLE property_media (
  id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
  property_id BIGINT UNSIGNED NOT NULL,
  room_type_id BIGINT UNSIGNED DEFAULT NULL,
  media_type ENUM('image','video','virtual_tour') NOT NULL DEFAULT 'image',
  category ENUM('exterior','interior','room','bathroom','dining','pool','amenity','other') NOT NULL DEFAULT 'other',
  url VARCHAR(500) NOT NULL,
  thumbnail_url VARCHAR(500) DEFAULT NULL,
  caption VARCHAR(500) DEFAULT NULL,
  is_cover TINYINT(1) NOT NULL DEFAULT 0,
  sort_order SMALLINT UNSIGNED NOT NULL DEFAULT 0,
  uploaded_by BIGINT UNSIGNED NOT NULL,
  created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
  PRIMARY KEY (id),
  KEY idx_pmedia_prop (property_id, category, sort_order),
  CONSTRAINT fk_pmedia_prop FOREIGN KEY (property_id) REFERENCES properties (id) ON DELETE CASCADE,
  CONSTRAINT fk_pmedia_rt FOREIGN KEY (room_type_id) REFERENCES room_types (id) ON DELETE CASCADE,
  CONSTRAINT fk_pmedia_up FOREIGN KEY (uploaded_by) REFERENCES users (id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

CREATE TABLE property_policies (
  id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
  property_id BIGINT UNSIGNED NOT NULL,
  cancellation_type ENUM('free','flexible','moderate','strict','non_refundable') NOT NULL DEFAULT 'flexible',
  free_cancel_hours SMALLINT UNSIGNED DEFAULT NULL,
  cancel_penalty_percent DECIMAL(5,2) NOT NULL DEFAULT 0.00,
  min_stay_nights TINYINT UNSIGNED NOT NULL DEFAULT 1,
  max_stay_nights TINYINT UNSIGNED DEFAULT NULL,
  check_in_from TIME NOT NULL DEFAULT '14:00:00',
  check_in_until TIME NOT NULL DEFAULT '22:00:00',
  check_out_until TIME NOT NULL DEFAULT '12:00:00',
  pets_allowed TINYINT(1) NOT NULL DEFAULT 0,
  smoking_allowed TINYINT(1) NOT NULL DEFAULT 0,
  children_allowed TINYINT(1) NOT NULL DEFAULT 1,
  extra_bed_available TINYINT(1) NOT NULL DEFAULT 0,
  extra_bed_charge DECIMAL(12,2) DEFAULT NULL,
  custom_rules TEXT DEFAULT NULL,
  created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
  updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
  PRIMARY KEY (id), UNIQUE KEY uq_policy_prop (property_id),
  CONSTRAINT fk_policy_prop FOREIGN KEY (property_id) REFERENCES properties (id) ON DELETE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
MODULE 3: PRICING & PROMOTIONS (5 Bảng)M3
CREATE TABLE rate_plans (
  id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
  room_type_id BIGINT UNSIGNED NOT NULL,
  name VARCHAR(200) NOT NULL,
  meal_plan ENUM('room_only','breakfast','half_board','full_board','all_inclusive') NOT NULL DEFAULT 'room_only',
  refundable TINYINT(1) NOT NULL DEFAULT 1,
  base_price DECIMAL(12,2) NOT NULL,
  is_active TINYINT(1) NOT NULL DEFAULT 1,
  created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
  updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
  PRIMARY KEY (id),
  CONSTRAINT fk_rp_rt FOREIGN KEY (room_type_id) REFERENCES room_types (id) ON DELETE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

CREATE TABLE daily_rates (
  id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
  rate_plan_id BIGINT UNSIGNED NOT NULL,
  date DATE NOT NULL,
  price DECIMAL(12,2) NOT NULL,
  available_qty SMALLINT UNSIGNED NOT NULL,
  min_stay TINYINT UNSIGNED NOT NULL DEFAULT 1,
  created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
  updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
  PRIMARY KEY (id), UNIQUE KEY uq_daily_rate (rate_plan_id, date),
  CONSTRAINT fk_dr_rp FOREIGN KEY (rate_plan_id) REFERENCES rate_plans (id) ON DELETE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

CREATE TABLE promotions (
  id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
  partner_id BIGINT UNSIGNED DEFAULT NULL,
  name VARCHAR(255) NOT NULL,
  promo_type ENUM('early_bird','last_minute','long_stay','flash_sale','loyalty','custom') NOT NULL,
  discount_type ENUM('percent','fixed') NOT NULL,
  discount_value DECIMAL(10,2) NOT NULL,
  max_discount DECIMAL(12,2) DEFAULT NULL,
  min_order_amount DECIMAL(12,2) NOT NULL DEFAULT 0.00,
  start_date DATE NOT NULL,
  end_date DATE NOT NULL,
  max_uses INT UNSIGNED DEFAULT NULL,
  total_used INT UNSIGNED NOT NULL DEFAULT 0,
  is_active TINYINT(1) NOT NULL DEFAULT 1,
  created_by BIGINT UNSIGNED NOT NULL,
  created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
  updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
  PRIMARY KEY (id),
  CONSTRAINT fk_promo_partner FOREIGN KEY (partner_id) REFERENCES partner_profiles (id) ON DELETE SET NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

CREATE TABLE vouchers (
  id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
  promotion_id BIGINT UNSIGNED NOT NULL,
  code VARCHAR(50) NOT NULL,
  max_uses_per_user TINYINT UNSIGNED NOT NULL DEFAULT 1,
  total_used INT UNSIGNED NOT NULL DEFAULT 0,
  is_active TINYINT(1) NOT NULL DEFAULT 1,
  created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
  updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
  PRIMARY KEY (id), UNIQUE KEY uq_voucher_code (code),
  CONSTRAINT fk_voucher_promo FOREIGN KEY (promotion_id) REFERENCES promotions (id) ON DELETE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

CREATE TABLE voucher_usages (
  id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
  voucher_id BIGINT UNSIGNED NOT NULL,
  user_id BIGINT UNSIGNED NOT NULL,
  booking_id BIGINT UNSIGNED NOT NULL,
  discount_applied DECIMAL(12,2) NOT NULL,
  used_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
  created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
  PRIMARY KEY (id), UNIQUE KEY uq_vu_voucher_booking (voucher_id, booking_id),
  KEY idx_vu_user_voucher (user_id, voucher_id),
  CONSTRAINT fk_vu_voucher FOREIGN KEY (voucher_id) REFERENCES vouchers (id) ON DELETE CASCADE,
  CONSTRAINT fk_vu_user FOREIGN KEY (user_id) REFERENCES users (id) ON DELETE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
MODULE 4: BOOKINGS & PAYMENTS (7 Bảng)M4
CREATE TABLE bookings (
  id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
  booking_code VARCHAR(30) NOT NULL,
  customer_id BIGINT UNSIGNED NOT NULL,
  property_id BIGINT UNSIGNED NOT NULL,
  check_in_date DATE NOT NULL,
  check_out_date DATE NOT NULL,
  num_nights TINYINT UNSIGNED NOT NULL,
  num_adults TINYINT UNSIGNED NOT NULL DEFAULT 1,
  num_children TINYINT UNSIGNED NOT NULL DEFAULT 0,
  subtotal_amount DECIMAL(12,2) NOT NULL,
  discount_amount DECIMAL(12,2) NOT NULL DEFAULT 0.00,
  tax_amount DECIMAL(12,2) NOT NULL DEFAULT 0.00,
  total_amount DECIMAL(12,2) NOT NULL,
  platform_fee_amount DECIMAL(12,2) NOT NULL DEFAULT 0.00,
  partner_payout_amount DECIMAL(12,2) NOT NULL DEFAULT 0.00,
  currency CHAR(3) NOT NULL DEFAULT 'VND',
  voucher_id BIGINT UNSIGNED DEFAULT NULL,
  loyalty_points_used INT UNSIGNED NOT NULL DEFAULT 0,
  status ENUM('pending','confirmed','checked_in','checked_out','cancelled','no_show') NOT NULL DEFAULT 'pending',
  payment_status ENUM('unpaid','partial','paid','refunded') NOT NULL DEFAULT 'unpaid',
  source_channel ENUM('web','mobile','ota','direct') NOT NULL DEFAULT 'web',
  special_requests TEXT DEFAULT NULL,
  cancellation_reason TEXT DEFAULT NULL,
  cancelled_by BIGINT UNSIGNED DEFAULT NULL,
  cancelled_at DATETIME DEFAULT NULL,
  created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
  updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
  PRIMARY KEY (id), UNIQUE KEY uq_booking_code (booking_code),
  KEY idx_booking_customer (customer_id), KEY idx_booking_property (property_id),
  CONSTRAINT fk_booking_cust FOREIGN KEY (customer_id) REFERENCES users (id),
  CONSTRAINT fk_booking_prop FOREIGN KEY (property_id) REFERENCES properties (id),
  CONSTRAINT fk_booking_voucher FOREIGN KEY (voucher_id) REFERENCES vouchers (id) ON DELETE SET NULL,
  CONSTRAINT fk_booking_canceler FOREIGN KEY (cancelled_by) REFERENCES users (id) ON DELETE SET NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

CREATE TABLE booking_rooms (
  id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
  booking_id BIGINT UNSIGNED NOT NULL,
  room_id BIGINT UNSIGNED NOT NULL,
  rate_plan_id BIGINT UNSIGNED NOT NULL,
  room_price DECIMAL(12,2) NOT NULL,
  created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
  updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
  PRIMARY KEY (id), UNIQUE KEY uq_br_booking_room (booking_id, room_id),
  CONSTRAINT fk_br_booking FOREIGN KEY (booking_id) REFERENCES bookings (id) ON DELETE CASCADE,
  CONSTRAINT fk_br_room FOREIGN KEY (room_id) REFERENCES rooms (id),
  CONSTRAINT fk_br_rp FOREIGN KEY (rate_plan_id) REFERENCES rate_plans (id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

CREATE TABLE booking_guests (
  id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
  booking_id BIGINT UNSIGNED NOT NULL,
  full_name VARCHAR(255) NOT NULL,
  id_card_number VARCHAR(50) DEFAULT NULL,
  date_of_birth DATE DEFAULT NULL,
  nationality CHAR(2) DEFAULT NULL,
  is_primary TINYINT(1) NOT NULL DEFAULT 0,
  created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
  updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
  PRIMARY KEY (id), KEY idx_bg_booking (booking_id),
  CONSTRAINT fk_bg_booking FOREIGN KEY (booking_id) REFERENCES bookings (id) ON DELETE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

CREATE TABLE booking_fees (
  id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
  booking_id BIGINT UNSIGNED NOT NULL,
  fee_config_id BIGINT UNSIGNED DEFAULT NULL,
  fee_name VARCHAR(200) NOT NULL,
  fee_type ENUM('platform','vat','service','other') NOT NULL,
  rate_snapshot DECIMAL(8,4) DEFAULT NULL,
  base_amount DECIMAL(12,2) NOT NULL,
  fee_amount DECIMAL(12,2) NOT NULL,
  charged_to ENUM('customer','partner') NOT NULL,
  created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
  PRIMARY KEY (id),
  CONSTRAINT fk_bf_booking FOREIGN KEY (booking_id) REFERENCES bookings (id) ON DELETE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

CREATE TABLE payments (
  id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
  booking_id BIGINT UNSIGNED NOT NULL,
  transaction_ref VARCHAR(100) NOT NULL,
  amount DECIMAL(12,2) NOT NULL,
  currency CHAR(3) NOT NULL DEFAULT 'VND',
  payment_method ENUM('credit_card','debit_card','ewallet','bank_transfer','pay_later','loyalty_cash') NOT NULL,
  gateway VARCHAR(50) NOT NULL,
  gateway_response JSON DEFAULT NULL,
  status ENUM('pending','success','failed','cancelled') NOT NULL DEFAULT 'pending',
  paid_at DATETIME DEFAULT NULL,
  created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
  updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
  PRIMARY KEY (id), UNIQUE KEY uq_pay_ref (transaction_ref),
  CONSTRAINT fk_pay_booking FOREIGN KEY (booking_id) REFERENCES bookings (id) ON DELETE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

CREATE TABLE refunds (
  id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
  payment_id BIGINT UNSIGNED NOT NULL,
  booking_id BIGINT UNSIGNED NOT NULL,
  amount DECIMAL(12,2) NOT NULL,
  reason TEXT NOT NULL,
  status ENUM('pending','processing','completed','failed') NOT NULL DEFAULT 'pending',
  processed_by BIGINT UNSIGNED DEFAULT NULL,
  processed_at DATETIME DEFAULT NULL,
  gateway_ref VARCHAR(100) DEFAULT NULL,
  created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
  updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
  PRIMARY KEY (id),
  CONSTRAINT fk_ref_pay FOREIGN KEY (payment_id) REFERENCES payments (id),
  CONSTRAINT fk_ref_booking FOREIGN KEY (booking_id) REFERENCES bookings (id),
  CONSTRAINT fk_ref_processor FOREIGN KEY (processed_by) REFERENCES users (id) ON DELETE SET NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

CREATE TABLE wishlists (
  id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
  user_id BIGINT UNSIGNED NOT NULL,
  property_id BIGINT UNSIGNED NOT NULL,
  note VARCHAR(500) DEFAULT NULL,
  created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
  PRIMARY KEY (id), UNIQUE KEY uq_wl_user_prop (user_id, property_id),
  CONSTRAINT fk_wl_user FOREIGN KEY (user_id) REFERENCES users (id) ON DELETE CASCADE,
  CONSTRAINT fk_wl_prop FOREIGN KEY (property_id) REFERENCES properties (id) ON DELETE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
MODULE 5: REVIEWS (3 Bảng)M5
CREATE TABLE reviews (
  id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
  booking_id BIGINT UNSIGNED NOT NULL,
  property_id BIGINT UNSIGNED NOT NULL,
  customer_id BIGINT UNSIGNED NOT NULL,
  overall_rating DECIMAL(3,1) NOT NULL,
  cleanliness_rating DECIMAL(3,1) DEFAULT NULL,
  service_rating DECIMAL(3,1) DEFAULT NULL,
  location_rating DECIMAL(3,1) DEFAULT NULL,
  value_rating DECIMAL(3,1) DEFAULT NULL,
  title VARCHAR(255) DEFAULT NULL,
  content TEXT DEFAULT NULL,
  moderation_status ENUM('pending','approved','rejected') NOT NULL DEFAULT 'pending',
  moderated_by BIGINT UNSIGNED DEFAULT NULL,
  moderated_at DATETIME DEFAULT NULL,
  created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
  updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
  PRIMARY KEY (id), UNIQUE KEY uq_rev_booking (booking_id),
  CONSTRAINT fk_rev_booking FOREIGN KEY (booking_id) REFERENCES bookings (id),
  CONSTRAINT fk_rev_prop FOREIGN KEY (property_id) REFERENCES properties (id),
  CONSTRAINT fk_rev_cust FOREIGN KEY (customer_id) REFERENCES users (id),
  CONSTRAINT fk_rev_mod FOREIGN KEY (moderated_by) REFERENCES users (id) ON DELETE SET NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

CREATE TABLE review_media (
  id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
  review_id BIGINT UNSIGNED NOT NULL,
  media_type ENUM('image','video') NOT NULL DEFAULT 'image',
  url VARCHAR(500) NOT NULL,
  thumbnail_url VARCHAR(500) DEFAULT NULL,
  sort_order TINYINT UNSIGNED NOT NULL DEFAULT 0,
  created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
  PRIMARY KEY (id),
  CONSTRAINT fk_rm_review FOREIGN KEY (review_id) REFERENCES reviews (id) ON DELETE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

CREATE TABLE review_responses (
  id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
  review_id BIGINT UNSIGNED NOT NULL,
  responder_id BIGINT UNSIGNED NOT NULL,
  content TEXT NOT NULL,
  created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
  updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
  PRIMARY KEY (id), UNIQUE KEY uq_rr_review (review_id),
  CONSTRAINT fk_rr_rev FOREIGN KEY (review_id) REFERENCES reviews (id) ON DELETE CASCADE,
  CONSTRAINT fk_rr_resp FOREIGN KEY (responder_id) REFERENCES users (id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
MODULE 6: FINANCE & PAYOUTS (6 Bảng)M6
CREATE TABLE payout_wallets (
  id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
  partner_id BIGINT UNSIGNED NOT NULL,
  available_balance DECIMAL(15,2) NOT NULL DEFAULT 0.00,
  pending_balance DECIMAL(15,2) NOT NULL DEFAULT 0.00,
  total_earned DECIMAL(15,2) NOT NULL DEFAULT 0.00,
  total_withdrawn DECIMAL(15,2) NOT NULL DEFAULT 0.00,
  currency CHAR(3) NOT NULL DEFAULT 'VND',
  created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
  updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
  PRIMARY KEY (id), UNIQUE KEY uq_wal_partner (partner_id),
  CONSTRAINT fk_wal_partner FOREIGN KEY (partner_id) REFERENCES partner_profiles (id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

CREATE TABLE wallet_transactions (
  id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
  wallet_id BIGINT UNSIGNED NOT NULL,
  type ENUM('credit','debit') NOT NULL,
  amount DECIMAL(12,2) NOT NULL,
  balance_after DECIMAL(15,2) NOT NULL,
  description VARCHAR(500) NOT NULL,
  ref_type VARCHAR(50) DEFAULT NULL,
  ref_id BIGINT UNSIGNED DEFAULT NULL,
  created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
  PRIMARY KEY (id),
  CONSTRAINT fk_wt_wal FOREIGN KEY (wallet_id) REFERENCES payout_wallets (id) ON DELETE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

CREATE TABLE payout_requests (
  id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
  wallet_id BIGINT UNSIGNED NOT NULL,
  amount DECIMAL(12,2) NOT NULL,
  status ENUM('pending','approved','rejected','processing','completed') NOT NULL DEFAULT 'pending',
  reviewed_by BIGINT UNSIGNED DEFAULT NULL,
  reviewed_at DATETIME DEFAULT NULL,
  note TEXT DEFAULT NULL,
  created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
  updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
  PRIMARY KEY (id),
  CONSTRAINT fk_pr_wal FOREIGN KEY (wallet_id) REFERENCES payout_wallets (id),
  CONSTRAINT fk_pr_rev FOREIGN KEY (reviewed_by) REFERENCES users (id) ON DELETE SET NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

CREATE TABLE commission_configs (
  id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
  name VARCHAR(200) NOT NULL,
  scope ENUM('global','partner_tier','partner','property_type','country') NOT NULL DEFAULT 'global',
  scope_value VARCHAR(100) DEFAULT NULL,
  commission_rate DECIMAL(5,2) NOT NULL,
  min_commission DECIMAL(12,2) DEFAULT NULL,
  max_commission DECIMAL(12,2) DEFAULT NULL,
  effective_from DATE NOT NULL,
  effective_to DATE DEFAULT NULL,
  is_active TINYINT(1) NOT NULL DEFAULT 1,
  created_by BIGINT UNSIGNED NOT NULL,
  notes TEXT DEFAULT NULL,
  created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
  updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
  PRIMARY KEY (id),
  CONSTRAINT fk_cc_creat FOREIGN KEY (created_by) REFERENCES users (id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

CREATE TABLE platform_fee_configs (
  id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
  name VARCHAR(200) NOT NULL,
  fee_type ENUM('percent','fixed') NOT NULL,
  fee_value DECIMAL(8,4) NOT NULL,
  applies_to ENUM('all','property_type','partner_tier','country') NOT NULL DEFAULT 'all',
  applies_value VARCHAR(100) DEFAULT NULL,
  min_fee DECIMAL(12,2) DEFAULT NULL,
  max_fee DECIMAL(12,2) DEFAULT NULL,
  effective_from DATE NOT NULL,
  effective_to DATE DEFAULT NULL,
  is_active TINYINT(1) NOT NULL DEFAULT 1,
  created_by BIGINT UNSIGNED NOT NULL,
  created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
  updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
  PRIMARY KEY (id),
  CONSTRAINT fk_pfc_creat FOREIGN KEY (created_by) REFERENCES users (id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

CREATE TABLE loyalty_point_ledger (
  id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
  user_id BIGINT UNSIGNED NOT NULL,
  type ENUM('earn','redeem','expire','adjust') NOT NULL,
  points INT NOT NULL,
  balance_after INT UNSIGNED NOT NULL,
  ref_type VARCHAR(50) DEFAULT NULL,
  ref_id BIGINT UNSIGNED DEFAULT NULL,
  description VARCHAR(500) DEFAULT NULL,
  expires_at DATE DEFAULT NULL,
  created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
  PRIMARY KEY (id),
  CONSTRAINT fk_lpl_user FOREIGN KEY (user_id) REFERENCES users (id) ON DELETE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
MODULE 7: SUPPORT & DISPUTES (3 Bảng)M7
CREATE TABLE support_tickets (
  id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
  ticket_code VARCHAR(30) NOT NULL,
  requester_id BIGINT UNSIGNED NOT NULL,
  booking_id BIGINT UNSIGNED DEFAULT NULL,
  category ENUM('booking_issue','payment','property','account','other') NOT NULL,
  subject VARCHAR(500) NOT NULL,
  status ENUM('open','in_progress','pending_customer','resolved','closed') NOT NULL DEFAULT 'open',
  priority ENUM('low','medium','high','urgent') NOT NULL DEFAULT 'medium',
  assigned_to BIGINT UNSIGNED DEFAULT NULL,
  resolved_at DATETIME DEFAULT NULL,
  created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
  updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
  PRIMARY KEY (id), UNIQUE KEY uq_ticket_code (ticket_code),
  CONSTRAINT fk_tick_req FOREIGN KEY (requester_id) REFERENCES users (id),
  CONSTRAINT fk_tick_book FOREIGN KEY (booking_id) REFERENCES bookings (id) ON DELETE SET NULL,
  CONSTRAINT fk_tick_agent FOREIGN KEY (assigned_to) REFERENCES users (id) ON DELETE SET NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

CREATE TABLE ticket_messages (
  id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
  ticket_id BIGINT UNSIGNED NOT NULL,
  sender_id BIGINT UNSIGNED NOT NULL,
  content TEXT NOT NULL,
  is_internal TINYINT(1) NOT NULL DEFAULT 0,
  created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
  updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
  PRIMARY KEY (id),
  CONSTRAINT fk_tm_tick FOREIGN KEY (ticket_id) REFERENCES support_tickets (id) ON DELETE CASCADE,
  CONSTRAINT fk_tm_sender FOREIGN KEY (sender_id) REFERENCES users (id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

CREATE TABLE disputes (
  id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
  booking_id BIGINT UNSIGNED NOT NULL,
  raised_by BIGINT UNSIGNED NOT NULL,
  dispute_type ENUM('cancellation','refund','property_issue','no_show','other') NOT NULL,
  description TEXT NOT NULL,
  status ENUM('open','under_review','resolved','closed') NOT NULL DEFAULT 'open',
  resolution TEXT DEFAULT NULL,
  resolved_by BIGINT UNSIGNED DEFAULT NULL,
  resolved_at DATETIME DEFAULT NULL,
  created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
  updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
  PRIMARY KEY (id),
  CONSTRAINT fk_disp_book FOREIGN KEY (booking_id) REFERENCES bookings (id),
  CONSTRAINT fk_disp_raised FOREIGN KEY (raised_by) REFERENCES users (id),
  CONSTRAINT fk_disp_resolv FOREIGN KEY (resolved_by) REFERENCES users (id) ON DELETE SET NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
MODULE 8: ADMIN, RBAC & SYSTEM (10 Bảng)M8
CREATE TABLE roles (
  id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
  name VARCHAR(100) NOT NULL,
  slug VARCHAR(100) NOT NULL,
  description TEXT DEFAULT NULL,
  is_system TINYINT(1) NOT NULL DEFAULT 0,
  created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
  updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
  PRIMARY KEY (id), UNIQUE KEY uq_role_slug (slug)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

CREATE TABLE permissions (
  id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
  action VARCHAR(100) NOT NULL,
  resource VARCHAR(100) NOT NULL,
  description TEXT DEFAULT NULL,
  created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
  updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
  PRIMARY KEY (id), UNIQUE KEY uq_perm_act_res (action, resource)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

CREATE TABLE role_permissions (
  role_id BIGINT UNSIGNED NOT NULL,
  permission_id BIGINT UNSIGNED NOT NULL,
  PRIMARY KEY (role_id, permission_id),
  CONSTRAINT fk_rp_role FOREIGN KEY (role_id) REFERENCES roles (id) ON DELETE CASCADE,
  CONSTRAINT fk_rp_perm FOREIGN KEY (permission_id) REFERENCES permissions (id) ON DELETE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

CREATE TABLE user_roles (
  id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
  user_id BIGINT UNSIGNED NOT NULL,
  role_id BIGINT UNSIGNED NOT NULL,
  scope_type VARCHAR(50) DEFAULT NULL,
  scope_value VARCHAR(100) DEFAULT NULL,
  granted_by BIGINT UNSIGNED DEFAULT NULL,
  created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
  updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
  PRIMARY KEY (id), UNIQUE KEY uq_ur_scope (user_id, role_id, scope_type, scope_value),
  CONSTRAINT fk_ur_user FOREIGN KEY (user_id) REFERENCES users (id) ON DELETE CASCADE,
  CONSTRAINT fk_ur_role FOREIGN KEY (role_id) REFERENCES roles (id) ON DELETE CASCADE,
  CONSTRAINT fk_ur_grant FOREIGN KEY (granted_by) REFERENCES users (id) ON DELETE SET NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

CREATE TABLE staff_profiles (
  id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
  user_id BIGINT UNSIGNED NOT NULL,
  employee_code VARCHAR(50) NOT NULL,
  department ENUM('engineering','customer_service','finance','operations','marketing','management') NOT NULL,
  job_title VARCHAR(200) NOT NULL,
  region_scope VARCHAR(200) DEFAULT NULL,
  city_scope JSON DEFAULT NULL,
  manager_id BIGINT UNSIGNED DEFAULT NULL,
  joined_at DATE DEFAULT NULL,
  is_active TINYINT(1) NOT NULL DEFAULT 1,
  created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
  updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
  PRIMARY KEY (id), UNIQUE KEY uq_staff_user (user_id), UNIQUE KEY uq_staff_emp (employee_code),
  CONSTRAINT fk_staff_user FOREIGN KEY (user_id) REFERENCES users (id) ON DELETE CASCADE,
  CONSTRAINT fk_staff_mgr FOREIGN KEY (manager_id) REFERENCES staff_profiles (id) ON DELETE SET NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

CREATE TABLE audit_logs (
  id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
  actor_id BIGINT UNSIGNED NOT NULL,
  action VARCHAR(100) NOT NULL,
  entity_type VARCHAR(50) NOT NULL,
  entity_id BIGINT UNSIGNED NOT NULL,
  old_values JSON DEFAULT NULL,
  new_values JSON DEFAULT NULL,
  ip_address VARCHAR(45) DEFAULT NULL,
  user_agent TEXT DEFAULT NULL,
  created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
  PRIMARY KEY (id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

CREATE TABLE system_configs (
  id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
  config_key VARCHAR(200) NOT NULL,
  config_value TEXT NOT NULL,
  value_type ENUM('string','integer','decimal','boolean','json') NOT NULL DEFAULT 'string',
  group_name VARCHAR(100) NOT NULL,
  description TEXT DEFAULT NULL,
  is_public TINYINT(1) NOT NULL DEFAULT 0,
  updated_by BIGINT UNSIGNED DEFAULT NULL,
  created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
  updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
  PRIMARY KEY (id), UNIQUE KEY uq_syscfg_key (config_key)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

CREATE TABLE notifications (
  id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
  user_id BIGINT UNSIGNED NOT NULL,
  type VARCHAR(100) NOT NULL,
  channel ENUM('in_app','email','sms','push') NOT NULL DEFAULT 'in_app',
  title VARCHAR(500) NOT NULL,
  body TEXT DEFAULT NULL,
  data JSON DEFAULT NULL,
  entity_type VARCHAR(50) DEFAULT NULL,
  entity_id BIGINT UNSIGNED DEFAULT NULL,
  is_read TINYINT(1) NOT NULL DEFAULT 0,
  read_at DATETIME DEFAULT NULL,
  sent_at DATETIME DEFAULT NULL,
  failed_at DATETIME DEFAULT NULL,
  created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
  PRIMARY KEY (id),
  CONSTRAINT fk_notif_user FOREIGN KEY (user_id) REFERENCES users (id) ON DELETE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

CREATE TABLE risk_rules (
  id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
  rule_code VARCHAR(100) NOT NULL,
  name VARCHAR(255) NOT NULL,
  description TEXT DEFAULT NULL,
  risk_weight DECIMAL(5,2) NOT NULL DEFAULT 10.00,
  is_active TINYINT(1) NOT NULL DEFAULT 1,
  created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
  updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
  PRIMARY KEY (id), UNIQUE KEY uq_risk_rule_code (rule_code)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

CREATE TABLE risk_assessments (
  id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
  payment_id BIGINT UNSIGNED NOT NULL,
  booking_id BIGINT UNSIGNED NOT NULL,
  user_id BIGINT UNSIGNED NOT NULL,
  risk_score DECIMAL(5,2) NOT NULL DEFAULT 0.00,
  decision ENUM('approve','flagged','review','reject') NOT NULL DEFAULT 'flagged',
  triggered_rules JSON DEFAULT NULL,
  reviewed_by BIGINT UNSIGNED DEFAULT NULL,
  reviewed_at DATETIME DEFAULT NULL,
  notes TEXT DEFAULT NULL,
  created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
  updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
  PRIMARY KEY (id), UNIQUE KEY uq_risk_pay (payment_id),
  CONSTRAINT fk_risk_pay FOREIGN KEY (payment_id) REFERENCES payments (id) ON DELETE CASCADE,
  CONSTRAINT fk_risk_book FOREIGN KEY (booking_id) REFERENCES bookings (id) ON DELETE CASCADE,
  CONSTRAINT fk_risk_user FOREIGN KEY (user_id) REFERENCES users (id),
  CONSTRAINT fk_risk_rev FOREIGN KEY (reviewed_by) REFERENCES users (id) ON DELETE SET NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;


SET FOREIGN_KEY_CHECKS = 1;
PATCH OPTIMIZE: Index & Constraints (Hiệu năng & Toàn vẹn dữ liệu)OPTIMIZE
ALTER TABLE voucher_usages 
ADD INDEX idx_vu_booking (booking_id);

ALTER TABLE bookings
ADD CONSTRAINT chk_booking_dates CHECK (check_in_date < check_out_date),
ADD CONSTRAINT chk_booking_nights CHECK (num_nights = DATEDIFF(check_out_date, check_in_date)),
ADD CONSTRAINT chk_booking_total CHECK (total_amount >= 0);

ALTER TABLE payments
ADD CONSTRAINT chk_payment_amount CHECK (amount > 0);

ALTER TABLE daily_rates
ADD CONSTRAINT chk_daily_rate_price CHECK (price >= 0);

-- Fix 4: Index cho audit_logs
ALTER TABLE audit_logs
ADD INDEX idx_audit_entity (entity_type, entity_id),
ADD INDEX idx_audit_actor (actor_id),
ADD INDEX idx_audit_created (created_at);

-- Fix 5: Index cho notifications (Rất quan trọng cho performance)
ALTER TABLE notifications
ADD INDEX idx_notif_user_read (user_id, is_read, created_at),
ADD INDEX idx_notif_entity (entity_type, entity_id);

-- Fix 7: Index cho payments (Phục vụ báo cáo)
ALTER TABLE payments
ADD INDEX idx_pay_status_method (status, payment_method),
ADD INDEX idx_pay_created (created_at);

-- Fix 8: Bổ sung index tìm kiếm Properties
ALTER TABLE properties
ADD INDEX idx_prop_rating (status, avg_rating),
ADD INDEX idx_prop_location (latitude, longitude);
PATCH UPDATE: Cập nhật cấu trúc bảng property_policiesUPDATE
-- Thêm các cột mới vào bảng property_policies
ALTER TABLE property_policies

-- 1. Nhận / Trả phòng nâng cao (Check-in / Check-out)
ADD COLUMN check_out_from TIME DEFAULT '00:00:00' AFTER check_in_until,
ADD COLUMN early_check_in_allowed TINYINT(1) NOT NULL DEFAULT 0,
ADD COLUMN early_check_in_fee DECIMAL(12,2) DEFAULT NULL COMMENT 'Phụ phí nhận phòng sớm (VNĐ)',
ADD COLUMN late_check_out_allowed TINYINT(1) NOT NULL DEFAULT 0,
ADD COLUMN late_check_out_fee DECIMAL(12,2) DEFAULT NULL COMMENT 'Phụ phí trả phòng muộn (VNĐ)',

-- 2. Trẻ em (Cụ thể hóa độ tuổi)
ADD COLUMN min_child_age TINYINT UNSIGNED DEFAULT 0 COMMENT 'Độ tuổi tối thiểu được lưu trú',
ADD COLUMN infant_0_4_fee DECIMAL(12,2) NOT NULL DEFAULT 0.00 COMMENT 'Phụ thu trẻ 0-4t',
ADD COLUMN free_baby_cot TINYINT(1) NOT NULL DEFAULT 0 COMMENT 'Hỗ trợ nôi/cũi miễn phí',
ADD COLUMN child_5_11_fee DECIMAL(12,2) NOT NULL DEFAULT 0.00 COMMENT 'Phụ thu trẻ 5-11t',
ADD COLUMN child_5_11_must_use_extra_bed TINYINT(1) NOT NULL DEFAULT 0 COMMENT 'Trẻ 5-11t bắt buộc dùng giường phụ',

-- 3. Vắng mặt (No-show)
ADD COLUMN no_show_penalty_type ENUM('full_amount','first_night','percent') NOT NULL DEFAULT 'full_amount',
ADD COLUMN no_show_penalty_value DECIMAL(12,2) DEFAULT NULL COMMENT 'Giá trị phạt tương ứng với type',

-- 4. Đặt phòng & Thanh toán
ADD COLUMN instant_confirmation TINYINT(1) NOT NULL DEFAULT 1 COMMENT '1: Tự động duyệt, 0: Host phải duyệt',
ADD COLUMN deposit_required TINYINT(1) NOT NULL DEFAULT 0,
ADD COLUMN deposit_type ENUM('percent','fixed_amount') DEFAULT NULL,
ADD COLUMN deposit_value DECIMAL(12,2) DEFAULT NULL,
ADD COLUMN deposit_days_before SMALLINT UNSIGNED DEFAULT NULL COMMENT 'Số ngày phải cọc trước khi check-in',
ADD COLUMN accepted_payment_methods JSON DEFAULT NULL COMMENT 'Mảng: ["cash", "credit_card", "transfer"]',

-- 5. Khách lưu trú
ADD COLUMN extra_person_fee DECIMAL(12,2) DEFAULT NULL COMMENT 'Phụ phí thêm người (vượt tiêu chuẩn)',

-- 6. Dịch vụ & Tiện ích (Bổ sung phần phí)
ADD COLUMN wifi_fee DECIMAL(12,2) NOT NULL DEFAULT 0.00,
ADD COLUMN breakfast_included TINYINT(1) NOT NULL DEFAULT 0,
ADD COLUMN breakfast_fee DECIMAL(12,2) DEFAULT NULL,
ADD COLUMN airport_shuttle_available TINYINT(1) NOT NULL DEFAULT 0,
ADD COLUMN airport_shuttle_fee DECIMAL(12,2) DEFAULT NULL,
ADD COLUMN parking_type ENUM('free','paid','none') NOT NULL DEFAULT 'none',
ADD COLUMN parking_fee DECIMAL(12,2) DEFAULT NULL,

-- 7. Quy định Hành vi (House Rules)
ADD COLUMN smoking_penalty DECIMAL(12,2) DEFAULT NULL COMMENT 'Phạt hút thuốc sai quy định',
ADD COLUMN pet_fee DECIMAL(12,2) DEFAULT NULL COMMENT 'Phí mang thú cưng',
ADD COLUMN pet_max_weight_kg DECIMAL(5,2) DEFAULT NULL COMMENT 'Cân nặng tối đa của thú cưng',
ADD COLUMN parties_allowed TINYINT(1) NOT NULL DEFAULT 0 COMMENT 'Cho phép tổ chức tiệc',
ADD COLUMN quiet_hours_start TIME DEFAULT NULL COMMENT 'Giờ bắt đầu giữ yên lặng',
ADD COLUMN quiet_hours_end TIME DEFAULT NULL COMMENT 'Giờ kết thúc giữ yên lặng',
ADD COLUMN requires_marriage_certificate TINYINT(1) NOT NULL DEFAULT 0 COMMENT 'Cần giấy ĐKKH cho khách Việt - Ngoại',

-- 8. Rủi ro & Bồi thường
ADD COLUMN damage_deposit_required TINYINT(1) NOT NULL DEFAULT 0,
ADD COLUMN damage_deposit_amount DECIMAL(12,2) DEFAULT NULL COMMENT 'Tiền cọc hư hỏng thu lúc check-in',
ADD COLUMN liability_waiver TEXT DEFAULT NULL COMMENT 'Miễn trừ trách nhiệm tài sản',
ADD COLUMN force_majeure_policy TEXT DEFAULT NULL COMMENT 'Chính sách bất khả kháng';