spree_cm_commissioner 2.13.4 → 2.13.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (404) hide show
  1. checksums.yaml +4 -4
  2. data/.env.example +5 -0
  3. data/Gemfile.lock +1 -1
  4. data/app/controllers/spree/admin/base_controller_decorator.rb +14 -0
  5. data/app/controllers/spree/admin/customer_notifications_controller.rb +16 -1
  6. data/app/controllers/spree/admin/inventory_items_controller.rb +11 -0
  7. data/app/controllers/spree/admin/payment_fee_rules_controller.rb +76 -0
  8. data/app/controllers/spree/admin/prepaid_transactions_controller.rb +69 -0
  9. data/app/controllers/spree/admin/private_sales_controller.rb +6 -0
  10. data/app/controllers/spree/admin/product_commissions_controller.rb +2 -1
  11. data/app/controllers/spree/admin/reschedule_policies_controller.rb +148 -0
  12. data/app/controllers/spree/admin/reschedule_requests_controller.rb +82 -0
  13. data/app/controllers/spree/admin/service_fee_rules_controller.rb +1 -0
  14. data/app/controllers/spree/admin/store_credit_admin_controller.rb +122 -0
  15. data/app/controllers/spree/admin/ticket_transfer_rules_controller.rb +7 -0
  16. data/app/controllers/spree/api/v2/platform/store_credits_controller_decorator.rb +35 -0
  17. data/app/controllers/spree/api/v2/storefront/account/prepaid_balances_controller.rb +52 -0
  18. data/app/controllers/spree/api/v2/storefront/account/prepaid_transactions_controller.rb +56 -0
  19. data/app/controllers/spree/api/v2/storefront/cart_payment_fees_controller.rb +48 -0
  20. data/app/controllers/spree/api/v2/storefront/draft_orders_controller.rb +37 -0
  21. data/app/controllers/spree/api/v2/storefront/prepaid_config_controller.rb +22 -0
  22. data/app/controllers/spree/api/v2/storefront/reschedule_requests_controller.rb +156 -0
  23. data/app/controllers/spree/api/v2/storefront/seat_layouts_controller.rb +3 -1
  24. data/app/controllers/spree/api/v2/storefront/transit/item_availabilities_controller.rb +66 -0
  25. data/app/controllers/spree/api/v2/tenant/account/prepaid_balances_controller.rb +51 -0
  26. data/app/controllers/spree/api/v2/tenant/account/prepaid_transactions_controller.rb +53 -0
  27. data/app/controllers/spree/api/v2/tenant/base_controller.rb +20 -0
  28. data/app/controllers/spree/api/v2/tenant/cart_payment_fees_controller.rb +43 -0
  29. data/app/controllers/spree/api/v2/tenant/draft_orders_controller.rb +42 -0
  30. data/app/controllers/spree/api/v2/tenant/prepaid_config_controller.rb +29 -0
  31. data/app/controllers/spree_cm_commissioner/admin/store_credits_controller_decorator.rb +217 -0
  32. data/app/errors/spree_cm_commissioner/reschedule_policies/invalid_policyable_error.rb +8 -0
  33. data/app/errors/spree_cm_commissioner/reschedule_policies/not_reschedulable_error.rb +11 -0
  34. data/app/errors/spree_cm_commissioner/reschedule_requests/cancel_not_allowed_error.rb +6 -0
  35. data/app/errors/spree_cm_commissioner/reschedule_requests/complete_cash_payment_error.rb +6 -0
  36. data/app/errors/spree_cm_commissioner/reschedule_requests/eligibility_denied_error.rb +7 -0
  37. data/app/factory/spree_cm_commissioner/order_telegram_message_factory.rb +6 -2
  38. data/app/factory/spree_cm_commissioner/reschedule_telegram_message_factory.rb +141 -0
  39. data/app/finders/spree_cm_commissioner/inventory_items/recently_changed_finder.rb +1 -1
  40. data/app/helpers/spree/admin/base_helper_decorator.rb +12 -0
  41. data/app/helpers/spree/admin/reschedule_policies_helper.rb +35 -0
  42. data/app/helpers/spree/admin/reschedule_requests_helper.rb +79 -0
  43. data/app/helpers/spree_cm_commissioner/admin/fee_rules_helper.rb +23 -0
  44. data/app/interactors/spree_cm_commissioner/create_ticket.rb +1 -0
  45. data/app/jobs/spree_cm_commissioner/audit_event_job.rb +29 -0
  46. data/app/jobs/spree_cm_commissioner/prepaid_reconciliation_cron_job.rb +9 -0
  47. data/app/jobs/spree_cm_commissioner/prepaid_reverse_top_up_job.rb +16 -0
  48. data/app/jobs/spree_cm_commissioner/prepaid_setup_types_job.rb +24 -0
  49. data/app/jobs/spree_cm_commissioner/prepaid_top_up_credit_job.rb +31 -0
  50. data/app/jobs/spree_cm_commissioner/reschedule_requests/approval_reminder_job.rb +17 -0
  51. data/app/jobs/spree_cm_commissioner/reschedule_requests/execute_job.rb +20 -0
  52. data/app/jobs/spree_cm_commissioner/reschedule_requests/expire_overdue_requests_job.rb +12 -0
  53. data/app/jobs/spree_cm_commissioner/reschedule_requests/expire_request_job.rb +15 -0
  54. data/app/jobs/spree_cm_commissioner/reschedule_requests/payment_reminder_job.rb +17 -0
  55. data/app/models/concerns/spree_cm_commissioner/audit_wrapper.rb +139 -0
  56. data/app/models/concerns/spree_cm_commissioner/integrations/inventory_sync_cachable.rb +9 -0
  57. data/app/models/concerns/spree_cm_commissioner/option_type_attr_type.rb +2 -1
  58. data/app/models/concerns/spree_cm_commissioner/order_prepaid_payment.rb +15 -0
  59. data/app/models/concerns/spree_cm_commissioner/order_state_machine.rb +58 -8
  60. data/app/models/concerns/spree_cm_commissioner/percent_fee_calculable.rb +36 -0
  61. data/app/models/concerns/spree_cm_commissioner/product_type.rb +6 -2
  62. data/app/models/concerns/spree_cm_commissioner/reschedule_request_state_machine.rb +332 -0
  63. data/app/models/concerns/spree_cm_commissioner/scoped_fee_rule.rb +129 -0
  64. data/app/models/concerns/spree_cm_commissioner/service_type.rb +1 -1
  65. data/app/models/concerns/spree_cm_commissioner/store_preference.rb +1 -0
  66. data/app/models/concerns/spree_cm_commissioner/tenant_preference.rb +6 -0
  67. data/app/models/concerns/spree_cm_commissioner/variant_metadata.rb +24 -0
  68. data/app/models/concerns/spree_cm_commissioner/vehicle_kind.rb +1 -1
  69. data/app/models/spree/core/controller_helpers/currency_decorator.rb +70 -0
  70. data/app/models/spree_cm_commissioner/adjustment_decorator.rb +1 -0
  71. data/app/models/spree_cm_commissioner/audit_event.rb +11 -0
  72. data/app/models/spree_cm_commissioner/calculators/payment_fee_flat_amount.rb +21 -0
  73. data/app/models/spree_cm_commissioner/calculators/payment_fee_percent.rb +17 -0
  74. data/app/models/spree_cm_commissioner/calculators/service_fee_flat_amount.rb +4 -0
  75. data/app/models/spree_cm_commissioner/calculators/service_fee_percent.rb +5 -2
  76. data/app/models/spree_cm_commissioner/configuration.rb +17 -9
  77. data/app/models/spree_cm_commissioner/currency_rate.rb +15 -0
  78. data/app/models/spree_cm_commissioner/current.rb +8 -0
  79. data/app/models/spree_cm_commissioner/customer_notification.rb +4 -1
  80. data/app/models/spree_cm_commissioner/exports/import_order_guests_csv.rb +153 -0
  81. data/app/models/spree_cm_commissioner/guest.rb +21 -0
  82. data/app/models/spree_cm_commissioner/integration.rb +31 -0
  83. data/app/models/spree_cm_commissioner/integrations/book_me_bus_v1.rb +64 -0
  84. data/app/models/spree_cm_commissioner/integrations/larryta.rb +81 -0
  85. data/app/models/spree_cm_commissioner/integrations/stadium_x_v1.rb +4 -10
  86. data/app/models/spree_cm_commissioner/integrations/vireak_buntham.rb +82 -0
  87. data/app/models/spree_cm_commissioner/inventory_item.rb +22 -5
  88. data/app/models/spree_cm_commissioner/line_item_decorator.rb +6 -2
  89. data/app/models/spree_cm_commissioner/notification.rb +8 -0
  90. data/app/models/spree_cm_commissioner/order_decorator.rb +31 -3
  91. data/app/models/spree_cm_commissioner/order_updater_decorator.rb +6 -1
  92. data/app/models/spree_cm_commissioner/payment_decorator.rb +31 -0
  93. data/app/models/spree_cm_commissioner/payment_fee_rule.rb +97 -0
  94. data/app/models/spree_cm_commissioner/payment_method_decorator.rb +5 -0
  95. data/app/models/spree_cm_commissioner/payment_method_store_credit_decorator.rb +81 -0
  96. data/app/models/spree_cm_commissioner/place.rb +2 -0
  97. data/app/models/spree_cm_commissioner/prepaid_config.rb +59 -0
  98. data/app/models/spree_cm_commissioner/prepaid_transaction.rb +102 -0
  99. data/app/models/spree_cm_commissioner/product_decorator.rb +6 -0
  100. data/app/models/spree_cm_commissioner/redis_stock/cached_inventory_items_builder.rb +4 -2
  101. data/app/models/spree_cm_commissioner/reschedule_policy.rb +94 -0
  102. data/app/models/spree_cm_commissioner/reschedule_policy_assignment.rb +19 -0
  103. data/app/models/spree_cm_commissioner/reschedule_request.rb +227 -0
  104. data/app/models/spree_cm_commissioner/reschedule_requests/guest_selection.rb +80 -0
  105. data/app/models/spree_cm_commissioner/reschedule_snapshot.rb +65 -0
  106. data/app/models/spree_cm_commissioner/route.rb +1 -0
  107. data/app/models/spree_cm_commissioner/seat_layout.rb +8 -0
  108. data/app/models/spree_cm_commissioner/service_calendar.rb +4 -0
  109. data/app/models/spree_cm_commissioner/service_fee_rule.rb +17 -114
  110. data/app/models/spree_cm_commissioner/stock/availability_checker.rb +1 -1
  111. data/app/models/spree_cm_commissioner/stock/order_availability_checker.rb +1 -1
  112. data/app/models/spree_cm_commissioner/stock_item_decorator.rb +4 -0
  113. data/app/models/spree_cm_commissioner/store_credit_category_decorator.rb +23 -0
  114. data/app/models/spree_cm_commissioner/store_credit_decorator.rb +142 -0
  115. data/app/models/spree_cm_commissioner/store_credit_event_decorator.rb +94 -0
  116. data/app/models/spree_cm_commissioner/store_credit_type_decorator.rb +43 -0
  117. data/app/models/spree_cm_commissioner/store_decorator.rb +8 -0
  118. data/app/models/spree_cm_commissioner/tenant.rb +37 -0
  119. data/app/models/spree_cm_commissioner/trip.rb +2 -1
  120. data/app/models/spree_cm_commissioner/trip_stop.rb +2 -0
  121. data/app/models/spree_cm_commissioner/user_decorator.rb +79 -0
  122. data/app/models/spree_cm_commissioner/variant_decorator.rb +9 -1
  123. data/app/models/spree_cm_commissioner/vehicle.rb +3 -0
  124. data/app/models/spree_cm_commissioner/vehicle_type.rb +1 -0
  125. data/app/models/spree_cm_commissioner/vendor_decorator.rb +53 -0
  126. data/app/models/spree_cm_commissioner/vendor_place.rb +2 -0
  127. data/app/notifications/spree_cm_commissioner/reschedule_approved_notification.rb +7 -0
  128. data/app/notifications/spree_cm_commissioner/reschedule_cancelled_notification.rb +7 -0
  129. data/app/notifications/spree_cm_commissioner/reschedule_completed_notification.rb +7 -0
  130. data/app/notifications/spree_cm_commissioner/reschedule_expired_notification.rb +7 -0
  131. data/app/notifications/spree_cm_commissioner/reschedule_failed_notification.rb +7 -0
  132. data/app/notifications/spree_cm_commissioner/reschedule_notification_base.rb +41 -0
  133. data/app/notifications/spree_cm_commissioner/reschedule_payment_reminder_notification.rb +15 -0
  134. data/app/notifications/spree_cm_commissioner/reschedule_rejected_notification.rb +7 -0
  135. data/app/notifications/spree_cm_commissioner/reschedule_requested_notification.rb +7 -0
  136. data/app/overrides/spree/admin/products/_form/offline.html.erb.deface +9 -0
  137. data/app/overrides/spree/admin/shared/_main_menu/sidebar_reschedule.html.erb.deface +9 -0
  138. data/app/overrides/spree/admin/shared/_main_menu/sidebar_store_credit.html.erb.deface +8 -0
  139. data/app/overrides/spree/admin/shared/_order_tabs/reschedule_requests.html.erb.deface +10 -0
  140. data/app/overrides/spree/admin/shared/_product_tabs/stock_managements.html.erb.deface +1 -1
  141. data/app/overrides/spree/admin/shared/sub_menu/_configuration/menu_items.html.erb.deface +5 -0
  142. data/app/presenters/spree_cm_commissioner/prepaid_balance_presenter.rb +16 -0
  143. data/app/queries/spree_cm_commissioner/store_credit_overview_query.rb +75 -0
  144. data/app/queries/spree_cm_commissioner/user_store_credit_summary_query.rb +53 -0
  145. data/app/serializables/spree_cm_commissioner/item_availability_sync.rb +10 -0
  146. data/app/serializers/spree/v2/storefront/prepaid_balance_serializer.rb +15 -0
  147. data/app/serializers/spree/v2/storefront/prepaid_config_serializer.rb +11 -0
  148. data/app/serializers/spree/v2/storefront/prepaid_transaction_serializer.rb +17 -0
  149. data/app/serializers/spree/v2/storefront/user_serializer_decorator.rb +6 -0
  150. data/app/serializers/spree/v2/tenant/cart_serializer.rb +2 -1
  151. data/app/serializers/spree/v2/tenant/payment_method_serializer.rb +3 -0
  152. data/app/serializers/spree/v2/tenant/prepaid_balance_serializer.rb +15 -0
  153. data/app/serializers/spree/v2/tenant/prepaid_config_serializer.rb +16 -0
  154. data/app/serializers/spree/v2/tenant/prepaid_transaction_serializer.rb +16 -0
  155. data/app/serializers/spree_cm_commissioner/api/v2/platform/homepage_section_relatable_options_serializer.rb +5 -0
  156. data/app/serializers/spree_cm_commissioner/v2/storefront/cart_serializer_decorator.rb +3 -0
  157. data/app/serializers/spree_cm_commissioner/v2/storefront/inventory_item_serializer.rb +1 -0
  158. data/app/serializers/spree_cm_commissioner/v2/storefront/item_availability_sync_serializer.rb +10 -0
  159. data/app/serializers/spree_cm_commissioner/v2/storefront/payment_fee_preview_serializer.rb +19 -0
  160. data/app/serializers/spree_cm_commissioner/v2/storefront/payment_method_serializer_decorator.rb +16 -0
  161. data/app/serializers/spree_cm_commissioner/v2/storefront/reschedule_request_serializer.rb +49 -0
  162. data/app/serializers/spree_cm_commissioner/v2/storefront/reschedule_snapshot_serializer.rb +24 -0
  163. data/app/serializers/spree_cm_commissioner/v2/storefront/seat_layout_serializer.rb +8 -0
  164. data/app/services/spree_cm_commissioner/audit_logger.rb +59 -0
  165. data/app/services/spree_cm_commissioner/checkout/add_store_credit_decorator.rb +17 -0
  166. data/app/services/spree_cm_commissioner/checkout/add_store_credit_payments.rb +96 -0
  167. data/app/services/spree_cm_commissioner/checkout/remove_store_credit_decorator.rb +17 -0
  168. data/app/services/spree_cm_commissioner/currency_converter/create.rb +27 -0
  169. data/app/services/spree_cm_commissioner/currency_rates/manage.rb +99 -0
  170. data/app/services/spree_cm_commissioner/customer_notifications/clone.rb +39 -0
  171. data/app/services/spree_cm_commissioner/google_maps_geocoder.rb +41 -0
  172. data/app/services/spree_cm_commissioner/imports/orders/create.rb +24 -11
  173. data/app/services/spree_cm_commissioner/integrations/base/sync_manager.rb +1 -2
  174. data/app/services/spree_cm_commissioner/integrations/base/sync_result.rb +11 -0
  175. data/app/services/spree_cm_commissioner/integrations/book_me_bus_v1/external_client/client.rb +230 -0
  176. data/app/services/spree_cm_commissioner/integrations/book_me_bus_v1/external_client/connection.rb +147 -0
  177. data/app/services/spree_cm_commissioner/integrations/book_me_bus_v1/inventory/external_inventory_item_status.rb +52 -0
  178. data/app/services/spree_cm_commissioner/integrations/book_me_bus_v1/inventory/external_inventory_items.rb +91 -0
  179. data/app/services/spree_cm_commissioner/integrations/book_me_bus_v1/inventory/unstock_inventory.rb +188 -0
  180. data/app/services/spree_cm_commissioner/integrations/book_me_bus_v1/polling/sync_inventory.rb +79 -0
  181. data/app/services/spree_cm_commissioner/integrations/book_me_bus_v1/polling/sync_item_availability.rb +354 -0
  182. data/app/services/spree_cm_commissioner/integrations/book_me_bus_v1/polling/sync_locations.rb +103 -0
  183. data/app/services/spree_cm_commissioner/integrations/book_me_bus_v1/polling/sync_nationality_pricing.rb +93 -0
  184. data/app/services/spree_cm_commissioner/integrations/book_me_bus_v1/polling/sync_product.rb +70 -0
  185. data/app/services/spree_cm_commissioner/integrations/book_me_bus_v1/polling/sync_route_stops.rb +121 -0
  186. data/app/services/spree_cm_commissioner/integrations/book_me_bus_v1/polling/sync_routes.rb +128 -0
  187. data/app/services/spree_cm_commissioner/integrations/book_me_bus_v1/polling/sync_seat_layout.rb +412 -0
  188. data/app/services/spree_cm_commissioner/integrations/book_me_bus_v1/polling/sync_service_calendars.rb +59 -0
  189. data/app/services/spree_cm_commissioner/integrations/book_me_bus_v1/polling/sync_trip_stop.rb +224 -0
  190. data/app/services/spree_cm_commissioner/integrations/book_me_bus_v1/polling/sync_trips.rb +769 -0
  191. data/app/services/spree_cm_commissioner/integrations/book_me_bus_v1/polling/sync_variant.rb +69 -0
  192. data/app/services/spree_cm_commissioner/integrations/book_me_bus_v1/polling/sync_vehicle_types.rb +92 -0
  193. data/app/services/spree_cm_commissioner/integrations/book_me_bus_v1/polling/time_parser.rb +19 -0
  194. data/app/services/spree_cm_commissioner/integrations/book_me_bus_v1/resources/base.rb +40 -0
  195. data/app/services/spree_cm_commissioner/integrations/book_me_bus_v1/resources/inventory_item_status.rb +148 -0
  196. data/app/services/spree_cm_commissioner/integrations/book_me_bus_v1/resources/location.rb +55 -0
  197. data/app/services/spree_cm_commissioner/integrations/book_me_bus_v1/resources/reservation_cart.rb +85 -0
  198. data/app/services/spree_cm_commissioner/integrations/book_me_bus_v1/resources/seat.rb +128 -0
  199. data/app/services/spree_cm_commissioner/integrations/book_me_bus_v1/resources/seat_layout.rb +386 -0
  200. data/app/services/spree_cm_commissioner/integrations/book_me_bus_v1/resources/stop.rb +84 -0
  201. data/app/services/spree_cm_commissioner/integrations/book_me_bus_v1/resources/trip.rb +237 -0
  202. data/app/services/spree_cm_commissioner/integrations/book_me_bus_v1/resources/wallet_payment.rb +48 -0
  203. data/app/services/spree_cm_commissioner/integrations/book_me_bus_v1/sync_manager.rb +22 -0
  204. data/app/services/spree_cm_commissioner/integrations/book_me_bus_v1/sync_strategies/full_sync_strategy.rb +116 -0
  205. data/app/services/spree_cm_commissioner/integrations/book_me_bus_v1/sync_strategies/incremental_sync_strategy.rb +63 -0
  206. data/app/services/spree_cm_commissioner/integrations/larryta/external_client/client.rb +314 -0
  207. data/app/services/spree_cm_commissioner/integrations/larryta/external_client/connection.rb +74 -0
  208. data/app/services/spree_cm_commissioner/integrations/larryta/inventory/external_inventory_item_status.rb +53 -0
  209. data/app/services/spree_cm_commissioner/integrations/larryta/inventory/external_inventory_items.rb +106 -0
  210. data/app/services/spree_cm_commissioner/integrations/larryta/inventory/hold_seats.rb +143 -0
  211. data/app/services/spree_cm_commissioner/integrations/larryta/inventory/release_seats.rb +90 -0
  212. data/app/services/spree_cm_commissioner/integrations/larryta/inventory/unstock_inventory.rb +143 -0
  213. data/app/services/spree_cm_commissioner/integrations/larryta/polling/sync_item_availability.rb +325 -0
  214. data/app/services/spree_cm_commissioner/integrations/larryta/polling/sync_product.rb +72 -0
  215. data/app/services/spree_cm_commissioner/integrations/larryta/polling/sync_routes.rb +142 -0
  216. data/app/services/spree_cm_commissioner/integrations/larryta/polling/sync_seat_layout.rb +255 -0
  217. data/app/services/spree_cm_commissioner/integrations/larryta/polling/sync_stock_item.rb +55 -0
  218. data/app/services/spree_cm_commissioner/integrations/larryta/polling/sync_trip_stop.rb +171 -0
  219. data/app/services/spree_cm_commissioner/integrations/larryta/polling/sync_trips.rb +397 -0
  220. data/app/services/spree_cm_commissioner/integrations/larryta/polling/sync_variant.rb +63 -0
  221. data/app/services/spree_cm_commissioner/integrations/larryta/polling/sync_vehicle_type.rb +79 -0
  222. data/app/services/spree_cm_commissioner/integrations/larryta/resources/base.rb +39 -0
  223. data/app/services/spree_cm_commissioner/integrations/larryta/resources/booking.rb +113 -0
  224. data/app/services/spree_cm_commissioner/integrations/larryta/resources/booking_session.rb +43 -0
  225. data/app/services/spree_cm_commissioner/integrations/larryta/resources/booking_transaction.rb +236 -0
  226. data/app/services/spree_cm_commissioner/integrations/larryta/resources/direction.rb +71 -0
  227. data/app/services/spree_cm_commissioner/integrations/larryta/resources/passenger.rb +48 -0
  228. data/app/services/spree_cm_commissioner/integrations/larryta/resources/schedule.rb +150 -0
  229. data/app/services/spree_cm_commissioner/integrations/larryta/resources/seat.rb +31 -0
  230. data/app/services/spree_cm_commissioner/integrations/larryta/resources/seat_availability.rb +49 -0
  231. data/app/services/spree_cm_commissioner/integrations/larryta/resources/seat_layout.rb +211 -0
  232. data/app/services/spree_cm_commissioner/integrations/larryta/sync_manager.rb +17 -0
  233. data/app/services/spree_cm_commissioner/integrations/larryta/sync_strategies/full_sync_strategy.rb +79 -0
  234. data/app/services/spree_cm_commissioner/integrations/larryta/sync_strategies/incremental_sync_strategy.rb +58 -0
  235. data/app/services/spree_cm_commissioner/integrations/stadium_x_v1/external_client/client.rb +14 -15
  236. data/app/services/spree_cm_commissioner/integrations/stadium_x_v1/polling/sync_matches.rb +7 -4
  237. data/app/services/spree_cm_commissioner/integrations/stadium_x_v1/polling/sync_zones.rb +6 -3
  238. data/app/services/spree_cm_commissioner/integrations/stadium_x_v1/sync_manager.rb +0 -3
  239. data/app/services/spree_cm_commissioner/integrations/stadium_x_v1/sync_strategies/full_sync_strategy.rb +1 -3
  240. data/app/services/spree_cm_commissioner/integrations/stadium_x_v1/sync_strategies/incremental_sync_strategy.rb +1 -3
  241. data/app/services/spree_cm_commissioner/integrations/stadium_x_v1/sync_strategies/webhook_sync_strategy.rb +1 -2
  242. data/app/services/spree_cm_commissioner/integrations/vireak_buntham/external_client/client.rb +170 -0
  243. data/app/services/spree_cm_commissioner/integrations/vireak_buntham/external_client/connection.rb +59 -0
  244. data/app/services/spree_cm_commissioner/integrations/vireak_buntham/inventory/external_inventory_item_status.rb +48 -0
  245. data/app/services/spree_cm_commissioner/integrations/vireak_buntham/inventory/external_inventory_items.rb +92 -0
  246. data/app/services/spree_cm_commissioner/integrations/vireak_buntham/inventory/unstock_inventory.rb +230 -0
  247. data/app/services/spree_cm_commissioner/integrations/vireak_buntham/polling/sync_item_availability.rb +270 -0
  248. data/app/services/spree_cm_commissioner/integrations/vireak_buntham/polling/sync_locations.rb +96 -0
  249. data/app/services/spree_cm_commissioner/integrations/vireak_buntham/polling/sync_nationality_pricing.rb +76 -0
  250. data/app/services/spree_cm_commissioner/integrations/vireak_buntham/polling/sync_product.rb +69 -0
  251. data/app/services/spree_cm_commissioner/integrations/vireak_buntham/polling/sync_routes.rb +110 -0
  252. data/app/services/spree_cm_commissioner/integrations/vireak_buntham/polling/sync_seat_layout.rb +207 -0
  253. data/app/services/spree_cm_commissioner/integrations/vireak_buntham/polling/sync_stock_item.rb +43 -0
  254. data/app/services/spree_cm_commissioner/integrations/vireak_buntham/polling/sync_trip_stop.rb +192 -0
  255. data/app/services/spree_cm_commissioner/integrations/vireak_buntham/polling/sync_trips.rb +377 -0
  256. data/app/services/spree_cm_commissioner/integrations/vireak_buntham/polling/sync_variant.rb +56 -0
  257. data/app/services/spree_cm_commissioner/integrations/vireak_buntham/polling/sync_vehicle_type.rb +51 -0
  258. data/app/services/spree_cm_commissioner/integrations/vireak_buntham/resources/base.rb +32 -0
  259. data/app/services/spree_cm_commissioner/integrations/vireak_buntham/resources/booking.rb +48 -0
  260. data/app/services/spree_cm_commissioner/integrations/vireak_buntham/resources/booking_transaction.rb +46 -0
  261. data/app/services/spree_cm_commissioner/integrations/vireak_buntham/resources/cell.rb +53 -0
  262. data/app/services/spree_cm_commissioner/integrations/vireak_buntham/resources/confirm_booking_request.rb +43 -0
  263. data/app/services/spree_cm_commissioner/integrations/vireak_buntham/resources/location.rb +37 -0
  264. data/app/services/spree_cm_commissioner/integrations/vireak_buntham/resources/schedule.rb +138 -0
  265. data/app/services/spree_cm_commissioner/integrations/vireak_buntham/resources/seat.rb +48 -0
  266. data/app/services/spree_cm_commissioner/integrations/vireak_buntham/resources/seat_layout.rb +79 -0
  267. data/app/services/spree_cm_commissioner/integrations/vireak_buntham/sync_manager.rb +17 -0
  268. data/app/services/spree_cm_commissioner/integrations/vireak_buntham/sync_strategies/full_sync_strategy.rb +87 -0
  269. data/app/services/spree_cm_commissioner/integrations/vireak_buntham/sync_strategies/incremental_sync_strategy.rb +53 -0
  270. data/app/services/spree_cm_commissioner/inventory_holds/release.rb +13 -0
  271. data/app/services/spree_cm_commissioner/inventory_items/generate_permanent_item.rb +48 -0
  272. data/app/services/spree_cm_commissioner/order_holds/hold.rb +17 -0
  273. data/app/services/spree_cm_commissioner/order_holds/release.rb +31 -0
  274. data/app/services/spree_cm_commissioner/payment_fees/apply.rb +30 -0
  275. data/app/services/spree_cm_commissioner/payment_fees/clear.rb +15 -0
  276. data/app/services/spree_cm_commissioner/payment_fees/preview.rb +70 -0
  277. data/app/services/spree_cm_commissioner/payments/find_or_create_decorator.rb +18 -0
  278. data/app/services/spree_cm_commissioner/place_resolver.rb +95 -0
  279. data/app/services/spree_cm_commissioner/prepaid/admin_credit.rb +44 -0
  280. data/app/services/spree_cm_commissioner/prepaid/check_top_up_limits.rb +47 -0
  281. data/app/services/spree_cm_commissioner/prepaid/freeze.rb +58 -0
  282. data/app/services/spree_cm_commissioner/prepaid/mutation.rb +78 -0
  283. data/app/services/spree_cm_commissioner/prepaid/reverse_order_top_up.rb +23 -0
  284. data/app/services/spree_cm_commissioner/prepaid/reverse_top_up.rb +53 -0
  285. data/app/services/spree_cm_commissioner/prepaid/top_up.rb +68 -0
  286. data/app/services/spree_cm_commissioner/prepaid/unfreeze.rb +47 -0
  287. data/app/services/spree_cm_commissioner/prepaid_metrics.rb +37 -0
  288. data/app/services/spree_cm_commissioner/prepaid_reconciliation_service.rb +71 -0
  289. data/app/services/spree_cm_commissioner/reschedule_policies/assign.rb +52 -0
  290. data/app/services/spree_cm_commissioner/reschedule_policies/create.rb +71 -0
  291. data/app/services/spree_cm_commissioner/reschedule_policies/exclude.rb +40 -0
  292. data/app/services/spree_cm_commissioner/reschedule_policies/resolve.rb +109 -0
  293. data/app/services/spree_cm_commissioner/reschedule_policies/unassign.rb +23 -0
  294. data/app/services/spree_cm_commissioner/reschedule_policies/update.rb +73 -0
  295. data/app/services/spree_cm_commissioner/reschedule_requests/authorize_initiator.rb +79 -0
  296. data/app/services/spree_cm_commissioner/reschedule_requests/build_request.rb +317 -0
  297. data/app/services/spree_cm_commissioner/reschedule_requests/calculate_fee.rb +71 -0
  298. data/app/services/spree_cm_commissioner/reschedule_requests/cancel.rb +55 -0
  299. data/app/services/spree_cm_commissioner/reschedule_requests/check_eligibility.rb +243 -0
  300. data/app/services/spree_cm_commissioner/reschedule_requests/collect_payment.rb +204 -0
  301. data/app/services/spree_cm_commissioner/reschedule_requests/execute.rb +252 -0
  302. data/app/services/spree_cm_commissioner/reschedule_requests/expire_overdue_requests.rb +28 -0
  303. data/app/services/spree_cm_commissioner/reschedule_requests/expire_request.rb +24 -0
  304. data/app/services/spree_cm_commissioner/reschedule_requests/guests/update_seats.rb +210 -0
  305. data/app/services/spree_cm_commissioner/reschedule_requests/initiate.rb +255 -0
  306. data/app/services/spree_cm_commissioner/reschedule_requests/issue_compensation.rb +67 -0
  307. data/app/services/spree_cm_commissioner/reschedule_requests/issue_coupon.rb +90 -0
  308. data/app/services/spree_cm_commissioner/reschedule_requests/issue_store_credit.rb +16 -0
  309. data/app/services/spree_cm_commissioner/reschedule_requests/notifications/send_approval_reminder.rb +43 -0
  310. data/app/services/spree_cm_commissioner/reschedule_requests/notifications/send_payment_reminder.rb +46 -0
  311. data/app/services/spree_cm_commissioner/reschedule_requests/notifications/send_status_update.rb +42 -0
  312. data/app/services/spree_cm_commissioner/reschedule_requests/seats/hold.rb +137 -0
  313. data/app/services/spree_cm_commissioner/reschedule_requests/split_line_item.rb +86 -0
  314. data/app/services/spree_cm_commissioner/reschedule_requests/telegram/notify_operations.rb +37 -0
  315. data/app/services/spree_cm_commissioner/reschedule_requests/telegram/notify_vendor.rb +51 -0
  316. data/app/services/spree_cm_commissioner/reschedule_requests/vendors/approve.rb +61 -0
  317. data/app/services/spree_cm_commissioner/reschedule_requests/vendors/reject.rb +58 -0
  318. data/app/services/spree_cm_commissioner/topup_draft_orders/create.rb +234 -0
  319. data/app/services/spree_cm_commissioner/trips/service_calendars/create_or_update.rb +25 -14
  320. data/app/services/spree_cm_commissioner/trips/update_single_leg.rb +43 -6
  321. data/app/views/spree/admin/classifications/edit.html.erb +17 -2
  322. data/app/views/spree/admin/classifications/new.html.erb +18 -2
  323. data/app/views/spree/admin/customer_notifications/index.html.erb +45 -3
  324. data/app/views/spree/admin/integrations/_book_me_bus_v1_fields.html.erb +47 -0
  325. data/app/views/spree/admin/integrations/_form.html.erb +12 -0
  326. data/app/views/spree/admin/integrations/_larryta_fields.html.erb +58 -0
  327. data/app/views/spree/admin/integrations/_vireak_buntham_fields.html.erb +75 -0
  328. data/app/views/spree/admin/integrations/index.html.erb +1 -1
  329. data/app/views/spree/admin/payment_fee_rules/_form.html.erb +135 -0
  330. data/app/views/spree/admin/payment_fee_rules/edit.html.erb +11 -0
  331. data/app/views/spree/admin/payment_fee_rules/index.html.erb +100 -0
  332. data/app/views/spree/admin/payment_fee_rules/new.html.erb +11 -0
  333. data/app/views/spree/admin/prepaid_transactions/index.html.erb +102 -0
  334. data/app/views/spree/admin/prepaid_transactions/show.html.erb +146 -0
  335. data/app/views/spree/admin/private_sales/_form.html.erb +24 -1
  336. data/app/views/spree/admin/private_sales/_stock.html.erb +6 -1
  337. data/app/views/spree/admin/product_commissions/index.html.erb +7 -2
  338. data/app/views/spree/admin/reschedule_policies/_form.html.erb +65 -0
  339. data/app/views/spree/admin/reschedule_policies/edit.html.erb +11 -0
  340. data/app/views/spree/admin/reschedule_policies/index.html.erb +84 -0
  341. data/app/views/spree/admin/reschedule_policies/new.html.erb +11 -0
  342. data/app/views/spree/admin/reschedule_requests/_cancel_modal.html.erb +34 -0
  343. data/app/views/spree/admin/reschedule_requests/_search_form.html.erb +57 -0
  344. data/app/views/spree/admin/reschedule_requests/_tabs.html.erb +14 -0
  345. data/app/views/spree/admin/reschedule_requests/index.html.erb +64 -0
  346. data/app/views/spree/admin/reschedule_requests/show.html.erb +212 -0
  347. data/app/views/spree/admin/reschedule_requests/state_changes.html.erb +42 -0
  348. data/app/views/spree/admin/service_fee_rules/_form.html.erb +15 -9
  349. data/app/views/spree/admin/service_fee_rules/index.html.erb +1 -1
  350. data/app/views/spree/admin/shared/_products.html.erb +3 -0
  351. data/app/views/spree/admin/shared/_reschedule_policies_tabs.html.erb +22 -0
  352. data/app/views/spree/admin/shared/_ticket_transfer_details.html.erb +3 -0
  353. data/app/views/spree/admin/shared/sub_menu/_reschedule.html.erb +4 -0
  354. data/app/views/spree/admin/stock_managements/_header.html.erb +26 -0
  355. data/app/views/spree/admin/stock_managements/_variant_stock_items.html.erb +1 -1
  356. data/app/views/spree/admin/stock_managements/calendar.html.erb +285 -171
  357. data/app/views/spree/admin/stock_managements/index.html.erb +1 -18
  358. data/app/views/spree/admin/store_credit_admin/_tabs.html.erb +31 -0
  359. data/app/views/spree/admin/store_credit_admin/_tenant_selector.html.erb +17 -0
  360. data/app/views/spree/admin/store_credit_admin/show.html.erb +200 -0
  361. data/app/views/spree/admin/store_credits/index.html.erb +224 -0
  362. data/app/views/spree/admin/tenants/_form.html.erb +33 -0
  363. data/app/views/spree/admin/ticket_transfer_rules/_form.html.erb +28 -5
  364. data/config/initializers/spree_dependencies.rb +3 -0
  365. data/config/initializers/spree_permitted_attributes.rb +1 -0
  366. data/config/locales/en.yml +363 -1
  367. data/config/locales/km.yml +282 -1
  368. data/config/routes.rb +67 -2
  369. data/db/migrate/20260327143200_create_cm_audit_events.rb +21 -0
  370. data/db/migrate/20260410045815_create_cm_currency_rates.rb +23 -0
  371. data/db/migrate/20260411102139_add_metadata_to_cm_inventory_items.rb +6 -0
  372. data/db/migrate/20260506120000_create_cm_reschedule_policies.rb +38 -0
  373. data/db/migrate/20260506120001_create_cm_reschedule_requests.rb +67 -0
  374. data/db/migrate/20260506120002_add_reschedule_count_to_cm_guests.rb +5 -0
  375. data/db/migrate/20260521140000_create_cm_reschedule_snapshots.rb +38 -0
  376. data/db/migrate/20260522160000_create_cm_reschedule_policy_assignments.rb +17 -0
  377. data/db/migrate/20260722000001_create_cm_prepaid_transactions.rb +31 -0
  378. data/db/migrate/20260722000002_add_constraints_to_store_credits.rb +46 -0
  379. data/db/migrate/20260722000003_add_expires_at_to_store_credits.rb +6 -0
  380. data/db/migrate/20260722000004_add_issuer_tenant_id_to_spree_store_credits.rb +7 -0
  381. data/db/migrate/20260722000005_add_immutability_trigger_to_cm_prepaid_transactions.rb +36 -0
  382. data/db/migrate/20260918045558_add_prepaid_currency_columns_to_cm_tenants.rb +6 -0
  383. data/db/migrate/20260923090000_add_public_private_metadata_to_cm_prepaid_transactions.rb +36 -0
  384. data/db/migrate/20260928000001_add_closed_to_cm_inventory_items.rb +5 -0
  385. data/db/migrate/20261001100000_create_cm_payment_fee_rules.rb +17 -0
  386. data/db/migrate/20261001100001_add_payment_fee_total_to_spree_orders.rb +5 -0
  387. data/db/migrate/20261006073317_add_offline_to_spree_products.rb +6 -0
  388. data/lib/spree_cm_commissioner/cached_inventory_item.rb +7 -1
  389. data/lib/spree_cm_commissioner/payment_fees/payment_fee_preview.rb +24 -0
  390. data/lib/spree_cm_commissioner/reschedule_policy_rules.rb +75 -0
  391. data/lib/spree_cm_commissioner/serializable_transaction.rb +38 -0
  392. data/lib/spree_cm_commissioner/store_credit_currency_summary.rb +11 -0
  393. data/lib/spree_cm_commissioner/store_credit_summary.rb +11 -0
  394. data/lib/spree_cm_commissioner/test_helper/factories/integration_factory.rb +11 -0
  395. data/lib/spree_cm_commissioner/test_helper/factories/prepaid_transaction_factory.rb +12 -0
  396. data/lib/spree_cm_commissioner/test_helper/factories/reschedule_policy_assignment_factory.rb +7 -0
  397. data/lib/spree_cm_commissioner/test_helper/factories/reschedule_policy_factory.rb +59 -0
  398. data/lib/spree_cm_commissioner/test_helper/factories/reschedule_request_factory.rb +51 -0
  399. data/lib/spree_cm_commissioner/test_helper/factories/reschedule_snapshot_factory.rb +27 -0
  400. data/lib/spree_cm_commissioner/version.rb +1 -1
  401. data/lib/spree_cm_commissioner.rb +5 -0
  402. data/lib/tasks/prepaid_reconciliation_cron_scheduler.rake +7 -0
  403. data/lib/tasks/prepaid_setup_types.rake +7 -0
  404. metadata +295 -2
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 565fc705e24c32c2f3bd35aad88607d80d7f26b1bab3ff62dc64e2890202ed9a
4
- data.tar.gz: b6647ca2838265f710f999389157765f24ba1baf92369521aab95a711779a0d9
3
+ metadata.gz: c83a53b69842100120ae6f6edea538c4a7335fc9859e52765f4be45ad2506b0c
4
+ data.tar.gz: 61cd2bea3a4a011a385ad636c6f3330bc44e04085eb39ea22c64c96f86c59978
5
5
  SHA512:
6
- metadata.gz: 36a8d1f75f387fb33ab1d394ee91e61ce6c1179bbf2323352f1d56e3bbb303942d57af72933910e8017f184dcf0a24174a12f70ec4a69e42309cbec47fe9cdd7
7
- data.tar.gz: d7dc21e3383b9784c9b0cef12c354f4a3d4d3535fe122b666b8abdd3b4e4fea4660f321ada830e83fdb409ebd8d2d91ed9fa6295b1801371d803475334431860
6
+ metadata.gz: 18012dec394174123a5fefcb459c369aab56bf075cabb068e695ab42eac92c1f487468047d6201bee6dc2f1f8730147260ec7d4b4dbfa1b4c0f5c98c31720508
7
+ data.tar.gz: 5dae3a462226fe008f4c17d43231d315d45e24a415fd07fe8c564f61694821ba70e037b311f8f653766df763a009e00b2ccf0d8f7bbbaf712e858a50b0cbdf43
data/.env.example CHANGED
@@ -86,3 +86,8 @@ PAYMENT_INCOMPLETE_NOTIFICATION_DELAY_IN_MINUTES=5 # Delay before notifying a c
86
86
 
87
87
  # See: app/jobs/spree_cm_commissioner/telegram_alerts/order_integrity_check_job.rb
