spree_cm_commissioner 2.1.9.pre.pre2 → 2.2.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 (71) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +3 -0
  3. data/.tool-versions +1 -1
  4. data/Gemfile.lock +2 -1
  5. data/app/controllers/spree/admin/users_controller_decorator.rb +2 -0
  6. data/app/controllers/spree/api/v2/storefront/account/guest_dynamic_fields_controller.rb +3 -5
  7. data/app/controllers/spree/api/v2/storefront/account/mark_guest_info_complete_controller.rb +3 -8
  8. data/app/controllers/spree/api/v2/storefront/guests_controller.rb +37 -18
  9. data/app/interactors/spree_cm_commissioner/account_recover.rb +2 -2
  10. data/app/interactors/spree_cm_commissioner/guest_dynamic_field_notification_sender.rb +11 -0
  11. data/app/interactors/spree_cm_commissioner/notification_reader.rb +3 -1
  12. data/app/interactors/spree_cm_commissioner/stock/inventory_item_resetter.rb +1 -1
  13. data/app/interactors/spree_cm_commissioner/team_member_adder.rb +37 -0
  14. data/app/interactors/spree_cm_commissioner/team_member_creator.rb +58 -0
  15. data/app/interactors/spree_cm_commissioner/transit/draft_order_creator.rb +4 -0
  16. data/app/interactors/spree_cm_commissioner/trip_clone_creator.rb +2 -21
  17. data/app/interactors/spree_cm_commissioner/user_password_authenticator.rb +2 -2
  18. data/app/interactors/spree_cm_commissioner/user_registration_with_fb_token.rb +1 -1
  19. data/app/interactors/spree_cm_commissioner/user_registration_with_id_token.rb +1 -1
  20. data/app/interactors/spree_cm_commissioner/user_telegram_web_app_authenticator.rb +2 -0
  21. data/app/interactors/spree_cm_commissioner/user_vendor_assigner.rb +39 -0
  22. data/app/interactors/spree_cm_commissioner/vattanac_bank_initiator.rb +2 -0
  23. data/app/jobs/spree_cm_commissioner/transit/route_fulfilled_order_count_incrementer_job.rb +10 -0
  24. data/app/jobs/spree_cm_commissioner/transit/route_order_count_incrementer_job.rb +10 -0
  25. data/app/jobs/spree_cm_commissioner/transit/route_previous_trip_count_decrementer_job.rb +13 -0
  26. data/app/jobs/spree_cm_commissioner/transit/route_trip_count_decrementer_job.rb +10 -0
  27. data/app/jobs/spree_cm_commissioner/transit/route_trip_count_incrementer_job.rb +10 -0
  28. data/app/models/concerns/spree_cm_commissioner/order_state_machine.rb +12 -0
  29. data/app/models/concerns/spree_cm_commissioner/route_order_countable.rb +30 -0
  30. data/app/models/concerns/spree_cm_commissioner/route_trip_count_callbacks.rb +48 -0
  31. data/app/models/concerns/spree_cm_commissioner/service_type.rb +35 -0
  32. data/app/models/concerns/spree_cm_commissioner/store_metadata.rb +14 -3
  33. data/app/models/spree_cm_commissioner/guest.rb +3 -0
  34. data/app/models/spree_cm_commissioner/inventory_item.rb +1 -1
  35. data/app/models/spree_cm_commissioner/notification.rb +5 -1
  36. data/app/models/spree_cm_commissioner/order_decorator.rb +5 -0
  37. data/app/models/spree_cm_commissioner/redis_stock/cached_inventory_items_builder.rb +2 -2
  38. data/app/models/spree_cm_commissioner/redis_stock/inventory_updater.rb +2 -2
  39. data/app/models/spree_cm_commissioner/route.rb +12 -0
  40. data/app/models/spree_cm_commissioner/trip.rb +40 -0
  41. data/app/models/spree_cm_commissioner/user_decorator.rb +11 -0
  42. data/app/models/spree_cm_commissioner/vendor_decorator.rb +1 -0
  43. data/app/models/spree_cm_commissioner/vendor_route.rb +9 -0
  44. data/app/notifications/spree_cm_commissioner/guest_dynamic_field_notification.rb +24 -0
  45. data/app/overrides/spree/admin/users/_form/user_add_on.html.erb.deface +27 -0
  46. data/app/services/spree_cm_commissioner/transit/base_route_order_metrics_updater.rb +62 -0
  47. data/app/services/spree_cm_commissioner/transit/route_fulfilled_order_count_incrementer_service.rb +18 -0
  48. data/app/services/spree_cm_commissioner/transit/route_order_count_incrementer_service.rb +19 -0
  49. data/app/services/spree_cm_commissioner/transit/route_previous_trip_count_decrementer_service.rb +30 -0
  50. data/app/services/spree_cm_commissioner/transit/route_trip_count_decrementer_service.rb +33 -0
  51. data/app/services/spree_cm_commissioner/transit/route_trip_count_incrementer_service.rb +33 -0
  52. data/app/services/spree_cm_commissioner/users/incomplete_guest_checker_service.rb +34 -0
  53. data/app/views/blazer/queries/embed/_content.html.erb +4 -2
  54. data/config/initializers/spree_permitted_attributes.rb +1 -0
  55. data/config/locales/en.yml +20 -0
  56. data/config/locales/km.yml +20 -0
  57. data/db/migrate/20250911042815_create_cm_routes.rb +16 -0
  58. data/db/migrate/20250911045649_create_cm_vendor_routes.rb +12 -0
  59. data/db/migrate/20251008064344_add_route_id_to_cm_trips.rb +7 -0
  60. data/db/migrate/20251009033331_add_registered_by_to_spree_users.rb +6 -0
  61. data/db/migrate/20251009073040_add_lock_version_to_cm_routes.rb +5 -0
  62. data/db/migrate/20251009073929_add_lock_version_to_cm_vendor_routes.rb +5 -0
  63. data/lib/spree_cm_commissioner/engine.rb +4 -0
  64. data/lib/spree_cm_commissioner/test_helper/factories/route_factory.rb +10 -0
  65. data/lib/spree_cm_commissioner/test_helper/factories/trip_factory.rb +1 -0
  66. data/lib/spree_cm_commissioner/test_helper/factories/vendor_route_factory.rb +7 -0
  67. data/lib/spree_cm_commissioner/version.rb +1 -1
  68. data/lib/spree_cm_commissioner.rb +8 -14
  69. data/lib/tasks/backfill_confirmed_at.rake +21 -0
  70. data/spree_cm_commissioner.gemspec +1 -0
  71. metadata +50 -4
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: ab72ce6c6d29366df93dc2df11c7ee0962ee514380213823f16de19dd2b245b6
4
- data.tar.gz: 37cb94bd2a38f5001204a5d5c50af10749269112738f6e5d213fd5c302aaab60
3
+ metadata.gz: 03b6ceb6f610d931285c1edb4bf699bb82098cb042c7a675a7cf4ca97d20c2d1
4
+ data.tar.gz: 7e4a0b523320e28f397556545a5c912a3efc9418c993a3dcc06c403a6d1b64b0
5
5
  SHA512:
6
- metadata.gz: fabf0f79539543ce41d8fdca27990d5d0b5ccff8af6ae39539c3aefb9d0b474fb0835b60f475db2d89f103b42626560d9d602936352a8b3f9c22de9743094fba
7
- data.tar.gz: efa133c49dff021c1abf6e0a56aca72a1998e3e5d2fd30da27649675f348533d19ccf43d0fe8cb56fdf682f7e01ec40b291efb6897df3e2863f850f464a7453c
6
+ metadata.gz: 0a01870d81ac4d66b2c931b49552891ca2167257d0ae83956b7748af60d6c17b17bacd6d444a32b87b12d1db722451e3fc3c374eb108f45dc454ab3648f57b7a
7
+ data.tar.gz: 11efa1fc32c865d667bd37e2753b79d6971b923c4fc0ffa9e463c7b17a0e3ccf5cfb211b84aa97fe4d8554ff4542ec1cc36483c45ca578ef57e5ba7e863c73a8
data/.gitignore CHANGED
@@ -30,3 +30,6 @@ node_modules
30
30
  !.vscode/.settings.json
31
31
  .env
32
32
  vendor/bundle/
33
+
34
+ # Cursor
35
+ .cursor
data/.tool-versions CHANGED
@@ -1 +1 @@
1
- ruby 3.2.0
1
+ ruby 3.3.5
data/Gemfile.lock CHANGED
@@ -34,7 +34,7 @@ GIT
34
34
  PATH
