spree_cm_commissioner 2.0.0.pre.pre → 2.0.1.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 (96) hide show
  1. checksums.yaml +4 -4
  2. data/.env.example +3 -0
  3. data/Gemfile.lock +1 -1
  4. data/app/assets/images/cm-hangmeas-checkout_image.svg +63 -0
  5. data/app/assets/images/cm-hangmeas-failed.svg +56 -0
  6. data/app/assets/images/cm-hangmeas-loader.svg +50 -0
  7. data/app/assets/images/cm-hangmeas-success.svg +51 -0
  8. data/app/assets/javascripts/spree_cm_commissioner/backend.js +1 -0
  9. data/app/assets/javascripts/spree_cm_commissioner/tenant_payment_icon_fields.js +65 -0
  10. data/app/assets/stylesheets/spree_cm_commissioner/backend/commissioner_admin.css.scss +1 -0
  11. data/app/assets/stylesheets/spree_cm_commissioner/backend/tenant_payment_icon_fields.scss +60 -0
  12. data/app/controllers/concerns/spree_cm_commissioner/content_cachable.rb +2 -1
  13. data/app/controllers/spree/admin/event_blazer_queries_controller.rb +11 -4
  14. data/app/controllers/spree/admin/tenants_controller.rb +8 -0
  15. data/app/controllers/spree/admin/trip_blazer_queries_controller.rb +35 -0
  16. data/app/controllers/spree/api/v2/organizer/invite_crews_controller.rb +33 -0
  17. data/app/controllers/spree/api/v2/storefront/dynamic_field_options_controller.rb +5 -1
  18. data/app/controllers/spree/api/v2/storefront/guests_controller.rb +31 -5
  19. data/app/controllers/spree/api/v2/tenant/checkout_controller.rb +2 -0
  20. data/app/controllers/spree_cm_commissioner/admin/variants_controller_decorator.rb +1 -1
  21. data/app/controllers/spree_cm_commissioner/api/v2/storefront/checkout_controller_decorator.rb +11 -0
  22. data/app/controllers/spree_cm_commissioner/well_known_controller.rb +31 -17
  23. data/app/interactors/spree_cm_commissioner/apple_app_site_association_fetcher.rb +27 -0
  24. data/app/interactors/spree_cm_commissioner/asset_links_fetcher.rb +27 -0
  25. data/app/interactors/spree_cm_commissioner/crew_invite_link_handler.rb +48 -0
  26. data/app/interactors/spree_cm_commissioner/telegram_chats_auto_finder.rb +144 -0
  27. data/app/interactors/spree_cm_commissioner/vattanac_bank_initiator.rb +5 -1
  28. data/app/models/concerns/spree_cm_commissioner/event_check_in_flowable.rb +30 -0
  29. data/app/models/concerns/spree_cm_commissioner/option_type_attr_type.rb +2 -1
  30. data/app/models/concerns/spree_cm_commissioner/tenant_preference.rb +4 -0
  31. data/app/models/spree_cm_commissioner/blazer_queryable.rb +8 -0
  32. data/app/models/spree_cm_commissioner/crew_invite_link.rb +4 -0
  33. data/app/models/spree_cm_commissioner/dynamic_field.rb +17 -3
  34. data/app/models/spree_cm_commissioner/dynamic_field_option.rb +7 -1
  35. data/app/models/spree_cm_commissioner/guest.rb +1 -1
  36. data/app/models/spree_cm_commissioner/guest_dynamic_field.rb +21 -3
  37. data/app/models/spree_cm_commissioner/invite.rb +4 -6
  38. data/app/models/spree_cm_commissioner/invite_team.rb +3 -1
  39. data/app/models/spree_cm_commissioner/pin_code.rb +3 -2
  40. data/app/models/spree_cm_commissioner/pin_code_telegram.rb +28 -0
  41. data/app/models/spree_cm_commissioner/place.rb +0 -1
  42. data/app/models/spree_cm_commissioner/product_decorator.rb +0 -2
  43. data/app/models/spree_cm_commissioner/promotion_category_decorator.rb +11 -0
  44. data/app/models/spree_cm_commissioner/taxon_decorator.rb +5 -2
  45. data/app/models/spree_cm_commissioner/telegram_chat.rb +6 -0
  46. data/app/models/spree_cm_commissioner/trip.rb +9 -13
  47. data/app/models/spree_cm_commissioner/trip_stop.rb +5 -23
  48. data/app/models/spree_cm_commissioner/user_taxon.rb +1 -0
  49. data/app/models/spree_cm_commissioner/variant_decorator.rb +1 -0
  50. data/app/models/spree_cm_commissioner/vendor_decorator.rb +9 -9
  51. data/app/models/spree_cm_commissioner/vendor_place.rb +8 -3
  52. data/app/overrides/spree/admin/taxons/_form/check_in_flows.html.erb.deface +18 -0
  53. data/app/overrides/spree/admin/variants/_form/kyc_field.html.erb.deface +2 -2
  54. data/app/queries/spree_cm_commissioner/guest_searcher_query.rb +45 -3
  55. data/app/queries/spree_cm_commissioner/trip_query.rb +56 -27
  56. data/app/serializers/spree/v2/organizer/invite_crews_serializer.rb +11 -0
  57. data/app/serializers/spree_cm_commissioner/v2/operator/dashboard_crew_event_serializer.rb +4 -1
  58. data/app/serializers/spree_cm_commissioner/v2/storefront/dynamic_field_option_serializer.rb +1 -3
  59. data/app/serializers/spree_cm_commissioner/v2/storefront/dynamic_field_serializer.rb +1 -3
  60. data/app/serializers/spree_cm_commissioner/v2/storefront/guest_dynamic_field_serializer.rb +2 -1
  61. data/app/services/spree_cm_commissioner/organizer/export_guest_csv_service.rb +23 -15
  62. data/app/views/blazer/queries/_content.html.erb +8 -0
  63. data/app/views/spree/admin/tenants/_form.html.erb +109 -42
  64. data/config/initializers/spree_permitted_attributes.rb +2 -0
  65. data/config/locales/en.yml +5 -0
  66. data/config/routes.rb +4 -1
  67. data/db/migrate/20250616084219_add_description_to_cm_vendor_place.rb +5 -0
  68. data/db/migrate/20250630103536_create_cm_telegram_chats.rb +13 -0
  69. data/db/migrate/20250701093203_add_configurations_to_cm_dynamic_field.rb +6 -0
  70. data/db/migrate/20250702091305_add_dynamic_field_option_to_guest_dynamic_field.rb +5 -0
  71. data/db/migrate/20250702091935_add_status_to_dynamic_field_option.rb +5 -0
  72. data/db/migrate/20250707032008_add_vendor_id_to_spree_category.rb +7 -0
  73. data/db/migrate/20250711092937_add_position_to_cm_dynamic_fields.rb +11 -0
  74. data/db/migrate/20250711093045_add_position_to_cm_dynamic_field_options.rb +11 -0
  75. data/db/migrate/20250714121508_rename_cm_taxon_blazer_query_to_blazer_queryables.rb +7 -0
  76. data/db/migrate/20250714124323_make_cm_blazer_queryables_polymorphic.rb +19 -0
  77. data/db/migrate/20250716022821_add_location_reference_to_cm_vendor_places.rb +5 -0
  78. data/db/migrate/20250716031743_drop_table_cm_vendor_stops.rb +5 -0
  79. data/db/migrate/20250717023824_add_vendor_reference_to_cm_trips.rb +5 -0
  80. data/db/migrate/20250717041414_add_location_place_reference_to_cm_trip_stops.rb +5 -0
  81. data/db/migrate/20250717042539_rename_cm_trip_stops_stop_id_column_to_stop_place_id.rb +7 -0
  82. data/db/migrate/20250717042707_rename_cm_trips_origin_and_destination_to_origin_place_and_destination_place.rb +11 -0
  83. data/lib/spree_cm_commissioner/test_helper/factories/block_factory.rb +9 -3
  84. data/lib/spree_cm_commissioner/test_helper/factories/seat_layout_factory.rb +6 -3
  85. data/lib/spree_cm_commissioner/test_helper/factories/seat_section_factory.rb +11 -3
  86. data/lib/spree_cm_commissioner/test_helper/factories/trip_factory.rb +3 -2
  87. data/lib/spree_cm_commissioner/test_helper/factories/trip_stop_factory.rb +10 -0
  88. data/lib/spree_cm_commissioner/test_helper/factories/vendor_factory.rb +5 -0
  89. data/lib/spree_cm_commissioner/test_helper/factories/vendor_place_factory.rb +13 -1
  90. data/lib/spree_cm_commissioner/trip_query_result.rb +8 -0
  91. data/lib/spree_cm_commissioner/trip_result.rb +5 -3
  92. data/lib/spree_cm_commissioner/version.rb +1 -1
  93. metadata +39 -5
  94. data/app/models/spree_cm_commissioner/taxon_blazer_query.rb +0 -8
  95. data/app/models/spree_cm_commissioner/vendor_stop.rb +0 -9
  96. data/app/queries/spree_cm_commissioner/vendor_stop_place_query.rb +0 -54
