spree_cm_commissioner 2.5.2.pre.pre3 → 2.5.2

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 (31) hide show
  1. checksums.yaml +4 -4
  2. data/Gemfile.lock +1 -1
  3. data/app/controllers/spree/api/v2/storefront/active_homepage_events_controller.rb +1 -6
  4. data/app/controllers/spree/api/v2/storefront/homepage_data_controller.rb +35 -4
  5. data/app/controllers/spree/api/v2/storefront/homepage_sections_controller.rb +4 -41
  6. data/app/controllers/spree/api/v2/storefront/invite_guests_controller.rb +2 -11
  7. data/app/interactors/spree_cm_commissioner/conversion_pre_calculator.rb +12 -3
  8. data/app/interactors/spree_cm_commissioner/notification_reader.rb +3 -1
  9. data/app/models/concerns/spree_cm_commissioner/option_type_attr_type.rb +1 -2
  10. data/app/models/concerns/spree_cm_commissioner/order_state_machine.rb +28 -4
  11. data/app/models/concerns/spree_cm_commissioner/variant_options_concern.rb +0 -1
  12. data/app/models/spree_cm_commissioner/guest.rb +3 -24
  13. data/app/models/spree_cm_commissioner/notification.rb +28 -0
  14. data/app/models/spree_cm_commissioner/taxon_decorator.rb +0 -26
  15. data/app/models/spree_cm_commissioner/variant_options.rb +0 -5
  16. data/app/serializers/spree/v2/storefront/product_serializer_decorator.rb +7 -36
  17. data/app/serializers/spree/v2/storefront/user_serializer_decorator.rb +2 -1
  18. data/app/serializers/spree_cm_commissioner/v2/storefront/homepage_data_serializer.rb +3 -9
  19. data/app/services/spree_cm_commissioner/guests/finalize.rb +76 -0
  20. data/app/services/spree_cm_commissioner/homepage_data.rb +23 -0
  21. data/app/services/spree_cm_commissioner/user_counters_service.rb +55 -0
  22. data/app/views/spree/admin/classifications/edit.html.erb +1 -1
  23. data/db/migrate/20260128043540_add_counter_cache_to_spree_users.rb +6 -0
  24. data/lib/spree_cm_commissioner/test_helper/factories/option_type_factory.rb +0 -6
  25. data/lib/spree_cm_commissioner/test_helper/factories/product_factory.rb +0 -13
  26. data/lib/spree_cm_commissioner/version.rb +1 -1
  27. metadata +7 -7
  28. data/app/controllers/concerns/spree/api/v2/product_list_includes_decorator.rb +0 -37
  29. data/app/controllers/spree/api/v2/storefront/products_controller_decorator.rb +0 -27
  30. data/app/controllers/spree/api/v2/storefront/taxons_controller_decorator.rb +0 -92
  31. data/app/models/spree/stock/quantifier_decorator.rb +0 -26
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: aa0e45576d6b86ad5c668c0ffe85f437e4e0e0ee01dcab1bef8c3cb4628950f9
4
- data.tar.gz: b0d0683d2b81ede7e5200c270ac8d0b52d9c48dcb7e3223c94cf9f944bafe864
3
+ metadata.gz: 847e4b0b554ef076b2daeafe2d90509a434254ef6bb7bc19d34905d9a72f5a42
4
+ data.tar.gz: ce1c22521e38c5d012d2cb3fbe1ac041958a6054808c9b1786f4446a3046483d
5
5
  SHA512:
6
- metadata.gz: 6304eee33f234cd8bcd652faefe614e48060b4bb0b0d61a7e5343935d9228f5fd66fcf46a6a24877cac346c161f5863f4bc84f3fe0021715d668f5021e64343c
7
- data.tar.gz: aaeb3660e2c2cd02e453e4fc83daae2be76cb7611c12ee2bc2f15de05a0172258a2bc336e05366544060611a827d04c19fa4726fe41c8b3ae5606909c7488eb9
6
+ metadata.gz: 1bfbb01640f43c2b9540063a8c5e368b44b4009ecd27653f739c030c65ceffcdc48d1141d466f90e2870d3c088f933260366f9db3f3bfb4b1c68dba402f5522e
7
+ data.tar.gz: 7f128de24e2ad50a06c9a148dc37c2ccc4e8396077a32c2456a89a35b45236562073a185f67e8e50dca5b1a59efd9b6dbcfeef2018aa63becc88b334fa070609
data/Gemfile.lock CHANGED
@@ -34,7 +34,7 @@ GIT
34
34
  PATH
35
35
  remote: .
36
36
  specs:
37
- spree_cm_commissioner (2.5.2.pre.pre3)
37
+ spree_cm_commissioner (2.5.2)
38
38
  activerecord-multi-tenant
39
39
  activerecord_json_validator (~> 2.1, >= 2.1.3)
40
40
  aws-sdk-cloudfront
@@ -6,12 +6,7 @@ module Spree
6
6
  private
7
7
 
8
8
  def collection
9
- @collection ||= model_class.active_homepage_events.includes(category_icon: { attachment_attachment: :blob })
10
- end
11
-
12
- def serialize_collection(collection)
13
- model_class.preload_ancestors_for(collection)
14
- super(collection)
9
+ @collection ||= model_class.active_homepage_events
15
10
  end
16
11
 
17
12
  def model_class
@@ -3,12 +3,43 @@ module Spree
3
3
  module V2
4
4
  module Storefront
5
5
  class HomepageDataController < ::Spree::Api::V2::ResourceController
6
+ before_action :load_homepage_background, only: [:show]
7
+ before_action :load_menu, only: [:show]
8
+
6
9
  def show
