spree_cm_commissioner 1.10.0 → 1.11.0.pre.pre2

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 (97) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/test_and_build_gem.yml +17 -124
  3. data/.gitignore +2 -1
  4. data/.vscode/settings.json +1 -1
  5. data/Gemfile.lock +22 -1
  6. data/Rakefile +33 -4
  7. data/app/controllers/spree/admin/stock_managements_controller.rb +62 -1
  8. data/app/controllers/spree/api/v2/storefront/accommodations/variants_controller.rb +42 -0
  9. data/app/controllers/spree/api/v2/storefront/accommodations_controller.rb +14 -31
  10. data/app/controllers/spree/api/v2/storefront/queue_cart/line_items_controller.rb +2 -2
  11. data/app/finders/spree_cm_commissioner/accommodations/find.rb +40 -0
  12. data/app/finders/spree_cm_commissioner/accommodations/find_variant.rb +35 -0
  13. data/app/interactors/spree_cm_commissioner/create_event.rb +23 -0
  14. data/app/interactors/spree_cm_commissioner/ensure_correct_product_type.rb +40 -0
  15. data/app/interactors/spree_cm_commissioner/inventory_item_syncer.rb +25 -0
  16. data/app/interactors/spree_cm_commissioner/stock/inventory_items_adjuster.rb +13 -0
  17. data/app/interactors/spree_cm_commissioner/stock/inventory_items_generator.rb +15 -0
  18. data/app/interactors/spree_cm_commissioner/stock/permanent_inventory_items_generator.rb +75 -0
  19. data/app/interactors/spree_cm_commissioner/stock/stock_movement_creator.rb +24 -0
  20. data/app/interactors/spree_cm_commissioner/vattanac_bank_initiator.rb +27 -8
  21. data/app/jobs/spree_cm_commissioner/ensure_correct_product_type_job.rb +7 -0
  22. data/app/jobs/spree_cm_commissioner/inventory_item_syncer_job.rb +7 -0
  23. data/app/jobs/spree_cm_commissioner/stock/inventory_items_adjuster_job.rb +11 -0
  24. data/app/jobs/spree_cm_commissioner/stock/inventory_items_generator_job.rb +11 -0
  25. data/app/jobs/spree_cm_commissioner/stock/permanent_inventory_items_generator_job.rb +9 -0
  26. data/app/models/concerns/spree_cm_commissioner/order_state_machine.rb +26 -0
  27. data/app/models/concerns/spree_cm_commissioner/product_delegation.rb +1 -3
  28. data/app/models/concerns/spree_cm_commissioner/product_type.rb +10 -0
  29. data/app/models/spree_cm_commissioner/inventory.rb +11 -0
  30. data/app/models/spree_cm_commissioner/inventory_item.rb +56 -0
  31. data/app/models/spree_cm_commissioner/line_item_decorator.rb +16 -5
  32. data/app/models/spree_cm_commissioner/order_decorator.rb +15 -0
  33. data/app/models/spree_cm_commissioner/place.rb +11 -2
  34. data/app/models/spree_cm_commissioner/product_decorator.rb +9 -2
  35. data/app/models/spree_cm_commissioner/redis_stock/cached_inventory_items_builder.rb +40 -0
  36. data/app/models/spree_cm_commissioner/redis_stock/inventory_updater.rb +126 -0
  37. data/app/models/spree_cm_commissioner/redis_stock/line_items_cached_inventory_items_builder.rb +36 -0
  38. data/app/models/spree_cm_commissioner/redis_stock/variant_cached_inventory_items_builder.rb +27 -0
  39. data/app/models/spree_cm_commissioner/stock/availability_checker.rb +27 -25
  40. data/app/models/spree_cm_commissioner/stock/availability_validator_decorator.rb +2 -1
  41. data/app/models/spree_cm_commissioner/stock/line_item_availability_checker.rb +3 -3
  42. data/app/models/spree_cm_commissioner/stock/order_availability_checker.rb +44 -0
  43. data/app/models/spree_cm_commissioner/stock_item_decorator.rb +14 -0
  44. data/app/models/spree_cm_commissioner/taxon_decorator.rb +11 -0
  45. data/app/models/spree_cm_commissioner/taxon_option_type.rb +8 -0
  46. data/app/models/spree_cm_commissioner/taxon_option_value.rb +8 -0
  47. data/app/models/spree_cm_commissioner/trip.rb +0 -11
  48. data/app/models/spree_cm_commissioner/trip_stop.rb +11 -4
  49. data/app/models/spree_cm_commissioner/variant_decorator.rb +39 -27
  50. data/app/models/spree_cm_commissioner/vendor_stop.rb +2 -1
  51. data/app/queries/spree_cm_commissioner/vendor_stop_place_query.rb +54 -0
  52. data/app/request_schemas/spree_cm_commissioner/accommodation_request_schema.rb +3 -0
  53. data/app/request_schemas/spree_cm_commissioner/application_request_schema.rb +1 -1
  54. data/app/request_schemas/spree_cm_commissioner/variant_request_schema.rb +19 -0
  55. data/app/serializers/spree/v2/storefront/accommodation_serializer.rb +2 -0
  56. data/app/serializers/spree/v2/tenant/guest_serializer.rb +1 -0
  57. data/app/services/spree_cm_commissioner/aes_encryption_service.rb +6 -4
  58. data/app/services/spree_cm_commissioner/organizer/export_guest_csv_service.rb +2 -0
  59. data/app/views/spree/admin/stock_managements/_events_popover.html.erb +23 -0
  60. data/app/views/spree/admin/stock_managements/_variant_stock_items.html.erb +3 -1
  61. data/app/views/spree/admin/stock_managements/calendar.html.erb +35 -0
  62. data/app/views/spree/admin/stock_managements/index.html.erb +40 -5
  63. data/config/initializers/spree_permitted_attributes.rb +5 -0
  64. data/config/routes.rb +11 -2
  65. data/db/migrate/20250304293518_create_cm_inventory_items.rb +21 -0
  66. data/db/migrate/20250418072528_add_nested_set_columns_to_places.rb +10 -0
  67. data/db/migrate/20250429094228_add_lock_version_to_cm_inventory_items.rb +5 -0
  68. data/db/migrate/20250430091742_create_cm_taxon_option_types.rb +9 -0
  69. data/db/migrate/20250430092928_create_cm_taxon_option_values.rb +9 -0
  70. data/db/migrate/20250502025848_add_index_to_spree_products.rb +5 -0
  71. data/db/migrate/20250502030001_add_product_type_to_spree_variants.rb +5 -0
  72. data/db/migrate/20250502030002_add_product_type_to_spree_line_items.rb +5 -0
  73. data/db/migrate/20250506092929_add_trip_count_to_cm_vendor_stops.rb +5 -0
  74. data/docker-compose.yml +1 -1
  75. data/lib/generators/spree_cm_commissioner/install/install_generator.rb +11 -3
  76. data/lib/generators/spree_cm_commissioner/install/templates/app/javascript/{spree_cm_commissioner → spree_dashboard/spree_cm_commissioner}/utilities.js +4 -0
  77. data/lib/spree_cm_commissioner/cached_inventory_item.rb +23 -0
  78. data/lib/spree_cm_commissioner/calendar_event.rb +11 -1
  79. data/lib/spree_cm_commissioner/test_helper/factories/homepage_section_relatable_factory.rb +1 -1
  80. data/lib/spree_cm_commissioner/test_helper/factories/inventory_item_factory.rb +9 -0
  81. data/lib/spree_cm_commissioner/test_helper/factories/line_item_factory.rb +1 -1
  82. data/lib/spree_cm_commissioner/test_helper/factories/place_factory.rb +11 -1
  83. data/lib/spree_cm_commissioner/test_helper/factories/product_factory.rb +18 -5
  84. data/lib/spree_cm_commissioner/test_helper/factories/stock_location_factory.rb +2 -2
  85. data/lib/spree_cm_commissioner/test_helper/factories/variant_factory.rb +41 -6
  86. data/lib/spree_cm_commissioner/test_helper/factories/vendor_factory.rb +1 -1
  87. data/lib/spree_cm_commissioner/version.rb +1 -1
  88. data/lib/spree_cm_commissioner.rb +34 -0
  89. data/lib/tasks/create_default_non_permanent_inventory_items.rake +16 -0
  90. data/lib/tasks/ensure_correct_product_type.rake +7 -0
  91. data/lib/tasks/generate_inventory_items.rake +7 -0
  92. data/lib/tasks/migrate_and_rebuild_place_hierarchy.rake +9 -0
  93. data/lib/tasks/update_orphan_root_places.rake +7 -0
  94. data/spree_cm_commissioner.gemspec +5 -0
  95. metadata +90 -7
  96. data/app/queries/spree_cm_commissioner/variant_availability/non_permanent_stock_query.rb +0 -45
  97. data/app/queries/spree_cm_commissioner/variant_availability/permanent_stock_query.rb +0 -55
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 634d4cd6a4373be586e59598dab82c2440eb292abe53abeeb1161e6bfca85da2
4
- data.tar.gz: 3e0e4f5293ce46f30d227269baf174b7b8b3a32ac269a30b733f3e7ea1065aca
3
+ metadata.gz: 72b30d2dd7e38275035e90f40357f304bf844828ef18cd7076a737b467f2d1ac
4
+ data.tar.gz: 21d98612977148a431c77fd26633c0011cc0be1325b75a0dd9175b3b7bbe11e3
5
5
  SHA512:
