spree_cm_commissioner 1.11.0.pre.pre4 → 1.13.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (175) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/test_and_build_gem.yml +143 -12
  3. data/.gitignore +1 -2
  4. data/Gemfile.lock +1 -22
  5. data/Rakefile +4 -33
  6. data/app/controllers/spree/admin/cms_pages_controller_decorator.rb +32 -0
  7. data/app/controllers/spree/admin/product_places_controller.rb +2 -0
  8. data/app/controllers/spree/admin/stock_managements_controller.rb +1 -62
  9. data/app/controllers/spree/api/v2/organizer/invite_guests_controller.rb +70 -0
  10. data/app/controllers/spree/api/v2/storefront/accommodations_controller.rb +31 -14
  11. data/app/controllers/spree/api/v2/storefront/queue_cart/line_items_controller.rb +2 -2
  12. data/app/controllers/spree/api/v2/storefront/waiting_room_sessions_controller.rb +1 -0
  13. data/app/controllers/spree/api/v2/tenant/account_checker_controller.rb +1 -1
  14. data/app/controllers/spree/api/v2/tenant/account_recovers_controller.rb +2 -2
  15. data/app/controllers/spree/api/v2/tenant/cms_pages_controller.rb +41 -0
  16. data/app/controllers/spree/api/v2/tenant/reset_passwords_controller.rb +1 -1
  17. data/app/controllers/spree/api/v2/tenant/waiting_room_sessions_controller.rb +30 -0
  18. data/app/controllers/spree_cm_commissioner/admin/variants_controller_decorator.rb +17 -0
  19. data/app/controllers/spree_cm_commissioner/api/v2/storefront/cms_pages_controller_decorator.rb +18 -0
  20. data/app/interactors/spree_cm_commissioner/account_recover.rb +2 -2
  21. data/app/interactors/spree_cm_commissioner/create_event.rb +7 -26
  22. data/app/interactors/spree_cm_commissioner/create_ticket.rb +95 -0
  23. data/app/interactors/spree_cm_commissioner/event_line_items_date_syncer.rb +19 -0
  24. data/app/interactors/spree_cm_commissioner/existing_account_checker.rb +1 -1
  25. data/app/interactors/spree_cm_commissioner/order_importer/multi_guest.rb +31 -0
  26. data/app/interactors/spree_cm_commissioner/organizers_transactional_email_notifier.rb +27 -0
  27. data/app/interactors/spree_cm_commissioner/pin_code_sender.rb +3 -3
  28. data/app/interactors/spree_cm_commissioner/product_event_id_to_children_syncer.rb +15 -0
  29. data/app/interactors/spree_cm_commissioner/sms.rb +14 -0
  30. data/app/interactors/spree_cm_commissioner/telegram_debug_pin_code_sender.rb +2 -1
  31. data/app/interactors/spree_cm_commissioner/transactional_email_sender.rb +50 -0
  32. data/app/interactors/spree_cm_commissioner/user_forgotten_password_updater.rb +2 -1
  33. data/app/interactors/spree_cm_commissioner/user_password_authenticator.rb +4 -2
  34. data/app/interactors/spree_cm_commissioner/waiting_room_session_creator.rb +7 -14
  35. data/app/interactors/spree_cm_commissioner/waiting_room_session_firebase_logger.rb +30 -0
  36. data/app/jobs/spree_cm_commissioner/ensure_event_for_product_line_item_guests_job.rb +13 -0
  37. data/app/jobs/spree_cm_commissioner/event_line_items_date_syncer_job.rb +8 -0
  38. data/app/jobs/spree_cm_commissioner/product_event_id_to_children_syncer_job.rb +8 -0
  39. data/app/jobs/spree_cm_commissioner/telegram_debug_pin_code_sender_job.rb +3 -1
  40. data/app/jobs/spree_cm_commissioner/waiting_room_session_firebase_logger_job.rb +13 -0
  41. data/app/mailers/spree_cm_commissioner/event_transactional_mailer.rb +23 -0
  42. data/app/mailers/spree_cm_commissioner/pin_code_mailer.rb +1 -0
  43. data/app/models/concerns/spree_cm_commissioner/kyc_bitwise.rb +2 -0
  44. data/app/models/concerns/spree_cm_commissioner/line_item_durationable.rb +10 -6
  45. data/app/models/concerns/spree_cm_commissioner/order_state_machine.rb +13 -26
  46. data/app/models/concerns/spree_cm_commissioner/product_delegation.rb +4 -3
  47. data/app/models/concerns/spree_cm_commissioner/product_type.rb +0 -10
  48. data/app/models/concerns/spree_cm_commissioner/user_identity.rb +7 -4
  49. data/app/models/concerns/spree_cm_commissioner/variant_options_concern.rb +8 -10
  50. data/app/models/spree_cm_commissioner/cms_page_decorator.rb +9 -0
  51. data/app/models/spree_cm_commissioner/event_ticket_google_wallet.rb +2 -2
  52. data/app/models/spree_cm_commissioner/guest.rb +1 -1
  53. data/app/models/spree_cm_commissioner/invite_guest.rb +23 -0
  54. data/app/models/spree_cm_commissioner/line_item_decorator.rb +10 -16
  55. data/app/models/spree_cm_commissioner/option_type_decorator.rb +6 -0
  56. data/app/models/spree_cm_commissioner/order_decorator.rb +0 -15
  57. data/app/models/spree_cm_commissioner/place.rb +1 -1
  58. data/app/models/spree_cm_commissioner/product_decorator.rb +17 -14
  59. data/app/models/spree_cm_commissioner/product_place.rb +1 -0
  60. data/app/models/spree_cm_commissioner/role_decorator.rb +3 -0
  61. data/app/models/spree_cm_commissioner/stock/availability_checker.rb +25 -27
  62. data/app/models/spree_cm_commissioner/stock/availability_validator_decorator.rb +1 -2
  63. data/app/models/spree_cm_commissioner/stock/line_item_availability_checker.rb +3 -3
  64. data/app/models/spree_cm_commissioner/stock_item_decorator.rb +0 -14
  65. data/app/models/spree_cm_commissioner/taxon_decorator.rb +26 -3
  66. data/app/models/spree_cm_commissioner/transactional_email.rb +6 -0
  67. data/app/models/spree_cm_commissioner/user_decorator.rb +6 -0
  68. data/app/models/spree_cm_commissioner/variant_decorator.rb +27 -34
  69. data/app/models/spree_cm_commissioner/vehicle.rb +0 -7
  70. data/app/models/spree_cm_commissioner/vendor_decorator.rb +9 -1
  71. data/app/overrides/spree/admin/cms_pages/_form/tenant_fields.html.erb.deface +9 -0
  72. data/app/overrides/spree/admin/cms_pages/index/cms_pages_tabs.html.erb.deface +21 -0
  73. data/app/overrides/spree/admin/taxons/_form/assets_form.html.erb.deface +2 -2
  74. data/app/overrides/spree/admin/taxons/_form/available_on.html.erb.deface +3 -1
  75. data/app/overrides/spree/admin/taxons/_form/background_color_and_foreground_color.html.erb.deface +3 -1
  76. data/app/overrides/spree/admin/taxons/_form/custom_redirect_url.html.erb.deface +3 -1
  77. data/app/overrides/spree/admin/taxons/_form/hide_video_banner.html.erb.deface +3 -1
  78. data/app/overrides/spree/admin/taxons/_form/purchasable_on_status.html.erb.deface +3 -1
  79. data/app/overrides/spree/admin/taxons/_form/show_badge_status.html.erb.deface +3 -1
  80. data/app/overrides/spree/admin/taxons/_form/to_date_form_date.html.erb.deface +3 -4
  81. data/app/overrides/spree/admin/users/_form/roles_fields.html.erb.deface +1 -1
  82. data/app/overrides/spree/admin/users/index/body.html.erb.deface +3 -0
  83. data/app/overrides/spree/admin/users/index/headers.html.erb.deface +3 -0
  84. data/app/overrides/spree/admin/variants/_form/kyc_field.html.erb.deface +40 -0
  85. data/app/overrides/spree/admin/variants/edit/variant_status.html.erb.deface +6 -3
  86. data/app/queries/spree_cm_commissioner/variant_availability/non_permanent_stock_query.rb +45 -0
  87. data/app/queries/spree_cm_commissioner/variant_availability/permanent_stock_query.rb +55 -0
  88. data/app/request_schemas/spree_cm_commissioner/accommodation_request_schema.rb +0 -3
  89. data/app/request_schemas/spree_cm_commissioner/application_request_schema.rb +1 -1
  90. data/app/serializers/spree/v2/organizer/invite_guest_serializer.rb +13 -0
  91. data/app/serializers/spree/v2/storefront/accommodation_serializer.rb +0 -2
  92. data/app/serializers/spree/v2/storefront/taxon_serializer_decorator.rb +2 -0
  93. data/app/serializers/spree/v2/tenant/waiting_room_session_serializer.rb +9 -0
  94. data/app/serializers/spree_cm_commissioner/v2/storefront/guest_serializer.rb +2 -1
  95. data/app/services/spree_cm_commissioner/user_authenticator.rb +3 -1
  96. data/app/services/spree_cm_commissioner/user_roles_assigner.rb +62 -0
  97. data/app/views/spree/admin/shared/_cms_pages_tabs.html.erb +20 -0
  98. data/app/views/spree/admin/shared/_taxon_tabs.html.erb +19 -19
  99. data/app/views/spree/admin/stock_managements/_variant_stock_items.html.erb +1 -3
  100. data/app/views/spree/admin/stock_managements/index.html.erb +5 -40
  101. data/app/views/spree/shared/_base_mailer_header.html.erb +10 -2
  102. data/app/views/spree_cm_commissioner/event_transactional_mailer/_event_banner.html.erb +3 -0
  103. data/app/views/spree_cm_commissioner/event_transactional_mailer/_mailer_stylesheets.html.erb +317 -0
  104. data/app/views/spree_cm_commissioner/event_transactional_mailer/_note.html.erb +8 -0
  105. data/app/views/spree_cm_commissioner/event_transactional_mailer/_share_button.html.erb +3 -0
  106. data/app/views/spree_cm_commissioner/event_transactional_mailer/send_to_organizer.html.erb +59 -0
  107. data/app/views/spree_cm_commissioner/event_transactional_mailer/send_to_participant.html.erb +52 -0
  108. data/app/views/spree_cm_commissioner/layouts/event_transactional_mailer.html.erb +14 -0
  109. data/config/initializers/spree_permitted_attributes.rb +0 -5
  110. data/config/locales/en.yml +36 -0
  111. data/config/locales/km.yml +2 -0
  112. data/config/routes.rb +6 -11
  113. data/db/migrate/20250422014417_create_spree_cm_commissioner_invite_guest.rb +21 -0
  114. data/db/migrate/20250509033437_create_spree_cm_commissioner_transactional_emails.rb +12 -0
  115. data/db/migrate/20250509075429_add_max_order_quantity_to_spree_product.rb +5 -0
  116. data/db/migrate/20250512075319_add_tenant_id_to_spree_cms_pages.rb +6 -0
  117. data/db/migrate/20250520042602_add_event_to_spree_products.rb +7 -0
  118. data/db/migrate/20250520044533_add_event_to_spree_line_items.rb +7 -0
  119. data/db/migrate/20250521024345_add_tenant_id_to_cm_waiting_room_sessions.rb +6 -0
  120. data/db/migrate/20250521095539_add_kyc_to_spree_variants.rb +5 -0
  121. data/docker-compose.yml +1 -1
  122. data/lib/generators/spree_cm_commissioner/install/install_generator.rb +3 -11
  123. data/lib/generators/spree_cm_commissioner/install/templates/app/javascript/{spree_dashboard/spree_cm_commissioner → spree_cm_commissioner}/utilities.js +0 -4
  124. data/lib/spree_cm_commissioner/calendar_event.rb +1 -11
  125. data/lib/spree_cm_commissioner/test_helper/factories/homepage_section_relatable_factory.rb +1 -1
  126. data/lib/spree_cm_commissioner/test_helper/factories/line_item_factory.rb +1 -1
  127. data/lib/spree_cm_commissioner/test_helper/factories/product_factory.rb +5 -18
  128. data/lib/spree_cm_commissioner/test_helper/factories/role.rb +7 -0
  129. data/lib/spree_cm_commissioner/test_helper/factories/stock_location_factory.rb +2 -2
  130. data/lib/spree_cm_commissioner/test_helper/factories/taxon_home_banner_factory.rb +5 -0
  131. data/lib/spree_cm_commissioner/test_helper/factories/transactional_email_factory.rb +6 -0
  132. data/lib/spree_cm_commissioner/test_helper/factories/variant_factory.rb +6 -41
  133. data/lib/spree_cm_commissioner/test_helper/factories/vendor_factory.rb +1 -1
  134. data/lib/spree_cm_commissioner/version.rb +1 -1
  135. data/lib/spree_cm_commissioner.rb +0 -34
  136. data/lib/tasks/ensure_event_for_product_line_item_guests.rake +7 -0
  137. data/lib/tasks/update_invalid_self_root_places.rake +9 -0
  138. data/lib/tasks/update_orphan_root_places.rake +1 -0
  139. data/spree_cm_commissioner.gemspec +0 -5
  140. metadata +54 -82
  141. data/app/controllers/spree/api/v2/storefront/accommodations/variants_controller.rb +0 -42
  142. data/app/finders/spree_cm_commissioner/accommodations/find.rb +0 -40
  143. data/app/finders/spree_cm_commissioner/accommodations/find_variant.rb +0 -35
  144. data/app/interactors/spree_cm_commissioner/ensure_correct_product_type.rb +0 -40
  145. data/app/interactors/spree_cm_commissioner/inventory_item_syncer.rb +0 -25
  146. data/app/interactors/spree_cm_commissioner/stock/inventory_items_adjuster.rb +0 -13
  147. data/app/interactors/spree_cm_commissioner/stock/inventory_items_generator.rb +0 -15
  148. data/app/interactors/spree_cm_commissioner/stock/permanent_inventory_items_generator.rb +0 -75
  149. data/app/interactors/spree_cm_commissioner/stock/stock_movement_creator.rb +0 -24
  150. data/app/interactors/spree_cm_commissioner/user_roles_assigner.rb +0 -22
  151. data/app/jobs/spree_cm_commissioner/ensure_correct_product_type_job.rb +0 -7
  152. data/app/jobs/spree_cm_commissioner/inventory_item_syncer_job.rb +0 -7
  153. data/app/jobs/spree_cm_commissioner/stock/inventory_items_adjuster_job.rb +0 -11
  154. data/app/jobs/spree_cm_commissioner/stock/inventory_items_generator_job.rb +0 -11
  155. data/app/jobs/spree_cm_commissioner/stock/permanent_inventory_items_generator_job.rb +0 -9
  156. data/app/models/spree_cm_commissioner/inventory.rb +0 -11
  157. data/app/models/spree_cm_commissioner/inventory_item.rb +0 -56
  158. data/app/models/spree_cm_commissioner/redis_stock/cached_inventory_items_builder.rb +0 -40
  159. data/app/models/spree_cm_commissioner/redis_stock/inventory_updater.rb +0 -126
  160. data/app/models/spree_cm_commissioner/redis_stock/line_items_cached_inventory_items_builder.rb +0 -36
  161. data/app/models/spree_cm_commissioner/redis_stock/variant_cached_inventory_items_builder.rb +0 -27
  162. data/app/models/spree_cm_commissioner/stock/order_availability_checker.rb +0 -44
  163. data/app/request_schemas/spree_cm_commissioner/variant_request_schema.rb +0 -19
  164. data/app/views/spree/admin/stock_managements/_events_popover.html.erb +0 -23
  165. data/app/views/spree/admin/stock_managements/calendar.html.erb +0 -35
  166. data/db/migrate/20250304293518_create_cm_inventory_items.rb +0 -21
  167. data/db/migrate/20250429094228_add_lock_version_to_cm_inventory_items.rb +0 -5
  168. data/db/migrate/20250502025848_add_index_to_spree_products.rb +0 -5
  169. data/db/migrate/20250502030001_add_product_type_to_spree_variants.rb +0 -5
  170. data/db/migrate/20250502030002_add_product_type_to_spree_line_items.rb +0 -5
  171. data/lib/spree_cm_commissioner/cached_inventory_item.rb +0 -23
  172. data/lib/spree_cm_commissioner/test_helper/factories/inventory_item_factory.rb +0 -9
  173. data/lib/tasks/create_default_non_permanent_inventory_items.rake +0 -16
  174. data/lib/tasks/ensure_correct_product_type.rake +0 -7
  175. data/lib/tasks/generate_inventory_items.rake +0 -7
