spree_cm_commissioner 1.10.0.pre.pre1 → 1.11.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.
- checksums.yaml +4 -4
- data/.github/workflows/test_and_build_gem.yml +131 -100
- data/.vscode/settings.json +1 -1
- data/Gemfile.lock +1 -1
- data/Rakefile +33 -4
- data/app/controllers/spree/admin/stock_managements_controller.rb +39 -0
- data/app/controllers/spree/api/v2/storefront/queue_cart/line_items_controller.rb +2 -2
- data/app/interactors/spree_cm_commissioner/create_event.rb +23 -0
- data/app/interactors/spree_cm_commissioner/ensure_correct_product_type.rb +40 -0
- data/app/interactors/spree_cm_commissioner/stock/inventory_items_adjuster.rb +13 -0
- data/app/interactors/spree_cm_commissioner/stock/permanent_inventory_items_generator.rb +6 -2
- data/app/interactors/spree_cm_commissioner/stock/stock_movement_creator.rb +24 -0
- data/app/interactors/spree_cm_commissioner/vattanac_bank_initiator.rb +2 -2
- data/app/jobs/spree_cm_commissioner/ensure_correct_product_type_job.rb +7 -0
- data/app/jobs/spree_cm_commissioner/stock/inventory_items_adjuster_job.rb +11 -0
- data/app/models/concerns/spree_cm_commissioner/option_type_attr_type.rb +3 -2
- data/app/models/concerns/spree_cm_commissioner/product_delegation.rb +1 -3
- data/app/models/spree_cm_commissioner/inventory_item.rb +21 -3
- data/app/models/spree_cm_commissioner/line_item_decorator.rb +16 -6
- data/app/models/spree_cm_commissioner/place.rb +11 -2
- data/app/models/spree_cm_commissioner/product_decorator.rb +8 -2
- data/app/models/spree_cm_commissioner/redis_stock/inventory_updater.rb +14 -2
- data/app/models/spree_cm_commissioner/redis_stock/line_items_cached_inventory_items_builder.rb +2 -8
- data/app/models/spree_cm_commissioner/stock_item_decorator.rb +18 -0
- data/app/models/spree_cm_commissioner/taxon_decorator.rb +11 -0
- data/app/models/spree_cm_commissioner/taxon_option_type.rb +8 -0
- data/app/models/spree_cm_commissioner/taxon_option_value.rb +8 -0
- data/app/models/spree_cm_commissioner/trip.rb +0 -11
- data/app/models/spree_cm_commissioner/trip_connection.rb +1 -0
- data/app/models/spree_cm_commissioner/trip_stop.rb +12 -5
- data/app/models/spree_cm_commissioner/variant_decorator.rb +19 -11
- data/app/models/spree_cm_commissioner/variant_options.rb +0 -14
- data/app/models/spree_cm_commissioner/vendor_stop.rb +2 -1
- data/app/queries/spree_cm_commissioner/trip_query.rb +11 -11
- data/app/queries/spree_cm_commissioner/vendor_stop_place_query.rb +54 -0
- data/app/serializers/spree/v2/tenant/guest_serializer.rb +1 -0
- data/app/services/spree_cm_commissioner/organizer/export_guest_csv_service.rb +2 -0
- data/app/views/spree/admin/stock_managements/_events_popover.html.erb +12 -6
- data/app/views/spree/admin/stock_managements/_variant_stock_items.html.erb +3 -1
- data/app/views/spree/admin/stock_managements/calendar.html.erb +6 -3
- data/app/views/spree/admin/stock_managements/index.html.erb +9 -0
- data/config/initializers/spree_permitted_attributes.rb +5 -0
- data/db/migrate/20250418072528_add_nested_set_columns_to_places.rb +10 -0
- data/db/migrate/20250430091742_create_cm_taxon_option_types.rb +9 -0
- data/db/migrate/20250430092928_create_cm_taxon_option_values.rb +9 -0
- data/db/migrate/20250502025848_add_index_to_spree_products.rb +5 -0
- data/db/migrate/20250502030001_add_product_type_to_spree_variants.rb +5 -0
- data/db/migrate/20250502030002_add_product_type_to_spree_line_items.rb +5 -0
- data/db/migrate/20250506092929_add_trip_count_to_cm_vendor_stops.rb +5 -0
- data/lib/generators/spree_cm_commissioner/install/install_generator.rb +11 -3
- data/lib/spree_cm_commissioner/test_helper/factories/homepage_section_relatable_factory.rb +1 -1
- data/lib/spree_cm_commissioner/test_helper/factories/inventory_item_factory.rb +1 -1
- data/lib/spree_cm_commissioner/test_helper/factories/line_item_factory.rb +1 -1
- data/lib/spree_cm_commissioner/test_helper/factories/place_factory.rb +11 -1
- data/lib/spree_cm_commissioner/test_helper/factories/product_factory.rb +18 -5
- data/lib/spree_cm_commissioner/test_helper/factories/stock_location_factory.rb +2 -2
- data/lib/spree_cm_commissioner/test_helper/factories/variant_factory.rb +12 -1
- data/lib/spree_cm_commissioner/test_helper/factories/vendor_factory.rb +1 -1
- data/lib/spree_cm_commissioner/version.rb +1 -1
- data/lib/tasks/ensure_correct_product_type.rake +7 -0
- data/lib/tasks/migrate_and_rebuild_place_hierarchy.rake +9 -0
- data/lib/tasks/update_orphan_root_places.rake +7 -0
- metadata +20 -3
- data/app/models/spree_cm_commissioner/stock_movement_decorator.rb +0 -34
@@ -8,19 +8,37 @@ module SpreeCmCommissioner
|
|
8
8
|
# Validation
|
9
9
|
validates :quantity_available, numericality: { greater_than_or_equal_to: 0 }
|
10
10
|
validates :max_capacity, numericality: { greater_than_or_equal_to: 0 } # Originally inventory of each variant.
|
11
|
-
validates :inventory_date, presence: true, if:
|
11
|
+
validates :inventory_date, presence: true, if: :permanent_stock?
|
12
12
|
validates :variant_id, uniqueness: { scope: :inventory_date, message: -> (object, _data) { "The variant is taken on #{object.inventory_date}" } }
|
13
13
|
|
14
14
|
# Scope
|
15
|
-
scope :for_product, -> (type) { where(product_type: type) }
|
16
15
|
scope :active, -> { where(inventory_date: nil).or(where('inventory_date >= ?', Time.zone.today)) }
|
17
16
|
|
17
|
+
before_save -> { self.product_type = variant.product_type }, if: -> { product_type.nil? }
|
18
|
+
|
18
19
|
def adjust_quantity!(quantity)
|
19
20
|
with_lock do
|
20
21
|
self.max_capacity = max_capacity + quantity
|
21
22
|
self.quantity_available = quantity_available + quantity
|
22
|
-
|
23
23
|
save!
|
24
|
+
|
25
|
+
# When user has been searched or booked a product, it has cached the quantity in redis,
|
26
|
+
# So we need to update redis cache if inventory key has been created in redis
|
27
|
+
adjust_quantity_in_redis(quantity)
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
def adjust_quantity_in_redis(quantity)
|
32
|
+
SpreeCmCommissioner.redis_pool.with do |redis|
|
33
|
+
cached_quantity_available = redis.get("inventory:#{id}")
|
34
|
+
# ignore if redis doesn't exist
|
35
|
+
return if cached_quantity_available.nil? # rubocop:disable Lint/NonLocalExitFromIterator
|
36
|
+
|
37
|
+
if quantity.positive?
|
38
|
+
redis.incrby("inventory:#{id}", quantity)
|
39
|
+
else
|
40
|
+
redis.decrby("inventory:#{id}", quantity.abs)
|
41
|
+
end
|
24
42
|
end
|
25
43
|
end
|
26
44
|
|
@@ -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
|
@@ -9,7 +9,11 @@ module SpreeCmCommissioner
|
|
9
9
|
base.has_one :google_wallet, class_name: 'SpreeCmCommissioner::GoogleWallet', through: :product
|
10
10
|
|
11
11
|
base.has_many :option_types, through: :product
|
12
|
-
|
12
|
+
|
13
|
+
base.has_many :inventory_items, lambda { |line_item|
|
14
|
+
where(inventory_date: nil).or(where(inventory_date: line_item.date_range))
|
15
|
+
}, through: :variant
|
16
|
+
|
13
17
|
base.has_many :taxons, class_name: 'Spree::Taxon', through: :product
|
14
18
|
base.has_many :guests, class_name: 'SpreeCmCommissioner::Guest', dependent: :destroy
|
15
19
|
base.has_many :pending_guests, pending_guests_query, class_name: 'SpreeCmCommissioner::Guest', dependent: :destroy
|
@@ -22,16 +26,15 @@ module SpreeCmCommissioner
|
|
22
26
|
base.validate :validate_seats_reservation, if: :transit?
|
23
27
|
|
24
28
|
base.before_create :add_due_date, if: :subscription?
|
29
|
+
base.before_save -> { self.product_type = variant.product_type }, if: -> { product_type.nil? }
|
25
30
|
|
26
31
|
base.validate :ensure_not_exceed_max_quantity_per_order, if: -> { variant&.max_quantity_per_order.present? }
|
27
32
|
|
28
33
|
base.whitelisted_ransackable_associations |= %w[guests order]
|
29
34
|
base.whitelisted_ransackable_attributes |= %w[number to_date from_date vendor_id]
|
30
35
|
|
31
|
-
base.delegate :delivery_required?, :
|
36
|
+
base.delegate :delivery_required?, :high_demand,
|
32
37
|
to: :variant
|
33
|
-
base.delegate :discontinue_on, :product_type, :accommodation?, :service?, :ecommerce?, :need_confirmation,
|
34
|
-
to: :product
|
35
38
|
|
36
39
|
base.accepts_nested_attributes_for :guests, allow_destroy: true
|
37
40
|
base.accepts_nested_attributes_for :line_item_seats, allow_destroy: true
|
@@ -42,6 +45,10 @@ module SpreeCmCommissioner
|
|
42
45
|
json_api_columns << :vendor_id
|
43
46
|
end
|
44
47
|
|
48
|
+
def discontinue_on
|
49
|
+
variant.discontinue_on || product.discontinue_on
|
50
|
+
end
|
51
|
+
|
45
52
|
def base.search_by_qr_data!(data)
|
46
53
|
matches = data.match(/(R\d+)-([A-Za-z0-9_\-]+)-(L\d+)/)&.captures
|
47
54
|
|
@@ -60,6 +67,7 @@ module SpreeCmCommissioner
|
|
60
67
|
base.include SpreeCmCommissioner::LineItemDurationable
|
61
68
|
base.include SpreeCmCommissioner::LineItemsFilterScope
|
62
69
|
base.include SpreeCmCommissioner::LineItemGuestsConcern
|
70
|
+
base.include SpreeCmCommissioner::ProductType
|
63
71
|
base.include SpreeCmCommissioner::ProductDelegation
|
64
72
|
base.include SpreeCmCommissioner::KycBitwise
|
65
73
|
end
|
@@ -175,7 +183,7 @@ module SpreeCmCommissioner
|
|
175
183
|
|
176
184
|
# override
|
177
185
|
def sufficient_stock?
|
178
|
-
return transit_sufficient_stock? if
|
186
|
+
return transit_sufficient_stock? if transit?
|
179
187
|
|
180
188
|
SpreeCmCommissioner::Stock::LineItemAvailabilityChecker.new(self).can_supply?(quantity)
|
181
189
|
end
|
@@ -231,6 +239,8 @@ module SpreeCmCommissioner
|
|
231
239
|
end
|
232
240
|
|
233
241
|
def validate_seats_reservation
|
242
|
+
return if reservation_trip.blank?
|
243
|
+
|
234
244
|
if reservation_trip.allow_seat_selection && !selected_seats_available?
|
235
245
|
errors.add(:base, :some_seats_are_booked, message: 'Some seats are already booked')
|
236
246
|
elsif !reservation_trip.allow_seat_selection && !seat_quantity_available?(reservation_trip)
|
@@ -2,6 +2,8 @@ require_dependency 'spree_cm_commissioner'
|
|
2
2
|
|
3
3
|
module SpreeCmCommissioner
|
4
4
|
class Place < ApplicationRecord
|
5
|
+
acts_as_nested_set
|
6
|
+
|
5
7
|
validates :reference, presence: true, if: :validate_reference?
|
6
8
|
validates :lat, presence: true, if: :validate_lat?
|
7
9
|
validates :lon, presence: true, if: :validate_lon?
|
@@ -13,15 +15,22 @@ module SpreeCmCommissioner
|
|
13
15
|
|
14
16
|
has_many :product_places, class_name: 'SpreeCmCommissioner::ProductPlace', dependent: :destroy
|
15
17
|
has_many :products, through: :product_places
|
16
|
-
has_many :children, class_name: 'SpreeCmCommissioner::Place', foreign_key: :parent_id, dependent: :destroy
|
17
|
-
belongs_to :parent, class_name: 'SpreeCmCommissioner::Place', optional: true
|
18
18
|
|
19
|
+
has_many :children, -> { order(:lft) }, class_name: 'SpreeCmCommissioner::Place', foreign_key: :parent_id, dependent: :destroy
|
19
20
|
has_many :vendor_stops, class_name: 'SpreeCmCommissioner::VendorStop', dependent: :destroy
|
20
21
|
|
21
22
|
def self.ransackable_attributes(auth_object = nil)
|
22
23
|
super & %w[name code]
|
23
24
|
end
|
24
25
|
|
26
|
+
def full_path_name
|
27
|
+
self_and_ancestors.map(&:name).reverse.join(', ')
|
28
|
+
end
|
29
|
+
|
30
|
+
def path_ids
|
31
|
+
self_and_ancestors.map(&:id)
|
32
|
+
end
|
33
|
+
|
25
34
|
def validate_reference?
|
26
35
|
Spree::Store.default.code.exclude?('billing')
|
27
36
|
end
|
@@ -58,9 +58,8 @@ module SpreeCmCommissioner
|
|
58
58
|
base.scope :subscribable, -> { where(subscribable: 1) }
|
59
59
|
|
60
60
|
base.validate :validate_event_taxons, if: -> { taxons.event.present? }
|
61
|
-
|
62
61
|
base.validate :validate_product_date, if: -> { available_on.present? && discontinue_on.present? }
|
63
|
-
|
62
|
+
base.validate :product_type_unchanged, on: :update
|
64
63
|
base.validates :commission_rate, numericality: { greater_than_or_equal_to: 0, less_than_or_equal_to: 100 }, allow_nil: true
|
65
64
|
|
66
65
|
base.whitelisted_ransackable_attributes = %w[description name slug discontinue_on status vendor_id short_name route_type]
|
@@ -103,6 +102,13 @@ module SpreeCmCommissioner
|
|
103
102
|
|
104
103
|
errors.add(:discontinue_on, 'must be after the available on date')
|
105
104
|
end
|
105
|
+
|
106
|
+
def product_type_unchanged
|
107
|
+
return if product_type_was.nil?
|
108
|
+
return unless product_type_changed?
|
109
|
+
|
110
|
+
errors.add(:product_type, 'cannot be changed once set')
|
111
|
+
end
|
106
112
|
end
|
107
113
|
end
|
108
114
|
|
@@ -34,8 +34,20 @@ module SpreeCmCommissioner
|
|
34
34
|
|
35
35
|
private
|
36
36
|
|
37
|
+
# only unstock or restock when variants is available, track inventory, not backorderable & not need_confirmation.
|
38
|
+
# otherwise, we can ignore them.
|
37
39
|
def line_items
|
38
|
-
@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
|
39
51
|
end
|
40
52
|
|
41
53
|
def unstock(keys, quantities)
|
@@ -52,7 +64,7 @@ module SpreeCmCommissioner
|
|
52
64
|
|
53
65
|
# Return: [CachedInventoryItem(...), CachedInventoryItem(...)]
|
54
66
|
def cached_inventory_items
|
55
|
-
@cached_inventory_items ||= SpreeCmCommissioner::RedisStock::LineItemsCachedInventoryItemsBuilder.new(line_item_ids:
|
67
|
+
@cached_inventory_items ||= SpreeCmCommissioner::RedisStock::LineItemsCachedInventoryItemsBuilder.new(line_item_ids: line_items.pluck(:id))
|
56
68
|
.call.values.flatten
|
57
69
|
end
|
58
70
|
|
data/app/models/spree_cm_commissioner/redis_stock/line_items_cached_inventory_items_builder.rb
CHANGED
@@ -25,17 +25,11 @@ module SpreeCmCommissioner
|
|
25
25
|
end
|
26
26
|
|
27
27
|
def inventory_items
|
28
|
-
@inventory_items ||= line_items.flat_map
|
29
|
-
# TODO: N+1, we could fix but have a product_type in line item
|
30
|
-
# then include inventory_items in line item directly #2581
|
31
|
-
scope = line_item.inventory_items
|
32
|
-
scope = scope.where(inventory_date: line_item.date_range) if line_item.permanent_stock?
|
33
|
-
scope
|
34
|
-
end
|
28
|
+
@inventory_items ||= line_items.flat_map(&:inventory_items)
|
35
29
|
end
|
36
30
|
|
37
31
|
def line_items
|
38
|
-
@line_items ||= Spree::LineItem.where(id: line_item_ids).includes(
|
32
|
+
@line_items ||= Spree::LineItem.where(id: line_item_ids).includes(:inventory_items)
|
39
33
|
end
|
40
34
|
end
|
41
35
|
end
|
@@ -3,11 +3,29 @@ module SpreeCmCommissioner
|
|
3
3
|
def self.prepended(base)
|
4
4
|
base.has_one :vendor, through: :variant
|
5
5
|
base.after_save :update_vendor_total_inventory, if: :saved_change_to_count_on_hand?
|
6
|
+
|
7
|
+
base.after_commit :create_inventory_items, on: :create
|
8
|
+
base.after_commit :adjust_inventory_items_async, on: :destroy
|
6
9
|
end
|
7
10
|
|
8
11
|
def update_vendor_total_inventory
|
9
12
|
SpreeCmCommissioner::VendorJob.perform_later(vendor.id) if vendor.present?
|
10
13
|
end
|
14
|
+
|
15
|
+
private
|
16
|
+
|
17
|
+
def create_inventory_items
|
18
|
+
if variant.permanent_stock?
|
19
|
+
SpreeCmCommissioner::Stock::PermanentInventoryItemsGeneratorJob.perform_later(variant_ids: [variant.id])
|
20
|
+
else
|
21
|
+
variant.create_default_non_permanent_inventory_item! unless variant.default_inventory_item_exist?
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
# When admin delete stock item, it will deduct stock from inventory item
|
26
|
+
def adjust_inventory_items_async
|
27
|
+
SpreeCmCommissioner::Stock::InventoryItemsAdjusterJob.perform_later(variant_id: variant_id, quantity: -count_on_hand)
|
28
|
+
end
|
11
29
|
end
|
12
30
|
end
|
13
31
|
|
@@ -57,6 +57,9 @@ module SpreeCmCommissioner
|
|
57
57
|
base.has_many :event_blazer_queries, class_name: 'SpreeCmCommissioner::TaxonBlazerQuery'
|
58
58
|
base.has_many :blazer_queries, through: :event_blazer_queries, class_name: 'Blazer::Query'
|
59
59
|
|
60
|
+
base.has_many :taxon_option_types, class_name: 'SpreeCmCommissioner::TaxonOptionType'
|
61
|
+
base.has_many :taxon_option_values, class_name: 'SpreeCmCommissioner::TaxonOptionValue'
|
62
|
+
|
60
63
|
def base.active_homepage_events
|
61
64
|
joins(:homepage_section_relatables)
|
62
65
|
.joins("INNER JOIN spree_taxons taxon ON taxon.id = cm_homepage_section_relatables.relatable_id
|
@@ -96,6 +99,14 @@ module SpreeCmCommissioner
|
|
96
99
|
.uniq
|
97
100
|
end
|
98
101
|
|
102
|
+
def selected_option_types
|
103
|
+
taxon_option_types.pluck(:option_type_id)
|
104
|
+
end
|
105
|
+
|
106
|
+
def selected_option_values
|
107
|
+
taxon_option_values.pluck(:option_value_id)
|
108
|
+
end
|
109
|
+
|
99
110
|
def event_url
|
100
111
|
"https://#{Spree::Store.default.url}/t/#{permalink}"
|
101
112
|
end
|
@@ -0,0 +1,8 @@
|
|
1
|
+
require_dependency 'spree_cm_commissioner'
|
2
|
+
|
3
|
+
module SpreeCmCommissioner
|
4
|
+
class TaxonOptionType < ApplicationRecord
|
5
|
+
belongs_to :taxon, class_name: 'Spree::Taxon', dependent: :destroy
|
6
|
+
belongs_to :option_type, class_name: 'Spree::OptionType', dependent: :destroy
|
7
|
+
end
|
8
|
+
end
|
@@ -0,0 +1,8 @@
|
|
1
|
+
require_dependency 'spree_cm_commissioner'
|
2
|
+
|
3
|
+
module SpreeCmCommissioner
|
4
|
+
class TaxonOptionValue < ApplicationRecord
|
5
|
+
belongs_to :taxon, class_name: 'Spree::Taxon', dependent: :destroy
|
6
|
+
belongs_to :option_value, class_name: 'Spree::OptionValue', dependent: :destroy
|
7
|
+
end
|
8
|
+
end
|
@@ -14,23 +14,12 @@ module SpreeCmCommissioner
|
|
14
14
|
validates :duration, numericality: { greater_than: 0 }
|
15
15
|
validate :origin_and_destination_cannot_be_the_same
|
16
16
|
|
17
|
-
has_many :trip_stops, class_name: 'SpreeCmCommissioner::TripStop', dependent: :destroy
|
18
|
-
|
19
|
-
after_commit :create_trip_stops
|
20
|
-
|
21
|
-
accepts_nested_attributes_for :trip_stops, allow_destroy: true
|
22
|
-
|
23
17
|
def convert_duration_to_seconds
|
24
18
|
return if hours.blank? && minutes.blank? && seconds.blank?
|
25
19
|
|
26
20
|
self.duration = (hours.to_i * 3600) + (minutes.to_i * 60) + seconds.to_i
|
27
21
|
end
|
28
22
|
|
29
|
-
def create_trip_stops
|
30
|
-
trip_stops.create(stop_type: :boarding, stop_id: origin_id)
|
31
|
-
trip_stops.create(stop_type: :drop_off, stop_id: destination_id)
|
32
|
-
end
|
33
|
-
|
34
23
|
def duration_in_hms
|
35
24
|
return { hours: 0, minutes: 0, seconds: 0 } if duration.nil?
|
36
25
|
|
@@ -8,22 +8,29 @@ module SpreeCmCommissioner
|
|
8
8
|
belongs_to :stop, class_name: 'SpreeCmCommissioner::Place'
|
9
9
|
|
10
10
|
before_validation :set_stop_name
|
11
|
-
|
12
|
-
|
11
|
+
after_destroy :decrement_trip_count
|
12
|
+
after_commit :create_vendor_stop
|
13
13
|
validates :stop_id, uniqueness: { scope: :trip_id }
|
14
14
|
|
15
15
|
def set_stop_name
|
16
|
-
self.stop_name = stop.name
|
16
|
+
self.stop_name = stop.name if stop.present?
|
17
17
|
end
|
18
18
|
|
19
19
|
def create_vendor_stop
|
20
|
-
vendor.vendor_stops.where(stop_id: stop_id, stop_type: stop_type).first_or_create
|
20
|
+
vendor_stop = vendor.vendor_stops.where(stop_id: stop_id, stop_type: stop_type).first_or_create
|
21
|
+
vendor_stop.update(trip_count: vendor_stop.trip_count.to_i + 1)
|
21
22
|
end
|
22
23
|
|
23
24
|
private
|
24
25
|
|
26
|
+
def decrement_trip_count
|
27
|
+
vendor.vendor_stops.where(stop_id: stop_id, stop_type: stop_type).find_each do |vendor_stop|
|
28
|
+
vendor_stop.update(trip_count: [vendor_stop.trip_count - 1, 0].max)
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
25
32
|
def vendor
|
26
|
-
Spree::Product.find(trip
|
33
|
+
Spree::Product.find(trip&.product_id).vendor
|
27
34
|
end
|
28
35
|
end
|
29
36
|
end
|
@@ -1,10 +1,9 @@
|
|
1
1
|
module SpreeCmCommissioner
|
2
2
|
module VariantDecorator
|
3
|
-
def self.prepended(base)
|
3
|
+
def self.prepended(base) # rubocop:disable Metrics/AbcSize
|
4
4
|
base.include SpreeCmCommissioner::ProductType
|
5
5
|
base.include SpreeCmCommissioner::ProductDelegation
|
6
6
|
base.include SpreeCmCommissioner::VariantOptionsConcern
|
7
|
-
|
8
7
|
base.after_commit :update_vendor_price
|
9
8
|
base.validate :validate_option_types
|
10
9
|
base.before_save -> { self.track_inventory = false }, if: :subscribable?
|
@@ -25,13 +24,26 @@ module SpreeCmCommissioner
|
|
25
24
|
base.has_many :inventory_items, class_name: 'SpreeCmCommissioner::InventoryItem'
|
26
25
|
|
27
26
|
base.scope :subscribable, -> { active.joins(:product).where(product: { subscribable: true, status: :active }) }
|
28
|
-
base.scope :with_permanent_stock, -> {
|
29
|
-
base.scope :with_non_permanent_stock, -> {
|
27
|
+
base.scope :with_permanent_stock, -> { where(product_type: base::PERMANENT_STOCK_PRODUCT_TYPES) }
|
28
|
+
base.scope :with_non_permanent_stock, -> { where.not(product_type: base::PERMANENT_STOCK_PRODUCT_TYPES) }
|
30
29
|
|
31
30
|
base.has_one :trip,
|
32
31
|
class_name: 'SpreeCmCommissioner::Trip'
|
32
|
+
base.has_many :trip_stops, class_name: 'SpreeCmCommissioner::TripStop', dependent: :destroy, foreign_key: :trip_id
|
33
33
|
base.accepts_nested_attributes_for :option_values
|
34
|
-
|
34
|
+
|
35
|
+
base.before_save -> { self.product_type = product.product_type }, if: -> { product_type.nil? }
|
36
|
+
|
37
|
+
base.after_commit :sync_trip, if: :transit?
|
38
|
+
base.accepts_nested_attributes_for :trip_stops, allow_destroy: true
|
39
|
+
base.after_commit :create_trip_stops, if: :transit?
|
40
|
+
end
|
41
|
+
|
42
|
+
def create_trip_stops
|
43
|
+
return if is_master?
|
44
|
+
|
45
|
+
trip_stops.find_or_create_by(stop_type: :boarding, stop_id: options.origin)
|
46
|
+
trip_stops.find_or_create_by(stop_type: :drop_off, stop_id: options.destination)
|
35
47
|
end
|
36
48
|
|
37
49
|
def delivery_required?
|
@@ -82,10 +94,6 @@ module SpreeCmCommissioner
|
|
82
94
|
"#{display_sku} - #{display_price}"
|
83
95
|
end
|
84
96
|
|
85
|
-
def transit?
|
86
|
-
product.product_type == 'transit'
|
87
|
-
end
|
88
|
-
|
89
97
|
# override
|
90
98
|
def in_stock?(options = {})
|
91
99
|
SpreeCmCommissioner::Stock::AvailabilityChecker.new(self, options).can_supply?
|
@@ -94,7 +102,7 @@ module SpreeCmCommissioner
|
|
94
102
|
private
|
95
103
|
|
96
104
|
def update_vendor_price
|
97
|
-
return unless vendor.present? &&
|
105
|
+
return unless vendor.present? && product_type == vendor&.primary_product_type
|
98
106
|
|
99
107
|
vendor.update(min_price: price) if price < vendor.min_price
|
100
108
|
vendor.update(max_price: price) if price > vendor.max_price
|
@@ -120,7 +128,7 @@ module SpreeCmCommissioner
|
|
120
128
|
end
|
121
129
|
|
122
130
|
def sync_trip
|
123
|
-
return unless
|
131
|
+
return unless transit?
|
124
132
|
|
125
133
|
trip = SpreeCmCommissioner::Trip.find_or_initialize_by(variant_id: id)
|
126
134
|
|
@@ -166,19 +166,5 @@ module SpreeCmCommissioner
|
|
166
166
|
Time.zone.parse(time) if time.present?
|
167
167
|
end
|
168
168
|
end
|
169
|
-
|
170
|
-
def arrival_time
|
171
|
-
@arrival_time ||= departure_time + total_duration_in_minutes.minutes
|
172
|
-
end
|
173
|
-
|
174
|
-
def total_duration_in_minutes
|
175
|
-
total_duration_in_minutes = 0
|
176
|
-
|
177
|
-
total_duration_in_minutes += duration_in_hours * 60 if duration_in_hours.present?
|
178
|
-
total_duration_in_minutes += duration_in_minutes if duration_in_minutes.present?
|
179
|
-
total_duration_in_minutes += duration_in_seconds / 60 if duration_in_seconds.present?
|
180
|
-
|
181
|
-
total_duration_in_minutes
|
182
|
-
end
|
183
169
|
end
|
184
170
|
end
|
@@ -4,6 +4,7 @@ module SpreeCmCommissioner
|
|
4
4
|
belongs_to :vendor, class_name: 'Spree::Vendor'
|
5
5
|
belongs_to :stop, class_name: 'SpreeCmCommissioner::Place'
|
6
6
|
|
7
|
-
|
7
|
+
validates :trip_count, numericality: { greater_than_or_equal_to: 0 }
|
8
|
+
enum :stop_type, { boarding: 0, drop_off: 1 }
|
8
9
|
end
|
9
10
|
end
|
@@ -34,11 +34,11 @@ module SpreeCmCommissioner
|
|
34
34
|
trips.vehicle_id AS vehicle_id'
|
35
35
|
)
|
36
36
|
.joins('INNER JOIN cm_trips AS trips ON trips.variant_id = spree_variants.id')
|
37
|
-
.joins('INNER JOIN cm_trip_stops AS boarding ON boarding.trip_id =
|
38
|
-
.joins('INNER JOIN cm_trip_stops AS drop_off ON drop_off.trip_id =
|
37
|
+
.joins('INNER JOIN cm_trip_stops AS boarding ON boarding.trip_id = spree_variants.id AND boarding.stop_type = 0')
|
38
|
+
.joins('INNER JOIN cm_trip_stops AS drop_off ON drop_off.trip_id = spree_variants.id AND drop_off.stop_type = 1')
|
39
39
|
.joins('INNER JOIN spree_vendors AS vendors ON vendors.id = spree_variants.vendor_id')
|
40
40
|
.joins('INNER JOIN spree_products AS routes ON routes.id = spree_variants.product_id')
|
41
|
-
.where('
|
41
|
+
.where('boarding.stop_id = ? AND drop_off.stop_id = ?', origin_id, destination_id)
|
42
42
|
|
43
43
|
result.map do |trip|
|
44
44
|
trip_result_options = {
|
@@ -69,9 +69,9 @@ module SpreeCmCommissioner
|
|
69
69
|
INNER JOIN spree_products AS routes2 ON routes2.id = variant2.product_id
|
70
70
|
INNER JOIN cm_trips AS trip1 ON trip1.variant_id = cm_trip_connections.from_trip_id
|
71
71
|
INNER JOIN cm_trips AS trip2 ON trip2.variant_id = cm_trip_connections.to_trip_id
|
72
|
-
INNER JOIN
|
73
|
-
INNER JOIN
|
74
|
-
INNER JOIN
|
72
|
+
INNER JOIN cm_trip_stops trip1_origin ON trip1_origin.trip_id = variant1.id AND trip1_origin.stop_type = 0
|
73
|
+
INNER JOIN cm_trip_stops trip2_origin ON trip2_origin.trip_id = variant2.id AND trip2_origin.stop_type = 0
|
74
|
+
INNER JOIN cm_trip_stops trip2_destination ON trip2_destination.trip_id = variant2.id AND trip2_destination.stop_type = 1
|
75
75
|
INNER JOIN spree_vendors AS vendor1 ON vendor1.id = variant1.vendor_id
|
76
76
|
INNER JOIN spree_vendors AS vendor2 ON vendor2.id = variant2.vendor_id'
|
77
77
|
)
|
@@ -93,12 +93,12 @@ module SpreeCmCommissioner
|
|
93
93
|
trip2.vehicle_id AS trip2_vehicle,
|
94
94
|
routes2.short_name AS route2_short_name,
|
95
95
|
routes2.name AS route2_name,
|
96
|
-
trip1_origin.
|
97
|
-
trip2_origin.
|
98
|
-
trip2_destination.
|
96
|
+
trip1_origin.stop_name AS trip1_origin,
|
97
|
+
trip2_origin.stop_name AS trip2_origin,
|
98
|
+
trip2_destination.stop_name AS trip2_destination,
|
99
99
|
vendor2.name AS trip2_vendor_name'
|
100
|
-
)
|
101
|
-
|
100
|
+
).where('trip1_origin.stop_id = ? AND trip2_destination.stop_id = ?', origin_id, destination_id
|
101
|
+
).uniq
|
102
102
|
|
103
103
|
return [] if result.blank?
|
104
104
|
|
@@ -0,0 +1,54 @@
|
|
1
|
+
module SpreeCmCommissioner
|
2
|
+
class VendorStopPlaceQuery
|
3
|
+
attr_reader :query, :vendor_id, :stop_type, :reference_stop_id
|
4
|
+
|
5
|
+
def initialize(query:, vendor_id:, stop_type: :boarding, reference_stop_id: nil)
|
6
|
+
@query = query.to_s.strip
|
7
|
+
@vendor_id = vendor_id
|
8
|
+
@stop_type = stop_type
|
9
|
+
@reference_stop_id = reference_stop_id
|
10
|
+
end
|
11
|
+
|
12
|
+
def call
|
13
|
+
results = vendor_stops
|
14
|
+
return [] if results.empty?
|
15
|
+
|
16
|
+
places = results.includes(:stop).map(&:stop).compact.uniq
|
17
|
+
|
18
|
+
format_places(places)
|
19
|
+
end
|
20
|
+
|
21
|
+
private
|
22
|
+
|
23
|
+
def vendor_stops
|
24
|
+
scope = base_scope
|
25
|
+
return scope if query.blank?
|
26
|
+
|
27
|
+
scope = scope.where.not(stop_id: reference_stop_id) if reference_stop_id.present?
|
28
|
+
scope.where('cm_places.name ILIKE ?', "%#{query}%")
|
29
|
+
end
|
30
|
+
|
31
|
+
def base_scope
|
32
|
+
SpreeCmCommissioner::VendorStop
|
33
|
+
.joins(:stop)
|
34
|
+
.where(vendor_id: vendor_id)
|
35
|
+
.where(stop_type: stop_type)
|
36
|
+
.order(trip_count: :desc)
|
37
|
+
.where.not(trip_count: 0)
|
38
|
+
end
|
39
|
+
|
40
|
+
def format_places(places)
|
41
|
+
places
|
42
|
+
.sort_by { |p| [-p.depth, -p.lft] }
|
43
|
+
.filter_map do |place|
|
44
|
+
next if place.depth.zero?
|
45
|
+
|
46
|
+
{
|
47
|
+
id: place.id,
|
48
|
+
parent_id: place.parent_id,
|
49
|
+
name: place.self_and_ancestors.map(&:name).reverse.join(', ')
|
50
|
+
}
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
@@ -13,6 +13,7 @@ module Spree
|
|
13
13
|
|
14
14
|
belongs_to :occupation, serializer: Spree::V2::Tenant::TaxonSerializer
|
15
15
|
belongs_to :nationality, serializer: Spree::V2::Tenant::TaxonSerializer
|
16
|
+
has_one :id_card, serializer: Spree::V2::Tenant::IdCardSerializer
|
16
17
|
|
17
18
|
# allowed_checkout updates frequently
|
18
19
|
cache_options store: nil
|
@@ -48,6 +48,8 @@ module SpreeCmCommissioner
|
|
48
48
|
end
|
49
49
|
|
50
50
|
def fetch_option_value(guest, option_type_name)
|
51
|
+
return guest.formatted_bib_number if option_type_name == 'bib-prefix'
|
52
|
+
|
51
53
|
guest.line_item&.variant&.find_option_value_name_for(option_type_name: option_type_name)
|
52
54
|
end
|
53
55
|
|
@@ -1,17 +1,23 @@
|
|
1
1
|
<div>
|
2
2
|
<% events.each do |event| %>
|
3
|
-
|
4
|
-
|
5
|
-
|
3
|
+
<% inventory_item = event.options[:inventory_item] %>
|
4
|
+
|
5
|
+
<div class="mb-3">
|
6
|
+
<h6><%= inventory_item.variant.options_text %></h6>
|
7
|
+
<ul class="list-group mt-1 mb-1">
|
6
8
|
<li class="list-group-item d-flex justify-content-between align-items-center">
|
7
9
|
Max capacity
|
8
|
-
<span class="badge badge-primary badge-pill"><%=
|
10
|
+
<span class="badge badge-primary badge-pill"><%= inventory_item.max_capacity %></span>
|
9
11
|
</li>
|
10
12
|
<li class="list-group-item d-flex justify-content-between align-items-center">
|
11
|
-
Quantity available
|
12
|
-
<span class="badge badge-primary badge-pill"><%=
|
13
|
+
Quantity available in DB
|
14
|
+
<span class="badge badge-primary badge-pill"><%= inventory_item.quantity_available %></span>
|
13
15
|
</li>
|
14
16
|
</ul>
|
17
|
+
|
18
|
+
<small class="text-muted">
|
19
|
+
<%= inventory_item_message(inventory_item, @cached_inventory_items[inventory_item.id]) %>
|
20
|
+
</small>
|
15
21
|
</div>
|
16
22
|
<% end %>
|
17
23
|
</div>
|
@@ -17,9 +17,11 @@
|
|
17
17
|
</td>
|
18
18
|
<td class="text-center d-flex flex-column align-items-center justify-content-center">
|
19
19
|
<div style="width: 90px;">
|
20
|
-
<%= form_tag
|
20
|
+
<%= form_tag admin_product_stock_managements_path(product_id: params[:product_id], variant_id: variant.id, stock_location_id: stock_location.id), method: :post do %>
|
21
21
|
<div class="input-group input-group-sm">
|
22
22
|
<%= number_field_tag 'stock_movement[quantity]', 0, class: 'form-control text-center p-0' %>
|
23
|
+
<%= hidden_field_tag 'stock_movement[originator_id]', spree_current_user.id %>
|
24
|
+
<%= hidden_field_tag 'stock_movement[originator_type]', spree_current_user.class.name %>
|
23
25
|
<div class="input-group-append">
|
24
26
|
<%= button_tag(class: 'btn btn-outline-success pl-2 pr-1') do %>
|
25
27
|
<%= svg_icon(name: 'arrow-left-right.svg', classes: "icon", width: 14, height: 14) %>
|
@@ -19,9 +19,12 @@
|
|
19
19
|
<%= date.day %>
|
20
20
|
<ul class="p-0 m-0 list-unstyled">
|
21
21
|
<% events.each do |event| %>
|
22
|
-
|
23
|
-
|
24
|
-
|
22
|
+
<% inventory_item = event.options[:inventory_item] %>
|
23
|
+
<% synced = inventory_item.quantity_available == @cached_inventory_items[inventory_item.id].quantity_available %>
|
24
|
+
|
25
|
+
<li class="badge <%= synced ? "badge-warning" : "badge-danger" %>">
|
26
|
+
<%= inventory_item.quantity_available %>
|
27
|
+
</li>
|
25
28
|
<% end %>
|
26
29
|
</ul>
|
27
30
|
</div>
|