spree_cm_commissioner 2.13.5 → 2.14.0

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.
Files changed (532) hide show
  1. checksums.yaml +4 -4
  2. data/Gemfile.lock +1 -1
  3. data/app/assets/javascripts/spree_cm_commissioner/backend.js +0 -1
  4. data/app/assets/stylesheets/spree_cm_commissioner/backend/calendar.scss +0 -39
  5. data/app/controllers/concerns/spree/admin/rates_availability_cells.rb +16 -0
  6. data/app/controllers/concerns/spree/api/v2/product_list_includes_decorator.rb +1 -0
  7. data/app/controllers/spree/admin/base_controller_decorator.rb +14 -0
  8. data/app/controllers/spree/admin/customer_notifications_controller.rb +16 -1
  9. data/app/controllers/spree/admin/guests_controller.rb +2 -1
  10. data/app/controllers/spree/admin/integrations_controller.rb +16 -7
  11. data/app/controllers/spree/admin/inventory_items_controller.rb +33 -62
  12. data/app/controllers/spree/admin/prepaid_transactions_controller.rb +69 -0
  13. data/app/controllers/spree/admin/private_sales_controller.rb +6 -0
  14. data/app/controllers/spree/admin/product_commissions_controller.rb +2 -1
  15. data/app/controllers/spree/admin/product_option_types_controller.rb +88 -0
  16. data/app/controllers/spree/admin/rate_plans_controller.rb +125 -0
  17. data/app/controllers/spree/admin/rates_availabilities_controller.rb +142 -0
  18. data/app/controllers/spree/admin/reschedule_policies_controller.rb +148 -0
  19. data/app/controllers/spree/admin/reschedule_requests_controller.rb +82 -0
  20. data/app/controllers/spree/admin/stock_managements_controller.rb +1 -53
  21. data/app/controllers/spree/admin/store_credit_admin_controller.rb +122 -0
  22. data/app/controllers/spree/admin/ticket_transfer_rules_controller.rb +7 -0
  23. data/app/controllers/spree/api/switch_cm/base_controller.rb +109 -0
  24. data/app/controllers/spree/api/switch_cm/tokens_controller.rb +68 -0
  25. data/app/controllers/spree/api/switch_cm/xml/availabilities_controller.rb +39 -0
  26. data/app/controllers/spree/api/switch_cm/xml/base_controller.rb +37 -0
  27. data/app/controllers/spree/api/switch_cm/xml/reservation_summaries_controller.rb +28 -0
  28. data/app/controllers/spree/api/switch_cm/xml/reservations_controller.rb +57 -0
  29. data/app/controllers/spree/api/switch_cm/xml/room_rate_availabilities_controller.rb +56 -0
  30. data/app/controllers/spree/api/switch_cm/xml/room_rates_controller.rb +45 -0
  31. data/app/controllers/spree/api/v2/operator/guests_controller.rb +1 -1
  32. data/app/controllers/spree/api/v2/platform/store_credits_controller_decorator.rb +35 -0
  33. data/app/controllers/spree/api/v2/storefront/accommodations/variants_controller.rb +31 -5
  34. data/app/controllers/spree/api/v2/storefront/account/prepaid_balances_controller.rb +52 -0
  35. data/app/controllers/spree/api/v2/storefront/account/prepaid_transactions_controller.rb +56 -0
  36. data/app/controllers/spree/api/v2/storefront/draft_orders_controller.rb +37 -0
  37. data/app/controllers/spree/api/v2/storefront/inventory_items_controller.rb +2 -2
  38. data/app/controllers/spree/api/v2/storefront/prepaid_config_controller.rb +22 -0
  39. data/app/controllers/spree/api/v2/storefront/reschedule_requests_controller.rb +156 -0
  40. data/app/controllers/spree/api/v2/storefront/transit/item_availabilities_controller.rb +66 -0
  41. data/app/controllers/spree/api/v2/tenant/account/prepaid_balances_controller.rb +51 -0
  42. data/app/controllers/spree/api/v2/tenant/account/prepaid_transactions_controller.rb +53 -0
  43. data/app/controllers/spree/api/v2/tenant/base_controller.rb +20 -0
  44. data/app/controllers/spree/api/v2/tenant/draft_orders_controller.rb +42 -0
  45. data/app/controllers/spree/api/v2/tenant/intercity_taxi/draft_orders_controller.rb +0 -1
  46. data/app/controllers/spree/api/v2/tenant/prepaid_config_controller.rb +29 -0
  47. data/app/controllers/spree_cm_commissioner/admin/store_credits_controller_decorator.rb +217 -0
  48. data/app/errors/spree_cm_commissioner/reschedule_policies/invalid_policyable_error.rb +8 -0
  49. data/app/errors/spree_cm_commissioner/reschedule_policies/not_reschedulable_error.rb +11 -0
  50. data/app/errors/spree_cm_commissioner/reschedule_requests/cancel_not_allowed_error.rb +6 -0
  51. data/app/errors/spree_cm_commissioner/reschedule_requests/complete_cash_payment_error.rb +6 -0
  52. data/app/errors/spree_cm_commissioner/reschedule_requests/eligibility_denied_error.rb +7 -0
  53. data/app/factory/spree_cm_commissioner/order_telegram_message_factory.rb +7 -3
  54. data/app/factory/spree_cm_commissioner/reschedule_telegram_message_factory.rb +141 -0
  55. data/app/finders/spree_cm_commissioner/accommodations/find.rb +10 -8
  56. data/app/finders/spree_cm_commissioner/accommodations/find_variant.rb +5 -11
  57. data/app/finders/spree_cm_commissioner/inventory_items/recently_changed_finder.rb +1 -1
  58. data/app/finders/spree_cm_commissioner/rates_availabilities/find_bookings.rb +116 -0
  59. data/app/helpers/spree/admin/base_helper_decorator.rb +12 -0
  60. data/app/helpers/spree/admin/reschedule_policies_helper.rb +35 -0
  61. data/app/helpers/spree/admin/reschedule_requests_helper.rb +79 -0
  62. data/app/helpers/spree_cm_commissioner/admin/inventory_items_helper.rb +36 -0
  63. data/app/helpers/spree_cm_commissioner/admin/rates_availabilities_helper.rb +247 -0
  64. data/app/interactors/spree_cm_commissioner/create_ticket.rb +1 -0
  65. data/app/interactors/spree_cm_commissioner/intercity_taxi_order_creator.rb +0 -1
  66. data/app/jobs/spree_cm_commissioner/audit_event_job.rb +29 -0
  67. data/app/jobs/spree_cm_commissioner/option_type_variants_public_metadata_updater_job.rb +1 -0
  68. data/app/jobs/spree_cm_commissioner/option_value_variants_public_metadata_updater_job.rb +1 -0
  69. data/app/jobs/spree_cm_commissioner/prepaid_reconciliation_cron_job.rb +9 -0
  70. data/app/jobs/spree_cm_commissioner/prepaid_reverse_top_up_job.rb +16 -0
  71. data/app/jobs/spree_cm_commissioner/prepaid_setup_types_job.rb +24 -0
  72. data/app/jobs/spree_cm_commissioner/prepaid_top_up_credit_job.rb +31 -0
  73. data/app/jobs/spree_cm_commissioner/product_variants_public_metadata_updater_job.rb +12 -0
  74. data/app/jobs/spree_cm_commissioner/reschedule_requests/approval_reminder_job.rb +17 -0
  75. data/app/jobs/spree_cm_commissioner/reschedule_requests/execute_job.rb +20 -0
  76. data/app/jobs/spree_cm_commissioner/reschedule_requests/expire_overdue_requests_job.rb +12 -0
  77. data/app/jobs/spree_cm_commissioner/reschedule_requests/expire_request_job.rb +15 -0
  78. data/app/jobs/spree_cm_commissioner/reschedule_requests/payment_reminder_job.rb +17 -0
  79. data/app/models/concerns/spree_cm_commissioner/audit_wrapper.rb +139 -0
  80. data/app/models/concerns/spree_cm_commissioner/integrations/inventory_sync_cachable.rb +9 -0
  81. data/app/models/concerns/spree_cm_commissioner/kyc_bitwise.rb +4 -0
  82. data/app/models/concerns/spree_cm_commissioner/line_item_durationable.rb +1 -10
  83. data/app/models/concerns/spree_cm_commissioner/line_item_guests_concern.rb +33 -3
  84. data/app/models/concerns/spree_cm_commissioner/line_item_rate_plannable.rb +55 -0
  85. data/app/models/concerns/spree_cm_commissioner/line_item_transitable.rb +0 -16
  86. data/app/models/concerns/spree_cm_commissioner/option_type_attr_type.rb +24 -5
  87. data/app/models/concerns/spree_cm_commissioner/order_prepaid_payment.rb +15 -0
  88. data/app/models/concerns/spree_cm_commissioner/order_state_machine.rb +56 -8
  89. data/app/models/concerns/spree_cm_commissioner/product_type.rb +1 -1
  90. data/app/models/concerns/spree_cm_commissioner/rate_plan_options_concern.rb +62 -0
  91. data/app/models/concerns/spree_cm_commissioner/reschedule_request_state_machine.rb +332 -0
  92. data/app/models/concerns/spree_cm_commissioner/service_type.rb +1 -1
  93. data/app/models/concerns/spree_cm_commissioner/store_preference.rb +1 -0
  94. data/app/models/concerns/spree_cm_commissioner/tenant_preference.rb +6 -0
  95. data/app/models/concerns/spree_cm_commissioner/variant_metadata.rb +24 -0
  96. data/app/models/concerns/spree_cm_commissioner/variant_options_concern.rb +12 -0
  97. data/app/models/concerns/spree_cm_commissioner/variant_rate_plannable.rb +81 -0
  98. data/app/models/concerns/spree_cm_commissioner/vehicle_kind.rb +1 -1
  99. data/app/models/spree/core/controller_helpers/currency_decorator.rb +70 -0
  100. data/app/models/spree_cm_commissioner/address_decorator.rb +2 -0
  101. data/app/models/spree_cm_commissioner/audit_event.rb +11 -0
  102. data/app/models/spree_cm_commissioner/configuration.rb +17 -15
  103. data/app/models/spree_cm_commissioner/currency_rate.rb +15 -0
  104. data/app/models/spree_cm_commissioner/current.rb +8 -0
  105. data/app/models/spree_cm_commissioner/customer_notification.rb +4 -1
  106. data/app/models/spree_cm_commissioner/exports/import_order_guests_csv.rb +154 -0
  107. data/app/models/spree_cm_commissioner/exports/operator_guest_json_gzip.rb +8 -2
  108. data/app/models/spree_cm_commissioner/guest.rb +22 -1
  109. data/app/models/spree_cm_commissioner/integration.rb +46 -0
  110. data/app/models/spree_cm_commissioner/integrations/book_me_bus_v1.rb +64 -0
  111. data/app/models/spree_cm_commissioner/integrations/larryta.rb +81 -0
  112. data/app/models/spree_cm_commissioner/integrations/stadium_x_v1.rb +4 -10
  113. data/app/models/spree_cm_commissioner/integrations/switch_cm.rb +96 -0
  114. data/app/models/spree_cm_commissioner/integrations/vireak_buntham.rb +82 -0
  115. data/app/models/spree_cm_commissioner/inventory_item.rb +44 -3
  116. data/app/models/spree_cm_commissioner/line_item_decorator.rb +11 -7
  117. data/app/models/spree_cm_commissioner/maintenance_tasks/cache_invalidation.rb +20 -4
  118. data/app/models/spree_cm_commissioner/maintenance_tasks/event.rb +3 -2
  119. data/app/models/spree_cm_commissioner/notification.rb +8 -0
  120. data/app/models/spree_cm_commissioner/option_type_decorator.rb +2 -1
  121. data/app/models/spree_cm_commissioner/option_value_decorator.rb +3 -0
  122. data/app/models/spree_cm_commissioner/option_value_rate_plan.rb +12 -0
  123. data/app/models/spree_cm_commissioner/order_decorator.rb +20 -2
  124. data/app/models/spree_cm_commissioner/payment_decorator.rb +31 -0
  125. data/app/models/spree_cm_commissioner/payment_method_store_credit_decorator.rb +81 -0
  126. data/app/models/spree_cm_commissioner/place.rb +2 -0
  127. data/app/models/spree_cm_commissioner/prepaid_config.rb +59 -0
  128. data/app/models/spree_cm_commissioner/prepaid_transaction.rb +102 -0
  129. data/app/models/spree_cm_commissioner/price_decorator.rb +13 -0
  130. data/app/models/spree_cm_commissioner/product_decorator.rb +24 -2
  131. data/app/models/spree_cm_commissioner/product_option_type_decorator.rb +42 -0
  132. data/app/models/spree_cm_commissioner/rate_plan.rb +139 -0
  133. data/app/models/spree_cm_commissioner/rate_plan_options.rb +36 -0
  134. data/app/models/spree_cm_commissioner/rate_restriction.rb +45 -0
  135. data/app/models/spree_cm_commissioner/reschedule_policy.rb +94 -0
  136. data/app/models/spree_cm_commissioner/reschedule_policy_assignment.rb +19 -0
  137. data/app/models/spree_cm_commissioner/reschedule_request.rb +227 -0
  138. data/app/models/spree_cm_commissioner/reschedule_requests/guest_selection.rb +80 -0
  139. data/app/models/spree_cm_commissioner/reschedule_snapshot.rb +65 -0
  140. data/app/models/spree_cm_commissioner/route.rb +1 -0
  141. data/app/models/spree_cm_commissioner/seat_layout.rb +1 -0
  142. data/app/models/spree_cm_commissioner/service_calendar.rb +4 -0
  143. data/app/models/spree_cm_commissioner/stock_item_decorator.rb +4 -0
  144. data/app/models/spree_cm_commissioner/store_credit_category_decorator.rb +23 -0
  145. data/app/models/spree_cm_commissioner/store_credit_decorator.rb +142 -0
  146. data/app/models/spree_cm_commissioner/store_credit_event_decorator.rb +94 -0
  147. data/app/models/spree_cm_commissioner/store_credit_type_decorator.rb +43 -0
  148. data/app/models/spree_cm_commissioner/store_decorator.rb +8 -0
  149. data/app/models/spree_cm_commissioner/taxon_decorator.rb +5 -2
  150. data/app/models/spree_cm_commissioner/tenant.rb +37 -0
  151. data/app/models/spree_cm_commissioner/trip.rb +2 -1
  152. data/app/models/spree_cm_commissioner/trip_stop.rb +2 -0
  153. data/app/models/spree_cm_commissioner/user_decorator.rb +79 -0
  154. data/app/models/spree_cm_commissioner/variant_decorator.rb +45 -6
  155. data/app/models/spree_cm_commissioner/variant_options.rb +3 -2
  156. data/app/models/spree_cm_commissioner/vehicle.rb +3 -0
  157. data/app/models/spree_cm_commissioner/vehicle_type.rb +1 -0
  158. data/app/models/spree_cm_commissioner/vendor_decorator.rb +54 -0
  159. data/app/models/spree_cm_commissioner/vendor_place.rb +2 -0
  160. data/app/notifications/spree_cm_commissioner/reschedule_approved_notification.rb +7 -0
  161. data/app/notifications/spree_cm_commissioner/reschedule_cancelled_notification.rb +7 -0
  162. data/app/notifications/spree_cm_commissioner/reschedule_completed_notification.rb +7 -0
  163. data/app/notifications/spree_cm_commissioner/reschedule_expired_notification.rb +7 -0
  164. data/app/notifications/spree_cm_commissioner/reschedule_failed_notification.rb +7 -0
  165. data/app/notifications/spree_cm_commissioner/reschedule_notification_base.rb +41 -0
  166. data/app/notifications/spree_cm_commissioner/reschedule_payment_reminder_notification.rb +15 -0
  167. data/app/notifications/spree_cm_commissioner/reschedule_rejected_notification.rb +7 -0
  168. data/app/notifications/spree_cm_commissioner/reschedule_requested_notification.rb +7 -0
  169. data/app/overrides/spree/admin/option_types/_form/checkbox.html.erb.deface +0 -8
  170. data/app/overrides/spree/admin/option_types/_form/name.html.erb.deface +1 -1
  171. data/app/overrides/spree/admin/products/_filters/vendor_filter.html.erb.deface +6 -0
  172. data/app/overrides/spree/admin/products/_form/offline.html.erb.deface +9 -0
  173. data/app/overrides/spree/admin/shared/_main_menu/sidebar_reschedule.html.erb.deface +9 -0
  174. data/app/overrides/spree/admin/shared/_main_menu/sidebar_store_credit.html.erb.deface +8 -0
  175. data/app/overrides/spree/admin/shared/_order_tabs/reschedule_requests.html.erb.deface +10 -0
  176. data/app/overrides/spree/admin/shared/_product_tabs/rates_availabilities.html.erb.deface +8 -0
  177. data/app/overrides/spree/admin/shared/_product_tabs/stock_managements.html.erb.deface +1 -1
  178. data/app/overrides/spree/admin/variants/index/discontinued_badge.html.erb.deface +13 -0
  179. data/app/overrides/spree/admin/variants/index/option_types_empty_state_link.html.erb.deface +3 -0
  180. data/app/overrides/spree/admin/variants/index/option_types_summary.html.erb.deface +24 -0
  181. data/app/presenters/spree/variants/visible_options_presenter.rb +34 -1
  182. data/app/presenters/spree_cm_commissioner/prepaid_balance_presenter.rb +16 -0
  183. data/app/queries/spree_cm_commissioner/multi_leg_trips_query.rb +40 -22
  184. data/app/queries/spree_cm_commissioner/single_leg_trips_query.rb +28 -14
  185. data/app/queries/spree_cm_commissioner/store_credit_overview_query.rb +75 -0
  186. data/app/queries/spree_cm_commissioner/user_store_credit_summary_query.rb +53 -0
  187. data/app/request_schemas/spree_cm_commissioner/intercity_taxi_draft_order_update_schema.rb +0 -1
  188. data/app/serializables/spree_cm_commissioner/item_availability_sync.rb +10 -0
  189. data/app/serializers/spree/v2/storefront/line_item_serializer_decorator.rb +2 -0
  190. data/app/serializers/spree/v2/storefront/option_type_serializer_decorator.rb +1 -1
  191. data/app/serializers/spree/v2/storefront/prepaid_balance_serializer.rb +15 -0
  192. data/app/serializers/spree/v2/storefront/prepaid_config_serializer.rb +11 -0
  193. data/app/serializers/spree/v2/storefront/prepaid_transaction_serializer.rb +17 -0
  194. data/app/serializers/spree/v2/storefront/product_option_type_serializer.rb +15 -0
  195. data/app/serializers/spree/v2/storefront/product_serializer_decorator.rb +15 -0
  196. data/app/serializers/spree/v2/storefront/user_serializer_decorator.rb +6 -0
  197. data/app/serializers/spree/v2/tenant/line_item_serializer.rb +1 -1
  198. data/app/serializers/spree/v2/tenant/option_type_serializer.rb +1 -1
  199. data/app/serializers/spree/v2/tenant/prepaid_balance_serializer.rb +15 -0
  200. data/app/serializers/spree/v2/tenant/prepaid_config_serializer.rb +16 -0
  201. data/app/serializers/spree/v2/tenant/prepaid_transaction_serializer.rb +16 -0
  202. data/app/serializers/spree_cm_commissioner/v2/operator/dynamic_field_option_serializer.rb +9 -0
  203. data/app/serializers/spree_cm_commissioner/v2/operator/dynamic_field_serializer.rb +11 -0
  204. data/app/serializers/spree_cm_commissioner/v2/operator/guest_dynamic_field_serializer.rb +14 -0
  205. data/app/serializers/spree_cm_commissioner/v2/operator/guest_serializer.rb +1 -0
  206. data/app/serializers/spree_cm_commissioner/v2/storefront/accommodation_variant_serializer.rb +44 -0
  207. data/app/serializers/spree_cm_commissioner/v2/storefront/cart_serializer_decorator.rb +2 -0
  208. data/app/serializers/spree_cm_commissioner/v2/storefront/inventory_item_serializer.rb +4 -4
  209. data/app/serializers/spree_cm_commissioner/v2/storefront/item_availability_sync_serializer.rb +10 -0
  210. data/app/serializers/spree_cm_commissioner/v2/storefront/rate_plan_serializer.rb +16 -0
  211. data/app/serializers/spree_cm_commissioner/v2/storefront/reschedule_request_serializer.rb +49 -0
  212. data/app/serializers/spree_cm_commissioner/v2/storefront/reschedule_snapshot_serializer.rb +24 -0
  213. data/app/services/spree_cm_commissioner/audit_logger.rb +59 -0
  214. data/app/services/spree_cm_commissioner/checkout/add_store_credit_payments.rb +96 -0
  215. data/app/services/spree_cm_commissioner/currency_converter/create.rb +27 -0
  216. data/app/services/spree_cm_commissioner/currency_rates/manage.rb +99 -0
  217. data/app/services/spree_cm_commissioner/customer_notifications/clone.rb +39 -0
  218. data/app/services/spree_cm_commissioner/google_maps_geocoder.rb +41 -0
  219. data/app/services/spree_cm_commissioner/imports/orders/create.rb +24 -11
  220. data/app/services/spree_cm_commissioner/integrations/base/sync_manager.rb +1 -2
  221. data/app/services/spree_cm_commissioner/integrations/base/sync_result.rb +11 -0
  222. data/app/services/spree_cm_commissioner/integrations/book_me_bus_v1/external_client/client.rb +230 -0
  223. data/app/services/spree_cm_commissioner/integrations/book_me_bus_v1/external_client/connection.rb +147 -0
  224. data/app/services/spree_cm_commissioner/integrations/book_me_bus_v1/inventory/external_inventory_item_status.rb +52 -0
  225. data/app/services/spree_cm_commissioner/integrations/book_me_bus_v1/inventory/external_inventory_items.rb +91 -0
  226. data/app/services/spree_cm_commissioner/integrations/book_me_bus_v1/inventory/unstock_inventory.rb +188 -0
  227. data/app/services/spree_cm_commissioner/integrations/book_me_bus_v1/polling/sync_inventory.rb +79 -0
  228. data/app/services/spree_cm_commissioner/integrations/book_me_bus_v1/polling/sync_item_availability.rb +354 -0
  229. data/app/services/spree_cm_commissioner/integrations/book_me_bus_v1/polling/sync_locations.rb +103 -0
  230. data/app/services/spree_cm_commissioner/integrations/book_me_bus_v1/polling/sync_nationality_pricing.rb +93 -0
  231. data/app/services/spree_cm_commissioner/integrations/book_me_bus_v1/polling/sync_product.rb +70 -0
  232. data/app/services/spree_cm_commissioner/integrations/book_me_bus_v1/polling/sync_route_stops.rb +121 -0
  233. data/app/services/spree_cm_commissioner/integrations/book_me_bus_v1/polling/sync_routes.rb +128 -0
  234. data/app/services/spree_cm_commissioner/integrations/book_me_bus_v1/polling/sync_seat_layout.rb +412 -0
  235. data/app/services/spree_cm_commissioner/integrations/book_me_bus_v1/polling/sync_service_calendars.rb +59 -0
  236. data/app/services/spree_cm_commissioner/integrations/book_me_bus_v1/polling/sync_trip_stop.rb +224 -0
  237. data/app/services/spree_cm_commissioner/integrations/book_me_bus_v1/polling/sync_trips.rb +769 -0
  238. data/app/services/spree_cm_commissioner/integrations/book_me_bus_v1/polling/sync_variant.rb +69 -0
  239. data/app/services/spree_cm_commissioner/integrations/book_me_bus_v1/polling/sync_vehicle_types.rb +92 -0
  240. data/app/services/spree_cm_commissioner/integrations/book_me_bus_v1/polling/time_parser.rb +19 -0
  241. data/app/services/spree_cm_commissioner/integrations/book_me_bus_v1/resources/base.rb +40 -0
  242. data/app/services/spree_cm_commissioner/integrations/book_me_bus_v1/resources/inventory_item_status.rb +148 -0
  243. data/app/services/spree_cm_commissioner/integrations/book_me_bus_v1/resources/location.rb +55 -0
  244. data/app/services/spree_cm_commissioner/integrations/book_me_bus_v1/resources/reservation_cart.rb +85 -0
  245. data/app/services/spree_cm_commissioner/integrations/book_me_bus_v1/resources/seat.rb +128 -0
  246. data/app/services/spree_cm_commissioner/integrations/book_me_bus_v1/resources/seat_layout.rb +386 -0
  247. data/app/services/spree_cm_commissioner/integrations/book_me_bus_v1/resources/stop.rb +84 -0
  248. data/app/services/spree_cm_commissioner/integrations/book_me_bus_v1/resources/trip.rb +237 -0
  249. data/app/services/spree_cm_commissioner/integrations/book_me_bus_v1/resources/wallet_payment.rb +48 -0
  250. data/app/services/spree_cm_commissioner/integrations/book_me_bus_v1/sync_manager.rb +22 -0
  251. data/app/services/spree_cm_commissioner/integrations/book_me_bus_v1/sync_strategies/full_sync_strategy.rb +116 -0
  252. data/app/services/spree_cm_commissioner/integrations/book_me_bus_v1/sync_strategies/incremental_sync_strategy.rb +63 -0
  253. data/app/services/spree_cm_commissioner/integrations/larryta/external_client/client.rb +314 -0
  254. data/app/services/spree_cm_commissioner/integrations/larryta/external_client/connection.rb +74 -0
  255. data/app/services/spree_cm_commissioner/integrations/larryta/inventory/external_inventory_item_status.rb +53 -0
  256. data/app/services/spree_cm_commissioner/integrations/larryta/inventory/external_inventory_items.rb +106 -0
  257. data/app/services/spree_cm_commissioner/integrations/larryta/inventory/hold_seats.rb +143 -0
  258. data/app/services/spree_cm_commissioner/integrations/larryta/inventory/release_seats.rb +90 -0
  259. data/app/services/spree_cm_commissioner/integrations/larryta/inventory/unstock_inventory.rb +143 -0
  260. data/app/services/spree_cm_commissioner/integrations/larryta/polling/sync_item_availability.rb +325 -0
  261. data/app/services/spree_cm_commissioner/integrations/larryta/polling/sync_product.rb +72 -0
  262. data/app/services/spree_cm_commissioner/integrations/larryta/polling/sync_routes.rb +142 -0
  263. data/app/services/spree_cm_commissioner/integrations/larryta/polling/sync_seat_layout.rb +255 -0
  264. data/app/services/spree_cm_commissioner/integrations/larryta/polling/sync_stock_item.rb +55 -0
  265. data/app/services/spree_cm_commissioner/integrations/larryta/polling/sync_trip_stop.rb +171 -0
  266. data/app/services/spree_cm_commissioner/integrations/larryta/polling/sync_trips.rb +397 -0
  267. data/app/services/spree_cm_commissioner/integrations/larryta/polling/sync_variant.rb +63 -0
  268. data/app/services/spree_cm_commissioner/integrations/larryta/polling/sync_vehicle_type.rb +79 -0
  269. data/app/services/spree_cm_commissioner/integrations/larryta/resources/base.rb +39 -0
  270. data/app/services/spree_cm_commissioner/integrations/larryta/resources/booking.rb +113 -0
  271. data/app/services/spree_cm_commissioner/integrations/larryta/resources/booking_session.rb +43 -0
  272. data/app/services/spree_cm_commissioner/integrations/larryta/resources/booking_transaction.rb +236 -0
  273. data/app/services/spree_cm_commissioner/integrations/larryta/resources/direction.rb +71 -0
  274. data/app/services/spree_cm_commissioner/integrations/larryta/resources/passenger.rb +48 -0
  275. data/app/services/spree_cm_commissioner/integrations/larryta/resources/schedule.rb +150 -0
  276. data/app/services/spree_cm_commissioner/integrations/larryta/resources/seat.rb +31 -0
  277. data/app/services/spree_cm_commissioner/integrations/larryta/resources/seat_availability.rb +49 -0
  278. data/app/services/spree_cm_commissioner/integrations/larryta/resources/seat_layout.rb +211 -0
  279. data/app/services/spree_cm_commissioner/integrations/larryta/sync_manager.rb +17 -0
  280. data/app/services/spree_cm_commissioner/integrations/larryta/sync_strategies/full_sync_strategy.rb +79 -0
  281. data/app/services/spree_cm_commissioner/integrations/larryta/sync_strategies/incremental_sync_strategy.rb +58 -0
  282. data/app/services/spree_cm_commissioner/integrations/stadium_x_v1/external_client/client.rb +14 -15
  283. data/app/services/spree_cm_commissioner/integrations/stadium_x_v1/polling/sync_matches.rb +7 -4
  284. data/app/services/spree_cm_commissioner/integrations/stadium_x_v1/polling/sync_zones.rb +6 -3
  285. data/app/services/spree_cm_commissioner/integrations/stadium_x_v1/sync_manager.rb +0 -3
  286. data/app/services/spree_cm_commissioner/integrations/stadium_x_v1/sync_strategies/full_sync_strategy.rb +1 -3
  287. data/app/services/spree_cm_commissioner/integrations/stadium_x_v1/sync_strategies/incremental_sync_strategy.rb +1 -3
  288. data/app/services/spree_cm_commissioner/integrations/stadium_x_v1/sync_strategies/webhook_sync_strategy.rb +1 -2
  289. data/app/services/spree_cm_commissioner/integrations/switch_cm/inventory/accept_push.rb +111 -0
  290. data/app/services/spree_cm_commissioner/integrations/switch_cm/inventory/accommodation_lock.rb +43 -0
  291. data/app/services/spree_cm_commissioner/integrations/switch_cm/inventory/apply_updates.rb +133 -0
  292. data/app/services/spree_cm_commissioner/integrations/switch_cm/inventory/availability_snapshot.rb +127 -0
  293. data/app/services/spree_cm_commissioner/integrations/switch_cm/inventory/inventory_update.rb +35 -0
  294. data/app/services/spree_cm_commissioner/integrations/switch_cm/inventory/pushed_nights.rb +82 -0
  295. data/app/services/spree_cm_commissioner/integrations/switch_cm/inventory/unseen_room_nights.rb +49 -0
  296. data/app/services/spree_cm_commissioner/integrations/switch_cm/inventory/validate_updates.rb +153 -0
  297. data/app/services/spree_cm_commissioner/integrations/switch_cm/reservations/feed.rb +209 -0
  298. data/app/services/spree_cm_commissioner/integrations/switch_cm/reservations/mark_seen.rb +62 -0
  299. data/app/services/spree_cm_commissioner/integrations/switch_cm/reservations/reservation.rb +171 -0
  300. data/app/services/spree_cm_commissioner/integrations/switch_cm/room_catalog.rb +88 -0
  301. data/app/services/spree_cm_commissioner/integrations/switch_cm/sync_manager.rb +16 -0
  302. data/app/services/spree_cm_commissioner/integrations/switch_cm/sync_strategies/full_sync_strategy.rb +39 -0
  303. data/app/services/spree_cm_commissioner/integrations/switch_cm/sync_strategies/incremental_sync_strategy.rb +51 -0
  304. data/app/services/spree_cm_commissioner/integrations/switch_cm/xml/availability_parser.rb +103 -0
  305. data/app/services/spree_cm_commissioner/integrations/switch_cm/xml/push_response.rb +45 -0
  306. data/app/services/spree_cm_commissioner/integrations/switch_cm/xml/reservation_serializer.rb +83 -0
  307. data/app/services/spree_cm_commissioner/integrations/switch_cm/xml/response_builder.rb +19 -0
  308. data/app/services/spree_cm_commissioner/integrations/vireak_buntham/external_client/client.rb +170 -0
  309. data/app/services/spree_cm_commissioner/integrations/vireak_buntham/external_client/connection.rb +59 -0
  310. data/app/services/spree_cm_commissioner/integrations/vireak_buntham/inventory/external_inventory_item_status.rb +48 -0
  311. data/app/services/spree_cm_commissioner/integrations/vireak_buntham/inventory/external_inventory_items.rb +92 -0
  312. data/app/services/spree_cm_commissioner/integrations/vireak_buntham/inventory/unstock_inventory.rb +230 -0
  313. data/app/services/spree_cm_commissioner/integrations/vireak_buntham/polling/sync_item_availability.rb +270 -0
  314. data/app/services/spree_cm_commissioner/integrations/vireak_buntham/polling/sync_locations.rb +96 -0
  315. data/app/services/spree_cm_commissioner/integrations/vireak_buntham/polling/sync_nationality_pricing.rb +76 -0
  316. data/app/services/spree_cm_commissioner/integrations/vireak_buntham/polling/sync_product.rb +69 -0
  317. data/app/services/spree_cm_commissioner/integrations/vireak_buntham/polling/sync_routes.rb +110 -0
  318. data/app/services/spree_cm_commissioner/integrations/vireak_buntham/polling/sync_seat_layout.rb +207 -0
  319. data/app/services/spree_cm_commissioner/integrations/vireak_buntham/polling/sync_stock_item.rb +43 -0
  320. data/app/services/spree_cm_commissioner/integrations/vireak_buntham/polling/sync_trip_stop.rb +192 -0
  321. data/app/services/spree_cm_commissioner/integrations/vireak_buntham/polling/sync_trips.rb +377 -0
  322. data/app/services/spree_cm_commissioner/integrations/vireak_buntham/polling/sync_variant.rb +56 -0
  323. data/app/services/spree_cm_commissioner/integrations/vireak_buntham/polling/sync_vehicle_type.rb +51 -0
  324. data/app/services/spree_cm_commissioner/integrations/vireak_buntham/resources/base.rb +32 -0
  325. data/app/services/spree_cm_commissioner/integrations/vireak_buntham/resources/booking.rb +48 -0
  326. data/app/services/spree_cm_commissioner/integrations/vireak_buntham/resources/booking_transaction.rb +46 -0
  327. data/app/services/spree_cm_commissioner/integrations/vireak_buntham/resources/cell.rb +53 -0
  328. data/app/services/spree_cm_commissioner/integrations/vireak_buntham/resources/confirm_booking_request.rb +43 -0
  329. data/app/services/spree_cm_commissioner/integrations/vireak_buntham/resources/location.rb +37 -0
  330. data/app/services/spree_cm_commissioner/integrations/vireak_buntham/resources/schedule.rb +138 -0
  331. data/app/services/spree_cm_commissioner/integrations/vireak_buntham/resources/seat.rb +48 -0
  332. data/app/services/spree_cm_commissioner/integrations/vireak_buntham/resources/seat_layout.rb +79 -0
  333. data/app/services/spree_cm_commissioner/integrations/vireak_buntham/sync_manager.rb +17 -0
  334. data/app/services/spree_cm_commissioner/integrations/vireak_buntham/sync_strategies/full_sync_strategy.rb +87 -0
  335. data/app/services/spree_cm_commissioner/integrations/vireak_buntham/sync_strategies/incremental_sync_strategy.rb +53 -0
  336. data/app/services/spree_cm_commissioner/intercity_taxi_order/update.rb +1 -3
  337. data/app/services/spree_cm_commissioner/inventory_holds/release.rb +13 -0
  338. data/app/services/spree_cm_commissioner/inventory_items/bulk_adjust_quantities_by_variant.rb +4 -0
  339. data/app/services/spree_cm_commissioner/inventory_items/generate_permanent_item.rb +48 -0
  340. data/app/services/spree_cm_commissioner/inventory_items/reset.rb +4 -0
  341. data/app/services/spree_cm_commissioner/order_holds/hold.rb +17 -0
  342. data/app/services/spree_cm_commissioner/order_holds/release.rb +31 -0
  343. data/app/services/spree_cm_commissioner/place_resolver.rb +95 -0
  344. data/app/services/spree_cm_commissioner/prepaid/admin_credit.rb +44 -0
  345. data/app/services/spree_cm_commissioner/prepaid/check_top_up_limits.rb +47 -0
  346. data/app/services/spree_cm_commissioner/prepaid/freeze.rb +58 -0
  347. data/app/services/spree_cm_commissioner/prepaid/mutation.rb +78 -0
  348. data/app/services/spree_cm_commissioner/prepaid/reverse_order_top_up.rb +23 -0
  349. data/app/services/spree_cm_commissioner/prepaid/reverse_top_up.rb +53 -0
  350. data/app/services/spree_cm_commissioner/prepaid/top_up.rb +68 -0
  351. data/app/services/spree_cm_commissioner/prepaid/unfreeze.rb +47 -0
  352. data/app/services/spree_cm_commissioner/prepaid_metrics.rb +37 -0
  353. data/app/services/spree_cm_commissioner/prepaid_reconciliation_service.rb +71 -0
  354. data/app/services/spree_cm_commissioner/rate_plans/save.rb +65 -0
  355. data/app/services/spree_cm_commissioner/rates_availabilities/bulk_update.rb +225 -0
  356. data/app/services/spree_cm_commissioner/reschedule_policies/assign.rb +52 -0
  357. data/app/services/spree_cm_commissioner/reschedule_policies/create.rb +71 -0
  358. data/app/services/spree_cm_commissioner/reschedule_policies/exclude.rb +40 -0
  359. data/app/services/spree_cm_commissioner/reschedule_policies/resolve.rb +109 -0
  360. data/app/services/spree_cm_commissioner/reschedule_policies/unassign.rb +23 -0
  361. data/app/services/spree_cm_commissioner/reschedule_policies/update.rb +73 -0
  362. data/app/services/spree_cm_commissioner/reschedule_requests/authorize_initiator.rb +79 -0
  363. data/app/services/spree_cm_commissioner/reschedule_requests/build_request.rb +317 -0
  364. data/app/services/spree_cm_commissioner/reschedule_requests/calculate_fee.rb +71 -0
  365. data/app/services/spree_cm_commissioner/reschedule_requests/cancel.rb +55 -0
  366. data/app/services/spree_cm_commissioner/reschedule_requests/check_eligibility.rb +243 -0
  367. data/app/services/spree_cm_commissioner/reschedule_requests/collect_payment.rb +204 -0
  368. data/app/services/spree_cm_commissioner/reschedule_requests/execute.rb +252 -0
  369. data/app/services/spree_cm_commissioner/reschedule_requests/expire_overdue_requests.rb +28 -0
  370. data/app/services/spree_cm_commissioner/reschedule_requests/expire_request.rb +24 -0
  371. data/app/services/spree_cm_commissioner/reschedule_requests/guests/update_seats.rb +210 -0
  372. data/app/services/spree_cm_commissioner/reschedule_requests/initiate.rb +255 -0
  373. data/app/services/spree_cm_commissioner/reschedule_requests/issue_compensation.rb +67 -0
  374. data/app/services/spree_cm_commissioner/reschedule_requests/issue_coupon.rb +90 -0
  375. data/app/services/spree_cm_commissioner/reschedule_requests/issue_store_credit.rb +16 -0
  376. data/app/services/spree_cm_commissioner/reschedule_requests/notifications/send_approval_reminder.rb +43 -0
  377. data/app/services/spree_cm_commissioner/reschedule_requests/notifications/send_payment_reminder.rb +46 -0
  378. data/app/services/spree_cm_commissioner/reschedule_requests/notifications/send_status_update.rb +42 -0
  379. data/app/services/spree_cm_commissioner/reschedule_requests/seats/hold.rb +137 -0
  380. data/app/services/spree_cm_commissioner/reschedule_requests/split_line_item.rb +91 -0
  381. data/app/services/spree_cm_commissioner/reschedule_requests/telegram/notify_operations.rb +37 -0
  382. data/app/services/spree_cm_commissioner/reschedule_requests/telegram/notify_vendor.rb +51 -0
  383. data/app/services/spree_cm_commissioner/reschedule_requests/vendors/approve.rb +61 -0
  384. data/app/services/spree_cm_commissioner/reschedule_requests/vendors/reject.rb +58 -0
  385. data/app/services/spree_cm_commissioner/room_types/create.rb +87 -0
  386. data/app/services/spree_cm_commissioner/room_types/update_properties.rb +32 -0
  387. data/app/services/spree_cm_commissioner/room_types.rb +40 -0
  388. data/app/services/spree_cm_commissioner/route_prices/update_price_range.rb +3 -1
  389. data/app/services/spree_cm_commissioner/topup_draft_orders/create.rb +234 -0
  390. data/app/services/spree_cm_commissioner/trips/search.rb +14 -7
  391. data/app/services/spree_cm_commissioner/trips/service_calendars/create_or_update.rb +25 -14
  392. data/app/services/spree_cm_commissioner/trips/update_single_leg.rb +48 -7
  393. data/app/views/spree/admin/classifications/edit.html.erb +17 -2
  394. data/app/views/spree/admin/classifications/new.html.erb +18 -2
  395. data/app/views/spree/admin/customer_notifications/index.html.erb +45 -3
  396. data/app/views/spree/admin/guests/_check_in_status.html.erb +28 -22
  397. data/app/views/spree/admin/guests/edit.html.erb +29 -26
  398. data/app/views/spree/admin/integrations/_book_me_bus_v1_fields.html.erb +47 -0
  399. data/app/views/spree/admin/integrations/_form.html.erb +16 -0
  400. data/app/views/spree/admin/integrations/_larryta_fields.html.erb +58 -0
  401. data/app/views/spree/admin/integrations/_switch_cm_fields.html.erb +53 -0
  402. data/app/views/spree/admin/integrations/_vireak_buntham_fields.html.erb +75 -0
  403. data/app/views/spree/admin/integrations/index.html.erb +1 -1
  404. data/app/views/spree/admin/inventory_items/_details.html.erb +56 -0
  405. data/app/views/spree/admin/prepaid_transactions/index.html.erb +102 -0
  406. data/app/views/spree/admin/prepaid_transactions/show.html.erb +146 -0
  407. data/app/views/spree/admin/private_sales/_form.html.erb +24 -1
  408. data/app/views/spree/admin/private_sales/_stock.html.erb +6 -1
  409. data/app/views/spree/admin/product_commissions/index.html.erb +7 -2
  410. data/app/views/spree/admin/product_option_types/index.html.erb +79 -0
  411. data/app/views/spree/admin/rate_plans/_form.html.erb +134 -0
  412. data/app/views/spree/admin/rate_plans/_order_form.html.erb +37 -0
  413. data/app/views/spree/admin/rates_availabilities/_block.html.erb +34 -0
  414. data/app/views/spree/admin/rates_availabilities/_bookings.html.erb +52 -0
  415. data/app/views/spree/admin/rates_availabilities/_filters.html.erb +48 -0
  416. data/app/views/spree/admin/rates_availabilities/_legend.html.erb +27 -0
  417. data/app/views/spree/admin/rates_availabilities/_list_pages.html.erb +14 -0
  418. data/app/views/spree/admin/rates_availabilities/_modal_header.html.erb +11 -0
  419. data/app/views/spree/admin/rates_availabilities/_names.html.erb +62 -0
  420. data/app/views/spree/admin/rates_availabilities/_script.html.erb +676 -0
  421. data/app/views/spree/admin/rates_availabilities/_styles.html.erb +159 -0
  422. data/app/views/spree/admin/rates_availabilities/index.html.erb +112 -0
  423. data/app/views/spree/admin/reschedule_policies/_form.html.erb +65 -0
  424. data/app/views/spree/admin/reschedule_policies/edit.html.erb +11 -0
  425. data/app/views/spree/admin/reschedule_policies/index.html.erb +84 -0
  426. data/app/views/spree/admin/reschedule_policies/new.html.erb +11 -0
  427. data/app/views/spree/admin/reschedule_requests/_cancel_modal.html.erb +34 -0
  428. data/app/views/spree/admin/reschedule_requests/_search_form.html.erb +57 -0
  429. data/app/views/spree/admin/reschedule_requests/_tabs.html.erb +14 -0
  430. data/app/views/spree/admin/reschedule_requests/index.html.erb +64 -0
  431. data/app/views/spree/admin/reschedule_requests/show.html.erb +212 -0
  432. data/app/views/spree/admin/reschedule_requests/state_changes.html.erb +42 -0
  433. data/app/views/spree/admin/shared/_products.html.erb +3 -0
  434. data/app/views/spree/admin/shared/_reschedule_policies_tabs.html.erb +22 -0
  435. data/app/views/spree/admin/shared/_ticket_transfer_details.html.erb +3 -0
  436. data/app/views/spree/admin/shared/sub_menu/_homepage.html.erb +0 -1
  437. data/app/views/spree/admin/shared/sub_menu/_reschedule.html.erb +4 -0
  438. data/app/views/spree/admin/stock_managements/_header.html.erb +0 -14
  439. data/app/views/spree/admin/stock_managements/_inventory_item_stats.html.erb +28 -0
  440. data/app/views/spree/admin/stock_managements/index.html.erb +1 -56
  441. data/app/views/spree/admin/store_credit_admin/_tabs.html.erb +31 -0
  442. data/app/views/spree/admin/store_credit_admin/_tenant_selector.html.erb +17 -0
  443. data/app/views/spree/admin/store_credit_admin/show.html.erb +200 -0
  444. data/app/views/spree/admin/store_credits/index.html.erb +224 -0
  445. data/app/views/spree/admin/tenants/_form.html.erb +33 -0
  446. data/app/views/spree/admin/ticket_transfer_rules/_form.html.erb +28 -5
  447. data/config/initializers/spree_dependencies.rb +3 -0
  448. data/config/initializers/spree_permitted_attributes.rb +2 -0
  449. data/config/locales/en.yml +592 -3
  450. data/config/locales/km.yml +511 -4
  451. data/config/routes.rb +98 -18
  452. data/db/migrate/20260327143200_create_cm_audit_events.rb +21 -0
  453. data/db/migrate/20260410045815_create_cm_currency_rates.rb +23 -0
  454. data/db/migrate/20260411102139_add_metadata_to_cm_inventory_items.rb +6 -0
  455. data/db/migrate/20260506120000_create_cm_reschedule_policies.rb +38 -0
  456. data/db/migrate/20260506120001_create_cm_reschedule_requests.rb +67 -0
  457. data/db/migrate/20260506120002_add_reschedule_count_to_cm_guests.rb +5 -0
  458. data/db/migrate/20260521140000_create_cm_reschedule_snapshots.rb +38 -0
  459. data/db/migrate/20260522160000_create_cm_reschedule_policy_assignments.rb +17 -0
  460. data/db/migrate/20260722000001_create_cm_prepaid_transactions.rb +31 -0
  461. data/db/migrate/20260722000002_add_constraints_to_store_credits.rb +46 -0
  462. data/db/migrate/20260722000003_add_expires_at_to_store_credits.rb +6 -0
  463. data/db/migrate/20260722000004_add_issuer_tenant_id_to_spree_store_credits.rb +7 -0
  464. data/db/migrate/20260722000005_add_immutability_trigger_to_cm_prepaid_transactions.rb +36 -0
  465. data/db/migrate/20260918045558_add_prepaid_currency_columns_to_cm_tenants.rb +6 -0
  466. data/db/migrate/20260923090000_add_public_private_metadata_to_cm_prepaid_transactions.rb +36 -0
  467. data/db/migrate/20260928000000_add_oauth_application_id_to_cm_integrations.rb +5 -0
  468. data/db/migrate/20260929000000_create_cm_rate_plans.rb +22 -0
  469. data/db/migrate/20260929000001_add_rate_plan_to_spree_prices.rb +24 -0
  470. data/db/migrate/20260929000002_add_rate_plan_to_spree_line_items.rb +7 -0
  471. data/db/migrate/20260929000003_create_cm_option_value_rate_plans.rb +16 -0
  472. data/db/migrate/20261006073317_add_offline_to_spree_products.rb +6 -0
  473. data/db/migrate/20261009000000_create_cm_rate_restrictions.rb +29 -0
  474. data/db/migrate/20261013000001_add_hidden_and_presentation_to_spree_product_option_types.rb +6 -0
  475. data/db/migrate/20261013000002_move_hidden_from_spree_option_types_to_product_option_types.rb +36 -0
  476. data/db/migrate/20261016000001_add_index_to_spree_line_items_number.rb +8 -0
  477. data/lib/spree_cm_commissioner/booking_dates.rb +43 -0
  478. data/lib/spree_cm_commissioner/calendar_event.rb +0 -25
  479. data/lib/spree_cm_commissioner/rate.rb +74 -0
  480. data/lib/spree_cm_commissioner/rate_closings.rb +48 -0
  481. data/lib/spree_cm_commissioner/rates_availability_grid.rb +113 -0
  482. data/lib/spree_cm_commissioner/reschedule_policy_rules.rb +75 -0
  483. data/lib/spree_cm_commissioner/serializable_transaction.rb +38 -0
  484. data/lib/spree_cm_commissioner/stay_price.rb +30 -0
  485. data/lib/spree_cm_commissioner/stay_prices.rb +58 -0
  486. data/lib/spree_cm_commissioner/store_credit_currency_summary.rb +11 -0
  487. data/lib/spree_cm_commissioner/store_credit_summary.rb +11 -0
  488. data/lib/spree_cm_commissioner/test_helper/factories/integration_factory.rb +17 -0
  489. data/lib/spree_cm_commissioner/test_helper/factories/option_type_factory.rb +6 -0
  490. data/lib/spree_cm_commissioner/test_helper/factories/option_value_rate_plan_factory.rb +6 -0
  491. data/lib/spree_cm_commissioner/test_helper/factories/prepaid_transaction_factory.rb +12 -0
  492. data/lib/spree_cm_commissioner/test_helper/factories/rate_plan_factory.rb +14 -0
  493. data/lib/spree_cm_commissioner/test_helper/factories/reschedule_policy_assignment_factory.rb +7 -0
  494. data/lib/spree_cm_commissioner/test_helper/factories/reschedule_policy_factory.rb +59 -0
  495. data/lib/spree_cm_commissioner/test_helper/factories/reschedule_request_factory.rb +51 -0
  496. data/lib/spree_cm_commissioner/test_helper/factories/reschedule_snapshot_factory.rb +27 -0
  497. data/lib/spree_cm_commissioner/test_helper/factories/variant_factory.rb +9 -0
  498. data/lib/spree_cm_commissioner/version.rb +1 -1
  499. data/lib/spree_cm_commissioner.rb +10 -0
  500. data/lib/tasks/prepaid_reconciliation_cron_scheduler.rake +7 -0
  501. data/lib/tasks/prepaid_setup_types.rake +7 -0
  502. metadata +361 -32
  503. data/app/assets/javascripts/spree_cm_commissioner/calendar.js +0 -59
  504. data/app/controllers/spree/admin/calendars/base_controller.rb +0 -27
  505. data/app/controllers/spree/admin/calendars/orders_controller.rb +0 -27
  506. data/app/controllers/spree/admin/homepage_feed_controller.rb +0 -36
  507. data/app/controllers/spree/admin/metafields_controller.rb +0 -69
  508. data/app/models/concerns/spree_cm_commissioner/metafield.rb +0 -15
  509. data/app/models/spree_cm_commissioner/homepage_feed.rb +0 -4
  510. data/app/overrides/spree/admin/shared/_main_menu/sidebar_calendars.html.erb.deface +0 -5
  511. data/app/overrides/spree/admin/shared/_product_tabs/metafields.html.erb.deface +0 -15
  512. data/app/serializers/spree_cm_commissioner/v2/storefront/taxon_include_product_serializer.rb +0 -13
  513. data/app/serializers/spree_cm_commissioner/v2/storefront/vendor_include_product_serializer.rb +0 -14
  514. data/app/services/spree_cm_commissioner/feed/taxon_include_product.rb +0 -20
  515. data/app/services/spree_cm_commissioner/feed/taxon_product.rb +0 -54
  516. data/app/services/spree_cm_commissioner/feed/vendor_include_product.rb +0 -20
  517. data/app/services/spree_cm_commissioner/feed/vendor_product.rb +0 -52
  518. data/app/services/spree_cm_commissioner/homepage_data_loader.rb +0 -112
  519. data/app/services/spree_cm_commissioner/metafields/product_metadata_service.rb +0 -32
  520. data/app/views/shared/_calendar.html.erb +0 -27
  521. data/app/views/shared/calendar/_order_popover.html.erb +0 -18
  522. data/app/views/shared/calendar/_orders_sidebar.html.erb +0 -41
  523. data/app/views/spree/admin/calendars/orders/index.html.erb +0 -22
  524. data/app/views/spree/admin/homepage_feed/_form.html.erb +0 -24
  525. data/app/views/spree/admin/homepage_feed/edit.html.erb +0 -17
  526. data/app/views/spree/admin/inventory_items/prices.html.erb +0 -43
  527. data/app/views/spree/admin/inventory_items/stocks.html.erb +0 -34
  528. data/app/views/spree/admin/metafields/_private_metafields_form.html.erb +0 -71
  529. data/app/views/spree/admin/metafields/_public_metafields_form.html.erb +0 -64
  530. data/app/views/spree/admin/metafields/edit.html.erb +0 -27
  531. data/app/views/spree/admin/shared/sub_menu/_calendar.html.erb +0 -3
  532. data/app/views/spree/admin/stock_managements/calendar.html.erb +0 -310