@@ -4,6 +4,7 @@ module SpreeCmCommissioner
4
4
 
5
5
  def send_pin_code(pin_code_id, action, tenant)
6
6
  @pin_code = SpreeCmCommissioner::PinCode.find(pin_code_id)
7
+ @tenant = tenant
7
8
 
8
9
  @sender_name = sender_name(tenant)
9
10
  @sender_email = sender_email(tenant)
@@ -53,6 +53,8 @@ module SpreeCmCommissioner
53
53
  end
54
54
 
55
55
  def kyc_value_enabled?(bit_value)
56
+ return false if kyc.nil?
57
+
56
58
  kyc & bit_value != 0
57
59
  end
58
60
 
@@ -3,6 +3,7 @@ module SpreeCmCommissioner
3
3
  extend ActiveSupport::Concern
4
4
 
5
5
  included do
6
+ before_validation :set_event_id
6
7
  before_validation :set_duration
7
8
  end
8
9
 
@@ -42,15 +43,18 @@ module SpreeCmCommissioner
42
43
  end
43
44
  end
44
45
 
45
- def event
46
- taxons.event.first
47
- end
48
-
49
46
  private
50
47
 
48
+ def set_event_id
49
+ self.event_id ||= product.event_id
50
+ end
51
+
52
+ # Line item date now depends directly on the event date and variant date.
53
+ # No longer depend on the event section date.
54
+ # This keeps the setup simple for the organizer and consistent for users.
51
55
  def set_duration
