spree_cm_commissioner 1.21.0 → 2.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (33) hide show
  1. checksums.yaml +4 -4
  2. data/Gemfile.lock +1 -5
  3. data/app/assets/images/mailer/mail.png +0 -0
  4. data/app/assets/images/mailer/tenant_phone.png +0 -0
  5. data/app/assets/images/mailer/tenant_user.png +0 -0
  6. data/app/controllers/spree/api/v2/storefront/guests_controller.rb +13 -13
  7. data/app/factory/spree_cm_commissioner/invite_guest_claimed_telegram_message_factory.rb +27 -38
  8. data/app/interactors/spree_cm_commissioner/create_ticket.rb +6 -5
  9. data/app/mailers/spree/order_mailer_decorator.rb +18 -3
  10. data/app/models/concerns/spree_cm_commissioner/tenant_preference.rb +1 -0
  11. data/app/models/spree_cm_commissioner/dynamic_field.rb +20 -0
  12. data/app/models/spree_cm_commissioner/guest.rb +56 -0
  13. data/app/models/spree_cm_commissioner/guest_dynamic_field.rb +41 -1
  14. data/app/models/spree_cm_commissioner/product_decorator.rb +22 -0
  15. data/app/models/spree_cm_commissioner/vendor_decorator.rb +3 -0
  16. data/app/serializers/spree_cm_commissioner/v2/storefront/dynamic_field_serializer.rb +1 -1
  17. data/app/views/spree/admin/tenant_vendors/index.html.erb +9 -2
  18. data/app/views/spree/admin/tenants/_form.html.erb +9 -0
  19. data/app/views/spree/admin/tenants/edit.html.erb +2 -1
  20. data/app/views/spree/admin/tenants/index.html.erb +7 -1
  21. data/app/views/spree/admin/vendors/_form.html.erb +14 -0
  22. data/app/views/spree/order_mailer/confirm_email.html.erb +27 -16
  23. data/app/views/spree_cm_commissioner/layouts/order_mailer.html.erb +5 -1
  24. data/app/views/spree_cm_commissioner/order_mailer/_mailer_stylesheets.html.erb +41 -4
  25. data/app/views/spree_cm_commissioner/order_mailer/tenant/_customer_info.html.erb +42 -0
  26. data/app/views/spree_cm_commissioner/order_mailer/tenant/_footer.html.erb +25 -0
  27. data/app/views/spree_cm_commissioner/order_mailer/tenant/_greeting.html.erb +19 -0
  28. data/app/views/spree_cm_commissioner/order_mailer/tenant/_support_contact.html.erb +33 -0
  29. data/config/locales/en.yml +5 -1
  30. data/db/migrate/20250709073455_add_email_fields_to_spree_vendors.rb +6 -0
  31. data/db/migrate/20250718071620_add_data_fill_stage_to_dynamic_fields.rb +5 -0
  32. data/lib/spree_cm_commissioner/version.rb +1 -1
  33. metadata +11 -2
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 7043a8acbe6da011c50dcf6d5f9c4fa4c09d44f0c273c7d8c5f35e9da6430628
4
- data.tar.gz: '049dc2649fbb41b2e43945741baf9c7815ddc11b38a83cec8157c2eac5ed8431'
3
+ metadata.gz: f153e6dedc0923e77cd5fb9e53f99fb8b5f18f1289acc2961df248054621bbc8
4
+ data.tar.gz: efc85b32c83368f26e02db04b67c14e71c430baafcedcf997ee5961a6217d7d4
5
5
  SHA512:
6
- metadata.gz: 17233d10bfd72385a100faf262a0c236017970a4429e33098a7992dd64c217ff02c06168931f3472ef8e5a3245786df65c72f0123f2cacca58223b66c5ca6a3f
7
- data.tar.gz: 68bac4de8bf2b866b75ccbcfef5c53b50786427d7cbe87e4d17fa0b284186cfbd08d71fbe8c5fe496dfce6b1e6340a6bb0be4cbac00103388c7545397ad417d1
6
+ metadata.gz: 9f13f470efb8892b55fac8bdbb0e6f180dd08ebc12a3d6639ded76855526849890cc02f17d7bcdc592e4528eaad619da94f82b403084a1e697afd7e2fd0100b2
7
+ data.tar.gz: a6646ac4739b414ffab2890f2230f0e07c6401d78ed77482e394303234a6d67e102132ca0ad589a879c6370eac0d1efc947f0fba4a115ebc10622950c09ecbfe
data/Gemfile.lock CHANGED
@@ -34,7 +34,7 @@ GIT
34
34
  PATH
35
35
  remote: .
36
36
  specs:
37
- spree_cm_commissioner (1.21.0)
37
+ spree_cm_commissioner (2.0.0)
38
38
  activerecord-multi-tenant
39
39
  activerecord_json_validator (~> 2.1, >= 2.1.3)
40
40
  aws-sdk-cloudfront
@@ -572,10 +572,6 @@ GEM
572
572
  nokogiri (1.15.4)
573
573
  mini_portile2 (~> 2.8.2)
574
574
  racc (~> 1.4)
575
- nokogiri (1.15.4-x86_64-darwin)
576
- racc (~> 1.4)
577
- nokogiri (1.15.4-x86_64-linux)
578
- racc (~> 1.4)
579
575
  noticed (1.6.3)
580
576
  http (>= 4.0.0)
581
577
  rails (>= 5.2.0)
Binary file
@@ -39,22 +39,22 @@ module Spree
39
39
  spree_authorize! :update, spree_current_order, order_token
40
40
 
41
41
  begin
