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
@@ -3,18 +3,42 @@ module SpreeCmCommissioner
3
3
  before_action :identify_request_source
4
4
  rescue_from StandardError, with: :handle_error
5
5
 
6
- def show
7
- case params[:id]
8
- when 'assetlinks.json'
9
- render_preference(:preferred_assetlinks)
10
- when 'apple-app-site-association'
11
- render_preference(:preferred_apple_app_site_association)
6
+ def assetlinks_json
7
+ render_platform_link(:android)
8
+ end
9
+
10
+ def apple_app_site_association
11
+ render_platform_link(:ios)
12
+ end
13
+
14
+ private
15
+
16
+ def render_platform_link(platform)
17
+ fetcher_class = platform_link_finder(platform)
18
+
19
+ result = fetcher_class.call(
20
+ store: @store,
21
+ tenant: @tenant,
22
+ refreshed: params[:_refreshed].present?
23
+ )
24
+
25
+ if result.success? && result.contents.present?
26
+ render json: result.contents
12
27
  else
13
28
  render_not_found
14
29
  end
15
30
  end
16
31
 
17
- private
32
+ def platform_link_finder(platform)
33
+ case platform
34
+ when :android
35
+ SpreeCmCommissioner::AssetLinksFetcher
36
+ when :ios
37
+ SpreeCmCommissioner::AppleAppSiteAssociationFetcher
38
+ else
39
+ render_not_found
40
+ end
41
+ end
18
42
 
19
43
  def identify_request_source
20
44
  host = params[:original_host].presence || request.host.downcase
@@ -25,16 +49,6 @@ module SpreeCmCommissioner
25
49
  @tenant = result.tenant
26
50
  end
27
51
 
28
- def render_preference(preference_key)
29
- if @store&.send(preference_key).present?
30
- render json: @store.send(preference_key)
31
- elsif @tenant&.send(preference_key).present?
32
- render json: @tenant.send(preference_key)
33
- else
34
- render_not_found
35
- end
36
- end
37
-
38
52
  def render_not_found
39
53
  render json: { error: 'Not found' }, status: :not_found
40
54
  end