52
- self.from_date ||= variant.start_date_time
53
- self.to_date ||= variant.end_date_time
56
+ self.from_date ||= variant.start_date_time || event&.from_date
57
+ self.to_date ||= variant.end_date_time || event&.to_date
54
58
  end
55
59
  end
56
60
  end
@@ -13,13 +13,11 @@ module SpreeCmCommissioner
13
13
  state_machine.after_transition to: :complete, do: :notify_order_complete_telegram_notification_to_user, unless: :subscription?
14
14
  state_machine.after_transition to: :complete, do: :send_order_complete_telegram_alert_to_vendors, unless: :need_confirmation?
15
15
  state_machine.after_transition to: :complete, do: :send_order_complete_telegram_alert_to_store, unless: :need_confirmation?
16
- state_machine.around_transition to: :complete, do: :handle_unstock_in_redis
17
16
 
17
+ state_machine.after_transition to: :complete, do: :send_transaction_email_to_user, if: :user_has_email?
18
18
  state_machine.after_transition to: :resumed, do: :precalculate_conversion
19
- state_machine.around_transition to: :resumed, do: :handle_unstock_in_redis
20
19
 
21
20
  state_machine.after_transition to: :canceled, do: :precalculate_conversion
22
- state_machine.after_transition to: :canceled, do: :restock_inventory_in_redis!
23
21
 
