spree_cm_commissioner 1.21.0 → 2.0.0.pre.pre

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 (241) hide show
  1. checksums.yaml +4 -4
  2. data/.env.example +0 -3
  3. data/.github/workflows/test_and_build_gem.yml +16 -2
  4. data/.gitignore +2 -1
  5. data/Gemfile.lock +28 -1
  6. data/Rakefile +33 -4
  7. data/app/assets/javascripts/spree_cm_commissioner/backend.js +0 -1
  8. data/app/assets/stylesheets/spree_cm_commissioner/backend/calendar.scss +8 -11
  9. data/app/assets/stylesheets/spree_cm_commissioner/backend/commissioner_admin.css.scss +0 -1
  10. data/app/controllers/concerns/spree_cm_commissioner/content_cachable.rb +1 -2
  11. data/app/controllers/spree/admin/event_blazer_queries_controller.rb +4 -11
  12. data/app/controllers/spree/admin/inventory_items_controller.rb +83 -0
  13. data/app/controllers/spree/admin/stock_managements_controller.rb +63 -1
  14. data/app/controllers/spree/admin/tenants_controller.rb +0 -8
  15. data/app/controllers/spree/api/v2/organizer/invite_guests_controller.rb +1 -1
  16. data/app/controllers/spree/api/v2/storefront/accommodations/variants_controller.rb +42 -0
  17. data/app/controllers/spree/api/v2/storefront/accommodations_controller.rb +14 -31
  18. data/app/controllers/spree/api/v2/storefront/dynamic_field_options_controller.rb +1 -5
  19. data/app/controllers/spree/api/v2/storefront/guests_controller.rb +5 -31
  20. data/app/controllers/spree/api/v2/storefront/queue_cart/line_items_controller.rb +2 -2
  21. data/app/controllers/spree/api/v2/tenant/checkout_controller.rb +0 -2
  22. data/app/controllers/spree_cm_commissioner/admin/variants_controller_decorator.rb +1 -1
  23. data/app/controllers/spree_cm_commissioner/api/v2/storefront/checkout_controller_decorator.rb +0 -11
  24. data/app/controllers/spree_cm_commissioner/well_known_controller.rb +17 -31
  25. data/app/factory/spree_cm_commissioner/invite_guest_claimed_telegram_message_factory.rb +84 -37
  26. data/app/finders/spree_cm_commissioner/accommodations/find.rb +37 -0
  27. data/app/finders/spree_cm_commissioner/accommodations/find_variant.rb +32 -0
  28. data/app/interactors/spree_cm_commissioner/ensure_correct_product_type.rb +40 -0
  29. data/app/interactors/spree_cm_commissioner/inventory_item_syncer.rb +25 -0
  30. data/app/interactors/spree_cm_commissioner/pin_code_sender.rb +1 -0
  31. data/app/interactors/spree_cm_commissioner/sms.rb +1 -1
  32. data/app/interactors/spree_cm_commissioner/stock/inventory_item_resetter.rb +44 -0
  33. data/app/interactors/spree_cm_commissioner/stock/inventory_items_adjuster.rb +13 -0
  34. data/app/interactors/spree_cm_commissioner/stock/inventory_items_generator.rb +15 -0
  35. data/app/interactors/spree_cm_commissioner/stock/permanent_inventory_items_generator.rb +75 -0
  36. data/app/interactors/spree_cm_commissioner/stock/stock_movement_creator.rb +32 -0
  37. data/app/interactors/spree_cm_commissioner/user_id_token_authenticator.rb +3 -7
  38. data/app/interactors/spree_cm_commissioner/user_id_token_checker.rb +3 -11
  39. data/app/interactors/spree_cm_commissioner/user_identity_checker.rb +6 -12
  40. data/app/interactors/spree_cm_commissioner/user_registration_with_id_token.rb +1 -7
  41. data/app/interactors/spree_cm_commissioner/vattanac_bank_initiator.rb +1 -5
  42. data/app/jobs/spree_cm_commissioner/application_job.rb +20 -0
  43. data/app/jobs/spree_cm_commissioner/application_unique_job.rb +20 -0
  44. data/app/jobs/spree_cm_commissioner/ensure_correct_product_type_job.rb +7 -0
  45. data/app/jobs/spree_cm_commissioner/inventory_item_syncer_job.rb +7 -0
  46. data/app/jobs/spree_cm_commissioner/sms_pin_code_job.rb +1 -1
  47. data/app/jobs/spree_cm_commissioner/stock/inventory_items_adjuster_job.rb +11 -0
  48. data/app/jobs/spree_cm_commissioner/stock/inventory_items_generator_job.rb +11 -0
  49. data/app/jobs/spree_cm_commissioner/stock/permanent_inventory_items_generator_job.rb +9 -0
  50. data/app/models/concerns/spree_cm_commissioner/line_item_durationable.rb +9 -15
  51. data/app/models/concerns/spree_cm_commissioner/option_type_attr_type.rb +1 -13
  52. data/app/models/concerns/spree_cm_commissioner/order_seatable.rb +44 -0
  53. data/app/models/concerns/spree_cm_commissioner/order_state_machine.rb +39 -0
  54. data/app/models/concerns/spree_cm_commissioner/product_delegation.rb +1 -3
  55. data/app/models/concerns/spree_cm_commissioner/product_type.rb +10 -0
  56. data/app/models/concerns/spree_cm_commissioner/taxon_kind.rb +1 -1
  57. data/app/models/concerns/spree_cm_commissioner/tenant_preference.rb +0 -4
  58. data/app/models/spree_cm_commissioner/block.rb +23 -0
  59. data/app/models/spree_cm_commissioner/dynamic_field.rb +3 -17
  60. data/app/models/spree_cm_commissioner/dynamic_field_option.rb +1 -7
  61. data/app/models/spree_cm_commissioner/guest.rb +19 -1
  62. data/app/models/spree_cm_commissioner/guest_dynamic_field.rb +3 -21
  63. data/app/models/spree_cm_commissioner/inventory.rb +11 -0
  64. data/app/models/spree_cm_commissioner/inventory_item.rb +69 -0
  65. data/app/models/spree_cm_commissioner/invite.rb +6 -4
  66. data/app/models/spree_cm_commissioner/invite_team.rb +1 -3
  67. data/app/models/spree_cm_commissioner/line_item_decorator.rb +46 -78
  68. data/app/models/spree_cm_commissioner/notification_taxon.rb +1 -1
  69. data/app/models/spree_cm_commissioner/option_type_decorator.rb +1 -11
  70. data/app/models/spree_cm_commissioner/order_decorator.rb +30 -1
  71. data/app/models/spree_cm_commissioner/pin_code.rb +2 -3
  72. data/app/models/spree_cm_commissioner/place.rb +1 -3
  73. data/app/models/spree_cm_commissioner/price_decorator.rb +9 -0
  74. data/app/models/spree_cm_commissioner/product_decorator.rb +10 -4
  75. data/app/models/spree_cm_commissioner/redis_stock/cached_inventory_items_builder.rb +41 -0
  76. data/app/models/spree_cm_commissioner/redis_stock/inventory_updater.rb +126 -0
  77. data/app/models/spree_cm_commissioner/redis_stock/line_items_cached_inventory_items_builder.rb +36 -0
  78. data/app/models/spree_cm_commissioner/redis_stock/variant_cached_inventory_items_builder.rb +25 -0
  79. data/app/models/spree_cm_commissioner/reserved_block.rb +30 -0
  80. data/app/models/spree_cm_commissioner/seat_layout.rb +20 -0
  81. data/app/models/spree_cm_commissioner/seat_section.rb +16 -0
  82. data/app/models/spree_cm_commissioner/seats/blocks_canceler.rb +30 -0
  83. data/app/models/spree_cm_commissioner/seats/blocks_holder.rb +53 -0
  84. data/app/models/spree_cm_commissioner/seats/blocks_reserver.rb +49 -0
  85. data/app/models/spree_cm_commissioner/seats/errors/blocks_are_on_hold_by_other_guest.rb +4 -0
  86. data/app/models/spree_cm_commissioner/seats/errors/blocks_are_reserved_by_other_guest.rb +4 -0
  87. data/app/models/spree_cm_commissioner/seats/errors/blocks_are_reserved_by_same_guest.rb +4 -0
  88. data/app/models/spree_cm_commissioner/seats/errors/unable_to_save_reserved_block_record.rb +4 -0
  89. data/app/models/spree_cm_commissioner/service_calendar.rb +0 -2
  90. data/app/models/spree_cm_commissioner/state_decorator.rb +0 -1
  91. data/app/models/spree_cm_commissioner/stock/availability_checker.rb +26 -25
  92. data/app/models/spree_cm_commissioner/stock/availability_validator_decorator.rb +2 -1
  93. data/app/models/spree_cm_commissioner/stock/line_item_availability_checker.rb +3 -3
  94. data/app/models/spree_cm_commissioner/stock/order_availability_checker.rb +44 -0
  95. data/app/models/spree_cm_commissioner/stock_item_decorator.rb +17 -0
  96. data/app/models/spree_cm_commissioner/taxon_blazer_query.rb +8 -0
  97. data/app/models/spree_cm_commissioner/taxon_decorator.rb +2 -6
  98. data/app/models/spree_cm_commissioner/taxonomy_decorator.rb +0 -6
  99. data/app/models/spree_cm_commissioner/trip.rb +12 -10
  100. data/app/models/spree_cm_commissioner/trip_connection.rb +5 -5
  101. data/app/models/spree_cm_commissioner/trip_stop.rb +2 -3
  102. data/app/models/spree_cm_commissioner/user_identity_provider.rb +4 -26
  103. data/app/models/spree_cm_commissioner/user_taxon.rb +0 -1
  104. data/app/models/spree_cm_commissioner/variant_block.rb +9 -0
  105. data/app/models/spree_cm_commissioner/variant_decorator.rb +36 -47
  106. data/app/models/spree_cm_commissioner/variant_options.rb +0 -23
  107. data/app/models/spree_cm_commissioner/vehicle.rb +9 -14
  108. data/app/models/spree_cm_commissioner/vendor_decorator.rb +7 -10
  109. data/app/models/spree_cm_commissioner/vendor_place.rb +6 -5
  110. data/app/models/spree_cm_commissioner/vendor_stop.rb +1 -2
  111. data/app/overrides/spree/admin/variants/_form/kyc_field.html.erb.deface +2 -2
  112. data/app/queries/spree_cm_commissioner/guest_searcher_query.rb +3 -45
  113. data/app/queries/spree_cm_commissioner/trip_query.rb +23 -26
  114. data/app/request_schemas/spree_cm_commissioner/accommodation_request_schema.rb +3 -0
  115. data/app/request_schemas/spree_cm_commissioner/application_request_schema.rb +1 -1
  116. data/app/request_schemas/spree_cm_commissioner/variant_request_schema.rb +19 -0
  117. data/app/serializers/spree/v2/storefront/accommodation_serializer.rb +2 -0
  118. data/app/serializers/spree/v2/storefront/line_item_serializer_decorator.rb +1 -0
  119. data/app/serializers/spree/v2/tenant/user_serializer.rb +0 -1
  120. data/app/serializers/spree_cm_commissioner/v2/operator/dashboard_crew_event_serializer.rb +1 -4
  121. data/app/serializers/spree_cm_commissioner/v2/storefront/dynamic_field_option_serializer.rb +3 -1
  122. data/app/serializers/spree_cm_commissioner/v2/storefront/dynamic_field_serializer.rb +3 -1
  123. data/app/serializers/spree_cm_commissioner/v2/storefront/guest_dynamic_field_serializer.rb +1 -2
  124. data/app/services/spree_cm_commissioner/organizer/export_guest_csv_service.rb +15 -23
  125. data/app/services/spree_cm_commissioner/user_authenticator.rb +1 -1
  126. data/app/views/blazer/queries/_content.html.erb +0 -8
  127. data/app/views/spree/admin/inventory_items/show.html.erb +72 -0
  128. data/app/views/spree/admin/stock_managements/_variant_stock_items.html.erb +7 -2
  129. data/app/views/spree/admin/stock_managements/calendar.html.erb +56 -0
  130. data/app/views/spree/admin/stock_managements/index.html.erb +55 -6
  131. data/app/views/spree/admin/tenants/_form.html.erb +42 -109
  132. data/config/initializers/paper_trail.rb +1 -0
  133. data/config/initializers/spree_permitted_attributes.rb +5 -2
  134. data/config/locales/en.yml +0 -5
  135. data/config/routes.rb +22 -6
  136. data/db/migrate/20240202080634_update_counter_cache_of_vehicle_type.rb +3 -1
  137. data/db/migrate/20250304293518_create_cm_inventory_items.rb +21 -0
  138. data/db/migrate/20250429094228_add_lock_version_to_cm_inventory_items.rb +5 -0
  139. data/db/migrate/20250502025848_add_index_to_spree_products.rb +5 -0
  140. data/db/migrate/20250502030001_add_product_type_to_spree_variants.rb +5 -0
  141. data/db/migrate/20250502030002_add_product_type_to_spree_line_items.rb +5 -0
  142. data/db/migrate/20250603035256_add_inventory_item_to_spree_prices.rb +7 -0
  143. data/db/migrate/20250619073724_drop_table_cm_line_item_seats.rb +5 -0
  144. data/db/migrate/20250619073812_drop_table_cm_vehicle_seats.rb +5 -0
  145. data/db/migrate/20250619073844_drop_table_cm_vehicle_types.rb +9 -0
  146. data/db/migrate/20250619073957_drop_table_cm_option_value_vehicle_types.rb +5 -0
  147. data/db/migrate/20250619082354_remove_unnecessary_fields_from_cm_places.rb +9 -0
  148. data/db/migrate/20250619082736_remove_route_type_from_spree_products.rb +5 -0
  149. data/db/migrate/20250619083055_remove_unnecessary_fields_from_spree_taxons.rb +5 -0
  150. data/db/migrate/20250620083055_remove_variant_id_from_cm_trips.rb +5 -0
  151. data/db/migrate/20250620090000_update_cm_trip_connections_to_use_cm_trips.rb +6 -0
  152. data/db/migrate/20250620090001_create_cm_seat_layouts.rb +17 -0
  153. data/db/migrate/20250620090002_create_cm_seat_sections.rb +18 -0
  154. data/db/migrate/20250620090003_create_cm_blocks.rb +18 -0
  155. data/db/migrate/20250624091005_create_cm_reserved_blocks.rb +29 -0
  156. data/db/migrate/20250626083642_create_cm_variant_blocks.rb +24 -0
  157. data/db/migrate/20250627023314_add_block_id_to_cm_guests.rb +13 -0
  158. data/docker-compose.yml +1 -1
  159. data/lib/cm_app_logger.rb +11 -4
  160. data/lib/generators/spree_cm_commissioner/install/install_generator.rb +14 -11
  161. data/lib/generators/spree_cm_commissioner/install/templates/app/javascript/{spree_cm_commissioner → spree_dashboard/spree_cm_commissioner}/utilities.js +4 -0
  162. data/lib/spree_cm_commissioner/cached_inventory_item.rb +23 -0
  163. data/lib/spree_cm_commissioner/calendar_event.rb +11 -1
  164. data/lib/spree_cm_commissioner/test_helper/factories/block_factory.rb +9 -0
  165. data/lib/spree_cm_commissioner/test_helper/factories/guest_factory.rb +10 -0
  166. data/lib/spree_cm_commissioner/test_helper/factories/homepage_section_relatable_factory.rb +1 -1
  167. data/lib/spree_cm_commissioner/test_helper/factories/inventory_item_factory.rb +9 -0
  168. data/lib/spree_cm_commissioner/test_helper/factories/line_item_factory.rb +1 -1
  169. data/lib/spree_cm_commissioner/test_helper/factories/option_type_factory.rb +6 -30
  170. data/lib/spree_cm_commissioner/test_helper/factories/order_factory.rb +0 -36
  171. data/lib/spree_cm_commissioner/test_helper/factories/product_factory.rb +18 -34
  172. data/lib/spree_cm_commissioner/test_helper/factories/reserved_block_factory.rb +27 -0
  173. data/lib/spree_cm_commissioner/test_helper/factories/seat_layout_factory.rb +8 -0
  174. data/lib/spree_cm_commissioner/test_helper/factories/seat_section_factory.rb +8 -0
  175. data/lib/spree_cm_commissioner/test_helper/factories/stock_location_factory.rb +2 -2
  176. data/lib/spree_cm_commissioner/test_helper/factories/trip_connection_factory.rb +6 -0
  177. data/lib/spree_cm_commissioner/test_helper/factories/trip_factory.rb +10 -3
  178. data/lib/spree_cm_commissioner/test_helper/factories/user_identity_provider_factory.rb +1 -1
  179. data/lib/spree_cm_commissioner/test_helper/factories/variant_block_factory.rb +7 -0
  180. data/lib/spree_cm_commissioner/test_helper/factories/variant_factory.rb +41 -19
  181. data/lib/spree_cm_commissioner/test_helper/factories/vehicle_factory.rb +1 -1
  182. data/lib/spree_cm_commissioner/test_helper/factories/vendor_factory.rb +1 -1
  183. data/lib/spree_cm_commissioner/version.rb +1 -1
  184. data/lib/spree_cm_commissioner.rb +35 -1
  185. data/lib/tasks/create_default_non_permanent_inventory_items.rake +16 -0
  186. data/lib/tasks/ensure_correct_product_type.rake +7 -0
  187. data/lib/tasks/generate_inventory_items.rake +7 -0
  188. data/spree_cm_commissioner.gemspec +7 -0
  189. metadata +135 -57
  190. data/app/assets/images/cm-hangmeas-checkout_image.svg +0 -63
  191. data/app/assets/images/cm-hangmeas-failed.svg +0 -56
  192. data/app/assets/images/cm-hangmeas-loader.svg +0 -50
  193. data/app/assets/images/cm-hangmeas-success.svg +0 -51
  194. data/app/assets/javascripts/spree_cm_commissioner/tenant_payment_icon_fields.js +0 -65
  195. data/app/assets/stylesheets/spree_cm_commissioner/backend/tenant_payment_icon_fields.scss +0 -60
  196. data/app/controllers/concerns/spree_cm_commissioner/transit/taxon_bitwise.rb +0 -44
  197. data/app/controllers/spree/admin/trip_blazer_queries_controller.rb +0 -35
  198. data/app/controllers/spree/api/v2/organizer/invite_crews_controller.rb +0 -33
  199. data/app/finders/spree_cm_commissioner/line_items/find_by_variant_decorator.rb +0 -20
  200. data/app/interactors/spree_cm_commissioner/apple_app_site_association_fetcher.rb +0 -27
  201. data/app/interactors/spree_cm_commissioner/asset_links_fetcher.rb +0 -27
  202. data/app/interactors/spree_cm_commissioner/crew_invite_link_handler.rb +0 -48
  203. data/app/interactors/spree_cm_commissioner/telegram_chats_auto_finder.rb +0 -144
  204. data/app/models/concerns/spree_cm_commissioner/event_check_in_flowable.rb +0 -30
  205. data/app/models/spree_cm_commissioner/blazer_queryable.rb +0 -8
  206. data/app/models/spree_cm_commissioner/branch.rb +0 -12
  207. data/app/models/spree_cm_commissioner/crew_invite_link.rb +0 -4
  208. data/app/models/spree_cm_commissioner/line_item_seat.rb +0 -10
  209. data/app/models/spree_cm_commissioner/option_value_vehicle_type.rb +0 -8
  210. data/app/models/spree_cm_commissioner/pin_code_telegram.rb +0 -28
  211. data/app/models/spree_cm_commissioner/promotion_category_decorator.rb +0 -11
  212. data/app/models/spree_cm_commissioner/stop.rb +0 -23
  213. data/app/models/spree_cm_commissioner/telegram_chat.rb +0 -6
  214. data/app/models/spree_cm_commissioner/vehicle_seat.rb +0 -11
  215. data/app/models/spree_cm_commissioner/vehicle_type.rb +0 -76
  216. data/app/overrides/spree/admin/taxons/_form/check_in_flows.html.erb.deface +0 -18
  217. data/app/queries/spree_cm_commissioner/trip_search_query.rb +0 -76
  218. data/app/queries/spree_cm_commissioner/variant_availability/non_permanent_stock_query.rb +0 -45
  219. data/app/queries/spree_cm_commissioner/variant_availability/permanent_stock_query.rb +0 -55
  220. data/app/serializers/spree/v2/organizer/invite_crews_serializer.rb +0 -11
  221. data/app/services/spree_cm_commissioner/vehicle_option_value_creator.rb +0 -11
  222. data/db/migrate/20250616084219_add_description_to_cm_vendor_place.rb +0 -5
  223. data/db/migrate/20250630103536_create_cm_telegram_chats.rb +0 -13
  224. data/db/migrate/20250701093203_add_configurations_to_cm_dynamic_field.rb +0 -6
  225. data/db/migrate/20250702091305_add_dynamic_field_option_to_guest_dynamic_field.rb +0 -5
  226. data/db/migrate/20250702091935_add_status_to_dynamic_field_option.rb +0 -5
  227. data/db/migrate/20250707032008_add_vendor_id_to_spree_category.rb +0 -7
  228. data/db/migrate/20250711092937_add_position_to_cm_dynamic_fields.rb +0 -11
  229. data/db/migrate/20250711093045_add_position_to_cm_dynamic_field_options.rb +0 -11
  230. data/db/migrate/20250714121508_rename_cm_taxon_blazer_query_to_blazer_queryables.rb +0 -7
  231. data/db/migrate/20250714124323_make_cm_blazer_queryables_polymorphic.rb +0 -19
  232. data/db/migrate/20250715103333_remove_indexes_from_cm_user_identity_providers.rb +0 -13
  233. data/lib/spree_cm_commissioner/test_helper/factories/branch_factory.rb +0 -12
  234. data/lib/spree_cm_commissioner/test_helper/factories/departure_time_option_type_factory.rb +0 -8
  235. data/lib/spree_cm_commissioner/test_helper/factories/duration_option_type_factory.rb +0 -8
  236. data/lib/spree_cm_commissioner/test_helper/factories/line_item_seat_factory.rb +0 -7
  237. data/lib/spree_cm_commissioner/test_helper/factories/stop_factory.rb +0 -14
  238. data/lib/spree_cm_commissioner/test_helper/factories/transit_place_factory.rb +0 -8
  239. data/lib/spree_cm_commissioner/test_helper/factories/vehicle_option_type_factory.rb +0 -8
  240. data/lib/spree_cm_commissioner/test_helper/factories/vehicle_type_factory.rb +0 -96
  241. data/lib/spree_cm_commissioner/trip_seat_layout_result.rb +0 -11
