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
@@ -1,47 +1,49 @@
1
1
  module SpreeCmCommissioner
2
2
  module Stock
3
3
  class AvailabilityChecker
4
- attr_reader :variant, :error_message
4
+ attr_reader :variant, :options, :error_message
5
5
 
6
- def initialize(variant)
6
+ def initialize(variant, options = {})
7
7
  @variant = variant
8
+ @options = options
8
9
  @error_message = nil
9
10
  end
10
11
 
11
- def can_supply?(quantity = 1, options = {})
12
+ def can_supply?(quantity = 1)
12
13
  return false unless variant.available?
13
14
  return true unless variant.should_track_inventory?
14
15
  return true if variant.backorderable?
15
16
  return true if variant.need_confirmation?
16
17
 
17
- # when delivery required, shipment will dynamically add / remove unit from stock item.
18
- # so we can directly check can_supply with stock items directly.
19
- return variant.stock_items.sum(:count_on_hand) >= quantity if variant.delivery_required?
18
+ variant_available?(quantity)
19
+ end
20
20
 
21
- if variant.permanent_stock?
22
- permanent_stock_variant_available?(quantity, options)
23
- else
24
- variant_available?(quantity, options)
21
+ def variant_available?(quantity = 1)
22
+ return false if cached_inventory_items.empty?
23
+
24
+ cached_inventory_items.all? do |cached_inventory_item|
25
+ cached_inventory_item.active? && cached_inventory_item.quantity_available >= quantity
25
26
  end
26
27
  end
27
28
 
28
- def variant_available?(quantity = 1, options = {})
29
- query = SpreeCmCommissioner::VariantAvailability::NonPermanentStockQuery.new(
30
- variant: variant,
31
- except_line_item_id: options[:except_line_item_id]
32
- )
33
- result = query.available?(quantity)
34
- @error_message = query.error_message unless result
35
- result
29
+ def cached_inventory_items
30
+ return @cached_inventory_items if defined?(@cached_inventory_items)
31
+
32
+ if variant.permanent_stock?
33
+ return [] if options[:from_date].blank? || options[:to_date].blank?
34
+
35
+ @cached_inventory_items = builder_klass.new(
36
+ variant_id: variant.id,
37
+ from_date: options[:from_date].to_date,
38
+ to_date: options[:to_date].to_date
39
+ ).call
40
+ else
41
+ @cached_inventory_items = builder_klass.new(variant_id: variant.id).call
42
+ end
36
43
  end
37
44
 
38
- def permanent_stock_variant_available?(quantity = 1, options = {})
39
- SpreeCmCommissioner::VariantAvailability::PermanentStockQuery.new(
40
- variant: variant,
41
- from_date: options[:from_date].to_date,
42
- to_date: options[:to_date].to_date,
43
- except_line_item_id: options[:except_line_item_id]
44
- ).available?(quantity)
45
+ def builder_klass
46
+ ::SpreeCmCommissioner::RedisStock::VariantCachedInventoryItemsBuilder
45
47
  end
46
48
  end
47
49
  end
@@ -3,7 +3,8 @@ module SpreeCmCommissioner
3
3
  module AvailabilityValidatorDecorator
4
4
  # override
5
5
  def item_available?(line_item, quantity)
6
- SpreeCmCommissioner::Stock::LineItemAvailabilityChecker.new(line_item).can_supply?(quantity)
6
+ SpreeCmCommissioner::Stock::LineItemAvailabilityChecker.new(line_item)
7
+ .can_supply?(quantity)
7
8
  end
8
9
  end
9
10
  end
@@ -8,14 +8,14 @@ module SpreeCmCommissioner
8
8
  end
9
9
 
10
10
  def can_supply?(quantity)
11
- AvailabilityChecker.new(line_item.variant).can_supply?(quantity, options)
11
+ ::SpreeCmCommissioner::Stock::AvailabilityChecker.new(line_item.variant, options)
12
+ .can_supply?(quantity)
12
13
  end
13
14
 
14
15
  def options
15
16
  {
16
17
  from_date: line_item.from_date,
17
- to_date: line_item.to_date,
18
- except_line_item_id: line_item.id
18
+ to_date: line_item.to_date
19
19
  }
20
20
  end
21
21
  end