24
22
  scope :accepted, -> { where(request_state: 'accepted') }
25
23
 
@@ -69,29 +67,6 @@ module SpreeCmCommissioner
69
67
  end
70
68
  end
71
69
 
72
- def handle_unstock_in_redis
73
- ActiveRecord::Base.transaction do
74
- yield # Equal to block.call
75
-
76
- # After the transition is complete, the following code will execute first before proceeding to other `after_transition` callbacks.
77
- # This ensures that if `unstock_inventory_in_redis!` fails, the state will be rolled back,
78
- # and neither the `finalize!` method nor any notifications will be triggered.
79
- # The payment will be reversed in vPago gem, and `Spree::Checkout::Complete` will be called, which checks `order.reload.complete?`.
80
- # This is critical because if the order state is complete, the payment will be marked as paid.
81
- CmAppLogger.log(label: 'order_state_machine_before_unstock', data: { order_id: id, state: state })
82
- unstock_inventory_in_redis!
83
- # We rollback only order state, and we keep payment state as it is.
84
- # We implement payment in vPago gem, and it will be reversed in the gem.
85
- # 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.
86
- CmAppLogger.log(label: 'order_state_machine_after_unstock', data: { order_id: id, state: state })
87
- end
88
- rescue StandardError => e
89
- CmAppLogger.log(label: 'order_state_machine',
90
- data: { order_id: id, error: e.message, type: e.class.name, backtrace: e.backtrace.first(5).join("\n") }
91
- )
92
- raise e
93
- end
94
-
95
70
  def generate_bib_number
96
71
  line_items.find_each(&:generate_remaining_guests)
97
72
 
@@ -119,6 +94,18 @@ module SpreeCmCommissioner
119
94
  end
120
95
  end
121
96
 
97
+ def send_transaction_email_to_user
98
+ line_items.each do |line_item|
99
+ next if line_item.event.nil?
100
+
101
+ SpreeCmCommissioner::EventTransactionalMailer.send_to_participant(line_item.event_id, user.id).deliver_later
102
+ end
103
+ end
104
+
105
+ def user_has_email?
106
+ user.present? && user.email.present?
107
+ end
108
+
122
109
  # allow authorized user to accept all requested line items
123
110
  def accepted_by(user)
124
111
  transaction do
@@ -3,11 +3,12 @@ module SpreeCmCommissioner
3
3
  extend ActiveSupport::Concern
4
4
 
5
5
  included do
6
- delegate :subscribable?,
6
+ delegate :product_type,
7
+ :subscribable?,
7
8
  :allowed_upload_later?,
