Ở phần 1 của series này, mình đã hướng dẫn các bạn tạo trang giới thiệu thông tin về các loại phòng của một khách sạn. Thông thường thì tại trang giới thiệu phòng này sẽ luôn có nút điều hướng đến phần đặt phòng để khách hàng có để đặt trực tiếp trên website. Tuy nhiên, với một số khách sạn phát triển kênh bán hàng trực tiếp nhiều, thì ngoài việc để khách hàng đặt trực tiếp như vậy, việc nhân viên kinh doanh tự tạo booking thay cho khách là việc khá phổ biến.
- 1. Tạo một Custom Post Type mới
- 2. Tạo Custom Fields cho post type “Booking” vừa tạo
- 3. Xử lý chức năng cho các fields của trang booking
- 4. Lời cuối
Ngoài ra, để tiện cho bạn hình dung việc xây dựng được trang đặt booking ngoài frontend sau này, mình sẽ hướng dẫn luôn các bạn cách tạo một trang đặt booking ở backend để nhân viên sử dụng nội bộ trong bài viết số 2 này. Phần tạo booking ngoài frontend mình sẽ để dành sang bài số 3.
Các plugin bạn cần cài đặt và chuẩn bị thì vẫn tương tự như mình đã hướng dẫn ở bài số 1 thôi nhé.
Bây giờ, bắt tay vào thực hiện thôi!
Tạo một Custom Post Type mới
Chúng ta sẽ tạo một post type mới có tên là “Booking”. Mỗi một đơn đặt phòng được tạo sau này sẽ tương ứng với một bài viết thuộc loại post type này nhé.
Bạn tìm tới menu Meta Box > Post Types > New Post Type trong admin dashboard để tạo một custom post type mới nhé.
Sau đó, bạn điền các thông tin và tùy chọn hiển thị cho post type. Trong đó:
- Tên của post type là: Booking;
- Supports: mình bỏ chọn các field mặc định và chỉ chọn Revision, tức cho phép xem lại các lần chỉnh sửa bài viết thuộc loại post type này;
- Menu position after: chọn vị trí hiển thị của loại post type trên menu trong dashboard.

Đến đây, bạn có thể nhấn Publish để lưu post type và đi tiếp đến mục Meta Box > Custom Fields > Add New, hoặc nhấn luôn Add Custom Fields ngay tại trang chỉnh sửa thông tin post type này để thực hiện bước tiếp theo.

Tạo Custom Fields cho post type “Booking” vừa tạo
Ở trang tạo Booking này, chúng ta sẽ cần tạo khá nhiều field với các loại field khác nhau. Bạn nên định hướng rõ ngay từ đầu xem các fields bạn cần là gì, điền thông tin như nào, thứ tự và cách hiển thị dữ liệu, chức năng như nào để thuận tiện hơn cho quá trình tạo field. Như bài toán của mình, thì các thông tin sẽ như sau:
Tên field | Loại field | Chức năng |
Số order | number | Là một dãy số duy nhất và tự sinh ra mỗi khi có booking |
Ngày tạo booking | date | Ngày tạo booking |
Nhân viên phụ trách | user | Chọn một từ trong danh sách các thành viên (users) trong WordPress |
Thông tin người đặt | heading | Ngăn cách nội dung các fields thông tin khách hàng |
· Họ và tên | text | Tên khách hàng |
· Số điện thoại | number | Số điện thoại khách hàng |
Email của khách hàng | ||
· Địa chỉ | text | Địa chỉ của khách hàng |
· Ghi chú | textarea | Ghi chú nếu có |
Thông tin Booking | heading | Ngăn cách nội dung các fields thông tin Booking |
Chi tiết Đặt phòng | group | Nhóm các fields về thông tin phòng được đặt. Nhóm field này có thể nhân bản được (dùng trong trường hợp: 1 khách hàng đặt nhiều phòng cùng một lúc) |
· Phòng | post | Chọn một từ trong danh sách phòng hiện có |
· Giá | number | |
· Số người lớn | number | Người dùng tự nhập |
· Số trẻ em | number | Người dùng tự nhập, mặc định là 0 |
· Tuổi của trẻ em | number | Số tuổi của từng trẻ. Tự động thêm 1 trường này khi có thêm 1 trẻ. |
· Số giường thêm (extra bed) | number | Số giường kê thêm nếu có |
· Ngày vào ở | date | |
· Ngày trả phòng | date | |
· Tổng số đêm ở | number | Tự động tính theo ngày vào ở và ngày trả phòng |
Số lượng phòng | number | Tự động tính ra số lượng phòng từ số lượng Group phòng ở trên |
Yêu cầu khác | textarea | Ghi chú các yêu cầu khác nếu có của khách hàng |
Thông tin thanh toán | heading | Ngăn cách nội dung các fields thông tin Thanh toán |
· Tổng tiền thanh toán | number | Tự tính theo số phòng và loại phòng + số giường thêm |
· Số tiền đã thanh toán | number | Cho sales nhập vào số tiền đã thanh toán hoặc cọc trước |
· Số tiền còn lại | number | = Tổng tiền thanh toán – Số tiền đã thanh toán |
Tình trạng đơn hàng | heading | |
· Tình trạng đơn hàng | select | Chọn một trong các nội dung: Đã hủy, Đã thanh toán, Chưa thanh toán |
· Số tiền hoàn lại cho khách (nếu hủy) | select | Các mức hoàn lại cho khách nếu hủy: 100%, 70%, 50%, 0% |
Vì số lượng field khá nhiều, nên mình sẽ chỉ hướng dẫn tạo một số field đặc trưng. Do mình sử dụng Meta Box Builder nên việc tạo các field khá đơn giản và thao tác gần tương tự như nhau. Bạn thao tác lần lượt như sau nhé:
Tạo Field Group
Trước khi tạo custom fields bất kỳ, chúng ta cần tạo Field Group (chính là custom meta box) để chứa custom field đó.
Như mình nói ở trên, bạn có thể đến mục Meta Box > Custom Fields > Add New trong admin dashboard, hoặc nhấn luôn Add Custom Fields ngay tại trang chỉnh sửa thông tin post type “Booking” để tạo Field Group.
Bạn điền các thông tin cho Field Group và ở tab Settings của giao diện tạo Field Group, bạn chọn loại post type là Booking ở mục Post Types nhé.