@@ -0,0 +1,44 @@
1
+ # On top of SpreeCmCommissioner::Stock::AvailabilityChecker, this class does the same thing but in bulk check with redis.
2
+ # Which mean, it check basic condition with db first, once pass, it check with redis.
3
+ module SpreeCmCommissioner
4
+ module Stock
5
+ class OrderAvailabilityChecker
6
+ attr_reader :order
7
+
8
+ def initialize(order)
9
+ @order = order
10
+ end
11
+
12
+ def can_supply_all?
13
+ insufficient_stock_lines.empty?
14
+ end
15
+
16
+ def insufficient_stock_lines
17
+ cached_inventory_items_group_by_line_item_id.map do |line_item_id, cached_inventory_items|
18
+ line_item = order.line_items.find { |item| item.id == line_item_id }
19
+ line_item unless sufficient_stock_for?(line_item, cached_inventory_items)
20
+ end.compact
21
+ end
22
+
23
+ # {
24
+ # 1: [ {inventory_key: "inventory:1", active: true, quantity_available: 5, inventory_item_id: 1, variant_id: 1}],
25
+ # 2: [ {inventory_key: "inventory:2", active: true, quantity_available: 5, inventory_item_id: 2, variant_id: 2}],
26
+ # }
27
+ def cached_inventory_items_group_by_line_item_id
28
+ @cached_inventory_items_group_by_line_item_id ||=
29
+ ::SpreeCmCommissioner::RedisStock::LineItemsCachedInventoryItemsBuilder.new(
30
+ line_item_ids: order.line_items.pluck(:id)
31
+ ).call
32
+ end
33
+
34
+ def sufficient_stock_for?(line_item, cached_inventory_items)
35
+ return false unless line_item.variant.available?
36
+ return true unless line_item.variant.should_track_inventory?
37
+ return true if line_item.variant.backorderable?
38
+ return true if line_item.variant.need_confirmation?
39
+
40
+ cached_inventory_items.all? { |item| item.active? && item.quantity_available >= line_item.quantity }
41
+ end
42
+ end
43
+ end
44
+ end
@@ -0,0 +1,34 @@
1
+ module SpreeCmCommissioner
2
+ module StockMovementDecorator
3
+ def self.prepended(base)
4
+ base.after_create :adjust_inventory_items, if: -> { stock_item.should_track_inventory? }
5
+ end
6
+
7
+ def adjust_inventory_items
8
+ if !variant.permanent_stock? && !variant.default_inventory_item_exist?
9
+ variant.create_default_non_permanent_inventory_item!
10
+ else
11
+ adjust_existing_inventory_items!
12
+ end
13
+ end
14
+
15
+ private
16
+
17
+ def should_create_default_non_permanent_inventory_item?
18
+ return false if variant.permanent_stock?
19
+ return false if variant.inventory_items.exists?(inventory_date: nil)
20
+
21
+ true
22
+ end
23
+
24
+ def adjust_existing_inventory_items!
25
+ variant.inventory_items.active.find_each(batch_size: 100) do |inventory_item|
26
+ inventory_item.adjust_quantity!(quantity)
27
+ end
28
+ end
29
+ end
30
+ end
31
+
32
+ unless Spree::StockMovement.included_modules.include?(SpreeCmCommissioner::StockMovementDecorator)
33
+ Spree::StockMovement.prepend(SpreeCmCommissioner::StockMovementDecorator)
34
+ end
@@ -46,7 +46,8 @@ module SpreeCmCommissioner
46
46
  base.before_save :set_kind
47
47
  base.before_save :set_slug
48
48
 
49
- base.whitelisted_ransackable_attributes |= %w[kind]
49
+ base.whitelisted_ransackable_associations |= %w[vendor]
50
+ base.whitelisted_ransackable_attributes |= %w[kind from_date to_date]
50
51
 
51
52
  base.enum purchasable_on: { both: 0, web: 1, app: 2 }
52
53
  base.has_many :crew_invites, class_name: 'SpreeCmCommissioner::CrewInvite', dependent: :destroy
@@ -1,7 +1,7 @@
1
1
  # rubocop:disable Metrics/MethodLength
2
2
  module SpreeCmCommissioner
3
3
  module UserDecorator
4
- def self.prepended(base)
4
+ def self.prepended(base) # rubocop:disable Metrics/AbcSize
5
5
  base.include SpreeCmCommissioner::UserNotification
6
6
  base.include SpreeCmCommissioner::UserIdentity
