diff options
221 files changed, 2033 insertions, 456 deletions
| @@ -33,9 +33,6 @@ gem 'spring', group: :development  # ActiveRecord associations on top of PostgreSQL arrays  gem 'has_array_of', af83: 'has_array_of' -# Track changes to your models' data. Good for auditing or versioning. -gem 'paper_trail' -  gem 'rails-observers'  # Use SeedBank for spliting seeds diff --git a/Gemfile.lock b/Gemfile.lock index 7fd58c713..4fb77eeb9 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -341,10 +341,6 @@ GEM        mini_portile2 (~> 2.3.0)      open4 (1.3.4)      orm_adapter (0.5.0) -    paper_trail (4.1.0) -      activerecord (>= 3.0, < 6.0) -      activesupport (>= 3.0, < 6.0) -      request_store (~> 1.1)      parser (2.4.0.0)        ast (~> 2.2)      pg (0.20.0) @@ -657,7 +653,6 @@ DEPENDENCIES    map_layers (= 0.0.4)    mimemagic    newrelic_rpm -  paper_trail    pg    phantomjs    poltergeist diff --git a/app/assets/stylesheets/components/_main_nav.sass b/app/assets/stylesheets/components/_main_nav.sass index 8e164fa01..ef3f14762 100644 --- a/app/assets/stylesheets/components/_main_nav.sass +++ b/app/assets/stylesheets/components/_main_nav.sass @@ -346,6 +346,7 @@ $menuW: 300px            flex: 1 1            height: $menuH            position: relative +          margin-right: 2em            h1              position: absolute @@ -355,8 +356,8 @@ $menuW: 300px              line-height: 1.1              white-space: nowrap              max-height: 1.1em -            margin: 0 -1.4em 0 0 -            padding: 0 1.4em 5px 0 +            margin: 0 +            padding: 0 0 5px 0              overflow: hidden              text-overflow: ellipsis diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb index c4961123d..8b66e6097 100644 --- a/app/controllers/application_controller.rb +++ b/app/controllers/application_controller.rb @@ -1,5 +1,5 @@  class ApplicationController < ActionController::Base -  include PaperTrailSupport +  include MetadataControllerSupport    include Pundit    include FeatureChecker @@ -10,7 +10,6 @@ class ApplicationController < ActionController::Base    before_action :authenticate_user!    before_action :set_locale -    # Load helpers in rails engine    helper LanguageEngine::Engine.helpers @@ -36,12 +35,6 @@ class ApplicationController < ActionController::Base    end    helper_method :current_organisation -  def current_functional_scope -    functional_scope = current_organisation.sso_attributes.try(:[], "functional_scope") if current_organisation -    JSON.parse(functional_scope) if functional_scope -  end -  helper_method :current_functional_scope -    def collection_name      self.class.name.split("::").last.gsub('Controller', '').underscore    end diff --git a/app/controllers/chouette_controller.rb b/app/controllers/chouette_controller.rb index 3e4f3af27..e6e7c0a8a 100644 --- a/app/controllers/chouette_controller.rb +++ b/app/controllers/chouette_controller.rb @@ -1,4 +1,3 @@  class ChouetteController < InheritedResources::Base -  include PaperTrailSupport    include ApplicationHelper  end diff --git a/app/controllers/companies_controller.rb b/app/controllers/companies_controller.rb index 4afd12be1..a09cab783 100644 --- a/app/controllers/companies_controller.rb +++ b/app/controllers/companies_controller.rb @@ -38,12 +38,14 @@ class CompaniesController < ChouetteController    protected    def collection -    @q = line_referential.companies.search(params[:q]) - +    scope = line_referential.companies +    @q = scope.search(params[:q]) +    ids = @q.result(:distinct => true).pluck(:id) +    scope = scope.where(id: ids)      if sort_column && sort_direction -      @companies ||= @q.result(:distinct => true).order(sort_column + ' ' + sort_direction).paginate(:page => params[:page]) +      @companies ||= scope.order(sort_column + ' ' + sort_direction).paginate(:page => params[:page])      else -      @companies ||= @q.result(:distinct => true).order(:name).paginate(:page => params[:page]) +      @companies ||= scope.order(:name).paginate(:page => params[:page])      end    end @@ -69,7 +71,9 @@ class CompaniesController < ChouetteController    end    def company_params -    params.require(:company).permit( :objectid, :object_version, :name, :short_name, :organizational_unit, :operating_department_name, :code, :phone, :fax, :email, :registration_number, :url, :time_zone ) +    fields = [:objectid, :object_version, :name, :short_name, :organizational_unit, :operating_department_name, :code, :phone, :fax, :email, :registration_number, :url, :time_zone] +    fields += permitted_custom_fields_params(Chouette::Company.custom_fields(line_referential.workgroup)) +    params.require(:company).permit( fields )    end    private diff --git a/app/controllers/compliance_control_blocks_controller.rb b/app/controllers/compliance_control_blocks_controller.rb index 9eee8dfaf..1173a548a 100644 --- a/app/controllers/compliance_control_blocks_controller.rb +++ b/app/controllers/compliance_control_blocks_controller.rb @@ -4,10 +4,23 @@ class ComplianceControlBlocksController < ChouetteController    belongs_to :compliance_control_set    actions :all, :except => [:show, :index] +  after_action :display_errors, only: [:create, :update] + +  def display_errors +    unless @compliance_control_block.errors[:condition_attributes].empty? +      flash[:error] = @compliance_control_block.errors[:condition_attributes].join(', ') +    end +  end +    private    def compliance_control_block_params      params.require(:compliance_control_block).permit(:transport_mode, :transport_submode)    end +  protected + +  alias_method :compliance_control_set, :parent +  helper_method :compliance_control_set +  end diff --git a/app/controllers/concerns/metadata_controller_support.rb b/app/controllers/concerns/metadata_controller_support.rb new file mode 100644 index 000000000..db83e79ae --- /dev/null +++ b/app/controllers/concerns/metadata_controller_support.rb @@ -0,0 +1,26 @@ +module MetadataControllerSupport +  extend ActiveSupport::Concern + +  included do +    after_action :set_creator_metadata, only: :create +    after_action :set_modifier_metadata, only: :update +  end + +  def user_for_metadata +    current_user ? current_user.username : '' +  end + +  def set_creator_metadata +    if resource.valid? +      resource.try(:set_metadata!, :creator_username, user_for_metadata) +      resource.try(:set_metadata!, :modifier_username, user_for_metadata) +    end +  end + +  def set_modifier_metadata +    _resource = @resources || [resource] +    _resource.flatten.each do |r| +      r.try :set_metadata!, :modifier_username, user_for_metadata +    end +  end +end diff --git a/app/controllers/concerns/paper_trail_support.rb b/app/controllers/concerns/paper_trail_support.rb deleted file mode 100644 index 4b0b1a7c7..000000000 --- a/app/controllers/concerns/paper_trail_support.rb +++ /dev/null @@ -1,11 +0,0 @@ -module PaperTrailSupport -  extend ActiveSupport::Concern - -  included do -    before_action :set_paper_trail_whodunnit - -    def user_for_paper_trail -      current_user ? current_user.name : '' -    end -  end -end diff --git a/app/controllers/exports_controller.rb b/app/controllers/exports_controller.rb index a5282a514..c89da5000 100644 --- a/app/controllers/exports_controller.rb +++ b/app/controllers/exports_controller.rb @@ -3,13 +3,14 @@ class ExportsController < ChouetteController    include RansackDateFilter    include IevInterfaces    skip_before_action :authenticate_user!, only: [:upload] +  skip_before_action :verify_authenticity_token, only: [:upload]    defaults resource_class: Export::Base, collection_name: 'exports', instance_name: 'export'    def upload      if params[:token] == resource.token_upload        resource.file = params[:file]        resource.save! -      redirect_to [resource.workbench, resource] +      render json: {status: :ok}      else        user_not_authorized      end diff --git a/app/controllers/journey_patterns_collections_controller.rb b/app/controllers/journey_patterns_collections_controller.rb index da567779e..db92d48f3 100644 --- a/app/controllers/journey_patterns_collections_controller.rb +++ b/app/controllers/journey_patterns_collections_controller.rb @@ -74,6 +74,7 @@ class JourneyPatternsCollectionsController < ChouetteController    def update      state  = JSON.parse request.raw_post      Chouette::JourneyPattern.state_update route, state +    @resources = route.journey_patterns      errors = state.any? {|item| item['errors']}      respond_to do |format| diff --git a/app/controllers/referential_companies_controller.rb b/app/controllers/referential_companies_controller.rb index 806a70c8f..200e56a89 100644 --- a/app/controllers/referential_companies_controller.rb +++ b/app/controllers/referential_companies_controller.rb @@ -40,11 +40,12 @@ class ReferentialCompaniesController < ChouetteController      end      @q = scope.search(params[:q]) - +    ids = @q.result(:distinct => true).pluck(:id) +    scope = scope.where(id: ids)      if sort_column && sort_direction -      @companies ||= @q.result(:distinct => true).order(sort_column + ' ' + sort_direction).paginate(:page => params[:page]) +      @companies ||= scope.order(sort_column + ' ' + sort_direction).paginate(:page => params[:page])      else -      @companies ||= @q.result(:distinct => true).order(:name).paginate(:page => params[:page]) +      @companies ||= scope.order(:name).paginate(:page => params[:page])      end    end @@ -57,7 +58,9 @@ class ReferentialCompaniesController < ChouetteController    end    def company_params -    params.require(:company).permit( :objectid, :object_version, :name, :short_name, :organizational_unit, :operating_department_name, :code, :phone, :fax, :email, :registration_number, :url, :time_zone ) +    fields = [:objectid, :object_version, :name, :short_name, :organizational_unit, :operating_department_name, :code, :phone, :fax, :email, :registration_number, :url, :time_zone] +    fields += permitted_custom_fields_params(Chouette::Company.custom_fields(@referential.workgroup)) +    params.require(:company).permit( fields )    end    private diff --git a/app/controllers/referentials_controller.rb b/app/controllers/referentials_controller.rb index 6e3694547..fe661651e 100644 --- a/app/controllers/referentials_controller.rb +++ b/app/controllers/referentials_controller.rb @@ -143,7 +143,7 @@ class ReferentialsController < ChouetteController    def build_referential      if params[:from]        source_referential = Referential.find(params[:from]) -      @referential = Referential.new_from(source_referential, current_functional_scope) +      @referential = Referential.new_from(source_referential, current_organisation)      end      @referential.data_format = current_organisation.data_format diff --git a/app/controllers/routes_controller.rb b/app/controllers/routes_controller.rb index 96a23c938..ac243c8eb 100644 --- a/app/controllers/routes_controller.rb +++ b/app/controllers/routes_controller.rb @@ -63,7 +63,8 @@ class RoutesController < ChouetteController    end    def duplicate -    route = Chouette::Route.find(params[:id]).duplicate +    source = Chouette::Route.find(params[:id]) +    route = source.duplicate params[:opposite]      flash[:notice] = t('routes.duplicate.success')      redirect_to referential_line_path(@referential, route.line)    end diff --git a/app/controllers/stop_areas_controller.rb b/app/controllers/stop_areas_controller.rb index d0d9f652d..b2634467d 100644 --- a/app/controllers/stop_areas_controller.rb +++ b/app/controllers/stop_areas_controller.rb @@ -161,7 +161,7 @@ class StopAreasController < ChouetteController    helper_method :current_referential    def stop_area_params -    params.require(:stop_area).permit( +    fields = [        :area_type,        :children_ids,        :city_name, @@ -192,7 +192,8 @@ class StopAreasController < ChouetteController        :kind,        :status,        localized_names: Chouette::StopArea::AVAILABLE_LOCALIZATIONS -    ) +    ] + permitted_custom_fields_params(Chouette::StopArea.custom_fields) # XXX filter on the workgroup +    params.require(:stop_area).permit(fields)    end     # Fake ransack filter diff --git a/app/decorators/route_decorator.rb b/app/decorators/route_decorator.rb index fa6367924..4a173cbb9 100644 --- a/app/decorators/route_decorator.rb +++ b/app/decorators/route_decorator.rb @@ -71,6 +71,23 @@ class RouteDecorator < AF83::Decorator        end      end +    instance_decorator.action_link( +      secondary: :show, +      policy: :create_opposite, +      if: ->{h.has_feature?(:create_opposite_routes) && object.opposite_route.nil?} +    ) do |l| +      l.content h.t('routes.create_opposite.title') +      l.method :post +      l.href do +        h.duplicate_referential_line_route_path( +          context[:referential], +          context[:line], +          object, +          opposite: true +        ) +      end +    end +      instance_decorator.destroy_action_link do |l|        l.data confirm: h.t('routes.actions.destroy_confirm')      end diff --git a/app/helpers/application_helper.rb b/app/helpers/application_helper.rb index 479b661c8..702ca0ffc 100644 --- a/app/helpers/application_helper.rb +++ b/app/helpers/application_helper.rb @@ -14,7 +14,7 @@ module ApplicationHelper    def page_header_title(object)      # Unwrap from decorator, we want to know the object model name      object = object.object if object.try(:object) -     +      if Referential === object        return object.full_name      end @@ -36,8 +36,8 @@ module ApplicationHelper      display = policy(object).synchronize? if policy(object).respond_to?(:synchronize?) rescue false      if display        info = t('last_update', time: l(object.updated_at, format: :short)) -      if object.try(:versions) -        author = object.versions.try(:last).try(:whodunnit) || t('default_whodunnit') +      if object.has_metadata? +        author = object.metadata.modifier_username || t('default_whodunnit')          info   = "#{info} <br/> #{t('whodunnit', author: author)}"        end        out += content_tag :div, info.html_safe, class: 'small last-update' @@ -136,5 +136,9 @@ module ApplicationHelper      url_for(:controller => "/help", :action => "show") + '/' + target    end - +  def permitted_custom_fields_params custom_fields +    [{ +      custom_field_values: custom_fields.map(&:code) +    }] +  end  end diff --git a/app/helpers/compliance_controls_helper.rb b/app/helpers/compliance_controls_helper.rb index 297ae3afa..abf909929 100644 --- a/app/helpers/compliance_controls_helper.rb +++ b/app/helpers/compliance_controls_helper.rb @@ -11,7 +11,7 @@ module ComplianceControlsHelper    def compliance_control_metadatas(compliance_control)      attributes = resource.class.dynamic_attributes -    attributes.push(*resource.control_attributes.keys) if resource.respond_to? :control_attributes +    attributes.push(*resource.control_attributes.keys) if resource&.control_attributes&.keys      {}.tap do |hash|        attributes.each do |attribute| @@ -19,4 +19,4 @@ module ComplianceControlsHelper        end      end    end -end  +end diff --git a/app/helpers/stop_areas_helper.rb b/app/helpers/stop_areas_helper.rb index 1c9d974a1..032f68494 100644 --- a/app/helpers/stop_areas_helper.rb +++ b/app/helpers/stop_areas_helper.rb @@ -68,7 +68,11 @@ module StopAreasHelper    end    def stop_area_registration_number_value stop_area -    stop_area&.registration_number || stop_area&.stop_area_referential&.generate_registration_number +    stop_area&.registration_number +  end + +  def stop_area_registration_number_hint +    t "formtastic.hints.stop_area.registration_number"    end    def stop_area_status(stop_area) diff --git a/app/javascript/journey_patterns/actions/index.js b/app/javascript/journey_patterns/actions/index.js index 666157ea4..ea54f4e05 100644 --- a/app/javascript/journey_patterns/actions/index.js +++ b/app/javascript/journey_patterns/actions/index.js @@ -43,7 +43,7 @@ const actions = {    }),    updateCheckboxValue : (e, index) => ({      type : 'UPDATE_CHECKBOX_VALUE', -    id : e.currentTarget.id, +    position : e.currentTarget.id,      index    }),    checkConfirmModal : (event, callback, stateChanged,dispatch) => { diff --git a/app/javascript/journey_patterns/components/CreateModal.js b/app/javascript/journey_patterns/components/CreateModal.js index 36b5740dc..946c13d9c 100644 --- a/app/javascript/journey_patterns/components/CreateModal.js +++ b/app/javascript/journey_patterns/components/CreateModal.js @@ -57,7 +57,7 @@ export default class CreateModal extends Component {                              <div className='row'>                                <div className='col-lg-6 col-md-6 col-sm-6 col-xs-6'>                                  <div className='form-group'> -                                  <label className='control-label is-required'>{I18n.attribute_name('journey_pattern', 'published_name')}c</label> +                                  <label className='control-label is-required'>{I18n.attribute_name('journey_pattern', 'published_name')}</label>                                    <input                                      type='text'                                      ref='published_name' @@ -120,4 +120,4 @@ CreateModal.propTypes = {    onOpenCreateModal: PropTypes.func.isRequired,    onModalClose: PropTypes.func.isRequired,    onAddJourneyPattern: PropTypes.func.isRequired -}
\ No newline at end of file +} diff --git a/app/javascript/journey_patterns/components/JourneyPattern.js b/app/javascript/journey_patterns/components/JourneyPattern.js index d381b0d50..4eba80030 100644 --- a/app/javascript/journey_patterns/components/JourneyPattern.js +++ b/app/javascript/journey_patterns/components/JourneyPattern.js @@ -45,7 +45,7 @@ export default class JourneyPattern extends Component{            <input              onChange = {(e) => this.props.onCheckboxChange(e)}              type='checkbox' -            id={sp.id} +            id={sp.position}              checked={sp.checked}              disabled={(this.props.value.deletable || this.props.status.policy['journey_patterns.update'] == false || this.props.editMode == false) ? 'disabled' : ''}              > diff --git a/app/javascript/journey_patterns/reducers/journeyPatterns.js b/app/javascript/journey_patterns/reducers/journeyPatterns.js index 6c38e9288..b046f2b38 100644 --- a/app/javascript/journey_patterns/reducers/journeyPatterns.js +++ b/app/javascript/journey_patterns/reducers/journeyPatterns.js @@ -22,7 +22,7 @@ const journeyPattern = (state = {}, action) =>{        }      case 'UPDATE_CHECKBOX_VALUE':        var updatedStopPoints = state.stop_points.map((s) => { -        if (String(s.id) == action.id) { +        if (String(s.position) == action.position) {            return _.assign({}, s, {checked : !s.checked})          }else {            return s diff --git a/app/javascript/time_tables/actions/index.js b/app/javascript/time_tables/actions/index.js index 98b9eab4b..7c79dfe52 100644 --- a/app/javascript/time_tables/actions/index.js +++ b/app/javascript/time_tables/actions/index.js @@ -4,7 +4,6 @@ import reject from 'lodash/reject'  import some from 'lodash/some'  import every from 'lodash/every'  import clone from '../../helpers/clone' -const I18n = clone(window, "I18n")  const actions = {    weekDays: (index) => { @@ -307,10 +306,11 @@ const actions = {        })    },    errorModalKey: (periods, dayTypes) => { -    const withoutPeriodsWithDaysTypes = reject(periods, 'deleted').length == 0 && some(dayTypes) && "withoutPeriodsWithDaysTypes" +    // const withoutPeriodsWithDaysTypes = reject(periods, 'deleted').length == 0 && some(dayTypes) && "withoutPeriodsWithDaysTypes"      const withPeriodsWithoutDayTypes = reject(periods, 'deleted').length > 0 &&  every(dayTypes, dt => dt == false) && "withPeriodsWithoutDayTypes" -    return (withoutPeriodsWithDaysTypes || withPeriodsWithoutDayTypes) && (withoutPeriodsWithDaysTypes ? "withoutPeriodsWithDaysTypes" : "withPeriodsWithoutDayTypes") +    // return (withoutPeriodsWithDaysTypes || withPeriodsWithoutDayTypes) && (withoutPeriodsWithDaysTypes ? "withoutPeriodsWithDaysTypes" : "withPeriodsWithoutDayTypes") +    return withPeriodsWithoutDayTypes    },    errorModalMessage: (errorKey) => { diff --git a/app/javascript/time_tables/components/ConfirmModal.js b/app/javascript/time_tables/components/ConfirmModal.js index 4e8583bc0..e4219348d 100644 --- a/app/javascript/time_tables/components/ConfirmModal.js +++ b/app/javascript/time_tables/components/ConfirmModal.js @@ -2,7 +2,7 @@ import React from 'react'  import PropTypes from 'prop-types' -export default function ConfirmModal({dispatch, modal, onModalAccept, onModalCancel, timetable, metas}, {I18n}) { +export default function ConfirmModal({dispatch, modal, onModalAccept, onModalCancel, timetable, metas}) {    return (      <div className={'modal fade ' + ((modal.type == 'confirm') ? 'in' : '')} id='ConfirmModal'>        <div className='modal-container'> @@ -45,8 +45,4 @@ ConfirmModal.propTypes = {    modal: PropTypes.object.isRequired,    onModalAccept: PropTypes.func.isRequired,    onModalCancel: PropTypes.func.isRequired -} - -ConfirmModal.contextTypes = { -  I18n: PropTypes.object -} +}
\ No newline at end of file diff --git a/app/javascript/time_tables/components/ErrorModal.js b/app/javascript/time_tables/components/ErrorModal.js index 8af12f1d1..a512d28fd 100644 --- a/app/javascript/time_tables/components/ErrorModal.js +++ b/app/javascript/time_tables/components/ErrorModal.js @@ -3,7 +3,7 @@ import PropTypes from 'prop-types'  import actions from '../actions' -export default function ErrorModal({dispatch, modal, onModalClose}, {I18n}) { +export default function ErrorModal({dispatch, modal, onModalClose}) {    return (      <div className={'modal fade ' + ((modal.type == 'error') ? 'in' : '')} id='ErrorModal'>        <div className='modal-container'> @@ -37,8 +37,4 @@ export default function ErrorModal({dispatch, modal, onModalClose}, {I18n}) {  ErrorModal.propTypes = {    modal: PropTypes.object.isRequired,    onModalClose: PropTypes.func.isRequired -} - -ErrorModal.contextTypes = { -  I18n: PropTypes.object -} +}
\ No newline at end of file diff --git a/app/javascript/time_tables/components/Metas.js b/app/javascript/time_tables/components/Metas.js index 08a6e26fe..d9746a379 100644 --- a/app/javascript/time_tables/components/Metas.js +++ b/app/javascript/time_tables/components/Metas.js @@ -4,7 +4,7 @@ import PropTypes from 'prop-types'  import actions from '../actions'  import TagsSelect2 from './TagsSelect2' -export default function Metas({metas, onUpdateDayTypes, onUpdateComment, onUpdateColor, onSelect2Tags, onUnselect2Tags}, {I18n}) { +export default function Metas({metas, onUpdateDayTypes, onUpdateComment, onUpdateColor, onSelect2Tags, onUnselect2Tags}) {    let colorList = ["", "#9B9B9B", "#FFA070", "#C67300", "#7F551B", "#41CCE3", "#09B09C", "#3655D7",   "#6321A0", "#E796C6", "#DD2DAA"]    return (      <div className='form-horizontal'> @@ -134,8 +134,4 @@ Metas.propTypes = {    onUpdateColor: PropTypes.func.isRequired,    onSelect2Tags: PropTypes.func.isRequired,    onUnselect2Tags: PropTypes.func.isRequired -} - -Metas.contextTypes = { -  I18n: PropTypes.object -} +}
\ No newline at end of file diff --git a/app/javascript/time_tables/components/PeriodForm.js b/app/javascript/time_tables/components/PeriodForm.js index d17a246f7..36ed6cfdf 100644 --- a/app/javascript/time_tables/components/PeriodForm.js +++ b/app/javascript/time_tables/components/PeriodForm.js @@ -33,7 +33,7 @@ const makeYearsOptions = (yearSelected) => {    return arr  } -export default function PeriodForm({modal, timetable, metas, onOpenAddPeriodForm, onClosePeriodForm, onUpdatePeriodForm, onValidatePeriodForm}, {I18n}) { +export default function PeriodForm({modal, timetable, metas, onOpenAddPeriodForm, onClosePeriodForm, onUpdatePeriodForm, onValidatePeriodForm}) {    return (      <div className="container-fluid">        <div className="row"> @@ -143,8 +143,4 @@ PeriodForm.propTypes = {    onUpdatePeriodForm: PropTypes.func.isRequired,    onValidatePeriodForm: PropTypes.func.isRequired,    timetable: PropTypes.object.isRequired -} - -PeriodForm.contextTypes = { -  I18n: PropTypes.object -} +}
\ No newline at end of file diff --git a/app/javascript/time_tables/components/PeriodManager.js b/app/javascript/time_tables/components/PeriodManager.js index 6b817fe73..6871d0b9b 100644 --- a/app/javascript/time_tables/components/PeriodManager.js +++ b/app/javascript/time_tables/components/PeriodManager.js @@ -55,7 +55,7 @@ export default class PeriodManager extends Component {                  type='button'                  onClick={() => this.props.onOpenEditPeriodForm(this.props.value, this.props.index)}                > -                Modifier +                {I18n.t('actions.edit')}                </button>              </li>              <li className='delete-action'> @@ -64,7 +64,7 @@ export default class PeriodManager extends Component {                  onClick={() => this.props.onDeletePeriod(this.props.index, this.props.metas.day_types)}                >                  <span className='fa fa-trash'></span> -                Supprimer +                {I18n.t('actions.destroy')}                </button>              </li>            </ul> @@ -79,8 +79,4 @@ PeriodManager.propTypes = {    currentDate: PropTypes.object.isRequired,    onDeletePeriod: PropTypes.func.isRequired,    onOpenEditPeriodForm: PropTypes.func.isRequired -} - -PeriodManager.contextTypes = { -  I18n: PropTypes.object  }
\ No newline at end of file diff --git a/app/javascript/time_tables/components/TagsSelect2.js b/app/javascript/time_tables/components/TagsSelect2.js index 43cf59fdf..dd8d6e9c0 100644 --- a/app/javascript/time_tables/components/TagsSelect2.js +++ b/app/javascript/time_tables/components/TagsSelect2.js @@ -40,7 +40,7 @@ export default class TagsSelect2 extends Component {            allowClear: true,            theme: 'bootstrap',            width: '100%', -          placeholder: this.context.I18n.t('time_tables.edit.select2.tag.placeholder'), +          placeholder: I18n.t('time_tables.edit.select2.tag.placeholder'),            ajax: {              url: origin + path + '/tags.json',              dataType: 'json', @@ -74,8 +74,4 @@ export default class TagsSelect2 extends Component {  const formatRepo = (props) => {    if(props.name) return props.name -} - -TagsSelect2.contextTypes = { -  I18n: PropTypes.object  }
\ No newline at end of file diff --git a/app/javascript/time_tables/components/Timetable.js b/app/javascript/time_tables/components/Timetable.js index 991f31435..3779fa2d0 100644 --- a/app/javascript/time_tables/components/Timetable.js +++ b/app/javascript/time_tables/components/Timetable.js @@ -31,11 +31,11 @@ export default class Timetable extends Component {          <div className="table table-2entries mb-sm">            <div className="t2e-head w20">              <div className="th"> -              <div className="strong">{this.context.I18n.t('time_tables.edit.synthesis')}</div> +              <div className="strong">{I18n.t('time_tables.edit.synthesis')}</div>              </div> -            <div className="td"><span>{this.context.I18n.t('time_tables.edit.day_types')}</span></div> -            <div className="td"><span>{this.context.I18n.t('time_tables.edit.periods')}</span></div> -            <div className="td"><span>{this.context.I18n.t('time_tables.edit.exceptions')}</span></div> +            <div className="td"><span>{I18n.t('time_tables.edit.day_types')}</span></div> +            <div className="td"><span>{I18n.t('time_tables.edit.periods')}</span></div> +            <div className="td"><span>{I18n.t('time_tables.edit.exceptions')}</span></div>            </div>            <div className="t2e-item-list w80">              <div> @@ -109,8 +109,4 @@ Timetable.propTypes = {    onDeletePeriod: PropTypes.func.isRequired,    onExcludeDateFromPeriod: PropTypes.func.isRequired,    onIncludeDateInPeriod: PropTypes.func.isRequired -} - -Timetable.contextTypes = { -  I18n: PropTypes.object -} +}
\ No newline at end of file diff --git a/app/javascript/time_tables/containers/App.js b/app/javascript/time_tables/containers/App.js index 5963f8f1d..c12e33ef5 100644 --- a/app/javascript/time_tables/containers/App.js +++ b/app/javascript/time_tables/containers/App.js @@ -10,7 +10,6 @@ import SaveTimetable from './SaveTimetable'  import ConfirmModal from './ConfirmModal'  import ErrorModal from './ErrorModal'  import clone from '../../helpers/clone' -const I18n = clone(window, "I18n", true)  class App extends Component {    componentDidMount(){ diff --git a/app/javascript/vehicle_journeys/actions/index.js b/app/javascript/vehicle_journeys/actions/index.js index e00e9b1b0..537dcfc06 100644 --- a/app/javascript/vehicle_journeys/actions/index.js +++ b/app/javascript/vehicle_journeys/actions/index.js @@ -113,14 +113,9 @@ const actions = {      type : 'EDIT_PURCHASE_WINDOWS_VEHICLEJOURNEY_MODAL',      vehicleJourneys    }), -  selectPurchaseWindowsModal: (selectedWindow) =>({ +  selectPurchaseWindowsModal: (selectedItem) =>({      type: 'SELECT_PURCHASE_WINDOW_MODAL', -    selectedItem:{ -      id: selectedWindow.id, -      name: selectedWindow.name, -      color: selectedWindow.color, -      objectid: selectedWindow.objectid -    } +    selectedItem    }),    addSelectedPurchaseWindow: () => ({      type: 'ADD_SELECTED_PURCHASE_WINDOW' diff --git a/app/javascript/vehicle_journeys/components/Filters.js b/app/javascript/vehicle_journeys/components/Filters.js index ae3ab3476..93fe015a8 100644 --- a/app/javascript/vehicle_journeys/components/Filters.js +++ b/app/javascript/vehicle_journeys/components/Filters.js @@ -145,12 +145,12 @@ export default function Filters({filters, pagination, missions, onFilter, onRese              <span                className='btn btn-link'                onClick={(e) => onResetFilters(e, pagination)}> -              Effacer +              {I18n.t('actions.erase')}              </span>              <span                className='btn btn-default'                onClick={(e) => onFilter(e, pagination)}> -              Filtrer +              {I18n.t('actions.filter')}              </span>            </div>          </div> diff --git a/app/javascript/vehicle_journeys/components/tools/CustomFieldsInputs.js b/app/javascript/vehicle_journeys/components/tools/CustomFieldsInputs.js index 90d72a801..827c36b76 100644 --- a/app/javascript/vehicle_journeys/components/tools/CustomFieldsInputs.js +++ b/app/javascript/vehicle_journeys/components/tools/CustomFieldsInputs.js @@ -27,6 +27,19 @@ export default class CustomFieldsInputs extends Component {      )    } +  stringInput(cf){ +    return( +      <input +        type='text' +        ref={'custom_fields.' + cf.code} +        className='form-control' +        disabled={this.props.disabled} +        defaultValue={cf.value} +        onChange={(e) => this.props.onUpdate(cf.code, e.target.value) } +        /> +    ) +  } +    render() {      return (        <div> diff --git a/app/javascript/vehicle_journeys/components/tools/TimetablesEditVehicleJourney.js b/app/javascript/vehicle_journeys/components/tools/TimetablesEditVehicleJourney.js index 7a2686c13..9ee2e1849 100644 --- a/app/javascript/vehicle_journeys/components/tools/TimetablesEditVehicleJourney.js +++ b/app/javascript/vehicle_journeys/components/tools/TimetablesEditVehicleJourney.js @@ -58,7 +58,7 @@ export default class TimetablesEditVehicleJourney extends Component {                                  <div className='wrapper'>                                    <div>                                      <div className='form-group'> -                                      <label className='control-label'>{this.props.modal.modalProps.timetables.length == 0 ? I18n.t('vehicle_journeys.vehicle_journeys_matrix.no_associated_timetables'): I18n.t('vehicle_journeys.form.timetables')}</label> +                                      <label className='control-label'>{this.props.modal.modalProps.timetables.length == 0 ? I18n.t('vehicle_journeys.vehicle_journeys_matrix.no_associated_timetables'): I18n.t('vehicle_journeys.form.time_tables')}</label>                                      </div>                                    </div>                                    <div></div> diff --git a/app/javascript/vehicle_journeys/components/tools/select2s/CompanySelect2.js b/app/javascript/vehicle_journeys/components/tools/select2s/CompanySelect2.js index b7e9691c1..60ad439b8 100644 --- a/app/javascript/vehicle_journeys/components/tools/select2s/CompanySelect2.js +++ b/app/javascript/vehicle_journeys/components/tools/select2s/CompanySelect2.js @@ -31,7 +31,7 @@ export default class BSelect4 extends Component {            theme: 'bootstrap',            width: '100%',            placeholder: I18n.t('vehicle_journeys.vehicle_journeys_matrix.affect_company'), -          language: require('./fr'), +          language: require('./language'),            ajax: {              url: origin + path + '/companies.json' + '?line_id=' + line,              dataType: 'json', diff --git a/app/javascript/vehicle_journeys/components/tools/select2s/MissionSelect2.js b/app/javascript/vehicle_journeys/components/tools/select2s/MissionSelect2.js index 96b34125d..cec39ab4e 100644 --- a/app/javascript/vehicle_journeys/components/tools/select2s/MissionSelect2.js +++ b/app/javascript/vehicle_journeys/components/tools/select2s/MissionSelect2.js @@ -75,7 +75,7 @@ export default class BSelect4 extends Component {        escapeMarkup: function (markup) { return markup; },        templateResult: formatRepo,        placeholder: I18n.t('vehicle_journeys.vehicle_journeys_matrix.filters.journey_pattern'), -      language: require('./fr'), +      language: require('./language'),        allowClear: false,        escapeMarkup: function (markup) { return markup; },      } diff --git a/app/javascript/vehicle_journeys/components/tools/select2s/TimetableSelect2.js b/app/javascript/vehicle_journeys/components/tools/select2s/TimetableSelect2.js index 9a345b464..d5aad20d0 100644 --- a/app/javascript/vehicle_journeys/components/tools/select2s/TimetableSelect2.js +++ b/app/javascript/vehicle_journeys/components/tools/select2s/TimetableSelect2.js @@ -27,7 +27,7 @@ export default class BSelect4 extends Component {            theme: 'bootstrap',            width: '100%',            placeholder: I18n.t('vehicle_journeys.vehicle_journeys_matrix.filters.timetable'), -          language: require('./fr'), +          language: require('./language'),            ajax: {              url: origin + path + this.props.chunkURL,              dataType: 'json', diff --git a/app/javascript/vehicle_journeys/components/tools/select2s/VJSelect2.js b/app/javascript/vehicle_journeys/components/tools/select2s/VJSelect2.js index f5881cef7..50a941b6d 100644 --- a/app/javascript/vehicle_journeys/components/tools/select2s/VJSelect2.js +++ b/app/javascript/vehicle_journeys/components/tools/select2s/VJSelect2.js @@ -27,7 +27,7 @@ export default class BSelect4b extends Component {            theme: 'bootstrap',            placeholder: I18n.t('vehicle_journeys.vehicle_journeys_matrix.filters.id'),            width: '100%', -          language: require('./fr'), +          language: require('./language'),            ajax: {              url: origin + path + '/vehicle_journeys.json',              dataType: 'json', diff --git a/app/javascript/vehicle_journeys/components/tools/select2s/fr.js b/app/javascript/vehicle_journeys/components/tools/select2s/fr.js deleted file mode 100644 index 20154d412..000000000 --- a/app/javascript/vehicle_journeys/components/tools/select2s/fr.js +++ /dev/null @@ -1,9 +0,0 @@ -module.exports = { -  errorLoading:function(){return"Les résultats ne peuvent pas être chargés."}, -  inputTooLong:function(e){var t=e.input.length-e.maximum,n="Supprimez "+t+" caractère";return t!==1&&(n+="s"),n}, -  inputTooShort:function(e){var t=e.minimum-e.input.length,n="Saisissez "+t+" caractère";return t!==1&&(n+="s"),n}, -  loadingMore:function(){return"Chargement de résultats supplémentaires…"}, -  maximumSelected:function(e){var t="Vous pouvez seulement sélectionner "+e.maximum+" élément";return e.maximum!==1&&(t+="s"),t}, -  noResults:function(){return"Aucun résultat trouvé"}, -  searching:function(){return"Recherche en cours…"} -} diff --git a/app/javascript/vehicle_journeys/components/tools/select2s/language.js b/app/javascript/vehicle_journeys/components/tools/select2s/language.js new file mode 100644 index 000000000..9d587f96e --- /dev/null +++ b/app/javascript/vehicle_journeys/components/tools/select2s/language.js @@ -0,0 +1,9 @@ +module.exports = { +  errorLoading: () => I18n.t('select2.error_loading'), +  inputTooLong: (e) => I18n.t('select2.input_too_short', { count: e.input.length - e.maximum}), +  inputTooShort: (e) => I18n.t('select2.input_too_long', { count: e.minimum - e.input.length }), +  loadingMore: () => I18n.t('select2.loading_more'), +  maximumSelected: (e) => I18n.t('select2.maximum_selected', {count: e.maximum}), +  noResults: () => I18n.t('select2.no_results'), +  searching: () => I18n.t('select2.searching') +} diff --git a/app/models/api/v1/api_key.rb b/app/models/api/v1/api_key.rb index 09c6f77ac..e6ceb977a 100644 --- a/app/models/api/v1/api_key.rb +++ b/app/models/api/v1/api_key.rb @@ -1,7 +1,8 @@  module Api    module V1 -    class ApiKey < ::ActiveRecord::Base -      has_paper_trail +    class ApiKey < ::ApplicationModel +      has_metadata +              before_create :generate_access_token        belongs_to :referential, :class_name => '::Referential'        belongs_to :organisation, :class_name => '::Organisation' @@ -47,4 +48,3 @@ module Api      end    end  end - diff --git a/app/models/application_model.rb b/app/models/application_model.rb new file mode 100644 index 000000000..1a2a5099d --- /dev/null +++ b/app/models/application_model.rb @@ -0,0 +1,5 @@ +class ApplicationModel < ::ActiveRecord::Base +  include MetadataSupport + +  self.abstract_class = true +end diff --git a/app/models/calendar.rb b/app/models/calendar.rb index 32eedf9ea..39e2b2cff 100644 --- a/app/models/calendar.rb +++ b/app/models/calendar.rb @@ -2,13 +2,13 @@ require 'range_ext'  require_relative 'calendar/date_value'  require_relative 'calendar/period' -class Calendar < ActiveRecord::Base +class Calendar < ApplicationModel    include DateSupport    include PeriodSupport    include ApplicationDaysSupport    include TimetableSupport -  has_paper_trail class_name: 'PublicVersion' +  has_metadata    belongs_to :organisation    belongs_to :workgroup diff --git a/app/models/calendar/date_value.rb b/app/models/calendar/date_value.rb index a4a405d43..f50b4237c 100644 --- a/app/models/calendar/date_value.rb +++ b/app/models/calendar/date_value.rb @@ -1,4 +1,4 @@ -class Calendar < ActiveRecord::Base +class Calendar < ApplicationModel    class DateValue      include ActiveAttr::Model diff --git a/app/models/calendar/period.rb b/app/models/calendar/period.rb index 8b3e4109b..07926e818 100644 --- a/app/models/calendar/period.rb +++ b/app/models/calendar/period.rb @@ -1,4 +1,4 @@ -class Calendar < ActiveRecord::Base +class Calendar < ApplicationModel    class Period      include ActiveAttr::Model diff --git a/app/models/chouette/access_link.rb b/app/models/chouette/access_link.rb index 6b08443be..7ab8ca715 100644 --- a/app/models/chouette/access_link.rb +++ b/app/models/chouette/access_link.rb @@ -1,6 +1,6 @@  module Chouette    class AccessLink < Chouette::TridentActiveRecord -    has_paper_trail +    has_metadata      include ObjectidSupport      attr_accessor :access_link_type, :link_orientation_type, :link_key diff --git a/app/models/chouette/access_point.rb b/app/models/chouette/access_point.rb index ac6580015..884460881 100644 --- a/app/models/chouette/access_point.rb +++ b/app/models/chouette/access_point.rb @@ -4,7 +4,7 @@ require 'geo_ruby'  module Chouette    class AccessPoint < Chouette::ActiveRecord -    has_paper_trail +    has_metadata      include Geokit::Mappable      include ProjectionFields diff --git a/app/models/chouette/active_record.rb b/app/models/chouette/active_record.rb index c2aab9d50..27f5426b3 100644 --- a/app/models/chouette/active_record.rb +++ b/app/models/chouette/active_record.rb @@ -1,7 +1,8 @@  #require "active_record"  require 'deep_cloneable'  module Chouette -  class ActiveRecord < ::ActiveRecord::Base +  class ActiveRecord < ::ApplicationModel +      self.abstract_class = true      before_save :nil_if_blank, :set_data_source_ref diff --git a/app/models/chouette/company.rb b/app/models/chouette/company.rb index f5ed4e0f8..9d5737a6c 100644 --- a/app/models/chouette/company.rb +++ b/app/models/chouette/company.rb @@ -1,9 +1,11 @@  module Chouette    class Company < Chouette::ActiveRecord +    has_metadata +      include CompanyRestrictions      include LineReferentialSupport      include ObjectidSupport -    has_paper_trail class_name: 'PublicVersion' +    include CustomFieldsSupport      has_many :lines diff --git a/app/models/chouette/connection_link.rb b/app/models/chouette/connection_link.rb index c53d6f5f1..fb93e5f90 100644 --- a/app/models/chouette/connection_link.rb +++ b/app/models/chouette/connection_link.rb @@ -1,6 +1,6 @@  module Chouette    class ConnectionLink < Chouette::TridentActiveRecord -    has_paper_trail +    has_metadata      include ObjectidSupport      include ConnectionLinkRestrictions diff --git a/app/models/chouette/for_alighting_enumerations.rb b/app/models/chouette/for_alighting_enumerations.rb index ab07a670d..2e15fcb58 100644 --- a/app/models/chouette/for_alighting_enumerations.rb +++ b/app/models/chouette/for_alighting_enumerations.rb @@ -3,6 +3,6 @@ module Chouette      extend Enumerize      extend ActiveModel::Naming -    enumerize :for_alighting, in: %w[normal forbidden request_stop is_flexible] +    enumerize :for_alighting, in: %w[normal forbidden request_stop is_flexible], default: :normal    end  end diff --git a/app/models/chouette/for_boarding_enumerations.rb b/app/models/chouette/for_boarding_enumerations.rb index 48f8762c2..0190bf805 100644 --- a/app/models/chouette/for_boarding_enumerations.rb +++ b/app/models/chouette/for_boarding_enumerations.rb @@ -3,6 +3,6 @@ module Chouette      extend Enumerize      extend ActiveModel::Naming -    enumerize :for_boarding, in: %w[normal forbidden request_stop is_flexible] +    enumerize :for_boarding, in: %w[normal forbidden request_stop is_flexible], default: :normal    end  end diff --git a/app/models/chouette/group_of_line.rb b/app/models/chouette/group_of_line.rb index 3b6a7cea7..a30c34ce7 100644 --- a/app/models/chouette/group_of_line.rb +++ b/app/models/chouette/group_of_line.rb @@ -1,6 +1,6 @@  module Chouette    class GroupOfLine < Chouette::ActiveRecord -    has_paper_trail +    has_metadata      include ObjectidSupport      include GroupOfLineRestrictions      include LineReferentialSupport diff --git a/app/models/chouette/journey_pattern.rb b/app/models/chouette/journey_pattern.rb index 5a5132200..830a6a808 100644 --- a/app/models/chouette/journey_pattern.rb +++ b/app/models/chouette/journey_pattern.rb @@ -1,6 +1,6 @@  module Chouette    class JourneyPattern < Chouette::TridentActiveRecord -    has_paper_trail +    has_metadata      include ChecksumSupport      include JourneyPatternRestrictions      include ObjectidSupport diff --git a/app/models/chouette/line.rb b/app/models/chouette/line.rb index 9f05b611a..4b5d1a68d 100644 --- a/app/models/chouette/line.rb +++ b/app/models/chouette/line.rb @@ -1,6 +1,6 @@  module Chouette    class Line < Chouette::ActiveRecord -    has_paper_trail class_name: 'PublicVersion' +    has_metadata      include LineRestrictions      include LineReferentialSupport      include ObjectidSupport @@ -51,6 +51,14 @@ module Chouette          )      } +    scope :for_organisation, ->(organisation){ +      if objectids = organisation&.lines_scope +        where(objectid: objectids) +      else +        all +      end +    } +      def self.nullable_attributes        [:published_name, :number, :comment, :url, :color, :text_color, :stable_id]      end diff --git a/app/models/chouette/network.rb b/app/models/chouette/network.rb index 942fc5d67..4802d7592 100644 --- a/app/models/chouette/network.rb +++ b/app/models/chouette/network.rb @@ -1,6 +1,6 @@  module Chouette    class Network < Chouette::ActiveRecord -    has_paper_trail class_name: 'PublicVersion' +    has_metadata      include NetworkRestrictions      include LineReferentialSupport      include ObjectidSupport diff --git a/app/models/chouette/pt_link.rb b/app/models/chouette/pt_link.rb index 399539d44..680632a14 100644 --- a/app/models/chouette/pt_link.rb +++ b/app/models/chouette/pt_link.rb @@ -2,7 +2,7 @@ require 'geokit'  module Chouette    class PtLink < Chouette::ActiveRecord -    has_paper_trail +    has_metadata      include Geokit::Mappable      def geometry diff --git a/app/models/chouette/purchase_window.rb b/app/models/chouette/purchase_window.rb index 4c8014780..e10b106ec 100644 --- a/app/models/chouette/purchase_window.rb +++ b/app/models/chouette/purchase_window.rb @@ -11,7 +11,7 @@ module Chouette      enumerize :color, in: %w(#9B9B9B #FFA070 #C67300 #7F551B #41CCE3 #09B09C #3655D7 #6321A0 #E796C6 #DD2DAA) -    has_paper_trail +    has_metadata      belongs_to :referential      has_and_belongs_to_many :vehicle_journeys, :class_name => 'Chouette::VehicleJourney' diff --git a/app/models/chouette/route.rb b/app/models/chouette/route.rb index 13288bc6b..9c7a3e6d9 100644 --- a/app/models/chouette/route.rb +++ b/app/models/chouette/route.rb @@ -1,6 +1,7 @@  module Chouette    class Route < Chouette::TridentActiveRecord -    has_paper_trail +    has_metadata +      include RouteRestrictions      include ChecksumSupport      include ObjectidSupport @@ -9,7 +10,6 @@ module Chouette      enumerize :direction, in: %i(straight_forward backward clockwise counter_clockwise north north_west west south_west south south_east east north_east)      enumerize :wayback, in: %i(outbound inbound), default: :outbound -      def self.nullable_attributes        [:published_name, :comment, :number, :name, :direction, :wayback]      end @@ -68,28 +68,34 @@ module Chouette      validates_presence_of :published_name      validates_presence_of :line      validates :wayback, inclusion: { in: self.wayback.values } -      after_save :calculate_costs!, if: ->() { TomTom.enabled? } - -    def duplicate +     +    def duplicate opposite=false        overrides = {          'opposite_route_id' => nil,          'name' => I18n.t('activerecord.copy', name: self.name)        } +      keys_for_create = attributes.keys - %w{id objectid created_at updated_at}        atts_for_create = attributes -        .slice!(*%w{id objectid created_at updated_at}) +        .slice(*keys_for_create)          .merge(overrides) +      if opposite +        atts_for_create[:wayback] = self.opposite_wayback +        atts_for_create[:name] = I18n.t('routes.opposite', name: self.name) +        atts_for_create[:published_name] = atts_for_create[:name] +        atts_for_create[:opposite_route_id] = self.id +      end        new_route = self.class.create!(atts_for_create) -      duplicate_stop_points(for_route: new_route) +      duplicate_stop_points(for_route: new_route, opposite: opposite)        new_route      end -    def duplicate_stop_points(for_route:) -      stop_points.each(&duplicate_stop_point(for_route: for_route)) +    def duplicate_stop_points(for_route:, opposite: false) +      stop_points.each(&duplicate_stop_point(for_route: for_route, opposite: opposite))      end -    def duplicate_stop_point(for_route:) +    def duplicate_stop_point(for_route:, opposite: false)        -> stop_point do -        stop_point.duplicate(for_route: for_route) +        stop_point.duplicate(for_route: for_route, opposite: opposite)        end      end diff --git a/app/models/chouette/routing_constraint_zone.rb b/app/models/chouette/routing_constraint_zone.rb index 58703598e..886eadc6c 100644 --- a/app/models/chouette/routing_constraint_zone.rb +++ b/app/models/chouette/routing_constraint_zone.rb @@ -1,6 +1,6 @@  module Chouette    class RoutingConstraintZone < Chouette::TridentActiveRecord -    has_paper_trail +    has_metadata      include ChecksumSupport      include ObjectidSupport diff --git a/app/models/chouette/stop_area.rb b/app/models/chouette/stop_area.rb index 5f2c92acb..4ddc7403b 100644 --- a/app/models/chouette/stop_area.rb +++ b/app/models/chouette/stop_area.rb @@ -2,11 +2,12 @@ require 'geokit'  require 'geo_ruby'  module Chouette    class StopArea < Chouette::ActiveRecord -    has_paper_trail class_name: 'PublicVersion' +    has_metadata      include ProjectionFields      include StopAreaRestrictions      include StopAreaReferentialSupport      include ObjectidSupport +    include CustomFieldsSupport      extend Enumerize      enumerize :area_type, in: Chouette::AreaType::ALL @@ -49,7 +50,7 @@ module Chouette      validate :registration_number_is_set      before_validation do -      self.registration_number ||= self.stop_area_referential.generate_registration_number +      self.registration_number = self.stop_area_referential.generate_registration_number unless self.registration_number.present?      end      def self.nullable_attributes @@ -90,7 +91,7 @@ module Chouette        end        unless self.stop_area_referential.validates_registration_number(self.registration_number) -        errors.add(:registration_number, I18n.t('stop_areas.errors.registration_number.invalid')) +        errors.add(:registration_number, I18n.t('stop_areas.errors.registration_number.invalid', mask: self.stop_area_referential.registration_number_format))        end      end diff --git a/app/models/chouette/stop_point.rb b/app/models/chouette/stop_point.rb index 6b363cd93..1df1a664a 100644 --- a/app/models/chouette/stop_point.rb +++ b/app/models/chouette/stop_point.rb @@ -1,6 +1,6 @@  module Chouette    class StopPoint < Chouette::TridentActiveRecord -    has_paper_trail +    has_metadata      def self.policy_class        RoutePolicy      end @@ -39,11 +39,12 @@ module Chouette        end      end -    def duplicate(for_route:) +    def duplicate(for_route:, opposite: false)        keys_for_create = attributes.keys - %w{id objectid created_at updated_at}        atts_for_create = attributes          .slice(*keys_for_create)          .merge('route_id' => for_route.id) +      atts_for_create["position"] = self.route.stop_points.size - atts_for_create["position"] if opposite        self.class.create!(atts_for_create)      end diff --git a/app/models/chouette/time_table.rb b/app/models/chouette/time_table.rb index 506e498b8..b59c95665 100644 --- a/app/models/chouette/time_table.rb +++ b/app/models/chouette/time_table.rb @@ -1,6 +1,6 @@  module Chouette    class TimeTable < Chouette::TridentActiveRecord -    has_paper_trail +    has_metadata      include ChecksumSupport      include TimeTableRestrictions      include ObjectidSupport diff --git a/app/models/chouette/timeband.rb b/app/models/chouette/timeband.rb index 5a4e17b98..38260b755 100644 --- a/app/models/chouette/timeband.rb +++ b/app/models/chouette/timeband.rb @@ -9,7 +9,7 @@ module Chouette    class Timeband < Chouette::TridentActiveRecord      include ObjectidSupport -    has_paper_trail +    has_metadata      validates :start_time, :end_time, presence: true      validates_with Chouette::TimebandValidator diff --git a/app/models/chouette/vehicle_journey.rb b/app/models/chouette/vehicle_journey.rb index 525036077..54aad290c 100644 --- a/app/models/chouette/vehicle_journey.rb +++ b/app/models/chouette/vehicle_journey.rb @@ -1,7 +1,7 @@  # coding: utf-8  module Chouette    class VehicleJourney < Chouette::TridentActiveRecord -    has_paper_trail +    has_metadata      include ChecksumSupport      include CustomFieldsSupport      include VehicleJourneyRestrictions @@ -346,6 +346,33 @@ module Chouette        end      end +    def fill_passing_time_at_borders +      encountered_borders = [] +      previous_stop = nil +      vehicle_journey_at_stops.each do |vjas| +        sp = vjas.stop_point +        if sp.stop_area.area_type == "border" +          encountered_borders << vjas +        else +          if encountered_borders.any? +            before_cost = journey_pattern.costs_between previous_stop.stop_point, encountered_borders.first.stop_point +            after_cost = journey_pattern.costs_between encountered_borders.last.stop_point, sp +            if before_cost && before_cost[:distance] && after_cost && after_cost[:distance] +              before_distance = before_cost[:distance].to_f +              after_distance = after_cost[:distance].to_f +              time = previous_stop.departure_time + before_distance / (before_distance+after_distance) * (vjas.arrival_time - previous_stop.departure_time) +              encountered_borders.each do |b| +                b.update_attribute :arrival_time, time +                b.update_attribute :departure_time, time +              end +            end +            encountered_borders = [] +          end +          previous_stop = vjas +        end +      end +    end +      def self.matrix(vehicle_journeys)        Hash[*VehicleJourneyAtStop.where(vehicle_journey_id: vehicle_journeys.pluck(:id)).map do |vjas|          [ "#{vjas.vehicle_journey_id}-#{vjas.stop_point_id}", vjas] diff --git a/app/models/clean_up.rb b/app/models/clean_up.rb index 7aab7f32e..ec47489e9 100644 --- a/app/models/clean_up.rb +++ b/app/models/clean_up.rb @@ -1,4 +1,4 @@ -class CleanUp < ActiveRecord::Base +class CleanUp < ApplicationModel    extend Enumerize    include AASM    belongs_to :referential diff --git a/app/models/clean_up_result.rb b/app/models/clean_up_result.rb index 24d262deb..dff4f5acd 100644 --- a/app/models/clean_up_result.rb +++ b/app/models/clean_up_result.rb @@ -1,3 +1,3 @@ -class CleanUpResult < ActiveRecord::Base +class CleanUpResult < ApplicationModel    belongs_to :clean_up  end diff --git a/app/models/compliance_check.rb b/app/models/compliance_check.rb index 9d817e146..4ef6170e9 100644 --- a/app/models/compliance_check.rb +++ b/app/models/compliance_check.rb @@ -1,4 +1,4 @@ -class ComplianceCheck < ActiveRecord::Base +class ComplianceCheck < ApplicationModel    include ComplianceItemSupport    self.inheritance_column = nil diff --git a/app/models/compliance_check_block.rb b/app/models/compliance_check_block.rb index 059547e1b..e4f4c1c37 100644 --- a/app/models/compliance_check_block.rb +++ b/app/models/compliance_check_block.rb @@ -1,4 +1,4 @@ -class ComplianceCheckBlock < ActiveRecord::Base +class ComplianceCheckBlock < ApplicationModel    include StifTransportModeEnumerations    include StifTransportSubmodeEnumerations diff --git a/app/models/compliance_check_message.rb b/app/models/compliance_check_message.rb index 738bd4a4b..a4b1062f6 100644 --- a/app/models/compliance_check_message.rb +++ b/app/models/compliance_check_message.rb @@ -1,4 +1,4 @@ -class ComplianceCheckMessage < ActiveRecord::Base +class ComplianceCheckMessage < ApplicationModel    extend Enumerize    belongs_to :compliance_check_set diff --git a/app/models/compliance_check_resource.rb b/app/models/compliance_check_resource.rb index 777254aaf..d2f782e2b 100644 --- a/app/models/compliance_check_resource.rb +++ b/app/models/compliance_check_resource.rb @@ -1,4 +1,4 @@ -class ComplianceCheckResource < ActiveRecord::Base +class ComplianceCheckResource < ApplicationModel    extend Enumerize    belongs_to :compliance_check_set diff --git a/app/models/compliance_check_set.rb b/app/models/compliance_check_set.rb index 49d324c53..8b1dbdd68 100644 --- a/app/models/compliance_check_set.rb +++ b/app/models/compliance_check_set.rb @@ -1,6 +1,7 @@ -class ComplianceCheckSet < ActiveRecord::Base +class ComplianceCheckSet < ApplicationModel    extend Enumerize -  has_paper_trail class_name: 'PublicVersion' + +  has_metadata    belongs_to :referential    belongs_to :compliance_control_set diff --git a/app/models/compliance_control.rb b/app/models/compliance_control.rb index 1cc06f927..672fb128c 100644 --- a/app/models/compliance_control.rb +++ b/app/models/compliance_control.rb @@ -1,4 +1,4 @@ -class ComplianceControl < ActiveRecord::Base +class ComplianceControl < ApplicationModel    include ComplianceItemSupport    class << self diff --git a/app/models/compliance_control_block.rb b/app/models/compliance_control_block.rb index d7d84fd06..6a3c8a34e 100644 --- a/app/models/compliance_control_block.rb +++ b/app/models/compliance_control_block.rb @@ -1,4 +1,4 @@ -class ComplianceControlBlock < ActiveRecord::Base +class ComplianceControlBlock < ApplicationModel    include StifTransportModeEnumerations    include StifTransportSubmodeEnumerations @@ -12,6 +12,8 @@ class ComplianceControlBlock < ActiveRecord::Base    validates :transport_mode, presence: true    validates :compliance_control_set, presence: true +  validates_uniqueness_of :condition_attributes, scope: :compliance_control_set_id +    def name      ApplicationController.helpers.transport_mode_text(self)    end diff --git a/app/models/compliance_control_set.rb b/app/models/compliance_control_set.rb index c0ea692f2..4f0f86d08 100644 --- a/app/models/compliance_control_set.rb +++ b/app/models/compliance_control_set.rb @@ -1,5 +1,6 @@ -class ComplianceControlSet < ActiveRecord::Base -  has_paper_trail class_name: 'PublicVersion' +class ComplianceControlSet < ApplicationModel +  has_metadata +    belongs_to :organisation    has_many :compliance_control_blocks, dependent: :destroy    has_many :compliance_controls, dependent: :destroy diff --git a/app/models/concerns/custom_fields_support.rb b/app/models/concerns/custom_fields_support.rb index 6c76bd653..017f496a8 100644 --- a/app/models/concerns/custom_fields_support.rb +++ b/app/models/concerns/custom_fields_support.rb @@ -3,22 +3,51 @@ module CustomFieldsSupport    included do      validate :custom_fields_values_are_valid +    after_initialize :initialize_custom_fields -    def self.custom_fields -      CustomField.where(resource_type: self.name.split("::").last) +    def self.custom_fields workgroup=:all +      fields = CustomField.where(resource_type: self.name.split("::").last) +      fields = fields.where(workgroup_id: workgroup&.id) if workgroup != :all +      fields      end -    def custom_fields -      CustomField::Collection.new self +    def method_missing method_name, *args +      if method_name =~ /custom_field_*/ && method_name.to_sym != :custom_field_values && !@custom_fields_initialized +        initialize_custom_fields +        send method_name, *args +      else +        super method_name, *args +      end +    end + +    def custom_fields workgroup=:all +      CustomField::Collection.new self, workgroup +    end + +    def custom_field_values= vals +      out = {} +      custom_fields.each do |code, field| +        out[code] = field.preprocess_value_for_assignment(vals.symbolize_keys[code.to_sym]) +      end +      write_attribute :custom_field_values, out +    end + +    def initialize_custom_fields +      self.custom_field_values ||= {} +      custom_fields(:all).values.each &:initialize_custom_field +      custom_fields(:all).each do |k, v| +        custom_field_values[k] ||= v.default_value +      end +      @custom_fields_initialized = true      end      def custom_field_value key -      (custom_field_values || {})[key.to_s] +      (custom_field_values&.stringify_keys || {})[key.to_s]      end      private      def custom_fields_values_are_valid -      custom_fields.values.all?{|cf| cf.valid?} +      custom_fields(:all).values.all?{|cf| cf.valid?}      end    end  end diff --git a/app/models/concerns/metadata_support.rb b/app/models/concerns/metadata_support.rb new file mode 100644 index 000000000..c4bedbcda --- /dev/null +++ b/app/models/concerns/metadata_support.rb @@ -0,0 +1,107 @@ +module MetadataSupport +  extend ActiveSupport::Concern + +  included do +    class << self +      def has_metadata? +        !!@has_metadata +      end + +      def has_metadata opts={} +        @has_metadata = true + +        define_method :metadata do +          attr_name = opts[:attr_name] || :metadata +          @wrapped_metadata ||= begin +            wrapped = MetadataSupport::MetadataWrapper.new self.read_attribute(attr_name) +            wrapped.attribute_name = attr_name +            wrapped.owner = self +            wrapped +          end +        end + +        define_method :metadata= do |val| +          @wrapped_metadata = nil +          super val +        end + +        define_method :set_metadata! do |name, value| +          self.metadata.send "#{name}=", value +          self.save! +        end +      end +    end +  end + +  def has_metadata? +    self.class.has_metadata? +  end + +  def merge_metadata_from source +    return unless source.has_metadata? +    source_metadata = source.metadata +    res = {} +    self.metadata.each do |k, v| +      unless self.metadata.is_timestamp_attr?(k) +        ts = self.metadata.timestamp_attr(k) +        if source_metadata[ts] && source_metadata[ts] > self.metadata[ts] +          res[k] = source_metadata[k] +        else +          res[k] = v +        end +      end +    end +    self.metadata = res +    self +  end + +  class MetadataWrapper < OpenStruct +    attr_accessor :attribute_name, :owner + +    def is_timestamp_attr? name +      name =~ /_updated_at$/ +    end + +    def timestamp_attr name +      "#{name}_updated_at".to_sym +    end + +    def method_missing(mid, *args) +      out = super(mid, *args) +      owner.send :write_attribute, attribute_name, @table +      out = out&.to_time if args.length == 0 && is_timestamp_attr?(mid) +      out +    end + +    def each +      @table.each do |k,v| +        yield k, v +      end +    end + +    def new_ostruct_member name +      unless is_timestamp_attr?(name) +        timestamp_attr_name = timestamp_attr(name) +      end + +      name = name.to_sym +      unless respond_to?(name) +        if timestamp_attr_name +          define_singleton_method(timestamp_attr_name) { @table[timestamp_attr_name]&.to_time } +          define_singleton_method(name) { @table[name] } +        else +          # we are defining an accessor for a timestamp +          define_singleton_method(name) { @table[name]&.to_time } +        end + +        define_singleton_method("#{name}=") do |x| +          modifiable[timestamp_attr_name] = Time.now if timestamp_attr_name +          modifiable[name] = x +          owner.send :write_attribute, attribute_name, @table +        end +        modifiable[timestamp_attr_name] = Time.now if timestamp_attr_name +      end +      name +    end +  end +end diff --git a/app/models/custom_field.rb b/app/models/custom_field.rb index 402df7fa9..22118a15a 100644 --- a/app/models/custom_field.rb +++ b/app/models/custom_field.rb @@ -1,16 +1,20 @@ -class CustomField < ActiveRecord::Base +class CustomField < ApplicationModel    extend Enumerize    belongs_to :workgroup -  enumerize :field_type, in: %i{list} +  enumerize :field_type, in: %i{list integer string attachment}    validates :name, uniqueness: {scope: [:resource_type, :workgroup_id]} -  validates :code, uniqueness: {scope: [:resource_type, :workgroup_id], case_sensitive: false} +  validates :code, uniqueness: {scope: [:resource_type, :workgroup_id], case_sensitive: false}, presence: true + +  scope :for_workgroup, ->(workgroup){ where workgroup_id: workgroup.id } + +  scope :for_workgroup, ->(workgroup){ where workgroup_id: workgroup.id }    class Collection < HashWithIndifferentAccess -    def initialize object -      vals = object.class.custom_fields.map do |v| -        [v.code, CustomField::Value.new(object, v, object.custom_field_value(v.code))] +    def initialize object, workgroup=:all +      vals = object.class.custom_fields(workgroup).map do |v| +        [v.code, CustomField::Instance.new(object, v, object.custom_field_value(v.code))]        end        super Hash[*vals.flatten]      end @@ -20,11 +24,11 @@ class CustomField < ActiveRecord::Base      end    end -  class Value +  class Instance      def self.new owner, custom_field, value -      field_type = custom_field.options["field_type"] -      klass_name = field_type && "CustomField::Value::#{field_type.classify}" -      klass = klass_name && const_defined?(klass_name) ? klass_name.constantize : CustomField::Value::Base +      field_type = custom_field.field_type +      klass_name = field_type && "CustomField::Instance::#{field_type.classify}" +      klass = klass_name.safe_constantize || CustomField::Instance::Base        klass.new owner, custom_field, value      end @@ -38,8 +42,14 @@ class CustomField < ActiveRecord::Base          @valid = false        end +      attr_accessor :owner, :custom_field +        delegate :code, :name, :field_type, to: :@custom_field +      def default_value +        options["default"] +      end +        def options          @custom_field.options || {}        end @@ -57,6 +67,14 @@ class CustomField < ActiveRecord::Base          @raw_value        end +      def input form_helper +        @input ||= begin +          klass_name = field_type && "CustomField::Instance::#{field_type.classify}::Input" +          klass = klass_name.safe_constantize || CustomField::Instance::Base::Input +          klass.new self, form_helper +        end +      end +        def errors_key          "custom_fields.#{code}"        end @@ -64,22 +82,184 @@ class CustomField < ActiveRecord::Base        def to_hash          HashWithIndifferentAccess[*%w(code name field_type options value).map{|k| [k, send(k)]}.flatten(1)]        end + +      def display_value +        value +      end + +      def initialize_custom_field +      end + +      def preprocess_value_for_assignment val +        val +      end + +      def render_partial +        ActionView::Base.new(Rails.configuration.paths["app/views"].first).render( +          :partial => "shared/custom_fields/#{field_type}", +          :locals => { field: self} +        ) +      end + +      class Input +        def initialize instance, form_helper +          @instance = instance +          @form_helper = form_helper +        end + +        def custom_field +          @instance.custom_field +        end + +        delegate :custom_field, :value, :options, to: :@instance +        delegate :code, :name, :field_type, to: :custom_field + +        def to_s +          out = form_input +          out.html_safe +        end + +        protected + +        def form_input_id +          "custom_field_#{code}" +        end + +        def form_input_name +          "#{@form_helper.object_name}[custom_field_values][#{code}]" +        end + +        def form_input_options +          { +            input_html: {value: value, name: form_input_name}, +            label: name +          } +        end + +        def form_input +          @form_helper.input form_input_id, form_input_options +        end +      end      end      class Integer < Base        def value -        @raw_value.to_i +        @raw_value&.to_i        end        def validate          @valid = true -        unless @raw_value =~ /\A\d*\Z/ +        return if @raw_value.is_a?(Fixnum) || @raw_value.is_a?(Float) +        unless @raw_value.to_s =~ /\A\d*\Z/            @owner.errors.add errors_key, "'#{@raw_value}' is not a valid integer"            @valid = false          end        end      end +    class List < Integer +      def validate +        super +        return unless value.present? +        unless value >= 0 && value < options["list_values"].size +          @owner.errors.add errors_key, "'#{@raw_value}' is not a valid value" +          @valid = false +        end +      end + +      def display_value +        return unless value +        k = options["list_values"].is_a?(Hash) ? value.to_s : value.to_i +        options["list_values"][k] +      end + +      class Input < Base::Input +        def form_input_options +          collection = options["list_values"] +          collection = collection.each_with_index.to_a if collection.is_a?(Array) +          collection = collection.map(&:reverse) if collection.is_a?(Hash) +          super.update({ +            selected: value, +            collection: collection +          }) +        end +      end +    end + +    class Attachment < Base +      def initialize_custom_field +        custom_field_code = self.code +        _attr_name = attr_name +        _uploader_name = uploader_name +        owner.send :define_singleton_method, "read_uploader" do |attr| +          if attr.to_s == _attr_name +            custom_field_values[custom_field_code] +          else +            read_attribute attr +          end +        end + +        owner.send :define_singleton_method, "write_uploader" do |attr, val| +          if attr.to_s == _attr_name +            custom_field_values[custom_field_code] = val +          else +            write_attribute attr, val +          end +        end + +        owner.send :define_singleton_method, "#{_attr_name}_will_change!" do +          custom_field_values_will_change! +        end + +        _extension_whitelist = options["extension_whitelist"] + +        owner.send :define_singleton_method, "#{_uploader_name}_extension_whitelist" do +          _extension_whitelist +        end + +        unless owner.class.uploaders.has_key? _uploader_name.to_sym +          owner.class.mount_uploader _uploader_name, CustomFieldAttachmentUploader, mount_on: "custom_field_#{code}_raw_value" +        end +      end + +      def preprocess_value_for_assignment val +        if val.present? +          owner.send "#{uploader_name}=", val +        else +          @raw_value +        end +      end + +      def value +        owner.send "custom_field_#{code}" +      end + +      def raw_value +        @raw_value +      end + +      def attr_name +        "custom_field_#{code}_raw_value" +      end + +      def uploader_name +        "custom_field_#{code}" +      end + +      def display_value +        render_partial +      end + +      class Input < Base::Input +        def form_input_options +          super.update({ +            as: :file, +            wrapper: :horizontal_file_input +          }) +        end +      end +    end +      class String < Base        def value          "#{@raw_value}" diff --git a/app/models/export/base.rb b/app/models/export/base.rb index 6cf4c6b02..c65539635 100644 --- a/app/models/export/base.rb +++ b/app/models/export/base.rb @@ -1,4 +1,8 @@ +require 'net/http/post/multipart' +  class Export::Base < ActiveRecord::Base +  include Rails.application.routes.url_helpers +    self.table_name = "exports"    belongs_to :referential @@ -21,6 +25,22 @@ class Export::Base < ActiveRecord::Base      %w(zip csv json)    end +  def upload_file file +    url = URI.parse upload_workbench_export_url(self.workbench_id, self.id, host: Rails.application.config.rails_host) +    res = nil +    filename = File.basename(file.path) +    content_type = MIME::Types.type_for(filename).first&.content_type +    File.open(file.path) do |file_content| +      req = Net::HTTP::Post::Multipart.new url.path, +        file: UploadIO.new(file_content, content_type, filename), +        token: self.token_upload +      res = Net::HTTP.start(url.host, url.port) do |http| +        http.request(req) +      end +    end +    res +  end +    if Rails.env.development?      def self.force_load_descendants        path = Rails.root.join 'app/models/export' diff --git a/app/models/export/message.rb b/app/models/export/message.rb index b64b524ac..223429900 100644 --- a/app/models/export/message.rb +++ b/app/models/export/message.rb @@ -1,4 +1,4 @@ -class Export::Message < ActiveRecord::Base +class Export::Message < ApplicationModel    self.table_name = :export_messages    include IevInterfaces::Message diff --git a/app/models/export/resource.rb b/app/models/export/resource.rb index 98f103be4..2a63c14a8 100644 --- a/app/models/export/resource.rb +++ b/app/models/export/resource.rb @@ -1,4 +1,4 @@ -class Export::Resource < ActiveRecord::Base +class Export::Resource < ApplicationModel    self.table_name = :export_resources    include IevInterfaces::Resource diff --git a/app/models/export/simple_exporter/base.rb b/app/models/export/simple_exporter/base.rb index 4e6e8eba4..e77e23468 100644 --- a/app/models/export/simple_exporter/base.rb +++ b/app/models/export/simple_exporter/base.rb @@ -48,15 +48,15 @@ class Export::SimpleExporter::Base < Export::Base      exporter.export      set_status_from_exporter      convert_exporter_journal_to_messages -    self.file = tmp      self.save! +    upload_file tmp    end    def set_status_from_exporter      if exporter.status.to_s == "error"        self.status = :failed      elsif exporter.status.to_s == "success" -        self.status = :successful +      self.status = :successful      else        self.status = :warning      end diff --git a/app/models/import/base.rb b/app/models/import/base.rb index 62494c92e..82494b1dc 100644 --- a/app/models/import/base.rb +++ b/app/models/import/base.rb @@ -1,4 +1,4 @@ -class Import::Base < ActiveRecord::Base +class Import::Base < ApplicationModel    self.table_name = "imports"    validates :file, presence: true diff --git a/app/models/import/message.rb b/app/models/import/message.rb index c1900a718..30b76ec5c 100644 --- a/app/models/import/message.rb +++ b/app/models/import/message.rb @@ -1,4 +1,4 @@ -class Import::Message < ActiveRecord::Base +class Import::Message < ApplicationModel    self.table_name = :import_messages    include IevInterfaces::Message diff --git a/app/models/import/resource.rb b/app/models/import/resource.rb index 5bd011039..1951daacd 100644 --- a/app/models/import/resource.rb +++ b/app/models/import/resource.rb @@ -1,4 +1,4 @@ -class Import::Resource < ActiveRecord::Base +class Import::Resource < ApplicationModel    self.table_name = :import_resources    include IevInterfaces::Resource diff --git a/app/models/line_referential.rb b/app/models/line_referential.rb index 0d2ed39b1..08193c960 100644 --- a/app/models/line_referential.rb +++ b/app/models/line_referential.rb @@ -1,7 +1,7 @@ -class LineReferential < ActiveRecord::Base +class LineReferential < ApplicationModel    include ObjectidFormatterSupport    extend StifTransportModeEnumerations -   +    has_many :line_referential_memberships    has_many :organisations, through: :line_referential_memberships    has_many :lines, class_name: 'Chouette::Line' @@ -14,7 +14,7 @@ class LineReferential < ActiveRecord::Base    def add_member(organisation, options = {})      attributes = options.merge organisation: organisation -    line_referential_memberships.build attributes +    line_referential_memberships.build attributes unless organisations.include?(organisation)    end    validates :name, presence: true diff --git a/app/models/line_referential_membership.rb b/app/models/line_referential_membership.rb index b49d1b5b1..8371bdc32 100644 --- a/app/models/line_referential_membership.rb +++ b/app/models/line_referential_membership.rb @@ -1,4 +1,6 @@ -class LineReferentialMembership < ActiveRecord::Base +class LineReferentialMembership < ApplicationModel    belongs_to :organisation    belongs_to :line_referential + +  validates :organisation_id, presence: true, uniqueness: { scope: :line_referential }  end diff --git a/app/models/line_referential_sync.rb b/app/models/line_referential_sync.rb index 75c1e48a2..39e3846f0 100644 --- a/app/models/line_referential_sync.rb +++ b/app/models/line_referential_sync.rb @@ -1,4 +1,4 @@ -class LineReferentialSync < ActiveRecord::Base +class LineReferentialSync < ApplicationModel    include AASM    belongs_to :line_referential    has_many :line_referential_sync_messages, :dependent => :destroy diff --git a/app/models/line_referential_sync_message.rb b/app/models/line_referential_sync_message.rb index 3b6cf3367..00a2b58a3 100644 --- a/app/models/line_referential_sync_message.rb +++ b/app/models/line_referential_sync_message.rb @@ -1,4 +1,4 @@ -class LineReferentialSyncMessage < ActiveRecord::Base +class LineReferentialSyncMessage < ApplicationModel    belongs_to :line_referential_sync    enum criticity: [:info, :warning, :error] diff --git a/app/models/merge.rb b/app/models/merge.rb index e72c794fe..6e2a7036a 100644 --- a/app/models/merge.rb +++ b/app/models/merge.rb @@ -1,4 +1,4 @@ -class Merge < ActiveRecord::Base +class Merge < ApplicationModel    extend Enumerize    belongs_to :workbench @@ -138,7 +138,9 @@ class Merge < ActiveRecord::Base      new.switch do        referential_routes.each do |route|          existing_route = new.routes.find_by line_id: route.line_id, checksum: route.checksum -        unless existing_route +        if existing_route +          existing_route.merge_metadata_from route +        else            objectid = Chouette::Route.where(objectid: route.objectid).exists? ? nil : route.objectid            attributes = route.attributes.merge(              id: nil, @@ -196,7 +198,9 @@ class Merge < ActiveRecord::Base          existing_journey_pattern = new.journey_patterns.find_by route_id: existing_associated_route.id, checksum: journey_pattern.checksum -        unless existing_journey_pattern +        if existing_journey_pattern +          existing_journey_pattern.merge_metadata_from journey_pattern +        else            objectid = Chouette::JourneyPattern.where(objectid: journey_pattern.objectid).exists? ? nil : journey_pattern.objectid            attributes = journey_pattern.attributes.merge(              id: nil, @@ -241,7 +245,9 @@ class Merge < ActiveRecord::Base          existing_vehicle_journey = new.vehicle_journeys.find_by journey_pattern_id: existing_associated_journey_pattern.id, checksum: vehicle_journey.checksum -        unless existing_vehicle_journey +        if existing_vehicle_journey +          existing_vehicle_journey.merge_metadata_from vehicle_journey +        else            objectid = Chouette::VehicleJourney.where(objectid: vehicle_journey.objectid).exists? ? nil : vehicle_journey.objectid            attributes = vehicle_journey.attributes.merge(              id: nil, @@ -338,7 +344,9 @@ class Merge < ActiveRecord::Base            existing_time_table = line.time_tables.find_by checksum: candidate_time_table.checksum -          unless existing_time_table +          if existing_time_table +            existing_time_table.merge_metadata_from candidate_time_table +          else              objectid = Chouette::TimeTable.where(objectid: time_table.objectid).exists? ? nil : time_table.objectid              candidate_time_table.objectid = objectid diff --git a/app/models/organisation.rb b/app/models/organisation.rb index 745bc0d22..5742c81e8 100644 --- a/app/models/organisation.rb +++ b/app/models/organisation.rb @@ -1,5 +1,5 @@  # coding: utf-8 -class Organisation < ActiveRecord::Base +class Organisation < ApplicationModel    include DataFormatEnumerations    has_many :users, :dependent => :destroy @@ -86,4 +86,8 @@ class Organisation < ActiveRecord::Base      workbenches.default    end +  def lines_scope +    functional_scope = sso_attributes.try(:[], "functional_scope") +    JSON.parse(functional_scope) if functional_scope +  end  end diff --git a/app/models/public_version.rb b/app/models/public_version.rb deleted file mode 100644 index 4dbf6ce27..000000000 --- a/app/models/public_version.rb +++ /dev/null @@ -1,4 +0,0 @@ -class PublicVersion < PaperTrail::Version -  # custom behaviour, e.g: -  self.table_name = :'public.versions' -end diff --git a/app/models/referential.rb b/app/models/referential.rb index 70148aa8e..1794126a2 100644 --- a/app/models/referential.rb +++ b/app/models/referential.rb @@ -1,5 +1,5 @@  # coding: utf-8 -class Referential < ActiveRecord::Base +class Referential < ApplicationModel    include DataFormatEnumerations    include ObjectidFormatterSupport @@ -241,7 +241,7 @@ class Referential < ActiveRecord::Base      end    end -  def self.new_from(from, functional_scope) +  def self.new_from(from, organisation)      Referential.new(        name: I18n.t("activerecord.copy", name: from.name),        slug: "#{from.slug}_clone", @@ -252,7 +252,7 @@ class Referential < ActiveRecord::Base        stop_area_referential: from.stop_area_referential,        created_from: from,        objectid_format: from.objectid_format, -      metadatas: from.metadatas.map { |m| ReferentialMetadata.new_from(m, functional_scope) } +      metadatas: from.metadatas.map { |m| ReferentialMetadata.new_from(m, organisation) }      )    end diff --git a/app/models/referential_cloning.rb b/app/models/referential_cloning.rb index d4b74bd52..f2c81009a 100644 --- a/app/models/referential_cloning.rb +++ b/app/models/referential_cloning.rb @@ -1,4 +1,4 @@ -class ReferentialCloning < ActiveRecord::Base +class ReferentialCloning < ApplicationModel    include AASM    belongs_to :source_referential, class_name: 'Referential'    belongs_to :target_referential, class_name: 'Referential' diff --git a/app/models/referential_metadata.rb b/app/models/referential_metadata.rb index 393dc70d3..7a8a01774 100644 --- a/app/models/referential_metadata.rb +++ b/app/models/referential_metadata.rb @@ -1,7 +1,7 @@  require 'activeattr_ext.rb'  require 'range_ext' -class ReferentialMetadata < ActiveRecord::Base +class ReferentialMetadata < ApplicationModel    belongs_to :referential, touch: true    belongs_to :referential_source, class_name: 'Referential'    has_array_of :lines, class_name: 'Chouette::Line' @@ -155,10 +155,10 @@ class ReferentialMetadata < ActiveRecord::Base    end    private :clear_periods -  def self.new_from(from, functional_scope) +  def self.new_from(from, organisation)      from.dup.tap do |metadata|        metadata.referential_source_id = from.referential_id -      metadata.line_ids = from.referential.lines.where(id: metadata.line_ids, objectid: functional_scope).collect(&:id) +      metadata.line_ids = from.referential.lines.where(id: metadata.line_ids).for_organisation(organisation).pluck(:id)        metadata.referential_id = nil      end    end diff --git a/app/models/referential_suite.rb b/app/models/referential_suite.rb index 4f825628c..f4a72f22c 100644 --- a/app/models/referential_suite.rb +++ b/app/models/referential_suite.rb @@ -1,4 +1,4 @@ -class ReferentialSuite < ActiveRecord::Base +class ReferentialSuite < ApplicationModel    belongs_to :new, class_name: 'Referential'    validate def validate_consistent_new      return true if new_id.nil? || new.nil? diff --git a/app/models/simple_exporter.rb b/app/models/simple_exporter.rb index c267b5b8c..1fcb76a29 100644 --- a/app/models/simple_exporter.rb +++ b/app/models/simple_exporter.rb @@ -64,7 +64,7 @@ class SimpleExporter < SimpleInterface    def map_item_to_rows item      return [item] unless configuration.item_to_rows_mapping -    configuration.item_to_rows_mapping.call(item).map {|row| row.is_a?(ActiveRecord::Base) ? row : CustomRow.new(row) } +    instance_exec(item, &configuration.item_to_rows_mapping).map {|row| row.is_a?(ActiveRecord::Base) ? row : CustomRow.new(row) }    end    def resolve_value item, col diff --git a/app/models/simple_interface.rb b/app/models/simple_interface.rb index 43c740b57..7b04a07df 100644 --- a/app/models/simple_interface.rb +++ b/app/models/simple_interface.rb @@ -1,4 +1,4 @@ -class SimpleInterface < ActiveRecord::Base +class SimpleInterface < ApplicationModel    attr_accessor :configuration, :interfaces_group    class << self diff --git a/app/models/stop_area_referential.rb b/app/models/stop_area_referential.rb index a9d3cc9b1..6c339547c 100644 --- a/app/models/stop_area_referential.rb +++ b/app/models/stop_area_referential.rb @@ -1,4 +1,4 @@ -class StopAreaReferential < ActiveRecord::Base +class StopAreaReferential < ApplicationModel    validates :registration_number_format, format: { with: /\AX*\z/ }    include ObjectidFormatterSupport @@ -12,7 +12,7 @@ class StopAreaReferential < ActiveRecord::Base    def add_member(organisation, options = {})      attributes = options.merge organisation: organisation -    stop_area_referential_memberships.build attributes +    stop_area_referential_memberships.build attributes unless organisations.include?(organisation)    end    def last_sync diff --git a/app/models/stop_area_referential_membership.rb b/app/models/stop_area_referential_membership.rb index 435970961..d507bc50e 100644 --- a/app/models/stop_area_referential_membership.rb +++ b/app/models/stop_area_referential_membership.rb @@ -1,4 +1,6 @@ -class StopAreaReferentialMembership < ActiveRecord::Base +class StopAreaReferentialMembership < ApplicationModel    belongs_to :organisation    belongs_to :stop_area_referential + +  validates :organisation_id, presence: true, uniqueness: { scope: :stop_area_referential }  end diff --git a/app/models/stop_area_referential_sync.rb b/app/models/stop_area_referential_sync.rb index e6cf2ecbc..8b48d35e6 100644 --- a/app/models/stop_area_referential_sync.rb +++ b/app/models/stop_area_referential_sync.rb @@ -1,4 +1,4 @@ -class StopAreaReferentialSync < ActiveRecord::Base +class StopAreaReferentialSync < ApplicationModel    include AASM    belongs_to :stop_area_referential    has_many :stop_area_referential_sync_messages, :dependent => :destroy diff --git a/app/models/stop_area_referential_sync_message.rb b/app/models/stop_area_referential_sync_message.rb index cd2e62405..642ccfc38 100644 --- a/app/models/stop_area_referential_sync_message.rb +++ b/app/models/stop_area_referential_sync_message.rb @@ -1,4 +1,4 @@ -class StopAreaReferentialSyncMessage < ActiveRecord::Base +class StopAreaReferentialSyncMessage < ApplicationModel    belongs_to :stop_area_referential_sync    enum criticity: [:info, :warning, :error] diff --git a/app/models/user.rb b/app/models/user.rb index eca7ede0c..29148d9e9 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -1,4 +1,4 @@ -class User < ActiveRecord::Base +class User < ApplicationModel    # Include default devise modules. Others available are:    # :token_authenticatable, :encryptable, :confirmable, :lockable, :timeoutable and :omniauthable, :database_authenticatable diff --git a/app/models/workbench.rb b/app/models/workbench.rb index b5f4673bb..ef0b2eaa4 100644 --- a/app/models/workbench.rb +++ b/app/models/workbench.rb @@ -1,4 +1,4 @@ -class Workbench < ActiveRecord::Base +class Workbench < ApplicationModel    DEFAULT_WORKBENCH_NAME = "Gestion de l'offre"    include ObjectidFormatterSupport diff --git a/app/models/workgroup.rb b/app/models/workgroup.rb index 7e3e857ec..3e8409634 100644 --- a/app/models/workgroup.rb +++ b/app/models/workgroup.rb @@ -1,4 +1,4 @@ -class Workgroup < ActiveRecord::Base +class Workgroup < ApplicationModel    belongs_to :line_referential    belongs_to :stop_area_referential diff --git a/app/policies/route_policy.rb b/app/policies/route_policy.rb index 0337a5300..4fcb6be11 100644 --- a/app/policies/route_policy.rb +++ b/app/policies/route_policy.rb @@ -20,4 +20,8 @@ class RoutePolicy < ApplicationPolicy    def duplicate?      create?    end + +  def create_opposite? +    create? +  end  end diff --git a/app/services/route_way_cost_calculator.rb b/app/services/route_way_cost_calculator.rb index 2e30c94fc..d41a2e59a 100644 --- a/app/services/route_way_cost_calculator.rb +++ b/app/services/route_way_cost_calculator.rb @@ -5,7 +5,7 @@ class RouteWayCostCalculator    def calculate!      way_costs = StopAreasToWayCostsConverter.new(@route.stop_areas).convert -    way_costs = TomTom.batch(way_costs) +    way_costs = TomTom.matrix(way_costs)      way_costs = WayCostCollectionJSONSerializer.dump(way_costs)      @route.update(costs: way_costs)    end diff --git a/app/uploaders/custom_field_attachment_uploader.rb b/app/uploaders/custom_field_attachment_uploader.rb new file mode 100644 index 000000000..411b65bc3 --- /dev/null +++ b/app/uploaders/custom_field_attachment_uploader.rb @@ -0,0 +1,12 @@ +class CustomFieldAttachmentUploader < CarrierWave::Uploader::Base + +  storage :file + +  def store_dir +    "uploads/#{model.class.to_s.underscore}/#{mounted_as}/#{model.id}" +  end + +  def extension_whitelist +    model.send "#{mounted_as}_extension_whitelist" +  end +end diff --git a/app/views/autocomplete_purchase_windows/index.rabl b/app/views/autocomplete_purchase_windows/index.rabl index 1d0287602..bdc513c31 100644 --- a/app/views/autocomplete_purchase_windows/index.rabl +++ b/app/views/autocomplete_purchase_windows/index.rabl @@ -2,11 +2,12 @@ collection @purchase_windows, :object_root => false  node do |window|    { -    :id => window.id, -    :name => window.name, -    :objectid => window.objectid, -    :color => window.color, -    :short_id => window.get_objectid.short_id, -    :text => "<strong><span class='fa fa-circle' style='color:" + (window.color ? window.color : '#4b4b4b') + "'></span> " + window.name + " - " + window.get_objectid.short_id + "</strong>" +    id: window.id, +    name: window.name, +    objectid: window.objectid, +    color: window.color, +    short_id: window.get_objectid.short_id, +    bounding_dates: window.bounding_dates, +    text: "<strong><span class='fa fa-circle' style='color:" + (window.color ? window.color : '#4b4b4b') + "'></span> " + window.name + " - " + window.get_objectid.short_id + "</strong>"    }  end diff --git a/app/views/calendars/show.html.slim b/app/views/calendars/show.html.slim index 648c98928..880db99f6 100644 --- a/app/views/calendars/show.html.slim +++ b/app/views/calendars/show.html.slim @@ -6,8 +6,7 @@      .row        .col-lg-6.col-md-6.col-sm-12.col-xs-12          = definition_list t('metadatas'), -          { Calendar.tmf('short_name') => resource.try(:short_name), -            Calendar.tmf('shared') => t("#{resource.shared}"), +          { Calendar.tmf('shared') => t("#{resource.shared}"),              Calendar.tmf('organisation') => resource.organisation.name,              Calendar.tmf('dates') =>  resource.dates.collect{|d| l(d, format: :short)}.join(', ').html_safe,              Calendar.tmf('date_ranges') => resource.periods.map{|d| t('validity_range', debut: l(d.begin, format: :short), end: l(d.end, format: :short))}.join('<br>').html_safe } @@ -18,7 +17,7 @@            .pagination.pull-right              = @year              .page_links -              = link_to '', calendar_path(@calendar, year: (@year - 1)), class: 'previous_page' -              = link_to '', calendar_path(@calendar, year: (@year + 1)), class: 'next_page' +              = link_to '', workgroup_calendar_path(@workgroup, @calendar, year: (@year - 1)), class: 'previous_page' +              = link_to '', workgroup_calendar_path(@workgroup, @calendar, year: (@year + 1)), class: 'next_page'        = render 'time_tables/show_time_table', time_table: @calendar diff --git a/app/views/companies/_form.html.slim b/app/views/companies/_form.html.slim index 3979c5800..e8b3fcede 100644 --- a/app/views/companies/_form.html.slim +++ b/app/views/companies/_form.html.slim @@ -12,7 +12,9 @@        = f.input :time_zone, include_blank: true        = f.input :url        = f.input :registration_number, :input_html => {:title => t("formtastic.titles#{format_restriction_for_locales(@line_referential)}.company.registration_number")} - +      - if resource.custom_fields(current_referential.workgroup).any? +        - resource.custom_fields.each do |code, field| +          = field.input(f).to_s    .separator    = f.button :submit, t('actions.submit'), class: 'btn btn-default formSubmitr', form: 'company_form' diff --git a/app/views/companies/show.html.slim b/app/views/companies/show.html.slim index ca0a410b3..8960b92dd 100644 --- a/app/views/companies/show.html.slim +++ b/app/views/companies/show.html.slim @@ -6,8 +6,11 @@    .container-fluid      .row        .col-lg-6.col-md-6.col-sm-12.col-xs-12 -        = definition_list t('metadatas'), -          { 'ID Codif' => @company.try(:get_objectid).try(:short_id), -            Chouette::Company.human_attribute_name(:phone) => resource.phone, -            Chouette::Company.human_attribute_name(:email) => resource.email, -            Chouette::Company.human_attribute_name(:url) => resource.url } +        - attributes = { t('id_codif') => @company.try(:objectid).try(:local_id), +          Chouette::Company.human_attribute_name(:phone) => @company.phone, +          Chouette::Company.human_attribute_name(:email) => @company.email, +          Chouette::Company.human_attribute_name(:url) => @company.url } +        - @company.custom_fields(current_referential.workgroup).each do |code, field| +          - attributes.merge!(field.name => field.display_value) + +        = definition_list t('metadatas'), attributes diff --git a/app/views/compliance_control_blocks/edit.html.slim b/app/views/compliance_control_blocks/edit.html.slim index 49aee7705..1d32256b0 100644 --- a/app/views/compliance_control_blocks/edit.html.slim +++ b/app/views/compliance_control_blocks/edit.html.slim @@ -1,3 +1,4 @@ +- breadcrumb :compliance_control_set, compliance_control_set  - page_header_content_for @compliance_control_block  .page_content diff --git a/app/views/compliance_control_blocks/new.html.slim b/app/views/compliance_control_blocks/new.html.slim index 7d2551311..aab40572b 100644 --- a/app/views/compliance_control_blocks/new.html.slim +++ b/app/views/compliance_control_blocks/new.html.slim @@ -1,3 +1,5 @@ +- breadcrumb :compliance_control_set, compliance_control_set +  .page_content    .container-fluid      .row diff --git a/app/views/lines/_filters.html.slim b/app/views/lines/_filters.html.slim index da0539bd0..f745d10a4 100644 --- a/app/views/lines/_filters.html.slim +++ b/app/views/lines/_filters.html.slim @@ -44,5 +44,5 @@    .actions -    = link_to 'Effacer', @workbench, class: 'btn btn-link' -    = f.submit 'Filtrer', class: 'btn btn-default' +    = link_to t('actions.erase'), @workbench, class: 'btn btn-link' +    = f.submit t('actions.filter'), class: 'btn btn-default' diff --git a/app/views/referential_companies/_form.html.slim b/app/views/referential_companies/_form.html.slim index b02eab3f1..0e7b20af4 100644 --- a/app/views/referential_companies/_form.html.slim +++ b/app/views/referential_companies/_form.html.slim @@ -1,18 +1,19 @@ -= semantic_form_for [@referential, @company] do |form| -  = form.inputs do -    = form.input :name, :input_html => {  :title => t("formtastic.titles#{format_restriction_for_locales(@referential)}.company.name") } -    = form.input :short_name -    = form.input :organizational_unit -    = form.input :operating_department_name -    = form.input :code -    = form.input :phone, as: :phone -    = form.input :fax, as: :phone -    = form.input :email, as: :email -    = form.input :time_zone, include_blank: true -    = form.input :url -    = form.input :registration_number, :input_html => { :title => t("formtastic.titles#{format_restriction_for_locales(@referential)}.company.registration_number") } -    = form.input :objectid, :required => !@company.new_record?, :input_html => { :title => t("formtastic.titles#{format_restriction_for_locales(@referential)}.company.objectid") } - -  = form.actions do -    = form.action :submit, as: :button -    = form.action :cancel, as: :link
\ No newline at end of file += simple_form_for [@referential, @company], html: {class: 'form-horizontal', id: 'company_form'}, wrapper: :horizontal_form do |f| +  .row +    .col-lg-12 +      = f.input :name, :input_html => {:title => t("formtastic.titles#{format_restriction_for_locales(@line_referential)}.company.name")} +      = f.input :short_name +      = f.input :organizational_unit +      = f.input :operating_department_name +      = f.input :code +      = f.input :phone +      = f.input :fax +      = f.input :email, as: :email +      = f.input :time_zone, include_blank: true +      = f.input :url +      = f.input :registration_number, :input_html => {:title => t("formtastic.titles#{format_restriction_for_locales(@line_referential)}.company.registration_number")} +      - if resource.custom_fields(@referential.workgroup).any? +        - resource.custom_fields.each do |code, field| +          = field.input(f).to_s +  .separator +  = f.button :submit, t('actions.submit'), class: 'btn btn-default formSubmitr', form: 'company_form' diff --git a/app/views/referential_companies/edit.html.slim b/app/views/referential_companies/edit.html.slim index b3fcf6cd8..95be64aa1 100644 --- a/app/views/referential_companies/edit.html.slim +++ b/app/views/referential_companies/edit.html.slim @@ -1,3 +1,5 @@  - breadcrumb :referential_company, @referential, @company  - page_header_content_for @company -= render 'form' +.page_content +  .container-fluid +    = render 'form' diff --git a/app/views/referential_companies/new.html.slim b/app/views/referential_companies/new.html.slim index 5e59db139..1dfdc8eb5 100644 --- a/app/views/referential_companies/new.html.slim +++ b/app/views/referential_companies/new.html.slim @@ -1,2 +1,6 @@  - breadcrumb :referential_companies, @referential -= render 'form' +.page_content +  .container-fluid +    .row +      .col-lg-8.col-lg-offset-2.col-md-8.col-md-offset-2.col-sm-10.col-sm-offset-1 +        = render 'form' diff --git a/app/views/referential_companies/show.html.slim b/app/views/referential_companies/show.html.slim index 1599145be..8ad011edf 100644 --- a/app/views/referential_companies/show.html.slim +++ b/app/views/referential_companies/show.html.slim @@ -17,8 +17,11 @@    .container-fluid      .row        .col-lg-6.col-md-6.col-sm-12.col-xs-12 -        = definition_list t('metadatas'), -          { t('id_codif') => @company.try(:objectid).try(:local_id), -            Chouette::Company.human_attribute_name(:phone) => @company.phone, -            Chouette::Company.human_attribute_name(:email) => @company.email, -            Chouette::Company.human_attribute_name(:url) => @company.url } +        - attributes = { t('id_codif') => @company.try(:objectid).try(:local_id), +          Chouette::Company.human_attribute_name(:phone) => @company.phone, +          Chouette::Company.human_attribute_name(:email) => @company.email, +          Chouette::Company.human_attribute_name(:url) => @company.url } +        - @company.custom_fields(@referential.workgroup).each do |code, field| +          - attributes.merge!(field.name => field.display_value) + +        = definition_list t('metadatas'), attributes diff --git a/app/views/referential_lines/_filters.html.slim b/app/views/referential_lines/_filters.html.slim index 501f61c16..15db0e33e 100644 --- a/app/views/referential_lines/_filters.html.slim +++ b/app/views/referential_lines/_filters.html.slim @@ -1,7 +1,7 @@  = search_form_for @q, url: referential_line_path(@referential, @line), class: 'form form-filter' do |f|    .ffg-row      .input-group.search_bar class=filter_item_class(params[:q], :name_or_objectid_cont) -      = f.search_field :name_or_objectid_cont, class: 'form-control', placeholder: "Indiquez un nom d'itinéraire ou un ID..." +      = f.search_field :name_or_objectid_cont, class: 'form-control', placeholder: t('.name_or_objectid_cont')        span.input-group-btn          button.btn.btn-default#search-btn type='submit'            span.fa.fa-search @@ -12,5 +12,5 @@        = f.input :wayback_eq_any, class: 'form-control', collection: Chouette::Route.wayback.values, as: :check_boxes, label: false, required: false, wrapper_html: { class: 'checkbox_list'}, label_method: lambda{|l| ("<span>" + t("enumerize.route.wayback.#{l}") + "</span>").html_safe}    .actions -    = link_to 'Effacer', referential_line_path(@referential, @line), class: 'btn btn-link' -    = f.submit 'Filtrer', class: 'btn btn-default' +    = link_to t('actions.erase'), referential_line_path(@referential, @line), class: 'btn btn-link' +    = f.submit t('actions.filter'), class: 'btn btn-default' diff --git a/app/views/referential_vehicle_journeys/_filters.html.slim b/app/views/referential_vehicle_journeys/_filters.html.slim index f1fbdb5d8..f9fa4fcf7 100644 --- a/app/views/referential_vehicle_journeys/_filters.html.slim +++ b/app/views/referential_vehicle_journeys/_filters.html.slim @@ -68,5 +68,5 @@    .actions -    = link_to 'Effacer', referential_vehicle_journeys_path(@referential), class: 'btn btn-link' -    = f.submit 'Filtrer', class: 'btn btn-default' +    = link_to t('actions.erase'), referential_vehicle_journeys_path(@referential), class: 'btn btn-link' +    = f.submit t('actions.filter'), class: 'btn btn-default' diff --git a/app/views/referentials/_form.html.slim b/app/views/referentials/_form.html.slim index 96d847ec1..c378f871e 100644 --- a/app/views/referentials/_form.html.slim +++ b/app/views/referentials/_form.html.slim @@ -49,7 +49,7 @@      .separator      .row        .col-lg-11 -        = subform.input :lines, as: :select, collection: Chouette::Line.includes(:company).order(:name).where(objectid: current_functional_scope), selected: subform.object.line_ids, label_method: :display_name, input_html: { 'data-select2ed': 'true', 'data-select2ed-placeholder': t('simple_form.labels.referential.placeholders.select_lines'), 'multiple': 'multiple', style: 'width: 100%' } +        = subform.input :lines, as: :select, collection: Chouette::Line.includes(:company).order(:name).for_organisation(current_organisation), selected: subform.object.line_ids, label_method: :display_name, input_html: { 'data-select2ed': 'true', 'data-select2ed-placeholder': t('simple_form.labels.referential.placeholders.select_lines'), 'multiple': 'multiple', style: 'width: 100%' }        .col-lg-1          a.clear-lines.btn.btn-default href='#'            .fa.fa-trash diff --git a/app/views/referentials/select_compliance_control_set.html.slim b/app/views/referentials/select_compliance_control_set.html.slim index 69c87aab2..7be82364d 100644 --- a/app/views/referentials/select_compliance_control_set.html.slim +++ b/app/views/referentials/select_compliance_control_set.html.slim @@ -1,3 +1,5 @@ +- breadcrumb @referential +  .page_content    .container-fluid      .row @@ -6,7 +8,7 @@            .row              .col-lg-12                .form-group -                = label_tag 'name', nil, class: 'string required col-sm-4 col-xs-5 control-label' +                = label_tag ComplianceControlSet.ts, nil, class: 'string required col-sm-4 col-xs-5 control-label'                  .col-sm-8.col-xs-7                    = select_tag :compliance_control_set, options_from_collection_for_select(@compliance_control_sets, "id", "name"), class: 'select optional form-control'                .separator diff --git a/app/views/routes/show.html.slim b/app/views/routes/show.html.slim index d2e750fb0..d4571c173 100644 --- a/app/views/routes/show.html.slim +++ b/app/views/routes/show.html.slim @@ -34,7 +34,7 @@                  end \                ), \                TableBuilderHelper::Column.new( \ -                key: :deleted_at, \ +                name: Chouette::Line.tmf('activated'), \                  attribute: Proc.new { |s| line_status(s.try(:stop_area).deleted_at) } \                ), \                TableBuilderHelper::Column.new( \ diff --git a/app/views/shared/custom_fields/_attachment.html.slim b/app/views/shared/custom_fields/_attachment.html.slim new file mode 100644 index 000000000..32d0fda4d --- /dev/null +++ b/app/views/shared/custom_fields/_attachment.html.slim @@ -0,0 +1,4 @@ +- if field.value.present? +  = link_to I18n.t("custom_fields.#{field.owner.class.name.demodulize.underscore}.#{field.code}.link"), field.value.url +- else +  = "-" diff --git a/app/views/shared/iev_interfaces/_messages.html.slim b/app/views/shared/iev_interfaces/_messages.html.slim index 022f4ee01..14157a88d 100644 --- a/app/views/shared/iev_interfaces/_messages.html.slim +++ b/app/views/shared/iev_interfaces/_messages.html.slim @@ -9,6 +9,6 @@            - else              .col-md-6= export_message_content message            .col-md-6 -            - if message.criticity != "info" +            - if message.resource_attributes                pre                  = JSON.pretty_generate message.resource_attributes || {} diff --git a/app/views/stop_areas/_filters.html.slim b/app/views/stop_areas/_filters.html.slim index a32638567..c698eaaa5 100644 --- a/app/views/stop_areas/_filters.html.slim +++ b/app/views/stop_areas/_filters.html.slim @@ -41,5 +41,5 @@              input_html: { checked: @status.try(:[], :deactivated) }    .actions -    = link_to 'Effacer', @workbench, class: 'btn btn-link' -    = f.submit 'Filtrer', class: 'btn btn-default' +    = link_to t('actions.erase'), @workbench, class: 'btn btn-link' +    = f.submit t('actions.filter'), class: 'btn btn-default' diff --git a/app/views/stop_areas/_form.html.slim b/app/views/stop_areas/_form.html.slim index 1cba88f94..00f2ad8bb 100644 --- a/app/views/stop_areas/_form.html.slim +++ b/app/views/stop_areas/_form.html.slim @@ -48,7 +48,7 @@            - if has_feature?(:stop_area_waiting_time)              = f.input :waiting_time, input_html: { min: 0 } -          = f.input :registration_number, required: stop_area_registration_number_is_required(f.object), :input_html => {title: stop_area_registration_number_title(f.object), value: stop_area_registration_number_value(f.object)} +          = f.input :registration_number, required: stop_area_registration_number_is_required(f.object), :input_html => {title: stop_area_registration_number_title(f.object), value: stop_area_registration_number_value(f.object)}, hint: stop_area_registration_number_hint            = f.input :fare_code            = f.input :nearest_topic_name, :input_html => {:title => t("formtastic.titles#{format_restriction_for_locales(@referential)}.stop_area.nearest_topic_name")}            = f.input :comment, as: :text, :input_html => {:rows => 5, :title => t("formtastic.titles#{format_restriction_for_locales(@referential)}.stop_area.comment")} @@ -62,6 +62,12 @@              = f.input :stairs_availability, as: :select, :collection => [[t("true"), true], [t("false"), false]], :include_blank => true              = f.input :lift_availability, as: :select, :collection => [[t("true"), true], [t("false"), false]], :include_blank => true +        - if resource.custom_fields(resource.stop_area_referential.workgroup).any? +          .custom_fields +            h3 = t("stop_areas.stop_area.custom_fields") +            - resource.custom_fields(resource.stop_area_referential.workgroup).each do |code, field| +              = field.input(f).to_s +    .separator    = f.button :submit, t('actions.submit'), class: 'btn btn-default formSubmitr', form: 'stop_area_form' diff --git a/app/views/stop_areas/index.html.slim b/app/views/stop_areas/index.html.slim index 587efbdaa..fbdb54e02 100644 --- a/app/views/stop_areas/index.html.slim +++ b/app/views/stop_areas/index.html.slim @@ -32,7 +32,7 @@                  attribute: 'registration_number' \                ), \                TableBuilderHelper::Column.new( \ -                name: t('activerecord.attributes.stop_area.state'), \ +                name: Chouette::StopArea.tmf('state'), \                  attribute: Proc.new { |s| stop_area_status(s) } \                ), \                TableBuilderHelper::Column.new( \ diff --git a/app/views/stop_areas/show.html.slim b/app/views/stop_areas/show.html.slim index a6147b86d..851bd9b82 100644 --- a/app/views/stop_areas/show.html.slim +++ b/app/views/stop_areas/show.html.slim @@ -23,4 +23,6 @@              t('activerecord.attributes.stop_area.state') => stop_area_status(@stop_area),              @stop_area.human_attribute_name(:comment) => @stop_area.try(:comment),              }) +        - @stop_area.custom_fields.each do |code, field| +          - attributes.merge!(field.name => field.display_value)          = definition_list t('metadatas'), attributes diff --git a/app/views/vehicle_journeys/index.html.slim b/app/views/vehicle_journeys/index.html.slim index cd55d3ce6..7fcee545f 100644 --- a/app/views/vehicle_journeys/index.html.slim +++ b/app/views/vehicle_journeys/index.html.slim @@ -7,7 +7,7 @@        - if has_feature? :purchase_windows          = link_to I18n.t("purchase_windows.index.title"), [@referential, :purchase_windows], class: 'btn btn-primary sticky-action', target: :blank        - if @route.opposite_route.present? -        = link_to(t('routes.actions.opposite_route_timetable'), [@referential, @route.line, @route.opposite_route, :vehicle_journeys], class: 'btn btn-primary sticky-action') +        = link_to(t('routes.actions.reversed_vehicle_journey'), [@referential, @route.line, @route.opposite_route, :vehicle_journeys], class: 'btn btn-primary sticky-action')  .page_content @@ -33,7 +33,6 @@    | window.all_missions = #{(@all_missions.to_json).html_safe};    | window.custom_fields = #{(@custom_fields.to_json).html_safe};    | window.extra_headers = #{(@extra_headers.to_json).html_safe}; -  // | window.I18n = #{(I18n.backend.send(:translations).to_json).html_safe};  - if has_feature?(:vehicle_journeys_return_route)    = javascript_tag do diff --git a/app/views/workbench_outputs/show.html.slim b/app/views/workbench_outputs/show.html.slim index a9e106dbb..b310119e6 100644 --- a/app/views/workbench_outputs/show.html.slim +++ b/app/views/workbench_outputs/show.html.slim @@ -6,7 +6,8 @@    .row.mb-sm      .col-lg-12.text-right        = link_to t('.see_current_output'), referential_path(@workbench.output.current), class: 'btn btn-primary' if @workbench.output&.current -      = link_to t('merges.actions.create'), new_workbench_merge_path(@workbench), class: 'btn btn-primary' +      - if policy(Merge).create? +        = link_to t('merges.actions.create'), new_workbench_merge_path(@workbench), class: 'btn btn-primary'  .page_content    .container-fluid diff --git a/app/views/workbenches/show.html.slim b/app/views/workbenches/show.html.slim index 8312338d0..7dd1583fa 100644 --- a/app/views/workbenches/show.html.slim +++ b/app/views/workbenches/show.html.slim @@ -32,7 +32,8 @@                    end \                  ), \                  TableBuilderHelper::Column.new( \ -                  key: :status, \ +                  key: :archived_at, \ +                  name: Referential.tmf('status'), \                    attribute: Proc.new {|w| w.referential_read_only? ? ("<div class='td-block'><span class='fa fa-archive'></span><span>#{t('activerecord.attributes.referential.archived_at')}</span></div>").html_safe : ("<div class='td-block'><span class='sb sb-lg sb-preparing'></span><span>#{t('activerecord.attributes.referential.archived_at_null')}</span></div>").html_safe} \                  ), \                  TableBuilderHelper::Column.new( \ @@ -45,7 +46,7 @@                  ), \                  TableBuilderHelper::Column.new( \                    key: :lines, \ -                  name: t('activerecord.attributes.referential.number_of_lines'), \ +                  name: Referential.tmf('number_of_lines'), \                    attribute: Proc.new {|w| w.lines.count} \                  ), \                  TableBuilderHelper::Column.new( \ diff --git a/config/database/ci.yml b/config/database/ci.yml index 44103454a..5671cb6ad 100644 --- a/config/database/ci.yml +++ b/config/database/ci.yml @@ -1,4 +1,4 @@ -test: +test: &default    adapter: <%= ENV.fetch 'RAILS_DB_ADAPTER', 'postgis' %>    encoding: unicode    pool: <%= ENV.fetch 'RAILS_DB_POOLSIZE', '5' %> @@ -9,3 +9,7 @@ test:    database: <%= ENV.fetch 'RAILS_DB_NAME', 'stif_boiv_test' %>    username: <%= ENV['RAILS_DB_USER'] || ENV['POSTGRESQL_ENV_POSTGRES_USER'] || 'jenkins' %>    password: <%= ENV['RAILS_DB_PASSWORD'] || ENV['POSTGRESQL_ENV_POSTGRES_PASSWORD'] %> + +# Only used to build assets +production: +  <<: *default diff --git a/config/initializers/simple_form_bootstrap.rb b/config/initializers/simple_form_bootstrap.rb index 4b9bd320d..8dbc3afee 100644 --- a/config/initializers/simple_form_bootstrap.rb +++ b/config/initializers/simple_form_bootstrap.rb @@ -72,9 +72,9 @@ SimpleForm.setup do |config|      b.use :placeholder      b.optional :maxlength      b.optional :readonly -    b.use :label, class: 'col-sm-3 control-label' +    b.use :label, class: 'col-sm-4 col-xs-5 control-label' -    b.wrapper tag: 'div', class: 'col-sm-9' do |ba| +    b.wrapper tag: 'div', class: 'col-sm-8 col-xs-7' do |ba|        ba.use :input        ba.use :error, wrap_with: { tag: 'span', class: 'help-block small' }        ba.use :hint,  wrap_with: { tag: 'p', class: 'help-block small' } diff --git a/config/locales/calendars.en.yml b/config/locales/calendars.en.yml index 3d16e7c05..696ae2734 100644 --- a/config/locales/calendars.en.yml +++ b/config/locales/calendars.en.yml @@ -69,7 +69,7 @@ en:          date_ranges: Date ranges          dates: Dates          shared: Shared -        organisation: Organisation +        organisation: Organization          monday: "Monday"          tuesday: "Tuesday"          wednesday: "Wednesday" diff --git a/config/locales/companies.en.yml b/config/locales/companies.en.yml index becb087b1..f2b19bc19 100644 --- a/config/locales/companies.en.yml +++ b/config/locales/companies.en.yml @@ -16,7 +16,7 @@ en:      index:        title: "Companies"        name: "Search by name..." -      name_or_objectid: "Search by name or by Codifligne ID..." +      name_or_objectid: "Search by name or by ID..."        advanced_search: "Advanced search"    activerecord:      models: diff --git a/config/locales/compliance_check_sets.en.yml b/config/locales/compliance_check_sets.en.yml index 63708328b..73ecf8996 100644 --- a/config/locales/compliance_check_sets.en.yml +++ b/config/locales/compliance_check_sets.en.yml @@ -23,7 +23,7 @@ en:        table_state: "%{lines_status} lines imported out of %{lines_in_compliance_check_set} in the archive"        table_explanation: "These controls apply to all imported data and condition the construction of your organization's offer."        table_title: Analysed lines state -      metrics: "%{ok_count} ok, %{error_count} errors, %{warning_count} warnings, %{uncheck_count} n/a" +      metrics: "%{error_count} errors, %{warning_count} warnings"        metadatas:          referential: "Object analysed"          referential_type: "Apply to" diff --git a/config/locales/compliance_check_sets.fr.yml b/config/locales/compliance_check_sets.fr.yml index 20bf11d85..045fed4ce 100644 --- a/config/locales/compliance_check_sets.fr.yml +++ b/config/locales/compliance_check_sets.fr.yml @@ -19,7 +19,7 @@ fr:        table_state: "%{lines_status} lignes valides sur %{lines_in_compliance_check_set} présentes dans l'offre de transport"        table_explanation: Ces contrôles s’appliquent pour toutes les données importées et conditionnent la construction de l’offre de votre organisation        table_title: État des lignes analysées -      metrics: "%{ok_count} ok, %{error_count} errors, %{warning_count} warnings, %{uncheck_count} n/a" +      metrics: "%{error_count} errors, %{warning_count} warnings"        metadatas:          referential: "Objet analysé"          referential_type: "Appliqué à" diff --git a/config/locales/compliance_control_blocks.en.yml b/config/locales/compliance_control_blocks.en.yml index 275f05106..0ec979549 100644 --- a/config/locales/compliance_control_blocks.en.yml +++ b/config/locales/compliance_control_blocks.en.yml @@ -9,6 +9,12 @@ en:        compliance_control_blocks:          transport_mode: Transport mode          sub_transport_mode: Transport submode +    errors: +      models: +        compliance_control_block: +          attributes: +            condition_attributes: +              taken: The same compliance control block already exists in this compliance control set    compliance_control_blocks:      clone:        prefix: 'Copy of' @@ -16,8 +22,12 @@ en:        destroy_confirm: Are you sure you want to destroy this block ?      new:        title: Create a control block +    create: +      title: Create a control block      edit:        title: "Edit the control block : %{name}" +    update: +      title: "Edit the control block : %{name}"      metas:        control:          zero: "No controls" diff --git a/config/locales/compliance_control_blocks.fr.yml b/config/locales/compliance_control_blocks.fr.yml index a6720881f..5ce5b4729 100644 --- a/config/locales/compliance_control_blocks.fr.yml +++ b/config/locales/compliance_control_blocks.fr.yml @@ -9,6 +9,12 @@ fr:        compliance_control_blocks:          transport_mode: Mode de transport          transport_submode: Sous-mode de transport +    errors: +      models: +        compliance_control_block: +          attributes: +            condition_attributes: +              taken: Un groupe de contrôle identique existe déjà au sein de ce jeu de contrôles    compliance_control_blocks:      clone:        prefix: 'Copie de' @@ -16,8 +22,12 @@ fr:        destroy_confirm: Etes vous sûr de supprimer ce bloc ?      new:        title: Créer un groupe de contrôle(s) +    create: +      title: Créer un groupe de contrôle(s)      edit:        title: "Editer le groupe de contrôle : %{name}" +    update: +      title: "Editer le groupe de contrôle : %{name}"      metas:        control:          zero: "Aucun contrôle" diff --git a/config/locales/import_resources.en.yml b/config/locales/import_resources.en.yml index 386039319..4b1f9c394 100644 --- a/config/locales/import_resources.en.yml +++ b/config/locales/import_resources.en.yml @@ -6,7 +6,7 @@ en:          table_state: "%{lines_imported} line(s) imported on %{lines_in_zipfile} presents in zipfile"          table_title: "Satus of anlyzed files"          table_explanation: "When calendriers.xml and/or commun.xml are not imported, then all lines file are not processed." -        metrics: "%{ok_count} ok, %{error_count} errors, %{warning_count} warnings, %{uncheck_count} n/a" +        metrics: "%{error_count} errors, %{warning_count} warnings"    import_resources:      <<: *resources    activerecord: diff --git a/config/locales/import_resources.fr.yml b/config/locales/import_resources.fr.yml index 50fb7f1ca..93a576f01 100644 --- a/config/locales/import_resources.fr.yml +++ b/config/locales/import_resources.fr.yml @@ -6,7 +6,7 @@ fr:          table_state: "%{lines_imported} ligne(s) importée(s) sur %{lines_in_zipfile} présente(s) dans l'archive"          table_title: "Etat des fichiers analysés"          table_explanation: "Dans le cas ou le(s) fichiers calendriers.xml et/ou commun.xml sont dans un état non importé, alors tous les fichiers lignes sont automatiquement dans un état non traité." -        metrics: "%{ok_count} ok, %{error_count} errors, %{warning_count} warnings, %{uncheck_count} n/a" +        metrics: "%{error_count} errors, %{warning_count} warnings"    import_resources:      <<: *resources    activerecord: diff --git a/config/locales/line_referentials.en.yml b/config/locales/line_referentials.en.yml index 5663ed691..18ff28c24 100644 --- a/config/locales/line_referentials.en.yml +++ b/config/locales/line_referentials.en.yml @@ -7,7 +7,7 @@ en:      edit:        title: "Edit %{name} referential"      show: -      title: "iLICO synchronization" +      title: Line referential        synchronized: Synchronized        status: Status        message: Message diff --git a/config/locales/lines.en.yml b/config/locales/lines.en.yml index e61013725..1cd5150db 100644 --- a/config/locales/lines.en.yml +++ b/config/locales/lines.en.yml @@ -1,6 +1,5 @@  en:    lines: &en_lines -    search_no_results: "No line matching your query"      actions:        new: "Add a new line"        edit: "Edit this line" @@ -20,6 +19,8 @@ en:        show: 'Show'        show_network: 'Show network'        show_company: 'Show company' +    filters: +      name_or_objectid_cont: "Search by name or objectid"      new:        title: "Add a new line"      create: @@ -33,11 +34,12 @@ en:        routes:          title: "Routes list"        group_of_lines: "Groups of lines" +      search_no_results: "No line matching your query"      index:        deactivated: "Disabled line"        title: "Lines"        line: "Line %{line}" -      name_or_number_or_objectid: "Search by name, short name or ID Codif..." +      name_or_number_or_objectid: "Search by name, short name or ID..."        no_networks: "No networks"        no_companies: "No companies"        no_group_of_lines: "No group of lines" @@ -115,7 +117,7 @@ en:          creator_id: "Created by"          footnotes: "Footnotes"          stable_id: External permanent idenifier" -        state: State +        state: Status          activated: Activated          deactivated: Deactivated    formtastic: diff --git a/config/locales/lines.fr.yml b/config/locales/lines.fr.yml index d3069f1d1..058238710 100644 --- a/config/locales/lines.fr.yml +++ b/config/locales/lines.fr.yml @@ -1,6 +1,5 @@  fr:    lines: &fr_lines -    search_no_results: "Aucune ligne ne correspond à votre recherche"      actions:        new: "Ajouter une ligne"        edit: "Editer cette ligne" @@ -20,6 +19,8 @@ fr:        show: 'Consulter'        show_network: 'Voir le réseau'        show_company: 'Voir le transporteur principal' +    filters: +      name_or_objectid_cont: "Indiquez un nom d'itinéraire ou un ID..."      new:        title: "Ajouter une ligne"      create: @@ -34,6 +35,7 @@ fr:          title: "Liste des Itinéraires"        itineraries: "Liste des séquences d'arrêts de la ligne"        group_of_lines: "Groupes de lignes" +      search_no_results: "Aucune ligne ne correspond à votre recherche"      index:        deactivated: "Ligne désactivée"        title: "Lignes" diff --git a/config/locales/networks.en.yml b/config/locales/networks.en.yml index 94a8d9df0..2046a30ae 100644 --- a/config/locales/networks.en.yml +++ b/config/locales/networks.en.yml @@ -15,7 +15,7 @@ en:      index:        title: "Networks"        name: "Search by name..." -      name_or_objectid: "Search by name or by Codifligne ID..." +      name_or_objectid: "Search by name or by ID..."        advanced_search: "Advanced search"    activerecord:      models: diff --git a/config/locales/referentials.fr.yml b/config/locales/referentials.fr.yml index ec6c7c643..cf012ef8e 100644 --- a/config/locales/referentials.fr.yml +++ b/config/locales/referentials.fr.yml @@ -32,7 +32,7 @@ fr:          title: 'Dupliquer un jeu de données'        submit: "Valider"      select_compliance_control_set: -      title: "Sélection du jeu de contôles" +      title: "Sélection du jeu de contrôles"      actions:        new: "Créer un jeu de données"        destroy_confirm: "Etes vous sûr de vouloir supprimer ce jeu de données ?" diff --git a/config/locales/routes.en.yml b/config/locales/routes.en.yml index 58869b895..66805e050 100644 --- a/config/locales/routes.en.yml +++ b/config/locales/routes.en.yml @@ -15,7 +15,7 @@ en:        export_hub_all: "Export HUB routes"        add_stop_point: "Add stop point"        new_stop_point: "Create new stop" -      opposite_route_timetable: "Timetable back" +      reversed_vehicle_journey: "Reversed vehicle journeys"      new:        title: "Add a new route"      edit: diff --git a/config/locales/routes.fr.yml b/config/locales/routes.fr.yml index ddf706794..f4eefa10d 100644 --- a/config/locales/routes.fr.yml +++ b/config/locales/routes.fr.yml @@ -15,7 +15,8 @@ fr:        export_hub_all: "Export HUB des itinéraires"        add_stop_point: "Ajouter un arrêt"        new_stop_point: "Créer un arrêt pour l'ajouter" -      opposite_route_timetable: "Horaires retour" +      reversed_vehicle_journey: "Horaires retour" +    opposite: "%{name} (retour)"      new:        title: "Ajouter un itinéraire"      edit: @@ -56,6 +57,9 @@ fr:        stop_area_name: "Nom de l'arrêt"        for_boarding: "Montée"        for_alighting: "Descente" +    create_opposite: +      title: "Créer retour" +      success: "itinéraire créé avec succès"      duplicate:        title: "Dupliquer l'itinéraire"        success: "itinéraire dupliqué avec succès" diff --git a/config/locales/select2.en.yml b/config/locales/select2.en.yml new file mode 100644 index 000000000..308540af0 --- /dev/null +++ b/config/locales/select2.en.yml @@ -0,0 +1,15 @@ +en: +  select2: +    error_loading: The results cannot be loaded. +    input_too_long: +      one: Remove %{count} letter +      other: Remove %{count} letters +    input_too_short: +      one: Type %{count} letter +      other: Type %{count} letters +    loading_more: Loading more… +    maximum_selected: +      one: You can select %{count} element +      other: You can select %{count} elements +    no_results: No Results +    searching: Searching... diff --git a/config/locales/select2.fr.yml b/config/locales/select2.fr.yml new file mode 100644 index 000000000..4a37a3d78 --- /dev/null +++ b/config/locales/select2.fr.yml @@ -0,0 +1,15 @@ +fr: +  select2: +    error_loading: Les résultats ne peuvent pas être chargés. +    input_too_long: +      one: Supprimez %{count} caractère +      other: Supprimez %{count} caractères +    input_too_short: +      one: Saisissez %{count} caractère +      other: Saisissez %{count} caractères +    loading_more: Chargement de résultats supplémentaires… +    maximum_selected: +      one: Vous pouvez sélectionner %{count} élément +      other: Vous pouvez sélectionner %{count} élément +    no_results: Aucun résultat trouvé +    searching: Recherche en cours...
\ No newline at end of file diff --git a/config/locales/stop_area_referentials.en.yml b/config/locales/stop_area_referentials.en.yml index 11baf67e2..9d49d7c5d 100644 --- a/config/locales/stop_area_referentials.en.yml +++ b/config/locales/stop_area_referentials.en.yml @@ -4,7 +4,7 @@ en:        sync: "Launch a new reflex synchronization"        cancel_sync: "Cancel reflex synchronization"      show: -      title: 'Synchronization iCAR' +      title: 'Stop area referential'    activerecord:      models:        stop_area_referential: diff --git a/config/locales/stop_areas.en.yml b/config/locales/stop_areas.en.yml index 37d39b76c..1da4b58b4 100644 --- a/config/locales/stop_areas.en.yml +++ b/config/locales/stop_areas.en.yml @@ -8,7 +8,7 @@ en:        registration_number:          already_taken: Already taken          cannot_be_empty: This field is mandatory -        invalid: Incorrect value +        invalid: "Incorrect value (expected value: \"%{mask}\")"      default_geometry_success: "%{count} modified stop areas"      stop_area:        no_position: "No Position" @@ -18,6 +18,7 @@ en:        general: "General"        localisation: "Localisation"        accessibility: "Accessibility" +      custom_fields: "Custom fields"      actions:        new: "Add a new stop area"        create: "Add a new stop area" @@ -47,9 +48,9 @@ en:        export_hub_physical: "Export HUB physical"      filters:        name_or_objectid: "Search by name or by objectid..." -      zip_code: Type a zip code... -      city_name: Type a city name... -      area_type: Type an area type... +      zip_code: Enter a zip code... +      city_name: Enter a city name... +      area_type: Enter an area type...      new:        title: "Add a new stop"      update: @@ -69,7 +70,7 @@ en:        stop_managment: "Parent-child relations"        access_managment: "Access Points and Links managment"        access_points: "Access Points" -      not_editable: "Le type d'arrêt est non modifiable" +      not_editable: "The area type is not editable"        state:          active: Active          deactivated: Deactivated @@ -147,7 +148,7 @@ en:          zip_code: "Zip code"          city_name: "City"          waiting_time: Waiting time (minutes) -        state: State +        state: Status    formtastic:      titles:        stop_area: @@ -205,6 +206,9 @@ en:            comment: "Maximum length = 255."            coordinates: "Coordinates are mandatory."            projection_xy: "x,y in secondary referential, dot for decimal separator" +    hints: +      stop_area: +        registration_number: Leave empty for automatic value.    referential_stop_areas:      <<: *en_stop_areas diff --git a/config/locales/stop_areas.fr.yml b/config/locales/stop_areas.fr.yml index aee112be7..6a5fbf24b 100644 --- a/config/locales/stop_areas.fr.yml +++ b/config/locales/stop_areas.fr.yml @@ -9,7 +9,7 @@ fr:        registration_number:          already_taken: Déjà utilisé          cannot_be_empty: Ce champ est requis -        invalid: Valeur invalide +        invalid: "Valeur invalide (valeur attendue: \"%{mask}\")"      default_geometry_success: "%{count} arrêts édités"      stop_area:        no_position: "Pas de position" @@ -19,6 +19,7 @@ fr:        general: "General"        localisation: "Localisation"        accessibility: "Accessibilité" +      custom_fields: "Champs personnalisés"      actions:        new: "Ajouter un arrêt"        create: "Ajouter un arrêt" @@ -208,6 +209,9 @@ fr:            comment: "Longueur maximale = 255."            coordinates: "Les coordonnées sont obligatoires."            projection_xy: "x,y dans le référentiel secondaire, le séparateur de décimales est 'point'" +    hints: +      stop_area: +        registration_number: Laisser blanc pour assigner une valeur automatiquement.    referential_stop_areas:      <<: *fr_stop_areas diff --git a/config/locales/stop_points.en.yml b/config/locales/stop_points.en.yml index 72e138270..76e142ba1 100644 --- a/config/locales/stop_points.en.yml +++ b/config/locales/stop_points.en.yml @@ -43,8 +43,8 @@ en:          created_at: Created          updated_at: Updated          deleted_at: "Activated" -        for_boarding: "For boarding" -        for_alighting: "For alighting" +        for_boarding: "Boarding" +        for_alighting: "Alighting"          area_type: "Area type"          city_name: "City name"          zip_code: "Zip code" diff --git a/config/locales/vehicle_journeys.en.yml b/config/locales/vehicle_journeys.en.yml index 79e805ee8..12d8d0da4 100644 --- a/config/locales/vehicle_journeys.en.yml +++ b/config/locales/vehicle_journeys.en.yml @@ -3,8 +3,8 @@ en:      vehicle_journeys_matrix:        filters:          id: Filter by ID... -        timetable: Filter by journey pattern... -        timetable: Filter by timetable... +        journey_pattern: Filter by journey pattern... +        timetable: Filter by timetable...                  cancel_selection: "Cancel Selection"        fetching_error: "There has been a problem fetching the data. Please reload the page to try again."        line_routes: "Line's routes" diff --git a/config/locales/vehicle_journeys.fr.yml b/config/locales/vehicle_journeys.fr.yml index 4336bc6dc..466eca684 100644 --- a/config/locales/vehicle_journeys.fr.yml +++ b/config/locales/vehicle_journeys.fr.yml @@ -4,7 +4,7 @@ fr:        filters:          id: Filtrer par ID course...          journey_pattern: 'Filtrer par code, nom ou OID de mission...' -        timetable: Filter by timetable... +        timetable: Filtrer par calendrier...        cancel_selection: "Annuler la sélection"        fetching_error: "La récupération des missions a rencontré un problème. Rechargez la page pour tenter de corriger le problème."        line_routes: "Séquences d'arrêts de la ligne" @@ -121,7 +121,7 @@ fr:          checksum: "Signature métier"          comment: "Commentaires"          company: "Transporteur" -        company|_name: "Nom du transporteur" +        company_name: "Nom du transporteur"          created_at: "Créé le"          creator_id: "Créé par"          departure_time: "Départ" diff --git a/db/migrate/20150219175300_insert_default_organisation.rb b/db/migrate/20150219175300_insert_default_organisation.rb index 2734893f5..ac8ecb9b6 100644 --- a/db/migrate/20150219175300_insert_default_organisation.rb +++ b/db/migrate/20150219175300_insert_default_organisation.rb @@ -1,5 +1,5 @@  class InsertDefaultOrganisation < ActiveRecord::Migration -  class Organisation  < ActiveRecord::Base +  class Organisation  < ApplicationModel      attr_accessor :name    end diff --git a/db/migrate/20180313082623_add_custom_field_values_to_stop_areas.rb b/db/migrate/20180313082623_add_custom_field_values_to_stop_areas.rb new file mode 100644 index 000000000..e49be7e40 --- /dev/null +++ b/db/migrate/20180313082623_add_custom_field_values_to_stop_areas.rb @@ -0,0 +1,5 @@ +class AddCustomFieldValuesToStopAreas < ActiveRecord::Migration +  def change +    add_column :stop_areas, :custom_field_values, :jsonb +  end +end diff --git a/db/migrate/20180316115003_add_custom_field_values_to_companies.rb b/db/migrate/20180316115003_add_custom_field_values_to_companies.rb new file mode 100644 index 000000000..1791a6970 --- /dev/null +++ b/db/migrate/20180316115003_add_custom_field_values_to_companies.rb @@ -0,0 +1,5 @@ +class AddCustomFieldValuesToCompanies < ActiveRecord::Migration +  def change +    add_column :companies, :custom_field_values, :json +  end +end diff --git a/db/migrate/20180330074336_add_metadata_to_routes.rb b/db/migrate/20180330074336_add_metadata_to_routes.rb new file mode 100644 index 000000000..1a35dbb65 --- /dev/null +++ b/db/migrate/20180330074336_add_metadata_to_routes.rb @@ -0,0 +1,5 @@ +class AddMetadataToRoutes < ActiveRecord::Migration +  def change +    add_column :routes, :metadata, :jsonb +  end +end diff --git a/db/migrate/20180330124436_add_metadata_to_other_models.rb b/db/migrate/20180330124436_add_metadata_to_other_models.rb new file mode 100644 index 000000000..db01c77df --- /dev/null +++ b/db/migrate/20180330124436_add_metadata_to_other_models.rb @@ -0,0 +1,28 @@ +class AddMetadataToOtherModels < ActiveRecord::Migration +  def change +    [ +      Api::V1::ApiKey, +      Calendar, +      Chouette::AccessLink, +      Chouette::AccessPoint, +      Chouette::Company, +      Chouette::ConnectionLink, +      Chouette::GroupOfLine, +      Chouette::JourneyPattern, +      Chouette::Line, +      Chouette::Network, +      Chouette::PtLink, +      Chouette::PurchaseWindow, +      Chouette::RoutingConstraintZone, +      Chouette::StopArea, +      Chouette::StopPoint, +      Chouette::TimeTable, +      Chouette::Timeband, +      Chouette::VehicleJourney, +      ComplianceCheckSet, +      ComplianceControlSet, +    ].each do |model| +      add_column model.table_name.split(".").last, :metadata, :jsonb, default: {} +    end +  end +end diff --git a/db/migrate/20180403065419_remove_papertrail_tables.rb b/db/migrate/20180403065419_remove_papertrail_tables.rb new file mode 100644 index 000000000..8494058e1 --- /dev/null +++ b/db/migrate/20180403065419_remove_papertrail_tables.rb @@ -0,0 +1,5 @@ +class RemovePapertrailTables < ActiveRecord::Migration +  def change +    drop_table :versions +  end +end diff --git a/db/migrate/20180405133659_change_companies_custom_fields_values_type.rb b/db/migrate/20180405133659_change_companies_custom_fields_values_type.rb new file mode 100644 index 000000000..7248c29f6 --- /dev/null +++ b/db/migrate/20180405133659_change_companies_custom_fields_values_type.rb @@ -0,0 +1,8 @@ +class ChangeCompaniesCustomFieldsValuesType < ActiveRecord::Migration +  def change +    reversible do |dir| +      dir.up { change_column :companies, :custom_field_values, 'jsonb USING CAST(custom_field_values AS jsonb)', :default => {} } +      dir.down { change_column :companies, :custom_field_values, 'json USING CAST(custom_field_values AS json)', :default => {} } +    end +  end +end diff --git a/db/schema.rb b/db/schema.rb index 77e35f449..7e0e9c2b5 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -15,14 +15,14 @@ ActiveRecord::Schema.define(version: 20180319043333) do    # These are extensions that must be enabled in order to support this database    enable_extension "plpgsql" -  enable_extension "hstore"    enable_extension "postgis" +  enable_extension "hstore"    enable_extension "unaccent"    create_table "access_links", id: :bigserial, force: :cascade do |t|      t.integer  "access_point_id",                        limit: 8      t.integer  "stop_area_id",                           limit: 8 -    t.string   "objectid",                                                                  null: false +    t.string   "objectid",                                                                               null: false      t.integer  "object_version",                         limit: 8      t.string   "name"      t.string   "comment" @@ -39,6 +39,7 @@ ActiveRecord::Schema.define(version: 20180319043333) do      t.string   "link_orientation"      t.datetime "created_at"      t.datetime "updated_at" +    t.jsonb    "metadata",                                                                  default: {}    end    add_index "access_links", ["objectid"], name: "access_links_objectid_key", unique: true, using: :btree @@ -66,6 +67,7 @@ ActiveRecord::Schema.define(version: 20180319043333) do      t.text     "import_xml"      t.datetime "created_at"      t.datetime "updated_at" +    t.jsonb    "metadata",                                                            default: {}    end    add_index "access_points", ["objectid"], name: "access_points_objectid_key", unique: true, using: :btree @@ -77,6 +79,7 @@ ActiveRecord::Schema.define(version: 20180319043333) do      t.datetime "created_at"      t.datetime "updated_at"      t.integer  "organisation_id", limit: 8 +    t.jsonb    "metadata",                  default: {}    end    add_index "api_keys", ["organisation_id"], name: "index_api_keys_on_organisation_id", using: :btree @@ -89,9 +92,10 @@ ActiveRecord::Schema.define(version: 20180319043333) 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 +    t.jsonb     "metadata",                  default: {}    end    add_index "calendars", ["organisation_id"], name: "index_calendars_on_organisation_id", using: :btree @@ -117,13 +121,12 @@ ActiveRecord::Schema.define(version: 20180319043333) do      t.datetime "updated_at"      t.date     "end_date"      t.string   "date_type" -    t.string   "mode"    end    add_index "clean_ups", ["referential_id"], name: "index_clean_ups_on_referential_id", using: :btree    create_table "companies", id: :bigserial, force: :cascade do |t| -    t.string   "objectid",                            null: false +    t.string   "objectid",                                         null: false      t.integer  "object_version",            limit: 8      t.string   "name"      t.string   "short_name" @@ -140,6 +143,8 @@ ActiveRecord::Schema.define(version: 20180319043333) do      t.text     "import_xml"      t.datetime "created_at"      t.datetime "updated_at" +    t.jsonb    "custom_field_values" +    t.jsonb    "metadata",                            default: {}    end    add_index "companies", ["line_referential_id"], name: "index_companies_on_line_referential_id", using: :btree @@ -192,14 +197,15 @@ ActiveRecord::Schema.define(version: 20180319043333) do      t.string   "status"      t.integer  "parent_id",                 limit: 8      t.string   "parent_type" -    t.datetime "created_at",                          null: false -    t.datetime "updated_at",                          null: false +    t.datetime "created_at",                                       null: false +    t.datetime "updated_at",                                       null: false      t.string   "current_step_id"      t.float    "current_step_progress"      t.string   "name"      t.datetime "started_at"      t.datetime "ended_at"      t.datetime "notified_parent_at" +    t.jsonb    "metadata",                            default: {}    end    add_index "compliance_check_sets", ["compliance_control_set_id"], name: "index_compliance_check_sets_on_compliance_control_set_id", using: :btree @@ -238,8 +244,9 @@ ActiveRecord::Schema.define(version: 20180319043333) do    create_table "compliance_control_sets", id: :bigserial, force: :cascade do |t|      t.string   "name"      t.integer  "organisation_id", limit: 8 -    t.datetime "created_at",                null: false -    t.datetime "updated_at",                null: false +    t.datetime "created_at",                             null: false +    t.datetime "updated_at",                             null: false +    t.jsonb    "metadata",                  default: {}    end    add_index "compliance_control_sets", ["organisation_id"], name: "index_compliance_control_sets_on_organisation_id", using: :btree @@ -265,7 +272,7 @@ ActiveRecord::Schema.define(version: 20180319043333) do    create_table "connection_links", id: :bigserial, force: :cascade do |t|      t.integer  "departure_id",                           limit: 8      t.integer  "arrival_id",                             limit: 8 -    t.string   "objectid",                                                                  null: false +    t.string   "objectid",                                                                               null: false      t.integer  "object_version",                         limit: 8      t.string   "name"      t.string   "comment" @@ -281,6 +288,7 @@ ActiveRecord::Schema.define(version: 20180319043333) do      t.integer  "int_user_needs"      t.datetime "created_at"      t.datetime "updated_at" +    t.jsonb    "metadata",                                                                  default: {}    end    add_index "connection_links", ["objectid"], name: "connection_links_objectid_key", unique: true, using: :btree @@ -398,7 +406,7 @@ ActiveRecord::Schema.define(version: 20180319043333) do    end    create_table "group_of_lines", id: :bigserial, force: :cascade do |t| -    t.string   "objectid",                      null: false +    t.string   "objectid",                                   null: false      t.integer  "object_version",      limit: 8      t.string   "name"      t.string   "comment" @@ -407,6 +415,7 @@ ActiveRecord::Schema.define(version: 20180319043333) do      t.text     "import_xml"      t.datetime "created_at"      t.datetime "updated_at" +    t.jsonb    "metadata",                      default: {}    end    add_index "group_of_lines", ["line_referential_id"], name: "index_group_of_lines_on_line_referential_id", using: :btree @@ -460,9 +469,9 @@ ActiveRecord::Schema.define(version: 20180319043333) do      t.string   "type"      t.integer  "parent_id",             limit: 8      t.string   "parent_type" +    t.datetime "notified_parent_at"      t.integer  "current_step",                    default: 0      t.integer  "total_steps",                     default: 0 -    t.datetime "notified_parent_at"      t.string   "creator"    end @@ -485,7 +494,7 @@ ActiveRecord::Schema.define(version: 20180319043333) do    create_table "journey_patterns", id: :bigserial, force: :cascade do |t|      t.integer  "route_id",                limit: 8 -    t.string   "objectid",                          null: false +    t.string   "objectid",                                       null: false      t.integer  "object_version",          limit: 8      t.string   "name"      t.string   "comment" @@ -499,6 +508,7 @@ ActiveRecord::Schema.define(version: 20180319043333) do      t.text     "checksum_source"      t.string   "data_source_ref"      t.json     "costs" +    t.jsonb    "metadata",                          default: {}    end    add_index "journey_patterns", ["objectid"], name: "journey_patterns_objectid_key", unique: true, using: :btree @@ -572,6 +582,7 @@ ActiveRecord::Schema.define(version: 20180319043333) do      t.datetime "created_at"      t.datetime "updated_at"      t.boolean  "seasonal" +    t.jsonb    "metadata",                                  default: {}    end    add_index "lines", ["line_referential_id"], name: "index_lines_on_line_referential_id", using: :btree @@ -593,7 +604,7 @@ ActiveRecord::Schema.define(version: 20180319043333) do    add_index "merges", ["workbench_id"], name: "index_merges_on_workbench_id", using: :btree    create_table "networks", id: :bigserial, force: :cascade do |t| -    t.string   "objectid",                      null: false +    t.string   "objectid",                                   null: false      t.integer  "object_version",      limit: 8      t.date     "version_date"      t.string   "description" @@ -607,6 +618,7 @@ ActiveRecord::Schema.define(version: 20180319043333) do      t.integer  "line_referential_id", limit: 8      t.datetime "created_at"      t.datetime "updated_at" +    t.jsonb    "metadata",                      default: {}    end    add_index "networks", ["line_referential_id"], name: "index_networks_on_line_referential_id", using: :btree @@ -631,13 +643,14 @@ ActiveRecord::Schema.define(version: 20180319043333) do      t.integer  "start_of_link_id", limit: 8      t.integer  "end_of_link_id",   limit: 8      t.integer  "route_id",         limit: 8 -    t.string   "objectid",                                            null: false +    t.string   "objectid",                                                         null: false      t.integer  "object_version",   limit: 8      t.string   "name"      t.string   "comment"      t.decimal  "link_distance",              precision: 19, scale: 2      t.datetime "created_at"      t.datetime "updated_at" +    t.jsonb    "metadata",                                            default: {}    end    add_index "pt_links", ["objectid"], name: "pt_links_objectid_key", unique: true, using: :btree @@ -645,13 +658,14 @@ ActiveRecord::Schema.define(version: 20180319043333) do    create_table "purchase_windows", id: :bigserial, force: :cascade do |t|      t.string    "name"      t.string    "color" -    t.daterange "date_ranges",                            array: true -    t.datetime  "created_at",                null: false -    t.datetime  "updated_at",                null: false +    t.daterange "date_ranges",                                         array: true +    t.datetime  "created_at",                             null: false +    t.datetime  "updated_at",                             null: false      t.string    "objectid"      t.string    "checksum"      t.text      "checksum_source"      t.integer   "referential_id",  limit: 8 +    t.jsonb     "metadata",                  default: {}    end    add_index "purchase_windows", ["referential_id"], name: "index_purchase_windows_on_referential_id", using: :btree @@ -742,6 +756,7 @@ ActiveRecord::Schema.define(version: 20180319043333) do      t.text     "checksum_source"      t.string   "data_source_ref"      t.json     "costs" +    t.jsonb    "metadata"    end    add_index "routes", ["objectid"], name: "routes_objectid_key", unique: true, using: :btree @@ -750,13 +765,14 @@ ActiveRecord::Schema.define(version: 20180319043333) do      t.string   "name"      t.datetime "created_at"      t.datetime "updated_at" -    t.string   "objectid",                  null: false +    t.string   "objectid",                               null: false      t.integer  "object_version",  limit: 8      t.integer  "route_id",        limit: 8 -    t.integer  "stop_point_ids",  limit: 8,              array: true +    t.integer  "stop_point_ids",  limit: 8,                           array: true      t.string   "checksum"      t.text     "checksum_source"      t.string   "data_source_ref" +    t.jsonb    "metadata",                  default: {}    end    create_table "routing_constraints_lines", id: false, force: :cascade do |t| @@ -810,7 +826,7 @@ ActiveRecord::Schema.define(version: 20180319043333) do    create_table "stop_areas", id: :bigserial, force: :cascade do |t|      t.integer  "parent_id",                       limit: 8 -    t.string   "objectid",                                                            null: false +    t.string   "objectid",                                                                         null: false      t.integer  "object_version",                  limit: 8      t.string   "name"      t.string   "comment" @@ -841,10 +857,9 @@ ActiveRecord::Schema.define(version: 20180319043333) do      t.integer  "waiting_time"      t.string   "kind"      t.jsonb    "localized_names" -      t.datetime "confirmed_at" -    t.json     "custom_field_values" - +    t.jsonb    "custom_field_values" +    t.jsonb    "metadata",                                                            default: {}    end    add_index "stop_areas", ["name"], name: "index_stop_areas_on_name", using: :btree @@ -860,13 +875,14 @@ ActiveRecord::Schema.define(version: 20180319043333) do    create_table "stop_points", id: :bigserial, force: :cascade do |t|      t.integer  "route_id",       limit: 8      t.integer  "stop_area_id",   limit: 8 -    t.string   "objectid",                 null: false +    t.string   "objectid",                              null: false      t.integer  "object_version", limit: 8      t.integer  "position"      t.string   "for_boarding"      t.string   "for_alighting"      t.datetime "created_at"      t.datetime "updated_at" +    t.jsonb    "metadata",                 default: {}    end    add_index "stop_points", ["objectid"], name: "stop_points_objectid_key", unique: true, using: :btree @@ -914,7 +930,7 @@ ActiveRecord::Schema.define(version: 20180319043333) do    add_index "time_table_periods", ["time_table_id"], name: "index_time_table_periods_on_time_table_id", using: :btree    create_table "time_tables", id: :bigserial, force: :cascade do |t| -    t.string   "objectid",                              null: false +    t.string   "objectid",                               null: false      t.integer  "object_version",  limit: 8, default: 1      t.string   "version"      t.string   "comment" @@ -929,6 +945,7 @@ ActiveRecord::Schema.define(version: 20180319043333) do      t.string   "checksum"      t.text     "checksum_source"      t.string   "data_source_ref" +    t.jsonb    "metadata",                  default: {}    end    add_index "time_tables", ["calendar_id"], name: "index_time_tables_on_calendar_id", using: :btree @@ -944,13 +961,14 @@ ActiveRecord::Schema.define(version: 20180319043333) do    add_index "time_tables_vehicle_journeys", ["vehicle_journey_id"], name: "index_time_tables_vehicle_journeys_on_vehicle_journey_id", using: :btree    create_table "timebands", id: :bigserial, force: :cascade do |t| -    t.string   "objectid",                 null: false +    t.string   "objectid",                              null: false      t.integer  "object_version", limit: 8      t.string   "name" -    t.time     "start_time",               null: false -    t.time     "end_time",                 null: false +    t.time     "start_time",                            null: false +    t.time     "end_time",                              null: false      t.datetime "created_at"      t.datetime "updated_at" +    t.jsonb    "metadata",                 default: {}    end    create_table "users", id: :bigserial, force: :cascade do |t| @@ -1033,6 +1051,7 @@ ActiveRecord::Schema.define(version: 20180319043333) do      t.text     "checksum_source"      t.string   "data_source_ref"      t.jsonb    "custom_field_values",                       default: {} +    t.jsonb    "metadata",                                  default: {}    end    add_index "vehicle_journeys", ["objectid"], name: "vehicle_journeys_objectid_key", unique: true, using: :btree @@ -1074,8 +1093,10 @@ ActiveRecord::Schema.define(version: 20180319043333) do      t.string   "name"      t.integer  "line_referential_id",      limit: 8      t.integer  "stop_area_referential_id", limit: 8 -    t.datetime "created_at",                         null: false -    t.datetime "updated_at",                         null: false +    t.datetime "created_at",                                      null: false +    t.datetime "updated_at",                                      null: false +    t.string   "import_types",                       default: [],              array: true +    t.string   "export_types",                       default: [],              array: true    end    add_foreign_key "access_links", "access_points", name: "aclk_acpt_fkey" diff --git a/db/seeds/dev/custom_fields.seeds.rb b/db/seeds/dev/custom_fields.seeds.rb new file mode 100644 index 000000000..eb3afc394 --- /dev/null +++ b/db/seeds/dev/custom_fields.seeds.rb @@ -0,0 +1,52 @@ +# coding: utf-8 + +require_relative '../seed_helpers' + +Workgroup.find_each do |workgroup| +  puts workgroup.inspect + +  workgroup.custom_fields.seed_by(code: "capacity") do |field| +    field.resource_type = "VehicleJourney" +    field.name = "Bus Capacity" +    field.field_type = "list" +    field.options = { list_values: { "0": "", "1": "48 places", "2": "54 places" }} +  end + +  workgroup.custom_fields.seed_by(code: "company_commercial_name") do |field| +    field.resource_type = "Company" +    field.name = "Nom commercial" +    field.field_type = "list" +    field.options = { list_values: { "0": "", "1": "OuiBus", "2": "Alsa" }} +  end + +  workgroup.custom_fields.seed_by(code: "company_contact_name") do |field| +    field.resource_type = "Company" +    field.name = "Nom du référent" +    field.field_type = "string" +  end + +  workgroup.custom_fields.seed_by(code: "stop_area_test_list") do |field| +    field.resource_type = "StopArea" +    field.name = "Test de Liste" +    field.field_type = "list" +    field.options = { list_values: { "0": "", "1": "Valeur 1", "2": "Valeur 2" }} +  end + +  workgroup.custom_fields.seed_by(code: "stop_area_test_string") do |field| +    field.resource_type = "StopArea" +    field.name = "Test de Texte" +    field.field_type = "string" +  end + +  workgroup.custom_fields.seed_by(code: "stop_area_test_integer") do |field| +    field.resource_type = "StopArea" +    field.name = "Test de Nomber" +    field.field_type = "integer" +  end + +  workgroup.custom_fields.seed_by(code: "stop_area_test_attachment") do |field| +    field.resource_type = "StopArea" +    field.name = "Test de Piece Jointe" +    field.field_type = "attachment" +  end +end diff --git a/db/seeds/development/custom_fields.seeds.rb b/db/seeds/development/custom_fields.seeds.rb new file mode 100644 index 000000000..eb3afc394 --- /dev/null +++ b/db/seeds/development/custom_fields.seeds.rb @@ -0,0 +1,52 @@ +# coding: utf-8 + +require_relative '../seed_helpers' + +Workgroup.find_each do |workgroup| +  puts workgroup.inspect + +  workgroup.custom_fields.seed_by(code: "capacity") do |field| +    field.resource_type = "VehicleJourney" +    field.name = "Bus Capacity" +    field.field_type = "list" +    field.options = { list_values: { "0": "", "1": "48 places", "2": "54 places" }} +  end + +  workgroup.custom_fields.seed_by(code: "company_commercial_name") do |field| +    field.resource_type = "Company" +    field.name = "Nom commercial" +    field.field_type = "list" +    field.options = { list_values: { "0": "", "1": "OuiBus", "2": "Alsa" }} +  end + +  workgroup.custom_fields.seed_by(code: "company_contact_name") do |field| +    field.resource_type = "Company" +    field.name = "Nom du référent" +    field.field_type = "string" +  end + +  workgroup.custom_fields.seed_by(code: "stop_area_test_list") do |field| +    field.resource_type = "StopArea" +    field.name = "Test de Liste" +    field.field_type = "list" +    field.options = { list_values: { "0": "", "1": "Valeur 1", "2": "Valeur 2" }} +  end + +  workgroup.custom_fields.seed_by(code: "stop_area_test_string") do |field| +    field.resource_type = "StopArea" +    field.name = "Test de Texte" +    field.field_type = "string" +  end + +  workgroup.custom_fields.seed_by(code: "stop_area_test_integer") do |field| +    field.resource_type = "StopArea" +    field.name = "Test de Nomber" +    field.field_type = "integer" +  end + +  workgroup.custom_fields.seed_by(code: "stop_area_test_attachment") do |field| +    field.resource_type = "StopArea" +    field.name = "Test de Piece Jointe" +    field.field_type = "attachment" +  end +end diff --git a/db/seeds/seed_helpers.rb b/db/seeds/seed_helpers.rb new file mode 100644 index 000000000..708362a6c --- /dev/null +++ b/db/seeds/seed_helpers.rb @@ -0,0 +1,12 @@ +class ActiveRecord::Base +  def self.seed_by(key_attribute, &block) +    model = find_or_initialize_by key_attribute +    print "Seed #{name} #{key_attribute.inspect} " +    yield model + +    puts "[#{(model.changed? ? 'updated' : 'no change')}]" +    model.save! + +    model +  end +end diff --git a/db/seeds/stif.seeds.rb b/db/seeds/stif.seeds.rb index aa87b6f6c..98192385f 100644 --- a/db/seeds/stif.seeds.rb +++ b/db/seeds/stif.seeds.rb @@ -1,46 +1,25 @@  # coding: utf-8 -# This file should contain all the record creation needed to seed the database with its default values. -# The data can then be loaded with the rake db:seed (or created alongside the db with db:setup). -stop_area_referential = StopAreaReferential.find_or_create_by!(name: "Reflex", objectid_format: "stif_netex") -line_referential = LineReferential.find_or_create_by!(name: "CodifLigne", objectid_format: "stif_netex") +require_relative 'seed_helpers' -workgroup = Workgroup.find_or_create_by!(name: "Gestion de l'offre théorique IDFm") do |w| -  w.line_referential      = line_referential -  w.stop_area_referential = stop_area_referential -  w.export_types = ["Export::Netex"] +stif = Organisation.seed_by(code: "STIF") do |o| +  o.name = 'STIF'  end -Workbench.update_all workgroup_id: workgroup - -# Organisations -stif = Organisation.find_or_create_by!(code: "STIF") do |org| -  org.name = 'STIF' +stop_area_referential = StopAreaReferential.seed_by(name: "Reflex") do |r| +  r.objectid_format = "stif_netex" +  r.add_member stif, owner: true  end -# operator = Organisation.find_or_create_by!(code: 'transporteur-a') do |organisation| -#   organisation.name = "Transporteur A" -# end - -# Member -line_referential.add_member stif, owner: true -# line_referential.add_member operator -stop_area_referential.add_member stif, owner: true -# stop_area_referential.add_member operator +line_referential = LineReferential.seed_by(name: "CodifLigne") do |r| +  r.objectid_format = "stif_codifligne" +  r.add_member stif, owner: true +end -# Users -# stif.users.find_or_create_by!(username: "admin") do |user| -#   user.email = 'stif-boiv@af83.com' -#   user.password = "secret" -#   user.name = "STIF Administrateur" -# end -# -# operator.users.find_or_create_by!(username: "transporteur") do |user| -#   user.email = 'stif-boiv+transporteur@af83.com' -#   user.password = "secret" -#   user.name = "Martin Lejeune" -# end +workgroup = Workgroup.seed_by(name: "Gestion de l'offre théorique IDFm") do |w| +  w.line_referential      = line_referential +  w.stop_area_referential = stop_area_referential +  w.export_types          = ["Export::Netex"] +end -# Include all Lines in organisation functional_scope -stif.update sso_attributes: { functional_scope: line_referential.lines.pluck(:objectid) } -#operator.update sso_attributes: { functional_scope: line_referential.lines.limit(3).pluck(:objectid) } +Workbench.update_all workgroup_id: workgroup diff --git a/lib/route_way_cost_unit_converter.rb b/lib/route_way_cost_unit_converter.rb index 45edbf538..52515e52c 100644 --- a/lib/route_way_cost_unit_converter.rb +++ b/lib/route_way_cost_unit_converter.rb @@ -8,18 +8,24 @@ class RouteWayCostUnitConverter      end    end -  private -    # Round to 2 decimal places to appease JavaScript validation    def self.meters_to_kilometers(num)      return 0 unless num -    (num / 1000.0).to_i +    snap_to_one(num / 1000.0).to_i    end    def self.seconds_to_minutes(num)      return 0 unless num -    num / 60 +    snap_to_one(num / 60.0).to_i +  end + +  private + +  def self.snap_to_one(decimal) +    return 1 if decimal > 0 && decimal <= 1 + +    decimal    end  end diff --git a/lib/tasks/ci.rake b/lib/tasks/ci.rake index 5b2c8ae3c..cb9ed77e7 100644 --- a/lib/tasks/ci.rake +++ b/lib/tasks/ci.rake @@ -10,6 +10,7 @@ namespace :ci do    desc "Prepare CI build"    task :setup do +    cp "config/database.yml", "config/database.yml.orig"      cp "config/database/ci.yml", "config/database.yml"      puts "Use #{database_name} database"      sh "RAILS_ENV=test rake db:drop db:create db:migrate" @@ -28,6 +29,7 @@ namespace :ci do    end    def deploy_env +    return ENV["DEPLOY_ENV"] if ENV["DEPLOY_ENV"]      if git_branch == "master"        "dev"      elsif git_branch.in?(deploy_envs) @@ -54,12 +56,12 @@ namespace :ci do    desc "Deploy after CI"    task :deploy do -    return if ENV["CHOUETTE_DEPLOY_DISABLED"] - -    if deploy_env -      sh "cap #{deploy_env} deploy:migrations" -    else -      puts "No deploy for branch #{git_branch}" +    unless ENV["CHOUETTE_DEPLOY_DISABLED"] +      if deploy_env +        sh "cap #{deploy_env} deploy:migrations deploy:seed" +      else +        puts "No deploy for branch #{git_branch}" +      end      end    end @@ -75,6 +77,9 @@ namespace :ci do      task :clean do        puts "Drop #{database_name} database"        sh "RAILS_ENV=test rake db:drop" + +      # Restore projet config/database.yml +      # cp "config/database.yml.orig", "config/database.yml" if File.exists?("config/database.yml.orig")      end    end diff --git a/lib/tom_tom.rb b/lib/tom_tom.rb index a1a2bda43..fcebcc7ac 100644 --- a/lib/tom_tom.rb +++ b/lib/tom_tom.rb @@ -19,4 +19,8 @@ module TomTom    def self.batch(way_costs)      TomTom::Batch.new(@connection).batch(way_costs)    end + +  def self.matrix(way_costs) +    TomTom::Matrix.new(@connection).matrix(way_costs) +  end  end diff --git a/lib/tom_tom/matrix.rb b/lib/tom_tom/matrix.rb new file mode 100644 index 000000000..b0c8cc335 --- /dev/null +++ b/lib/tom_tom/matrix.rb @@ -0,0 +1,114 @@ +module TomTom +  class Matrix +    def initialize(connection) +      @connection = connection +    end + +    def matrix(way_costs) +      points_with_ids = points_from_way_costs(way_costs) +      points = points_as_params(points_with_ids) + +      Rails.logger.info "Invoke TomTom for #{points.size} points" + +      response = @connection.post do |req| +        req.url '/routing/1/matrix/json' +        req.headers['Content-Type'] = 'application/json' + +        req.params[:routeType] = 'shortest' +        req.params[:travelMode] = 'bus' + +        req.body = build_request_body(points) +      end + +      extract_costs_to_way_costs!( +        way_costs, +        points_with_ids, +        JSON.parse(response.body) +      ) +    end + +    def points_from_way_costs(way_costs) +      points = [] + +      way_costs.each do |way_cost| +        departure_id, arrival_id = way_cost.id.split('-') + +        departure = TomTom::Matrix::Point.new( +          way_cost.departure, +          departure_id +        ) +        arrival = TomTom::Matrix::Point.new( +          way_cost.arrival, +          arrival_id +        ) + +        # Don't add duplicate coordinates. This assumes that +        # `way_costs` consists of an ordered route of points where +        # each departure coordinate is the same as the preceding +        # arrival coordinate. +        if points.empty? || +            points.last.coordinates != departure.coordinates +          points << departure +        end + +        points << arrival +      end + +      points +    end + +    def points_as_params(points) +      points.map do |point| +        { +          point: { +            latitude: point.coordinates.lat, +            longitude: point.coordinates.lng +          } +        } +      end +    end + +    def build_request_body(points) +      # Serialize `BigDecimal` values as floats to please the TomTom API +      RequestJSONSerializer.dump({ +        origins: points, +        destinations: points +      }) +    end + +    def extract_costs_to_way_costs!(way_costs, points, matrix_json) +      way_costs = [] + +      # `row` and `column` order is the same as `points` +      matrix_json['matrix'].each_with_index do |row, row_i| +        row.each_with_index do |column, column_i| +          next if column['statusCode'] != 200 + +          distance = column['response']['routeSummary']['lengthInMeters'] + +          # Ignore costs between a point and itself (e.g. from A to A) +          next if distance == 0 + +          departure = points[row_i] +          arrival = points[column_i] + +          way_costs << WayCost.new( +            departure: Geokit::LatLng.new( +              departure.coordinates.lat, +              departure.coordinates.lng +            ), +            arrival: Geokit::LatLng.new( +              arrival.coordinates.lat, +              arrival.coordinates.lng +            ), +            distance: distance, +            time: column['response']['routeSummary']['travelTimeInSeconds'], +            id: "#{departure.id}-#{arrival.id}" +          ) +        end +      end + +      way_costs +    end +  end +end diff --git a/lib/tom_tom/matrix/point.rb b/lib/tom_tom/matrix/point.rb new file mode 100644 index 000000000..435b4d4b0 --- /dev/null +++ b/lib/tom_tom/matrix/point.rb @@ -0,0 +1,18 @@ +module TomTom +  class Matrix +    class Point +      attr_reader :coordinates, :id + +      def initialize(coordinates, id) +        @coordinates = coordinates +        @id = id +      end + +      def ==(other) +        other.is_a?(self.class) && +          @coordinates == other.coordinates && +          @id == other.id +      end +    end +  end +end diff --git a/lib/tom_tom/matrix/request_json_serializer.rb b/lib/tom_tom/matrix/request_json_serializer.rb new file mode 100644 index 000000000..f4d12e482 --- /dev/null +++ b/lib/tom_tom/matrix/request_json_serializer.rb @@ -0,0 +1,25 @@ +module TomTom +  class Matrix +    class RequestJSONSerializer +      def self.dump(hash) +        hash[:origins].map! do |point| +          point_to_f(point) +        end +        hash[:destinations].map! do |point| +          point_to_f(point) +        end + +        JSON.dump(hash) +      end + +      private + +      def self.point_to_f(point) +        point[:point][:latitude] = point[:point][:latitude].to_f +        point[:point][:longitude] = point[:point][:longitude].to_f + +        point +      end +    end +  end +end diff --git a/spec/controllers/exports_controller_spec.rb b/spec/controllers/exports_controller_spec.rb index 9d8dde4ff..e2b89fc26 100644 --- a/spec/controllers/exports_controller_spec.rb +++ b/spec/controllers/exports_controller_spec.rb @@ -85,7 +85,7 @@ RSpec.describe ExportsController, :type => :controller do      context "with the token" do        it 'should be successful' do          post :upload, workbench_id: workbench.id, id: export.id, token: export.token_upload -        expect(response).to be_redirect +        expect(response).to be_success        end      end diff --git a/spec/controllers/routes_controller_spec.rb b/spec/controllers/routes_controller_spec.rb index e4dc6bc23..b7cb66b46 100644 --- a/spec/controllers/routes_controller_spec.rb +++ b/spec/controllers/routes_controller_spec.rb @@ -42,11 +42,14 @@ RSpec.describe RoutesController, type: :controller do      before(:each) do        post :create, line_id: route.line_id,            referential_id: referential.id, -          route: { name: "changed"} +          route: { name: "changed", published_name: "published_name"}      end      it_behaves_like "line and referential linked"      it_behaves_like "redirected to referential_line_path(referential,line)" +    it "sets metadata" do +      expect(Chouette::Route.last.metadata.creator_username).to eq @user.username +    end    end    describe "PUT /update" do @@ -58,6 +61,9 @@ RSpec.describe RoutesController, type: :controller do      it_behaves_like "route, line and referential linked"      it_behaves_like "redirected to referential_line_path(referential,line)" +    it "sets metadata" do +      expect(Chouette::Route.last.metadata.modifier_username).to eq @user.username +    end    end    describe "GET /show" do @@ -83,6 +89,42 @@ RSpec.describe RoutesController, type: :controller do        expect(Chouette::Route.last.name).to eq(I18n.t('activerecord.copy', name: route.name))        expect(Chouette::Route.last.published_name).to eq(route.published_name) +      expect(Chouette::Route.last.stop_area_ids).to eq route.stop_area_ids +    end + +    context "when opposite = true" do +      it "creates a new route on the opposite way " do +        expect do +          post :duplicate, +            referential_id: route.line.line_referential_id, +            line_id: route.line_id, +            id: route.id, +            opposite: TRUE +        end.to change { Chouette::Route.count }.by(1) + +        expect(Chouette::Route.last.name).to eq(I18n.t('routes.opposite', name: route.name)) +        expect(Chouette::Route.last.published_name).to eq(Chouette::Route.last.name) +        expect(Chouette::Route.last.opposite_route).to eq(route) +        expect(Chouette::Route.last.stop_area_ids).to eq route.stop_area_ids.reverse +      end +    end + +    context "on a duplicated route" do +      let!(:duplicated){ route.duplicate } +      it "creates a new route on the opposite way " do +        expect do +          post :duplicate, +            referential_id: duplicated.line.line_referential_id, +            line_id: duplicated.line_id, +            id: duplicated.id, +            opposite: TRUE +        end.to change { Chouette::Route.count }.by(1) + +        expect(Chouette::Route.last.name).to eq(I18n.t('routes.opposite', name: duplicated.name)) +        expect(Chouette::Route.last.published_name).to eq(Chouette::Route.last.name) +        expect(Chouette::Route.last.opposite_route).to eq(duplicated) +        expect(Chouette::Route.last.stop_area_ids).to eq duplicated.stop_area_ids.reverse +      end      end    end  end diff --git a/spec/factories/custom_fields.rb b/spec/factories/custom_fields.rb index 7c43a6147..db7a3823e 100644 --- a/spec/factories/custom_fields.rb +++ b/spec/factories/custom_fields.rb @@ -3,7 +3,7 @@ FactoryGirl.define do      code "code"      resource_type "VehicleJourney"      sequence(:name){|n| "custom field ##{n}"} -    field_type "list" +    field_type "integer"      options( { capacity: "0" } )    end  end diff --git a/spec/features/calendars_permissions_spec.rb b/spec/features/calendars_permissions_spec.rb index 4857592d5..656c0dd78 100644 --- a/spec/features/calendars_permissions_spec.rb +++ b/spec/features/calendars_permissions_spec.rb @@ -1,8 +1,8 @@  RSpec.describe 'Calendars', type: :feature do    login_user -  let(:calendar) { create :calendar, organisation_id: 1 } -  let(:workgroup) { calendar.workgroup } +  let(:calendar) { create :calendar, organisation: first_organisation, workgroup: first_workgroup } +  let(:workgroup) { first_workgroup }    describe 'permissions' do      before do diff --git a/spec/features/compliance_control_sets_spec.rb b/spec/features/compliance_control_sets_spec.rb index 0f4597db3..306f363a5 100644 --- a/spec/features/compliance_control_sets_spec.rb +++ b/spec/features/compliance_control_sets_spec.rb @@ -12,7 +12,7 @@ RSpec.describe "ComplianceControlSets", type: :feature do    let(:other_control_cset) { create :compliance_control_set, organisation: other_orga }    let(:blox){ -    2.times.map{ | _ | create :compliance_control_block, compliance_control_set: control_set } +    2.times.map{ |n| create :compliance_control_block, compliance_control_set: control_set, transport_mode: StifTransportModeEnumerations.transport_modes[n], transport_submode: StifTransportSubmodeEnumerations.transport_submodes[n] }    }    before do diff --git a/spec/fixtures/tom_tom_matrix.json b/spec/fixtures/tom_tom_matrix.json new file mode 100644 index 000000000..30048576c --- /dev/null +++ b/spec/fixtures/tom_tom_matrix.json @@ -0,0 +1,123 @@ +{ +  "formatVersion": "0.0.1", +  "matrix": [ +    [ +      { +        "statusCode": 200, +        "response": { +          "routeSummary": { +            "lengthInMeters": 0, +            "travelTimeInSeconds": 0, +            "trafficDelayInSeconds": 0, +            "departureTime": "2018-03-23T11:20:17+01:00", +            "arrivalTime": "2018-03-23T11:20:17+01:00" +          } +        } +      }, +      { +        "statusCode": 200, +        "response": { +          "routeSummary": { +            "lengthInMeters": 117947, +            "travelTimeInSeconds": 8356, +            "trafficDelayInSeconds": 0, +            "departureTime": "2018-03-23T11:20:17+01:00", +            "arrivalTime": "2018-03-23T13:39:32+01:00" +          } +        } +      }, +      { +        "statusCode": 200, +        "response": { +          "routeSummary": { +            "lengthInMeters": 999088, +            "travelTimeInSeconds": 62653, +            "trafficDelayInSeconds": 298, +            "departureTime": "2018-03-23T11:20:17+01:00", +            "arrivalTime": "2018-03-24T04:44:30+01:00" +          } +        } +      } +    ], +    [ +      { +        "statusCode": 200, +        "response": { +          "routeSummary": { +            "lengthInMeters": 117231, +            "travelTimeInSeconds": 9729, +            "trafficDelayInSeconds": 0, +            "departureTime": "2018-03-23T11:20:17+01:00", +            "arrivalTime": "2018-03-23T14:02:25+01:00" +          } +        } +      }, +      { +        "statusCode": 200, +        "response": { +          "routeSummary": { +            "lengthInMeters": 0, +            "travelTimeInSeconds": 0, +            "trafficDelayInSeconds": 0, +            "departureTime": "2018-03-23T11:20:17+01:00", +            "arrivalTime": "2018-03-23T11:20:17+01:00" +          } +        } +      }, +      { +        "statusCode": 200, +        "response": { +          "routeSummary": { +            "lengthInMeters": 1114635, +            "travelTimeInSeconds": 72079, +            "trafficDelayInSeconds": 298, +            "departureTime": "2018-03-23T11:20:18+01:00", +            "arrivalTime": "2018-03-24T07:21:36+01:00" +          } +        } +      } +    ], +    [ +      { +        "statusCode": 200, +        "response": { +          "routeSummary": { +            "lengthInMeters": 997232, +            "travelTimeInSeconds": 63245, +            "trafficDelayInSeconds": 179, +            "departureTime": "2018-03-23T11:20:18+01:00", +            "arrivalTime": "2018-03-24T04:54:23+01:00" +          } +        } +      }, +      { +        "statusCode": 200, +        "response": { +          "routeSummary": { +            "lengthInMeters": 1113108, +            "travelTimeInSeconds": 68485, +            "trafficDelayInSeconds": 52, +            "departureTime": "2018-03-23T11:20:18+01:00", +            "arrivalTime": "2018-03-24T06:21:43+01:00" +          } +        } +      }, +      { +        "statusCode": 200, +        "response": { +          "routeSummary": { +            "lengthInMeters": 344, +            "travelTimeInSeconds": 109, +            "trafficDelayInSeconds": 0, +            "departureTime": "2018-03-23T11:20:18+01:00", +            "arrivalTime": "2018-03-23T11:22:07+01:00" +          } +        } +      } +    ] +  ], +  "summary": { +    "successfulRoutes": 9, +    "totalRoutes": 9 +  } +} diff --git a/spec/lib/compliance_control_set_cloner_spec.rb b/spec/lib/compliance_control_set_cloner_spec.rb index 7efe27659..0d3561e0e 100644 --- a/spec/lib/compliance_control_set_cloner_spec.rb +++ b/spec/lib/compliance_control_set_cloner_spec.rb @@ -49,7 +49,7 @@ RSpec.describe ComplianceControlSetCloner do        context 'Directed Acyclic Graph is copied correctly' do          let(:source_blox){ -          3.times.map{ |_| create :compliance_control_block, compliance_control_set: source_set } +          3.times.map{ |n| create :compliance_control_block, compliance_control_set: source_set, transport_mode: StifTransportModeEnumerations.transport_modes[n], transport_submode: StifTransportSubmodeEnumerations.transport_submodes[n]   }          }          let(:direct_ccs){            3.times.map{ |n| create :generic_attribute_control_min_max, compliance_control_set: source_set, name: "direct #{n.succ}", code: "direct-#{n.succ}" } diff --git a/spec/lib/compliance_control_set_copier_spec.rb b/spec/lib/compliance_control_set_copier_spec.rb index 0f15d86d0..d1a56cd7f 100644 --- a/spec/lib/compliance_control_set_copier_spec.rb +++ b/spec/lib/compliance_control_set_copier_spec.rb @@ -23,7 +23,7 @@ RSpec.describe ComplianceControlSetCopier do        context 'Directed Acyclic Graph is copied correctly' do          let(:cc_blox){ -          3.times.map{ |_| create :compliance_control_block, compliance_control_set: cc_set } +          3.times.map{ |n| create :compliance_control_block, compliance_control_set: cc_set, transport_mode: StifTransportModeEnumerations.transport_modes[n], transport_submode: StifTransportSubmodeEnumerations.transport_submodes[n] }          }          let!(:direct_ccs){            3.times.map{ |n| create :compliance_control, compliance_control_set: cc_set, name: "direct #{n.succ}", code: "direct-#{n.succ}" } diff --git a/spec/lib/route_way_cost_unit_converter_spec.rb b/spec/lib/route_way_cost_unit_converter_spec.rb index 3c5e51710..aa25d57d2 100644 --- a/spec/lib/route_way_cost_unit_converter_spec.rb +++ b/spec/lib/route_way_cost_unit_converter_spec.rb @@ -35,4 +35,32 @@ RSpec.describe RouteWayCostUnitConverter do        })      end    end + +  describe ".meters_to_kilometers" do +    it "converts meters to integer kilometres" do +      expect( +        RouteWayCostUnitConverter.meters_to_kilometers(6350) +      ).to eq(6) +    end + +    it "snaps values between 0 and 1 to 1" do +      expect( +        RouteWayCostUnitConverter.meters_to_kilometers(50) +      ).to eq(1) +    end +  end + +  describe ".seconds_to_minutes" do +    it "converts seconds to minutes" do +      expect( +        RouteWayCostUnitConverter.seconds_to_minutes(300) +      ).to eq(5) +    end + +    it "snaps values between 0 and 1 to 1" do +      expect( +        RouteWayCostUnitConverter.seconds_to_minutes(3) +      ).to eq(1) +    end +  end  end diff --git a/spec/lib/tom_tom/matrix/request_json_serializer_spec.rb b/spec/lib/tom_tom/matrix/request_json_serializer_spec.rb new file mode 100644 index 000000000..1fafad302 --- /dev/null +++ b/spec/lib/tom_tom/matrix/request_json_serializer_spec.rb @@ -0,0 +1,39 @@ +RSpec.describe TomTom::Matrix::RequestJSONSerializer do +  describe ".dump" do +    it "serializes BigDecimal values to floats" do +      points = [{ +        point: { +          latitude: 52.50867.to_d, +          longitude: 13.42879.to_d +        }, +      }] +      data = { +        origins: points, +        destinations: points +      } + +      expect( +        TomTom::Matrix::RequestJSONSerializer.dump(data) +      ).to eq(<<-JSON.delete(" \n")) +        { +          "origins": [ +            { +              "point": { +                "latitude": 52.50867, +                "longitude": 13.42879 +              } +            } +          ], +          "destinations": [ +            { +              "point": { +                "latitude": 52.50867, +                "longitude": 13.42879 +              } +            } +          ] +        } +      JSON +    end +  end +end diff --git a/spec/lib/tom_tom/matrix_spec.rb b/spec/lib/tom_tom/matrix_spec.rb new file mode 100644 index 000000000..605f1d254 --- /dev/null +++ b/spec/lib/tom_tom/matrix_spec.rb @@ -0,0 +1,228 @@ +RSpec.describe TomTom::Matrix do +  let(:matrix) { TomTom::Matrix.new(nil) } + +  describe "#points_from_way_costs" do +    it "extracts a set of lat/lng coordinates from a list of WayCosts" do +      way_costs = [ +        WayCost.new( +          departure: Geokit::LatLng.new(48.85086, 2.36143), +          arrival: Geokit::LatLng.new(47.91231, 1.87606), +          id: '44-77' +        ), +        WayCost.new( +          departure: Geokit::LatLng.new(47.91231, 1.87606), +          arrival: Geokit::LatLng.new(52.50867, 13.42879), +          id: '77-88' +        ) +      ] + +      expect( +        matrix.points_from_way_costs(way_costs) +      ).to eq([ +        TomTom::Matrix::Point.new( +          Geokit::LatLng.new(48.85086, 2.36143), +          '44' +        ), +        TomTom::Matrix::Point.new( +          Geokit::LatLng.new(47.91231, 1.87606), +          '77' +        ), +        TomTom::Matrix::Point.new( +          Geokit::LatLng.new(52.50867, 13.42879), +          '88' +        ) +      ]) +    end +  end + +  describe "#points_as_params" do +    it "transforms a set of LatLng points into a hash for use by TomTom Matrix" do +      points = [ +        TomTom::Matrix::Point.new( +          Geokit::LatLng.new(48.85086, 2.36143), +          '44' +        ), +        TomTom::Matrix::Point.new( +          Geokit::LatLng.new(47.91231, 1.87606), +          '77' +        ), +        TomTom::Matrix::Point.new( +          Geokit::LatLng.new(52.50867, 13.42879), +          '88' +        ) +      ] + +      expect( +        matrix.points_as_params(points) +      ).to eq([ +        { +          point: { +            latitude: 48.85086, +            longitude: 2.36143 +          }, +        }, +        { +          point: { +            latitude: 47.91231, +            longitude: 1.87606 +          }, +        }, +        { +          point: { +            latitude: 52.50867, +            longitude: 13.42879 +          }, +        } +      ]) +    end +  end + +  describe "#build_request_body" do +    it "serializes BigDecimal coordinates to floats" do +      points = [ +        { +          point: { +            latitude: 48.85086.to_d, +            longitude: 2.36143.to_d +          }, +        }, +        { +          point: { +            latitude: 47.91231.to_d, +            longitude: 1.87606.to_d +          }, +        }, +        { +          point: { +            latitude: 52.50867.to_d, +            longitude: 13.42879.to_d +          }, +        } +      ] + +      expect( +        matrix.build_request_body(points) +      ).to eq(<<-JSON.delete(" \n")) +        { +          "origins": [ +            { +              "point": { +                "latitude": 48.85086, +                "longitude": 2.36143 +              } +            }, +            { +              "point": { +                "latitude": 47.91231, +                "longitude": 1.87606 +              } +            }, +            { +              "point": { +                "latitude": 52.50867, +                "longitude": 13.42879 +              } +            } +          ], +          "destinations": [ +            { +              "point": { +                "latitude": 48.85086, +                "longitude": 2.36143 +              } +            }, +            { +              "point": { +                "latitude": 47.91231, +                "longitude": 1.87606 +              } +            }, +            { +              "point": { +                "latitude": 52.50867, +                "longitude": 13.42879 +              } +            } +          ] +        } +      JSON +    end +  end + +  describe "#extract_costs_to_way_costs!" do +    it "puts distance & time costs in way_costs" do +      way_costs = [ +        WayCost.new( +          departure: Geokit::LatLng.new(48.85086, 2.36143), +          arrival: Geokit::LatLng.new(47.91231, 1.87606), +          id: '55-99' +        ), +        WayCost.new( +          departure: Geokit::LatLng.new(47.91231, 1.87606), +          arrival: Geokit::LatLng.new(52.50867, 13.42879), +          id: '99-22' +        ) +      ] + +      expected_way_costs = [ +        WayCost.new( +          departure: Geokit::LatLng.new(48.85086, 2.36143), +          arrival: Geokit::LatLng.new(47.91231, 1.87606), +          distance: 117947, +          time: 8356, +          id: '55-99' +        ), +        WayCost.new( +          departure: Geokit::LatLng.new(48.85086, 2.36143), +          arrival: Geokit::LatLng.new(52.50867, 13.42879), +          distance: 999088, +          time: 62653, +          id: '55-22' +        ), +        WayCost.new( +          departure: Geokit::LatLng.new(47.91231, 1.87606), +          arrival: Geokit::LatLng.new(48.85086, 2.36143), +          distance: 117231, +          time: 9729, +          id: '99-55' +        ), +        WayCost.new( +          departure: Geokit::LatLng.new(47.91231, 1.87606), +          arrival: Geokit::LatLng.new(52.50867, 13.42879), +          distance: 1114635, +          time: 72079, +          id: '99-22' +        ), +        WayCost.new( +          departure: Geokit::LatLng.new(52.50867, 13.42879), +          arrival: Geokit::LatLng.new(48.85086, 2.36143), +          distance: 997232, +          time: 63245, +          id: '22-55' +        ), +        WayCost.new( +          departure: Geokit::LatLng.new(52.50867, 13.42879), +          arrival: Geokit::LatLng.new(47.91231, 1.87606), +          distance: 1113108, +          time: 68485, +          id: '22-99' +        ), +        WayCost.new( +          departure: Geokit::LatLng.new(52.50867, 13.42879), +          arrival: Geokit::LatLng.new(52.50867, 13.42879), +          distance: 344, +          time: 109, +          id: '22-22' +        ) +      ] + +      matrix_response = JSON.parse(read_fixture('tom_tom_matrix.json')) + +      points = matrix.points_from_way_costs(way_costs) + +      expect( +        matrix.extract_costs_to_way_costs!(way_costs, points, matrix_response) +      ).to match_array(expected_way_costs) +    end +  end +end diff --git a/spec/models/api/v1/api_key_spec.rb b/spec/models/api/v1/api_key_spec.rb index cc483a118..5c5a6bde1 100644 --- a/spec/models/api/v1/api_key_spec.rb +++ b/spec/models/api/v1/api_key_spec.rb @@ -4,7 +4,7 @@ RSpec.describe Api::V1::ApiKey, type: :model do    subject { create(:api_key) }    it { should validate_presence_of :organisation } -  it { is_expected.to be_versioned } +    it 'should have a valid factory' do      expect(build(:api_key)).to be_valid diff --git a/spec/models/calendar_spec.rb b/spec/models/calendar_spec.rb index 09ac0e416..e57eee3b2 100644 --- a/spec/models/calendar_spec.rb +++ b/spec/models/calendar_spec.rb @@ -4,7 +4,7 @@ RSpec.describe Calendar, :type => :model do    it { is_expected.to validate_presence_of(:organisation) }    it { is_expected.to validate_presence_of(:name) } -  it { is_expected.to be_versioned } +      describe '#to_time_table' do      let(:calendar) { create(:calendar, int_day_types: Calendar::MONDAY | Calendar::SUNDAY, date_ranges: [Date.today...(Date.today + 1.month)]) } diff --git a/spec/models/chouette/access_link_spec.rb b/spec/models/chouette/access_link_spec.rb index ced99eb1d..448c22d33 100644 --- a/spec/models/chouette/access_link_spec.rb +++ b/spec/models/chouette/access_link_spec.rb @@ -4,7 +4,7 @@ describe Chouette::AccessLink, :type => :model do    subject { create(:access_link) }    it { is_expected.to validate_uniqueness_of :objectid } -  it { is_expected.to be_versioned } +       describe '#get_objectid' do      subject { super().get_objectid } diff --git a/spec/models/chouette/access_point_spec.rb b/spec/models/chouette/access_point_spec.rb index 2184c6ec2..9c637cf41 100644 --- a/spec/models/chouette/access_point_spec.rb +++ b/spec/models/chouette/access_point_spec.rb @@ -12,7 +12,7 @@ describe Chouette::AccessPoint, :type => :model do    it { is_expected.to validate_presence_of :name }    it { is_expected.to validate_numericality_of :latitude }    it { is_expected.to validate_numericality_of :longitude } -  it { is_expected.to be_versioned } +      describe ".latitude" do      it "should accept -90 value" do diff --git a/spec/models/chouette/company_spec.rb b/spec/models/chouette/company_spec.rb index 34b19eeda..677c60dd9 100644 --- a/spec/models/chouette/company_spec.rb +++ b/spec/models/chouette/company_spec.rb @@ -3,7 +3,7 @@ require 'spec_helper'  describe Chouette::Company, :type => :model do    subject { create(:company) }    it { should validate_presence_of :name } -  it { is_expected.to be_versioned } +      describe "#nullables empty" do      it "should set null empty nullable attributes" do diff --git a/spec/models/chouette/connection_link_spec.rb b/spec/models/chouette/connection_link_spec.rb index 4ab67d007..4486e348c 100644 --- a/spec/models/chouette/connection_link_spec.rb +++ b/spec/models/chouette/connection_link_spec.rb @@ -8,7 +8,7 @@ describe Chouette::ConnectionLink, :type => :model do    subject { create(:connection_link) }    it { is_expected.to validate_uniqueness_of :objectid } -  it { is_expected.to be_versioned } +      describe '#get_objectid' do      subject { super().get_objectid } diff --git a/spec/models/chouette/group_of_line_spec.rb b/spec/models/chouette/group_of_line_spec.rb index d43d75374..8b2df69e5 100644 --- a/spec/models/chouette/group_of_line_spec.rb +++ b/spec/models/chouette/group_of_line_spec.rb @@ -4,7 +4,7 @@ describe Chouette::GroupOfLine, :type => :model do    subject { create(:group_of_line) }    it { should validate_presence_of :name } -  it { is_expected.to be_versioned } +      describe "#stop_areas" do      let!(:line){create(:line, :group_of_lines => [subject])} diff --git a/spec/models/chouette/journey_pattern_spec.rb b/spec/models/chouette/journey_pattern_spec.rb index dac45d6b5..078e3c1f1 100644 --- a/spec/models/chouette/journey_pattern_spec.rb +++ b/spec/models/chouette/journey_pattern_spec.rb @@ -1,7 +1,7 @@  require 'spec_helper'  describe Chouette::JourneyPattern, :type => :model do -  it { is_expected.to be_versioned } +      subject { create(:journey_pattern) }    describe 'checksum' do diff --git a/spec/models/chouette/line_spec.rb b/spec/models/chouette/line_spec.rb index 056d5da9e..cd7cdcb09 100644 --- a/spec/models/chouette/line_spec.rb +++ b/spec/models/chouette/line_spec.rb @@ -7,7 +7,7 @@ describe Chouette::Line, :type => :model do    # it { is_expected.to validate_presence_of :network }    # it { is_expected.to validate_presence_of :company }    it { should validate_presence_of :name } -  it { is_expected.to be_versioned } +      describe '#display_name' do      it 'should display local_id, number, name and company name' do diff --git a/spec/models/chouette/network_spec.rb b/spec/models/chouette/network_spec.rb index 78a4150df..11ad7cacb 100644 --- a/spec/models/chouette/network_spec.rb +++ b/spec/models/chouette/network_spec.rb @@ -3,7 +3,7 @@ require 'spec_helper'  describe Chouette::Network, :type => :model do    subject { create(:network) }    it { should validate_presence_of :name } -  it { is_expected.to be_versioned } +      describe "#stop_areas" do      let!(:line){create(:line, :network => subject)} diff --git a/spec/models/chouette/route/route_base_spec.rb b/spec/models/chouette/route/route_base_spec.rb index d24ad6348..3d4a87791 100644 --- a/spec/models/chouette/route/route_base_spec.rb +++ b/spec/models/chouette/route/route_base_spec.rb @@ -15,8 +15,7 @@ RSpec.describe Chouette::Route, :type => :model do    #it { is_expected.to validate_presence_of :direction_code }    it { is_expected.to validate_inclusion_of(:direction).in_array(%i(straight_forward backward clockwise counter_clockwise north north_west west south_west south south_east east north_east)) }    it { is_expected.to validate_inclusion_of(:wayback).in_array(%i(outbound inbound)) } -  it { is_expected.to be_versioned } - +      context "reordering methods" do      let(:bad_stop_point_ids){subject.stop_points.map { |sp| sp.id + 1}}      let(:ident){subject.stop_points.map(&:id)} diff --git a/spec/models/chouette/route/route_duplication_spec.rb b/spec/models/chouette/route/route_duplication_spec.rb index 8b3a948a2..47233b04e 100644 --- a/spec/models/chouette/route/route_duplication_spec.rb +++ b/spec/models/chouette/route/route_duplication_spec.rb @@ -8,9 +8,6 @@ RSpec.describe Chouette::Route do          route.duplicate          expect( values_for_create(Chouette::Route.last, except: %w{objectid name checksum checksum_source}) ).to eq( values_for_create( route, except: %w{objectid name checksum checksum_source} ) )        end -      it 'and others cannot' do -        expect{ route.duplicate name: 'YAN', line_id: 42  }.to raise_error(ArgumentError) -      end        it 'same associated stop_areeas' do          expect( route.duplicate.stop_areas.pluck(:id) ).to eq(route.stop_areas.pluck(:id))        end diff --git a/spec/models/chouette/routing_constraint_zone_spec.rb b/spec/models/chouette/routing_constraint_zone_spec.rb index bda6bb04a..321b41b7b 100644 --- a/spec/models/chouette/routing_constraint_zone_spec.rb +++ b/spec/models/chouette/routing_constraint_zone_spec.rb @@ -8,7 +8,7 @@ describe Chouette::RoutingConstraintZone, type: :model do    it { is_expected.to validate_presence_of :route_id }    # shoulda matcher to validate length of array ?    xit { is_expected.to validate_length_of(:stop_point_ids).is_at_least(2) } -  it { is_expected.to be_versioned } +      describe 'checksum' do      it_behaves_like 'checksum support' diff --git a/spec/models/chouette/stop_area_spec.rb b/spec/models/chouette/stop_area_spec.rb index e35300caf..185820388 100644 --- a/spec/models/chouette/stop_area_spec.rb +++ b/spec/models/chouette/stop_area_spec.rb @@ -13,7 +13,7 @@ describe Chouette::StopArea, :type => :model do    it { should validate_presence_of :kind }    it { should validate_numericality_of :latitude }    it { should validate_numericality_of :longitude } -  it { is_expected.to be_versioned } +      describe "#area_type" do      it "should validate the value is correct regarding to the kind" do diff --git a/spec/models/chouette/stop_point_spec.rb b/spec/models/chouette/stop_point_spec.rb index 6b9e7727f..ba3799619 100644 --- a/spec/models/chouette/stop_point_spec.rb +++ b/spec/models/chouette/stop_point_spec.rb @@ -4,7 +4,7 @@ describe Chouette::StopPoint, :type => :model do    it { is_expected.to validate_uniqueness_of :objectid }    it { is_expected.to validate_presence_of :stop_area } -  it { is_expected.to be_versioned } +      describe '#objectid' do      subject { super().get_objectid } diff --git a/spec/models/chouette/time_table_spec.rb b/spec/models/chouette/time_table_spec.rb index bb88877b9..a3354facb 100644 --- a/spec/models/chouette/time_table_spec.rb +++ b/spec/models/chouette/time_table_spec.rb @@ -6,7 +6,7 @@ describe Chouette::TimeTable, :type => :model do    it { is_expected.to validate_presence_of :comment }    it { is_expected.to validate_uniqueness_of :objectid } -  it { is_expected.to be_versioned } +        def create_time_table_periode time_table, start_date, end_date        create(:time_table_period, time_table: time_table, :period_start => start_date, :period_end => end_date) diff --git a/spec/models/chouette/timeband_spec.rb b/spec/models/chouette/timeband_spec.rb index b960c203f..fa7c8f06e 100644 --- a/spec/models/chouette/timeband_spec.rb +++ b/spec/models/chouette/timeband_spec.rb @@ -1,7 +1,7 @@  require 'spec_helper'  describe Chouette::Timeband, :type => :model do -  it { is_expected.to be_versioned } +      describe '#create' do      context 'when valid' do diff --git a/spec/models/chouette/vehicle_journey_spec.rb b/spec/models/chouette/vehicle_journey_spec.rb index c69655bd4..6d44eeb2f 100644 --- a/spec/models/chouette/vehicle_journey_spec.rb +++ b/spec/models/chouette/vehicle_journey_spec.rb @@ -3,7 +3,7 @@ require 'spec_helper'  describe Chouette::VehicleJourney, :type => :model do    subject { create(:vehicle_journey) } -  it { is_expected.to be_versioned } +      it { should have_and_belong_to_many(:purchase_windows) }    it "must be valid with an at-stop day offset of 1" do @@ -420,6 +420,7 @@ describe Chouette::VehicleJourney, :type => :model do        state['published_journey_name']       = 'edited_name'        state['published_journey_identifier'] = 'edited_identifier'        state['custom_fields'] = {energy: {value: 99}} +      create :custom_field, field_type: :integer, code: :energy, name: :energy        Chouette::VehicleJourney.state_update(route, collection)        expect(state['errors']).to be_nil @@ -867,7 +868,6 @@ describe Chouette::VehicleJourney, :type => :model do        let!( :footnote_first) {create( :footnote, :code => "1", :label => "dummy 1", :line => route.line)}        let!( :footnote_second) {create( :footnote, :code => "2", :label => "dummy 2", :line => route.line)} -        it "should update vehicle's footnotes" do          expect(Chouette::VehicleJourney.find(subject.id).footnotes).to be_empty          subject.footnote_ids = [ footnote_first.id ] @@ -876,4 +876,54 @@ describe Chouette::VehicleJourney, :type => :model do        end      end    end + +  describe "#fill_passing_time_at_borders" do +    before do +      start = create :stop_area +      border = create :stop_area, kind: :non_commercial, area_type: :border +      border_2 = create :stop_area, kind: :non_commercial, area_type: :border +      middle = create :stop_area +      border_3 = create :stop_area, kind: :non_commercial, area_type: :border +      border_4 = create :stop_area, kind: :non_commercial, area_type: :border +      _end = create :stop_area +      journey_pattern = create :journey_pattern +      journey_pattern.stop_points.destroy_all +      journey_pattern.stop_points << start_point = create(:stop_point, stop_area: start, position: 0) +      journey_pattern.stop_points << border_point = create(:stop_point, stop_area: border, position: 1) +      journey_pattern.stop_points << border_point_2 = create(:stop_point, stop_area: border_2, position: 2) +      journey_pattern.stop_points << middle_point = create(:stop_point, stop_area: middle, position: 3) +      journey_pattern.stop_points << border_point_3 = create(:stop_point, stop_area: border_3, position: 4) +      journey_pattern.stop_points << border_point_4 = create(:stop_point, stop_area: border_4, position: 5) +      journey_pattern.stop_points << end_point = create(:stop_point, stop_area: _end, position: 6) +      journey_pattern.update_attribute :costs, { +        "#{start_point.stop_area_id}-#{border_point.stop_area_id}" => {distance: 50}, +        "#{border_point.stop_area_id}-#{border_point_2.stop_area_id}" => {distance: 0}, +        "#{border_point_2.stop_area_id}-#{middle_point.stop_area_id}" => {distance: 100}, +        "#{middle_point.stop_area_id}-#{border_point_3.stop_area_id}" => {distance: 100}, +        "#{border_point_3.stop_area_id}-#{border_point_4.stop_area_id}" => {distance: 0}, +        "#{border_point_4.stop_area_id}-#{end_point.stop_area_id}" => {distance: 100} +      } +      @journey = create :vehicle_journey, journey_pattern: journey_pattern +      @journey.vehicle_journey_at_stops.destroy_all +      @start = create :vehicle_journey_at_stop, stop_point: start_point, vehicle_journey: @journey +      @target = create :vehicle_journey_at_stop, stop_point: border_point, vehicle_journey: @journey, arrival_time: nil, departure_time: nil +      @target_2 = create :vehicle_journey_at_stop, stop_point: border_point_2, vehicle_journey: @journey, arrival_time: nil, departure_time: nil +      @middle = create :vehicle_journey_at_stop, stop_point: middle_point, vehicle_journey: @journey, arrival_time: @start.arrival_time + 4.hours, departure_time: @start.departure_time + 4.hours +      @target_3 = create :vehicle_journey_at_stop, stop_point: border_point_3, vehicle_journey: @journey, arrival_time: nil, departure_time: nil +      @target_4 = create :vehicle_journey_at_stop, stop_point: border_point_4, vehicle_journey: @journey, arrival_time: nil, departure_time: nil +      @end = create :vehicle_journey_at_stop, stop_point: end_point, vehicle_journey: @journey, arrival_time: @middle.arrival_time + 4.hours, departure_time: @middle.departure_time + 4.hours +    end + +    it "should compute passing time" do +      @journey.reload.fill_passing_time_at_borders +      expect(@target.reload.arrival_time.to_i).to eq (@start.reload.departure_time + 1.0/3 * (@middle.reload.arrival_time - @start.departure_time)).to_i +      expect(@target_2.reload.arrival_time).to eq @target.arrival_time +      expect(@target.departure_time).to eq @target.arrival_time +      expect(@target_2.departure_time).to eq @target.arrival_time +      expect(@target_3.reload.arrival_time.to_i).to eq (@middle.reload.departure_time + 0.5 * (@end.reload.arrival_time - @middle.departure_time)).to_i +      expect(@target_4.reload.arrival_time).to eq @target_3.arrival_time +      expect(@target_3.departure_time).to eq @target_3.arrival_time +      expect(@target_4.departure_time).to eq @target_3.arrival_time +    end +  end  end diff --git a/spec/models/compliance_check_set_spec.rb b/spec/models/compliance_check_set_spec.rb index 61421287a..b6f854829 100644 --- a/spec/models/compliance_check_set_spec.rb +++ b/spec/models/compliance_check_set_spec.rb @@ -12,7 +12,7 @@ RSpec.describe ComplianceCheckSet, type: :model do    it { should have_many :compliance_checks }    it { should have_many :compliance_check_blocks } -  it { is_expected.to be_versioned } +      describe "#update_status" do      it "updates :status to successful when all resources are OK" do diff --git a/spec/models/compliance_control_block_spec.rb b/spec/models/compliance_control_block_spec.rb index 4abe0ed9c..089d78434 100644 --- a/spec/models/compliance_control_block_spec.rb +++ b/spec/models/compliance_control_block_spec.rb @@ -17,4 +17,16 @@ RSpec.describe ComplianceControlBlock, type: :model do    it { should_not allow_values( *%w{ demandResponseBus nightus irportLinkBus highrequencyBus expressBUs                                       Shuttle suburban regioalRail interregion4lRail })          .for(:transport_submode) } + +  context "transport mode & submode uniqueness" do +    let(:cc_block) {create :compliance_control_block, transport_mode: 'bus', transport_submode: 'nightBus'} +    let(:cc_set1) { cc_block.compliance_control_set } +    let(:cc_set2) { create :compliance_control_set }      + +  it "sould be unique in a compliance control set" do +    expect( ComplianceControlBlock.new(transport_mode: 'bus', transport_submode: 'nightBus', compliance_control_set: cc_set1) ).not_to be_valid +    expect( ComplianceControlBlock.new(transport_mode: 'bus', transport_submode: 'nightBus', compliance_control_set: cc_set2) ).to be_valid +  end + +end  end diff --git a/spec/models/compliance_control_set_spec.rb b/spec/models/compliance_control_set_spec.rb index c157dcaf3..a66e7f030 100644 --- a/spec/models/compliance_control_set_spec.rb +++ b/spec/models/compliance_control_set_spec.rb @@ -10,5 +10,5 @@ RSpec.describe ComplianceControlSet, type: :model do    it { should have_many(:compliance_control_blocks).dependent(:destroy) }    it { should validate_presence_of :name } -  it { is_expected.to be_versioned } +    end diff --git a/spec/models/custom_field_spec.rb b/spec/models/custom_field_spec.rb index b92bcfbdb..1dfe6d33c 100644 --- a/spec/models/custom_field_spec.rb +++ b/spec/models/custom_field_spec.rb @@ -1,6 +1,7 @@  require 'rails_helper'  RSpec.describe CustomField, type: :model do +    let( :vj ){ create :vehicle_journey, custom_field_values: {energy: 99} }    context "validates" do @@ -35,11 +36,43 @@ RSpec.describe CustomField, type: :model do    end    context "custom field_values for a resource" do +    before do +      create :custom_field, field_type: :integer, code: :energy, name: :energy +    end +      it { expect(vj.custom_field_value("energy")).to eq(99) }    end +  context "with a 'list' field_type" do +    let!(:field){ [create(:custom_field, code: :energy, field_type: 'list', options: {list_values: %w(foo bar baz)})] } +    let!( :vj ){ create :vehicle_journey, custom_field_values: {energy: "1"} } +    it "should cast the value" do +      p vj.custom_fields +      expect(vj.custom_fields[:energy].value).to eq 1 +      expect(vj.custom_fields[:energy].display_value).to eq "bar" +    end + +    it "should validate the value" do +      { +        "1" => true, +        1 => true, +        "azerty" => false, +        "10" => false, +        10 => false +      }.each do |val, valid| +        vj = build :vehicle_journey, custom_field_values: {energy: val} +        if valid +          expect(vj.validate).to be_truthy +        else +          expect(vj.validate).to be_falsy +          expect(vj.errors.messages[:"custom_fields.energy"]).to be_present +        end +      end +    end +  end +    context "with an 'integer' field_type" do -    let!(:field){ [create(:custom_field, code: :energy, options: {field_type: 'integer'})] } +    let!(:field){ [create(:custom_field, code: :energy, field_type: 'integer')] }      let!( :vj ){ create :vehicle_journey, custom_field_values: {energy: "99"} }      it "should cast the value" do        expect(vj.custom_fields[:energy].value).to eq 99 @@ -47,6 +80,7 @@ RSpec.describe CustomField, type: :model do      it "should validate the value" do        { +        99 => true,          "99" => true,          "azerty" => false,          "91a" => false, @@ -64,10 +98,39 @@ RSpec.describe CustomField, type: :model do    end    context "with a 'string' field_type" do -    let!(:field){ [create(:custom_field, code: :energy, options: {field_type: 'string'})] } +    let!(:field){ [create(:custom_field, code: :energy, field_type: 'string')] }      let!( :vj ){ create :vehicle_journey, custom_field_values: {energy: 99} }      it "should cast the value" do        expect(vj.custom_fields[:energy].value).to eq '99'      end    end + +  context "with a 'attachment' field_type" do +    let!(:field){ [create(:custom_field, code: :energy, field_type: 'attachment')] } +    let( :vj ){ create :vehicle_journey, custom_field_values: {energy: File.open(Rails.root.join('spec', 'fixtures', 'users.json'))} } +    it "should cast the value" do +      expect(vj.custom_fields[:energy].value.class).to be CustomFieldAttachmentUploader +      path = vj.custom_fields[:energy].value.path +      expect(File.exists?(path)).to be_truthy +      expect(vj).to receive(:remove_custom_field_energy!).and_call_original +      vj.destroy +      vj.run_callbacks(:commit) +      expect(File.exists?(path)).to be_falsy +    end + +    it "should display a link" do +      val = vj.custom_fields[:energy].value +      out = vj.custom_fields[:energy].display_value +      expect(out).to match(val.url) +      expect(out).to match(/\<a.*\>/) +    end + +    context "with a whitelist" do +      let!(:field){ [create(:custom_field, code: :energy, field_type: 'attachment', options: {extension_whitelist: %w(zip)})] } +      it "should validate extension" do +        expect(build(:vehicle_journey, custom_field_values: {energy: File.open(Rails.root.join('spec', 'fixtures', 'users.json'))})).to_not be_valid +        expect(build(:vehicle_journey, custom_field_values: {energy: File.open(Rails.root.join('spec', 'fixtures', 'nozip.zip'))})).to be_valid +      end +    end +  end  end diff --git a/spec/models/referential_metadata_spec.rb b/spec/models/referential_metadata_spec.rb index 291ed974a..88a12b2bb 100644 --- a/spec/models/referential_metadata_spec.rb +++ b/spec/models/referential_metadata_spec.rb @@ -12,14 +12,19 @@ RSpec.describe ReferentialMetadata, :type => :model do    describe ".new_from" do      let(:referential_metadata) { create :referential_metadata, referential_source: create(:referential) } -    let(:new_referential_metadata) { ReferentialMetadata.new_from(referential_metadata, []) } +    let(:new_referential_metadata) { ReferentialMetadata.new_from(referential_metadata, nil) } +    before do +      referential_metadata.line_ids.each do |id| +        Chouette::Line.find(id).update_attribute :line_referential_id, referential_metadata.referential.line_referential_id +      end +    end      it "should not have an associated referential" do        expect(new_referential_metadata).to be_a_new(ReferentialMetadata)      end -    xit "should have the same lines" do -      expect(new_referential_metadata.lines).to eq(referential_metadata.lines) +    it "should have the same lines" do +      expect(new_referential_metadata.line_ids.sort).to eq(referential_metadata.line_ids.sort)      end      it "should have the same periods" do @@ -34,6 +39,14 @@ RSpec.describe ReferentialMetadata, :type => :model do        expect(new_referential_metadata.referential_source).to eq(referential_metadata.referential)      end +    context "with a functional scope" do +      let(:organisation){ create :organisation, sso_attributes: {"functional_scope" => [referential_metadata.referential.lines.first.objectid]} } +      let(:new_referential_metadata) { ReferentialMetadata.new_from(referential_metadata, organisation) } + +      it "should scope the lines" do +        expect(new_referential_metadata.line_ids).to eq [referential_metadata.referential.lines.first.id] +      end +    end    end    describe "Period" do diff --git a/spec/models/referential_spec.rb b/spec/models/referential_spec.rb index 1d9b3d78a..ca2caf57f 100644 --- a/spec/models/referential_spec.rb +++ b/spec/models/referential_spec.rb @@ -55,7 +55,7 @@ describe Referential, :type => :model do    context "Cloning referential" do      let(:clone) do -      Referential.new_from(ref, []) +      Referential.new_from(ref, nil)      end      let!(:workbench){ create :workbench } diff --git a/spec/models/route_spec.rb b/spec/models/route_spec.rb new file mode 100644 index 000000000..b407cd866 --- /dev/null +++ b/spec/models/route_spec.rb @@ -0,0 +1,68 @@ +require 'spec_helper' + +RSpec.describe Chouette::Route, :type => :model do +  subject(:route){ create :route } +  context "metadatas" do +    it "should be empty at first" do +      expect(Chouette::Route.has_metadata?).to be_truthy +      expect(route.has_metadata?).to be_truthy +      expect(route.metadata.creator_username).to be_nil +      expect(route.metadata.modifier_username).to be_nil +    end + +    context "once set" do +      it "should set the correct values" do +        Timecop.freeze(Time.now) do +          route.metadata.creator_username = "john.doe" +          route.save! +          id = route.id +          route = Chouette::Route.find id +          expect(route.metadata.creator_username).to eq "john.doe" +          expect(route.metadata.creator_username_updated_at.strftime('%Y-%m-%d %H:%M:%S.%3N')).to eq Time.now.strftime('%Y-%m-%d %H:%M:%S.%3N') +        end +      end +    end + +    describe "#merge_metadata_from" do +      let(:source){ create :route } +      let(:metadata){ target.merge_metadata_from(source).metadata } +      let(:target){ create :route } +      before do +        target.metadata.creator_username = "john" +        target.metadata.modifier_username = "john" +      end +      context "when the source has no metadata" do +        it "should do nothing" do +          expect(metadata.creator_username).to eq "john" +          expect(metadata.modifier_username).to eq "john" +        end +      end + +      context "when the source has older metadata" do +        before do +          source.metadata.creator_username = "jane" +          source.metadata.modifier_username = "jane" +          source.metadata.creator_username_updated_at = 1.month.ago +          source.metadata.modifier_username_updated_at = 1.month.ago +        end +        it "should do nothing" do +          expect(metadata.creator_username).to eq "john" +          expect(metadata.modifier_username).to eq "john" +        end +      end + +      context "when the source has new metadata" do +        before do +          source.metadata.creator_username = "jane" +          source.metadata.modifier_username = "jane" +          target.metadata.creator_username_updated_at = 1.month.ago +          target.metadata.modifier_username_updated_at = 1.month.ago +        end +        it "should update metadata" do +          expect(metadata.creator_username).to eq "jane" +          expect(metadata.modifier_username).to eq "jane" +        end +      end +    end +  end +end diff --git a/spec/services/route_way_cost_calculator_spec.rb b/spec/services/route_way_cost_calculator_spec.rb index d5358fcf6..79b81e34d 100644 --- a/spec/services/route_way_cost_calculator_spec.rb +++ b/spec/services/route_way_cost_calculator_spec.rb @@ -7,18 +7,20 @@ RSpec.describe RouteWayCostCalculator do        # things in the request or response. This is just to fake the request so        # we don't actually call their API in tests. The test doesn't test        # anything given in the response. -      stub_request(:post, "https://api.tomtom.com/routing/1/batch/json?key") +      stub_request( +        :post, +        "https://api.tomtom.com/routing/1/matrix/json?key&routeType=shortest&travelMode=bus" +      )          .with(            headers: {              'Accept'=>'*/*',              'Accept-Encoding'=>'gzip;q=1.0,deflate;q=0.6,identity;q=0.3',              'Content-Type'=>'application/json',              'User-Agent'=>'Faraday v0.9.2' -          } -        ) +          })          .to_return(            status: 200, -          body: "{\"formatVersion\":\"0.0.1\",\"batchItems\":[{\"statusCode\":200,\"response\":{\"routes\":[{\"summary\":{\"lengthInMeters\":117947,\"travelTimeInSeconds\":7969,\"trafficDelayInSeconds\":0,\"departureTime\":\"2018-03-12T12:32:26+01:00\",\"arrivalTime\":\"2018-03-12T14:45:14+01:00\"}}]}}]}", +          body: "{\"formatVersion\":\"0.0.1\",\"matrix\":[[{\"statusCode\":200,\"response\":{\"routeSummary\":{\"lengthInMeters\":0,\"travelTimeInSeconds\":0,\"trafficDelayInSeconds\":0,\"departureTime\":\"2018-03-23T11:20:17+01:00\",\"arrivalTime\":\"2018-03-23T11:20:17+01:00\"}}}]]}",            headers: {}          ) diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index cde252236..947efd602 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -34,7 +34,6 @@ require 'webmock/rspec'  require 'simplecov'  require 'sidekiq/testing'  Sidekiq::Testing.fake! -require 'paper_trail/frameworks/rspec'  # Requires supporting ruby files with custom matchers and macros, etc, in  # spec/support/ and its subdirectories. Files matching `spec/**/*_spec.rb` are diff --git a/spec/support/referential.rb b/spec/support/referential.rb index 9acdce73a..b50844ae4 100644 --- a/spec/support/referential.rb +++ b/spec/support/referential.rb @@ -8,6 +8,10 @@ module ReferentialHelper      Organisation.find_by!(code: "first")    end +  def first_workgroup +    Workgroup.find_by_name('IDFM') +  end +    def self.included(base)      base.class_eval do        extend ClassMethods @@ -53,10 +57,18 @@ RSpec.configure do |config|        referential.add_member organisation, owner: true      end +    workgroup = FactoryGirl.create( +      :workgroup, +      name: "IDFM", +      line_referential: line_referential, +      stop_area_referential: stop_area_referential +    ) +      workbench = FactoryGirl.create(        :workbench,        name: "Gestion de l'offre",        organisation: organisation, +      workgroup: workgroup,        line_referential: line_referential,        stop_area_referential: stop_area_referential      ) diff --git a/spec/views/companies/edit.html.erb_spec.rb b/spec/views/companies/edit.html.erb_spec.rb index 8aaf705ab..c72d84c0b 100644 --- a/spec/views/companies/edit.html.erb_spec.rb +++ b/spec/views/companies/edit.html.erb_spec.rb @@ -5,7 +5,10 @@ describe "/companies/edit", :type => :view do    let!(:company) { assign(:company, create(:company)) }    let!(:companies) { Array.new(2) { create(:company) } }    let!(:line_referential) { assign :line_referential, company.line_referential } - +  before do +    allow(view).to receive(:resource){ company } +    allow(view).to receive(:current_referential){ first_referential } +  end    describe "form" do      it "should render input for name" do        render diff --git a/spec/views/companies/new.html.erb_spec.rb b/spec/views/companies/new.html.erb_spec.rb index ebb8c03c5..6c2163677 100644 --- a/spec/views/companies/new.html.erb_spec.rb +++ b/spec/views/companies/new.html.erb_spec.rb @@ -4,7 +4,10 @@ describe "/companies/new", :type => :view do    let!(:company) { assign(:company, build(:company)) }    let!(:line_referential) { assign :line_referential, company.line_referential } - +  before do +    allow(view).to receive(:resource){company} +    allow(view).to receive(:current_referential){ first_referential } +  end    describe "form" do      it "should render input for name" do diff --git a/spec/views/stop_areas/edit.html.erb_spec.rb b/spec/views/stop_areas/edit.html.erb_spec.rb index bfbb0bb55..1bdb42817 100644 --- a/spec/views/stop_areas/edit.html.erb_spec.rb +++ b/spec/views/stop_areas/edit.html.erb_spec.rb @@ -8,6 +8,7 @@ describe "/stop_areas/edit", :type => :view do    before do      allow(view).to receive(:has_feature?) +    allow(view).to receive(:resource){ stop_area }    end    describe "form" do diff --git a/spec/views/stop_areas/new.html.erb_spec.rb b/spec/views/stop_areas/new.html.erb_spec.rb index 23f7387fa..eced129e4 100644 --- a/spec/views/stop_areas/new.html.erb_spec.rb +++ b/spec/views/stop_areas/new.html.erb_spec.rb @@ -7,6 +7,7 @@ describe "/stop_areas/new", :type => :view do    before do      allow(view).to receive(:has_feature?) +    allow(view).to receive(:resource){ stop_area }    end    describe "form" do | 