6
- metadata.gz: 7830251cadb15def91fb9d7bc6c6c68dacb3af529d941f6c1d17364f3dd621c2e3539bf24d7d870d0b82e88f2309d78ab895de19219144192dd8924f1a1943b1
7
- data.tar.gz: 64bbba3e3a8227f1788748ac27ee108d8f3ad93d89829336f53817556eb6a7e67fd4c43263ce6aa57e087df046139db1c9a665f57411634e1648d8dbd55b9f33
6
+ metadata.gz: fceca341a978f1022e8420f7a5b96e405de662fdb0cb1ea8fb745b4ceca48271aeaf9179e04e7512f3a7ee4049d93fb0b80443bcea1473d89b02b7d709f0f7dd
7
+ data.tar.gz: 3f15f7304214c925f4257cab2eb802211eb0ddde0af8e13bf6378ada2f668af6d509499df2be71eb9cd0aaa923c586b5843757101c4cb16c950fdc1e585d0351
@@ -3,96 +3,19 @@ name: Commissioner Gem
3
3
 
4
4
  on:
5
5
  pull_request:
6
+ types:
7
+ - opened
8
+ - edited
9
+ - synchronize
10
+ - reopened
6
11
  branches:
7
12
  - develop
13
+ - milestone-77-scalable-design
8
14
  push:
