diff options
257 files changed, 3254 insertions, 881 deletions
| diff --git a/.gitignore b/.gitignore index acdb5e230..dd4d057ef 100644 --- a/.gitignore +++ b/.gitignore @@ -15,6 +15,8 @@  /tmp  *~  public/assets/ +public/javascripts/i18n.js +public/javascripts/translations.js  # for vim users  *.swp diff --git a/Dockerfile b/Dockerfile index 8259981f8..e484de431 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,6 +1,6 @@  FROM debian:stable-slim -ENV RAILS_ENV=production RAILS_SERVE_STATIC_FILES=true RAILS_LOG_TO_STDOUT=true +ENV RAILS_ENV=production RAILS_SERVE_STATIC_FILES=true RAILS_LOG_TO_STDOUT=true SIDEKIQ_REDIS_URL=redis://redis:6379/12  RUN apt-get update && \      apt-get install -y --no-install-recommends ruby2.3 && \ @@ -12,10 +12,10 @@ RUN apt-get update && \  COPY stif-boiv-release.tar.gz /  RUN mkdir /app && apt-get update &&\ -    apt-get -y install --no-install-recommends build-essential ruby2.3-dev libpq-dev libxml2-dev zlib1g-dev libproj-dev&& \ +    apt-get -y install --no-install-recommends build-essential ruby2.3-dev libpq-dev libxml2-dev zlib1g-dev libproj-dev libmagic1 libmagic-dev&& \      tar -C /app -zxf stif-boiv-release.tar.gz && \      cd /app && bundle install --local && \ -    apt-get -y remove build-essential ruby2.3-dev libpq-dev libxml2-dev zlib1g-dev && \ +    apt-get -y remove build-essential ruby2.3-dev libpq-dev libxml2-dev zlib1g-dev libmagic-dev&& \      apt-get clean && apt-get -y autoremove && rm -rf /var/lib/apt/lists/* && \      cd /app && rm config/database.yml && mv config/database.yml.docker config/database.yml && \      cd /app && rm config/secrets.yml && mv config/secrets.yml.docker config/secrets.yml && \ @@ -16,7 +16,7 @@ gem 'uglifier', '~> 2.7.2'  gem 'coffee-rails', '~> 4.0.0'  # Webpacker -gem 'webpacker', '3.0.2' +gem 'webpacker', '3.2.1'  # Use jquery as the JavaScript library  gem 'jquery-rails', '~> 3.1.4' # Update to v4 for Rails 4.2 @@ -103,6 +103,7 @@ gem 'will_paginate-bootstrap'  gem 'gretel'  gem 'country_select'  gem 'flag-icons-rails' +gem 'i18n-js'  # Format Output  gem 'json' @@ -141,6 +142,7 @@ gem 'rake'  gem 'devise-async'  gem 'apartment', '~> 1.0.0'  gem 'aasm' +gem 'activerecord-nulldb-adapter'  gem 'puma', '~> 3.10.0'  gem 'newrelic_rpm' @@ -176,6 +178,7 @@ group :test do    gem 'simplecov-rcov', :require => false    gem 'htmlbeautifier'    gem 'timecop' +  gem 'rspec-snapshot'  end  group :test, :development, :dev do diff --git a/Gemfile.lock b/Gemfile.lock index 805ee460d..046167e69 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -269,8 +269,10 @@ GEM      htmlbeautifier (1.3.1)      httparty (0.14.0)        multi_xml (>= 0.5.2) -    i18n (0.9.3) +    i18n (0.9.4)        concurrent-ruby (~> 1.0) +    i18n-js (3.0.4) +      i18n (~> 0.6, >= 0.6.6)      i18n-tasks (0.9.15)        activesupport (>= 4.0.2)        ast (>= 2.1.0) @@ -318,7 +320,7 @@ GEM      mime-types-data (3.2016.0521)      mimemagic (0.3.2)      mini_portile2 (2.3.0) -    minitest (5.11.2) +    minitest (5.11.3)      money (6.10.1)        i18n (>= 0.6.4, < 1.0)      multi_json (1.12.1) @@ -447,6 +449,10 @@ GEM      roo (2.7.1)        nokogiri (~> 1)        rubyzip (~> 1.1, < 2.0.0) +    rspec (3.5.0) +      rspec-core (~> 3.5.0) +      rspec-expectations (~> 3.5.0) +      rspec-mocks (~> 3.5.0)      rspec-core (3.5.4)        rspec-support (~> 3.5.0)      rspec-expectations (3.5.0) @@ -463,6 +469,8 @@ GEM        rspec-expectations (~> 3.5.0)        rspec-mocks (~> 3.5.0)        rspec-support (~> 3.5.0) +    rspec-snapshot (0.1.1) +      rspec (> 3.0.0)      rspec-support (3.5.0)      ruby-filemagic (0.7.2)      ruby-graphviz (1.2.3) @@ -553,7 +561,7 @@ GEM        json (>= 1.8, < 3.0)        parser (>= 2.3.0.7)        rainbow (>= 1.99.1, < 3.0) -    tzinfo (1.2.4) +    tzinfo (1.2.5)        thread_safe (~> 0.1)      uglifier (2.7.2)        execjs (>= 0.3.0) @@ -566,7 +574,7 @@ GEM        addressable (>= 2.3.6)        crack (>= 0.3.2)        hashdiff -    webpacker (3.0.2) +    webpacker (3.2.1)        activesupport (>= 4.2)        rack-proxy (>= 0.6.1)        railties (>= 4.2) @@ -638,6 +646,7 @@ DEPENDENCIES    gretel    has_array_of!    htmlbeautifier +  i18n-js    i18n-tasks    inherited_resources    jbuilder (~> 2.0) @@ -681,6 +690,7 @@ DEPENDENCIES    rgeo (~> 0.5.2)    roo    rspec-rails (~> 3.5.0) +  rspec-snapshot    rubyzip    sass-rails (~> 4.0.3)    sawyer (~> 0.6.0) @@ -703,7 +713,7 @@ DEPENDENCIES    transpec    uglifier (~> 2.7.2)    webmock -  webpacker (= 3.0.2) +  webpacker (= 3.2.1)    whenever!    will_paginate    will_paginate-bootstrap diff --git a/INSTALL.md b/INSTALL.md index 9e04730f7..e44b072f4 100644 --- a/INSTALL.md +++ b/INSTALL.md @@ -46,6 +46,7 @@ yarn install  bundle config build.libv8 --with-system-v8  bundle  ``` +  or  ```sh @@ -95,6 +96,46 @@ On mac/OS :  brew install postgis  ``` +<<<<<<< HEAD +### Authentication + +See `config.chouette_authentication_settings`. + +Use the database authentication or get an invitation to [STIF Portail](http://stif-portail-dev.af83.priv/). + +### Run seed + +Run : + +      bundle exec rake db:seed + +Two users are created : stif-boiv@af83.com/secret and stif-boiv+transporteur@af83.com/secret + +If you have access to STIF CodifLigne and Reflex : + +      bundle exec rake codifligne:sync +      bundle exec rake reflex:sync + +To create Referential with some data (Route, JourneyPattern, VehicleJourney, etc) : + +      bundle exec rake referential:create + +# Troubleshooting + +If PG complains about illegal type `hstore` in your tests that is probably because the shared extension is not installed, here is what to do: + +#### Check installation + +* Run tests + +      bundle exec rake spec +      bundle exec rake teaspoon + +* Start local server + +      bundle exec rails server + +=======  On debian/ubuntu system :  ```sh @@ -135,6 +176,7 @@ RAILS_ENV=test bundle exec rake db:create db:migrate  ```  #### Load seed datas +>>>>>>> master  ```sh  bundle exec rake db:seed:stif @@ -169,7 +211,7 @@ bundle exec rake referential:create  #### Run tests -#### Rspec  +#### Rspec  ```sh  bundle exec rake spec diff --git a/app/assets/javascripts/application.js b/app/assets/javascripts/application.js index 4c5aff22f..6a79f7e8e 100644 --- a/app/assets/javascripts/application.js +++ b/app/assets/javascripts/application.js @@ -22,3 +22,6 @@  //= require_directory .  // require('whatwg-fetch')  // require('babel-polyfill') +//= require "i18n" +//= require "i18n/extended" +//= require "i18n/translations" diff --git a/app/assets/javascripts/i18n/extended.coffee b/app/assets/javascripts/i18n/extended.coffee new file mode 100644 index 000000000..aeb67bd09 --- /dev/null +++ b/app/assets/javascripts/i18n/extended.coffee @@ -0,0 +1,24 @@ +#= require i18n + +decorateI18n = (_i18n)-> +  _i18n.tc = (key, opts={}) -> +    out = _i18n.t(key, opts) +    out += " " if _i18n.locale == "fr" +    out + ":" + +  _i18n.model_name = (model, opts={}) -> +    last_key = if opts.plural then "other" else "one" +    _i18n.t("activerecord.models.#{model}.#{last_key}") + +  _i18n.attribute_name = (model, attribute, opts={}) -> +    _i18n.t("activerecord.attributes.#{model}.#{attribute}") + +  _i18n.enumerize = (enumerize, key, opts={}) -> +    I18n.t("enumerize.#{enumerize}.#{key}") + +  _i18n + +module?.exports = decorateI18n + +if I18n? +  decorateI18n(I18n) diff --git a/app/assets/stylesheets/components/_tables.sass b/app/assets/stylesheets/components/_tables.sass index 35e1122f3..ef19bd538 100644 --- a/app/assets/stylesheets/components/_tables.sass +++ b/app/assets/stylesheets/components/_tables.sass @@ -9,7 +9,6 @@        font-weight: 700        border-bottom: 2px solid $darkgrey        vertical-align: middle -        > a          position: relative          display: block @@ -326,6 +325,26 @@      padding: 6px 8px      border-bottom: 2px solid rgba($grey, 0.5)      border-top: 1px solid rgba($grey, 0.5) +    text-transform: capitalize + +    .info-button +      position: absolute +      width: 20px +      height: 20px +      top: 0 +      right: 0 +      margin: 6px 8px +      button +        border: none +        background: $blue +        border-radius: 20px +        width: 100% +        height: 100% +        font-size: 12px +        line-height: 14px +        color: white +        outline: none +    .td      position: relative      padding: 6px 8px @@ -355,6 +374,8 @@      .th        text-align: right        border-top-color: transparent +      > div:not(.btn-group) +        min-height: 20px      .td > .headlined        &:before @@ -389,7 +410,7 @@        .th          > div:not(.btn-group) -          min-height: 19px +          min-height: 20px          > *:first-child            padding-right: 30px diff --git a/app/assets/stylesheets/modules/_vj_collection.sass b/app/assets/stylesheets/modules/_vj_collection.sass index d99c67bd7..d9079daa2 100644 --- a/app/assets/stylesheets/modules/_vj_collection.sass +++ b/app/assets/stylesheets/modules/_vj_collection.sass @@ -86,6 +86,20 @@          &:after            bottom: 50% + +  .table-2entries .t2e-head +    .detailed-timetables +      .fa +        margin-right: 5px +    .detailed-timetables-bt +      text-decoration: none +      .fa +        margin-right: 5px +        color: $red +        transition: transform 0.1s +      &.active .fa +        transform: rotate(180deg) +    .table-2entries .t2e-head > .td:nth-child(2) > div,    .table-2entries .t2e-head > .td:last-child > div,    .table-2entries.no_result .t2e-head > .td:last-child > div @@ -103,6 +117,54 @@        top: 50%        margin-top: -8px +  .detailed-timetables +    padding-top: 10px +    text-align: left +    margin-bottom: -5px + +    & > div +      position: relative +      border-left: 1px solid $lightgrey +      padding-left: 10px +      a +        text-decoration: none +        border: none +      &:before +        position: absolute +        left: 0px +        top: 0 +        right: -8px +        content: "" +        border-top: 1px solid $lightgrey +      font-size: 0.8em +      height: 44px +      position: relative +      padding-bottom: 5px + +      p +        margin: 0 +      p:first-child +        padding-top: 8px +        font-weight: bold + +  .t2e-item-list .detailed-timetables > div +    border-left: none +    &:after +      top: 50% +      left: 50% +      content: "" +      border: 1px solid black +      width: 20px +      height: 20px +      margin-left: -10px +      margin-top: -10px +      position: absolute +      border-radius: 20px +    &.active:after +      background: black +    &:before +      left: -8px +    // Errors    .table-2entries .t2e-item-list      .t2e-item diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb index c5ecc505a..c4961123d 100644 --- a/app/controllers/application_controller.rb +++ b/app/controllers/application_controller.rb @@ -36,16 +36,6 @@ class ApplicationController < ActionController::Base    end    helper_method :current_organisation -  def current_offer_workbench -    current_organisation.workbenches.find_by_name("Gestion de l'offre") -  end -  helper_method :current_offer_workbench - -  def current_workgroup -    current_offer_workbench.workgroup -  end -  helper_method :current_workgroup -    def current_functional_scope      functional_scope = current_organisation.sso_attributes.try(:[], "functional_scope") if current_organisation      JSON.parse(functional_scope) if functional_scope diff --git a/app/controllers/autocomplete_stop_areas_controller.rb b/app/controllers/autocomplete_stop_areas_controller.rb index d82fa316a..79154a6e0 100644 --- a/app/controllers/autocomplete_stop_areas_controller.rb +++ b/app/controllers/autocomplete_stop_areas_controller.rb @@ -21,7 +21,7 @@ class AutocompleteStopAreasController < ChouetteController        scope = StopAreaPolicy::Scope.new(current_user, scope).search_scope(search_scope)      end      args = [].tap{|arg| 4.times{arg << "%#{params[:q]}%"}} -    @stop_areas = scope.where("unaccent(name) ILIKE unaccent(?) OR unaccent(city_name) ILIKE unaccent(?) OR registration_number ILIKE ? OR objectid ILIKE ?", *args).limit(50) +    @stop_areas = scope.where("unaccent(stop_areas.name) ILIKE unaccent(?) OR unaccent(stop_areas.city_name) ILIKE unaccent(?) OR stop_areas.registration_number ILIKE ? OR stop_areas.objectid ILIKE ?", *args).limit(50)      @stop_areas    end diff --git a/app/controllers/calendars_controller.rb b/app/controllers/calendars_controller.rb index 5cc7912b7..75d4cbd09 100644 --- a/app/controllers/calendars_controller.rb +++ b/app/controllers/calendars_controller.rb @@ -8,14 +8,21 @@ class CalendarsController < ChouetteController    respond_to :json, only: :show    respond_to :js, only: :index +  belongs_to :workgroup +    def index      index! do -      @calendars = CalendarDecorator.decorate(@calendars) +      @calendars = decorate_calendars(@calendars)      end    end    def show -    @year = params[:year] ? params[:year].to_i : Date.today.cwyear +    show! do +      @year = params[:year] ? params[:year].to_i : Date.today.cwyear +      @calendar = @calendar.decorate(context: { +        workgroup: workgroup +      }) +    end    end    def month @@ -45,6 +52,16 @@ class CalendarsController < ChouetteController    end    private + +  def decorate_calendars(calendars) +    CalendarDecorator.decorate( +      calendars, +      context: { +        workgroup: workgroup +      } +    ) +  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 << :shared if policy(Calendar).share? @@ -60,25 +77,30 @@ class CalendarsController < ChouetteController    end    protected + +  alias_method :workgroup, :parent +  helper_method :workgroup +    def resource -    @calendar = Calendar.where('organisation_id = ? OR shared = true', current_organisation.id).find_by_id(params[:id]).decorate +    @calendar ||= workgroup.calendars.where('(organisation_id = ? OR shared = ?)', current_organisation.id, true).find_by_id(params[:id])    end    def build_resource      super.tap do |calendar| +      calendar.workgroup = workgroup        calendar.organisation = current_organisation      end    end    def collection -    return @calendars if @calendars -    scope = Calendar.where('organisation_id = ? OR shared = ?', current_organisation.id, true) -    scope = shared_scope(scope) -    @q = scope.ransack(params[:q]) - -    calendars = @q.result -    calendars = calendars.order(sort_column + ' ' + sort_direction) if sort_column && sort_direction -    @calendars = calendars.paginate(page: params[:page]) +    @calendars ||= begin +      scope = workgroup.calendars.where('(organisation_id = ? OR shared = ?)', current_organisation.id, true) +      scope = shared_scope(scope) +      @q = scope.ransack(params[:q]) +      calendars = @q.result +      calendars = calendars.order(sort_column + ' ' + sort_direction) if sort_column && sort_direction +      calendars = calendars.paginate(page: params[:page]) +    end    end    def ransack_contains_date @@ -103,4 +125,4 @@ class CalendarsController < ChouetteController      scope    end -end +end
\ No newline at end of file diff --git a/app/controllers/compliance_control_sets_controller.rb b/app/controllers/compliance_control_sets_controller.rb index ae1d01feb..8f9251155 100644 --- a/app/controllers/compliance_control_sets_controller.rb +++ b/app/controllers/compliance_control_sets_controller.rb @@ -7,10 +7,8 @@ class ComplianceControlSetsController < ChouetteController    def index      index! do |format| -      scope = self.ransack_period_range(scope: @compliance_control_sets, error_message: t('imports.filters.error_period_filter'), query: :where_updated_at_between) -      @q_for_form = scope.ransack(params[:q])        format.html { -        @compliance_control_sets = decorate_compliance_control_sets(@q_for_form.result.paginate(page: params[:page], per_page: 30)) +        @compliance_control_sets = decorate_compliance_control_sets(@compliance_control_sets)        }      end    end @@ -37,6 +35,14 @@ class ComplianceControlSetsController < ChouetteController    private +  def collection +    scope = self.ransack_period_range(scope: ComplianceControlSet.all, error_message: t('imports.filters.error_period_filter'), query: :where_updated_at_between) +    @q_for_form = scope.ransack(params[:q]) +    compliance_control_sets = @q_for_form.result +    compliance_control_sets = joins_with_associated_objects(compliance_control_sets).order(sort_column + ' ' + sort_direction) if sort_column && sort_direction +    @compliance_control_sets = compliance_control_sets.paginate(page: params[:page], per_page: 30) +  end +    def decorate_compliance_control_sets(compliance_control_sets)      ComplianceControlSetDecorator.decorate(compliance_control_sets)    end @@ -58,4 +64,32 @@ class ComplianceControlSetsController < ChouetteController      @direct_compliance_controls        = compliance_controls.delete nil      @blocks_to_compliance_controls_map = compliance_controls    end + +  def sort_column +    case params[:sort] +      when 'name' then 'lower(compliance_control_sets.name)' +      when 'owner_jdc' then 'lower(organisations.name)' +      when 'control_numbers' then 'COUNT(compliance_controls.id)' +      else +        ComplianceControlSet.column_names.include?(params[:sort]) ? params[:sort] : 'lower(compliance_control_sets.name)' +    end +  end + +  def joins_with_associated_objects(collection) + +    # dont know if this is the right way to do it but since we need to join table deoending of the params +    # it was to avoid loading associated objects if we don't need them +    case params[:sort] +      when 'owner_jdc' +        collection.joins("LEFT JOIN organisations ON compliance_control_sets.organisation_id = organisations.id") +      when 'control_numbers'  +        collection.joins("LEFT JOIN compliance_controls ON compliance_controls.compliance_control_set_id = compliance_control_sets.id").group(:id) +      else  +        collection +    end +  end + +  def sort_direction +    %w[asc desc].include?(params[:direction]) ?  params[:direction] : 'asc' +  end  end diff --git a/app/controllers/referential_vehicle_journeys_controller.rb b/app/controllers/referential_vehicle_journeys_controller.rb index 2ce28a5cc..e36ef8153 100644 --- a/app/controllers/referential_vehicle_journeys_controller.rb +++ b/app/controllers/referential_vehicle_journeys_controller.rb @@ -20,11 +20,30 @@ class ReferentialVehicleJourneysController < ChouetteController      @q = ransack_period_range(scope: @q, error_message:  t('vehicle_journeys.errors.purchase_window'), query: :in_purchase_window, prefix: :purchase_window)      @q = ransack_period_range(scope: @q, error_message:  t('vehicle_journeys.errors.time_table'), query: :with_matching_timetable, prefix: :time_table)      @q = @q.ransack(params[:q]) -    @vehicle_journeys ||= @q.result.order(:published_journey_name).includes(:vehicle_journey_at_stops).paginate page: params[:page], per_page: params[:per_page] || 10 +    @vehicle_journeys ||= @q.result +    @vehicle_journeys = parse_order @vehicle_journeys +    @vehicle_journeys = @vehicle_journeys.paginate page: params[:page], per_page: params[:per_page] || 10      @all_companies = Chouette::Company.where("id IN (#{@referential.vehicle_journeys.select(:company_id).to_sql})").distinct      @all_stop_areas = Chouette::StopArea.where("id IN (#{@referential.vehicle_journeys.joins(:stop_areas).select("stop_areas.id").to_sql})").distinct      stop_area_ids = params[:q].try(:[], :stop_area_ids).try(:select, &:present?)      @filters_stop_areas = Chouette::StopArea.find(stop_area_ids) if stop_area_ids.present? && stop_area_ids.size <= 2    end +  def parse_order scope +    return scope.order(:published_journey_name) unless params[:sort].present? +    direction = params[:direction] || "asc" +    attributes = Chouette::VehicleJourney.column_names.map{|n| "vehicle_journeys.#{n}"}.join(',') +    case params[:sort] +      when "line" +        scope.order("lines.name #{direction}").joins(route: :line) +      when "route" +        scope.order("routes.name #{direction}").joins(:route) +      when "departure_time" +        scope.joins(:vehicle_journey_at_stops).group(attributes).select(attributes).order("MIN(vehicle_journey_at_stops.departure_time) #{direction}") +      when "arrival_time" +        scope.joins(:vehicle_journey_at_stops).group(attributes).select(attributes).order("MAX(vehicle_journey_at_stops.departure_time) #{direction}") +      else +        scope.order "#{params[:sort]} #{direction}" +      end +  end  end diff --git a/app/controllers/referentials_controller.rb b/app/controllers/referentials_controller.rb index 09ea35c26..6e3694547 100644 --- a/app/controllers/referentials_controller.rb +++ b/app/controllers/referentials_controller.rb @@ -1,5 +1,6 @@  class ReferentialsController < ChouetteController    defaults :resource_class => Referential +  before_action :load_workbench    include PolicyChecker    respond_to :html @@ -32,7 +33,7 @@ class ReferentialsController < ChouetteController    def show      resource.switch      show! do |format| -      @referential = @referential.decorate(context: { current_workbench_id: params[:current_workbench_id] } ) +      @referential = @referential.decorate()        @reflines = lines_collection.paginate(page: params[:page], per_page: 10)        @reflines = ReferentialLineDecorator.decorate(          @reflines, @@ -143,7 +144,6 @@ class ReferentialsController < ChouetteController      if params[:from]        source_referential = Referential.find(params[:from])        @referential = Referential.new_from(source_referential, current_functional_scope) -      @referential.workbench_id = params[:current_workbench_id]      end      @referential.data_format = current_organisation.data_format @@ -183,4 +183,12 @@ class ReferentialsController < ChouetteController      return user_not_authorized unless current_user.organisation.workgroups.include?(source.workbench.workgroup)    end +  def load_workbench +    @workbench ||= Workbench.find(params[:workbench_id]) if params[:workbench_id] +    @workbench ||= resource&.workbench if params[:id] +    @workbench +  end + +  alias_method :current_workbench, :load_workbench +  helper_method :current_workbench  end diff --git a/app/controllers/snapshots_controller.rb b/app/controllers/snapshots_controller.rb new file mode 100644 index 000000000..e453b4965 --- /dev/null +++ b/app/controllers/snapshots_controller.rb @@ -0,0 +1,14 @@ +class SnapshotsController < ApplicationController +  if Rails.env.development? || Rails.env.test? +    layout :which_layout +    def show +      tpl = params[:snap] +      tpl = tpl.gsub Rails.root.to_s, '' +      render file: tpl +    end + +    def which_layout +      "snapshots/#{params[:layout] || "default"}" +    end +  end +end diff --git a/app/controllers/statuses_controller.rb b/app/controllers/statuses_controller.rb new file mode 100644 index 000000000..e38a92982 --- /dev/null +++ b/app/controllers/statuses_controller.rb @@ -0,0 +1,20 @@ +class StatusesController < ChouetteController +  respond_to :json + +  def index + +    status = { +      referentials_blocked: Referential.blocked.count, +      imports_blocked: Import.blocked.count, +      compliance_check_sets_blocked: ComplianceCheckSet.blocked.count +    } +    status[:status] = global_status status +    render json: status.to_json +  end + +  private + +  def global_status status +    status.values.all?(&:zero?) ? 'ok' : 'ko' +  end +end diff --git a/app/controllers/vehicle_journeys_controller.rb b/app/controllers/vehicle_journeys_controller.rb index e031e4952..14795227c 100644 --- a/app/controllers/vehicle_journeys_controller.rb +++ b/app/controllers/vehicle_journeys_controller.rb @@ -154,7 +154,7 @@ class VehicleJourneysController < ChouetteController    private    def load_custom_fields -    @custom_fields = current_workgroup.custom_fields_definitions +    @custom_fields = referential.workgroup&.custom_fields_definitions || {}    end    def map_stop_points points @@ -185,7 +185,9 @@ class VehicleJourneysController < ChouetteController          :long_lat_type => sp.stop_area.try(:long_lat_type),          :country_code => sp.stop_area.try(:country_code),          :country_name => sp.stop_area.try(:country_name), -        :street_name => sp.stop_area.try(:street_name) +        :street_name => sp.stop_area.try(:street_name), +        :waiting_time => sp.stop_area.try(:waiting_time), +        :waiting_time_text => sp.stop_area.decorate.try(:waiting_time_text),        }      end    end diff --git a/app/decorators/calendar_decorator.rb b/app/decorators/calendar_decorator.rb index be1f9e3bf..4c6088e8e 100644 --- a/app/decorators/calendar_decorator.rb +++ b/app/decorators/calendar_decorator.rb @@ -1,6 +1,6 @@  class CalendarDecorator < AF83::Decorator    decorates Calendar - +  set_scope { context[:workgroup] }    create_action_link    with_instance_decorator do |instance_decorator| diff --git a/app/decorators/company_decorator.rb b/app/decorators/company_decorator.rb index aadce68bb..5580e0d4a 100644 --- a/app/decorators/company_decorator.rb +++ b/app/decorators/company_decorator.rb @@ -1,34 +1,21 @@  class CompanyDecorator < AF83::Decorator    decorates Chouette::Company +  set_scope { context[:referential] } +    create_action_link do |l|      l.content { h.t('companies.actions.new') } -    l.href    { [:new, context[:referential], :company] }    end    with_instance_decorator do |instance_decorator| -    instance_decorator.show_action_link do |l| -      l.href { [context[:referential], object] } -    end +    instance_decorator.show_action_link      instance_decorator.edit_action_link do |l|        l.content {|l| l.action == "show" ? h.t('actions.edit') : h.t('companies.actions.edit') } -      l.href { -        h.edit_line_referential_company_path( -          context[:referential], -          object -        ) -      }      end      instance_decorator.destroy_action_link do |l|        l.content { h.destroy_link_content('companies.actions.destroy') } -      l.href { -        h.edit_line_referential_company_path( -          context[:referential], -          object -        ) -      }        l.data {{ confirm: h.t('companies.actions.destroy_confirm') }}      end    end diff --git a/app/decorators/compliance_control_decorator.rb b/app/decorators/compliance_control_decorator.rb index c57a7ccc7..fd2dbd9ce 100644 --- a/app/decorators/compliance_control_decorator.rb +++ b/app/decorators/compliance_control_decorator.rb @@ -1,6 +1,8 @@  class ComplianceControlDecorator < AF83::Decorator    decorates ComplianceControl +  set_scope { object.compliance_control_set } +    with_instance_decorator do |instance_decorator|      instance_decorator.show_action_link do |l|        l.content h.t('compliance_control_sets.actions.show') @@ -12,23 +14,9 @@ class ComplianceControlDecorator < AF83::Decorator        end      end -    instance_decorator.edit_action_link do |l| -      l.href do -        h.edit_compliance_control_set_compliance_control_path( -          object.compliance_control_set_id, -          object.id -        ) -      end -    end +    instance_decorator.edit_action_link      instance_decorator.destroy_action_link do |l| -      l.content h.destroy_link_content -      l.href do -        h.compliance_control_set_compliance_control_path( -          object.compliance_control_set.id, -          object.id -        ) -      end        l.data confirm: h.t('compliance_controls.actions.destroy_confirm')      end    end diff --git a/app/decorators/compliance_control_set_decorator.rb b/app/decorators/compliance_control_set_decorator.rb index 387822c67..b16a06886 100644 --- a/app/decorators/compliance_control_set_decorator.rb +++ b/app/decorators/compliance_control_set_decorator.rb @@ -6,6 +6,8 @@ class ComplianceControlSetDecorator < AF83::Decorator    end    with_instance_decorator do |instance_decorator| +    instance_decorator.show_action_link +      instance_decorator.edit_action_link do |l|        l.content t('compliance_control_sets.actions.edit')      end diff --git a/app/decorators/import_decorator.rb b/app/decorators/import_decorator.rb index c6b1f2349..1964365ae 100644 --- a/app/decorators/import_decorator.rb +++ b/app/decorators/import_decorator.rb @@ -1,6 +1,8 @@  class ImportDecorator < AF83::Decorator    decorates Import +  set_scope { context[:workbench] } +    define_instance_method :import_status_css_class do      cls =''      cls = 'overheaded-success' if object.status == 'successful' @@ -11,13 +13,10 @@ class ImportDecorator < AF83::Decorator    create_action_link do |l|      l.content t('imports.actions.new') -    l.href { h.new_workbench_import_path(workbench_id: context[:workbench]) }    end    with_instance_decorator do |instance_decorator| -    instance_decorator.show_action_link do |l| -      l.href { h.workbench_import_path(context[:workbench], object) } -    end +    instance_decorator.show_action_link      instance_decorator.action_link secondary: :show do |l|        l.content t('imports.actions.download') diff --git a/app/decorators/import_resources_decorator.rb b/app/decorators/import_resources_decorator.rb index 2b1a25ef9..88a8057cf 100644 --- a/app/decorators/import_resources_decorator.rb +++ b/app/decorators/import_resources_decorator.rb @@ -1,4 +1,4 @@ -class ImportResourcesDecorator < ModelDecorator +class ImportResourcesDecorator < AF83::Decorator    delegate :where    def lines_imported diff --git a/app/decorators/line_decorator.rb b/app/decorators/line_decorator.rb index adeb89f70..0e7b6b9ae 100644 --- a/app/decorators/line_decorator.rb +++ b/app/decorators/line_decorator.rb @@ -1,9 +1,10 @@  class LineDecorator < AF83::Decorator    decorates Chouette::Line +  set_scope { context[:line_referential] } +    create_action_link do |l|      l.content t('lines.actions.new') -    l.href    { h.new_line_referential_line_path(context[:line_referential]) }    end    with_instance_decorator do |instance_decorator| @@ -14,18 +15,17 @@ class LineDecorator < AF83::Decorator      instance_decorator.show_action_link do |l|        l.content t('lines.actions.show') -      l.href   { [context[:line_referential], object] }      end      instance_decorator.action_link secondary: :show do |l|        l.content t('lines.actions.show_network') -      l.href   { [context[:line_referential], object.network] } +      l.href   { [scope, object.network] }        l.disabled { object.network.nil? }      end      instance_decorator.action_link secondary: :show do |l|        l.content  t('lines.actions.show_company') -      l.href     { [context[:line_referential], object.company] } +      l.href     { [scope, object.company] }        l.disabled { object.company.nil? }      end @@ -34,7 +34,6 @@ class LineDecorator < AF83::Decorator      instance_decorator.with_condition can_edit_line do        edit_action_link do |l|          l.content {|l| l.primary? ? h.t('actions.edit') : h.t('lines.actions.edit') } -        l.href    { h.edit_line_referential_line_path(context[:line_referential], object.id) }        end        action_link on: :index, secondary: :index do |l| @@ -64,7 +63,6 @@ class LineDecorator < AF83::Decorator      instance_decorator.destroy_action_link do |l|        l.content  { h.destroy_link_content('lines.actions.destroy') } -      l.href     { h.line_referential_line_path(context[:line_referential], object) }        l.data     confirm: h.t('lines.actions.destroy_confirm')        l.add_class "delete-action"      end diff --git a/app/decorators/network_decorator.rb b/app/decorators/network_decorator.rb index 90f0d0e82..ea0f73dc2 100644 --- a/app/decorators/network_decorator.rb +++ b/app/decorators/network_decorator.rb @@ -1,6 +1,7 @@  class NetworkDecorator < AF83::Decorator    decorates Chouette::Network +  set_scope { context[:line_referential] }    # Action links require:    #   context: {    #     line_referential: , @@ -8,15 +9,10 @@ class NetworkDecorator < AF83::Decorator    create_action_link do |l|      l.content t('networks.actions.new') -    l.href { h.new_line_referential_network_path(context[:line_referential]) }    end    with_instance_decorator do |instance_decorator| -    instance_decorator.show_action_link do |l| -      l.href do -        h.line_referential_network_path(context[:line_referential], object) -      end -    end +    instance_decorator.show_action_link      instance_decorator.action_link secondary: true, policy: :edit do |l|        l.content t('networks.actions.edit') @@ -30,12 +26,6 @@ class NetworkDecorator < AF83::Decorator      instance_decorator.destroy_action_link do |l|        l.content h.destroy_link_content('networks.actions.destroy') -      l.href do -        h.line_referential_network_path( -          context[:line_referential], -          object -        ) -      end        l.data confirm: h.t('networks.actions.destroy_confirm')      end    end diff --git a/app/decorators/purchase_window_decorator.rb b/app/decorators/purchase_window_decorator.rb index 54b241173..9b58577b2 100644 --- a/app/decorators/purchase_window_decorator.rb +++ b/app/decorators/purchase_window_decorator.rb @@ -1,32 +1,20 @@  class PurchaseWindowDecorator < AF83::Decorator    decorates Chouette::PurchaseWindow +  set_scope { context[:referential] } +    create_action_link do |l|      l.content t('purchase_windows.actions.new') -    l.href { h.new_referential_purchase_window_path(context[:referential]) }    end    with_instance_decorator do |instance_decorator|      instance_decorator.show_action_link do |l|        l.content t('purchase_windows.actions.show') -      l.href do -        h.referential_purchase_window_path( -          context[:referential], -          object -        ) -      end      end -    instance_decorator.edit_action_link do |l| -      l.href do -        h.edit_referential_purchase_window_path(context[:referential].id, object) -      end -    end +    instance_decorator.edit_action_link      instance_decorator.destroy_action_link do |l| -      l.href do -        h.referential_purchase_window_path(context[:referential].id, object) -      end        l.data confirm: h.t('purchase_windows.actions.destroy_confirm')      end    end diff --git a/app/decorators/referential_decorator.rb b/app/decorators/referential_decorator.rb index 3132cbf92..41cad237d 100644 --- a/app/decorators/referential_decorator.rb +++ b/app/decorators/referential_decorator.rb @@ -22,12 +22,12 @@ class ReferentialDecorator < AF83::Decorator      instance_decorator.action_link policy: :clone, secondary: :show do |l|        l.content t('actions.clone') -      l.href { h.new_referential_path(from: object.id, current_workbench_id: context[:current_workbench_id]) } +      l.href { h.duplicate_workbench_referential_path(object) }      end      instance_decorator.action_link policy: :validate, secondary: :show do |l|        l.content t('actions.validate') -      l.href { h.referential_select_compliance_control_set_path(object.id) } +      l.href { h.select_compliance_control_set_referential_path(object.id) }      end      instance_decorator.action_link policy: :archive, secondary: :show do |l| diff --git a/app/decorators/referential_line_decorator.rb b/app/decorators/referential_line_decorator.rb index 8f884a8e0..3ac846d76 100644 --- a/app/decorators/referential_line_decorator.rb +++ b/app/decorators/referential_line_decorator.rb @@ -1,6 +1,8 @@  class ReferentialLineDecorator < AF83::Decorator    decorates Chouette::Line +  set_scope { context[:referential] } +    # Action links require:    #   context: {    #     referential: , @@ -8,9 +10,7 @@ class ReferentialLineDecorator < AF83::Decorator    #   }    with_instance_decorator do |instance_decorator| -    instance_decorator.show_action_link do |l| -      l.href { h.referential_line_path(context[:referential], object) } -    end +    instance_decorator.show_action_link      instance_decorator.action_link secondary: true do |l|        l.content Chouette::Line.human_attribute_name(:footnotes) @@ -21,7 +21,7 @@ class ReferentialLineDecorator < AF83::Decorator        l.content h.t('routing_constraint_zones.index.title')        l.href do          h.referential_line_routing_constraint_zones_path( -          context[:referential], +          scope,            object          )        end @@ -37,7 +37,7 @@ class ReferentialLineDecorator < AF83::Decorator        secondary: true      ) do |l|        l.content h.t('routes.actions.new') -      l.href { h.new_referential_line_route_path(context[:referential], object) } +      l.href { h.new_referential_line_route_path(scope, object) }      end    end  end diff --git a/app/decorators/referential_network_decorator.rb b/app/decorators/referential_network_decorator.rb index ff3467188..c508452c0 100644 --- a/app/decorators/referential_network_decorator.rb +++ b/app/decorators/referential_network_decorator.rb @@ -1,6 +1,8 @@  class ReferentialNetworkDecorator < AF83::Decorator    decorates Chouette::Network +  set_scope { context[:referential] } +    # Action links require:    #   context: {    #     referential: , @@ -8,33 +10,18 @@ class ReferentialNetworkDecorator < AF83::Decorator    create_action_link do |l|      l.content t('networks.actions.new') -    l.href { h.new_referential_network_path(context[:referential]) }    end    with_instance_decorator do |instance_decorator| -    instance_decorator.show_action_link do |l| -      l.href { h.referential_network_path(context[:referential], object) } -    end +    instance_decorator.show_action_link      instance_decorator.edit_action_link do |l|        l.content t('networks.actions.edit') -      l.href do -        h.edit_referential_network_path( -          context[:referential], -          object -        ) -      end      end      instance_decorator.destroy_action_link do |l|        l.content h.destroy_link_content('networks.actions.destroy') -      l.href do -        h.referential_network_path( -          context[:referential], -          object -        ) -      end        l.data confirm: h.t('networks.actions.destroy_confirm')      end    end -end
\ No newline at end of file +end diff --git a/app/decorators/route_decorator.rb b/app/decorators/route_decorator.rb index f9870fbbe..fa6367924 100644 --- a/app/decorators/route_decorator.rb +++ b/app/decorators/route_decorator.rb @@ -7,26 +7,12 @@ class RouteDecorator < AF83::Decorator    #     line:    #   } +  set_scope { [context[:referential], context[:line]] } +    with_instance_decorator do |instance_decorator| -    instance_decorator.show_action_link do |l| -      l.href do -        h.referential_line_route_path( -          context[:referential], -          context[:line], -          object -        ) -      end -    end +    instance_decorator.show_action_link -    instance_decorator.edit_action_link do |l| -      l.href do -        h.edit_referential_line_route_path( -          context[:referential], -          context[:line], -          object -        ) -      end -    end +    instance_decorator.edit_action_link      instance_decorator.action_link(        if: ->() { object.stop_points.any? }, @@ -86,13 +72,6 @@ class RouteDecorator < AF83::Decorator      end      instance_decorator.destroy_action_link do |l| -      l.href do -        h.referential_line_route_path( -          context[:referential], -          context[:line], -          object -        ) -      end        l.data confirm: h.t('routes.actions.destroy_confirm')      end    end diff --git a/app/decorators/routing_constraint_zone_decorator.rb b/app/decorators/routing_constraint_zone_decorator.rb index 962625fa7..de73068be 100644 --- a/app/decorators/routing_constraint_zone_decorator.rb +++ b/app/decorators/routing_constraint_zone_decorator.rb @@ -1,6 +1,8 @@  class RoutingConstraintZoneDecorator < AF83::Decorator    decorates Chouette::RoutingConstraintZone +  set_scope { [context[:referential], context[:line]] } +    # Action links require:    #   context: {    #     referential: , @@ -12,44 +14,13 @@ class RoutingConstraintZoneDecorator < AF83::Decorator        h.policy(Chouette::RoutingConstraintZone).create? &&          context[:referential].organisation == h.current_organisation      } -  ) do |l| -    l.href do -      h.new_referential_line_routing_constraint_zone_path( -       context[:referential], -       context[:line] -     ) -    end -  end +  )    with_instance_decorator do |instance_decorator| -    instance_decorator.show_action_link do |l| -      l.href do -        h.referential_line_routing_constraint_zone_path( -          context[:referential], -          context[:line], -          object -        ) -      end -    end - -    instance_decorator.edit_action_link do |l| -      l.href do -        h.edit_referential_line_routing_constraint_zone_path( -          context[:referential], -          context[:line], -          object -        ) -      end -    end +    instance_decorator.show_action_link +    instance_decorator.edit_action_link      instance_decorator.destroy_action_link do |l| -      l.href do -        h.referential_line_routing_constraint_zone_path( -          context[:referential], -          context[:line], -          object -        ) -      end        l.data confirm: h.t('routing_constraint_zones.actions.destroy_confirm')      end    end diff --git a/app/decorators/stop_area_decorator.rb b/app/decorators/stop_area_decorator.rb index 2e57da0e4..525681971 100644 --- a/app/decorators/stop_area_decorator.rb +++ b/app/decorators/stop_area_decorator.rb @@ -7,23 +7,11 @@ class StopAreaDecorator < AF83::Decorator    end    with_instance_decorator do |instance_decorator| -    instance_decorator.show_action_link do |l| -      l.href do -        h.stop_area_referential_stop_area_path( -          object.stop_area_referential, -          object -        ) -      end -    end +    set_scope { object.stop_area_referential } +    instance_decorator.show_action_link      instance_decorator.edit_action_link do |l|        l.content h.t('stop_areas.actions.edit') -      l.href do -        h.edit_stop_area_referential_stop_area_path( -          object.stop_area_referential, -          object -        ) -      end      end      instance_decorator.action_link policy: :deactivate, secondary: true do |l| @@ -54,12 +42,6 @@ class StopAreaDecorator < AF83::Decorator      instance_decorator.destroy_action_link do |l|        l.content h.destroy_link_content('stop_areas.actions.destroy') -      l.href do -        h.stop_area_referential_stop_area_path( -          object.stop_area_referential, -          object -        ) -      end        l.data confirm: h.t('stop_areas.actions.destroy_confirm')      end    end diff --git a/app/decorators/time_table_decorator.rb b/app/decorators/time_table_decorator.rb index 95b1fd959..e4f9d7dbc 100644 --- a/app/decorators/time_table_decorator.rb +++ b/app/decorators/time_table_decorator.rb @@ -14,7 +14,7 @@ class TimeTableDecorator < AF83::Decorator        l.href { [:edit, context[:referential], object] }      end -    instance_decorator.action_link if: ->{ object.calendar }, secondary: true do |l| +    instance_decorator.action_link policy: :actualize, if: ->{ object.calendar }, secondary: true do |l|        l.content t('actions.actualize')        l.href do           h.actualize_referential_time_table_path( diff --git a/app/helpers/referentials_helper.rb b/app/helpers/referentials_helper.rb index 8251377aa..e464ec8a5 100644 --- a/app/helpers/referentials_helper.rb +++ b/app/helpers/referentials_helper.rb @@ -15,4 +15,14 @@ module ReferentialsHelper      service = ReferentialOverview.new referential, self      render partial: "referentials/overview", locals: {referential: referential, overview: service}    end + +  def mutual_workbench workbench +    current_user.organisation.workbenches.where(workgroup_id: workbench.workgroup_id).last +  end + +  def duplicate_workbench_referential_path referential +    workbench = mutual_workbench referential.workbench +    raise "Missing workbench for referential #{referential.name}" unless workbench.present? +    new_workbench_referential_path(workbench, from: referential.id) +  end  end diff --git a/app/helpers/table_builder_helper.rb b/app/helpers/table_builder_helper.rb index 9a255e757..2068dd23c 100644 --- a/app/helpers/table_builder_helper.rb +++ b/app/helpers/table_builder_helper.rb @@ -330,7 +330,7 @@ module TableBuilderHelper      else        menu = content_tag :ul, class: 'dropdown-menu' do          ( -          CustomLinks.new(item, pundit_user, links, referential).links + +          CustomLinks.new(item, pundit_user, links, referential, workgroup).links +            action_links.select { |link| link.is_a?(Link) }          ).map do |link|            gear_menu_link(link) @@ -395,7 +395,6 @@ module TableBuilderHelper      klass << link.extra_class if link.extra_class      klass << 'delete-action' if link.method == :delete      klass << 'disabled' if link.disabled -      content_tag(        :li,        link_to( @@ -414,4 +413,10 @@ module TableBuilderHelper      # cases, avoid a `NoMethodError`.      @__referential__ ||= try(:current_referential)    end + +  def workgroup +    # Certain controllers don't define a `#current_referential`. In these +    # cases, avoid a `NoMethodError`. +    @__workgroup__ ||= try(:current_workgroup) +  end  end diff --git a/app/helpers/table_builder_helper/custom_links.rb b/app/helpers/table_builder_helper/custom_links.rb index b1bb11f10..e09078be0 100644 --- a/app/helpers/table_builder_helper/custom_links.rb +++ b/app/helpers/table_builder_helper/custom_links.rb @@ -8,13 +8,14 @@ module TableBuilderHelper        unarchive: :put      } -    attr_reader :actions, :object, :user_context, :referential +    attr_reader :actions, :object, :user_context, :referential, :workgroup -    def initialize(object, user_context, actions, referential = nil) +    def initialize(object, user_context, actions, referential = nil, workgroup = nil)        @object       = object        @user_context = user_context        @actions      = actions        @referential  = referential +      @workgroup  = workgroup      end      def links @@ -34,7 +35,7 @@ module TableBuilderHelper          polymorph_url << action        end -      polymorph_url += URL.polymorphic_url_parts(object, referential) +      polymorph_url += URL.polymorphic_url_parts(object, referential, workgroup)      end      def method_for_action(action) diff --git a/app/helpers/table_builder_helper/url.rb b/app/helpers/table_builder_helper/url.rb index 28f1ade76..0e3dce0aa 100644 --- a/app/helpers/table_builder_helper/url.rb +++ b/app/helpers/table_builder_helper/url.rb @@ -1,6 +1,6 @@  module TableBuilderHelper    class URL -    def self.polymorphic_url_parts(item, referential) +    def self.polymorphic_url_parts(item, referential, workgroup)        polymorph_url = []        unless item.is_a?(Calendar) || item.is_a?(Referential) || item.is_a?(ComplianceControlSet) @@ -20,6 +20,7 @@ module TableBuilderHelper            end          end        else +        polymorph_url << item.workgroup if item.respond_to? :workgroup          polymorph_url << item        end diff --git a/app/javascript/helpers/stop_area_header_manager.js b/app/javascript/helpers/stop_area_header_manager.js index 5b18e2f63..1003b2cf6 100644 --- a/app/javascript/helpers/stop_area_header_manager.js +++ b/app/javascript/helpers/stop_area_header_manager.js @@ -15,11 +15,15 @@ export default class StopAreaHeaderManager {      let index = this.ids_list.indexOf(object_id)      let sp = this.stopPointsList[index]      let showHeadline = this.showHeader(object_id) +    let title = sp.city_name ? sp.city_name + ' (' + sp.zip_code +')' : "" +    if(sp.waiting_time > 0){ +      title += " | " + sp.waiting_time_text +    }      return (        <div          className={(showHeadline) ? 'headlined' : ''}          data-headline={showHeadline} -        title={sp.city_name ? sp.city_name + ' (' + sp.zip_code +')' : ""} +        title={title}        >          <span>            <span> diff --git a/app/javascript/routes/components/BSelect2.js b/app/javascript/routes/components/BSelect2.js index 158deaa17..035bce155 100644 --- a/app/javascript/routes/components/BSelect2.js +++ b/app/javascript/routes/components/BSelect2.js @@ -85,7 +85,7 @@ class BSelect2 extends Component{          onSelect={ this.props.onSelect }          ref='newSelect'          options={{ -          placeholder: this.context.I18n.routes.edit.select2.placeholder, +          placeholder: this.context.I18n.t("routes.edit.select2.placeholder"),            allowClear: true,            language: 'fr', /* Doesn't seem to work... :( */            theme: 'bootstrap', diff --git a/app/javascript/routes/components/OlMap.js b/app/javascript/routes/components/OlMap.js index 056bddbcb..4beb02872 100644 --- a/app/javascript/routes/components/OlMap.js +++ b/app/javascript/routes/components/OlMap.js @@ -115,40 +115,40 @@ export default class OlMap extends Component{                <strong>{this.props.value.olMap.json.name}</strong>              </p>              <p> -              <strong>{this.context.I18n.routes.edit.stop_point_type} : </strong> +              <strong>{this.context.I18n.t('routes.edit.map.stop_point_type')} : </strong>                {this.props.value.olMap.json.area_type}              </p>              <p> -              <strong>{this.context.I18n.routes.edit.short_name} : </strong> +              <strong>{this.context.I18n.t('routes.edit.map.short_name')} : </strong>                {this.props.value.olMap.json.short_name}              </p>              <p> -              <strong>{this.context.I18n.id_reflex} : </strong> +              <strong>{this.context.I18n.t('id_reflex')} : </strong>                {this.props.value.olMap.json.user_objectid}              </p> -            <p><strong>{this.context.I18n.routes.edit.map.coordinates} : </strong></p> +            <p><strong>{this.context.I18n.t('routes.edit.map.coordinates')} : </strong></p>              <p style={{paddingLeft: 10, marginTop: 0}}> -              <em>{this.context.I18n.routes.edit.map.proj}.: </em>WSG84<br/> -              <em>{this.context.I18n.routes.edit.map.lat}.: </em>{this.props.value.olMap.json.latitude} <br/> -              <em>{this.context.I18n.routes.edit.map.lon}.: </em>{this.props.value.olMap.json.longitude} +              <em>{this.context.I18n.t('routes.edit.map.proj')}.: </em>WSG84<br/> +              <em>{this.context.I18n.t('routes.edit.map.lat')}.: </em>{this.props.value.olMap.json.latitude} <br/> +              <em>{this.context.I18n.t('routes.edit.map.lon')}.: </em>{this.props.value.olMap.json.longitude}              </p>              <p> -              <strong>{this.context.I18n.routes.edit.map.postal_code} : </strong> +              <strong>{this.context.I18n.t('routes.edit.map.postal_code')} : </strong>                {this.props.value.olMap.json.zip_code}              </p>              <p> -              <strong>{this.context.I18n.routes.edit.map.city} : </strong> +              <strong>{this.context.I18n.t('routes.edit.map.city')} : </strong>                {this.props.value.olMap.json.city_name}              </p>              <p> -              <strong>{this.context.I18n.routes.edit.map.comment} : </strong> +              <strong>{this.context.I18n.t('routes.edit.map.comment')} : </strong>                {this.props.value.olMap.json.comment}              </p>              {(this.props.value.stoparea_id != this.props.value.olMap.json.stoparea_id) &&(                <div className='btn btn-outline-primary btn-sm'                  onClick= {() => {this.props.onUpdateViaOlMap(this.props.index, this.props.value.olMap.json)}} -              >{this.context.I18n.actions.select}</div> +              >{this.context.I18n.t('actions.select')}</div>              )}            </div>              <div className='map_content'> @@ -162,7 +162,7 @@ export default class OlMap extends Component{    }  } -OlMap.PropTypes = { +OlMap.propTypes = {  }  OlMap.contextTypes = { diff --git a/app/javascript/routes/components/StopPoint.js b/app/javascript/routes/components/StopPoint.js index 2d47e802b..af51a6bb4 100644 --- a/app/javascript/routes/components/StopPoint.js +++ b/app/javascript/routes/components/StopPoint.js @@ -18,15 +18,15 @@ export default function StopPoint(props, {I18n}) {          <div>            <select className='form-control' value={props.value.for_boarding} id="for_boarding" onChange={props.onSelectChange}> -            <option value="normal">{I18n.routes.edit.stop_point.boarding.normal}</option> -            <option value="forbidden">{I18n.routes.edit.stop_point.boarding.forbidden}</option> +            <option value="normal">{I18n.t('routes.edit.stop_point.boarding.normal')}</option> +            <option value="forbidden">{I18n.t('routes.edit.stop_point.boarding.forbidden')}</option>            </select>          </div>          <div>            <select className='form-control' value={props.value.for_alighting} id="for_alighting" onChange={props.onSelectChange}> -            <option value="normal">{I18n.routes.edit.stop_point.alighting.normal}</option> -            <option value="forbidden">{I18n.routes.edit.stop_point.alighting.forbidden}</option> +            <option value="normal">{I18n.t('routes.edit.stop_point.alighting.normal')}</option> +            <option value="forbidden">{I18n.t('routes.edit.stop_point.alighting.forbidden')}</option>            </select>          </div> @@ -77,7 +77,7 @@ export default function StopPoint(props, {I18n}) {    )  } -StopPoint.PropTypes = { +StopPoint.propTypes = {    onToggleMap: PropTypes.func.isRequired,    onToggleEdit: PropTypes.func.isRequired,    onDeleteClick: PropTypes.func.isRequired, @@ -93,4 +93,4 @@ StopPoint.PropTypes = {  StopPoint.contextTypes = {    I18n: PropTypes.object -}
\ No newline at end of file +} diff --git a/app/javascript/routes/components/StopPointList.js b/app/javascript/routes/components/StopPointList.js index b39fa0c9c..b227abdea 100644 --- a/app/javascript/routes/components/StopPointList.js +++ b/app/javascript/routes/components/StopPointList.js @@ -10,22 +10,22 @@ export default function StopPointList({ stopPoints, onDeleteClick, onMoveUpClick          <div className="wrapper">            <div style={{width: 100}}>              <div className="form-group"> -              <label className="control-label">{I18n.reflex_id}</label> +              <label className="control-label">{I18n.t('simple_form.labels.stop_point.reflex_id')}</label>              </div>            </div>            <div>              <div className="form-group"> -              <label className="control-label">{I18n.simple_form.labels.stop_point.name}</label> +              <label className="control-label">{I18n.t('simple_form.labels.stop_point.name')}</label>              </div>            </div>            <div>              <div className="form-group"> -              <label className="control-label">{I18n.simple_form.labels.stop_point.for_boarding}</label> +              <label className="control-label">{I18n.t('simple_form.labels.stop_point.for_boarding')}</label>              </div>            </div>            <div>              <div className="form-group"> -              <label className="control-label">{I18n.simple_form.labels.stop_point.for_alighting}</label> +              <label className="control-label">{I18n.t('simple_form.labels.stop_point.for_alighting')}</label>              </div>            </div>            <div className='actions-5'></div> diff --git a/app/javascript/time_tables/actions/index.js b/app/javascript/time_tables/actions/index.js index 4a36ec4e1..98b9eab4b 100644 --- a/app/javascript/time_tables/actions/index.js +++ b/app/javascript/time_tables/actions/index.js @@ -8,7 +8,7 @@ const I18n = clone(window, "I18n")  const actions = {    weekDays: (index) => { -    return range(1, 8).map(n => I18n.time_tables.edit.metas.days[n]) +    return range(1, 8).map(n => I18n.t('time_tables.edit.metas.days')[n])    },    strToArrayDayTypes: (str) =>{      return actions.weekDays().map(day => str.indexOf(day) !== -1) @@ -155,7 +155,7 @@ const actions = {      type : 'CLOSE_MODAL'    }),    monthName(strDate) { -    let monthList = range(1,13).map(n => I18n.calendars.months[n]) +    let monthList = range(1,13).map(n => I18n.t('calendars.months.'+ n ))      let date = new Date(strDate)      return monthList[date.getUTCMonth()]    }, @@ -225,7 +225,7 @@ const actions = {        let period = periods[i]        if (index !== i && !period.deleted) {          if (new Date(period.period_start) <= end && new Date(period.period_end) >= start)  { -          error = I18n.time_tables.edit.error_submit.periods_overlaps +          error = I18n.t('time_tables.edit.error_submit.periods_overlaps')            break          }        } @@ -239,7 +239,7 @@ const actions = {      for (let day of in_days) {        if (start <= new Date(day.date) && end >= new Date(day.date)) { -        error = I18n.time_tables.edit.error_submit.dates_overlaps +        error = I18n.t('time_tables.edit.error_submit.dates_overlaps')          break        }      } @@ -316,9 +316,9 @@ const actions = {    errorModalMessage: (errorKey) => {      switch (errorKey) {        case "withoutPeriodsWithDaysTypes": -        return I18n.time_tables.edit.error_modal.withoutPeriodsWithDaysTypes +        return I18n.t('time_tables.edit.error_modal.withoutPeriodsWithDaysTypes')        case "withPeriodsWithoutDayTypes": -        return I18n.time_tables.edit.error_modal.withPeriodsWithoutDayTypes +        return I18n.t('time_tables.edit.error_modal.withPeriodsWithoutDayTypes')        default:          return errorKey diff --git a/app/javascript/time_tables/components/ConfirmModal.js b/app/javascript/time_tables/components/ConfirmModal.js index 845e7ed1b..4e8583bc0 100644 --- a/app/javascript/time_tables/components/ConfirmModal.js +++ b/app/javascript/time_tables/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'>{I18n.time_tables.edit.confirm_modal.title}</h4> +              <h4 className='modal-title'>{I18n.t('time_tables.edit.confirm_modal.title')}</h4>              </div>              <div className='modal-body'>                <div className='mt-md mb-md'> -                <p>{I18n.time_tables.edit.confirm_modal.message}</p> +                <p>{I18n.t('time_tables.edit.confirm_modal.message')}</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) }}                > -                {I18n.cancel} +                {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, timetable, metas) }}                > -                {I18n.actions.submit} +                {I18n.t('actions.submit')}                </button>              </div>            </div> @@ -49,4 +49,4 @@ ConfirmModal.propTypes = {  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 543177e54..8af12f1d1 100644 --- a/app/javascript/time_tables/components/ErrorModal.js +++ b/app/javascript/time_tables/components/ErrorModal.js @@ -10,7 +10,7 @@ export default function ErrorModal({dispatch, modal, onModalClose}, {I18n}) {          <div className='modal-dialog'>            <div className='modal-content'>              <div className='modal-header'> -              <h4 className='modal-title'>{I18n.time_tables.edit.error_modal.title}</h4> +              <h4 className='modal-title'>{I18n.t('time_tables.edit.error_modal.title')}</h4>              </div>              <div className='modal-body'>                <div className='mt-md mb-md'> @@ -24,7 +24,7 @@ export default function ErrorModal({dispatch, modal, onModalClose}, {I18n}) {                  type='button'                  onClick={() => { onModalClose() }}                > -                {I18n.back} +                {I18n.t('back')}                </button>              </div>            </div> @@ -41,4 +41,4 @@ ErrorModal.propTypes = {  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 3c6848d27..08a6e26fe 100644 --- a/app/javascript/time_tables/components/Metas.js +++ b/app/javascript/time_tables/components/Metas.js @@ -13,7 +13,7 @@ export default function Metas({metas, onUpdateDayTypes, onUpdateComment, onUpdat            {/* comment (name) */}            <div className="form-group">              <label htmlFor="" className="control-label col-sm-4 required"> -              {I18n.time_tables.edit.metas.name} <abbr title="">*</abbr> +              {I18n.t('time_tables.edit.metas.name')} <abbr title="">*</abbr>              </label>              <div className="col-sm-8">                <input @@ -28,7 +28,7 @@ export default function Metas({metas, onUpdateDayTypes, onUpdateComment, onUpdat            {/* color */}            {metas.color !== undefined && <div className="form-group"> -            <label htmlFor="" className="control-label col-sm-4">{I18n.activerecord.attributes.time_table.color}</label> +            <label htmlFor="" className="control-label col-sm-4">{I18n.attribute_name('time_table', 'color')}</label>              <div className="col-sm-8">                <div className="dropdown color_selector">                  <button @@ -73,7 +73,7 @@ export default function Metas({metas, onUpdateDayTypes, onUpdateComment, onUpdat            {/* tags */}            {metas.tags !== undefined && <div className="form-group"> -            <label htmlFor="" className="control-label col-sm-4">{I18n.activerecord.attributes.time_table.tag_list}</label> +            <label htmlFor="" className="control-label col-sm-4">{I18n.attribute_name('time_table', 'tag_list')}</label>              <div className="col-sm-8">                <TagsSelect2                  initialTags={metas.initial_tags} @@ -86,16 +86,16 @@ export default function Metas({metas, onUpdateDayTypes, onUpdateComment, onUpdat            {/* calendar */}            {metas.calendar !== null && <div className="form-group"> -            <label htmlFor="" className="control-label col-sm-4">{I18n.activerecord.attributes.time_table.calendar}</label> +            <label htmlFor="" className="control-label col-sm-4">{I18n.attribute_name('time_table', 'calendar')}</label>              <div className="col-sm-8"> -              <span>{metas.calendar ? metas.calendar.name : I18n.time_tables.edit.metas.no_calendar}</span> +              <span>{metas.calendar ? metas.calendar.name : I18n.t('time_tables.edit.metas.no_calendar')}</span>              </div>            </div>}            {/* day_types */}            <div className="form-group">              <label htmlFor="" className="control-label col-sm-4"> -              {I18n.time_tables.edit.metas.day_types} +              {I18n.t('time_tables.edit.metas.day_types')}              </label>              <div className="col-sm-8">                <div className="form-group labelled-checkbox-group"> diff --git a/app/javascript/time_tables/components/PeriodForm.js b/app/javascript/time_tables/components/PeriodForm.js index 085654a88..d17a246f7 100644 --- a/app/javascript/time_tables/components/PeriodForm.js +++ b/app/javascript/time_tables/components/PeriodForm.js @@ -46,7 +46,7 @@ export default function PeriodForm({modal, timetable, metas, onOpenAddPeriodForm                      <div>                        <div className="form-group">                          <label htmlFor="" className="control-label required"> -                          {I18n.time_tables.edit.period_form.begin} +                          {I18n.t('time_tables.edit.period_form.begin')}                            <abbr title="requis">*</abbr>                          </label>                        </div> @@ -54,7 +54,7 @@ export default function PeriodForm({modal, timetable, metas, onOpenAddPeriodForm                      <div>                        <div className="form-group">                          <label htmlFor="" className="control-label required"> -                          {I18n.time_tables.edit.period_form.end} +                          {I18n.t('time_tables.edit.period_form.end')}                            <abbr title="requis">*</abbr>                          </label>                        </div> @@ -105,14 +105,14 @@ export default function PeriodForm({modal, timetable, metas, onOpenAddPeriodForm                      className='btn btn-link'                      onClick={onClosePeriodForm}                    > -                    {I18n.cancel} +                    {I18n.t('cancel')}                    </button>                    <button                      type='button'                      className='btn btn-outline-primary mr-sm'                      onClick={() => onValidatePeriodForm(modal.modalProps, timetable.time_table_periods, metas, filter(timetable.time_table_dates, ['in_out', true]))}                    > -                    {I18n.actions.submit} +                    {I18n.t('actions.submit')}                    </button>                  </div>                </div> @@ -124,7 +124,7 @@ export default function PeriodForm({modal, timetable, metas, onOpenAddPeriodForm                    className='btn btn-outline-primary'                    onClick={onOpenAddPeriodForm}                  > -                  {I18n.time_tables.actions.add_period} +                  {I18n.t('time_tables.actions.add_period')}                  </button>                </div>              } @@ -132,7 +132,7 @@ export default function PeriodForm({modal, timetable, metas, onOpenAddPeriodForm          </div>        </div>      </div> -  )  +  )  }  PeriodForm.propTypes = { @@ -147,4 +147,4 @@ PeriodForm.propTypes = {  PeriodForm.contextTypes = {    I18n: PropTypes.object -}
\ No newline at end of file +} diff --git a/app/javascript/time_tables/components/TagsSelect2.js b/app/javascript/time_tables/components/TagsSelect2.js index dc3739d58..43cf59fdf 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.time_tables.edit.select2.tag.placeholder, +          placeholder: this.context.I18n.t('time_tables.edit.select2.tag.placeholder'),            ajax: {              url: origin + path + '/tags.json',              dataType: 'json', diff --git a/app/javascript/time_tables/components/Timetable.js b/app/javascript/time_tables/components/Timetable.js index c44f2a134..991f31435 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.time_tables.synthesis}</div> +              <div className="strong">{this.context.I18n.t('time_tables.edit.synthesis')}</div>              </div> -            <div className="td"><span>{this.context.I18n.time_tables.edit.day_types}</span></div> -            <div className="td"><span>{this.context.I18n.time_tables.edit.periods}</span></div> -            <div className="td"><span>{this.context.I18n.time_tables.edit.exceptions}</span></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>            <div className="t2e-item-list w80">              <div> diff --git a/app/javascript/vehicle_journeys/actions/index.js b/app/javascript/vehicle_journeys/actions/index.js index 4ca8bd73b..b398d78fa 100644 --- a/app/javascript/vehicle_journeys/actions/index.js +++ b/app/javascript/vehicle_journeys/actions/index.js @@ -348,21 +348,11 @@ const actions = {              var purchaseWindows = []              let tt              for (tt of val.time_tables){ -              timeTables.push({ -                objectid: tt.objectid, -                comment: tt.comment, -                id: tt.id, -                color: tt.color -              }) +              timeTables.push(tt)              }              if(val.purchase_windows){                for (tt of val.purchase_windows){ -                purchaseWindows.push({ -                  objectid: tt.objectid, -                  name: tt.name, -                  id: tt.id, -                  color: tt.color -                }) +                purchaseWindows.push(tt)                }              }              let vjasWithDelta = val.vehicle_journey_at_stops.map((vjas, i) => { @@ -527,6 +517,22 @@ const actions = {          minute: actions.simplePad(newArrivalDT.getUTCMinutes())        }      } +  }, +  addMinutesToTime: (time, minutes) => { +    let res = { +      hour: time.hour, +      minute: time.minute +    } +    let delta_hour = parseInt(minutes/60) +    let delta_minute = minutes - 60*delta_hour +    res.hour += delta_hour +    res.minute += delta_minute +    let extra_hours = parseInt(res.minute/60) +    res.hour += extra_hours +    res.minute -= extra_hours*60 +    res.hour = res.hour % 24 + +    return res    }  } diff --git a/app/javascript/vehicle_journeys/components/ConfirmModal.js b/app/javascript/vehicle_journeys/components/ConfirmModal.js index 3bfc852fb..75e8a3932 100644 --- a/app/javascript/vehicle_journeys/components/ConfirmModal.js +++ b/app/javascript/vehicle_journeys/components/ConfirmModal.js @@ -7,7 +7,7 @@ export default function ConfirmModal({dispatch, modal, onModalAccept, onModalCan        <div className='modal-dialog'>          <div className='modal-content'>            <div className='modal-body'> -            <p> Voulez-vous valider vos modifications avant de changer de page? </p> +            <p> {I18n.t('vehicle_journeys.vehicle_journeys_matrix.modal_confirm')} </p>            </div>            <div className='modal-footer'>              <button @@ -31,11 +31,11 @@ export default function ConfirmModal({dispatch, modal, onModalAccept, onModalCan        </div>      </div>    ) -}  +}  ConfirmModal.propTypes = {    vehicleJourneys: PropTypes.array.isRequired,    modal: PropTypes.object.isRequired,    onModalAccept: PropTypes.func.isRequired,    onModalCancel: PropTypes.func.isRequired -}
\ No newline at end of file +} diff --git a/app/javascript/vehicle_journeys/components/Filters.js b/app/javascript/vehicle_journeys/components/Filters.js index 2bd912e3e..f8697c930 100644 --- a/app/javascript/vehicle_journeys/components/Filters.js +++ b/app/javascript/vehicle_journeys/components/Filters.js @@ -46,10 +46,10 @@ export default function Filters({filters, pagination, missions, onFilter, onRese            <div className='ffg-row'>              {/* Plage horaire */}              <div className='form-group togglable'> -              <label className='control-label'>Plage horaire au départ de la course</label> +              <label className='control-label'>{I18n.t("vehicle_journeys.form.departure_range.label")}</label>                <div className='filter_menu'>                  <div className='form-group time filter_menu-item'> -                  <label className='control-label time'>Début</label> +                  <label className='control-label time'>{I18n.t("vehicle_journeys.form.departure_range.start")}</label>                    <div className='form-inline'>                      <div className='input-group time'>                        <input @@ -73,7 +73,7 @@ export default function Filters({filters, pagination, missions, onFilter, onRese                    </div>                  </div>                  <div className='form-group time filter_menu-item'> -                  <label className='control-label time'>Fin</label> +                  <label className='control-label time'>{I18n.t("vehicle_journeys.form.departure_range.end")}</label>                    <div className='form-inline'>                      <div className='input-group time'>                        <input @@ -101,7 +101,7 @@ export default function Filters({filters, pagination, missions, onFilter, onRese              {/* Switch avec/sans horaires */}              <div className='form-group has_switch'> -              <label className='control-label pull-left'>Afficher les courses sans horaires</label> +              <label className='control-label pull-left'>{I18n.t("vehicle_journeys.form.show_journeys_without_schedule")}</label>                <div className='form-group pull-left' style={{padding: 0}}>                  <div className='checkbox'>                    <label> @@ -110,8 +110,8 @@ export default function Filters({filters, pagination, missions, onFilter, onRese                        onChange={onToggleWithoutSchedule}                        checked={filters.query.withoutSchedule}                        ></input> -                    <span className='switch-label' data-checkedvalue='Non' data-uncheckedvalue='Oui'> -                      {filters.query.withoutSchedule ? 'Oui' : 'Non'} +                    <span className='switch-label' data-checkedvalue={I18n.t("no")} data-uncheckedvalue={I18n.t("yes")}> +                      {filters.query.withoutSchedule ? I18n.t("yes") : I18n.t("no")}                      </span>                    </label>                  </div> @@ -122,7 +122,7 @@ export default function Filters({filters, pagination, missions, onFilter, onRese            <div className="ffg-row">              {/* Switch avec/sans calendrier */}              <div className='form-group has_switch'> -              <label className='control-label pull-left'>Afficher les courses avec calendrier</label> +              <label className='control-label pull-left'>{I18n.t("vehicle_journeys.form.show_journeys_with_calendar")}</label>                <div className='form-group pull-left' style={{padding: 0}}>                  <div className='checkbox'>                    <label> @@ -131,8 +131,8 @@ export default function Filters({filters, pagination, missions, onFilter, onRese                        onChange={onToggleWithoutTimeTable}                        checked={filters.query.withoutTimeTable}                        ></input> -                    <span className='switch-label' data-checkedvalue='Non' data-uncheckedvalue='Oui'> -                      {filters.query.withoutTimeTable ? 'Oui' : 'Non'} +                    <span className='switch-label' data-checkedvalue={I18n.t("no")} data-uncheckedvalue={I18n.t("yes")}> +                      {filters.query.withoutTimeTable ? I18n.t("yes") : I18n.t("no")}                      </span>                    </label>                  </div> diff --git a/app/javascript/vehicle_journeys/components/Navigate.js b/app/javascript/vehicle_journeys/components/Navigate.js index 0158b8392..24843babc 100644 --- a/app/javascript/vehicle_journeys/components/Navigate.js +++ b/app/javascript/vehicle_journeys/components/Navigate.js @@ -17,8 +17,7 @@ export default function Navigate({ dispatch, vehicleJourneys, pagination, status    if(status.fetchSuccess == true) {      return (        <div className="pagination"> -        Liste des horaires {minVJ} à {maxVJ} sur {pagination.totalCount} - +        {I18n.t("vehicle_journeys.vehicle_journeys_matrix.pagination", {minVJ, maxVJ, total:pagination.totalCount})}          <form className='page_links' onSubmit={e => {e.preventDefault()}}>            <button              onClick={e => { @@ -53,4 +52,4 @@ Navigate.propTypes = {    status: PropTypes.object.isRequired,    pagination: PropTypes.object.isRequired,    dispatch: PropTypes.func.isRequired -}
\ No newline at end of file +} diff --git a/app/javascript/vehicle_journeys/components/ToggleArrivals.js b/app/javascript/vehicle_journeys/components/ToggleArrivals.js index 9e7089be5..9a2b0097f 100644 --- a/app/javascript/vehicle_journeys/components/ToggleArrivals.js +++ b/app/javascript/vehicle_journeys/components/ToggleArrivals.js @@ -5,7 +5,7 @@ import PropTypes from 'prop-types'  export default function ToggleArrivals({filters, onToggleArrivals}) {    return (      <div className='has_switch form-group inline'> -      <label htmlFor='toggleArrivals' className='control-label'>Afficher et éditer les horaires d'arrivée</label> +      <label htmlFor='toggleArrivals' className='control-label'>{I18n.t('vehicle_journeys.form.show_arrival_time')}</label>        <div className='form-group'>          <div className='checkbox'>            <label> diff --git a/app/javascript/vehicle_journeys/components/Tools.js b/app/javascript/vehicle_journeys/components/Tools.js index ee02e5a68..22ea44283 100644 --- a/app/javascript/vehicle_journeys/components/Tools.js +++ b/app/javascript/vehicle_journeys/components/Tools.js @@ -44,8 +44,8 @@ export default class Tools extends Component {            <DeleteVehicleJourneys disabled={!this.hasPolicy("destroy") || !editMode}/>          </ul> -        <span className='info-msg'>{actions.getSelected(vehicleJourneys).length} course(s) sélectionnée(s)</span> -        <button className='btn btn-xs btn-link pull-right' onClick={onCancelSelection}>Annuler la sélection</button> +        <span className='info-msg'>{I18n.t('vehicle_journeys.vehicle_journeys_matrix.selected_journeys', {count: actions.getSelected(vehicleJourneys).length})}</span> +        <button className='btn btn-xs btn-link pull-right' onClick={onCancelSelection}>{I18n.t('vehicle_journeys.vehicle_journeys_matrix.cancel_selection')}</button>        </div>      )    } diff --git a/app/javascript/vehicle_journeys/components/VehicleJourney.js b/app/javascript/vehicle_journeys/components/VehicleJourney.js index 99a458f50..e11e91497 100644 --- a/app/javascript/vehicle_journeys/components/VehicleJourney.js +++ b/app/javascript/vehicle_journeys/components/VehicleJourney.js @@ -23,7 +23,7 @@ export default class VehicleJourney extends Component {      let ttURL = refURL + '/time_tables/' + tt.id      return ( -      <a href={ttURL} title='Voir le calendrier'><span className='fa fa-calendar' style={{ color: (tt.color ? tt.color : '#4B4B4B')}}></span></a> +      <a href={ttURL} title={I18n.t('vehicle_journeys.vehicle_journeys_matrix.show_timetable')}><span className='fa fa-calendar' style={{ color: (tt.color ? tt.color : '#4B4B4B')}}></span></a>      )    } @@ -32,7 +32,7 @@ export default class VehicleJourney extends Component {      let ttURL = refURL + '/purchase_windows/' + tt.id      return ( -      <a href={ttURL} title='Voir le calendrier commercial'><span className='fa fa-calendar' style={{color: (tt.color ? tt.color : '')}}></span></a> +      <a href={ttURL} title={I18n.t('vehicle_journeys.vehicle_journeys_matrix.show_purchase_window')}><span className='fa fa-calendar' style={{color: (tt.color ? tt.color : '')}}></span></a>      )    } @@ -48,12 +48,24 @@ export default class VehicleJourney extends Component {      }    } +  hasTimeTable(time_tables, tt) { +    let found = false +    time_tables.map((t, index) => { +      if(t.id == tt.id){ +        found = true +        return +      } +    }) +    return found +  } +    isDisabled(bool1, bool2) {      return (bool1 || bool2)    }    render() {      this.previousCity = undefined +    let detailed_calendars = this.hasFeature('detailed_calendars') && !this.disabled      let {time_tables, purchase_windows} = this.props.value      return ( @@ -65,23 +77,23 @@ export default class VehicleJourney extends Component {            }            >            <div className='strong mb-xs'>{this.props.value.short_id || '-'}</div> -          <div>{this.props.value.published_journey_name && this.props.value.published_journey_name != "non renseigné" ? this.props.value.published_journey_name : '-'}</div> +          <div>{this.props.value.published_journey_name && this.props.value.published_journey_name != I18n.t('undefined') ? this.props.value.published_journey_name : '-'}</div>            <div>{this.props.value.journey_pattern.short_id || '-'}</div>            <div>{this.props.value.company ? this.props.value.company.name : '-'}</div> +          { this.hasFeature('purchase_windows') && +            <div> +            {purchase_windows.slice(0,3).map((tt, i)=> +              <span key={i} className='vj_tt'>{this.purchaseWindowURL(tt)}</span> +            )} +            {purchase_windows.length > 3 && <span className='vj_tt'> + {purchase_windows.length - 3}</span>} +            </div> +          }            <div>              {time_tables.slice(0,3).map((tt, i)=>                <span key={i} className='vj_tt'>{this.timeTableURL(tt)}</span>              )}              {time_tables.length > 3 && <span className='vj_tt'> + {time_tables.length - 3}</span>}            </div> -          { this.hasFeature('purchase_windows') && -            <div> -              {purchase_windows.slice(0,3).map((tt, i)=> -                <span key={i} className='vj_tt'>{this.purchaseWindowURL(tt)}</span> -              )} -              {purchase_windows.length > 3 && <span className='vj_tt'> + {purchase_windows.length - 3}</span>} -            </div> -          }            {!this.props.disabled && <div className={(this.props.value.deletable ? 'disabled ' : '') + 'checkbox'}>              <input                id={this.props.index} @@ -94,13 +106,22 @@ export default class VehicleJourney extends Component {              ></input>              <label htmlFor={this.props.index}></label>            </div>} +            {this.props.disabled && <VehicleJourneyInfoButton vehicleJourney={this.props.value} />} + +          { detailed_calendars && +            <div className="detailed-timetables hidden"> +            {this.props.allTimeTables.map((tt, i) => +              <div key={i} className={(this.hasTimeTable(time_tables, tt) ? "active" : "inactive")}></div> +            )} +            </div> +          }          </div>          {this.props.value.vehicle_journey_at_stops.map((vj, i) =>            <div key={i} className='td text-center'>              <div className={'cellwrap' + (this.cityNameChecker(vj) ? ' headlined' : '')}>                {this.props.filters.toggleArrivals && -                <div data-headline='Arrivée à'> +                <div data-headline={I18n.t("vehicle_journeys.form.arrival_at")}>                    <span className={((this.isDisabled(this.props.value.deletable, vj.dummy) || this.props.filters.policy['vehicle_journeys.update'] == false) ? 'disabled ' : '') + 'input-group time'}>                      <input                        type='number' @@ -131,7 +152,7 @@ export default class VehicleJourney extends Component {                      <span className='sb sb-chrono sb-lg text-warning' data-textinside={vj.delta}></span>                    }                  </div> -                <div data-headline='Départ à'> +                <div data-headline={I18n.t("vehicle_journeys.form.departure_at")}>                    <span className={((this.isDisabled(this.props.value.deletable, vj.dummy) || this.props.filters.policy['vehicle_journeys.update'] == false) ? 'disabled ' : '') + 'input-group time'}>                      <input                        type='number' @@ -174,4 +195,5 @@ VehicleJourney.propTypes = {    onUpdateTime: PropTypes.func.isRequired,    onSelectVehicleJourney: PropTypes.func.isRequired,    vehicleJourneys: PropTypes.object.isRequired, +  allTimeTables: PropTypes.array.isRequired,  } diff --git a/app/javascript/vehicle_journeys/components/VehicleJourneys.js b/app/javascript/vehicle_journeys/components/VehicleJourneys.js index ae852b35a..843aec1a8 100644 --- a/app/javascript/vehicle_journeys/components/VehicleJourneys.js +++ b/app/javascript/vehicle_journeys/components/VehicleJourneys.js @@ -12,6 +12,7 @@ export default class VehicleJourneys extends Component {        this.stopPoints(),        this.props.filters.features      ) +    this.toggleTimetables = this.toggleTimetables.bind(this)    }    isReturn() { @@ -48,9 +49,35 @@ export default class VehicleJourneys extends Component {      return this.headerManager.showHeader(object_id)    } +  allTimeTables() { +    if(this._allTimeTables){ +      return this._allTimeTables +    } +    let keys = [] +    this._allTimeTables = [] +    this.vehicleJourneysList().map((vj, index) => { +      vj.time_tables.map((tt, _) => { +        if(keys.indexOf(tt.id) < 0){ +            keys.push(tt.id) +            this._allTimeTables.push(tt) +        } +      }) +    }) +    return this._allTimeTables +  } + +  toggleTimetables(e) { +    $('.table-2entries .detailed-timetables').toggleClass('hidden') +    $('.table-2entries .detailed-timetables-bt').toggleClass('active') +    this.componentDidUpdate() +    e.preventDefault() +    false +  } +    componentDidUpdate(prevProps, prevState) {      if(this.props.status.isFetching == false){        $('.table-2entries').each(function() { +        $(this).find('.th').css('height', 'auto')          var refH = []          var refCol = [] @@ -91,9 +118,19 @@ export default class VehicleJourneys extends Component {      }    } +  timeTableURL(tt) { +    let refURL = window.location.pathname.split('/', 3).join('/') +    let ttURL = refURL + '/time_tables/' + tt.id + +    return ( +      <a href={ttURL} title='Voir le calendrier'><span className='fa fa-calendar' style={{color: (tt.color ? tt.color : '#4B4B4B')}}></span>{tt.days || tt.comment}</a> +    ) +  } +    render() {      this.previousBreakpoint = undefined - +    this._allTimeTables = null +    let detailed_calendars = this.hasFeature('detailed_calendars') && !this.isReturn() && (this.allTimeTables().length > 0)      if(this.props.status.isFetching == true) {        return (          <div className="isLoading" style={{marginTop: 80, marginBottom: 80}}> @@ -106,14 +143,14 @@ export default class VehicleJourneys 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.tc("error")}</strong> +                {I18n.t("vehicle_journeys.vehicle_journeys_matrix.fetching_error")}                </div>              )}              { this.vehicleJourneysList().errors && this.vehicleJourneysList().errors.length && _.some(this.vehicleJourneysList(), 'errors') && (                <div className="alert alert-danger mt-sm"> -                <strong>Erreur : </strong> +                <strong>{I18n.tc("error")}</strong>                  {this.vehicleJourneysList().map((vj, index) =>                    vj.errors && vj.errors.map((err, i) => {                      return ( @@ -129,12 +166,32 @@ export default class VehicleJourneys extends Component {              <div className={'table table-2entries mt-sm mb-sm' + ((this.vehicleJourneysList().length > 0) ? '' : ' no_result')}>                <div className='t2e-head w20'>                  <div className='th'> -                  <div className='strong mb-xs'>ID course</div> -                  <div>Nom course</div> -                  <div>ID mission</div> -                  <div>Transporteur</div> -                  <div>Calendriers</div> -                  { this.hasFeature('purchase_windows') && <div>Calendriers Commerciaux</div> } +                  <div className='strong mb-xs'>{I18n.attribute_name("vehicle_journey", "id")}</div> +                  <div>{I18n.attribute_name("vehicle_journey", "name")}</div> +                  <div>{I18n.attribute_name("vehicle_journey", "journey_pattern_id")}</div> +                  <div>{I18n.model_name("company")}</div> +                  { this.hasFeature('purchase_windows') && <div>{I18n.model_name("purchase_window", "plural": true)}</div> } +                  <div> +                    { detailed_calendars && +                      <a href='#' onClick={this.toggleTimetables} className='detailed-timetables-bt'> +                        <span className='fa fa-angle-up'></span> +                        {I18n.model_name("time_table", "plural": true)} +                      </a> +                    } +                    { !detailed_calendars && I18n.model_name("time_table", "plural": true)} +                  </div> +                  { !this.isReturn() && +                    <div className="detailed-timetables hidden"> +                      {this.allTimeTables().map((tt, i)=> +                        <div key={i}> +                          <p> +                            {this.timeTableURL(tt)} +                          </p> +                          <p>{tt.bounding_dates}</p> +                        </div> +                      )} +                    </div> +                  }                  </div>                  {this.stopPoints().map((sp, i) =>{                    return ( @@ -159,6 +216,7 @@ export default class VehicleJourneys extends Component {                        onSelectVehicleJourney={this.props.onSelectVehicleJourney}                        vehicleJourneys={this}                        disabled={this.isReturn()} +                      allTimeTables={this.allTimeTables()}                        />                    )}                  </div> diff --git a/app/javascript/vehicle_journeys/components/tools/EditVehicleJourney.js b/app/javascript/vehicle_journeys/components/tools/EditVehicleJourney.js index f6a0e3c61..d3c01f154 100644 --- a/app/javascript/vehicle_journeys/components/tools/EditVehicleJourney.js +++ b/app/javascript/vehicle_journeys/components/tools/EditVehicleJourney.js @@ -59,7 +59,7 @@ export default class EditVehicleJourney extends Component {                <div className='modal-dialog'>                  <div className='modal-content'>                    <div className='modal-header'> -                    <h4 className='modal-title'>Informations</h4> +                    <h4 className='modal-title'>{I18n.t('vehicle_journeys.form.infos')}</h4>                      <span type="button" className="close modal-close" data-dismiss="modal">×</span>                    </div> @@ -69,7 +69,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'>Nom de la course</label> +                                <label className='control-label'>{I18n.attribute_name('vehicle_journey', 'journey_name')}</label>                                  <input                                    type='text'                                    ref='published_journey_name' @@ -82,7 +82,7 @@ export default class EditVehicleJourney 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'>Mission</label> +                                <label className='control-label'>{I18n.attribute_name('vehicle_journey', 'journey_pattern')}</label>                                  <input                                    type='text'                                    className='form-control' @@ -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'>Numéro de train</label> +                              <label className='control-label'>{I18n.attribute_name('vehicle_journey', 'company')}</label>                                <input                                  type='text'                                  ref='published_journey_identifier' @@ -109,7 +109,7 @@ export default class EditVehicleJourney 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'>Transporteur</label> +                              <label className='control-label'>{I18n.attribute_name('vehicle_journey', 'company')}</label>                                <CompanySelect2                                  editModal={this.props.modal.type == "edit"}                                  editMode={this.editMode()} @@ -124,29 +124,29 @@ 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'>Mode de transport</label> +                              <label className='control-label'>{I18n.attribute_name('vehicle_journey', 'transport_mode')}</label>                                <input                                  type='text'                                  className='form-control' -                                value={window.I18n.fr.enumerize.transport_mode[this.props.modal.modalProps.vehicleJourney.transport_mode]} +                                value={I18n.enumerize('transport_mode', this.props.modal.modalProps.vehicleJourney.transport_mode)}                                  disabled={true}                                />                              </div>                            </div>                            <div className='col-lg-6 col-md-6 col-sm-6 col-xs-12'>                              <div className='form-group'> -                              <label className='control-label'>Sous mode de transport</label> +                              <label className='control-label'>{I18n.attribute_name('vehicle_journey', 'transport_submode')}</label>                                <input                                  type='text'                                  className='form-control' -                                value={window.I18n.fr.enumerize.transport_submode[this.props.modal.modalProps.vehicleJourney.transport_submode]} +                                value={I18n.enumerize('transport_submode', this.props.modal.modalProps.vehicleJourney.transport_submode)}                                  disabled={true}                                />                              </div>                            </div>                          </div>                          <div className='form-group'> -                          <label className='control-label'>Signature métier</label> +                          <label className='control-label'>{I18n.attribute_name('vehicle_journey', 'checksum')}</label>                              <input                              type='text'                              ref='checksum' diff --git a/app/javascript/vehicle_journeys/components/tools/VehicleJourneyInfoButton.js b/app/javascript/vehicle_journeys/components/tools/VehicleJourneyInfoButton.js index a63a1d701..538bbdbd6 100644 --- a/app/javascript/vehicle_journeys/components/tools/VehicleJourneyInfoButton.js +++ b/app/javascript/vehicle_journeys/components/tools/VehicleJourneyInfoButton.js @@ -10,7 +10,7 @@ export default class VehicleJourneyInfoButton extends Component {    render() {      return ( -      <li className='st_action'> +      <div className='info-button'>          <button            type='button'            data-toggle='modal' @@ -19,7 +19,7 @@ export default class VehicleJourneyInfoButton extends Component {          >            <span className='fa fa-info'></span>          </button> -      </li> +      </div>      )    }  } diff --git a/app/javascript/vehicle_journeys/components/tools/select2s/MissionSelect2.js b/app/javascript/vehicle_journeys/components/tools/select2s/MissionSelect2.js index 7ab85a1ea..72dbd0152 100644 --- a/app/javascript/vehicle_journeys/components/tools/select2s/MissionSelect2.js +++ b/app/javascript/vehicle_journeys/components/tools/select2s/MissionSelect2.js @@ -29,11 +29,11 @@ export default class BSelect4 extends Component {          val = this.props.selection.selectedJPModal        }      } -    if(this.useAjax()){ -      val = val.published_name -    } -    else{ -      if(val){ +    if(val){ +      if(this.useAjax()){ +        val = val.published_name +      } +      else{          val = val.id        }      } diff --git a/app/javascript/vehicle_journeys/reducers/vehicleJourneys.js b/app/javascript/vehicle_journeys/reducers/vehicleJourneys.js index 1a15ec46d..383dea4a0 100644 --- a/app/javascript/vehicle_journeys/reducers/vehicleJourneys.js +++ b/app/javascript/vehicle_journeys/reducers/vehicleJourneys.js @@ -19,26 +19,17 @@ const vehicleJourney= (state = {}, action, keep) => {          current_time.minute = parseInt(action.data["start_time.minute"].value) || 0        }        _.each(action.stopPointsList, (sp) =>{ +        let inJourney = false          if(action.selectedJourneyPattern.full_schedule && action.selectedJourneyPattern.costs && 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) -          let delta_hour = parseInt(delta/60) -          let delta_minute = delta - 60*delta_hour -          current_time.hour += delta_hour -          current_time.minute += delta_minute -          let extra_hours = parseInt(current_time.minute/60) -          current_time.hour += extra_hours -          current_time.minute -= extra_hours*60 -          current_time.hour = current_time.hour % 24 +          current_time = actions.addMinutesToTime(current_time, delta)            prevSp = sp +          inJourney = true          }          let offsetHours = sp.time_zone_offset / 3600          let offsetminutes = sp.time_zone_offset/60 - 60*offsetHours          let newVjas = {            delta: 0, -          departure_time:{ -            hour: (24 + current_time.hour + offsetHours) % 24, -            minute: current_time.minute + offsetminutes -          },            arrival_time:{              hour: (24 + current_time.hour + offsetHours) % 24,              minute: current_time.minute + offsetminutes @@ -47,6 +38,16 @@ const vehicleJourney= (state = {}, action, keep) => {            stop_area_cityname: sp.city_name,            dummy: true          } + +        if(sp.waiting_time && inJourney){ +          current_time = actions.addMinutesToTime(current_time, parseInt(sp.waiting_time)) +        } + +        newVjas.departure_time = { +          hour: (24 + current_time.hour + offsetHours) % 24, +          minute: current_time.minute + offsetminutes +        } +          if(current_time.hour + offsetHours > 24){            newVjas.departure_day_offset = 1            newVjas.arrival_day_offset = 1 diff --git a/app/models/calendar.rb b/app/models/calendar.rb index 561a2e3f7..84b569ab4 100644 --- a/app/models/calendar.rb +++ b/app/models/calendar.rb @@ -10,8 +10,9 @@ class Calendar < ActiveRecord::Base    has_paper_trail class_name: 'PublicVersion'    belongs_to :organisation +  belongs_to :workgroup -  validates_presence_of :name, :short_name, :organisation +  validates_presence_of :name, :short_name, :organisation, :workgroup    validates_uniqueness_of :short_name    has_many :time_tables diff --git a/app/models/chouette/company.rb b/app/models/chouette/company.rb index b3d40ab96..53e412600 100644 --- a/app/models/chouette/company.rb +++ b/app/models/chouette/company.rb @@ -15,6 +15,5 @@ module Chouette        [:organizational_unit, :operating_department_name, :code, :phone, :fax, :email, :url, :time_zone]      end -    end  end diff --git a/app/models/chouette/journey_pattern.rb b/app/models/chouette/journey_pattern.rb index aa9fdb810..830e985d9 100644 --- a/app/models/chouette/journey_pattern.rb +++ b/app/models/chouette/journey_pattern.rb @@ -170,5 +170,21 @@ module Chouette        end        full      end + +    def set_distances distances +      raise "inconsistent data: #{distances.count} values for #{stop_points.count} stops" unless distances.count == stop_points.count +      prev = distances[0].to_i +      _costs = self.costs +      distances[1..-1].each_with_index do |distance, i| +        distance = distance.to_i +        relative = distance - prev +        prev = distance +        start, stop = stop_points[i..i+1] +        key = "#{start.stop_area_id}-#{stop.stop_area_id}" +        _costs[key] ||= {} +        _costs[key]["distance"] = relative +      end +      self.costs = _costs +    end    end  end diff --git a/app/models/chouette/line.rb b/app/models/chouette/line.rb index ba2e2755d..d077d5c9d 100644 --- a/app/models/chouette/line.rb +++ b/app/models/chouette/line.rb @@ -41,6 +41,7 @@ module Chouette      validates_presence_of :name +      scope :by_text, ->(text) { where('lower(name) LIKE :t or lower(published_name) LIKE :t or lower(objectid) LIKE :t or lower(comment) LIKE :t or lower(number) LIKE :t',        t: "%#{text.downcase}%") } @@ -80,6 +81,14 @@ module Chouette        line_referential.companies.where(id: ([company_id] + Array(secondary_company_ids)).compact)      end +    def deactivate +      self.deactivated = true +    end + +    def activate +      self.deactivated = false +    end +      def deactivate!        update_attribute :deactivated, true      end diff --git a/app/models/chouette/purchase_window.rb b/app/models/chouette/purchase_window.rb index 334493015..157390a21 100644 --- a/app/models/chouette/purchase_window.rb +++ b/app/models/chouette/purchase_window.rb @@ -19,6 +19,7 @@ module Chouette      scope :contains_date, ->(date) { where('date ? <@ any (date_ranges)', date) }      scope :overlap_dates, ->(date_range) { where('daterange(?, ?) && any (date_ranges)', date_range.first, date_range.last + 1.day) } +    scope :matching_dates, ->(date_range) { where('ARRAY[daterange(?, ?)] = date_ranges', date_range.first, date_range.last + 1.day) }      def self.ransackable_scopes(auth_object = nil)        [:contains_date] diff --git a/app/models/chouette/route.rb b/app/models/chouette/route.rb index 47c18af09..3729deb7d 100644 --- a/app/models/chouette/route.rb +++ b/app/models/chouette/route.rb @@ -133,7 +133,7 @@ module Chouette      def checksum_attributes        values = self.slice(*['name', 'published_name', 'wayback']).values        values.tap do |attrs| -        attrs << self.stop_points.sort_by(&:position).map{|sp| "#{sp.stop_area.user_objectid}#{sp.for_boarding}#{sp.for_alighting}" }.join +        attrs << self.stop_points.sort_by(&:position).map{|sp| [sp.stop_area.user_objectid, sp.for_boarding, sp.for_alighting]}          attrs << self.routing_constraint_zones.map(&:checksum)        end      end @@ -185,6 +185,12 @@ module Chouette        return true      end +    def full_journey_pattern +      journey_pattern = journey_patterns.find_or_create_by registration_number: self.number, name: self.name +      journey_pattern.stop_points = self.stop_points +      journey_pattern +    end +      protected      def self.vehicle_journeys_timeless(stop_point_id) diff --git a/app/models/chouette/routing_constraint_zone.rb b/app/models/chouette/routing_constraint_zone.rb index 903922241..58703598e 100644 --- a/app/models/chouette/routing_constraint_zone.rb +++ b/app/models/chouette/routing_constraint_zone.rb @@ -25,7 +25,9 @@ module Chouette      end      def checksum_attributes -      self.stop_points.map(&:stop_area).map(&:user_objectid) +      [ +        self.stop_points.map(&:stop_area).map(&:user_objectid) +      ]      end      def stop_points_belong_to_route diff --git a/app/models/chouette/stop_area.rb b/app/models/chouette/stop_area.rb index bb8747faa..7170dd217 100644 --- a/app/models/chouette/stop_area.rb +++ b/app/models/chouette/stop_area.rb @@ -369,6 +369,14 @@ module Chouette        !activated?      end +    def activate +      self.deleted_at = nil +    end + +    def deactivate +      self.deleted_at = Time.now +    end +      def activate!        update_attribute :deleted_at, nil      end @@ -384,8 +392,8 @@ module Chouette      def country_name        return unless country_code -        country = ISO3166::Country[country_code] +      return unless country        country.translations[I18n.locale.to_s] || country.name      end diff --git a/app/models/chouette/time_table.rb b/app/models/chouette/time_table.rb index 15b22b671..b76de852a 100644 --- a/app/models/chouette/time_table.rb +++ b/app/models/chouette/time_table.rb @@ -44,10 +44,10 @@ module Chouette          attrs << self.int_day_types          dates = self.dates          dates += TimeTableDate.where(time_table_id: self.id) -        attrs << dates.map(&:checksum).map(&:to_s).sort +        attrs << dates.map(&:checksum).map(&:to_s).uniq.sort          periods = self.periods          periods += TimeTablePeriod.where(time_table_id: self.id) -        attrs << periods.map(&:checksum).map(&:to_s).sort +        attrs << periods.map(&:checksum).map(&:to_s).uniq.sort        end      end diff --git a/app/models/chouette/vehicle_journey.rb b/app/models/chouette/vehicle_journey.rb index 4a6ba3f75..1a79db823 100644 --- a/app/models/chouette/vehicle_journey.rb +++ b/app/models/chouette/vehicle_journey.rb @@ -105,7 +105,7 @@ module Chouette          attrs << self.try(:company).try(:get_objectid).try(:local_id)          attrs << self.footnotes.map(&:checksum).sort          vjas =  self.vehicle_journey_at_stops -        vjas += VehicleJourneyAtStop.where(vehicle_journey_id: self.id) +        vjas += VehicleJourneyAtStop.where(vehicle_journey_id: self.id) unless self.new_record?          attrs << vjas.uniq.sort_by { |s| s.stop_point&.position }.map(&:checksum).sort        end      end @@ -381,8 +381,8 @@ module Chouette      end      def self.lines -      lines_query = joins(:route).select("routes.line_id").to_sql -      Chouette::Line.where("id IN (#{lines_query})") +      lines_query = joins(:route).select("routes.line_id").reorder(nil).except(:group).pluck(:'routes.line_id') +      Chouette::Line.where(id: lines_query)      end    end  end diff --git a/app/models/chouette/vehicle_journey_at_stop.rb b/app/models/chouette/vehicle_journey_at_stop.rb index eda711ade..3b4f35f13 100644 --- a/app/models/chouette/vehicle_journey_at_stop.rb +++ b/app/models/chouette/vehicle_journey_at_stop.rb @@ -41,7 +41,7 @@ module Chouette            :arrival_day_offset,            I18n.t(              'vehicle_journey_at_stops.errors.day_offset_must_not_exceed_max', -            short_id: vehicle_journey.get_objectid.short_id, +            short_id: vehicle_journey&.get_objectid&.short_id,              max: DAY_OFFSET_MAX + 1            )          ) @@ -52,7 +52,7 @@ module Chouette            :departure_day_offset,            I18n.t(              'vehicle_journey_at_stops.errors.day_offset_must_not_exceed_max', -            short_id: vehicle_journey.get_objectid.short_id, +            short_id: vehicle_journey&.get_objectid&.short_id,              max: DAY_OFFSET_MAX + 1            )          ) @@ -69,8 +69,8 @@ module Chouette      def checksum_attributes        [].tap do |attrs| -        attrs << self.departure_time.try(:to_s, :time) -        attrs << self.arrival_time.try(:to_s, :time) +        attrs << self.departure_time&.utc.try(:to_s, :time) +        attrs << self.arrival_time&.utc.try(:to_s, :time)          attrs << self.departure_day_offset.to_s          attrs << self.arrival_day_offset.to_s        end diff --git a/app/models/chouette/vehicle_journey_at_stops_day_offset.rb b/app/models/chouette/vehicle_journey_at_stops_day_offset.rb index b2cb90d11..7497cd72c 100644 --- a/app/models/chouette/vehicle_journey_at_stops_day_offset.rb +++ b/app/models/chouette/vehicle_journey_at_stops_day_offset.rb @@ -11,13 +11,19 @@ module Chouette        @at_stops.inject(nil) do |prior_stop, stop|          next stop if prior_stop.nil? -        if stop.arrival_time < prior_stop.departure_time || -            stop.arrival_time < prior_stop.arrival_time +        # we only compare time of the day, not actual times +        stop_arrival_time = stop.arrival_time - stop.arrival_time.to_date.to_time +        stop_departure_time = stop.departure_time - stop.departure_time.to_date.to_time +        prior_stop_arrival_time = prior_stop.arrival_time - prior_stop.arrival_time.to_date.to_time +        prior_stop_departure_time = prior_stop.departure_time - prior_stop.departure_time.to_date.to_time + +        if stop_arrival_time < prior_stop_departure_time || +            stop_arrival_time < prior_stop_arrival_time            arrival_offset += 1          end -        if stop.departure_time < stop.arrival_time || -            stop.departure_time < prior_stop.departure_time +        if stop_departure_time < stop_arrival_time || +            stop_departure_time < prior_stop_departure_time            departure_offset += 1          end @@ -39,4 +45,4 @@ module Chouette        save      end    end -end
\ No newline at end of file +end diff --git a/app/models/compliance_check_set.rb b/app/models/compliance_check_set.rb index f4c44d26d..289fc134f 100644 --- a/app/models/compliance_check_set.rb +++ b/app/models/compliance_check_set.rb @@ -19,6 +19,20 @@ class ComplianceCheckSet < ActiveRecord::Base      where('created_at BETWEEN :begin AND :end', begin: period_range.begin, end: period_range.end)    end +  scope :blocked, -> { where('created_at < ? AND status = ?', 4.hours.ago, 'running') } + +  def self.finished_statuses +    %w(successful failed warning aborted canceled) +  end + +  def self.abort_old +    where( +      'created_at < ? AND status NOT IN (?)', +      4.hours.ago, +      finished_statuses +    ).update_all(status: 'aborted') +  end +    def notify_parent      if parent        # parent.child_change diff --git a/app/models/compliance_control.rb b/app/models/compliance_control.rb index 2bde5b95a..298a63ab9 100644 --- a/app/models/compliance_control.rb +++ b/app/models/compliance_control.rb @@ -3,8 +3,6 @@ class ComplianceControl < ActiveRecord::Base    class << self      def criticities; %i(warning error) end      def default_code; "" end -    def prerequisite; I18n.t('compliance_controls.metas.no_prerequisite'); end -    def predicate; I18n.t("compliance_controls.#{self.name.underscore}.description") end      def dynamic_attributes        stored_attributes[:control_attributes] || []      end @@ -65,6 +63,9 @@ def initialize(attributes = {})    self.origin_code ||= self.class.default_code  end +def predicate; I18n.t("compliance_controls.#{self.class.name.underscore}.description") end +def prerequisite; I18n.t('compliance_controls.metas.no_prerequisite'); end +  end  # Ensure STI subclasses are loaded diff --git a/app/models/concerns/checksum_support.rb b/app/models/concerns/checksum_support.rb index a76995b0f..92103798e 100644 --- a/app/models/concerns/checksum_support.rb +++ b/app/models/concerns/checksum_support.rb @@ -24,10 +24,29 @@ module ChecksumSupport      self.attributes.values    end +  def checksum_replace_nil_or_empty_values values +    # Replace empty array by nil & nil by VALUE_FOR_NIL_ATTRIBUTE +    values +      .map { |x| x.present? && x || VALUE_FOR_NIL_ATTRIBUTE } +      .map do |item| +        item = +          if item.kind_of?(Array) +            checksum_replace_nil_or_empty_values(item) +          else +            item +          end +      end +  end +    def current_checksum_source -    source = self.checksum_attributes.map{ |x| x unless x.try(:empty?) } -    source = source.map{ |x| x || VALUE_FOR_NIL_ATTRIBUTE } -    source.map(&:to_s).join(SEPARATOR) +    source = checksum_replace_nil_or_empty_values(self.checksum_attributes) +    source.map{ |item| +      if item.kind_of?(Array) +        item.map{ |x| x.kind_of?(Array) ? "(#{x.join(',')})" : x }.join(',') +      else +        item +      end +    }.join(SEPARATOR)    end    def set_current_checksum_source diff --git a/app/models/concerns/min_max_values_validation.rb b/app/models/concerns/min_max_values_validation.rb index 9b2e0d548..eff779d81 100644 --- a/app/models/concerns/min_max_values_validation.rb +++ b/app/models/concerns/min_max_values_validation.rb @@ -2,6 +2,7 @@ module MinMaxValuesValidation    extend ActiveSupport::Concern    included do +    validates_presence_of :minimum, :maximum      validate :min_max_values_validation    end diff --git a/app/models/concerns/timetable_support.rb b/app/models/concerns/timetable_support.rb index d2bc99d51..5242abc33 100644 --- a/app/models/concerns/timetable_support.rb +++ b/app/models/concerns/timetable_support.rb @@ -100,6 +100,7 @@ module TimetableSupport        period.period_start = Date.parse(item['period_start'])        period.period_end   = Date.parse(item['period_end']) +        period.save if period.is_a?(ActiveRecord::Base) && period.changed?        item['id'] = period.id diff --git a/app/models/import.rb b/app/models/import.rb index 049a65f40..29aadcd56 100644 --- a/app/models/import.rb +++ b/app/models/import.rb @@ -13,6 +13,8 @@ class Import < ActiveRecord::Base      where('started_at BETWEEN :begin AND :end', begin: period_range.begin, end: period_range.end)     end +  scope :blocked, -> { where('created_at < ? AND status = ?', 4.hours.ago, 'running') } +    extend Enumerize    enumerize :status, in: %w(new pending successful warning failed running aborted canceled), scope: true, default: :new @@ -42,6 +44,14 @@ class Import < ActiveRecord::Base      %w(successful failed warning aborted canceled)    end +  def self.abort_old +    where( +      'created_at < ? AND status NOT IN (?)', +      4.hours.ago, +      finished_statuses +    ).update_all(status: 'aborted') +  end +    def notify_parent      parent.child_change      update(notified_parent_at: DateTime.now) diff --git a/app/models/line_control/route.rb b/app/models/line_control/route.rb index b4b2bd9d8..b6c1f3630 100644 --- a/app/models/line_control/route.rb +++ b/app/models/line_control/route.rb @@ -3,6 +3,6 @@ module LineControl      def self.default_code; "3-Line-1" end -    def self.prerequisite; I18n.t("compliance_controls.#{self.name.underscore}.prerequisite") end +    def prerequisite; I18n.t("compliance_controls.#{self.class.name.underscore}.prerequisite") end    end  end diff --git a/app/models/organisation.rb b/app/models/organisation.rb index 0598bfecf..745bc0d22 100644 --- a/app/models/organisation.rb +++ b/app/models/organisation.rb @@ -82,4 +82,8 @@ class Organisation < ActiveRecord::Base      features && features.include?(feature.to_s)    end +  def default_workbench +    workbenches.default +  end +  end diff --git a/app/models/referential.rb b/app/models/referential.rb index baaa354da..09c2e7d34 100644 --- a/app/models/referential.rb +++ b/app/models/referential.rb @@ -51,7 +51,9 @@ class Referential < ActiveRecord::Base    belongs_to :stop_area_referential    validates_presence_of :stop_area_referential    has_many :stop_areas, through: :stop_area_referential +    belongs_to :workbench +  delegate :workgroup, to: :workbench, allow_nil: true    belongs_to :referential_suite @@ -62,6 +64,7 @@ class Referential < ActiveRecord::Base    scope :order_by_validity_period, ->(dir) { joins(:metadatas).order("unnest(periodes) #{dir}") }    scope :order_by_lines, ->(dir) { joins(:metadatas).group("referentials.id").order("sum(array_length(referential_metadata.line_ids,1)) #{dir}") }    scope :not_in_referential_suite, -> { where referential_suite_id: nil } +  scope :blocked, -> { where('ready = ? AND created_at < ?', false, 4.hours.ago) }    def save_with_table_lock_timeout(options = {})      save_without_table_lock_timeout(options) @@ -153,10 +156,6 @@ class Referential < ActiveRecord::Base      end    end -  def stop_areas -    Chouette::StopArea.all -  end -    def access_points      Chouette::AccessPoint.all    end diff --git a/app/models/route_control/opposite_route.rb b/app/models/route_control/opposite_route.rb index d5616ca6f..e0e9572ce 100644 --- a/app/models/route_control/opposite_route.rb +++ b/app/models/route_control/opposite_route.rb @@ -4,6 +4,6 @@ module RouteControl      def self.default_code; "3-Route-2" end -    def self.prerequisite; I18n.t("compliance_controls.#{self.name.underscore}.prerequisite") end +    def prerequisite; I18n.t("compliance_controls.#{self.class.name.underscore}.prerequisite") end    end  end diff --git a/app/models/route_control/opposite_route_terminus.rb b/app/models/route_control/opposite_route_terminus.rb index 24c557734..e70d2c702 100644 --- a/app/models/route_control/opposite_route_terminus.rb +++ b/app/models/route_control/opposite_route_terminus.rb @@ -3,6 +3,6 @@ module RouteControl      def self.default_code; "3-Route-5" end -    def self.prerequisite; I18n.t("compliance_controls.#{self.name.underscore}.prerequisite") end      +    def prerequisite; I18n.t("compliance_controls.#{self.class.name.underscore}.prerequisite") end         end  end diff --git a/app/models/simple_importer.rb b/app/models/simple_importer.rb new file mode 100644 index 000000000..b824d596d --- /dev/null +++ b/app/models/simple_importer.rb @@ -0,0 +1,422 @@ +class SimpleImporter < ActiveRecord::Base +  attr_accessor :configuration + +  def self.define name +    @importers ||= {} +    configuration = Configuration.new name +    yield configuration +    configuration.validate! +    @importers[name.to_sym] = configuration +  end + +  def self.find_configuration name +    @importers ||= {} +    configuration = @importers[name.to_sym] +    raise "Importer not found: #{name}" unless configuration +    configuration +  end + +  def initialize *args +    super *args +    self.configuration = self.class.find_configuration self.configuration_name +    self.journal ||= [] +  end + +  def configure +    new_config = configuration.duplicate +    yield new_config +    new_config.validate! +    self.configuration = new_config +  end + +  def context +    self.configuration.context +  end + +  def resolve col_name, value, &block +    val = block.call(value) +    return val if val.present? +    @resolution_queue[[col_name.to_s, value]].push({record: @current_record, attribute: @current_attribute, block: block}) +    nil +  end + +  def import opts={} +    @verbose = opts.delete :verbose + + +    @resolution_queue = Hash.new{|h,k| h[k] = []} +    @errors = [] +    @messages = [] +    @number_of_lines = 0 +    @padding = 1 +    @current_line = 0 +    fail_with_error "File not found: #{self.filepath}" do +      @number_of_lines = CSV.read(self.filepath, self.configuration.csv_options).length +      @padding = [1, Math.log(@number_of_lines, 10).ceil()].max +    end + + +    self.configuration.before_actions(:parsing).each do |action| action.call self end + +    @statuses = "" + +    if ENV["NO_TRANSACTION"] +      process_csv_file +    else +      ActiveRecord::Base.transaction do +        process_csv_file +      end +    end +    self.status ||= :success +  rescue FailedImport +    self.status = :failed +  ensure +    self.save! +  end + +  def fail_with_error msg=nil, opts={} +    begin +      yield +    rescue => e +      msg = msg.call if msg.is_a?(Proc) +      custom_print "\nFAILED: \n errors: #{msg}\n exception: #{e.message}\n#{e.backtrace.join("\n")}", color: :red unless self.configuration.ignore_failures +      push_in_journal({message: msg, error: e.message, event: :error, kind: :error}) +      @new_status = colorize("x", :red) +      if self.configuration.ignore_failures +        raise FailedRow if opts[:abort_row] +      else +        raise FailedImport +      end +    end +  end + +  def encode_string s +    s.encode("utf-8").force_encoding("utf-8") +  end + +  def dump_csv_from_context +    filepath = "./#{self.configuration_name}_#{Time.now.strftime "%y%m%d%H%M"}.csv" +    # for some reason, context[:csv].to_csv does not work +    CSV.open(filepath, 'w') do |csv| +      header = true +      context[:csv].each do |row| +        csv << row.headers if header +        csv << row.fields +        header = false +      end +    end +    log "CSV file dumped in #{filepath}" +  end + +  def log msg, opts={} +    msg = colorize msg, opts[:color] if opts[:color] +    if opts[:append] +      @messages[-1] = (@messages[-1] || "") + msg +    else +      @messages << msg +    end +    print_state +  end + +  protected + +  def process_csv_file +    self.configuration.before_actions(:all).each do |action| action.call self end +    log "Starting import ...", color: :green + +    (context[:csv] || CSV.read(filepath, self.configuration.csv_options)).each do |row| +      @current_row = row +      @new_status = nil +      begin +        handle_row row +        fail_with_error ->(){ @current_record.errors.messages } do +          new_record = @current_record&.new_record? +          @new_status ||= new_record ? colorize("✓", :green) : colorize("-", :orange) +          @event = new_record ? :creation : :update +          self.configuration.before_actions(:each_save).each do |action| +            action.call self, @current_record +          end +          ### This could fail if the record has a mandatory relation which is not yet resolved +          ### TODO: do not attempt to save if the current record if waiting for resolution +          ###       and fail at the end if there remains unresolved relations +          if @current_record +            if self.configuration.ignore_failures +              unless @current_record.save +                @new_status = colorize("x", :red) +                push_in_journal({message: "errors: #{@current_record.errors.messages}", error: "invalid record", event: :error, kind: :error}) +              end +            else +              @current_record.save! +            end +          end +          self.configuration.after_actions(:each_save).each do |action| +            action.call self, @current_record +          end +        end +      rescue FailedRow +        @new_status = colorize("x", :red) +      end +      push_in_journal({event: @event, kind: :log}) if @current_record&.valid? +      @statuses += @new_status +      self.configuration.columns.each do |col| +      if @current_record && col.name && @resolution_queue.any? +        val = @current_record.send col[:attribute] +        (@resolution_queue.delete([col.name, val]) || []).each do |res| +          record = res[:record] +          attribute = res[:attribute] +          value = res[:block].call(val, record) +          record.send "#{attribute}=", value +          record.save! +        end +      end +    end +      print_state +      @current_line += 1 +    end + +    begin +      self.configuration.after_actions(:all).each do |action| +        action.call self +      end +    rescue FailedRow +    end +  end + +  def handle_row row +    if self.configuration.get_custom_handler +      instance_exec(row, &self.configuration.get_custom_handler) +    else +      fail_with_error "", abort_row: true do +        @current_record = self.configuration.find_record row +        self.configuration.columns.each do |col| +          @current_attribute = col[:attribute] +          val = col[:value] +          if val.nil? || val.is_a?(Proc) +            if row.has_key? col.name +              if val.is_a?(Proc) +                val = instance_exec(row[col.name], &val) +              else +                val = row[col.name] +              end +            else +              push_in_journal({event: :column_not_found, message: "Column not found: #{col.name}", kind: :warning}) +              self.status ||= :success_with_warnings +            end +          end + +          if val.nil? && col.required? +            raise "MISSING VALUE FOR COLUMN #{col.name}" +          end +          val = encode_string(val) if val.is_a?(String) +          @current_record.send "#{@current_attribute}=", val if val +        end +      end +    end +  end + +  def push_in_journal data +    line = @current_line + 1 +    line += 1 if configuration.headers +    self.journal.push data.update(line: line, row: @current_row) +    if data[:kind] == :error || data[:kind] == :warning +      @errors.push data +    end +  end + +  def colorize txt, color +    color = { +      red: "31", +      green: "32", +      orange: "33", +    }[color] || "33" +    "\e[#{color}m#{txt}\e[0m" +  end + +  def print_state +    return unless @verbose + +    @status_width ||= begin +      term_width = %x(tput cols).to_i +      term_width - @padding - 10 +    rescue +      100 +    end + +    @status_height ||= begin +      term_height = %x(tput lines).to_i +      term_height - 3 +    rescue +      50 +    end + +    full_status = @statuses || "" +    full_status = full_status.last(@status_width*10) || "" +    padding_size = [(@number_of_lines - @current_line - 1), (@status_width - full_status.size/10)].min +    full_status = "#{full_status}#{"."*[padding_size, 0].max}" + +    msg = "#{"%#{@padding}d" % (@current_line + 1)}/#{@number_of_lines}: #{full_status}" + +    lines_count = [(@status_height / 2) - 3, 1].max + +    if @messages.any? +      msg += "\n\n" +      msg += colorize "=== MESSAGES (#{@messages.count}) ===\n", :green +      msg += "[...]\n" if @messages.count > lines_count +      msg += @messages.last(lines_count).join("\n") +      msg += "\n"*[lines_count-@messages.count, 0].max +    end + +    if @errors.any? +      msg += "\n\n" +      msg += colorize "=== ERRORS (#{@errors.count}) ===\n", :red +      msg += "[...]\n" if @errors.count > lines_count +      msg += @errors.last(lines_count).map do |j| +        kind = j[:kind] +        kind = colorize(kind, kind == :error ? :red : :orange) +        encode_string "[#{kind}]\t\tL#{j[:line]}\t#{j[:error]}\t\t#{j[:message]}" +      end.join("\n") +    end +    custom_print msg, clear: true +  end + +  def custom_print msg, opts={} +    return unless @verbose +    out = "" +    msg = colorize(msg, opts[:color]) if opts[:color] +    puts "\e[H\e[2J" if opts[:clear] +    out += msg +    print out +  end + +  class FailedImport < RuntimeError +  end + +  class FailedRow < RuntimeError +  end + +  class Configuration +    attr_accessor :model, :headers, :separator, :key, :context, :encoding, :ignore_failures, :scope +    attr_reader :columns + +    def initialize import_name, opts={} +      @import_name = import_name +      @key = opts[:key] || "id" +      @headers = opts.has_key?(:headers) ? opts[:headers] : true +      @separator = opts[:separator] || "," +      @encoding = opts[:encoding] +      @columns = opts[:columns] || [] +      @model = opts[:model] +      @custom_handler = opts[:custom_handler] +      @before = opts[:before] +      @after = opts[:after] +      @ignore_failures = opts[:ignore_failures] +      @context = opts[:context] || {} +      @scope = opts[:scope] +    end + +    def duplicate +      Configuration.new @import_name, self.options +    end + +    def options +      { +        key: @key, +        headers: @headers, +        separator: @separator, +        encoding: @encoding, +        columns: @columns.map(&:duplicate), +        model: model, +        custom_handler: @custom_handler, +        before: @before, +        after: @after, +        ignore_failures: @ignore_failures, +        context: @context, +        scope: @scope +      } +    end + +    def validate! +      raise "Incomplete configuration, missing model for #{@import_name}" unless model.present? +    end + +    def attribute_for_col col_name +      column = self.columns.find{|c| c.name == col_name} +      column && column[:attribute] || col_name +    end + +    def record_scope +      _scope = @scope +      _scope = instance_exec(&_scope) if _scope.is_a?(Proc) +      _scope || model +    end + +    def find_record attrs +      record_scope.find_or_initialize_by(attribute_for_col(@key) => attrs[@key.to_s]) +    end + +    def csv_options +      { +        headers: self.headers, +        col_sep: self.separator, +        encoding: self.encoding +      } +    end + +    def add_column name, opts={} +      @columns.push Column.new({name: name.to_s}.update(opts)) +    end + +    def add_value attribute, value +      @columns.push Column.new({attribute: attribute, value: value}) +    end + +    def before group=:all, &block +      @before ||= Hash.new{|h, k| h[k] = []} +      @before[group].push block +    end + +    def after group=:all, &block +      @after ||= Hash.new{|h, k| h[k] = []} +      @after[group].push block +    end + +    def before_actions group=:all +      @before ||= Hash.new{|h, k| h[k] = []} +      @before[group] +    end + +    def after_actions group=:all +      @after ||= Hash.new{|h, k| h[k] = []} +      @after[group] +    end + +    def custom_handler &block +      @custom_handler = block +    end + +    def get_custom_handler +      @custom_handler +    end + +    class Column +      attr_accessor :name +      def initialize opts={} +        @name = opts[:name] +        @options = opts +        @options[:attribute] ||= @name +      end + +      def duplicate +        Column.new @options.dup +      end + +      def required? +        !!@options[:required] +      end + +      def [](key) +        @options[key] +      end +    end +  end +end diff --git a/app/models/user.rb b/app/models/user.rb index 1342f60ed..31e634415 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -15,6 +15,7 @@ class User < ActiveRecord::Base    # Setup accessible (or protected) attributes for your model    # attr_accessible :email, :password, :current_password, :password_confirmation, :remember_me, :name, :organisation_attributes    belongs_to :organisation +  has_many :workbenches, through: :organisation    accepts_nested_attributes_for :organisation    validates :organisation, :presence => true diff --git a/app/models/vehicle_journey_control/delta.rb b/app/models/vehicle_journey_control/delta.rb index f061b9fdd..737b7d78c 100644 --- a/app/models/vehicle_journey_control/delta.rb +++ b/app/models/vehicle_journey_control/delta.rb @@ -4,6 +4,7 @@ module VehicleJourneyControl      store_accessor :control_attributes, :maximum      validates_numericality_of :maximum, allow_nil: true, greater_than_or_equal_to: 0 +    validates_presence_of :maximum      def self.default_code; "3-VehicleJourney-3" end    end diff --git a/app/models/vehicle_journey_control/waiting_time.rb b/app/models/vehicle_journey_control/waiting_time.rb index f2666cb72..89a18a5d9 100644 --- a/app/models/vehicle_journey_control/waiting_time.rb +++ b/app/models/vehicle_journey_control/waiting_time.rb @@ -3,6 +3,7 @@ module VehicleJourneyControl      store_accessor :control_attributes, :maximum      validates_numericality_of :maximum, allow_nil: true, greater_than_or_equal_to: 0 +    validates_presence_of :maximum      def self.default_code; "3-VehicleJourney-1" end    end diff --git a/app/models/workbench.rb b/app/models/workbench.rb index b80fa64ac..eb53af7aa 100644 --- a/app/models/workbench.rb +++ b/app/models/workbench.rb @@ -1,4 +1,6 @@  class Workbench < ActiveRecord::Base +  DEFAULT_WORKBENCH_NAME = "Gestion de l'offre" +    include ObjectidFormatterSupport    belongs_to :organisation    belongs_to :line_referential @@ -40,6 +42,11 @@ class Workbench < ActiveRecord::Base      end    end +  def self.default +    self.last if self.count == 1 +    where(name: DEFAULT_WORKBENCH_NAME).last +  end +    private    def initialize_output diff --git a/app/models/workgroup.rb b/app/models/workgroup.rb index 3d761e81f..3af20ae23 100644 --- a/app/models/workgroup.rb +++ b/app/models/workgroup.rb @@ -3,6 +3,7 @@ class Workgroup < ActiveRecord::Base    belongs_to :stop_area_referential    has_many :workbenches +  has_many :calendars    has_many :organisations, through: :workbenches    has_many :referentials, through: :workbenches diff --git a/app/services/parent_import_notifier.rb b/app/services/parent_import_notifier.rb deleted file mode 100644 index 47e6755e4..000000000 --- a/app/services/parent_import_notifier.rb +++ /dev/null @@ -1,15 +0,0 @@ -class ParentImportNotifier -  def self.notify_when_finished(imports = nil) -    imports ||= imports_pending_notification -    imports.each(&:notify_parent) -  end - -  def self.imports_pending_notification -    Import -      .where( -        notified_parent_at: nil, -        status: Import.finished_statuses -      ) -      .where.not(parent: nil) -  end -end diff --git a/app/services/parent_notifier.rb b/app/services/parent_notifier.rb new file mode 100644 index 000000000..653c98aff --- /dev/null +++ b/app/services/parent_notifier.rb @@ -0,0 +1,19 @@ +class ParentNotifier +  def initialize(klass) +    @klass = klass +  end + +  def notify_when_finished(collection = nil) +    collection ||= objects_pending_notification +    collection.each(&:notify_parent) +  end + +  def objects_pending_notification +    @klass +      .where( +        notified_parent_at: nil, +        status: @klass.finished_statuses +      ) +      .where.not(parent: nil) +  end +end diff --git a/app/views/calendar_mailer/created.html.slim b/app/views/calendar_mailer/created.html.slim index 37b2a86ea..bee071150 100644 --- a/app/views/calendar_mailer/created.html.slim +++ b/app/views/calendar_mailer/created.html.slim @@ -1,4 +1,4 @@ -div = t('mailers.calendar_mailer.created.body', cal_name: @calendar.name, cal_index_url: calendars_url) +div = t('mailers.calendar_mailer.created.body', cal_name: @calendar.name, cal_index_url: workgroup_calendars_url(@calendar.workgroup))  table style="border-collapse:collapse;font-family:'Open Sans', Arial, sans serif;width:550px;margin:0px auto;color:#333333;" @@ -16,7 +16,7 @@ table style="border-collapse:collapse;font-family:'Open Sans', Arial, sans serif            = t('mailers.calendar_mailer.updated.subject')          p style="font-size:14px;margin:0px 0px 10px 0px;" -          = t('mailers.calendar_mailer.created.body', cal_name: @calendar.name, cal_index_url: calendars_url).html_safe +          = t('mailers.calendar_mailer.created.body', cal_name: @calendar.name, cal_index_url: workgroup_calendars_url(@calendar.workgroup)).html_safe      tr        td style="text-align:center;padding:20px 0 0 0;border-top:1px solid #007fbb;" diff --git a/app/views/calendar_mailer/updated.html.slim b/app/views/calendar_mailer/updated.html.slim index bf128439a..0bdc2e7db 100644 --- a/app/views/calendar_mailer/updated.html.slim +++ b/app/views/calendar_mailer/updated.html.slim @@ -14,7 +14,7 @@ table style="border-collapse:collapse;font-family:'Open Sans', Arial, sans serif            = t('mailers.calendar_mailer.updated.subject')          p style="font-size:14px;margin:0px 0px 10px 0px;" -          = t('mailers.calendar_mailer.updated.body', cal_name: @calendar.name, cal_index_url: calendars_url).html_safe +          = t('mailers.calendar_mailer.updated.body', cal_name: @calendar.name, cal_index_url: workgroup_calendars_url(@calendar.workgroup)).html_safe      tr        td style="text-align:center;padding:20px 0 0 0;border-top:1px solid #007fbb;" diff --git a/app/views/calendars/_filters.html.slim b/app/views/calendars/_filters.html.slim index d9c936b64..8bfe1974e 100644 --- a/app/views/calendars/_filters.html.slim +++ b/app/views/calendars/_filters.html.slim @@ -1,4 +1,4 @@ -= search_form_for @q, url: calendars_path, builder: SimpleForm::FormBuilder, html: { method: :get, class: 'form form-filter' } do |f| += 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...' @@ -18,5 +18,5 @@        = f.input :contains_date, as: :date, label: false, wrapper_html: { class: 'date smart_date' }, class: 'form-control', include_blank: true    .actions -    = link_to 'Effacer', calendars_path, class: 'btn btn-link' +    = link_to 'Effacer', workgroup_calendars_path(@workgroup), class: 'btn btn-link'      = f.submit 'Filtrer', id: 'calendar_filter_btn', class: 'btn btn-default' diff --git a/app/views/calendars/_form_advanced.html.slim b/app/views/calendars/_form_advanced.html.slim index b4154166b..e796e2e36 100644 --- a/app/views/calendars/_form_advanced.html.slim +++ b/app/views/calendars/_form_advanced.html.slim @@ -2,7 +2,7 @@  = javascript_tag do    | window.actionType = "#{raw params[:action]}"; -  | window.I18n = #{(I18n.backend.send(:translations)[I18n.locale].to_json).html_safe}; +  // | window.I18n = #{(I18n.backend.send(:translations)[I18n.locale].to_json).html_safe};    | window.timetablesUrl = "#{calendar_url(@calendar).html_safe}";  = javascript_pack_tag 'calendars/edit.js' diff --git a/app/views/calendars/_form_simple.html.slim b/app/views/calendars/_form_simple.html.slim index 2f469ada7..ba18c765b 100644 --- a/app/views/calendars/_form_simple.html.slim +++ b/app/views/calendars/_form_simple.html.slim @@ -1,6 +1,6 @@  .row    .col-lg-8.col-lg-offset-2.col-md-8.col-md-offset-2.col-sm-10.col-sm-offset-1 -    = simple_form_for @calendar, html: { class: 'form-horizontal', id: 'calendar_form' }, wrapper: :horizontal_form do |f| +    = simple_form_for [@workgroup, @calendar], html: { class: 'form-horizontal', id: 'calendar_form' }, wrapper: :horizontal_form do |f|        .row          .col-lg-12            = f.input :name diff --git a/app/views/calendars/edit.html.slim b/app/views/calendars/edit.html.slim index e64790daf..79ab1f5d0 100644 --- a/app/views/calendars/edit.html.slim +++ b/app/views/calendars/edit.html.slim @@ -1,4 +1,4 @@ -- breadcrumb :calendar, @calendar +- breadcrumb :calendar, @workgroup, @calendar  - page_header_content_for @calendar  .page_content    .container-fluid diff --git a/app/views/calendars/index.html.slim b/app/views/calendars/index.html.slim index 92c24be5b..0b58c0c72 100644 --- a/app/views/calendars/index.html.slim +++ b/app/views/calendars/index.html.slim @@ -1,4 +1,4 @@ -- breadcrumb :calendars +- breadcrumb :calendars, workgroup  .page_content    .container-fluid @@ -16,7 +16,7 @@                  key: :name, \                  attribute: 'name', \                  link_to: lambda do |calendar| \ -                  calendar_path(calendar) \ +                  workgroup_calendar_path(workgroup, calendar) \                  end \                ), \                TableBuilderHelper::Column.new( \ diff --git a/app/views/calendars/new.html.slim b/app/views/calendars/new.html.slim index b3840b705..5657a0c55 100644 --- a/app/views/calendars/new.html.slim +++ b/app/views/calendars/new.html.slim @@ -1,4 +1,4 @@ -- breadcrumb :calendars +- breadcrumb :calendars, @workgroup  .page_content    .container-fluid      = render 'form_simple' diff --git a/app/views/calendars/show.html.slim b/app/views/calendars/show.html.slim index ec53be0ef..cec4f66a5 100644 --- a/app/views/calendars/show.html.slim +++ b/app/views/calendars/show.html.slim @@ -1,4 +1,4 @@ -- breadcrumb :calendar, @calendar +- breadcrumb :calendar, @workgroup, @calendar  - page_header_content_for @calendar  .page_content diff --git a/app/views/compliance_check_sets/index.html.slim b/app/views/compliance_check_sets/index.html.slim index ead467174..31ad31e5b 100644 --- a/app/views/compliance_check_sets/index.html.slim +++ b/app/views/compliance_check_sets/index.html.slim @@ -23,9 +23,9 @@                  ), \                  TableBuilderHelper::Column.new( \                    key: :associated_object, \ -                  attribute: Proc.new{|n| n.referential.name}, \ +                  attribute: Proc.new{|n| n.referential.present? ? n.referential.name : ''}, \                    link_to: lambda do |compliance_check_set| \ -                    referential_path(compliance_check_set.referential_id) \ +                    compliance_check_set.referential.present? ? referential_path(compliance_check_set.referential_id) : '#' \                    end \                  ), \                  TableBuilderHelper::Column.new( \ diff --git a/app/views/compliance_controls/new.html.slim b/app/views/compliance_controls/new.html.slim index f7f47fba3..c0abc522f 100644 --- a/app/views/compliance_controls/new.html.slim +++ b/app/views/compliance_controls/new.html.slim @@ -6,5 +6,5 @@          = render 'form'          = definition_list t('metadatas'), -          I18n.t('activerecord.attributes.compliance_control.predicate') => @compliance_control.class.predicate, -          I18n.t('activerecord.attributes.compliance_control.prerequisite') => @compliance_control.class.prerequisite +          I18n.t('activerecord.attributes.compliance_control.predicate') => @compliance_control.predicate, +          I18n.t('activerecord.attributes.compliance_control.prerequisite') => @compliance_control.prerequisite diff --git a/app/views/compliance_controls/show.html.slim b/app/views/compliance_controls/show.html.slim index 8a65bb864..ab25747a9 100644 --- a/app/views/compliance_controls/show.html.slim +++ b/app/views/compliance_controls/show.html.slim @@ -13,8 +13,8 @@                  ComplianceControl.human_attribute_name(:code) => @compliance_control.code,                  ComplianceControl.human_attribute_name(:criticity) => @compliance_control.criticity,                  ComplianceControl.human_attribute_name(:comment) => @compliance_control.comment, -                I18n.t('activerecord.attributes.compliance_control.predicate') => @compliance_control.class.predicate, -                I18n.t('activerecord.attributes.compliance_control.prerequisite') => @compliance_control.class.prerequisite, +                I18n.t('activerecord.attributes.compliance_control.predicate') => @compliance_control.predicate, +                I18n.t('activerecord.attributes.compliance_control.prerequisite') => @compliance_control.prerequisite,                }.merge( \                  {}.tap do |hash| \                    @compliance_control.class.dynamic_attributes.each do |attribute| \ diff --git a/app/views/dashboards/_dashboard.html.slim b/app/views/dashboards/_dashboard.html.slim index 075b94ddc..7f78934a6 100644 --- a/app/views/dashboards/_dashboard.html.slim +++ b/app/views/dashboards/_dashboard.html.slim @@ -9,30 +9,30 @@                span.badge.ml-xs = workbench.referentials.count if workbench.referentials.present?              div -              = link_to '', workbench_path(workbench), class: ' fa fa-chevron-right pull-right', title: t('.offers.see') +              = link_to '', workbench_path(workbench), class: ' fa fa-chevron-right pull-right', title: t('workbenches.index.offers.see')          - if workbench.referentials.present?            .list-group              - workbench.referentials.limit(5).each do |referential| -              = link_to referential.name, referential_path(referential, workbench_id: referential.workbench_id, current_workbench_id: workbench.id), class: 'list-group-item' +              = link_to referential.name, referential_path(referential), class: 'list-group-item'          - else            .panel-body -            em.small.text-muted = t('.offers.no_content') +            em.small.text-muted = t('workbenches.index.offers.no_content') -    .panel.panel-default -      .panel-heading -        h3.panel-title.with_actions -          = link_to I18n.t("activerecord.models.calendar", count: @dashboard.current_organisation.calendars.size), calendars_path -          div -            = link_to '', calendars_path, class: ' fa fa-chevron-right pull-right' -      - if @dashboard.current_organisation.calendars.present? -        .list-group -          - @dashboard.current_organisation.calendars.order("updated_at desc").limit(5).each do |calendar| -            = link_to calendar.name, calendar_path(calendar), class: 'list-group-item' -      - else -        .panel-body -          em.small.text-muted -            = t('dasboard.calendars.none') +      .panel.panel-default +        .panel-heading +          h3.panel-title.with_actions +            = link_to I18n.t("activerecord.models.calendar", count: @dashboard.current_organisation.calendars.size), workgroup_calendars_path(workbench.workgroup) +            div +              = link_to '', workgroup_calendars_path(workbench.workgroup), class: ' fa fa-chevron-right pull-right' +        - if @dashboard.current_organisation.calendars.present? +          .list-group +            - @dashboard.current_organisation.calendars.order("updated_at desc").limit(5).each do |calendar| +              = link_to calendar.name, workgroup_calendars_path(workbench.workgroup, calendar), class: 'list-group-item' +        - else +          .panel-body +            em.small.text-muted +              = t('dasboard.calendars.none')    .col-lg-6.col-md-6.col-sm-6.col-xs-12      .panel.panel-default @@ -51,4 +51,4 @@          .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' -            = link_to "Réseaux", line_referential_networks_path(referential), class: 'list-group-item' +            = link_to Chouette::Network.model_name.human.pluralize.capitalize, line_referential_networks_path(referential), class: 'list-group-item' diff --git a/app/views/errors/forbidden.html.slim b/app/views/errors/forbidden.html.slim index 23ea67eff..8c35b46a8 100644 --- a/app/views/errors/forbidden.html.slim +++ b/app/views/errors/forbidden.html.slim @@ -9,7 +9,7 @@              p                strong = "Désolé, la page demandée la page n'est pas accessible avec votre profil utilisateur." -            p = "Vous pouvez néanmoins continuer à utiliser l'application IBOO." +            p = "Vous pouvez néanmoins continuer à utiliser l'application."            - else              p diff --git a/app/views/errors/server_error.html.slim b/app/views/errors/server_error.html.slim index 189a48760..529ad73e8 100644 --- a/app/views/errors/server_error.html.slim +++ b/app/views/errors/server_error.html.slim @@ -9,7 +9,7 @@              p                strong = "Désolé, une erreur est survenue." -            p = "Vous pouvez néanmoins continuer à utiliser l'application IBOO." +            p = "Vous pouvez néanmoins continuer à utiliser l'application."            - else              p diff --git a/app/views/layouts/application.html.slim b/app/views/layouts/application.html.slim index 34b373295..3921c8701 100644 --- a/app/views/layouts/application.html.slim +++ b/app/views/layouts/application.html.slim @@ -13,6 +13,8 @@ html lang=I18n.locale      = javascript_pack_tag 'application'      = javascript_include_tag 'application' +    = javascript_tag do +      | I18n.locale = '#{I18n.locale}'    body      = render 'layouts/navigation/main_nav' 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 3963d4cd4..cb0698cf8 100644 --- a/app/views/layouts/navigation/_main_nav_left_content_stif.html.slim +++ b/app/views/layouts/navigation/_main_nav_left_content_stif.html.slim @@ -24,14 +24,14 @@      #miTwo.panel-collapse.collapse        .list-group -        - if current_user -          = link_to workbench_path(current_offer_workbench), class: "list-group-item #{params[:controller] == 'workbenches' ? 'active' : ''}" do +        - current_user.workbenches.each do |current_workbench| +          = link_to workbench_path(current_workbench), class: "list-group-item #{params[:controller] == 'workbenches' ? 'active' : ''}" do              span Jeux de données -          = link_to workbench_imports_path(current_offer_workbench), class: "list-group-item #{(params[:controller] == 'imports') ? 'active' : ''}" do +          = link_to workbench_imports_path(current_workbench), class: "list-group-item #{(params[:controller] == 'imports') ? 'active' : ''}" do              span Import -          = link_to calendars_path, class: 'list-group-item' do +          = link_to workgroup_calendars_path(current_workbench.workgroup), class: 'list-group-item' do              span Modèles de calendrier -          = link_to workbench_compliance_check_sets_path(current_offer_workbench), class: 'list-group-item' do +          = link_to workbench_compliance_check_sets_path(current_workbench), class: 'list-group-item' do              span Rapport de contrôle            = link_to compliance_control_sets_path, class: 'list-group-item' do              span Jeux de contrôle diff --git a/app/views/layouts/snapshots/actions_links.html.slim b/app/views/layouts/snapshots/actions_links.html.slim new file mode 100644 index 000000000..f1fa55e87 --- /dev/null +++ b/app/views/layouts/snapshots/actions_links.html.slim @@ -0,0 +1,21 @@ +doctype html +html lang=I18n.locale +  head +    meta charset="utf-8" +    meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no" + +    = csrf_meta_tag + +    title = t('brandname') + +    = stylesheet_link_tag 'base' +    = stylesheet_link_tag 'application' + +    = javascript_pack_tag 'application' +    = javascript_include_tag 'application' + +  body +    = render 'layouts/navigation/main_nav' +    = render 'layouts/flash_messages', flash: flash +    div.page_header +      = yield diff --git a/app/views/layouts/snapshots/default.html.slim b/app/views/layouts/snapshots/default.html.slim new file mode 100644 index 000000000..9e4565dcb --- /dev/null +++ b/app/views/layouts/snapshots/default.html.slim @@ -0,0 +1,19 @@ +doctype html +html lang=I18n.locale +  head +    meta charset="utf-8" +    meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no" + +    = csrf_meta_tag + +    title = t('brandname') + +    = stylesheet_link_tag 'base' +    = stylesheet_link_tag 'application' + +    = javascript_pack_tag 'application' +    = javascript_include_tag 'application' + +  body +    = yield +     diff --git a/app/views/purchase_windows/show.html.slim b/app/views/purchase_windows/show.html.slim index 4e836f424..8ed9e393f 100644 --- a/app/views/purchase_windows/show.html.slim +++ b/app/views/purchase_windows/show.html.slim @@ -7,6 +7,5 @@        .col-lg-6.col-md-6.col-sm-12.col-xs-12          = definition_list t('metadatas'),            { Chouette::PurchaseWindow.human_attribute_name(:name) => @purchase_window.try(:name), -            'Organisation' => @purchase_window.referential.organisation.name,              Chouette::PurchaseWindow.human_attribute_name(:date_ranges) => @purchase_window.periods.map{|d| t('validity_range', debut: l(d.begin, format: :short), end: l(d.end, format: :short))}.join('<br>').html_safe,              Chouette::PurchaseWindow.human_attribute_name(:checksum) => @purchase_window.checksum } diff --git a/app/views/referential_vehicle_journeys/index.html.slim b/app/views/referential_vehicle_journeys/index.html.slim index 69e29597c..ca1b1ecd9 100644 --- a/app/views/referential_vehicle_journeys/index.html.slim +++ b/app/views/referential_vehicle_journeys/index.html.slim @@ -25,28 +25,28 @@                    link_to: lambda do |vehicle_journey| \                      vehicle_journey.published_journey_name ? referential_line_route_vehicle_journeys_path(@referential, vehicle_journey.route.line, vehicle_journey.route) : '' \                    end, \ -                  sortable: false \ +                  sortable: true \                  ), \                  TableBuilderHelper::Column.new( \                    key: :line, \                    attribute: Proc.new {|v| v.route.line.name}, \ -                  sortable: false \ +                  sortable: true \                  ), \                  TableBuilderHelper::Column.new( \                    key: :route, \                    attribute: Proc.new {|v| v.route.name}, \ -                  sortable: false \ +                  sortable: true \                  ), \                  TableBuilderHelper::Column.new( \                    key: :departure_time, \                    attribute: Proc.new {|v| v.vehicle_journey_at_stops.first&.departure }, \ -                  sortable: false \ +                  sortable: true \                  ), \                  @filters_stop_areas&.map{|s| table_builder_column_for_stop_area(s)},                  TableBuilderHelper::Column.new( \                    key: :arrival_time, \                    attribute: Proc.new {|v| v.vehicle_journey_at_stops.last&.arrival }, \ -                  sortable: false \ +                  sortable: true \                  ), \                ].flatten.compact,                cls: 'table has-filter has-search' diff --git a/app/views/referentials/_form.html.slim b/app/views/referentials/_form.html.slim index 9927f05bd..1e59ab566 100644 --- a/app/views/referentials/_form.html.slim +++ b/app/views/referentials/_form.html.slim @@ -1,4 +1,6 @@ -= simple_form_for @referential, html: {class: 'form-horizontal', id: 'referential_form'}, wrapper: :horizontal_form do |form| +- url = @referential.new_record? ? [@workbench, @referential] : [@referential] + += simple_form_for @referential, url: url, html: {class: 'form-horizontal', id: 'referential_form'}, wrapper: :horizontal_form do |form|    .row      .col-lg-12 diff --git a/app/views/referentials/_overview.html.slim b/app/views/referentials/_overview.html.slim index 143784800..539c25fd4 100644 --- a/app/views/referentials/_overview.html.slim +++ b/app/views/referentials/_overview.html.slim @@ -9,7 +9,7 @@                span.fa.fa-search          .form-group.togglable            = f.label Chouette::Line.human_attribute_name(:company_id), required: false, class: 'control-label' -          = f.input :company_id_eq_any, collection: overview.referential_lines.map(&:company).uniq.sort_by(&:name), as: :check_boxes, label: false, label_method: lambda{|l| ("<span>" + l.name + "</span>").html_safe}, required: false, wrapper_html: { class: 'checkbox_list'} +          = f.input :company_id_eq_any, collection: overview.referential_lines.map(&:company).compact.uniq.sort_by(&:name), as: :check_boxes, label: false, label_method: lambda{|l| ("<span>" + l.name + "</span>").html_safe}, required: false, wrapper_html: { class: 'checkbox_list'}          .form-group.togglable            = f.label Chouette::Line.human_attribute_name(:transport_mode), required: false, class: 'control-label' diff --git a/app/views/referentials/select_compliance_control_set.html.slim b/app/views/referentials/select_compliance_control_set.html.slim index 87a888c0a..69c87aab2 100644 --- a/app/views/referentials/select_compliance_control_set.html.slim +++ b/app/views/referentials/select_compliance_control_set.html.slim @@ -2,7 +2,7 @@    .container-fluid      .row        .col-lg-8.col-lg-offset-2.col-md-8.col-md-offset-2.col-sm-10.col-sm-offset-1 -        = form_tag validate_referential_path(params[:referential_id]), {class: 'form-horizontal', id: 'select_compliance_control_set', wrapper: :horizontal_form} do +        = form_tag validate_referential_path(@referential), {class: 'form-horizontal', id: 'select_compliance_control_set', wrapper: :horizontal_form} do            .row              .col-lg-12                .form-group diff --git a/app/views/routes/_form.html.slim b/app/views/routes/_form.html.slim index 29e5be3d2..81f719437 100644 --- a/app/views/routes/_form.html.slim +++ b/app/views/routes/_form.html.slim @@ -27,7 +27,7 @@  // Get JSON data for route stop points  = javascript_tag do    | window.itinerary_stop = "#{URI.escape(route_json_for_edit(@route))}"; -  | window.I18n = #{(I18n.backend.send(:translations)[I18n.locale].to_json).html_safe}; +  // | window.I18n = #{(I18n.backend.send(:translations)[I18n.locale].to_json).html_safe};  / StopPoints Reactux component  = javascript_pack_tag 'routes/edit.js' diff --git a/app/views/stif/dashboards/_dashboard.html.slim b/app/views/stif/dashboards/_dashboard.html.slim index 64e7d4f96..83a2106bb 100644 --- a/app/views/stif/dashboards/_dashboard.html.slim +++ b/app/views/stif/dashboards/_dashboard.html.slim @@ -47,7 +47,7 @@        - if @dashboard.referentials.present?          .list-group            - @dashboard.referentials.first(5).each_with_index do |referential, i| -            = link_to referential.name, referential_path(referential, workbench_id: referential.workbench_id, current_workbench_id: @dashboard.workbench.id), class: 'list-group-item' if i < 6 +            = link_to referential.name, referential_path(referential), class: 'list-group-item' if i < 6        - else          .panel-body @@ -60,12 +60,12 @@            span.badge.ml-xs = @dashboard.calendars.count if @dashboard.calendars.present?            div -            = link_to '', calendars_path, class: ' fa fa-chevron-right pull-right', title: t('.see') +            = link_to '', workgroup_calendars_path(@dashboard.workbench.workgroup), class: ' fa fa-chevron-right pull-right', title: t('.see')        - if @dashboard.calendars.present?          .list-group            - @dashboard.calendars.first(5).each_with_index do |calendar, i| -            = link_to calendar.name, calendar_path(calendar), class: 'list-group-item' if i < 6 +            = link_to calendar.name, workgroup_calendar_path(@dashboard.workbench.workgroup, calendar), class: 'list-group-item' if i < 6        - else          .panel-body diff --git a/app/views/time_tables/_form.html.slim b/app/views/time_tables/_form.html.slim index d06fdf444..007044e65 100644 --- a/app/views/time_tables/_form.html.slim +++ b/app/views/time_tables/_form.html.slim @@ -5,7 +5,7 @@        = form.input :comment, :input_html => { :title => t("formtastic.titles#{format_restriction_for_locales(@referential)}.time_table.comment")}        - if @time_table.new_record? && !@time_table.created_from -        = form.input :calendar_id, as: :select, input_html: { class: 'tt_target', style: "width: 100%", data: { 'select2-ajax': 'true', 'select2ed-placeholder': 'Indiquez un modèle de calendrier...', term: 'name_cont', url: autocomplete_calendars_path}} +        = form.input :calendar_id, as: :select, input_html: { class: 'tt_target', style: "width: 100%", data: { 'select2-ajax': 'true', 'select2ed-placeholder': 'Indiquez un modèle de calendrier...', term: 'name_cont', url: autocomplete_workgroup_calendars_path(current_workgroup)}}        - if @time_table.created_from          = form.input :created_from, disabled: true, input_html: { value: @time_table.created_from.comment } diff --git a/app/views/time_tables/edit.html.slim b/app/views/time_tables/edit.html.slim index e1c566ff4..d8cffb1b0 100644 --- a/app/views/time_tables/edit.html.slim +++ b/app/views/time_tables/edit.html.slim @@ -8,6 +8,6 @@  = javascript_tag do    | window.actionType = "#{raw params[:action]}"; -  | window.I18n = #{(I18n.backend.send(:translations)[I18n.locale].to_json).html_safe}; +  // | window.I18n = #{(I18n.backend.send(:translations)[I18n.locale].to_json).html_safe};  = javascript_pack_tag 'time_tables/edit.js' diff --git a/app/views/time_tables/index.html.slim b/app/views/time_tables/index.html.slim index f58fbb5ea..6913712a0 100644 --- a/app/views/time_tables/index.html.slim +++ b/app/views/time_tables/index.html.slim @@ -61,6 +61,6 @@            = replacement_msg t('time_tables.search_no_results')  = javascript_tag do -  | window.I18n = #{(I18n.backend.send(:translations).to_json).html_safe}; +  // | window.I18n = #{(I18n.backend.send(:translations).to_json).html_safe};  = javascript_pack_tag 'date_filters' diff --git a/app/views/vehicle_journeys/index.html.slim b/app/views/vehicle_journeys/index.html.slim index caa8450a0..d53d8b50c 100644 --- a/app/views/vehicle_journeys/index.html.slim +++ b/app/views/vehicle_journeys/index.html.slim @@ -29,7 +29,7 @@    | window.features = #{raw @features};    | window.all_missions = #{(@all_missions.to_json).html_safe};    | window.custom_fields = #{(@custom_fields.to_json).html_safe}; -  | window.I18n = #{(I18n.backend.send(:translations).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/vehicle_journeys/show.rabl b/app/views/vehicle_journeys/show.rabl index dca0866b3..bb26ce797 100644 --- a/app/views/vehicle_journeys/show.rabl +++ b/app/views/vehicle_journeys/show.rabl @@ -23,6 +23,12 @@ end  child(:time_tables, :object_root => false) do |time_tables|    attributes :id, :objectid, :comment, :color +  node(:days) do |tt| +    tt.display_day_types +  end +  node(:bounding_dates) do |tt| +    tt.presenter.time_table_bounding +  end    child(:calendar) do      attributes :id, :name, :date_ranges, :dates, :shared    end @@ -39,10 +45,8 @@ child :footnotes, :object_root => false do |footnotes|  end  child(:vehicle_journey_at_stops_matrix, :object_root => false) do |vehicle_stops| +  attributes :id, :connecting_service_id, :boarding_alighting_possibility    node do |vehicle_stop| -    [:id, :connecting_service_id, :boarding_alighting_possibility].map do |att| -      node(att) { vehicle_stop.send(att) ? vehicle_stop.send(att) : nil  } -    end      node(:dummy) { vehicle_stop.dummy }      node(:area_kind) { vehicle_stop.stop_point.stop_area.kind } diff --git a/app/views/workbenches/show.html.slim b/app/views/workbenches/show.html.slim index a162ca334..159aa8ea2 100644 --- a/app/views/workbenches/show.html.slim +++ b/app/views/workbenches/show.html.slim @@ -5,7 +5,7 @@      .col-lg-12.text-right        - if policy(Referential).create?          = link_to t('actions.import'), workbench_imports_path(@workbench), class: 'btn btn-primary' -        = link_to t('actions.add'), new_referential_path(workbench_id: @workbench), class: 'btn btn-primary' +        = link_to t('actions.add'), new_workbench_referential_path(@workbench), class: 'btn btn-primary'        = link_to t('workbenches.actions.show_output'), workbench_output_path(@workbench), class: 'btn btn-primary'  .page_content @@ -25,7 +25,7 @@                    key: :name, \                    attribute: 'name', \                    link_to: lambda do |referential| \ -                    referential_path(referential, current_workbench_id: params[:id]) \ +                    referential_path(referential) \                    end \                  ), \                  TableBuilderHelper::Column.new( \ @@ -72,6 +72,6 @@            = replacement_msg t('referentials.search_no_results')  = javascript_tag do -  | window.I18n = #{(I18n.backend.send(:translations).to_json).html_safe}; +  // | window.I18n = #{(I18n.backend.send(:translations).to_json).html_safe};  = javascript_pack_tag 'date_filters' diff --git a/config/breadcrumbs.rb b/config/breadcrumbs.rb index 6da96d73a..00ccf16bd 100644 --- a/config/breadcrumbs.rb +++ b/config/breadcrumbs.rb @@ -23,12 +23,12 @@ end  crumb :referential do |referential|    link breadcrumb_name(referential), referential_path(referential) -  parent :workbench, current_offer_workbench +  parent :workbench, mutual_workbench(referential.workbench)  end  crumb :referentials do |referential| -  link I18n.t('referentials.index.title'), referentials_path() -  parent :workbench, current_offer_workbench +  link I18n.t('referentials.index.title'), workbench_path(current_workbench) +  parent :workbench, mutual_workbench(current_workbench)  end  crumb :referential_companies do |referential| @@ -202,13 +202,13 @@ crumb :purchase_window do |referential, purchase_window|    parent :purchase_windows, referential  end -crumb :calendars do -  link I18n.t('calendars.index.title'), calendars_path +crumb :calendars do |workgroup| +  link I18n.t('calendars.index.title'), workgroup_calendars_path(workgroup)  end -crumb :calendar do |calendar| -  link breadcrumb_name(calendar), calendar_path(calendar) -  parent :calendars +crumb :calendar do |workgroup, calendar| +  link breadcrumb_name(calendar), workgroup_calendar_path(workgroup, calendar) +  parent :calendars, workgroup  end  crumb :referential_line do |referential, line| diff --git a/config/deploy.rb b/config/deploy.rb index 9be023adc..86339d137 100644 --- a/config/deploy.rb +++ b/config/deploy.rb @@ -13,7 +13,8 @@ set :group_writable, true  set :bundle_cmd, "/var/lib/gems/#{ruby_version}/bin/bundle"  set :rake, "#{bundle_cmd} exec rake"  set :default_environment, { -  'PATH' => "/var/lib/gems/#{ruby_version}/bin:$PATH" +  'PATH' => "/var/lib/gems/#{ruby_version}/bin:$PATH", +  'NODE_ENV' => "production"  }  set :keep_releases, -> { fetch(:kept_releases, 5) } @@ -79,6 +80,7 @@ namespace :deploy do    end    after 'deploy:update_code', 'deploy:symlink_shared'    before 'deploy:assets:precompile', 'deploy:symlink_shared' +  after 'deploy:assets:precompile', "deploy:i18n_js_export"    desc "Make group writable all deployed files"    task :group_writable do @@ -92,8 +94,13 @@ namespace :deploy do    end    after "deploy:restart", "deploy:sidekiq_restart" +  desc "Run i18n:js:export" +  task :i18n_js_export do +    run "cd #{release_path} && RAILS_ENV=#{rails_env} #{rake} i18n:js:export" +  end +    desc "Run db:seed"    task :seed do -    run "cd #{current_path} && #{rake} db:seed RAILS_ENV=#{rails_env}" +    run "cd #{current_path} && RAILS_ENV=#{rails_env} #{rake} db:seed"    end  end diff --git a/config/environments/development.rb b/config/environments/development.rb index 1d2fee44f..446e72190 100644 --- a/config/environments/development.rb +++ b/config/environments/development.rb @@ -95,6 +95,7 @@ Rails.application.configure do    config.i18n.available_locales = [:fr, :en]    config.middleware.insert_after(ActionDispatch::Static, Rack::LiveReload) if ENV['LIVERELOAD'] +  config.middleware.use I18n::JS::Middleware    config.development_toolbar = false    if ENV['TOOLBAR'] && File.exists?("config/development_toolbar.rb")      config.development_toolbar = OpenStruct.new diff --git a/config/environments/production.rb b/config/environments/production.rb index 57a8e1483..9a699eb44 100644 --- a/config/environments/production.rb +++ b/config/environments/production.rb @@ -47,6 +47,7 @@ Rails.application.configure do    # Set to :debug to see everything in the log.    # config.log_level = :info +  config.log_level = :info    # Prepend all log lines with the following tags.    # config.log_tags = [ :subdomain, :uuid ] diff --git a/config/initializers/apartment.rb b/config/initializers/apartment.rb index 2d06fb88b..fc652a2da 100644 --- a/config/initializers/apartment.rb +++ b/config/initializers/apartment.rb @@ -81,6 +81,7 @@ Apartment.configure do |config|      'ComplianceCheckMessage',      'Merge',      'CustomField', +    'SimpleImporter',    ]    # use postgres schemas? diff --git a/config/initializers/sidekiq.rb b/config/initializers/sidekiq.rb index a177e7091..2f65b8800 100644 --- a/config/initializers/sidekiq.rb +++ b/config/initializers/sidekiq.rb @@ -14,4 +14,8 @@ Sidekiq.configure_client do |config|    config.redis = { url: ENV.fetch('SIDEKIQ_REDIS_URL', 'redis://localhost:6379/12') }  end +Sidekiq.configure_client do |config| +  config.redis = { url: ENV.fetch('SIDEKIQ_REDIS_URL', 'redis://localhost:6379/12') } +end +  Sidekiq.default_worker_options = { retry: false } diff --git a/config/locales/compliance_controls.fr.yml b/config/locales/compliance_controls.fr.yml index 1448cdbc8..f5f7e351f 100644 --- a/config/locales/compliance_controls.fr.yml +++ b/config/locales/compliance_controls.fr.yml @@ -7,8 +7,8 @@ fr:        name: "Chercher le nom ou code d'un contrôle"        subclass: Objet        subclasses: -        generic: 'Généric' -        journey_pattern: 'JourneyPattern' +        generic: 'Générique' +        journey_pattern: 'Mission'          line: 'Ligne'          route: 'Itinéraire'          routing_constraint_zone: 'ITL' diff --git a/config/locales/en.yml b/config/locales/en.yml index e59960f95..8af8067db 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -3,6 +3,7 @@ en:    "false": "No"    "unknown": "Unknown" +    time:      formats:        hour: "%Hh%M" @@ -61,3 +62,7 @@ en:    reflex_data: 'Reflex datas'    objectid: 'ID'    brandname: IBOO +  error: "Error" +  undefined: 'undefined' +  "yes": yes +  "no": no diff --git a/config/locales/fr.yml b/config/locales/fr.yml index 175b71ebc..e1f52ff55 100644 --- a/config/locales/fr.yml +++ b/config/locales/fr.yml @@ -61,3 +61,7 @@ fr:    reflex_data: 'Données Reflex'    objectid: 'ID'    brandname: IBOO +  error: "Erreur" +  undefined: 'non renseigné' +  "yes": oui +  "no": non diff --git a/config/locales/imports.en.yml b/config/locales/imports.en.yml index f843eea47..b0644acd3 100644 --- a/config/locales/imports.en.yml +++ b/config/locales/imports.en.yml @@ -17,6 +17,8 @@ en:        warning: ""      new:        title: "Generate a new import" +    create: +      title: "Generate a new import"      show:        title: "Import %{name}"        report: "Report" diff --git a/config/locales/imports.fr.yml b/config/locales/imports.fr.yml index c24f08e76..2380eac45 100644 --- a/config/locales/imports.fr.yml +++ b/config/locales/imports.fr.yml @@ -17,6 +17,8 @@ fr:        warning: ""      new:        title: "Générer un import" +    create: +      title: "Générer un import"      show:        title: "Import %{name}"        report: "Rapport" diff --git a/config/locales/journey_patterns.en.yml b/config/locales/journey_patterns.en.yml index e5248c29c..9d9bc21e2 100644 --- a/config/locales/journey_patterns.en.yml +++ b/config/locales/journey_patterns.en.yml @@ -6,6 +6,7 @@ en:        vehicle_journeys_count: "Vehicle journeys: %{count}"        vehicle_journey_at_stops: "Vehicle journey at stops"      actions: +      index: "Journey patterns"        new: "Add a new journey_pattern"        edit: "Edit this journey pattern"        destroy: "Remove this journey pattern" diff --git a/config/locales/stop_areas.en.yml b/config/locales/stop_areas.en.yml index 09e8d1a82..c1ced1094 100644 --- a/config/locales/stop_areas.en.yml +++ b/config/locales/stop_areas.en.yml @@ -48,11 +48,15 @@ en:        area_type: Type an area type...      new:        title: "Add a new stop" +    update: +      title: "Add a new stop"      form:        address: "246 Boulevard Saint-Germain, 75007 Paris"        geolocalize: "Pinpoint "      edit:        title: "Update stop %{stop_area}" +    update: +      title: "Update stop %{stop_area}"      show:        title: "Stop %{stop_area}"        geographic_data: "Geographic data" diff --git a/config/locales/stop_areas.fr.yml b/config/locales/stop_areas.fr.yml index 283000960..ede1aada6 100644 --- a/config/locales/stop_areas.fr.yml +++ b/config/locales/stop_areas.fr.yml @@ -49,11 +49,15 @@ fr:        area_type: "Indiquez un type d'arrêt..."      new:        title: "Ajouter un arrêt" +    create: +      title: "Ajouter un arrêt"      form:        address: "246 Boulevard Saint-Germain, 75007 Paris"        geolocalize: "Géolocalisez "      edit:        title: "Editer l'arrêt %{name}" +    update: +      title: "Editer l'arrêt %{name}"      show:        title: "Arrêt %{name}"        geographic_data: "Données géographiques" diff --git a/config/locales/stop_points.en.yml b/config/locales/stop_points.en.yml index d22d85731..72e138270 100644 --- a/config/locales/stop_points.en.yml +++ b/config/locales/stop_points.en.yml @@ -55,3 +55,4 @@ en:          name: Stop Point          for_boarding: "Pickup"          for_alighting: "Drop off" +        reflex_id: ID diff --git a/config/locales/stop_points.fr.yml b/config/locales/stop_points.fr.yml index d3c873442..71be684f6 100644 --- a/config/locales/stop_points.fr.yml +++ b/config/locales/stop_points.fr.yml @@ -52,6 +52,7 @@ fr:    simple_form:      labels:        stop_point: -        name: Arrêt  +        name: Arrêt          for_boarding: "Montée"          for_alighting: "Descente" +        reflex_id: ID diff --git a/config/locales/vehicle_journeys.en.yml b/config/locales/vehicle_journeys.en.yml index abb1da530..0c8a75b0c 100644 --- a/config/locales/vehicle_journeys.en.yml +++ b/config/locales/vehicle_journeys.en.yml @@ -1,7 +1,14 @@  en:    vehicle_journeys:      vehicle_journeys_matrix: +      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'      vehicle_journey:        title_stopless: "Vehicle journey %{name}"        title: "Vehicle journey leaving from %{stop} at %{time}" @@ -25,44 +32,54 @@ en:        title_stopless: "Update vehicle journey %{name}"        title: "Update vehicle journey %{name} leaving from %{stop} at %{time}"      form: -      stop_title: "Stop" -      departure: "Departure" +      arrival_at: "Arrival at"        arrival: "Arrival" -      to_arrivals: "Copy departures to arrivals" -      to_departures: "Copy arrivals to departures" -      time_tables: "Associated calendars to vehicle journey" -      slide: "Shift" -      slide_title: "Shift all vehicle passing times" +      departure_at: "Departure at" +      departure: "Departure" +      departure_range: +        label: Journey departure range +        start: Start +        end: End +      infos: Informations        set: "Set" -      to: "at" -      slide_departure: "departure time at first stop" +      show_arrival_time: "Show and edit arrival times" +      show_journeys_with_calendar: "Show journeys with calendar" +      show_journeys_without_schedule: "Show journeys without schedule"        slide_arrival: "arrival time at first stop" -      submit_timed: "Create vehicle journey" +      slide_departure: "departure time at first stop" +      slide_title: "Shift all vehicle passing times" +      slide: "Shift" +      stop_title: "Stop" +      submit_frequency_edit: "Edit frequency vehicle journey"        submit_frequency: "Create frequency vehicle journey"        submit_timed_edit: "Edit vehicle journey" -      submit_frequency_edit: "Edit frequency vehicle journey" +      submit_timed: "Create vehicle journey" +      time_tables: "Associated calendars to vehicle journey" +      to_arrivals: "Copy departures to arrivals" +      to_departures: "Copy arrivals to departures" +      to: "at"      timeless:        title: "Timeless vehicle journeys"        vehicle_journeys: "Vehicle journeys with times at stop"        vehicles_list: "Vehicle journeys list"      show: -      title: "Vehicle Journey %{vehicle journey}" -      stop_title: "Stop" -      departure: "Departure"        arrival: "Arrival" -      time_tables: "Calendars list"        bounding: "From %{start} to %{end}" -      translation_form: "Vehicle journey translations" +      departure: "Departure"        journey_frequencies: "Timeband" +      stop_title: "Stop" +      time_tables: "Calendars list" +      title: "Vehicle Journey %{vehicle journey}" +      translation_form: "Vehicle journey translations"      index: -      title: "Vehicle journeys on route %{route}" -      vehicle_journeys: "Departure's times" -      selection: "Filter on" -      selection_all: "All" +      advanced_search: "Advanced Search"        select_journey_patterns: "Select journey pattern"        select_time_tables: "Enter a timetable" +      selection_all: "All" +      selection: "Filter on"        time_range: "Departure time threshold" -      advanced_search: "Advanced Search" +      title: "Vehicle journeys on route %{route}" +      vehicle_journeys: "Departure's times"      time_filter:        time_range_filter: "Filter"      sidebar: @@ -78,40 +95,47 @@ en:          other: "vehicle journeys"      attributes:        vehicle_journey: -        line: "Line" -        route: "Route" -        journey_pattern: "Journey Pattern" -        time_tables: "Calendars" -        time_slot: "Time Slot" -        company: "Company" -        number: "Number" +        accessible: "Accessible" +        arrival_time: "Arrival" +        checksum: "Checksum"          comment: "Comments" -        status_value: "Status Value" -        transport_mode: "Transport Mode" -        mobility_restricted_suitability: "PRM accessibility" +        company: "Company" +        created_at: Created at +        creator_id: "Created by" +        departure_time: "Departure" +        facility: "Facility"          flexible_service: "On demond transportation" -        unspecified_mrs: "Not specified" -        accessible: "Accessible" +        footnote_ids: "Footnotes" +        id: "Journey ID" +        journey_frequency_ids: "Timeband" +        journey_name: "Name of the journey" +        journey_pattern_id: "Pattern ID" +        journey_pattern: "Journey Pattern" +        line: "Line" +        mobility_restricted_suitability: "PRM accessibility" +        name: "Journey Name"          not_accessible: "Not accessible" -        unspecified_fs: "Not specified" +        number: "Number" +        object_version: "Version" +        objectid: "Neptune identifier"          on_demand_fs: "On demand service" -        regular_fs: "Regular service" -        published_journey_name: "Published Name"          published_journey_identifier: "Published Identifier" -        facility: "Facility" -        vehicle_type_identifier: "Vehicle Type Identifier" +        published_journey_name: "Published Name" +        purchase_window: "Purchase availability" +        regular_fs: "Regular service" +        route: "Route" +        status_value: "Status Value" +        time_slot: "Time Slot"          time_table_ids: "Calendar list" -        vehicle_journey_at_stop_ids: "Time list" -        journey_frequency_ids: "Timeband" -        objectid: "Neptune identifier" -        object_version: "Version" -        created_at: Created at +        time_tables: "Calendars" +        train_number: "Train number" +        transport_mode: "Transport Mode" +        transport_submode: "Transport Submode" +        unspecified_fs: "Not specified" +        unspecified_mrs: "Not specified"          updated_at: Updated at -        creator_id: "Created by" -        footnote_ids: "Footnotes" -        departure_time: "Departure" -        arrival_time: "Arrival" -        purchase_window: "Purchase availability" +        vehicle_journey_at_stop_ids: "Time list" +        vehicle_type_identifier: "Vehicle Type Identifier"      errors:        models:          vehicle_journey: diff --git a/config/locales/vehicle_journeys.fr.yml b/config/locales/vehicle_journeys.fr.yml index ca8475812..1034a3fba 100644 --- a/config/locales/vehicle_journeys.fr.yml +++ b/config/locales/vehicle_journeys.fr.yml @@ -1,7 +1,14 @@  fr:    vehicle_journeys:      vehicle_journeys_matrix: +      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'      vehicle_journey:        title_stopless: "Course %{name}"        title: "Course partant de %{stop} à %{time}" @@ -25,44 +32,55 @@ fr:        title_stopless: "Editer la course %{name}"        title: "Editer la course partant de %{stop} à %{time}"      form: -      stop_title: "Arrêt" -      departure: "Départ" +      arrival_at: "Arrivée à"        arrival: "Arrivée" -      to_arrivals: "Copie départs vers arrivées" -      to_departures: "Copie arrivées vers départs" -      time_tables: "Calendriers associés à la course" -      slide: "Décaler" -      slide_title: "Décaler l'ensemble des horaires de course" +      departure_at: "Départ à" +      departure: "Départ" +      departure_range: +        label: Plage horaire au départ de la course +        start: Début +        end: Fin +      infos: Informations        set: "Fixer" -      to: "à" -      slide_departure: "horaire de départ au 1° arrêt à" +      show_arrival_time: "Afficher et éditer les horaires d'arrivée" +      show_journeys_with_calendar: "Afficher les courses avec calendrier" +      show_journeys_without_schedule: "Afficher les courses sans horaires"        slide_arrival: "horaire d'arrivée au 1° arrêt à" -      submit_timed: "Créer course" +      slide_departure: "horaire de départ au 1° arrêt à" +      slide_title: "Décaler l'ensemble des horaires de course" +      slide: "Décaler" +      stop_title: "Arrêt" +      submit_frequency_edit: "Editer course en fréquence"        submit_frequency: "Créer course en fréquence"        submit_timed_edit: "Editer course" -      submit_frequency_edit: "Editer course en fréquence" +      submit_timed: "Créer course" +      time_tables: "Calendriers 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" +      to: "à"      timeless:        title: "Courses sans horaire"        vehicle_journeys: "Courses ayant des horaires"        vehicles_list: "Liste des courses"      show: -      title: "Course au départ de %{stop} à %{time} sur la séquence %{route}" -      stop_title: "Arrêt" -      departure: "Départ"        arrival: "Arrivée" -      time_tables: "Liste des calendriers"        bounding: "De %{start} à %{end}" -      translation_form: "Cloner la course" +      departure: "Départ"        journey_frequencies: "Créneau horaire" +      stop_title: "Arrêt" +      time_tables: "Liste des calendriers" +      title: "Course au départ de %{stop} à %{time} sur la séquence %{route}" +      translation_form: "Cloner la course"      index: -      title: "Horaires de '%{route}'" -      vehicle_journeys: "Horaires de départ aux arrêts" -      selection: "Filtrer sur" -      selection_all: "Tous" +      advanced_search: "Recherche avancée"        select_journey_patterns: "Sélectionner une mission"        select_time_tables: "Saisir un calendrier" +      selection_all: "Tous" +      selection: "Filtrer sur"        time_range: "Seuil horaire au départ" -      advanced_search: "Recherche avancée" +      title: "Horaires de '%{route}'" +      vehicle_journeys: "Horaires de départ aux arrêts"      time_filter:        time_range_filter: "Filtrer"      sidebar: @@ -78,40 +96,47 @@ fr:          other: "courses"      attributes:        vehicle_journey: -        line: "Ligne" -        route: "Séquence d'arrêt" -        journey_pattern: "Mission" -        time_tables: "Calendriers" -        time_slot: "Fréquence" -        company: "Transporteur" -        number: "Numéro" +        accessible: "Accessible" +        arrival_time: "Arrivée" +        checksum: "Signature métier"          comment: "Commentaires" -        status_value: "Etat de trafic" -        transport_mode: "Mode de transport" -        mobility_restricted_suitability: "Accessibilité PMR" +        company: "Transporteur" +        created_at: "Créé le" +        creator_id: "Créé par" +        departure_time: "Départ" +        facility: "Equipement"          flexible_service: "Transport à la demande" -        unspecified_mrs: "Non spécifié" -        accessible: "Accessible" +        footnote_ids: "Notes de bas de page" +        id: "ID Course" +        journey_frequency_ids: "Créneau horaire" +        journey_name: "Nom de la course" +        journey_pattern_id: "ID Mission" +        journey_pattern: "Mission" +        line: "Ligne" +        mobility_restricted_suitability: "Accessibilité PMR" +        name: "Nom Course"          not_accessible: "Non accessible" -        unspecified_fs: "Non spécifié" +        number: "Numéro" +        object_version: "Version" +        objectid: "Identifiant Neptune"          on_demand_fs: "Service à la demande" -        regular_fs: "Service régulier" -        published_journey_name: "Nom public"          published_journey_identifier: "Identifiant public" -        facility: "Equipement" -        vehicle_type_identifier: "Type d'identifiant du véhicule" +        published_journey_name: "Nom public" +        purchase_window: "Disponibilité commerciale" +        regular_fs: "Service régulier" +        route: "Itinéraire" +        status_value: "Etat de trafic" +        time_slot: "Fréquence"          time_table_ids: "Liste des calendriers" -        vehicle_journey_at_stop_ids: "Liste des horaires" -        journey_frequency_ids: "Créneau horaire" -        objectid: "Identifiant Neptune" -        object_version: "Version" -        created_at: "Créé le" +        time_tables: "Calendriers" +        train_number: "Numéro de train" +        transport_mode: "Mode de transport" +        transport_submode: "Sous-mode de transport" +        unspecified_fs: "Non spécifié" +        unspecified_mrs: "Non spécifié"          updated_at: "Edité le" -        creator_id: "Créé par" -        footnote_ids: "Notes de bas de page" -        departure_time: "Départ" -        arrival_time: "Arrivée" -        purchase_window: "Disponibilité commerciale" +        vehicle_journey_at_stop_ids: "Liste des horaires" +        vehicle_type_identifier: "Type d'identifiant du véhicule"      errors:        models:          vehicle_journey: diff --git a/config/routes.rb b/config/routes.rb index fc2807ab1..456cb66f5 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -19,113 +19,32 @@ ChouetteIhm::Application.routes.draw do      resource :output, controller: :workbench_outputs      resources :merges -  end - -  devise_for :users, :controllers => { -    :registrations => 'users/registrations', :invitations => 'users/invitations' -  } - -  devise_scope :user do -    authenticated :user do -      root :to => 'workbenches#index', as: :authenticated_root -    end - -    unauthenticated :user do -      target = 'devise/sessions#new' -      if Rails.application.config.chouette_authentication_settings[:type] == "cas" -        target = 'devise/cas_sessions#new' -      end - -      root :to => target, as: :unauthenticated_root -    end +    resources :referentials, only: %w(new create)    end -  mount Sidekiq::Web => '/sidekiq' - -  namespace :api do -    namespace :v1 do -      resources :workbenches, only: [:index, :show] do -        resources :imports, only: [:index, :show, :create] -      end -      resources :access_links, only: [:index, :show] -      resources :access_points, only: [:index, :show] -      resources :connection_links, only: [:index, :show] -      resources :companies, only: [:index, :show] -      resources :group_of_lines, only: [:index, :show] -      resources :netex_imports, only: :create -      resources :journey_patterns, only: :show -      resources :lines, only: [:index, :show] do -        resources :journey_patterns, only: [:index, :show] -        resources :routes, only: [:index, :show] do -          resources :vehicle_journeys, only: [:index, :show] -          resources :journey_patterns, only: [:index, :show] -          resources :stop_areas, only: [:index, :show] -        end -      end -      resources :networks, only: [:index, :show] -      resources :routes, only: :show -      resources :stop_areas, only: [:index, :show] -      resources :time_tables, only: [:index, :show] -      resources :vehicle_journeys, only: :show -      namespace :internals do -        get 'compliance_check_sets/:id/notify_parent', to: 'compliance_check_sets#notify_parent' -        get 'netex_imports/:id/notify_parent', to: 'netex_imports#notify_parent' +  resources :workgroups do +    resources :calendars do +      get :autocomplete, on: :collection, controller: 'autocomplete_calendars' +      member do +        get 'month', defaults: { format: :json }        end      end    end -  resource :organisation, :only => [:show, :edit, :update] do -    resources :users -  end - -  resources :api_keys, :only => [:edit, :update, :new, :create, :destroy] - -  resources :compliance_control_sets do -    get :simple, on: :member -    get :clone, on: :member -    resources :compliance_controls, except: :index do -      get :select_type, on: :collection -    end -    resources :compliance_control_blocks, :except => [:show, :index] -  end - -  deactivable = Proc.new do -    put :deactivate, on: :member -    put :activate, on: :member -  end - -  resources :stop_area_referentials, :only => [:show] do -    post :sync, on: :member -    resources :stop_areas do -      put :deactivate, on: :member -      put :activate, on: :member -      get :autocomplete, on: :collection -    end -  end - -  resources :line_referentials, :only => [:show, :edit, :update] do -    post :sync, on: :member -    resources :lines, &deactivable -    resources :group_of_lines -    resources :companies -    resources :networks -  end +  resources :referentials, except: %w(new create) do -  resources :calendars do -    get :autocomplete, on: :collection, controller: 'autocomplete_calendars'      member do -      get 'month', defaults: { format: :json } +      put :archive +      put :unarchive +      get :select_compliance_control_set +      post :validate      end -  end -  resources :referentials, except: :index do      resources :autocomplete_stop_areas, only: [:show, :index] do        get 'around', on: :member      end      resources :autocomplete_purchase_windows, only: [:index] -    get :select_compliance_control_set -    post :validate, on: :member      resources :autocomplete_time_tables, only: [:index]      resources :autocomplete_timebands      resources :group_of_lines, controller: "referential_group_of_lines" do @@ -134,12 +53,6 @@ ChouetteIhm::Application.routes.draw do        end      end -    # Archive/unarchive -    member do -      put :archive -      put :unarchive -    end -      resources :networks, controller: "referential_networks"      match 'lines' => 'lines#destroy_all', :via => :delete @@ -237,8 +150,111 @@ ChouetteIhm::Application.routes.draw do      resources :clean_ups    end +  devise_for :users, :controllers => { +    :registrations => 'users/registrations', :invitations => 'users/invitations' +  } + +  devise_scope :user do +    authenticated :user do +      root :to => 'workbenches#index', as: :authenticated_root +    end + +    unauthenticated :user do +      target = 'devise/sessions#new' + +      if Rails.application.config.chouette_authentication_settings[:type] == "cas" +        target = 'devise/cas_sessions#new' +      end + +      root :to => target, as: :unauthenticated_root +    end +  end + +  mount Sidekiq::Web => '/sidekiq' + +  namespace :api do +    namespace :v1 do +      resources :workbenches, only: [:index, :show] do +        resources :imports, only: [:index, :show, :create] +      end +      resources :access_links, only: [:index, :show] +      resources :access_points, only: [:index, :show] +      resources :connection_links, only: [:index, :show] +      resources :companies, only: [:index, :show] +      resources :group_of_lines, only: [:index, :show] +      resources :netex_imports, only: :create +      resources :journey_patterns, only: :show +      resources :lines, only: [:index, :show] do +        resources :journey_patterns, only: [:index, :show] +        resources :routes, only: [:index, :show] do +          resources :vehicle_journeys, only: [:index, :show] +          resources :journey_patterns, only: [:index, :show] +          resources :stop_areas, only: [:index, :show] +        end +      end +      resources :networks, only: [:index, :show] +      resources :routes, only: :show +      resources :stop_areas, only: [:index, :show] +      resources :time_tables, only: [:index, :show] +      resources :vehicle_journeys, only: :show +      namespace :internals do +        get 'compliance_check_sets/:id/notify_parent', to: 'compliance_check_sets#notify_parent' +        get 'netex_imports/:id/notify_parent', to: 'netex_imports#notify_parent' +      end +    end +  end + +  resource :organisation, :only => [:show, :edit, :update] do +    resources :users +  end + +  resources :api_keys, :only => [:edit, :update, :new, :create, :destroy] + +  resources :compliance_control_sets do +    get :simple, on: :member +    get :clone, on: :member +    resources :compliance_controls, except: :index do +      get :select_type, on: :collection +    end +    resources :compliance_control_blocks, :except => [:show, :index] +  end + +  deactivable = Proc.new do +    put :deactivate, on: :member +    put :activate, on: :member +  end + +  resources :stop_area_referentials, :only => [:show] do +    post :sync, on: :member +    resources :stop_areas do +      put :deactivate, on: :member +      put :activate, on: :member +      get :autocomplete, on: :collection +    end +  end + +  resources :line_referentials, :only => [:show, :edit, :update] do +    post :sync, on: :member +    resources :lines, &deactivable +    resources :group_of_lines +    resources :companies +    resources :networks +  end + +  resources :calendars do +    get :autocomplete, on: :collection, controller: 'autocomplete_calendars' +    member do +      get 'month', defaults: { format: :json } +    end +  end + +    root :to => "dashboards#show" +  if Rails.env.development? || Rails.env.test? +    get "/snap" => "snapshots#show" +  end +    get '/help/(*slug)' => 'help#show'    if Rails.application.config.development_toolbar @@ -250,4 +266,6 @@ ChouetteIhm::Application.routes.draw do    match '/422', to: 'errors#server_error', via: :all, as: 'unprocessable_entity'    match '/500', to: 'errors#server_error', via: :all, as: 'server_error' +  match '/status', to: 'statuses#index', via: :get +  end diff --git a/config/schedule.rb b/config/schedule.rb index 08488c255..0d2a24f31 100644 --- a/config/schedule.rb +++ b/config/schedule.rb @@ -40,9 +40,15 @@ every :day, :at => '4:00 am' do  end  every 5.minutes do +  rake "import:netex_abort_old"    rake "import:notify_parent"  end +every 5.minutes do +  rake "compliance_check_sets:abort_old" +  rake "compliance_check_sets:notify_parent" +end +  every 1.minute do    command "/bin/echo HeartBeat"  end diff --git a/config/secrets.yml.docker b/config/secrets.yml.docker index 1bef794a8..f9bbf5fa0 100644 --- a/config/secrets.yml.docker +++ b/config/secrets.yml.docker @@ -14,3 +14,4 @@    secret_key_base: <%= ENV.fetch 'SECRET_KEY_BASE', 'change_this_string_for_something_more_secure' %>    api_endpoint: <%= ENV.fetch 'IEV_API_ENDPOINT', 'http://iev:8080/chouette_iev/' %>    api_token: <%= ENV.fetch 'IEV_API_TOKEN', 'change this according to IEV configuration' %> +  newrelic_licence_key: <%= ENV.fetch 'NR_LICENCE_KEY', 'will_not_work' %> diff --git a/config/webpack/environment.js b/config/webpack/environment.js index 688bcbe8e..743cf66a8 100644 --- a/config/webpack/environment.js +++ b/config/webpack/environment.js @@ -14,7 +14,7 @@ let cleanOptions = {  }; -environment.plugins.set( +environment.plugins.append(    'CleanWebpack',    new CleanWebpackPlugin(pathsToClean, cleanOptions)  ) diff --git a/config/webpack/production.js b/config/webpack/production.js index 7703f8452..8e30c09ea 100644 --- a/config/webpack/production.js +++ b/config/webpack/production.js @@ -2,7 +2,12 @@ const environment = require('./environment')  const webpack = require('webpack')  const UglifyJsPlugin = require('uglify-js') -environment.plugins.set( +let plugin = new webpack.EnvironmentPlugin({ +	NODE_ENV: 'production' +}) + +environment.plugins.append('EnvironmentPlugin', plugin) +environment.plugins.append(    'UglifyJs',    new webpack.optimize.UglifyJsPlugin({      compress: { diff --git a/config/webpack/staging.js b/config/webpack/staging.js index 7703f8452..82478b156 100644 --- a/config/webpack/staging.js +++ b/config/webpack/staging.js @@ -2,7 +2,7 @@ const environment = require('./environment')  const webpack = require('webpack')  const UglifyJsPlugin = require('uglify-js') -environment.plugins.set( +environment.plugins.append(    'UglifyJs',    new webpack.optimize.UglifyJsPlugin({      compress: { diff --git a/db/migrate/20180123174450_add_workgroup_id_to_calendars.rb b/db/migrate/20180123174450_add_workgroup_id_to_calendars.rb new file mode 100644 index 000000000..64ad1a752 --- /dev/null +++ b/db/migrate/20180123174450_add_workgroup_id_to_calendars.rb @@ -0,0 +1,6 @@ +class AddWorkgroupIdToCalendars < ActiveRecord::Migration +   def change +    add_column :calendars, :workgroup_id, :integer, limit: 8 +    add_index :calendars, :workgroup_id +  end +end diff --git a/db/migrate/20180129210928_create_simple_importers.rb b/db/migrate/20180129210928_create_simple_importers.rb new file mode 100644 index 000000000..c2a918900 --- /dev/null +++ b/db/migrate/20180129210928_create_simple_importers.rb @@ -0,0 +1,10 @@ +class CreateSimpleImporters < ActiveRecord::Migration +  def change +    create_table :simple_importers do |t| +      t.string :configuration_name +      t.string :filepath +      t.string :status +      t.json :journal +    end +  end +end diff --git a/db/schema.rb b/db/schema.rb index cd4d42005..b10fa565c 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -90,12 +90,14 @@ ActiveRecord::Schema.define(version: 20180202170009) do      t.integer   "organisation_id", limit: 8      t.datetime  "created_at"      t.datetime  "updated_at" +    t.integer   "workgroup_id",    limit: 8      t.integer   "int_day_types"      t.date      "excluded_dates",                            array: true    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|      t.string   "message_key" @@ -722,6 +724,13 @@ ActiveRecord::Schema.define(version: 20180202170009) do      t.integer "line_id",      limit: 8    end +  create_table "simple_importers", id: :bigserial, force: :cascade do |t| +    t.string "configuration_name" +    t.string "filepath" +    t.string "status" +    t.json   "journal" +  end +    create_table "stop_area_referential_memberships", id: :bigserial, force: :cascade do |t|      t.integer "organisation_id",          limit: 8      t.integer "stop_area_referential_id", limit: 8 diff --git a/lib/af83/decorator.rb b/lib/af83/decorator.rb index f990555fe..71cf1170d 100644 --- a/lib/af83/decorator.rb +++ b/lib/af83/decorator.rb @@ -2,40 +2,49 @@ class AF83::Decorator < ModelDecorator    include AF83::Decorator::EnhancedDecorator    extend AF83::Decorator::EnhancedDecorator::ClassMethods -  def self.decorates klass -    instance_decorator.decorates klass -  end +  class << self +    def decorates klass +      instance_decorator.decorates klass +    end -  def self.instance_decorator -    @instance_decorator ||= begin -      klass = Class.new(AF83::Decorator::InstanceDecorator) -      klass.delegate_all -      klass +    def instance_decorator +      @instance_decorator ||= begin +        klass = Class.new(AF83::Decorator::InstanceDecorator) +        klass.delegate_all +        klass +      end      end -  end -  def self.with_instance_decorator -    @_with_instance_decorator = true -    yield instance_decorator -    @_with_instance_decorator = false -  end +    def with_instance_decorator +      @_with_instance_decorator = true +      yield instance_decorator +      @_with_instance_decorator = false +    end + +    def decorate object, options = {} +      if object.is_a?(ActiveRecord::Base) +        return instance_decorator.decorate object, options +      else +        self.new object, options.update(with: instance_decorator) +      end +    end -  def self.decorate object, options = {} -    if object.is_a?(ActiveRecord::Base) -      return instance_decorator.decorate object, options -    else -      self.new object, options.update(with: instance_decorator) +    def define_instance_method method_name, &block +      instance_decorator.send(:define_method, method_name, &block)      end -  end -  def self.define_instance_method method_name, &block -    instance_decorator.send(:define_method, method_name, &block) -  end +    # Defines a class method on the decorated object's class. These +    # can be called like `object.class.my_method`. +    def define_instance_class_method method_name, &block +      instance_decorator.send(:define_singleton_method, method_name, &block) +    end + +    def set_scope_with_instance_decorator value=nil, &block +      set_scope_without_instance_decorator value, &block +      instance_decorator.set_scope value, &block +    end -  # Defines a class method on the decorated object's class. These -  # can be called like `object.class.my_method`. -  def self.define_instance_class_method method_name, &block -    instance_decorator.send(:define_singleton_method, method_name, &block) +    alias_method_chain :set_scope, :instance_decorator    end    class ActionLinks diff --git a/lib/af83/decorator/enhanced_decorator.rb b/lib/af83/decorator/enhanced_decorator.rb index 904d1b2da..fff8bb8b3 100644 --- a/lib/af83/decorator/enhanced_decorator.rb +++ b/lib/af83/decorator/enhanced_decorator.rb @@ -25,7 +25,7 @@ module AF83::Decorator::EnhancedDecorator          policy: :create,          before_block: -> (l){            l.content { h.t('actions.add') } -          l.href    { [:new, object.klass.name.underscore.singularize] } +          l.href    { [:new, scope, object.klass.model_name.singular] }          }        }        action_link opts.update(args), &block @@ -37,7 +37,7 @@ module AF83::Decorator::EnhancedDecorator          primary: :index,          before_block: -> (l){            l.content { h.t('actions.show') } -          l.href { [object] } +          l.href { [scope, object] }          }        }        action_link opts.update(args), &block @@ -49,7 +49,7 @@ module AF83::Decorator::EnhancedDecorator          policy: :edit,          before_block: -> (l){            l.content { h.t('actions.edit') } -          l.href { [:edit, object] } +          l.href { [:edit, scope, object] }          }        }        action_link opts.update(args), &block @@ -62,7 +62,7 @@ module AF83::Decorator::EnhancedDecorator          secondary: :show,          before_block: -> (l){            l.content { h.destroy_link_content } -          l.href { [object] } +          l.href { [scope, object] }            l.method :delete            l.data {{ confirm: h.t('actions.destroy_confirm') }}          } @@ -70,6 +70,14 @@ module AF83::Decorator::EnhancedDecorator        action_link opts.update(args), &block      end +    def set_scope value=nil, &block +      @scope = value || block +    end + +    def scope +      @scope +    end +      def t key        eval  "-> (l){ h.t('#{key}') }"      end @@ -142,4 +150,10 @@ module AF83::Decorator::EnhancedDecorator    def check_feature feature      h.has_feature? feature    end + +  def scope +    scope = self.class.scope +    scope = instance_exec &scope if scope.is_a? Proc +    scope +  end  end diff --git a/lib/af83/decorator/link.rb b/lib/af83/decorator/link.rb index 7d2896e6a..ee09f80dc 100644 --- a/lib/af83/decorator/link.rb +++ b/lib/af83/decorator/link.rb @@ -30,7 +30,8 @@ class AF83::Decorator::Link        @options[name] = block      elsif args.size == 0        out = @options[name] -      out = context.instance_exec(self, &out)  if out.is_a?(Proc) +      out = context.instance_exec(self, &out) if out.is_a?(Proc) +      out = out.flatten.compact if name.to_s == "href" && out.is_a?(Array)        out      else        # we can use l.foo("bar") or l.foo = "bar" @@ -129,7 +130,9 @@ class AF83::Decorator::Link      out[:method] = link_method      out[:class] = extra_class      out.delete(:link_class) +    out.delete(:link_method)      out[:class] += " disabled" if disabled +    out[:class].strip!      out[:disabled] = !!disabled      out    end diff --git a/lib/stif/dashboard.rb b/lib/stif/dashboard.rb index b6b6b8284..46c635091 100644 --- a/lib/stif/dashboard.rb +++ b/lib/stif/dashboard.rb @@ -4,12 +4,16 @@ module Stif        @workbench ||= current_organisation.workbenches.find_by(name: "Gestion de l'offre")      end +    def workgroup +      workbench.workgroup +    end +      def referentials        @referentials ||= self.workbench.all_referentials      end      def calendars -      @calendars ||= Calendar.where('organisation_id = ? OR shared = ?', current_organisation.id, true) +      @calendars ||= Calendar.where('(organisation_id = ? OR shared = ?) AND workgroup_id = ?', current_organisation.id, true, workgroup.id)      end    end  end diff --git a/lib/tasks/ci.rake b/lib/tasks/ci.rake index 13d7b8d73..89f9aa9c8 100644 --- a/lib/tasks/ci.rake +++ b/lib/tasks/ci.rake @@ -31,19 +31,22 @@ namespace :ci do      sh "bundle exec bundle-audit check --update"    end -  task :spec => ["ci:assets","spec"] -    task :assets do      sh "RAILS_ENV=test bundle exec rake assets:precompile"    end -  task :jest => "ci:assets" do -    sh "yarn --no-progress install" # Hack to force install jest after webpack  -    sh "node_modules/.bin/jest" unless ["CHOUETTE_JEST_DISABLED"] +  task :i18n_js_export do +    sh "RAILS_ENV=test bundle exec rake i18n:js:export" +  end + +  task :jest do +    sh "node_modules/.bin/jest" unless ENV["CHOUETTE_JEST_DISABLED"]    end    desc "Deploy after CI"    task :deploy do +    return if ENV["CHOUETTE_DEPLOY_DISABLED"] +      if deploy_env        sh "cap #{deploy_env} deploy:migrations"      else @@ -59,4 +62,4 @@ namespace :ci do  end  desc "Run continuous integration tasks (spec, ...)" -task :ci => ["ci:setup", "ci:spec", "ci:jest", "cucumber", "ci:check_security", "ci:deploy", "ci:clean"] +task :ci => ["ci:setup", "ci:assets", "ci:i18n_js_export", "spec", "ci:jest", "cucumber", "ci:check_security", "ci:deploy", "ci:clean"] diff --git a/lib/tasks/compliance_check_sets.rb b/lib/tasks/compliance_check_sets.rb new file mode 100644 index 000000000..c53c7f9ed --- /dev/null +++ b/lib/tasks/compliance_check_sets.rb @@ -0,0 +1,11 @@ +namespace :compliance_check_sets do +  desc "Notify parent check sets when children finish" +  task notify_parent: :environment do +    ParentNotifier.new(ComplianceCheckSet).notify_when_finished +  end + +  desc "Mark old unfinished check sets as 'aborted'" +  task abort_old: :environment do +    ComplianceCheckSet.abort_old +  end +end diff --git a/lib/tasks/imports.rake b/lib/tasks/imports.rake index 6bc84acc8..b91ff7efb 100644 --- a/lib/tasks/imports.rake +++ b/lib/tasks/imports.rake @@ -1,6 +1,108 @@ +require 'csv' +  namespace :import do    desc "Notify parent imports when children finish"    task notify_parent: :environment do -    ParentImportNotifier.notify_when_finished +    ParentNotifier.new(Import).notify_when_finished +  end + +  desc "Mark old unfinished Netex imports as 'aborted'" +  task netex_abort_old: :environment do +    NetexImport.abort_old +  end + +  def importer_output_to_csv importer +    filepath = "./#{importer.configuration_name}_#{Time.now.strftime "%y%m%d%H%M"}_out.csv" +    cols = %w(line kind event message error) +    if importer.reload.journal.size > 0 +      keys = importer.journal.first["row"].map(&:first) +      CSV.open(filepath, "w") do |csv| +        csv << cols + keys +        importer.journal.each do |j| +          csv << cols.map{|c| j[c]} + j["row"].map(&:last) +        end +      end +      puts "Import Output written in #{filepath}" +    end +  end + +  desc "import the given file with the corresponding importer" +  task :import, [:configuration_name, :filepath, :referential_id] => :environment do |t, args| +    importer = SimpleImporter.create configuration_name: args[:configuration_name], filepath: args[:filepath] +    if args[:referential_id].present? +      referential = Referential.find args[:referential_id] +      importer.configure do |config| +        config.add_value :referential, referential +        config.context = {referential: referential} +      end +    end +    puts "\e[33m***\e[0m Start importing" +    begin +      importer.import(verbose: true) +    rescue Interrupt +      raise +    ensure +      puts "\n\e[33m***\e[0m Import done, status: " + (importer.status == "success" ? "\e[32m" : "\e[31m" ) + (importer.status || "") + "\e[0m" +      importer_output_to_csv importer +    end +  end + +  desc "import the given file with the corresponding importer in the given StopAreaReferential" +  task :import_in_stop_area_referential, [:referential_id, :configuration_name, :filepath] => :environment do |t, args| +    referential = StopAreaReferential.find args[:referential_id] +    importer = SimpleImporter.create configuration_name: args[:configuration_name], filepath: args[:filepath] +    importer.configure do |config| +      config.add_value :stop_area_referential, referential +      config.context = {stop_area_referential: referential} +    end +    puts "\e[33m***\e[0m Start importing" +    begin +      importer.import(verbose: true) +    rescue Interrupt +      raise +    ensure +      puts "\n\e[33m***\e[0m Import done, status: " + (importer.status == "success" ? "\e[32m" : "\e[31m" ) + (importer.status || "") + "\e[0m" +      importer_output_to_csv importer +    end +  end + +  desc "import the given routes files" +  task :import_routes, [:referential_id, :configuration_name, :mapping_filepath, :filepath] => :environment do |t, args| +    referential = Referential.find args[:referential_id] +    referential.switch +    stop_area_referential = referential.stop_area_referential +    importer = SimpleImporter.create configuration_name: args[:configuration_name], filepath: args[:filepath] +    importer.configure do |config| +      config.add_value :stop_area_referential, referential +      config.context = {stop_area_referential: stop_area_referential, mapping_filepath: args[:mapping_filepath]} +    end +    puts "\e[33m***\e[0m Start importing" +    begin +      importer.import(verbose: true) +    rescue Interrupt +      raise +    ensure +      puts "\n\e[33m***\e[0m Import done, status: " + (importer.status == "success" ? "\e[32m" : "\e[31m" ) + (importer.status || "") + "\e[0m" +      importer_output_to_csv importer +    end +  end + +  desc "import the given file with the corresponding importer in the given LineReferential" +  task :import_in_line_referential, [:referential_id, :configuration_name, :filepath] => :environment do |t, args| +    referential = LineReferential.find args[:referential_id] +    importer = SimpleImporter.create configuration_name: args[:configuration_name], filepath: args[:filepath] +    importer.configure do |config| +      config.add_value :line_referential, referential +      config.context = {line_referential: referential} +    end +    puts "\e[33m***\e[0m Start importing" +    begin +      importer.import(verbose: true) +    rescue Interrupt +      raise +    ensure +      puts "\n\e[33m***\e[0m Import done, status: " + (importer.status == "success" ? "\e[32m" : "\e[31m" ) + (importer.status || "") + "\e[0m" +      importer_output_to_csv importer +    end    end  end diff --git a/lib/tasks/install.rake b/lib/tasks/install.rake index 1150825b2..7d8ecdce0 100644 --- a/lib/tasks/install.rake +++ b/lib/tasks/install.rake @@ -9,9 +9,11 @@ task :package do    sh "bundle package --all"    sh "bundle exec rake assets:clobber RAILS_ENV=production"    sh "bundle exec rake assets:precompile RAILS_ENV=production" +  sh "bundle exec rake i18n:js:export RAILS_ENV=production"    sh "tar -rf tmp/package/stif-boiv-release-#{release_name}.tar vendor/cache"    sh "tar -rf tmp/package/stif-boiv-release-#{release_name}.tar public/assets"    sh "tar -rf tmp/package/stif-boiv-release-#{release_name}.tar public/packs" +  sh "tar -rf tmp/package/stif-boiv-release-#{release_name}.tar public/javascripts"    %w{deploy-helper.sh README sidekiq-stif-boiv.service stif-boiv.conf stif-boiv-setup.sh template-stif-boiv.sql}.each do |f|      cp "install/#{f}", "tmp/package/#{f}" @@ -37,9 +39,11 @@ task :pkg4docker do  #  sh "RAILS_DB_ADAPTER=nulldb bundle exec rake assets:precompile RAILS_ENV=production"    sh "bundle exec rake assets:clobber RAILS_ENV=production"    sh "bundle exec rake assets:precompile RAILS_ENV=production" +  sh "bundle exec rake i18n:js:export RAILS_ENV=production"    sh "tar -rf tmp/package/stif-boiv-release-#{release_name}.tar vendor/cache"    sh "tar -rf tmp/package/stif-boiv-release-#{release_name}.tar public/assets"    sh "tar -rf tmp/package/stif-boiv-release-#{release_name}.tar public/packs" +  sh "tar -rf tmp/package/stif-boiv-release-#{release_name}.tar public/javascripts"    sh "gzip -c tmp/package/stif-boiv-release-#{release_name}.tar > tmp/stif-boiv-release.tar.gz" diff --git a/package.json b/package.json index e80f5231e..262d80b97 100644 --- a/package.json +++ b/package.json @@ -23,11 +23,12 @@      "redux-logger": "3.0.6",      "redux-promise": "0.5.3",      "redux-thunk": "2.2.0", +    "uglify-js": "3.3.2",      "whatwg-fetch": "2.0.3"    },    "license": "MIT",    "engines": { -    "node": "~6.12.0" +    "node": "^6.12.3"    },    "devDependencies": {      "es6-object-assign": "1.1.0", @@ -41,7 +42,6 @@      "react-addons-test-utils": "15.6.2",      "react-test-renderer": "^16.2.0",      "sinon": "4.1.3", -    "uglify-js": "3.3.2",      "webpack-dev-server": "2.9.7"    },    "jest": { @@ -49,6 +49,10 @@      "roots": [        "<rootDir>/spec/javascript"      ], +    "transform": { +      "^.+\\.coffee$": "<rootDir>/spec/javascript/preprocessor.js", +      "^.+\\.jsx?$": "babel-jest" +    },      "testEnvironment": "jest-environment-jsdom-global",      "setupFiles": [        "<rootDir>/spec/javascript/spec_helper.js", diff --git a/spec/controllers/api/v1/stop_area_controller_spec.rb b/spec/controllers/api/v1/stop_area_controller_spec.rb index eb0c87661..1ad71a95a 100644 --- a/spec/controllers/api/v1/stop_area_controller_spec.rb +++ b/spec/controllers/api/v1/stop_area_controller_spec.rb @@ -18,16 +18,15 @@ describe Api::V1::StopAreasController, :type => :controller do      end    end    describe "GET #index, :q => { :name_cont => 'aa'}" do -    let!(:sa1) { create(:stop_area, :name => "aaa") } -    let!(:sa2) { create(:stop_area, :name => "aab") } -    let!(:sa3) { create(:stop_area, :name => "abb") } +    let!(:sa1) { create(:stop_area, :name => "aaa", stop_area_referential: referential.stop_area_referential) } +    let!(:sa2) { create(:stop_area, :name => "aab", stop_area_referential: referential.stop_area_referential) } +    let!(:sa3) { create(:stop_area, :name => "abb", stop_area_referential: referential.stop_area_referential) }      before :each do        config_formatted_request_with_authorization( "application/json") -      get :index, :q => { :name_cont => "aa"}  +      get :index, :q => { :name_cont => "aa"}      end      it "should assign expected stop_areas" do        expect(assigns[:stop_areas].map(&:name).sort).to eq([ sa1.name, sa2.name])      end    end  end - diff --git a/spec/controllers/autocomplete_stop_areas_controller_spec.rb b/spec/controllers/autocomplete_stop_areas_controller_spec.rb index 2af471361..e0d1cd714 100644 --- a/spec/controllers/autocomplete_stop_areas_controller_spec.rb +++ b/spec/controllers/autocomplete_stop_areas_controller_spec.rb @@ -4,9 +4,11 @@ RSpec.describe AutocompleteStopAreasController, type: :controller do    login_user    let(:referential) { Referential.first } -  let!(:stop_area) { create :stop_area, name: 'écolà militaire' } -  let!(:zdep_stop_area) { create :stop_area, area_type: "zdep" } -  let!(:not_zdep_stop_area) { create :stop_area, area_type: "lda" } +  let(:other_referential) { create :referential } +  let!(:stop_area) { create :stop_area, name: 'écolà militaire', referential: referential } +  let!(:other_referential_stop_area) { create :stop_area, name: 'écolà militaire', referential: other_referential } +  let!(:zdep_stop_area) { create :stop_area, area_type: "zdep", referential: referential } +  let!(:not_zdep_stop_area) { create :stop_area, area_type: "lda", referential: referential }    describe 'GET #index' do      it 'should be successful' do @@ -14,6 +16,12 @@ RSpec.describe AutocompleteStopAreasController, type: :controller do        expect(response).to be_success      end +    it "should filter stop areas based on referential" do +      get :index, referential_id: referential.id +      expect(assigns(:stop_areas)).to include(stop_area) +      expect(assigns(:stop_areas)).to_not include(other_referential_stop_area) +    end +      context 'search by name' do        it 'should be successful' do          get :index, referential_id: referential.id, q: 'écolà', :format => :json diff --git a/spec/controllers/referentials_controller_spec.rb b/spec/controllers/referentials_controller_spec.rb index 521856258..ff450c905 100644 --- a/spec/controllers/referentials_controller_spec.rb +++ b/spec/controllers/referentials_controller_spec.rb @@ -7,7 +7,7 @@ describe ReferentialsController, :type => :controller do    let(:other_referential) { create :referential, organisation: organisation }    describe "GET new" do -    let(:request){ get :new } +    let(:request){ get :new, workbench_id: referential.workbench_id }      before{ request }      it 'returns http success' do @@ -16,7 +16,7 @@ describe ReferentialsController, :type => :controller do      context "when cloning another referential" do        let(:source){ referential } -      let(:request){ get :new, from: source.id } +      let(:request){ get :new, workbench_id: referential.workbench_id, from: source.id }        it 'returns http success' do          expect(response).to have_http_status(200) @@ -51,8 +51,7 @@ describe ReferentialsController, :type => :controller do      end      context "user's organisation doesn't match referential's organisation" do -      pending "hotfix opens all unknow actions need to close the uneeded later" do -      #it 'raises a ActiveRecord::RecordNotFound' do +      it 'raises a ActiveRecord::RecordNotFound' do          expect { put :archive, id: other_referential.id }.to raise_error(ActiveRecord::RecordNotFound)        end      end @@ -62,7 +61,7 @@ describe ReferentialsController, :type => :controller do      it 'gets compliance control set for current organisation' do        compliance_control_set = create(:compliance_control_set, organisation: @user.organisation)        create(:compliance_control_set) -      get :select_compliance_control_set, referential_id: referential.id +      get :select_compliance_control_set, id: referential.id        expect(assigns[:compliance_control_sets]).to eq([compliance_control_set])      end    end @@ -79,16 +78,51 @@ describe ReferentialsController, :type => :controller do      end    end +  describe "GET #new" do +    context "when duplicating" do +      let(:workbench){ create :workbench} +      let(:request){ +        get :new, +          workbench_id: workbench.id, +          from: referential.id +      } + +      it "duplicates the given referential" do +        request +        new_referential = assigns(:referential) +        expect(new_referential.line_referential).to eq referential.line_referential +        expect(new_referential.stop_area_referential).to eq referential.stop_area_referential +        expect(new_referential.objectid_format).to eq referential.objectid_format +        expect(new_referential.prefix).to eq referential.prefix +        expect(new_referential.slug).to eq "#{referential.slug}_clone" +        expect(new_referential.workbench).to eq workbench +      end +    end +  end +    describe "POST #create" do +    let(:workbench){ create :workbench}      context "when duplicating" do -      it "displays a flash message", pending: 'requires more params to create a valid Referential' do +      let(:request){          post :create, -          from: referential.id, -          current_workbench_id: referential.workbench_id, -          referential: { -            name: 'Duplicated' -          } +        workbench_id: workbench.id, +        referential: { +          name: 'Duplicated', +          created_from_id: referential.id, +          stop_area_referential: referential.stop_area_referential, +          line_referential: referential.line_referential, +          objectid_format: referential.objectid_format, +          workbench_id: referential.workbench_id +        } +      } + +      it "creates the new referential" do +        expect{request}.to change{Referential.count}.by 1 +        expect(Referential.last.name).to eq "Duplicated" +      end +      it "displays a flash message" do +        request          expect(controller).to set_flash[:notice].to(            I18n.t('notice.referentials.duplicate')          ) diff --git a/spec/controllers/statuses_controller_spec.rb b/spec/controllers/statuses_controller_spec.rb new file mode 100644 index 000000000..8a6db8e28 --- /dev/null +++ b/spec/controllers/statuses_controller_spec.rb @@ -0,0 +1,50 @@ +RSpec.describe StatusesController, :type => :controller do + +  describe "GET index" do +    login_user +    render_views + + +    let(:request){ get :index} +    let(:parsed_response){ JSON.parse response.body } +    it "should be ok" do +      request +      expect(response).to have_http_status 200 +      expect(parsed_response["status"]).to eq "ok" +    end +    context "without blocked object" do +      before do +        create :referential +        create :import +        create :compliance_check_set +        request +      end + +      it "should be ok" do +        expect(response).to have_http_status 200 +        expect(parsed_response["status"]).to eq "ok" +        expect(parsed_response["referentials_blocked"]).to eq 0 +        expect(parsed_response["imports_blocked"]).to eq 0 +        expect(parsed_response["imports_blocked"]).to eq 0 +      end +    end + +    context "with a blocked object" do +      before do +        create :referential, created_at: 5.hours.ago, ready: false +        create :import +        create :compliance_check_set +        request +      end + +      it "should be ko" do +        expect(Referential.blocked.count).to eq 1 +        expect(response).to have_http_status 200 +        expect(parsed_response["status"]).to eq "ko" +        expect(parsed_response["referentials_blocked"]).to eq 1 +        expect(parsed_response["imports_blocked"]).to eq 0 +        expect(parsed_response["imports_blocked"]).to eq 0 +      end +    end +  end +end diff --git a/spec/controllers/vehicle_journeys_controller_spec.rb b/spec/controllers/vehicle_journeys_controller_spec.rb index 416450c21..300684532 100644 --- a/spec/controllers/vehicle_journeys_controller_spec.rb +++ b/spec/controllers/vehicle_journeys_controller_spec.rb @@ -26,4 +26,28 @@ RSpec.describe VehicleJourneysController, :type => :controller do      end    end +  describe "GET index" do +    login_user +    render_views + +    context "in JSON" do +      let(:vehicle_journey){ create :vehicle_journey } +      let(:route){ vehicle_journey.route } +      let(:line){ route.line } +      let!(:request){ get :index, referential_id: referential.id, line_id: line.id, route_id: route.id, format: :json} +      let(:parsed_response){ JSON.parse response.body } +      it "should have all the attributes" do +        expect(response).to have_http_status 200 +        vehicle_journey = parsed_response["vehicle_journeys"].first +        vehicle_journey_at_stops_matrix = vehicle_journey["vehicle_journey_at_stops"] +        vehicle_journey_at_stops_matrix.each do |received_vjas| +          expect(received_vjas).to have_key("id") +          vjas = Chouette::VehicleJourneyAtStop.find received_vjas["id"] +          [:connecting_service_id, :boarding_alighting_possibility].each do |att| +            expect(received_vjas[att]).to eq vjas.send(att) +          end +        end +      end +    end +  end  end diff --git a/spec/decorators/referential_decorator_spec.rb b/spec/decorators/referential_decorator_spec.rb index efc438132..1224aaf75 100644 --- a/spec/decorators/referential_decorator_spec.rb +++ b/spec/decorators/referential_decorator_spec.rb @@ -1,7 +1,8 @@  RSpec.describe ReferentialDecorator, type: [:helper, :decorator] do    include Support::DecoratorHelpers -  let( :object ){ build_stubbed :referential } +  let( :workbench ){ build_stubbed :workbench } +  let( :object ){ build_stubbed :referential, workbench: workbench }    let( :referential ){ object }    let( :user ){ build_stubbed :user } @@ -35,7 +36,7 @@ RSpec.describe ReferentialDecorator, type: [:helper, :decorator] do            expect_action_link_hrefs.to eq([              [object],              referential_time_tables_path(object), -            new_referential_path(from: object) +            new_workbench_referential_path(referential.workbench, from: object.id)            ])          end        end @@ -50,8 +51,8 @@ RSpec.describe ReferentialDecorator, type: [:helper, :decorator] do                [object],                [:edit, object],                referential_time_tables_path(object), -              new_referential_path(from: object), -              referential_select_compliance_control_set_path(object), +              new_workbench_referential_path(referential.workbench, from: object.id), +              select_compliance_control_set_referential_path(object),                archive_referential_path(object),                referential_path(object)              ]) @@ -65,8 +66,8 @@ RSpec.describe ReferentialDecorator, type: [:helper, :decorator] do              expect_action_link_hrefs(action).to eq([                [:edit, object],                referential_time_tables_path(object), -              new_referential_path(from: object), -              referential_select_compliance_control_set_path(object), +              new_workbench_referential_path(referential.workbench, from: object.id), +              select_compliance_control_set_referential_path(object),                archive_referential_path(object),                "#",                referential_path(object) @@ -91,7 +92,7 @@ RSpec.describe ReferentialDecorator, type: [:helper, :decorator] do            expect_action_link_hrefs.to eq([              [object],              referential_time_tables_path(object), -            new_referential_path(from: object) +            new_workbench_referential_path(referential.workbench, from: object.id)            ])          end        end diff --git a/spec/factories/calendars.rb b/spec/factories/calendars.rb index 5f3188bee..d9fd242d1 100644 --- a/spec/factories/calendars.rb +++ b/spec/factories/calendars.rb @@ -6,6 +6,7 @@ FactoryGirl.define do      sequence(:dates) { |n| [ Date.yesterday - n, Date.yesterday - 2*n ] }      shared false      organisation +    workgroup    end    sequence :date_range do |n| diff --git a/spec/factories/chouette_routes.rb b/spec/factories/chouette_routes.rb index 7443d08bc..92a50b924 100644 --- a/spec/factories/chouette_routes.rb +++ b/spec/factories/chouette_routes.rb @@ -19,6 +19,7 @@ FactoryGirl.define do        after(:create) do |route, evaluator|          create_list(:stop_point, evaluator.stop_points_count, route: route)          route.reload +        route.update_checksum!        end        factory :route_with_journey_patterns do diff --git a/spec/factories/chouette_stop_areas.rb b/spec/factories/chouette_stop_areas.rb index 9b4764781..dab135ca6 100644 --- a/spec/factories/chouette_stop_areas.rb +++ b/spec/factories/chouette_stop_areas.rb @@ -10,6 +10,14 @@ FactoryGirl.define do      association :stop_area_referential +    transient do +      referential nil +    end + +    before(:create) do |stop_area, evaluator| +      stop_area.stop_area_referential = evaluator.referential.stop_area_referential if evaluator.referential +    end +      trait :deactivated do        deleted_at { 1.hour.ago }      end diff --git a/spec/factories/compliance_controls/vehicle_journey_control_factories.rb b/spec/factories/compliance_controls/vehicle_journey_control_factories.rb index e8f68cbdf..86a335aba 100644 --- a/spec/factories/compliance_controls/vehicle_journey_control_factories.rb +++ b/spec/factories/compliance_controls/vehicle_journey_control_factories.rb @@ -1,10 +1,12 @@  FactoryGirl.define do    factory :vehicle_journey_control_wating_time, class: 'VehicleJourneyControl::WaitingTime' do +    maximum 10      association :compliance_control_set    end    factory :vehicle_journey_control_delta, class: 'VehicleJourneyControl::Delta' do +    maximum 10      association :compliance_control_set    end diff --git a/spec/features/access_points_spec.rb b/spec/features/access_points_spec.rb index c16039d67..890906de7 100644 --- a/spec/features/access_points_spec.rb +++ b/spec/features/access_points_spec.rb @@ -4,7 +4,7 @@ require 'spec_helper'  describe "Access points", :type => :feature do    login_user -  let!(:stop_area) { create(:stop_area) } +  let!(:stop_area) { create(:stop_area, stop_area_referential: referential.stop_area_referential) }    let!(:access_points) { Array.new(2) { create(:access_point, :stop_area => stop_area) } }    subject { access_points.first } diff --git a/spec/features/calendars_permissions_spec.rb b/spec/features/calendars_permissions_spec.rb index 9b47ab2bb..4857592d5 100644 --- a/spec/features/calendars_permissions_spec.rb +++ b/spec/features/calendars_permissions_spec.rb @@ -2,6 +2,7 @@ RSpec.describe 'Calendars', type: :feature do    login_user    let(:calendar) { create :calendar, organisation_id: 1 } +  let(:workgroup) { calendar.workgroup }    describe 'permissions' do      before do @@ -13,7 +14,7 @@ RSpec.describe 'Calendars', type: :feature do      end      context 'on show view' do -      let( :path ){ calendar_path(calendar) } +      let( :path ){ workgroup_calendar_path(workgroup, calendar) }        context 'if present → ' do          let( :permission ){ true } @@ -33,7 +34,7 @@ RSpec.describe 'Calendars', type: :feature do      end      context 'on edit view' do -      let( :path ){ edit_calendar_path(calendar) } +      let( :path ){ edit_workgroup_calendar_path(workgroup, calendar) }        context 'if present → ' do          let( :permission ){ true } @@ -51,7 +52,7 @@ RSpec.describe 'Calendars', type: :feature do      end      context 'on index view' do -      let( :path ){ calendars_path } +      let( :path ){ workgroup_calendars_path(workgroup) }        context 'if present → ' do          let( :permission ){ true } diff --git a/spec/features/referential_stop_areas_spec.rb b/spec/features/referential_stop_areas_spec.rb index 0dc7951a7..5e70857e7 100644 --- a/spec/features/referential_stop_areas_spec.rb +++ b/spec/features/referential_stop_areas_spec.rb @@ -5,7 +5,7 @@ describe 'ReferentialStopAreas', type: :feature do    login_user    let(:referential) { Referential.first } -  let(:stop_area_referential) { create :stop_area_referential } +  let(:stop_area_referential) { referential.stop_area_referential }    let!(:stop_areas) { Array.new(2) { create :stop_area, stop_area_referential: stop_area_referential } }    describe 'index' do diff --git a/spec/features/referentials_spec.rb b/spec/features/referentials_spec.rb index 9af0ed32e..d4890fda4 100644 --- a/spec/features/referentials_spec.rb +++ b/spec/features/referentials_spec.rb @@ -55,7 +55,7 @@ describe "Referentials", :type => :feature do      context 'user has the permission to create referentials' do        it 'shows the clone link for referetnial' do -        expect(page).to have_link(I18n.t('actions.clone'), href: new_referential_path(from: referential.id)) +        expect(page).to have_link(I18n.t('actions.clone'), href: new_workbench_referential_path(referential.workbench, from: referential.id))        end      end @@ -63,7 +63,7 @@ describe "Referentials", :type => :feature do        it 'does not show the clone link for referetnial' do          @user.update_attribute(:permissions, [])          visit referential_path(referential) -        expect(page).not_to have_link(I18n.t('actions.clone'), href: new_referential_path(from: referential.id)) +        expect(page).not_to have_link(I18n.t('actions.clone'), href: new_workbench_referential_path(referential.workbench, from: referential.id))        end      end @@ -108,8 +108,9 @@ describe "Referentials", :type => :feature do    end    describe "create" do +    let(:workbench){ @user.organisation.workbenches.last }      it "should" do -      visit new_referential_path +      visit new_workbench_referential_path(workbench)        fill_in "Nom", :with => "Test"        click_button "Valider" @@ -132,7 +133,7 @@ describe "Referentials", :type => :feature do      context "when user is from the same organisation" do        xit "should" do -        visit new_referential_path(from: referential.id, current_workbench_id: @user.organisation.workbenches.first.id) +        visit new_workbench_referential_path(referential.workbench, from: referential.id, current_workbench_id: @user.organisation.workbenches.first.id)          select "2018", :from => "referential_metadatas_attributes_0_periods_attributes_0_begin_1i" @@ -187,7 +188,8 @@ describe "Referentials", :type => :feature do    end    describe "destroy" do -    let(:referential) {  create(:referential, :organisation => @user.organisation) } +    let(:workbench){ @user.organisation.workbenches.last } +    let(:referential) {  create(:referential, :organisation => @user.organisation, workbench: workbench) }      it "should remove referential" do        visit referential_path(referential) diff --git a/spec/features/workbenches/workbenches_permissions_spec.rb b/spec/features/workbenches/workbenches_permissions_spec.rb index d58293538..1c073a4c5 100644 --- a/spec/features/workbenches/workbenches_permissions_spec.rb +++ b/spec/features/workbenches/workbenches_permissions_spec.rb @@ -22,7 +22,7 @@ describe 'Workbenches', type: :feature do          let( :permission ){ true }          it 'shows the corresponding button' do -          expected_href = new_referential_path(workbench_id: workbench) +          expected_href = new_workbench_referential_path(workbench)            expect( page ).to have_link('Créer', href: expected_href)          end        end diff --git a/spec/features/workbenches/workbenches_show_spec.rb b/spec/features/workbenches/workbenches_show_spec.rb index 7be813b94..405fdce82 100644 --- a/spec/features/workbenches/workbenches_show_spec.rb +++ b/spec/features/workbenches/workbenches_show_spec.rb @@ -232,7 +232,7 @@ RSpec.describe 'Workbenches', type: :feature do          context 'user has the permission to create referentials' do            it 'shows the link for a new referetnial' do -            expect(page).to have_link(I18n.t('actions.add'), href: new_referential_path(workbench_id: workbench.id)) +            expect(page).to have_link(I18n.t('actions.add'), href: new_workbench_referential_path(workbench))            end          end @@ -240,7 +240,7 @@ RSpec.describe 'Workbenches', type: :feature do            it 'does not show the clone link for referential' do              @user.update_attribute(:permissions, [])              visit referential_path(referential) -            expect(page).not_to have_link(I18n.t('actions.add'), href: new_referential_path(workbench_id: workbench.id)) +            expect(page).not_to have_link(I18n.t('actions.add'), href: new_workbench_referential_path(workbench))            end          end        end diff --git a/spec/fixtures/simple_importer/lines_mapping.csv b/spec/fixtures/simple_importer/lines_mapping.csv new file mode 100644 index 000000000..b26d0ab59 --- /dev/null +++ b/spec/fixtures/simple_importer/lines_mapping.csv @@ -0,0 +1,11 @@ +id;timetable_route_id;route_name;stop_sequence;stop_distance;station_code;station_name;border;Ligne Chouette;Transporteur
 +3354;1136;Paris centre - Bercy > Lille > Londres;1;0;XPB;Paris City Center - Bercy;f;Paris <> Londres - OUIBUS;OUIBUS
 +3355;1136;Paris centre - Bercy > Lille > Londres;2;232;XDB;Lille;f;Paris <> Londres - OUIBUS;OUIBUS
 +3749;1136;Paris centre - Bercy > Lille > Londres;3;350;COF;Coquelles - France;t;Paris <> Londres - OUIBUS;OUIBUS
 +4772;1136;Paris centre - Bercy > Lille > Londres;4;350;COU;Coquelles - UK;t;Paris <> Londres - OUIBUS;OUIBUS
 +3357;1136;Paris centre - Bercy > Lille > Londres;5;527;ZEP;London;f;Paris <> Londres - OUIBUS;OUIBUS
 +3358;1137;Londres > Lille > Paris centre - Bercy;1;0;ZEP;London;f;Paris <> Londres - OUIBUS;OUIBUS
 +3559;1137;Londres > Lille > Paris centre - Bercy;2;177;COU;Coquelles - UK;t;Paris <> Londres - OUIBUS;OUIBUS
 +3743;1137;Londres > Lille > Paris centre - Bercy;3;177;COF;Coquelles - France;t;Paris <> Londres - OUIBUS;OUIBUS
 +3360;1137;Londres > Lille > Paris centre - Bercy;4;295;XDB;Lille;f;Paris <> Londres - OUIBUS;OUIBUS
 +3361;1137;Londres > Lille > Paris centre - Bercy;5;527;XPB;Paris City Center - Bercy;f;Paris <> Londres - OUIBUS;OUIBUS
 diff --git a/spec/fixtures/simple_importer/stop_area.csv b/spec/fixtures/simple_importer/stop_area.csv new file mode 100644 index 000000000..9361d022b --- /dev/null +++ b/spec/fixtures/simple_importer/stop_area.csv @@ -0,0 +1,2 @@ +name;lat;long;type;street_name +Nom du Stop;45.00;12;ZDEP;99 rue des Poissonieres diff --git a/spec/fixtures/simple_importer/stop_area_full.csv b/spec/fixtures/simple_importer/stop_area_full.csv new file mode 100644 index 000000000..250caab30 --- /dev/null +++ b/spec/fixtures/simple_importer/stop_area_full.csv @@ -0,0 +1,3 @@ +"id";"station_code";"uic_code";"country_code";"province";"district";"county";"station_name";"inactive";"change_timestamp";"longitude";"latitude";"parent_station_code";"additional_info";"external_reference";"timezone";"address";"postal_code";"city" +5669;"PAR";"PAR";"FRA";"";"";"";"Paris - All stations";f;"2017-07-17 11:56:53.138";2.35222190000002;48.856614;"";"";"{""Région"":""Ile-de-France""}";"Europe/Paris";"";"";"" +5748;"XED";"XED";"FRA";"";"";"";"Paris MLV";t;"2017-05-29 11:24:34.575";2.783409;48.870569;"PAR";"";"{""Région"":""Ile-de-France""}";"Europe/Paris";"";"";"" diff --git a/spec/fixtures/simple_importer/stop_area_full_reverse.csv b/spec/fixtures/simple_importer/stop_area_full_reverse.csv new file mode 100644 index 000000000..9ea15f6cc --- /dev/null +++ b/spec/fixtures/simple_importer/stop_area_full_reverse.csv @@ -0,0 +1,3 @@ +"id";"station_code";"uic_code";"country_code";"province";"district";"county";"station_name";"inactive";"change_timestamp";"longitude";"latitude";"parent_station_code";"additional_info";"external_reference";"timezone";"address";"postal_code";"city" +5748;"XED";"XED";"FRA";"";"";"";"Paris MLV";t;"2017-05-29 11:24:34.575";2.783409;48.870569;"PAR";"";"{""Région"":""Ile-de-France""}";"Europe/Paris";"";"";"" +5669;"PAR";"PAR";"FRA";"";"";"";"Paris - All stations";f;"2017-07-17 11:56:53.138";2.35222190000002;48.856614;"";"";"{""Région"":""Ile-de-France""}";"Europe/Paris";"";"";"" diff --git a/spec/fixtures/simple_importer/stop_area_incomplete.csv b/spec/fixtures/simple_importer/stop_area_incomplete.csv new file mode 100644 index 000000000..9b11aa02c --- /dev/null +++ b/spec/fixtures/simple_importer/stop_area_incomplete.csv @@ -0,0 +1,3 @@ +name;lat;long;type;street_name +Foo;45.00;12;ZDEP +;45.00;12;ZDEP diff --git a/spec/fixtures/simple_importer/stop_area_missing_street_name.csv b/spec/fixtures/simple_importer/stop_area_missing_street_name.csv new file mode 100644 index 000000000..aa845c3f5 --- /dev/null +++ b/spec/fixtures/simple_importer/stop_area_missing_street_name.csv @@ -0,0 +1,2 @@ +name;lat;long;type;foo +Nom du Stop;45.00;12;ZDEP;blabla diff --git a/spec/fixtures/simple_importer/stop_points_full.csv b/spec/fixtures/simple_importer/stop_points_full.csv new file mode 100644 index 000000000..9c5b3c1b7 --- /dev/null +++ b/spec/fixtures/simple_importer/stop_points_full.csv @@ -0,0 +1,11 @@ +"id";"timetable_route_id";"route_name";"stop_sequence";"stop_distance";"station_code";"station_name";"border"
 +3354;1136;"Paris centre - Bercy > Lille > Londres";1;0;"XPB";"Paris City Center - Bercy";f
 +3355;1136;"Paris centre - Bercy > Lille > Londres";2;232;"XDB";"Lille";f
 +3749;1136;"Paris centre - Bercy > Lille > Londres";3;350;"COF";"Coquelles - France";t
 +4772;1136;"Paris centre - Bercy > Lille > Londres";4;350;"COU";"Coquelles - UK";t
 +3357;1136;"Paris centre - Bercy > Lille > Londres";5;527;"ZEP";"London";f
 +3358;1137;"Londres > Lille > Paris centre - Bercy";1;0;"ZEP";"London";f
 +3559;1137;"Londres > Lille > Paris centre - Bercy";2;177;"COU";"Coquelles - UK";t
 +3743;1137;"Londres > Lille > Paris centre - Bercy";3;177;"COF";"Coquelles - France";t
 +3360;1137;"Londres > Lille > Paris centre - Bercy";4;295;"XDB";"Lille";f
 +3361;1137;"Londres > Lille > Paris centre - Bercy";5;527;"XPB";"Paris City Center - Bercy";f
 diff --git a/spec/helpers/table_builder_helper_spec.rb b/spec/helpers/table_builder_helper_spec.rb index 5bddbb16f..478875118 100644 --- a/spec/helpers/table_builder_helper_spec.rb +++ b/spec/helpers/table_builder_helper_spec.rb @@ -15,8 +15,9 @@ describe TableBuilderHelper, type: :helper do    describe "#table_builder_2" do      it "builds a table" do -      referential = build_stubbed(:workbench_referential) +      referential = create(:workbench_referential)        workbench = referential.workbench +      referential.organisation.workbenches << workbench        user_context = UserContext.new(          build_stubbed( @@ -30,7 +31,8 @@ describe TableBuilderHelper, type: :helper do          ),          referential: referential        ) -      allow(helper).to receive(:current_user).and_return(user_context) +      allow(helper).to receive(:pundit_user).and_return(user_context) +      allow(helper).to receive(:current_user).and_return(user_context.user)        referentials = [referential] @@ -90,7 +92,7 @@ describe TableBuilderHelper, type: :helper do                          </ul>                          <ul class="other">                              <li class=""><a href="/referentials/#{referential.id}/time_tables">Calendriers</a></li> -                            <li class=""><a href="/referentials/new?from=#{referential.id}">Dupliquer</a></li> +                            <li class=""><a href="/workbenches/#{workbench.id}/referentials/new?from=#{referential.id}">Dupliquer</a></li>                              <li class=""><a href="/referentials/#{referential.id}/select_compliance_control_set">Valider</a></li>                              <li class=""><a rel="nofollow" data-method="put" href="/referentials/#{referential.id}/archive">Conserver</a></li>                          </ul> @@ -193,7 +195,8 @@ describe TableBuilderHelper, type: :helper do          ),          referential: referential        ) -      allow(helper).to receive(:current_user).and_return(user_context) +      allow(helper).to receive(:pundit_user).and_return(user_context) +      allow(helper).to receive(:current_user).and_return(user_context.user)        allow(helper).to receive(:current_referential)          .and_return(referential) @@ -307,7 +310,8 @@ describe TableBuilderHelper, type: :helper do          ),          referential: referential        ) -      allow(helper).to receive(:current_user).and_return(user_context) +      allow(helper).to receive(:pundit_user).and_return(user_context) +      allow(helper).to receive(:current_user).and_return(user_context.user)        allow(helper).to receive(:current_referential)          .and_return(referential) @@ -398,8 +402,8 @@ describe TableBuilderHelper, type: :helper do      end      context "on a single row" do -      let(:referential){ build_stubbed :referential } -      let(:other_referential){ build_stubbed :referential } +      let(:referential){ build_stubbed :workbench_referential } +      let(:other_referential){ build_stubbed :workbench_referential }        let(:user_context){          UserContext.new(            build_stubbed( @@ -432,7 +436,9 @@ describe TableBuilderHelper, type: :helper do        let(:items){ [item, other_item] }        before(:each){ -        allow(helper).to receive(:current_user).and_return(user_context) +        allow(helper).to receive(:pundit_user).and_return(user_context) +        allow(helper).to receive(:current_user).and_return(user_context.user) +        allow(helper).to receive(:mutual_workbench).and_return(referential.workbench)        }        context "with all rows non-selectable" do diff --git a/spec/javascript/preprocessor.js b/spec/javascript/preprocessor.js new file mode 100644 index 000000000..a2de8e4be --- /dev/null +++ b/spec/javascript/preprocessor.js @@ -0,0 +1,15 @@ +'use strict'; + +var coffee = require('coffeescript'); + +module.exports = { +  process: function(src, filename) { +    if (coffee.helpers.isCoffee(filename)) { +      return coffee.compile(src, { +        'bare': false, +        'inlineMap': true +      }) +    } +    return src; +  } +}; diff --git a/spec/javascript/time_table/actions_spec.js b/spec/javascript/time_table/actions_spec.js index 9c1a6b6f1..003b7f6b5 100644 --- a/spec/javascript/time_table/actions_spec.js +++ b/spec/javascript/time_table/actions_spec.js @@ -61,7 +61,6 @@ describe('actions', () => {      expect(actions.unselect2Tags(selectedItem)).toEqual(expectedAction)    }) -    it('should create an action to go to previous page', () => {      let pagination = {        currentPage: '2017-01-01', diff --git a/spec/javascript/vehicle_journeys/components/VehicleJourneys_spec.js b/spec/javascript/vehicle_journeys/components/VehicleJourneys_spec.js index 87151c64b..2a84cb9ca 100644 --- a/spec/javascript/vehicle_journeys/components/VehicleJourneys_spec.js +++ b/spec/javascript/vehicle_journeys/components/VehicleJourneys_spec.js @@ -1,6 +1,13 @@  import React, { Component } from 'react'  import VehicleJourneys from '../../../../app/javascript/vehicle_journeys/components/VehicleJourneys'  import renderer from 'react-test-renderer' +import fs from 'fs' + +import I18n from '../../../../public/javascripts/i18n' +import decorateI18n from '../../../../app/assets/javascripts/i18n/extended.coffee' +window.I18n = decorateI18n(I18n) +I18n.locale = "fr" +eval(fs.readFileSync('./public/javascripts/translations.js')+'')  describe('stopPointHeader', () => {    set('features', () => { diff --git a/spec/javascript/vehicle_journeys/components/__snapshots__/VehicleJourneys_spec.js.snap b/spec/javascript/vehicle_journeys/components/__snapshots__/VehicleJourneys_spec.js.snap index 703f727d7..818845ec8 100644 --- a/spec/javascript/vehicle_journeys/components/__snapshots__/VehicleJourneys_spec.js.snap +++ b/spec/javascript/vehicle_journeys/components/__snapshots__/VehicleJourneys_spec.js.snap @@ -19,20 +19,23 @@ exports[`stopPointHeader should display the city name 1`] = `            <div              className="strong mb-xs"            > -            ID course +            ID Course            </div>            <div> -            Nom course +            Nom Course            </div>            <div> -            ID mission +            ID Mission            </div>            <div> -            Transporteur +            transporteur            </div>            <div> -            Calendriers +            calendrier            </div> +          <div +            className="detailed-timetables hidden" +          />          </div>          <div            className="td" @@ -109,20 +112,23 @@ exports[`stopPointHeader with the "long_distance_routes" feature should display            <div              className="strong mb-xs"            > -            ID course +            ID Course            </div>            <div> -            Nom course +            Nom Course            </div>            <div> -            ID mission +            ID Mission            </div>            <div> -            Transporteur +            transporteur            </div>            <div> -            Calendriers +            calendrier            </div> +          <div +            className="detailed-timetables hidden" +          />          </div>          <div            className="td" diff --git a/spec/javascript/vehicle_journeys/reducers/vehicleJourneys_spec.js b/spec/javascript/vehicle_journeys/reducers/vehicleJourneys_spec.js index 0d7612a80..389c60add 100644 --- a/spec/javascript/vehicle_journeys/reducers/vehicleJourneys_spec.js +++ b/spec/javascript/vehicle_journeys/reducers/vehicleJourneys_spec.js @@ -152,7 +152,7 @@ describe('vehicleJourneys reducer', () => {        },        departure_time : {          hour: 23, -        minute: 2 +        minute: 12        },        departure_day_offset: -1,        arrival_day_offset: -1, @@ -178,11 +178,11 @@ describe('vehicleJourneys reducer', () => {        delta : 0,        arrival_time : {          hour: 0, -        minute: 32 +        minute: 42        },        departure_time : {          hour: 0, -        minute: 32 +        minute: 42        },        stop_point_objectid: 'test-4',        stop_area_cityname: 'city', @@ -219,7 +219,7 @@ describe('vehicleJourneys reducer', () => {          type: 'ADD_VEHICLEJOURNEY',          data: fakeData,          selectedJourneyPattern: fakeSelectedJourneyPattern, -        stopPointsList: [{object_id: 'test-1', city_name: 'city', stop_area_id: 1, id: 1, time_zone_offset: 0}, {object_id: 'test-2', city_name: 'city', stop_area_id: 2, id: 2, time_zone_offset: -3600}, {object_id: 'test-3', city_name: 'city', stop_area_id: 3, id: 3, time_zone_offset: 0}, {object_id: 'test-4', city_name: 'city', stop_area_id: 4, id: 4, time_zone_offset: 0}], +        stopPointsList: [{object_id: 'test-1', city_name: 'city', stop_area_id: 1, id: 1, time_zone_offset: 0, waiting_time: null}, {object_id: 'test-2', city_name: 'city', stop_area_id: 2, id: 2, time_zone_offset: -3600, waiting_time: 10}, {object_id: 'test-3', city_name: 'city', stop_area_id: 3, id: 3, time_zone_offset: 0, waiting_time: 20}, {object_id: 'test-4', city_name: 'city', stop_area_id: 4, id: 4, time_zone_offset: 0}],          selectedCompany: fakeSelectedCompany        })      ).toEqual([{ diff --git a/spec/mailers/calendar_mailer_spec.rb b/spec/mailers/calendar_mailer_spec.rb index 9a2076f64..00d73a58b 100644 --- a/spec/mailers/calendar_mailer_spec.rb +++ b/spec/mailers/calendar_mailer_spec.rb @@ -20,7 +20,7 @@ RSpec.describe CalendarMailer, type: :mailer do      end      it 'should have correct body' do -      key = I18n.t("mailers.calendar_mailer.#{type}.body", cal_name: calendar.name, cal_index_url: calendars_url) +      key = I18n.t("mailers.calendar_mailer.#{type}.body", cal_name: calendar.name, cal_index_url: workgroup_calendars_url(calendar.workgroup))        expect(email).to have_body_text /#{key}/      end    end diff --git a/spec/models/calendar_spec.rb b/spec/models/calendar_spec.rb index 3cffd0f8a..a5c0a7471 100644 --- a/spec/models/calendar_spec.rb +++ b/spec/models/calendar_spec.rb @@ -36,7 +36,7 @@ RSpec.describe Calendar, :type => :model do      end      it 'validates that dates and date_ranges do not overlap but allow for days not in the list' do -      expect(build(:calendar, dates: [Date.today.beginning_of_week], date_ranges: [Date.today.beginning_of_week..Date.today], int_day_types: Calendar::THURSDAY)).to be_valid +      expect(build(:calendar, dates: [Date.today.beginning_of_week - 1.week], date_ranges: [(Date.today.beginning_of_week - 1.week)..Date.today], int_day_types: Calendar::THURSDAY)).to be_valid      end      it 'validates that there are no duplicates in dates' do diff --git a/spec/models/chouette/footnote_spec.rb b/spec/models/chouette/footnote_spec.rb index fc5e5f306..05f55c2f0 100644 --- a/spec/models/chouette/footnote_spec.rb +++ b/spec/models/chouette/footnote_spec.rb @@ -1,12 +1,12 @@  require 'spec_helper'  describe Chouette::Footnote, type: :model do -  let(:footnote) { create(:footnote) } +  subject { create(:footnote) }    it { should validate_presence_of :line }    describe 'data_source_ref' do      it 'should set default if omitted' do -      expect(footnote.data_source_ref).to eq "DATASOURCEREF_EDITION_BOIV" +      expect(subject.data_source_ref).to eq "DATASOURCEREF_EDITION_BOIV"      end      it 'should not set default if not omitted' do @@ -18,16 +18,16 @@ describe Chouette::Footnote, type: :model do    end    describe 'checksum' do -    it_behaves_like 'checksum support', :footnote +    it_behaves_like 'checksum support'      context '#checksum_attributes' do        it 'should return code and label' do -        expected = [footnote.code, footnote.label] -        expect(footnote.checksum_attributes).to include(*expected) +        expected = [subject.code, subject.label] +        expect(subject.checksum_attributes).to include(*expected)        end        it 'should not return other atrributes' do -        expect(footnote.checksum_attributes).to_not include(footnote.updated_at) +        expect(subject.checksum_attributes).to_not include(subject.updated_at)        end      end    end diff --git a/spec/models/chouette/journey_pattern_spec.rb b/spec/models/chouette/journey_pattern_spec.rb index b5eb9004c..7c767e4d1 100644 --- a/spec/models/chouette/journey_pattern_spec.rb +++ b/spec/models/chouette/journey_pattern_spec.rb @@ -2,9 +2,10 @@ require 'spec_helper'  describe Chouette::JourneyPattern, :type => :model do    it { is_expected.to be_versioned } +  subject { create(:journey_pattern) }    describe 'checksum' do -    it_behaves_like 'checksum support', :journey_pattern +    it_behaves_like 'checksum support'    end    # context 'validate minimum stop_points size' do @@ -70,6 +71,30 @@ describe Chouette::JourneyPattern, :type => :model do      end    end +  describe "set_distances" do +    let(:journey_pattern) { create :journey_pattern } +    let(:distances){ [] } +    it "should raise an error" do +      expect{journey_pattern.set_distances(distances)}.to raise_error +    end + +    context "with consistent data" do +      let(:distances){ [0, 100, "200", 500, 1000] } + +      it "should set costs" do +        expect{journey_pattern.set_distances(distances)}.to_not raise_error +        start, stop = journey_pattern.stop_points[0..1] +        expect(journey_pattern.costs_between(start, stop)[:distance]).to eq 100 +        start, stop = journey_pattern.stop_points[1..2] +        expect(journey_pattern.costs_between(start, stop)[:distance]).to eq 100 +        start, stop = journey_pattern.stop_points[2..3] +        expect(journey_pattern.costs_between(start, stop)[:distance]).to eq 300 +        start, stop = journey_pattern.stop_points[3..4] +        expect(journey_pattern.costs_between(start, stop)[:distance]).to eq 500 +      end +    end +  end +    describe "state_update" do      def journey_pattern_to_state jp        jp.attributes.slice('name', 'published_name', 'registration_number').tap do |item| diff --git a/spec/models/chouette/route/route_base_spec.rb b/spec/models/chouette/route/route_base_spec.rb index 26f57eae5..98cb3e358 100644 --- a/spec/models/chouette/route/route_base_spec.rb +++ b/spec/models/chouette/route/route_base_spec.rb @@ -1,8 +1,8 @@  RSpec.describe Chouette::Route, :type => :model do -    subject { create(:route) } +    describe 'checksum' do -    it_behaves_like 'checksum support', :route +    it_behaves_like 'checksum support'    end    it { is_expected.to enumerize(:direction).in(:straight_forward, :backward, :clockwise, :counter_clockwise, :north, :north_west, :west, :south_west, :south, :south_east, :east, :north_east) } diff --git a/spec/models/chouette/routing_constraint_zone_spec.rb b/spec/models/chouette/routing_constraint_zone_spec.rb index 0282cb8b1..bda6bb04a 100644 --- a/spec/models/chouette/routing_constraint_zone_spec.rb +++ b/spec/models/chouette/routing_constraint_zone_spec.rb @@ -11,7 +11,7 @@ describe Chouette::RoutingConstraintZone, type: :model do    it { is_expected.to be_versioned }    describe 'checksum' do -    it_behaves_like 'checksum support', :routing_constraint_zone +    it_behaves_like 'checksum support'    end    describe 'validations' do diff --git a/spec/models/chouette/time_table_period_spec.rb b/spec/models/chouette/time_table_period_spec.rb index cc1a3ae09..e14d38ade 100644 --- a/spec/models/chouette/time_table_period_spec.rb +++ b/spec/models/chouette/time_table_period_spec.rb @@ -10,7 +10,7 @@ describe Chouette::TimeTablePeriod, :type => :model do    it { is_expected.to validate_presence_of :period_end }    describe 'checksum' do -    it_behaves_like 'checksum support', :time_table_period +    it_behaves_like 'checksum support'    end    describe "#overlap" do diff --git a/spec/models/chouette/time_table_spec.rb b/spec/models/chouette/time_table_spec.rb index a501f234a..bb88877b9 100644 --- a/spec/models/chouette/time_table_spec.rb +++ b/spec/models/chouette/time_table_spec.rb @@ -1049,7 +1049,7 @@ end    # it { is_expected.to validate_uniqueness_of :objectid }    describe 'checksum' do -    it_behaves_like 'checksum support', :time_table +    it_behaves_like 'checksum support'      it "handles newly built dates and periods" do        time_table = build(:time_table) diff --git a/spec/models/chouette/vehicle_journey_at_stop_spec.rb b/spec/models/chouette/vehicle_journey_at_stop_spec.rb index 02306883c..f79d19c88 100644 --- a/spec/models/chouette/vehicle_journey_at_stop_spec.rb +++ b/spec/models/chouette/vehicle_journey_at_stop_spec.rb @@ -1,14 +1,16 @@  require 'spec_helper'  RSpec.describe Chouette::VehicleJourneyAtStop, type: :model do +  subject { create(:vehicle_journey_at_stop) } +    describe 'checksum' do      let(:at_stop) { build_stubbed(:vehicle_journey_at_stop) } -    it_behaves_like 'checksum support', :vehicle_journey_at_stop +    it_behaves_like 'checksum support'      context '#checksum_attributes' do        it 'should return attributes' do -        expected = [at_stop.departure_time.to_s(:time), at_stop.arrival_time.to_s(:time)] +        expected = [at_stop.departure_time.utc.to_s(:time), at_stop.arrival_time.utc.to_s(:time)]          expected << at_stop.departure_day_offset.to_s          expected << at_stop.arrival_day_offset.to_s          expect(at_stop.checksum_attributes).to include(*expected) diff --git a/spec/models/chouette/vehicle_journey_spec.rb b/spec/models/chouette/vehicle_journey_spec.rb index 7279980a3..76e73d9cf 100644 --- a/spec/models/chouette/vehicle_journey_spec.rb +++ b/spec/models/chouette/vehicle_journey_spec.rb @@ -1,6 +1,8 @@  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) } @@ -21,7 +23,7 @@ describe Chouette::VehicleJourney, :type => :model do    end    describe 'checksum' do -    it_behaves_like 'checksum support', :vehicle_journey +    it_behaves_like 'checksum support'      it "changes when a vjas is updated" do        vehicle_journey = create(:vehicle_journey)        expect{vehicle_journey.vehicle_journey_at_stops.last.update_attribute(:departure_time, Time.now)}.to change{vehicle_journey.reload.checksum} diff --git a/spec/models/compliance_check_spec.rb b/spec/models/compliance_check_spec.rb index f83d78c29..ffa59245c 100644 --- a/spec/models/compliance_check_spec.rb +++ b/spec/models/compliance_check_spec.rb @@ -15,4 +15,36 @@ RSpec.describe ComplianceCheck, type: :model do    it { should validate_presence_of :name }    it { should validate_presence_of :code }    it { should validate_presence_of :origin_code } + +  describe ".abort_old" do +    it "changes check sets older than 4 hours to aborted" do +      Timecop.freeze(Time.now) do +        old_check_set = create( +          :compliance_check_set, +          status: 'pending', +          created_at: 4.hours.ago - 1.minute +        ) +        current_check_set = create(:compliance_check_set, status: 'pending') + +        ComplianceCheckSet.abort_old + +        expect(current_check_set.reload.status).to eq('pending') +        expect(old_check_set.reload.status).to eq('aborted') +      end +    end + +    it "doesn't work on check sets with a `finished_status`" do +      Timecop.freeze(Time.now) do +        check_set = create( +          :compliance_check_set, +          status: 'successful', +          created_at: 4.hours.ago - 1.minute +        ) + +        ComplianceCheckSet.abort_old + +        expect(check_set.reload.status).to eq('successful') +      end +    end +  end  end diff --git a/spec/models/import_spec.rb b/spec/models/import_spec.rb index ffb2360c2..8b85f151b 100644 --- a/spec/models/import_spec.rb +++ b/spec/models/import_spec.rb @@ -29,6 +29,58 @@ RSpec.describe Import, type: :model do      )    end +  describe ".abort_old" do +    it "changes imports older than 4 hours to aborted" do +      Timecop.freeze(Time.now) do +        old_import = create( +          :workbench_import, +          status: 'pending', +          created_at: 4.hours.ago - 1.minute +        ) +        current_import = create(:workbench_import, status: 'pending') + +        Import.abort_old + +        expect(current_import.reload.status).to eq('pending') +        expect(old_import.reload.status).to eq('aborted') +      end +    end + +    it "doesn't work on imports with a `finished_status`" do +      Timecop.freeze(Time.now) do +        import = create( +          :workbench_import, +          status: 'successful', +          created_at: 4.hours.ago - 1.minute +        ) + +        Import.abort_old + +        expect(import.reload.status).to eq('successful') +      end +    end + +    it "only works on the caller type" do +      Timecop.freeze(Time.now) do +        workbench_import = create( +          :workbench_import, +          status: 'pending', +          created_at: 4.hours.ago - 1.minute +        ) +        netex_import = create( +          :netex_import, +          status: 'pending', +          created_at: 4.hours.ago - 1.minute +        ) + +        NetexImport.abort_old + +        expect(workbench_import.reload.status).to eq('pending') +        expect(netex_import.reload.status).to eq('aborted') +      end +    end +  end +    describe "#destroy" do      it "must destroy all child imports" do        netex_import = create(:netex_import) diff --git a/spec/models/simple_importer_spec.rb b/spec/models/simple_importer_spec.rb new file mode 100644 index 000000000..60d7b7882 --- /dev/null +++ b/spec/models/simple_importer_spec.rb @@ -0,0 +1,394 @@ +RSpec.describe SimpleImporter do +  describe "#define" do +    context "with an incomplete configuration" do + +      it "should raise an error" do +        expect do +          SimpleImporter.define :foo +        end.to raise_error +      end +    end +    context "with a complete configuration" do +      before do +        SimpleImporter.define :foo do |config| +          config.model = "example" +        end +      end + +      it "should define an importer" do +        expect{SimpleImporter.find_configuration(:foo)}.to_not raise_error +        expect{SimpleImporter.new(configuration_name: :foo, filepath: "")}.to_not raise_error +        expect{SimpleImporter.find_configuration(:bar)}.to raise_error +        expect{SimpleImporter.new(configuration_name: :bar, filepath: "")}.to raise_error +        expect{SimpleImporter.create(configuration_name: :foo, filepath: "")}.to change{SimpleImporter.count}.by 1 +      end +    end +  end + +  describe "#import" do +    let(:importer){ importer = SimpleImporter.new(configuration_name: :test, filepath: filepath) } +    let(:filepath){ fixtures_path 'simple_importer', filename } +    let(:filename){ "stop_area.csv" } +    let(:stop_area_referential){ create(:stop_area_referential, objectid_format: :stif_netex) } + +    before(:each) do +      SimpleImporter.define :test do |config| +        config.model = Chouette::StopArea +        config.separator = ";" +        config.key = "name" +        config.add_column :name +        config.add_column :lat, attribute: :latitude +        config.add_column :lat, attribute: :longitude, value: ->(raw){ raw.to_f + 1 } +        config.add_column :type, attribute: :area_type, value: ->(raw){ raw&.downcase } +        config.add_column :street_name +        config.add_column :stop_area_referential, value: stop_area_referential +        config.add_value  :kind, :commercial +      end +    end + +    it "should import the given file" do +      expect{importer.import verbose: false}.to change{Chouette::StopArea.count}.by 1 +      expect(importer.status).to eq "success" +      stop = Chouette::StopArea.last +      expect(stop.name).to eq "Nom du Stop" +      expect(stop.latitude).to eq 45.00 +      expect(stop.longitude).to eq 46.00 +      expect(stop.area_type).to eq "zdep" +      expect(importer.reload.journal.last["event"]).to eq("creation") +    end + +    context "when overriding configuration" do +      before(:each){ +        importer.configure do |config| +          config.add_value :latitude, 88 +        end +      } + +      it "should import the given file and not mess with the global configuration" do +        expect{importer.import}.to change{Chouette::StopArea.count}.by 1 +        expect(importer.status).to eq "success" +        stop = Chouette::StopArea.last +        expect(stop.latitude).to eq 88 +        importer = SimpleImporter.new(configuration_name: :test, filepath: filepath) +        expect{importer.import}.to change{Chouette::StopArea.count}.by 0 +        expect(stop.reload.latitude).to eq 45 +      end +    end + +    context "with an already existing record" do +      let(:filename){ "stop_area.csv" } +      before(:each){ +        create :stop_area, name: "Nom du Stop" +      } +      it "should only update the record" do +        expect{importer.import}.to change{Chouette::StopArea.count}.by 0 +        expect(importer.status).to eq "success" +        stop = Chouette::StopArea.last +        expect(stop.name).to eq "Nom du Stop" +        expect(stop.latitude).to eq 45.00 +        expect(stop.longitude).to eq 46.00 +        expect(stop.area_type).to eq "zdep" +        expect(importer.reload.journal.last["event"]).to eq("update") +      end + +      context "in another scope" do +        before(:each) do +          ref = create(:stop_area_referential) +          importer.configure do |config| +            config.context = { stop_area_referential: ref } +            config.scope = ->{ context[:stop_area_referential].stop_areas } +          end +        end + +        it "should create the record" do +          expect{importer.import verbose: false}.to change{Chouette::StopArea.count}.by 1 +          expect(importer.status).to eq "success" +        end +      end +    end + +    context "with a missing column" do +      let(:filename){ "stop_area_missing_street_name.csv" } +      it "should set an error message" do +        expect{importer.import(verbose: false)}.to_not raise_error +        expect(importer.status).to eq "success_with_warnings" +        expect(importer.reload.journal.first["event"]).to eq("column_not_found") +      end +    end + +    context "with an incomplete dataset" do +      let(:filename){ "stop_area_incomplete.csv" } +      it "should fail" do +        expect{importer.import}.to_not raise_error +        expect(importer.status).to eq "failed" +        expect(importer.reload.journal.last["message"]).to eq({"name" => ["doit être rempli(e)"]}) +      end + +      it "should be transactional" do +        expect{importer.import}.to_not change {Chouette::StopArea.count} +      end +    end + +    context "with a wrong filepath" do +      let(:filename){ "not_found.csv" } +      it "should fail" do +        expect{importer.import}.to_not raise_error +        expect(importer.status).to eq "failed" +        expect(importer.reload.journal.first["message"]).to eq "File not found: #{importer.filepath}" +      end +    end + +    context "with a custom behaviour" do +      let!(:present){ create :stop_area, name: "Nom du Stop", stop_area_referential: stop_area_referential } +      let!(:missing){ create :stop_area, name: "Another", stop_area_referential: stop_area_referential } +      before(:each){ +        importer.configure do |config| +          config.before do |importer| +            stop_area_referential.stop_areas.each &:deactivate! +          end + +          config.before(:each_save) do |importer, stop_area| +            stop_area.activate! +          end +        end +      } + +      it "should disable all missing areas" do +        expect{importer.import}.to change{Chouette::StopArea.count}.by 0 +        expect(present.reload.activated?).to be_truthy +        expect(missing.reload.activated?).to be_falsy +      end + +      context "with an error" do +        let(:filename){ "stop_area_incomplete.csv" } +        it "should do nothing" do +          expect{importer.import}.to_not change {Chouette::StopArea.count} +          expect(present.reload.activated?).to be_truthy +          expect(missing.reload.activated?).to be_truthy +        end +      end +    end + +    context "with a full file" do +      let(:filename){ "stop_area_full.csv" } +      let!(:missing){ create :stop_area, name: "Another", stop_area_referential: stop_area_referential } + +      before(:each) do +        SimpleImporter.define :test do |config| +          config.model = Chouette::StopArea +          config.separator = ";" +          config.key = "station_code" +          config.add_column :station_code, attribute: :registration_number +          config.add_column :country_code +          config.add_column :station_name, attribute: :name +          config.add_column :inactive, attribute: :deleted_at, value: ->(raw){ raw == "t" ? Time.now : nil } +          config.add_column :change_timestamp, attribute: :updated_at +          config.add_column :longitude +          config.add_column :latitude +          config.add_column :parent_station_code, attribute: :parent, value: ->(raw){ raw.present? && resolve(:station_code, raw){|value| Chouette::StopArea.find_by(registration_number: value) } } +          config.add_column :parent_station_code, attribute: :area_type, value: ->(raw){ raw.present? ? "zdep" : "gdl" } +          config.add_column :timezone, attribute: :time_zone +          config.add_column :address, attribute: :street_name +          config.add_column :postal_code, attribute: :zip_code +          config.add_column :city, attribute: :city_name +          config.add_value  :stop_area_referential_id, stop_area_referential.id +          config.add_value  :long_lat_type, "WGS84" +          config.add_value  :kind, :commercial +          config.before do |importer| +            stop_area_referential.stop_areas.each &:deactivate! +          end + +          config.before(:each_save) do |importer, stop_area| +            stop_area.activate +          end +        end +      end + +      it "should import the given file" do +        expect{importer.import(verbose: false)}.to change{Chouette::StopArea.count}.by 2 +        expect(importer.status).to eq "success" +        first = Chouette::StopArea.find_by registration_number: "PAR" +        last = Chouette::StopArea.find_by registration_number: "XED" + +        expect(last.parent).to eq first +        expect(first.area_type).to eq "gdl" +        expect(last.area_type).to eq "zdep" +        expect(first.long_lat_type).to eq "WGS84" +        expect(first.activated?).to be_truthy +        expect(last.activated?).to be_truthy +        expect(missing.reload.activated?).to be_falsy +      end + +      context "with a relation in reverse order" do +        let(:filename){ "stop_area_full_reverse.csv" } + +        it "should import the given file" do +          expect{importer.import}.to change{Chouette::StopArea.count}.by 2 +          expect(importer.status).to eq "success" +          first = Chouette::StopArea.find_by registration_number: "XED" +          last = Chouette::StopArea.find_by registration_number: "PAR" +          expect(first.parent).to eq last +        end +      end +    end + +    context "with a specific importer" do +      let(:filename){ "stop_points_full.csv" } + +      before(:each) do +        create :line, name: "Paris <> Londres - OUIBUS" + +        SimpleImporter.define :test do |config| +          config.model = Chouette::Route +          config.separator = ";" +          config.context = {stop_area_referential: stop_area_referential} + +          config.before do |importer| +            mapping = {} +            path = Rails.root + "spec/fixtures/simple_importer/lines_mapping.csv" +            CSV.foreach(path, importer.configuration.csv_options) do |row| +              if row["Ligne Chouette"].present? +                mapping[row["timetable_route_id"]] ||= Chouette::Line.find_by(name: importer.encode_string(row["Ligne Chouette"])) +              end +            end +            importer.context[:mapping] = mapping +          end + +          config.custom_handler do |row| +            line = nil +            fail_with_error "MISSING LINE FOR ROUTE: #{encode_string row["route_name"]}" do +              line = context[:mapping][row["timetable_route_id"]] +              raise unless line +            end +            @current_record = Chouette::Route.find_or_initialize_by number: row["timetable_route_id"] +            @current_record.name = encode_string row["route_name"] +            @current_record.published_name = encode_string row["route_name"] + +            @current_record.line = line +            if @prev_route != @current_record +              if @prev_route && @prev_route.valid? +                journey_pattern = @prev_route.full_journey_pattern +                fail_with_error "WRONG DISTANCES FOR ROUTE #{@prev_route.name} (#{@prev_route.number}): #{@distances.count} distances for #{@prev_route.stop_points.count} stops" do +                  journey_pattern.stop_points = @prev_route.stop_points +                  journey_pattern.set_distances @distances +                end +                fail_with_error ->(){ journey_pattern.errors.messages } do +                  journey_pattern.save! +                end +              end +              @distances = [] +            end +            @distances.push row["stop_distance"] +            position = row["stop_sequence"].to_i - 1 + +            stop_area = context[:stop_area_referential].stop_areas.where(registration_number: row["station_code"]).last +            unless stop_area +              stop_area = Chouette::StopArea.new registration_number: row["station_code"] +              stop_area.name = row["station_name"] +              stop_area.kind = row["border"] == "f" ? :commercial : :non_commercial +              stop_area.area_type = row["border"] == "f" ? :zdep : :border +              stop_area.stop_area_referential = context[:stop_area_referential] +              fail_with_error ->{p stop_area; "UNABLE TO CREATE STOP_AREA: #{stop_area.errors.messages}" }, abort_row: true do +                stop_area.save! +              end +            end +            stop_point = @current_record.stop_points.find_by(stop_area_id: stop_area.id) +            if stop_point +              stop_point.set_list_position position +            else +              stop_point = @current_record.stop_points.build(stop_area_id: stop_area.id, position: position) +              stop_point.for_boarding = :normal +              stop_point.for_alighting = :normal +            end + +            @prev_route = @current_record +          end + +          config.after(:each_save) do |importer, route| +            opposite_route_name = route.name.split(" > ").reverse.join(' > ') +            opposite_route = Chouette::Route.where(name: opposite_route_name).where('id < ?', route.id).last +            if opposite_route && opposite_route.line == route.line +              route.update_attribute :wayback, :inbound +              opposite_route.update_attribute :wayback, :outbound +              route.update_attribute :opposite_route_id, opposite_route.id +              opposite_route.update_attribute :opposite_route_id, route.id +            end +          end + +          config.after do |importer| +            prev_route = importer.instance_variable_get "@prev_route" +            if prev_route && prev_route.valid? +              journey_pattern = prev_route.full_journey_pattern +              importer.fail_with_error "WRONG DISTANCES FOR ROUTE #{prev_route.name}: #{importer.instance_variable_get("@distances").count} distances for #{prev_route.stop_points.count} stops" do +                journey_pattern.set_distances importer.instance_variable_get("@distances") +                journey_pattern.stop_points = prev_route.stop_points +              end +              importer.fail_with_error ->(){ journey_pattern.errors.messages } do +                journey_pattern.save! +              end +            end +          end +        end +      end + +      it "should import the given file" do +        routes_count = Chouette::Route.count +        journey_pattern_count = Chouette::JourneyPattern.count +        stop_areas_count = Chouette::StopArea.count + +        expect{importer.import(verbose: false)}.to change{Chouette::StopPoint.count}.by 10 +        expect(importer.status).to eq "success" +        expect(Chouette::Route.count).to eq routes_count + 2 +        expect(Chouette::JourneyPattern.count).to eq journey_pattern_count + 2 +        expect(Chouette::StopArea.count).to eq stop_areas_count + 5 +        route = Chouette::Route.find_by number: 1136 +        expect(route.stop_areas.count).to eq 5 +        expect(route.opposite_route).to eq Chouette::Route.find_by(number: 1137) +        journey_pattern = route.full_journey_pattern +        expect(journey_pattern.stop_areas.count).to eq 5 +        start, stop = journey_pattern.stop_points[0..1] +        expect(journey_pattern.costs_between(start, stop)[:distance]).to eq 232 +        start, stop = journey_pattern.stop_points[1..2] +        expect(journey_pattern.costs_between(start, stop)[:distance]).to eq 118 +        start, stop = journey_pattern.stop_points[2..3] +        expect(journey_pattern.costs_between(start, stop)[:distance]).to eq 0 +        start, stop = journey_pattern.stop_points[3..4] +        expect(journey_pattern.costs_between(start, stop)[:distance]).to eq 177 + +        route = Chouette::Route.find_by number: 1137 +        expect(route.opposite_route).to eq Chouette::Route.find_by(number: 1136) +        expect(route.stop_areas.count).to eq 5 +        journey_pattern = route.full_journey_pattern +        expect(journey_pattern.stop_areas.count).to eq 5 +        start, stop = journey_pattern.stop_points[0..1] +        expect(journey_pattern.costs_between(start, stop)[:distance]).to eq 177 +        start, stop = journey_pattern.stop_points[1..2] +        expect(journey_pattern.costs_between(start, stop)[:distance]).to eq 0 +        start, stop = journey_pattern.stop_points[2..3] +        expect(journey_pattern.costs_between(start, stop)[:distance]).to eq 118 +        start, stop = journey_pattern.stop_points[3..4] +        expect(journey_pattern.costs_between(start, stop)[:distance]).to eq 232 + +        stop_area = Chouette::StopArea.where(registration_number: "XPB").last +        expect(stop_area.kind).to eq :commercial +        expect(stop_area.area_type).to eq :zdep + +        stop_area = Chouette::StopArea.where(registration_number: "XDB").last +        expect(stop_area.kind).to eq :commercial +        expect(stop_area.area_type).to eq :zdep + +        stop_area = Chouette::StopArea.where(registration_number: "COF").last +        expect(stop_area.kind).to eq :non_commercial +        expect(stop_area.area_type).to eq :border + +        stop_area = Chouette::StopArea.where(registration_number: "COU").last +        expect(stop_area.kind).to eq :non_commercial +        expect(stop_area.area_type).to eq :border + +        stop_area = Chouette::StopArea.where(registration_number: "ZEP").last +        expect(stop_area.kind).to eq :commercial +        expect(stop_area.area_type).to eq :zdep +      end +    end +  end +end diff --git a/spec/services/parent_import_notifier_spec.rb b/spec/services/parent_notifier_spec.rb index 3ab505f88..ecf508fcd 100644 --- a/spec/services/parent_import_notifier_spec.rb +++ b/spec/services/parent_notifier_spec.rb @@ -1,4 +1,4 @@ -RSpec.describe ParentImportNotifier do +RSpec.describe ParentNotifier do    let(:workbench_import) { create(:workbench_import) }    describe ".notify_when_finished" do @@ -20,7 +20,7 @@ RSpec.describe ParentImportNotifier do          expect(netex_import).to receive(:notify_parent)        end -      ParentImportNotifier.notify_when_finished(netex_imports) +      ParentNotifier.new(Import).notify_when_finished(netex_imports)      end      it "doesn't call #notify_parent if its `notified_parent_at` is set" do @@ -33,11 +33,11 @@ RSpec.describe ParentImportNotifier do        expect(netex_import).not_to receive(:notify_parent) -      ParentImportNotifier.notify_when_finished +      ParentNotifier.new(Import).notify_when_finished      end    end -  describe ".imports_pending_notification" do +  describe ".objects_pending_notification" do      it "includes imports with a parent and `notified_parent_at` unset" do        netex_import = create(          :netex_import, @@ -47,7 +47,7 @@ RSpec.describe ParentImportNotifier do        )        expect( -        ParentImportNotifier.imports_pending_notification +        ParentNotifier.new(Import).objects_pending_notification        ).to eq([netex_import])      end @@ -55,7 +55,7 @@ RSpec.describe ParentImportNotifier do        create(:import, parent: nil)        expect( -        ParentImportNotifier.imports_pending_notification +        ParentNotifier.new(Import).objects_pending_notification        ).to be_empty      end @@ -70,7 +70,7 @@ RSpec.describe ParentImportNotifier do        end        expect( -        ParentImportNotifier.imports_pending_notification +        ParentNotifier.new(Import).objects_pending_notification        ).to be_empty      end @@ -83,7 +83,7 @@ RSpec.describe ParentImportNotifier do        )        expect( -        ParentImportNotifier.imports_pending_notification +        ParentNotifier.new(Import).objects_pending_notification        ).to be_empty      end    end diff --git a/spec/support/checksum_support.rb b/spec/support/checksum_support.rb index e02d9f9f3..f8dffb1b7 100644 --- a/spec/support/checksum_support.rb +++ b/spec/support/checksum_support.rb @@ -1,25 +1,23 @@ -shared_examples 'checksum support' do |factory_name| -  let(:instance) { create(factory_name) } - +shared_examples 'checksum support' do    describe '#current_checksum_source' do      let(:attributes) { ['code_value', 'label_value'] } -    let(:seperator)  { ChecksumSupport::SEPARATOR } +    let(:separator)  { ChecksumSupport::SEPARATOR }      let(:nil_value)  { ChecksumSupport::VALUE_FOR_NIL_ATTRIBUTE }      before do -      allow_any_instance_of(instance.class).to receive(:checksum_attributes).and_return(attributes) +      allow_any_instance_of(subject.class).to receive(:checksum_attributes).and_return(attributes)      end -    it 'should separate attribute by seperator' do -      expect(instance.current_checksum_source).to eq("code_value#{seperator}label_value") +    it 'should separate attribute by separator' do +      expect(subject.current_checksum_source).to eq("code_value#{separator}label_value")      end      context 'nil value' do        let(:attributes) { ['code_value', nil] }        it 'should replace nil attributes by default value' do -        source = "code_value#{seperator}#{nil_value}" -        expect(instance.current_checksum_source).to eq(source) +        source = "code_value#{separator}#{nil_value}" +        expect(subject.current_checksum_source).to eq(source)        end      end @@ -27,27 +25,62 @@ shared_examples 'checksum support' do |factory_name|        let(:attributes) { ['code_value', []] }        it 'should convert to nil' do -        source = "code_value#{seperator}#{nil_value}" -        expect(instance.current_checksum_source).to eq(source) +        source = "code_value#{separator}#{nil_value}" +        expect(subject.current_checksum_source).to eq(source) +      end +    end + +    context 'array value' do +      let(:attributes) { [['v1', 'v2', 'v3'], 'code_value'] } + +      it 'should convert to list' do +        source = "v1,v2,v3#{separator}code_value" +        expect(subject.current_checksum_source).to eq(source) +      end +    end + +    context 'array of array value' do +      let(:attributes) { [[['a1', 'a2', 'a3'], ['b1', 'b2', 'b3']], 'code_value'] } + +      it 'should convert to list' do +        source = "(a1,a2,a3),(b1,b2,b3)#{separator}code_value" +        expect(subject.current_checksum_source).to eq(source) +      end +    end + +    context 'array of array value, with empty array' do +      let(:attributes) { [[['a1', 'a2', 'a3'], []], 'code_value'] } + +      it 'should convert to list' do +        source = "(a1,a2,a3),-#{separator}code_value" +        expect(subject.current_checksum_source).to eq(source)        end      end    end    it 'should save checksum on create' do -    expect(instance.checksum).to_not be_nil +    expect(subject.checksum).to_not be_nil    end    it 'should save checksum_source' do -    expect(instance.checksum_source).to_not be_nil +    expect(subject.checksum_source).to_not be_nil    end    it 'should trigger set_current_checksum_source on save' do -    expect(instance).to receive(:set_current_checksum_source).at_least(:once) -    instance.save +    expect(subject).to receive(:set_current_checksum_source).at_least(:once) +    subject.save    end    it 'should trigger update_checksum on save' do -    expect(instance).to receive(:update_checksum).at_least(:once) -    instance.save +    expect(subject).to receive(:update_checksum).at_least(:once) +    subject.save +  end + +  it "doesn't change the checksum on save if the source hasn't been changed" do +    checksum = subject.checksum + +    subject.save + +    expect(subject.checksum).to eq(checksum)    end  end diff --git a/spec/support/decorator_helpers.rb b/spec/support/decorator_helpers.rb index b2c41e842..544604f61 100644 --- a/spec/support/decorator_helpers.rb +++ b/spec/support/decorator_helpers.rb @@ -8,6 +8,7 @@ module Support          let( :features ){ [] }          let( :filtered_action_links){}          before do +          allow(subject.h).to receive(:duplicate_workbench_referential_path).and_return new_workbench_referential_path(referential.workbench, from: referential.id)            allow_any_instance_of(Draper::HelperProxy).to receive(:policy).and_return policy            allow_any_instance_of(AF83::Decorator::Link).to receive(:check_feature){|f|              features.include?(f) diff --git a/spec/support/integration_spec_helper.rb b/spec/support/integration_spec_helper.rb index 36306559d..7ba7e9f92 100644 --- a/spec/support/integration_spec_helper.rb +++ b/spec/support/integration_spec_helper.rb @@ -3,7 +3,11 @@ module IntegrationSpecHelper    def paginate_collection klass, decorator, page=1, context={}      collection = klass.page(page)      if decorator -      collection = ModelDecorator.decorate(collection, with: decorator, context: context) +      if decorator < AF83::Decorator +        collection = decorator.decorate(collection, context: context) +      else +        collection = ModelDecorator.decorate(collection, with: decorator, context: context) +      end      end      collection    end @@ -24,6 +28,13 @@ module IntegrationSpecHelper          context('', &block) if block_given?        end      end + +    def with_feature feature, &block +      context "with feature #{feature}" do +        let(:features){ [feature] } +        context('', &block) if block_given? +      end +    end    end    def self.included into @@ -48,7 +59,7 @@ RSpec::Matchers.define :have_link_for_each_item do |collection, name, opts|    end    description { "have #{name} link for each item" }    failure_message do -    "expected view to have #{name} link for each item, failed with selector: \"#{@selector}\"" +    "expected view to have one #{name} link for each item, failed with selector: \"#{@selector}\""    end  end diff --git a/spec/support/pundit/pundit_view_policy.rb b/spec/support/pundit/pundit_view_policy.rb index 91be0624c..63970de02 100644 --- a/spec/support/pundit/pundit_view_policy.rb +++ b/spec/support/pundit/pundit_view_policy.rb @@ -2,13 +2,18 @@ module Pundit    module PunditViewPolicy      def self.included into        into.let(:permissions){ nil } -      into.let(:organisation){ referential.try(:organisation) } -      into.let(:current_referential){ referential || build_stubbed(:referential) } -      into.let(:current_user){ build_stubbed :user, permissions: permissions, organisation: organisation } +      into.let(:current_referential){ referential || build_stubbed(:referential, organisation: organisation) } +      into.let(:current_user){ create :user, permissions: permissions, organisation: organisation }        into.let(:pundit_user){ UserContext.new(current_user, referential: current_referential) } +      into.let(:current_offer_workbench) { create :workbench, organisation: organisation}        into.before do          allow(view).to receive(:pundit_user) { pundit_user } - +        allow(view).to receive(:current_user) { current_user } +        allow(view).to receive(:current_organisation).and_return(organisation) +        allow(view).to receive(:current_offer_workbench).and_return(current_offer_workbench) +        allow(view).to receive(:current_workgroup).and_return(current_offer_workbench.workgroup) +        allow(view).to receive(:has_feature?){ |f| features.include?(f)} +        allow(view).to receive(:user_signed_in?).and_return true          allow(view).to receive(:policy) do |instance|            ::Pundit.policy pundit_user, instance          end diff --git a/spec/support/referential.rb b/spec/support/referential.rb index 497ff47a8..9acdce73a 100644 --- a/spec/support/referential.rb +++ b/spec/support/referential.rb @@ -11,8 +11,8 @@ module ReferentialHelper    def self.included(base)      base.class_eval do        extend ClassMethods -      alias_method :referential, :first_referential -      alias_method :organisation, :first_organisation +      base.let(:referential){ first_referential } +      base.let(:organisation){ first_organisation }      end    end diff --git a/spec/support/snapshot_support.rb b/spec/support/snapshot_support.rb new file mode 100644 index 000000000..b1ade5288 --- /dev/null +++ b/spec/support/snapshot_support.rb @@ -0,0 +1,60 @@ +module SnaphostSpecHelper + +  module Methods +    def set_invariant expr, val=nil +      val ||= expr +      chain = expr.split(".") +      method = chain.pop + +      before(:each) do +        allow(eval(chain.join('.'))).to receive(method){ val } +      end +    end +  end + +  def self.included into +    into.extend Methods +  end +end + +RSpec.configure do |config| +  config.include SnaphostSpecHelper, type: :view +end + + +RSpec::Matchers.define :match_actions_links_snapshot do |name| +  match do |actual| +    @content = Capybara::Node::Simple.new(rendered).find('.page_header').native.inner_html +    expect(@content).to match_snapshot(name) +  end + +  failure_message do |actual| +    out = ["Snapshots did not match."] +    snap_path = File.dirname(method_missing(:class).metadata[:file_path]) + "/__snapshots__/#{name}.snap" +    temp_path = Pathname.new "#{Rails.root}/tmp/__snapshots__/#{name}.failed.snap" +    FileUtils.mkdir_p temp_path.dirname +    tmp = File.new temp_path, "w" +    tmp.write @content +    tmp.close() +    expected = File.read snap_path +    out << "Expected: #{expected}" +    out << "Actual: #{@content}" +    out << "\n\n --- DIFF ---" +    out << differ.diff_as_string(@content, expected) +    out << "\n\n --- Previews : ---" +    out << "Expected: \n" + snapshot_url(snap: snap_path, layout: :actions_links) +    out << " \nActual:  \n" + snapshot_url(snap: tmp.path, layout: :actions_links) +    out.join("\n") +  end + +  def snapshot_url snap:, layout: +    "http://localhost:3000/snap/?snap=#{URI.encode(snap.to_s)}&layout=#{URI.encode(layout.to_s)}" +  end + +  def differ +    RSpec::Support::Differ.new( +        :object_preparer => lambda { |object| RSpec::Matchers::Composable.surface_descriptions_in(object) }, +        :color => RSpec::Matchers.configuration.color? +    ) +  end +end diff --git a/spec/views/companies/__snapshots__/companies/index.snap b/spec/views/companies/__snapshots__/companies/index.snap new file mode 100644 index 000000000..2c5c23400 --- /dev/null +++ b/spec/views/companies/__snapshots__/companies/index.snap @@ -0,0 +1,4 @@ +<div class="container-fluid"><div class="row"> +<div class="col-lg-9 col-md-8 col-sm-7 col-xs-7"><div class="page-title"></div></div> +<div class="col-lg-3 col-md-4 col-sm-5 col-xs-5 text-right"><div class="page-action"></div></div> +</div></div>
\ No newline at end of file diff --git a/spec/views/companies/__snapshots__/companies/index_create.snap b/spec/views/companies/__snapshots__/companies/index_create.snap new file mode 100644 index 000000000..df36d5f49 --- /dev/null +++ b/spec/views/companies/__snapshots__/companies/index_create.snap @@ -0,0 +1,4 @@ +<div class="container-fluid"><div class="row"> +<div class="col-lg-9 col-md-8 col-sm-7 col-xs-7"><div class="page-title"></div></div> +<div class="col-lg-3 col-md-4 col-sm-5 col-xs-5 text-right"><div class="page-action"><a class="btn btn-default" href="/line_referentials/99/companies/new">Ajouter un transporteur</a></div></div> +</div></div>
\ No newline at end of file diff --git a/spec/views/companies/__snapshots__/companies/index_destroy.snap b/spec/views/companies/__snapshots__/companies/index_destroy.snap new file mode 100644 index 000000000..2c5c23400 --- /dev/null +++ b/spec/views/companies/__snapshots__/companies/index_destroy.snap @@ -0,0 +1,4 @@ +<div class="container-fluid"><div class="row"> +<div class="col-lg-9 col-md-8 col-sm-7 col-xs-7"><div class="page-title"></div></div> +<div class="col-lg-3 col-md-4 col-sm-5 col-xs-5 text-right"><div class="page-action"></div></div> +</div></div>
\ No newline at end of file diff --git a/spec/views/companies/__snapshots__/companies/index_update.snap b/spec/views/companies/__snapshots__/companies/index_update.snap new file mode 100644 index 000000000..2c5c23400 --- /dev/null +++ b/spec/views/companies/__snapshots__/companies/index_update.snap @@ -0,0 +1,4 @@ +<div class="container-fluid"><div class="row"> +<div class="col-lg-9 col-md-8 col-sm-7 col-xs-7"><div class="page-title"></div></div> +<div class="col-lg-3 col-md-4 col-sm-5 col-xs-5 text-right"><div class="page-action"></div></div> +</div></div>
\ No newline at end of file diff --git a/spec/views/companies/__snapshots__/companies/show.snap b/spec/views/companies/__snapshots__/companies/show.snap new file mode 100644 index 000000000..8fe847427 --- /dev/null +++ b/spec/views/companies/__snapshots__/companies/show.snap @@ -0,0 +1,4 @@ +<div class="container-fluid"><div class="row"> +<div class="col-lg-9 col-md-8 col-sm-7 col-xs-7"><div class="page-title"><h1>Transporteur Company Name</h1></div></div> +<div class="col-lg-3 col-md-4 col-sm-5 col-xs-5 text-right"><div class="page-action"><div class="small last-update">Dernière mise à jour le 23/01/2018 <br> Par web service</div></div></div> +</div></div>
\ No newline at end of file diff --git a/spec/views/companies/__snapshots__/companies/show_create.snap b/spec/views/companies/__snapshots__/companies/show_create.snap new file mode 100644 index 000000000..8fe847427 --- /dev/null +++ b/spec/views/companies/__snapshots__/companies/show_create.snap @@ -0,0 +1,4 @@ +<div class="container-fluid"><div class="row"> +<div class="col-lg-9 col-md-8 col-sm-7 col-xs-7"><div class="page-title"><h1>Transporteur Company Name</h1></div></div> +<div class="col-lg-3 col-md-4 col-sm-5 col-xs-5 text-right"><div class="page-action"><div class="small last-update">Dernière mise à jour le 23/01/2018 <br> Par web service</div></div></div> +</div></div>
\ No newline at end of file diff --git a/spec/views/companies/__snapshots__/companies/show_destroy.snap b/spec/views/companies/__snapshots__/companies/show_destroy.snap new file mode 100644 index 000000000..5d574e460 --- /dev/null +++ b/spec/views/companies/__snapshots__/companies/show_destroy.snap @@ -0,0 +1,7 @@ +<div class="container-fluid"> +<div class="row"> +<div class="col-lg-9 col-md-8 col-sm-7 col-xs-7"><div class="page-title"><h1>Transporteur Company Name</h1></div></div> +<div class="col-lg-3 col-md-4 col-sm-5 col-xs-5 text-right"><div class="page-action"><div class="small last-update">Dernière mise à jour le 23/01/2018 <br> Par web service</div></div></div> +</div> +<div class="row mb-sm"><div class="col-lg-12 text-right"><a data-confirm="Etes vous sûr de supprimer ce transporteur ?" class="btn btn-primary" rel="nofollow" data-method="delete" href="/line_referentials/99/companies/909"><span class="fa fa-trash mr-xs"></span>Supprimer ce transporteur</a></div></div> +</div>
\ No newline at end of file diff --git a/spec/views/companies/__snapshots__/companies/show_update.snap b/spec/views/companies/__snapshots__/companies/show_update.snap new file mode 100644 index 000000000..c2fbd3297 --- /dev/null +++ b/spec/views/companies/__snapshots__/companies/show_update.snap @@ -0,0 +1,7 @@ +<div class="container-fluid"><div class="row"> +<div class="col-lg-9 col-md-8 col-sm-7 col-xs-7"><div class="page-title"><h1>Transporteur Company Name</h1></div></div> +<div class="col-lg-3 col-md-4 col-sm-5 col-xs-5 text-right"><div class="page-action"> +<div class="small last-update">Dernière mise à jour le 23/01/2018 <br> Par web service</div> +<a class="btn btn-default" href="/line_referentials/99/companies/909/edit">Editer ce transporteur</a> +</div></div> +</div></div>
\ No newline at end of file diff --git a/spec/views/companies/index.html.erb_spec.rb b/spec/views/companies/index.html.erb_spec.rb index 9db689ba6..8ed5f2c21 100644 --- a/spec/views/companies/index.html.erb_spec.rb +++ b/spec/views/companies/index.html.erb_spec.rb @@ -3,7 +3,10 @@ require 'spec_helper'  RSpec.describe "/companies/index", :type => :view do    let!(:line_referential) { assign :line_referential, create(:line_referential) } -  let!(:companies) { assign :companies, CompanyDecorator.decorate_collection(Array.new(2) { create(:company, line_referential: line_referential) }.paginate) } +  let(:context){{referential: line_referential}} +  let!(:companies) do +    assign :companies, build_paginated_collection(:company, CompanyDecorator, line_referential: line_referential, context: context) +  end    let!(:search) { assign :q, Ransack::Search.new(Chouette::Company) }    # Fixme #1795 @@ -22,4 +25,28 @@ RSpec.describe "/companies/index", :type => :view do    #   expect(view.content_for(:sidebar)).to have_selector(".actions a[href='#{new_line_referential_company_path(line_referential)}']")    # end +  before(:each) do +    allow(view).to receive(:collection).and_return(companies) +    allow(view).to receive(:decorated_collection).and_return(companies) +    allow(view).to receive(:current_referential).and_return(line_referential) +    controller.request.path_parameters[:line_referential_id] = line_referential.id +    allow(view).to receive(:params).and_return({action: :index}) +  end + +  describe "action links" do +    set_invariant "line_referential.id", "99" + +    before(:each){ +      render template: "companies/index", layout: "layouts/application" +    } + +    it { should match_actions_links_snapshot "companies/index" } + +    %w(create update destroy).each do |p| +      with_permission "companies.#{p}" do +        it { should match_actions_links_snapshot "companies/index_#{p}" } +      end +    end +  end +  end diff --git a/spec/views/companies/show.html.erb_spec.rb b/spec/views/companies/show.html.erb_spec.rb index aeb93aebb..b127bdf44 100644 --- a/spec/views/companies/show.html.erb_spec.rb +++ b/spec/views/companies/show.html.erb_spec.rb @@ -2,11 +2,38 @@ require 'spec_helper'  describe "/companies/show", :type => :view do -  let!(:company) { assign(:company, create(:company)) } +  let!(:company) { c = create(:company); assign(:company, c.decorate(context: {referential: c.line_referential})) }    let!(:line_referential) { assign :line_referential, company.line_referential }    # it "should display a map with class 'company'" do    #   render    #  expect(rendered).to have_selector("#map", :class => 'company')    # end + +  before(:each) do +    allow(view).to receive(:current_referential).and_return(line_referential) +    allow(view).to receive(:resource).and_return(company) +    controller.request.path_parameters[:line_referential_id] = line_referential.id +    controller.request.path_parameters[:id] = company.id +    allow(view).to receive(:params).and_return({action: :show}) +  end + +  describe "action links" do +    set_invariant "line_referential.id", "99" +    set_invariant "company.object.id", "909" +    set_invariant "company.object.name", "Company Name" +    set_invariant "company.object.updated_at", "2018/01/23".to_time + +    before(:each){ +      render template: "companies/show", layout: "layouts/application" +    } + +    it { should match_actions_links_snapshot "companies/show" } + +    %w(create update destroy).each do |p| +      with_permission "companies.#{p}" do +        it { should match_actions_links_snapshot "companies/show_#{p}" } +      end +    end +  end  end diff --git a/spec/views/lines/__snapshots__/lines/index.snap b/spec/views/lines/__snapshots__/lines/index.snap new file mode 100644 index 000000000..2c5c23400 --- /dev/null +++ b/spec/views/lines/__snapshots__/lines/index.snap @@ -0,0 +1,4 @@ +<div class="container-fluid"><div class="row"> +<div class="col-lg-9 col-md-8 col-sm-7 col-xs-7"><div class="page-title"></div></div> +<div class="col-lg-3 col-md-4 col-sm-5 col-xs-5 text-right"><div class="page-action"></div></div> +</div></div>
\ No newline at end of file diff --git a/spec/views/lines/__snapshots__/lines/index_create.snap b/spec/views/lines/__snapshots__/lines/index_create.snap new file mode 100644 index 000000000..4e4f54e7f --- /dev/null +++ b/spec/views/lines/__snapshots__/lines/index_create.snap @@ -0,0 +1,4 @@ +<div class="container-fluid"><div class="row"> +<div class="col-lg-9 col-md-8 col-sm-7 col-xs-7"><div class="page-title"></div></div> +<div class="col-lg-3 col-md-4 col-sm-5 col-xs-5 text-right"><div class="page-action"><a class="btn btn-default" href="/line_referentials/99/lines/new">Ajouter une ligne</a></div></div> +</div></div>
\ No newline at end of file diff --git a/spec/views/lines/__snapshots__/lines/index_destroy.snap b/spec/views/lines/__snapshots__/lines/index_destroy.snap new file mode 100644 index 000000000..2c5c23400 --- /dev/null +++ b/spec/views/lines/__snapshots__/lines/index_destroy.snap @@ -0,0 +1,4 @@ +<div class="container-fluid"><div class="row"> +<div class="col-lg-9 col-md-8 col-sm-7 col-xs-7"><div class="page-title"></div></div> +<div class="col-lg-3 col-md-4 col-sm-5 col-xs-5 text-right"><div class="page-action"></div></div> +</div></div>
\ No newline at end of file diff --git a/spec/views/lines/__snapshots__/lines/index_update.snap b/spec/views/lines/__snapshots__/lines/index_update.snap new file mode 100644 index 000000000..2c5c23400 --- /dev/null +++ b/spec/views/lines/__snapshots__/lines/index_update.snap @@ -0,0 +1,4 @@ +<div class="container-fluid"><div class="row"> +<div class="col-lg-9 col-md-8 col-sm-7 col-xs-7"><div class="page-title"></div></div> +<div class="col-lg-3 col-md-4 col-sm-5 col-xs-5 text-right"><div class="page-action"></div></div> +</div></div>
\ No newline at end of file diff --git a/spec/views/lines/__snapshots__/lines/show.snap b/spec/views/lines/__snapshots__/lines/show.snap new file mode 100644 index 000000000..30eb6786e --- /dev/null +++ b/spec/views/lines/__snapshots__/lines/show.snap @@ -0,0 +1,9 @@ +<div class="container-fluid"> +<div class="row"> +<div class="col-lg-9 col-md-8 col-sm-7 col-xs-7"><div class="page-title"><h1>Ligne Name</h1></div></div> +<div class="col-lg-3 col-md-4 col-sm-5 col-xs-5 text-right"><div class="page-action"><div class="small last-update">Dernière mise à jour le 23/01/2018 <br> Par web service</div></div></div> +</div> +<div class="row mb-sm"><div class="col-lg-12 text-right"> +<a class="btn btn-primary" href="/line_referentials/99/networks/99">Voir le réseau</a><a class="btn btn-primary" href="/line_referentials/99/companies/99">Voir le transporteur principal</a> +</div></div> +</div>
\ No newline at end of file diff --git a/spec/views/lines/__snapshots__/lines/show_create.snap b/spec/views/lines/__snapshots__/lines/show_create.snap new file mode 100644 index 000000000..30eb6786e --- /dev/null +++ b/spec/views/lines/__snapshots__/lines/show_create.snap @@ -0,0 +1,9 @@ +<div class="container-fluid"> +<div class="row"> +<div class="col-lg-9 col-md-8 col-sm-7 col-xs-7"><div class="page-title"><h1>Ligne Name</h1></div></div> +<div class="col-lg-3 col-md-4 col-sm-5 col-xs-5 text-right"><div class="page-action"><div class="small last-update">Dernière mise à jour le 23/01/2018 <br> Par web service</div></div></div> +</div> +<div class="row mb-sm"><div class="col-lg-12 text-right"> +<a class="btn btn-primary" href="/line_referentials/99/networks/99">Voir le réseau</a><a class="btn btn-primary" href="/line_referentials/99/companies/99">Voir le transporteur principal</a> +</div></div> +</div>
\ No newline at end of file diff --git a/spec/views/lines/__snapshots__/lines/show_destroy.snap b/spec/views/lines/__snapshots__/lines/show_destroy.snap new file mode 100644 index 000000000..8ed08a90d --- /dev/null +++ b/spec/views/lines/__snapshots__/lines/show_destroy.snap @@ -0,0 +1,9 @@ +<div class="container-fluid"> +<div class="row"> +<div class="col-lg-9 col-md-8 col-sm-7 col-xs-7"><div class="page-title"><h1>Ligne Name</h1></div></div> +<div class="col-lg-3 col-md-4 col-sm-5 col-xs-5 text-right"><div class="page-action"><div class="small last-update">Dernière mise à jour le 23/01/2018 <br> Par web service</div></div></div> +</div> +<div class="row mb-sm"><div class="col-lg-12 text-right"> +<a class="btn btn-primary" href="/line_referentials/99/networks/99">Voir le réseau</a><a class="btn btn-primary" href="/line_referentials/99/companies/99">Voir le transporteur principal</a><a data-confirm="Etes vous sûr de supprimer cette ligne ?" class="btn btn-primary" rel="nofollow" data-method="delete" href="/line_referentials/99/lines/99"><span class="fa fa-trash mr-xs"></span>Supprimer cette ligne</a> +</div></div> +</div>
\ No newline at end of file diff --git a/spec/views/lines/__snapshots__/lines/show_update.snap b/spec/views/lines/__snapshots__/lines/show_update.snap new file mode 100644 index 000000000..30eb6786e --- /dev/null +++ b/spec/views/lines/__snapshots__/lines/show_update.snap @@ -0,0 +1,9 @@ +<div class="container-fluid"> +<div class="row"> +<div class="col-lg-9 col-md-8 col-sm-7 col-xs-7"><div class="page-title"><h1>Ligne Name</h1></div></div> +<div class="col-lg-3 col-md-4 col-sm-5 col-xs-5 text-right"><div class="page-action"><div class="small last-update">Dernière mise à jour le 23/01/2018 <br> Par web service</div></div></div> +</div> +<div class="row mb-sm"><div class="col-lg-12 text-right"> +<a class="btn btn-primary" href="/line_referentials/99/networks/99">Voir le réseau</a><a class="btn btn-primary" href="/line_referentials/99/companies/99">Voir le transporteur principal</a> +</div></div> +</div>
\ No newline at end of file diff --git a/spec/views/lines/index.html.slim_spec.rb b/spec/views/lines/index.html.slim_spec.rb index fb436c545..20e1783e3 100644 --- a/spec/views/lines/index.html.slim_spec.rb +++ b/spec/views/lines/index.html.slim_spec.rb @@ -13,11 +13,12 @@ describe "/lines/index", :type => :view do    let(:lines) do      assign :lines, build_paginated_collection(:line, LineDecorator, line_referential: line_referential, context: context)    end -  let!(:q) {  assign :q, Ransack::Search.new(Chouette::Line) } +  let!(:q) { assign :q, Ransack::Search.new(Chouette::Line) }    before :each do      deactivated_line      allow(view).to receive(:collection).and_return(lines) +    allow(view).to receive(:decorated_collection).and_return(lines)      allow(view).to receive(:current_referential).and_return(line_referential)      allow(view).to receive(:params).and_return({action: :index})      controller.request.path_parameters[:line_referential_id] = line_referential.id @@ -25,49 +26,68 @@ describe "/lines/index", :type => :view do      render    end -  common_items = ->{ -    it { should have_link_for_each_item(lines, "show", -> (line){ view.line_referential_line_path(line_referential, line) }) } -    it { should have_link_for_each_item(lines, "network", -> (line){ view.line_referential_network_path(line_referential, line.network) }) } -    it { should have_link_for_each_item(lines, "company", -> (line){ view.line_referential_company_path(line_referential, line.company) }) } -  } +  describe "action links" do +    set_invariant "line_referential.id", "99" +    set_invariant "line_referential.name", "Name" -  common_items.call() -  it { should have_the_right_number_of_links(lines, 3) } +    before(:each){ +      render template: "lines/index", layout: "layouts/application" +    } -  with_permission "lines.change_status" do -    common_items.call() -    it { should have_link_for_each_item(lines, "deactivate", -> (line){ view.deactivate_line_referential_line_path(line_referential, line) }) } -    it { should have_the_right_number_of_links(lines, 4) } +    it { should match_actions_links_snapshot "lines/index" } + +    %w(create update destroy).each do |p| +      with_permission "lines.#{p}" do +        it { should match_actions_links_snapshot "lines/index_#{p}" } +      end +    end    end -  with_permission "lines.destroy" do -    common_items.call() -    it { -      should have_link_for_each_item(lines, "destroy", { -        href: ->(line){ view.line_referential_line_path(line_referential, line)}, -        method: :delete -      }) +  context "links" do +    common_items = ->{ +      it { should have_link_for_each_item(lines, "show", -> (line){ view.line_referential_line_path(line_referential, line) }) } +      it { should have_link_for_each_item(lines, "network", -> (line){ view.line_referential_network_path(line_referential, line.network) }) } +      it { should have_link_for_each_item(lines, "company", -> (line){ view.line_referential_company_path(line_referential, line.company) }) }      } -    it { should have_the_right_number_of_links(lines, 4) } -  end -  context "with a deactivated item" do +    common_items.call() +    it { should have_the_right_number_of_links(lines, 3) } +      with_permission "lines.change_status" do -      let(:deactivated_line){ create :line, deactivated: true } +      common_items.call() +      it { should have_link_for_each_item(lines, "deactivate", -> (line){ view.deactivate_line_referential_line_path(line_referential, line) }) } +      it { should have_the_right_number_of_links(lines, 4) } +    end +    with_permission "lines.destroy" do        common_items.call() -      it "should display an activate link for the deactivated one" do -        lines.each do |line| -          if line == deactivated_line -            href = view.activate_line_referential_line_path(line_referential, line) -          else -            href = view.deactivate_line_referential_line_path(line_referential, line) +      it { +        should have_link_for_each_item(lines, "destroy", { +          href: ->(line){ view.line_referential_line_path(line_referential, line)}, +          method: :delete +        }) +      } +      it { should have_the_right_number_of_links(lines, 4) } +    end + +    context "with a deactivated item" do +      with_permission "lines.change_status" do +        let(:deactivated_line){ create :line, deactivated: true } + +        common_items.call() +        it "should display an activate link for the deactivated one" do +          lines.each do |line| +            if line == deactivated_line +              href = view.activate_line_referential_line_path(line_referential, line) +            else +              href = view.deactivate_line_referential_line_path(line_referential, line) +            end +            selector = "tr.#{TableBuilderHelper.item_row_class_name(lines)}-#{line.id} .actions a[href='#{href}']" +            expect(rendered).to have_selector(selector, count: 1)            end -          selector = "tr.#{TableBuilderHelper.item_row_class_name(lines)}-#{line.id} .actions a[href='#{href}']" -          expect(rendered).to have_selector(selector, count: 1)          end +        it { should have_the_right_number_of_links(lines, 4) }        end -      it { should have_the_right_number_of_links(lines, 4) }      end    end  end diff --git a/spec/views/lines/show.html.erb_spec.rb b/spec/views/lines/show.html.erb_spec.rb index 9649d9c8e..effdcd014 100644 --- a/spec/views/lines/show.html.erb_spec.rb +++ b/spec/views/lines/show.html.erb_spec.rb @@ -15,6 +15,30 @@ describe "/lines/show", :type => :view do    let!(:map) { assign(:map, double(:to_html => '<div id="map"/>'.html_safe)) }    before do -    allow(view).to receive_messages(current_organisation: referential.organisation) +    allow(view).to receive_messages(current_organisation: referential.organisation, resource: line) +    controller.request.path_parameters[:line_referential_id] = line_referential.id +    controller.request.path_parameters[:id] = line.id +    allow(view).to receive(:params).and_return({action: :show}) +  end + +  describe "action links" do +    set_invariant "line_referential.id", "99" +    set_invariant "line.object.id", "99" +    set_invariant "line.object.name", "Name" +    set_invariant "line.company.id", "99" +    set_invariant "line.network.id", "99" +    set_invariant "line.updated_at", "2018/01/23".to_time + +    before(:each){ +      render template: "lines/show", layout: "layouts/application" +    } + +    it { should match_actions_links_snapshot "lines/show" } + +    %w(create update destroy).each do |p| +      with_permission "lines.#{p}" do +        it { should match_actions_links_snapshot "lines/show_#{p}" } +      end +    end    end  end diff --git a/spec/views/networks/__snapshots__/networks/index.snap b/spec/views/networks/__snapshots__/networks/index.snap new file mode 100644 index 000000000..2c5c23400 --- /dev/null +++ b/spec/views/networks/__snapshots__/networks/index.snap @@ -0,0 +1,4 @@ +<div class="container-fluid"><div class="row"> +<div class="col-lg-9 col-md-8 col-sm-7 col-xs-7"><div class="page-title"></div></div> +<div class="col-lg-3 col-md-4 col-sm-5 col-xs-5 text-right"><div class="page-action"></div></div> +</div></div>
\ No newline at end of file diff --git a/spec/views/networks/__snapshots__/networks/index_create.snap b/spec/views/networks/__snapshots__/networks/index_create.snap new file mode 100644 index 000000000..afd4aa41b --- /dev/null +++ b/spec/views/networks/__snapshots__/networks/index_create.snap @@ -0,0 +1,4 @@ +<div class="container-fluid"><div class="row"> +<div class="col-lg-9 col-md-8 col-sm-7 col-xs-7"><div class="page-title"></div></div> +<div class="col-lg-3 col-md-4 col-sm-5 col-xs-5 text-right"><div class="page-action"><a class="btn btn-default" href="/line_referentials/99/networks/new">Ajouter un réseau</a></div></div> +</div></div>
\ No newline at end of file diff --git a/spec/views/networks/__snapshots__/networks/index_destroy.snap b/spec/views/networks/__snapshots__/networks/index_destroy.snap new file mode 100644 index 000000000..2c5c23400 --- /dev/null +++ b/spec/views/networks/__snapshots__/networks/index_destroy.snap @@ -0,0 +1,4 @@ +<div class="container-fluid"><div class="row"> +<div class="col-lg-9 col-md-8 col-sm-7 col-xs-7"><div class="page-title"></div></div> +<div class="col-lg-3 col-md-4 col-sm-5 col-xs-5 text-right"><div class="page-action"></div></div> +</div></div>
\ No newline at end of file diff --git a/spec/views/networks/__snapshots__/networks/index_update.snap b/spec/views/networks/__snapshots__/networks/index_update.snap new file mode 100644 index 000000000..2c5c23400 --- /dev/null +++ b/spec/views/networks/__snapshots__/networks/index_update.snap @@ -0,0 +1,4 @@ +<div class="container-fluid"><div class="row"> +<div class="col-lg-9 col-md-8 col-sm-7 col-xs-7"><div class="page-title"></div></div> +<div class="col-lg-3 col-md-4 col-sm-5 col-xs-5 text-right"><div class="page-action"></div></div> +</div></div>
\ No newline at end of file diff --git a/spec/views/networks/__snapshots__/networks/show.snap b/spec/views/networks/__snapshots__/networks/show.snap new file mode 100644 index 000000000..8f2992065 --- /dev/null +++ b/spec/views/networks/__snapshots__/networks/show.snap @@ -0,0 +1,4 @@ +<div class="container-fluid"><div class="row"> +<div class="col-lg-9 col-md-8 col-sm-7 col-xs-7"><div class="page-title"><h1>Réseau Name</h1></div></div> +<div class="col-lg-3 col-md-4 col-sm-5 col-xs-5 text-right"><div class="page-action"><div class="small last-update">Dernière mise à jour le 23/01/2018 <br> Par web service</div></div></div> +</div></div>
\ No newline at end of file diff --git a/spec/views/networks/__snapshots__/networks/show_create.snap b/spec/views/networks/__snapshots__/networks/show_create.snap new file mode 100644 index 000000000..8f2992065 --- /dev/null +++ b/spec/views/networks/__snapshots__/networks/show_create.snap @@ -0,0 +1,4 @@ +<div class="container-fluid"><div class="row"> +<div class="col-lg-9 col-md-8 col-sm-7 col-xs-7"><div class="page-title"><h1>Réseau Name</h1></div></div> +<div class="col-lg-3 col-md-4 col-sm-5 col-xs-5 text-right"><div class="page-action"><div class="small last-update">Dernière mise à jour le 23/01/2018 <br> Par web service</div></div></div> +</div></div>
\ No newline at end of file diff --git a/spec/views/networks/__snapshots__/networks/show_destroy.snap b/spec/views/networks/__snapshots__/networks/show_destroy.snap new file mode 100644 index 000000000..c525c05b7 --- /dev/null +++ b/spec/views/networks/__snapshots__/networks/show_destroy.snap @@ -0,0 +1,7 @@ +<div class="container-fluid"> +<div class="row"> +<div class="col-lg-9 col-md-8 col-sm-7 col-xs-7"><div class="page-title"><h1>Réseau Name</h1></div></div> +<div class="col-lg-3 col-md-4 col-sm-5 col-xs-5 text-right"><div class="page-action"><div class="small last-update">Dernière mise à jour le 23/01/2018 <br> Par web service</div></div></div> +</div> +<div class="row mb-sm"><div class="col-lg-12 text-right"><a data-confirm="Etes vous sûr de supprimer ce réseau ?" class="btn btn-primary" rel="nofollow" data-method="delete" href="/line_referentials/99/networks/909"><span class="fa fa-trash mr-xs"></span>Supprimer ce réseau</a></div></div> +</div>
\ No newline at end of file diff --git a/spec/views/networks/__snapshots__/networks/show_update.snap b/spec/views/networks/__snapshots__/networks/show_update.snap new file mode 100644 index 000000000..35f8ee9ac --- /dev/null +++ b/spec/views/networks/__snapshots__/networks/show_update.snap @@ -0,0 +1,7 @@ +<div class="container-fluid"> +<div class="row"> +<div class="col-lg-9 col-md-8 col-sm-7 col-xs-7"><div class="page-title"><h1>Réseau Name</h1></div></div> +<div class="col-lg-3 col-md-4 col-sm-5 col-xs-5 text-right"><div class="page-action"><div class="small last-update">Dernière mise à jour le 23/01/2018 <br> Par web service</div></div></div> +</div> +<div class="row mb-sm"><div class="col-lg-12 text-right"><a class="btn btn-primary" href="/line_referentials/99/networks/909/edit">Editer ce réseau</a></div></div> +</div>
\ No newline at end of file diff --git a/spec/views/networks/index.html.erb_spec.rb b/spec/views/networks/index.html.erb_spec.rb index d2dde7f87..80e755163 100644 --- a/spec/views/networks/index.html.erb_spec.rb +++ b/spec/views/networks/index.html.erb_spec.rb @@ -3,7 +3,11 @@ require 'spec_helper'  describe "/networks/index", :type => :view do    let!(:line_referential) { assign :line_referential, create(:line_referential) } -  let!(:networks) { assign :networks, Array.new(2){ create(:network, line_referential: line_referential) }.paginate } +  let(:context){{line_referential: line_referential}} +  let!(:networks) do +    assign :networks, build_paginated_collection(:network, NetworkDecorator, line_referential: line_referential, context: context) +  end +    let!(:search) { assign :q, Ransack::Search.new(Chouette::Network) }    # it "should render a show link for each group" do @@ -18,5 +22,27 @@ describe "/networks/index", :type => :view do    #   render    #   expect(view.content_for(:sidebar)).to have_selector("a[href='#{new_line_referential_network_path(line_referential)}']")    # end +  before(:each) do +    allow(view).to receive(:collection).and_return(networks) +    allow(view).to receive(:decorated_collection).and_return(networks) +    allow(view).to receive(:current_referential).and_return(line_referential) +    controller.request.path_parameters[:line_referential_id] = line_referential.id +    allow(view).to receive(:params).and_return({action: :index}) +  end + +  describe "action links" do +    set_invariant "line_referential.id", "99" + +    before(:each){ +      render template: "networks/index", layout: "layouts/application" +    } + +    it { should match_actions_links_snapshot "networks/index" } +    %w(create update destroy).each do |p| +      with_permission "networks.#{p}" do +        it { should match_actions_links_snapshot "networks/index_#{p}" } +      end +    end +  end  end diff --git a/spec/views/networks/show.html.erb_spec.rb b/spec/views/networks/show.html.erb_spec.rb index 3926ead14..998e8ac44 100644 --- a/spec/views/networks/show.html.erb_spec.rb +++ b/spec/views/networks/show.html.erb_spec.rb @@ -11,8 +11,30 @@ describe "/networks/show", :type => :view do    let!(:map) { assign(:map, double(:to_html => '<div id="map"/>'.html_safe)) }    let!(:line_referential) { assign :line_referential, network.line_referential } -  # it "should display a map with class 'network'" do -  #   render -  #   expect(rendered).to have_selector("#map") -  # end +  before(:each) do +    allow(view).to receive(:current_referential).and_return(line_referential) +    allow(view).to receive(:resource).and_return(network) +    controller.request.path_parameters[:line_referential_id] = line_referential.id +    controller.request.path_parameters[:id] = network.id +    allow(view).to receive(:params).and_return({action: :show}) +  end + +  describe "action links" do +    set_invariant "line_referential.id", "99" +    set_invariant "network.object.id", "909" +    set_invariant "network.object.updated_at", "2018/01/23".to_time +    set_invariant "network.object.name", "Name" + +    before(:each){ +      render template: "networks/show", layout: "layouts/application" +    } + +    it { should match_actions_links_snapshot "networks/show" } + +    %w(create update destroy).each do |p| +      with_permission "networks.#{p}" do +        it { should match_actions_links_snapshot "networks/show_#{p}" } +      end +    end +  end  end diff --git a/spec/views/referentials/__snapshots__/referentials/show.snap b/spec/views/referentials/__snapshots__/referentials/show.snap new file mode 100644 index 000000000..83531ac0e --- /dev/null +++ b/spec/views/referentials/__snapshots__/referentials/show.snap @@ -0,0 +1,9 @@ +<div class="container-fluid"> +<div class="row"> +<div class="col-lg-9 col-md-8 col-sm-7 col-xs-7"><div class="page-title"><h1>referential_full_name</h1></div></div> +<div class="col-lg-3 col-md-4 col-sm-5 col-xs-5 text-right"><div class="page-action"><div class="small last-update">Dernière mise à jour le 01/01/2000</div></div></div> +</div> +<div class="row mb-sm"><div class="col-lg-12 text-right"> +<a class="btn btn-primary" href="/referentials/99/vehicle_journeys">Courses</a><a class="btn btn-primary" href="/referentials/99/purchase_windows">Calendriers commerciaux</a><a class="btn btn-primary" href="/referentials/99/time_tables">Calendriers</a> +</div></div> +</div>
\ No newline at end of file diff --git a/spec/views/referentials/__snapshots__/referentials/show_create.snap b/spec/views/referentials/__snapshots__/referentials/show_create.snap new file mode 100644 index 000000000..e5d309b96 --- /dev/null +++ b/spec/views/referentials/__snapshots__/referentials/show_create.snap @@ -0,0 +1,9 @@ +<div class="container-fluid"> +<div class="row"> +<div class="col-lg-9 col-md-8 col-sm-7 col-xs-7"><div class="page-title"><h1>referential_full_name</h1></div></div> +<div class="col-lg-3 col-md-4 col-sm-5 col-xs-5 text-right"><div class="page-action"><div class="small last-update">Dernière mise à jour le 01/01/2000</div></div></div> +</div> +<div class="row mb-sm"><div class="col-lg-12 text-right"> +<a class="btn btn-primary" href="/referentials/99/vehicle_journeys">Courses</a><a class="btn btn-primary" href="/referentials/99/purchase_windows">Calendriers commerciaux</a><a class="btn btn-primary" href="/referentials/99/time_tables">Calendriers</a><a class="btn btn-primary" href="/referentials/new?from=99">Dupliquer</a><a class="btn btn-primary" href="/referentials/99/select_compliance_control_set">Valider</a> +</div></div> +</div>
\ No newline at end of file diff --git a/spec/views/referentials/__snapshots__/referentials/show_destroy.snap b/spec/views/referentials/__snapshots__/referentials/show_destroy.snap new file mode 100644 index 000000000..d90198391 --- /dev/null +++ b/spec/views/referentials/__snapshots__/referentials/show_destroy.snap @@ -0,0 +1,9 @@ +<div class="container-fluid"> +<div class="row"> +<div class="col-lg-9 col-md-8 col-sm-7 col-xs-7"><div class="page-title"><h1>referential_full_name</h1></div></div> +<div class="col-lg-3 col-md-4 col-sm-5 col-xs-5 text-right"><div class="page-action"><div class="small last-update">Dernière mise à jour le 01/01/2000</div></div></div> +</div> +<div class="row mb-sm"><div class="col-lg-12 text-right"> +<a class="btn btn-primary" href="/referentials/99/vehicle_journeys">Courses</a><a class="btn btn-primary" href="/referentials/99/purchase_windows">Calendriers commerciaux</a><a class="btn btn-primary" href="/referentials/99/time_tables">Calendriers</a><a data-confirm="Etes vous sûr de vouloir supprimer ce jeu de données ?" class="btn btn-primary" rel="nofollow" data-method="delete" href="/referentials/99"><span class="fa fa-trash mr-xs"></span>Supprimer</a> +</div></div> +</div>
\ No newline at end of file diff --git a/spec/views/referentials/__snapshots__/referentials/show_purchase_windows.snap b/spec/views/referentials/__snapshots__/referentials/show_purchase_windows.snap new file mode 100644 index 000000000..83531ac0e --- /dev/null +++ b/spec/views/referentials/__snapshots__/referentials/show_purchase_windows.snap @@ -0,0 +1,9 @@ +<div class="container-fluid"> +<div class="row"> +<div class="col-lg-9 col-md-8 col-sm-7 col-xs-7"><div class="page-title"><h1>referential_full_name</h1></div></div> +<div class="col-lg-3 col-md-4 col-sm-5 col-xs-5 text-right"><div class="page-action"><div class="small last-update">Dernière mise à jour le 01/01/2000</div></div></div> +</div> +<div class="row mb-sm"><div class="col-lg-12 text-right"> +<a class="btn btn-primary" href="/referentials/99/vehicle_journeys">Courses</a><a class="btn btn-primary" href="/referentials/99/purchase_windows">Calendriers commerciaux</a><a class="btn btn-primary" href="/referentials/99/time_tables">Calendriers</a> +</div></div> +</div>
\ No newline at end of file diff --git a/spec/views/referentials/__snapshots__/referentials/show_purchase_windows_create.snap b/spec/views/referentials/__snapshots__/referentials/show_purchase_windows_create.snap new file mode 100644 index 000000000..e5d309b96 --- /dev/null +++ b/spec/views/referentials/__snapshots__/referentials/show_purchase_windows_create.snap @@ -0,0 +1,9 @@ +<div class="container-fluid"> +<div class="row"> +<div class="col-lg-9 col-md-8 col-sm-7 col-xs-7"><div class="page-title"><h1>referential_full_name</h1></div></div> +<div class="col-lg-3 col-md-4 col-sm-5 col-xs-5 text-right"><div class="page-action"><div class="small last-update">Dernière mise à jour le 01/01/2000</div></div></div> +</div> +<div class="row mb-sm"><div class="col-lg-12 text-right"> +<a class="btn btn-primary" href="/referentials/99/vehicle_journeys">Courses</a><a class="btn btn-primary" href="/referentials/99/purchase_windows">Calendriers commerciaux</a><a class="btn btn-primary" href="/referentials/99/time_tables">Calendriers</a><a class="btn btn-primary" href="/referentials/new?from=99">Dupliquer</a><a class="btn btn-primary" href="/referentials/99/select_compliance_control_set">Valider</a> +</div></div> +</div>
\ No newline at end of file diff --git a/spec/views/referentials/__snapshots__/referentials/show_purchase_windows_destroy.snap b/spec/views/referentials/__snapshots__/referentials/show_purchase_windows_destroy.snap new file mode 100644 index 000000000..d90198391 --- /dev/null +++ b/spec/views/referentials/__snapshots__/referentials/show_purchase_windows_destroy.snap @@ -0,0 +1,9 @@ +<div class="container-fluid"> +<div class="row"> +<div class="col-lg-9 col-md-8 col-sm-7 col-xs-7"><div class="page-title"><h1>referential_full_name</h1></div></div> +<div class="col-lg-3 col-md-4 col-sm-5 col-xs-5 text-right"><div class="page-action"><div class="small last-update">Dernière mise à jour le 01/01/2000</div></div></div> +</div> +<div class="row mb-sm"><div class="col-lg-12 text-right"> +<a class="btn btn-primary" href="/referentials/99/vehicle_journeys">Courses</a><a class="btn btn-primary" href="/referentials/99/purchase_windows">Calendriers commerciaux</a><a class="btn btn-primary" href="/referentials/99/time_tables">Calendriers</a><a data-confirm="Etes vous sûr de vouloir supprimer ce jeu de données ?" class="btn btn-primary" rel="nofollow" data-method="delete" href="/referentials/99"><span class="fa fa-trash mr-xs"></span>Supprimer</a> +</div></div> +</div>
\ No newline at end of file diff --git a/spec/views/referentials/__snapshots__/referentials/show_purchase_windows_update.snap b/spec/views/referentials/__snapshots__/referentials/show_purchase_windows_update.snap new file mode 100644 index 000000000..32d46beda --- /dev/null +++ b/spec/views/referentials/__snapshots__/referentials/show_purchase_windows_update.snap @@ -0,0 +1,12 @@ +<div class="container-fluid"> +<div class="row"> +<div class="col-lg-9 col-md-8 col-sm-7 col-xs-7"><div class="page-title"><h1>referential_full_name</h1></div></div> +<div class="col-lg-3 col-md-4 col-sm-5 col-xs-5 text-right"><div class="page-action"> +<div class="small last-update">Dernière mise à jour le 01/01/2000</div> +<a class="btn btn-default" href="/referentials/99/edit">Editer</a> +</div></div> +</div> +<div class="row mb-sm"><div class="col-lg-12 text-right"> +<a class="btn btn-primary" href="/referentials/99/vehicle_journeys">Courses</a><a class="btn btn-primary" href="/referentials/99/purchase_windows">Calendriers commerciaux</a><a class="btn btn-primary" href="/referentials/99/time_tables">Calendriers</a><a class="btn btn-primary" rel="nofollow" data-method="put" href="/referentials/99/archive">Conserver</a><button type="button" data-toggle="modal" data-target="#purgeModal" class="btn btn-primary">Purger</button> +</div></div> +</div>
\ No newline at end of file diff --git a/spec/views/referentials/__snapshots__/referentials/show_readonly.snap b/spec/views/referentials/__snapshots__/referentials/show_readonly.snap new file mode 100644 index 000000000..83531ac0e --- /dev/null +++ b/spec/views/referentials/__snapshots__/referentials/show_readonly.snap @@ -0,0 +1,9 @@ +<div class="container-fluid"> +<div class="row"> +<div class="col-lg-9 col-md-8 col-sm-7 col-xs-7"><div class="page-title"><h1>referential_full_name</h1></div></div> +<div class="col-lg-3 col-md-4 col-sm-5 col-xs-5 text-right"><div class="page-action"><div class="small last-update">Dernière mise à jour le 01/01/2000</div></div></div> +</div> +<div class="row mb-sm"><div class="col-lg-12 text-right"> +<a class="btn btn-primary" href="/referentials/99/vehicle_journeys">Courses</a><a class="btn btn-primary" href="/referentials/99/purchase_windows">Calendriers commerciaux</a><a class="btn btn-primary" href="/referentials/99/time_tables">Calendriers</a> +</div></div> +</div>
\ No newline at end of file diff --git a/spec/views/referentials/__snapshots__/referentials/show_readonly_create.snap b/spec/views/referentials/__snapshots__/referentials/show_readonly_create.snap new file mode 100644 index 000000000..82a77521a --- /dev/null +++ b/spec/views/referentials/__snapshots__/referentials/show_readonly_create.snap @@ -0,0 +1,9 @@ +<div class="container-fluid"> +<div class="row"> +<div class="col-lg-9 col-md-8 col-sm-7 col-xs-7"><div class="page-title"><h1>referential_full_name</h1></div></div> +<div class="col-lg-3 col-md-4 col-sm-5 col-xs-5 text-right"><div class="page-action"><div class="small last-update">Dernière mise à jour le 01/01/2000</div></div></div> +</div> +<div class="row mb-sm"><div class="col-lg-12 text-right"> +<a class="btn btn-primary" href="/referentials/99/vehicle_journeys">Courses</a><a class="btn btn-primary" href="/referentials/99/purchase_windows">Calendriers commerciaux</a><a class="btn btn-primary" href="/referentials/99/time_tables">Calendriers</a><a class="btn btn-primary" href="/referentials/new?from=99">Dupliquer</a> +</div></div> +</div>
\ No newline at end of file diff --git a/spec/views/referentials/__snapshots__/referentials/show_readonly_destroy.snap b/spec/views/referentials/__snapshots__/referentials/show_readonly_destroy.snap new file mode 100644 index 000000000..83531ac0e --- /dev/null +++ b/spec/views/referentials/__snapshots__/referentials/show_readonly_destroy.snap @@ -0,0 +1,9 @@ +<div class="container-fluid"> +<div class="row"> +<div class="col-lg-9 col-md-8 col-sm-7 col-xs-7"><div class="page-title"><h1>referential_full_name</h1></div></div> +<div class="col-lg-3 col-md-4 col-sm-5 col-xs-5 text-right"><div class="page-action"><div class="small last-update">Dernière mise à jour le 01/01/2000</div></div></div> +</div> +<div class="row mb-sm"><div class="col-lg-12 text-right"> +<a class="btn btn-primary" href="/referentials/99/vehicle_journeys">Courses</a><a class="btn btn-primary" href="/referentials/99/purchase_windows">Calendriers commerciaux</a><a class="btn btn-primary" href="/referentials/99/time_tables">Calendriers</a> +</div></div> +</div>
\ No newline at end of file diff --git a/spec/views/referentials/__snapshots__/referentials/show_readonly_update.snap b/spec/views/referentials/__snapshots__/referentials/show_readonly_update.snap new file mode 100644 index 000000000..83531ac0e --- /dev/null +++ b/spec/views/referentials/__snapshots__/referentials/show_readonly_update.snap @@ -0,0 +1,9 @@ +<div class="container-fluid"> +<div class="row"> +<div class="col-lg-9 col-md-8 col-sm-7 col-xs-7"><div class="page-title"><h1>referential_full_name</h1></div></div> +<div class="col-lg-3 col-md-4 col-sm-5 col-xs-5 text-right"><div class="page-action"><div class="small last-update">Dernière mise à jour le 01/01/2000</div></div></div> +</div> +<div class="row mb-sm"><div class="col-lg-12 text-right"> +<a class="btn btn-primary" href="/referentials/99/vehicle_journeys">Courses</a><a class="btn btn-primary" href="/referentials/99/purchase_windows">Calendriers commerciaux</a><a class="btn btn-primary" href="/referentials/99/time_tables">Calendriers</a> +</div></div> +</div>
\ No newline at end of file diff --git a/spec/views/referentials/__snapshots__/referentials/show_referential_vehicle_journeys.snap b/spec/views/referentials/__snapshots__/referentials/show_referential_vehicle_journeys.snap new file mode 100644 index 000000000..83531ac0e --- /dev/null +++ b/spec/views/referentials/__snapshots__/referentials/show_referential_vehicle_journeys.snap @@ -0,0 +1,9 @@ +<div class="container-fluid"> +<div class="row"> +<div class="col-lg-9 col-md-8 col-sm-7 col-xs-7"><div class="page-title"><h1>referential_full_name</h1></div></div> +<div class="col-lg-3 col-md-4 col-sm-5 col-xs-5 text-right"><div class="page-action"><div class="small last-update">Dernière mise à jour le 01/01/2000</div></div></div> +</div> +<div class="row mb-sm"><div class="col-lg-12 text-right"> +<a class="btn btn-primary" href="/referentials/99/vehicle_journeys">Courses</a><a class="btn btn-primary" href="/referentials/99/purchase_windows">Calendriers commerciaux</a><a class="btn btn-primary" href="/referentials/99/time_tables">Calendriers</a> +</div></div> +</div>
\ No newline at end of file diff --git a/spec/views/referentials/__snapshots__/referentials/show_referential_vehicle_journeys_create.snap b/spec/views/referentials/__snapshots__/referentials/show_referential_vehicle_journeys_create.snap new file mode 100644 index 000000000..e5d309b96 --- /dev/null +++ b/spec/views/referentials/__snapshots__/referentials/show_referential_vehicle_journeys_create.snap @@ -0,0 +1,9 @@ +<div class="container-fluid"> +<div class="row"> +<div class="col-lg-9 col-md-8 col-sm-7 col-xs-7"><div class="page-title"><h1>referential_full_name</h1></div></div> +<div class="col-lg-3 col-md-4 col-sm-5 col-xs-5 text-right"><div class="page-action"><div class="small last-update">Dernière mise à jour le 01/01/2000</div></div></div> +</div> +<div class="row mb-sm"><div class="col-lg-12 text-right"> +<a class="btn btn-primary" href="/referentials/99/vehicle_journeys">Courses</a><a class="btn btn-primary" href="/referentials/99/purchase_windows">Calendriers commerciaux</a><a class="btn btn-primary" href="/referentials/99/time_tables">Calendriers</a><a class="btn btn-primary" href="/referentials/new?from=99">Dupliquer</a><a class="btn btn-primary" href="/referentials/99/select_compliance_control_set">Valider</a> +</div></div> +</div>
\ No newline at end of file diff --git a/spec/views/referentials/__snapshots__/referentials/show_referential_vehicle_journeys_destroy.snap b/spec/views/referentials/__snapshots__/referentials/show_referential_vehicle_journeys_destroy.snap new file mode 100644 index 000000000..d90198391 --- /dev/null +++ b/spec/views/referentials/__snapshots__/referentials/show_referential_vehicle_journeys_destroy.snap @@ -0,0 +1,9 @@ +<div class="container-fluid"> +<div class="row"> +<div class="col-lg-9 col-md-8 col-sm-7 col-xs-7"><div class="page-title"><h1>referential_full_name</h1></div></div> +<div class="col-lg-3 col-md-4 col-sm-5 col-xs-5 text-right"><div class="page-action"><div class="small last-update">Dernière mise à jour le 01/01/2000</div></div></div> +</div> +<div class="row mb-sm"><div class="col-lg-12 text-right"> +<a class="btn btn-primary" href="/referentials/99/vehicle_journeys">Courses</a><a class="btn btn-primary" href="/referentials/99/purchase_windows">Calendriers commerciaux</a><a class="btn btn-primary" href="/referentials/99/time_tables">Calendriers</a><a data-confirm="Etes vous sûr de vouloir supprimer ce jeu de données ?" class="btn btn-primary" rel="nofollow" data-method="delete" href="/referentials/99"><span class="fa fa-trash mr-xs"></span>Supprimer</a> +</div></div> +</div>
\ No newline at end of file diff --git a/spec/views/referentials/__snapshots__/referentials/show_referential_vehicle_journeys_update.snap b/spec/views/referentials/__snapshots__/referentials/show_referential_vehicle_journeys_update.snap new file mode 100644 index 000000000..32d46beda --- /dev/null +++ b/spec/views/referentials/__snapshots__/referentials/show_referential_vehicle_journeys_update.snap @@ -0,0 +1,12 @@ +<div class="container-fluid"> +<div class="row"> +<div class="col-lg-9 col-md-8 col-sm-7 col-xs-7"><div class="page-title"><h1>referential_full_name</h1></div></div> +<div class="col-lg-3 col-md-4 col-sm-5 col-xs-5 text-right"><div class="page-action"> +<div class="small last-update">Dernière mise à jour le 01/01/2000</div> +<a class="btn btn-default" href="/referentials/99/edit">Editer</a> +</div></div> +</div> +<div class="row mb-sm"><div class="col-lg-12 text-right"> +<a class="btn btn-primary" href="/referentials/99/vehicle_journeys">Courses</a><a class="btn btn-primary" href="/referentials/99/purchase_windows">Calendriers commerciaux</a><a class="btn btn-primary" href="/referentials/99/time_tables">Calendriers</a><a class="btn btn-primary" rel="nofollow" data-method="put" href="/referentials/99/archive">Conserver</a><button type="button" data-toggle="modal" data-target="#purgeModal" class="btn btn-primary">Purger</button> +</div></div> +</div>
\ No newline at end of file diff --git a/spec/views/referentials/__snapshots__/referentials/show_update.snap b/spec/views/referentials/__snapshots__/referentials/show_update.snap new file mode 100644 index 000000000..32d46beda --- /dev/null +++ b/spec/views/referentials/__snapshots__/referentials/show_update.snap @@ -0,0 +1,12 @@ +<div class="container-fluid"> +<div class="row"> +<div class="col-lg-9 col-md-8 col-sm-7 col-xs-7"><div class="page-title"><h1>referential_full_name</h1></div></div> +<div class="col-lg-3 col-md-4 col-sm-5 col-xs-5 text-right"><div class="page-action"> +<div class="small last-update">Dernière mise à jour le 01/01/2000</div> +<a class="btn btn-default" href="/referentials/99/edit">Editer</a> +</div></div> +</div> +<div class="row mb-sm"><div class="col-lg-12 text-right"> +<a class="btn btn-primary" href="/referentials/99/vehicle_journeys">Courses</a><a class="btn btn-primary" href="/referentials/99/purchase_windows">Calendriers commerciaux</a><a class="btn btn-primary" href="/referentials/99/time_tables">Calendriers</a><a class="btn btn-primary" rel="nofollow" data-method="put" href="/referentials/99/archive">Conserver</a><button type="button" data-toggle="modal" data-target="#purgeModal" class="btn btn-primary">Purger</button> +</div></div> +</div>
\ No newline at end of file diff --git a/spec/views/referentials/show.html.erb_spec.rb b/spec/views/referentials/show.html.erb_spec.rb index 21d296441..82328cb8e 100644 --- a/spec/views/referentials/show.html.erb_spec.rb +++ b/spec/views/referentials/show.html.erb_spec.rb @@ -3,26 +3,25 @@ require 'spec_helper'  describe "referentials/show", type: :view do    let!(:referential) do -    referential = create(:referential) +    referential = create(:workbench_referential)      assign :referential, referential.decorate(context: {        current_organisation: referential.organisation      })    end +  let(:organisation){ referential.try(:organisation) }    let(:permissions){ [] }    let(:current_organisation) { organisation }    let(:organisation) { referential.organisation } -  let(:current_offer_workbench) { create :workbench, organisation: current_organisation}    let(:readonly){ false }    before :each do      assign :reflines, [] -    allow(view).to receive(:current_offer_workbench).and_return(current_offer_workbench)      allow(view).to receive(:current_organisation).and_return(current_organisation)      allow(view).to receive(:current_user).and_return(current_user) -      allow(view).to receive(:resource).and_return(referential)      allow(view).to receive(:has_feature?).and_return(true)      allow(view).to receive(:user_signed_in?).and_return true +    allow(view).to receive(:mutual_workbench).and_return referential.workbench      controller.request.path_parameters[:id] = referential.id      allow(view).to receive(:params).and_return({action: :show}) | 
