spree_cm_commissioner 2.8.3.pre.pre5 โ†’ 2.8.3.pre.pre7

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 (172) hide show
  1. checksums.yaml +4 -4
  2. data/.env.example +1 -0
  3. data/Gemfile.lock +1 -1
  4. data/app/controllers/spree/admin/guests_controller.rb +1 -1
  5. data/app/controllers/spree/admin/order_ticket_transfers_controller.rb +25 -0
  6. data/app/controllers/spree/admin/stock_managements_controller.rb +1 -1
  7. data/app/controllers/spree/admin/ticket_transfer_rules_controller.rb +47 -0
  8. data/app/controllers/spree/admin/ticket_transfers_controller.rb +61 -0
  9. data/app/controllers/spree/api/v2/storefront/account/recipients_controller.rb +32 -0
  10. data/app/controllers/spree/api/v2/storefront/ticket_transfer_acceptances_controller.rb +32 -0
  11. data/app/controllers/spree/api/v2/storefront/ticket_transfers_controller.rb +117 -0
  12. data/app/controllers/spree/api/v2/storefront/trip_search_controller.rb +6 -4
  13. data/app/controllers/spree/api/v2/tenant/episodes_controller.rb +1 -1
  14. data/app/controllers/spree/api/v2/tenant/trips_controller.rb +11 -0
  15. data/app/controllers/spree_cm_commissioner/admin/orders_controller_decorator.rb +7 -3
  16. data/app/factory/spree_cm_commissioner/event_telegram_message_factory.rb +68 -0
  17. data/app/factory/spree_cm_commissioner/order_telegram_message_factory.rb +31 -17
  18. data/app/factory/spree_cm_commissioner/telegram_message_factory.rb +6 -0
  19. data/app/helpers/spree_cm_commissioner/admin/guest_helper.rb +10 -0
  20. data/app/helpers/spree_cm_commissioner/admin/ticket_transfers_helper.rb +32 -0
  21. data/app/interactors/spree_cm_commissioner/invalidate_cache_request.rb +5 -1
  22. data/app/jobs/spree_cm_commissioner/event_creation_telegram_alert_sender_job.rb +10 -0
  23. data/app/jobs/spree_cm_commissioner/expire_ticket_transfer_job.rb +10 -0
  24. data/app/jobs/spree_cm_commissioner/invalidate_cache_request_job.rb +4 -1
  25. data/app/jobs/spree_cm_commissioner/inventory_items/bulk_adjust_quantities_job.rb +4 -2
  26. data/app/jobs/spree_cm_commissioner/inventory_items/bulk_adjust_quantities_on_hold_job.rb +4 -2
  27. data/app/jobs/spree_cm_commissioner/oauth_access_tokens/cleanup_expired_job.rb +11 -0
  28. data/app/jobs/spree_cm_commissioner/route_metrics/decrease_trip_count_job.rb +8 -2
  29. data/app/jobs/spree_cm_commissioner/route_metrics/increase_trip_count_job.rb +5 -2
  30. data/app/jobs/spree_cm_commissioner/vendor_route_metrics/decrease_trip_count_job.rb +17 -0
  31. data/app/jobs/spree_cm_commissioner/vendor_route_metrics/increase_trip_count_job.rb +14 -0
  32. data/app/models/concerns/spree_cm_commissioner/line_item_transitable.rb +8 -0
  33. data/app/models/concerns/spree_cm_commissioner/line_items_filter_scope.rb +5 -0
  34. data/app/models/concerns/spree_cm_commissioner/service_origin.rb +22 -0
  35. data/app/models/concerns/spree_cm_commissioner/store_preference.rb +1 -0
  36. data/app/models/concerns/spree_cm_commissioner/ticket_transferable.rb +15 -0
  37. data/app/models/spree_cm_commissioner/guest.rb +20 -0
  38. data/app/models/spree_cm_commissioner/inventory_item.rb +17 -2
  39. data/app/models/spree_cm_commissioner/line_item_decorator.rb +7 -0
  40. data/app/models/spree_cm_commissioner/maintenance_tasks/cache_invalidation.rb +2 -0
  41. data/app/models/spree_cm_commissioner/notification.rb +10 -1
  42. data/app/models/spree_cm_commissioner/order_decorator.rb +14 -3
  43. data/app/models/spree_cm_commissioner/payment_decorator.rb +19 -0
  44. data/app/models/spree_cm_commissioner/payment_method_decorator.rb +8 -0
  45. data/app/models/spree_cm_commissioner/product_decorator.rb +1 -1
  46. data/app/models/spree_cm_commissioner/reserved_block.rb +2 -0
  47. data/app/models/spree_cm_commissioner/route.rb +6 -1
  48. data/app/models/spree_cm_commissioner/show_episode.rb +6 -8
  49. data/app/models/spree_cm_commissioner/show_person_assignment.rb +2 -1
  50. data/app/models/spree_cm_commissioner/stock/availability_validator_decorator.rb +4 -0
  51. data/app/models/spree_cm_commissioner/stock/order_availability_checker.rb +3 -0
  52. data/app/models/spree_cm_commissioner/stock_item_decorator.rb +0 -3
  53. data/app/models/spree_cm_commissioner/taxon_decorator.rb +9 -0
  54. data/app/models/spree_cm_commissioner/ticket_transfer.rb +191 -0
  55. data/app/models/spree_cm_commissioner/ticket_transfer_rule.rb +88 -0
  56. data/app/models/spree_cm_commissioner/trip.rb +46 -2
  57. data/app/models/spree_cm_commissioner/user_decorator.rb +1 -0
  58. data/app/models/spree_cm_commissioner/variant_decorator.rb +4 -0
  59. data/app/models/spree_cm_commissioner/vendor_route_metric.rb +25 -0
  60. data/app/models/spree_cm_commissioner/voting_contestant.rb +11 -0
  61. data/app/models/spree_cm_commissioner/voting_session.rb +2 -1
  62. data/app/notifications/spree_cm_commissioner/ticket_transfer_accepted_notification.rb +48 -0
  63. data/app/notifications/spree_cm_commissioner/ticket_transfer_completed_notification.rb +48 -0
  64. data/app/notifications/spree_cm_commissioner/ticket_transfer_expired_notification.rb +46 -0
  65. data/app/notifications/spree_cm_commissioner/ticket_transfer_received_notification.rb +48 -0
  66. data/app/notifications/spree_cm_commissioner/ticket_transfer_rejected_notification.rb +48 -0
  67. data/app/overrides/spree/admin/products/_form/allow_gift_transfer.html.erb.deface +9 -0
  68. data/app/overrides/spree/admin/products/_form/allow_transfer.html.erb.deface +9 -0
  69. data/app/overrides/spree/admin/shared/_order_tabs/ticket_transfers.html.erb.deface +10 -0
  70. data/app/overrides/spree/admin/shared/sub_menu/_product/ticket_transfers_tab.html.erb.deface +3 -0
  71. data/app/overrides/spree/admin/stores/_form/store_preferences.html.erb.deface +9 -0
  72. data/app/queries/spree_cm_commissioner/multi_leg_trips_query.rb +77 -50
  73. data/app/queries/spree_cm_commissioner/recipient_searcher_query.rb +14 -0
  74. data/app/queries/spree_cm_commissioner/single_leg_trips_query.rb +67 -31
  75. data/app/queries/spree_cm_commissioner/trip_query.rb +2 -4
  76. data/app/serializers/spree/v2/storefront/line_item_serializer_decorator.rb +4 -2
  77. data/app/serializers/spree/v2/storefront/product_serializer_decorator.rb +2 -1
  78. data/app/serializers/spree/v2/tenant/campaign_serializer.rb +1 -5
  79. data/app/serializers/spree/v2/tenant/dynamic_field_serializer.rb +1 -1
  80. data/app/serializers/spree/v2/tenant/show_episode_serializer.rb +1 -0
  81. data/app/serializers/spree/v2/tenant/vote_package_serializer.rb +9 -0
  82. data/app/serializers/spree_cm_commissioner/v2/storefront/amenity_serializer.rb +3 -0
  83. data/app/serializers/spree_cm_commissioner/v2/storefront/dynamic_field_serializer.rb +1 -1
  84. data/app/serializers/spree_cm_commissioner/v2/storefront/guest_minimal_serializer.rb +30 -0
  85. data/app/serializers/spree_cm_commissioner/v2/storefront/guest_serializer.rb +3 -1
  86. data/app/serializers/spree_cm_commissioner/v2/storefront/line_item_minimal_serializer.rb +57 -0
  87. data/app/serializers/spree_cm_commissioner/v2/storefront/recipient_serializer.rb +13 -0
  88. data/app/serializers/spree_cm_commissioner/v2/storefront/reserved_block_serializer.rb +1 -1
  89. data/app/serializers/spree_cm_commissioner/v2/storefront/ticket_transfer_minimal_serializer.rb +17 -0
  90. data/app/serializers/spree_cm_commissioner/v2/storefront/ticket_transfer_serializer.rb +20 -0
  91. data/app/serializers/spree_cm_commissioner/v2/storefront/trip_query_result_serializer.rb +2 -1
  92. data/app/serializers/spree_cm_commissioner/v2/storefront/trip_result_serializer.rb +8 -3
  93. data/app/serializers/spree_cm_commissioner/v2/storefront/trip_stop_serializer.rb +1 -0
  94. data/app/serializers/spree_cm_commissioner/v2/storefront/trip_vehicle_type_serializer.rb +6 -3
  95. data/app/serializers/spree_cm_commissioner/v2/storefront/trip_vendor_serializer.rb +6 -2
  96. data/app/services/spree_cm_commissioner/advertisements/update_campaign_sorted_ads_ids.rb +33 -0
  97. data/app/services/spree_cm_commissioner/check_ins/create_bulk.rb +11 -5
  98. data/app/services/spree_cm_commissioner/checkout/complete_decorator.rb +22 -0
  99. data/app/services/spree_cm_commissioner/inventory_holds/acquire.rb +3 -0
  100. data/app/services/spree_cm_commissioner/inventory_holds/convert.rb +7 -2
  101. data/app/services/spree_cm_commissioner/inventory_holds/release.rb +7 -2
  102. data/app/services/spree_cm_commissioner/inventory_items/bulk_adjust_quantities.rb +36 -4
  103. data/app/services/spree_cm_commissioner/inventory_items/bulk_adjust_quantities_on_hold.rb +39 -4
  104. data/app/services/spree_cm_commissioner/inventory_items/bulk_generate_permanent_items.rb +4 -6
  105. data/app/services/spree_cm_commissioner/inventory_items/reset.rb +1 -1
  106. data/app/services/spree_cm_commissioner/oauth_access_tokens/cleanup_expired.rb +59 -0
  107. data/app/services/spree_cm_commissioner/pricing_models/preview.rb +15 -6
  108. data/app/services/spree_cm_commissioner/redis_stock/base.rb +2 -1
  109. data/app/services/spree_cm_commissioner/reserved_blocks/reserve.rb +2 -1
  110. data/app/services/spree_cm_commissioner/route_metrics/decrease_trip_count.rb +3 -15
  111. data/app/services/spree_cm_commissioner/route_metrics/increase_trip_count.rb +7 -15
  112. data/app/services/spree_cm_commissioner/routes/create.rb +0 -5
  113. data/app/services/spree_cm_commissioner/show_persons/contestant_assigner.rb +51 -0
  114. data/app/services/spree_cm_commissioner/telegram_alerts/event_creation.rb +35 -0
  115. data/app/services/spree_cm_commissioner/ticket_transfers/accept.rb +132 -0
  116. data/app/services/spree_cm_commissioner/ticket_transfers/cancel.rb +54 -0
  117. data/app/services/spree_cm_commissioner/ticket_transfers/complete.rb +68 -0
  118. data/app/services/spree_cm_commissioner/ticket_transfers/eligibility_checker.rb +99 -0
  119. data/app/services/spree_cm_commissioner/ticket_transfers/expire.rb +47 -0
  120. data/app/services/spree_cm_commissioner/ticket_transfers/fee_calculator.rb +57 -0
  121. data/app/services/spree_cm_commissioner/ticket_transfers/initiate.rb +98 -0
  122. data/app/services/spree_cm_commissioner/ticket_transfers/preview_rule.rb +38 -0
  123. data/app/services/spree_cm_commissioner/ticket_transfers/reject.rb +55 -0
  124. data/app/services/spree_cm_commissioner/ticket_transfers/transfer_guest.rb +29 -0
  125. data/app/services/spree_cm_commissioner/transit_order/create.rb +8 -2
  126. data/app/services/spree_cm_commissioner/trips/create_multi_leg.rb +5 -16
  127. data/app/services/spree_cm_commissioner/trips/create_single_leg.rb +0 -1
  128. data/app/services/spree_cm_commissioner/vendor_route_metrics/decrease_trip_count.rb +19 -0
  129. data/app/services/spree_cm_commissioner/vendor_route_metrics/increase_trip_count.rb +24 -0
  130. data/app/services/spree_cm_commissioner/voting_leaderboards/combined_result.rb +12 -5
  131. data/app/services/spree_cm_commissioner/voting_sessions/finalize.rb +12 -2
  132. data/app/views/spree/admin/guests/_table.html.erb +82 -87
  133. data/app/views/spree/admin/line_items/_form.html.erb +2 -3
  134. data/app/views/spree/admin/order_ticket_transfers/show.html.erb +7 -0
  135. data/app/views/spree/admin/shared/_ticket_transfer_details.html.erb +226 -0
  136. data/app/views/spree/admin/shared/_ticket_transfer_tabs.html.erb +14 -0
  137. data/app/views/spree/admin/ticket_transfer_rules/_form.html.erb +193 -0
  138. data/app/views/spree/admin/ticket_transfer_rules/edit.html.erb +11 -0
  139. data/app/views/spree/admin/ticket_transfer_rules/index.html.erb +79 -0
  140. data/app/views/spree/admin/ticket_transfer_rules/new.html.erb +11 -0
  141. data/app/views/spree/admin/ticket_transfers/_search_form.html.erb +70 -0
  142. data/app/views/spree/admin/ticket_transfers/_tabs.html.erb +14 -0
  143. data/app/views/spree/admin/ticket_transfers/index.html.erb +74 -0
  144. data/app/views/spree/admin/ticket_transfers/show.html.erb +13 -0
  145. data/app/views/spree/admin/ticket_transfers/state_changes.html.erb +47 -0
  146. data/config/initializers/spree_permitted_attributes.rb +1 -0
  147. data/config/locales/en.yml +64 -1
  148. data/config/locales/km.yml +105 -0
  149. data/config/routes.rb +20 -0
  150. data/db/migrate/20260303101440_add_allow_transfer_to_spree_products.rb +5 -0
  151. data/db/migrate/20260303101907_create_cm_ticket_transfers.rb +32 -0
  152. data/db/migrate/20260303102316_add_state_to_cm_guests.rb +7 -0
  153. data/db/migrate/20260408100000_create_cm_ticket_transfer_rules.rb +28 -0
  154. data/db/migrate/20260410120000_add_transfer_type_to_cm_ticket_transfers.rb +5 -0
  155. data/db/migrate/20260422120000_add_settlement_status_to_cm_ticket_transfers.rb +6 -0
  156. data/db/migrate/20260507000000_add_index_to_cm_ticket_transfers_completed_at.rb +5 -0
  157. data/db/migrate/20260520000000_add_number_to_cm_ticket_transfers.rb +6 -0
  158. data/db/migrate/20260605120908_add_description_to_cm_dynamic_fields.rb +5 -0
  159. data/db/migrate/20260608000000_add_allow_gift_transfer_to_spree_products.rb +5 -0
  160. data/db/migrate/20260608000000_create_cm_vendor_route_metrics.rb +25 -0
  161. data/db/migrate/20260608152251_add_gender_to_cm_reserved_blocks.rb +5 -0
  162. data/lib/spree_cm_commissioner/test_helper/factories/ticket_transfer_factory.rb +18 -0
  163. data/lib/spree_cm_commissioner/test_helper/factories/vendor_route_metric_factory.rb +10 -0
  164. data/lib/spree_cm_commissioner/trip_query_result.rb +34 -1
  165. data/lib/spree_cm_commissioner/trip_result.rb +52 -22
  166. data/lib/spree_cm_commissioner/version.rb +1 -1
  167. data/lib/tasks/backfill_route_metrics.rake +21 -0
  168. data/lib/tasks/backfill_vendor_route_metrics.rake +23 -0
  169. metadata +81 -5
  170. data/app/jobs/spree_cm_commissioner/cleanup_expired_access_tokens_job.rb +0 -9
  171. data/app/overrides/spree/layouts/admin/map_head.html.erb.deface +0 -2
  172. data/app/services/spree_cm_commissioner/cleanup_expired_access_tokens.rb +0 -52
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 145ade12d325f15a3f1963b4fd75e56093cbb182e1f3ac115d0101c0c6cad285
4
- data.tar.gz: e47239cbfa8a359ce7e0b0ed2f73e5270876f679d3047a2d88be89493683004a
3
+ metadata.gz: 6453df013c8c48c7750f2c78315a47a008d71d2d08a18a627e6f23d1cb8e0564
4
+ data.tar.gz: d28c67f073a707a50fe3f44a6bb6c9184fe3403ffc6909585137b73ae8305c39
5
5
  SHA512:
6
- metadata.gz: '06134389566fe49ae2dfe58a65919130cc57dcf43694d33af480d2930d517cfbba1ba97c7714f0b011cdb063a9a7d1611dc205fdcce1cf7f77fd4618c6d9ffd9'
7
- data.tar.gz: 60d6491f501f2baa87fb971bee6a906f3180425d9f72917462adc2d266c0f3302a44bf532c39fe9885b3b965d7c94fdae9b158d0c7bc9263879b2d2e43197e23
6
+ metadata.gz: 18c051ff735f3af699821c909d430921a245cae3ba50bf798db8a5e7d20decab213b2432855d28ad22283e9fbdc17e45326a78e14e6c6a9f42d332be94a91b45
7
+ data.tar.gz: 37cbb8aea0043989a2eac1ade7f2184aa71d8a57349c1fd9978c2147787a65aa80134be083cf48d3e9074134eccb3e91197694dca20098140340276333b71e5a
data/.env.example CHANGED
@@ -34,6 +34,7 @@ DISTANCE_SIGNING_KEY=hei********************VY
34
34
 
35
35
  # Cache durations (in seconds) for different content types
36
36
  # See: app/controllers/concerns/spree_cm_commissioner/content_cachable.rb
37
+ CACHE_AUTO_INVALIDATION_DISABLED="" # Set to yes to disable CDN cache invalidation entirely (kill switch)
37
38
  CACHE_CDN_MAX_AGE=86400 # CDN/server-side cache duration for all responses