9
15
  tags:
10
16
  - "*"
11
17
  jobs:
12
- validate-commits:
13
- runs-on: ubuntu-latest
14
-
15
- steps:
16
- - name: Check PR title format
17
- uses: actions/github-script@v7
18
- if: github.event_name == 'pull_request'
19
- with:
20
- script: |
21
- const title = context.payload.pull_request.title.trim();
22
-
23
- console.log("PR title: ", title);
24
-
25
- const pattern = /^Close\s+#\d+\s.+/;
26
-
27
- if (!pattern.test(title)) {
28
- core.setFailed(
29
- `PR title must start with "Close #issue_number message". Example: Close #123 Add login form`
30
- );
31
- } else {
32
- console.log("PR title format is correct.");
33
- }
34
- - name: Check commit messages format
35
- uses: actions/github-script@v7
36
- if: github.event_name == 'pull_request'
37
- with:
38
- script: |
39
- const pr = context.payload.pull_request;
40
- const commits_url = pr.commits_url;
41
-
42
- const commits = await github.request(commits_url);
43
- const pattern = /^Close\s+#\d+\s.+/;
44
-
45
- let invalidCommits = [];
46
-
47
- for (const commit of commits.data) {
48
- const msg = commit.commit.message.trim();
49
-
50
- console.log("commit message: ", msg);
51
-
52
- if (!pattern.test(msg)) {
53
- invalidCommits.push(`- ${msg}`);
54
- }
55
- }
56
-
57
- if (invalidCommits.length > 0) {
58
- core.setFailed(
59
- `The following commit messages are not in the correct format:\n\n${invalidCommits.join(
60
- '\n'
61
- )}\n\nEach commit message must start with "Close #<issue_number> <message>"`
62
- );
63
- } else {
64
- console.log("All commit messages are correctly formatted.");
65
- }
66
- - name: Check for unresolved review threads
67
- uses: actions/github-script@v7
68
- if: github.event_name == 'pull_request'
69
- with:
70
- script: |
71
- const pr = context.payload.pull_request;
72
- const owner = context.repo.owner;
73
- const repo = context.repo.repo;
74
-
75
- try {
76
- const response = await github.rest.pulls.listReviewComments({
77
- owner,
78
- repo,
79
- pull_number: pr.number,
80
- per_page: 100
81
- });
82
-
83
- const unresolved = response.data.filter(comment => comment.in_reply_to_id && !comment.resolved);
84
-
85
- if (unresolved.length > 0) {
86
- core.setFailed(`There are ${unresolved.length} unresolved review comment(s). Please resolve all threads before merging.`);
87
- } else {
88
- console.log("All review comments are resolved.");
89
- }
90
- } catch (error) {
91
- console.error("Error fetching review comments:", error.message);
92
- core.setFailed("Failed to check for unresolved review threads.");
93
- }
94
18
  test_and_build_gem:
95
- needs: [validate-commits]
96
19
  # if: github.head_ref != '2572-enforce-pr-workflow' || github.base_ref != 'develop'
97
20
  runs-on: ubuntu-latest
98
21
 
@@ -112,6 +35,17 @@ jobs:
112
35
  --health-timeout 5s
113
36
  --health-retries 5
114
37
 
38
+ redis:
39
+ image: redis
40
+ # Set health checks to wait until redis has started
41
+ options: >-
42
+ --health-cmd "redis-cli ping"
43
+ --health-interval 10s
44
+ --health-timeout 5s
45
+ --health-retries 5
46
+ ports:
47
+ - 6379:6379 # Maps port 6379 on service container to the host
48
+
115
49
  steps:
116
50
  - uses: actions/checkout@v3
117
51
 
@@ -132,47 +66,6 @@ jobs:
132
66
  gem install bundler
133
67
  bundle install --jobs 4 --retry 3
134
68
 
135
- - name: Quality
136
- env:
137
- DATABASE_URL: postgres://myuser:mypassword@localhost:5432/test_db
138
- run: |
139
- bundle exec rubocop
140
-
141
- - name: Security
142
- env:
143
- DATABASE_URL: postgres://myuser:mypassword@localhost:5432/test_db
144
- run: |
145
- bundle exec brakeman
146
-
147
- - name: Run test
148
- env:
149
- DATABASE_URL: postgres://myuser:mypassword@localhost:5432/test_db
150
-
151
- if: github.event_name == 'pull_request' && github.event.pull_request.base.ref == 'develop'
152
- run: |
153
- bundle exec rake
154
-
155
- # 2405-build-and-publish-gem
156
- # - name: Rename long migration files
157
- # env:
158
- # DATABASE_URL: postgres://myuser:mypassword@localhost:5432/test_db
159
- # # if: startsWith(github.ref, 'refs/tags/') # Only on tag pushes
160
- # if: github.ref == 'refs/heads/2405-build-and-publish-gem'
161
-
162
- # run: |
163
- # cd db/migrate
164
- # for file in *.rb; do
165
- # if [ ${#file} -gt 60 ]; then # Adjust threshold if needed
166
- # # Extract timestamp (first 14 chars) and shorten the rest
167
- # timestamp=${file:0:14}
168
- # suffix=$(echo "$file" | sed 's/^[0-9]\{14\}_//' | sed 's/\.rb$//')
169
- # short_suffix=$(echo "$suffix" | cut -c1-40) # Limit suffix to 40 chars
170
- # new_name="${timestamp}_${short_suffix}.rb"
171
- # mv "$file" "$new_name"
172
- # echo "Renamed $file to $new_name"
173
- # fi
174
- # done
175
-
176
69
  # Build the gem
177
70
  - name: Build gem
178
71
  env:
data/.gitignore CHANGED
@@ -28,4 +28,5 @@ node_modules
28
28
  # VScode