8
- :need_confirmation?, :need_confirmation, :kyc,
9
+ :need_confirmation?, :need_confirmation,
9
10
  :allow_anonymous_booking,
10
- :associated_event,
11
+ :accommodation?, :service?, :ecommerce?,
11
12
  :allow_self_check_in,
12
13
  :allow_self_check_in?,
13
14
  :required_self_check_in_location,
@@ -6,20 +6,10 @@ 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
11
9
 
12
10
  included do
13
11
  enum product_type: PRODUCT_TYPES if table_exists? && column_names.include?('product_type')
14
12
  enum primary_product_type: PRODUCT_TYPES if table_exists? && column_names.include?('primary_product_type')
15
13
  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
24
14
  end
25
15
  end
@@ -25,18 +25,21 @@ module SpreeCmCommissioner
25
25
  end
26
26
 
27
27
  class_methods do
28
- def find_user_by_login(login)
28
+ def find_user_by_login(login, tenant_id)
29
29
  return nil if login.blank?
30
30
 
31
31
  login = login.downcase
32
32
  parser = PhoneNumberParser.call(phone_number: login)
33
33
 
34
+ scope = Spree.user_class.all
35
+ scope = scope.where(tenant_id: tenant_id) if tenant_id.present?
36
+
34
37
  if parser.intel_phone_number.present?
35
- find_by(intel_phone_number: parser.intel_phone_number)
38
+ scope.find_by(intel_phone_number: parser.intel_phone_number)
36
39
  elsif login =~ URI::MailTo::EMAIL_REGEXP
37
- find_by(email: login)
40
+ scope.find_by(email: login)
38
41
  else
39
- find_by(login: login)
42
+ scope.find_by(login: login)
40
43
  end
41
44
  end
42
45
  end
@@ -6,6 +6,8 @@ module SpreeCmCommissioner
6
6
  before_save :set_options_to_public_metadata
7
7
 
8
8
  delegate :location,
9
+ :start_date,
10
+ :start_time,
9
11
  :reminder_in_hours,
10
12
  :duration_in_hours,
11
13
  :duration_in_minutes,
@@ -48,6 +50,8 @@ module SpreeCmCommissioner
48
50
  option_values.detect { |o| o.option_type.name.downcase.strip == option_type_name.downcase.strip }.try(:name)
49
51
  end
50
52
 
53
+ # No need to fallback to event start date to avoid n+1.
54
+ # To get end_time duration of event, include event when needed instead.
51
55
  def start_date_time
52
56
  return nil if start_date.blank?
53
57
  return start_date if start_time.blank?
@@ -55,6 +59,8 @@ module SpreeCmCommissioner
55
59
  start_date.change(hour: start_time.hour, min: start_time.min, sec: start_time.sec)
56
60
  end
57
61
 
62
+ # No need to fallback to event end date to avoid n+1.
63
+ # To get end_time duration of event, include event when needed instead.
58
64
  def end_date_time
59
65
  return nil if end_date.blank?
60
66
  return end_date if end_time.blank?
@@ -62,24 +68,16 @@ module SpreeCmCommissioner
62
68
  end_date.change(hour: end_time.hour, min: end_time.min, sec: end_time.sec)
63
69
  end
64
70
 
65
- def start_date
66
- options.start_date || event&.from_date
67
- end
68
-
69
71
  def end_date
70
72
  return start_date + options.total_duration_in_seconds.seconds if start_date.present? && options.total_duration_in_seconds.positive?
71
73
 
72
- options.end_date || event&.to_date
73
- end
74
-
75
- def start_time
76
- options.start_time || event&.from_date
74
+ options.end_date
77
75
  end
78
76
 
79
77
  def end_time
80
78
  return start_time + options.total_duration_in_seconds.seconds if start_time.present? && options.total_duration_in_seconds.positive?
81
79
 
82
- options.end_time || event&.to_date
80
+ options.end_time
83
81
  end
84
82
 
85
83
  def post_paid?
@@ -0,0 +1,9 @@
1
+ module SpreeCmCommissioner
2
+ module CmsPageDecorator
3
+ def self.prepended(base)
4
+ base.belongs_to :tenant, class_name: 'SpreeCmCommissioner::Tenant'
5
+ end
6
+ end
7
+ end
8
+
9
+ Spree::CmsPage.prepend(SpreeCmCommissioner::CmsPageDecorator) unless Spree::CmsPage.included_modules.include?(SpreeCmCommissioner::CmsPageDecorator)
@@ -53,11 +53,11 @@ module SpreeCmCommissioner
53
53
  end
54
54
 
55
55
  def event_start_date
56
- product.taxons.first&.from_date
56
+ product.event&.from_date
57
57
  end
58
58
 
59
59
  def event_end_date
60
- product.taxons.first&.to_date
60
+ product.event&.to_date
61
61
  end
62
62
 
63
63
  def set_class_id
@@ -117,7 +117,7 @@ module SpreeCmCommissioner
117
117
  end
118
118
 
119
119
  def set_event_id
120
- self.event_id ||= line_item.associated_event&.id if line_item.present?
120
+ self.event_id ||= line_item&.event_id
121
121
  end
122
122
 
123
123
  def assign_seat_number
