spree_cm_commissioner 2.13.7 → 2.14.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/Gemfile.lock +1 -1
- data/app/assets/javascripts/spree_cm_commissioner/backend.js +0 -1
- data/app/assets/stylesheets/spree_cm_commissioner/backend/calendar.scss +0 -39
- data/app/controllers/concerns/spree/admin/rates_availability_cells.rb +16 -0
- data/app/controllers/concerns/spree/api/v2/product_list_includes_decorator.rb +1 -0
- data/app/controllers/spree/admin/guests_controller.rb +2 -1
- data/app/controllers/spree/admin/integrations_controller.rb +16 -7
- data/app/controllers/spree/admin/inventory_items_controller.rb +33 -62
- data/app/controllers/spree/admin/product_option_types_controller.rb +88 -0
- data/app/controllers/spree/admin/rate_plans_controller.rb +125 -0
- data/app/controllers/spree/admin/rates_availabilities_controller.rb +142 -0
- data/app/controllers/spree/admin/stock_managements_controller.rb +1 -53
- data/app/controllers/spree/api/switch_cm/base_controller.rb +109 -0
- data/app/controllers/spree/api/switch_cm/tokens_controller.rb +68 -0
- data/app/controllers/spree/api/switch_cm/xml/availabilities_controller.rb +39 -0
- data/app/controllers/spree/api/switch_cm/xml/base_controller.rb +37 -0
- data/app/controllers/spree/api/switch_cm/xml/reservation_summaries_controller.rb +28 -0
- data/app/controllers/spree/api/switch_cm/xml/reservations_controller.rb +57 -0
- data/app/controllers/spree/api/switch_cm/xml/room_rate_availabilities_controller.rb +56 -0
- data/app/controllers/spree/api/switch_cm/xml/room_rates_controller.rb +45 -0
- data/app/controllers/spree/api/v2/operator/guests_controller.rb +1 -1
- data/app/controllers/spree/api/v2/storefront/accommodations/variants_controller.rb +31 -5
- data/app/controllers/spree/api/v2/storefront/inventory_items_controller.rb +2 -2
- data/app/controllers/spree/api/v2/tenant/intercity_taxi/draft_orders_controller.rb +0 -1
- data/app/factory/spree_cm_commissioner/order_telegram_message_factory.rb +1 -1
- data/app/finders/spree_cm_commissioner/accommodations/find.rb +10 -8
- data/app/finders/spree_cm_commissioner/accommodations/find_variant.rb +5 -11
- data/app/finders/spree_cm_commissioner/rates_availabilities/find_bookings.rb +116 -0
- data/app/helpers/spree_cm_commissioner/admin/inventory_items_helper.rb +36 -0
- data/app/helpers/spree_cm_commissioner/admin/rates_availabilities_helper.rb +247 -0
- data/app/interactors/spree_cm_commissioner/intercity_taxi_order_creator.rb +0 -1
- data/app/jobs/spree_cm_commissioner/option_type_variants_public_metadata_updater_job.rb +1 -0
- data/app/jobs/spree_cm_commissioner/option_value_variants_public_metadata_updater_job.rb +1 -0
- data/app/jobs/spree_cm_commissioner/product_variants_public_metadata_updater_job.rb +12 -0
- data/app/models/concerns/spree_cm_commissioner/kyc_bitwise.rb +4 -0
- data/app/models/concerns/spree_cm_commissioner/line_item_durationable.rb +1 -10
- data/app/models/concerns/spree_cm_commissioner/line_item_guests_concern.rb +33 -3
- data/app/models/concerns/spree_cm_commissioner/line_item_rate_plannable.rb +55 -0
- data/app/models/concerns/spree_cm_commissioner/line_item_transitable.rb +0 -16
- data/app/models/concerns/spree_cm_commissioner/option_type_attr_type.rb +22 -4
- data/app/models/concerns/spree_cm_commissioner/rate_plan_options_concern.rb +62 -0
- data/app/models/concerns/spree_cm_commissioner/variant_options_concern.rb +12 -0
- data/app/models/concerns/spree_cm_commissioner/variant_rate_plannable.rb +81 -0
- data/app/models/spree_cm_commissioner/address_decorator.rb +2 -0
- data/app/models/spree_cm_commissioner/configuration.rb +0 -6
- data/app/models/spree_cm_commissioner/exports/import_order_guests_csv.rb +2 -1
- data/app/models/spree_cm_commissioner/exports/operator_guest_json_gzip.rb +8 -2
- data/app/models/spree_cm_commissioner/guest.rb +1 -1
- data/app/models/spree_cm_commissioner/integration.rb +15 -0
- data/app/models/spree_cm_commissioner/integrations/switch_cm.rb +96 -0
- data/app/models/spree_cm_commissioner/inventory_item.rb +32 -3
- data/app/models/spree_cm_commissioner/line_item_decorator.rb +7 -7
- data/app/models/spree_cm_commissioner/maintenance_tasks/cache_invalidation.rb +20 -4
- data/app/models/spree_cm_commissioner/maintenance_tasks/event.rb +3 -2
- data/app/models/spree_cm_commissioner/option_type_decorator.rb +2 -1
- data/app/models/spree_cm_commissioner/option_value_decorator.rb +3 -0
- data/app/models/spree_cm_commissioner/option_value_rate_plan.rb +12 -0
- data/app/models/spree_cm_commissioner/order_decorator.rb +1 -0
- data/app/models/spree_cm_commissioner/price_decorator.rb +13 -0
- data/app/models/spree_cm_commissioner/product_decorator.rb +18 -2
- data/app/models/spree_cm_commissioner/product_option_type_decorator.rb +42 -0
- data/app/models/spree_cm_commissioner/rate_plan.rb +139 -0
- data/app/models/spree_cm_commissioner/rate_plan_options.rb +36 -0
- data/app/models/spree_cm_commissioner/rate_restriction.rb +45 -0
- data/app/models/spree_cm_commissioner/taxon_decorator.rb +5 -2
- data/app/models/spree_cm_commissioner/variant_decorator.rb +36 -5
- data/app/models/spree_cm_commissioner/variant_options.rb +3 -2
- data/app/models/spree_cm_commissioner/vendor_decorator.rb +1 -0
- data/app/overrides/spree/admin/option_types/_form/checkbox.html.erb.deface +0 -8
- data/app/overrides/spree/admin/option_types/_form/name.html.erb.deface +1 -1
- data/app/overrides/spree/admin/products/_filters/vendor_filter.html.erb.deface +6 -0
- data/app/overrides/spree/admin/shared/_product_tabs/rates_availabilities.html.erb.deface +8 -0
- data/app/overrides/spree/admin/shared/_product_tabs/stock_managements.html.erb.deface +1 -1
- data/app/overrides/spree/admin/variants/index/discontinued_badge.html.erb.deface +13 -0
- data/app/overrides/spree/admin/variants/index/option_types_empty_state_link.html.erb.deface +3 -0
- data/app/overrides/spree/admin/variants/index/option_types_summary.html.erb.deface +24 -0
- data/app/presenters/spree/variants/visible_options_presenter.rb +34 -1
- data/app/queries/spree_cm_commissioner/multi_leg_trips_query.rb +40 -22
- data/app/queries/spree_cm_commissioner/single_leg_trips_query.rb +28 -14
- data/app/request_schemas/spree_cm_commissioner/intercity_taxi_draft_order_update_schema.rb +0 -1
- data/app/serializers/spree/v2/storefront/line_item_serializer_decorator.rb +2 -0
- data/app/serializers/spree/v2/storefront/option_type_serializer_decorator.rb +1 -1
- data/app/serializers/spree/v2/storefront/product_option_type_serializer.rb +15 -0
- data/app/serializers/spree/v2/storefront/product_serializer_decorator.rb +15 -0
- data/app/serializers/spree/v2/tenant/line_item_serializer.rb +1 -1
- data/app/serializers/spree/v2/tenant/option_type_serializer.rb +1 -1
- data/app/serializers/spree_cm_commissioner/v2/operator/dynamic_field_option_serializer.rb +9 -0
- data/app/serializers/spree_cm_commissioner/v2/operator/dynamic_field_serializer.rb +11 -0
- data/app/serializers/spree_cm_commissioner/v2/operator/guest_dynamic_field_serializer.rb +14 -0
- data/app/serializers/spree_cm_commissioner/v2/operator/guest_serializer.rb +1 -0
- data/app/serializers/spree_cm_commissioner/v2/storefront/accommodation_variant_serializer.rb +44 -0
- data/app/serializers/spree_cm_commissioner/v2/storefront/inventory_item_serializer.rb +4 -4
- data/app/serializers/spree_cm_commissioner/v2/storefront/rate_plan_serializer.rb +16 -0
- data/app/services/spree_cm_commissioner/integrations/book_me_bus_v1/polling/sync_item_availability.rb +1 -1
- data/app/services/spree_cm_commissioner/integrations/larryta/polling/sync_item_availability.rb +1 -1
- data/app/services/spree_cm_commissioner/integrations/switch_cm/inventory/accept_push.rb +111 -0
- data/app/services/spree_cm_commissioner/integrations/switch_cm/inventory/accommodation_lock.rb +43 -0
- data/app/services/spree_cm_commissioner/integrations/switch_cm/inventory/apply_updates.rb +133 -0
- data/app/services/spree_cm_commissioner/integrations/switch_cm/inventory/availability_snapshot.rb +127 -0
- data/app/services/spree_cm_commissioner/integrations/switch_cm/inventory/inventory_update.rb +35 -0
- data/app/services/spree_cm_commissioner/integrations/switch_cm/inventory/pushed_nights.rb +82 -0
- data/app/services/spree_cm_commissioner/integrations/switch_cm/inventory/unseen_room_nights.rb +49 -0
- data/app/services/spree_cm_commissioner/integrations/switch_cm/inventory/validate_updates.rb +153 -0
- data/app/services/spree_cm_commissioner/integrations/switch_cm/reservations/feed.rb +209 -0
- data/app/services/spree_cm_commissioner/integrations/switch_cm/reservations/mark_seen.rb +62 -0
- data/app/services/spree_cm_commissioner/integrations/switch_cm/reservations/reservation.rb +171 -0
- data/app/services/spree_cm_commissioner/integrations/switch_cm/room_catalog.rb +88 -0
- data/app/services/spree_cm_commissioner/integrations/switch_cm/sync_manager.rb +16 -0
- data/app/services/spree_cm_commissioner/integrations/switch_cm/sync_strategies/full_sync_strategy.rb +39 -0
- data/app/services/spree_cm_commissioner/integrations/switch_cm/sync_strategies/incremental_sync_strategy.rb +51 -0
- data/app/services/spree_cm_commissioner/integrations/switch_cm/xml/availability_parser.rb +103 -0
- data/app/services/spree_cm_commissioner/integrations/switch_cm/xml/push_response.rb +45 -0
- data/app/services/spree_cm_commissioner/integrations/switch_cm/xml/reservation_serializer.rb +83 -0
- data/app/services/spree_cm_commissioner/integrations/switch_cm/xml/response_builder.rb +19 -0
- data/app/services/spree_cm_commissioner/integrations/vireak_buntham/polling/sync_item_availability.rb +1 -1
- data/app/services/spree_cm_commissioner/intercity_taxi_order/update.rb +1 -3
- data/app/services/spree_cm_commissioner/inventory_items/bulk_adjust_quantities_by_variant.rb +4 -0
- data/app/services/spree_cm_commissioner/inventory_items/reset.rb +4 -0
- data/app/services/spree_cm_commissioner/rate_plans/save.rb +65 -0
- data/app/services/spree_cm_commissioner/rates_availabilities/bulk_update.rb +225 -0
- data/app/services/spree_cm_commissioner/reschedule_requests/split_line_item.rb +5 -0
- data/app/services/spree_cm_commissioner/room_types/create.rb +87 -0
- data/app/services/spree_cm_commissioner/room_types/update_properties.rb +32 -0
- data/app/services/spree_cm_commissioner/room_types.rb +40 -0
- data/app/services/spree_cm_commissioner/route_prices/update_price_range.rb +3 -1
- data/app/services/spree_cm_commissioner/trips/search.rb +14 -7
- data/app/services/spree_cm_commissioner/trips/update_single_leg.rb +5 -1
- data/app/views/spree/admin/guests/_check_in_status.html.erb +28 -22
- data/app/views/spree/admin/guests/edit.html.erb +29 -26
- data/app/views/spree/admin/integrations/_form.html.erb +4 -0
- data/app/views/spree/admin/integrations/_switch_cm_fields.html.erb +53 -0
- data/app/views/spree/admin/inventory_items/_details.html.erb +56 -0
- data/app/views/spree/admin/product_option_types/index.html.erb +79 -0
- data/app/views/spree/admin/rate_plans/_form.html.erb +134 -0
- data/app/views/spree/admin/rate_plans/_order_form.html.erb +37 -0
- data/app/views/spree/admin/rates_availabilities/_block.html.erb +34 -0
- data/app/views/spree/admin/rates_availabilities/_bookings.html.erb +52 -0
- data/app/views/spree/admin/rates_availabilities/_filters.html.erb +48 -0
- data/app/views/spree/admin/rates_availabilities/_legend.html.erb +27 -0
- data/app/views/spree/admin/rates_availabilities/_list_pages.html.erb +14 -0
- data/app/views/spree/admin/rates_availabilities/_modal_header.html.erb +11 -0
- data/app/views/spree/admin/rates_availabilities/_names.html.erb +62 -0
- data/app/views/spree/admin/rates_availabilities/_script.html.erb +676 -0
- data/app/views/spree/admin/rates_availabilities/_styles.html.erb +159 -0
- data/app/views/spree/admin/rates_availabilities/index.html.erb +112 -0
- data/app/views/spree/admin/shared/sub_menu/_homepage.html.erb +0 -1
- data/app/views/spree/admin/stock_managements/_header.html.erb +0 -14
- data/app/views/spree/admin/stock_managements/_inventory_item_stats.html.erb +28 -0
- data/app/views/spree/admin/stock_managements/index.html.erb +1 -56
- data/config/initializers/spree_permitted_attributes.rb +1 -0
- data/config/locales/en.yml +241 -3
- data/config/locales/km.yml +241 -4
- data/config/routes.rb +36 -17
- data/db/migrate/20260928000000_add_oauth_application_id_to_cm_integrations.rb +5 -0
- data/db/migrate/20260929000000_create_cm_rate_plans.rb +22 -0
- data/db/migrate/20260929000001_add_rate_plan_to_spree_prices.rb +24 -0
- data/db/migrate/20260929000002_add_rate_plan_to_spree_line_items.rb +7 -0
- data/db/migrate/20260929000003_create_cm_option_value_rate_plans.rb +16 -0
- data/db/migrate/20261009000000_create_cm_rate_restrictions.rb +29 -0
- data/db/migrate/20261013000001_add_hidden_and_presentation_to_spree_product_option_types.rb +6 -0
- data/db/migrate/20261013000002_move_hidden_from_spree_option_types_to_product_option_types.rb +36 -0
- data/db/migrate/20261016000001_add_index_to_spree_line_items_number.rb +8 -0
- data/lib/spree_cm_commissioner/booking_dates.rb +43 -0
- data/lib/spree_cm_commissioner/calendar_event.rb +0 -25
- data/lib/spree_cm_commissioner/rate.rb +74 -0
- data/lib/spree_cm_commissioner/rate_closings.rb +48 -0
- data/lib/spree_cm_commissioner/rates_availability_grid.rb +113 -0
- data/lib/spree_cm_commissioner/stay_price.rb +30 -0
- data/lib/spree_cm_commissioner/stay_prices.rb +58 -0
- data/lib/spree_cm_commissioner/test_helper/factories/integration_factory.rb +6 -0
- data/lib/spree_cm_commissioner/test_helper/factories/option_type_factory.rb +6 -0
- data/lib/spree_cm_commissioner/test_helper/factories/option_value_rate_plan_factory.rb +6 -0
- data/lib/spree_cm_commissioner/test_helper/factories/rate_plan_factory.rb +14 -0
- data/lib/spree_cm_commissioner/test_helper/factories/variant_factory.rb +9 -0
- data/lib/spree_cm_commissioner/version.rb +1 -1
- data/lib/spree_cm_commissioner.rb +6 -0
- metadata +94 -32
- data/app/assets/javascripts/spree_cm_commissioner/calendar.js +0 -59
- data/app/controllers/spree/admin/calendars/base_controller.rb +0 -27
- data/app/controllers/spree/admin/calendars/orders_controller.rb +0 -27
- data/app/controllers/spree/admin/homepage_feed_controller.rb +0 -36
- data/app/controllers/spree/admin/metafields_controller.rb +0 -69
- data/app/models/concerns/spree_cm_commissioner/metafield.rb +0 -15
- data/app/models/spree_cm_commissioner/homepage_feed.rb +0 -4
- data/app/overrides/spree/admin/shared/_main_menu/sidebar_calendars.html.erb.deface +0 -5
- data/app/overrides/spree/admin/shared/_product_tabs/metafields.html.erb.deface +0 -15
- data/app/serializers/spree_cm_commissioner/v2/storefront/taxon_include_product_serializer.rb +0 -13
- data/app/serializers/spree_cm_commissioner/v2/storefront/vendor_include_product_serializer.rb +0 -14
- data/app/services/spree_cm_commissioner/feed/taxon_include_product.rb +0 -20
- data/app/services/spree_cm_commissioner/feed/taxon_product.rb +0 -54
- data/app/services/spree_cm_commissioner/feed/vendor_include_product.rb +0 -20
- data/app/services/spree_cm_commissioner/feed/vendor_product.rb +0 -52
- data/app/services/spree_cm_commissioner/homepage_data_loader.rb +0 -112
- data/app/services/spree_cm_commissioner/metafields/product_metadata_service.rb +0 -32
- data/app/views/shared/_calendar.html.erb +0 -27
- data/app/views/shared/calendar/_order_popover.html.erb +0 -18
- data/app/views/shared/calendar/_orders_sidebar.html.erb +0 -41
- data/app/views/spree/admin/calendars/orders/index.html.erb +0 -22
- data/app/views/spree/admin/homepage_feed/_form.html.erb +0 -24
- data/app/views/spree/admin/homepage_feed/edit.html.erb +0 -17
- data/app/views/spree/admin/inventory_items/prices.html.erb +0 -43
- data/app/views/spree/admin/inventory_items/stocks.html.erb +0 -34
- data/app/views/spree/admin/metafields/_private_metafields_form.html.erb +0 -71
- data/app/views/spree/admin/metafields/_public_metafields_form.html.erb +0 -64
- data/app/views/spree/admin/metafields/edit.html.erb +0 -27
- data/app/views/spree/admin/shared/sub_menu/_calendar.html.erb +0 -3
- data/app/views/spree/admin/stock_managements/calendar.html.erb +0 -310
|
@@ -5,7 +5,7 @@ module Spree
|
|
|
5
5
|
|
|
6
6
|
before_action :load_parent
|
|
7
7
|
|
|
8
|
-
|
|
8
|
+
helper SpreeCmCommissioner::Admin::InventoryItemsHelper
|
|
9
9
|
|
|
10
10
|
def load_parent
|
|
11
11
|
@product = Spree::Product.find_by(slug: params[:product_id])
|
|
@@ -40,58 +40,6 @@ module Spree
|
|
|
40
40
|
redirect_back fallback_location: admin_product_stock_managements_path(@product)
|
|
41
41
|
end
|
|
42
42
|
|
|
43
|
-
# GET /products/:slug/stock_managements/calendar?year=&selected_option_value_ids[]=
|
|
44
|
-
def calendar
|
|
45
|
-
@year = params[:year].present? ? params[:year].to_i : Time.zone.today.year
|
|
46
|
-
|
|
47
|
-
from_date = Date.new(@year, 1, 1).beginning_of_year
|
|
48
|
-
to_date = Date.new(@year, 1, 1).end_of_year
|
|
49
|
-
|
|
50
|
-
@variant_kind_option_types = Spree::Variants::OptionTypesFinder.new(variant_ids: @product.variants.ids)
|
|
51
|
-
.execute.select(&:variant?)
|
|
52
|
-
@selected_option_value_ids = Array(params[:selected_option_value_ids]).map(&:to_i)
|
|
53
|
-
|
|
54
|
-
inventory_items = @product.inventory_items.includes(:variant, :prices).where(
|
|
55
|
-
inventory_date: from_date..to_date
|
|
56
|
-
)
|
|
57
|
-
|
|
58
|
-
if @selected_option_value_ids.present?
|
|
59
|
-
variant_ids = SpreeCmCommissioner::Variants::FindByOptionValueIds.new(
|
|
60
|
-
product: @product, option_value_ids: @selected_option_value_ids
|
|
61
|
-
).execute.ids
|
|
62
|
-
inventory_items = inventory_items.where(variant_id: variant_ids)
|
|
63
|
-
@matched_variants_count = variant_ids.size
|
|
64
|
-
else
|
|
65
|
-
@matched_variants_count = @product.variants.count
|
|
66
|
-
end
|
|
67
|
-
|
|
68
|
-
@inventory_items = inventory_items.to_a
|
|
69
|
-
|
|
70
|
-
@cached_inventory_items = ::SpreeCmCommissioner::RedisStock::CachedInventoryItemsBuilder.new(@inventory_items)
|
|
71
|
-
.call
|
|
72
|
-
.index_by(&:inventory_item_id)
|
|
73
|
-
@events = SpreeCmCommissioner::CalendarEvent.from_inventory_items(@inventory_items)
|
|
74
|
-
end
|
|
75
|
-
|
|
76
|
-
def inventory_item_message(inventory_item, cached_inventory_item)
|
|
77
|
-
synced = inventory_item.quantity_available == cached_inventory_item.quantity_available
|
|
78
|
-
|
|
79
|
-
if synced
|
|
80
|
-
"Synced: Quantity available matches in both DB and Redis (#{cached_inventory_item.quantity_available})."
|
|
81
|
-
else
|
|
82
|
-
"Out of sync: Redis shows #{cached_inventory_item.quantity_available} available, which doesn't match the database."
|
|
83
|
-
end
|
|
84
|
-
end
|
|
85
|
-
|
|
86
|
-
def inventory_item_hold_message(inventory_item, cached_inventory_item)
|
|
87
|
-
synced = inventory_item.quantity_on_hold == cached_inventory_item.quantity_on_hold
|
|
88
|
-
if synced
|
|
89
|
-
"Synced: Quantity on hold matches in both DB and Redis (#{cached_inventory_item.quantity_on_hold})."
|
|
90
|
-
else
|
|
91
|
-
"Out of sync: Redis shows #{cached_inventory_item.quantity_on_hold} on hold, which doesn't match the database."
|
|
92
|
-
end
|
|
93
|
-
end
|
|
94
|
-
|
|
95
43
|
private
|
|
96
44
|
|
|
97
45
|
def load_inventories
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
module Spree::Api::SwitchCm
|
|
2
|
+
# The Booking.com-compatible API channel managers (switch.cm) call under /api/cm.
|
|
3
|
+
# It's built on ActionController::API rather than Spree's v2 base, whose rescue handlers
|
|
4
|
+
# answer in JSON:API: a Booking.com connector parses every answer, errors included, as its
|
|
5
|
+
# family's XML, which Xml::BaseController renders.
|
|
6
|
+
#
|
|
7
|
+
# One Doorkeeper app per channel manager, presented as a Bearer token from the token
|
|
8
|
+
# exchange or as HTTP Basic with the app's client id and secret. Booking.com accepted Basic
|
|
9
|
+
# until 31 Dec 2025, and an older connector may still send it.
|
|
10
|
+
class BaseController < ActionController::API
|
|
11
|
+
include ActionController::HttpAuthentication::Basic::ControllerMethods
|
|
12
|
+
|
|
13
|
+
# Bodies are parsed whole and pushes applied in the request, so both are capped; the cap on
|
|
14
|
+
# updates is AcceptPush's.
|
|
15
|
+
MAX_BODY_BYTES = 1.megabyte
|
|
16
|
+
|
|
17
|
+
before_action :authenticate_switch_cm!
|
|
18
|
+
before_action :reject_oversized_body!
|
|
19
|
+
|
|
20
|
+
rescue_from Nokogiri::XML::SyntaxError do
|
|
21
|
+
render_fault(:bad_request, 'Request body is not well-formed XML')
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
private
|
|
25
|
+
|
|
26
|
+
def authenticate_switch_cm!
|
|
27
|
+
@oauth_application = application_from_bearer || application_from_basic
|
|
28
|
+
return if @oauth_application.present? && switch_cm_integrations.exists?
|
|
29
|
+
|
|
30
|
+
render_fault(:unauthorized, 'Authorization Required')
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def reject_oversized_body!
|
|
34
|
+
return if request.raw_post.bytesize <= MAX_BODY_BYTES
|
|
35
|
+
|
|
36
|
+
render_fault(:payload_too_large, "Request body must not exceed #{MAX_BODY_BYTES / 1.megabyte} MB")
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
def application_from_bearer
|
|
40
|
+
doorkeeper_token.application if doorkeeper_token&.accessible?
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
def application_from_basic
|
|
44
|
+
authenticate_with_http_basic do |client_id, client_secret|
|
|
45
|
+
application = Spree::OauthApplication.find_by(uid: client_id)
|
|
46
|
+
application if application&.secret_matches?(client_secret)
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
def switch_cm_integrations
|
|
51
|
+
SpreeCmCommissioner::Integrations::SwitchCm.readable.where(oauth_application_id: @oauth_application.id)
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
# nil unless the requested hotel is one of this channel manager's connected hotels.
|
|
55
|
+
def current_integration
|
|
56
|
+
return @current_integration if defined?(@current_integration)
|
|
57
|
+
|
|
58
|
+
accommodation_id = accommodation_id_from_request
|
|
59
|
+
@current_integration = accommodation_id.present? ? switch_cm_integrations.find_by(vendor_id: accommodation_id) : nil
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
def require_integration!
|
|
63
|
+
render_fault(:forbidden, 'Not authorized for this hotel') if current_integration.nil?
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
# Pushes need an active connection: a paused one is still being set up, and the hotel keeps
|
|
67
|
+
# selling on its own counts until it's activated.
|
|
68
|
+
def require_active_integration!
|
|
69
|
+
return render_fault(:forbidden, 'Not authorized for this hotel') if current_integration.nil?
|
|
70
|
+
|
|
71
|
+
render_fault(:forbidden, 'This hotel is not active on BookMe+ yet') unless current_integration.active?
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
# Pushes may leave out the hotel id (B.XML never sends one); Booking.com derives it from the
|
|
75
|
+
# rooms. A room of another channel manager's hotel resolves to a hotel this app can't access.
|
|
76
|
+
def vendor_id_of_room(room_id)
|
|
77
|
+
Spree::Variant.where(id: room_id.to_i).pick(:vendor_id) if room_id.present?
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
# Reservation pulls take a comma-separated list of hotel ids, or none for all of this
|
|
81
|
+
# channel manager's hotels. Unknown ids come back separately, for a warning each, as at
|
|
82
|
+
# Booking.com; a list with no known hotel at all is refused.
|
|
83
|
+
def requested_integrations(hotel_ids)
|
|
84
|
+
ids = hotel_ids.to_s.split(',').map(&:strip).compact_blank
|
|
85
|
+
scope = switch_cm_integrations.includes(:vendor).order(:vendor_id)
|
|
86
|
+
return [scope.to_a, []] if ids.empty?
|
|
87
|
+
|
|
88
|
+
integrations = scope.where(vendor_id: ids).to_a
|
|
89
|
+
[integrations, ids - integrations.map { |integration| integration.vendor_id.to_s }]
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
def accommodation_id_from_request
|
|
93
|
+
raise NotImplementedError, "#{self.class.name} must implement accommodation_id_from_request"
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
def render_fault(_status, _text, code: nil)
|
|
97
|
+
raise NotImplementedError, "#{self.class.name} must implement render_fault"
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
# Rails parses neither XML nor text/xml bodies into params, so both families read the raw body.
|
|
101
|
+
def request_document
|
|
102
|
+
@request_document ||= Nokogiri::XML(request.raw_post) { |config| config.strict.nonet }.tap(&:remove_namespaces!)
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
def ruid
|
|
106
|
+
request.request_id
|
|
107
|
+
end
|
|
108
|
+
end
|
|
109
|
+
end
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
module Spree::Api::SwitchCm
|
|
2
|
+
class TokensController < BaseController
|
|
3
|
+
# Booking.com's tokens also last an hour; connectors refresh on that schedule.
|
|
4
|
+
TOKEN_LIFETIME = 1.hour
|
|
5
|
+
|
|
6
|
+
skip_before_action :authenticate_switch_cm!
|
|
7
|
+
|
|
8
|
+
# POST /api/cm/token-based-authentication/exchange
|
|
9
|
+
# Client id and secret in (JSON or XML), { jwt, ruid } out, then `Authorization: Bearer` on every
|
|
10
|
+
# other call. The value is a Doorkeeper access token rather than a signed JWT, so a connector
|
|
11
|
+
# that only forwards it works but one that decodes it won't.
|
|
12
|
+
def create
|
|
13
|
+
client_id, client_secret = credentials
|
|
14
|
+
return render_fault(:bad_request, 'client_id and client_secret are required') if client_id.blank? || client_secret.blank?
|
|
15
|
+
|
|
16
|
+
@oauth_application = Spree::OauthApplication.find_by(uid: client_id)
|
|
17
|
+
unless @oauth_application&.secret_matches?(client_secret) && switch_cm_integrations.exists?
|
|
18
|
+
return render_fault(:unauthorized, 'Invalid client credentials')
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
render_token(issue_token.plaintext_token)
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
private
|
|
25
|
+
|
|
26
|
+
def issue_token
|
|
27
|
+
Spree::OauthAccessToken.create_for(
|
|
28
|
+
application: @oauth_application,
|
|
29
|
+
resource_owner: nil,
|
|
30
|
+
scopes: '',
|
|
31
|
+
expires_in: TOKEN_LIFETIME.to_i,
|
|
32
|
+
use_refresh_token: false
|
|
33
|
+
)
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def credentials
|
|
37
|
+
return [params[:client_id], params[:client_secret]] unless xml_request?
|
|
38
|
+
|
|
39
|
+
%w[client_id client_secret].map { |name| request_document.at_xpath("/request/#{name}")&.text&.strip }
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
def xml_request?
|
|
43
|
+
request.content_mime_type&.symbol == :xml
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
def render_token(token)
|
|
47
|
+
return render json: { jwt: token, ruid: ruid } unless xml_request?
|
|
48
|
+
|
|
49
|
+
document = SpreeCmCommissioner::Integrations::SwitchCm::Xml::ResponseBuilder.build('response', ruid: ruid) do |xml|
|
|
50
|
+
xml.jwt token
|
|
51
|
+
xml.ruid ruid
|
|
52
|
+
end
|
|
53
|
+
render xml: document
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
# override
|
|
57
|
+
def render_fault(status, text, code: nil)
|
|
58
|
+
code ||= Rack::Utils.status_code(status)
|
|
59
|
+
|
|
60
|
+
if xml_request?
|
|
61
|
+
render xml: SpreeCmCommissioner::Integrations::SwitchCm::Xml::ResponseBuilder.fault('response', code: code, text: text, ruid: ruid),
|
|
62
|
+
status: status
|
|
63
|
+
else
|
|
64
|
+
render json: { errors: [{ code: code, message: text }], ruid: ruid }, status: status
|
|
65
|
+
end
|
|
66
|
+
end
|
|
67
|
+
end
|
|
68
|
+
end
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
module Spree::Api::SwitchCm
|
|
2
|
+
module Xml
|
|
3
|
+
class AvailabilitiesController < BaseController
|
|
4
|
+
before_action :require_active_integration!
|
|
5
|
+
|
|
6
|
+
# POST /api/cm/hotels/xml/availability
|
|
7
|
+
# Rooms to sell, prices and closed in one message, validated and applied in the request
|
|
8
|
+
# (Inventory::AcceptPush); the answer is the real outcome.
|
|
9
|
+
def create
|
|
10
|
+
result = SpreeCmCommissioner::Integrations::SwitchCm::Inventory::AcceptPush.call(
|
|
11
|
+
integration: current_integration,
|
|
12
|
+
message: 'availability',
|
|
13
|
+
updates: parser.updates,
|
|
14
|
+
request_id: ruid
|
|
15
|
+
)
|
|
16
|
+
|
|
17
|
+
push_response = SpreeCmCommissioner::Integrations::SwitchCm::Xml::PushResponse.new(result: result.value, ruid: ruid)
|
|
18
|
+
render xml: push_response.to_xml, status: push_response.http_status
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
private
|
|
22
|
+
|
|
23
|
+
# override
|
|
24
|
+
def response_root
|
|
25
|
+
'availability'
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def parser
|
|
29
|
+
@parser ||= SpreeCmCommissioner::Integrations::SwitchCm::Xml::AvailabilityParser.new(request_document)
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
# B.XML pushes carry no hotel id; the rooms name it.
|
|
33
|
+
# override
|
|
34
|
+
def accommodation_id_from_request
|
|
35
|
+
vendor_id_of_room(parser.first_room_id)
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
end
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
module Spree::Api::SwitchCm
|
|
2
|
+
module Xml
|
|
3
|
+
# Booking.com's B.XML family (/hotels/xml/...): a request that fails as a whole answers with
|
|
4
|
+
# a <fault code string/> under the call's root element. Requests carry their parameters
|
|
5
|
+
# in a <request> body.
|
|
6
|
+
class BaseController < Spree::Api::SwitchCm::BaseController
|
|
7
|
+
private
|
|
8
|
+
|
|
9
|
+
def response_root
|
|
10
|
+
raise NotImplementedError, "#{self.class.name} must implement response_root"
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
# override
|
|
14
|
+
def render_fault(status, text, code: nil)
|
|
15
|
+
code ||= Rack::Utils.status_code(status)
|
|
16
|
+
render xml: SpreeCmCommissioner::Integrations::SwitchCm::Xml::ResponseBuilder.fault(response_root, code: code, text: text, ruid: ruid),
|
|
17
|
+
status: status
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def render_document(&block)
|
|
21
|
+
render xml: SpreeCmCommissioner::Integrations::SwitchCm::Xml::ResponseBuilder.build(response_root, ruid: ruid, &block)
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
# override
|
|
25
|
+
def accommodation_id_from_request
|
|
26
|
+
request_value('hotel_id')
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
# A parameter from the <request> body, falling back to the query string.
|
|
30
|
+
def request_value(name)
|
|
31
|
+
return params[name] if request.raw_post.blank?
|
|
32
|
+
|
|
33
|
+
request_document.at_xpath("/request/#{name}")&.text&.strip.presence || params[name]
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
end
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
module Spree::Api::SwitchCm
|
|
2
|
+
module Xml
|
|
3
|
+
class ReservationSummariesController < BaseController
|
|
4
|
+
before_action :require_integration!
|
|
5
|
+
|
|
6
|
+
# POST /api/cm/hotels/xml/reservationssummary with <request><hotel_id/></request>
|
|
7
|
+
# The hotel's confirmed stays checking out from tomorrow on, however long ago they were
|
|
8
|
+
# booked. The channel manager imports these while setting the hotel up, so they count against
|
|
9
|
+
# its availability before its first push. Read-only: nothing is marked seen.
|
|
10
|
+
def create
|
|
11
|
+
reservations = SpreeCmCommissioner::Integrations::SwitchCm::Reservations::Feed.new([current_integration]).summary
|
|
12
|
+
|
|
13
|
+
render_document do |xml|
|
|
14
|
+
reservations.each do |reservation|
|
|
15
|
+
SpreeCmCommissioner::Integrations::SwitchCm::Xml::ReservationSerializer.new(reservation, summary: true).build(xml)
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
private
|
|
21
|
+
|
|
22
|
+
# override
|
|
23
|
+
def response_root
|
|
24
|
+
'reservations'
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
end
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
module Spree::Api::SwitchCm
|
|
2
|
+
module Xml
|
|
3
|
+
class ReservationsController < BaseController
|
|
4
|
+
# POST /api/cm/hotels/xml/reservations with <request><hotel_id/><last_change/><limit/><id/></request>
|
|
5
|
+
# New, modified and cancelled reservations in one stream. B.XML has no acknowledgement, so returning a
|
|
6
|
+
# reservation is what marks it seen; `last_change` re-reads recent ones regardless.
|
|
7
|
+
def create
|
|
8
|
+
integrations, = requested_integrations(request_value('hotel_id') || request_value('hotel_ids'))
|
|
9
|
+
return render_fault(:forbidden, 'Not authorized for these hotels') if integrations.empty?
|
|
10
|
+
|
|
11
|
+
reservations = SpreeCmCommissioner::Integrations::SwitchCm::Reservations::Feed.new(integrations).changed_reservations(
|
|
12
|
+
id: request_value('id'),
|
|
13
|
+
last_change: SpreeCmCommissioner::Integrations::SwitchCm::Reservations::Feed.parse_last_change(request_value('last_change')),
|
|
14
|
+
limit: request_value('limit')
|
|
15
|
+
)
|
|
16
|
+
|
|
17
|
+
document = SpreeCmCommissioner::Integrations::SwitchCm::Xml::ResponseBuilder.build(response_root, ruid: ruid) do |xml|
|
|
18
|
+
reservations.each { |reservation| SpreeCmCommissioner::Integrations::SwitchCm::Xml::ReservationSerializer.new(reservation).build(xml) }
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
# Marked only once the document is built. One that fails to mark is simply returned
|
|
22
|
+
# again next time, which is harmless: its status is the same.
|
|
23
|
+
failed = mark_seen(reservations)
|
|
24
|
+
log_mark_seen_failures(failed) if failed.any?
|
|
25
|
+
|
|
26
|
+
render xml: document
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
private
|
|
30
|
+
|
|
31
|
+
# override
|
|
32
|
+
def response_root
|
|
33
|
+
'reservations'
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def mark_seen(reservations)
|
|
37
|
+
reservations.each_with_object([]) do |reservation, failed|
|
|
38
|
+
SpreeCmCommissioner::Integrations::SwitchCm::Reservations::MarkSeen.call(
|
|
39
|
+
integration: reservation.integration, order: reservation.order, status: reservation.status,
|
|
40
|
+
modified_at: reservation.modified_at
|
|
41
|
+
)
|
|
42
|
+
rescue StandardError => e
|
|
43
|
+
failed << {
|
|
44
|
+
order_id: reservation.id,
|
|
45
|
+
integration_id: reservation.integration.id,
|
|
46
|
+
error_class: e.class.name,
|
|
47
|
+
error_message: e.message
|
|
48
|
+
}
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
def log_mark_seen_failures(failed)
|
|
53
|
+
CmAppLogger.error(label: "#{self.class.name}#mark_seen failed", data: { ruid: ruid, failed: failed })
|
|
54
|
+
end
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
end
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
module Spree::Api::SwitchCm
|
|
2
|
+
module Xml
|
|
3
|
+
class RoomRateAvailabilitiesController < BaseController
|
|
4
|
+
DAYS_RANGE = (1..31)
|
|
5
|
+
|
|
6
|
+
before_action :require_integration!
|
|
7
|
+
|
|
8
|
+
# POST /api/cm/hotels/xml/roomrateavailability with <request><hotel_id/><start_date/><number_of_days/><room_id/></request>
|
|
9
|
+
# What we hold per room and night, so the channel manager can check its pushes landed.
|
|
10
|
+
# number_of_days is 1-31, 31 by default, as at Booking.com.
|
|
11
|
+
def create
|
|
12
|
+
start_date = parse_date(request_value('start_date')) || Time.zone.today
|
|
13
|
+
days = (request_value('number_of_days').presence || DAYS_RANGE.max).to_i
|
|
14
|
+
return render_fault(:bad_request, "number_of_days must be between #{DAYS_RANGE.min} and #{DAYS_RANGE.max}") unless DAYS_RANGE.cover?(days)
|
|
15
|
+
|
|
16
|
+
rows = SpreeCmCommissioner::Integrations::SwitchCm::Inventory::AvailabilitySnapshot.new(
|
|
17
|
+
integration: current_integration,
|
|
18
|
+
dates: (start_date...(start_date + days)).to_a,
|
|
19
|
+
room_id: request_value('room_id')
|
|
20
|
+
).rows
|
|
21
|
+
|
|
22
|
+
render_document { |xml| rows.each { |row| build_row(xml, row) } }
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
private
|
|
26
|
+
|
|
27
|
+
# override
|
|
28
|
+
def response_root
|
|
29
|
+
'result'
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def build_row(xml, row)
|
|
33
|
+
xml.roomrate(
|
|
34
|
+
{
|
|
35
|
+
date: row.date.iso8601,
|
|
36
|
+
room_id: row.room_id,
|
|
37
|
+
rate_id: row.rate_id,
|
|
38
|
+
rooms_to_sell: row.rooms_to_sell,
|
|
39
|
+
booked: row.booked,
|
|
40
|
+
cancelled: row.cancelled,
|
|
41
|
+
closed: row.closed ? 1 : 0,
|
|
42
|
+
price: row.price && format('%.2f', row.price),
|
|
43
|
+
currency: row.currency,
|
|
44
|
+
pushed_at: row.pushed_at&.utc&.iso8601
|
|
45
|
+
}.compact
|
|
46
|
+
)
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
def parse_date(value)
|
|
50
|
+
Date.iso8601(value) if value.present?
|
|
51
|
+
rescue Date::Error
|
|
52
|
+
nil
|
|
53
|
+
end
|
|
54
|
+
end
|
|
55
|
+
end
|
|
56
|
+
end
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
module Spree::Api::SwitchCm
|
|
2
|
+
module Xml
|
|
3
|
+
class RoomRatesController < BaseController
|
|
4
|
+
before_action :require_integration!
|
|
5
|
+
|
|
6
|
+
# POST /api/cm/hotels/xml/roomrates with <request><hotel_id/></request>
|
|
7
|
+
# The hotel's rooms, each with its rates, which the channel manager maps to its own
|
|
8
|
+
# ("Refresh Room" in switch.cm). A derived rate plan is a child rate of the room's base rate.
|
|
9
|
+
def create
|
|
10
|
+
vendor = current_integration.vendor
|
|
11
|
+
rooms = SpreeCmCommissioner::Integrations::SwitchCm::RoomCatalog.new(vendor).rooms
|
|
12
|
+
|
|
13
|
+
render_document do |xml|
|
|
14
|
+
rooms.each do |room|
|
|
15
|
+
xml.room(id: room.id, hotel_id: vendor.id, hotel_name: vendor.name, room_name: room.name, max_children: room.max_children) do
|
|
16
|
+
xml.rates { room.rates.each { |rate| build_rate(xml, room, rate) } }
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
private
|
|
23
|
+
|
|
24
|
+
def build_rate(xml, room, rate)
|
|
25
|
+
attributes = { id: rate.id, rate_name: rate.name, max_persons: room.max_persons, is_child_rate: rate.child? ? 1 : 0, readonly: 0 }
|
|
26
|
+
return xml.rate(attributes) unless rate.child?
|
|
27
|
+
|
|
28
|
+
# Restrictions other than closing aren't stored, so the child rate follows only its parent's price and closings.
|
|
29
|
+
xml.rate(attributes) do
|
|
30
|
+
xml.rate_relation(
|
|
31
|
+
{
|
|
32
|
+
parent_rate_id: rate.parent_id, percentage: rate.percentage&.to_f, additional: rate.additional&.to_f,
|
|
33
|
+
follows_price: 1, follows_closed: 3, follows_restrictions: 0
|
|
34
|
+
}.compact
|
|
35
|
+
)
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
# override
|
|
40
|
+
def response_root
|
|
41
|
+
'rooms'
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
end
|
|
@@ -7,13 +7,39 @@ module Spree
|
|
|
7
7
|
|
|
8
8
|
# override
|
|
9
9
|
def collection
|
|
10
|
-
|
|
10
|
+
# Load in batches what every room's JSON reads, instead of per room: the storefront variant's base price
|
|
11
|
+
# (Variant#price_in), relationships and stock ids, and the rate plans and terms its rates are built from.
|
|
12
|
+
@collection ||= finder.execute.includes(
|
|
13
|
+
:product, :images, :option_values, :stock_items, :stock_locations, :default_price,
|
|
14
|
+
active_rate_plans: { option_values: :option_type }
|
|
15
|
+
)
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def finder
|
|
19
|
+
@finder ||= SpreeCmCommissioner::Accommodations::FindVariant.new(
|
|
11
20
|
from_date: params[:from_date]&.to_date,
|
|
12
21
|
to_date: params[:to_date]&.to_date,
|
|
13
|
-
vendor_id: params[:accommodation_id],
|
|
22
|
+
vendor_id: Spree::Vendor.by_vendor_id!(params[:accommodation_id]).id,
|
|
14
23
|
number_of_adults: params[:adult].to_i,
|
|
15
24
|
number_of_kids: params[:children].to_i
|
|
16
|
-
)
|
|
25
|
+
)
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
# override: price every room's rates from one batch of prices, instead of a query per room. Runs only
|
|
29
|
+
# when the response isn't cached, and the batch loads only when a room's JSON isn't cached either.
|
|
30
|
+
def serialize_collection(collection)
|
|
31
|
+
Spree::Variant.preload_stay_prices(
|
|
32
|
+
collection,
|
|
33
|
+
dates: finder.dates,
|
|
34
|
+
currency: current_currency
|
|
35
|
+
)
|
|
36
|
+
|
|
37
|
+
super
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
# override: rates are priced for the nights searched.
|
|
41
|
+
def serializer_params
|
|
42
|
+
super.merge(dates: finder.dates)
|
|
17
43
|
end
|
|
18
44
|
|
|
19
45
|
# override
|
|
@@ -23,12 +49,12 @@ module Spree
|
|
|
23
49
|
|
|
24
50
|
# override
|
|
25
51
|
def resource_serializer
|
|
26
|
-
|
|
52
|
+
SpreeCmCommissioner::V2::Storefront::AccommodationVariantSerializer
|
|
27
53
|
end
|
|
28
54
|
|
|
29
55
|
# override
|
|
30
56
|
def collection_serializer
|
|
31
|
-
|
|
57
|
+
SpreeCmCommissioner::V2::Storefront::AccommodationVariantSerializer
|
|
32
58
|
end
|
|
33
59
|
|
|
34
60
|
# override
|
|
@@ -21,9 +21,9 @@ module Spree
|
|
|
21
21
|
return @collection if defined?(@collection)
|
|
22
22
|
|
|
23
23
|
scope = if include_reserved_blocks?
|
|
24
|
-
SpreeCmCommissioner::InventoryItem.includes(:
|
|
24
|
+
SpreeCmCommissioner::InventoryItem.includes(:variant_prices, :reserved_blocks)
|
|
25
25
|
else
|
|
26
|
-
SpreeCmCommissioner::InventoryItem.includes(:
|
|
26
|
+
SpreeCmCommissioner::InventoryItem.includes(:variant_prices)
|
|
27
27
|
end
|
|
28
28
|
|
|
29
29
|
@collection = scope.where(variant_id: params[:variant_ids], inventory_date: params[:inventory_dates])
|
|
@@ -31,7 +31,6 @@ module Spree
|
|
|
31
31
|
result = SpreeCmCommissioner::IntercityTaxiOrder::Update.call(
|
|
32
32
|
order: spree_current_order,
|
|
33
33
|
remark: params[:remark],
|
|
34
|
-
passenger_count: params[:passenger_count],
|
|
35
34
|
pickup_map_place_attributes: params[:pickup_map_place_attributes]&.permit(
|
|
36
35
|
:place_name,
|
|
37
36
|
:lat,
|
|
@@ -165,7 +165,7 @@ module SpreeCmCommissioner
|
|
|
165
165
|
text << bold('🚗 Trip Details')
|
|
166
166
|
text << "Route: #{route_text}" if route_text.present?
|
|
167
167
|
text << "Vehicle: #{vehicle_text}" if vehicle_text.present?
|
|
168
|
-
text << "Passengers: #{line_item.
|
|
168
|
+
text << "Passengers: #{line_item.number_of_guests}"
|
|
169
169
|
text << "Departure: #{departure_text}" if departure_text.present?
|
|
170
170
|
text << seat_labels_text(line_item) if seat_labels_text(line_item).present?
|
|
171
171
|
|
|
@@ -13,11 +13,8 @@ module SpreeCmCommissioner
|
|
|
13
13
|
def execute
|
|
14
14
|
scope
|
|
15
15
|
.where(default_state_id: state_id)
|
|
16
|
-
.
|
|
17
|
-
.
|
|
18
|
-
.where('COALESCE(CAST(spree_variants.public_metadata->\'cm_options\'->>\'number-of-adults\' AS INTEGER), 0) +
|
|
19
|
-
COALESCE(CAST(spree_variants.public_metadata->\'cm_options\'->>\'number-of-kids\' AS INTEGER), 0) >= ?', number_of_guests
|
|
20
|
-
)
|
|
16
|
+
.merge(bookable_variants)
|
|
17
|
+
.merge(Spree::Variant.with_guest_capacity(number_of_guests))
|
|
21
18
|
.distinct
|
|
22
19
|
end
|
|
23
20
|
|
|
@@ -26,12 +23,17 @@ module SpreeCmCommissioner
|
|
|
26
23
|
def scope
|
|
27
24
|
Spree::Vendor
|
|
28
25
|
.joins(products: :variants)
|
|
29
|
-
.joins('LEFT OUTER JOIN cm_inventory_items ON cm_inventory_items.variant_id = spree_variants.id')
|
|
30
26
|
.where(primary_product_type: :accommodation, state: :active)
|
|
31
27
|
end
|
|
32
28
|
|
|
33
|
-
def
|
|
34
|
-
from_date
|
|
29
|
+
def dates
|
|
30
|
+
@dates ||= SpreeCmCommissioner::BookingDates.nights(check_in: from_date, check_out: to_date)
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
# Same rule as the rooms endpoint: a hotel is listed only if one of its rooms is bookable every night
|
|
34
|
+
# with a rate open, so a listed hotel always has a room to show. Rooms that don't track inventory always count.
|
|
35
|
+
def bookable_variants
|
|
36
|
+
Spree::Variant.where(track_inventory: false).or(Spree::Variant.bookable_on_every_date(dates)).with_open_rate_on_every_date(dates)
|
|
35
37
|
end
|
|
36
38
|
end
|
|
37
39
|
end
|