spree_cm_commissioner 1.9.2 → 1.10.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 (83) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/test_and_build_gem.yml +86 -0
  3. data/.gitignore +2 -1
  4. data/Gemfile.lock +22 -1
  5. data/app/controllers/spree/admin/events_controller.rb +47 -0
  6. data/app/controllers/spree/admin/prototypes_controller_decorator.rb +20 -0
  7. data/app/controllers/spree/admin/stock_managements_controller.rb +17 -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/finders/spree_cm_commissioner/accommodations/find.rb +40 -0
  11. data/app/finders/spree_cm_commissioner/accommodations/find_variant.rb +35 -0
  12. data/app/interactors/spree_cm_commissioner/create_event.rb +65 -0
  13. data/app/interactors/spree_cm_commissioner/firebase_email_fetcher.rb +30 -4
  14. data/app/interactors/spree_cm_commissioner/firebase_email_fetcher_cron_executor.rb +23 -0
  15. data/app/interactors/spree_cm_commissioner/inventory_item_syncer.rb +25 -0
  16. data/app/interactors/spree_cm_commissioner/stock/permanent_inventory_items_generator.rb +71 -0
  17. data/app/interactors/spree_cm_commissioner/vattanac_bank_initiator.rb +25 -6
  18. data/app/jobs/spree_cm_commissioner/firebase_email_fetcher_job.rb +9 -0
  19. data/app/jobs/spree_cm_commissioner/inventory_item_syncer_job.rb +7 -0
  20. data/app/jobs/spree_cm_commissioner/stock/permanent_inventory_items_generator_job.rb +9 -0
  21. data/app/models/concerns/spree_cm_commissioner/order_state_machine.rb +26 -0
  22. data/app/models/concerns/spree_cm_commissioner/product_type.rb +10 -0
  23. data/app/models/spree_cm_commissioner/inventory.rb +11 -0
  24. data/app/models/spree_cm_commissioner/inventory_item.rb +37 -0
  25. data/app/models/spree_cm_commissioner/invite_user_taxon.rb +1 -0
  26. data/app/models/spree_cm_commissioner/line_item_decorator.rb +1 -0
  27. data/app/models/spree_cm_commissioner/order_decorator.rb +15 -0
  28. data/app/models/spree_cm_commissioner/product_decorator.rb +10 -0
  29. data/app/models/spree_cm_commissioner/prototype_decorator.rb +9 -0
  30. data/app/models/spree_cm_commissioner/redis_stock/cached_inventory_items_builder.rb +40 -0
  31. data/app/models/spree_cm_commissioner/redis_stock/inventory_updater.rb +114 -0
  32. data/app/models/spree_cm_commissioner/redis_stock/line_items_cached_inventory_items_builder.rb +42 -0
  33. data/app/models/spree_cm_commissioner/redis_stock/variant_cached_inventory_items_builder.rb +27 -0
  34. data/app/models/spree_cm_commissioner/stock/availability_checker.rb +27 -25
  35. data/app/models/spree_cm_commissioner/stock/availability_validator_decorator.rb +2 -1
  36. data/app/models/spree_cm_commissioner/stock/line_item_availability_checker.rb +3 -3
  37. data/app/models/spree_cm_commissioner/stock/order_availability_checker.rb +44 -0
  38. data/app/models/spree_cm_commissioner/stock_movement_decorator.rb +34 -0
  39. data/app/models/spree_cm_commissioner/taxon_decorator.rb +2 -1
  40. data/app/models/spree_cm_commissioner/user_decorator.rb +5 -1
  41. data/app/models/spree_cm_commissioner/variant_decorator.rb +24 -17
  42. data/app/overrides/spree/admin/prototypes/_form/description.html.erb.deface +6 -0
  43. data/app/overrides/spree/admin/prototypes/_form/icon.html.erb.deface +8 -0
  44. data/app/overrides/spree/admin/prototypes/_form/product_type.html.erb.deface +2 -2
  45. data/app/overrides/spree/admin/shared/sub_menu/_product/events_tab.html.erb.deface +3 -0
  46. data/app/request_schemas/spree_cm_commissioner/accommodation_request_schema.rb +3 -0
  47. data/app/request_schemas/spree_cm_commissioner/application_request_schema.rb +1 -1
  48. data/app/request_schemas/spree_cm_commissioner/variant_request_schema.rb +19 -0
  49. data/app/serializers/spree/v2/storefront/accommodation_serializer.rb +2 -0
  50. data/app/serializers/spree/v2/tenant/payment_method_serializer.rb +12 -0
  51. data/app/serializers/spree/v2/tenant/user_serializer.rb +1 -1
  52. data/app/views/spree/admin/events/_search_form.html.erb +61 -0
  53. data/app/views/spree/admin/events/_tab.html.erb +25 -0
  54. data/app/views/spree/admin/events/_table.html.erb +27 -0
  55. data/app/views/spree/admin/events/index.html.erb +15 -0
  56. data/app/views/spree/admin/stock_managements/_events_popover.html.erb +17 -0
  57. data/app/views/spree/admin/stock_managements/calendar.html.erb +32 -0
  58. data/app/views/spree/admin/stock_managements/index.html.erb +31 -5
  59. data/app/views/spree_cm_commissioner/crew_invite_mailer/_mailer_stylesheets.html.erb +2 -2
  60. data/app/views/spree_cm_commissioner/crew_invite_mailer/send_crew_invite_email.html.erb +1 -1
  61. data/config/initializers/user_manager_decorator.rb +26 -0
  62. data/config/routes.rb +12 -2
  63. data/db/migrate/20250304293518_create_cm_inventory_items.rb +21 -0
  64. data/db/migrate/20250425084929_add_description_to_spree_prototypes.rb +5 -0
  65. data/db/migrate/20250425085938_add_preferences_to_spree_prototypes.rb +5 -0
  66. data/db/migrate/20250428025645_add_slug_to_spree_prototypes.rb +6 -0
  67. data/db/migrate/20250429094228_add_lock_version_to_cm_inventory_items.rb +5 -0
  68. data/docker-compose.yml +1 -1
  69. data/lib/generators/spree_cm_commissioner/install/install_generator.rb +2 -2
  70. data/lib/generators/spree_cm_commissioner/install/templates/app/javascript/{spree_cm_commissioner → spree_dashboard/spree_cm_commissioner}/utilities.js +4 -0
  71. data/lib/spree_cm_commissioner/cached_inventory_item.rb +23 -0
  72. data/lib/spree_cm_commissioner/calendar_event.rb +11 -1
  73. data/lib/spree_cm_commissioner/test_helper/factories/inventory_item_factory.rb +9 -0
  74. data/lib/spree_cm_commissioner/test_helper/factories/variant_factory.rb +28 -6
  75. data/lib/spree_cm_commissioner/version.rb +1 -1
  76. data/lib/spree_cm_commissioner.rb +34 -0
  77. data/lib/tasks/create_default_non_permanent_inventory_items.rake +16 -0
  78. data/lib/tasks/fetch_email.rake +7 -0
  79. data/lib/tasks/generate_inventory_items.rake +7 -0
  80. data/spree_cm_commissioner.gemspec +5 -0
  81. metadata +88 -7
  82. data/app/queries/spree_cm_commissioner/variant_availability/non_permanent_stock_query.rb +0 -45
  83. 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: e972e51894bfc4e409d01171c367f576afb90f41d6b1f0dcd71094c5fcf4604b