7
- home_data_loader = SpreeCmCommissioner::HomepageDataLoader.with_cache
10
+ resource = SpreeCmCommissioner::HomepageData.new(
11
+ menu: @menu,
12
+ homepage_background: @homepage_background
13
+ )
14
+ render_serialized_payload { serialize_resource(resource) }
15
+ end
16
+
17
+ private
18
+
19
+ def serialize_resource(resource)
20
+ resource_serializer.new(
21
+ resource,
22
+ params: serializer_params,
23
+ include: resource_includes
24
+ ).serializable_hash
25
+ end
26
+
27
+ def load_homepage_background
28
+ @homepage_background ||= SpreeCmCommissioner::HomepageBackground
29
+ .active
30
+ .includes(:app_image, :web_image)
31
+ .where(segment: params[:segment] || :general)
32
+ .order(priority: :asc)
33
+ .first
34
+ end
8
35
 
9
- render_serialized_payload do
10
- serialize_resource(home_data_loader)
11
- end
36
+ def load_menu
37
+ # Spree::Menu::MENU_LOCATIONS = ['Header', 'Footer']
38
+ # Current our homepage use only location: header
39
+ @menu ||= Spree::Menu.by_locale(I18n.locale)
40
+ .includes({ menu_items: %i[children parent icon] })
41
+ .where(location: 'header')
42
+ .first
12
43
  end
13
44
 
14
45
  def resource_serializer
@@ -16,47 +16,10 @@ module Spree
16
16
  end
17
17
 
18
18
  def collection
19
- @collection = model_class.filter_by_segment(params[:homepage_id] || :general)
20
- .active
21
- .where(tenant_id: nil)
22
- .order(position: :asc)
23
- .includes(preload_associations)
24
- end
25
-
26
- def preload_associations
27
- {
28
- homepage_section_relatables: {
29
- relatable: [
30
- :icon,
31
- {
32
- home_banner: { attachment_attachment: :blob }
33
- },
34
- {
35
- category_icon: { attachment_attachment: :blob }
36
- },
37
- {
38
- video_banner: { attachment_attachment: :blob }
39
- },
40
- {
41
- app_banner: { attachment_attachment: :blob }
42
- },
43
- {
44
- web_banner: { attachment_attachment: :blob }
45
- },
46
- {
47
- children: [
48
- :icon,
49
- {
50
- vendors: [
51
- { photos: { attachment_attachment: :blob } },
52
- :default_state
53
- ]
54
- }
55
- ]
56
- }
57
- ]
58
- }
59
- }
19
+ @collection ||= model_class.filter_by_segment(params[:homepage_id] || :general)
20
+ .active
21
+ .where(tenant_id: nil)
22
+ .order(position: :asc)
60
23
  end
61
24
  end
62
25
  end
@@ -42,17 +42,8 @@ module Spree
42
42
  end
43
43
 
44
44
  def load_invite_guest_by_token
45
- @invite_guest = SpreeCmCommissioner::InviteGuest.includes(
46
- order: {
47
- line_items: {
48
- guests: %i[
49
- guest_dynamic_fields
50
- id_card
51
- check_in
52
- ]
53
- }
54
- }
55
- ).find_by(token: params[:id])
45
+ @invite_guest = SpreeCmCommissioner::InviteGuest.find_by!(token: params[:id])
46
+ rescue ActiveRecord::RecordNotFound
56
47
  render_error_payload(I18n.t('invite.url_not_found'))
57
48
  end
58
49
 
@@ -5,7 +5,7 @@ module SpreeCmCommissioner
5
5
  def call
6
6
  update_conversion
7
7
  reassign_guests_event_id
8
- generate_guests_bib_number
8
+ refinalize_guests
9
9
  end
10
10
 
11
11
  def update_conversion
@@ -28,14 +28,23 @@ module SpreeCmCommissioner
28
28
  end
29
29
  end
30
30
 
31
- def generate_guests_bib_number
31
+ def refinalize_guests
32
32
  return if event_id.blank?
33
33
 
34
34
  SpreeCmCommissioner::Guest
35
35
  .complete
36
36
  .where(event_id: event_id)
37
37
  .none_bib
38
- .find_each(&:generate_bib!)
38
+ .find_each do |guest|
39
+ result = SpreeCmCommissioner::Guests::Finalize.call(
40
+ guest: guest,
41
+ user: guest.line_item.order.user,
42
+ event: guest.event,
43
+ variant: guest.variant
44
+ )
45
+
46
+ result.success? || raise(ActiveRecord::RecordInvalid, guest)
47
+ end
39
48
  end
40
49
 
41
50
  def event_id
@@ -15,9 +15,11 @@ module SpreeCmCommissioner
15
15
  end
16
16
 
17
17
  def read_all_user_unread_notifications
18
- # mark all unread notifications accept type 'guest_dynamic_field_notification'
18
+ # mark all unread notifications except type 'guest_dynamic_field_notification'
19
19
  # due to it requires user to fill info before mark as read
20
20
  user.notifications.markable_notifications.mark_as_read!
21
+
22
+ SpreeCmCommissioner::UserCountersService.reset_unread_notifications_count(user)
21
23
  end
22
24
  end
23
25
  end
@@ -45,8 +45,7 @@ module SpreeCmCommissioner
45
45
  'color' => 'color',
46
46
  'ticket-type' => 'string',
47
47
  'seat-type' => 'string',
48
- 'intercity-taxi' => 'string',
49
- 'rules' => 'string'
48
+ 'intercity-taxi' => 'string'
50
49
  }.freeze
51
50
 
52
51
  included do
@@ -4,7 +4,7 @@ module SpreeCmCommissioner
4
4
  module OrderStateMachine
5
5
  extend ActiveSupport::Concern
6
6
 