29
29
  .vscode
30
30
  !.vscode/.settings.json
31
- .env
31
+ .env
32
+ vendor/bundle/
@@ -3,6 +3,6 @@
3
3
  "files.trimTrailingWhitespace": true,
4
4
  "editor.formatOnSave": true,
5
5
  "[yaml]": {
6
- "editor.defaultFormatter": "aaron-bond.better-comments"
6
+ "editor.defaultFormatter": "redhat.vscode-yaml"
7
7
  },
8
8
  }
data/Gemfile.lock CHANGED
@@ -34,7 +34,7 @@ GIT
34
34
  PATH
35
35
  remote: .
36
36
  specs:
37
- spree_cm_commissioner (1.10.0)
37
+ spree_cm_commissioner (1.11.0.pre.pre2)
38
38
  activerecord-multi-tenant
39
39
  activerecord_json_validator (~> 2.1, >= 2.1.3)
40
40
  aws-sdk-cloudfront
@@ -42,6 +42,7 @@ PATH
42
42
  aws-sdk-s3
43
43
  blazer (~> 3.0.4)
44
44
  byebug
45
+ connection_pool
45
46
  counter_culture (~> 3.2)
46
47
  dry-validation (~> 1.10)
47
48
  elasticsearch (~> 8.5)
@@ -57,6 +58,8 @@ PATH
57
58
  phonelib
58
59
  premailer-rails
59
60
  rails (~> 7.0.4)
61
+ redis
62
+ redis-rails
60
63
  rqrcode (~> 2.0)
61
64
  searchkick (~> 5.1)
62
65
  simple_calendar (~> 2.4)
@@ -617,6 +620,8 @@ GEM
617
620
  activesupport (>= 2.3.14)
618
621
  racc (1.7.1)
619
622
  rack (2.2.8)
623
+ rack-session (1.0.2)
624
+ rack (< 3)
620
625
  rack-test (2.1.0)
621
626
  rack (>= 1.3)
622
627
  rails (7.0.8)
@@ -660,8 +665,24 @@ GEM
660
665
  rbtree (0.4.6)
661
666
  redis (5.0.7)
662
667
  redis-client (>= 0.9.0)
668
+ redis-actionpack (5.5.0)
669
+ actionpack (>= 5)
670
+ redis-rack (>= 2.1.0, < 4)
671
+ redis-store (>= 1.1.0, < 2)
672
+ redis-activesupport (5.3.0)
673
+ activesupport (>= 3, < 8)
674
+ redis-store (>= 1.3, < 2)
663
675
  redis-client (0.17.0)
664
676
  connection_pool
677
+ redis-rack (3.0.0)
678
+ rack-session (>= 0.2.0)
679
+ redis-store (>= 1.2, < 2)
680
+ redis-rails (5.0.2)
681
+ redis-actionpack (>= 5.0, < 6)
682
+ redis-activesupport (>= 5.0, < 6)
683
+ redis-store (>= 1.2, < 2)
684
+ redis-store (1.11.0)
685
+ redis (>= 4, < 6)
665
686
  regexp_parser (2.8.2)
666
687
  representable (3.2.0)
667
688
  declarative (< 0.1.0)
data/Rakefile CHANGED
@@ -2,7 +2,7 @@ require 'bundler'
2
2
  Bundler::GemHelper.install_tasks
3
3
 
4
4
  require 'rspec/core/rake_task'
5
- require 'spree/testing_support/extension_rake'
5
+ require 'spree/testing_support/common_rake'
6
6
 
7
7
  RSpec::Core::RakeTask.new
8
8
 
@@ -14,8 +14,37 @@ task :default do
14
14
  Rake::Task[:spec].invoke
15
15
  end
16
16
 
17
+ # Copied from:
18
+ # spree_core:lib/spree/testing_support/common_rake.rb
19
+ # But refactored to skip frontend & backend setup.
17
20
  desc 'Generates a dummy app for testing'