@@ -0,0 +1,11 @@
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
@@ -0,0 +1,69 @@
1
+ module SpreeCmCommissioner
2
+ class InventoryItem < Base
3
+ include SpreeCmCommissioner::ProductType
4
+
5
+ # Association
6
+ belongs_to :variant, class_name: 'Spree::Variant', optional: false
7
+ has_many :prices, class_name: 'Spree::Price', inverse_of: :inventory_item
8
+
9
+ # Validation
10
+ validates :quantity_available, numericality: { greater_than_or_equal_to: 0 }
11
+ validates :max_capacity, numericality: { greater_than_or_equal_to: 0 } # Originally inventory of each variant.
12
+ validates :inventory_date, presence: true, if: :permanent_stock?
13
+ validates :variant_id, uniqueness: { scope: :inventory_date, message: -> (object, _data) { "The variant is taken on #{object.inventory_date}" } }
14
+
15
+ # Scope
16
+ scope :active, -> { where(inventory_date: nil).or(where('inventory_date >= ?', Time.zone.today)) }
17
+
18
+ before_save -> { self.product_type = variant.product_type }, if: -> { product_type.nil? }
19
+
20
+ # This method is only used when admin update stock
21
+ def adjust_quantity!(quantity)
22
+ with_lock do
23
+ self.max_capacity = [max_capacity + quantity, 0].max
24
+ self.quantity_available = [quantity_available + quantity, 0].max
25
+ save!
26
+
27
+ # When user has been searched or booked a product, it has cached the quantity in redis,
28
+ # So we need to update redis cache if inventory key has been created in redis
29
+ adjust_quantity_in_redis(quantity)
30
+ end
31
+ end
32
+
33
+ def redis_key
34
+ "inventory:#{id}"
35
+ end
36
+
37
+ def adjust_quantity_in_redis(quantity)
38
+ SpreeCmCommissioner.redis_pool.with do |redis|
39
+ cached_quantity_available = redis.get(redis_key)
40
+ # ignore if redis doesn't exist
41
+ return if cached_quantity_available.nil? # rubocop:disable Lint/NonLocalExitFromIterator
42
+
43
+ script = <<~LUA
44
+ local key = KEYS[1]
45
+ local increment = tonumber(ARGV[1])
46
+ local current = tonumber(redis.call('GET', key) or 0)
47
+ local new_value = current + increment
48
+ if new_value < 0 then
49
+ new_value = 0
50
+ end
51
+ redis.call('SET', key, new_value)
52
+ return new_value
53
+ LUA
54
+
55
+ redis.eval(script, keys: [redis_key], argv: [quantity])
56
+ end
57
+ end
58
+
59
+ def active?
60
+ inventory_date.nil? || inventory_date >= Time.zone.today
61
+ end
62
+
63
+ def redis_expired_in
64
+ expired_in = 31_536_000 # 1 year for normal stock
65
+ expired_in = Time.parse(inventory_date.to_s).end_of_day.to_i - Time.zone.now.to_i if inventory_date.present?
66
+ [expired_in, 0].max
67
+ end
68
+ end
69
+ end
@@ -5,12 +5,14 @@ module SpreeCmCommissioner
5
5
  has_many :invite_user_events, class_name: 'SpreeCmCommissioner::InviteUserEvent', dependent: :destroy