7
7
  base.include SpreeCmCommissioner::UserPreference
@@ -18,6 +18,10 @@ module SpreeCmCommissioner
18
18
  base.has_many :events, through: :user_events, class_name: 'Spree::Taxon', source: 'taxon'
19
19
  base.has_many :guests, class_name: 'SpreeCmCommissioner::Guest', dependent: :destroy
20
20
 
21
+ base.has_many :google_user_identity_providers,
22
+ -> { where(identity_type: :google) },
23
+ class_name: 'SpreeCmCommissioner::UserIdentityProvider'
24
+
21
25
  base.has_many :wished_items, class_name: 'Spree::WishedItem', through: :wishlists
22
26
  base.has_many :promotions, through: :promotion_rules, class_name: 'Spree::Promotion'
23
27
 
@@ -1,6 +1,7 @@
1
1
  module SpreeCmCommissioner
2
2
  module VariantDecorator
3
3
  def self.prepended(base)
4
+ base.include SpreeCmCommissioner::ProductType
4
5
  base.include SpreeCmCommissioner::ProductDelegation
5
6
  base.include SpreeCmCommissioner::VariantOptionsConcern
6
7
 
@@ -21,7 +22,12 @@ module SpreeCmCommissioner
21
22
  base.has_many :variant_guest_card_class, class_name: 'SpreeCmCommissioner::VariantGuestCardClass'
22
23
  base.has_many :guest_card_classes, class_name: 'SpreeCmCommissioner::GuestCardClass', through: :variant_guest_card_class
23
24
 
25
+ base.has_many :inventory_items, class_name: 'SpreeCmCommissioner::InventoryItem'
26
+
24
27
  base.scope :subscribable, -> { active.joins(:product).where(product: { subscribable: true, status: :active }) }
28
+ base.scope :with_permanent_stock, -> { joins(:product).where(product: { product_type: base::PERMANENT_STOCK_PRODUCT_TYPES }) }
29
+ base.scope :with_non_permanent_stock, -> { joins(:product).where.not(product: { product_type: base::PERMANENT_STOCK_PRODUCT_TYPES }) }
30
+
25
31
  base.has_one :trip,
26
32
  class_name: 'SpreeCmCommissioner::Trip'
27
33
  base.accepts_nested_attributes_for :option_values
@@ -41,14 +47,26 @@ module SpreeCmCommissioner
41
47
  super || product.discontinued?
42
48
  end
43
49
 
44
- def permanent_stock?
45
- accommodation?
46
- end
47
-
48
50
  def event
49
51
  taxons.event.first
50
52
  end
51
53
 
54
+ def default_inventory_item_exist?
55
+ inventory_items.exists?(inventory_date: nil)
56
+ end
57
+
58
+ def create_default_non_permanent_inventory_item!(quantity_available: nil, max_capacity: nil)
59
+ return if product_type.blank? # handle in case product not exist for variant.
60
+ return unless should_track_inventory?
61
+ return if default_inventory_item_exist?
62
+
63
+ inventory_items.create!(
64
+ product_type: product_type,
65
+ quantity_available: [0, quantity_available || total_on_hand].max,
66
+ max_capacity: [0, max_capacity || total_on_hand].max
67
+ )
68
+ end
69
+
52
70
  # override
53
71
  def options_text
54
72
  @options_text ||= Spree::Variants::VisableOptionsPresenter.new(self).to_sentence
@@ -69,23 +87,12 @@ module SpreeCmCommissioner
69
87
  end
70
88
 
71
89
  # override
72
- def in_stock?
73
- available_quantity.positive?
90
+ def in_stock?(options = {})
91
+ SpreeCmCommissioner::Stock::AvailabilityChecker.new(self, options).can_supply?
74
92
  end
75
93
 
76
94
  private
77
95
 
78
- def total_purchases
79
- Spree::LineItem.complete.where(variant_id: id).sum(:quantity).to_i
80
- end
81
-
82
- def available_quantity
83
- stock_count = stock_items.sum(&:count_on_hand)
84
- return stock_count if delivery_required?
85
-
86
- stock_count - total_purchases
87
- end
88
-
89
96
  def update_vendor_price
90
97
  return unless vendor.present? && product&.product_type == vendor&.primary_product_type
91
98
 