18
- task :test_app do
19
- ENV['LIB_NAME'] = 'spree_cm_commissioner'
20
- Rake::Task['extension:test_app'].invoke
21
+ task :test_app do |_t, args|
22
+ require 'spree_cm_commissioner'
23
+ require "generators/spree_cm_commissioner/install/install_generator"
24
+
25
+ Rails.env = ENV['RAILS_ENV'] = 'test'
26
+
27
+ Spree::DummyGeneratorHelper.inject_extension_requirements = true
28
+ Spree::DummyGenerator.start ["--lib_name=spree_cm_commissioner", '--quiet']
29
+ Spree::InstallGenerator.start [
30
+ "--lib_name=spree_cm_commissioner",
31
+ '--auto-accept',
32
+ '--migrate=false',
33
+ '--seed=false',
34
+ '--sample=false',
35
+ '--quiet',
36
+ '--copy_storefront=false',
37
+ "--install_storefront=false",
38
+ "--install_admin=false",
39
+ "--user_class=Spree::User"
40
+ ]
41
+
42
+ puts 'Setting up dummy database...'
43
+ system('bin/rails db:environment:set RAILS_ENV=test')
44
+ system('bundle exec rake db:drop db:create')
45
+ Spree::DummyModelGenerator.start
46
+ system('bundle exec rake db:migrate')
47
+
48
+ puts 'Running extension installation generator...'
49
+ SpreeCmCommissioner::Generators::InstallGenerator.start(['--auto-run-migrations'])
21
50
  end
@@ -5,6 +5,8 @@ module Spree
5
5
 
6
6
  before_action :load_parent
7
7
 
8
+ helper_method :inventory_item_message
9
+
8
10
  def load_parent
9
11
  @product = Spree::Product.find_by(slug: params[:product_id])
10
12
  end
@@ -12,9 +14,58 @@ module Spree
12
14
  def index
13
15
  @variants = @product.variants.includes(:images, stock_items: :stock_location, option_values: :option_type)
14
16
  @variants = [@product.master] if @variants.empty?
15
-
16
17
  @stock_locations = (@variants.flat_map(&:stock_locations) + @product.vendor.stock_locations).uniq
17
18
 
19
+ load_inventories unless @product.permanent_stock?
20
+ end
21
+
22
+ def create
23
+ result = SpreeCmCommissioner::Stock::StockMovementCreator.call(
24
+ variant_id: params[:variant_id],
25
+ stock_location_id: params[:stock_location_id],
26
+ current_store: current_store,
27
+ stock_movement_params: stock_movement_params
28
+ )
29
+
30
+ if result.success?
31
+ flash[:success] = flash_message_for(result.stock_movement, :successfully_created)
32
+ else
33
+ flash[:error] = result.message
34
+ end
35
+
36
+ redirect_back fallback_location: admin_product_stock_managements_path(@product)
37
+ end
38
+
39
+ def calendar
40
+ @year = params[:year].present? ? params[:year].to_i : Time.zone.today.year
41
+
42
+ from_date = Date.new(@year, 1, 1).beginning_of_year
43
+ to_date = Date.new(@year, 1, 1).end_of_year
44
+
45
+ @inventory_items = @product.inventory_items.includes(:variant).where(inventory_date: from_date..to_date).to_a
46
+ @cached_inventory_items = ::SpreeCmCommissioner::RedisStock::CachedInventoryItemsBuilder.new(@inventory_items)
47
+ .call
48
+ .index_by(&:inventory_item_id)
49
+ @events = SpreeCmCommissioner::CalendarEvent.from_inventory_items(@inventory_items)
50
+ end
51
+
52
+ def inventory_item_message(inventory_item, cached_inventory_item)
53
+ synced = inventory_item.quantity_available == cached_inventory_item.quantity_available
54
+
55
+ if synced
56
+ "Synced: Quantity available matches in both DB and Redis (#{cached_inventory_item.quantity_available})."
57
+ else
58
+ "Out of sync: Redis shows #{cached_inventory_item.quantity_available} available, which doesn't match the database."
59
+ end
60
+ end
61
+
62
+ private
63
+
64
+ def load_inventories
65
+ @inventory_items = @product.inventory_items.group_by { |item| item.variant.id }
66
+ @cached_inventory_items = ::SpreeCmCommissioner::RedisStock::CachedInventoryItemsBuilder.new(@product.inventory_items)
67
+ .call
68
+ .index_by(&:inventory_item_id)
18
69
  @reserved_stocks = Spree::LineItem
