In the first post of this series, we created a page showing all the hotel room’s information. It normally has a button or several call-to-action areas to go to the booking page which allows your customers to make an order. However, ‘cause some businesses have strong direct channels, they want their sales to make booking orders for their customers as well. Along with that, to have you imagine how to create a booking page in the frontend later (which is in the 3rd post), I made this post to show you how to create one in the backend. That is for internal users only.
- 1. Create a New Custom Post Type
- 2. Create Custom Fields for the created “Booking” post type
- 3. Add Extra Functions to the Custom Fields
- 4. Final Words
The necessary plugins were mentioned in the first post of this series, just follow them.
Now, let’s get started!
Create a New Custom Post Type
We are going to create a new custom post type named Booking. Each created booking order will be a post in this post type.
Head to Meta Box > Post Types > New Post Type in the admin dashboard to create a new custom post type.
Fill in the information for the post type:
- Name of the post type: Booking;
- Supports: unchoose default fields and just tick the Revision to allow tracking changes;
Next, you can press Publish to save the post type then go to Meta Box > Custom Fields > Add New to create custom fields for this post type.
Note: Meta Box has an online tool called Post Type Generator in case you don't want to install MB Custom Post Types and Custom Taxonomies.
Create Custom Fields for the created “Booking” post type
For the booking page, we need to create a bunch of different fields. You should clarify in advance what you want, how users fill in the data, how to show fields and their value, etc. That’s will make the process to create them easier. As my supposition, all the fields I need are as follows:
Name | Field type | Function |
Order Number | number | Is an array of numbers and auto-generated whenever the booking is created |
Booking Date | date | Date of creating the booking |
Person in Charge | user | Choose one from the list of users in WordPress |
Customer information | heading | Separate the sections |
· Full Name | text | |
· Mobile | number | |
· Address | text | |
· Notes | textarea | |
Booking Information | heading | Separate the sections |
Booking Details | group | Group of fields regarding the booking room. This group is cloneable for the case that the customer books multiple rooms |
· Room | post | Choose one from the list of the rooms |
· Price | number | |
· Adults | number | |
· Children | number | |
· Age of Children (1, 2) | number | Each field is for a child. This field is auto-display whenever there is one more child |
· Extra Bed | number | |
· Check-in Date | date | |
· Check-out Date | date | |
· Total Nights of Stay | number | Auto-calculated bases on the check-in and check-out date |
Total Number of Rooms | number | Auto-calculated bases on the number of the group about the room details |
Other Request | textarea | |
Payment Information | heading | Separate the sections |
· Total Amount | number | Auto-calculated bases on room details and number of rooms |
· Paid Amount | number | Manual input by sales |
· Unpaid Amount | number | = Total amount - the paid amount |
Booking Status | heading | Separate the sections |
· Booking Status | select | Choose one from the provided statuses |
· Refund (if any) | select | Choose one level (%) from the provided levels |
We’ll create some of the prominent fields because there are quite many fields here and although they are different field types but have the same way to create.
I’m using Meta Box Builder so the execution will be visual and easier. In case you are not, you must create each field by code.
Create a Field Group
Before creating any field, we need to create a field group (which means custom meta box) to contain all the fields.
As I said above, you may go to Meta Box > Custom Fields > Add New.
In the Settings tab of the Edit Field Group screen, choose Booking in the Post Types section.
Next, come back to the Fields tab in the Edit Field Group screen to create one by one custom fields as you need.
Create a Number Field for Order Number
In the Fields tab of the Edit Field Group screen, click Add Field button and search for the Number field in the drop-down menu. You can do similarly with other fields.
Because the Order Number field is auto-generated information that users can not input or edit, I ticked to the Ready Only and Disable boxes.
Create a field for Person in Charge
Sales, who are the persons in charge of the booking orders, have their own admin account in WordPress. So, we create a field that shows all the usernames in WordPress to pick one from them.
In the left bar, just choose User field type.
Create a heading to separate the sections
Heading actually is a field in the type of Layout. It displays as a heading to separate the sections or groups so that the form will look more well-structured.
Create a Group of Fields for Booking Details
This group includes all the fields about the room, price, adults, children, age of children, extra bed, check-in & check-out date. To group them all, we need help from the Meta Box Group.
Select Group in the drop-down menu. And click Add Fields button inside this group field to add sub-fields.
Then, in this group, I set Custom CSS Class to track the event when the user clones the group. This will help to calculate the total room later.
Besides that, each sub-field in this group has some remarks:
Fields for the Room Name:
I choose the field type is Post for this field and show all the posts which are in the Room post type (rooms we created in the first post of this series).
The Children field:
As the regulation of each hotel, children may be charged or not depending on their ages. So we will create additional fields to fill in their ages. An additional field for age will appear when you add one more in the Children field. I restrict the maximum number of children is two as well.
I use Meta Box Conditional Logic to set a condition for the age fields.
Like the above image, I set the Age of Children 1 field displays when Children >=1. It is likewise for the Age of Children 2, displays when Children >=2.
Check-in Date and Check-out Date:
We create these fields in the type of normal date with a calendar. In the last post of this series, we will show the vacant room to the calendar in real time.
In the drop-down menu, choose Date. This field will show a calendar to choose a date instead of typing.
Create Custom Fields for Others
It is likewise to create other custom fields.
Regarding the price and payment fields, just create it. We will tackle them in the next step to make it be auto-calculated.
When you finish creating the custom fields, the form of the booking order will be like this:
Add Extra Functions to the Custom Fields
In this step, we are going to deal with some special fields to automatically calculate or generate value for them. In details:
- Automatically generate booking order number;
- Automatically calculate the total number of booking rooms;
- Automatically display the corresponding price of the booking room;
- Automatically calculate the total nights of stay;
- Automatically calculate the total amount.
We’ll need to code much in this part. Let’s go!
Automatically Generate Booking Order Number
Each post in the Booking post type has neither title nor the permalink. All of them will be kinds of the Auto Draft after saving. So, we will get the post ID and set it to be the title and permalink as well.
This is the post before we create a title for it:
Now, add these codes to the functions.php
in the theme folder:
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);
In there, update_post_meta($post_id, 'order', $post_id)
helps to set the post ID to be the value of the Order Number field.
Save the functions.php
file, then create a new booking order for a try. You’ll see the title of order as below:
Automatically Calculate the Total Number of Booking Rooms
The quantity of room will be 1 in default, equivalent to 1 group of booking details for 1 room. Whenever users press Add More or Add Room, we will add one more unit to this field.
We will solve this by javascript in the backend. So, add these codes to the functions.php
file. It is used to register a file that has the link is /js/booking.js to post-page.php
of the Booking post type.
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' );
Next, create a file named booking.js
in the js
folder.
Open it and paste these codes there:
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); }); } });
This js code helps us to update the quantity of room whenever users press the Add Room or Remove buttons.
In there:
‘group-booking’
: is the Custom CSS Class of the group of fields about booking details;‘amount’
: is the ID of the Total Number of Room field;‘add-clone’
,‘remove-clone’
: is the Custom CSS Class of the Add Room or Remove button. To see this information, you may press F12:
Automatically Display the Corresponding Price of the Booking Room
First, we set the field Price is Read only with a condition to display is that the value of the Room field is not “empty”. It means that when the Room field has any value (not leave it blank), the Price field will appear.
Next, I will create a global
variable in js
. This variable will be an array containing all the information about the price and ID of the rooms. Whenever users choose a room, this variable will be checked. If the ID of the booking room matches an ID in the array, the corresponding price will be shown.
To create this variable, open the functions.php
file and replace the code registering the booking.js
file by these ones:
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 );
Then paste these codes to 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'])); } }); });
The price will be auto-displayed now:
Automatically Calculate the Total Nights of Stay
Before calculating the total nights of stay, we should set a condition that the check-out date must be after the check-in date. I use js to set the minDate of the check-out date is the check-in date plus one unit.
Paste these codes to the booking.js
file:
$( ".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); });
Now, to automatically count the number of booking nights, add these codes to the booking.js
file:
$( ".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); }
From now on, your Total Nights of Stay field’s value is automatically-changed when you choose a date:
Automatically Calculate the Total Amount
Formula to calculate the total amount as below:
Total Amount = (Price of room 1 + numbers of extra bed in room 1 * price) * nights + (Price of room 2 + numbers of extra bed in room 2 * price) * nights + … + (Price of room N + numbers of extra bed in room N * price) * nights
In there, I set the Extra Bed is a kind of room and has a fixed price. At the same time, I create a field for the Total Amount of Each Room:
Total Amount of Each Room = (Price of room + numbers of extra bed * price) * nights
We just need to summarize all the Total Amount of Each Room to get the Total Amount of the booking.
Add these codes to the booking.js
file to calculate the total amount of each room:
$( ".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); });
Note: (498 == val['id'])
: 498
is my ID, so remember to change it with yours.
And also add this one to calculate the Total Amount (Grand Total):
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); }
Now, all the fields will automatically run as your logic:
For your quick reference, this is my booking.js
file:
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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); | |
}); | |
$( ".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'])); | |
} | |
if (498 == val['id']){ | |
total_extra = extra*parseInt(val['price']); | |
} | |
}); | |
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(); | |
$(this).closest('.rwmb-field').siblings().find("input[name*='[total_amount]']").val((parseInt(price_unit)+parseInt(total_extra))*total); | |
update_total_payment(); | |
}); | |
$( ".group-bookings .rwmb-field input[name*='[check_in]']" ).on('change', function(){ | |
$(this).closest('.rwmb-field').siblings().find("input[name*='[check_out]']").datepicker('option', 'minDate', $(this).val()); | |
}); | |
$( ".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); | |
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); | |
update_total_payment(); | |
}); | |
} | |
$( ".group-bookings .rwmb-field input[name*='[extra_bed]']" ).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).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); | |
update_total_payment(); | |
}); | |
$( ".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'])); | |
} | |
if (498 == val['id']){ | |
total_extra = extra*parseInt(val['price']); | |
} | |
}); | |
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(); | |
$(this).closest('.rwmb-field').siblings().find("input[name*='[total_amount]']").val((parseInt(price_unit)+parseInt(total_extra))*total); | |
update_total_payment(); | |
}); | |
function extra_bed(){ | |
var extra = 0; | |
$( ".group-bookings .rwmb-field input[name*='[extra_bed]']" ).each(function(){ | |
extra = extra + $(this).val(); | |
}) | |
} | |
function rooms(){ | |
var rooms_id = []; | |
$( ".group-bookings .rwmb-field select[name*='[room]']" ).each(function(index, value){ | |
rooms_id[index] = $(this).val(); | |
}) | |
} | |
$("#paid").on('change', function(){ | |
$('#remaining').val(parseInt($('#total').val())-parseInt($(this).val())); | |
}); | |
$( ".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); | |
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); | |
update_total_payment(); | |
}); | |
$( ".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); | |
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); | |
update_total_payment(); | |
}); | |
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); | |
} | |
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); | |
} | |
}); | |
Final Words
In this tutorial, I assumed all about the logic and process of booking. So, you should adjust the fields and functions to meet your using in reality. We will create a booking order form in the frontend as well as show the vacant rooms in the calendar in the next posts. Keep an eye on our next blog posts!
- How to Build a Hotel Booking Website Using Meta Box - P1
- How to Build a Hotel Booking Website Using Meta Box - P2 - Booking Page in Backend
- How to Build a Hotel Booking Website Using Meta Box – P3 – Booking Page for Customer
- How to Build a Hotel Booking Website Using Meta Box - P4 - Booking Management Page
- How to Build a Hotel Booking Website Using Meta Box - P1
- How to Build a Hotel Booking Website Using Meta Box - P2 - Booking Page in Backend
- How to Build a Hotel Booking Website Using Meta Box – P3 – Booking Page for Customer
- How to Build a Hotel Booking Website Using Meta Box - P4 - Booking Management Page
Other series you might be interested in
- Author Bio
- Better 404 Page
- Blogs for Developers
- Building a Simple Listing Website with Filters
- Building an Event Website
- Building Forms with MB Frontend Submission
- Coding
- Create a Chronological Timeline
- Custom Fields Fundamentals
- Design Patterns
- Displaying Posts with Filters
- Download and Preview Buttons
- Dynamic Banners
- FAQs Page
- Featured Products
- Full Site Editing
- Google Fonts
- Gutenberg
- Hotel Booking
- Latest Products
- Logo Carousel
- MB Views Applications
- Meta Box Builder Applications
- Meta Box Group Applications
- Most Viewed Posts
- Opening Hours
- OTA Website
- Product Page
- Product Variations
- Querying and Showing Posts by Custom Fields
- Recipe
- Restaurant Menus
- SEO Analysis
- Simple LMS
- Speed Up Website
- Taxonomy Thumbnails
- Team Members
- User Profile
- Video Gallery
Is part 3 ever coming?
Yes, it's on the schedule.
Can you please provide an exported dump (.dat file) of the "Bookings" custom fields?
that shall help many to save time
The source code is available on Github. We've just added it recently.
I can create first booking, without adding the function (booking post name AUTO DRAFT).
Then i follow the tutorial to add the functions.
Then create new booking, it will lead to 500 server error.
If i delete that function, it goes normal.
I also tried to import your .dat file on github, it gives me error
Please help.
Anyone still looking for solution, try this:
add_action('save_post', function ($post_id) {
global $wpdb;
if (get_post_type($post_id) == 'booking') {
$title = $post_id;
$where = array('ID' => $post_id);
$wpdb->update($wpdb->posts, array('post_title' => $title), $where);
update_post_meta($post_id, 'order', $post_id);
}
});
Be careful what you write/allow to wpdb as it can lead to SQL injection attack.
Hi, I am trying to follow the tutorial to set up a billboard booking system. I am stuck at the stage of selecting the start and end dates of the reservation. the script does not work. Can you help me ?
$( ".group-reserv .rwmb-field input[name*='[date_de_debut]']" ).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*='[date_de_fin]']").datepicker('option', 'minDate', min_date);
});
$( ".group-reserv .rwmb-field input[name*='[date_de_fin]']" ).on('change', function(){
var total = calculate_total_day($(this).closest('.rwmb-field').siblings().find("input[name*='[date_de_debut]']").val(), $(this).val());
$(this).closest('.rwmb-field').siblings().find("input[name*='[duree_de_campagne]']").val(total);
});
function calculate_total_day(date_de_debut, date_de_fin){
var date1 = new Date(date_de_debut);
var date2 = new Date(date_de_fin);
return (date2.getTime() - date1.getTime()) / (1000 * 3600 * 24);
}