@@ -0,0 +1,6 @@
1
+ module SpreeCmCommissioner
2
+ class TelegramChat < Base
3
+ belongs_to :vendor, class_name: 'Spree::Vendor'
4
+ validates :chat_id, presence: true, uniqueness: true
5
+ end
6
+ end
@@ -1,15 +1,18 @@
1
1
  module SpreeCmCommissioner
2
2
  class Trip < Base
3
- include SpreeCmCommissioner::RouteType
4
-
5
3
  attr_accessor :hours, :minutes, :seconds
6
4
 
7
5
  before_validation :convert_duration_to_seconds
8
6
 
7
+ belongs_to :vendor, class_name: 'Spree::Vendor', inverse_of: :trips, optional: false
9
8
  belongs_to :product, class_name: 'Spree::Product', inverse_of: :trip, optional: false
10
9
  belongs_to :vehicle, class_name: 'SpreeCmCommissioner::Vehicle', optional: false
11
- belongs_to :origin, class_name: 'SpreeCmCommissioner::Place', optional: true
12
- belongs_to :destination, class_name: 'SpreeCmCommissioner::Place', optional: true
10
+
11
+ belongs_to :origin_place, class_name: 'SpreeCmCommissioner::Place', optional: false
12
+ belongs_to :destination_place, class_name: 'SpreeCmCommissioner::Place', optional: false
13
+
14
+ has_many :trip_blazer_queries, as: :queryable, class_name: 'SpreeCmCommissioner::BlazerQueryable'
15
+ has_many :blazer_queries, through: :trip_blazer_queries, source: :blazer_query, class_name: 'Blazer::Query'
13
16
 