@@ -0,0 +1,27 @@
1
+ module SpreeCmCommissioner
2
+ class AppleAppSiteAssociationFetcher < BaseInteractor
3
+ delegate :store, :tenant, :refreshed, to: :context
4
+
5
+ def call
6
+ key = cache_key
7
+
8
+ CmAppLogger.log(label: 'LOOKING_UP_CACHE', data: { cache_key: key })
9
+
10
+ Rails.cache.delete(key) if refreshed
11
+
12
+ context.contents = Rails.cache.fetch(key) do
13
+ CmAppLogger.log(label: 'CACHE_MISS', data: { cache_key: key })
14
+ resource.preferred_apple_app_site_association
15
+ end
16
+ end
17
+
18
+ def cache_key
19
+ updated_at = tenant&.updated_at || store&.updated_at
20
+ "well_known:apple-app-site-association:#{resource.class.name}:#{resource.id}:#{updated_at}"
21
+ end
22
+
23
+ def resource
24
+ tenant || store
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,27 @@
1
+ module SpreeCmCommissioner
2
+ class AssetLinksFetcher < BaseInteractor
3
+ delegate :store, :tenant, :refreshed, to: :context
4
+
5
+ def call
6
+ key = cache_key
7
+
8
+ CmAppLogger.log(label: 'LOOKING_UP_CACHE', data: { cache_key: key })
9
+
10
+ Rails.cache.delete(key) if refreshed
11
+
12
+ context.contents = Rails.cache.fetch(key) do
13
+ CmAppLogger.log(label: 'CACHE_MISS', data: { cache_key: key })
14
+ resource.preferred_assetlinks
15
+ end
16
+ end
17
+
18
+ def cache_key
19
+ updated_at = tenant&.updated_at || store&.updated_at
20
+ "well_known:assetlinks.json:#{resource.class.name}:#{resource.id}:#{updated_at}"
21
+ end
22
+
23
+ def resource
24
+ tenant || store
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,48 @@
1
+ module SpreeCmCommissioner
2
+ class CrewInviteLinkHandler < BaseInteractor
3
+ delegate :params, to: :context
4
+
5
+ def call
6
+ validate_data
7
+ assign_role_to_user
8
+ create_user_event
9
+ end
10
+
11
+ private
12
+
13
+ def validate_data
14
+ if invite.nil?
15
+ context.fail!(message: I18n.t('invite.url_not_found'))
16
+ else
17
+ context.invite = invite
18
+ end
19
+ end
20
+
21
+ def assign_role_to_user
22
+ return if user.operator?
23
+
24
+ role = Spree::Role.find_by(name: 'operator')
25
+ role_user = Spree::RoleUser.new(user_id: user.id, role_id: role.id)
26
+ role_user.save
27
+ end
28
+
29
+ def create_user_event
30
+ @user_event = SpreeCmCommissioner::UserEvent.new(
31
+ taxon_id: invite.taxon_id,
32
+ user_id: user.id
33
+ )
34
+
35
+ return if @user_event.save
36
+
37
+ context.fail!(message: I18n.t('invite.already_invited'))
38
+ end
39
+
40
+ def invite
41
+ @invite ||= SpreeCmCommissioner::Invite.find_by(token: params[:id])
42
+ end
43
+
44
+ def user
45
+ @user ||= Spree::User.find(params[:user_id])
46
+ end
47
+ end
48
+ end
@@ -0,0 +1,144 @@
1
+ module SpreeCmCommissioner
2
+ class TelegramChatsAutoFinder < BaseInteractor
3
+ delegate :contact, :pin_code, to: :context
4
+
5
+ def call
6
+ context.chats = []
7
+ context.verified_chat_ids = []
8
+
9
+ updates = fetch_updates
10
+
11
+ if updates['result'].any?
12
+ updates['result'].sort_by { |u| u['update_id'] }.reverse.each do |update|
13
+ handle_message_update(update) if update['message']
14
+ handle_channel_post_update(update) if update['channel_post']
15
+ handle_chat_member_update(update) if update['my_chat_member']
16
+ end
17
+ end
18
+ context.fail!(message: 'No pin codes verified') if context.verified_chat_ids.empty?
19
+ end
20
+
21
+ private
22
+
23
+ def handle_chat_member_update(update)
24
+ chat = update.dig('my_chat_member', 'chat')
25
+ return unless chat
26
+
27
+ chat_data = {
28
+ id: chat['id'],
29
+ title: chat['title'] || 'Unnamed Chat',
30
+ type: chat['type']
31
+ }
32
+
33
+ return if context.chats.any? { |c| c[:id] == chat_data[:id] }
34
+
35
+ db_chat = SpreeCmCommissioner::TelegramChat.find_or_initialize_by(chat_id: chat_data[:id])
36
+ is_new_chat = db_chat.new_record?
37
+ db_chat.update(chat_title: chat_data[:title], chat_type: chat_data[:type])
38
+ context.chats << chat_data
39
+ message = "👋Hello! This is Bookme+ bot.\n\nPlease enter the 6-digit PIN code displayed in your portal."
40
+ send_message(chat_data[:id], message) if is_new_chat
41
+ end
42
+
43
+ def handle_channel_post_update(update)
44
+ message = update['channel_post']
45
+ process_pin_code_message(message)
46
+ end
47
+
48
+ def handle_message_update(update)
49
+ message = update['message']
50
+ process_pin_code_message(message)
51
+ end
52
+
53
+ def process_pin_code_message(message)
54
+ chat_id = message.dig('chat', 'id')
55
+ text = message['text']&.strip
56
+
57
+ return unless valid_pin_code?(text, chat_id)
58
+
59
+ pin_code_record = find_pin_code_record
60
+ return unless pin_code_record
61
+
62
+ vendor = find_vendor
63
+ return unless vendor
64
+
65
+ chat = update_telegram_chat(message, chat_id)
66
+ return unless chat
67
+
68
+ handle_successful_verification(chat, vendor, pin_code_record)
69
+ end
70
+
71
+ def valid_pin_code?(text, chat_id)
72
+ text && chat_id && text == pin_code
73
+ end
74
+
75
+ def find_pin_code_record
76
+ pin_code_record = SpreeCmCommissioner::PinCodeTelegram.find_by(code: pin_code, contact: contact)
77
+ unless pin_code_record
78
+ context.fail!(message: "Pin code #{pin_code} not found for contact #{contact}")
79
+ return nil
80
+ end
81
+ pin_code_record
82
+ end
83
+
84
+ def find_vendor
85
+ vendor = Spree::Vendor.find_by(id: contact)
86
+ unless vendor
87
+ context.fail!(message: "Vendor not found for contact #{contact}")
88
+ return nil
89
+ end
90
+ vendor
91
+ end
92
+
93
+ def update_telegram_chat(message, chat_id)
94
+ chat = SpreeCmCommissioner::TelegramChat.find_or_initialize_by(chat_id: chat_id)
95
+ unless chat.update(
96
+ chat_title: message.dig('chat', 'title') || 'Unnamed Chat',
97
+ chat_type: message.dig('chat', 'type')
98
+ )
99
+ context.fail!(message: "Failed to update chat: #{chat.errors.full_messages.join(', ')}")
100
+ return nil
101
+ end
102
+
103
+ context.chats << { id: chat.chat_id, title: chat.chat_title, type: chat.chat_type }
104
+ chat
105
+ end
106
+
107
+ def handle_successful_verification(chat, vendor, pin_code_record)
108
+ return if chat.vendor && chat.vendor != vendor
109
+
110
+ update_vendor_preferences(vendor, chat)
111
+ chat.update(active: true, vendor: vendor)
112
+ expire_pin_code(pin_code_record)
113
+ send_success_message(chat.chat_id, vendor.name)
114
+ context.verified_chat_ids << chat.chat_id
115
+ end
116
+
117
+ def update_vendor_preferences(vendor, chat)
118
+ vendor.update(
119
+ preferred_telegram_chat_id: chat.chat_id,
120
+ preferred_telegram_chat_name: chat.chat_title,
121
+ preferred_telegram_chat_type: chat.chat_type
122
+ )
123
+ end
124
+
125
+ def expire_pin_code(pin_code_record)
126
+ pin_code_record.expire
127
+ pin_code_record.save
128
+ end
129
+
130
+ def send_success_message(chat_id, vendor_name)
131
+ message = "✅ PIN code verified successfully! This chat is now connected to <b>#{vendor_name}</b>.\n\n" \
132
+ 'All completed orders will be sent to this chat.'
133
+ send_message(chat_id, message)
134
+ end
135
+
136
+ def fetch_updates
137
+ ::Telegram.bot.get_updates
138
+ end
139
+
140
+ def send_message(chat_id, text)
141
+ ::Telegram.bot.send_message(chat_id: chat_id, text: text, parse_mode: 'HTML')
142
+ end
143
+ end
144
+ end
@@ -93,6 +93,10 @@ module SpreeCmCommissioner
93
93
  context.fail!(message: "User creation failed: #{context.user.errors.full_messages.join(', ')}")