7
- included do
7
+ included do # rubocop:disable Metrics/BlockLength
8
8
  state_machine.before_transition to: :address, do: :hold_blocks
9
9
  state_machine.before_transition to: :payment, do: :ensure_blocks_held
10
10
 
@@ -43,12 +43,14 @@ module SpreeCmCommissioner
43
43
  event :request do
44
44
  transition from: nil, to: :requested
45
45
  end
46
+ after_transition to: :requested, do: :increment_user_request_orders_count
46
47
  after_transition to: :requested, do: :send_order_requested_app_notification_to_user
47
48
  after_transition to: :requested, do: :send_order_requested_telegram_alert_to_store
48
49
 
49
50
  event :accept do
50
51
  transition from: :requested, to: :accepted
51
52
  end
53
+ after_transition from: :requested, to: :accepted, do: :decrement_user_request_orders_count
52
54
  after_transition to: :accepted, do: :send_order_accepted_app_notification_to_user
53
55
  after_transition to: :accepted, do: :send_order_accepted_telegram_alert_to_store
54
56
 
@@ -59,6 +61,8 @@ module SpreeCmCommissioner
59
61
  transition from: :requested, to: :rejected
60
62
  end
61
63
 
64
+ after_transition from: :requested, to: :rejected, do: :decrement_user_request_orders_count
65
+
62
66
  after_transition to: :rejected, do: :send_order_rejected_app_notification_to_user
63
67
  after_transition to: :rejected, do: :send_order_rejected_telegram_alert_to_store
64
68
 
@@ -72,6 +76,18 @@ module SpreeCmCommissioner
72
76
  end
73
77
  end
74
78
 
79
+ def increment_user_request_orders_count
80
+ return if user.nil?
81
+
82
+ SpreeCmCommissioner::UserCountersService.increment_request_orders_count(user)
83
+ end
84
+
85
+ def decrement_user_request_orders_count
86
+ return if user.nil?
87
+
88
+ SpreeCmCommissioner::UserCountersService.decrement_request_orders_count(user)
89
+ end
90
+
75
91
  def product_completion_steps_exist?
76
92
  product_completion_steps.any?
77
93
  end
@@ -174,9 +190,17 @@ module SpreeCmCommissioner
174
190
  line_item.generate_remaining_guests
175
191
 
176
192
  line_item.guests.each do |guest|
177
- guest.user = user if user.present?
178
- guest.generate_bib_if_needed
179
- guest.save!
193
+ result = SpreeCmCommissioner::Guests::Finalize.call(
194
+ guest: guest,
195
+ user: user,
196
+ event: line_item.event,
197
+ variant: line_item.variant
198
+ )
199
+
200
+ unless result.success?
201
+ guest.errors.add(:base, result.error.value || result.error.to_s)
202
+ raise(ActiveRecord::RecordInvalid, guest)
203
+ end
180
204
  end
181
205
  end
182
206
  end
@@ -26,7 +26,6 @@ module SpreeCmCommissioner
26
26
  :bib_prefix,
27
27
  :bib_zerofill,
28
28
  :bib_display_prefix?,
29
- :bib_pre_generation_on_create?,
30
29
  :seat_number_positions,
31
30
  :seat_number_layouts,
32
31
  :color,
@@ -62,7 +62,6 @@ module SpreeCmCommissioner
62
62
  before_validation :assign_seat_number_with_block, if: -> { will_save_change_to_block_id? }
63
63
 
64
64
  before_save :preload_eligible_check_in_session_ids, if: -> { new_record? }
65
- before_create :generate_bib_if_needed, if: -> { line_item.reload && variant.bib_pre_generation_on_create? }
66
65
  after_create :preload_order_block_ids, if: -> { block_id.present? }
67
66
  after_update :preload_order_block_ids, if: :saved_change_to_block_id?
68
67
  before_destroy :cancel_reserved_block!, if: -> { reserved_block.present? }
@@ -248,26 +247,6 @@ module SpreeCmCommissioner
248
247
  line_item.variant.bib_display_prefix?
249
248
  end
250
249
 
251
- def generate_bib_if_needed
252
- return if bib_prefix.present?
253
- return unless bib_required?
254
- return if event_id.blank?
255
-
256
- self.bib_prefix = line_item.variant.bib_prefix
257
-
258
- last_bib_number = event.guests
259
- .where(bib_prefix: bib_prefix)
260
- .maximum(:bib_number) || 0
261
-
262
- self.bib_number = last_bib_number + 1
263
- self.bib_index = "#{event_id}-#{bib_prefix}-#{bib_number}"
264
- end
265
-
266
- def generate_bib!
267
- generate_bib_if_needed
268
- save!
269
- end
270
-
271
250
  # bib_number: 345, bib_prefix: 5KM, bib_zerofill: 5 => return 5KM00345
272
251
  # bib_number: 345, bib_prefix: 5KM, bib_zerofill: 2 => return 5KM345
273
252
  def formatted_bib_number
@@ -446,15 +425,15 @@ module SpreeCmCommissioner
446
425
  end
447
426
 
448
427
  def pre_registration_fields?
449
- line_item&.product&.dynamic_fields&.select(&:pre_registration?)&.any?
428
+ line_item&.product&.dynamic_fields&.pre_registration&.any?
450
429
  end
451
430
 
452
431
  def post_registration_fields?
453
- line_item&.product&.dynamic_fields&.select(&:post_registration?)&.any?
432
+ line_item&.product&.dynamic_fields&.post_registration&.any?
454
433
  end
455
434
 
456
435
  def during_check_in_fields?
457
- line_item&.product&.dynamic_fields&.select(&:during_check_in?)&.any?
436
+ line_item&.product&.dynamic_fields&.during_check_in&.any?
458
437
  end