38
39
  CACHE_STATIC_MAX_AGE=86400 # Static content (countries, provinces, seat layouts, CMS pages) - 1 day
39
40
  CACHE_SEMI_STATIC_MAX_AGE=3600 # Semi-static content (menus, homepage backgrounds, routes) - 1 hour
data/Gemfile.lock CHANGED
@@ -34,7 +34,7 @@ GIT
34
34
  PATH
35
35
  remote: .
36
36
  specs:
37
- spree_cm_commissioner (2.8.3.pre.pre5)
37
+ spree_cm_commissioner (2.8.3.pre.pre7)
38
38
  activerecord-multi-tenant
39
39
  activerecord_json_validator (~> 2.1, >= 2.1.3)
40
40
  aws-sdk-cloudfront
@@ -55,7 +55,7 @@ module Spree
55
55
  if result.success?
56
56
  flash[:success] = "Guest #{@guest.full_name} has been checked in."
57
57
  else
58
- flash[:error] = result.message.to_s.titleize
58
+ flash[:error] = result.error.value.to_s.titleize
59
59
  end
60
60
 
61
61
  redirect_to collection_url
@@ -0,0 +1,25 @@
1
+ module Spree
2
+ module Admin
3
+ class OrderTicketTransfersController < Spree::Admin::ResourceController
4
+ include Spree::Admin::OrderConcern
5
+
6
+ helper SpreeCmCommissioner::Admin::TicketTransfersHelper
7
+
8
+ before_action :load_order
9
+
10
+ def show
11
+ @ticket_transfer = model_class.includes(:order, from_guest: :event).find(params[:id])
12
+ end
13
+
14
+ private
15
+
16
+ def model_class
17
+ SpreeCmCommissioner::TicketTransfer
18
+ end
19
+
20
+ def object_name
21
+ 'spree_cm_commissioner_ticket_transfer'
22
+ end
23
+ end
24
+ end
25
+ end
@@ -92,7 +92,7 @@ module Spree
92
92
  .call