Tiếp sau đây, bạn quay trở lại với tab Fields của giao diện tạo Field Group để tạo từng custom fields mà bạn cần nhé.
Tạo field Number cho thông tin Số Order
Vẫn ở tab Fields trong phần Edit Field Group, bạn chọn Number ở mục Basic phía bên cột trái để tạo một field loại Number nhé.
Vì trường thông tin số order (mã số của đơn hàng) là một trường thông tin do hệ thống tự động sinh ra, và người dùng không tự thay đổi được, nên mình chọn tick vào ô Read Only và ô Disable.

Tạo Field cho trường thông tin Nhân viên phụ trách
Các nhân viên phụ trách cho đơn hàng được nhập chính là nhân viên sale của khách sạn. Mỗi người này sẽ có một tài khoản trong WordPress admin (gọi là một user). Vậy nên, chúng ta sẽ tạo một field cho phép hiển thị ra danh sách các user trong WordPress để chọn một trong số họ làm người phụ trách cho đơn hàng.
Ở phía bên cột trái, bạn chọn User ở mục WordPress là được nhé.

Tạo heading để phân tách các mục trong đơn hàng
Heading thực ra là một field thuộc mục Layout. Nó khác ở chỗ là không cho phép người dùng nhập giá trị và cũng không có ID. Trường này chỉ hiển thị như một heading giúp phân tách các khu vực, nhóm nội dung khác nhau. Các trường heading này sẽ giúp form điền thông tin của bạn dễ nhìn hơn.

Tạo một group các field về thông tin chi tiết đặt phòng
Nhóm các field này bao gồm thông tin loại phòng, số người ở, số lượng trẻ em, số lượng extra bed. Việc tạo từng field lẻ này khá là dễ dàng và cơ bản, bạn chỉ cần chọn loại field tương ứng từ phía bên cột trái trong giao diện tạo field là được. Nhưng để nhóm chúng lại thành một nhóm và có thể nhân bản được thì bạn sẽ cần đến sự trợ giúp của Meta Box Group.
Ở mục Layout, bạn chọn Group nhé:
Ở group các field này, mình chọn đặt Custom CSS Class để ở bước sau mình sẽ xử lý cho phép bắt sự kiện click và update số lượng phòng trong JS.

