spree_cm_commissioner 1.11.0.pre.pre3 → 1.12.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 (166) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/test_and_build_gem.yml +143 -12
  3. data/.gitignore +1 -2
  4. data/Gemfile.lock +1 -22
  5. data/Rakefile +4 -33
  6. data/app/controllers/spree/admin/cms_pages_controller_decorator.rb +32 -0
  7. data/app/controllers/spree/admin/stock_managements_controller.rb +1 -62
  8. data/app/controllers/spree/api/v2/storefront/accommodations_controller.rb +31 -14
  9. data/app/controllers/spree/api/v2/storefront/queue_cart/line_items_controller.rb +2 -2
  10. data/app/controllers/spree/api/v2/storefront/waiting_room_sessions_controller.rb +1 -0
  11. data/app/controllers/spree/api/v2/tenant/account_checker_controller.rb +1 -1
  12. data/app/controllers/spree/api/v2/tenant/account_recovers_controller.rb +2 -2
  13. data/app/controllers/spree/api/v2/tenant/cms_pages_controller.rb +41 -0
  14. data/app/controllers/spree/api/v2/tenant/reset_passwords_controller.rb +1 -1
  15. data/app/controllers/spree/api/v2/tenant/waiting_room_sessions_controller.rb +30 -0
  16. data/app/controllers/spree_cm_commissioner/admin/variants_controller_decorator.rb +17 -0
  17. data/app/controllers/spree_cm_commissioner/api/v2/storefront/cms_pages_controller_decorator.rb +18 -0
  18. data/app/interactors/spree_cm_commissioner/account_recover.rb +2 -2
  19. data/app/interactors/spree_cm_commissioner/create_event.rb +7 -26
  20. data/app/interactors/spree_cm_commissioner/create_ticket.rb +95 -0
  21. data/app/interactors/spree_cm_commissioner/event_line_items_date_syncer.rb +19 -0
  22. data/app/interactors/spree_cm_commissioner/existing_account_checker.rb +1 -1
  23. data/app/interactors/spree_cm_commissioner/organizers_transactional_email_notifier.rb +27 -0
  24. data/app/interactors/spree_cm_commissioner/pin_code_sender.rb +3 -3
  25. data/app/interactors/spree_cm_commissioner/product_event_id_to_children_syncer.rb +15 -0
  26. data/app/interactors/spree_cm_commissioner/sms.rb +14 -0
  27. data/app/interactors/spree_cm_commissioner/telegram_debug_pin_code_sender.rb +2 -1
  28. data/app/interactors/spree_cm_commissioner/transactional_email_sender.rb +50 -0
  29. data/app/interactors/spree_cm_commissioner/user_forgotten_password_updater.rb +2 -1
  30. data/app/interactors/spree_cm_commissioner/user_password_authenticator.rb +4 -2
  31. data/app/interactors/spree_cm_commissioner/waiting_room_session_creator.rb +7 -14
  32. data/app/interactors/spree_cm_commissioner/waiting_room_session_firebase_logger.rb +30 -0
  33. data/app/jobs/spree_cm_commissioner/ensure_event_for_product_line_item_guests_job.rb +13 -0
  34. data/app/jobs/spree_cm_commissioner/event_line_items_date_syncer_job.rb +8 -0
  35. data/app/jobs/spree_cm_commissioner/product_event_id_to_children_syncer_job.rb +8 -0
  36. data/app/jobs/spree_cm_commissioner/telegram_debug_pin_code_sender_job.rb +3 -1
  37. data/app/jobs/spree_cm_commissioner/waiting_room_session_firebase_logger_job.rb +13 -0
  38. data/app/mailers/spree_cm_commissioner/event_transactional_mailer.rb +23 -0
  39. data/app/mailers/spree_cm_commissioner/pin_code_mailer.rb +1 -0
  40. data/app/models/concerns/spree_cm_commissioner/kyc_bitwise.rb +2 -0
  41. data/app/models/concerns/spree_cm_commissioner/line_item_durationable.rb +10 -6
  42. data/app/models/concerns/spree_cm_commissioner/order_state_machine.rb +14 -26
  43. data/app/models/concerns/spree_cm_commissioner/product_delegation.rb +4 -3
  44. data/app/models/concerns/spree_cm_commissioner/product_type.rb +0 -10
  45. data/app/models/concerns/spree_cm_commissioner/user_identity.rb +7 -4
  46. data/app/models/concerns/spree_cm_commissioner/variant_options_concern.rb +8 -10
  47. data/app/models/spree_cm_commissioner/cms_page_decorator.rb +9 -0
  48. data/app/models/spree_cm_commissioner/event_ticket_google_wallet.rb +2 -2
  49. data/app/models/spree_cm_commissioner/guest.rb +1 -1
  50. data/app/models/spree_cm_commissioner/line_item_decorator.rb +10 -16
  51. data/app/models/spree_cm_commissioner/option_type_decorator.rb +6 -0
  52. data/app/models/spree_cm_commissioner/order_decorator.rb +0 -15
  53. data/app/models/spree_cm_commissioner/product_decorator.rb +17 -14
  54. data/app/models/spree_cm_commissioner/role_decorator.rb +3 -0
  55. data/app/models/spree_cm_commissioner/stock/availability_checker.rb +25 -27
  56. data/app/models/spree_cm_commissioner/stock/availability_validator_decorator.rb +1 -2
  57. data/app/models/spree_cm_commissioner/stock/line_item_availability_checker.rb +3 -3
  58. data/app/models/spree_cm_commissioner/stock_item_decorator.rb +0 -14
  59. data/app/models/spree_cm_commissioner/taxon_decorator.rb +24 -3
  60. data/app/models/spree_cm_commissioner/transactional_email.rb +6 -0
  61. data/app/models/spree_cm_commissioner/user_decorator.rb +6 -0
  62. data/app/models/spree_cm_commissioner/variant_decorator.rb +27 -34
  63. data/app/models/spree_cm_commissioner/vehicle.rb +0 -7
  64. data/app/models/spree_cm_commissioner/vendor_decorator.rb +9 -1
  65. data/app/overrides/spree/admin/cms_pages/_form/tenant_fields.html.erb.deface +9 -0
  66. data/app/overrides/spree/admin/cms_pages/index/cms_pages_tabs.html.erb.deface +21 -0
  67. data/app/overrides/spree/admin/taxons/_form/assets_form.html.erb.deface +2 -2
  68. data/app/overrides/spree/admin/taxons/_form/available_on.html.erb.deface +3 -1
  69. data/app/overrides/spree/admin/taxons/_form/background_color_and_foreground_color.html.erb.deface +3 -1
  70. data/app/overrides/spree/admin/taxons/_form/custom_redirect_url.html.erb.deface +3 -1
  71. data/app/overrides/spree/admin/taxons/_form/hide_video_banner.html.erb.deface +3 -1
  72. data/app/overrides/spree/admin/taxons/_form/purchasable_on_status.html.erb.deface +3 -1
  73. data/app/overrides/spree/admin/taxons/_form/show_badge_status.html.erb.deface +3 -1
  74. data/app/overrides/spree/admin/taxons/_form/to_date_form_date.html.erb.deface +3 -4
  75. data/app/overrides/spree/admin/users/_form/roles_fields.html.erb.deface +1 -1
  76. data/app/overrides/spree/admin/users/index/body.html.erb.deface +3 -0
  77. data/app/overrides/spree/admin/users/index/headers.html.erb.deface +3 -0
  78. data/app/overrides/spree/admin/variants/_form/kyc_field.html.erb.deface +40 -0
  79. data/app/overrides/spree/admin/variants/edit/variant_status.html.erb.deface +6 -3
  80. data/app/queries/spree_cm_commissioner/variant_availability/non_permanent_stock_query.rb +45 -0
  81. data/app/queries/spree_cm_commissioner/variant_availability/permanent_stock_query.rb +55 -0
  82. data/app/request_schemas/spree_cm_commissioner/accommodation_request_schema.rb +0 -3
  83. data/app/request_schemas/spree_cm_commissioner/application_request_schema.rb +1 -1
  84. data/app/serializers/spree/v2/storefront/accommodation_serializer.rb +0 -2
  85. data/app/serializers/spree/v2/storefront/taxon_serializer_decorator.rb +2 -0
  86. data/app/serializers/spree/v2/tenant/waiting_room_session_serializer.rb +9 -0
  87. data/app/services/spree_cm_commissioner/user_authenticator.rb +3 -1
  88. data/app/services/spree_cm_commissioner/user_roles_assigner.rb +62 -0
  89. data/app/views/spree/admin/shared/_cms_pages_tabs.html.erb +20 -0
  90. data/app/views/spree/admin/shared/_taxon_tabs.html.erb +19 -19
  91. data/app/views/spree/admin/stock_managements/_variant_stock_items.html.erb +1 -3
  92. data/app/views/spree/admin/stock_managements/index.html.erb +5 -40
  93. data/app/views/spree/shared/_base_mailer_header.html.erb +10 -2
  94. data/app/views/spree_cm_commissioner/event_transactional_mailer/_event_banner.html.erb +3 -0
  95. data/app/views/spree_cm_commissioner/event_transactional_mailer/_mailer_stylesheets.html.erb +315 -0
  96. data/app/views/spree_cm_commissioner/event_transactional_mailer/_note.html.erb +8 -0
  97. data/app/views/spree_cm_commissioner/event_transactional_mailer/_share_button.html.erb +3 -0
  98. data/app/views/spree_cm_commissioner/event_transactional_mailer/send_to_organizer.html.erb +59 -0
  99. data/app/views/spree_cm_commissioner/event_transactional_mailer/send_to_participant.html.erb +52 -0
  100. data/app/views/spree_cm_commissioner/layouts/event_transactional_mailer.html.erb +14 -0
  101. data/config/initializers/spree_permitted_attributes.rb +0 -5
  102. data/config/locales/en.yml +36 -0
  103. data/config/locales/km.yml +2 -0
  104. data/config/routes.rb +5 -11
  105. data/db/migrate/20250509033437_create_spree_cm_commissioner_transactional_emails.rb +12 -0
  106. data/db/migrate/20250509075429_add_max_order_quantity_to_spree_product.rb +5 -0
  107. data/db/migrate/20250512075319_add_tenant_id_to_spree_cms_pages.rb +6 -0
  108. data/db/migrate/20250520042602_add_event_to_spree_products.rb +7 -0
  109. data/db/migrate/20250520044533_add_event_to_spree_line_items.rb +7 -0
  110. data/db/migrate/20250521024345_add_tenant_id_to_cm_waiting_room_sessions.rb +6 -0
  111. data/db/migrate/20250521095539_add_kyc_to_spree_variants.rb +5 -0
  112. data/docker-compose.yml +1 -1
  113. data/lib/generators/spree_cm_commissioner/install/install_generator.rb +3 -11
  114. data/lib/generators/spree_cm_commissioner/install/templates/app/javascript/{spree_dashboard/spree_cm_commissioner → spree_cm_commissioner}/utilities.js +0 -4
  115. data/lib/spree_cm_commissioner/calendar_event.rb +1 -11
  116. data/lib/spree_cm_commissioner/test_helper/factories/homepage_section_relatable_factory.rb +1 -1
  117. data/lib/spree_cm_commissioner/test_helper/factories/line_item_factory.rb +1 -1
  118. data/lib/spree_cm_commissioner/test_helper/factories/product_factory.rb +5 -18
  119. data/lib/spree_cm_commissioner/test_helper/factories/role.rb +7 -0
  120. data/lib/spree_cm_commissioner/test_helper/factories/stock_location_factory.rb +2 -2
  121. data/lib/spree_cm_commissioner/test_helper/factories/taxon_home_banner_factory.rb +5 -0
  122. data/lib/spree_cm_commissioner/test_helper/factories/transactional_email_factory.rb +6 -0
  123. data/lib/spree_cm_commissioner/test_helper/factories/variant_factory.rb +6 -41
  124. data/lib/spree_cm_commissioner/test_helper/factories/vendor_factory.rb +1 -1
  125. data/lib/spree_cm_commissioner/version.rb +1 -1
  126. data/lib/spree_cm_commissioner.rb +0 -34
  127. data/lib/tasks/ensure_event_for_product_line_item_guests.rake +7 -0
  128. data/lib/tasks/update_invalid_self_root_places.rake +9 -0
  129. data/lib/tasks/update_orphan_root_places.rake +1 -0
  130. data/spree_cm_commissioner.gemspec +0 -5
  131. metadata +49 -82
  132. data/app/controllers/spree/api/v2/storefront/accommodations/variants_controller.rb +0 -42
  133. data/app/finders/spree_cm_commissioner/accommodations/find.rb +0 -40
  134. data/app/finders/spree_cm_commissioner/accommodations/find_variant.rb +0 -35
  135. data/app/interactors/spree_cm_commissioner/ensure_correct_product_type.rb +0 -40
  136. data/app/interactors/spree_cm_commissioner/inventory_item_syncer.rb +0 -25
  137. data/app/interactors/spree_cm_commissioner/stock/inventory_items_adjuster.rb +0 -13
  138. data/app/interactors/spree_cm_commissioner/stock/inventory_items_generator.rb +0 -15
  139. data/app/interactors/spree_cm_commissioner/stock/permanent_inventory_items_generator.rb +0 -75
  140. data/app/interactors/spree_cm_commissioner/stock/stock_movement_creator.rb +0 -24
  141. data/app/interactors/spree_cm_commissioner/user_roles_assigner.rb +0 -22
  142. data/app/jobs/spree_cm_commissioner/ensure_correct_product_type_job.rb +0 -7
  143. data/app/jobs/spree_cm_commissioner/inventory_item_syncer_job.rb +0 -7
  144. data/app/jobs/spree_cm_commissioner/stock/inventory_items_adjuster_job.rb +0 -11
  145. data/app/jobs/spree_cm_commissioner/stock/inventory_items_generator_job.rb +0 -11
  146. data/app/jobs/spree_cm_commissioner/stock/permanent_inventory_items_generator_job.rb +0 -9
  147. data/app/models/spree_cm_commissioner/inventory.rb +0 -11
  148. data/app/models/spree_cm_commissioner/inventory_item.rb +0 -56
  149. data/app/models/spree_cm_commissioner/redis_stock/cached_inventory_items_builder.rb +0 -40
  150. data/app/models/spree_cm_commissioner/redis_stock/inventory_updater.rb +0 -126
  151. data/app/models/spree_cm_commissioner/redis_stock/line_items_cached_inventory_items_builder.rb +0 -36
  152. data/app/models/spree_cm_commissioner/redis_stock/variant_cached_inventory_items_builder.rb +0 -27
  153. data/app/models/spree_cm_commissioner/stock/order_availability_checker.rb +0 -44
  154. data/app/request_schemas/spree_cm_commissioner/variant_request_schema.rb +0 -19
  155. data/app/views/spree/admin/stock_managements/_events_popover.html.erb +0 -23
  156. data/app/views/spree/admin/stock_managements/calendar.html.erb +0 -35
  157. data/db/migrate/20250304293518_create_cm_inventory_items.rb +0 -21
  158. data/db/migrate/20250429094228_add_lock_version_to_cm_inventory_items.rb +0 -5
  159. data/db/migrate/20250502025848_add_index_to_spree_products.rb +0 -5
  160. data/db/migrate/20250502030001_add_product_type_to_spree_variants.rb +0 -5
  161. data/db/migrate/20250502030002_add_product_type_to_spree_line_items.rb +0 -5
  162. data/lib/spree_cm_commissioner/cached_inventory_item.rb +0 -23
  163. data/lib/spree_cm_commissioner/test_helper/factories/inventory_item_factory.rb +0 -9
  164. data/lib/tasks/create_default_non_permanent_inventory_items.rake +0 -16
  165. data/lib/tasks/ensure_correct_product_type.rake +0 -7
  166. data/lib/tasks/generate_inventory_items.rake +0 -7