35
35
  remote: .
36
36
  specs:
37
- spree_cm_commissioner (2.1.9.pre.pre2)
37
+ spree_cm_commissioner (2.2.0)
38
38
  activerecord-multi-tenant
39
39
  activerecord_json_validator (~> 2.1, >= 2.1.3)
40
40
  aws-sdk-cloudfront
@@ -69,6 +69,7 @@ PATH
69
69
  spree_api_v1 (>= 4.5.0)
70
70
  spree_auth_devise (>= 4.5.0)
71
71
  spree_backend (>= 4.5.0)
72
+ spree_emails (>= 4.5.0)
72
73
  spree_extension
73
74
  spree_multi_vendor (>= 2.4.1)
74
75
  spree_vpago
@@ -9,6 +9,8 @@ module Spree
9
9
  def create
10
10
  @user = Spree.user_class.new(user_params)
11
11
  @user.assigned_roles = Spree::Role.where(id: params.dig(:user, :spree_role_ids)).pluck(:name) if params.dig(:user, :spree_role_ids)
12
+ @user.confirmed_at = Time.zone.now
13
+ @user.registered_by = :system_registered
12
14
 
13
15
  if @user.save
14
16
  flash[:success] = flash_message_for(@user, :successfully_created)
@@ -22,7 +22,7 @@ module Spree
22
22
 
23
23
  ActiveRecord::Base.transaction do
24
24
  fields.each do |field|
25
- process_dynamic_field(field, phase)
25
+ process_dynamic_field(field)
26
26
  end
27
27
 
28
28
  @guest.save_and_move_to_next_stage
@@ -52,11 +52,9 @@ module Spree
52
52
  end
53
53
  end
54
54
 
55
- def process_dynamic_field(field, phase)
55
+ def process_dynamic_field(field)
56
56
  dynamic_field = SpreeCmCommissioner::DynamicField.find(field[:id])
57
- guest_field = @guest.guest_dynamic_fields.find_or_initialize_by(dynamic_field_id: dynamic_field.id)
58
-
59
- return if guest_field.new_record? && dynamic_field.data_fill_stage != phase
57
+ guest_field = @guest.guest_dynamic_fields.build(dynamic_field_id: dynamic_field.id)
60
58
 
61
59
  if dynamic_field.requires_dynamic_field_options?
62
60
  option = dynamic_field.dynamic_field_options.find_by(value: field[:value])
@@ -7,18 +7,13 @@ module Spree
7
7
  before_action :require_spree_current_user
8
8
 
9
9
  def update
10
- update_user_metadata
11
- render_success_response
10
+ SpreeCmCommissioner::Users::IncompleteGuestCheckerService.new(spree_current_user).call
11
+ render_success
12
12
  end
13
13
 
14
14
  private
15
15
 
16
- def update_user_metadata
17
- spree_current_user.public_metadata['has_incomplete_guest_info'] = false
18
- spree_current_user.save!
19
- end
20
-
21
- def render_success_response
16
+ def render_success
22
17
  render_serialized_payload(200) do
