Add additional services system (v0.5.0)
All checks were successful
Create Release Package / build-release (push) Successful in 1m0s

- Service CPT with pricing types: Included, Per Booking, Per Night
- ServiceCategory taxonomy with default categories
- Booking-services integration with service selector
- Real-time price calculation based on nights and quantity
- Services total and grand total display in booking admin

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
2026-01-31 15:19:56 +01:00
parent aab3a4d1aa
commit 05f24fdec7
10 changed files with 1684 additions and 46 deletions

View File

@@ -1088,3 +1088,208 @@
color: #646970;
margin-top: 5px;
}
/* ==========================================================================
Services Styles
========================================================================== */
/* Services List in Admin */
.column-pricing_type,
.column-service_status {
width: 120px;
}
/* Service Status Badges */
.bnb-service-status {
display: inline-block;
padding: 3px 8px;
border-radius: 3px;
font-size: 11px;
font-weight: 600;
text-transform: uppercase;
}
.bnb-service-status-active {
background: #d4edda;
color: #155724;
}
.bnb-service-status-inactive {
background: #f6f7f7;
color: #646970;
}
.bnb-service-included {
color: #00a32a;
font-weight: 600;
}
/* Service Pricing Meta Box */
.bnb-service-pricing-type fieldset label {
display: block;
margin-bottom: 15px;
}
.bnb-service-pricing-type fieldset label input {
margin-right: 8px;
}
.bnb-service-pricing-type fieldset p.description {
margin-left: 24px;
margin-top: 4px;
}
/* ==========================================================================
Booking Services Selector
========================================================================== */
.bnb-services-selector {
padding: 10px 0;
}
.bnb-services-list {
margin: 15px 0;
}
.bnb-service-item {
display: flex;
justify-content: space-between;
align-items: center;
padding: 12px 15px;
background: #f6f7f7;
border: 1px solid #c3c4c7;
border-radius: 4px;
margin-bottom: 8px;
transition: background 0.15s ease, border-color 0.15s ease;
}
.bnb-service-item:hover {
background: #f0f6fc;
}
.bnb-service-item.selected {
background: #d4edda;
border-color: #c3e6cb;
}
.bnb-service-checkbox {
display: flex;
align-items: center;
gap: 10px;
cursor: pointer;
flex: 1;
}
.bnb-service-checkbox input[type="checkbox"] {
margin: 0;
}
.bnb-service-name {
font-weight: 600;
color: #1d2327;
}
.bnb-service-details {
display: flex;
align-items: center;
gap: 15px;
flex-shrink: 0;
}
.bnb-service-price-label {
color: #135e96;
font-weight: 500;
}
.bnb-service-included-badge {
display: inline-block;
padding: 2px 8px;
background: #d4edda;
color: #155724;
border-radius: 3px;
font-size: 11px;
font-weight: 600;
text-transform: uppercase;
}
.bnb-service-quantity {
display: flex;
align-items: center;
gap: 5px;
}
.bnb-service-quantity label {
display: flex;
align-items: center;
gap: 5px;
font-size: 12px;
color: #646970;
}
.bnb-service-qty-input {
width: 50px !important;
}
.bnb-service-line-total {
color: #1d2327;
}
.bnb-service-total-value {
color: #135e96;
}
/* Services Total */
.bnb-services-total {
display: flex;
justify-content: flex-end;
align-items: center;
gap: 15px;
padding: 15px;
background: #f0f6fc;
border: 1px solid #c3c4c7;
border-radius: 4px;
margin-top: 15px;
}
.bnb-services-total strong {
color: #1d2327;
}
#bnb-services-total-amount {
font-size: 16px;
font-weight: 600;
color: #135e96;
}
/* No Services Message */
.bnb-no-services-message {
padding: 20px;
text-align: center;
color: #646970;
font-style: italic;
}
.bnb-no-services-message a {
margin-left: 5px;
}
/* Booking Pricing with Services */
.bnb-booking-services-summary {
padding: 8px 12px;
background: #d4edda;
border: 1px solid #c3e6cb;
border-radius: 4px;
color: #155724;
}
.bnb-booking-grand-total {
padding: 12px 15px;
background: #f0f6fc;
border: 1px solid #c3c4c7;
border-radius: 4px;
}
.bnb-booking-grand-total strong {
font-size: 18px;
color: #135e96;
}

View File

