diff --git a/api/v01/entities/vrp_input.rb b/api/v01/entities/vrp_input.rb index 2f3dbf1ab..3597e49d5 100644 --- a/api/v01/entities/vrp_input.rb +++ b/api/v01/entities/vrp_input.rb @@ -200,6 +200,7 @@ module VrpConfiguration optional(:geometry_polyline, type: Boolean, documentation: { hidden: true }, desc: '[DEPRECATED] Use geometry instead, with :polylines or :encoded_polylines') optional(:intermediate_solutions, type: Boolean, desc: 'Return intermediate solutions if available') optional(:csv, type: Boolean, desc: 'The output is a CSV file if you do not specify api format') + optional(:use_deprecated_csv_headers, type: Boolean, desc: 'Forces API to ignore provided language to return old CSV headers') optional(:allow_empty_result, type: Boolean, desc: 'Allow no solution from the solver used') end diff --git a/lib/helper.rb b/lib/helper.rb index 79dd6714c..0556ae638 100644 --- a/lib/helper.rb +++ b/lib/helper.rb @@ -65,19 +65,21 @@ def self.euclidean_distance(loc_a, loc_b) def self.merge_results(results, merge_unassigned = true) results.flatten! + results.compact! { - solvers: results.flat_map{ |r| r && r[:solvers] }.compact, - cost: results.map{ |r| r && r[:cost] }.compact.reduce(&:+), - cost_details: results.map{ |r| r && r[:cost_details] }.compact.sum, - iterations: (results.size != 1) ? nil : results[0] && results[0][:iterations], - heuristic_synthesis: (results.size != 1) ? nil : results[0] && results[0][:heuristic_synthesis], - routes: results.flat_map{ |r| r && r[:routes] }.compact.uniq, - unassigned: merge_unassigned ? results.flat_map{ |r| r && r[:unassigned] }.compact.uniq : results.map{ |r| r && r[:unassigned] }.compact.last, - elapsed: results.map{ |r| r && r[:elapsed] || 0 }.reduce(&:+), - total_time: results.map{ |r| r && r[:total_time] }.compact.reduce(&:+), - total_travel_time: results.map{ |r| r && r[:total_travel_time] }.compact.reduce(&:+), - total_value: results.map{ |r| r && r[:total_travel_value] }.compact.reduce(&:+), - total_distance: results.map{ |r| r && r[:total_distance] }.compact.reduce(&:+) + solvers: results.flat_map{ |r| r[:solvers] }.compact, + cost: results.map{ |r| r[:cost] }.compact.reduce(&:+), + cost_details: results.map{ |r| r[:cost_details] }.compact.sum, + iterations: results.size != 1 ? nil : results[0][:iterations], + heuristic_synthesis: results.size != 1 ? nil : results[0][:heuristic_synthesis], + routes: results.flat_map{ |r| r[:routes] }.compact.uniq, + unassigned: merge_unassigned ? results.flat_map{ |r| r[:unassigned] }.compact.uniq : results.map{ |r| r[:unassigned] }.compact.last, + elapsed: results.map{ |r| r[:elapsed] || 0 }.reduce(&:+), + total_time: results.map{ |r| r[:total_time] }.compact.reduce(&:+), + total_travel_time: results.map{ |r| r[:total_travel_time] }.compact.reduce(&:+), + total_value: results.map{ |r| r[:total_travel_value] }.compact.reduce(&:+), + total_distance: results.map{ |r| r[:total_distance] }.compact.reduce(&:+), + use_deprecated_csv_headers: results.any?{ |r| r[:use_deprecated_csv_headers] }, } end diff --git a/lib/output_helper.rb b/lib/output_helper.rb index 288d0b5b2..34458eee9 100644 --- a/lib/output_helper.rb +++ b/lib/output_helper.rb @@ -208,6 +208,7 @@ def self.formatted_duration(duration) def self.build_csv(solutions) return unless solutions + I18n.locale = :legacy if solutions.any?{ |s| s[:use_deprecated_csv_headers] } header, unit_ids, max_timewindows_size, scheduling, any_unassigned = generate_header(solutions) CSV.generate{ |output_csv| diff --git a/models/configuration.rb b/models/configuration.rb index 9bd462dfa..cfc17a04d 100644 --- a/models/configuration.rb +++ b/models/configuration.rb @@ -79,6 +79,7 @@ class Restitution < Base field :geometry, default: [] field :intermediate_solutions, default: true field :csv, default: false + field :use_deprecated_csv_headers, default: false field :allow_empty_result, default: false end diff --git a/models/vrp.rb b/models/vrp.rb index 533485895..d054dad6d 100644 --- a/models/vrp.rb +++ b/models/vrp.rb @@ -79,6 +79,7 @@ class Vrp < Base field :restitution_geometry, default: [] field :restitution_intermediate_solutions, default: true field :restitution_csv, default: false + field :restitution_use_deprecated_csv_headers, default: false field :restitution_allow_empty_result, default: false field :schedule_range_indices, default: nil # extends schedule_range_date @@ -462,6 +463,7 @@ def restitution=(restitution) self.restitution_geometry = restitution[:geometry] self.restitution_intermediate_solutions = restitution[:intermediate_solutions] self.restitution_csv = restitution[:csv] + self.restitution_use_deprecated_csv_headers = restitution[:use_deprecated_csv_headers] self.restitution_allow_empty_result = restitution[:allow_empty_result] end diff --git a/optimizer_wrapper.rb b/optimizer_wrapper.rb index 634e423a4..58a4e5cc7 100644 --- a/optimizer_wrapper.rb +++ b/optimizer_wrapper.rb @@ -190,6 +190,7 @@ def self.define_process(service_vrp, job = nil, &block) check_result_consistency(expected_activity_count, result) if service_vrp[:service] != :demo # demo solver returns a fixed solution log "<-- define_process levels (dicho: #{dicho_level}, split: #{split_level}) elapsed: #{(Time.now - tic).round(2)} sec", level: :info + result[:use_deprecated_csv_headers] = vrp.restitution_use_deprecated_csv_headers result end diff --git a/test/api/v01/output_test.rb b/test/api/v01/output_test.rb index 23eae9f01..20f234425 100644 --- a/test/api/v01/output_test.rb +++ b/test/api/v01/output_test.rb @@ -275,6 +275,18 @@ def test_provided_language submit_csv api_key: 'demo', vrp: vrp, http_accept_language: provided end } + + [true, false].each{ |parameter_value| + vrp[:configuration][:restitution][:use_deprecated_csv_headers] = parameter_value + OutputHelper::Result.stub( + :build_csv, + lambda { |solutions| + assert_equal parameter_value, solutions.first[:use_deprecated_csv_headers] + } + ) do + submit_csv api_key: 'demo', vrp: vrp, http_accept_language: 'fr' + end + } end def test_returned_types @@ -478,7 +490,7 @@ def test_csv_headers_compatible_with_import_according_to_language 'duration per destination', 'visit duration', 'tags visit', 'tags', 'quantity[kg]', 'time window start 1', 'time window end 1', 'vehicle', 'reference'], es: [ - 'nombre del plan', 'referencia del plan', 'gira', 'nombre', 'tipo parada', 'lat', 'lng', 'hora', 'fin', + 'plan', 'referencia del plan', 'gira', 'nombre', 'tipo parada', 'lat', 'lng', 'hora', 'fin', 'horario inicio 1', 'horario fin 1', 'duración de preparación', 'duración visita', 'cantidad[kg]', 'etiquetas visita', 'etiquetas', 'vehículo', 'referencia visita'], fr: ['plan', 'référence plan', 'tournée', 'nom', 'lat', 'lng', 'type arrêt', 'heure', @@ -497,4 +509,26 @@ def test_csv_headers_compatible_with_import_according_to_language } end end + + def test_use_deprecated_csv_headers_asynchronously + vrp = VRP.lat_lon + vrp[:configuration][:restitution] = { csv: true } + + legacy_basic_headers = %w[vehicle_id id point_id type begin_time end_time setup_duration duration skills] + french_basic_headers = ['tournée', 'référence', 'heure', 'fin de la mission', 'durée client', 'durée visite', 'libellés'] + [[true, legacy_basic_headers], + [false, french_basic_headers]].each{ |parameter, expected| + + vrp[:configuration][:restitution][:use_deprecated_csv_headers] = parameter + + asynchronously start_worker: true do + @job_id = submit_csv api_key: 'demo', vrp: vrp, http_accept_language: 'fr' + wait_status_csv @job_id, 200, api_key: 'demo', http_accept_language: 'fr' + current_headers = last_response.body.split("\n").first.split(',') + assert_empty expected - current_headers + + delete_completed_job @job_id, api_key: 'ortools' + end + } + end end