14
17
  has_many :trip_stops, class_name: 'SpreeCmCommissioner::TripStop', dependent: :destroy
15
18
  has_many :variants, through: :product
@@ -19,9 +22,7 @@ module SpreeCmCommissioner
19
22
  validates :duration, numericality: { greater_than: 0 }
20
23
  validate :origin_and_destination_cannot_be_the_same
21
24
 
22
- accepts_nested_attributes_for :trip_stops, allow_destroy: true
23
-
24
- after_create :create_trip_stops
25
+ accepts_nested_attributes_for :trip_stops, :product, allow_destroy: true
25
26
 
26
27
  def convert_duration_to_seconds
27
28
  return if hours.blank? && minutes.blank? && seconds.blank?
@@ -29,11 +30,6 @@ module SpreeCmCommissioner
29
30
  self.duration = (hours.to_i * 3600) + (minutes.to_i * 60) + seconds.to_i
30
31
  end
31
32
 
32
- def create_trip_stops
33
- trip_stops.find_or_create_by(stop_type: :boarding, stop_id: origin_id)
34
- trip_stops.find_or_create_by(stop_type: :drop_off, stop_id: destination_id)
35
- end
36
-
37
33
  def duration_in_hms
38
34
  return { hours: 0, minutes: 0, seconds: 0 } if duration.nil?
39
35
 
@@ -52,7 +48,7 @@ module SpreeCmCommissioner
52
48
  private
53
49
 
54
50
  def origin_and_destination_cannot_be_the_same
55
- return unless origin_id == destination_id
51
+ return unless origin_place_id == destination_place_id
56
52
 
57
53
  errors.add(:base, 'Origin and destination cannot be the same')
58
54
  end
@@ -3,33 +3,15 @@ module SpreeCmCommissioner
3
3
  acts_as_list column: :sequence, scope: :trip_id
4
4
  enum :stop_type, { boarding: 0, drop_off: 1 }
5
5
 
6
- belongs_to :trip, class_name: 'SpreeCmCommissioner::Trip'
7
- belongs_to :stop, class_name: 'SpreeCmCommissioner::Place'
6
+ belongs_to :trip, class_name: 'SpreeCmCommissioner::Trip', optional: false
7
+ belongs_to :stop_place, class_name: 'SpreeCmCommissioner::Place', optional: false
8
+ belongs_to :location_place, class_name: 'SpreeCmCommissioner::Place', optional: false
8
9
 
9
10
  before_validation :set_stop_name
10
- after_destroy :decrement_trip_count
11
- after_commit :create_vendor_stop
12
- validates :stop_id, uniqueness: { scope: :trip_id }
11
+ validates :stop_place_id, uniqueness: { scope: :trip_id }
13
12
 
14
13
  def set_stop_name
15
- self.stop_name = stop.name if stop.present?
16
- end
17
-
18
- def create_vendor_stop
19
- vendor_stop = vendor.vendor_stops.where(stop_id: stop_id, stop_type: stop_type).first_or_create
20
- vendor_stop.update(trip_count: vendor_stop.trip_count.to_i + 1)
21
- end
22
-
23
- private
24
-
25
- def decrement_trip_count
26
- vendor.vendor_stops.where(stop_id: stop_id, stop_type: stop_type).find_each do |vendor_stop|
27
- vendor_stop.update(trip_count: [vendor_stop.trip_count - 1, 0].max)
28
- end
29
- end
30
-
31
- def vendor
32
- Spree::Product.find(trip&.product_id).vendor
14
+ self.stop_name = stop_place.name if stop_place.present?
33
15
  end
34
16
  end
35
17
  end
@@ -2,5 +2,6 @@ module SpreeCmCommissioner
2
2
  class UserTaxon < Base
3
3
  belongs_to :user, class_name: Spree.user_class.to_s, optional: false
4
4
  belongs_to :taxon, class_name: 'Spree::Taxon', optional: false
5
+ has_many :invite_user_taxons, class_name: 'SpreeCmCommissioner::InviteUserTaxon', dependent: :destroy
5
6
  end
6
7
  end
@@ -101,6 +101,7 @@ module SpreeCmCommissioner
101
101
 
102
102
  def update_vendor_price
103
103
  return unless vendor.present? && product_type == vendor&.primary_product_type
104
+ return if price.blank?
104
105
 
105
106
  vendor.update(min_price: price) if vendor.min_price.nil? || price < vendor.min_price
106
107
  vendor.update(max_price: price) if vendor.max_price.nil? || price > vendor.max_price
@@ -36,9 +36,9 @@ module SpreeCmCommissioner
36
36
  base.has_many :vendor_kind_option_values,
37
37
  through: :option_value_vendors, source: :option_value
38
38
 
39
- base.has_many :branches, -> { branches }, class_name: 'SpreeCmCommissioner::VendorPlace'
40
- base.has_many :stops, -> { stops }, class_name: 'SpreeCmCommissioner::VendorPlace'
41
- base.has_many :locations, -> { locations }, class_name: 'SpreeCmCommissioner::VendorPlace'
39
+ base.has_many :branches, -> { branch }, class_name: 'SpreeCmCommissioner::VendorPlace'
40
+ base.has_many :stops, -> { stop }, class_name: 'SpreeCmCommissioner::VendorPlace'
41
+ base.has_many :locations, -> { location }, class_name: 'SpreeCmCommissioner::VendorPlace'
42
42
 
