spree_cm_commissioner 1.11.0.pre.pre4 → 1.13.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 (175) 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/product_places_controller.rb +2 -0
  8. data/app/controllers/spree/admin/stock_managements_controller.rb +1 -62
  9. data/app/controllers/spree/api/v2/organizer/invite_guests_controller.rb +70 -0
  10. data/app/controllers/spree/api/v2/storefront/accommodations_controller.rb +31 -14
  11. data/app/controllers/spree/api/v2/storefront/queue_cart/line_items_controller.rb +2 -2
  12. data/app/controllers/spree/api/v2/storefront/waiting_room_sessions_controller.rb +1 -0
  13. data/app/controllers/spree/api/v2/tenant/account_checker_controller.rb +1 -1
  14. data/app/controllers/spree/api/v2/tenant/account_recovers_controller.rb +2 -2
  15. data/app/controllers/spree/api/v2/tenant/cms_pages_controller.rb +41 -0
  16. data/app/controllers/spree/api/v2/tenant/reset_passwords_controller.rb +1 -1
  17. data/app/controllers/spree/api/v2/tenant/waiting_room_sessions_controller.rb +30 -0
  18. data/app/controllers/spree_cm_commissioner/admin/variants_controller_decorator.rb +17 -0
  19. data/app/controllers/spree_cm_commissioner/api/v2/storefront/cms_pages_controller_decorator.rb +18 -0
  20. data/app/interactors/spree_cm_commissioner/account_recover.rb +2 -2
  21. data/app/interactors/spree_cm_commissioner/create_event.rb +7 -26
  22. data/app/interactors/spree_cm_commissioner/create_ticket.rb +95 -0
  23. data/app/interactors/spree_cm_commissioner/event_line_items_date_syncer.rb +19 -0
  24. data/app/interactors/spree_cm_commissioner/existing_account_checker.rb +1 -1
  25. data/app/interactors/spree_cm_commissioner/order_importer/multi_guest.rb +31 -0
  26. data/app/interactors/spree_cm_commissioner/organizers_transactional_email_notifier.rb +27 -0
  27. data/app/interactors/spree_cm_commissioner/pin_code_sender.rb +3 -3
  28. data/app/interactors/spree_cm_commissioner/product_event_id_to_children_syncer.rb +15 -0
  29. data/app/interactors/spree_cm_commissioner/sms.rb +14 -0
  30. data/app/interactors/spree_cm_commissioner/telegram_debug_pin_code_sender.rb +2 -1
  31. data/app/interactors/spree_cm_commissioner/transactional_email_sender.rb +50 -0
  32. data/app/interactors/spree_cm_commissioner/user_forgotten_password_updater.rb +2 -1
  33. data/app/interactors/spree_cm_commissioner/user_password_authenticator.rb +4 -2
  34. data/app/interactors/spree_cm_commissioner/waiting_room_session_creator.rb +7 -14
  35. data/app/interactors/spree_cm_commissioner/waiting_room_session_firebase_logger.rb +30 -0
  36. data/app/jobs/spree_cm_commissioner/ensure_event_for_product_line_item_guests_job.rb +13 -0
  37. data/app/jobs/spree_cm_commissioner/event_line_items_date_syncer_job.rb +8 -0
  38. data/app/jobs/spree_cm_commissioner/product_event_id_to_children_syncer_job.rb +8 -0
  39. data/app/jobs/spree_cm_commissioner/telegram_debug_pin_code_sender_job.rb +3 -1
  40. data/app/jobs/spree_cm_commissioner/waiting_room_session_firebase_logger_job.rb +13 -0
  41. data/app/mailers/spree_cm_commissioner/event_transactional_mailer.rb +23 -0
  42. data/app/mailers/spree_cm_commissioner/pin_code_mailer.rb +1 -0
  43. data/app/models/concerns/spree_cm_commissioner/kyc_bitwise.rb +2 -0
  44. data/app/models/concerns/spree_cm_commissioner/line_item_durationable.rb +10 -6
  45. data/app/models/concerns/spree_cm_commissioner/order_state_machine.rb +13 -26
  46. data/app/models/concerns/spree_cm_commissioner/product_delegation.rb +4 -3
  47. data/app/models/concerns/spree_cm_commissioner/product_type.rb +0 -10
  48. data/app/models/concerns/spree_cm_commissioner/user_identity.rb +7 -4
  49. data/app/models/concerns/spree_cm_commissioner/variant_options_concern.rb +8 -10
  50. data/app/models/spree_cm_commissioner/cms_page_decorator.rb +9 -0
  51. data/app/models/spree_cm_commissioner/event_ticket_google_wallet.rb +2 -2
  52. data/app/models/spree_cm_commissioner/guest.rb +1 -1
  53. data/app/models/spree_cm_commissioner/invite_guest.rb +23 -0
  54. data/app/models/spree_cm_commissioner/line_item_decorator.rb +10 -16
  55. data/app/models/spree_cm_commissioner/option_type_decorator.rb +6 -0
  56. data/app/models/spree_cm_commissioner/order_decorator.rb +0 -15
  57. data/app/models/spree_cm_commissioner/place.rb +1 -1
  58. data/app/models/spree_cm_commissioner/product_decorator.rb +17 -14
  59. data/app/models/spree_cm_commissioner/product_place.rb +1 -0
  60. data/app/models/spree_cm_commissioner/role_decorator.rb +3 -0
  61. data/app/models/spree_cm_commissioner/stock/availability_checker.rb +25 -27
  62. data/app/models/spree_cm_commissioner/stock/availability_validator_decorator.rb +1 -2
  63. data/app/models/spree_cm_commissioner/stock/line_item_availability_checker.rb +3 -3
  64. data/app/models/spree_cm_commissioner/stock_item_decorator.rb +0 -14
  65. data/app/models/spree_cm_commissioner/taxon_decorator.rb +26 -3
  66. data/app/models/spree_cm_commissioner/transactional_email.rb +6 -0
  67. data/app/models/spree_cm_commissioner/user_decorator.rb +6 -0
  68. data/app/models/spree_cm_commissioner/variant_decorator.rb +27 -34
  69. data/app/models/spree_cm_commissioner/vehicle.rb +0 -7
  70. data/app/models/spree_cm_commissioner/vendor_decorator.rb +9 -1
  71. data/app/overrides/spree/admin/cms_pages/_form/tenant_fields.html.erb.deface +9 -0
  72. data/app/overrides/spree/admin/cms_pages/index/cms_pages_tabs.html.erb.deface +21 -0
  73. data/app/overrides/spree/admin/taxons/_form/assets_form.html.erb.deface +2 -2
  74. data/app/overrides/spree/admin/taxons/_form/available_on.html.erb.deface +3 -1
  75. data/app/overrides/spree/admin/taxons/_form/background_color_and_foreground_color.html.erb.deface +3 -1
  76. data/app/overrides/spree/admin/taxons/_form/custom_redirect_url.html.erb.deface +3 -1
  77. data/app/overrides/spree/admin/taxons/_form/hide_video_banner.html.erb.deface +3 -1
  78. data/app/overrides/spree/admin/taxons/_form/purchasable_on_status.html.erb.deface +3 -1
  79. data/app/overrides/spree/admin/taxons/_form/show_badge_status.html.erb.deface +3 -1
  80. data/app/overrides/spree/admin/taxons/_form/to_date_form_date.html.erb.deface +3 -4
  81. data/app/overrides/spree/admin/users/_form/roles_fields.html.erb.deface +1 -1
  82. data/app/overrides/spree/admin/users/index/body.html.erb.deface +3 -0
  83. data/app/overrides/spree/admin/users/index/headers.html.erb.deface +3 -0
  84. data/app/overrides/spree/admin/variants/_form/kyc_field.html.erb.deface +40 -0
  85. data/app/overrides/spree/admin/variants/edit/variant_status.html.erb.deface +6 -3
  86. data/app/queries/spree_cm_commissioner/variant_availability/non_permanent_stock_query.rb +45 -0
  87. data/app/queries/spree_cm_commissioner/variant_availability/permanent_stock_query.rb +55 -0
  88. data/app/request_schemas/spree_cm_commissioner/accommodation_request_schema.rb +0 -3
  89. data/app/request_schemas/spree_cm_commissioner/application_request_schema.rb +1 -1
  90. data/app/serializers/spree/v2/organizer/invite_guest_serializer.rb +13 -0
  91. data/app/serializers/spree/v2/storefront/accommodation_serializer.rb +0 -2
  92. data/app/serializers/spree/v2/storefront/taxon_serializer_decorator.rb +2 -0
  93. data/app/serializers/spree/v2/tenant/waiting_room_session_serializer.rb +9 -0
  94. data/app/serializers/spree_cm_commissioner/v2/storefront/guest_serializer.rb +2 -1
  95. data/app/services/spree_cm_commissioner/user_authenticator.rb +3 -1
  96. data/app/services/spree_cm_commissioner/user_roles_assigner.rb +62 -0
  97. data/app/views/spree/admin/shared/_cms_pages_tabs.html.erb +20 -0
  98. data/app/views/spree/admin/shared/_taxon_tabs.html.erb +19 -19
  99. data/app/views/spree/admin/stock_managements/_variant_stock_items.html.erb +1 -3
  100. data/app/views/spree/admin/stock_managements/index.html.erb +5 -40
  101. data/app/views/spree/shared/_base_mailer_header.html.erb +10 -2
  102. data/app/views/spree_cm_commissioner/event_transactional_mailer/_event_banner.html.erb +3 -0
  103. data/app/views/spree_cm_commissioner/event_transactional_mailer/_mailer_stylesheets.html.erb +317 -0
  104. data/app/views/spree_cm_commissioner/event_transactional_mailer/_note.html.erb +8 -0
  105. data/app/views/spree_cm_commissioner/event_transactional_mailer/_share_button.html.erb +3 -0
  106. data/app/views/spree_cm_commissioner/event_transactional_mailer/send_to_organizer.html.erb +59 -0
  107. data/app/views/spree_cm_commissioner/event_transactional_mailer/send_to_participant.html.erb +52 -0
  108. data/app/views/spree_cm_commissioner/layouts/event_transactional_mailer.html.erb +14 -0
  109. data/config/initializers/spree_permitted_attributes.rb +0 -5
  110. data/config/locales/en.yml +36 -0
  111. data/config/locales/km.yml +2 -0
  112. data/config/routes.rb +6 -11
  113. data/db/migrate/20250422014417_create_spree_cm_commissioner_invite_guest.rb +21 -0
  114. data/db/migrate/20250509033437_create_spree_cm_commissioner_transactional_emails.rb +12 -0
  115. data/db/migrate/20250509075429_add_max_order_quantity_to_spree_product.rb +5 -0
  116. data/db/migrate/20250512075319_add_tenant_id_to_spree_cms_pages.rb +6 -0
  117. data/db/migrate/20250520042602_add_event_to_spree_products.rb +7 -0
  118. data/db/migrate/20250520044533_add_event_to_spree_line_items.rb +7 -0
  119. data/db/migrate/20250521024345_add_tenant_id_to_cm_waiting_room_sessions.rb +6 -0
  120. data/db/migrate/20250521095539_add_kyc_to_spree_variants.rb +5 -0
  121. data/docker-compose.yml +1 -1
  122. data/lib/generators/spree_cm_commissioner/install/install_generator.rb +3 -11
  123. data/lib/generators/spree_cm_commissioner/install/templates/app/javascript/{spree_dashboard/spree_cm_commissioner → spree_cm_commissioner}/utilities.js +0 -4
  124. data/lib/spree_cm_commissioner/calendar_event.rb +1 -11
  125. data/lib/spree_cm_commissioner/test_helper/factories/homepage_section_relatable_factory.rb +1 -1
  126. data/lib/spree_cm_commissioner/test_helper/factories/line_item_factory.rb +1 -1
  127. data/lib/spree_cm_commissioner/test_helper/factories/product_factory.rb +5 -18
  128. data/lib/spree_cm_commissioner/test_helper/factories/role.rb +7 -0
  129. data/lib/spree_cm_commissioner/test_helper/factories/stock_location_factory.rb +2 -2
  130. data/lib/spree_cm_commissioner/test_helper/factories/taxon_home_banner_factory.rb +5 -0
  131. data/lib/spree_cm_commissioner/test_helper/factories/transactional_email_factory.rb +6 -0
  132. data/lib/spree_cm_commissioner/test_helper/factories/variant_factory.rb +6 -41
  133. data/lib/spree_cm_commissioner/test_helper/factories/vendor_factory.rb +1 -1
  134. data/lib/spree_cm_commissioner/version.rb +1 -1
  135. data/lib/spree_cm_commissioner.rb +0 -34
  136. data/lib/tasks/ensure_event_for_product_line_item_guests.rake +7 -0
  137. data/lib/tasks/update_invalid_self_root_places.rake +9 -0
  138. data/lib/tasks/update_orphan_root_places.rake +1 -0
  139. data/spree_cm_commissioner.gemspec +0 -5
  140. metadata +54 -82
  141. data/app/controllers/spree/api/v2/storefront/accommodations/variants_controller.rb +0 -42
  142. data/app/finders/spree_cm_commissioner/accommodations/find.rb +0 -40
  143. data/app/finders/spree_cm_commissioner/accommodations/find_variant.rb +0 -35
  144. data/app/interactors/spree_cm_commissioner/ensure_correct_product_type.rb +0 -40
  145. data/app/interactors/spree_cm_commissioner/inventory_item_syncer.rb +0 -25
  146. data/app/interactors/spree_cm_commissioner/stock/inventory_items_adjuster.rb +0 -13
  147. data/app/interactors/spree_cm_commissioner/stock/inventory_items_generator.rb +0 -15
  148. data/app/interactors/spree_cm_commissioner/stock/permanent_inventory_items_generator.rb +0 -75
  149. data/app/interactors/spree_cm_commissioner/stock/stock_movement_creator.rb +0 -24
  150. data/app/interactors/spree_cm_commissioner/user_roles_assigner.rb +0 -22
  151. data/app/jobs/spree_cm_commissioner/ensure_correct_product_type_job.rb +0 -7
  152. data/app/jobs/spree_cm_commissioner/inventory_item_syncer_job.rb +0 -7
  153. data/app/jobs/spree_cm_commissioner/stock/inventory_items_adjuster_job.rb +0 -11
  154. data/app/jobs/spree_cm_commissioner/stock/inventory_items_generator_job.rb +0 -11
  155. data/app/jobs/spree_cm_commissioner/stock/permanent_inventory_items_generator_job.rb +0 -9
  156. data/app/models/spree_cm_commissioner/inventory.rb +0 -11
  157. data/app/models/spree_cm_commissioner/inventory_item.rb +0 -56
  158. data/app/models/spree_cm_commissioner/redis_stock/cached_inventory_items_builder.rb +0 -40
  159. data/app/models/spree_cm_commissioner/redis_stock/inventory_updater.rb +0 -126
  160. data/app/models/spree_cm_commissioner/redis_stock/line_items_cached_inventory_items_builder.rb +0 -36
  161. data/app/models/spree_cm_commissioner/redis_stock/variant_cached_inventory_items_builder.rb +0 -27
  162. data/app/models/spree_cm_commissioner/stock/order_availability_checker.rb +0 -44
  163. data/app/request_schemas/spree_cm_commissioner/variant_request_schema.rb +0 -19
  164. data/app/views/spree/admin/stock_managements/_events_popover.html.erb +0 -23
  165. data/app/views/spree/admin/stock_managements/calendar.html.erb +0 -35
  166. data/db/migrate/20250304293518_create_cm_inventory_items.rb +0 -21
  167. data/db/migrate/20250429094228_add_lock_version_to_cm_inventory_items.rb +0 -5
  168. data/db/migrate/20250502025848_add_index_to_spree_products.rb +0 -5
  169. data/db/migrate/20250502030001_add_product_type_to_spree_variants.rb +0 -5
  170. data/db/migrate/20250502030002_add_product_type_to_spree_line_items.rb +0 -5
  171. data/lib/spree_cm_commissioner/cached_inventory_item.rb +0 -23
  172. data/lib/spree_cm_commissioner/test_helper/factories/inventory_item_factory.rb +0 -9
  173. data/lib/tasks/create_default_non_permanent_inventory_items.rake +0 -16
  174. data/lib/tasks/ensure_correct_product_type.rake +0 -7
  175. data/lib/tasks/generate_inventory_items.rake +0 -7