6
6
  scope :active, -> { where('expires_at > ?', Time.current) }
7
7
 
8
- def invite_url
9
- "#{Spree::Store.default.formatted_url}/invite/#{token}?utm_source=email"
8
+ after_create :set_expiration
9
+
10
+ def set_expiration
11
+ update(expires_at: 3.days.from_now)
10
12
  end
11
13
 
12
- def invite_crew_link
13
- "#{Spree::Store.default.formatted_url}/invite_crews/#{token}"
14
+ def invite_url
15
+ "#{Spree::Store.default.formatted_url}/invite/#{token}?utm_source=email"
14
16
  end
15
17
 
16
18
  def url_valid?
@@ -1,7 +1,5 @@
1
1
  module SpreeCmCommissioner
2
2
  class InviteTeam < SpreeCmCommissioner::Base
3
- validates :email, presence: true, uniqueness: { scope: :vendor_id, message: I18n.t('invite.already_invited') }
4
-
5
3
  belongs_to :user, class_name: 'Spree::User', optional: true
6
4
  belongs_to :inviter, class_name: 'Spree::User'
7
5
  belongs_to :vendor, class_name: 'Spree::Vendor'
@@ -36,7 +34,7 @@ module SpreeCmCommissioner
36
34
  end
37
35
 
38
36
  def invitation_link
