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
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 9473bef71175dc42b69c1959ac56827e1ca0ef2b6c13205ed2c83793e678a21f
4
- data.tar.gz: 314a9fbe0dbc6fe925f435b3eaeef9d737689f81fb7d95e631bbcf10a2e3e8bf
3
+ metadata.gz: ba98474e53d7bb369512ec096ad8459a8baae5d2d77985462e99d48cd4d68820
4
+ data.tar.gz: 9588226b0da09b905292b7518b656ee4261d03ca1e6e55beb526a96ee3365e87
5
5
  SHA512:
6
- metadata.gz: fa509b4c09684a80915bca9927228aa41de193821593d00f84da4cd1bb2f2b0d5034146d9c52e4dec654ad57092ac87273771be63841b66546cb1a1b63e0077d
7
- data.tar.gz: f04d83d04ce10ebb051931a0bde85c690e279ac08d18e90b3be9e2f14721992c9e04096ef1eab051e1dde8b81ba6ea6aef356ca77422ac27ecb05aebf7f2e5c8
6
+ metadata.gz: f09ce118715100ba4d56d0b3c828c946277d632329c1b84e0517f1b513ec4cf64d055bceb6696983324b46f4df428ad0626f959dc961f1e73bdf22c4fac79b51
7
+ data.tar.gz: 72ca04d1fe1ab0f6741269127a90b9f005c7569b55b7e41e619fa3bce15564131414dacf36d619a4d44020a57e8e298581d42f0a06b7933a682343058a266e39
data/Gemfile.lock CHANGED
@@ -34,7 +34,7 @@ GIT
34
34
  PATH
35
35
  remote: .
36
36
  specs:
37
- spree_cm_commissioner (2.13.5)
37
+ spree_cm_commissioner (2.14.0)
38
38
  activerecord-multi-tenant
39
39
  activerecord_json_validator (~> 2.1, >= 2.1.3)
40
40
  aws-sdk-cloudfront
@@ -1,7 +1,6 @@
1
1
  //= require map/map-control-button
2
2
  //= require map/map-picker
3
3
  //= require spree_cm_commissioner/routes
4
- //= require spree_cm_commissioner/calendar
5
4
  //= require spree_cm_commissioner/annual_calendar_form
6
5
  //= require spree_cm_commissioner/tenant_payment_icon_fields
7
6
  //= require spree_cm_commissioner/copy_to_clipboard
@@ -22,37 +22,6 @@ $day-background: theme-color("light");
22
22
  @include hover {
23
23
  background-color: darken($day-background, 5%);
24
24
  }
25
-
26
- .c-date-container {
27
- display: flex;
28
- flex-flow: column;
29
- height: 100%;
30
-
31
- .c-date {
32
- padding: 6px;
33
- cursor: pointer;
34
- }
35
-
36
- .c-events-container {
37
- flex-grow: 1;
38
- display: flex;
39
- align-items: flex-end;
40
- }
41
-
42
- .c-events {
43
- display: flex;
44
- align-items: flex-end;
45
-
46
- .c-event {
47
- width: 8px;
48
- height: 8px;
49
- border-radius: 4px;
50
- background-color: theme-color("primary");
51
- margin-right: 4px;
52
- cursor: pointer;
53
- }
54
- }
55
- }
56
25
  }
57
26
 
58
27
  .wday-0 {
@@ -103,14 +72,6 @@ $day-background: theme-color("light");
103
72
  // }
104
73
  }
105
74
 
106
- .c-month-calendar {
107
- .simple-calendar {
108
- table {
109
- @extend .table-responsive-md;
110
- }
111
- }
112
- }
113
-
114
75
  .c-annual-calendar {
115
76
  @extend .row;
116
77
 
@@ -0,0 +1,16 @@
1
+ module Spree
2
+ module Admin
3
+ # The `cells` the Rates & Availability grid posts for its selection, shaped by the script's raCellData:
4
+ # { type: 'stock' | 'price', variant_id:, rate_plan_id: ('' for the variant's own prices), date: ('' for Base) }.
5
+ # They travel in a JSON body, as a selection across months can be thousands.
6
+ module RatesAvailabilityCells
7
+ extend ActiveSupport::Concern
8
+
9
+ private
10
+
11
+ def selected_cells
12
+ @selected_cells ||= params.permit(cells: %i[type variant_id rate_plan_id date]).fetch(:cells, []).map { |cell| cell.to_h.symbolize_keys }
13
+ end
14
+ end
15
+ end
16
+ end
@@ -18,6 +18,7 @@ module Spree
18
18
  stock_items: :stock_location
19
19
  }
