spree_cm_commissioner 1.21.0 → 2.0.0.pre.pre

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 (241) hide show
  1. checksums.yaml +4 -4
  2. data/.env.example +0 -3
  3. data/.github/workflows/test_and_build_gem.yml +16 -2
  4. data/.gitignore +2 -1
  5. data/Gemfile.lock +28 -1
  6. data/Rakefile +33 -4
  7. data/app/assets/javascripts/spree_cm_commissioner/backend.js +0 -1
  8. data/app/assets/stylesheets/spree_cm_commissioner/backend/calendar.scss +8 -11
  9. data/app/assets/stylesheets/spree_cm_commissioner/backend/commissioner_admin.css.scss +0 -1
  10. data/app/controllers/concerns/spree_cm_commissioner/content_cachable.rb +1 -2
  11. data/app/controllers/spree/admin/event_blazer_queries_controller.rb +4 -11
  12. data/app/controllers/spree/admin/inventory_items_controller.rb +83 -0
  13. data/app/controllers/spree/admin/stock_managements_controller.rb +63 -1
  14. data/app/controllers/spree/admin/tenants_controller.rb +0 -8
  15. data/app/controllers/spree/api/v2/organizer/invite_guests_controller.rb +1 -1
  16. data/app/controllers/spree/api/v2/storefront/accommodations/variants_controller.rb +42 -0
  17. data/app/controllers/spree/api/v2/storefront/accommodations_controller.rb +14 -31
  18. data/app/controllers/spree/api/v2/storefront/dynamic_field_options_controller.rb +1 -5
  19. data/app/controllers/spree/api/v2/storefront/guests_controller.rb +5 -31
  20. data/app/controllers/spree/api/v2/storefront/queue_cart/line_items_controller.rb +2 -2
  21. data/app/controllers/spree/api/v2/tenant/checkout_controller.rb +0 -2
  22. data/app/controllers/spree_cm_commissioner/admin/variants_controller_decorator.rb +1 -1
  23. data/app/controllers/spree_cm_commissioner/api/v2/storefront/checkout_controller_decorator.rb +0 -11
  24. data/app/controllers/spree_cm_commissioner/well_known_controller.rb +17 -31
  25. data/app/factory/spree_cm_commissioner/invite_guest_claimed_telegram_message_factory.rb +84 -37
  26. data/app/finders/spree_cm_commissioner/accommodations/find.rb +37 -0
  27. data/app/finders/spree_cm_commissioner/accommodations/find_variant.rb +32 -0
  28. data/app/interactors/spree_cm_commissioner/ensure_correct_product_type.rb +40 -0
  29. data/app/interactors/spree_cm_commissioner/inventory_item_syncer.rb +25 -0
  30. data/app/interactors/spree_cm_commissioner/pin_code_sender.rb +1 -0
  31. data/app/interactors/spree_cm_commissioner/sms.rb +1 -1
  32. data/app/interactors/spree_cm_commissioner/stock/inventory_item_resetter.rb +44 -0
  33. data/app/interactors/spree_cm_commissioner/stock/inventory_items_adjuster.rb +13 -0
  34. data/app/interactors/spree_cm_commissioner/stock/inventory_items_generator.rb +15 -0
  35. data/app/interactors/spree_cm_commissioner/stock/permanent_inventory_items_generator.rb +75 -0
  36. data/app/interactors/spree_cm_commissioner/stock/stock_movement_creator.rb +32 -0
  37. data/app/interactors/spree_cm_commissioner/user_id_token_authenticator.rb +3 -7
  38. data/app/interactors/spree_cm_commissioner/user_id_token_checker.rb +3 -11
  39. data/app/interactors/spree_cm_commissioner/user_identity_checker.rb +6 -12
  40. data/app/interactors/spree_cm_commissioner/user_registration_with_id_token.rb +1 -7
  41. data/app/interactors/spree_cm_commissioner/vattanac_bank_initiator.rb +1 -5
  42. data/app/jobs/spree_cm_commissioner/application_job.rb +20 -0
  43. data/app/jobs/spree_cm_commissioner/application_unique_job.rb +20 -0
  44. data/app/jobs/spree_cm_commissioner/ensure_correct_product_type_job.rb +7 -0
  45. data/app/jobs/spree_cm_commissioner/inventory_item_syncer_job.rb +7 -0
  46. data/app/jobs/spree_cm_commissioner/sms_pin_code_job.rb +1 -1
  47. data/app/jobs/spree_cm_commissioner/stock/inventory_items_adjuster_job.rb +11 -0
  48. data/app/jobs/spree_cm_commissioner/stock/inventory_items_generator_job.rb +11 -0
  49. data/app/jobs/spree_cm_commissioner/stock/permanent_inventory_items_generator_job.rb +9 -0
  50. data/app/models/concerns/spree_cm_commissioner/line_item_durationable.rb +9 -15
  51. data/app/models/concerns/spree_cm_commissioner/option_type_attr_type.rb +1 -13
  52. data/app/models/concerns/spree_cm_commissioner/order_seatable.rb +44 -0
  53. data/app/models/concerns/spree_cm_commissioner/order_state_machine.rb +39 -0
  54. data/app/models/concerns/spree_cm_commissioner/product_delegation.rb +1 -3
  55. data/app/models/concerns/spree_cm_commissioner/product_type.rb +10 -0
  56. data/app/models/concerns/spree_cm_commissioner/taxon_kind.rb +1 -1
  57. data/app/models/concerns/spree_cm_commissioner/tenant_preference.rb +0 -4
  58. data/app/models/spree_cm_commissioner/block.rb +23 -0
  59. data/app/models/spree_cm_commissioner/dynamic_field.rb +3 -17
  60. data/app/models/spree_cm_commissioner/dynamic_field_option.rb +1 -7
  61. data/app/models/spree_cm_commissioner/guest.rb +19 -1
  62. data/app/models/spree_cm_commissioner/guest_dynamic_field.rb +3 -21
  63. data/app/models/spree_cm_commissioner/inventory.rb +11 -0
  64. data/app/models/spree_cm_commissioner/inventory_item.rb +69 -0
  65. data/app/models/spree_cm_commissioner/invite.rb +6 -4
  66. data/app/models/spree_cm_commissioner/invite_team.rb +1 -3
  67. data/app/models/spree_cm_commissioner/line_item_decorator.rb +46 -78
  68. data/app/models/spree_cm_commissioner/notification_taxon.rb +1 -1
  69. data/app/models/spree_cm_commissioner/option_type_decorator.rb +1 -11
  70. data/app/models/spree_cm_commissioner/order_decorator.rb +30 -1
  71. data/app/models/spree_cm_commissioner/pin_code.rb +2 -3
  72. data/app/models/spree_cm_commissioner/place.rb +1 -3
  73. data/app/models/spree_cm_commissioner/price_decorator.rb +9 -0
  74. data/app/models/spree_cm_commissioner/product_decorator.rb +10 -4
  75. data/app/models/spree_cm_commissioner/redis_stock/cached_inventory_items_builder.rb +41 -0
  76. data/app/models/spree_cm_commissioner/redis_stock/inventory_updater.rb +126 -0
  77. data/app/models/spree_cm_commissioner/redis_stock/line_items_cached_inventory_items_builder.rb +36 -0
  78. data/app/models/spree_cm_commissioner/redis_stock/variant_cached_inventory_items_builder.rb +25 -0
  79. data/app/models/spree_cm_commissioner/reserved_block.rb +30 -0
  80. data/app/models/spree_cm_commissioner/seat_layout.rb +20 -0
  81. data/app/models/spree_cm_commissioner/seat_section.rb +16 -0
  82. data/app/models/spree_cm_commissioner/seats/blocks_canceler.rb +30 -0
  83. data/app/models/spree_cm_commissioner/seats/blocks_holder.rb +53 -0
  84. data/app/models/spree_cm_commissioner/seats/blocks_reserver.rb +49 -0
  85. data/app/models/spree_cm_commissioner/seats/errors/blocks_are_on_hold_by_other_guest.rb +4 -0
  86. data/app/models/spree_cm_commissioner/seats/errors/blocks_are_reserved_by_other_guest.rb +4 -0
  87. data/app/models/spree_cm_commissioner/seats/errors/blocks_are_reserved_by_same_guest.rb +4 -0
  88. data/app/models/spree_cm_commissioner/seats/errors/unable_to_save_reserved_block_record.rb +4 -0
  89. data/app/models/spree_cm_commissioner/service_calendar.rb +0 -2
  90. data/app/models/spree_cm_commissioner/state_decorator.rb +0 -1
  91. data/app/models/spree_cm_commissioner/stock/availability_checker.rb +26 -25
  92. data/app/models/spree_cm_commissioner/stock/availability_validator_decorator.rb +2 -1
  93. data/app/models/spree_cm_commissioner/stock/line_item_availability_checker.rb +3 -3
  94. data/app/models/spree_cm_commissioner/stock/order_availability_checker.rb +44 -0
  95. data/app/models/spree_cm_commissioner/stock_item_decorator.rb +17 -0
  96. data/app/models/spree_cm_commissioner/taxon_blazer_query.rb +8 -0
  97. data/app/models/spree_cm_commissioner/taxon_decorator.rb +2 -6
  98. data/app/models/spree_cm_commissioner/taxonomy_decorator.rb +0 -6
  99. data/app/models/spree_cm_commissioner/trip.rb +12 -10
  100. data/app/models/spree_cm_commissioner/trip_connection.rb +5 -5
  101. data/app/models/spree_cm_commissioner/trip_stop.rb +2 -3
  102. data/app/models/spree_cm_commissioner/user_identity_provider.rb +4 -26
  103. data/app/models/spree_cm_commissioner/user_taxon.rb +0 -1
  104. data/app/models/spree_cm_commissioner/variant_block.rb +9 -0
  105. data/app/models/spree_cm_commissioner/variant_decorator.rb +36 -47
  106. data/app/models/spree_cm_commissioner/variant_options.rb +0 -23
  107. data/app/models/spree_cm_commissioner/vehicle.rb +9 -14
  108. data/app/models/spree_cm_commissioner/vendor_decorator.rb +7 -10
  109. data/app/models/spree_cm_commissioner/vendor_place.rb +6 -5
  110. data/app/models/spree_cm_commissioner/vendor_stop.rb +1 -2
  111. data/app/overrides/spree/admin/variants/_form/kyc_field.html.erb.deface +2 -2
  112. data/app/queries/spree_cm_commissioner/guest_searcher_query.rb +3 -45
  113. data/app/queries/spree_cm_commissioner/trip_query.rb +23 -26
  114. data/app/request_schemas/spree_cm_commissioner/accommodation_request_schema.rb +3 -0
  115. data/app/request_schemas/spree_cm_commissioner/application_request_schema.rb +1 -1
  116. data/app/request_schemas/spree_cm_commissioner/variant_request_schema.rb +19 -0
  117. data/app/serializers/spree/v2/storefront/accommodation_serializer.rb +2 -0
  118. data/app/serializers/spree/v2/storefront/line_item_serializer_decorator.rb +1 -0
  119. data/app/serializers/spree/v2/tenant/user_serializer.rb +0 -1
  120. data/app/serializers/spree_cm_commissioner/v2/operator/dashboard_crew_event_serializer.rb +1 -4
  121. data/app/serializers/spree_cm_commissioner/v2/storefront/dynamic_field_option_serializer.rb +3 -1
  122. data/app/serializers/spree_cm_commissioner/v2/storefront/dynamic_field_serializer.rb +3 -1
  123. data/app/serializers/spree_cm_commissioner/v2/storefront/guest_dynamic_field_serializer.rb +1 -2
  124. data/app/services/spree_cm_commissioner/organizer/export_guest_csv_service.rb +15 -23
  125. data/app/services/spree_cm_commissioner/user_authenticator.rb +1 -1
  126. data/app/views/blazer/queries/_content.html.erb +0 -8
  127. data/app/views/spree/admin/inventory_items/show.html.erb +72 -0
  128. data/app/views/spree/admin/stock_managements/_variant_stock_items.html.erb +7 -2
  129. data/app/views/spree/admin/stock_managements/calendar.html.erb +56 -0
  130. data/app/views/spree/admin/stock_managements/index.html.erb +55 -6
  131. data/app/views/spree/admin/tenants/_form.html.erb +42 -109
  132. data/config/initializers/paper_trail.rb +1 -0
  133. data/config/initializers/spree_permitted_attributes.rb +5 -2
  134. data/config/locales/en.yml +0 -5
  135. data/config/routes.rb +22 -6
  136. data/db/migrate/20240202080634_update_counter_cache_of_vehicle_type.rb +3 -1
  137. data/db/migrate/20250304293518_create_cm_inventory_items.rb +21 -0
  138. data/db/migrate/20250429094228_add_lock_version_to_cm_inventory_items.rb +5 -0
  139. data/db/migrate/20250502025848_add_index_to_spree_products.rb +5 -0
  140. data/db/migrate/20250502030001_add_product_type_to_spree_variants.rb +5 -0
  141. data/db/migrate/20250502030002_add_product_type_to_spree_line_items.rb +5 -0
  142. data/db/migrate/20250603035256_add_inventory_item_to_spree_prices.rb +7 -0
  143. data/db/migrate/20250619073724_drop_table_cm_line_item_seats.rb +5 -0
  144. data/db/migrate/20250619073812_drop_table_cm_vehicle_seats.rb +5 -0
  145. data/db/migrate/20250619073844_drop_table_cm_vehicle_types.rb +9 -0
  146. data/db/migrate/20250619073957_drop_table_cm_option_value_vehicle_types.rb +5 -0
  147. data/db/migrate/20250619082354_remove_unnecessary_fields_from_cm_places.rb +9 -0
  148. data/db/migrate/20250619082736_remove_route_type_from_spree_products.rb +5 -0
  149. data/db/migrate/20250619083055_remove_unnecessary_fields_from_spree_taxons.rb +5 -0
  150. data/db/migrate/20250620083055_remove_variant_id_from_cm_trips.rb +5 -0
  151. data/db/migrate/20250620090000_update_cm_trip_connections_to_use_cm_trips.rb +6 -0
  152. data/db/migrate/20250620090001_create_cm_seat_layouts.rb +17 -0
  153. data/db/migrate/20250620090002_create_cm_seat_sections.rb +18 -0
  154. data/db/migrate/20250620090003_create_cm_blocks.rb +18 -0
  155. data/db/migrate/20250624091005_create_cm_reserved_blocks.rb +29 -0
  156. data/db/migrate/20250626083642_create_cm_variant_blocks.rb +24 -0
  157. data/db/migrate/20250627023314_add_block_id_to_cm_guests.rb +13 -0
  158. data/docker-compose.yml +1 -1
  159. data/lib/cm_app_logger.rb +11 -4
  160. data/lib/generators/spree_cm_commissioner/install/install_generator.rb +14 -11
  161. data/lib/generators/spree_cm_commissioner/install/templates/app/javascript/{spree_cm_commissioner → spree_dashboard/spree_cm_commissioner}/utilities.js +4 -0
  162. data/lib/spree_cm_commissioner/cached_inventory_item.rb +23 -0
  163. data/lib/spree_cm_commissioner/calendar_event.rb +11 -1
  164. data/lib/spree_cm_commissioner/test_helper/factories/block_factory.rb +9 -0
  165. data/lib/spree_cm_commissioner/test_helper/factories/guest_factory.rb +10 -0
  166. data/lib/spree_cm_commissioner/test_helper/factories/homepage_section_relatable_factory.rb +1 -1
  167. data/lib/spree_cm_commissioner/test_helper/factories/inventory_item_factory.rb +9 -0
  168. data/lib/spree_cm_commissioner/test_helper/factories/line_item_factory.rb +1 -1
  169. data/lib/spree_cm_commissioner/test_helper/factories/option_type_factory.rb +6 -30
  170. data/lib/spree_cm_commissioner/test_helper/factories/order_factory.rb +0 -36
  171. data/lib/spree_cm_commissioner/test_helper/factories/product_factory.rb +18 -34
  172. data/lib/spree_cm_commissioner/test_helper/factories/reserved_block_factory.rb +27 -0
  173. data/lib/spree_cm_commissioner/test_helper/factories/seat_layout_factory.rb +8 -0
  174. data/lib/spree_cm_commissioner/test_helper/factories/seat_section_factory.rb +8 -0
  175. data/lib/spree_cm_commissioner/test_helper/factories/stock_location_factory.rb +2 -2
  176. data/lib/spree_cm_commissioner/test_helper/factories/trip_connection_factory.rb +6 -0
  177. data/lib/spree_cm_commissioner/test_helper/factories/trip_factory.rb +10 -3
  178. data/lib/spree_cm_commissioner/test_helper/factories/user_identity_provider_factory.rb +1 -1
  179. data/lib/spree_cm_commissioner/test_helper/factories/variant_block_factory.rb +7 -0
  180. data/lib/spree_cm_commissioner/test_helper/factories/variant_factory.rb +41 -19
  181. data/lib/spree_cm_commissioner/test_helper/factories/vehicle_factory.rb +1 -1
  182. data/lib/spree_cm_commissioner/test_helper/factories/vendor_factory.rb +1 -1
  183. data/lib/spree_cm_commissioner/version.rb +1 -1
  184. data/lib/spree_cm_commissioner.rb +35 -1
  185. data/lib/tasks/create_default_non_permanent_inventory_items.rake +16 -0
  186. data/lib/tasks/ensure_correct_product_type.rake +7 -0
  187. data/lib/tasks/generate_inventory_items.rake +7 -0
  188. data/spree_cm_commissioner.gemspec +7 -0
  189. metadata +135 -57
  190. data/app/assets/images/cm-hangmeas-checkout_image.svg +0 -63
  191. data/app/assets/images/cm-hangmeas-failed.svg +0 -56
  192. data/app/assets/images/cm-hangmeas-loader.svg +0 -50
  193. data/app/assets/images/cm-hangmeas-success.svg +0 -51
  194. data/app/assets/javascripts/spree_cm_commissioner/tenant_payment_icon_fields.js +0 -65
  195. data/app/assets/stylesheets/spree_cm_commissioner/backend/tenant_payment_icon_fields.scss +0 -60
  196. data/app/controllers/concerns/spree_cm_commissioner/transit/taxon_bitwise.rb +0 -44
  197. data/app/controllers/spree/admin/trip_blazer_queries_controller.rb +0 -35
  198. data/app/controllers/spree/api/v2/organizer/invite_crews_controller.rb +0 -33
  199. data/app/finders/spree_cm_commissioner/line_items/find_by_variant_decorator.rb +0 -20
  200. data/app/interactors/spree_cm_commissioner/apple_app_site_association_fetcher.rb +0 -27
  201. data/app/interactors/spree_cm_commissioner/asset_links_fetcher.rb +0 -27
  202. data/app/interactors/spree_cm_commissioner/crew_invite_link_handler.rb +0 -48
  203. data/app/interactors/spree_cm_commissioner/telegram_chats_auto_finder.rb +0 -144
  204. data/app/models/concerns/spree_cm_commissioner/event_check_in_flowable.rb +0 -30
  205. data/app/models/spree_cm_commissioner/blazer_queryable.rb +0 -8
  206. data/app/models/spree_cm_commissioner/branch.rb +0 -12
  207. data/app/models/spree_cm_commissioner/crew_invite_link.rb +0 -4
  208. data/app/models/spree_cm_commissioner/line_item_seat.rb +0 -10
  209. data/app/models/spree_cm_commissioner/option_value_vehicle_type.rb +0 -8
  210. data/app/models/spree_cm_commissioner/pin_code_telegram.rb +0 -28
  211. data/app/models/spree_cm_commissioner/promotion_category_decorator.rb +0 -11
  212. data/app/models/spree_cm_commissioner/stop.rb +0 -23
  213. data/app/models/spree_cm_commissioner/telegram_chat.rb +0 -6
  214. data/app/models/spree_cm_commissioner/vehicle_seat.rb +0 -11
  215. data/app/models/spree_cm_commissioner/vehicle_type.rb +0 -76
  216. data/app/overrides/spree/admin/taxons/_form/check_in_flows.html.erb.deface +0 -18
  217. data/app/queries/spree_cm_commissioner/trip_search_query.rb +0 -76
  218. data/app/queries/spree_cm_commissioner/variant_availability/non_permanent_stock_query.rb +0 -45
  219. data/app/queries/spree_cm_commissioner/variant_availability/permanent_stock_query.rb +0 -55
  220. data/app/serializers/spree/v2/organizer/invite_crews_serializer.rb +0 -11
  221. data/app/services/spree_cm_commissioner/vehicle_option_value_creator.rb +0 -11
  222. data/db/migrate/20250616084219_add_description_to_cm_vendor_place.rb +0 -5
  223. data/db/migrate/20250630103536_create_cm_telegram_chats.rb +0 -13
  224. data/db/migrate/20250701093203_add_configurations_to_cm_dynamic_field.rb +0 -6
  225. data/db/migrate/20250702091305_add_dynamic_field_option_to_guest_dynamic_field.rb +0 -5
  226. data/db/migrate/20250702091935_add_status_to_dynamic_field_option.rb +0 -5
  227. data/db/migrate/20250707032008_add_vendor_id_to_spree_category.rb +0 -7
  228. data/db/migrate/20250711092937_add_position_to_cm_dynamic_fields.rb +0 -11
  229. data/db/migrate/20250711093045_add_position_to_cm_dynamic_field_options.rb +0 -11
  230. data/db/migrate/20250714121508_rename_cm_taxon_blazer_query_to_blazer_queryables.rb +0 -7
  231. data/db/migrate/20250714124323_make_cm_blazer_queryables_polymorphic.rb +0 -19
  232. data/db/migrate/20250715103333_remove_indexes_from_cm_user_identity_providers.rb +0 -13
  233. data/lib/spree_cm_commissioner/test_helper/factories/branch_factory.rb +0 -12
  234. data/lib/spree_cm_commissioner/test_helper/factories/departure_time_option_type_factory.rb +0 -8
  235. data/lib/spree_cm_commissioner/test_helper/factories/duration_option_type_factory.rb +0 -8
  236. data/lib/spree_cm_commissioner/test_helper/factories/line_item_seat_factory.rb +0 -7
  237. data/lib/spree_cm_commissioner/test_helper/factories/stop_factory.rb +0 -14
  238. data/lib/spree_cm_commissioner/test_helper/factories/transit_place_factory.rb +0 -8
  239. data/lib/spree_cm_commissioner/test_helper/factories/vehicle_option_type_factory.rb +0 -8
  240. data/lib/spree_cm_commissioner/test_helper/factories/vehicle_type_factory.rb +0 -96
  241. data/lib/spree_cm_commissioner/trip_seat_layout_result.rb +0 -11