@@ -0,0 +1,23 @@
1
+ module SpreeCmCommissioner
2
+ class InviteGuest < SpreeCmCommissioner::Base
3
+ enum invite_type: { invite: 0, open: 1, sponsor: 2 }
4
+ enum claimed_status: { active: 0, claimed: 1, revoked: 2 }
5
+
6
+ belongs_to :variant, class_name: 'Spree::Variant'
7
+ belongs_to :order, class_name: 'Spree::Order'
8
+ belongs_to :event, class_name: 'Spree::Taxon'
9
+
10
+ self.whitelisted_ransackable_attributes = %w[claimed_status]
11
+
12
+ def expired?
13
+ expiration_date.present? && expiration_date < Time.current
14
+ end
15
+
16
+ def fully_claimed?
17
+ return false unless order.present? && order.line_items.any?
18
+
19
+ line_item = order.line_items.first
20
+ line_item.guests.count >= order.line_items.first.number_of_guests
21
+ end
22
+ end
23
+ end
@@ -1,19 +1,15 @@
1
1
  module SpreeCmCommissioner
2
2
  module LineItemDecorator
3
- def self.prepended(base) # rubocop:disable Metrics/MethodLength,Metrics/AbcSize
3
+ def self.prepended(base) # rubocop:disable Metrics/MethodLength
4
4
  include_modules(base)
5
5
 
6
6
  base.belongs_to :accepter, class_name: 'Spree::User', optional: true
7
7
  base.belongs_to :rejecter, class_name: 'Spree::User', optional: true
8
+ base.belongs_to :event, class_name: 'Spree::Taxon', optional: true, inverse_of: :line_items
8
9
 
9
10
  base.has_one :google_wallet, class_name: 'SpreeCmCommissioner::GoogleWallet', through: :product
10
11
 
11
12
  base.has_many :option_types, through: :product
12
-
13
- base.has_many :inventory_items, lambda { |line_item|
14
- where(inventory_date: nil).or(where(inventory_date: line_item.date_range))
15
- }, through: :variant
16
-
17
13
  base.has_many :taxons, class_name: 'Spree::Taxon', through: :product
18
14
  base.has_many :guests, class_name: 'SpreeCmCommissioner::Guest', dependent: :destroy
19
15
  base.has_many :pending_guests, pending_guests_query, class_name: 'SpreeCmCommissioner::Guest', dependent: :destroy
@@ -26,15 +22,16 @@ module SpreeCmCommissioner
26
22
  base.validate :validate_seats_reservation, if: :transit?
27
23
 
28
24
  base.before_create :add_due_date, if: :subscription?
29
- base.before_save -> { self.product_type = variant.product_type }, if: -> { product_type.nil? }
30
25
 
31
26
  base.validate :ensure_not_exceed_max_quantity_per_order, if: -> { variant&.max_quantity_per_order.present? }
32
27
 
33
28
  base.whitelisted_ransackable_associations |= %w[guests order]
34
29
  base.whitelisted_ransackable_attributes |= %w[number to_date from_date vendor_id]
35
30
 
36
- base.delegate :delivery_required?, :high_demand,
31
+ base.delegate :delivery_required?, :permanent_stock?, :high_demand, :transit?,
37
32
  to: :variant
33
+ base.delegate :discontinue_on, :product_type, :accommodation?, :service?, :ecommerce?, :need_confirmation,
34
+ to: :product
38
35
 
39
36
  base.accepts_nested_attributes_for :guests, allow_destroy: true
40
37
  base.accepts_nested_attributes_for :line_item_seats, allow_destroy: true
@@ -45,10 +42,6 @@ module SpreeCmCommissioner
45
42
  json_api_columns << :vendor_id
46
43
  end
47
44
 
48
- def discontinue_on
49
- variant.discontinue_on || product.discontinue_on
50
- end
51
-
52
45
  def base.search_by_qr_data!(data)
53
46
  matches = data.match(/(R\d+)-([A-Za-z0-9_\-]+)-(L\d+)/)&.captures
54
47
 
@@ -67,11 +60,14 @@ module SpreeCmCommissioner
67
60
  base.include SpreeCmCommissioner::LineItemDurationable
68
61
  base.include SpreeCmCommissioner::LineItemsFilterScope
69
62
  base.include SpreeCmCommissioner::LineItemGuestsConcern
70
- base.include SpreeCmCommissioner::ProductType
71
63
  base.include SpreeCmCommissioner::ProductDelegation
72
64
  base.include SpreeCmCommissioner::KycBitwise
73
65
  end
74
66
 
67
+ def kyc
68
+ variant.kyc || product.kyc
69
+ end
70
+
75
71
  def self.pending_guests_query
76
72
  lambda {
77
73
  left_outer_joins(:id_card)
@@ -183,7 +179,7 @@ module SpreeCmCommissioner
183
179
 
184
180
  # override
185
181
  def sufficient_stock?
186
- return transit_sufficient_stock? if transit?
182
+ return transit_sufficient_stock? if variant.product.product_type == 'transit'
187
183
 
188
184
  SpreeCmCommissioner::Stock::LineItemAvailabilityChecker.new(self).can_supply?(quantity)
189
185
  end
@@ -239,8 +235,6 @@ module SpreeCmCommissioner
239
235
  end
240
236
 
241
237
  def validate_seats_reservation
242
- return if reservation_trip.blank?
243
-
244
238
  if reservation_trip.allow_seat_selection && !selected_seats_available?
245
239
  errors.add(:base, :some_seats_are_booked, message: 'Some seats are already booked')
246
240
  elsif !reservation_trip.allow_seat_selection && !seat_quantity_available?(reservation_trip)
@@ -1,5 +1,7 @@
1
1
  module SpreeCmCommissioner
2
2
  module OptionTypeDecorator
3
+ RULES_OPTION_TYPE_NAME = 'rules'.freeze
4
+
3
5
  def self.prepended(base)
4
6
  base.include SpreeCmCommissioner::ParameterizeName
5
7
  base.include SpreeCmCommissioner::OptionTypeAttrType
@@ -24,6 +26,10 @@ module SpreeCmCommissioner
24
26
  name: 'vehicle'
25
27
  ).first_or_create