42
- if guest_params[:guest_dynamic_fields_attributes]
43
- resource.guest_dynamic_fields.destroy_all
44
-
45
- guest_params[:guest_dynamic_fields_attributes].each do |attr|
46
- field = build_dynamic_field(attr)
47
- field.save!
42
+ ActiveRecord::Base.transaction do
43
+ if guest_params[:guest_dynamic_fields_attributes]
44
+ dynamic_field_ids = guest_params[:guest_dynamic_fields_attributes].pluck(:dynamic_field_id)
45
+ resource.guest_dynamic_fields.where(dynamic_field_id: dynamic_field_ids).destroy_all
46
+
47
+ guest_params[:guest_dynamic_fields_attributes].each do |attr|
48
+ field = build_dynamic_field(attr)
49
+ field.save!
50
+ end
48
51
  end
49
- end
50
52
 
51
- resource.assign_attributes(guest_params.except(:guest_dynamic_fields_attributes))
52
-
53
- if resource.save
54
- render_serialized_payload { serialize_resource(resource) }
55
- else
56
- render_error_payload(resource.errors, 400)
53
+ resource.assign_attributes(guest_params.except(:guest_dynamic_fields_attributes))
54
+ resource.save!
57
55
  end
56
+
57
+ render_serialized_payload { serialize_resource(resource) }
58
58
  rescue ActiveRecord::RecordInvalid => e
59
59
  render_error_payload(e.record.errors, 400)
60
60
  end
@@ -1,17 +1,16 @@
1
- # <p>📣 --- <b>[NEW GUEST CLAIMED INVITATION]</b> ---</p>
2
-
3
- # <p>✨ <b>Ticket Successfully Claimed!</b> ✨</p>
4
-
5
- # <p><b>🙍 Claimed By</b><br>
6
- # Name: Orng SreyMoch<br>
7
- # Tel: <code>0966827830</code></p>
8
-
9
- # <p><b>Run Ticket + 2 way bus ticket + Tent</b><br>
10
- # <i>👉 Distance: 5km, Tent: tent (1 people), and T-shirt: S</i><br>
11
- # <i>🗓️ May 30, 2025 -> Jun 30, 2025</i></p>
12
-
13
- # <p><b>📑 Order Number:</b><br>
14
- # <code>R172627517</code></p>
1
+ # 🎫 --- [NEW INVITATION CLAIMED] ---
2
+ #
3
+ # <b>🙍 Claimed By</b>
4
+ # Name: Orng SreyMoch
5
+ # Tel: <code>+85570760548</code>
6
+ #
7
+ # <b>5km Running Ticket</b>
8
+ # Quantity: 1
9
+ # <i>👉 Distance: 5km, T-shirt: S</i>
10
+ # <i>🗓️ Nov 15, 2023 -> Nov 17, 2023</i>
11
+ #
12
+ # Order Number:
13
+ # <code>R621016753</code>
15
14
 
16
15
  module SpreeCmCommissioner
17
16
  class InviteGuestClaimedTelegramMessageFactory < TelegramMessageFactory
@@ -27,40 +26,30 @@ module SpreeCmCommissioner
27
26
  end
28
27
 
29
28
  def selected_line_items
30
- return order.line_items.for_vendor(vendor) if vendor.present?
31
-
32
- order.line_items
29
+ vendor.present? ? order.line_items.for_vendor(vendor) : order.line_items
33
30
  end
34
31
 
35
32
  # override
36
33
  def body
37
34
  text = []
38
- text << '<p>📣 --- <b>[NEW GUEST CLAIMED INVITATION]</b> ---</p>'
39
- text << '<p>✨ <b>Ticket Successfully Claimed!</b> ✨</p>'
40
- text << '<p><b>🙍 Claimed By</b><br>'
41
- text << "Name: #{guest.first_name} #{guest.last_name}<br>"
42
- text << "Tel: <code>#{guest.phone_number}</code></p>"
35
+
36
+ text << bold('🙍 Claimed By')
37
+ text << "Name: #{guest.first_name} #{guest.last_name}"
38
+ text << "Tel: #{inline_code(guest.phone_number)}"
39
+ text << ''
43
40
 
44
41
  selected_line_items.each do |item|
45
- text << line_item_block(item)
42
+ text << bold(item.product.name.to_s)
43
+ text << "🎟️ Quantity: #{item.quantity}"
44
+ text << italic("👉 #{item.options_text}") if item.options_text.present?
45
+ text << italic(pretty_date_for(item)) if item.date_present?
46
+ text << ''
46
47
  end
47
48
 
48
- text << "<p><b>📑 Order Number:</b><br><code>#{order.number}</code></p>"
49
-
50
- text.join("\n")
51
- end
52
-
53
- def line_item_block(item)
54
- parts = []
55
- parts << "<p><b>#{item.product.name}</b><br>"
56
-
57
- details = []
58
- details << "👉 #{item.options_text}" if item.options_text.present?
59
- details << pretty_date_for(item) if item.date_present?
49
+ text << bold('Order Number:')
50
+ text << inline_code(order.number)
60
51
 
61
- parts << details.map { |d| "<i>#{d}</i><br>" }.join
62
- parts << '</p>'
63
- parts.join("\n")
52
+ text.compact.join("\n")
64
53
  end
65
54
 
66
55
  def pretty_date_for(line_item)
@@ -9,7 +9,8 @@ module SpreeCmCommissioner
9
9
  set_option_value
10
10
  create_variant
11
11
  create_stock_item
12
- upload_image
12
+ upload_image_to(@ticket.master)
13
+ upload_image_to(@variant)
13
14
  create_place
14
15
  end
15
16
  end
@@ -77,13 +78,13 @@ module SpreeCmCommissioner
77
78
  context.fail!(message: @stock_item.errors.full_messages.join(', '))
78
79
  end
