diff options
89 files changed, 1143 insertions, 434 deletions
diff --git a/.gitignore b/.gitignore index dd4d057ef..28960565b 100644 --- a/.gitignore +++ b/.gitignore @@ -12,6 +12,7 @@ # Ignore all logfiles and tempfiles. /log/*.log +/log/importers /tmp *~ public/assets/ @@ -66,7 +66,6 @@ end gem 'activerecord-postgis-adapter', "~> 3.0.0" gem 'polylines' -gem 'activerecord-nulldb-adapter', require: false # Codifligne API gem 'codifligne', af83: 'stif-codifline-api' @@ -136,13 +135,12 @@ gem 'rabl' gem 'carrierwave', '~> 1.0' gem 'sidekiq' -gem 'sinatra' gem 'whenever', github: 'af83/whenever', require: false # '~> 0.9' gem 'rake' gem 'devise-async' gem 'apartment', '~> 1.0.0' gem 'aasm' -gem 'activerecord-nulldb-adapter' +gem 'activerecord-nulldb-adapter' if ENV['RAILS_DB_ADAPTER'] == 'nulldb' gem 'puma', '~> 3.10.0' gem 'newrelic_rpm' diff --git a/Gemfile.lock b/Gemfile.lock index 046167e69..090886f3d 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -76,8 +76,6 @@ GEM activemodel (= 4.2.8) activesupport (= 4.2.8) arel (~> 6.0) - activerecord-nulldb-adapter (0.3.7) - activerecord (>= 2.0.0) activerecord-postgis-adapter (3.0.0) activerecord (~> 4.2) rgeo-activerecord (~> 4.0) @@ -370,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 @@ -511,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) @@ -597,7 +591,6 @@ DEPENDENCIES SyslogLogger aasm active_attr - activerecord-nulldb-adapter activerecord-postgis-adapter (~> 3.0.0) acts-as-taggable-on (~> 4.0.0) acts_as_list (~> 0.6.0) @@ -703,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/javascripts/select2.coffee b/app/assets/javascripts/select2.coffee index 05b73dc6b..f34b5d5ce 100644 --- a/app/assets/javascripts/select2.coffee +++ b/app/assets/javascripts/select2.coffee @@ -4,7 +4,7 @@ bind_select2 = (el, cfg = {}) -> theme: 'bootstrap' language: 'fr' placeholder: target.data('select2ed-placeholder') - allowClear: false + allowClear: !!target.data('select2ed-allow-clear') target.select2 $.extend({}, default_cfg, cfg) 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/_forms.sass b/app/assets/stylesheets/components/_forms.sass index caa8ac0e4..b13c5fc23 100644 --- a/app/assets/stylesheets/components/_forms.sass +++ b/app/assets/stylesheets/components/_forms.sass @@ -601,6 +601,9 @@ table, .table white-space: nowrap overflow: auto + &.stop-areas + min-width: 250px + > .filter_menu-item display: block margin: 0 diff --git a/app/assets/stylesheets/components/_main_nav.sass b/app/assets/stylesheets/components/_main_nav.sass index 2af070389..8e164fa01 100644 --- a/app/assets/stylesheets/components/_main_nav.sass +++ b/app/assets/stylesheets/components/_main_nav.sass @@ -375,3 +375,16 @@ $menuW: 300px + .btn margin-left: 10px + + .languages + .dropdown-menu + top: 0 + right: 0 + left: auto + white-space: nowrap + line-height: 11px + min-width: 0 + li + display: inline-block + a + padding: 2px 10px 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/autocomplete_time_tables_controller.rb b/app/controllers/autocomplete_time_tables_controller.rb index 0a9f51ced..dded24a36 100644 --- a/app/controllers/autocomplete_time_tables_controller.rb +++ b/app/controllers/autocomplete_time_tables_controller.rb @@ -23,7 +23,7 @@ class AutocompleteTimeTablesController < ChouetteController end def split_params! search - params[:q][search] = params[:q][search].split(" ") if params[:q][search] + params[:q][search] = params[:q][search].split(" ") if params[:q] && params[:q][search] end def collection 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/concerns/feature_checker.rb b/app/controllers/concerns/feature_checker.rb index 9ca5ed0a7..5e102ef1b 100644 --- a/app/controllers/concerns/feature_checker.rb +++ b/app/controllers/concerns/feature_checker.rb @@ -27,6 +27,8 @@ module FeatureChecker protected def has_feature?(*features) + return false unless current_organisation + features.all? do |feature| current_organisation.has_feature? feature 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/referential_vehicle_journeys_controller.rb b/app/controllers/referential_vehicle_journeys_controller.rb index e36ef8153..b07d6c600 100644 --- a/app/controllers/referential_vehicle_journeys_controller.rb +++ b/app/controllers/referential_vehicle_journeys_controller.rb @@ -19,14 +19,24 @@ class ReferentialVehicleJourneysController < ChouetteController @q = @q.with_stop_area_ids(params[:q][:stop_area_ids]) if params[:q] && params[:q][:stop_area_ids] @q = ransack_period_range(scope: @q, error_message: t('vehicle_journeys.errors.purchase_window'), query: :in_purchase_window, prefix: :purchase_window) @q = ransack_period_range(scope: @q, error_message: t('vehicle_journeys.errors.time_table'), query: :with_matching_timetable, prefix: :time_table) + @starting_stop = params[:q] && params[:q][:stop_areas] && params[:q][:stop_areas][:start].present? ? Chouette::StopArea.find(params[:q][:stop_areas][:start]) : nil + @ending_stop = params[:q] && params[:q][:stop_areas] && params[:q][:stop_areas][:end].present? ? Chouette::StopArea.find(params[:q][:stop_areas][:end]) : nil + + if @starting_stop + @q = + unless @ending_stop + @q.with_stop_area_id(@starting_stop.id) + else + @q.with_ordered_stop_area_ids(@starting_stop.id, @ending_stop.id) + end + end + @q = @q.ransack(params[:q]) @vehicle_journeys ||= @q.result @vehicle_journeys = parse_order @vehicle_journeys @vehicle_journeys = @vehicle_journeys.paginate page: params[:page], per_page: params[:per_page] || 10 @all_companies = Chouette::Company.where("id IN (#{@referential.vehicle_journeys.select(:company_id).to_sql})").distinct - @all_stop_areas = Chouette::StopArea.where("id IN (#{@referential.vehicle_journeys.joins(:stop_areas).select("stop_areas.id").to_sql})").distinct - stop_area_ids = params[:q].try(:[], :stop_area_ids).try(:select, &:present?) - @filters_stop_areas = Chouette::StopArea.find(stop_area_ids) if stop_area_ids.present? && stop_area_ids.size <= 2 + end def parse_order scope 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/pagination_helper.rb b/app/helpers/pagination_helper.rb index 9b6042377..639d19de6 100644 --- a/app/helpers/pagination_helper.rb +++ b/app/helpers/pagination_helper.rb @@ -25,7 +25,7 @@ module PaginationHelper if collection.total_pages > 1 links = content_tag :div, '', class: 'page_links' do - will_paginate collection, container: false, page_links: false, previous_label: '', next_label: '', param_name: collection.try(:pagination_param_name) + will_paginate collection, container: false, page_links: false, previous_label: '', next_label: '', param_name: (collection.try(:pagination_param_name) || "page") end content_tag :div, pinfos.concat(links).html_safe, class: "pagination #{cls}" 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 b4c569882..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 @@ -23,7 +25,7 @@ module TableBuilderHelper end def header_label(model = nil) - return @name unless @name.empty? + return @name if @name.present? # Transform `Chouette::Line` into "line" model_key = model.to_s.demodulize.underscore @@ -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 b398d78fa..e67753e4b 100644 --- a/app/javascript/vehicle_journeys/actions/index.js +++ b/app/javascript/vehicle_journeys/actions/index.js @@ -92,7 +92,9 @@ const actions = { id: selectedTT.id, comment: selectedTT.comment, objectid: selectedTT.objectid, - color: selectedTT.color + color: selectedTT.color, + bounding_dates: selectedTT.time_table_bounding, + days: selectedTT.day_types } }), addSelectedTimetable: () => ({ diff --git a/app/javascript/vehicle_journeys/components/Filters.js b/app/javascript/vehicle_journeys/components/Filters.js index f8697c930..ae3ab3476 100644 --- a/app/javascript/vehicle_journeys/components/Filters.js +++ b/app/javascript/vehicle_journeys/components/Filters.js @@ -36,7 +36,7 @@ export default function Filters({filters, pagination, missions, onFilter, onRese onSelect2Timetable={onSelect2Timetable} hasRoute={true} chunkURL={("/autocomplete_time_tables.json?route_id=" + String(window.route_id))} - searchKey={"comment_or_objectid_cont_any"} + searchKey={"unaccented_comment_or_objectid_cont_any"} filters={filters} isFilter={true} /> diff --git a/app/javascript/vehicle_journeys/components/VehicleJourney.js b/app/javascript/vehicle_journeys/components/VehicleJourney.js index e11e91497..7db0cee1c 100644 --- a/app/javascript/vehicle_journeys/components/VehicleJourney.js +++ b/app/javascript/vehicle_journeys/components/VehicleJourney.js @@ -66,6 +66,7 @@ export default class VehicleJourney extends Component { render() { this.previousCity = undefined let detailed_calendars = this.hasFeature('detailed_calendars') && !this.disabled + let detailed_calendars_shown = $('.detailed-timetables-bt').hasClass('active') let {time_tables, purchase_windows} = this.props.value return ( @@ -110,7 +111,7 @@ export default class VehicleJourney extends Component { {this.props.disabled && <VehicleJourneyInfoButton vehicleJourney={this.props.value} />} { detailed_calendars && - <div className="detailed-timetables hidden"> + <div className={"detailed-timetables" + (detailed_calendars_shown ? "" : " hidden")}> {this.props.allTimeTables.map((tt, i) => <div key={i} className={(this.hasTimeTable(time_tables, tt) ? "active" : "inactive")}></div> )} diff --git a/app/javascript/vehicle_journeys/components/VehicleJourneys.js b/app/javascript/vehicle_journeys/components/VehicleJourneys.js index 843aec1a8..384afba17 100644 --- a/app/javascript/vehicle_journeys/components/VehicleJourneys.js +++ b/app/javascript/vehicle_journeys/components/VehicleJourneys.js @@ -187,7 +187,7 @@ export default class VehicleJourneys extends Component { <p> {this.timeTableURL(tt)} </p> - <p>{tt.bounding_dates}</p> + <p>{tt.bounding_dates.split(' ').join(' > ')}</p> </div> )} </div> diff --git a/app/javascript/vehicle_journeys/components/tools/CreateModal.js b/app/javascript/vehicle_journeys/components/tools/CreateModal.js index 8536f66e6..24d9a23c2 100644 --- a/app/javascript/vehicle_journeys/components/tools/CreateModal.js +++ b/app/javascript/vehicle_journeys/components/tools/CreateModal.js @@ -117,6 +117,11 @@ export default class CreateModal extends Component { className='form-control' onKeyDown={(e) => actions.resetValidation(e.currentTarget)} /> + <input + type='hidden' + ref='tz_offset' + value={new Date().getTimezoneOffset()} + /> </div> </div> </div> diff --git a/app/javascript/vehicle_journeys/components/tools/TimetablesEditVehicleJourney.js b/app/javascript/vehicle_journeys/components/tools/TimetablesEditVehicleJourney.js index e2fcd27d5..f21480563 100644 --- a/app/javascript/vehicle_journeys/components/tools/TimetablesEditVehicleJourney.js +++ b/app/javascript/vehicle_journeys/components/tools/TimetablesEditVehicleJourney.js @@ -99,7 +99,7 @@ export default class TimetablesEditVehicleJourney extends Component { <TimetableSelect2 onSelect2Timetable={this.props.onSelect2Timetable} chunkURL={'/autocomplete_time_tables.json'} - searchKey={"comment_or_objectid_cont_any"} + searchKey={"unaccented_comment_or_objectid_cont_any"} isFilter={false} /> </div> diff --git a/app/javascript/vehicle_journeys/reducers/vehicleJourneys.js b/app/javascript/vehicle_journeys/reducers/vehicleJourneys.js index 383dea4a0..8705b3cf2 100644 --- a/app/javascript/vehicle_journeys/reducers/vehicleJourneys.js +++ b/app/javascript/vehicle_journeys/reducers/vehicleJourneys.js @@ -14,47 +14,73 @@ const vehicleJourney= (state = {}, action, keep) => { hour: 0, minute: 0 } - if(action.data["start_time.hour"] && action.data["start_time.minute"] && action.selectedJourneyPattern.full_schedule){ - current_time.hour = parseInt(action.data["start_time.hour"].value) - current_time.minute = parseInt(action.data["start_time.minute"].value) || 0 + let computeSchedule = false + let userTZOffet = 0 + if(action.data["start_time.hour"] && action.data["start_time.hour"].value && action.data["start_time.hour"].value.length > 0 && action.data["start_time.minute"] && action.selectedJourneyPattern.full_schedule && action.selectedJourneyPattern.costs){ + computeSchedule = true + userTZOffet = action.data["tz_offset"] && parseInt(action.data["tz_offset"].value) || 0 + current_time.hour = parseInt(action.data["start_time.hour"].value) + parseInt(userTZOffet / 60) + current_time.minute = 0 + if(action.data["start_time.minute"].value){ + current_time.minute = parseInt(action.data["start_time.minute"].value) + (userTZOffet - 60 * parseInt(userTZOffet / 60)) + } } _.each(action.stopPointsList, (sp) =>{ let inJourney = false - if(action.selectedJourneyPattern.full_schedule && action.selectedJourneyPattern.costs && action.selectedJourneyPattern.costs[prevSp.stop_area_id + "-" + sp.stop_area_id]){ - let delta = parseInt(action.selectedJourneyPattern.costs[prevSp.stop_area_id + "-" + sp.stop_area_id].time) - current_time = actions.addMinutesToTime(current_time, delta) - prevSp = sp - inJourney = true - } - let offsetHours = sp.time_zone_offset / 3600 - let offsetminutes = sp.time_zone_offset/60 - 60*offsetHours - let newVjas = { - delta: 0, - arrival_time:{ - hour: (24 + current_time.hour + offsetHours) % 24, - minute: current_time.minute + offsetminutes - }, - stop_point_objectid: sp.object_id, - stop_area_cityname: sp.city_name, - dummy: true - } + let newVjas + if(computeSchedule){ + if(action.selectedJourneyPattern.costs[prevSp.stop_area_id + "-" + sp.stop_area_id]){ + let delta = parseInt(action.selectedJourneyPattern.costs[prevSp.stop_area_id + "-" + sp.stop_area_id].time) + current_time = actions.addMinutesToTime(current_time, delta) + prevSp = sp + inJourney = true + } + let offsetHours = sp.time_zone_offset / 3600 + let offsetminutes = sp.time_zone_offset/60 - 60*offsetHours + newVjas = { + delta: 0, + arrival_time:{ + hour: (24 + current_time.hour + offsetHours) % 24, + minute: current_time.minute + offsetminutes + }, + stop_point_objectid: sp.object_id, + stop_area_cityname: sp.city_name, + dummy: true + } - if(sp.waiting_time && inJourney){ - current_time = actions.addMinutesToTime(current_time, parseInt(sp.waiting_time)) - } + if(sp.waiting_time && inJourney){ + current_time = actions.addMinutesToTime(current_time, parseInt(sp.waiting_time)) + } - newVjas.departure_time = { - hour: (24 + current_time.hour + offsetHours) % 24, - minute: current_time.minute + offsetminutes - } + newVjas.departure_time = { + hour: (24 + current_time.hour + offsetHours) % 24, + minute: current_time.minute + offsetminutes + } - if(current_time.hour + offsetHours > 24){ - newVjas.departure_day_offset = 1 - newVjas.arrival_day_offset = 1 + if(current_time.hour + offsetHours > 24){ + newVjas.departure_day_offset = 1 + newVjas.arrival_day_offset = 1 + } + if(current_time.hour + offsetHours < 0){ + newVjas.departure_day_offset = -1 + newVjas.arrival_day_offset = -1 + } } - if(current_time.hour + offsetHours < 0){ - newVjas.departure_day_offset = -1 - newVjas.arrival_day_offset = -1 + else{ + newVjas = { + delta: 0, + arrival_time: { + hour: 0, + minute: 0 + }, + departure_time: { + hour: 0, + minute: 0 + }, + stop_point_objectid: sp.object_id, + stop_area_cityname: sp.city_name, + dummy: true + } } _.each(action.selectedJourneyPattern.stop_areas, (jp) =>{ diff --git a/app/models/chouette/time_table.rb b/app/models/chouette/time_table.rb index b76de852a..20d6e69ac 100644 --- a/app/models/chouette/time_table.rb +++ b/app/models/chouette/time_table.rb @@ -17,6 +17,10 @@ module Chouette (column_names + ['tag_search']) + _ransackers.keys end + ransacker :unaccented_comment, formatter: ->(val){ val.parameterize } do + Arel.sql('unaccent(comment)') + end + has_and_belongs_to_many :vehicle_journeys, :class_name => 'Chouette::VehicleJourney' has_many :dates, -> {order(:date)}, inverse_of: :time_table, :validate => :true, :class_name => "Chouette::TimeTableDate", :dependent => :destroy diff --git a/app/models/chouette/time_table_date.rb b/app/models/chouette/time_table_date.rb index b3b2fd561..98d8fa765 100644 --- a/app/models/chouette/time_table_date.rb +++ b/app/models/chouette/time_table_date.rb @@ -20,4 +20,4 @@ module Chouette self.slice(*attrs).values end end -end
\ No newline at end of file +end diff --git a/app/models/chouette/vehicle_journey.rb b/app/models/chouette/vehicle_journey.rb index 1a79db823..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 @@ -52,10 +53,48 @@ module Chouette end } + scope :with_stop_area_id, ->(id){ + if id.present? + joins(journey_pattern: :stop_points).where('stop_points.stop_area_id = ?', id) + else + all + end + } + + scope :with_ordered_stop_area_ids, ->(first, second){ + if first.present? && second.present? + joins(journey_pattern: :stop_points). + joins('INNER JOIN "journey_patterns" ON "journey_patterns"."id" = "vehicle_journeys"."journey_pattern_id" INNER JOIN "journey_patterns_stop_points" ON "journey_patterns_stop_points"."journey_pattern_id" = "journey_patterns"."id" INNER JOIN "stop_points" as "second_stop_points" ON "stop_points"."id" = "journey_patterns_stop_points"."stop_point_id"'). + where('stop_points.stop_area_id = ?', first). + where('second_stop_points.stop_area_id = ? and stop_points.position < second_stop_points.position', second) + else + all + end + } + + scope :starting_with, ->(id){ + if id.present? + joins(journey_pattern: :stop_points).where('stop_points.position = 0 AND stop_points.stop_area_id = ?', id) + else + all + end + } + + scope :ending_with, ->(id){ + if id.present? + pattern_ids = all.select(:journey_pattern_id).uniq.map(&:journey_pattern_id) + pattern_ids = Chouette::JourneyPattern.where(id: pattern_ids).to_a.select{|jp| p "ici: #{jp.stop_points.order(:position).last.stop_area_id}" ; jp.stop_points.order(:position).last.stop_area_id == id.to_i}.map &:id + where(journey_pattern_id: pattern_ids) + else + all + end + } + + scope :in_purchase_window, ->(range){ purchase_windows = Chouette::PurchaseWindow.overlap_dates(range) sql = purchase_windows.joins(:vehicle_journeys).select('vehicle_journeys.id').uniq.to_sql - where("id IN (#{sql})") + where("vehicle_journeys.id IN (#{sql})") } # We need this for the ransack object in the filters @@ -302,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/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/referential.rb b/app/models/referential.rb index 09c2e7d34..91a88d02d 100644 --- a/app/models/referential.rb +++ b/app/models/referential.rb @@ -408,6 +408,7 @@ class Referential < ActiveRecord::Base end check_migration_count(report) + # raise "Wrong migration count: #{migration_count}" if migration_count < 300 end end @@ -417,13 +418,20 @@ class Referential < ActiveRecord::Base end def migration_count - if self.class.connection.table_exists?("#{slug}.schema_migrations") - self.class.connection.select_value("select count(*) from #{slug}.schema_migrations;") - end + raw_value = + if self.class.connection.table_exists?("#{slug}.schema_migrations") + self.class.connection.select_value("select count(*) from #{slug}.schema_migrations;") + end + + raw_value.to_i end - def assign_slug - self.slug ||= "#{name.parameterize.gsub('-', '_')}_#{Time.now.to_i}" if name + def assign_slug(time_reference = Time) + self.slug ||= begin + prefix = name.parameterize.gsub('-','_').gsub(/[^a-zA-Z_]/,'').gsub(/^_/,'') + prefix = "referential" if prefix.blank? + "#{prefix}_#{time_reference.now.to_i}" + end if name end def assign_prefix diff --git a/app/models/simple_importer.rb b/app/models/simple_importer.rb index b824d596d..d6ba64494 100644 --- a/app/models/simple_importer.rb +++ b/app/models/simple_importer.rb @@ -95,7 +95,8 @@ class SimpleImporter < ActiveRecord::Base end def dump_csv_from_context - filepath = "./#{self.configuration_name}_#{Time.now.strftime "%y%m%d%H%M"}.csv" + dir = context[:output_dir] || "log/importers" + filepath = File.join dir, "#{self.configuration_name}_#{Time.now.strftime "%y%m%d%H%M"}.csv" # for some reason, context[:csv].to_csv does not work CSV.open(filepath, 'w') do |csv| header = true @@ -262,7 +263,7 @@ class SimpleImporter < ActiveRecord::Base msg += "\n\n" msg += colorize "=== MESSAGES (#{@messages.count}) ===\n", :green msg += "[...]\n" if @messages.count > lines_count - msg += @messages.last(lines_count).join("\n") + msg += @messages.last(lines_count).map{|m| m.truncate(@status_width)}.join("\n") msg += "\n"*[lines_count-@messages.count, 0].max end @@ -273,7 +274,9 @@ class SimpleImporter < ActiveRecord::Base msg += @errors.last(lines_count).map do |j| kind = j[:kind] kind = colorize(kind, kind == :error ? :red : :orange) - encode_string "[#{kind}]\t\tL#{j[:line]}\t#{j[:error]}\t\t#{j[:message]}" + kind = "[#{kind}]" + kind += " "*(25 - kind.size) + encode_string("#{kind}L#{j[:line]}\t#{j[:error]}\t\t#{j[:message]}").truncate(@status_width) end.join("\n") end custom_print msg, clear: true 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/layouts/navigation/_main_nav_top.html.slim b/app/views/layouts/navigation/_main_nav_top.html.slim index f664d5416..12355dfb7 100644 --- a/app/views/layouts/navigation/_main_nav_top.html.slim +++ b/app/views/layouts/navigation/_main_nav_top.html.slim @@ -21,6 +21,18 @@ = link_to destroy_user_session_path, method: :delete, class: 'menu-item', title: 'Se déconnecter' do span.fa.fa-lg.fa-sign-out + - if has_feature?(:change_locale) + .menu-item-group.pull-right + .dropdown.languages + a href="#" class="dropdown-toggle" data-toggle="dropdown" + = image_tag("language_engine/#{selected_language}_flag.png", { :'data-locale' => "#{selected_language}" } ) + b.caret + + ul.dropdown-menu + - I18n.available_locales.each do |locale| + li= link_to_language locale, { :class => language_class( locale ) } + + = render 'layouts/navigation/nav_panel_operations' = render 'layouts/navigation/nav_panel_profile' if user_signed_in? 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/referential_vehicle_journeys/_filters.html.slim b/app/views/referential_vehicle_journeys/_filters.html.slim index 3104e3a71..5f102ae1b 100644 --- a/app/views/referential_vehicle_journeys/_filters.html.slim +++ b/app/views/referential_vehicle_journeys/_filters.html.slim @@ -40,9 +40,18 @@ = f.input :published_journey_name_gteq, label: false, wrapper_html: { class: 'w45'} .form-group.w10.to= I18n.t('vehicle_journeys.form.to') = f.input :published_journey_name_lteq, label: false, wrapper_html: { class: 'w45'} - .form-group.togglable class=filter_item_class(params[:q], :stop_area_ids) + .form-group.togglable class=filter_item_class(params[:q], :stop_areas) = f.label Chouette::StopArea.model_name.human.pluralize, required: false, class: 'control-label' - = f.input :stop_area_ids, collection: @all_stop_areas.select(:id, :name).order(name: :asc), checked: params[:q] && params[:q][:stop_area_ids], as: :check_boxes, label: false, label_method: lambda{|l| ("<span>" + l.name + "</span>").html_safe}, required: false, wrapper_html: { class: 'checkbox_list'}, multiple: true + .filter_menu.stop-areas + = f.simple_fields_for :stop_areas do |p| + - json_url = referential_autocomplete_stop_areas_path(@referential, :format => :json) + - opts = {as: :select, label: false, required: false, wrapper_html: { class: 'filter_menu-item select2ed' }, input_html: {style: "width: 100%", data: { 'select2-ajax': 'true', 'select2ed-placeholder': '', url: json_url, 'select2ed-allow-clear': true}}} + - opts = opts.update({collection: [@starting_stop].compact, selected: @starting_stop&.id}) + - opts[:input_html][:data][:'select2ed-placeholder'] = I18n.t('vehicle_journeys.form.starting_stop') + = p.input :start, opts + - opts = opts.update({collection: [@ending_stop].compact, selected: @ending_stop&.id}) + - opts[:input_html][:data][:'select2ed-placeholder'] = I18n.t('vehicle_journeys.form.ending_stop') + = p.input :end, opts .form-group.togglable class=filter_item_class(params[:q], :purchase_window) = f.label Chouette::VehicleJourney.human_attribute_name(:purchase_window), class: 'control-label' .filter_menu diff --git a/app/views/referential_vehicle_journeys/index.html.slim b/app/views/referential_vehicle_journeys/index.html.slim index ca1b1ecd9..00f63cb65 100644 --- a/app/views/referential_vehicle_journeys/index.html.slim +++ b/app/views/referential_vehicle_journeys/index.html.slim @@ -39,14 +39,20 @@ ), \ TableBuilderHelper::Column.new( \ key: :departure_time, \ - attribute: Proc.new {|v| v.vehicle_journey_at_stops.first&.departure }, \ + attribute: Proc.new {|v| v.vehicle_journey_at_stops.first&.departure_local }, \ sortable: true \ ), \ - @filters_stop_areas&.map{|s| table_builder_column_for_stop_area(s)}, + [@starting_stop, @ending_stop].compact.map{|stop| \ + TableBuilderHelper::Column.new( \ + attribute: Proc.new {|v| v.vehicle_journey_at_stops.where("stop_points.stop_area_id" => stop.id).last&.arrival_local }, \ + sortable: false, \ + name: stop.name \ + )\ + }, \ TableBuilderHelper::Column.new( \ key: :arrival_time, \ - attribute: Proc.new {|v| v.vehicle_journey_at_stops.last&.arrival }, \ - sortable: true \ + attribute: Proc.new {|v| v.vehicle_journey_at_stops.last&.arrival_local }, \ + sortable: true, \ ), \ ].flatten.compact, cls: 'table has-filter has-search' diff --git a/app/views/referentials/_form.html.slim b/app/views/referentials/_form.html.slim index 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 539c25fd4..6bed5f282 100644 --- a/app/views/referentials/_overview.html.slim +++ b/app/views/referentials/_overview.html.slim @@ -13,7 +13,7 @@ .form-group.togglable = f.label Chouette::Line.human_attribute_name(:transport_mode), required: false, class: 'control-label' - = f.input :transport_mode_eq_any, collection: overview.referential_lines.map(&:transport_mode).uniq.sort, as: :check_boxes, label: false, label_method: lambda{|l| ("<span>" + t("enumerize.transport_mode.#{l}") + "</span>").html_safe}, required: false, wrapper_html: { class: 'checkbox_list'} + = f.input :transport_mode_eq_any, collection: overview.referential_lines.map(&:transport_mode).compact.uniq.sort, as: :check_boxes, label: false, label_method: lambda{|l| ("<span>" + t("enumerize.transport_mode.#{l}") + "</span>").html_safe}, required: false, wrapper_html: { class: 'checkbox_list'} .actions = link_to 'Effacer', url_for() + "##{overview.pagination_param_name}", class: 'btn btn-link' @@ -35,11 +35,14 @@ .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= t("enumerize.transport_mode.#{line.transport_mode}") + .mode= line.transport_mode.present? ? t("enumerize.transport_mode.#{line.transport_mode}") : "" .right .inner .head 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/app/views/time_table_combinations/_form.html.slim b/app/views/time_table_combinations/_form.html.slim index 426624ee3..6a0fda01e 100644 --- a/app/views/time_table_combinations/_form.html.slim +++ b/app/views/time_table_combinations/_form.html.slim @@ -7,7 +7,7 @@ abbr title='Champ requis' * = f.input :combined_type, as: :boolean, checked_value: 'time_table', unchecked_value: 'calendar', required: false, label: content_tag(:span, t("time_table_combinations.combined_type.#{@combination.combined_type}"), class: 'switch-label', data: { checkedValue: 'Calendriers', uncheckedValue: 'Modèles de calendriers' }), wrapper_html: { class: 'col-sm-8 col-xs-7' } - = f.input :time_table_id, as: :select, input_html: {class: 'tt_combination_target', style: "width: 100%", data: { 'select2-ajax': 'true', 'select2ed-placeholder': 'Indiquez un calendrier...', term: 'comment_or_objectid_cont_any', url: referential_autocomplete_time_tables_path(@referential, format: :json, :source_id => @combination.source_id)}}, wrapper_html: {class: @combination.combined_type != 'time_table' ? 'hidden' : ''} + = f.input :time_table_id, as: :select, input_html: {class: 'tt_combination_target', style: "width: 100%", data: { 'select2-ajax': 'true', 'select2ed-placeholder': 'Indiquez un calendrier...', term: 'unaccented_comment_or_objectid_cont_any', url: referential_autocomplete_time_tables_path(@referential, format: :json, :source_id => @combination.source_id)}}, wrapper_html: {class: @combination.combined_type != 'time_table' ? 'hidden' : ''} = f.input :calendar_id, as: :select, input_html: { class: 'tt_combination_target', style: "width: 100%", data: { 'select2-ajax': 'true', 'select2ed-placeholder': 'Indiquez un modèle de calendrier...', term: 'name_cont', url: autocomplete_calendars_path}}, wrapper_html: {class: @combination.combined_type != 'calendar' ? 'hidden' : ''} diff --git a/app/views/time_tables/_form.html.slim b/app/views/time_tables/_form.html.slim index 007044e65..000da870e 100644 --- a/app/views/time_tables/_form.html.slim +++ b/app/views/time_tables/_form.html.slim @@ -5,7 +5,7 @@ = form.input :comment, :input_html => { :title => t("formtastic.titles#{format_restriction_for_locales(@referential)}.time_table.comment")} - if @time_table.new_record? && !@time_table.created_from - = form.input :calendar_id, as: :select, input_html: { class: 'tt_target', style: "width: 100%", data: { 'select2-ajax': 'true', 'select2ed-placeholder': 'Indiquez un modèle de calendrier...', term: 'name_cont', url: autocomplete_workgroup_calendars_path(current_workgroup)}} + = form.input :calendar_id, as: :select, input_html: { class: 'tt_target', style: "width: 100%", data: { 'select2-ajax': 'true', 'select2ed-placeholder': 'Indiquez un modèle de calendrier...', term: 'name_cont', url: autocomplete_workgroup_calendars_path(@referential.workgroup)}} - if @time_table.created_from = form.input :created_from, disabled: true, input_html: { value: @time_table.created_from.comment } diff --git a/app/workers/workbench_import_worker.rb b/app/workers/workbench_import_worker.rb index 6420be835..53cbb222a 100644 --- a/app/workers/workbench_import_worker.rb +++ b/app/workers/workbench_import_worker.rb @@ -53,17 +53,20 @@ class WorkbenchImportWorker end def upload_entry_group_stream eg_name, eg_stream - FileUtils.mkdir_p(Rails.root.join('tmp', 'imports')) + FileUtils.mkdir_p(temp_directory) - File.open(Rails.root.join('tmp', 'imports', "WorkbenchImport_#{eg_name}_#{$$}.zip"), 'wb') do |file| + eg_file_path = Tempfile.open( + ["WorkbenchImport_#{eg_name}_", '.zip'], + temp_directory + ) do |f| eg_stream.rewind - file.write eg_stream.read + f.write eg_stream.read + + f.path end - upload_entry_group_tmpfile eg_name, File.new(Rails.root.join('tmp', 'imports', "WorkbenchImport_#{eg_name}_#{$$}.zip")) - end - - def upload_entry_group_tmpfile eg_name, eg_file + eg_file = File.open(eg_file_path) + result = execute_post eg_name, eg_file if result && result.status < 400 @entries += 1 @@ -73,8 +76,8 @@ class WorkbenchImportWorker raise StopIteration, result.body end ensure - eg_file.close rescue nil - eg_file.unlink rescue nil + eg_file.close + File.unlink(eg_file.path) end @@ -117,6 +120,11 @@ class WorkbenchImportWorker file: HTTPService.upload(file, 'application/zip', "#{name}.zip") } } end + def temp_directory + Rails.application.config.try(:import_temporary_directory) || + Rails.root.join('tmp', 'imports') + end + # Lazy Values # =========== 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/initializers/apartment.rb b/config/initializers/apartment.rb index fc652a2da..a996549fd 100644 --- a/config/initializers/apartment.rb +++ b/config/initializers/apartment.rb @@ -98,7 +98,7 @@ Apartment.configure do |config| # config.append_environment = true # supply list of database names for migrations to run on - config.tenant_names = lambda{ Referential.order("created_from_id asc").pluck(:slug) } + config.tenant_names = lambda{ Referential.where(ready: true).order("created_from_id asc").pluck(:slug) } end ## diff --git a/config/locales/area_types.fr.yml b/config/locales/area_types.fr.yml index bb249c235..71c26df92 100644 --- a/config/locales/area_types.fr.yml +++ b/config/locales/area_types.fr.yml @@ -10,5 +10,5 @@ fr: deposit: Dépôt border: Frontière service_area: Aire de service / Pause - relief: Point de releve + relief: Point de relève other: Autre 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/config/locales/vehicle_journeys.en.yml b/config/locales/vehicle_journeys.en.yml index 0c8a75b0c..1fa2618dd 100644 --- a/config/locales/vehicle_journeys.en.yml +++ b/config/locales/vehicle_journeys.en.yml @@ -40,6 +40,7 @@ en: label: Journey departure range start: Start end: End + ending_stop: "Arrival" infos: Informations set: "Set" show_arrival_time: "Show and edit arrival times" @@ -49,6 +50,7 @@ en: slide_departure: "departure time at first stop" slide_title: "Shift all vehicle passing times" slide: "Shift" + starting_stop: "Departure" stop_title: "Stop" submit_frequency_edit: "Edit frequency vehicle journey" submit_frequency: "Create frequency vehicle journey" diff --git a/config/locales/vehicle_journeys.fr.yml b/config/locales/vehicle_journeys.fr.yml index 1034a3fba..a43fa4580 100644 --- a/config/locales/vehicle_journeys.fr.yml +++ b/config/locales/vehicle_journeys.fr.yml @@ -40,6 +40,7 @@ fr: label: Plage horaire au départ de la course start: Début end: Fin + ending_stop: "Arrivée" infos: Informations set: "Fixer" show_arrival_time: "Afficher et éditer les horaires d'arrivée" @@ -49,6 +50,7 @@ fr: slide_departure: "horaire de départ au 1° arrêt à" slide_title: "Décaler l'ensemble des horaires de course" slide: "Décaler" + starting_stop: "Origine" stop_title: "Arrêt" submit_frequency_edit: "Editer course en fréquence" submit_frequency: "Créer course en fréquence" 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 b10fa565c..045fc658d 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -11,7 +11,7 @@ # # 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" @@ -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 @@ -220,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 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/tasks/imports.rake b/lib/tasks/imports.rake index b91ff7efb..f01d3f34f 100644 --- a/lib/tasks/imports.rake +++ b/lib/tasks/imports.rake @@ -11,8 +11,8 @@ namespace :import do NetexImport.abort_old end - def importer_output_to_csv importer - filepath = "./#{importer.configuration_name}_#{Time.now.strftime "%y%m%d%H%M"}_out.csv" + def importer_output_to_csv importer, output_dir + filepath = File.join output_dir, + "#{importer.configuration_name}_#{Time.now.strftime "%y%m%d%H%M"}_out.csv" cols = %w(line kind event message error) if importer.reload.journal.size > 0 keys = importer.journal.first["row"].map(&:first) @@ -27,13 +27,17 @@ namespace :import do end desc "import the given file with the corresponding importer" - task :import, [:configuration_name, :filepath, :referential_id] => :environment do |t, args| + task :import, [:configuration_name, :filepath, :referential_id, :output_dir] => :environment do |t, args| + args.with_defaults(output_dir: "./log/importers/") + FileUtils.mkdir_p args[:output_dir] + importer = SimpleImporter.create configuration_name: args[:configuration_name], filepath: args[:filepath] + if args[:referential_id].present? referential = Referential.find args[:referential_id] importer.configure do |config| config.add_value :referential, referential - config.context = {referential: referential} + config.context = {referential: referential, output_dir: args[:output_dir]} end end puts "\e[33m***\e[0m Start importing" @@ -43,17 +47,20 @@ namespace :import do raise ensure puts "\n\e[33m***\e[0m Import done, status: " + (importer.status == "success" ? "\e[32m" : "\e[31m" ) + (importer.status || "") + "\e[0m" - importer_output_to_csv importer + importer_output_to_csv importer, args[:output_dir] end end desc "import the given file with the corresponding importer in the given StopAreaReferential" task :import_in_stop_area_referential, [:referential_id, :configuration_name, :filepath] => :environment do |t, args| + args.with_defaults(output_dir: "./log/importers/") + FileUtils.mkdir_p args[:output_dir] + referential = StopAreaReferential.find args[:referential_id] importer = SimpleImporter.create configuration_name: args[:configuration_name], filepath: args[:filepath] importer.configure do |config| config.add_value :stop_area_referential, referential - config.context = {stop_area_referential: referential} + config.context = {stop_area_referential: referential, output_dir: args[:output_dir]} end puts "\e[33m***\e[0m Start importing" begin @@ -62,19 +69,22 @@ namespace :import do raise ensure puts "\n\e[33m***\e[0m Import done, status: " + (importer.status == "success" ? "\e[32m" : "\e[31m" ) + (importer.status || "") + "\e[0m" - importer_output_to_csv importer + importer_output_to_csv importer, args[:output_dir] end end desc "import the given routes files" task :import_routes, [:referential_id, :configuration_name, :mapping_filepath, :filepath] => :environment do |t, args| + args.with_defaults(output_dir: "./log/importers/") + FileUtils.mkdir_p args[:output_dir] + referential = Referential.find args[:referential_id] referential.switch stop_area_referential = referential.stop_area_referential importer = SimpleImporter.create configuration_name: args[:configuration_name], filepath: args[:filepath] importer.configure do |config| config.add_value :stop_area_referential, referential - config.context = {stop_area_referential: stop_area_referential, mapping_filepath: args[:mapping_filepath]} + config.context = {stop_area_referential: stop_area_referential, mapping_filepath: args[:mapping_filepath], output_dir: args[:output_dir]} end puts "\e[33m***\e[0m Start importing" begin @@ -83,17 +93,20 @@ namespace :import do raise ensure puts "\n\e[33m***\e[0m Import done, status: " + (importer.status == "success" ? "\e[32m" : "\e[31m" ) + (importer.status || "") + "\e[0m" - importer_output_to_csv importer + importer_output_to_csv importer, args[:output_dir] end end desc "import the given file with the corresponding importer in the given LineReferential" - task :import_in_line_referential, [:referential_id, :configuration_name, :filepath] => :environment do |t, args| + task :import_in_line_referential, [:referential_id, :configuration_name, :filepath, :output_dir] => :environment do |t, args| + args.with_defaults(output_dir: "./log/importers/") + FileUtils.mkdir_p args[:output_dir] + referential = LineReferential.find args[:referential_id] importer = SimpleImporter.create configuration_name: args[:configuration_name], filepath: args[:filepath] importer.configure do |config| config.add_value :line_referential, referential - config.context = {line_referential: referential} + config.context = {line_referential: referential, output_dir: args[:output_dir]} end puts "\e[33m***\e[0m Start importing" begin @@ -102,7 +115,7 @@ namespace :import do raise ensure puts "\n\e[33m***\e[0m Import done, status: " + (importer.status == "success" ? "\e[32m" : "\e[31m" ) + (importer.status || "") + "\e[0m" - importer_output_to_csv importer + importer_output_to_csv importer, args[:output_dir] end end end 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/autocomplete_time_tables_controller_spec.rb b/spec/controllers/autocomplete_time_tables_controller_spec.rb new file mode 100644 index 000000000..85a8eb714 --- /dev/null +++ b/spec/controllers/autocomplete_time_tables_controller_spec.rb @@ -0,0 +1,37 @@ +require 'rails_helper' + +RSpec.describe AutocompleteTimeTablesController, type: :controller do + login_user + + let(:referential) { Referential.first } + let(:other_referential) { create :referential } + let!(:time_table) { create :time_table, comment: 'écolà militaire' } + let!(:blargh) { create :time_table, comment: 'écolàë militaire' } + let!(:other_time_table) { create :time_table, comment: 'foo bar baz' } + + describe 'GET #index' do + it 'should be successful' do + get :index, referential_id: referential.id + expect(response).to be_success + end + + context 'search by name' do + it 'should be successful' do + get :index, referential_id: referential.id, q: {unaccented_comment_or_objectid_cont_any: 'écolà'}, :format => :json + expect(response).to be_success + expect(assigns(:time_tables)).to include(time_table) + expect(assigns(:time_tables)).to include(blargh) + expect(assigns(:time_tables)).to_not include(other_time_table) + end + + it 'should be accent insensitive' do + get :index, referential_id: referential.id, q: {unaccented_comment_or_objectid_cont_any: 'ecola'}, :format => :json + expect(response).to be_success + expect(assigns(:time_tables)).to include(time_table) + expect(assigns(:time_tables)).to include(blargh) + expect(assigns(:time_tables)).to_not include(other_time_table) + end + end + end + +end diff --git a/spec/controllers/referential_vehicle_journeys_controller_spec.rb b/spec/controllers/referential_vehicle_journeys_controller_spec.rb index cc6b44b9d..50230dd9e 100644 --- a/spec/controllers/referential_vehicle_journeys_controller_spec.rb +++ b/spec/controllers/referential_vehicle_journeys_controller_spec.rb @@ -44,7 +44,8 @@ RSpec.describe ReferentialVehicleJourneysController, type: :controller do get :index, referential_id: referential, q: q } - let(:stop_area_ids){ [] } + let(:from_area_id){ nil } + let(:to_area_id){ nil } def create_journey_pattern_with_stop_areas(*stop_areas) j = create(:journey_pattern) @@ -56,40 +57,27 @@ RSpec.describe ReferentialVehicleJourneysController, type: :controller do j end - let(:q){ {stop_area_ids: stop_area_ids}} - let(:stop_area_1){ create :stop_area } - let(:stop_area_2){ create :stop_area } - let!(:journey_1){ create_journey_pattern_with_stop_areas(stop_area_1)} - let!(:journey_2){ create_journey_pattern_with_stop_areas(stop_area_2)} - let!(:journey_1_and_2){ create_journey_pattern_with_stop_areas(stop_area_1, stop_area_2)} - let!(:vehicle_journey_1){ create(:vehicle_journey, journey_pattern: journey_1)} - let!(:vehicle_journey_2){ create(:vehicle_journey, journey_pattern: journey_2)} - let!(:vehicle_journey_1_and_2){ create(:vehicle_journey, journey_pattern: journey_1_and_2)} - - context "with one stop" do - let(:stop_area_ids){[stop_area_1.id]} + let(:q){ {stop_areas: {start: from_area_id, end: to_area_id}} } + let(:journey_pattern){ create(:journey_pattern) } + let(:journey_pattern_2){ create(:journey_pattern) } + + let!(:vehicle_journey_1){ create(:vehicle_journey, journey_pattern: journey_pattern)} + let!(:vehicle_journey_2){ create(:vehicle_journey, journey_pattern: journey_pattern_2)} + + context "with the start stop" do + let(:from_area_id){ vehicle_journey_1.stop_areas.first.id } it "should apply filters" do - expect(vehicle_journey_1.stop_areas).to include stop_area_1 - expect(vehicle_journey_2.stop_areas).to_not include stop_area_1 - expect(vehicle_journey_1_and_2.stop_areas).to include stop_area_1 expect(assigns[:vehicle_journeys]).to include(vehicle_journey_1) expect(assigns[:vehicle_journeys]).to_not include(vehicle_journey_2) - expect(assigns[:vehicle_journeys]).to include(vehicle_journey_1_and_2) end end - context "with 2 stops" do - let(:stop_area_ids){[stop_area_1.id, stop_area_2.id]} + context "with both stops" do + let(:from_area_id){ vehicle_journey_1.stop_areas.first.id } + let(:to_area_id){ vehicle_journey_1.stop_areas.last.id } it "should apply filters" do - expect(vehicle_journey_1.stop_areas).to include stop_area_1 - expect(vehicle_journey_1.stop_areas).to_not include stop_area_2 - expect(vehicle_journey_2.stop_areas).to include stop_area_2 - expect(vehicle_journey_2.stop_areas).to_not include stop_area_1 - expect(vehicle_journey_1_and_2.stop_areas).to include stop_area_1 - expect(vehicle_journey_1_and_2.stop_areas).to include stop_area_2 - expect(assigns[:vehicle_journeys]).to_not include(vehicle_journey_1) + expect(assigns[:vehicle_journeys]).to include(vehicle_journey_1) expect(assigns[:vehicle_journeys]).to_not include(vehicle_journey_2) - expect(assigns[:vehicle_journeys]).to include(vehicle_journey_1_and_2) end end end 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/javascript/vehicle_journeys/reducers/vehicleJourneys_spec.js b/spec/javascript/vehicle_journeys/reducers/vehicleJourneys_spec.js index 389c60add..608115727 100644 --- a/spec/javascript/vehicle_journeys/reducers/vehicleJourneys_spec.js +++ b/spec/javascript/vehicle_journeys/reducers/vehicleJourneys_spec.js @@ -241,6 +241,194 @@ describe('vehicleJourneys reducer', () => { }, ...state]) }) + it('should handle ADD_VEHICLEJOURNEY with a start time and a fully timed JP, and use user\'s TZ', () => { + let pristineVjasList = [{ + delta : 0, + arrival_time : { + hour: 21, + minute: 54 + }, + departure_time : { + hour: 21, + minute: 54 + }, + stop_point_objectid: 'test-1', + stop_area_cityname: 'city', + dummy: false + }, + { + delta : 0, + arrival_time : { + hour: 21, + minute: 57 + }, + departure_time : { + hour: 22, + minute: 7 + }, + stop_point_objectid: 'test-2', + stop_area_cityname: 'city', + dummy: false + }, + { + delta : 0, + arrival_time : { + hour: "00", + minute: "00" + }, + departure_time : { + hour: "00", + minute: "00" + }, + stop_point_objectid: 'test-3', + stop_area_cityname: 'city', + dummy: true + }, + { + delta : 0, + arrival_time : { + hour: 23, + minute: 37 + }, + departure_time : { + hour: 23, + minute: 37 + }, + stop_point_objectid: 'test-4', + stop_area_cityname: 'city', + dummy: false + }] + let fakeData = { + published_journey_name: {value: 'test'}, + published_journey_identifier: {value : ''}, + "start_time.hour": {value : '22'}, + "start_time.minute": {value : '59'}, + "tz_offset": {value : '-65'} + } + let fakeSelectedJourneyPattern = { + id: "1", + full_schedule: true, + stop_areas: [ + {stop_area_short_description: {id: 1}}, + {stop_area_short_description: {id: 2}}, + {stop_area_short_description: {id: 4}}, + ], + costs: { + "1-2": { + distance: 10, + time: 63 + }, + "2-4": { + distance: 10, + time: 30 + } + } + } + let fakeSelectedCompany = {name: "ALBATRANS"} + expect( + vjReducer(state, { + type: 'ADD_VEHICLEJOURNEY', + data: fakeData, + selectedJourneyPattern: fakeSelectedJourneyPattern, + stopPointsList: [{object_id: 'test-1', city_name: 'city', stop_area_id: 1, id: 1, time_zone_offset: 0, waiting_time: null}, {object_id: 'test-2', city_name: 'city', stop_area_id: 2, id: 2, time_zone_offset: -3600, waiting_time: 10}, {object_id: 'test-3', city_name: 'city', stop_area_id: 3, id: 3, time_zone_offset: 0, waiting_time: 20}, {object_id: 'test-4', city_name: 'city', stop_area_id: 4, id: 4, time_zone_offset: 0}], + selectedCompany: fakeSelectedCompany + }) + ).toEqual([{ + journey_pattern: fakeSelectedJourneyPattern, + company: fakeSelectedCompany, + published_journey_name: 'test', + published_journey_identifier: '', + short_id: '', + objectid: '', + footnotes: [], + time_tables: [], + purchase_windows: [], + vehicle_journey_at_stops: pristineVjasList, + selected: false, + custom_fields: undefined, + deletable: false, + transport_mode: 'undefined', + transport_submode: 'undefined' + }, ...state]) + }) + + it('should handle ADD_VEHICLEJOURNEY with a start time and a fully timed JP but no time is set', () => { + let pristineVjasList = [{ + delta : 0, + arrival_time : { + hour: 0, + minute: 0 + }, + departure_time : { + hour: 0, + minute: 0 + }, + stop_point_objectid: 'test-1', + stop_area_cityname: 'city', + dummy: false + }, + { + delta : 0, + arrival_time : { + hour: 0, + minute: 0 + }, + departure_time : { + hour: 0, + minute: 0 + }, + stop_point_objectid: 'test-2', + stop_area_cityname: 'city', + dummy: false + }] + let fakeData = { + published_journey_name: {value: 'test'}, + published_journey_identifier: {value : ''}, + "start_time.hour": {value : ''}, + "start_time.minute": {value : ''} + } + let fakeSelectedJourneyPattern = { + id: "1", + full_schedule: true, + stop_areas: [ + {stop_area_short_description: {id: 1}}, + {stop_area_short_description: {id: 2}}, + ], + costs: { + "1-2": { + distance: 10, + time: 63 + }, + } + } + let fakeSelectedCompany = {name: "ALBATRANS"} + expect( + vjReducer(state, { + type: 'ADD_VEHICLEJOURNEY', + data: fakeData, + selectedJourneyPattern: fakeSelectedJourneyPattern, + stopPointsList: [{object_id: 'test-1', city_name: 'city', stop_area_id: 1, id: 1, time_zone_offset: 0}, {object_id: 'test-2', city_name: 'city', stop_area_id: 2, id: 2, time_zone_offset: -3600}], + selectedCompany: fakeSelectedCompany + }) + ).toEqual([{ + journey_pattern: fakeSelectedJourneyPattern, + company: fakeSelectedCompany, + published_journey_name: 'test', + published_journey_identifier: '', + short_id: '', + objectid: '', + footnotes: [], + time_tables: [], + purchase_windows: [], + vehicle_journey_at_stops: pristineVjasList, + selected: false, + custom_fields: undefined, + deletable: false, + transport_mode: 'undefined', + transport_submode: 'undefined' + }, ...state]) + }) + it('should handle ADD_VEHICLEJOURNEY with a start time and a fully timed JP but the minutes are not set', () => { let pristineVjasList = [{ delta : 0, 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 eeedf6562..1d9b3d78a 100644 --- a/spec/models/referential_spec.rb +++ b/spec/models/referential_spec.rb @@ -9,6 +9,24 @@ describe Referential, :type => :model do subject { build_stubbed(:referential) } it { should validate_presence_of(:objectid_format) } + + it "assign slug with a good format" do + time_reference = double(now: 1234567890) + + conditions = { + "2018-Hiver-Jezequel-MM-Lyon-Nice": "hiver_jezequel_mm_lyon_nice_1234567890", + "2018-Hiver-Jezequel-23293MM-Lyon-Nice": "hiver_jezequel_mm_lyon_nice_1234567890", + "-Hiver-Jezequel-MM-Lyon-Nice": "hiver_jezequel_mm_lyon_nice_1234567890", + "Hiver-Jezequel-MM-Lyon-Nice": "hiver_jezequel_mm_lyon_nice_1234567890", + "20179282": "referential_1234567890" + } + + conditions.each do |name, expected_slug| + ref = Referential.new name: name + ref.assign_slug time_reference + expect(ref.slug).to eq(expected_slug) + end + end end context ".referential_ids_in_periode" do @@ -25,6 +43,16 @@ describe Referential, :type => :model do end end + context "schema creation" do + + it "should create a schema named as the slug" do + referential = FactoryGirl.create :referential + expect(referential.migration_count).to be ActiveRecord::Migrator.get_all_versions.count + expect(referential.migration_count).to be > 300 + end + + end + context "Cloning referential" do let(:clone) do Referential.new_from(ref, []) diff --git a/spec/support/pundit/pundit_view_policy.rb b/spec/support/pundit/pundit_view_policy.rb index 63970de02..316ff6718 100644 --- a/spec/support/pundit/pundit_view_policy.rb +++ b/spec/support/pundit/pundit_view_policy.rb @@ -12,7 +12,7 @@ module Pundit allow(view).to receive(:current_organisation).and_return(organisation) allow(view).to receive(:current_offer_workbench).and_return(current_offer_workbench) allow(view).to receive(:current_workgroup).and_return(current_offer_workbench.workgroup) - allow(view).to receive(:has_feature?){ |f| features.include?(f)} + allow(view).to receive(:has_feature?){ |f| respond_to?(:features) && features.include?(f)} allow(view).to receive(:user_signed_in?).and_return true allow(view).to receive(:policy) do |instance| ::Pundit.policy pundit_user, instance |