@@ -755,6 +755,185 @@
});
}
/**
* Initialize service pricing type toggle.
*/
function initServicePricing() {
var $pricingTypeInputs = $('input[name="bnb_service_pricing_type"]');
var $priceRow = $('#bnb-service-price-row');
var $priceSuffix = $('#bnb-service-price-suffix');
var $priceDescription = $('#bnb-service-price-description');
if (!$pricingTypeInputs.length) {
return;
}
$pricingTypeInputs.on('change', function() {
var pricingType = $('input[name="bnb_service_pricing_type"]:checked').val();
if (pricingType === 'included') {
$priceRow.hide();
} else {
$priceRow.show();
if (pricingType === 'per_night') {
$priceSuffix.text(' / ' + (wpBnbAdmin.i18n.night || 'night'));
$priceDescription.text(wpBnbAdmin.i18n.perNightDescription || 'This price will be charged per night of the stay.');
} else {
$priceSuffix.text('');
$priceDescription.text(wpBnbAdmin.i18n.perBookingDescription || 'This price will be charged once for the booking.');
}
}
});
}
/**
* Initialize booking services selector.
*/
function initBookingServices() {
var $servicesSelector = $('.bnb-services-selector');
var $servicesList = $servicesSelector.find('.bnb-services-list');
var $totalDisplay = $('#bnb-services-total-amount');
if (!$servicesSelector.length) {
return;
}
/**
* Get current number of nights from booking form.
*
* @return {number} Number of nights.
*/
function getNights() {
var checkIn = $('#bnb_booking_check_in').val();
var checkOut = $('#bnb_booking_check_out').val();
if (checkIn && checkOut) {
var startDate = new Date(checkIn);
var endDate = new Date(checkOut);
var nights = Math.ceil((endDate - startDate) / (1000 * 60 * 60 * 24));
return Math.max(1, nights);
}
return parseInt($servicesSelector.data('nights'), 10) || 1;
}
/**
* Calculate service line total.
*
* @param {jQuery} $item Service item element.
* @param {number} nights Number of nights.
* @return {number} Calculated price.
*/
function calculateServiceTotal($item, nights) {
var price = parseFloat($item.data('price')) || 0;
var pricingType = $item.data('pricing-type');
var quantity = parseInt($item.find('.bnb-service-qty-input').val(), 10) || 1;
if (pricingType === 'included') {
return 0;
}
if (pricingType === 'per_night') {
return price * quantity * nights;
}
return price * quantity;
}
/**
* Update service line total display.
*
* @param {jQuery} $item Service item element.
*/
function updateServiceLineTotal($item) {
var nights = getNights();
var total = calculateServiceTotal($item, nights);
var $lineTotal = $item.find('.bnb-service-line-total');
var $totalValue = $item.find('.bnb-service-total-value');
var isSelected = $item.find('input[type="checkbox"]').is(':checked');
var pricingType = $item.data('pricing-type');
if (isSelected && pricingType !== 'included' && total > 0) {
$totalValue.text(formatPrice(total));
$lineTotal.show();
} else {
$lineTotal.hide();
}
}
/**
* Update total services amount.
*/
function updateServicesTotal() {
var nights = getNights();
var total = 0;
$servicesList.find('.bnb-service-item').each(function() {
var $item = $(this);
var isSelected = $item.find('input[type="checkbox"]').is(':checked');
if (isSelected) {
total += calculateServiceTotal($item, nights);
}
});
$totalDisplay.text(formatPrice(total));
}
/**
* Format price for display (simple formatting).
*
* @param {number} price Price value.
* @return {string} Formatted price.
*/
function formatPrice(price) {
return parseFloat(price).toFixed(2);
}
// Handle service checkbox change.
$servicesList.on('change', 'input[type="checkbox"]', function() {
var $item = $(this).closest('.bnb-service-item');
var isSelected = $(this).is(':checked');
$item.toggleClass('selected', isSelected);
// Show/hide quantity input.
var $quantity = $item.find('.bnb-service-quantity');
if ($quantity.length) {
$quantity.toggle(isSelected);
}
updateServiceLineTotal($item);
updateServicesTotal();
});
// Handle quantity change.
$servicesList.on('change input', '.bnb-service-qty-input', function() {
var $item = $(this).closest('.bnb-service-item');
var maxQty = parseInt($item.data('max-quantity'), 10) || 1;
var value = parseInt($(this).val(), 10) || 1;
// Enforce min/max.
value = Math.max(1, Math.min(value, maxQty));
$(this).val(value);
updateServiceLineTotal($item);
updateServicesTotal();
});
// Update when booking dates change.
$('#bnb_booking_check_in, #bnb_booking_check_out').on('change', function() {
$servicesList.find('.bnb-service-item.selected').each(function() {
updateServiceLineTotal($(this));
});
updateServicesTotal();
});
// Initial calculation.
updateServicesTotal();
}
// Initialize on document ready.
$(document).ready(function() {
initLicenseManagement();
@@ -765,6 +944,8 @@
initBookingForm();
initCalendarPage();
initGuestSearch();
initServicePricing();
initBookingServices();
});
})(jQuery);