79
80
 
80
- def upload_image
81
+ def upload_image_to(target)
81
82
  return if context.params[:ticket_image].blank?
82
83
 
83
- @image = @ticket.master.images.new(attachment: context.params[:ticket_image])
84
- return if @image.save
84
+ image = target.images.new(attachment: context.params[:ticket_image])
85
+ return if image.save
85
86
 
86
- context.fail!(message: @image.errors.full_messages.join(', '))
87
+ context.fail!(message: image.errors.full_messages.join(', '))
87
88
  end
88
89
 
89
90
  def create_place
@@ -1,6 +1,5 @@
1
1
  module Spree
2
2
  module OrderMailerDecorator
3
- # overrided
4
3
  def cancel_email(order, resend: false)
5
4
  @order = order.respond_to?(:id) ? order : Spree::Order.find(order)
6
5
  return false if @order.email.blank?
@@ -12,18 +11,34 @@ module Spree
12
11
  @order = order.respond_to?(:id) ? order : Spree::Order.find(order)
13
12
  return false if @order.email.blank?
14
13
 
14
+ @tenant = @order.tenant
15
+ if @tenant.present?
16
+ @brand_color = @tenant.preferences[:brand_primary_color]
17
+ @vendor_logo_url = @tenant.active_vendor&.logo&.original_url
18
+ end
19
+
15
20
  @current_store = @order.store
16
21
  @product_type = @order.products.first&.product_type || 'accommodation'
17
22
 
18
- subject = (resend ? "[#{Spree.t(:resend).upcase}] " : '')
23
+ subject = resend ? "[#{Spree.t(:resend).upcase}] " : ''
19
24
  subject += "#{@current_store&.name} Booking Confirmation ##{@order.number}"
20
25
 
21
- mail(to: @order.email, from: from_address, subject: subject, store_url: @current_store.url) do |format|
26
+ mail(to: @order.email, from: from_email_address, subject: subject, store_url: @current_store.url) do |format|
22
27
  format.html { render layout: 'spree_cm_commissioner/layouts/order_mailer' }
23
28
  format.text
24
29
  end
25
30
  end
26
31
 
32
+ private
33
+
34
+ def from_email_address
35
+ if @order.tenant.nil?
36
+ from_address
37
+ else
38
+ @order.tenant.active_vendor&.from_email
39
+ end
40
+ end
41
+
27
42
  def ticket_email(guest, email)
28
43
  @guest = guest
29
44
  @event = @guest.event
@@ -9,6 +9,7 @@ module SpreeCmCommissioner
9
9
  preference :payment_failed_image, :string, default: ''
10
10
  preference :payment_success_image, :string, default: ''
11
11
  preference :payment_loader, :string, default: ''
12
+ preference :brand_primary_color, :string, default: ''
12
13
  end
13
14
  end
14
15
  end
@@ -9,12 +9,14 @@ module SpreeCmCommissioner
9
9
  has_many :product_dynamic_fields, class_name: 'SpreeCmCommissioner::ProductDynamicField', dependent: :destroy
10
10
 
11
11
  enum data_type: { text: 0, number: 1, boolean: 2, checkbox: 3, radio: 4, selection: 5, textarea: 6 }
12
+ enum data_fill_stage: { pre_registration: 0, post_registration: 1, during_check_in: 2 }
12
13
 
13
14
  accepts_nested_attributes_for :dynamic_field_options, allow_destroy: true, reject_if: :all_blank
14
15
 
15
16
  validates :label, presence: true
16
17
  validates :data_type, presence: true
17
18
  validates :configurations, presence: true, allow_blank: false
19
+ validate :validate_data_fill_stage_phase
18
20
 
19
21
  def requires_dynamic_field_options?
20
22
  checkbox? || radio? || selection?
@@ -30,5 +32,23 @@ module SpreeCmCommissioner
30
32
  self.configurations ||= {}
31
33
  self.configurations['multiple_select'] = ActiveModel::Type::Boolean.new.cast(value)
32
34
  end
35
+
36
+ def required_at_stage?(stage)
37
+ data_fill_stage == stage.to_s
38
+ end
39
+
40
+ def display_label
41
+ label + " (#{data_fill_stage.humanize})"
42
+ end
43
+
44
+ private
45
+
46
+ def validate_data_fill_stage_phase
47
+ return if data_fill_stage.blank?
48
+
49
+ return if data_fill_stage.in?(self.class.data_fill_stages.keys)
50
+
51
+ errors.add(:data_fill_stage, "must be one of: #{self.class.data_fill_stages.keys.join(', ')}")
52
+ end
33
53
  end
34
54
  end
@@ -213,6 +213,62 @@ module SpreeCmCommissioner
213
213
  save!
214
214
  end
215
215
 