@@ -0,0 +1,142 @@
1
+ module Spree
2
+ module Admin
3
+ # Stock, prices and sale status of a product's variants and their rate plans, per date.
4
+ # StockManagementsController keeps per-variant setup: stock locations, track inventory, purchases, resets.
5
+ class RatesAvailabilitiesController < Spree::Admin::BaseController
6
+ include RatesAvailabilityCells
7
+
8
+ MIN_DAYS_AHEAD = 14
9
+ MAX_BLOCK_DAYS = 62
10
+ VARIANTS_PER_PAGE = 25
11
+ BOOKINGS_PER_PAGE = 25
12
+
13
+ before_action :load_product
14
+ before_action :load_grid, only: %i[index dates]
15
+
16
+ helper SpreeCmCommissioner::Admin::RatesAvailabilitiesHelper
17
+ helper_method :filter_params
18
+
19
+ # GET /products/:product_id/rates_availabilities?from=&currency=&option_value_ids[]=&archived=&page=
20
+ def index
21
+ @option_types = Spree::Variants::OptionTypesFinder.new(variant_ids: @product.variants.ids).execute.select(&:variant?)
22
+ @currencies = supported_currencies_for_all_stores.map(&:iso_code)
23
+ # A channel manager sets these variants' stock, prices and closings, so BulkUpdate skips them; the page says why.
24
+ @managing_integration = @product.vendor&.integration if @grid.variants.any?(&:inventory_external_wins?)
25
+ end
26
+
27
+ # GET /products/:product_id/rates_availabilities/dates?from=&to=&<index filters>
28
+ #
29
+ # The page adds this block next to the dates it already shows, so a range across months doesn't need a reload.
30
+ # It asks for whole months, so a month never spans two blocks.
31
+ def dates
32
+ render partial: 'block', locals: { grid: @grid }
33
+ end
34
+
35
+ # PATCH /products/:product_id/rates_availabilities/bulk_update, JSON: { bulk_action:, cells: [...], values: {...} }
36
+ #
37
+ # Answers with the counts, and the page then reloads its loaded dates.
38
+ def bulk_update
39
+ result = SpreeCmCommissioner::RatesAvailabilities::BulkUpdate.call(
40
+ product: @product,
41
+ store: current_store,
42
+ action: params[:bulk_action].to_s,
43
+ cells: selected_cells,
44
+ values: params.fetch(:values, {}).permit(:amount, :compare_at_amount, :quantity, :currency),
45
+ user: try_spree_current_user
46
+ )
47
+
48
+ if result.success?
49
+ render json: result.value
50
+ else
51
+ render json: { error: result.error.to_s }, status: :unprocessable_entity
52
+ end
53
+ end
54
+
55
+ # POST /products/:product_id/rates_availabilities/bookings, JSON: { cells: [...], filter:, q:, page: }
56
+ #
57
+ # The bookings modal for the selected cells. POST, as cells travel in the body.
58
+ def bookings
59
+ finder = SpreeCmCommissioner::RatesAvailabilities::FindBookings.new(
60
+ product: @product,
61
+ cells: selected_cells,
62
+ filter: params[:filter].to_s,
63
+ query: params[:q]
64
+ )
65
+
66
+ line_items = finder.execute.page(params[:page]).per(BOOKINGS_PER_PAGE)
67
+ render partial: 'bookings', formats: [:html], locals: {
68
+ finder: finder,
69
+ line_items: line_items,
70
+ cells: selected_cells,
71
+ held_order_ids: finder.held_order_ids_among(line_items)
72
+ }
73
+ end
74
+
75
+ private
76
+
77
+ def filter_params
78
+ params.permit(:currency, :archived, option_value_ids: []).to_h
79
+ end
80
+
81
+ def load_product
82
+ @product = Spree::Product.find_by!(slug: params[:product_id])
83
+ end
84
+
85
+ def load_grid
86
+ @include_archived = params[:archived] == '1'
87
+ @variants = variants.page(params[:page]).per(VARIANTS_PER_PAGE)
88
+ @currency = (params[:currency].presence || current_currency).upcase
89
+ @from = parse_date(params[:from]) || Time.zone.today
90
+
91
+ dates = @product.permanent_stock? ? date_window.to_a : []
92
+
93
+ @grid = SpreeCmCommissioner::RatesAvailabilityGrid.new(
94
+ variants: @variants.to_a,
95
+ dates: dates,
96
+ currency: @currency,
97
+ include_archived: @include_archived
98
+ )
99
+ end
100
+
101
+ def variants
102
+ rate_plans = @include_archived ? :rate_plans : :active_rate_plans
103
+ scope = @product.variants.exists? ? @product.variants : Spree::Variant.where(id: @product.master.id)
104
+ scope = scope.where(id: matching_variant_ids) if option_value_ids.present?
105
+ scope.includes(:stock_items, { rate_plans => { option_values: :option_type } }, { vendor: :integration }, option_values: :option_type)
106
+ .order(:position, :id)
107
+ end
108
+
109
+ def option_value_ids
110
+ @option_value_ids ||= Array(params[:option_value_ids]).compact_blank.map(&:to_i)
111
+ end
112
+
113
+ def matching_variant_ids
114
+ SpreeCmCommissioner::Variants::FindByOptionValueIds.new(product: @product, option_value_ids: option_value_ids).execute.ids
115
+ end
116
+
117
+ # The first window is `from`'s month, plus the next one when fewer than MIN_DAYS_AHEAD days are left, so a
118
+ # late `from` still shows weeks ahead. Whole months, so the blocks added next to it are whole months too.
119
+ # Kept to about a month: every day is a column on every row, and a dated product can have many variants.
120
+ # The page scrolls to `from`.
121
+ def date_window
122
+ to = parse_date(params[:to])
123
+ return @from..[to, @from + (MAX_BLOCK_DAYS - 1)].min if to
124
+
125
+ last = @from.end_of_month
126
+ last = @from.next_month.end_of_month if (last - @from).to_i + 1 < MIN_DAYS_AHEAD
127
+ @from.beginning_of_month..last
128
+ end
129
+
130
+ def parse_date(value)
131
+ Date.parse(value.to_s) if value.present?
132
+ rescue Date::Error
133
+ nil
134
+ end
135
+
136
+ # Same permission as the stock calendar this replaces.
137
+ def model_class
138
+ Spree::StockItem
139
+ end
140
+ end
141
+ end
142
+ end
@@ -0,0 +1,148 @@
1
+ module Spree
2
+ module Admin
3
+ # Platform-admin management of reschedule policies, built like the Payment
4
+ # Methods page: a scoped tab bar (Store / Vendors / Tenants) over the shared
5
+ # ReschedulePolicy table.
6
+ #
7
+ # - Store tab (default): platform policies (vendor_id nil). Full CRUD.
8
+ # - Vendors tab: every vendor policy, read-only.
9
+ # - Tenants tab: vendor policies whose vendor belongs to a tenant, read-only.
10
+ #
11
+ # Create/Update delegate to the existing ReschedulePolicies services (with
12
+ # vendor: nil), which own calculator building, typed rules, and the
13
+ # one-default-per-scope rule.
14
+ class ReschedulePoliciesController < BaseController
15
+ # Only these tabs switch the page into read-only vendor/tenant mode; any
16
+ # other value falls back to the store tab, in the controller and the view
17
+ # alike (see `current_tab`).
18
+ TABS = %w[vendors tenants].freeze
19
+
20
+ before_action :load_policy, only: %i[edit update destroy set_default]
21
+
22
+ helper_method :current_tab
23
+
24
+ def index
25
+ authorize! :manage, SpreeCmCommissioner::ReschedulePolicy
26
+
27
+ @search = collection.ransack(params[:q])
28
+ @reschedule_policies = @search.result
29
+ .order(default: :desc, name: :asc)
30
+ .page(params[:page])
31
+ .per(params[:per_page] || 20)
32
+ end
33
+
34
+ def new
35
+ authorize! :manage, SpreeCmCommissioner::ReschedulePolicy
36
+ @reschedule_policy = SpreeCmCommissioner::ReschedulePolicy.new(status: :active)
37
+ # Seed a calculator so the fee fields render; the form/services can swap it.
38
+ @reschedule_policy.calculator_type = 'Spree::Calculator::FlatRate'
39
+ end
40
+
41
+ def create
42
+ authorize! :manage, SpreeCmCommissioner::ReschedulePolicy
43
+
44
+ result = SpreeCmCommissioner::ReschedulePolicies::Create.call(vendor: nil, options: policy_params)
45
+ if result.success?
46
+ flash[:success] = Spree.t(:successfully_created, resource: reschedule_policy_name)
47
+ redirect_to admin_reschedule_policies_path
48
+ else
49
+ @reschedule_policy = result.value || SpreeCmCommissioner::ReschedulePolicy.new
50
+ flash.now[:error] = result.error&.value
51
+ render :new, status: :unprocessable_entity
52
+ end
53
+ end
54
+
55
+ def update
56
+ result = SpreeCmCommissioner::ReschedulePolicies::Update.call(policy: @reschedule_policy, options: policy_params)
57
+ if result.success?
58
+ flash[:success] = Spree.t(:successfully_updated, resource: reschedule_policy_name)
59
+ redirect_to admin_reschedule_policies_path
60
+ else
61
+ flash.now[:error] = result.error&.value
62
+ render :edit, status: :unprocessable_entity
63
+ end
64
+ end
65
+
66
+ def destroy
67
+ if @reschedule_policy.active?
68
+ # The reschedule flow resolves the store policy from the *active*
69
+ # store policies, so deleting this one would reject every reschedule.
70
+ flash[:error] = Spree.t('admin.reschedule_policies.cannot_delete_active')
71
+ elsif @reschedule_policy.destroy
72
+ flash[:success] = Spree.t(:successfully_removed, resource: reschedule_policy_name)
73
+ else
74
+ flash[:error] = @reschedule_policy.errors.full_messages.to_sentence
75
+ end
76
+
77
+ redirect_to admin_reschedule_policies_path
78
+ end
79
+
80
+ # Promote this store policy to the active default; the Update service
81
+ # demotes the current default in the same scope first.
82
+ def set_default
83
+ result = SpreeCmCommissioner::ReschedulePolicies::Update.call(
84
+ policy: @reschedule_policy, options: { default: true, status: :active }
85
+ )
86
+ flash[:success] = Spree.t(:successfully_updated, resource: reschedule_policy_name) if result.success?
87
+ flash[:error] = result.error&.value if result.failure?
88
+ redirect_to admin_reschedule_policies_path
89
+ end
90
+
91
+ private
92
+
93
+ # nil for the store tab (including unknown/blank `tab` values), otherwise
94
+ # the recognised tab name.
95
+ def current_tab
96
+ params[:tab] if TABS.include?(params[:tab])
97
+ end
98
+
99
+ # All statuses are shown (draft/inactive included) so the admin can manage
100
+ # them, so the raw vendor_id/tenant_id filters are used, not the
101
+ # active-only `store_policies` / `vendor_policies` scopes.
102
+ def collection
103
+ base = SpreeCmCommissioner::ReschedulePolicy
104
+ case current_tab
105
+ when 'vendors'
106
+ base.where.not(vendor_id: nil).includes(:vendor)
107
+ when 'tenants'
108
+ base.where.not(vendor_id: nil).where.not(tenant_id: nil).includes(vendor: :tenant)
109
+ else
110
+ base.where(vendor_id: nil)
111
+ end
112
+ end
113
+
114
+ def load_policy
115
+ authorize! :manage, SpreeCmCommissioner::ReschedulePolicy
116
+ # Edits apply to store policies only; vendor/tenant policies are read-only here.
117
+ @reschedule_policy = SpreeCmCommissioner::ReschedulePolicy.where(vendor_id: nil).find(params[:id])
118
+ end
119
+
120
+ # Store policies only carry a fee calculator; the typed reschedule rules
121
+ # (cutoff, max-per-booking, vendor approval, ...) belong to vendor policies
122
+ # and are managed elsewhere, so they are not collected here.
123
+ def policy_params
124
+ params.require(:spree_cm_commissioner_reschedule_policy).permit(
125
+ :name,
126
+ :description,
127
+ :status,
128
+ :default,
129
+ :calculator_type,
130
+ calculator_attributes: calculator_attribute_keys
131
+ )
132
+ end
133
+
134
+ # Only the preferences of the selected (whitelisted) calculator subclass
135
+ # are permitted, so preferences never land on the wrong calculator type.
136
+ def calculator_attribute_keys
137
+ type = params.dig(:spree_cm_commissioner_reschedule_policy, :calculator_type)
138
+ return [] unless %w[Spree::Calculator::FlatRate Spree::Calculator::PercentOnLineItem].include?(type)
139
+
140
+ type.constantize.new.defined_preferences.map { |preference| "preferred_#{preference}" }
141
+ end
142
+
143
+ def reschedule_policy_name
144
+ SpreeCmCommissioner::ReschedulePolicy.model_name.human
145
+ end
146
+ end
147
+ end
148
+ end
@@ -0,0 +1,82 @@
1
+ module Spree
2
+ module Admin
3
+ class RescheduleRequestsController < Spree::Admin::ResourceController
4
+ helper Spree::Admin::RescheduleRequestsHelper
5
+
6
+ def index
7
+ @search = model_class.includes(:order, :user, :vendor, :vendor_policy).ransack(params[:q])
8
+ @reschedule_requests = @search.result
9
+ .order(created_at: :desc)
10
+ .page(params[:page])
11
+ .per(params[:per_page] || Spree::Backend::Config[:admin_orders_per_page])
12
+ end
13
+
14
+ def show
15
+ @reschedule_request = load_request
16
+ end
17
+
18
+ def state_changes
19
+ @reschedule_request = model_class.find(params[:id])
20
+ @state_changes = @reschedule_request.state_changes.includes(:user).order(created_at: :desc)
21
+ end
22
+
23
+ def approve
24
+ run_service(
25
+ SpreeCmCommissioner::RescheduleRequests::Vendors::Approve,
26
+ approved_by: try_spree_current_user,
27
+ success_key: 'approved'
28
+ )
29
+ end
30
+
31
+ def reject
32
+ run_service(
33
+ SpreeCmCommissioner::RescheduleRequests::Vendors::Reject,
34
+ rejected_by: try_spree_current_user,
35
+ success_key: 'rejected'
36
+ )
37
+ end
38
+
39
+ def cancel
40
+ run_service(
41
+ SpreeCmCommissioner::RescheduleRequests::Cancel,
42
+ cancelled_by: try_spree_current_user,
43
+ reason: params[:cancellation_reason].presence,
44
+ success_key: 'cancelled'
45
+ )
46
+ end
47
+
48
+ private
49
+
50
+ # Each service takes the acting admin so audit rows record who acted
51
+ # rather than the request's customer.
52
+ def run_service(service, success_key:, **)
53
+ @reschedule_request = model_class.find(params[:id])
54
+
55
+ result = service.call(reschedule_request: @reschedule_request, **)
56
+ if result.success?
57
+ flash[:success] = t("spree.admin.reschedule_requests.#{success_key}")
58
+ else
59
+ flash[:error] = result.error&.value
60
+ end
61
+
62
+ redirect_to admin_reschedule_request_path(@reschedule_request)
63
+ end
64
+
65
+ def load_request
66
+ model_class.includes(
67
+ :order, :user, :vendor, :tenant, :vendor_policy, :store_policy,
68
+ previous_request_snapshots: %i[variant line_item],
69
+ new_request_snapshots: %i[variant line_item]
70
+ ).find(params[:id])
71
+ end
72
+
73
+ def model_class
74
+ SpreeCmCommissioner::RescheduleRequest
75
+ end
76
+
77
+ def object_name
78
+ 'spree_cm_commissioner_reschedule_request'
79
+ end
80
+ end
81
+ end
82
+ end
@@ -5,7 +5,7 @@ module Spree
5
5
 