39
- "#{ENV.fetch('ORGANIZER_URL')}/invite_teams/#{token}?utm_source=email"
37
+ "http://#{ENV.fetch('APP_HOST')}/organizer/invite_teams/#{token}?utm_source=email"
40
38
  end
41
39
 
42
40
  def url_valid?
@@ -1,6 +1,6 @@
1
1
  module SpreeCmCommissioner
2
2
  module LineItemDecorator
3
- def self.prepended(base) # rubocop:disable Metrics/MethodLength
3
+ def self.prepended(base) # rubocop:disable Metrics/MethodLength,Metrics/AbcSize
4
4
  include_modules(base)
5
5
 
6
6
  base.belongs_to :accepter, class_name: 'Spree::User', optional: true
@@ -10,31 +10,32 @@ module SpreeCmCommissioner
10
10
  base.has_one :google_wallet, class_name: 'SpreeCmCommissioner::GoogleWallet', through: :product
11
11
 
12
12
  base.has_many :option_types, through: :product
13
+
14
+ base.has_many :inventory_items, lambda { |line_item|
15
+ where(inventory_date: nil).or(where(inventory_date: line_item.date_range))
16
+ }, through: :variant
17
+
13
18
  base.has_many :taxons, class_name: 'Spree::Taxon', through: :product