94
94
  end
95
95
 
96
+ def disable_services
97
+ 'bus,ferry'
98
+ end
99
+
96
100
  def construct_data
97
101
  user = context.user
98
102
 
@@ -101,7 +105,7 @@ module SpreeCmCommissioner
101
105
  name: user.full_name,
102
106
  phone: user.phone_number,
103
107
  email: user.email,
104
- webUrl: "#{Spree::Store.default.formatted_url}/vattanac_bank_web_app?session_id=#{session_id}"
108
+ webUrl: "#{Spree::Store.default.formatted_url}/vattanac_bank_web_app?session_id=#{session_id}&ds=#{CGI.escape(disable_services)}"
105
109
  }
106
110
 
107
111
  json_data = raw_data.to_json
@@ -0,0 +1,30 @@
1
+ module SpreeCmCommissioner
2
+ module EventCheckInFlowable
3
+ extend ActiveSupport::Concern
4
+
5
+ included do
6
+ preference :group_check_in_enabled, :boolean, default: true
7
+ preference :individual_check_in_enabled, :boolean, default: true
8
+
9
+ before_validation :validate_at_least_one_check_in_flow_presence
10
+ end
11
+
12
+ def check_in_flows
13
+ flows = []
14
+ flows << 'group' if group_check_in_enabled?
15
+ flows << 'individual' if individual_check_in_enabled?
16
+ flows
17
+ end
18
+
19
+ def group_check_in_enabled? = preferred_group_check_in_enabled
20
+ def individual_check_in_enabled? = preferred_individual_check_in_enabled
21
+
22
+ private
23
+
24
+ def validate_at_least_one_check_in_flow_presence
25
+ return if check_in_flows.any?
26
+
27
+ errors.add(:check_in_flows, 'must have at least one flow selected')
28
+ end
29
+ end
30
+ end
@@ -40,7 +40,8 @@ module SpreeCmCommissioner
40
40
  'bib-zerofill' => 'integer',