4
- data.tar.gz: 9cac3457deabbcfe0d50b8750c2af67850efa64c13c1edaa44db37862b612af1
3
+ metadata.gz: f65a106e5b63ad907020c8170440edf6f39855bb166020954b51b9960aa8a052
4
+ data.tar.gz: f41887bf50e45ed36763177daeeaf433b88c8a41169ca3abcb67d59544f4faee
5
5
  SHA512:
6
- metadata.gz: d96cfb084a4f81a8f570f1b4c0f12ec0a29299f85afd70ed593282cebf0f6fbf319566fee6808e824e00d1d956725fdac16b4f19efbca4b04781e5ee3b1c633c
7
- data.tar.gz: 210063eed0f95f71c7b0fc330d7f2edad404e286d703e5b1f0cfcfd21e538c8af950827245a1a1a63c2a0fa09c79f40b4555eefa7ec08a838b5e90165f5ce93d
6
+ metadata.gz: 5aa3d6bf790a31b5214d5e043c376288236283a0df96b8251574412530fd7a38b524592036303677bc141c44586ad58dbcb7e35b3f2a66218848036ecb78c82a
7
+ data.tar.gz: 8ba3abdb4a446e4493bf3369e5a375989e71c03643bfb9298169bf4335774a9189826fbc295c06cfcecf9f5edbb560c6ae9643d7a347cd619bdea7803d23c2c1
@@ -9,7 +9,91 @@ on:
9
9
  tags:
10
10
  - "*"
11
11
  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
+ }
12
94
  test_and_build_gem:
95
+ needs: [validate-commits]
96
+ # if: github.head_ref != '2572-enforce-pr-workflow' || github.base_ref != 'develop'
13
97
  runs-on: ubuntu-latest
14
98
 
15
99
  services:
@@ -49,12 +133,14 @@ jobs:
49
133
  bundle install --jobs 4 --retry 3
50
134
 
51
135
  - name: Quality
136
+ if: github.event_name == 'pull_request'
52
137
  env:
53
138
  DATABASE_URL: postgres://myuser:mypassword@localhost:5432/test_db
54
139
  run: |
55
140
  bundle exec rubocop
56
141
 
57
142
  - name: Security
143
+ if: github.event_name == 'pull_request'
58
144
  env:
59
145
  DATABASE_URL: postgres://myuser:mypassword@localhost:5432/test_db
60
146
  run: |
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/
data/Gemfile.lock CHANGED
@@ -34,7 +34,7 @@ GIT
34
34
  PATH
35
35
  remote: .
36
36
  specs:
37
- spree_cm_commissioner (1.9.2)
37
+ spree_cm_commissioner (1.10.0.pre.pre)
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)
@@ -0,0 +1,47 @@
1
+ module Spree
2
+ module Admin
3
+ class EventsController < Spree::Admin::ResourceController
4
+ before_action :load_taxonomy, only: [:index]
5
+
6
+ def index
7
+ params[:q] ||= {}
8
+ @search = taxon_scope.ransack(params[:q])
9
+ @taxons = fetch_taxons
10
+ @taxons_for_dropdown = @taxonomy.taxons if @taxonomy
11
+ end
12
+
13
+ private
14
+
15
+ def fetch_taxons
16
+ scope = params[:taxon_id].present? ? filtered_taxons : searched_taxons
17
+ scope.includes(:taxonomy).page(params[:page]).per(15)
18
+ end
19
+
20
+ def model_class
21
+ Spree::Taxon
22
+ end
23
+
24
+ def load_taxonomy
25
+ @taxonomy = Spree::Taxonomy.find_by(name: 'Events')
26
+ return if @taxonomy
27
+
28
+ flash[:error] = Spree.t('admin.events.taxonomy_not_found')
29
+ redirect_to admin_events_path
30
+ end
31
+
32
+ def taxon_scope
33
+ @taxonomy ? Spree::Taxon.where(kind: 2, depth: 1) : Spree::Taxon.none
34
+ end
35
+
36
+ def filtered_taxons
37
+ taxon_scope.where(id: params[:taxon_id]).or(
38
+ taxon_scope.where(parent_id: params[:taxon_id])
39
+ ).distinct
40
+ end
41
+
42
+ def searched_taxons
43
+ @search.result(distinct: true)
44
+ end
45
+ end
46
+ end
47
+ end
@@ -0,0 +1,20 @@
1
+ module Spree
2
+ module Admin
3
+ module PrototypesControllerDecorator
4
+ def self.prepended(base)
5
+ base.before_action :load_icons
6
+ end
7
+
8
+ # overrided
9
+ def find_resource
10
+ Spree::Prototype.friendly.find(params[:id])
11
+ end
12
+
13
+ def load_icons
14
+ @icons = SpreeCmCommissioner::VectorIcon.all
15
+ end
16
+ end
17
+ end
18
+ end
19
+
20
+ Spree::Admin::PrototypesController.prepend(Spree::Admin::PrototypesControllerDecorator)
@@ -12,9 +12,25 @@ module Spree
12
12
  def index