26
28
  end
29
+
30
+ def base.rules_option_type
31
+ Spree::OptionType.find_by(name: RULES_OPTION_TYPE_NAME)
32
+ end
27
33
  end
28
34
 
29
35
  private
@@ -58,13 +58,6 @@ module SpreeCmCommissioner
58
58
  end
59
59
  end
60
60
 
61
- # override
62
- # spree use this method to check stock availability & consider whether :order can continue to next state.
63
- def insufficient_stock_lines
64
- checker = SpreeCmCommissioner::Stock::OrderAvailabilityChecker.new(self)
65
- checker.insufficient_stock_lines
66
- end
67
-
68
61
  def ticket_seller_user?
69
62
  return false if user.nil?
70
63
 
@@ -201,14 +194,6 @@ module SpreeCmCommissioner
201
194
 
202
195
  private
203
196
 
204
- def unstock_inventory_in_redis!
205
- SpreeCmCommissioner::RedisStock::InventoryUpdater.new(line_item_ids).unstock!
206
- end
207
-
208
- def restock_inventory_in_redis!
209
- SpreeCmCommissioner::RedisStock::InventoryUpdater.new(line_item_ids).restock!
210
- end
211
-
212
197
  # override :spree_api
213
198
  def webhook_payload_body
214
199
  resource_serializer.new(
@@ -17,7 +17,7 @@ module SpreeCmCommissioner
17
17
  has_many :products, through: :product_places
18
18
 
19
19
  has_many :children, -> { order(:lft) }, class_name: 'SpreeCmCommissioner::Place', foreign_key: :parent_id, dependent: :destroy
20
- has_many :vendor_stops, class_name: 'SpreeCmCommissioner::VendorStop', dependent: :destroy
20
+ has_many :vendor_stops, class_name: 'SpreeCmCommissioner::VendorStop', dependent: :destroy, foreign_key: :stop_id
21
21
 
22
22
  def self.ransackable_attributes(auth_object = nil)
23
23
  super & %w[name code]
@@ -33,7 +33,7 @@ module SpreeCmCommissioner
33
33
  base.has_one :google_wallet, class_name: 'SpreeCmCommissioner::GoogleWallet', dependent: :destroy
34
34
 
35
35
  base.has_many :complete_line_items, through: :classifications, source: :line_items
36
- base.has_many :inventory_items, through: :variants
36
+ base.has_many :guests, through: :line_items
37
37
 
38
38
  base.has_many :product_places, class_name: 'SpreeCmCommissioner::ProductPlace', dependent: :destroy
39
39
  base.has_many :places, through: :product_places
@@ -44,6 +44,8 @@ module SpreeCmCommissioner
44
44
 
45
45
  base.has_one :trip, class_name: 'SpreeCmCommissioner::Trip', dependent: :destroy
46
46
 
47
+ base.belongs_to :event, class_name: 'Spree::Taxon', optional: true
48
+
47
49
  base.scope :min_price, lambda { |vendor|
48
50
  joins(:prices_including_master)
49
51
  .where(vendor_id: vendor.id, product_type: vendor.primary_product_type)
@@ -57,14 +59,18 @@ module SpreeCmCommissioner
57
59
  }
58
60
  base.scope :subscribable, -> { where(subscribable: 1) }
59
61
 
62
+ base.before_validation :set_event_id
63
+
60
64
  base.validate :validate_event_taxons, if: -> { taxons.event.present? }
65
+
61
66
  base.validate :validate_product_date, if: -> { available_on.present? && discontinue_on.present? }
62
- base.validate :product_type_unchanged, on: :update
67
+
63
68
  base.validates :commission_rate, numericality: { greater_than_or_equal_to: 0, less_than_or_equal_to: 100 }, allow_nil: true
64
69
 
65
70
  base.whitelisted_ransackable_attributes = %w[description name slug discontinue_on status vendor_id short_name route_type]
66
71
 
67
72
  base.after_update :update_variants_vendor_id, if: :saved_change_to_vendor_id?
73
+ base.after_update :sync_event_id_to_children, if: :saved_change_to_event_id?
68
74
 
69
75
  base.enum purchasable_on: { both: 0, web: 1, app: 2 }
70
76
 
@@ -74,10 +80,6 @@ module SpreeCmCommissioner
74
80
  base.before_save :set_tenant
75
81
  end
76
82
 
77
- def associated_event
78
- taxons.event.first&.parent
79
- end
80
-
81
83
  def ticket_url
82
84
  "#{Spree::Store.default.formatted_url}/tickets/#{slug}"
83
85
  end
@@ -88,13 +90,21 @@ module SpreeCmCommissioner
88
90
  self.tenant_id = vendor&.tenant_id
89
91
  end
90
92
 
93
+ def set_event_id
94
+ self.event_id = taxons.select(&:event?).first&.parent_id
95
+ end
96
+
91
97
  def update_variants_vendor_id
92
98
  variants_including_master.find_each { |variant| variant.update!(vendor_id: vendor_id) }
93
99
  end
94
100
 
101
+ def sync_event_id_to_children
102
+ ::SpreeCmCommissioner::ProductEventIdToChildrenSyncerJob.perform_later(id)
103
+ end
104
+
95
105
  def validate_event_taxons
96
106
  errors.add(:taxons, 'Event Taxon can\'t not be more than 1') if taxons.event.size > 1
97
- errors.add(:taxons, 'Must add event date to taxon') if taxons.event.first.from_date.nil? || taxons.event.first.to_date.nil?
107
+ errors.add(:taxons, 'Must add event date to taxon') if event.from_date.nil? || event.to_date.nil?
98
108
  end
99
109
 
100
110
  def validate_product_date
@@ -102,13 +112,6 @@ module SpreeCmCommissioner
102
112
 
103
113
  errors.add(:discontinue_on, 'must be after the available on date')
104
114
  end
105
-
106
- def product_type_unchanged
107
- return if product_type_was.nil?
108
- return unless product_type_changed?
109
-
110
- errors.add(:product_type, 'cannot be changed once set')
111
- end
112
115
  end
113
116
  end
114
117
 
@@ -1,6 +1,7 @@
1
1
  module SpreeCmCommissioner
2
2
  class ProductPlace < Base
3
3
  self.inheritance_column = '_type_disabled'
4
+ acts_as_list column: :position
4
5
 
5
6
  belongs_to :product, class_name: 'Spree::Product', optional: false
6
7
  belongs_to :place, class_name: 'SpreeCmCommissioner::Place', optional: false
@@ -7,6 +7,9 @@ module SpreeCmCommissioner
7
7
  base.belongs_to :vendor, optional: true
8
8
 
9
9
  base.scope :non_vendor, -> { where(vendor_id: nil) }
10
+ base.scope :filter_by_vendor, lambda { |vendor|
11
+ where(vendor_id: vendor)
12
+ }
10
13
 
11
14
  base.accepts_nested_attributes_for :role_permissions, allow_destroy: true
12
15
 
@@ -1,49 +1,47 @@
1
1
  module SpreeCmCommissioner
2
2
  module Stock
3
3
  class AvailabilityChecker
4
- attr_reader :variant, :options, :error_message
4
+ attr_reader :variant, :error_message
5
5
 
6
- def initialize(variant, options = {})
6
+ def initialize(variant)
7
7
  @variant = variant
8
- @options = options
9
8
  @error_message = nil
10
9
  end
11
10
 
12
- def can_supply?(quantity = 1)
11
+ def can_supply?(quantity = 1, options = {})
13
12
  return false unless variant.available?
14
13
  return true unless variant.should_track_inventory?
15
14
  return true if variant.backorderable?
16
15
  return true if variant.need_confirmation?
17
16
 
18
- variant_available?(quantity)
19
- end
20
-
21
- def variant_available?(quantity = 1)
22
- return false if cached_inventory_items.empty?
23
-
24
- cached_inventory_items.all? do |cached_inventory_item|
25
- cached_inventory_item.active? && cached_inventory_item.quantity_available >= quantity
26
- end
27
- end
28
-
29
- def cached_inventory_items
30
- return @cached_inventory_items if defined?(@cached_inventory_items)
17
+ # when delivery required, shipment will dynamically add / remove unit from stock item.
18
+ # so we can directly check can_supply with stock items directly.
19
+ return variant.stock_items.sum(:count_on_hand) >= quantity if variant.delivery_required?
31
20
 
32
21
  if variant.permanent_stock?
33
- return [] if options[:from_date].blank? || options[:to_date].blank?
34
-
35
- @cached_inventory_items = builder_klass.new(
36
- variant_id: variant.id,
37
- from_date: options[:from_date].to_date,
38
- to_date: options[:to_date].to_date
39
- ).call
22
+ permanent_stock_variant_available?(quantity, options)
40
23
  else
41
- @cached_inventory_items = builder_klass.new(variant_id: variant.id).call
24
+ variant_available?(quantity, options)
42
25
  end
43
26
  end
44
27
 
45
- def builder_klass
46
- ::SpreeCmCommissioner::RedisStock::VariantCachedInventoryItemsBuilder
28
+ def variant_available?(quantity = 1, options = {})
29
+ query = SpreeCmCommissioner::VariantAvailability::NonPermanentStockQuery.new(
30
+ variant: variant,
31
+ except_line_item_id: options[:except_line_item_id]
32
+ )
33
+ result = query.available?(quantity)
34
+ @error_message = query.error_message unless result
35
+ result
36
+ end
37
+
38
+ def permanent_stock_variant_available?(quantity = 1, options = {})
39
+ SpreeCmCommissioner::VariantAvailability::PermanentStockQuery.new(
40
+ variant: variant,
41
+ from_date: options[:from_date].to_date,
42
+ to_date: options[:to_date].to_date,
43
+ except_line_item_id: options[:except_line_item_id]
44
+ ).available?(quantity)
47
45
  end
48
46
  end
49
47
  end
@@ -3,8 +3,7 @@ module SpreeCmCommissioner
3
3
  module AvailabilityValidatorDecorator
4
4
  # override
5
5
  def item_available?(line_item, quantity)
6
- SpreeCmCommissioner::Stock::LineItemAvailabilityChecker.new(line_item)
7
- .can_supply?(quantity)
6
+ SpreeCmCommissioner::Stock::LineItemAvailabilityChecker.new(line_item).can_supply?(quantity)
8
7
  end
9
8
  end
10
9
  end
@@ -8,14 +8,14 @@ module SpreeCmCommissioner
8
8
  end
9
9
 
10
10
  def can_supply?(quantity)
11
- ::SpreeCmCommissioner::Stock::AvailabilityChecker.new(line_item.variant, options)
12
- .can_supply?(quantity)
11
+ AvailabilityChecker.new(line_item.variant).can_supply?(quantity, options)
13
12
  end
14
13
 
15
14
  def options
16
15
  {
17
16
  from_date: line_item.from_date,
18
- to_date: line_item.to_date
17
+ to_date: line_item.to_date,
18
+ except_line_item_id: line_item.id
19
19
  }
20
20
  end
21
21
  end