43
43
  base.has_many :branch_places, through: :branches, class_name: 'SpreeCmCommissioner::Place', source: :place
44
44
  base.has_many :stop_places, through: :stops, class_name: 'SpreeCmCommissioner::Place', source: :place
@@ -47,11 +47,6 @@ module SpreeCmCommissioner
47
47
  base.has_many :places,
48
48
  through: :nearby_places, source: :place, class_name: 'SpreeCmCommissioner::Place'
49
49
 
50
- base.has_many :vendor_stops, class_name: 'SpreeCmCommissioner::VendorStop', dependent: :destroy
51
- base.has_many :boarding_points, -> { where(cm_vendor_stops: { stop_type: 0 }) },
52
- through: :vendor_stops, source: :stop, class_name: 'SpreeCmCommissioner::Place'
53
- base.has_many :drop_off_points, -> { where(cm_vendor_stops: { stop_type: 1 }) },
54
- through: :vendor_stops, source: :stop, class_name: 'SpreeCmCommissioner::Place'
55
50
  base.has_many :dynamic_fields, class_name: 'SpreeCmCommissioner::DynamicField', foreign_key: :vendor_id, dependent: :destroy
56
51
 
57
52
  base.has_one :logo, as: :viewable, dependent: :destroy, class_name: 'SpreeCmCommissioner::VendorLogo'
@@ -79,12 +74,17 @@ module SpreeCmCommissioner
79
74
  base.has_many :invoices, class_name: 'SpreeCmCommissioner::Invoice', dependent: :destroy
80
75
  base.has_many :subscriptions, through: :customers, class_name: 'SpreeCmCommissioner::Subscription'
81
76
  base.has_many :subscription_orders, through: :subscriptions, class_name: 'Spree::Order', source: :orders
77
+ base.has_many :promotion_categories, class_name: 'Spree::PromotionCategory', foreign_key: :vendor_id, dependent: :destroy
82
78
 
83
79
  base.has_many :homepage_section_relatables,
80
+ as: :relatable,
84
81
  class_name: 'SpreeCmCommissioner::HomepageSectionRelatable',
85
- dependent: :destroy, inverse_of: :relatable
82
+ dependent: :destroy,
83
+ inverse_of: :relatable
86
84
 
87
85
  base.has_many :vehicles, class_name: 'SpreeCmCommissioner::Vehicle', dependent: :destroy
86
+ base.has_many :trips, class_name: 'SpreeCmCommissioner::Trip', dependent: :destroy
87
+
88
88
  base.validates :account_name, :account_number, presence: true, if: lambda {
89
89
  payment_qrcode.present? && Spree::Store.default.code.include?('billing')
90
90
  }
@@ -5,17 +5,22 @@ module SpreeCmCommissioner
5
5
 
6
6
  belongs_to :vendor, class_name: 'Spree::Vendor', optional: false
7
7
  belongs_to :place, class_name: 'SpreeCmCommissioner::Place', optional: false
8
+ belongs_to :location, class_name: 'SpreeCmCommissioner::VendorPlace', optional: true
9
+
10
+ has_many :branches, -> { branch }, class_name: 'SpreeCmCommissioner::VendorPlace'
11
+ has_many :stops, -> { stop }, class_name: 'SpreeCmCommissioner::VendorPlace'
8
12
 
9
13
  validates :place_type, presence: true
10
- validates :position, presence: true, numericality: { only_integer: true, greater_than: 0 }
14
+ validates :place_id, uniqueness: { scope: %i[vendor_id place_type] }
15
+ validates :location, presence: true, if: -> { branch? || stop? }
16
+
17
+ accepts_nested_attributes_for :place, allow_destroy: true
11
18
 
12
19
  # include place when using these delegate to avoid N+1
13
20
  def display_name
14
21
  place&.name
15
22
  end
16
23
 
17
- accepts_nested_attributes_for :place, allow_destroy: true
18
-
19
24
  def selected
20
25
  vendor.selected_place_references.include?(place&.reference)
21
26
  end
@@ -0,0 +1,18 @@
1
+ <!-- insert_before "erb[loud]:contains('field_container :hide_from_nav')" -->
2
+
3
+ <%# Sections don't use video banner, so we hide the form here to avoid confusing admin. %>
4
+
5
+ <%= f.field_container :preferred_group_check_in_enabled, class: ['custom-control', 'custom-checkbox', 'my-4'] do %>
6
+ <%= f.check_box :preferred_group_check_in_enabled, class: 'custom-control-input' %>
7
+ <%= f.label :preferred_group_check_in_enabled, Spree.t(:enable_group_check_in), class: 'custom-control-label' %>
8
+ <%= f.error_message_on :preferred_group_check_in_enabled %>
9
+ <small class="form-text text-muted">
10
+ Enabled by default for most events where users can purchase multiple tickets or use group scanning. For large events (like PSK, ASK, etc.) or invitation-based events, you should disable this to ensure each ticket or invitation is scanned individually.
11
+ </small>
12
+ <% end if @taxon.event? && @taxon.depth == 1 %> %>
13
+
14
+ <%= f.field_container :preferred_individual_check_in_enabled, class: ['custom-control', 'custom-checkbox', 'my-4'] do %>
15
+ <%= f.check_box :preferred_individual_check_in_enabled, class: 'custom-control-input' %>
16
+ <%= f.label :preferred_individual_check_in_enabled, Spree.t(:enable_individual_check_in), class: 'custom-control-label' %>
17
+ <%= f.error_message_on :preferred_individual_check_in_enabled %>
18
+ <% end if @taxon.event? && @taxon.depth == 1 %> %>
@@ -2,13 +2,13 @@
2
2
 