216
+ def missing_dynamic_fields_for_stage(stage)
217
+ required_fields = line_item.product.dynamic_fields.where(data_fill_stage: stage)
218
+ filled_ids = guest_dynamic_fields.map(&:dynamic_field_id)
219
+
220
+ required_fields.reject { |field| filled_ids.include?(field.id) }
221
+ end
222
+
223
+ def all_required_dynamic_fields_completed?(stage)
224
+ missing_dynamic_fields_for_stage(stage).blank?
225
+ end
226
+
227
+ def validate_dynamic_fields_for_phase(phase)
228
+ required_fields = line_item.product.dynamic_fields.where(data_fill_stage: phase)
229
+ filled_ids = guest_dynamic_fields.map(&:dynamic_field_id)
230
+
231
+ required_fields.each do |field|
232
+ errors.add(:base, "#{field.label} is required for #{phase} phase") unless filled_ids.include?(field.id)
233
+ end
234
+ end
235
+
236
+ def pre_registration_completed?
237
+ return true unless line_item.product.dynamic_fields.pre_registration.any?
238
+
239
+ pre_registration_fields_completed?
240
+ end
241
+
242
+ def post_registration_completed?
243
+ return true unless line_item.product.dynamic_fields.post_registration.any?
244
+
245
+ post_registration_fields_completed?
246
+ end
247
+
248
+ def check_in_completed?
249
+ return true unless line_item.product.dynamic_fields.during_check_in.any?
250
+
251
+ check_in_fields_completed?
252
+ end
253
+
254
+ def current_collection_phase
255
+ if check_in.present?
256
+ :during_check_in
257
+ elsif line_item.order.completed?
258
+ :post_registration
259
+ else
260
+ :pre_registration
261
+ end
262
+ end
263
+
264
+ def phase_completion_status
265
+ {
266
+ pre_registration: pre_registration_completed?,
267
+ post_registration: post_registration_completed?,
268
+ during_check_in: check_in_completed?
269
+ }
270
+ end
271
+
216
272
  # bib_number: 345, bib_prefix: 5KM, bib_zerofill: 5 => return 5KM00345
217
273
  # bib_number: 345, bib_prefix: 5KM, bib_zerofill: 2 => return 5KM345
218
274
  def formatted_bib_number
@@ -7,6 +7,7 @@ module SpreeCmCommissioner
7
7
  validates :value, presence: true
8
8
  validate :validate_value_format, if: -> { value.present? && dynamic_field.present? }
9
9
  validate :validate_option_reference, if: -> { dynamic_field_option.present? }
10
+ validate :check_required_fields_based_on_phase
10
11
 
11
12
  private
12
13
 
@@ -34,10 +35,49 @@ module SpreeCmCommissioner
34
35
 
35
36
  def validate_option_reference
36
37
  return if dynamic_field.blank? || dynamic_field_option.blank?
37
-
38
38
  return if dynamic_field_option.dynamic_field_id == dynamic_field_id
39
39
 
40
40
  errors.add(:dynamic_field_option, 'must belong to the same dynamic field')
41
41
  end
42
+
43
+ def before_checkin_phase?
44
+ guest.present? && guest.line_item.present? && guest.check_in.nil?
45
+ end
46
+
47
+ def after_purchase_phase?
48
+ guest.present? && guest.line_item.present? && guest.line_item.completed? && guest.check_in.nil?
49
+ end
50
+
51
+ def during_checkin_phase?
52
+ guest.present? && guest.check_in.present?
53
+ end
54
+
55
+ def pre_registration_required?
56
+ dynamic_field.data_fill_stage.to_sym == :pre_registration &&
57
+ guest.line_item.order.completed? && guest.check_in.blank?
58
+ end
59
+
60
+ def post_registration_required?
61
+ dynamic_field.data_fill_stage.to_sym == :post_registration &&
62
+ guest.line_item.order.completed? && guest.check_in.blank?
63
+ end
64
+
65
+ def during_check_in_required?
66
+ dynamic_field.data_fill_stage.to_sym == :during_check_in &&
67
+ guest.check_in.present?
68
+ end
69
+
70
+ def check_required_fields_based_on_phase
71
+ return if dynamic_field.blank? || value.present?
72
+
73
+ case dynamic_field.data_fill_stage.to_sym
74
+ when :pre_registration
75
+ errors.add(:value, I18n.t('guest_dynamic_field.errors.pre_registration_required')) if pre_registration_required?
76
+ when :post_registration
77
+ errors.add(:value, I18n.t('guest_dynamic_field.errors.post_registration_required')) if post_registration_required?
78
+ when :during_check_in
79
+ errors.add(:value, I18n.t('guest_dynamic_field.errors.during_check_in_required')) if during_check_in_required?
80
+ end
81
+ end
42
82
  end
43
83
  end
@@ -86,6 +86,28 @@ module SpreeCmCommissioner
86
86
  "#{Spree::Store.default.formatted_url}/tickets/#{slug}"
87
87
  end
88
88
 
89
+ def dynamic_fields_by_collection_phase
90
+ {
91
+ pre_registration: dynamic_fields.pre_registration.order(:position),
92
+ post_registration: dynamic_fields.post_registration.order(:position),
93
+ during_check_in: dynamic_fields.during_check_in.order(:position)
94
+ }
95
+ end
96
+
97
+ def required_dynamic_fields_completed?(guest, phase)
98
+ required_fields = dynamic_fields.where(data_fill_stage: phase)
99
+ return true if required_fields.empty?
100
+
101
+ filled_ids = guest.guest_dynamic_fields.where(dynamic_field_id: required_fields.select(:id)).pluck(:dynamic_field_id)
102
+ (required_fields.pluck(:id) - filled_ids).empty?
103
+ end
104
+
105
+ def required_fields_for_guest(guest)
106
+ dynamic_fields_by_collection_phase.transform_values do |fields|
107
+ fields.select { |field| guest.guest_dynamic_fields.where(dynamic_field: field).empty? }
108
+ end
109
+ end
110
+
89
111
  private
90
112
 
91
113
  def set_tenant
@@ -93,6 +93,7 @@ module SpreeCmCommissioner
93
93
  }
94
94
 
95
95
  base.validates :commission_rate, presence: true, numericality: { greater_than_or_equal_to: 0, less_than_or_equal_to: 100 }
96
+ base.validates :from_email, presence: true, if: :tenant_present?
96
97
 
97
98
  def base.by_vendor_id!(vendor_id)
98
99
  if vendor_id.to_s =~ /^\d+$/
@@ -195,6 +196,8 @@ module SpreeCmCommissioner
195
196
 