41
41
  'bib-display-prefix' => 'boolean',
42
42
  'bib-pre-generation-on-create' => 'boolean',
43
- 'seat-number-positions' => 'array'
43
+ 'seat-number-positions' => 'array',
44
+ 'seat-type' => 'string'
44
45
  }.freeze
45
46
 
46
47
  included do
@@ -5,6 +5,10 @@ module SpreeCmCommissioner
5
5
  included do
6
6
  preference :assetlinks, :string, default: ''
7
7
  preference :apple_app_site_association, :string, default: ''
8
+ preference :payment_checkout_image, :string, default: ''
9
+ preference :payment_failed_image, :string, default: ''
10
+ preference :payment_success_image, :string, default: ''
11
+ preference :payment_loader, :string, default: ''
8
12
  end
9
13
  end
10
14
  end
@@ -0,0 +1,8 @@
1
+ module SpreeCmCommissioner
2
+ class BlazerQueryable < SpreeCmCommissioner::Base
3
+ belongs_to :queryable, polymorphic: true
4
+ belongs_to :blazer_query, class_name: 'Blazer::Query'
5
+
6
+ validates :queryable_id, uniqueness: { scope: %i[queryable_type blazer_query_id], message: I18n.t('event_blazer_queries.fail') }
7
+ end
8
+ end
@@ -0,0 +1,4 @@
1
+ module SpreeCmCommissioner
2
+ class CrewInviteLink < Invite
3
+ end
4
+ end
@@ -1,20 +1,34 @@
1
1
  module SpreeCmCommissioner
2
2
  class DynamicField < SpreeCmCommissioner::Base
3
+ acts_as_list column: :position, scope: :vendor_id
4
+
3
5
  belongs_to :vendor, class_name: 'Spree::Vendor', optional: false
4
6
 
5
7
  has_many :dynamic_field_options, class_name: 'SpreeCmCommissioner::DynamicFieldOption', dependent: :destroy
6
8
  has_many :guest_dynamic_fields, class_name: 'SpreeCmCommissioner::GuestDynamicField', dependent: :destroy
7
9
  has_many :product_dynamic_fields, class_name: 'SpreeCmCommissioner::ProductDynamicField', dependent: :destroy
8
10
 
9
- enum data_type: { string: 0, integer: 1, boolean: 2, checkbox: 3, radio: 4 }
11
+ enum data_type: { text: 0, number: 1, boolean: 2, checkbox: 3, radio: 4, selection: 5, textarea: 6 }
10
12
 
11
13
  accepts_nested_attributes_for :dynamic_field_options, allow_destroy: true, reject_if: :all_blank
12
14
 
13
15
  validates :label, presence: true
14
16
  validates :data_type, presence: true
17
+ validates :configurations, presence: true, allow_blank: false
18
+
19
+ def requires_dynamic_field_options?
20
+ checkbox? || radio? || selection?
21
+ end
22
+
23
+ def multiple_select
24
+ return nil unless selection?
25
+
26
+ configurations['multiple_select'] == true
27
+ end
15
28
 
16
- def selection?
17
- checkbox? || radio?
29
+ def multiple_select=(value)
30
+ self.configurations ||= {}
31
+ self.configurations['multiple_select'] = ActiveModel::Type::Boolean.new.cast(value)
18
32
  end