93
93
  .index_by(&:inventory_item_id)
94
94
  @reserved_stocks = Spree::LineItem
95
- .complete
95
+ .affecting_inventory
96
96
  .where(variant_id: @variants.pluck(:id))
97
97
  .group('spree_line_items.variant_id')
98
98
  .sum(:quantity)
@@ -0,0 +1,47 @@
1
+ module Spree
2
+ module Admin
3
+ class TicketTransferRulesController < Spree::Admin::ResourceController
4
+ def index
5
+ @search = model_class.ransack(params[:q])
6
+ @ticket_transfer_rules = @search.result
7
+ .order(ruleable_type: :asc, ruleable_id: :asc, currency: :asc)
8
+ .page(params[:page])
9
+ .per(25)
10
+ end
11
+
12
+ private
13
+
14
+ def model_class
15
+ SpreeCmCommissioner::TicketTransferRule
16
+ end
17
+
18
+ def object_name
19
+ 'spree_cm_commissioner_ticket_transfer_rule'
20
+ end
21
+
22
+ def collection_url
23
+ admin_ticket_transfer_rules_url
24
+ end
25
+
26
+ def location_after_save
27
+ edit_admin_ticket_transfer_rule_path(@object)
28
+ end
29
+
30
+ def permitted_resource_params
31
+ params.require(:spree_cm_commissioner_ticket_transfer_rule).permit(
32
+ :ruleable_type,
33
+ :ruleable_id,
34
+ :active,
35
+ :currency,
36
+ :fee_percentage,
37
+ :fee_flat_amount,
38
+ :cooldown_hours,
39
+ :max_transfers_per_ticket,
40
+ :event_cutoff_hours,
41
+ :min_price,
42
+ :max_price
43
+ )
44
+ end
45
+ end
46
+ end
47
+ end
@@ -0,0 +1,61 @@
1
+ module Spree
2
+ module Admin
3
+ class TicketTransfersController < Spree::Admin::ResourceController
4
+ helper SpreeCmCommissioner::Admin::TicketTransfersHelper
5
+
6
+ def index
7
+ @search = model_class.includes(:from_user, :to_user, :from_guest).ransack(params[:q])
8
+ @ticket_transfers = @search.result.order(created_at: :desc).page(params[:page]).per(15)
9
+ end
10
+
11
+ def show
12
+ @ticket_transfer = model_class.includes(:order, from_guest: [:event, { line_item: :order }]).find(params[:id])
13
+ end
14
+
15
+ def state_changes
16
+ @ticket_transfer = model_class.find(params[:id])
17
+ @state_changes = @ticket_transfer.state_changes.includes(:user).order(created_at: :desc)
18
+ end
19
+
20
+ def cancel
21
+ @ticket_transfer = model_class.find(params[:id])
22
+ result = SpreeCmCommissioner::TicketTransfers::Cancel.call(
23
+ ticket_transfer: @ticket_transfer,
24
+ acting_user: try_spree_current_user
25
+ )
26
+
27
+ if result.success?
28
+ flash[:success] = 'Ticket transfer has been cancelled' # rubocop:disable Rails/I18nLocaleTexts
29
+ else
30
+ flash[:error] = result.error.value
31
+ end
32
+ redirect_to admin_ticket_transfer_path(@ticket_transfer)
33
+ end
34
+
35
+ def update_settlement_status
36
+ @ticket_transfer = model_class.find(params[:id])
37
+ new_status = params[:settlement_status]
38
+
39
+ unless @ticket_transfer.purchase? && @ticket_transfer.completed?
40
+ flash[:error] = 'Settlement status can only be updated for completed purchase transfers' # rubocop:disable Rails/I18nLocaleTexts
41
+ redirect_to admin_ticket_transfer_path(@ticket_transfer)
42
+ return
43
+ end
44
+
45
+ if @ticket_transfer.update(settlement_status: new_status)
46
+ @ticket_transfer.log_settlement_status_changes(user_id: try_spree_current_user&.id)
47
+ flash[:success] = "Settlement status updated to #{new_status}"
48
+ else
49
+ flash[:error] = @ticket_transfer.errors.full_messages.to_sentence.presence || 'Failed to update settlement status'
50
+ end
51
+ redirect_to admin_ticket_transfer_path(@ticket_transfer)
52
+ end
53
+
54
+ private
55
+
56
+ def model_class
57
+ SpreeCmCommissioner::TicketTransfer
58
+ end
59
+ end
60
+ end
61
+ end
@@ -0,0 +1,32 @@
1
+ module Spree
2
+ module Api
3
+ module V2
4
+ module Storefront
5
+ module Account
6
+ class RecipientsController < ::Spree::Api::V2::ResourceController
7
+ before_action :require_spree_current_user
8
+
9
+ def show
10
+ identifier = params[:q].to_s.strip
11
+ return render_error_payload('Missing q parameter', 400) if identifier.blank?
12
+
13
+ user = SpreeCmCommissioner::RecipientSearcherQuery.new(identifier, MultiTenant.current_tenant_id).call
14
+
15
+ if user
16
+ render_serialized_payload { serialize_resource(user) }
17
+ else
18
+ render_error_payload('User not found', 404)
19
+ end
20
+ end
21
+
22
+ private
23
+
24
+ def resource_serializer
25
+ SpreeCmCommissioner::V2::Storefront::RecipientSerializer
26
+ end
27
+ end
28
+ end
29
+ end
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,32 @@
1
+ module Spree
2
+ module Api
3
+ module V2
4
+ module Storefront
5
+ class TicketTransferAcceptancesController < ::Spree::Api::V2::ResourceController
6
+ before_action :require_spree_current_user
7
+
8
+ def create
9
+ ticket_transfer = SpreeCmCommissioner::TicketTransfer.find_by!(token: params[:ticket_transfer_id])
10
+
11
+ result = SpreeCmCommissioner::TicketTransfers::Accept.call(
12
+ ticket_transfer: ticket_transfer,
13
+ buyer: spree_current_user
14
+ )
15
+
16
+ if result.success?
17
+ render_serialized_payload(201) { serialize_resource(result.value[:ticket_transfer]) }
18
+ else
19
+ render_error_payload(result.error)
20
+ end
21
+ end
22
+
23
+ private
24
+
25
+ def resource_serializer
26
+ SpreeCmCommissioner::V2::Storefront::TicketTransferSerializer
27
+ end
28
+ end
29
+ end
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,117 @@
1
+ module Spree
2
+ module Api
3
+ module V2
4
+ module Storefront
5
+ class TicketTransfersController < ::Spree::Api::V2::ResourceController
6
+ before_action :require_spree_current_user
7
+
8
+ def index
9
+ render_serialized_payload do
10
+ serialize_collection(paginated_collection)
11
+ end
12
+ end
13
+
14
+ def show
15
+ ticket_transfer = model_class.includes(:order, from_guest: [:event, { line_item: :product }]).find_by!(token: params[:id])
16
+ return render_error_payload(I18n.t('ticket_transfer.unauthorized_view'), 403) unless authorized_to_view?(ticket_transfer)
17
+
18
+ render_serialized_payload { serialize_resource(ticket_transfer) }
19
+ end
20
+
21
+ def preview_rule
22
+ result = SpreeCmCommissioner::TicketTransfers::PreviewRule.call(
23
+ from_guest_id: params[:guest_id],
24
+ currency: params[:currency] || 'USD'
25
+ )
26
+
27
+ if result.success?
28
+ render json: { data: result.value }
29
+ else
30
+ render_error_payload(result.error)
31
+ end
32
+ end
33
+
34
+ def create
35
+ result = SpreeCmCommissioner::TicketTransfers::Initiate.call(
36
+ from_user_id: spree_current_user.id,
37
+ to_user_id: ticket_transfer_params[:to_user_id],
38
+ from_guest_id: ticket_transfer_params[:guest_id],
39
+ price: ticket_transfer_params[:price] || 0,
40
+ currency: ticket_transfer_params[:currency] || 'USD',
41
+ expires_at: ticket_transfer_params[:expires_at],
42
+ transfer_type: ticket_transfer_params[:transfer_type] || 'purchase'
43
+ )
44
+
45
+ if result.success?
46
+ render_serialized_payload(201) { serialize_resource(result.value[:ticket_transfer]) }
47
+ else
48
+ render_error_payload(result.error)
49
+ end
50
+ end
51
+
52
+ def cancel
53
+ ticket_transfer = model_class.find_by!(token: params[:id])
54
+
55
+ result = SpreeCmCommissioner::TicketTransfers::Cancel.call(
56
+ ticket_transfer: ticket_transfer,
57
+ acting_user: spree_current_user
58
+ )
59
+
60
+ if result.success?
61
+ render_serialized_payload { serialize_resource(result.value[:ticket_transfer]) }
62
+ else
63
+ render_error_payload(result.error)
64
+ end
65
+ end
66
+
67
+ def reject
68
+ ticket_transfer = model_class.find_by!(token: params[:id])
69
+
70
+ result = SpreeCmCommissioner::TicketTransfers::Reject.call(
71
+ ticket_transfer: ticket_transfer,
72
+ acting_user: spree_current_user
73
+ )
74
+
75
+ if result.success?
76
+ render_serialized_payload { serialize_resource(result.value[:ticket_transfer]) }
77
+ else
78
+ render_error_payload(result.error)
79
+ end
80
+ end
81
+
82
+ private
83
+
84
+ def model_class
85
+ SpreeCmCommissioner::TicketTransfer
86
+ end
87
+
88
+ def collection
89
+ SpreeCmCommissioner::TicketTransfer.includes(:order,
90
+ from_guest: [:event, { line_item: :product }]
91
+ ).where(from_user_id: spree_current_user.id)
92
+ end
93
+
94
+ def resource_serializer
95
+ SpreeCmCommissioner::V2::Storefront::TicketTransferSerializer
96
+ end
97
+
98
+ def collection_serializer
99
+ SpreeCmCommissioner::V2::Storefront::TicketTransferSerializer
100
+ end
101
+
102
+ def ticket_transfer_params
103
+ params.require(:ticket_transfer).permit(:to_user_id, :guest_id, :price, :currency, :expires_at, :transfer_type)
104
+ end
105
+
106
+ # When sending to a specific user, only sender and that buyer can access.
107
+ def authorized_to_view?(ticket_transfer)
108
+ return true if ticket_transfer.to_user_id.blank?
109
+
110
+ current_user_id = spree_current_user&.id
111
+ ticket_transfer.from_user_id == current_user_id || ticket_transfer.to_user_id == current_user_id
112
+ end
113
+ end
114
+ end
115
+ end
116
+ end
117
+ end
@@ -39,7 +39,6 @@ module Spree
39
39
  params[:date],