@@ -1,6 +1,6 @@
1
1
  module SpreeCmCommissioner
2
2
  class UserIdTokenAuthenticator < BaseInteractor
3
- # :id_token, :tenant_id (optional)
3
+ # :id_token
4
4
  def call
5
5
  context.user = if checker.user.nil?
6
6
  register_user
@@ -14,11 +14,7 @@ module SpreeCmCommissioner
14
14
  end
15
15
 
16
16
  def register_user
17
- register_context = SpreeCmCommissioner::UserRegistrationWithIdToken.call(
18
- id_token: context.id_token,
19
- tenant_id: context.tenant_id
20
- )
21
-
17
+ register_context = SpreeCmCommissioner::UserRegistrationWithIdToken.call(id_token: context.id_token)
22
18
  register_context.user
23
19
  end
24
20
 
@@ -31,7 +27,7 @@ module SpreeCmCommissioner
31
27
  end
32
28
 
33
29
  def checker
34
- @checker ||= SpreeCmCommissioner::UserIdTokenChecker.call(id_token: context.id_token, tenant_id: context.tenant_id)
30
+ @checker ||= SpreeCmCommissioner::UserIdTokenChecker.call(id_token: context.id_token)
35
31
  @checker
36
32
  end
37
33
  end
@@ -1,9 +1,6 @@
1
1
  module SpreeCmCommissioner