88
88
  ORDER_INTEGRITY_TELEGRAM_ALERT_ENABLED="no" # Set to yes to enable the order integrity Telegram alert job (off by default to avoid flooding Telegram rate limits during traffic spikes)
89
+
90
+ # See: app/models/concerns/spree_cm_commissioner/product_type.rb
91
+ PRE_INVENTORY_DAYS_TRANSIT=180 # How many days ahead inventory items are pre-generated for transit variants
92
+ PRE_INVENTORY_DAYS_ACCOMMODATION=365 # How many days ahead inventory items are pre-generated for accommodation variants
93
+ PRE_INVENTORY_DAYS_SERVICE=180 # How many days ahead inventory items are pre-generated for service variants
data/Gemfile.lock CHANGED
@@ -34,7 +34,7 @@ GIT
34
34
  PATH
35
35
  remote: .
36
36
  specs:
37
- spree_cm_commissioner (2.13.4)
37
+ spree_cm_commissioner (2.13.7)
38
38
  activerecord-multi-tenant
39
39
  activerecord_json_validator (~> 2.1, >= 2.1.3)
40
40
  aws-sdk-cloudfront
@@ -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
@@ -59,6 +59,17 @@ module Spree
59
59
  redirect_back fallback_location: admin_product_stock_managements_path(@product)