196
197
  vendor_kind_option_values.where(option_type_id: rules_option_type.id)
197
198
  end
199
+
200
+ delegate :present?, to: :tenant, prefix: true
198
201
  end
199
202
  end
200
203
 
@@ -2,7 +2,7 @@ module SpreeCmCommissioner
2
2
  module V2
3
3
  module Storefront
4
4
  class DynamicFieldSerializer < BaseSerializer
5
- attributes :id, :label, :data_type, :vendor_id, :multiple_select, :created_at, :updated_at, :position
5
+ attributes :id, :label, :data_type, :vendor_id, :multiple_select, :created_at, :updated_at, :position, :data_fill_stage
6
6
 
7
7
  has_many :dynamic_field_options, serializer: SpreeCmCommissioner::V2::Storefront::DynamicFieldOptionSerializer
8
8
  end
@@ -1,6 +1,6 @@
1
1
  <% content_for :page_title do %>
2
2
  <%= page_header_back_button spree.admin_tenants_url %>
3
- <%= link_to @tenant.name, spree.admin_tenants_url %> / <%= Spree.t('vendors') %>
3
+ <%= link_to Spree.t('tenants'), spree.admin_tenants_url %> / <%= link_to @tenant.name, spree.admin_tenants_url%> / <%= Spree.t('vendors') %>
4
4
  <% end %>
5
5
 
6
6
  <% content_for :page_actions do %>
@@ -24,13 +24,20 @@
24
24
  <tbody>
25
25
  <% @vendors.each do |vendor| %>
26
26
  <tr id="<%= spree_dom_id vendor %>" data-hook="admin_vendors_index_rows">
27
- <td><%= vendor.name %></td>
27
+ <td>
28
+ <% if can?(:edit, vendor) %>
29
+ <%= link_to vendor.name, edit_admin_vendor_path(vendor.slug) %>
30
+ <% else %>
31
+ <td><%= vendor.name %></td>
32
+ <% end %>
33
+ </td>
28
34
  <td><%= vendor.slug %></td>
29
35
  <td><%= vendor.created_at %></td>
30
36
  <td><%= vendor.updated_at %></td>
31
37
  <td><%= vendor.tenant.name %></td>
32
38
  <td data-hook="admin_tenants_index_row_actions" class="actions">
33
39
  <span class="d-flex justify-content-end">
40
+ <%= link_to_edit vendor, url: edit_admin_vendor_path(vendor.slug), no_text: true if can?(:edit, vendor) %>
34
41
  <%= link_to_delete vendor, url: admin_tenant_vendor_path(@tenant, vendor.id), no_text: true if can?(:delete, vendor) %>
35
42
  </span>
36
43
  </td>
@@ -101,6 +101,15 @@
101
101
  <% end %>
102
102
  </div>
103
103
 
104
+ <!-- Brand Primary Color Field -->
105
+ <div class="col-md-6">
106
+ <%= f.label :preferred_brand_primary_color, Spree.t(:brand_primary_color), class: 'form-label' %>
107
+ <%= f.text_field :preferred_brand_primary_color, class: 'form-control color-picker', placeholder: '#FF5733', value: @object.preferred_brand_primary_color %>
108
+ <div class="color-preview" style="margin-top: 10px;">
109
+ <%= content_tag :div, nil, style: "background-color: #{@object.preferred_brand_primary_color}; width: 60px; height: 60px; border: 1px solid #ccc; box-sizing: border-box; display: block;" %>
110
+ </div>
111
+ </div>
112
+
104
113
  <!-- State Field -->
105
114
  <div class="col-6">
106
115
  <%= f.field_container :state do %>
@@ -1,6 +1,7 @@
1
1
  <% content_for :page_title do %>
2
2
  <%= page_header_back_button spree.admin_tenants_url %>
3
- <%= link_to @object.name, spree.admin_tenants_url %>
3
+ <%= link_to Spree.t('tenants'), spree.admin_tenants_url %> /
4
+ <%= @object.name %>
4
5
  <% end %>
5
6
 
6
7
  <%= render partial: 'tabs', locals: { current: :edit } %>
@@ -40,7 +40,13 @@
40
40
  <tbody>
41
41
  <% @collection.each do |tenant| %>
42
42
  <tr id="<%= spree_dom_id tenant %>" data-hook="admin_tenants_index_rows">
43
- <td><%= tenant.name %></td>
43
+ <td>
44
+ <% if can?(:edit, tenant) %>
45
+ <%= link_to tenant.name, edit_admin_tenant_path(tenant.slug) %>
46
+ <% else %>
47
+ <%= tenant.name %>
48
+ <% end %>
49
+ </td>
44
50
  <td><%= tenant.slug %></td>
45
51
  <td><%= tenant.description %></td>
46
52
  <td><%= tenant.created_at %></td>
@@ -60,4 +60,18 @@
60
60
  <%= f.label :notification_email %>
61
61
  <%= f.email_field :notification_email, class: 'form-control' %>
62
62
  <% end %>
63
+ <%= f.field_container :from_email do %>
64
+ <%= f.label :from_email, Spree.t(:from_email)%>
65
+ <%= f.email_field :from_email, class: 'form-control' %>
66
+ <small class="form-text text-muted">
67
+ This is the email which will be the sender of all your Store emails (Order Confirmation, Shipment notification etc)
68
+ </small>
69
+ <% end %>
70
+ <%= f.field_container :support_email do %>
71
+ <%= f.label :support_email, Spree.t(:support_email) %>
72
+ <%= f.email_field :support_email, class: 'form-control' %>
73
+ <small class="form-text text-muted">
74
+ This email is visible to your Store visitors in the Footer section
75
+ </small>
76
+ <% end %>
63
77
  </div>