2
2
  class UserIdTokenChecker < BaseInteractor
3
- # expects:
4
- # context.id_token
5
- # context.tenant_id (optional)
6
-
3
+ # :id_token
7
4
  def call
8
5
  firebase_id_token_context = SpreeCmCommissioner::FirebaseIdTokenProvider.call(id_token: context.id_token)
9
6
 
@@ -14,14 +11,9 @@ module SpreeCmCommissioner
14
11
  end
15
12
  end
16
13
 
17
- # :identity_type, :sub, :tenant_id
14
+ # :identity_type, :sub
18
15
  def validate_user_by_provider(provider)
19
- identity_checker = SpreeCmCommissioner::UserIdentityChecker.call(
20
- identity_type: provider[:identity_type],
21
- sub: provider[:sub],
22
- tenant_id: context.tenant_id
23
- )
24
-
16
+ identity_checker = SpreeCmCommissioner::UserIdentityChecker.call(provider)
25
17
  if identity_checker.success?
26
18
  context.user = identity_checker.user
27
19
  else
@@ -1,10 +1,6 @@
1
1
  module SpreeCmCommissioner
2
2
  class UserIdentityChecker < BaseInteractor
3
- # expects:
4
- # context.identity_type
5
- # context.sub
6
- # context.tenant_id (optional)
7
-
3
+ # :identity_type, :sub
8
4
  def call