13
13
  @variants = @product.variants.includes(:images, stock_items: :stock_location, option_values: :option_type)
14
14
  @variants = [@product.master] if @variants.empty?
15
-
16
15
  @stock_locations = (@variants.flat_map(&:stock_locations) + @product.vendor.stock_locations).uniq
17
16
 
17
+ load_inventories unless @product.permanent_stock?
18
+ end
19
+
20
+ def calendar
21
+ @year = params[:year].present? ? params[:year].to_i : Time.zone.today.year
22
+
23
+ from_date = Date.new(@year, 1, 1).beginning_of_year
24
+ to_date = Date.new(@year, 1, 1).end_of_year
25
+
26
+ @inventory_items = @product.inventory_items.includes(:variant).where(inventory_date: from_date..to_date).to_a
27
+ @events = SpreeCmCommissioner::CalendarEvent.from_inventory_items(@inventory_items)
28
+ end
29
+
30
+ private
31
+
32
+ def load_inventories
33
+ @inventory_items = @product.inventory_items.group_by { |item| item.variant.id }
18
34
  @reserved_stocks = Spree::LineItem
19
35
  .complete
20
36
  .where(variant_id: @variants.pluck(:id))
@@ -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[:state_id],
14
+ number_of_adults: params[:number_of_adults].to_i,
15
+ number_of_kids: params[:number_of_kids].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
@@ -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
@@ -0,0 +1,65 @@
1
+ module SpreeCmCommissioner
2
+ class CreateEvent
3
+ include Interactor
4
+ delegate :params, :current_vendor, to: :context
5
+
6
+ def call
7
+ ActiveRecord::Base.transaction do
8
+ build_parent_taxon
9
+ assign_vendor
10
+ assign_prototype
11
+ create_child_taxon
12
+ build_home_banner
13
+ end
14
+ end
15
+
16
+ private
17
+
18
+ def build_parent_taxon
19
+ @parent_taxon = Spree::Taxon.new(
20
+ name: context.params[:name],
21
+ description: context.params[:description],
22
+ from_date: context.params[:from_date],
23
+ to_date: context.params[:to_date],
24
+ parent_id: context.params[:parent_id],
25
+ taxonomy_id: context.params[:taxonomy_id]
26
+ )
27
+
28
+ return if @parent_taxon.save
29
+
30
+ context.fail!(message: @parent_taxon.errors.full_messages.join(', '))
31
+ end
32
+
33
+ def assign_vendor
34
+ @parent_taxon.vendors << context.current_vendor
35
+ end
36
+
37
+ def assign_prototype
38
+ prototype = Spree::Prototype.find_by(slug: context.params[:prototype_id])
39
+ @parent_taxon.prototypes << prototype if prototype.present?
40
+ end
41
+
42
+ def create_child_taxon
43
+ child_taxon = Spree::Taxon.new(
44
+ name: 'Ticket Type',
45
+ parent_id: @parent_taxon.id,
46
+ taxonomy_id: context.params[:taxonomy_id]
47
+ )
48
+
49
+ return if child_taxon.save
50
+
51
+ context.fail!(message: child_taxon.errors.full_messages.join(', '))
52
+ end
53
+
54
+ def build_home_banner
55
+ return if context.params[:home_banner].nil?
56
+
57
+ banner = SpreeCmCommissioner::TaxonHomeBanner.create(
58
+ viewable: @parent_taxon,
59
+ attachment: context.params[:home_banner]
60
+ )
61
+
62
+ context.fail!(message: 'Home banner upload failed') unless banner.persisted?
63
+ end
64
+ end
65
+ end
@@ -2,11 +2,37 @@ require 'firebase-admin-sdk'
2
2
 