23
18
  {
24
19
  success: true,
@@ -5,8 +5,8 @@ module Spree
5
5
  class GuestsController < ::Spree::Api::V2::ResourceController
6
6
  include OrderConcern
7
7
 
8
- # raise 404 when no order
9
- before_action :ensure_order
8
+ # raise 404 when no order, except for show
9
+ before_action :ensure_order, except: [:show]
10
10
 
11
11
  # override
12
12
  def model_class
@@ -23,6 +23,11 @@ module Spree
23
23
  SpreeCmCommissioner::V2::Storefront::GuestSerializer
24
24
  end
25
25
 
26
+ def show
27
+ @guest = SpreeCmCommissioner::Guest.find(params[:id])
28
+ render_serialized_payload { serialize_resource(@guest) }
29
+ end
30
+
26
31
  def create
27
32
  spree_authorize! :update, spree_current_order, order_token
28
33
 
@@ -90,22 +95,15 @@ module Spree
90
95
  end
91
96
 
92
97
  def process_dynamic_fields(dynamic_fields_attributes)
93
- dynamic_fields_attributes.each do |attr|
94
- if attr[:_destroy] == '1' && attr[:id].present?
95
- field = resource.guest_dynamic_fields.find_by(id: attr[:id])
96
- field&.destroy!
97
- elsif attr[:id].present?
98
- field = resource.guest_dynamic_fields.find_by(id: attr[:id])
99
- if field
100
- field.assign_attributes(attr.except(:id, :_destroy))
101
- associate_dynamic_field_option(field) if field.dynamic_field.present?
102
- field.save!
103
- end
104
- else
105
- field = resource.guest_dynamic_fields.build(attr.except(:id, :_destroy))
106
- associate_dynamic_field_option(field) if field.dynamic_field.present?
107
- field.save!
98
+ dynamic_fields_attributes.each do |raw_attributes|
99
+ attributes = raw_attributes.to_h.symbolize_keys
100
+
101
+ if ActiveModel::Type::Boolean.new.cast(attributes[:_destroy])
102
+ destroy_guest_dynamic_field(attributes[:id])
103
+ next
108
104
  end
105
+
106
+ upsert_guest_dynamic_field(attributes)
109
107
  end
110
108
  end
111
109
 
@@ -113,7 +111,28 @@ module Spree
113
111
  return unless field.dynamic_field&.requires_dynamic_field_options?
114
112
 
115
113
  option = field.dynamic_field.dynamic_field_options.find_by(id: field.value)
116
- field.dynamic_field_option = option if option
114
+ field.dynamic_field_option = option
115
+ end
116
+
117
+ def destroy_guest_dynamic_field(id)
118
+ return if id.blank?
119
+
120
+ resource.guest_dynamic_fields.find(id).destroy!
121
+ end
122
+
123
+ def upsert_guest_dynamic_field(attributes)
124
+ field = find_or_initialize_guest_dynamic_field(attributes)
125
+ field.assign_attributes(attributes.except(:id, :_destroy))
126
+ associate_dynamic_field_option(field) if field.dynamic_field.present?
127
+ field.save!
128
+ end
129
+
130
+ def find_or_initialize_guest_dynamic_field(attributes)
131
+ if attributes[:id].present?
132
+ resource.guest_dynamic_fields.find(attributes[:id])
133
+ else
134
+ resource.guest_dynamic_fields.find_or_initialize_by(dynamic_field_id: attributes[:dynamic_field_id])
135
+ end
117
136
  end
118
137
  end
119
138
  end
@@ -8,8 +8,8 @@ module SpreeCmCommissioner
8
8
  end
9
9
 
10
10
  def recover_user
11
- updated = context.user.update(account_deletion_at: nil, account_restored_at: Time.current)
12
- context.fail!(message: user.errors.full_messages.to_sentence) unless updated
11
+ updated = context.user.update(account_deletion_at: nil, account_restored_at: Time.current, confirmed_at: Time.current)
12
+ context.fail!(message: context.user.errors.full_messages.to_sentence) unless updated
13
13
  end
14
14
 
15
15
  def validate_user
@@ -0,0 +1,11 @@
1
+ module SpreeCmCommissioner
2
+ class GuestDynamicFieldNotificationSender < BaseInteractor
3
+ def call
4
+ notification = SpreeCmCommissioner::GuestDynamicFieldNotification.with(
5
+ guest: context.guest
6
+ ).deliver_later(context.guest.user)
7
+
8
+ context.notification = notification
9
+ end
10
+ end
11
+ end
@@ -15,7 +15,9 @@ module SpreeCmCommissioner
15
15
  end
16
16
 
17
17
  def read_all_user_unread_notifications
18
- user.notifications.unread_notifications.mark_as_read!
18
+ # mark all unread notifications accept type 'guest_dynamic_field_notification'
19
+ # due to it requires user to fill info before mark as read
20
+ user.notifications.markable_notifications.mark_as_read!
19
21
  end
20
22
  end
21
23
  end
@@ -35,7 +35,7 @@ module SpreeCmCommissioner
35
35
  end
36
36
 
37
37
  def clear_inventory_cache
38
- SpreeCmCommissioner.inventory_redis_pool.with do |redis|
38
+ SpreeCmCommissioner.redis_pool.with do |redis|
39
39
  redis.del(inventory_item.redis_key)
40
40
  end
41
41
  end
@@ -0,0 +1,37 @@
1
+ module SpreeCmCommissioner
2
+ class TeamMemberAdder < BaseInteractor
3
+ delegate :user_id, :role_ids, :branch_ids, :vendor, :inviter, :service_type, to: :context
4
+
5
+ def call
6
+ find_user
7
+ assign_user_to_vendor
8
+ set_success_message
9
+ end
10
+
11
+ private
12
+
13
+ def find_user
14
+ context.user = Spree::User.find_by(id: user_id)
15
+
16
+ return if context.user
17
+
18
+ context.fail!(
19
+ message: I18n.t('views.manage_team.add.user_not_found')
20
+ )
21
+ end
22
+
23
+ def assign_user_to_vendor
24
+ SpreeCmCommissioner::UserVendorAssigner.call(
25
+ user: context.user,
26
+ role_ids: role_ids,
27
+ branch_ids: branch_ids,
28
+ vendor: vendor,
29
+ service_type: service_type
30
+ )
31
+ end
32
+
33
+ def set_success_message
34
+ context.success_message = I18n.t('views.manage_team.add.user_assigned_successfully')
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,58 @@
1
+ module SpreeCmCommissioner
2
+ class TeamMemberCreator < BaseInteractor
3
+ delegate :email, :first_name, :last_name, :password, :role_ids, :branch_ids, :vendor, :inviter, :service_type, to: :context
4
+
5
+ def call
6
+ create_user
7
+ assign_user_to_vendor
8
+ set_success_message
9
+ end
10
+
11
+ private
12
+
13
+ def create_user
14
+ existing_user = Spree::User.find_by(email: normalized_email)
15
+ if existing_user.present?
16
+ context.fail!(
17
+ message: I18n.t('views.manage_team.create.user_already_exists'),
18
+ user: existing_user
19
+ )
20
+ end
21
+
22
+ context.user = Spree::User.new(
23
+ email: normalized_email,
24
+ first_name: first_name,
25
+ last_name: last_name,
26
+ password: password,
27
+ password_confirmation: password,
28
+ registered_by: :system_registered,
29
+ primary_service_type: service_type
30
+ )
31
+
32
+ return if context.user.save
33
+
34
+ context.fail!(
35
+ message: context.user.errors.full_messages.to_sentence,
36
+ user: context.user
37
+ )
38
+ end
39
+
40
+ def assign_user_to_vendor
41
+ SpreeCmCommissioner::UserVendorAssigner.call(
42
+ user: context.user,
43
+ role_ids: role_ids,
44
+ branch_ids: branch_ids,
45
+ vendor: vendor,
46
+ service_type: service_type
47
+ )
48
+ end
49
+
50
+ def set_success_message
51
+ context.success_message = I18n.t('views.manage_team.create.success')
52
+ end
53
+
54
+ def normalized_email
55
+ @normalized_email ||= email&.strip&.downcase
56
+ end
57
+ end
58
+ end
@@ -40,6 +40,10 @@ module SpreeCmCommissioner
40
40
  inbound_qty = inbound_line_items.sum(&:quantity)
41
41
 
42
42
  raise StandardError, 'Outbound & inbound line item quantities do not match' if inbound_line_items.any? && outbound_qty != inbound_qty
43
+
44
+ trip_ids = (outbound_legs.map(&:trip_id) + inbound_legs.map(&:trip_id)).uniq
45
+ order.preload_trip_ids = trip_ids
46
+
43
47
  raise StandardError, order.errors.full_messages.to_sentence unless order.save
44
48
 
45
49
  order.update_with_updater!
@@ -1,4 +1,3 @@
1
- # TODO: fix this to remove variant block related code as we already drop the table.
2
1
  module SpreeCmCommissioner
3
2
  class TripCloneCreator < BaseInteractor
4
3
  delegate :vendor, :params, :original_trip, :places, :current_store, :current_vendor, to: :context
@@ -26,6 +25,7 @@ module SpreeCmCommissioner
26
25
  return nil unless original_product
27
26
 
28
27
  cloned_product = duplicate_product(original_product)
28
+ cloned_product.short_name = "Copy of #{original_product.short_name}"
29
29
  assign_vendor_attributes(cloned_product)
30
30
 
31
31
  cloned_product.save!
@@ -110,35 +110,16 @@ module SpreeCmCommissioner
110
110
  # Clone Variants
111
111
  def clone_variants(original_trip, cloned_trip)
112
112
  original_trip.product.variants.each do |variant|
113
- new_variant = cloned_trip.product.variants.create!(
113
+ cloned_trip.product.variants.create!(
114
114
  price: variant.price,
115
115
  option_value_ids: variant.option_value_ids,
116
116
  sku: ''
117
117
  )
118
- clone_variant_blocks(variant, new_variant)
119
118
  end
120
119
  rescue StandardError => e
121
120
  raise StandardError, "Failed to clone variants: #{e.message}"
122
121
  end
123
122
 
124
- # clone variant_block (seat)
125
- def clone_variant_blocks(origin_variant, new_variant)
126
- block_data = {
127
- block_ids: origin_variant.block_ids || []
128
- }
129
-
130
- context = SpreeCmCommissioner::VariantBlockUpdater.call(
131
- variant: new_variant,
132
- current_store: current_store,
133
- data: block_data,
134
- stock_location: current_vendor.stock_locations.first
135
- )
136
-
137
- raise StandardError, "Failed to update variant blocks: #{context.message}" unless context.success?
138
- rescue StandardError => e
139
- raise StandardError, "Failed to clone variant blocks: #{e.message}"
140
- end
141
-
142
123
  # Helpers
143
124
  def departure_time_from_stops(trip)
144
125
  first_departure = trip.trip_stops.first&.departure_time
@@ -6,7 +6,7 @@ module SpreeCmCommissioner
6
6
  context.user = Spree.user_class.find_user_by_login(login, tenant_id)
7
7
  context.fail!(message: I18n.t('authenticator.incorrect_login')) if context.user.nil?
8
8
 
9
- if spree_confirmable? && active_for_authentication? && !validate_password(user)
9
+ if spree_confirmable? && active_for_authentication? && !validate_password(context.user)
10
10
  context.fail!(message: I18n.t('authenticator.incorrect_password'))
11
11
  end
12
12
 
@@ -25,7 +25,7 @@ module SpreeCmCommissioner
25
25
  end
26
26
 
27
27
  def active_for_authentication?
28
- user.active_for_authentication?
28
+ context.user.active_for_authentication?
29
29
  end
30
30
  end
31
31
  end
@@ -24,7 +24,7 @@ module SpreeCmCommissioner
24
24
  # Flag as social network registration to skip presence validation for email, login, phone_number, and user_identity_providers
25
25
  # Other validations (uniqueness, format) still run if present
26
26
  user.registering_via_social_network = true
27
-
27
+ user.confirmed_at = Time.zone.now
28
28
  if user.save
29
29
  context.user = user
30
30
  else
@@ -21,7 +21,7 @@ module SpreeCmCommissioner
21
21
  tenant_id: context.tenant_id,
22
22
  **name_attributes(name)
23
23
  )
24
-
24
+ user.confirmed_at = Time.zone.now
25
25
  if user.save(validate: false)
26
26
  context.user = user
27
27
  else
@@ -25,6 +25,8 @@ module SpreeCmCommissioner
25
25
  u.user_identity_providers = [identity]
26
26
  end
27
27
 
28
+ user.confirmed_at = Time.zone.now
29
+
28
30
  user.save!
29
31
  user
30
32
  else
@@ -0,0 +1,39 @@
1
+ module SpreeCmCommissioner
2
+ class UserVendorAssigner < BaseInteractor
3
+ delegate :user, :role_ids, :branch_ids, :vendor, :service_type, to: :context
4
+
5
+ def call
6
+ assign_roles_to_user
7
+ assign_branches_to_user if should_assign_branches?
8
+ end
9
+
10
+ private
11
+
12
+ def assign_roles_to_user
13
+ SpreeCmCommissioner::UserRolesAssigner.create(
14
+ user_id: user.id,
15
+ role_ids: role_ids,
16
+ vendor_id: vendor.id
17
+ )
18
+ end
19
+
20
+ def should_assign_branches?
21
+ (service_type == 'transit' || service_type == 'intercity_taxi') && branch_ids.present?
22
+ end
23
+
24
+ def assign_branches_to_user
25
+ cleaned_ids = Array(branch_ids).compact_blank.map(&:to_i)
26
+
27
+ return unless cleaned_ids.any?
28
+
29
+ valid_branch_ids = SpreeCmCommissioner::VendorPlace
30
+ .where(id: cleaned_ids, vendor_id: vendor.id, place_type: :branch)
31
+ .pluck(:id)
32
+
33
+ return unless valid_branch_ids.any?
34
+
35
+ user.branch_ids = valid_branch_ids
36
+ user.save
37
+ end
38
+ end
39
+ end
@@ -92,6 +92,8 @@ module SpreeCmCommissioner
92
92
  user_identity_providers: [identity]
93
93
  )