14
19
  base.has_many :guests, class_name: 'SpreeCmCommissioner::Guest', dependent: :destroy
20
+ base.has_many :guests_with_blocks, -> { where.not(block_id: nil) }, class_name: 'SpreeCmCommissioner::Guest'
21
+ base.has_many :reserved_blocks, through: :guests, class_name: 'SpreeCmCommissioner::ReservedBlock'
15
22
  base.has_many :pending_guests, pending_guests_query, class_name: 'SpreeCmCommissioner::Guest', dependent: :destroy
16
23
  base.has_many :product_completion_steps, class_name: 'SpreeCmCommissioner::ProductCompletionStep', through: :product
17
- base.has_many :line_item_seats, class_name: 'SpreeCmCommissioner::LineItemSeat', dependent: :destroy
18
24
 
19
25
  base.before_save :update_vendor_id
20
- base.before_save :update_quantity, if: :transit?
21
-
22
- base.validate :validate_seats_reservation, if: :transit?
23
26
 
24
27
  base.before_create :add_due_date, if: :subscription?
28
+ base.before_save -> { self.product_type = variant.product_type }, if: -> { product_type.nil? }
25
29
 
26
30
  base.validate :ensure_not_exceed_max_quantity_per_order, if: -> { variant&.max_quantity_per_order.present? }