3
3
  module SpreeCmCommissioner
4
4
  class FirebaseEmailFetcher < BaseInteractor
5
- delegate :user_id, to: :context
5
+ delegate :user_id, :sub, to: :context
6
+
7
+ # Firebase response
8
+ # {
9
+ # "localId" => "8AGwn0V88kP7vkticwuYZkNNoIJ2",
10
+ # "displayName" => "Sreyleak Deth",
11
+ # "photoUrl" => "https://lh3.googleusercontent.com/a/ACg8ocIkL62VaxNb7bOAXV30sZOGQ_Dw7ZYvlBH-Hk2jm3swNg=s96-c",
12
+ # "providerUserInfo" => [
13
+ # {
14
+ # "providerId" => "google.com",
15
+ # "displayName" => "Sreyleak Deth",
16
+ # "photoUrl" => "https://lh3.googleusercontent.com/a/ACg8ocK88Fm3GhVeCS98vLGE-vmShSi76xZwYYA1QwImuyck7zAqpR0=s96-c",
17
+ # "federatedId" => "109192493976909808585",
18
+ # "email" => "sreyleak.deth19@gmail.com",
19
+ # "rawId" => "109192493976909808585"
20
+ # }
21
+ # ],
22
+ # "validSince" => "1707378937",
23
+ # "lastLoginAt" => "1739440924021",
24
+ # "createdAt" => "1707378937388",
25
+ # "lastRefreshAt" => "2025-04-01T08:54:24.480031Z"
26
+ # }
6
27
 
7
28
  def call
8
- manager = initialize_firebase_manager
9
- user = manager.get_user_by(uid: context.user_id)
29
+ @manager = initialize_firebase_manager
30
+
31
+ if context.user_id.present?
32
+ user = @manager.get_user_by(uid: context.user_id)
33
+ elsif context.sub.present?
34
+ user = @manager.get_user_by_sub(sub: context.sub)
35
+ end
10
36
 
11
37
  context.email = user.provider_data.first&.email
12
38
  end
@@ -19,7 +45,7 @@ module SpreeCmCommissioner
19
45
 
20
46
  def initialize_firebase_manager
21
47
  @credentials ||= Firebase::Admin::Credentials.from_json(service_account.to_json)
22
- @manager ||= Firebase::Admin::Auth::UserManager.new(service_account[:project_id], @credentials)
48
+ Firebase::Admin::Auth::UserManager.new(service_account[:project_id], @credentials)
23
49
  end
24
50
  end
25
51
  end
@@ -0,0 +1,23 @@
1
+ module SpreeCmCommissioner
2
+ class FirebaseEmailFetcherCronExecutor < BaseInteractor
3
+ def call
4
+ users = Spree::User.includes(:google_user_identity_providers)
5
+ .joins(:google_user_identity_providers)
6
+ .where(email: nil)
7
+
8
+ users.find_each do |user|
9
+ google_identity = user.google_user_identity_providers.first
10
+ firebase_user = SpreeCmCommissioner::FirebaseEmailFetcher.call(sub: google_identity.sub)
11
+ next unless firebase_user.success?
12
+
13
+ email = firebase_user.email
14
+
15
+ next if email.blank?
16
+ next if Spree::User.exists?(email: email)
17
+
18
+ google_identity.update(email: email)
19
+ user.update(email: email)
20
+ end
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,25 @@
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