459
438
 
460
439
  def product_dynamic_fields
@@ -26,5 +26,33 @@ module SpreeCmCommissioner
26
26
 
27
27
  belongs_to :recipient, polymorphic: true
28
28
  belongs_to :notificable, polymorphic: true
29
+
30
+ after_create :increment_user_counters
31
+ after_update :decrement_user_counters, if: :saved_change_to_read_at?
32
+ after_destroy :decrement_user_counters
33
+
34
+ private
35
+
36
+ def increment_user_counters
37
+ return unless recipient.is_a?(Spree::User)
38
+
39
+ return unless unread?
40
+
41
+ SpreeCmCommissioner::UserCountersService.increment_unread_notifications_count(recipient)
42
+ end
43
+
44
+ def decrement_user_counters
45
+ return unless recipient.is_a?(Spree::User)
46
+
47
+ # On update: decrement only if transitioning from unread (nil) to read (present)
48
+ if saved_change_to_read_at
49
+ return unless saved_change_to_read_at[0].nil? && saved_change_to_read_at[1].present?
50
+ # On destroy: decrement only if currently unread
51
+ else
52
+ return unless read_at.nil?
53
+ end
54
+
55
+ SpreeCmCommissioner::UserCountersService.decrement_unread_notifications_count(recipient)
56
+ end
29
57
  end
30
58
  end
@@ -94,32 +94,6 @@ module SpreeCmCommissioner
94
94
  def base.find_event(id)
95
95
  find_by(slug: "events-#{id}")
96
96
  end
97
-
98
- def base.preload_ancestors_for(taxons)
99
- return taxons if taxons.empty?
100
-
101
- # Build a single query to fetch all ancestors for all taxons
102
- conditions = taxons.map do |t|
103
- sanitize_sql_array(['(lft <= ? AND rgt >= ?)', t.lft, t.rgt])
104
- end.join(' OR ')
105
- all_ancestors = where(conditions).distinct.order(:lft).to_a
106
-
107
- # Store preloaded ancestors for each taxon
108
- taxons.each do |t|
109
- taxon_ancestors = all_ancestors.select { |a| a.lft <= t.lft && a.rgt >= t.rgt && a.id != t.id }
110
- t.instance_variable_set(:@preloaded_ancestors, taxon_ancestors)
111
- end
112
-
113
- taxons
114
- end
115
- end
116
-
117
- def pretty_name
118
- ancestor_chain = @preloaded_ancestors || ancestors
119
- ancestor_chain = ancestor_chain.inject('') do |chain, ancestor|
120
- chain + "#{ancestor.name} -> "
121
- end
122
- ancestor_chain + name.to_s
123
97
  end
124
98
 
125
99
  def set_kind
@@ -126,11 +126,6 @@ module SpreeCmCommissioner
126
126
  @bib_display_prefix == 1
127
127
  end
128
128
 
129
- def bib_pre_generation_on_create?
130
- @bib_pre_generation_on_create ||= option_value_name_for(option_type_name: 'bib-pre-generation-on-create')&.to_i || 0
131
- @bib_pre_generation_on_create == 1
132
- end
133
-
134
129
  def seat_number_positions
135
130
  @seat_number_positions ||= option_value_name_for(option_type_name: 'seat-number-positions')&.split(',')
136
131
  end
@@ -2,52 +2,24 @@ module Spree
2
2
  module V2
3
3
  module Storefront
4
4
  module ProductSerializerDecorator
5
- def self.prepended(base)
6
- add_option_type_associations(base)
7
- add_promotion_associations(base)
8
- add_dynamic_field_associations(base)
9
- add_taxon_associations(base)
10
- add_basic_attributes(base)
11
- add_custom_attributes(base)
12
- add_cache_and_action_button(base)
13
- end
14
-
15
- def self.add_option_type_associations(base)
5
+ def self.prepended(base) # rubocop:disable Metrics/MethodLength
16
6
  base.has_many :variant_kind_option_types, serializer: :option_type
17
7
  base.has_many :product_kind_option_types, serializer: :option_type
18
8
  base.has_many :promoted_option_types, serializer: :option_type
19
- end
20
-
21
- def self.add_promotion_associations(base)
22
9
  base.has_many :possible_promotions, serializer: ::SpreeCmCommissioner::V2::Storefront::PromotionSerializer
23
- end
24
-
25
- def self.add_dynamic_field_associations(base)
26
10
  base.has_many :dynamic_fields, serializer: SpreeCmCommissioner::V2::Storefront::DynamicFieldSerializer
27
- end
28
11
 
29
- def self.add_taxon_associations(base)
30
- base.has_many :taxons, serializer: :taxon, record_type: :taxon do |object, params|
31
- filter_store_taxons(object, params)
32
- end
33
- end
34
-
35
- def self.filter_store_taxons(object, params)
36
- object.classifications
37
- .map(&:taxon)
38
- .select { |taxon| taxon.taxonomy&.store_id == params[:store]&.id }
39
- .sort_by(&:id)
40
- end
41
-
42
- def self.add_basic_attributes(base)
43
12
  base.has_one :default_state, serializer: :state
44
13
  base.has_one :venue, serializer: ::SpreeCmCommissioner::V2::Storefront::ProductPlaceSerializer
14
+
45
15
  base.attributes :need_confirmation, :product_type, :kyc, :kyc_fields, :allowed_upload_later, :allow_anonymous_booking, :use_video_as_default
46
16
  base.attributes :reveal_description, :discontinue_on, :public_metadata, :purchasable_on
17
+
18
+ # Expose only the `event_id` here instead of the full event object.
19
+ # This lets the client fetch event details separately (usually already cached),
20
+ # avoids extra DB queries from a `has_one :event` association, and keeps this response lightweight.
47
21
  base.attributes :event_id
