spree_cm_commissioner 2.13.7 → 2.14.1

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 (232) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/test_and_build_gem.yml +5 -3
  3. data/Gemfile.lock +1 -1
  4. data/app/assets/javascripts/spree_cm_commissioner/backend.js +1 -1
  5. data/app/assets/javascripts/spree_cm_commissioner/promotion_rule_pickers.js +12 -0
  6. data/app/assets/stylesheets/spree_cm_commissioner/backend/calendar.scss +0 -39
  7. data/app/controllers/concerns/spree/admin/rates_availability_cells.rb +16 -0
  8. data/app/controllers/concerns/spree/api/v2/product_list_includes_decorator.rb +1 -0
  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/product_option_types_controller.rb +88 -0
  13. data/app/controllers/spree/admin/rate_plans_controller.rb +125 -0
  14. data/app/controllers/spree/admin/rates_availabilities_controller.rb +142 -0
  15. data/app/controllers/spree/admin/stock_managements_controller.rb +1 -53
  16. data/app/controllers/spree/api/switch_cm/base_controller.rb +109 -0
  17. data/app/controllers/spree/api/switch_cm/tokens_controller.rb +68 -0
  18. data/app/controllers/spree/api/switch_cm/xml/availabilities_controller.rb +39 -0
  19. data/app/controllers/spree/api/switch_cm/xml/base_controller.rb +37 -0
  20. data/app/controllers/spree/api/switch_cm/xml/reservation_summaries_controller.rb +28 -0
  21. data/app/controllers/spree/api/switch_cm/xml/reservations_controller.rb +57 -0
  22. data/app/controllers/spree/api/switch_cm/xml/room_rate_availabilities_controller.rb +56 -0
  23. data/app/controllers/spree/api/switch_cm/xml/room_rates_controller.rb +45 -0
  24. data/app/controllers/spree/api/v2/operator/guests_controller.rb +1 -1
  25. data/app/controllers/spree/api/v2/platform/homepage_section_relatable_options_controller.rb +1 -0
  26. data/app/controllers/spree/api/v2/storefront/accommodations/variants_controller.rb +31 -5
  27. data/app/controllers/spree/api/v2/storefront/inventory_items_controller.rb +2 -2
  28. data/app/controllers/spree/api/v2/tenant/intercity_taxi/draft_orders_controller.rb +0 -1
  29. data/app/factory/spree_cm_commissioner/order_telegram_message_factory.rb +1 -1
  30. data/app/finders/spree_cm_commissioner/accommodations/find.rb +10 -8
  31. data/app/finders/spree_cm_commissioner/accommodations/find_variant.rb +5 -11
  32. data/app/finders/spree_cm_commissioner/rates_availabilities/find_bookings.rb +116 -0
  33. data/app/helpers/spree_cm_commissioner/admin/inventory_items_helper.rb +36 -0
  34. data/app/helpers/spree_cm_commissioner/admin/rates_availabilities_helper.rb +247 -0
  35. data/app/interactors/spree_cm_commissioner/intercity_taxi_order_creator.rb +0 -1
  36. data/app/jobs/spree_cm_commissioner/option_type_variants_public_metadata_updater_job.rb +1 -0
  37. data/app/jobs/spree_cm_commissioner/option_value_variants_public_metadata_updater_job.rb +1 -0
  38. data/app/jobs/spree_cm_commissioner/product_variants_public_metadata_updater_job.rb +12 -0
  39. data/app/models/concerns/spree_cm_commissioner/kyc_bitwise.rb +4 -0
  40. data/app/models/concerns/spree_cm_commissioner/line_item_durationable.rb +1 -10
  41. data/app/models/concerns/spree_cm_commissioner/line_item_guests_concern.rb +33 -3
  42. data/app/models/concerns/spree_cm_commissioner/line_item_rate_plannable.rb +55 -0
  43. data/app/models/concerns/spree_cm_commissioner/line_item_transitable.rb +0 -16
  44. data/app/models/concerns/spree_cm_commissioner/option_type_attr_type.rb +22 -4
  45. data/app/models/concerns/spree_cm_commissioner/rate_plan_options_concern.rb +62 -0
  46. data/app/models/concerns/spree_cm_commissioner/scoped_fee_rule.rb +1 -6
  47. data/app/models/concerns/spree_cm_commissioner/variant_options_concern.rb +12 -0
  48. data/app/models/concerns/spree_cm_commissioner/variant_rate_plannable.rb +81 -0
  49. data/app/models/spree_cm_commissioner/address_decorator.rb +2 -0
  50. data/app/models/spree_cm_commissioner/configuration.rb +0 -6
  51. data/app/models/spree_cm_commissioner/exports/import_order_guests_csv.rb +2 -1
  52. data/app/models/spree_cm_commissioner/exports/operator_guest_json_gzip.rb +8 -2
  53. data/app/models/spree_cm_commissioner/guest.rb +1 -1
  54. data/app/models/spree_cm_commissioner/integration.rb +15 -0
  55. data/app/models/spree_cm_commissioner/integrations/switch_cm.rb +96 -0
  56. data/app/models/spree_cm_commissioner/inventory_item.rb +32 -3
  57. data/app/models/spree_cm_commissioner/line_item_decorator.rb +7 -7
  58. data/app/models/spree_cm_commissioner/maintenance_tasks/cache_invalidation.rb +20 -4
  59. data/app/models/spree_cm_commissioner/maintenance_tasks/event.rb +3 -2
  60. data/app/models/spree_cm_commissioner/option_type_decorator.rb +2 -1
  61. data/app/models/spree_cm_commissioner/option_value_decorator.rb +3 -0
  62. data/app/models/spree_cm_commissioner/option_value_rate_plan.rb +12 -0
  63. data/app/models/spree_cm_commissioner/order_decorator.rb +1 -0
  64. data/app/models/spree_cm_commissioner/price_decorator.rb +13 -0
  65. data/app/models/spree_cm_commissioner/product_decorator.rb +23 -2
  66. data/app/models/spree_cm_commissioner/product_option_type_decorator.rb +42 -0
  67. data/app/models/spree_cm_commissioner/rate_plan.rb +139 -0
  68. data/app/models/spree_cm_commissioner/rate_plan_options.rb +36 -0
  69. data/app/models/spree_cm_commissioner/rate_restriction.rb +45 -0
  70. data/app/models/spree_cm_commissioner/relatable_option_label.rb +20 -0
  71. data/app/models/spree_cm_commissioner/taxon_decorator.rb +5 -2
  72. data/app/models/spree_cm_commissioner/variant_decorator.rb +36 -5
  73. data/app/models/spree_cm_commissioner/variant_options.rb +3 -2
  74. data/app/models/spree_cm_commissioner/vendor_decorator.rb +16 -0
  75. data/app/overrides/spree/admin/option_types/_form/checkbox.html.erb.deface +0 -8
  76. data/app/overrides/spree/admin/option_types/_form/name.html.erb.deface +1 -1
  77. data/app/overrides/spree/admin/products/_filters/vendor_filter.html.erb.deface +6 -0
  78. data/app/overrides/spree/admin/shared/_product_tabs/rates_availabilities.html.erb.deface +8 -0
  79. data/app/overrides/spree/admin/shared/_product_tabs/stock_managements.html.erb.deface +1 -1
  80. data/app/overrides/spree/admin/variants/index/discontinued_badge.html.erb.deface +13 -0
  81. data/app/overrides/spree/admin/variants/index/option_types_empty_state_link.html.erb.deface +3 -0
  82. data/app/overrides/spree/admin/variants/index/option_types_summary.html.erb.deface +24 -0
  83. data/app/presenters/spree/variants/visible_options_presenter.rb +34 -1
  84. data/app/queries/spree_cm_commissioner/multi_leg_trips_query.rb +40 -22
  85. data/app/queries/spree_cm_commissioner/single_leg_trips_query.rb +28 -14
  86. data/app/request_schemas/spree_cm_commissioner/intercity_taxi_draft_order_update_schema.rb +0 -1
  87. data/app/serializers/spree/v2/storefront/line_item_serializer_decorator.rb +2 -0
  88. data/app/serializers/spree/v2/storefront/option_type_serializer_decorator.rb +1 -1
  89. data/app/serializers/spree/v2/storefront/product_option_type_serializer.rb +15 -0
  90. data/app/serializers/spree/v2/storefront/product_serializer_decorator.rb +15 -0
  91. data/app/serializers/spree/v2/tenant/line_item_serializer.rb +1 -1
  92. data/app/serializers/spree/v2/tenant/option_type_serializer.rb +1 -1
  93. data/app/serializers/spree_cm_commissioner/api/v2/platform/homepage_section_relatable_options_serializer.rb +2 -2
  94. data/app/serializers/spree_cm_commissioner/v2/operator/dynamic_field_option_serializer.rb +9 -0
  95. data/app/serializers/spree_cm_commissioner/v2/operator/dynamic_field_serializer.rb +11 -0
  96. data/app/serializers/spree_cm_commissioner/v2/operator/guest_dynamic_field_serializer.rb +14 -0
  97. data/app/serializers/spree_cm_commissioner/v2/operator/guest_serializer.rb +1 -0
  98. data/app/serializers/spree_cm_commissioner/v2/storefront/accommodation_variant_serializer.rb +44 -0
  99. data/app/serializers/spree_cm_commissioner/v2/storefront/inventory_item_serializer.rb +4 -4
  100. data/app/serializers/spree_cm_commissioner/v2/storefront/rate_plan_serializer.rb +16 -0
  101. data/app/services/spree_cm_commissioner/integrations/book_me_bus_v1/external_client/client.rb +11 -0
  102. data/app/services/spree_cm_commissioner/integrations/book_me_bus_v1/inventory/external_inventory_items.rb +16 -3
  103. data/app/services/spree_cm_commissioner/integrations/book_me_bus_v1/inventory/unstock_inventory.rb +35 -5
  104. data/app/services/spree_cm_commissioner/integrations/book_me_bus_v1/polling/sync_item_availability.rb +1 -1
  105. data/app/services/spree_cm_commissioner/integrations/book_me_bus_v1/polling/sync_locations.rb +39 -7
  106. data/app/services/spree_cm_commissioner/integrations/book_me_bus_v1/polling/sync_product.rb +1 -1
  107. data/app/services/spree_cm_commissioner/integrations/book_me_bus_v1/polling/sync_routes.rb +31 -13
  108. data/app/services/spree_cm_commissioner/integrations/book_me_bus_v1/polling/sync_seat_layout.rb +20 -2
  109. data/app/services/spree_cm_commissioner/integrations/book_me_bus_v1/polling/sync_service_calendars.rb +18 -10
  110. data/app/services/spree_cm_commissioner/integrations/book_me_bus_v1/polling/sync_trip_stop.rb +7 -2
  111. data/app/services/spree_cm_commissioner/integrations/book_me_bus_v1/polling/sync_variant.rb +9 -2
  112. data/app/services/spree_cm_commissioner/integrations/book_me_bus_v1/polling/sync_vehicle_types.rb +12 -1
  113. data/app/services/spree_cm_commissioner/integrations/book_me_bus_v1/resources/trip.rb +8 -1
  114. data/app/services/spree_cm_commissioner/integrations/book_me_bus_v1/resources/trip_location.rb +32 -0
  115. data/app/services/spree_cm_commissioner/integrations/book_me_bus_v1/sync_strategies/full_sync_strategy.rb +2 -1
  116. data/app/services/spree_cm_commissioner/integrations/larryta/polling/sync_item_availability.rb +1 -1
  117. data/app/services/spree_cm_commissioner/integrations/switch_cm/inventory/accept_push.rb +111 -0
  118. data/app/services/spree_cm_commissioner/integrations/switch_cm/inventory/accommodation_lock.rb +43 -0
  119. data/app/services/spree_cm_commissioner/integrations/switch_cm/inventory/apply_updates.rb +133 -0
  120. data/app/services/spree_cm_commissioner/integrations/switch_cm/inventory/availability_snapshot.rb +127 -0
  121. data/app/services/spree_cm_commissioner/integrations/switch_cm/inventory/inventory_update.rb +35 -0
  122. data/app/services/spree_cm_commissioner/integrations/switch_cm/inventory/pushed_nights.rb +82 -0
  123. data/app/services/spree_cm_commissioner/integrations/switch_cm/inventory/unseen_room_nights.rb +49 -0
  124. data/app/services/spree_cm_commissioner/integrations/switch_cm/inventory/validate_updates.rb +153 -0
  125. data/app/services/spree_cm_commissioner/integrations/switch_cm/reservations/feed.rb +209 -0
  126. data/app/services/spree_cm_commissioner/integrations/switch_cm/reservations/mark_seen.rb +62 -0
  127. data/app/services/spree_cm_commissioner/integrations/switch_cm/reservations/reservation.rb +171 -0
  128. data/app/services/spree_cm_commissioner/integrations/switch_cm/room_catalog.rb +88 -0
  129. data/app/services/spree_cm_commissioner/integrations/switch_cm/sync_manager.rb +16 -0
  130. data/app/services/spree_cm_commissioner/integrations/switch_cm/sync_strategies/full_sync_strategy.rb +39 -0
  131. data/app/services/spree_cm_commissioner/integrations/switch_cm/sync_strategies/incremental_sync_strategy.rb +51 -0
  132. data/app/services/spree_cm_commissioner/integrations/switch_cm/xml/availability_parser.rb +103 -0
  133. data/app/services/spree_cm_commissioner/integrations/switch_cm/xml/push_response.rb +45 -0
  134. data/app/services/spree_cm_commissioner/integrations/switch_cm/xml/reservation_serializer.rb +83 -0
  135. data/app/services/spree_cm_commissioner/integrations/switch_cm/xml/response_builder.rb +19 -0
  136. data/app/services/spree_cm_commissioner/integrations/vireak_buntham/polling/sync_item_availability.rb +1 -1
  137. data/app/services/spree_cm_commissioner/intercity_taxi_order/update.rb +1 -3
  138. data/app/services/spree_cm_commissioner/inventory_items/bulk_adjust_quantities_by_variant.rb +4 -0
  139. data/app/services/spree_cm_commissioner/inventory_items/reset.rb +4 -0
  140. data/app/services/spree_cm_commissioner/rate_plans/save.rb +65 -0
  141. data/app/services/spree_cm_commissioner/rates_availabilities/bulk_update.rb +225 -0
  142. data/app/services/spree_cm_commissioner/reschedule_requests/split_line_item.rb +5 -0
  143. data/app/services/spree_cm_commissioner/room_types/create.rb +94 -0
  144. data/app/services/spree_cm_commissioner/room_types/update_properties.rb +32 -0
  145. data/app/services/spree_cm_commissioner/room_types/update_room_variant.rb +22 -0
  146. data/app/services/spree_cm_commissioner/room_types.rb +49 -0
  147. data/app/services/spree_cm_commissioner/route_prices/update_price_range.rb +3 -1
  148. data/app/services/spree_cm_commissioner/trips/search.rb +14 -7
  149. data/app/services/spree_cm_commissioner/trips/update_single_leg.rb +5 -1
  150. data/app/views/spree/admin/guests/_check_in_status.html.erb +28 -22
  151. data/app/views/spree/admin/guests/edit.html.erb +29 -26
  152. data/app/views/spree/admin/integrations/_form.html.erb +4 -0
  153. data/app/views/spree/admin/integrations/_switch_cm_fields.html.erb +53 -0
  154. data/app/views/spree/admin/inventory_items/_details.html.erb +56 -0
  155. data/app/views/spree/admin/product_option_types/index.html.erb +79 -0
  156. data/app/views/spree/admin/promotions/rules/_product.html.erb +25 -0
  157. data/app/views/spree/admin/promotions/rules/_user.html.erb +21 -0
  158. data/app/views/spree/admin/promotions/rules/_vendors.html.erb +14 -1
  159. data/app/views/spree/admin/rate_plans/_form.html.erb +134 -0
  160. data/app/views/spree/admin/rate_plans/_order_form.html.erb +37 -0
  161. data/app/views/spree/admin/rates_availabilities/_block.html.erb +34 -0
  162. data/app/views/spree/admin/rates_availabilities/_bookings.html.erb +52 -0
  163. data/app/views/spree/admin/rates_availabilities/_filters.html.erb +48 -0
  164. data/app/views/spree/admin/rates_availabilities/_legend.html.erb +27 -0
  165. data/app/views/spree/admin/rates_availabilities/_list_pages.html.erb +14 -0
  166. data/app/views/spree/admin/rates_availabilities/_modal_header.html.erb +11 -0
  167. data/app/views/spree/admin/rates_availabilities/_names.html.erb +62 -0
  168. data/app/views/spree/admin/rates_availabilities/_script.html.erb +676 -0
  169. data/app/views/spree/admin/rates_availabilities/_styles.html.erb +159 -0
  170. data/app/views/spree/admin/rates_availabilities/index.html.erb +112 -0
  171. data/app/views/spree/admin/shared/sub_menu/_homepage.html.erb +0 -1
  172. data/app/views/spree/admin/stock_managements/_header.html.erb +0 -14
  173. data/app/views/spree/admin/stock_managements/_inventory_item_stats.html.erb +28 -0
  174. data/app/views/spree/admin/stock_managements/index.html.erb +1 -56
  175. data/config/initializers/spree_permitted_attributes.rb +1 -0
  176. data/config/locales/en.yml +246 -3
  177. data/config/locales/km.yml +246 -4
  178. data/config/routes.rb +36 -17
  179. data/db/migrate/20260928000000_add_oauth_application_id_to_cm_integrations.rb +5 -0
  180. data/db/migrate/20260929000000_create_cm_rate_plans.rb +22 -0
  181. data/db/migrate/20260929000001_add_rate_plan_to_spree_prices.rb +24 -0
  182. data/db/migrate/20260929000002_add_rate_plan_to_spree_line_items.rb +7 -0
  183. data/db/migrate/20260929000003_create_cm_option_value_rate_plans.rb +16 -0
  184. data/db/migrate/20261009000000_create_cm_rate_restrictions.rb +29 -0
  185. data/db/migrate/20261013000001_add_hidden_and_presentation_to_spree_product_option_types.rb +6 -0
  186. data/db/migrate/20261013000002_move_hidden_from_spree_option_types_to_product_option_types.rb +36 -0
  187. data/db/migrate/20261016000001_add_index_to_spree_line_items_number.rb +8 -0
  188. data/lib/spree_cm_commissioner/booking_dates.rb +43 -0
  189. data/lib/spree_cm_commissioner/calendar_event.rb +0 -25
  190. data/lib/spree_cm_commissioner/rate.rb +74 -0
  191. data/lib/spree_cm_commissioner/rate_closings.rb +48 -0
  192. data/lib/spree_cm_commissioner/rates_availability_grid.rb +113 -0
  193. data/lib/spree_cm_commissioner/stay_price.rb +30 -0
  194. data/lib/spree_cm_commissioner/stay_prices.rb +58 -0
  195. data/lib/spree_cm_commissioner/test_helper/factories/integration_factory.rb +6 -0
  196. data/lib/spree_cm_commissioner/test_helper/factories/option_type_factory.rb +6 -0
  197. data/lib/spree_cm_commissioner/test_helper/factories/option_value_rate_plan_factory.rb +6 -0
  198. data/lib/spree_cm_commissioner/test_helper/factories/rate_plan_factory.rb +14 -0
  199. data/lib/spree_cm_commissioner/test_helper/factories/variant_factory.rb +9 -0
  200. data/lib/spree_cm_commissioner/version.rb +1 -1
  201. data/lib/spree_cm_commissioner.rb +6 -0
  202. metadata +100 -32
  203. data/app/assets/javascripts/spree_cm_commissioner/calendar.js +0 -59
  204. data/app/controllers/spree/admin/calendars/base_controller.rb +0 -27
  205. data/app/controllers/spree/admin/calendars/orders_controller.rb +0 -27
  206. data/app/controllers/spree/admin/homepage_feed_controller.rb +0 -36
  207. data/app/controllers/spree/admin/metafields_controller.rb +0 -69
  208. data/app/models/concerns/spree_cm_commissioner/metafield.rb +0 -15
  209. data/app/models/spree_cm_commissioner/homepage_feed.rb +0 -4
  210. data/app/overrides/spree/admin/shared/_main_menu/sidebar_calendars.html.erb.deface +0 -5
  211. data/app/overrides/spree/admin/shared/_product_tabs/metafields.html.erb.deface +0 -15
  212. data/app/serializers/spree_cm_commissioner/v2/storefront/taxon_include_product_serializer.rb +0 -13
  213. data/app/serializers/spree_cm_commissioner/v2/storefront/vendor_include_product_serializer.rb +0 -14
  214. data/app/services/spree_cm_commissioner/feed/taxon_include_product.rb +0 -20
  215. data/app/services/spree_cm_commissioner/feed/taxon_product.rb +0 -54
  216. data/app/services/spree_cm_commissioner/feed/vendor_include_product.rb +0 -20
  217. data/app/services/spree_cm_commissioner/feed/vendor_product.rb +0 -52
  218. data/app/services/spree_cm_commissioner/homepage_data_loader.rb +0 -112
  219. data/app/services/spree_cm_commissioner/metafields/product_metadata_service.rb +0 -32
  220. data/app/views/shared/_calendar.html.erb +0 -27
  221. data/app/views/shared/calendar/_order_popover.html.erb +0 -18
  222. data/app/views/shared/calendar/_orders_sidebar.html.erb +0 -41
  223. data/app/views/spree/admin/calendars/orders/index.html.erb +0 -22
  224. data/app/views/spree/admin/homepage_feed/_form.html.erb +0 -24
  225. data/app/views/spree/admin/homepage_feed/edit.html.erb +0 -17
  226. data/app/views/spree/admin/inventory_items/prices.html.erb +0 -43
  227. data/app/views/spree/admin/inventory_items/stocks.html.erb +0 -34
  228. data/app/views/spree/admin/metafields/_private_metafields_form.html.erb +0 -71
  229. data/app/views/spree/admin/metafields/_public_metafields_form.html.erb +0 -64
  230. data/app/views/spree/admin/metafields/edit.html.erb +0 -27
  231. data/app/views/spree/admin/shared/sub_menu/_calendar.html.erb +0 -3
  232. 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: c83a53b69842100120ae6f6edea538c4a7335fc9859e52765f4be45ad2506b0c