3
3
  <p>Guest Information Fields</p>
4
4
  <div class="form-check mb-3">
5
- <%= f.check_box :use_product_kyc, id: "toggleGuestInfo", class: 'form-check-input mr-2', checked: (@variant.kyc == @product.kyc) %>
5
+ <%= f.check_box :use_product_kyc, id: "toggleGuestInfo", class: 'form-check-input mr-2', checked: @variant.kyc.nil? %>
6
6
  <label for="toggleGuestInfo" class="form-check-label">
7
7
  Use Product Guest Information
8
8
  </label>
9
9
  </div>
10
10
 
11
- <div id="guestInfoForm" <% if @variant.kyc == @product.kyc %>style="display: none;"<% end %>">
11
+ <div id="guestInfoForm" <% if @variant.kyc.nil? %>style="display: none;"<% end %>">
12
12
  <small class="form-text text-muted mb-4">
13
13
  <%= raw I18n.t('kyc.variant_note') %>
14
14
  </small>
@@ -8,20 +8,36 @@ module SpreeCmCommissioner
8
8
 
9
9
  def event_id = params[:event_id]
10
10
 
11
- def call
11
+ def call # rubocop:disable Metrics/PerceivedComplexity
12
12
  return SpreeCmCommissioner::Guest.none if event_id.blank?
13
13
 
14
- if params[:qr_data].present?
14
+ if params[:qr_data].present? && order_qr_data?
15
+ search_by_order_qr
16
+ elsif params[:qr_data].present? && line_item_qr_data?
17
+ search_by_line_item_qr
18
+ elsif params[:qr_data].present? && guest_qr_data?
15
19
  search_by_guest_qr
16
20
  elsif params[:term].present?
17
21
  search_by_term
22
+ elsif params[:ids].present?
23
+ SpreeCmCommissioner::Guest.complete_or_canceled.where(event_id: event_id, id: params[:ids])
18
24
  else
19
- SpreeCmCommissioner::Guest.none
25
+ SpreeCmCommissioner::Guest.complete_or_canceled.where(event_id: event_id)
20
26
  end
21
27
  end
22
28
 
23
29
  private
24
30
 
31
+ def search_by_order_qr
32
+ order = Spree::Order.complete_or_canceled.search_by_qr_data!(params[:qr_data])
33
+ order.guests.where(event_id: event_id)
34
+ end
35
+
36
+ def search_by_line_item_qr
37
+ line_item = Spree::LineItem.complete_or_canceled.search_by_qr_data!(params[:qr_data])
38
+ line_item.guests.where(event_id: event_id)
39
+ end
40
+
25
41
  def search_by_guest_qr
26
42
  SpreeCmCommissioner::Guest.complete_or_canceled.where(
27
43
  token: params[:qr_data],
@@ -29,6 +45,32 @@ module SpreeCmCommissioner
29
45
  )
30
46
  end
31
47
 
48
+ def order_qr_data?
49
+ matches = construct_matches
50
+ matches&.size == 2
51
+ end
52
+
53
+ def line_item_qr_data?
54
+ matches = construct_matches
55
+ matches&.size == 3
56
+ end
57
+
58
+ def guest_qr_data?
59
+ matches = construct_matches
60
+ matches.nil?
61
+ end
62
+
63
+ def construct_matches
64
+ qr_data = params[:qr_data]
65
+ return nil unless qr_data
66
+
67
+ if qr_data =~ /-L\d+$/
68
+ qr_data.match(/(R\d+)-([A-Za-z0-9_\-]+)-(L\d+)/)&.captures
69
+ else
70
+ qr_data.match(/(R\d+)-([A-Za-z0-9_\-]+)/)&.captures
71
+ end
72
+ end
73
+
32
74
  def search_by_term
33
75
  terms = params[:term].split.map { |term| "%#{term.downcase}%" }
34
76
 
@@ -7,8 +7,7 @@ module SpreeCmCommissioner
7
7
  def initialize(origin_id:, destination_id:, date:, params: {})
8
8
  @origin_id = origin_id
9
9
  @destination_id = destination_id
10
- @date = date
11
- @date = Time.zone.now if date.to_date == Time.zone.now.to_date
10
+ @date = date.to_date == Time.zone.now.to_date ? Time.zone.now : Time.zone.parse(date.to_s)
12
11
  @params = params
13
12
  end
14
13
 
@@ -20,26 +19,29 @@ module SpreeCmCommissioner
20
19
  direct_results + connected_results
21
20
  end
22
21
 