94
94
 
95
+ context.user.confirmed_at = Time.zone.now
96
+
95
97
  return if context.user.save
96
98
 
97
99
  context.fail!(message: "User creation failed: #{context.user.errors.full_messages.join(', ')}")
@@ -0,0 +1,10 @@
1
+ module SpreeCmCommissioner
2
+ module Transit
3
+ class RouteFulfilledOrderCountIncrementerJob < ApplicationUniqueJob
4
+ def perform(order_id:)
5
+ order = Spree::Order.find(order_id)
6
+ SpreeCmCommissioner::Transit::RouteFulfilledOrderCountIncrementerService.call(order: order)
7
+ end
8
+ end
9
+ end
10
+ end
@@ -0,0 +1,10 @@
1
+ module SpreeCmCommissioner
2
+ module Transit
3
+ class RouteOrderCountIncrementerJob < ApplicationUniqueJob
4
+ def perform(order_id:)
5
+ order = Spree::Order.find(order_id)
6
+ SpreeCmCommissioner::Transit::RouteOrderCountIncrementerService.call(order: order)
7
+ end
8
+ end
9
+ end
10
+ end
@@ -0,0 +1,13 @@
1
+ # Perform the previous-route decrement outside the Trip transaction.
2
+
3
+ module SpreeCmCommissioner
4
+ module Transit
5
+ class RoutePreviousTripCountDecrementerJob < ApplicationUniqueJob
6
+ queue_as :default
7
+
8
+ def perform(previous_route_id:)
9
+ SpreeCmCommissioner::Transit::RoutePreviousTripCountDecrementerService.call(previous_route_id: previous_route_id)
10
+ end
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,10 @@
1
+ module SpreeCmCommissioner
2
+ module Transit
3
+ class RouteTripCountDecrementerJob < ApplicationUniqueJob
4
+ def perform(trip_id:)
5
+ trip = SpreeCmCommissioner::Trip.find(trip_id)
6
+ SpreeCmCommissioner::Transit::RouteTripCountDecrementerService.call(trip: trip)
7
+ end
8
+ end
9
+ end
10
+ end
@@ -0,0 +1,10 @@
1
+ module SpreeCmCommissioner
2
+ module Transit
3
+ class RouteTripCountIncrementerJob < ApplicationUniqueJob
4
+ def perform(trip_id:)
5
+ trip = SpreeCmCommissioner::Trip.find(trip_id)
6
+ SpreeCmCommissioner::Transit::RouteTripCountIncrementerService.call(trip: trip)
7
+ end
8
+ end
9
+ end
10
+ end
@@ -26,6 +26,8 @@ module SpreeCmCommissioner
26
26
  state_machine.after_transition to: :canceled, do: :precalculate_conversion