48
- end
49
22
 
50
- def self.add_custom_attributes(base)
51
23
  base.attribute :purchasable_on_app do |product|
52
24
  product.purchasable_on == 'app' || product.purchasable_on == 'both'
53
25
  end
@@ -75,10 +47,9 @@ module Spree
75
47
  value = product.available?
76
48
  [true, false].include?(value) ? value : nil
77
49
  end
78
- end
79
50
 
80
- def self.add_cache_and_action_button(base)
81
51
  base.cache_options store: nil
52
+
82
53
  base.attribute :action_button, &:action_button
83
54
  end
84
55
  end
@@ -6,7 +6,8 @@ module Spree
6
6
  base.attributes :first_name, :last_name, :gender, :phone_number, :intel_phone_number,
7
7
  :country_code, :otp_enabled, :otp_email, :otp_phone_number,
8
8
  :confirm_pin_code_enabled, :tenant_id, :has_incomplete_guest_info,
9
- :login, :qr_data, :qr_data_version, :qr_data_invalidated_at
9
+ :login, :qr_data, :qr_data_version, :qr_data_invalidated_at,
10
+ :unread_notifications_count, :request_orders_count
10
11
 
11
12
  base.has_one :profile, serializer: ::Spree::V2::Storefront::UserProfileSerializer
12
13
  base.has_many :device_tokens, serializer: Spree::V2::Storefront::UserDeviceTokenSerializer
@@ -2,16 +2,10 @@ module SpreeCmCommissioner
2
2
  module V2
3
3
  module Storefront
4
4
  class HomepageDataSerializer < BaseSerializer
5
- set_type :homepage_data
5
+ attributes :menu_id, :homepage_background_id
6
6
 
7
- has_many :homepage_backgrounds, serializer: :homepage_background
8
- has_many :homepage_banners, serializer: :homepage_banner
9
- has_many :featured_vendors, serializer: ::Spree::V2::Storefront::AccommodationSerializer
10
-
11
- # has_many :trending_categories, serializer: :category_taxon
12
- # has_many :top_categories, serializer: :category_taxon
13
- # has_many :display_products, serializer: :taxon_include_product
14
- # has_many :featured_brands, serializer: :brand_taxon
7
+ has_one :menu, serializer: Spree::Api::Dependencies.storefront_menu_serializer.constantize
8
+ has_one :homepage_background, serializer: SpreeCmCommissioner::V2::Storefront::HomepageBackgroundSerializer
15
9
  end
16
10
  end
17
11
  end
@@ -0,0 +1,76 @@
1
+ module SpreeCmCommissioner
2
+ module Guests
3
+ class Finalize
4
+ prepend ::Spree::ServiceModule::Base
5
+
6
+ MAX_RETRY_ATTEMPTS = 5
7
+
8
+ def call(guest:, variant:, event: nil, user: nil) # rubocop:disable Metrics/MethodLength,Metrics/PerceivedComplexity
9
+ attempts = 0
10
+
11
+ begin
12
+ attempts += 1
13
+
14
+ guest.user = user if user.present?
15
+ assign_bib(guest: guest, event: event, variant: variant)
16
+ guest.save!
17
+
18
+ success(guest)
19
+ rescue ActiveRecord::RecordInvalid => e
20
+ # Handle Rails-level validation error
21
+ if e.record.errors[:bib_index].any? && attempts < MAX_RETRY_ATTEMPTS
22
+ # Reload guest to get fresh data and retry
23
+ if guest.persisted?
24
+ guest.reload
25
+ else
26
+ # Clear bib values for new records so they get recalculated on retry
27
+ guest.bib_prefix = nil
28
+ guest.bib_number = nil
29
+ guest.bib_index = nil
30
+ end
31
+ retry
32
+ else
33
+ # Return failure if not a bib_index error or exceeded max attempts
34
+ failure(nil, e.message)
35
+ end
36
+ rescue ActiveRecord::RecordNotUnique => e
37
+ # Handle database-level uniqueness constraint violation
38
+ # Return failure if not a bib_index error or exceeded max attempts
39
+ return failure(nil, e.message) unless e.message.include?('bib_index') && attempts < MAX_RETRY_ATTEMPTS
40
+
41
+ # Reload guest to get fresh data and retry
42
+ if guest.persisted?
43
+ guest.reload
44
+ else
45
+ # Clear bib values for new records so they get recalculated on retry
46
+ guest.bib_prefix = nil
47
+ guest.bib_number = nil
48
+ guest.bib_index = nil
49
+ end
50
+ retry
51
+ end
52
+ end
53
+
54
+ def assign_bib(guest:, event:, variant:)
55
+ bib_prefix = variant.bib_prefix
56
+
57
+ return false unless variant.bib_required?
58
+
59
+ return false if guest.bib_prefix.present?
60
+ return false if guest.event_id.blank?
61
+ return false if event.nil? || guest.event_id != event.id
62
+
63
+ guest.bib_prefix = bib_prefix
64
+
65
+ last_bib_number = event.guests
66
+ .where(bib_prefix: bib_prefix)
67
+ .maximum(:bib_number) || 0
68
+
69
+ guest.bib_number = last_bib_number + 1
70
+ guest.bib_index = "#{event.id}-#{bib_prefix}-#{guest.bib_number}"
71
+
72
+ true
73
+ end
74
+ end
75
+ end
76
+ end
@@ -0,0 +1,23 @@
1
+ module SpreeCmCommissioner
2
+ class HomepageData
3
+ attr_reader :menu, :homepage_background
4
+
5
+ # Plain Old Ruby Object (PORO) for homepage data
6
+ def initialize(menu:, homepage_background:)
7
+ @menu = menu
8
+ @homepage_background = homepage_background
9
+ end
10
+
11
+ def id
12
+ @id ||= SecureRandom.uuid
13
+ end
14
+
15
+ def menu_id
16
+ menu&.id
17
+ end
18
+
19
+ def homepage_background_id
20
+ homepage_background&.id
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,55 @@
1
+ module SpreeCmCommissioner
2
+ class UserCountersService
3
+ def self.increment_unread_notifications_count(user)
4
+ return if user.nil?
5
+
6
+ user.increment!(:unread_notifications_count) # rubocop:disable Rails/SkipsModelValidations
7
+ end
8
+
9
+ def self.decrement_unread_notifications_count(user)
10
+ return if user.nil?
11
+
12
+ user.class
13
+ .where(id: user.id)
14
+ .where('unread_notifications_count > 0')
15
+ .update_all('unread_notifications_count = unread_notifications_count - 1') # rubocop:disable Rails/SkipsModelValidations
16
+ end
17
+
18
+ def self.reset_unread_notifications_count(user)
19
+ return if user.nil?
20
+
21
+ user.unread_notifications_count = 0
22
+ user.save!
23
+ end
24
+
25
+ def self.increment_request_orders_count(user)
26
+ return if user.nil?
27
+
28
+ user.increment!(:request_orders_count) # rubocop:disable Rails/SkipsModelValidations
29
+ end
30
+
31
+ def self.decrement_request_orders_count(user)
32
+ return if user.nil?
33
+
34
+ user.class
35
+ .where(id: user.id)
36
+ .where('request_orders_count > 0')
37
+ .update_all('request_orders_count = request_orders_count - 1') # rubocop:disable Rails/SkipsModelValidations
38
+ end
39
+
40
+ # This Method is to Resolve the race condition of increment and decrement user counters
41
+ # We will create a Cron job to sync user counters from database Later
42
+ def self.sync_counters(user)
43
+ return if user.nil?
44
+
45
+ unread = user.notifications.unread_notifications.count
46
+ requested = user.orders.where(request_state: 'requested').count
47
+
48
+ user.update_columns( # rubocop:disable Rails/SkipsModelValidations
49
+ unread_notifications_count: unread,
50
+ request_orders_count: requested,
51
+ updated_at: Time.current
52
+ )
53
+ end
54
+ end
55
+ end
@@ -24,7 +24,7 @@
24
24
  <%= f.field_container :product do %>