19
33
  end
20
34
  end
@@ -1,8 +1,14 @@
1
1
  module SpreeCmCommissioner
2
2
  class DynamicFieldOption < SpreeCmCommissioner::Base
3
+ acts_as_list column: :position, scope: :dynamic_field_id
4
+
3
5
  belongs_to :dynamic_field, class_name: 'SpreeCmCommissioner::DynamicField', optional: false
6
+ has_many :guest_dynamic_fields, class_name: 'SpreeCmCommissioner::GuestDynamicField', dependent: :restrict_with_error
7
+
8
+ enum status: { active: 0, draft: 1, archived: 2 }
4
9
 
5
10
  validates :value, presence: true, uniqueness: { scope: :dynamic_field_id }
11
+ validates :status, presence: true
6
12
  validate :parent_field_must_be_selection_type
7
13
 
8
14
  private
@@ -10,7 +16,7 @@ module SpreeCmCommissioner
10
16
  def parent_field_must_be_selection_type
11
17
  return unless dynamic_field
12
18
 
13
- return if dynamic_field.selection?
19
+ return if dynamic_field.requires_dynamic_field_options?
14
20
 
15
21
  errors.add(:dynamic_field, 'must be a selection type field')
16
22
  end
@@ -61,7 +61,7 @@ module SpreeCmCommissioner
61
61
  validates :seat_number, uniqueness: { scope: :event_id }, allow_nil: true, if: -> { event_id.present? }
62
62
  validates :bib_index, uniqueness: true, allow_nil: true
63
63
 
64
- self.whitelisted_ransackable_associations = %w[id_card event]
64
+ self.whitelisted_ransackable_associations = %w[id_card event line_item]
65
65
  self.whitelisted_ransackable_attributes = %w[first_name last_name phone_number gender contact occupation_id card_type created_at check_in_status]
66
66
 
67
67
  def self.csv_importable_columns
@@ -2,9 +2,11 @@ module SpreeCmCommissioner
2
2
  class GuestDynamicField < SpreeCmCommissioner::Base
3
3
  belongs_to :guest, class_name: 'SpreeCmCommissioner::Guest', optional: false
4
4
  belongs_to :dynamic_field, class_name: 'SpreeCmCommissioner::DynamicField', optional: false
5
+ belongs_to :dynamic_field_option, class_name: 'SpreeCmCommissioner::DynamicFieldOption', optional: true
5
6
 
6
7
  validates :value, presence: true
7
8
  validate :validate_value_format, if: -> { value.present? && dynamic_field.present? }
9
+ validate :validate_option_reference, if: -> { dynamic_field_option.present? }
8
10
 
9
11
  private
10
12
 
@@ -12,14 +14,30 @@ module SpreeCmCommissioner
12
14
  return if dynamic_field.blank?
13
15
 
14
16
  case dynamic_field.data_type.to_s
15
- when 'integer'
17
+ when 'number'
16
18
  errors.add(:value, 'must be a number') unless value.to_s.match?(/\A\d+\z/)
17
19
  when 'boolean'
18
- errors.add(:value, 'must be true or false') unless %w[true false 1 0].include?(value.to_s.downcase)
19
- when 'checkbox', 'radio'
20
+ errors.add(:value, 'must be true or false') unless %w[true false].include?(value.to_s.downcase)
21
+ when 'checkbox', 'radio', 'selection'
22
+ validate_option_value
23
+ end
24
+ end
25
+
26
+ def validate_option_value
27
+ if dynamic_field_option.present?
28
+ self.value ||= dynamic_field_option.value
29
+ else
20
30
  valid_options = dynamic_field.dynamic_field_options.pluck(:id).map(&:to_s)
21
31
  errors.add(:value, 'is not a valid option') unless valid_options.include?(value)
22
32
  end
23
33
  end
34
+
35
+ def validate_option_reference
36
+ return if dynamic_field.blank? || dynamic_field_option.blank?
37
+
38
+ return if dynamic_field_option.dynamic_field_id == dynamic_field_id
39
+
40
+ errors.add(:dynamic_field_option, 'must belong to the same dynamic field')
41
+ end
24
42
  end