19
70
  .complete
20
71
  .where(variant_id: @variants.pluck(:id))
@@ -25,6 +76,16 @@ module Spree
25
76
  def model_class
26
77
  Spree::StockItem
27
78
  end
79
+
80
+ # This method processes the parameters for stock movement creation.
81
+ # Expected attributes for `stock_movement` include:
82
+ # - :quantity (Integer): The quantity of stock to move.
83
+ # - :action (String): The type of stock movement (e.g., "add" or "remove").
84
+ # - :originator_id, :originator_ty (String, optional): The ID and type of the entity creating the stock movement.
85
+ # Ensure that `permitted_stock_movement_attributes` is updated to reflect any changes.
86
+ def stock_movement_params
87
+ params.require(:stock_movement).permit(permitted_stock_movement_attributes)
88
+ end
28
89
  end
29
90
  end
30
91
  end
@@ -0,0 +1,42 @@
1
+ module Spree
2
+ module Api
3
+ module V2
4
+ module Storefront
5
+ class Accommodations::VariantsController < ::Spree::Api::V2::ResourceController
6
+ private
7
+
8
+ # override
9
+ def collection
10
+ @collection ||= SpreeCmCommissioner::Accommodations::FindVariant.new(
11
+ from_date: params[:from_date]&.to_date,
12
+ to_date: params[:to_date]&.to_date,
13
+ vendor_id: params[:accommodation_id],
14
+ number_of_adults: params[:number_of_adults].to_i,
15
+ number_of_kids: params[:number_of_kids].to_i
16
+ ).execute
17
+ end
18
+
19
+ # override
20
+ def resource
21
+ @resource ||= collection.find(params[:id])
22
+ end
23
+
24
+ # override
25
+ def resource_serializer
26
+ Spree::V2::Storefront::VariantSerializer
27
+ end
28
+
29
+ # override
30
+ def collection_serializer
31
+ Spree::V2::Storefront::VariantSerializer
32
+ end
33
+
34
+ # override
35
+ def required_schema
36
+ SpreeCmCommissioner::VariantRequestSchema
37
+ end
38
+ end
39
+ end
40
+ end
41
+ end
42
+ end
@@ -5,55 +5,38 @@ module Spree
5
5
  class AccommodationsController < ::Spree::Api::V2::ResourceController
6
6
  private
7
7
 
8
+ # override
8
9
  def collection
9
- @collection ||= collection_finder.call(params: params).value
10
- end
11
-
12
- def paginated_collection
13
- return @paginated_collection if defined?(@paginated_collection)
14
-
15
- @paginated_collection = super
16
- @paginated_collection = apply_service_availability(@paginated_collection)
10
+ @collection ||= SpreeCmCommissioner::Accommodations::Find.new(
11
+ from_date: params[:from_date]&.to_date,
12
+ to_date: params[:to_date]&.to_date,
13
+ state_id: params[:province_id],
14
+ number_of_adults: params[:adult].to_i,
15
+ number_of_kids: params[:children].to_i
16
+ ).execute
17
17
  end
18
18
 
19
+ # override
19
20
  def resource
20
- resource = resource_finder.call(params: params).value
21
- raise ActiveRecord::RecordNotFound if resource.nil?
22
-
23
- apply_service_availability(resource)
24
- end
25
-
26
- def apply_service_availability(resource)
27
- SpreeCmCommissioner::ApplyServiceAvailability.call(calendarable: resource,
28
- from_date: params[:from_date].to_date,
29
- to_date: params[:to_date].to_date
30
- ).value
21
+ @resource ||= collection.find(params[:id])
31
22
  end
32
23
 
24
+ # override
33
25
  def allowed_sort_attributes
34
26
  super << :min_price << :max_price
35
27
  end
36
28
 
37
- def model_class
38
- Spree::Vendor
39
- end
40
-
29
+ # override
41
30
  def resource_serializer
42
31
  Spree::V2::Storefront::AccommodationSerializer
43
32
  end
44
33
 
34
+ # override
45
35
  def collection_serializer
46
36
  Spree::V2::Storefront::AccommodationSerializer