9
5
  load_user
10
6
 
@@ -15,14 +11,12 @@ module SpreeCmCommissioner
15
11
  end
16
12
 
17
13
  def load_user
18
- return if context.sub.blank? || context.identity_type.blank?
19
-
20
- user_identity_provider = UserIdentityProvider.joins(:user)
21
- .where(identity_type: context.identity_type, sub: context.sub)
22
- .where(spree_users: { tenant_id: context.tenant_id })
23
- .first
14
+ user_identity_provider = UserIdentityProvider.where(
15
+ identity_type: context.identity_type,
16
+ sub: context.sub
17
+ ).first
24
18
 
25
- context.user = user_identity_provider&.user
19
+ context.user = (user_identity_provider.user if user_identity_provider.present?)
26
20
  end
27
21
  end
28
22
  end
@@ -15,13 +15,7 @@ module SpreeCmCommissioner
15
15
  end
16
16
 
17
17
  def register_user!(name, email)
18
- user = Spree.user_class.new(
19
- password: SecureRandom.base64(16),
20
- email: email,
21
- tenant_id: context.tenant_id,
22
- **name_attributes(name)
23
- )
24
-
18
+ user = Spree.user_class.new(password: SecureRandom.base64(16), email: email, **name_attributes(name))
25
19
  if user.save(validate: false)