@@ -134,6 +134,12 @@ module SpreeCmCommissioner
134
134
  def update_otp_enabled
135
135
  self.otp_enabled = otp_email || otp_phone_number
136
136
  end
137
+
138
+ def update_tracked_fields!(request)
139
+ ActiveRecord::Base.connected_to(role: :writing) do
140
+ super(request)
141
+ end
142
+ end
137
143
  end
138
144
  end
139
145
 
@@ -1,14 +1,16 @@
1
1
  module SpreeCmCommissioner
2
2
  module VariantDecorator
3
- def self.prepended(base) # rubocop:disable Metrics/AbcSize
4
- base.include SpreeCmCommissioner::ProductType
3
+ def self.prepended(base)
5
4
  base.include SpreeCmCommissioner::ProductDelegation
6
5
  base.include SpreeCmCommissioner::VariantOptionsConcern
6
+ base.include SpreeCmCommissioner::KycBitwise
7
+
7
8
  base.after_commit :update_vendor_price
8
9
  base.validate :validate_option_types
9
10
  base.before_save -> { self.track_inventory = false }, if: :subscribable?
10
11
 
11
12
  base.belongs_to :vendor, class_name: 'Spree::Vendor'
13
+ base.has_one :event, class_name: 'Spree::Taxon', through: :product
12
14
 