25
43
  end
@@ -5,16 +5,14 @@ module SpreeCmCommissioner
5
5
  has_many :invite_user_events, class_name: 'SpreeCmCommissioner::InviteUserEvent', dependent: :destroy
6
6
  scope :active, -> { where('expires_at > ?', Time.current) }
7
7
 
8
- after_create :set_expiration
9
-
10
- def set_expiration
11
- update(expires_at: 3.days.from_now)
12
- end
13
-
14
8
  def invite_url
15
9
  "#{Spree::Store.default.formatted_url}/invite/#{token}?utm_source=email"
16
10
  end
17
11
 
12
+ def invite_crew_link
13
+ "#{Spree::Store.default.formatted_url}/invite_crews/#{token}"
14
+ end
15
+
18
16
  def url_valid?
19
17
  expires_at.present? && expires_at > Time.current
20
18
  end
@@ -1,5 +1,7 @@
1
1
  module SpreeCmCommissioner
2
2
  class InviteTeam < SpreeCmCommissioner::Base
3
+ validates :email, presence: true, uniqueness: { scope: :vendor_id, message: I18n.t('invite.already_invited') }
4
+
3
5
  belongs_to :user, class_name: 'Spree::User', optional: true
4
6
  belongs_to :inviter, class_name: 'Spree::User'
5
7
  belongs_to :vendor, class_name: 'Spree::Vendor'
@@ -34,7 +36,7 @@ module SpreeCmCommissioner
34
36
  end
35
37
 
36
38
  def invitation_link
37
- "http://#{ENV.fetch('APP_HOST')}/organizer/invite_teams/#{token}?utm_source=email"
39
+ "#{ENV.fetch('ORGANIZER_URL')}/invite_teams/#{token}?utm_source=email"
38
40
  end
39
41
 
40
42
  def url_valid?
@@ -2,7 +2,7 @@ module SpreeCmCommissioner
2
2
  class PinCode < SpreeCmCommissioner::Base
3
3
  has_secure_token
4
4
 
5
- enum contact_type: { 'phone_number' => 0, 'email' => 1 }
5
+ enum contact_type: { 'phone_number' => 0, 'email' => 1, 'telegram' => 2 }
6
6
 
7
7
  validates :code, length: { maximum: 6 }
8
8
  validates :contact, presence: true
@@ -130,7 +130,8 @@ module SpreeCmCommissioner
130
130
  'SpreeCmCommissioner::PinCodeMobileConfirm' => I18n.t('pincode.readable_type.confirmation_code'),
131
131
  'SpreeCmCommissioner::PinCodeUpdateUserLogin' => I18n.t('pincode.readable_type.update_user_login'),
132
132
  'SpreeCmCommissioner::PinCodeEmailConfirm' => I18n.t('pincode.readable_type.confirmation_code'),
133
- 'SpreeCmCommissioner::OtpCode' => I18n.t('pincode.readable_type.otp_code')
133
+ 'SpreeCmCommissioner::OtpCode' => I18n.t('pincode.readable_type.otp_code'),
134
+ 'SpreeCmCommissioner::PinCodeTelegram' => I18n.t('pincode.readable_type.telegram_verification')
134
135
  }
135
136
  end
136
137
 
@@ -0,0 +1,28 @@
1
+ module SpreeCmCommissioner
2
+ class PinCodeTelegram < PinCode
3
+ before_validation :set_telegram_attrs, on: :create
4
+ validates :contact, presence: true, numericality: { only_integer: true }, if: :telegram?
5
+
6
+ belongs_to :vendor, class_name: 'Spree::Vendor', foreign_key: 'contact', primary_key: 'id'
7
+
8
+ def telegram?
9
+ true
10
+ end
11
+
12
+ def expires_in_seconds
13
+ 5 * 60
14
+ end
15
+
16
+ def expired?
17
+ expired_at < Time.zone.now
18
+ end
19
+
20
+ private
21
+
22
+ def set_telegram_attrs
23
+ self.contact_type = :telegram
24
+ self.type = 'SpreeCmCommissioner::PinCodeTelegram'
25
+ self.expired_at = Time.zone.now + expires_in_seconds unless expired_at
26
+ end
27
+ end
28
+ end
@@ -15,7 +15,6 @@ module SpreeCmCommissioner
15
15
  has_many :products, through: :product_places