23
- def direct_trips
24
- result = Spree::Variant
25
- .select('spree_variants.id AS variant_id,
22
+ def direct_trips # rubocop:disable Metrics/MethodLength
23
+ result = Spree::Product
24
+ .select('
26
25
  vendors.id AS vendor_id,
27
26
  vendors.name AS vendor_name,
28
- routes.name AS route_name,
29
- routes.short_name AS short_name,
27
+ spree_products.name AS route_name,
28
+ spree_products.short_name AS short_name,
30
29
  boarding.stop_id AS origin_id,
31
30
  drop_off.stop_id AS destination_id,
32
31
  boarding.stop_name AS origin,
33
32
  drop_off.stop_name AS destination,
33
+ spree_products.id AS trip_id,
34
34
  trips.departure_time AS departure_time,
35
35
  trips.duration AS duration,
36
- trips.vehicle_id AS vehicle_id'
36
+ trips.vehicle_id AS vehicle_id,
37
+ COALESCE(iv.quantity_available, 0) AS quantity_available,
38
+ COALESCE(iv.max_capacity, 0) AS max_capacity'
37
39
  )
38
- .joins('INNER JOIN spree_products AS routes ON routes.id = spree_variants.product_id')
39
- .joins('INNER JOIN cm_trips AS trips ON trips.product_id = routes.id')
40
- .joins('INNER JOIN cm_trip_stops AS boarding ON boarding.trip_id = spree_variants.id AND boarding.stop_type = 0')
41
- .joins('INNER JOIN cm_trip_stops AS drop_off ON drop_off.trip_id = spree_variants.id AND drop_off.stop_type = 1')
42
- .joins('INNER JOIN spree_vendors AS vendors ON vendors.id = spree_variants.vendor_id')
40
+ .joins('INNER JOIN cm_trips AS trips ON trips.product_id = spree_products.id')
41
+ .joins('INNER JOIN cm_trip_stops AS boarding ON boarding.trip_id = trips.id AND boarding.stop_type = 0')
42
+ .joins('INNER JOIN cm_trip_stops AS drop_off ON drop_off.trip_id = trips.id AND drop_off.stop_type = 1')
43
+ .joins('INNER JOIN spree_vendors AS vendors ON vendors.id = spree_products.vendor_id')
44
+ .joins("LEFT JOIN (#{product_inventory_totals.to_sql}) iv ON spree_products.id = iv.product_id")
43
45
 
44
46
  result = result.where(vendors: { id: params[:vendor_id] }) if params[:vendor_id].present?
45
47
  result = result.where('boarding.stop_id = ? AND drop_off.stop_id = ?', origin_id, destination_id)
@@ -47,9 +49,10 @@ module SpreeCmCommissioner
47
49
  date.strftime('%H:%M:%S'),
48
50
  date.end_of_day.strftime('%H:%M:%S')
49
51
  )
52
+
50
53
  result.map do |trip|
51
54
  trip_result_options = {
52
- trip_id: trip[:variant_id],
55
+ trip_id: trip[:trip_id],
53
56
  vendor_id: trip[:vendor_id],
54
57
  vendor_name: trip[:vendor_name],
55
58
  route_name: trip[:route_name],
@@ -61,7 +64,9 @@ module SpreeCmCommissioner
61
64
  destination: trip[:destination],
62
65
  vehicle_id: trip[:vehicle_id],
63
66
  departure_time: parse_time(trip[:departure_time]),
64
- duration: trip[:duration]
67
+ duration: trip[:duration],
68
+ quantity_available: trip[:quantity_available].to_i,
69
+ max_capacity: trip[:max_capacity].to_i
65
70
  }
66
71
  SpreeCmCommissioner::TripResult.new(trip_result_options)
67
72
  end
@@ -69,16 +74,18 @@ module SpreeCmCommissioner
69
74
 
70
75
  def connected_trips # rubocop:disable Metrics/MethodLength
71
76
  result = SpreeCmCommissioner::TripConnection