@@ -0,0 +1,6 @@
1
+ <!-- insert_before "[id=properties]" -->
2
+
3
+ <div id="description" class="field mb-3">
4
+ <%= f.label :description, Spree.t(:description) %><br>
5
+ <%= f.text_area :description, rows: 5, class: 'form-control' %>
6
+ </div>
@@ -0,0 +1,8 @@
1
+ <!-- insert_bottom "[data-hook='admin_prototype_form_fields']" -->
2
+
3
+ <div id='icon'>
4
+ <%= f.field_container :icon do %>
5
+ <%= f.label :preferred_icon, Spree.t(:icon_name) %> <%= link_to Spree.t(:icons), spree.admin_vectors_icons_url %>
6
+ <%= f.select :preferred_icon, @icons.map(&:path), { include_blank: Spree.t(:None) }, { class: "select2" } %>
7
+ <% end %>
8
+ </div>
@@ -1,3 +1,3 @@
1
- <!-- insert_bottom "[data-hook='admin_prototype_form_fields']" -->
1
+ <!-- insert_bottom "#taxons" -->
2
2
 
3
- <%= render 'shared/product_type_field', field: :product_type, f: f %>
3
+ <%= render 'shared/product_type_field', field: :product_type, f: f %>
@@ -0,0 +1,3 @@
1
+ <!-- insert_bottom "[data-hook='admin_product_sub_tabs']" -->
2
+
3
+ <%= tab :events, match_path: '/events', label: 'Events' if can? :manage, Spree::Taxon %>
@@ -3,6 +3,9 @@ module SpreeCmCommissioner
3
3
  params do
4
4
  required(:from_date).value(:date)
5
5
  required(:to_date).value(:date)
6
+ required(:state_id).value(:integer)
7
+ required(:number_of_adults).value(:integer)
8
+ required(:number_of_kids).value(:integer)
6
9
  end
7
10
 
8
11
  rule(:from_date, :to_date) do
@@ -11,7 +11,7 @@ module SpreeCmCommissioner
11
11
  end
12
12
 
13
13
  def error_message
14
- errors.map(&:text).join(', ')
14
+ errors.map { |error| "#{error.path.join(', ')}: #{error.text}" }.to_sentence
15
15
  end
16
16
 
17
17
  private
@@ -0,0 +1,19 @@
1
+ module SpreeCmCommissioner
2
+ class VariantRequestSchema < ApplicationRequestSchema
3
+ params do
4
+ required(:from_date).value(:date)
5
+ required(:to_date).value(:date)
6
+ required(:number_of_adults).value(:integer)
7
+ required(:number_of_kids).value(:integer)
8
+ end
9
+
10
+ rule(:from_date, :to_date) do
11
+ from_date = values[:from_date]
12
+ to_date = values[:to_date]
13
+
14
+ key.failure(:must_be_in_future) if from_date < Time.zone.today
15
+ key.failure(:must_be_later_than_start_date) if from_date > to_date
16
+ key.failure(:stay_is_too_long) if (to_date - from_date).to_i > ENV.fetch('ACCOMMODATION_MAX_STAY_DAYS', 10).to_i
17
+ end
18
+ end
19
+ end
@@ -9,10 +9,12 @@ module Spree
9
9
 
10
10
  attributes :total_inventory, :service_availabilities
11
11
 
12
+ # Deprecated
12
13
  attribute :total_booking do |vendor|
13
14
  vendor.respond_to?(:total_booking) ? vendor.total_booking : 0
14
15
  end
15
16
 
17
+ # Deprecated
16
18
  attribute :remaining do |vendor|
17
19
  vendor.respond_to?(:remaining) ? vendor.remaining : vendor.total_inventory
18
20
  end
@@ -7,6 +7,18 @@ module Spree
7
7
  attribute :preferences do |object|
8
8
  object.public_preferences.as_json
9
9
  end
10
+
11
+ attribute :icon_name do |payment_method|
12
+ return nil if payment_method.preferences.blank?
13
+
14
+ payment_method.preferences[:icon_name]
15
+ end
16
+
17
+ attribute :payment_option do |payment_method|
18
+ pref = payment_method.preferences
19
+
20
+ pref.blank? || pref[:payment_option].blank? ? payment_method.method_type : pref[:payment_option]
21
+ end
10
22
  end
11
23
  end
12
24
  end
@@ -2,7 +2,7 @@ module Spree
2
2
  module V2
3
3
  module Tenant