25
25
  <%= f.label :product_id, Spree.t(:products) %>
26
26
  <%= f.select :product_id,
27
- options_from_collection_for_select(Spree::Product.all, :id, :name),
27
+ options_from_collection_for_select(Spree::Product.all, :id, :name, f.object.product_id),
28
28
  { include_hidden: true }, class: 'select2 form-control', disabled: f.object.persisted? %>
29
29
  <%= f.error_message_on :product_id %>
30
30
  <% end %>
@@ -0,0 +1,6 @@
1
+ class AddCounterCacheToSpreeUsers < ActiveRecord::Migration[7.0]
2
+ def change
3
+ add_column :spree_users, :unread_notifications_count, :integer, default: 0, null: false, if_not_exists: true
4
+ add_column :spree_users, :request_orders_count, :integer, default: 0, null: false, if_not_exists: true
5
+ end
6
+ end
@@ -141,12 +141,6 @@ FactoryBot.define do
141
141
  presentation { 'Should display bib prefix?' }
142
142
  end
143
143
 
144
- trait :bib_pre_generation_on_create do
145
- attr_type { :boolean }
146
- name { 'bib-pre-generation-on-create' }
147
- presentation { 'Should pre generate bib on create' }
148
- end
149
-
150
144
  trait :seat_number_positions do
151
145
  attr_type { :array }
152
146
  name { 'seat-number-positions' }
@@ -103,31 +103,18 @@ FactoryBot.define do
103
103
  transient do
104
104
  variant1_bib_prefix { '3KM' }
105
105
  variant2_bib_prefix { '5KM' }
106
- bib_pre_generation_on_create { false }
107
- end
108
-
109
- before(:create) do |product, evaluator|
110
- if evaluator.bib_pre_generation_on_create
111
- product.option_types << create(:cm_option_type, :bib_pre_generation_on_create)
112
- end
113
106
  end
114
107
 
115
108
  after(:create) do |product, evaluator|
116
109
  option_value1 = create(:cm_option_value, presentation: evaluator.variant1_bib_prefix, name: evaluator.variant1_bib_prefix, option_type: product.option_types[0])
117
110
  option_value2 = create(:cm_option_value, presentation: evaluator.variant2_bib_prefix, name: evaluator.variant2_bib_prefix, option_type: product.option_types[0])
118
111
 
119
- bib_pre_generation_option_value = if evaluator.bib_pre_generation_on_create
120
- create(:cm_option_value, presentation: 'Yes', name: '1', option_type: product.option_types.find_by(name: 'bib-pre-generation-on-create'))
121
- end
122
-
123
112
  variant1 = create(:cm_variant, price: product.price, product: product)
124
113
  variant1.option_values = [option_value1]
125
- variant1.option_values << bib_pre_generation_option_value if evaluator.bib_pre_generation_on_create
126
114
  variant1.save!
127
115
 
128
116
  variant2 = create(:cm_variant, price: product.price, product: product)
129
117
  variant2.option_values = [option_value2]
130
- variant2.option_values << bib_pre_generation_option_value if evaluator.bib_pre_generation_on_create
131
118
  variant2.save!
132
119
  end
133
120
  end
@@ -1,5 +1,5 @@
1
1
  module SpreeCmCommissioner
2
- VERSION = '2.5.2-pre3'.freeze
2
+ VERSION = '2.5.2'.freeze
3
3
 