60
60
  end
61
61
 
62
+ # PATCH /products/:slug/inventory_items/bulk_update_closed
63
+ #
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)
71
+ end
72
+
62
73
  # PATCH /products/:slug/inventory_items/bulk_reset?inventory_item_ids=1,2,3
63
74
  def bulk_reset
64
75
  inventory_item_ids = params[:inventory_item_ids]
@@ -0,0 +1,76 @@
1
+ module Spree
2
+ module Admin
3
+ # Same CRUD as ServiceFeeRulesController (scope picker, tenant dropdown, calculator partial),
4
+ # plus the payment method a rule applies to.
5
+ class PaymentFeeRulesController < Spree::Admin::ResourceController
6
+ helper SpreeCmCommissioner::Admin::FeeRulesHelper
7
+ helper_method :selected_tenant
8
+
9
+ before_action :load_data
10
+
11
+ def index
12
+ @search = model_class.ransack(params[:q])
13
+ @status = %w[active inactive].include?(params[:status]) ? params[:status] : 'all'
14
+
15
+ scope = @search.result.includes(:payment_method, :ruleable, :calculator, :tenant)
16
+ scope = scope.where(active: true) if @status == 'active'
17
+ scope = scope.where(active: false) if @status == 'inactive'
18
+ # Same tenant semantics as ServiceFeeRulesController#index.
19
+ scope = scope.where(tenant_id: selected_tenant&.id)
20
+
21
+ @payment_fee_rules = scope.order(ruleable_type: :asc, ruleable_id: :asc, payment_method_id: :asc, currency: :asc)
22
+ .page(params[:page])
23
+ .per(25)
24
+ end
25
+
26
+ # See ServiceFeeRulesController#build_resource: tenant_id comes from the URL only.
27
+ def build_resource
28
+ model_class.new(tenant_id: params[:tenant_id].presence)
29
+ end
30
+
31
+ private
32
+
33
+ def load_data
34
+ @calculators = model_class.available_calculator_types.sort_by(&:name)
35
+ @tenants ||= SpreeCmCommissioner::Tenant.order(:name)
36
+ @payment_methods = Spree::PaymentMethod.includes(:vendor).order(:name)
37
+ @currencies = (current_store.supported_currencies_list.map(&:iso_code) | [@object&.currency]).compact.sort
38
+ end
39
+
40
+ def selected_tenant
41
+ return @selected_tenant if defined?(@selected_tenant)
42
+
43
+ @selected_tenant = params[:tenant_id].presence && SpreeCmCommissioner::Tenant.find_by(id: params[:tenant_id])
44
+ end
45
+
46
+ def model_class
47
+ SpreeCmCommissioner::PaymentFeeRule
48
+ end
49
+
50
+ def object_name
51
+ 'spree_cm_commissioner_payment_fee_rule'
52
+ end
53
+
54
+ def collection_url
55
+ admin_payment_fee_rules_url
56
+ end
57
+
58
+ def location_after_save
59
+ edit_admin_payment_fee_rule_path(@object)
60
+ end
61
+
62
+ # tenant_id is deliberately excluded -- see build_resource.
63
+ def permitted_resource_params
64
+ params.require(:spree_cm_commissioner_payment_fee_rule).permit(
65
+ :payment_method_id,
66
+ :ruleable_type,
67
+ :ruleable_id,
68
+ :active,
69
+ :currency,
70
+ :calculator_type,
71
+ calculator_attributes: %i[id preferred_percent preferred_flat_amount]
72
+ )
73
+ end
74
+ end
75
+ end
76
+ 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,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
@@ -1,6 +1,7 @@
1
1
  module Spree
2
2
  module Admin
3
3
  class ServiceFeeRulesController < Spree::Admin::ResourceController
4
+ helper SpreeCmCommissioner::Admin::FeeRulesHelper
4
5
  helper_method :selected_tenant
5
6
 
6
7
  before_action :load_data
@@ -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