47
37
  end
48
38
 
49
- def collection_finder
50
- SpreeCmCommissioner::AccommodationSearchDetail
51
- end
52
-
53
- def resource_finder
54
- SpreeCmCommissioner::AccommodationSearchDetail
55
- end
56
-
39
+ # override
57
40
  def required_schema
58
41
  SpreeCmCommissioner::AccommodationRequestSchema
59
42
  end
@@ -12,9 +12,9 @@ module Spree
12
12
  spree_authorize! :update, spree_current_order, order_token
13
13
  spree_authorize! :show, @variant
14
14
 
15
- availability_checker = SpreeCmCommissioner::Stock::AvailabilityChecker.new(@variant)
15
+ availability_checker = SpreeCmCommissioner::Stock::AvailabilityChecker.new(@variant, add_item_params[:options])
16
16
 
17
- unless availability_checker.can_supply?(add_item_params[:quantity].to_i, add_item_params[:options])
17
+ unless availability_checker.can_supply?(add_item_params[:quantity].to_i)
18
18
  return render_error_payload(availability_checker.error_message || I18n.t('variant_availability.items_out_of_stock'))
19
19
  end
20
20
 
@@ -0,0 +1,40 @@
1
+ module SpreeCmCommissioner
2
+ module Accommodations
3
+ class Find
4
+ attr_reader :from_date, :to_date, :state_id, :number_of_guests
5
+
6
+ def initialize(from_date:, to_date:, state_id:, number_of_adults:, number_of_kids:)
7
+ @from_date = from_date
8
+ @to_date = to_date
9
+ @state_id = state_id
10
+ @number_of_guests = number_of_adults.to_i + number_of_kids.to_i
11
+ end
12
+
13
+ def execute
14
+ scope
15
+ .where(default_state_id: state_id)
16
+ .where(inventory_items: { inventory_date: date_range_excluding_checkout })
17
+ .where('CAST(spree_variants.public_metadata->\'cm_options\'->>\'number-of-adults\' AS INTEGER) +
18
+ CAST(spree_variants.public_metadata->\'cm_options\'->>\'number-of-kids\' AS INTEGER) >= ?', number_of_guests
19
+ )
20
+ .where('inventory_items.quantity_available > 0')
21
+ .distinct
22
+ end
23
+
24
+ private
25
+
26
+ def scope
27
+ Spree::Vendor
28
+ .joins(variants: :inventory_items)
29
+ .where(primary_product_type: :accommodation, state: :active)
30
+ end
31
+
32
+ # Why? check_out date is not considered to be charged
33
+ # For example, if you check in on 2023-10-01 and check out on 2023-10-02,
34
+ # you will be charged for one night (2023-10-01).
35
+ def date_range_excluding_checkout
36
+ from_date..to_date.prev_day
37
+ end
38
+ end
39
+ end
40
+ end
@@ -0,0 +1,35 @@
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
@@ -10,6 +10,8 @@ module SpreeCmCommissioner
10
10
  assign_prototype
11
11
  create_child_taxon
12
12
  build_home_banner
13
+ assign_option_types
14
+ assign_option_values
13
15
  end
14
16
  end
15
17
 
@@ -61,5 +63,26 @@ module SpreeCmCommissioner
61
63
 
62
64
  context.fail!(message: 'Home banner upload failed') unless banner.persisted?
63
65
  end
66
+
67
+ def assign_options(model_class, param_key, foreign_key)
68
+ return unless params[param_key]
69
+
70
+ params[param_key].each do |id|
71
+ record = model_class.new(
72
+ taxon_id: @parent_taxon.id,
73
+ foreign_key => id
74
+ )
75
+
76
+ context.fail!(message: record.errors.full_messages.join(', ')) unless record.save
77
+ end
78
+ end
79
+
80
+ def assign_option_types
81
+ assign_options(SpreeCmCommissioner::TaxonOptionType, :option_type_id, :option_type_id)
82
+ end
83
+
84
+ def assign_option_values
85
+ assign_options(SpreeCmCommissioner::TaxonOptionValue, :option_value_id, :option_value_id)
86
+ end
64
87
  end
65
88
  end
@@ -0,0 +1,40 @@
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