spree_cm_commissioner 2.13.4 → 2.13.7
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 +5 -0
- data/Gemfile.lock +1 -1
- data/app/controllers/spree/admin/base_controller_decorator.rb +14 -0
- data/app/controllers/spree/admin/customer_notifications_controller.rb +16 -1
- data/app/controllers/spree/admin/inventory_items_controller.rb +11 -0
- data/app/controllers/spree/admin/payment_fee_rules_controller.rb +76 -0
- data/app/controllers/spree/admin/prepaid_transactions_controller.rb +69 -0
- data/app/controllers/spree/admin/private_sales_controller.rb +6 -0
- data/app/controllers/spree/admin/product_commissions_controller.rb +2 -1
- data/app/controllers/spree/admin/reschedule_policies_controller.rb +148 -0
- data/app/controllers/spree/admin/reschedule_requests_controller.rb +82 -0
- data/app/controllers/spree/admin/service_fee_rules_controller.rb +1 -0
- data/app/controllers/spree/admin/store_credit_admin_controller.rb +122 -0
- data/app/controllers/spree/admin/ticket_transfer_rules_controller.rb +7 -0
- data/app/controllers/spree/api/v2/platform/store_credits_controller_decorator.rb +35 -0
- data/app/controllers/spree/api/v2/storefront/account/prepaid_balances_controller.rb +52 -0
- data/app/controllers/spree/api/v2/storefront/account/prepaid_transactions_controller.rb +56 -0
- data/app/controllers/spree/api/v2/storefront/cart_payment_fees_controller.rb +48 -0
- data/app/controllers/spree/api/v2/storefront/draft_orders_controller.rb +37 -0
- data/app/controllers/spree/api/v2/storefront/prepaid_config_controller.rb +22 -0
- data/app/controllers/spree/api/v2/storefront/reschedule_requests_controller.rb +156 -0
- data/app/controllers/spree/api/v2/storefront/seat_layouts_controller.rb +3 -1
- data/app/controllers/spree/api/v2/storefront/transit/item_availabilities_controller.rb +66 -0
- data/app/controllers/spree/api/v2/tenant/account/prepaid_balances_controller.rb +51 -0
- data/app/controllers/spree/api/v2/tenant/account/prepaid_transactions_controller.rb +53 -0
- data/app/controllers/spree/api/v2/tenant/base_controller.rb +20 -0
- data/app/controllers/spree/api/v2/tenant/cart_payment_fees_controller.rb +43 -0
- data/app/controllers/spree/api/v2/tenant/draft_orders_controller.rb +42 -0
- data/app/controllers/spree/api/v2/tenant/prepaid_config_controller.rb +29 -0
- data/app/controllers/spree_cm_commissioner/admin/store_credits_controller_decorator.rb +217 -0
- data/app/errors/spree_cm_commissioner/reschedule_policies/invalid_policyable_error.rb +8 -0
- data/app/errors/spree_cm_commissioner/reschedule_policies/not_reschedulable_error.rb +11 -0
- data/app/errors/spree_cm_commissioner/reschedule_requests/cancel_not_allowed_error.rb +6 -0
- data/app/errors/spree_cm_commissioner/reschedule_requests/complete_cash_payment_error.rb +6 -0
- data/app/errors/spree_cm_commissioner/reschedule_requests/eligibility_denied_error.rb +7 -0
- data/app/factory/spree_cm_commissioner/order_telegram_message_factory.rb +6 -2
- data/app/factory/spree_cm_commissioner/reschedule_telegram_message_factory.rb +141 -0
- data/app/finders/spree_cm_commissioner/inventory_items/recently_changed_finder.rb +1 -1
- data/app/helpers/spree/admin/base_helper_decorator.rb +12 -0
- data/app/helpers/spree/admin/reschedule_policies_helper.rb +35 -0
- data/app/helpers/spree/admin/reschedule_requests_helper.rb +79 -0
- data/app/helpers/spree_cm_commissioner/admin/fee_rules_helper.rb +23 -0
- data/app/interactors/spree_cm_commissioner/create_ticket.rb +1 -0
- data/app/jobs/spree_cm_commissioner/audit_event_job.rb +29 -0
- data/app/jobs/spree_cm_commissioner/prepaid_reconciliation_cron_job.rb +9 -0
- data/app/jobs/spree_cm_commissioner/prepaid_reverse_top_up_job.rb +16 -0
- data/app/jobs/spree_cm_commissioner/prepaid_setup_types_job.rb +24 -0
- data/app/jobs/spree_cm_commissioner/prepaid_top_up_credit_job.rb +31 -0
- data/app/jobs/spree_cm_commissioner/reschedule_requests/approval_reminder_job.rb +17 -0
- data/app/jobs/spree_cm_commissioner/reschedule_requests/execute_job.rb +20 -0
- data/app/jobs/spree_cm_commissioner/reschedule_requests/expire_overdue_requests_job.rb +12 -0
- data/app/jobs/spree_cm_commissioner/reschedule_requests/expire_request_job.rb +15 -0
- data/app/jobs/spree_cm_commissioner/reschedule_requests/payment_reminder_job.rb +17 -0
- data/app/models/concerns/spree_cm_commissioner/audit_wrapper.rb +139 -0
- data/app/models/concerns/spree_cm_commissioner/integrations/inventory_sync_cachable.rb +9 -0
- data/app/models/concerns/spree_cm_commissioner/option_type_attr_type.rb +2 -1
- data/app/models/concerns/spree_cm_commissioner/order_prepaid_payment.rb +15 -0
- data/app/models/concerns/spree_cm_commissioner/order_state_machine.rb +58 -8
- data/app/models/concerns/spree_cm_commissioner/percent_fee_calculable.rb +36 -0
- data/app/models/concerns/spree_cm_commissioner/product_type.rb +6 -2
- data/app/models/concerns/spree_cm_commissioner/reschedule_request_state_machine.rb +332 -0
- data/app/models/concerns/spree_cm_commissioner/scoped_fee_rule.rb +129 -0
- data/app/models/concerns/spree_cm_commissioner/service_type.rb +1 -1
- data/app/models/concerns/spree_cm_commissioner/store_preference.rb +1 -0
- data/app/models/concerns/spree_cm_commissioner/tenant_preference.rb +6 -0
- 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/core/controller_helpers/currency_decorator.rb +70 -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/payment_fee_flat_amount.rb +21 -0
- data/app/models/spree_cm_commissioner/calculators/payment_fee_percent.rb +17 -0
- data/app/models/spree_cm_commissioner/calculators/service_fee_flat_amount.rb +4 -0
- data/app/models/spree_cm_commissioner/calculators/service_fee_percent.rb +5 -2
- data/app/models/spree_cm_commissioner/configuration.rb +17 -9
- data/app/models/spree_cm_commissioner/currency_rate.rb +15 -0
- data/app/models/spree_cm_commissioner/current.rb +8 -0
- data/app/models/spree_cm_commissioner/customer_notification.rb +4 -1
- data/app/models/spree_cm_commissioner/exports/import_order_guests_csv.rb +153 -0
- data/app/models/spree_cm_commissioner/guest.rb +21 -0
- data/app/models/spree_cm_commissioner/integration.rb +31 -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/vireak_buntham.rb +82 -0
- data/app/models/spree_cm_commissioner/inventory_item.rb +22 -5
- data/app/models/spree_cm_commissioner/line_item_decorator.rb +6 -2
- data/app/models/spree_cm_commissioner/notification.rb +8 -0
- data/app/models/spree_cm_commissioner/order_decorator.rb +31 -3
- data/app/models/spree_cm_commissioner/order_updater_decorator.rb +6 -1
- data/app/models/spree_cm_commissioner/payment_decorator.rb +31 -0
- data/app/models/spree_cm_commissioner/payment_fee_rule.rb +97 -0
- data/app/models/spree_cm_commissioner/payment_method_decorator.rb +5 -0
- data/app/models/spree_cm_commissioner/payment_method_store_credit_decorator.rb +81 -0
- data/app/models/spree_cm_commissioner/place.rb +2 -0
- data/app/models/spree_cm_commissioner/prepaid_config.rb +59 -0
- data/app/models/spree_cm_commissioner/prepaid_transaction.rb +102 -0
- data/app/models/spree_cm_commissioner/product_decorator.rb +6 -0
- data/app/models/spree_cm_commissioner/redis_stock/cached_inventory_items_builder.rb +4 -2
- data/app/models/spree_cm_commissioner/reschedule_policy.rb +94 -0
- data/app/models/spree_cm_commissioner/reschedule_policy_assignment.rb +19 -0
- data/app/models/spree_cm_commissioner/reschedule_request.rb +227 -0
- data/app/models/spree_cm_commissioner/reschedule_requests/guest_selection.rb +80 -0
- data/app/models/spree_cm_commissioner/reschedule_snapshot.rb +65 -0
- data/app/models/spree_cm_commissioner/route.rb +1 -0
- data/app/models/spree_cm_commissioner/seat_layout.rb +8 -0
- data/app/models/spree_cm_commissioner/service_calendar.rb +4 -0
- data/app/models/spree_cm_commissioner/service_fee_rule.rb +17 -114
- 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/store_credit_category_decorator.rb +23 -0
- data/app/models/spree_cm_commissioner/store_credit_decorator.rb +142 -0
- data/app/models/spree_cm_commissioner/store_credit_event_decorator.rb +94 -0
- data/app/models/spree_cm_commissioner/store_credit_type_decorator.rb +43 -0
- data/app/models/spree_cm_commissioner/store_decorator.rb +8 -0
- data/app/models/spree_cm_commissioner/tenant.rb +37 -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 +79 -0
- data/app/models/spree_cm_commissioner/variant_decorator.rb +9 -1
- 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 +53 -0
- data/app/models/spree_cm_commissioner/vendor_place.rb +2 -0
- data/app/notifications/spree_cm_commissioner/reschedule_approved_notification.rb +7 -0
- data/app/notifications/spree_cm_commissioner/reschedule_cancelled_notification.rb +7 -0
- data/app/notifications/spree_cm_commissioner/reschedule_completed_notification.rb +7 -0
- data/app/notifications/spree_cm_commissioner/reschedule_expired_notification.rb +7 -0
- data/app/notifications/spree_cm_commissioner/reschedule_failed_notification.rb +7 -0
- data/app/notifications/spree_cm_commissioner/reschedule_notification_base.rb +41 -0
- data/app/notifications/spree_cm_commissioner/reschedule_payment_reminder_notification.rb +15 -0
- data/app/notifications/spree_cm_commissioner/reschedule_rejected_notification.rb +7 -0
- data/app/notifications/spree_cm_commissioner/reschedule_requested_notification.rb +7 -0
- data/app/overrides/spree/admin/products/_form/offline.html.erb.deface +9 -0
- data/app/overrides/spree/admin/shared/_main_menu/sidebar_reschedule.html.erb.deface +9 -0
- data/app/overrides/spree/admin/shared/_main_menu/sidebar_store_credit.html.erb.deface +8 -0
- data/app/overrides/spree/admin/shared/_order_tabs/reschedule_requests.html.erb.deface +10 -0
- data/app/overrides/spree/admin/shared/_product_tabs/stock_managements.html.erb.deface +1 -1
- data/app/overrides/spree/admin/shared/sub_menu/_configuration/menu_items.html.erb.deface +5 -0
- data/app/presenters/spree_cm_commissioner/prepaid_balance_presenter.rb +16 -0
- data/app/queries/spree_cm_commissioner/store_credit_overview_query.rb +75 -0
- data/app/queries/spree_cm_commissioner/user_store_credit_summary_query.rb +53 -0
- data/app/serializables/spree_cm_commissioner/item_availability_sync.rb +10 -0
- data/app/serializers/spree/v2/storefront/prepaid_balance_serializer.rb +15 -0
- data/app/serializers/spree/v2/storefront/prepaid_config_serializer.rb +11 -0
- data/app/serializers/spree/v2/storefront/prepaid_transaction_serializer.rb +17 -0
- data/app/serializers/spree/v2/storefront/user_serializer_decorator.rb +6 -0
- data/app/serializers/spree/v2/tenant/cart_serializer.rb +2 -1
- data/app/serializers/spree/v2/tenant/payment_method_serializer.rb +3 -0
- data/app/serializers/spree/v2/tenant/prepaid_balance_serializer.rb +15 -0
- data/app/serializers/spree/v2/tenant/prepaid_config_serializer.rb +16 -0
- data/app/serializers/spree/v2/tenant/prepaid_transaction_serializer.rb +16 -0
- data/app/serializers/spree_cm_commissioner/api/v2/platform/homepage_section_relatable_options_serializer.rb +5 -0
- data/app/serializers/spree_cm_commissioner/v2/storefront/cart_serializer_decorator.rb +3 -0
- data/app/serializers/spree_cm_commissioner/v2/storefront/inventory_item_serializer.rb +1 -0
- data/app/serializers/spree_cm_commissioner/v2/storefront/item_availability_sync_serializer.rb +10 -0
- data/app/serializers/spree_cm_commissioner/v2/storefront/payment_fee_preview_serializer.rb +19 -0
- data/app/serializers/spree_cm_commissioner/v2/storefront/payment_method_serializer_decorator.rb +16 -0
- data/app/serializers/spree_cm_commissioner/v2/storefront/reschedule_request_serializer.rb +49 -0
- data/app/serializers/spree_cm_commissioner/v2/storefront/reschedule_snapshot_serializer.rb +24 -0
- data/app/serializers/spree_cm_commissioner/v2/storefront/seat_layout_serializer.rb +8 -0
- data/app/services/spree_cm_commissioner/audit_logger.rb +59 -0
- data/app/services/spree_cm_commissioner/checkout/add_store_credit_decorator.rb +17 -0
- data/app/services/spree_cm_commissioner/checkout/add_store_credit_payments.rb +96 -0
- data/app/services/spree_cm_commissioner/checkout/remove_store_credit_decorator.rb +17 -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/customer_notifications/clone.rb +39 -0
- data/app/services/spree_cm_commissioner/google_maps_geocoder.rb +41 -0
- data/app/services/spree_cm_commissioner/imports/orders/create.rb +24 -11
- 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/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_holds/release.rb +13 -0
- data/app/services/spree_cm_commissioner/inventory_items/generate_permanent_item.rb +48 -0
- data/app/services/spree_cm_commissioner/order_holds/hold.rb +17 -0
- data/app/services/spree_cm_commissioner/order_holds/release.rb +31 -0
- data/app/services/spree_cm_commissioner/payment_fees/apply.rb +30 -0
- data/app/services/spree_cm_commissioner/payment_fees/clear.rb +15 -0
- data/app/services/spree_cm_commissioner/payment_fees/preview.rb +70 -0
- data/app/services/spree_cm_commissioner/payments/find_or_create_decorator.rb +18 -0
- data/app/services/spree_cm_commissioner/place_resolver.rb +95 -0
- data/app/services/spree_cm_commissioner/prepaid/admin_credit.rb +44 -0
- data/app/services/spree_cm_commissioner/prepaid/check_top_up_limits.rb +47 -0
- data/app/services/spree_cm_commissioner/prepaid/freeze.rb +58 -0
- data/app/services/spree_cm_commissioner/prepaid/mutation.rb +78 -0
- data/app/services/spree_cm_commissioner/prepaid/reverse_order_top_up.rb +23 -0
- data/app/services/spree_cm_commissioner/prepaid/reverse_top_up.rb +53 -0
- data/app/services/spree_cm_commissioner/prepaid/top_up.rb +68 -0
- data/app/services/spree_cm_commissioner/prepaid/unfreeze.rb +47 -0
- data/app/services/spree_cm_commissioner/prepaid_metrics.rb +37 -0
- data/app/services/spree_cm_commissioner/prepaid_reconciliation_service.rb +71 -0
- data/app/services/spree_cm_commissioner/reschedule_policies/assign.rb +52 -0
- data/app/services/spree_cm_commissioner/reschedule_policies/create.rb +71 -0
- data/app/services/spree_cm_commissioner/reschedule_policies/exclude.rb +40 -0
- data/app/services/spree_cm_commissioner/reschedule_policies/resolve.rb +109 -0
- data/app/services/spree_cm_commissioner/reschedule_policies/unassign.rb +23 -0
- data/app/services/spree_cm_commissioner/reschedule_policies/update.rb +73 -0
- data/app/services/spree_cm_commissioner/reschedule_requests/authorize_initiator.rb +79 -0
- data/app/services/spree_cm_commissioner/reschedule_requests/build_request.rb +317 -0
- data/app/services/spree_cm_commissioner/reschedule_requests/calculate_fee.rb +71 -0
- data/app/services/spree_cm_commissioner/reschedule_requests/cancel.rb +55 -0
- data/app/services/spree_cm_commissioner/reschedule_requests/check_eligibility.rb +243 -0
- data/app/services/spree_cm_commissioner/reschedule_requests/collect_payment.rb +204 -0
- data/app/services/spree_cm_commissioner/reschedule_requests/execute.rb +252 -0
- data/app/services/spree_cm_commissioner/reschedule_requests/expire_overdue_requests.rb +28 -0
- data/app/services/spree_cm_commissioner/reschedule_requests/expire_request.rb +24 -0
- data/app/services/spree_cm_commissioner/reschedule_requests/guests/update_seats.rb +210 -0
- data/app/services/spree_cm_commissioner/reschedule_requests/initiate.rb +255 -0
- data/app/services/spree_cm_commissioner/reschedule_requests/issue_compensation.rb +67 -0
- data/app/services/spree_cm_commissioner/reschedule_requests/issue_coupon.rb +90 -0
- data/app/services/spree_cm_commissioner/reschedule_requests/issue_store_credit.rb +16 -0
- data/app/services/spree_cm_commissioner/reschedule_requests/notifications/send_approval_reminder.rb +43 -0
- data/app/services/spree_cm_commissioner/reschedule_requests/notifications/send_payment_reminder.rb +46 -0
- data/app/services/spree_cm_commissioner/reschedule_requests/notifications/send_status_update.rb +42 -0
- data/app/services/spree_cm_commissioner/reschedule_requests/seats/hold.rb +137 -0
- data/app/services/spree_cm_commissioner/reschedule_requests/split_line_item.rb +86 -0
- data/app/services/spree_cm_commissioner/reschedule_requests/telegram/notify_operations.rb +37 -0
- data/app/services/spree_cm_commissioner/reschedule_requests/telegram/notify_vendor.rb +51 -0
- data/app/services/spree_cm_commissioner/reschedule_requests/vendors/approve.rb +61 -0
- data/app/services/spree_cm_commissioner/reschedule_requests/vendors/reject.rb +58 -0
- data/app/services/spree_cm_commissioner/topup_draft_orders/create.rb +234 -0
- data/app/services/spree_cm_commissioner/trips/service_calendars/create_or_update.rb +25 -14
- data/app/services/spree_cm_commissioner/trips/update_single_leg.rb +43 -6
- data/app/views/spree/admin/classifications/edit.html.erb +17 -2
- data/app/views/spree/admin/classifications/new.html.erb +18 -2
- data/app/views/spree/admin/customer_notifications/index.html.erb +45 -3
- data/app/views/spree/admin/integrations/_book_me_bus_v1_fields.html.erb +47 -0
- data/app/views/spree/admin/integrations/_form.html.erb +12 -0
- data/app/views/spree/admin/integrations/_larryta_fields.html.erb +58 -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/payment_fee_rules/_form.html.erb +135 -0
- data/app/views/spree/admin/payment_fee_rules/edit.html.erb +11 -0
- data/app/views/spree/admin/payment_fee_rules/index.html.erb +100 -0
- data/app/views/spree/admin/payment_fee_rules/new.html.erb +11 -0
- data/app/views/spree/admin/prepaid_transactions/index.html.erb +102 -0
- data/app/views/spree/admin/prepaid_transactions/show.html.erb +146 -0
- data/app/views/spree/admin/private_sales/_form.html.erb +24 -1
- data/app/views/spree/admin/private_sales/_stock.html.erb +6 -1
- data/app/views/spree/admin/product_commissions/index.html.erb +7 -2
- data/app/views/spree/admin/reschedule_policies/_form.html.erb +65 -0
- data/app/views/spree/admin/reschedule_policies/edit.html.erb +11 -0
- data/app/views/spree/admin/reschedule_policies/index.html.erb +84 -0
- data/app/views/spree/admin/reschedule_policies/new.html.erb +11 -0
- data/app/views/spree/admin/reschedule_requests/_cancel_modal.html.erb +34 -0
- data/app/views/spree/admin/reschedule_requests/_search_form.html.erb +57 -0
- data/app/views/spree/admin/reschedule_requests/_tabs.html.erb +14 -0
- data/app/views/spree/admin/reschedule_requests/index.html.erb +64 -0
- data/app/views/spree/admin/reschedule_requests/show.html.erb +212 -0
- data/app/views/spree/admin/reschedule_requests/state_changes.html.erb +42 -0
- data/app/views/spree/admin/service_fee_rules/_form.html.erb +15 -9
- data/app/views/spree/admin/service_fee_rules/index.html.erb +1 -1
- data/app/views/spree/admin/shared/_products.html.erb +3 -0
- data/app/views/spree/admin/shared/_reschedule_policies_tabs.html.erb +22 -0
- data/app/views/spree/admin/shared/_ticket_transfer_details.html.erb +3 -0
- data/app/views/spree/admin/shared/sub_menu/_reschedule.html.erb +4 -0
- data/app/views/spree/admin/stock_managements/_header.html.erb +26 -0
- data/app/views/spree/admin/stock_managements/_variant_stock_items.html.erb +1 -1
- data/app/views/spree/admin/stock_managements/calendar.html.erb +285 -171
- data/app/views/spree/admin/stock_managements/index.html.erb +1 -18
- data/app/views/spree/admin/store_credit_admin/_tabs.html.erb +31 -0
- data/app/views/spree/admin/store_credit_admin/_tenant_selector.html.erb +17 -0
- data/app/views/spree/admin/store_credit_admin/show.html.erb +200 -0
- data/app/views/spree/admin/store_credits/index.html.erb +224 -0
- data/app/views/spree/admin/tenants/_form.html.erb +33 -0
- data/app/views/spree/admin/ticket_transfer_rules/_form.html.erb +28 -5
- data/config/initializers/spree_dependencies.rb +3 -0
- data/config/initializers/spree_permitted_attributes.rb +1 -0
- data/config/locales/en.yml +363 -1
- data/config/locales/km.yml +282 -1
- data/config/routes.rb +67 -2
- 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/20260506120000_create_cm_reschedule_policies.rb +38 -0
- data/db/migrate/20260506120001_create_cm_reschedule_requests.rb +67 -0
- data/db/migrate/20260506120002_add_reschedule_count_to_cm_guests.rb +5 -0
- data/db/migrate/20260521140000_create_cm_reschedule_snapshots.rb +38 -0
- data/db/migrate/20260522160000_create_cm_reschedule_policy_assignments.rb +17 -0
- data/db/migrate/20260722000001_create_cm_prepaid_transactions.rb +31 -0
- data/db/migrate/20260722000002_add_constraints_to_store_credits.rb +46 -0
- data/db/migrate/20260722000003_add_expires_at_to_store_credits.rb +6 -0
- data/db/migrate/20260722000004_add_issuer_tenant_id_to_spree_store_credits.rb +7 -0
- data/db/migrate/20260722000005_add_immutability_trigger_to_cm_prepaid_transactions.rb +36 -0
- data/db/migrate/20260918045558_add_prepaid_currency_columns_to_cm_tenants.rb +6 -0
- data/db/migrate/20260923090000_add_public_private_metadata_to_cm_prepaid_transactions.rb +36 -0
- data/db/migrate/20260928000001_add_closed_to_cm_inventory_items.rb +5 -0
- data/db/migrate/20261001100000_create_cm_payment_fee_rules.rb +17 -0
- data/db/migrate/20261001100001_add_payment_fee_total_to_spree_orders.rb +5 -0
- data/db/migrate/20261006073317_add_offline_to_spree_products.rb +6 -0
- data/lib/spree_cm_commissioner/cached_inventory_item.rb +7 -1
- data/lib/spree_cm_commissioner/payment_fees/payment_fee_preview.rb +24 -0
- data/lib/spree_cm_commissioner/reschedule_policy_rules.rb +75 -0
- data/lib/spree_cm_commissioner/serializable_transaction.rb +38 -0
- data/lib/spree_cm_commissioner/store_credit_currency_summary.rb +11 -0
- data/lib/spree_cm_commissioner/store_credit_summary.rb +11 -0
- data/lib/spree_cm_commissioner/test_helper/factories/integration_factory.rb +11 -0
- data/lib/spree_cm_commissioner/test_helper/factories/prepaid_transaction_factory.rb +12 -0
- data/lib/spree_cm_commissioner/test_helper/factories/reschedule_policy_assignment_factory.rb +7 -0
- data/lib/spree_cm_commissioner/test_helper/factories/reschedule_policy_factory.rb +59 -0
- data/lib/spree_cm_commissioner/test_helper/factories/reschedule_request_factory.rb +51 -0
- data/lib/spree_cm_commissioner/test_helper/factories/reschedule_snapshot_factory.rb +27 -0
- data/lib/spree_cm_commissioner/version.rb +1 -1
- data/lib/spree_cm_commissioner.rb +5 -0
- data/lib/tasks/prepaid_reconciliation_cron_scheduler.rake +7 -0
- data/lib/tasks/prepaid_setup_types.rake +7 -0
- metadata +295 -2
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
module Spree
|
|
2
|
+
module Api
|
|
3
|
+
module V2
|
|
4
|
+
module Tenant
|
|
5
|
+
# See Spree::Api::V2::Storefront::CartPaymentFeesController.
|
|
6
|
+
class CartPaymentFeesController < BaseController
|
|
7
|
+
include Spree::Api::V2::Storefront::OrderConcern
|
|
8
|
+
|
|
9
|
+
before_action :ensure_order
|
|
10
|
+
|
|
11
|
+
# override
|
|
12
|
+
def index
|
|
13
|
+
render_serialized_payload do
|
|
14
|
+
collection_serializer.new(collection).serializable_hash
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
# override
|
|
19
|
+
def collection
|
|
20
|
+
@collection ||= requested_payment_methods.map do |payment_method|
|
|
21
|
+
SpreeCmCommissioner::PaymentFees::Preview.call(order: spree_current_order, payment_method: payment_method)
|
|
22
|
+
.value[:payment_fee_preview]
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
# override
|
|
27
|
+
def collection_serializer
|
|
28
|
+
SpreeCmCommissioner::V2::Storefront::PaymentFeePreviewSerializer
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
private
|
|
32
|
+
|
|
33
|
+
# Ids the order can't actually pay with are dropped rather than rejected, so a stale client
|
|
34
|
+
# list (e.g. a method disabled since the catalog was cached) still gets the rest.
|
|
35
|
+
def requested_payment_methods
|
|
36
|
+
ids = Array(params[:payment_method_ids]).map(&:to_s)
|
|
37
|
+
spree_current_order.available_payment_methods.select { |pm| ids.include?(pm.id.to_s) }
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
end
|
|
@@ -7,6 +7,10 @@ module Spree
|
|
|
7
7
|
|
|
8
8
|
before_action -> { ensure_anonymous_booking_allowed!(draft_order_products) }, only: :create
|
|
9
9
|
|
|
10
|
+
# A top-up credits a wallet, which must belong to a real user -- unlike #create,
|
|
11
|
+
# this action has no guest-checkout path.
|
|
12
|
+
before_action :require_spree_current_user, only: [:topup]
|
|
13
|
+
|
|
10
14
|
# Creates a generic draft order from a locally-assembled cart in a single request.
|
|
11
15
|
# Endpoint: POST /api/v2/tenant/draft_orders
|
|
12
16
|
# Params:
|
|
@@ -33,6 +37,44 @@ module Spree
|
|
|
33
37
|
end
|
|
34
38
|
end
|
|
35
39
|
|
|
40
|
+
# Builds a prepaid top-up order from just an amount and a payment method -- the client
|
|
41
|
+
# never fetches/knows the prepaid_top_up product or variant. Tenant-scoped: the resulting
|
|
42
|
+
# order.tenant_id makes PrepaidTopUpCreditJob produce tenant-issued (not platform-issued)
|
|
43
|
+
# credit.
|
|
44
|
+
# Endpoint: POST /api/v2/tenant/draft_orders/topup
|
|
45
|
+
# Params:
|
|
46
|
+
# - amount: the custom top-up amount (required)
|
|
47
|
+
# - payment_method_id: id of the payment method to attach (required)
|
|
48
|
+
# - currency: optional -- must be inside this tenant's own supported_currencies_list if
|
|
49
|
+
# given; omitted, resolves to Tenant#preferred_currency_or_default (see
|
|
50
|
+
# BaseController#resolve_requested_currency). This deliberately doesn't read
|
|
51
|
+
# current_currency here even though it's tenant-aware now too -- resolve_requested_currency
|
|
52
|
+
# hard-rejects an out-of-list currency (422), where current_currency would silently
|
|
53
|
+
# fall back instead, which would make this endpoint's own validation cosmetic.
|
|
54
|
+
def topup
|
|
55
|
+
currency, error = resolve_requested_currency
|
|
56
|
+
return render_error_payload(error) if error
|
|
57
|
+
|
|
58
|
+
result = SpreeCmCommissioner::TopupDraftOrders::Create.call(
|
|
59
|
+
amount: params[:amount],
|
|
60
|
+
payment_method_id: params[:payment_method_id],
|
|
61
|
+
user: spree_current_user,
|
|
62
|
+
store: current_store,
|
|
63
|
+
currency: currency,
|
|
64
|
+
tenant: @tenant,
|
|
65
|
+
options: {
|
|
66
|
+
device_id: params[:device_id],
|
|
67
|
+
last_ip_address: request.remote_ip
|
|
68
|
+
}
|
|
69
|
+
)
|
|
70
|
+
|
|
71
|
+
if result.success?
|
|
72
|
+
render_serialized_payload(201) { serialize_resource(serializable_order(result.value[:order])) }
|
|
73
|
+
else
|
|
74
|
+
render_error_payload(result.error)
|
|
75
|
+
end
|
|
76
|
+
end
|
|
77
|
+
|
|
36
78
|
private
|
|
37
79
|
|
|
38
80
|
# Re-fetch the created order with everything the enriched payload touches eager-loaded, so
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
module Spree
|
|
2
|
+
module Api
|
|
3
|
+
module V2
|
|
4
|
+
module Tenant
|
|
5
|
+
# Endpoint: GET /api/v2/tenant/prepaid_config
|
|
6
|
+
# Mirrors Spree::Api::V2::Storefront::PrepaidConfigController, resolved with this tenant
|
|
7
|
+
# so supported_currencies/preferred_currency reflect Tenant#supported_currencies_list/
|
|
8
|
+
# #preferred_currency_or_default -- the same values BaseController#
|
|
9
|
+
# resolve_requested_currency already validates/defaults `?currency=` against on
|
|
10
|
+
# GET account/prepaid_balances and POST draft_orders/topup, now discoverable up front
|
|
11
|
+
# instead of only enforced after the fact on submit. Not under account/ -- this is
|
|
12
|
+
# tenant-wide config, not per-user account data (identical response for every user).
|
|
13
|
+
class PrepaidConfigController < BaseController
|
|
14
|
+
before_action :require_spree_current_user
|
|
15
|
+
|
|
16
|
+
def show
|
|
17
|
+
render_serialized_payload { serialize_resource(SpreeCmCommissioner::PrepaidConfig.resolve(tenant: @tenant)) }
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
private
|
|
21
|
+
|
|
22
|
+
def resource_serializer
|
|
23
|
+
Spree::V2::Tenant::PrepaidConfigSerializer
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
end
|
|
@@ -0,0 +1,217 @@
|
|
|
1
|
+
module SpreeCmCommissioner
|
|
2
|
+
module Admin
|
|
3
|
+
# Core's admin/users/:user_id/store_credits form (Spree::Admin::StoreCreditsController) builds
|
|
4
|
+
# a raw Spree::StoreCredit directly -- fine for ordinary store credit, but for the prepaid
|
|
5
|
+
# category (still named "Wallet" at the DB row level -- see #prepaid_category? below) it would
|
|
6
|
+
# silently bypass everything Prepaid::AdminCredit exists for: the ledger
|
|
7
|
+
# (cm_prepaid_transactions), the one-row-per-(user,currency,issuer_tenant_id) invariant
|
|
8
|
+
# (Mutation#find_or_create_prepaid_credit!), and the credit_type the prepaid system actually
|
|
9
|
+
# looks up by (core's own associate_credit_type would set "Expiring"/"Non-expiring" here, not
|
|
10
|
+
# our "prepaid" type -- the row wouldn't even show up in GET /prepaid). Route a prepaid-category
|
|
11
|
+
# submission through AdminCredit instead; everything else keeps core's original behavior.
|
|
12
|
+
module StoreCreditsControllerDecorator
|
|
13
|
+
TABS = %w[credits transactions].freeze
|
|
14
|
+
TRANSACTIONS_PER_PAGE = 25
|
|
15
|
+
|
|
16
|
+
# override
|
|
17
|
+
# Adds a per-currency balance summary (actual balance, topped up, reversed, held) for this
|
|
18
|
+
# user's prepaid-category credits, same shape as the admin store-credit dashboard's overview
|
|
19
|
+
# tab (StoreCreditOverviewQuery) -- see UserStoreCreditSummaryQuery for why it's a separate
|
|
20
|
+
# query rather than reusing that one directly. Also adds a second, filterable "Transactions"
|
|
21
|
+
# tab across this user's own prepaid ledger, same tab/search pattern as that dashboard's own
|
|
22
|
+
# Transactions tab -- see build_transactions_search below.
|
|
23
|
+
def index
|
|
24
|
+
super
|
|
25
|
+
@currency_summaries = SpreeCmCommissioner::UserStoreCreditSummaryQuery.new(user: @user).call
|
|
26
|
+
@current_tab = TABS.include?(params[:tab]) ? params[:tab] : 'credits'
|
|
27
|
+
build_transactions_search if @current_tab == 'transactions'
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
# override
|
|
31
|
+
def create
|
|
32
|
+
return super unless prepaid_category?
|
|
33
|
+
|
|
34
|
+
result = SpreeCmCommissioner::Prepaid::AdminCredit.call(
|
|
35
|
+
user: @user,
|
|
36
|
+
amount: params[:store_credit][:amount],
|
|
37
|
+
currency: params[:store_credit][:currency],
|
|
38
|
+
reason: params[:store_credit][:memo].presence || 'admin credit',
|
|
39
|
+
admin: try_spree_current_user,
|
|
40
|
+
idempotency_key: admin_credit_idempotency_key
|
|
41
|
+
)
|
|
42
|
+
|
|
43
|
+
if result.success?
|
|
44
|
+
flash[:success] = flash_message_for(result.value, :successfully_created)
|
|
45
|
+
redirect_to spree.admin_user_store_credits_path(@user)
|
|
46
|
+
else
|
|
47
|
+
@store_credit = @user.store_credits.new(permitted_store_credit_params)
|
|
48
|
+
load_categories
|
|
49
|
+
flash[:error] = result.error.value || result.error.to_s
|
|
50
|
+
render :new, status: :unprocessable_entity
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
# override
|
|
55
|
+
# A prepaid credit's amount is derived entirely from the ledger (top-ups, admin credits,
|
|
56
|
+
# reversals) -- letting this form edit it directly would silently desync amount from
|
|
57
|
+
# cm_prepaid_transactions with no ledger row to explain the change. destroy is already safe
|
|
58
|
+
# (prepaid_transactions has dependent: :restrict_with_exception), so only update needs a guard.
|
|
59
|
+
#
|
|
60
|
+
# For a non-prepaid credit, core's own #update leaves `action`/`action_originator` unset, so
|
|
61
|
+
# Spree::StoreCredit#store_event (store_credit_decorator.rb) reuses/overwrites the single
|
|
62
|
+
# `allocation` event instead of appending one -- the prior amount and who changed it are
|
|
63
|
+
# both lost, and originator ends up nil. Setting `action` here makes store_event build a new
|
|
64
|
+
# event row per edit instead, so amount history survives.
|
|
65
|
+
def update
|
|
66
|
+
return credit_not_editable if prepaid_category?
|
|
67
|
+
|
|
68
|
+
@store_credit.assign_attributes(permitted_store_credit_params)
|
|
69
|
+
@store_credit.action = SpreeCmCommissioner::StoreCreditDecorator::ADMIN_ADJUSTMENT_ACTION
|
|
70
|
+
@store_credit.action_originator = try_spree_current_user
|
|
71
|
+
@store_credit.created_by = try_spree_current_user
|
|
72
|
+
|
|
73
|
+
if @store_credit.save
|
|
74
|
+
flash[:success] = flash_message_for(@store_credit, :successfully_updated)
|
|
75
|
+
redirect_to spree.admin_user_store_credits_path(@user)
|
|
76
|
+
else
|
|
77
|
+
load_categories
|
|
78
|
+
flash[:error] = Spree.t('store_credit.errors.unable_to_update')
|
|
79
|
+
render :edit, status: :unprocessable_entity
|
|
80
|
+
end
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
# Reverses a single top_up/admin_credit ledger row (member route, admin/users/:user_id/
|
|
84
|
+
# store_credits/:id/reverse_top_up -- :id is the store_credit, params[:transaction_id]
|
|
85
|
+
# picks which ledger row). Keyed off the transaction id so a resubmitted form (double-click,
|
|
86
|
+
# retried request) can't reverse the same row twice.
|
|
87
|
+
def reverse_top_up
|
|
88
|
+
@store_credit = @user.store_credits.find(params[:id])
|
|
89
|
+
transaction = @store_credit.prepaid_transactions.find(params[:transaction_id])
|
|
90
|
+
|
|
91
|
+
error = not_reversible_reason(transaction)
|
|
92
|
+
if error
|
|
93
|
+
flash[:error] = error
|
|
94
|
+
return redirect_to spree.admin_user_store_credits_path(@user)
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
result = SpreeCmCommissioner::Prepaid::ReverseTopUp.call(
|
|
98
|
+
store_credit: @store_credit,
|
|
99
|
+
amount: transaction.amount,
|
|
100
|
+
idempotency_key: "admin_reverse:#{transaction.id}",
|
|
101
|
+
reason: params[:reason].presence || "admin reversal of #{transaction.transaction_type} ##{transaction.id}",
|
|
102
|
+
admin: try_spree_current_user
|
|
103
|
+
)
|
|
104
|
+
|
|
105
|
+
if result.success?
|
|
106
|
+
flash[:success] = Spree.t(:successfully_updated, resource: Spree::StoreCredit.model_name.human)
|
|
107
|
+
else
|
|
108
|
+
flash[:error] = result.error.value || result.error.to_s
|
|
109
|
+
end
|
|
110
|
+
|
|
111
|
+
redirect_to spree.admin_user_store_credits_path(@user)
|
|
112
|
+
end
|
|
113
|
+
|
|
114
|
+
# Freezes a prepaid-category store_credit (member route, admin/users/:user_id/store_credits/
|
|
115
|
+
# :id/freeze). 'full' blocks both spend and top-up; 'topup_only' blocks only top-up -- see
|
|
116
|
+
# StoreCreditDecorator#spend_blocked?/#topup_blocked?.
|
|
117
|
+
def freeze
|
|
118
|
+
@store_credit = @user.store_credits.find(params[:id])
|
|
119
|
+
|
|
120
|
+
result = SpreeCmCommissioner::Prepaid::Freeze.call(
|
|
121
|
+
store_credit: @store_credit,
|
|
122
|
+
admin: try_spree_current_user,
|
|
123
|
+
reason: params[:reason],
|
|
124
|
+
level: params[:level].presence || 'full',
|
|
125
|
+
until_at: params[:until_at].presence
|
|
126
|
+
)
|
|
127
|
+
|
|
128
|
+
if result.success?
|
|
129
|
+
flash[:success] = Spree.t(:successfully_updated, resource: Spree::StoreCredit.model_name.human)
|
|
130
|
+
else
|
|
131
|
+
flash[:error] = result.error.value || result.error.to_s
|
|
132
|
+
end
|
|
133
|
+
|
|
134
|
+
redirect_to spree.admin_user_store_credits_path(@user)
|
|
135
|
+
end
|
|
136
|
+
|
|
137
|
+
# Lifts a freeze on a prepaid-category store_credit (member route, admin/users/:user_id/
|
|
138
|
+
# store_credits/:id/unfreeze).
|
|
139
|
+
def unfreeze
|
|
140
|
+
@store_credit = @user.store_credits.find(params[:id])
|
|
141
|
+
SpreeCmCommissioner::Prepaid::Unfreeze.call(store_credit: @store_credit, admin: try_spree_current_user)
|
|
142
|
+
|
|
143
|
+
flash[:success] = Spree.t(:successfully_updated, resource: Spree::StoreCredit.model_name.human)
|
|
144
|
+
redirect_to spree.admin_user_store_credits_path(@user)
|
|
145
|
+
end
|
|
146
|
+
|
|
147
|
+
private
|
|
148
|
+
|
|
149
|
+
# This user's own prepaid ledger rows across every currency/tenant, filtered via Ransack
|
|
150
|
+
# (q[currency_eq], q[transaction_type_eq], q[created_at_gteq]/[created_at_lteq], ...) --
|
|
151
|
+
# same fields the admin store-credit dashboard's own Transactions tab filters on, minus
|
|
152
|
+
# user_email_cont (already scoped to one user here). Also carries the per-credit "recent
|
|
153
|
+
# activity + Reverse" behavior formerly on an inline <details> block under each credit row
|
|
154
|
+
# -- @reversed_transaction_ids is computed over this user's *entire* ledger, not just the
|
|
155
|
+
# current page, since a reversal row and the top_up/admin_credit it reverses can land on
|
|
156
|
+
# different pages once this is paginated.
|
|
157
|
+
def build_transactions_search
|
|
158
|
+
params[:q] = {} if params[:q].blank?
|
|
159
|
+
@transactions_search = SpreeCmCommissioner::PrepaidTransaction.where(user: @user).ransack(params[:q])
|
|
160
|
+
@transactions = @transactions_search.result
|
|
161
|
+
.includes(:order, :tenant, :store_credit, :admin)
|
|
162
|
+
.order(created_at: :desc)
|
|
163
|
+
.page(params[:page])
|
|
164
|
+
.per(TRANSACTIONS_PER_PAGE)
|
|
165
|
+
|
|
166
|
+
@reversed_transaction_ids = SpreeCmCommissioner::PrepaidTransaction
|
|
167
|
+
.where(user: @user, transaction_type: 'reverse_top_up')
|
|
168
|
+
.pluck(:idempotency_key)
|
|
169
|
+
.filter_map { |key| key[/\Aadmin_reverse:(\d+)\z/, 1]&.to_i }
|
|
170
|
+
.to_set
|
|
171
|
+
end
|
|
172
|
+
|
|
173
|
+
# Deterministic within a 30s window so a double-clicked/retried submit of the same form
|
|
174
|
+
# collides on the ledger's unique idempotency_key (see Mutation#write_ledger!) instead of
|
|
175
|
+
# double-crediting the user; a genuinely separate credit made later (new window) or with a
|
|
176
|
+
# different amount/currency/memo still gets its own row.
|
|
177
|
+
def admin_credit_idempotency_key
|
|
178
|
+
memo_digest = Digest::SHA256.hexdigest(params[:store_credit][:memo].to_s)[0, 12]
|
|
179
|
+
window = Time.current.to_i / 30
|
|
180
|
+
"admin_credit:#{@user.id}:#{params[:store_credit][:amount]}:#{params[:store_credit][:currency]}:#{memo_digest}:#{window}"
|
|
181
|
+
end
|
|
182
|
+
|
|
183
|
+
# See SpreeCmCommissioner::StoreCreditCategoryDecorator::WALLET_CATEGORY_NAME for why this
|
|
184
|
+
# is still "Wallet" at the DB level -- renaming that row's `name` is a data migration
|
|
185
|
+
# outside this refactor's scope; this method just gives the DB-level check a
|
|
186
|
+
# codebase-consistent name to call.
|
|
187
|
+
def prepaid_category?
|
|
188
|
+
category_id = params.dig(:store_credit, :category_id) || @store_credit&.category_id
|
|
189
|
+
wallet_name = SpreeCmCommissioner::StoreCreditCategoryDecorator::WALLET_CATEGORY_NAME
|
|
190
|
+
category_id.present? && Spree::StoreCreditCategory.find_by(id: category_id)&.name == wallet_name
|
|
191
|
+
end
|
|
192
|
+
|
|
193
|
+
def credit_not_editable
|
|
194
|
+
flash[:error] = I18n.t('spree_cm_commissioner.store_credit.credit_not_editable')
|
|
195
|
+
redirect_to spree.admin_user_store_credits_path(@user)
|
|
196
|
+
end
|
|
197
|
+
|
|
198
|
+
def not_reversible_reason(transaction)
|
|
199
|
+
unless %w[top_up admin_credit].include?(transaction.transaction_type) && transaction.status == 'completed'
|
|
200
|
+
return I18n.t('spree_cm_commissioner.store_credit.transaction_not_reversible')
|
|
201
|
+
end
|
|
202
|
+
|
|
203
|
+
# ReverseTopUp only ever adjusts the prepaid ledger -- it never touches the order/payment. For
|
|
204
|
+
# an order-backed top_up (a real payment the customer made), reversing here would silently
|
|
205
|
+
# claw back store credit without refunding the original payment, leaving the order looking
|
|
206
|
+
# paid while the money it credited is gone. Route that case through a real refund instead.
|
|
207
|
+
return I18n.t('spree_cm_commissioner.store_credit.order_backed_transaction_not_reversible') if transaction.order.present?
|
|
208
|
+
|
|
209
|
+
nil
|
|
210
|
+
end
|
|
211
|
+
end
|
|
212
|
+
end
|
|
213
|
+
end
|
|
214
|
+
|
|
215
|
+
unless Spree::Admin::StoreCreditsController.ancestors.include?(SpreeCmCommissioner::Admin::StoreCreditsControllerDecorator)
|
|
216
|
+
Spree::Admin::StoreCreditsController.prepend(SpreeCmCommissioner::Admin::StoreCreditsControllerDecorator)
|
|
217
|
+
end
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
# Raised when one or more line items are explicitly excluded from rescheduling.
|
|
2
|
+
module SpreeCmCommissioner
|
|
3
|
+
module ReschedulePolicies
|
|
4
|
+
class NotReschedulableError < StandardError
|
|
5
|
+
# override
|
|
6
|
+
def message
|
|
7
|
+
I18n.t('reschedule_policies.resolve.not_reschedulable')
|
|
8
|
+
end
|
|
9
|
+
end
|
|
10
|
+
end
|
|
11
|
+
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,141 @@
|
|
|
1
|
+
# 🔁 --- [RESCHEDULE REQUESTED] ---
|
|
2
|
+
#
|
|
3
|
+
# Request Number: <code>RS525393292</code>
|
|
4
|
+
# Booking: <code>R898147633</code>
|
|
5
|
+
#
|
|
6
|
+
# <b>Leg 1</b>
|
|
7
|
+
# <i>🗓️ Aug 24, 2026 - 08:00 → Aug 26, 2026 - 08:00</i>
|
|
8
|
+
# <i>💺 A1, A2 → B3, B4</i>
|
|
9
|
+
#
|
|
10
|
+
# <b>Leg 2</b>
|
|
11
|
+
# <i>🗓️ Aug 27, 2026 - 18:00 → Aug 29, 2026 - 18:00</i>
|
|
12
|
+
#
|
|
13
|
+
# <b>Fee:</b> $5.00
|
|
14
|
+
# <b>Balance due:</b> $12.00
|
|
15
|
+
# <i>⏳ Respond before Aug 24, 2026 - 20:00</i>
|
|
16
|
+
|
|
17
|
+
module SpreeCmCommissioner
|
|
18
|
+
class RescheduleTelegramMessageFactory < TelegramMessageFactory
|
|
19
|
+
attr_reader :reschedule_request
|
|
20
|
+
|
|
21
|
+
def initialize(title:, reschedule_request:, subtitle: nil)
|
|
22
|
+
@reschedule_request = reschedule_request
|
|
23
|
+
|
|
24
|
+
# `subtitle` carries `last_error` for the ops escalation - exception text, the
|
|
25
|
+
# likeliest place for `<` and `&`. Telegram rejects malformed HTML with a 400,
|
|
26
|
+
# so an unescaped one loses the very alert that needed a human.
|
|
27
|
+
super(title: title, subtitle: escape(subtitle))
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
# override
|
|
31
|
+
def body
|
|
32
|
+
text = []
|
|
33
|
+
text << "Request Number: #{inline_code(reschedule_request.number)}\nBooking: #{inline_code(reschedule_request.order&.number)}"
|
|
34
|
+
text << leg_blocks.join("\n\n") if leg_blocks.any?
|
|
35
|
+
text << money_lines.join("\n")
|
|
36
|
+
text.compact_blank.join("\n\n")
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
# override
|
|
40
|
+
def footer
|
|
41
|
+
return if reschedule_request.expires_at.blank?
|
|
42
|
+
|
|
43
|
+
italic("⏳ Respond before #{pretty_datetime(reschedule_request.expires_at)}")
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
private
|
|
47
|
+
|
|
48
|
+
# Snapshots are paired by `position`, so zipping the two sides gives one
|
|
49
|
+
# before -> after block per leg. Seats, checkout and product only get a line when
|
|
50
|
+
# they changed; the date is always shown, because a seat-only move still needs to
|
|
51
|
+
# say which departure it is about.
|
|
52
|
+
def leg_blocks
|
|
53
|
+
@leg_blocks ||= begin
|
|
54
|
+
before = reschedule_request.previous_request_snapshots.sort_by(&:position)
|
|
55
|
+
after = reschedule_request.new_request_snapshots.sort_by(&:position)
|
|
56
|
+
pairs = before.zip(after)
|
|
57
|
+
|
|
58
|
+
pairs.each_with_index.filter_map do |(before_snapshot, after_snapshot), index|
|
|
59
|
+
next if before_snapshot.nil?
|
|
60
|
+
|
|
61
|
+
lines = []
|
|
62
|
+
lines << bold("Leg #{index + 1}") if pairs.size > 1
|
|
63
|
+
lines << product_line(before_snapshot, after_snapshot)
|
|
64
|
+
lines << date_line(before_snapshot, after_snapshot)
|
|
65
|
+
lines << checkout_line(before_snapshot, after_snapshot)
|
|
66
|
+
lines << seat_line(before_snapshot, after_snapshot)
|
|
67
|
+
lines.compact.join("\n").presence
|
|
68
|
+
end
|
|
69
|
+
end
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
def date_line(before_snapshot, after_snapshot)
|
|
73
|
+
from = pretty_datetime(before_snapshot&.from_date)
|
|
74
|
+
to = pretty_datetime(after_snapshot&.from_date)
|
|
75
|
+
return if from.blank? && to.blank?
|
|
76
|
+
return italic("🗓️ #{from}") if from == to
|
|
77
|
+
|
|
78
|
+
italic("🗓️ #{from} → #{to}")
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
# Accommodation legs carry a checkout date; transit legs do not.
|
|
82
|
+
def checkout_line(before_snapshot, after_snapshot)
|
|
83
|
+
from = checkout_text(before_snapshot)
|
|
84
|
+
to = checkout_text(after_snapshot)
|
|
85
|
+
return if from.blank? && to.blank?
|
|
86
|
+
return if from == to
|
|
87
|
+
|
|
88
|
+
italic("🏁 until #{from} → until #{to}")
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
def checkout_text(snapshot)
|
|
92
|
+
return '' if snapshot.nil? || snapshot.to_date.blank?
|
|
93
|
+
return '' if snapshot.from_date.present? && snapshot.to_date.to_date == snapshot.from_date.to_date
|
|
94
|
+
|
|
95
|
+
pretty_date(snapshot.to_date)
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
# Blank when the seats did not move, or when the product has no seating at all.
|
|
99
|
+
def seat_line(before_snapshot, after_snapshot)
|
|
100
|
+
from = seat_text(before_snapshot)
|
|
101
|
+
to = seat_text(after_snapshot)
|
|
102
|
+
return if from.blank? && to.blank?
|
|
103
|
+
return if from == to
|
|
104
|
+
|
|
105
|
+
italic("💺 #{from.presence || '-'} → #{to.presence || 'to be assigned'}")
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
# Only meaningful when the policy allowed a variant change.
|
|
109
|
+
def product_line(before_snapshot, after_snapshot)
|
|
110
|
+
from = before_snapshot.variant&.product&.name.to_s
|
|
111
|
+
to = after_snapshot&.variant&.product&.name.to_s
|
|
112
|
+
return if from.blank? || from == to
|
|
113
|
+
|
|
114
|
+
bold("#{escape(from)} → #{escape(to)}")
|
|
115
|
+
end
|
|
116
|
+
|
|
117
|
+
def seat_text(snapshot)
|
|
118
|
+
escape(Array(snapshot&.block_labels).compact_blank.join(', '))
|
|
119
|
+
end
|
|
120
|
+
|
|
121
|
+
# Telegram's HTML parse mode only tolerates `<`, `>` and `&` as entities, and
|
|
122
|
+
# rejects the whole message otherwise. Vendor-supplied names ("Bed & Breakfast")
|
|
123
|
+
# hit this in normal use.
|
|
124
|
+
def escape(text)
|
|
125
|
+
return text if text.blank?
|
|
126
|
+
|
|
127
|
+
text.to_s.gsub('&', '&').gsub('<', '<').gsub('>', '>')
|
|
128
|
+
end
|
|
129
|
+
|
|
130
|
+
def money_lines
|
|
131
|
+
lines = [bold_amount('Fee', reschedule_request.display_fee_amount)]
|
|
132
|
+
lines << bold_amount('Balance due', reschedule_request.display_amount_due_total) if reschedule_request.amount_due_total.to_f.positive?
|
|
133
|
+
lines << bold_amount('Credit', reschedule_request.display_credit_amount_total) if reschedule_request.credit_amount_total.to_f.positive?
|
|
134
|
+
lines.compact
|
|
135
|
+
end
|
|
136
|
+
|
|
137
|
+
def bold_amount(label, money)
|
|
138
|
+
"#{bold("#{label}:")} #{money}"
|
|
139
|
+
end
|
|
140
|
+
end
|
|
141
|
+
end
|
|
@@ -23,7 +23,7 @@ module SpreeCmCommissioner
|
|
|
23
23
|
def fetch_recent_items
|
|
24
24
|
query = SpreeCmCommissioner::InventoryItem.active
|
|
25
25
|
.joins(variant: :product)
|
|
26
|
-
.where(spree_variants: { track_inventory: true })
|
|
26
|
+
.where(spree_variants: { track_inventory: true }, spree_products: { offline: false })
|
|
27
27
|
.where('cm_inventory_items.updated_at > ?', @time_range)
|
|
28
28
|
.select(
|
|
29
29
|
'cm_inventory_items.id',
|
|
@@ -26,6 +26,18 @@ module Spree
|
|
|
26
26
|
rescue StandardError
|
|
27
27
|
nil
|
|
28
28
|
end
|
|
29
|
+
|
|
30
|
+
# A small info-circle icon with a native-title hover tooltip, for labeling a metric/column
|
|
31
|
+
# whose meaning isn't obvious from its name alone (e.g. "Held" -> "Amount currently
|
|
32
|
+
# authorized ..."). Uses svg_icon/backend-info-circle.svg -- the same inline-SVG mechanism
|
|
33
|
+
# every other icon in this admin theme already renders through (menu tabs, buttons via
|
|
34
|
+
# link_to_with_icon) -- rather than a text/Unicode glyph, which can render as an invisible
|
|
35
|
+
# box depending on the system font and was the reason two earlier attempts here didn't show.
|
|
36
|
+
def hint_icon(hint)
|
|
37
|
+
icon = svg_icon(name: 'info-circle.svg', width: 12, height: 12)
|
|
38
|
+
icon_style = 'cursor: help; margin-left: 4px; display: inline-flex; vertical-align: middle;'
|
|
39
|
+
content_tag(:span, icon, title: hint, style: icon_style)
|
|
40
|
+
end
|
|
29
41
|
end
|
|
30
42
|
end
|
|
31
43
|
end
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
module Spree
|
|
2
|
+
module Admin
|
|
3
|
+
module ReschedulePoliciesHelper
|
|
4
|
+
# Human-readable fee summary for a policy's calculator.
|
|
5
|
+
def reschedule_policy_fee_label(policy)
|
|
6
|
+
calculator = policy.calculator
|
|
7
|
+
return '-' if calculator.blank?
|
|
8
|
+
|
|
9
|
+
case calculator
|
|
10
|
+
when Spree::Calculator::FlatRate
|
|
11
|
+
Spree::Money.new(calculator.preferred_amount, currency: calculator.preferred_currency).to_s
|
|
12
|
+
when Spree::Calculator::PercentOnLineItem
|
|
13
|
+
"#{calculator.preferred_percent}%"
|
|
14
|
+
else
|
|
15
|
+
calculator.class.name.demodulize.titleize
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
# 'percent' or 'flat', used to pre-select the fee-type toggle on the form.
|
|
20
|
+
def reschedule_policy_fee_type(policy)
|
|
21
|
+
policy.calculator.is_a?(Spree::Calculator::PercentOnLineItem) ? 'percent' : 'flat'
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
# Bootstrap badge for the policy status enum.
|
|
25
|
+
def reschedule_policy_status_badge(status)
|
|
26
|
+
klass = { 'active' => 'badge-success',
|
|
27
|
+
'draft' => 'badge-secondary',
|
|
28
|
+
'inactive' => 'badge-warning',
|
|
29
|
+
'archived' => 'badge-dark'
|
|
30
|
+
}.fetch(status.to_s, 'badge-light')
|
|
31
|
+
content_tag :span, status.to_s.titleize, class: "badge #{klass}"
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
end
|