4
- data.tar.gz: 61cd2bea3a4a011a385ad636c6f3330bc44e04085eb39ea22c64c96f86c59978
3
+ metadata.gz: 2d7734080da716bf13a816acbd174d75272acdbbf4fd5a257d5e6c535eb7cece
4
+ data.tar.gz: 310a647df40f1c0d6f0cab1a3f676d7f558e07d0a45108addd7a1d552ebd4083
5
5
  SHA512:
6
- metadata.gz: 18012dec394174123a5fefcb459c369aab56bf075cabb068e695ab42eac92c1f487468047d6201bee6dc2f1f8730147260ec7d4b4dbfa1b4c0f5c98c31720508
7
- data.tar.gz: 5dae3a462226fe008f4c17d43231d315d45e24a415fd07fe8c564f61694821ba70e037b311f8f653766df763a009e00b2ccf0d8f7bbbaf712e858a50b0cbdf43
6
+ metadata.gz: efb1b8f98edd7bb4a815086717aad5722a2e22ca6dd7c9472df0aa18ce1259cbd26674c45a3b0e8a064ff69e194f6b650bfded02528d7ef6078c74e62aad0fee
7
+ data.tar.gz: e578ec7e879cfade2a629c9a452283689942890aae8ad5b82b81bb67dba4f3155aaba401f76d77ae603435614e90a610293f2e1c9fde942627dab76bec5f6241
@@ -117,7 +117,9 @@ jobs:
117
117
 