16
16
 
17
17
  has_many :children, -> { order(:lft) }, class_name: 'SpreeCmCommissioner::Place', foreign_key: :parent_id, dependent: :destroy
18
- has_many :vendor_stops, class_name: 'SpreeCmCommissioner::VendorStop', dependent: :destroy, foreign_key: :stop_id
19
18
 
20
19
  has_many :taxon_places, class_name: 'SpreeCmCommissioner::TaxonPlace', dependent: :destroy
21
20
  has_many :taxons, through: :taxon_places
@@ -75,8 +75,6 @@ module SpreeCmCommissioner
75
75
 
76
76
  base.enum purchasable_on: { both: 0, web: 1, app: 2 }
77
77
 
78
- base.accepts_nested_attributes_for :trip, allow_destroy: true
79
-
80
78
  base.multi_tenant :tenant, class_name: 'SpreeCmCommissioner::Tenant'
81
79
  base.before_save :set_tenant
82
80
  end
@@ -0,0 +1,11 @@
1
+ module SpreeCmCommissioner
2
+ module PromotionCategoryDecorator
3
+ def self.prepended(base)
4
+ base.belongs_to :vendor, class_name: 'Spree::Vendor', optional: true
5
+ end
6
+ end
7
+ end
8
+
9
+ unless Spree::PromotionCategory.included_modules.include?(SpreeCmCommissioner::PromotionCategoryDecorator)
10
+ Spree::PromotionCategory.prepend(SpreeCmCommissioner::PromotionCategoryDecorator)
11
+ end
@@ -3,6 +3,7 @@ module SpreeCmCommissioner
3
3
  def self.prepended(base) # rubocop:disable Metrics/AbcSize,Metrics/MethodLength
4
4
  base.include SpreeCmCommissioner::TaxonKind
5
5
  base.include SpreeCmCommissioner::ParticipationTypeBitwise
6
+ base.include SpreeCmCommissioner::EventCheckInFlowable
6
7
 
7
8
  base.preference :background_color, :string
8
9
  base.preference :foreground_color, :string
@@ -55,14 +56,16 @@ module SpreeCmCommissioner
55
56
  base.has_many :crew_invites, class_name: 'SpreeCmCommissioner::CrewInvite', dependent: :destroy
56
57
  base.has_many :invite_user_events, through: :user_events, class_name: 'SpreeCmCommissioner::InviteUserEvent'
57
58
 
59
+ base.has_many :crew_invite_links, class_name: 'SpreeCmCommissioner::CrewInviteLink', dependent: :destroy
60
+
58
61
  base.has_many :invite_guests, class_name: 'SpreeCmCommissioner::InviteGuest', foreign_key: :event_id
59
62
 
60
63
  base.has_many :line_items, through: :products
61
64
  base.has_many :event_line_items, class_name: 'Spree::LineItem', foreign_key: :event_id
62
65
  base.has_many :event_products, class_name: 'Spree::Product', foreign_key: :event_id
63
66
 
64
- base.has_many :event_blazer_queries, class_name: 'SpreeCmCommissioner::TaxonBlazerQuery'
65
- base.has_many :blazer_queries, through: :event_blazer_queries, class_name: 'Blazer::Query'
67
+ base.has_many :event_blazer_queries, as: :queryable, class_name: 'SpreeCmCommissioner::BlazerQueryable'
68
+ base.has_many :blazer_queries, through: :event_blazer_queries, source: :blazer_query, class_name: 'Blazer::Query'
66
69
 
67
70
  base.has_many :taxon_option_types, class_name: 'SpreeCmCommissioner::TaxonOptionType'
68
71
  base.has_many :taxon_option_values, class_name: 'SpreeCmCommissioner::TaxonOptionValue'