27
31
 
28
32
  base.whitelisted_ransackable_associations |= %w[guests order]
29
33
  base.whitelisted_ransackable_attributes |= %w[number to_date from_date vendor_id]
30
34
 
31
- base.delegate :delivery_required?, :permanent_stock?, :high_demand, :transit?,
35
+ base.delegate :delivery_required?, :high_demand,
32
36
  to: :variant
33
- base.delegate :discontinue_on, :product_type, :accommodation?, :service?, :ecommerce?, :need_confirmation,
34
- to: :product
35
37
 
36
38
  base.accepts_nested_attributes_for :guests, allow_destroy: true
37
- base.accepts_nested_attributes_for :line_item_seats, allow_destroy: true
38
39
 
39
40
  def base.json_api_columns
40
41
  json_api_columns = column_names.reject { |c| c.match(/_id$|id|preferences|(.*)password|(.*)token|(.*)api_key/) }
@@ -42,6 +43,10 @@ module SpreeCmCommissioner
42
43
  json_api_columns << :vendor_id
43
44
  end
44
45
 
46
+ def discontinue_on
47
+ variant.discontinue_on || product.discontinue_on
48
+ end
49
+
45
50
  def base.search_by_qr_data!(data)
46
51
  matches = data.match(/(R\d+)-([A-Za-z0-9_\-]+)-(L\d+)/)&.captures
47
52
 
@@ -60,6 +65,7 @@ module SpreeCmCommissioner
60
65
  base.include SpreeCmCommissioner::LineItemDurationable
61
66
  base.include SpreeCmCommissioner::LineItemsFilterScope
62
67
  base.include SpreeCmCommissioner::LineItemGuestsConcern
68
+ base.include SpreeCmCommissioner::ProductType
63
69
  base.include SpreeCmCommissioner::ProductDelegation
64
70
  base.include SpreeCmCommissioner::KycBitwise
65
71
  end
@@ -75,24 +81,39 @@ module SpreeCmCommissioner
75
81
  }
76
82
  end
77
83
 
78
- def reservation?
79
- date_present? && !subscription?
80
- end
84
+ # override to calculate price per date for permanent stock
85
+ def update_price_from_modifier(currency, opts)
86
+ self.currency = currency
81
87
 
82
- # date_unit could be number of nights, or days or hours depending on usecases
83
- # For example:
84
- # - accomodation uses number of nights.
85
- # - appointment uses number of hours, etc.
86
- #
87
- # override
88
- def amount
89
- base_price = price * quantity
88
+ # these fields can be null during this process below.
89
+ # it is needed for permanent_stock?, date_range to work.
90
+ self.product_type = variant.product_type
91
+ self.from_date = opts.delete(:from_date)&.to_date
92
+ self.to_date = opts.delete(:to_date)&.to_date
90
93
 
91
- if permanent_stock? && date_unit.present?
92
- base_price * date_unit
93
- else
94
- base_price
95
- end
94
+ base_price = variant.price_in(currency)
95
+
96
+ self.price = if permanent_stock?
97
+ date_range.map do |date|
98
+ selected_price = variant.price_for_date(date: date, currency: currency) || base_price
99
+ (selected_price.amount || 0) + variant.price_modifier_amount_in(currency, opts.merge(date: date))
100
+ end.sum
101
+ else
102
+ (base_price.amount || 0) + variant.price_modifier_amount_in(currency, opts)
103
+ end
104
+ end
105
+
106
+ # override to calculate price per date for permanent stock
107
+ def update_price
108
+ base_price = variant.price_in(order.currency)
109
+ self.price = if permanent_stock?
110
+ date_range.map do |date|
111
+ selected_price = variant.price_for_date(date: date, currency: order.currency) || base_price
112
+ selected_price.amount || 0
113
+ end.sum
114
+ else
115
+ base_price.amount || 0
116
+ end
96
117
  end
97
118
 
98
119
  def allowed_self_check_in?
@@ -179,8 +200,6 @@ module SpreeCmCommissioner
179
200
 
180
201
  # override
181
202
  def sufficient_stock?
182
- return transit_sufficient_stock? if variant.product.product_type == 'transit'
183
-
184
203
  SpreeCmCommissioner::Stock::LineItemAvailabilityChecker.new(self).can_supply?(quantity)
185
204
  end
186
205
 
@@ -199,18 +218,6 @@ module SpreeCmCommissioner
199
218
  errors.add(:quantity, 'exceeded_max_quantity_per_order') if quantity > variant.max_quantity_per_order
200
219
  end
201
220
 
202
- def transit_sufficient_stock?
203
- return selected_seats_available? if reservation_trip.allow_seat_selection
204
-
205
- seat_quantity_available?(reservation_trip)
206
- end
207
-
208
- def update_quantity
209
- return if line_item_seats.blank?
210
-
211
- self.quantity = line_item_seats.size
212
- end
213
-
214
221
  def update_vendor_id
215
222
  self.vendor_id = variant.vendor_id
216
223
  end
@@ -233,45 +240,6 @@ module SpreeCmCommissioner
233
240
 
234
241
  from_date + variant.month.month + day.days