118
118
  setup:
119
119
  needs: [validate-commits]
120
- runs-on: ubuntu-latest
120
+ # Pinned (also test/build_and_publish): newer images only ship the latest 3.2.x patch, and
121
+ # production runs exactly ruby:3.2.0 (cm-market-server Dockerfile-app).
122
+ runs-on: ubuntu-24.04
121
123
 
122
124
  services:
123
125
  db:
@@ -170,7 +172,7 @@ jobs:
170
172
 
171
173
  test:
172
174
  needs: [setup]
173
- runs-on: ubuntu-latest
175
+ runs-on: ubuntu-24.04
174
176
  if: github.event_name == 'pull_request'
175
177
 
176
178
  strategy:
@@ -279,7 +281,7 @@ jobs:
279
281
 
280
282
  build_and_publish:
281
283
  needs: [setup]
282
- runs-on: ubuntu-latest
284
+ runs-on: ubuntu-24.04
283
285
  if: startsWith(github.ref, 'refs/tags/')
284
286
 
285
287
  steps:
data/Gemfile.lock CHANGED
@@ -34,7 +34,7 @@ GIT
34
34
  PATH
35
35
  remote: .
36
36
  specs:
37
- spree_cm_commissioner (2.13.7)
37
+ spree_cm_commissioner (2.14.1)
38
38
  activerecord-multi-tenant
39
39
  activerecord_json_validator (~> 2.1, >= 2.1.3)
40
40
  aws-sdk-cloudfront
@@ -1,7 +1,7 @@
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
7
+ //= require spree_cm_commissioner/promotion_rule_pickers
@@ -0,0 +1,12 @@
1
+ // Our product/user/vendors promotion rule partials use select2autocomplete, which spree_backend only
2
+ // initializes on spree:load. "Add rule" is a remote form whose create.js.erb appends the new rule and
3
+ // re-inits only the core product_picker/user_picker, so without this the new rule's picker stays a
4
+ // plain, empty select until the page is reloaded.
5
+ //
6
+ // jquery_ujs fires ajax:complete as a jQuery event (not a DOM one), after the JS response has run.
7
+ // Delegated from document so Turbo page loads don't stack duplicate listeners.
8
+ $(document).on("ajax:complete", "#new_product_rule_form", function () {
9
+ $("#rules select[data-autocomplete-url-value]:not(.select2-hidden-accessible)").each(function () {
10
+ buildParamsFromDataAttrs(this);
11
+ });
12
+ });
@@ -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: []
@@ -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,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