@@ -1,22 +1,33 @@
1
1
  <tr>
2
- <td class="email-body" width="570" cellpadding="0" cellspacing="0">
3
- <table class="email-body_inner" align="center" width="570" cellpadding="0" cellspacing="0" role="presentation">
4
- <!-- Body content -->
2
+ <td class="email-body" width="570" cellpadding="0" cellspacing="0">
3
+ <table class="email-body_inner" align="center" width="570" cellpadding="0" cellspacing="0" role="presentation">
5
4
  <tbody>
6
- <tr>
7
- <td>
8
- <div class="f-fallback">
9
- <%= render 'spree_cm_commissioner/order_mailer/greeting', order: @order %>
10
- <div class="content">
5
+ <tr>
6
+ <td>
7
+ <% if @order.tenant.nil? %>
8
+ <div class="f-fallback">
9
+ <%= render 'spree_cm_commissioner/order_mailer/greeting', order: @order %>
10
+ <div class="content">
11
11
  <%= render 'spree_cm_commissioner/order_mailer/order_total', order: @order %>
12
12
  <%= render 'spree_cm_commissioner/order_mailer/your_booking', order: @order, show_booking_header: true %>
13
13
  <%= render 'spree_cm_commissioner/order_mailer/customer_info', order: @order %>
14
- <%= render 'spree_cm_commissioner/order_mailer/support_contact'%>
15
- </div>
16
- </div>
17
- </td>
18
- </tr>
14
+ <%= render 'spree_cm_commissioner/order_mailer/support_contact' %>
15
+ </div>
16
+ </div>
17
+ <% else %>
18
+ <div class="f-fallback">
19
+ <%= render 'spree_cm_commissioner/order_mailer/tenant/greeting', order: @order %>
20
+ <div class="content">
21
+ <%= render 'spree_cm_commissioner/order_mailer/order_total', order: @order %>
22
+ <%= render 'spree_cm_commissioner/order_mailer/your_booking', order: @order, show_booking_header: true %>
23
+ <%= render 'spree_cm_commissioner/order_mailer/tenant/customer_info', order: @order %>
24
+ <%= render 'spree_cm_commissioner/order_mailer/tenant/support_contact' %>
25
+ </div>
26
+ </div>
27
+ <% end %>
28
+ </td>
29
+ </tr>
19
30
  </tbody>
20
- </table>
21
- </td>
22
- </tr>
31
+ </table>
32
+ </td>
33
+ </tr>
@@ -23,7 +23,11 @@
23
23
  <table class="email-footer" align="center" width="570" cellpadding="0" cellspacing="0" role="presentation">
24
24
  <tr>
25
25
  <td>
26
- <%= render 'spree_cm_commissioner/order_mailer/footer', store: @order.store%>
26
+ <% if @order.tenant.nil? %>
27
+ <%= render 'spree_cm_commissioner/order_mailer/footer', store: @order.store %>
28
+ <% else %>
29
+ <%= render 'spree_cm_commissioner/order_mailer/tenant/footer', store: @order.store %>
30
+ <% end %>
27
31
  </td>
28
32
  </tr>
29
33
  </table>
@@ -55,10 +55,10 @@
55
55
  border-radius: 1.5625rem 1.5625rem 0 0;
56
56
  background: #653ed7;
57
57
  background: url(<%= image_path("mailer/event-mailer-bg.png") %>) center center / cover,
58
- linear-gradient(45deg, #653ed7, rgba(0, 0, 255, 0) 70.71%),
59
- linear-gradient(225deg, #653ed7, rgba(0, 0, 255, 0) 70.71%),
60
- linear-gradient(330deg, #35ace0, rgba(0, 0, 255, 0) 70.71%),
61
- linear-gradient(150deg, #35ace0, rgba(0, 0, 255, 0) 70.71%);
58
+ linear-gradient(45deg, #653ed7, rgba(0, 0, 255, 0) 70.71%),
59
+ linear-gradient(225deg, #653ed7, rgba(0, 0, 255, 0) 70.71%),
60
+ linear-gradient(330deg, #35ace0, rgba(0, 0, 255, 0) 70.71%),
61
+ linear-gradient(150deg, #35ace0, rgba(0, 0, 255, 0) 70.71%);
62
62
  }
63
63
  #confirm-email #greeting-card p,
64
64
  #confirm-email #greeting-card .booking-confirmm,
@@ -66,6 +66,24 @@
66
66
  #confirm-email #greeting-card .description {
67
67
  color: #fff;
68
68
  }
69
+
70
+ #confirm-email #greeting-card-1 {
71
+ color: #fff;
72
+ padding: 1.5rem;
73
+ background-size: cover;
74
+ background-position: center;
75
+ background-repeat: no-repeat;
76
+ border-radius: 1.5625rem 1.5625rem 0 0;
77
+ background: #<%= @brand_color %>;
78
+ }
79
+ #confirm-email #greeting-card-1 p,
80
+ #confirm-email #greeting-card-1 .header,
81
+ #confirm-email #greeting-card-1 .booking-confirm,
82
+ #confirm-email #greeting-card-1 .hello,
83
+ #confirm-email #greeting-card-1 .description {
84
+ color: #fff;
85
+ }
86
+
69
87
  #confirm-email .align-right {
70
88
  vertical-align: top;
71
89
  }
@@ -74,6 +92,15 @@
74
92
  margin-top: 1.25rem;
75
93
  margin-bottom: 0.625rem;
76
94
  }
95
+ #confirm-email .icon-hang-meas {
96
+ width: 6.25rem;
97
+ }
98
+ #confirm-email .header {
99
+ display: flex;
100
+ align-items: center;
101
+ justify-content: space-between;
102
+ width: 100%;
103
+ }
77
104
  #confirm-email #greeting-card .hello,