72
- .joins('
77
+ .joins("
73
78
  INNER JOIN cm_trips trip1 ON trip1.id = cm_trip_connections.from_trip_id
74
79
  INNER JOIN cm_trips trip2 ON trip2.id = cm_trip_connections.to_trip_id
75
- INNER JOIN spree_products AS routes1 ON routes1.id = trip1.product_id
76
- INNER JOIN spree_products AS routes2 ON routes2.id = trip2.product_id
80
+ INNER JOIN spree_products AS product1 ON product1.id = trip1.product_id
81
+ INNER JOIN spree_products AS product2 ON product2.id = trip2.product_id
77
82
  INNER JOIN cm_trip_stops trip1_origin ON trip1_origin.trip_id = trip1.id AND trip1_origin.stop_type = 0
78
83
  INNER JOIN cm_trip_stops trip2_origin ON trip2_origin.trip_id = trip2.id AND trip2_origin.stop_type = 0
79
84
  INNER JOIN cm_trip_stops trip2_destination ON trip2_destination.trip_id = trip2.id AND trip2_destination.stop_type = 1
80
- INNER JOIN spree_vendors AS vendor1 ON vendor1.id = routes1.vendor_id
81
- INNER JOIN spree_vendors AS vendor2 ON vendor2.id = routes2.vendor_id'
85
+ INNER JOIN spree_vendors AS vendor1 ON vendor1.id = product1.vendor_id
86
+ INNER JOIN spree_vendors AS vendor2 ON vendor2.id = product2.vendor_id
87
+ LEFT JOIN (#{product_inventory_totals.to_sql}) AS iv1 ON iv1.product_id = product1.id
88
+ LEFT JOIN (#{product_inventory_totals.to_sql}) AS iv2 ON iv2.product_id = product2.id"
82
89
  )
83
90
  .select('cm_trip_connections.id AS id,
84
91
  trip1.id AS trip1_id,
@@ -86,8 +93,9 @@ module SpreeCmCommissioner
86
93
  trip1.destination_id AS trip1_destination_id,
87
94
  trip1.departure_time AS trip1_departure_time,
88
95
  trip1.duration AS trip1_duration,
89
- routes1.short_name AS route1_short_name,
90
- routes1.name AS route1_name,
96
+ product1.id AS trip1_id,
97
+ product1.short_name AS route1_short_name,
98
+ product1.name AS route1_name,
91
99
  trip1.vehicle_id AS trip1_vehicle,
92
100
  vendor1.name AS trip1_vendor_name,
93
101
  trip2.id AS trip2_id,
@@ -96,12 +104,17 @@ module SpreeCmCommissioner
96
104
  trip2.departure_time AS trip2_departure_time,
97
105
  trip2.duration AS trip2_duration,
98
106
  trip2.vehicle_id AS trip2_vehicle,
99
- routes2.short_name AS route2_short_name,
100
- routes2.name AS route2_name,
107
+ product2.id AS trip2_id,
108
+ product2.short_name AS route2_short_name,
109
+ product2.name AS route2_name,
101
110
  trip1_origin.stop_name AS trip1_origin,
102
111
  trip2_origin.stop_name AS trip2_origin,
103
112
  trip2_destination.stop_name AS trip2_destination,
104
- vendor2.name AS trip2_vendor_name'
113
+ vendor2.name AS trip2_vendor_name,
114
+ COALESCE(iv1.quantity_available, 0) as trip1_quantity_available,
115
+ COALESCE(iv2.quantity_available, 0) as trip2_quantity_available,
116
+ COALESCE(iv1.max_capacity, 0) as trip1_max_capacity,
117
+ COALESCE(iv2.max_capacity, 0) as trip2_max_capacity'
105
118
  ).where('trip1_origin.stop_id = ? AND trip2_destination.stop_id = ?', origin_id, destination_id)
106
119
  result = result.where('vendor1.id = ? OR vendor2.id = ?', params[:vendor_id], params[:vendor_id]) if params[:vendor_id].present?
107
120
  result = result.where('trip1.departure_time > ? AND trip1.departure_time <= ?', date.strftime('%H:%M:%S'),
@@ -112,6 +125,18 @@ module SpreeCmCommissioner
112
125
  build_trip_query_result(result)
113
126
  end
114
127
 
128
+ def product_inventory_totals
129
+ Spree::Product
130
+ .select(
131
+ 'spree_products.id AS product_id,
132
+ SUM(cm_inventory_items.max_capacity) AS max_capacity,
133
+ SUM(cm_inventory_items.quantity_available) AS quantity_available'
134
+ )
135
+ .joins(variants: :inventory_items)
136
+ .where(cm_inventory_items: { inventory_date: date.to_date })
137
+ .group('spree_products.id')
138
+ end
139
+
115
140
  private
116
141
 
117
142
  def build_trip_query_result(connections)
@@ -127,7 +152,9 @@ module SpreeCmCommissioner
127
152
  short_name: trip[:route1_short_name],
128
153
  vehicle_id: trip[:trip1_vehicle],
129
154
  origin: trip[:trip1_origin],
130
- destination: trip[:trip2_origin]
155
+ destination: trip[:trip2_origin],
156
+ quantity_available: trip[:trip1_quantity_available].to_i,
157
+ max_capacity: trip[:trip1_max_capacity].to_i
131
158
  }
132
159
  to_trip = {
133
160
  trip_id: trip[:trip2_id],
@@ -140,7 +167,9 @@ module SpreeCmCommissioner
140
167
  short_name: trip[:route2_short_name],
141
168
  vehicle_id: trip[:trip2_vehicle],
142
169
  origin: trip[:trip2_origin],
143
- destination: trip[:trip2_destination]
170
+ destination: trip[:trip2_destination],
171
+ quantity_available: trip[:trip2_quantity_available].to_i,
172
+ max_capacity: trip[:trip2_max_capacity].to_i
144
173
  }
145
174
  data = [SpreeCmCommissioner::TripResult.new(from_trip),
146
175
  SpreeCmCommissioner::TripResult.new(to_trip)
@@ -0,0 +1,11 @@
1
+ module Spree
2
+ module V2
3
+ module Organizer
4
+ class InviteCrewsSerializer < BaseSerializer
5
+ attributes :token, :expires_at
6
+ belongs_to :taxon, serializer: Spree::V2::Organizer::TaxonSerializer
7
+ belongs_to :inviter, serializer: Spree::V2::Organizer::UserSerializer
8
+ end
9
+ end
10
+ end
11
+ end
@@ -2,7 +2,10 @@ module SpreeCmCommissioner
2
2
  module V2
3
3
  module Operator
4
4
  class DashboardCrewEventSerializer < BaseSerializer
5
- attributes :id, :name, :permalink, :from_date, :to_date, :updated_at
5
+ attributes :id, :name, :permalink,
6
+ :from_date, :to_date,
7
+ :check_in_flows,
8
+ :updated_at
6
9
 
7
10
  has_many :children_classifications, serializer: :classification
8
11
  has_one :category_icon, serializer: SpreeCmCommissioner::V2::Storefront::AssetSerializer
@@ -2,9 +2,7 @@ module SpreeCmCommissioner
2
2
  module V2
3
3
  module Storefront
4
4
  class DynamicFieldOptionSerializer < BaseSerializer
5
- set_type :dynamic_field_option
6
-
7
- attributes :id, :value, :dynamic_field_id, :created_at, :updated_at
5
+ attributes :id, :value, :dynamic_field_id, :status, :created_at, :updated_at, :position
8
6
  end
9
7
  end
10
8
  end
@@ -2,9 +2,7 @@ module SpreeCmCommissioner
2
2
  module V2
3
3
  module Storefront
4
4
  class DynamicFieldSerializer < BaseSerializer
5
- set_type :dynamic_field
6
-
7
- attributes :id, :label, :data_type, :vendor_id, :created_at, :updated_at
5
+ attributes :id, :label, :data_type, :vendor_id, :multiple_select, :created_at, :updated_at, :position
8
6
 
9
7
  has_many :dynamic_field_options, serializer: SpreeCmCommissioner::V2::Storefront::DynamicFieldOptionSerializer
10
8
  end
@@ -4,9 +4,10 @@ module SpreeCmCommissioner
4
4
  class GuestDynamicFieldSerializer < BaseSerializer
5
5
  set_type :guest_dynamic_field
6
6
 
7
- attributes :id, :value, :guest_id, :dynamic_field_id, :created_at, :updated_at
7
+ attributes :id, :value, :guest_id, :dynamic_field_id, :dynamic_field_option_id, :created_at, :updated_at
8
8
 
9
9
  belongs_to :dynamic_field, serializer: SpreeCmCommissioner::V2::Storefront::DynamicFieldSerializer
10
+ belongs_to :dynamic_field_option, serializer: SpreeCmCommissioner::V2::Storefront::DynamicFieldOptionSerializer
10
11
  end
11
12
  end
12
13
  end
@@ -1,11 +1,12 @@
1
1
  module SpreeCmCommissioner
2
2
  module Organizer
3
3
  class ExportGuestCsvService
4
- attr_reader :event_id, :columns
4
+ attr_reader :event_id, :columns, :filters
5
5
 
6
- def initialize(event_id, columns)
6
+ def initialize(event_id, columns, filters = {})
7
7
  @event_id = event_id
8
8
  @columns = columns
9
+ @filters = filters
9
10
  end
10
11
 
11
12
  def call
@@ -18,7 +19,7 @@ module SpreeCmCommissioner
18
19
  private
19
20
 
20
21
  def headers
21
- columns.map(&:humanize).uniq
22
+ columns.map { |col| col.titleize(keep_id_suffix: true) }.uniq
22
23
  end
23
24
 
24
25
  def build_row(guest)
@@ -28,10 +29,8 @@ module SpreeCmCommissioner
28
29
  def fetch_value(guest, column)
29
30
  if guest_field?(column)
30
31
  fetch_guest_value(guest, column)
31
- elsif option_type_field?(column)
32
- fetch_option_value(guest, column)
33
32
  else
34
- ''
33
+ fetch_option_value(guest, column)
35
34
  end
36
35
  end
37
36
 
@@ -39,19 +38,21 @@ module SpreeCmCommissioner
39
38
  SpreeCmCommissioner::KycBitwise::ORDERED_BIT_FIELDS.include?(column.to_sym)
40
39
  end
41
40
 
42
- def option_type_field?(column)
43
- Spree::OptionType.exists?(name: column)
44
- end
45
-
46
41
  def fetch_guest_value(guest, column)
47
42
  column_mappings(guest)[column] || ''
48
43
  end
49
44
 
50
- def fetch_option_value(guest, option_type_name)
51
- return guest.formatted_bib_number if option_type_name == 'bib-prefix'
45
+ # rubocop:disable Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
46
+ def fetch_option_value(guest, column_name)
47
+ return guest.formatted_bib_number if column_name == 'bib-prefix'
48
+ return guest.line_item&.number if column_name == 'item_id'
49
+ return guest.line_item&.order&.number if column_name == 'order_id'
50
+ return guest.created_at if column_name == 'created_at'
51
+ return guest.updated_at if column_name == 'updated_at'
52
52
 
53
- guest.line_item&.variant&.find_option_value_name_for(option_type_name: option_type_name)
53
+ guest.line_item&.variant&.find_option_value_name_for(option_type_name: column_name) || ''
54
54
  end
55
+ # rubocop:enable Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
55
56
 
56
57
  def column_mappings(guest)
57
58
  {
@@ -77,12 +78,19 @@ module SpreeCmCommissioner
77
78
  end
78
79
 
79
80
  def guests_with_associations
80
- event.guests.includes(
81
+ scope = event.guests.includes(
81
82
  :occupation,
82
83
  :nationality,
83
84
  :id_card,
84
85
  line_item: { variant: { option_values: :option_type } }
85
- )
86
+ ).complete
87
+ scope = scope.where('cm_guests.created_at >= ?', formatted_date_time(filters[:from_date])) if filters[:from_date].present?
88
+ scope = scope.where('cm_guests.created_at <= ?', formatted_date_time(filters[:to_date])) if filters[:to_date].present?
89
+ scope
90
+ end
91
+
92
+ def formatted_date_time(date)
93
+ Time.zone.parse(date.to_s).strftime('%Y-%m-%d %H:%M:%S %z')
86
94
  end
87
95
 
88
96
  def event
@@ -47,6 +47,14 @@
47
47
  class: "btn btn-primary",
48
48
  style: "margin-left: 10px;" %>
49
49
  </div>
50
+ <% elsif !@error && @success && params[:trip_id].present? %>
51
+ <div>
52
+ <%= link_to 'Save Trip',
53
+ spree.admin_trip_blazer_queries_path(trip_id: params[:trip_id], blazer_query_id: @query.id),
54
+ method: :post,
55
+ class: "btn btn-primary",
56
+ style: "margin-left: 10px;" %>
57
+ </div>
50
58
  <% end %>
51
59
  </div>
52
60