27
27
  state_machine.after_transition to: :canceled, do: :restock_inventory!
28
28
 
29
+ state_machine.after_transition to: :complete, do: :increment_route_fulfilled_order_count
30
+
29
31
  scope :accepted, -> { where(request_state: 'accepted') }
30
32
 
31
33
  scope :filter_by_request_state, lambda {
@@ -77,6 +79,16 @@ module SpreeCmCommissioner
77
79
  def mark_user_as_has_incomplete_guest_info
78
80
  user&.public_metadata&.[]=('has_incomplete_guest_info', true)
79
81
  user&.save!
82
+
83
+ line_items.each do |line_item|
84
+ line_item.guests.each do |guest|
85
+ guest.user ||= user
86
+ guest.save!(validate: false) if guest.changed?
87
+ guest.save_and_move_to_next_stage
88
+ # send notification only for guests that still have incomplete dynamic fields
89
+ SpreeCmCommissioner::GuestDynamicFieldNotificationSender.call(guest: guest) if guest_incomplete?(guest)
90
+ end
91
+ end
80
92
  end
81
93
 
82
94
  def has_incomplete_guest_info? # rubocop:disable Naming/PredicateName
@@ -0,0 +1,30 @@
1
+ module SpreeCmCommissioner
2
+ module RouteOrderCountable
3
+ def should_update_count?
4
+ preload_trip_ids.any?
5
+ end
6
+
7
+ def increment_route_fulfilled_order_count
8
+ return unless should_update_count?
9
+
10
+ SpreeCmCommissioner::Transit::RouteFulfilledOrderCountIncrementerJob.perform_later(order_id: id)
11
+ end
12
+
13
+ def increment_route_order_count
14
+ return unless should_update_count?
15
+
16
+ SpreeCmCommissioner::Transit::RouteOrderCountIncrementerJob.perform_later(order_id: id)
17
+ end
18
+
19
+ # Calling `.trip_ids` directly can cause many slow database queries (N+1 problem)
20
+ # every time `.should_update_trip_ids?` or `.preload_trip_ids` runs.
21
+ # To avoid this, we store a precomputed list of trip IDs in `private_metadata`.
22
+ def preload_trip_ids=(preload_trip_ids = [])
23
+ self.private_metadata = (private_metadata || {}).merge('preload_trip_ids' => preload_trip_ids)
24
+ end
25
+
26
+ def preload_trip_ids
27
+ private_metadata&.fetch('preload_trip_ids', []) || []
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,48 @@
1
+ module SpreeCmCommissioner
2
+ module RouteTripCountCallbacks
3
+ extend ActiveSupport::Concern
4
+
5
+ included do
6
+ after_create :increase_trip_count
7
+ before_destroy :decrease_trip_count
8
+
9
+ # When route context may change, enqueue an async job after commit that
10
+ # reads the previous values from `previous_changes` to decrement counts
11
+ # on the old route/vendor. This avoids keeping in-memory state and keeps
12
+ # the Trip update fast.
13
+ after_commit :enqueue_decrement_previous_route_trip_counts, on: :update
14
+
15
+ after_commit :increment_new_route_trip_counts, on: :update, if: :changed_route_context?
16
+ end
17
+
18
+ private
19
+
20
+ def decrease_trip_count
21
+ SpreeCmCommissioner::Transit::RouteTripCountDecrementerJob.perform_now(trip_id: id)
22
+ end
23
+
24
+ def increase_trip_count
25
+ SpreeCmCommissioner::Transit::RouteTripCountIncrementerJob.perform_later(trip_id: id)
26
+ end
27
+
28
+ # Read previous_changes to find prior route context and enqueue the job.
29
+ def enqueue_decrement_previous_route_trip_counts
30
+ prev_route_id = previous_changes['route_id']&.first
31
+ prev_origin_id = previous_changes['origin_place_id']&.first
32
+ prev_destination_id = previous_changes['destination_place_id']&.first
33
+
34
+ return unless prev_route_id.present? && prev_origin_id.present? && prev_destination_id.present?
35
+
36
+ SpreeCmCommissioner::Transit::RoutePreviousTripCountDecrementerJob.perform_later(
37
+ previous_route_id: prev_route_id
38
+ )
39
+ end
40
+
41
+ # Safely increment the trip_count for the trip's (new) route after update.
42
+ def increment_new_route_trip_counts
43
+ return if origin_place_id.blank? || destination_place_id.blank? || route_id.blank?
44
+
45
+ SpreeCmCommissioner::Transit::RouteTripCountIncrementerJob.perform_later(trip_id: id)
46
+ end
47
+ end
48
+ end