235
242
  end
236
-
237
- def validate_seats_reservation
238
- if reservation_trip.allow_seat_selection && !selected_seats_available?
239
- errors.add(:base, :some_seats_are_booked, message: 'Some seats are already booked')
240
- elsif !reservation_trip.allow_seat_selection && !seat_quantity_available?(reservation_trip)
241
- errors.add(:quantity, :exceeded_available_quantity, message: 'exceeded available quantity')
242
- end
243
- end
244
-
245
- def selected_seats_available?
246
- selected_seat_ids = line_item_seats.map(&:seat_id)
247
- !selected_seat_ids_occupied?(selected_seat_ids)
248
- end
249
-
250
- def seat_quantity_available?(trip)
251
- booked_quantity = Spree::LineItem.joins(:order)
252
- .where(variant_id: variant_id, date: date, spree_orders: { state: 'complete' })
253
- .where.not(spree_line_items: { id: id })
254
- .sum(:quantity)
255
- remaining_quantity = trip.vehicle.number_of_seats - booked_quantity
256
- remaining_quantity >= quantity
257
- end
258
-
259
- def reservation_trip
260
- return @trip if defined? @trip
261
-
262
- route = Spree::Variant.find_by(id: variant_id).product
263
- @trip = route.trip
264
- end
265
-
266
- def selected_seat_ids_occupied?(selected_seat_ids)
267
- # check to see if there are any selected_ids exist in the line_item_seats and belongs to completed order
268
- SpreeCmCommissioner::LineItemSeat.joins(line_item: :order)
269
- .where(seat_id: selected_seat_ids, date: date, variant_id: variant_id, spree_orders: { state: 'complete',
270
- canceled_at: nil
271
- }
272
- )
273
- .present?
274
- end
275
243
  end
276
244
  end
277
245
 
@@ -1,5 +1,5 @@
1
1
  module SpreeCmCommissioner
2
- class NotificationTaxon < SpreeCmCommissioner::Base
2
+ class NotificationTaxon < Base
3
3
  belongs_to :customer_notification, class_name: 'SpreeCmCommissioner::CustomerNotification'
4
4
  belongs_to :taxon, class_name: 'Spree::Taxon'
5
5
  end
@@ -6,7 +6,7 @@ module SpreeCmCommissioner
6
6
  base.include SpreeCmCommissioner::ParameterizeName
7
7
  base.include SpreeCmCommissioner::OptionTypeAttrType
8
8
 
9
- base.enum kind: %i[variant product vendor vehicle_type transit]
9
+ base.enum kind: %i[variant product vendor vehicle]
10
10
 
11
11
  base.validates :name, presence: true
12
12
 
@@ -19,16 +19,6 @@ module SpreeCmCommissioner
19
19
  base.scope :promoted, -> { where(promoted: true) }
20
20
  base.whitelisted_ransackable_attributes = %w[kind]
21
21
 
22
- def base.amenities
23
- Spree::OptionType.where(kind: 'vehicle_type', name: 'amenities', presentation: 'Amenities', attr_type: 'amenity').first_or_create
24
- end
25
-
26
- def base.vehicle
27
- Spree::OptionType.where(presentation: 'vehicle', attr_type: 'vehicle_id', kind: 'variant',
28
- name: 'vehicle'
29
- ).first_or_create
30
- end
31
-
32
22
  def base.rules_option_type
33
23
  Spree::OptionType.find_by(name: RULES_OPTION_TYPE_NAME)
34
24
  end
@@ -2,6 +2,7 @@ module SpreeCmCommissioner
2
2
  module OrderDecorator
3
3
  def self.prepended(base) # rubocop:disable Metrics/MethodLength,Metrics/AbcSize
4
4
  base.include SpreeCmCommissioner::PhoneNumberSanitizer
5
+ base.include SpreeCmCommissioner::OrderSeatable
5
6
  base.include SpreeCmCommissioner::OrderStateMachine
6
7
 
7
8
  base.scope :subscription, -> { where.not(subscription_id: nil) }
@@ -42,6 +43,8 @@ module SpreeCmCommissioner
42
43
  base.has_many :vendors, through: :products, class_name: 'Spree::Vendor'
43
44
  base.has_many :taxons, through: :products, class_name: 'Spree::Taxon'
44
45
  base.has_many :guests, through: :line_items, class_name: 'SpreeCmCommissioner::Guest'
46
+ base.has_many :blocks, through: :guests, class_name: 'SpreeCmCommissioner::Block', source: :block
47
+ base.has_many :reserved_blocks, through: :guests, class_name: 'SpreeCmCommissioner::ReservedBlock'
45
48
  base.has_many :guest_card_classes, class_name: 'SpreeCmCommissioner::GuestCardClass', through: :variants
46
49
 
47
50
  base.delegate :customer, to: :user, allow_nil: true
@@ -58,6 +61,13 @@ module SpreeCmCommissioner
58
61
  end
59
62
  end
60
63
 
64
+ # override
65
+ # spree use this method to check stock availability & consider whether :order can continue to next state.
66
+ def insufficient_stock_lines
67
+ checker = SpreeCmCommissioner::Stock::OrderAvailabilityChecker.new(self)
68
+ checker.insufficient_stock_lines
69
+ end
70
+
61
71
  def ticket_seller_user?
62
72
  return false if user.nil?
63
73
 
@@ -106,7 +116,14 @@ module SpreeCmCommissioner
106
116
  end
107
117
 
108
118
  def mark_as_archive
109
- update(archived_at: Time.current)
119
+ blocks_canceled = begin
120
+ cancel_blocks!
121
+ true
122
+ rescue StandardError
123
+ false
124
+ end
125
+
126
+ update(archived_at: Time.current) if blocks_canceled
110
127
  end
111
128
 
112
129
  # overrided
@@ -199,6 +216,18 @@ module SpreeCmCommissioner
199
216
 
200
217
  private
201
218
 