6
6
  before_action :load_parent
7
7
 
8
- helper_method :inventory_item_message, :inventory_item_hold_message
8
+ helper SpreeCmCommissioner::Admin::InventoryItemsHelper
9
9
 
10
10
  def load_parent
11
11
  @product = Spree::Product.find_by(slug: params[:product_id])
@@ -40,58 +40,6 @@ module Spree
40
40
  redirect_back fallback_location: admin_product_stock_managements_path(@product)
41
41
  end
42
42
 
43
- # GET /products/:slug/stock_managements/calendar?year=&selected_option_value_ids[]=
44
- def calendar
45
- @year = params[:year].present? ? params[:year].to_i : Time.zone.today.year
46
-
47
- from_date = Date.new(@year, 1, 1).beginning_of_year
48
- to_date = Date.new(@year, 1, 1).end_of_year
49
-
50
- @variant_kind_option_types = Spree::Variants::OptionTypesFinder.new(variant_ids: @product.variants.ids)
51
- .execute.select(&:variant?)
52
- @selected_option_value_ids = Array(params[:selected_option_value_ids]).map(&:to_i)
53
-
54
- inventory_items = @product.inventory_items.includes(:variant, :prices).where(
55
- inventory_date: from_date..to_date
56
- )
57
-
58
- if @selected_option_value_ids.present?
59
- variant_ids = SpreeCmCommissioner::Variants::FindByOptionValueIds.new(
60
- product: @product, option_value_ids: @selected_option_value_ids
61
- ).execute.ids
62
- inventory_items = inventory_items.where(variant_id: variant_ids)
63
- @matched_variants_count = variant_ids.size
64
- else
65
- @matched_variants_count = @product.variants.count
66
- end
67
-
68
- @inventory_items = inventory_items.to_a
69
-
70
- @cached_inventory_items = ::SpreeCmCommissioner::RedisStock::CachedInventoryItemsBuilder.new(@inventory_items)
71
- .call
72
- .index_by(&:inventory_item_id)
73
- @events = SpreeCmCommissioner::CalendarEvent.from_inventory_items(@inventory_items)
74
- end
75
-
76
- def inventory_item_message(inventory_item, cached_inventory_item)
77
- synced = inventory_item.quantity_available == cached_inventory_item.quantity_available
78
-
79
- if synced
80
- "Synced: Quantity available matches in both DB and Redis (#{cached_inventory_item.quantity_available})."
81
- else
82
- "Out of sync: Redis shows #{cached_inventory_item.quantity_available} available, which doesn't match the database."
83
- end
84
- end
85
-
86
- def inventory_item_hold_message(inventory_item, cached_inventory_item)
87
- synced = inventory_item.quantity_on_hold == cached_inventory_item.quantity_on_hold
88
- if synced
89
- "Synced: Quantity on hold matches in both DB and Redis (#{cached_inventory_item.quantity_on_hold})."
90
- else
91
- "Out of sync: Redis shows #{cached_inventory_item.quantity_on_hold} on hold, which doesn't match the database."
92
- end
93
- end
94
-
95
43
  private