26
20
  context.user = user
27
21
  else
@@ -93,10 +93,6 @@ module SpreeCmCommissioner
93
93
  context.fail!(message: "User creation failed: #{context.user.errors.full_messages.join(', ')}")
94
94
  end
95
95
 
96
- def disable_services
97
- 'bus,ferry'
98
- end
99
-
100
96
  def construct_data
101
97
  user = context.user
102
98
 
@@ -105,7 +101,7 @@ module SpreeCmCommissioner
105
101
  name: user.full_name,
106
102
  phone: user.phone_number,
107
103
  email: user.email,
108
- webUrl: "#{Spree::Store.default.formatted_url}/vattanac_bank_web_app?session_id=#{session_id}&ds=#{CGI.escape(disable_services)}"
104
+ webUrl: "#{Spree::Store.default.formatted_url}/vattanac_bank_web_app?session_id=#{session_id}"
109
105
  }
110
106
 
111
107
  json_data = raw_data.to_json
@@ -1,5 +1,25 @@
1
1
  module SpreeCmCommissioner
2
2
  class ApplicationJob < ::ApplicationJob
3
3
  queue_as :default
4
+
5
+ around_perform :log_exceptions
6
+
7
+ private
8
+
9
+ def log_exceptions
10
+ yield
11
+ rescue StandardError => e
12
+ CmAppLogger.log(
13
+ label: "#{self.class.name} failed",
14
+ data: {
15
+ error_class: e.class.name,
16
+ error_message: e.message,
17
+ arguments: arguments.inspect,
18
+ backtrace: e.backtrace&.first(5)&.join("\n")
19
+ }
20
+ )
21
+
22
+ raise # Re-raise the exception to let Sidekiq handle retries or failure
23
+ end
4
24
  end
5
25
  end
@@ -2,5 +2,25 @@
2
2
  module SpreeCmCommissioner
3
3
  class ApplicationUniqueJob < ::ApplicationUniqueJob
4
4
  queue_as :default
5
+
6
+ around_perform :log_exceptions
7
+
8
+ private
9
+
10
+ def log_exceptions
11
+ yield
12
+ rescue StandardError => e
13
+ CmAppLogger.log(
14
+ label: "#{self.class.name} failed",
15
+ data: {
16
+ error_class: e.class.name,
17
+ error_message: e.message,
18
+ arguments: arguments.inspect,
19
+ backtrace: e.backtrace&.first(5)&.join("\n")
20
+ }
21
+ )
22
+
23
+ raise # Re-raise the exception to let Sidekiq handle retries or failure
24
+ end
5
25
  end
6
26
  end
@@ -0,0 +1,7 @@
1
+ module SpreeCmCommissioner
2
+ class EnsureCorrectProductTypeJob < ApplicationJob
3
+ def perform
4
+ SpreeCmCommissioner::EnsureCorrectProductType.call
5
+ end
6
+ end
7
+ end
@@ -0,0 +1,7 @@
1
+ module SpreeCmCommissioner
2
+ class InventoryItemSyncerJob < ApplicationUniqueJob
3
+ def perform(inventory_id_and_quantities:)
4
+ InventoryItemSyncer.call(inventory_id_and_quantities:)
5
+ end
6
+ end
7
+ end
@@ -1,6 +1,6 @@
1
1
  module SpreeCmCommissioner
2
2
  class SmsPinCodeJob < SpreeCmCommissioner::SmsJob
3
- # options = { to: xxxx, body: xxxx }
3
+ # options = { from: xxx, to: xxxx, body: xxxx }
4
4
  def perform(options)
5
5
  SpreeCmCommissioner::Sms.call(options)
6
6
  end
@@ -0,0 +1,11 @@
1
+ module SpreeCmCommissioner
2
+ module Stock
3
+ class InventoryItemsAdjusterJob < ApplicationUniqueJob
4
+ def perform(variant_id:, quantity:)
5
+ variant = Spree::Variant.find(variant_id)
6
+
7
+ SpreeCmCommissioner::Stock::InventoryItemsAdjuster.call(variant: variant, quantity: quantity)
8
+ end
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,11 @@
1
+ module SpreeCmCommissioner
2
+ module Stock
3
+ class InventoryItemsGeneratorJob < ApplicationUniqueJob
4
+ def perform(variant_id:)
5
+ variant = Spree::Variant.find(variant_id)
6
+
7
+ SpreeCmCommissioner::Stock::InventoryItemsGenerator.call(variant: variant)
8
+ end
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,9 @@
1
+ module SpreeCmCommissioner
2
+ module Stock
3
+ class PermanentInventoryItemsGeneratorJob < ApplicationUniqueJob
4
+ def perform
5
+ SpreeCmCommissioner::Stock::PermanentInventoryItemsGenerator.call
6
+ end
7
+ end
8
+ end
9
+ end
@@ -18,29 +18,23 @@ module SpreeCmCommissioner
18
18
  def date_unit