13
15
  base.has_many :taxons, class_name: 'Spree::Taxon', through: :product
14
16
  base.has_many :visible_option_values, lambda {
@@ -21,19 +23,11 @@ module SpreeCmCommissioner
21
23
  base.has_many :variant_guest_card_class, class_name: 'SpreeCmCommissioner::VariantGuestCardClass'
22
24
  base.has_many :guest_card_classes, class_name: 'SpreeCmCommissioner::GuestCardClass', through: :variant_guest_card_class
23
25
 
24
- base.has_many :inventory_items, class_name: 'SpreeCmCommissioner::InventoryItem'
25
-
26
26
  base.scope :subscribable, -> { active.joins(:product).where(product: { subscribable: true, status: :active }) }
27
- base.scope :with_permanent_stock, -> { where(product_type: base::PERMANENT_STOCK_PRODUCT_TYPES) }
28
- base.scope :with_non_permanent_stock, -> { where.not(product_type: base::PERMANENT_STOCK_PRODUCT_TYPES) }
29
-
30
27
  base.has_one :trip,
31
28
  class_name: 'SpreeCmCommissioner::Trip'
32
29
  base.has_many :trip_stops, class_name: 'SpreeCmCommissioner::TripStop', dependent: :destroy, foreign_key: :trip_id
33
30
  base.accepts_nested_attributes_for :option_values
34
-
35
- base.before_save -> { self.product_type = product.product_type }, if: -> { product_type.nil? }
36
-
37
31
  base.after_commit :sync_trip, if: :transit?
38
32
  base.accepts_nested_attributes_for :trip_stops, allow_destroy: true
39
33
  base.after_commit :create_trip_stops, if: :transit?
@@ -59,24 +53,8 @@ module SpreeCmCommissioner
59
53
  super || product.discontinued?
60
54
  end
61
55
 
62
- def event
63
- taxons.event.first
64
- end
65
-
66
- def default_inventory_item_exist?
67
- inventory_items.exists?(inventory_date: nil)
68
- end
69
-
70
- def create_default_non_permanent_inventory_item!(quantity_available: nil, max_capacity: nil)
71
- return if product_type.blank? # handle in case product not exist for variant.
72
- return unless should_track_inventory?
73
- return if default_inventory_item_exist?
74
-
75
- inventory_items.create!(
76
- product_type: product_type,
77
- quantity_available: [0, quantity_available || total_on_hand].max,
78
- max_capacity: [0, max_capacity || total_on_hand].max
79
- )
56
+ def permanent_stock?
57
+ accommodation?
80
58
  end
81
59
 
82
60
  # override
@@ -94,18 +72,33 @@ module SpreeCmCommissioner
94
72
  "#{display_sku} - #{display_price}"
95
73
  end
96
74
 
75
+ def transit?
76
+ product.product_type == 'transit'
77
+ end
78
+
97
79
  # override
98
- def in_stock?(options = {})
99
- SpreeCmCommissioner::Stock::AvailabilityChecker.new(self, options).can_supply?
80
+ def in_stock?
81
+ available_quantity.positive?
100
82
  end
101
83
 
102
84
  private
103
85
 
86
+ def total_purchases
87
+ Spree::LineItem.complete.where(variant_id: id).sum(:quantity).to_i
88
+ end
89
+
90
+ def available_quantity
91
+ stock_count = stock_items.sum(&:count_on_hand)
92
+ return stock_count if delivery_required?
93
+
94
+ stock_count - total_purchases
95
+ end
96
+
104
97
  def update_vendor_price
105
- return unless vendor.present? && product_type == vendor&.primary_product_type
98
+ return unless vendor.present? && product&.product_type == vendor&.primary_product_type
106
99
 
107
- vendor.update(min_price: price) if price < vendor.min_price
108
- vendor.update(max_price: price) if price > vendor.max_price
100
+ vendor.update(min_price: price) if vendor.min_price.nil? || price < vendor.min_price
101
+ vendor.update(max_price: price) if vendor.max_price.nil? || price > vendor.max_price
109
102
  end
110
103
 
111
104
  def validate_option_types
@@ -128,7 +121,7 @@ module SpreeCmCommissioner
128
121
  end
129
122
 
130
123
  def sync_trip
131
- return unless transit?
124
+ return unless product.product_type == 'transit'
132
125
 
133
126
  trip = SpreeCmCommissioner::Trip.find_or_initialize_by(variant_id: id)
134
127
 
@@ -8,7 +8,6 @@ module SpreeCmCommissioner
8
8
  has_one :primary_photo, -> { order(position: :asc) }, class_name: 'SpreeCmCommissioner::VehiclePhoto', as: :viewable, dependent: :destroy
9
9
  belongs_to :vendor, class_name: 'Spree::Vendor'
10
10
 
11
- before_save :set_attributes
12
11
  after_commit :create_vehicle_option_value
13
12
 
14
13
  has_many :vehicle_photo, class_name: 'SpreeCmCommissioner::VehiclePhoto', as: :viewable, dependent: :destroy
@@ -17,12 +16,6 @@ module SpreeCmCommissioner
17
16
  validates :code, uniqueness: { scope: :vendor_id }, presence: true
18
17
  validates :license_plate, uniqueness: {}, allow_blank: true
19
18
 
20
- def set_attributes
21
- self.route_type = vehicle_type.route_type
22
- self.number_of_seats = vehicle_type.vehicle_seats_count
23
- self.allow_seat_selection = vehicle_type.allow_seat_selection
24
- end
25
-
26
19
  def create_vehicle_option_value
27
20
  SpreeCmCommissioner::VehicleOptionValueCreator.call(self)
28
21
  end
@@ -79,7 +79,8 @@ module SpreeCmCommissioner
79
79
  dependent: :destroy, inverse_of: :relatable
80
80
 
81
81
  base.has_many :vehicle_types, class_name: 'SpreeCmCommissioner::VehicleType', dependent: :destroy
82
- base.has_many :vehicles, through: :vehicle_types, class_name: 'SpreeCmCommissioner::Vehicle', dependent: :destroy
82
+ base.has_many :vehicles, class_name: 'SpreeCmCommissioner::Vehicle', dependent: :destroy
83
+ # base.has_many :vehicles, through: :vehicle_types, class_name: 'SpreeCmCommissioner::Vehicle', dependent: :destroy
83
84
 
84
85
  base.validates :account_name, :account_number, presence: true, if: lambda {
85
86
  payment_qrcode.present? && Spree::Store.default.code.include?('billing')
@@ -181,6 +182,13 @@ module SpreeCmCommissioner
181
182
  def organizer_url
182
183
  "#{Spree::Store.default.formatted_url}/organizers/#{slug}"
183
184
  end
185
+
186
+ def vendor_rules
187
+ rules_option_type = Spree::OptionType.rules_option_type
188
+ return vendor_kind_option_values.none if rules_option_type.nil?
189
+
190
+ vendor_kind_option_values.where(option_type_id: rules_option_type.id)
191
+ end
184
192
  end
185
193
  end
186
194
 
@@ -0,0 +1,9 @@
1
+ <!-- insert_before "erb[loud]:contains('f.field_container :title')" -->
2
+
3
+ <%= f.field_container :tenant_id, class: ['col-12'] do %>
4
+ <%= f.label :tenant_id, raw(Spree.t(:tenant) + ' (optional)') %>
5
+ <%= f.collection_select :tenant_id, SpreeCmCommissioner::Tenant.all, :id, :name,
6
+ { prompt: Spree.t('select_tenant'), include_blank: Spree.t('default_store') },
7
+ { class: 'form-control select2' } %>
8
+ <%= f.error_message_on :tenant_id %>
9
+ <% end %>
@@ -0,0 +1,21 @@
1
+ <!-- insert_before "erb[silent]:contains('content_for :page_actions')" -->
2
+
3
+ <%= render partial: 'spree/admin/shared/cms_pages_tabs' %>
4
+
5
+ <% if params[:tab] == 'default' %>
6
+ <div class="alert alert-info mb-3">
7
+ <%= svg_icon name: "info-circle.svg", classes: 'mr-2', width: '16', height: '16' %>
8
+ CMS Pages for the default store. These pages are shared globally and are not tied to any specific tenant.
9
+ </div>
10
+ <% elsif params[:tab] == 'tenants' && params[:tenant_id].present? %>
11
+ <div class="alert alert-info mb-3">
12
+ <%= svg_icon name: "info-circle.svg", classes: 'mr-2', width: '16', height: '16' %>
13
+ CMS Pages for the tenant: <strong><%= SpreeCmCommissioner::Tenant.find(params[:tenant_id]).name %></strong>.
14
+ These pages are displayed only to users associated with this tenant.
15
+ </div>
16
+ <% elsif params[:tab] == 'tenants' %>
17
+ <div class="alert alert-info mb-3">
18
+ <%= svg_icon name: "info-circle.svg", classes: 'mr-2', width: '16', height: '16' %>
19
+ CMS Pages for all tenants. These pages are displayed only to users associated with specific tenants.
20
+ </div>
21
+ <% end %>
@@ -1,7 +1,8 @@
1
1
  <!-- replace "erb[loud]:contains('assets_form')" -->
2
2
 
3
+ <%# Sections don't use assets, so we hide the form here to avoid confusing admin. %>
3
4
 
4
- <div class="row">
5
+ <div class="row <%= "d-none" unless @taxon.depth == 1 %>">
5
6
  <%# "@taxon.icon is not used yet" %>
6
7
  <%# render 'shared/asset_field',
7
8
  field: :icon,
@@ -67,4 +68,3 @@
67
68
  <% end %>
68
69
  <% end %>
69
70
  </div>
70
-
@@ -1,6 +1,8 @@
1
1
  <!-- insert_before "erb[loud]:contains('field_container :parent_id')" -->
2
2
 
3
- <div class="form-group">
3
+ <%# Sections don't use available_on, so we hide the form here to avoid confusing admin. %>
4
+
5
+ <div class="form-group <%= "d-none" unless @taxon.depth == 1 %>">
4
6
  <%= label_tag nil, Spree.t(:available_on) %>
5
7
  <div class="input-group datePickerFrom"
6
8
  data-wrap="true"
@@ -1,6 +1,8 @@
1
1
  <!-- insert_before "erb[loud]:contains('field_container :meta_title')" -->
2
2
 
3
- <div class="row">
3
+ <%# Sections don't use colors, so we hide the form here to avoid confusing admin. %>
4
+
5
+ <div class="row <%= "d-none" unless @taxon.depth == 1 %>">
4
6
  <div class="col-md-6">
5
7
  <%= f.label :preferred_background_color, Spree.t('background_color'), class: 'form-label' %>
6
8
  <%= f.text_field :preferred_background_color, class: 'form-control color-picker', placeholder: '#FFFFFF', value: @object.preferred_background_color %>
@@ -1,6 +1,8 @@
1
1
  <!-- insert_after "erb[loud]:contains('field_container :permalink')" -->
2
2
 
3
+ <%# Sections don't use custom_redirect_url, so we hide the form here to avoid confusing admin. %>
4
+
3
5
  <%= f.field_container :custom_redirect_url do %>
4
6
  <%= f.label :custom_redirect_url, Spree.t(:custom_redirect_url) %>
5
7
  <%= f.text_field :custom_redirect_url, class: 'form-control', rows: 6 %>
6
- <% end %>
8
+ <% end if @taxon.depth == 1 %>
@@ -1,7 +1,9 @@
1
1
  <!-- insert_before "erb[loud]:contains('field_container :hide_from_nav')" -->
2
2
 
3
+ <%# Sections don't use video banner, so we hide the form here to avoid confusing admin. %>
4
+
3
5
  <%= f.field_container :hide_video_banner, class: ['custom-control', 'custom-checkbox', 'my-4'] do %>
4
6
  <%= f.check_box :hide_video_banner, class: 'custom-control-input' %>
5
7
  <%= f.label :hide_video_banner, Spree.t(:hide_video_banner), class: 'custom-control-label' %>
6
8
  <%= f.error_message_on :hide_video_banner %>
7
- <% end %>
9
+ <% end if @taxon.depth == 1 %> %>
@@ -1,5 +1,7 @@
1
1
  <!-- insert_before "erb[loud]:contains('field_container :parent_id')" -->
2
2
 
3
+ <%# Sections don't use purchasable_on, so we hide the form here to avoid confusing admin. %>
4
+
3
5
  <%= f.field_container :purchasable_on do %>
4
6
  <%= f.label :purchasable_on, Spree.t('purchasable_on'), class: 'form-label' %>
5
7
  <%= f.select :purchasable_on,
@@ -7,4 +9,4 @@
7
9
  { },
8
10
  { class: 'select2 form-control' } %>
9
11
  <%= f.error_message_on :purchasable_on, class: 'text-danger' %>
10
- <% end %>
12
+ <% end if @taxon.depth == 1 %>
@@ -1,5 +1,7 @@
1
1
  <!-- insert_before "erb[loud]:contains('field_container :parent_id')" -->
2
2
 
3
+ <%# Sections don't use show_badge_status, so we hide the form here to avoid confusing admin. %>
4
+
3
5
  <%= f.field_container :show_badge_status do %>
4
6
  <%= f.label :show_badge_status, Spree.t('show_badge_status'), class: 'form-label' %>
5
7
  <%= f.select :show_badge_status,
@@ -7,5 +9,5 @@
7
9
  { },
8
10
  { class: 'select2 form-control' } %>
9
11
  <%= f.error_message_on :show_badge_status, class: 'text-danger' %>
10
- <% end %>
12
+ <% end if @taxon.depth == 1 %>
11
13
 
@@ -1,6 +1,8 @@
1
1
  <!-- insert_after "erb[loud]:contains('field_container :permalink')" -->
2
2
 
3
- <div class="form-group date-range-filter">
3
+ <%# Beside event, from_date & to_date aren't used inside the system elsewhere. Hide them from UI to avoid confusion %>
4
+
5
+ <div class="form-group date-range-filter <%= "d-none" unless @object.event? && @object.depth == 1 %>">
4
6
  <div class="date-range-filter row">
5
7
  <div class="col-12 col-md-6 mb-3 mb-md-0">
6
8
  <%= label_tag nil, Spree.t(:from_date) %>
@@ -35,6 +37,3 @@
35
37
  </div>
36
38
  </div>
37
39
  </div>
38
-
39
-
40
-
@@ -1,2 +1,2 @@
1
1
  <!-- replace 'erb[loud]:contains("f.collection_check_boxes :spree_role_ids")' -->
2
- <%= f.collection_check_boxes :spree_role_ids, Spree::Role.non_vendor, :id, :name do |role_form| %>
2
+ <%= f.collection_check_boxes :spree_role_ids, Spree::Role.non_vendor, :id, :name do |role_form| %>
@@ -23,6 +23,9 @@
23
23
  <td class='phone_number'>
24
24
  <%= user.phone_number%>
25
25
  </td>
26
+ <td class='tenant'>
27
+ <%= user.tenant&.name || Spree::Store.default.name %>
28
+ </td>
26
29
  <td class='created_at'>
27
30
  <%= user.created_at %>
28
31
  </td>
@@ -19,6 +19,9 @@
19
19
  <th>
20
20
  <%= sort_link @search, :phone_number, I18n.t('activerecord.attributes.spree/order.phone_number'), {}, {title: 'phone_number_title'} %>
21
21
  </th>
22
+ <th>
23
+ <%= Spree.t(:tenant) %>
24
+ </th>
22
25
  <th>
23
26
  <%= sort_link @search, :created_at, Spree.t(:create_at), {}, {title: 'create_at_title'} %>
24
27
  </th>
@@ -0,0 +1,40 @@
1
+ <!-- insert_bottom "[data-hook='admin_variant_form_additional_fields']" -->
2
+
3
+ <p>Guest Information Fields</p>
4
+
5
+ <div class="form-check mb-3">
6
+ <%= f.check_box :use_product_kyc, id: "toggleGuestInfo", class: 'form-check-input mr-2', checked: (@variant.kyc == nil) %>
7
+ <label for="toggleGuestInfo" class="form-check-label">
8
+ Use Product KYC
9
+ </label>
10
+ </div>
11
+
12
+ <div id="guestInfoForm" <% if @variant.kyc == nil %>style="display: none;"<% end %>">
13
+ <small class="form-text text-muted mb-4">
14
+ <%= raw I18n.t('kyc.variant_note') %>
15
+ </small>
16
+ <div class="row row-cols-2 row-cols-md-3">
17
+ <% @variant.class::BIT_FIELDS.each do |key, value| %>
18
+ <div class="col">
19
+ <div class="form-check form-check-inline">
20
+ <%= f.field_container key do %>
21
+ <%= f.check_box key, class: 'form-check-input', checked: @variant.kyc_value_enabled?(value) %>
22
+ <%= f.label key, key.to_s.sub(/^guest_/, '').titleize, class: 'form-check-label' %>
23
+ <%= f.error_message_on key, class: 'text-danger' %>
24
+ <% end %>
25
+ </div>
26
+ </div>
27
+ <% end %>
28
+ </div>
29
+ </div>
30
+
31
+ <script>
32
+ document.addEventListener("turbo:load", function () {
33
+ const checkbox = document.getElementById("toggleGuestInfo");
34
+ const guestForm = document.getElementById("guestInfoForm");
35
+
36
+ checkbox.addEventListener("change", function () {
37
+ guestForm.style.display = this.checked ? "none" : "block";
38
+ });
39
+ });
40
+ </script>
@@ -2,12 +2,15 @@
2
2
 
3
3
  <% unless f.object.accommodation? %>
4
4
  <div class="m-3 mb-0">
5
- <% valid_duration = f.object.start_date_time.present? && f.object.end_date_time.present? %>
5
+ <% from_date = f.object.start_date_time || f.object.event&.from_date %>
6
+ <% to_date = f.object.end_date_time || f.object.event&.to_date %>
7
+
8
+ <% valid_duration = from_date.present? && to_date.present? %>
6
9
  <% alert_class = valid_duration ? 'alert-success' : 'alert-warning' %>
7
10
 
8
11
  <div class="alert <%= alert_class %> mb-0">
9
12
  <%= svg_icon(name: valid_duration ? "check2-circle.svg" : 'cancel.svg', width: '16', height: '16') %>
10
- This variant have start date: <strong><%= pretty_time(f.object.start_date_time).presence || 'N/A' %></strong> and end date: <strong><%= pretty_time(f.object.end_date_time).presence || 'N/A' %></strong>
13
+ This variant have start date: <strong><%= pretty_time(from_date).presence || 'N/A' %></strong> and end date: <strong><%= pretty_time(to_date).presence || 'N/A' %></strong>
11
14
  </div>
12
15
 
13
16
  <div class="mb-1"></div>
@@ -16,7 +19,7 @@
16
19
  <% option_types = Spree::OptionType.where(name: ["start-date", "end-date", "start-time", "end-time", "reminder-in-hours", "duration-in-hours", "duration-in-minutes", "duration-in-seconds"]) %>
17
20
  <% event_link = f.object.event.present? ? edit_admin_taxonomy_taxon_url(f.object.event.taxonomy.id, f.object.event.id) : edit_admin_product_url(f.object.product) %>
18
21
 
19
- Duration can be set by either adding <%= link_to 'event section date', event_link, target: '_blank' %> or add option type such as <%= option_types.pluck(:presentation).to_sentence %>
22
+ Duration can be set by either adding <%= link_to 'event date', event_link, target: '_blank' %> or add option type such as <%= option_types.pluck(:presentation).to_sentence %>
20
23
  </small>
21
24
  </div>
22
25
  <% end %>
@@ -0,0 +1,45 @@
1
+ module SpreeCmCommissioner
2
+ module VariantAvailability
3
+ class NonPermanentStockQuery
4
+ attr_reader :variant, :except_line_item_id, :error_message
5
+
6
+ def initialize(variant:, except_line_item_id: nil)
7
+ @variant = variant
8
+ @except_line_item_id = except_line_item_id
9
+ @error_message = nil
10
+ end
11
+
12
+ def available?(quantity)
13
+ reserve_variants =
14
+ Spree::LineItem
15
+ .complete
16
+ .select('(SUM(DISTINCT spree_stock_items.count_on_hand) - SUM(spree_line_items.quantity)) AS available_quantity')
17
+ .joins(variant: :stock_items)
18
+ .where(variant_id: variant.id)
19
+ .where.not(id: except_line_item_id)
20
+ .group(:variant_id)
21
+
22
+ # there is a case when variant does not have any purchaces yet, it will return empty & always available true.
23
+ # in this case, we check with stock items directly.
24
+
25
+ available_quantity = if reserve_variants.any?
26
+ reserve_variants.sum(&:available_quantity)
27
+ else
28
+ variant.stock_items.sum(:count_on_hand)
29
+ end
30
+ if available_quantity >= quantity
31
+ true
32
+ elsif available_quantity == 1
33
+ @error_message = I18n.t('variant_availability.item_available_instock')
34
+ false
35
+ elsif available_quantity <= 0
36
+ @error_message = I18n.t('variant_availability.items_out_of_stock')
37
+ false
38
+ else
39
+ @error_message = I18n.t('variant_availability.items_available_instock', available_quantity: available_quantity)
40
+ false
41
+ end
42
+ end
43
+ end
44
+ end
45
+ end
@@ -0,0 +1,55 @@
1
+ module SpreeCmCommissioner
2
+ module VariantAvailability
3
+ class PermanentStockQuery
4
+ attr_reader :variant, :from_date, :to_date, :except_line_item_id
5
+
6
+ def initialize(variant:, from_date:, to_date:, except_line_item_id: nil)
7
+ @variant = variant
8
+ @from_date = from_date
9
+ @to_date = to_date
10
+ @except_line_item_id = except_line_item_id
11
+ end
12
+
13
+ def available?(quantity)
14
+ (from_date..from_date).all? do |booking_date|
15
+ booked_date_quantity[booking_date].nil? ||
16
+ (booked_date_quantity[booking_date] - quantity) >= 0
17
+ end
18
+ end
19
+
20
+ def booked_date_quantity
21
+ dates_sql = ApplicationRecord.sanitize_sql_array(
22
+ [
23
+ 'SELECT date FROM generate_series(?::date, ?::date, ?) AS date',
24
+ from_date,
25
+ to_date,
26
+ date_interval
27
+ ]
28
+ )
29
+
30
+ @booked_variants ||=
31
+ Spree::LineItem
32
+ .complete
33
+ .select(
34
+ '(SUM(DISTINCT spree_stock_items.count_on_hand) - SUM(spree_line_items.quantity)) AS available_quantity',
35
+ 'dates.date AS reservation_date'
36
+ )
37
+ .joins(variant: :stock_items)
38
+ .where(variant_id: variant.id)
39
+ .where.not(id: except_line_item_id)
40
+ .joins("INNER JOIN (#{dates_sql}) dates ON dates.date >= spree_line_items.from_date AND dates.date < spree_line_items.to_date")
41
+ .group(:variant_id, :reservation_date)
42
+ .order(:reservation_date)
43
+ .each_with_object({}) do |record, hash|
44
+ hash[record.reservation_date.to_date] = record.available_quantity
45
+ end
46
+ end
47
+
48
+ # override this for product that have
49
+ # different date time interval than 1 day.
50
+ def date_interval
51
+ '1 day'
52
+ end
53
+ end
54
+ end
55
+ end
@@ -3,9 +3,6 @@ module SpreeCmCommissioner
3
3
  params do
4
4
  required(:from_date).value(:date)
5
5
  required(:to_date).value(:date)
6
- required(:province_id).value(:integer)
7
- required(:adult).value(:integer)
8
- required(:children).value(:integer)
9
6
  end
10
7
 
11
8
  rule(:from_date, :to_date) do
@@ -11,7 +11,7 @@ module SpreeCmCommissioner
11
11
  end
12
12
 
13
13
  def error_message
14
- errors.map { |error| "#{error.path.join(', ')}: #{error.text}" }.to_sentence
14
+ errors.map(&:text).join(', ')
15
15
  end
16
16
 
17
17
  private
@@ -9,12 +9,10 @@ module Spree
9
9
 
10
10
  attributes :total_inventory, :service_availabilities
11
11
 
12
- # Deprecated
13
12
  attribute :total_booking do |vendor|
14
13
  vendor.respond_to?(:total_booking) ? vendor.total_booking : 0
15
14
  end
16
15
 
17
- # Deprecated
18
16
  attribute :remaining do |vendor|
19
17
  vendor.respond_to?(:remaining) ? vendor.remaining : vendor.total_inventory
20
18
  end
@@ -25,6 +25,8 @@ module Spree
25
25
  end
26
26
 
27
27
  base.cache_options store: nil
28
+
29
+ base.attribute :event_url, &:event_url
28
30
  end
29
31
  end
30
32
  end
@@ -0,0 +1,9 @@
1
+ module Spree
2
+ module V2
3
+ module Tenant
4
+ class WaitingRoomSessionSerializer < BaseSerializer
5
+ attributes :jwt_token, :expired_at
6
+ end
7
+ end
8
+ end
9
+ end
@@ -25,9 +25,11 @@ module SpreeCmCommissioner
25
25
  end
26
26
 
27
27
  def self.auth_context(params)
28
+ tenant_id = find_oauth_application(params)&.tenant_id
29
+
28
30
  case flow_type(params)
29
31
  when 'login_auth'
30
- options = { login: params[:username], password: params[:password] }
32
+ options = { login: params[:username], password: params[:password], tenant_id: tenant_id }
31
33
  SpreeCmCommissioner::UserPasswordAuthenticator.call(options)
32
34
  when 'social_auth'
33
35
  options = { id_token: params[:id_token] }
@@ -0,0 +1,62 @@
1
+ module SpreeCmCommissioner
2
+ class UserRolesAssigner
3
+ attr_reader :user_id, :email, :role_ids, :vendor_id, :user
4
+
5
+ def self.create(user_id: nil, email: nil, role_ids: nil, vendor_id: nil)
6
+ new(user_id: user_id, email: email, role_ids: role_ids, vendor_id: vendor_id).create
7
+ end
8
+
9
+ def self.update(user_id: nil, email: nil, role_ids: nil, vendor_id: nil)
10
+ new(user_id: user_id, email: email, role_ids: role_ids, vendor_id: vendor_id).update
11
+ end
12
+
13
+ def initialize(user_id: nil, email: nil, role_ids: nil, vendor_id: nil)
14
+ @user_id = user_id
15
+ @email = email
16
+ @role_ids = role_ids
17
+ @vendor_id = vendor_id
18
+ @user = find_user
19
+ end
20
+
21
+ def create
22
+ return { success: false, message: I18n.t('user_roles_assigner.user_not_found') } unless user
23
+ return { success: false, message: I18n.t('user_roles_assigner.user_already_assigned') } if user.vendors.exists?(id: vendor_id)
24
+ return { success: false, message: I18n.t('user_roles_assigner.roles_required') } if role_ids.blank?
25
+
26
+ create_roles
27
+ { success: true }
28
+ end
29
+
30
+ def update
31
+ return { success: false, message: I18n.t('user_roles_assigner.roles_required') } if role_ids.blank?
32
+
33
+ update_roles
34
+ { success: true }
35
+ end
36
+
37
+ private
38
+
39
+ def find_user
40
+ return Spree::User.find_by(id: user_id) if user_id.present?
41
+ return Spree::User.find_by(email: email) if email.present?
42
+
43
+ nil
44
+ end
45
+
46
+ def users_role
47
+ roles = Spree::Role.filter_by_vendor(vendor_id)
48
+ user.role_users.where(role: roles)
49
+ end
50
+
51
+ def create_roles
52
+ vendor = Spree::Vendor.find_by(id: vendor_id)
53
+ user.vendors << vendor unless user.vendors.include?(vendor)
54
+ role_ids.each { |role_id| users_role.find_or_create_by(role_id: role_id) }
55
+ end
56
+
57
+ def update_roles
58
+ users_role.where.not(role_id: role_ids).destroy_all
59
+ role_ids.each { |role_id| users_role.find_or_create_by(role_id: role_id) }
60
+ end
61
+ end
62
+ end