spree_cm_commissioner 2.13.2 → 2.13.3.pre.pre2
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 +5 -7
- 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/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 +46 -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 +86 -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/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 +79 -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/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/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/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 +36 -0
- data/config/locales/km.yml +32 -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 +187 -4
|
@@ -8,8 +8,10 @@ module SpreeCmCommissioner
|
|
|
8
8
|
|
|
9
9
|
scope :push_notificable, -> { where('device_tokens_count > 0') }
|
|
10
10
|
|
|
11
|
+
# Previously chained with `end_users`, which excludes users who are assigned to a vendor (organizers),
|
|
12
|
+
# so organizers couldn't test notifications on their own account. Dropped that scope here.
|
|
11
13
|
def self.end_users_push_notificable
|
|
12
|
-
|
|
14
|
+
push_notificable
|
|
13
15
|
end
|
|
14
16
|
end
|
|
15
17
|
end
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
module SpreeCmCommissioner
|
|
2
|
+
module VariantMetadata
|
|
3
|
+
extend ActiveSupport::Concern
|
|
4
|
+
|
|
5
|
+
def manual_generate_inventory_items?
|
|
6
|
+
ActiveModel::Type::Boolean.new.cast(private_metadata['manual_generate_inventory_items'])
|
|
7
|
+
end
|
|
8
|
+
|
|
9
|
+
def manual_generate_inventory_items=(value)
|
|
10
|
+
boolean_value = ActiveModel::Type::Boolean.new.cast(value)
|
|
11
|
+
set_private_metadata_value('manual_generate_inventory_items', boolean_value)
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
private
|
|
15
|
+
|
|
16
|
+
def set_private_metadata_value(key, value)
|
|
17
|
+
if value.nil? || value == false
|
|
18
|
+
private_metadata.delete(key)
|
|
19
|
+
else
|
|
20
|
+
private_metadata[key] = value.to_s
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
end
|
|
@@ -2,7 +2,7 @@ module SpreeCmCommissioner
|
|
|
2
2
|
module VehicleKind
|
|
3
3
|
extend ActiveSupport::Concern
|
|
4
4
|
|
|
5
|
-
VEHICLE_KINDS = %i[suv sedan minivan van sleeping_bus luxury_van air_bus bus ferry].freeze
|
|
5
|
+
VEHICLE_KINDS = %i[suv sedan minivan van sleeping_bus luxury_van air_bus bus ferry boat train].freeze
|
|
6
6
|
|
|
7
7
|
included do
|
|
8
8
|
enum kind: VEHICLE_KINDS if table_exists? && column_names.include?('kind')
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
module SpreeCmCommissioner
|
|
2
|
+
module Adjustable
|
|
3
|
+
module Adjuster
|
|
4
|
+
class ServiceFee < Spree::Adjustable::Adjuster::Base
|
|
5
|
+
def update
|
|
6
|
+
service_fee_total = adjustments.service_fee.eligible.reload.sum(&:amount)
|
|
7
|
+
|
|
8
|
+
update_totals(service_fee_total)
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
private
|
|
12
|
+
|
|
13
|
+
def adjustments
|
|
14
|
+
adjustable.try(:all_adjustments) || adjustable.adjustments
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
# Folds into the existing non_taxable_adjustment_total bucket (same as the
|
|
18
|
+
# PricingAction adjuster) so order.total/adjustment_total are correct with no core
|
|
19
|
+
# decoration needed. `service_fee_total` is set as its own key only for line items —
|
|
20
|
+
# it becomes its own DB column there (for a clean "Service Fee: $X" display total),
|
|
21
|
+
# but Spree::Shipment has no such column, and this same adjuster instance also runs
|
|
22
|
+
# for shipments (adjusters are global, not adjustable-type-specific).
|
|
23
|
+
def update_totals(service_fee_total)
|
|
24
|
+
service_fee_total ||= 0.0
|
|
25
|
+
@totals[:non_taxable_adjustment_total] += service_fee_total
|
|
26
|
+
@totals[:service_fee_total] = service_fee_total if adjustable.is_a?(Spree::LineItem)
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
end
|
|
@@ -3,6 +3,7 @@ module SpreeCmCommissioner
|
|
|
3
3
|
def self.prepended(base)
|
|
4
4
|
base.belongs_to :payable, polymorphic: true, optional: true
|
|
5
5
|
base.scope :pricing_action, -> { where(source_type: 'SpreeCmCommissioner::PricingAction') }
|
|
6
|
+
base.scope :service_fee, -> { where(source_type: 'SpreeCmCommissioner::ServiceFeeRule') }
|
|
6
7
|
base.whitelisted_ransackable_attributes |= %w[payable_id]
|
|
7
8
|
end
|
|
8
9
|
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
module SpreeCmCommissioner
|
|
2
|
+
class AuditEvent < Base
|
|
3
|
+
belongs_to :auditable, polymorphic: true, optional: true
|
|
4
|
+
belongs_to :user, class_name: 'Spree::User', optional: true
|
|
5
|
+
|
|
6
|
+
validates :event_type, presence: true
|
|
7
|
+
|
|
8
|
+
scope :success, -> { where(success: true) }
|
|
9
|
+
scope :failed, -> { where(success: false) }
|
|
10
|
+
end
|
|
11
|
+
end
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
module SpreeCmCommissioner
|
|
2
|
+
module Calculators
|
|
3
|
+
class ServiceFeeFlatAmount < Spree::Calculator
|
|
4
|
+
preference :flat_amount, :decimal, default: 0
|
|
5
|
+
|
|
6
|
+
def self.description
|
|
7
|
+
Spree.t(:service_fee_flat_amount)
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
# Per unit, not per line item — a $0.25 fee on a 2-ticket line item charges $0.50, matching
|
|
11
|
+
# how a booking fee is normally priced (confirmed decision, see plan.md).
|
|
12
|
+
def compute(line_item)
|
|
13
|
+
preferred_flat_amount * line_item.quantity
|
|
14
|
+
end
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
end
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
module SpreeCmCommissioner
|
|
2
|
+
module Calculators
|
|
3
|
+
class ServiceFeePercent < Spree::Calculator
|
|
4
|
+
preference :percent, :decimal, default: 0
|
|
5
|
+
|
|
6
|
+
def self.description
|
|
7
|
+
Spree.t(:service_fee_percent)
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
# `discounted_amount` (price net of taxable promotions, before tax) rather than
|
|
11
|
+
# `pre_tax_amount`: the latter is only ever populated as a side effect of
|
|
12
|
+
# Spree::TaxRate.store_pre_tax_amount running for this line item, so it would make
|
|
13
|
+
# service-fee computation order-dependent on tax having already run. discounted_amount
|
|
14
|
+
# is a live computed value (amount + taxable_adjustment_total) with no such dependency.
|
|
15
|
+
def compute(line_item)
|
|
16
|
+
line_item.discounted_amount * preferred_percent / 100
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
end
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
module SpreeCmCommissioner
|
|
2
|
+
class CurrencyRate < SpreeCmCommissioner::Base
|
|
3
|
+
belongs_to :vendor, class_name: 'Spree::Vendor', optional: false
|
|
4
|
+
|
|
5
|
+
validates :from_currency, :to_currency, :rate, presence: true
|
|
6
|
+
validates :rate, numericality: { greater_than: 0 }
|
|
7
|
+
validates :from_currency, :to_currency, inclusion: { in: %w[USD KHR] }
|
|
8
|
+
|
|
9
|
+
self.whitelisted_ransackable_attributes = %w[from_currency to_currency status]
|
|
10
|
+
|
|
11
|
+
def self.by_pair(from, to)
|
|
12
|
+
where(from_currency: from.upcase, to_currency: to.upcase, status: 'active')
|
|
13
|
+
end
|
|
14
|
+
end
|
|
15
|
+
end
|
|
@@ -9,12 +9,14 @@ module SpreeCmCommissioner
|
|
|
9
9
|
has_many :notification_taxons, dependent: :destroy, class_name: 'SpreeCmCommissioner::NotificationTaxon'
|
|
10
10
|
has_many :taxons, through: :notification_taxons, class_name: 'Spree::Taxon'
|
|
11
11
|
|
|
12
|
-
belongs_to :vendor, class_name: 'Spree::Vendor'
|
|
12
|
+
belongs_to :vendor, class_name: 'Spree::Vendor', optional: true
|
|
13
13
|
|
|
14
14
|
validates :title, presence: true
|
|
15
15
|
|
|
16
16
|
validates :notification_type, presence: true
|
|
17
17
|
|
|
18
|
+
validate :taxons_must_belong_to_vendor
|
|
19
|
+
|
|
18
20
|
self.whitelisted_ransackable_attributes = %w[title excerpt notification_type]
|
|
19
21
|
self.whitelisted_ransackable_associations = %w[notification_taxons taxons]
|
|
20
22
|
|
|
@@ -33,5 +35,17 @@ module SpreeCmCommissioner
|
|
|
33
35
|
def self.scheduled_items
|
|
34
36
|
where(['sent_at IS NULL AND started_at <= ? AND active', Time.current])
|
|
35
37
|
end
|
|
38
|
+
|
|
39
|
+
private
|
|
40
|
+
|
|
41
|
+
# Prevents picking a taxon that isn't one of the selected vendor's own taxons
|
|
42
|
+
def taxons_must_belong_to_vendor
|
|
43
|
+
return if vendor_id.blank? || taxon_ids.blank?
|
|
44
|
+
|
|
45
|
+
invalid_taxon_ids = taxon_ids - vendor.taxon_ids
|
|
46
|
+
return if invalid_taxon_ids.empty?
|
|
47
|
+
|
|
48
|
+
errors.add(:taxon_ids, :must_belong_to_vendor, vendor_name: vendor.name)
|
|
49
|
+
end
|
|
36
50
|
end
|
|
37
51
|
end
|
|
@@ -8,6 +8,9 @@ module SpreeCmCommissioner
|
|
|
8
8
|
multi_tenant :tenant, class_name: 'SpreeCmCommissioner::Tenant'
|
|
9
9
|
|
|
10
10
|
attribute :segment, :integer, default: 0
|
|
11
|
+
attribute :mini_app, :integer, default: 0
|
|
12
|
+
|
|
13
|
+
self.whitelisted_ransackable_attributes = %w[title active]
|
|
11
14
|
|
|
12
15
|
has_many :homepage_section_relatables, -> { active }, inverse_of: :homepage_section, dependent: :destroy
|
|
13
16
|
|
|
@@ -20,6 +23,28 @@ module SpreeCmCommissioner
|
|
|
20
23
|
|
|
21
24
|
scope :filter_by_segment, -> (segment) { where('segment & ? > 0', BIT_SEGMENT[segment.to_sym]) }
|
|
22
25
|
|
|
26
|
+
# Sections tagged for `mini_app`; an unknown value means the untagged ones.
|
|
27
|
+
scope :filter_by_mini_app, lambda { |mini_app|
|
|
28
|
+
bit = BIT_MINI_APP[mini_app.to_s.to_sym]
|
|
29
|
+
|
|
30
|
+
bit ? where('cm_homepage_sections.mini_app & ? > 0', bit) : where(mini_app: 0)
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
# Which sections a homepage feed shows. Using Wing as the example:
|
|
34
|
+
#
|
|
35
|
+
# BookMe+ -> every section of this segment; ticking a Mini App never hides one here
|
|
36
|
+
# Wing -> sections of this segment with Wing ticked; if none, the BookMe+ ones
|
|
37
|
+
scope :for_feed, lambda { |segment, mini_app = nil|
|
|
38
|
+
name = mini_app.to_s.to_sym
|
|
39
|
+
everything = filter_by_segment(segment)
|
|
40
|
+
next everything unless BIT_MINI_APP.key?(name)
|
|
41
|
+
|
|
42
|
+
curated = filter_by_mini_app(name)
|
|
43
|
+
curated = curated.filter_by_segment(segment) unless MINI_APP_HOME_SEGMENT[name] == segment.to_sym
|
|
44
|
+
|
|
45
|
+
curated.exists? ? curated : everything
|
|
46
|
+
}
|
|
47
|
+
|
|
23
48
|
# Create maintaining task to purge homepage section related caches
|
|
24
49
|
after_save { SpreeCmCommissioner::MaintenanceTasks::CacheInvalidation.pending.create_or_find_by(maintainable: self) }
|
|
25
50
|
end
|
|
@@ -14,10 +14,25 @@ module SpreeCmCommissioner
|
|
|
14
14
|
validates :incremental_sync_interval_seconds, presence: true, numericality: { only_integer: true, greater_than: 0, less_than_or_equal_to: 3600 }
|
|
15
15
|
validates :full_sync_interval_hours, presence: true, numericality: { only_integer: true, greater_than: 0, less_than_or_equal_to: 168 }
|
|
16
16
|
|
|
17
|
+
# Listed from the folder rather than `descendants`, which misses subclasses Zeitwerk hasn't
|
|
18
|
+
# loaded yet in dev and test.
|
|
19
|
+
def self.types
|
|
20
|
+
Dir[SpreeCmCommissioner::Engine.root.join('app/models/spree_cm_commissioner/integrations/*.rb')]
|
|
21
|
+
.map { |path| "SpreeCmCommissioner::Integrations::#{File.basename(path, '.rb').camelize}" }
|
|
22
|
+
.sort
|
|
23
|
+
end
|
|
24
|
+
|
|
17
25
|
def sync_manager
|
|
18
26
|
raise NotImplementedError, 'Subclasses must implement the sync_manager method'
|
|
19
27
|
end
|
|
20
28
|
|
|
29
|
+
# Whether the partner, not BookMe+, sets this variant's per-night stock, prices and closings.
|
|
30
|
+
# When true, stock resets and dashboard edits leave its inventory items alone, and new nights
|
|
31
|
+
# start closed until the partner opens them (InventoryItem). Off by default.
|
|
32
|
+
def inventory_external_wins?(_variant)
|
|
33
|
+
false
|
|
34
|
+
end
|
|
35
|
+
|
|
21
36
|
def restock_external_inventory!(_order, _line_items)
|
|
22
37
|
raise NotImplementedError, 'Subclasses must implement the restock_external_inventory! method'
|
|
23
38
|
end
|
|
@@ -25,5 +40,36 @@ module SpreeCmCommissioner
|
|
|
25
40
|
def unstock_external_inventory!(_order, _line_items)
|
|
26
41
|
raise NotImplementedError, 'Subclasses must implement the unstock_external_inventory! method'
|
|
27
42
|
end
|
|
43
|
+
|
|
44
|
+
def sync_item_availability!(_options = {})
|
|
45
|
+
raise NotImplementedError, 'Subclasses must implement the sync_item_availability! method'
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
def hold_inventory!(_order, _line_items)
|
|
49
|
+
raise NotImplementedError, 'Subclasses must implement the hold_inventory! method'
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
# Integrations that manage external inventory reservations should override this to return true
|
|
53
|
+
def supports_external_inventory_holding?
|
|
54
|
+
false
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
# Integrations that support automatically releasing external inventory
|
|
58
|
+
# after reservation expiration or order cancellation should override this to return true
|
|
59
|
+
def supports_auto_release_inventory?
|
|
60
|
+
false
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
def release_inventory!(_order, _line_items)
|
|
64
|
+
raise NotImplementedError, 'Subclasses must implement the release_inventory! method'
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
def external_inventory_items(_order)
|
|
68
|
+
raise NotImplementedError, 'Subclasses must implement the external_inventory_items method'
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
def external_inventory_item_status(_order, _options)
|
|
72
|
+
raise NotImplementedError, 'Subclasses must implement the external_inventory_item_status method'
|
|
73
|
+
end
|
|
28
74
|
end
|
|
29
75
|
end
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
class SpreeCmCommissioner::Integrations::BookMeBusV1 < SpreeCmCommissioner::Integration
|
|
2
|
+
store_private_metadata :client_id, :string
|
|
3
|
+
store_private_metadata :client_secret, :string
|
|
4
|
+
store_private_metadata :base_url, :string # e.g. 'https://bookmebus.com'
|
|
5
|
+
store_private_metadata :username, :string # Contact email (optional)
|
|
6
|
+
store_private_metadata :access_token, :string
|
|
7
|
+
store_private_metadata :token_expires_at, :datetime
|
|
8
|
+
store_private_metadata :operator_id, :string
|
|
9
|
+
# override
|
|
10
|
+
def sync_manager
|
|
11
|
+
SpreeCmCommissioner::Integrations::BookMeBusV1::SyncManager.new(integration: self)
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
# override
|
|
15
|
+
def unstock_external_inventory!(order, line_items)
|
|
16
|
+
result = SpreeCmCommissioner::Integrations::BookMeBusV1::Inventory::UnstockInventory.new.call(
|
|
17
|
+
integration: self,
|
|
18
|
+
order: order,
|
|
19
|
+
line_items: line_items
|
|
20
|
+
)
|
|
21
|
+
|
|
22
|
+
raise SpreeCmCommissioner::Integrations::SyncError, result.error unless result.success?
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
# override
|
|
26
|
+
# BookMeBus has no working cancellation API (its public reservation-cancellation endpoint is
|
|
27
|
+
# disabled in production), so there is nothing to actually restock on their side. Raise instead
|
|
28
|
+
# of silently succeeding - matching VireakBuntham/Larryta - so order cancellation surfaces this
|
|
29
|
+
# loudly rather than letting local inventory quietly diverge from what BookMeBus still shows as
|
|
30
|
+
# booked (a silent no-op here would let BookMe+ resell a seat BookMeBus still considers taken).
|
|
31
|
+
def restock_external_inventory!(_order, _line_items)
|
|
32
|
+
raise SpreeCmCommissioner::Integrations::SyncError, 'Booking cannot be cancelled once confirmed'
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
# override - Fetch all external inventory items
|
|
36
|
+
def external_inventory_items(order)
|
|
37
|
+
SpreeCmCommissioner::Integrations::BookMeBusV1::Inventory::ExternalInventoryItems.new.call(
|
|
38
|
+
integration: self,
|
|
39
|
+
order: order
|
|
40
|
+
)
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
# override - Fetch detailed status for a specific external inventory item
|
|
44
|
+
def external_inventory_item_status(order, options)
|
|
45
|
+
SpreeCmCommissioner::Integrations::BookMeBusV1::Inventory::ExternalInventoryItemStatus.new.call(
|
|
46
|
+
integration: self,
|
|
47
|
+
order: order,
|
|
48
|
+
options: options
|
|
49
|
+
)
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
def sync_item_availability!(options = {})
|
|
53
|
+
SpreeCmCommissioner::Integrations::BookMeBusV1::Polling::SyncItemAvailability.new(
|
|
54
|
+
integration: self
|
|
55
|
+
).call(
|
|
56
|
+
trip_id: options[:trip_id],
|
|
57
|
+
on_date: options[:on_date]
|
|
58
|
+
)
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
def client
|
|
62
|
+
@client ||= SpreeCmCommissioner::Integrations::BookMeBusV1::ExternalClient::Client.new(integration: self)
|
|
63
|
+
end
|
|
64
|
+
end
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
class SpreeCmCommissioner::Integrations::Larryta < SpreeCmCommissioner::Integration
|
|
2
|
+
store_private_metadata :base_url, :string
|
|
3
|
+
store_private_metadata :username, :string
|
|
4
|
+
store_private_metadata :api_token, :string
|
|
5
|
+
store_private_metadata :api_uid, :string
|
|
6
|
+
store_private_metadata :sync_days, :integer, default: 7
|
|
7
|
+
|
|
8
|
+
def sync_manager
|
|
9
|
+
SpreeCmCommissioner::Integrations::Larryta::SyncManager.new(integration: self)
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def hold_inventory!(order, line_items)
|
|
13
|
+
result = SpreeCmCommissioner::Integrations::Larryta::Inventory::HoldSeats.new.call(
|
|
14
|
+
integration: self,
|
|
15
|
+
order: order,
|
|
16
|
+
line_items: line_items
|
|
17
|
+
)
|
|
18
|
+
|
|
19
|
+
raise SpreeCmCommissioner::Integrations::SyncError, result.error unless result.success?
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def unstock_external_inventory!(order, line_items)
|
|
23
|
+
result = SpreeCmCommissioner::Integrations::Larryta::Inventory::UnstockInventory.new.call(
|
|
24
|
+
integration: self,
|
|
25
|
+
order: order,
|
|
26
|
+
line_items: line_items
|
|
27
|
+
)
|
|
28
|
+
|
|
29
|
+
raise SpreeCmCommissioner::Integrations::SyncError, result.error unless result.success?
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def supports_external_inventory_holding?
|
|
33
|
+
true
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def restock_external_inventory!(_order, _line_items)
|
|
37
|
+
raise SpreeCmCommissioner::Integrations::SyncError, 'Booking cannot be cancelled once confirmed'
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def supports_auto_release_inventory?
|
|
41
|
+
true
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
def release_inventory!(order, line_items)
|
|
45
|
+
SpreeCmCommissioner::Integrations::Larryta::Inventory::ReleaseSeats.new.call(
|
|
46
|
+
integration: self,
|
|
47
|
+
order: order,
|
|
48
|
+
line_items: line_items
|
|
49
|
+
)
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
def sync_item_availability!(options = {})
|
|
53
|
+
SpreeCmCommissioner::Integrations::Larryta::Polling::SyncItemAvailability.new(
|
|
54
|
+
integration: self
|
|
55
|
+
).call(
|
|
56
|
+
trip_id: options[:trip_id],
|
|
57
|
+
on_date: options[:on_date]
|
|
58
|
+
)
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
# override - Fetch all external inventory items for an order
|
|
62
|
+
def external_inventory_items(order)
|
|
63
|
+
SpreeCmCommissioner::Integrations::Larryta::Inventory::ExternalInventoryItems.new.call(
|
|
64
|
+
integration: self,
|
|
65
|
+
order: order
|
|
66
|
+
)
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
# override - Fetch detailed status for a specific external inventory item
|
|
70
|
+
def external_inventory_item_status(order, options)
|
|
71
|
+
SpreeCmCommissioner::Integrations::Larryta::Inventory::ExternalInventoryItemStatus.new.call(
|
|
72
|
+
integration: self,
|
|
73
|
+
order: order,
|
|
74
|
+
options: options
|
|
75
|
+
)
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
def client
|
|
79
|
+
@client ||= SpreeCmCommissioner::Integrations::Larryta::ExternalClient::Client.new(integration: self)
|
|
80
|
+
end
|
|
81
|
+
end
|
|
@@ -5,10 +5,7 @@ class SpreeCmCommissioner::Integrations::StadiumXV1 < SpreeCmCommissioner::Integ
|
|
|
5
5
|
|
|
6
6
|
# override
|
|
7
7
|
def sync_manager
|
|
8
|
-
SpreeCmCommissioner::Integrations::StadiumXV1::SyncManager.new(
|
|
9
|
-
integration: self,
|
|
10
|
-
client: client
|
|
11
|
-
)
|
|
8
|
+
SpreeCmCommissioner::Integrations::StadiumXV1::SyncManager.new(integration: self)
|
|
12
9
|
end
|
|
13
10
|
|
|
14
11
|
# override
|
|
@@ -18,7 +15,6 @@ class SpreeCmCommissioner::Integrations::StadiumXV1 < SpreeCmCommissioner::Integ
|
|
|
18
15
|
order: order,
|
|
19
16
|
line_items: line_items
|
|
20
17
|
)
|
|
21
|
-
|
|
22
18
|
raise SpreeCmCommissioner::Integrations::SyncError, result.error unless result.success?
|
|
23
19
|
end
|
|
24
20
|
|
|
@@ -27,11 +23,9 @@ class SpreeCmCommissioner::Integrations::StadiumXV1 < SpreeCmCommissioner::Integ
|
|
|
27
23
|
raise SpreeCmCommissioner::Integrations::SyncError, 'Ticket cannot be cancelled'
|
|
28
24
|
end
|
|
29
25
|
|
|
26
|
+
def sync_item_availability!(_options = {}); end
|
|
27
|
+
|
|
30
28
|
def client
|
|
31
|
-
SpreeCmCommissioner::Integrations::StadiumXV1::ExternalClient::Client.new(
|
|
32
|
-
public_key: public_key,
|
|
33
|
-
private_key: private_key,
|
|
34
|
-
base_url: base_url
|
|
35
|
-
)
|
|
29
|
+
@client ||= SpreeCmCommissioner::Integrations::StadiumXV1::ExternalClient::Client.new(integration: self)
|
|
36
30
|
end
|
|
37
31
|
end
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
# A hotel connected to switch.cm, which calls our Booking.com-compatible channel-manager API
|
|
2
|
+
# (Spree::Api::SwitchCm) instead of us calling it: it pushes availability, prices and
|
|
3
|
+
# restrictions, and pulls reservations. So there's nothing to pull, hold or unstock externally;
|
|
4
|
+
# polling only runs our own checks (SyncManager).
|
|
5
|
+
#
|
|
6
|
+
# Every switch.cm row shares one Doorkeeper app; the hotels switch.cm may manage are the rows
|
|
7
|
+
# pointing at it. `paused` is the setup state: switch.cm can read the hotel (room list,
|
|
8
|
+
# reservation summary) while it keeps selling on its own counts. `active` with conflict strategy
|
|
9
|
+
# `external_wins` hands the rooms' availability, prices and restrictions over to switch.cm.
|
|
10
|
+
class SpreeCmCommissioner::Integrations::SwitchCm < SpreeCmCommissioner::Integration
|
|
11
|
+
READABLE_STATUSES = %i[active paused].freeze
|
|
12
|
+
|
|
13
|
+
belongs_to :oauth_application, class_name: 'Spree::OauthApplication', optional: false
|
|
14
|
+
|
|
15
|
+
# Reservations completed before this aren't in the reservation feeds: switch.cm imports them
|
|
16
|
+
# through the reservation summary while setting the hotel up.
|
|
17
|
+
store_public_metadata :connected_at, :datetime
|
|
18
|
+
|
|
19
|
+
before_create :set_connected_at
|
|
20
|
+
after_commit :sync_unpushed_nights, on: %i[create update]
|
|
21
|
+
|
|
22
|
+
# Polling runs the hotel's periodic checks (SyncManager), and the incremental one alerts on
|
|
23
|
+
# every run while reservations sit unpulled, so the column's 10-second default would flood
|
|
24
|
+
# Telegram. Full runs daily upkeep.
|
|
25
|
+
attribute :incremental_sync_interval_seconds, :integer, default: -> { SyncStrategies::IncrementalSyncStrategy::UNSEEN_AFTER.to_i }
|
|
26
|
+
|
|
27
|
+
scope :readable, -> { where(status: READABLE_STATUSES) }
|
|
28
|
+
|
|
29
|
+
# override
|
|
30
|
+
def sync_manager
|
|
31
|
+
SyncManager.new(integration: self)
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
# Only for rooms switch.cm can see (RoomCatalog): a hotel's other products, and rooms that need
|
|
35
|
+
# confirmation, keep BookMe+ counts since switch.cm never pushes them.
|
|
36
|
+
# override
|
|
37
|
+
def inventory_external_wins?(variant)
|
|
38
|
+
owns_inventory? && variant.accommodation? && !variant.need_confirmation?
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
def owns_inventory?
|
|
42
|
+
active? && external_wins?
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
# switch.cm pulls reservations, so a BookMe+ sale needs nothing from it at checkout, and the
|
|
46
|
+
# hotel's own cancellation rules decide whether a booking can be cancelled.
|
|
47
|
+
# override
|
|
48
|
+
def unstock_external_inventory!(_order, _line_items); end
|
|
49
|
+
|
|
50
|
+
# override
|
|
51
|
+
def restock_external_inventory!(_order, _line_items); end
|
|
52
|
+
|
|
53
|
+
# override
|
|
54
|
+
def sync_item_availability!(_options = {}); end
|
|
55
|
+
|
|
56
|
+
# override
|
|
57
|
+
def external_inventory_items(_order)
|
|
58
|
+
[]
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
# override
|
|
62
|
+
def external_inventory_item_status(_order, _options)
|
|
63
|
+
{}
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
# While switch.cm owns the rooms, nights it hasn't pushed stay closed, so the admin page shows
|
|
67
|
+
# this until the first full push lands.
|
|
68
|
+
def push_progress
|
|
69
|
+
PushedNights.new(self).progress
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
private
|
|
73
|
+
|
|
74
|
+
def set_connected_at
|
|
75
|
+
self.connected_at ||= Time.current.iso8601
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
# Taking over closes the rooms' unpushed nights; letting go (paused, disconnected, or another
|
|
79
|
+
# conflict strategy) reopens them, while pushed nights keep switch.cm's last numbers.
|
|
80
|
+
def sync_unpushed_nights
|
|
81
|
+
owned_before = status_before_last_save.to_s == 'active' && conflict_strategy_before_last_save.to_s == 'external_wins'
|
|
82
|
+
return if owned_before == owns_inventory?
|
|
83
|
+
|
|
84
|
+
PushedNights.new(self).reconcile_unpushed!
|
|
85
|
+
end
|
|
86
|
+
end
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
class SpreeCmCommissioner::Integrations::VireakBuntham < SpreeCmCommissioner::Integration
|
|
2
|
+
# VET API requires a `type` parameter to scope responses to a vehicle category.
|
|
3
|
+
# Each VireakBuntham integration instance represents one category.
|
|
4
|
+
ROUTE_TYPES = { bus: 1, ferry: 2, air_bus: 3 }.freeze
|
|
5
|
+
|
|
6
|
+
store_private_metadata :api_url, :string
|
|
7
|
+
store_private_metadata :api_token, :string
|
|
8
|
+
store_private_metadata :callback_username, :string
|
|
9
|
+
store_private_metadata :callback_pwd, :string
|
|
10
|
+
store_private_metadata :sync_days, :integer, default: 7
|
|
11
|
+
store_private_metadata :route_type, :integer, default: 1
|
|
12
|
+
|
|
13
|
+
validate :route_type_must_be_valid
|
|
14
|
+
|
|
15
|
+
def route_type_name
|
|
16
|
+
ROUTE_TYPES.key(route_type.to_i)
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
# VET `type` query param used by /destination/from, /schedule/list, /schedule/listByDate
|
|
20
|
+
def vet_type_param
|
|
21
|
+
route_type.to_i
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def sync_manager
|
|
25
|
+
SpreeCmCommissioner::Integrations::VireakBuntham::SyncManager.new(integration: self)
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
# Confirm and finalize booking on VET: POST /booking/confirm + /booking/complete in atomic transaction.
|
|
29
|
+
def unstock_external_inventory!(order, line_items)
|
|
30
|
+
result = SpreeCmCommissioner::Integrations::VireakBuntham::Inventory::UnstockInventory.new.call(
|
|
31
|
+
integration: self,
|
|
32
|
+
order: order,
|
|
33
|
+
line_items: line_items
|
|
34
|
+
)
|
|
35
|
+
|
|
36
|
+
raise SpreeCmCommissioner::Integrations::SyncError, result.error unless result.success?
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
def restock_external_inventory!(_order, _line_items)
|
|
40
|
+
raise SpreeCmCommissioner::Integrations::SyncError, 'Booking cannot be cancelled once confirmed'
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
# Sync trip prices and reserved seats from VET via GET /schedule/list + /seat/unavailable APIs.
|
|
44
|
+
def sync_item_availability!(options = {})
|
|
45
|
+
SpreeCmCommissioner::Integrations::VireakBuntham::Polling::SyncItemAvailability.new(
|
|
46
|
+
integration: self
|
|
47
|
+
).call(
|
|
48
|
+
trip_id: options[:trip_id],
|
|
49
|
+
on_date: options[:on_date]
|
|
50
|
+
)
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
# Fetch available seats and prices from VET for order's trips.
|
|
54
|
+
def external_inventory_items(order)
|
|
55
|
+
SpreeCmCommissioner::Integrations::VireakBuntham::Inventory::ExternalInventoryItems.new.call(
|
|
56
|
+
integration: self,
|
|
57
|
+
order: order
|
|
58
|
+
)
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
# Check seat availability and pricing for specific trip/date/nationality from VET.
|
|
62
|
+
def external_inventory_item_status(order, options)
|
|
63
|
+
SpreeCmCommissioner::Integrations::VireakBuntham::Inventory::ExternalInventoryItemStatus.new.call(
|
|
64
|
+
integration: self,
|
|
65
|
+
order: order,
|
|
66
|
+
options: options
|
|
67
|
+
)
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
# Lazy-loaded HTTP client instance for all VET API calls.
|
|
71
|
+
def client
|
|
72
|
+
@client ||= SpreeCmCommissioner::Integrations::VireakBuntham::ExternalClient::Client.new(integration: self)
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
private
|
|
76
|
+
|
|
77
|
+
def route_type_must_be_valid
|
|
78
|
+
return if ROUTE_TYPES.values.include?(route_type.to_i)
|
|
79
|
+
|
|
80
|
+
errors.add(:route_type, "must be one of: #{ROUTE_TYPES.values.join(', ')}")
|
|
81
|
+
end
|
|
82
|
+
end
|