19
19
  return nil unless permanent_stock?
20
20
 
21
- date_range_excluding_checkout.size if accommodation?
21
+ date_range.size
22
22
  end
23
23
 
24
- def date_range_excluding_checkout
24
+ def date_range
25
25
  return [] unless date_present?
26
26
 
27
- date_range = (from_date.to_date..to_date.to_date).to_a
28
- date_range.pop if date_range.size > 1
29
- date_range
27
+ (from_date.to_date..to_date.to_date).to_a
30
28
  end
31
29
 
32
- def date_range_including_checkout
33
- return [] unless date_present?
34
-
35
- (from_date.to_date..to_date.to_date).to_a
30
+ def checkin_date
31
+ from_date&.to_date
36
32
  end
37
33
 
38
- def date_range
39
- if accommodation?
40
- date_range_excluding_checkout
41
- else
42
- date_range_including_checkout
43
- end
34
+ def checkout_date
35
+ return to_date ? to_date.to_date - 1.day : nil if accommodation?
36
+
37
+ to_date&.to_date
44
38
  end
45
39
 
46
40
  private
@@ -14,13 +14,6 @@ module SpreeCmCommissioner
14
14
  state_selection
15
15
  payment_option
16
16
  delivery_option
17
- amenity
18
- departure_time
19
- duration
20
- vehicle_id
21
- origin
22
- destination
23
- place_id
24
17
  ].freeze
25
18
 
26
19
  RESERVED_OPTIONS = {
@@ -47,12 +40,7 @@ module SpreeCmCommissioner
47
40
  'bib-zerofill' => 'integer',
48
41
  'bib-display-prefix' => 'boolean',
49
42
  'bib-pre-generation-on-create' => 'boolean',
50
- 'seat-number-positions' => 'array',
51
- 'origin' => 'place_id',
52
- 'destination' => 'place_id',
53
- 'departure-time' => 'time',
54
- 'vehicle' => 'vehicle_id',
55
- 'allow-seat-selection' => 'boolean'
43
+ 'seat-number-positions' => 'array'
56
44
  }.freeze
57
45
 
58
46
  included do
@@ -0,0 +1,44 @@
1
+ module SpreeCmCommissioner
2
+ module OrderSeatable
3
+ def should_manage_blocks?
4
+ preload_block_ids.any?
5
+ end
6
+
7
+ def hold_blocks!
8
+ return unless should_manage_blocks?
9
+
10
+ CmAppLogger.log(label: "#{self.class.name}#hold_blocks!", data: { order_id: id }) do
11
+ SpreeCmCommissioner::Seats::BlocksHolder.new(line_item_ids: line_item_ids, hold_by: user).hold_blocks!
12
+ end
13
+ end
14
+
15
+ def cancel_blocks!
16
+ return unless should_manage_blocks?
17
+
18
+ CmAppLogger.log(label: "#{self.class.name}#cancel_blocks!", data: { order_id: id }) do
19
+ SpreeCmCommissioner::Seats::BlocksCanceler.new(order_id: id, cancel_by: user).cancel_blocks!
20
+ end
21
+ end
22
+
23
+ def reserve_blocks!
24
+ return unless should_manage_blocks?
25
+
26
+ CmAppLogger.log(label: "#{self.class.name}#reserve_blocks!", data: { order_id: id }) do
27
+ SpreeCmCommissioner::Seats::BlocksReserver.new(line_item_ids: line_item_ids, reserve_by: user).reserve_blocks!
28
+ end
29
+ end
30
+
31
+ # Calling `.block_ids` directly can cause many slow database queries (N+1 problem)
32
+ # every time `.should_manage_blocks?` or `.preload_block_ids` runs.
33
+ # To avoid this, we store a precomputed list of block IDs in `private_metadata`.
34
+ # This list is updated whenever a guest’s block is saved or destroy.
35
+ def preload_block_ids=(preload_block_ids = [])
36
+ self.private_metadata ||= {}
37
+ self.private_metadata['preload_block_ids'] = preload_block_ids
38
+ end
39
+
40
+ def preload_block_ids
41
+ self.private_metadata&.fetch('preload_block_ids', []) || []
42
+ end
43
+ end
44
+ end
@@ -5,6 +5,8 @@ module SpreeCmCommissioner
5
5
  extend ActiveSupport::Concern
6
6
 
7
7
  included do
8
+ state_machine.before_transition to: :payment, do: :hold_blocks!
9
+
8
10
  state_machine.before_transition to: :complete, do: :request, if: :need_confirmation?
9
11
  state_machine.before_transition to: :complete, do: :generate_bib_number
10
12
 
@@ -14,10 +16,14 @@ module SpreeCmCommissioner
14
16
  state_machine.after_transition to: :complete, do: :send_order_complete_telegram_alert_to_vendors, unless: :need_confirmation?
15
17
  state_machine.after_transition to: :complete, do: :send_order_complete_telegram_alert_to_store, unless: :need_confirmation?
16
18
 
19
+ state_machine.around_transition to: :complete, do: :unstock_inventory!
20
+
17
21
  state_machine.after_transition to: :complete, do: :send_transaction_email_to_user, if: :user_has_email?
18
22
  state_machine.after_transition to: :resumed, do: :precalculate_conversion
23
+ state_machine.around_transition to: :resumed, do: :unstock_inventory!
19
24
 
20
25
  state_machine.after_transition to: :canceled, do: :precalculate_conversion
26
+ state_machine.after_transition to: :canceled, do: :restock_inventory!
21
27
 
22
28
  scope :accepted, -> { where(request_state: 'accepted') }
23
29
 
@@ -67,6 +73,39 @@ module SpreeCmCommissioner
67
73
  end
68
74
  end
69
75
 