4
4
  class UserSerializer < BaseSerializer
5
- attributes :email, :first_name, :last_name, :gender,
5
+ attributes :email, :first_name, :last_name, :gender, :dob,
6
6
  :public_metadata, :completed_orders,
7
7
  :phone_number, :intel_phone_number, :country_code,
8
8
  :otp_enabled, :otp_email, :otp_phone_number,
@@ -0,0 +1,61 @@
1
+ <% content_for :table_filter do %>
2
+ <%= search_form_for [:admin, @search], url: admin_events_path do |f| %>
3
+ <div class="row">
4
+
5
+ <div class="date-range-filter col-12 col-lg-8">
6
+ <div class="form-group">
7
+ <%= label_tag :q_from_date_gt, Spree.t(:date_range) %>
8
+ <div class="row pb-0">
9
+ <div class="col-12 col-md-6 mb-3 mb-md-0">
10
+ <div class="input-group datePickerFrom"
11
+ data-wrap="true"
12
+ data-alt-format="<%= Spree.t(:fpr_human_friendly_date_format, scope: 'date_picker', default: 'M j, Y') %>"
13
+ data-max-date="<%= params[:q][:from_date_lt] %>">
14
+ <%= f.text_field :from_date_gt,
15
+ class: 'form-control js-filterable',
16
+ value: params[:q][:from_date_gt],
17
+ placeholder: Spree.t(:starting_from),
18
+ 'data-input':'' %>
19
+ <%= render partial: 'spree/admin/shared/cal_close' %>
20
+ </div>
21
+ </div>
22
+ <div class="col-12 col-md-6 mt-3 mt-md-0">
23
+ <div class="input-group datePickerTo"
24
+ data-wrap="true"
25
+ data-alt-format="<%= Spree.t(:fpr_human_friendly_date_format, scope: 'date_picker', default: 'M j, Y') %>"
26
+ data-min-date="<%= params[:q][:from_date_gt] %>">
27
+
28
+ <%= f.text_field :from_date_lt,
29
+ class: 'form-control js-filterable',
30
+ value: params[:q][:from_date_lt],
31
+ placeholder: Spree.t(:ending_at),
32
+ 'data-input':'' %>
33
+ <%= render partial: 'spree/admin/shared/cal_close' %>
34
+ </div>
35
+ </div>
36
+ </div>
37
+ </div>
38
+ </div>
39
+
40
+ <div class="col-md-6">
41
+ <div class="form-group">
42
+ <%= label_tag :q_name_cont, "Event Name" %>
43
+ <%= f.text_field :name_cont, class: "form-control js-quick-search-target js-filterable" %>
44
+ </div>
45
+ </div>
46
+
47
+ <div class="col-md-6">
48
+ <div class="form-group">
49
+ <%= label_tag :q_vendor_name_cont, "Vendor" %>
50
+ <%= f.text_field :vendor_name_cont, class: "form-control js-quick-search-target js-filterable" %>
51
+ </div>
52
+ </div>
53
+
54
+
55
+ </div>
56
+ <div class="form-actions">
57
+ <%= button Spree.t(:filter_results), 'search' %>
58
+ <%= link_to Spree.t(:clear_filters), admin_events_path(q: {}), class: "btn btn-secondary" %>
59
+ </div>
60
+ <% end %>
61
+ <% end %>
@@ -0,0 +1,25 @@
1
+ <% content_for :page_tabs do %>
2
+ <li class="nav-item">
3
+ <%= link_to 'All',
4
+ admin_events_path,
5
+ class: "nav-link #{'active' if params[:q][:status_eq].blank? }" %>
6
+ </li>
7
+ <li class="nav-item">
8
+ <%= link_to_with_icon 'calendar.svg',
9
+ Spree.t(:"admin.events.upcoming"),
10
+ admin_events_path(q: { from_date_gt: Date.today, status_eq: 'Upcoming' }),
11
+ class: "nav-link #{'active' if params[:q][:status_eq] == 'Upcoming'}" %>
12
+ </li>
13
+ <li class="nav-item">
14
+ <%= link_to_with_icon 'clock.svg',
15
+ Spree.t(:"admin.events.happening"),
16
+ admin_events_path(q: { from_date_lteq: Date.today, to_date_gteq: Date.today, status_eq: 'Happening' }),
17
+ class: "nav-link #{'active' if params[:q][:status_eq] == 'Happening'}" %>
18
+ </li>
19
+ <li class="nav-item">
20
+ <%= link_to_with_icon 'clock.svg',
21
+ Spree.t(:"admin.events.past"),
22
+ admin_events_path(q: { to_date_lt: Date.today, status_eq: 'Past' }),
23
+ class: "nav-link #{'active' if params[:q][:status_eq] == 'Past'}" %>
24
+ </li>
25
+ <% end %>
@@ -0,0 +1,27 @@
1
+ <div class="table-responsive border rounded bg-white mb-4">
2
+ <table class="table " id="listing_products">
3
+ <thead class="text-muted">
4
+ <tr data-hook="admin_products_index_headers">
5
+ <th><%= sort_link @search,:name, Spree.t(:name), { default_order: "asc" } %></th>
6
+ <th class="col-2">Vendor</th>
7
+ <th><%= sort_link @search,:from_date, Spree.t(:from_date), { default_order: "asc" } %></th>
8
+ <th><%= sort_link @search,:to_date, Spree.t(:to_date), { default_order: "asc" } %></th>
9
+ <th class="col-1 text-center" data-hook="admin_event_header_actions">Actions</th>
10
+ </tr>
11
+ </thead>
12
+ <tbody>
13
+ <% @taxons.each do |taxon| %>
14
+ <tr>
15
+ <td class="col-4 align-middle"><%= taxon.name %></td>
16
+ <td class="col-2 align-middle"><%= taxon.vendor.name if taxon.vendor.present? %></td>
17
+ <td class="col-2 align-middle"><%= taxon.from_date %></td>
18
+ <td class="col-2 align-middle"><%= taxon.to_date %></td>
19
+ <td class="col-1 align-middle text-center" data-hook="admin_products_index_row_actions">
20
+ <%= link_to_edit taxon.id, url: edit_admin_taxonomy_taxon_url(taxon.taxonomy.id, taxon.id), no_text: true if can?(:edit, taxon) %>
21
+ <%= link_to_delete taxon.id, url: admin_taxonomy_taxon_url(taxon.taxonomy.id, taxon.id), no_text: true if can?(:delete, taxon) %>
22
+ </td>
23
+ </tr>
24
+ <% end %>
25
+ </tbody>
26
+ </table>
27
+ </div>
@@ -0,0 +1,15 @@
1
+ <%= content_for :page_title do %>
2
+ <%= Spree.t(:Events) %>
3
+ <% end %>
4
+
5
+ <%= content_for :page_actions do %>
6
+ <%= button_link_to Spree.t(:new_event), new_admin_taxonomy_taxon_path(@taxonomy.id) , class: "btn btn-success", icon: 'add.svg', id: 'admin_new_taxonomy_link' %>
7
+ <% end %>
8
+
9
+ <%= render 'tab' %>
10
+
11
+ <%= render 'search_form' %>
12
+
13
+ <%= render 'table' %>
14
+
15
+ <%= paginate @taxons, theme: 'admin-twitter-bootstrap-4', params: {taxon_id: params[:taxon_id] } %>
@@ -0,0 +1,17 @@
1
+ <div>
2
+ <% events.each do |event| %>
3
+ <div>
4
+ <h6><%= event.options[:inventory_item].variant.options_text %></h6>
5
+ <ul class="list-group mt-1">
6
+ <li class="list-group-item d-flex justify-content-between align-items-center">
7
+ Max capacity
8
+ <span class="badge badge-primary badge-pill"><%= event.options[:inventory_item].max_capacity %></span>
9
+ </li>
10
+ <li class="list-group-item d-flex justify-content-between align-items-center">
11
+ Quantity available
12
+ <span class="badge badge-primary badge-pill"><%= event.options[:inventory_item].quantity_available %></span>
13
+ </li>
14
+ </ul>
15
+ </div>
16
+ <% end %>
17
+ </div>
@@ -0,0 +1,32 @@
1
+ <%= turbo_frame_tag "calendar" do %>
2
+ <%= form_with url: admin_product_stock_managements_path, method: :get, class: "mt-4" do %>
3
+ <%= select_year(params[:year]&.to_i || @year, { start_year: 2025, end_year: @year + 3 }, { name: "year", onchange: "this.form.submit()" }) %>
4
+ <% end %>
5
+
6
+ <div class="c-annual-calendar-container mt-2">
7
+ <div class="c-annual-calendar">
8
+ <% (1..12).each do |month| %>
9
+ <%= month_calendar(start_date: Date.new(@year, month, 1), attribute: :from_date, end_attribute: :to_date, events: @events) do |date, events| %>
10
+ <div class="c-annual-day"
11
+ data-date="<%= date.strftime '%Y-%m-%d' %>"
12
+ type="button"
13
+ tabindex="0"
14
+ data-toggle="popover"
15
+ data-trigger="hover"
16
+ data-placement="right"
17
+ data-html="true"
18
+ data-content="<%= raw render_escape_html partial: "events_popover", locals: { events: events } if events.any? %>">
19
+ <%= date.day %>
20
+ <ul class="p-0 m-0 list-unstyled">
21
+ <% events.each do |event| %>
22
+ <li class="badge badge-warning">
23
+ <%= event.options[:inventory_item].quantity_available %>
24
+ </li>
25
+ <% end %>
26
+ </ul>
27
+ </div>
28
+ <% end %>
29
+ <% end %>
30
+ </div>
31
+ </div>
32
+ <% end %>
@@ -19,10 +19,7 @@
19
19
  <% @variants.each do |variant| %>