40
40
  params[:route_type],
41
41
  params[:vendor_id],
42
- params[:featured],
43
42
  params[:number_of_guests],
44
43
  resource_includes&.sort&.join(','),
45
44
  sparse_fields&.sort&.join(','),
@@ -65,8 +64,7 @@ module Spree
65
64
  'trips.vendor',
66
65
  'trips.vendor.logo',
67
66
  'trips.vehicle_type',
68
- 'trips.vehicle_type.vehicle_photos',
69
- 'trips.amenities'
67
+ 'trips.vehicle_type.vehicle_photos'
70
68
  ]
71
69
  end
72
70
 
@@ -100,7 +98,11 @@ module Spree
100
98
 
101
99
  # override
102
100
  def serializer_params
103
- params.permit(:include).to_hash
101
+ super.merge(
102
+ exclude_vendor_kind_option_types: true,
103
+ exclude_vendor_kind_option_values: true,
104
+ exclude_vehicle_type_amenities: true
105
+ )
104
106
  end
105
107
 
106
108
  # override
@@ -14,7 +14,7 @@ module Spree
14
14
  end
15
15
 
16
16
  def scope
17
- current_season.episodes.includes(:voting_sessions)
17
+ current_season.episodes.where(product_type: :show_episode).includes(:voting_sessions, :vote_packages)
18
18
  end
