diff options
374 files changed, 4784 insertions, 945 deletions
| @@ -33,9 +33,6 @@ gem 'spring', group: :development  # ActiveRecord associations on top of PostgreSQL arrays  gem 'has_array_of', af83: 'has_array_of' -# Track changes to your models' data. Good for auditing or versioning. -gem 'paper_trail' -  gem 'rails-observers'  # Use SeedBank for spliting seeds @@ -134,7 +131,7 @@ gem 'acts_as_tree', '~> 2.1.0', require: 'acts_as_tree'  gem 'rabl'  gem 'carrierwave', '~> 1.0' -gem 'sidekiq' +gem 'sidekiq', require: ['sidekiq', 'sidekiq/web']  gem 'whenever', github: 'af83/whenever', require: false # '~> 0.9'  gem 'rake'  gem 'devise-async' @@ -146,6 +143,8 @@ gem 'puma', '~> 3.10.0'  gem 'newrelic_rpm'  gem 'letter_opener' +gem 'gtfs' +  group :development do    gem 'capistrano', '2.13.5'    gem 'capistrano-ext' diff --git a/Gemfile.lock b/Gemfile.lock index 4e3c76690..4fb77eeb9 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -258,6 +258,10 @@ GEM      google-analytics-rails (1.1.0)      gretel (3.0.9)        rails (>= 3.1.0) +    gtfs (0.2.5) +      multi_json +      rake +      rubyzip (~> 1.1)      has_scope (0.7.0)        actionpack (>= 4.1, < 5.1)        activesupport (>= 4.1, < 5.1) @@ -321,7 +325,7 @@ GEM      minitest (5.11.3)      money (6.10.1)        i18n (>= 0.6.4, < 1.0) -    multi_json (1.12.1) +    multi_json (1.13.1)      multi_test (0.1.2)      multi_xml (0.6.0)      multipart-post (2.0.0) @@ -337,10 +341,6 @@ GEM        mini_portile2 (~> 2.3.0)      open4 (1.3.4)      orm_adapter (0.5.0) -    paper_trail (4.1.0) -      activerecord (>= 3.0, < 6.0) -      activesupport (>= 3.0, < 6.0) -      request_store (~> 1.1)      parser (2.4.0.0)        ast (~> 2.2)      pg (0.20.0) @@ -424,7 +424,7 @@ GEM        thor (>= 0.18.1, < 2.0)      rainbow (2.2.2)        rake -    rake (12.3.0) +    rake (12.3.1)      ransack (1.8.3)        actionpack (>= 3.0)        activerecord (>= 3.0) @@ -637,6 +637,7 @@ DEPENDENCIES    georuby-ext (= 0.0.5)    google-analytics-rails    gretel +  gtfs    has_array_of!    htmlbeautifier    i18n-js @@ -652,7 +653,6 @@ DEPENDENCIES    map_layers (= 0.0.4)    mimemagic    newrelic_rpm -  paper_trail    pg    phantomjs    poltergeist diff --git a/app/assets/javascripts/application.js b/app/assets/javascripts/application.js index 6a79f7e8e..3eaade37f 100644 --- a/app/assets/javascripts/application.js +++ b/app/assets/javascripts/application.js @@ -25,3 +25,9 @@  //= require "i18n"  //= require "i18n/extended"  //= require "i18n/translations" + +$(document).ready(function() { +    $('a[disabled=disabled]').click(function(event){ +        event.preventDefault(); // Prevent link from following its href +    }); +}); diff --git a/app/assets/stylesheets/components/_compliance_control_blocks.sass b/app/assets/stylesheets/components/_compliance_control_blocks.sass new file mode 100644 index 000000000..46880075c --- /dev/null +++ b/app/assets/stylesheets/components/_compliance_control_blocks.sass @@ -0,0 +1,3 @@ +#compliance_control_block_form +	.condition-attributes-errors +		margin-bottom: 20px diff --git a/app/assets/stylesheets/components/_main_nav.sass b/app/assets/stylesheets/components/_main_nav.sass index 8e164fa01..ef3f14762 100644 --- a/app/assets/stylesheets/components/_main_nav.sass +++ b/app/assets/stylesheets/components/_main_nav.sass @@ -346,6 +346,7 @@ $menuW: 300px            flex: 1 1            height: $menuH            position: relative +          margin-right: 2em            h1              position: absolute @@ -355,8 +356,8 @@ $menuW: 300px              line-height: 1.1              white-space: nowrap              max-height: 1.1em -            margin: 0 -1.4em 0 0 -            padding: 0 1.4em 5px 0 +            margin: 0 +            padding: 0 0 5px 0              overflow: hidden              text-overflow: ellipsis diff --git a/app/assets/stylesheets/components/_pagination.sass b/app/assets/stylesheets/components/_pagination.sass index 88ba61c3c..b811a559c 100644 --- a/app/assets/stylesheets/components/_pagination.sass +++ b/app/assets/stylesheets/components/_pagination.sass @@ -7,6 +7,9 @@    border-radius: 0    line-height: 34px +  &:first-letter +    text-transform: capitalize +    .page_links      display: inline-block      vertical-align: top diff --git a/app/assets/stylesheets/components/_referential_overview.sass b/app/assets/stylesheets/components/_referential_overview.sass index 7a0cc98c5..cf440b22c 100644 --- a/app/assets/stylesheets/components/_referential_overview.sass +++ b/app/assets/stylesheets/components/_referential_overview.sass @@ -24,6 +24,7 @@    .time-travel      padding-top: 3px      padding-bottom: 4px +    height: 43px      a.btn:first-child        margin-right: 1px      a.btn:last-child diff --git a/app/controllers/api/v1/netex_imports_controller.rb b/app/controllers/api/v1/netex_imports_controller.rb index 2654fa088..186ddc35c 100644 --- a/app/controllers/api/v1/netex_imports_controller.rb +++ b/app/controllers/api/v1/netex_imports_controller.rb @@ -25,56 +25,19 @@ module Api        def create_models          find_workbench -        create_referential          create_netex_import        end        def create_netex_import          attributes = netex_import_params.merge creator: "Webservice" - -        attributes = attributes.merge referential_id: @new_referential.id -          @netex_import = Import::Netex.new attributes          @netex_import.save! - -        unless @netex_import.referential -          Rails.logger.info "Can't create referential for import #{@netex_import.id}: #{@new_referential.inspect} #{@new_referential.metadatas.inspect} #{@new_referential.errors.full_messages}" -          @netex_import.messages.create criticity: :error, message_key: "referential_creation" -        end +        @netex_import.create_referential!        rescue ActiveRecord::RecordInvalid          render json: {errors: @netex_import.errors}, status: 406          finish_action!        end -      def create_referential -        @new_referential = -          Referential.new( -            name: netex_import_params['name'], -            organisation_id: @workbench.organisation_id, -            workbench_id: @workbench.id, -            metadatas: [metadata] -          ) -        @new_referential.save -      end - -      def metadata -        metadata = ReferentialMetadata.new - -        if netex_import_params['file'] -          netex_file = STIF::NetexFile.new(netex_import_params['file'].to_io) -          frame = netex_file.frames.first - -          if frame -            metadata.periodes = frame.periods - -            line_objectids = frame.line_refs.map { |ref| "STIF:CODIFLIGNE:Line:#{ref}" } -            metadata.line_ids = @workbench.lines.where(objectid: line_objectids).pluck(:id) -          end -        end - -        metadata -      end -        def netex_import_params          params            .require('netex_import') diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb index c4961123d..8b66e6097 100644 --- a/app/controllers/application_controller.rb +++ b/app/controllers/application_controller.rb @@ -1,5 +1,5 @@  class ApplicationController < ActionController::Base -  include PaperTrailSupport +  include MetadataControllerSupport    include Pundit    include FeatureChecker @@ -10,7 +10,6 @@ class ApplicationController < ActionController::Base    before_action :authenticate_user!    before_action :set_locale -    # Load helpers in rails engine    helper LanguageEngine::Engine.helpers @@ -36,12 +35,6 @@ class ApplicationController < ActionController::Base    end    helper_method :current_organisation -  def current_functional_scope -    functional_scope = current_organisation.sso_attributes.try(:[], "functional_scope") if current_organisation -    JSON.parse(functional_scope) if functional_scope -  end -  helper_method :current_functional_scope -    def collection_name      self.class.name.split("::").last.gsub('Controller', '').underscore    end diff --git a/app/controllers/calendars_controller.rb b/app/controllers/calendars_controller.rb index cc7570d65..adb3b4764 100644 --- a/app/controllers/calendars_controller.rb +++ b/app/controllers/calendars_controller.rb @@ -64,13 +64,13 @@ class CalendarsController < ChouetteController    end    def calendar_params -    permitted_params = [:id, :name, :short_name, :shared, periods_attributes: [:id, :begin, :end, :_destroy], date_values_attributes: [:id, :value, :_destroy]] +    permitted_params = [:id, :name, :shared, periods_attributes: [:id, :begin, :end, :_destroy], date_values_attributes: [:id, :value, :_destroy]]      permitted_params << :shared if policy(Calendar).share?      params.require(:calendar).permit(*permitted_params)    end    def sort_column -    Calendar.column_names.include?(params[:sort]) ? params[:sort] : 'short_name' +    Calendar.column_names.include?(params[:sort]) ? params[:sort] : 'name'    end    def sort_direction @@ -104,6 +104,10 @@ class CalendarsController < ChouetteController      end    end +   def begin_of_association_chain +    current_organisation +  end +    def ransack_contains_date      date =[]      if params[:q] && !params[:q]['contains_date(1i)'].empty? diff --git a/app/controllers/chouette_controller.rb b/app/controllers/chouette_controller.rb index 3e4f3af27..e6e7c0a8a 100644 --- a/app/controllers/chouette_controller.rb +++ b/app/controllers/chouette_controller.rb @@ -1,4 +1,3 @@  class ChouetteController < InheritedResources::Base -  include PaperTrailSupport    include ApplicationHelper  end diff --git a/app/controllers/companies_controller.rb b/app/controllers/companies_controller.rb index 4afd12be1..a09cab783 100644 --- a/app/controllers/companies_controller.rb +++ b/app/controllers/companies_controller.rb @@ -38,12 +38,14 @@ class CompaniesController < ChouetteController    protected    def collection -    @q = line_referential.companies.search(params[:q]) - +    scope = line_referential.companies +    @q = scope.search(params[:q]) +    ids = @q.result(:distinct => true).pluck(:id) +    scope = scope.where(id: ids)      if sort_column && sort_direction -      @companies ||= @q.result(:distinct => true).order(sort_column + ' ' + sort_direction).paginate(:page => params[:page]) +      @companies ||= scope.order(sort_column + ' ' + sort_direction).paginate(:page => params[:page])      else -      @companies ||= @q.result(:distinct => true).order(:name).paginate(:page => params[:page]) +      @companies ||= scope.order(:name).paginate(:page => params[:page])      end    end @@ -69,7 +71,9 @@ class CompaniesController < ChouetteController    end    def company_params -    params.require(:company).permit( :objectid, :object_version, :name, :short_name, :organizational_unit, :operating_department_name, :code, :phone, :fax, :email, :registration_number, :url, :time_zone ) +    fields = [:objectid, :object_version, :name, :short_name, :organizational_unit, :operating_department_name, :code, :phone, :fax, :email, :registration_number, :url, :time_zone] +    fields += permitted_custom_fields_params(Chouette::Company.custom_fields(line_referential.workgroup)) +    params.require(:company).permit( fields )    end    private diff --git a/app/controllers/compliance_control_blocks_controller.rb b/app/controllers/compliance_control_blocks_controller.rb index 9eee8dfaf..0851e2800 100644 --- a/app/controllers/compliance_control_blocks_controller.rb +++ b/app/controllers/compliance_control_blocks_controller.rb @@ -10,4 +10,9 @@ class ComplianceControlBlocksController < ChouetteController      params.require(:compliance_control_block).permit(:transport_mode, :transport_submode)    end +  protected + +  alias_method :compliance_control_set, :parent +  helper_method :compliance_control_set +  end diff --git a/app/controllers/compliance_controls_controller.rb b/app/controllers/compliance_controls_controller.rb index 73dc18f59..7df922d01 100644 --- a/app/controllers/compliance_controls_controller.rb +++ b/app/controllers/compliance_controls_controller.rb @@ -5,7 +5,7 @@ class ComplianceControlsController < ChouetteController    actions :all, :except => [:index]    def select_type -    @sti_subclasses = ComplianceControl.subclasses +    @sti_subclasses = ComplianceControl.subclasses.sort_by {|compliance_control| compliance_control.default_code}    end    def show diff --git a/app/controllers/concerns/metadata_controller_support.rb b/app/controllers/concerns/metadata_controller_support.rb new file mode 100644 index 000000000..db83e79ae --- /dev/null +++ b/app/controllers/concerns/metadata_controller_support.rb @@ -0,0 +1,26 @@ +module MetadataControllerSupport +  extend ActiveSupport::Concern + +  included do +    after_action :set_creator_metadata, only: :create +    after_action :set_modifier_metadata, only: :update +  end + +  def user_for_metadata +    current_user ? current_user.username : '' +  end + +  def set_creator_metadata +    if resource.valid? +      resource.try(:set_metadata!, :creator_username, user_for_metadata) +      resource.try(:set_metadata!, :modifier_username, user_for_metadata) +    end +  end + +  def set_modifier_metadata +    _resource = @resources || [resource] +    _resource.flatten.each do |r| +      r.try :set_metadata!, :modifier_username, user_for_metadata +    end +  end +end diff --git a/app/controllers/concerns/paper_trail_support.rb b/app/controllers/concerns/paper_trail_support.rb deleted file mode 100644 index 4b0b1a7c7..000000000 --- a/app/controllers/concerns/paper_trail_support.rb +++ /dev/null @@ -1,11 +0,0 @@ -module PaperTrailSupport -  extend ActiveSupport::Concern - -  included do -    before_action :set_paper_trail_whodunnit - -    def user_for_paper_trail -      current_user ? current_user.name : '' -    end -  end -end diff --git a/app/controllers/exports_controller.rb b/app/controllers/exports_controller.rb index a5282a514..c89da5000 100644 --- a/app/controllers/exports_controller.rb +++ b/app/controllers/exports_controller.rb @@ -3,13 +3,14 @@ class ExportsController < ChouetteController    include RansackDateFilter    include IevInterfaces    skip_before_action :authenticate_user!, only: [:upload] +  skip_before_action :verify_authenticity_token, only: [:upload]    defaults resource_class: Export::Base, collection_name: 'exports', instance_name: 'export'    def upload      if params[:token] == resource.token_upload        resource.file = params[:file]        resource.save! -      redirect_to [resource.workbench, resource] +      render json: {status: :ok}      else        user_not_authorized      end diff --git a/app/controllers/journey_patterns_collections_controller.rb b/app/controllers/journey_patterns_collections_controller.rb index da567779e..db92d48f3 100644 --- a/app/controllers/journey_patterns_collections_controller.rb +++ b/app/controllers/journey_patterns_collections_controller.rb @@ -74,6 +74,7 @@ class JourneyPatternsCollectionsController < ChouetteController    def update      state  = JSON.parse request.raw_post      Chouette::JourneyPattern.state_update route, state +    @resources = route.journey_patterns      errors = state.any? {|item| item['errors']}      respond_to do |format| diff --git a/app/controllers/lines_controller.rb b/app/controllers/lines_controller.rb index ae8c9ed0c..cd8091252 100644 --- a/app/controllers/lines_controller.rb +++ b/app/controllers/lines_controller.rb @@ -122,7 +122,7 @@ class LinesController < ChouetteController    end    def line_params -    params.require(:line).permit( +    out = params.require(:line).permit(        :transport_mode,        :network_id,        :company_id, @@ -148,6 +148,8 @@ class LinesController < ChouetteController        :secondary_company_ids => [],        footnotes_attributes: [:code, :label, :_destroy, :id]      ) +    out[:secondary_company_ids] = (out[:secondary_company_ids] || []).select(&:present?) +    out    end     # Fake ransack filter diff --git a/app/controllers/purchase_windows_controller.rb b/app/controllers/purchase_windows_controller.rb index 293a7d8e4..cf73d0ed1 100644 --- a/app/controllers/purchase_windows_controller.rb +++ b/app/controllers/purchase_windows_controller.rb @@ -63,7 +63,9 @@ class PurchaseWindowsController < ChouetteController    def ransack_contains_date      date =[] -    if params[:q] && params[:q]['contains_date(1i)'].present? +    if params[:q] && params[:q]['contains_date'].present? +      params[:q]['contains_date'] = @date = params[:q]['contains_date'].to_date +    elsif params[:q] && params[:q]['contains_date(1i)'].present?        ['contains_date(1i)', 'contains_date(2i)', 'contains_date(3i)'].each do |key|          date << params[:q][key].to_i          params[:q].delete(key) diff --git a/app/controllers/referential_companies_controller.rb b/app/controllers/referential_companies_controller.rb index 806a70c8f..200e56a89 100644 --- a/app/controllers/referential_companies_controller.rb +++ b/app/controllers/referential_companies_controller.rb @@ -40,11 +40,12 @@ class ReferentialCompaniesController < ChouetteController      end      @q = scope.search(params[:q]) - +    ids = @q.result(:distinct => true).pluck(:id) +    scope = scope.where(id: ids)      if sort_column && sort_direction -      @companies ||= @q.result(:distinct => true).order(sort_column + ' ' + sort_direction).paginate(:page => params[:page]) +      @companies ||= scope.order(sort_column + ' ' + sort_direction).paginate(:page => params[:page])      else -      @companies ||= @q.result(:distinct => true).order(:name).paginate(:page => params[:page]) +      @companies ||= scope.order(:name).paginate(:page => params[:page])      end    end @@ -57,7 +58,9 @@ class ReferentialCompaniesController < ChouetteController    end    def company_params -    params.require(:company).permit( :objectid, :object_version, :name, :short_name, :organizational_unit, :operating_department_name, :code, :phone, :fax, :email, :registration_number, :url, :time_zone ) +    fields = [:objectid, :object_version, :name, :short_name, :organizational_unit, :operating_department_name, :code, :phone, :fax, :email, :registration_number, :url, :time_zone] +    fields += permitted_custom_fields_params(Chouette::Company.custom_fields(@referential.workgroup)) +    params.require(:company).permit( fields )    end    private diff --git a/app/controllers/referentials_controller.rb b/app/controllers/referentials_controller.rb index 6e3694547..fe661651e 100644 --- a/app/controllers/referentials_controller.rb +++ b/app/controllers/referentials_controller.rb @@ -143,7 +143,7 @@ class ReferentialsController < ChouetteController    def build_referential      if params[:from]        source_referential = Referential.find(params[:from]) -      @referential = Referential.new_from(source_referential, current_functional_scope) +      @referential = Referential.new_from(source_referential, current_organisation)      end      @referential.data_format = current_organisation.data_format diff --git a/app/controllers/routes_controller.rb b/app/controllers/routes_controller.rb index 96a23c938..ac243c8eb 100644 --- a/app/controllers/routes_controller.rb +++ b/app/controllers/routes_controller.rb @@ -63,7 +63,8 @@ class RoutesController < ChouetteController    end    def duplicate -    route = Chouette::Route.find(params[:id]).duplicate +    source = Chouette::Route.find(params[:id]) +    route = source.duplicate params[:opposite]      flash[:notice] = t('routes.duplicate.success')      redirect_to referential_line_path(@referential, route.line)    end diff --git a/app/controllers/stop_areas_controller.rb b/app/controllers/stop_areas_controller.rb index d0d9f652d..b2634467d 100644 --- a/app/controllers/stop_areas_controller.rb +++ b/app/controllers/stop_areas_controller.rb @@ -161,7 +161,7 @@ class StopAreasController < ChouetteController    helper_method :current_referential    def stop_area_params -    params.require(:stop_area).permit( +    fields = [        :area_type,        :children_ids,        :city_name, @@ -192,7 +192,8 @@ class StopAreasController < ChouetteController        :kind,        :status,        localized_names: Chouette::StopArea::AVAILABLE_LOCALIZATIONS -    ) +    ] + permitted_custom_fields_params(Chouette::StopArea.custom_fields) # XXX filter on the workgroup +    params.require(:stop_area).permit(fields)    end     # Fake ransack filter diff --git a/app/controllers/time_tables_controller.rb b/app/controllers/time_tables_controller.rb index 0dcadad1e..2ac8532e0 100644 --- a/app/controllers/time_tables_controller.rb +++ b/app/controllers/time_tables_controller.rb @@ -128,12 +128,43 @@ class TimeTablesController < ChouetteController      scope = self.ransack_period_range(scope: scope, error_message: t('referentials.errors.validity_period'), query: :overlapping)      @q = scope.search(params[:q]) -    if sort_column && sort_direction -      @time_tables ||= @q.result(:distinct => true).order("#{sort_column} #{sort_direction}") -    else -      @time_tables ||= @q.result(:distinct => true).order(:comment) +    @time_tables ||= begin +      time_tables = @q.result(:distinct => true) +      if sort_column == "bounding_dates" +        time_tables = @q.result(:distinct => false).paginate(page: params[:page], per_page: 10) +        ids = time_tables.pluck(:id).uniq +        query = """ +        WITH time_tables_dates AS( +        SELECT time_tables.id, time_table_dates.date FROM time_tables +        LEFT JOIN time_table_dates ON time_table_dates.time_table_id = time_tables.id +        WHERE time_table_dates.in_out IS NULL OR time_table_dates.in_out = 't' +        UNION +        SELECT time_tables.id, time_table_periods.period_start FROM time_tables +        LEFT JOIN time_table_periods ON time_table_periods.time_table_id = time_tables.id +        ) +        SELECT time_tables.id, MIN(time_tables_dates.date) AS min_date FROM time_tables +        INNER JOIN time_tables_dates ON time_tables_dates.id = time_tables.id +        WHERE time_tables.id IN (#{ids.map(&:to_s).join(',')}) +        GROUP BY time_tables.id +        ORDER BY min_date #{sort_direction} +  """ + +        ordered_ids =  ActiveRecord::Base.connection.exec_query(query).map {|r| r["id"]} +        order_by = ["CASE"] +        ordered_ids.each_with_index do |id, index| +          order_by << "WHEN id='#{id}' THEN #{index}" +        end +        order_by << "END" +        time_tables = time_tables.order(order_by.join(" ")) +      elsif sort_column == "vehicle_journeys_count" +        time_tables = time_tables.joins("LEFT JOIN time_tables_vehicle_journeys ON time_tables_vehicle_journeys.time_table_id = time_tables.id LEFT JOIN vehicle_journeys ON vehicle_journeys.id = time_tables_vehicle_journeys.vehicle_journey_id")\ +          .group("time_tables.id").select('time_tables.*, COUNT(vehicle_journeys.id) as vehicle_journeys_count').order("#{sort_column} #{sort_direction}") +      else +        time_tables = time_tables.order("#{sort_column} #{sort_direction}") +      end +      time_tables = time_tables.paginate(page: params[:page], per_page: 10) +      time_tables      end -    @time_tables = @time_tables.paginate(page: params[:page], per_page: 10)    end    def select_time_tables @@ -155,7 +186,10 @@ class TimeTablesController < ChouetteController    private    def sort_column -    referential.time_tables.column_names.include?(params[:sort]) ? params[:sort] : 'comment' +    valid_cols = referential.time_tables.column_names +    valid_cols << "bounding_dates" +    valid_cols << "vehicle_journeys_count" +    valid_cols.include?(params[:sort]) ? params[:sort] : 'comment'    end    def sort_direction      %w[asc desc].include?(params[:direction]) ?  params[:direction] : 'asc' diff --git a/app/controllers/vehicle_journeys_collections_controller.rb b/app/controllers/vehicle_journeys_collections_controller.rb index 712bcc154..a117888ab 100644 --- a/app/controllers/vehicle_journeys_collections_controller.rb +++ b/app/controllers/vehicle_journeys_collections_controller.rb @@ -9,8 +9,8 @@ class VehicleJourneysCollectionsController < ChouetteController    alias_method :route, :parent    def update -    state  = JSON.parse request.raw_post -    Chouette::VehicleJourney.state_update route, state +    state = JSON.parse request.raw_post +    @resources = Chouette::VehicleJourney.state_update route, state      errors = state.any? {|item| item['errors']}      respond_to do |format| diff --git a/app/controllers/vehicle_journeys_controller.rb b/app/controllers/vehicle_journeys_controller.rb index 821ea83ff..220f2d29e 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 = referential.workgroup&.custom_fields_definitions || {} +    @custom_fields = Chouette::VehicleJourney.custom_fields_definitions(referential.workgroup)      @extra_headers = Rails.application.config.vehicle_journeys_extra_headers.dup.delete_if do |header|        header[:type] == :custom_field and not @custom_fields.has_key?(header[:name].to_s) diff --git a/app/decorators/referential_decorator.rb b/app/decorators/referential_decorator.rb index 41cad237d..cce14d160 100644 --- a/app/decorators/referential_decorator.rb +++ b/app/decorators/referential_decorator.rb @@ -43,7 +43,7 @@ class ReferentialDecorator < AF83::Decorator      end      instance_decorator.action_link policy: :edit, secondary: :show, on: :show do |l| -      l.content 'Purger' +      l.content t('actions.clean_up')        l.href '#'        l.type 'button'        l.data {{ diff --git a/app/decorators/route_decorator.rb b/app/decorators/route_decorator.rb index fa6367924..646bc1620 100644 --- a/app/decorators/route_decorator.rb +++ b/app/decorators/route_decorator.rb @@ -71,6 +71,24 @@ class RouteDecorator < AF83::Decorator        end      end +    instance_decorator.action_link( +      secondary: :show, +      policy: :create_opposite, +      if: ->{h.has_feature?(:create_opposite_routes)} +    ) do |l| +      l.content h.t('routes.create_opposite.title') +      l.method :post +      l.disabled { object.opposite_route.present? } +      l.href do +        h.duplicate_referential_line_route_path( +          context[:referential], +          context[:line], +          object, +          opposite: true +        ) +      end +    end +      instance_decorator.destroy_action_link do |l|        l.data confirm: h.t('routes.actions.destroy_confirm')      end diff --git a/app/helpers/application_helper.rb b/app/helpers/application_helper.rb index 356c7e69e..702ca0ffc 100644 --- a/app/helpers/application_helper.rb +++ b/app/helpers/application_helper.rb @@ -20,8 +20,11 @@ module ApplicationHelper      end      local = "#{object.model_name.name.underscore.pluralize}.#{params[:action]}.title" + +    arg = object.organisation.name if Workbench === object +      if object.try(:name) -      t(local, name: object.name || object.id) +      t(local, name: arg || object.name || object.id)      else        t(local)      end @@ -33,8 +36,8 @@ module ApplicationHelper      display = policy(object).synchronize? if policy(object).respond_to?(:synchronize?) rescue false      if display        info = t('last_update', time: l(object.updated_at, format: :short)) -      if object.try(:versions) -        author = object.versions.try(:last).try(:whodunnit) || t('default_whodunnit') +      if object.has_metadata? +        author = object.metadata.modifier_username || t('default_whodunnit')          info   = "#{info} <br/> #{t('whodunnit', author: author)}"        end        out += content_tag :div, info.html_safe, class: 'small last-update' @@ -133,5 +136,9 @@ module ApplicationHelper      url_for(:controller => "/help", :action => "show") + '/' + target    end - +  def permitted_custom_fields_params custom_fields +    [{ +      custom_field_values: custom_fields.map(&:code) +    }] +  end  end diff --git a/app/helpers/compliance_controls_helper.rb b/app/helpers/compliance_controls_helper.rb index ba0c538c9..abf909929 100644 --- a/app/helpers/compliance_controls_helper.rb +++ b/app/helpers/compliance_controls_helper.rb @@ -8,4 +8,15 @@ module ComplianceControlsHelper      key, pattern = key_pattern      [t("compliance_controls.filters.subclasses.#{key}"), "-#{pattern}-"]    end -end  + +  def compliance_control_metadatas(compliance_control) +    attributes = resource.class.dynamic_attributes +    attributes.push(*resource.control_attributes.keys) if resource&.control_attributes&.keys + +    {}.tap do |hash| +      attributes.each do |attribute| +        hash[ComplianceControl.human_attribute_name(attribute)] = resource.send(attribute) +      end +    end +  end +end diff --git a/app/helpers/multiple_selection_toolbox_helper.rb b/app/helpers/multiple_selection_toolbox_helper.rb index e0a1d2dd4..7e02c6d73 100644 --- a/app/helpers/multiple_selection_toolbox_helper.rb +++ b/app/helpers/multiple_selection_toolbox_helper.rb @@ -20,7 +20,7 @@ module MultipleSelectionToolboxHelper              data: {                path: delete_path,                # #5206 Missing Translations -              confirm: 'Etes-vous sûr(e) de vouloir effectuer cette action ?' +              confirm: t('actions.are_you_sure')              },              title: t("actions.#{action}")            ) do @@ -34,7 +34,7 @@ module MultipleSelectionToolboxHelper      label = content_tag(        :span, -      ("<span>0</span> élément(s) sélectionné(s)").html_safe, +      ("<span>0</span> #{t('table_builders.selected_elements')}").html_safe,        class: 'info-msg'      ) diff --git a/app/helpers/routes_helper.rb b/app/helpers/routes_helper.rb index 61714a066..84b4015a2 100644 --- a/app/helpers/routes_helper.rb +++ b/app/helpers/routes_helper.rb @@ -24,7 +24,7 @@ module RoutesHelper        stop_area_attributes = stop_point.stop_area.attributes.slice("name","city_name", "zip_code", "registration_number", "longitude", "latitude", "area_type", "comment")        stop_area_attributes["short_name"] = truncate(stop_area_attributes["name"], :length => 30) || ""        stop_point_attributes = stop_point.attributes.slice("for_boarding","for_alighting") -      stop_area_attributes.merge(stop_point_attributes).merge(stoppoint_id: stop_point.id, stoparea_id: stop_point.stop_area.id).merge(user_objectid: stop_point.stop_area.user_objectid) +      stop_area_attributes.merge(stop_point_attributes).merge(stoppoint_id: stop_point.id, stoparea_id: stop_point.stop_area.id, stoparea_kind: stop_point.stop_area.kind).merge(user_objectid: stop_point.stop_area.user_objectid)      end      data = data.to_json if serialize      data diff --git a/app/helpers/search_helper.rb b/app/helpers/search_helper.rb index be70d974d..16081b660 100644 --- a/app/helpers/search_helper.rb +++ b/app/helpers/search_helper.rb @@ -15,7 +15,7 @@ module SearchHelper        if val.is_a?(Array)          active = val.any? &:present?        elsif val.is_a?(Hash) -        active = val.values.any? &:present? +        active = val.values.any? {|v| v.present? && v != "false" }        else          active = true        end diff --git a/app/helpers/stop_areas_helper.rb b/app/helpers/stop_areas_helper.rb index 1c9d974a1..032f68494 100644 --- a/app/helpers/stop_areas_helper.rb +++ b/app/helpers/stop_areas_helper.rb @@ -68,7 +68,11 @@ module StopAreasHelper    end    def stop_area_registration_number_value stop_area -    stop_area&.registration_number || stop_area&.stop_area_referential&.generate_registration_number +    stop_area&.registration_number +  end + +  def stop_area_registration_number_hint +    t "formtastic.hints.stop_area.registration_number"    end    def stop_area_status(stop_area) diff --git a/app/helpers/table_builder_helper.rb b/app/helpers/table_builder_helper.rb index 63125b161..e2aa2e9ea 100644 --- a/app/helpers/table_builder_helper.rb +++ b/app/helpers/table_builder_helper.rb @@ -402,9 +402,10 @@ module TableBuilderHelper      content_tag(        :li,        link_to( -        link.href, -        method: link.method, -        data: link.data +        link.disabled ? '#' : link.href, +        method: link.disabled ? nil : link.method, +        data: link.data, +        disabled: link.disabled        ) do          link.content        end, diff --git a/app/javascript/helpers/polyfills.js b/app/javascript/helpers/polyfills.js new file mode 100644 index 000000000..93e3e9846 --- /dev/null +++ b/app/javascript/helpers/polyfills.js @@ -0,0 +1,4 @@ +import 'promise-polyfill/src/polyfill' +import 'es6-symbol/implement' +import 'polyfill-array-includes' +import 'whatwg-fetch' diff --git a/app/javascript/helpers/save_button.js b/app/javascript/helpers/save_button.js index 7e0bd5bbe..af5ee54a8 100644 --- a/app/javascript/helpers/save_button.js +++ b/app/javascript/helpers/save_button.js @@ -35,7 +35,7 @@ export default class SaveButton extends Component{                      this.props.editMode ? this.submitForm() : this.props.onEnterEditMode()                    }}                  > -                  {this.props.editMode ? "Valider" : "Editer"} +                  {this.props.editMode ? I18n.t('actions.submit') : I18n.t('actions.edit')}                  </button>                </div>              </form> diff --git a/app/javascript/journey_patterns/actions/index.js b/app/javascript/journey_patterns/actions/index.js index b90908264..a813f4b9e 100644 --- a/app/javascript/journey_patterns/actions/index.js +++ b/app/javascript/journey_patterns/actions/index.js @@ -1,10 +1,3 @@ -import Promise from 'promise-polyfill' - -// To add to window -if (!window.Promise) { -  window.Promise = Promise; -} -  const actions = {    enterEditMode: () => ({      type: "ENTER_EDIT_MODE" @@ -43,7 +36,7 @@ const actions = {    }),    updateCheckboxValue : (e, index) => ({      type : 'UPDATE_CHECKBOX_VALUE', -    id : e.currentTarget.id, +    position : e.currentTarget.id,      index    }),    checkConfirmModal : (event, callback, stateChanged,dispatch) => { @@ -195,15 +188,19 @@ const actions = {            dispatch(actions.unavailableServer())          } else {            if(json.length != 0){ -            let val -            for (val of json){ -              for (let stop_point of val.route_short_description.stop_points){ +            let j = 0 +            while(j < json.length){ +              let val = json[j] +              let i = 0 +              while(i < val.route_short_description.stop_points.length){ +                let stop_point = val.route_short_description.stop_points[i]                  stop_point.checked = false                  val.stop_area_short_descriptions.map((element) => {                    if(element.stop_area_short_description.id === stop_point.id){                      stop_point.checked = true                    }                  }) +                i ++                }                journeyPatterns.push(                  _.assign({}, val, { @@ -212,6 +209,7 @@ const actions = {                    deletable: false                  })                ) +              j ++              }            }            window.currentItemsLength = journeyPatterns.length @@ -220,7 +218,16 @@ const actions = {        })    },    fetchRouteCosts: (dispatch, key, index) => { -    if (actions.routeCostsCache) { +    if(actions.fetchingRouteCosts){ +        // A request is already sent, wait for it +        if(!actions.waitingForRouteCosts){ +          actions.waitingForRouteCosts = [] +        } +        actions.waitingForRouteCosts.push([key, index]) +        return +    } + +    if(actions.routeCostsCache) {        // Dispatch asynchronously to prevent warning when        // this executes during `render()`        requestAnimationFrame(() => { @@ -231,6 +238,7 @@ const actions = {          ))        })      } else { +      actions.fetchingRouteCosts = true        fetch(window.routeCostsUrl, {          credentials: 'same-origin',        }).then(response => { @@ -240,6 +248,12 @@ const actions = {          actions.routeCostsCache = costs          dispatch(actions.receiveRouteCosts(costs, key, index)) +        if(actions.waitingForRouteCosts){ +          actions.waitingForRouteCosts.map(function(item, i) { +            dispatch(actions.receiveRouteCosts(costs, item[0], item[1])) +          }) +        } +        actions.fetchingRouteCosts = false        })      }    }, diff --git a/app/javascript/journey_patterns/components/ConfirmModal.js b/app/javascript/journey_patterns/components/ConfirmModal.js index ccd0a9384..fdf32649f 100644 --- a/app/javascript/journey_patterns/components/ConfirmModal.js +++ b/app/javascript/journey_patterns/components/ConfirmModal.js @@ -9,11 +9,11 @@ export default function ConfirmModal({dispatch, modal, onModalAccept, onModalCan          <div className='modal-dialog'>            <div className='modal-content'>              <div className='modal-header'> -              <h4 className='modal-title'>Confirmation</h4> +              <h4 className='modal-title'>{I18n.t('journey_patterns.show.confirmation')}</h4>              </div>              <div className='modal-body'>                <div className='mt-md mb-md'> -                <p>Vous vous apprêtez à changer de page. Voulez-vous valider vos modifications avant cela ?</p> +                <p>{I18n.t('journey_patterns.show.confirm_page_change')}</p>                </div>              </div>              <div className='modal-footer'> @@ -23,7 +23,7 @@ export default function ConfirmModal({dispatch, modal, onModalAccept, onModalCan                  type='button'                  onClick={() => { onModalCancel(modal.confirmModal.callback) }}                > -                Ne pas valider +                {I18n.t('cancel')}              </button>                <button                  className='btn btn-primary' @@ -31,7 +31,7 @@ export default function ConfirmModal({dispatch, modal, onModalAccept, onModalCan                  type='button'                  onClick={() => { onModalAccept(modal.confirmModal.callback, journeyPatterns) }}                > -                Valider +                {I18n.t('actions.submit')}              </button>              </div>            </div> diff --git a/app/javascript/journey_patterns/components/CreateModal.js b/app/javascript/journey_patterns/components/CreateModal.js index a6c1b608a..946c13d9c 100644 --- a/app/javascript/journey_patterns/components/CreateModal.js +++ b/app/javascript/journey_patterns/components/CreateModal.js @@ -38,14 +38,14 @@ export default class CreateModal extends Component {                    <div className='modal-dialog'>                      <div className='modal-content'>                        <div className='modal-header'> -                        <h4 className='modal-title'>Ajouter une mission</h4> +                        <h4 className='modal-title'>{I18n.t('journey_patterns.actions.new')}</h4>                        </div>                        {(this.props.modal.type == 'create') && (                          <form>                            <div className='modal-body'>                              <div className='form-group'> -                              <label className='control-label is-required'>Nom</label> +                              <label className='control-label is-required'>{I18n.attribute_name('journey_pattern', 'name')}</label>                                <input                                  type='text'                                  ref='name' @@ -57,7 +57,7 @@ export default class CreateModal extends Component {                              <div className='row'>                                <div className='col-lg-6 col-md-6 col-sm-6 col-xs-6'>                                  <div className='form-group'> -                                  <label className='control-label is-required'>Nom public</label> +                                  <label className='control-label is-required'>{I18n.attribute_name('journey_pattern', 'published_name')}</label>                                    <input                                      type='text'                                      ref='published_name' @@ -69,7 +69,7 @@ export default class CreateModal extends Component {                                </div>                                <div className='col-lg-6 col-md-6 col-sm-6 col-xs-6'>                                  <div className='form-group'> -                                  <label className='control-label'>Code mission</label> +                                  <label className='control-label'>{I18n.attribute_name('journey_pattern', 'registration_number')}</label>                                    <input                                      type='text'                                      ref='registration_number' @@ -87,14 +87,14 @@ export default class CreateModal extends Component {                                type='button'                                onClick={this.props.onModalClose}                                > -                              Annuler +                              {I18n.t('cancel')}                              </button>                              <button                                className='btn btn-primary'                                type='button'                                onClick={this.handleSubmit.bind(this)}                                > -                              Valider +                              {I18n.t('actions.submit')}                              </button>                            </div>                          </form> @@ -120,4 +120,4 @@ CreateModal.propTypes = {    onOpenCreateModal: PropTypes.func.isRequired,    onModalClose: PropTypes.func.isRequired,    onAddJourneyPattern: PropTypes.func.isRequired -}
\ No newline at end of file +} diff --git a/app/javascript/journey_patterns/components/EditModal.js b/app/javascript/journey_patterns/components/EditModal.js index c960cb41c..1960849fb 100644 --- a/app/javascript/journey_patterns/components/EditModal.js +++ b/app/javascript/journey_patterns/components/EditModal.js @@ -18,12 +18,12 @@ export default class EditModal extends Component {      if (this.props.editMode) {        return (          <h4 className='modal-title'> -          Editer la mission +          {I18n.t('journey_patterns.actions.edit')}            {this.props.modal.type == 'edit' && <em> "{this.props.modal.modalProps.journeyPattern.name}"</em>}          </h4>        )      } else { -      return <h4 className='modal-title'> Informations </h4> +      return <h4 className='modal-title'> {I18n.t('journey_patterns.show.informations')} </h4>      }    } @@ -41,7 +41,7 @@ export default class EditModal extends Component {                  <form>                    <div className='modal-body'>                      <div className='form-group'> -                      <label className='control-label is-required'>Nom</label> +                      <label className='control-label is-required'>{I18n.attribute_name('journey_pattern', 'name')}</label>                        <input                          type='text'                          ref='name' @@ -57,7 +57,7 @@ export default class EditModal extends Component {                      <div className='row'>                        <div className='col-lg-6 col-md-6 col-sm-6 col-xs-6'>                          <div className='form-group'> -                          <label className='control-label is-required'>Nom public</label> +                          <label className='control-label is-required'>{I18n.attribute_name('journey_pattern', 'published_name')}</label>                            <input                              type='text'                              ref='published_name' @@ -72,7 +72,7 @@ export default class EditModal extends Component {                        </div>                        <div className='col-lg-6 col-md-6 col-sm-6 col-xs-6'>                          <div className='form-group'> -                          <label className='control-label'>Code mission</label> +                          <label className='control-label'>{I18n.attribute_name('journey_pattern', 'registration_number')}</label>                            <input                              type='text'                              ref='registration_number' @@ -86,7 +86,7 @@ export default class EditModal extends Component {                        </div>                      </div>                      <div> -                      <label className='control-label'>Signature métier</label> +                      <label className='control-label'>{I18n.attribute_name('journey_pattern', 'checksum')}</label>                          <input                          type='text'                          ref='checksum' @@ -105,14 +105,14 @@ export default class EditModal extends Component {                          type='button'                          onClick={this.props.onModalClose}                        > -                        Annuler +                        {I18n.t('cancel')}                        </button>                        <button                          className='btn btn-primary'                          type='button'                          onClick={this.handleSubmit.bind(this)}                        > -                        Valider +                        {I18n.t('actions.submit')}                        </button>                      </div>                    } diff --git a/app/javascript/journey_patterns/components/JourneyPattern.js b/app/javascript/journey_patterns/components/JourneyPattern.js index 15d8b6db4..4eba80030 100644 --- a/app/javascript/journey_patterns/components/JourneyPattern.js +++ b/app/javascript/journey_patterns/components/JourneyPattern.js @@ -23,7 +23,7 @@ export default class JourneyPattern extends Component{      let vjURL = routeURL + '/vehicle_journeys?jp=' + jpOid      return ( -      <a href={vjURL}>Horaires des courses</a> +      <a href={vjURL}>{I18n.t('journey_patterns.journey_pattern.vehicle_journey_at_stops')}</a>      )    } @@ -45,7 +45,7 @@ export default class JourneyPattern extends Component{            <input              onChange = {(e) => this.props.onCheckboxChange(e)}              type='checkbox' -            id={sp.id} +            id={sp.position}              checked={sp.checked}              disabled={(this.props.value.deletable || this.props.status.policy['journey_patterns.update'] == false || this.props.editMode == false) ? 'disabled' : ''}              > @@ -80,7 +80,6 @@ export default class JourneyPattern extends Component{      let from = null      this.props.value.stop_points.map((stopPoint, i) =>{        let usePoint = stopPoint.checked -      console.log(stopPoint)        if(onlyCommercial && (i == 0 || i == this.props.value.stop_points.length - 1) && stopPoint.kind == "non_commercial"){          usePoint = false        } @@ -131,23 +130,20 @@ export default class JourneyPattern extends Component{      }    } -  componentWillUpdate() { -    [this.totalTime, this.totalDistance] = this.totals(false) -  } -    render() {      this.previousSpId = undefined +    let [totalTime, totalDistance] = this.totals(false)      let [commercialTotalTime, commercialTotalDistance] = this.totals(true)      return (        <div className={'t2e-item' + (this.props.value.deletable ? ' disabled' : '') + (this.props.value.object_id ? '' : ' to_record') + (this.props.value.errors ? ' has-error': '') + (this.hasFeature('costs_in_journey_patterns') ? ' with-costs' : '')}>          <div className='th'>            <div className='strong mb-xs'>{this.props.value.object_id ? this.props.value.short_id : '-'}</div>            <div>{this.props.value.registration_number}</div> -          <div>{actions.getChecked(this.props.value.stop_points).length} arrêt(s)</div> +          <div>{I18n.t('journey_patterns.show.stop_points_count', {count: actions.getChecked(this.props.value.stop_points).length})}</div>            {this.hasFeature('costs_in_journey_patterns') &&              <div className="small row totals"> -              <span className="col-md-6"><i className="fa fa-arrows-h"></i>{this.totalDistance}</span> -              <span className="col-md-6"><i className="fa fa-clock-o"></i>{this.totalTime}</span> +              <span className="col-md-6"><i className="fa fa-arrows-h"></i>{totalDistance}</span> +              <span className="col-md-6"><i className="fa fa-clock-o"></i>{totalTime}</span>              </div>            }            {this.hasFeature('costs_in_journey_patterns') && @@ -171,7 +167,7 @@ export default class JourneyPattern extends Component{                    data-toggle='modal'                    data-target='#JourneyPatternModal'                    > -                  {this.props.editMode ? 'Editer' : 'Consulter'} +                  {this.props.editMode ? I18n.t('actions.edit') : I18n.t('actions.show')}                  </button>                </li>                <li className={this.props.value.object_id ? '' : 'disabled'}> @@ -187,7 +183,7 @@ export default class JourneyPattern extends Component{                      this.props.onDeleteJourneyPattern(this.props.index)}                    }                    > -                    <span className='fa fa-trash'></span>Supprimer +                    <span className='fa fa-trash'></span>{I18n.t('actions.destroy')}                    </button>                  </li>                </ul> diff --git a/app/javascript/journey_patterns/components/JourneyPatterns.js b/app/javascript/journey_patterns/components/JourneyPatterns.js index 930acb390..91c783189 100644 --- a/app/javascript/journey_patterns/components/JourneyPatterns.js +++ b/app/javascript/journey_patterns/components/JourneyPatterns.js @@ -84,14 +84,14 @@ export default class JourneyPatterns extends Component {            <div className='col-lg-12'>              {(this.props.status.fetchSuccess == false) && (                <div className="alert alert-danger mt-sm"> -                <strong>Erreur : </strong> -                la récupération des missions a rencontré un problème. Rechargez la page pour tenter de corriger le problème +                <strong>{I18n.t('error')} : </strong> +                {I18n.t('journeys_patterns.journey_pattern.fetching_error')}                </div>              )}              { _.some(this.props.journeyPatterns, 'errors') && (                <div className="alert alert-danger mt-sm"> -                <strong>Erreur : </strong> +                <strong> {I18n.t('error')} : </strong>                  {this.props.journeyPatterns.map((jp, index) =>                    jp.errors && jp.errors.map((err, i) => {                      return ( @@ -107,9 +107,9 @@ export default class JourneyPatterns extends Component {              <div className={'table table-2entries mt-sm mb-sm' + ((this.props.journeyPatterns.length > 0) ? '' : ' no_result')}>                <div className='t2e-head w20'>                  <div className='th'> -                  <div className='strong mb-xs'>ID Mission</div> -                  <div>Code mission</div> -                  <div>Nb arrêts</div> +                  <div className='strong mb-xs'>{I18n.t('objectid')}</div> +                  <div>{I18n.attribute_name('journey_pattern', 'registration_number')}</div> +                  <div>{I18n.attribute_name('journey_pattern', 'stop_points')}</div>                    { this.hasFeature('costs_in_journey_patterns') &&                       <div>                         <div>{I18n.attribute_name('journey_pattern', 'full_journey_time')}</div> diff --git a/app/javascript/journey_patterns/components/Navigate.js b/app/javascript/journey_patterns/components/Navigate.js index 78f324a7d..9e454da5e 100644 --- a/app/javascript/journey_patterns/components/Navigate.js +++ b/app/javascript/journey_patterns/components/Navigate.js @@ -1,5 +1,6 @@  import React, { Component } from 'react'  import PropTypes from 'prop-types' +import capitalize from 'lodash/capitalize'  import actions from '../actions'  export default function Navigate({ dispatch, journeyPatterns, pagination, status }) { @@ -17,7 +18,7 @@ export default function Navigate({ dispatch, journeyPatterns, pagination, status        <div className='row'>          <div className='col-lg-12 text-right'>            <div className='pagination'> -            Liste des missions {firstItemOnPage} à {(lastItemOnPage < pagination.totalCount) ? lastItemOnPage : pagination.totalCount} sur {pagination.totalCount} +            {I18n.t('will_paginate.page_entries_info.multi_page', { model: capitalize(I18n.model_name('journey_pattern', { plural: true })), from: firstItemOnPage, to: lastItemOnPage, count: pagination.totalCount})}              <form className='page_links' onSubmit={e => {                  e.preventDefault()                }}> diff --git a/app/javascript/journey_patterns/reducers/journeyPatterns.js b/app/javascript/journey_patterns/reducers/journeyPatterns.js index 6c38e9288..b046f2b38 100644 --- a/app/javascript/journey_patterns/reducers/journeyPatterns.js +++ b/app/javascript/journey_patterns/reducers/journeyPatterns.js @@ -22,7 +22,7 @@ const journeyPattern = (state = {}, action) =>{        }      case 'UPDATE_CHECKBOX_VALUE':        var updatedStopPoints = state.stop_points.map((s) => { -        if (String(s.id) == action.id) { +        if (String(s.position) == action.position) {            return _.assign({}, s, {checked : !s.checked})          }else {            return s diff --git a/app/javascript/packs/calendars/edit.js b/app/javascript/packs/calendars/edit.js index bd09657ec..0c46313b9 100644 --- a/app/javascript/packs/calendars/edit.js +++ b/app/javascript/packs/calendars/edit.js @@ -1,3 +1,5 @@ +import '../../helpers/polyfills' +  import React from 'react'  import { render } from 'react-dom'  import { Provider } from 'react-redux' diff --git a/app/javascript/packs/exports/new.js b/app/javascript/packs/exports/new.js index ffe702cdb..b113cc709 100644 --- a/app/javascript/packs/exports/new.js +++ b/app/javascript/packs/exports/new.js @@ -1,3 +1,5 @@ +import '../../helpers/polyfills' +  import MasterSlave from "../../helpers/master_slave"  new MasterSlave("form") diff --git a/app/javascript/packs/journey_patterns/index.js b/app/javascript/packs/journey_patterns/index.js index 367a8830f..075eea13a 100644 --- a/app/javascript/packs/journey_patterns/index.js +++ b/app/javascript/packs/journey_patterns/index.js @@ -1,3 +1,5 @@ +import '../../helpers/polyfills' +  import React from 'react'  import { render } from 'react-dom'  import { Provider } from 'react-redux' diff --git a/app/javascript/packs/referential_lines/show.js b/app/javascript/packs/referential_lines/show.js index 99c5072ef..523f2040f 100644 --- a/app/javascript/packs/referential_lines/show.js +++ b/app/javascript/packs/referential_lines/show.js @@ -1,3 +1,5 @@ +import '../../helpers/polyfills' +  import clone from '../../helpers/clone'  import RoutesMap from '../../helpers/routes_map' diff --git a/app/javascript/packs/referential_overview/overview.js b/app/javascript/packs/referential_overview/overview.js index 59c326e9a..03da12ef3 100644 --- a/app/javascript/packs/referential_overview/overview.js +++ b/app/javascript/packs/referential_overview/overview.js @@ -1 +1,3 @@ +import '../../helpers/polyfills' +  import ReferentialOverview from '../../referential_overview' diff --git a/app/javascript/packs/routes/edit.js b/app/javascript/packs/routes/edit.js index b787bec97..0512b7aff 100644 --- a/app/javascript/packs/routes/edit.js +++ b/app/javascript/packs/routes/edit.js @@ -1,3 +1,5 @@ +import '../../helpers/polyfills' +  import React from 'react'  import PropTypes from 'prop-types' @@ -29,6 +31,7 @@ const getInitialState = () => {      state.push({        stoppoint_id: v.stoppoint_id,        stoparea_id: v.stoparea_id, +      stoparea_kind: v.stoparea_kind,        user_objectid: v.user_objectid,        short_name: v.short_name ? v.short_name.replace("'", "\'") : '',        area_type: v.area_type, @@ -39,8 +42,8 @@ const getInitialState = () => {        name: v.name ? v.name.replace("'", "\'") : '',        registration_number: v.registration_number,        text: fancyText, -      for_boarding: v.for_boarding || "normal", -      for_alighting: v.for_alighting || "normal", +      for_boarding: v.for_boarding, +      for_alighting: v.for_alighting,        longitude: v.longitude || 0,        latitude: v.latitude || 0,        comment: v.comment ? v.comment.replace("'", "\'") : '', @@ -55,12 +58,25 @@ const getInitialState = () => {  }  var initialState = { stopPoints: getInitialState() } -const loggerMiddleware = createLogger() -let store = createStore( -  reducers, -  initialState, -  applyMiddleware(thunkMiddleware, promise, loggerMiddleware) -) +let store = null + +if(Object.assign){ +  const loggerMiddleware = createLogger() +  store = createStore( +   reducers, +   initialState, +   applyMiddleware(thunkMiddleware, promise, loggerMiddleware) + ) +} +else{ +  // IE +  store = createStore( +   reducers, +   initialState, +   applyMiddleware(thunkMiddleware, promise) + ) +} +  render(    <Provider store={store}> diff --git a/app/javascript/packs/routes/show.js b/app/javascript/packs/routes/show.js index c20de0800..e8e068ddd 100644 --- a/app/javascript/packs/routes/show.js +++ b/app/javascript/packs/routes/show.js @@ -1,3 +1,5 @@ +import '../../helpers/polyfills' +  import clone from '../../helpers/clone'  import RoutesMap from '../../helpers/routes_map' diff --git a/app/javascript/packs/stop_areas/new.js b/app/javascript/packs/stop_areas/new.js index ffe702cdb..b113cc709 100644 --- a/app/javascript/packs/stop_areas/new.js +++ b/app/javascript/packs/stop_areas/new.js @@ -1,3 +1,5 @@ +import '../../helpers/polyfills' +  import MasterSlave from "../../helpers/master_slave"  new MasterSlave("form") diff --git a/app/javascript/packs/time_tables/edit.js b/app/javascript/packs/time_tables/edit.js index cf058d501..873bdea50 100644 --- a/app/javascript/packs/time_tables/edit.js +++ b/app/javascript/packs/time_tables/edit.js @@ -1,3 +1,5 @@ +import '../../helpers/polyfills' +  import React from 'react'  import { render } from 'react-dom'  import { Provider } from 'react-redux' diff --git a/app/javascript/packs/vehicle_journeys/index.js b/app/javascript/packs/vehicle_journeys/index.js index 9cad3870e..0b4351d26 100644 --- a/app/javascript/packs/vehicle_journeys/index.js +++ b/app/javascript/packs/vehicle_journeys/index.js @@ -1,3 +1,5 @@ +import '../../helpers/polyfills' +  import React from 'react'  import { render } from 'react-dom'  import { Provider } from 'react-redux' diff --git a/app/javascript/routes/components/BSelect2.js b/app/javascript/routes/components/BSelect2.js index 035bce155..89e1b6cfa 100644 --- a/app/javascript/routes/components/BSelect2.js +++ b/app/javascript/routes/components/BSelect2.js @@ -17,6 +17,7 @@ export default class BSelect3 extends Component {      this.props.onChange(this.props.index, {        text: e.currentTarget.textContent,        stoparea_id: e.currentTarget.value, +      stoparea_kind: e.params.data.kind,        user_objectid: e.params.data.user_objectid,        longitude: e.params.data.longitude,        latitude: e.params.data.latitude, diff --git a/app/javascript/routes/components/StopPoint.js b/app/javascript/routes/components/StopPoint.js index af51a6bb4..908e97263 100644 --- a/app/javascript/routes/components/StopPoint.js +++ b/app/javascript/routes/components/StopPoint.js @@ -4,6 +4,8 @@ import PropTypes from 'prop-types'  import BSelect2 from './BSelect2'  import OlMap from './OlMap' +import { defaultAttribute } from '../actions' +  export default function StopPoint(props, {I18n}) {    return (      <div className='nested-fields'> @@ -40,13 +42,13 @@ export default function StopPoint(props, {I18n}) {            <div              className={'btn btn-link' + (props.first ? ' disabled' : '')} -            onClick={props.onMoveUpClick} +            onClick={props.first ? null : props.onMoveUpClick}            >              <span className='fa fa-arrow-up'></span>            </div>            <div              className={'btn btn-link' + (props.last ? ' disabled' : '')} -            onClick={props.onMoveDownClick} +            onClick={props.last ? null : props.onMoveDownClick}            >              <span className='fa fa-arrow-down'></span>            </div> diff --git a/app/javascript/routes/index.js b/app/javascript/routes/index.js index febae7d54..fc99a3086 100644 --- a/app/javascript/routes/index.js +++ b/app/javascript/routes/index.js @@ -22,10 +22,12 @@ const getInitialState = () => {      let fancyText = v.name.replace("'", "\'")      if(v.zip_code && v.city_name)        fancyText += ", " + v.zip_code + " " + v.city_name.replace("'", "\'") +      forAlightingAndBoarding = v.stoparea_kind === 'commercial' ? 'normal' : 'forbidden'      state.push({        stoppoint_id: v.stoppoint_id,        stoparea_id: v.stoparea_id, +      stoparea_kind: v.stoparea_kind,        user_objectid: v.user_objectid,        short_name: v.short_name ? v.short_name.replace("'", "\'") : '',        area_type: v.area_type, @@ -36,8 +38,8 @@ const getInitialState = () => {        name: v.name ? v.name.replace("'", "\'") : '',        registration_number: v.registration_number,        text: fancyText, -      for_boarding: v.for_boarding || "normal", -      for_alighting: v.for_alighting || "normal", +      for_boarding: v.for_boarding || forAlightingAndBoarding, +      for_alighting: v.for_alighting || forAlightingAndBoarding,        longitude: v.longitude || 0,        latitude: v.latitude || 0,        comment: v.comment ? v.comment.replace("'", "\'") : '', diff --git a/app/javascript/routes/reducers/stopPoints.js b/app/javascript/routes/reducers/stopPoints.js index 0b42b504f..54142338d 100644 --- a/app/javascript/routes/reducers/stopPoints.js +++ b/app/javascript/routes/reducers/stopPoints.js @@ -61,6 +61,7 @@ const stopPoints = (state = [], action) => {      case 'UPDATE_INPUT_VALUE':        return state.map( (t, i) => {          if (i === action.index) { +          let forAlightingAndBoarding = action.text.stoparea_kind === 'commercial' ? 'normal' : 'forbidden'            return _.assign(              {},              t, @@ -68,6 +69,7 @@ const stopPoints = (state = [], action) => {                stoppoint_id: t.stoppoint_id,                text: action.text.text,                stoparea_id: action.text.stoparea_id, +              stoparea_kind: action.text.stoparea_kind,                user_objectid: action.text.user_objectid,                latitude: action.text.latitude,                longitude: action.text.longitude, @@ -76,7 +78,9 @@ const stopPoints = (state = [], action) => {                area_type: action.text.area_type,                city_name: action.text.city_name,                comment: action.text.comment, -              registration_number: action.text.registration_number +              registration_number: action.text.registration_number, +              for_alighting: forAlightingAndBoarding, +              for_boarding: forAlightingAndBoarding              }            )          } else { diff --git a/app/javascript/time_tables/actions/index.js b/app/javascript/time_tables/actions/index.js index 98b9eab4b..a5c454a18 100644 --- a/app/javascript/time_tables/actions/index.js +++ b/app/javascript/time_tables/actions/index.js @@ -4,7 +4,6 @@ import reject from 'lodash/reject'  import some from 'lodash/some'  import every from 'lodash/every'  import clone from '../../helpers/clone' -const I18n = clone(window, "I18n")  const actions = {    weekDays: (index) => { @@ -192,10 +191,13 @@ const actions = {    isInPeriod: (periods, date) => {      date = new Date(date) -    for (let period of periods) { +    let i = 0; +    while(i < periods.length){ +      let period = periods[i]        let begin = new Date(period.period_start)        let end = new Date(period.period_end)        if (date >= begin && date <= end) return true +      i++      }      return false @@ -236,12 +238,14 @@ const actions = {      let error = ''      start = new Date(start)      end = new Date(end) - -    for (let day of in_days) { +    let i = 0; +    while(i < in_days){ +      let day = in_days[i]        if (start <= new Date(day.date) && end >= new Date(day.date)) {          error = I18n.t('time_tables.edit.error_submit.dates_overlaps')          break        } +      i ++      }      return error    }, @@ -307,10 +311,11 @@ const actions = {        })    },    errorModalKey: (periods, dayTypes) => { -    const withoutPeriodsWithDaysTypes = reject(periods, 'deleted').length == 0 && some(dayTypes) && "withoutPeriodsWithDaysTypes" +    // const withoutPeriodsWithDaysTypes = reject(periods, 'deleted').length == 0 && some(dayTypes) && "withoutPeriodsWithDaysTypes"      const withPeriodsWithoutDayTypes = reject(periods, 'deleted').length > 0 &&  every(dayTypes, dt => dt == false) && "withPeriodsWithoutDayTypes" -    return (withoutPeriodsWithDaysTypes || withPeriodsWithoutDayTypes) && (withoutPeriodsWithDaysTypes ? "withoutPeriodsWithDaysTypes" : "withPeriodsWithoutDayTypes") +    // return (withoutPeriodsWithDaysTypes || withPeriodsWithoutDayTypes) && (withoutPeriodsWithDaysTypes ? "withoutPeriodsWithDaysTypes" : "withPeriodsWithoutDayTypes") +    return withPeriodsWithoutDayTypes    },    errorModalMessage: (errorKey) => { diff --git a/app/javascript/time_tables/components/ConfirmModal.js b/app/javascript/time_tables/components/ConfirmModal.js index 4e8583bc0..e4219348d 100644 --- a/app/javascript/time_tables/components/ConfirmModal.js +++ b/app/javascript/time_tables/components/ConfirmModal.js @@ -2,7 +2,7 @@ import React from 'react'  import PropTypes from 'prop-types' -export default function ConfirmModal({dispatch, modal, onModalAccept, onModalCancel, timetable, metas}, {I18n}) { +export default function ConfirmModal({dispatch, modal, onModalAccept, onModalCancel, timetable, metas}) {    return (      <div className={'modal fade ' + ((modal.type == 'confirm') ? 'in' : '')} id='ConfirmModal'>        <div className='modal-container'> @@ -45,8 +45,4 @@ ConfirmModal.propTypes = {    modal: PropTypes.object.isRequired,    onModalAccept: PropTypes.func.isRequired,    onModalCancel: PropTypes.func.isRequired -} - -ConfirmModal.contextTypes = { -  I18n: PropTypes.object -} +}
\ No newline at end of file diff --git a/app/javascript/time_tables/components/ErrorModal.js b/app/javascript/time_tables/components/ErrorModal.js index 8af12f1d1..a512d28fd 100644 --- a/app/javascript/time_tables/components/ErrorModal.js +++ b/app/javascript/time_tables/components/ErrorModal.js @@ -3,7 +3,7 @@ import PropTypes from 'prop-types'  import actions from '../actions' -export default function ErrorModal({dispatch, modal, onModalClose}, {I18n}) { +export default function ErrorModal({dispatch, modal, onModalClose}) {    return (      <div className={'modal fade ' + ((modal.type == 'error') ? 'in' : '')} id='ErrorModal'>        <div className='modal-container'> @@ -37,8 +37,4 @@ export default function ErrorModal({dispatch, modal, onModalClose}, {I18n}) {  ErrorModal.propTypes = {    modal: PropTypes.object.isRequired,    onModalClose: PropTypes.func.isRequired -} - -ErrorModal.contextTypes = { -  I18n: PropTypes.object -} +}
\ No newline at end of file diff --git a/app/javascript/time_tables/components/Metas.js b/app/javascript/time_tables/components/Metas.js index 08a6e26fe..d9746a379 100644 --- a/app/javascript/time_tables/components/Metas.js +++ b/app/javascript/time_tables/components/Metas.js @@ -4,7 +4,7 @@ import PropTypes from 'prop-types'  import actions from '../actions'  import TagsSelect2 from './TagsSelect2' -export default function Metas({metas, onUpdateDayTypes, onUpdateComment, onUpdateColor, onSelect2Tags, onUnselect2Tags}, {I18n}) { +export default function Metas({metas, onUpdateDayTypes, onUpdateComment, onUpdateColor, onSelect2Tags, onUnselect2Tags}) {    let colorList = ["", "#9B9B9B", "#FFA070", "#C67300", "#7F551B", "#41CCE3", "#09B09C", "#3655D7",   "#6321A0", "#E796C6", "#DD2DAA"]    return (      <div className='form-horizontal'> @@ -134,8 +134,4 @@ Metas.propTypes = {    onUpdateColor: PropTypes.func.isRequired,    onSelect2Tags: PropTypes.func.isRequired,    onUnselect2Tags: PropTypes.func.isRequired -} - -Metas.contextTypes = { -  I18n: PropTypes.object -} +}
\ No newline at end of file diff --git a/app/javascript/time_tables/components/PeriodForm.js b/app/javascript/time_tables/components/PeriodForm.js index d17a246f7..36ed6cfdf 100644 --- a/app/javascript/time_tables/components/PeriodForm.js +++ b/app/javascript/time_tables/components/PeriodForm.js @@ -33,7 +33,7 @@ const makeYearsOptions = (yearSelected) => {    return arr  } -export default function PeriodForm({modal, timetable, metas, onOpenAddPeriodForm, onClosePeriodForm, onUpdatePeriodForm, onValidatePeriodForm}, {I18n}) { +export default function PeriodForm({modal, timetable, metas, onOpenAddPeriodForm, onClosePeriodForm, onUpdatePeriodForm, onValidatePeriodForm}) {    return (      <div className="container-fluid">        <div className="row"> @@ -143,8 +143,4 @@ PeriodForm.propTypes = {    onUpdatePeriodForm: PropTypes.func.isRequired,    onValidatePeriodForm: PropTypes.func.isRequired,    timetable: PropTypes.object.isRequired -} - -PeriodForm.contextTypes = { -  I18n: PropTypes.object -} +}
\ No newline at end of file diff --git a/app/javascript/time_tables/components/PeriodManager.js b/app/javascript/time_tables/components/PeriodManager.js index 6b817fe73..6871d0b9b 100644 --- a/app/javascript/time_tables/components/PeriodManager.js +++ b/app/javascript/time_tables/components/PeriodManager.js @@ -55,7 +55,7 @@ export default class PeriodManager extends Component {                  type='button'                  onClick={() => this.props.onOpenEditPeriodForm(this.props.value, this.props.index)}                > -                Modifier +                {I18n.t('actions.edit')}                </button>              </li>              <li className='delete-action'> @@ -64,7 +64,7 @@ export default class PeriodManager extends Component {                  onClick={() => this.props.onDeletePeriod(this.props.index, this.props.metas.day_types)}                >                  <span className='fa fa-trash'></span> -                Supprimer +                {I18n.t('actions.destroy')}                </button>              </li>            </ul> @@ -79,8 +79,4 @@ PeriodManager.propTypes = {    currentDate: PropTypes.object.isRequired,    onDeletePeriod: PropTypes.func.isRequired,    onOpenEditPeriodForm: PropTypes.func.isRequired -} - -PeriodManager.contextTypes = { -  I18n: PropTypes.object  }
\ No newline at end of file diff --git a/app/javascript/time_tables/components/SaveTimetable.js b/app/javascript/time_tables/components/SaveTimetable.js index 704590abd..468f1773b 100644 --- a/app/javascript/time_tables/components/SaveTimetable.js +++ b/app/javascript/time_tables/components/SaveTimetable.js @@ -26,7 +26,7 @@ export default class SaveTimetable extends Component{                  }                }}              > -              Valider +              {I18n.t('actions.submit')}              </button>            </form>          </div> diff --git a/app/javascript/time_tables/components/TagsSelect2.js b/app/javascript/time_tables/components/TagsSelect2.js index 43cf59fdf..dd8d6e9c0 100644 --- a/app/javascript/time_tables/components/TagsSelect2.js +++ b/app/javascript/time_tables/components/TagsSelect2.js @@ -40,7 +40,7 @@ export default class TagsSelect2 extends Component {            allowClear: true,            theme: 'bootstrap',            width: '100%', -          placeholder: this.context.I18n.t('time_tables.edit.select2.tag.placeholder'), +          placeholder: I18n.t('time_tables.edit.select2.tag.placeholder'),            ajax: {              url: origin + path + '/tags.json',              dataType: 'json', @@ -74,8 +74,4 @@ export default class TagsSelect2 extends Component {  const formatRepo = (props) => {    if(props.name) return props.name -} - -TagsSelect2.contextTypes = { -  I18n: PropTypes.object  }
\ No newline at end of file diff --git a/app/javascript/time_tables/components/Timetable.js b/app/javascript/time_tables/components/Timetable.js index 991f31435..3779fa2d0 100644 --- a/app/javascript/time_tables/components/Timetable.js +++ b/app/javascript/time_tables/components/Timetable.js @@ -31,11 +31,11 @@ export default class Timetable extends Component {          <div className="table table-2entries mb-sm">            <div className="t2e-head w20">              <div className="th"> -              <div className="strong">{this.context.I18n.t('time_tables.edit.synthesis')}</div> +              <div className="strong">{I18n.t('time_tables.edit.synthesis')}</div>              </div> -            <div className="td"><span>{this.context.I18n.t('time_tables.edit.day_types')}</span></div> -            <div className="td"><span>{this.context.I18n.t('time_tables.edit.periods')}</span></div> -            <div className="td"><span>{this.context.I18n.t('time_tables.edit.exceptions')}</span></div> +            <div className="td"><span>{I18n.t('time_tables.edit.day_types')}</span></div> +            <div className="td"><span>{I18n.t('time_tables.edit.periods')}</span></div> +            <div className="td"><span>{I18n.t('time_tables.edit.exceptions')}</span></div>            </div>            <div className="t2e-item-list w80">              <div> @@ -109,8 +109,4 @@ Timetable.propTypes = {    onDeletePeriod: PropTypes.func.isRequired,    onExcludeDateFromPeriod: PropTypes.func.isRequired,    onIncludeDateInPeriod: PropTypes.func.isRequired -} - -Timetable.contextTypes = { -  I18n: PropTypes.object -} +}
\ No newline at end of file diff --git a/app/javascript/time_tables/containers/App.js b/app/javascript/time_tables/containers/App.js index 5963f8f1d..c12e33ef5 100644 --- a/app/javascript/time_tables/containers/App.js +++ b/app/javascript/time_tables/containers/App.js @@ -10,7 +10,6 @@ import SaveTimetable from './SaveTimetable'  import ConfirmModal from './ConfirmModal'  import ErrorModal from './ErrorModal'  import clone from '../../helpers/clone' -const I18n = clone(window, "I18n", true)  class App extends Component {    componentDidMount(){ diff --git a/app/javascript/vehicle_journeys/actions/index.js b/app/javascript/vehicle_journeys/actions/index.js index e00e9b1b0..d51012cdb 100644 --- a/app/javascript/vehicle_journeys/actions/index.js +++ b/app/javascript/vehicle_journeys/actions/index.js @@ -1,9 +1,3 @@ -import Promise from 'promise-polyfill' - -// To add to window -if (!window.Promise) { -  window.Promise = Promise; -}  import { batchActions } from '../batch'  const actions = { @@ -113,14 +107,9 @@ const actions = {      type : 'EDIT_PURCHASE_WINDOWS_VEHICLEJOURNEY_MODAL',      vehicleJourneys    }), -  selectPurchaseWindowsModal: (selectedWindow) =>({ +  selectPurchaseWindowsModal: (selectedItem) =>({      type: 'SELECT_PURCHASE_WINDOW_MODAL', -    selectedItem:{ -      id: selectedWindow.id, -      name: selectedWindow.name, -      color: selectedWindow.color, -      objectid: selectedWindow.objectid -    } +    selectedItem    }),    addSelectedPurchaseWindow: () => ({      type: 'ADD_SELECTED_PURCHASE_WINDOW' @@ -344,16 +333,23 @@ const actions = {          if(hasError == true) {            dispatch(actions.unavailableServer())          } else { -          let val -          for (val of json.vehicle_journeys){ +          let i = 0 +          while(i < json.vehicle_journeys.length){ +            let val = json.vehicle_journeys[i] +            i++              var timeTables = []              var purchaseWindows = [] -            let tt -            for (tt of val.time_tables){ +            let k = 0 +            while(k < val.time_tables.length){ +              let tt = val.time_tables[k] +              k++                timeTables.push(tt)              }              if(val.purchase_windows){ -              for (tt of val.purchase_windows){ +              k = 0 +              while(k < val.purchase_windows.length){ +                let tt = val.purchase_windows[k] +                k++                  purchaseWindows.push(tt)                }              } diff --git a/app/javascript/vehicle_journeys/components/ConfirmModal.js b/app/javascript/vehicle_journeys/components/ConfirmModal.js index 75e8a3932..330b4e02f 100644 --- a/app/javascript/vehicle_journeys/components/ConfirmModal.js +++ b/app/javascript/vehicle_journeys/components/ConfirmModal.js @@ -16,7 +16,7 @@ export default function ConfirmModal({dispatch, modal, onModalAccept, onModalCan                type='button'                onClick={() => { onModalCancel(modal.confirmModal.callback) }}              > -              Ne pas valider +              {I18n.t('cancel')}            </button>              <button                className='btn btn-danger' @@ -24,7 +24,7 @@ export default function ConfirmModal({dispatch, modal, onModalAccept, onModalCan                type='button'                onClick={() => { onModalAccept(modal.confirmModal.callback, vehicleJourneys) }}              > -              Valider +              {I18n.t('actions.submit')}            </button>            </div>          </div> diff --git a/app/javascript/vehicle_journeys/components/Filters.js b/app/javascript/vehicle_journeys/components/Filters.js index ae3ab3476..93fe015a8 100644 --- a/app/javascript/vehicle_journeys/components/Filters.js +++ b/app/javascript/vehicle_journeys/components/Filters.js @@ -145,12 +145,12 @@ export default function Filters({filters, pagination, missions, onFilter, onRese              <span                className='btn btn-link'                onClick={(e) => onResetFilters(e, pagination)}> -              Effacer +              {I18n.t('actions.erase')}              </span>              <span                className='btn btn-default'                onClick={(e) => onFilter(e, pagination)}> -              Filtrer +              {I18n.t('actions.filter')}              </span>            </div>          </div> diff --git a/app/javascript/vehicle_journeys/components/Navigate.js b/app/javascript/vehicle_journeys/components/Navigate.js index 24843babc..e79823e49 100644 --- a/app/javascript/vehicle_journeys/components/Navigate.js +++ b/app/javascript/vehicle_journeys/components/Navigate.js @@ -1,5 +1,6 @@  import React, { Component } from 'react'  import PropTypes from 'prop-types' +import capitalize from 'lodash/capitalize'  import actions from'../actions'  export default function Navigate({ dispatch, vehicleJourneys, pagination, status, filters}) { @@ -17,7 +18,7 @@ export default function Navigate({ dispatch, vehicleJourneys, pagination, status    if(status.fetchSuccess == true) {      return (        <div className="pagination"> -        {I18n.t("vehicle_journeys.vehicle_journeys_matrix.pagination", {minVJ, maxVJ, total:pagination.totalCount})} +        {I18n.t('will_paginate.page_entries_info.multi_page', { model: capitalize(I18n.model_name('vehicle_journey', { plural: true })), from: minVJ, to: maxVJ, count: pagination.totalCount })}          <form className='page_links' onSubmit={e => {e.preventDefault()}}>            <button              onClick={e => { diff --git a/app/javascript/vehicle_journeys/components/VehicleJourneys.js b/app/javascript/vehicle_journeys/components/VehicleJourneys.js index c6f59ce9d..e4f5ad11c 100644 --- a/app/javascript/vehicle_journeys/components/VehicleJourneys.js +++ b/app/javascript/vehicle_journeys/components/VehicleJourneys.js @@ -87,16 +87,18 @@ export default class VehicleJourneys extends Component {    }    toggleTimetables(e) { -    $('.table-2entries .detailed-timetables').toggleClass('hidden') -    $('.table-2entries .detailed-timetables-bt').toggleClass('active') +    let root = $(this.refs['vehicleJourneys']) +    root.find('.table-2entries .detailed-timetables').toggleClass('hidden') +    root.find('.table-2entries .detailed-timetables-bt').toggleClass('active')      this.componentDidUpdate()      e.preventDefault()      false    }    togglePurchaseWindows(e) { -    $('.table-2entries .detailed-purchase-windows').toggleClass('hidden') -    $('.table-2entries .detailed-purchase-windows-bt').toggleClass('active') +    let root = $(this.refs['vehicleJourneys']) +    root.find('.table-2entries .detailed-purchase-windows').toggleClass('hidden') +    root.find('.table-2entries .detailed-purchase-windows-bt').toggleClass('active')      this.componentDidUpdate()      e.preventDefault()      false @@ -186,7 +188,7 @@ export default class VehicleJourneys extends Component {        )      } else {        return ( -        <div className='row'> +        <div className='row' ref='vehicleJourneys'>            <div className='col-lg-12'>              {(this.props.status.fetchSuccess == false) && (                <div className='alert alert-danger mt-sm'> diff --git a/app/javascript/vehicle_journeys/components/tools/CreateModal.js b/app/javascript/vehicle_journeys/components/tools/CreateModal.js index a60429765..f49b51f08 100644 --- a/app/javascript/vehicle_journeys/components/tools/CreateModal.js +++ b/app/javascript/vehicle_journeys/components/tools/CreateModal.js @@ -47,7 +47,7 @@ export default class CreateModal extends Component {                <div className='modal-dialog'>                  <div className='modal-content'>                    <div className='modal-header'> -                    <h4 className='modal-title'>Ajouter une course</h4> +                    <h4 className='modal-title'>{I18n.t('vehicle_journeys.actions.new')}</h4>                      <span type="button" className="close modal-close" data-dismiss="modal">×</span>                    </div> @@ -57,7 +57,7 @@ export default class CreateModal extends Component {                          <div className='row'>                            <div className='col-lg-6 col-md-6 col-sm-6 col-xs-12'>                              <div className='form-group'> -                              <label className='control-label'>Nom de la course</label> +                              <label className='control-label'>{I18n.attribute_name('vehicle_journey', 'journey_name')}</label>                                <input                                  type='text'                                  ref='published_journey_name' @@ -68,7 +68,7 @@ export default class CreateModal extends Component {                            </div>                            <div className='col-lg-6 col-md-6 col-sm-6 col-xs-12'>                              <div className='form-group'> -                              <label className='control-label'>Nom du transporteur</label> +                              <label className='control-label'>{I18n.attribute_name('vehicle_journey', 'company_name')}</label>                                <CompanySelect2                                  company = {this.props.modal.modalProps.vehicleJourney && this.props.modal.modalProps.vehicleJourney.company || undefined}                                  onSelect2Company = {(e) => this.props.onSelect2Company(e)} @@ -78,7 +78,7 @@ export default class CreateModal extends Component {                            </div>                            <div className='col-lg-6 col-md-6 col-sm-6 col-xs-12'>                              <div className='form-group'> -                              <label className='control-label is-required'>Nom public de la mission</label> +                              <label className='control-label is-required'>{I18n.attribute_name('vehicle_journey', 'journey_pattern_published_name')}</label>                                <MissionSelect2                                  selection={this.props.modal.modalProps}                                  onSelect2JourneyPattern={this.props.onSelect2JourneyPattern} @@ -89,7 +89,7 @@ export default class CreateModal extends Component {                            </div>                            <div className='col-lg-6 col-md-6 col-sm-6 col-xs-12'>                              <div className='form-group'> -                              <label className='control-label'>Numéro de train</label> +                              <label className='control-label'>{I18n.attribute_name('vehicle_journey', 'published_journey_identifier')}</label>                                <input                                  type='text'                                  ref='published_journey_identifier' @@ -105,7 +105,7 @@ export default class CreateModal extends Component {                            />                            { this.props.modal.modalProps.selectedJPModal && this.props.modal.modalProps.selectedJPModal.full_schedule && <div className='col-lg-6 col-md-6 col-sm-6 col-xs-12'>                                <div className='form-group'> -                                <label className='control-label'>Heure de départ</label> +                              <label className='control-label'>{I18n.attribute_name('vehicle_journey', 'start_time')}</label>                                  <div className='input-group time'>                                    <input                                      type='number' @@ -142,14 +142,14 @@ export default class CreateModal extends Component {                            type='button'                            onClick={this.props.onModalClose}                            > -                          Annuler +                          {I18n.t('cancel')}                          </button>                          <button                            className='btn btn-primary'                            type='button'                            onClick={this.handleSubmit.bind(this)}                            > -                          Valider +                          {I18n.t('actions.submit')}                          </button>                        </div>                      </form> diff --git a/app/javascript/vehicle_journeys/components/tools/CustomFieldsInputs.js b/app/javascript/vehicle_journeys/components/tools/CustomFieldsInputs.js index 90d72a801..827c36b76 100644 --- a/app/javascript/vehicle_journeys/components/tools/CustomFieldsInputs.js +++ b/app/javascript/vehicle_journeys/components/tools/CustomFieldsInputs.js @@ -27,6 +27,19 @@ export default class CustomFieldsInputs extends Component {      )    } +  stringInput(cf){ +    return( +      <input +        type='text' +        ref={'custom_fields.' + cf.code} +        className='form-control' +        disabled={this.props.disabled} +        defaultValue={cf.value} +        onChange={(e) => this.props.onUpdate(cf.code, e.target.value) } +        /> +    ) +  } +    render() {      return (        <div> diff --git a/app/javascript/vehicle_journeys/components/tools/DeleteVehicleJourneys.js b/app/javascript/vehicle_journeys/components/tools/DeleteVehicleJourneys.js index 4815003d3..b1ce3786b 100644 --- a/app/javascript/vehicle_journeys/components/tools/DeleteVehicleJourneys.js +++ b/app/javascript/vehicle_journeys/components/tools/DeleteVehicleJourneys.js @@ -13,7 +13,7 @@ export default function DeleteVehicleJourneys({onDeleteVehicleJourneys, vehicleJ            e.preventDefault()            onDeleteVehicleJourneys()          }} -        title='Supprimer' +        title={ I18n.t('actions.delete') }        >          <span className='fa fa-trash'></span>        </button> diff --git a/app/javascript/vehicle_journeys/components/tools/DuplicateVehicleJourney.js b/app/javascript/vehicle_journeys/components/tools/DuplicateVehicleJourney.js index 102a87d85..d7e48bf08 100644 --- a/app/javascript/vehicle_journeys/components/tools/DuplicateVehicleJourney.js +++ b/app/javascript/vehicle_journeys/components/tools/DuplicateVehicleJourney.js @@ -93,7 +93,7 @@ export default class DuplicateVehicleJourney extends Component {                  <div className='modal-content'>                    <div className='modal-header'>                      <h4 className='modal-title'> -                      Dupliquer { actions.getSelected(this.props.vehicleJourneys).length > 1 ? 'plusieurs courses' : 'une course' } +                      {I18n.t('vehicle_journeys.vehicle_journeys_matrix.duplicate', { count: actions.getSelected(this.props.vehicleJourneys).length })}                      </h4>                      <span type="button" className="close modal-close" data-dismiss="modal">×</span>                    </div> @@ -102,7 +102,7 @@ export default class DuplicateVehicleJourney extends Component {                      <form className='form-horizontal'>                        <div className='modal-body'>                          <div className={'form-group ' + (actions.getSelected(this.props.vehicleJourneys).length > 1 ? 'hidden' : '' )}> -                          <label className='control-label is-required col-sm-8'>Horaire de départ indicatif</label> +                          <label className='control-label is-required col-sm-8'>{I18n.t('vehicle_journeys.vehicle_journeys_matrix.duplicate.start_time')}</label>                            <span className="col-sm-4">                              <span className={'input-group time' + (actions.getSelected(this.props.vehicleJourneys).length > 1 ? ' disabled' : '')}>                                <input @@ -133,7 +133,7 @@ export default class DuplicateVehicleJourney extends Component {                          </div>                          <div className='form-group'> -                          <label className='control-label is-required col-sm-8'>Nombre de courses à créer et dupliquer</label> +                          <label className='control-label is-required col-sm-8'>{I18n.t('vehicle_journeys.vehicle_journeys_matrix.duplicate.number')}</label>                            <div className="col-sm-4">                              <input                                type='number' @@ -152,7 +152,7 @@ export default class DuplicateVehicleJourney extends Component {                          </div>                          <div className='form-group'> -                          <label className='control-label is-required col-sm-8'>Décalage à partir duquel on créé les courses</label> +                          <label className='control-label is-required col-sm-8'>{I18n.t('vehicle_journeys.vehicle_journeys_matrix.duplicate.delta')}</label>                            <span className="col-sm-4">                              <input                                type='number' @@ -178,7 +178,7 @@ export default class DuplicateVehicleJourney extends Component {                            type='button'                            onClick={this.props.onModalClose}                            > -                          Annuler +                          {I18n.t('cancel')}                          </button>                          <button                            className={'btn btn-primary ' + (this.state.additional_time == 0 && this.state.originalDT.hour == this.state.duplicate_time_hh && this.state.originalDT.minute == this.state.duplicate_time_mm ? 'disabled' : '')} @@ -186,7 +186,7 @@ export default class DuplicateVehicleJourney extends Component {                            onClick={this.handleSubmit}                            disabled={this.disableValidateButton()}                            > -                          Valider +                          {I18n.t('actions.submit')}                          </button>                        </div>                      </form> diff --git a/app/javascript/vehicle_journeys/components/tools/EditVehicleJourney.js b/app/javascript/vehicle_journeys/components/tools/EditVehicleJourney.js index d3c01f154..e4e266c79 100644 --- a/app/javascript/vehicle_journeys/components/tools/EditVehicleJourney.js +++ b/app/javascript/vehicle_journeys/components/tools/EditVehicleJourney.js @@ -96,7 +96,7 @@ export default class EditVehicleJourney extends Component {                          <div className='row'>                            <div className='col-lg-6 col-md-6 col-sm-6 col-xs-12'>                              <div className='form-group'> -                              <label className='control-label'>{I18n.attribute_name('vehicle_journey', 'company')}</label> +                              <label className='control-label'>{I18n.attribute_name('vehicle_journey', 'published_journey_identifier')}</label>                                <input                                  type='text'                                  ref='published_journey_identifier' @@ -173,14 +173,14 @@ export default class EditVehicleJourney extends Component {                              type='button'                              onClick={this.props.onModalClose}                            > -                            Annuler +                            {I18n.t('cancel')}                          </button>                            <button                              className='btn btn-primary'                              type='button'                              onClick={this.handleSubmit.bind(this)}                            > -                            Valider +                            {I18n.t('actions.submit')}                          </button>                          </div>                        } diff --git a/app/javascript/vehicle_journeys/components/tools/NotesEditVehicleJourney.js b/app/javascript/vehicle_journeys/components/tools/NotesEditVehicleJourney.js index 880542216..5d300f70c 100644 --- a/app/javascript/vehicle_journeys/components/tools/NotesEditVehicleJourney.js +++ b/app/javascript/vehicle_journeys/components/tools/NotesEditVehicleJourney.js @@ -43,11 +43,11 @@ export default class NotesEditVehicleJourney extends Component {    renderAssociatedFN() {      if (this.footnotes().associated.length == 0) { -      return <h3>Aucune note associée</h3> +      return <h3>{I18n.t('vehicle_journeys.vehicle_journeys_matrix.no_associated_footnotes')}</h3>      } else {        return (          <div> -          <h3>Notes associées :</h3> +          <h3>{I18n.t('vehicle_journeys.form.purchase_windows')} :</h3>            {this.footnotes().associated.map((lf, i) =>              <div                key={i} @@ -68,13 +68,13 @@ export default class NotesEditVehicleJourney extends Component {    }    renderToAssociateFN() { -    if (window.line_footnotes.length == 0) return <h3>La ligne ne possède pas de notes</h3> +    if (window.line_footnotes.length == 0) return <h3>{I18n.t('vehicle_journeys.vehicle_journeys_matrix.no_line_footnotes')}</h3>      if (this.footnotes().to_associate.length == 0) return false      return (        <div> -        <h3 className='mt-lg'>Sélectionnez les notes à associer à cette course :</h3> +        <h3 className='mt-lg'>{I18n.t('vehicle_journeys.vehicle_journeys_matrix.select_footnotes')} :</h3>          {this.footnotes().to_associate.map((lf, i) =>            <div key={i} className='panel panel-default'>              <div className='panel-heading'> @@ -111,7 +111,7 @@ export default class NotesEditVehicleJourney extends Component {                <div className='modal-dialog'>                  <div className='modal-content'>                    <div className='modal-header'> -                    <h4 className='modal-title'>Notes</h4> +                    <h4 className='modal-title'>{I18n.t('vehicle_journeys.form.footnotes')}</h4>                      <span type="button" className="close modal-close" data-dismiss="modal">×</span>                    </div> @@ -130,14 +130,14 @@ export default class NotesEditVehicleJourney extends Component {                              type='button'                              onClick={this.props.onModalClose}                            > -                            Annuler +                            {I18n.t('cancel')}                          </button>                            <button                              className='btn btn-primary'                              type='button'                              onClick={this.handleSubmit.bind(this)}                            > -                            Valider +                            {I18n.t('actions.submit')}                          </button>                          </div>                        } diff --git a/app/javascript/vehicle_journeys/components/tools/PurchaseWindowsEditVehicleJourney.js b/app/javascript/vehicle_journeys/components/tools/PurchaseWindowsEditVehicleJourney.js index ce9a4cde9..30c511302 100644 --- a/app/javascript/vehicle_journeys/components/tools/PurchaseWindowsEditVehicleJourney.js +++ b/app/javascript/vehicle_journeys/components/tools/PurchaseWindowsEditVehicleJourney.js @@ -44,7 +44,7 @@ export default class PurchaseWindowsEditVehicleJourney extends Component {                <div className='modal-dialog'>                  <div className='modal-content'>                    <div className='modal-header'> -                    <h4 className='modal-title'>Calendriers commerciaux associés</h4> +                    <h4 className='modal-title'>{I18n.t('vehicle_journeys.form.purchase_windows')}s</h4>                      <span type="button" className="close modal-close" data-dismiss="modal">×</span>                    </div> @@ -58,7 +58,7 @@ export default class PurchaseWindowsEditVehicleJourney extends Component {                                  <div className='wrapper'>                                    <div>                                      <div className='form-group'> -                                      <label className='control-label'>{this.props.modal.modalProps.purchase_windows.length == 0 ? "Aucun calendrier commercial associé" : "Calendriers commerciaux associés"}</label> +                                      <label className='control-label'>{this.props.modal.modalProps.purchase_windows.length == 0 ? I18n.t('vehicle_journeys.vehicle_journeys_matrix.no_associated_purchase_windows') : I18n.t('vehicle_journeys.form.purchase_windows')}</label>                                      </div>                                    </div>                                    <div></div> @@ -117,14 +117,14 @@ export default class PurchaseWindowsEditVehicleJourney extends Component {                              type='button'                              onClick={this.props.onModalClose}                            > -                            Annuler +                            {I18n.t('cancel')}                            </button>                            <button                              className='btn btn-primary'                              type='button'                              onClick={this.handleSubmit}                            > -                            Valider +                            {I18n.t('actions.submit')}                            </button>                          </div>                        } diff --git a/app/javascript/vehicle_journeys/components/tools/ShiftVehicleJourney.js b/app/javascript/vehicle_journeys/components/tools/ShiftVehicleJourney.js index 6574bfa2d..bc3d8db34 100644 --- a/app/javascript/vehicle_journeys/components/tools/ShiftVehicleJourney.js +++ b/app/javascript/vehicle_journeys/components/tools/ShiftVehicleJourney.js @@ -27,6 +27,7 @@ export default class ShiftVehicleJourney extends Component {    }    render() { +    let id = this.props.modal.type == 'shift' && actions.getSelected(this.props.vehicleJourneys)[0].short_id      if(this.props.status.isFetching == true) {        return false      } @@ -48,10 +49,7 @@ export default class ShiftVehicleJourney extends Component {                <div className='modal-dialog'>                  <div className='modal-content'>                    <div className='modal-header'> -                    <h4 className='modal-title'>Mettre à jour une course</h4> -                    {(this.props.modal.type == 'shift') && ( -                      <em>Mettre à jour les horaires de la course {actions.getSelected(this.props.vehicleJourneys)[0].short_id}</em> -                    )} +                    <h4 className='modal-title'>{I18n.t('vehicle_journeys.form.slide_title', {id: id})}</h4>                      <span type="button" className="close modal-close" data-dismiss="modal">×</span>                    </div> @@ -61,7 +59,7 @@ export default class ShiftVehicleJourney extends Component {                          <div className='row'>                            <div className='col-lg-4 col-lg-offset-4 col-md-4 col-md-offset-4 col-sm-4 col-sm-offset-4 col-xs-12'>                              <div className='form-group'> -                              <label className='control-label is-required'>Avec un décalage de</label> +                              <label className='control-label is-required'>{I18n.t('vehicle_journeys.form.slide_delta')}</label>                                <input                                  type='number'                                  style={{'width': 104}} @@ -85,14 +83,14 @@ export default class ShiftVehicleJourney extends Component {                            type='button'                            onClick={this.props.onModalClose}                            > -                          Annuler +                          {I18n.t('cancel')}                          </button>                          <button                            className={'btn btn-primary ' + (this.state.additional_time == 0 ? 'disabled' : '')}                            type='button'                            onClick={this.handleSubmit.bind(this)}                            > -                          Valider +                          {I18n.t('actions.submit')}                          </button>                        </div>                      </form> diff --git a/app/javascript/vehicle_journeys/components/tools/TimetablesEditVehicleJourney.js b/app/javascript/vehicle_journeys/components/tools/TimetablesEditVehicleJourney.js index f21480563..9ee2e1849 100644 --- a/app/javascript/vehicle_journeys/components/tools/TimetablesEditVehicleJourney.js +++ b/app/javascript/vehicle_journeys/components/tools/TimetablesEditVehicleJourney.js @@ -44,7 +44,7 @@ export default class TimetablesEditVehicleJourney extends Component {                <div className='modal-dialog'>                  <div className='modal-content'>                    <div className='modal-header'> -                    <h4 className='modal-title'>Calendriers associés</h4> +                    <h4 className='modal-title'>{I18n.t('vehicle_journeys.form.time_tables')}</h4>                      <span type="button" className="close modal-close" data-dismiss="modal">×</span>                    </div> @@ -58,7 +58,7 @@ export default class TimetablesEditVehicleJourney extends Component {                                  <div className='wrapper'>                                    <div>                                      <div className='form-group'> -                                      <label className='control-label'>{this.props.modal.modalProps.timetables.length == 0 ? "Aucun calendrier associé" : "Calendriers associés"}</label> +                                      <label className='control-label'>{this.props.modal.modalProps.timetables.length == 0 ? I18n.t('vehicle_journeys.vehicle_journeys_matrix.no_associated_timetables'): I18n.t('vehicle_journeys.form.time_tables')}</label>                                      </div>                                    </div>                                    <div></div> @@ -119,14 +119,14 @@ export default class TimetablesEditVehicleJourney extends Component {                              type='button'                              onClick={this.props.onModalClose}                            > -                            Annuler +                            {I18n.t('cancel')}                            </button>                            <button                              className='btn btn-primary'                              type='button'                              onClick={this.handleSubmit}                            > -                            Valider +                            {I18n.t('actions.submit')}                            </button>                          </div>                        } diff --git a/app/javascript/vehicle_journeys/components/tools/select2s/CompanySelect2.js b/app/javascript/vehicle_journeys/components/tools/select2s/CompanySelect2.js index 5c7f75d99..60ad439b8 100644 --- a/app/javascript/vehicle_journeys/components/tools/select2s/CompanySelect2.js +++ b/app/javascript/vehicle_journeys/components/tools/select2s/CompanySelect2.js @@ -16,6 +16,7 @@ export default class BSelect4 extends Component {    }    render() { +    let placeHolder = I18n.t('')      return (        <Select2          data={(this.props.company) ? [this.props.company.name] : undefined} @@ -29,8 +30,8 @@ export default class BSelect4 extends Component {            allowClear: true,            theme: 'bootstrap',            width: '100%', -          placeholder: 'Filtrer par transporteur...', -          language: require('./fr'), +          placeholder: I18n.t('vehicle_journeys.vehicle_journeys_matrix.affect_company'), +          language: require('./language'),            ajax: {              url: origin + path + '/companies.json' + '?line_id=' + line,              dataType: 'json', diff --git a/app/javascript/vehicle_journeys/components/tools/select2s/MissionSelect2.js b/app/javascript/vehicle_journeys/components/tools/select2s/MissionSelect2.js index 72dbd0152..cec39ab4e 100644 --- a/app/javascript/vehicle_journeys/components/tools/select2s/MissionSelect2.js +++ b/app/javascript/vehicle_journeys/components/tools/select2s/MissionSelect2.js @@ -74,8 +74,8 @@ export default class BSelect4 extends Component {        width: '100%',        escapeMarkup: function (markup) { return markup; },        templateResult: formatRepo, -      placeholder: 'Filtrer par code, nom ou OID de mission...', -      language: require('./fr'), +      placeholder: I18n.t('vehicle_journeys.vehicle_journeys_matrix.filters.journey_pattern'), +      language: require('./language'),        allowClear: false,        escapeMarkup: function (markup) { return markup; },      } diff --git a/app/javascript/vehicle_journeys/components/tools/select2s/TimetableSelect2.js b/app/javascript/vehicle_journeys/components/tools/select2s/TimetableSelect2.js index 0339455ca..d5aad20d0 100644 --- a/app/javascript/vehicle_journeys/components/tools/select2s/TimetableSelect2.js +++ b/app/javascript/vehicle_journeys/components/tools/select2s/TimetableSelect2.js @@ -26,8 +26,8 @@ export default class BSelect4 extends Component {            allowClear: false,            theme: 'bootstrap',            width: '100%', -          placeholder: 'Filtrer par calendrier...', -          language: require('./fr'), +          placeholder: I18n.t('vehicle_journeys.vehicle_journeys_matrix.filters.timetable'), +          language: require('./language'),            ajax: {              url: origin + path + this.props.chunkURL,              dataType: 'json', diff --git a/app/javascript/vehicle_journeys/components/tools/select2s/VJSelect2.js b/app/javascript/vehicle_journeys/components/tools/select2s/VJSelect2.js index ccb4c9595..50a941b6d 100644 --- a/app/javascript/vehicle_journeys/components/tools/select2s/VJSelect2.js +++ b/app/javascript/vehicle_journeys/components/tools/select2s/VJSelect2.js @@ -25,9 +25,9 @@ export default class BSelect4b extends Component {          options={{            allowClear: false,            theme: 'bootstrap', -          placeholder: 'Filtrer par ID course...', +          placeholder: I18n.t('vehicle_journeys.vehicle_journeys_matrix.filters.id'),            width: '100%', -          language: require('./fr'), +          language: require('./language'),            ajax: {              url: origin + path + '/vehicle_journeys.json',              dataType: 'json', diff --git a/app/javascript/vehicle_journeys/components/tools/select2s/fr.js b/app/javascript/vehicle_journeys/components/tools/select2s/fr.js deleted file mode 100644 index 20154d412..000000000 --- a/app/javascript/vehicle_journeys/components/tools/select2s/fr.js +++ /dev/null @@ -1,9 +0,0 @@ -module.exports = { -  errorLoading:function(){return"Les résultats ne peuvent pas être chargés."}, -  inputTooLong:function(e){var t=e.input.length-e.maximum,n="Supprimez "+t+" caractère";return t!==1&&(n+="s"),n}, -  inputTooShort:function(e){var t=e.minimum-e.input.length,n="Saisissez "+t+" caractère";return t!==1&&(n+="s"),n}, -  loadingMore:function(){return"Chargement de résultats supplémentaires…"}, -  maximumSelected:function(e){var t="Vous pouvez seulement sélectionner "+e.maximum+" élément";return e.maximum!==1&&(t+="s"),t}, -  noResults:function(){return"Aucun résultat trouvé"}, -  searching:function(){return"Recherche en cours…"} -} diff --git a/app/javascript/vehicle_journeys/components/tools/select2s/language.js b/app/javascript/vehicle_journeys/components/tools/select2s/language.js new file mode 100644 index 000000000..9d587f96e --- /dev/null +++ b/app/javascript/vehicle_journeys/components/tools/select2s/language.js @@ -0,0 +1,9 @@ +module.exports = { +  errorLoading: () => I18n.t('select2.error_loading'), +  inputTooLong: (e) => I18n.t('select2.input_too_short', { count: e.input.length - e.maximum}), +  inputTooShort: (e) => I18n.t('select2.input_too_long', { count: e.minimum - e.input.length }), +  loadingMore: () => I18n.t('select2.loading_more'), +  maximumSelected: (e) => I18n.t('select2.maximum_selected', {count: e.maximum}), +  noResults: () => I18n.t('select2.no_results'), +  searching: () => I18n.t('select2.searching') +} diff --git a/app/javascript/vehicle_journeys/reducers/vehicleJourneys.js b/app/javascript/vehicle_journeys/reducers/vehicleJourneys.js index 6524f8d6f..b02c19a69 100644 --- a/app/javascript/vehicle_journeys/reducers/vehicleJourneys.js +++ b/app/javascript/vehicle_journeys/reducers/vehicleJourneys.js @@ -9,32 +9,35 @@ const vehicleJourney= (state = {}, action, keep) => {        return _.assign({}, state, {selected: false})      case 'ADD_VEHICLEJOURNEY':        let pristineVjasList = [] -      let prevSp = action.stopPointsList[0] +      let prevSp        let current_time = {          hour: 0,          minute: 0        }        let computeSchedule = false -      let userTZOffet = 0 +      let initTZOffet = 0        if(action.data["start_time.hour"] && action.data["start_time.hour"].value && action.data["start_time.hour"].value.length > 0 && action.data["start_time.minute"] && action.selectedJourneyPattern.full_schedule && action.selectedJourneyPattern.costs){          computeSchedule = true -        userTZOffet = action.data["tz_offset"] && parseInt(action.data["tz_offset"].value) || 0 -        current_time.hour = parseInt(action.data["start_time.hour"].value) + parseInt(userTZOffet / 60) +        initTZOffet = - action.stopPointsList[0].time_zone_offset / 60 || 0 +        current_time.hour = parseInt(action.data["start_time.hour"].value) + parseInt(initTZOffet / 60)          current_time.minute = 0          if(action.data["start_time.minute"].value){ -          current_time.minute = parseInt(action.data["start_time.minute"].value) + (userTZOffet - 60 * parseInt(userTZOffet / 60)) +          current_time.minute = parseInt(action.data["start_time.minute"].value) + (initTZOffet - 60 * parseInt(initTZOffet / 60))          }        }        _.each(action.stopPointsList, (sp) =>{          let inJourney = false          let newVjas          if(computeSchedule){ -          if(action.selectedJourneyPattern.costs[prevSp.stop_area_id + "-" + sp.stop_area_id]){ +          if(prevSp && action.selectedJourneyPattern.costs[prevSp.stop_area_id + "-" + sp.stop_area_id]){              let delta = parseInt(action.selectedJourneyPattern.costs[prevSp.stop_area_id + "-" + sp.stop_area_id].time)              current_time = actions.addMinutesToTime(current_time, delta)              prevSp = sp              inJourney = true            } +          if(!prevSp){ +            prevSp = sp +          }            let offsetHours = sp.time_zone_offset / 3600            let offsetminutes = sp.time_zone_offset/60 - 60*offsetHours            newVjas = { diff --git a/app/models/api/v1/api_key.rb b/app/models/api/v1/api_key.rb index 09c6f77ac..e6ceb977a 100644 --- a/app/models/api/v1/api_key.rb +++ b/app/models/api/v1/api_key.rb @@ -1,7 +1,8 @@  module Api    module V1 -    class ApiKey < ::ActiveRecord::Base -      has_paper_trail +    class ApiKey < ::ApplicationModel +      has_metadata +              before_create :generate_access_token        belongs_to :referential, :class_name => '::Referential'        belongs_to :organisation, :class_name => '::Organisation' @@ -47,4 +48,3 @@ module Api      end    end  end - diff --git a/app/models/application_model.rb b/app/models/application_model.rb new file mode 100644 index 000000000..1a2a5099d --- /dev/null +++ b/app/models/application_model.rb @@ -0,0 +1,5 @@ +class ApplicationModel < ::ActiveRecord::Base +  include MetadataSupport + +  self.abstract_class = true +end diff --git a/app/models/calendar.rb b/app/models/calendar.rb index 84b569ab4..39e2b2cff 100644 --- a/app/models/calendar.rb +++ b/app/models/calendar.rb @@ -2,18 +2,17 @@ require 'range_ext'  require_relative 'calendar/date_value'  require_relative 'calendar/period' -class Calendar < ActiveRecord::Base +class Calendar < ApplicationModel    include DateSupport    include PeriodSupport    include ApplicationDaysSupport    include TimetableSupport -  has_paper_trail class_name: 'PublicVersion' +  has_metadata    belongs_to :organisation    belongs_to :workgroup -  validates_presence_of :name, :short_name, :organisation, :workgroup -  validates_uniqueness_of :short_name +  validates_presence_of :name, :organisation, :workgroup    has_many :time_tables diff --git a/app/models/calendar/date_value.rb b/app/models/calendar/date_value.rb index a4a405d43..f50b4237c 100644 --- a/app/models/calendar/date_value.rb +++ b/app/models/calendar/date_value.rb @@ -1,4 +1,4 @@ -class Calendar < ActiveRecord::Base +class Calendar < ApplicationModel    class DateValue      include ActiveAttr::Model diff --git a/app/models/calendar/period.rb b/app/models/calendar/period.rb index 8b3e4109b..07926e818 100644 --- a/app/models/calendar/period.rb +++ b/app/models/calendar/period.rb @@ -1,4 +1,4 @@ -class Calendar < ActiveRecord::Base +class Calendar < ApplicationModel    class Period      include ActiveAttr::Model diff --git a/app/models/calendar_observer.rb b/app/models/calendar_observer.rb index c81addff4..0414d01d2 100644 --- a/app/models/calendar_observer.rb +++ b/app/models/calendar_observer.rb @@ -3,7 +3,7 @@ class CalendarObserver < ActiveRecord::Observer    def after_update calendar      return unless calendar.shared -    User.with_organisation.each do |user| +    User.from_workgroup(calendar.workgroup_id).each do |user|        MailerJob.perform_later('CalendarMailer', 'updated', [calendar.id, user.id])      end    end @@ -11,7 +11,7 @@ class CalendarObserver < ActiveRecord::Observer    def after_create calendar      return unless calendar.shared -    User.with_organisation.each do |user| +    User.from_workgroup(calendar.workgroup_id).each do |user|        MailerJob.perform_later('CalendarMailer', 'created', [calendar.id, user.id])      end    end diff --git a/app/models/chouette/access_link.rb b/app/models/chouette/access_link.rb index 6b08443be..7ab8ca715 100644 --- a/app/models/chouette/access_link.rb +++ b/app/models/chouette/access_link.rb @@ -1,6 +1,6 @@  module Chouette    class AccessLink < Chouette::TridentActiveRecord -    has_paper_trail +    has_metadata      include ObjectidSupport      attr_accessor :access_link_type, :link_orientation_type, :link_key diff --git a/app/models/chouette/access_point.rb b/app/models/chouette/access_point.rb index ac6580015..884460881 100644 --- a/app/models/chouette/access_point.rb +++ b/app/models/chouette/access_point.rb @@ -4,7 +4,7 @@ require 'geo_ruby'  module Chouette    class AccessPoint < Chouette::ActiveRecord -    has_paper_trail +    has_metadata      include Geokit::Mappable      include ProjectionFields diff --git a/app/models/chouette/active_record.rb b/app/models/chouette/active_record.rb index c2aab9d50..27f5426b3 100644 --- a/app/models/chouette/active_record.rb +++ b/app/models/chouette/active_record.rb @@ -1,7 +1,8 @@  #require "active_record"  require 'deep_cloneable'  module Chouette -  class ActiveRecord < ::ActiveRecord::Base +  class ActiveRecord < ::ApplicationModel +      self.abstract_class = true      before_save :nil_if_blank, :set_data_source_ref diff --git a/app/models/chouette/company.rb b/app/models/chouette/company.rb index 53e412600..9d5737a6c 100644 --- a/app/models/chouette/company.rb +++ b/app/models/chouette/company.rb @@ -1,13 +1,15 @@  module Chouette    class Company < Chouette::ActiveRecord +    has_metadata +      include CompanyRestrictions      include LineReferentialSupport      include ObjectidSupport -    has_paper_trail class_name: 'PublicVersion' +    include CustomFieldsSupport      has_many :lines -    validates_format_of :registration_number, :with => %r{\A[0-9A-Za-z_-]+\Z}, :allow_nil => true, :allow_blank => true +    # validates_format_of :registration_number, :with => %r{\A[0-9A-Za-z_-]+\Z}, :allow_nil => true, :allow_blank => true      validates_presence_of :name      validates_format_of :url, :with => %r{\Ahttps?:\/\/([\da-z\.-]+)\.([a-z\.]{2,6})([\/\w \.-]*)*\/?\Z}, :allow_nil => true, :allow_blank => true diff --git a/app/models/chouette/connection_link.rb b/app/models/chouette/connection_link.rb index c53d6f5f1..fb93e5f90 100644 --- a/app/models/chouette/connection_link.rb +++ b/app/models/chouette/connection_link.rb @@ -1,6 +1,6 @@  module Chouette    class ConnectionLink < Chouette::TridentActiveRecord -    has_paper_trail +    has_metadata      include ObjectidSupport      include ConnectionLinkRestrictions diff --git a/app/models/chouette/for_alighting_enumerations.rb b/app/models/chouette/for_alighting_enumerations.rb index ab07a670d..2e15fcb58 100644 --- a/app/models/chouette/for_alighting_enumerations.rb +++ b/app/models/chouette/for_alighting_enumerations.rb @@ -3,6 +3,6 @@ module Chouette      extend Enumerize      extend ActiveModel::Naming -    enumerize :for_alighting, in: %w[normal forbidden request_stop is_flexible] +    enumerize :for_alighting, in: %w[normal forbidden request_stop is_flexible], default: :normal    end  end diff --git a/app/models/chouette/for_boarding_enumerations.rb b/app/models/chouette/for_boarding_enumerations.rb index 48f8762c2..0190bf805 100644 --- a/app/models/chouette/for_boarding_enumerations.rb +++ b/app/models/chouette/for_boarding_enumerations.rb @@ -3,6 +3,6 @@ module Chouette      extend Enumerize      extend ActiveModel::Naming -    enumerize :for_boarding, in: %w[normal forbidden request_stop is_flexible] +    enumerize :for_boarding, in: %w[normal forbidden request_stop is_flexible], default: :normal    end  end diff --git a/app/models/chouette/group_of_line.rb b/app/models/chouette/group_of_line.rb index 3b6a7cea7..a30c34ce7 100644 --- a/app/models/chouette/group_of_line.rb +++ b/app/models/chouette/group_of_line.rb @@ -1,6 +1,6 @@  module Chouette    class GroupOfLine < Chouette::ActiveRecord -    has_paper_trail +    has_metadata      include ObjectidSupport      include GroupOfLineRestrictions      include LineReferentialSupport diff --git a/app/models/chouette/journey_pattern.rb b/app/models/chouette/journey_pattern.rb index 5a5132200..830a6a808 100644 --- a/app/models/chouette/journey_pattern.rb +++ b/app/models/chouette/journey_pattern.rb @@ -1,6 +1,6 @@  module Chouette    class JourneyPattern < Chouette::TridentActiveRecord -    has_paper_trail +    has_metadata      include ChecksumSupport      include JourneyPatternRestrictions      include ObjectidSupport diff --git a/app/models/chouette/line.rb b/app/models/chouette/line.rb index ae7c25377..4b5d1a68d 100644 --- a/app/models/chouette/line.rb +++ b/app/models/chouette/line.rb @@ -1,6 +1,6 @@  module Chouette    class Line < Chouette::ActiveRecord -    has_paper_trail class_name: 'PublicVersion' +    has_metadata      include LineRestrictions      include LineReferentialSupport      include ObjectidSupport @@ -29,7 +29,7 @@ module Chouette      # validates_presence_of :network      # validates_presence_of :company -    validates_format_of :registration_number, :with => %r{\A[\d\w_\-]+\Z}, :allow_nil => true, :allow_blank => true +    # validates_format_of :registration_number, :with => %r{\A[\d\w_\-]+\Z}, :allow_nil => true, :allow_blank => true      validates_format_of :stable_id, :with => %r{\A[\d\w_\-]+\Z}, :allow_nil => true, :allow_blank => true      validates_format_of :url, :with => %r{\Ahttps?:\/\/([\da-z\.-]+)\.([a-z\.]{2,6})([\/\w \.-]*)*\/?\Z}, :allow_nil => true, :allow_blank => true      validates_format_of :color, :with => %r{\A[0-9a-fA-F]{6}\Z}, :allow_nil => true, :allow_blank => true @@ -51,6 +51,14 @@ module Chouette          )      } +    scope :for_organisation, ->(organisation){ +      if objectids = organisation&.lines_scope +        where(objectid: objectids) +      else +        all +      end +    } +      def self.nullable_attributes        [:published_name, :number, :comment, :url, :color, :text_color, :stable_id]      end diff --git a/app/models/chouette/network.rb b/app/models/chouette/network.rb index 942fc5d67..4802d7592 100644 --- a/app/models/chouette/network.rb +++ b/app/models/chouette/network.rb @@ -1,6 +1,6 @@  module Chouette    class Network < Chouette::ActiveRecord -    has_paper_trail class_name: 'PublicVersion' +    has_metadata      include NetworkRestrictions      include LineReferentialSupport      include ObjectidSupport diff --git a/app/models/chouette/pt_link.rb b/app/models/chouette/pt_link.rb index 399539d44..680632a14 100644 --- a/app/models/chouette/pt_link.rb +++ b/app/models/chouette/pt_link.rb @@ -2,7 +2,7 @@ require 'geokit'  module Chouette    class PtLink < Chouette::ActiveRecord -    has_paper_trail +    has_metadata      include Geokit::Mappable      def geometry diff --git a/app/models/chouette/purchase_window.rb b/app/models/chouette/purchase_window.rb index 4c8014780..e10b106ec 100644 --- a/app/models/chouette/purchase_window.rb +++ b/app/models/chouette/purchase_window.rb @@ -11,7 +11,7 @@ module Chouette      enumerize :color, in: %w(#9B9B9B #FFA070 #C67300 #7F551B #41CCE3 #09B09C #3655D7 #6321A0 #E796C6 #DD2DAA) -    has_paper_trail +    has_metadata      belongs_to :referential      has_and_belongs_to_many :vehicle_journeys, :class_name => 'Chouette::VehicleJourney' diff --git a/app/models/chouette/route.rb b/app/models/chouette/route.rb index 13288bc6b..c5e909d7e 100644 --- a/app/models/chouette/route.rb +++ b/app/models/chouette/route.rb @@ -1,15 +1,25 @@  module Chouette    class Route < Chouette::TridentActiveRecord -    has_paper_trail +    has_metadata +      include RouteRestrictions      include ChecksumSupport      include ObjectidSupport      extend Enumerize +    if ENV["CHOUETTE_ROUTE_POSITION_CHECK"] == "true" || !Rails.env.production? +      after_commit do +        positions = stop_points.pluck(:position) +        Rails.logger.debug "Check positions in Route #{id} : #{positions.inspect}" +        if positions.size != positions.uniq.size +          raise "DUPLICATED stop_points positions in Route #{id} : #{positions.inspect}" +        end +      end +    end +      enumerize :direction, in: %i(straight_forward backward clockwise counter_clockwise north north_west west south_west south south_east east north_east)      enumerize :wayback, in: %i(outbound inbound), default: :outbound -      def self.nullable_attributes        [:published_name, :comment, :number, :name, :direction, :wayback]      end @@ -68,28 +78,36 @@ module Chouette      validates_presence_of :published_name      validates_presence_of :line      validates :wayback, inclusion: { in: self.wayback.values } +    after_commit :calculate_costs!, +      on: [:create, :update], +      if: ->() { TomTom.enabled? } -    after_save :calculate_costs!, if: ->() { TomTom.enabled? } - -    def duplicate +    def duplicate opposite=false        overrides = {          'opposite_route_id' => nil,          'name' => I18n.t('activerecord.copy', name: self.name)        } +      keys_for_create = attributes.keys - %w{id objectid created_at updated_at}        atts_for_create = attributes -        .slice!(*%w{id objectid created_at updated_at}) +        .slice(*keys_for_create)          .merge(overrides) +      if opposite +        atts_for_create[:wayback] = self.opposite_wayback +        atts_for_create[:name] = I18n.t('routes.opposite', name: self.name) +        atts_for_create[:published_name] = atts_for_create[:name] +        atts_for_create[:opposite_route_id] = self.id +      end        new_route = self.class.create!(atts_for_create) -      duplicate_stop_points(for_route: new_route) +      duplicate_stop_points(for_route: new_route, opposite: opposite)        new_route      end -    def duplicate_stop_points(for_route:) -      stop_points.each(&duplicate_stop_point(for_route: for_route)) +    def duplicate_stop_points(for_route:, opposite: false) +      stop_points.each(&duplicate_stop_point(for_route: for_route, opposite: opposite))      end -    def duplicate_stop_point(for_route:) +    def duplicate_stop_point(for_route:, opposite: false)        -> stop_point do -        stop_point.duplicate(for_route: for_route) +        stop_point.duplicate(for_route: for_route, opposite: opposite)        end      end diff --git a/app/models/chouette/routing_constraint_zone.rb b/app/models/chouette/routing_constraint_zone.rb index 58703598e..886eadc6c 100644 --- a/app/models/chouette/routing_constraint_zone.rb +++ b/app/models/chouette/routing_constraint_zone.rb @@ -1,6 +1,6 @@  module Chouette    class RoutingConstraintZone < Chouette::TridentActiveRecord -    has_paper_trail +    has_metadata      include ChecksumSupport      include ObjectidSupport diff --git a/app/models/chouette/stop_area.rb b/app/models/chouette/stop_area.rb index ccdff609f..4ddc7403b 100644 --- a/app/models/chouette/stop_area.rb +++ b/app/models/chouette/stop_area.rb @@ -2,11 +2,12 @@ require 'geokit'  require 'geo_ruby'  module Chouette    class StopArea < Chouette::ActiveRecord -    has_paper_trail class_name: 'PublicVersion' +    has_metadata      include ProjectionFields      include StopAreaRestrictions      include StopAreaReferentialSupport      include ObjectidSupport +    include CustomFieldsSupport      extend Enumerize      enumerize :area_type, in: Chouette::AreaType::ALL @@ -32,7 +33,7 @@ module Chouette      after_update :journey_patterns_control_route_sections,                  if: Proc.new { |stop_area| ['boarding_position', 'quay'].include? stop_area.stop_area_type } -    validates_format_of :registration_number, :with => %r{\A[\d\w_\-]+\Z}, :allow_blank => true +    # validates_format_of :registration_number, :with => %r{\A[\d\w_:\-]+\Z}, :allow_blank => true      validates_presence_of :name      validates_presence_of :kind      validates_presence_of :latitude, :if => :longitude @@ -49,7 +50,7 @@ module Chouette      validate :registration_number_is_set      before_validation do -      self.registration_number ||= self.stop_area_referential.generate_registration_number +      self.registration_number = self.stop_area_referential.generate_registration_number unless self.registration_number.present?      end      def self.nullable_attributes @@ -90,7 +91,7 @@ module Chouette        end        unless self.stop_area_referential.validates_registration_number(self.registration_number) -        errors.add(:registration_number, I18n.t('stop_areas.errors.registration_number.invalid')) +        errors.add(:registration_number, I18n.t('stop_areas.errors.registration_number.invalid', mask: self.stop_area_referential.registration_number_format))        end      end diff --git a/app/models/chouette/stop_point.rb b/app/models/chouette/stop_point.rb index 6b363cd93..1df1a664a 100644 --- a/app/models/chouette/stop_point.rb +++ b/app/models/chouette/stop_point.rb @@ -1,6 +1,6 @@  module Chouette    class StopPoint < Chouette::TridentActiveRecord -    has_paper_trail +    has_metadata      def self.policy_class        RoutePolicy      end @@ -39,11 +39,12 @@ module Chouette        end      end -    def duplicate(for_route:) +    def duplicate(for_route:, opposite: false)        keys_for_create = attributes.keys - %w{id objectid created_at updated_at}        atts_for_create = attributes          .slice(*keys_for_create)          .merge('route_id' => for_route.id) +      atts_for_create["position"] = self.route.stop_points.size - atts_for_create["position"] if opposite        self.class.create!(atts_for_create)      end diff --git a/app/models/chouette/time_table.rb b/app/models/chouette/time_table.rb index 506e498b8..b59c95665 100644 --- a/app/models/chouette/time_table.rb +++ b/app/models/chouette/time_table.rb @@ -1,6 +1,6 @@  module Chouette    class TimeTable < Chouette::TridentActiveRecord -    has_paper_trail +    has_metadata      include ChecksumSupport      include TimeTableRestrictions      include ObjectidSupport diff --git a/app/models/chouette/timeband.rb b/app/models/chouette/timeband.rb index 5a4e17b98..38260b755 100644 --- a/app/models/chouette/timeband.rb +++ b/app/models/chouette/timeband.rb @@ -9,7 +9,7 @@ module Chouette    class Timeband < Chouette::TridentActiveRecord      include ObjectidSupport -    has_paper_trail +    has_metadata      validates :start_time, :end_time, presence: true      validates_with Chouette::TimebandValidator diff --git a/app/models/chouette/vehicle_journey.rb b/app/models/chouette/vehicle_journey.rb index 525036077..c269d478e 100644 --- a/app/models/chouette/vehicle_journey.rb +++ b/app/models/chouette/vehicle_journey.rb @@ -1,7 +1,7 @@  # coding: utf-8  module Chouette    class VehicleJourney < Chouette::TridentActiveRecord -    has_paper_trail +    has_metadata      include ChecksumSupport      include CustomFieldsSupport      include VehicleJourneyRestrictions @@ -244,11 +244,13 @@ module Chouette      end      def self.state_update route, state +      objects = []        transaction do          state.each do |item|            item.delete('errors')            vj = find_by(objectid: item['objectid']) || state_create_instance(route, item)            next if item['deletable'] && vj.persisted? && vj.destroy +          objects << vj            if vj.state_update_vjas?(item['vehicle_journey_at_stops'])              vj.update_vjas_from_state(item['vehicle_journey_at_stops']) @@ -276,6 +278,7 @@ module Chouette          item['vehicle_journey_at_stops'].map {|vjas| vjas.delete('new_record') }        end        state.delete_if {|item| item['deletable']} +      objects      end      def self.state_create_instance route, item @@ -346,6 +349,33 @@ module Chouette        end      end +    def fill_passing_time_at_borders +      encountered_borders = [] +      previous_stop = nil +      vehicle_journey_at_stops.each do |vjas| +        sp = vjas.stop_point +        if sp.stop_area.area_type == "border" +          encountered_borders << vjas +        else +          if encountered_borders.any? +            before_cost = journey_pattern.costs_between previous_stop.stop_point, encountered_borders.first.stop_point +            after_cost = journey_pattern.costs_between encountered_borders.last.stop_point, sp +            if before_cost && before_cost[:distance] && after_cost && after_cost[:distance] +              before_distance = before_cost[:distance].to_f +              after_distance = after_cost[:distance].to_f +              time = previous_stop.departure_time + before_distance / (before_distance+after_distance) * (vjas.arrival_time - previous_stop.departure_time) +              encountered_borders.each do |b| +                b.update_attribute :arrival_time, time +                b.update_attribute :departure_time, time +              end +            end +            encountered_borders = [] +          end +          previous_stop = vjas +        end +      end +    end +      def self.matrix(vehicle_journeys)        Hash[*VehicleJourneyAtStop.where(vehicle_journey_id: vehicle_journeys.pluck(:id)).map do |vjas|          [ "#{vjas.vehicle_journey_id}-#{vjas.stop_point_id}", vjas] diff --git a/app/models/clean_up.rb b/app/models/clean_up.rb index 7aab7f32e..ec47489e9 100644 --- a/app/models/clean_up.rb +++ b/app/models/clean_up.rb @@ -1,4 +1,4 @@ -class CleanUp < ActiveRecord::Base +class CleanUp < ApplicationModel    extend Enumerize    include AASM    belongs_to :referential diff --git a/app/models/clean_up_result.rb b/app/models/clean_up_result.rb index 24d262deb..dff4f5acd 100644 --- a/app/models/clean_up_result.rb +++ b/app/models/clean_up_result.rb @@ -1,3 +1,3 @@ -class CleanUpResult < ActiveRecord::Base +class CleanUpResult < ApplicationModel    belongs_to :clean_up  end diff --git a/app/models/compliance_check.rb b/app/models/compliance_check.rb index 9d817e146..4ef6170e9 100644 --- a/app/models/compliance_check.rb +++ b/app/models/compliance_check.rb @@ -1,4 +1,4 @@ -class ComplianceCheck < ActiveRecord::Base +class ComplianceCheck < ApplicationModel    include ComplianceItemSupport    self.inheritance_column = nil diff --git a/app/models/compliance_check_block.rb b/app/models/compliance_check_block.rb index 059547e1b..e4f4c1c37 100644 --- a/app/models/compliance_check_block.rb +++ b/app/models/compliance_check_block.rb @@ -1,4 +1,4 @@ -class ComplianceCheckBlock < ActiveRecord::Base +class ComplianceCheckBlock < ApplicationModel    include StifTransportModeEnumerations    include StifTransportSubmodeEnumerations diff --git a/app/models/compliance_check_message.rb b/app/models/compliance_check_message.rb index 738bd4a4b..a4b1062f6 100644 --- a/app/models/compliance_check_message.rb +++ b/app/models/compliance_check_message.rb @@ -1,4 +1,4 @@ -class ComplianceCheckMessage < ActiveRecord::Base +class ComplianceCheckMessage < ApplicationModel    extend Enumerize    belongs_to :compliance_check_set diff --git a/app/models/compliance_check_resource.rb b/app/models/compliance_check_resource.rb index 777254aaf..d2f782e2b 100644 --- a/app/models/compliance_check_resource.rb +++ b/app/models/compliance_check_resource.rb @@ -1,4 +1,4 @@ -class ComplianceCheckResource < ActiveRecord::Base +class ComplianceCheckResource < ApplicationModel    extend Enumerize    belongs_to :compliance_check_set diff --git a/app/models/compliance_check_set.rb b/app/models/compliance_check_set.rb index 49d324c53..8b1dbdd68 100644 --- a/app/models/compliance_check_set.rb +++ b/app/models/compliance_check_set.rb @@ -1,6 +1,7 @@ -class ComplianceCheckSet < ActiveRecord::Base +class ComplianceCheckSet < ApplicationModel    extend Enumerize -  has_paper_trail class_name: 'PublicVersion' + +  has_metadata    belongs_to :referential    belongs_to :compliance_control_set diff --git a/app/models/compliance_control.rb b/app/models/compliance_control.rb index 537343005..672fb128c 100644 --- a/app/models/compliance_control.rb +++ b/app/models/compliance_control.rb @@ -1,4 +1,4 @@ -class ComplianceControl < ActiveRecord::Base +class ComplianceControl < ApplicationModel    include ComplianceItemSupport    class << self @@ -76,6 +76,7 @@ require_dependency 'generic_attribute_control/uniqueness'  require_dependency 'journey_pattern_control/duplicates'  require_dependency 'journey_pattern_control/vehicle_journey'  require_dependency 'line_control/route' +require_dependency 'line_control/lines_scope'  require_dependency 'route_control/duplicates'  require_dependency 'route_control/journey_pattern'  require_dependency 'route_control/minimum_length' diff --git a/app/models/compliance_control_block.rb b/app/models/compliance_control_block.rb index d7d84fd06..6a3c8a34e 100644 --- a/app/models/compliance_control_block.rb +++ b/app/models/compliance_control_block.rb @@ -1,4 +1,4 @@ -class ComplianceControlBlock < ActiveRecord::Base +class ComplianceControlBlock < ApplicationModel    include StifTransportModeEnumerations    include StifTransportSubmodeEnumerations @@ -12,6 +12,8 @@ class ComplianceControlBlock < ActiveRecord::Base    validates :transport_mode, presence: true    validates :compliance_control_set, presence: true +  validates_uniqueness_of :condition_attributes, scope: :compliance_control_set_id +    def name      ApplicationController.helpers.transport_mode_text(self)    end diff --git a/app/models/compliance_control_set.rb b/app/models/compliance_control_set.rb index c0ea692f2..4f0f86d08 100644 --- a/app/models/compliance_control_set.rb +++ b/app/models/compliance_control_set.rb @@ -1,5 +1,6 @@ -class ComplianceControlSet < ActiveRecord::Base -  has_paper_trail class_name: 'PublicVersion' +class ComplianceControlSet < ApplicationModel +  has_metadata +    belongs_to :organisation    has_many :compliance_control_blocks, dependent: :destroy    has_many :compliance_controls, dependent: :destroy diff --git a/app/models/concerns/application_days_support.rb b/app/models/concerns/application_days_support.rb index 2d00b5847..6086d9580 100644 --- a/app/models/concerns/application_days_support.rb +++ b/app/models/concerns/application_days_support.rb @@ -10,8 +10,10 @@ module ApplicationDaysSupport    SUNDAY    = 256    EVERYDAY  = MONDAY | TUESDAY | WEDNESDAY | THURSDAY | FRIDAY | SATURDAY | SUNDAY +  ALL_DAYS = %w(monday tuesday wednesday thursday friday saturday sunday).freeze +    def display_day_types -    %w(monday tuesday wednesday thursday friday saturday sunday).select{ |d| self.send(d) }.map{ |d| self.human_attribute_name(d).first(2)}.join(', ') +    ALL_DAYS.select{ |d| self.send(d) }.map{ |d| self.human_attribute_name(d).first(2)}.join(', ')    end    def day_by_mask(flag) @@ -39,6 +41,10 @@ module ApplicationDaysSupport      def self.day_by_mask(int_day_types,flag)        int_day_types & flag == flag      end + +    def self.all_days +      ALL_DAYS +    end    end    def valid_days diff --git a/app/models/concerns/custom_fields_support.rb b/app/models/concerns/custom_fields_support.rb index 6c76bd653..46fc8e73d 100644 --- a/app/models/concerns/custom_fields_support.rb +++ b/app/models/concerns/custom_fields_support.rb @@ -3,22 +3,56 @@ module CustomFieldsSupport    included do      validate :custom_fields_values_are_valid +    after_initialize :initialize_custom_fields -    def self.custom_fields -      CustomField.where(resource_type: self.name.split("::").last) +    def self.custom_fields workgroup=:all +      fields = CustomField.where(resource_type: self.name.split("::").last) +      fields = fields.where(workgroup_id: workgroup&.id) if workgroup != :all +      fields      end -    def custom_fields -      CustomField::Collection.new self +    def self.custom_fields_definitions workgroup=:all +      Hash[*custom_fields(workgroup).map{|cf| [cf.code, cf]}.flatten] +    end + +    def method_missing method_name, *args +      if method_name =~ /custom_field_*/ && method_name.to_sym != :custom_field_values && !@custom_fields_initialized +        initialize_custom_fields +        send method_name, *args +      else +        super method_name, *args +      end +    end + +    def custom_fields workgroup=:all +      CustomField::Collection.new self, workgroup +    end + +    def custom_field_values= vals +      out = {} +      custom_fields.each do |code, field| +        out[code] = field.preprocess_value_for_assignment(vals.symbolize_keys[code.to_sym]) +      end +      write_attribute :custom_field_values, out +    end + +    def initialize_custom_fields +      return unless self.attributes.has_key?("custom_field_values") +      self.custom_field_values ||= {} +      custom_fields(:all).values.each &:initialize_custom_field +      custom_fields(:all).each do |k, v| +        custom_field_values[k] ||= v.default_value +      end +      @custom_fields_initialized = true      end      def custom_field_value key -      (custom_field_values || {})[key.to_s] +      (custom_field_values&.stringify_keys || {})[key.to_s]      end      private      def custom_fields_values_are_valid -      custom_fields.values.all?{|cf| cf.valid?} +      custom_fields(:all).values.all?{|cf| cf.valid?}      end    end  end diff --git a/app/models/concerns/iev_interfaces/task.rb b/app/models/concerns/iev_interfaces/task.rb index bc78ff28c..f052b3a8f 100644 --- a/app/models/concerns/iev_interfaces/task.rb +++ b/app/models/concerns/iev_interfaces/task.rb @@ -25,7 +25,7 @@ module IevInterfaces::Task      scope :blocked, -> { where('created_at < ? AND status = ?', 4.hours.ago, 'running') } -    before_create :initialize_fields +    before_save :initialize_fields, on: :create      after_save :notify_parent    end diff --git a/app/models/concerns/metadata_support.rb b/app/models/concerns/metadata_support.rb new file mode 100644 index 000000000..c4bedbcda --- /dev/null +++ b/app/models/concerns/metadata_support.rb @@ -0,0 +1,107 @@ +module MetadataSupport +  extend ActiveSupport::Concern + +  included do +    class << self +      def has_metadata? +        !!@has_metadata +      end + +      def has_metadata opts={} +        @has_metadata = true + +        define_method :metadata do +          attr_name = opts[:attr_name] || :metadata +          @wrapped_metadata ||= begin +            wrapped = MetadataSupport::MetadataWrapper.new self.read_attribute(attr_name) +            wrapped.attribute_name = attr_name +            wrapped.owner = self +            wrapped +          end +        end + +        define_method :metadata= do |val| +          @wrapped_metadata = nil +          super val +        end + +        define_method :set_metadata! do |name, value| +          self.metadata.send "#{name}=", value +          self.save! +        end +      end +    end +  end + +  def has_metadata? +    self.class.has_metadata? +  end + +  def merge_metadata_from source +    return unless source.has_metadata? +    source_metadata = source.metadata +    res = {} +    self.metadata.each do |k, v| +      unless self.metadata.is_timestamp_attr?(k) +        ts = self.metadata.timestamp_attr(k) +        if source_metadata[ts] && source_metadata[ts] > self.metadata[ts] +          res[k] = source_metadata[k] +        else +          res[k] = v +        end +      end +    end +    self.metadata = res +    self +  end + +  class MetadataWrapper < OpenStruct +    attr_accessor :attribute_name, :owner + +    def is_timestamp_attr? name +      name =~ /_updated_at$/ +    end + +    def timestamp_attr name +      "#{name}_updated_at".to_sym +    end + +    def method_missing(mid, *args) +      out = super(mid, *args) +      owner.send :write_attribute, attribute_name, @table +      out = out&.to_time if args.length == 0 && is_timestamp_attr?(mid) +      out +    end + +    def each +      @table.each do |k,v| +        yield k, v +      end +    end + +    def new_ostruct_member name +      unless is_timestamp_attr?(name) +        timestamp_attr_name = timestamp_attr(name) +      end + +      name = name.to_sym +      unless respond_to?(name) +        if timestamp_attr_name +          define_singleton_method(timestamp_attr_name) { @table[timestamp_attr_name]&.to_time } +          define_singleton_method(name) { @table[name] } +        else +          # we are defining an accessor for a timestamp +          define_singleton_method(name) { @table[name]&.to_time } +        end + +        define_singleton_method("#{name}=") do |x| +          modifiable[timestamp_attr_name] = Time.now if timestamp_attr_name +          modifiable[name] = x +          owner.send :write_attribute, attribute_name, @table +        end +        modifiable[timestamp_attr_name] = Time.now if timestamp_attr_name +      end +      name +    end +  end +end diff --git a/app/models/concerns/min_max_values_validation.rb b/app/models/concerns/min_max_values_validation.rb index eff779d81..a79f5ec85 100644 --- a/app/models/concerns/min_max_values_validation.rb +++ b/app/models/concerns/min_max_values_validation.rb @@ -3,11 +3,13 @@ module MinMaxValuesValidation    included do      validates_presence_of :minimum, :maximum +    validates_numericality_of :minimum, :maximum, allow_nil: true, greater_than_or_equal_to: 0 +    validates_format_of :minimum, :maximum, with: %r{\A\d+(\.\d+)?\Z}      validate :min_max_values_validation    end    def min_max_values_validation -    return true if (minimum && maximum) && (minimum.to_i < maximum.to_i) +    return true if (minimum && maximum) && (minimum.to_f < maximum.to_f)      errors.add(:minimum, I18n.t('compliance_controls.min_max_values', min: minimum, max: maximum))    end  end diff --git a/app/models/custom_field.rb b/app/models/custom_field.rb index 4a840744e..65eabb205 100644 --- a/app/models/custom_field.rb +++ b/app/models/custom_field.rb @@ -1,16 +1,16 @@ -class CustomField < ActiveRecord::Base +class CustomField < ApplicationModel    extend Enumerize    belongs_to :workgroup -  enumerize :field_type, in: %i{list} +  enumerize :field_type, in: %i{list integer string attachment}    validates :name, uniqueness: {scope: [:resource_type, :workgroup_id]} -  validates :code, uniqueness: {scope: [:resource_type, :workgroup_id], case_sensitive: false} +  validates :code, uniqueness: {scope: [:resource_type, :workgroup_id], case_sensitive: false}, presence: true    class Collection < HashWithIndifferentAccess -    def initialize object -      vals = object.class.custom_fields.map do |v| -        [v.code, CustomField::Value.new(object, v, object.custom_field_value(v.code))] +    def initialize object, workgroup=:all +      vals = object.class.custom_fields(workgroup).map do |v| +        [v.code, CustomField::Instance.new(object, v, object.custom_field_value(v.code))]        end        super Hash[*vals.flatten]      end @@ -20,11 +20,11 @@ class CustomField < ActiveRecord::Base      end    end -  class Value +  class Instance      def self.new owner, custom_field, value -      field_type = custom_field.options["field_type"] -      klass_name = field_type && "CustomField::Value::#{field_type.classify}" -      klass = klass_name && const_defined?(klass_name) ? klass_name.constantize : CustomField::Value::Base +      field_type = custom_field.field_type +      klass_name = field_type && "CustomField::Instance::#{field_type.classify}" +      klass = klass_name.safe_constantize || CustomField::Instance::Base        klass.new owner, custom_field, value      end @@ -38,7 +38,17 @@ class CustomField < ActiveRecord::Base          @valid = false        end -      delegate :code, :name, :field_type, :options, to: :@custom_field +      attr_accessor :owner, :custom_field + +      delegate :code, :name, :field_type, to: :@custom_field + +      def default_value +        options["default"] +      end + +      def options +        @custom_field.options || {} +      end        def validate          @valid = true @@ -53,6 +63,14 @@ class CustomField < ActiveRecord::Base          @raw_value        end +      def input form_helper +        @input ||= begin +          klass_name = field_type && "CustomField::Instance::#{field_type.classify}::Input" +          klass = klass_name.safe_constantize || CustomField::Instance::Base::Input +          klass.new self, form_helper +        end +      end +        def errors_key          "custom_fields.#{code}"        end @@ -60,22 +78,184 @@ class CustomField < ActiveRecord::Base        def to_hash          HashWithIndifferentAccess[*%w(code name field_type options value).map{|k| [k, send(k)]}.flatten(1)]        end + +      def display_value +        value +      end + +      def initialize_custom_field +      end + +      def preprocess_value_for_assignment val +        val +      end + +      def render_partial +        ActionView::Base.new(Rails.configuration.paths["app/views"].first).render( +          :partial => "shared/custom_fields/#{field_type}", +          :locals => { field: self} +        ) +      end + +      class Input +        def initialize instance, form_helper +          @instance = instance +          @form_helper = form_helper +        end + +        def custom_field +          @instance.custom_field +        end + +        delegate :custom_field, :value, :options, to: :@instance +        delegate :code, :name, :field_type, to: :custom_field + +        def to_s +          out = form_input +          out.html_safe +        end + +        protected + +        def form_input_id +          "custom_field_#{code}" +        end + +        def form_input_name +          "#{@form_helper.object_name}[custom_field_values][#{code}]" +        end + +        def form_input_options +          { +            input_html: {value: value, name: form_input_name}, +            label: name +          } +        end + +        def form_input +          @form_helper.input form_input_id, form_input_options +        end +      end      end      class Integer < Base        def value -        @raw_value.to_i +        @raw_value&.to_i        end        def validate          @valid = true -        unless @raw_value =~ /\A\d*\Z/ +        return if @raw_value.is_a?(Fixnum) || @raw_value.is_a?(Float) +        unless @raw_value.to_s =~ /\A\d*\Z/            @owner.errors.add errors_key, "'#{@raw_value}' is not a valid integer"            @valid = false          end        end      end +    class List < Integer +      def validate +        super +        return unless value.present? +        unless value >= 0 && value < options["list_values"].size +          @owner.errors.add errors_key, "'#{@raw_value}' is not a valid value" +          @valid = false +        end +      end + +      def display_value +        return unless value +        k = options["list_values"].is_a?(Hash) ? value.to_s : value.to_i +        options["list_values"][k] +      end + +      class Input < Base::Input +        def form_input_options +          collection = options["list_values"] +          collection = collection.each_with_index.to_a if collection.is_a?(Array) +          collection = collection.map(&:reverse) if collection.is_a?(Hash) +          super.update({ +            selected: value, +            collection: collection +          }) +        end +      end +    end + +    class Attachment < Base +      def initialize_custom_field +        custom_field_code = self.code +        _attr_name = attr_name +        _uploader_name = uploader_name +        owner.send :define_singleton_method, "read_uploader" do |attr| +          if attr.to_s == _attr_name +            custom_field_values[custom_field_code] +          else +            read_attribute attr +          end +        end + +        owner.send :define_singleton_method, "write_uploader" do |attr, val| +          if attr.to_s == _attr_name +            custom_field_values[custom_field_code] = val +          else +            write_attribute attr, val +          end +        end + +        owner.send :define_singleton_method, "#{_attr_name}_will_change!" do +          custom_field_values_will_change! +        end + +        _extension_whitelist = options["extension_whitelist"] + +        owner.send :define_singleton_method, "#{_uploader_name}_extension_whitelist" do +          _extension_whitelist +        end + +        unless owner.class.uploaders.has_key? _uploader_name.to_sym +          owner.class.mount_uploader _uploader_name, CustomFieldAttachmentUploader, mount_on: "custom_field_#{code}_raw_value" +        end +      end + +      def preprocess_value_for_assignment val +        if val.present? +          owner.send "#{uploader_name}=", val +        else +          @raw_value +        end +      end + +      def value +        owner.send "custom_field_#{code}" +      end + +      def raw_value +        @raw_value +      end + +      def attr_name +        "custom_field_#{code}_raw_value" +      end + +      def uploader_name +        "custom_field_#{code}" +      end + +      def display_value +        render_partial +      end + +      class Input < Base::Input +        def form_input_options +          super.update({ +            as: :file, +            wrapper: :horizontal_file_input +          }) +        end +      end +    end +      class String < Base        def value          "#{@raw_value}" diff --git a/app/models/export/base.rb b/app/models/export/base.rb index 6cf4c6b02..c65539635 100644 --- a/app/models/export/base.rb +++ b/app/models/export/base.rb @@ -1,4 +1,8 @@ +require 'net/http/post/multipart' +  class Export::Base < ActiveRecord::Base +  include Rails.application.routes.url_helpers +    self.table_name = "exports"    belongs_to :referential @@ -21,6 +25,22 @@ class Export::Base < ActiveRecord::Base      %w(zip csv json)    end +  def upload_file file +    url = URI.parse upload_workbench_export_url(self.workbench_id, self.id, host: Rails.application.config.rails_host) +    res = nil +    filename = File.basename(file.path) +    content_type = MIME::Types.type_for(filename).first&.content_type +    File.open(file.path) do |file_content| +      req = Net::HTTP::Post::Multipart.new url.path, +        file: UploadIO.new(file_content, content_type, filename), +        token: self.token_upload +      res = Net::HTTP.start(url.host, url.port) do |http| +        http.request(req) +      end +    end +    res +  end +    if Rails.env.development?      def self.force_load_descendants        path = Rails.root.join 'app/models/export' diff --git a/app/models/export/message.rb b/app/models/export/message.rb index b64b524ac..223429900 100644 --- a/app/models/export/message.rb +++ b/app/models/export/message.rb @@ -1,4 +1,4 @@ -class Export::Message < ActiveRecord::Base +class Export::Message < ApplicationModel    self.table_name = :export_messages    include IevInterfaces::Message diff --git a/app/models/export/resource.rb b/app/models/export/resource.rb index 98f103be4..2a63c14a8 100644 --- a/app/models/export/resource.rb +++ b/app/models/export/resource.rb @@ -1,4 +1,4 @@ -class Export::Resource < ActiveRecord::Base +class Export::Resource < ApplicationModel    self.table_name = :export_resources    include IevInterfaces::Resource diff --git a/app/models/export/simple_exporter/base.rb b/app/models/export/simple_exporter/base.rb index 4e6e8eba4..e77e23468 100644 --- a/app/models/export/simple_exporter/base.rb +++ b/app/models/export/simple_exporter/base.rb @@ -48,15 +48,15 @@ class Export::SimpleExporter::Base < Export::Base      exporter.export      set_status_from_exporter      convert_exporter_journal_to_messages -    self.file = tmp      self.save! +    upload_file tmp    end    def set_status_from_exporter      if exporter.status.to_s == "error"        self.status = :failed      elsif exporter.status.to_s == "success" -        self.status = :successful +      self.status = :successful      else        self.status = :warning      end diff --git a/app/models/generic_attribute_control/min_max.rb b/app/models/generic_attribute_control/min_max.rb index 18873b683..bab900f0e 100644 --- a/app/models/generic_attribute_control/min_max.rb +++ b/app/models/generic_attribute_control/min_max.rb @@ -1,9 +1,7 @@  module GenericAttributeControl    class MinMax < ComplianceControl      store_accessor :control_attributes, :minimum, :maximum, :target - -    validates_numericality_of :minimum, allow_nil: true, greater_than_or_equal_to: 0 -    validates_numericality_of :maximum, allow_nil: true, greater_than_or_equal_to: 0 +          validates :target, presence: true      include MinMaxValuesValidation diff --git a/app/models/import/base.rb b/app/models/import/base.rb index 62494c92e..f98e359d4 100644 --- a/app/models/import/base.rb +++ b/app/models/import/base.rb @@ -1,4 +1,4 @@ -class Import::Base < ActiveRecord::Base +class Import::Base < ApplicationModel    self.table_name = "imports"    validates :file, presence: true @@ -41,7 +41,7 @@ class Import::Base < ActiveRecord::Base    def initialize_fields      super -    self.token_download = SecureRandom.urlsafe_base64 +    self.token_download ||= SecureRandom.urlsafe_base64    end  end diff --git a/app/models/import/gtfs.rb b/app/models/import/gtfs.rb index 03cf49e60..70f448132 100644 --- a/app/models/import/gtfs.rb +++ b/app/models/import/gtfs.rb @@ -1,36 +1,296 @@ -require 'net/http'  class Import::Gtfs < Import::Base -  before_destroy :destroy_non_ready_referential +  after_commit :launch_worker, :on => :create -  after_commit :launch_java_import, on: :create -  before_save def abort_unless_referential -    self.status = 'aborted' unless referential +  def launch_worker +    GtfsImportWorker.perform_async id    end -  def launch_java_import -    return if self.class.finished_statuses.include?(status) -    threaded_call_boiv_iev +  def import +    update status: 'running', started_at: Time.now + +    import_without_status +    update status: 'successful', ended_at: Time.now +  rescue Exception => e +    update status: 'failed', ended_at: Time.now +    Rails.logger.error "Error in GTFS import: #{e} #{e.backtrace.join('\n')}" +  ensure +    notify_parent +    referential&.update ready: true +  end + +  def self.accept_file?(file) +    Zip::File.open(file) do |zip_file| +      zip_file.glob('agency.txt').size == 1 +    end +  rescue Exception => e +    Rails.logger.debug "Error in testing GTFS file: #{e}" +    return false +  end + +  def create_referential +    self.referential ||= Referential.create!( +      name: "GTFS Import", +      organisation_id: workbench.organisation_id, +      workbench_id: workbench.id, +      metadatas: [referential_metadata] +    ) +  end + +  def referential_metadata +    registration_numbers = source.routes.map(&:id) +    line_ids = line_referential.lines.where(registration_number: registration_numbers).pluck(:id) + +    start_dates, end_dates = source.calendars.map { |c| [c.start_date, c.end_date ] }.transpose +    excluded_dates = source.calendar_dates.select { |d| d.exception_type == "2" }.map(&:date) + +    min_date = Date.parse (start_dates + [excluded_dates.min]).compact.min +    max_date = Date.parse (end_dates + [excluded_dates.max]).compact.max + +    ReferentialMetadata.new line_ids: line_ids, periodes: [min_date..max_date] +  end + +  attr_accessor :local_file +  def local_file +    @local_file ||= download_local_file    end -  private +  attr_accessor :download_host +  def download_host +    @download_host ||= Rails.application.config.rails_host.gsub("http://","") +  end -  def destroy_non_ready_referential -    if referential && !referential.ready -      referential.destroy +  def local_temp_directory +    Rails.application.config.try(:import_temporary_directory) || +      Rails.root.join('tmp', 'imports') +  end + +  def local_temp_file(&block) +    Tempfile.open("chouette-import", local_temp_directory) do |file| +      file.binmode +      yield file      end    end -  def threaded_call_boiv_iev -    Thread.new(&method(:call_boiv_iev)) +  def download_path +    Rails.application.routes.url_helpers.download_workbench_import_path(workbench, id, token: token_download)    end -  def call_boiv_iev -    Rails.logger.error("Begin IEV call for import") -    Net::HTTP.get(URI("#{Rails.configuration.iev_url}/boiv_iev/referentials/importer/new?id=#{id}")) -    Rails.logger.error("End IEV call for import") -  rescue Exception => e -    logger.error "IEV server error : #{e.message}" -    logger.error e.backtrace.inspect +  def download_local_file +    local_temp_file do |file| +      begin +        Net::HTTP.start(download_host) do |http| +          http.request_get(download_path) do |response| +            response.read_body do |segment| +              file.write segment +            end +          end +        end +      ensure +        file.close +      end + +      file.path +    end +  end + +  def source +    @source ||= ::GTFS::Source.build local_file +  end + +  delegate :line_referential, :stop_area_referential, to: :workbench + +  def prepare_referential +    import_agencies +    import_stops +    import_routes + +    create_referential +    referential.switch +  end + +  def import_without_status +    prepare_referential + +    import_calendars +    import_trips +    import_stop_times +  end + +  def import_agencies +    Chouette::Company.transaction do +      source.agencies.each do |agency| +        company = line_referential.companies.find_or_initialize_by(registration_number: agency.id) +        company.attributes = { name: agency.name } + +        save_model company +      end +    end +  end + +  def import_stops +    Chouette::StopArea.transaction do +      source.stops.each do |stop| +        stop_area = stop_area_referential.stop_areas.find_or_initialize_by(registration_number: stop.id) + +        stop_area.name = stop.name +        stop_area.area_type = stop.location_type == "1" ? "zdlp" : "zdep" +        stop_area.parent = stop_area_referential.stop_areas.find_by!(registration_number: stop.parent_station) if stop.parent_station.present? +        stop_area.latitude, stop_area.longitude = stop.lat, stop.lon +        stop_area.kind = "commercial" + +        # TODO correct default timezone + +        save_model stop_area +      end +    end +  end + +  def import_routes +    Chouette::Line.transaction do +      source.routes.each do |route| +        line = line_referential.lines.find_or_initialize_by(registration_number: route.id) +        line.name = route.long_name.presence || route.short_name +        line.number = route.short_name +        line.published_name = route.long_name + +        line.company = line_referential.companies.find_by(registration_number: route.agency_id) if route.agency_id.present? + +        # TODO transport mode + +        line.comment = route.desc + +        # TODO colors + +        line.url = route.url + +        save_model line +      end +    end +  end + +  def vehicle_journey_by_trip_id +    @vehicle_journey_by_trip_id ||= {} +  end + +  def import_trips +    source.trips.each_slice(100) do |slice| +      slice.each do |trip| +        Chouette::Route.transaction do +          line = line_referential.lines.find_by registration_number: trip.route_id + +          route = referential.routes.build line: line +          route.wayback = (trip.direction_id == "0" ? :outbound : :inbound) +          # TODO better name ? +          name = route.published_name = trip.short_name.presence || trip.headsign.presence || route.wayback.to_s.capitalize +          route.name = name +          save_model route + +          journey_pattern = route.journey_patterns.build name: name +          save_model journey_pattern + +          vehicle_journey = journey_pattern.vehicle_journeys.build route: route +          vehicle_journey.published_journey_name = trip.headsign.presence || trip.id +          save_model vehicle_journey + +          time_table = referential.time_tables.find_by(id: time_tables_by_service_id[trip.service_id]) if time_tables_by_service_id[trip.service_id] +          if time_table +            vehicle_journey.time_tables << time_table +          else +            messages.create! criticity: "warning", message_key: "gtfs.trips.unkown_service_id", message_attributes: {service_id: trip.service_id} +          end + +          vehicle_journey_by_trip_id[trip.id] = vehicle_journey.id +        end +      end +    end +  end + +  def import_stop_times +    source.stop_times.group_by(&:trip_id).each_slice(50) do |slice| +      slice.each do |trip_id, stop_times| +        Chouette::VehicleJourneyAtStop.transaction do +          vehicle_journey = referential.vehicle_journeys.find vehicle_journey_by_trip_id[trip_id] +          journey_pattern = vehicle_journey.journey_pattern +          route = journey_pattern.route + +          stop_times.sort_by! { |s| s.stop_sequence.to_i } + +          stop_times.each do |stop_time| +            stop_area = stop_area_referential.stop_areas.find_by(registration_number: stop_time.stop_id) + +            stop_point = route.stop_points.build stop_area: stop_area +            save_model stop_point + +            journey_pattern.stop_points << stop_point + +            # JourneyPattern#vjas_add creates automaticaly VehicleJourneyAtStop +            vehicle_journey_at_stop = journey_pattern.vehicle_journey_at_stops.find_by(stop_point_id: stop_point.id) + +            departure_time = GTFS::Time.parse(stop_time.departure_time) +            arrival_time = GTFS::Time.parse(stop_time.arrival_time) + +            vehicle_journey_at_stop.departure_time = departure_time.time +            vehicle_journey_at_stop.arrival_time = arrival_time.time +            vehicle_journey_at_stop.departure_day_offset = departure_time.day_offset +            vehicle_journey_at_stop.arrival_day_offset = arrival_time.day_offset + +            # TODO offset + +            save_model vehicle_journey_at_stop +          end +        end +      end +    end +  end + +  def time_tables_by_service_id +    @time_tables_by_service_id ||= {} +  end + +  def import_calendars +    source.calendars.each_slice(500) do |slice| +      Chouette::TimeTable.transaction do +        slice.each do |calendar| +          time_table = referential.time_tables.build comment: "Calendar #{calendar.service_id}" +          Chouette::TimeTable.all_days.each do |day| +            time_table.send("#{day}=", calendar.send(day)) +          end +          time_table.periods.build period_start: calendar.start_date, period_end: calendar.end_date + +          save_model time_table + +          time_tables_by_service_id[calendar.service_id] = time_table.id +        end +      end +    end +  end + +  def import_calendar_dates +    source.calendar_dates.each_slice(500) do |slice| +      Chouette::TimeTable.transaction do +        slice.each do |calendar_date| +          time_table = referential.time_tables.find time_tables_by_service_id[calendar_date.service_id] +          date = time_table.dates.build date: Date.parse(calendar_date.date), in_out: calendar_date.exception_type == "1" + +          save_model date +        end +      end +    end +  end + +  def save_model(model) +    unless model.save +      Rails.logger.info "Can't save #{model.class.name} : #{model.errors.inspect}" +      raise ActiveRecord::RecordNotSaved.new("Invalid #{model.class.name} : #{model.errors.inspect}") +    end +    Rails.logger.debug "Created #{model.inspect}" +  end + +  def notify_parent +    return unless parent.present? +    return if notified_parent_at +    parent.child_change +    update_column :notified_parent_at, Time.now    end  end diff --git a/app/models/import/message.rb b/app/models/import/message.rb index c1900a718..30b76ec5c 100644 --- a/app/models/import/message.rb +++ b/app/models/import/message.rb @@ -1,4 +1,4 @@ -class Import::Message < ActiveRecord::Base +class Import::Message < ApplicationModel    self.table_name = :import_messages    include IevInterfaces::Message diff --git a/app/models/import/netex.rb b/app/models/import/netex.rb index 2b0982229..93604c5f9 100644 --- a/app/models/import/netex.rb +++ b/app/models/import/netex.rb @@ -10,6 +10,26 @@ class Import::Netex < Import::Base    validates_presence_of :parent +  def create_referential! +    self.referential = +      Referential.new( +        name: self.name, +        organisation_id: workbench.organisation_id, +        workbench_id: workbench.id, +        metadatas: [referential_metadata] +      ) +    self.referential.save +    unless self.referential.valid? +      Rails.logger.info "Can't create referential for import #{self.id}: #{referential.inspect} #{referential.metadatas.inspect} #{referential.errors.messages}" +      if referential.metadatas.all?{|m| m.line_ids.empty?} +        parent.messages.create criticity: :error, message_key: "referential_creation_missing_lines", message_attributes: {referential_name: referential.name} +      else +        parent.messages.create criticity: :error, message_key: "referential_creation", message_attributes: {referential_name: referential.name} +      end +    end +    save! +  end +    private    def iev_callback_url @@ -21,4 +41,22 @@ class Import::Netex < Import::Base        referential.destroy      end    end + +  def referential_metadata +    metadata = ReferentialMetadata.new + +    if self.file +      netex_file = STIF::NetexFile.new(self.file.path) +      frame = netex_file.frames.first + +      if frame +        metadata.periodes = frame.periods + +        line_objectids = frame.line_refs.map { |ref| "STIF:CODIFLIGNE:Line:#{ref}" } +        metadata.line_ids = workbench.lines.where(objectid: line_objectids).pluck(:id) +      end +    end + +    metadata +  end  end diff --git a/app/models/import/resource.rb b/app/models/import/resource.rb index 5bd011039..1951daacd 100644 --- a/app/models/import/resource.rb +++ b/app/models/import/resource.rb @@ -1,4 +1,4 @@ -class Import::Resource < ActiveRecord::Base +class Import::Resource < ApplicationModel    self.table_name = :import_resources    include IevInterfaces::Resource diff --git a/app/models/import/workbench.rb b/app/models/import/workbench.rb index f6e15cb89..124b9b0d8 100644 --- a/app/models/import/workbench.rb +++ b/app/models/import/workbench.rb @@ -2,6 +2,25 @@ class Import::Workbench < Import::Base    after_commit :launch_worker, :on => :create    def launch_worker -    WorkbenchImportWorker.perform_async(id) +    unless Import::Gtfs.accept_file?(file.path) +      WorkbenchImportWorker.perform_async(id) +    else +      import_gtfs +    end +  end + +  def import_gtfs +    update_column :status, 'running' +    update_column :started_at, Time.now + +    Import::Gtfs.create! parent_id: self.id, workbench: workbench, file: File.new(file.path), name: "Import GTFS", creator: "Web service" + +    update_column :status, 'successful' +    update_column :ended_at, Time.now +  rescue Exception => e +    Rails.logger.error "Error while processing GTFS file: #{e}" + +    update_column :status, 'failed' +    update_column :ended_at, Time.now    end  end diff --git a/app/models/line_control/lines_scope.rb b/app/models/line_control/lines_scope.rb new file mode 100644 index 000000000..4210a10dd --- /dev/null +++ b/app/models/line_control/lines_scope.rb @@ -0,0 +1,8 @@ +module LineControl +  class LinesScope < ComplianceControl + +    def self.default_code; "3-Line-2" end + +    def prerequisite; I18n.t("compliance_controls.#{self.class.name.underscore}.prerequisite") end +  end +end diff --git a/app/models/line_referential.rb b/app/models/line_referential.rb index 0d2ed39b1..08193c960 100644 --- a/app/models/line_referential.rb +++ b/app/models/line_referential.rb @@ -1,7 +1,7 @@ -class LineReferential < ActiveRecord::Base +class LineReferential < ApplicationModel    include ObjectidFormatterSupport    extend StifTransportModeEnumerations -   +    has_many :line_referential_memberships    has_many :organisations, through: :line_referential_memberships    has_many :lines, class_name: 'Chouette::Line' @@ -14,7 +14,7 @@ class LineReferential < ActiveRecord::Base    def add_member(organisation, options = {})      attributes = options.merge organisation: organisation -    line_referential_memberships.build attributes +    line_referential_memberships.build attributes unless organisations.include?(organisation)    end    validates :name, presence: true diff --git a/app/models/line_referential_membership.rb b/app/models/line_referential_membership.rb index b49d1b5b1..8371bdc32 100644 --- a/app/models/line_referential_membership.rb +++ b/app/models/line_referential_membership.rb @@ -1,4 +1,6 @@ -class LineReferentialMembership < ActiveRecord::Base +class LineReferentialMembership < ApplicationModel    belongs_to :organisation    belongs_to :line_referential + +  validates :organisation_id, presence: true, uniqueness: { scope: :line_referential }  end diff --git a/app/models/line_referential_sync.rb b/app/models/line_referential_sync.rb index 75c1e48a2..39e3846f0 100644 --- a/app/models/line_referential_sync.rb +++ b/app/models/line_referential_sync.rb @@ -1,4 +1,4 @@ -class LineReferentialSync < ActiveRecord::Base +class LineReferentialSync < ApplicationModel    include AASM    belongs_to :line_referential    has_many :line_referential_sync_messages, :dependent => :destroy diff --git a/app/models/line_referential_sync_message.rb b/app/models/line_referential_sync_message.rb index 3b6cf3367..00a2b58a3 100644 --- a/app/models/line_referential_sync_message.rb +++ b/app/models/line_referential_sync_message.rb @@ -1,4 +1,4 @@ -class LineReferentialSyncMessage < ActiveRecord::Base +class LineReferentialSyncMessage < ApplicationModel    belongs_to :line_referential_sync    enum criticity: [:info, :warning, :error] diff --git a/app/models/merge.rb b/app/models/merge.rb index e72c794fe..8d661f209 100644 --- a/app/models/merge.rb +++ b/app/models/merge.rb @@ -1,4 +1,4 @@ -class Merge < ActiveRecord::Base +class Merge < ApplicationModel    extend Enumerize    belongs_to :workbench @@ -50,7 +50,7 @@ class Merge < ActiveRecord::Base      new =        if workbench.output.current          Rails.logger.debug "Clone current output" -        Referential.new_from(workbench.output.current, fixme_functional_scope).tap do |clone| +        Referential.new_from(workbench.output.current, workbench.organisation).tap do |clone|            clone.inline_clone = true          end        else @@ -138,7 +138,9 @@ class Merge < ActiveRecord::Base      new.switch do        referential_routes.each do |route|          existing_route = new.routes.find_by line_id: route.line_id, checksum: route.checksum -        unless existing_route +        if existing_route +          existing_route.merge_metadata_from route +        else            objectid = Chouette::Route.where(objectid: route.objectid).exists? ? nil : route.objectid            attributes = route.attributes.merge(              id: nil, @@ -196,7 +198,9 @@ class Merge < ActiveRecord::Base          existing_journey_pattern = new.journey_patterns.find_by route_id: existing_associated_route.id, checksum: journey_pattern.checksum -        unless existing_journey_pattern +        if existing_journey_pattern +          existing_journey_pattern.merge_metadata_from journey_pattern +        else            objectid = Chouette::JourneyPattern.where(objectid: journey_pattern.objectid).exists? ? nil : journey_pattern.objectid            attributes = journey_pattern.attributes.merge(              id: nil, @@ -241,7 +245,9 @@ class Merge < ActiveRecord::Base          existing_vehicle_journey = new.vehicle_journeys.find_by journey_pattern_id: existing_associated_journey_pattern.id, checksum: vehicle_journey.checksum -        unless existing_vehicle_journey +        if existing_vehicle_journey +          existing_vehicle_journey.merge_metadata_from vehicle_journey +        else            objectid = Chouette::VehicleJourney.where(objectid: vehicle_journey.objectid).exists? ? nil : vehicle_journey.objectid            attributes = vehicle_journey.attributes.merge(              id: nil, @@ -338,7 +344,9 @@ class Merge < ActiveRecord::Base            existing_time_table = line.time_tables.find_by checksum: candidate_time_table.checksum -          unless existing_time_table +          if existing_time_table +            existing_time_table.merge_metadata_from candidate_time_table +          else              objectid = Chouette::TimeTable.where(objectid: time_table.objectid).exists? ? nil : time_table.objectid              candidate_time_table.objectid = objectid @@ -363,7 +371,7 @@ class Merge < ActiveRecord::Base    def save_current      output.update current: new, new: nil -    output.current.update referential_suite: output +    output.current.update referential_suite: output, ready: true      referentials.update_all merged_at: created_at, archived_at: created_at    end diff --git a/app/models/organisation.rb b/app/models/organisation.rb index 745bc0d22..5742c81e8 100644 --- a/app/models/organisation.rb +++ b/app/models/organisation.rb @@ -1,5 +1,5 @@  # coding: utf-8 -class Organisation < ActiveRecord::Base +class Organisation < ApplicationModel    include DataFormatEnumerations    has_many :users, :dependent => :destroy @@ -86,4 +86,8 @@ class Organisation < ActiveRecord::Base      workbenches.default    end +  def lines_scope +    functional_scope = sso_attributes.try(:[], "functional_scope") +    JSON.parse(functional_scope) if functional_scope +  end  end diff --git a/app/models/public_version.rb b/app/models/public_version.rb deleted file mode 100644 index 4dbf6ce27..000000000 --- a/app/models/public_version.rb +++ /dev/null @@ -1,4 +0,0 @@ -class PublicVersion < PaperTrail::Version -  # custom behaviour, e.g: -  self.table_name = :'public.versions' -end diff --git a/app/models/referential.rb b/app/models/referential.rb index 91a88d02d..1794126a2 100644 --- a/app/models/referential.rb +++ b/app/models/referential.rb @@ -1,5 +1,5 @@  # coding: utf-8 -class Referential < ActiveRecord::Base +class Referential < ApplicationModel    include DataFormatEnumerations    include ObjectidFormatterSupport @@ -168,6 +168,10 @@ class Referential < ActiveRecord::Base      Chouette::TimeTable.all    end +  def time_table_dates +    Chouette::TimeTableDate.all +  end +    def timebands      Chouette::Timeband.all    end @@ -184,6 +188,10 @@ class Referential < ActiveRecord::Base      Chouette::VehicleJourneyFrequency.all    end +  def vehicle_journey_at_stops +    Chouette::VehicleJourneyAtStop.all +  end +    def routing_constraint_zones      Chouette::RoutingConstraintZone.all    end @@ -233,7 +241,7 @@ class Referential < ActiveRecord::Base      end    end -  def self.new_from(from, functional_scope) +  def self.new_from(from, organisation)      Referential.new(        name: I18n.t("activerecord.copy", name: from.name),        slug: "#{from.slug}_clone", @@ -244,7 +252,7 @@ class Referential < ActiveRecord::Base        stop_area_referential: from.stop_area_referential,        created_from: from,        objectid_format: from.objectid_format, -      metadatas: from.metadatas.map { |m| ReferentialMetadata.new_from(m, functional_scope) } +      metadatas: from.metadatas.map { |m| ReferentialMetadata.new_from(m, organisation) }      )    end diff --git a/app/models/referential_cloning.rb b/app/models/referential_cloning.rb index d4b74bd52..f2c81009a 100644 --- a/app/models/referential_cloning.rb +++ b/app/models/referential_cloning.rb @@ -1,4 +1,4 @@ -class ReferentialCloning < ActiveRecord::Base +class ReferentialCloning < ApplicationModel    include AASM    belongs_to :source_referential, class_name: 'Referential'    belongs_to :target_referential, class_name: 'Referential' diff --git a/app/models/referential_metadata.rb b/app/models/referential_metadata.rb index 393dc70d3..7a8a01774 100644 --- a/app/models/referential_metadata.rb +++ b/app/models/referential_metadata.rb @@ -1,7 +1,7 @@  require 'activeattr_ext.rb'  require 'range_ext' -class ReferentialMetadata < ActiveRecord::Base +class ReferentialMetadata < ApplicationModel    belongs_to :referential, touch: true    belongs_to :referential_source, class_name: 'Referential'    has_array_of :lines, class_name: 'Chouette::Line' @@ -155,10 +155,10 @@ class ReferentialMetadata < ActiveRecord::Base    end    private :clear_periods -  def self.new_from(from, functional_scope) +  def self.new_from(from, organisation)      from.dup.tap do |metadata|        metadata.referential_source_id = from.referential_id -      metadata.line_ids = from.referential.lines.where(id: metadata.line_ids, objectid: functional_scope).collect(&:id) +      metadata.line_ids = from.referential.lines.where(id: metadata.line_ids).for_organisation(organisation).pluck(:id)        metadata.referential_id = nil      end    end diff --git a/app/models/referential_suite.rb b/app/models/referential_suite.rb index 4f825628c..f4a72f22c 100644 --- a/app/models/referential_suite.rb +++ b/app/models/referential_suite.rb @@ -1,4 +1,4 @@ -class ReferentialSuite < ActiveRecord::Base +class ReferentialSuite < ApplicationModel    belongs_to :new, class_name: 'Referential'    validate def validate_consistent_new      return true if new_id.nil? || new.nil? diff --git a/app/models/simple_exporter.rb b/app/models/simple_exporter.rb index c267b5b8c..1fcb76a29 100644 --- a/app/models/simple_exporter.rb +++ b/app/models/simple_exporter.rb @@ -64,7 +64,7 @@ class SimpleExporter < SimpleInterface    def map_item_to_rows item      return [item] unless configuration.item_to_rows_mapping -    configuration.item_to_rows_mapping.call(item).map {|row| row.is_a?(ActiveRecord::Base) ? row : CustomRow.new(row) } +    instance_exec(item, &configuration.item_to_rows_mapping).map {|row| row.is_a?(ActiveRecord::Base) ? row : CustomRow.new(row) }    end    def resolve_value item, col diff --git a/app/models/simple_interface.rb b/app/models/simple_interface.rb index 43c740b57..7b04a07df 100644 --- a/app/models/simple_interface.rb +++ b/app/models/simple_interface.rb @@ -1,4 +1,4 @@ -class SimpleInterface < ActiveRecord::Base +class SimpleInterface < ApplicationModel    attr_accessor :configuration, :interfaces_group    class << self diff --git a/app/models/stop_area_referential.rb b/app/models/stop_area_referential.rb index a9d3cc9b1..6c339547c 100644 --- a/app/models/stop_area_referential.rb +++ b/app/models/stop_area_referential.rb @@ -1,4 +1,4 @@ -class StopAreaReferential < ActiveRecord::Base +class StopAreaReferential < ApplicationModel    validates :registration_number_format, format: { with: /\AX*\z/ }    include ObjectidFormatterSupport @@ -12,7 +12,7 @@ class StopAreaReferential < ActiveRecord::Base    def add_member(organisation, options = {})      attributes = options.merge organisation: organisation -    stop_area_referential_memberships.build attributes +    stop_area_referential_memberships.build attributes unless organisations.include?(organisation)    end    def last_sync diff --git a/app/models/stop_area_referential_membership.rb b/app/models/stop_area_referential_membership.rb index 435970961..d507bc50e 100644 --- a/app/models/stop_area_referential_membership.rb +++ b/app/models/stop_area_referential_membership.rb @@ -1,4 +1,6 @@ -class StopAreaReferentialMembership < ActiveRecord::Base +class StopAreaReferentialMembership < ApplicationModel    belongs_to :organisation    belongs_to :stop_area_referential + +  validates :organisation_id, presence: true, uniqueness: { scope: :stop_area_referential }  end diff --git a/app/models/stop_area_referential_sync.rb b/app/models/stop_area_referential_sync.rb index e6cf2ecbc..8b48d35e6 100644 --- a/app/models/stop_area_referential_sync.rb +++ b/app/models/stop_area_referential_sync.rb @@ -1,4 +1,4 @@ -class StopAreaReferentialSync < ActiveRecord::Base +class StopAreaReferentialSync < ApplicationModel    include AASM    belongs_to :stop_area_referential    has_many :stop_area_referential_sync_messages, :dependent => :destroy diff --git a/app/models/stop_area_referential_sync_message.rb b/app/models/stop_area_referential_sync_message.rb index cd2e62405..642ccfc38 100644 --- a/app/models/stop_area_referential_sync_message.rb +++ b/app/models/stop_area_referential_sync_message.rb @@ -1,4 +1,4 @@ -class StopAreaReferentialSyncMessage < ActiveRecord::Base +class StopAreaReferentialSyncMessage < ApplicationModel    belongs_to :stop_area_referential_sync    enum criticity: [:info, :warning, :error] diff --git a/app/models/user.rb b/app/models/user.rb index 31e634415..ba166b06f 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -1,4 +1,4 @@ -class User < ActiveRecord::Base +class User < ApplicationModel    # Include default devise modules. Others available are:    # :token_authenticatable, :encryptable, :confirmable, :lockable, :timeoutable and :omniauthable, :database_authenticatable @@ -9,7 +9,7 @@ class User < ActiveRecord::Base           :recoverable, :rememberable, :trackable, :async, authentication_type    # FIXME https://github.com/nbudin/devise_cas_authenticatable/issues/53 -  # Work around :validatable, when database_authenticatable is diabled. +  # Work around :validatable, when database_authenticatable is disabled.    attr_accessor :password unless authentication_type == :database_authenticatable    # Setup accessible (or protected) attributes for your model @@ -30,6 +30,8 @@ class User < ActiveRecord::Base    scope :with_organisation, -> { where.not(organisation_id: nil) } +  scope :from_workgroup, ->(workgroup_id) { joins(:workbenches).where(workbenches: {workgroup_id: workgroup_id}) } +    # Callback invoked by DeviseCasAuthenticable::Model#authernticate_with_cas_ticket    def cas_extra_attributes=(extra_attributes) @@ -67,6 +69,10 @@ class User < ActiveRecord::Base      permissions && permissions.include?(permission)    end +  def can_monitor_sidekiq? +    has_permission?("sidekiq.monitor") +  end +    private    # remove organisation and referentials if last user of it diff --git a/app/models/vehicle_journey_control/speed.rb b/app/models/vehicle_journey_control/speed.rb index e5e331b50..c9775e7a3 100644 --- a/app/models/vehicle_journey_control/speed.rb +++ b/app/models/vehicle_journey_control/speed.rb @@ -2,8 +2,6 @@ module VehicleJourneyControl    class Speed < ComplianceControl      store_accessor :control_attributes, :minimum, :maximum -    validates_numericality_of :minimum, allow_nil: true, greater_than_or_equal_to: 0 -    validates_numericality_of :maximum, allow_nil: true, greater_than_or_equal_to: 0      include MinMaxValuesValidation      def self.default_code; "3-VehicleJourney-2" end diff --git a/app/models/workbench.rb b/app/models/workbench.rb index b5f4673bb..ef0b2eaa4 100644 --- a/app/models/workbench.rb +++ b/app/models/workbench.rb @@ -1,4 +1,4 @@ -class Workbench < ActiveRecord::Base +class Workbench < ApplicationModel    DEFAULT_WORKBENCH_NAME = "Gestion de l'offre"    include ObjectidFormatterSupport diff --git a/app/models/workgroup.rb b/app/models/workgroup.rb index 7e3e857ec..3e8409634 100644 --- a/app/models/workgroup.rb +++ b/app/models/workgroup.rb @@ -1,4 +1,4 @@ -class Workgroup < ActiveRecord::Base +class Workgroup < ApplicationModel    belongs_to :line_referential    belongs_to :stop_area_referential diff --git a/app/policies/route_policy.rb b/app/policies/route_policy.rb index 0337a5300..4fcb6be11 100644 --- a/app/policies/route_policy.rb +++ b/app/policies/route_policy.rb @@ -20,4 +20,8 @@ class RoutePolicy < ApplicationPolicy    def duplicate?      create?    end + +  def create_opposite? +    create? +  end  end diff --git a/app/services/route_way_cost_calculator.rb b/app/services/route_way_cost_calculator.rb index 2e30c94fc..d41a2e59a 100644 --- a/app/services/route_way_cost_calculator.rb +++ b/app/services/route_way_cost_calculator.rb @@ -5,7 +5,7 @@ class RouteWayCostCalculator    def calculate!      way_costs = StopAreasToWayCostsConverter.new(@route.stop_areas).convert -    way_costs = TomTom.batch(way_costs) +    way_costs = TomTom.matrix(way_costs)      way_costs = WayCostCollectionJSONSerializer.dump(way_costs)      @route.update(costs: way_costs)    end diff --git a/app/services/zip_service.rb b/app/services/zip_service.rb index 7166e6448..2402721fb 100644 --- a/app/services/zip_service.rb +++ b/app/services/zip_service.rb @@ -1,8 +1,8 @@  class ZipService -  class Subdir < Struct.new(:name, :stream, :spurious, :foreign_lines) +  class Subdir < Struct.new(:name, :stream, :spurious, :foreign_lines, :missing_calendar, :wrong_calendar)      def ok? -      foreign_lines.empty? && spurious.empty? +      foreign_lines.empty? && spurious.empty? && !missing_calendar && !wrong_calendar      end    end @@ -38,8 +38,21 @@ class ZipService      add_to_current_output entry    end +  def validate entry +    if is_calendar_file?(entry.name) +      @current_calendar_is_missing = false +      if wrong_calendar_data?(entry) +        @current_calendar_is_wrong = true +        return false +      end +    end +    return false if is_spurious?(entry.name) +    return false if is_foreign_line?(entry.name) +    true +  end +    def add_to_current_output entry -    return if is_spurious!(entry.name) || is_foreign_line!(entry.name) +    return unless validate(entry)      current_output.put_next_entry entry.name      write_to_current_output entry.get_input_stream @@ -48,7 +61,7 @@ class ZipService    def write_to_current_output input_stream      # the condition below is true for directory entries      return if Zip::NullInputStream == input_stream -    current_output.write input_stream.read  +    current_output.write input_stream.read    end    def finish_current_output @@ -58,7 +71,9 @@ class ZipService          # Second part of the solution, yield the closed stream          current_output.close_buffer,          current_spurious.to_a, -        foreign_lines) +        foreign_lines, +        @current_calendar_is_missing, +        @current_calendar_is_wrong)      end    end @@ -68,6 +83,8 @@ class ZipService      @current_output   = Zip::OutputStream.new(StringIO.new(''), true, nil)      @current_spurious = Set.new      @foreign_lines    = [] +    @current_calendar_is_missing = true +    @current_calendar_is_wrong = false    end    def entry_key entry @@ -75,7 +92,7 @@ class ZipService      entry.name.split('/').first    end -  def is_spurious! entry_name +  def is_spurious? entry_name      segments = entry_name.split('/', 3)      return false if segments.size < 3 @@ -83,11 +100,25 @@ class ZipService      return true    end -  def is_foreign_line! entry_name +  def is_foreign_line? entry_name      STIF::NetexFile::Frame.get_short_id(entry_name).tap do | line_object_id |        return nil unless line_object_id        return nil if line_object_id.in? allowed_lines        foreign_lines << line_object_id      end    end + +  def is_calendar_file? entry_name +    entry_name =~ /calendriers.xml$/ +  end + +  def wrong_calendar_data? entry +    content = entry.get_input_stream.read +    period = STIF::NetexFile::Frame.parse_calendars content.to_s +    return true unless period +    return true unless period.first +    return true unless period.end +    return true unless period.first <= period.end +    false +  end  end diff --git a/app/uploaders/custom_field_attachment_uploader.rb b/app/uploaders/custom_field_attachment_uploader.rb new file mode 100644 index 000000000..411b65bc3 --- /dev/null +++ b/app/uploaders/custom_field_attachment_uploader.rb @@ -0,0 +1,12 @@ +class CustomFieldAttachmentUploader < CarrierWave::Uploader::Base + +  storage :file + +  def store_dir +    "uploads/#{model.class.to_s.underscore}/#{mounted_as}/#{model.id}" +  end + +  def extension_whitelist +    model.send "#{mounted_as}_extension_whitelist" +  end +end diff --git a/app/views/autocomplete_calendars/autocomplete.rabl b/app/views/autocomplete_calendars/autocomplete.rabl index 3a7703c53..9a91e1d69 100644 --- a/app/views/autocomplete_calendars/autocomplete.rabl +++ b/app/views/autocomplete_calendars/autocomplete.rabl @@ -1,5 +1,5 @@  collection @calendars, :object_root => false -attribute :id, :name, :short_name, :shared +attribute :id, :name, :shared  node :text do |cal|    "<strong>" + cal.name + " - " + cal.id.to_s + "</strong>" diff --git a/app/views/autocomplete_purchase_windows/index.rabl b/app/views/autocomplete_purchase_windows/index.rabl index 1d0287602..bdc513c31 100644 --- a/app/views/autocomplete_purchase_windows/index.rabl +++ b/app/views/autocomplete_purchase_windows/index.rabl @@ -2,11 +2,12 @@ collection @purchase_windows, :object_root => false  node do |window|    { -    :id => window.id, -    :name => window.name, -    :objectid => window.objectid, -    :color => window.color, -    :short_id => window.get_objectid.short_id, -    :text => "<strong><span class='fa fa-circle' style='color:" + (window.color ? window.color : '#4b4b4b') + "'></span> " + window.name + " - " + window.get_objectid.short_id + "</strong>" +    id: window.id, +    name: window.name, +    objectid: window.objectid, +    color: window.color, +    short_id: window.get_objectid.short_id, +    bounding_dates: window.bounding_dates, +    text: "<strong><span class='fa fa-circle' style='color:" + (window.color ? window.color : '#4b4b4b') + "'></span> " + window.name + " - " + window.get_objectid.short_id + "</strong>"    }  end diff --git a/app/views/autocomplete_stop_areas/around.rabl b/app/views/autocomplete_stop_areas/around.rabl index d067dc4d0..116038639 100644 --- a/app/views/autocomplete_stop_areas/around.rabl +++ b/app/views/autocomplete_stop_areas/around.rabl @@ -15,6 +15,7 @@ child @stop_areas, root: :features, object_root: false do        area_type: Chouette::AreaType.find(s.area_type).label,        registration_number: s.registration_number,        stoparea_id: s.id, +      stoparea_kind: s.kind,        text: "#{s.name}, #{s.zip_code} #{s.city_name}",        user_objectid: s.user_objectid,        zip_code: s.zip_code, diff --git a/app/views/autocomplete_stop_areas/index.rabl b/app/views/autocomplete_stop_areas/index.rabl index c92b708f4..786f942d6 100644 --- a/app/views/autocomplete_stop_areas/index.rabl +++ b/app/views/autocomplete_stop_areas/index.rabl @@ -15,7 +15,8 @@ node do |stop_area|    :latitude => stop_area.latitude,    :area_type => Chouette::AreaType.find(stop_area.area_type).label,    :comment => stop_area.comment, -  :text => stop_area.full_name +  :text => stop_area.full_name, +  :kind => stop_area.kind    }  end diff --git a/app/views/autocomplete_stop_areas/show.rabl b/app/views/autocomplete_stop_areas/show.rabl index 73ce277cf..6ebf38900 100644 --- a/app/views/autocomplete_stop_areas/show.rabl +++ b/app/views/autocomplete_stop_areas/show.rabl @@ -9,7 +9,8 @@ node do |stop_area|    :short_name => truncate(stop_area.name, :length => 30) || "",    :zip_code => stop_area.zip_code || "",    :city_name => stop_area.city_name || "", -  :short_city_name => truncate(stop_area.city_name, :length => 15) || "" +  :short_city_name => truncate(stop_area.city_name, :length => 15) || "", +  :kind => stop_area.kind    }  end diff --git a/app/views/calendars/_filters.html.slim b/app/views/calendars/_filters.html.slim index 8bfe1974e..d7e2a927e 100644 --- a/app/views/calendars/_filters.html.slim +++ b/app/views/calendars/_filters.html.slim @@ -1,7 +1,7 @@  = search_form_for @q, url: workgroup_calendars_path(@workgroup), builder: SimpleForm::FormBuilder, html: { method: :get, class: 'form form-filter' } do |f|    .ffg-row      .input-group.search_bar class=filter_item_class(params[:q], :name_or_short_name_cont) -      = f.search_field :name_or_short_name_cont, class: 'form-control', placeholder: 'Indiquez un nom/nom court de calendrier...' +      = f.search_field :name_cont, class: 'form-control', placeholder: I18n.t('calendars.filters.name_cont')        span.input-group-btn          button.btn.btn-default#search_btn type='submit'            span.fa.fa-search @@ -10,13 +10,13 @@      .form-group.togglable class=filter_item_class(params[:q], :shared_true)        = f.label Calendar.human_attribute_name(:shared), required: false, class: 'control-label'        .form-group.checkbox_list -        = f.input :shared_true, as: :boolean, label: ("<span>Oui</span>").html_safe, wrapper_html: { class: 'checkbox-wrapper' } -        = f.input :shared_false, as: :boolean, label: ("<span>Non</span>").html_safe, wrapper_html: { class: 'checkbox-wrapper' } +        = f.input :shared_true, as: :boolean, label: ("<span>#{I18n.t('yes')}</span>").html_safe, wrapper_html: { class: 'checkbox-wrapper' } +        = f.input :shared_false, as: :boolean, label: ("<span>#{I18n.t('no')}</span>").html_safe, wrapper_html: { class: 'checkbox-wrapper' }      .form-group class=filter_item_class(params[:q], :contains_date)        = f.label Calendar.human_attribute_name(:date), class: 'control-label'        = f.input :contains_date, as: :date, label: false, wrapper_html: { class: 'date smart_date' }, class: 'form-control', include_blank: true    .actions -    = link_to 'Effacer', workgroup_calendars_path(@workgroup), class: 'btn btn-link' -    = f.submit 'Filtrer', id: 'calendar_filter_btn', class: 'btn btn-default' +    = link_to I18n.t('actions.erase'), workgroup_calendars_path(@workgroup), class: 'btn btn-link' +    = f.submit I18n.t('actions.filter'), id: 'calendar_filter_btn', class: 'btn btn-default' diff --git a/app/views/calendars/_form_simple.html.slim b/app/views/calendars/_form_simple.html.slim index ba18c765b..a87a3dab5 100644 --- a/app/views/calendars/_form_simple.html.slim +++ b/app/views/calendars/_form_simple.html.slim @@ -4,8 +4,7 @@        .row          .col-lg-12            = f.input :name -          = f.input :short_name - +                      - if policy(@calendar).share?              .form-group.has_switch                = f.label :shared, class: 'col-sm-4 col-xs-5 control-label' @@ -33,24 +32,24 @@          .separator -      .row -        .col-lg-12 -          .subform -            .nested-head -              .wrapper -                div -                  .form-group -                    label.control-label -                      = t('simple_form.labels.calendar.ranges.begin') -                div -                  .form-group -                    label.control-label -                      = t('simple_form.labels.calendar.ranges.end') -                div - -            = f.simple_fields_for :periods do |period| -              = render 'period_fields', f: period -            .links.nested-linker -              = link_to_add_association t('simple_form.labels.calendar.add_a_date_range'), f, :periods, class: 'btn btn-outline-primary' +        .row +          .col-lg-12 +            .subform +              .nested-head +                .wrapper +                  div +                    .form-group +                      label.control-label +                        = t('simple_form.labels.calendar.ranges.begin') +                  div +                    .form-group +                      label.control-label +                        = t('simple_form.labels.calendar.ranges.end') +                  div + +              = f.simple_fields_for :periods do |period| +                = render 'period_fields', f: period +              .links.nested-linker +                = link_to_add_association t('simple_form.labels.calendar.add_a_date_range'), f, :periods, class: 'btn btn-outline-primary'        = f.button :submit, t('actions.submit'), class: 'btn btn-default formSubmitr', form: 'calendar_form' diff --git a/app/views/calendars/index.html.slim b/app/views/calendars/index.html.slim index 0b58c0c72..2c87a6f7a 100644 --- a/app/views/calendars/index.html.slim +++ b/app/views/calendars/index.html.slim @@ -20,10 +20,6 @@                  end \                ), \                TableBuilderHelper::Column.new( \ -                key: :short_name, \ -                attribute: 'short_name' \ -              ), \ -              TableBuilderHelper::Column.new( \                  key: :organisation, \                  attribute: Proc.new { |c| c.organisation.name } \                ), \ @@ -39,6 +35,6 @@      - unless @calendars.any?        .row.mt-xs          .col-lg-12 -          = replacement_msg t('calendars.search_no_results') +          = replacement_msg t('.search_no_results')  = javascript_pack_tag 'date_filters' diff --git a/app/views/calendars/show.html.slim b/app/views/calendars/show.html.slim index cec4f66a5..880db99f6 100644 --- a/app/views/calendars/show.html.slim +++ b/app/views/calendars/show.html.slim @@ -6,11 +6,10 @@      .row        .col-lg-6.col-md-6.col-sm-12.col-xs-12          = definition_list t('metadatas'), -          { 'Nom court' => resource.try(:short_name), -            Calendar.human_attribute_name(:shared) => t("#{resource.shared}"), -            'Organisation' => resource.organisation.name, -            Calendar.human_attribute_name(:dates) =>  resource.dates.collect{|d| l(d, format: :short)}.join(', ').html_safe, -            Calendar.human_attribute_name(:date_ranges) => resource.periods.map{|d| t('validity_range', debut: l(d.begin, format: :short), end: l(d.end, format: :short))}.join('<br>').html_safe } +          { Calendar.tmf('shared') => t("#{resource.shared}"), +            Calendar.tmf('organisation') => resource.organisation.name, +            Calendar.tmf('dates') =>  resource.dates.collect{|d| l(d, format: :short)}.join(', ').html_safe, +            Calendar.tmf('date_ranges') => resource.periods.map{|d| t('validity_range', debut: l(d.begin, format: :short), end: l(d.end, format: :short))}.join('<br>').html_safe }      - if has_feature?('application_days_on_calendars')        .row @@ -18,7 +17,7 @@            .pagination.pull-right              = @year              .page_links -              = link_to '', calendar_path(@calendar, year: (@year - 1)), class: 'previous_page' -              = link_to '', calendar_path(@calendar, year: (@year + 1)), class: 'next_page' +              = link_to '', workgroup_calendar_path(@workgroup, @calendar, year: (@year - 1)), class: 'previous_page' +              = link_to '', workgroup_calendar_path(@workgroup, @calendar, year: (@year + 1)), class: 'next_page'        = render 'time_tables/show_time_table', time_table: @calendar diff --git a/app/views/companies/_form.html.slim b/app/views/companies/_form.html.slim index 3979c5800..e8b3fcede 100644 --- a/app/views/companies/_form.html.slim +++ b/app/views/companies/_form.html.slim @@ -12,7 +12,9 @@        = f.input :time_zone, include_blank: true        = f.input :url        = f.input :registration_number, :input_html => {:title => t("formtastic.titles#{format_restriction_for_locales(@line_referential)}.company.registration_number")} - +      - if resource.custom_fields(current_referential.workgroup).any? +        - resource.custom_fields.each do |code, field| +          = field.input(f).to_s    .separator    = f.button :submit, t('actions.submit'), class: 'btn btn-default formSubmitr', form: 'company_form' diff --git a/app/views/companies/new.html.slim b/app/views/companies/new.html.slim index cc085ffe2..1747b8e10 100644 --- a/app/views/companies/new.html.slim +++ b/app/views/companies/new.html.slim @@ -1,4 +1,4 @@ -- breadcrumb :lines, @line_referential +- breadcrumb :companies, @line_referential  .page_content    .container-fluid      .row diff --git a/app/views/companies/show.html.slim b/app/views/companies/show.html.slim index ca0a410b3..8960b92dd 100644 --- a/app/views/companies/show.html.slim +++ b/app/views/companies/show.html.slim @@ -6,8 +6,11 @@    .container-fluid      .row        .col-lg-6.col-md-6.col-sm-12.col-xs-12 -        = definition_list t('metadatas'), -          { 'ID Codif' => @company.try(:get_objectid).try(:short_id), -            Chouette::Company.human_attribute_name(:phone) => resource.phone, -            Chouette::Company.human_attribute_name(:email) => resource.email, -            Chouette::Company.human_attribute_name(:url) => resource.url } +        - attributes = { t('id_codif') => @company.try(:objectid).try(:local_id), +          Chouette::Company.human_attribute_name(:phone) => @company.phone, +          Chouette::Company.human_attribute_name(:email) => @company.email, +          Chouette::Company.human_attribute_name(:url) => @company.url } +        - @company.custom_fields(current_referential.workgroup).each do |code, field| +          - attributes.merge!(field.name => field.display_value) + +        = definition_list t('metadatas'), attributes diff --git a/app/views/compliance_check_sets/show.html.slim b/app/views/compliance_check_sets/show.html.slim index b54bf6c5c..4e1a8e2f9 100644 --- a/app/views/compliance_check_sets/show.html.slim +++ b/app/views/compliance_check_sets/show.html.slim @@ -39,7 +39,7 @@                attribute: Proc.new { |n| I18n.t('compliance_check_sets.show.metrics', n.metrics.deep_symbolize_keys) } \              ), \              TableBuilderHelper::Column.new( \ -              name: 'Téléchargement' , \ +              key: :download , \                attribute: Proc.new { |n| '<i class="fa fa-download" aria-hidden="true"></i>'.html_safe }, \                sortable: false, \                link_to: lambda do |compliance_check_resource| \ diff --git a/app/views/compliance_checks/show.html.slim b/app/views/compliance_checks/show.html.slim index 3b3861e0c..535fce67d 100644 --- a/app/views/compliance_checks/show.html.slim +++ b/app/views/compliance_checks/show.html.slim @@ -9,5 +9,5 @@          = render partial: "shared/controls/metadatas"          - if resource.compliance_check_block            = definition_list t('compliance_controls.show.metadatas.compliance_check_block'), -            I18n.t('activerecord.attributes.compliance_control_blocks.transport_mode') => I18n.t("enumerize.transport_mode.#{resource.compliance_check_block.transport_mode}"), -            I18n.t('activerecord.attributes.compliance_control_blocks.transport_submode') => resource.compliance_check_block.transport_submode.empty? ? I18n.t("enumerize.transport_submode.undefined") : I18n.t("enumerize.transport_submode.#{resource.compliance_check_block.transport_submode}") +            ComplianceCheckBlock.tmf('transport_mode') => I18n.t("enumerize.transport_mode.#{resource.compliance_check_block.transport_mode}"), +            ComplianceCheckBlock.tmf('transport_submode') => resource.compliance_check_block.transport_submode.empty? ? I18n.t("enumerize.transport_submode.undefined") : I18n.t("enumerize.transport_submode.#{resource.compliance_check_block.transport_submode}") diff --git a/app/views/compliance_control_blocks/_form.html.slim b/app/views/compliance_control_blocks/_form.html.slim index 2e87a877e..e8ae63384 100644 --- a/app/views/compliance_control_blocks/_form.html.slim +++ b/app/views/compliance_control_blocks/_form.html.slim @@ -1,6 +1,12 @@  = simple_form_for [@compliance_control_set, @compliance_control_block], html: { class: 'form-horizontal', id: 'compliance_control_block_form' }, wrapper: :horizontal_form do |f|    .row      .col-lg-12 +      - if @compliance_control_block.errors.has_key? :condition_attributes +        .row.condition-attributes-errors +          .col-lg-12 +            .alert.alert-danger +              - @compliance_control_block.errors[:condition_attributes].each do |msg| +                p.small = "- #{msg}"        .form-group          = f.input :transport_mode, as: :select, collection: ComplianceControlBlock.sorted_transport_modes, label: t('activerecord.attributes.compliance_control_blocks.transport_mode'), label_method: lambda {|t| ("<span>" + t("enumerize.transport_mode.#{t}") + "</span>").html_safe }          = f.input :transport_submode, as: :select, collection: ComplianceControlBlock.sorted_transport_submodes, label: t('activerecord.attributes.compliance_control_blocks.transport_submode'), label_method: lambda {|t| ("<span>" + t("enumerize.transport_submode.#{t}") + "</span>").html_safe } diff --git a/app/views/compliance_control_blocks/edit.html.slim b/app/views/compliance_control_blocks/edit.html.slim index 49aee7705..1d32256b0 100644 --- a/app/views/compliance_control_blocks/edit.html.slim +++ b/app/views/compliance_control_blocks/edit.html.slim @@ -1,3 +1,4 @@ +- breadcrumb :compliance_control_set, compliance_control_set  - page_header_content_for @compliance_control_block  .page_content diff --git a/app/views/compliance_control_blocks/new.html.slim b/app/views/compliance_control_blocks/new.html.slim index 7d2551311..aab40572b 100644 --- a/app/views/compliance_control_blocks/new.html.slim +++ b/app/views/compliance_control_blocks/new.html.slim @@ -1,3 +1,5 @@ +- breadcrumb :compliance_control_set, compliance_control_set +  .page_content    .container-fluid      .row diff --git a/app/views/compliance_control_sets/_filters.html.slim b/app/views/compliance_control_sets/_filters.html.slim index 5cf282559..5f6d9e27b 100644 --- a/app/views/compliance_control_sets/_filters.html.slim +++ b/app/views/compliance_control_sets/_filters.html.slim @@ -8,7 +8,7 @@    .ffg-row      .form-group.togglable class=filter_item_class(params[:q], :organisation_name_eq_any)        = f.label t('activerecord.models.organisation.one'), required: false, class: 'control-label' -      = f.input :organisation_name_eq_any, collection: organisations_filters_values, as: :check_boxes, label: false, label_method: lambda {|w| ("<span>#{w.name}</span>").html_safe}, required: false, wrapper_html: {class: 'checkbox_list'} +      = f.input :organisation_name_eq_any, collection: organisations_filters_values, as: :check_boxes, value_method: :name, label: false, label_method: lambda {|w| ("<span>#{w.name}</span>").html_safe}, required: false, wrapper_html: {class: 'checkbox_list'}      .form-group.togglable class=filter_item_class(params[:q], :updated_at)        = f.label Import::Base.human_attribute_name(:updated_at), required: false, class: 'control-label' diff --git a/app/views/compliance_control_sets/show.html.slim b/app/views/compliance_control_sets/show.html.slim index 59100681d..729c1ce43 100644 --- a/app/views/compliance_control_sets/show.html.slim +++ b/app/views/compliance_control_sets/show.html.slim @@ -6,8 +6,8 @@      .row        .col-lg-6.col-md-6.col-sm-12.col-xs-12          = definition_list t('metadatas'), -            ComplianceControlSet.human_attribute_name(:name) => @compliance_control_set.name, -            I18n.t('activerecord.attributes.compliance_control_set.owner_jdc') => @compliance_control_set.organisation.name +            ComplianceControlSet.tmf('name') => @compliance_control_set.name, +            ComplianceControlSet.tmf('owner_jdc') => @compliance_control_set.organisation.name    - if params[:q].present? || @blocks_to_compliance_controls_map.any? || @direct_compliance_controls      .row diff --git a/app/views/dashboards/_dashboard.html.slim b/app/views/dashboards/_dashboard.html.slim index 2f0791f50..466695b5a 100644 --- a/app/views/dashboards/_dashboard.html.slim +++ b/app/views/dashboards/_dashboard.html.slim @@ -5,8 +5,8 @@          .panel-heading            h3.panel-title.with_actions              div -              = link_to workbench.name, workbench_path(workbench) -              span.badge.ml-xs = workbench.referentials.count if workbench.referentials.present? +              = link_to t('dashboards.workbench.title', organisation: workbench.organisation.name), workbench_path(workbench) +              span.badge.ml-xs = workbench.all_referentials.uniq.count if workbench.all_referentials.present?              div                = link_to '', workbench_path(workbench), class: ' fa fa-chevron-right pull-right', title: t('workbenches.index.offers.see') @@ -23,6 +23,7 @@          .panel-heading            h3.panel-title.with_actions              = link_to I18n.t("activerecord.models.calendar", count: workbench.calendars.size), workgroup_calendars_path(workbench.workgroup) +            span.badge.ml-xs = workbench.calendars.count if workbench.calendars.present?              div                = link_to '', workgroup_calendars_path(workbench.workgroup), class: ' fa fa-chevron-right pull-right'          - if workbench.calendars.present? @@ -39,7 +40,7 @@        - @dashboard.current_organisation.stop_area_referentials.each do |referential|          .panel-heading            h3.panel-title -            = referential.name +            = t('dashboards.stop_area_referentials.title')          .list-group            = link_to Chouette::StopArea.model_name.human.pluralize.capitalize, stop_area_referential_stop_areas_path(referential), class: 'list-group-item' @@ -47,7 +48,7 @@        - @dashboard.current_organisation.line_referentials.all.each do |referential|          .panel-heading            h3.panel-title -            = referential.name +            = t('dashboards.line_referentials.title')          .list-group              = link_to Chouette::Line.model_name.human.pluralize.capitalize, line_referential_lines_path(referential), class: 'list-group-item'              = link_to Chouette::Company.model_name.human.pluralize.capitalize, line_referential_companies_path(referential), class: 'list-group-item' diff --git a/app/views/imports/show.html.slim b/app/views/imports/show.html.slim index 48a4f334c..9d0a6423d 100644 --- a/app/views/imports/show.html.slim +++ b/app/views/imports/show.html.slim @@ -6,7 +6,7 @@    .container-fluid      .row        .col-lg-6.col-md-6.col-sm-12.col-xs-12 -        = definition_list t('metadatas'), { 'Récupération des données' => '-', "Nom de l'archive" => @import.try(:file_identifier)} +        = definition_list t('metadatas'), { t('.data_recovery') => '-', t('.filename') => @import.try(:file_identifier)}      .row        .col-lg-12 @@ -19,7 +19,7 @@            = table_builder_2 @import.children,              [ \                TableBuilderHelper::Column.new( \ -                name: 'Nom du jeu de données', \ +                name: t('.referential_name'), \                  attribute: 'name', \                  sortable: false, \                  link_to: lambda do |import| \ @@ -35,12 +35,12 @@                  end \                ), \                TableBuilderHelper::Column.new( \ -                name: 'Contrôle STIF', \ +                name: t('.stif_control'), \                  attribute: '', \                  sortable: false, \                ), \                TableBuilderHelper::Column.new( \ -                name: 'Contrôle organisation', \ +                name: t('.organisation_control'), \                  attribute: '', \                  sortable: false, \                ) \ @@ -49,11 +49,11 @@              overhead: [ \                {}, \                { \ -                title: "#{@import.children_succeedeed} jeu de données validé sur #{@import.children.count} présent(s) dans l'archive", \ +                title: I18n.t('imports.show.results', count: @import.children_succeedeed, total: @import.children.count), \                  width: 1, \                  cls: "#{@import.import_status_css_class} full-border" \                }, { \ -                title: 'Bilan des jeux de contrôles d\'import <span title="Lorem ipsum..." class="fa fa-lg fa-info-circle text-info"></span>', \ +                title: I18n.t('imports.show.summary').html_safe, \                  width: 2, \                  cls: 'overheaded-default colspan="2"' \                } \ 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 02614dcab..3741ef19b 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 @@ -5,44 +5,44 @@      .panel-heading        h4.panel-title          = link_to '#miOne', data: {toggle: 'collapse', parent: '#menu-items'}, 'aria-expanded' => 'false' do -          |Offres courantes +          = t('layouts.navbar.current_offer.other')      #miOne.panel-collapse.collapse        .list-group          = link_to root_path, class: "list-group-item #{(@localizationUrl == 'workbenches#index') ? 'active' : ''}" do -          span Tableau de bord +          span = t('layouts.navbar.dashboard') +        = link_to workbench_output_path(current_user.workbenches.first), class: 'list-group-item' do +          span = t('layouts.navbar.workbench_outputs.organisation')          = link_to '#', class: 'list-group-item' do -          span Offre de mon organisation -        = link_to '#', class: 'list-group-item' do -          span Offre IDF +          span = t('layouts.navbar.workbench_outputs.idf')    .menu-item.panel      .panel-heading        h4.panel-title          = link_to '#miTwo', data: {toggle: 'collapse', parent: '#menu-items'}, 'aria-expanded' => 'false' do -          |Espace de travail +          - t('activerecord.models.workbench.one').capitalize      #miTwo.panel-collapse.collapse        .list-group          - 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 +            span = t('activerecord.models.referential.other').capitalize            = link_to workbench_imports_path(current_workbench), class: "list-group-item #{(params[:controller] == 'imports') ? 'active' : ''}" do -            span Import +            span = t('activerecord.models.import.other').capitalize            = link_to workbench_exports_path(current_workbench), class: "list-group-item #{(params[:controller] == 'exports') ? 'active' : ''}" do -            span Export +            span = t('activerecord.models.export.other').capitalize            = link_to workgroup_calendars_path(current_workbench.workgroup), class: 'list-group-item' do -            span Modèles de calendrier +            span = t('activerecord.models.calendar.other').capitalize            = link_to workbench_compliance_check_sets_path(current_workbench), class: 'list-group-item' do -            span Rapport de contrôle +            span = t('activerecord.models.compliance_check_set.other').capitalize            = link_to compliance_control_sets_path, class: 'list-group-item' do -            span Jeux de contrôle +            span = t('activerecord.models.compliance_control_set.other').capitalize    .menu-item.panel      .panel-heading        h4.panel-title          = link_to '#miThree', data: {toggle: 'collapse', parent: '#menu-items'}, 'aria-expanded' => 'false' do -          |Données +          = t('layouts.navbar.referential_datas')      #miThree.panel-collapse.collapse        - if @referential.try(:id) && respond_to?(:current_referential) @@ -57,7 +57,7 @@                  span = t('companies.index.title')                = link_to '#', class: 'list-group-item disabled' do -                span Tracés +                span = t('layouts.navbar.shapes')                = link_to referential_time_tables_path(current_referential), class: 'list-group-item' do                  span = t('time_tables.index.title') @@ -65,45 +65,45 @@        - else          .panel-body            em.text-muted -            = "Sélectionnez un jeu de données pour accéder à plus de fonctionnalités" +            = t('layouts.navbar.select_referential_for_more_features')    .menu-item.panel      .panel-heading        h4.panel-title          = link_to '#miFour', data: {toggle: 'collapse', parent: '#menu-items'}, 'aria-expanded' => 'false' do -          |Synchronisation +          = t('layouts.navbar.sync')      #miFour.panel-collapse.collapse        .list-group          = link_to line_referential_path(1), class: "list-group-item #{(@localizationUrl == 'line_referentials#show') ? 'active' : ''}" do -          span Synchronisation iLICO +          span = t('layouts.navbar.sync_ilico')          = link_to stop_area_referential_path(1), class: "list-group-item #{(@localizationUrl == 'stop_area_referentials#show') ? 'active' : ''}" do -          span Synchronisation iCAR +          span = t('layouts.navbar.sync_icar')    .menu-item.panel      .panel-heading        h4.panel-title          = link_to '#miFive', data: {toggle: 'collapse', parent: '#menu-items'}, 'aria-expanded' => 'false' do -          |Outils +          = t('layouts.navbar.tools')      #miFive.panel-collapse.collapse        .list-group          = link_to Rails.application.config.try(:portal_url), target: '_blank', class: 'list-group-item' do            span              span.fa.fa-2x.fa-circle -            |Portail (POSTIF) +            = t('layouts.navbar.portal')          = link_to Rails.application.config.try(:codifligne_url), target: '_blank', class: 'list-group-item' do            span              span.fa.fa-2x.fa-circle -            |iLICO +            = t('layouts.navbar.ilico')          = link_to Rails.application.config.try(:reflex_url), target: '_blank', class: 'list-group-item' do            span              span.fa.fa-2x.fa-circle -            |iCAR +            = t('layouts.navbar.icar')          = link_to '#', target: '_blank', class: 'list-group-item' do            span              span.fa.fa-2x.fa-circle -            |Support +            = t('layouts.navbar.support')
\ No newline at end of file diff --git a/app/views/layouts/navigation/_nav_panel_operations.html.slim b/app/views/layouts/navigation/_nav_panel_operations.html.slim index 8dce829cd..1c5a1f14b 100644 --- a/app/views/layouts/navigation/_nav_panel_operations.html.slim +++ b/app/views/layouts/navigation/_nav_panel_operations.html.slim @@ -1,5 +1,5 @@  #operations_panel.nav_panel    .panel-title -    h2 Opérations +    h2 = t('layouts.operations')    .panel-body      p = "Lorem ipsum dolor sit amet..." diff --git a/app/views/layouts/navigation/_nav_panel_profile.html.slim b/app/views/layouts/navigation/_nav_panel_profile.html.slim index bcbf89e67..b0dee5d53 100644 --- a/app/views/layouts/navigation/_nav_panel_profile.html.slim +++ b/app/views/layouts/navigation/_nav_panel_profile.html.slim @@ -1,6 +1,6 @@  #profile_panel.nav_panel    .panel-title -    h2 Mon Profil +    h2 = t('layouts.user.profile')    .panel-body      p = current_user.name      p = current_organisation.name diff --git a/app/views/lines/_filters.html.slim b/app/views/lines/_filters.html.slim index da0539bd0..f745d10a4 100644 --- a/app/views/lines/_filters.html.slim +++ b/app/views/lines/_filters.html.slim @@ -44,5 +44,5 @@    .actions -    = link_to 'Effacer', @workbench, class: 'btn btn-link' -    = f.submit 'Filtrer', class: 'btn btn-default' +    = link_to t('actions.erase'), @workbench, class: 'btn btn-link' +    = f.submit t('actions.filter'), class: 'btn btn-default' diff --git a/app/views/lines/show.html.slim b/app/views/lines/show.html.slim index 96bb5bb0d..9e1ae6d6f 100644 --- a/app/views/lines/show.html.slim +++ b/app/views/lines/show.html.slim @@ -6,7 +6,7 @@      .row        .col-lg-6.col-md-6.col-sm-12.col-xs-12          = definition_list t('metadatas'), -          {  t('id_codif') => @line.get_objectid.short_id, +          {  t('objectid') => @line.get_objectid.short_id,               @line.human_attribute_name(:deactivated) => (@line.deactivated? ? t('false') : t('true')),               @line.human_attribute_name(:network_id) => (@line.network.nil? ? t('lines.index.unset') : @line.network.name),               @line.human_attribute_name(:company_id) => (@line.company.nil? ? t('lines.index.unset') : @line.company.name), diff --git a/app/views/referential_companies/_form.html.slim b/app/views/referential_companies/_form.html.slim index b02eab3f1..0e7b20af4 100644 --- a/app/views/referential_companies/_form.html.slim +++ b/app/views/referential_companies/_form.html.slim @@ -1,18 +1,19 @@ -= semantic_form_for [@referential, @company] do |form| -  = form.inputs do -    = form.input :name, :input_html => {  :title => t("formtastic.titles#{format_restriction_for_locales(@referential)}.company.name") } -    = form.input :short_name -    = form.input :organizational_unit -    = form.input :operating_department_name -    = form.input :code -    = form.input :phone, as: :phone -    = form.input :fax, as: :phone -    = form.input :email, as: :email -    = form.input :time_zone, include_blank: true -    = form.input :url -    = form.input :registration_number, :input_html => { :title => t("formtastic.titles#{format_restriction_for_locales(@referential)}.company.registration_number") } -    = form.input :objectid, :required => !@company.new_record?, :input_html => { :title => t("formtastic.titles#{format_restriction_for_locales(@referential)}.company.objectid") } - -  = form.actions do -    = form.action :submit, as: :button -    = form.action :cancel, as: :link
\ No newline at end of file += simple_form_for [@referential, @company], html: {class: 'form-horizontal', id: 'company_form'}, wrapper: :horizontal_form do |f| +  .row +    .col-lg-12 +      = f.input :name, :input_html => {:title => t("formtastic.titles#{format_restriction_for_locales(@line_referential)}.company.name")} +      = f.input :short_name +      = f.input :organizational_unit +      = f.input :operating_department_name +      = f.input :code +      = f.input :phone +      = f.input :fax +      = f.input :email, as: :email +      = f.input :time_zone, include_blank: true +      = f.input :url +      = f.input :registration_number, :input_html => {:title => t("formtastic.titles#{format_restriction_for_locales(@line_referential)}.company.registration_number")} +      - if resource.custom_fields(@referential.workgroup).any? +        - resource.custom_fields.each do |code, field| +          = field.input(f).to_s +  .separator +  = f.button :submit, t('actions.submit'), class: 'btn btn-default formSubmitr', form: 'company_form' diff --git a/app/views/referential_companies/edit.html.slim b/app/views/referential_companies/edit.html.slim index b3fcf6cd8..0c9fb1f87 100644 --- a/app/views/referential_companies/edit.html.slim +++ b/app/views/referential_companies/edit.html.slim @@ -1,3 +1,8 @@  - breadcrumb :referential_company, @referential, @company  - page_header_content_for @company -= render 'form' + +.page_content +  .container-fluid +    .row +      .col-lg-8.col-lg-offset-2.col-md-8.col-md-offset-2.col-sm-10.col-sm-offset-1 +        = render 'form' diff --git a/app/views/referential_companies/new.html.slim b/app/views/referential_companies/new.html.slim index 5e59db139..1dfdc8eb5 100644 --- a/app/views/referential_companies/new.html.slim +++ b/app/views/referential_companies/new.html.slim @@ -1,2 +1,6 @@  - breadcrumb :referential_companies, @referential -= render 'form' +.page_content +  .container-fluid +    .row +      .col-lg-8.col-lg-offset-2.col-md-8.col-md-offset-2.col-sm-10.col-sm-offset-1 +        = render 'form' diff --git a/app/views/referential_companies/show.html.slim b/app/views/referential_companies/show.html.slim index 1599145be..8ad011edf 100644 --- a/app/views/referential_companies/show.html.slim +++ b/app/views/referential_companies/show.html.slim @@ -17,8 +17,11 @@    .container-fluid      .row        .col-lg-6.col-md-6.col-sm-12.col-xs-12 -        = definition_list t('metadatas'), -          { t('id_codif') => @company.try(:objectid).try(:local_id), -            Chouette::Company.human_attribute_name(:phone) => @company.phone, -            Chouette::Company.human_attribute_name(:email) => @company.email, -            Chouette::Company.human_attribute_name(:url) => @company.url } +        - attributes = { t('id_codif') => @company.try(:objectid).try(:local_id), +          Chouette::Company.human_attribute_name(:phone) => @company.phone, +          Chouette::Company.human_attribute_name(:email) => @company.email, +          Chouette::Company.human_attribute_name(:url) => @company.url } +        - @company.custom_fields(@referential.workgroup).each do |code, field| +          - attributes.merge!(field.name => field.display_value) + +        = definition_list t('metadatas'), attributes diff --git a/app/views/referential_lines/_filters.html.slim b/app/views/referential_lines/_filters.html.slim index 501f61c16..15db0e33e 100644 --- a/app/views/referential_lines/_filters.html.slim +++ b/app/views/referential_lines/_filters.html.slim @@ -1,7 +1,7 @@  = search_form_for @q, url: referential_line_path(@referential, @line), class: 'form form-filter' do |f|    .ffg-row      .input-group.search_bar class=filter_item_class(params[:q], :name_or_objectid_cont) -      = f.search_field :name_or_objectid_cont, class: 'form-control', placeholder: "Indiquez un nom d'itinéraire ou un ID..." +      = f.search_field :name_or_objectid_cont, class: 'form-control', placeholder: t('.name_or_objectid_cont')        span.input-group-btn          button.btn.btn-default#search-btn type='submit'            span.fa.fa-search @@ -12,5 +12,5 @@        = f.input :wayback_eq_any, class: 'form-control', collection: Chouette::Route.wayback.values, as: :check_boxes, label: false, required: false, wrapper_html: { class: 'checkbox_list'}, label_method: lambda{|l| ("<span>" + t("enumerize.route.wayback.#{l}") + "</span>").html_safe}    .actions -    = link_to 'Effacer', referential_line_path(@referential, @line), class: 'btn btn-link' -    = f.submit 'Filtrer', class: 'btn btn-default' +    = link_to t('actions.erase'), referential_line_path(@referential, @line), class: 'btn btn-link' +    = f.submit t('actions.filter'), class: 'btn btn-default' diff --git a/app/views/referential_lines/show.html.slim b/app/views/referential_lines/show.html.slim index ef32ef6b0..91868a002 100644 --- a/app/views/referential_lines/show.html.slim +++ b/app/views/referential_lines/show.html.slim @@ -7,16 +7,16 @@        .col-lg-6.col-md-6.col-sm-12.col-xs-12          = definition_list t('metadatas'),            {  t('id_codif') => @line.get_objectid.short_id, -             'Activé' => (@line.deactivated? ? t('false') : t('true')), -             @line.human_attribute_name(:network) => (@line.network.nil? ? t('lines.index.unset') : link_to(@line.network.name, [@referential, @line.network]) ), -             @line.human_attribute_name(:company) => (@line.company.nil? ? t('lines.index.unset') : link_to(@line.company.name, [@referential, @line.company]) ), -             'Transporteur(s) secondaire(s)' => (@line.secondary_companies.nil? ? t('lines.index.unset') : @line.secondary_companies.collect(&:name).join(', ')), -             'Nom court' => @line.number, -             'Code public' => (@line.registration_number ? @line.registration_number : '-'), -             @line.human_attribute_name(:transport_mode) => (@line.transport_mode.present? ? t("enumerize.transport_mode.#{@line.transport_mode}") : '-'), -             @line.human_attribute_name(:transport_submode) => (@line.transport_submode.present? ? t("enumerize.transport_submode.#{@line.transport_submode}") : '-'), -             @line.human_attribute_name(:url) => (@line.url ? @line.url : '-'), -             @line.human_attribute_name(:seasonal) => (@line.seasonal? ? t('true') : t('false')),} +             Chouette::Line.tmf('activated') => (@line.deactivated? ? t('false') : t('true')), +             Chouette::Line.tmf('network_id') => (@line.network.nil? ? t('lines.index.unset') : link_to(@line.network.name, [@referential, @line.network]) ), +             Chouette::Line.tmf('company') => (@line.company.nil? ? t('lines.index.unset') : link_to(@line.company.name, [@referential, @line.company]) ), +             Chouette::Line.tmf('secondary_company') => (@line.secondary_companies.nil? ? t('lines.index.unset') : @line.secondary_companies.collect(&:name).join(', ')), +             Chouette::Line.tmf('registration_number') => @line.number, +             Chouette::Line.tmf('published_name') => (@line.registration_number ? @line.registration_number : '-'), +             Chouette::Line.tmf('transport_mode') => (@line.transport_mode.present? ? t("enumerize.transport_mode.#{@line.transport_mode}") : '-'), +             Chouette::Line.tmf('transport_submode') => (@line.transport_submode.present? ? t("enumerize.transport_submode.#{@line.transport_submode}") : '-'), +             Chouette::Line.tmf('url') => (@line.url ? @line.url : '-'), +             Chouette::Line.tmf('seasonal') => (@line.seasonal? ? t('true') : t('false')),}        .col-lg-6.col-md-6.col-sm-12.col-xs-12          #routes_map.map.mb-lg      .row @@ -53,12 +53,12 @@                      attribute: 'wayback_text' \                    ), \                    TableBuilderHelper::Column.new( \ -                    name: 'Arrêt de départ', \ +                    name: Chouette::Route.tmf('stop_area_departure'), \                      attribute: Proc.new { |r| r.try(:stop_points).first.try(:stop_area).try(:name) }, \                      sortable: false \                    ), \                    TableBuilderHelper::Column.new( \ -                    name: "Arrêt d'arrivée", \ +                    name: Chouette::Route.tmf('stop_area_arrival'), \                      attribute: Proc.new{ |r| r.try(:stop_points).last.try(:stop_area).try(:name) }, \                      sortable: false \                    ), \ @@ -79,7 +79,7 @@          - unless @routes.any?            .row.mt-xs              .col-lg-12 -              = replacement_msg t('routes.search_no_results') +              = replacement_msg t('routes.filters.no_results')  = javascript_tag do    | window.routes = "#{URI.escape(@routes.select{|r| r.wayback == :outbound}.map{|r| {name: r.name, id: r.id, stops: route_json_for_edit(r, serialize: false)}}.to_json)}" diff --git a/app/views/referential_vehicle_journeys/_filters.html.slim b/app/views/referential_vehicle_journeys/_filters.html.slim index f1fbdb5d8..f9fa4fcf7 100644 --- a/app/views/referential_vehicle_journeys/_filters.html.slim +++ b/app/views/referential_vehicle_journeys/_filters.html.slim @@ -68,5 +68,5 @@    .actions -    = link_to 'Effacer', referential_vehicle_journeys_path(@referential), class: 'btn btn-link' -    = f.submit 'Filtrer', class: 'btn btn-default' +    = link_to t('actions.erase'), referential_vehicle_journeys_path(@referential), class: 'btn btn-link' +    = f.submit t('actions.filter'), class: 'btn btn-default' diff --git a/app/views/referentials/_form.html.slim b/app/views/referentials/_form.html.slim index 96d847ec1..c378f871e 100644 --- a/app/views/referentials/_form.html.slim +++ b/app/views/referentials/_form.html.slim @@ -49,7 +49,7 @@      .separator      .row        .col-lg-11 -        = subform.input :lines, as: :select, collection: Chouette::Line.includes(:company).order(:name).where(objectid: current_functional_scope), selected: subform.object.line_ids, label_method: :display_name, input_html: { 'data-select2ed': 'true', 'data-select2ed-placeholder': t('simple_form.labels.referential.placeholders.select_lines'), 'multiple': 'multiple', style: 'width: 100%' } +        = subform.input :lines, as: :select, collection: Chouette::Line.includes(:company).order(:name).for_organisation(current_organisation), selected: subform.object.line_ids, label_method: :display_name, input_html: { 'data-select2ed': 'true', 'data-select2ed-placeholder': t('simple_form.labels.referential.placeholders.select_lines'), 'multiple': 'multiple', style: 'width: 100%' }        .col-lg-1          a.clear-lines.btn.btn-default href='#'            .fa.fa-trash diff --git a/app/views/referentials/_overview.html.slim b/app/views/referentials/_overview.html.slim index 6bed5f282..b3258ffd1 100644 --- a/app/views/referentials/_overview.html.slim +++ b/app/views/referentials/_overview.html.slim @@ -16,8 +16,8 @@            = f.input :transport_mode_eq_any, collection: overview.referential_lines.map(&:transport_mode).compact.uniq.sort, as: :check_boxes, label: false, label_method: lambda{|l| ("<span>" + t("enumerize.transport_mode.#{l}") + "</span>").html_safe}, required: false, wrapper_html: { class: 'checkbox_list'}        .actions -        = link_to 'Effacer', url_for() + "##{overview.pagination_param_name}", class: 'btn btn-link' -        = f.submit 'Filtrer', class: 'btn btn-default' +        = link_to t('actions.erase'), url_for() + "##{overview.pagination_param_name}", class: 'btn btn-link' +        = f.submit t('actions.filter'), class: 'btn btn-default'    .time-travel      .btn-group diff --git a/app/views/referentials/select_compliance_control_set.html.slim b/app/views/referentials/select_compliance_control_set.html.slim index 69c87aab2..7be82364d 100644 --- a/app/views/referentials/select_compliance_control_set.html.slim +++ b/app/views/referentials/select_compliance_control_set.html.slim @@ -1,3 +1,5 @@ +- breadcrumb @referential +  .page_content    .container-fluid      .row @@ -6,7 +8,7 @@            .row              .col-lg-12                .form-group -                = label_tag 'name', nil, class: 'string required col-sm-4 col-xs-5 control-label' +                = label_tag ComplianceControlSet.ts, nil, class: 'string required col-sm-4 col-xs-5 control-label'                  .col-sm-8.col-xs-7                    = select_tag :compliance_control_set, options_from_collection_for_select(@compliance_control_sets, "id", "name"), class: 'select optional form-control'                .separator diff --git a/app/views/routes/show.html.slim b/app/views/routes/show.html.slim index 375d7c57b..d4571c173 100644 --- a/app/views/routes/show.html.slim +++ b/app/views/routes/show.html.slim @@ -34,7 +34,7 @@                  end \                ), \                TableBuilderHelper::Column.new( \ -                key: :deleted_at, \ +                name: Chouette::Line.tmf('activated'), \                  attribute: Proc.new { |s| line_status(s.try(:stop_area).deleted_at) } \                ), \                TableBuilderHelper::Column.new( \ @@ -59,7 +59,7 @@              action: :index          - else -          = replacement_msg t('stop_areas.search_no_results') +          = replacement_msg t('stop_areas.filters.search_no_results')  = javascript_tag do    | window.route = "#{URI.escape(route_json_for_edit(@route))}" diff --git a/app/views/routing_constraint_zones/_filters.html.slim b/app/views/routing_constraint_zones/_filters.html.slim index 74e299a8b..561359943 100644 --- a/app/views/routing_constraint_zones/_filters.html.slim +++ b/app/views/routing_constraint_zones/_filters.html.slim @@ -1,16 +1,16 @@  = search_form_for @q, url: referential_line_routing_constraint_zones_path(@referential, @line), class: 'form form-filter' do |f|    .ffg-row      .input-group.search_bar class=filter_item_class(params[:q], :name_or_objectid_cont) -      = f.search_field :name_or_objectid_cont, class: 'form-control', placeholder: "Indiquez un nom d'ITL ou un ID..." +      = f.search_field :name_or_objectid_cont, class: 'form-control', placeholder: t('.name_or_objectid_cont')        span.input-group-btn          button.btn.btn-default#search-btn type='submit'            span.fa.fa-search    .ffg-row      .form-group class=filter_item_class(params[:q], :route_id_eq) -      = f.label 'Itinéraire associé', required: false, class: 'control-label' -      = f.input :route_id_eq, as: :select, collection: @line.routing_constraint_zones.pluck(:route_id).uniq, label: false, label_method: lambda { |r| @line.routing_constraint_zones.find_by(route_id: r).route_name }, input_html: { 'data-select2ed': 'true', 'data-select2ed-placeholder': 'Indiquez un itinéraire...' }, wrapper_html: { class: 'select2ed'} +      = f.label t('.associated_route.title'), required: false, class: 'control-label' +      = f.input :route_id_eq, as: :select, collection: @line.routing_constraint_zones.pluck(:route_id).uniq, label: false, label_method: lambda { |r| @line.routing_constraint_zones.find_by(route_id: r).route_name }, input_html: { 'data-select2ed': 'true', 'data-select2ed-placeholder': t('.associated_route.placeholder') }, wrapper_html: { class: 'select2ed'}    .actions -    = link_to 'Effacer', referential_line_routing_constraint_zones_path(@referential, @line), class: 'btn btn-link' -    = f.submit 'Filtrer', class: 'btn btn-default' +    = link_to t('actions.erase'), referential_line_routing_constraint_zones_path(@referential, @line), class: 'btn btn-link' +    = f.submit t('actions.filter'), class: 'btn btn-default' diff --git a/app/views/routing_constraint_zones/index.html.slim b/app/views/routing_constraint_zones/index.html.slim index 2f67b467e..7e9fb12a3 100644 --- a/app/views/routing_constraint_zones/index.html.slim +++ b/app/views/routing_constraint_zones/index.html.slim @@ -13,7 +13,7 @@            = table_builder_2 @routing_constraint_zones,              [ \                TableBuilderHelper::Column.new( \ -                name: 'ID', \ +                name: t('objectid'), \                  attribute: Proc.new { |n| n.get_objectid.local_id }, \                  sortable: false \                ), \ @@ -43,4 +43,4 @@      - unless @routing_constraint_zones.any?        .row.mt-xs          .col-lg-12 -          = replacement_msg t('routing_constraint_zones.search_no_results') +          = replacement_msg t('.search_no_results') diff --git a/app/views/shared/controls/_metadatas.html.slim b/app/views/shared/controls/_metadatas.html.slim index 49df06166..80f3936e6 100644 --- a/app/views/shared/controls/_metadatas.html.slim +++ b/app/views/shared/controls/_metadatas.html.slim @@ -7,9 +7,5 @@      I18n.t('activerecord.attributes.compliance_control.predicate') => resource.predicate,      I18n.t('activerecord.attributes.compliance_control.prerequisite') => resource.prerequisite,    }.merge( \ -    {}.tap do |hash| \ -      resource.class.dynamic_attributes.each do |attribute| \ -        hash[ComplianceControl.human_attribute_name(attribute)] = resource.send(attribute) \ -      end \ -    end \ -  ) +    compliance_control_metadatas(resource) \ +  )
\ No newline at end of file diff --git a/app/views/shared/custom_fields/_attachment.html.slim b/app/views/shared/custom_fields/_attachment.html.slim new file mode 100644 index 000000000..32d0fda4d --- /dev/null +++ b/app/views/shared/custom_fields/_attachment.html.slim @@ -0,0 +1,4 @@ +- if field.value.present? +  = link_to I18n.t("custom_fields.#{field.owner.class.name.demodulize.underscore}.#{field.code}.link"), field.value.url +- else +  = "-" diff --git a/app/views/shared/iev_interfaces/_messages.html.slim b/app/views/shared/iev_interfaces/_messages.html.slim index 022f4ee01..4e2c4d849 100644 --- a/app/views/shared/iev_interfaces/_messages.html.slim +++ b/app/views/shared/iev_interfaces/_messages.html.slim @@ -1,14 +1,15 @@  - if messages.any?    ul.list-unstyled.import_message-list      - messages.order(:created_at).each do | message | +      - width = message.resource_attributes.present? ? 6 : 12        li          .row class=bootstrap_class_for_message_criticity(message.criticity)            - if message.message_attributes && message.message_attributes["line"]              .col-md-1= "L. #{message.message_attributes["line"]}" -            .col-md-5= export_message_content message +            div class="col-md-#{width-1}"= export_message_content message            - else -            .col-md-6= export_message_content message +            div class="col-md-#{width}"= export_message_content message            .col-md-6 -            - if message.criticity != "info" +            - if message.resource_attributes.present?                pre                  = JSON.pretty_generate message.resource_attributes || {} diff --git a/app/views/stif/dashboards/_dashboard.html.slim b/app/views/stif/dashboards/_dashboard.html.slim index e0f754fd4..74e607e26 100644 --- a/app/views/stif/dashboards/_dashboard.html.slim +++ b/app/views/stif/dashboards/_dashboard.html.slim @@ -1,16 +1,17 @@  .row -  .col-lg-12 -    h2.content_header = t('.subtitle') - -.row    .col-lg-6.col-md-6.col-sm-6.col-xs-12      .panel.panel-default        .panel-heading          h3.panel-title            = t('.organisation') -      .panel-body -        em.small.text-muted = t('.no_content') +      - if @dashboard.workbench.output.referentials.present? +        - @dashboard.workbench.output.referentials.first(5).each do |referential| +          .list-group +            = link_to referential.name, referential_path(referential), class: 'list-group-item' +      - else +        .panel-body +          em.small.text-muted = t('.no_content')      .panel.panel-default        .panel-heading diff --git a/app/views/stop_areas/_filters.html.slim b/app/views/stop_areas/_filters.html.slim index a32638567..c698eaaa5 100644 --- a/app/views/stop_areas/_filters.html.slim +++ b/app/views/stop_areas/_filters.html.slim @@ -41,5 +41,5 @@              input_html: { checked: @status.try(:[], :deactivated) }    .actions -    = link_to 'Effacer', @workbench, class: 'btn btn-link' -    = f.submit 'Filtrer', class: 'btn btn-default' +    = link_to t('actions.erase'), @workbench, class: 'btn btn-link' +    = f.submit t('actions.filter'), class: 'btn btn-default' diff --git a/app/views/stop_areas/_form.html.slim b/app/views/stop_areas/_form.html.slim index 1cba88f94..00f2ad8bb 100644 --- a/app/views/stop_areas/_form.html.slim +++ b/app/views/stop_areas/_form.html.slim @@ -48,7 +48,7 @@            - if has_feature?(:stop_area_waiting_time)              = f.input :waiting_time, input_html: { min: 0 } -          = f.input :registration_number, required: stop_area_registration_number_is_required(f.object), :input_html => {title: stop_area_registration_number_title(f.object), value: stop_area_registration_number_value(f.object)} +          = f.input :registration_number, required: stop_area_registration_number_is_required(f.object), :input_html => {title: stop_area_registration_number_title(f.object), value: stop_area_registration_number_value(f.object)}, hint: stop_area_registration_number_hint            = f.input :fare_code            = f.input :nearest_topic_name, :input_html => {:title => t("formtastic.titles#{format_restriction_for_locales(@referential)}.stop_area.nearest_topic_name")}            = f.input :comment, as: :text, :input_html => {:rows => 5, :title => t("formtastic.titles#{format_restriction_for_locales(@referential)}.stop_area.comment")} @@ -62,6 +62,12 @@              = f.input :stairs_availability, as: :select, :collection => [[t("true"), true], [t("false"), false]], :include_blank => true              = f.input :lift_availability, as: :select, :collection => [[t("true"), true], [t("false"), false]], :include_blank => true +        - if resource.custom_fields(resource.stop_area_referential.workgroup).any? +          .custom_fields +            h3 = t("stop_areas.stop_area.custom_fields") +            - resource.custom_fields(resource.stop_area_referential.workgroup).each do |code, field| +              = field.input(f).to_s +    .separator    = f.button :submit, t('actions.submit'), class: 'btn btn-default formSubmitr', form: 'stop_area_form' diff --git a/app/views/stop_areas/autocomplete.rabl b/app/views/stop_areas/autocomplete.rabl index a5f0bd5ec..26fca36b2 100644 --- a/app/views/stop_areas/autocomplete.rabl +++ b/app/views/stop_areas/autocomplete.rabl @@ -15,7 +15,8 @@ node do |stop_area|      :latitude                  => stop_area.latitude,      :area_type                 => stop_area.area_type,      :comment                   => stop_area.comment, -    :text                      => "<span class='small label label-info'>#{I18n.t("area_types.label.#{stop_area.area_type}")}</span>#{stop_area.full_name}" +    :text                      => "<span class='small label label-info'>#{I18n.t("area_types.label.#{stop_area.area_type}")}</span>#{stop_area.full_name}", +    :kind                      => stop_area.kind    }  end diff --git a/app/views/stop_areas/index.html.slim b/app/views/stop_areas/index.html.slim index 587efbdaa..fbdb54e02 100644 --- a/app/views/stop_areas/index.html.slim +++ b/app/views/stop_areas/index.html.slim @@ -32,7 +32,7 @@                  attribute: 'registration_number' \                ), \                TableBuilderHelper::Column.new( \ -                name: t('activerecord.attributes.stop_area.state'), \ +                name: Chouette::StopArea.tmf('state'), \                  attribute: Proc.new { |s| stop_area_status(s) } \                ), \                TableBuilderHelper::Column.new( \ diff --git a/app/views/stop_areas/show.html.slim b/app/views/stop_areas/show.html.slim index a6147b86d..851bd9b82 100644 --- a/app/views/stop_areas/show.html.slim +++ b/app/views/stop_areas/show.html.slim @@ -23,4 +23,6 @@              t('activerecord.attributes.stop_area.state') => stop_area_status(@stop_area),              @stop_area.human_attribute_name(:comment) => @stop_area.try(:comment),              }) +        - @stop_area.custom_fields.each do |code, field| +          - attributes.merge!(field.name => field.display_value)          = definition_list t('metadatas'), attributes diff --git a/app/views/time_tables/index.html.slim b/app/views/time_tables/index.html.slim index 6913712a0..58bf66a9d 100644 --- a/app/views/time_tables/index.html.slim +++ b/app/views/time_tables/index.html.slim @@ -28,14 +28,14 @@                  end \                ), \                TableBuilderHelper::Column.new( \ +                key: :bounding_dates, \                  name: "Période englobante", \ -                attribute: Proc.new { |tt| tt.bounding_dates.empty? ? '-' : t('bounding_dates', debut: l(tt.bounding_dates.min), end: l(tt.bounding_dates.max)) }, \ -                sortable: false \ +                attribute: Proc.new { |tt| tt.object.bounding_dates.empty? ? '-' : t('bounding_dates', debut: l(tt.object.bounding_dates.min), end: l(tt.object.bounding_dates.max)) }, \                ), \                TableBuilderHelper::Column.new( \ +                key: :vehicle_journeys_count, \                  name: "Nombre de courses associées", \                  attribute: Proc.new{ |tt| tt.vehicle_journeys.count }, \ -                sortable: false \                ), \                TableBuilderHelper::Column.new( \                  name: "Journées d'application", \ diff --git a/app/views/vehicle_journeys/index.html.slim b/app/views/vehicle_journeys/index.html.slim index d23c61394..7fcee545f 100644 --- a/app/views/vehicle_journeys/index.html.slim +++ b/app/views/vehicle_journeys/index.html.slim @@ -1,10 +1,13 @@  - breadcrumb :vehicle_journeys, @referential, @route  - content_for :page_header_title, t('vehicle_journeys.index.title', route: @route.name) -- if @route.opposite_route.present? -  - content_for :page_header_content do -    .row.mb-sm -     .col-lg-12.text-right -       = link_to(t('routes.actions.opposite_route_timetable'), [@referential, @route.line, @route.opposite_route, :vehicle_journeys], class: 'btn btn-primary sticky-action') +- content_for :page_header_content do +  .row.mb-sm +    .col-lg-12.text-right +      = link_to I18n.t("time_tables.index.title"), [@referential, :time_tables], class: 'btn btn-primary sticky-action', target: :blank +      - if has_feature? :purchase_windows +        = link_to I18n.t("purchase_windows.index.title"), [@referential, :purchase_windows], class: 'btn btn-primary sticky-action', target: :blank +      - if @route.opposite_route.present? +        = link_to(t('routes.actions.reversed_vehicle_journey'), [@referential, @route.line, @route.opposite_route, :vehicle_journeys], class: 'btn btn-primary sticky-action')  .page_content @@ -30,7 +33,6 @@    | window.all_missions = #{(@all_missions.to_json).html_safe};    | window.custom_fields = #{(@custom_fields.to_json).html_safe};    | window.extra_headers = #{(@extra_headers.to_json).html_safe}; -  // | window.I18n = #{(I18n.backend.send(:translations).to_json).html_safe};  - if has_feature?(:vehicle_journeys_return_route)    = javascript_tag do diff --git a/app/views/workbench_outputs/show.html.slim b/app/views/workbench_outputs/show.html.slim index a9e106dbb..b310119e6 100644 --- a/app/views/workbench_outputs/show.html.slim +++ b/app/views/workbench_outputs/show.html.slim @@ -6,7 +6,8 @@    .row.mb-sm      .col-lg-12.text-right        = link_to t('.see_current_output'), referential_path(@workbench.output.current), class: 'btn btn-primary' if @workbench.output&.current -      = link_to t('merges.actions.create'), new_workbench_merge_path(@workbench), class: 'btn btn-primary' +      - if policy(Merge).create? +        = link_to t('merges.actions.create'), new_workbench_merge_path(@workbench), class: 'btn btn-primary'  .page_content    .container-fluid diff --git a/app/views/workbenches/show.html.slim b/app/views/workbenches/show.html.slim index 8312338d0..7dd1583fa 100644 --- a/app/views/workbenches/show.html.slim +++ b/app/views/workbenches/show.html.slim @@ -32,7 +32,8 @@                    end \                  ), \                  TableBuilderHelper::Column.new( \ -                  key: :status, \ +                  key: :archived_at, \ +                  name: Referential.tmf('status'), \                    attribute: Proc.new {|w| w.referential_read_only? ? ("<div class='td-block'><span class='fa fa-archive'></span><span>#{t('activerecord.attributes.referential.archived_at')}</span></div>").html_safe : ("<div class='td-block'><span class='sb sb-lg sb-preparing'></span><span>#{t('activerecord.attributes.referential.archived_at_null')}</span></div>").html_safe} \                  ), \                  TableBuilderHelper::Column.new( \ @@ -45,7 +46,7 @@                  ), \                  TableBuilderHelper::Column.new( \                    key: :lines, \ -                  name: t('activerecord.attributes.referential.number_of_lines'), \ +                  name: Referential.tmf('number_of_lines'), \                    attribute: Proc.new {|w| w.lines.count} \                  ), \                  TableBuilderHelper::Column.new( \ diff --git a/app/workers/gtfs_import_worker.rb b/app/workers/gtfs_import_worker.rb new file mode 100644 index 000000000..02f5053b0 --- /dev/null +++ b/app/workers/gtfs_import_worker.rb @@ -0,0 +1,7 @@ +class GtfsImportWorker +  include Sidekiq::Worker + +  def perform(import_id) +    Import::Gtfs.find(import_id).import +  end +end diff --git a/app/workers/route_way_cost_worker.rb b/app/workers/route_way_cost_worker.rb index d6bfed592..b62416c3d 100644 --- a/app/workers/route_way_cost_worker.rb +++ b/app/workers/route_way_cost_worker.rb @@ -7,10 +7,11 @@ class RouteWayCostWorker      # Prevent recursive worker spawning since this call updates the      # `costs` field of the route. -    Chouette::Route.skip_callback(:save, :after, :calculate_costs!) - -    RouteWayCostCalculator.new(route).calculate! - -    Chouette::Route.set_callback(:save, :after, :calculate_costs!) +    begin +      Chouette::Route.skip_callback(:commit, :after, :calculate_costs!) +      RouteWayCostCalculator.new(route).calculate! +    ensure +      Chouette::Route.set_callback(:commit, :after, :calculate_costs!) +    end    end  end diff --git a/app/workers/workbench_import_worker/object_state_updater.rb b/app/workers/workbench_import_worker/object_state_updater.rb index 67bdc0654..1edc6b9a1 100644 --- a/app/workers/workbench_import_worker/object_state_updater.rb +++ b/app/workers/workbench_import_worker/object_state_updater.rb @@ -6,9 +6,10 @@ class WorkbenchImportWorker        workbench_import.update( total_steps: count )        update_spurious entry        update_foreign_lines entry +      update_missing_calendar entry +      update_wrong_calendar entry      end -      private      def update_foreign_lines entry @@ -19,7 +20,7 @@ class WorkbenchImportWorker          message_attributes: {            'source_filename' => workbench_import.file.file.file,            'foreign_lines'   => entry.foreign_lines.join(', ') -        })  +        })      end      def update_spurious entry @@ -30,7 +31,27 @@ class WorkbenchImportWorker          message_attributes: {            'source_filename' => workbench_import.file.file.file,            'spurious_dirs'   => entry.spurious.join(', ') -        })  +        }) +    end + +    def update_missing_calendar entry +      return unless entry.missing_calendar +      workbench_import.messages.create( +        criticity: :error, +        message_key: 'missing_calendar_in_zip_file', +        message_attributes: { +          'source_filename' => entry.name +        }) +    end + +    def update_wrong_calendar entry +      return unless entry.wrong_calendar +      workbench_import.messages.create( +        criticity: :error, +        message_key: 'wrong_calendar_in_zip_file', +        message_attributes: { +          'source_filename' => entry.name +        })      end    end  end diff --git a/config/database/ci.yml b/config/database/ci.yml index 44103454a..5671cb6ad 100644 --- a/config/database/ci.yml +++ b/config/database/ci.yml @@ -1,4 +1,4 @@ -test: +test: &default    adapter: <%= ENV.fetch 'RAILS_DB_ADAPTER', 'postgis' %>    encoding: unicode    pool: <%= ENV.fetch 'RAILS_DB_POOLSIZE', '5' %> @@ -9,3 +9,7 @@ test:    database: <%= ENV.fetch 'RAILS_DB_NAME', 'stif_boiv_test' %>    username: <%= ENV['RAILS_DB_USER'] || ENV['POSTGRESQL_ENV_POSTGRES_USER'] || 'jenkins' %>    password: <%= ENV['RAILS_DB_PASSWORD'] || ENV['POSTGRESQL_ENV_POSTGRES_PASSWORD'] %> + +# Only used to build assets +production: +  <<: *default diff --git a/config/initializers/simple_form_bootstrap.rb b/config/initializers/simple_form_bootstrap.rb index 4b9bd320d..8dbc3afee 100644 --- a/config/initializers/simple_form_bootstrap.rb +++ b/config/initializers/simple_form_bootstrap.rb @@ -72,9 +72,9 @@ SimpleForm.setup do |config|      b.use :placeholder      b.optional :maxlength      b.optional :readonly -    b.use :label, class: 'col-sm-3 control-label' +    b.use :label, class: 'col-sm-4 col-xs-5 control-label' -    b.wrapper tag: 'div', class: 'col-sm-9' do |ba| +    b.wrapper tag: 'div', class: 'col-sm-8 col-xs-7' do |ba|        ba.use :input        ba.use :error, wrap_with: { tag: 'span', class: 'help-block small' }        ba.use :hint,  wrap_with: { tag: 'p', class: 'help-block small' } diff --git a/config/locales/actions.en.yml b/config/locales/actions.en.yml index 04f4aca6b..c349b709f 100644 --- a/config/locales/actions.en.yml +++ b/config/locales/actions.en.yml @@ -16,7 +16,7 @@ en:      unarchive: "Unarchive"      clone: 'Clone'      duplicate: 'Clone' -    clean_up: 'Clean up' +    clean_up: 'Purge'      sync: 'Synchronize'      combine: 'Combine'      actualize: 'Actualize' diff --git a/config/locales/calendars.en.yml b/config/locales/calendars.en.yml index c3df413af..696ae2734 100644 --- a/config/locales/calendars.en.yml +++ b/config/locales/calendars.en.yml @@ -1,6 +1,8 @@  en:    calendars: -    search_no_results: 'No calendar matching your query' +    filters: +      name_cont: Search by name +    search_no_results: 'No calendar template matching your query'      days:        monday: M        tuesday: Tu @@ -37,7 +39,7 @@ en:        all: All        shared: Shared        not_shared: Not shared -      search_no_results: No calendar matching your query +      search_no_results: No calendar templates matching your query        date: Date      new:        title: Add a new calendar @@ -59,16 +61,15 @@ en:    activerecord:      models:        calendar: -        one: calendar -        other: calendars +        one: calendar template +        other: calendar templates      attributes:        calendar:          name: Name -        short_name: Short name          date_ranges: Date ranges          dates: Dates          shared: Shared -        organisation: Organisation +        organisation: Organization          monday: "Monday"          tuesday: "Tuesday"          wednesday: "Wednesday" diff --git a/config/locales/calendars.fr.yml b/config/locales/calendars.fr.yml index 6fd265925..8c933f168 100644 --- a/config/locales/calendars.fr.yml +++ b/config/locales/calendars.fr.yml @@ -1,6 +1,8 @@  fr:    calendars: -    search_no_results: 'Aucun calendrier ne correspond à votre recherche' +    filters: +      name_cont: 'Indiquez un nom de calendrier...' +      no_results: 'Aucun calendrier ne correspond à votre recherche'      days:        monday: L        tuesday: Ma @@ -64,7 +66,6 @@ fr:      attributes:        calendar:          name: Nom -        short_name: Nom court          date_ranges: Intervalles de dates          dates: Dates          shared: Partagé diff --git a/config/locales/clean_ups.en.yml b/config/locales/clean_ups.en.yml index 876694592..6cbb2c453 100644 --- a/config/locales/clean_ups.en.yml +++ b/config/locales/clean_ups.en.yml @@ -5,7 +5,7 @@ en:      success_jp: "%{count} journey patterns deleted"      failure: "Fail when clean_up : %{error_message}"      actions: -      clean_up: "clean up" +      clean_up: "Clean up"        confirm: "Clean up will destroy time tables which ended on requested date\nand next recursively all object without any time table\nPlease confirm this action"    activemodel:      attributes: diff --git a/config/locales/companies.en.yml b/config/locales/companies.en.yml index becb087b1..f2b19bc19 100644 --- a/config/locales/companies.en.yml +++ b/config/locales/companies.en.yml @@ -16,7 +16,7 @@ en:      index:        title: "Companies"        name: "Search by name..." -      name_or_objectid: "Search by name or by Codifligne ID..." +      name_or_objectid: "Search by name or by ID..."        advanced_search: "Advanced search"    activerecord:      models: diff --git a/config/locales/compliance_check_messages.en.yml b/config/locales/compliance_check_messages.en.yml index 216a363a3..88841f308 100644 --- a/config/locales/compliance_check_messages.en.yml +++ b/config/locales/compliance_check_messages.en.yml @@ -22,10 +22,11 @@ en:      3_routingconstraint_2: "The Routing Constraint Zone %{source_objectid} covers all the stop points of its related route : %{target_0_objectid}."      3_routingconstraint_3: "The Routing Constraint Zone %{source_objectid} has less than 2 stop points"      3_line_1: "On line :%{source_label} (%{source_objectid}), no route has an opposite route" +    3_line_2: "The line %{source_label} (%{source_objectid}) is not in the lines scope of the organization %{reference_value}"      3_generic_1: "%{source_objectid} : the %{source_attribute} attribute value (%{error_value}) does not respect the following pattern : %{reference_value}"      3_generic_2_1: "%{source_objectid}  : the %{source_attribute} attributes's value (%{error_value}) is greater than the authorized maximum value : %{reference_value}"      3_generic_2_2: "%{source_objectid}  : the %{source_attribute} attributes's value (%{error_value}) is smaller than the authorized minimum value %{reference_value}"      3_generic_3: "%{source_objectid}  : the %{source_attribute} attribute (%{error_value}) has a value shared with : %{target_0_objectid}"      3_shape_1: "Tracé %{source_objectid} : le tracé passe trop loin de l'arrêt %{target_0_label} (%{target_0_objectid}) : %{error_value} > %{reference_value}"      3_shape_2: "Tracé %{source_objectid} : le tracé n'est pas défini entre les arrêts %{target_0_label} (%{target_0_objectid}) et %{target_1_label} (%{target_1_objectid})" -    3_shape_3: "Le tracé de l'itinéraire %{source_objectid} est en écart avec la voirie sur %{error_value} sections"
\ No newline at end of file +    3_shape_3: "Le tracé de l'itinéraire %{source_objectid} est en écart avec la voirie sur %{error_value} sections" diff --git a/config/locales/compliance_check_messages.fr.yml b/config/locales/compliance_check_messages.fr.yml index db127d236..167ef411a 100644 --- a/config/locales/compliance_check_messages.fr.yml +++ b/config/locales/compliance_check_messages.fr.yml @@ -22,10 +22,11 @@ fr:      3_routingconstraint_2: "L'ITL %{source_objectid} couvre tous les arrêts de l'itinéraire %{target_0_objectid}."      3_routingconstraint_3: "L'ITL %{source_objectid} n'a pas suffisament d'arrêts (minimum 2 arrêts requis)"      3_line_1: "Sur la ligne %{source_label} (%{source_objectid}), aucun itinéraire n'a d'itinéraire inverse" +    3_line_2: "La ligne %{source_label} (%{source_objectid}) ne fait pas partie du périmètre de lignes de l'organisation %{reference_value}"      3_generic_1: "%{source_objectid} : l'attribut %{source_attribute} a une valeur %{error_value} qui ne respecte pas le motif %{reference_value}"      3_generic_2_1: "%{source_objectid} : l'attribut %{source_attribute} a une valeur %{error_value} supérieure à la valeur maximale autorisée %{reference_value}"      3_generic_2_2: "%{source_objectid} : l'attribut %{source_attribute} a une valeur %{error_value} inférieure à la valeur minimale autorisée %{reference_value}"      3_generic_3: "%{source_objectid} : l'attribut %{source_attribute} a une valeur %{error_value} partagée avec %{target_0_objectid}"      3_shape_1: "Tracé %{source_objectid} : le tracé passe trop loin de l'arrêt %{target_0_label} (%{target_0_objectid}) : %{error_value} > %{reference_value}"      3_shape_2: "Tracé %{source_objectid} : le tracé n'est pas défini entre les arrêts %{target_0_label} (%{target_0_objectid}) et %{target_1_label} (%{target_1_objectid})" -    3_shape_3: "Le tracé de l'itinéraire %{source_objectid} est en écart avec la voirie sur %{error_value} sections"
\ No newline at end of file +    3_shape_3: "Le tracé de l'itinéraire %{source_objectid} est en écart avec la voirie sur %{error_value} sections" diff --git a/config/locales/compliance_check_resource.en.yml b/config/locales/compliance_check_resource.en.yml new file mode 100644 index 000000000..f8a31b81a --- /dev/null +++ b/config/locales/compliance_check_resource.en.yml @@ -0,0 +1,8 @@ +en: +  activerecord: +    attributes: +      compliance_check_resource: +        name: Name +        status: Status +        metrics: Metrics +        download: Download
\ No newline at end of file diff --git a/config/locales/compliance_check_resources.fr.yml b/config/locales/compliance_check_resources.fr.yml new file mode 100644 index 000000000..0fe4b83ed --- /dev/null +++ b/config/locales/compliance_check_resources.fr.yml @@ -0,0 +1,8 @@ +fr: +  activerecord: +    attributes: +      compliance_check_resource: +        name: Nom +        status: Statut +        metrics: Métriques +        download: Téléchargement
\ No newline at end of file diff --git a/config/locales/compliance_check_sets.en.yml b/config/locales/compliance_check_sets.en.yml index 5e8c3b24f..73ecf8996 100644 --- a/config/locales/compliance_check_sets.en.yml +++ b/config/locales/compliance_check_sets.en.yml @@ -20,10 +20,10 @@ en:        title: Executed control report %{name}      show:        title: Compliance check set report -      table_state: "%{lines_status} lines imported on %{lines_in_compliance_check_set} in the archive" +      table_state: "%{lines_status} lines imported out of %{lines_in_compliance_check_set} in the archive"        table_explanation: "These controls apply to all imported data and condition the construction of your organization's offer."        table_title: Analysed lines state -      metrics: "%{ok_count} ok, %{error_count} errors, %{warning_count} warnings, %{uncheck_count} n/a" +      metrics: "%{error_count} errors, %{warning_count} warnings"        metadatas:          referential: "Object analysed"          referential_type: "Apply to" diff --git a/config/locales/compliance_check_sets.fr.yml b/config/locales/compliance_check_sets.fr.yml index 20bf11d85..045fed4ce 100644 --- a/config/locales/compliance_check_sets.fr.yml +++ b/config/locales/compliance_check_sets.fr.yml @@ -19,7 +19,7 @@ fr:        table_state: "%{lines_status} lignes valides sur %{lines_in_compliance_check_set} présentes dans l'offre de transport"        table_explanation: Ces contrôles s’appliquent pour toutes les données importées et conditionnent la construction de l’offre de votre organisation        table_title: État des lignes analysées -      metrics: "%{ok_count} ok, %{error_count} errors, %{warning_count} warnings, %{uncheck_count} n/a" +      metrics: "%{error_count} errors, %{warning_count} warnings"        metadatas:          referential: "Objet analysé"          referential_type: "Appliqué à" diff --git a/config/locales/compliance_control_blocks.en.yml b/config/locales/compliance_control_blocks.en.yml index b9c01278c..0ec979549 100644 --- a/config/locales/compliance_control_blocks.en.yml +++ b/config/locales/compliance_control_blocks.en.yml @@ -1,4 +1,4 @@ -fr: +en:    activerecord:      models:        compliance_control_block:  @@ -9,6 +9,12 @@ fr:        compliance_control_blocks:          transport_mode: Transport mode          sub_transport_mode: Transport submode +    errors: +      models: +        compliance_control_block: +          attributes: +            condition_attributes: +              taken: The same compliance control block already exists in this compliance control set    compliance_control_blocks:      clone:        prefix: 'Copy of' @@ -16,8 +22,12 @@ fr:        destroy_confirm: Are you sure you want to destroy this block ?      new:        title: Create a control block +    create: +      title: Create a control block      edit:        title: "Edit the control block : %{name}" +    update: +      title: "Edit the control block : %{name}"      metas:        control:          zero: "No controls" diff --git a/config/locales/compliance_control_blocks.fr.yml b/config/locales/compliance_control_blocks.fr.yml index a6720881f..5ce5b4729 100644 --- a/config/locales/compliance_control_blocks.fr.yml +++ b/config/locales/compliance_control_blocks.fr.yml @@ -9,6 +9,12 @@ fr:        compliance_control_blocks:          transport_mode: Mode de transport          transport_submode: Sous-mode de transport +    errors: +      models: +        compliance_control_block: +          attributes: +            condition_attributes: +              taken: Un groupe de contrôle identique existe déjà au sein de ce jeu de contrôles    compliance_control_blocks:      clone:        prefix: 'Copie de' @@ -16,8 +22,12 @@ fr:        destroy_confirm: Etes vous sûr de supprimer ce bloc ?      new:        title: Créer un groupe de contrôle(s) +    create: +      title: Créer un groupe de contrôle(s)      edit:        title: "Editer le groupe de contrôle : %{name}" +    update: +      title: "Editer le groupe de contrôle : %{name}"      metas:        control:          zero: "Aucun contrôle" diff --git a/config/locales/compliance_control_sets.en.yml b/config/locales/compliance_control_sets.en.yml index 10c4f5e9a..c69689390 100644 --- a/config/locales/compliance_control_sets.en.yml +++ b/config/locales/compliance_control_sets.en.yml @@ -10,6 +10,12 @@ en:        new_control: Creating a Control        select_types: Control Type Selection        edit: Edit compliance control set +    new: +      title: New compliance control set %{name} +    show: +      title: Consult compliance control set %{name} +    edit: +      title: Edit compliance control set %{name}      actions:        new: Add        edit: Edit @@ -18,6 +24,7 @@ en:        add_compliance_control: Compliance Control        add_compliance_control_block: Compliance Control Block        destroy_confirm: Are you sure ? +      loaded: Load the control      filters:        name: 'Enter name ...'      search_no_results: 'No compliance control set found' diff --git a/config/locales/compliance_controls.en.yml b/config/locales/compliance_controls.en.yml index f7d461fdb..18069f2f7 100644 --- a/config/locales/compliance_controls.en.yml +++ b/config/locales/compliance_controls.en.yml @@ -29,6 +29,8 @@ en:        title: "Add a new compliance control"      edit:        title: "Update compliance control" +    select_type: +      title: "Select a control type"      actions:        new: Add        edit: Edit @@ -140,6 +142,11 @@ en:          3_line_1: "On line :%{source_label} (%{source_objectid}), no route has an opposite route"        description: "The routes of a line must have an opposite route"        prerequisite: Line has multiple routes +    line_control/lines_scope: +      messages: +        3_line_2: "The line %{source_label} (%{source_objectid}) is not in the lines scope of the organization %{reference_value}" +      description: "The line must be included in the lines scope of the organization" +      prerequisite: "None"      generic_attribute_control/pattern:        messages:          3_generic_1: "%{source_objectid} : the %{source_attribute} attribute value (%{error_value}) does not respect the following pattern : %{reference_value}" @@ -207,6 +214,8 @@ en:          one: "Unactivated stop points"        line_control/route:          one: "The routes of a line must have an opposite route" +      line_control/lines_scope: +        one: "Lines must be included in the lines scope of the organization"        generic_attribute_control/pattern:          one: "Attribute pattern of an object in a line"        generic_attribute_control/min_max: diff --git a/config/locales/compliance_controls.fr.yml b/config/locales/compliance_controls.fr.yml index 78b92451f..7dc6eeeb3 100644 --- a/config/locales/compliance_controls.fr.yml +++ b/config/locales/compliance_controls.fr.yml @@ -139,6 +139,11 @@ fr:          3_line_1: "Sur la ligne %{source_label} (%{source_objectid}), aucun itinéraire n'a d'itinéraire inverse"        description: "Les itinéraires d'une ligne doivent être associés en aller/retour"        prerequisite: Ligne disposant de plusieurs itinéraires +    line_control/lines_scope: +      messages: +        3_line_2: "La ligne %{source_label} (%{source_objectid}) ne fait pas partie du périmètre de lignes de l'organisation %{reference_value}" +      description: "Les lignes doivent appartenir au périmètre de lignes de l'organisation" +      prerequisite: "Aucun"      generic_attribute_control/pattern:        messages:          3_generic_1: "%{source_objectid} : l'attribut %{source_attribute} a une valeur %{error_value} qui ne respecte pas le motif %{reference_value}" @@ -206,6 +211,8 @@ fr:          one: "ITL & arret désactivé"        line_control/route:          one: "Appariement des itinéraires" +      line_control/lines_scope: +        one: "Les lignes doivent appartenir au périmètre de lignes de l'organisation"        generic_attribute_control/pattern:          one: "Contrôle du contenu selon un pattern"        generic_attribute_control/min_max: diff --git a/config/locales/dashboard.en.yml b/config/locales/dashboard.en.yml index 8d46ff7aa..472f5bc2f 100644 --- a/config/locales/dashboard.en.yml +++ b/config/locales/dashboard.en.yml @@ -1,7 +1,10 @@  en:    dashboards: +    main_nav_left: Dashboard       show:        title: "Dashboard %{organisation}" +    workbench: +      title: Transport offer %{organisation}      calendars:        title: Calendars        none: No calendar created diff --git a/config/locales/dashboard.fr.yml b/config/locales/dashboard.fr.yml index d0aa36d61..65022fcb8 100644 --- a/config/locales/dashboard.fr.yml +++ b/config/locales/dashboard.fr.yml @@ -1,7 +1,10 @@  fr:    dashboards: +    main_nav_left: Tableau de bord      show:        title: "Tableau de bord %{organisation}" +    workbench: +      title: Offre de transport %{organisation}      calendars:        title: Modèles de calendrier        none: Aucun calendrier défini diff --git a/config/locales/import_messages.en.yml b/config/locales/import_messages.en.yml index bc06c46f0..9f370d319 100644 --- a/config/locales/import_messages.en.yml +++ b/config/locales/import_messages.en.yml @@ -2,7 +2,10 @@ en:    import_message:      corrupt_zip_file: "The zip file %{source_filename} is corrupted and cannot be read"      inconsistent_zip_file: "The zip file %{source_filename} contains unexpected directories: %{spurious_dirs}, which are ignored" -    referential_creation: "Le référentiel n'a pas pu être créé car un référentiel existe déjà sur les mêmes périodes et lignes" +    missing_calendar_in_zip_file: "The folder %{source_filename} lacks a calendar file" +    wrong_calendar_in_zip_file: "The calendar file %{source_filename} cannot be parsed, or contains inconsistant data" +    referential_creation: "The referential %{referential_name} has not been created because another referential with the same lines and periods already exists" +    referential_creation_missing_lines: "The referential %{referential_name} has not been created because no matching line has been found"      1_netexstif_2: "Le fichier %{source_filename} ne respecte pas la syntaxe XML ou la XSD NeTEx : erreur '%{error_value}' rencontré"      1_netexstif_5: "%{source_filename}-Ligne %{source_line_number}-Colonne %{source_column_number} : l'objet %{source_label} d'identifiant %{source_objectid} a une date de mise à jour dans le futur"      2_netexstif_1_1: "Le fichier commun.xml ne contient pas de frame nommée NETEX_COMMUN" diff --git a/config/locales/import_messages.fr.yml b/config/locales/import_messages.fr.yml index 1e5054648..5ecef68d1 100644 --- a/config/locales/import_messages.fr.yml +++ b/config/locales/import_messages.fr.yml @@ -2,10 +2,13 @@ fr:    import_messages:      corrupt_zip_file: "Le fichier zip est corrompu, et ne peut être lu"      inconsistent_zip_file: "Le fichier zip contient des repertoires non prévus : %{spurious_dirs} qui seront ignorés" -    referential_creation: "Le référentiel n'a pas pu être créé car un référentiel existe déjà sur les mêmes périodes et lignes" +    missing_calendar_in_zip_file: "Le dossier %{source_filename} ne contient pas de calendrier" +    wrong_calendar_in_zip_file: "Le calendrier contenu dans %{source_filename} contient des données incorrectes ou incohérentes" +    referential_creation: "Le référentiel %{referential_name} n'a pas pu être créé car un référentiel existe déjà sur les mêmes périodes et lignes" +    referential_creation_missing_lines: "Le référentiel %{referential_name} n'a pas pu être créé car aucune ligne ne correspond"      1_netexstif_2: "Le fichier %{source_filename} ne respecte pas la syntaxe XML ou la XSD NeTEx : erreur '%{error_value}' rencontré"      1_netexstif_5: "%{source_filename}-Ligne %{source_line_number}-Colonne %{source_column_number} : l'objet %{source_label} d'identifiant %{source_objectid} a une date de mise à jour dans le futur" -    2_netexstif_1_1: "Le fichier commun.xml ne contient pas de frame nommée NETEX_COMMUN" +    2_netexstif_1_1: "Le fichier commun.xml ne contient pas de frame nommée NTEX_COMMUN"      2_netexstif_1_2: "Le fichier commun.xml contient une frame nommée %{error_value} non acceptée"      2_netexstif_2_1: "Le fichier calendriers.xml ne contient pas de frame nommée NETEX_CALENDRIER"      2_netexstif_2_2: "Le fichier calendriers.xml contient une frame nommée %{error_value} non acceptée" diff --git a/config/locales/import_resources.en.yml b/config/locales/import_resources.en.yml index 386039319..4b1f9c394 100644 --- a/config/locales/import_resources.en.yml +++ b/config/locales/import_resources.en.yml @@ -6,7 +6,7 @@ en:          table_state: "%{lines_imported} line(s) imported on %{lines_in_zipfile} presents in zipfile"          table_title: "Satus of anlyzed files"          table_explanation: "When calendriers.xml and/or commun.xml are not imported, then all lines file are not processed." -        metrics: "%{ok_count} ok, %{error_count} errors, %{warning_count} warnings, %{uncheck_count} n/a" +        metrics: "%{error_count} errors, %{warning_count} warnings"    import_resources:      <<: *resources    activerecord: diff --git a/config/locales/import_resources.fr.yml b/config/locales/import_resources.fr.yml index 50fb7f1ca..93a576f01 100644 --- a/config/locales/import_resources.fr.yml +++ b/config/locales/import_resources.fr.yml @@ -6,7 +6,7 @@ fr:          table_state: "%{lines_imported} ligne(s) importée(s) sur %{lines_in_zipfile} présente(s) dans l'archive"          table_title: "Etat des fichiers analysés"          table_explanation: "Dans le cas ou le(s) fichiers calendriers.xml et/ou commun.xml sont dans un état non importé, alors tous les fichiers lignes sont automatiquement dans un état non traité." -        metrics: "%{ok_count} ok, %{error_count} errors, %{warning_count} warnings, %{uncheck_count} n/a" +        metrics: "%{error_count} errors, %{warning_count} warnings"    import_resources:      <<: *resources    activerecord: diff --git a/config/locales/imports.en.yml b/config/locales/imports.en.yml index d0db87fb1..c8683a2a7 100644 --- a/config/locales/imports.en.yml +++ b/config/locales/imports.en.yml @@ -26,6 +26,13 @@ en:        compliance_check: "Validation report"        compliance_check_of: "Validation of import: "        import_of_validation: "Import of the validation" +      data_recovery: Data recovery +      filename: Filename +      referential_name: Referential name +      stif_control: STIF Control +      organisation_control: Organization control +      results: "%{count} validated referential(s) out of %{total} in the file" +      summary: Summay of import compliance check sets <span title="Lorem ipsum..." class="fa fa-lg fa-info-circle text-info"></span>      compliance_check_task: "Validate Report"      severities:        info: "Information" diff --git a/config/locales/imports.fr.yml b/config/locales/imports.fr.yml index 40272889a..733254fa4 100644 --- a/config/locales/imports.fr.yml +++ b/config/locales/imports.fr.yml @@ -26,6 +26,13 @@ fr:        compliance_check: "Test de conformité"        compliance_check_of: "Validation de l'import : "        import_of_validation: "L'import de la validation" +      data_recorvery: Récupération des données +      filename: Nom de l'archive +      referential_name: Nom du référentiel +      stif_control: Contrôle STIF +      organisation_control: Contrôle organisation +      results: "%{count} jeu(x) de données validé(s) sur %{total}" +      summary: Bilan des jeux de contrôles d'import <span title="Lorem ipsum..." class="fa fa-lg fa-info-circle text-info"></span>      compliance_check_task: "Validation"      severities:        info: "Information" diff --git a/config/locales/journey_patterns.en.yml b/config/locales/journey_patterns.en.yml index d480e144d..70ae94dd9 100644 --- a/config/locales/journey_patterns.en.yml +++ b/config/locales/journey_patterns.en.yml @@ -1,6 +1,7 @@  en:    journey_patterns:      journey_pattern: +      fetching_error: "There has been a problem fetching the data. Please reload the page to try again."        from_to: "From '%{departure}' to '%{arrival}'"        stop_count: "%{count}/%{route_count} stops"        vehicle_journeys_count: "Vehicle journeys: %{count}" @@ -19,6 +20,13 @@ en:      show:        title: "Journey Pattern %{journey_pattern}"        stop_points: "Stop point on journey pattern list" +      stop_points_count:  +        none: '%{count} stop areas' +        one: '%{count} stop area' +        other: '%{count} stop areas' +      informations: Informations +      confirmation: Confimation +      confirm_page_change: You are about to change page. Would you like to save your work before that ?      index:        title: "Journey Patterns of %{route}"      form: @@ -50,7 +58,8 @@ en:          creator_id: "Created by"          full_journey_time: Full journey          commercial_journey_time: Commercial journey - +        stop_points: Nb stop areas +        checksum: Checksum    formtastic:      titles:        journey_pattern: diff --git a/config/locales/journey_patterns.fr.yml b/config/locales/journey_patterns.fr.yml index 32c1f3f97..10653a02d 100644 --- a/config/locales/journey_patterns.fr.yml +++ b/config/locales/journey_patterns.fr.yml @@ -1,6 +1,7 @@  fr:    journey_patterns:      journey_pattern: +      fetching_error: "La récupération des courses a rencontré un problème. Rechargez la page pour tenter de corriger le problème."        from_to: "De '%{departure}' à '%{arrival}'"        stop_count: "%{count}/%{route_count} arrêts"        vehicle_journeys_count: "Courses: %{count}" @@ -19,6 +20,13 @@ fr:      show:        title: "Mission %{journey_pattern}"        stop_points: "Liste des arrêts de la mission" +      stop_points_count:  +        none: '%{count} arrêt' +        one: '%{count} arrêt' +        other: '%{count} arrêts' +      informations: Informations +      confirmation: Confimation +      confirm_page_change: Vous vous apprêtez à changer de page. Voulez-vous valider vos modifications avant cela ?      index:        title: "Missions de %{route}"      form: @@ -50,6 +58,8 @@ fr:          creator_id: "Créé par"          full_journey_time: Parcours complet          commercial_journey_time: Parcours commercial +        stop_points: Nb arrêts +        checksum: Signature métier    formtastic:      titles:        journey_pattern: diff --git a/config/locales/layouts.en.yml b/config/locales/layouts.en.yml index debff05e5..954525ac4 100644 --- a/config/locales/layouts.en.yml +++ b/config/locales/layouts.en.yml @@ -3,15 +3,33 @@ en:      back_to_dashboard: "Back to Dashboard"      help: "Help"      home: "Home" +    operations: Operations      user:        profile: "My Profile"        sign_out: "Sign out"      navbar:        return_to_referentials: "Return to data spaces"        select_referential: "Select data space" +      select_referential_for_more_features: "Select data space for more foeatures"        select_referential_datas: "Select datas"        return_to_dashboard: "Return to Dashboard" +      dashboard: Dashboard        referential_datas: "Datas" +      current_offer:  +        one: Current offer +        other: Current offers +      workbench_output: +        organisation: Organisation offers +        idf: IDF offers +      tools: Tools +      sync: Synchronization +      sync_icar: iCAR synchronization +      sync_ilico: iLICO synchronization +      icar: iCAR +      ilico: iLICO +      portal: Portal (POSTIF) +      support: Support +      shapes: Shapes      history_tag:        title: "Metadatas"        created_at: "Created at" diff --git a/config/locales/layouts.fr.yml b/config/locales/layouts.fr.yml index 5e835bcf7..f125a002d 100644 --- a/config/locales/layouts.fr.yml +++ b/config/locales/layouts.fr.yml @@ -3,15 +3,33 @@ fr:      back_to_dashboard: "Retour au Tableau de Bord"      help: "Aide"      home: "Accueil" +    operations: Opérations      user:        profile: "Mon Profil"        sign_out: "Déconnexion"      navbar:        return_to_referentials: "Retour à la liste des espaces de données"        select_referential: "Sélection de l'espace de données" +      select_referential_for_more_features: "Sélectionnez un jeu de données pour accéder à plus de fonctionnalités"        select_referential_datas: "Sélection des données"        return_to_dashboard: "Retour au Tableau de Bord" +      dashboard: Tableau de bord        referential_datas: "Données" +      current_offer:  +        one: Offres courante +        other: Offres courantes +      workbench_outputs: +        organisation: Offres de mon organisation +        idf: Offres IDF +      tools: Outils +      sync: Synchronisation +      sync_icar: Synchronisation iCAR +      sync_ilico: Synchronisation iLICO +      icar: iCAR +      ilico: iLICO +      portal: Portail (POSTIF) +      support: Support +      shapes: Tracés      history_tag:        title: "Métadonnées"        created_at: "Créé le" diff --git a/config/locales/line_referentials.en.yml b/config/locales/line_referentials.en.yml index 5663ed691..18ff28c24 100644 --- a/config/locales/line_referentials.en.yml +++ b/config/locales/line_referentials.en.yml @@ -7,7 +7,7 @@ en:      edit:        title: "Edit %{name} referential"      show: -      title: "iLICO synchronization" +      title: Line referential        synchronized: Synchronized        status: Status        message: Message diff --git a/config/locales/lines.en.yml b/config/locales/lines.en.yml index e61013725..1cd5150db 100644 --- a/config/locales/lines.en.yml +++ b/config/locales/lines.en.yml @@ -1,6 +1,5 @@  en:    lines: &en_lines -    search_no_results: "No line matching your query"      actions:        new: "Add a new line"        edit: "Edit this line" @@ -20,6 +19,8 @@ en:        show: 'Show'        show_network: 'Show network'        show_company: 'Show company' +    filters: +      name_or_objectid_cont: "Search by name or objectid"      new:        title: "Add a new line"      create: @@ -33,11 +34,12 @@ en:        routes:          title: "Routes list"        group_of_lines: "Groups of lines" +      search_no_results: "No line matching your query"      index:        deactivated: "Disabled line"        title: "Lines"        line: "Line %{line}" -      name_or_number_or_objectid: "Search by name, short name or ID Codif..." +      name_or_number_or_objectid: "Search by name, short name or ID..."        no_networks: "No networks"        no_companies: "No companies"        no_group_of_lines: "No group of lines" @@ -115,7 +117,7 @@ en:          creator_id: "Created by"          footnotes: "Footnotes"          stable_id: External permanent idenifier" -        state: State +        state: Status          activated: Activated          deactivated: Deactivated    formtastic: diff --git a/config/locales/lines.fr.yml b/config/locales/lines.fr.yml index d3069f1d1..6f4a2e9bf 100644 --- a/config/locales/lines.fr.yml +++ b/config/locales/lines.fr.yml @@ -1,6 +1,5 @@  fr:    lines: &fr_lines -    search_no_results: "Aucune ligne ne correspond à votre recherche"      actions:        new: "Ajouter une ligne"        edit: "Editer cette ligne" @@ -20,6 +19,8 @@ fr:        show: 'Consulter'        show_network: 'Voir le réseau'        show_company: 'Voir le transporteur principal' +    filters: +      name_or_objectid_cont: "Indiquez un nom d'itinéraire ou un ID..."      new:        title: "Ajouter une ligne"      create: @@ -34,11 +35,12 @@ fr:          title: "Liste des Itinéraires"        itineraries: "Liste des séquences d'arrêts de la ligne"        group_of_lines: "Groupes de lignes" +      search_no_results: "Aucune ligne ne correspond à votre recherche"      index:        deactivated: "Ligne désactivée"        title: "Lignes"        line: "Ligne %{line}" -      name_or_number_or_objectid: "Recherche par nom, nom court ou ID Codif..." +      name_or_number_or_objectid: "Recherche par nom, nom court ou ID..."        no_networks: "Aucun réseaux"        no_companies: "Aucun transporteurs"        no_group_of_lines: "Aucun groupes de ligne" diff --git a/config/locales/merges.yml b/config/locales/merges.yml index d8511a7b4..345727b74 100644 --- a/config/locales/merges.yml +++ b/config/locales/merges.yml @@ -8,7 +8,7 @@ fr:      show:        title: "Finalisation de l'offre %{name}"      actions: -      create: "Finaliser des Jeux de Données" +      create: Finaliser des Jeux de Données    activerecord:      models:        merge: "Finalisation de l'offre" diff --git a/config/locales/networks.en.yml b/config/locales/networks.en.yml index 94a8d9df0..2046a30ae 100644 --- a/config/locales/networks.en.yml +++ b/config/locales/networks.en.yml @@ -15,7 +15,7 @@ en:      index:        title: "Networks"        name: "Search by name..." -      name_or_objectid: "Search by name or by Codifligne ID..." +      name_or_objectid: "Search by name or by ID..."        advanced_search: "Advanced search"    activerecord:      models: diff --git a/config/locales/purchase_windows.en.yml b/config/locales/purchase_windows.en.yml index 9ce05a1b9..9ff1576cd 100644 --- a/config/locales/purchase_windows.en.yml +++ b/config/locales/purchase_windows.en.yml @@ -63,7 +63,7 @@ en:          one: purchase window          other: purchase windows      attributes: -      purchase_windows: +      purchase_window:          name: Name          date_ranges: Date ranges          referential: Referential @@ -71,7 +71,7 @@ en:          bounding_dates: Bounding Dates      errors:        models: -        purchase_windows: +        purchase_window:            attributes:              dates:                date_in_date_ranges: A date can not be in Dates and in Date ranges. diff --git a/config/locales/referentials.en.yml b/config/locales/referentials.en.yml index b7483c0aa..1381d5ddd 100644 --- a/config/locales/referentials.en.yml +++ b/config/locales/referentials.en.yml @@ -3,7 +3,7 @@ en:      filters:        name_or_number_or_objectid: 'Search by name, number or objectid'        name: 'Search by name' -      line: 'Seach by associated lines' +      line: 'Search by associated lines'      search_no_results: 'No data space matching your query'      error_period_filter: "The period filter must have valid bounding dates"      index: @@ -51,7 +51,7 @@ en:      overview:        head:          dates: Dates -        lines: Lignes +        lines: Lines          today: Today          prev_page: Prev. page          next_page: Next page @@ -64,7 +64,7 @@ en:          other: "data spaces"      attributes:        referential: -        name: "Data space name" +        name: "Name"          status: "Status"          slug: "Code"          prefix: "Neptune Object Id prefix" @@ -105,7 +105,7 @@ en:          created_from: 'Created from'          updated_at: "Updated"          created_at: "Created" -        organisation: 'Organisation' +        organisation: 'Organization'          number_of_lines: 'No. of lines'    formtastic:      titles: diff --git a/config/locales/referentials.fr.yml b/config/locales/referentials.fr.yml index 53183a4c2..cf012ef8e 100644 --- a/config/locales/referentials.fr.yml +++ b/config/locales/referentials.fr.yml @@ -32,7 +32,7 @@ fr:          title: 'Dupliquer un jeu de données'        submit: "Valider"      select_compliance_control_set: -      title: "Sélection du jeu de contôle" +      title: "Sélection du jeu de contrôles"      actions:        new: "Créer un jeu de données"        destroy_confirm: "Etes vous sûr de vouloir supprimer ce jeu de données ?" diff --git a/config/locales/routes.en.yml b/config/locales/routes.en.yml index bd8358bdd..66805e050 100644 --- a/config/locales/routes.en.yml +++ b/config/locales/routes.en.yml @@ -1,6 +1,8 @@  en:    routes: -    search_no_results: "No route matching your query" +    filters: +      placeholder: Search by name or ID +      no_results: "No route matching your query"      actions:        new: "Add a new route"        edit: "Edit this route" @@ -13,7 +15,7 @@ en:        export_hub_all: "Export HUB routes"        add_stop_point: "Add stop point"        new_stop_point: "Create new stop" -      opposite_route_timetable: "Timetable back" +      reversed_vehicle_journey: "Reversed vehicle journeys"      new:        title: "Add a new route"      edit: @@ -55,7 +57,7 @@ en:        for_boarding: "Boarding"        for_alighting: "Alighting"      duplicate: -      title: "Duplicate route" +      title: "Clone route"        success: "Route cloned with success"      route:        no_journey_pattern: "No Journey pattern" @@ -82,7 +84,7 @@ en:          number: "Number"          direction: "Direction"          wayback: "Direction" -        stop_points: "Nb Stop points" +        stop_points: "Nb Stop areas"          opposite_route: "Reversed route"          opposite_route_id: "Reversed route"          objectid: "Neptune identifier" @@ -91,6 +93,8 @@ en:          updated_at: Updated at          creator_id: "Created by"          no_journey_pattern: "No journey pattern" +        stop_area_departure: Stop area departure  +        stop_area_arrival: Stop area arrival     formtastic:      titles:        route: diff --git a/config/locales/routes.fr.yml b/config/locales/routes.fr.yml index c08e64cfd..f4eefa10d 100644 --- a/config/locales/routes.fr.yml +++ b/config/locales/routes.fr.yml @@ -1,6 +1,8 @@  fr:    routes: -    search_no_results: "Aucun itinéraire ne correspond à votre recherche" +    filters: +      placeholder: Indiquez un nom d'itinéraire ou un ID... +      no_results: "Aucun itinéraire ne correspond à votre recherche"      actions:        new: "Ajouter un itinéraire"        edit: "Editer cet itinéraire" @@ -13,7 +15,8 @@ fr:        export_hub_all: "Export HUB des itinéraires"        add_stop_point: "Ajouter un arrêt"        new_stop_point: "Créer un arrêt pour l'ajouter" -      opposite_route_timetable: "Horaires retour" +      reversed_vehicle_journey: "Horaires retour" +    opposite: "%{name} (retour)"      new:        title: "Ajouter un itinéraire"      edit: @@ -54,6 +57,9 @@ fr:        stop_area_name: "Nom de l'arrêt"        for_boarding: "Montée"        for_alighting: "Descente" +    create_opposite: +      title: "Créer retour" +      success: "itinéraire créé avec succès"      duplicate:        title: "Dupliquer l'itinéraire"        success: "itinéraire dupliqué avec succès" @@ -95,6 +101,8 @@ fr:          updated_at: "Edité le"          creator_id: "Créé par"          no_journey_pattern: "Pas de mission" +        stop_area_departure: Arrêt de départ +        stop_area_arrival: Arrêt d'arrivée    formtastic:      titles:        route: diff --git a/config/locales/routing_constraint_zones.en.yml b/config/locales/routing_constraint_zones.en.yml index 5675fd5db..2143ce1e1 100644 --- a/config/locales/routing_constraint_zones.en.yml +++ b/config/locales/routing_constraint_zones.en.yml @@ -26,7 +26,11 @@ en:                stop_points_not_from_route: 'Stop point does not belong to the Route of this Routing constraint zone.'                all_stop_points_selected: 'All stop points from route cannot be selected.'    routing_constraint_zones: -    search_no_results: "No ITL matches your query" +    filters: +      associated_route:  +        title: Associated route +        placeholder: Put the name of a route... +      name_or_objectid_cont:  Search by name or ID...      actions:        destroy_confirm: Are you sure you want to delete this routing constraint zone?      new: @@ -36,4 +40,5 @@ en:      show:        title: "Routing constraint zone %{name}"      index: -      title: "Interdictions of local trafficous" +      title: "Routing constraint zones" +      search_no_results: "No ITL matches your query" diff --git a/config/locales/routing_constraint_zones.fr.yml b/config/locales/routing_constraint_zones.fr.yml index d4b97fff2..b5e0fa7fb 100644 --- a/config/locales/routing_constraint_zones.fr.yml +++ b/config/locales/routing_constraint_zones.fr.yml @@ -26,7 +26,11 @@ fr:                stop_points_not_from_route: "Arrêt sur séquence d'arrêts n'appartient pas à la Route de cette Zone de contrainte."                all_stop_points_selected: "Une ITL ne peut pas couvrir tous les arrêts d'un itinéraire."    routing_constraint_zones: -    search_no_results: "Aucune ITL ne correspond à votre recherche" +    filters: +      associated_route:  +        title: Itinéraire associé +        placeholder: Indiquez un itinéraire... +      name_or_objectid_cont:  Indiquez un nom d'ITL ou un ID...      actions:        destroy_confirm: Etes vous sûr de supprimer cette ITL ?      new: @@ -37,3 +41,4 @@ fr:        title: "Zone de contrainte %{name}"      index:        title: "Interdictions de trafic local" +      search_no_results: "Aucune ITL ne correspond à votre recherche" diff --git a/config/locales/select2.en.yml b/config/locales/select2.en.yml new file mode 100644 index 000000000..308540af0 --- /dev/null +++ b/config/locales/select2.en.yml @@ -0,0 +1,15 @@ +en: +  select2: +    error_loading: The results cannot be loaded. +    input_too_long: +      one: Remove %{count} letter +      other: Remove %{count} letters +    input_too_short: +      one: Type %{count} letter +      other: Type %{count} letters +    loading_more: Loading more… +    maximum_selected: +      one: You can select %{count} element +      other: You can select %{count} elements +    no_results: No Results +    searching: Searching... diff --git a/config/locales/select2.fr.yml b/config/locales/select2.fr.yml new file mode 100644 index 000000000..4a37a3d78 --- /dev/null +++ b/config/locales/select2.fr.yml @@ -0,0 +1,15 @@ +fr: +  select2: +    error_loading: Les résultats ne peuvent pas être chargés. +    input_too_long: +      one: Supprimez %{count} caractère +      other: Supprimez %{count} caractères +    input_too_short: +      one: Saisissez %{count} caractère +      other: Saisissez %{count} caractères +    loading_more: Chargement de résultats supplémentaires… +    maximum_selected: +      one: Vous pouvez sélectionner %{count} élément +      other: Vous pouvez sélectionner %{count} élément +    no_results: Aucun résultat trouvé +    searching: Recherche en cours...
\ No newline at end of file diff --git a/config/locales/stop_area_referentials.en.yml b/config/locales/stop_area_referentials.en.yml index 11baf67e2..9d49d7c5d 100644 --- a/config/locales/stop_area_referentials.en.yml +++ b/config/locales/stop_area_referentials.en.yml @@ -4,7 +4,7 @@ en:        sync: "Launch a new reflex synchronization"        cancel_sync: "Cancel reflex synchronization"      show: -      title: 'Synchronization iCAR' +      title: 'Stop area referential'    activerecord:      models:        stop_area_referential: diff --git a/config/locales/stop_areas.en.yml b/config/locales/stop_areas.en.yml index 9e70993aa..1da4b58b4 100644 --- a/config/locales/stop_areas.en.yml +++ b/config/locales/stop_areas.en.yml @@ -8,7 +8,7 @@ en:        registration_number:          already_taken: Already taken          cannot_be_empty: This field is mandatory -        invalid: Incorrect value +        invalid: "Incorrect value (expected value: \"%{mask}\")"      default_geometry_success: "%{count} modified stop areas"      stop_area:        no_position: "No Position" @@ -18,12 +18,13 @@ en:        general: "General"        localisation: "Localisation"        accessibility: "Accessibility" +      custom_fields: "Custom fields"      actions: -      new: "Add a new stop" -      create: "Add a new stop" -      edit: "Edit this stop" -      update: "Edit this stop" -      destroy: "Remove" +      new: "Add a new stop area" +      create: "Add a new stop area" +      edit: "Edit stop area" +      update: "Edit stop area" +      destroy: "Delete stop area"        activate: "Activate this stop"        deactivate: "Deactivate this stop"        activate_confirm: "Are you sure you want to activate this stop ?" @@ -47,9 +48,9 @@ en:        export_hub_physical: "Export HUB physical"      filters:        name_or_objectid: "Search by name or by objectid..." -      zip_code: Type a zip code... -      city_name: Type a city name... -      area_type: Type an area type... +      zip_code: Enter a zip code... +      city_name: Enter a city name... +      area_type: Enter an area type...      new:        title: "Add a new stop"      update: @@ -69,7 +70,7 @@ en:        stop_managment: "Parent-child relations"        access_managment: "Access Points and Links managment"        access_points: "Access Points" -      not_editable: "Le type d'arrêt est non modifiable" +      not_editable: "The area type is not editable"        state:          active: Active          deactivated: Deactivated @@ -147,7 +148,7 @@ en:          zip_code: "Zip code"          city_name: "City"          waiting_time: Waiting time (minutes) -        state: State +        state: Status    formtastic:      titles:        stop_area: @@ -205,6 +206,9 @@ en:            comment: "Maximum length = 255."            coordinates: "Coordinates are mandatory."            projection_xy: "x,y in secondary referential, dot for decimal separator" +    hints: +      stop_area: +        registration_number: Leave empty for automatic value.    referential_stop_areas:      <<: *en_stop_areas diff --git a/config/locales/stop_areas.fr.yml b/config/locales/stop_areas.fr.yml index aee112be7..6a5fbf24b 100644 --- a/config/locales/stop_areas.fr.yml +++ b/config/locales/stop_areas.fr.yml @@ -9,7 +9,7 @@ fr:        registration_number:          already_taken: Déjà utilisé          cannot_be_empty: Ce champ est requis -        invalid: Valeur invalide +        invalid: "Valeur invalide (valeur attendue: \"%{mask}\")"      default_geometry_success: "%{count} arrêts édités"      stop_area:        no_position: "Pas de position" @@ -19,6 +19,7 @@ fr:        general: "General"        localisation: "Localisation"        accessibility: "Accessibilité" +      custom_fields: "Champs personnalisés"      actions:        new: "Ajouter un arrêt"        create: "Ajouter un arrêt" @@ -208,6 +209,9 @@ fr:            comment: "Longueur maximale = 255."            coordinates: "Les coordonnées sont obligatoires."            projection_xy: "x,y dans le référentiel secondaire, le séparateur de décimales est 'point'" +    hints: +      stop_area: +        registration_number: Laisser blanc pour assigner une valeur automatiquement.    referential_stop_areas:      <<: *fr_stop_areas diff --git a/config/locales/stop_points.en.yml b/config/locales/stop_points.en.yml index 72e138270..76e142ba1 100644 --- a/config/locales/stop_points.en.yml +++ b/config/locales/stop_points.en.yml @@ -43,8 +43,8 @@ en:          created_at: Created          updated_at: Updated          deleted_at: "Activated" -        for_boarding: "For boarding" -        for_alighting: "For alighting" +        for_boarding: "Boarding" +        for_alighting: "Alighting"          area_type: "Area type"          city_name: "City name"          zip_code: "Zip code" diff --git a/config/locales/table_builders.en.yml b/config/locales/table_builders.en.yml new file mode 100644 index 000000000..9ee59a1e1 --- /dev/null +++ b/config/locales/table_builders.en.yml @@ -0,0 +1,3 @@ +en: +  table_builders: +    selected_elements: "selected element(s)"
\ No newline at end of file diff --git a/config/locales/table_builders.fr.yml b/config/locales/table_builders.fr.yml new file mode 100644 index 000000000..3c92640fc --- /dev/null +++ b/config/locales/table_builders.fr.yml @@ -0,0 +1,3 @@ +fr: +  table_builders: +    selected_elements: "élement(s) sélectionné(s)"
\ No newline at end of file diff --git a/config/locales/vehicle_journey_exports.en.yml b/config/locales/vehicle_journey_exports.en.yml index 93a782026..4e658353e 100644 --- a/config/locales/vehicle_journey_exports.en.yml +++ b/config/locales/vehicle_journey_exports.en.yml @@ -1,7 +1,7 @@  en:    vehicle_journey_exports:      new: -      title: "Export existing vehicle journey at stops" +      title: Vehicle journeys export        basename: "vehicle_journeys"      label:        vehicle_journey_id: "vj id (empty for new vj)" diff --git a/config/locales/vehicle_journeys.en.yml b/config/locales/vehicle_journeys.en.yml index f1ba96dc5..12d8d0da4 100644 --- a/config/locales/vehicle_journeys.en.yml +++ b/config/locales/vehicle_journeys.en.yml @@ -1,14 +1,29 @@  en:    vehicle_journeys:      vehicle_journeys_matrix: +      filters: +        id: Filter by ID... +        journey_pattern: Filter by journey pattern... +        timetable: Filter by timetable...                  cancel_selection: "Cancel Selection"        fetching_error: "There has been a problem fetching the data. Please reload the page to try again."        line_routes: "Line's routes"        modal_confirm: 'Do you want to save mofications before moving on to the next page ?' -      pagination: "Schedules %{minVJ} to %{maxVJ} over %{total}"        selected_journeys: "%{count} selected journeys"        show_purchase_window: 'Show the purchase window'        show_timetable: 'Show calendar' +      no_associated_timetables: No associated timetables +      no_associated_purchase_windows: No associated purchase windows +      no_associated_footnotes: No associated footnotes +      duplicate: +        one: Clone %{count} vehicle journey +        other: Clone %{count} vehicle journeys +        start_time: Indicative start time +        number: Number of vehicle journeys to create and clone +        delta: Delta from which vehicle journeys are created +      affect_company: Affect company +      no_line_footnotes: The line doesn't have any footnotes +      select_footnotes: Select footnotes to associate with the vehicle journey      vehicle_journey:        title_stopless: "Vehicle journey %{name}"        title: "Vehicle journey leaving from %{stop} at %{time}" @@ -17,10 +32,10 @@ en:        title: "Vehicle journey frequency leaving from %{stop} at %{time}"        title_frequency: "Vehicle journey frequency with %{interval}min leaving from %{stop} at %{time_first} to %{time_last}"      actions: -      index: "Vehicle time's board" -      new: "Add a new timed vehicle journey" +      index: "Vehicle journeys" +      new: "Add a new vehicle journey"        new_frequency: "Add a new frequency vehicle journey" -      edit: "Edit this timed vehicle journey" +      edit: "Edit this vehicle journey"        edit_frequency: "Edit this frequency vehicle journey"        destroy: "Remove this vehicle journey"        destroy_confirm: "Are you sure you want destroy this vehicle journey?" @@ -48,15 +63,18 @@ en:        show_journeys_without_schedule: "Show journeys without schedule"        slide_arrival: "arrival time at first stop"        slide_departure: "departure time at first stop" -      slide_title: "Shift all vehicle passing times" +      slide_title: "Shift all the vehicle journey passing times : %{id}"        slide: "Shift" +      slide_delta: "Shift of"        starting_stop: "Departure"        stop_title: "Stop"        submit_frequency_edit: "Edit frequency vehicle journey"        submit_frequency: "Create frequency vehicle journey"        submit_timed_edit: "Edit vehicle journey"        submit_timed: "Create vehicle journey" -      time_tables: "Associated calendars to vehicle journey" +      time_tables: "Associated timetables" +      purchase_windows: Associated purchase windows +      footnotes: Associated footnotes        to_arrivals: "Copy departures to arrivals"        to_departures: "Copy arrivals to departures"        to: "at" @@ -102,6 +120,7 @@ en:          checksum: "Checksum"          comment: "Comments"          company: "Company" +        company_name: "Company name"          created_at: Created at          creator_id: "Created by"          departure_time: "Departure" @@ -110,9 +129,10 @@ en:          footnote_ids: "Footnotes"          id: "Journey ID"          journey_frequency_ids: "Timeband" -        journey_name: "Name of the journey" +        journey_name: "Name of the vehicle journey"          journey_pattern_id: "Pattern ID"          journey_pattern: "Journey Pattern" +        journey_pattern_published_name: "Journey Pattern published name"          line: "Line"          mobility_restricted_suitability: "PRM accessibility"          name: "Journey Name" @@ -137,6 +157,7 @@ en:          updated_at: Updated at          vehicle_journey_at_stop_ids: "Time list"          vehicle_type_identifier: "Vehicle Type Identifier" +        start_time: Start time      errors:        models:          vehicle_journey: diff --git a/config/locales/vehicle_journeys.fr.yml b/config/locales/vehicle_journeys.fr.yml index d144e580f..466eca684 100644 --- a/config/locales/vehicle_journeys.fr.yml +++ b/config/locales/vehicle_journeys.fr.yml @@ -1,14 +1,29 @@  fr:    vehicle_journeys:      vehicle_journeys_matrix: +      filters: +        id: Filtrer par ID course... +        journey_pattern: 'Filtrer par code, nom ou OID de mission...' +        timetable: Filtrer par calendrier...        cancel_selection: "Annuler la sélection"        fetching_error: "La récupération des missions a rencontré un problème. Rechargez la page pour tenter de corriger le problème."        line_routes: "Séquences d'arrêts de la ligne"        modal_confirm: 'Voulez-vous valider vos modifications avant de changer de page?' -      pagination: "Liste des horaires %{minVJ} à %{maxVJ} sur %{total}"        selected_journeys: "%{count} course(s) sélectionnée(s)"        show_purchase_window: 'Voir le calendrier commercial'        show_timetable: 'Voir le calendrier' +      no_associated_timetables: Aucun calendrier associé +      no_associated_purchase_windows: Aucun calendrier commercial associé +      no_associated_footnotes: Aucune note associée +      duplicate: +        one: Dupliquer %{count} course +        other: Dupliquer %{count} courses +        start_time: Horaire de départ indicatif +        number: Nombre de courses à créer et dupliquer +        delta: Décalage à partir duquel on créé les courses +      affect_company: Indiquez un nom de transporteur... +      no_line_footnotes: La ligne ne possède pas de notes +      select_footnotes: Sélectionnez les notes à associer à cette course      vehicle_journey:        title_stopless: "Course %{name}"        title: "Course partant de %{stop} à %{time}" @@ -48,8 +63,9 @@ fr:        show_journeys_without_schedule: "Afficher les courses sans horaires"        slide_arrival: "horaire d'arrivée au 1° arrêt à"        slide_departure: "horaire de départ au 1° arrêt à" -      slide_title: "Décaler l'ensemble des horaires de course" +      slide_title: "Décaler l'ensemble des horaires de la course : %{id}"        slide: "Décaler" +      slide_delta: "Avec un décalage de"        starting_stop: "Origine"        stop_title: "Arrêt"        submit_frequency_edit: "Editer course en fréquence" @@ -57,6 +73,8 @@ fr:        submit_timed_edit: "Editer course"        submit_timed: "Créer course"        time_tables: "Calendriers associés à la course" +      purchase_windows: "Calendriers commerciaux associés à la course" +      footnotes: "Notes associés à la course"        to_arrivals: "Copie départs vers arrivées"        to_arrivals: "Copie départs vers arrivées"        to_departures: "Copie arrivées vers départs" @@ -103,6 +121,7 @@ fr:          checksum: "Signature métier"          comment: "Commentaires"          company: "Transporteur" +        company_name: "Nom du transporteur"          created_at: "Créé le"          creator_id: "Créé par"          departure_time: "Départ" @@ -114,6 +133,7 @@ fr:          journey_name: "Nom de la course"          journey_pattern_id: "ID Mission"          journey_pattern: "Mission" +        journey_pattern_published_name: "Nom public de la mission"          line: "Ligne"          mobility_restricted_suitability: "Accessibilité PMR"          name: "Nom Course" @@ -129,7 +149,7 @@ fr:          route: "Itinéraire"          time_slot: "Fréquence"          time_table_ids: "Liste des calendriers" -        time_tables: "Calendriers" +        time_tables: "Calendriers associés"          train_number: "Numéro de train"          transport_mode: "Mode de transport"          transport_submode: "Sous-mode de transport" @@ -138,6 +158,7 @@ fr:          updated_at: "Edité le"          vehicle_journey_at_stop_ids: "Liste des horaires"          vehicle_type_identifier: "Type d'identifiant du véhicule" +        start_time: Heure de départ      errors:        models:          vehicle_journey: diff --git a/config/locales/will_paginate.en.yml b/config/locales/will_paginate.en.yml index 29b8fe2bf..8f3189675 100644 --- a/config/locales/will_paginate.en.yml +++ b/config/locales/will_paginate.en.yml @@ -32,11 +32,11 @@ en:        single_page:          zero:  "No item found"          one:   "1 %{model} shown" -        other: "%{model} 1 to %{count} of %{count}" +        other: "%{model} 1 to %{count} out of %{count}"        single_page_html:          zero:  "No item found"          one:   "1 %{model} shown" -        other: "%{model} 1 to %{count} of %{count}" +        other: "%{model} 1 to %{count} out of %{count}" -      multi_page: "%{model} %{from} to %{to} of %{count}" -      multi_page_html: "%{model} %{from} to %{to} of %{count}" +      multi_page: "%{model} list %{from} to %{to} out of %{count}" +      multi_page_html: "%{model} list %{from} to %{to} out of %{count}" diff --git a/config/locales/workbench_outputs.en.yml b/config/locales/workbench_outputs.en.yml index 05cf52d0e..2af2f7023 100644 --- a/config/locales/workbench_outputs.en.yml +++ b/config/locales/workbench_outputs.en.yml @@ -1,7 +1,7 @@  en:    workbench_outputs:      show: -      title: "Finalisations de l'offre" -      see_current_output: "Voir l'Offre actuelle" +      title: Current offers +      see_current_output: See the current offer        table_headers: -        ended_at: "Offer created date" +        ended_at: "Ended at" diff --git a/config/locales/workbench_outputs.fr.yml b/config/locales/workbench_outputs.fr.yml index 560888c54..b4d339434 100644 --- a/config/locales/workbench_outputs.fr.yml +++ b/config/locales/workbench_outputs.fr.yml @@ -1,7 +1,7 @@  fr:    workbench_outputs:      show: -      title: "Finalisations de l'offre" +      title: Finaliser des jeux de données        see_current_output: "Voir l'Offre actuelle"        table_headers: -        ended_at: "Date et heure de création de l'offre" +        ended_at: "Date et heure de création" diff --git a/config/locales/workbenches.en.yml b/config/locales/workbenches.en.yml index 2d9b27045..876f18766 100644 --- a/config/locales/workbenches.en.yml +++ b/config/locales/workbenches.en.yml @@ -1,7 +1,7 @@  en:    workbenches:      show: -      title: "%{name}" +      title: "Transport offer %{name}"      edit:        title: "Configure the workbench"      update: @@ -21,7 +21,7 @@ en:          see: "See the list"          no_content: "No content yet."      actions: -      show_output: "Merge offer" +      show_output: "Merge Transport Offer"        affect_ccset: "Configure"    activerecord:      models: @@ -29,3 +29,8 @@ en:          zero: "workbench"          one: "workbench"          other: "workbenches" +    attributes: +      workbench: +        import_compliance_control_set_id: Space data before import +        merge_compliance_control_set_id: Space data before merge + diff --git a/config/locales/workbenches.fr.yml b/config/locales/workbenches.fr.yml index 8eee1a516..f857bfcd1 100644 --- a/config/locales/workbenches.fr.yml +++ b/config/locales/workbenches.fr.yml @@ -1,7 +1,7 @@  fr:    workbenches:      show: -      title: "%{name}" +      title: "Offre de transport %{name}"      edit:        title: "Configurer l'espace de travail"      update: @@ -29,3 +29,7 @@ fr:          zero: "espace de travail"          one: "espace de travail"          other: "espaces de travail" +    attributes: +      workbench: +        import_compliance_control_set_id: Jeu de données après import +        merge_compliance_control_set_id: Jeu de Données avant intégration diff --git a/config/routes.rb b/config/routes.rb index 25105241a..41b345aa5 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -1,5 +1,3 @@ -require 'sidekiq/web' -  ChouetteIhm::Application.routes.draw do    resource :dashboard @@ -179,7 +177,9 @@ ChouetteIhm::Application.routes.draw do      end    end -  mount Sidekiq::Web => '/sidekiq' +  authenticate :user, lambda { |u| u.can_monitor_sidekiq? } do +    mount Sidekiq::Web => '/sidekiq' +  end    namespace :api do      namespace :v1 do diff --git a/config/webpacker.yml b/config/webpacker.yml index be1105bad..24a7e32dc 100644 --- a/config/webpacker.yml +++ b/config/webpacker.yml @@ -41,7 +41,7 @@ development:      public: localhost:3035      hmr: false      # Inline should be set to true if using HMR -    inline: true +    inline: false      overlay: true      disable_host_check: true      use_local_ip: false diff --git a/db/migrate/20150219175300_insert_default_organisation.rb b/db/migrate/20150219175300_insert_default_organisation.rb index 2734893f5..ac8ecb9b6 100644 --- a/db/migrate/20150219175300_insert_default_organisation.rb +++ b/db/migrate/20150219175300_insert_default_organisation.rb @@ -1,5 +1,5 @@  class InsertDefaultOrganisation < ActiveRecord::Migration -  class Organisation  < ActiveRecord::Base +  class Organisation  < ApplicationModel      attr_accessor :name    end diff --git a/db/migrate/20180313082623_add_custom_field_values_to_stop_areas.rb b/db/migrate/20180313082623_add_custom_field_values_to_stop_areas.rb new file mode 100644 index 000000000..e49be7e40 --- /dev/null +++ b/db/migrate/20180313082623_add_custom_field_values_to_stop_areas.rb @@ -0,0 +1,5 @@ +class AddCustomFieldValuesToStopAreas < ActiveRecord::Migration +  def change +    add_column :stop_areas, :custom_field_values, :jsonb +  end +end diff --git a/db/migrate/20180315152714_remove_short_name_from_calendars.rb b/db/migrate/20180315152714_remove_short_name_from_calendars.rb new file mode 100644 index 000000000..3b6759f7b --- /dev/null +++ b/db/migrate/20180315152714_remove_short_name_from_calendars.rb @@ -0,0 +1,5 @@ +class RemoveShortNameFromCalendars < ActiveRecord::Migration +  def change +    remove_column :calendars, :short_name, :string +  end +end diff --git a/db/migrate/20180316115003_add_custom_field_values_to_companies.rb b/db/migrate/20180316115003_add_custom_field_values_to_companies.rb new file mode 100644 index 000000000..1791a6970 --- /dev/null +++ b/db/migrate/20180316115003_add_custom_field_values_to_companies.rb @@ -0,0 +1,5 @@ +class AddCustomFieldValuesToCompanies < ActiveRecord::Migration +  def change +    add_column :companies, :custom_field_values, :json +  end +end diff --git a/db/migrate/20180330074336_add_metadata_to_routes.rb b/db/migrate/20180330074336_add_metadata_to_routes.rb new file mode 100644 index 000000000..1a35dbb65 --- /dev/null +++ b/db/migrate/20180330074336_add_metadata_to_routes.rb @@ -0,0 +1,5 @@ +class AddMetadataToRoutes < ActiveRecord::Migration +  def change +    add_column :routes, :metadata, :jsonb +  end +end diff --git a/db/migrate/20180330124436_add_metadata_to_other_models.rb b/db/migrate/20180330124436_add_metadata_to_other_models.rb new file mode 100644 index 000000000..db01c77df --- /dev/null +++ b/db/migrate/20180330124436_add_metadata_to_other_models.rb @@ -0,0 +1,28 @@ +class AddMetadataToOtherModels < ActiveRecord::Migration +  def change +    [ +      Api::V1::ApiKey, +      Calendar, +      Chouette::AccessLink, +      Chouette::AccessPoint, +      Chouette::Company, +      Chouette::ConnectionLink, +      Chouette::GroupOfLine, +      Chouette::JourneyPattern, +      Chouette::Line, +      Chouette::Network, +      Chouette::PtLink, +      Chouette::PurchaseWindow, +      Chouette::RoutingConstraintZone, +      Chouette::StopArea, +      Chouette::StopPoint, +      Chouette::TimeTable, +      Chouette::Timeband, +      Chouette::VehicleJourney, +      ComplianceCheckSet, +      ComplianceControlSet, +    ].each do |model| +      add_column model.table_name.split(".").last, :metadata, :jsonb, default: {} +    end +  end +end diff --git a/db/migrate/20180403065419_remove_papertrail_tables.rb b/db/migrate/20180403065419_remove_papertrail_tables.rb new file mode 100644 index 000000000..8494058e1 --- /dev/null +++ b/db/migrate/20180403065419_remove_papertrail_tables.rb @@ -0,0 +1,5 @@ +class RemovePapertrailTables < ActiveRecord::Migration +  def change +    drop_table :versions +  end +end diff --git a/db/migrate/20180403100007_add_registration_number_indexes.rb b/db/migrate/20180403100007_add_registration_number_indexes.rb new file mode 100644 index 000000000..bc2d48329 --- /dev/null +++ b/db/migrate/20180403100007_add_registration_number_indexes.rb @@ -0,0 +1,7 @@ +class AddRegistrationNumberIndexes < ActiveRecord::Migration +  def change +    add_index :stop_areas, [:stop_area_referential_id, :registration_number], name: 'index_stop_areas_on_referential_id_and_registration_number' +    add_index :lines, [:line_referential_id, :registration_number], name: 'index_lines_on_referential_id_and_registration_number' +    add_index :companies, [:line_referential_id, :registration_number], name: 'index_companies_on_referential_id_and_registration_number' +  end +end diff --git a/db/migrate/20180405133659_change_companies_custom_fields_values_type.rb b/db/migrate/20180405133659_change_companies_custom_fields_values_type.rb new file mode 100644 index 000000000..7248c29f6 --- /dev/null +++ b/db/migrate/20180405133659_change_companies_custom_fields_values_type.rb @@ -0,0 +1,8 @@ +class ChangeCompaniesCustomFieldsValuesType < ActiveRecord::Migration +  def change +    reversible do |dir| +      dir.up { change_column :companies, :custom_field_values, 'jsonb USING CAST(custom_field_values AS jsonb)', :default => {} } +      dir.down { change_column :companies, :custom_field_values, 'json USING CAST(custom_field_values AS json)', :default => {} } +    end +  end +end diff --git a/db/migrate/20180413082929_clean_lines_secondary_companies.rb b/db/migrate/20180413082929_clean_lines_secondary_companies.rb new file mode 100644 index 000000000..4c5659e8f --- /dev/null +++ b/db/migrate/20180413082929_clean_lines_secondary_companies.rb @@ -0,0 +1,5 @@ +class CleanLinesSecondaryCompanies < ActiveRecord::Migration +  def up +    Chouette::Line.where("secondary_company_ids = '{NULL}'").update_all secondary_company_ids: nil +  end +end diff --git a/db/schema.rb b/db/schema.rb index 58c8b0779..7e0e9c2b5 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -22,7 +22,7 @@ ActiveRecord::Schema.define(version: 20180319043333) do    create_table "access_links", id: :bigserial, force: :cascade do |t|      t.integer  "access_point_id",                        limit: 8      t.integer  "stop_area_id",                           limit: 8 -    t.string   "objectid",                                                                  null: false +    t.string   "objectid",                                                                               null: false      t.integer  "object_version",                         limit: 8      t.string   "name"      t.string   "comment" @@ -39,6 +39,7 @@ ActiveRecord::Schema.define(version: 20180319043333) do      t.string   "link_orientation"      t.datetime "created_at"      t.datetime "updated_at" +    t.jsonb    "metadata",                                                                  default: {}    end    add_index "access_links", ["objectid"], name: "access_links_objectid_key", unique: true, using: :btree @@ -66,6 +67,7 @@ ActiveRecord::Schema.define(version: 20180319043333) do      t.text     "import_xml"      t.datetime "created_at"      t.datetime "updated_at" +    t.jsonb    "metadata",                                                            default: {}    end    add_index "access_points", ["objectid"], name: "access_points_objectid_key", unique: true, using: :btree @@ -77,13 +79,13 @@ ActiveRecord::Schema.define(version: 20180319043333) do      t.datetime "created_at"      t.datetime "updated_at"      t.integer  "organisation_id", limit: 8 +    t.jsonb    "metadata",                  default: {}    end    add_index "api_keys", ["organisation_id"], name: "index_api_keys_on_organisation_id", using: :btree    create_table "calendars", id: :bigserial, force: :cascade do |t|      t.string    "name" -    t.string    "short_name"      t.daterange "date_ranges",                               array: true      t.date      "dates",                                     array: true      t.boolean   "shared",                    default: false @@ -93,10 +95,10 @@ ActiveRecord::Schema.define(version: 20180319043333) do      t.integer   "workgroup_id",    limit: 8      t.integer   "int_day_types"      t.date      "excluded_dates",                            array: true +    t.jsonb     "metadata",                  default: {}    end    add_index "calendars", ["organisation_id"], name: "index_calendars_on_organisation_id", using: :btree -  add_index "calendars", ["short_name"], name: "index_calendars_on_short_name", unique: true, using: :btree    add_index "calendars", ["workgroup_id"], name: "index_calendars_on_workgroup_id", using: :btree    create_table "clean_up_results", id: :bigserial, force: :cascade do |t| @@ -124,7 +126,7 @@ ActiveRecord::Schema.define(version: 20180319043333) do    add_index "clean_ups", ["referential_id"], name: "index_clean_ups_on_referential_id", using: :btree    create_table "companies", id: :bigserial, force: :cascade do |t| -    t.string   "objectid",                            null: false +    t.string   "objectid",                                         null: false      t.integer  "object_version",            limit: 8      t.string   "name"      t.string   "short_name" @@ -141,6 +143,8 @@ ActiveRecord::Schema.define(version: 20180319043333) do      t.text     "import_xml"      t.datetime "created_at"      t.datetime "updated_at" +    t.jsonb    "custom_field_values" +    t.jsonb    "metadata",                            default: {}    end    add_index "companies", ["line_referential_id"], name: "index_companies_on_line_referential_id", using: :btree @@ -193,14 +197,15 @@ ActiveRecord::Schema.define(version: 20180319043333) do      t.string   "status"      t.integer  "parent_id",                 limit: 8      t.string   "parent_type" -    t.datetime "created_at",                          null: false -    t.datetime "updated_at",                          null: false +    t.datetime "created_at",                                       null: false +    t.datetime "updated_at",                                       null: false      t.string   "current_step_id"      t.float    "current_step_progress"      t.string   "name"      t.datetime "started_at"      t.datetime "ended_at"      t.datetime "notified_parent_at" +    t.jsonb    "metadata",                            default: {}    end    add_index "compliance_check_sets", ["compliance_control_set_id"], name: "index_compliance_check_sets_on_compliance_control_set_id", using: :btree @@ -239,8 +244,9 @@ ActiveRecord::Schema.define(version: 20180319043333) do    create_table "compliance_control_sets", id: :bigserial, force: :cascade do |t|      t.string   "name"      t.integer  "organisation_id", limit: 8 -    t.datetime "created_at",                null: false -    t.datetime "updated_at",                null: false +    t.datetime "created_at",                             null: false +    t.datetime "updated_at",                             null: false +    t.jsonb    "metadata",                  default: {}    end    add_index "compliance_control_sets", ["organisation_id"], name: "index_compliance_control_sets_on_organisation_id", using: :btree @@ -266,7 +272,7 @@ ActiveRecord::Schema.define(version: 20180319043333) do    create_table "connection_links", id: :bigserial, force: :cascade do |t|      t.integer  "departure_id",                           limit: 8      t.integer  "arrival_id",                             limit: 8 -    t.string   "objectid",                                                                  null: false +    t.string   "objectid",                                                                               null: false      t.integer  "object_version",                         limit: 8      t.string   "name"      t.string   "comment" @@ -282,6 +288,7 @@ ActiveRecord::Schema.define(version: 20180319043333) do      t.integer  "int_user_needs"      t.datetime "created_at"      t.datetime "updated_at" +    t.jsonb    "metadata",                                                                  default: {}    end    add_index "connection_links", ["objectid"], name: "connection_links_objectid_key", unique: true, using: :btree @@ -299,18 +306,58 @@ ActiveRecord::Schema.define(version: 20180319043333) do    add_index "custom_fields", ["resource_type"], name: "index_custom_fields_on_resource_type", using: :btree +  create_table "export_messages", id: :bigserial, force: :cascade do |t| +    t.string   "criticity" +    t.string   "message_key" +    t.hstore   "message_attributes" +    t.integer  "export_id",           limit: 8 +    t.integer  "resource_id",         limit: 8 +    t.datetime "created_at" +    t.datetime "updated_at" +    t.hstore   "resource_attributes" +  end + +  add_index "export_messages", ["export_id"], name: "index_export_messages_on_export_id", using: :btree +  add_index "export_messages", ["resource_id"], name: "index_export_messages_on_resource_id", using: :btree + +  create_table "export_resources", id: :bigserial, force: :cascade do |t| +    t.integer  "export_id",     limit: 8 +    t.string   "status" +    t.datetime "created_at" +    t.datetime "updated_at" +    t.string   "resource_type" +    t.string   "reference" +    t.string   "name" +    t.hstore   "metrics" +  end + +  add_index "export_resources", ["export_id"], name: "index_export_resources_on_export_id", using: :btree +    create_table "exports", id: :bigserial, force: :cascade do |t| -    t.integer  "referential_id",  limit: 8      t.string   "status" -    t.string   "type" -    t.string   "options" +    t.string   "current_step_id" +    t.float    "current_step_progress" +    t.integer  "workbench_id",          limit: 8 +    t.integer  "referential_id",        limit: 8 +    t.string   "name"      t.datetime "created_at"      t.datetime "updated_at" -    t.string   "references_type" -    t.string   "reference_ids" +    t.string   "file" +    t.datetime "started_at" +    t.datetime "ended_at" +    t.string   "token_upload" +    t.string   "type" +    t.integer  "parent_id",             limit: 8 +    t.string   "parent_type" +    t.datetime "notified_parent_at" +    t.integer  "current_step",                    default: 0 +    t.integer  "total_steps",                     default: 0 +    t.string   "creator" +    t.hstore   "options"    end    add_index "exports", ["referential_id"], name: "index_exports_on_referential_id", using: :btree +  add_index "exports", ["workbench_id"], name: "index_exports_on_workbench_id", using: :btree    create_table "facilities", id: :bigserial, force: :cascade do |t|      t.integer  "stop_area_id",       limit: 8 @@ -359,7 +406,7 @@ ActiveRecord::Schema.define(version: 20180319043333) do    end    create_table "group_of_lines", id: :bigserial, force: :cascade do |t| -    t.string   "objectid",                      null: false +    t.string   "objectid",                                   null: false      t.integer  "object_version",      limit: 8      t.string   "name"      t.string   "comment" @@ -368,6 +415,7 @@ ActiveRecord::Schema.define(version: 20180319043333) do      t.text     "import_xml"      t.datetime "created_at"      t.datetime "updated_at" +    t.jsonb    "metadata",                      default: {}    end    add_index "group_of_lines", ["line_referential_id"], name: "index_group_of_lines_on_line_referential_id", using: :btree @@ -421,9 +469,9 @@ ActiveRecord::Schema.define(version: 20180319043333) do      t.string   "type"      t.integer  "parent_id",             limit: 8      t.string   "parent_type" +    t.datetime "notified_parent_at"      t.integer  "current_step",                    default: 0      t.integer  "total_steps",                     default: 0 -    t.datetime "notified_parent_at"      t.string   "creator"    end @@ -446,7 +494,7 @@ ActiveRecord::Schema.define(version: 20180319043333) do    create_table "journey_patterns", id: :bigserial, force: :cascade do |t|      t.integer  "route_id",                limit: 8 -    t.string   "objectid",                          null: false +    t.string   "objectid",                                       null: false      t.integer  "object_version",          limit: 8      t.string   "name"      t.string   "comment" @@ -460,6 +508,7 @@ ActiveRecord::Schema.define(version: 20180319043333) do      t.text     "checksum_source"      t.string   "data_source_ref"      t.json     "costs" +    t.jsonb    "metadata",                          default: {}    end    add_index "journey_patterns", ["objectid"], name: "journey_patterns_objectid_key", unique: true, using: :btree @@ -533,6 +582,7 @@ ActiveRecord::Schema.define(version: 20180319043333) do      t.datetime "created_at"      t.datetime "updated_at"      t.boolean  "seasonal" +    t.jsonb    "metadata",                                  default: {}    end    add_index "lines", ["line_referential_id"], name: "index_lines_on_line_referential_id", using: :btree @@ -554,7 +604,7 @@ ActiveRecord::Schema.define(version: 20180319043333) do    add_index "merges", ["workbench_id"], name: "index_merges_on_workbench_id", using: :btree    create_table "networks", id: :bigserial, force: :cascade do |t| -    t.string   "objectid",                      null: false +    t.string   "objectid",                                   null: false      t.integer  "object_version",      limit: 8      t.date     "version_date"      t.string   "description" @@ -568,6 +618,7 @@ ActiveRecord::Schema.define(version: 20180319043333) do      t.integer  "line_referential_id", limit: 8      t.datetime "created_at"      t.datetime "updated_at" +    t.jsonb    "metadata",                      default: {}    end    add_index "networks", ["line_referential_id"], name: "index_networks_on_line_referential_id", using: :btree @@ -592,13 +643,14 @@ ActiveRecord::Schema.define(version: 20180319043333) do      t.integer  "start_of_link_id", limit: 8      t.integer  "end_of_link_id",   limit: 8      t.integer  "route_id",         limit: 8 -    t.string   "objectid",                                            null: false +    t.string   "objectid",                                                         null: false      t.integer  "object_version",   limit: 8      t.string   "name"      t.string   "comment"      t.decimal  "link_distance",              precision: 19, scale: 2      t.datetime "created_at"      t.datetime "updated_at" +    t.jsonb    "metadata",                                            default: {}    end    add_index "pt_links", ["objectid"], name: "pt_links_objectid_key", unique: true, using: :btree @@ -606,13 +658,14 @@ ActiveRecord::Schema.define(version: 20180319043333) do    create_table "purchase_windows", id: :bigserial, force: :cascade do |t|      t.string    "name"      t.string    "color" -    t.daterange "date_ranges",                            array: true -    t.datetime  "created_at",                null: false -    t.datetime  "updated_at",                null: false +    t.daterange "date_ranges",                                         array: true +    t.datetime  "created_at",                             null: false +    t.datetime  "updated_at",                             null: false      t.string    "objectid"      t.string    "checksum"      t.text      "checksum_source"      t.integer   "referential_id",  limit: 8 +    t.jsonb     "metadata",                  default: {}    end    add_index "purchase_windows", ["referential_id"], name: "index_purchase_windows_on_referential_id", using: :btree @@ -703,6 +756,7 @@ ActiveRecord::Schema.define(version: 20180319043333) do      t.text     "checksum_source"      t.string   "data_source_ref"      t.json     "costs" +    t.jsonb    "metadata"    end    add_index "routes", ["objectid"], name: "routes_objectid_key", unique: true, using: :btree @@ -711,13 +765,14 @@ ActiveRecord::Schema.define(version: 20180319043333) do      t.string   "name"      t.datetime "created_at"      t.datetime "updated_at" -    t.string   "objectid",                  null: false +    t.string   "objectid",                               null: false      t.integer  "object_version",  limit: 8      t.integer  "route_id",        limit: 8 -    t.integer  "stop_point_ids",  limit: 8,              array: true +    t.integer  "stop_point_ids",  limit: 8,                           array: true      t.string   "checksum"      t.text     "checksum_source"      t.string   "data_source_ref" +    t.jsonb    "metadata",                  default: {}    end    create_table "routing_constraints_lines", id: false, force: :cascade do |t| @@ -766,11 +821,12 @@ ActiveRecord::Schema.define(version: 20180319043333) do      t.datetime "created_at"      t.datetime "updated_at"      t.string   "objectid_format" +    t.string   "registration_number_format"    end    create_table "stop_areas", id: :bigserial, force: :cascade do |t|      t.integer  "parent_id",                       limit: 8 -    t.string   "objectid",                                                            null: false +    t.string   "objectid",                                                                         null: false      t.integer  "object_version",                  limit: 8      t.string   "name"      t.string   "comment" @@ -802,6 +858,8 @@ ActiveRecord::Schema.define(version: 20180319043333) do      t.string   "kind"      t.jsonb    "localized_names"      t.datetime "confirmed_at" +    t.jsonb    "custom_field_values" +    t.jsonb    "metadata",                                                            default: {}    end    add_index "stop_areas", ["name"], name: "index_stop_areas_on_name", using: :btree @@ -817,13 +875,14 @@ ActiveRecord::Schema.define(version: 20180319043333) do    create_table "stop_points", id: :bigserial, force: :cascade do |t|      t.integer  "route_id",       limit: 8      t.integer  "stop_area_id",   limit: 8 -    t.string   "objectid",                 null: false +    t.string   "objectid",                              null: false      t.integer  "object_version", limit: 8      t.integer  "position"      t.string   "for_boarding"      t.string   "for_alighting"      t.datetime "created_at"      t.datetime "updated_at" +    t.jsonb    "metadata",                 default: {}    end    add_index "stop_points", ["objectid"], name: "stop_points_objectid_key", unique: true, using: :btree @@ -871,7 +930,7 @@ ActiveRecord::Schema.define(version: 20180319043333) do    add_index "time_table_periods", ["time_table_id"], name: "index_time_table_periods_on_time_table_id", using: :btree    create_table "time_tables", id: :bigserial, force: :cascade do |t| -    t.string   "objectid",                              null: false +    t.string   "objectid",                               null: false      t.integer  "object_version",  limit: 8, default: 1      t.string   "version"      t.string   "comment" @@ -886,6 +945,7 @@ ActiveRecord::Schema.define(version: 20180319043333) do      t.string   "checksum"      t.text     "checksum_source"      t.string   "data_source_ref" +    t.jsonb    "metadata",                  default: {}    end    add_index "time_tables", ["calendar_id"], name: "index_time_tables_on_calendar_id", using: :btree @@ -901,13 +961,14 @@ ActiveRecord::Schema.define(version: 20180319043333) do    add_index "time_tables_vehicle_journeys", ["vehicle_journey_id"], name: "index_time_tables_vehicle_journeys_on_vehicle_journey_id", using: :btree    create_table "timebands", id: :bigserial, force: :cascade do |t| -    t.string   "objectid",                 null: false +    t.string   "objectid",                              null: false      t.integer  "object_version", limit: 8      t.string   "name" -    t.time     "start_time",               null: false -    t.time     "end_time",                 null: false +    t.time     "start_time",                            null: false +    t.time     "end_time",                              null: false      t.datetime "created_at"      t.datetime "updated_at" +    t.jsonb    "metadata",                 default: {}    end    create_table "users", id: :bigserial, force: :cascade do |t| @@ -990,6 +1051,7 @@ ActiveRecord::Schema.define(version: 20180319043333) do      t.text     "checksum_source"      t.string   "data_source_ref"      t.jsonb    "custom_field_values",                       default: {} +    t.jsonb    "metadata",                                  default: {}    end    add_index "vehicle_journeys", ["objectid"], name: "vehicle_journeys_objectid_key", unique: true, using: :btree @@ -1031,8 +1093,10 @@ ActiveRecord::Schema.define(version: 20180319043333) do      t.string   "name"      t.integer  "line_referential_id",      limit: 8      t.integer  "stop_area_referential_id", limit: 8 -    t.datetime "created_at",                         null: false -    t.datetime "updated_at",                         null: false +    t.datetime "created_at",                                      null: false +    t.datetime "updated_at",                                      null: false +    t.string   "import_types",                       default: [],              array: true +    t.string   "export_types",                       default: [],              array: true    end    add_foreign_key "access_links", "access_points", name: "aclk_acpt_fkey" diff --git a/db/seeds/dev/custom_fields.seeds.rb b/db/seeds/dev/custom_fields.seeds.rb new file mode 100644 index 000000000..eb3afc394 --- /dev/null +++ b/db/seeds/dev/custom_fields.seeds.rb @@ -0,0 +1,52 @@ +# coding: utf-8 + +require_relative '../seed_helpers' + +Workgroup.find_each do |workgroup| +  puts workgroup.inspect + +  workgroup.custom_fields.seed_by(code: "capacity") do |field| +    field.resource_type = "VehicleJourney" +    field.name = "Bus Capacity" +    field.field_type = "list" +    field.options = { list_values: { "0": "", "1": "48 places", "2": "54 places" }} +  end + +  workgroup.custom_fields.seed_by(code: "company_commercial_name") do |field| +    field.resource_type = "Company" +    field.name = "Nom commercial" +    field.field_type = "list" +    field.options = { list_values: { "0": "", "1": "OuiBus", "2": "Alsa" }} +  end + +  workgroup.custom_fields.seed_by(code: "company_contact_name") do |field| +    field.resource_type = "Company" +    field.name = "Nom du référent" +    field.field_type = "string" +  end + +  workgroup.custom_fields.seed_by(code: "stop_area_test_list") do |field| +    field.resource_type = "StopArea" +    field.name = "Test de Liste" +    field.field_type = "list" +    field.options = { list_values: { "0": "", "1": "Valeur 1", "2": "Valeur 2" }} +  end + +  workgroup.custom_fields.seed_by(code: "stop_area_test_string") do |field| +    field.resource_type = "StopArea" +    field.name = "Test de Texte" +    field.field_type = "string" +  end + +  workgroup.custom_fields.seed_by(code: "stop_area_test_integer") do |field| +    field.resource_type = "StopArea" +    field.name = "Test de Nomber" +    field.field_type = "integer" +  end + +  workgroup.custom_fields.seed_by(code: "stop_area_test_attachment") do |field| +    field.resource_type = "StopArea" +    field.name = "Test de Piece Jointe" +    field.field_type = "attachment" +  end +end diff --git a/db/seeds/development/custom_fields.seeds.rb b/db/seeds/development/custom_fields.seeds.rb new file mode 100644 index 000000000..eb3afc394 --- /dev/null +++ b/db/seeds/development/custom_fields.seeds.rb @@ -0,0 +1,52 @@ +# coding: utf-8 + +require_relative '../seed_helpers' + +Workgroup.find_each do |workgroup| +  puts workgroup.inspect + +  workgroup.custom_fields.seed_by(code: "capacity") do |field| +    field.resource_type = "VehicleJourney" +    field.name = "Bus Capacity" +    field.field_type = "list" +    field.options = { list_values: { "0": "", "1": "48 places", "2": "54 places" }} +  end + +  workgroup.custom_fields.seed_by(code: "company_commercial_name") do |field| +    field.resource_type = "Company" +    field.name = "Nom commercial" +    field.field_type = "list" +    field.options = { list_values: { "0": "", "1": "OuiBus", "2": "Alsa" }} +  end + +  workgroup.custom_fields.seed_by(code: "company_contact_name") do |field| +    field.resource_type = "Company" +    field.name = "Nom du référent" +    field.field_type = "string" +  end + +  workgroup.custom_fields.seed_by(code: "stop_area_test_list") do |field| +    field.resource_type = "StopArea" +    field.name = "Test de Liste" +    field.field_type = "list" +    field.options = { list_values: { "0": "", "1": "Valeur 1", "2": "Valeur 2" }} +  end + +  workgroup.custom_fields.seed_by(code: "stop_area_test_string") do |field| +    field.resource_type = "StopArea" +    field.name = "Test de Texte" +    field.field_type = "string" +  end + +  workgroup.custom_fields.seed_by(code: "stop_area_test_integer") do |field| +    field.resource_type = "StopArea" +    field.name = "Test de Nomber" +    field.field_type = "integer" +  end + +  workgroup.custom_fields.seed_by(code: "stop_area_test_attachment") do |field| +    field.resource_type = "StopArea" +    field.name = "Test de Piece Jointe" +    field.field_type = "attachment" +  end +end diff --git a/db/seeds/seed_helpers.rb b/db/seeds/seed_helpers.rb new file mode 100644 index 000000000..708362a6c --- /dev/null +++ b/db/seeds/seed_helpers.rb @@ -0,0 +1,12 @@ +class ActiveRecord::Base +  def self.seed_by(key_attribute, &block) +    model = find_or_initialize_by key_attribute +    print "Seed #{name} #{key_attribute.inspect} " +    yield model + +    puts "[#{(model.changed? ? 'updated' : 'no change')}]" +    model.save! + +    model +  end +end diff --git a/db/seeds/stif.seeds.rb b/db/seeds/stif.seeds.rb index aa87b6f6c..98192385f 100644 --- a/db/seeds/stif.seeds.rb +++ b/db/seeds/stif.seeds.rb @@ -1,46 +1,25 @@  # coding: utf-8 -# This file should contain all the record creation needed to seed the database with its default values. -# The data can then be loaded with the rake db:seed (or created alongside the db with db:setup). -stop_area_referential = StopAreaReferential.find_or_create_by!(name: "Reflex", objectid_format: "stif_netex") -line_referential = LineReferential.find_or_create_by!(name: "CodifLigne", objectid_format: "stif_netex") +require_relative 'seed_helpers' -workgroup = Workgroup.find_or_create_by!(name: "Gestion de l'offre théorique IDFm") do |w| -  w.line_referential      = line_referential -  w.stop_area_referential = stop_area_referential -  w.export_types = ["Export::Netex"] +stif = Organisation.seed_by(code: "STIF") do |o| +  o.name = 'STIF'  end -Workbench.update_all workgroup_id: workgroup - -# Organisations -stif = Organisation.find_or_create_by!(code: "STIF") do |org| -  org.name = 'STIF' +stop_area_referential = StopAreaReferential.seed_by(name: "Reflex") do |r| +  r.objectid_format = "stif_netex" +  r.add_member stif, owner: true  end -# operator = Organisation.find_or_create_by!(code: 'transporteur-a') do |organisation| -#   organisation.name = "Transporteur A" -# end - -# Member -line_referential.add_member stif, owner: true -# line_referential.add_member operator -stop_area_referential.add_member stif, owner: true -# stop_area_referential.add_member operator +line_referential = LineReferential.seed_by(name: "CodifLigne") do |r| +  r.objectid_format = "stif_codifligne" +  r.add_member stif, owner: true +end -# Users -# stif.users.find_or_create_by!(username: "admin") do |user| -#   user.email = 'stif-boiv@af83.com' -#   user.password = "secret" -#   user.name = "STIF Administrateur" -# end -# -# operator.users.find_or_create_by!(username: "transporteur") do |user| -#   user.email = 'stif-boiv+transporteur@af83.com' -#   user.password = "secret" -#   user.name = "Martin Lejeune" -# end +workgroup = Workgroup.seed_by(name: "Gestion de l'offre théorique IDFm") do |w| +  w.line_referential      = line_referential +  w.stop_area_referential = stop_area_referential +  w.export_types          = ["Export::Netex"] +end -# Include all Lines in organisation functional_scope -stif.update sso_attributes: { functional_scope: line_referential.lines.pluck(:objectid) } -#operator.update sso_attributes: { functional_scope: line_referential.lines.limit(3).pluck(:objectid) } +Workbench.update_all workgroup_id: workgroup diff --git a/lib/af83/decorator/link.rb b/lib/af83/decorator/link.rb index ee09f80dc..9bf6c1ed9 100644 --- a/lib/af83/decorator/link.rb +++ b/lib/af83/decorator/link.rb @@ -85,6 +85,10 @@ class AF83::Decorator::Link      in_group_for_action? :secondary    end +  def disabled? +    !!disabled +  end +    def enabled?      enabled = false      if @options[:_if].nil? @@ -131,9 +135,9 @@ class AF83::Decorator::Link      out[:class] = extra_class      out.delete(:link_class)      out.delete(:link_method) -    out[:class] += " disabled" if disabled +    out[:class] += " disabled" if disabled?      out[:class].strip! -    out[:disabled] = !!disabled +    out[:disabled] = disabled?      out    end @@ -150,7 +154,7 @@ class AF83::Decorator::Link          html_options        ).to_html      else -      context.h.link_to content, href, html_options +      context.h.link_to content, (disabled? ? "#" : href), html_options      end    end  end diff --git a/lib/gtfs/time.rb b/lib/gtfs/time.rb new file mode 100644 index 000000000..49546532a --- /dev/null +++ b/lib/gtfs/time.rb @@ -0,0 +1,28 @@ +module GTFS +  class Time +    attr_reader :hours, :minutes, :seconds +    def initialize(hours, minutes, seconds) +      @hours, @minutes, @seconds = hours, minutes, seconds +    end + +    def real_hours +      hours.modulo(24) +    end + +    def time +      @time ||= ::Time.new(2000, 1, 1, real_hours, minutes, seconds, "+00:00") +    end + +    def day_offset +      hours / 24 +    end + +    FORMAT = /(\d{1,2}):(\d{2}):(\d{2})/ + +    def self.parse(definition) +      if definition.to_s =~ FORMAT +        new *[$1, $2, $3].map(&:to_i) +      end +    end +  end +end diff --git a/lib/route_way_cost_unit_converter.rb b/lib/route_way_cost_unit_converter.rb index 45edbf538..52515e52c 100644 --- a/lib/route_way_cost_unit_converter.rb +++ b/lib/route_way_cost_unit_converter.rb @@ -8,18 +8,24 @@ class RouteWayCostUnitConverter      end    end -  private -    # Round to 2 decimal places to appease JavaScript validation    def self.meters_to_kilometers(num)      return 0 unless num -    (num / 1000.0).to_i +    snap_to_one(num / 1000.0).to_i    end    def self.seconds_to_minutes(num)      return 0 unless num -    num / 60 +    snap_to_one(num / 60.0).to_i +  end + +  private + +  def self.snap_to_one(decimal) +    return 1 if decimal > 0 && decimal <= 1 + +    decimal    end  end diff --git a/lib/stif/netex_file.rb b/lib/stif/netex_file.rb index db0801bbe..1e78ca04a 100644 --- a/lib/stif/netex_file.rb +++ b/lib/stif/netex_file.rb @@ -47,6 +47,23 @@ module STIF            base_name = File.basename(file_name)            STIF::NetexFile::LINE_FILE_FORMAT.match(base_name).try(:[], 'line_object_id')          end + +        def parse_calendars calendars +          # <netex:ValidBetween> +          #  <netex:FromDate>2017-03-01</netex:FromDate> +          #  <netex:ToDate>2017-03-31</netex:ToDate> +          # </netex:ValidBetween> +          xml = Nokogiri::XML(calendars) +          from_date = nil +          to_date = nil +          xml.xpath("//netex:ValidBetween", "netex" => NetexFile::XML_NAME_SPACE).each do |valid_between| +            from_date = valid_between.xpath("netex:FromDate").try :text +            to_date = valid_between.xpath("netex:ToDate").try :text +          end +          from_date = from_date && Date.parse(from_date) +          to_date = to_date && Date.parse(to_date) +          Range.new from_date, to_date +        end        end        attr_accessor :name @@ -56,16 +73,7 @@ module STIF        end        def parse_calendars(calendars) -        # <netex:ValidBetween> -        #  <netex:FromDate>2017-03-01</netex:FromDate> -        #  <netex:ToDate>2017-03-31</netex:ToDate> -        # </netex:ValidBetween> -        xml = Nokogiri::XML(calendars) -        xml.xpath("//netex:ValidBetween", "netex" => NetexFile::XML_NAME_SPACE).each do |valid_between| -          from_date = valid_between.xpath("netex:FromDate").try :text -          to_date = valid_between.xpath("netex:ToDate").try :text -          periods << Range.new(Date.parse(from_date), Date.parse(to_date)) -        end +        periods << self.class.parse_calendars(calendars)        end        def add_offer_file(line_object_id) diff --git a/lib/tasks/ci.rake b/lib/tasks/ci.rake index 5b2c8ae3c..cb9ed77e7 100644 --- a/lib/tasks/ci.rake +++ b/lib/tasks/ci.rake @@ -10,6 +10,7 @@ namespace :ci do    desc "Prepare CI build"    task :setup do +    cp "config/database.yml", "config/database.yml.orig"      cp "config/database/ci.yml", "config/database.yml"      puts "Use #{database_name} database"      sh "RAILS_ENV=test rake db:drop db:create db:migrate" @@ -28,6 +29,7 @@ namespace :ci do    end    def deploy_env +    return ENV["DEPLOY_ENV"] if ENV["DEPLOY_ENV"]      if git_branch == "master"        "dev"      elsif git_branch.in?(deploy_envs) @@ -54,12 +56,12 @@ namespace :ci do    desc "Deploy after CI"    task :deploy do -    return if ENV["CHOUETTE_DEPLOY_DISABLED"] - -    if deploy_env -      sh "cap #{deploy_env} deploy:migrations" -    else -      puts "No deploy for branch #{git_branch}" +    unless ENV["CHOUETTE_DEPLOY_DISABLED"] +      if deploy_env +        sh "cap #{deploy_env} deploy:migrations deploy:seed" +      else +        puts "No deploy for branch #{git_branch}" +      end      end    end @@ -75,6 +77,9 @@ namespace :ci do      task :clean do        puts "Drop #{database_name} database"        sh "RAILS_ENV=test rake db:drop" + +      # Restore projet config/database.yml +      # cp "config/database.yml.orig", "config/database.yml" if File.exists?("config/database.yml.orig")      end    end diff --git a/lib/tom_tom.rb b/lib/tom_tom.rb index a1a2bda43..fcebcc7ac 100644 --- a/lib/tom_tom.rb +++ b/lib/tom_tom.rb @@ -19,4 +19,8 @@ module TomTom    def self.batch(way_costs)      TomTom::Batch.new(@connection).batch(way_costs)    end + +  def self.matrix(way_costs) +    TomTom::Matrix.new(@connection).matrix(way_costs) +  end  end diff --git a/lib/tom_tom/matrix.rb b/lib/tom_tom/matrix.rb new file mode 100644 index 000000000..b0c8cc335 --- /dev/null +++ b/lib/tom_tom/matrix.rb @@ -0,0 +1,114 @@ +module TomTom +  class Matrix +    def initialize(connection) +      @connection = connection +    end + +    def matrix(way_costs) +      points_with_ids = points_from_way_costs(way_costs) +      points = points_as_params(points_with_ids) + +      Rails.logger.info "Invoke TomTom for #{points.size} points" + +      response = @connection.post do |req| +        req.url '/routing/1/matrix/json' +        req.headers['Content-Type'] = 'application/json' + +        req.params[:routeType] = 'shortest' +        req.params[:travelMode] = 'bus' + +        req.body = build_request_body(points) +      end + +      extract_costs_to_way_costs!( +        way_costs, +        points_with_ids, +        JSON.parse(response.body) +      ) +    end + +    def points_from_way_costs(way_costs) +      points = [] + +      way_costs.each do |way_cost| +        departure_id, arrival_id = way_cost.id.split('-') + +        departure = TomTom::Matrix::Point.new( +          way_cost.departure, +          departure_id +        ) +        arrival = TomTom::Matrix::Point.new( +          way_cost.arrival, +          arrival_id +        ) + +        # Don't add duplicate coordinates. This assumes that +        # `way_costs` consists of an ordered route of points where +        # each departure coordinate is the same as the preceding +        # arrival coordinate. +        if points.empty? || +            points.last.coordinates != departure.coordinates +          points << departure +        end + +        points << arrival +      end + +      points +    end + +    def points_as_params(points) +      points.map do |point| +        { +          point: { +            latitude: point.coordinates.lat, +            longitude: point.coordinates.lng +          } +        } +      end +    end + +    def build_request_body(points) +      # Serialize `BigDecimal` values as floats to please the TomTom API +      RequestJSONSerializer.dump({ +        origins: points, +        destinations: points +      }) +    end + +    def extract_costs_to_way_costs!(way_costs, points, matrix_json) +      way_costs = [] + +      # `row` and `column` order is the same as `points` +      matrix_json['matrix'].each_with_index do |row, row_i| +        row.each_with_index do |column, column_i| +          next if column['statusCode'] != 200 + +          distance = column['response']['routeSummary']['lengthInMeters'] + +          # Ignore costs between a point and itself (e.g. from A to A) +          next if distance == 0 + +          departure = points[row_i] +          arrival = points[column_i] + +          way_costs << WayCost.new( +            departure: Geokit::LatLng.new( +              departure.coordinates.lat, +              departure.coordinates.lng +            ), +            arrival: Geokit::LatLng.new( +              arrival.coordinates.lat, +              arrival.coordinates.lng +            ), +            distance: distance, +            time: column['response']['routeSummary']['travelTimeInSeconds'], +            id: "#{departure.id}-#{arrival.id}" +          ) +        end +      end + +      way_costs +    end +  end +end diff --git a/lib/tom_tom/matrix/point.rb b/lib/tom_tom/matrix/point.rb new file mode 100644 index 000000000..435b4d4b0 --- /dev/null +++ b/lib/tom_tom/matrix/point.rb @@ -0,0 +1,18 @@ +module TomTom +  class Matrix +    class Point +      attr_reader :coordinates, :id + +      def initialize(coordinates, id) +        @coordinates = coordinates +        @id = id +      end + +      def ==(other) +        other.is_a?(self.class) && +          @coordinates == other.coordinates && +          @id == other.id +      end +    end +  end +end diff --git a/lib/tom_tom/matrix/request_json_serializer.rb b/lib/tom_tom/matrix/request_json_serializer.rb new file mode 100644 index 000000000..f4d12e482 --- /dev/null +++ b/lib/tom_tom/matrix/request_json_serializer.rb @@ -0,0 +1,25 @@ +module TomTom +  class Matrix +    class RequestJSONSerializer +      def self.dump(hash) +        hash[:origins].map! do |point| +          point_to_f(point) +        end +        hash[:destinations].map! do |point| +          point_to_f(point) +        end + +        JSON.dump(hash) +      end + +      private + +      def self.point_to_f(point) +        point[:point][:latitude] = point[:point][:latitude].to_f +        point[:point][:longitude] = point[:point][:longitude].to_f + +        point +      end +    end +  end +end diff --git a/package.json b/package.json index 262d80b97..ef956105c 100644 --- a/package.json +++ b/package.json @@ -11,8 +11,10 @@      "clean-webpack-plugin": "^0.1.18",      "coffee-loader": "^0.9.0",      "coffeescript": "1.12.7", +    "es6-symbol": "^3.1.1",      "jquery": "3.2.1",      "lodash": "4.17.4", +    "polyfill-array-includes": "^1.0.0",      "promise-polyfill": "7.0.0",      "prop-types": "^15.6.0",      "react": "16.2.0", @@ -24,7 +26,7 @@      "redux-promise": "0.5.3",      "redux-thunk": "2.2.0",      "uglify-js": "3.3.2", -    "whatwg-fetch": "2.0.3" +    "whatwg-fetch": "^2.0.4"    },    "license": "MIT",    "engines": { diff --git a/spec/controllers/exports_controller_spec.rb b/spec/controllers/exports_controller_spec.rb index 3a67497ec..e2b89fc26 100644 --- a/spec/controllers/exports_controller_spec.rb +++ b/spec/controllers/exports_controller_spec.rb @@ -2,7 +2,7 @@ RSpec.describe ExportsController, :type => :controller do    login_user    let(:workbench) { create :workbench } -  let(:export)    { create(:netex_export, workbench: workbench) } +  let(:export)    { create(:netex_export, workbench: workbench, referential: first_referential) }    describe 'GET #new' do      it 'should be successful if authorized' do @@ -21,7 +21,7 @@ RSpec.describe ExportsController, :type => :controller do      let(:params){ {name: "foo"} }      let(:request){ post :create, workbench_id: workbench.id, export: params  }      it 'should create no objects' do -      expect{request}.to_not change{Export::Base.count} +      expect{request}.to_not change{Export::Netex.count}      end      context "with full params" do @@ -29,11 +29,12 @@ RSpec.describe ExportsController, :type => :controller do          name: "foo",          type: "Export::Netex",          duration: 12, -        export_type: :line +        export_type: :line, +        referential_id: first_referential.id        }}        it 'should be successful' do -        expect{request}.to change{Export::Base.count}.by(1) +        expect{request}.to change{Export::Netex.count}.by(1)        end        it "displays a flash message" do @@ -51,7 +52,7 @@ RSpec.describe ExportsController, :type => :controller do        }}        it 'should be unsuccessful' do -        expect{request}.to change{Export::Base.count}.by(0) +        expect{request}.to change{Export::Netex.count}.by(0)        end      end @@ -59,11 +60,12 @@ RSpec.describe ExportsController, :type => :controller do        let(:params){{          name: "foo",          type: "Export::Workgroup", -        duration: 90 +        duration: 90, +        referential_id: first_referential.id        }}        it 'should be successful' do -        expect{request}.to change{Export::Base.count}.by(1) +        expect{request}.to change{Export::Workgroup.count}.by(1)        end      end @@ -83,7 +85,7 @@ RSpec.describe ExportsController, :type => :controller do      context "with the token" do        it 'should be successful' do          post :upload, workbench_id: workbench.id, id: export.id, token: export.token_upload -        expect(response).to be_redirect +        expect(response).to be_success        end      end diff --git a/spec/controllers/lines_controller_spec.rb b/spec/controllers/lines_controller_spec.rb index 96f49bb36..78020484b 100644 --- a/spec/controllers/lines_controller_spec.rb +++ b/spec/controllers/lines_controller_spec.rb @@ -1,9 +1,36 @@  RSpec.describe LinesController, :type => :controller do    login_user -  let(:line_referential) { create :line_referential, member: @user.organisation } +  let(:line_referential) { create :line_referential, member: @user.organisation, objectid_format: :netex }    let(:line) { create :line, line_referential: line_referential } +  describe 'POST create' do +    let(:line_attrs){{ +      name: "test", +      transport_mode: "bus" +    }} +    let(:request){ post :create, line_referential_id: line_referential.id, line: line_attrs } + +    with_permission "lines.create" do +      it "should create a new line" do +        expect{request}.to change{ line_referential.lines.count }.by 1 +      end + +      context "with an empty value in secondary_company_ids" do +        let(:line_attrs){{ +          name: "test", +          transport_mode: "bus", +          secondary_company_ids: [""] +        }} + +        it "should cleanup secondary_company_ids" do +          expect{request}.to change{ line_referential.lines.count }.by 1 +          expect(line_referential.lines.last.secondary_company_ids).to eq [] +        end +      end +    end +  end +    describe 'PUT deactivate' do      let(:request){ put :deactivate, id: line.id, line_referential_id: line_referential.id } diff --git a/spec/controllers/routes_controller_spec.rb b/spec/controllers/routes_controller_spec.rb index e4dc6bc23..59020914d 100644 --- a/spec/controllers/routes_controller_spec.rb +++ b/spec/controllers/routes_controller_spec.rb @@ -42,11 +42,14 @@ RSpec.describe RoutesController, type: :controller do      before(:each) do        post :create, line_id: route.line_id,            referential_id: referential.id, -          route: { name: "changed"} +          route: { name: "changed", published_name: "published_name"}      end      it_behaves_like "line and referential linked"      it_behaves_like "redirected to referential_line_path(referential,line)" +    it "sets metadata" do +      expect(Chouette::Route.last.metadata.creator_username).to eq @user.username +    end    end    describe "PUT /update" do @@ -58,6 +61,9 @@ RSpec.describe RoutesController, type: :controller do      it_behaves_like "route, line and referential linked"      it_behaves_like "redirected to referential_line_path(referential,line)" +    it "sets metadata" do +      expect(Chouette::Route.last.metadata.modifier_username).to eq @user.username +    end    end    describe "GET /show" do @@ -83,6 +89,48 @@ RSpec.describe RoutesController, type: :controller do        expect(Chouette::Route.last.name).to eq(I18n.t('activerecord.copy', name: route.name))        expect(Chouette::Route.last.published_name).to eq(route.published_name) +      expect(Chouette::Route.last.stop_area_ids).to eq route.stop_area_ids +    end + +    context "when opposite = true" do +      before do +        @positions = Hash[*route.stop_points.map{|sp| [sp.id, sp.position]}.flatten] +      end +      it "creates a new route on the opposite way " do +        expect do +          post :duplicate, +            referential_id: route.line.line_referential_id, +            line_id: route.line_id, +            id: route.id, +            opposite: TRUE +        end.to change { Chouette::Route.count }.by(1) + +        expect(Chouette::Route.last.name).to eq(I18n.t('routes.opposite', name: route.name)) +        expect(Chouette::Route.last.published_name).to eq(Chouette::Route.last.name) +        expect(Chouette::Route.last.opposite_route).to eq(route) +        expect(Chouette::Route.last.stop_area_ids).to eq route.stop_area_ids.reverse +        route.reload.stop_points.each do |sp| +          expect(sp.position).to eq @positions[sp.id] +        end +      end +    end + +    context "on a duplicated route" do +      let!(:duplicated){ route.duplicate } +      it "creates a new route on the opposite way " do +        expect do +          post :duplicate, +            referential_id: duplicated.line.line_referential_id, +            line_id: duplicated.line_id, +            id: duplicated.id, +            opposite: TRUE +        end.to change { Chouette::Route.count }.by(1) + +        expect(Chouette::Route.last.name).to eq(I18n.t('routes.opposite', name: duplicated.name)) +        expect(Chouette::Route.last.published_name).to eq(Chouette::Route.last.name) +        expect(Chouette::Route.last.opposite_route).to eq(duplicated) +        expect(Chouette::Route.last.stop_area_ids).to eq duplicated.stop_area_ids.reverse +      end      end    end  end diff --git a/spec/factories/calendars.rb b/spec/factories/calendars.rb index d9fd242d1..d78f230c6 100644 --- a/spec/factories/calendars.rb +++ b/spec/factories/calendars.rb @@ -1,7 +1,6 @@  FactoryGirl.define do    factory :calendar do      sequence(:name) { |n| "Calendar #{n}" } -    sequence(:short_name) { |n| "Cal #{n}" }      date_ranges { [generate(:date_range)] }      sequence(:dates) { |n| [ Date.yesterday - n, Date.yesterday - 2*n ] }      shared false @@ -14,4 +13,3 @@ FactoryGirl.define do      date..(date+1)    end  end - diff --git a/spec/factories/custom_fields.rb b/spec/factories/custom_fields.rb index 7c43a6147..db7a3823e 100644 --- a/spec/factories/custom_fields.rb +++ b/spec/factories/custom_fields.rb @@ -3,7 +3,7 @@ FactoryGirl.define do      code "code"      resource_type "VehicleJourney"      sequence(:name){|n| "custom field ##{n}"} -    field_type "list" +    field_type "integer"      options( { capacity: "0" } )    end  end diff --git a/spec/features/calendars_permissions_spec.rb b/spec/features/calendars_permissions_spec.rb index 4857592d5..656c0dd78 100644 --- a/spec/features/calendars_permissions_spec.rb +++ b/spec/features/calendars_permissions_spec.rb @@ -1,8 +1,8 @@  RSpec.describe 'Calendars', type: :feature do    login_user -  let(:calendar) { create :calendar, organisation_id: 1 } -  let(:workgroup) { calendar.workgroup } +  let(:calendar) { create :calendar, organisation: first_organisation, workgroup: first_workgroup } +  let(:workgroup) { first_workgroup }    describe 'permissions' do      before do diff --git a/spec/features/calendars_spec.rb b/spec/features/calendars_spec.rb new file mode 100644 index 000000000..26220746b --- /dev/null +++ b/spec/features/calendars_spec.rb @@ -0,0 +1,16 @@ +RSpec.describe 'Calendars', type: :feature do +  login_user + +  let(:calendar1)        { create(:calendar, workgroup: @user.organisation.workgroups.first, organisation: @user.organisation) } +  let(:calendar2)        { create(:calendar) } + +  describe "index" do +    before(:each) do +      visit workgroup_calendars_path(calendar1.workgroup) +    end +    it "should only display calendars from same workgroup" do +      expect(page).to have_content calendar1.name +      expect(page).to_not have_content calendar2.name +    end +  end +end
\ No newline at end of file diff --git a/spec/features/compliance_control_sets_spec.rb b/spec/features/compliance_control_sets_spec.rb index 0f4597db3..306f363a5 100644 --- a/spec/features/compliance_control_sets_spec.rb +++ b/spec/features/compliance_control_sets_spec.rb @@ -12,7 +12,7 @@ RSpec.describe "ComplianceControlSets", type: :feature do    let(:other_control_cset) { create :compliance_control_set, organisation: other_orga }    let(:blox){ -    2.times.map{ | _ | create :compliance_control_block, compliance_control_set: control_set } +    2.times.map{ |n| create :compliance_control_block, compliance_control_set: control_set, transport_mode: StifTransportModeEnumerations.transport_modes[n], transport_submode: StifTransportSubmodeEnumerations.transport_submodes[n] }    }    before do diff --git a/spec/fixtures/google-sample-feed.zip b/spec/fixtures/google-sample-feed.zipBinary files differ new file mode 100644 index 000000000..79819e21a --- /dev/null +++ b/spec/fixtures/google-sample-feed.zip diff --git a/spec/fixtures/multiple_with_wrong_calendar.zip b/spec/fixtures/multiple_with_wrong_calendar.zipBinary files differ new file mode 100644 index 000000000..85c0ec61d --- /dev/null +++ b/spec/fixtures/multiple_with_wrong_calendar.zip diff --git a/spec/fixtures/multiple_with_wrong_calendar/OFFRE_TRANSDEV_20170301122517/calendriers.xml b/spec/fixtures/multiple_with_wrong_calendar/OFFRE_TRANSDEV_20170301122517/calendriers.xml new file mode 100644 index 000000000..bfbd0aea1 --- /dev/null +++ b/spec/fixtures/multiple_with_wrong_calendar/OFFRE_TRANSDEV_20170301122517/calendriers.xml @@ -0,0 +1,86 @@ +<?xml version="1.0" encoding="UTF-8"?> +<netex:PublicationDelivery xmlns:netex="http://www.netex.org.uk/netex" +    xmlns:siri="http://www.siri.org.uk/siri" xmlns:core="http://www.govtalk.gov.uk/core" +    xmlns:gml="http://www.opengis.net/gml/3.2" xmlns:ifopt="http://www.ifopt.org.uk/ifopt" +    xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" +    version="1.0"> +    <netex:PublicationTimestamp>2017-02-14T09:13:51.0</netex:PublicationTimestamp> +    <netex:ParticipantRef>CITYWAY</netex:ParticipantRef> +    <netex:dataObjects> +        <netex:GeneralFrame id="CITYWAY:GeneralFrame:NETEX_CALENDRIER-1_20170214090012:LOC" +            version="any"> +            <netex:TypeOfFrameRef ref="NETEX_CALENDRIER"/> +            <netex:ValidBetween> +                <netex:FromDate>2017-03-01</netex:FromDate> +                <netex:ToDate>2017-03-31</netex:ToDate> +            </netex:ValidBetween> +            <netex:members>  +                <netex:dayTypes> +                    <netex:DayType id="CITYWAY:DayType:1:LOC"  version="any" > +                        <netex:Name>Semaine</netex:Name> +                        <netex:properties> +                            <netex:PropertyOfDay> +                                <netex:DaysOfWeek>Monday</netex:DaysOfWeek> +                            </netex:PropertyOfDay> +                            <netex:PropertyOfDay> +                                <netex:DaysOfWeek>Tuesday</netex:DaysOfWeek> +                            </netex:PropertyOfDay> +                            <netex:PropertyOfDay> +                                <netex:DaysOfWeek>Wednesday</netex:DaysOfWeek> +                            </netex:PropertyOfDay> +                            <netex:PropertyOfDay> +                                <netex:DaysOfWeek>Thursday</netex:DaysOfWeek> +                            </netex:PropertyOfDay> +                            <netex:PropertyOfDay> +                                <netex:DaysOfWeek>Friday</netex:DaysOfWeek> +                            </netex:PropertyOfDay> +                        </netex:properties> +                    </netex:DayType>                     +                    <netex:DayType id="CITYWAY:DayType:2:LOC"  version="any" > +                        <netex:Name>Fin de semaine</netex:Name> +                        <netex:properties> +                            <netex:PropertyOfDay> +                                <netex:DaysOfWeek>Saturday</netex:DaysOfWeek> +                            </netex:PropertyOfDay> +                            <netex:PropertyOfDay> +                                <netex:DaysOfWeek>Sunday</netex:DaysOfWeek> +                            </netex:PropertyOfDay> +                        </netex:properties> +                    </netex:DayType>                     +                    <netex:DayType id="CITYWAY:DayType:3:LOC"  version="any" > +                        <netex:Name>Service spécial</netex:Name> +                    </netex:DayType>                     +                    <netex:DayType id="CITYWAY:DayType:4:LOC"  version="any" > +                        <netex:Name>Restriction</netex:Name> +                    </netex:DayType>                     +                </netex:dayTypes> +                <netex:dayTypeAssignments> +                    <netex:DayTypeAssignment version="any" > +                        <netex:OperatingPeriodRef ref="CITYWAY:OperatingPeriod:1:LOC" version="any"/> +                        <netex:DayTypeRef ref="CITYWAY:DayType:1:LOC" version="any"/> +                    </netex:DayTypeAssignment> +                    <netex:DayTypeAssignment version="any" > +                        <netex:OperatingPeriodRef ref="CITYWAY:OperatingPeriod:1:LOC" version="any"/> +                        <netex:DayTypeRef ref="CITYWAY:DayType:2:LOC" version="any"/> +                    </netex:DayTypeAssignment> +                    <netex:DayTypeAssignment version="any" > +                        <netex:Date>2017-03-15</netex:Date> +                        <netex:DayTypeRef ref="CITYWAY:DayType:3:LOC" version="any"/> +                        <netex:isAvailable>true</netex:isAvailable>  +                    </netex:DayTypeAssignment> +                    <netex:DayTypeAssignment version="any" > +                        <netex:Date>2017-03-15</netex:Date> +                        <netex:DayTypeRef ref="CITYWAY:DayType:4:LOC" version="any"/> +                        <netex:isAvailable>false</netex:isAvailable>  +                    </netex:DayTypeAssignment> +                </netex:dayTypeAssignments> +                <netex:operatingPeriods> +                    <netex:OperatingPeriod id="CITYWAY:OperatingPeriod:1:LOC" version="any" > +                        <netex:FromDate>2017-01-01</netex:FromDate> +                        <netex:ToDate>2017-12-31</netex:ToDate> +                    </netex:OperatingPeriod> +                </netex:operatingPeriods> +            </netex:members> +        </netex:GeneralFrame> +    </netex:dataObjects> +</netex:PublicationDelivery> diff --git a/spec/fixtures/multiple_with_wrong_calendar/OFFRE_TRANSDEV_20170301122517/commun.xml b/spec/fixtures/multiple_with_wrong_calendar/OFFRE_TRANSDEV_20170301122517/commun.xml new file mode 100644 index 000000000..266c8a598 --- /dev/null +++ b/spec/fixtures/multiple_with_wrong_calendar/OFFRE_TRANSDEV_20170301122517/commun.xml @@ -0,0 +1,33 @@ +<?xml version="1.0" encoding="UTF-8"?> +<netex:PublicationDelivery xmlns:netex="http://www.netex.org.uk/netex" +    xmlns:siri="http://www.siri.org.uk/siri" xmlns:core="http://www.govtalk.gov.uk/core" +    xmlns:gml="http://www.opengis.net/gml/3.2" xmlns:ifopt="http://www.ifopt.org.uk/ifopt" +    xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" +    version="1.0"> +    <netex:PublicationTimestamp>2017-02-14T09:13:51.0</netex:PublicationTimestamp> +    <netex:ParticipantRef>CITYWAY</netex:ParticipantRef> +    <netex:dataObjects> +        <netex:GeneralFrame id="CITYWAY:GeneralFrame:NETEX_COMMUN-1_20170214090012:LOC" version="any"> +            <netex:TypeOfFrameRef ref="NETEX_COMMUN"/> +            <netex:members> +                <netex:notices> +                    <netex:Notice id="CITYWAY:Notice:1:LOC" version="any"> +                        <netex:Text>Notice 1</netex:Text> +                        <netex:PublicCode>1</netex:PublicCode> +                        <netex:TypeOfNoticeRef>ServiceJourneyNotice</netex:TypeOfNoticeRef> +                    </netex:Notice> +                    <netex:Notice id="CITYWAY:Notice:2:LOC" version="any"> +                        <netex:Text>Notice 2</netex:Text> +                        <netex:PublicCode>2</netex:PublicCode> +                        <netex:TypeOfNoticeRef>ServiceJourneyNotice</netex:TypeOfNoticeRef> +                    </netex:Notice> +                    <netex:Notice id="CITYWAY:Notice:3:LOC" version="any"> +                        <netex:Text>Notice 3</netex:Text> +                        <netex:PublicCode>3</netex:PublicCode> +                        <netex:TypeOfNoticeRef>ServiceJourneyNotice</netex:TypeOfNoticeRef> +                    </netex:Notice> +                </netex:notices> +            </netex:members> +        </netex:GeneralFrame> +    </netex:dataObjects> +</netex:PublicationDelivery> diff --git a/spec/fixtures/multiple_with_wrong_calendar/OFFRE_TRANSDEV_20170301122517/offre_C00108_9.xml b/spec/fixtures/multiple_with_wrong_calendar/OFFRE_TRANSDEV_20170301122517/offre_C00108_9.xml new file mode 100644 index 000000000..832793036 --- /dev/null +++ b/spec/fixtures/multiple_with_wrong_calendar/OFFRE_TRANSDEV_20170301122517/offre_C00108_9.xml @@ -0,0 +1,202 @@ +<?xml version="1.0" encoding="UTF-8"?> +<netex:PublicationDelivery xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:schemaLocation="http://www.netex.org.uk/netex ../../xsd/NeTEx_publication.xsd" + xmlns:netex="http://www.netex.org.uk/netex" xmlns:xlink="http://www.w3.org/1999/xlink" + xmlns:ifopt="http://www.ifopt.org.uk/ifopt" xmlns:gml="http://www.opengis.net/gml/3.2" + xmlns:core="http://www.govtalk.gov.uk/core" xmlns:siri="http://www.siri.org.uk/siri" version="1.0"> +    <netex:PublicationTimestamp>2017-02-14T09:13:51.0</netex:PublicationTimestamp> +    <netex:ParticipantRef>CITYWAY</netex:ParticipantRef> +    <netex:dataObjects> +        <netex:CompositeFrame id="CITYWAY:CompositeFrame:NETEX_OFFRE_LIGNE-1:LOC" version="any"> +            <netex:Name>Ligne 1</netex:Name> +            <netex:TypeOfFrameRef ref="NETEX_OFFRE_LIGNE"/> +            <netex:frames> +                <netex:GeneralFrame id="CITYWAY:GeneralFrame:NETEX_STRUCTURE-20170214090012:LOC" +                    version="any"> +                    <netex:TypeOfFrameRef ref="NETEX_STRUCTURE"/> +                    <netex:members> +                        <netex:routes> +                            <netex:Route id="CITYWAY:Route:1:LOC" version="any"> +                                <netex:Name>route 1</netex:Name> +										  <netex:LineRef ref="STIF:CODIFLIGNE:Line:C00108">version="any"</netex:LineRef> +                                <netex:DirectionType>outbound</netex:DirectionType> +                                <netex:DirectionRef ref="CITYWAY:Direction:1:LOC" version="any"/> +                                <netex:InverseRouteRef ref="CITYWAY:Route:2:LOC" version="any"/> +                            </netex:Route> +                            <netex:Route id="CITYWAY:Route:2:LOC" version="any"> +                                <netex:Name>route 2</netex:Name> +										  <netex:LineRef ref="STIF:CODIFLIGNE:Line:C00108">version="any"</netex:LineRef> +                                <netex:DirectionType>inbound</netex:DirectionType> +                                <netex:DirectionRef ref="CITYWAY:Direction:2:LOC" version="any"/> +                                <netex:InverseRouteRef ref="CITYWAY:Route:1:LOC" version="any"/> +                            </netex:Route> +                        </netex:routes> +                        <netex:directions> +                            <netex:Direction id="CITYWAY:Direction:1:LOC" version="any"> +                                <netex:Name>Par ici</netex:Name> +                            </netex:Direction> +                            <netex:Direction id="CITYWAY:Direction:2:LOC" version="any"> +                                <netex:Name>Par là</netex:Name> +                            </netex:Direction> +                        </netex:directions> +                        <netex:serviceJourneyPatterns> +                            <netex:ServiceJourneyPattern id="CITYWAY:ServiceJourneyPattern:1:LOC" +                                version="any"> +                                <netex:Name>Par ici</netex:Name> +                                <netex:RouteRef ref="CITYWAY:Route:1:LOC" version="any"/> +                                <netex:DestinationDisplayRef ref="CITYWAY:DestinationDisplay:1:LOC" +                                    version="any"/> +                                <netex:pointsInSequence> +                                    <netex:StopPointInJourneyPattern +                                        id="CITYWAY:StopPointInJourneyPattern:1-1-1:LOC" order="1" +                                        version="any"> +                                        <netex:ScheduledStopPointRef +                                            ref="CITYWAY:ScheduledStopPoint:1-1:LOC" version="any"/> +                                        <netex:ForAlighting>true</netex:ForAlighting> +                                        <netex:ForBoarding>true</netex:ForBoarding> +                                    </netex:StopPointInJourneyPattern> +                                    <netex:StopPointInJourneyPattern +                                        id="CITYWAY:StopPointInJourneyPattern:1-1-2:LOC" order="2" +                                        version="any"> +                                        <netex:ScheduledStopPointRef +                                            ref="CITYWAY:ScheduledStopPoint:1-2:LOC" version="any"/> +                                        <netex:ForAlighting>true</netex:ForAlighting> +                                        <netex:ForBoarding>true</netex:ForBoarding> +                                    </netex:StopPointInJourneyPattern> +                                </netex:pointsInSequence> +                                <netex:ServiceJourneyPatternType>passenger</netex:ServiceJourneyPatternType> +                            </netex:ServiceJourneyPattern> +                            <netex:ServiceJourneyPattern id="CITYWAY:ServiceJourneyPattern:2:LOC" +                                version="any"> +                                <netex:Name>Par là</netex:Name> +                                <netex:RouteRef ref="CITYWAY:Route:2:LOC" version="any"/> +                                <netex:DestinationDisplayRef ref="CITYWAY:DestinationDisplay:2:LOC" +                                    version="any"/> +                                <netex:pointsInSequence> +                                    <netex:StopPointInJourneyPattern +                                        id="CITYWAY:StopPointInJourneyPattern:2-2-1:LOC" order="1" +                                        version="any"> +                                        <netex:ScheduledStopPointRef +                                            ref="CITYWAY:ScheduledStopPoint:2-1:LOC" version="any"/> +                                        <netex:ForAlighting>true</netex:ForAlighting> +                                        <netex:ForBoarding>true</netex:ForBoarding> +                                    </netex:StopPointInJourneyPattern> +                                    <netex:StopPointInJourneyPattern +                                        id="CITYWAY:StopPointInJourneyPattern:2-2-2:LOC" order="2" +                                        version="any"> +                                        <netex:ScheduledStopPointRef +                                            ref="CITYWAY:ScheduledStopPoint:2-2:LOC" version="any"/> +                                        <netex:ForAlighting>true</netex:ForAlighting> +                                        <netex:ForBoarding>true</netex:ForBoarding> +                                    </netex:StopPointInJourneyPattern> +                                </netex:pointsInSequence> +                                <netex:ServiceJourneyPatternType>passenger</netex:ServiceJourneyPatternType> +                            </netex:ServiceJourneyPattern> +                        </netex:serviceJourneyPatterns> +                        <netex:destinationDisplays> +                            <netex:DestinationDisplay id="CITYWAY:DestinationDisplay:1:LOC" +                                version="any"> +                                <netex:FrontText>Mission 1</netex:FrontText> +                                <netex:PublicCode>1234</netex:PublicCode> +                            </netex:DestinationDisplay> +                            <netex:DestinationDisplay id="CITYWAY:DestinationDisplay:2:LOC" +                                version="any"> +                                <netex:FrontText>Mission 2</netex:FrontText> +                                <netex:PublicCode>2345</netex:PublicCode> +                            </netex:DestinationDisplay> +                        </netex:destinationDisplays> +                        <netex:scheduledStopPoints> +                            <netex:ScheduledStopPoint id="CITYWAY:ScheduledStopPoint:1-1:LOC" +                                version="any"/> +                            <netex:ScheduledStopPoint id="CITYWAY:ScheduledStopPoint:1-2:LOC" +                                version="any"/> +                            <netex:ScheduledStopPoint id="CITYWAY:ScheduledStopPoint:2-1:LOC" +                                version="any"/> +                            <netex:ScheduledStopPoint id="CITYWAY:ScheduledStopPoint:2-2:LOC" +                                version="any"/> +                        </netex:scheduledStopPoints> +                        <netex:passengerStopAssignments> +                            <netex:PassengerStopAssignment +                                id="CITYWAY:PassengerStopAssignment:1-1:LOC" version="any"> +                                <netex:ScheduledStopPointRef +                                    ref="CITYWAY:ScheduledStopPoint:1-1:LOC" version="any"/> +										  <netex:QuayRef ref="FR:78217:ZDE:50094817:STIF">version="any"</netex:QuayRef> +                            </netex:PassengerStopAssignment> +                            <netex:PassengerStopAssignment +                                id="CITYWAY:PassengerStopAssignment:2-1:LOC" version="any"> +                                <netex:ScheduledStopPointRef +                                    ref="CITYWAY:ScheduledStopPoint:2-1:LOC" version="any"/> +										  <netex:QuayRef ref="FR:78217:ZDE:50009052:STIF">version="any"</netex:QuayRef> +                            </netex:PassengerStopAssignment> +                            <netex:PassengerStopAssignment +                                id="CITYWAY:PassengerStopAssignment:1-2:LOC" version="any"> +                                <netex:ScheduledStopPointRef +                                    ref="CITYWAY:ScheduledStopPoint:1-2:LOC" version="any"/> +										  <netex:QuayRef ref="FR:78217:ZDE:50009053:STIF">version="any"</netex:QuayRef> +                            </netex:PassengerStopAssignment> +                            <netex:PassengerStopAssignment +                                id="CITYWAY:PassengerStopAssignment:2-2:LOC" version="any"> +                                <netex:ScheduledStopPointRef +                                    ref="CITYWAY:ScheduledStopPoint:2-2:LOC" version="any"/> +										  <netex:QuayRef ref="FR:78217:ZDE:50094816:STIF">version="any"</netex:QuayRef> +                            </netex:PassengerStopAssignment> +                        </netex:passengerStopAssignments> +                        <netex:routingConstraintZones> +                            <netex:RoutingConstraintZone id="CITYWAY:RoutingConstraintZone:1:LOC" +                                version="any"> +                                <netex:Name>ITL 1</netex:Name> +                                <netex:members> +                                    <netex:ScheduledStopPointRef +                                        ref="CITYWAY:ScheduledStopPoint:1-1:LOC" version="any"/> +                                    <netex:ScheduledStopPointRef +                                        ref="CITYWAY:ScheduledStopPoint:2-1:LOC" version="any"/> +                                </netex:members> +                                <netex:ZoneUse>cannotBoardAndAlightInSameZone</netex:ZoneUse> +                            </netex:RoutingConstraintZone> +                        </netex:routingConstraintZones> +                    </netex:members> +                </netex:GeneralFrame> +                <netex:GeneralFrame id="CITYWAY:GeneralFrame:NETEX_HORAIRE-20170214090012:LOC" +                    version="any"> +                    <netex:TypeOfFrameRef ref="NETEX_HORAIRE"/> +                    <netex:members> +                        <netex:serviceJourneys> +                            <netex:ServiceJourney id="CITYWAY:ServiceJourney:1-1:LOC" version="any"> +                                <netex:Name>Course 1 par ici</netex:Name> +                                <netex:noticeAssignments> +                                    <netex:NoticeAssignment> +												  <netex:NoticeRef ref="CITYWAY:Notice:1:LOC"> +                                            version="any"</netex:NoticeRef> +                                    </netex:NoticeAssignment> +                                </netex:noticeAssignments> +										  <netex:DayTypeRef ref="CITYWAY:DayType:1:LOC"> +                                    version="any"</netex:DayTypeRef> +                                <netex:JourneyPatternRef ref="CITYWAY:ServiceJourneyPattern:1:LOC" +                                    version="any"/> +										  <netex:OperatorRef ref="STIF:CODIFLIGNE:Operator:011"> +                                    version="any"</netex:OperatorRef> +                                <netex:trainNumbers> +											 <netex:TrainNumberRef ref="CITYWAY:TrainNumber:1234:LOC">version="any"</netex:TrainNumberRef> +                                </netex:trainNumbers> +                                <netex:passingTimes> +                                    <netex:TimetabledPassingTime version="any"> +                                        <netex:ArrivalTime>01:01:00.000</netex:ArrivalTime> +                                        <netex:ArrivalDayOffset>0</netex:ArrivalDayOffset> +                                        <netex:DepartureTime>01:01:00.000</netex:DepartureTime> +                                        <netex:DepartureDayOffset>0</netex:DepartureDayOffset> +                                    </netex:TimetabledPassingTime> +                                    <netex:TimetabledPassingTime version="any"> +                                        <netex:ArrivalTime>01:05:00.000</netex:ArrivalTime> +                                        <netex:ArrivalDayOffset>0</netex:ArrivalDayOffset> +                                        <netex:DepartureTime>01:05:00.000</netex:DepartureTime> +                                        <netex:DepartureDayOffset>0</netex:DepartureDayOffset> +                                    </netex:TimetabledPassingTime> +                                </netex:passingTimes> +                            </netex:ServiceJourney> +                        </netex:serviceJourneys> +                    </netex:members> +                </netex:GeneralFrame> +            </netex:frames> +        </netex:CompositeFrame> +    </netex:dataObjects> +</netex:PublicationDelivery> diff --git a/spec/fixtures/multiple_with_wrong_calendar/OFFRE_TRANSDEV_20170301122517/offre_C00109_10.xml b/spec/fixtures/multiple_with_wrong_calendar/OFFRE_TRANSDEV_20170301122517/offre_C00109_10.xml new file mode 100644 index 000000000..9dff0d850 --- /dev/null +++ b/spec/fixtures/multiple_with_wrong_calendar/OFFRE_TRANSDEV_20170301122517/offre_C00109_10.xml @@ -0,0 +1,204 @@ +<?xml version="1.0" encoding="UTF-8"?> +<netex:PublicationDelivery xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:schemaLocation="http://www.netex.org.uk/netex ../../xsd/NeTEx_publication.xsd" + xmlns:netex="http://www.netex.org.uk/netex" xmlns:xlink="http://www.w3.org/1999/xlink" + xmlns:ifopt="http://www.ifopt.org.uk/ifopt" xmlns:gml="http://www.opengis.net/gml/3.2" + xmlns:core="http://www.govtalk.gov.uk/core" xmlns:siri="http://www.siri.org.uk/siri" version="1.0"> +    <netex:PublicationTimestamp>2017-02-14T09:13:51.0</netex:PublicationTimestamp> +    <netex:ParticipantRef>CITYWAY</netex:ParticipantRef> +    <netex:dataObjects> +        <netex:CompositeFrame id="CITYWAY:CompositeFrame:NETEX_OFFRE_LIGNE-1:LOC" version="any"> +            <netex:Name>Ligne 1</netex:Name> +            <netex:TypeOfFrameRef ref="NETEX_OFFRE_LIGNE"/> +            <netex:frames> +                <netex:GeneralFrame id="CITYWAY:GeneralFrame:NETEX_STRUCTURE-20170214090012:LOC" +                    version="any"> +                    <netex:TypeOfFrameRef ref="NETEX_STRUCTURE"/> +                    <netex:members> +                        <netex:routes> +                            <netex:Route id="CITYWAY:Route:1:LOC" version="any"> +                                <netex:Name>route 1</netex:Name> +										  <netex:LineRef ref="STIF:CODIFLIGNE:Line:C00109">version="any"</netex:LineRef> +                                <netex:DirectionType>outbound</netex:DirectionType> +                                <netex:DirectionRef ref="CITYWAY:Direction:1:LOC" version="any"/> +                                <netex:InverseRouteRef ref="CITYWAY:Route:2:LOC" version="any"/> +                            </netex:Route> +                            <netex:Route id="CITYWAY:Route:2:LOC" version="any"> +                                <netex:Name>route 2</netex:Name> +										  <netex:LineRef ref="STIF:CODIFLIGNE:Line:C00109">version="any"</netex:LineRef> +                                <netex:DirectionType>inbound</netex:DirectionType> +                                <netex:DirectionRef ref="CITYWAY:Direction:2:LOC" version="any"/> +                                <netex:InverseRouteRef ref="CITYWAY:Route:1:LOC" version="any"/> +                            </netex:Route> +                        </netex:routes> +                        <netex:directions> +                            <netex:Direction id="CITYWAY:Direction:1:LOC" version="any"> +                                <netex:Name>Par ici aussi</netex:Name> +                            </netex:Direction> +                            <netex:Direction id="CITYWAY:Direction:2:LOC" version="any"> +                                <netex:Name>Par là aussi</netex:Name> +                            </netex:Direction> +                        </netex:directions> +                        <netex:serviceJourneyPatterns> +                            <netex:ServiceJourneyPattern id="CITYWAY:ServiceJourneyPattern:1:LOC" +                                version="any"> +                                <netex:Name>Par ici itou</netex:Name> +                                <netex:RouteRef ref="CITYWAY:Route:1:LOC" version="any"/> +                                <netex:DestinationDisplayRef ref="CITYWAY:DestinationDisplay:1:LOC" +                                    version="any"/> +                                <netex:pointsInSequence> +                                    <netex:StopPointInJourneyPattern +                                        id="CITYWAY:StopPointInJourneyPattern:1-1-1:LOC" order="1" +                                        version="any"> +                                        <netex:ScheduledStopPointRef +                                            ref="CITYWAY:ScheduledStopPoint:1-1:LOC" version="any"/> +                                        <netex:ForAlighting>true</netex:ForAlighting> +                                        <netex:ForBoarding>true</netex:ForBoarding> +                                    </netex:StopPointInJourneyPattern> +                                    <netex:StopPointInJourneyPattern +                                        id="CITYWAY:StopPointInJourneyPattern:1-1-2:LOC" order="2" +                                        version="any"> +                                        <netex:ScheduledStopPointRef +                                            ref="CITYWAY:ScheduledStopPoint:1-2:LOC" version="any"/> +                                        <netex:ForAlighting>true</netex:ForAlighting> +                                        <netex:ForBoarding>true</netex:ForBoarding> +                                    </netex:StopPointInJourneyPattern> +                                </netex:pointsInSequence> +                                <netex:ServiceJourneyPatternType>passenger</netex:ServiceJourneyPatternType> +                            </netex:ServiceJourneyPattern> +                            <netex:ServiceJourneyPattern id="CITYWAY:ServiceJourneyPattern:2:LOC" +                                version="any"> +                                <netex:Name>Par là itou</netex:Name> +                                <netex:RouteRef ref="CITYWAY:Route:2:LOC" version="any"/> +                                <netex:DestinationDisplayRef ref="CITYWAY:DestinationDisplay:2:LOC" +                                    version="any"/> +                                <netex:pointsInSequence> +                                    <netex:StopPointInJourneyPattern +                                        id="CITYWAY:StopPointInJourneyPattern:2-2-1:LOC" order="1" +                                        version="any"> +                                        <netex:ScheduledStopPointRef +                                            ref="CITYWAY:ScheduledStopPoint:2-1:LOC" version="any"/> +                                        <netex:ForAlighting>true</netex:ForAlighting> +                                        <netex:ForBoarding>true</netex:ForBoarding> +                                    </netex:StopPointInJourneyPattern> +                                    <netex:StopPointInJourneyPattern +                                        id="CITYWAY:StopPointInJourneyPattern:2-2-2:LOC" order="2" +                                        version="any"> +                                        <netex:ScheduledStopPointRef +                                            ref="CITYWAY:ScheduledStopPoint:2-2:LOC" version="any"/> +                                        <netex:ForAlighting>true</netex:ForAlighting> +                                        <netex:ForBoarding>true</netex:ForBoarding> +                                    </netex:StopPointInJourneyPattern> +                                </netex:pointsInSequence> +                                <netex:ServiceJourneyPatternType>passenger</netex:ServiceJourneyPatternType> +                            </netex:ServiceJourneyPattern> +                        </netex:serviceJourneyPatterns> +                        <netex:destinationDisplays> +                            <netex:DestinationDisplay id="CITYWAY:DestinationDisplay:1:LOC" +                                version="any"> +                                <netex:FrontText>Mission 1 bis</netex:FrontText> +                                <netex:PublicCode>1234</netex:PublicCode> +                            </netex:DestinationDisplay> +                            <netex:DestinationDisplay id="CITYWAY:DestinationDisplay:2:LOC" +                                version="any"> +                                <netex:FrontText>Mission 2 bis</netex:FrontText> +                                <netex:PublicCode>2345</netex:PublicCode> +                            </netex:DestinationDisplay> +                        </netex:destinationDisplays> +                        <netex:scheduledStopPoints> +                            <netex:ScheduledStopPoint id="CITYWAY:ScheduledStopPoint:1-1:LOC" +                                version="any"/> +                            <netex:ScheduledStopPoint id="CITYWAY:ScheduledStopPoint:1-2:LOC" +                                version="any"/> +                            <netex:ScheduledStopPoint id="CITYWAY:ScheduledStopPoint:2-1:LOC" +                                version="any"/> +                            <netex:ScheduledStopPoint id="CITYWAY:ScheduledStopPoint:2-2:LOC" +                                version="any"/> +                        </netex:scheduledStopPoints> +                        <netex:passengerStopAssignments> +                            <netex:PassengerStopAssignment +                                id="CITYWAY:PassengerStopAssignment:1-1:LOC" version="any"> +                                <netex:ScheduledStopPointRef +                                    ref="CITYWAY:ScheduledStopPoint:1-1:LOC" version="any"/> +										  <netex:QuayRef ref="FR:78217:ZDE:50094817:STIF">version="any"</netex:QuayRef> +                            </netex:PassengerStopAssignment> +                            <netex:PassengerStopAssignment +                                id="CITYWAY:PassengerStopAssignment:2-1:LOC" version="any"> +                                <netex:ScheduledStopPointRef +                                    ref="CITYWAY:ScheduledStopPoint:2-1:LOC" version="any"/> +										  <netex:QuayRef ref="FR:78402:ZDE:50000918:STIF">version="any"</netex:QuayRef> +                            </netex:PassengerStopAssignment> +                            <netex:PassengerStopAssignment +                                id="CITYWAY:PassengerStopAssignment:1-2:LOC" version="any"> +                                <netex:ScheduledStopPointRef +                                    ref="CITYWAY:ScheduledStopPoint:1-2:LOC" version="any"/> +										  <netex:QuayRef ref="FR:78402:ZDE:50000917:STIF">version="any"</netex:QuayRef> +                            </netex:PassengerStopAssignment> +                            <netex:PassengerStopAssignment +                                id="CITYWAY:PassengerStopAssignment:2-2:LOC" version="any"> +                                <netex:ScheduledStopPointRef +                                    ref="CITYWAY:ScheduledStopPoint:2-2:LOC" version="any"/> +										  <netex:QuayRef ref="FR:78217:ZDE:50094816:STIF">version="any"</netex:QuayRef> +                            </netex:PassengerStopAssignment> +                        </netex:passengerStopAssignments> +                        <netex:routingConstraintZones> +                            <netex:RoutingConstraintZone id="CITYWAY:RoutingConstraintZone:1:LOC" +                                version="any"> +                                <netex:Name>ITL 1</netex:Name> +                                <netex:members> +                                    <netex:ScheduledStopPointRef +                                        ref="CITYWAY:ScheduledStopPoint:1-1:LOC" version="any"/> +                                    <netex:ScheduledStopPointRef +                                        ref="CITYWAY:ScheduledStopPoint:2-1:LOC" version="any"/> +                                </netex:members> +                                <netex:ZoneUse>cannotBoardAndAlightInSameZone</netex:ZoneUse> +                            </netex:RoutingConstraintZone> +                        </netex:routingConstraintZones> +                    </netex:members> +                </netex:GeneralFrame> +                <netex:GeneralFrame id="CITYWAY:GeneralFrame:NETEX_HORAIRE-20170214090012:LOC" +                    version="any"> +                    <netex:TypeOfFrameRef ref="NETEX_HORAIRE"/> +                    <netex:members> +                        <netex:serviceJourneys> +                            <netex:ServiceJourney id="CITYWAY:ServiceJourney:1-1:LOC" version="any"> +                                <netex:Name>Course 1 par ici aussi</netex:Name> +                                <netex:noticeAssignments> +                                    <netex:NoticeAssignment> +												  <netex:NoticeRef ref="CITYWAY:Notice:2:LOC"> +                                            version="any"</netex:NoticeRef> +                                    </netex:NoticeAssignment> +                                </netex:noticeAssignments> +										  <netex:DayTypeRef ref="CITYWAY:DayType:1:LOC"> +                                    version="any"</netex:DayTypeRef> +										  <netex:DayTypeRef ref="CITYWAY:DayType:4:LOC"> +                                    version="any"</netex:DayTypeRef> +                                <netex:JourneyPatternRef ref="CITYWAY:ServiceJourneyPattern:1:LOC" +                                    version="any"/> +										  <netex:OperatorRef ref="STIF:CODIFLIGNE:Operator:212"> +                                    version="any"</netex:OperatorRef> +                                <netex:trainNumbers> +											 <netex:TrainNumberRef ref="CITYWAY:TrainNumber:1234:LOC">version="any"</netex:TrainNumberRef> +                                </netex:trainNumbers> +                                <netex:passingTimes> +                                    <netex:TimetabledPassingTime version="any"> +                                        <netex:ArrivalTime>23:58:00.000</netex:ArrivalTime> +                                        <netex:ArrivalDayOffset>0</netex:ArrivalDayOffset> +                                        <netex:DepartureTime>23:59:00.000</netex:DepartureTime> +                                        <netex:DepartureDayOffset>0</netex:DepartureDayOffset> +                                    </netex:TimetabledPassingTime> +                                    <netex:TimetabledPassingTime version="any"> +                                        <netex:ArrivalTime>00:03:00.000</netex:ArrivalTime> +                                        <netex:ArrivalDayOffset>1</netex:ArrivalDayOffset> +                                        <netex:DepartureTime>00:04:00.000</netex:DepartureTime> +                                        <netex:DepartureDayOffset>1</netex:DepartureDayOffset> +                                    </netex:TimetabledPassingTime> +                                </netex:passingTimes> +                            </netex:ServiceJourney> +                        </netex:serviceJourneys> +                    </netex:members> +                </netex:GeneralFrame> +            </netex:frames> +        </netex:CompositeFrame> +    </netex:dataObjects> +</netex:PublicationDelivery> diff --git a/spec/fixtures/multiple_with_wrong_calendar/OFFRE_TRANSDEV_20170301122519/calendriers.xml b/spec/fixtures/multiple_with_wrong_calendar/OFFRE_TRANSDEV_20170301122519/calendriers.xml new file mode 100644 index 000000000..712ea8be1 --- /dev/null +++ b/spec/fixtures/multiple_with_wrong_calendar/OFFRE_TRANSDEV_20170301122519/calendriers.xml @@ -0,0 +1,80 @@ +<?xml version="1.0" encoding="UTF-8"?> +<netex:PublicationDelivery xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" +    xsi:schemaLocation="http://www.netex.org.uk/netex ../../xsd/NeTEx_publication.xsd" xmlns:netex="http://www.netex.org.uk/netex" +    xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:ifopt="http://www.ifopt.org.uk/ifopt" +    xmlns:gml="http://www.opengis.net/gml/3.2" xmlns:core="http://www.govtalk.gov.uk/core" +    xmlns:siri="http://www.siri.org.uk/siri" version="1.0"> +    <netex:PublicationTimestamp>2017-02-14T09:13:51.0</netex:PublicationTimestamp> +    <netex:ParticipantRef>CITYWAY</netex:ParticipantRef> +    <netex:dataObjects> +        <netex:GeneralFrame id="CITYWAY:GeneralFrame:NETEX_CALENDRIER-1_20170214090012:LOC" version="any"> +            <netex:ValidBetween> +                <netex:FromDate>2017-04-01T00:00:00</netex:FromDate> +                <netex:ToDate>2016-12-31T00:00:00</netex:ToDate> +            </netex:ValidBetween> +            <netex:TypeOfFrameRef ref="NETEX_CALENDRIER"/> +            <netex:members> +                <netex:DayType id="CITYWAY:DayType:1:LOC" version="any"> +                    <netex:Name>Semaine</netex:Name> +                    <netex:properties> +                        <netex:PropertyOfDay> +                            <netex:DaysOfWeek>Monday</netex:DaysOfWeek> +                        </netex:PropertyOfDay> +                        <netex:PropertyOfDay> +                            <netex:DaysOfWeek>Tuesday</netex:DaysOfWeek> +                        </netex:PropertyOfDay> +                        <netex:PropertyOfDay> +                            <netex:DaysOfWeek>Wednesday</netex:DaysOfWeek> +                        </netex:PropertyOfDay> +                        <netex:PropertyOfDay> +                            <netex:DaysOfWeek>Thursday</netex:DaysOfWeek> +                        </netex:PropertyOfDay> +                        <netex:PropertyOfDay> +                            <netex:DaysOfWeek>Friday</netex:DaysOfWeek> +                        </netex:PropertyOfDay> +                    </netex:properties> +                </netex:DayType> +                <netex:DayType id="CITYWAY:DayType:2:LOC" version="any"> +                    <netex:Name>Fin de semaine</netex:Name> +                    <netex:properties> +                        <netex:PropertyOfDay> +                            <netex:DaysOfWeek>Saturday</netex:DaysOfWeek> +                        </netex:PropertyOfDay> +                        <netex:PropertyOfDay> +                            <netex:DaysOfWeek>Sunday</netex:DaysOfWeek> +                        </netex:PropertyOfDay> +                    </netex:properties> +                </netex:DayType> +                <netex:DayType id="CITYWAY:DayType:3:LOC" version="any"> +                    <netex:Name>Service spécial</netex:Name> +                </netex:DayType> +                <netex:DayType id="CITYWAY:DayType:4:LOC" version="any"> +                    <netex:Name>Restriction</netex:Name> +                </netex:DayType> +                <netex:DayTypeAssignment id="dta1" version="any" order="0"> +                    <netex:OperatingPeriodRef ref="CITYWAY:OperatingPeriod:1:LOC" version="any"/> +                    <netex:DayTypeRef ref="CITYWAY:DayType:1:LOC" version="any"/> +                </netex:DayTypeAssignment> +                <netex:DayTypeAssignment id="dta2" version="any" order="0"> +                    <netex:OperatingPeriodRef ref="CITYWAY:OperatingPeriod:1:LOC" version="any"/> +                    <netex:DayTypeRef ref="CITYWAY:DayType:2:LOC" version="any"/> +                </netex:DayTypeAssignment> +                <netex:DayTypeAssignment id="dta3" version="any" order="0"> +                    <netex:Date>2017-03-15</netex:Date> +                    <netex:DayTypeRef ref="CITYWAY:DayType:3:LOC" version="any"/> +                    <netex:isAvailable>true</netex:isAvailable> +                </netex:DayTypeAssignment> +                <netex:DayTypeAssignment id="dta4" version="any" order="0"> +                    <netex:Date>2017-03-15</netex:Date> +                    <netex:DayTypeRef ref="CITYWAY:DayType:4:LOC" version="any"/> +                    <netex:isAvailable>false</netex:isAvailable> +                </netex:DayTypeAssignment> +                <netex:OperatingPeriod id="CITYWAY:OperatingPeriod:1:LOC" version="any"> +                    <netex:FromDate>2017-01-01T00:00:00</netex:FromDate> +                    <netex:ToDate>2017-12-31T00:00:00</netex:ToDate> +                </netex:OperatingPeriod> + +            </netex:members> +        </netex:GeneralFrame> +    </netex:dataObjects> +</netex:PublicationDelivery> diff --git a/spec/fixtures/multiple_with_wrong_calendar/OFFRE_TRANSDEV_20170301122519/commun.xml b/spec/fixtures/multiple_with_wrong_calendar/OFFRE_TRANSDEV_20170301122519/commun.xml new file mode 100644 index 000000000..f59f8ac2d --- /dev/null +++ b/spec/fixtures/multiple_with_wrong_calendar/OFFRE_TRANSDEV_20170301122519/commun.xml @@ -0,0 +1,32 @@ +<?xml version="1.0" encoding="UTF-8"?> +<netex:PublicationDelivery xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:schemaLocation="http://www.netex.org.uk/netex ../../xsd/NeTEx_publication.xsd" xmlns:netex="http://www.netex.org.uk/netex" + xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:ifopt="http://www.ifopt.org.uk/ifopt" xmlns:gml="http://www.opengis.net/gml/3.2" + xmlns:core="http://www.govtalk.gov.uk/core" xmlns:siri="http://www.siri.org.uk/siri" version="1.0"> +    <netex:PublicationTimestamp>2017-02-14T09:13:51.0</netex:PublicationTimestamp> +    <netex:ParticipantRef>CITYWAY</netex:ParticipantRef> +    <netex:dataObjects> +        <netex:GeneralFrame id="CITYWAY:GeneralFrame:NETEX_COMMUN-1_20170214090012:LOC" version="any"> +            <netex:TypeOfFrameRef ref="NETEX_COMMUN"/> +            <netex:members> +                +                    <netex:Notice id="CITYWAY:Notice:1:LOC" version="any"> +                        <netex:Text>Notice 1</netex:Text> +                        <netex:PublicCode>1</netex:PublicCode> +                        <netex:TypeOfNoticeRef ref="ServiceJourneyNotice"/> +                    </netex:Notice> +                    <netex:Notice id="CITYWAY:Notice:2:LOC" version="any"> +                        <netex:Text>Notice 2</netex:Text> +                        <netex:PublicCode>2</netex:PublicCode> +                        <netex:TypeOfNoticeRef ref="ServiceJourneyNotice"/> +                    </netex:Notice> +                    <netex:Notice id="CITYWAY:Notice:3:LOC" version="any"> +                        <netex:Text>Notice 3</netex:Text> +                        <netex:PublicCode>3</netex:PublicCode> +                        <netex:TypeOfNoticeRef ref="ServiceJourneyNotice"/> +                    </netex:Notice> +                 +            </netex:members> +        </netex:GeneralFrame> +    </netex:dataObjects> +</netex:PublicationDelivery> diff --git a/spec/fixtures/multiple_with_wrong_calendar/OFFRE_TRANSDEV_20170301122519/offre_C00108_9.xml b/spec/fixtures/multiple_with_wrong_calendar/OFFRE_TRANSDEV_20170301122519/offre_C00108_9.xml new file mode 100644 index 000000000..9eefeeb43 --- /dev/null +++ b/spec/fixtures/multiple_with_wrong_calendar/OFFRE_TRANSDEV_20170301122519/offre_C00108_9.xml @@ -0,0 +1,172 @@ +<?xml version="1.0" encoding="UTF-8"?> +<netex:PublicationDelivery xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" +    xsi:schemaLocation="http://www.netex.org.uk/netex ../../xsd/NeTEx_publication.xsd" xmlns:netex="http://www.netex.org.uk/netex" +    xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:ifopt="http://www.ifopt.org.uk/ifopt" +    xmlns:gml="http://www.opengis.net/gml/3.2" xmlns:core="http://www.govtalk.gov.uk/core" +    xmlns:siri="http://www.siri.org.uk/siri" version="1.0"> +    <netex:PublicationTimestamp>2017-02-14T09:13:51.0</netex:PublicationTimestamp> +    <netex:ParticipantRef>CITYWAY</netex:ParticipantRef> +    <netex:dataObjects> +        <netex:CompositeFrame id="CITYWAY:CompositeFrame:NETEX_OFFRE_LIGNE-1:LOC" version="any"> +            <netex:Name>Ligne 1</netex:Name> +            <netex:TypeOfFrameRef ref="NETEX_OFFRE_LIGNE"/> +            <netex:frames> +                <netex:GeneralFrame id="CITYWAY:GeneralFrame:NETEX_STRUCTURE-20170214090012:LOC" version="any"> +                    <netex:TypeOfFrameRef ref="NETEX_STRUCTURE"/> +                    <netex:members> + +                        <netex:Route id="CITYWAY:Route:1:LOC" version="any"> +                            <netex:Name>route 1</netex:Name> +                            <netex:LineRef ref="STIF:CODIFLIGNE:Line:C00108">version="any"</netex:LineRef> +                            <netex:DirectionType>outbound</netex:DirectionType> +                            <netex:DirectionRef ref="CITYWAY:Direction:1:LOC" version="any"/> +                            <netex:InverseRouteRef ref="CITYWAY:Route:2:LOC" version="any"/> +                        </netex:Route> +                        <netex:Route id="CITYWAY:Route:2:LOC" version="any"> +                            <netex:Name>route 2</netex:Name> +                            <netex:LineRef ref="STIF:CODIFLIGNE:Line:C00108">version="any"</netex:LineRef> +                            <netex:DirectionType>inbound</netex:DirectionType> +                            <netex:DirectionRef ref="CITYWAY:Direction:2:LOC" version="any"/> +                            <netex:InverseRouteRef ref="CITYWAY:Route:1:LOC" version="any"/> +                        </netex:Route> + + +                        <netex:Direction id="CITYWAY:Direction:1:LOC" version="any"> +                            <netex:Name>Par ici</netex:Name> +                        </netex:Direction> +                        <netex:Direction id="CITYWAY:Direction:2:LOC" version="any"> +                            <netex:Name>Par là</netex:Name> +                        </netex:Direction> + + +                        <netex:ServiceJourneyPattern id="CITYWAY:ServiceJourneyPattern:1:LOC" version="any"> +                            <netex:Name>Par ici</netex:Name> +                            <netex:RouteRef ref="CITYWAY:Route:1:LOC" version="any"/> +                            <netex:DestinationDisplayRef ref="CITYWAY:DestinationDisplay:1:LOC" version="any"/> +                            <netex:pointsInSequence> +                                <netex:StopPointInJourneyPattern id="CITYWAY:StopPointInJourneyPattern:1-1-1:LOC" order="1" +                                    version="any"> +                                    <netex:ScheduledStopPointRef ref="CITYWAY:ScheduledStopPoint:1-1:LOC" version="any"/> +                                    <netex:ForAlighting>true</netex:ForAlighting> +                                    <netex:ForBoarding>true</netex:ForBoarding> +                                </netex:StopPointInJourneyPattern> +                                <netex:StopPointInJourneyPattern id="CITYWAY:StopPointInJourneyPattern:1-1-2:LOC" order="2" +                                    version="any"> +                                    <netex:ScheduledStopPointRef ref="CITYWAY:ScheduledStopPoint:1-2:LOC" version="any"/> +                                    <netex:ForAlighting>true</netex:ForAlighting> +                                    <netex:ForBoarding>true</netex:ForBoarding> +                                </netex:StopPointInJourneyPattern> +                            </netex:pointsInSequence> +                            <netex:ServiceJourneyPatternType>passenger</netex:ServiceJourneyPatternType> +                        </netex:ServiceJourneyPattern> +                        <netex:ServiceJourneyPattern id="CITYWAY:ServiceJourneyPattern:2:LOC" version="any"> +                            <netex:Name>Par là</netex:Name> +                            <netex:RouteRef ref="CITYWAY:Route:2:LOC" version="any"/> +                            <netex:DestinationDisplayRef ref="CITYWAY:DestinationDisplay:2:LOC" version="any"/> +                            <netex:pointsInSequence> +                                <netex:StopPointInJourneyPattern id="CITYWAY:StopPointInJourneyPattern:2-2-1:LOC" order="1" +                                    version="any"> +                                    <netex:ScheduledStopPointRef ref="CITYWAY:ScheduledStopPoint:2-1:LOC" version="any"/> +                                    <netex:ForAlighting>true</netex:ForAlighting> +                                    <netex:ForBoarding>true</netex:ForBoarding> +                                </netex:StopPointInJourneyPattern> +                                <netex:StopPointInJourneyPattern id="CITYWAY:StopPointInJourneyPattern:2-2-2:LOC" order="2" +                                    version="any"> +                                    <netex:ScheduledStopPointRef ref="CITYWAY:ScheduledStopPoint:2-2:LOC" version="any"/> +                                    <netex:ForAlighting>true</netex:ForAlighting> +                                    <netex:ForBoarding>true</netex:ForBoarding> +                                </netex:StopPointInJourneyPattern> +                            </netex:pointsInSequence> +                            <netex:ServiceJourneyPatternType>passenger</netex:ServiceJourneyPatternType> +                        </netex:ServiceJourneyPattern> + + +                        <netex:DestinationDisplay id="CITYWAY:DestinationDisplay:1:LOC" version="any"> +                            <netex:FrontText>Mission 1</netex:FrontText> +                            <netex:PublicCode>1234</netex:PublicCode> +                        </netex:DestinationDisplay> +                        <netex:DestinationDisplay id="CITYWAY:DestinationDisplay:2:LOC" version="any"> +                            <netex:FrontText>Mission 2</netex:FrontText> +                            <netex:PublicCode>2345</netex:PublicCode> +                        </netex:DestinationDisplay> + + +                        <netex:ScheduledStopPoint id="CITYWAY:ScheduledStopPoint:1-1:LOC" version="any"/> +                        <netex:ScheduledStopPoint id="CITYWAY:ScheduledStopPoint:1-2:LOC" version="any"/> +                        <netex:ScheduledStopPoint id="CITYWAY:ScheduledStopPoint:2-1:LOC" version="any"/> +                        <netex:ScheduledStopPoint id="CITYWAY:ScheduledStopPoint:2-2:LOC" version="any"/> + + +                        <netex:PassengerStopAssignment id="CITYWAY:PassengerStopAssignment:1-1:LOC" version="any" order="0"> +                            <netex:ScheduledStopPointRef ref="CITYWAY:ScheduledStopPoint:1-1:LOC" version="any"/> +                            <netex:QuayRef ref="FR:78217:ZDE:50094817:STIF">version="any"</netex:QuayRef> +                        </netex:PassengerStopAssignment> +                        <netex:PassengerStopAssignment id="CITYWAY:PassengerStopAssignment:2-1:LOC" version="any" order="0"> +                            <netex:ScheduledStopPointRef ref="CITYWAY:ScheduledStopPoint:2-1:LOC" version="any"/> +                            <netex:QuayRef ref="FR:78217:ZDE:50009052:STIF">version="any"</netex:QuayRef> +                        </netex:PassengerStopAssignment> +                        <netex:PassengerStopAssignment id="CITYWAY:PassengerStopAssignment:1-2:LOC" version="any" order="0"> +                            <netex:ScheduledStopPointRef ref="CITYWAY:ScheduledStopPoint:1-2:LOC" version="any"/> +                            <netex:QuayRef ref="FR:78217:ZDE:50009053:STIF">version="any"</netex:QuayRef> +                        </netex:PassengerStopAssignment> +                        <netex:PassengerStopAssignment id="CITYWAY:PassengerStopAssignment:2-2:LOC" version="any" order="0"> +                            <netex:ScheduledStopPointRef ref="CITYWAY:ScheduledStopPoint:2-2:LOC" version="any"/> +                            <netex:QuayRef ref="FR:78217:ZDE:50094816:STIF">version="any"</netex:QuayRef> +                        </netex:PassengerStopAssignment> + + +                        <netex:RoutingConstraintZone id="CITYWAY:RoutingConstraintZone:1:LOC" version="any"> +                            <netex:Name>ITL 1</netex:Name> +                            <netex:members> +                                <netex:ScheduledStopPointRef ref="CITYWAY:ScheduledStopPoint:1-1:LOC" version="any"/> +                                <netex:ScheduledStopPointRef ref="CITYWAY:ScheduledStopPoint:1-2:LOC" version="any"/> +                            </netex:members> +                            <netex:ZoneUse>cannotBoardAndAlightInSameZone</netex:ZoneUse> +                        </netex:RoutingConstraintZone> + +                    </netex:members> +                </netex:GeneralFrame> +                <netex:GeneralFrame id="CITYWAY:GeneralFrame:NETEX_HORAIRE-20170214090012:LOC" version="any"> +                    <netex:TypeOfFrameRef ref="NETEX_HORAIRE"/> +                    <netex:members> + +                        <netex:ServiceJourney id="CITYWAY:ServiceJourney:1-1:LOC" version="any"> +                            <netex:Name>Course 1 par ici</netex:Name> +                            <netex:noticeAssignments> +                                <netex:NoticeAssignment id="ns1" version="any" order="0"> +                                    <netex:NoticeRef ref="CITYWAY:Notice:1:LOC"> +                                            version="any"</netex:NoticeRef> +                                </netex:NoticeAssignment> +                            </netex:noticeAssignments> +                            <netex:dayTypes> +                                <netex:DayTypeRef ref="CITYWAY:DayType:1:LOC"> version="any"</netex:DayTypeRef> +                            </netex:dayTypes> + +                            <netex:JourneyPatternRef ref="CITYWAY:ServiceJourneyPattern:1:LOC" version="any"/> +                            <netex:OperatorRef ref="STIF:CODIFLIGNE:Operator:011"> +                                    version="any"</netex:OperatorRef> +                            <netex:trainNumbers> +                                <netex:TrainNumberRef ref="CITYWAY:TrainNumber:1234:LOC">version="any"</netex:TrainNumberRef> +                            </netex:trainNumbers> +                            <netex:passingTimes> +                                <netex:TimetabledPassingTime version="any"> +                                    <netex:ArrivalTime>01:01:00.000</netex:ArrivalTime> +                                    <netex:ArrivalDayOffset>0</netex:ArrivalDayOffset> +                                    <netex:DepartureTime>01:01:00.000</netex:DepartureTime> +                                    <netex:DepartureDayOffset>0</netex:DepartureDayOffset> +                                </netex:TimetabledPassingTime> +                                <netex:TimetabledPassingTime version="any"> +                                    <netex:ArrivalTime>01:05:00.000</netex:ArrivalTime> +                                    <netex:ArrivalDayOffset>0</netex:ArrivalDayOffset> +                                    <netex:DepartureTime>01:05:00.000</netex:DepartureTime> +                                    <netex:DepartureDayOffset>0</netex:DepartureDayOffset> +                                </netex:TimetabledPassingTime> +                            </netex:passingTimes> +                        </netex:ServiceJourney> + +                    </netex:members> +                </netex:GeneralFrame> +            </netex:frames> +        </netex:CompositeFrame> +    </netex:dataObjects> +</netex:PublicationDelivery> diff --git a/spec/fixtures/multiple_with_wrong_calendar/OFFRE_TRANSDEV_20170301122519/offre_C00109_10.xml b/spec/fixtures/multiple_with_wrong_calendar/OFFRE_TRANSDEV_20170301122519/offre_C00109_10.xml new file mode 100644 index 000000000..d260ef17e --- /dev/null +++ b/spec/fixtures/multiple_with_wrong_calendar/OFFRE_TRANSDEV_20170301122519/offre_C00109_10.xml @@ -0,0 +1,172 @@ +<?xml version="1.0" encoding="UTF-8"?> +<netex:PublicationDelivery xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" +    xsi:schemaLocation="http://www.netex.org.uk/netex ../../xsd/NeTEx_publication.xsd" xmlns:netex="http://www.netex.org.uk/netex" +    xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:ifopt="http://www.ifopt.org.uk/ifopt" +    xmlns:gml="http://www.opengis.net/gml/3.2" xmlns:core="http://www.govtalk.gov.uk/core" +    xmlns:siri="http://www.siri.org.uk/siri" version="1.0"> +    <netex:PublicationTimestamp>2017-02-14T09:13:51.0</netex:PublicationTimestamp> +    <netex:ParticipantRef>CITYWAY</netex:ParticipantRef> +    <netex:dataObjects> +        <netex:CompositeFrame id="CITYWAY:CompositeFrame:NETEX_OFFRE_LIGNE-1:LOC" version="any"> +            <netex:Name>Ligne 1</netex:Name> +            <netex:TypeOfFrameRef ref="NETEX_OFFRE_LIGNE"/> +            <netex:frames> +                <netex:GeneralFrame id="CITYWAY:GeneralFrame:NETEX_STRUCTURE-20170214090012:LOC" version="any"> +                    <netex:TypeOfFrameRef ref="NETEX_STRUCTURE"/> +                    <netex:members> + +                        <netex:Route id="CITYWAY:Route:1:LOC" version="any"> +                            <netex:Name>route 1</netex:Name> +                            <netex:LineRef ref="STIF:CODIFLIGNE:Line:C00109">version="any"</netex:LineRef> +                            <netex:DirectionType>outbound</netex:DirectionType> +                            <netex:DirectionRef ref="CITYWAY:Direction:1:LOC" version="any"/> +                            <netex:InverseRouteRef ref="CITYWAY:Route:2:LOC" version="any"/> +                        </netex:Route> +                        <netex:Route id="CITYWAY:Route:2:LOC" version="any"> +                            <netex:Name>route 2</netex:Name> +                            <netex:LineRef ref="STIF:CODIFLIGNE:Line:C00109">version="any"</netex:LineRef> +                            <netex:DirectionType>inbound</netex:DirectionType> +                            <netex:DirectionRef ref="CITYWAY:Direction:2:LOC" version="any"/> +                            <netex:InverseRouteRef ref="CITYWAY:Route:1:LOC" version="any"/> +                        </netex:Route> + + +                        <netex:Direction id="CITYWAY:Direction:1:LOC" version="any"> +                            <netex:Name>Par ici aussi</netex:Name> +                        </netex:Direction> +                        <netex:Direction id="CITYWAY:Direction:2:LOC" version="any"> +                            <netex:Name>Par là aussi</netex:Name> +                        </netex:Direction> + + +                        <netex:ServiceJourneyPattern id="CITYWAY:ServiceJourneyPattern:1:LOC" version="any"> +                            <netex:Name>Par ici itou</netex:Name> +                            <netex:RouteRef ref="CITYWAY:Route:1:LOC" version="any"/> +                            <netex:DestinationDisplayRef ref="CITYWAY:DestinationDisplay:1:LOC" version="any"/> +                            <netex:pointsInSequence> +                                <netex:StopPointInJourneyPattern id="CITYWAY:StopPointInJourneyPattern:1-1-1:LOC" order="1" +                                    version="any"> +                                    <netex:ScheduledStopPointRef ref="CITYWAY:ScheduledStopPoint:1-1:LOC" version="any"/> +                                    <netex:ForAlighting>true</netex:ForAlighting> +                                    <netex:ForBoarding>true</netex:ForBoarding> +                                </netex:StopPointInJourneyPattern> +                                <netex:StopPointInJourneyPattern id="CITYWAY:StopPointInJourneyPattern:1-1-2:LOC" order="2" +                                    version="any"> +                                    <netex:ScheduledStopPointRef ref="CITYWAY:ScheduledStopPoint:1-2:LOC" version="any"/> +                                    <netex:ForAlighting>true</netex:ForAlighting> +                                    <netex:ForBoarding>true</netex:ForBoarding> +                                </netex:StopPointInJourneyPattern> +                            </netex:pointsInSequence> +                            <netex:ServiceJourneyPatternType>passenger</netex:ServiceJourneyPatternType> +                        </netex:ServiceJourneyPattern> +                        <netex:ServiceJourneyPattern id="CITYWAY:ServiceJourneyPattern:2:LOC" version="any"> +                            <netex:Name>Par là itou</netex:Name> +                            <netex:RouteRef ref="CITYWAY:Route:2:LOC" version="any"/> +                            <netex:DestinationDisplayRef ref="CITYWAY:DestinationDisplay:2:LOC" version="any"/> +                            <netex:pointsInSequence> +                                <netex:StopPointInJourneyPattern id="CITYWAY:StopPointInJourneyPattern:2-2-1:LOC" order="1" +                                    version="any"> +                                    <netex:ScheduledStopPointRef ref="CITYWAY:ScheduledStopPoint:2-1:LOC" version="any"/> +                                    <netex:ForAlighting>true</netex:ForAlighting> +                                    <netex:ForBoarding>true</netex:ForBoarding> +                                </netex:StopPointInJourneyPattern> +                                <netex:StopPointInJourneyPattern id="CITYWAY:StopPointInJourneyPattern:2-2-2:LOC" order="2" +                                    version="any"> +                                    <netex:ScheduledStopPointRef ref="CITYWAY:ScheduledStopPoint:2-2:LOC" version="any"/> +                                    <netex:ForAlighting>true</netex:ForAlighting> +                                    <netex:ForBoarding>true</netex:ForBoarding> +                                </netex:StopPointInJourneyPattern> +                            </netex:pointsInSequence> +                            <netex:ServiceJourneyPatternType>passenger</netex:ServiceJourneyPatternType> +                        </netex:ServiceJourneyPattern> + + +                        <netex:DestinationDisplay id="CITYWAY:DestinationDisplay:1:LOC" version="any"> +                            <netex:FrontText>Mission 1 bis</netex:FrontText> +                            <netex:PublicCode>1234</netex:PublicCode> +                        </netex:DestinationDisplay> +                        <netex:DestinationDisplay id="CITYWAY:DestinationDisplay:2:LOC" version="any"> +                            <netex:FrontText>Mission 2 bis</netex:FrontText> +                            <netex:PublicCode>2345</netex:PublicCode> +                        </netex:DestinationDisplay> + + +                        <netex:ScheduledStopPoint id="CITYWAY:ScheduledStopPoint:1-1:LOC" version="any"/> +                        <netex:ScheduledStopPoint id="CITYWAY:ScheduledStopPoint:1-2:LOC" version="any"/> +                        <netex:ScheduledStopPoint id="CITYWAY:ScheduledStopPoint:2-1:LOC" version="any"/> +                        <netex:ScheduledStopPoint id="CITYWAY:ScheduledStopPoint:2-2:LOC" version="any"/> + + +                        <netex:PassengerStopAssignment id="CITYWAY:PassengerStopAssignment:1-1:LOC" version="any" order="0"> +                            <netex:ScheduledStopPointRef ref="CITYWAY:ScheduledStopPoint:1-1:LOC" version="any"/> +                            <netex:QuayRef ref="FR:78217:ZDE:50094817:STIF">version="any"</netex:QuayRef> +                        </netex:PassengerStopAssignment> +                        <netex:PassengerStopAssignment id="CITYWAY:PassengerStopAssignment:2-1:LOC" version="any" order="0"> +                            <netex:ScheduledStopPointRef ref="CITYWAY:ScheduledStopPoint:2-1:LOC" version="any"/> +                            <netex:QuayRef ref="FR:78402:ZDE:50000918:STIF">version="any"</netex:QuayRef> +                        </netex:PassengerStopAssignment> +                        <netex:PassengerStopAssignment id="CITYWAY:PassengerStopAssignment:1-2:LOC" version="any" order="0"> +                            <netex:ScheduledStopPointRef ref="CITYWAY:ScheduledStopPoint:1-2:LOC" version="any"/> +                            <netex:QuayRef ref="FR:78402:ZDE:50000917:STIF">version="any"</netex:QuayRef> +                        </netex:PassengerStopAssignment> +                        <netex:PassengerStopAssignment id="CITYWAY:PassengerStopAssignment:2-2:LOC" version="any" order="0"> +                            <netex:ScheduledStopPointRef ref="CITYWAY:ScheduledStopPoint:2-2:LOC" version="any"/> +                            <netex:QuayRef ref="FR:78217:ZDE:50094816:STIF">version="any"</netex:QuayRef> +                        </netex:PassengerStopAssignment> + + +                        <netex:RoutingConstraintZone id="CITYWAY:RoutingConstraintZone:1:LOC" version="any"> +                            <netex:Name>ITL 1</netex:Name> +                            <netex:members> +                                <netex:ScheduledStopPointRef ref="CITYWAY:ScheduledStopPoint:1-1:LOC" version="any"/> +                                <netex:ScheduledStopPointRef ref="CITYWAY:ScheduledStopPoint:1-2:LOC" version="any"/> +                            </netex:members> +                            <netex:ZoneUse>cannotBoardAndAlightInSameZone</netex:ZoneUse> +                        </netex:RoutingConstraintZone> + +                    </netex:members> +                </netex:GeneralFrame> +                <netex:GeneralFrame id="CITYWAY:GeneralFrame:NETEX_HORAIRE-20170214090012:LOC" version="any"> +                    <netex:TypeOfFrameRef ref="NETEX_HORAIRE"/> +                    <netex:members> + +                        <netex:ServiceJourney id="CITYWAY:ServiceJourney:1-1:LOC" version="any"> +                            <netex:Name>Course 1 par ici aussi</netex:Name> +                            <netex:noticeAssignments> +                                <netex:NoticeAssignment id="ns1" version="any" order="0"> +                                    <netex:NoticeRef ref="CITYWAY:Notice:2:LOC"> +                                            version="any"</netex:NoticeRef> +                                </netex:NoticeAssignment> +                            </netex:noticeAssignments> +                            <netex:dayTypes> +                                <netex:DayTypeRef ref="CITYWAY:DayType:1:LOC"> version="any"</netex:DayTypeRef> +                                <netex:DayTypeRef ref="CITYWAY:DayType:4:LOC"> version="any"</netex:DayTypeRef> +                            </netex:dayTypes> +                            <netex:JourneyPatternRef ref="CITYWAY:ServiceJourneyPattern:1:LOC" version="any"/> +                            <netex:OperatorRef ref="STIF:CODIFLIGNE:Operator:212"> +                                    version="any"</netex:OperatorRef> +                            <netex:trainNumbers> +                                <netex:TrainNumberRef ref="CITYWAY:TrainNumber:1234:LOC">version="any"</netex:TrainNumberRef> +                            </netex:trainNumbers> +                            <netex:passingTimes> +                                <netex:TimetabledPassingTime version="any"> +                                    <netex:ArrivalTime>23:58:00.000</netex:ArrivalTime> +                                    <netex:ArrivalDayOffset>0</netex:ArrivalDayOffset> +                                    <netex:DepartureTime>23:59:00.000</netex:DepartureTime> +                                    <netex:DepartureDayOffset>0</netex:DepartureDayOffset> +                                </netex:TimetabledPassingTime> +                                <netex:TimetabledPassingTime version="any"> +                                    <netex:ArrivalTime>00:03:00.000</netex:ArrivalTime> +                                    <netex:ArrivalDayOffset>1</netex:ArrivalDayOffset> +                                    <netex:DepartureTime>00:04:00.000</netex:DepartureTime> +                                    <netex:DepartureDayOffset>1</netex:DepartureDayOffset> +                                </netex:TimetabledPassingTime> +                            </netex:passingTimes> +                        </netex:ServiceJourney> + +                    </netex:members> +                </netex:GeneralFrame> +            </netex:frames> +        </netex:CompositeFrame> +    </netex:dataObjects> +</netex:PublicationDelivery> diff --git a/spec/fixtures/tom_tom_matrix.json b/spec/fixtures/tom_tom_matrix.json new file mode 100644 index 000000000..30048576c --- /dev/null +++ b/spec/fixtures/tom_tom_matrix.json @@ -0,0 +1,123 @@ +{ +  "formatVersion": "0.0.1", +  "matrix": [ +    [ +      { +        "statusCode": 200, +        "response": { +          "routeSummary": { +            "lengthInMeters": 0, +            "travelTimeInSeconds": 0, +            "trafficDelayInSeconds": 0, +            "departureTime": "2018-03-23T11:20:17+01:00", +            "arrivalTime": "2018-03-23T11:20:17+01:00" +          } +        } +      }, +      { +        "statusCode": 200, +        "response": { +          "routeSummary": { +            "lengthInMeters": 117947, +            "travelTimeInSeconds": 8356, +            "trafficDelayInSeconds": 0, +            "departureTime": "2018-03-23T11:20:17+01:00", +            "arrivalTime": "2018-03-23T13:39:32+01:00" +          } +        } +      }, +      { +        "statusCode": 200, +        "response": { +          "routeSummary": { +            "lengthInMeters": 999088, +            "travelTimeInSeconds": 62653, +            "trafficDelayInSeconds": 298, +            "departureTime": "2018-03-23T11:20:17+01:00", +            "arrivalTime": "2018-03-24T04:44:30+01:00" +          } +        } +      } +    ], +    [ +      { +        "statusCode": 200, +        "response": { +          "routeSummary": { +            "lengthInMeters": 117231, +            "travelTimeInSeconds": 9729, +            "trafficDelayInSeconds": 0, +            "departureTime": "2018-03-23T11:20:17+01:00", +            "arrivalTime": "2018-03-23T14:02:25+01:00" +          } +        } +      }, +      { +        "statusCode": 200, +        "response": { +          "routeSummary": { +            "lengthInMeters": 0, +            "travelTimeInSeconds": 0, +            "trafficDelayInSeconds": 0, +            "departureTime": "2018-03-23T11:20:17+01:00", +            "arrivalTime": "2018-03-23T11:20:17+01:00" +          } +        } +      }, +      { +        "statusCode": 200, +        "response": { +          "routeSummary": { +            "lengthInMeters": 1114635, +            "travelTimeInSeconds": 72079, +            "trafficDelayInSeconds": 298, +            "departureTime": "2018-03-23T11:20:18+01:00", +            "arrivalTime": "2018-03-24T07:21:36+01:00" +          } +        } +      } +    ], +    [ +      { +        "statusCode": 200, +        "response": { +          "routeSummary": { +            "lengthInMeters": 997232, +            "travelTimeInSeconds": 63245, +            "trafficDelayInSeconds": 179, +            "departureTime": "2018-03-23T11:20:18+01:00", +            "arrivalTime": "2018-03-24T04:54:23+01:00" +          } +        } +      }, +      { +        "statusCode": 200, +        "response": { +          "routeSummary": { +            "lengthInMeters": 1113108, +            "travelTimeInSeconds": 68485, +            "trafficDelayInSeconds": 52, +            "departureTime": "2018-03-23T11:20:18+01:00", +            "arrivalTime": "2018-03-24T06:21:43+01:00" +          } +        } +      }, +      { +        "statusCode": 200, +        "response": { +          "routeSummary": { +            "lengthInMeters": 344, +            "travelTimeInSeconds": 109, +            "trafficDelayInSeconds": 0, +            "departureTime": "2018-03-23T11:20:18+01:00", +            "arrivalTime": "2018-03-23T11:22:07+01:00" +          } +        } +      } +    ] +  ], +  "summary": { +    "successfulRoutes": 9, +    "totalRoutes": 9 +  } +} diff --git a/spec/lib/compliance_control_set_cloner_spec.rb b/spec/lib/compliance_control_set_cloner_spec.rb index 7efe27659..0d3561e0e 100644 --- a/spec/lib/compliance_control_set_cloner_spec.rb +++ b/spec/lib/compliance_control_set_cloner_spec.rb @@ -49,7 +49,7 @@ RSpec.describe ComplianceControlSetCloner do        context 'Directed Acyclic Graph is copied correctly' do          let(:source_blox){ -          3.times.map{ |_| create :compliance_control_block, compliance_control_set: source_set } +          3.times.map{ |n| create :compliance_control_block, compliance_control_set: source_set, transport_mode: StifTransportModeEnumerations.transport_modes[n], transport_submode: StifTransportSubmodeEnumerations.transport_submodes[n]   }          }          let(:direct_ccs){            3.times.map{ |n| create :generic_attribute_control_min_max, compliance_control_set: source_set, name: "direct #{n.succ}", code: "direct-#{n.succ}" } diff --git a/spec/lib/compliance_control_set_copier_spec.rb b/spec/lib/compliance_control_set_copier_spec.rb index 0f15d86d0..d1a56cd7f 100644 --- a/spec/lib/compliance_control_set_copier_spec.rb +++ b/spec/lib/compliance_control_set_copier_spec.rb @@ -23,7 +23,7 @@ RSpec.describe ComplianceControlSetCopier do        context 'Directed Acyclic Graph is copied correctly' do          let(:cc_blox){ -          3.times.map{ |_| create :compliance_control_block, compliance_control_set: cc_set } +          3.times.map{ |n| create :compliance_control_block, compliance_control_set: cc_set, transport_mode: StifTransportModeEnumerations.transport_modes[n], transport_submode: StifTransportSubmodeEnumerations.transport_submodes[n] }          }          let!(:direct_ccs){            3.times.map{ |n| create :compliance_control, compliance_control_set: cc_set, name: "direct #{n.succ}", code: "direct-#{n.succ}" } diff --git a/spec/lib/gtfs/time_spec.rb b/spec/lib/gtfs/time_spec.rb new file mode 100644 index 000000000..540d7cc79 --- /dev/null +++ b/spec/lib/gtfs/time_spec.rb @@ -0,0 +1,27 @@ +require "rails_helper" + +RSpec.describe GTFS::Time do + +  it "returns an UTC Time with given H:M:S" do +    expect(GTFS::Time.parse("14:29:00").time).to eq(Time.parse("2000-01-01 14:29:00 +00")) +  end + +  it "support hours with a single number" do +    expect(GTFS::Time.parse("4:29:00").time).to eq(Time.parse("2000-01-01 04:29:00 +00")) +  end + +  it "return nil for invalid format" do +    expect(GTFS::Time.parse("abc")).to be_nil +  end + +  it "removes 24 hours after 23:59:59" do +    expect(GTFS::Time.parse("25:29:00").time).to eq(Time.parse("2000-01-01 01:29:00 +00")) +  end + +  it "returns a day_offset for each 24 hours turn" do +    expect(GTFS::Time.parse("10:00:00").day_offset).to eq(0) +    expect(GTFS::Time.parse("30:00:00").day_offset).to eq(1) +    expect(GTFS::Time.parse("50:00:00").day_offset).to eq(2) +  end + +end diff --git a/spec/lib/route_way_cost_unit_converter_spec.rb b/spec/lib/route_way_cost_unit_converter_spec.rb index 3c5e51710..aa25d57d2 100644 --- a/spec/lib/route_way_cost_unit_converter_spec.rb +++ b/spec/lib/route_way_cost_unit_converter_spec.rb @@ -35,4 +35,32 @@ RSpec.describe RouteWayCostUnitConverter do        })      end    end + +  describe ".meters_to_kilometers" do +    it "converts meters to integer kilometres" do +      expect( +        RouteWayCostUnitConverter.meters_to_kilometers(6350) +      ).to eq(6) +    end + +    it "snaps values between 0 and 1 to 1" do +      expect( +        RouteWayCostUnitConverter.meters_to_kilometers(50) +      ).to eq(1) +    end +  end + +  describe ".seconds_to_minutes" do +    it "converts seconds to minutes" do +      expect( +        RouteWayCostUnitConverter.seconds_to_minutes(300) +      ).to eq(5) +    end + +    it "snaps values between 0 and 1 to 1" do +      expect( +        RouteWayCostUnitConverter.seconds_to_minutes(3) +      ).to eq(1) +    end +  end  end diff --git a/spec/lib/tom_tom/matrix/request_json_serializer_spec.rb b/spec/lib/tom_tom/matrix/request_json_serializer_spec.rb new file mode 100644 index 000000000..1fafad302 --- /dev/null +++ b/spec/lib/tom_tom/matrix/request_json_serializer_spec.rb @@ -0,0 +1,39 @@ +RSpec.describe TomTom::Matrix::RequestJSONSerializer do +  describe ".dump" do +    it "serializes BigDecimal values to floats" do +      points = [{ +        point: { +          latitude: 52.50867.to_d, +          longitude: 13.42879.to_d +        }, +      }] +      data = { +        origins: points, +        destinations: points +      } + +      expect( +        TomTom::Matrix::RequestJSONSerializer.dump(data) +      ).to eq(<<-JSON.delete(" \n")) +        { +          "origins": [ +            { +              "point": { +                "latitude": 52.50867, +                "longitude": 13.42879 +              } +            } +          ], +          "destinations": [ +            { +              "point": { +                "latitude": 52.50867, +                "longitude": 13.42879 +              } +            } +          ] +        } +      JSON +    end +  end +end diff --git a/spec/lib/tom_tom/matrix_spec.rb b/spec/lib/tom_tom/matrix_spec.rb new file mode 100644 index 000000000..605f1d254 --- /dev/null +++ b/spec/lib/tom_tom/matrix_spec.rb @@ -0,0 +1,228 @@ +RSpec.describe TomTom::Matrix do +  let(:matrix) { TomTom::Matrix.new(nil) } + +  describe "#points_from_way_costs" do +    it "extracts a set of lat/lng coordinates from a list of WayCosts" do +      way_costs = [ +        WayCost.new( +          departure: Geokit::LatLng.new(48.85086, 2.36143), +          arrival: Geokit::LatLng.new(47.91231, 1.87606), +          id: '44-77' +        ), +        WayCost.new( +          departure: Geokit::LatLng.new(47.91231, 1.87606), +          arrival: Geokit::LatLng.new(52.50867, 13.42879), +          id: '77-88' +        ) +      ] + +      expect( +        matrix.points_from_way_costs(way_costs) +      ).to eq([ +        TomTom::Matrix::Point.new( +          Geokit::LatLng.new(48.85086, 2.36143), +          '44' +        ), +        TomTom::Matrix::Point.new( +          Geokit::LatLng.new(47.91231, 1.87606), +          '77' +        ), +        TomTom::Matrix::Point.new( +          Geokit::LatLng.new(52.50867, 13.42879), +          '88' +        ) +      ]) +    end +  end + +  describe "#points_as_params" do +    it "transforms a set of LatLng points into a hash for use by TomTom Matrix" do +      points = [ +        TomTom::Matrix::Point.new( +          Geokit::LatLng.new(48.85086, 2.36143), +          '44' +        ), +        TomTom::Matrix::Point.new( +          Geokit::LatLng.new(47.91231, 1.87606), +          '77' +        ), +        TomTom::Matrix::Point.new( +          Geokit::LatLng.new(52.50867, 13.42879), +          '88' +        ) +      ] + +      expect( +        matrix.points_as_params(points) +      ).to eq([ +        { +          point: { +            latitude: 48.85086, +            longitude: 2.36143 +          }, +        }, +        { +          point: { +            latitude: 47.91231, +            longitude: 1.87606 +          }, +        }, +        { +          point: { +            latitude: 52.50867, +            longitude: 13.42879 +          }, +        } +      ]) +    end +  end + +  describe "#build_request_body" do +    it "serializes BigDecimal coordinates to floats" do +      points = [ +        { +          point: { +            latitude: 48.85086.to_d, +            longitude: 2.36143.to_d +          }, +        }, +        { +          point: { +            latitude: 47.91231.to_d, +            longitude: 1.87606.to_d +          }, +        }, +        { +          point: { +            latitude: 52.50867.to_d, +            longitude: 13.42879.to_d +          }, +        } +      ] + +      expect( +        matrix.build_request_body(points) +      ).to eq(<<-JSON.delete(" \n")) +        { +          "origins": [ +            { +              "point": { +                "latitude": 48.85086, +                "longitude": 2.36143 +              } +            }, +            { +              "point": { +                "latitude": 47.91231, +                "longitude": 1.87606 +              } +            }, +            { +              "point": { +                "latitude": 52.50867, +                "longitude": 13.42879 +              } +            } +          ], +          "destinations": [ +            { +              "point": { +                "latitude": 48.85086, +                "longitude": 2.36143 +              } +            }, +            { +              "point": { +                "latitude": 47.91231, +                "longitude": 1.87606 +              } +            }, +            { +              "point": { +                "latitude": 52.50867, +                "longitude": 13.42879 +              } +            } +          ] +        } +      JSON +    end +  end + +  describe "#extract_costs_to_way_costs!" do +    it "puts distance & time costs in way_costs" do +      way_costs = [ +        WayCost.new( +          departure: Geokit::LatLng.new(48.85086, 2.36143), +          arrival: Geokit::LatLng.new(47.91231, 1.87606), +          id: '55-99' +        ), +        WayCost.new( +          departure: Geokit::LatLng.new(47.91231, 1.87606), +          arrival: Geokit::LatLng.new(52.50867, 13.42879), +          id: '99-22' +        ) +      ] + +      expected_way_costs = [ +        WayCost.new( +          departure: Geokit::LatLng.new(48.85086, 2.36143), +          arrival: Geokit::LatLng.new(47.91231, 1.87606), +          distance: 117947, +          time: 8356, +          id: '55-99' +        ), +        WayCost.new( +          departure: Geokit::LatLng.new(48.85086, 2.36143), +          arrival: Geokit::LatLng.new(52.50867, 13.42879), +          distance: 999088, +          time: 62653, +          id: '55-22' +        ), +        WayCost.new( +          departure: Geokit::LatLng.new(47.91231, 1.87606), +          arrival: Geokit::LatLng.new(48.85086, 2.36143), +          distance: 117231, +          time: 9729, +          id: '99-55' +        ), +        WayCost.new( +          departure: Geokit::LatLng.new(47.91231, 1.87606), +          arrival: Geokit::LatLng.new(52.50867, 13.42879), +          distance: 1114635, +          time: 72079, +          id: '99-22' +        ), +        WayCost.new( +          departure: Geokit::LatLng.new(52.50867, 13.42879), +          arrival: Geokit::LatLng.new(48.85086, 2.36143), +          distance: 997232, +          time: 63245, +          id: '22-55' +        ), +        WayCost.new( +          departure: Geokit::LatLng.new(52.50867, 13.42879), +          arrival: Geokit::LatLng.new(47.91231, 1.87606), +          distance: 1113108, +          time: 68485, +          id: '22-99' +        ), +        WayCost.new( +          departure: Geokit::LatLng.new(52.50867, 13.42879), +          arrival: Geokit::LatLng.new(52.50867, 13.42879), +          distance: 344, +          time: 109, +          id: '22-22' +        ) +      ] + +      matrix_response = JSON.parse(read_fixture('tom_tom_matrix.json')) + +      points = matrix.points_from_way_costs(way_costs) + +      expect( +        matrix.extract_costs_to_way_costs!(way_costs, points, matrix_response) +      ).to match_array(expected_way_costs) +    end +  end +end diff --git a/spec/models/api/v1/api_key_spec.rb b/spec/models/api/v1/api_key_spec.rb index cc483a118..5c5a6bde1 100644 --- a/spec/models/api/v1/api_key_spec.rb +++ b/spec/models/api/v1/api_key_spec.rb @@ -4,7 +4,7 @@ RSpec.describe Api::V1::ApiKey, type: :model do    subject { create(:api_key) }    it { should validate_presence_of :organisation } -  it { is_expected.to be_versioned } +    it 'should have a valid factory' do      expect(build(:api_key)).to be_valid diff --git a/spec/models/calendar_observer_spec.rb b/spec/models/calendar_observer_spec.rb index 4fba02bef..dd7034af4 100644 --- a/spec/models/calendar_observer_spec.rb +++ b/spec/models/calendar_observer_spec.rb @@ -1,8 +1,13 @@  require 'rails_helper'  RSpec.describe CalendarObserver, type: :observer do -  let(:calendar) { create(:calendar, shared: true) } -  let(:user)     { create(:user, organisation: create(:organisation)) } +  let(:workgroup_1) { create :workgroup } +  let(:workgroup_2) { create :workgroup } + +  let(:calendar) { create(:calendar, shared: true, workgroup_id: workgroup_1.id) } +   +  let(:user_1)     { create(:user, organisation: create(:organisation, workbenches: [create(:workbench, workgroup_id: workgroup_1.id)] )) } +  let(:user_2)     { create(:user, organisation: create(:organisation, workbenches: [create(:workbench, workgroup_id: workgroup_2.id)] )) }    context 'after_update' do      it 'should observe calendar updates' do @@ -12,14 +17,21 @@ RSpec.describe CalendarObserver, type: :observer do      it 'should schedule mailer on calendar update' do        calendar.name = 'edited_name' -      expect(MailerJob).to receive(:perform_later).with 'CalendarMailer', 'updated', [calendar.id, user.id] +      expect(MailerJob).to receive(:perform_later).with 'CalendarMailer', 'updated', [calendar.id, user_1.id]        calendar.save      end      it 'should not schedule mailer for none shared calendar on update' do        calendar = create(:calendar, shared: false)        calendar.name = 'edited_name' -      expect(MailerJob).to_not receive(:perform_later).with 'CalendarMailer', 'updated', [calendar.id, user.id] +      expect(MailerJob).to_not receive(:perform_later).with 'CalendarMailer', 'updated', [calendar.id, user_1.id] +      calendar.save +    end + +    it "should only send mail to user from the same workgroup" do +      calendar.name = 'edited_name' +      expect(MailerJob).to receive(:perform_later).with 'CalendarMailer', 'updated', [calendar.id, user_1.id] +      expect(MailerJob).to_not receive(:perform_later).with 'CalendarMailer', 'updated', [calendar.id, user_2.id]        calendar.save      end    end @@ -31,13 +43,13 @@ RSpec.describe CalendarObserver, type: :observer do      end      it 'should schedule mailer on calendar create' do -      expect(MailerJob).to receive(:perform_later).with 'CalendarMailer', 'created', [anything, user.id] -      build(:calendar, shared: true).save +      expect(MailerJob).to receive(:perform_later).with 'CalendarMailer', 'created', [anything, user_1.id] +      build(:calendar, shared: true, workgroup_id: workgroup_1.id).save      end      it 'should not schedule mailer for none shared calendar on create' do -      expect(MailerJob).to_not receive(:perform_later).with 'CalendarMailer', 'created', [anything, user.id] -      build(:calendar, shared: false).save +      expect(MailerJob).to_not receive(:perform_later).with 'CalendarMailer', 'created', [anything, user_1.id] +      build(:calendar, shared: false, workgroup_id: workgroup_1.id).save      end    end  end diff --git a/spec/models/calendar_spec.rb b/spec/models/calendar_spec.rb index a5c0a7471..e57eee3b2 100644 --- a/spec/models/calendar_spec.rb +++ b/spec/models/calendar_spec.rb @@ -4,9 +4,7 @@ RSpec.describe Calendar, :type => :model do    it { is_expected.to validate_presence_of(:organisation) }    it { is_expected.to validate_presence_of(:name) } -  it { is_expected.to validate_presence_of(:short_name) } -  it { is_expected.to validate_uniqueness_of(:short_name) } -  it { is_expected.to be_versioned } +      describe '#to_time_table' do      let(:calendar) { create(:calendar, int_day_types: Calendar::MONDAY | Calendar::SUNDAY, date_ranges: [Date.today...(Date.today + 1.month)]) } diff --git a/spec/models/chouette/access_link_spec.rb b/spec/models/chouette/access_link_spec.rb index ced99eb1d..448c22d33 100644 --- a/spec/models/chouette/access_link_spec.rb +++ b/spec/models/chouette/access_link_spec.rb @@ -4,7 +4,7 @@ describe Chouette::AccessLink, :type => :model do    subject { create(:access_link) }    it { is_expected.to validate_uniqueness_of :objectid } -  it { is_expected.to be_versioned } +       describe '#get_objectid' do      subject { super().get_objectid } diff --git a/spec/models/chouette/access_point_spec.rb b/spec/models/chouette/access_point_spec.rb index 2184c6ec2..9c637cf41 100644 --- a/spec/models/chouette/access_point_spec.rb +++ b/spec/models/chouette/access_point_spec.rb @@ -12,7 +12,7 @@ describe Chouette::AccessPoint, :type => :model do    it { is_expected.to validate_presence_of :name }    it { is_expected.to validate_numericality_of :latitude }    it { is_expected.to validate_numericality_of :longitude } -  it { is_expected.to be_versioned } +      describe ".latitude" do      it "should accept -90 value" do diff --git a/spec/models/chouette/company_spec.rb b/spec/models/chouette/company_spec.rb index 34b19eeda..677c60dd9 100644 --- a/spec/models/chouette/company_spec.rb +++ b/spec/models/chouette/company_spec.rb @@ -3,7 +3,7 @@ require 'spec_helper'  describe Chouette::Company, :type => :model do    subject { create(:company) }    it { should validate_presence_of :name } -  it { is_expected.to be_versioned } +      describe "#nullables empty" do      it "should set null empty nullable attributes" do diff --git a/spec/models/chouette/connection_link_spec.rb b/spec/models/chouette/connection_link_spec.rb index 4ab67d007..4486e348c 100644 --- a/spec/models/chouette/connection_link_spec.rb +++ b/spec/models/chouette/connection_link_spec.rb @@ -8,7 +8,7 @@ describe Chouette::ConnectionLink, :type => :model do    subject { create(:connection_link) }    it { is_expected.to validate_uniqueness_of :objectid } -  it { is_expected.to be_versioned } +      describe '#get_objectid' do      subject { super().get_objectid } diff --git a/spec/models/chouette/group_of_line_spec.rb b/spec/models/chouette/group_of_line_spec.rb index d43d75374..8b2df69e5 100644 --- a/spec/models/chouette/group_of_line_spec.rb +++ b/spec/models/chouette/group_of_line_spec.rb @@ -4,7 +4,7 @@ describe Chouette::GroupOfLine, :type => :model do    subject { create(:group_of_line) }    it { should validate_presence_of :name } -  it { is_expected.to be_versioned } +      describe "#stop_areas" do      let!(:line){create(:line, :group_of_lines => [subject])} diff --git a/spec/models/chouette/journey_pattern_spec.rb b/spec/models/chouette/journey_pattern_spec.rb index dac45d6b5..02a54f056 100644 --- a/spec/models/chouette/journey_pattern_spec.rb +++ b/spec/models/chouette/journey_pattern_spec.rb @@ -1,7 +1,7 @@  require 'spec_helper'  describe Chouette::JourneyPattern, :type => :model do -  it { is_expected.to be_versioned } +    subject { create(:journey_pattern) }    describe 'checksum' do @@ -92,7 +92,7 @@ describe Chouette::JourneyPattern, :type => :model do      let(:journey_pattern) { create :journey_pattern }      let(:distances){ [] }      it "should raise an error" do -      expect{journey_pattern.set_distances(distances)}.to raise_error +      expect{journey_pattern.set_distances(distances)}.to raise_error(RuntimeError)      end      context "with consistent data" do diff --git a/spec/models/chouette/line_spec.rb b/spec/models/chouette/line_spec.rb index 056d5da9e..cd7cdcb09 100644 --- a/spec/models/chouette/line_spec.rb +++ b/spec/models/chouette/line_spec.rb @@ -7,7 +7,7 @@ describe Chouette::Line, :type => :model do    # it { is_expected.to validate_presence_of :network }    # it { is_expected.to validate_presence_of :company }    it { should validate_presence_of :name } -  it { is_expected.to be_versioned } +      describe '#display_name' do      it 'should display local_id, number, name and company name' do diff --git a/spec/models/chouette/network_spec.rb b/spec/models/chouette/network_spec.rb index 78a4150df..11ad7cacb 100644 --- a/spec/models/chouette/network_spec.rb +++ b/spec/models/chouette/network_spec.rb @@ -3,7 +3,7 @@ require 'spec_helper'  describe Chouette::Network, :type => :model do    subject { create(:network) }    it { should validate_presence_of :name } -  it { is_expected.to be_versioned } +      describe "#stop_areas" do      let!(:line){create(:line, :network => subject)} diff --git a/spec/models/chouette/route/route_base_spec.rb b/spec/models/chouette/route/route_base_spec.rb index d24ad6348..43ff28c40 100644 --- a/spec/models/chouette/route/route_base_spec.rb +++ b/spec/models/chouette/route/route_base_spec.rb @@ -15,8 +15,7 @@ RSpec.describe Chouette::Route, :type => :model do    #it { is_expected.to validate_presence_of :direction_code }    it { is_expected.to validate_inclusion_of(:direction).in_array(%i(straight_forward backward clockwise counter_clockwise north north_west west south_west south south_east east north_east)) }    it { is_expected.to validate_inclusion_of(:wayback).in_array(%i(outbound inbound)) } -  it { is_expected.to be_versioned } - +      context "reordering methods" do      let(:bad_stop_point_ids){subject.stop_points.map { |sp| sp.id + 1}}      let(:ident){subject.stop_points.map(&:id)} @@ -63,17 +62,17 @@ RSpec.describe Chouette::Route, :type => :model do    end    context "callbacks" do -    it "calls #calculate_costs! after_save when TomTom is enabled" do +    it "calls #calculate_costs! after_commit when TomTom is enabled", truncation: true do        allow(TomTom).to receive(:enabled?).and_return(true) -      route = create(:route) +      route = build(:route)        expect(route).to receive(:calculate_costs!)        route.save      end -    it "doesn't call #calculate_costs! after_save if TomTom is disabled" do +    it "doesn't call #calculate_costs! after_commit if TomTom is disabled", truncation: true do        allow(TomTom).to receive(:enabled?).and_return(false) -      route = create(:route) +      route = build(:route)        expect(route).not_to receive(:calculate_costs!)        route.save diff --git a/spec/models/chouette/route/route_duplication_spec.rb b/spec/models/chouette/route/route_duplication_spec.rb index 8b3a948a2..47233b04e 100644 --- a/spec/models/chouette/route/route_duplication_spec.rb +++ b/spec/models/chouette/route/route_duplication_spec.rb @@ -8,9 +8,6 @@ RSpec.describe Chouette::Route do          route.duplicate          expect( values_for_create(Chouette::Route.last, except: %w{objectid name checksum checksum_source}) ).to eq( values_for_create( route, except: %w{objectid name checksum checksum_source} ) )        end -      it 'and others cannot' do -        expect{ route.duplicate name: 'YAN', line_id: 42  }.to raise_error(ArgumentError) -      end        it 'same associated stop_areeas' do          expect( route.duplicate.stop_areas.pluck(:id) ).to eq(route.stop_areas.pluck(:id))        end diff --git a/spec/models/chouette/route/route_stop_points_spec.rb b/spec/models/chouette/route/route_stop_points_spec.rb index 03c53b4cf..af26f017a 100644 --- a/spec/models/chouette/route/route_stop_points_spec.rb +++ b/spec/models/chouette/route/route_stop_points_spec.rb @@ -78,15 +78,16 @@ RSpec.describe Chouette::Route, :type => :model do    end    describe "#stop_points" do +    let(:first_stop_point) { subject.stop_points.first}      context "#find_by_stop_area" do        context "when arg is first quay id" do -        let(:first_stop_point) { subject.stop_points.first}          it "should return first quay" do            expect(subject.stop_points.find_by_stop_area( first_stop_point.stop_area_id)).to eq( first_stop_point)          end        end      end    end +    describe "#stop_areas" do      let(:line){ create(:line)}      let(:route_1){ create(:route, :line => line)} diff --git a/spec/models/chouette/routing_constraint_zone_spec.rb b/spec/models/chouette/routing_constraint_zone_spec.rb index bda6bb04a..321b41b7b 100644 --- a/spec/models/chouette/routing_constraint_zone_spec.rb +++ b/spec/models/chouette/routing_constraint_zone_spec.rb @@ -8,7 +8,7 @@ describe Chouette::RoutingConstraintZone, type: :model do    it { is_expected.to validate_presence_of :route_id }    # shoulda matcher to validate length of array ?    xit { is_expected.to validate_length_of(:stop_point_ids).is_at_least(2) } -  it { is_expected.to be_versioned } +      describe 'checksum' do      it_behaves_like 'checksum support' diff --git a/spec/models/chouette/stop_area_spec.rb b/spec/models/chouette/stop_area_spec.rb index e35300caf..185820388 100644 --- a/spec/models/chouette/stop_area_spec.rb +++ b/spec/models/chouette/stop_area_spec.rb @@ -13,7 +13,7 @@ describe Chouette::StopArea, :type => :model do    it { should validate_presence_of :kind }    it { should validate_numericality_of :latitude }    it { should validate_numericality_of :longitude } -  it { is_expected.to be_versioned } +      describe "#area_type" do      it "should validate the value is correct regarding to the kind" do diff --git a/spec/models/chouette/stop_point_spec.rb b/spec/models/chouette/stop_point_spec.rb index 6b9e7727f..ba3799619 100644 --- a/spec/models/chouette/stop_point_spec.rb +++ b/spec/models/chouette/stop_point_spec.rb @@ -4,7 +4,7 @@ describe Chouette::StopPoint, :type => :model do    it { is_expected.to validate_uniqueness_of :objectid }    it { is_expected.to validate_presence_of :stop_area } -  it { is_expected.to be_versioned } +      describe '#objectid' do      subject { super().get_objectid } diff --git a/spec/models/chouette/time_table_spec.rb b/spec/models/chouette/time_table_spec.rb index bb88877b9..a3354facb 100644 --- a/spec/models/chouette/time_table_spec.rb +++ b/spec/models/chouette/time_table_spec.rb @@ -6,7 +6,7 @@ describe Chouette::TimeTable, :type => :model do    it { is_expected.to validate_presence_of :comment }    it { is_expected.to validate_uniqueness_of :objectid } -  it { is_expected.to be_versioned } +        def create_time_table_periode time_table, start_date, end_date        create(:time_table_period, time_table: time_table, :period_start => start_date, :period_end => end_date) diff --git a/spec/models/chouette/timeband_spec.rb b/spec/models/chouette/timeband_spec.rb index b960c203f..fa7c8f06e 100644 --- a/spec/models/chouette/timeband_spec.rb +++ b/spec/models/chouette/timeband_spec.rb @@ -1,7 +1,7 @@  require 'spec_helper'  describe Chouette::Timeband, :type => :model do -  it { is_expected.to be_versioned } +      describe '#create' do      context 'when valid' do diff --git a/spec/models/chouette/vehicle_journey_spec.rb b/spec/models/chouette/vehicle_journey_spec.rb index c69655bd4..6d44eeb2f 100644 --- a/spec/models/chouette/vehicle_journey_spec.rb +++ b/spec/models/chouette/vehicle_journey_spec.rb @@ -3,7 +3,7 @@ require 'spec_helper'  describe Chouette::VehicleJourney, :type => :model do    subject { create(:vehicle_journey) } -  it { is_expected.to be_versioned } +      it { should have_and_belong_to_many(:purchase_windows) }    it "must be valid with an at-stop day offset of 1" do @@ -420,6 +420,7 @@ describe Chouette::VehicleJourney, :type => :model do        state['published_journey_name']       = 'edited_name'        state['published_journey_identifier'] = 'edited_identifier'        state['custom_fields'] = {energy: {value: 99}} +      create :custom_field, field_type: :integer, code: :energy, name: :energy        Chouette::VehicleJourney.state_update(route, collection)        expect(state['errors']).to be_nil @@ -867,7 +868,6 @@ describe Chouette::VehicleJourney, :type => :model do        let!( :footnote_first) {create( :footnote, :code => "1", :label => "dummy 1", :line => route.line)}        let!( :footnote_second) {create( :footnote, :code => "2", :label => "dummy 2", :line => route.line)} -        it "should update vehicle's footnotes" do          expect(Chouette::VehicleJourney.find(subject.id).footnotes).to be_empty          subject.footnote_ids = [ footnote_first.id ] @@ -876,4 +876,54 @@ describe Chouette::VehicleJourney, :type => :model do        end      end    end + +  describe "#fill_passing_time_at_borders" do +    before do +      start = create :stop_area +      border = create :stop_area, kind: :non_commercial, area_type: :border +      border_2 = create :stop_area, kind: :non_commercial, area_type: :border +      middle = create :stop_area +      border_3 = create :stop_area, kind: :non_commercial, area_type: :border +      border_4 = create :stop_area, kind: :non_commercial, area_type: :border +      _end = create :stop_area +      journey_pattern = create :journey_pattern +      journey_pattern.stop_points.destroy_all +      journey_pattern.stop_points << start_point = create(:stop_point, stop_area: start, position: 0) +      journey_pattern.stop_points << border_point = create(:stop_point, stop_area: border, position: 1) +      journey_pattern.stop_points << border_point_2 = create(:stop_point, stop_area: border_2, position: 2) +      journey_pattern.stop_points << middle_point = create(:stop_point, stop_area: middle, position: 3) +      journey_pattern.stop_points << border_point_3 = create(:stop_point, stop_area: border_3, position: 4) +      journey_pattern.stop_points << border_point_4 = create(:stop_point, stop_area: border_4, position: 5) +      journey_pattern.stop_points << end_point = create(:stop_point, stop_area: _end, position: 6) +      journey_pattern.update_attribute :costs, { +        "#{start_point.stop_area_id}-#{border_point.stop_area_id}" => {distance: 50}, +        "#{border_point.stop_area_id}-#{border_point_2.stop_area_id}" => {distance: 0}, +        "#{border_point_2.stop_area_id}-#{middle_point.stop_area_id}" => {distance: 100}, +        "#{middle_point.stop_area_id}-#{border_point_3.stop_area_id}" => {distance: 100}, +        "#{border_point_3.stop_area_id}-#{border_point_4.stop_area_id}" => {distance: 0}, +        "#{border_point_4.stop_area_id}-#{end_point.stop_area_id}" => {distance: 100} +      } +      @journey = create :vehicle_journey, journey_pattern: journey_pattern +      @journey.vehicle_journey_at_stops.destroy_all +      @start = create :vehicle_journey_at_stop, stop_point: start_point, vehicle_journey: @journey +      @target = create :vehicle_journey_at_stop, stop_point: border_point, vehicle_journey: @journey, arrival_time: nil, departure_time: nil +      @target_2 = create :vehicle_journey_at_stop, stop_point: border_point_2, vehicle_journey: @journey, arrival_time: nil, departure_time: nil +      @middle = create :vehicle_journey_at_stop, stop_point: middle_point, vehicle_journey: @journey, arrival_time: @start.arrival_time + 4.hours, departure_time: @start.departure_time + 4.hours +      @target_3 = create :vehicle_journey_at_stop, stop_point: border_point_3, vehicle_journey: @journey, arrival_time: nil, departure_time: nil +      @target_4 = create :vehicle_journey_at_stop, stop_point: border_point_4, vehicle_journey: @journey, arrival_time: nil, departure_time: nil +      @end = create :vehicle_journey_at_stop, stop_point: end_point, vehicle_journey: @journey, arrival_time: @middle.arrival_time + 4.hours, departure_time: @middle.departure_time + 4.hours +    end + +    it "should compute passing time" do +      @journey.reload.fill_passing_time_at_borders +      expect(@target.reload.arrival_time.to_i).to eq (@start.reload.departure_time + 1.0/3 * (@middle.reload.arrival_time - @start.departure_time)).to_i +      expect(@target_2.reload.arrival_time).to eq @target.arrival_time +      expect(@target.departure_time).to eq @target.arrival_time +      expect(@target_2.departure_time).to eq @target.arrival_time +      expect(@target_3.reload.arrival_time.to_i).to eq (@middle.reload.departure_time + 0.5 * (@end.reload.arrival_time - @middle.departure_time)).to_i +      expect(@target_4.reload.arrival_time).to eq @target_3.arrival_time +      expect(@target_3.departure_time).to eq @target_3.arrival_time +      expect(@target_4.departure_time).to eq @target_3.arrival_time +    end +  end  end diff --git a/spec/models/compliance_check_set_spec.rb b/spec/models/compliance_check_set_spec.rb index 61421287a..b6f854829 100644 --- a/spec/models/compliance_check_set_spec.rb +++ b/spec/models/compliance_check_set_spec.rb @@ -12,7 +12,7 @@ RSpec.describe ComplianceCheckSet, type: :model do    it { should have_many :compliance_checks }    it { should have_many :compliance_check_blocks } -  it { is_expected.to be_versioned } +      describe "#update_status" do      it "updates :status to successful when all resources are OK" do diff --git a/spec/models/compliance_control_block_spec.rb b/spec/models/compliance_control_block_spec.rb index 4abe0ed9c..089d78434 100644 --- a/spec/models/compliance_control_block_spec.rb +++ b/spec/models/compliance_control_block_spec.rb @@ -17,4 +17,16 @@ RSpec.describe ComplianceControlBlock, type: :model do    it { should_not allow_values( *%w{ demandResponseBus nightus irportLinkBus highrequencyBus expressBUs                                       Shuttle suburban regioalRail interregion4lRail })          .for(:transport_submode) } + +  context "transport mode & submode uniqueness" do +    let(:cc_block) {create :compliance_control_block, transport_mode: 'bus', transport_submode: 'nightBus'} +    let(:cc_set1) { cc_block.compliance_control_set } +    let(:cc_set2) { create :compliance_control_set }      + +  it "sould be unique in a compliance control set" do +    expect( ComplianceControlBlock.new(transport_mode: 'bus', transport_submode: 'nightBus', compliance_control_set: cc_set1) ).not_to be_valid +    expect( ComplianceControlBlock.new(transport_mode: 'bus', transport_submode: 'nightBus', compliance_control_set: cc_set2) ).to be_valid +  end + +end  end diff --git a/spec/models/compliance_control_set_spec.rb b/spec/models/compliance_control_set_spec.rb index c157dcaf3..a66e7f030 100644 --- a/spec/models/compliance_control_set_spec.rb +++ b/spec/models/compliance_control_set_spec.rb @@ -10,5 +10,5 @@ RSpec.describe ComplianceControlSet, type: :model do    it { should have_many(:compliance_control_blocks).dependent(:destroy) }    it { should validate_presence_of :name } -  it { is_expected.to be_versioned } +    end diff --git a/spec/models/custom_field_spec.rb b/spec/models/custom_field_spec.rb index b92bcfbdb..ce6ce9fa5 100644 --- a/spec/models/custom_field_spec.rb +++ b/spec/models/custom_field_spec.rb @@ -1,6 +1,7 @@  require 'rails_helper'  RSpec.describe CustomField, type: :model do +    let( :vj ){ create :vehicle_journey, custom_field_values: {energy: 99} }    context "validates" do @@ -35,11 +36,46 @@ RSpec.describe CustomField, type: :model do    end    context "custom field_values for a resource" do +    before do +      create :custom_field, field_type: :integer, code: :energy, name: :energy +    end +      it { expect(vj.custom_field_value("energy")).to eq(99) }    end +  context "with a 'list' field_type" do +    let!(:field){ [create(:custom_field, code: :energy, field_type: 'list', options: {list_values: %w(foo bar baz)})] } +    let!( :vj ){ create :vehicle_journey, custom_field_values: {energy: "1"} } +    it "should cast the value" do +      expect(vj.custom_fields[:energy].value).to eq 1 +      expect(vj.custom_fields[:energy].display_value).to eq "bar" +    end + +    it "should not break initailizartion if the model does not have the :custom_field_values attribute" do +      expect{Chouette::VehicleJourney.where(id: vj.id).select(:id).last}.to_not raise_error +    end + +    it "should validate the value" do +      { +        "1" => true, +        1 => true, +        "azerty" => false, +        "10" => false, +        10 => false +      }.each do |val, valid| +        vj = build :vehicle_journey, custom_field_values: {energy: val} +        if valid +          expect(vj.validate).to be_truthy +        else +          expect(vj.validate).to be_falsy +          expect(vj.errors.messages[:"custom_fields.energy"]).to be_present +        end +      end +    end +  end +    context "with an 'integer' field_type" do -    let!(:field){ [create(:custom_field, code: :energy, options: {field_type: 'integer'})] } +    let!(:field){ [create(:custom_field, code: :energy, field_type: 'integer')] }      let!( :vj ){ create :vehicle_journey, custom_field_values: {energy: "99"} }      it "should cast the value" do        expect(vj.custom_fields[:energy].value).to eq 99 @@ -47,6 +83,7 @@ RSpec.describe CustomField, type: :model do      it "should validate the value" do        { +        99 => true,          "99" => true,          "azerty" => false,          "91a" => false, @@ -64,10 +101,39 @@ RSpec.describe CustomField, type: :model do    end    context "with a 'string' field_type" do -    let!(:field){ [create(:custom_field, code: :energy, options: {field_type: 'string'})] } +    let!(:field){ [create(:custom_field, code: :energy, field_type: 'string')] }      let!( :vj ){ create :vehicle_journey, custom_field_values: {energy: 99} }      it "should cast the value" do        expect(vj.custom_fields[:energy].value).to eq '99'      end    end + +  context "with a 'attachment' field_type" do +    let!(:field){ [create(:custom_field, code: :energy, field_type: 'attachment')] } +    let( :vj ){ create :vehicle_journey, custom_field_values: {energy: File.open(Rails.root.join('spec', 'fixtures', 'users.json'))} } +    it "should cast the value" do +      expect(vj.custom_fields[:energy].value.class).to be CustomFieldAttachmentUploader +      path = vj.custom_fields[:energy].value.path +      expect(File.exists?(path)).to be_truthy +      expect(vj).to receive(:remove_custom_field_energy!).and_call_original +      vj.destroy +      vj.run_callbacks(:commit) +      expect(File.exists?(path)).to be_falsy +    end + +    it "should display a link" do +      val = vj.custom_fields[:energy].value +      out = vj.custom_fields[:energy].display_value +      expect(out).to match(val.url) +      expect(out).to match(/\<a.*\>/) +    end + +    context "with a whitelist" do +      let!(:field){ [create(:custom_field, code: :energy, field_type: 'attachment', options: {extension_whitelist: %w(zip)})] } +      it "should validate extension" do +        expect(build(:vehicle_journey, custom_field_values: {energy: File.open(Rails.root.join('spec', 'fixtures', 'users.json'))})).to_not be_valid +        expect(build(:vehicle_journey, custom_field_values: {energy: File.open(Rails.root.join('spec', 'fixtures', 'nozip.zip'))})).to be_valid +      end +    end +  end  end diff --git a/spec/models/import/gtfs_spec.rb b/spec/models/import/gtfs_spec.rb new file mode 100644 index 000000000..b4b23be00 --- /dev/null +++ b/spec/models/import/gtfs_spec.rb @@ -0,0 +1,283 @@ +require "rails_helper" + +RSpec.describe Import::Gtfs do + +  let(:referential) do +    create :referential do |referential| +      referential.line_referential.objectid_format = "netex" +      referential.stop_area_referential.objectid_format = "netex" +    end +  end + +  let(:workbench) do +    create :workbench do |workbench| +      workbench.line_referential.objectid_format = "netex" +      workbench.stop_area_referential.objectid_format = "netex" +    end +  end + +  def create_import(file) +    Import::Gtfs.new workbench: workbench, local_file: fixtures_path(file) +  end + +  describe "#import_agencies" do +    let(:import) { create_import "google-sample-feed.zip" } +    it "should create a company for each agency" do +      import.import_agencies + +      expect(workbench.line_referential.companies.pluck(:registration_number, :name)).to eq([["DTA","Demo Transit Authority"]]) +    end +  end + +  describe "#import_stops" do +    let(:import) { create_import "google-sample-feed.zip" } +    it "should create a company for each agency" do +      import.import_stops + +      defined_attributes = [ +        :registration_number, :name, :parent_id, :latitude, :longitude +      ] +      expected_attributes = [ +        ["AMV", "Amargosa Valley (Demo)", nil, 36.641496, -116.40094], +        ["EMSI", "E Main St / S Irving St (Demo)", nil, 36.905697, -116.76218], +        ["DADAN", "Doing Ave / D Ave N (Demo)", nil, 36.909489, -116.768242], +        ["NANAA", "North Ave / N A Ave (Demo)", nil, 36.914944, -116.761472], +        ["NADAV", "North Ave / D Ave N (Demo)", nil, 36.914893, -116.76821], +        ["STAGECOACH", "Stagecoach Hotel & Casino (Demo)", nil, 36.915682, -116.751677], +        ["BULLFROG", "Bullfrog (Demo)", nil, 36.88108, -116.81797], +        ["BEATTY_AIRPORT", "Nye County Airport (Demo)", nil, 36.868446, -116.784582], +        ["FUR_CREEK_RES", "Furnace Creek Resort (Demo)", nil, 36.425288, -117.133162] +      ] + +      expect(workbench.stop_area_referential.stop_areas.pluck(*defined_attributes)).to match_array(expected_attributes) +    end +  end + +  describe "#import_routes" do +    let(:import) { create_import "google-sample-feed.zip" } +    it "should create a line for each route" do +      import.import_routes + +      defined_attributes = [ +        :registration_number, :name, :number, :published_name, +        "companies.registration_number", +        :comment, :url +      ] +      expected_attributes = [ +        ["AAMV", "Airport - Amargosa Valley", "50", "Airport - Amargosa Valley", nil, nil, nil], +        ["CITY", "City", "40", "City", nil, nil, nil], +        ["STBA", "Stagecoach - Airport Shuttle", "30", "Stagecoach - Airport Shuttle", nil, nil, nil], +        ["BFC", "Bullfrog - Furnace Creek Resort", "20", "Bullfrog - Furnace Creek Resort", nil, nil, nil], +        ["AB", "Airport - Bullfrog", "10", "Airport - Bullfrog", nil, nil, nil] +      ] + +      expect(workbench.line_referential.lines.includes(:company).pluck(*defined_attributes)).to match_array(expected_attributes) +    end +  end + +  describe "#import_trips" do +    let(:import) { create_import "google-sample-feed.zip" } +    before do +      import.prepare_referential +      import.import_calendars +    end + +    it "should create a Route for each trip" do +      import.import_trips + +      defined_attributes = [ +        "lines.registration_number", :wayback, :name, :published_name +      ] +      expected_attributes = [ +        ["AB", "outbound", "to Bullfrog", "to Bullfrog"], +        ["AB", "inbound", "to Airport", "to Airport"], +        ["STBA", "inbound", "Shuttle", "Shuttle"], +        ["CITY", "outbound", "Outbound", "Outbound"], +        ["CITY", "inbound", "Inbound", "Inbound"], +        ["BFC", "outbound", "to Furnace Creek Resort", "to Furnace Creek Resort"], +        ["BFC", "inbound", "to Bullfrog", "to Bullfrog"], +        ["AAMV", "outbound", "to Amargosa Valley", "to Amargosa Valley"], +        ["AAMV", "inbound", "to Airport", "to Airport"], +        ["AAMV", "outbound", "to Amargosa Valley", "to Amargosa Valley"], +        ["AAMV", "inbound", "to Airport", "to Airport"] +      ] + +      expect(import.referential.routes.includes(:line).pluck(*defined_attributes)).to match_array(expected_attributes) +    end + +    it "should create a JourneyPattern for each trip" do +      import.import_trips + +      defined_attributes = [ +        :name +      ] +      expected_attributes = [ +        "to Bullfrog", "to Airport", "Shuttle", "Outbound", "Inbound", "to Furnace Creek Resort", "to Bullfrog", "to Amargosa Valley", "to Airport", "to Amargosa Valley", "to Airport" +      ] + +      expect(import.referential.journey_patterns.pluck(*defined_attributes)).to match_array(expected_attributes) +    end + +    it "should create a VehicleJourney for each trip" do +      import.import_trips + +      defined_attributes = ->(v) { +        [v.published_journey_name, v.time_tables.first&.comment] +      } +      expected_attributes = [ +        ["to Bullfrog", "Calendar FULLW"], +        ["to Airport", "Calendar FULLW"], +        ["Shuttle", "Calendar FULLW"], +        ["CITY1", "Calendar FULLW"], +        ["CITY2", "Calendar FULLW"], +        ["to Furnace Creek Resort", "Calendar FULLW"], +        ["to Bullfrog", "Calendar FULLW"], +        ["to Amargosa Valley", "Calendar WE"], +        ["to Airport", "Calendar WE"], +        ["to Amargosa Valley", "Calendar WE"], +        ["to Airport", "Calendar WE"] +      ] + +      expect(import.referential.vehicle_journeys.map(&defined_attributes)).to match_array(expected_attributes) +    end +  end + +  describe "#import_stop_times" do +    let(:import) { create_import "google-sample-feed.zip" } + +    before do +      import.prepare_referential +      import.import_calendars +      import.import_trips +    end + +    it "should create a VehicleJourneyAtStop for each stop_time" do +      import.import_stop_times + +      def t(value) +        Time.parse(value) +      end + +      defined_attributes = [ +        "stop_areas.registration_number", :position, :departure_time, :arrival_time, +      ] +      expected_attributes = [ +        ["STAGECOACH", 0, t("2000-01-01 06:00:00 UTC"), t("2000-01-01 06:00:00 UTC")], +        ["BEATTY_AIRPORT", 1, t("2000-01-01 06:20:00 UTC"), t("2000-01-01 06:20:00 UTC")], +        ["STAGECOACH", 0, t("2000-01-01 06:00:00 UTC"), t("2000-01-01 06:00:00 UTC")], +        ["NANAA", 1, t("2000-01-01 06:07:00 UTC"), t("2000-01-01 06:05:00 UTC")], +        ["NADAV", 2, t("2000-01-01 06:14:00 UTC"), t("2000-01-01 06:12:00 UTC")], +        ["DADAN", 3, t("2000-01-01 06:21:00 UTC"), t("2000-01-01 06:19:00 UTC")], +        ["EMSI", 4, t("2000-01-01 06:28:00 UTC"), t("2000-01-01 06:26:00 UTC")], +        ["EMSI", 0, t("2000-01-01 06:30:00 UTC"), t("2000-01-01 06:28:00 UTC")], +        ["DADAN", 1, t("2000-01-01 06:37:00 UTC"), t("2000-01-01 06:35:00 UTC")], +        ["NADAV", 2, t("2000-01-01 06:44:00 UTC"), t("2000-01-01 06:42:00 UTC")], +        ["NANAA", 3, t("2000-01-01 06:51:00 UTC"), t("2000-01-01 06:49:00 UTC")], +        ["STAGECOACH", 4, t("2000-01-01 06:58:00 UTC"), t("2000-01-01 06:56:00 UTC")], +        ["BEATTY_AIRPORT", 0, t("2000-01-01 08:00:00 UTC"), t("2000-01-01 08:00:00 UTC")], +        ["BULLFROG", 1, t("2000-01-01 08:15:00 UTC"), t("2000-01-01 08:10:00 UTC")], +        ["BULLFROG", 0, t("2000-01-01 12:05:00 UTC"), t("2000-01-01 12:05:00 UTC")], +        ["BEATTY_AIRPORT", 1, t("2000-01-01 12:15:00 UTC"), t("2000-01-01 12:15:00 UTC")], +        ["BULLFROG", 0, t("2000-01-01 08:20:00 UTC"), t("2000-01-01 08:20:00 UTC")], +        ["FUR_CREEK_RES", 1, t("2000-01-01 09:20:00 UTC"), t("2000-01-01 09:20:00 UTC")], +        ["FUR_CREEK_RES", 0, t("2000-01-01 11:00:00 UTC"), t("2000-01-01 11:00:00 UTC")], +        ["BULLFROG", 1, t("2000-01-01 12:00:00 UTC"), t("2000-01-01 12:00:00 UTC")], +        ["BEATTY_AIRPORT", 0, t("2000-01-01 08:00:00 UTC"), t("2000-01-01 08:00:00 UTC")], +        ["AMV", 1, t("2000-01-01 09:00:00 UTC"), t("2000-01-01 09:00:00 UTC")], +        ["AMV", 0, t("2000-01-01 10:00:00 UTC"), t("2000-01-01 10:00:00 UTC")], +        ["BEATTY_AIRPORT", 1, t("2000-01-01 11:00:00 UTC"), t("2000-01-01 11:00:00 UTC")], +        ["BEATTY_AIRPORT", 0, t("2000-01-01 13:00:00 UTC"), t("2000-01-01 13:00:00 UTC")], +        ["AMV", 1, t("2000-01-01 14:00:00 UTC"), t("2000-01-01 14:00:00 UTC")], +        ["AMV", 0, t("2000-01-01 15:00:00 UTC"), t("2000-01-01 15:00:00 UTC")], +        ["BEATTY_AIRPORT", 1, t("2000-01-01 16:00:00 UTC"), t("2000-01-01 16:00:00 UTC")] +      ] +      expect(referential.vehicle_journey_at_stops.includes(stop_point: :stop_area).pluck(*defined_attributes)).to match_array(expected_attributes) +    end +  end + +  describe "#import_calendars" do +    let(:import) { create_import "google-sample-feed.zip" } + +    before do +      import.prepare_referential +    end + +    it "should create a Timetable for each calendar" do +      import.import_calendars + +      def d(value) +        Date.parse(value) +      end + +      defined_attributes = ->(t) { +        [t.comment, t.valid_days, t.periods.first.period_start, t.periods.first.period_end] +      } +      expected_attributes = [ +        ["Calendar FULLW", [1, 2, 3, 4, 5, 6, 7], d("Mon, 01 Jan 2007"), d("Fri, 31 Dec 2010")], +        ["Calendar WE", [6, 7], d("Mon, 01 Jan 2007"), d("Fri, 31 Dec 2010")] +      ] +      expect(referential.time_tables.map(&defined_attributes)).to match_array(expected_attributes) +    end +  end + +  describe "#import_calendar_dates" do +    let(:import) { create_import "google-sample-feed.zip" } + +    before do +      import.prepare_referential +      import.import_calendars +    end + +    it "should create a Timetable::Date for each calendar date" do +      import.import_calendar_dates + +      def d(value) +        Date.parse(value) +      end + +      defined_attributes = ->(d) { +        [d.time_table.comment, d.date, d.in_out] +      } +      expected_attributes = [ +        ["Calendar FULLW", d("Mon, 04 Jun 2007"), false] +      ] +      expect(referential.time_table_dates.map(&defined_attributes)).to match_array(expected_attributes) +    end +  end + +  describe "#download_local_file" do +    let(:file) { "google-sample-feed.zip" } +    let(:import) do +      Import::Gtfs.create! name: "GTFS test", creator: "Test", workbench: workbench, file: open_fixture(file), download_host: "rails_host" +    end + +    let(:download_url) { "#{import.download_host}/workbenches/#{import.workbench_id}/imports/#{import.id}/download?token=#{import.token_download}" } + +    before do +      stub_request(:get, download_url).to_return(status: 200, body: read_fixture(file)) +    end + +    it "should download local_file" do +      expect(File.read(import.download_local_file)).to eq(read_fixture(file)) +    end +  end + +  describe "#download_host" do +    it "should return host defined by Rails.application.config.rails_host (without http:// schema)" do +      allow(Rails.application.config).to receive(:rails_host).and_return("http://download_host") + +      expect(Import::Gtfs.new.download_host).to eq("download_host") +    end +  end + +  describe "#download_path" do +    let(:file) { "google-sample-feed.zip" } +    let(:import) do +      Import::Gtfs.create! name: "GTFS test", creator: "Test", workbench: workbench, file: open_fixture(file), download_host: "rails_host" +    end + +    it "should return the pathwith the token" do +      expect(import.download_path).to eq("/workbenches/#{import.workbench_id}/imports/#{import.id}/download?token=#{import.token_download}") +    end +  end +end diff --git a/spec/models/referential_metadata_spec.rb b/spec/models/referential_metadata_spec.rb index 291ed974a..88a12b2bb 100644 --- a/spec/models/referential_metadata_spec.rb +++ b/spec/models/referential_metadata_spec.rb @@ -12,14 +12,19 @@ RSpec.describe ReferentialMetadata, :type => :model do    describe ".new_from" do      let(:referential_metadata) { create :referential_metadata, referential_source: create(:referential) } -    let(:new_referential_metadata) { ReferentialMetadata.new_from(referential_metadata, []) } +    let(:new_referential_metadata) { ReferentialMetadata.new_from(referential_metadata, nil) } +    before do +      referential_metadata.line_ids.each do |id| +        Chouette::Line.find(id).update_attribute :line_referential_id, referential_metadata.referential.line_referential_id +      end +    end      it "should not have an associated referential" do        expect(new_referential_metadata).to be_a_new(ReferentialMetadata)      end -    xit "should have the same lines" do -      expect(new_referential_metadata.lines).to eq(referential_metadata.lines) +    it "should have the same lines" do +      expect(new_referential_metadata.line_ids.sort).to eq(referential_metadata.line_ids.sort)      end      it "should have the same periods" do @@ -34,6 +39,14 @@ RSpec.describe ReferentialMetadata, :type => :model do        expect(new_referential_metadata.referential_source).to eq(referential_metadata.referential)      end +    context "with a functional scope" do +      let(:organisation){ create :organisation, sso_attributes: {"functional_scope" => [referential_metadata.referential.lines.first.objectid]} } +      let(:new_referential_metadata) { ReferentialMetadata.new_from(referential_metadata, organisation) } + +      it "should scope the lines" do +        expect(new_referential_metadata.line_ids).to eq [referential_metadata.referential.lines.first.id] +      end +    end    end    describe "Period" do diff --git a/spec/models/referential_spec.rb b/spec/models/referential_spec.rb index 1d9b3d78a..ca2caf57f 100644 --- a/spec/models/referential_spec.rb +++ b/spec/models/referential_spec.rb @@ -55,7 +55,7 @@ describe Referential, :type => :model do    context "Cloning referential" do      let(:clone) do -      Referential.new_from(ref, []) +      Referential.new_from(ref, nil)      end      let!(:workbench){ create :workbench } diff --git a/spec/models/route_spec.rb b/spec/models/route_spec.rb new file mode 100644 index 000000000..b407cd866 --- /dev/null +++ b/spec/models/route_spec.rb @@ -0,0 +1,68 @@ +require 'spec_helper' + +RSpec.describe Chouette::Route, :type => :model do +  subject(:route){ create :route } +  context "metadatas" do +    it "should be empty at first" do +      expect(Chouette::Route.has_metadata?).to be_truthy +      expect(route.has_metadata?).to be_truthy +      expect(route.metadata.creator_username).to be_nil +      expect(route.metadata.modifier_username).to be_nil +    end + +    context "once set" do +      it "should set the correct values" do +        Timecop.freeze(Time.now) do +          route.metadata.creator_username = "john.doe" +          route.save! +          id = route.id +          route = Chouette::Route.find id +          expect(route.metadata.creator_username).to eq "john.doe" +          expect(route.metadata.creator_username_updated_at.strftime('%Y-%m-%d %H:%M:%S.%3N')).to eq Time.now.strftime('%Y-%m-%d %H:%M:%S.%3N') +        end +      end +    end + +    describe "#merge_metadata_from" do +      let(:source){ create :route } +      let(:metadata){ target.merge_metadata_from(source).metadata } +      let(:target){ create :route } +      before do +        target.metadata.creator_username = "john" +        target.metadata.modifier_username = "john" +      end +      context "when the source has no metadata" do +        it "should do nothing" do +          expect(metadata.creator_username).to eq "john" +          expect(metadata.modifier_username).to eq "john" +        end +      end + +      context "when the source has older metadata" do +        before do +          source.metadata.creator_username = "jane" +          source.metadata.modifier_username = "jane" +          source.metadata.creator_username_updated_at = 1.month.ago +          source.metadata.modifier_username_updated_at = 1.month.ago +        end +        it "should do nothing" do +          expect(metadata.creator_username).to eq "john" +          expect(metadata.modifier_username).to eq "john" +        end +      end + +      context "when the source has new metadata" do +        before do +          source.metadata.creator_username = "jane" +          source.metadata.modifier_username = "jane" +          target.metadata.creator_username_updated_at = 1.month.ago +          target.metadata.modifier_username_updated_at = 1.month.ago +        end +        it "should update metadata" do +          expect(metadata.creator_username).to eq "jane" +          expect(metadata.modifier_username).to eq "jane" +        end +      end +    end +  end +end diff --git a/spec/models/simple_json_exporter_spec.rb b/spec/models/simple_json_exporter_spec.rb index 4b48dc732..afecf7e51 100644 --- a/spec/models/simple_json_exporter_spec.rb +++ b/spec/models/simple_json_exporter_spec.rb @@ -5,7 +5,7 @@ RSpec.describe SimpleJsonExporter do          SimpleJsonExporter.define :foo          expect do            SimpleJsonExporter.new(configuration_name: :test).export -        end.to raise_error +        end.to raise_error(RuntimeError)        end      end      context "with a complete configuration" do @@ -18,9 +18,9 @@ RSpec.describe SimpleJsonExporter do        it "should define an exporter" do          expect{SimpleJsonExporter.find_configuration(:foo)}.to_not raise_error          expect{SimpleJsonExporter.new(configuration_name: :foo, filepath: "").export}.to_not raise_error -        expect{SimpleJsonExporter.find_configuration(:bar)}.to raise_error -        expect{SimpleJsonExporter.new(configuration_name: :bar, filepath: "")}.to raise_error -        expect{SimpleJsonExporter.new(configuration_name: :bar, filepath: "").export}.to raise_error +        expect{SimpleJsonExporter.find_configuration(:bar)}.to raise_error(RuntimeError) +        expect{SimpleJsonExporter.new(configuration_name: :bar, filepath: "")}.to raise_error(RuntimeError) +        expect{SimpleJsonExporter.new(configuration_name: :bar, filepath: "").export}.to raise_error(RuntimeError)          expect{SimpleJsonExporter.create(configuration_name: :foo, filepath: "")}.to change{SimpleJsonExporter.count}.by 1        end      end @@ -33,7 +33,7 @@ RSpec.describe SimpleJsonExporter do              config.add_field :name              config.add_field :name            end -        end.to raise_error +        end.to raise_error(RuntimeError)        end      end    end diff --git a/spec/services/route_way_cost_calculator_spec.rb b/spec/services/route_way_cost_calculator_spec.rb index d5358fcf6..79b81e34d 100644 --- a/spec/services/route_way_cost_calculator_spec.rb +++ b/spec/services/route_way_cost_calculator_spec.rb @@ -7,18 +7,20 @@ RSpec.describe RouteWayCostCalculator do        # things in the request or response. This is just to fake the request so        # we don't actually call their API in tests. The test doesn't test        # anything given in the response. -      stub_request(:post, "https://api.tomtom.com/routing/1/batch/json?key") +      stub_request( +        :post, +        "https://api.tomtom.com/routing/1/matrix/json?key&routeType=shortest&travelMode=bus" +      )          .with(            headers: {              'Accept'=>'*/*',              'Accept-Encoding'=>'gzip;q=1.0,deflate;q=0.6,identity;q=0.3',              'Content-Type'=>'application/json',              'User-Agent'=>'Faraday v0.9.2' -          } -        ) +          })          .to_return(            status: 200, -          body: "{\"formatVersion\":\"0.0.1\",\"batchItems\":[{\"statusCode\":200,\"response\":{\"routes\":[{\"summary\":{\"lengthInMeters\":117947,\"travelTimeInSeconds\":7969,\"trafficDelayInSeconds\":0,\"departureTime\":\"2018-03-12T12:32:26+01:00\",\"arrivalTime\":\"2018-03-12T14:45:14+01:00\"}}]}}]}", +          body: "{\"formatVersion\":\"0.0.1\",\"matrix\":[[{\"statusCode\":200,\"response\":{\"routeSummary\":{\"lengthInMeters\":0,\"travelTimeInSeconds\":0,\"trafficDelayInSeconds\":0,\"departureTime\":\"2018-03-23T11:20:17+01:00\",\"arrivalTime\":\"2018-03-23T11:20:17+01:00\"}}}]]}",            headers: {}          ) diff --git a/spec/services/zip_service_spec.rb b/spec/services/zip_service_spec.rb index 1eadaa3bf..540de5bfc 100644 --- a/spec/services/zip_service_spec.rb +++ b/spec/services/zip_service_spec.rb @@ -42,6 +42,24 @@ RSpec.describe ZipService, type: :zip do      end    end +  context 'one referential without calendar' do +    let( :zip_name ){ 'one_referential_no_calendar.zip' } +    let( :zip_content ){ first_referential_no_calendar_data } + +    it 'returns a not ok object' do +      expect_incorrect_subdir subject.first, expected_missing_calendar: true +    end +  end + +  context 'one referential with an unparsable calendar' do +    let( :zip_name ){ 'one_referential_unparsable_calendar.zip' } +    let( :zip_content ){ first_referential_unparsable_calendar_data } + +    it 'returns a not ok object' do +      expect_incorrect_subdir subject.first, expected_wrong_calendar: true +    end +  end +    context 'one referential with a foreign line' do      let( :zip_name ){ 'one_referential_foreign.zip' }      let( :zip_content ){ first_referential_foreign_data } @@ -109,17 +127,29 @@ RSpec.describe ZipService, type: :zip do      end    end -  def expect_incorrect_subdir subdir, expected_spurious: [], expected_foreign_lines: [] +  def expect_incorrect_subdir subdir, expected_spurious: [], expected_foreign_lines: [], expected_missing_calendar: false, expected_wrong_calendar: false      expect( subdir ).not_to be_ok      expect( subdir.foreign_lines ).to eq(expected_foreign_lines)      expect( subdir.spurious ).to eq(expected_spurious) +    expect( subdir.missing_calendar ).to eq(expected_missing_calendar) +    expect( subdir.wrong_calendar ).to eq(expected_wrong_calendar)    end    # Data    # ---- +  let :valid_calendar do +    """ +    <netex:PublicationDelivery xmlns:netex=\"http://www.netex.org.uk/netex\"> +      <netex:ValidBetween> +        <netex:FromDate>2017-03-01</netex:FromDate> +        <netex:ToDate>2017-03-31</netex:ToDate> +      </netex:ValidBetween> +    </netex:PublicationDelivery> +    """ +  end    let :first_referential_ok_data do      { -       'Referential1/calendriers.xml'     => 'calendriers', +       'Referential1/calendriers.xml'     => valid_calendar,         'Referential1/commun.xml'          => 'common',         'Referential1/offre_C00108_9.xml'  => 'line 108 ref 1',         'Referential1/offre_C00109_10.xml' => 'line 109 ref 1' @@ -127,7 +157,7 @@ RSpec.describe ZipService, type: :zip do    end    let :first_referential_foreign_data do      { -       'Referential2/calendriers.xml'     => 'calendriers', +       'Referential2/calendriers.xml'     => valid_calendar,         'Referential2/commun.xml'          => 'common',         'Referential2/offre_C00110_11.xml' => 'foreign line ref 1',         'Referential2/offre_C00108_9.xml'  => 'line 108 ref 1', @@ -136,7 +166,7 @@ RSpec.describe ZipService, type: :zip do    end    let :first_referential_spurious_data do      { -       'Referential3/calendriers.xml'     => 'calendriers', +       'Referential3/calendriers.xml'     => valid_calendar,         'Referential3/commun.xml'          => 'common',         'Referential3/SPURIOUS/commun.xml' => 'common',         'Referential3/offre_C00108_9.xml'  => 'line 108 ref 1', @@ -145,7 +175,7 @@ RSpec.describe ZipService, type: :zip do    end    let :second_referential_ok_data do      { -       'Referential4/calendriers.xml'     => 'calendriers', +       'Referential4/calendriers.xml'     => valid_calendar,         'Referential4/commun.xml'          => 'common',         'Referential4/offre_C00108_9.xml'  => 'line 108 ref 2',         'Referential4/offre_C00109_10.xml' => 'line 109 ref 2' @@ -153,7 +183,7 @@ RSpec.describe ZipService, type: :zip do    end    let :messed_up_referential_data do      { -       'Referential5/calendriers.xml'      => 'calendriers', +       'Referential5/calendriers.xml'      => valid_calendar,         'Referential5/commun.xml'           => 'common',         'Referential5/SPURIOUS1/commun.xml' => 'common',         'Referential5/SPURIOUS2/commun.xml' => 'common', @@ -163,5 +193,21 @@ RSpec.describe ZipService, type: :zip do         'Referential5/offre_C00109_10.xml'  => 'line 109 ref 1'      }    end +  let :first_referential_no_calendar_data do +    { +       'Referential6/commun.xml'          => 'common', +       'Referential6/offre_C00108_9.xml'  => 'line 108 ref 1', +       'Referential6/offre_C00109_10.xml' => 'line 109 ref 1' +    } +  end +  let :first_referential_unparsable_calendar_data do +    { +       'Referential7/calendriers.xml'     => 'calendriers', +       'Referential7/commun.xml'          => 'common', +       'Referential7/offre_C00108_9.xml'  => 'line 108 ref 1', +       'Referential7/offre_C00109_10.xml' => 'line 109 ref 1' +    } +  end +  end diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index cde252236..947efd602 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -34,7 +34,6 @@ require 'webmock/rspec'  require 'simplecov'  require 'sidekiq/testing'  Sidekiq::Testing.fake! -require 'paper_trail/frameworks/rspec'  # Requires supporting ruby files with custom matchers and macros, etc, in  # spec/support/ and its subdirectories. Files matching `spec/**/*_spec.rb` are diff --git a/spec/support/referential.rb b/spec/support/referential.rb index 9acdce73a..b50844ae4 100644 --- a/spec/support/referential.rb +++ b/spec/support/referential.rb @@ -8,6 +8,10 @@ module ReferentialHelper      Organisation.find_by!(code: "first")    end +  def first_workgroup +    Workgroup.find_by_name('IDFM') +  end +    def self.included(base)      base.class_eval do        extend ClassMethods @@ -53,10 +57,18 @@ RSpec.configure do |config|        referential.add_member organisation, owner: true      end +    workgroup = FactoryGirl.create( +      :workgroup, +      name: "IDFM", +      line_referential: line_referential, +      stop_area_referential: stop_area_referential +    ) +      workbench = FactoryGirl.create(        :workbench,        name: "Gestion de l'offre",        organisation: organisation, +      workgroup: workgroup,        line_referential: line_referential,        stop_area_referential: stop_area_referential      ) diff --git a/spec/support/shared_examples/compliance_control_validation.rb b/spec/support/shared_examples/compliance_control_validation.rb index b23c2984f..3a8232193 100644 --- a/spec/support/shared_examples/compliance_control_validation.rb +++ b/spec/support/shared_examples/compliance_control_validation.rb @@ -12,7 +12,25 @@ RSpec.shared_examples_for 'has min_max_values' do      end    end +  context "is valid" do +    it 'if minimum is well formatted' do +      subject.minimum = "12" +      expect_it.to be_valid +      subject.minimum = "12.0" +      expect_it.to be_valid +      subject.minimum = "12.01" +      expect_it.to be_valid +    end +  end +    context "is invalid" do +    it 'if minimum is not well formatted' do +      subject.minimum = "AA" +      expect_it.not_to be_valid +      subject.minimum = "12." +      expect_it.not_to be_valid +    end +      it 'if no value is provided' do        subject.minimum = nil        subject.maximum = nil diff --git a/spec/views/companies/edit.html.erb_spec.rb b/spec/views/companies/edit.html.erb_spec.rb index 8aaf705ab..c72d84c0b 100644 --- a/spec/views/companies/edit.html.erb_spec.rb +++ b/spec/views/companies/edit.html.erb_spec.rb @@ -5,7 +5,10 @@ describe "/companies/edit", :type => :view do    let!(:company) { assign(:company, create(:company)) }    let!(:companies) { Array.new(2) { create(:company) } }    let!(:line_referential) { assign :line_referential, company.line_referential } - +  before do +    allow(view).to receive(:resource){ company } +    allow(view).to receive(:current_referential){ first_referential } +  end    describe "form" do      it "should render input for name" do        render diff --git a/spec/views/companies/new.html.erb_spec.rb b/spec/views/companies/new.html.erb_spec.rb index ebb8c03c5..6c2163677 100644 --- a/spec/views/companies/new.html.erb_spec.rb +++ b/spec/views/companies/new.html.erb_spec.rb @@ -4,7 +4,10 @@ describe "/companies/new", :type => :view do    let!(:company) { assign(:company, build(:company)) }    let!(:line_referential) { assign :line_referential, company.line_referential } - +  before do +    allow(view).to receive(:resource){company} +    allow(view).to receive(:current_referential){ first_referential } +  end    describe "form" do      it "should render input for name" do diff --git a/spec/views/stop_areas/edit.html.erb_spec.rb b/spec/views/stop_areas/edit.html.erb_spec.rb index bfbb0bb55..1bdb42817 100644 --- a/spec/views/stop_areas/edit.html.erb_spec.rb +++ b/spec/views/stop_areas/edit.html.erb_spec.rb @@ -8,6 +8,7 @@ describe "/stop_areas/edit", :type => :view do    before do      allow(view).to receive(:has_feature?) +    allow(view).to receive(:resource){ stop_area }    end    describe "form" do diff --git a/spec/views/stop_areas/new.html.erb_spec.rb b/spec/views/stop_areas/new.html.erb_spec.rb index 23f7387fa..eced129e4 100644 --- a/spec/views/stop_areas/new.html.erb_spec.rb +++ b/spec/views/stop_areas/new.html.erb_spec.rb @@ -7,6 +7,7 @@ describe "/stop_areas/new", :type => :view do    before do      allow(view).to receive(:has_feature?) +    allow(view).to receive(:resource){ stop_area }    end    describe "form" do @@ -4719,6 +4719,10 @@ pn@^1.0.0:    version "1.1.0"    resolved "https://registry.yarnpkg.com/pn/-/pn-1.1.0.tgz#e2f4cef0e219f463c179ab37463e4e1ecdccbafb" +polyfill-array-includes@^1.0.0: +  version "1.0.0" +  resolved "https://registry.yarnpkg.com/polyfill-array-includes/-/polyfill-array-includes-1.0.0.tgz#3dda070475859e99d653acf06ec3622cc76f8430" +  portfinder@^1.0.9:    version "1.0.13"    resolved "https://registry.yarnpkg.com/portfinder/-/portfinder-1.0.13.tgz#bb32ecd87c27104ae6ee44b5a3ccbf0ebb1aede9" @@ -6777,10 +6781,14 @@ whatwg-encoding@^1.0.1:    dependencies:      iconv-lite "0.4.13" -whatwg-fetch@2.0.3, whatwg-fetch@>=0.10.0: +whatwg-fetch@>=0.10.0:    version "2.0.3"    resolved "https://registry.yarnpkg.com/whatwg-fetch/-/whatwg-fetch-2.0.3.tgz#9c84ec2dcf68187ff00bc64e1274b442176e1c84" +whatwg-fetch@^2.0.4: +  version "2.0.4" +  resolved "https://registry.yarnpkg.com/whatwg-fetch/-/whatwg-fetch-2.0.4.tgz#dde6a5df315f9d39991aa17621853d720b85566f" +  whatwg-url@^6.3.0:    version "6.4.0"    resolved "https://registry.yarnpkg.com/whatwg-url/-/whatwg-url-6.4.0.tgz#08fdf2b9e872783a7a1f6216260a1d66cc722e08" | 