20
20
  <tr id="<%= spree_dom_id variant %>" data-hook="admin_product_stock_management_index_rows">
21
21
  <td class="image text-center">
22
- <%= small_image(variant) %>
23
- <div class="mt-4">
24
- Reserved Stock: <%= @reserved_stocks[variant.id] || 0 %></strong>
25
- </div>
22
+ <%= small_image(variant) %>
26
23
  </td>
27
24
  <td>
28
25
  <%= variant.sku_and_options_text %>
@@ -35,14 +32,37 @@
35
32
  <% end %>
36
33
  </div>
37
34
  <% end if can?(:update, @product) && can?(:update, variant) %>
35
+
36
+ <% if defined?(@reserved_stocks) %>
37
+ <div>
38
+ <span type="button" data-toggle="popover" data-trigger="hover" data-placement="right" data-content="This product stock will renew every day">
39
+ <%= svg_icon name: "cart-check.svg", width: '14', height: '14' %>
40
+ </span>
41
+ <%= label_tag "reserved_stock#{variant.id}", "Reserved Stock: #{@reserved_stocks[variant.id] || 0}", class: "m-0" %>
42
+ </div>
43
+ <% end %>
44
+
38
45
  <% if variant.permanent_stock? %>
39
46
  <div>
40
47
  <span type="button" data-toggle="popover" data-trigger="hover" data-placement="right" data-content="This product stock will renew every day">