4
4
  module_function
5
5
 
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: spree_cm_commissioner
3
3
  version: !ruby/object:Gem::Version
4
- version: 2.5.2.pre.pre3
4
+ version: 2.5.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - You
@@ -817,7 +817,6 @@ files:
817
817
  - app/controllers/.gitkeep
818
818
  - app/controllers/blazer/base_controller_decorator.rb
819
819
  - app/controllers/blazer/queries_controller_decorator.rb
820
- - app/controllers/concerns/spree/api/v2/product_list_includes_decorator.rb
821
820
  - app/controllers/concerns/spree/billing/order_parents_concern.rb
822
821
  - app/controllers/concerns/spree/billing/payment_creatable.rb
823
822
  - app/controllers/concerns/spree/billing/payment_fireable.rb
@@ -980,7 +979,6 @@ files:
980
979
  - app/controllers/spree/api/v2/storefront/pin_code_otp_checkers_controller.rb
981
980
  - app/controllers/spree/api/v2/storefront/pin_code_otp_generators_controller.rb
982
981
  - app/controllers/spree/api/v2/storefront/popular_route_places_controller.rb
983
- - app/controllers/spree/api/v2/storefront/products_controller_decorator.rb
984
982
  - app/controllers/spree/api/v2/storefront/profile_images_controller.rb
985
983
  - app/controllers/spree/api/v2/storefront/provinces_controller.rb
986
984
  - app/controllers/spree/api/v2/storefront/qr_urls_controller.rb
@@ -990,7 +988,6 @@ files:
990
988
  - app/controllers/spree/api/v2/storefront/s3_signed_urls_controller.rb
991
989
  - app/controllers/spree/api/v2/storefront/seat_layouts_controller.rb
992
990
  - app/controllers/spree/api/v2/storefront/self_check_in_controller.rb
993
- - app/controllers/spree/api/v2/storefront/taxons_controller_decorator.rb
994
991
  - app/controllers/spree/api/v2/storefront/tenants_controller.rb
995
992
  - app/controllers/spree/api/v2/storefront/transit/draft_orders_controller.rb
996
993
  - app/controllers/spree/api/v2/storefront/trip_places_controller.rb
@@ -1414,7 +1411,6 @@ files:
1414
1411
  - app/models/concerns/spree_cm_commissioner/vendor_promotable.rb
1415
1412
  - app/models/concerns/spree_cm_commissioner/video_on_demand_bitwise.rb
1416
1413
  - app/models/concerns/spree_cm_commissioner/webhooks/subscriber_rulable.rb
1417
- - app/models/spree/stock/quantifier_decorator.rb
1418
1414
  - app/models/spree_cm_commissioner.rb
1419
1415
  - app/models/spree_cm_commissioner/ability_decorator.rb
1420
1416
  - app/models/spree_cm_commissioner/address_decorator.rb
@@ -2017,7 +2013,9 @@ files:
2017
2013
  - app/services/spree_cm_commissioner/google_wallets/hotel_class_updater.rb
2018
2014
  - app/services/spree_cm_commissioner/google_wallets/hotel_object_builder.rb
2019
2015
  - app/services/spree_cm_commissioner/guests/claim_invite_guest_service.rb
2016
+ - app/services/spree_cm_commissioner/guests/finalize.rb
2020
2017
  - app/services/spree_cm_commissioner/guests/preload_check_in_session_ids.rb
2018
+ - app/services/spree_cm_commissioner/homepage_data.rb
2021
2019
  - app/services/spree_cm_commissioner/homepage_data_loader.rb
2022
2020
  - app/services/spree_cm_commissioner/imports/base_import_order_service.rb
2023
2021
  - app/services/spree_cm_commissioner/imports/create_order_service.rb
@@ -2088,6 +2086,7 @@ files:
2088
2086
  - app/services/spree_cm_commissioner/trips/variants/create.rb
2089
2087
  - app/services/spree_cm_commissioner/update_guest_service.rb
2090
2088
  - app/services/spree_cm_commissioner/user_authenticator.rb
2089
+ - app/services/spree_cm_commissioner/user_counters_service.rb
2091
2090
  - app/services/spree_cm_commissioner/user_roles_assigner.rb
2092
2091
  - app/services/spree_cm_commissioner/users/incomplete_guest_checker_service.rb
2093
2092
  - app/services/spree_cm_commissioner/users/qr_data/extract_login.rb
@@ -2997,6 +2996,7 @@ files:
2997
2996
  - db/migrate/20260108101406_add_allow_booking_to_cm_trips.rb
2998
2997
  - db/migrate/20260121024645_add_nationality_group_to_cm_guests.rb
2999
2998
  - db/migrate/20260126110528_seed_user_initial_usernames.rb
2999
+ - db/migrate/20260128043540_add_counter_cache_to_spree_users.rb
3000
3000
  - docker-compose.yml
3001
3001
  - docs/api/scoped-access-token-endpoints.md
3002
3002
  - docs/option_types/attr_types.md
@@ -3177,9 +3177,9 @@ required_ruby_version: !ruby/object:Gem::Requirement
3177
3177
  version: '2.7'
3178
3178
  required_rubygems_version: !ruby/object:Gem::Requirement
3179
3179
  requirements:
3180
- - - ">"
3180
+ - - ">="
3181
3181
  - !ruby/object:Gem::Version
3182
- version: 1.3.1
3182
+ version: '0'
3183
3183
  requirements:
3184
3184
  - none
3185
3185
  rubygems_version: 3.4.1
