black friday sale

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.

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;
Name of the post type: Booking
Name of the post type: Booking
unchoose default fields and just tick the Revision to allow tracking changes
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
·  Email email
·  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.

Allows the Field Group display in the post type is Booking

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 number field for the Order Number

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.

Create Custom CSS Class for a group

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.

Limit the maximum number of children

Set a condition to decide when the age field displays

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:

Add Extra Functions to the Custom Fields

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:

Post ID is set to be title and permalink of the order
Post ID is set to be title and permalink of the order
The order number is auto-generated by post ID
The order number is auto-generated by post ID

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.

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:
View the custom CSS class of two buttons
View the custom CSS class of two buttons

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.

Automatically Display the Corresponding Price of the Booking Room

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:

The Auto-displayed Price

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 nights of stay
Automatically calculate the total nights of stay

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:

Total Amount (Grand Total) run automatically

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);
}
});
view raw

booking.js

hosted with ❤ by GitHub

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!

7 thoughts on “How to Build a Hotel Booking Website Using Meta Box - P2 - Booking Page in Backend

  1. Can you please provide an exported dump (.dat file) of the "Bookings" custom fields?

    that shall help many to save time

  2. 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.

    1. 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.

  3. 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);
    }

Leave a Reply

Your email address will not be published. Required fields are marked *