41
48
  <%= svg_icon name: "info-circle-fill.svg", width: '14', height: '14' %>
42
49
  </span>
43
- <%= label_tag "permanent_stock_#{ variant.id }", Spree.t(:permanent_stock) %>
50
+ <%= label_tag "permanent_stock_#{ variant.id }", Spree.t(:permanent_stock), class: "m-0" %>
44
51
  </div>
45
52
  <% end %>
53
+
54
+ <% if defined?(@inventory_items) %>
55
+ <% @inventory_items[variant.id]&.each do |inventory_item| %>
56
+ <div>
57
+ <%= svg_icon name: "handbag.svg", width: '14', height: '14' %>
58
+ <%= label_tag "max_capacity_#{ inventory_item.id }", "Max capacity: #{inventory_item.max_capacity}", class: "m-0" %>
59
+ </div>
60
+ <div>
61
+ <%= svg_icon name: "approve.svg", width: '14', height: '14' %>
62
+ <%= label_tag "quantity_available_#{ inventory_item.id }", "Quantity available: #{inventory_item.quantity_available}", class: "m-0" %>
63
+ </div>
64
+ <% end %>
65
+ <% end %>
46
66
  </td>
47
67
 
48
68
  <td colspan="3" class="stock_location_info">
@@ -53,3 +73,9 @@
53
73
  </tbody>
54
74
  </table>
55
75
  </div>
76
+
77
+ <%= turbo_frame_tag "calendar", src: calendar_admin_product_stock_managements_path(year: params[:year]) do %>
78
+ <div class="spinner-border mt-2" role="status">
79
+ <span class="sr-only">Loading...</span>
80
+ </div>
81
+ <% end if @product.permanent_stock? %>