diff options
70 files changed, 724 insertions, 344 deletions
| @@ -135,7 +135,6 @@ gem 'rabl'  gem 'carrierwave', '~> 1.0'  gem 'sidekiq' -gem 'sinatra'  gem 'whenever', github: 'af83/whenever', require: false # '~> 0.9'  gem 'rake'  gem 'devise-async' diff --git a/Gemfile.lock b/Gemfile.lock index 63d78f9cd..090886f3d 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -368,10 +368,10 @@ GEM        railties (>= 3.1, < 5.0)      rabl (0.13.1)        activesupport (>= 2.3.14) -    rack (1.6.8) +    rack (1.6.9)      rack-livereload (0.3.16)        rack -    rack-protection (1.5.3) +    rack-protection (1.5.4)        rack      rack-proxy (0.6.3)        rack @@ -509,10 +509,6 @@ GEM      simplecov-html (0.10.0)      simplecov-rcov (0.2.3)        simplecov (>= 0.4.1) -    sinatra (1.4.8) -      rack (~> 1.5) -      rack-protection (~> 1.4) -      tilt (>= 1.3, < 3)      sixarm_ruby_unaccent (1.2.0)      slim (3.0.7)        temple (~> 0.7.6) @@ -700,7 +696,6 @@ DEPENDENCIES    simple_form (~> 3.1.0)    simplecov    simplecov-rcov -  sinatra    slim-rails (~> 3.1)    spring    spring-commands-rspec diff --git a/INSTALL.md b/INSTALL.md index e44b072f4..392ef5d9f 100644 --- a/INSTALL.md +++ b/INSTALL.md @@ -8,6 +8,17 @@ Example with [rvm](https://rvm.io/) (other solutions : rbenv, packages..):  rvm install 2.3.1  ``` +Nokogiri on macOS + +http://www.nokogiri.org/tutorials/installing_nokogiri.html tells us that `xz` can cause troubles, here is what to do + +``` +brew unlink xz +gem install nokogiri # or bundle install +brew link xz +``` + +  ## Node and Yarn  Yarn needs node. If you use Node Version Manager [NVM](https://github.com/creationix/nvm)  you can rely on the content of `.nvmrc`. Otherwise please make sure to use a compatible version, still best to use the same as indicated by `.nvrmc`. @@ -15,7 +26,7 @@ Yarn needs node. If you use Node Version Manager [NVM](https://github.com/creati  * Install node  ```sh -nvm install 6.12.0 +nvm install 6.13.0  ```  * Install [yarn](https://yarnpkg.com/lang/en/docs/install/) @@ -36,41 +47,6 @@ sudo apt-get update && sudo apt-get install yarn  yarn install  ``` -### Installation Caveats - -#### Node Related Issue, libv8 - -`libv8` might cause you troubles, depending on your local configuration. If you have `libv8` installed (probably because of `node.js`) you might need to tell bundler/Rubygems to use the system version. - -```sh -bundle config build.libv8 --with-system-v8 -bundle -``` - -or - -```sh -gem install libv8 -v '<version>' -- --with-system-v8 -bundle -``` - -You will get the correct value of `<version>` from bundler's error message. - -#### Node Related Issue, therubyracer - -Even after `libv8` installation working, the gem `therubyracer` might not like the `libv8` version chosen. - -In that case however we can let the gem make its own choice: - -```sh -gem uninstall libv8 -gem install therubyracer -v '<version>' -``` - -The version to be installed is indicated in the error message bundler gave us in the first place. - -This will install an appropriate `libv8` version and we can continue with `bundle`. -  ## Postgres  ### Create user @@ -89,53 +65,12 @@ When promted for the password enter the highly secure string `chouette`.  As documented [here](https://github.com/dryade/georuby-ext/issues/2) we need some more libs before we can start the `rake` setup tasks. -  On mac/OS :  ```sh  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 @@ -156,50 +91,34 @@ Go into your local repository and install the gems  bundle install  ``` -#### Nokogiri on macOS - -http://www.nokogiri.org/tutorials/installing_nokogiri.html tells us that `xz` can cause troubles, here is what to do - -``` -brew unlink xz -gem install nokogiri # or bundle install -brew link xz -``` -  ### Database  #### Create database  ```sh  bundle exec rake db:create db:migrate -RAILS_ENV=test bundle exec rake db:create db:migrate  ``` -#### Load seed datas ->>>>>>> master +#### Use seed + +Run :  ```sh  bundle exec rake db:seed:stif  ``` -#### Synchronise datas with lines and stop areas referentials - -* Launch Sidekiq - -```sh -bundle exec sidekiq -``` +Two users are created : stif-boiv@af83.com/secret and stif-boiv+transporteur@af83.com/secret -* Execute the Synchronization Tasks +#### Synchronize with STIF CODIFLIGNE (Line) and REFLEX (StopArea)  ```sh  bundle exec rake codifligne:sync  bundle exec rake reflex:sync  ``` -**N.B.** These are asynchronious tasks, you can observe the launched jobs in your [Sidekiq Console](http://localhost:3000/sidekiq) +**N.B.** These are asynchronous tasks, you can observe the launched jobs in your [Sidekiq Console](http://localhost:3000/sidekiq) -#### Data in various Apartments (Referentials) +#### Create Referential  To create `Referential` objects with some data (`Route`, `JourneyPattern`, `VehicleJourney`, etc), you need to wait codifligne and reflex jobs finished. And then you can launch : @@ -207,33 +126,32 @@ To create `Referential` objects with some data (`Route`, `JourneyPattern`, `Vehi  bundle exec rake referential:create  ``` -### Check installation +### Run tests -#### Run tests - -#### Rspec +* Rspec (Rails test)  ```sh  bundle exec rake spec -bundle exec rake teaspoon  ``` -If Postgres complains about illegal type `hstore` or `unaccent` in your tests that is probably because the shared extension is not installed, here is what to do: - -      bundle exec rake db:test:purge - -Thanks to `lib/tasks/extensions.rake`. +* Jest (JavaScript tests) -#### Jest (React integration specs) +```sh +grunt jest #to run the whole specs. +grunt #to watch for changes and automatically run corresponding tests. +``` -`grunt jest` to run the whole specs. +### Run -`grunt` to watch for changes and automatically run corresponding tests. +Launch Sidekiq -#### Start local server +```sh +bundle exec sidekiq +```  ```sh  bin/webpack-dev-server // Launch webpack server to compile assets on the fly  bundle exec rails server // Launch rails server  ``` +  You need to have an account on [STIF Portail](http://stif-portail-dev.af83.priv/) to connect to the Rails application. diff --git a/app/assets/stylesheets/OpenLayers/custom.sass b/app/assets/stylesheets/OpenLayers/custom.sass index fa874d924..013c056d6 100644 --- a/app/assets/stylesheets/OpenLayers/custom.sass +++ b/app/assets/stylesheets/OpenLayers/custom.sass @@ -2,15 +2,20 @@    .list-group-item &      margin-top: 15px -  .routes-labels +  .ol-routes-layers +    position: absolute +    right: 42px +    top: .5em      padding: 0 -    display: flex -    justify-content: space-between -    flex-wrap: wrap      margin: 5px -5px -    li -      list-style-type: none -      flex: 1 0 25% +    width: 30% +    background-color: transparentize(white, 0.2) +    border-radius: 3px +    max-height: 90% +    overflow-y: scroll +    a +      display: block +      width: calc(100% - 10px)        border: 1px solid $lightgrey        padding: 5px        margin: 5px @@ -18,11 +23,21 @@        cursor: pointer        color: $blue        white-space: nowrap - +      text-overflow: ellipsis +      overflow: hidden +      font-size: 0.6em        &:hover -        background: $orange +        text-decoration: none + +      &.active +        background: $blue          color: white +  .ol-routes-layers-button-wrapper +    position: absolute +    right: .5em +    top: .5em +    .ol-scale-line      background-color: transparent      bottom: 5px @@ -55,20 +70,20 @@    .ol-zoom      background-color: transparent -    .ol-zoom-in, .ol-zoom-out -      background-color: $darkblue -      color: #fff -      border-radius: 3px -      margin: 0 +  .ol-zoom-in, .ol-zoom-out, .ol-routes-layers-button +    background-color: $darkblue +    color: #fff +    border-radius: 3px +    margin: 0 -    .ol-zoom-in -      border-bottom-left-radius: 0 -      border-bottom-right-radius: 0 +  .ol-zoom-in +    border-bottom-left-radius: 0 +    border-bottom-right-radius: 0 -    .ol-zoom-out -      margin-top: 1px -      border-top-left-radius: 0 -      border-top-right-radius: 0 +  .ol-zoom-out +    margin-top: 1px +    border-top-left-radius: 0 +    border-top-right-radius: 0    .ol-zoomslider      margin: 2px diff --git a/app/assets/stylesheets/components/_modals.sass b/app/assets/stylesheets/components/_modals.sass index e52a2e125..14b783c51 100644 --- a/app/assets/stylesheets/components/_modals.sass +++ b/app/assets/stylesheets/components/_modals.sass @@ -50,3 +50,9 @@ $modalW: 600px      .modal-footer        border-color: rgba($blue, 0.25)        padding: 15px 30px + +    .has-error .form-group +      margin-bottom: -10px + +    .form-group +      margin-bottom: 25px diff --git a/app/assets/stylesheets/components/_referential_overview.sass b/app/assets/stylesheets/components/_referential_overview.sass index 0beb8ab67..7a0cc98c5 100644 --- a/app/assets/stylesheets/components/_referential_overview.sass +++ b/app/assets/stylesheets/components/_referential_overview.sass @@ -22,7 +22,7 @@        box-shadow: 0 0 10px rgba(0,0,0,0.5)        z-index: 1    .time-travel -    padding-top: 4px +    padding-top: 3px      padding-bottom: 4px      a.btn:first-child        margin-right: 1px @@ -61,7 +61,7 @@          border-color: $grey          width: auto          flex: 1 1 -        padding: 4px 11px 5px +        padding: 6px 11px          .input-group-btn            right: 10px          &.togglable @@ -117,6 +117,7 @@          padding: 7px 10px          border-bottom: 1px solid $grey          font-size: 0.8em +        display: block          &:last-child            border-bottom: none          .number @@ -135,10 +136,9 @@            overflow: hidden          .name            display: inline-block -          width: $left-size - 50px() +          width: $left-size - 10px            white-space: nowrap            line-height: 20px -          margin-left: 5px            text-overflow: ellipsis            overflow: hidden            vertical-align: bottom @@ -182,8 +182,8 @@            height: 100%            transition: margin 0.5s            background: white -          &:last-child -            box-shadow: 0 -10px 10px rgba(0,0,0,0.5) +          // &:last-child +          //   box-shadow: 0 -10px 10px rgba(0,0,0,0.5)            .week-span              left: 15px              top: 15px @@ -246,18 +246,17 @@              &.selected, &:hover                color: $blue                background-color: transparentize($blue, 0.7) + +            &:hover +              background-color: transparentize(white, 0.7)                &:after                  content: ""                  left: -1px                  right: -1px                  top: 100%                  height: 10000px -                background-color: transparentize($blue, 0.7)                  position: absolute                  z-index: 4 -            &:hover -              background-color: transparentize(white, 0.7) -              &:after                  background-color: transparentize(white, 0.7)        .line diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb index 45b7f55f6..c4961123d 100644 --- a/app/controllers/application_controller.rb +++ b/app/controllers/application_controller.rb @@ -28,7 +28,7 @@ class ApplicationController < ActionController::Base    protected    def user_not_authorized -    redirect_to forbidden_path +    render 'errors/forbidden', status: 403    end    def current_organisation diff --git a/app/controllers/calendars_controller.rb b/app/controllers/calendars_controller.rb index 75d4cbd09..cc7570d65 100644 --- a/app/controllers/calendars_controller.rb +++ b/app/controllers/calendars_controller.rb @@ -31,10 +31,11 @@ class CalendarsController < ChouetteController    end    def create -    create! do -      if @calendar.valid? && has_feature?('application_days_on_calendars') -        redirect_to([:edit, @calendar]) -        return +    create! do |success, failure| +      if has_feature?('application_days_on_calendars') +        success.html do +          redirect_to([:edit, @workgroup, @calendar]) +        end        end      end    end @@ -125,4 +126,4 @@ class CalendarsController < ChouetteController      scope    end -end
\ No newline at end of file +end diff --git a/app/controllers/compliance_check_messages_controller.rb b/app/controllers/compliance_check_messages_controller.rb index 7c416584e..36745981e 100644 --- a/app/controllers/compliance_check_messages_controller.rb +++ b/app/controllers/compliance_check_messages_controller.rb @@ -7,7 +7,7 @@ class ComplianceCheckMessagesController < ChouetteController    def index      index! do |format|        format.csv { -        send_data ComplianceCheckMessageExport.new(compliance_check_messages: collection).to_csv(:col_sep => "\;", :quote_char=>'"', force_quotes: true, server_url: request.base_url) , :filename => "compliance_check_set_errors_#{line_code}_#{Time.now.to_i}.csv" +        send_data ComplianceCheckMessageExport.new(compliance_check_messages: collection).to_csv(:col_sep => "\;", :quote_char=>'"', force_quotes: true, server_url: request.base_url) , :filename => "compliance_check_set_errors_#{line_code}_#{Date.today.to_s}.csv"        }      end    end diff --git a/app/controllers/compliance_check_sets_controller.rb b/app/controllers/compliance_check_sets_controller.rb index 271598428..62b0e6ba3 100644 --- a/app/controllers/compliance_check_sets_controller.rb +++ b/app/controllers/compliance_check_sets_controller.rb @@ -12,7 +12,7 @@ class ComplianceCheckSetsController < ChouetteController        @q_for_form = scope.ransack(params[:q])        format.html {          @compliance_check_sets = ComplianceCheckSetDecorator.decorate( -          @q_for_form.result.order(created_at: :desc) +          @q_for_form.result.order(created_at: :desc).paginate(page: params[:page], per_page: 30)          )        }      end diff --git a/app/controllers/compliance_checks_controller.rb b/app/controllers/compliance_checks_controller.rb index 81749e292..ad32bc538 100644 --- a/app/controllers/compliance_checks_controller.rb +++ b/app/controllers/compliance_checks_controller.rb @@ -1,4 +1,5 @@  class ComplianceChecksController <  InheritedResources::Base - - +  belongs_to :workbench do +    belongs_to :compliance_check_set +  end  end diff --git a/app/controllers/compliance_control_sets_controller.rb b/app/controllers/compliance_control_sets_controller.rb index 8f9251155..6461b38c8 100644 --- a/app/controllers/compliance_control_sets_controller.rb +++ b/app/controllers/compliance_control_sets_controller.rb @@ -36,11 +36,15 @@ 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) +    @compliance_control_sets ||= begin +      scope = end_of_association_chain.all +      scope = self.ransack_period_range(scope: scope, 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 +    end    def decorate_compliance_control_sets(compliance_control_sets) @@ -82,9 +86,9 @@ class ComplianceControlSetsController < ChouetteController      case params[:sort]        when 'owner_jdc'          collection.joins("LEFT JOIN organisations ON compliance_control_sets.organisation_id = organisations.id") -      when 'control_numbers'  +      when 'control_numbers'          collection.joins("LEFT JOIN compliance_controls ON compliance_controls.compliance_control_set_id = compliance_control_sets.id").group(:id) -      else  +      else          collection      end    end diff --git a/app/controllers/import_messages_controller.rb b/app/controllers/import_messages_controller.rb index 286bb0ce8..4f8fe7a25 100644 --- a/app/controllers/import_messages_controller.rb +++ b/app/controllers/import_messages_controller.rb @@ -9,7 +9,7 @@ class ImportMessagesController < ChouetteController    def index      index! do |format|        format.csv { -        send_data ImportMessageExport.new(:import_messages => @import_messages).to_csv(:col_sep => "\;", :quote_char=>'"', force_quotes: true) , :filename => "import_errors_#{@import_resource.name.gsub('.xml', '')}_#{Time.now.to_i}.csv" +        send_data ImportMessageExport.new(:import_messages => @import_messages).to_csv(:col_sep => "\;", :quote_char=>'"', force_quotes: true) , :filename => "import_errors_#{@import_resource.name.gsub('.xml', '')}_#{Date.today.to_s}.csv"        }      end    end diff --git a/app/controllers/referentials_controller.rb b/app/controllers/referentials_controller.rb index 5267c15d8..6e3694547 100644 --- a/app/controllers/referentials_controller.rb +++ b/app/controllers/referentials_controller.rb @@ -7,6 +7,8 @@ class ReferentialsController < ChouetteController    respond_to :json, :only => :show    respond_to :js, :only => :show +  before_action :check_cloning_source_is_accessible, only: %i(new create) +    def new      new! do        build_referential @@ -175,6 +177,12 @@ class ReferentialsController < ChouetteController      )    end +  def check_cloning_source_is_accessible +    return unless params[:from] +    source = Referential.find params[:from] +    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] diff --git a/app/helpers/application_helper.rb b/app/helpers/application_helper.rb index 0058c210d..356c7e69e 100644 --- a/app/helpers/application_helper.rb +++ b/app/helpers/application_helper.rb @@ -19,7 +19,7 @@ module ApplicationHelper        return object.full_name      end -    local  = "#{object.model_name.name.underscore.pluralize}.#{params[:action]}.title" +    local = "#{object.model_name.name.underscore.pluralize}.#{params[:action]}.title"      if object.try(:name)        t(local, name: object.name || object.id)      else diff --git a/app/helpers/table_builder_helper.rb b/app/helpers/table_builder_helper.rb index 2068dd23c..d16858678 100644 --- a/app/helpers/table_builder_helper.rb +++ b/app/helpers/table_builder_helper.rb @@ -224,7 +224,7 @@ module TableBuilderHelper          if column.linkable?            path = column.link_to(item) -          link = link_to(value, path) +          link = value.present? && path.present? ? link_to(value, path) : ""            if overhead.empty?              bcont << content_tag(:td, link, title: 'Voir') diff --git a/app/helpers/table_builder_helper/column.rb b/app/helpers/table_builder_helper/column.rb index 05aa9f563..ff6f2f36f 100644 --- a/app/helpers/table_builder_helper/column.rb +++ b/app/helpers/table_builder_helper/column.rb @@ -2,19 +2,21 @@ module TableBuilderHelper    class Column      attr_reader :key, :name, :attribute, :sortable -    def initialize(key: nil, name: '', attribute:, sortable: true, link_to: nil) +    def initialize(key: nil, name: '', attribute:, sortable: true, link_to: nil, **opts)        if key.nil? && name.empty?          raise ColumnMustHaveKeyOrNameError        end - +      opts ||= {}        @key = key        @name = name        @attribute = attribute        @sortable = sortable        @link_to = link_to +      @condition = opts[:if]      end      def value(obj) +      return unless check_condition(obj)        if @attribute.is_a?(Proc)          @attribute.call(obj)        else @@ -36,8 +38,18 @@ module TableBuilderHelper      end      def link_to(obj) +      return unless check_condition(obj)        @link_to.call(obj)      end + +    def check_condition(obj) +      condition_val = true +      if @condition.present? +        condition_val = @condition +        condition_val = condition_val.call(obj) if condition_val.is_a?(Proc) +      end +      !!condition_val +    end    end diff --git a/app/javascript/helpers/routes_map.coffee b/app/javascript/helpers/routes_map.coffee index 6834406fc..42377cd6e 100644 --- a/app/javascript/helpers/routes_map.coffee +++ b/app/javascript/helpers/routes_map.coffee @@ -1,3 +1,65 @@ +RoutesLayersButton = (options) -> +  menu = options.menu + +  toggleMenu = (e)=> +    $(menu.element).toggleClass 'hidden' +    button.innerHTML = if button.innerHTML == "+" then "-" else "+" + +  button = document.createElement("button") +  button.innerHTML = "+" +  button.addEventListener('click', toggleMenu, false) +  button.addEventListener('touchstart', toggleMenu, false) +  button.className = "ol-routes-layers-button" + +  element = document.createElement('div'); +  element.className = 'ol-control ol-routes-layers-button-wrapper'; + +  element.appendChild(button) + +  ol.control.Control.call(this, { +    element +    target: options.target +  }) + +ol.inherits RoutesLayersButton, ol.control.Control + +RoutesLayersControl = (routes, routes_map) -> + +  element = document.createElement('div') +  element.className = 'ol-unselectable ol-routes-layers hidden' +  Object.keys(routes).forEach (id)=> +    route = routes[id] +    route.active = true +    label = document.createElement('a') +    label.title = route.name +    label.className = 'active' +    label.innerHTML = route.name +    element.appendChild label +    label.addEventListener "click", => +      route.active = !route.active +      $(label).toggleClass "active" +      route.active +      route.vectorPtsLayer.setStyle routes_map.defaultStyles(route.active) +      route.vectorEdgesLayer.setStyle routes_map.edgeStyles(route.active) +      route.vectorLnsLayer.setStyle routes_map.lineStyle(route.active) +      routes_map.fitZoom() +    label.addEventListener "mouseenter", => +      route.vectorPtsLayer.setStyle routes_map.defaultStyles(true) +      route.vectorEdgesLayer.setStyle routes_map.edgeStyles(true) +      route.vectorLnsLayer.setStyle routes_map.lineStyle(true) + +    label.addEventListener "mouseleave", => +      route.vectorPtsLayer.setStyle routes_map.defaultStyles(route.active) +      route.vectorEdgesLayer.setStyle routes_map.edgeStyles(route.active) +      route.vectorLnsLayer.setStyle routes_map.lineStyle(route.active) + + +  ol.control.Control.call(this, { +    element +  }) + +ol.inherits RoutesLayersControl, ol.control.Control +  class RoutesMap    constructor: (@target)->      @initMap() @@ -20,6 +82,7 @@ class RoutesMap    addRoute: (route)->      geoColPts = []      geoColLns = [] +    route.active = true      @routes[route.id] = route if route.id      stops = route.stops || route      geoColEdges = [ @@ -77,68 +140,49 @@ class RoutesMap      @map.addLayer vectorEdgesLayer      @map.addLayer vectorLnsLayer -  lineStyle: (highlighted=false)-> +  lineStyle: (active=true)->      new ol.style.Style        stroke: new ol.style.Stroke -        color: if highlighted then "#ed7f00" else '#007fbb' -        width: 3 +        color: '#007fbb' +        width: if active then 3 else 0 -  edgeStyles: (highlighted=false)-> +  edgeStyles: (active=true)->      new ol.style.Style        image: new ol.style.Circle          radius: 5          stroke: new ol.style.Stroke -          color: if highlighted then "#ed7f00" else '#007fbb' -          width: 2 +          color: '#007fbb' +          width: if active then 3 else 0          fill: new ol.style.Fill -          color: if highlighted then "#ed7f00" else '#007fbb' -          width: 2 +          color: '#007fbb' +          width: if active then 3 else 0 -  defaultStyles: (highlighted=false)-> +  defaultStyles: (active=true)->      new ol.style.Style        image: new ol.style.Circle          radius: 4          stroke: new ol.style.Stroke -          color: if highlighted then "#ed7f00" else '#007fbb' -          width: 2 +          color: '#007fbb' +          width: if active then 3 else 0          fill: new ol.style.Fill            color: '#ffffff' -          width: 2 +          width: if active then 3 else 0    addRoutesLabels: -> -    labelsContainer = $("<ul class='routes-labels'></ul>") -    labelsContainer.appendTo $("##{@target}") -    @vectorPtsLayer = null -    @vectorEdgesLayer = null -    @vectorLnsLayer = null +    menu = new RoutesLayersControl(@routes, this) +    @map.addControl menu +    @map.addControl new RoutesLayersButton(menu: menu) + +  fitZoom: ()-> +    area = [] +    found = false      Object.keys(@routes).forEach (id)=>        route = @routes[id] -      label = $("<li>#{route.name}</ul>") -      label.appendTo labelsContainer -      label.mouseleave => -        route.vectorPtsLayer.setStyle @defaultStyles(false) -        route.vectorEdgesLayer.setStyle @edgeStyles(false) -        route.vectorLnsLayer.setStyle @lineStyle(false) -        route.vectorPtsLayer.setZIndex 2 -        route.vectorEdgesLayer.setZIndex 3 -        route.vectorLnsLayer.setZIndex 1 -        @fitZoom() -      label.mouseenter => -        route.vectorPtsLayer.setStyle @defaultStyles(true) -        route.vectorEdgesLayer.setStyle @edgeStyles(true) -        route.vectorLnsLayer.setStyle @lineStyle(true) -        route.vectorPtsLayer.setZIndex 11 -        route.vectorEdgesLayer.setZIndex 12 -        route.vectorLnsLayer.setZIndex 10 -        @fitZoom(route) - -  fitZoom: (route)-> -    if route -      area = [] -      route.stops.forEach (stop, i) => -        area.push [parseFloat(stop.longitude), parseFloat(stop.latitude)] -    else -      area = @area +      if route.active +        found = true +        route.stops.forEach (stop, i) => +          area.push [parseFloat(stop.longitude), parseFloat(stop.latitude)] +    area = @area unless found      boundaries = ol.extent.applyTransform(        ol.extent.boundingExtent(area), ol.proj.getTransform('EPSG:4326', 'EPSG:3857')      ) diff --git a/app/javascript/packs/referential_lines/show.js b/app/javascript/packs/referential_lines/show.js index 542188018..99c5072ef 100644 --- a/app/javascript/packs/referential_lines/show.js +++ b/app/javascript/packs/referential_lines/show.js @@ -6,5 +6,5 @@ routes = JSON.parse(decodeURIComponent(routes))  var map = new RoutesMap('routes_map')  map.addRoutes(routes) -// map.addRoutesLabels() +map.addRoutesLabels()  map.fitZoom() diff --git a/app/javascript/vehicle_journeys/actions/index.js b/app/javascript/vehicle_journeys/actions/index.js index e67753e4b..5fb88f024 100644 --- a/app/javascript/vehicle_journeys/actions/index.js +++ b/app/javascript/vehicle_journeys/actions/index.js @@ -203,11 +203,10 @@ const actions = {        let field = fields[key]        if(field.validity && !field.validity.valid){          valid = false -        $(field).parent().addClass('has-error').children('.help-block').remove() +        $(field).parent().parent().addClass('has-error').children('.help-block').remove()          $(field).parent().append("<span class='small help-block'>" + field.validationMessage + "</span>")        }      }) -      return valid    },    toggleArrivals : () => ({ diff --git a/app/javascript/vehicle_journeys/components/tools/CreateModal.js b/app/javascript/vehicle_journeys/components/tools/CreateModal.js index 24d9a23c2..a60429765 100644 --- a/app/javascript/vehicle_journeys/components/tools/CreateModal.js +++ b/app/javascript/vehicle_journeys/components/tools/CreateModal.js @@ -12,7 +12,13 @@ export default class CreateModal extends Component {    }    handleSubmit() { -    if (actions.validateFields(...this.refs, $('.vjCreateSelectJP')[0]) && this.props.modal.modalProps.selectedJPModal) { +    if(!this.props.modal.modalProps.selectedJPModal){ +      let field = $('#NewVehicleJourneyModal').find(".vjCreateSelectJP") +      field.parent().parent().addClass('has-error').children('.help-block').remove() +      field.parent().append("<span class='small help-block'>" + I18n.t("simple_form.required.text") + "</span>") +      return +    } +    if (actions.validateFields(...this.refs, $('.vjCreateSelectJP')[0])) {        this.props.onAddVehicleJourney(_.assign({}, this.refs, {custom_fields: this.custom_fields}), this.props.modal.modalProps.selectedJPModal, this.props.stopPointsList, this.props.modal.modalProps.vehicleJourney && this.props.modal.modalProps.vehicleJourney.company)        this.props.onModalClose()        $('#NewVehicleJourneyModal').modal('hide') diff --git a/app/models/chouette/vehicle_journey.rb b/app/models/chouette/vehicle_journey.rb index 9b94f7f0e..c5a6901d7 100644 --- a/app/models/chouette/vehicle_journey.rb +++ b/app/models/chouette/vehicle_journey.rb @@ -3,6 +3,7 @@ module Chouette    class VehicleJourney < Chouette::TridentActiveRecord      has_paper_trail      include ChecksumSupport +    include CustomFieldsSupport      include VehicleJourneyRestrictions      include ObjectidSupport      include StifTransportModeEnumerations @@ -340,21 +341,6 @@ module Chouette        end      end -    def self.custom_fields -      CustomField.where(resource_type: self.name.split("::").last) -    end - - -    def custom_fields -      Hash[*self.class.custom_fields.map do |v| -        [v.code, v.slice(:code, :name, :field_type, :options).update(value: custom_field_value(v.code))] -      end.flatten] -    end - -    def custom_field_value key -      (custom_field_values || {})[key.to_s] -    end -      def self.matrix(vehicle_journeys)        Hash[*VehicleJourneyAtStop.where(vehicle_journey_id: vehicle_journeys.pluck(:id)).map do |vjas|          [ "#{vjas.vehicle_journey_id}-#{vjas.stop_point_id}", vjas] diff --git a/app/models/compliance_check.rb b/app/models/compliance_check.rb index 55f2ae228..9d817e146 100644 --- a/app/models/compliance_check.rb +++ b/app/models/compliance_check.rb @@ -1,14 +1,23 @@  class ComplianceCheck < ActiveRecord::Base +  include ComplianceItemSupport    self.inheritance_column = nil    extend Enumerize    belongs_to :compliance_check_set    belongs_to :compliance_check_block -   +    enumerize :criticity, in: %i(warning error), scope: true, default: :warning    validates :criticity, presence: true    validates :name, presence: true    validates :code, presence: true    validates :origin_code, presence: true + +  def control_class +    compliance_control_name.present? ? compliance_control_name.constantize : nil +  end + +  delegate :predicate, to: :control_class, allow_nil: true +  delegate :prerequisite, to: :control_class, allow_nil: true +  end diff --git a/app/models/compliance_check_message_export.rb b/app/models/compliance_check_message_export.rb index 04e1a9caa..bbaaa8e3f 100644 --- a/app/models/compliance_check_message_export.rb +++ b/app/models/compliance_check_message_export.rb @@ -26,12 +26,14 @@ class ComplianceCheckMessageExport    end    def to_csv(options = {}) -    CSV.generate(options.slice(:col_sep, :quote_char, :force_quotes)) do |csv| +    csv_string = CSV.generate(options.slice(:col_sep, :quote_char, :force_quotes)) do |csv|        csv << column_names        compliance_check_messages.each do |compliance_check_message|          csv << [compliance_check_message.compliance_check.criticity, *compliance_check_message.message_attributes.values_at('test_id', 'source_objectid'), options[:server_url] + compliance_check_message.message_attributes['source_object_path'], I18n.t("compliance_check_messages.#{compliance_check_message.message_key}", compliance_check_message.message_attributes.deep_symbolize_keys)]        end      end +    # We add a BOM to indicate we use UTF-8 +    "\uFEFF" + csv_string    end    def to_zip(temp_file,options = {}) diff --git a/app/models/compliance_control.rb b/app/models/compliance_control.rb index 298a63ab9..537343005 100644 --- a/app/models/compliance_control.rb +++ b/app/models/compliance_control.rb @@ -1,18 +1,16 @@  class ComplianceControl < ActiveRecord::Base +  include ComplianceItemSupport    class << self      def criticities; %i(warning error) end      def default_code; "" end -    def dynamic_attributes -      stored_attributes[:control_attributes] || [] -    end      def policy_class        ComplianceControlPolicy      end      def subclass_patterns -      {  +      {          generic: 'Generic',          journey_pattern: 'JourneyPattern',          line: 'Line', @@ -30,6 +28,9 @@ class ComplianceControl < ActiveRecord::Base        end        super      end + +    def predicate; I18n.t("compliance_controls.#{self.name.underscore}.description") end +    def prerequisite; I18n.t("compliance_controls.#{self.name.underscore}.prerequisite") end    end    extend Enumerize @@ -45,26 +46,25 @@ class ComplianceControl < ActiveRecord::Base    validates :compliance_control_set, presence: true    validate def coherent_control_set -  return true if compliance_control_block_id.nil? -  ids = [compliance_control_block.compliance_control_set_id, compliance_control_set_id] -  return true if ids.first == ids.last -  names = ids.map{|id| ComplianceControlSet.find(id).name} -  errors.add(:coherent_control_set, -             I18n.t('compliance_controls.errors.incoherent_control_sets', -                    indirect_set_name: names.first, -                    direct_set_name: names.last)) -end - +    return true if compliance_control_block_id.nil? +    ids = [compliance_control_block.compliance_control_set_id, compliance_control_set_id] +    return true if ids.first == ids.last +    names = ids.map{|id| ComplianceControlSet.find(id).name} +    errors.add(:coherent_control_set, +               I18n.t('compliance_controls.errors.incoherent_control_sets', +                      indirect_set_name: names.first, +                      direct_set_name: names.last)) +  end -def initialize(attributes = {}) -  super -  self.name ||= I18n.t("activerecord.models.#{self.class.name.underscore}.one") -  self.code ||= self.class.default_code -  self.origin_code ||= self.class.default_code -end +  def initialize(attributes = {}) +    super +    self.name ||= I18n.t("activerecord.models.#{self.class.name.underscore}.one") +    self.code ||= self.class.default_code +    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 +  def predicate; self.class.predicate end +  def prerequisite; self.class.prerequisite end  end diff --git a/app/models/concerns/compliance_item_support.rb b/app/models/concerns/compliance_item_support.rb new file mode 100644 index 000000000..f44f5719f --- /dev/null +++ b/app/models/concerns/compliance_item_support.rb @@ -0,0 +1,13 @@ +module ComplianceItemSupport +  extend ActiveSupport::Concern +  included do + +  end + +  module ClassMethods +    def dynamic_attributes +      stored_attributes[:control_attributes] || [] +    end +  end + +end diff --git a/app/models/concerns/custom_fields_support.rb b/app/models/concerns/custom_fields_support.rb new file mode 100644 index 000000000..6c76bd653 --- /dev/null +++ b/app/models/concerns/custom_fields_support.rb @@ -0,0 +1,24 @@ +module CustomFieldsSupport +  extend ActiveSupport::Concern + +  included do +    validate :custom_fields_values_are_valid + +    def self.custom_fields +      CustomField.where(resource_type: self.name.split("::").last) +    end + +    def custom_fields +      CustomField::Collection.new self +    end + +    def custom_field_value key +      (custom_field_values || {})[key.to_s] +    end + +    private +    def custom_fields_values_are_valid +      custom_fields.values.all?{|cf| cf.valid?} +    end +  end +end diff --git a/app/models/custom_field.rb b/app/models/custom_field.rb index 774c8b0f6..4a840744e 100644 --- a/app/models/custom_field.rb +++ b/app/models/custom_field.rb @@ -6,4 +6,80 @@ class CustomField < ActiveRecord::Base    validates :name, uniqueness: {scope: [:resource_type, :workgroup_id]}    validates :code, uniqueness: {scope: [:resource_type, :workgroup_id], case_sensitive: false} + +  class Collection < HashWithIndifferentAccess +    def initialize object +      vals = object.class.custom_fields.map do |v| +        [v.code, CustomField::Value.new(object, v, object.custom_field_value(v.code))] +      end +      super Hash[*vals.flatten] +    end + +    def to_hash +      HashWithIndifferentAccess[*self.map{|k, v| [k, v.to_hash]}.flatten(1)] +    end +  end + +  class Value +    def self.new owner, custom_field, value +      field_type = custom_field.options["field_type"] +      klass_name = field_type && "CustomField::Value::#{field_type.classify}" +      klass = klass_name && const_defined?(klass_name) ? klass_name.constantize : CustomField::Value::Base +      klass.new owner, custom_field, value +    end + +    class Base +      def initialize owner, custom_field, value +        @custom_field = custom_field +        @raw_value = value +        @owner = owner +        @errors = [] +        @validated = false +        @valid = false +      end + +      delegate :code, :name, :field_type, :options, to: :@custom_field + +      def validate +        @valid = true +      end + +      def valid? +        validate unless @validated +        @valid +      end + +      def value +        @raw_value +      end + +      def errors_key +        "custom_fields.#{code}" +      end + +      def to_hash +        HashWithIndifferentAccess[*%w(code name field_type options value).map{|k| [k, send(k)]}.flatten(1)] +      end +    end + +    class Integer < Base +      def value +        @raw_value.to_i +      end + +      def validate +        @valid = true +        unless @raw_value =~ /\A\d*\Z/ +          @owner.errors.add errors_key, "'#{@raw_value}' is not a valid integer" +          @valid = false +        end +      end +    end + +    class String < Base +      def value +        "#{@raw_value}" +      end +    end +  end  end diff --git a/app/models/dashboard.rb b/app/models/dashboard.rb index 46c621266..bcd92de5a 100644 --- a/app/models/dashboard.rb +++ b/app/models/dashboard.rb @@ -27,4 +27,16 @@ class Dashboard    def current_organisation      context.send(:current_organisation)    end + +  def workbench +    @workbench ||= current_organisation.workbenches.default +  end + +  def workgroup +    workbench.workgroup +  end + +  def calendars +    workgroup.calendars.where('(organisation_id = ? OR shared = ?)', current_organisation.id, true) +  end  end diff --git a/app/models/import_message_export.rb b/app/models/import_message_export.rb index 05f8a2cc7..991eb0f61 100644 --- a/app/models/import_message_export.rb +++ b/app/models/import_message_export.rb @@ -26,12 +26,14 @@ class ImportMessageExport    end    def to_csv(options = {}) -    CSV.generate(options) do |csv| +    csv_string = CSV.generate(options) do |csv|        csv << column_names        import_messages.each do |import_message|          csv << [import_message.criticity, import_message.message_key, I18n.t("import_messages.#{import_message.message_key}", import_message.message_attributes.deep_symbolize_keys), *import_message.resource_attributes.values_at("filename", "line_number", "column_number")  ]        end      end +    # We add a BOM to indicate we use UTF-8 +    "\uFEFF" + csv_string    end    def to_zip(temp_file,options = {}) diff --git a/app/models/merge.rb b/app/models/merge.rb index 62bf581d6..d42d882ac 100644 --- a/app/models/merge.rb +++ b/app/models/merge.rb @@ -152,7 +152,7 @@ class Merge < ActiveRecord::Base            route_stop_points = referential_stop_points_by_route[route.id]            # Stop Points -          route_stop_points.each do |stop_point| +          route_stop_points.sort_by(&:position).each do |stop_point|              objectid = Chouette::StopPoint.where(objectid: stop_point.objectid).exists? ? nil : stop_point.objectid              attributes = stop_point.attributes.merge(                id: nil, @@ -166,7 +166,7 @@ class Merge < ActiveRecord::Base            new_route.save!            if new_route.checksum != route.checksum -            raise "Checksum has changed: #{route.inspect} #{new_route.inspect}" +            raise "Checksum has changed: \"#{route.checksum}\", \"#{route.checksum_source}\" -> \"#{new_route.checksum}\", \"#{new_route.checksum_source}\""            end          end        end @@ -221,7 +221,7 @@ class Merge < ActiveRecord::Base            new_journey_pattern = new.journey_patterns.create! attributes            if new_journey_pattern.checksum != journey_pattern.checksum -            raise "Checksum has changed for #{journey_pattern.inspect}: #{journey_pattern.checksum_source} #{new_journey_pattern.checksum_source} " +            raise "Checksum has changed for #{journey_pattern.inspect}: \"#{journey_pattern.checksum_source}\" -> \"#{new_journey_pattern.checksum_source}\""            end          end        end diff --git a/app/models/organisation.rb b/app/models/organisation.rb index e8fb4e060..745bc0d22 100644 --- a/app/models/organisation.rb +++ b/app/models/organisation.rb @@ -13,6 +13,8 @@ class Organisation < ActiveRecord::Base    has_many :line_referentials, through: :line_referential_memberships    has_many :workbenches +  has_many :workgroups, through: :workbenches +    has_many :calendars    has_many :api_keys, class_name: 'Api::V1::ApiKey' diff --git a/app/policies/application_policy.rb b/app/policies/application_policy.rb index c44937c9e..33d88660c 100644 --- a/app/policies/application_policy.rb +++ b/app/policies/application_policy.rb @@ -95,7 +95,6 @@ class ApplicationPolicy      referential.try(:organisation_id) || record.try(:organisation_id)    end -    #    #  Helpers    #  ------- diff --git a/app/policies/compliance_control_set_policy.rb b/app/policies/compliance_control_set_policy.rb index 011f6c0c7..55507ffd9 100644 --- a/app/policies/compliance_control_set_policy.rb +++ b/app/policies/compliance_control_set_policy.rb @@ -5,6 +5,10 @@ class ComplianceControlSetPolicy < ApplicationPolicy      end    end +  def show? +    organisation_match? +  end +    def destroy?      user.has_permission?('compliance_control_sets.destroy')    end diff --git a/app/services/referential_overview.rb b/app/services/referential_overview.rb index ccfe0617a..7ef2909ad 100644 --- a/app/services/referential_overview.rb +++ b/app/services/referential_overview.rb @@ -208,7 +208,11 @@ class ReferentialOverview      end      def span -      h.l(@start_date, format: "#{@start_date.day}-#{@end_date.day} %b") +      if @start_date.month == @end_date.month +        h.l(@start_date, format: "#{@start_date.day}-#{@end_date.day} %b %Y") +      else +        "#{h.l(@start_date, format: "%d %b")} - #{h.l(@end_date, format: "%d %b %Y")}" +      end      end      def number diff --git a/app/views/calendars/_form_advanced.html.slim b/app/views/calendars/_form_advanced.html.slim index e796e2e36..aa4fd4e40 100644 --- a/app/views/calendars/_form_advanced.html.slim +++ b/app/views/calendars/_form_advanced.html.slim @@ -3,6 +3,6 @@  = javascript_tag do    | window.actionType = "#{raw params[:action]}";    // | window.I18n = #{(I18n.backend.send(:translations)[I18n.locale].to_json).html_safe}; -  | window.timetablesUrl = "#{calendar_url(@calendar).html_safe}"; +  | window.timetablesUrl = "#{workgroup_calendar_url(@workgroup, @calendar).html_safe}";  = javascript_pack_tag 'calendars/edit.js' diff --git a/app/views/compliance_check_sets/index.html.slim b/app/views/compliance_check_sets/index.html.slim index 31ad31e5b..9abd95dd1 100644 --- a/app/views/compliance_check_sets/index.html.slim +++ b/app/views/compliance_check_sets/index.html.slim @@ -23,9 +23,10 @@                  ), \                  TableBuilderHelper::Column.new( \                    key: :associated_object, \ -                  attribute: Proc.new{|n| n.referential.present? ? n.referential.name : ''}, \ +                  if: ->(compliance_check_set){ compliance_check_set.referential.present? }, \ +                  attribute: Proc.new{|n| n.referential.name}, \                    link_to: lambda do |compliance_check_set| \ -                    compliance_check_set.referential.present? ? referential_path(compliance_check_set.referential_id) : '#' \ +                    referential_path(compliance_check_set.referential_id) \                    end \                  ), \                  TableBuilderHelper::Column.new( \ @@ -42,6 +43,8 @@                ],                sortable: true,                cls: 'table has-filter has-search' +               +            = new_pagination @compliance_check_sets, 'pull-right'      - unless @compliance_check_sets.any?        .row.mt-xs          .col-lg-12 diff --git a/app/views/compliance_checks/show.html.slim b/app/views/compliance_checks/show.html.slim new file mode 100644 index 000000000..8dd699c65 --- /dev/null +++ b/app/views/compliance_checks/show.html.slim @@ -0,0 +1,13 @@ +- breadcrumb :compliance_check, @workbench, resource +- page_header_content_for resource + + +.page_content +  .container-fluid +    .row +      .col-lg-6.col-md-6.col-sm-12.col-xs-12 +        = render partial: "shared/controls/metadatas" +        - if resource.compliance_check_block +          = definition_list t('compliance_controls.show.metadatas.compliance_check_block'), +            I18n.t('activerecord.attributes.compliance_control_blocks.transport_mode') => I18n.t("enumerize.transport_mode.#{resource.compliance_check_block.transport_mode}"), +            I18n.t('activerecord.attributes.compliance_control_blocks.transport_submode') => I18n.t("enumerize.transport_submode.#{resource.compliance_check_block.transport_submode}") diff --git a/app/views/compliance_controls/_filters.html.slim b/app/views/compliance_controls/_filters.html.slim index f6b9970f2..a16d2c33d 100644 --- a/app/views/compliance_controls/_filters.html.slim +++ b/app/views/compliance_controls/_filters.html.slim @@ -5,8 +5,8 @@    class: 'form form-filter' do |f|    .ffg-row -    .input-group.search_bar class=filter_item_class(params[:q], :name_cont) -      = f.search_field :name_cont, +    .input-group.search_bar class=filter_item_class(params[:q], :name_or_code_cont) +      = f.search_field :name_or_code_cont,          class: 'form-control',          placeholder: t('compliance_controls.filters.name')        span.input-group-btn diff --git a/app/views/compliance_controls/show.html.slim b/app/views/compliance_controls/show.html.slim index ab25747a9..6e7a45d12 100644 --- a/app/views/compliance_controls/show.html.slim +++ b/app/views/compliance_controls/show.html.slim @@ -6,22 +6,7 @@    .container-fluid      .row        .col-lg-6.col-md-6.col-sm-12.col-xs-12 -        /- @compliance_control.control_attributes.each_with_index do |(key,value), index| -        = definition_list t('metadatas'), -              { \ -                ComplianceControl.human_attribute_name(:name) => @compliance_control.name, -                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.predicate, -                I18n.t('activerecord.attributes.compliance_control.prerequisite') => @compliance_control.prerequisite, -              }.merge( \ -                {}.tap do |hash| \ -                  @compliance_control.class.dynamic_attributes.each do |attribute| \ -                    hash[ComplianceControl.human_attribute_name(attribute)] = @compliance_control.send(attribute) \ -                  end \ -                end \ -              ) +        = render partial: "shared/controls/metadatas"          - if @compliance_control.compliance_control_block            = definition_list t('compliance_controls.show.metadatas.compliance_control_block'),              I18n.t('activerecord.attributes.compliance_control_blocks.transport_mode') => I18n.t("enumerize.transport_mode.#{@compliance_control.compliance_control_block.transport_mode}"), diff --git a/app/views/dashboards/_dashboard.html.slim b/app/views/dashboards/_dashboard.html.slim index 7f78934a6..8d0e723a6 100644 --- a/app/views/dashboards/_dashboard.html.slim +++ b/app/views/dashboards/_dashboard.html.slim @@ -22,12 +22,12 @@        .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) +            = link_to I18n.t("activerecord.models.calendar", count: @dashboard.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? +        - if @dashboard.calendars.present?            .list-group -            - @dashboard.current_organisation.calendars.order("updated_at desc").limit(5).each do |calendar| +            - @dashboard.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 diff --git a/app/views/lines/show.html.slim b/app/views/lines/show.html.slim index 34b907bdd..96bb5bb0d 100644 --- a/app/views/lines/show.html.slim +++ b/app/views/lines/show.html.slim @@ -6,13 +6,13 @@      .row        .col-lg-6.col-md-6.col-sm-12.col-xs-12          = definition_list t('metadatas'), -          {  'ID Codif' => @line.get_objectid.short_id, -             'Activé' => (@line.deactivated? ? t('false') : t('true')), +          {  t('id_codif') => @line.get_objectid.short_id, +             @line.human_attribute_name(:deactivated) => (@line.deactivated? ? t('false') : t('true')),               @line.human_attribute_name(:network_id) => (@line.network.nil? ? t('lines.index.unset') : @line.network.name),               @line.human_attribute_name(:company_id) => (@line.company.nil? ? t('lines.index.unset') : @line.company.name), -             'Transporteur(s) secondaire(s)' => (@line.secondary_companies.nil? ? t('lines.index.unset') : array_to_html_list(@line.secondary_companies.collect(&:name))), -             'Nom court' => @line.number, -             'Code public' => (@line.registration_number ? @line.registration_number : '-'), +             @line.human_attribute_name(:secondary_companies) => (@line.secondary_companies.nil? ? t('lines.index.unset') : array_to_html_list(@line.secondary_companies.collect(&:name))), +             @line.human_attribute_name(:number) => @line.number, +             @line.human_attribute_name(:registration_number) => (@line.registration_number ? @line.registration_number : '-'),               @line.human_attribute_name(:transport_mode) => (@line.transport_mode.present? ? t("enumerize.transport_mode.#{@line.transport_mode}") : '-'),               @line.human_attribute_name(:transport_submode) => (@line.transport_submode.present? ? t("enumerize.transport_submode.#{@line.transport_submode}") : '-'),               @line.human_attribute_name(:url) => (@line.url ? @line.url : '-'), diff --git a/app/views/referential_lines/show.html.slim b/app/views/referential_lines/show.html.slim index 5ea0e31bb..ef32ef6b0 100644 --- a/app/views/referential_lines/show.html.slim +++ b/app/views/referential_lines/show.html.slim @@ -82,6 +82,6 @@                = replacement_msg t('routes.search_no_results')  = javascript_tag do -  | window.routes = "#{URI.escape(@routes.map{|r| {name: r.name, id: r.id, stops: route_json_for_edit(r, serialize: false)}}.to_json)}" +  | window.routes = "#{URI.escape(@routes.select{|r| r.wayback == :outbound}.map{|r| {name: r.name, id: r.id, stops: route_json_for_edit(r, serialize: false)}}.to_json)}"  = javascript_pack_tag 'referential_lines/show.js' diff --git a/app/views/referentials/_form.html.slim b/app/views/referentials/_form.html.slim index 1e59ab566..96d847ec1 100644 --- a/app/views/referentials/_form.html.slim +++ b/app/views/referentials/_form.html.slim @@ -47,8 +47,13 @@              = link_to_add_association t('simple_form.labels.referential.actions.add_period'), subform, :periods, class: 'btn btn-outline-primary'      .separator +    .row +      .col-lg-11 +        = subform.input :lines, as: :select, collection: Chouette::Line.includes(:company).order(:name).where(objectid: current_functional_scope), selected: subform.object.line_ids, label_method: :display_name, input_html: { 'data-select2ed': 'true', 'data-select2ed-placeholder': t('simple_form.labels.referential.placeholders.select_lines'), 'multiple': 'multiple', style: 'width: 100%' } +      .col-lg-1 +        a.clear-lines.btn.btn-default href='#' +          .fa.fa-trash -    = subform.input :lines, as: :select, collection: Chouette::Line.includes(:company).order(:name).where(objectid: current_functional_scope), selected: subform.object.line_ids, label_method: :display_name, input_html: { 'data-select2ed': 'true', 'data-select2ed-placeholder': t('simple_form.labels.referential.placeholders.select_lines'), 'multiple': 'multiple', style: 'width: 100%' }    .hidden = form.input :workbench_id, as: :hidden @@ -58,3 +63,10 @@        class: 'btn btn-default formSubmitr',        data: { disable_with: t('actions.processing') },        form: 'referential_form' + +  - content_for :javascript do +    coffee: +      $(".clear-lines").click (e)-> +        e.preventDefault() +        $(e.currentTarget).parents('.row').first().find('[name*=line]').val('').trigger('change') +        false diff --git a/app/views/referentials/_overview.html.slim b/app/views/referentials/_overview.html.slim index 870f642d4..6bed5f282 100644 --- a/app/views/referentials/_overview.html.slim +++ b/app/views/referentials/_overview.html.slim @@ -35,9 +35,12 @@          .lines= I18n.t("referentials.overview.head.lines")        .lines          - overview.lines.each do |line| -          .line -            a.number style="background-color: #{line.color.present? ? "##{line.color}" : 'whitesmoke'}" title=line.name -              = line.number +          a.line title=line.name +            - if line.number.present? +              .number style="background-color: #{line.color.present? ? "##{line.color}" : 'whitesmoke'}" +                = line.number +            - else +              .name= line.name              .company= line.company&.name              .mode= line.transport_mode.present? ? t("enumerize.transport_mode.#{line.transport_mode}") : ""      .right diff --git a/app/views/shared/controls/_metadatas.html.slim b/app/views/shared/controls/_metadatas.html.slim new file mode 100644 index 000000000..49df06166 --- /dev/null +++ b/app/views/shared/controls/_metadatas.html.slim @@ -0,0 +1,15 @@ += definition_list t('metadatas'), +  { \ +    ComplianceControl.human_attribute_name(:name) => resource.name, +    ComplianceControl.human_attribute_name(:code) => resource.code, +    ComplianceControl.human_attribute_name(:criticity) => resource.criticity, +    ComplianceControl.human_attribute_name(:comment) => resource.comment, +    I18n.t('activerecord.attributes.compliance_control.predicate') => resource.predicate, +    I18n.t('activerecord.attributes.compliance_control.prerequisite') => resource.prerequisite, +  }.merge( \ +    {}.tap do |hash| \ +      resource.class.dynamic_attributes.each do |attribute| \ +        hash[ComplianceControl.human_attribute_name(attribute)] = resource.send(attribute) \ +      end \ +    end \ +  ) diff --git a/app/views/stop_areas/show.html.slim b/app/views/stop_areas/show.html.slim index a4e14a272..34b872e91 100644 --- a/app/views/stop_areas/show.html.slim +++ b/app/views/stop_areas/show.html.slim @@ -20,7 +20,7 @@              @stop_area.human_attribute_name(:zip_code) => @stop_area.zip_code,              @stop_area.human_attribute_name(:city_name) => @stop_area.city_name,              @stop_area.human_attribute_name(:country_code) => @stop_area.country_code.presence || '-', -            'Etat' => (@stop_area.deleted_at ? 'Supprimé' : 'Actif'), +            t('activerecord.attributes.stop_area.state') => (@stop_area.deleted_at ? t('stop_areas.show.state.deactivated') : t('stop_areas.show.state.active')),              @stop_area.human_attribute_name(:comment) => @stop_area.try(:comment),              })          = definition_list t('metadatas'), attributes diff --git a/config/breadcrumbs.rb b/config/breadcrumbs.rb index 00ccf16bd..adcbb0b6f 100644 --- a/config/breadcrumbs.rb +++ b/config/breadcrumbs.rb @@ -8,7 +8,7 @@ end  crumb :workbench_output do |workbench|    link I18n.t('workbench_outputs.show.title'), workbench_output_path(workbench) -  parent :workbench, current_offer_workbench +  parent :workbench, mutual_workbench(workbench)  end  crumb :merges do |workbench| @@ -96,6 +96,11 @@ crumb :compliance_check_set do |workbench, compliance_check_set|    parent :compliance_check_sets, workbench  end +crumb :compliance_check do |workbench, compliance_check| +  link breadcrumb_name(compliance_check), workbench_compliance_check_set_compliance_check_path(workbench, compliance_check.compliance_check_set, compliance_check) +  parent :compliance_check_set_executed, workbench, compliance_check.compliance_check_set +end +  crumb :compliance_check_set_executed do |workbench, compliance_check_set|    link I18n.t('compliance_check_sets.executed.title', name: compliance_check_set.name), executed_workbench_compliance_check_set_path(workbench, compliance_check_set)    parent :compliance_check_sets, workbench diff --git a/config/locales/compliance_checks.en.yml b/config/locales/compliance_checks.en.yml index 177c87852..f960755da 100644 --- a/config/locales/compliance_checks.en.yml +++ b/config/locales/compliance_checks.en.yml @@ -8,3 +8,7 @@ en:        subclass: Object        criticity: Severity        name: Name +    show: +      title: "Compliance check" +      metadatas: +        compliance_control_block: "Control block informations" diff --git a/config/locales/compliance_checks.fr.yml b/config/locales/compliance_checks.fr.yml index d11d37003..041ab4f43 100644 --- a/config/locales/compliance_checks.fr.yml +++ b/config/locales/compliance_checks.fr.yml @@ -11,4 +11,7 @@ fr:        subclass: Objet        criticity: Criticité        name: Nom -     +    show: +      title: "Consulter un contrôle" +      metadatas: +        compliance_control_block: "Informations sur le groupe de contrôle" diff --git a/config/locales/compliance_controls.en.yml b/config/locales/compliance_controls.en.yml index ca9d83872..f7d461fdb 100644 --- a/config/locales/compliance_controls.en.yml +++ b/config/locales/compliance_controls.en.yml @@ -29,8 +29,6 @@ en:        title: "Add a new compliance control"      edit:        title: "Update compliance control" -    metas: -      no_prerequisite: "None"      actions:        new: Add        edit: Edit @@ -41,6 +39,7 @@ en:        messages:          3_route_1: "The route with %{source_objectid} objectid connect the stop points %{target_0_label} (%{target_0_objectid}) and %{target_1_label} (%{target_1_objectid}) which belong to the same ZDL"        description: "Two stop points which belong to the same ZDL cannot follow one another in a route" +      prerequisite: "None"      route_control/opposite_route:        messages:          3_route_2: "The route with %{source_objectid} objectid references an incoherent oppposite route %{target_0_objectid}" @@ -53,10 +52,12 @@ en:        messages:          3_route_3: "The route with %{source_objectid} objectid doesn't have any journey pattern"        description: "A route must have at least one journey pattern" +      prerequisite: "None"      route_control/duplicates:        messages:          3_route_4: "The route with %{source_objectid} objectid is identical with another route %{target_0_objectid}"        description: "2 routes cannot connect the same stop points with the same order and the same boarding and alighting characteristics" +      prerequisite: "None"      route_control/opposite_route_terminus:        messages:          3_route_5: "The route with %{source_objectid} objectid has a first stop from the %{target_0_label} ZDL whereas its oppoite route's last stop is from the ZDL %{target_1_label}" @@ -66,78 +67,95 @@ en:        messages:          3_route_6: "The route with %{source_objectid} objectid does not connect enough stop points (required 2 stop points)"        description: "A route must have at least 2 stop points" +      prerequisite: "None"      route_control/stop_points_in_journey_pattern:        messages:          3_route_8: "The stop point %{target_0_label} (%{target_0_objectid}) of the route %{source_objectid} is not used by any journey pattern"        description: "The stop points of a route must be used by at least one journey pattern" +      prerequisite: "None"      route_control/omnibus_journey_pattern:        messages:          3_route_9: "The route with %{source_objectid} objectid does not have a journey pattern that connect all of its stop points"        description: "A journey pattern of a route should connect all of a route's stop points" +      prerequisite: "None"      route_control/unactivated_stop_point:        messages:          3_route_10: "L'itinéraire %{source_objectid} référence un arrêt (ZDEp) désactivé %{target_0_label} (%{target_0_objectid})"        description: "Les arrêts d'un itinéraire ne doivent pas être désactivés" +      prerequisite: "None"      journey_pattern_control/duplicates:        messages:          3_journeypattern_1: "The journey pattern with objectid %{source_objectid} is identical with another one %{target_0_objectid}"        description: "Two journey patterns belonging to the same line must not connect the same stop points in the same order" +      prerequisite: "None"      journey_pattern_control/vehicle_journey:        messages:          3_journeypattern_2: "The journey pattern with %{source_objectid} objectid doesn't have any vehicle journey"        description: "A journey pattern must have at least one vehicle journey" +      prerequisite: "None"      vehicle_journey_control/waiting_time:        messages:          3_vehiclejourney_1: "On the following vehicle journey %{source_objectid}, the waiting time %{error_value} a this stop point %{target_0_label} (%{target_0_objectid}) is greater than the threshold (%{reference_value})"        description: "The waiting time, in minutes, at a specific stop point cannot be too big" +      prerequisite: "None"      vehicle_journey_control/speed:        messages:          3_vehiclejourney_2_1: "On the following vehicle journey %{source_objectid}, the computed speed %{error_value} between the stop points %{target_0_label} (%{target_0_objectid}) and %{target_1_label} (%{target_1_objectid}) is greater than the threshold (%{reference_value})"          3_vehiclejourney_2_2: "On the following vehicle journey %{source_objectid}, the computed speed %{error_value} between the stop points %{target_0_label} (%{target_0_objectid}) and %{target_1_label} (%{target_1_objectid}) is smaller than the threshold (%{reference_value})"        description: "The speed between 2 stop points should be confined between thresholds" +      prerequisite: "None"      vehicle_journey_control/delta:        messages:          3_vehiclejourney_3: "The travel time on the vehicle journey with %{source_objectid} objectid between the stop points %{target_0_label} (%{target_0_objectid}) and %{target_1_label} (%{target_1_objectid}) is too far off %{error_value} the average waiting on the journey pattern"        description: "The travel time between two following stop points must be close to all the vehicle journey of a journey pattern" +      prerequisite: "None"      vehicle_journey_control/time_table:        messages:          3_vehiclejourney_4: "The vehicle journey with %{source_objectid} objectid does not have a timetable"        description: "A vehicle journey must have at least one timetable" +      prerequisite: "None"      vehicle_journey_control/vehicle_journey_at_stops:        messages:          3_vehiclejourney_5_1: "The vehicle journey with %{source_objectid} objectid has an arrival time %{error_value} greater than the departure time %{reference_value} at the stop point %{target_0_label} (%{target_0_objectid})"          3_vehiclejourney_5_2: "The vehicle journey with %{source_objectid} objectid has an departure time %{error_value} at stop point %{target_0_label} (%{target_0_objectid}) greater than the arrival %{reference_value} at the next stop point"        description: "The arrival time of a stop point must be smaller than the departure time of this stop point AND the departure time of the stop points must be in chronological order" +      prerequisite: "None"      routing_constraint_zone_control/vehicle_journey_at_stops:        messages:          3_routingconstraint_1: "The Routing Constraint Zone %{source_objectid} references an unactivated stop point (ZDEp) %{target_0_label} (%{target_0_objectid})"        description: "The stop points of a Routing Constraint Zone must be activated" +      prerequisite: "None"      routing_constraint_zone_control/maximum_length:        messages:          3_routingconstraint_2: "The Routing Constraint Zone %{source_objectid} covers all the stop points of its related route : %{target_0_objectid}."        description: "A Routing Constraint Zone cannot cover all the stop points of a route" +      prerequisite: "None"      routing_constraint_zone_control/minimum_length:        messages:          3_routingconstraint_3: "The Routing Constraint Zone %{source_objectid} has less than 2 stop points"        description: "A Routing Constraint Zone must have at least 2 stop points" +      prerequisite: "None"      line_control/route:        messages:          3_line_1: "On line :%{source_label} (%{source_objectid}), no route has an opposite route"        description: "The routes of a line must have an opposite route" -      prerequisite: Lign has multiple routes +      prerequisite: Line has multiple routes      generic_attribute_control/pattern:        messages:          3_generic_1: "%{source_objectid} : the %{source_attribute} attribute value (%{error_value}) does not respect the following pattern : %{reference_value}"        description: "The object attribute must respect a patten (regular expression)" +      prerequisite: "None"      generic_attribute_control/min_max:        messages:          3_generic_2_1: "%{source_objectid}  : the %{source_attribute} attributes's value (%{error_value}) is greater than the authorized maximum value : %{reference_value}"          3_generic_2_2: "%{source_objectid}  : the %{source_attribute} attributes's value (%{error_value}) is smaller than the authorized minimum value %{reference_value}"        description: "The numeric value of an attribute must be contained between 2 values" +      prerequisite: "None"      generic_attribute_control/uniqueness:        messages:          3_generic_3: "%{source_objectid}  : the %{source_attribute} attribute (%{error_value}) has a value shared with : %{target_0_objectid}"        description: "The attribute's value must be unique compared to the other objects ofthe same type (related to the same line)" +      prerequisite: "None"      shape_control:        3_shape_1: "Tracé %{source_objectid} : le tracé passe trop loin de l'arrêt %{target_0_label} (%{target_0_objectid}) : %{error_value} > %{reference_value}"        3_shape_2: "Tracé %{source_objectid} : le tracé n'est pas défini entre les arrêts %{target_0_label} (%{target_0_objectid}) et %{target_1_label} (%{target_1_objectid})" diff --git a/config/locales/compliance_controls.fr.yml b/config/locales/compliance_controls.fr.yml index f5f7e351f..44d01a973 100644 --- a/config/locales/compliance_controls.fr.yml +++ b/config/locales/compliance_controls.fr.yml @@ -28,8 +28,6 @@ fr:        title: "Editer un contrôle"      select_type:        title: "Sélectionner un type de contrôle" -    metas: -      no_prerequisite: "Aucun"      actions:        new: Ajouter        edit: Editer @@ -40,6 +38,7 @@ fr:        messages:          3_route_1: "L'itinéraire %{source_objectid} dessert successivement les arrêts %{target_0_label} (%{target_0_objectid}) et %{target_1_label} (%{target_1_objectid}) de la même zone de lieu"        description: "Deux arrêts d’une même ZDL ne peuvent pas se succéder dans un itinéraire" +      prerequisite: "Aucun"      route_control/opposite_route:        messages:          3_route_2: "L'itinéraire %{source_objectid} référence un itinéraire retour %{target_0_objectid} incohérent" @@ -52,10 +51,12 @@ fr:        messages:          3_route_3: "L'itinéraire %{source_objectid} n'a pas de mission"        description: "Un itinéraire doit avoir au moins une mission" +      prerequisite: "Aucun"      route_control/duplicates:        messages:          3_route_4: "L'itinéraire %{source_objectid} est identique à l'itinéraire %{target_0_objectid}"        description: "2 itinéraires ne doivent pas desservir strictement les mêmes arrêts dans le même ordre avec les mêmes critères de monté/descente" +      prerequisite: "Aucun"      route_control/opposite_route_terminus:        messages:          3_route_5: "L'itinéraire %{source_objectid} dessert au départ un arrêt de la ZDL %{target_0_label} alors que l'itinéraire inverse dessert à l'arrivée un arrêt de la ZDL %{target_1_label}" @@ -65,60 +66,74 @@ fr:        messages:          3_route_6: "L'itinéraire %{source_objectid} ne dessert pas assez d'arrêts (minimum 2 requis)"        description: "Un itinéraire doit référencer au moins 2 arrêts" +      prerequisite: "Aucun"      route_control/stop_points_in_journey_pattern:        messages:          3_route_8: "l'arrêt %{target_0_label} (%{target_0_objectid}) de l'itinéraire %{source_objectid} n'est desservi par aucune mission"        description: "Les arrêts de l'itinéraire doivent être desservis par au moins une mission" +      prerequisite: "Aucun"      route_control/omnibus_journey_pattern:        messages:          3_route_9: "L'itinéraire %{source_objectid} n'a aucune mission desservant l'ensemble de ses arrêts"        description: "Une mission de l'itinéraire devrait desservir l'ensemble des arrêts de celui-ci" +      prerequisite: "Aucun"      route_control/unactivated_stop_point:        messages:          3_route_10: "L'itinéraire %{source_objectid} référence un arrêt (ZDEp) désactivé %{target_0_label} (%{target_0_objectid})"        description: "Les arrêts d'un itinéraire ne doivent pas être désactivés" +      prerequisite: "Aucun"      journey_pattern_control/duplicates:        messages:          3_journeypattern_1: "La mission %{source_objectid} est identique à la mission %{target_0_objectid}"        description: "Deux missions de la même ligne ne doivent pas desservir les mêmes arrêts dans le même ordre" +      prerequisite: "Aucun"      journey_pattern_control/vehicle_journey:        messages:          3_journeypattern_2: "La mission %{source_objectid} n'a pas de course"        description: "Une mission doit avoir au moins une course" +      prerequisite: "Aucun"      vehicle_journey_control/waiting_time:        messages:          3_vehiclejourney_1: "Sur la course %{source_objectid}, le temps d'attente %{error_value} à l'arrêt %{target_0_label} (%{target_0_objectid}) est supérieur au seuil toléré (%{reference_value})"        description: "La durée d’attente, en minutes, à un arrêt ne doit pas être trop grande" +      prerequisite: "Aucun"      vehicle_journey_control/speed:        messages:          3_vehiclejourney_2_1: "Sur la course %{source_objectid}, la vitesse calculée %{error_value} entre les arrêts %{target_0_label} (%{target_0_objectid}) et %{target_1_label} (%{target_1_objectid}) est supérieure au seuil toléré (%{reference_value})"          3_vehiclejourney_2_2: "Sur la course %{source_objectid}, la vitesse calculée %{error_value} entre les arrêts %{target_0_label} (%{target_0_objectid}) et %{target_1_label} (%{target_1_objectid}) est inférieure au seuil toléré (%{reference_value})"        description: "La vitesse entre deux arrêts doit être dans une fourchette paramétrable" +      prerequisite: "Aucun"      vehicle_journey_control/delta:        messages:          3_vehiclejourney_3: "Le temps de parcours sur la course %{source_objectid} entre les arrêts %{target_0_label} (%{target_0_objectid}) et %{target_1_label} (%{target_1_objectid}) s'écarte de %{error_value} du temps moyen constaté sur la mission"        description: "Les temps de parcours entre 2 arrêts successifs doivent être similaires pour toutes les courses d’une même mission" +      prerequisite: "Aucun"      vehicle_journey_control/time_table:        messages:          3_vehiclejourney_4: "La course %{source_objectid} n'a pas de calendrier d'application"        description: "Une course doit avoir au moins un calendrier d’application" +      prerequisite: "Aucun"      vehicle_journey_control/vehicle_journey_at_stops:        messages:          3_vehiclejourney_5_1: "La course %{source_objectid} a un horaire d'arrivé %{error_value} supérieur à l'horaire de départ %{reference_value} à l'arrêt %{target_0_label} (%{target_0_objectid})"          3_vehiclejourney_5_2: "La course %{source_objectid} a un horaire de départ %{error_value} à l'arrêt %{target_0_label} (%{target_0_objectid}) supérieur à l'horaire d'arrivé %{reference_value} à l'arrêt suivant"        description: "L'horaire d'arrivée à un arrêt doit être antérieur à l'horaire de départ de cet arrêt ET les horaires de départ aux arrêts doivent être dans l'ordre chronologique croissant." +      prerequisite: "Aucun"      routing_constraint_zone_control/unactivated_stop_point:        messages:          3_routingconstraint_1: "L'ITL %{source_objectid} référence un arrêt (ZDEp) désactivé %{target_0_label} (%{target_0_objectid})"        description: "Les arrêts d'une ITL ne doivent pas être désactivés" +      prerequisite: "Aucun"      routing_constraint_zone_control/maximum_length:        messages:          3_routingconstraint_2: "L'ITL %{source_objectid} couvre tous les arrêts de l'itinéraire %{target_0_objectid}."        description: "Une ITL ne peut pas couvrir l'ensemble des arrêts de l'itinéraire" +      prerequisite: "Aucun"      routing_constraint_zone_control/minimum_length:        messages:          3_routingconstraint_3: "L'ITL %{source_objectid} n'a pas suffisament d'arrêts (minimum 2 arrêts requis)"        description: "Une ITL doit référencer au moins 2 arrêts" +      prerequisite: "Aucun"      line_control/route:        messages:          3_line_1: "Sur la ligne %{source_label} (%{source_objectid}), aucun itinéraire n'a d'itinéraire inverse" @@ -128,15 +143,18 @@ fr:        messages:          3_generic_1: "%{source_objectid} : l'attribut %{source_attribute} a une valeur %{error_value} qui ne respecte pas le motif %{reference_value}"        description: "l'attribut de l'objet doit respecter un motif (expression régulière)" +      prerequisite: "Aucun"      generic_attribute_control/min_max:        messages:          3_generic_2_1: "%{source_objectid} : l'attribut %{source_attribute} a une valeur %{error_value} supérieure à la valeur maximale autorisée %{reference_value}"          3_generic_2_2: "%{source_objectid} : l'attribut %{source_attribute} a une valeur %{error_value} inférieure à la valeur minimale autorisée %{reference_value}"        description: "La valeur numérique de l'attribut doit rester comprise entre 2 valeurs" +      prerequisite: "Aucun"      generic_attribute_control/uniqueness:        messages:          3_generic_3: "%{source_objectid} : l'attribut %{source_attribute} a une valeur %{error_value} partagée avec %{target_0_objectid}"        description: "La valeur de l'attribut doit être unique au sein des objets de la ligne" +      prerequisite: "Aucun"      shape_control:        3_shape_1: "Tracé %{source_objectid} : le tracé passe trop loin de l'arrêt %{target_0_label} (%{target_0_objectid}) : %{error_value} > %{reference_value}"        3_shape_2: "Tracé %{source_objectid} : le tracé n'est pas défini entre les arrêts %{target_0_label} (%{target_0_objectid}) et %{target_1_label} (%{target_1_objectid})" diff --git a/config/locales/stop_areas.en.yml b/config/locales/stop_areas.en.yml index c1ced1094..ac3dce280 100644 --- a/config/locales/stop_areas.en.yml +++ b/config/locales/stop_areas.en.yml @@ -66,6 +66,9 @@ en:        access_managment: "Access Points and Links managment"        access_points: "Access Points"        not_editable: "Le type d'arrêt est non modifiable" +      state: +        active: Active +        deactivated: Deactivated      genealogical:        genealogical: "Links between stop area"        genealogical_routing: "Routing constraint's links" @@ -139,6 +142,7 @@ en:          created_at: Created at          updated_at: Updated at          waiting_time: Waiting time (minutes) +        state: State    formtastic:      titles:        stop_area: diff --git a/config/locales/stop_areas.fr.yml b/config/locales/stop_areas.fr.yml index ede1aada6..f75c4ebe7 100644 --- a/config/locales/stop_areas.fr.yml +++ b/config/locales/stop_areas.fr.yml @@ -67,6 +67,9 @@ fr:        access_managment: "Gestion des accès et liens associés"        access_points: "Points d'accès"        not_editable: "Le type d'arrêt est non modifiable" +      state: +        active: Actif +        deactivated: Désactivé      genealogical:        genealogical: "Lien entre arrêts"        genealogical_routing: "Liens de l'ITL" @@ -141,6 +144,7 @@ fr:          created_at: "Créé le"          updated_at: "Edité le"          waiting_time: Temps de desserte (minutes) +        state: État    formtastic:      titles:        stop_area: diff --git a/db/migrate/20180227151937_add_compliance_control_name_to_compliance_checks.rb b/db/migrate/20180227151937_add_compliance_control_name_to_compliance_checks.rb new file mode 100644 index 000000000..49ab0da11 --- /dev/null +++ b/db/migrate/20180227151937_add_compliance_control_name_to_compliance_checks.rb @@ -0,0 +1,5 @@ +class AddComplianceControlNameToComplianceChecks < ActiveRecord::Migration +  def change +    add_column :compliance_checks, :compliance_control_name, :string +  end +end diff --git a/db/schema.rb b/db/schema.rb index c709290f5..045fc658d 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -11,12 +11,12 @@  #  # It's strongly recommended that you check this file into your version control system. -ActiveRecord::Schema.define(version: 20180202170009) do +ActiveRecord::Schema.define(version: 20180227151937) do    # These are extensions that must be enabled in order to support this database    enable_extension "plpgsql" -  enable_extension "hstore"    enable_extension "postgis" +  enable_extension "hstore"    enable_extension "unaccent"    create_table "access_links", id: :bigserial, force: :cascade do |t| @@ -90,9 +90,9 @@ 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 +    t.integer   "workgroup_id",    limit: 8    end    add_index "calendars", ["organisation_id"], name: "index_calendars_on_organisation_id", using: :btree @@ -119,7 +119,6 @@ ActiveRecord::Schema.define(version: 20180202170009) do      t.datetime "updated_at"      t.date     "end_date"      t.string   "date_type" -    t.string   "mode"    end    add_index "clean_ups", ["referential_id"], name: "index_clean_ups_on_referential_id", using: :btree @@ -221,6 +220,7 @@ ActiveRecord::Schema.define(version: 20180202170009) do      t.datetime "created_at",                          null: false      t.datetime "updated_at",                          null: false      t.string   "origin_code" +    t.string   "compliance_control_name"    end    add_index "compliance_checks", ["compliance_check_block_id"], name: "index_compliance_checks_on_compliance_check_block_id", using: :btree @@ -421,9 +421,9 @@ ActiveRecord::Schema.define(version: 20180202170009) do      t.string   "type"      t.integer  "parent_id",             limit: 8      t.string   "parent_type" +    t.datetime "notified_parent_at"      t.integer  "current_step",                    default: 0      t.integer  "total_steps",                     default: 0 -    t.datetime "notified_parent_at"      t.string   "creator"    end diff --git a/lib/compliance_control_set_copier.rb b/lib/compliance_control_set_copier.rb index 58d40cdbf..06622f302 100644 --- a/lib/compliance_control_set_copier.rb +++ b/lib/compliance_control_set_copier.rb @@ -21,7 +21,7 @@ class ComplianceControlSetCopier    # Workers    # -------    def check_organisation_coherence! -    return true if cc_set.organisation_id == referential.organisation_id  +    return true if cc_set.organisation_id == referential.organisation_id      raise ArgumentError, "Incoherent organisation of referential"    end @@ -66,7 +66,8 @@ class ComplianceControlSetCopier        name: name_with_refid(compliance_control.name),        comment: compliance_control.comment,        code: compliance_control.code, -      origin_code: compliance_control.origin_code +      origin_code: compliance_control.origin_code, +      compliance_control_name: compliance_control.class.name      ).tap do | compliance_check |        control_id_to_check.update compliance_control.id => compliance_check      end diff --git a/lib/stif/dashboard.rb b/lib/stif/dashboard.rb index 46c635091..2f5792547 100644 --- a/lib/stif/dashboard.rb +++ b/lib/stif/dashboard.rb @@ -1,7 +1,7 @@  module Stif    class Dashboard < ::Dashboard      def workbench -      @workbench ||= current_organisation.workbenches.find_by(name: "Gestion de l'offre") +      @workbench ||= current_organisation.workbenches.default      end      def workgroup diff --git a/lib/tasks/seeds.rake b/lib/tasks/seeds.rake new file mode 100644 index 000000000..9038b64f9 --- /dev/null +++ b/lib/tasks/seeds.rake @@ -0,0 +1,19 @@ +namespace :db do + +  include Seedbank::DSL + +  base_dependencies   = ['db:seed:original'] +  override_dependency = ['db:seed:common'] + +  namespace :seed do +    seeds_environment = ENV.fetch("SEED_ENV", Rails.env) +    glob_seed_files_matching('/*/').each do |directory| +      environment = File.basename(directory) +      override_dependency << "db:seed:#{environment}" if defined?(Rails) && seeds_environment == environment +    end +  end + +  # Override db:seed to run all the common and environments seeds plus the original db:seed. +  desc 'Load the seed data from db/seeds.rb, db/seeds/*.seeds.rb and db/seeds/ENVIRONMENT/*.seeds.rb. ENVIRONMENT is the env var SEED_ENV or the current environment in Rails.env.' +  override_seed_task :seed => override_dependency +end diff --git a/spec/controllers/line_referentials_controller_spec.rb b/spec/controllers/line_referentials_controller_spec.rb index 17ffb670d..8e8d48fda 100644 --- a/spec/controllers/line_referentials_controller_spec.rb +++ b/spec/controllers/line_referentials_controller_spec.rb @@ -6,8 +6,8 @@ RSpec.describe LineReferentialsController, :type => :controller do    describe 'PUT sync' do      let(:request){ put :sync, id: line_referential.id } -    it 'should redirect to 403' do -       expect(request).to redirect_to "/403" +    it 'should respond with 403' do +       expect(request).to have_http_status 403      end      with_permission "line_referentials.synchronize" do diff --git a/spec/controllers/lines_controller_spec.rb b/spec/controllers/lines_controller_spec.rb index 65fe88b96..96f49bb36 100644 --- a/spec/controllers/lines_controller_spec.rb +++ b/spec/controllers/lines_controller_spec.rb @@ -7,8 +7,8 @@ RSpec.describe LinesController, :type => :controller do    describe 'PUT deactivate' do      let(:request){ put :deactivate, id: line.id, line_referential_id: line_referential.id } -    it 'should redirect to 403' do -      expect(request).to redirect_to "/403" +    it 'should respond with 403' do +      expect(request).to have_http_status 403      end      with_permission "lines.change_status" do @@ -24,8 +24,8 @@ RSpec.describe LinesController, :type => :controller do      before(:each){        line.deactivate!      } -    it 'should redirect to 403' do -       expect(request).to redirect_to "/403" +    it 'should respond with 403' do +      expect(request).to have_http_status 403      end      with_permission "lines.change_status" do diff --git a/spec/controllers/referentials_controller_spec.rb b/spec/controllers/referentials_controller_spec.rb index 5e0b1e505..ff450c905 100644 --- a/spec/controllers/referentials_controller_spec.rb +++ b/spec/controllers/referentials_controller_spec.rb @@ -6,6 +6,42 @@ describe ReferentialsController, :type => :controller do    let(:organisation) { create :organisation }    let(:other_referential) { create :referential, organisation: organisation } +  describe "GET new" do +    let(:request){ get :new, workbench_id: referential.workbench_id } +    before{ request } + +    it 'returns http success' do +      expect(response).to have_http_status(200) +    end + +    context "when cloning another referential" do +      let(:source){ referential } +      let(:request){ get :new, workbench_id: referential.workbench_id, from: source.id } + +      it 'returns http success' do +        expect(response).to have_http_status(200) +      end + +      context "when the referential is in another organisation but accessible by the user" do +        let(:source){ create(:workbench_referential) } +        before do +          source.workbench.update_attribute :workgroup_id, referential.workbench.workgroup_id +        end + +        it 'returns http forbidden' do +          expect(response).to have_http_status(403) +        end +      end + +      context "when the referential is not accessible by the user" do +        let(:source){ create(:workbench_referential) } +        it 'returns http forbidden' do +          expect(response).to have_http_status(403) +        end +      end +    end +  end +    describe 'PUT archive' do      context "user's organisation matches referential's organisation" do        it 'returns http success' do diff --git a/spec/controllers/stop_area_referentials_controller_spec.rb b/spec/controllers/stop_area_referentials_controller_spec.rb index 384323334..737ef631f 100644 --- a/spec/controllers/stop_area_referentials_controller_spec.rb +++ b/spec/controllers/stop_area_referentials_controller_spec.rb @@ -6,7 +6,9 @@ RSpec.describe StopAreaReferentialsController, :type => :controller do    describe 'PUT sync' do      let(:request){ put :sync, id: stop_area_referential.id } -    it { expect(request).to redirect_to "/403" } +    it 'should respond with 403' do +      expect(request).to have_http_status 403 +    end      with_permission "stop_area_referentials.synchronize" do        it 'returns HTTP success' do diff --git a/spec/controllers/stop_areas_controller_spec.rb b/spec/controllers/stop_areas_controller_spec.rb index 23bca3c36..f39ac5776 100644 --- a/spec/controllers/stop_areas_controller_spec.rb +++ b/spec/controllers/stop_areas_controller_spec.rb @@ -7,8 +7,8 @@ RSpec.describe StopAreasController, :type => :controller do    describe 'PUT deactivate' do      let(:request){ put :deactivate, id: stop_area.id, stop_area_referential_id: stop_area_referential.id } -    it 'should redirect to 403' do -       expect(request).to redirect_to "/403" +    it 'should respond with 403' do +      expect(request).to have_http_status 403      end      with_permission "stop_areas.change_status" do @@ -24,8 +24,8 @@ RSpec.describe StopAreasController, :type => :controller do      before(:each){        stop_area.deactivate!      } -    it 'should redirect to 403' do -       expect(request).to redirect_to "/403" +    it 'should respond with 403' do +      expect(request).to have_http_status 403      end      with_permission "stop_areas.change_status" do diff --git a/spec/factories/custom_fields.rb b/spec/factories/custom_fields.rb index 2f5fae555..7c43a6147 100644 --- a/spec/factories/custom_fields.rb +++ b/spec/factories/custom_fields.rb @@ -4,6 +4,6 @@ FactoryGirl.define do      resource_type "VehicleJourney"      sequence(:name){|n| "custom field ##{n}"}      field_type "list" -    options( { "capacity" => "0" } ) +    options( { capacity: "0" } )    end  end diff --git a/spec/helpers/table_builder_helper_spec.rb b/spec/helpers/table_builder_helper_spec.rb index 478875118..b0c17859f 100644 --- a/spec/helpers/table_builder_helper_spec.rb +++ b/spec/helpers/table_builder_helper_spec.rb @@ -441,6 +441,53 @@ describe TableBuilderHelper, type: :helper do          allow(helper).to receive(:mutual_workbench).and_return(referential.workbench)        } +      context "with a condition" do +        let(:columns){ +          [ +            TableBuilderHelper::Column.new( +              key: :name, +              attribute: 'name', +              if: condition +            ), +          ] +        } + +        context "when the condition is true" do +          let(:condition){ ->(obj){true} } +          it "should show the value" do +            items.each do |i| +              tr = helper.send(:tr, i, columns, selectable, links, overhead, model_name, :index) +              klass = "#{TableBuilderHelper.item_row_class_name([referential])}-#{i.id}" +              expect(tr).to include(i.name) +            end +          end +        end + +        context "when the condition depends on the object" do +          let(:condition){ ->(obj){ obj == referential } } +          it "should show the value accordingly" do +            tr = helper.send(:tr, item, columns, selectable, links, overhead, model_name, :index) +            klass = "#{TableBuilderHelper.item_row_class_name([referential])}-#{referential.id}" +            expect(tr).to include(referential.name) +            tr = helper.send(:tr, other_item, columns, selectable, links, overhead, model_name, :index) +            klass = "#{TableBuilderHelper.item_row_class_name([referential])}-#{other_referential.id}" +            expect(tr).to_not include(other_referential.name) +          end +        end + +        context "when the condition is false" do +          let(:condition){ ->(obj){false} } +          it "should not show the value" do +            items.each do |i| +              tr = helper.send(:tr, i, columns, selectable, links, overhead, model_name, :index) +              klass = "#{TableBuilderHelper.item_row_class_name([referential])}-#{i.id}" +              expect(tr).to_not include(i.name) +            end +          end +        end + +      end +        context "with all rows non-selectable" do          let(:selectable){ false }          it "sets all rows as non selectable" do diff --git a/spec/models/chouette/vehicle_journey_spec.rb b/spec/models/chouette/vehicle_journey_spec.rb index 76e73d9cf..c69655bd4 100644 --- a/spec/models/chouette/vehicle_journey_spec.rb +++ b/spec/models/chouette/vehicle_journey_spec.rb @@ -260,7 +260,7 @@ describe Chouette::VehicleJourney, :type => :model do          item['purchase_windows']         = []          item['footnotes']                = []          item['purchase_windows']         = [] -        item['custom_fields']            = vj.custom_fields +        item['custom_fields']            = vj.custom_fields.to_hash          vj.vehicle_journey_at_stops.each do |vjas|            item['vehicle_journey_at_stops'] << vehicle_journey_at_stop_to_state(vjas) @@ -282,7 +282,6 @@ describe Chouette::VehicleJourney, :type => :model do          Chouette::VehicleJourney.state_update(route, collection)        }.to change {Chouette::VehicleJourney.count}.by(1) -        obj = Chouette::VehicleJourney.last        expect(obj).to receive(:after_commit_objectid).and_call_original @@ -292,7 +291,7 @@ describe Chouette::VehicleJourney, :type => :model do        expect(collection.last['objectid']).to eq obj.objectid        expect(obj.published_journey_name).to eq 'dummy' -      expect(obj.custom_fields["energy"]["value"]).to eq 99 +      expect(obj.custom_fields["energy"].value).to eq 99      end      it 'should expect local times' do diff --git a/spec/models/custom_field_spec.rb b/spec/models/custom_field_spec.rb index 51128b0a2..b92bcfbdb 100644 --- a/spec/models/custom_field_spec.rb +++ b/spec/models/custom_field_spec.rb @@ -16,7 +16,6 @@ RSpec.describe CustomField, type: :model do      end    end -    context "custom fields for a resource" do      let!( :fields ){ [create(:custom_field), create(:custom_field, code: :energy)] }      let!( :instance_fields ){ @@ -26,10 +25,49 @@ RSpec.describe CustomField, type: :model do        }      }      it { expect(Chouette::VehicleJourney.custom_fields).to eq(fields) } -    it { expect(vj.custom_fields).to eq(instance_fields) } +    it { +      instance_fields.each do |code, cf| +        cf.each do |k, v| +          expect(vj.custom_fields[code].send(k)).to eq(v) +        end +      end +    }    end    context "custom field_values for a resource" do      it { expect(vj.custom_field_value("energy")).to eq(99) }    end + +  context "with an 'integer' field_type" do +    let!(:field){ [create(:custom_field, code: :energy, options: {field_type: 'integer'})] } +    let!( :vj ){ create :vehicle_journey, custom_field_values: {energy: "99"} } +    it "should cast the value" do +      expect(vj.custom_fields[:energy].value).to eq 99 +    end + +    it "should validate the value" do +      { +        "99" => true, +        "azerty" => false, +        "91a" => false, +        "a91" => false +      }.each do |val, valid| +        vj = build :vehicle_journey, custom_field_values: {energy: val} +        if valid +          expect(vj.validate).to be_truthy +        else +          expect(vj.validate).to be_falsy +          expect(vj.errors.messages[:"custom_fields.energy"]).to be_present +        end +      end +    end +  end + +  context "with a 'string' field_type" do +    let!(:field){ [create(:custom_field, code: :energy, options: {field_type: 'string'})] } +    let!( :vj ){ create :vehicle_journey, custom_field_values: {energy: 99} } +    it "should cast the value" do +      expect(vj.custom_fields[:energy].value).to eq '99' +    end +  end  end diff --git a/spec/models/referential_spec.rb b/spec/models/referential_spec.rb index 025ad80f9..1d9b3d78a 100644 --- a/spec/models/referential_spec.rb +++ b/spec/models/referential_spec.rb @@ -58,16 +58,21 @@ describe Referential, :type => :model do        Referential.new_from(ref, [])      end -    # let(:saved_clone) do -    #   clone.tap do |clone| -    #     clone.organisation = ref.organisation -    #     clone.metadatas.each do |metadata| -    #       metadata.line_ids = ref.lines.where(id: clone.line_ids, objectid: JSON.parse(ref.organisation.sso_attributes["functional_scope"]).collect(&:id) -    #       metadata.periodes = metadata.periodes.map { |period| Range.new(period.end+1, period.end+10) } -    #     end -    #     clone.save! -    #   end -    # end +    let!(:workbench){ create :workbench } + +    let(:saved_clone) do +      clone.tap do |clone| +        clone.organisation = workbench.organisation +        clone.workbench = workbench +        clone.metadatas = [create(:referential_metadata, referential: clone)] +        clone.save! +      end +    end + +    it 'should create a Referential' do +      ref +      expect { saved_clone }.to change{Referential.count}.by(1) +    end      xit 'should create a ReferentialCloning' do        expect { saved_clone }.to change{ReferentialCloning.count}.by(1) diff --git a/spec/views/referentials/show.html.erb_spec.rb b/spec/views/referentials/show.html.erb_spec.rb index a7f37d180..82328cb8e 100644 --- a/spec/views/referentials/show.html.erb_spec.rb +++ b/spec/views/referentials/show.html.erb_spec.rb @@ -11,6 +11,7 @@ describe "referentials/show", type: :view do    let(:organisation){ referential.try(:organisation) }    let(:permissions){ [] }    let(:current_organisation) { organisation } +  let(:organisation) { referential.organisation }    let(:readonly){ false }    before :each do | 