Ngoài ra, với mỗi sub-field bên trong group này, mình có các lưu ý như sau:
1. Field cho tên loại phòng:
Mình chọn loại field type là Post và chọn hiển thị tất cả các post có trong loại post type là Room (các loại phòng mà mình đã tạo ở bài số 1).
2. Ở trường thông tin Trẻ em:
Người dùng sẽ cần điền số lượng trẻ em sẽ ở phòng đang đặt vào ô này. Tùy theo quy định của mỗi khách sạn, thì độ tuổi của trẻ em sẽ quyết định việc có tính thêm tiền vào giá phòng hay không. Vì thế nên nếu người dùng chọn số lượng trẻ em từ 1 trở lên, thì sẽ có thêm các trường thông tin để người dùng điền tuổi của trẻ vào, mình đặt các trường này tên là Age Children 1 và Age Children 2.
Số lượng trường để điền tuổi sẽ tương ứng với số lượng trẻ em được nhập vào ô “Trẻ em”. Ở đây, mình cũng có thêm giới hạn là mỗi phòng chỉ cho phép tối đa 2 trẻ em.
Để làm được việc này, mình sử dụng Meta Box Conditional Logic để quy định khi nào thì hiển thị các field độ tuổi.


Trên đây mình tạo điều kiện cho field độ tuổi của trẻ số 1 là Children >=1, bạn có thể tự đặt điều kiện cho field độ tuổi của trẻ số 2 hiển thị khi Children >=2 nhé.
3. Trường thông tin ngày vào ở và ngày trả phòng:
Hai trường này mình tạo field dưới dạng ngày tháng thông thường và hiển thị lịch cơ bản ra. Ở bài cuối cùng trong series này, mình sẽ hướng dẫn các bạn đưa các thông tin ngày còn phòng trống lên lịch này sau nhé.
Ở mục Advanced bên cột trái, chọn Date. Field này sẽ hiển thị lịch để bạn chọn ngày thay vì tự nhập.

Tạo các field cho các trường thông tin khác
Bạn cũng áp dụng cách chọn một loại field từ phía bên cột trái để tạo field tương ứng cho loại thông tin bạn cần nhé.
Riêng với các trường thông tin về giá và thanh toán, bạn cứ tạo như bình thường. Chúng ta sẽ xử lý ở bước sau để các trường thông tin này tự động tính giá dựa trên các thông tin điền vào ở các trường thông tin đặt phòng.
Cuối cùng, bạn sẽ thấy ở trang đặt phòng sẽ có các field hiển thị như sau:

Xử lý chức năng cho các fields của trang booking
Ở bước này, chúng ta sẽ xử lý thêm để tự động sinh giá trị cho một số fields đặc biệt, cụ thể:
- Tự động sinh ra số order cho mỗi đơn đặt phòng;
- Tự đếm số phòng dựa trên số lần nhân bản nhóm field về thông tin đặt phòng (Số lần nhấn nút “Add Room” (tức “Thêm phòng”) để điền vào ô số lượng phòng đặt;
- Tự động hiển thị giá phòng tương ứng sau khi chọn loại phòng;
- Tự động tính tổng số đêm đặt từng phòng;
- Tự động tính tổng tiền cần thanh toán;
Riêng phần này, chúng ta sẽ cần dùng đến code nhé.
Tự động sinh ra số order cho mỗi đơn đặt phòng
Mỗi bài viết có loại post type là Booking thì đều không có tiêu đề (Title) nên đường dẫn (permalink) cũng không tự sinh ra được, và sẽ ở dạng Auto Draft sau khi lưu. Vì thế, mình sẽ lấy ID của bài viết đó làm tiêu đề và làm đường dẫn luôn cho nó nhé.
Đây là bài viết được lưu khi mình chưa tạo tiêu đề cho nó:
Bạn thêm đoạn code sau vào file functions.php
trong thư mục theme của bạn nhé:
function update_post( $post_id ) { $post = array( 'ID' => $post_id, 'post_title' => '#'.$post_id, 'post_name' => $post_id, ); wp_update_post( $post ); update_post_meta($post_id, 'order', $post_id); } add_action('save_post_booking', 'update_post', 20);
Trong đó, update_post_meta($post_id, 'order', $post_id)
là để tự động đặt post ID làm giá trị của trường số order.
Lưu file functions.php
và bạn thử tạo một đơn đặt phòng mới, bạn sẽ thấy tiêu đề của mỗi đơn đặt phòng như sau:


Tự đếm số phòng dựa trên số lần nhân bản nhóm field về thông tin đặt phòng
Mặc định trường số lượng phòng sẽ có giá trị là 1, tương đương với 1 group field ban đầu cho 1 phòng. Khi người dùng ấn vào nút Add More hay Add Room (thêm phòng) thì ta sẽ cộng giá trị cho trường này thêm 1 đơn vị.
Việc này cần được xử lý bằng Javascript ở backend như sau:
Copy đoạn code sau vào file functions.php
. Đoạn code này được sử dụng nhằm nhập file có đường dẫn là /js/booking.js
vào trang post-new.php
của loại post type là Booking.
function add_admin_scripts( $hook ) { global $post; if ( $hook == 'post-new.php' && 'booking' === $post->post_type) { wp_enqueue_script( 'booking-js', get_stylesheet_directory_uri().'/js/booking.js' ); } } add_action( 'admin_enqueue_scripts', 'add_admin_scripts' );
Tiếp theo, bạn tạo một file có tên là booking.js
trong thư mục js
. Đường dẫn cụ thể như sau:
Tiếp theo, bạn mở file booking.js
vừa tạo ra và dán đoạn code sau vào file này:
jQuery( document ).ready( function( $ ) { $('.group-bookings .add-clone').on('click', function(e){ setTimeout(function(){ var rooms = $('.group-bookings .rwmb-group-clone').length; $('input#amount').val(rooms); update_js(); }, 100); }); function update_js(){ $('.group-bookings .remove-clone').on('click', function(e){ setTimeout(function(){ var rooms = $('.group-bookings .rwmb-group-clone').length; $('input#amount').val(rooms); }, 100); }); } });
Đoạn js
trên sẽ giúp bạn cập nhật số lượng phòng mỗi khi người dùng click vào nút Add Room (thêm phòng) hoặc Remove.
Trong đó:
‘group-booking’
: là Custom CSS Class của group mà ta đã đặt ở bước tạo một nhóm các fields cho thông tin đặt phòng;‘amount’
: là ID của field tổng số lượng phòng;Add-clone
,remove-clone
: là Custom CSS Class của 2 nút thêm và xóa phòng. Để xem thông tin này, bạn có thể nhấn nút F12:

Tự động hiển thị giá phòng tương ứng sau khi chọn loại phòng
Đầu tiên, chúng ta cần tạo một field loại Number để hiển thị giá phòng. Mình đặt field này ở dạng Read only và thêm điều kiện hiển thị là khi giá trị của trường loại phòng (room) không phải (khác) “rỗng”. Tức khi field tên loại phòng (Room) có một giá trị bất kỳ (không để trống) thì trường giá phòng mới hiển thị.

Tiếp theo, mình sẽ tạo 1 biến dạng global trong js
. Biến này sẽ là một mảng chứa tất cả các thông tin như giá phòng và ID của phòng đó. Mỗi khi người dùng chọn một phòng, biến này sẽ được lôi ra để kiểm tra, nếu ID của loại phòng được chọn bằng với ID nào trong mảng, thì giá phòng của ID đó sẽ được hiển thị.
Để tạo được biến này, ta mở file functions.php
, thay đoạn code nhập file booking.js
lúc nãy bằng đoạn code sau nhé.
function add_admin_scripts( $hook ) { global $post; if ( $hook == 'post-new.php' || $hook == 'post.php' ) { if ( 'booking' === $post->post_type ) { wp_register_script( 'booking-js', get_stylesheet_directory_uri().'/js/booking.js' ); $rooms = get_posts(array( 'post_type' => 'room', 'posts_per_page'=> -1 )); $arr_rooms = array(); foreach ($rooms as $room) { array_push($arr_rooms, array('id'=>$room->ID, 'price'=>rwmb_get_value('price','', $room->ID))); } wp_localize_script( 'booking-js', 'rooms_data', $arr_rooms); wp_enqueue_script( 'booking-js' ); } } } add_action( 'admin_enqueue_scripts', 'add_admin_scripts', 10, 1 );
Sau đó, copy đoạn code dưới đây và đưa vào file booking.js
:
$( ".group-bookings .rwmb-field select[name*='[room]']" ).on('change', function(){ var curr = $(this).val(); var price_unit = $(this).closest('.rwmb-field').siblings().find("input[name*='[price_unit]']"); rooms_data.forEach(function(val, i){ if (curr == val['id']){ price_unit.val(parseInt(val['price'])); } }); });
Bạn sẽ thấy kết quả như này:

Tính tổng số đêm dựa vào ngày vào ở và ngày trả phòng
Trước khi tính tổng số đêm khách đặt, mình sẽ làm một chức năng liên quan ở đây nữa là đặt điều kiện ngày trả phòng phải là ngày sau ngày vào ở. Mình sẽ sử dụng js để xử lý minDate cho trường thông tin ngày trả phòng bằng với giá trị của trường thông tin ngày vào ở cộng thêm 1 đơn vị.
Bạn copy đoạn code dưới đây và đặt vào file booking.js
nhé:
$( ".group-bookings .rwmb-field input[name*='[check_in]']" ).on('change', function(){ var d = new Date($(this).val()); var af_date = d.getDate() + 1; var af_month = d.getMonth() + 1; var af_year = d.getFullYear(); var min_date = af_year + '-' + af_month + '-' + af_date; $(this).closest('.rwmb-field').siblings().find("input[name*='[check_out]']").datepicker('option', 'minDate', min_date); });
Bây giờ, để tính số đêm mà khách sẽ ở tự động dựa theo ngày vào ở và ngày trả phòng, bạn thêm đoạn code sau vào file booking.js
nhé:
$( ".group-bookings .rwmb-field input[name*='[check_out]']" ).on('change', function(){ var total = calculate_total_day($(this).closest('.rwmb-field').siblings().find("input[name*='[check_in]']").val(), $(this).val()); $(this).closest('.rwmb-field').siblings().find("input[name*='[total_nights_stay]']").val(total); }); function calculate_total_day(check_in, check_out){ var date1 = new Date(check_in); var date2 = new Date(check_out); return (date2.getTime() - date1.getTime()) / (1000 * 3600 * 24); }
Bạn sẽ thấy field Tổng số đêm ở của bạn đã tự động nhảy số khi bạn chọn ngày:

Tính tổng tiền cần thanh toán
Công thức tính tổng tiền cần thanh toán như sau:
Tổng tiền cần thanh toán = (Giá phòng 1 + số extra bed phòng 1 * giá) * số đêm
+ (Giá phòng 2 + số extra bed phòng 2 * giá) * số đêm
+ …
+ (Giá phòng N + số extra bed phòng N * giá) * số đêm
Trong đó, mình coi extra bed như một loại phòng và có mức giá cố định luôn, đồng thời, mình sẽ tạo thêm một field là Tổng giá mỗi phòng để tiện cho việc tính toán.
Tổng giá mỗi phòng = (Giá phòng + số extra bed * giá extra bed) * số đêm
Cuối cùng bạn chỉ cần cộng tổng giá trị các field tổng giá mỗi phòng là ra tổng tiền cần thanh toán.
Bạn cần đưa đoạn code này vào file booking.js
để tính tổng giá của mỗi phòng:
$( ".group-bookings .rwmb-field input[name*='[check_out]']" ).on('change', function(){ var total = $(this).closest('.rwmb-field').siblings().find("input[name*='[total_nights_stay]']").val(); var price_unit = $(this).closest('.rwmb-field').siblings().find("input[name*='[price_unit]']").val(); var extra = $(this).closest('.rwmb-field').siblings().find("input[name*='[extra_bed]']").val(); rooms_data.forEach(function(val, i){ if (498 == val['id']){ total_extra = extra*parseInt(val['price']); } }); $(this).closest('.rwmb-field').siblings().find("input[name*='[total_amount]']").val((parseInt(price_unit)+parseInt(total_extra))*total); });
Và đưa tiếp đoạn code này vào file booking.js
để tính tổng giá trị tất cả các phòng:
function update_total_payment(){ var total_payment = 0; $( ".group-bookings .rwmb-field input[name*='[total_amount]']" ).each(function(){ total_payment = parseInt(total_payment) + parseInt($(this).val()); }) $('#total').val(total_payment); }
Bạn sẽ thấy các trường trong form đặt booking của bạn hoạt động đúng logic như sau:
Dưới đây là toàn bộ phần code của file booking.js
mà mình đã làm để các bạn tham khảo:
https://gist.github.com/rilwis/33b7e524f9e348b17216767558616355
Lời cuối
Trong phần hướng dẫn này, mình tự đặt giả thiết về logic đặt phòng nên khi bạn vẫn nên điều chỉnh đôi chút cho phù hợp thực tế sử dụng của bạn. Riêng với phần đặt phòng từ frontend để cho phép khách hàng tự tạo booking, mình sẽ hướng dẫn bạn thực hiện ở bài tiếp theo. Hãy chờ đón đọc bài viết mới của mình nhé!