20
20
  base_includes[:product_option_types] = :option_type
21
+ base_includes[:variant_kind_product_option_types] = :option_type
21
22
  base_includes[:product_promotion_rules] = {
22
23
  promotion_rule: {
23
24
  promotion: []
@@ -1,6 +1,10 @@
1
1
  module Spree
2
2
  module Admin
3
3
  module BaseControllerDecorator
4
+ def self.prepended(base)
5
+ base.before_action :set_prepaid_current_actor
6
+ end
7
+
4
8
  # even db in read only mode, authorizor still need to run Warden callbacks which will write to db
5
9
  # this include update tracked fields such as sign_in_count, last_sign_in_at, etc.
6
10
  def authorize!(*args)
@@ -40,6 +44,16 @@ module Spree
40
44
 
41
45
  redirect_back fallback_location: admin_root_path
42
46
  end
47
+
48
+ private
49
+
50
+ # See SpreeCmCommissioner::Current#actor -- lets StoreCreditEventDecorator's after_create
51
+ # mirror attribute an admin-triggered authorize/capture/void/credit event (refunds, voids)
52
+ # to whoever is signed in, without threading current_user through Spree core's own
53
+ # Payment/Refund/StoreCredit call chain.
54
+ def set_prepaid_current_actor
55
+ SpreeCmCommissioner::Current.actor = try_spree_current_user
56
+ end
43
57
  end
44
58
  end
45
59
  end
@@ -2,7 +2,22 @@ module Spree
2
2
  module Admin
3
3
  class CustomerNotificationsController < Spree::Admin::ResourceController
4
4
  def index
5
- @notifications = SpreeCmCommissioner::CustomerNotification.all
5
+ @search = model_class.ransack(params[:q])
6
+ @search.sorts = 'created_at desc' if @search.sorts.empty?
7
+ @notifications = @search.result.page(params[:page]).per(params[:per_page] || 25)
8
+ end
9
+
10
+ # POST /admin/customer_notifications/:id/clone
11
+ def clone
12
+ result = SpreeCmCommissioner::CustomerNotifications::Clone.call(customer_notification: @object)
13
+
14
+ if result.success?
15
+ flash[:success] = Spree.t(:customer_notification_cloned)
16
+ redirect_to edit_admin_customer_notification_url(result.value)
17
+ else
18
+ flash[:error] = Spree.t(:customer_notification_not_cloned, error: result.error.to_s)
19
+ redirect_to admin_customer_notifications_url
20
+ end
6
21
  end
7
22
 
8
23
  def model_class
@@ -43,7 +43,8 @@ module Spree
43
43
 
44
44
  def edit
45
45
  @kyc_fields = @object.line_item.kyc_fields
46
- @check_in = @object.check_in
46
+ @check_ins = @object.check_ins.includes(:check_in_session, :check_in_by)
47
+ @eligible_check_in_sessions = @object.eligible_check_in_sessions.order(:position)
47
48
  end
48
49
 
49
50
  def check_in
@@ -35,6 +35,17 @@ module Spree
35
35
  admin_integrations_url(options)
36
36
  end
37
37
 
38
+ # Builds the chosen subclass rather than the base Integration: `create` only assigns `type`
39
+ # afterwards, which changes the column but not the object's class, so the subclass's
40
+ # callbacks and attribute defaults would never run. `create` posts the type in the form;
41
+ # `new` gets it as ?type= when one is picked. With no type yet, or one that isn't an
42
+ # integration, it stays the base class (never constantized).
43
+ # override
44
+ def build_resource
45
+ type = params.dig(:spree_cm_commissioner_integration, :type).presence || params[:type]
46
+ SpreeCmCommissioner::Integration.types.include?(type) ? type.constantize.new : model_class.new
47
+ end
48
+
38
49
  private
39
50
 
40
51
  def permitted_resource_params
@@ -47,6 +58,7 @@ module Spree
47
58
  :full_sync_interval_hours,
48
59
  :tenant_id,
49
60
  :vendor_id,
61
+ :oauth_application_id,
50
62
  public_metadata: {},
51
63
  private_metadata: {}
52
64
  )
@@ -62,6 +74,7 @@ module Spree
62
74
  @conflict_strategy_options ||= enum_to_options(SpreeCmCommissioner::Integration.conflict_strategies)
63
75
  @tenants ||= SpreeCmCommissioner::Tenant.select(:id, :name).order(:name)
64
76
  @vendors ||= Spree::Vendor.select(:id, :name).order(:name)
77
+ @oauth_applications ||= Spree::OauthApplication.select(:id, :name).order(:name)
65
78
  end
66
79
 
67
80
  def enum_to_options(enum_hash)
@@ -69,14 +82,10 @@ module Spree
69
82
  end
70
83
 
71
84
  def integration_type_options
72
- files = Dir[SpreeCmCommissioner::Engine.root.join('app/models/spree_cm_commissioner/integrations/*.rb')]
73
-
74
- return [[Spree.t(:type), 'SpreeCmCommissioner::Integration']] if files.blank?
85
+ types = SpreeCmCommissioner::Integration.types
86
+ return [[Spree.t(:type), 'SpreeCmCommissioner::Integration']] if types.blank?
75
87
 
76
- files.map do |path|
77
- class_name = "SpreeCmCommissioner::Integrations::#{File.basename(path, '.rb').camelize}"
78
- [class_name, class_name]
79
- end.sort_by(&:first)
88
+ types.map { |class_name| [class_name, class_name] }
80
89
  end
81
90
  end
82
91
  end
@@ -1,73 +1,25 @@
1
1
  module Spree
2
2
  module Admin
3
3
  class InventoryItemsController < StockManagementsController
4
- before_action :load_parent
5
-
6
- # GET /products/:slug/inventory_items/prices?inventory_item_ids=1,2,3
7
- def prices
8
- inventory_item_ids = params[:inventory_item_ids]
9
- @inventory_items = @product.inventory_items.where(id: inventory_item_ids)
10
- end
11
-
12
- # GET /products/:slug/inventory_items/stocks?inventory_item_ids=1,2,3
13
- def stocks
14
- inventory_item_ids = params[:inventory_item_ids]
15
- @inventory_items = @product.inventory_items.where(id: inventory_item_ids)
16
- end
17
-
18
- # PATCH /products/:slug/inventory_items/bulk_update_prices
19
- def bulk_update_prices # rubocop:disable Metrics/CyclomaticComplexity,Metrics/PerceivedComplexity
20
- inventory_item_ids = params[:inventory_item_ids]
21
- inventory_items = @product.inventory_items.where(id: inventory_item_ids)
22
-
23
- inventory_items.each do |inventory_item|
24
- params[:prices]&.values&.each do |price_params|
25
- next if price_params[:price].empty?
4
+ include RatesAvailabilityCells
26
5
 
27
- price = inventory_item.prices.find_or_initialize_by(currency: price_params[:currency])
28
- price.price = price_params[:price]
29
- price.compare_at_price = price_params[:compare_at_price].empty? ? nil : price_params[:compare_at_price]
30
- price.variant_id = inventory_item.variant_id
31
- price.save! if (price.new_record? && price.price) || (!price.new_record? && price.changed?)
32
- end
33
- end
34
-
35
- flash[:success] = "Successfully updated prices for #{inventory_items.count} items"
36
- redirect_back fallback_location: admin_product_stock_managements_path(@product)
37
- end
6
+ DETAILS_PER_PAGE = 50
38
7
 
39
- # PATCH /products/:slug/inventory_items/bulk_update_stocks
40
- def bulk_update_stocks
41
- inventory_item_ids = params[:inventory_item_ids]
42
- inventory_items = @product.inventory_items.where(id: inventory_item_ids)
43
- target_quantity = params[:quantity].to_i
44
-
45
- if target_quantity.negative?
46
- flash[:error] = "Target quantity (#{target_quantity}) must not be negative"
47
- return redirect_back(fallback_location: admin_product_stock_managements_path(@product))
48
- end
49
-
50
- inventory_items.each do |inventory_item|
51
- # Calculate the difference between target and current quantity
52
- quantity_change = target_quantity - inventory_item.quantity_available
53
-
54
- # Use adjust_quantity! to update max_capacity, quantity_available, and sync to Redis
55
- inventory_item.adjust_quantity!(quantity_change)
56
- end
8
+ before_action :load_parent
57
9
 
58
- flash[:success] = "Successfully updated stocks for #{inventory_items.count} items"
59
- redirect_back fallback_location: admin_product_stock_managements_path(@product)
60
- end
10
+ helper SpreeCmCommissioner::Admin::RatesAvailabilitiesHelper
61
11
 
62
- # PATCH /products/:slug/inventory_items/bulk_update_closed
12
+ # POST /products/:slug/inventory_items/details, JSON: { cells: [...], page: }
63
13
  #
64
- # Ids travel in the form body, not the query string: a range across a year can be hundreds of ids.
65
- def bulk_update_closed
66
- closed = ActiveModel::Type::Boolean.new.cast(params[:closed])
67
- count = @product.inventory_items.where(id: params[:inventory_item_ids]).update_all(closed: closed, updated_at: Time.current) # rubocop:disable Rails/SkipsModelValidations
68
-
69
- flash[:success] = "#{closed ? 'Closed' : 'Reopened'} #{count} inventory items for sale"
70
- redirect_back fallback_location: admin_product_stock_managements_path(@product)
14
+ # The Rates & Availability grid's stock details modal: the counts of each selected variant and date, as stock
15
+ # managements shows them. POST, as cells travel in the body.
16
+ def details
17
+ inventory_items = selected_inventory_items.page(params[:page]).per(DETAILS_PER_PAGE)
18
+ cached = SpreeCmCommissioner::RedisStock::CachedInventoryItemsBuilder.new(inventory_items.to_a).call.index_by(&:inventory_item_id)
19
+
20
+ render partial: 'details', formats: [:html], locals: {
21
+ inventory_items: inventory_items, cached_inventory_items: cached, cells: selected_cells
22
+ }
71
23
  end
72
24
 
73
25
  # PATCH /products/:slug/inventory_items/bulk_reset?inventory_item_ids=1,2,3
@@ -89,6 +41,25 @@ module Spree
89
41
 
90
42
  redirect_back fallback_location: admin_product_stock_managements_path(@product)
91
43
  end
44
+
45
+ private
46
+
47
+ # Price cells count too: each names a variant and a date. A dated product's Base has no inventory item.
48
+ def selected_inventory_items
49
+ pairs = selected_cells.map { |cell| [cell[:variant_id].to_i, cell[:date].presence] }.uniq
50
+ return SpreeCmCommissioner::InventoryItem.none if pairs.empty?
51
+
52
+ # Joined to the exact pairs: variant and date lists alone would also match every other variant on those dates.
53
+ values = pairs.map { |variant_id, date| ActiveRecord::Base.sanitize_sql_array(['(?, ?::date)', variant_id, date]) }.join(', ')
54
+ selected = "JOIN (VALUES #{values}) AS selected(variant_id, inventory_date) ON selected.variant_id = cm_inventory_items.variant_id " \
55
+ 'AND selected.inventory_date IS NOT DISTINCT FROM cm_inventory_items.inventory_date'
56
+ SpreeCmCommissioner::InventoryItem
57
+ .where(variant_id: @product.variants_including_master.select(:id))
58
+ .joins(selected)
59
+ .joins(:variant)
60
+ .order('spree_variants.position, cm_inventory_items.variant_id, cm_inventory_items.inventory_date')
61
+ .preload(variant: [:product, { option_values: :option_type }])
62
+ end
92
63
  end
93
64
  end
94
65
  end
@@ -0,0 +1,69 @@
1
+ module Spree
2
+ module Admin
3
+ # Two read-only views over the prepaid ledger:
4
+ # - #index: platform-wide filterable browse (GET /admin/store_credit/transactions).
5
+ # - #show: a single ledger row's full detail (GET /admin/store_credit/transactions/:id),
6
+ # reached from both #index's row links and the per-user store_credits Transactions tab.
7
+ # Not nested under a user/store_credit -- PrepaidTransaction has a global id, and one
8
+ # detail page reached from two different listings is simpler than two near-identical ones.
9
+ # Currency, balance, admin, and reason/freeze metadata live here rather than cluttering
10
+ # either listing table.
11
+ class PrepaidTransactionsController < Spree::Admin::BaseController
12
+ TRANSACTIONS_PER_PAGE = 25
13
+
14
+ before_action :load_transaction, only: :show
15
+
16
+ # GET /admin/store_credit/transactions -- the admin/store_credit dashboard's own
17
+ # platform-wide ledger browse (see Spree::Admin::StoreCreditAdminController's other tabs and
18
+ # store_credit_admin/_tabs.html.erb), but its own route/controller rather than a #show?
19
+ # tab=transactions query param, so filters/pagination live in a real URL.
20
+ def index
21
+ @current_tab = 'transactions'
22
+ @tenants = SpreeCmCommissioner::Tenant.order(:name)
23
+ @current_tenant_id = params[:tenant_id].presence&.to_i
24
+ @store = Spree::Store.default
25
+ build_transactions_search
26
+ end
27
+
28
+ def show; end
29
+
30
+ private
31
+
32
+ def load_transaction
33
+ @transaction = SpreeCmCommissioner::PrepaidTransaction.find(params[:id])
34
+ @user = @transaction.user
35
+ @store_credit = @transaction.store_credit
36
+ @reversed = SpreeCmCommissioner::PrepaidTransaction
37
+ .exists?(user: @user, transaction_type: 'reverse_top_up', idempotency_key: "admin_reverse:#{@transaction.id}")
38
+ @return_to = safe_return_to
39
+ end
40
+
41
+ # So the back link on the detail page returns to the exact filtered/paginated Transactions
42
+ # tab state the admin came from, not a bare reset to page 1 -- only same-admin-namespace
43
+ # paths are honored, since this is attacker-controllable input (an admin could be tricked
44
+ # into following a crafted link) and open-redirecting out of /admin would be a phishing vector.
45
+ def safe_return_to
46
+ candidate = params[:return_to]
47
+ candidate if candidate.present? && candidate.start_with?('/admin/')
48
+ end
49
+
50
+ # Individual ledger row search/browse -- scoped to whichever tenant the dashboard's own
51
+ # tenant selector has picked (tenant_id: nil => platform-issued, same convention as
52
+ # StoreCreditOverviewQuery), further filtered via Ransack (q[user_email_cont],
53
+ # q[currency_eq], q[transaction_type_eq], q[created_at_gteq]/[created_at_lteq], ...).
54
+ def build_transactions_search
55
+ params[:q] = {} if params[:q].blank?
56
+ @transactions_search = SpreeCmCommissioner::PrepaidTransaction.where(tenant_id: @current_tenant_id).ransack(params[:q])
57
+ @transactions = @transactions_search.result
58
+ .includes(:user, :order, :tenant, :admin, :store_credit)
59
+ .order(created_at: :desc)
60
+ .page(params[:page])
61
+ .per(TRANSACTIONS_PER_PAGE)
62
+ end
63
+
64
+ def model_class
65
+ SpreeCmCommissioner::PrepaidTransaction
66
+ end
67
+ end
68
+ end
69
+ end
@@ -14,6 +14,7 @@ module Spree
14
14
  # partial reads these ivars — without this a rejected save is a 500 instead of a form.
15
15
  before_action :load_stock_rows, only: %i[edit update]
16
16
  before_action :load_assignable_roles, only: %i[new create edit update]
17
+ before_action :load_offline_product_ids, only: %i[new create edit update]
17
18
 
18
19
  # GET /admin/private_sales
19
20
  def index
@@ -45,6 +46,11 @@ module Spree
45
46
  @assignable_roles = Spree::Role.non_vendor.order(:name)
46
47
  end
47
48
 
49
+ # The product search only returns `name`, so the form labels offline products from this list.
50
+ def load_offline_product_ids
51
+ @offline_product_ids = Spree::Product.where(offline: true).pluck(:id)
52
+ end
53
+
48
54
  def location_after_save
49
55
  edit_admin_private_sale_path(@private_sale)
50
56
  end
@@ -42,7 +42,8 @@ module Spree
42
42
 
43
43
  def commission_for(product)
44
44
  commission_rate = product.commission_rate || product.vendor&.commission_rate || 0
45
- product.price * commission_rate / 100
45
+ # A product can have no price in the store currency yet (e.g. a freshly created ticket).
46
+ (product.price || 0) * commission_rate / 100
46
47
  end
47
48
 
48
49
  # override
@@ -0,0 +1,88 @@
1
+ module Spree
2
+ module Admin
3
+ class ProductOptionTypesController < Spree::Admin::ResourceController
4
+ belongs_to 'spree/product', find_by: :slug
5
+
6
+ before_action :load_available_option_types, only: :index
7
+ before_action :ensure_not_used_by_variants, only: :destroy
8
+
9
+ # override: the base version finds ids across every product; scope them to this one.
10
+ def update_positions
11
+ ApplicationRecord.transaction do
12
+ params[:positions].each do |id, index|
13
+ parent.product_option_types.find(id).set_list_position(index)
14
+ end
15
+ end
16
+
17
+ head :ok
18
+ end
19
+
20
+ # One Save for the whole table instead of a submit per row. Only this product's rows are looked up,
21
+ # so ids belonging to another product are ignored.
22
+ def update_all
23
+ rows = parent.product_option_types.where(id: bulk_params.keys).index_by { |row| row.id.to_s }
24
+
25
+ ApplicationRecord.transaction do
26
+ bulk_params.each { |id, attributes| rows[id]&.update!(attributes) }
27
+ end
28
+
29
+ flash[:success] = I18n.t('spree.admin.product_option_types.saved')
30
+ redirect_to collection_url
31
+ rescue ActiveRecord::RecordInvalid => e
32
+ flash[:error] = e.record.errors.full_messages.to_sentence
33
+ redirect_to collection_url
34
+ end
35
+
36
+ private
37
+
38
+ # override
39
+ def collection_actions
40
+ super + [:update_all]
41
+ end
42
+
43
+ # { "<product_option_type id>" => { hidden:, presentation: } }
44
+ def bulk_params
45
+ @bulk_params ||= params.fetch(:product_option_types, {}).to_unsafe_h.to_h do |id, attributes|
46
+ [id.to_s, ActionController::Parameters.new(attributes).permit(:hidden, :presentation)]
47
+ end
48
+ end
49
+
50
+ # override
51
+ def collection
52
+ parent.product_option_types.includes(:option_type).order(:position)
53
+ end
54
+
55
+ # override
56
+ def model_class
57
+ Spree::ProductOptionType
58
+ end
59
+
60
+ # override: only create goes through the resource params; display settings are saved by #update_all.
61
+ def permitted_resource_params
62
+ params.require(:product_option_type).permit(:option_type_id)
63
+ end
64
+
65
+ def load_available_option_types
66
+ @available_option_types = Spree::OptionType.where(kind: %i[variant product])
67
+ .where.not(id: parent.option_type_ids)
68
+ .order(:presentation)
69
+ end
70
+
71
+ # Removing an option type that variants still carry values for would leave those values orphaned
72
+ # from the product's option types, the same rule the dashboard's variant options page enforces.
73
+ def ensure_not_used_by_variants
74
+ used = parent.variants_including_master.joins(:option_values)
75
+ .exists?(spree_option_values: { option_type_id: @object.option_type_id })
76
+ return unless used
77
+
78
+ flash[:error] = I18n.t('spree.admin.product_option_types.still_used_by_variants', name: @object.display_presentation)
79
+
80
+ # link_to_delete removes over AJAX and only drops the row when the JS response carries a success flash.
81
+ respond_to do |format|
82
+ format.html { redirect_to collection_url }
83
+ format.js { render_js_for_destroy }
84
+ end
85
+ end
86
+ end
87
+ end
88
+ end
@@ -0,0 +1,125 @@
1
+ module Spree
2
+ module Admin
3
+ # A product's rate plans: the record (name, kind, adjustment, terms), not its prices, which the Rates &
4
+ # Availability grid edits. Forms open in a modal on that grid, so a save answers with no content and the
5
+ # grid reloads its rows, while an invalid form comes back as HTML to show in place.
6
+ class RatePlansController < Spree::Admin::BaseController
7
+ before_action :load_product
8
+ before_action :load_rate_plan, only: %i[edit update destroy]
9
+
10
+ helper SpreeCmCommissioner::Admin::RatesAvailabilitiesHelper
11
+ helper_method :rate_plan_option_types, :currencies
12
+
13
+ def new
14
+ variant = variants.find(params[:variant_id])
15
+ @rate_plan = variant.rate_plans.new(kind: :derived, adjustment_type: :percent)
16
+ render_form
17
+ end
18
+
19
+ def edit
20
+ render_form
21
+ end
22
+
23
+ # Also creates it on each variant in `also_variant_ids`, with the same terms.
24
+ def create
25
+ variant = variants.find(params[:variant_id])
26
+ also_variants = variants.where(id: params[:also_variant_ids]).where.not(id: variant.id)
27
+ @rate_plan = variant.rate_plans.new
28
+ rate_plans = [@rate_plan] + also_variants.map { |also_variant| also_variant.rate_plans.new }
29
+
30
+ save(rate_plans, rate_plan_params.merge(kind: params.dig(:rate_plan, :kind)))
31
+ end
32
+
33
+ # Kind stays as created: an independent rate plan has its own price rows, a derived one only date overrides.
34
+ def update
35
+ save([@rate_plan], rate_plan_params.merge(status: params.dig(:rate_plan, :status)).compact)
36
+ end
37
+
38
+ # Only a rate plan never sold: line items keep theirs (`restrict_with_error`), so a sold one is archived.
39
+ def destroy
40
+ if @rate_plan.destroy
41
+ head :no_content
42
+ else
43
+ render json: { error: @rate_plan.errors.full_messages.to_sentence }, status: :unprocessable_entity
44
+ end
45
+ end
46
+
47
+ # A dialog listing one variant's rate plans, archived too, as positions run across all of them.
48
+ def edit_order
49
+ @variant = variants.find(params[:variant_id])
50
+ render_order_form
51
+ end
52
+
53
+ # Saves the whole order at once, so a misclick in the dialog costs nothing until Save. `rate_plan_ids` must be
54
+ # exactly the variant's rate plans, or a plan added or deleted meanwhile would end up out of place.
55
+ def update_order
56
+ @variant = variants.find(params[:variant_id])
57
+ ids = Array(params[:rate_plan_ids]).map(&:to_i)
58
+ rate_plans = @variant.rate_plans.index_by(&:id)
59
+
60
+ return render_order_form(error: t('rates_availabilities.reorder_form.changed'), status: :conflict) unless ids.sort == rate_plans.keys.sort
61
+
62
+ # update_columns: acts_as_list would shift the others on every single save.
63
+ SpreeCmCommissioner::RatePlan.transaction do
64
+ ids.each.with_index(1) do |id, position|
65
+ rate_plans[id].update_columns(position: position, updated_at: Time.current) # rubocop:disable Rails/SkipsModelValidations
66
+ end
67
+ end
68
+
69
+ head :no_content
70
+ end
71
+
72
+ private
73
+
74
+ def rate_plan_option_types
75
+ @rate_plan_option_types ||= Spree::OptionType.where(kind: :rate_plan).includes(:option_values).order(:position)
76
+ end
77
+
78
+ def currencies
79
+ supported_currencies_for_all_stores.map(&:iso_code)
80
+ end
81
+
82
+ def save(rate_plans, attributes)
83
+ result = SpreeCmCommissioner::RatePlans::Save.call(
84
+ rate_plans: rate_plans,
85
+ attributes: attributes,
86
+ option_value_ids: Array(params.dig(:rate_plan, :option_value_ids)),
87
+ starting_prices: params.fetch(:starting_prices, {}).permit(*currencies).to_h
88
+ )
89
+
90
+ return head :no_content if result.success?
91
+
92
+ render_form(error: result.error.to_s, status: :unprocessable_entity)
93
+ end
94
+
95
+ def render_form(error: nil, status: :ok)
96
+ render partial: 'form', locals: { rate_plan: @rate_plan, error: error }, status: status
97
+ end
98
+
99
+ def render_order_form(error: nil, status: :ok)
100
+ rate_plans = @variant.rate_plans.includes(option_values: :option_type)
101
+ render partial: 'order_form', locals: { variant: @variant, rate_plans: rate_plans, error: error }, status: status
102
+ end
103
+
104
+ def rate_plan_params
105
+ params.require(:rate_plan).permit(:name, :adjustment_type, :adjustment_value, :currency)
106
+ end
107
+
108
+ def load_product
109
+ @product = Spree::Product.find_by!(slug: params[:product_id])
110
+ end
111
+
112
+ def load_rate_plan
113
+ @rate_plan = SpreeCmCommissioner::RatePlan.where(variant_id: variants.select(:id)).find(params[:id])
114
+ end
115
+
116
+ def variants
117
+ Spree::Variant.where(product_id: @product.id)
118
+ end
119
+
120
+ def model_class
121
+ SpreeCmCommissioner::RatePlan
122
+ end
123
+ end
124
+ end
125
+ end