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
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
module SpreeCmCommissioner::Integrations::BookMeBusV1::Polling
|
|
2
|
+
class SyncLocations
|
|
3
|
+
# Initialize SyncLocations service
|
|
4
|
+
# @param integration [SpreeCmCommissioner::Integrations::BookMeBusV1] The integration instance
|
|
5
|
+
# @param sync_result [SpreeCmCommissioner::Integrations::Base::SyncResult] Optional sync result tracker
|
|
6
|
+
def initialize(integration:, sync_result: nil)
|
|
7
|
+
@integration = integration
|
|
8
|
+
@sync_result = sync_result || SpreeCmCommissioner::Integrations::Base::SyncResult.new
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
def call
|
|
12
|
+
raise SpreeCmCommissioner::Integrations::SyncError, 'Integration must have a vendor to sync locations' if @integration.vendor.blank?
|
|
13
|
+
|
|
14
|
+
synced_location_mappings = sync_items!
|
|
15
|
+
|
|
16
|
+
cleanup_stale_mappings!(synced_location_mappings)
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
private
|
|
20
|
+
|
|
21
|
+
def client
|
|
22
|
+
@client ||= @integration.client
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def geocoder
|
|
26
|
+
@geocoder ||= SpreeCmCommissioner::GoogleMapsGeocoder.new
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
# Sync all available locations from the API and create them as places
|
|
30
|
+
def sync_items!
|
|
31
|
+
external_locations = begin
|
|
32
|
+
@sync_result.track_api_call('get_locations') { client.get_locations! }
|
|
33
|
+
rescue SpreeCmCommissioner::Integrations::ExternalClientError => e
|
|
34
|
+
raise SpreeCmCommissioner::Integrations::SyncError, "Failed to sync locations: #{e.message}"
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
external_locations.map do |external_location|
|
|
38
|
+
sync_item!(external_location)
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
# Sync a single location and create it as a place with vendor place mapping
|
|
43
|
+
def sync_item!(external_location)
|
|
44
|
+
mapping = SpreeCmCommissioner::VendorPlace.find_or_initialize_integration_mapping(
|
|
45
|
+
integration_id: @integration.id,
|
|
46
|
+
external_id: external_location.id
|
|
47
|
+
)
|
|
48
|
+
|
|
49
|
+
vendor_place = mapping.internal
|
|
50
|
+
|
|
51
|
+
place = vendor_place.place ||
|
|
52
|
+
SpreeCmCommissioner::Place.find_by(name: external_location.name) ||
|
|
53
|
+
SpreeCmCommissioner::Place.new
|
|
54
|
+
|
|
55
|
+
@sync_result.track(:location, place) do |tracker|
|
|
56
|
+
if place.new_record? || place.lat.nil? || place.lon.nil?
|
|
57
|
+
location_data = geocoder.fetch_location_data(external_location.name, external_location.country_name)
|
|
58
|
+
place.assign_attributes(
|
|
59
|
+
lat: location_data[:lat],
|
|
60
|
+
lon: location_data[:lon],
|
|
61
|
+
reference: location_data[:reference]
|
|
62
|
+
)
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
place.assign_attributes(
|
|
66
|
+
name: external_location.name,
|
|
67
|
+
code: external_location.abbreviation.presence || external_location.slug.presence
|
|
68
|
+
)
|
|
69
|
+
|
|
70
|
+
tracker.save_if_changed!(place)
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
@sync_result.track(:vendor_place, vendor_place) do |tracker|
|
|
74
|
+
vendor_place.assign_attributes(
|
|
75
|
+
vendor_id: @integration.vendor.id,
|
|
76
|
+
place_type: :location,
|
|
77
|
+
place: place
|
|
78
|
+
)
|
|
79
|
+
tracker.save_if_changed!(vendor_place)
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
mapping.mark_as_active!(external_payload: external_location.to_h)
|
|
83
|
+
mapping
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
# Archive stale location mappings that are no longer in the external API
|
|
87
|
+
def cleanup_stale_mappings!(synced_location_mappings)
|
|
88
|
+
synced_ids = synced_location_mappings.to_set(&:external_id)
|
|
89
|
+
active_mappings = SpreeCmCommissioner::IntegrationMapping.where(
|
|
90
|
+
integration_id: @integration.id,
|
|
91
|
+
internal_type: 'SpreeCmCommissioner::VendorPlace',
|
|
92
|
+
status: :active
|
|
93
|
+
)
|
|
94
|
+
|
|
95
|
+
active_mappings.find_each do |mapping|
|
|
96
|
+
next if synced_ids.include?(mapping.external_id)
|
|
97
|
+
|
|
98
|
+
mapping.mark_as_archived!
|
|
99
|
+
@sync_result.increment_metric(:location, :archived)
|
|
100
|
+
end
|
|
101
|
+
end
|
|
102
|
+
end
|
|
103
|
+
end
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
module SpreeCmCommissioner::Integrations::BookMeBusV1::Polling
|
|
2
|
+
# SyncNationalityPricing - applies BookMeBus local/non-local pricing to a Route via a
|
|
3
|
+
# PricingModel, so BookMe+ charges the correct price based on the ACTUAL guest's nationality at
|
|
4
|
+
# checkout instead of always flatly charging non_local_price (Resources::Trip#sellable_price,
|
|
5
|
+
# unchanged by this class) to every customer.
|
|
6
|
+
#
|
|
7
|
+
# Deliberately keeps non_local_price as the base (matching sellable_price's existing default)
|
|
8
|
+
# and layers a DISCOUNT adjustment on top for guests who positively match the vendor's own
|
|
9
|
+
# nationality - the opposite direction from the sibling VireakBuntham integration's
|
|
10
|
+
# SyncNationalityPricing, which stores local as the base and surcharges foreigners. That
|
|
11
|
+
# direction is riskier here: real storefront checkout (spree/api/v2/storefront/guests_controller.rb)
|
|
12
|
+
# only ever permits `nationality_id`, never `nationality_group`, so any guest whose nationality
|
|
13
|
+
# can't be positively matched must fall back to today's already-approved default (non_local),
|
|
14
|
+
# not silently receive the cheaper local price.
|
|
15
|
+
#
|
|
16
|
+
# Uses PricingRules::Nationality (not NationalityGroup, which VET uses) for the same reason -
|
|
17
|
+
# Nationality matches on nationality_id, which real guests actually have; NationalityGroup
|
|
18
|
+
# requires nationality_group, which real storefront checkout never sets.
|
|
19
|
+
#
|
|
20
|
+
# One PricingModel per vendor; one PricingRuleGroup per route (so each route gets its own
|
|
21
|
+
# independent FlatRate calculator) - confirmed per-route granularity with the user: if two trips
|
|
22
|
+
# on the same route ever have genuinely different local/non-local differentials, the most
|
|
23
|
+
# recently synced one wins for the whole route.
|
|
24
|
+
#
|
|
25
|
+
# Skipped when there's no real differential to encode (either price missing, or they're equal -
|
|
26
|
+
# both are always present per BookMeBus's own price_by_nationalities, so equality is the
|
|
27
|
+
# meaningful no-op signal, not blankness).
|
|
28
|
+
class SyncNationalityPricing
|
|
29
|
+
PRICING_MODEL_NAME = 'BookMeBus Nationality Pricing'.freeze
|
|
30
|
+
|
|
31
|
+
def initialize(route:, vendor:, local_price:, non_local_price:)
|
|
32
|
+
@route = route
|
|
33
|
+
@vendor = vendor
|
|
34
|
+
@local_price = local_price&.to_f
|
|
35
|
+
@non_local_price = non_local_price&.to_f
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
def call
|
|
39
|
+
return if @local_price.nil? || @non_local_price.nil? || @local_price == @non_local_price
|
|
40
|
+
return if @route.blank? || @vendor.blank?
|
|
41
|
+
|
|
42
|
+
local_taxon = @vendor.service_origin
|
|
43
|
+
return if local_taxon.blank?
|
|
44
|
+
|
|
45
|
+
delta = @local_price - @non_local_price
|
|
46
|
+
|
|
47
|
+
model = find_or_create_pricing_model
|
|
48
|
+
group = find_or_create_rule_group(model, local_taxon)
|
|
49
|
+
action = find_or_create_action(group, delta)
|
|
50
|
+
|
|
51
|
+
action.calculator.update!(preferred_amount: delta)
|
|
52
|
+
find_or_create_model_route(model)
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
private
|
|
56
|
+
|
|
57
|
+
def find_or_create_pricing_model
|
|
58
|
+
@vendor.pricing_models.find_or_create_by!(name: PRICING_MODEL_NAME) do |m|
|
|
59
|
+
m.status = :active
|
|
60
|
+
end
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
def find_or_create_rule_group(model, local_taxon)
|
|
64
|
+
group_name = "Local (#{@route.route_name})"
|
|
65
|
+
model.pricing_rule_groups.find_or_create_by!(name: group_name) do |g|
|
|
66
|
+
g.status = :active
|
|
67
|
+
g.private_metadata = { match_type: 'any' }
|
|
68
|
+
g.pricing_rules.build(
|
|
69
|
+
type: 'SpreeCmCommissioner::PricingRules::Nationality',
|
|
70
|
+
private_metadata: { nationalities: [local_taxon.id], rule_type: 'any' }
|
|
71
|
+
)
|
|
72
|
+
end
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
def find_or_create_action(group, delta)
|
|
76
|
+
group.pricing_action || group.create_pricing_action!(
|
|
77
|
+
type: 'SpreeCmCommissioner::PricingActions::CreateGuestAdjustments',
|
|
78
|
+
calculator: Spree::Calculator::FlatRate.new(
|
|
79
|
+
preferred_amount: delta,
|
|
80
|
+
preferred_currency: Spree::Store.default.default_currency
|
|
81
|
+
)
|
|
82
|
+
)
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
def find_or_create_model_route(model)
|
|
86
|
+
model.pricing_model_routes.find_or_create_by!(
|
|
87
|
+
route: @route,
|
|
88
|
+
origin_place: @route.origin_place,
|
|
89
|
+
destination_place: @route.destination_place
|
|
90
|
+
)
|
|
91
|
+
end
|
|
92
|
+
end
|
|
93
|
+
end
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
module SpreeCmCommissioner::Integrations::BookMeBusV1::Polling
|
|
2
|
+
# SyncProduct handles product synchronization for BookMeBus trips
|
|
3
|
+
#
|
|
4
|
+
# This service is responsible for:
|
|
5
|
+
# - Creating or updating Product records from external trip data
|
|
6
|
+
# - Managing product attributes (name, status, pricing)
|
|
7
|
+
# - Handling integration mappings for products
|
|
8
|
+
#
|
|
9
|
+
# Usage:
|
|
10
|
+
# sync_service = SyncProduct.new(integration: integration, sync_result: sync_result)
|
|
11
|
+
# product = sync_service.call(external_trip: external_trip, price: 10.0)
|
|
12
|
+
#
|
|
13
|
+
class SyncProduct
|
|
14
|
+
def initialize(integration:, sync_result:)
|
|
15
|
+
@integration = integration
|
|
16
|
+
@sync_result = sync_result
|
|
17
|
+
@vendor = @integration.vendor
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def call(external_trip:, price:)
|
|
21
|
+
external_id = "product-#{external_trip.trip_id}"
|
|
22
|
+
|
|
23
|
+
mapping = Spree::Product.find_or_initialize_integration_mapping(
|
|
24
|
+
integration_id: @integration.id,
|
|
25
|
+
external_id: external_id
|
|
26
|
+
)
|
|
27
|
+
|
|
28
|
+
product = mapping.internal
|
|
29
|
+
|
|
30
|
+
@sync_result.track(:product, product) do |tracker|
|
|
31
|
+
product.assign_attributes(
|
|
32
|
+
name: build_product_name(external_trip),
|
|
33
|
+
status: :active,
|
|
34
|
+
vendor: @vendor,
|
|
35
|
+
product_type: :transit,
|
|
36
|
+
available_on: Time.current,
|
|
37
|
+
shipping_category: default_shipping_category,
|
|
38
|
+
stores: [Spree::Store.default],
|
|
39
|
+
option_types: [seat_type_option].compact,
|
|
40
|
+
price: price
|
|
41
|
+
)
|
|
42
|
+
tracker.save_if_changed!(product)
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
mapping.mark_as_active!(external_payload: { trip_id: external_trip.trip_id })
|
|
46
|
+
product
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
private
|
|
50
|
+
|
|
51
|
+
def build_product_name(external_trip)
|
|
52
|
+
origin_name = external_trip.origin_location_name
|
|
53
|
+
destination_name = external_trip.destination_location_name
|
|
54
|
+
"#{origin_name} → #{destination_name}"
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
def seat_type_option
|
|
58
|
+
# find_or_create_by! (not find_by) - matches SyncVariant's own lookup for the same OptionType,
|
|
59
|
+
# so the very first sync in a fresh environment doesn't create a Product with no option_types
|
|
60
|
+
# just because SyncProduct happened to run before the type existed.
|
|
61
|
+
@seat_type_option ||= Spree::OptionType.find_or_create_by!(name: 'seat-type') do |ot|
|
|
62
|
+
ot.presentation = 'Seat Type'
|
|
63
|
+
end
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
def default_shipping_category
|
|
67
|
+
@default_shipping_category ||= Spree::ShippingCategory.first_or_create!(name: 'Default')
|
|
68
|
+
end
|
|
69
|
+
end
|
|
70
|
+
end
|
data/app/services/spree_cm_commissioner/integrations/book_me_bus_v1/polling/sync_route_stops.rb
ADDED
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
module SpreeCmCommissioner::Integrations::BookMeBusV1::Polling
|
|
2
|
+
# SyncRouteStops synchronizes route stops from BookMeBus trips
|
|
3
|
+
#
|
|
4
|
+
# Structure follows SyncZones pattern:
|
|
5
|
+
# - initialize: Setup integration and sync_result
|
|
6
|
+
# - call: Main entry point, validates inputs, orchestrates sync
|
|
7
|
+
# - private methods: Implementation details
|
|
8
|
+
class SyncRouteStops
|
|
9
|
+
def initialize(integration:, sync_result:, route:)
|
|
10
|
+
@integration = integration
|
|
11
|
+
@sync_result = sync_result
|
|
12
|
+
@route = route
|
|
13
|
+
@location_mappings = {}
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def call(external_trips:)
|
|
17
|
+
raise SpreeCmCommissioner::Integrations::SyncError, 'external_trips is required' if external_trips.blank?
|
|
18
|
+
raise SpreeCmCommissioner::Integrations::SyncError, 'route is required' if @route.blank?
|
|
19
|
+
|
|
20
|
+
preload_location_mappings!
|
|
21
|
+
sync_route_stops!(external_trips)
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
private
|
|
25
|
+
|
|
26
|
+
# Preload all location mappings to avoid N+1 queries
|
|
27
|
+
def preload_location_mappings!
|
|
28
|
+
@location_mappings = SpreeCmCommissioner::IntegrationMapping
|
|
29
|
+
.where(
|
|
30
|
+
integration_id: @integration.id,
|
|
31
|
+
internal_type: 'SpreeCmCommissioner::VendorPlace',
|
|
32
|
+
status: :active
|
|
33
|
+
)
|
|
34
|
+
.includes(:internal)
|
|
35
|
+
.index_by(&:external_id)
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
def sync_route_stops!(external_trips)
|
|
39
|
+
return unless @route && external_trips.any?
|
|
40
|
+
|
|
41
|
+
route_stops = build_route_stops_from(external_trips)
|
|
42
|
+
return if route_stops.empty?
|
|
43
|
+
|
|
44
|
+
update_route_stops(route_stops)
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
def build_route_stops_from(external_trips)
|
|
48
|
+
external_trips.flat_map do |trip|
|
|
49
|
+
next [] unless trip.stop_times.is_a?(Array)
|
|
50
|
+
|
|
51
|
+
trip.stop_times.each_with_index.filter_map do |stop_time, index|
|
|
52
|
+
build_route_stop_from(stop_time, index + 1)
|
|
53
|
+
end
|
|
54
|
+
end
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
def build_route_stop_from(stop_time, sequence)
|
|
58
|
+
stop_data = stop_time['stop']
|
|
59
|
+
location_data = stop_time['location']
|
|
60
|
+
return nil unless stop_data && location_data
|
|
61
|
+
|
|
62
|
+
external_stop = SpreeCmCommissioner::Integrations::BookMeBusV1::Resources::Stop.new(stop_data.merge('location' => location_data))
|
|
63
|
+
vendor_place = find_vendor_place_for(external_stop.unique_external_id)
|
|
64
|
+
location_vendor_place = find_vendor_place_for(location_data['id'])
|
|
65
|
+
return nil unless vendor_place && location_vendor_place
|
|
66
|
+
|
|
67
|
+
# Use actual sequence from stop_times order to preserve trip structure
|
|
68
|
+
{
|
|
69
|
+
vendor_place_id: vendor_place.id,
|
|
70
|
+
location_id: location_vendor_place.id,
|
|
71
|
+
type: external_stop.branch? ? :branch : :stop,
|
|
72
|
+
sequence: sequence
|
|
73
|
+
}
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
def update_route_stops(new_stops, retried: false)
|
|
77
|
+
@sync_result.track(:route, @route) do |tracker|
|
|
78
|
+
# Merge new stops with existing ones, preserving existing stops not in new data
|
|
79
|
+
existing = @route.route_stops.to_h.index_by { |s| s[:vendor_place_id] || s['vendor_place_id'] }
|
|
80
|
+
new_stops.each { |stop| existing[stop[:vendor_place_id]] = stop }
|
|
81
|
+
|
|
82
|
+
# Sort by sequence to maintain trip order
|
|
83
|
+
sorted_stops = existing.values.sort_by { |s| s[:sequence] || 0 }
|
|
84
|
+
|
|
85
|
+
# Re-sequence to ensure continuous sequence from 0
|
|
86
|
+
sorted_stops.each_with_index { |stop, idx| stop[:sequence] = idx }
|
|
87
|
+
|
|
88
|
+
# Validate route_stops before assignment
|
|
89
|
+
route_stop_collection = SpreeCmCommissioner::Transit::RouteStopCollection.from_hash(sorted_stops)
|
|
90
|
+
unless route_stop_collection.valid?
|
|
91
|
+
error_message = route_stop_collection.errors.join(', ')
|
|
92
|
+
raise SpreeCmCommissioner::Integrations::SyncError, "Invalid route stops: #{error_message}"
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
@route.route_stops = sorted_stops if @route.route_stops.to_h != sorted_stops
|
|
96
|
+
tracker.save_if_changed!(@route)
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
CmAppLogger.log(
|
|
100
|
+
label: 'SyncRouteStops synced',
|
|
101
|
+
data: { route_id: @route.id, stops_count: new_stops.count }
|
|
102
|
+
)
|
|
103
|
+
rescue ActiveRecord::StaleObjectError
|
|
104
|
+
# A concurrent sync (full + incremental overlapping) updated this route's lock_version
|
|
105
|
+
# between our read and write. Reload to pick up the latest route_stops and merge again,
|
|
106
|
+
# once, rather than losing this sync's stops or aborting the whole run outright.
|
|
107
|
+
raise if retried
|
|
108
|
+
|
|
109
|
+
@route.reload
|
|
110
|
+
update_route_stops(new_stops, retried: true)
|
|
111
|
+
end
|
|
112
|
+
|
|
113
|
+
def find_vendor_place_for(external_id)
|
|
114
|
+
return nil unless external_id
|
|
115
|
+
|
|
116
|
+
# Use preloaded mappings to avoid N+1 queries
|
|
117
|
+
mapping = @location_mappings[external_id.to_s]
|
|
118
|
+
mapping&.internal
|
|
119
|
+
end
|
|
120
|
+
end
|
|
121
|
+
end
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
module SpreeCmCommissioner::Integrations::BookMeBusV1::Polling
|
|
2
|
+
class SyncRoutes
|
|
3
|
+
# Initialize SyncRoutes service
|
|
4
|
+
# @param integration [SpreeCmCommissioner::Integrations::BookMeBusV1] The integration instance
|
|
5
|
+
# @param sync_result [SpreeCmCommissioner::Integrations::Base::SyncResult] Optional sync result tracker
|
|
6
|
+
def initialize(integration:, sync_result: nil)
|
|
7
|
+
@integration = integration
|
|
8
|
+
@sync_result = sync_result || SpreeCmCommissioner::Integrations::Base::SyncResult.new
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
def call
|
|
12
|
+
raise SpreeCmCommissioner::Integrations::SyncError, 'Integration must have a vendor to sync routes' if @integration.vendor.blank?
|
|
13
|
+
|
|
14
|
+
synced_route_mappings = sync_items!
|
|
15
|
+
|
|
16
|
+
cleanup_stale_mappings!(synced_route_mappings)
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
private
|
|
20
|
+
|
|
21
|
+
# Generate routes from location pairs and sync them
|
|
22
|
+
def sync_items!
|
|
23
|
+
location_pairs = build_location_pairs
|
|
24
|
+
return [] if location_pairs.empty?
|
|
25
|
+
|
|
26
|
+
location_pairs.map do |origin_mapping, destination_mapping|
|
|
27
|
+
sync_item!(origin_mapping, destination_mapping)
|
|
28
|
+
end.compact
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
# Sync a single route from location pair
|
|
32
|
+
def sync_item!(origin_mapping, destination_mapping)
|
|
33
|
+
# Extract places from the location mappings
|
|
34
|
+
origin_place = origin_mapping.internal&.place
|
|
35
|
+
destination_place = destination_mapping.internal&.place
|
|
36
|
+
|
|
37
|
+
# Skip if either place is missing (invalid location mapping)
|
|
38
|
+
return nil unless origin_place && destination_place
|
|
39
|
+
|
|
40
|
+
# Build unique external_id from mapping external_ids
|
|
41
|
+
external_id = build_route_external_id(origin_mapping, destination_mapping)
|
|
42
|
+
|
|
43
|
+
# Find or create integration mapping for this route
|
|
44
|
+
mapping = SpreeCmCommissioner::Route.find_or_initialize_integration_mapping(
|
|
45
|
+
integration_id: @integration.id,
|
|
46
|
+
external_id: external_id
|
|
47
|
+
)
|
|
48
|
+
|
|
49
|
+
# Get route from mapping (already created by find_or_initialize_integration_mapping)
|
|
50
|
+
route = mapping.internal
|
|
51
|
+
route.vendor = @integration.vendor if route.new_record?
|
|
52
|
+
|
|
53
|
+
# Update route attributes and save if changed
|
|
54
|
+
@sync_result.track(:route, route) do |tracker|
|
|
55
|
+
route.origin_place = origin_place
|
|
56
|
+
route.destination_place = destination_place
|
|
57
|
+
route.route_name = build_route_name(origin_place, destination_place)
|
|
58
|
+
route.short_name = build_short_name(origin_place, destination_place)
|
|
59
|
+
route.route_type ||= :bus
|
|
60
|
+
tracker.save_if_changed!(route)
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
mapping.mark_as_active!(
|
|
64
|
+
external_payload: {
|
|
65
|
+
origin_place_id: origin_mapping.external_id,
|
|
66
|
+
destination_place_id: destination_mapping.external_id,
|
|
67
|
+
origin_name: origin_mapping.external_payload&.dig('name'),
|
|
68
|
+
destination_name: destination_mapping.external_payload&.dig('name')
|
|
69
|
+
}
|
|
70
|
+
)
|
|
71
|
+
|
|
72
|
+
mapping
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
# Build external_id using mapping external_ids (stable across environments)
|
|
76
|
+
def build_route_external_id(origin_mapping, destination_mapping)
|
|
77
|
+
"route-#{origin_mapping.external_id}-#{destination_mapping.external_id}"
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
# Build all location pairs (A->B and B->A)
|
|
81
|
+
def build_location_pairs
|
|
82
|
+
location_mappings = SpreeCmCommissioner::IntegrationMapping
|
|
83
|
+
.includes(internal: :place)
|
|
84
|
+
.where(
|
|
85
|
+
integration_id: @integration.id,
|
|
86
|
+
internal_type: 'SpreeCmCommissioner::VendorPlace',
|
|
87
|
+
status: :active
|
|
88
|
+
)
|
|
89
|
+
.to_a
|
|
90
|
+
pairs = []
|
|
91
|
+
location_mappings.combination(2).each do |origin, destination|
|
|
92
|
+
pairs << [origin, destination]
|
|
93
|
+
pairs << [destination, origin]
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
pairs
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
# Archive stale route mappings that are no longer in the generated pairs
|
|
100
|
+
def cleanup_stale_mappings!(synced_route_mappings)
|
|
101
|
+
synced_ids = synced_route_mappings.to_set(&:external_id)
|
|
102
|
+
active_mappings = SpreeCmCommissioner::IntegrationMapping.where(
|
|
103
|
+
integration_id: @integration.id,
|
|
104
|
+
internal_type: 'SpreeCmCommissioner::Route',
|
|
105
|
+
status: :active
|
|
106
|
+
)
|
|
107
|
+
|
|
108
|
+
active_mappings.find_each do |mapping|
|
|
109
|
+
next if synced_ids.include?(mapping.external_id)
|
|
110
|
+
|
|
111
|
+
mapping.mark_as_archived!
|
|
112
|
+
@sync_result.increment_metric(:route, :archived)
|
|
113
|
+
end
|
|
114
|
+
end
|
|
115
|
+
|
|
116
|
+
def build_route_name(origin_place, destination_place)
|
|
117
|
+
"#{origin_place.name} - #{destination_place.name}"
|
|
118
|
+
end
|
|
119
|
+
|
|
120
|
+
def build_short_name(origin_place, destination_place)
|
|
121
|
+
"#{place_code(origin_place)}-#{place_code(destination_place)}"
|
|
122
|
+
end
|
|
123
|
+
|
|
124
|
+
def place_code(place)
|
|
125
|
+
place.code.presence || place.id.to_s
|
|
126
|
+
end
|
|
127
|
+
end
|
|
128
|
+
end
|