@@ -1,37 +0,0 @@
1
- module Spree
2
- module Api
3
- module V2
4
- module ProductListIncludesDecorator
5
- # OVERRIDE: Extends base product_list_includes from Spree::Api::V2::ProductListIncludes
6
- def product_list_includes
7
- # Gets base from ProductListIncludes concern
8
- base_includes = super
9
-
10
- # Preload taxons with their taxonomies
11
- base_includes[:classifications] = { taxon: :taxonomy }
12
-
13
- # Add missing associations
14
- base_includes[:vendor] = [:default_state]
15
- base_includes[:venue] = []
16
- base_includes[:variants_including_master] = { prices: [] }
17
- base_includes[:product_option_types] = :option_type
18
- base_includes[:product_promotion_rules] = {
19
- promotion_rule: {
20
- promotion: []
21
- }
22
- }
23
- base_includes[:product_dynamic_fields] = :dynamic_field
24
-
25
- %i[master variants variants_including_master].each do |key|
26
- base_includes[key][:stock_items] = :stock_location if base_includes[key]
27
- base_includes[key][:stock_locations] = []
28
- end
29
-
30
- base_includes
31
- end
32
- end
33
- end
34
- end
35
- end
36
-
37
- Spree::Api::V2::ProductListIncludes.prepend Spree::Api::V2::ProductListIncludesDecorator
@@ -1,27 +0,0 @@
1
- # gems/spree_cm_commissioner/app/controllers/spree/api/v2/storefront/products_controller_decorator.rb
2
- module Spree
3
- module Api
4
- module V2
5
- module Storefront
6
- module ProductsControllerDecorator
7
- def scope_includes
8
- base_includes = super
9
-
10
- base_includes[:vendor] = { default_state: [] }
11
- base_includes[:venue] = []
12
- base_includes[:variants_including_master] = { prices: [] }
13
-
14
- %i[master variants variants_including_master].each do |key|
15
- base_includes[key][:stock_items] = :stock_location if base_includes[key]
16
- end
17
-
18
- base_includes
19
- end
20
- end
21
- end
22
- end
23
- end
24
- end
25
-
26
- Spree::Api::V2::Storefront::ProductsController
27
- .prepend Spree::Api::V2::Storefront::ProductsControllerDecorator
@@ -1,92 +0,0 @@
1
- module Spree
2
- module Api
3
- module V2
4
- module Storefront
5
- module TaxonsControllerDecorator
6
- def resource
7
- @resource ||= begin
8
- taxon = scope.find_by(permalink: params[:id]) || scope.find(params[:id])
9
-
10
- if action_name == 'show' && should_include_products?
11
- taxon.products = taxon.products
12
- .includes(product_includes_for_show)
13
- .order('spree_products_taxons.position ASC')
14
- end
15
-
16
- if action_name == 'show' && should_include_visible_products?
17
- taxon.visible_products = taxon.visible_products
18
- .includes(product_includes_for_show)
19
- .order('spree_products_taxons.position ASC')
20
- end
21
-
22
- taxon
23
- end
24
- end
25
-
26
- protected
27
-
28
- def scope(skip_cancancan: false)
29
- base_scope = super(skip_cancancan: skip_cancancan)
30
- base_scope = base_scope.includes(asset_includes) if action_name == 'show'
31
- base_scope
32
- end
33
-
34
- private
35
-
36
- def should_include_products?
37
- params[:include]&.include?('products')
38
- end
39
-
40
- def should_include_visible_products?
41
- params[:include]&.include?('visible_products')
42
- end
43
-
44
- def product_includes_for_show
45
- {
46
- product_properties: [],
47
- option_types: [],
48
- variant_images: [],
49
- classifications: :taxon,
50
- variants_including_master: product_variant_includes,
51
- master: product_variant_includes,
52
- variants: product_variant_includes,
53
- vendor: { default_state: [] },
54
- venue: []
55
- }
56
- end
57
-
58
- def product_variant_includes
59
- {
60
- prices: [],
61
- option_values: :option_type,
62
- images: [],
63
- stock_items: :stock_location
64
- }
65
- end
66
-
67
- def asset_includes
68
- {
69
- category_icon: { attachment_attachment: :blob },
70
- app_banner: { attachment_attachment: :blob },
71
- web_banner: { attachment_attachment: :blob },
72
- home_banner: { attachment_attachment: :blob },
73
- video_banner: { attachment_attachment: :blob },
74
- children: {
75
- icon: { attachment_attachment: :blob },
76
- category_icon: { attachment_attachment: :blob },
77
- app_banner: { attachment_attachment: :blob },
78
- web_banner: { attachment_attachment: :blob },
79
- home_banner: { attachment_attachment: :blob },
80
- video_banner: { attachment_attachment: :blob }
81
- }
82
- }
83
- end
84
- end
85
- end
86
- end
87
- end
88
- end
89
-
90
- Spree::Api::V2::Storefront::TaxonsController.prepend(
91
- Spree::Api::V2::Storefront::TaxonsControllerDecorator
92
- )
@@ -1,26 +0,0 @@
1
- module Spree
2
- module Stock
3
- module QuantifierDecorator
4
- private
5
-
6
- def scope_to_location(collection)
7
- # If stock_items are already loaded (preloaded via includes), filter in memory
8
- if collection.loaded?
9
- if stock_location.blank?
10
- # Filter to only active stock locations in memory
11
- collection.select { |si| si.stock_location&.active? }
12
- else
13
- collection.select { |si| si.stock_location_id == stock_location.id }
14
- end
15
- else
16
- # Fall back to original behavior if not preloaded
17
- return collection.with_active_stock_location if stock_location.blank?
18
-
19
- collection.where(stock_location: stock_location)
20
- end
21
- end
22
- end
23
- end
24
- end
25
-
26
- Spree::Stock::Quantifier.prepend Spree::Stock::QuantifierDecorator