spree_cm_commissioner 2.0.0 → 2.0.1.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 +16 -2
- data/.gitignore +2 -1
- data/Gemfile.lock +32 -1
- data/Rakefile +33 -4
- data/app/assets/stylesheets/spree_cm_commissioner/backend/calendar.scss +8 -11
- data/app/controllers/spree/admin/inventory_items_controller.rb +83 -0
- data/app/controllers/spree/admin/stock_managements_controller.rb +63 -1
- data/app/controllers/spree/api/v2/organizer/invite_guests_controller.rb +1 -1
- data/app/controllers/spree/api/v2/storefront/accommodations/variants_controller.rb +42 -0
- data/app/controllers/spree/api/v2/storefront/accommodations_controller.rb +14 -31
- data/app/controllers/spree/api/v2/storefront/guests_controller.rb +13 -13
- data/app/controllers/spree/api/v2/storefront/queue_cart/line_items_controller.rb +2 -2
- data/app/factory/spree_cm_commissioner/invite_guest_claimed_telegram_message_factory.rb +81 -23
- data/app/finders/spree_cm_commissioner/accommodations/find.rb +37 -0
- data/app/finders/spree_cm_commissioner/accommodations/find_variant.rb +32 -0
- data/app/interactors/spree_cm_commissioner/create_ticket.rb +5 -6
- data/app/interactors/spree_cm_commissioner/ensure_correct_product_type.rb +40 -0
- data/app/interactors/spree_cm_commissioner/inventory_item_syncer.rb +25 -0
- data/app/interactors/spree_cm_commissioner/pin_code_sender.rb +1 -0
- data/app/interactors/spree_cm_commissioner/sms.rb +1 -1
- data/app/interactors/spree_cm_commissioner/stock/inventory_item_resetter.rb +44 -0
- data/app/interactors/spree_cm_commissioner/stock/inventory_items_adjuster.rb +13 -0
- data/app/interactors/spree_cm_commissioner/stock/inventory_items_generator.rb +15 -0
- data/app/interactors/spree_cm_commissioner/stock/permanent_inventory_items_generator.rb +75 -0
- data/app/interactors/spree_cm_commissioner/stock/stock_movement_creator.rb +32 -0
- data/app/interactors/spree_cm_commissioner/user_id_token_authenticator.rb +3 -7
- data/app/interactors/spree_cm_commissioner/user_id_token_checker.rb +3 -11
- data/app/interactors/spree_cm_commissioner/user_identity_checker.rb +6 -12
- data/app/interactors/spree_cm_commissioner/user_registration_with_id_token.rb +1 -7
- data/app/jobs/spree_cm_commissioner/application_job.rb +20 -0
- data/app/jobs/spree_cm_commissioner/application_unique_job.rb +20 -0
- data/app/jobs/spree_cm_commissioner/ensure_correct_product_type_job.rb +7 -0
- data/app/jobs/spree_cm_commissioner/inventory_item_syncer_job.rb +7 -0
- data/app/jobs/spree_cm_commissioner/sms_pin_code_job.rb +1 -1
- data/app/jobs/spree_cm_commissioner/stock/inventory_items_adjuster_job.rb +11 -0
- data/app/jobs/spree_cm_commissioner/stock/inventory_items_generator_job.rb +11 -0
- data/app/jobs/spree_cm_commissioner/stock/permanent_inventory_items_generator_job.rb +9 -0
- data/app/mailers/spree/order_mailer_decorator.rb +3 -18
- data/app/models/concerns/spree_cm_commissioner/line_item_durationable.rb +9 -15
- data/app/models/concerns/spree_cm_commissioner/option_type_attr_type.rb +1 -12
- data/app/models/concerns/spree_cm_commissioner/order_seatable.rb +44 -0
- data/app/models/concerns/spree_cm_commissioner/order_state_machine.rb +39 -0
- data/app/models/concerns/spree_cm_commissioner/product_delegation.rb +1 -3
- data/app/models/concerns/spree_cm_commissioner/product_type.rb +10 -0
- data/app/models/concerns/spree_cm_commissioner/taxon_kind.rb +1 -1
- data/app/models/concerns/spree_cm_commissioner/tenant_preference.rb +0 -1
- data/app/models/spree_cm_commissioner/block.rb +23 -0
- data/app/models/spree_cm_commissioner/dynamic_field.rb +0 -20
- data/app/models/spree_cm_commissioner/guest.rb +18 -56
- data/app/models/spree_cm_commissioner/guest_dynamic_field.rb +1 -41
- data/app/models/spree_cm_commissioner/inventory.rb +11 -0
- data/app/models/spree_cm_commissioner/inventory_item.rb +69 -0
- data/app/models/spree_cm_commissioner/line_item_decorator.rb +46 -78
- data/app/models/spree_cm_commissioner/notification_taxon.rb +1 -1
- data/app/models/spree_cm_commissioner/option_type_decorator.rb +1 -11
- data/app/models/spree_cm_commissioner/order_decorator.rb +30 -1
- data/app/models/spree_cm_commissioner/place.rb +1 -4
- data/app/models/spree_cm_commissioner/price_decorator.rb +9 -0
- data/app/models/spree_cm_commissioner/product_decorator.rb +10 -28
- data/app/models/spree_cm_commissioner/redis_stock/cached_inventory_items_builder.rb +41 -0
- data/app/models/spree_cm_commissioner/redis_stock/inventory_updater.rb +126 -0
- data/app/models/spree_cm_commissioner/redis_stock/line_items_cached_inventory_items_builder.rb +36 -0
- data/app/models/spree_cm_commissioner/redis_stock/variant_cached_inventory_items_builder.rb +25 -0
- data/app/models/spree_cm_commissioner/reserved_block.rb +30 -0
- data/app/models/spree_cm_commissioner/seat_layout.rb +20 -0
- data/app/models/spree_cm_commissioner/seat_section.rb +16 -0
- data/app/models/spree_cm_commissioner/seats/blocks_canceler.rb +30 -0
- data/app/models/spree_cm_commissioner/seats/blocks_holder.rb +53 -0
- data/app/models/spree_cm_commissioner/seats/blocks_reserver.rb +49 -0
- data/app/models/spree_cm_commissioner/seats/errors/blocks_are_on_hold_by_other_guest.rb +4 -0
- data/app/models/spree_cm_commissioner/seats/errors/blocks_are_reserved_by_other_guest.rb +4 -0
- data/app/models/spree_cm_commissioner/seats/errors/blocks_are_reserved_by_same_guest.rb +4 -0
- data/app/models/spree_cm_commissioner/seats/errors/unable_to_save_reserved_block_record.rb +4 -0
- data/app/models/spree_cm_commissioner/service_calendar.rb +0 -2
- data/app/models/spree_cm_commissioner/state_decorator.rb +0 -1
- data/app/models/spree_cm_commissioner/stock/availability_checker.rb +26 -25
- data/app/models/spree_cm_commissioner/stock/availability_validator_decorator.rb +2 -1
- data/app/models/spree_cm_commissioner/stock/line_item_availability_checker.rb +3 -3
- data/app/models/spree_cm_commissioner/stock/order_availability_checker.rb +44 -0
- data/app/models/spree_cm_commissioner/stock_item_decorator.rb +17 -0
- data/app/models/spree_cm_commissioner/taxon_decorator.rb +0 -1
- data/app/models/spree_cm_commissioner/taxonomy_decorator.rb +0 -6
- data/app/models/spree_cm_commissioner/trip.rb +8 -10
- data/app/models/spree_cm_commissioner/trip_connection.rb +5 -5
- data/app/models/spree_cm_commissioner/trip_stop.rb +6 -25
- data/app/models/spree_cm_commissioner/user_identity_provider.rb +4 -26
- data/app/models/spree_cm_commissioner/variant_block.rb +9 -0
- data/app/models/spree_cm_commissioner/variant_decorator.rb +37 -47
- data/app/models/spree_cm_commissioner/variant_options.rb +0 -23
- data/app/models/spree_cm_commissioner/vehicle.rb +9 -14
- data/app/models/spree_cm_commissioner/vendor_decorator.rb +11 -17
- data/app/models/spree_cm_commissioner/vendor_place.rb +9 -3
- data/app/queries/spree_cm_commissioner/trip_query.rb +70 -44
- data/app/request_schemas/spree_cm_commissioner/accommodation_request_schema.rb +3 -0
- data/app/request_schemas/spree_cm_commissioner/application_request_schema.rb +1 -1
- data/app/request_schemas/spree_cm_commissioner/variant_request_schema.rb +19 -0
- data/app/serializers/spree/v2/storefront/accommodation_serializer.rb +2 -0
- data/app/serializers/spree/v2/storefront/line_item_serializer_decorator.rb +1 -0
- data/app/serializers/spree/v2/tenant/user_serializer.rb +0 -1
- data/app/serializers/spree_cm_commissioner/v2/storefront/dynamic_field_serializer.rb +1 -1
- data/app/services/spree_cm_commissioner/user_authenticator.rb +1 -1
- data/app/views/spree/admin/inventory_items/show.html.erb +72 -0
- data/app/views/spree/admin/stock_managements/_variant_stock_items.html.erb +7 -2
- data/app/views/spree/admin/stock_managements/calendar.html.erb +56 -0
- data/app/views/spree/admin/stock_managements/index.html.erb +55 -6
- data/app/views/spree/admin/tenant_vendors/index.html.erb +2 -9
- data/app/views/spree/admin/tenants/_form.html.erb +0 -9
- data/app/views/spree/admin/tenants/edit.html.erb +1 -2
- data/app/views/spree/admin/tenants/index.html.erb +1 -7
- data/app/views/spree/admin/vendors/_form.html.erb +0 -14
- data/app/views/spree/order_mailer/confirm_email.html.erb +16 -27
- data/app/views/spree_cm_commissioner/layouts/order_mailer.html.erb +1 -5
- data/app/views/spree_cm_commissioner/order_mailer/_mailer_stylesheets.html.erb +4 -41
- data/config/initializers/paper_trail.rb +1 -0
- data/config/initializers/spree_permitted_attributes.rb +5 -0
- data/config/locales/en.yml +1 -5
- data/config/routes.rb +21 -2
- data/db/migrate/20240202080634_update_counter_cache_of_vehicle_type.rb +3 -1
- data/db/migrate/20250304293518_create_cm_inventory_items.rb +21 -0
- data/db/migrate/20250429094228_add_lock_version_to_cm_inventory_items.rb +5 -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/20250603035256_add_inventory_item_to_spree_prices.rb +7 -0
- data/db/migrate/20250619073724_drop_table_cm_line_item_seats.rb +5 -0
- data/db/migrate/20250619073812_drop_table_cm_vehicle_seats.rb +5 -0
- data/db/migrate/20250619073844_drop_table_cm_vehicle_types.rb +9 -0
- data/db/migrate/20250619073957_drop_table_cm_option_value_vehicle_types.rb +5 -0
- data/db/migrate/20250619082354_remove_unnecessary_fields_from_cm_places.rb +9 -0
- data/db/migrate/20250619082736_remove_route_type_from_spree_products.rb +5 -0
- data/db/migrate/20250619083055_remove_unnecessary_fields_from_spree_taxons.rb +5 -0
- data/db/migrate/20250620083055_remove_variant_id_from_cm_trips.rb +5 -0
- data/db/migrate/20250620090000_update_cm_trip_connections_to_use_cm_trips.rb +6 -0
- data/db/migrate/20250620090001_create_cm_seat_layouts.rb +17 -0
- data/db/migrate/20250620090002_create_cm_seat_sections.rb +18 -0
- data/db/migrate/20250620090003_create_cm_blocks.rb +18 -0
- data/db/migrate/20250624091005_create_cm_reserved_blocks.rb +29 -0
- data/db/migrate/20250626083642_create_cm_variant_blocks.rb +24 -0
- data/db/migrate/20250627023314_add_block_id_to_cm_guests.rb +13 -0
- data/db/migrate/20250716022821_add_location_reference_to_cm_vendor_places.rb +5 -0
- data/db/migrate/20250716031743_drop_table_cm_vendor_stops.rb +5 -0
- data/db/migrate/20250717023824_add_vendor_reference_to_cm_trips.rb +5 -0
- data/db/migrate/20250717041414_add_location_place_reference_to_cm_trip_stops.rb +5 -0
- data/db/migrate/20250717042539_rename_cm_trip_stops_stop_id_column_to_stop_place_id.rb +7 -0
- data/db/migrate/20250717042707_rename_cm_trips_origin_and_destination_to_origin_place_and_destination_place.rb +11 -0
- data/docker-compose.yml +1 -1
- data/lib/cm_app_logger.rb +11 -4
- data/lib/generators/spree_cm_commissioner/install/install_generator.rb +14 -11
- data/lib/generators/spree_cm_commissioner/install/templates/app/javascript/{spree_cm_commissioner → spree_dashboard/spree_cm_commissioner}/utilities.js +4 -0
- data/lib/spree_cm_commissioner/cached_inventory_item.rb +23 -0
- data/lib/spree_cm_commissioner/calendar_event.rb +11 -1
- data/lib/spree_cm_commissioner/test_helper/factories/block_factory.rb +15 -0
- data/lib/spree_cm_commissioner/test_helper/factories/guest_factory.rb +10 -0
- 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 +9 -0
- data/lib/spree_cm_commissioner/test_helper/factories/line_item_factory.rb +1 -1
- data/lib/spree_cm_commissioner/test_helper/factories/option_type_factory.rb +6 -30
- data/lib/spree_cm_commissioner/test_helper/factories/order_factory.rb +0 -36
- data/lib/spree_cm_commissioner/test_helper/factories/product_factory.rb +18 -34
- data/lib/spree_cm_commissioner/test_helper/factories/reserved_block_factory.rb +27 -0
- data/lib/spree_cm_commissioner/test_helper/factories/seat_layout_factory.rb +11 -0
- data/lib/spree_cm_commissioner/test_helper/factories/seat_section_factory.rb +16 -0
- data/lib/spree_cm_commissioner/test_helper/factories/stock_location_factory.rb +2 -2
- data/lib/spree_cm_commissioner/test_helper/factories/trip_connection_factory.rb +6 -0
- data/lib/spree_cm_commissioner/test_helper/factories/trip_factory.rb +11 -3
- data/lib/spree_cm_commissioner/test_helper/factories/trip_stop_factory.rb +10 -0
- data/lib/spree_cm_commissioner/test_helper/factories/user_identity_provider_factory.rb +1 -1
- data/lib/spree_cm_commissioner/test_helper/factories/variant_block_factory.rb +7 -0
- data/lib/spree_cm_commissioner/test_helper/factories/variant_factory.rb +41 -19
- data/lib/spree_cm_commissioner/test_helper/factories/vehicle_factory.rb +1 -1
- data/lib/spree_cm_commissioner/test_helper/factories/vendor_factory.rb +6 -1
- data/lib/spree_cm_commissioner/test_helper/factories/vendor_place_factory.rb +13 -1
- data/lib/spree_cm_commissioner/trip_query_result.rb +8 -0
- data/lib/spree_cm_commissioner/trip_result.rb +5 -3
- data/lib/spree_cm_commissioner/version.rb +1 -1
- data/lib/spree_cm_commissioner.rb +35 -1
- data/lib/tasks/create_default_non_permanent_inventory_items.rake +16 -0
- data/lib/tasks/ensure_correct_product_type.rake +7 -0
- data/lib/tasks/generate_inventory_items.rake +7 -0
- data/spree_cm_commissioner.gemspec +7 -0
- metadata +141 -38
- data/app/assets/images/mailer/mail.png +0 -0
- data/app/assets/images/mailer/tenant_phone.png +0 -0
- data/app/assets/images/mailer/tenant_user.png +0 -0
- data/app/controllers/concerns/spree_cm_commissioner/transit/taxon_bitwise.rb +0 -44
- data/app/finders/spree_cm_commissioner/line_items/find_by_variant_decorator.rb +0 -20
- data/app/models/spree_cm_commissioner/branch.rb +0 -12
- data/app/models/spree_cm_commissioner/line_item_seat.rb +0 -10
- data/app/models/spree_cm_commissioner/option_value_vehicle_type.rb +0 -8
- data/app/models/spree_cm_commissioner/stop.rb +0 -23
- data/app/models/spree_cm_commissioner/vehicle_seat.rb +0 -11
- data/app/models/spree_cm_commissioner/vehicle_type.rb +0 -76
- data/app/models/spree_cm_commissioner/vendor_stop.rb +0 -10
- data/app/queries/spree_cm_commissioner/trip_search_query.rb +0 -76
- data/app/queries/spree_cm_commissioner/variant_availability/non_permanent_stock_query.rb +0 -45
- data/app/queries/spree_cm_commissioner/variant_availability/permanent_stock_query.rb +0 -55
- data/app/queries/spree_cm_commissioner/vendor_stop_place_query.rb +0 -54
- data/app/services/spree_cm_commissioner/vehicle_option_value_creator.rb +0 -11
- data/app/views/spree_cm_commissioner/order_mailer/tenant/_customer_info.html.erb +0 -42
- data/app/views/spree_cm_commissioner/order_mailer/tenant/_footer.html.erb +0 -25
- data/app/views/spree_cm_commissioner/order_mailer/tenant/_greeting.html.erb +0 -19
- data/app/views/spree_cm_commissioner/order_mailer/tenant/_support_contact.html.erb +0 -33
- data/db/migrate/20250709073455_add_email_fields_to_spree_vendors.rb +0 -6
- data/db/migrate/20250715103333_remove_indexes_from_cm_user_identity_providers.rb +0 -13
- data/db/migrate/20250718071620_add_data_fill_stage_to_dynamic_fields.rb +0 -5
- data/lib/spree_cm_commissioner/test_helper/factories/branch_factory.rb +0 -12
- data/lib/spree_cm_commissioner/test_helper/factories/departure_time_option_type_factory.rb +0 -8
- data/lib/spree_cm_commissioner/test_helper/factories/duration_option_type_factory.rb +0 -8
- data/lib/spree_cm_commissioner/test_helper/factories/line_item_seat_factory.rb +0 -7
- data/lib/spree_cm_commissioner/test_helper/factories/stop_factory.rb +0 -14
- data/lib/spree_cm_commissioner/test_helper/factories/transit_place_factory.rb +0 -8
- data/lib/spree_cm_commissioner/test_helper/factories/vehicle_option_type_factory.rb +0 -8
- data/lib/spree_cm_commissioner/test_helper/factories/vehicle_type_factory.rb +0 -96
- data/lib/spree_cm_commissioner/trip_seat_layout_result.rb +0 -11
@@ -0,0 +1,11 @@
|
|
1
|
+
module SpreeCmCommissioner
|
2
|
+
module Stock
|
3
|
+
class InventoryItemsAdjusterJob < ApplicationUniqueJob
|
4
|
+
def perform(variant_id:, quantity:)
|
5
|
+
variant = Spree::Variant.find(variant_id)
|
6
|
+
|
7
|
+
SpreeCmCommissioner::Stock::InventoryItemsAdjuster.call(variant: variant, quantity: quantity)
|
8
|
+
end
|
9
|
+
end
|
10
|
+
end
|
11
|
+
end
|
@@ -0,0 +1,11 @@
|
|
1
|
+
module SpreeCmCommissioner
|
2
|
+
module Stock
|
3
|
+
class InventoryItemsGeneratorJob < ApplicationUniqueJob
|
4
|
+
def perform(variant_id:)
|
5
|
+
variant = Spree::Variant.find(variant_id)
|
6
|
+
|
7
|
+
SpreeCmCommissioner::Stock::InventoryItemsGenerator.call(variant: variant)
|
8
|
+
end
|
9
|
+
end
|
10
|
+
end
|
11
|
+
end
|
@@ -1,5 +1,6 @@
|
|
1
1
|
module Spree
|
2
2
|
module OrderMailerDecorator
|
3
|
+
# overrided
|
3
4
|
def cancel_email(order, resend: false)
|
4
5
|
@order = order.respond_to?(:id) ? order : Spree::Order.find(order)
|
5
6
|
return false if @order.email.blank?
|
@@ -11,34 +12,18 @@ module Spree
|
|
11
12
|
@order = order.respond_to?(:id) ? order : Spree::Order.find(order)
|
12
13
|
return false if @order.email.blank?
|
13
14
|
|
14
|
-
@tenant = @order.tenant
|
15
|
-
if @tenant.present?
|
16
|
-
@brand_color = @tenant.preferences[:brand_primary_color]
|
17
|
-
@vendor_logo_url = @tenant.active_vendor&.logo&.original_url
|
18
|
-
end
|
19
|
-
|
20
15
|
@current_store = @order.store
|
21
16
|
@product_type = @order.products.first&.product_type || 'accommodation'
|
22
17
|
|
23
|
-
subject = resend ? "[#{Spree.t(:resend).upcase}] " : ''
|
18
|
+
subject = (resend ? "[#{Spree.t(:resend).upcase}] " : '')
|
24
19
|
subject += "#{@current_store&.name} Booking Confirmation ##{@order.number}"
|
25
20
|
|
26
|
-
mail(to: @order.email, from:
|
21
|
+
mail(to: @order.email, from: from_address, subject: subject, store_url: @current_store.url) do |format|
|
27
22
|
format.html { render layout: 'spree_cm_commissioner/layouts/order_mailer' }
|
28
23
|
format.text
|
29
24
|
end
|
30
25
|
end
|
31
26
|
|
32
|
-
private
|
33
|
-
|
34
|
-
def from_email_address
|
35
|
-
if @order.tenant.nil?
|
36
|
-
from_address
|
37
|
-
else
|
38
|
-
@order.tenant.active_vendor&.from_email
|
39
|
-
end
|
40
|
-
end
|
41
|
-
|
42
27
|
def ticket_email(guest, email)
|
43
28
|
@guest = guest
|
44
29
|
@event = @guest.event
|
@@ -18,29 +18,23 @@ module SpreeCmCommissioner
|
|
18
18
|
def date_unit
|
19
19
|
return nil unless permanent_stock?
|
20
20
|
|
21
|
-
|
21
|
+
date_range.size
|
22
22
|
end
|
23
23
|
|
24
|
-
def
|
24
|
+
def date_range
|
25
25
|
return [] unless date_present?
|
26
26
|
|
27
|
-
|
28
|
-
date_range.pop if date_range.size > 1
|
29
|
-
date_range
|
27
|
+
(from_date.to_date..to_date.to_date).to_a
|
30
28
|
end
|
31
29
|
|
32
|
-
def
|
33
|
-
|
34
|
-
|
35
|
-
(from_date.to_date..to_date.to_date).to_a
|
30
|
+
def checkin_date
|
31
|
+
from_date&.to_date
|
36
32
|
end
|
37
33
|
|
38
|
-
def
|
39
|
-
if accommodation?
|
40
|
-
|
41
|
-
|
42
|
-
date_range_including_checkout
|
43
|
-
end
|
34
|
+
def checkout_date
|
35
|
+
return to_date ? to_date.to_date - 1.day : nil if accommodation?
|
36
|
+
|
37
|
+
to_date&.to_date
|
44
38
|
end
|
45
39
|
|
46
40
|
private
|
@@ -14,13 +14,6 @@ module SpreeCmCommissioner
|
|
14
14
|
state_selection
|
15
15
|
payment_option
|
16
16
|
delivery_option
|
17
|
-
amenity
|
18
|
-
departure_time
|
19
|
-
duration
|
20
|
-
vehicle_id
|
21
|
-
origin
|
22
|
-
destination
|
23
|
-
place_id
|
24
17
|
].freeze
|
25
18
|
|
26
19
|
RESERVED_OPTIONS = {
|
@@ -48,11 +41,7 @@ module SpreeCmCommissioner
|
|
48
41
|
'bib-display-prefix' => 'boolean',
|
49
42
|
'bib-pre-generation-on-create' => 'boolean',
|
50
43
|
'seat-number-positions' => 'array',
|
51
|
-
'
|
52
|
-
'destination' => 'place_id',
|
53
|
-
'departure-time' => 'time',
|
54
|
-
'vehicle' => 'vehicle_id',
|
55
|
-
'allow-seat-selection' => 'boolean'
|
44
|
+
'seat-type' => 'string'
|
56
45
|
}.freeze
|
57
46
|
|
58
47
|
included do
|
@@ -0,0 +1,44 @@
|
|
1
|
+
module SpreeCmCommissioner
|
2
|
+
module OrderSeatable
|
3
|
+
def should_manage_blocks?
|
4
|
+
preload_block_ids.any?
|
5
|
+
end
|
6
|
+
|
7
|
+
def hold_blocks!
|
8
|
+
return unless should_manage_blocks?
|
9
|
+
|
10
|
+
CmAppLogger.log(label: "#{self.class.name}#hold_blocks!", data: { order_id: id }) do
|
11
|
+
SpreeCmCommissioner::Seats::BlocksHolder.new(line_item_ids: line_item_ids, hold_by: user).hold_blocks!
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
def cancel_blocks!
|
16
|
+
return unless should_manage_blocks?
|
17
|
+
|
18
|
+
CmAppLogger.log(label: "#{self.class.name}#cancel_blocks!", data: { order_id: id }) do
|
19
|
+
SpreeCmCommissioner::Seats::BlocksCanceler.new(order_id: id, cancel_by: user).cancel_blocks!
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
def reserve_blocks!
|
24
|
+
return unless should_manage_blocks?
|
25
|
+
|
26
|
+
CmAppLogger.log(label: "#{self.class.name}#reserve_blocks!", data: { order_id: id }) do
|
27
|
+
SpreeCmCommissioner::Seats::BlocksReserver.new(line_item_ids: line_item_ids, reserve_by: user).reserve_blocks!
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
# Calling `.block_ids` directly can cause many slow database queries (N+1 problem)
|
32
|
+
# every time `.should_manage_blocks?` or `.preload_block_ids` runs.
|
33
|
+
# To avoid this, we store a precomputed list of block IDs in `private_metadata`.
|
34
|
+
# This list is updated whenever a guest’s block is saved or destroy.
|
35
|
+
def preload_block_ids=(preload_block_ids = [])
|
36
|
+
self.private_metadata ||= {}
|
37
|
+
self.private_metadata['preload_block_ids'] = preload_block_ids
|
38
|
+
end
|
39
|
+
|
40
|
+
def preload_block_ids
|
41
|
+
self.private_metadata&.fetch('preload_block_ids', []) || []
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
@@ -5,6 +5,8 @@ module SpreeCmCommissioner
|
|
5
5
|
extend ActiveSupport::Concern
|
6
6
|
|
7
7
|
included do
|
8
|
+
state_machine.before_transition to: :payment, do: :hold_blocks!
|
9
|
+
|
8
10
|
state_machine.before_transition to: :complete, do: :request, if: :need_confirmation?
|
9
11
|
state_machine.before_transition to: :complete, do: :generate_bib_number
|
10
12
|
|
@@ -14,10 +16,14 @@ module SpreeCmCommissioner
|
|
14
16
|
state_machine.after_transition to: :complete, do: :send_order_complete_telegram_alert_to_vendors, unless: :need_confirmation?
|
15
17
|
state_machine.after_transition to: :complete, do: :send_order_complete_telegram_alert_to_store, unless: :need_confirmation?
|
16
18
|
|
19
|
+
state_machine.around_transition to: :complete, do: :unstock_inventory!
|
20
|
+
|
17
21
|
state_machine.after_transition to: :complete, do: :send_transaction_email_to_user, if: :user_has_email?
|
18
22
|
state_machine.after_transition to: :resumed, do: :precalculate_conversion
|
23
|
+
state_machine.around_transition to: :resumed, do: :unstock_inventory!
|
19
24
|
|
20
25
|
state_machine.after_transition to: :canceled, do: :precalculate_conversion
|
26
|
+
state_machine.after_transition to: :canceled, do: :restock_inventory!
|
21
27
|
|
22
28
|
scope :accepted, -> { where(request_state: 'accepted') }
|
23
29
|
|
@@ -67,6 +73,39 @@ module SpreeCmCommissioner
|
|
67
73
|
end
|
68
74
|
end
|
69
75
|
|
76
|
+
def unstock_inventory!
|
77
|
+
ActiveRecord::Base.transaction do
|
78
|
+
yield # Equal to block.call
|
79
|
+
|
80
|
+
# After the transition is complete, the following code will execute first before proceeding to other `after_transition` callbacks.
|
81
|
+
# This ensures that if `reserve_blocks!` or `unstock_inventory_in_redis!` fails, the state will be rolled back,
|
82
|
+
# and neither the `finalize!` method nor any notifications will be triggered.
|
83
|
+
# The payment will be reversed in vPago gem, and `Spree::Checkout::Complete` will be called, which checks `order.reload.complete?`.
|
84
|
+
# This is critical because if the order state is complete, the payment will be marked as paid.
|
85
|
+
|
86
|
+
reserve_blocks!
|
87
|
+
unstock_inventory_in_redis!
|
88
|
+
|
89
|
+
# We rollback only order state, and we keep payment state as it is.
|
90
|
+
# We implement payment in vPago gem, and it will be reversed in the gem.
|
91
|
+
# Some bank has api for refund, but some don't have the api to refund yet. So we keep the payment state as it is and refund manually.
|
92
|
+
end
|
93
|
+
rescue StandardError => e
|
94
|
+
CmAppLogger.log(
|
95
|
+
label: 'SpreeCmCommissioner::OrderStateMachine#reserve_inventory failed',
|
96
|
+
data: { order_id: id, error: e.message, type: e.class.name, backtrace: e.backtrace&.first(5)&.join("\n") }
|
97
|
+
)
|
98
|
+
|
99
|
+
raise e
|
100
|
+
end
|
101
|
+
|
102
|
+
def restock_inventory!
|
103
|
+
ActiveRecord::Base.transaction do
|
104
|
+
cancel_blocks!
|
105
|
+
restock_inventory_in_redis!
|
106
|
+
end
|
107
|
+
end
|
108
|
+
|
70
109
|
def generate_bib_number
|
71
110
|
line_items.find_each(&:generate_remaining_guests)
|
72
111
|
|
@@ -3,12 +3,10 @@ module SpreeCmCommissioner
|
|
3
3
|
extend ActiveSupport::Concern
|
4
4
|
|
5
5
|
included do
|
6
|
-
delegate :
|
7
|
-
:subscribable?,
|
6
|
+
delegate :subscribable?,
|
8
7
|
:allowed_upload_later?,
|
9
8
|
:need_confirmation?, :need_confirmation,
|
10
9
|
:allow_anonymous_booking,
|
11
|
-
:accommodation?, :service?, :ecommerce?,
|
12
10
|
:allow_self_check_in,
|
13
11
|
:allow_self_check_in?,
|
14
12
|
:required_self_check_in_location,
|
@@ -6,10 +6,20 @@ module SpreeCmCommissioner
|
|
6
6
|
extend ActiveSupport::Concern
|
7
7
|
|
8
8
|
PRODUCT_TYPES = %i[accommodation service ecommerce transit].freeze
|
9
|
+
PERMANENT_STOCK_PRODUCT_TYPES = %w[accommodation service transit].freeze
|
10
|
+
PRE_INVENTORY_DAYS = { 'transit' => 90, 'accommodation' => 365, 'service' => 30 }.freeze
|
9
11
|
|
10
12
|
included do
|
11
13
|
enum product_type: PRODUCT_TYPES if table_exists? && column_names.include?('product_type')
|
12
14
|
enum primary_product_type: PRODUCT_TYPES if table_exists? && column_names.include?('primary_product_type')
|
13
15
|
end
|
16
|
+
|
17
|
+
def permanent_stock?
|
18
|
+
PERMANENT_STOCK_PRODUCT_TYPES.include?(product_type)
|
19
|
+
end
|
20
|
+
|
21
|
+
def pre_inventory_days
|
22
|
+
PRE_INVENTORY_DAYS[product_type]
|
23
|
+
end
|
14
24
|
end
|
15
25
|
end
|
@@ -3,7 +3,7 @@ module SpreeCmCommissioner
|
|
3
3
|
extend ActiveSupport::Concern
|
4
4
|
|
5
5
|
included do
|
6
|
-
enum kind: { category: 0, cms: 1, event: 2, occupation: 3, nationality: 4, organization: 5
|
6
|
+
enum kind: { category: 0, cms: 1, event: 2, occupation: 3, nationality: 4, organization: 5 }
|
7
7
|
end
|
8
8
|
end
|
9
9
|
end
|
@@ -9,7 +9,6 @@ module SpreeCmCommissioner
|
|
9
9
|
preference :payment_failed_image, :string, default: ''
|
10
10
|
preference :payment_success_image, :string, default: ''
|
11
11
|
preference :payment_loader, :string, default: ''
|
12
|
-
preference :brand_primary_color, :string, default: ''
|
13
12
|
end
|
14
13
|
end
|
15
14
|
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
module SpreeCmCommissioner
|
2
|
+
class Block < Base
|
3
|
+
belongs_to :seat_section, class_name: 'SpreeCmCommissioner::SeatSection', optional: true
|
4
|
+
belongs_to :seat_layout, class_name: 'SpreeCmCommissioner::SeatLayout', optional: false
|
5
|
+
|
6
|
+
validates :label, presence: true
|
7
|
+
|
8
|
+
has_many :variant_blocks, class_name: 'SpreeCmCommissioner::VariantBlock', dependent: :destroy
|
9
|
+
has_many :variants, class_name: 'Spree::Variant', through: :variant_blocks
|
10
|
+
|
11
|
+
validates :width, presence: true, numericality: { greater_than: 0 }
|
12
|
+
validates :height, presence: true, numericality: { greater_than: 0 }
|
13
|
+
validates :x, presence: true, numericality: true
|
14
|
+
validates :y, presence: true, numericality: true
|
15
|
+
validates :rotation, presence: true, numericality: { greater_than_or_equal_to: -360, less_than_or_equal_to: 360 }
|
16
|
+
|
17
|
+
before_validation :assign_layout_from_section, if: -> { seat_layout.nil? && seat_section.present? }
|
18
|
+
|
19
|
+
def assign_layout_from_section
|
20
|
+
self.seat_layout = seat_section.seat_layout
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
@@ -9,14 +9,12 @@ module SpreeCmCommissioner
|
|
9
9
|
has_many :product_dynamic_fields, class_name: 'SpreeCmCommissioner::ProductDynamicField', dependent: :destroy
|
10
10
|
|
11
11
|
enum data_type: { text: 0, number: 1, boolean: 2, checkbox: 3, radio: 4, selection: 5, textarea: 6 }
|
12
|
-
enum data_fill_stage: { pre_registration: 0, post_registration: 1, during_check_in: 2 }
|
13
12
|
|
14
13
|
accepts_nested_attributes_for :dynamic_field_options, allow_destroy: true, reject_if: :all_blank
|
15
14
|
|
16
15
|
validates :label, presence: true
|
17
16
|
validates :data_type, presence: true
|
18
17
|
validates :configurations, presence: true, allow_blank: false
|
19
|
-
validate :validate_data_fill_stage_phase
|
20
18
|
|
21
19
|
def requires_dynamic_field_options?
|
22
20
|
checkbox? || radio? || selection?
|
@@ -32,23 +30,5 @@ module SpreeCmCommissioner
|
|
32
30
|
self.configurations ||= {}
|
33
31
|
self.configurations['multiple_select'] = ActiveModel::Type::Boolean.new.cast(value)
|
34
32
|
end
|
35
|
-
|
36
|
-
def required_at_stage?(stage)
|
37
|
-
data_fill_stage == stage.to_s
|
38
|
-
end
|
39
|
-
|
40
|
-
def display_label
|
41
|
-
label + " (#{data_fill_stage.humanize})"
|
42
|
-
end
|
43
|
-
|
44
|
-
private
|
45
|
-
|
46
|
-
def validate_data_fill_stage_phase
|
47
|
-
return if data_fill_stage.blank?
|
48
|
-
|
49
|
-
return if data_fill_stage.in?(self.class.data_fill_stages.keys)
|
50
|
-
|
51
|
-
errors.add(:data_fill_stage, "must be one of: #{self.class.data_fill_stages.keys.join(', ')}")
|
52
|
-
end
|
53
33
|
end
|
54
34
|
end
|
@@ -24,10 +24,16 @@ module SpreeCmCommissioner
|
|
24
24
|
scope :none_bib, -> { where(bib_prefix: [nil, '']) }
|
25
25
|
|
26
26
|
belongs_to :line_item, class_name: 'Spree::LineItem'
|
27
|
+
|
27
28
|
has_one :variant, class_name: 'Spree::Variant', through: :line_item
|
29
|
+
|
28
30
|
belongs_to :user, class_name: 'Spree::User'
|
29
31
|
belongs_to :occupation, class_name: 'Spree::Taxon'
|
30
32
|
belongs_to :nationality, class_name: 'Spree::Taxon'
|
33
|
+
belongs_to :block, class_name: 'SpreeCmCommissioner::Block', optional: true
|
34
|
+
|
35
|
+
has_one :reserved_block, class_name: 'SpreeCmCommissioner::ReservedBlock'
|
36
|
+
|
31
37
|
scope :checked_ins, -> { joins(:check_in) }
|
32
38
|
scope :no_show, -> { left_outer_joins(:check_in).where(cm_check_ins: { id: nil }) }
|
33
39
|
|
@@ -48,6 +54,10 @@ module SpreeCmCommissioner
|
|
48
54
|
|
49
55
|
before_create :generate_bib, if: -> { line_item.reload && variant.bib_pre_generation_on_create? }
|
50
56
|
|
57
|
+
after_create :preload_order_block_ids, if: -> { block_id.present? }
|
58
|
+
after_update :preload_order_block_ids, if: :saved_change_to_block_id?
|
59
|
+
after_destroy :preload_order_block_ids
|
60
|
+
|
51
61
|
validates :seat_number, uniqueness: { scope: :event_id }, allow_nil: true, if: -> { event_id.present? }
|
52
62
|
validates :bib_index, uniqueness: true, allow_nil: true
|
53
63
|
|
@@ -213,62 +223,6 @@ module SpreeCmCommissioner
|
|
213
223
|
save!
|
214
224
|
end
|
215
225
|
|
216
|
-
def missing_dynamic_fields_for_stage(stage)
|
217
|
-
required_fields = line_item.product.dynamic_fields.where(data_fill_stage: stage)
|
218
|
-
filled_ids = guest_dynamic_fields.map(&:dynamic_field_id)
|
219
|
-
|
220
|
-
required_fields.reject { |field| filled_ids.include?(field.id) }
|
221
|
-
end
|
222
|
-
|
223
|
-
def all_required_dynamic_fields_completed?(stage)
|
224
|
-
missing_dynamic_fields_for_stage(stage).blank?
|
225
|
-
end
|
226
|
-
|
227
|
-
def validate_dynamic_fields_for_phase(phase)
|
228
|
-
required_fields = line_item.product.dynamic_fields.where(data_fill_stage: phase)
|
229
|
-
filled_ids = guest_dynamic_fields.map(&:dynamic_field_id)
|
230
|
-
|
231
|
-
required_fields.each do |field|
|
232
|
-
errors.add(:base, "#{field.label} is required for #{phase} phase") unless filled_ids.include?(field.id)
|
233
|
-
end
|
234
|
-
end
|
235
|
-
|
236
|
-
def pre_registration_completed?
|
237
|
-
return true unless line_item.product.dynamic_fields.pre_registration.any?
|
238
|
-
|
239
|
-
pre_registration_fields_completed?
|
240
|
-
end
|
241
|
-
|
242
|
-
def post_registration_completed?
|
243
|
-
return true unless line_item.product.dynamic_fields.post_registration.any?
|
244
|
-
|
245
|
-
post_registration_fields_completed?
|
246
|
-
end
|
247
|
-
|
248
|
-
def check_in_completed?
|
249
|
-
return true unless line_item.product.dynamic_fields.during_check_in.any?
|
250
|
-
|
251
|
-
check_in_fields_completed?
|
252
|
-
end
|
253
|
-
|
254
|
-
def current_collection_phase
|
255
|
-
if check_in.present?
|
256
|
-
:during_check_in
|
257
|
-
elsif line_item.order.completed?
|
258
|
-
:post_registration
|
259
|
-
else
|
260
|
-
:pre_registration
|
261
|
-
end
|
262
|
-
end
|
263
|
-
|
264
|
-
def phase_completion_status
|
265
|
-
{
|
266
|
-
pre_registration: pre_registration_completed?,
|
267
|
-
post_registration: post_registration_completed?,
|
268
|
-
during_check_in: check_in_completed?
|
269
|
-
}
|
270
|
-
end
|
271
|
-
|
272
226
|
# bib_number: 345, bib_prefix: 5KM, bib_zerofill: 5 => return 5KM00345
|
273
227
|
# bib_number: 345, bib_prefix: 5KM, bib_zerofill: 2 => return 5KM345
|
274
228
|
def formatted_bib_number
|
@@ -283,6 +237,14 @@ module SpreeCmCommissioner
|
|
283
237
|
filled_bib_number
|
284
238
|
end
|
285
239
|
end
|
240
|
+
|
241
|
+
def preload_order_block_ids
|
242
|
+
return if line_item.blank?
|
243
|
+
return if line_item.order.blank?
|
244
|
+
|
245
|
+
block_ids = line_item.order.blocks.pluck(:id)
|
246
|
+
line_item.order.update(preload_block_ids: block_ids)
|
247
|
+
end
|
286
248
|
end
|
287
249
|
end
|
288
250
|
# rubocop:enable Metrics/ClassLength
|
@@ -7,7 +7,6 @@ module SpreeCmCommissioner
|
|
7
7
|
validates :value, presence: true
|
8
8
|
validate :validate_value_format, if: -> { value.present? && dynamic_field.present? }
|
9
9
|
validate :validate_option_reference, if: -> { dynamic_field_option.present? }
|
10
|
-
validate :check_required_fields_based_on_phase
|
11
10
|
|
12
11
|
private
|
13
12
|
|
@@ -35,49 +34,10 @@ module SpreeCmCommissioner
|
|
35
34
|
|
36
35
|
def validate_option_reference
|
37
36
|
return if dynamic_field.blank? || dynamic_field_option.blank?
|
37
|
+
|
38
38
|
return if dynamic_field_option.dynamic_field_id == dynamic_field_id
|
39
39
|
|
40
40
|
errors.add(:dynamic_field_option, 'must belong to the same dynamic field')
|
41
41
|
end
|
42
|
-
|
43
|
-
def before_checkin_phase?
|
44
|
-
guest.present? && guest.line_item.present? && guest.check_in.nil?
|
45
|
-
end
|
46
|
-
|
47
|
-
def after_purchase_phase?
|
48
|
-
guest.present? && guest.line_item.present? && guest.line_item.completed? && guest.check_in.nil?
|
49
|
-
end
|
50
|
-
|
51
|
-
def during_checkin_phase?
|
52
|
-
guest.present? && guest.check_in.present?
|
53
|
-
end
|
54
|
-
|
55
|
-
def pre_registration_required?
|
56
|
-
dynamic_field.data_fill_stage.to_sym == :pre_registration &&
|
57
|
-
guest.line_item.order.completed? && guest.check_in.blank?
|
58
|
-
end
|
59
|
-
|
60
|
-
def post_registration_required?
|
61
|
-
dynamic_field.data_fill_stage.to_sym == :post_registration &&
|
62
|
-
guest.line_item.order.completed? && guest.check_in.blank?
|
63
|
-
end
|
64
|
-
|
65
|
-
def during_check_in_required?
|
66
|
-
dynamic_field.data_fill_stage.to_sym == :during_check_in &&
|
67
|
-
guest.check_in.present?
|
68
|
-
end
|
69
|
-
|
70
|
-
def check_required_fields_based_on_phase
|
71
|
-
return if dynamic_field.blank? || value.present?
|
72
|
-
|
73
|
-
case dynamic_field.data_fill_stage.to_sym
|
74
|
-
when :pre_registration
|
75
|
-
errors.add(:value, I18n.t('guest_dynamic_field.errors.pre_registration_required')) if pre_registration_required?
|
76
|
-
when :post_registration
|
77
|
-
errors.add(:value, I18n.t('guest_dynamic_field.errors.post_registration_required')) if post_registration_required?
|
78
|
-
when :during_check_in
|
79
|
-
errors.add(:value, I18n.t('guest_dynamic_field.errors.during_check_in_required')) if during_check_in_required?
|
80
|
-
end
|
81
|
-
end
|
82
42
|
end
|
83
43
|
end
|
@@ -0,0 +1,11 @@
|
|
1
|
+
module SpreeCmCommissioner
|
2
|
+
class Inventory
|
3
|
+
include ActiveModel::Model
|
4
|
+
|
5
|
+
attr_accessor :variant_id, :inventory_date, :quantity_available, :max_capacity, :product_type
|
6
|
+
|
7
|
+
validates :variant_id, presence: true
|
8
|
+
validates :quantity_available, numericality: { only_integer: true, greater_than_or_equal_to: 0 }, allow_nil: true
|
9
|
+
validates :max_capacity, numericality: { only_integer: true, greater_than_or_equal_to: 0 }, allow_nil: true
|
10
|
+
end
|
11
|
+
end
|
@@ -0,0 +1,69 @@
|
|
1
|
+
module SpreeCmCommissioner
|
2
|
+
class InventoryItem < Base
|
3
|
+
include SpreeCmCommissioner::ProductType
|
4
|
+
|
5
|
+
# Association
|
6
|
+
belongs_to :variant, class_name: 'Spree::Variant', optional: false
|
7
|
+
has_many :prices, class_name: 'Spree::Price', inverse_of: :inventory_item
|
8
|
+
|
9
|
+
# Validation
|
10
|
+
validates :quantity_available, numericality: { greater_than_or_equal_to: 0 }
|
11
|
+
validates :max_capacity, numericality: { greater_than_or_equal_to: 0 } # Originally inventory of each variant.
|
12
|
+
validates :inventory_date, presence: true, if: :permanent_stock?
|
13
|
+
validates :variant_id, uniqueness: { scope: :inventory_date, message: -> (object, _data) { "The variant is taken on #{object.inventory_date}" } }
|
14
|
+
|
15
|
+
# Scope
|
16
|
+
scope :active, -> { where(inventory_date: nil).or(where('inventory_date >= ?', Time.zone.today)) }
|
17
|
+
|
18
|
+
before_save -> { self.product_type = variant.product_type }, if: -> { product_type.nil? }
|
19
|
+
|
20
|
+
# This method is only used when admin update stock
|
21
|
+
def adjust_quantity!(quantity)
|
22
|
+
with_lock do
|
23
|
+
self.max_capacity = [max_capacity + quantity, 0].max
|
24
|
+
self.quantity_available = [quantity_available + quantity, 0].max
|
25
|
+
save!
|
26
|
+
|
27
|
+
# When user has been searched or booked a product, it has cached the quantity in redis,
|
28
|
+
# So we need to update redis cache if inventory key has been created in redis
|
29
|
+
adjust_quantity_in_redis(quantity)
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
def redis_key
|
34
|
+
"inventory:#{id}"
|
35
|
+
end
|
36
|
+
|
37
|
+
def adjust_quantity_in_redis(quantity)
|
38
|
+
SpreeCmCommissioner.redis_pool.with do |redis|
|
39
|
+
cached_quantity_available = redis.get(redis_key)
|
40
|
+
# ignore if redis doesn't exist
|
41
|
+
return if cached_quantity_available.nil? # rubocop:disable Lint/NonLocalExitFromIterator
|
42
|
+
|
43
|
+
script = <<~LUA
|
44
|
+
local key = KEYS[1]
|
45
|
+
local increment = tonumber(ARGV[1])
|
46
|
+
local current = tonumber(redis.call('GET', key) or 0)
|
47
|
+
local new_value = current + increment
|
48
|
+
if new_value < 0 then
|
49
|
+
new_value = 0
|
50
|
+
end
|
51
|
+
redis.call('SET', key, new_value)
|
52
|
+
return new_value
|
53
|
+
LUA
|
54
|
+
|
55
|
+
redis.eval(script, keys: [redis_key], argv: [quantity])
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
def active?
|
60
|
+
inventory_date.nil? || inventory_date >= Time.zone.today
|
61
|
+
end
|
62
|
+
|
63
|
+
def redis_expired_in
|
64
|
+
expired_in = 31_536_000 # 1 year for normal stock
|
65
|
+
expired_in = Time.parse(inventory_date.to_s).end_of_day.to_i - Time.zone.now.to_i if inventory_date.present?
|
66
|
+
[expired_in, 0].max
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|