96
44
 
97
45
  def load_inventories
@@ -0,0 +1,122 @@
1
+ module Spree
2
+ module Admin
3
+ class StoreCreditAdminController < Spree::Admin::BaseController
4
+ TABS = %w[overview frozen_store_credits settings].freeze
5
+
6
+ def show
7
+ @current_tab = TABS.include?(params[:tab]) ? params[:tab] : 'overview'
8
+ @tenants = SpreeCmCommissioner::Tenant.order(:name)
9
+ @current_tenant_id = params[:tenant_id].presence&.to_i
10
+ store_credit_overview = SpreeCmCommissioner::StoreCreditOverviewQuery.new(issuer_tenant_id: @current_tenant_id)
11
+ @summary = store_credit_overview.call
12
+ @frozen_store_credits = store_credit_overview.frozen_store_credits
13
+ @config = SpreeCmCommissioner::Configuration.new
14
+ @store = Spree::Store.default
15
+ end
16
+
17
+ # Covers every prepaid_* Configuration preference editable from the Settings tab -- the
18
+ # currency rate table (keyed by Spree::Store.default#supported_currency_codes -- edit the
19
+ # currency list itself on the store's own admin settings page, not here) and the
20
+ # fraud/velocity limits (Configuration's comment on prepaid_max_topups_per_hour and
21
+ # friends). One form/action since nothing here is ever meaningfully saved independently.
22
+ SETTINGS_KEYS = %i[
23
+ prepaid_currency_rates prepaid_topup_min_amount prepaid_topup_max_amount
24
+ prepaid_max_topups_per_hour prepaid_max_topup_count_daily prepaid_daily_topup_limit
25
+ prepaid_max_balance
26
+ ].freeze
27
+
28
+ def update_settings
29
+ config = SpreeCmCommissioner::Configuration.new
30
+ new_values = {
31
+ prepaid_currency_rates: permitted_currency_rates(config),
32
+ prepaid_topup_min_amount: params[:prepaid_topup_min_amount].to_d,
33
+ prepaid_topup_max_amount: params[:prepaid_topup_max_amount].to_d,
34
+ prepaid_max_topups_per_hour: params[:prepaid_max_topups_per_hour].to_i,
35
+ prepaid_max_topup_count_daily: params[:prepaid_max_topup_count_daily].to_i,
36
+ prepaid_daily_topup_limit: params[:prepaid_daily_topup_limit].to_d,
37
+ prepaid_max_balance: params[:prepaid_max_balance].to_d
38
+ }
39
+
40
+ error = invalid_settings_reason(new_values)
41
+ if error
42
+ flash[:error] = error
43
+ return redirect_to admin_store_credit_path(tab: 'settings')
44
+ end
45
+
46
+ old_values = SETTINGS_KEYS.index_with { |key| config[key] }
47
+ SETTINGS_KEYS.each { |key| config[key] = new_values[key] }
48
+
49
+ log_settings_change(old_values, new_values)
50
+
51
+ flash[:success] = I18n.t('spree_cm_commissioner.store_credit_admin.settings_updated')
52
+ redirect_to admin_store_credit_path(tab: 'settings')
53
+ end
54
+
55
+ def run_reconciliation
56
+ report = SpreeCmCommissioner::PrepaidReconciliationService.run_daily
57
+
58
+ if report.drift_count.zero?
59
+ flash[:success] = I18n.t('spree_cm_commissioner.store_credit_admin.reconciliation_success', checked_count: report.checked_count)
60
+ else
61
+ flash[:error] = I18n.t(
62
+ 'spree_cm_commissioner.store_credit_admin.reconciliation_drift',
63
+ checked_count: report.checked_count, drift_count: report.drift_count
64
+ )
65
+ end
66
+
67
+ redirect_back fallback_location: admin_store_credit_path
68
+ end
69
+
70
+ private
71
+
72
+ # Keyed by the store's current supported-currencies list, so a dropped currency's rate
73
+ # doesn't linger and a new one gets an explicit (fallback 1.0) entry.
74
+ def permitted_currency_rates(config)
75
+ currency_codes = Spree::Store.default.supported_currency_codes
76
+ submitted = params[:prepaid_currency_rates]
77
+ submitted = submitted.is_a?(ActionController::Parameters) ? submitted.permit(*currency_codes) : {}
78
+
79
+ currency_codes.index_with do |currency|
80
+ submitted[currency].presence&.to_f || config.prepaid_currency_rates[currency] || 1.0
81
+ end
82
+ end
83
+
84
+ # These preferences are the only fraud/velocity ceiling enforced on store credit money-in
85
+ # (Prepaid::CheckTopUpLimits, Prepaid::TopUp) -- a negative value would be silently treated
86
+ # as "no limit" by comparisons like `amount > limit`, and a non-positive currency rate would
87
+ # corrupt every cross-currency conversion built on it.
88
+ def invalid_settings_reason(values)
89
+ %i[
90
+ prepaid_topup_min_amount prepaid_topup_max_amount prepaid_max_topups_per_hour
91
+ prepaid_max_topup_count_daily prepaid_daily_topup_limit prepaid_max_balance
92
+ ].each do |key|
93
+ return "#{key} can't be negative" if values[key].negative?
94
+ end
95
+
96
+ if values[:prepaid_topup_min_amount] > values[:prepaid_topup_max_amount] && values[:prepaid_topup_max_amount].positive?
97
+ return 'prepaid_topup_min_amount cannot exceed prepaid_topup_max_amount'
98
+ end
99
+
100
+ values[:prepaid_currency_rates].each do |currency, rate|
101
+ return "currency rate for #{currency} must be positive" unless rate.positive?
102
+ end
103
+
104
+ nil
105
+ end
106
+
107
+ # No dedicated audit-log model for Configuration preferences (they're key/value, not an
108
+ # ActiveRecord row PaperTrail can version) -- structured log line is the same pattern
109
+ # #run_reconciliation already uses ("-- see logs") for admin-triggered store credit actions.
110
+ def log_settings_change(old_values, new_values)
111
+ changed = SETTINGS_KEYS.each_with_object({}) do |key, diff|
112
+ diff[key] = { from: old_values[key], to: new_values[key] } if old_values[key] != new_values[key]
113
+ end
114
+ return if changed.empty?
115
+
116
+ Rails.logger.info(
117
+ "[store_credit_settings_change] admin_id=#{try_spree_current_user&.id} changes=#{changed}"
118
+ )
119
+ end
120
+ end
121
+ end
122
+ end
@@ -1,6 +1,8 @@
1
1
  module Spree
2
2
  module Admin
3
3
  class TicketTransferRulesController < Spree::Admin::ResourceController
4
+ before_action :load_offline_product_ids, only: %i[new edit create update]
5
+
4
6
  def index
5
7
  @search = model_class.ransack(params[:q])
6
8
  @ticket_transfer_rules = @search.result
@@ -11,6 +13,11 @@ module Spree
11
13
 
12
14
  private
13
15
 
16
+ # The scope-target search only returns `name`, so the form labels offline products from this list.
17
+ def load_offline_product_ids
18
+ @offline_product_ids = Spree::Product.where(offline: true).pluck(:id)
19
+ end
20
+
14
21
  def model_class
15
22
  SpreeCmCommissioner::TicketTransferRule
16
23
  end