78
105
  .booking-confirm {
79
106
  font-size: 1.375rem;
@@ -83,6 +110,15 @@
83
110
  margin-bottom: 0.25rem;
84
111
  margin-top: 1.25rem;
85
112
  }
113
+ #confirm-email #greeting-card-1 .booking-confirm {
114
+ font-size: 1.375rem;
115
+ font-style: normal;
116
+ font-weight: 700;
117
+ line-height: 1.75rem;
118
+ margin-bottom: 0.25rem;
119
+ margin-top: 1.25rem;
120
+ color: #fff;
121
+ }
86
122
  #confirm-email #greeting-card .booking-confirm {
87
123
  font-size: 1.5rem;
88
124
  padding: 0.625rem 0;
@@ -233,6 +269,7 @@
233
269
  }
234
270
  #confirm-email .notice-info .description {
235
271
  padding-left: 0.75rem;
272
+ color: #939393;
236
273
  }
237
274
  #confirm-email .notice-info a {
238
275
  color: #6444D8;
@@ -0,0 +1,42 @@
1
+ <div class="content-cell">
2
+ <div class="container">
3
+ <h2><%= I18n.t('mail.order_mailer.customer_info') %></h2>
4
+ <div class="flex two-columns">
5
+ <div class="two-columns-item">
6
+ <div class="flex two-columns-item_detail">
7
+ <div class="two-columns_icon"><%= image_tag "mailer/tenant_user.png", class: "mail-icon" %></div>
8
+ <div class="two-columns_text-container">
9
+ <p class="two-columns_title">Name</p>
10
+ <p class="two-columns_description"><%= user_full_name(order) %></p>
11
+ </div>
12
+ </div>
13
+ <div class="flex two-columns-item_detail">
14
+ <div class="two-columns_icon"><%= image_tag "mailer/mail.png", class: "mail-icon" %></div>
15
+ <div class="two-columns_text-container">
16
+ <p class="two-columns_title">Email</p>
17
+ <% if order.email.present? %>
18
+ <p class="two-columns_description"><%= link_to order.email, "mailto:#{order.email}" %></p>
19
+ <% end %>
20
+ </div>
21
+ </div>
22
+ </div>
23
+ <div class="vertical-separater"></div>
24
+ <div class="two-columns-item">
25
+ <div class="flex two-columns-item_detail">
26
+ <div class="two-columns_icon"><%= image_tag "mailer/tenant_phone.png", class: "mail-icon" %></div>
27
+ <div class="two-columns_text-container">
28
+ <p class="two-columns_title">Contact</p>
29
+ <% if order.phone_number.present? || order.intel_phone_number.present? %>
30
+ <p class="two-columns_description">
31
+ <%= link_to(
32
+ order.phone_number || order.intel_phone_number,
33
+ "tel:#{order.phone_number || order.intel_phone_number}") %>
34
+ </p>
35
+ <% end %>
36
+ </div>
37
+ </div>
38
+ </div>
39
+ </div>
40
+ <hr>
41
+ </div>
42
+ </div>
@@ -0,0 +1,25 @@
1
+ <div class="email-footer_inner">
2
+ <div class="mb-24"><%= store.description%></div>
3
+ <div class="mb-24 f-14"><%= store.address%></div>
4
+ <h2 class="mb-24">Connect with us</h2>
5
+ <table class="mb-24 social_icons">
6
+ <tbody>
7
+ <tr>
8
+ <% if store.facebook? %>
9
+ <td class="social_icon">
10
+ <%= link_to store.facebook, target: :_blank do %>
11
+ <%= image_tag "mailer/facebook.png", class: "mail-icon"%>
12
+ <% end %>
13
+ </td>
14
+ <% end %>
15
+ <% if store.instagram?%>
16
+ <td class="social_icon">
17
+ <%= link_to store.instagram, target: :_blank do %>
18
+ <%= image_tag "mailer/instagram.png", class: "mail-icon"%>
19
+ <% end %>
20
+ </td>
21
+ <% end %>
22
+ </tr>
23
+ </tbody>
24
+ </table>
25
+ </div>
@@ -0,0 +1,19 @@
1
+ <div id="greeting-card-1">
2
+ <div>
3
+ <div class="header">
4
+ <h1><%=@order.store.name %></h1>
5
+ <% if @vendor_logo_url.present? %>
6
+ <img src="<%= @vendor_logo_url %>" alt="Vendor Logo" class="icon-hang-meas">
7
+ <% end %>
8
+ </div>
9
+ <div class="booking-confirm">
10
+ <%= I18n.t('mail.order_mailer.booking_confirm') %>
11
+ </div>
12
+ <div class="hello">
13
+ <%= I18n.t('mail.order_mailer.hello', full_name: user_full_name(order)) %>
14
+ </div>
15
+ <div class="description">
16
+ <%= I18n.t('mail.order_mailer.booking_event', order_number: @order.number || 'NA') %>
17
+ </div>
18
+ </div>
19
+ </div>
@@ -0,0 +1,33 @@
1
+ <div class="content-cell">
2
+ <div class="container">
3
+ <h2><%= I18n.t('mail.order_mailer.bookmeplus_support')%></h2>
4
+ <div class="flex two-columns">
5
+ <div class="two-columns-item">
6
+ <div class="flex two-columns-item_detail">
7
+ <div class="two-columns_icon"><%= image_tag "mailer/tenant_phone.png", class: "mail-icon"%></div>
8
+ <div class="two-columns_text-container">
9
+ <p class="two-columns_title">Contact</p>
10
+ <% if current_store.contact_phone.present? %>
11
+ <p class="two-columns_description">
12
+ <%= link_to current_store.contact_phone, "tel:#{current_store.contact_phone}" %>
13
+ </p>
14
+ <% end %>
15
+ </div>
16
+ </div>
17
+ </div>
18
+ <div class="vertical-separater"></div>
19
+ <div class="two-columns-item">
20
+ <div class="flex two-columns-item_detail">
21
+ <div class="two-columns_icon"><%= image_tag "mailer/mail.png", class: "mail-icon"%></div>
22
+ <div class="two-columns_text-container">
23
+ <p class="two-columns_title">Email</p>
24
+ <p class="two-columns_description">
25
+ <%= @vendor&.support_email.present? ? link_to(@vendor.support_email, "mailto:#{@vendor.support_email}") : "N/A" %>
26
+ </p>
27
+
28
+ </div>
29
+ </div>
30
+ </div>
31
+ </div>
32
+ </div>
33
+ </div>
@@ -54,7 +54,11 @@ en:
54
54
  guest_card_classes:
55
55
  empty_info: "No guest card classes information available."
56
56
  background_image: "Background Image"
57
-
57
+ guest_dynamic_field:
58
+ errors:
59
+ pre_registration_required: "is required before ticket issuance"
60
+ post_registration_required: "must be completed after purchase"
61
+ during_check_in_required: "is required during check-in"
58
62
  google_wallet:
59
63
  note: "Click <b>Create</b> or <b> Update</b> to connect to Google Wallet after filled all information "
60
64
  google_wallet_class_created: "Google Wallet class created successfully."
@@ -0,0 +1,6 @@
1
+ class AddEmailFieldsToSpreeVendors < ActiveRecord::Migration[7.0]
2
+ def change
3
+ add_column :spree_vendors, :from_email, :string, if_not_exists: true
4
+ add_column :spree_vendors, :support_email, :string, if_not_exists: true
5
+ end
6
+ end
@@ -0,0 +1,5 @@
1
+ class AddDataFillStageToDynamicFields < ActiveRecord::Migration[7.0]
2
+ def change
3
+ add_column :cm_dynamic_fields, :data_fill_stage, :integer, default: 0, null: false, if_not_exists: true
4
+ end
5
+ end
@@ -1,5 +1,5 @@
1
1
  module SpreeCmCommissioner
2
- VERSION = '1.21.0'.freeze
2
+ VERSION = '2.0.0'.freeze
3
3
 
4
4
  module_function
5
5
 
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: spree_cm_commissioner
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.21.0
4
+ version: 2.0.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - You
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2025-07-23 00:00:00.000000000 Z
11
+ date: 2025-07-28 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: spree
@@ -652,8 +652,11 @@ files:
652
652
  - app/assets/images/mailer/facebook.png
653
653
  - app/assets/images/mailer/instagram.png
654
654
  - app/assets/images/mailer/line_item-mailer-bg.png
655
+ - app/assets/images/mailer/mail.png
655
656
  - app/assets/images/mailer/map-marker.png
656
657
  - app/assets/images/mailer/phone.png
658
+ - app/assets/images/mailer/tenant_phone.png
659
+ - app/assets/images/mailer/tenant_user.png
657
660
  - app/assets/images/mailer/twitter.png
658
661
  - app/assets/images/mailer/user.png
659
662
  - app/assets/images/payment_methods/cm-payment-affirm.svg
@@ -2187,6 +2190,10 @@ files:
2187
2190
  - app/views/spree_cm_commissioner/order_mailer/hotel/_booking_detail.html.erb
2188
2191
  - app/views/spree_cm_commissioner/order_mailer/purchased_items/_items.html.erb
2189
2192
  - app/views/spree_cm_commissioner/order_mailer/purchased_items/_summary.html.erb
2193
+ - app/views/spree_cm_commissioner/order_mailer/tenant/_customer_info.html.erb
2194
+ - app/views/spree_cm_commissioner/order_mailer/tenant/_footer.html.erb
2195
+ - app/views/spree_cm_commissioner/order_mailer/tenant/_greeting.html.erb
2196
+ - app/views/spree_cm_commissioner/order_mailer/tenant/_support_contact.html.erb
2190
2197
  - app/views/spree_cm_commissioner/pin_code_mailer/send_pin_code.html.erb
2191
2198
  - app/views/spree_cm_commissioner/team_invite_mailer/_mailer_stylesheets.html.erb
2192
2199
  - app/views/spree_cm_commissioner/team_invite_mailer/send_team_invite_email.html.erb
@@ -2533,11 +2540,13 @@ files:
2533
2540
  - db/migrate/20250702091305_add_dynamic_field_option_to_guest_dynamic_field.rb
2534
2541
  - db/migrate/20250702091935_add_status_to_dynamic_field_option.rb
2535
2542
  - db/migrate/20250707032008_add_vendor_id_to_spree_category.rb
2543
+ - db/migrate/20250709073455_add_email_fields_to_spree_vendors.rb
2536
2544
  - db/migrate/20250711092937_add_position_to_cm_dynamic_fields.rb
2537
2545
  - db/migrate/20250711093045_add_position_to_cm_dynamic_field_options.rb
2538
2546
  - db/migrate/20250714121508_rename_cm_taxon_blazer_query_to_blazer_queryables.rb
2539
2547
  - db/migrate/20250714124323_make_cm_blazer_queryables_polymorphic.rb
2540
2548
  - db/migrate/20250715103333_remove_indexes_from_cm_user_identity_providers.rb
2549
+ - db/migrate/20250718071620_add_data_fill_stage_to_dynamic_fields.rb
2541
2550
  - docker-compose.yml
2542
2551
  - docs/option_types/attr_types.md
2543
2552
  - docs/private_key.pem