spree_cm_commissioner 2.13.2 → 2.13.3.pre.pre1
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/.env.example +1 -0
- data/Gemfile.lock +1 -1
- data/app/controllers/concerns/spree_cm_commissioner/operator/event_authorization.rb +19 -0
- data/app/controllers/spree/admin/homepage_section_controller.rb +32 -8
- data/app/controllers/spree/admin/integrations_controller.rb +8 -8
- data/app/controllers/spree/admin/inventory_items_controller.rb +12 -0
- data/app/controllers/spree/admin/notification_users_controller.rb +39 -7
- data/app/controllers/spree/admin/service_fee_rules_controller.rb +83 -0
- data/app/controllers/spree/admin/stock_managements_controller.rb +5 -5
- data/app/controllers/spree/api/switch_cm/base_controller.rb +98 -0
- data/app/controllers/spree/api/switch_cm/ota/avail_notifs_controller.rb +17 -0
- data/app/controllers/spree/api/switch_cm/ota/base_controller.rb +27 -0
- data/app/controllers/spree/api/switch_cm/ota/product_notifs_controller.rb +44 -0
- data/app/controllers/spree/api/switch_cm/ota/pushes_controller.rb +46 -0
- data/app/controllers/spree/api/switch_cm/ota/rate_amount_notifs_controller.rb +17 -0
- data/app/controllers/spree/api/switch_cm/ota/res_modify_notifs_controller.rb +31 -0
- data/app/controllers/spree/api/switch_cm/ota/res_notifs_controller.rb +29 -0
- data/app/controllers/spree/api/switch_cm/ota/reservation_streams_controller.rb +83 -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 +56 -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 +32 -0
- data/app/controllers/spree/api/v2/operator/check_ins_controller.rb +7 -6
- data/app/controllers/spree/api/v2/operator/event_charts_controller.rb +8 -10
- data/app/controllers/spree/api/v2/operator/event_ticket_aggregators_controller.rb +14 -4
- data/app/controllers/spree/api/v2/operator/pie_chart_event_aggregators_controller.rb +15 -1
- data/app/controllers/spree/api/v2/platform/homepage_section_relatable_options_controller.rb +28 -0
- data/app/controllers/spree/api/v2/storefront/cross_app_handoff_tokens_controller.rb +41 -0
- data/app/controllers/spree/api/v2/storefront/homepage_sections_controller.rb +4 -2
- data/app/controllers/spree/api/v2/storefront/transit/item_availabilities_controller.rb +66 -0
- data/app/controllers/spree/api/v2/tenant/checkout_controller.rb +11 -0
- data/app/controllers/spree/api/v2/tenant/vendor_photos_controller.rb +25 -0
- data/app/controllers/spree_cm_commissioner/admin/orders_controller_decorator.rb +9 -1
- data/app/controllers/spree_cm_commissioner/api/v2/storefront/checkout_controller_decorator.rb +15 -0
- data/app/factory/spree_cm_commissioner/order_telegram_message_factory.rb +6 -2
- data/app/helpers/spree/admin/customer_notifications_helper.rb +57 -0
- data/app/helpers/spree_cm_commissioner/admin/homepage_segment_helper.rb +45 -23
- data/app/interactors/spree_cm_commissioner/customer_notification_sender.rb +18 -9
- data/app/interactors/spree_cm_commissioner/user_cross_app_handoff_authenticator.rb +17 -0
- data/app/jobs/spree_cm_commissioner/audit_event_job.rb +29 -0
- data/app/jobs/spree_cm_commissioner/integrations/switch_cm/unseen_reservations_alert_job.rb +7 -0
- data/app/models/concerns/spree_cm_commissioner/audit_wrapper.rb +139 -0
- data/app/models/concerns/spree_cm_commissioner/homepage_section_bitwise.rb +37 -1
- data/app/models/concerns/spree_cm_commissioner/integrations/inventory_sync_cachable.rb +9 -0
- data/app/models/concerns/spree_cm_commissioner/line_item_open_dated_trippable.rb +2 -2
- data/app/models/concerns/spree_cm_commissioner/order_state_machine.rb +10 -0
- data/app/models/concerns/spree_cm_commissioner/phone_number_sanitizer.rb +5 -1
- data/app/models/concerns/spree_cm_commissioner/user_notification.rb +3 -1
- data/app/models/concerns/spree_cm_commissioner/variant_metadata.rb +24 -0
- data/app/models/concerns/spree_cm_commissioner/vehicle_kind.rb +1 -1
- data/app/models/spree_cm_commissioner/adjustable/adjuster/service_fee.rb +31 -0
- data/app/models/spree_cm_commissioner/adjustment_decorator.rb +1 -0
- data/app/models/spree_cm_commissioner/audit_event.rb +11 -0
- data/app/models/spree_cm_commissioner/calculators/service_fee_flat_amount.rb +17 -0
- data/app/models/spree_cm_commissioner/calculators/service_fee_percent.rb +20 -0
- data/app/models/spree_cm_commissioner/currency_rate.rb +15 -0
- data/app/models/spree_cm_commissioner/customer_notification.rb +15 -1
- data/app/models/spree_cm_commissioner/homepage_section.rb +25 -0
- data/app/models/spree_cm_commissioner/integration.rb +58 -0
- data/app/models/spree_cm_commissioner/integrations/book_me_bus_v1.rb +64 -0
- data/app/models/spree_cm_commissioner/integrations/larryta.rb +81 -0
- data/app/models/spree_cm_commissioner/integrations/stadium_x_v1.rb +4 -10
- data/app/models/spree_cm_commissioner/integrations/switch_cm.rb +80 -0
- data/app/models/spree_cm_commissioner/integrations/vireak_buntham.rb +82 -0
- data/app/models/spree_cm_commissioner/inventory_item.rb +18 -0
- data/app/models/spree_cm_commissioner/line_item_decorator.rb +2 -0
- data/app/models/spree_cm_commissioner/order_decorator.rb +13 -1
- data/app/models/spree_cm_commissioner/order_updater_decorator.rb +31 -0
- data/app/models/spree_cm_commissioner/place.rb +2 -0
- data/app/models/spree_cm_commissioner/redis_stock/cached_inventory_items_builder.rb +2 -1
- data/app/models/spree_cm_commissioner/route.rb +1 -0
- data/app/models/spree_cm_commissioner/seat_layout.rb +1 -0
- data/app/models/spree_cm_commissioner/service_fee_rule.rb +183 -0
- data/app/models/spree_cm_commissioner/stock/availability_checker.rb +1 -1
- data/app/models/spree_cm_commissioner/stock/order_availability_checker.rb +1 -1
- data/app/models/spree_cm_commissioner/stock_item_decorator.rb +4 -0
- data/app/models/spree_cm_commissioner/trip.rb +2 -1
- data/app/models/spree_cm_commissioner/trip_stop.rb +2 -0
- data/app/models/spree_cm_commissioner/user_decorator.rb +4 -0
- data/app/models/spree_cm_commissioner/variant_decorator.rb +5 -0
- data/app/models/spree_cm_commissioner/vehicle.rb +3 -0
- data/app/models/spree_cm_commissioner/vehicle_type.rb +1 -0
- data/app/models/spree_cm_commissioner/vendor_decorator.rb +17 -0
- data/app/models/spree_cm_commissioner/vendor_place.rb +2 -0
- data/app/overrides/spree/admin/orders/index/line_items_row.html.erb.deface +14 -0
- data/app/overrides/spree/admin/orders/index/row_actions.html.erb.deface +17 -0
- data/app/overrides/spree/admin/shared/sub_menu/_configuration/menu_items.html.erb.deface +5 -0
- data/app/queries/spree_cm_commissioner/users_by_event_fetcher_query.rb +6 -7
- data/app/serializables/spree_cm_commissioner/item_availability_sync.rb +10 -0
- data/app/serializers/spree/api/v2/platform/service_fee_rule_serializer.rb +17 -0
- data/app/serializers/spree/v2/storefront/homepage_section_serializer.rb +1 -1
- data/app/serializers/spree/v2/storefront/line_item_serializer_decorator.rb +8 -2
- data/app/serializers/spree/v2/tenant/cart_serializer.rb +2 -1
- data/app/serializers/spree/v2/tenant/line_item_serializer.rb +7 -1
- data/app/serializers/spree_cm_commissioner/v2/operator/guest_serializer.rb +2 -1
- data/app/serializers/spree_cm_commissioner/v2/storefront/cart_serializer_decorator.rb +2 -1
- data/app/serializers/spree_cm_commissioner/v2/storefront/cross_app_handoff_token_serializer.rb +9 -0
- data/app/serializers/spree_cm_commissioner/v2/storefront/item_availability_sync_serializer.rb +10 -0
- data/app/services/spree_cm_commissioner/api_caches/invalidate.rb +1 -1
- data/app/services/spree_cm_commissioner/audit_logger.rb +59 -0
- data/app/services/spree_cm_commissioner/checkout/advance_to_payment.rb +27 -0
- data/app/services/spree_cm_commissioner/cross_app_handoff_tokens/decode.rb +42 -0
- data/app/services/spree_cm_commissioner/cross_app_handoff_tokens/encode.rb +47 -0
- data/app/services/spree_cm_commissioner/currency_converter/create.rb +27 -0
- data/app/services/spree_cm_commissioner/currency_rates/manage.rb +99 -0
- data/app/services/spree_cm_commissioner/draft_orders/create.rb +1 -0
- data/app/services/spree_cm_commissioner/google_maps_geocoder.rb +41 -0
- data/app/services/spree_cm_commissioner/integrations/base/sync_manager.rb +1 -2
- data/app/services/spree_cm_commissioner/integrations/base/sync_result.rb +11 -0
- data/app/services/spree_cm_commissioner/integrations/book_me_bus_v1/external_client/client.rb +230 -0
- data/app/services/spree_cm_commissioner/integrations/book_me_bus_v1/external_client/connection.rb +147 -0
- data/app/services/spree_cm_commissioner/integrations/book_me_bus_v1/inventory/external_inventory_item_status.rb +52 -0
- data/app/services/spree_cm_commissioner/integrations/book_me_bus_v1/inventory/external_inventory_items.rb +91 -0
- data/app/services/spree_cm_commissioner/integrations/book_me_bus_v1/inventory/unstock_inventory.rb +188 -0
- data/app/services/spree_cm_commissioner/integrations/book_me_bus_v1/polling/sync_inventory.rb +79 -0
- data/app/services/spree_cm_commissioner/integrations/book_me_bus_v1/polling/sync_item_availability.rb +354 -0
- data/app/services/spree_cm_commissioner/integrations/book_me_bus_v1/polling/sync_locations.rb +103 -0
- data/app/services/spree_cm_commissioner/integrations/book_me_bus_v1/polling/sync_nationality_pricing.rb +93 -0
- data/app/services/spree_cm_commissioner/integrations/book_me_bus_v1/polling/sync_product.rb +70 -0
- data/app/services/spree_cm_commissioner/integrations/book_me_bus_v1/polling/sync_route_stops.rb +121 -0
- data/app/services/spree_cm_commissioner/integrations/book_me_bus_v1/polling/sync_routes.rb +128 -0
- data/app/services/spree_cm_commissioner/integrations/book_me_bus_v1/polling/sync_seat_layout.rb +412 -0
- data/app/services/spree_cm_commissioner/integrations/book_me_bus_v1/polling/sync_service_calendars.rb +59 -0
- data/app/services/spree_cm_commissioner/integrations/book_me_bus_v1/polling/sync_trip_stop.rb +224 -0
- data/app/services/spree_cm_commissioner/integrations/book_me_bus_v1/polling/sync_trips.rb +769 -0
- data/app/services/spree_cm_commissioner/integrations/book_me_bus_v1/polling/sync_variant.rb +69 -0
- data/app/services/spree_cm_commissioner/integrations/book_me_bus_v1/polling/sync_vehicle_types.rb +92 -0
- data/app/services/spree_cm_commissioner/integrations/book_me_bus_v1/polling/time_parser.rb +19 -0
- data/app/services/spree_cm_commissioner/integrations/book_me_bus_v1/resources/base.rb +40 -0
- data/app/services/spree_cm_commissioner/integrations/book_me_bus_v1/resources/inventory_item_status.rb +148 -0
- data/app/services/spree_cm_commissioner/integrations/book_me_bus_v1/resources/location.rb +55 -0
- data/app/services/spree_cm_commissioner/integrations/book_me_bus_v1/resources/reservation_cart.rb +85 -0
- data/app/services/spree_cm_commissioner/integrations/book_me_bus_v1/resources/seat.rb +128 -0
- data/app/services/spree_cm_commissioner/integrations/book_me_bus_v1/resources/seat_layout.rb +386 -0
- data/app/services/spree_cm_commissioner/integrations/book_me_bus_v1/resources/stop.rb +84 -0
- data/app/services/spree_cm_commissioner/integrations/book_me_bus_v1/resources/trip.rb +237 -0
- data/app/services/spree_cm_commissioner/integrations/book_me_bus_v1/resources/wallet_payment.rb +48 -0
- data/app/services/spree_cm_commissioner/integrations/book_me_bus_v1/sync_manager.rb +22 -0
- data/app/services/spree_cm_commissioner/integrations/book_me_bus_v1/sync_strategies/full_sync_strategy.rb +116 -0
- data/app/services/spree_cm_commissioner/integrations/book_me_bus_v1/sync_strategies/incremental_sync_strategy.rb +63 -0
- data/app/services/spree_cm_commissioner/integrations/larryta/external_client/client.rb +314 -0
- data/app/services/spree_cm_commissioner/integrations/larryta/external_client/connection.rb +74 -0
- data/app/services/spree_cm_commissioner/integrations/larryta/inventory/external_inventory_item_status.rb +53 -0
- data/app/services/spree_cm_commissioner/integrations/larryta/inventory/external_inventory_items.rb +106 -0
- data/app/services/spree_cm_commissioner/integrations/larryta/inventory/hold_seats.rb +143 -0
- data/app/services/spree_cm_commissioner/integrations/larryta/inventory/release_seats.rb +90 -0
- data/app/services/spree_cm_commissioner/integrations/larryta/inventory/unstock_inventory.rb +143 -0
- data/app/services/spree_cm_commissioner/integrations/larryta/polling/sync_item_availability.rb +325 -0
- data/app/services/spree_cm_commissioner/integrations/larryta/polling/sync_product.rb +72 -0
- data/app/services/spree_cm_commissioner/integrations/larryta/polling/sync_routes.rb +142 -0
- data/app/services/spree_cm_commissioner/integrations/larryta/polling/sync_seat_layout.rb +255 -0
- data/app/services/spree_cm_commissioner/integrations/larryta/polling/sync_stock_item.rb +55 -0
- data/app/services/spree_cm_commissioner/integrations/larryta/polling/sync_trip_stop.rb +171 -0
- data/app/services/spree_cm_commissioner/integrations/larryta/polling/sync_trips.rb +397 -0
- data/app/services/spree_cm_commissioner/integrations/larryta/polling/sync_variant.rb +63 -0
- data/app/services/spree_cm_commissioner/integrations/larryta/polling/sync_vehicle_type.rb +79 -0
- data/app/services/spree_cm_commissioner/integrations/larryta/resources/base.rb +39 -0
- data/app/services/spree_cm_commissioner/integrations/larryta/resources/booking.rb +113 -0
- data/app/services/spree_cm_commissioner/integrations/larryta/resources/booking_session.rb +43 -0
- data/app/services/spree_cm_commissioner/integrations/larryta/resources/booking_transaction.rb +236 -0
- data/app/services/spree_cm_commissioner/integrations/larryta/resources/direction.rb +71 -0
- data/app/services/spree_cm_commissioner/integrations/larryta/resources/passenger.rb +48 -0
- data/app/services/spree_cm_commissioner/integrations/larryta/resources/schedule.rb +150 -0
- data/app/services/spree_cm_commissioner/integrations/larryta/resources/seat.rb +31 -0
- data/app/services/spree_cm_commissioner/integrations/larryta/resources/seat_availability.rb +49 -0
- data/app/services/spree_cm_commissioner/integrations/larryta/resources/seat_layout.rb +211 -0
- data/app/services/spree_cm_commissioner/integrations/larryta/sync_manager.rb +17 -0
- data/app/services/spree_cm_commissioner/integrations/larryta/sync_strategies/full_sync_strategy.rb +79 -0
- data/app/services/spree_cm_commissioner/integrations/larryta/sync_strategies/incremental_sync_strategy.rb +58 -0
- data/app/services/spree_cm_commissioner/integrations/polling_scheduler.rb +1 -1
- data/app/services/spree_cm_commissioner/integrations/stadium_x_v1/external_client/client.rb +14 -15
- data/app/services/spree_cm_commissioner/integrations/stadium_x_v1/polling/sync_matches.rb +7 -4
- data/app/services/spree_cm_commissioner/integrations/stadium_x_v1/polling/sync_zones.rb +6 -3
- data/app/services/spree_cm_commissioner/integrations/stadium_x_v1/sync_manager.rb +0 -3
- data/app/services/spree_cm_commissioner/integrations/stadium_x_v1/sync_strategies/full_sync_strategy.rb +1 -3
- data/app/services/spree_cm_commissioner/integrations/stadium_x_v1/sync_strategies/incremental_sync_strategy.rb +1 -3
- data/app/services/spree_cm_commissioner/integrations/stadium_x_v1/sync_strategies/webhook_sync_strategy.rb +1 -2
- data/app/services/spree_cm_commissioner/integrations/switch_cm/accept_push.rb +101 -0
- data/app/services/spree_cm_commissioner/integrations/switch_cm/accommodation_lock.rb +41 -0
- data/app/services/spree_cm_commissioner/integrations/switch_cm/acknowledge_reservations.rb +43 -0
- data/app/services/spree_cm_commissioner/integrations/switch_cm/apply_ari.rb +119 -0
- data/app/services/spree_cm_commissioner/integrations/switch_cm/ari_update.rb +24 -0
- data/app/services/spree_cm_commissioner/integrations/switch_cm/availability_readback.rb +86 -0
- data/app/services/spree_cm_commissioner/integrations/switch_cm/mark_seen.rb +53 -0
- data/app/services/spree_cm_commissioner/integrations/switch_cm/ota/avail_notif_parser.rb +60 -0
- data/app/services/spree_cm_commissioner/integrations/switch_cm/ota/base_parser.rb +60 -0
- data/app/services/spree_cm_commissioner/integrations/switch_cm/ota/push_response.rb +38 -0
- data/app/services/spree_cm_commissioner/integrations/switch_cm/ota/rate_amount_notif_parser.rb +46 -0
- data/app/services/spree_cm_commissioner/integrations/switch_cm/ota/reservation_presenter.rb +99 -0
- data/app/services/spree_cm_commissioner/integrations/switch_cm/ota/response_builder.rb +52 -0
- data/app/services/spree_cm_commissioner/integrations/switch_cm/pushed_nights.rb +69 -0
- data/app/services/spree_cm_commissioner/integrations/switch_cm/reservation.rb +118 -0
- data/app/services/spree_cm_commissioner/integrations/switch_cm/reservation_feed.rb +143 -0
- data/app/services/spree_cm_commissioner/integrations/switch_cm/room_catalog.rb +56 -0
- data/app/services/spree_cm_commissioner/integrations/switch_cm/unseen_reservations_alert.rb +45 -0
- data/app/services/spree_cm_commissioner/integrations/switch_cm/unseen_room_nights.rb +39 -0
- data/app/services/spree_cm_commissioner/integrations/switch_cm/validate_ari_updates.rb +136 -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_presenter.rb +74 -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/external_client/client.rb +170 -0
- data/app/services/spree_cm_commissioner/integrations/vireak_buntham/external_client/connection.rb +59 -0
- data/app/services/spree_cm_commissioner/integrations/vireak_buntham/inventory/external_inventory_item_status.rb +48 -0
- data/app/services/spree_cm_commissioner/integrations/vireak_buntham/inventory/external_inventory_items.rb +92 -0
- data/app/services/spree_cm_commissioner/integrations/vireak_buntham/inventory/unstock_inventory.rb +230 -0
- data/app/services/spree_cm_commissioner/integrations/vireak_buntham/polling/sync_item_availability.rb +270 -0
- data/app/services/spree_cm_commissioner/integrations/vireak_buntham/polling/sync_locations.rb +96 -0
- data/app/services/spree_cm_commissioner/integrations/vireak_buntham/polling/sync_nationality_pricing.rb +76 -0
- data/app/services/spree_cm_commissioner/integrations/vireak_buntham/polling/sync_product.rb +69 -0
- data/app/services/spree_cm_commissioner/integrations/vireak_buntham/polling/sync_routes.rb +110 -0
- data/app/services/spree_cm_commissioner/integrations/vireak_buntham/polling/sync_seat_layout.rb +207 -0
- data/app/services/spree_cm_commissioner/integrations/vireak_buntham/polling/sync_stock_item.rb +43 -0
- data/app/services/spree_cm_commissioner/integrations/vireak_buntham/polling/sync_trip_stop.rb +192 -0
- data/app/services/spree_cm_commissioner/integrations/vireak_buntham/polling/sync_trips.rb +377 -0
- data/app/services/spree_cm_commissioner/integrations/vireak_buntham/polling/sync_variant.rb +56 -0
- data/app/services/spree_cm_commissioner/integrations/vireak_buntham/polling/sync_vehicle_type.rb +51 -0
- data/app/services/spree_cm_commissioner/integrations/vireak_buntham/resources/base.rb +32 -0
- data/app/services/spree_cm_commissioner/integrations/vireak_buntham/resources/booking.rb +48 -0
- data/app/services/spree_cm_commissioner/integrations/vireak_buntham/resources/booking_transaction.rb +46 -0
- data/app/services/spree_cm_commissioner/integrations/vireak_buntham/resources/cell.rb +53 -0
- data/app/services/spree_cm_commissioner/integrations/vireak_buntham/resources/confirm_booking_request.rb +43 -0
- data/app/services/spree_cm_commissioner/integrations/vireak_buntham/resources/location.rb +37 -0
- data/app/services/spree_cm_commissioner/integrations/vireak_buntham/resources/schedule.rb +138 -0
- data/app/services/spree_cm_commissioner/integrations/vireak_buntham/resources/seat.rb +48 -0
- data/app/services/spree_cm_commissioner/integrations/vireak_buntham/resources/seat_layout.rb +79 -0
- data/app/services/spree_cm_commissioner/integrations/vireak_buntham/sync_manager.rb +17 -0
- data/app/services/spree_cm_commissioner/integrations/vireak_buntham/sync_strategies/full_sync_strategy.rb +87 -0
- data/app/services/spree_cm_commissioner/integrations/vireak_buntham/sync_strategies/incremental_sync_strategy.rb +53 -0
- data/app/services/spree_cm_commissioner/inventory_items/bulk_adjust_quantities_by_variant.rb +4 -0
- data/app/services/spree_cm_commissioner/inventory_items/generate_permanent_item.rb +48 -0
- data/app/services/spree_cm_commissioner/inventory_items/reset.rb +4 -0
- data/app/services/spree_cm_commissioner/order_holds/hold.rb +17 -0
- data/app/services/spree_cm_commissioner/order_holds/release.rb +17 -0
- data/app/services/spree_cm_commissioner/place_resolver.rb +95 -0
- data/app/services/spree_cm_commissioner/transit_order/create.rb +1 -0
- data/app/services/spree_cm_commissioner/user_authenticator.rb +20 -12
- data/app/views/spree/admin/customer_notifications/_form.html.erb +9 -9
- data/app/views/spree/admin/customer_notifications/index.html.erb +36 -19
- data/app/views/spree/admin/homepage_section/_form.html.erb +19 -2
- data/app/views/spree/admin/homepage_section/_tabs.html.erb +7 -7
- data/app/views/spree/admin/homepage_section/index.html.erb +73 -6
- data/app/views/spree/admin/integration_sessions/index.html.erb +8 -6
- data/app/views/spree/admin/integrations/_book_me_bus_v1_fields.html.erb +47 -0
- data/app/views/spree/admin/integrations/_form.html.erb +16 -0
- data/app/views/spree/admin/integrations/_larryta_fields.html.erb +58 -0
- data/app/views/spree/admin/integrations/_switch_cm_fields.html.erb +53 -0
- data/app/views/spree/admin/integrations/_vireak_buntham_fields.html.erb +75 -0
- data/app/views/spree/admin/integrations/index.html.erb +1 -1
- data/app/views/spree/admin/notifications/index.html.erb +2 -1
- data/app/views/spree/admin/orders/line_items.html.erb +53 -0
- data/app/views/spree/admin/service_fee_rules/_form.html.erb +119 -0
- data/app/views/spree/admin/service_fee_rules/edit.html.erb +11 -0
- data/app/views/spree/admin/service_fee_rules/index.html.erb +91 -0
- data/app/views/spree/admin/service_fee_rules/new.html.erb +11 -0
- data/app/views/spree/admin/shared/_send_all_form.html.erb +1 -0
- data/app/views/spree/admin/shared/_send_test_form.html.erb +3 -3
- data/app/views/spree/admin/stock_managements/index.html.erb +14 -0
- data/config/locales/en.yml +37 -0
- data/config/locales/km.yml +33 -0
- data/config/routes.rb +34 -0
- data/db/migrate/20260327143200_create_cm_audit_events.rb +21 -0
- data/db/migrate/20260410045815_create_cm_currency_rates.rb +23 -0
- data/db/migrate/20260411102139_add_metadata_to_cm_inventory_items.rb +6 -0
- data/db/migrate/20260914000000_add_mini_app_to_homepage_sections.rb +5 -0
- data/db/migrate/20260917085551_create_cm_service_fee_rules.rb +16 -0
- data/db/migrate/20260917085552_add_service_fee_total_to_spree_line_items_and_orders.rb +6 -0
- data/db/migrate/20260928000000_add_oauth_application_id_to_cm_integrations.rb +5 -0
- data/db/migrate/20260928000001_add_closed_to_cm_inventory_items.rb +5 -0
- data/lib/spree_cm_commissioner/cached_inventory_item.rb +7 -1
- data/lib/spree_cm_commissioner/engine.rb +3 -0
- data/lib/spree_cm_commissioner/test_helper/factories/homepage_section_factory.rb +7 -0
- data/lib/spree_cm_commissioner/test_helper/factories/integration_factory.rb +17 -0
- data/lib/spree_cm_commissioner/version.rb +1 -1
- metadata +186 -4
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 6debbf57a54a0a2fcbc3ba4ee4c3e07045c520377afbd0ad13d6f6fde2120654
|
|
4
|
+
data.tar.gz: 3c66791879a8187925ac7ecba67437a4e049bce327f7d19ff9f435d0acddd54e
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: f5a7c7f1e396e46ae559484c92900a1180ede361e562b192a0b13abc922779f521fbc093a86d695b760abcc8609e0563785e5fadd285fb919670c0c8816bf5d9
|
|
7
|
+
data.tar.gz: 124912777c8bacddd0a1f067ab1e6faaa245af600da5343150f4862fd5c9cabdea60e54efddf64e636903a99cb2e0a7143c91647ff34e05a5c0d9d705ff8a5da
|
data/.env.example
CHANGED
|
@@ -87,3 +87,4 @@ PAYMENT_INCOMPLETE_NOTIFICATION_DELAY_IN_MINUTES=5 # Delay before notifying a c
|
|
|
87
87
|
|
|
88
88
|
# See: app/jobs/spree_cm_commissioner/telegram_alerts/order_integrity_check_job.rb
|
|
89
89
|
ORDER_INTEGRITY_TELEGRAM_ALERT_ENABLED="no" # Set to yes to enable the order integrity Telegram alert job (off by default to avoid flooding Telegram rate limits during traffic spikes)
|
|
90
|
+
|
data/Gemfile.lock
CHANGED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
module SpreeCmCommissioner
|
|
2
|
+
module Operator
|
|
3
|
+
module EventAuthorization
|
|
4
|
+
extend ActiveSupport::Concern
|
|
5
|
+
|
|
6
|
+
private
|
|
7
|
+
|
|
8
|
+
# True only when the current user holds the organizer role AND owns the
|
|
9
|
+
# vendor that `taxon`'s event belongs to — the organizer role alone has no
|
|
10
|
+
# link to any specific event, so it must be paired with vendor ownership.
|
|
11
|
+
def organizer_for_event?(taxon)
|
|
12
|
+
return false if taxon.nil? || taxon.vendor_id.nil? || spree_current_user.nil?
|
|
13
|
+
return false unless spree_current_user.organizer?
|
|
14
|
+
|
|
15
|
+
spree_current_user.vendors.exists?(id: taxon.vendor_id)
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
end
|
|
@@ -5,6 +5,8 @@ module Spree
|
|
|
5
5
|
|
|
6
6
|
before_action :load_tenants, only: [:index]
|
|
7
7
|
|
|
8
|
+
helper_method :selected_tenant
|
|
9
|
+
|
|
8
10
|
def model_class
|
|
9
11
|
SpreeCmCommissioner::HomepageSection
|
|
10
12
|
end
|
|
@@ -14,7 +16,13 @@ module Spree
|
|
|
14
16
|
end
|
|
15
17
|
|
|
16
18
|
def collection_url
|
|
17
|
-
admin_homepage_feed_homepage_section_index_url
|
|
19
|
+
admin_homepage_feed_homepage_section_index_url(tenant_id: params[:tenant_id].presence || @object&.tenant_id)
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
# A new section joins the storefront you were viewing. Reading it from the URL keeps
|
|
23
|
+
# `tenant_id` out of permitted_resource_params, so the form cannot reassign a storefront.
|
|
24
|
+
def build_resource
|
|
25
|
+
model_class.new(tenant_id: params[:tenant_id].presence)
|
|
18
26
|
end
|
|
19
27
|
|
|
20
28
|
def edit
|
|
@@ -41,25 +49,41 @@ module Spree
|
|
|
41
49
|
@collection = super.order(position: :asc)
|
|
42
50
|
@search = @collection.ransack(params[:q])
|
|
43
51
|
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
52
|
+
# An unknown segment would compare against a nil bit and silently return nothing.
|
|
53
|
+
scoped = @search.result.where(tenant_id: selected_tenant&.id)
|
|
54
|
+
scoped = scoped.filter_by_segment(params[:segment]) if valid_segment?(params[:segment])
|
|
55
|
+
scoped = scoped.filter_by_mini_app(params[:mini_app]) if params[:mini_app].present?
|
|
56
|
+
|
|
57
|
+
@collection = scoped.page(params[:page]).per(params[:per_page])
|
|
48
58
|
end
|
|
49
59
|
|
|
50
60
|
# overrided
|
|
51
61
|
def permitted_resource_params
|
|
52
|
-
|
|
62
|
+
object_params = params[:spree_cm_commissioner_homepage_section]
|
|
63
|
+
segment_value = helpers.calculate_segment_value(object_params)
|
|
64
|
+
mini_app_value = helpers.calculate_mini_app_value(object_params)
|
|
53
65
|
|
|
54
66
|
params
|
|
55
67
|
.require(:spree_cm_commissioner_homepage_section)
|
|
56
|
-
.permit(:title, :description, :active, :
|
|
57
|
-
.merge(segment: segment_value)
|
|
68
|
+
.permit(:title, :description, :active, :preview)
|
|
69
|
+
.merge(segment: segment_value, mini_app: mini_app_value)
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
# The storefront being viewed; no tenant in the URL means BookMe+. Also gates the mini app
|
|
73
|
+
# column and form field, which apply to BookMe+ only.
|
|
74
|
+
def selected_tenant
|
|
75
|
+
return @selected_tenant if defined?(@selected_tenant)
|
|
76
|
+
|
|
77
|
+
@selected_tenant = params[:tenant_id].presence && SpreeCmCommissioner::Tenant.find_by(id: params[:tenant_id])
|
|
58
78
|
end
|
|
59
79
|
|
|
60
80
|
def load_tenants
|
|
61
81
|
@tenants ||= SpreeCmCommissioner::Tenant.order(:name)
|
|
62
82
|
end
|
|
83
|
+
|
|
84
|
+
def valid_segment?(segment)
|
|
85
|
+
segment.present? && SpreeCmCommissioner::HomepageSectionBitwise::BIT_SEGMENT.key?(segment.to_sym)
|
|
86
|
+
end
|
|
63
87
|
end
|
|
64
88
|
end
|
|
65
89
|
end
|
|
@@ -12,7 +12,9 @@ module Spree
|
|
|
12
12
|
def enqueue_polling
|
|
13
13
|
sync_type = params[:sync_type].presence || :full
|
|
14
14
|
|
|
15
|
-
if
|
|
15
|
+
if !@integration.supports_polling?
|
|
16
|
+
flash[:error] = Spree.t('admin.integration_polling_not_supported')
|
|
17
|
+
elsif @integration.active?
|
|
16
18
|
SpreeCmCommissioner::Integrations::PollingJob.perform_later(
|
|
17
19
|
integration_id: @integration.id,
|
|
18
20
|
sync_type: sync_type
|
|
@@ -47,6 +49,7 @@ module Spree
|
|
|
47
49
|
:full_sync_interval_hours,
|
|
48
50
|
:tenant_id,
|
|
49
51
|
:vendor_id,
|
|
52
|
+
:oauth_application_id,
|
|
50
53
|
public_metadata: {},
|
|
51
54
|
private_metadata: {}
|
|
52
55
|
)
|
|
@@ -62,6 +65,7 @@ module Spree
|
|
|
62
65
|
@conflict_strategy_options ||= enum_to_options(SpreeCmCommissioner::Integration.conflict_strategies)
|
|
63
66
|
@tenants ||= SpreeCmCommissioner::Tenant.select(:id, :name).order(:name)
|
|
64
67
|
@vendors ||= Spree::Vendor.select(:id, :name).order(:name)
|
|
68
|
+
@oauth_applications ||= Spree::OauthApplication.select(:id, :name).order(:name)
|
|
65
69
|
end
|
|
66
70
|
|
|
67
71
|
def enum_to_options(enum_hash)
|
|
@@ -69,14 +73,10 @@ module Spree
|
|
|
69
73
|
end
|
|
70
74
|
|
|
71
75
|
def integration_type_options
|
|
72
|
-
|
|
76
|
+
types = SpreeCmCommissioner::Integration.types
|
|
77
|
+
return [[Spree.t(:type), 'SpreeCmCommissioner::Integration']] if types.blank?
|
|
73
78
|
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
files.map do |path|
|
|
77
|
-
class_name = "SpreeCmCommissioner::Integrations::#{File.basename(path, '.rb').camelize}"
|
|
78
|
-
[class_name, class_name]
|
|
79
|
-
end.sort_by(&:first)
|
|
79
|
+
types.map { |class_name| [class_name, class_name] }
|
|
80
80
|
end
|
|
81
81
|
end
|
|
82
82
|
end
|
|
@@ -2,6 +2,7 @@ module Spree
|
|
|
2
2
|
module Admin
|
|
3
3
|
class InventoryItemsController < StockManagementsController
|
|
4
4
|
before_action :load_parent
|
|
5
|
+
before_action :reject_external_inventory_edits, only: %i[bulk_update_prices bulk_update_stocks]
|
|
5
6
|
|
|
6
7
|
# GET /products/:slug/inventory_items/prices?inventory_item_ids=1,2,3
|
|
7
8
|
def prices
|
|
@@ -78,6 +79,17 @@ module Spree
|
|
|
78
79
|
|
|
79
80
|
redirect_back fallback_location: admin_product_stock_managements_path(@product)
|
|
80
81
|
end
|
|
82
|
+
|
|
83
|
+
private
|
|
84
|
+
|
|
85
|
+
# The partner owns these per-night stocks and prices; its next update would silently overwrite
|
|
86
|
+
# an edit made here.
|
|
87
|
+
def reject_external_inventory_edits
|
|
88
|
+
return unless @product.variants.includes(vendor: :integration).any?(&:inventory_external_wins?)
|
|
89
|
+
|
|
90
|
+
flash[:error] = Spree.t('admin.inventory_external_wins', name: @product.vendor.integration.name)
|
|
91
|
+
redirect_back fallback_location: admin_product_stock_managements_path(@product)
|
|
92
|
+
end
|
|
81
93
|
end
|
|
82
94
|
end
|
|
83
95
|
end
|
|
@@ -1,26 +1,58 @@
|
|
|
1
|
+
# Looks up users targetable by a customer notification, for the "send test" user picker in the modal form.
|
|
2
|
+
|
|
1
3
|
module Spree
|
|
2
4
|
module Admin
|
|
3
5
|
class NotificationUsersController < Spree::Admin::BaseController
|
|
6
|
+
RESULTS_LIMIT = 20
|
|
7
|
+
|
|
4
8
|
def index
|
|
5
9
|
notification = SpreeCmCommissioner::CustomerNotification.find(params[:customer_notification_id])
|
|
6
|
-
|
|
10
|
+
scope = fetch_users_with_device_token(notification)
|
|
11
|
+
users = search(scope, params[:q])
|
|
7
12
|
|
|
8
|
-
render json:
|
|
13
|
+
render json: users.limit(RESULTS_LIMIT).as_json(only: %i[id], methods: %i[display_name])
|
|
9
14
|
end
|
|
10
15
|
|
|
11
16
|
private
|
|
12
17
|
|
|
18
|
+
# Filters scope by email or phone number matching query (case-insensitive)
|
|
19
|
+
def search(scope, query)
|
|
20
|
+
return scope.none if query.blank?
|
|
21
|
+
|
|
22
|
+
term = "%#{Spree::User.sanitize_sql_like(query)}%"
|
|
23
|
+
scope.where('spree_users.email ILIKE :term OR spree_users.phone_number ILIKE :term', term: term)
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
# Narrows the notification's target users down to those with a registered push device token
|
|
13
27
|
def fetch_users_with_device_token(notification)
|
|
28
|
+
scope = users_scope_for(notification)
|
|
29
|
+
|
|
30
|
+
scope.where(id: registered_user_ids)
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
# User ids that have a device token registered for push notifications
|
|
34
|
+
def registered_user_ids
|
|
35
|
+
device_tokens = SpreeCmCommissioner::DeviceToken.where.not(registration_token: nil)
|
|
36
|
+
device_tokens.select(:user_id)
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
# Resolves the base user scope for a notification: by its taxons, by its vendor's own
|
|
40
|
+
# taxons (when the vendor has no tenant), or by tenant otherwise
|
|
41
|
+
def users_scope_for(notification)
|
|
42
|
+
tenant_id = notification.vendor&.tenant_id
|
|
43
|
+
|
|
14
44
|
if notification.notification_taxons.present?
|
|
15
|
-
users
|
|
16
|
-
|
|
45
|
+
users(notification.notification_taxons.pluck(:taxon_id))
|
|
46
|
+
elsif tenant_id.nil? && notification.vendor.present?
|
|
47
|
+
users(notification.vendor.taxons.ids)
|
|
17
48
|
else
|
|
18
|
-
Spree::User.where(
|
|
49
|
+
Spree::User.where(tenant_id: tenant_id)
|
|
19
50
|
end
|
|
20
51
|
end
|
|
21
52
|
|
|
22
|
-
|
|
23
|
-
|
|
53
|
+
# Fetches users who purchased under the given events
|
|
54
|
+
def users(event_id)
|
|
55
|
+
SpreeCmCommissioner::UsersByEventFetcherQuery.new(event_id: event_id).call
|
|
24
56
|
end
|
|
25
57
|
end
|
|
26
58
|
end
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
module Spree
|
|
2
|
+
module Admin
|
|
3
|
+
class ServiceFeeRulesController < Spree::Admin::ResourceController
|
|
4
|
+
helper_method :selected_tenant
|
|
5
|
+
|
|
6
|
+
before_action :load_data
|
|
7
|
+
|
|
8
|
+
def index
|
|
9
|
+
@search = model_class.ransack(params[:q])
|
|
10
|
+
# Same 'chip button' status filter convention as
|
|
11
|
+
# admin/system/waiting_room_sessions#guests (see guests.html.erb) -- an unrecognized or
|
|
12
|
+
# missing param falls back to showing everything rather than silently hiding rows.
|
|
13
|
+
@status = %w[active inactive].include?(params[:status]) ? params[:status] : 'all'
|
|
14
|
+
|
|
15
|
+
scope = @search.result
|
|
16
|
+
scope = scope.where(active: true) if @status == 'active'
|
|
17
|
+
scope = scope.where(active: false) if @status == 'inactive'
|
|
18
|
+
|
|
19
|
+
# Same tenant-dropdown convention as admin/homepage_section#index. tenant_id only ever
|
|
20
|
+
# gets set on a ruleable-less row (see ServiceFeeRule's tenant_id absence validation), so
|
|
21
|
+
# picking a specific tenant narrows down to just that tenant's own default rule -- the
|
|
22
|
+
# default (no param, "BookMe+") view shows tenant_id: nil, i.e. everything except other
|
|
23
|
+
# tenants' own overrides: the true platform-wide default plus every product/event/vendor
|
|
24
|
+
# rule (their tenant is already implied by their ruleable, so they never set tenant_id).
|
|
25
|
+
scope = scope.where(tenant_id: selected_tenant&.id)
|
|
26
|
+
|
|
27
|
+
@service_fee_rules = scope.order(ruleable_type: :asc, ruleable_id: :asc, currency: :asc)
|
|
28
|
+
.page(params[:page])
|
|
29
|
+
.per(25)
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
# A new rule inherits the tenant you were browsing under (index's tenant dropdown). Reading
|
|
33
|
+
# it from the URL keeps `tenant_id` out of permitted_resource_params, so the form can't be
|
|
34
|
+
# used to reassign a rule to a different tenant after the fact -- same shape as
|
|
35
|
+
# HomepageSectionController#build_resource.
|
|
36
|
+
def build_resource
|
|
37
|
+
model_class.new(tenant_id: params[:tenant_id].presence)
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
private
|
|
41
|
+
|
|
42
|
+
def load_data
|
|
43
|
+
@calculators = model_class.available_calculator_types.sort_by(&:name)
|
|
44
|
+
@tenants ||= SpreeCmCommissioner::Tenant.order(:name)
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
def selected_tenant
|
|
48
|
+
return @selected_tenant if defined?(@selected_tenant)
|
|
49
|
+
|
|
50
|
+
@selected_tenant = params[:tenant_id].presence && SpreeCmCommissioner::Tenant.find_by(id: params[:tenant_id])
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
def model_class
|
|
54
|
+
SpreeCmCommissioner::ServiceFeeRule
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
def object_name
|
|
58
|
+
'spree_cm_commissioner_service_fee_rule'
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
def collection_url
|
|
62
|
+
admin_service_fee_rules_url
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
def location_after_save
|
|
66
|
+
edit_admin_service_fee_rule_path(@object)
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
# tenant_id is deliberately excluded here -- see build_resource. It's set once from the URL
|
|
70
|
+
# at creation time and never reassignable via the form afterwards.
|
|
71
|
+
def permitted_resource_params
|
|
72
|
+
params.require(:spree_cm_commissioner_service_fee_rule).permit(
|
|
73
|
+
:ruleable_type,
|
|
74
|
+
:ruleable_id,
|
|
75
|
+
:active,
|
|
76
|
+
:currency,
|
|
77
|
+
:calculator_type,
|
|
78
|
+
calculator_attributes: %i[id preferred_percent preferred_flat_amount]
|
|
79
|
+
)
|
|
80
|
+
end
|
|
81
|
+
end
|
|
82
|
+
end
|
|
83
|
+
end
|
|
@@ -99,11 +99,11 @@ module Spree
|
|
|
99
99
|
@cached_inventory_items = ::SpreeCmCommissioner::RedisStock::CachedInventoryItemsBuilder.new(@product.inventory_items)
|
|
100
100
|
.call
|
|
101
101
|
.index_by(&:inventory_item_id)
|
|
102
|
-
@
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
102
|
+
@purchased_quantities = Spree::LineItem
|
|
103
|
+
.affecting_inventory
|
|
104
|
+
.where(variant_id: @variants.pluck(:id))
|
|
105
|
+
.group('spree_line_items.variant_id')
|
|
106
|
+
.sum(:quantity)
|
|
107
107
|
end
|
|
108
108
|
|
|
109
109
|
def model_class
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
module Spree::Api::SwitchCm
|
|
2
|
+
# The Booking.com-compatible API channel managers (switch.cm) call under /api/switch_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 Ota::BaseController and Xml::BaseController render.
|
|
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
|
+
before_action :authenticate_switch_cm!
|
|
14
|
+
|
|
15
|
+
rescue_from Nokogiri::XML::SyntaxError do
|
|
16
|
+
render_fault(:bad_request, 'Request body is not well-formed XML')
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
private
|
|
20
|
+
|
|
21
|
+
def authenticate_switch_cm!
|
|
22
|
+
@oauth_application = application_from_bearer || application_from_basic
|
|
23
|
+
return if @oauth_application.present? && switch_cm_integrations.exists?
|
|
24
|
+
|
|
25
|
+
render_fault(:unauthorized, 'Authorization Required')
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def application_from_bearer
|
|
29
|
+
doorkeeper_token.application if doorkeeper_token&.accessible?
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def application_from_basic
|
|
33
|
+
authenticate_with_http_basic do |client_id, client_secret|
|
|
34
|
+
application = Spree::OauthApplication.find_by(uid: client_id)
|
|
35
|
+
application if application&.secret_matches?(client_secret)
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
def switch_cm_integrations
|
|
40
|
+
SpreeCmCommissioner::Integrations::SwitchCm.readable.where(oauth_application_id: @oauth_application.id)
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
# nil unless the requested hotel is one of this channel manager's connected hotels.
|
|
44
|
+
def current_integration
|
|
45
|
+
return @current_integration if defined?(@current_integration)
|
|
46
|
+
|
|
47
|
+
accommodation_id = accommodation_id_from_request
|
|
48
|
+
@current_integration = accommodation_id.present? ? switch_cm_integrations.find_by(vendor_id: accommodation_id) : nil
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
def require_integration!
|
|
52
|
+
render_fault(:forbidden, 'Not authorized for this hotel') if current_integration.nil?
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
# Pushes need an active connection: a paused one is still being set up, and the hotel keeps
|
|
56
|
+
# selling on its own counts until it's activated.
|
|
57
|
+
def require_active_integration!
|
|
58
|
+
return render_fault(:forbidden, 'Not authorized for this hotel') if current_integration.nil?
|
|
59
|
+
|
|
60
|
+
render_fault(:forbidden, 'This hotel is not active on BookMe+ yet') unless current_integration.active?
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
# Pushes may leave out the hotel id (B.XML never sends one); Booking.com derives it from the
|
|
64
|
+
# rooms. A room of another channel manager's hotel resolves to a hotel this app can't access.
|
|
65
|
+
def vendor_id_of_room(room_id)
|
|
66
|
+
Spree::Variant.where(id: room_id.to_i).pick(:vendor_id) if room_id.present?
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
# Reservation pulls take a comma-separated list of hotel ids, or none for all of this
|
|
70
|
+
# channel manager's hotels. Unknown ids come back separately, for a warning each, as at
|
|
71
|
+
# Booking.com; a list with no known hotel at all is refused.
|
|
72
|
+
def requested_integrations(hotel_ids)
|
|
73
|
+
ids = hotel_ids.to_s.split(',').map(&:strip).compact_blank
|
|
74
|
+
scope = switch_cm_integrations.includes(:vendor).order(:vendor_id)
|
|
75
|
+
return [scope.to_a, []] if ids.empty?
|
|
76
|
+
|
|
77
|
+
integrations = scope.where(vendor_id: ids).to_a
|
|
78
|
+
[integrations, ids - integrations.map { |integration| integration.vendor_id.to_s }]
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
def accommodation_id_from_request
|
|
82
|
+
raise NotImplementedError, "#{self.class.name} must implement accommodation_id_from_request"
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
def render_fault(_status, _text, code: nil)
|
|
86
|
+
raise NotImplementedError, "#{self.class.name} must implement render_fault"
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
# Rails parses neither XML nor text/xml bodies into params, so both families read the raw body.
|
|
90
|
+
def request_document
|
|
91
|
+
@request_document ||= Nokogiri::XML(request.raw_post) { |config| config.strict.nonet }.tap(&:remove_namespaces!)
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
def ruid
|
|
95
|
+
request.request_id
|
|
96
|
+
end
|
|
97
|
+
end
|
|
98
|
+
end
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
module Spree::Api::SwitchCm
|
|
2
|
+
module Ota
|
|
3
|
+
# POST /api/switch_cm/hotels/ota/OTA_HotelAvailNotif, served by PushesController#create.
|
|
4
|
+
class AvailNotifsController < PushesController
|
|
5
|
+
private
|
|
6
|
+
|
|
7
|
+
# override
|
|
8
|
+
def parser_class = SpreeCmCommissioner::Integrations::SwitchCm::Ota::AvailNotifParser
|
|
9
|
+
|
|
10
|
+
# override
|
|
11
|
+
def message_name = 'OTA_HotelAvailNotif'
|
|
12
|
+
|
|
13
|
+
# override
|
|
14
|
+
def response_root = 'OTA_HotelAvailNotifRS'
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
end
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
module Spree::Api::SwitchCm
|
|
2
|
+
module Ota
|
|
3
|
+
# Booking.com's OTA family (/hotels/ota/...): a failed request answers with its HTTP status
|
|
4
|
+
# and an RS document holding only <Errors>.
|
|
5
|
+
class BaseController < Spree::Api::SwitchCm::BaseController
|
|
6
|
+
# OTA EWT codes for the Error Type attribute.
|
|
7
|
+
ERROR_TYPES = { unauthorized: 4, forbidden: 6 }.freeze
|
|
8
|
+
PROCESSING_ERROR_TYPE = 12
|
|
9
|
+
|
|
10
|
+
private
|
|
11
|
+
|
|
12
|
+
def response_root
|
|
13
|
+
raise NotImplementedError, "#{self.class.name} must implement response_root"
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
# override
|
|
17
|
+
def render_fault(status, text, code: nil)
|
|
18
|
+
errors = [{ type: ERROR_TYPES.fetch(status, PROCESSING_ERROR_TYPE), code: code, text: text }]
|
|
19
|
+
render xml: SpreeCmCommissioner::Integrations::SwitchCm::Ota::ResponseBuilder.errors(response_root, errors, ruid: ruid), status: status
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def render_success(warnings: [], &block)
|
|
23
|
+
render xml: SpreeCmCommissioner::Integrations::SwitchCm::Ota::ResponseBuilder.success(response_root, ruid: ruid, warnings: warnings, &block)
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
end
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
module Spree::Api::SwitchCm
|
|
2
|
+
module Ota
|
|
3
|
+
class ProductNotifsController < BaseController
|
|
4
|
+
before_action :require_integration!
|
|
5
|
+
|
|
6
|
+
# GET /api/switch_cm/hotels/ota/OTA_HotelProductNotif?HotelCode=
|
|
7
|
+
# The hotel's rooms and rates, which the channel manager maps to its own ("Refresh Room" in
|
|
8
|
+
# switch.cm). One HotelProduct per room and rate, like Booking.com's roomrates.
|
|
9
|
+
def show
|
|
10
|
+
rooms = SpreeCmCommissioner::Integrations::SwitchCm::RoomCatalog.new(current_integration.vendor).rooms
|
|
11
|
+
|
|
12
|
+
render_success do |xml|
|
|
13
|
+
xml.HotelProducts(HotelCode: current_integration.vendor_id) do
|
|
14
|
+
rooms.each do |room|
|
|
15
|
+
xml.HotelProduct do
|
|
16
|
+
xml.RoomTypes do
|
|
17
|
+
xml.RoomType(
|
|
18
|
+
RoomTypeCode: room.id,
|
|
19
|
+
RoomTypeName: room.name,
|
|
20
|
+
MaxOccupancy: room.max_persons,
|
|
21
|
+
MaxChildOccupancy: room.max_children
|
|
22
|
+
)
|
|
23
|
+
end
|
|
24
|
+
xml.RatePlans { xml.RatePlan(RatePlanCode: room.rate_id, RatePlanName: room.rate_name) }
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
private
|
|
32
|
+
|
|
33
|
+
# override
|
|
34
|
+
def response_root
|
|
35
|
+
'OTA_HotelProductNotifRS'
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
# override
|
|
39
|
+
def accommodation_id_from_request
|
|
40
|
+
params[:HotelCode]
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
end
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
module Spree::Api::SwitchCm
|
|
2
|
+
module Ota
|
|
3
|
+
# The two OTA push endpoints; subclasses name the parser.
|
|
4
|
+
class PushesController < BaseController
|
|
5
|
+
before_action :require_active_integration!
|
|
6
|
+
|
|
7
|
+
# POST /api/switch_cm/hotels/ota/OTA_HotelAvailNotif (AvailNotifsController): rooms to sell and open/close
|
|
8
|
+
# POST /api/switch_cm/hotels/ota/OTA_HotelRateAmountNotif (RateAmountNotifsController): per-night prices
|
|
9
|
+
# Validated and applied in the request (AcceptPush); the answer is the real outcome.
|
|
10
|
+
def create
|
|
11
|
+
result = SpreeCmCommissioner::Integrations::SwitchCm::AcceptPush.call(
|
|
12
|
+
integration: current_integration,
|
|
13
|
+
message: message_name,
|
|
14
|
+
updates: updates,
|
|
15
|
+
request_id: ruid
|
|
16
|
+
)
|
|
17
|
+
|
|
18
|
+
push_response = SpreeCmCommissioner::Integrations::SwitchCm::Ota::PushResponse.new(root: response_root, result: result.value, ruid: ruid)
|
|
19
|
+
render xml: push_response.to_xml, status: push_response.http_status
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
private
|
|
23
|
+
|
|
24
|
+
def parser_class
|
|
25
|
+
raise NotImplementedError, "#{self.class.name} must implement parser_class"
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def message_name
|
|
29
|
+
raise NotImplementedError, "#{self.class.name} must implement message_name"
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def parser
|
|
33
|
+
@parser ||= parser_class.new(request_document)
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def updates
|
|
37
|
+
@updates ||= parser.updates
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
# override
|
|
41
|
+
def accommodation_id_from_request
|
|
42
|
+
parser.accommodation_id.presence || vendor_id_of_room(updates.first&.room_id)
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
end
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
module Spree::Api::SwitchCm
|
|
2
|
+
module Ota
|
|
3
|
+
# POST /api/switch_cm/hotels/ota/OTA_HotelRateAmountNotif, served by PushesController#create.
|
|
4
|
+
class RateAmountNotifsController < PushesController
|
|
5
|
+
private
|
|
6
|
+
|
|
7
|
+
# override
|
|
8
|
+
def parser_class = SpreeCmCommissioner::Integrations::SwitchCm::Ota::RateAmountNotifParser
|
|
9
|
+
|
|
10
|
+
# override
|
|
11
|
+
def message_name = 'OTA_HotelRateAmountNotif'
|
|
12
|
+
|
|
13
|
+
# override
|
|
14
|
+
def response_root = 'OTA_HotelRateAmountNotifRS'
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
end
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
module Spree::Api::SwitchCm
|
|
2
|
+
module Ota
|
|
3
|
+
# GET and POST /api/switch_cm/hotels/ota/OTA_HotelResModifyNotif, served by
|
|
4
|
+
# ReservationStreamsController: cancelled reservations, and their acknowledgements.
|
|
5
|
+
# BookMe+ doesn't modify a reservation's rooms or dates, so this stream only carries
|
|
6
|
+
# cancellations, including those cancelled before their new-reservation acknowledgement.
|
|
7
|
+
class ResModifyNotifsController < ReservationStreamsController
|
|
8
|
+
private
|
|
9
|
+
|
|
10
|
+
# override
|
|
11
|
+
def feed_method = :cancelled_reservations
|
|
12
|
+
|
|
13
|
+
# override
|
|
14
|
+
def acknowledged_status = :cancelled
|
|
15
|
+
|
|
16
|
+
# override
|
|
17
|
+
def notification_root = 'OTA_HotelResModifyNotifRQ'
|
|
18
|
+
|
|
19
|
+
# override
|
|
20
|
+
def response_root = 'OTA_HotelResModifyNotifRS'
|
|
21
|
+
|
|
22
|
+
# override
|
|
23
|
+
def container = :HotelResModifies
|
|
24
|
+
|
|
25
|
+
# override
|
|
26
|
+
def present(presenter, xml)
|
|
27
|
+
presenter.build_modification(xml)
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
end
|