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,66 @@
|
|
|
1
|
+
module Spree
|
|
2
|
+
module Api
|
|
3
|
+
module V2
|
|
4
|
+
module Storefront
|
|
5
|
+
module Transit
|
|
6
|
+
class ItemAvailabilitiesController < ::Spree::Api::V2::ResourceController
|
|
7
|
+
include SpreeCmCommissioner::Integrations::InventorySyncCachable
|
|
8
|
+
|
|
9
|
+
def create
|
|
10
|
+
sync_result = sync_item_availability!
|
|
11
|
+
|
|
12
|
+
unless sync_result.success?
|
|
13
|
+
error_message = sync_result.to_h.dig(:error, :message) || 'Sync failed'
|
|
14
|
+
return render_error_payload(error_message, 422)
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
item_availability = SpreeCmCommissioner::ItemAvailabilitySync.new(
|
|
18
|
+
id: trip.id,
|
|
19
|
+
last_sync_at: sync_result.last_synced_at
|
|
20
|
+
)
|
|
21
|
+
render_serialized_payload { serialize_resource(item_availability) }
|
|
22
|
+
rescue ActiveRecord::RecordNotFound => e
|
|
23
|
+
render_error_payload(e.message, 404)
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
private
|
|
27
|
+
|
|
28
|
+
def trip
|
|
29
|
+
@trip ||= SpreeCmCommissioner::Trip.find(params[:trip_id])
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def integration
|
|
33
|
+
@integration ||= trip.vendor&.integration
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def sync_item_availability!
|
|
37
|
+
# Return success result if vendor has no integration
|
|
38
|
+
return SpreeCmCommissioner::Integrations::Base::SyncResult.new if integration.nil?
|
|
39
|
+
|
|
40
|
+
# Check cache first for successful results only
|
|
41
|
+
cached_result = Rails.cache.read(sync_cache_key)
|
|
42
|
+
return cached_result if cached_result
|
|
43
|
+
|
|
44
|
+
# Perform sync
|
|
45
|
+
result = integration.sync_item_availability!(trip_id: trip.id, on_date: params[:on_date])
|
|
46
|
+
|
|
47
|
+
# Only cache successful results to avoid sticky failures
|
|
48
|
+
Rails.cache.write(sync_cache_key, result, expires_in: sync_cache_ttl) if result.success?
|
|
49
|
+
|
|
50
|
+
result
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
def sync_cache_key
|
|
54
|
+
# Include trip.updated_at to auto-bust cache when trip is updated by background syncs
|
|
55
|
+
"item_availability_syncs:#{trip.id}:#{params[:on_date]}:#{trip.updated_at.to_i}"
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
def resource_serializer
|
|
59
|
+
SpreeCmCommissioner::V2::Storefront::ItemAvailabilitySyncSerializer
|
|
60
|
+
end
|
|
61
|
+
end
|
|
62
|
+
end
|
|
63
|
+
end
|
|
64
|
+
end
|
|
65
|
+
end
|
|
66
|
+
end
|
|
@@ -26,6 +26,17 @@ module Spree
|
|
|
26
26
|
render_order(result)
|
|
27
27
|
end
|
|
28
28
|
|
|
29
|
+
# Like `advance`, but stops at `payment` (see SpreeCmCommissioner::Checkout::AdvanceToPayment).
|
|
30
|
+
def advance_to_payment
|
|
31
|
+
return render_serialized_payload { serialized_current_order } if spree_current_order.complete?
|
|
32
|
+
|
|
33
|
+
spree_authorize! :update, spree_current_order, order_token
|
|
34
|
+
|
|
35
|
+
result = SpreeCmCommissioner::Checkout::AdvanceToPayment.call(order: spree_current_order)
|
|
36
|
+
|
|
37
|
+
render_order(result)
|
|
38
|
+
end
|
|
39
|
+
|
|
29
40
|
def complete
|
|
30
41
|
return render_serialized_payload { serialized_current_order } if spree_current_order.confirm? || spree_current_order.complete?
|
|
31
42
|
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
module Spree
|
|
2
|
+
module Api
|
|
3
|
+
module V2
|
|
4
|
+
module Tenant
|
|
5
|
+
class VendorPhotosController < BaseController
|
|
6
|
+
def index
|
|
7
|
+
collection = scope.page(params[:page]).per(params[:per_page])
|
|
8
|
+
|
|
9
|
+
render_serialized_payload do
|
|
10
|
+
serialize_collection(collection)
|
|
11
|
+
end
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def scope
|
|
15
|
+
current_vendor.photos.includes(attachment_attachment: :blob)
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def collection_serializer
|
|
19
|
+
Spree::V2::Tenant::AssetSerializer
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
end
|
|
@@ -10,7 +10,7 @@ module SpreeCmCommissioner
|
|
|
10
10
|
close_adjustments cart channel set_channel
|
|
11
11
|
accept_all reject_all alert_request_to_vendor
|
|
12
12
|
notifications fire_notification queue_webhooks_requests update_order_status
|
|
13
|
-
update_internal_note
|
|
13
|
+
update_internal_note line_items
|
|
14
14
|
]
|
|
15
15
|
|
|
16
16
|
base.before_action :initialize_notification_methods, only: %i[
|
|
@@ -82,6 +82,14 @@ module SpreeCmCommissioner
|
|
|
82
82
|
|
|
83
83
|
def notifications; end
|
|
84
84
|
|
|
85
|
+
# Rendered lazily into a turbo_frame from the orders index (see line_items.html.erb),
|
|
86
|
+
# since eager-loading every order's line items on the index would be wasteful.
|
|
87
|
+
def line_items
|
|
88
|
+
@line_items = @order.line_items.includes(:event, variant: :product)
|
|
89
|
+
|
|
90
|
+
render layout: false
|
|
91
|
+
end
|
|
92
|
+
|
|
85
93
|
def update_internal_note
|
|
86
94
|
internal_note = params.dig(:order, :internal_note).to_s.strip.presence
|
|
87
95
|
@order.update_columns(internal_note: internal_note, updated_at: Time.current) # rubocop:disable Rails/SkipsModelValidations
|
data/app/controllers/spree_cm_commissioner/api/v2/storefront/checkout_controller_decorator.rb
CHANGED
|
@@ -5,6 +5,9 @@ module SpreeCmCommissioner
|
|
|
5
5
|
module CheckoutControllerDecorator
|
|
6
6
|
def self.prepended(base)
|
|
7
7
|
base.include SpreeCmCommissioner::OrderConcern
|
|
8
|
+
base.include SpreeCmCommissioner::TurnstileProtectable
|
|
9
|
+
|
|
10
|
+
base.before_action -> { verify_turnstile! }, only: %i[create_payment]
|
|
8
11
|
# Sets Current.load_test_bypass from the X-Request-Verify secret so
|
|
9
12
|
# InventoryHolds::ValidateLimits can skip anti-abuse checks for trusted load tests.
|
|
10
13
|
base.include SpreeCmCommissioner::LoadTestBypassable
|
|
@@ -20,6 +23,18 @@ module SpreeCmCommissioner
|
|
|
20
23
|
|
|
21
24
|
super
|
|
22
25
|
end
|
|
26
|
+
|
|
27
|
+
# Like `advance`, but stops at `payment` (see SpreeCmCommissioner::Checkout::AdvanceToPayment) so the
|
|
28
|
+
# client can attach a payment before confirming.
|
|
29
|
+
def advance_to_payment
|
|
30
|
+
return render_serialized_payload { serialized_current_order } if spree_current_order.complete?
|
|
31
|
+
|
|
32
|
+
spree_authorize! :update, spree_current_order, order_token
|
|
33
|
+
|
|
34
|
+
result = SpreeCmCommissioner::Checkout::AdvanceToPayment.call(order: spree_current_order)
|
|
35
|
+
|
|
36
|
+
render_order(result)
|
|
37
|
+
end
|
|
23
38
|
end
|
|
24
39
|
end
|
|
25
40
|
end
|
|
@@ -200,10 +200,14 @@ module SpreeCmCommissioner
|
|
|
200
200
|
grouped.map { |nationality_group, guests| customer_type_summary(nationality_group, guests) }.join(', ')
|
|
201
201
|
end
|
|
202
202
|
|
|
203
|
-
# e.g. "Local (Adult x1, Child x1)". age_group is
|
|
203
|
+
# e.g. "Local (Adult x1, Child x1)". `cm_saved_guests.age_group` is declared `null: false` in
|
|
204
|
+
# the migration, but a dev/staging DB whose column predates that migration (added via
|
|
205
|
+
# `add_column ... if_not_exists: true`, which silently skips the constraint if the column
|
|
206
|
+
# already exists) can still carry real NULL rows - group those under "Unknown" rather than
|
|
207
|
+
# crashing the whole order-completion Telegram notification on nil.humanize.
|
|
204
208
|
def customer_type_summary(nationality_group, guests)
|
|
205
209
|
age_breakdown = guests
|
|
206
|
-
.group_by { |sg| sg.age_group
|
|
210
|
+
.group_by { |sg| sg.age_group&.humanize || 'Unknown' }
|
|
207
211
|
.map { |age_group, age_guests| "#{age_group} x#{age_guests.count}" }
|
|
208
212
|
.join(', ')
|
|
209
213
|
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
# Builds the audience description shown in the "Send to All" confirmation modal.
|
|
2
|
+
# This help to clarify who will receive the notification based on the notification's taxons and vendor.
|
|
3
|
+
# 1. No vendor and no taxons: sent to all BookMe+ users, excluding tenant users
|
|
4
|
+
# 2. Has taxons: sent to users who bought a ticket for those specific events
|
|
5
|
+
# 3. Has vendor, vendor has no tenant: sent to users who bought a ticket for any of the vendor's events
|
|
6
|
+
# 4. Has vendor, vendor has a tenant: sent to all users in that tenant
|
|
7
|
+
|
|
8
|
+
module Spree
|
|
9
|
+
module Admin
|
|
10
|
+
module CustomerNotificationsHelper
|
|
11
|
+
def send_all_description(notification)
|
|
12
|
+
if notification.taxons.any?
|
|
13
|
+
send_all_taxons_description(notification)
|
|
14
|
+
elsif notification.vendor.present?
|
|
15
|
+
send_all_vendor_description(notification.vendor)
|
|
16
|
+
else
|
|
17
|
+
content_tag(:p, 'This notification will be sent to all BookMe+ users, excluding tenant users.')
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
private
|
|
22
|
+
|
|
23
|
+
def send_all_taxons_description(notification)
|
|
24
|
+
content_tag(:div) do
|
|
25
|
+
concat('This notification will be sent to everyone who bought the ticket from these events:')
|
|
26
|
+
concat(
|
|
27
|
+
content_tag(:ul) do
|
|
28
|
+
safe_join(notification.taxons.map { |taxon| content_tag(:li, taxon_link(taxon)) })
|
|
29
|
+
end
|
|
30
|
+
)
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def send_all_vendor_description(vendor)
|
|
35
|
+
content_tag(:p) do
|
|
36
|
+
if vendor.tenant.present?
|
|
37
|
+
safe_join(['This notification will be sent to all users in Tenant ', tenant_link(vendor.tenant), '.'])
|
|
38
|
+
else
|
|
39
|
+
safe_join(['This notification will be sent to everyone who bought a ticket for any event from vendor ', vendor_link(vendor), '.'])
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
def taxon_link(taxon)
|
|
45
|
+
link_to(taxon.name, edit_admin_taxonomy_taxon_path(taxon.taxonomy_id, taxon.id))
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
def vendor_link(vendor)
|
|
49
|
+
link_to(vendor.name, edit_admin_vendor_path(vendor.slug))
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
def tenant_link(tenant)
|
|
53
|
+
link_to(tenant.name, edit_admin_tenant_path(tenant.slug))
|
|
54
|
+
end
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
end
|
|
@@ -1,35 +1,57 @@
|
|
|
1
1
|
module SpreeCmCommissioner
|
|
2
2
|
module Admin
|
|
3
3
|
module HomepageSegmentHelper
|
|
4
|
+
# Bootstrap has only eight badge colors, so later segments share one; the label tells them apart.
|
|
5
|
+
SEGMENT_BADGE_COLORS = {
|
|
6
|
+
general: 'primary',
|
|
7
|
+
ticket: 'secondary',
|
|
8
|
+
tour: 'info',
|
|
9
|
+
accommodation: 'warning',
|
|
10
|
+
things_to_do: 'success',
|
|
11
|
+
football: 'dark',
|
|
12
|
+
show: 'danger',
|
|
13
|
+
bus: 'light',
|
|
14
|
+
intercity_taxi: 'warning',
|
|
15
|
+
rails: 'secondary',
|
|
16
|
+
ferry: 'info'
|
|
17
|
+
}.freeze
|
|
18
|
+
|
|
4
19
|
def badge_class_for_segment(segment_name)
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
'badge'
|
|
22
|
-
end
|
|
20
|
+
color = SEGMENT_BADGE_COLORS[segment_name.to_sym]
|
|
21
|
+
color ? "badge badge-#{color} text-uppercase" : 'badge'
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def badge_class_for_mini_app(_mini_app_name)
|
|
25
|
+
'badge badge-info text-uppercase'
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def mini_app_label(mini_app_name)
|
|
29
|
+
mini_app_name.to_s.titleize
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
# `none` is any value outside BIT_MINI_APP, which filter_by_mini_app reads as untagged.
|
|
33
|
+
def mini_app_filter_options
|
|
34
|
+
[['No mini app', 'none']] +
|
|
35
|
+
SpreeCmCommissioner::HomepageSectionBitwise::BIT_MINI_APP.keys.map { |name| [mini_app_label(name), name] }
|
|
23
36
|
end
|
|
24
37
|
|
|
25
38
|
def calculate_segment_value(params)
|
|
26
|
-
|
|
39
|
+
calculate_bitwise_value(params, SpreeCmCommissioner::HomepageSectionBitwise::BIT_SEGMENT)
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
def calculate_mini_app_value(params)
|
|
43
|
+
calculate_bitwise_value(params, SpreeCmCommissioner::HomepageSectionBitwise::BIT_MINI_APP)
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
private
|
|
27
47
|
|
|
28
|
-
|
|
48
|
+
# Sums the checked boxes into one integer. Keyed by name, not position, so a checkbox the
|
|
49
|
+
# form omits cannot shift the rest onto the wrong bits. Returns 0, never nil: `value & bit`
|
|
50
|
+
# raises on NULL.
|
|
51
|
+
def calculate_bitwise_value(params, bit_map)
|
|
52
|
+
return 0 if params.blank?
|
|
29
53
|
|
|
30
|
-
|
|
31
|
-
value.to_i * (2**index)
|
|
32
|
-
end
|
|
54
|
+
bit_map.sum { |name, bit_value| ActiveModel::Type::Boolean.new.cast(params[name]) ? bit_value : 0 }
|
|
33
55
|
end
|
|
34
56
|
end
|
|
35
57
|
end
|
|
@@ -54,21 +54,30 @@ module SpreeCmCommissioner
|
|
|
54
54
|
.where(cm_notifications: { id: nil })
|
|
55
55
|
end
|
|
56
56
|
|
|
57
|
+
# Resolves the base user scope for the notification: by its taxons, by its vendor's own
|
|
58
|
+
# taxons (when the vendor has no tenant), or by tenant otherwise
|
|
57
59
|
def end_users
|
|
58
|
-
users_scope = Spree.user_class
|
|
59
|
-
.where(tenant_id: notification_tenant_id)
|
|
60
|
-
|
|
61
60
|
if customer_notification.notification_taxons.exists?
|
|
62
61
|
taxon_ids = customer_notification.notification_taxons.pluck(:taxon_id)
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
.where(tenant_id: notification_tenant_id)
|
|
62
|
+
users_by_taxon_ids(taxon_ids)
|
|
63
|
+
elsif notification_tenant_id.nil? && customer_notification.vendor.present?
|
|
64
|
+
taxon_ids = customer_notification.vendor.taxons.ids
|
|
65
|
+
users_by_taxon_ids(taxon_ids)
|
|
68
66
|
else
|
|
69
|
-
|
|
67
|
+
Spree.user_class
|
|
68
|
+
.where(tenant_id: notification_tenant_id)
|
|
69
|
+
.end_users_push_notificable
|
|
70
70
|
end
|
|
71
|
+
end
|
|
71
72
|
|
|
73
|
+
# Fetches push-notificable users who purchased under the given taxon(s), scoped to the
|
|
74
|
+
# notification's tenant when it has one
|
|
75
|
+
def users_by_taxon_ids(taxon_ids)
|
|
76
|
+
users_scope = SpreeCmCommissioner::UsersByEventFetcherQuery
|
|
77
|
+
.new(event_id: taxon_ids)
|
|
78
|
+
.call
|
|
79
|
+
.end_users_push_notificable
|
|
80
|
+
users_scope = users_scope.where(tenant_id: notification_tenant_id) if notification_tenant_id.present?
|
|
72
81
|
users_scope
|
|
73
82
|
end
|
|
74
83
|
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
# Resolves a user from a cross-app handoff JWT (see CrossAppHandoffTokens::Decode) instead of a
|
|
2
|
+
# username/password pair. Routed here by UserAuthenticator when the grant_type=password request
|
|
3
|
+
# carries a `handoff_token` param — used by the operator app to sign a user in after the customer
|
|
4
|
+
# app minted the token on their behalf (purpose: operator_sign_in).
|
|
5
|
+
module SpreeCmCommissioner
|
|
6
|
+
class UserCrossAppHandoffAuthenticator < BaseInteractor
|
|
7
|
+
delegate :handoff_token, to: :context
|
|
8
|
+
|
|
9
|
+
def call
|
|
10
|
+
result = SpreeCmCommissioner::CrossAppHandoffTokens::Decode.call(token: handoff_token, purpose: 'operator_sign_in')
|
|
11
|
+
context.fail!(message: I18n.t('authenticator.incorrect_login')) unless result.success?
|
|
12
|
+
|
|
13
|
+
context.user = result.value[:user]
|
|
14
|
+
reject_unauthenticatable_user!(context.user)
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
end
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
module SpreeCmCommissioner
|
|
2
|
+
class AuditEventJob < ApplicationUniqueJob
|
|
3
|
+
def perform(options = {})
|
|
4
|
+
SpreeCmCommissioner::AuditEvent.create!(
|
|
5
|
+
event_type: options[:event_type],
|
|
6
|
+
success: options.fetch(:success, true),
|
|
7
|
+
auditable: options[:auditable],
|
|
8
|
+
user: options[:user],
|
|
9
|
+
correlation_id: options[:correlation_id],
|
|
10
|
+
attempt: options.fetch(:attempt, 0),
|
|
11
|
+
metadata: options.fetch(:metadata, {}),
|
|
12
|
+
retention_until: options[:retention_until]
|
|
13
|
+
)
|
|
14
|
+
rescue ActiveRecord::RecordInvalid, ActiveRecord::RecordNotSaved => e
|
|
15
|
+
# Log validation errors but don't retry - these are data issues, not transient failures
|
|
16
|
+
CmAppLogger.log(
|
|
17
|
+
label: 'AuditEventJob validation failed - non-retryable',
|
|
18
|
+
data: {
|
|
19
|
+
event_type: options[:event_type],
|
|
20
|
+
error_class: e.class.name,
|
|
21
|
+
error_message: e.message,
|
|
22
|
+
validation_errors: e.record&.errors&.full_messages
|
|
23
|
+
}
|
|
24
|
+
)
|
|
25
|
+
# Return nil to indicate the job completed (but audit wasn't saved)
|
|
26
|
+
nil
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
end
|
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
module SpreeCmCommissioner
|
|
2
|
+
# AuditWrapper is a reusable mixin for classes that make external API calls.
|
|
3
|
+
#
|
|
4
|
+
# It wraps an API call in a three-event lifecycle:
|
|
5
|
+
# 1. <event_base>.request – logged before the call
|
|
6
|
+
# 2. <event_base>.response – logged after a successful call (includes timing)
|
|
7
|
+
# 3. <event_base>.failed – logged when the block raises, then re-raises
|
|
8
|
+
#
|
|
9
|
+
# Metadata follows this canonical shape:
|
|
10
|
+
# {
|
|
11
|
+
# request: { method:, endpoint:, body:, params: },
|
|
12
|
+
# response: { status:, body: },
|
|
13
|
+
# timing: { duration_ms: },
|
|
14
|
+
# error: nil | { message: }
|
|
15
|
+
# }
|
|
16
|
+
#
|
|
17
|
+
# Usage:
|
|
18
|
+
# include SpreeCmCommissioner::AuditWrapper
|
|
19
|
+
#
|
|
20
|
+
# with_audit(
|
|
21
|
+
# event_base: 'bookmebus.order.hold',
|
|
22
|
+
# request: { method: 'POST', endpoint: '/api/v2/trip_reservations', body: body },
|
|
23
|
+
# auditable: order,
|
|
24
|
+
# user: current_user
|
|
25
|
+
# ) do
|
|
26
|
+
# post("#{BASE_PATH}/trip_reservations", body)
|
|
27
|
+
# end
|
|
28
|
+
|
|
29
|
+
module AuditWrapper
|
|
30
|
+
extend ActiveSupport::Concern
|
|
31
|
+
|
|
32
|
+
def with_audit(event_base:, request: {}, auditable: nil, user: nil, # rubocop:disable Metrics/ParameterLists
|
|
33
|
+
attempt: 0, retention_days: nil
|
|
34
|
+
)
|
|
35
|
+
correlation_id = SecureRandom.uuid
|
|
36
|
+
|
|
37
|
+
shared = {
|
|
38
|
+
auditable: auditable,
|
|
39
|
+
user: user,
|
|
40
|
+
correlation_id: correlation_id,
|
|
41
|
+
attempt: attempt,
|
|
42
|
+
retention_days: retention_days
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
# 1. Log the outbound request
|
|
46
|
+
AuditLogger.log!(
|
|
47
|
+
**shared,
|
|
48
|
+
event_type: "#{event_base}.request",
|
|
49
|
+
success: true,
|
|
50
|
+
metadata: build_request_metadata(request)
|
|
51
|
+
)
|
|
52
|
+
|
|
53
|
+
started_at = Process.clock_gettime(Process::CLOCK_MONOTONIC)
|
|
54
|
+
result = yield
|
|
55
|
+
duration = elapsed_ms(started_at)
|
|
56
|
+
|
|
57
|
+
# 2. Log the successful response
|
|
58
|
+
AuditLogger.log!(
|
|
59
|
+
**shared,
|
|
60
|
+
event_type: "#{event_base}.response",
|
|
61
|
+
success: true,
|
|
62
|
+
metadata: build_response_metadata(request, result, duration)
|
|
63
|
+
)
|
|
64
|
+
|
|
65
|
+
result
|
|
66
|
+
rescue StandardError => e
|
|
67
|
+
duration = elapsed_ms(started_at || Process.clock_gettime(Process::CLOCK_MONOTONIC))
|
|
68
|
+
|
|
69
|
+
# 3. Log the failure event
|
|
70
|
+
AuditLogger.log!(
|
|
71
|
+
**shared,
|
|
72
|
+
event_type: "#{event_base}.failed",
|
|
73
|
+
success: false,
|
|
74
|
+
metadata: build_error_metadata(request, e, duration)
|
|
75
|
+
)
|
|
76
|
+
|
|
77
|
+
raise
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
private
|
|
81
|
+
|
|
82
|
+
def build_request_metadata(request)
|
|
83
|
+
{
|
|
84
|
+
request: normalize_request(request),
|
|
85
|
+
response: nil,
|
|
86
|
+
timing: nil,
|
|
87
|
+
error: nil
|
|
88
|
+
}
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
def build_response_metadata(request, result, duration_ms)
|
|
92
|
+
status, body = extract_response(result)
|
|
93
|
+
|
|
94
|
+
{
|
|
95
|
+
request: normalize_request(request),
|
|
96
|
+
response: { status: status, body: body },
|
|
97
|
+
timing: { duration_ms: duration_ms },
|
|
98
|
+
error: nil
|
|
99
|
+
}
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
def build_error_metadata(request, error, duration_ms)
|
|
103
|
+
{
|
|
104
|
+
request: normalize_request(request),
|
|
105
|
+
response: nil,
|
|
106
|
+
timing: { duration_ms: duration_ms },
|
|
107
|
+
error: { message: error.message }
|
|
108
|
+
}
|
|
109
|
+
end
|
|
110
|
+
|
|
111
|
+
def normalize_request(request)
|
|
112
|
+
{
|
|
113
|
+
method: request[:method],
|
|
114
|
+
endpoint: request[:endpoint],
|
|
115
|
+
body: request[:body],
|
|
116
|
+
params: request[:params]
|
|
117
|
+
}
|
|
118
|
+
end
|
|
119
|
+
|
|
120
|
+
# Try to extract status + body from common response objects.
|
|
121
|
+
# Falls back to inspecting the raw result.
|
|
122
|
+
def extract_response(result)
|
|
123
|
+
if result.respond_to?(:status) && result.respond_to?(:body)
|
|
124
|
+
[result.status, result.body]
|
|
125
|
+
elsif result.is_a?(Array)
|
|
126
|
+
# Handle arrays of resource objects
|
|
127
|
+
[nil, result.map(&:to_h)]
|
|
128
|
+
elsif result.respond_to?(:to_h)
|
|
129
|
+
[nil, result.to_h]
|
|
130
|
+
else
|
|
131
|
+
[nil, result.inspect]
|
|
132
|
+
end
|
|
133
|
+
end
|
|
134
|
+
|
|
135
|
+
def elapsed_ms(started_at)
|
|
136
|
+
((Process.clock_gettime(Process::CLOCK_MONOTONIC) - started_at) * 1000).round(2)
|
|
137
|
+
end
|
|
138
|
+
end
|
|
139
|
+
end
|
|
@@ -2,6 +2,8 @@ module SpreeCmCommissioner
|
|
|
2
2
|
module HomepageSectionBitwise
|
|
3
3
|
extend ActiveSupport::Concern
|
|
4
4
|
|
|
5
|
+
# Append only: [SpreeCmCommissioner::HomepageBackground] maps these keys to an integer enum by
|
|
6
|
+
# index, so inserting one renumbers every stored background.
|
|
5
7
|
BIT_SEGMENT = {
|
|
6
8
|
general: 0b00001,
|
|
7
9
|
ticket: 0b00010,
|
|
@@ -9,7 +11,25 @@ module SpreeCmCommissioner
|
|
|
9
11
|
accommodation: 0b01000,
|
|
10
12
|
things_to_do: 0b10000,
|
|
11
13
|
football: 0b100000,
|
|
12
|
-
show: 0b1000000
|
|
14
|
+
show: 0b1000000,
|
|
15
|
+
bus: 0b10000000,
|
|
16
|
+
intercity_taxi: 0b100000000,
|
|
17
|
+
rails: 0b1000000000,
|
|
18
|
+
ferry: 0b10000000000
|
|
19
|
+
}.freeze
|
|
20
|
+
|
|
21
|
+
# Keys match the `mini_app` query param the app sends (ApiConstant.miniApp.paramValue).
|
|
22
|
+
BIT_MINI_APP = {
|
|
23
|
+
vattanac: 0b001,
|
|
24
|
+
acleda: 0b010,
|
|
25
|
+
wing: 0b100
|
|
26
|
+
}.freeze
|
|
27
|
+
|
|
28
|
+
# The feed each mini app's home requests, mirroring `isWing ? BusRoute : HomeRoute` in routes.dart.
|
|
29
|
+
MINI_APP_HOME_SEGMENT = {
|
|
30
|
+
vattanac: :general,
|
|
31
|
+
acleda: :general,
|
|
32
|
+
wing: :bus
|
|
13
33
|
}.freeze
|
|
14
34
|
|
|
15
35
|
BIT_SEGMENT.each do |segment, bit_value|
|
|
@@ -18,6 +38,12 @@ module SpreeCmCommissioner
|
|
|
18
38
|
end
|
|
19
39
|
end
|
|
20
40
|
|
|
41
|
+
BIT_MINI_APP.each do |mini_app, bit_value|
|
|
42
|
+
define_method "#{mini_app}?" do
|
|
43
|
+
mini_app_enabled?(bit_value)
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
|
|
21
47
|
def segments
|
|
22
48
|
BIT_SEGMENT.filter_map do |segment_value, bit_value|
|
|
23
49
|
segment_value if segment_enabled?(bit_value)
|
|
@@ -27,5 +53,15 @@ module SpreeCmCommissioner
|
|
|
27
53
|
def segment_enabled?(bit_value)
|
|
28
54
|
segment & bit_value != 0
|
|
29
55
|
end
|
|
56
|
+
|
|
57
|
+
def mini_apps
|
|
58
|
+
BIT_MINI_APP.filter_map do |mini_app_value, bit_value|
|
|
59
|
+
mini_app_value if mini_app_enabled?(bit_value)
|
|
60
|
+
end
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
def mini_app_enabled?(bit_value)
|
|
64
|
+
mini_app.to_i & bit_value != 0
|
|
65
|
+
end
|
|
30
66
|
end
|
|
31
67
|
end
|
|
@@ -66,11 +66,11 @@ module SpreeCmCommissioner
|
|
|
66
66
|
end
|
|
67
67
|
|
|
68
68
|
def redeemed?
|
|
69
|
-
redeemed_at.present?
|
|
69
|
+
open_dated? && redeemed_at.present?
|
|
70
70
|
end
|
|
71
71
|
|
|
72
72
|
def expired?
|
|
73
|
-
valid_until.present? && valid_until.to_date < Date.current
|
|
73
|
+
open_dated? && valid_until.present? && valid_until.to_date < Date.current
|
|
74
74
|
end
|
|
75
75
|
|
|
76
76
|
def can_redeem?
|
|
@@ -9,6 +9,16 @@ module SpreeCmCommissioner
|
|
|
9
9
|
|
|
10
10
|
state_machine.before_transition to: :address, do: :hold_order_holds
|
|
11
11
|
|
|
12
|
+
# Same transitions core calls create_tax_charge! from (gems/core/app/models/spree/order/checkout.rb,
|
|
13
|
+
# before_transition to: :payment / from: :address), so this fires twice per checkout, plus
|
|
14
|
+
# again for orders TransitOrder::Create/DraftOrders::Create already called it on at creation.
|
|
15
|
+
#
|
|
16
|
+
# Each run is a destroy_all plus one rule lookup per line item — cheap enough that recomputing
|
|
17
|
+
# on every step isn't worth guarding against, and it's what keeps the fee correct if a rule or
|
|
18
|
+
# line item changes between steps.
|
|
19
|
+
state_machine.before_transition to: :payment, do: :create_service_fee_charge!
|
|
20
|
+
state_machine.before_transition from: :address, do: :create_service_fee_charge!
|
|
21
|
+
|
|
12
22
|
# Fails fast, before any of the other before_transition/around_transition work below runs
|
|
13
23
|
# (external-system unstock, Redis unstock, notifications, ...), when the inventory hold has
|
|
14
24
|
# already expired and been released rather than converted — see
|
|
@@ -10,7 +10,11 @@ module SpreeCmCommissioner
|
|
|
10
10
|
private
|
|
11
11
|
|
|
12
12
|
def sanitize_phone_number
|
|
13
|
-
|
|
13
|
+
if phone_number.blank?
|
|
14
|
+
self.intel_phone_number = nil
|
|
15
|
+
self.country_code = nil
|
|
16
|
+
return
|
|
17
|
+
end
|
|
14
18
|
|
|
15
19
|
parser = PhoneNumberParser.call(
|
|
16
20
|
phone_number: phone_number,
|