@@ -1,35 +0,0 @@
1
- module SpreeCmCommissioner
2
- module Accommodations
3
- class FindVariant
4
- attr_reader :vendor_id, :from_date, :to_date, :number_of_guests
5
-
6
- def initialize(vendor_id:, from_date:, to_date:, number_of_adults:, number_of_kids:)
7
- @vendor_id = vendor_id
8
- @from_date = from_date
9
- @to_date = to_date
10
- @number_of_guests = number_of_adults.to_i + number_of_kids.to_i
11
- end
12
-
13
- def execute
14
- Spree::Variant
15
- .joins(:inventory_items)
16
- .where(vendor_id: vendor_id)
17
- .where(inventory_items: { inventory_date: date_range_excluding_checkout })
18
- .where('CAST(public_metadata->\'cm_options\'->>\'number-of-adults\' AS INTEGER) +
19
- CAST(public_metadata->\'cm_options\'->>\'number-of-kids\' AS INTEGER) >= ?', number_of_guests
20
- )
21
- .where('inventory_items.quantity_available > 0')
22
- .distinct
23
- end
24
-
25
- private
26
-
27
- # Why? check_out date is not considered to be charged
28
- # For example, if you check in on 2023-10-01 and check out on 2023-10-02,
29
- # you will be charged for one night (2023-10-01).
30
- def date_range_excluding_checkout
31
- from_date..to_date.prev_day
32
- end
33
- end
34
- end
35
- end
@@ -1,40 +0,0 @@
1
- module SpreeCmCommissioner
2
- class EnsureCorrectProductType < BaseInteractor
3
- def call
4
- Spree::Product
5
- .left_joins(variants_including_master: %i[inventory_items line_items])
6
- .where(
7
- 'spree_variants.product_type IS NULL OR
8
- spree_variants.product_type != spree_products.product_type OR
9
-
10
- cm_inventory_items.product_type IS NULL OR
11
- cm_inventory_items.product_type != spree_products.product_type OR
12
-
13
- spree_line_items.product_type IS NULL OR
14
- spree_line_items.product_type != spree_products.product_type OR
15
-
16
- spree_products.product_type IS NOT NULL
17
- '
18
- )
19
- .distinct.find_each do |product|
20
- sync_product_type_for(product)
21
- end
22
- end
23
-
24
- def sync_product_type_for(product)
25
- product_type = Spree::Variant.product_types[product.product_type]
26
-
27
- product.variants_including_master
28
- .where('spree_variants.product_type IS NULL OR spree_variants.product_type != ?', product_type)
29
- .update_all(product_type: product_type) # rubocop:disable Rails/SkipsModelValidations
30
-
31
- product.line_items
32
- .where('spree_line_items.product_type IS NULL OR spree_line_items.product_type != ?', product_type)
33
- .update_all(product_type: product_type) # rubocop:disable Rails/SkipsModelValidations
34
-
35
- product.inventory_items
36
- .where('cm_inventory_items.product_type IS NULL OR cm_inventory_items.product_type != ?', product_type)
37
- .update_all(product_type: product_type) # rubocop:disable Rails/SkipsModelValidations
38
- end
39
- end
40
- end
@@ -1,25 +0,0 @@
1
- module SpreeCmCommissioner
2
- class InventoryItemSyncer < BaseInteractor
3
- # inventory_id_and_quantities = [{ inventory_id: inventory_item1.id, quantity: 5 } ]
4
- delegate :inventory_id_and_quantities, to: :context
5
-
6
- def call
7
- ActiveRecord::Base.transaction do
8
- inventory_items.each do |inventory_item|
9
- quantity = inventory_id_and_quantities.find { |item| item[:inventory_id] == inventory_item.id }&.dig(:quantity) || 0
10
- adjust_quantity_available(inventory_item, quantity)
11
- end
12
- end
13
- end
14
-
15
- private
16
-
17
- def adjust_quantity_available(inventory_item, quantity)
18
- inventory_item.update!(quantity_available: inventory_item.quantity_available + quantity)
19
- end
20
-
21
- def inventory_items
22
- @inventory_items ||= InventoryItem.where(id: inventory_id_and_quantities.pluck(:inventory_id))
23
- end
24
- end
25
- end
@@ -1,13 +0,0 @@
1
- module SpreeCmCommissioner
2
- module Stock
3
- class InventoryItemsAdjuster < BaseInteractor
4
- delegate :variant, :quantity, to: :context
5
-
6
- def call
7
- variant.inventory_items.active.find_each do |inventory_item|
8
- inventory_item.adjust_quantity!(quantity)
9
- end
10
- end
11
- end
12
- end
13
- end
@@ -1,15 +0,0 @@
1
- module SpreeCmCommissioner
2
- module Stock
3
- class InventoryItemsGenerator < BaseInteractor
4
- delegate :variant, to: :context
5
-
6
- def call
7
- if variant.permanent_stock?
8
- SpreeCmCommissioner::Stock::PermanentInventoryItemsGenerator.call(variant_ids: [variant.id])
9
- else
10
- variant.create_default_non_permanent_inventory_item! unless variant.default_inventory_item_exist?
11
- end
12
- end
13
- end
14
- end
15
- end
@@ -1,75 +0,0 @@
1
- module SpreeCmCommissioner
2
- module Stock
3
- class PermanentInventoryItemsGenerator < BaseInteractor
4
- delegate :variant_ids, to: :context
5
-
6
- def variants_per_batch = 1000
7
-
8
- def pre_inventory_days_for(variant)
9
- context.pre_inventory_days || variant.pre_inventory_days
10
- end
11
-
12
- def call
13
- variants.in_batches(of: variants_per_batch) do |batch|
14
- generate_inventory_items_for_batch(batch)
15
- end
16
- end
17
-
18
- private
19
-
20
- def generate_inventory_items_for_batch(batch)
21
- total_on_hand_by_variant = total_on_hand_for(batch)
22
- batch.each do |variant|
23
- count_on_hand = total_on_hand_by_variant[variant.id] || 0
24
- generate_inventory_items_for_variant(variant, count_on_hand)
25
- end
26
- end
27
-
28
- def generate_inventory_items_for_variant(variant, count_on_hand)
29
- inventory_dates_for(variant).each do |inventory_date|
30
- next if inventory_exist?(variant, inventory_date)
31
-
32
- create_inventory_item(variant, inventory_date, count_on_hand)
33
- end
34
- end
35
-
36
- def inventory_dates_for(variant)
37
- start_date = Time.zone.tomorrow
38
- end_date = Time.zone.today + pre_inventory_days_for(variant)
39
-
40
- (start_date..end_date)
41
- end
42
-
43
- def inventory_exist?(variant, inventory_date)
44
- variant.inventory_items.exists?(inventory_date: inventory_date)
45
- end
46
-
47
- def create_inventory_item(variant, inventory_date, count_on_hand)
48
- variant.inventory_items.create!(
49
- inventory_date: inventory_date,
50
- quantity_available: count_on_hand,
51
- max_capacity: count_on_hand,
52
- product_type: variant.product_type
53
- )
54
- end
55
-
56
- # Returns a hash: { variant_id => total_on_hand, ... }
57
- def total_on_hand_for(variants)
58
- variant_ids = variants.pluck(:id)
59
-
60
- Spree::StockItem
61
- .joins(:stock_location)
62
- .where(deleted_at: nil, variant_id: variant_ids)
63
- .where(spree_stock_locations: { active: true })
64
- .group(:variant_id)
65
- .sum(:count_on_hand)
66
- end
67
-
68
- def variants
69
- scope = Spree::Variant.active.with_permanent_stock.where(is_master: false)
70
- scope = scope.where(id: variant_ids) if variant_ids.present?
71
- scope
72
- end
73
- end
74
- end
75
- end
@@ -1,24 +0,0 @@
1
- module SpreeCmCommissioner
2
- module Stock
3
- class StockMovementCreator < BaseInteractor
4
- delegate :variant_id, :stock_location_id, :current_store, :stock_movement_params, to: :context
5
-
6
- def call
7
- variant = current_store.variants.find(variant_id)
8
-
9
- return context.fail!(message: Spree.t(:doesnt_track_inventory)) unless variant.track_inventory?
10
-
11
- stock_location = Spree::StockLocation.find(stock_location_id)
12
- stock_movement = stock_location.stock_movements.build(stock_movement_params)
13
- stock_movement.stock_item = stock_location.set_up_stock_item(variant)
14
-
15
- if stock_movement.save
16
- SpreeCmCommissioner::Stock::InventoryItemsAdjusterJob.perform_later(variant_id: variant.id, quantity: stock_movement.quantity)
17
- context.stock_movement = stock_movement
18
- else
19
- context.fail!(message: stock_movement.errors.full_messages.join(', '))
20
- end
21
- end
22
- end
23
- end
24
- end
@@ -1,22 +0,0 @@
1
- module SpreeCmCommissioner
2
- class UserRolesAssigner < BaseInteractor
3
- delegate :user_id, :role_ids, to: :context
4
-
5
- def call
6
- context.fail!(message: I18n.t('user_roles_assigner.user_not_found')) unless user
7
- context.fail!(message: I18n.t('user_roles_assigner.roles_empty')) if role_ids.blank?
8
- update_roles
9
- end
10
-
11
- private
12
-
13
- def user
14
- @user ||= Spree::User.find_by(id: user_id)
15
- end
16
-
17
- def update_roles
18
- user.role_users.where.not(role_id: role_ids).destroy_all
19
- role_ids.each { |role_id| user.role_users.find_or_create_by(role_id: role_id) }
20
- end
21
- end
22
- end
@@ -1,7 +0,0 @@
1
- module SpreeCmCommissioner
2
- class EnsureCorrectProductTypeJob < ApplicationJob
3
- def perform
4
- SpreeCmCommissioner::EnsureCorrectProductType.call
5
- end
6
- end
7
- end
@@ -1,7 +0,0 @@
1
- module SpreeCmCommissioner
2
- class InventoryItemSyncerJob < ApplicationUniqueJob
3
- def perform(inventory_id_and_quantities:)
4
- InventoryItemSyncer.call(inventory_id_and_quantities:)
5
- end
6
- end
7
- end
@@ -1,11 +0,0 @@
1
- module SpreeCmCommissioner
2
- module Stock
3
- class InventoryItemsAdjusterJob < ApplicationUniqueJob
4
- def perform(variant_id:, quantity:)
5
- variant = Spree::Variant.find(variant_id)
6
-
7
- SpreeCmCommissioner::Stock::InventoryItemsAdjuster.call(variant: variant, quantity: quantity)
8
- end
9
- end
10
- end
11
- end
@@ -1,11 +0,0 @@
1
- module SpreeCmCommissioner
2
- module Stock
3
- class InventoryItemsGeneratorJob < ApplicationUniqueJob
4
- def perform(variant_id:)
5
- variant = Spree::Variant.find(variant_id)
6
-
7
- SpreeCmCommissioner::Stock::InventoryItemsGenerator.call(variant: variant)
8
- end
9
- end
10
- end
11
- end
@@ -1,9 +0,0 @@
1
- module SpreeCmCommissioner
2
- module Stock
3
- class PermanentInventoryItemsGeneratorJob < ApplicationUniqueJob
4
- def perform
5
- SpreeCmCommissioner::Stock::PermanentInventoryItemsGenerator.call
6
- end
7
- end
8
- end
9
- end
@@ -1,11 +0,0 @@
1
- module SpreeCmCommissioner
2
- class Inventory
3
- include ActiveModel::Model
4
-
5
- attr_accessor :variant_id, :inventory_date, :quantity_available, :max_capacity, :product_type
6
-
7
- validates :variant_id, presence: true
8
- validates :quantity_available, numericality: { only_integer: true, greater_than_or_equal_to: 0 }, allow_nil: true
9
- validates :max_capacity, numericality: { only_integer: true, greater_than_or_equal_to: 0 }, allow_nil: true
10
- end
11
- end
@@ -1,56 +0,0 @@
1
- module SpreeCmCommissioner
2
- class InventoryItem < ApplicationRecord
3
- include SpreeCmCommissioner::ProductType
4
-
5
- # Association
6
- belongs_to :variant, class_name: 'Spree::Variant'
7
-
8
- # Validation
9
- validates :quantity_available, numericality: { greater_than_or_equal_to: 0 }
10
- validates :max_capacity, numericality: { greater_than_or_equal_to: 0 } # Originally inventory of each variant.
11
- validates :inventory_date, presence: true, if: :permanent_stock?
12
- validates :variant_id, uniqueness: { scope: :inventory_date, message: -> (object, _data) { "The variant is taken on #{object.inventory_date}" } }
13
-
14
- # Scope
15
- scope :active, -> { where(inventory_date: nil).or(where('inventory_date >= ?', Time.zone.today)) }
16
-
17
- before_save -> { self.product_type = variant.product_type }, if: -> { product_type.nil? }
18
-
19
- # This method is only used when admin update stock
20
- def adjust_quantity!(quantity)
21
- with_lock do
22
- self.max_capacity = max_capacity + quantity
23
- self.quantity_available = [0, quantity_available + quantity].max
24
- save!
25
-
26
- # When user has been searched or booked a product, it has cached the quantity in redis,
27
- # So we need to update redis cache if inventory key has been created in redis
28
- adjust_quantity_in_redis(quantity)
29
- end
30
- end
31
-
32
- def adjust_quantity_in_redis(quantity)
33
- SpreeCmCommissioner.redis_pool.with do |redis|
34
- cached_quantity_available = redis.get("inventory:#{id}")
35
- # ignore if redis doesn't exist
36
- return if cached_quantity_available.nil? # rubocop:disable Lint/NonLocalExitFromIterator
37
-
38
- if quantity.positive?
39
- redis.incrby("inventory:#{id}", quantity)
40
- else
41
- redis.decrby("inventory:#{id}", quantity.abs)
42
- end
43
- end
44
- end
45
-
46
- def active?
47
- inventory_date.nil? || inventory_date >= Time.zone.today
48
- end
49
-
50
- def redis_expired_in
51
- expired_in = 31_536_000 # 1 year for normal stock
52
- expired_in = Time.parse(inventory_date.to_s).end_of_day.to_i - Time.zone.now.to_i if inventory_date.present?
53
- [expired_in, 0].max
54
- end
55
- end
56
- end
@@ -1,40 +0,0 @@
1
- module SpreeCmCommissioner
2
- module RedisStock
3
- class CachedInventoryItemsBuilder
4
- attr_reader :inventory_items
5
-
6
- def initialize(inventory_items)
7
- @inventory_items = inventory_items
8
- end
9
-
10
- # output: [ CachedInventoryItem(...), CachedInventoryItem(...) ]
11
- def call
12
- keys = inventory_items.map { |item| "inventory:#{item.id}" }
13
- return [] unless keys.any?
14
-
15
- counts = SpreeCmCommissioner.redis_pool.with { |redis| redis.mget(*keys) }
16
- inventory_items.map.with_index do |inventory_item, i|
17
- ::SpreeCmCommissioner::CachedInventoryItem.new(
18
- inventory_key: keys[i],
19
- active: inventory_item.active?,
20
- quantity_available: cache_inventory(keys[i], inventory_item, counts[i]),
21
- inventory_item_id: inventory_item.id,
22
- variant_id: inventory_item.variant_id
23
- )
24
- end
25
- end
26
-
27
- private
28
-
29
- def cache_inventory(key, inventory_item, count_in_redis)
30
- return count_in_redis.to_i if count_in_redis.present?
31
-
32
- SpreeCmCommissioner.redis_pool.with do |redis|
33
- redis.set(key, inventory_item.quantity_available, ex: inventory_item.redis_expired_in)
34
- end
35
-
36
- inventory_item.quantity_available
37
- end
38
- end
39
- end
40
- end
@@ -1,126 +0,0 @@
1
- module SpreeCmCommissioner
2
- module RedisStock
3
- class InventoryUpdater
4
- class UnableToRestock < StandardError; end
5
- class UnableToUnstock < StandardError; end
6
-
7
- def initialize(line_item_ids)
8
- @line_item_ids = line_item_ids
9
- end
10
-
11
- def unstock!
12
- keys, quantities, inventory_ids = extract_inventory_data
13
-
14
- raise UnableToUnstock, Spree.t(:insufficient_stock_lines_present) unless unstock(keys, quantities)
15
-
16
- inventory_id_and_quantities = inventory_ids.map.with_index do |inventory_id, i|
17
- { inventory_id: inventory_id, quantity: -quantities[i] }
18
- end
19
-
20
- schedule_sync_inventory(inventory_id_and_quantities)
21
- end
22
-
23
- def restock!
24
- keys, quantities, inventory_ids = extract_inventory_data
25
-
26
- raise UnableToRestock unless restock(keys, quantities)
27
-
28
- inventory_id_and_quantities = inventory_ids.map.with_index do |inventory_id, i|
29
- { inventory_id: inventory_id, quantity: quantities[i] }
30
- end
31
-
32
- schedule_sync_inventory(inventory_id_and_quantities)
33
- end
34
-
35
- private
36
-
37
- # only unstock or restock when variants is available, track inventory, not backorderable & not need_confirmation.
38
- # otherwise, we can ignore them.
39
- def line_items
40
- return @line_items if defined?(@line_items)
41
-
42
- @line_items = Spree::LineItem.where(id: @line_item_ids).includes(variant: :stock_items)
43
- @line_items = @line_items.map do |line_item|
44
- next unless line_item.variant.available?
45
- next unless line_item.variant.should_track_inventory?
46
- next if line_item.variant.backorderable?
47
- next if line_item.variant.need_confirmation?
48
-
49
- line_item
50
- end.compact
51
- end
52
-
53
- def unstock(keys, quantities)
54
- SpreeCmCommissioner.redis_pool.with do |redis|
55
- redis.eval(unstock_redis_script, keys: keys, argv: quantities)
56
- end.positive?
57
- end
58
-
59
- def restock(keys, quantities)
60
- SpreeCmCommissioner.redis_pool.with do |redis|
61
- redis.eval(restock_redis_script, keys: keys, argv: quantities)
62
- end.positive?
63
- end
64
-
65
- # Return: [CachedInventoryItem(...), CachedInventoryItem(...)]
66
- def cached_inventory_items
67
- @cached_inventory_items ||= SpreeCmCommissioner::RedisStock::LineItemsCachedInventoryItemsBuilder.new(line_item_ids: line_items.pluck(:id))
68
- .call.values.flatten
69
- end
70
-
71
- def extract_inventory_data
72
- keys = []
73
- quantities = []
74
- inventory_ids = []
75
-
76
- cached_inventory_items.each do |cached_inventory_item|
77
- keys << cached_inventory_item.inventory_key
78
- quantities << line_items.find { |item| item.variant_id == cached_inventory_item.variant_id }.quantity
79
- inventory_ids << cached_inventory_item.inventory_item_id
80
- end
81
-
82
- [keys, quantities, inventory_ids]
83
- end
84
-
85
- def unstock_redis_script
86
- <<~LUA
87
- local keys = KEYS
88
- local quantities = ARGV
89
-
90
- -- Check availability first
91
- for i, key in ipairs(keys) do
92
- local current = tonumber(redis.call('GET', key) or 0)
93
- if current - tonumber(quantities[i]) < 0 then
94
- return 0
95
- end
96
- end
97
-
98
- -- Apply updates
99
- for i, key in ipairs(keys) do
100
- redis.call('DECRBY', key, tonumber(quantities[i]))
101
- end
102
-
103
- return 1
104
- LUA
105
- end
106
-
107
- def restock_redis_script
108
- <<~LUA
109
- local keys = KEYS
110
- local quantities = ARGV
111
-
112
- -- Apply restock updates
113
- for i, key in ipairs(keys) do
114
- redis.call('INCRBY', key, tonumber(quantities[i]))
115
- end
116
-
117
- return 1
118
- LUA
119
- end
120
-
121
- def schedule_sync_inventory(inventory_id_and_quantities)
122
- SpreeCmCommissioner::InventoryItemSyncerJob.perform_later(inventory_id_and_quantities:)
123
- end
124
- end
125
- end
126
- end
@@ -1,36 +0,0 @@
1
- module SpreeCmCommissioner
2
- module RedisStock
3
- class LineItemsCachedInventoryItemsBuilder
4
- attr_reader :line_item_ids
5
-
6
- def initialize(line_item_ids:)
7
- @line_item_ids = line_item_ids
8
- end
9
-
10
- # return list of inventory items group by :line_item_id:
11
- # {
12
- # 1: [ CachedInventoryItem(...), CachedInventoryItem(...) ],
13
- # 2: [ CachedInventoryItem(...), CachedInventoryItem(...) ],
14
- # }
15
- def call
16
- cached_inventory_items.group_by do |cached_inventory_item|
17
- line_item = line_items.find { |item| item.variant_id == cached_inventory_item.variant_id }
18
- line_item.id
19
- end
20
- end
21
-
22
- def cached_inventory_items
23
- @cached_inventory_items ||= SpreeCmCommissioner::RedisStock::CachedInventoryItemsBuilder.new(inventory_items)
24
- .call
25
- end
26
-
27
- def inventory_items
28
- @inventory_items ||= line_items.flat_map(&:inventory_items)
29
- end
30
-
31
- def line_items
32
- @line_items ||= Spree::LineItem.where(id: line_item_ids).includes(:inventory_items)
33
- end
34
- end
35
- end
36
- end
@@ -1,27 +0,0 @@
1
- module SpreeCmCommissioner
2
- module RedisStock
3
- class VariantCachedInventoryItemsBuilder
4
- attr_reader :variant_id, :from_date, :to_date
5
-
6
- def initialize(variant_id:, from_date: nil, to_date: nil)
7
- @variant_id = variant_id
8
- @from_date = from_date
9
- @to_date = to_date
10
- end
11
-
12
- # output: [ CachedInventoryItem(...), CachedInventoryItem(...) ]
13
- def call
14
- ::SpreeCmCommissioner::RedisStock::CachedInventoryItemsBuilder.new(inventory_items).call
15
- end
16
-
17
- def inventory_items
18
- variant = Spree::Variant.find(variant_id)
19
-
20
- inventory_items = variant.inventory_items
21
- inventory_items.where(inventory_date: from_date..to_date) if variant.permanent_stock?
22
-
23
- inventory_items
24
- end
25
- end
26
- end
27
- end
@@ -1,44 +0,0 @@
1
- # On top of SpreeCmCommissioner::Stock::AvailabilityChecker, this class does the same thing but in bulk check with redis.
2
- # Which mean, it check basic condition with db first, once pass, it check with redis.
3
- module SpreeCmCommissioner
4
- module Stock
5
- class OrderAvailabilityChecker
6
- attr_reader :order
7
-
8
- def initialize(order)
9
- @order = order
10
- end
11
-
12
- def can_supply_all?
13
- insufficient_stock_lines.empty?
14
- end
15
-
16
- def insufficient_stock_lines
17
- cached_inventory_items_group_by_line_item_id.map do |line_item_id, cached_inventory_items|
18
- line_item = order.line_items.find { |item| item.id == line_item_id }
19
- line_item unless sufficient_stock_for?(line_item, cached_inventory_items)
20
- end.compact
21
- end
22
-
23
- # {
24
- # 1: [ {inventory_key: "inventory:1", active: true, quantity_available: 5, inventory_item_id: 1, variant_id: 1}],
25
- # 2: [ {inventory_key: "inventory:2", active: true, quantity_available: 5, inventory_item_id: 2, variant_id: 2}],
26
- # }
27
- def cached_inventory_items_group_by_line_item_id
28
- @cached_inventory_items_group_by_line_item_id ||=
29
- ::SpreeCmCommissioner::RedisStock::LineItemsCachedInventoryItemsBuilder.new(
30
- line_item_ids: order.line_items.pluck(:id)
31
- ).call
32
- end
33
-
34
- def sufficient_stock_for?(line_item, cached_inventory_items)
35
- return false unless line_item.variant.available?
36
- return true unless line_item.variant.should_track_inventory?
37
- return true if line_item.variant.backorderable?
38
- return true if line_item.variant.need_confirmation?
39
-
40
- cached_inventory_items.all? { |item| item.active? && item.quantity_available >= line_item.quantity }
41
- end
42
- end
43
- end
44
- end
@@ -1,19 +0,0 @@
1
- module SpreeCmCommissioner
2
- class VariantRequestSchema < ApplicationRequestSchema
3
- params do
4
- required(:from_date).value(:date)
5
- required(:to_date).value(:date)
6
- required(:adult).value(:integer)
7
- required(:children).value(:integer)
8
- end
9
-
10
- rule(:from_date, :to_date) do
11
- from_date = values[:from_date]
12
- to_date = values[:to_date]
13
-
14
- key.failure(:must_be_in_future) if from_date < Time.zone.today
15
- key.failure(:must_be_later_than_start_date) if from_date > to_date
16
- key.failure(:stay_is_too_long) if (to_date - from_date).to_i > ENV.fetch('ACCOMMODATION_MAX_STAY_DAYS', 10).to_i
17
- end
18
- end
19
- end