76
+ def unstock_inventory!
77
+ ActiveRecord::Base.transaction do
78
+ yield # Equal to block.call
79
+
80
+ # After the transition is complete, the following code will execute first before proceeding to other `after_transition` callbacks.
81
+ # This ensures that if `reserve_blocks!` or `unstock_inventory_in_redis!` fails, the state will be rolled back,
82
+ # and neither the `finalize!` method nor any notifications will be triggered.
83
+ # The payment will be reversed in vPago gem, and `Spree::Checkout::Complete` will be called, which checks `order.reload.complete?`.
84
+ # This is critical because if the order state is complete, the payment will be marked as paid.
85
+
86
+ reserve_blocks!
87
+ unstock_inventory_in_redis!
88
+
89
+ # We rollback only order state, and we keep payment state as it is.
90
+ # We implement payment in vPago gem, and it will be reversed in the gem.
91
+ # Some bank has api for refund, but some don't have the api to refund yet. So we keep the payment state as it is and refund manually.
92
+ end
93
+ rescue StandardError => e
94
+ CmAppLogger.log(
95
+ label: 'SpreeCmCommissioner::OrderStateMachine#reserve_inventory failed',
96
+ data: { order_id: id, error: e.message, type: e.class.name, backtrace: e.backtrace&.first(5)&.join("\n") }
97
+ )
98
+
99
+ raise e
100
+ end
101
+
102
+ def restock_inventory!
103
+ ActiveRecord::Base.transaction do
104
+ cancel_blocks!
105
+ restock_inventory_in_redis!
106
+ end
107
+ end
108
+
70
109
  def generate_bib_number
71
110
  line_items.find_each(&:generate_remaining_guests)
72
111
 
@@ -3,12 +3,10 @@ module SpreeCmCommissioner
3
3
  extend ActiveSupport::Concern
4
4
 
5
5
  included do
6
- delegate :product_type,
7
- :subscribable?,
6
+ delegate :subscribable?,
8
7
  :allowed_upload_later?,
9
8
  :need_confirmation?, :need_confirmation,
10
9
  :allow_anonymous_booking,
11
- :accommodation?, :service?, :ecommerce?,
12
10
  :allow_self_check_in,
13
11
  :allow_self_check_in?,
14
12
  :required_self_check_in_location,
@@ -6,10 +6,20 @@ module SpreeCmCommissioner
6
6
  extend ActiveSupport::Concern
7
7
 
8
8
  PRODUCT_TYPES = %i[accommodation service ecommerce transit].freeze
9
+ PERMANENT_STOCK_PRODUCT_TYPES = %w[accommodation service transit].freeze
10
+ PRE_INVENTORY_DAYS = { 'transit' => 90, 'accommodation' => 365, 'service' => 30 }.freeze
9
11
 
10
12
  included do
11
13
  enum product_type: PRODUCT_TYPES if table_exists? && column_names.include?('product_type')
12
14
  enum primary_product_type: PRODUCT_TYPES if table_exists? && column_names.include?('primary_product_type')
13
15
  end
16
+
17
+ def permanent_stock?
18
+ PERMANENT_STOCK_PRODUCT_TYPES.include?(product_type)
19
+ end
20
+
21
+ def pre_inventory_days
22
+ PRE_INVENTORY_DAYS[product_type]
23
+ end
14
24
  end
15
25
  end
@@ -3,7 +3,7 @@ module SpreeCmCommissioner
3
3
  extend ActiveSupport::Concern
4
4
 
5
5
  included do
6
- enum kind: { category: 0, cms: 1, event: 2, occupation: 3, nationality: 4, organization: 5, transit: 6 }
6
+ enum kind: { category: 0, cms: 1, event: 2, occupation: 3, nationality: 4, organization: 5 }
7
7
  end
8
8
  end
9
9
  end
@@ -5,10 +5,6 @@ module SpreeCmCommissioner
5
5
  included do
6
6
  preference :assetlinks, :string, default: ''
7
7
  preference :apple_app_site_association, :string, default: ''
8
- preference :payment_checkout_image, :string, default: ''
9
- preference :payment_failed_image, :string, default: ''
10
- preference :payment_success_image, :string, default: ''
11
- preference :payment_loader, :string, default: ''
12
8
  end
13
9
  end
14
10
  end
@@ -0,0 +1,23 @@
1
+ module SpreeCmCommissioner
2
+ class Block < Base
3
+ belongs_to :seat_section, class_name: 'SpreeCmCommissioner::SeatSection', optional: true
4
+ belongs_to :seat_layout, class_name: 'SpreeCmCommissioner::SeatLayout', optional: false
5
+
6
+ validates :label, presence: true
7
+
8
+ has_many :variant_blocks, class_name: 'SpreeCmCommissioner::VariantBlock', dependent: :destroy
9
+ has_many :variants, class_name: 'Spree::Variant', through: :variant_blocks
10
+
11
+ validates :width, presence: true, numericality: { greater_than: 0 }
12
+ validates :height, presence: true, numericality: { greater_than: 0 }
13
+ validates :x, presence: true, numericality: true
14
+ validates :y, presence: true, numericality: true
15
+ validates :rotation, presence: true, numericality: { greater_than_or_equal_to: -360, less_than_or_equal_to: 360 }
16
+
17
+ before_validation :assign_layout_from_section, if: -> { seat_layout.nil? && seat_section.present? }
18
+
19
+ def assign_layout_from_section
20
+ self.seat_layout = seat_section.seat_layout
21
+ end
22
+ end
23
+ end
@@ -1,34 +1,20 @@
1
1
  module SpreeCmCommissioner
2
2
  class DynamicField < SpreeCmCommissioner::Base
3
- acts_as_list column: :position, scope: :vendor_id
4
-
5
3
  belongs_to :vendor, class_name: 'Spree::Vendor', optional: false
6
4
 
7
5
  has_many :dynamic_field_options, class_name: 'SpreeCmCommissioner::DynamicFieldOption', dependent: :destroy
8
6
  has_many :guest_dynamic_fields, class_name: 'SpreeCmCommissioner::GuestDynamicField', dependent: :destroy
9
7
  has_many :product_dynamic_fields, class_name: 'SpreeCmCommissioner::ProductDynamicField', dependent: :destroy
10
8
 
11
- enum data_type: { text: 0, number: 1, boolean: 2, checkbox: 3, radio: 4, selection: 5, textarea: 6 }
9
+ enum data_type: { string: 0, integer: 1, boolean: 2, checkbox: 3, radio: 4 }
12
10
 
13
11
  accepts_nested_attributes_for :dynamic_field_options, allow_destroy: true, reject_if: :all_blank
14
12
 
15
13
  validates :label, presence: true
16
14
  validates :data_type, presence: true