19
19
 
20
20
  def current_season
@@ -25,6 +25,17 @@ module Spree
25
25
  def resource_serializer
26
26
  Spree::V2::Tenant::TripSerializer
27
27
  end
28
+
29
+ # override
30
+ def serializer_params
31
+ super.merge(
32
+ include_details: include_details?
33
+ )
34
+ end
35
+
36
+ def include_details?
37
+ params.fetch(:include_details, 'true') == 'true'
38
+ end
28
39
  end
29
40
  end
30
41
  end
@@ -40,9 +40,13 @@ module SpreeCmCommissioner
40
40
 
41
41
  # override
42
42
  def cancel
43
- internal_note = params.dig(:order, :internal_note)
44
- @order.canceled_by(try_spree_current_user, cancellation_reason: internal_note)
45
- flash[:success] = Spree.t(:order_canceled)
43
+ if @order.guests_in_transfer?
44
+ flash[:error] = Spree.t('admin.orders.cannot_cancel_guest_transfer')
45
+ else
46
+ internal_note = params.dig(:order, :internal_note)
47
+ @order.canceled_by(try_spree_current_user, cancellation_reason: internal_note)
48
+ flash[:success] = Spree.t(:order_canceled)
49
+ end
46
50
  redirect_back fallback_location: spree.edit_admin_order_url(@order)
