spree_cm_commissioner 2.1.9.pre.pre1 → 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/.env.example +29 -75
  3. data/.gitignore +3 -0
  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 +13 -34
  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: a278c011c4129a11c968c695ac8ae1a22996991ab69f8700bdafa6d9002c470e
4
- data.tar.gz: 5354f593b70e02b4956f27006a82cc06341a2630ee45306b9e8f33a4c246eb9f
3
+ metadata.gz: 03b6ceb6f610d931285c1edb4bf699bb82098cb042c7a675a7cf4ca97d20c2d1
4
+ data.tar.gz: 7e4a0b523320e28f397556545a5c912a3efc9418c993a3dcc06c403a6d1b64b0
5
5
  SHA512:
6
- metadata.gz: 140d7641c8e2542f82e64f8d8bf5758b31b5c3a6daf9232bd57bc9835c27685aba70182cf173df43fb805edc08b0968c80612d726469fd1780ad05a05d38d596
7
- data.tar.gz: fe162b2e261ace9661ea9b2e05a699617ff5da1a707dab0cad139b72c6fb0242966a9728c37e2a653213665c34767177ab8a4ee1d11c8c02ecf82088631386bb
6
+ metadata.gz: 0a01870d81ac4d66b2c931b49552891ca2167257d0ae83956b7748af60d6c17b17bacd6d444a32b87b12d1db722451e3fc3c374eb108f45dc454ab3648f57b7a
7
+ data.tar.gz: 11efa1fc32c865d667bd37e2753b79d6971b923c4fc0ffa9e463c7b17a0e3ccf5cfb211b84aa97fe4d8554ff4542ec1cc36483c45ca578ef57e5ba7e863c73a8
data/.env.example CHANGED
@@ -1,75 +1,29 @@
1
- # Rails Configuration
2
- RAILS_ENV=development # Rails environment (development, test, production)
3
- RAILS_MASTER_KEY= # Rails master key for encrypted credentials
4
- RAILS_MIN_THREADS=5 # Minimum number of threads for Puma server
5
- RAILS_MAX_THREADS=5 # Maximum number of threads for Puma server
6
- RAILS_SERVE_STATIC_FILES= # Enable static file serving (useful for production)
7
- RAILS_LOG_TO_STDOUT=enabled # Enable Rails logging to STDOUT
8
-
9
- # Server Configuration
10
- PORT=3000 # Port for the server to listen on
11
- PIDFILE=tmp/pids/server.pid # Process ID file location
12
- WEB_CONCURRENCY=2 # Number of web workers for Puma
13
- DEFAULT_URL_HOST=http://localhost:4000 # Default host URL for the application
14
- ORGANIZER_URL= # Organizer platform URL (e.g., https://organizer.bookme.plus)
15
-
16
- # Redis Configuration
17
- REDIS_URL=redis://localhost:6379/12 # Redis connection URL
18
- REDIS_POOL_SIZE=5 # Redis connection pool size
19
- REDIS_TIMEOUT=5 # Redis connection timeout in seconds
20
-
21
- # AWS Configuration
22
- AWS_REGION=ap-southeast-1 # AWS region for services
23
- AWS_ACCESS_KEY_ID= # AWS access key ID for authentication
24
- AWS_SECRET_ACCESS_KEY= # AWS secret access key for authentication
25
- AWS_BUCKET_NAME=contigoasia-production # AWS S3 bucket name for storage
26
- AWS_OUTPUT_BUCKET_NAME=output-production-cm # AWS S3 output bucket name for media processing
27
- AWS_CLUSTER_NAME=production-cm-web # AWS ECS cluster name for waiting room system
28
-
29
- # AWS CloudFront Configuration
30
- ASSETS_SYNC_CF_DIST_ID=E3DV5NICAX5NCH # CloudFront distribution ID for asset synchronization
31
- AWS_CF_MEDIA_DOMAIN=medias.bookme.plus # CloudFront media domain (e.g., medias.bookme.plus)
32
- AWS_CF_PUBLIC_KEY_ID= # CloudFront public key ID for signed URLs
33
- AWS_CF_PRIVATE_KEY= # CloudFront private key for signed URLs (PEM format)
34
-
35
- # Cache Configuration
36
- CONTENT_CACHE_MAX_AGE=180 # Maximum age for CDN content cache in seconds (default: 86400)
37
-
38
- # Email Configuration
39
- NO_REPLY_EMAIL=noreply@example.com # No-reply email address for outgoing emails
40
-
41
- # SMS Configuration
42
- SMS_SENDER_ID=SMS Info # Sender ID for SMS messages (default: 'SMS Info')
43
-
44
- # Payment Gateway Configuration
45
- VATTANAC_AES_SECRET_KEY= # Vattanac Bank AES secret key for encryption
46
- BOOKMEPLUS_PRIVATE_KEY= # BookMe+ private key for Vattanac Bank integration
47
- VATTANAC_PUBLIC_KEY= # Vattanac Bank public key for signature verification
48
-
49
- # Google Services Configuration
50
- GOOGLE_MAP_KEY=AIzaSyBt-OSnDZze6hxGjfiapSxpcyGzBo_COv8 # Google Maps API key for nearby places and location services
51
- ISSUER_ID=3388000000022336356 # Google Wallet issuer ID for digital passes
52
-
53
- # Waiting Room System Configuration
54
- WAITING_ROOM_DISABLED=no # Disable waiting room functionality (set to 'yes' to disable)
55
- WAITING_ROOM_SESSION_SIGNATURE=4234f0a3-c42f-40b5-9057-7c006793cea4 # JWT signature key for waiting room session tokens
56
- WAITING_ROOM_SESSION_EXPIRE_DURATION_IN_SECOND=180 # Session expiration duration in seconds (default: 180 / 3 minutes)
57
- WAITING_ROOM_MIN_SESSIONS_COUNT=5 # Minimum sessions count for waiting room calculation (default: 5)
58
- WAITING_ROOM_SERVERS_COUNT=2 # Number of running servers for capacity calculation (default: 2)
59
- WAITING_ROOM_MAX_THREAD_COUNT=10 # Maximum thread count per server (default: 10)
60
- WAITING_ROOM_MULTIPLIER=150 # Multiplier for waiting room capacity calculation (default: 150)
61
-
62
- # Exception Notification Configuration
63
- EXCEPTION_NOTIFY_ENABLE=yes # Enable exception notifications (set to 'yes' to enable)
64
- EXCEPTION_TELEGRAM_BOT_TOKEN=6441690414:AAFBpevRdBaRTXmalJR2vcSdPNzYoDnMEFk # Telegram bot token for exception notifications
65
- EXCEPTION_NOTIFIER_TELEGRAM_CHANNEL_ID=-1001807686211 # Telegram channel ID for exception notifications
66
-
67
- # Telegram Bot Configuration
68
- DEFAULT_TELEGRAM_BOT_API_TOKEN=6441690414:AAFBpevRdBaRTXmalJR2vcSdPNzYoDnMEFk # Default Telegram bot API token
69
- PIN_CODE_DEBUG_NOTIFIY_TELEGRAM_ENABLE=yes # Enable PIN code debug notifications via Telegram (set to 'yes' to enable)
70
-
71
- # Business Rules Configuration
72
- ACCOMMODATION_MAX_STAY_DAYS=10 # Maximum number of days allowed for accommodation stays (default: 10)
73
-
74
- # Development/Testing Configuration
75
- BUNDLE_GEMFILE= # Gemfile location for bundler
1
+ # aws
2
+ AWS_ACCESS_KEY_ID=""
3
+ AWS_BUCKET_NAME=""
4
+ AWS_REGION=""
5
+ AWS_SECRET_ACCESS_KEY=
6
+ AWS_CLUSTER_NAME="" # fetch all tasks
7
+
8
+ AWS_CF_MEDIA_DOMAIN="medias.bookme.plus"
9
+ AWS_CF_PUBLIC_KEY_ID="KKC****QFJ2"
10
+ AWS_OUTPUT_BUCKET_NAME="output-production-cm"
11
+
12
+ AWS_CF_PRIVATE_KEY="-----BEGIN PRIVATE KEY-----
13
+ -----END PRIVATE KEY-----"
14
+
15
+ # for GET request with following host, it will be cached.
16
+ CONTENT_HOST_URL=https://content.bookme.plus
17
+ CONTENT_CACHE_MAX_AGE=7200
18
+
19
+ WAITING_ROOM_SESSION_SIGNATURE="e6b2********************26e3"
20
+ WAITING_ROOM_SESSION_EXPIRE_DURATION_IN_SECOND=
21
+ WAITING_ROOM_MIN_SESSIONS_COUNT=5
22
+ WAITING_ROOM_DISABLED=no
23
+
24
+ # Vattanac Bank
25
+ VATTANAC_AES_SECRET_KEY= ""
26
+ VATTANAC_PUBLIC_KEY=""
27
+
28
+ # Organizer URL
29
+ ORGANIZER_URL=http://127.0.0.1:4000/organizer
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/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.pre1)
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