diff options
140 files changed, 1419 insertions, 298 deletions
| diff --git a/app/assets/stylesheets/components/_main_nav.sass b/app/assets/stylesheets/components/_main_nav.sass index 8e164fa01..ef3f14762 100644 --- a/app/assets/stylesheets/components/_main_nav.sass +++ b/app/assets/stylesheets/components/_main_nav.sass @@ -346,6 +346,7 @@ $menuW: 300px            flex: 1 1            height: $menuH            position: relative +          margin-right: 2em            h1              position: absolute @@ -355,8 +356,8 @@ $menuW: 300px              line-height: 1.1              white-space: nowrap              max-height: 1.1em -            margin: 0 -1.4em 0 0 -            padding: 0 1.4em 5px 0 +            margin: 0 +            padding: 0 0 5px 0              overflow: hidden              text-overflow: ellipsis diff --git a/app/assets/stylesheets/components/_pagination.sass b/app/assets/stylesheets/components/_pagination.sass index 88ba61c3c..b811a559c 100644 --- a/app/assets/stylesheets/components/_pagination.sass +++ b/app/assets/stylesheets/components/_pagination.sass @@ -7,6 +7,9 @@    border-radius: 0    line-height: 34px +  &:first-letter +    text-transform: capitalize +    .page_links      display: inline-block      vertical-align: top diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb index c4961123d..9a83394e2 100644 --- a/app/controllers/application_controller.rb +++ b/app/controllers/application_controller.rb @@ -36,12 +36,6 @@ class ApplicationController < ActionController::Base    end    helper_method :current_organisation -  def current_functional_scope -    functional_scope = current_organisation.sso_attributes.try(:[], "functional_scope") if current_organisation -    JSON.parse(functional_scope) if functional_scope -  end -  helper_method :current_functional_scope -    def collection_name      self.class.name.split("::").last.gsub('Controller', '').underscore    end diff --git a/app/controllers/calendars_controller.rb b/app/controllers/calendars_controller.rb index 3d88e4910..adb3b4764 100644 --- a/app/controllers/calendars_controller.rb +++ b/app/controllers/calendars_controller.rb @@ -104,6 +104,10 @@ class CalendarsController < ChouetteController      end    end +   def begin_of_association_chain +    current_organisation +  end +    def ransack_contains_date      date =[]      if params[:q] && !params[:q]['contains_date(1i)'].empty? diff --git a/app/controllers/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/compliance_controls_controller.rb b/app/controllers/compliance_controls_controller.rb index 73dc18f59..7df922d01 100644 --- a/app/controllers/compliance_controls_controller.rb +++ b/app/controllers/compliance_controls_controller.rb @@ -5,7 +5,7 @@ class ComplianceControlsController < ChouetteController    actions :all, :except => [:index]    def select_type -    @sti_subclasses = ComplianceControl.subclasses +    @sti_subclasses = ComplianceControl.subclasses.sort_by {|compliance_control| compliance_control.default_code}    end    def show diff --git a/app/controllers/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/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/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/helpers/application_helper.rb b/app/helpers/application_helper.rb index 479b661c8..a0c6796ea 100644 --- a/app/helpers/application_helper.rb +++ b/app/helpers/application_helper.rb @@ -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/routes_helper.rb b/app/helpers/routes_helper.rb index 61714a066..84b4015a2 100644 --- a/app/helpers/routes_helper.rb +++ b/app/helpers/routes_helper.rb @@ -24,7 +24,7 @@ module RoutesHelper        stop_area_attributes = stop_point.stop_area.attributes.slice("name","city_name", "zip_code", "registration_number", "longitude", "latitude", "area_type", "comment")        stop_area_attributes["short_name"] = truncate(stop_area_attributes["name"], :length => 30) || ""        stop_point_attributes = stop_point.attributes.slice("for_boarding","for_alighting") -      stop_area_attributes.merge(stop_point_attributes).merge(stoppoint_id: stop_point.id, stoparea_id: stop_point.stop_area.id).merge(user_objectid: stop_point.stop_area.user_objectid) +      stop_area_attributes.merge(stop_point_attributes).merge(stoppoint_id: stop_point.id, stoparea_id: stop_point.stop_area.id, stoparea_kind: stop_point.stop_area.kind).merge(user_objectid: stop_point.stop_area.user_objectid)      end      data = data.to_json if serialize      data diff --git a/app/helpers/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/components/ConfirmModal.js b/app/javascript/journey_patterns/components/ConfirmModal.js index ccd0a9384..fdf32649f 100644 --- a/app/javascript/journey_patterns/components/ConfirmModal.js +++ b/app/javascript/journey_patterns/components/ConfirmModal.js @@ -9,11 +9,11 @@ export default function ConfirmModal({dispatch, modal, onModalAccept, onModalCan          <div className='modal-dialog'>            <div className='modal-content'>              <div className='modal-header'> -              <h4 className='modal-title'>Confirmation</h4> +              <h4 className='modal-title'>{I18n.t('journey_patterns.show.confirmation')}</h4>              </div>              <div className='modal-body'>                <div className='mt-md mb-md'> -                <p>Vous vous apprêtez à changer de page. Voulez-vous valider vos modifications avant cela ?</p> +                <p>{I18n.t('journey_patterns.show.confirm_page_change')}</p>                </div>              </div>              <div className='modal-footer'> @@ -23,7 +23,7 @@ export default function ConfirmModal({dispatch, modal, onModalAccept, onModalCan                  type='button'                  onClick={() => { onModalCancel(modal.confirmModal.callback) }}                > -                Ne pas valider +                {I18n.t('cancel')}              </button>                <button                  className='btn btn-primary' @@ -31,7 +31,7 @@ export default function ConfirmModal({dispatch, modal, onModalAccept, onModalCan                  type='button'                  onClick={() => { onModalAccept(modal.confirmModal.callback, journeyPatterns) }}                > -                Valider +                {I18n.t('actions.submit')}              </button>              </div>            </div> diff --git a/app/javascript/journey_patterns/components/CreateModal.js b/app/javascript/journey_patterns/components/CreateModal.js index a6c1b608a..36b5740dc 100644 --- a/app/javascript/journey_patterns/components/CreateModal.js +++ b/app/javascript/journey_patterns/components/CreateModal.js @@ -38,14 +38,14 @@ export default class CreateModal extends Component {                    <div className='modal-dialog'>                      <div className='modal-content'>                        <div className='modal-header'> -                        <h4 className='modal-title'>Ajouter une mission</h4> +                        <h4 className='modal-title'>{I18n.t('journey_patterns.actions.new')}</h4>                        </div>                        {(this.props.modal.type == 'create') && (                          <form>                            <div className='modal-body'>                              <div className='form-group'> -                              <label className='control-label is-required'>Nom</label> +                              <label className='control-label is-required'>{I18n.attribute_name('journey_pattern', 'name')}</label>                                <input                                  type='text'                                  ref='name' @@ -57,7 +57,7 @@ export default class CreateModal extends Component {                              <div className='row'>                                <div className='col-lg-6 col-md-6 col-sm-6 col-xs-6'>                                  <div className='form-group'> -                                  <label className='control-label is-required'>Nom public</label> +                                  <label className='control-label is-required'>{I18n.attribute_name('journey_pattern', 'published_name')}c</label>                                    <input                                      type='text'                                      ref='published_name' @@ -69,7 +69,7 @@ export default class CreateModal extends Component {                                </div>                                <div className='col-lg-6 col-md-6 col-sm-6 col-xs-6'>                                  <div className='form-group'> -                                  <label className='control-label'>Code mission</label> +                                  <label className='control-label'>{I18n.attribute_name('journey_pattern', 'registration_number')}</label>                                    <input                                      type='text'                                      ref='registration_number' @@ -87,14 +87,14 @@ export default class CreateModal extends Component {                                type='button'                                onClick={this.props.onModalClose}                                > -                              Annuler +                              {I18n.t('cancel')}                              </button>                              <button                                className='btn btn-primary'                                type='button'                                onClick={this.handleSubmit.bind(this)}                                > -                              Valider +                              {I18n.t('actions.submit')}                              </button>                            </div>                          </form> diff --git a/app/javascript/journey_patterns/components/EditModal.js b/app/javascript/journey_patterns/components/EditModal.js index c960cb41c..1960849fb 100644 --- a/app/javascript/journey_patterns/components/EditModal.js +++ b/app/javascript/journey_patterns/components/EditModal.js @@ -18,12 +18,12 @@ export default class EditModal extends Component {      if (this.props.editMode) {        return (          <h4 className='modal-title'> -          Editer la mission +          {I18n.t('journey_patterns.actions.edit')}            {this.props.modal.type == 'edit' && <em> "{this.props.modal.modalProps.journeyPattern.name}"</em>}          </h4>        )      } else { -      return <h4 className='modal-title'> Informations </h4> +      return <h4 className='modal-title'> {I18n.t('journey_patterns.show.informations')} </h4>      }    } @@ -41,7 +41,7 @@ export default class EditModal extends Component {                  <form>                    <div className='modal-body'>                      <div className='form-group'> -                      <label className='control-label is-required'>Nom</label> +                      <label className='control-label is-required'>{I18n.attribute_name('journey_pattern', 'name')}</label>                        <input                          type='text'                          ref='name' @@ -57,7 +57,7 @@ export default class EditModal extends Component {                      <div className='row'>                        <div className='col-lg-6 col-md-6 col-sm-6 col-xs-6'>                          <div className='form-group'> -                          <label className='control-label is-required'>Nom public</label> +                          <label className='control-label is-required'>{I18n.attribute_name('journey_pattern', 'published_name')}</label>                            <input                              type='text'                              ref='published_name' @@ -72,7 +72,7 @@ export default class EditModal extends Component {                        </div>                        <div className='col-lg-6 col-md-6 col-sm-6 col-xs-6'>                          <div className='form-group'> -                          <label className='control-label'>Code mission</label> +                          <label className='control-label'>{I18n.attribute_name('journey_pattern', 'registration_number')}</label>                            <input                              type='text'                              ref='registration_number' @@ -86,7 +86,7 @@ export default class EditModal extends Component {                        </div>                      </div>                      <div> -                      <label className='control-label'>Signature métier</label> +                      <label className='control-label'>{I18n.attribute_name('journey_pattern', 'checksum')}</label>                          <input                          type='text'                          ref='checksum' @@ -105,14 +105,14 @@ export default class EditModal extends Component {                          type='button'                          onClick={this.props.onModalClose}                        > -                        Annuler +                        {I18n.t('cancel')}                        </button>                        <button                          className='btn btn-primary'                          type='button'                          onClick={this.handleSubmit.bind(this)}                        > -                        Valider +                        {I18n.t('actions.submit')}                        </button>                      </div>                    } diff --git a/app/javascript/journey_patterns/components/JourneyPattern.js b/app/javascript/journey_patterns/components/JourneyPattern.js index 00b5497f7..d381b0d50 100644 --- a/app/javascript/journey_patterns/components/JourneyPattern.js +++ b/app/javascript/journey_patterns/components/JourneyPattern.js @@ -23,7 +23,7 @@ export default class JourneyPattern extends Component{      let vjURL = routeURL + '/vehicle_journeys?jp=' + jpOid      return ( -      <a href={vjURL}>Horaires des courses</a> +      <a href={vjURL}>{I18n.t('journey_patterns.journey_pattern.vehicle_journey_at_stops')}</a>      )    } @@ -139,7 +139,7 @@ export default class JourneyPattern extends Component{          <div className='th'>            <div className='strong mb-xs'>{this.props.value.object_id ? this.props.value.short_id : '-'}</div>            <div>{this.props.value.registration_number}</div> -          <div>{actions.getChecked(this.props.value.stop_points).length} arrêt(s)</div> +          <div>{I18n.t('journey_patterns.show.stop_points_count', {count: actions.getChecked(this.props.value.stop_points).length})}</div>            {this.hasFeature('costs_in_journey_patterns') &&              <div className="small row totals">                <span className="col-md-6"><i className="fa fa-arrows-h"></i>{totalDistance}</span> @@ -167,7 +167,7 @@ export default class JourneyPattern extends Component{                    data-toggle='modal'                    data-target='#JourneyPatternModal'                    > -                  {this.props.editMode ? 'Editer' : 'Consulter'} +                  {this.props.editMode ? I18n.t('actions.edit') : I18n.t('actions.show')}                  </button>                </li>                <li className={this.props.value.object_id ? '' : 'disabled'}> @@ -183,7 +183,7 @@ export default class JourneyPattern extends Component{                      this.props.onDeleteJourneyPattern(this.props.index)}                    }                    > -                    <span className='fa fa-trash'></span>Supprimer +                    <span className='fa fa-trash'></span>{I18n.t('actions.destroy')}                    </button>                  </li>                </ul> diff --git a/app/javascript/journey_patterns/components/JourneyPatterns.js b/app/javascript/journey_patterns/components/JourneyPatterns.js index 930acb390..91c783189 100644 --- a/app/javascript/journey_patterns/components/JourneyPatterns.js +++ b/app/javascript/journey_patterns/components/JourneyPatterns.js @@ -84,14 +84,14 @@ export default class JourneyPatterns extends Component {            <div className='col-lg-12'>              {(this.props.status.fetchSuccess == false) && (                <div className="alert alert-danger mt-sm"> -                <strong>Erreur : </strong> -                la récupération des missions a rencontré un problème. Rechargez la page pour tenter de corriger le problème +                <strong>{I18n.t('error')} : </strong> +                {I18n.t('journeys_patterns.journey_pattern.fetching_error')}                </div>              )}              { _.some(this.props.journeyPatterns, 'errors') && (                <div className="alert alert-danger mt-sm"> -                <strong>Erreur : </strong> +                <strong> {I18n.t('error')} : </strong>                  {this.props.journeyPatterns.map((jp, index) =>                    jp.errors && jp.errors.map((err, i) => {                      return ( @@ -107,9 +107,9 @@ export default class JourneyPatterns extends Component {              <div className={'table table-2entries mt-sm mb-sm' + ((this.props.journeyPatterns.length > 0) ? '' : ' no_result')}>                <div className='t2e-head w20'>                  <div className='th'> -                  <div className='strong mb-xs'>ID Mission</div> -                  <div>Code mission</div> -                  <div>Nb arrêts</div> +                  <div className='strong mb-xs'>{I18n.t('objectid')}</div> +                  <div>{I18n.attribute_name('journey_pattern', 'registration_number')}</div> +                  <div>{I18n.attribute_name('journey_pattern', 'stop_points')}</div>                    { this.hasFeature('costs_in_journey_patterns') &&                       <div>                         <div>{I18n.attribute_name('journey_pattern', 'full_journey_time')}</div> diff --git a/app/javascript/journey_patterns/components/Navigate.js b/app/javascript/journey_patterns/components/Navigate.js index 78f324a7d..9e454da5e 100644 --- a/app/javascript/journey_patterns/components/Navigate.js +++ b/app/javascript/journey_patterns/components/Navigate.js @@ -1,5 +1,6 @@  import React, { Component } from 'react'  import PropTypes from 'prop-types' +import capitalize from 'lodash/capitalize'  import actions from '../actions'  export default function Navigate({ dispatch, journeyPatterns, pagination, status }) { @@ -17,7 +18,7 @@ export default function Navigate({ dispatch, journeyPatterns, pagination, status        <div className='row'>          <div className='col-lg-12 text-right'>            <div className='pagination'> -            Liste des missions {firstItemOnPage} à {(lastItemOnPage < pagination.totalCount) ? lastItemOnPage : pagination.totalCount} sur {pagination.totalCount} +            {I18n.t('will_paginate.page_entries_info.multi_page', { model: capitalize(I18n.model_name('journey_pattern', { plural: true })), from: firstItemOnPage, to: lastItemOnPage, count: pagination.totalCount})}              <form className='page_links' onSubmit={e => {                  e.preventDefault()                }}> diff --git a/app/javascript/packs/routes/edit.js b/app/javascript/packs/routes/edit.js index 81745ad23..fc7aa203d 100644 --- a/app/javascript/packs/routes/edit.js +++ b/app/javascript/packs/routes/edit.js @@ -29,6 +29,7 @@ const getInitialState = () => {      state.push({        stoppoint_id: v.stoppoint_id,        stoparea_id: v.stoparea_id, +      stoparea_kind: v.stoparea_kind,        user_objectid: v.user_objectid,        short_name: v.short_name ? v.short_name.replace("'", "\'") : '',        area_type: v.area_type, diff --git a/app/javascript/routes/actions/index.js b/app/javascript/routes/actions/index.js index 13b2d60b2..5fbf5bce9 100644 --- a/app/javascript/routes/actions/index.js +++ b/app/javascript/routes/actions/index.js @@ -56,7 +56,12 @@ const actions = {    unselectMarker: (index) => ({      type: 'UNSELECT_MARKER',      index -  }) +  }), +  defaultAttribute: (attribute, stopAreaKind) => { +    if (attribute !== '') return attribute +    if (stopAreaKind === undefined) return '' +    return stopAreaKind === "commercial" ? "normal" : "forbidden"  +  }   }  module.exports = actions diff --git a/app/javascript/routes/components/BSelect2.js b/app/javascript/routes/components/BSelect2.js index 035bce155..89e1b6cfa 100644 --- a/app/javascript/routes/components/BSelect2.js +++ b/app/javascript/routes/components/BSelect2.js @@ -17,6 +17,7 @@ export default class BSelect3 extends Component {      this.props.onChange(this.props.index, {        text: e.currentTarget.textContent,        stoparea_id: e.currentTarget.value, +      stoparea_kind: e.params.data.kind,        user_objectid: e.params.data.user_objectid,        longitude: e.params.data.longitude,        latitude: e.params.data.latitude, diff --git a/app/javascript/routes/components/StopPoint.js b/app/javascript/routes/components/StopPoint.js index af51a6bb4..368ec8261 100644 --- a/app/javascript/routes/components/StopPoint.js +++ b/app/javascript/routes/components/StopPoint.js @@ -4,6 +4,8 @@ import PropTypes from 'prop-types'  import BSelect2 from './BSelect2'  import OlMap from './OlMap' +import { defaultAttribute } from '../actions'  +  export default function StopPoint(props, {I18n}) {    return (      <div className='nested-fields'> @@ -17,14 +19,14 @@ export default function StopPoint(props, {I18n}) {          </div>          <div> -          <select className='form-control' value={props.value.for_boarding} id="for_boarding" onChange={props.onSelectChange}> +          <select className='form-control' value={defaultAttribute(props.value.for_boarding, props.value.stoparea_kind)} id="for_boarding" onChange={props.onSelectChange}>              <option value="normal">{I18n.t('routes.edit.stop_point.boarding.normal')}</option>              <option value="forbidden">{I18n.t('routes.edit.stop_point.boarding.forbidden')}</option>            </select>          </div>          <div> -          <select className='form-control' value={props.value.for_alighting} id="for_alighting" onChange={props.onSelectChange}> +          <select className='form-control' value={defaultAttribute(props.value.for_alighting, props.value.stoparea_kind)} id="for_alighting" onChange={props.onSelectChange}>              <option value="normal">{I18n.t('routes.edit.stop_point.alighting.normal')}</option>              <option value="forbidden">{I18n.t('routes.edit.stop_point.alighting.forbidden')}</option>            </select> diff --git a/app/javascript/routes/index.js b/app/javascript/routes/index.js index febae7d54..3c7322953 100644 --- a/app/javascript/routes/index.js +++ b/app/javascript/routes/index.js @@ -26,6 +26,7 @@ const getInitialState = () => {      state.push({        stoppoint_id: v.stoppoint_id,        stoparea_id: v.stoparea_id, +      stoparea_kind: v.stoparea_kind,        user_objectid: v.user_objectid,        short_name: v.short_name ? v.short_name.replace("'", "\'") : '',        area_type: v.area_type, @@ -36,8 +37,8 @@ const getInitialState = () => {        name: v.name ? v.name.replace("'", "\'") : '',        registration_number: v.registration_number,        text: fancyText, -      for_boarding: v.for_boarding || "normal", -      for_alighting: v.for_alighting || "normal", +      for_boarding: v.for_boarding || '', +      for_alighting: v.for_alighting || '',        longitude: v.longitude || 0,        latitude: v.latitude || 0,        comment: v.comment ? v.comment.replace("'", "\'") : '', diff --git a/app/javascript/routes/reducers/stopPoints.js b/app/javascript/routes/reducers/stopPoints.js index 0b42b504f..ba183d002 100644 --- a/app/javascript/routes/reducers/stopPoints.js +++ b/app/javascript/routes/reducers/stopPoints.js @@ -8,8 +8,8 @@ const stopPoint = (state = {}, action, length) => {          text: '',          index: length,          edit: true, -        for_boarding: 'normal', -        for_alighting: 'normal', +        for_boarding: '', +        for_alighting: '',          olMap: {            isOpened: false,            json: {} @@ -68,6 +68,7 @@ const stopPoints = (state = [], action) => {                stoppoint_id: t.stoppoint_id,                text: action.text.text,                stoparea_id: action.text.stoparea_id, +              stoparea_kind: action.text.stoparea_kind,                user_objectid: action.text.user_objectid,                latitude: action.text.latitude,                longitude: action.text.longitude, diff --git a/app/javascript/time_tables/actions/index.js b/app/javascript/time_tables/actions/index.js index 98b9eab4b..3127d11b8 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) => { 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/components/ConfirmModal.js b/app/javascript/vehicle_journeys/components/ConfirmModal.js index 75e8a3932..330b4e02f 100644 --- a/app/javascript/vehicle_journeys/components/ConfirmModal.js +++ b/app/javascript/vehicle_journeys/components/ConfirmModal.js @@ -16,7 +16,7 @@ export default function ConfirmModal({dispatch, modal, onModalAccept, onModalCan                type='button'                onClick={() => { onModalCancel(modal.confirmModal.callback) }}              > -              Ne pas valider +              {I18n.t('cancel')}            </button>              <button                className='btn btn-danger' @@ -24,7 +24,7 @@ export default function ConfirmModal({dispatch, modal, onModalAccept, onModalCan                type='button'                onClick={() => { onModalAccept(modal.confirmModal.callback, vehicleJourneys) }}              > -              Valider +              {I18n.t('actions.submit')}            </button>            </div>          </div> diff --git a/app/javascript/vehicle_journeys/components/Navigate.js b/app/javascript/vehicle_journeys/components/Navigate.js index 24843babc..e79823e49 100644 --- a/app/javascript/vehicle_journeys/components/Navigate.js +++ b/app/javascript/vehicle_journeys/components/Navigate.js @@ -1,5 +1,6 @@  import React, { Component } from 'react'  import PropTypes from 'prop-types' +import capitalize from 'lodash/capitalize'  import actions from'../actions'  export default function Navigate({ dispatch, vehicleJourneys, pagination, status, filters}) { @@ -17,7 +18,7 @@ export default function Navigate({ dispatch, vehicleJourneys, pagination, status    if(status.fetchSuccess == true) {      return (        <div className="pagination"> -        {I18n.t("vehicle_journeys.vehicle_journeys_matrix.pagination", {minVJ, maxVJ, total:pagination.totalCount})} +        {I18n.t('will_paginate.page_entries_info.multi_page', { model: capitalize(I18n.model_name('vehicle_journey', { plural: true })), from: minVJ, to: maxVJ, count: pagination.totalCount })}          <form className='page_links' onSubmit={e => {e.preventDefault()}}>            <button              onClick={e => { diff --git a/app/javascript/vehicle_journeys/components/tools/CreateModal.js b/app/javascript/vehicle_journeys/components/tools/CreateModal.js index a60429765..f49b51f08 100644 --- a/app/javascript/vehicle_journeys/components/tools/CreateModal.js +++ b/app/javascript/vehicle_journeys/components/tools/CreateModal.js @@ -47,7 +47,7 @@ export default class CreateModal extends Component {                <div className='modal-dialog'>                  <div className='modal-content'>                    <div className='modal-header'> -                    <h4 className='modal-title'>Ajouter une course</h4> +                    <h4 className='modal-title'>{I18n.t('vehicle_journeys.actions.new')}</h4>                      <span type="button" className="close modal-close" data-dismiss="modal">×</span>                    </div> @@ -57,7 +57,7 @@ export default class CreateModal extends Component {                          <div className='row'>                            <div className='col-lg-6 col-md-6 col-sm-6 col-xs-12'>                              <div className='form-group'> -                              <label className='control-label'>Nom de la course</label> +                              <label className='control-label'>{I18n.attribute_name('vehicle_journey', 'journey_name')}</label>                                <input                                  type='text'                                  ref='published_journey_name' @@ -68,7 +68,7 @@ export default class CreateModal extends Component {                            </div>                            <div className='col-lg-6 col-md-6 col-sm-6 col-xs-12'>                              <div className='form-group'> -                              <label className='control-label'>Nom du transporteur</label> +                              <label className='control-label'>{I18n.attribute_name('vehicle_journey', 'company_name')}</label>                                <CompanySelect2                                  company = {this.props.modal.modalProps.vehicleJourney && this.props.modal.modalProps.vehicleJourney.company || undefined}                                  onSelect2Company = {(e) => this.props.onSelect2Company(e)} @@ -78,7 +78,7 @@ export default class CreateModal extends Component {                            </div>                            <div className='col-lg-6 col-md-6 col-sm-6 col-xs-12'>                              <div className='form-group'> -                              <label className='control-label is-required'>Nom public de la mission</label> +                              <label className='control-label is-required'>{I18n.attribute_name('vehicle_journey', 'journey_pattern_published_name')}</label>                                <MissionSelect2                                  selection={this.props.modal.modalProps}                                  onSelect2JourneyPattern={this.props.onSelect2JourneyPattern} @@ -89,7 +89,7 @@ export default class CreateModal extends Component {                            </div>                            <div className='col-lg-6 col-md-6 col-sm-6 col-xs-12'>                              <div className='form-group'> -                              <label className='control-label'>Numéro de train</label> +                              <label className='control-label'>{I18n.attribute_name('vehicle_journey', 'published_journey_identifier')}</label>                                <input                                  type='text'                                  ref='published_journey_identifier' @@ -105,7 +105,7 @@ export default class CreateModal extends Component {                            />                            { this.props.modal.modalProps.selectedJPModal && this.props.modal.modalProps.selectedJPModal.full_schedule && <div className='col-lg-6 col-md-6 col-sm-6 col-xs-12'>                                <div className='form-group'> -                                <label className='control-label'>Heure de départ</label> +                              <label className='control-label'>{I18n.attribute_name('vehicle_journey', 'start_time')}</label>                                  <div className='input-group time'>                                    <input                                      type='number' @@ -142,14 +142,14 @@ export default class CreateModal extends Component {                            type='button'                            onClick={this.props.onModalClose}                            > -                          Annuler +                          {I18n.t('cancel')}                          </button>                          <button                            className='btn btn-primary'                            type='button'                            onClick={this.handleSubmit.bind(this)}                            > -                          Valider +                          {I18n.t('actions.submit')}                          </button>                        </div>                      </form> diff --git a/app/javascript/vehicle_journeys/components/tools/CustomFieldsInputs.js b/app/javascript/vehicle_journeys/components/tools/CustomFieldsInputs.js index 90d72a801..827c36b76 100644 --- a/app/javascript/vehicle_journeys/components/tools/CustomFieldsInputs.js +++ b/app/javascript/vehicle_journeys/components/tools/CustomFieldsInputs.js @@ -27,6 +27,19 @@ export default class CustomFieldsInputs extends Component {      )    } +  stringInput(cf){ +    return( +      <input +        type='text' +        ref={'custom_fields.' + cf.code} +        className='form-control' +        disabled={this.props.disabled} +        defaultValue={cf.value} +        onChange={(e) => this.props.onUpdate(cf.code, e.target.value) } +        /> +    ) +  } +    render() {      return (        <div> diff --git a/app/javascript/vehicle_journeys/components/tools/DeleteVehicleJourneys.js b/app/javascript/vehicle_journeys/components/tools/DeleteVehicleJourneys.js index 4815003d3..b1ce3786b 100644 --- a/app/javascript/vehicle_journeys/components/tools/DeleteVehicleJourneys.js +++ b/app/javascript/vehicle_journeys/components/tools/DeleteVehicleJourneys.js @@ -13,7 +13,7 @@ export default function DeleteVehicleJourneys({onDeleteVehicleJourneys, vehicleJ            e.preventDefault()            onDeleteVehicleJourneys()          }} -        title='Supprimer' +        title={ I18n.t('actions.delete') }        >          <span className='fa fa-trash'></span>        </button> diff --git a/app/javascript/vehicle_journeys/components/tools/DuplicateVehicleJourney.js b/app/javascript/vehicle_journeys/components/tools/DuplicateVehicleJourney.js index 102a87d85..d7e48bf08 100644 --- a/app/javascript/vehicle_journeys/components/tools/DuplicateVehicleJourney.js +++ b/app/javascript/vehicle_journeys/components/tools/DuplicateVehicleJourney.js @@ -93,7 +93,7 @@ export default class DuplicateVehicleJourney extends Component {                  <div className='modal-content'>                    <div className='modal-header'>                      <h4 className='modal-title'> -                      Dupliquer { actions.getSelected(this.props.vehicleJourneys).length > 1 ? 'plusieurs courses' : 'une course' } +                      {I18n.t('vehicle_journeys.vehicle_journeys_matrix.duplicate', { count: actions.getSelected(this.props.vehicleJourneys).length })}                      </h4>                      <span type="button" className="close modal-close" data-dismiss="modal">×</span>                    </div> @@ -102,7 +102,7 @@ export default class DuplicateVehicleJourney extends Component {                      <form className='form-horizontal'>                        <div className='modal-body'>                          <div className={'form-group ' + (actions.getSelected(this.props.vehicleJourneys).length > 1 ? 'hidden' : '' )}> -                          <label className='control-label is-required col-sm-8'>Horaire de départ indicatif</label> +                          <label className='control-label is-required col-sm-8'>{I18n.t('vehicle_journeys.vehicle_journeys_matrix.duplicate.start_time')}</label>                            <span className="col-sm-4">                              <span className={'input-group time' + (actions.getSelected(this.props.vehicleJourneys).length > 1 ? ' disabled' : '')}>                                <input @@ -133,7 +133,7 @@ export default class DuplicateVehicleJourney extends Component {                          </div>                          <div className='form-group'> -                          <label className='control-label is-required col-sm-8'>Nombre de courses à créer et dupliquer</label> +                          <label className='control-label is-required col-sm-8'>{I18n.t('vehicle_journeys.vehicle_journeys_matrix.duplicate.number')}</label>                            <div className="col-sm-4">                              <input                                type='number' @@ -152,7 +152,7 @@ export default class DuplicateVehicleJourney extends Component {                          </div>                          <div className='form-group'> -                          <label className='control-label is-required col-sm-8'>Décalage à partir duquel on créé les courses</label> +                          <label className='control-label is-required col-sm-8'>{I18n.t('vehicle_journeys.vehicle_journeys_matrix.duplicate.delta')}</label>                            <span className="col-sm-4">                              <input                                type='number' @@ -178,7 +178,7 @@ export default class DuplicateVehicleJourney extends Component {                            type='button'                            onClick={this.props.onModalClose}                            > -                          Annuler +                          {I18n.t('cancel')}                          </button>                          <button                            className={'btn btn-primary ' + (this.state.additional_time == 0 && this.state.originalDT.hour == this.state.duplicate_time_hh && this.state.originalDT.minute == this.state.duplicate_time_mm ? 'disabled' : '')} @@ -186,7 +186,7 @@ export default class DuplicateVehicleJourney extends Component {                            onClick={this.handleSubmit}                            disabled={this.disableValidateButton()}                            > -                          Valider +                          {I18n.t('actions.submit')}                          </button>                        </div>                      </form> diff --git a/app/javascript/vehicle_journeys/components/tools/EditVehicleJourney.js b/app/javascript/vehicle_journeys/components/tools/EditVehicleJourney.js index 795c2ecff..e4e266c79 100644 --- a/app/javascript/vehicle_journeys/components/tools/EditVehicleJourney.js +++ b/app/javascript/vehicle_journeys/components/tools/EditVehicleJourney.js @@ -173,14 +173,14 @@ export default class EditVehicleJourney extends Component {                              type='button'                              onClick={this.props.onModalClose}                            > -                            Annuler +                            {I18n.t('cancel')}                          </button>                            <button                              className='btn btn-primary'                              type='button'                              onClick={this.handleSubmit.bind(this)}                            > -                            Valider +                            {I18n.t('actions.submit')}                          </button>                          </div>                        } diff --git a/app/javascript/vehicle_journeys/components/tools/NotesEditVehicleJourney.js b/app/javascript/vehicle_journeys/components/tools/NotesEditVehicleJourney.js index 880542216..5d300f70c 100644 --- a/app/javascript/vehicle_journeys/components/tools/NotesEditVehicleJourney.js +++ b/app/javascript/vehicle_journeys/components/tools/NotesEditVehicleJourney.js @@ -43,11 +43,11 @@ export default class NotesEditVehicleJourney extends Component {    renderAssociatedFN() {      if (this.footnotes().associated.length == 0) { -      return <h3>Aucune note associée</h3> +      return <h3>{I18n.t('vehicle_journeys.vehicle_journeys_matrix.no_associated_footnotes')}</h3>      } else {        return (          <div> -          <h3>Notes associées :</h3> +          <h3>{I18n.t('vehicle_journeys.form.purchase_windows')} :</h3>            {this.footnotes().associated.map((lf, i) =>              <div                key={i} @@ -68,13 +68,13 @@ export default class NotesEditVehicleJourney extends Component {    }    renderToAssociateFN() { -    if (window.line_footnotes.length == 0) return <h3>La ligne ne possède pas de notes</h3> +    if (window.line_footnotes.length == 0) return <h3>{I18n.t('vehicle_journeys.vehicle_journeys_matrix.no_line_footnotes')}</h3>      if (this.footnotes().to_associate.length == 0) return false      return (        <div> -        <h3 className='mt-lg'>Sélectionnez les notes à associer à cette course :</h3> +        <h3 className='mt-lg'>{I18n.t('vehicle_journeys.vehicle_journeys_matrix.select_footnotes')} :</h3>          {this.footnotes().to_associate.map((lf, i) =>            <div key={i} className='panel panel-default'>              <div className='panel-heading'> @@ -111,7 +111,7 @@ export default class NotesEditVehicleJourney extends Component {                <div className='modal-dialog'>                  <div className='modal-content'>                    <div className='modal-header'> -                    <h4 className='modal-title'>Notes</h4> +                    <h4 className='modal-title'>{I18n.t('vehicle_journeys.form.footnotes')}</h4>                      <span type="button" className="close modal-close" data-dismiss="modal">×</span>                    </div> @@ -130,14 +130,14 @@ export default class NotesEditVehicleJourney extends Component {                              type='button'                              onClick={this.props.onModalClose}                            > -                            Annuler +                            {I18n.t('cancel')}                          </button>                            <button                              className='btn btn-primary'                              type='button'                              onClick={this.handleSubmit.bind(this)}                            > -                            Valider +                            {I18n.t('actions.submit')}                          </button>                          </div>                        } diff --git a/app/javascript/vehicle_journeys/components/tools/PurchaseWindowsEditVehicleJourney.js b/app/javascript/vehicle_journeys/components/tools/PurchaseWindowsEditVehicleJourney.js index ce9a4cde9..30c511302 100644 --- a/app/javascript/vehicle_journeys/components/tools/PurchaseWindowsEditVehicleJourney.js +++ b/app/javascript/vehicle_journeys/components/tools/PurchaseWindowsEditVehicleJourney.js @@ -44,7 +44,7 @@ export default class PurchaseWindowsEditVehicleJourney extends Component {                <div className='modal-dialog'>                  <div className='modal-content'>                    <div className='modal-header'> -                    <h4 className='modal-title'>Calendriers commerciaux associés</h4> +                    <h4 className='modal-title'>{I18n.t('vehicle_journeys.form.purchase_windows')}s</h4>                      <span type="button" className="close modal-close" data-dismiss="modal">×</span>                    </div> @@ -58,7 +58,7 @@ export default class PurchaseWindowsEditVehicleJourney extends Component {                                  <div className='wrapper'>                                    <div>                                      <div className='form-group'> -                                      <label className='control-label'>{this.props.modal.modalProps.purchase_windows.length == 0 ? "Aucun calendrier commercial associé" : "Calendriers commerciaux associés"}</label> +                                      <label className='control-label'>{this.props.modal.modalProps.purchase_windows.length == 0 ? I18n.t('vehicle_journeys.vehicle_journeys_matrix.no_associated_purchase_windows') : I18n.t('vehicle_journeys.form.purchase_windows')}</label>                                      </div>                                    </div>                                    <div></div> @@ -117,14 +117,14 @@ export default class PurchaseWindowsEditVehicleJourney extends Component {                              type='button'                              onClick={this.props.onModalClose}                            > -                            Annuler +                            {I18n.t('cancel')}                            </button>                            <button                              className='btn btn-primary'                              type='button'                              onClick={this.handleSubmit}                            > -                            Valider +                            {I18n.t('actions.submit')}                            </button>                          </div>                        } diff --git a/app/javascript/vehicle_journeys/components/tools/ShiftVehicleJourney.js b/app/javascript/vehicle_journeys/components/tools/ShiftVehicleJourney.js index 6574bfa2d..bc3d8db34 100644 --- a/app/javascript/vehicle_journeys/components/tools/ShiftVehicleJourney.js +++ b/app/javascript/vehicle_journeys/components/tools/ShiftVehicleJourney.js @@ -27,6 +27,7 @@ export default class ShiftVehicleJourney extends Component {    }    render() { +    let id = this.props.modal.type == 'shift' && actions.getSelected(this.props.vehicleJourneys)[0].short_id      if(this.props.status.isFetching == true) {        return false      } @@ -48,10 +49,7 @@ export default class ShiftVehicleJourney extends Component {                <div className='modal-dialog'>                  <div className='modal-content'>                    <div className='modal-header'> -                    <h4 className='modal-title'>Mettre à jour une course</h4> -                    {(this.props.modal.type == 'shift') && ( -                      <em>Mettre à jour les horaires de la course {actions.getSelected(this.props.vehicleJourneys)[0].short_id}</em> -                    )} +                    <h4 className='modal-title'>{I18n.t('vehicle_journeys.form.slide_title', {id: id})}</h4>                      <span type="button" className="close modal-close" data-dismiss="modal">×</span>                    </div> @@ -61,7 +59,7 @@ export default class ShiftVehicleJourney extends Component {                          <div className='row'>                            <div className='col-lg-4 col-lg-offset-4 col-md-4 col-md-offset-4 col-sm-4 col-sm-offset-4 col-xs-12'>                              <div className='form-group'> -                              <label className='control-label is-required'>Avec un décalage de</label> +                              <label className='control-label is-required'>{I18n.t('vehicle_journeys.form.slide_delta')}</label>                                <input                                  type='number'                                  style={{'width': 104}} @@ -85,14 +83,14 @@ export default class ShiftVehicleJourney extends Component {                            type='button'                            onClick={this.props.onModalClose}                            > -                          Annuler +                          {I18n.t('cancel')}                          </button>                          <button                            className={'btn btn-primary ' + (this.state.additional_time == 0 ? 'disabled' : '')}                            type='button'                            onClick={this.handleSubmit.bind(this)}                            > -                          Valider +                          {I18n.t('actions.submit')}                          </button>                        </div>                      </form> diff --git a/app/javascript/vehicle_journeys/components/tools/TimetablesEditVehicleJourney.js b/app/javascript/vehicle_journeys/components/tools/TimetablesEditVehicleJourney.js index f21480563..9ee2e1849 100644 --- a/app/javascript/vehicle_journeys/components/tools/TimetablesEditVehicleJourney.js +++ b/app/javascript/vehicle_journeys/components/tools/TimetablesEditVehicleJourney.js @@ -44,7 +44,7 @@ export default class TimetablesEditVehicleJourney extends Component {                <div className='modal-dialog'>                  <div className='modal-content'>                    <div className='modal-header'> -                    <h4 className='modal-title'>Calendriers associés</h4> +                    <h4 className='modal-title'>{I18n.t('vehicle_journeys.form.time_tables')}</h4>                      <span type="button" className="close modal-close" data-dismiss="modal">×</span>                    </div> @@ -58,7 +58,7 @@ export default class TimetablesEditVehicleJourney extends Component {                                  <div className='wrapper'>                                    <div>                                      <div className='form-group'> -                                      <label className='control-label'>{this.props.modal.modalProps.timetables.length == 0 ? "Aucun calendrier associé" : "Calendriers associés"}</label> +                                      <label className='control-label'>{this.props.modal.modalProps.timetables.length == 0 ? I18n.t('vehicle_journeys.vehicle_journeys_matrix.no_associated_timetables'): I18n.t('vehicle_journeys.form.time_tables')}</label>                                      </div>                                    </div>                                    <div></div> @@ -119,14 +119,14 @@ export default class TimetablesEditVehicleJourney extends Component {                              type='button'                              onClick={this.props.onModalClose}                            > -                            Annuler +                            {I18n.t('cancel')}                            </button>                            <button                              className='btn btn-primary'                              type='button'                              onClick={this.handleSubmit}                            > -                            Valider +                            {I18n.t('actions.submit')}                            </button>                          </div>                        } diff --git a/app/javascript/vehicle_journeys/components/tools/select2s/CompanySelect2.js b/app/javascript/vehicle_journeys/components/tools/select2s/CompanySelect2.js index 5c7f75d99..b7e9691c1 100644 --- a/app/javascript/vehicle_journeys/components/tools/select2s/CompanySelect2.js +++ b/app/javascript/vehicle_journeys/components/tools/select2s/CompanySelect2.js @@ -16,6 +16,7 @@ export default class BSelect4 extends Component {    }    render() { +    let placeHolder = I18n.t('')      return (        <Select2          data={(this.props.company) ? [this.props.company.name] : undefined} @@ -29,7 +30,7 @@ export default class BSelect4 extends Component {            allowClear: true,            theme: 'bootstrap',            width: '100%', -          placeholder: 'Filtrer par transporteur...', +          placeholder: I18n.t('vehicle_journeys.vehicle_journeys_matrix.affect_company'),            language: require('./fr'),            ajax: {              url: origin + path + '/companies.json' + '?line_id=' + line, diff --git a/app/javascript/vehicle_journeys/components/tools/select2s/MissionSelect2.js b/app/javascript/vehicle_journeys/components/tools/select2s/MissionSelect2.js index 72dbd0152..96b34125d 100644 --- a/app/javascript/vehicle_journeys/components/tools/select2s/MissionSelect2.js +++ b/app/javascript/vehicle_journeys/components/tools/select2s/MissionSelect2.js @@ -74,7 +74,7 @@ export default class BSelect4 extends Component {        width: '100%',        escapeMarkup: function (markup) { return markup; },        templateResult: formatRepo, -      placeholder: 'Filtrer par code, nom ou OID de mission...', +      placeholder: I18n.t('vehicle_journeys.vehicle_journeys_matrix.filters.journey_pattern'),        language: require('./fr'),        allowClear: false,        escapeMarkup: function (markup) { return markup; }, diff --git a/app/javascript/vehicle_journeys/components/tools/select2s/TimetableSelect2.js b/app/javascript/vehicle_journeys/components/tools/select2s/TimetableSelect2.js index 0339455ca..9a345b464 100644 --- a/app/javascript/vehicle_journeys/components/tools/select2s/TimetableSelect2.js +++ b/app/javascript/vehicle_journeys/components/tools/select2s/TimetableSelect2.js @@ -26,7 +26,7 @@ export default class BSelect4 extends Component {            allowClear: false,            theme: 'bootstrap',            width: '100%', -          placeholder: 'Filtrer par calendrier...', +          placeholder: I18n.t('vehicle_journeys.vehicle_journeys_matrix.filters.timetable'),            language: require('./fr'),            ajax: {              url: origin + path + this.props.chunkURL, diff --git a/app/javascript/vehicle_journeys/components/tools/select2s/VJSelect2.js b/app/javascript/vehicle_journeys/components/tools/select2s/VJSelect2.js index ccb4c9595..f5881cef7 100644 --- a/app/javascript/vehicle_journeys/components/tools/select2s/VJSelect2.js +++ b/app/javascript/vehicle_journeys/components/tools/select2s/VJSelect2.js @@ -25,7 +25,7 @@ export default class BSelect4b extends Component {          options={{            allowClear: false,            theme: 'bootstrap', -          placeholder: 'Filtrer par ID course...', +          placeholder: I18n.t('vehicle_journeys.vehicle_journeys_matrix.filters.id'),            width: '100%',            language: require('./fr'),            ajax: { diff --git a/app/models/chouette/company.rb b/app/models/chouette/company.rb index 53e412600..8d6dbee92 100644 --- a/app/models/chouette/company.rb +++ b/app/models/chouette/company.rb @@ -3,6 +3,8 @@ module Chouette      include CompanyRestrictions      include LineReferentialSupport      include ObjectidSupport +    include CustomFieldsSupport +      has_paper_trail class_name: 'PublicVersion'      has_many :lines 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/line.rb b/app/models/chouette/line.rb index ae7c25377..c8a02da1f 100644 --- a/app/models/chouette/line.rb +++ b/app/models/chouette/line.rb @@ -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/stop_area.rb b/app/models/chouette/stop_area.rb index ccdff609f..c263fa987 100644 --- a/app/models/chouette/stop_area.rb +++ b/app/models/chouette/stop_area.rb @@ -7,6 +7,7 @@ module Chouette      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 27407243b..da2da998a 100644 --- a/app/models/chouette/stop_point.rb +++ b/app/models/chouette/stop_point.rb @@ -30,13 +30,6 @@ module Chouette      delegate :name, to: :stop_area -    after_create :set_defaults -    def set_defaults -      value = stop_area.kind == 'commercial' ? 'normal' : 'forbidden' -      update_attribute :for_boarding, value -      update_attribute :for_alighting, value -    end -      before_destroy :remove_dependent_journey_pattern_stop_points      def remove_dependent_journey_pattern_stop_points        route.journey_patterns.each do |jp| diff --git a/app/models/compliance_control.rb b/app/models/compliance_control.rb index 537343005..1cc06f927 100644 --- a/app/models/compliance_control.rb +++ b/app/models/compliance_control.rb @@ -76,6 +76,7 @@ require_dependency 'generic_attribute_control/uniqueness'  require_dependency 'journey_pattern_control/duplicates'  require_dependency 'journey_pattern_control/vehicle_journey'  require_dependency 'line_control/route' +require_dependency 'line_control/lines_scope'  require_dependency 'route_control/duplicates'  require_dependency 'route_control/journey_pattern'  require_dependency 'route_control/minimum_length' diff --git a/app/models/compliance_control_block.rb b/app/models/compliance_control_block.rb index d7d84fd06..bc5d6fd4a 100644 --- a/app/models/compliance_control_block.rb +++ b/app/models/compliance_control_block.rb @@ -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/concerns/custom_fields_support.rb b/app/models/concerns/custom_fields_support.rb index 6c76bd653..ef6b4f4af 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_*/ && !@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/custom_field.rb b/app/models/custom_field.rb index 402df7fa9..8347d84f9 100644 --- a/app/models/custom_field.rb +++ b/app/models/custom_field.rb @@ -2,15 +2,19 @@ class CustomField < ActiveRecord::Base    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/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/line_control/lines_scope.rb b/app/models/line_control/lines_scope.rb new file mode 100644 index 000000000..4210a10dd --- /dev/null +++ b/app/models/line_control/lines_scope.rb @@ -0,0 +1,8 @@ +module LineControl +  class LinesScope < ComplianceControl + +    def self.default_code; "3-Line-2" end + +    def prerequisite; I18n.t("compliance_controls.#{self.class.name.underscore}.prerequisite") end +  end +end diff --git a/app/models/line_referential.rb b/app/models/line_referential.rb index 0d2ed39b1..89700c06f 100644 --- a/app/models/line_referential.rb +++ b/app/models/line_referential.rb @@ -1,7 +1,7 @@  class LineReferential < ActiveRecord::Base    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..dcada25bf 100644 --- a/app/models/line_referential_membership.rb +++ b/app/models/line_referential_membership.rb @@ -1,4 +1,6 @@  class LineReferentialMembership < ActiveRecord::Base    belongs_to :organisation    belongs_to :line_referential + +  validates :organisation_id, presence: true, uniqueness: { scope: :line_referential }  end diff --git a/app/models/organisation.rb b/app/models/organisation.rb index 745bc0d22..5bef67941 100644 --- a/app/models/organisation.rb +++ b/app/models/organisation.rb @@ -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/referential.rb b/app/models/referential.rb index 91a88d02d..0e48be43f 100644 --- a/app/models/referential.rb +++ b/app/models/referential.rb @@ -233,7 +233,7 @@ class Referential < ActiveRecord::Base      end    end -  def self.new_from(from, functional_scope) +  def self.new_from(from, organisation)      Referential.new(        name: I18n.t("activerecord.copy", name: from.name),        slug: "#{from.slug}_clone", @@ -244,7 +244,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_metadata.rb b/app/models/referential_metadata.rb index 393dc70d3..017eb1449 100644 --- a/app/models/referential_metadata.rb +++ b/app/models/referential_metadata.rb @@ -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/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/stop_area_referential.rb b/app/models/stop_area_referential.rb index a9d3cc9b1..4706cdd77 100644 --- a/app/models/stop_area_referential.rb +++ b/app/models/stop_area_referential.rb @@ -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..fbed1c004 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    belongs_to :organisation    belongs_to :stop_area_referential + +  validates :organisation_id, presence: true, uniqueness: { scope: :stop_area_referential }  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_stop_areas/around.rabl b/app/views/autocomplete_stop_areas/around.rabl index d067dc4d0..116038639 100644 --- a/app/views/autocomplete_stop_areas/around.rabl +++ b/app/views/autocomplete_stop_areas/around.rabl @@ -15,6 +15,7 @@ child @stop_areas, root: :features, object_root: false do        area_type: Chouette::AreaType.find(s.area_type).label,        registration_number: s.registration_number,        stoparea_id: s.id, +      stoparea_kind: s.kind,        text: "#{s.name}, #{s.zip_code} #{s.city_name}",        user_objectid: s.user_objectid,        zip_code: s.zip_code, diff --git a/app/views/autocomplete_stop_areas/index.rabl b/app/views/autocomplete_stop_areas/index.rabl index c92b708f4..786f942d6 100644 --- a/app/views/autocomplete_stop_areas/index.rabl +++ b/app/views/autocomplete_stop_areas/index.rabl @@ -15,7 +15,8 @@ node do |stop_area|    :latitude => stop_area.latitude,    :area_type => Chouette::AreaType.find(stop_area.area_type).label,    :comment => stop_area.comment, -  :text => stop_area.full_name +  :text => stop_area.full_name, +  :kind => stop_area.kind    }  end diff --git a/app/views/autocomplete_stop_areas/show.rabl b/app/views/autocomplete_stop_areas/show.rabl index 73ce277cf..6ebf38900 100644 --- a/app/views/autocomplete_stop_areas/show.rabl +++ b/app/views/autocomplete_stop_areas/show.rabl @@ -9,7 +9,8 @@ node do |stop_area|    :short_name => truncate(stop_area.name, :length => 30) || "",    :zip_code => stop_area.zip_code || "",    :city_name => stop_area.city_name || "", -  :short_city_name => truncate(stop_area.city_name, :length => 15) || "" +  :short_city_name => truncate(stop_area.city_name, :length => 15) || "", +  :kind => stop_area.kind    }  end diff --git a/app/views/calendars/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/dashboards/_dashboard.html.slim b/app/views/dashboards/_dashboard.html.slim index 2f0791f50..e1be3df4a 100644 --- a/app/views/dashboards/_dashboard.html.slim +++ b/app/views/dashboards/_dashboard.html.slim @@ -5,7 +5,7 @@          .panel-heading            h3.panel-title.with_actions              div -              = link_to workbench.name, workbench_path(workbench) +              = link_to t('dashboards.workbench.title', organisation: workbench.organisation.name), workbench_path(workbench)                span.badge.ml-xs = workbench.referentials.count if workbench.referentials.present?              div @@ -23,6 +23,7 @@          .panel-heading            h3.panel-title.with_actions              = link_to I18n.t("activerecord.models.calendar", count: workbench.calendars.size), workgroup_calendars_path(workbench.workgroup) +            span.badge.ml-xs = workbench.calendars.count if workbench.calendars.present?              div                = link_to '', workgroup_calendars_path(workbench.workgroup), class: ' fa fa-chevron-right pull-right'          - if workbench.calendars.present? @@ -39,7 +40,7 @@        - @dashboard.current_organisation.stop_area_referentials.each do |referential|          .panel-heading            h3.panel-title -            = referential.name +            = t('dashboards.stop_area_referentials.title')          .list-group            = link_to Chouette::StopArea.model_name.human.pluralize.capitalize, stop_area_referential_stop_areas_path(referential), class: 'list-group-item' @@ -47,7 +48,7 @@        - @dashboard.current_organisation.line_referentials.all.each do |referential|          .panel-heading            h3.panel-title -            = referential.name +            = t('dashboards.line_referentials.title')          .list-group              = link_to Chouette::Line.model_name.human.pluralize.capitalize, line_referential_lines_path(referential), class: 'list-group-item'              = link_to Chouette::Company.model_name.human.pluralize.capitalize, line_referential_companies_path(referential), class: 'list-group-item' diff --git a/app/views/layouts/navigation/_nav_panel_operations.html.slim b/app/views/layouts/navigation/_nav_panel_operations.html.slim index 8dce829cd..1c5a1f14b 100644 --- a/app/views/layouts/navigation/_nav_panel_operations.html.slim +++ b/app/views/layouts/navigation/_nav_panel_operations.html.slim @@ -1,5 +1,5 @@  #operations_panel.nav_panel    .panel-title -    h2 Opérations +    h2 = t('layouts.operations')    .panel-body      p = "Lorem ipsum dolor sit amet..." diff --git a/app/views/layouts/navigation/_nav_panel_profile.html.slim b/app/views/layouts/navigation/_nav_panel_profile.html.slim index bcbf89e67..b0dee5d53 100644 --- a/app/views/layouts/navigation/_nav_panel_profile.html.slim +++ b/app/views/layouts/navigation/_nav_panel_profile.html.slim @@ -1,6 +1,6 @@  #profile_panel.nav_panel    .panel-title -    h2 Mon Profil +    h2 = t('layouts.user.profile')    .panel-body      p = current_user.name      p = current_organisation.name diff --git a/app/views/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/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..3550bb202 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 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/stif/dashboards/_dashboard.html.slim b/app/views/stif/dashboards/_dashboard.html.slim index e0f754fd4..7538c7fc7 100644 --- a/app/views/stif/dashboards/_dashboard.html.slim +++ b/app/views/stif/dashboards/_dashboard.html.slim @@ -1,8 +1,4 @@  .row -  .col-lg-12 -    h2.content_header = t('.subtitle') - -.row    .col-lg-6.col-md-6.col-sm-6.col-xs-12      .panel.panel-default        .panel-heading diff --git a/app/views/stop_areas/_form.html.slim b/app/views/stop_areas/_form.html.slim index 1cba88f94..00f2ad8bb 100644 --- a/app/views/stop_areas/_form.html.slim +++ b/app/views/stop_areas/_form.html.slim @@ -48,7 +48,7 @@            - if has_feature?(:stop_area_waiting_time)              = f.input :waiting_time, input_html: { min: 0 } -          = f.input :registration_number, required: stop_area_registration_number_is_required(f.object), :input_html => {title: stop_area_registration_number_title(f.object), value: stop_area_registration_number_value(f.object)} +          = f.input :registration_number, required: stop_area_registration_number_is_required(f.object), :input_html => {title: stop_area_registration_number_title(f.object), value: stop_area_registration_number_value(f.object)}, hint: stop_area_registration_number_hint            = f.input :fare_code            = f.input :nearest_topic_name, :input_html => {:title => t("formtastic.titles#{format_restriction_for_locales(@referential)}.stop_area.nearest_topic_name")}            = f.input :comment, as: :text, :input_html => {:rows => 5, :title => t("formtastic.titles#{format_restriction_for_locales(@referential)}.stop_area.comment")} @@ -62,6 +62,12 @@              = f.input :stairs_availability, as: :select, :collection => [[t("true"), true], [t("false"), false]], :include_blank => true              = f.input :lift_availability, as: :select, :collection => [[t("true"), true], [t("false"), false]], :include_blank => true +        - if resource.custom_fields(resource.stop_area_referential.workgroup).any? +          .custom_fields +            h3 = t("stop_areas.stop_area.custom_fields") +            - resource.custom_fields(resource.stop_area_referential.workgroup).each do |code, field| +              = field.input(f).to_s +    .separator    = f.button :submit, t('actions.submit'), class: 'btn btn-default formSubmitr', form: 'stop_area_form' diff --git a/app/views/stop_areas/autocomplete.rabl b/app/views/stop_areas/autocomplete.rabl index a5f0bd5ec..26fca36b2 100644 --- a/app/views/stop_areas/autocomplete.rabl +++ b/app/views/stop_areas/autocomplete.rabl @@ -15,7 +15,8 @@ node do |stop_area|      :latitude                  => stop_area.latitude,      :area_type                 => stop_area.area_type,      :comment                   => stop_area.comment, -    :text                      => "<span class='small label label-info'>#{I18n.t("area_types.label.#{stop_area.area_type}")}</span>#{stop_area.full_name}" +    :text                      => "<span class='small label label-info'>#{I18n.t("area_types.label.#{stop_area.area_type}")}</span>#{stop_area.full_name}", +    :kind                      => stop_area.kind    }  end diff --git a/app/views/stop_areas/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..c92fb7bae 100644 --- a/app/views/vehicle_journeys/index.html.slim +++ b/app/views/vehicle_journeys/index.html.slim @@ -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/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/compliance_check_messages.en.yml b/config/locales/compliance_check_messages.en.yml index 216a363a3..88841f308 100644 --- a/config/locales/compliance_check_messages.en.yml +++ b/config/locales/compliance_check_messages.en.yml @@ -22,10 +22,11 @@ en:      3_routingconstraint_2: "The Routing Constraint Zone %{source_objectid} covers all the stop points of its related route : %{target_0_objectid}."      3_routingconstraint_3: "The Routing Constraint Zone %{source_objectid} has less than 2 stop points"      3_line_1: "On line :%{source_label} (%{source_objectid}), no route has an opposite route" +    3_line_2: "The line %{source_label} (%{source_objectid}) is not in the lines scope of the organization %{reference_value}"      3_generic_1: "%{source_objectid} : the %{source_attribute} attribute value (%{error_value}) does not respect the following pattern : %{reference_value}"      3_generic_2_1: "%{source_objectid}  : the %{source_attribute} attributes's value (%{error_value}) is greater than the authorized maximum value : %{reference_value}"      3_generic_2_2: "%{source_objectid}  : the %{source_attribute} attributes's value (%{error_value}) is smaller than the authorized minimum value %{reference_value}"      3_generic_3: "%{source_objectid}  : the %{source_attribute} attribute (%{error_value}) has a value shared with : %{target_0_objectid}"      3_shape_1: "Tracé %{source_objectid} : le tracé passe trop loin de l'arrêt %{target_0_label} (%{target_0_objectid}) : %{error_value} > %{reference_value}"      3_shape_2: "Tracé %{source_objectid} : le tracé n'est pas défini entre les arrêts %{target_0_label} (%{target_0_objectid}) et %{target_1_label} (%{target_1_objectid})" -    3_shape_3: "Le tracé de l'itinéraire %{source_objectid} est en écart avec la voirie sur %{error_value} sections"
\ No newline at end of file +    3_shape_3: "Le tracé de l'itinéraire %{source_objectid} est en écart avec la voirie sur %{error_value} sections" diff --git a/config/locales/compliance_check_messages.fr.yml b/config/locales/compliance_check_messages.fr.yml index db127d236..167ef411a 100644 --- a/config/locales/compliance_check_messages.fr.yml +++ b/config/locales/compliance_check_messages.fr.yml @@ -22,10 +22,11 @@ fr:      3_routingconstraint_2: "L'ITL %{source_objectid} couvre tous les arrêts de l'itinéraire %{target_0_objectid}."      3_routingconstraint_3: "L'ITL %{source_objectid} n'a pas suffisament d'arrêts (minimum 2 arrêts requis)"      3_line_1: "Sur la ligne %{source_label} (%{source_objectid}), aucun itinéraire n'a d'itinéraire inverse" +    3_line_2: "La ligne %{source_label} (%{source_objectid}) ne fait pas partie du périmètre de lignes de l'organisation %{reference_value}"      3_generic_1: "%{source_objectid} : l'attribut %{source_attribute} a une valeur %{error_value} qui ne respecte pas le motif %{reference_value}"      3_generic_2_1: "%{source_objectid} : l'attribut %{source_attribute} a une valeur %{error_value} supérieure à la valeur maximale autorisée %{reference_value}"      3_generic_2_2: "%{source_objectid} : l'attribut %{source_attribute} a une valeur %{error_value} inférieure à la valeur minimale autorisée %{reference_value}"      3_generic_3: "%{source_objectid} : l'attribut %{source_attribute} a une valeur %{error_value} partagée avec %{target_0_objectid}"      3_shape_1: "Tracé %{source_objectid} : le tracé passe trop loin de l'arrêt %{target_0_label} (%{target_0_objectid}) : %{error_value} > %{reference_value}"      3_shape_2: "Tracé %{source_objectid} : le tracé n'est pas défini entre les arrêts %{target_0_label} (%{target_0_objectid}) et %{target_1_label} (%{target_1_objectid})" -    3_shape_3: "Le tracé de l'itinéraire %{source_objectid} est en écart avec la voirie sur %{error_value} sections"
\ No newline at end of file +    3_shape_3: "Le tracé de l'itinéraire %{source_objectid} est en écart avec la voirie sur %{error_value} sections" diff --git a/config/locales/compliance_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/compliance_controls.en.yml b/config/locales/compliance_controls.en.yml index d8dc44ecc..18069f2f7 100644 --- a/config/locales/compliance_controls.en.yml +++ b/config/locales/compliance_controls.en.yml @@ -142,6 +142,11 @@ en:          3_line_1: "On line :%{source_label} (%{source_objectid}), no route has an opposite route"        description: "The routes of a line must have an opposite route"        prerequisite: Line has multiple routes +    line_control/lines_scope: +      messages: +        3_line_2: "The line %{source_label} (%{source_objectid}) is not in the lines scope of the organization %{reference_value}" +      description: "The line must be included in the lines scope of the organization" +      prerequisite: "None"      generic_attribute_control/pattern:        messages:          3_generic_1: "%{source_objectid} : the %{source_attribute} attribute value (%{error_value}) does not respect the following pattern : %{reference_value}" @@ -209,6 +214,8 @@ en:          one: "Unactivated stop points"        line_control/route:          one: "The routes of a line must have an opposite route" +      line_control/lines_scope: +        one: "Lines must be included in the lines scope of the organization"        generic_attribute_control/pattern:          one: "Attribute pattern of an object in a line"        generic_attribute_control/min_max: diff --git a/config/locales/compliance_controls.fr.yml b/config/locales/compliance_controls.fr.yml index 78b92451f..7dc6eeeb3 100644 --- a/config/locales/compliance_controls.fr.yml +++ b/config/locales/compliance_controls.fr.yml @@ -139,6 +139,11 @@ fr:          3_line_1: "Sur la ligne %{source_label} (%{source_objectid}), aucun itinéraire n'a d'itinéraire inverse"        description: "Les itinéraires d'une ligne doivent être associés en aller/retour"        prerequisite: Ligne disposant de plusieurs itinéraires +    line_control/lines_scope: +      messages: +        3_line_2: "La ligne %{source_label} (%{source_objectid}) ne fait pas partie du périmètre de lignes de l'organisation %{reference_value}" +      description: "Les lignes doivent appartenir au périmètre de lignes de l'organisation" +      prerequisite: "Aucun"      generic_attribute_control/pattern:        messages:          3_generic_1: "%{source_objectid} : l'attribut %{source_attribute} a une valeur %{error_value} qui ne respecte pas le motif %{reference_value}" @@ -206,6 +211,8 @@ fr:          one: "ITL & arret désactivé"        line_control/route:          one: "Appariement des itinéraires" +      line_control/lines_scope: +        one: "Les lignes doivent appartenir au périmètre de lignes de l'organisation"        generic_attribute_control/pattern:          one: "Contrôle du contenu selon un pattern"        generic_attribute_control/min_max: diff --git a/config/locales/dashboard.en.yml b/config/locales/dashboard.en.yml index 8d46ff7aa..361a3cf2b 100644 --- a/config/locales/dashboard.en.yml +++ b/config/locales/dashboard.en.yml @@ -2,6 +2,8 @@ en:    dashboards:      show:        title: "Dashboard %{organisation}" +    workbench: +      title: Transport offer %{organisation}      calendars:        title: Calendars        none: No calendar created diff --git a/config/locales/dashboard.fr.yml b/config/locales/dashboard.fr.yml index d0aa36d61..1e1c095b1 100644 --- a/config/locales/dashboard.fr.yml +++ b/config/locales/dashboard.fr.yml @@ -2,6 +2,8 @@ fr:    dashboards:      show:        title: "Tableau de bord %{organisation}" +    workbench: +      title: Offre de transport %{organisation}      calendars:        title: Modèles de calendrier        none: Aucun calendrier défini diff --git a/config/locales/journey_patterns.en.yml b/config/locales/journey_patterns.en.yml index d480e144d..70ae94dd9 100644 --- a/config/locales/journey_patterns.en.yml +++ b/config/locales/journey_patterns.en.yml @@ -1,6 +1,7 @@  en:    journey_patterns:      journey_pattern: +      fetching_error: "There has been a problem fetching the data. Please reload the page to try again."        from_to: "From '%{departure}' to '%{arrival}'"        stop_count: "%{count}/%{route_count} stops"        vehicle_journeys_count: "Vehicle journeys: %{count}" @@ -19,6 +20,13 @@ en:      show:        title: "Journey Pattern %{journey_pattern}"        stop_points: "Stop point on journey pattern list" +      stop_points_count:  +        none: '%{count} stop areas' +        one: '%{count} stop area' +        other: '%{count} stop areas' +      informations: Informations +      confirmation: Confimation +      confirm_page_change: You are about to change page. Would you like to save your work before that ?      index:        title: "Journey Patterns of %{route}"      form: @@ -50,7 +58,8 @@ en:          creator_id: "Created by"          full_journey_time: Full journey          commercial_journey_time: Commercial journey - +        stop_points: Nb stop areas +        checksum: Checksum    formtastic:      titles:        journey_pattern: diff --git a/config/locales/journey_patterns.fr.yml b/config/locales/journey_patterns.fr.yml index 32c1f3f97..10653a02d 100644 --- a/config/locales/journey_patterns.fr.yml +++ b/config/locales/journey_patterns.fr.yml @@ -1,6 +1,7 @@  fr:    journey_patterns:      journey_pattern: +      fetching_error: "La récupération des courses a rencontré un problème. Rechargez la page pour tenter de corriger le problème."        from_to: "De '%{departure}' à '%{arrival}'"        stop_count: "%{count}/%{route_count} arrêts"        vehicle_journeys_count: "Courses: %{count}" @@ -19,6 +20,13 @@ fr:      show:        title: "Mission %{journey_pattern}"        stop_points: "Liste des arrêts de la mission" +      stop_points_count:  +        none: '%{count} arrêt' +        one: '%{count} arrêt' +        other: '%{count} arrêts' +      informations: Informations +      confirmation: Confimation +      confirm_page_change: Vous vous apprêtez à changer de page. Voulez-vous valider vos modifications avant cela ?      index:        title: "Missions de %{route}"      form: @@ -50,6 +58,8 @@ fr:          creator_id: "Créé par"          full_journey_time: Parcours complet          commercial_journey_time: Parcours commercial +        stop_points: Nb arrêts +        checksum: Signature métier    formtastic:      titles:        journey_pattern: diff --git a/config/locales/layouts.en.yml b/config/locales/layouts.en.yml index debff05e5..d5717b400 100644 --- a/config/locales/layouts.en.yml +++ b/config/locales/layouts.en.yml @@ -3,6 +3,7 @@ en:      back_to_dashboard: "Back to Dashboard"      help: "Help"      home: "Home" +    operations: Operations      user:        profile: "My Profile"        sign_out: "Sign out" diff --git a/config/locales/layouts.fr.yml b/config/locales/layouts.fr.yml index 5e835bcf7..17d23c756 100644 --- a/config/locales/layouts.fr.yml +++ b/config/locales/layouts.fr.yml @@ -3,6 +3,7 @@ fr:      back_to_dashboard: "Retour au Tableau de Bord"      help: "Aide"      home: "Accueil" +    operations: Opérations      user:        profile: "Mon Profil"        sign_out: "Déconnexion" diff --git a/config/locales/stop_areas.en.yml b/config/locales/stop_areas.en.yml index 37d39b76c..ddb2d940c 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" @@ -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/vehicle_journey_exports.en.yml b/config/locales/vehicle_journey_exports.en.yml index 93a782026..4e658353e 100644 --- a/config/locales/vehicle_journey_exports.en.yml +++ b/config/locales/vehicle_journey_exports.en.yml @@ -1,7 +1,7 @@  en:    vehicle_journey_exports:      new: -      title: "Export existing vehicle journey at stops" +      title: Vehicle journeys export        basename: "vehicle_journeys"      label:        vehicle_journey_id: "vj id (empty for new vj)" diff --git a/config/locales/vehicle_journeys.en.yml b/config/locales/vehicle_journeys.en.yml index f1ba96dc5..79e805ee8 100644 --- a/config/locales/vehicle_journeys.en.yml +++ b/config/locales/vehicle_journeys.en.yml @@ -1,14 +1,29 @@  en:    vehicle_journeys:      vehicle_journeys_matrix: +      filters: +        id: Filter by ID... +        timetable: Filter by journey pattern... +        timetable: Filter by timetable...        cancel_selection: "Cancel Selection"        fetching_error: "There has been a problem fetching the data. Please reload the page to try again."        line_routes: "Line's routes"        modal_confirm: 'Do you want to save mofications before moving on to the next page ?' -      pagination: "Schedules %{minVJ} to %{maxVJ} over %{total}"        selected_journeys: "%{count} selected journeys"        show_purchase_window: 'Show the purchase window'        show_timetable: 'Show calendar' +      no_associated_timetables: No associated timetables +      no_associated_purchase_windows: No associated purchase windows +      no_associated_footnotes: No associated footnotes +      duplicate: +        one: Clone %{count} vehicle journey +        other: Clone %{count} vehicle journeys +        start_time: Indicative start time +        number: Number of vehicle journeys to create and clone +        delta: Delta from which vehicle journeys are created +      affect_company: Affect company +      no_line_footnotes: The line doesn't have any footnotes +      select_footnotes: Select footnotes to associate with the vehicle journey      vehicle_journey:        title_stopless: "Vehicle journey %{name}"        title: "Vehicle journey leaving from %{stop} at %{time}" @@ -17,10 +32,10 @@ en:        title: "Vehicle journey frequency leaving from %{stop} at %{time}"        title_frequency: "Vehicle journey frequency with %{interval}min leaving from %{stop} at %{time_first} to %{time_last}"      actions: -      index: "Vehicle time's board" -      new: "Add a new timed vehicle journey" +      index: "Vehicle journeys" +      new: "Add a new vehicle journey"        new_frequency: "Add a new frequency vehicle journey" -      edit: "Edit this timed vehicle journey" +      edit: "Edit this vehicle journey"        edit_frequency: "Edit this frequency vehicle journey"        destroy: "Remove this vehicle journey"        destroy_confirm: "Are you sure you want destroy this vehicle journey?" @@ -48,15 +63,18 @@ en:        show_journeys_without_schedule: "Show journeys without schedule"        slide_arrival: "arrival time at first stop"        slide_departure: "departure time at first stop" -      slide_title: "Shift all vehicle passing times" +      slide_title: "Shift all the vehicle journey passing times : %{id}"        slide: "Shift" +      slide_delta: "Shift of"        starting_stop: "Departure"        stop_title: "Stop"        submit_frequency_edit: "Edit frequency vehicle journey"        submit_frequency: "Create frequency vehicle journey"        submit_timed_edit: "Edit vehicle journey"        submit_timed: "Create vehicle journey" -      time_tables: "Associated calendars to vehicle journey" +      time_tables: "Associated timetables" +      purchase_windows: Associated purchase windows +      footnotes: Associated footnotes        to_arrivals: "Copy departures to arrivals"        to_departures: "Copy arrivals to departures"        to: "at" @@ -102,6 +120,7 @@ en:          checksum: "Checksum"          comment: "Comments"          company: "Company" +        company_name: "Company name"          created_at: Created at          creator_id: "Created by"          departure_time: "Departure" @@ -110,9 +129,10 @@ en:          footnote_ids: "Footnotes"          id: "Journey ID"          journey_frequency_ids: "Timeband" -        journey_name: "Name of the journey" +        journey_name: "Name of the vehicle journey"          journey_pattern_id: "Pattern ID"          journey_pattern: "Journey Pattern" +        journey_pattern_published_name: "Journey Pattern published name"          line: "Line"          mobility_restricted_suitability: "PRM accessibility"          name: "Journey Name" @@ -137,6 +157,7 @@ en:          updated_at: Updated at          vehicle_journey_at_stop_ids: "Time list"          vehicle_type_identifier: "Vehicle Type Identifier" +        start_time: Start time      errors:        models:          vehicle_journey: diff --git a/config/locales/vehicle_journeys.fr.yml b/config/locales/vehicle_journeys.fr.yml index d144e580f..466eca684 100644 --- a/config/locales/vehicle_journeys.fr.yml +++ b/config/locales/vehicle_journeys.fr.yml @@ -1,14 +1,29 @@  fr:    vehicle_journeys:      vehicle_journeys_matrix: +      filters: +        id: Filtrer par ID course... +        journey_pattern: 'Filtrer par code, nom ou OID de mission...' +        timetable: Filtrer par calendrier...        cancel_selection: "Annuler la sélection"        fetching_error: "La récupération des missions a rencontré un problème. Rechargez la page pour tenter de corriger le problème."        line_routes: "Séquences d'arrêts de la ligne"        modal_confirm: 'Voulez-vous valider vos modifications avant de changer de page?' -      pagination: "Liste des horaires %{minVJ} à %{maxVJ} sur %{total}"        selected_journeys: "%{count} course(s) sélectionnée(s)"        show_purchase_window: 'Voir le calendrier commercial'        show_timetable: 'Voir le calendrier' +      no_associated_timetables: Aucun calendrier associé +      no_associated_purchase_windows: Aucun calendrier commercial associé +      no_associated_footnotes: Aucune note associée +      duplicate: +        one: Dupliquer %{count} course +        other: Dupliquer %{count} courses +        start_time: Horaire de départ indicatif +        number: Nombre de courses à créer et dupliquer +        delta: Décalage à partir duquel on créé les courses +      affect_company: Indiquez un nom de transporteur... +      no_line_footnotes: La ligne ne possède pas de notes +      select_footnotes: Sélectionnez les notes à associer à cette course      vehicle_journey:        title_stopless: "Course %{name}"        title: "Course partant de %{stop} à %{time}" @@ -48,8 +63,9 @@ fr:        show_journeys_without_schedule: "Afficher les courses sans horaires"        slide_arrival: "horaire d'arrivée au 1° arrêt à"        slide_departure: "horaire de départ au 1° arrêt à" -      slide_title: "Décaler l'ensemble des horaires de course" +      slide_title: "Décaler l'ensemble des horaires de la course : %{id}"        slide: "Décaler" +      slide_delta: "Avec un décalage de"        starting_stop: "Origine"        stop_title: "Arrêt"        submit_frequency_edit: "Editer course en fréquence" @@ -57,6 +73,8 @@ fr:        submit_timed_edit: "Editer course"        submit_timed: "Créer course"        time_tables: "Calendriers associés à la course" +      purchase_windows: "Calendriers commerciaux associés à la course" +      footnotes: "Notes associés à la course"        to_arrivals: "Copie départs vers arrivées"        to_arrivals: "Copie départs vers arrivées"        to_departures: "Copie arrivées vers départs" @@ -103,6 +121,7 @@ fr:          checksum: "Signature métier"          comment: "Commentaires"          company: "Transporteur" +        company_name: "Nom du transporteur"          created_at: "Créé le"          creator_id: "Créé par"          departure_time: "Départ" @@ -114,6 +133,7 @@ fr:          journey_name: "Nom de la course"          journey_pattern_id: "ID Mission"          journey_pattern: "Mission" +        journey_pattern_published_name: "Nom public de la mission"          line: "Ligne"          mobility_restricted_suitability: "Accessibilité PMR"          name: "Nom Course" @@ -129,7 +149,7 @@ fr:          route: "Itinéraire"          time_slot: "Fréquence"          time_table_ids: "Liste des calendriers" -        time_tables: "Calendriers" +        time_tables: "Calendriers associés"          train_number: "Numéro de train"          transport_mode: "Mode de transport"          transport_submode: "Sous-mode de transport" @@ -138,6 +158,7 @@ fr:          updated_at: "Edité le"          vehicle_journey_at_stop_ids: "Liste des horaires"          vehicle_type_identifier: "Type d'identifiant du véhicule" +        start_time: Heure de départ      errors:        models:          vehicle_journey: diff --git a/config/locales/will_paginate.en.yml b/config/locales/will_paginate.en.yml index 29b8fe2bf..8f3189675 100644 --- a/config/locales/will_paginate.en.yml +++ b/config/locales/will_paginate.en.yml @@ -32,11 +32,11 @@ en:        single_page:          zero:  "No item found"          one:   "1 %{model} shown" -        other: "%{model} 1 to %{count} of %{count}" +        other: "%{model} 1 to %{count} out of %{count}"        single_page_html:          zero:  "No item found"          one:   "1 %{model} shown" -        other: "%{model} 1 to %{count} of %{count}" +        other: "%{model} 1 to %{count} out of %{count}" -      multi_page: "%{model} %{from} to %{to} of %{count}" -      multi_page_html: "%{model} %{from} to %{to} of %{count}" +      multi_page: "%{model} list %{from} to %{to} out of %{count}" +      multi_page_html: "%{model} list %{from} to %{to} out of %{count}" diff --git a/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/schema.rb b/db/schema.rb index d90bf7b6c..0f6f21b83 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -141,6 +141,7 @@ ActiveRecord::Schema.define(version: 20180319043333) do      t.text     "import_xml"      t.datetime "created_at"      t.datetime "updated_at" +    t.json     "custom_field_values"    end    add_index "companies", ["line_referential_id"], name: "index_companies_on_line_referential_id", using: :btree @@ -842,10 +843,8 @@ 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"    end    add_index "stop_areas", ["name"], name: "index_stop_areas_on_name", using: :btree diff --git a/db/seeds/seed_helpers.rb b/db/seeds/seed_helpers.rb new file mode 100644 index 000000000..8e47e10bd --- /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_create_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..bb73b0b9c 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_netex" +  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/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/factories/custom_fields.rb b/spec/factories/custom_fields.rb index 7c43a6147..db7a3823e 100644 --- a/spec/factories/custom_fields.rb +++ b/spec/factories/custom_fields.rb @@ -3,7 +3,7 @@ FactoryGirl.define do      code "code"      resource_type "VehicleJourney"      sequence(:name){|n| "custom field ##{n}"} -    field_type "list" +    field_type "integer"      options( { capacity: "0" } )    end  end diff --git a/spec/features/calendars_permissions_spec.rb b/spec/features/calendars_permissions_spec.rb index 4857592d5..656c0dd78 100644 --- a/spec/features/calendars_permissions_spec.rb +++ b/spec/features/calendars_permissions_spec.rb @@ -1,8 +1,8 @@  RSpec.describe 'Calendars', type: :feature do    login_user -  let(:calendar) { create :calendar, organisation_id: 1 } -  let(:workgroup) { calendar.workgroup } +  let(:calendar) { create :calendar, organisation: first_organisation, workgroup: first_workgroup } +  let(:workgroup) { first_workgroup }    describe 'permissions' do      before do diff --git a/spec/features/calendars_spec.rb b/spec/features/calendars_spec.rb new file mode 100644 index 000000000..26220746b --- /dev/null +++ b/spec/features/calendars_spec.rb @@ -0,0 +1,16 @@ +RSpec.describe 'Calendars', type: :feature do +  login_user + +  let(:calendar1)        { create(:calendar, workgroup: @user.organisation.workgroups.first, organisation: @user.organisation) } +  let(:calendar2)        { create(:calendar) } + +  describe "index" do +    before(:each) do +      visit workgroup_calendars_path(calendar1.workgroup) +    end +    it "should only display calendars from same workgroup" do +      expect(page).to have_content calendar1.name +      expect(page).to_not have_content calendar2.name +    end +  end +end
\ No newline at end of file diff --git a/spec/features/compliance_control_sets_spec.rb b/spec/features/compliance_control_sets_spec.rb index 0f4597db3..306f363a5 100644 --- a/spec/features/compliance_control_sets_spec.rb +++ b/spec/features/compliance_control_sets_spec.rb @@ -12,7 +12,7 @@ RSpec.describe "ComplianceControlSets", type: :feature do    let(:other_control_cset) { create :compliance_control_set, organisation: other_orga }    let(:blox){ -    2.times.map{ | _ | create :compliance_control_block, compliance_control_set: control_set } +    2.times.map{ |n| create :compliance_control_block, compliance_control_set: control_set, transport_mode: StifTransportModeEnumerations.transport_modes[n], transport_submode: StifTransportSubmodeEnumerations.transport_submodes[n] }    }    before do diff --git a/spec/fixtures/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/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/chouette/route/route_stop_points_spec.rb b/spec/models/chouette/route/route_stop_points_spec.rb index f8edadfee..af26f017a 100644 --- a/spec/models/chouette/route/route_stop_points_spec.rb +++ b/spec/models/chouette/route/route_stop_points_spec.rb @@ -86,24 +86,6 @@ RSpec.describe Chouette::Route, :type => :model do          end        end      end - -    context 'defaults attributes' do -      let(:new_stop_area) { create :stop_area, kind: 'non_commercial', area_type: 'deposit' } -      let(:new_stop_point) { create :stop_point, stop_area_id: new_stop_area.id } -      it 'should have the correct default attributes' do -        subject.stop_points << new_stop_point -        subject.stop_points.each do |sp| -          # binding.pry -          if sp.stop_area.commercial? -            expect(sp.for_boarding).to eq('normal') -            expect(sp.for_alighting).to eq('normal') -          else -            expect(sp.for_boarding).to eq('forbidden') -            expect(sp.for_alighting).to eq('forbidden') -          end -        end -      end -    end    end    describe "#stop_areas" do diff --git a/spec/models/chouette/vehicle_journey_spec.rb b/spec/models/chouette/vehicle_journey_spec.rb index c69655bd4..7292f09f9 100644 --- a/spec/models/chouette/vehicle_journey_spec.rb +++ b/spec/models/chouette/vehicle_journey_spec.rb @@ -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 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/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/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/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 | 