219
+ def unstock_inventory_in_redis!
220
+ CmAppLogger.log(label: "#{self.class.name}#unstock_inventory_in_redis!", data: { order_id: id }) do
221
+ SpreeCmCommissioner::RedisStock::InventoryUpdater.new(line_item_ids).unstock!
222
+ end
223
+ end
224
+
225
+ def restock_inventory_in_redis!
226
+ CmAppLogger.log(label: "#{self.class.name}#restock_inventory_in_redis!", data: { order_id: id }) do
227
+ SpreeCmCommissioner::RedisStock::InventoryUpdater.new(line_item_ids).restock!
228
+ end
229
+ end
230
+
202
231
  # override :spree_api
203
232
  def webhook_payload_body
204
233
  resource_serializer.new(
@@ -2,7 +2,7 @@ module SpreeCmCommissioner
2
2
  class PinCode < SpreeCmCommissioner::Base
3
3
  has_secure_token
4
4
 
5
- enum contact_type: { 'phone_number' => 0, 'email' => 1, 'telegram' => 2 }
5
+ enum contact_type: { 'phone_number' => 0, 'email' => 1 }
6
6
 
7
7
  validates :code, length: { maximum: 6 }
8
8
  validates :contact, presence: true
@@ -130,8 +130,7 @@ module SpreeCmCommissioner
130
130
  'SpreeCmCommissioner::PinCodeMobileConfirm' => I18n.t('pincode.readable_type.confirmation_code'),
131
131
  'SpreeCmCommissioner::PinCodeUpdateUserLogin' => I18n.t('pincode.readable_type.update_user_login'),
132
132
  'SpreeCmCommissioner::PinCodeEmailConfirm' => I18n.t('pincode.readable_type.confirmation_code'),
133
- 'SpreeCmCommissioner::OtpCode' => I18n.t('pincode.readable_type.otp_code'),
134
- 'SpreeCmCommissioner::PinCodeTelegram' => I18n.t('pincode.readable_type.telegram_verification')
133
+ 'SpreeCmCommissioner::OtpCode' => I18n.t('pincode.readable_type.otp_code')
135
134
  }
136
135
  end
137
136
 
@@ -1,7 +1,5 @@
1
- require_dependency 'spree_cm_commissioner'
2
-
3
1
  module SpreeCmCommissioner
4
- class Place < ApplicationRecord
2
+ class Place < Base
5
3
  acts_as_nested_set
6
4
 
7
5
  validates :reference, presence: true, if: :validate_reference?
@@ -0,0 +1,9 @@
1
+ module SpreeCmCommissioner
2
+ module PriceDecorator
3
+ def self.prepended(base)
4
+ base.belongs_to :inventory_item, class_name: 'SpreeCmCommissioner::InventoryItem', inverse_of: :prices, optional: true
5
+ end
6
+ end
7
+ end
8
+
9
+ Spree::Price.prepend(SpreeCmCommissioner::PriceDecorator) unless Spree::Price.included_modules.include?(SpreeCmCommissioner::PriceDecorator)
@@ -6,7 +6,6 @@ module SpreeCmCommissioner
6
6
  base.include SpreeCmCommissioner::KycBitwise
7
7
  base.include SpreeCmCommissioner::Metafield
8
8
  base.include SpreeCmCommissioner::TenantUpdatable
9
- base.include SpreeCmCommissioner::RouteType
10
9
 
11
10
  base.has_many :variant_kind_option_types, -> { where(kind: :variant).order(:position) },
12
11
  through: :product_option_types, source: :option_type
@@ -33,6 +32,7 @@ module SpreeCmCommissioner
33
32
  base.has_one :google_wallet, class_name: 'SpreeCmCommissioner::GoogleWallet', dependent: :destroy
34
33
 
35
34
  base.has_many :complete_line_items, through: :classifications, source: :line_items
35
+ base.has_many :inventory_items, through: :variants
36
36
  base.has_many :guests, through: :line_items
37
37
 
38
38
  base.has_many :product_places, class_name: 'SpreeCmCommissioner::ProductPlace', dependent: :destroy
@@ -64,12 +64,11 @@ module SpreeCmCommissioner
64
64
  base.before_validation :set_event_id
65
65
 
66
66
  base.validate :validate_event_taxons, if: -> { taxons.event.present? }
67
-
68
67
  base.validate :validate_product_date, if: -> { available_on.present? && discontinue_on.present? }
69
-
68
+ base.validate :product_type_unchanged, on: :update
70
69
  base.validates :commission_rate, numericality: { greater_than_or_equal_to: 0, less_than_or_equal_to: 100 }, allow_nil: true
71
70
 
72
- base.whitelisted_ransackable_attributes = %w[description name slug discontinue_on status vendor_id short_name route_type]
71
+ base.whitelisted_ransackable_attributes = %w[description name slug discontinue_on status vendor_id short_name]
73
72
 
74
73
  base.after_update :update_variants_vendor_id, if: :saved_change_to_vendor_id?
75
74
  base.after_update :sync_event_id_to_children, if: :saved_change_to_event_id?
@@ -114,6 +113,13 @@ module SpreeCmCommissioner
114
113
 
115
114
  errors.add(:discontinue_on, 'must be after the available on date')
116
115
  end
116
+
117
+ def product_type_unchanged
118
+ return if product_type_was.nil?
119
+ return unless product_type_changed?
120
+
121
+ errors.add(:product_type, 'cannot be changed once set')
122
+ end
117
123
  end
118
124
  end
119
125
 
@@ -0,0 +1,41 @@
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
+ return inventory_item.quantity_available unless inventory_item.active?
32
+
33
+ SpreeCmCommissioner.redis_pool.with do |redis|
34
+ redis.set(key, inventory_item.quantity_available, ex: inventory_item.redis_expired_in)
35
+ end
36
+
37
+ inventory_item.quantity_available
38
+ end
39
+ end
40
+ end
41
+ end
@@ -0,0 +1,126 @@
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