diff options
54 files changed, 1336 insertions, 240 deletions
diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb index 8bd3da2f9..45b7f55f6 100644 --- a/app/controllers/application_controller.rb +++ b/app/controllers/application_controller.rb @@ -36,16 +36,6 @@ class ApplicationController < ActionController::Base end helper_method :current_organisation - def current_offer_workbench - current_organisation.workbenches.find_by_name("Gestion de l'offre") - end - helper_method :current_offer_workbench - - def current_workgroup - current_offer_workbench.workgroup - end - helper_method :current_workgroup - def current_functional_scope functional_scope = current_organisation.sso_attributes.try(:[], "functional_scope") if current_organisation JSON.parse(functional_scope) if functional_scope diff --git a/app/controllers/referentials_controller.rb b/app/controllers/referentials_controller.rb index 0ed3f75dd..5267c15d8 100644 --- a/app/controllers/referentials_controller.rb +++ b/app/controllers/referentials_controller.rb @@ -1,5 +1,6 @@ class ReferentialsController < ChouetteController defaults :resource_class => Referential + before_action :load_workbench include PolicyChecker respond_to :html @@ -30,7 +31,7 @@ class ReferentialsController < ChouetteController def show resource.switch show! do |format| - @referential = @referential.decorate(context: { current_workbench_id: params[:current_workbench_id] } ) + @referential = @referential.decorate() @reflines = lines_collection.paginate(page: params[:page], per_page: 10) @reflines = ReferentialLineDecorator.decorate( @reflines, @@ -141,7 +142,6 @@ class ReferentialsController < ChouetteController if params[:from] source_referential = Referential.find(params[:from]) @referential = Referential.new_from(source_referential, current_functional_scope) - @referential.workbench_id = params[:current_workbench_id] end @referential.data_format = current_organisation.data_format @@ -175,4 +175,12 @@ class ReferentialsController < ChouetteController ) end + def load_workbench + @workbench ||= Workbench.find(params[:workbench_id]) if params[:workbench_id] + @workbench ||= resource&.workbench if params[:id] + @workbench + end + + alias_method :current_workbench, :load_workbench + helper_method :current_workbench end diff --git a/app/controllers/vehicle_journeys_controller.rb b/app/controllers/vehicle_journeys_controller.rb index 9dbb3bc43..14795227c 100644 --- a/app/controllers/vehicle_journeys_controller.rb +++ b/app/controllers/vehicle_journeys_controller.rb @@ -154,7 +154,7 @@ class VehicleJourneysController < ChouetteController private def load_custom_fields - @custom_fields = current_workgroup&.custom_fields_definitions || {} + @custom_fields = referential.workgroup&.custom_fields_definitions || {} end def map_stop_points points diff --git a/app/decorators/referential_decorator.rb b/app/decorators/referential_decorator.rb index 3132cbf92..41cad237d 100644 --- a/app/decorators/referential_decorator.rb +++ b/app/decorators/referential_decorator.rb @@ -22,12 +22,12 @@ class ReferentialDecorator < AF83::Decorator instance_decorator.action_link policy: :clone, secondary: :show do |l| l.content t('actions.clone') - l.href { h.new_referential_path(from: object.id, current_workbench_id: context[:current_workbench_id]) } + l.href { h.duplicate_workbench_referential_path(object) } end instance_decorator.action_link policy: :validate, secondary: :show do |l| l.content t('actions.validate') - l.href { h.referential_select_compliance_control_set_path(object.id) } + l.href { h.select_compliance_control_set_referential_path(object.id) } end instance_decorator.action_link policy: :archive, secondary: :show do |l| diff --git a/app/helpers/referentials_helper.rb b/app/helpers/referentials_helper.rb index 8251377aa..e464ec8a5 100644 --- a/app/helpers/referentials_helper.rb +++ b/app/helpers/referentials_helper.rb @@ -15,4 +15,14 @@ module ReferentialsHelper service = ReferentialOverview.new referential, self render partial: "referentials/overview", locals: {referential: referential, overview: service} end + + def mutual_workbench workbench + current_user.organisation.workbenches.where(workgroup_id: workbench.workgroup_id).last + end + + def duplicate_workbench_referential_path referential + workbench = mutual_workbench referential.workbench + raise "Missing workbench for referential #{referential.name}" unless workbench.present? + new_workbench_referential_path(workbench, from: referential.id) + end end diff --git a/app/javascript/vehicle_journeys/components/tools/select2s/MissionSelect2.js b/app/javascript/vehicle_journeys/components/tools/select2s/MissionSelect2.js index 7ab85a1ea..72dbd0152 100644 --- a/app/javascript/vehicle_journeys/components/tools/select2s/MissionSelect2.js +++ b/app/javascript/vehicle_journeys/components/tools/select2s/MissionSelect2.js @@ -29,11 +29,11 @@ export default class BSelect4 extends Component { val = this.props.selection.selectedJPModal } } - if(this.useAjax()){ - val = val.published_name - } - else{ - if(val){ + if(val){ + if(this.useAjax()){ + val = val.published_name + } + else{ val = val.id } } diff --git a/app/models/chouette/company.rb b/app/models/chouette/company.rb index b3d40ab96..53e412600 100644 --- a/app/models/chouette/company.rb +++ b/app/models/chouette/company.rb @@ -15,6 +15,5 @@ module Chouette [:organizational_unit, :operating_department_name, :code, :phone, :fax, :email, :url, :time_zone] end - end end diff --git a/app/models/chouette/journey_pattern.rb b/app/models/chouette/journey_pattern.rb index aa9fdb810..830e985d9 100644 --- a/app/models/chouette/journey_pattern.rb +++ b/app/models/chouette/journey_pattern.rb @@ -170,5 +170,21 @@ module Chouette end full end + + def set_distances distances + raise "inconsistent data: #{distances.count} values for #{stop_points.count} stops" unless distances.count == stop_points.count + prev = distances[0].to_i + _costs = self.costs + distances[1..-1].each_with_index do |distance, i| + distance = distance.to_i + relative = distance - prev + prev = distance + start, stop = stop_points[i..i+1] + key = "#{start.stop_area_id}-#{stop.stop_area_id}" + _costs[key] ||= {} + _costs[key]["distance"] = relative + end + self.costs = _costs + end end end diff --git a/app/models/chouette/line.rb b/app/models/chouette/line.rb index ba2e2755d..d077d5c9d 100644 --- a/app/models/chouette/line.rb +++ b/app/models/chouette/line.rb @@ -41,6 +41,7 @@ module Chouette validates_presence_of :name + scope :by_text, ->(text) { where('lower(name) LIKE :t or lower(published_name) LIKE :t or lower(objectid) LIKE :t or lower(comment) LIKE :t or lower(number) LIKE :t', t: "%#{text.downcase}%") } @@ -80,6 +81,14 @@ module Chouette line_referential.companies.where(id: ([company_id] + Array(secondary_company_ids)).compact) end + def deactivate + self.deactivated = true + end + + def activate + self.deactivated = false + end + def deactivate! update_attribute :deactivated, true end diff --git a/app/models/chouette/purchase_window.rb b/app/models/chouette/purchase_window.rb index 334493015..157390a21 100644 --- a/app/models/chouette/purchase_window.rb +++ b/app/models/chouette/purchase_window.rb @@ -19,6 +19,7 @@ module Chouette scope :contains_date, ->(date) { where('date ? <@ any (date_ranges)', date) } scope :overlap_dates, ->(date_range) { where('daterange(?, ?) && any (date_ranges)', date_range.first, date_range.last + 1.day) } + scope :matching_dates, ->(date_range) { where('ARRAY[daterange(?, ?)] = date_ranges', date_range.first, date_range.last + 1.day) } def self.ransackable_scopes(auth_object = nil) [:contains_date] diff --git a/app/models/chouette/route.rb b/app/models/chouette/route.rb index 5cc5d8b0d..3729deb7d 100644 --- a/app/models/chouette/route.rb +++ b/app/models/chouette/route.rb @@ -185,6 +185,12 @@ module Chouette return true end + def full_journey_pattern + journey_pattern = journey_patterns.find_or_create_by registration_number: self.number, name: self.name + journey_pattern.stop_points = self.stop_points + journey_pattern + end + protected def self.vehicle_journeys_timeless(stop_point_id) diff --git a/app/models/chouette/stop_area.rb b/app/models/chouette/stop_area.rb index bb8747faa..7170dd217 100644 --- a/app/models/chouette/stop_area.rb +++ b/app/models/chouette/stop_area.rb @@ -369,6 +369,14 @@ module Chouette !activated? end + def activate + self.deleted_at = nil + end + + def deactivate + self.deleted_at = Time.now + end + def activate! update_attribute :deleted_at, nil end @@ -384,8 +392,8 @@ module Chouette def country_name return unless country_code - country = ISO3166::Country[country_code] + return unless country country.translations[I18n.locale.to_s] || country.name end diff --git a/app/models/chouette/time_table.rb b/app/models/chouette/time_table.rb index 15b22b671..b76de852a 100644 --- a/app/models/chouette/time_table.rb +++ b/app/models/chouette/time_table.rb @@ -44,10 +44,10 @@ module Chouette attrs << self.int_day_types dates = self.dates dates += TimeTableDate.where(time_table_id: self.id) - attrs << dates.map(&:checksum).map(&:to_s).sort + attrs << dates.map(&:checksum).map(&:to_s).uniq.sort periods = self.periods periods += TimeTablePeriod.where(time_table_id: self.id) - attrs << periods.map(&:checksum).map(&:to_s).sort + attrs << periods.map(&:checksum).map(&:to_s).uniq.sort end end diff --git a/app/models/chouette/vehicle_journey.rb b/app/models/chouette/vehicle_journey.rb index 1963797d2..6209993de 100644 --- a/app/models/chouette/vehicle_journey.rb +++ b/app/models/chouette/vehicle_journey.rb @@ -143,7 +143,7 @@ module Chouette attrs << self.try(:company).try(:get_objectid).try(:local_id) attrs << self.footnotes.map(&:checksum).sort vjas = self.vehicle_journey_at_stops - vjas += VehicleJourneyAtStop.where(vehicle_journey_id: self.id) + vjas += VehicleJourneyAtStop.where(vehicle_journey_id: self.id) unless self.new_record? attrs << vjas.uniq.sort_by { |s| s.stop_point&.position }.map(&:checksum).sort end end diff --git a/app/models/chouette/vehicle_journey_at_stop.rb b/app/models/chouette/vehicle_journey_at_stop.rb index eda711ade..3b4f35f13 100644 --- a/app/models/chouette/vehicle_journey_at_stop.rb +++ b/app/models/chouette/vehicle_journey_at_stop.rb @@ -41,7 +41,7 @@ module Chouette :arrival_day_offset, I18n.t( 'vehicle_journey_at_stops.errors.day_offset_must_not_exceed_max', - short_id: vehicle_journey.get_objectid.short_id, + short_id: vehicle_journey&.get_objectid&.short_id, max: DAY_OFFSET_MAX + 1 ) ) @@ -52,7 +52,7 @@ module Chouette :departure_day_offset, I18n.t( 'vehicle_journey_at_stops.errors.day_offset_must_not_exceed_max', - short_id: vehicle_journey.get_objectid.short_id, + short_id: vehicle_journey&.get_objectid&.short_id, max: DAY_OFFSET_MAX + 1 ) ) @@ -69,8 +69,8 @@ module Chouette def checksum_attributes [].tap do |attrs| - attrs << self.departure_time.try(:to_s, :time) - attrs << self.arrival_time.try(:to_s, :time) + attrs << self.departure_time&.utc.try(:to_s, :time) + attrs << self.arrival_time&.utc.try(:to_s, :time) attrs << self.departure_day_offset.to_s attrs << self.arrival_day_offset.to_s end diff --git a/app/models/chouette/vehicle_journey_at_stops_day_offset.rb b/app/models/chouette/vehicle_journey_at_stops_day_offset.rb index b2cb90d11..7497cd72c 100644 --- a/app/models/chouette/vehicle_journey_at_stops_day_offset.rb +++ b/app/models/chouette/vehicle_journey_at_stops_day_offset.rb @@ -11,13 +11,19 @@ module Chouette @at_stops.inject(nil) do |prior_stop, stop| next stop if prior_stop.nil? - if stop.arrival_time < prior_stop.departure_time || - stop.arrival_time < prior_stop.arrival_time + # we only compare time of the day, not actual times + stop_arrival_time = stop.arrival_time - stop.arrival_time.to_date.to_time + stop_departure_time = stop.departure_time - stop.departure_time.to_date.to_time + prior_stop_arrival_time = prior_stop.arrival_time - prior_stop.arrival_time.to_date.to_time + prior_stop_departure_time = prior_stop.departure_time - prior_stop.departure_time.to_date.to_time + + if stop_arrival_time < prior_stop_departure_time || + stop_arrival_time < prior_stop_arrival_time arrival_offset += 1 end - if stop.departure_time < stop.arrival_time || - stop.departure_time < prior_stop.departure_time + if stop_departure_time < stop_arrival_time || + stop_departure_time < prior_stop_departure_time departure_offset += 1 end @@ -39,4 +45,4 @@ module Chouette save end end -end
\ No newline at end of file +end diff --git a/app/models/organisation.rb b/app/models/organisation.rb index da7d1fcf3..e8fb4e060 100644 --- a/app/models/organisation.rb +++ b/app/models/organisation.rb @@ -80,4 +80,8 @@ class Organisation < ActiveRecord::Base features && features.include?(feature.to_s) end + def default_workbench + workbenches.default + end + end diff --git a/app/models/referential.rb b/app/models/referential.rb index 509e0412f..09c2e7d34 100644 --- a/app/models/referential.rb +++ b/app/models/referential.rb @@ -51,7 +51,9 @@ class Referential < ActiveRecord::Base belongs_to :stop_area_referential validates_presence_of :stop_area_referential has_many :stop_areas, through: :stop_area_referential + belongs_to :workbench + delegate :workgroup, to: :workbench, allow_nil: true belongs_to :referential_suite diff --git a/app/models/simple_importer.rb b/app/models/simple_importer.rb new file mode 100644 index 000000000..b824d596d --- /dev/null +++ b/app/models/simple_importer.rb @@ -0,0 +1,422 @@ +class SimpleImporter < ActiveRecord::Base + attr_accessor :configuration + + def self.define name + @importers ||= {} + configuration = Configuration.new name + yield configuration + configuration.validate! + @importers[name.to_sym] = configuration + end + + def self.find_configuration name + @importers ||= {} + configuration = @importers[name.to_sym] + raise "Importer not found: #{name}" unless configuration + configuration + end + + def initialize *args + super *args + self.configuration = self.class.find_configuration self.configuration_name + self.journal ||= [] + end + + def configure + new_config = configuration.duplicate + yield new_config + new_config.validate! + self.configuration = new_config + end + + def context + self.configuration.context + end + + def resolve col_name, value, &block + val = block.call(value) + return val if val.present? + @resolution_queue[[col_name.to_s, value]].push({record: @current_record, attribute: @current_attribute, block: block}) + nil + end + + def import opts={} + @verbose = opts.delete :verbose + + + @resolution_queue = Hash.new{|h,k| h[k] = []} + @errors = [] + @messages = [] + @number_of_lines = 0 + @padding = 1 + @current_line = 0 + fail_with_error "File not found: #{self.filepath}" do + @number_of_lines = CSV.read(self.filepath, self.configuration.csv_options).length + @padding = [1, Math.log(@number_of_lines, 10).ceil()].max + end + + + self.configuration.before_actions(:parsing).each do |action| action.call self end + + @statuses = "" + + if ENV["NO_TRANSACTION"] + process_csv_file + else + ActiveRecord::Base.transaction do + process_csv_file + end + end + self.status ||= :success + rescue FailedImport + self.status = :failed + ensure + self.save! + end + + def fail_with_error msg=nil, opts={} + begin + yield + rescue => e + msg = msg.call if msg.is_a?(Proc) + custom_print "\nFAILED: \n errors: #{msg}\n exception: #{e.message}\n#{e.backtrace.join("\n")}", color: :red unless self.configuration.ignore_failures + push_in_journal({message: msg, error: e.message, event: :error, kind: :error}) + @new_status = colorize("x", :red) + if self.configuration.ignore_failures + raise FailedRow if opts[:abort_row] + else + raise FailedImport + end + end + end + + def encode_string s + s.encode("utf-8").force_encoding("utf-8") + end + + def dump_csv_from_context + filepath = "./#{self.configuration_name}_#{Time.now.strftime "%y%m%d%H%M"}.csv" + # for some reason, context[:csv].to_csv does not work + CSV.open(filepath, 'w') do |csv| + header = true + context[:csv].each do |row| + csv << row.headers if header + csv << row.fields + header = false + end + end + log "CSV file dumped in #{filepath}" + end + + def log msg, opts={} + msg = colorize msg, opts[:color] if opts[:color] + if opts[:append] + @messages[-1] = (@messages[-1] || "") + msg + else + @messages << msg + end + print_state + end + + protected + + def process_csv_file + self.configuration.before_actions(:all).each do |action| action.call self end + log "Starting import ...", color: :green + + (context[:csv] || CSV.read(filepath, self.configuration.csv_options)).each do |row| + @current_row = row + @new_status = nil + begin + handle_row row + fail_with_error ->(){ @current_record.errors.messages } do + new_record = @current_record&.new_record? + @new_status ||= new_record ? colorize("✓", :green) : colorize("-", :orange) + @event = new_record ? :creation : :update + self.configuration.before_actions(:each_save).each do |action| + action.call self, @current_record + end + ### This could fail if the record has a mandatory relation which is not yet resolved + ### TODO: do not attempt to save if the current record if waiting for resolution + ### and fail at the end if there remains unresolved relations + if @current_record + if self.configuration.ignore_failures + unless @current_record.save + @new_status = colorize("x", :red) + push_in_journal({message: "errors: #{@current_record.errors.messages}", error: "invalid record", event: :error, kind: :error}) + end + else + @current_record.save! + end + end + self.configuration.after_actions(:each_save).each do |action| + action.call self, @current_record + end + end + rescue FailedRow + @new_status = colorize("x", :red) + end + push_in_journal({event: @event, kind: :log}) if @current_record&.valid? + @statuses += @new_status + self.configuration.columns.each do |col| + if @current_record && col.name && @resolution_queue.any? + val = @current_record.send col[:attribute] + (@resolution_queue.delete([col.name, val]) || []).each do |res| + record = res[:record] + attribute = res[:attribute] + value = res[:block].call(val, record) + record.send "#{attribute}=", value + record.save! + end + end + end + print_state + @current_line += 1 + end + + begin + self.configuration.after_actions(:all).each do |action| + action.call self + end + rescue FailedRow + end + end + + def handle_row row + if self.configuration.get_custom_handler + instance_exec(row, &self.configuration.get_custom_handler) + else + fail_with_error "", abort_row: true do + @current_record = self.configuration.find_record row + self.configuration.columns.each do |col| + @current_attribute = col[:attribute] + val = col[:value] + if val.nil? || val.is_a?(Proc) + if row.has_key? col.name + if val.is_a?(Proc) + val = instance_exec(row[col.name], &val) + else + val = row[col.name] + end + else + push_in_journal({event: :column_not_found, message: "Column not found: #{col.name}", kind: :warning}) + self.status ||= :success_with_warnings + end + end + + if val.nil? && col.required? + raise "MISSING VALUE FOR COLUMN #{col.name}" + end + val = encode_string(val) if val.is_a?(String) + @current_record.send "#{@current_attribute}=", val if val + end + end + end + end + + def push_in_journal data + line = @current_line + 1 + line += 1 if configuration.headers + self.journal.push data.update(line: line, row: @current_row) + if data[:kind] == :error || data[:kind] == :warning + @errors.push data + end + end + + def colorize txt, color + color = { + red: "31", + green: "32", + orange: "33", + }[color] || "33" + "\e[#{color}m#{txt}\e[0m" + end + + def print_state + return unless @verbose + + @status_width ||= begin + term_width = %x(tput cols).to_i + term_width - @padding - 10 + rescue + 100 + end + + @status_height ||= begin + term_height = %x(tput lines).to_i + term_height - 3 + rescue + 50 + end + + full_status = @statuses || "" + full_status = full_status.last(@status_width*10) || "" + padding_size = [(@number_of_lines - @current_line - 1), (@status_width - full_status.size/10)].min + full_status = "#{full_status}#{"."*[padding_size, 0].max}" + + msg = "#{"%#{@padding}d" % (@current_line + 1)}/#{@number_of_lines}: #{full_status}" + + lines_count = [(@status_height / 2) - 3, 1].max + + if @messages.any? + msg += "\n\n" + msg += colorize "=== MESSAGES (#{@messages.count}) ===\n", :green + msg += "[...]\n" if @messages.count > lines_count + msg += @messages.last(lines_count).join("\n") + msg += "\n"*[lines_count-@messages.count, 0].max + end + + if @errors.any? + msg += "\n\n" + msg += colorize "=== ERRORS (#{@errors.count}) ===\n", :red + msg += "[...]\n" if @errors.count > lines_count + msg += @errors.last(lines_count).map do |j| + kind = j[:kind] + kind = colorize(kind, kind == :error ? :red : :orange) + encode_string "[#{kind}]\t\tL#{j[:line]}\t#{j[:error]}\t\t#{j[:message]}" + end.join("\n") + end + custom_print msg, clear: true + end + + def custom_print msg, opts={} + return unless @verbose + out = "" + msg = colorize(msg, opts[:color]) if opts[:color] + puts "\e[H\e[2J" if opts[:clear] + out += msg + print out + end + + class FailedImport < RuntimeError + end + + class FailedRow < RuntimeError + end + + class Configuration + attr_accessor :model, :headers, :separator, :key, :context, :encoding, :ignore_failures, :scope + attr_reader :columns + + def initialize import_name, opts={} + @import_name = import_name + @key = opts[:key] || "id" + @headers = opts.has_key?(:headers) ? opts[:headers] : true + @separator = opts[:separator] || "," + @encoding = opts[:encoding] + @columns = opts[:columns] || [] + @model = opts[:model] + @custom_handler = opts[:custom_handler] + @before = opts[:before] + @after = opts[:after] + @ignore_failures = opts[:ignore_failures] + @context = opts[:context] || {} + @scope = opts[:scope] + end + + def duplicate + Configuration.new @import_name, self.options + end + + def options + { + key: @key, + headers: @headers, + separator: @separator, + encoding: @encoding, + columns: @columns.map(&:duplicate), + model: model, + custom_handler: @custom_handler, + before: @before, + after: @after, + ignore_failures: @ignore_failures, + context: @context, + scope: @scope + } + end + + def validate! + raise "Incomplete configuration, missing model for #{@import_name}" unless model.present? + end + + def attribute_for_col col_name + column = self.columns.find{|c| c.name == col_name} + column && column[:attribute] || col_name + end + + def record_scope + _scope = @scope + _scope = instance_exec(&_scope) if _scope.is_a?(Proc) + _scope || model + end + + def find_record attrs + record_scope.find_or_initialize_by(attribute_for_col(@key) => attrs[@key.to_s]) + end + + def csv_options + { + headers: self.headers, + col_sep: self.separator, + encoding: self.encoding + } + end + + def add_column name, opts={} + @columns.push Column.new({name: name.to_s}.update(opts)) + end + + def add_value attribute, value + @columns.push Column.new({attribute: attribute, value: value}) + end + + def before group=:all, &block + @before ||= Hash.new{|h, k| h[k] = []} + @before[group].push block + end + + def after group=:all, &block + @after ||= Hash.new{|h, k| h[k] = []} + @after[group].push block + end + + def before_actions group=:all + @before ||= Hash.new{|h, k| h[k] = []} + @before[group] + end + + def after_actions group=:all + @after ||= Hash.new{|h, k| h[k] = []} + @after[group] + end + + def custom_handler &block + @custom_handler = block + end + + def get_custom_handler + @custom_handler + end + + class Column + attr_accessor :name + def initialize opts={} + @name = opts[:name] + @options = opts + @options[:attribute] ||= @name + end + + def duplicate + Column.new @options.dup + end + + def required? + !!@options[:required] + end + + def [](key) + @options[key] + end + end + end +end diff --git a/app/models/user.rb b/app/models/user.rb index 1342f60ed..31e634415 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -15,6 +15,7 @@ class User < ActiveRecord::Base # Setup accessible (or protected) attributes for your model # attr_accessible :email, :password, :current_password, :password_confirmation, :remember_me, :name, :organisation_attributes belongs_to :organisation + has_many :workbenches, through: :organisation accepts_nested_attributes_for :organisation validates :organisation, :presence => true diff --git a/app/models/workbench.rb b/app/models/workbench.rb index b80fa64ac..eb53af7aa 100644 --- a/app/models/workbench.rb +++ b/app/models/workbench.rb @@ -1,4 +1,6 @@ class Workbench < ActiveRecord::Base + DEFAULT_WORKBENCH_NAME = "Gestion de l'offre" + include ObjectidFormatterSupport belongs_to :organisation belongs_to :line_referential @@ -40,6 +42,11 @@ class Workbench < ActiveRecord::Base end end + def self.default + self.last if self.count == 1 + where(name: DEFAULT_WORKBENCH_NAME).last + end + private def initialize_output diff --git a/app/views/dashboards/_dashboard.html.slim b/app/views/dashboards/_dashboard.html.slim index 05257a766..7f78934a6 100644 --- a/app/views/dashboards/_dashboard.html.slim +++ b/app/views/dashboards/_dashboard.html.slim @@ -14,25 +14,25 @@ - if workbench.referentials.present? .list-group - workbench.referentials.limit(5).each do |referential| - = link_to referential.name, referential_path(referential, workbench_id: referential.workbench_id, current_workbench_id: workbench.id), class: 'list-group-item' + = link_to referential.name, referential_path(referential), class: 'list-group-item' - else .panel-body em.small.text-muted = t('workbenches.index.offers.no_content') - .panel.panel-default - .panel-heading - h3.panel-title.with_actions - = link_to I18n.t("activerecord.models.calendar", count: @dashboard.current_organisation.calendars.size), workgroup_calendars_path(current_workgroup) - div - = link_to '', workgroup_calendars_path(current_workgroup), class: ' fa fa-chevron-right pull-right' - - if @dashboard.current_organisation.calendars.present? - .list-group - - @dashboard.current_organisation.calendars.order("updated_at desc").limit(5).each do |calendar| - = link_to calendar.name, workgroup_calendars_path(current_workgroup, calendar), class: 'list-group-item' - - else - .panel-body - em.small.text-muted - = t('dasboard.calendars.none') + .panel.panel-default + .panel-heading + h3.panel-title.with_actions + = link_to I18n.t("activerecord.models.calendar", count: @dashboard.current_organisation.calendars.size), workgroup_calendars_path(workbench.workgroup) + div + = link_to '', workgroup_calendars_path(workbench.workgroup), class: ' fa fa-chevron-right pull-right' + - if @dashboard.current_organisation.calendars.present? + .list-group + - @dashboard.current_organisation.calendars.order("updated_at desc").limit(5).each do |calendar| + = link_to calendar.name, workgroup_calendars_path(workbench.workgroup, calendar), class: 'list-group-item' + - else + .panel-body + em.small.text-muted + = t('dasboard.calendars.none') .col-lg-6.col-md-6.col-sm-6.col-xs-12 .panel.panel-default diff --git a/app/views/layouts/navigation/_main_nav_left_content_stif.html.slim b/app/views/layouts/navigation/_main_nav_left_content_stif.html.slim index 1b7293d21..cb0698cf8 100644 --- a/app/views/layouts/navigation/_main_nav_left_content_stif.html.slim +++ b/app/views/layouts/navigation/_main_nav_left_content_stif.html.slim @@ -24,14 +24,14 @@ #miTwo.panel-collapse.collapse .list-group - - if current_user - = link_to workbench_path(current_offer_workbench), class: "list-group-item #{params[:controller] == 'workbenches' ? 'active' : ''}" do + - current_user.workbenches.each do |current_workbench| + = link_to workbench_path(current_workbench), class: "list-group-item #{params[:controller] == 'workbenches' ? 'active' : ''}" do span Jeux de données - = link_to workbench_imports_path(current_offer_workbench), class: "list-group-item #{(params[:controller] == 'imports') ? 'active' : ''}" do + = link_to workbench_imports_path(current_workbench), class: "list-group-item #{(params[:controller] == 'imports') ? 'active' : ''}" do span Import - = link_to workgroup_calendars_path(current_workgroup), class: 'list-group-item' do + = link_to workgroup_calendars_path(current_workbench.workgroup), class: 'list-group-item' do span Modèles de calendrier - = link_to workbench_compliance_check_sets_path(current_offer_workbench), class: 'list-group-item' do + = link_to workbench_compliance_check_sets_path(current_workbench), class: 'list-group-item' do span Rapport de contrôle = link_to compliance_control_sets_path, class: 'list-group-item' do span Jeux de contrôle diff --git a/app/views/referentials/_form.html.slim b/app/views/referentials/_form.html.slim index 9927f05bd..1e59ab566 100644 --- a/app/views/referentials/_form.html.slim +++ b/app/views/referentials/_form.html.slim @@ -1,4 +1,6 @@ -= simple_form_for @referential, html: {class: 'form-horizontal', id: 'referential_form'}, wrapper: :horizontal_form do |form| +- url = @referential.new_record? ? [@workbench, @referential] : [@referential] + += simple_form_for @referential, url: url, html: {class: 'form-horizontal', id: 'referential_form'}, wrapper: :horizontal_form do |form| .row .col-lg-12 diff --git a/app/views/referentials/select_compliance_control_set.html.slim b/app/views/referentials/select_compliance_control_set.html.slim index 87a888c0a..69c87aab2 100644 --- a/app/views/referentials/select_compliance_control_set.html.slim +++ b/app/views/referentials/select_compliance_control_set.html.slim @@ -2,7 +2,7 @@ .container-fluid .row .col-lg-8.col-lg-offset-2.col-md-8.col-md-offset-2.col-sm-10.col-sm-offset-1 - = form_tag validate_referential_path(params[:referential_id]), {class: 'form-horizontal', id: 'select_compliance_control_set', wrapper: :horizontal_form} do + = form_tag validate_referential_path(@referential), {class: 'form-horizontal', id: 'select_compliance_control_set', wrapper: :horizontal_form} do .row .col-lg-12 .form-group diff --git a/app/views/stif/dashboards/_dashboard.html.slim b/app/views/stif/dashboards/_dashboard.html.slim index c28696a94..83a2106bb 100644 --- a/app/views/stif/dashboards/_dashboard.html.slim +++ b/app/views/stif/dashboards/_dashboard.html.slim @@ -47,7 +47,7 @@ - if @dashboard.referentials.present? .list-group - @dashboard.referentials.first(5).each_with_index do |referential, i| - = link_to referential.name, referential_path(referential, workbench_id: referential.workbench_id, current_workbench_id: @dashboard.workbench.id), class: 'list-group-item' if i < 6 + = link_to referential.name, referential_path(referential), class: 'list-group-item' if i < 6 - else .panel-body @@ -60,12 +60,12 @@ span.badge.ml-xs = @dashboard.calendars.count if @dashboard.calendars.present? div - = link_to '', workgroup_calendars_path(current_workgroup), class: ' fa fa-chevron-right pull-right', title: t('.see') + = link_to '', workgroup_calendars_path(@dashboard.workbench.workgroup), class: ' fa fa-chevron-right pull-right', title: t('.see') - if @dashboard.calendars.present? .list-group - @dashboard.calendars.first(5).each_with_index do |calendar, i| - = link_to calendar.name, workgroup_calendar_path(current_workgroup, calendar), class: 'list-group-item' if i < 6 + = link_to calendar.name, workgroup_calendar_path(@dashboard.workbench.workgroup, calendar), class: 'list-group-item' if i < 6 - else .panel-body diff --git a/app/views/workbenches/show.html.slim b/app/views/workbenches/show.html.slim index aae34c51b..159aa8ea2 100644 --- a/app/views/workbenches/show.html.slim +++ b/app/views/workbenches/show.html.slim @@ -5,7 +5,7 @@ .col-lg-12.text-right - if policy(Referential).create? = link_to t('actions.import'), workbench_imports_path(@workbench), class: 'btn btn-primary' - = link_to t('actions.add'), new_referential_path(workbench_id: @workbench), class: 'btn btn-primary' + = link_to t('actions.add'), new_workbench_referential_path(@workbench), class: 'btn btn-primary' = link_to t('workbenches.actions.show_output'), workbench_output_path(@workbench), class: 'btn btn-primary' .page_content @@ -25,7 +25,7 @@ key: :name, \ attribute: 'name', \ link_to: lambda do |referential| \ - referential_path(referential, current_workbench_id: params[:id]) \ + referential_path(referential) \ end \ ), \ TableBuilderHelper::Column.new( \ diff --git a/config/breadcrumbs.rb b/config/breadcrumbs.rb index 2772895fe..00ccf16bd 100644 --- a/config/breadcrumbs.rb +++ b/config/breadcrumbs.rb @@ -23,12 +23,12 @@ end crumb :referential do |referential| link breadcrumb_name(referential), referential_path(referential) - parent :workbench, current_offer_workbench + parent :workbench, mutual_workbench(referential.workbench) end crumb :referentials do |referential| - link I18n.t('referentials.index.title'), referentials_path() - parent :workbench, current_offer_workbench + link I18n.t('referentials.index.title'), workbench_path(current_workbench) + parent :workbench, mutual_workbench(current_workbench) end crumb :referential_companies do |referential| diff --git a/config/deploy.rb b/config/deploy.rb index 136c60196..86339d137 100644 --- a/config/deploy.rb +++ b/config/deploy.rb @@ -13,7 +13,8 @@ set :group_writable, true set :bundle_cmd, "/var/lib/gems/#{ruby_version}/bin/bundle" set :rake, "#{bundle_cmd} exec rake" set :default_environment, { - 'PATH' => "/var/lib/gems/#{ruby_version}/bin:$PATH" + 'PATH' => "/var/lib/gems/#{ruby_version}/bin:$PATH", + 'NODE_ENV' => "production" } set :keep_releases, -> { fetch(:kept_releases, 5) } diff --git a/config/initializers/apartment.rb b/config/initializers/apartment.rb index 2d06fb88b..fc652a2da 100644 --- a/config/initializers/apartment.rb +++ b/config/initializers/apartment.rb @@ -81,6 +81,7 @@ Apartment.configure do |config| 'ComplianceCheckMessage', 'Merge', 'CustomField', + 'SimpleImporter', ] # use postgres schemas? diff --git a/config/routes.rb b/config/routes.rb index 0b657b028..456cb66f5 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -19,99 +19,10 @@ ChouetteIhm::Application.routes.draw do resource :output, controller: :workbench_outputs resources :merges - end - - devise_for :users, :controllers => { - :registrations => 'users/registrations', :invitations => 'users/invitations' - } - - devise_scope :user do - authenticated :user do - root :to => 'workbenches#index', as: :authenticated_root - end - - unauthenticated :user do - target = 'devise/sessions#new' - - if Rails.application.config.chouette_authentication_settings[:type] == "cas" - target = 'devise/cas_sessions#new' - end - - root :to => target, as: :unauthenticated_root - end - end - - mount Sidekiq::Web => '/sidekiq' - - namespace :api do - namespace :v1 do - resources :workbenches, only: [:index, :show] do - resources :imports, only: [:index, :show, :create] - end - resources :access_links, only: [:index, :show] - resources :access_points, only: [:index, :show] - resources :connection_links, only: [:index, :show] - resources :companies, only: [:index, :show] - resources :group_of_lines, only: [:index, :show] - resources :netex_imports, only: :create - resources :journey_patterns, only: :show - resources :lines, only: [:index, :show] do - resources :journey_patterns, only: [:index, :show] - resources :routes, only: [:index, :show] do - resources :vehicle_journeys, only: [:index, :show] - resources :journey_patterns, only: [:index, :show] - resources :stop_areas, only: [:index, :show] - end - end - resources :networks, only: [:index, :show] - resources :routes, only: :show - resources :stop_areas, only: [:index, :show] - resources :time_tables, only: [:index, :show] - resources :vehicle_journeys, only: :show - namespace :internals do - get 'compliance_check_sets/:id/notify_parent', to: 'compliance_check_sets#notify_parent' - get 'netex_imports/:id/notify_parent', to: 'netex_imports#notify_parent' - end - end - end - resource :organisation, :only => [:show, :edit, :update] do - resources :users + resources :referentials, only: %w(new create) end - resources :api_keys, :only => [:edit, :update, :new, :create, :destroy] - - resources :compliance_control_sets do - get :simple, on: :member - get :clone, on: :member - resources :compliance_controls, except: :index do - get :select_type, on: :collection - end - resources :compliance_control_blocks, :except => [:show, :index] - end - - deactivable = Proc.new do - put :deactivate, on: :member - put :activate, on: :member - end - - resources :stop_area_referentials, :only => [:show] do - post :sync, on: :member - resources :stop_areas do - put :deactivate, on: :member - put :activate, on: :member - get :autocomplete, on: :collection - end - end - - resources :line_referentials, :only => [:show, :edit, :update] do - post :sync, on: :member - resources :lines, &deactivable - resources :group_of_lines - resources :companies - resources :networks - end - resources :workgroups do resources :calendars do get :autocomplete, on: :collection, controller: 'autocomplete_calendars' @@ -121,13 +32,19 @@ ChouetteIhm::Application.routes.draw do end end - resources :referentials, except: :index do + resources :referentials, except: %w(new create) do + + member do + put :archive + put :unarchive + get :select_compliance_control_set + post :validate + end + resources :autocomplete_stop_areas, only: [:show, :index] do get 'around', on: :member end resources :autocomplete_purchase_windows, only: [:index] - get :select_compliance_control_set - post :validate, on: :member resources :autocomplete_time_tables, only: [:index] resources :autocomplete_timebands resources :group_of_lines, controller: "referential_group_of_lines" do @@ -136,12 +53,6 @@ ChouetteIhm::Application.routes.draw do end end - # Archive/unarchive - member do - put :archive - put :unarchive - end - resources :networks, controller: "referential_networks" match 'lines' => 'lines#destroy_all', :via => :delete @@ -239,6 +150,105 @@ ChouetteIhm::Application.routes.draw do resources :clean_ups end + devise_for :users, :controllers => { + :registrations => 'users/registrations', :invitations => 'users/invitations' + } + + devise_scope :user do + authenticated :user do + root :to => 'workbenches#index', as: :authenticated_root + end + + unauthenticated :user do + target = 'devise/sessions#new' + + if Rails.application.config.chouette_authentication_settings[:type] == "cas" + target = 'devise/cas_sessions#new' + end + + root :to => target, as: :unauthenticated_root + end + end + + mount Sidekiq::Web => '/sidekiq' + + namespace :api do + namespace :v1 do + resources :workbenches, only: [:index, :show] do + resources :imports, only: [:index, :show, :create] + end + resources :access_links, only: [:index, :show] + resources :access_points, only: [:index, :show] + resources :connection_links, only: [:index, :show] + resources :companies, only: [:index, :show] + resources :group_of_lines, only: [:index, :show] + resources :netex_imports, only: :create + resources :journey_patterns, only: :show + resources :lines, only: [:index, :show] do + resources :journey_patterns, only: [:index, :show] + resources :routes, only: [:index, :show] do + resources :vehicle_journeys, only: [:index, :show] + resources :journey_patterns, only: [:index, :show] + resources :stop_areas, only: [:index, :show] + end + end + resources :networks, only: [:index, :show] + resources :routes, only: :show + resources :stop_areas, only: [:index, :show] + resources :time_tables, only: [:index, :show] + resources :vehicle_journeys, only: :show + namespace :internals do + get 'compliance_check_sets/:id/notify_parent', to: 'compliance_check_sets#notify_parent' + get 'netex_imports/:id/notify_parent', to: 'netex_imports#notify_parent' + end + end + end + + resource :organisation, :only => [:show, :edit, :update] do + resources :users + end + + resources :api_keys, :only => [:edit, :update, :new, :create, :destroy] + + resources :compliance_control_sets do + get :simple, on: :member + get :clone, on: :member + resources :compliance_controls, except: :index do + get :select_type, on: :collection + end + resources :compliance_control_blocks, :except => [:show, :index] + end + + deactivable = Proc.new do + put :deactivate, on: :member + put :activate, on: :member + end + + resources :stop_area_referentials, :only => [:show] do + post :sync, on: :member + resources :stop_areas do + put :deactivate, on: :member + put :activate, on: :member + get :autocomplete, on: :collection + end + end + + resources :line_referentials, :only => [:show, :edit, :update] do + post :sync, on: :member + resources :lines, &deactivable + resources :group_of_lines + resources :companies + resources :networks + end + + resources :calendars do + get :autocomplete, on: :collection, controller: 'autocomplete_calendars' + member do + get 'month', defaults: { format: :json } + end + end + + root :to => "dashboards#show" if Rails.env.development? || Rails.env.test? diff --git a/config/secrets.yml.docker b/config/secrets.yml.docker index 7e7808070..f9bbf5fa0 100644 --- a/config/secrets.yml.docker +++ b/config/secrets.yml.docker @@ -14,4 +14,4 @@ secret_key_base: <%= ENV.fetch 'SECRET_KEY_BASE', 'change_this_string_for_something_more_secure' %> api_endpoint: <%= ENV.fetch 'IEV_API_ENDPOINT', 'http://iev:8080/chouette_iev/' %> api_token: <%= ENV.fetch 'IEV_API_TOKEN', 'change this according to IEV configuration' %> - newrelic_licence_key: <%= ENF.fetch 'NR_LICENCE_KEY', 'will_not_work' %> + newrelic_licence_key: <%= ENV.fetch 'NR_LICENCE_KEY', 'will_not_work' %> diff --git a/config/webpack/production.js b/config/webpack/production.js index 82478b156..8e30c09ea 100644 --- a/config/webpack/production.js +++ b/config/webpack/production.js @@ -2,6 +2,11 @@ const environment = require('./environment') const webpack = require('webpack') const UglifyJsPlugin = require('uglify-js') +let plugin = new webpack.EnvironmentPlugin({ + NODE_ENV: 'production' +}) + +environment.plugins.append('EnvironmentPlugin', plugin) environment.plugins.append( 'UglifyJs', new webpack.optimize.UglifyJsPlugin({ diff --git a/db/migrate/20180129210928_create_simple_importers.rb b/db/migrate/20180129210928_create_simple_importers.rb new file mode 100644 index 000000000..c2a918900 --- /dev/null +++ b/db/migrate/20180129210928_create_simple_importers.rb @@ -0,0 +1,10 @@ +class CreateSimpleImporters < ActiveRecord::Migration + def change + create_table :simple_importers do |t| + t.string :configuration_name + t.string :filepath + t.string :status + t.json :journal + end + end +end diff --git a/db/schema.rb b/db/schema.rb index 596682ce8..f77961f8d 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -15,9 +15,10 @@ ActiveRecord::Schema.define(version: 20180202170009) do # These are extensions that must be enabled in order to support this database enable_extension "plpgsql" - enable_extension "postgis" enable_extension "hstore" + enable_extension "postgis" enable_extension "unaccent" + enable_extension "objectid" create_table "access_links", id: :bigserial, force: :cascade do |t| t.integer "access_point_id", limit: 8 @@ -119,6 +120,7 @@ ActiveRecord::Schema.define(version: 20180202170009) do t.datetime "updated_at" t.date "end_date" t.string "date_type" + t.string "mode" end add_index "clean_ups", ["referential_id"], name: "index_clean_ups_on_referential_id", using: :btree @@ -724,6 +726,13 @@ ActiveRecord::Schema.define(version: 20180202170009) do t.integer "line_id", limit: 8 end + create_table "simple_importers", id: :bigserial, force: :cascade do |t| + t.string "configuration_name" + t.string "filepath" + t.string "status" + t.json "journal" + end + create_table "stop_area_referential_memberships", id: :bigserial, force: :cascade do |t| t.integer "organisation_id", limit: 8 t.integer "stop_area_referential_id", limit: 8 diff --git a/lib/tasks/imports.rake b/lib/tasks/imports.rake index 02e32fd3d..b91ff7efb 100644 --- a/lib/tasks/imports.rake +++ b/lib/tasks/imports.rake @@ -1,3 +1,5 @@ +require 'csv' + namespace :import do desc "Notify parent imports when children finish" task notify_parent: :environment do @@ -8,4 +10,99 @@ namespace :import do task netex_abort_old: :environment do NetexImport.abort_old end + + def importer_output_to_csv importer + filepath = "./#{importer.configuration_name}_#{Time.now.strftime "%y%m%d%H%M"}_out.csv" + cols = %w(line kind event message error) + if importer.reload.journal.size > 0 + keys = importer.journal.first["row"].map(&:first) + CSV.open(filepath, "w") do |csv| + csv << cols + keys + importer.journal.each do |j| + csv << cols.map{|c| j[c]} + j["row"].map(&:last) + end + end + puts "Import Output written in #{filepath}" + end + end + + desc "import the given file with the corresponding importer" + task :import, [:configuration_name, :filepath, :referential_id] => :environment do |t, args| + importer = SimpleImporter.create configuration_name: args[:configuration_name], filepath: args[:filepath] + if args[:referential_id].present? + referential = Referential.find args[:referential_id] + importer.configure do |config| + config.add_value :referential, referential + config.context = {referential: referential} + end + end + puts "\e[33m***\e[0m Start importing" + begin + importer.import(verbose: true) + rescue Interrupt + raise + ensure + puts "\n\e[33m***\e[0m Import done, status: " + (importer.status == "success" ? "\e[32m" : "\e[31m" ) + (importer.status || "") + "\e[0m" + importer_output_to_csv importer + end + end + + desc "import the given file with the corresponding importer in the given StopAreaReferential" + task :import_in_stop_area_referential, [:referential_id, :configuration_name, :filepath] => :environment do |t, args| + referential = StopAreaReferential.find args[:referential_id] + importer = SimpleImporter.create configuration_name: args[:configuration_name], filepath: args[:filepath] + importer.configure do |config| + config.add_value :stop_area_referential, referential + config.context = {stop_area_referential: referential} + end + puts "\e[33m***\e[0m Start importing" + begin + importer.import(verbose: true) + rescue Interrupt + raise + ensure + puts "\n\e[33m***\e[0m Import done, status: " + (importer.status == "success" ? "\e[32m" : "\e[31m" ) + (importer.status || "") + "\e[0m" + importer_output_to_csv importer + end + end + + desc "import the given routes files" + task :import_routes, [:referential_id, :configuration_name, :mapping_filepath, :filepath] => :environment do |t, args| + referential = Referential.find args[:referential_id] + referential.switch + stop_area_referential = referential.stop_area_referential + importer = SimpleImporter.create configuration_name: args[:configuration_name], filepath: args[:filepath] + importer.configure do |config| + config.add_value :stop_area_referential, referential + config.context = {stop_area_referential: stop_area_referential, mapping_filepath: args[:mapping_filepath]} + end + puts "\e[33m***\e[0m Start importing" + begin + importer.import(verbose: true) + rescue Interrupt + raise + ensure + puts "\n\e[33m***\e[0m Import done, status: " + (importer.status == "success" ? "\e[32m" : "\e[31m" ) + (importer.status || "") + "\e[0m" + importer_output_to_csv importer + end + end + + desc "import the given file with the corresponding importer in the given LineReferential" + task :import_in_line_referential, [:referential_id, :configuration_name, :filepath] => :environment do |t, args| + referential = LineReferential.find args[:referential_id] + importer = SimpleImporter.create configuration_name: args[:configuration_name], filepath: args[:filepath] + importer.configure do |config| + config.add_value :line_referential, referential + config.context = {line_referential: referential} + end + puts "\e[33m***\e[0m Start importing" + begin + importer.import(verbose: true) + rescue Interrupt + raise + ensure + puts "\n\e[33m***\e[0m Import done, status: " + (importer.status == "success" ? "\e[32m" : "\e[31m" ) + (importer.status || "") + "\e[0m" + importer_output_to_csv importer + end + end end diff --git a/spec/controllers/referentials_controller_spec.rb b/spec/controllers/referentials_controller_spec.rb index f97480600..5e0b1e505 100644 --- a/spec/controllers/referentials_controller_spec.rb +++ b/spec/controllers/referentials_controller_spec.rb @@ -15,8 +15,7 @@ describe ReferentialsController, :type => :controller do end context "user's organisation doesn't match referential's organisation" do - pending "hotfix opens all unknow actions need to close the uneeded later" do - #it 'raises a ActiveRecord::RecordNotFound' do + it 'raises a ActiveRecord::RecordNotFound' do expect { put :archive, id: other_referential.id }.to raise_error(ActiveRecord::RecordNotFound) end end @@ -26,7 +25,7 @@ describe ReferentialsController, :type => :controller do it 'gets compliance control set for current organisation' do compliance_control_set = create(:compliance_control_set, organisation: @user.organisation) create(:compliance_control_set) - get :select_compliance_control_set, referential_id: referential.id + get :select_compliance_control_set, id: referential.id expect(assigns[:compliance_control_sets]).to eq([compliance_control_set]) end end @@ -43,16 +42,51 @@ describe ReferentialsController, :type => :controller do end end + describe "GET #new" do + context "when duplicating" do + let(:workbench){ create :workbench} + let(:request){ + get :new, + workbench_id: workbench.id, + from: referential.id + } + + it "duplicates the given referential" do + request + new_referential = assigns(:referential) + expect(new_referential.line_referential).to eq referential.line_referential + expect(new_referential.stop_area_referential).to eq referential.stop_area_referential + expect(new_referential.objectid_format).to eq referential.objectid_format + expect(new_referential.prefix).to eq referential.prefix + expect(new_referential.slug).to eq "#{referential.slug}_clone" + expect(new_referential.workbench).to eq workbench + end + end + end + describe "POST #create" do + let(:workbench){ create :workbench} context "when duplicating" do - it "displays a flash message", pending: 'requires more params to create a valid Referential' do + let(:request){ post :create, - from: referential.id, - current_workbench_id: referential.workbench_id, - referential: { - name: 'Duplicated' - } + workbench_id: workbench.id, + referential: { + name: 'Duplicated', + created_from_id: referential.id, + stop_area_referential: referential.stop_area_referential, + line_referential: referential.line_referential, + objectid_format: referential.objectid_format, + workbench_id: referential.workbench_id + } + } + + it "creates the new referential" do + expect{request}.to change{Referential.count}.by 1 + expect(Referential.last.name).to eq "Duplicated" + end + it "displays a flash message" do + request expect(controller).to set_flash[:notice].to( I18n.t('notice.referentials.duplicate') ) diff --git a/spec/decorators/referential_decorator_spec.rb b/spec/decorators/referential_decorator_spec.rb index efc438132..1224aaf75 100644 --- a/spec/decorators/referential_decorator_spec.rb +++ b/spec/decorators/referential_decorator_spec.rb @@ -1,7 +1,8 @@ RSpec.describe ReferentialDecorator, type: [:helper, :decorator] do include Support::DecoratorHelpers - let( :object ){ build_stubbed :referential } + let( :workbench ){ build_stubbed :workbench } + let( :object ){ build_stubbed :referential, workbench: workbench } let( :referential ){ object } let( :user ){ build_stubbed :user } @@ -35,7 +36,7 @@ RSpec.describe ReferentialDecorator, type: [:helper, :decorator] do expect_action_link_hrefs.to eq([ [object], referential_time_tables_path(object), - new_referential_path(from: object) + new_workbench_referential_path(referential.workbench, from: object.id) ]) end end @@ -50,8 +51,8 @@ RSpec.describe ReferentialDecorator, type: [:helper, :decorator] do [object], [:edit, object], referential_time_tables_path(object), - new_referential_path(from: object), - referential_select_compliance_control_set_path(object), + new_workbench_referential_path(referential.workbench, from: object.id), + select_compliance_control_set_referential_path(object), archive_referential_path(object), referential_path(object) ]) @@ -65,8 +66,8 @@ RSpec.describe ReferentialDecorator, type: [:helper, :decorator] do expect_action_link_hrefs(action).to eq([ [:edit, object], referential_time_tables_path(object), - new_referential_path(from: object), - referential_select_compliance_control_set_path(object), + new_workbench_referential_path(referential.workbench, from: object.id), + select_compliance_control_set_referential_path(object), archive_referential_path(object), "#", referential_path(object) @@ -91,7 +92,7 @@ RSpec.describe ReferentialDecorator, type: [:helper, :decorator] do expect_action_link_hrefs.to eq([ [object], referential_time_tables_path(object), - new_referential_path(from: object) + new_workbench_referential_path(referential.workbench, from: object.id) ]) end end diff --git a/spec/features/referentials_spec.rb b/spec/features/referentials_spec.rb index 9af0ed32e..d4890fda4 100644 --- a/spec/features/referentials_spec.rb +++ b/spec/features/referentials_spec.rb @@ -55,7 +55,7 @@ describe "Referentials", :type => :feature do context 'user has the permission to create referentials' do it 'shows the clone link for referetnial' do - expect(page).to have_link(I18n.t('actions.clone'), href: new_referential_path(from: referential.id)) + expect(page).to have_link(I18n.t('actions.clone'), href: new_workbench_referential_path(referential.workbench, from: referential.id)) end end @@ -63,7 +63,7 @@ describe "Referentials", :type => :feature do it 'does not show the clone link for referetnial' do @user.update_attribute(:permissions, []) visit referential_path(referential) - expect(page).not_to have_link(I18n.t('actions.clone'), href: new_referential_path(from: referential.id)) + expect(page).not_to have_link(I18n.t('actions.clone'), href: new_workbench_referential_path(referential.workbench, from: referential.id)) end end @@ -108,8 +108,9 @@ describe "Referentials", :type => :feature do end describe "create" do + let(:workbench){ @user.organisation.workbenches.last } it "should" do - visit new_referential_path + visit new_workbench_referential_path(workbench) fill_in "Nom", :with => "Test" click_button "Valider" @@ -132,7 +133,7 @@ describe "Referentials", :type => :feature do context "when user is from the same organisation" do xit "should" do - visit new_referential_path(from: referential.id, current_workbench_id: @user.organisation.workbenches.first.id) + visit new_workbench_referential_path(referential.workbench, from: referential.id, current_workbench_id: @user.organisation.workbenches.first.id) select "2018", :from => "referential_metadatas_attributes_0_periods_attributes_0_begin_1i" @@ -187,7 +188,8 @@ describe "Referentials", :type => :feature do end describe "destroy" do - let(:referential) { create(:referential, :organisation => @user.organisation) } + let(:workbench){ @user.organisation.workbenches.last } + let(:referential) { create(:referential, :organisation => @user.organisation, workbench: workbench) } it "should remove referential" do visit referential_path(referential) diff --git a/spec/features/workbenches/workbenches_permissions_spec.rb b/spec/features/workbenches/workbenches_permissions_spec.rb index d58293538..1c073a4c5 100644 --- a/spec/features/workbenches/workbenches_permissions_spec.rb +++ b/spec/features/workbenches/workbenches_permissions_spec.rb @@ -22,7 +22,7 @@ describe 'Workbenches', type: :feature do let( :permission ){ true } it 'shows the corresponding button' do - expected_href = new_referential_path(workbench_id: workbench) + expected_href = new_workbench_referential_path(workbench) expect( page ).to have_link('Créer', href: expected_href) end end diff --git a/spec/features/workbenches/workbenches_show_spec.rb b/spec/features/workbenches/workbenches_show_spec.rb index 7be813b94..405fdce82 100644 --- a/spec/features/workbenches/workbenches_show_spec.rb +++ b/spec/features/workbenches/workbenches_show_spec.rb @@ -232,7 +232,7 @@ RSpec.describe 'Workbenches', type: :feature do context 'user has the permission to create referentials' do it 'shows the link for a new referetnial' do - expect(page).to have_link(I18n.t('actions.add'), href: new_referential_path(workbench_id: workbench.id)) + expect(page).to have_link(I18n.t('actions.add'), href: new_workbench_referential_path(workbench)) end end @@ -240,7 +240,7 @@ RSpec.describe 'Workbenches', type: :feature do it 'does not show the clone link for referential' do @user.update_attribute(:permissions, []) visit referential_path(referential) - expect(page).not_to have_link(I18n.t('actions.add'), href: new_referential_path(workbench_id: workbench.id)) + expect(page).not_to have_link(I18n.t('actions.add'), href: new_workbench_referential_path(workbench)) end end end diff --git a/spec/fixtures/simple_importer/lines_mapping.csv b/spec/fixtures/simple_importer/lines_mapping.csv new file mode 100644 index 000000000..b26d0ab59 --- /dev/null +++ b/spec/fixtures/simple_importer/lines_mapping.csv @@ -0,0 +1,11 @@ +id;timetable_route_id;route_name;stop_sequence;stop_distance;station_code;station_name;border;Ligne Chouette;Transporteur
+3354;1136;Paris centre - Bercy > Lille > Londres;1;0;XPB;Paris City Center - Bercy;f;Paris <> Londres - OUIBUS;OUIBUS
+3355;1136;Paris centre - Bercy > Lille > Londres;2;232;XDB;Lille;f;Paris <> Londres - OUIBUS;OUIBUS
+3749;1136;Paris centre - Bercy > Lille > Londres;3;350;COF;Coquelles - France;t;Paris <> Londres - OUIBUS;OUIBUS
+4772;1136;Paris centre - Bercy > Lille > Londres;4;350;COU;Coquelles - UK;t;Paris <> Londres - OUIBUS;OUIBUS
+3357;1136;Paris centre - Bercy > Lille > Londres;5;527;ZEP;London;f;Paris <> Londres - OUIBUS;OUIBUS
+3358;1137;Londres > Lille > Paris centre - Bercy;1;0;ZEP;London;f;Paris <> Londres - OUIBUS;OUIBUS
+3559;1137;Londres > Lille > Paris centre - Bercy;2;177;COU;Coquelles - UK;t;Paris <> Londres - OUIBUS;OUIBUS
+3743;1137;Londres > Lille > Paris centre - Bercy;3;177;COF;Coquelles - France;t;Paris <> Londres - OUIBUS;OUIBUS
+3360;1137;Londres > Lille > Paris centre - Bercy;4;295;XDB;Lille;f;Paris <> Londres - OUIBUS;OUIBUS
+3361;1137;Londres > Lille > Paris centre - Bercy;5;527;XPB;Paris City Center - Bercy;f;Paris <> Londres - OUIBUS;OUIBUS
diff --git a/spec/fixtures/simple_importer/stop_area.csv b/spec/fixtures/simple_importer/stop_area.csv new file mode 100644 index 000000000..9361d022b --- /dev/null +++ b/spec/fixtures/simple_importer/stop_area.csv @@ -0,0 +1,2 @@ +name;lat;long;type;street_name +Nom du Stop;45.00;12;ZDEP;99 rue des Poissonieres diff --git a/spec/fixtures/simple_importer/stop_area_full.csv b/spec/fixtures/simple_importer/stop_area_full.csv new file mode 100644 index 000000000..250caab30 --- /dev/null +++ b/spec/fixtures/simple_importer/stop_area_full.csv @@ -0,0 +1,3 @@ +"id";"station_code";"uic_code";"country_code";"province";"district";"county";"station_name";"inactive";"change_timestamp";"longitude";"latitude";"parent_station_code";"additional_info";"external_reference";"timezone";"address";"postal_code";"city" +5669;"PAR";"PAR";"FRA";"";"";"";"Paris - All stations";f;"2017-07-17 11:56:53.138";2.35222190000002;48.856614;"";"";"{""Région"":""Ile-de-France""}";"Europe/Paris";"";"";"" +5748;"XED";"XED";"FRA";"";"";"";"Paris MLV";t;"2017-05-29 11:24:34.575";2.783409;48.870569;"PAR";"";"{""Région"":""Ile-de-France""}";"Europe/Paris";"";"";"" diff --git a/spec/fixtures/simple_importer/stop_area_full_reverse.csv b/spec/fixtures/simple_importer/stop_area_full_reverse.csv new file mode 100644 index 000000000..9ea15f6cc --- /dev/null +++ b/spec/fixtures/simple_importer/stop_area_full_reverse.csv @@ -0,0 +1,3 @@ +"id";"station_code";"uic_code";"country_code";"province";"district";"county";"station_name";"inactive";"change_timestamp";"longitude";"latitude";"parent_station_code";"additional_info";"external_reference";"timezone";"address";"postal_code";"city" +5748;"XED";"XED";"FRA";"";"";"";"Paris MLV";t;"2017-05-29 11:24:34.575";2.783409;48.870569;"PAR";"";"{""Région"":""Ile-de-France""}";"Europe/Paris";"";"";"" +5669;"PAR";"PAR";"FRA";"";"";"";"Paris - All stations";f;"2017-07-17 11:56:53.138";2.35222190000002;48.856614;"";"";"{""Région"":""Ile-de-France""}";"Europe/Paris";"";"";"" diff --git a/spec/fixtures/simple_importer/stop_area_incomplete.csv b/spec/fixtures/simple_importer/stop_area_incomplete.csv new file mode 100644 index 000000000..9b11aa02c --- /dev/null +++ b/spec/fixtures/simple_importer/stop_area_incomplete.csv @@ -0,0 +1,3 @@ +name;lat;long;type;street_name +Foo;45.00;12;ZDEP +;45.00;12;ZDEP diff --git a/spec/fixtures/simple_importer/stop_area_missing_street_name.csv b/spec/fixtures/simple_importer/stop_area_missing_street_name.csv new file mode 100644 index 000000000..aa845c3f5 --- /dev/null +++ b/spec/fixtures/simple_importer/stop_area_missing_street_name.csv @@ -0,0 +1,2 @@ +name;lat;long;type;foo +Nom du Stop;45.00;12;ZDEP;blabla diff --git a/spec/fixtures/simple_importer/stop_points_full.csv b/spec/fixtures/simple_importer/stop_points_full.csv new file mode 100644 index 000000000..9c5b3c1b7 --- /dev/null +++ b/spec/fixtures/simple_importer/stop_points_full.csv @@ -0,0 +1,11 @@ +"id";"timetable_route_id";"route_name";"stop_sequence";"stop_distance";"station_code";"station_name";"border"
+3354;1136;"Paris centre - Bercy > Lille > Londres";1;0;"XPB";"Paris City Center - Bercy";f
+3355;1136;"Paris centre - Bercy > Lille > Londres";2;232;"XDB";"Lille";f
+3749;1136;"Paris centre - Bercy > Lille > Londres";3;350;"COF";"Coquelles - France";t
+4772;1136;"Paris centre - Bercy > Lille > Londres";4;350;"COU";"Coquelles - UK";t
+3357;1136;"Paris centre - Bercy > Lille > Londres";5;527;"ZEP";"London";f
+3358;1137;"Londres > Lille > Paris centre - Bercy";1;0;"ZEP";"London";f
+3559;1137;"Londres > Lille > Paris centre - Bercy";2;177;"COU";"Coquelles - UK";t
+3743;1137;"Londres > Lille > Paris centre - Bercy";3;177;"COF";"Coquelles - France";t
+3360;1137;"Londres > Lille > Paris centre - Bercy";4;295;"XDB";"Lille";f
+3361;1137;"Londres > Lille > Paris centre - Bercy";5;527;"XPB";"Paris City Center - Bercy";f
diff --git a/spec/helpers/table_builder_helper_spec.rb b/spec/helpers/table_builder_helper_spec.rb index 5bddbb16f..478875118 100644 --- a/spec/helpers/table_builder_helper_spec.rb +++ b/spec/helpers/table_builder_helper_spec.rb @@ -15,8 +15,9 @@ describe TableBuilderHelper, type: :helper do describe "#table_builder_2" do it "builds a table" do - referential = build_stubbed(:workbench_referential) + referential = create(:workbench_referential) workbench = referential.workbench + referential.organisation.workbenches << workbench user_context = UserContext.new( build_stubbed( @@ -30,7 +31,8 @@ describe TableBuilderHelper, type: :helper do ), referential: referential ) - allow(helper).to receive(:current_user).and_return(user_context) + allow(helper).to receive(:pundit_user).and_return(user_context) + allow(helper).to receive(:current_user).and_return(user_context.user) referentials = [referential] @@ -90,7 +92,7 @@ describe TableBuilderHelper, type: :helper do </ul> <ul class="other"> <li class=""><a href="/referentials/#{referential.id}/time_tables">Calendriers</a></li> - <li class=""><a href="/referentials/new?from=#{referential.id}">Dupliquer</a></li> + <li class=""><a href="/workbenches/#{workbench.id}/referentials/new?from=#{referential.id}">Dupliquer</a></li> <li class=""><a href="/referentials/#{referential.id}/select_compliance_control_set">Valider</a></li> <li class=""><a rel="nofollow" data-method="put" href="/referentials/#{referential.id}/archive">Conserver</a></li> </ul> @@ -193,7 +195,8 @@ describe TableBuilderHelper, type: :helper do ), referential: referential ) - allow(helper).to receive(:current_user).and_return(user_context) + allow(helper).to receive(:pundit_user).and_return(user_context) + allow(helper).to receive(:current_user).and_return(user_context.user) allow(helper).to receive(:current_referential) .and_return(referential) @@ -307,7 +310,8 @@ describe TableBuilderHelper, type: :helper do ), referential: referential ) - allow(helper).to receive(:current_user).and_return(user_context) + allow(helper).to receive(:pundit_user).and_return(user_context) + allow(helper).to receive(:current_user).and_return(user_context.user) allow(helper).to receive(:current_referential) .and_return(referential) @@ -398,8 +402,8 @@ describe TableBuilderHelper, type: :helper do end context "on a single row" do - let(:referential){ build_stubbed :referential } - let(:other_referential){ build_stubbed :referential } + let(:referential){ build_stubbed :workbench_referential } + let(:other_referential){ build_stubbed :workbench_referential } let(:user_context){ UserContext.new( build_stubbed( @@ -432,7 +436,9 @@ describe TableBuilderHelper, type: :helper do let(:items){ [item, other_item] } before(:each){ - allow(helper).to receive(:current_user).and_return(user_context) + allow(helper).to receive(:pundit_user).and_return(user_context) + allow(helper).to receive(:current_user).and_return(user_context.user) + allow(helper).to receive(:mutual_workbench).and_return(referential.workbench) } context "with all rows non-selectable" do diff --git a/spec/models/chouette/journey_pattern_spec.rb b/spec/models/chouette/journey_pattern_spec.rb index 19a74a0e7..7c767e4d1 100644 --- a/spec/models/chouette/journey_pattern_spec.rb +++ b/spec/models/chouette/journey_pattern_spec.rb @@ -71,6 +71,30 @@ describe Chouette::JourneyPattern, :type => :model do end end + describe "set_distances" do + let(:journey_pattern) { create :journey_pattern } + let(:distances){ [] } + it "should raise an error" do + expect{journey_pattern.set_distances(distances)}.to raise_error + end + + context "with consistent data" do + let(:distances){ [0, 100, "200", 500, 1000] } + + it "should set costs" do + expect{journey_pattern.set_distances(distances)}.to_not raise_error + start, stop = journey_pattern.stop_points[0..1] + expect(journey_pattern.costs_between(start, stop)[:distance]).to eq 100 + start, stop = journey_pattern.stop_points[1..2] + expect(journey_pattern.costs_between(start, stop)[:distance]).to eq 100 + start, stop = journey_pattern.stop_points[2..3] + expect(journey_pattern.costs_between(start, stop)[:distance]).to eq 300 + start, stop = journey_pattern.stop_points[3..4] + expect(journey_pattern.costs_between(start, stop)[:distance]).to eq 500 + end + end + end + describe "state_update" do def journey_pattern_to_state jp jp.attributes.slice('name', 'published_name', 'registration_number').tap do |item| diff --git a/spec/models/chouette/vehicle_journey_at_stop_spec.rb b/spec/models/chouette/vehicle_journey_at_stop_spec.rb index a97559a0c..f79d19c88 100644 --- a/spec/models/chouette/vehicle_journey_at_stop_spec.rb +++ b/spec/models/chouette/vehicle_journey_at_stop_spec.rb @@ -10,7 +10,7 @@ RSpec.describe Chouette::VehicleJourneyAtStop, type: :model do context '#checksum_attributes' do it 'should return attributes' do - expected = [at_stop.departure_time.to_s(:time), at_stop.arrival_time.to_s(:time)] + expected = [at_stop.departure_time.utc.to_s(:time), at_stop.arrival_time.utc.to_s(:time)] expected << at_stop.departure_day_offset.to_s expected << at_stop.arrival_day_offset.to_s expect(at_stop.checksum_attributes).to include(*expected) diff --git a/spec/models/simple_importer_spec.rb b/spec/models/simple_importer_spec.rb new file mode 100644 index 000000000..60d7b7882 --- /dev/null +++ b/spec/models/simple_importer_spec.rb @@ -0,0 +1,394 @@ +RSpec.describe SimpleImporter do + describe "#define" do + context "with an incomplete configuration" do + + it "should raise an error" do + expect do + SimpleImporter.define :foo + end.to raise_error + end + end + context "with a complete configuration" do + before do + SimpleImporter.define :foo do |config| + config.model = "example" + end + end + + it "should define an importer" do + expect{SimpleImporter.find_configuration(:foo)}.to_not raise_error + expect{SimpleImporter.new(configuration_name: :foo, filepath: "")}.to_not raise_error + expect{SimpleImporter.find_configuration(:bar)}.to raise_error + expect{SimpleImporter.new(configuration_name: :bar, filepath: "")}.to raise_error + expect{SimpleImporter.create(configuration_name: :foo, filepath: "")}.to change{SimpleImporter.count}.by 1 + end + end + end + + describe "#import" do + let(:importer){ importer = SimpleImporter.new(configuration_name: :test, filepath: filepath) } + let(:filepath){ fixtures_path 'simple_importer', filename } + let(:filename){ "stop_area.csv" } + let(:stop_area_referential){ create(:stop_area_referential, objectid_format: :stif_netex) } + + before(:each) do + SimpleImporter.define :test do |config| + config.model = Chouette::StopArea + config.separator = ";" + config.key = "name" + config.add_column :name + config.add_column :lat, attribute: :latitude + config.add_column :lat, attribute: :longitude, value: ->(raw){ raw.to_f + 1 } + config.add_column :type, attribute: :area_type, value: ->(raw){ raw&.downcase } + config.add_column :street_name + config.add_column :stop_area_referential, value: stop_area_referential + config.add_value :kind, :commercial + end + end + + it "should import the given file" do + expect{importer.import verbose: false}.to change{Chouette::StopArea.count}.by 1 + expect(importer.status).to eq "success" + stop = Chouette::StopArea.last + expect(stop.name).to eq "Nom du Stop" + expect(stop.latitude).to eq 45.00 + expect(stop.longitude).to eq 46.00 + expect(stop.area_type).to eq "zdep" + expect(importer.reload.journal.last["event"]).to eq("creation") + end + + context "when overriding configuration" do + before(:each){ + importer.configure do |config| + config.add_value :latitude, 88 + end + } + + it "should import the given file and not mess with the global configuration" do + expect{importer.import}.to change{Chouette::StopArea.count}.by 1 + expect(importer.status).to eq "success" + stop = Chouette::StopArea.last + expect(stop.latitude).to eq 88 + importer = SimpleImporter.new(configuration_name: :test, filepath: filepath) + expect{importer.import}.to change{Chouette::StopArea.count}.by 0 + expect(stop.reload.latitude).to eq 45 + end + end + + context "with an already existing record" do + let(:filename){ "stop_area.csv" } + before(:each){ + create :stop_area, name: "Nom du Stop" + } + it "should only update the record" do + expect{importer.import}.to change{Chouette::StopArea.count}.by 0 + expect(importer.status).to eq "success" + stop = Chouette::StopArea.last + expect(stop.name).to eq "Nom du Stop" + expect(stop.latitude).to eq 45.00 + expect(stop.longitude).to eq 46.00 + expect(stop.area_type).to eq "zdep" + expect(importer.reload.journal.last["event"]).to eq("update") + end + + context "in another scope" do + before(:each) do + ref = create(:stop_area_referential) + importer.configure do |config| + config.context = { stop_area_referential: ref } + config.scope = ->{ context[:stop_area_referential].stop_areas } + end + end + + it "should create the record" do + expect{importer.import verbose: false}.to change{Chouette::StopArea.count}.by 1 + expect(importer.status).to eq "success" + end + end + end + + context "with a missing column" do + let(:filename){ "stop_area_missing_street_name.csv" } + it "should set an error message" do + expect{importer.import(verbose: false)}.to_not raise_error + expect(importer.status).to eq "success_with_warnings" + expect(importer.reload.journal.first["event"]).to eq("column_not_found") + end + end + + context "with an incomplete dataset" do + let(:filename){ "stop_area_incomplete.csv" } + it "should fail" do + expect{importer.import}.to_not raise_error + expect(importer.status).to eq "failed" + expect(importer.reload.journal.last["message"]).to eq({"name" => ["doit être rempli(e)"]}) + end + + it "should be transactional" do + expect{importer.import}.to_not change {Chouette::StopArea.count} + end + end + + context "with a wrong filepath" do + let(:filename){ "not_found.csv" } + it "should fail" do + expect{importer.import}.to_not raise_error + expect(importer.status).to eq "failed" + expect(importer.reload.journal.first["message"]).to eq "File not found: #{importer.filepath}" + end + end + + context "with a custom behaviour" do + let!(:present){ create :stop_area, name: "Nom du Stop", stop_area_referential: stop_area_referential } + let!(:missing){ create :stop_area, name: "Another", stop_area_referential: stop_area_referential } + before(:each){ + importer.configure do |config| + config.before do |importer| + stop_area_referential.stop_areas.each &:deactivate! + end + + config.before(:each_save) do |importer, stop_area| + stop_area.activate! + end + end + } + + it "should disable all missing areas" do + expect{importer.import}.to change{Chouette::StopArea.count}.by 0 + expect(present.reload.activated?).to be_truthy + expect(missing.reload.activated?).to be_falsy + end + + context "with an error" do + let(:filename){ "stop_area_incomplete.csv" } + it "should do nothing" do + expect{importer.import}.to_not change {Chouette::StopArea.count} + expect(present.reload.activated?).to be_truthy + expect(missing.reload.activated?).to be_truthy + end + end + end + + context "with a full file" do + let(:filename){ "stop_area_full.csv" } + let!(:missing){ create :stop_area, name: "Another", stop_area_referential: stop_area_referential } + + before(:each) do + SimpleImporter.define :test do |config| + config.model = Chouette::StopArea + config.separator = ";" + config.key = "station_code" + config.add_column :station_code, attribute: :registration_number + config.add_column :country_code + config.add_column :station_name, attribute: :name + config.add_column :inactive, attribute: :deleted_at, value: ->(raw){ raw == "t" ? Time.now : nil } + config.add_column :change_timestamp, attribute: :updated_at + config.add_column :longitude + config.add_column :latitude + config.add_column :parent_station_code, attribute: :parent, value: ->(raw){ raw.present? && resolve(:station_code, raw){|value| Chouette::StopArea.find_by(registration_number: value) } } + config.add_column :parent_station_code, attribute: :area_type, value: ->(raw){ raw.present? ? "zdep" : "gdl" } + config.add_column :timezone, attribute: :time_zone + config.add_column :address, attribute: :street_name + config.add_column :postal_code, attribute: :zip_code + config.add_column :city, attribute: :city_name + config.add_value :stop_area_referential_id, stop_area_referential.id + config.add_value :long_lat_type, "WGS84" + config.add_value :kind, :commercial + config.before do |importer| + stop_area_referential.stop_areas.each &:deactivate! + end + + config.before(:each_save) do |importer, stop_area| + stop_area.activate + end + end + end + + it "should import the given file" do + expect{importer.import(verbose: false)}.to change{Chouette::StopArea.count}.by 2 + expect(importer.status).to eq "success" + first = Chouette::StopArea.find_by registration_number: "PAR" + last = Chouette::StopArea.find_by registration_number: "XED" + + expect(last.parent).to eq first + expect(first.area_type).to eq "gdl" + expect(last.area_type).to eq "zdep" + expect(first.long_lat_type).to eq "WGS84" + expect(first.activated?).to be_truthy + expect(last.activated?).to be_truthy + expect(missing.reload.activated?).to be_falsy + end + + context "with a relation in reverse order" do + let(:filename){ "stop_area_full_reverse.csv" } + + it "should import the given file" do + expect{importer.import}.to change{Chouette::StopArea.count}.by 2 + expect(importer.status).to eq "success" + first = Chouette::StopArea.find_by registration_number: "XED" + last = Chouette::StopArea.find_by registration_number: "PAR" + expect(first.parent).to eq last + end + end + end + + context "with a specific importer" do + let(:filename){ "stop_points_full.csv" } + + before(:each) do + create :line, name: "Paris <> Londres - OUIBUS" + + SimpleImporter.define :test do |config| + config.model = Chouette::Route + config.separator = ";" + config.context = {stop_area_referential: stop_area_referential} + + config.before do |importer| + mapping = {} + path = Rails.root + "spec/fixtures/simple_importer/lines_mapping.csv" + CSV.foreach(path, importer.configuration.csv_options) do |row| + if row["Ligne Chouette"].present? + mapping[row["timetable_route_id"]] ||= Chouette::Line.find_by(name: importer.encode_string(row["Ligne Chouette"])) + end + end + importer.context[:mapping] = mapping + end + + config.custom_handler do |row| + line = nil + fail_with_error "MISSING LINE FOR ROUTE: #{encode_string row["route_name"]}" do + line = context[:mapping][row["timetable_route_id"]] + raise unless line + end + @current_record = Chouette::Route.find_or_initialize_by number: row["timetable_route_id"] + @current_record.name = encode_string row["route_name"] + @current_record.published_name = encode_string row["route_name"] + + @current_record.line = line + if @prev_route != @current_record + if @prev_route && @prev_route.valid? + journey_pattern = @prev_route.full_journey_pattern + fail_with_error "WRONG DISTANCES FOR ROUTE #{@prev_route.name} (#{@prev_route.number}): #{@distances.count} distances for #{@prev_route.stop_points.count} stops" do + journey_pattern.stop_points = @prev_route.stop_points + journey_pattern.set_distances @distances + end + fail_with_error ->(){ journey_pattern.errors.messages } do + journey_pattern.save! + end + end + @distances = [] + end + @distances.push row["stop_distance"] + position = row["stop_sequence"].to_i - 1 + + stop_area = context[:stop_area_referential].stop_areas.where(registration_number: row["station_code"]).last + unless stop_area + stop_area = Chouette::StopArea.new registration_number: row["station_code"] + stop_area.name = row["station_name"] + stop_area.kind = row["border"] == "f" ? :commercial : :non_commercial + stop_area.area_type = row["border"] == "f" ? :zdep : :border + stop_area.stop_area_referential = context[:stop_area_referential] + fail_with_error ->{p stop_area; "UNABLE TO CREATE STOP_AREA: #{stop_area.errors.messages}" }, abort_row: true do + stop_area.save! + end + end + stop_point = @current_record.stop_points.find_by(stop_area_id: stop_area.id) + if stop_point + stop_point.set_list_position position + else + stop_point = @current_record.stop_points.build(stop_area_id: stop_area.id, position: position) + stop_point.for_boarding = :normal + stop_point.for_alighting = :normal + end + + @prev_route = @current_record + end + + config.after(:each_save) do |importer, route| + opposite_route_name = route.name.split(" > ").reverse.join(' > ') + opposite_route = Chouette::Route.where(name: opposite_route_name).where('id < ?', route.id).last + if opposite_route && opposite_route.line == route.line + route.update_attribute :wayback, :inbound + opposite_route.update_attribute :wayback, :outbound + route.update_attribute :opposite_route_id, opposite_route.id + opposite_route.update_attribute :opposite_route_id, route.id + end + end + + config.after do |importer| + prev_route = importer.instance_variable_get "@prev_route" + if prev_route && prev_route.valid? + journey_pattern = prev_route.full_journey_pattern + importer.fail_with_error "WRONG DISTANCES FOR ROUTE #{prev_route.name}: #{importer.instance_variable_get("@distances").count} distances for #{prev_route.stop_points.count} stops" do + journey_pattern.set_distances importer.instance_variable_get("@distances") + journey_pattern.stop_points = prev_route.stop_points + end + importer.fail_with_error ->(){ journey_pattern.errors.messages } do + journey_pattern.save! + end + end + end + end + end + + it "should import the given file" do + routes_count = Chouette::Route.count + journey_pattern_count = Chouette::JourneyPattern.count + stop_areas_count = Chouette::StopArea.count + + expect{importer.import(verbose: false)}.to change{Chouette::StopPoint.count}.by 10 + expect(importer.status).to eq "success" + expect(Chouette::Route.count).to eq routes_count + 2 + expect(Chouette::JourneyPattern.count).to eq journey_pattern_count + 2 + expect(Chouette::StopArea.count).to eq stop_areas_count + 5 + route = Chouette::Route.find_by number: 1136 + expect(route.stop_areas.count).to eq 5 + expect(route.opposite_route).to eq Chouette::Route.find_by(number: 1137) + journey_pattern = route.full_journey_pattern + expect(journey_pattern.stop_areas.count).to eq 5 + start, stop = journey_pattern.stop_points[0..1] + expect(journey_pattern.costs_between(start, stop)[:distance]).to eq 232 + start, stop = journey_pattern.stop_points[1..2] + expect(journey_pattern.costs_between(start, stop)[:distance]).to eq 118 + start, stop = journey_pattern.stop_points[2..3] + expect(journey_pattern.costs_between(start, stop)[:distance]).to eq 0 + start, stop = journey_pattern.stop_points[3..4] + expect(journey_pattern.costs_between(start, stop)[:distance]).to eq 177 + + route = Chouette::Route.find_by number: 1137 + expect(route.opposite_route).to eq Chouette::Route.find_by(number: 1136) + expect(route.stop_areas.count).to eq 5 + journey_pattern = route.full_journey_pattern + expect(journey_pattern.stop_areas.count).to eq 5 + start, stop = journey_pattern.stop_points[0..1] + expect(journey_pattern.costs_between(start, stop)[:distance]).to eq 177 + start, stop = journey_pattern.stop_points[1..2] + expect(journey_pattern.costs_between(start, stop)[:distance]).to eq 0 + start, stop = journey_pattern.stop_points[2..3] + expect(journey_pattern.costs_between(start, stop)[:distance]).to eq 118 + start, stop = journey_pattern.stop_points[3..4] + expect(journey_pattern.costs_between(start, stop)[:distance]).to eq 232 + + stop_area = Chouette::StopArea.where(registration_number: "XPB").last + expect(stop_area.kind).to eq :commercial + expect(stop_area.area_type).to eq :zdep + + stop_area = Chouette::StopArea.where(registration_number: "XDB").last + expect(stop_area.kind).to eq :commercial + expect(stop_area.area_type).to eq :zdep + + stop_area = Chouette::StopArea.where(registration_number: "COF").last + expect(stop_area.kind).to eq :non_commercial + expect(stop_area.area_type).to eq :border + + stop_area = Chouette::StopArea.where(registration_number: "COU").last + expect(stop_area.kind).to eq :non_commercial + expect(stop_area.area_type).to eq :border + + stop_area = Chouette::StopArea.where(registration_number: "ZEP").last + expect(stop_area.kind).to eq :commercial + expect(stop_area.area_type).to eq :zdep + end + end + end +end diff --git a/spec/support/decorator_helpers.rb b/spec/support/decorator_helpers.rb index b2c41e842..544604f61 100644 --- a/spec/support/decorator_helpers.rb +++ b/spec/support/decorator_helpers.rb @@ -8,6 +8,7 @@ module Support let( :features ){ [] } let( :filtered_action_links){} before do + allow(subject.h).to receive(:duplicate_workbench_referential_path).and_return new_workbench_referential_path(referential.workbench, from: referential.id) allow_any_instance_of(Draper::HelperProxy).to receive(:policy).and_return policy allow_any_instance_of(AF83::Decorator::Link).to receive(:check_feature){|f| features.include?(f) diff --git a/spec/views/referentials/show.html.erb_spec.rb b/spec/views/referentials/show.html.erb_spec.rb index ea3bc1fe1..a7f37d180 100644 --- a/spec/views/referentials/show.html.erb_spec.rb +++ b/spec/views/referentials/show.html.erb_spec.rb @@ -1,73 +1,48 @@ require 'spec_helper' describe "referentials/show", type: :view do + let!(:referential) do - referential = create(:referential, organisation: organisation) + referential = create(:workbench_referential) assign :referential, referential.decorate(context: { current_organisation: referential.organisation }) end + let(:organisation){ referential.try(:organisation) } let(:permissions){ [] } let(:current_organisation) { organisation } - let(:current_offer_workbench) { create :workbench, organisation: organisation} - let(:current_workgroup) { current_offer_workbench.workgroup } let(:readonly){ false } before :each do assign :reflines, [] - allow(view).to receive(:current_offer_workbench).and_return(current_offer_workbench) allow(view).to receive(:current_organisation).and_return(current_organisation) - allow(view).to receive(:current_workgroup).and_return(current_workgroup) allow(view).to receive(:current_user).and_return(current_user) - allow(view).to receive(:resource).and_return(referential) allow(view).to receive(:has_feature?).and_return(true) allow(view).to receive(:user_signed_in?).and_return true + allow(view).to receive(:mutual_workbench).and_return referential.workbench controller.request.path_parameters[:id] = referential.id allow(view).to receive(:params).and_return({action: :show}) allow(referential).to receive(:referential_read_only?){ readonly } + render template: "referentials/show", layout: "layouts/application" end - describe "action links" do - set_invariant "referential.object.full_name", "referential_full_name" - set_invariant "referential.object.updated_at", "01/01/2000 00:00".to_time - set_invariant "referential.object.id", "99" - - before(:each){ - render template: "referentials/show", layout: "layouts/application" - } - context "with a readonly referential" do - let(:readonly){ true } - it { should match_actions_links_snapshot "referentials/show_readonly" } - - %w(create destroy update).each do |p| - with_permission "referentials.#{p}" do - it { should match_actions_links_snapshot "referentials/show_readonly_#{p}" } - end - end - end - - context "with a non-readonly referential" do - it { should match_actions_links_snapshot "referentials/show" } + it "should not present edit button" do + expect(rendered).to_not have_selector("a[href=\"#{view.edit_referential_path(referential)}\"]") + end - %w(create destroy update).each do |p| - with_permission "referentials.#{p}" do - it { should match_actions_links_snapshot "referentials/show_#{p}" } - end - end + with_permission "referentials.update" do + it "should present edit button" do + expect(rendered).to have_selector("a[href=\"#{view.edit_referential_path(referential)}\"]") end - %w(purchase_windows referential_vehicle_journeys).each do |f| - with_feature f do - it { should match_actions_links_snapshot "referentials/show_#{f}" } - - %w(create update destroy).each do |p| - with_permission "referentials.#{p}" do - it { should match_actions_links_snapshot "referentials/show_#{f}_#{p}" } - end - end + context "with a readonly referential" do + let(:readonly){ true } + it "should not present edit button" do + expect(rendered).to_not have_selector("a[href=\"#{view.edit_referential_path(referential)}\"]") end end end + end |