47
51
  end
48
52
 
@@ -0,0 +1,68 @@
1
+ # ๐ŸŽ‰ ---[Event]---
2
+ #
3
+ # ๐Ÿ“‹ Event: {{event.name}}
4
+ #
5
+ # ๐Ÿ‘ค Organizer: {{vendor.name}}
6
+ #
7
+ # ๐Ÿ—“๏ธ Start Date: {{from_date}}
8
+ # ๐Ÿ—“๏ธ End Date: {{to_date}}
9
+ # ๐Ÿ”— URL: ......
10
+ #
11
+ module SpreeCmCommissioner
12
+ class EventTelegramMessageFactory < TelegramMessageFactory
13
+ attr_reader :event
14
+
15
+ def initialize(event:)
16
+ @event = event
17
+
18
+ title = "โœจ #{event.name} โœจ"
19
+
20
+ super(title: title)
21
+ end
22
+
23
+ def body
24
+ [
25
+ event_name_line,
26
+ organizer_line,
27
+ [start_date_line, end_date_line].compact.join("\n"),
28
+ event_url_line
29
+ ].compact_blank.join("\n\n")
30
+ end
31
+
32
+ private
33
+
34
+ def event_name_line
35
+ "๐Ÿ“‹ Event: #{event.name}" if event.name.present?
36
+ end
37
+
38
+ def organizer_line
39
+ return if event.vendor&.name.blank?
40
+
41
+ "๐Ÿ‘ค Organizer: #{bold(event.vendor.name)}"
42
+ end
43
+
44
+ def start_date_line
45
+ return if event.from_date.blank?
46
+
47
+ "๐Ÿ—“๏ธ Start Date: #{pretty_datetime(event.from_date)}"
48
+ end
49
+
50
+ def end_date_line
51
+ return if event.to_date.blank?
52
+
53
+ "๐Ÿ—“๏ธ End Date: #{pretty_datetime(event.to_date)}"
54
+ end
55
+
56
+ def pretty_datetime(date)
57
+ return '' if date.blank?
58
+
59
+ date.strftime('%b %d, %Y ยท %H:%M')
60
+ end
61
+
62
+ def event_url_line
63
+ return if event.event_url.blank?
64
+
65
+ "๐Ÿ”— URL: #{event.event_url}"
66
+ end
67
+ end
68
+ end
@@ -48,7 +48,7 @@ module SpreeCmCommissioner
48
48
  end
49
49
 
50
50
  def line_item_content(line_item)
51
- return intercity_taxi_line_item_content(line_item) if intercity_taxi?(line_item)
51
+ return transit_line_item_content(line_item) if line_item.transit?
52
52
 
53
53
  text = []
54
54
 
@@ -64,8 +64,8 @@ module SpreeCmCommissioner
64
64
  def pretty_date_for(line_item)
65
65
  return nil unless line_item.date_present?
66
66
 
67
- from_date = pretty_date(line_item.from_date)
68
- to_date = pretty_date(line_item.to_date)
67
+ from_date = pretty_datetime(line_item.from_date)
68
+ to_date = pretty_datetime(line_item.to_date)
69
69
 