17
- validates :configurations, presence: true, allow_blank: false
18
-
19
- def requires_dynamic_field_options?
20
- checkbox? || radio? || selection?
21
- end
22
-
23
- def multiple_select
24
- return nil unless selection?
25
-
26
- configurations['multiple_select'] == true
27
- end
28
15
 
29
- def multiple_select=(value)
30
- self.configurations ||= {}
31
- self.configurations['multiple_select'] = ActiveModel::Type::Boolean.new.cast(value)
16
+ def selection?
17
+ checkbox? || radio?
32
18
  end
33
19
  end
34
20
  end
@@ -1,14 +1,8 @@
1
1
  module SpreeCmCommissioner
2
2
  class DynamicFieldOption < SpreeCmCommissioner::Base
3
- acts_as_list column: :position, scope: :dynamic_field_id
4
-
5
3
  belongs_to :dynamic_field, class_name: 'SpreeCmCommissioner::DynamicField', optional: false
6
- has_many :guest_dynamic_fields, class_name: 'SpreeCmCommissioner::GuestDynamicField', dependent: :restrict_with_error
7
-
8
- enum status: { active: 0, draft: 1, archived: 2 }
9
4
 
10
5
  validates :value, presence: true, uniqueness: { scope: :dynamic_field_id }
11
- validates :status, presence: true
12
6
  validate :parent_field_must_be_selection_type
13
7
 
14
8
  private
@@ -16,7 +10,7 @@ module SpreeCmCommissioner
16
10
  def parent_field_must_be_selection_type
17
11
  return unless dynamic_field
18
12
 
19
- return if dynamic_field.requires_dynamic_field_options?
13
+ return if dynamic_field.selection?
20
14
 
21
15
  errors.add(:dynamic_field, 'must be a selection type field')
22
16
  end
@@ -24,10 +24,16 @@ module SpreeCmCommissioner
24
24
  scope :none_bib, -> { where(bib_prefix: [nil, '']) }
25
25
 
26
26
  belongs_to :line_item, class_name: 'Spree::LineItem'
27
+
27
28
  has_one :variant, class_name: 'Spree::Variant', through: :line_item
29
+
28
30
  belongs_to :user, class_name: 'Spree::User'
29
31
  belongs_to :occupation, class_name: 'Spree::Taxon'
30
32
  belongs_to :nationality, class_name: 'Spree::Taxon'
33
+ belongs_to :block, class_name: 'SpreeCmCommissioner::Block', optional: true
34
+
35
+ has_one :reserved_block, class_name: 'SpreeCmCommissioner::ReservedBlock'
36
+
31
37
  scope :checked_ins, -> { joins(:check_in) }
32
38
  scope :no_show, -> { left_outer_joins(:check_in).where(cm_check_ins: { id: nil }) }
33
39
 
@@ -48,10 +54,14 @@ module SpreeCmCommissioner
48
54
 
49
55
  before_create :generate_bib, if: -> { line_item.reload && variant.bib_pre_generation_on_create? }
50
56
 
57
+ after_create :preload_order_block_ids, if: -> { block_id.present? }
58
+ after_update :preload_order_block_ids, if: :saved_change_to_block_id?
59
+ after_destroy :preload_order_block_ids
60
+
51
61
  validates :seat_number, uniqueness: { scope: :event_id }, allow_nil: true, if: -> { event_id.present? }
52
62
  validates :bib_index, uniqueness: true, allow_nil: true
53
63
 
54
- self.whitelisted_ransackable_associations = %w[id_card event line_item]
64
+ self.whitelisted_ransackable_associations = %w[id_card event]
55
65
  self.whitelisted_ransackable_attributes = %w[first_name last_name phone_number gender contact occupation_id card_type created_at check_in_status]
56
66
 
57
67
  def self.csv_importable_columns
@@ -227,6 +237,14 @@ module SpreeCmCommissioner
227
237
  filled_bib_number
228
238
  end
229
239
  end
240
+
241
+ def preload_order_block_ids
242
+ return if line_item.blank?
243
+ return if line_item.order.blank?
244
+
245
+ block_ids = line_item.order.blocks.pluck(:id)
246
+ line_item.order.update(preload_block_ids: block_ids)
247
+ end
230
248
  end
231
249
  end
232
250
  # rubocop:enable Metrics/ClassLength
@@ -2,11 +2,9 @@ module SpreeCmCommissioner
2
2
  class GuestDynamicField < SpreeCmCommissioner::Base
3
3
  belongs_to :guest, class_name: 'SpreeCmCommissioner::Guest', optional: false
4
4
  belongs_to :dynamic_field, class_name: 'SpreeCmCommissioner::DynamicField', optional: false
5
- belongs_to :dynamic_field_option, class_name: 'SpreeCmCommissioner::DynamicFieldOption', optional: true
6
5
 
7
6
  validates :value, presence: true
8
7
  validate :validate_value_format, if: -> { value.present? && dynamic_field.present? }
9
- validate :validate_option_reference, if: -> { dynamic_field_option.present? }
10
8
 
11
9
  private
12
10
 
@@ -14,30 +12,14 @@ module SpreeCmCommissioner
14
12
  return if dynamic_field.blank?
15
13
 
16
14
  case dynamic_field.data_type.to_s
17
- when 'number'
15
+ when 'integer'
18
16
  errors.add(:value, 'must be a number') unless value.to_s.match?(/\A\d+\z/)
19
17
  when 'boolean'
20
- errors.add(:value, 'must be true or false') unless %w[true false].include?(value.to_s.downcase)
21
- when 'checkbox', 'radio', 'selection'
22
- validate_option_value
23
- end
24
- end
25
-
26
- def validate_option_value
27
- if dynamic_field_option.present?
28
- self.value ||= dynamic_field_option.value
29
- else
18
+ errors.add(:value, 'must be true or false') unless %w[true false 1 0].include?(value.to_s.downcase)
19
+ when 'checkbox', 'radio'
30
20
  valid_options = dynamic_field.dynamic_field_options.pluck(:id).map(&:to_s)
31
21
  errors.add(:value, 'is not a valid option') unless valid_options.include?(value)
32
22
  end
33
23
  end
34
-
35
- def validate_option_reference
36
- return if dynamic_field.blank? || dynamic_field_option.blank?
37
-
38
- return if dynamic_field_option.dynamic_field_id == dynamic_field_id
39
-
40
- errors.add(:dynamic_field_option, 'must belong to the same dynamic field')
41
- end
42
24
  end
43
25
  end