70
70
  if from_date == to_date
71
71
  "๐Ÿ—“๏ธ #{from_date}"
@@ -84,7 +84,7 @@ module SpreeCmCommissioner
84
84
  text << "Email: #{inline_code(order.email)}" if order.email.present?
85
85
  text << "Delivery Address: #{formatted_shipping_address.presence || 'N/A'}" if order.delivery_required?
86
86
 
87
- append_intercity_taxi_footer(text) if intercity_taxi_order?
87
+ append_transit_footer(text) if transit_order?
88
88
 
89
89
  if show_details_link && order.guests.any?
90
90
  text << ''
@@ -132,12 +132,12 @@ module SpreeCmCommissioner
132
132
 
133
133
  private
134
134
 
135
- def intercity_taxi?(line_item)
136
- trip_for(line_item)&.intercity_taxi?
135
+ def transit_order?
136
+ selected_line_items.any?(&:transit?)
137
137
  end
138
138
 
139
139
  def intercity_taxi_order?
140
- selected_line_items.any? { |li| intercity_taxi?(li) }
140
+ selected_line_items.any? { |li| trip_for(li)&.intercity_taxi? }
141
141
  end
142
142
 
143
143
  def trip_for(line_item)
@@ -147,10 +147,10 @@ module SpreeCmCommissioner
147
147
  @trip_cache[line_item.trip_id] ||= SpreeCmCommissioner::Trip.find_by(id: line_item.trip_id)
148
148
  end
149
149
 
150
- def intercity_taxi_line_item_content(line_item)
150
+ def transit_line_item_content(line_item)
151
151
  text = []
152
- route_text = taxi_route_text(line_item)
153
- departure_text = taxi_departure_text(line_item)
152
+ route_text = transit_route_text(line_item)
153
+ departure_text = transit_departure_text(line_item)
154
154
  text << bold('๐Ÿš— Trip Details')
155
155
  text << "Route: #{route_text}" if route_text.present?
156
156
  text << "Passengers: #{line_item.passenger_count || line_item.quantity}"
@@ -159,7 +159,7 @@ module SpreeCmCommissioner
159
159
  text.compact.join("\n")
160
160
  end
161
161
 
162
- def taxi_route_text(line_item)
162
+ def transit_route_text(line_item)
163
163
  trip = trip_for(line_item)
164
164
  return line_item.product.name if trip.blank?
165
165
 
@@ -171,21 +171,35 @@ module SpreeCmCommissioner
171
171
  vehicle.present? ? "#{parts} (#{vehicle})" : parts
172
172
  end
173
173
 
174
- def taxi_departure_text(line_item)
174
+ def transit_departure_text(line_item)
175
175
  return nil unless line_item.date_present?
176
176
 
177
177
  line_item.from_date.strftime('%b %d, %Y ยท %H:%M')
178
178
  end
179
179
 
180
180
  def nationality_text
181
- values = order.saved_guests.map { |sg| sg.nationality&.name.presence || sg.nationality_group&.humanize }.compact.uniq
182
- return nil if values.empty?
181
+ grouped = order.saved_guests.group_by { |sg| sg.nationality_group&.humanize }
182
+ grouped.delete(nil)
183
+ return nil if grouped.empty?
183
184
 
184
- values.join(', ')
185
+ grouped.map { |nationality_group, guests| customer_type_summary(nationality_group, guests) }.join(', ')
185
186
  end
186
187
 
187
- def append_intercity_taxi_footer(text)
188
- text << "Nationality: #{nationality_text}" if nationality_text.present?
188
+ # e.g. "Local (Adult x1, Child x1)". age_group is NOT NULL, so it is always present.
189
+ def customer_type_summary(nationality_group, guests)
190
+ age_breakdown = guests
191
+ .group_by { |sg| sg.age_group.humanize }
192
+ .map { |age_group, age_guests| "#{age_group} x#{age_guests.count}" }
193
+ .join(', ')
194
+
195
+ "#{nationality_group} (#{age_breakdown})"
196
+ end
197
+
198
+ def append_transit_footer(text)
199
+ nationality = nationality_text
200
+ text << "Nationality: #{nationality}" if nationality.present?
201
+
202
+ return unless intercity_taxi_order?
189
203
 
190
204
  pickup = selected_line_items.filter_map(&:pickup_map_place).first
191
205
  dropoff = selected_line_items.filter_map(&:dropoff_map_place).first
@@ -46,5 +46,11 @@ module SpreeCmCommissioner
46
46
 
47
47
  date.to_date.strftime('%b %d, %Y')
48
48
  end
49
+
50
+ def pretty_datetime(datetime)
51
+ return '' if datetime.blank?
52
+
53
+ datetime&.in_time_zone&.strftime('%b %d, %Y - %H:%M')
54
+ end
49
55
  end
50
56
  end
@@ -61,6 +61,16 @@ module SpreeCmCommissioner
61
61
  'badge-info'
62
62
  end
63
63
  end
64
+
65
+ def guest_state_badge(guest)
66
+ badge_class = case guest.state
67
+ when 'active' then 'badge-success'
68
+ when 'locked' then 'badge-warning'
69
+ when 'transferred' then 'badge-secondary'
70
+ else 'badge-info'
71
+ end
72
+ content_tag(:span, guest.state.humanize, class: "badge #{badge_class}")
73
+ end
64
74
  end
65
75
  end
66
76
  end